File size: 25,625 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
# 横切关注点 — 错误处理、日志、遥测与性能

## 1. 错误处理架构

### 1.1 错误类型层次

代码库定义了一个多层级的错误类体系,所有自定义错误最终继承自 `Error`- **基类 `ClaudeError`** (`src/utils/errors.ts`) — 设置 `this.name = this.constructor.name`,是所有内部错误的根类。
- **中止错误**`AbortError` 和 SDK 的 `APIUserAbortError` 通过 `isAbortError()` 统一检测;该函数也兼容 DOMException 的 `name === 'AbortError'` 模式。
- **Shell 错误**`ShellError` 携带 `stdout``stderr``code``interrupted` 四个字段,用于统一处理子进程失败。
- **配置解析错误**`ConfigParseError` 携带 `filePath` 和后备的 `defaultConfig`- **Axios 错误分类**`classifyAxiosError()` 将 HTTP 客户端错误归类为 `auth``timeout``network``http``other` 五种类型。
- **遥测安全错误**`TelemetrySafeError_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS` 在构造时分离用户可见消息和遥测安全消息。

领域特定错误分布在各个模块中:

- `StopTaskError` (`src/tasks/stopTask.ts`) — 带有 `code` 字段区分 `not_found``not_running``unsupported_type`- `ToolExecutionError` — 工具执行过程中的运行时错误。
- `BridgeFatalError` / `BridgeHeadlessPermanentError` (`src/bridge/`) — Bridge 模式下的致命/永久错误。
- `CannotRetryError` / `FallbackTriggeredError` (`src/services/api/withRetry.ts`) — API 重试耗尽或模型降级时的错误。
- `McpAuthError` / `XaaTokenExchangeError` (`src/services/mcp/`) — MCP 协议认证相关错误。
- 辅助函数 `toError()``errorMessage()``shortErrorStack()``isFsInaccessible()` 提供了通用的错误处理基础设施。

### 1.2 工具执行错误

工具调用执行时产生的错误通过 Task 系统的 catch 边界捕获。每个 Task 类型(`LocalShellTask``LocalAgentTask``InProcessTeammateTask`)实现自己的错误处理逻辑:

- **`AbortError`** 在 agent 任务中被特殊处理 — 提取部分结果 (`extractPartialResult`) 后生成通知而非静默丢弃。Bash 任务中则静默抑制 "exit code 137" 通知以减少噪音。
- **`StopTaskError`** 区分任务不存在、未在运行和不支持的类型,让调用方可以根据 `error.code` 决定处理策略。
- **`MaxFileReadTokenExceededError`** (`src/tools/FileReadTool/`) — 文件读取超过 token 上限时抛出,触发调用方改用分块读取。
- **`SessionBranchingError`** (`src/utils/sessionBranching.ts`) — 会话分支操作失败时的特定错误。
- **`ConversationStartupError`** (`src/server/services/conversationService.ts`) — 服务器端会话恢复失败时的错误。
- 工具执行器在上层 catch 边界中将错误序列化为 `tool_result` 内容,确保 LLM 可以感知错误并调整行为。

### 1.3 模型调用错误与恢复策略

`withRetry()` (`src/services/api/withRetry.ts`) 实现了完整的 API 调用重试引擎:

