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。