Claude Code 源码分析Claude Code 源码分析
首页
源码统计
系统架构
UML 图表
工具系统
CodeGraph
首页
源码统计
系统架构
UML 图表
工具系统
CodeGraph
  • 概览

    • Claude Code 源码分析
    • 源码统计
    • CodeGraph 图谱
  • 架构

    • 系统架构
    • UML 图表索引
    • 查询引擎
    • 核心流程
    • 消息系统
    • 状态管理
  • 功能模块

    • 工具系统
    • 斜杠命令
    • 服务层
    • MCP 协议
    • Skills 技能
    • 子代理系统
  • 分层深度

    • 入口层
    • UI / Ink 层
    • utils 基础设施
    • 桥接 / 远程
    • 上下文压缩
  • 原理与安全

    • 底层原理
    • 技术难点
    • 权限与安全
    • 内部机制
    • 遥测与分析
  • 深度专题

    • Hooks 系统
    • 插件系统
    • 记忆系统
    • API 通信层
    • Ink 终端 UI
    • 认证系统
    • 构建与发布
    • 术语表
  • 调用分析

    • 调用链分析
    • 核心文件索引
  • 模块详解

    • utils

      • 模块: utils
      • messages · 消息工厂与规范化
      • session-storage · JSONL 会话持久化
      • permissions · 工具权限决策
      • shell-hooks · 用户 Shell Hook 系统
    • components

      • 模块: components
      • REPL · 主屏编排
      • messages · 消息行渲染
      • PermissionRequest · 权限弹窗
      • PromptInput · 底部输入
    • services

      • 模块: services
      • api-claude · Anthropic API 流式与重试
      • mcp-client · MCP 连接与工具调用
      • compact · 上下文压缩与自动触发
      • analytics · GrowthBook、Datadog 与 1P 事件
    • tools

      • 模块: tools
      • tool-interface · Tool 契约与注册表
      • bash-tool · Shell 执行与权限
      • streaming-executor · 流式工具并发调度
      • agent-tool · 子 Agent 委派
    • commands

      • 模块: commands
      • command-registry · commands.ts 注册与分派
      • model-command · /model 模型选择
      • mcp-commands · /mcp 服务器管理
      • compact-memory-commands · /compact 与 /memory
    • ink

      • 模块: ink
      • Ink 渲染管线 · Screen 与终端输出
      • 终端事件 · resize、paste、stdin
      • Ink Hooks · 输入、搜索、终端状态
      • Ink 组件 · Box、Text、ScrollBox 原语
    • hooks

      • 模块: hooks
      • useCanUseTool · 权限 UI 接缝
      • 输入与快捷键 Hook
      • 合并态 Hook(MCP + 本地)
      • notifs 通知 Hook
    • bridge

      • 模块: bridge
      • repl-bridge · REPL 桥初始化与传输
      • bridge-messaging · 桥消息路由与入站处理
      • remote-bridge-core · env-less 核心与守护主循环
      • bridge-permissions-ui · 权限、API 与 TUI
    • cli

      • 模块: cli
      • Structured IO · NDJSON SDK 协议
      • CLI Transports · Session Ingress 传输层
      • CLI Handlers · 子命令懒加载实现
      • Update & Upload · 自更新与串行上传原语
    • screens

      • 模块: screens
      • REPL 屏 · Screen 类型与顶层路由
      • ResumeConversation · 会话恢复选择器
      • Doctor · 安装诊断全屏
    • entrypoints

      • 模块: entrypoints
      • cli-entrypoint · Bootstrap 与快路径
      • sdk-types · core / control / runtime 类型体系
      • mcp-entrypoint · MCP stdio 服务器
      • sandbox-types · 沙箱配置单一真相源
    • skills

      • 模块: skills
      • skills-loading · 磁盘加载与 bundled 注册表
      • bundled-skills · 内置 skill 与 initBundledSkills
      • mcp-skills · MCP prompt 转 skill
      • skill-tool-integration · SkillTool 与命令注册
    • types

      • 模块: types
      • message-types · Message 联合与 content blocks
      • tool-permission-types · Tool、Permission、Command 类型
      • api-sdk-types · API 与 Hooks 协议类型
      • misc-types · ids、plugin、generated 与其余类型
    • tasks

      • 模块: tasks
      • local-agent-task · 本地 Agent 与主会话后台化
      • remote-agent-task · 远程 CCR 与 In-Process Teammate
      • shell-workflow-tasks · Bash 后台、Workflow 与 stopTask
      • dream-monitor-tasks · Dream、Monitor MCP 与 pill 文案
    • keybindings

      • 模块: keybindings
      • keybinding-registry · 注册、Provider 与 useKeybinding
      • default-bindings · 默认键位表与平台差异
      • command-bindings · command:* 动态斜杠命令绑定
      • vim-bindings · Vim 模式与 keybindings 边界
    • memdir

      • 模块: memdir
      • memdir-core · 路径、加载与 MEMORY.md
      • memory-extraction · extractMemories 与 SessionMemory
      • memdir-commands · /memory、/remember 与命令集成
    • state

      • 模块: state
      • app-state-core · store、AppState 类型与 Provider
      • app-state-selectors · selectors 与 onChangeAppState
      • teammate-state · 队友视图与 swarm 状态
      • state-boundaries · bootstrap、sessionStorage、FileStateCache
    • query

      • 模块: query
      • query config 与 deps · 配置快照与依赖注入
      • query tokenBudget · +500k 自动续跑
      • query transitions · Continue / Terminal 状态机
      • query stopHooks · Stop 事件与 turn 结束编排
  • 模块详解(扩展)

    • messages · 消息工厂与规范化
    • session-storage · JSONL 会话持久化
    • permissions · 工具权限决策
    • shell-hooks · 用户 Shell Hook 系统
    • REPL · 主屏编排
    • messages · 消息行渲染
    • PermissionRequest · 权限弹窗
    • PromptInput · 底部输入
    • api-claude · Anthropic API 流式与重试
    • mcp-client · MCP 连接与工具调用
    • compact · 上下文压缩与自动触发
    • analytics · GrowthBook、Datadog 与 1P 事件
    • tool-interface · Tool 契约与注册表
    • bash-tool · Shell 执行与权限
    • streaming-executor · 流式工具并发调度
    • agent-tool · 子 Agent 委派
    • command-registry · commands.ts 注册与分派
    • model-command · /model 模型选择
    • mcp-commands · /mcp 服务器管理
    • compact-memory-commands · /compact 与 /memory
    • Ink 渲染管线 · Screen 与终端输出
    • 终端事件 · resize、paste、stdin
    • Ink Hooks · 输入、搜索、终端状态
    • Ink 组件 · Box、Text、ScrollBox 原语
    • useCanUseTool · 权限 UI 接缝
    • 输入与快捷键 Hook
    • 合并态 Hook(MCP + 本地)
    • notifs 通知 Hook
    • repl-bridge · REPL 桥初始化与传输
    • bridge-messaging · 桥消息路由与入站处理
    • remote-bridge-core · env-less 核心与守护主循环
    • bridge-permissions-ui · 权限、API 与 TUI
    • Structured IO · NDJSON SDK 协议
    • CLI Transports · Session Ingress 传输层
    • CLI Handlers · 子命令懒加载实现
    • Update & Upload · 自更新与串行上传原语
    • REPL 屏 · Screen 类型与顶层路由
    • ResumeConversation · 会话恢复选择器
    • Doctor · 安装诊断全屏
    • cli-entrypoint · Bootstrap 与快路径
    • sdk-types · core / control / runtime 类型体系
    • mcp-entrypoint · MCP stdio 服务器
    • sandbox-types · 沙箱配置单一真相源
    • skills-loading · 磁盘加载与 bundled 注册表
    • bundled-skills · 内置 skill 与 initBundledSkills
    • mcp-skills · MCP prompt 转 skill
    • skill-tool-integration · SkillTool 与命令注册
    • message-types · Message 联合与 content blocks
    • tool-permission-types · Tool、Permission、Command 类型
    • api-sdk-types · API 与 Hooks 协议类型
    • misc-types · ids、plugin、generated 与其余类型
    • local-agent-task · 本地 Agent 与主会话后台化
    • remote-agent-task · 远程 CCR 与 In-Process Teammate
    • shell-workflow-tasks · Bash 后台、Workflow 与 stopTask
    • dream-monitor-tasks · Dream、Monitor MCP 与 pill 文案
    • keybinding-registry · 注册、Provider 与 useKeybinding
    • default-bindings · 默认键位表与平台差异
    • command-bindings · command:* 动态斜杠命令绑定
    • vim-bindings · Vim 模式与 keybindings 边界
    • memdir-core · 路径、加载与 MEMORY.md
    • memory-extraction · extractMemories 与 SessionMemory
    • memdir-commands · /memory、/remember 与命令集成
    • app-state-core · store、AppState 类型与 Provider
    • app-state-selectors · selectors 与 onChangeAppState
    • teammate-state · 队友视图与 swarm 状态
    • state-boundaries · bootstrap、sessionStorage、FileStateCache
    • query config 与 deps · 配置快照与依赖注入
    • query tokenBudget · +500k 自动续跑
    • query transitions · Continue / Terminal 状态机
    • query stopHooks · Stop 事件与 turn 结束编排
  • 工具详解

    • tool-interface · Tool 契约与注册表
    • tool-permission-types · Tool、Permission、Command 类型
    • 工具: Bash
    • 工具: PowerShell
    • 工具: Agent
    • 工具: LSP
    • 工具: FileEdit
    • 工具: FileRead
    • 工具: Skill
    • 工具: WebFetch
    • 工具: MCP
    • 工具: SendMessage
    • 工具: FileWrite
    • 工具: Config
    • 工具: Grep
    • 工具: Brief
    • 工具: ExitPlanMode
    • 工具: ToolSearch
    • 工具: NotebookEdit
    • 工具: TaskOutput
    • 工具: WebSearch
    • 工具: ScheduleCron