- **默认最大重试次数** 为 10(可通过 `CLAUDE_CODE_MAX_RETRIES` 环境变量覆盖)。
- **错误分类**: 529 过载、429 速率限制(含 fast-mode overage)、401 认证过期、403 token 吊销、ECONNRESET/EPIPE 连接中断、400 max_tokens context overflow。
- **退避策略**: 指数退避 + 随机 jitter(`BASE_DELAY_MS * 2^(attempt-1) + jitter`),优先使用 `Retry-After` 响应头。
- **Fast Mode 降级**: 短延迟直接重试(保留 prompt cache),长延迟触发 cooldown 切换为标准速度。
- **模型降级**: 连续 3 次 529 错误后触发 `FallbackTriggeredError`,切换到 fallback 模型。
- **持久模式** (`CLAUDE_CODE_UNATTENDED_RETRY`): 无限重试 429/529,最大退避 5 分钟,6 小时上限,每 30 秒输出心跳防止会话超时。
- **背景请求不重试**: 非前台 `QuerySource`(如摘要、标题、建议)在 529 时直接放弃,避免容量级联放大。`FOREGROUND_529_RETRY_SOURCES` 集合中仅包含用户等待结果的查询源。
- **认证错误处理链**: 401/403 错误触发 OAuth token 刷新 (`handleOAuth401Error`)、AWS credential cache 清理 (`clearAwsCredentialsCache`)、GCP credential cache 清理 (`clearGcpCredentialsCache`)。CCR 模式下 401/403 被视为瞬态错误(网络抖动)而非坏凭证。
- **Context Overflow 恢复**: `parseMaxTokensContextOverflowError()` 解析 "input length and `max_tokens` exceed context limit" 错误消息,提取 `inputTokens``contextLimit`,自动计算调整后的 `max_tokens` 并重试。至少保留 1000 token 安全缓冲区和 3000 输出 token 下限。
- **连接池管理**: ECONNRESET/EPIPE 错误触发 `disableKeepAlive()`,在重试时创建新连接而非复用可能损坏的 keep-alive socket。
- **Mock 错误集成**: Ant 员工通过 `/mock-limits` 命令触发的模拟速率限制错误 (`checkMockRateLimitError`) 在重试引擎中受到特殊处理 — 不被视为可重试错误,确保测试场景不会进入无限重试循环。

### 1.4 致命错误 vs 可恢复错误

- **可恢复错误**: 网络抖动、速率限制、认证过期、token 吊销 — 通过重试、token 刷新、连接池禁用等机制自动恢复。
- **致命错误**: `BridgeFatalError``CannotRetryError`(重试耗尽后)、配置解析失败 — 需要用户干预或进程重启。

---

## 2. 日志系统

### 2.1 控制台日志与文件日志

- **`logForDebugging()`** (`src/utils/debug.ts`) — 双重输出到 stderr 和文件。支持日志级别过滤(`CLAUDE_CODE_DEBUG_LOG_LEVEL` 从 verbose 到 error),`CLAUDE_CODE_DEBUG` 环境变量启用输出。`CLAUDE_CODE_DEBUG_FILTER` 按模块名称过滤减少噪音。
- **缓冲写入**: `BufferedWriter` 模式将高频日志积累到内存缓冲区后批量写入磁盘,减少细粒度 I/O 的系统调用开销。日志文件位于 `{CLAUDE_CONFIG_HOME}/debug.log`,使用符号链接跟踪最新会话,便于快速定位当前 session 的日志。
- **`logError()`** (`src/utils/log.ts`) — 专用错误日志函数,捕获完整错误栈和结构化元数据,输出到标准错误流。
- **日志文件轮转**: 通过 `getClaudeConfigHomeDir()` 确定日志目录路径,`registerCleanup` 在进程退出时确保缓冲区排空。CLAUDE_CODE_FORCE_FULL_LOGO 等构建时常量控制日志展示格式。
- **调试过滤器**: `parseDebugFilter()``shouldShowDebugMessage()` 允许按来源模块名称精确控制哪些调试消息可见,在开发高噪音模块时可以只关注特定子系统的输出。

### 2.2 LogSelector UI 组件 (`src/components/LogSelector.tsx`)

一个功能完整的日志浏览器,提供:

- **模糊搜索** (Fuse.js) — 按会话标题、摘要、内容搜索。
- **标签分类** — 按 agent 名称、自定义标签、项目过滤。
- **树形浏览** — 按日期层级组织日志。
- **对话预览** — 在侧面板显示选中会话的摘要消息。
- **智能搜索** — 可以通过 AI 驱动的语义搜索查找相关日志。
- **分页加载** (`onLoadMore`) — 支持无限滚动。

### 2.3 诊断工具 (`/doctor` 命令 & DiagnosticsService)

