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

本章总览

query/config.ts 与 query/deps.ts 是 query 循环的两条「测试与演进接缝」:QueryConfig 在 query() 入口一次性快照 session 与 Statsig/env 门控;QueryDeps 把 callModel、microcompact、autocompact、uuid 四类 I/O 收口,测试通过 QueryParams.deps 注入 fake 而无需 spyOn 整模块图。本章带源码走读,要求你能说明为何 feature() 门控不能进 config。

学完本章你应该能

  • 解释 QueryConfig 字段与 buildQueryConfig 的调用时机
  • 说明 gates 中 streamingToolExecution / fastModeEnabled 等在 loop 中的消费点
  • 掌握 QueryDeps 四依赖与 productionDeps 工厂
  • 能在测试中构造最小 deps 替身跑 queryLoop
  • 理解 deps 与 config 分离的设计动机(immutable vs injectable I/O)

核心概念(先读懂这些)

Config 是 plain data,不是 service locator

buildQueryConfig() 在 queryLoop 开头调用一次,结果存入 const config。循环内只读 config.gates.* 与 config.sessionId。注释明确:未来 step() reducer 签名是 (state, event, config)。因此 config 不得持有函数或可变引用——否则 reducer 无法纯化。

feature() 门控必须留在 guarded block 内

QueryConfig 注释:intentionally excludes feature() gates — tree-shaking boundaries。Bun bundle 的 feature() 在编译期消除死分支;若把 feature 结果存进 config 对象,bundler 可能无法 DCE 整个分支。Statsig CACHED_MAY_BE_STALE 门控可以快照,因为已有「可能过期」契约。

Deps 模式:typeof fn 保持签名同步

QueryDeps 用 typeof queryModelWithStreaming 等标注,productionDeps 直接引用真实实现。deps.ts 注释:tests importing for typing already import query.ts — no new module-graph cost。范围刻意收窄为 4 项;后续 PR 可加 runTools、handleStopHooks 等。

建议学习步骤

  1. 阅读 QueryConfig 类型与 buildQueryConfig 源码块 A
  2. 在 query.ts 中 grep config.gates 与 deps. 的所有消费点
  3. 阅读 QueryDeps 类型与 productionDeps 源码块 B
  4. 阅读 queryLoop 入口:deps 默认与 config 快照源码块 C
  5. 对照 mod-services/compact 理解 autocompact 参数列表

常见误区

注意

不要把 fastMode.ts 整模块 import 进 config——注释说明会破坏 test shard 初始化顺序

注意

deps 省略时默认 productionDeps(),集成测试忘记注入会导致真实 API 调用

注意

config.sessionId 与 toolUseContext.agentId 不同:后者标识 subagent

在 queryLoop 中的位置

queryLoop 解构 params 后立即执行:

const deps = params.deps ?? productionDeps()
let state: State = { messages, toolUseContext, ... }
const config = buildQueryConfig()
while (true) {
  // 每轮迭代顶部使用 deps.microcompact / deps.autocompact
  // API 阶段 deps.callModel(...)
  // queryTracking.chainId 首次用 deps.uuid()
}

QueryParams.deps 可选;生产路径不传,测试传入 { callModel: async function*(){...}, ... }。

config 每轮 query 调用只 build 一次,跨 while 迭代共享——Statsig 值在 turn 内视为常量,符合 CACHED_MAY_BE_STALE 语义。

QueryConfig 类型与 buildQueryConfig

config.ts 顶部注释是理解整个 query 模块化路线的关键文档:

  1. Immutable values snapshotted once at query() entry
  2. Separating from State and ToolUseContext makes step() extraction tractable
  3. Excludes feature() gates for dead-code elimination

gates 字段:

字段来源loop 内用途
streamingToolExecutionStatsig tengu_streaming_tool_execution2是否用 StreamingToolExecutor
emitToolUseSummariesCLAUDE_CODE_EMIT_TOOL_USE_SUMMARIES工具摘要生成
isAntUSER_TYPE === antcreateDumpPromptsFetch
fastModeEnabled!DISABLE_FAST_MODE envcallModel fast mode 参数