本章总览

queryLoop 是 while (true) 驱动的协程式状态机:每次 continue 开始新 iteration;return 产出 Terminal 完成值。State.transition 记录上一轮为何 continue,供测试断言 recovery 路径而无需解析 messages。query/transitions.ts 当前导出 identity 函数 transitionQueryState 与类型锚点;Continue/Terminal discriminated union 由 query.ts 消费。本章绘制完整转移图并逐条解释 seven+ continue 站点。

学完本章你应该能

  • 解释 query() 与 queryLoop() 的双层 generator 与 Terminal return
  • 列举全部 transition.reason 枚举及触发条件
  • 理解 State 批量更新模式(state = next; continue)
  • 说明 transitionQueryState 占位与未来 step() reducer 路线
  • 能在测试中 assert state.transition.reason

核心概念(先读懂这些)

Continue = 新 iteration,不是递归 query()

queryLoop 内所有 continue 仍在同一 async generator 实例内。messages 与 toolUseContext 写入 State 后跳回 while 顶。仅 maxTurns 或正常 completed 才 return Terminal。这与「递归调用 query()」不同——chainId 在 loop 内保持一致。

transition 是 observability,不是调度器

代码不用 switch(state.transition.reason) 决定下一分支;reason 主要给测试与注释。实际调度由 loop 内 if/else(api error、tool results、stop hooks)决定。transition 字段是「上次 exit edge 的标签」。

