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