Skip to content

命令與檔案讀取工具:ExecuteCommand / ReadFile / SearchFiles / ListFiles

源码版本v4.0.10

職責

這一頁講 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)。

關鍵檔案

資料流

ExecuteCommand 是這四個裡最複雜的,核心是審批閘門與超時解析。模型給的 requires_approval + command 字串決定走哪條審批路徑:

typescript
// 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 的核心是快取去重:

typescript
// 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 多了多工作區並行:

typescript
// 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

對照官方資料:Cline 文件 · README