sessionId 来自 getSessionId(),用于 analytics 与 dump prompts 路径。

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

   6| // -- config
   7| 
   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.
  15| export type QueryConfig = {
  16|   sessionId: SessionId
  17| 
  18|   // Runtime gates (env/statsig). NOT feature() gates — see above.
  19|   gates: {
  20|     // Statsig — CACHED_MAY_BE_STALE already admits staleness, so snapshotting
  21|     // once per query() call stays within the existing contract.
  22|     streamingToolExecution: boolean
  23|     emitToolUseSummaries: boolean
  24|     isAnt: boolean
  25|     fastModeEnabled: boolean
  26|   }
  27| }

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

  29| export function buildQueryConfig(): QueryConfig {
  30|   return {
  31|     sessionId: getSessionId(),
  32|     gates: {
  33|       streamingToolExecution: checkStatsigFeatureGate_CACHED_MAY_BE_STALE(
  34|         'tengu_streaming_tool_execution2',
  35|       ),
  36|       emitToolUseSummaries: isEnvTruthy(
  37|         process.env.CLAUDE_CODE_EMIT_TOOL_USE_SUMMARIES,
  38|       ),
  39|       isAnt: process.env.USER_TYPE === 'ant',
  40|       // Inlined from fastMode.ts to avoid pulling its heavy module graph
  41|       // (axios, settings, auth, model, oauth, config) into test shards that
  42|       // didn't previously load it — changes init order and breaks unrelated tests.
  43|       fastModeEnabled: !isEnvTruthy(process.env.CLAUDE_CODE_DISABLE_FAST_MODE),
  44|     },
  45|   }
  46| }

config.gates 在 query.ts 的消费点

buildQueryConfig 本身无逻辑分支;价值在 query.ts 多处读取:

streamingToolExecution(约 L561):const useStreamingToolExecution = config.gates.streamingToolExecution 决定是否构造 StreamingToolExecutor,流式并行执行 tool_use block。

isAnt + sessionId(约 L588):Ant 内部 dogfood 时 createDumpPromptsFetch(agentId ?? config.sessionId) 把 prompt 落盘调试。

fastModeEnabled(约 L671):spread 进 callModel 参数,启用 fast mode sampling。

emitToolUseSummaries(约 L1416):assistant 无 further tool_use 时异步 generateToolUseSummary。

