codev / docs /architecture /cross-cutting.md
chenbhao's picture
chore: rename VersperClaw to Codev, update org to chenbhao
96f34e3
|
Raw
History Blame Contribute Delete
25.6 kB
# 横切关注点 — 错误处理、日志、遥测与性能
## 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 构建中默认禁用 | 保持开放源代码版本的隐私友好特性,同时保持接口兼容性 |