ToolValidator 与 autoApprove:执行前的两道闸门
职责
工具真正跑起来之前有两道独立的检查:ToolValidator 管参数和路径合法性,AutoApprove 管要不要弹审批框。两者都不执行工具,只决定「能不能跑」和「要不要问用户」。
ToolValidator 是个非常薄的工具类,给 handler 用。它提供两个方法:assertRequiredParams 检查必需参数有没有,checkClineIgnorePath 检查路径有没有被 .clineignore 拦掉 (assertRequiredParams:17)。它本身没有副作用,只返回 ValidationResult 让 handler 决定怎么处理。
AutoApprove 稍微复杂一点,它是 ToolExecutor 私有字段,被 asToolConfig 注入到 TaskConfig 里 (autoApprover init:126)。handler 通过 config.callbacks.shouldAutoApproveTool 或 config.callbacks.shouldAutoApproveToolWithPath 间接调它 (callback wiring:181)。它读三个全局开关:yoloModeToggled、autoApproveAllToggled、autoApprovalSettings,加上路径在不在 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第一次取后缓存到workspacePathsCache和isMultiRootScenarioCache(getWorkspaceInfo:23)。因为 task 生命周期内 workspace 根目录基本不变,缓存省掉重复 IPC 调用。
关键文件
ToolValidator:10— 类定义,构造接ClineIgnoreController。assertRequiredParams:17— 遍历参数名,空值或纯空格返回错误。checkClineIgnorePath:32— 调clineIgnoreController.validateAccess,被拦返回错误。AutoApprove:8— 类定义,持有stateManager和两个缓存字段。getWorkspaceInfo:23— 缓存的 workspace 路径和多根判定。shouldAutoApproveTool:42— 三档开关:YOLO、autoApproveAll、细粒度 autoApprovalSettings。yolo branch:43— YOLO 模式下只读 / 文件操作返回[true, true],浏览器/web/MCP 返回true。autoApproveAll branch:66— 跟 YOLO 同范围,但作为次级开关。autoApprovalSettings:88— 细粒度分支,按 actions.readFiles / editFiles / executeSafeCommands 等细分。shouldAutoApproveToolWithPath:122— 带路径的版本,判定本地 / 外部后跟 auto-approve 元组拼。multi-root workspace check:138— 多根场景用isLocatedInWorkspace检查任意根。final decision:163—local && autoApproveLocal或external && autoApproveLocal && autoApproveExternal才放行。ToolExecutor delegations:48— ToolExecutor 把自己绑成 callback,转发给autoApprover。complete block approval:205— 完整块走shouldAutoApproveToolWithPath决定 auto-approval 还是 manualask("tool", ...)。partial block approval:74— 流式 partial 块也走同一套 auto-approve 检查,边流边决定要不要弹框。
数据流
handler 拿到 block 后,典型流程是先用 Validator 校参数,再用 AutoApprove 决定走哪条 UI 路径。下面是 WriteToFileToolHandler 在 partial block 阶段的实际调用:
// 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 返回的元组拼出最终决策:
// 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 的默认安全姿态:外部操作永远比本地严格一档。
边界与失败
- 空字符串也算缺失:
assertRequiredParams用String(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 等,没列
ASK、ATTEMPT、NEW_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。