- **`DiagnosticsService`** (`src/server/services/diagnosticsService.ts`) — 捕获 `console.error``console.warn``process.on('uncaughtException')``process.on('unhandledRejection')`,将诊断事件写入文件系统。7 天保留期,50MB 上限。
- **REST API** (`src/server/api/diagnostics.ts`) — 提供 `GET /api/diagnostics/status``GET /api/diagnostics/events``POST /api/diagnostics/export` 等端点。导出为压缩的 tar.gz,自动脱敏 API key、token 等敏感信息。
- **`DiagnosticsTrackingError`** (`src/services/diagnosticTracking.ts`) — 内部跟踪诊断事件类型的错误。

---

## 3. 遥测与分析

### 3.1 GrowthBook 集成 (`src/services/analytics/growthbook.ts`)

GrowthBook 提供 feature flags 和 A/B 测试能力:

- **远程评估**: `remoteEval: true` 模式下,服务器端评估 feature flag 值,客户端缓存到内存 (`remoteEvalFeatureValues`) 和磁盘 (`cachedGrowthBookFeatures``~/.claude.json`)。
- **用户属性**: 发送 `deviceID``platform``organizationUUID``accountUUID``subscriptionType``rateLimitTier` 等用于定向。
- **缓存策略**:
  - `getFeatureValue_CACHED_MAY_BE_STALE()` — 非阻塞,优先内存缓存,后备磁盘缓存,适用于启动关键路径。
  - `getDynamicConfig_BLOCKS_ON_INIT()` — 阻塞直到初始化完成(最多 5 秒超时)。
  - `checkSecurityRestrictionGate()` — 安全检查相关 gate,等待 re-init 完成以确保值新鲜。
  - `checkGate_CACHED_OR_BLOCKING()` — 磁盘缓存为 true 时快速返回,false 时等待服务器确认(避免 false 误阻止用户功能)。
- **实验曝光**: 每个 feature 在一个 session 内只记录一次曝光事件 (`loggedExposures` 去重),通过 `logGrowthBookExperimentTo1P` 发送到第一方遥测。
- **周期刷新**: 非 Ant 构建每 6 小时刷新,Ant 构建每 20 分钟刷新。刷新时重建 `remoteEvalFeatureValues` 并同步到磁盘。
- **认证变更**: `refreshGrowthBookAfterAuthChange()` 销毁旧客户端并使用新认证头重建,防止 API key 变更后返回过期值。
- **环境变量覆盖**: Ant 员工可通过 `CLAUDE_INTERNAL_FC_OVERRIDES` 全局覆盖 feature flag(用于评估工具)。

### 3.2 遥测 Sink 架构 (`src/services/analytics/sink.ts`)

分析系统采用 sink 模式,支持热插拔遥测后端:

- **`AnalyticsSink` 接口** (`src/services/analytics/index.ts`) — 定义 `logEvent()` 同步和 `logEventAsync()` 异步方法,以及 `attachAnalyticsSink()` 注册函数。OSS 构建中所有实现体为空(no-op),降低二进制体积。
- **`initializeAnalyticsGates()` / `initializeAnalyticsSink()`** (`src/services/analytics/sink.ts`) — OSS 构建中为空函数。在完整构建中会初始化 GrowthBook 和 Datadog 后端。
- **`sinkKillswitch.ts`** — 通过 GrowthBook feature flag 在运行时动态禁用遥测发送,用于紧急情况下的数据收集开关。
- **元数据标记**`AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS` 类型确保只有在调用方显式声明"此元数据不包含敏感信息"时才能传递,编译期防止数据泄露。

### 3.3 第一方事件日志 (`src/services/analytics/firstPartyEventLogger.ts`)

Ant 内部构建使用第一方事件日志系统:

- **实验曝光日志**: `logGrowthBookExperimentTo1P()` 记录 A/B 实验的分组信息和用户属性。
- **批处理配置**: 通过 `tengu_1p_event_batch_config` 动态配置批量发送策略。使用 `onGrowthBookRefresh` 订阅配置变更,在运行时重建 LoggerProvider。
- **条件启用**: `is1PEventLoggingEnabled()` 检查隐私级别、用户认证状态等前置条件,只有满足所有条件时才启用。

### 3.4 Datadog (`src/services/analytics/datadog.ts`)

