2026年Claude Code 源码分析 — 核心对话循环

Claude Code 源码分析 — 核心对话循环本文基于项目实际源码 深入分析 Claude Code CLI 从用户提交输入到一轮完整对话结束的核心对话循环 涵盖输入分发 并发控制 上下文加载 API 流式调用 工具执行 错误恢复 中止处理与循环续接的完整链路 Claude Code 的核心对话循环从用户在 REPL 提交输入开始 经过多层处理和分发 最终进入一个 while true 驱动的 API 调用 工具执行 续行循环

大家好,我是讯享网,很高兴认识大家。这里提供最前沿的Ai技术和互联网信息。



本文基于项目实际源码,深入分析 Claude Code CLI 从用户提交输入到一轮完整对话结束的核心对话循环。涵盖输入分发、并发控制、上下文加载、API 流式调用、工具执行、错误恢复、中止处理与循环续接的完整链路。

Claude Code 的核心对话循环从用户在 REPL 提交输入开始,经过多层处理和分发,最终进入一个 while(true) 驱动的 API 调用-工具执行-续行循环。完整路径为:

以下逐层拆解每个阶段的源码实现。

源码位置:src/screens/REPL.tsx:3723

onSubmitPromptInput 组件的回调,是所有用户输入的第一个处理点。它接收四个参数:

// 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: truelocal-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 是连接用户输入处理和查询系统的桥梁。它的核心职责:

  1. 创建 AbortController
  2. 预留 queryGuardreserve() 将状态从 idle 转为 dispatching)
  3. 循环处理所有 queuedCommands
  4. 调用 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, …);

 

onQueryfinally 块通过 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:1311 feature 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' } }

两个中止路径的区别在于 createUserInterruptionMessagetoolUse 参数,标识中断发生在工具执行期间。

源码位置: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 回到循环顶部

 

注意每次正常循环续接时,maxOutputTokensRecoveryCounthasAttemptedReactiveCompact重置 ,这意味着恢复保护是每轮而非全局的。

Claude Code 的核心对话循环是一个精心设计的多层架构:

  1. 输入层(onSubmit → handlePromptSubmit → executeUserInput):负责输入分类、即时命令拦截、队列管理和并发保护。五条互斥路径确保每种输入场景都有明确的处理策略。
  2. 查询控制层 (onQuery → onQueryImpl):QueryGuard 状态机提供严格的并发控制,并行上下文加载最小化延迟,for await 模式优雅地消费异步事件流。
  3. 核心引擎层 (query → queryLoop → while(true)):可变 State 对象在迭代间传递状态,五阶段预查询管线渐进式压缩上下文,六种恢复转换通过 state = next; continue 模式实现优雅的错误恢复。
  4. 执行层(StreamingToolExecutor +循环续接管线):流内工具并发执行与模型生成重叠,异步工具摘要生成和记忆/技能预取充分利用等待时间,工具刷新和队列压缩确保动态资源及时可用。

整个系统的核心设计哲学是弹性和自修复:通过错误扣留机制推迟决策,通过分级恢复策略(压缩 → 反应式压缩 → 输出限制升级 → 多轮续写)逐步降级,通过严格的单次触发保护和 generation 编号防止无限循环。这使得 Claude Code 能够在面对各种运行时错误(上下文溢出、输出截断、模型高负载、用户中断)时自动恢复,极大提升了交互式代码助手的可靠性。

小讯
上一篇 2026-04-18 13:14
下一篇 2026-04-18 13:12

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/270224.html