读 config 章时应在 IDE 对 query.ts 全局搜索 config.gates,确认没有遗漏 gate。

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

 293|   // Snapshot immutable env/statsig/session state once at entry. See QueryConfig
 294|   // for what's included and why feature() gates are intentionally excluded.
 295|   const config = buildQueryConfig()
 296| 

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

 559| 
 560|     queryCheckpoint('query_setup_start')
 561|     const useStreamingToolExecution = config.gates.streamingToolExecution
 562|     let streamingToolExecutor = useStreamingToolExecution

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

 657|           let streamingFallbackOccured = false
 658|           queryCheckpoint('query_api_streaming_start')
 659|           for await (const message of deps.callModel({
 660|             messages: prependUserContext(messagesForQuery, userContext),
 661|             systemPrompt: fullSystemPrompt,
 662|             thinkingConfig: toolUseContext.options.thinkingConfig,
 663|             tools: toolUseContext.options.tools,
 664|             signal: toolUseContext.abortController.signal,
 665|             options: {
 666|               async getToolPermissionContext() {
 667|                 const appState = toolUseContext.getAppState()
 668|                 return appState.toolPermissionContext
 669|               },
 670|               model: currentModel,
 671|               ...(config.gates.fastModeEnabled && {
 672|                 fastMode: appState.fastMode,
 673|               }),
 674|               toolChoice: undefined,
 675|               isNonInteractiveSession:

QueryDeps 与 productionDeps

deps.ts 定义四类依赖:

键类型来源职责
callModelqueryModelWithStreaming流式 Anthropic API
microcompactmicrocompactMessages轮前微压缩
autocompactautoCompactIfNeeded超阈值自动 compact
uuidrandomUUIDquery chain id / turn id

注释强调:today 6-8 个测试文件各自 spyOn callModel/autocompact——deps 注入可删除 boilerplate。

productionDeps() 是纯工厂,无 lazy init。测试典型写法:

await collectAsync(query({
  ...baseParams,
  deps: {
    ...productionDeps(),
    callModel: fakeStreamingModel,
  },
}))

源码引用: src/query/deps.ts · 第 6–31 行(共 41 行)

   6| // -- deps
   7| 
   8| // I/O dependencies for query(). Passing a `deps` override into QueryParams
   9| // lets tests inject fakes directly instead of spyOn-per-module — the most
  10| // common mocks (callModel, autocompact) are each spied in 6-8 test files
  11| // today with module-import-and-spy boilerplate.
  12| //
  13| // Using `typeof fn` keeps signatures in sync with the real implementations
  14| // automatically. This file imports the real functions for both typing and
  15| // the production factory — tests that import this file for typing are
  16| // already importing query.ts (which imports everything), so there's no
  17| // new module-graph cost.
  18| //
  19| // Scope is intentionally narrow (4 deps) to prove the pattern. Followup
  20| // PRs can add runTools, handleStopHooks, logEvent, queue ops, etc.
  21| export type QueryDeps = {
  22|   // -- model
  23|   callModel: typeof queryModelWithStreaming
  24| 
  25|   // -- compaction
  26|   microcompact: typeof microcompactMessages
  27|   autocompact: typeof autoCompactIfNeeded
  28| 
  29|   // -- platform
  30|   uuid: () => string
  31| }

源码引用: src/query/deps.ts · 第 33–40 行(共 41 行)

  33| export function productionDeps(): QueryDeps {
  34|   return {
  35|     callModel: queryModelWithStreaming,
  36|     microcompact: microcompactMessages,
  37|     autocompact: autoCompactIfNeeded,
  38|     uuid: randomUUID,
  39|   }
  40| }

deps 在循环内的调用链

每轮 iteration 开头按固定顺序调用 deps:

  1. deps.uuid() — 初始化 queryTracking.chainId(depth 0 时)
  2. deps.microcompact(messages, ctx, querySource) — L414,在 snip 之后 autocompact 之前
  3. deps.autocompact(...) — L454,可能 yield compact 消息并 continue
  4. deps.callModel({...}) — L659,主 API 流

autocompact 返回 compactionResult 时 query.ts 会 buildPostCompactMessages、更新 taskBudgetRemaining、设置 transition reason 后 continue——compact 章有完整说明;此处只需知 deps.autocompact 是 loop 中唯一主动 mutate messages 的 deps 调用(callModel 产出新 assistant 消息经 yield 收集)。

microcompact 与 autocompact 分离的原因:microcompact cheap、可每轮跑;autocompact fork 子 Agent 昂贵且带 recursion guard。

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

 263|   const deps = params.deps ?? productionDeps()
 264| 

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

 347|     const queryTracking = toolUseContext.queryTracking
 348|       ? {
 349|           chainId: toolUseContext.queryTracking.chainId,
 350|           depth: toolUseContext.queryTracking.depth + 1,
 351|         }
 352|       : {
 353|           chainId: deps.uuid(),
 354|           depth: 0,
 355|         }

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

 412|     // Apply microcompact before autocompact
 413|     queryCheckpoint('query_microcompact_start')
 414|     const microcompactResult = await deps.microcompact(
 415|       messagesForQuery,
 416|       toolUseContext,
 417|       querySource,
 418|     )
 419|     messagesForQuery = microcompactResult.messages

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

 453|     queryCheckpoint('query_autocompact_start')
 454|     const { compactionResult, consecutiveFailures } = await deps.autocompact(
 455|       messagesForQuery,
 456|       toolUseContext,
 457|       {
 458|         systemPrompt,
 459|         userContext,
 460|         systemContext,
 461|         toolUseContext,
 462|         forkContextMessages: messagesForQuery,
 463|       },
 464|       querySource,
 465|       tracking,
 466|       snipTokensFreed,
 467|     )
 468|     queryCheckpoint('query_autocompact_end')

QueryParams 中的 deps 与 taskBudget

QueryParams(query.ts L181-199)除 deps 外还有 taskBudget、maxTurns、querySource 等。注意注释区分:

  • taskBudget — API output_config.task_budget(beta),与 TOKEN_BUDGET feature 的 +500k 自动续跑无关
  • deps — 仅 I/O 边界,不包含 canUseTool(仍走 QueryParams 顶层)

taskBudgetRemaining 是 queryLoop 局部变量(非 State 字段),刻意不放进 7 个 continue 站点,避免每次 state spread 都要维护。compact 后从 finalContextTokensFromLastResponse 扣减 remaining。

设计表:

概念存储位置可变?
QueryConfigconst configturn 内不变
QueryDepsparams.depsturn 内不变
Statelet statecontinue 站点替换
taskBudgetRemaininglet 局部compact 时更新

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

 181| export type QueryParams = {
 182|   messages: Message[]
 183|   systemPrompt: SystemPrompt
 184|   userContext: { [k: string]: string }
 185|   systemContext: { [k: string]: string }
 186|   canUseTool: CanUseToolFn
 187|   toolUseContext: ToolUseContext
 188|   fallbackModel?: string
 189|   querySource: QuerySource
 190|   maxOutputTokensOverride?: number
 191|   maxTurns?: number
 192|   skipCacheWrite?: boolean
 193|   // API task_budget (output_config.task_budget, beta task-budgets-2026-03-13).
 194|   // Distinct from the tokenBudget +500k auto-continue feature. `total` is the
 195|   // budget for the whole agentic turn; `remaining` is computed per iteration
 196|   // from cumulative API usage. See configureTaskBudgetParams in claude.ts.
 197|   taskBudget?: { total: number }
 198|   deps?: QueryDeps
 199| }

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

 282|   // task_budget.remaining tracking across compaction boundaries. Undefined
 283|   // until first compact fires — while context is uncompacted the server can
 284|   // see the full history and handles the countdown from {total} itself (see
 285|   // api/api/sampling/prompt/renderer.py:292). After a compact, the server sees
 286|   // only the summary and would under-count spend; remaining tells it the
 287|   // pre-compact final window that got summarized away. Cumulative across
 288|   // multiple compacts: each subtracts the final context at that compact's
 289|   // trigger point. Loop-local (not on State) to avoid touching the 7 continue
 290|   // sites.
 291|   let taskBudgetRemaining: number | undefined = undefined

测试与演进:为何先拆 config/deps

query.ts 行数仍超 1700,团队采用「窄接缝优先」:

已提取: config、deps、tokenBudget、stopHooks、transitions 占位

仍 inline: tool 执行、413 reactive compact、max_output_tokens recovery、attachment 管线

extract step() 的前置条件是可注入 config + deps + 显式 State.transition。读源码时看到 state = next; continue 应联想到:此处本应是 state = reduce(state, event, config)。

测试策略:

  • 单元测 checkTokenBudget / buildQueryConfig — 直接 import 子模块
  • 集成测 query loop — 注入 deps.callModel 返回固定 assistant JSON
  • 勿 mock buildQueryConfig 除非测 gate 组合——Statsig 应用 growthbook test helper

fastMode 内联原因: config.ts L40-43 把 fastModeEnabled 写成 env 检查而非 import fastMode.ts,避免 axios/settings/auth 链进入 previously-unloaded test shards。这是「配置模块也要控制 module graph」的典型案例。

源码目录(本主题)

query.ts 在树中根级高亮;config/deps 在 query/ 子目录。

动手练习

  1. 在 query.ts 列出所有 deps. 与 config. 引用,制表对照本章
  2. 写一个 fake callModel generator 返回单条 text assistant message,跑通 query 无 tool
  3. 阅读 mod-services/compact 的 shouldAutoCompact guard,理解 deps.autocompact 何时 no-op
  4. 思考:若把 runTools 加入 QueryDeps,签名应如何 typedef 才能与 toolOrchestration 同步

本章小结与延伸

config = 每 turn immutable 快照;deps = 可替换 I/O。下一章读 transitions,理解 state 如何在 continue 站点批量更新。 继续学习:

  • query 模块总览
  • transitions
  • compact 服务
Prev
模块: query
Next
query tokenBudget · +500k 自动续跑