Skip to content

ToolValidator 与 autoApprove:执行前的两道闸门

源码版本v4.0.10

职责

工具真正跑起来之前有两道独立的检查:ToolValidator 管参数和路径合法性,AutoApprove 管要不要弹审批框。两者都不执行工具,只决定「能不能跑」和「要不要问用户」。

ToolValidator 是个非常薄的工具类,给 handler 用。它提供两个方法:assertRequiredParams 检查必需参数有没有,checkClineIgnorePath 检查路径有没有被 .clineignore 拦掉 (assertRequiredParams:17)。它本身没有副作用,只返回 ValidationResult 让 handler 决定怎么处理。

AutoApprove 稍微复杂一点,它是 ToolExecutor 私有字段,被 asToolConfig 注入到 TaskConfig 里 (autoApprover init:126)。handler 通过 config.callbacks.shouldAutoApproveToolconfig.callbacks.shouldAutoApproveToolWithPath 间接调它 (callback wiring:181)。它读三个全局开关:yoloModeToggledautoApproveAllToggledautoApprovalSettings,加上路径在不在 workspace 内,给出「本地自动批准」「外部自动批准」「不批准」三档结果。

设计动机

  • Validator 与 AutoApprove 分离:合法性检查和审批是两件正交的事——参数合法但用户没批准,或者参数非法但用户已批准,都可能出现。拆成两个对象让 handler 能在错误路径上不绕开审批、在审批通过后还能拦下非法参数。
  • Validator 只看不改:返回 ValidationResult 而不是抛错,handler 自己决定是 push 错误结果还是给个降级路径 (ValidationResult:4)。这样不同 handler 可以共享同一种错误展示方式。
  • AutoApprove 的三档开关:yolo 一切放行、autoApproveAll 范围次之、autoApprovalSettings 最细粒度 (区分本地/外部、安全命令/全部命令) (shouldAutoApproveTool:42)。从宽到严的开关优先级在前者。
  • 路径分类决定外部访问:读 / 写工具的 auto-approve 结果是 [local, external] 元组 (tuple result:96)。local 控制工作区内文件,external 控制工作区外文件。默认工作区外更严格,避免误改系统文件。
  • workspace info 缓存到 task 范围:getWorkspaceInfo 第一次取后缓存到 workspacePathsCacheisMultiRootScenarioCache (getWorkspaceInfo:23)。因为 task 生命周期内 workspace 根目录基本不变,缓存省掉重复 IPC 调用。

关键文件

数据流

handler 拿到 block 后,典型流程是先用 Validator 校参数,再用 AutoApprove 决定走哪条 UI 路径。下面是 WriteToFileToolHandler 在 partial block 阶段的实际调用:

typescript
// apps/vscode/src/core/task/tools/handlers/WriteToFileToolHandler.ts
// Handle auto-approval vs manual approval for partial
if (await uiHelpers.shouldAutoApproveToolWithPath(block.name, relPath)) {
    await uiHelpers.removeLastPartialMessageIfExistsWithType("ask", "tool") // in case the user changes auto-approval settings mid stream
    await uiHelpers.say("tool", partialMessage, undefined, undefined, block.partial)
} else {
    await uiHelpers.removeLastPartialMessageIfExistsWithType("say", "tool")
    await uiHelpers.ask("tool", partialMessage, block.partial).catch(() => {})
}

shouldAutoApproveToolWithPath 内部先看 YOLO / autoApproveAll 两个总开关,任何一个为真直接返回 true (yolo short circuit:126)。再算路径在不在 workspace 内,跟 shouldAutoApproveTool 返回的元组拼出最终决策:

typescript
// apps/vscode/src/core/task/tools/autoApprove.ts
const autoApproveResult = this.shouldAutoApproveTool(blockname)
const [autoApproveLocal, autoApproveExternal] = Array.isArray(autoApproveResult)
    ? autoApproveResult
    : [autoApproveResult, false]

if ((isLocalRead && autoApproveLocal) || (!isLocalRead && autoApproveLocal && autoApproveExternal)) {
    return true
}
return false

这里 autoApproveExternal 只有在 autoApproveLocal 也为真时才被检查——意味着「外部自动批准」隐含「本地也自动批准」。这是 Cline 的默认安全姿态:外部操作永远比本地严格一档。

边界与失败

  • 空字符串也算缺失:assertRequiredParamsString(val).trim() === "" 判断,空串或纯空格都算缺失 (empty check:21)。但 content 字段允许空字符串 (新建空文件),所以 handler 用 == null 自行判断,不走 Validator (content null check:42)。
  • 无路径默认不批:shouldAutoApproveToolWithPath 拿到 undefined 路径时直接设 isLocalRead = false (no path default:153)。安全侧的默认值,工具拿不到路径就当外部操作处理。
  • 缓存假设 workspace 不变:注释明说「assumes that the task has a fixed set of workspace roots」(cache assumption:11)。如果用户在 task 跑到一半加新根目录,这个 task 拿到的还是旧缓存,要等下一个 task 才生效。
  • YOLO 不覆盖所有工具:YOLO 模式 switch 里只列了读 / 写 / bash / browser / web / MCP 等,没列 ASKATTEMPTNEW_TASK 这些 (yolo switch list:44)。这类工具默认 fallthrough 到 return false,需要正常审批。
  • YOLO 元组只对 file/bash 类生效:[true, true] 元组只给 read/write/bash/subagents (yolo tuple:55)。browser/web/MCP 拿到的是 true 单值,意味着这俩在 YOLO 下不区分本地 / 外部。
  • Validator 不被旧 ToolExecutor 使用:类注释明说「The legacy ToolExecutor switch remains unchanged and does not depend on this」(legacy note:8)。旧路径里参数检查是散落在各 case 里的,Validator 只服务新 handler。

小结

Validator 是参数 / 路径的硬检查,失败就报错让模型重试;AutoApprove 是用户偏好驱动的软开关,决定走 auto-approval 还是弹框。两者一起在工具执行前充当守门员,handler 调它们之后才决定下一步动作。要看 handler 列表怎么注册,转 /tools/handlers-overview;要看路由本身,转 /tools/coordinator

对照官方资料:Cline 文档 · README