# Agent 循环与查询引擎深度分析 > 本文基于 `src/query.ts`、`src/QueryEngine.ts`、`src/query/*`、`src/services/tools/*` 等核心模块, > 详细阐述 Codev (Claude Code) 的 Agent 循环架构、模型调用管道、工具调度机制、恢复策略与继续决策逻辑。 > 文中列出具体文件路径、函数名称与行号,供开发者快速定位代码。 --- ## 目录 1. [主循环架构 (queryLoop)](#1-主循环架构-queryloop) 2. [模型调用管道](#2-模型调用管道) 3. [工具调度与流式执行](#3-工具调度与流式执行) 4. [Stop Hooks (后处理管道)](#4-stop-hooks-后处理管道) 5. [继续决策](#5-继续决策) 6. [恢复机制](#6-恢复机制) 7. [QueryEngine.ts 的角色](#7-queryenginets-的角色) 8. [工具执行引擎](#8-工具执行引擎) 9. [参考文件索引](#9-参考文件索引) --- ## 1. 主循环架构 (queryLoop) ### 1.1 概述 `queryLoop()` 是整个 Agent 的核心,它是一个 `AsyncGenerator`,运行在 `query()` 函数内部的 `while (true)` 无限循环中。每次迭代代表一个 **turn**(回合),包括:输入处理、模型调用、工具执行、后处理钩子、继续决策。 ``` ┌─────────────────────────────────────────────────────────────────┐ │ queryLoop (AsyncGenerator) │ │ │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │ Pre-model │──>│ Model │──>│ Tool │──>│ Stop │──> │ │ │ Context │ │Invocation│ │Dispatch │ │ Hooks │ │ │ │ Shaping │ │(Stream) │ │& Exec │ │(Post-turn)│ │ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └──────────────┴──────────────┴──────────────┘ │ │ │ │ │ ┌─────▼──────┐ │ │ │ Continuation│ │ │ │ Decision │──> continue (loop again) │ │ └─────┬──────┘ │ │ │ stop │ │ ▼ │ │ return Terminal │ └─────────────────────────────────────────────────────────────────┘ ``` **文件**: `src/query.ts` - `query()` 函数: 第 219-239 行 — 公共入口,封装 queryLoop 并处理 consumedCommandUuids - `queryLoop()` 函数: 第 241-1729 行 — 主循环体 - `QueryParams` 类型: 第 181-199 行 — 循环的输入参数 - `State` 类型: 第 204-217 行 — 跨迭代的可变状态 ### 1.2 五种核心阶段 每一个 turn 循环包含以下阶段: #### 阶段 A: Pre-model Context Shaping (第 365-548 行) 在调用模型之前,对消息列表进行一系列上下文压缩和优化: 1. **Tool Result Budget** (第 376-394 行): `applyToolResultBudget()` 限制每个消息中 tool_result 的总大小,防止工具输出膨胀。 2. **Snip Compact** (第 400-410 行): `snipCompactIfNeeded()` — 被 `HISTORY_SNIP` 特性门控,裁剪历史消息中的冗余内容。 3. **Microcompact** (第 413-426 行): `deps.microcompact()` — 对连续工具结果进行微压缩,减小上下文体积。 4. **Context Collapse** (第 440-447 行): `applyCollapsesIfNeeded()` — 被 `CONTEXT_COLLAPSE` 门控,对历史消息进行投影式折叠。 5. **Auto-compact** (第 454-543 行): `deps.autocompact()` — 全自动上下文压缩,当 Token 数超过阈值时触发。 6. **Blocking Limit Check** (第 628-648 行): 计算是否已达到硬性阻塞限制,阻止 API 调用并返回 `blocking_limit`。 #### 阶段 B: Model Invocation (第 652-863 行) 见第 2 节详细分析。 #### 阶段 C: Tool Dispatch & Execution (第 1360-1409 行) - 如果启用 `StreamingToolExecutor`,使用 `getRemainingResults()` 处理流式工具结果 - 否则使用 `runTools()` 执行工具(通过 `toolOrchestration.ts`) - 工具结果被收集到 `toolResults` 数组中 #### 阶段 D: Stop Hooks (第 1267-1306 行) `handleStopHooks()` 处理后处理管道(见第 4 节)。 #### 阶段 E: Continuation Decision (第 1308-1357 行) 根据 token 预算、stop hooks 结果等决定是否继续循环。 ### 1.3 状态管理 `queryLoop` 使用 `State` 类型(第 204-217 行)管理跨迭代的可变状态: ```typescript 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 } ``` 每次迭代开始时解构 state(第 311-321 行),在所有继续点(continue sites)通过 `state = { ... }`(第 1099-1116 行、第 1207-1221 行等)整体替换。 `transition` 字段记录上次迭代的继续原因,用于测试断言恢复路径是否正确触发。 --- ## 2. 模型调用管道 ### 2.1 callModel 实现 模型调用通过 `deps.callModel()`(第 659 行)进行,其类型为 `typeof queryModelWithStreaming`(`src/query/deps.ts` 第 23 行)。 `queryDeps.ts` (第 21-30 行) 定义了四种依赖: - `callModel`: `typeof queryModelWithStreaming` — 流式 API 调用 - `microcompact`: `typeof microcompactMessages` — 微压缩 - `autocompact`: `typeof autoCompactIfNeeded` — 自动压缩 - `uuid`: `() => string` — UUID 生成 `productionDeps()` (第 33-39 行) 提供生产环境实现。 ### 2.2 流式事件处理 模型输出的流式事件在 `for await (const message of deps.callModel({...}))` 循环中处理(第 659-863 行): | 事件类型 | 处理位置 | 说明 | |---------|---------|------| | `text_delta` | 由 claude.ts 封装 | 文本增量 | | `tool_use` | 第 829-845 行 | 提取 tool_use 块,推入 toolUseBlocks | | `content_block` | 第 748-787 行 | 处理 content block,backfill tool_use input | | `message_stop` | 第 866-892 行 | 处理缓存的微压缩边界消息 | 关键逻辑: - **Backfill tool_use input** (第 748-787 行): 当工具定义包含 `backfillObservableInput` 时,对 tool_use 块进行输入回填(如展开文件路径)。 - **Withhold 机制** (第 799-825 行): 可恢复的错误(prompt-too-long、max-output-tokens、media-size-error)在流中被扣留(withhold),不 yield 给调用方,直到恢复机制确认无法恢复后才暴露。 - **Streaming Fallback** (第 712-741 行): 当发生流式模型回退时,清空 previous assistant messages 和 tool results,创建新的 StreamingToolExecutor。 ### 2.3 max_output_tokens 恢复机制 代码位置: 第 164 行、第 1188-1256 行 ``` 恢复步骤: 1. 第1次: 设置 maxOutputTokensOverride = ESCALATED_MAX_TOKENS (64K) 重试 (第 1194-1221 行, 仅当 capEnabled 且第一次) 2. 第2-4次: 注入恢复消息 "Output token limit hit. Resume directly..." (第 1223-1252 行, 最多 3 次 = MAX_OUTPUT_TOKENS_RECOVERY_LIMIT) 3. 超出限制: 暴露扣留的错误消息并返回 (第 1254-1256 行) ``` 如果用户设置了 `CLAUDE_CODE_MAX_OUTPUT_TOKENS` 环境变量,8K→64K 的自动升级会被跳过(第 1202 行)。 ### 2.4 缓存控制 - `skipCacheWrite` 参数(第 192 行)— 传递给 API 调用选项(第 697 行),控制是否跳过缓存写入。 - `pendingCacheEdits`(第 423-425 行)— `CACHED_MICROCOMPACT` 特性门控,在 API 响应后使用实际 API 报告的 `cache_deleted_input_tokens` 生成边界消息(第 870-892 行)。 --- ## 3. 工具调度与流式执行 ### 3.1 StreamingToolExecutor 架构 **文件**: `src/services/tools/StreamingToolExecutor.ts` `StreamingToolExecutor` 是一个类(第 40-519 行),实现工具的流式执行调度。它在模型仍输出内容时就开始执行已到达的工具。 ``` 模型流式输出工具调用 │ ▼ StreamingToolExecutor.addTool() │ ├── 并发安全工具(concurrency-safe)→ 并行执行 └── 非并发安全工具 → 独占执行 │ ▼ 收集结果 → getCompletedResults() / getRemainingResults() ``` #### 核心数据结构 ```typescript type TrackedTool = { id: string block: ToolUseBlock assistantMessage: AssistantMessage status: ToolStatus // 'queued' | 'executing' | 'completed' | 'yielded' isConcurrencySafe: boolean promise?: Promise results?: Message[] pendingProgress: Message[] contextModifiers?: Array<(context: ToolUseContext) => ToolUseContext> } ``` #### 并发控制 - **`addTool()`** (第 76-124 行): 将工具加入队列,立即触发 `processQueue()`。 - **`canExecuteTool()`** (第 129-135 行): 决定是否可以执行: - 如果没有正在执行的工具 → 总是可以 - 如果工具是并发安全的且所有正在执行的工具也是并发安全的 → 可以 - 否则 → 阻塞 - **`processQueue()`** (第 140-150 行): 遍历队列,对每个 queued 工具检查执行条件。 - **`getCompletedResults()`** (第 412-440 行): 非阻塞收集已完成的结果,保持顺序(非并发工具会阻断后续工具的 yield)。 - **`getRemainingResults()`** (第 453-490 行): 等待所有工具完成,带进度唤醒。 #### Bash 错误级联 当 Bash 工具失败时(第 359-363 行),`hasErrored` 被置为 `true`,兄弟工具通过 `siblingAbortController` 被取消。这防止了在 `mkdir` 失败后继续执行依赖的命令。 #### 进度通知 - **BashProgress**: Bash 工具执行的 stdout/stderr 增量更新 - **AgentProgress**: 子 agent 执行进度 - **MCPProgress**: MCP 工具执行的进度 进度消息通过 `pendingProgress` 队列立即 yield(第 368-374 行),并通过 `progressAvailableResolve` 信号唤醒 `getRemainingResults()`。 ### 3.2 非流式工具执行 (runTools) **文件**: `src/services/tools/toolOrchestration.ts` 当 `StreamingToolExecutor` 未启用时,使用 `runTools()` 函数(第 19-82 行)。 #### 工具批处理分区 `partitionToolCalls()`(第 91-116 行)将工具调用分区为批次: - **并发安全批次**: 多个工具并行执行(通过 `runToolsConcurrently()`) - **非并发安全批次**: 单个工具串行执行(通过 `runToolsSerially()`) 最大并发数由 `CLAUDE_CODE_MAX_TOOL_USE_CONCURRENCY` 环境变量控制,默认为 10(第 8-12 行)。 --- ## 4. Stop Hooks (后处理管道) **文件**: `src/query/stopHooks.ts` ### 4.1 概要 `handleStopHooks()` 函数(第 65-473 行)在每一 turn 的模型响应结束后运行。它是一个 `AsyncGenerator`,`yield` 进度/附件消息,最终返回 `StopHookResult`。 ```typescript type StopHookResult = { blockingErrors: Message[] // 阻断性错误(钩子注入的消息) preventContinuation: boolean // 是否阻止继续循环 } ``` ### 4.2 执行流程 ``` 1. 保存 cacheSafeParams (第 96-98 行) 2. 模板作业分类 (第 108-132 行) 3. Prompt 建议 (第 139 行) [fire-and-forget] 4. Memory 提取 (第 141-153 行) [fire-and-forget] 5. Auto-dream (第 154-156 行) [fire-and-forget] 6. MCP 清理 (第 164-173 行) 7. executeStopHooks() (第 180 行) — 主钩子执行 8. Teammate hooks (第 335-453 行) ``` ### 4.3 Stop Hooks 执行 `executeStopHooks()`(第 180 行)返回一个 generator,产生进度消息和阻断错误。 钩子结果处理(第 192-295 行): - **进度消息** (第 201-215 行): 收集每个钩子的 `command` 和 `promptText` - **阻断错误** (第 257-267 行): 创建 `createUserMessage({ isMeta: true })` 作为阻断消息 - **继续阻止** (第 269-280 行): 生成 `hook_stopped_continuation` 附件 - **中止检测** (第 283-294 行): 如果被中止,返回 `{ preventContinuation: true }` ### 4.4 后台任务 | 任务 | 位置 | 说明 | |------|------|------| | `executePromptSuggestion()` | 第 139 行 | 生成提示建议(仅非 bare 模式) | | `executeExtractMemories()` | 第 141-153 行 | 提取记忆(`EXTRACT_MEMORIES` 门控) | | `executeAutoDream()` | 第 154-156 行 | 自动记忆整理 | | `cleanupComputerUseAfterTurn()` | 第 164-173 行 | MCP 计算机使用清理(`CHICAGO_MCP` 门控) | ### 4.5 模板作业分类 当设置了 `CLAUDE_JOB_DIR` 环境变量时(第 110 行),`jobClassifierModule!.classifyAndWriteState()`(第 121 行)在每次 turn 后分类作业状态。加 60 秒超时(第 127-131 行)。 ### 4.6 Teammate Hooks 在 teammate 模式下(第 335 行): 1. **TaskCompleted hooks** (第 345-400 行): 对每个 `in_progress` 且属于当前 teammate 的任务执行 `executeTaskCompletedHooks()`。 2. **TeammateIdle hooks** (第 402-441 行): 执行 `executeTeammateIdleHooks()`。 这两个钩子都支持 `preventContinuation` 和 `blockingErrors`。 ### 4.7 阻断错误与继续抑制 - `blockingErrors`: 由钩子注入的系统消息,作为当前 turn 的继续输入(第 1282-1306 行) - `preventContinuation`: 立即结束循环返回 `stop_hook_prevented`(第 1278 行) - **错误免入死亡螺旋** (第 1260-1264 行): 当 lastMessage 是 API 错误时,跳过 stop hooks --- ## 5. 继续决策 **文件**: `src/query/tokenBudget.ts` ### 5.1 checkTokenBudget ```typescript function checkTokenBudget( tracker: BudgetTracker, agentId: string | undefined, budget: number | null, // getCurrentTurnTokenBudget() globalTurnTokens: number, // getTurnOutputTokens() ): TokenBudgetDecision ``` 第 45-93 行。 ### 5.2 决策逻辑 ``` 1. 跳过条件: agentId 存在 OR budget 为 null/0 → stop (第 51-53 行) 2. 计算使用率 pct = turnTokens / budget * 100 (第 56 行) 3. 收益递减检测: 连续 3+ 次继续且每次增量 < 500 tokens (第 59-62 行) 4. 如果 pct < 90% 且非收益递减 → continue (第 66-75 行) 5. 否则 → stop (第 78-92 行) ``` ### 5.3 90% 阈值 `COMPLETION_THRESHOLD = 0.9`(第 3 行): 当 token 消耗达到预算的 90% 时触发继续。 ### 5.4 收益递减检测 `DIMINISHING_THRESHOLD = 500`(第 4 行): 当连续 3+ 次继续且每次增量 < 500 tokens,认为模型收益递减,提前停止。 ### 5.5 集成到 queryLoop 在 `queryLoop` 中(`src/query.ts` 第 1308-1355 行): ```typescript if (feature('TOKEN_BUDGET')) { const decision = checkTokenBudget(budgetTracker!, ...) if (decision.action === 'continue') { // 注入 nudgemessage // incrementBudgetContinuationCount() // 设置 state.transition = { reason: 'token_budget_continuation' } // continue (继续循环) } // 否则记录 completionEvent } // return { reason: 'completed' } ``` ### 5.6 任务预算 (taskBudget) `taskBudget`(`src/query.ts` 第 197-198 行)是 API 端的 `task_budget`(output_config.task_budget, beta task-budgets-2026-03-13)。与 `tokenBudget` +500k 自动继续的不同。 - 在每次压缩后计算 `taskBudgetRemaining`(第 508-515 行、第 1138-1146 行) - 传递给 API 调用(第 699-706 行) --- ## 6. 恢复机制 ### 6.1 max_output_tokens 恢复 **文件**: `src/query.ts` | 恢复阶段 | 触发条件 | 行为 | 行号 | |---------|---------|------|------| | 8K→64K 升级 | 首次命中上限,capEnabled 且无用户自定义 | 设置 maxOutputTokensOverride=64K,重试 | 第 1194-1221 行 | | 恢复消息注入 | 已升级或 cap 关闭 | 注入 "Output token limit hit" 消息 | 第 1223-1252 行 | | 限制耗尽 | 超过 3 次 | 暴露扣留的错误 | 第 1254-1256 行 | `MAX_OUTPUT_TOKENS_RECOVERY_LIMIT = 3`(第 164 行)。 ### 6.2 Context Collapse 压缩 **文件**: `src/query.ts` 当 API 返回 prompt-too-long 错误时(第 1085-1118 行): 1. 首先尝试 `contextCollapse.recoverFromOverflow()`(第 1094 行) — 从已分阶段(staged)的折叠队列中释放 2. 如果已经尝试过 `collapse_drain_retry`(第 1092 行),则跳过直接走 reactive compact ### 6.3 Reactive Compact **文件**: `src/query.ts` 当 prompt-too-long 错误和 reactive compact 都启用时(第 1119-1175 行): 1. 调用 `reactiveCompact.tryReactiveCompact()`(第 1120 行) 2. 成功→构建压缩消息,设置 `hasAttemptedReactiveCompact = true` 3. 失败→暴露错误消息 ### 6.4 错误恢复策略总结 ``` API 413 (prompt too long) ├── Context Collapse drain (第 1094 行) │ └── 失败→ fall through ├── Reactive Compact (第 1120 行) │ └── 失败→ 返回 prompt_too_long └── (如果两者都不可用) → 返回 blocking_limit max_output_tokens ├── 8K→64K escalate (第 1194 行) ├── 恢复消息注入 x3 (第 1223 行) └── 暴露错误 (第 1254 行) model fallback (第 893-953 行) └── 切换到备用模型,清理并重试 一般错误 (第 955-997 行) └── yield 错误消息,返回 model_error ``` --- ## 7. QueryEngine.ts 的角色 **文件**: `src/QueryEngine.ts` ### 7.1 概述 `QueryEngine` 类(第 184-1177 行)封装了查询生命周期和会话状态,是 `ask()` 函数的核心引擎。它提取了在 headless/SDK 和 REPL 间共享的逻辑。 ### 7.2 与 query.ts 的关系 ``` ask() 函数 QueryEngine.submitMessage() 方法 │ │ │ 创建 QueryEngine 实例 │ │ (第 1249-1285 行) │ │ │ └─── query() ────────────────────────────┘ │ │ AsyncGenerator ▼ yield 消息流 (assistant/user/attachment/stream_event/...) ``` - `ask()`(第 1186-1295 行)是一个便利包装器,创建 `QueryEngine` 实例并调用 `submitMessage()` - `QueryEngine.submitMessage()`(第 209-1156 行)处理完整的查询生命周期: 1. 构建 `ProcessUserInputContext`(第 335-395 行) 2. 处理用户输入(第 410-428 行) 3. 记录 transcript(第 450-463 行) 4. 调用 `query()`(第 675-686 行) 5. 处理 query 产出的所有消息类型(第 757-969 行) 6. 生成最终 result(第 1082-1155 行) ### 7.3 关键职责 - **消息持久化**: `recordTranscript()`(第 717-732 行) - **权限跟踪**: `wrappedCanUseTool()` 包装(第 244-271 行)记录权限拒绝 - **SDK 消息规范化**: `normalizeMessage()`(第 769、783、787 行) - **预算检查**: USD 预算(第 972-1002 行)和结构化输出重试限制(第 1005-1048 行) - **Snip 回放**: `snipReplay` 回调(第 905-914 行)在 SDK 模式下处理 snip 边界 ### 7.4 submitMessage 的消息处理 `submitMessage()` 的 `for await` 循环处理 10+ 种消息类型: | 消息类型 | 处理 | 行号 | |---------|------|------| | `assistant` | push 到 mutableMessages,yield 规范化 | 第 761-769 行 | | `user` | push,yield 规范化,turnCount++ | 第 753-787 行 | | `progress` | push,记录 transcript | 第 771-783 行 | | `stream_event` | 累积 usage,跟踪 stop_reason | 第 788-827 行 | | `attachment` | 处理结构化输出、max_turns、queued_command | 第 829-893 行 | | `system` | 处理 compact_boundary、api_error、snip | 第 897-958 行 | | `tool_use_summary` | yield 工具使用摘要 | 第 959-969 行 | --- ## 8. 工具执行引擎 **文件**: `src/services/tools/toolExecution.ts` ### 8.1 工具执行九步生命周期 `runToolUse()` 函数(第 337-490 行)实现工具的完整执行生命周期: ``` 1. Tool Lookup & Validation │ findToolByName() (第 345 行) │ 别名回退 (第 350-355 行) ▼ 2. Abort Check │ abortController.signal.aborted (第 415 行) ▼ 3. Input Validation (Zod) │ tool.inputSchema.safeParse() (第 615 行) ▼ 4. Custom Validation │ tool.validateInput() (第 683 行) ▼ 5. Pre-Tool Hooks │ runPreToolUseHooks() (第 800 行) ▼ 6. Permission Check & User Confirmation │ resolveHookPermissionDecision() (第 921 行) │ canUseTool() (第 927 行) ▼ 7. Tool Execution │ tool.call() (第 1207 行) ▼ 8. Result Processing │ tool.mapToolResultToToolResultBlockParam() (第 1292 行) │ processToolResultBlock() (第 1415 行) ▼ 9. Post-Tool Hooks │ runPostToolUseHooks() (第 1483 行) │ runPostToolUseFailureHooks() (第 1700 行) ``` ### 8.2 详细步骤解析 #### 步骤 1-2: 工具查找与中止检查 `runToolUse()`(第 337-490 行): ```typescript // 1. 查找工具 (第 345-355 行) let tool = findToolByName(toolUseContext.options.tools, toolName) if (!tool) { // 通过别名回退 (第 350-355 行) } // 2. 中止检查 (第 415-453 行) if (toolUseContext.abortController.signal.aborted) { // yield "cancelled" 消息 } ``` #### 步骤 3-4: 输入验证 `checkPermissionsAndCallTool()`(第 599-1745 行): **Zod Schema 验证**(第 615-679 行): - `tool.inputSchema.safeParse(input)` 使用 Zod 验证模型输入 - 失败时生成格式化的 Zod 错误并附加 schema-not-sent 提示(第 578-597 行) - `buildSchemaNotSentHint()`: 检测到延迟工具(deferred tool)的 schema 没有被发送到 API 时,提示模型使用 ToolSearch 重新加载 **自定义验证**(第 683-733 行): - 每个工具可以定义自己的 `validateInput()` 方法 - 验证失败返回 `isValidCall.result === false` #### 步骤 5: Pre-Tool Hooks **文件**: `src/services/tools/toolHooks.ts` `runPreToolUseHooks()`(第 435-650 行): 返回多类型结果: | 结果类型 | 说明 | |---------|------| | `message` | 进度消息或附件消息 | | `hookPermissionResult` | 钩子做出的权限决定 (allow/deny/ask) | | `hookUpdatedInput` | 钩子修改后的输入 (passthrough) | | `preventContinuation` | 阻止继续 | | `stopReason` | 停止原因 | | `additionalContext` | 额外的上下文消息 | | `stop` | 立即停止 | #### 步骤 6: 权限检查 **文件**: `src/services/tools/toolHooks.ts` `resolveHookPermissionDecision()`(第 332-433 行): - **Hook allow**: 仍然检查 settings.json 的 deny/ask 规则(第 373-385 行) - **Hook deny**: 直接拒绝(第 408-411 行) - **无钩子决定**: 走正常权限流程,可能包含 forceDecision(第 413-432 行) - **需用户交互**: 钩子批准后如果 `requiresUserInteraction()` 或 `requireCanUseTool`,仍调用 `canUseTool()`(第 356-370 行) `canUseTool` 在 `src/hooks/useCanUseTool.tsx` 中实现(React hook),处理 interactive/coordinator/swarm 三种权限模式。 #### 步骤 7: 工具执行 `tool.call()`(第 1207 行): - 使用处理后的输入调用工具 - 通过 `onToolProgress` 回调报告进度 - 使用 `toolAbortController`(第 301 行)实现 per-tool 取消 Bash 错误级联(第 359-363 行): ```typescript if (tool.block.name === BASH_TOOL_NAME) { this.hasErrored = true this.siblingAbortController.abort('sibling_error') } ``` #### 步骤 8: 结果处理 - `tool.mapToolResultToToolResultBlockParam()`(第 1292 行)映射工具结果 - `processPreMappedToolResultBlock()` / `processToolResultBlock()`(第 1409-1415 行)进行后处理 - `applyToolResultBudget()`(第 379 行)限制工具结果大小 #### 步骤 9: Post-Tool Hooks **文件**: `src/services/tools/toolHooks.ts` `runPostToolUseHooks()`(第 39-191 行): - 对 MCP 工具,支持 `updatedMCPToolOutput` - 支持 `blockingError`、`preventContinuation`、`additionalContext` `runPostToolUseFailureHooks()`(第 193-319 行): - 工具失败时执行 - 同样支持 `blockingError`、`preventContinuation`、`additionalContext` ### 8.3 错误分类与处理 `classifyToolError()`(第 150-171 行)将错误分类为安全的 telemetry 字符串: - `TelemetrySafeError`: 使用预审的 telemetryMessage - Node.js `errno` 错误: 记录 `ENOENT` 等代码 - 已知错误类型: 使用构造函数名称 - 未知错误: 降级为 `"Error"` ### 8.4 工具遥测 工具执行的每个阶段都会发出遥测事件: | 事件 | 触发时机 | 代码位置 | |------|---------|---------| | `tengu_tool_use_error` | 工具不存在 | 第 372 行 | | `tengu_tool_use_cancelled` | 工具被取消 | 第 416 行 | | `tengu_tool_use_progress` | 进度更新 | 第 523 行 | | `tengu_tool_use_can_use_tool_rejected` | 权限拒绝 | 第 1001 行 | | `tengu_tool_use_can_use_tool_allowed` | 权限批准 | 第 1105 行 | | `tengu_tool_use_success` | 工具执行成功 | 第 1331 行 | | `tool_decision` (OTel) | 权限决策 | 第 962 行 | | `tool_result` (OTel) | 工具结果 | 第 1381 行 | --- ## 9. 参考文件索引 | 文件 | 路径 | 核心内容 | |------|------|---------| | 主查询循环 | `src/query.ts` | `query()`, `queryLoop()`, 完整 Agent 循环 | | 查询配置 | `src/query/config.ts` | `buildQueryConfig()`, `QueryConfig` 类型 | | 查询依赖 | `src/query/deps.ts` | `QueryDeps`, `productionDeps()` | | 停止钩子 | `src/query/stopHooks.ts` | `handleStopHooks()`, StopHookResult | | Token 预算 | `src/query/tokenBudget.ts` | `checkTokenBudget()`, `BudgetTracker` | | 转换类型 | `src/query/transitions.ts` | `Terminal`, `Continue` 类型 | | 查询引擎 | `src/QueryEngine.ts` | `QueryEngine` 类, `ask()` 函数 | | 工具流式执行器 | `src/services/tools/StreamingToolExecutor.ts` | `StreamingToolExecutor` 类 | | 工具执行 | `src/services/tools/toolExecution.ts` | `runToolUse()`, `checkPermissionsAndCallTool()` | | 工具编排 | `src/services/tools/toolOrchestration.ts` | `runTools()`, `partitionToolCalls()` | | 工具钩子 | `src/services/tools/toolHooks.ts` | `runPreToolUseHooks()`, `runPostToolUseHooks()`, `resolveHookPermissionDecision()` | | 权限检查 | `src/hooks/useCanUseTool.tsx` | `useCanUseTool()` React hook, 权限模式 |