File size: 27,254 Bytes
f468102
 
 
96f34e3
f468102
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
# 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<ToolUseSummaryMessage | null> | 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<void>
  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, 权限模式 |