OSS 构建中 Datadog 遥测被禁用(`initializeDatadog` 返回 `false``trackDatadogEvent` 函数体为空)。接口保留以便启动代码不需要对 OSS 变体特殊处理。Datadog 用于生产环境的性能监控、错误追踪和自定义指标仪表板。

### 3.5 分析配置 (`src/services/analytics/config.ts`)

`isAnalyticsDisabled()` 在以下情况下禁用分析:NODE_ENV=test、Bedrock/Vertex/Foundry 第三方提供商、隐私级别为 no-telemetry 或 essential-traffic 模式。

### 3.6 成本与令牌用量追踪 (`src/cost-tracker.ts`)

- **全局状态**: `getTotalCostUSD()`、`getTotalInputTokens()`、`getTotalOutputTokens()`、`getTotalAPIDuration()` 等函数从 bootstrap state 读取聚合数据。
- **按模型统计**: `getUsageForModel()` 按模型名跟踪 `inputTokens`、`outputTokens`、`cacheReadInputTokens`、`cacheCreationInputTokens`、`costUSD`。
- **会话持久化**: `saveCurrentSessionCosts()` 将当前 session 的成本写入 project config;`restoreCostStateForSession()` 在恢复 session 时重建成本状态。
- **成本展示**: `formatTotalCost()` 输出格式化成本摘要(总成本、API 时长、代码变更行数、按模型的令牌使用量)。
- **exit 回调**: `useCostSummary()` (`src/costHook.ts`) 在 `process.on('exit')` 时自动输出成本摘要并保存 session 数据。

---

## 4. 性能工程

### 4.1 流式工具执行

工具执行采用流式架构(`QueryEngine` 中的 `AsyncGenerator`),LLM 输出 token 流式到达的同时,工具结果可以并行返回:

- **`QueryEngine`** 将 LLM 的流式响应解析为 `TextBlock`、`ToolUseBlock`、`ContentBlockStop` 等事件,边解析边交付给 UI 渲染。
- **工具并行**: 当 LLM 发起多个工具调用时,`tools.ts` 中的调度器可以并行执行无依赖的工具(如同时读取多个文件),减少总等待时间。流式响应持续接收新的工具调用请求。
- **前台 vs 背景**: 前台 `QuerySource`(如 `repl_main_thread`)使用完整的重试、提示缓存和速率限制逻辑;背景任务(摘要、标题生成)使用轻量级路径以减少延迟。
- **`QuerySource` 分类**: 定义在 `src/constants/querySource.ts`,包括 `repl_main_thread`、`repl_main_thread:outputStyle:custom`、`sdk`、`agent:custom`、`compact`、`hook_agent`、`auto_mode` 等 20+ 种类型,影响重试策略和日志记录。

### 4.2 提示缓存 (Prompt Caching)

- **缓存写入控制** (`skipCacheWrite`) — 某些场景(如快速诊断查询)跳过缓存写入以节省 cache creation token 成本。适用于可丢弃的中间查询,如标题生成、会话摘要等。
- **缓存读取追踪** — `cache_read_input_tokens` 和 `cache_creation_input_tokens` 通过 `cost-tracker.ts` 分模型追踪。API 响应中的 `usage.cache_read_input_tokens` 和 `usage.cache_creation_input_tokens` 字段在 `addToTotalSessionCost()` 中累加。
- **Fast Mode 缓存保留** — 在 fast mode 中,短延迟重试使用相同的模型名以保留 prompt cache(减少 cache creation 消耗);长延迟触发 cooldown 切换到标准模型时,失去缓存被视为可接受成本。
- **Cache Break Detection** (`PROMPT_CACHE_BREAK_DETECTION` feature flag) — 监控和检测 prompt cache 命中率异常下降的场景,帮助诊断缓存失效原因。
- **成本核算**: `calculateUSDCost()` 函数根据模型单价、输入/输出/cache read/cache creation token 用量计算精确成本。未知模型通过 `hasUnknownModelCost()` 标记。

### 4.3 上下文预取