Terminal reason 与 SDK 契约

return { reason, ... } 是 queryLoop 的 completion value。外层 query() yield* queryLoop 后 notifyCommandLifecycle。SDK 模式依赖 generator return value 判断 turn 是正常完成、被 hook 截断、被 abort、还是因 API / prompt / image 问题提前终止。

建议学习步骤

  1. 阅读 query/queryLoop 签名与 State 类型源码块 A
  2. 阅读 query() 包装层与 Terminal return 源码块 B
  3. 按表格逐个 continue 站点对照 query.ts 行号
  4. 阅读 transitions.ts 占位实现
  5. 画状态图对照本文 Mermaid

常见误区

注意

不要把 ink Terminal 类型与 query Terminal 混淆——后者是 { reason: string }

注意

collapse_drain_retry 仅在 CONTEXT_COLLAPSE feature 存在

注意

stop_hook_blocking continue 会设 stopHookActive: true,影响下一轮 Stop hook

状态机总览

渲染图表中…

两层 generator:

query(params)
  yield* queryLoop(...)  // 所有 StreamEvent/Message
  notifyCommandLifecycle(completed)
  return terminal       // Terminal 传给 caller

query() 的 AsyncGenerator 既 yield 中间事件,又 return Terminal——REPL for-await 取 final return value 更新 session 状态。

State 与 transition 字段

query.ts L204-217 定义 loop 可变状态:

