本文基于项目实际源码,深入分析 Claude Code CLI 从用户提交输入到一轮完整对话结束的核心对话循环。涵盖输入分发、并发控制、上下文加载、API 流式调用、工具执行、错误恢复、中止处理与循环续接的完整链路。
Claude Code 的核心对话循环从用户在 REPL 提交输入开始,经过多层处理和分发,最终进入一个 while(true) 驱动的 API 调用-工具执行-续行循环。完整路径为:
以下逐层拆解每个阶段的源码实现。
源码位置:
src/screens/REPL.tsx:3723
onSubmit 是 PromptInput 组件的回调,是所有用户输入的第一个处理点。它接收四个参数:
// src/screens/REPL.tsx:3723 const onSubmit = useCallback( async ( input: string, helpers: PromptInputHelpers, speculationAccept?: , options?: { fromKeybinding?: boolean }, ) =>
当输入以 / 开头且不是推测接受 (speculationAccept) 时,onSubmit 尝试匹配即时命令:
// src/screens/REPL.tsx:3746 if (!speculationAccept && input.trim().startsWith('/')) ); } }; void executeImmediateCommand(); return; // 始终提前返回 }
即时命令检查之后,onSubmit 处理两个特殊场景:
远程模式空输入过滤:
// src/screens/REPL.tsx:3884 if (activeRemote.isRemoteMode && !input.trim()) { return; }
空闲回归检测 --- 当用户长时间离开后返回,根据 willowMode 配置决定处理方式:
'dialog':显示阻塞式对话框'hint':显示提示通知'off':无操作
判定条件包括空闲时间超过阈值(默认 75 分钟)且累计 token 数超过阈值(默认 100k)。
通过前述检查后,onSubmit 将控制权委托给 handlePromptSubmit:
onSubmit ├─ 即时命令 → executeImmediateCommand() → return ├─ 远程模式空输入 → return ├─ 空闲回归 → dialog/hint/pass └─ 正常路径 → handlePromptSubmit(...)
源码位置:
src/utils/handlePromptSubmit.ts:120
handlePromptSubmit 是输入处理的核心分发器,有五条互斥路径:
handlePromptSubmit(params) │ ├─① queuedCommands?.length → executeUserInput() [直接执行预验证命令] │ ├─② ['exit','quit',':q',...] → handlePromptSubmit({input:'/exit'}) [递归转换] │ ├─③ queryGuard.isActive + immediate command → load() + call() [即时命令] │ ├─④ queryGuard.isActive || isExternalLoading → enqueue() [入队等待] │ └─⑤ 默认路径 → executeUserInput([cmd]) [直接执行]
当 queuedCommands 已经存在时(来自队列处理器 useQueueProcessor),命令已经过预验证,直接进入执行:
// src/utils/handlePromptSubmit.ts:150 if (queuedCommands?.length) { startQueryProfile() await executeUserInput({ queuedCommands, messages, mainLoopModel, // ... 其他参数 }) return }
这一步一般发生在对话已经进行时输入一些命令时,Claude Code不会直接执行,而是被enqueue()进队列(参考3.4),等上一轮对话完成后才会从队列移出并执行executeQueuedInput。因为在进队列前已经做了必要的判断,所以立即执行,避免重复判断。
识别多种退出命令格式,统一转换为 /exit 斜杠命令处理:
// src/utils/handlePromptSubmit.ts:194 if ( !skipSlashCommands && ['exit', 'quit', ':q', ':q!', ':wq', ':wq!'].includes(input.trim()) ) ) // 递归调用 } else { exit() // 降级直接退出 } return }
当系统正在处理查询(queryGuard.isActive || isExternalLoading)且匹配到 immediate: true 的 local-jsx 命令时,直接执行而不入队:
// src/utils/handlePromptSubmit.ts:248 if ( immediateCommand && immediateCommand.type === 'local-jsx' && (queryGuard.isActive || isExternalLoading) ) ) } return }
当查询正在运行且输入不是即时命令时,将输入入队等待:
// src/utils/handlePromptSubmit.ts:313 if (queryGuard.isActive || isExternalLoading) // 如果当前工具支持中断,发送中止信号 if (params.hasInterruptibleToolInProgress) { params.abortController?.abort('interrupt') } enqueue({ value: finalInput.trim(), preExpansionValue: input.trim(), mode, pastedContents: hasImages ? pastedContents : undefined, skipSlashCommands, uuid, }) // 清理输入框 ... return }
空闲状态下的默认路径,将输入包装为 QueuedCommand 后调用 executeUserInput:
// src/utils/handlePromptSubmit.ts:353 startQueryProfile() const cmd: QueuedCommand = { value: finalInput, preExpansionValue: input, mode, pastedContents: hasImages ? pastedContents : undefined, skipSlashCommands, uuid, } await executeUserInput({ queuedCommands: [cmd], messages, mainLoopModel, // ... 其他参数 })
源码位置:
src/utils/handlePromptSubmit.ts:396
executeUserInput 是连接用户输入处理和查询系统的桥梁。它的核心职责:
- 创建
AbortController - 预留
queryGuard(reserve()将状态从 idle 转为 dispatching) - 循环处理所有
queuedCommands - 调用
onQuery触发查询
// src/utils/handlePromptSubmit.ts:419 const abortController = createAbortController() setAbortController(abortController) try { queryGuard.reserve() // idle → dispatching const newMessages: Message[] = [] let shouldQuery = false // ... await runWithWorkload(turnWorkload, async () => { for (let i = 0; i < commands.length; i++) { const cmd = commands[i]! const isFirst = i === 0 const result = await processUserInput({ input: cmd.value, // 第一条命令获完整处理(附件、IDE选区、粘贴内容) // 后续命令跳过附件避免重复 skipAttachments: !isFirst, // ... }) newMessages.push(...result.messages) if (isFirst) { shouldQuery = result.shouldQuery allowedTools = result.allowedTools model = result.model effort = result.effort } } // 文件历史快照 if (fileHistoryEnabled()) { newMessages.filter(selectableUserMessagesFilter).forEach(message => { void fileHistoryMakeSnapshot(/* ... */) }) } if (newMessages.length) { await onQuery( newMessages, abortController, shouldQuery, allowedTools ?? [], model ? resolveSkillModelOverride(model, mainLoopModel) : mainLoopModel, shouldCallBeforeQuery ? onBeforeQuery : undefined, primaryInput, effort, ) } }) } finally
processUserInput 的作用是把原始用户输入转换为可以发送给 API 的结构化消息数组,同时决定是否需要发起 API 调用。
命令处理的关键设计:第一条命令得到完整处理 (包括附件、IDE 选区、粘贴内容),后续命令跳过附件以避免重复注入上下文。
源码位置:
src/screens/REPL.tsx:3382
onQuery 的第一件事是通过 queryGuard.tryStart() 进行并发控制。QueryGuard 维护一个简单的状态机:
idle → dispatching → running → idle ↑ reserve() ↑ tryStart() ↑ end()
tryStart() 原子地检查并转换 dispatching → running,返回一个单调递增的 generation 号。如果已经处于 running 状态,返回 null。
当 tryStart() 返回 null 时,说明已有查询在运行。此时将用户消息入队而非丢弃:
// src/screens/REPL.tsx:3406
const thisGeneration = queryGuard.tryStart(); if (thisGeneration === null) { logEvent(‘tengu_concurrent_onquery_detected’, {}); // 提取并入队用户消息文本,跳过 meta 消息 newMessages
.filter((m): m is UserMessage => m.type === 'user' && !m.isMeta) .map(_ => getContentText(_.message.content)) .filter(_ => _ !== null) .forEach((msg, i) => { enqueue({ value: msg, mode: 'prompt' }); });
return; }
成功获取 guard 后,初始化各类状态:
// src/screens/REPL.tsx:3429
try apiMetricsRef.current = []; setStreamingToolUses([]); setStreamingText(null); const latestMessages = messagesRef.current; // … await onQueryImpl(latestMessages, newMessages, abortController, shouldQuery, …);
onQuery 的 finally 块通过 queryGuard.end(thisGeneration) 原子地检查 generation 并转换 running → idle。如果 generation 不匹配(取消+重提交竞态),返回 false 跳过清理:
// src/screens/REPL.tsx:3468
finally
// 超过 30s 的轮次显示耗时消息 const turnDurationMs = Date.now() - loadingStartTimeRef.current - totalPausedMsRef.current; if ((turnDurationMs > 30000 || budgetInfo !== undefined) && !abortController.signal.aborted) setAbortController(null); // 清理 controller
} }
源码位置:
src/screens/REPL.tsx:3120
onQueryImpl 是实际执行查询的核心函数。
通过 Promise.all 并行加载多个上下文源,最小化延迟:
// src/screens/REPL.tsx:3253 const [, , defaultSystemPrompt, baseUserContext, systemContext] = await Promise.all([ checkAndDisableBypassPermissionsIfNeeded(toolPermissionContext, setAppState), feature('TRANSCRIPT_CLASSIFIER') ? checkAndDisableAutoModeIfNeeded(...) : undefined, getSystemPrompt(freshTools, mainLoopModelParam, ..., freshMcpClients), // 系统提示词 getUserContext(), // 用户上下文 (CLAUDE.md, 内存文件等) getSystemContext(), // 系统上下文 (git status, 日期等) ]);
加载完成后,构建最终生效的系统提示词:
// src/screens/REPL.tsx:3282 const systemPrompt = buildEffectiveSystemPrompt({ mainThreadAgentDefinition, toolUseContext, customSystemPrompt, defaultSystemPrompt, appendSystemPrompt, });
核心查询通过 for await 消费 query() 生成器产生的事件流:
// src/screens/REPL.tsx:3296 for await (const event of query()) { onQueryEvent(event); // 分发给 UI 层处理 }
onQueryEvent 根据事件类型分发到不同的 UI 更新路径:消息追加、流式文本更新、工具调用状态等。
查询完成后执行收尾工作:
// src/screens/REPL.tsx:3308 // Companion 观察者(BUDDY 功能) if (feature('BUDDY') && typeof fireCompanionObserver === 'function') { void fireCompanionObserver(messagesRef.current, ...); } // API 指标捕获(ant-only) if (process.env.USER_TYPE === 'ant' && apiMetricsRef.current.length > 0) resetLoadingState(); await onTurnComplete?.(messagesRef.current);
源码位置:
src/query.ts:219
queryLoop 的核心是一个可变的 State 对象,在循环迭代间传递状态:
// src/query.ts:204
type State = { messages: Message[] // 消息历史 toolUseContext: ToolUseContext // 工具执行上下文 autoCompactTracking: AutoCompactTrackingState | undefined // 自动压缩跟踪 maxOutputTokensRecoveryCount: number // 输出限制恢复计数 hasAttemptedReactiveCompact: boolean // 是否已尝试反应式压缩 maxOutputTokensOverride: number | undefined // 输出限制覆盖值 pendingToolUseSummary: Promise
| undefined // 待处理工具摘要 stopHookActive: boolean | undefined // 停止钩子状态 turnCount: number // 当前循环计数 transition: Continue | undefined // 上次迭代的转换原因 }
初始化时设定默认值:
// src/query.ts:268
let state: State = { messages: params.messages, toolUseContext: params.toolUseContext, maxOutputTokensOverride: params.maxOutputTokensOverride, autoCompactTracking: undefined, stopHookActive: undefined, maxOutputTokensRecoveryCount: 0, hasAttemptedReactiveCompact: false, turnCount: 1, pendingToolUseSummary: undefined, transition: undefined, }
同时创建两个循环级别的资源:
// src/query.ts:280
const budgetTracker = feature(‘TOKEN_BUDGET’) ? createBudgetTracker() : null
// using 关键字确保在生成器所有退出路径上自动 dispose using pendingMemoryPrefetch = startRelevantMemoryPrefetch( state.messages, state.toolUseContext, )
queryLoop 的主体是一个 while(true) 无限循环,每次迭代构成一个完整的"查询-执行-决策"周期:
在每次 API 调用之前,消息历史经过五个阶段的处理以控制上下文窗口大小。
源码位置:
src/query.ts:379
对聚合工具结果的总大小施加预算限制,替换超出预算的内容:
messagesForQuery = await applyToolResultBudget( messagesForQuery, toolUseContext.contentReplacementState, persistReplacements ? records => void recordContentReplacement(...) : undefined, new Set(toolUseContext.options.tools.filter(t => !Number.isFinite(t.maxResultSizeChars)).map(t => t.name)), )
运行在 microcompact 之前,因为缓存的 MC 通过 tool_use_id 操作而不检查内容,两者可以干净地组合。
feature flag:
HISTORY_SNIP
基于片段的压缩策略,保留消息结构但截断过长的内容。
源码位置:
src/query.ts:414
细粒度压缩,支持缓存编辑以避免缓存失效:
const mcResult = await deps.microcompact( messagesForQuery, toolUseContext, querySource, ) messagesForQuery = mcResult.messages
feature flag:
CONTEXT_COLLAPSE
读取时投影:不修改原始消息,而是在查询时动态折叠上下文:
messagesForQuery = contextCollapse.applyCollapsesIfNeeded( messagesForQuery, querySource, )
源码位置:
src/query.ts:454
当上下文接近限制时触发的全量压缩,使用系统提示词和用户上下文生成摘要:
const compactResult = await deps.autocompact( messagesForQuery, systemPrompt, userContext, toolUseContext, tracking, // ... )
五个阶段按严格顺序执行,每个阶段的输出作为下一阶段的输入。
源码位置:
src/query.ts:659
API 调用通过 deps.callModel() 发起,返回一个 AsyncGenerator 产生 BetaRawMessageStreamEvent 事件流:
// src/query.ts:659 for await (const message of deps.callModel(), }, // ... 更多选项 }, })) } // 收集已完成的结果(非阻塞) if (streamingToolExecutor && !toolUseContext.abortController.signal.aborted) } }
这种设计使得工具执行可以与模型的流式输出重叠,显著减少总循环时间。
流式回退(Streaming Fallback):当模型在流式传输中降级到备用模型时,需要清理已产生的孤立消息:
// src/query.ts:712 if (streamingFallbackOccured) { // 为孤立消息生成墓碑消息(从 UI 和记录中移除) for (const msg of assistantMessages) { yield { type: 'tombstone' as const, message: msg } } assistantMessages.length = 0 toolResults.length = 0 toolUseBlocks.length = 0 needsFollowUp = false // 丢弃旧执行器的待处理结果,创建新的 if (streamingToolExecutor) { streamingToolExecutor.discard() streamingToolExecutor = new StreamingToolExecutor(...) } }
错误扣留 (Withheld Errors):某些可恢复的错误(prompt-too-long、media-size、max-output-tokens)在流式传输期间被扣留而不是立即 yield,等待后续恢复逻辑决定是否可以自动修复:
// src/query.ts:801 let withheld = false if (feature('CONTEXT_COLLAPSE')) } if (reactiveCompact?.isWithheldPromptTooLong(message as Message)) { withheld = true } if (isWithheldMaxOutputTokens(message)) { withheld = true } if (!withheld) { yield yieldMessage // 只有非扣留消息才 yield 给上层 }
被扣留的错误仍然被 push 到 assistantMessages 数组中,供后续恢复检查使用。
源码位置:
src/query.ts:897
当主模型因高负载不可用时,抛出 FallbackTriggeredError。处理逻辑:
目前从源码中没找到哪里可以设置
fallbackModel,官方文档也没找到设置的方式,可能是个还没对外的feature
// src/query.ts:897
catch (innerError)
// 更新上下文中的模型 toolUseContext.options.mainLoopModel = fallbackModel // 剥离签名块(thinking 签名与模型绑定) if (process.env.USER_TYPE === 'ant') { messagesForQuery = stripSignatureBlocks(messagesForQuery) } // 向用户显示降级通知 yield createSystemMessage( `Switched to ${renderModelName(innerError.fallbackModel)} due to high demand for ${renderModelName(innerError.originalModel)}`, 'warning', ) continue // 重试 API 调用
} throw innerError }
外层 catch 处理所有未被内层捕获的错误:
// src/query.ts:958
catch (error) )
return { reason: 'image_error' }
}
// 为孤立的 tool_use 块补充合成 tool_result yield* yieldMissingToolResultBlocks(assistantMessages, errorMessage)
// 显示真实错误而非误导性的"用户中断"消息 yield createAssistantAPIErrorMessage({ content: errorMessage }) return { reason: ‘model_error’, error } }
yieldMissingToolResultBlocks 确保每个 tool_use 块都有对应的 tool_result,防止后续 API 调用因消息格式不完整而报错。
当模型响应不需要后续工具调用(!needsFollowUp)时,进入恢复检查逻辑。六种恢复转换形成一个优先级递减的级联结构:
源码位置:
src/query.ts:1092触发条件:prompt-too-long 错误 + CONTEXT_COLLAPSE 功能启用 + 上次转换不是此类型
当出现模型API返回413表面超出最大token时,将所有已暂存的上下文折叠,这是成本最低的恢复手段------保留细粒度上下文:
if (feature('CONTEXT_COLLAPSE') && contextCollapse && state.transition?.reason !== 'collapse_drain_retry') } continue } }
单次触发保护------如果压缩后重试仍然 413,将 fall through 到反应式压缩。
源码位置:
src/query.ts:1122触发条件:prompt-too-long 或 media-size 错误 +reactiveCompact可用 + 未尝试过
全量反应式压缩,生成消息历史的完整摘要:
if ((isWithheld413 || isWithheldMedia) && reactiveCompact) { const compacted = await reactiveCompact.tryReactiveCompact({ hasAttempted: hasAttemptedReactiveCompact, messages: messagesForQuery, // ... }) if (compacted) { state = { ...state, messages: buildPostCompactMessages(compacted), hasAttemptedReactiveCompact: true, transition: { reason: 'reactive_compact_retry' } } continue } // 恢复失败 → 显示扣留的错误并退出 yield lastMessage return { reason: isWithheldMedia ? 'image_error' : 'prompt_too_long' } }
源码位置:
src/query.ts:1191触发条件:max-output-tokens 错误 +maxOutputTokensOverride === undefined
当模型使用默认的 8k 输出限制触顶时,升级到 64k (ESCALATED_MAX_TOKENS) 重试同一请求------无需注入 meta 消息:
if (capEnabled && maxOutputTokensOverride === undefined && !process.env.CLAUDE_CODE_MAX_OUTPUT_TOKENS) { state = { ...state, maxOutputTokensOverride: ESCALATED_MAX_TOKENS, transition: { reason: 'max_output_tokens_escalate' } } continue }
单次触发------如果 64k 也触顶,fall through 到多轮恢复。
源码位置:
src/query.ts:1226触发条件:max-output-tokens 错误 + 恢复次数 <MAX_OUTPUT_TOKENS_RECOVERY_LIMIT(3)
注入一条 meta 用户消息要求模型从断点续写:
if (maxOutputTokensRecoveryCount < MAX_OUTPUT_TOKENS_RECOVERY_LIMIT) ) state = { ...state, messages: [...messagesForQuery, ...assistantMessages, recoveryMessage], maxOutputTokensRecoveryCount: maxOutputTokensRecoveryCount + 1, transition: { reason: 'max_output_tokens_recovery', attempt: maxOutputTokensRecoveryCount + 1 }, } continue }
最多重试 3 次,超出后显示扣留的错误消息。
源码位置:
src/query.ts:1270触发条件:handleStopHooks()返回阻塞错误
停止钩子(用户配置的 shell 命令)在模型响应完成后执行。如果钩子返回阻塞错误,将错误消息注入对话并重试:
const stopHookResult = yield* handleStopHooks( messagesForQuery, assistantMessages, systemPrompt, userContext, systemContext, toolUseContext, querySource, stopHookActive, ) if (stopHookResult.blockingErrors.length > 0) { state = { ...state, messages: [...messagesForQuery, ...assistantMessages, ...stopHookResult.blockingErrors], stopHookActive: true, hasAttemptedReactiveCompact, // 保留压缩保护 transition: { reason: 'stop_hook_blocking' }, } continue }
注意 hasAttemptedReactiveCompact 被保留而非重置------防止"压缩 → 仍然太长 → 停止钩子阻塞 → 压缩 → ..."的无限循环。
源码位置:
src/query.ts:1311feature flag:TOKEN_BUDGET触发条件:checkTokenBudget()返回action === 'continue'
当启用令牌预算且模型尚未用完预算时,注入 nudge 消息继续执行:
if (feature('TOKEN_BUDGET')) )], transition: , } continue } }
如果没有任何恢复条件命中,返回正常完成:
return { reason: 'completed' }
用户可以在两个阶段中止对话:流式传输阶段和工具执行阶段。
源码位置:
src/query.ts:1018
流式 API 调用完成后立即检查中止信号:
// src/query.ts:1018 if (toolUseContext.abortController.signal.aborted) } } else { yield* yieldMissingToolResultBlocks(assistantMessages, 'Interrupted by user') } // Computer Use 清理(CHICAGO_MCP 功能) if (feature('CHICAGO_MCP') && !toolUseContext.agentId) { const { cleanupComputerUseAfterTurn } = await import('./utils/computerUse/cleanup.js') await cleanupComputerUseAfterTurn(toolUseContext) } // 对于 submit-interrupt(输入中断),跳过中断消息 if (toolUseContext.abortController.signal.reason !== 'interrupt') { yield createUserInterruptionMessage({ toolUse: false }) } return { reason: 'aborted_streaming' } }
关键细节:当中止原因是 'interrupt'(用户提交了新输入触发的中断)时,跳过中断消息------因为紧随其后的用户消息已经提供了足够的上下文。
源码位置:
src/query.ts:1488
工具执行完成后检查中止信号:
// src/query.ts:1488 if (toolUseContext.abortController.signal.aborted) = await import('./utils/computerUse/cleanup.js') await cleanupComputerUseAfterTurn(toolUseContext) } if (toolUseContext.abortController.signal.reason !== 'interrupt') { yield createUserInterruptionMessage({ toolUse: true }) // 注意 toolUse: true } // 中止时也检查 maxTurns const nextTurnCountOnAbort = turnCount + 1 if (maxTurns && nextTurnCountOnAbort > maxTurns) { yield createAttachmentMessage({ type: 'max_turns_reached', maxTurns, turnCount: nextTurnCountOnAbort }) } return { reason: 'aborted_tools' } }
两个中止路径的区别在于 createUserInterruptionMessage 的 toolUse 参数,标识中断发生在工具执行期间。
源码位置:
src/query.ts:1369
当模型响应包含 tool_use 块时(needsFollowUp === true),进入工具执行阶段:
// src/query.ts:1383 const toolUpdates = streamingToolExecutor ? streamingToolExecutor.getRemainingResults() // 流式执行器:收集剩余结果 : runTools(toolUseBlocks, assistantMessages, canUseTool, toolUseContext) // 回退:顺序执行 for await (const update of toolUpdates) toolResults.push(...normalizeMessagesForAPI([update.message], ...).filter(_ => _.type === 'user')) } if (update.newContext) { updatedToolUseContext = { ...update.newContext, queryTracking } } }
工具执行的同时,异步生成工具使用摘要(用于移动端 UI):
// src/query.ts:1472 nextPendingToolUseSummary = generateToolUseSummary({ tools: toolInfoForSummary, signal: toolUseContext.abortController.signal, isNonInteractiveSession: toolUseContext.options.isNonInteractiveSession, lastAssistantText, }) .then(summary => summary ? createToolUseSummaryMessage(summary, toolUseIds) : null) .catch(() => null)
这个摘要生成是非阻塞的------异步调用 Haiku 模型(~1s),在下一次 API 调用期间(5-30s)完成,在下一轮迭代开始时 yield。
工具执行完成后,进入续行管线为下一轮 API 调用准备额外上下文:
1. 队列命令
源码位置:
src/query.ts:1573
从全局队列中获取待处理的命令,按优先级和范围过滤:
const queuedCommandsSnapshot = getCommandsByMaxPriority(sleepRan ? 'later' : 'next') .filter(cmd => )
2. 获取附件消息
源码位置:
src/query.ts:1583
for await (const attachment of getAttachmentMessages(null, updatedToolUseContext, null, queuedCommandsSnapshot, [...messagesForQuery, ...assistantMessages, ...toolResults], querySource)) {
yield attachment toolResults.push(attachment) }
3. 消费记忆预取
源码位置:
src/query.ts:1602
零等待消费------如果预取已完成则使用,否则跳过等下一轮重试:
if (pendingMemoryPrefetch && pendingMemoryPrefetch.settledAt !== null && pendingMemoryPrefetch.consumedOnIteration === -1) { const memoryAttachments = filterDuplicateMemoryAttachments(await pendingMemoryPrefetch.promise, toolUseContext.readFileState) for (const memAttachment of memoryAttachments) { const msg = createAttachmentMessage(memAttachment) yield msg toolResults.push(msg) } pendingMemoryPrefetch.consumedOnIteration = turnCount - 1 }
4. 注入技能发现预取
源码位置:
src/query.ts:1623-1631
if (skillPrefetch && pendingSkillPrefetch) {
const skillAttachments = await skillPrefetch.collectSkillDiscoveryPrefetch(pendingSkillPrefetch) for (const att of skillAttachments) {
const msg = createAttachmentMessage(att) yield msg toolResults.push(msg)
} }
5. 刷新工具列表
源码位置:
src/query.ts:1663-1674
在循环间刷新工具,使新连接的 MCP 服务器可用:
if (updatedToolUseContext.options.refreshTools) }
} }
循环续接管线完成后,检查是否达到最大循环限制:
// src/query.ts:1708
const nextTurnCount = turnCount + 1 if (maxTurns && nextTurnCount > maxTurns) { yield createAttachmentMessage({ type: ‘max_turns_reached’, maxTurns, turnCount: nextTurnCount }) return { reason: ‘max_turns’, turnCount: nextTurnCount } }
未达到限制时,构建下一轮状态并 continue:
// src/query.ts:1718
const next: State = { messages: […messagesForQuery, …assistantMessages, …toolResults], toolUseContext: toolUseContextWithQueryTracking, autoCompactTracking: tracking, turnCount: nextTurnCount, maxOutputTokensRecoveryCount: 0, // 重置恢复计数 hasAttemptedReactiveCompact: false, // 重置压缩标记 pendingToolUseSummary: nextPendingToolUseSummary, maxOutputTokensOverride: undefined, // 重置输出限制覆盖 stopHookActive, transition: { reason: ‘next_turn’ }, } state = next // } // while (true) → continue 回到循环顶部
注意每次正常循环续接时,maxOutputTokensRecoveryCount 和 hasAttemptedReactiveCompact 被重置 ,这意味着恢复保护是每轮而非全局的。
Claude Code 的核心对话循环是一个精心设计的多层架构:
- 输入层(onSubmit → handlePromptSubmit → executeUserInput):负责输入分类、即时命令拦截、队列管理和并发保护。五条互斥路径确保每种输入场景都有明确的处理策略。
- 查询控制层 (onQuery → onQueryImpl):QueryGuard 状态机提供严格的并发控制,并行上下文加载最小化延迟,
for await模式优雅地消费异步事件流。 - 核心引擎层 (query → queryLoop → while(true)):可变 State 对象在迭代间传递状态,五阶段预查询管线渐进式压缩上下文,六种恢复转换通过
state = next; continue模式实现优雅的错误恢复。 - 执行层(StreamingToolExecutor +循环续接管线):流内工具并发执行与模型生成重叠,异步工具摘要生成和记忆/技能预取充分利用等待时间,工具刷新和队列压缩确保动态资源及时可用。
整个系统的核心设计哲学是弹性和自修复:通过错误扣留机制推迟决策,通过分级恢复策略(压缩 → 反应式压缩 → 输出限制升级 → 多轮续写)逐步降级,通过严格的单次触发保护和 generation 编号防止无限循环。这使得 Claude Code 能够在面对各种运行时错误(上下文溢出、输出截断、模型高负载、用户中断)时自动恢复,极大提升了交互式代码助手的可靠性。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/270224.html