- **内存预取**: `memory` 系统在 agent 启动时主动从磁盘加载相关记忆片段。
- **技能预取**: `skills` 系统预加载已启用的技能描述和工具定义。
- **推测执行** (`SpeculationState`): 在用户输入到达之前,系统可以推测性地启动任务执行。

### 4.4 令牌估算

`getContextWindowForModel()``getModelMaxOutputTokens()` 提供按模型的上下文窗口和输出上限。`parseMaxTokensContextOverflowError()` 解析 API 返回的 context overflow 错误并自动调整 `max_tokens` 参数。

### 4.5 速率限制模拟 (`src/services/rateLimitMocking.ts`)

Ant 员工可以通过 `/mock-limits` 命令模拟各种速率限制场景(429、529、fast-mode cooldown),用于测试 UI 行为和恢复逻辑。与 `withRetry()` 集成,确保模拟错误不触发真实重试。

### 4.6 背景任务卸载

在 stop hooks 中使用 fire-and-forget 模式(`void` 前缀 + `.catch()`),确保关键路径不被背景任务阻塞。例如:`LogSelector.tsx` 中的日志写入、`onChangeAppState` 中的 side-effect 通知。

---

## 5. 跨组件通信模式

### 5.0 Signal 模式 (`src/utils/signal.ts`)

`createSignal<T>()` 提供轻量级的事件信号原语,与 Store 不同,Signal 不持有状态快照,仅用于通知"某事发生了":

- **订阅/取消**: `subscribe(listener)` 返回取消函数,与 React useEffect 的 cleanup 模式天然兼容。
- **类型安全**: 通过泛型 `Args` 指定事件参数类型。
- **去重**: `clear()` 移除所有监听器,用于 dispose 和 reset 路径。
- **使用场景**: GrowthBook 刷新通知 (`refreshed` signal)、设置变更通知、`onChangeAppState` 中的事件广播。
- **与 Store 的区别**: Signal 无 `getState()`,不存储值,仅做事件通知。在代码库中替换了 ~15 处重复的 `new Set<Listener>()` + `subscribe/notify` 模式。

## 6. 状态管理性能

### 6.1 AppState 可观察存储 (`createStore` 模式)

`createStore<T>()` (`src/state/store.ts`) 是一个轻量级不可变状态存储,模式灵感来源于 Redux 但更简洁:

- `getState()` — O(1) 引用读取,无任何开销。
- `setState(updater)` — 接收 `(prev: T) => T` 更新函数。通过 `Object.is` 做引用相等性检查,避免无变更时通知监听器。`onChange` 回调在每次有效状态变更时同步触发,接收 `{ newState, oldState }` 上下文。
- `subscribe(listener)` — 基于 `Set<Listener>` 的发布-订阅,返回取消订阅函数。无额外分配。
- `AppStateStore` 类型 (`src/state/AppStateStore.ts`) 继承自 `Store<AppState>`,添加了 `AppState` 的默认值工厂函数 `getDefaultAppState()`。完整的 AppState 包含 `tasks``toolPermissionContext``settings``isUltraplanMode``viewingAgentTaskId` 等顶级字段。
- **不可变性保证**: `setState` 使用 updater 函数模式,每次变更产生新的状态对象。React 渲染层依赖引用相等性进行短路优化。

### 6.2 onChangeAppState 副作用处理器 (`src/state/onChangeAppState.ts`)

一个集中式 `onChange` 处理器,在 AppState 变更时协调跨系统的副作用:

- 权限模式同步(CCR/SDK)
- 会话元数据变更通知
- 设置变更应用 (`applySettingsChange`)
- API key 缓存清理

这种模式避免了 React 组件中散落的 `useEffect` 链,让状态变更的副作用可预测且可测试。

### 6.3 Selector 模式 (`src/state/selectors.ts`)

纯函数选择器从 AppState 派生计算状态:

- `getViewedTeammateTask()` — 提取当前查看的 teammate 任务
- `getActiveAgentForInput()` — 确定用户输入路由目标(leader / viewed / named_agent)

选择器保持轻量、无副作用,便于组合和测试。

### 6.4 React Context 订阅