字段用途
messages下一轮输入 transcript
toolUseContext含 queryTracking、readFileState 等
autoCompactTrackingautocompact 连续失败计数
maxOutputTokensRecoveryCountmax_output_tokens 恢复次数
hasAttemptedReactiveCompact413 reactive 只尝试一次
maxOutputTokensOverride_escalate 临时提高 max_tokens
pendingToolUseSummary异步工具摘要 Promise
stopHookActive嵌套 Stop hook 标记
turnCountagentic turn 计数(对比 maxTurns)
transitionContinue | undefined

注释 L214-215:Why the previous iteration continued. Undefined on first iteration. Lets tests assert recovery paths.

Continue 类型从 transitions.ts import;当前源码 transitions.ts 仅 identity 函数——类型可能由 TS 声明合并或在 bundle 中内联。语义上以 query.ts 中 transition: { reason: ... } 字面量为准。

源码引用: src/query.ts · 第 201–217 行(共 1730 行)

 201| // -- query loop state
 202| 
 203| // Mutable state carried between loop iterations
 204| type State = {
 205|   messages: Message[]
 206|   toolUseContext: ToolUseContext
 207|   autoCompactTracking: AutoCompactTrackingState | undefined
 208|   maxOutputTokensRecoveryCount: number
 209|   hasAttemptedReactiveCompact: boolean
 210|   maxOutputTokensOverride: number | undefined
 211|   pendingToolUseSummary: Promise<ToolUseSummaryMessage | null> | undefined
 212|   stopHookActive: boolean | undefined
 213|   turnCount: number
 214|   // Why the previous iteration continued. Undefined on first iteration.
 215|   // Lets tests assert recovery paths fired without inspecting message contents.
 216|   transition: Continue | undefined
 217| }

源码引用: src/query.ts · 第 219–239 行(共 1730 行)

 219| export async function* query(
 220|   params: QueryParams,
 221| ): AsyncGenerator<
 222|   | StreamEvent
 223|   | RequestStartEvent
 224|   | Message
 225|   | TombstoneMessage
 226|   | ToolUseSummaryMessage,
 227|   Terminal
 228| > {
 229|   const consumedCommandUuids: string[] = []
 230|   const terminal = yield* queryLoop(params, consumedCommandUuids)
 231|   // Only reached if queryLoop returned normally. Skipped on throw (error
 232|   // propagates through yield*) and on .return() (Return completion closes
 233|   // both generators). This gives the same asymmetric started-without-completed
 234|   // signal as print.ts's drainCommandQueue when the turn fails.
 235|   for (const uuid of consumedCommandUuids) {
 236|     notifyCommandLifecycle(uuid, 'completed')
 237|   }
 238|   return terminal
 239| }

源码引用: src/query/transitions.ts · 第 1–3 行(共 4 行)

   1| export function transitionQueryState<T>(value: T): T {
   2|   return value
   3| }

queryLoop 入口与 state 初始化

queryLoop L268-279 初始化 state.transition = undefined。

L265-267 注释揭示 continue 站点模式:

// Continue sites write `state = { ... }` instead of 9 separate assignments.

每个 continue 必须 spread 完整 State——遗漏字段会导致 silent reset(如 hasAttemptedReactiveCompact 被误清零)。

taskBudgetRemaining 故意不在 State 上(L289-290):7+ continue 站点不必同步更新 API task_budget 局部变量。

destructure 模式(L307-321):每 iteration 顶部从 state 解构 messages、turnCount 等;toolUseContext 可在 iteration 内 reassignment。

