命令与文件读取工具:ExecuteCommand / ReadFile / SearchFiles / ListFiles
职责
这一页讲 Cline 的四个「能力类」只读/执行工具,都注册在 IFullyManagedTool 接口下、由 ToolExecutor 统一调度:
- ExecuteCommandToolHandler(
ClineDefaultTool.BASH):在终端跑一条 shell 命令,带超时、多工作区、权限校验(class declaration:58)。 - ReadFileToolHandler(
ClineDefaultTool.FILE_READ):读单个文件,加 1-based 行号、支持 start_line/end_line 切片、对图片文件返回 image block(class declaration:142)。 - SearchFilesToolHandler(
ClineDefaultTool.SEARCH):基于 ripgrep 的正则搜索,支持跨多工作区并行搜、file_pattern 过滤(class declaration:21)。 - ListFilesToolHandler(
ClineDefaultTool.LIST_FILES):列目录,支持递归,200 条上限后截断(class declaration:17)。
这四个工具共享一套交互模式:handlePartialBlock 提前推送 UI 让用户看到「模型正在读 X」,execute 做校验(clineignore + 路径 + 参数)→ 审批 → PreToolUse hook → 真正执行 → 遥测。它们都尊重 config.isSubagentExecution:子 agent 执行时跳过部分 UI 推送和 ask,直接干活。
它们和 WriteToFileToolHandler 的根本差异是「只读或外部副作用」——读、列、搜不改变文件系统状态;execute_command 改变状态但通过 terminal 走,不进 diff 视图,所以这套工具没有 diff / userEdits 回流路径。
设计动机
- mistake 计数统一规则:每个工具都在缺参数或 clineignore 命中时
consecutiveMistakeCount++,在核心操作成功后= 0,让 YOLO 模式的连错限额能跨工具累计(reset counter:377)。 - 命令分类超时:execute_command 把常见长跑命令(npm install、cargo build、pytest 等)匹配出 300 秒超时,其他 30 秒,避免短命令被长超时拖住(
LONG_RUNNING patterns:22-34)。 - 命令权限双闸:
CLINE_COMMAND_PERMISSIONS环境变量做的 segment 级白名单 + clineignore 路径校验,两层独立拒绝(permission checks:162-190)。 - 读文件去重缓存:
fileReadCache只存元数据(readCount + mtime + imageBlock),不存内容,命中且 mtime 没变就直接返回「已读过」提示,防止模型烧 token 反复读同一文件(dedup cache:298-314)。 - 行号化展示:
formatFileContentWithLineNumbers给每行加N |前缀并附 continuation 提示,让模型用 start_line 精确续读(formatFileContentWithLineNumbers:79)。 - 多工作区并行搜:SearchFilesToolHandler 在多工作区模式下把 searchPaths 全部并行
Promise.all跑,再按 workspace 名分桶汇总(parallel search:279-285)。 - ListFiles 200 上限:listFiles 第三参 limit=200,超出后返回 didHitLimit=true,formatFilesList 末尾附提示让模型用更具体的 path 缩小范围(
listFiles call:100)。
关键文件
LONG_RUNNING patterns:22-34— 长跑命令的正则数组,npm/cargo/pytest/docker build/torchrun 等。timeout resolution:36-56—isLikelyLongRunningCommand+resolveCommandTimeoutSeconds,优先用模型给的 timeout 参数。ExecuteCommand class:58—name = ClineDefaultTool.BASH,handlePartialBlock 在 auto-approve 时等完整 block 再 say。workspace hint:135-159— 命令前缀@workspace:command解析,把 executionDir 改到对应 workspace 根。permission checks:162-190—commandPermissionController.validateCommand+clineIgnoreController.validateCommand。approval flow:223-279— auto-approve 需!requiresApprovalPerLLM && autoApproveSafe或 risky 命令的autoApproveSafe && autoApproveAll双闸。cache clear:319-327— 命令执行后整体fileReadCache.clear(),因为 sed/git checkout/mv 等可能改文件不可预测。formatFileContentWithLineNumbers:79— 切片、加行号、附「Showing lines X-Y of Z」提示。ReadFile class:142—name = ClineDefaultTool.FILE_READ。dedup cache:298-356— mtime 比较 + readCount 累加,第 3 次起返回 DUPLICATE READ 警告。extract + cache:359-373— 调extractFileContent(图片返回 imageBlock),失败时 tool error 而不抛错给上层。determineSearchPaths:35— 多工作区下按 hint 或全工作区展开搜索路径。executeSearch:74— 调regexSearchFiles(ripgrep),用 clineIgnoreController 过滤结果。formatSearchResults:120— 多工作区时按 workspace 名分桶,单工作区直接返回。listFiles call:100—listFiles(absolutePath, recursive, 200)返回 [files, didHitLimit]。
数据流
ExecuteCommand 是这四个里最复杂的,核心是审批闸门与超时解析。模型给的 requires_approval + command 字符串决定走哪条审批路径:
// apps/vscode/src/core/task/tools/handlers/ExecuteCommandToolHandler.ts
timeoutSeconds = resolveCommandTimeoutSeconds(
command,
timeoutParam,
config.yoloModeToggled || config.vscodeTerminalExecutionMode === "backgroundExec",
)
// ...
if (
config.isSubagentExecution ||
(!requiresApprovalPerLLM && autoApproveSafe) ||
(requiresApprovalPerLLM && autoApproveSafe && autoApproveAll)
) {
// Auto-approve flow
await config.callbacks.removeLastPartialMessageIfExistsWithType("ask", "command")
await config.callbacks.say("command", actualCommand, undefined, undefined, false)
didAutoApprove = true
} else {
// Manual approval flow
const didApprove = await ToolResultUtils.askApprovalAndPushFeedback(
"command",
actualCommand + `${autoApproveSafe && requiresApprovalPerLLM ? COMMAND_REQ_APP_STRING : ""}`,
config,
)
if (!didApprove) {
return formatResponse.toolDenied()
}
}autoApprover.shouldAutoApproveTool 返回 [autoApproveSafe, autoApproveAll] 二元组:前者是「safe 命令自动批」,后者是「all 命令自动批」。模型说 risky(requires_approval=true)且只配了 safe 自动批时,走手动审批且在命令后追加强审批提示串(auto-approve logic:223-227)。审批通过后 PreToolUse hook、然后 30 秒通知定时器(只在自动批 + 通知开时设),最后 executeCommandTool(finalCommand, timeoutSeconds) 在 terminal 跑。完成后整个 fileReadCache 清空。
ReadFileToolHandler 的核心是缓存去重:
// apps/vscode/src/core/task/tools/handlers/ReadFileToolHandler.ts
const cacheKey = absolutePath.toLowerCase()
const cached = config.taskState.fileReadCache.get(cacheKey)
if (cached) {
try {
const stat = await import("node:fs/promises").then((fs) => fs.stat(absolutePath))
if (stat.mtimeMs !== cached.mtime) {
config.taskState.fileReadCache.delete(cacheKey)
}
} catch {
config.taskState.fileReadCache.delete(cacheKey)
}
}mtime 变了(用户在编辑器里手改了)就 evict,否则复用缓存。缓存里不存内容省内存,命中后仍然从盘上重读。readCount 到 3 次以上返回 DUPLICATE READ 强提示。新读成功后 extractFileContent 拿内容(图片文件返回 imageBlock 推到 userMessageContent),把 mtime + readCount=1 存进缓存。
SearchFiles 和 ListFiles 都做路径解析 + clineignore + 审批 + 执行 + 格式化,代码结构相似。SearchFiles 多了多工作区并行:
// apps/vscode/src/core/task/tools/handlers/SearchFilesToolHandler.ts
const searchPromises = searchPaths.map(({ absolutePath, workspaceName, workspaceRoot }) =>
this.executeSearch(config, absolutePath, workspaceName, workspaceRoot, regex, filePattern),
)
const searchResults = await Promise.all(searchPromises)
const results = this.formatSearchResults(config, searchResults, searchPaths)regexSearchFiles 调底层 ripgrep,结果按 workspace 名分桶汇总。
边界与失败
- 缺参数:execute_command 缺 command / requires_approval,read/search/list 缺 path,search 缺 regex 都
consecutiveMistakeCount+++ 走sayAndCreateMissingParamError(missing params:100-112)。 - 命令被拒:
CLINE_COMMAND_PERMISSIONS拒时返回permissionDeniedError,带 failedSegment 或 matchedPattern 给模型可修正信息(permission denied:162-181)。 - 长跑命令通知:auto-approve + 通知开时,30 秒后弹系统通知「Command is still running」,避免用户以为卡死(
timeout notification:294-303)。 - 读文件不存在:
extractFileContent抛错被 catch 成toolError("Error reading file: ..."),不向 Task 上层抛,让模型看错误自己改路径(read error:361-373)。 - 缓存 mtime 不一致:stat 失败或 mtime 变了都 evict 缓存,保证下次读到最新内容(
mtime check:304-313)。 - search path 解析失败:
parseWorkspaceInlinePath抛错时返回Error resolving search path: ...,mistake++(path resolution error:233-242)。 - list files 超限:
listFiles抛或路径不对返回Error listing files: ...,成功后 didHitLimit 由formatFilesList转成提示(list error:101-105)。 - 子 agent 跳过 UI:四个 handler 都在
isSubagentExecution时跳过 partial say 和 ask 流程,直接干完返回(subagent skip:155-157)。
小结
这四个 handler 是 Cline 的「眼睛和手」:ExecuteCommand 跑 shell,ReadFile 读盘,SearchFiles 跑 ripgrep,ListFiles 列目录。它们共享「校验 → 审批 → hook → 执行 → 遥测」的骨架,差异在执行细节:execute_command 有命令分类超时和双闸审批,read_file 有 mtime 去重缓存,search 支持多工作区并行,list 有 200 上限。所有工具的 consecutiveMistakeCount 在成功后归零,失败累计到 YOLO 上限就停。
想往里钻可以接着看:
- 写盘工具:
/edit-tools/write-to-file - 多文件补丁:
/edit-tools/apply-patch - 浏览器工具:
/cap-tools/browser - Web fetch/search:
/cap-tools/web