`AppStateProvider` (`src/state/AppState.tsx`) 使用 React Context + `useSyncExternalStore` 将存储桥接到 React 渲染周期。`VoiceProvider` 通过 `feature('VOICE_MODE')` 进行死代码消除 — 外部构建中它是一个无操作的包裹器。`useSettingsChange` hook 监听设置文件变更并增量更新状态。

---

## 7. 构建优化

### 7.1 Feature Flag 死代码消除 (`bun:bundle feature()`)

Bun 的编译时 `feature()` 函数允许在构建阶段消除未使用的代码分支:

- `scripts/build.ts` 定义了完整的实验特性列表(如 `VOICE_MODE``AGENT_MEMORY_SNAPSHOT``BASH_CLASSIFIER` 等 50+ 个 flag)。
- 外部构建默认只启用 `VOICE_MODE`- 构建命令 `bun build --feature=<flag>` 将 feature flag 注入编译步骤,未启用的分支在 tree-shaking 中被完全移除。
- 这使得 ant-internal 代码可以直接嵌入仓库,而外部构建不携带任何内部逻辑。

### 7.2 React Compiler 输出 (`src/components/LogoV2/`)

`LogoV2/` 目录中的组件(`LogoV2.tsx``AnimatedClawd.tsx``Feed.tsx``WelcomeV2.tsx` 等)使用了 React Compiler (`react/compiler-runtime`) 自动记忆化输出。编译器将 `useMemo`/`useCallback` 模式自动化,减少手动优化的工作量。

### 7.3 WASM 二进制文件

VAD(Voice Activity Detection)服务 (`src/friend/voice/vad-service.ts`) 使用 `onnxruntime-web` 的 WASM 后端(而非 `onnxruntime-node` 原生插件),因为 Bun 不支持 Node-API 原生插件。WASM 二进制文件 (`onnxruntime-web`) 随 dist 打包。

### 7.4 Build 脚本 (`scripts/build.ts`)

构建管道使用 Bun 原生打包器,生成单一可执行文件:

1. **预构建 Friend VRM 前端**: 检查 `src/components/friend/frontend/dist/index.html` 是否存在,不存在时执行 `npm run build`2. **主构建**: 使用 `bun build --compile --target bun --minify --bytecode` 生成单个可执行文件。`--minify` 减小二进制体积,`--bytecode` 编译为 Bun 字节码提升启动速度。
3. **编译时常量注入**:
   - `MACRO.VERSION` — 语义版本号(开发版附加 git SHA 和时间戳)。
   - `MACRO.BUILD_TIME` — ISO 8601 构建时间戳。
   - `MACRO.FEEDBACK_CHANNEL` — 反馈渠道(OSS 构建为 `github`)。
   - `MACRO.VERSION_CHANGELOG` — 最近的 git commit 日志或指向 GitHub 的 URL。
4. **外部模块排除**: `@ant/*``audio-capture-napi``image-processor-napi``modifiers-napi``url-handler-napi` 通过 `--external` 排除捆绑,减小二进制体积并允许运行时加载原生模块。
5. **环境变量定义**: `process.env.USER_TYPE='external'``process.env.CLAUDE_CODE_FORCE_FULL_LOGO='true'``process.env.CCR_FORCE_BUNDLE='true'` 等定义确保构建产物运行于正确模式。
6. **完整特性集**: 使用 `--feature-set=dev-full` 启用全部实验特性(50+ feature flags),默认仅启用 `VOICE_MODE`7. **输出产物**: 开发版本输出到 `./Codev`,发布版本输出到 `./dist/cli`。

---

## 8. 资源管理

### 8.1 子进程管理

- **音频采集** (`arecord` / `sox`) — 通过 `child_process.spawn` 管理,采样率 16kHz,16-bit PCM。
- **Whisper STT** (`src/services/voice/whisperSTT.ts`) — 本地语音识别子进程,管理其生命周期并处理输出解析。
- **Tauri 子进程** — 在 Friend 桌面模式下启动和管理 Tauri shell 进程。
- **子进程超时**`RipgrepTimeoutError``StallTimeoutError` 确保子进程不会无限期挂起。`cleanupRegistry.ts` 注册进程清理回调,在优雅关闭时统一终止。