源码引用: src/query.ts · 第 241–279 行(共 1730 行)

 241| async function* queryLoop(
 242|   params: QueryParams,
 243|   consumedCommandUuids: string[],
 244| ): AsyncGenerator<
 245|   | StreamEvent
 246|   | RequestStartEvent
 247|   | Message
 248|   | TombstoneMessage
 249|   | ToolUseSummaryMessage,
 250|   Terminal
 251| > {
 252|   // Immutable params — never reassigned during the query loop.
 253|   const {
 254|     systemPrompt,
 255|     userContext,
 256|     systemContext,
 257|     canUseTool,
 258|     fallbackModel,
 259|     querySource,
 260|     maxTurns,
 261|     skipCacheWrite,
 262|   } = params
 263|   const deps = params.deps ?? productionDeps()
 264| 
 265|   // Mutable cross-iteration state. The loop body destructures this at the top
 266|   // of each iteration so reads stay bare-name (`messages`, `toolUseContext`).
 267|   // Continue sites write `state = { ... }` instead of 9 separate assignments.
 268|   let state: State = {
 269|     messages: params.messages,
 270|     toolUseContext: params.toolUseContext,
 271|     maxOutputTokensOverride: params.maxOutputTokensOverride,
 272|     autoCompactTracking: undefined,
 273|     stopHookActive: undefined,
 274|     maxOutputTokensRecoveryCount: 0,
 275|     hasAttemptedReactiveCompact: false,
 276|     turnCount: 1,
 277|     pendingToolUseSummary: undefined,
 278|     transition: undefined,
 279|   }

源码引用: src/query.ts · 第 306–321 行(共 1730 行)

 306|   // eslint-disable-next-line no-constant-condition
 307|   while (true) {
 308|     // Destructure state at the top of each iteration. toolUseContext alone
 309|     // is reassigned within an iteration (queryTracking, messages updates);
 310|     // the rest are read-only between continue sites.
 311|     let { toolUseContext } = state
 312|     const {
 313|       messages,
 314|       autoCompactTracking,
 315|       maxOutputTokensRecoveryCount,
 316|       hasAttemptedReactiveCompact,
 317|       maxOutputTokensOverride,
 318|       pendingToolUseSummary,
 319|       stopHookActive,
 320|       turnCount,
 321|     } = state

Continue 站点一览(recovery 与 multi-turn)

reason触发场景关键 state 变化
collapse_drain_retryCONTEXT_COLLAPSE 413 前先 drain collapsesmessages 替换为 drained
reactive_compact_retryreactiveCompact 成功压缩后重试 APIhasAttemptedReactiveCompact: true, messages 为 post-compact
max_output_tokens_escalatemax_output_tokens 且可 escalatemaxOutputTokensOverride: ESCALATED_MAX_TOKENS
max_output_tokens_recovery未 escalate 时注入 meta recovery user msgrecoveryCount++
stop_hook_blockingStop hook exit 2 blocking errorsstopHookActive: true, messages 含 blocking
token_budget_continuationTOKEN_BUDGET continuemeta nudge user message
next_turntool results 后进入下一 agentic turnturnCount++, messages 含 tool_results

guard 示例: collapse_drain_retry 要求 state.transition?.reason !== 'collapse_drain_retry'——已 drain 仍 413 则 fall through reactive compact,避免 drain 死循环。

stop_hook_blocking 保留 hasAttemptedReactiveCompact——注释 L1292-1296 描述 infinite compact loop bug 若重置为 false。

源码引用: src/query.ts · 第 1098–1115 行(共 1730 行)

1098|           if (drained.committed > 0) {
1099|             const next: State = {
1100|               messages: drained.messages,
1101|               toolUseContext,
1102|               autoCompactTracking: tracking,
1103|               maxOutputTokensRecoveryCount,
1104|               hasAttemptedReactiveCompact,
1105|               maxOutputTokensOverride: undefined,
1106|               pendingToolUseSummary: undefined,
1107|               stopHookActive: undefined,
1108|               turnCount,
1109|               transition: {
1110|                 reason: 'collapse_drain_retry',
1111|                 committed: drained.committed,
1112|               },
1113|             }
1114|             state = next
1115|             continue

源码引用: src/query.ts · 第 1152–1163 行(共 1730 行)

1152|           const next: State = {
1153|             messages: postCompactMessages,
1154|             toolUseContext,
1155|             autoCompactTracking: undefined,
1156|             maxOutputTokensRecoveryCount,
1157|             hasAttemptedReactiveCompact: true,
1158|             maxOutputTokensOverride: undefined,
1159|             pendingToolUseSummary: undefined,
1160|             stopHookActive: undefined,
1161|             turnCount,
1162|             transition: { reason: 'reactive_compact_retry' },
1163|           }

源码引用: src/query.ts · 第 1207–1220 行(共 1730 行)

1207|           const next: State = {
1208|             messages: messagesForQuery,
1209|             toolUseContext,
1210|             autoCompactTracking: tracking,
1211|             maxOutputTokensRecoveryCount,
1212|             hasAttemptedReactiveCompact,
1213|             maxOutputTokensOverride: ESCALATED_MAX_TOKENS,
1214|             pendingToolUseSummary: undefined,
1215|             stopHookActive: undefined,
1216|             turnCount,
1217|             transition: { reason: 'max_output_tokens_escalate' },
1218|           }
1219|           state = next
1220|           continue

源码引用: src/query.ts · 第 1231–1251 行(共 1730 行)

1231|           const next: State = {
1232|             messages: [
1233|               ...messagesForQuery,
1234|               ...assistantMessages,
1235|               recoveryMessage,
1236|             ],
1237|             toolUseContext,
1238|             autoCompactTracking: tracking,
1239|             maxOutputTokensRecoveryCount: maxOutputTokensRecoveryCount + 1,
1240|             hasAttemptedReactiveCompact,
1241|             maxOutputTokensOverride: undefined,
1242|             pendingToolUseSummary: undefined,
1243|             stopHookActive: undefined,
1244|             turnCount,
1245|             transition: {
1246|               reason: 'max_output_tokens_recovery',
1247|               attempt: maxOutputTokensRecoveryCount + 1,
1248|             },
1249|           }
1250|           state = next
1251|           continue

next_turn:tool 执行后的主循环边

当 assistant 含 tool_use 且 tools 执行完毕,query.ts L1714-1727 更新 state:

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
// implicit continue at end of while body when tools path loops

maxTurns 检查(L1704-1711)在 next_turn 赋值前:若 nextTurnCount > maxTurns,yield max_turns_reached attachment 并 return { reason: 'max_turns', turnCount }——这是 Terminal 而非 Continue。

tool 路径与 no-tool 路径汇合:no-tool 走 stop hooks → budget → return completed;tool 路径走 next_turn continue 开始新 iteration(可能再次 compact + API)。

源码引用: src/query.ts · 第 1704–1728 行(共 1730 行)

1704|     // Check if we've reached the max turns limit
1705|     if (maxTurns && nextTurnCount > maxTurns) {
1706|       yield createAttachmentMessage({
1707|         type: 'max_turns_reached',
1708|         maxTurns,
1709|         turnCount: nextTurnCount,
1710|       })
1711|       return { reason: 'max_turns', turnCount: nextTurnCount }
1712|     }
1713| 
1714|     queryCheckpoint('query_recursive_call')
1715|     const next: State = {
1716|       messages: [...messagesForQuery, ...assistantMessages, ...toolResults],
1717|       toolUseContext: toolUseContextWithQueryTracking,
1718|       autoCompactTracking: tracking,
1719|       turnCount: nextTurnCount,
1720|       maxOutputTokensRecoveryCount: 0,
1721|       hasAttemptedReactiveCompact: false,
1722|       pendingToolUseSummary: nextPendingToolUseSummary,
1723|       maxOutputTokensOverride: undefined,
1724|       stopHookActive,
1725|       transition: { reason: 'next_turn' },
1726|     }
1727|     state = next
1728|   } // while (true)

Terminal 出口

queryLoop 的 Terminal 不是只有正常完成。它覆盖正常结束、hook 截断、用户中断、API/recovery 失败、工具 hook 拦截等多种出口:

reason条件
completedassistant 无 tool、stop hooks 通过、budget 不 continue;或 api error 跳过 hooks
stop_hook_preventedstopHookResult.preventContinuation
max_turnsturnCount 超限
blocking_limit超 hard token 上限且无法继续 compact
aborted_streaming流式阶段收到 abort
aborted_tools工具执行阶段 abort
hook_stopped工具路径 PreToolUse 等 hook 阻止继续
image_error图片错误或 withheld media
model_errorcallModel 抛错
prompt_too_long413 recovery 耗尽

completed 双路径:

  • L1264:lastMessage.isApiErrorMessage → executeStopFailureHooks → return completed(不跑 Stop hooks,防 death spiral)
  • L1357:正常 turn 结束

stop_hook_prevented(L1278-1280):Stop hook 或 TeammateIdle/TaskCompleted 设置 preventContinuation;blocking errors 为空也可能 prevent。

外层 query() L235-237:consumedCommandUuids notifyCommandLifecycle completed——throw 或 .return() 跳过,保证 slash command 生命周期 asymmetric signal。

源码引用: src/query.ts · 第 1258–1280 行(共 1730 行)

1258|       // Skip stop hooks when the last message is an API error (rate limit,
1259|       // prompt-too-long, auth failure, etc.). The model never produced a
1260|       // real response — hooks evaluating it create a death spiral:
1261|       // error → hook blocking → retry → error → …
1262|       if (lastMessage?.isApiErrorMessage) {
1263|         void executeStopFailureHooks(lastMessage, toolUseContext)
1264|         return { reason: 'completed' }
1265|       }
1266| 
1267|       const stopHookResult = yield* handleStopHooks(
1268|         messagesForQuery,
1269|         assistantMessages,
1270|         systemPrompt,
1271|         userContext,
1272|         systemContext,
1273|         toolUseContext,
1274|         querySource,
1275|         stopHookActive,
1276|       )
1277| 
1278|       if (stopHookResult.preventContinuation) {
1279|         return { reason: 'stop_hook_prevented' }
1280|       }

源码引用: src/query.ts · 第 1355–1358 行(共 1730 行)

1355|       }
1356| 
1357|       return { reason: 'completed' }
1358|     }

