Skip to content

recursivelyMakeClineRequests:遞迴驅動器

源码版本v4.0.10

職責

recursivelyMakeClineRequests 是 Task 類別裡那台「每遞迴一次 = 一次完整 LLM 呼叫 + 工具執行」的核心驅動器,定義在 apps/vscode/src/core/task/index.tsTask 類別裡。它接收上一輪累積的 userContent (包含工具結果、使用者回饋、noToolsUsed 提示等),呼叫 attemptApiRequest 拿流,把流交給 StreamChunkCoordinator 分發,流結束後等所有 block 被 presentAssistantMessage 處理完,然後用這一輪累積的 taskState.userMessageContent 再呼叫自己一次。整個 agent loop 的「回合」概念就在這個遞迴裡。

它在三層巢狀迴圈的中間。最外層是 initiateTaskLoop,用 while (!abort) 兜底「模型只回文本不呼叫工具」的情況,塞個 noToolsUsed 提示再進遞迴 (initiateTaskLoop:1717)。中間就是 recursivelyMakeClineRequests 自己,每呼叫一次代表一次 LLM API 請求。最內層是 presentAssistantMessage,被流回呼或 scheduler 反覆叫醒推進 block。recursivelyMakeClineRequests 在結尾 await this.recursivelyMakeClineRequests(this.taskState.userMessageContent) 再呼叫自己 (recurse:3830),工具結果天然就是下一輪的 user content,不需要額外編排。

設計動機

  • 遞迴而非 while 迴圈:每一輪 LLM 回應後,工具結果就是下一輪的 user content,所以函式結尾自然 await this.recursivelyMakeClineRequests(this.taskState.userMessageContent) (recurse:3830)。這種寫法把「單輪 API 呼叫 + 工具執行 + 狀態重置」只寫一遍,呼叫堆疊深度自然反映回合數,出錯時堆疊可讀。
  • mistake 限額優先檢查:函式開頭立刻檢查 consecutiveMistakeCount >= maxConsecutiveMistakes (mistake limit check:2826),YOLO 模式直接 return true 終止任務,否則 ask("mistake_limit_reached") 讓使用者決定。這避免模型在死迴圈裡燒 token。
  • 流式狀態全量重置:每輪開始都把 currentStreamingContentIndexassistantMessageContentuserMessageContentdidRejectToolpresentAssistantMessageLocked 等十來個欄位清零 (reset streaming state:3302),保證本輪不受上一輪殘留影響。重置也覆蓋 streamHandler.reset()presentationScheduler.reset()
  • StreamChunkCoordinator 分流:不直接 for await 流,而是包一層 StreamChunkCoordinator (stream coordinator:3368),把流分成 reasoning / text / usage 三種 chunk 分別回呼。reasoning 走 reasoning handler,text 走 parseAssistantMessageV2 重解析,usage 累加 token 計數。
  • pWaitFor userMessageContentReady:流結束後不立刻遞迴,而是 await pWaitFor(() => this.taskState.userMessageContentReady) (pWaitFor ready:3808),等 presentAssistantMessage 把所有 block 處理完。這保證工具結果都累積進 userMessageContent 後才進下一輪。
  • noToolsUsed 自增 mistake:如果整輪 assistant 沒有任何 tool_use block,把 formatResponse.noToolsUsed 文本塞進 userMessageContent 並 consecutiveMistakeCount++ (noToolsUsed:3818)。下一輪模型會被提示「要麼呼叫工具要麼 attempt_completion」,連續不呼叫工具會被 mistake 限額終止。
  • 空回應走錯誤路徑:整輪 assistant 既沒 text 也沒 tool_use,記 empty_assistant_message 遙測,say 一條 error,然後 ask("api_req_failed") 讓使用者決定是否重試 (empty response:3834),不讓任務無聲繼續。

關鍵檔案

資料流

每次進入 recursivelyMakeClineRequests,先做 mistake 限額檢查和遠端 workspace 偵測,然後重置流式狀態、啟動流。下面這段是流式狀態重置 + 流啟動的核心:

