命令與檔案讀取工具: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