transitionQueryState 与未来 step() 提取

transitions.ts 全文:

export function transitionQueryState<T>(value: T): T {
  return value
}

identity 函数是 refactor 占位:未来 reducer 可能在 commit transition 时调用 transitionQueryState(state) 做 invariant 检查或 dev-only 日志,当前 no-op 保持 zero overhead。

config.ts 注释描绘目标签名:

step(state, event, config) where config is plain data

Continue 事件对应:CompactApplied、ApiErrorRecovered、StopHookBlocking、TokenBudgetNudge、ToolResultsCommitted 等;Terminal 对应 TurnCompleted、StopHookPrevented、MaxTurnsExceeded。

迁移策略(从注释推断):

  1. ✅ 提取 config/deps
  2. ✅ 统一 State + transition reason
  3. ⬜ 把 continue 站点改为 step() 返回 { state, outcome: 'continue' | 'terminal' }
  4. ⬜ transitionQueryState 添加 runtime assert(如 messages 非空)

读源码时把每个 state = next; continue 标号,即 future event handler 列表。

源码引用: src/query/transitions.ts · 第 1–3 行(共 4 行)

   1| export function transitionQueryState<T>(value: T): T {
   2|   return value
   3| }

源码引用: src/query/config.ts · 第 8–14 行(共 47 行)

   8| // Immutable values snapshotted once at query() entry. Separating these from
   9| // the per-iteration State struct and the mutable ToolUseContext makes future
  10| // step() extraction tractable — a pure reducer can take (state, event, config)
  11| // where config is plain data.
  12| //
  13| // Intentionally excludes feature() gates — those are tree-shaking boundaries
  14| // and must stay inline at the guarded blocks for dead-code elimination.