### 8.2 定时器和超时

- **GrowthBook 周期刷新**`setupPeriodicGrowthBookRefresh()` 使用 `setInterval`(非 Ant: 6h,Ant: 20min),`unref()` 保证不阻止进程退出。
- **API 重试退避**`sleep()` + `AbortSignal` 组合,支持中途取消。
- **持久重试心跳** — 每 30 秒输出系统消息防止空闲超时。
- **优雅关闭**`gracefulShutdown.ts` 中的 `CleanupTimeoutError` 确保关闭过程不会无限阻塞。

### 8.3 优雅关闭与清理注册表

`cleanupRegistry.ts``gracefulShutdown.ts` 实现了进程级资源清理系统:

- **`registerCleanup()`** (`src/utils/cleanupRegistry.ts`) — 全局 `Set<() => Promise<void>>` 注册表。任何模块可以注册异步清理函数,返回取消注册函数。`runCleanupFunctions()` 使用 `Promise.all` 并行执行所有清理任务。
- **`gracefulShutdown()`** (`src/utils/gracefulShutdown.ts`) — 主关闭流程,按顺序执行:
  1. `onExit('signal-exit')` 捕获 SIGTERM/SIGINT,设置 `isShuttingDown` 标志。
  2. 同步恢复终端模式(退出 alt screen、恢复光标、禁用 mouse tracking、恢复 Kitty 键盘模式)。
  3. `runCleanupFunctions()` 并行运行所有注册的异步清理。
  4. 关闭 Datadog(`shutdownDatadog()`)和第一方事件日志。
  5. 最终日志发送(会话成本、诊断事件)。`CleanupTimeoutError` 防止清理阶段无限阻塞。

### 8.4 文件句柄管理

- **SSE 客户端** — EventSource 连接在设置/清理生命周期中管理,使用 `AbortController` 确保断开。
- **音频文件** — 语音录制生成临时 WAV 文件,`FriendService` 负责生命周期管理。
- **调试日志**`BufferedWriter` 管理文件写入,`registerCleanup` 注册关闭回调。
- **诊断事件**`DiagnosticsService` 使用文件系统存储诊断事件,7 天保留期和 50MB 上限防止磁盘膨胀。

### 8.5 VAD 内存状态管理

`SileroVad` (`src/friend/voice/vad-service.ts`) 在 ONNX 推理会话中维护 LSTM 状态张量 (`stateH``stateC`)。每个音频帧(512 采样 / 32ms)处理后更新状态。关键优化:

- **RMS 能量过滤**: 低于 `rmsThreshold`(默认 ~-48dBFS)的帧跳过 ONNX 推理,直接视为静音。在高噪声环境中可以减少约 60% 的推理次数。
- **状态重置**: `reset()` 方法清空 LSTM 状态和累积缓冲区,用于会话间清理。
- **预处理触发**: `preSpeechTriggerFrames`(默认 10 帧 / 320ms)过滤短时噪音爆发,避免误触发。

---

## 架构决策记录

| 决策 | 理由 |
|------|------|
| 错误使用 `class extends Error` 并显式设置 `this.name` | 确保压缩构建中 `instanceof` 失效时仍可通过 `name` 属性识别错误类型 |
| 重试引擎使用 generator (`yield`) | 允许在重试间隔中间向调用方输出系统消息(心跳、进度) |
| GrowthBook 使用 `remoteEval: true` | 服务器端评估避免客户端下载完整规则集,减少网络负载和延迟 |
| VAD 使用 WASM 而非原生插件 | 兼容 Bun 运行时(Node-API 原生插件在 Bun 中导致 segfault) |
| 选择器使用纯函数而非 memoized selector | AppState 不可变性保证引用相等性检查足够高效,不需要额外记忆化开销 |
| Feature flag 死代码消除在构建时而非运行时完成 | 显著减小外部构建的二进制体积,消除内部代码泄漏风险 |
| Datadog / 遥测在 OSS 构建中默认禁用 | 保持开放源代码版本的隐私友好特性,同时保持接口兼容性 |