typescript
// apps/vscode/src/core/task/index.ts
// reset streaming state
this.taskState.currentStreamingContentIndex = 0;
this.taskState.assistantMessageContent = [];
this.taskState.didCompleteReadingStream = false;
this.taskState.userMessageContent = [];
this.taskState.userMessageContentReady = false;
this.taskState.didRejectTool = false;
this.taskState.didAlreadyUseTool = false;
this.taskState.presentAssistantMessageLocked = false;
this.taskState.presentAssistantMessageHasPendingUpdates = false;
this.taskState.didAutomaticallyRetryFailedApiRequest = false;
await this.diffViewProvider.reset();
this.streamHandler.reset();
this.presentationScheduler.reset();
this.taskState.toolUseIdMap.clear();

const { toolUseHandler, reasonsHandler } =
    this.streamHandler.getHandlers();
const stream = this.attemptApiRequest(previousApiReqIndex);

這段在 reset streaming state:3302 附近。重置完後,StreamChunkCoordinator 接管流 (stream coordinator:3368),while (true) 迴圈從 coordinator 拿 chunk。text chunk 進 assistantMessage += chunk.text; assistantTextOnly += chunk.text; 然後立刻 parseAssistantMessageV2(assistantMessage) 重解析整段 (parseAssistantMessageV2 call:3509),長度增加就 scheduleAssistantPresentationpresentAssistantMessage 推進新 block。reasoning chunk 走 reasoning handler 增量更新 thinking 訊息。usage chunk 累加 token 計數和 cost。流結束後,processNativeToolCalls 處理原生 tool call,flushAssistantPresentationOrThrow 把殘留 partial block 強制 finalize,然後 await pWaitFor(() => userMessageContentReady) 等所有 block 被 presentAssistantMessage 處理完 (pWaitFor ready:3808)。之後判斷有沒有 tool_use:有就呼叫 checkpoint + 用 userMessageContent 遞迴;沒有就塞 noToolsUsed 提示再遞迴;完全空回應走錯誤路徑。遞迴回傳的 didEndLoop 一路傳回 initiateTaskLoop,真就退出 while,假就讓外層加 noToolsUsed 提示再來一輪。

邊界與失敗

  • abort 優先於一切:函式第一行檢查 taskState.abort (abort check:2795),被取消直接拋 Task instance aborted,不進入流式重置或 API 呼叫。這保證取消後即使遞迴還掛著也會立刻退出。
  • mistake 限額:YOLO 模式 return true 終止任務 (yolo return:2847);非 YOLO 模式 ask 使用者,使用者給新 prompt 就把它作為下一輪的 userContent 遞迴 (ask mistake_limit_reached:2860)。
  • 空回應:整輪 assistant 沒有 text 也沒有 tool_use,記 empty_assistant_message 遙測 (empty telemetry:3840),say error 文本帶 request ID,然後 ask("api_req_failed") 讓使用者決定重試。
  • stream 中段失敗:首 chunk 之後的流失敗由 recursivelyMakeClineRequests 的外層 try/catch 接管 (outer catch:3935),理論上 attemptApiRequest 內部已經 catch 了首 chunk 錯誤,這裡的 catch 是雙保險防 unhandled rejection。
  • partial block 殘留:流結束時還有 partial block (沒看到閉合標籤),強制 partial = false (force partial false:3792),讓 presentAssistantMessage 能推進並最終置 userMessageContentReady = true,否則 pWaitFor 會無限等。
  • remote workspace 偵測未完成:await this.remoteWorkspaceDetectionPromise 在遞迴開始等 (remote workspace wait:2801),讓 presentation scheduler 從第一個 flush 起就用正確的 cadence。
  • apiRequestCount 自增:每輪 apiRequestCount++ (apiRequestCount++:2804),apiRequestsSinceLastTodoUpdate++,用於 focus chain list 管理和 todo 更新節奏。
  • checkpoint 在遞迴前:工具都執行完、userMessageContentReady 後再 checkpointManager.saveCheckpoint (saveCheckpoint:3811),保證 checkpoint 反映這一輪所有檔案改動。然後才進下一輪遞迴。

小結

recursivelyMakeClineRequests 是 Cline agent loop 的「單回合驅動器」。它把「重置狀態 → 拉流 → 解析 → 呈現/執行 → 等待完成 → 用工具結果再遞迴」這條鏈路串起來,mistake 限額、空回應、abort 這些邊界都集中在這裡處理。要看它拉流和重試的那段,轉 /agent-loop/attempt-api-request;要看它把 block 推進 UI 和工具的最內層,轉 /agent-loop/present-assistant-message;要看整體狀態機的邊界,轉 /agent-loop/task-class

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