测试:用 transition 断言 recovery

集成测试模式:

// 伪代码:收集 queryLoop 内部 state 需 hook 或 deps.callModel 序列
expect(finalTransition.reason).toBe('reactive_compact_retry')

比断言 messages 含 compact boundary 更稳定——boundary 格式可能变。

场景对照:

  • 413 → reactive compact → 应看到 transition reactive_compact_retry 后再 success
  • max_output_tokens → 先 max_output_tokens_recovery 最多 3 次,可能 escalate
  • Stop hook blocking → stop_hook_blocking continue 且 stopHookActive true

勿测 transition 顺序跨 turn——collapse_drain 与 reactive 互斥由 transition.reason guard 保证,测单次 413 输入即可。

query.ts import type { Terminal, Continue } from './query/transitions.js'——若 TS 报错缺失 export,以 query.ts runtime 行为为准;文档站源码快照 v2.1.88 可能 transitions 类型在 .d.ts 或未提取到 complete 包。

源码目录(本主题)

主状态机在 query.ts;transitions.ts 为辅助模块。

动手练习

  1. 在 query.ts 搜索 transition: 列出全部 reason 字面量
  2. 画 iteration 内 phase 时间线:compact → API → tools → stop → budget
  3. 说明 stop_hook_blocking continue 与 stop_hook_prevented return 区别
  4. 设计 step() 的 Event union 类型,覆盖表中 7 种 continue reason

本章小结与延伸

transitions = loop 边的命名与 future reducer 锚点。下一章 stop-hooks 详解 turn 结束两条 continue/return 分支。 继续学习:

  • query 模块总览
  • stop-hooks
  • config-deps
Prev
query tokenBudget · +500k 自动续跑
Next
query stopHooks · Stop 事件与 turn 结束编排