recursivelyMakeClineRequests:遞迴驅動器
職責
recursivelyMakeClineRequests 是 Task 類別裡那台「每遞迴一次 = 一次完整 LLM 呼叫 + 工具執行」的核心驅動器,定義在 apps/vscode/src/core/task/index.ts 的 Task 類別裡。它接收上一輪累積的 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。 - 流式狀態全量重置:每輪開始都把
currentStreamingContentIndex、assistantMessageContent、userMessageContent、didRejectTool、presentAssistantMessageLocked等十來個欄位清零 (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:2790— 函式入口,簽名為(userContent, includeFileDetails?) => Promise<boolean>,回傳didEndLoop。abort check:2795— 進函式立刻檢查taskState.abort,被取消時拋Task instance aborted。apiRequestCount++:2804— 自增請求計數器,用於 focus chain list 管理。mistake limit check:2826—consecutiveMistakeCount >= maxConsecutiveMistakes時進入 mistake 處理分支。yolo fail:2841— YOLO 模式下直接 say error + return true 終止。ask mistake_limit_reached:2860— 非 YOLO 模式 ask 使用者,使用者可以給新 prompt 繼續。reset streaming state:3302— 流式狀態全量重置,十來個欄位清零 + handler/scheduler reset。attemptApiRequest call:3319— 拿流,yield 第一個 chunk 失敗會被 attemptApiRequest 內部 try/catch 轉成api_req_failedask。StreamChunkCoordinator:3368— 包一層,把流分成 reasoning/text/usage 三種 chunk 分發。while true chunk loop:3387— 主消費迴圈,從 coordinator 拿下一個 chunk,switch 分發。accumulate assistantMessage:3503— text chunk 累積進 assistantMessage + assistantTextOnly,然後重解析。force partial false:3792— 流結束後把殘留的 partial tool block 強制partial = false,讓 presentAssistantMessage 能 finalize。pWaitFor ready:3808— 等所有 block 處理完,userMessageContentReady 置真。noToolsUsed bump:3818— 沒工具呼叫時塞 noToolsUsed 提示 + mistake++。recurse:3830— 用累積的 userMessageContent 再呼叫自己,回傳 didEndLoop。empty response:3834— 空回應路徑,say error + ask api_req_failed。outer catch:3935— 兜底 catch,理論上 attemptApiRequest 自己已經 catch 了,這裡是雙保險。initiateTaskLoop:1717— 外層 while,處理 noToolsUsed 提示 + didEndLoop 退出。
資料流
每次進入 recursivelyMakeClineRequests,先做 mistake 限額檢查和遠端 workspace 偵測,然後重置流式狀態、啟動流。下面這段是流式狀態重置 + 流啟動的核心:
// 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),長度增加就 scheduleAssistantPresentation 讓 presentAssistantMessage 推進新 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。