diff --git a/docs/cc_analysis/analysis_20260401 b/docs/cc_analysis/analysis_20260401 deleted file mode 160000 index 185359d..0000000 --- a/docs/cc_analysis/analysis_20260401 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 185359d451f4f6144e97f4ed477f9a9a43e8d140 diff --git a/docs/cc_analysis/analysis_20260401/Claude Code 默认 Effort Level 设置笔记.md b/docs/cc_analysis/analysis_20260401/Claude Code 默认 Effort Level 设置笔记.md new file mode 100644 index 0000000..1c9968c --- /dev/null +++ b/docs/cc_analysis/analysis_20260401/Claude Code 默认 Effort Level 设置笔记.md @@ -0,0 +1,38 @@ +# Claude Code 默认 Effort Level 设置为 Max + +## 问题 + +`/effort max` 命令只对当前会话生效,重启 Claude Code 后会恢复为默认的 `medium`。 + +## 解决方案 + +在 `~/.zshrc` 中添加环境变量: + +```bash +export CLAUDE_CODE_EFFORT_LEVEL=max +``` + +新开终端窗口后自动生效。 + +## 为什么不能用 settings.json + +Claude Code 的 `~/.claude/settings.json` 支持 `effortLevel` 字段,但只接受 `low`、`medium`、`high` 三个值。`max` 是 Opus 4.6 专属的深度推理模式,只能通过环境变量持久化。 + +## Effort Level 一览 + +| 级别 | 说明 | +|------|------| +| `low` | 快速响应,适合简单问答 | +| `medium` | 默认级别 | +| `high` | 更深入的推理 | +| `max` | 最深度推理,无 token 限制(仅 Opus 4.6) | + +## 其他设置方式 + +- **启动参数**(单次):`claude --effort max` +- **会话内**(单次):`/effort max` +- **环境变量**(永久):`export CLAUDE_CODE_EFFORT_LEVEL=max` ← 推荐 + +--- + +*记录于 2026-03-30* diff --git a/docs/cc_analysis/analysis_20260401/Claude-Code-Version-Rollback.md b/docs/cc_analysis/analysis_20260401/Claude-Code-Version-Rollback.md new file mode 100644 index 0000000..3b15d4c --- /dev/null +++ b/docs/cc_analysis/analysis_20260401/Claude-Code-Version-Rollback.md @@ -0,0 +1,150 @@ +# Claude Code 自动回滚机制解析与版本切换指南 + +> 日期:2026-03-31 + +## 背景 + +Claude Code 从 v2.1.88 自动回滚到 v2.1.87,本文记录了完整的排查过程和解决方案。 + +## 回滚原因 + +**Anthropic 在 npm 做了服务端回滚**,不是本地触发的。 + +### npm Registry 状态 + +| dist-tag | 版本 | +|----------|------| +| `stable` | 2.1.81 | +| `latest` | 2.1.87 | +| `next` | 2.1.89 | + +- v2.1.88 **已被完全从 npm 删除**(registry 中不存在) +- `latest` tag 从 2.1.88 退回到 2.1.87 + +### 时间线 + +| 时间 | 事件 | +|------|------| +| Mar 28 19:25 | 自动更新下载 v2.1.87 | +| Mar 30 18:31 | 自动更新下载 v2.1.88 | +| Mar 30 ~ Mar 31 | Anthropic 从 npm 撤掉 v2.1.88,将 `latest` tag 退回 v2.1.87 | +| Mar 31 05:59 | 自动更新器检查 npm registry,发现 `latest` 是 2.1.87,将 symlink 切回 | + +## 自动更新机制 + +### 核心架构 + +``` +~/.local/bin/claude → symlink → ~/.local/share/claude/versions/{version} +``` + +- 包名:`@anthropic-ai/claude-code` +- 版本二进制存储:`~/.local/share/claude/versions/` +- 入口 symlink:`~/.local/bin/claude` + +### AutoUpdater 工作流程 + +1. 每次 Claude Code 启动时,AutoUpdater 检查 npm registry 的 `latest` dist-tag +2. 如果本地版本与 `latest` 不匹配,下载目标版本二进制到 `versions/` 目录 +3. 更新 symlink 指向新版本 +4. 逻辑是**跟随 `latest` tag**,不是单调递增 — 所以 Anthropic 退 tag 就等于回滚 + +### 关键发现 + +- 二进制是 Bun 编译的 Mach-O arm64 可执行文件 +- 内部包含 `auto_updater_disabled`、`AutoUpdater`、`autoUpdaterStatus` 等标识 +- 启动遥测会上报 `auto_updater_disabled` 状态 +- 并发更新有互斥锁保护("Another instance is currently performing an update") + +## 禁用自动更新的正确方式 + +通过逆向二进制中的 `h1H()` / `isAutoUpdaterDisabled` 函数,确认自动更新器的检查逻辑: + +```javascript +// 反编译后的禁用检查逻辑(简化) +function getAutoUpdaterDisabledReason() { + if (process.env.DISABLE_AUTOUPDATER) return { type: "env" }; + if (config.autoUpdates === false) return { type: "config" }; + return null; // 未禁用,自动更新正常运行 +} +``` + +### 踩坑记录 + +`autoUpdaterDisabled: true` 是**错误的 key**,写了不生效,自动更新器仍会在启动时抢先将 symlink 切回 `latest` 指向的版本。 + +### 方法 A:环境变量(推荐,最可靠) + +```bash +# 加到 ~/.zshrc,每次 shell 启动自动生效 +echo 'export DISABLE_AUTOUPDATER=1' >> ~/.zshrc +source ~/.zshrc +``` + +### 方法 B:settings.json + +编辑 `~/.claude/settings.json`,注意 key 是 `autoUpdates`: + +```json +"autoUpdates": false +``` + +> 注意:对于 native 安装方式,如果同时设置了 `autoUpdatesProtectedForNative: true`,则 `autoUpdates: false` 会被覆盖,此时只能用环境变量方式。 + +## 解决方案:切回 v2.1.88 + +### 步骤 1:禁用自动更新 + +```bash +# 确保环境变量已生效 +export DISABLE_AUTOUPDATER=1 +``` + +### 步骤 2:切换 symlink + +```bash +ln -sf ~/.local/share/claude/versions/2.1.88 ~/.local/bin/claude +``` + +### 步骤 3:验证 + +```bash +claude --version +# 输出:2.1.88 (Claude Code) +``` + +### 步骤 4:使用 + +```bash +claude --dangerously-skip-permissions +``` + +## 恢复自动更新 + +```bash +# 1. 从 ~/.zshrc 删掉 export DISABLE_AUTOUPDATER=1 +# 2. 如果用了方法 B,从 settings.json 删掉 "autoUpdates": false +# 3. 重启终端,自动更新器会在下次启动时恢复工作 +``` + +## 其他版本切换方式 + +```bash +# 切到 next channel (v2.1.89) +claude update --channel next + +# 切到 stable channel (v2.1.81) +claude update --channel stable + +# 查看本地已有的版本 +ls ~/.local/share/claude/versions/ + +# 手动切换到任意本地版本 +ln -sf ~/.local/share/claude/versions/<版本号> ~/.local/bin/claude +``` + +## 注意事项 + +- v2.1.88 被 Anthropic 从 npm 删除,可能存在已知问题 +- 禁用自动更新后不会收到安全修复,需定期手动检查 +- 本地残留的 v2.1.88 二进制不会被自动清理 diff --git a/docs/cc_analysis/analysis_20260401/Claude_Code_Context_Management_Notes.md b/docs/cc_analysis/analysis_20260401/Claude_Code_Context_Management_Notes.md new file mode 100644 index 0000000..e8ed8ad --- /dev/null +++ b/docs/cc_analysis/analysis_20260401/Claude_Code_Context_Management_Notes.md @@ -0,0 +1,297 @@ +# Claude Code 上下文管理算法学习笔记 + +> 来源:Claude Code 源码快照(2026-03-31) +> 核心文件:`src/query.ts`, `src/services/compact/`, `src/utils/toolResultStorage.ts` + +--- + +## 架构总览:7 层递进式防御 + +每轮 query loop 按以下顺序执行,从便宜到昂贵,每一层减轻压力,可能阻止下一层触发: + +``` +1. Tool Result Budget ← 单轮预算(同步,零 API 调用) +2. Snip Compact ← 历史裁剪(零 API 调用) +3. Microcompact ← 工具结果精细清理(零或极低 API 成本) +4. Context Collapse ← 增量投影摘要(零 API 调用) +5. Auto-Compact ← LLM 全量摘要(昂贵) +6. Blocking Limit ← 硬停(所有自动措施关闭时) +7. Reactive Compact ← 413 错误后紧急摘要(最后手段) +``` + +互斥门控防止昂贵操作竞争:Context Collapse 启用时抑制 Auto-Compact,Reactive Compact 实验模式抑制主动 Auto-Compact。 + +--- + +## 第 1 层:Tool Result Budget — 单轮聚合预算 + +**问题**:N 个并行工具可能在一轮中集体产生超大上下文。 + +**三分区算法**: + +| 分区 | 含义 | 处理 | +|------|------|------| +| mustReapply | 之前已替换过的结果 | 直接重用缓存的替换字符串(零 I/O,字节级一致) | +| frozen | 之前见过但没替换 | 永不替换(保护 prompt cache) | +| fresh | 新结果 | 参与预算分配 | + +**阈值**: +- 总预算:200K 字符/消息(远程可配置) +- 单工具上限:50K 字符(默认) +- 超出时:保留前 ~2KB 预览 + 持久化到磁盘,模型可用 Read 按需读取 + +**核心洞察**:frozen 分区 — 宁可浪费空间也不破坏缓存命中率。**Prompt cache 稳定性优先于空间效率**。 + +--- + +## 第 2 层:Snip Compact — 历史裁剪 + +**特点**:零 API 调用,直接删除旧消息。 + +**协调机制**:snipTokensFreed 传递给下游层,因为 tokenCountWithEstimation 读的是上一轮 API 返回的 input_tokens(反映 snip 前的数值),需要手动减去 snip 释放的量。 + +**双视图**:REPL 保留被 snip 的消息用于 UI 回滚,但投影层在发送 API 前过滤掉它们。 + +--- + +## 第 3 层:Microcompact — 三种精细清理路径 + +### 3a. 基于时间的清理 + +``` +触发条件:距上次助手消息 > 60 分钟(= 服务端 cache TTL) +逻辑:cache 已过期 → 全量前缀会被重写 → 趁机清理旧工具结果 +行为:替换为 "[Old tool result content cleared]" +保留:最近 5 个结果 +``` + +**洞察**:利用缓存 TTL 过期的"免费窗口"搭车做清理。 + +### 3b. 缓存编辑(Cache Editing)— 最精妙的设计 + +``` +触发条件:可压缩工具结果数超过阈值 +关键创新:不修改本地消息!使用 API 的 cache_edits / cache_reference 机制 +效果:服务端缓存被精确编辑,本地保持不变 +确认:用 API 返回的 cache_deleted_input_tokens(非客户端估算) +范围:仅主线程,仅特定工具(FileRead, Bash, Grep, Glob 等) +``` + +**洞察**:读写分离 — 本地消息不变(保证重放一致性),服务端缓存被精确编辑(节省空间)。 + +### 3c. API 原生上下文管理 + +``` +策略:clear_tool_uses + clear_thinking +触发:输入超 180K tokens +目标:保留最后 40K tokens +思考块:cache 冷(>1h)时仅保留最后一轮思考 +``` + +--- + +## 第 4 层:Context Collapse — 增量投影式摘要(CQRS 思想) + +**核心思想**:不破坏性替换消息,而是维护一个 commit log,每轮查询时 project 出压缩视图。 + +**阈值**: +- 90% 上下文窗口 → 开始提交 collapse +- 95% 上下文窗口 → 阻塞新 spawn +- ~93%(auto-compact 位置)→ 被抑制避免竞争 + +**关键 API**: +- `applyCollapsesIfNeeded()` — 投影压缩视图 + 可选提交新 collapse +- `recoverFromOverflow()` — 413 时排空所有暂存 collapse(第一道防线) +- `projectView()` — 每轮重放提交日志 + +**设计亮点**: +- Collapse 摘要存在 commit store 中,而非 REPL 消息数组 → 跨轮持久化 +- REPL 保留完整历史(UI 回滚),API 调用看到投影视图(节省 tokens) +- 会话恢复时从 commits + snapshot 重建 + +--- + +## 第 5 层:Auto-Compact — 带熔断的 LLM 摘要 + +**触发**:`tokenCount > contextWindow - 13,000` + +### 摘要 prompt 结构 + +```xml +(内部草稿,生成后丢弃) + + 1. Primary Request / Intent + 2. Key Technical Concepts + 3. Files / Code + 4. Errors / Fixes + 5. Problem Solving + 6. All User Messages(用户说过的每句话都保留) + 7. Pending Tasks + 8. Current Work + 9. Optional Next Step + +``` + +### Prompt Cache 共享优化 + +摘要子 agent 复用主对话的缓存前缀(通过 runForkedAgent)。 +没有此优化:cache miss 率 98%,浪费全局 ~38B tok/天的 cache_creation。 + +### PTL(Prompt-Too-Long)重试 + +``` +最多重试 3 次: + → 按 API 轮次分组(groupMessagesByApiRound) + → 丢弃最老的组以覆盖 token 缺口 + → 无法精确计算时 fallback 丢弃 20% 的组 +图片:所有图片/文档块替换为 [image]/[document] 标记后再发送 +``` + +### 熔断器 + +``` +MAX_CONSECUTIVE_AUTOCOMPACT_FAILURES = 3 +触发后:本会话剩余时间停止尝试 +背景数据:~1,279 个会话曾出现 50+ 次连续失败(最高 3,272 次), + 每天浪费 ~250K API 调用 +``` + +### Post-Compact 恢复清单 + +| 恢复项 | 预算 | +|--------|------| +| 最近读取的文件 | 最多 5 个,每个 5K tokens,总计 50K | +| Skill 附件 | 每个 5K,总计 25K | +| Plan 状态 | 完整恢复 | +| Deferred tools | 完整恢复 | +| Agent 列表 | 完整恢复 | +| MCP 指令 | 完整恢复 | + +### Session Memory Compact(替代路径) + +在 auto-compact 中**优先尝试**,失败回退到完整 LLM 摘要: +``` +用预提取的 session memory 替代 LLM 摘要 +保留范围:从最后摘要消息向后扩展 + 满足 minTokens(10K) 且 minMessages(5) + 不超过 maxTokens(40K) +不变量:tool_use / tool_result 对永不拆分 +``` + +--- + +## 第 6-7 层:Reactive Compact + 错误恢复级联 + +### 5 层错误恢复(从便宜到昂贵) + +``` +API 返回 prompt_too_long 或媒体尺寸被拒 + | + v +Layer 1: Context Collapse drain(排空所有暂存 collapse,最便宜) + | 失败 + v +Layer 2: Reactive Compact(完整 LLM 摘要) + | 失败 + v +Layer 3: Max Output 升级(8K → 64K tokens) + | 失败 + v +Layer 4: Multi-turn Recovery(注入 nudge 消息,最多 3 次) + | 失败 + v +Layer 5: Model Fallback(切换到备用模型) +``` + +### 错误扣留(Error Withholding)模式 + +``` +流式传输期间,可恢复错误不 yield 给调用者 + → 推入 assistantMessages 供恢复检查 + → 防止 SDK 消费者终止会话 + → 所有恢复手段都失败后才暴露给用户 +``` + +防循环守卫:`hasAttemptedReactiveCompact` 防止同一轮无限重试。 + +--- + +## Token Budget Continuation — 输出预算跟踪(附加机制) + +``` +用途:长任务自动续跑(不是上下文管理,是输出预算控制) +完成阈值:输出 < 预算 90% → 继续 +递减检测:连续 3+ 次续跑 且 最近两次增量 < 500 tokens → 停止 +每次续跑注入 nudge 消息告知进度百分比 +``` + +--- + +## 值得借鉴的设计原则 + +### 1. Prompt Cache 稳定性高于空间效率 + +- frozen 分区:进入 cache 的内容永不修改 +- 字节级替换一致性:mustReapply 重用完全相同的替换字符串 +- Tool pool 排序稳定:`assembleToolPool()` 排序防止 MCP 变化破坏缓存 +- 缓存编辑不改本地消息:读写分离保证重放一致性 + +### 2. 分层防御,从便宜到昂贵 + +7 层递进,每层有可能阻止下一层触发。互斥门控防止昂贵操作竞争(collapse 抑制 autocompact)。snipTokensFreed 显式传递确保下游层看到真实 token 数。 + +### 3. 错误扣留 + 延迟恢复 + +流式传输中不立即暴露可恢复错误。给恢复机制留出空间后再决定是否暴露。这个模式可以推广到任何有多层 fallback 的系统。 + +### 4. CQRS 式双视图 + +REPL 保留完整历史(UI 回滚 + 会话恢复),API 看到投影视图(节省 tokens)。Context Collapse 的 commit log 和 projectView 就是这个思想的实现。 + +### 5. 熔断器模式 + +连续失败 3 次后停止重试。用真实数据驱动:1,279 个会话的失控循环 → 250K API 调用/天的浪费。 + +### 6. 利用缓存 TTL 做"免费清理" + +cache 自然过期(60min)时搭车执行清理。既然前缀要重建,就把脏活一起干了。 + +### 7. Post-Compact 不从零开始 + +摘要后精心恢复关键上下文(最近文件、plan、skills)。有明确的 token 预算分配,不是全量恢复也不是什么都不恢复。 + +### 8. 不变量保护无处不在 + +- tool_use / tool_result 对永不拆分 +- Compact 边界消息记录 pre-compact 状态供恢复重链 +- Partial compact 的 "up_to" 变体清除旧边界防止级联剪枝 bug +- 子 agent compact 不重置主线程的模块级状态 + +--- + +## Query Loop 状态机一览 + +```typescript +State = { + messages, + toolUseContext, + autoCompactTracking, // 熔断计数 + maxOutputTokensRecoveryCount, // 输出恢复计数 + hasAttemptedReactiveCompact, // 防循环守卫 + maxOutputTokensOverride, // 8K → 64K 升级 + pendingToolUseSummary, + stopHookActive, + turnCount, + transition // 状态转移原因 +} + +transition 类型: + collapse_drain_retry + reactive_compact_retry + max_output_tokens_escalate + max_output_tokens_recovery + stop_hook_blocking + token_budget_continuation +``` + +每轮不是递归而是 `while(true)` + 显式状态转移,避免长会话栈溢出。 diff --git a/docs/cc_analysis/analysis_20260401/Claude_Code_Deep_Analysis_Report.pdf b/docs/cc_analysis/analysis_20260401/Claude_Code_Deep_Analysis_Report.pdf new file mode 100644 index 0000000..426ea46 Binary files /dev/null and b/docs/cc_analysis/analysis_20260401/Claude_Code_Deep_Analysis_Report.pdf differ diff --git a/docs/cc_analysis/analysis_20260401/Claude_Code_Notable_Algorithms.md b/docs/cc_analysis/analysis_20260401/Claude_Code_Notable_Algorithms.md new file mode 100644 index 0000000..25cccb1 --- /dev/null +++ b/docs/cc_analysis/analysis_20260401/Claude_Code_Notable_Algorithms.md @@ -0,0 +1,710 @@ +# Claude Code 值得借鉴的算法与设计模式 + +> 来源:Claude Code 源码快照(2026-03-31) +> 上下文管理部分见另一份笔记 `Claude_Code_Context_Management_Notes.md` + +--- + +## 目录 + +1. [权限系统:多层安全级联](#1-权限系统多层安全级联) +2. [流式工具执行:模型推理与工具执行重叠](#2-流式工具执行模型推理与工具执行重叠) +3. [并发控制与重试策略](#3-并发控制与重试策略) +4. [终端渲染引擎:双缓冲 + Diff 写入](#4-终端渲染引擎双缓冲--diff-写入) +5. [纯 TypeScript 原生模块移植](#5-纯-typescript-原生模块移植) +6. [FileEditTool:12 步验证链](#6-filedittool12-步验证链) +7. [记忆系统:提取与召回](#7-记忆系统提取与召回) +8. [多 Agent 协调:Swarm 架构](#8-多-agent-协调swarm-架构) +9. [启动优化:并行预取](#9-启动优化并行预取) +10. [其他精巧设计](#10-其他精巧设计) + +--- + +## 1. 权限系统:多层安全级联 + +### 1.1 Bash 命令权限解析(8 步级联) + +文件:`src/tools/BashTool/bashPermissions.ts` + +``` +Step 0: Tree-sitter AST 解析 + → simple(干净命令)/ too-complex(无法静态分析)/ parse-unavailable(降级正则) + + Shadow 模式:Tree-sitter 观察性运行,记录与 legacy 路径的分歧 + +Step 1: 沙盒自动放行 + → sandboxing 启用 + autoAllowBashIfSandboxed → 允许 + → 复合命令拆分,逐子命令检查 deny 规则 + +Step 2: 精确匹配权限检查 + → 优先级:deny > ask > allow > passthrough + +Step 3: LLM 分类器 deny/ask 规则 + → Haiku 模型并行分类 deny 和 ask 描述列表 + → 仅高置信度结果触发 + +Step 4: 命令操作符拆分 + → 对 |, &&, ||, ; 递归调用权限检查 + → 即使管道段被允许,原始命令仍重新验证 + +Step 5: Legacy 误解析门控 + → 仅当 Tree-sitter 不可用时运行 + +Step 6: 逐子命令检查 + → splitCommand → 过滤 cd ${cwd} → checkCommandAndSuggestRules + +Step 7: 8 步子级联 + → 精确匹配 → 前缀 deny → ask 规则 → 路径约束 + → 精确 allow → 前缀/通配符 allow → sed 约束 + → 模式权限 → 只读检查 → passthrough +``` + +### 1.2 规则匹配中的安全技巧 + +**复合命令防护**:前缀和通配符规则拒绝匹配复合命令。防止 `cd /path && python3 evil.py` 被 `cd:*` 规则放行。 + +**环境变量剥离的不对称设计**: +- Deny 规则:使用激进的 `stripAllLeadingEnvVars`(固定点循环剥离所有环境变量),防止 `FOO=bar denied_cmd` 绕过 +- Allow 规则:使用保守的 `stripSafeWrappers`,只接受白名单中的 ~60 个安全环境变量 + +**安全环境变量白名单**(SAFE_ENV_VARS): +- 包含:`NODE_ENV`, `RUST_LOG`, `CGO_ENABLED` 等 +- 排除:`PATH`, `LD_PRELOAD`, `LD_LIBRARY_PATH`, `DYLD_*`, `PYTHONPATH`, `NODE_OPTIONS`, `BASH_ENV` + +### 1.3 Shell 安全分析(23 个验证器) + +文件:`src/tools/BashTool/bashSecurity.ts` + +按安全优先级排序的验证链: + +| 验证器 | 检测内容 | +|--------|---------| +| `validateJqCommand` | `system()`, `-f`, `--from-file`, `-L` | +| `validateObfuscatedFlags` | 引号内隐藏的标志(如 `"-rf"`) | +| `validateShellMetacharacters` | 参数中的 `;`, `\|`, `&` | +| `validateDangerousVariables` | `BASH_ENV`, `PROMPT_COMMAND`, `PS1`, `BASH_FUNC_*` | +| `validateCommentQuoteDesync` | 引号内的 `#`(注释跟踪混淆) | +| `validateCarriageReturn` | CR 字符(shell-quote/bash 分词差异) | +| `validateIFSInjection` | `IFS=` 赋值 | +| `validateDangerousPatterns` | 反引号, `$()`, `${}`, zsh 扩展 (`=cmd`, `~[`, `(e:`) | +| `validateUnicodeWhitespace` | 非 ASCII 空白字符 | +| `validateBraceExpansion` | `{...,...}` 模式 | +| `validateZshDangerousCommands` | 20+ 个 zsh 内建命令(`zmodload`, `syswrite`, `ztcp` 等) | + +**关键排序技巧**:非误解析验证器的 `ask` 结果被延迟返回。循环继续运行误解析验证器;只有没有误解析验证器触发时,延迟的非误解析结果才被返回。防止非误解析的 `ask` 掩盖应该设置 `isBashSecurityCheckForMisparsing` 的误解析 `ask`。 + +### 1.4 只读命令分类(双层判定) + +文件:`src/tools/BashTool/readOnlyValidation.ts` + +**Tier 1 — Flag 级别白名单**:每个命令的每个 flag 都有类型标注(`none`, `number`, `string`, 特定字面量)。特例: +- `xargs` 的 `-i`/`-e` 被移除(GNU `getopt_long` 可选参数语义漏洞) +- `tree` 的 `-R` 被移除(它会写文件) +- `fd`/`fdfind` 的 `-x`/`--exec` 被排除 + +**Tier 2 — 正则匹配**:`cat`, `head`, `tail`, `wc`, `jq`, `echo`, `pwd` 等简单命令。 + +**复合命令安全**:`cd && git` 组合被阻止(沙盒逃逸 — 通过恶意 git hooks)。检测写入 `.git/hooks/`、`objects/`、`refs/` 后运行 git 的命令链。 + +### 1.5 Auto-Mode (YOLO) 分类器 + +文件:`src/utils/permissions/yoloClassifier.ts` + +**不是传统 ML — 是 LLM 即分类器**。 + +**两阶段 XML 分类**: +``` +Stage 1(快速判定): + max_tokens=64, stop_sequences=[''] + → "no"(允许):立即返回 + → "yes" 或无法解析:升级到 Stage 2 + +Stage 2(深度推理): + max_tokens=4096, 启用 chain-of-thought + → 用 标签推理后再决定 + → 解析时先剥离 块再匹配 标签 +``` + +**200ms 竞赛模式**(interactiveHandler.ts): +``` +5 个参赛者同时启动: + 1. 用户权限对话框 + 2. Hooks 异步执行 + 3. Bash 分类器异步执行 + 4. Bridge 权限响应(claude.ai) + 5. Channel 权限中继(Telegram 等) + +createResolveOnce 原子 claim() — 第一个到达的赢,其他 no-op + +200ms 宽限期: + 前 200ms 内忽略用户交互(防止意外按键取消分类器) + 200ms 后任何用户交互都会杀死分类器的自动批准机会 +``` + +**投机性分类器检查**:在权限对话框出现之前就启动分类器(`startSpeculativeClassifierCheck`),与 deny/ask 分类器、hooks、对话框设置并行运行。 + +--- + +## 2. 流式工具执行:模型推理与工具执行重叠 + +文件:`src/services/tools/StreamingToolExecutor.ts` + +### 核心思想 + +工具在模型**还在生成 token 时就开始执行**,而非等完整响应结束。 + +### 并发控制算法 + +```typescript +canExecuteTool(isConcurrencySafe: boolean): boolean { + const executing = this.tools.filter(t => t.status === 'executing') + return ( + executing.length === 0 || + (isConcurrencySafe && executing.every(t => t.isConcurrencySafe)) + ) +} +``` + +- 没有正在执行的工具 → 可以执行 +- 自身并发安全 且 所有正在执行的也并发安全 → 可以执行 +- 否则 → 等待 + +### 工具状态机 + +``` +queued → executing → completed → yielded +``` + +队列有序处理。非并发工具遇到无法执行时直接 break(保持严格顺序);并发工具可跳过。 + +### 错误级联 + +**只有 Bash 错误才取消兄弟工具**。原因:Bash 命令有隐式依赖链(mkdir 失败 → 后续命令无意义)。Read/WebFetch 等独立工具不取消。 + +`siblingAbortController` 是 parent 的子控制器 — 兄弟子进程立即死亡,但父 query loop 不中断。 + +### 进度流式传输 + +使用 Promise 信号机制(`progressAvailableResolve`)。`getRemainingResults()` async generator 用 `Promise.race` 在工具完成和新进度到达之间等待,确保进度消息实时传递而非缓冲。 + +### 与 query.ts 的集成 + +``` +流式循环中: + API content_block_stop 事件 + → 立即 yield AssistantMessage + → query.ts 喂给 StreamingToolExecutor.addTool() + + 事件间隙: + → getCompletedResults() 收割已完成的工具 + + 流式结束后: + → getRemainingResults() 排空所有待处理工具 +``` + +--- + +## 3. 并发控制与重试策略 + +### 3.1 并发 Generator 合并(all 函数) + +文件:`src/utils/generators.ts` + +``` +维护 waiting 队列 + active Promise 集合 +1. 初始填充 concurrencyCap 个 generator +2. Promise.race 所有活跃 generator +3. 某个 yield → 立即 .next() 推进它 +4. 某个完成 → waiting 中取一个填补 +5. 公平交错 + 有界并行(默认上限 10) +``` + +### 3.2 工具并发安全分类 + +| 分类 | 工具 | 依据 | +|------|------|------| +| 始终安全 | FileRead, LSP, TaskCreate, TaskGet, Brief | 返回 `true` | +| 输入依赖 | BashTool | 委托给 `isReadOnly(input)`,只读命令并发,写命令独占 | +| 默认不安全 | FileEdit, FileWrite 等 | 使用默认 `false`(fail-closed) | + +### 3.3 指数退避 + 抖动重试 + +文件:`src/services/api/withRetry.ts` + +``` +baseDelay = min(500ms * 2^(attempt-1), 32s) +jitter = random(0, 25% * baseDelay) // 加性抖动,非乘性 +finalDelay = baseDelay + jitter +// server retry-after header 存在时覆盖 +``` + +**529(过载)处理 — 级联放大防护**: +``` +非前台查询(摘要、标题、分类器)→ 收到 529 立即放弃(不重试) +前台查询 → 重试最多 3 次 +3 次 529 后 → FallbackTriggeredError → 切换备用模型 +``` + +**Fast Mode 降级**: +``` +429/529 + fast mode 激活: + retry-after < 20s → 继续 fast mode 重试(保持 prompt cache) + retry-after ≥ 20s → 冷却(最少 10min)切换标准速度 +``` + +**无人值守模式**(`CLAUDE_CODE_UNATTENDED_RETRY`): +``` +429/529 无限重试,最大退避 5 分钟 +长休眠拆为 30s 心跳间隔(防止主机标记会话空闲) +6 小时硬上限重置 +``` + +### 3.4 流式看门狗 + +文件:`src/services/api/claude.ts` + +``` +空闲超时默认 90s(可配置) +50% 时间 → 警告 +100% 时间 → 硬中止挂起的流 +每个 chunk 重置计时器 +``` + +--- + +## 4. 终端渲染引擎:双缓冲 + Diff 写入 + +### 4.1 双缓冲帧交换 + +文件:`src/ink/ink.tsx`, `src/ink/output.ts`, `src/ink/screen.ts` + +``` +每次渲染后: + backFrame = frontFrame + frontFrame = newFrame +``` + +**Screen 缓冲区**:packed `Int32Array`,每个单元格 2 个 word(8 bytes)。 +- Word 0: char pool ID +- Word 1: style ID + hyperlink ID + cell width(位打包) +- `resetScreen()` 复用同一 buffer(只增不缩),用 `BigInt64Array` 视图批量清零 + +**charCache**(16384 上限):跨帧缓存 grapheme 聚类结果,大多数行不变时直接命中。 + +### 4.2 Diff 算法 + +文件:`src/ink/screen.ts` (diffEach), `src/ink/log-update.ts` + +``` +1. 计算两帧 damage rectangle 的并集 +2. 仅在 damage 区域内逐 word 比较 Int32Array +3. findNextDiff() 是纯函数,设计为 JIT 内联 +4. VirtualScreen 跟踪光标位置,只在目标不一致时发移动指令 +``` + +**关键优化**: +- **DECSTBM 硬件滚动**:ScrollBox 的 scrollTop 变化时用终端硬件滚动(`CSI top;bot r + CSI n S/T`),而非重写整个区域。先对 prev.screen 执行 `shiftRows()` 模拟硬件位移,后续 diff 自然只找到新滚入的行。 +- **StylePool.transition()**:按 (fromId, toId) 对缓存 ANSI 样式转换字符串 — 预热后零分配 +- **fg-only 空格跳过**:只有前景色的空格单元格视为不可见,跳过写入 + +### 4.3 Blit 优化 + +文件:`src/ink/render-node-to-output.ts` + +``` +节点干净(not dirty)且布局位置不变 + → 直接从 prevScreen 复制单元格(blit) + → blitRegion() 使用 TypedArray.set() 批量内存拷贝 + → 每行一次调用,连续全宽区域只需一次 + → 跳过整个子树的重新渲染 +``` + +### 4.4 渲染器 Peephole 优化 + +文件:`src/ink/optimizer.ts` + +单趟扫描 Diff 数组: +- 合并连续 `cursorMove`(加 dx/dy) +- 折叠连续 `cursorTo`(只保留最后一个) +- 拼接相邻 `styleStr` +- 取消 cursor hide/show 对 +- 去重相同 URI 的 hyperlink patch +- 移除 count=0 的 clear patch + +--- + +## 5. 纯 TypeScript 原生模块移植 + +### 5.1 Yoga 布局引擎(C++ → TypeScript) + +文件:`src/native-ts/yoga-layout/index.ts`(~2400 行) + +完整的 Flexbox 布局实现,消除了 native binary 依赖。 + +**多层缓存策略**: +| 缓存 | 机制 | 效果 | +|------|------|------| +| Dirty-flag | 干净子树 + 匹配输入 → 跳过 | 最基本的剪枝 | +| 双槽缓存 | 分别缓存 layout 和 measure 结果 | 同一节点两种调用模式 | +| 4 槽环形缓存 | packed Float64Array | 500 消息 scrollbox: 76k→4k layoutNode 调用 | +| flex-basis 缓存 | generation-stamped | 短路递归 computeFlexBasis | +| 快速路径标志 | `_hasAutoMargin` 等 | 全零情况单分支跳过 | + +**`resolveEdges4Into()`**:一次遍历解析全部 4 条物理边到预分配元组,提升共享 fallback 查找。 + +### 5.2 模糊搜索(Rust nucleo/fzf → TypeScript) + +文件:`src/native-ts/file-index/index.ts` + +**逐步过滤架构**: +``` +Step 1: 字符位图过滤(O(1) 拒绝) + → 每个路径一个 26-bit charBits 掩码 + → (charBits & needleBitmap) !== needleBitmap → 跳过 + +Step 2: 融合 indexOf 扫描 + → String.indexOf()(V8/JSC 中 SIMD 加速) + → 同时找到匹配位置 + 累积 gap/consecutive 分数 + → 无需第二次评分遍历 + +Step 3: Gap-bound 拒绝 + → 计算分数上限(所有边界奖励)减去已知 gap 惩罚 + → 无法超过当前 top-k 阈值 → 跳过昂贵的边界评分 + +Step 4: 边界/驼峰评分 + → 路径分隔符 (/\-_.) 匹配奖励 + → 驼峰转换匹配奖励 + → 首字符匹配奖励 + → 常数近似 nucleo/fzf-v2 权重 + +Top-k 维护:升序数组 + 二分插入(避免全量 O(n log n) 排序) +``` + +**其他特性**: +- 异步构建:每 ~4ms yield 事件循环,`readyCount` 支持构建中的部分索引搜索 +- 智能大小写:全小写查询 = 大小写不敏感;有大写 = 敏感 +- 测试文件惩罚:路径包含 "test" → 1.05x 分数惩罚 + +### 5.3 语法高亮 + Word-Level Diff(Rust syntect/bat → TypeScript) + +文件:`src/native-ts/color-diff/index.ts` + +- highlight.js 延迟加载(避免 ~200ms 的 190+ 语法注册启动成本) +- `diff` npm 包的 `diffArrays` 做词级 diff +- RGB → ANSI-256 颜色近似:移植 `ansi_colours` Rust crate 的立方体 vs 灰阶感知最近索引算法 +- Monokai Extended / GitHub-light 作用域到颜色映射 +- Storage 关键字重分割(highlight.js 把 `const`/`function`/`class` 归为 "keyword";端口重分割以匹配 syntect 的 cyan storage 颜色) + +--- + +## 6. FileEditTool:12 步验证链 + +文件:`src/tools/FileEditTool/FileEditTool.ts` + +``` + 1. 密钥检测 → 阻止向 team memory 文件写入密钥 + 2. 空操作检测 → old_string === new_string 直接拒绝 + 3. Deny 规则检查 → 文件路径匹配 deny 权限规则 + 4. UNC 路径安全 → 跳过 \\server\share(防止 NTLM 凭证泄露) + 5. 文件大小守卫 → > 1 GiB 拒绝 + 6. 编码检测 → UTF-16LE BOM (0xFF 0xFE) / UTF-8, \r\n → \n + 7. 文件存在检查 → 不存在时建议相似文件(findSimilarFile) + 8. 空 old_string → 仅在文件为空时允许(创建文件场景) + 9. Notebook 重定向 → .ipynb 必须使用 NotebookEditTool +10. 陈旧性检测 → mtime 比较,失败时回退到内容比较(避免云同步/杀毒软件时间戳干扰的误报) +11. 引号规范化 → 弯引号→直引号搜索;写入时用启发式恢复弯引号样式 +12. 歧义匹配 → 多处匹配 + 非 replace_all → 拒绝并要求更多上下文 +``` + +### 引号规范化算法(utils.ts) + +``` +搜索阶段: + 1. 精确匹配 old_string → 找到则使用 + 2. normalizeQuotes(old_string) → 弯引号转直引号 + 3. 在 normalizeQuotes(fileContent) 中搜索 + 4. 返回 fileContent 中的原始子串(保留弯引号) + +写入阶段(preserveQuoteStyle): + 检测到规范化被应用时: + → 将 new_string 中的直引号转回弯引号 + → 启发式:空白/行首/开括号后 = 开引号;字母间 = 撇号 +``` + +### 反序列化映射(desanitizeMatchString) + +模型不会看到某些 XML 标签(发送给 API 前被清理)。当模型在编辑中输出清理后的形式时,反向映射: +- `` → `` +- `\n\nH:` → `\n\nHuman:` + +### call() 写入路径中的双重陈旧性检查 + +``` +validateInput 时检查一次陈旧性 + → 通过 + → call() 中重新同步读取文件,再次检查 + → 防止 validate 和 call 之间的 TOCTOU 竞态 +``` + +--- + +## 7. 记忆系统:提取与召回 + +### 7.1 记忆提取(extractMemories) + +文件:`src/services/extractMemories/` + +**架构**:每个完整 query loop 结束时,fork 一个子 agent(共享父级 prompt cache)执行提取。 + +``` +提取流程: +1. 门控:仅主 agent,非子 agent,非远程模式 +2. 重叠守卫:已有提取在运行 → 暂存当前上下文(最新覆盖旧的) +3. 轮次节流:合格轮次未达阈值 → 跳过 +4. 互斥:主 agent 已手动写入记忆 → 跳过并推进游标 +5. 注入记忆清单:扫描目录 + 读 frontmatter → 预格式化 +6. Fork agent 执行:最多 5 轮,受限工具访问 +7. 游标推进:仅在成功后;失败时留在原位以重新考虑 +8. 尾随运行:完成后检查暂存的待处理上下文 +``` + +**工具限制**:Read/Grep/Glob 无限制;只读 Bash;Edit/Write 仅限记忆目录内。 + +### 7.2 记忆召回(findRelevantMemories) + +文件:`src/memdir/findRelevantMemories.ts` + +**不是启发式评分 — 是 LLM 评分**: + +``` +Phase 1: 扫描 + → 读取记忆目录所有 .md 文件(排除 MEMORY.md) + → 每个文件读前 30 行提取 frontmatter(name, description, type) + → 按 mtime 降序排列,上限 200 个文件 + → 单遍设计:读取后排序(而非 stat-排序-读取),syscall 减半 + +Phase 2: LLM 选择 + → 发送查询 + 格式化清单 + 最近使用工具列表给 Sonnet + → 结构化 JSON 输出 + → "只包含你确定有帮助的记忆。不确定就不包含。最多 5 个。" + → 最近使用工具列表防止为已活跃使用的工具推荐 API 文档 + → 但关于这些工具的警告/陷阱仍然会被选中 + +Phase 3: 新鲜度处理 + → 超过 1 天的记忆注入 告警 + → "此记忆已 N 天。关于代码行为的声明可能过时。" +``` + +**already-surfaced 过滤**:过滤掉之前轮次已展示的路径,5 个名额全部花在新候选上。 + +--- + +## 8. 多 Agent 协调:Swarm 架构 + +### 8.1 协调者模式(Coordinator) + +文件:`src/coordinator/coordinatorMode.ts` + +``` +任务工作流阶段: + Research(并行 workers)→ Synthesis(协调者)→ Implementation(workers)→ Verification(workers) + +并发规则: + 只读任务 → 自由并行 + 写密集任务 → 按文件区域串行 + 验证可与不同文件的实现重叠 + +Worker prompt 必须自包含: + Worker 看不到协调者对话 → 每个 prompt 需要完整上下文(文件路径、行号等) + +Continue vs Spawn 决策: + 高上下文重叠 → continue(复用已加载上下文) + 低上下文重叠 → spawn 新 worker +``` + +### 8.2 两种后端策略 + +| 后端 | 隔离方式 | 通信 | 特点 | +|------|---------|------|------| +| In-Process | `AsyncLocalStorage` 上下文隔离 | 基于文件的 mailbox | 共享 API client + MCP 连接;独立 AbortController(leader 中断不杀 worker) | +| Pane-Based (tmux/iTerm2) | 独立 OS 进程 | 基于文件的 mailbox | CLI flag 传播(`--agent-id`, `--agent-name`, `--team-name`, `--agent-color`);leader 的模型/权限/环境变量全部传播 | + +### 8.3 权限桥接 + +In-process teammate 通过 `leaderPermissionBridge` 路由权限提示到 leader 的 UI(复用 BashPermissionRequest、FileEditToolDiff 等对话框)。Bridge 不可用时回退到 mailbox 权限同步。 + +### 8.4 Fork Subagent + +文件:`src/tools/AgentTool/forkSubagent.ts` + +``` +子进程继承父级完整对话上下文 + system prompt + +递归 fork 防护: + isInForkChild() 检查对话历史中的 标签 + +Cache 共享设计: + 保留完整父级 assistant 消息(所有 tool_use 块) + 构建 tool_result 块(占位文本:"Fork started -- processing in background") + 只有最后的 text 块不同 → 最大化 prompt cache 命中 + +子进程 10 条严格规则: + 不生成子 agent、不评论、只用工具、提交变更、 + 结构化输出(Scope/Result/Key files/Files changed/Issues)、最多 500 字 +``` + +--- + +## 9. 启动优化:并行预取 + +文件:`src/main.tsx` 前 20 行 + +```typescript +// 这些副作用必须在所有其他 import 之前运行: +profileCheckpoint('main_tsx_entry') // 标记入口(在 ~135ms import 之前) +startMdmRawRead() // 并行:MDM 子进程读取(plutil/reg query) +startKeychainPrefetch() // 并行:macOS 钥匙串双读取 + // 无此优化:~65ms 同步阻塞(每次 macOS 启动) +``` + +**启动分析器**(`startupProfiler.ts`): +- 采样日志:内部 100%,外部 0.5%。记录 import_time, init_time, settings_time, total_time +- 详细分析:`CLAUDE_CODE_PROFILE_STARTUP=1`,带 `process.memoryUsage()` 快照的完整时间线 +- 非采样用户零开销(`profileCheckpoint` 立即返回) + +--- + +## 10. 其他精巧设计 + +### 10.1 35 行状态管理 + +文件:`src/state/store.ts` + +```typescript +createStore(initialState, onChange?) => { + getState() // 返回当前状态 + setState(updater) // updater: (prev) => next + // Object.is() 相等检查(引用相同则跳过) + // 触发 onChange + 通知 Set + subscribe(listener) // 返回 unsubscribe +} +``` + +无中间件,无选择器,无 devtools。配合 `useSyncExternalStore` 实现最小化重渲染。 + +### 10.2 ToolSearchTool — 延迟工具发现 + +文件:`src/tools/ToolSearchTool/ToolSearchTool.ts` + +**两种查询模式**: + +直接选择(`select:ToolA,ToolB`):精确查找,返回 `tool_reference` 块。 + +关键词搜索评分: +| 匹配类型 | 分数 | +|---------|------| +| 名称部分精确匹配 | +10(MCP: +12) | +| 名称部分子串匹配 | +5(MCP: +6) | +| 全名回退 | +3 | +| searchHint 词边界匹配 | +4 | +| 描述词边界匹配 | +2 | + +`+` 前缀标记必需词(全部必须匹配才入围)。 + +工具名解析:MCP 工具去 `mcp__` 前缀后按 `__` 和 `_` 分割;普通工具按驼峰转换和 `_` 分割。 + +### 10.3 Token 估算(无 API 调用) + +文件:`src/utils/tokens.ts` + +``` +粗略估算:content.length / bytesPerToken + +文件类型感知: + JSON/JSONL/JSONC: 2 bytes/token(密集单字符 token) + 其他: 4 bytes/token + +区块级估算: + text/thinking: length / 4 + image/document: 固定 2000 tokens + tool_use: (name + JSON.stringify(input)).length / 4 + +上下文窗口估算(tokenCountWithEstimation): + 1. 从后向前找到最后一条有 API usage 数据的消息 + 2. 处理并行工具调用:跨越共享 message.id 的兄弟记录 + 3. 返回 usage.input_tokens + 粗略估算(后续消息) + 4. 无 usage 数据时全量粗略估算 +``` + +### 10.4 错误扣留模式(Error Withholding) + +``` +流式传输中可恢复错误不暴露给调用者: + → prompt_too_long, media_size, max_output_tokens + → 推入 assistantMessages 供恢复检查 + → 所有恢复失败后才 yield 给用户 + → 防止 SDK 消费者在中间错误时终止会话 +``` + +### 10.5 Prompt Cache 稳定性设计集锦 + +| 技术 | 位置 | 效果 | +|------|------|------| +| 工具池排序 | `assembleToolPool()` | 防止 MCP 变更破坏缓存前缀 | +| 系统 prompt 分界标记 | `SYSTEM_PROMPT_DYNAMIC_BOUNDARY` | 静态部分 `scope: global` 跨组织缓存 | +| Agent 列表附件注入 | AgentTool prompt.ts | 从工具描述中移出动态列表(减少 10.2% cache_creation) | +| Fork 消息构造 | forkSubagent.ts | 所有子进程共享相同 tool_result 占位符,仅最后文本块不同 | +| Tool result 替换一致性 | toolResultStorage.ts | mustReapply 重用完全相同的替换字符串(字节级一致) | +| frozen 分区 | toolResultStorage.ts | 进入 cache 的内容永不修改 | + +### 10.6 文本选择算法 + +文件:`src/ink/selection.ts` + +- anchor + focus 点用屏幕缓冲区坐标(col, row) +- 双击/三击选词/行模式:`anchorSpan` 启用拖拽时按词/行扩展 +- 滚动捕获:`scrolledOffAbove`/`scrolledOffBelow` 累加器捕获拖拽滚动时离开视口的行 +- 词边界检测匹配 iTerm2 默认行为(路径字符 `/-+\~_.` 视为词字符) +- `getSelectedText()` 合并离屏和在屏行,尊重软换行标记重建逻辑行 + +### 10.7 鼠标 Hit Testing + +文件:`src/ink/hit-test.ts` + +递归深度优先遍历 DOM 树。**子节点逆序遍历**(后绘制的在上层),确保正确 z-order。 +- `dispatchClick()`:从最深命中节点沿 parentNode 冒泡 +- `dispatchHover()`:类 DOM mouseEnter/mouseLeave(非冒泡),diff hovered-node 集合 + +### 10.8 GrepTool 分页 + +``` +默认限制:250 条(未指定时) +head_limit=0:无限制(谨慎使用) +offset 参数:跳过前 N 条 +分页在 ripgrep 返回后、路径相对化之前应用(节省 CPU) +appliedLimit 仅在实际截断时报告(让模型知道有更多结果) +``` + +### 10.9 Partial Compact 方向性 + +``` +from(默认): + 摘要 pivot 之后的消息,保留之前的 + → prompt cache 保留(保留的消息在前) + +up_to: + 摘要 pivot 之前的消息,保留之后的 + → prompt cache 失效(摘要在保留消息之前) + → 剥离旧 compact 边界和摘要(防止陈旧边界混淆扫描器) +``` + +### 10.10 API 消息轮次分组 + +文件:`src/services/compact/grouping.ts` + +``` +groupMessagesByApiRound: + 按 API 轮次边界分组(不同 message.id 标记新轮次) + 比之前的 human-turn 分组更细粒度 + → 支持单 human turn 的 SDK/CCR/eval 会话中的精确 compact + 流式 chunk 共享 id → 同一响应内的交错 tool_result 保持正确分组 +``` diff --git a/docs/cc_analysis/analysis_20260401/Claude_Code_深度分析报告.pdf b/docs/cc_analysis/analysis_20260401/Claude_Code_深度分析报告.pdf new file mode 100644 index 0000000..1fc5bd8 Binary files /dev/null and b/docs/cc_analysis/analysis_20260401/Claude_Code_深度分析报告.pdf differ diff --git a/docs/cc_analysis/analysis_20260401/Claude_Code上下文压缩算法深度分析.md b/docs/cc_analysis/analysis_20260401/Claude_Code上下文压缩算法深度分析.md new file mode 100644 index 0000000..bf11e35 --- /dev/null +++ b/docs/cc_analysis/analysis_20260401/Claude_Code上下文压缩算法深度分析.md @@ -0,0 +1,871 @@ +# Claude Code 上下文压缩算法深度分析 + +> 基于 Claude Code 源码(2026-03-31 快照,512K 行 TypeScript)逆向分析 +> 核心文件:`src/services/compact/` 目录下 11 个文件 + +--- + +## 目录 + +1. [架构总览](#1-架构总览) +2. [第1层:微压缩(Microcompact)](#2-第1层微压缩microcompact) +3. [第2层:自动压缩(Auto-Compact)](#3-第2层自动压缩auto-compact) +4. [第3层:传统压缩(Full Compact)](#4-第3层传统压缩full-compact) +5. [第4层:Session Memory 压缩](#5-第4层session-memory-压缩) +6. [消息分组算法](#6-消息分组算法) +7. [Token 估算算法](#7-token-估算算法) +8. [压缩提示词工程](#8-压缩提示词工程) +9. [5层错误恢复中的压缩角色](#9-5层错误恢复中的压缩角色) +10. [各层对比与设计哲学](#10-各层对比与设计哲学) + +--- + +## 1. 架构总览 + +Claude Code 的上下文压缩不是单一算法,而是一个 **4层递进的压缩体系**,每层解决不同层面的问题: + +``` +用户消息 → [第1层:微压缩] → [第2层:自动压缩] → API 调用 + ↓ ↓ + 细粒度清理旧工具输出 上下文即将超限时触发 + (不丢语义,<1ms) (调用 LLM 或 Session Memory) + ↓ + ┌──────────────┴──────────────┐ + [第4层:SM压缩] [第3层:传统压缩] + (用已有摘要,<10ms) (Fork Agent 生成摘要,5-30s) +``` + +**核心原则:** 尽可能用廉价的规则操作延迟昂贵的 LLM 调用,只在不得已时丢弃信息。 + +### 涉及的源文件 + +| 文件 | 行数 | 职责 | +|------|------|------| +| `microCompact.ts` | ~400 | 微压缩:规则清理旧工具结果 | +| `apiMicrocompact.ts` | — | API 层缓存编辑集成 | +| `timeBasedMCConfig.ts` | — | 时间触发微压缩配置 | +| `autoCompact.ts` | ~350 | 自动压缩:阈值判断 + 断路器 | +| `compact.ts` | ~600+ | 传统压缩:Fork Agent 摘要 | +| `prompt.ts` | ~375 | 压缩提示词模板 | +| `sessionMemoryCompact.ts` | ~630 | Session Memory 压缩路径 | +| `grouping.ts` | ~63 | 消息按 API 轮次分组 | +| `postCompactCleanup.ts` | — | 压缩后清理 | +| `compactWarningHook.ts` | — | 压缩警告钩子 | +| `compactWarningState.ts` | — | 压缩警告状态 | + +--- + +## 2. 第1层:微压缩(Microcompact) + +**源文件:** `microCompact.ts` + +### 核心思想 + +不调用 LLM,纯规则操作——清理旧的、大块的工具输出结果,保留语义信息。这是每轮查询前都会执行的最轻量操作。 + +### 可压缩工具白名单 + +```typescript +const COMPACTABLE_TOOLS = new Set([ + 'Read', // 文件读取结果可能很大 + 'Bash', // Shell 输出可能很长 + 'Grep', // 搜索结果 + 'Glob', // 文件列表 + 'WebSearch', // 网页搜索结果 + 'WebFetch', // 网页抓取结果 + 'Edit', // 文件编辑的 diff + 'Write', // 文件写入确认 +]) +``` + +不在白名单中的工具(如 Agent、Skill、MCP 等)的结果不会被微压缩。 + +### 两个子路径 + +#### 子路径 A:时间触发微压缩 + +``` +触发条件:距上次助手消息的时间间隔超过阈值(API 缓存已过期) + +执行逻辑: +1. 收集所有可压缩工具的 tool_use ID +2. 保留最近 N 个工具结果 +3. 将更早的 tool_result 内容替换为: + "[Old tool result content cleared]" +4. 不修改 tool_use 块(保持 API 配对完整性) + +特点: +- 缓存已过期,所以无需保护缓存 +- 直接修改本地消息内容 +- 减少重传时的 token 消耗 +``` + +#### 子路径 B:缓存编辑微压缩(Cached MC) + +这是更精巧的路径——在缓存仍然有效时工作: + +``` +触发条件: +- 特性开关 CACHED_MICROCOMPACT 开启 +- 模型支持缓存编辑 API +- 当前是主线程查询(非 fork agent) + +执行逻辑: +1. collectCompactableToolIds(): 收集所有可压缩的 tool_use ID +2. registerToolResult(): 注册每个工具结果(按用户消息分组) +3. registerToolMessage(): 记录工具消息组 +4. getToolResultsToDelete(): 根据 count/keep 阈值决定删除哪些 +5. createCacheEditsBlock(): 生成 cache_edits API 块 + +关键区别: +- 不修改本地消息内容! +- 通过 API 的 cache_edits 字段告诉服务端删除特定工具结果的缓存 +- 保持 prompt cache 命中率 +- 状态通过 pendingCacheEdits / pinnedCacheEdits 管理 +``` + +**缓存编辑的状态管理:** + +```typescript +// 全局状态 +let cachedMCState: CachedMCState | null = null +let pendingCacheEdits: CacheEditsBlock | null = null + +// consumePendingCacheEdits(): +// 返回待插入的缓存编辑块,然后清空 pending 状态 +// 调用者在 API 请求后必须调用 pinCacheEdits() 固定它们 + +// getPinnedCacheEdits(): +// 返回之前已固定的缓存编辑,需要在后续请求中重新发送 + +// markToolsSentToAPIState(): +// 标记工具已发送给 API(成功响应后调用) +``` + +### Token 估算辅助 + +```typescript +function calculateToolResultTokens(block: ToolResultBlockParam): number { + if (typeof block.content === 'string') { + return roughTokenCountEstimation(block.content) // 字符数 / 4 + } + // 数组:逐项计算 + return block.content.reduce((sum, item) => { + if (item.type === 'text') return sum + roughTokenCountEstimation(item.text) + if (item.type === 'image' || item.type === 'document') return sum + 2000 + return sum + }, 0) +} + +function estimateMessageTokens(messages: Message[]): number { + let totalTokens = 0 + for (const message of messages) { + for (const block of message.message.content) { + switch (block.type) { + case 'text': totalTokens += roughTokenCountEstimation(block.text); break + case 'tool_result': totalTokens += calculateToolResultTokens(block); break + case 'image': + case 'document': totalTokens += 2000; break // 固定估算 + case 'thinking': totalTokens += roughTokenCountEstimation(block.thinking); break + case 'redacted_thinking': totalTokens += roughTokenCountEstimation(block.data); break + case 'tool_use': totalTokens += roughTokenCountEstimation(block.name + JSON.stringify(block.input ?? {})); break + default: totalTokens += roughTokenCountEstimation(JSON.stringify(block)); break + } + } + } + return Math.ceil(totalTokens * (4 / 3)) // × 4/3 保守填充 +} +``` + +--- + +## 3. 第2层:自动压缩(Auto-Compact) + +**源文件:** `autoCompact.ts` + +### 阈值计算 + +```typescript +// 有效上下文窗口 = 模型上下文窗口 - 摘要输出预留 +const MAX_OUTPUT_TOKENS_FOR_SUMMARY = 20_000 // p99.99 的摘要输出是 17,387 tokens + +function getEffectiveContextWindowSize(model: string): number { + const reservedTokensForSummary = Math.min( + getMaxOutputTokensForModel(model), + MAX_OUTPUT_TOKENS_FOR_SUMMARY, + ) + let contextWindow = getContextWindowForModel(model) + // 支持环境变量覆盖(用于测试) + if (process.env.CLAUDE_CODE_AUTO_COMPACT_WINDOW) { + contextWindow = Math.min(contextWindow, parsed) + } + return contextWindow - reservedTokensForSummary +} + +// 自动压缩阈值 = 有效上下文窗口 - 13K 缓冲 +const AUTOCOMPACT_BUFFER_TOKENS = 13_000 + +function getAutoCompactThreshold(model: string): number { + return getEffectiveContextWindowSize(model) - AUTOCOMPACT_BUFFER_TOKENS +} + +// 举例(Opus 200K 上下文): +// 有效窗口 = 200,000 - 20,000 = 180,000 +// 自动压缩阈值 = 180,000 - 13,000 = 167,000 tokens +``` + +### 其他阈值 + +```typescript +const WARNING_THRESHOLD_BUFFER_TOKENS = 20_000 // 警告阈值 +const ERROR_THRESHOLD_BUFFER_TOKENS = 20_000 // 错误阈值 +const MANUAL_COMPACT_BUFFER_TOKENS = 3_000 // 手动 /compact 的阻塞限制 +``` + +### 断路器机制 + +```typescript +const MAX_CONSECUTIVE_AUTOCOMPACT_FAILURES = 3 + +// BQ 数据分析发现:1,279 个会话连续失败 50+ 次(最多 3,272 次), +// 每天浪费约 250K 次 API 调用。所以加了断路器。 + +if (tracking.consecutiveFailures >= MAX_CONSECUTIVE_AUTOCOMPACT_FAILURES) { + return { wasCompacted: false } // 直接跳过,不再尝试 +} +``` + +### shouldAutoCompact 决策树 + +``` +shouldAutoCompact(messages, model, querySource): + +1. querySource 是 'session_memory' 或 'compact'? + → 返回 false(防止递归死锁——压缩代理自己不能触发压缩) + +2. querySource 是 'marble_origami'(上下文折叠代理)? + → 返回 false(防止破坏主线程的已提交日志) + +3. isAutoCompactEnabled() 返回 false? + → 返回 false + (检查 DISABLE_COMPACT、DISABLE_AUTO_COMPACT 环境变量和用户配置) + +4. 响应式压缩模式开启?(tengu_cobalt_raccoon gate) + → 返回 false(让 API 的 prompt-too-long 错误触发响应式压缩) + +5. 上下文折叠模式开启? + → 返回 false(上下文折叠是 90%/95% 流程,自动压缩在 93% 会干扰它) + +6. tokenCountWithEstimation(messages) - snipTokensFreed >= threshold? + → 返回 true +``` + +### autoCompactIfNeeded 执行流程 + +``` +autoCompactIfNeeded(messages, context, ...): + +1. DISABLE_COMPACT 环境变量?→ 跳过 + +2. 断路器检查:consecutiveFailures >= 3?→ 跳过 + +3. shouldAutoCompact() 返回 true? + ↓ 是 +4. 优先尝试 Session Memory 压缩 + ↓ 成功 → 返回结果 + ↓ 失败 + +5. 回退到传统压缩(compactConversation) + ↓ 成功 → 重置 consecutiveFailures = 0,返回结果 + ↓ 失败 + +6. consecutiveFailures++ + ↓ 达到 3 次 +7. 日志:断路器触发,本次会话不再尝试自动压缩 +``` + +--- + +## 4. 第3层:传统压缩(Full Compact) + +**源文件:** `compact.ts` + `prompt.ts` + +### 核心机制:Fork Agent + +传统压缩使用一个 **Fork Agent**——创建当前会话的一个分支,让它生成摘要。关键优势是**共享主会话的 prompt cache**。 + +``` +主会话消息: [user1, assistant1, user2, assistant2, ...] + ↓ 全部传入 +Fork Agent(共享 prompt cache) + ↓ 单轮回复 +生成结构化摘要( + ) + ↓ +后处理 → 替换原消息 +``` + +### 预处理管线 + +``` +原始消息 + ↓ +stripImagesFromMessages() ← 图片 → "[image]",文档 → "[document]" + ↓ (防止压缩请求自身超过上下文限制) +stripReinjectedAttachments() ← 删除技能发现/列表附件 + ↓ (压缩后会自动重新注入) +normalizeMessagesForAPI() ← 规范化消息格式 + ↓ +发送给 Fork Agent +``` + +### 摘要输出格式 + +Fork Agent 被要求生成两个 XML 块: + +```xml + +[思考草稿——用于提高摘要质量的中间推理过程] +[这部分最终会被删除,不会进入压缩后的上下文] + + + +1. Primary Request and Intent: + [详细描述用户的所有请求和意图] + +2. Key Technical Concepts: + - [概念1] + - [概念2] + +3. Files and Code Sections: + - [文件名1] + - [为什么这个文件重要] + - [代码片段] + - [文件名2] + - [代码片段] + +4. Errors and fixes: + - [错误描述]: + - [修复方式] + - [用户反馈] + +5. Problem Solving: + [问题解决过程] + +6. All user messages: + - [逐条列出所有非工具结果的用户消息] + +7. Pending Tasks: + - [待办事项1] + - [待办事项2] + +8. Current Work: + [精确描述当前工作内容,包含文件名和代码片段] + +9. Optional Next Step: + [下一步计划,包含最近对话的直接引用] + +``` + +### 防止工具调用的强力前导词 + +```typescript +const NO_TOOLS_PREAMBLE = `CRITICAL: Respond with TEXT ONLY. Do NOT call any tools. + +- Do NOT use Read, Bash, Grep, Glob, Edit, Write, or ANY other tool. +- You already have all the context you need in the conversation above. +- Tool calls will be REJECTED and will waste your only turn — you will fail the task. +- Your entire response must be plain text: an block followed by a block. +` +``` + +为什么需要这么强力?注释解释了: + +``` +// Sonnet 4.6+ 自适应思考模型有时会忽略较弱的尾部指令并尝试调用工具。 +// 在 maxTurns: 1 的情况下,被拒绝的工具调用意味着没有文本输出 +// → 回退到流式备用路径(4.6 上 2.79% vs 4.5 上 0.01%)。 +// 把这个放在最前面并明确说明拒绝后果,可以防止浪费轮次。 +``` + +### 后处理(formatCompactSummary) + +```typescript +function formatCompactSummary(summary: string): string { + // 1. 删除 块(草稿,已无价值) + summary = summary.replace(/[\s\S]*?<\/analysis>/, '') + + // 2. 提取 内容,替换为可读标题 + const match = summary.match(/([\s\S]*?)<\/summary>/) + if (match) { + summary = summary.replace(/[\s\S]*?<\/summary>/, + `Summary:\n${match[1].trim()}`) + } + + // 3. 清理多余空行 + summary = summary.replace(/\n\n+/g, '\n\n') + return summary.trim() +} +``` + +### 压缩后消息序列 + +``` +[CompactBoundaryMessage] ← 标记压缩边界(含 token 统计、trigger 类型) +[SummaryUserMessage] ← 格式化后的摘要 +[messagesToKeep] ← 保留的最近消息(如果有) +[Attachments] ← 重新注入的附件 + - 最近读取的文件(前 5 个,每个 ≤ 5K tokens,总预算 50K) + - Plan 文件(如果有活跃计划) + - MCP 指令增量 + - 技能发现增量 + - 代理列表增量 +[HookResults] ← PreCompact/PostCompact 钩子结果 +``` + +### Prompt-Too-Long 重试机制 + +当压缩请求本身超过上下文限制时: + +```typescript +const MAX_PTL_RETRIES = 3 + +function truncateHeadForPTLRetry(messages, ptlResponse): Message[] | null { + // 1. 移除上一次重试的标记消息(防止重试停滞) + // 2. 按 API 轮次分组 + const groups = groupMessagesByApiRound(messages) + if (groups.length < 2) return null // 无法再裁剪 + + // 3. 决定删除多少组 + const tokenGap = getPromptTooLongTokenGap(ptlResponse) + if (tokenGap !== undefined) { + // 能解析出 token 缺口 → 精确删除 + let acc = 0, dropCount = 0 + for (const g of groups) { + acc += roughTokenCountEstimationForMessages(g) + dropCount++ + if (acc >= tokenGap) break + } + } else { + // 不能解析 → 删除 20% 最旧的组 + dropCount = Math.max(1, Math.floor(groups.length * 0.2)) + } + + // 4. 保证至少保留 1 组 + dropCount = Math.min(dropCount, groups.length - 1) + + // 5. 如果裁剪后第一条是 assistant 消息,补一个 user marker + // (API 要求第一条消息必须是 user) + const sliced = groups.slice(dropCount).flat() + if (sliced[0]?.type === 'assistant') { + return [createUserMessage({ content: PTL_RETRY_MARKER, isMeta: true }), ...sliced] + } + return sliced +} +``` + +### 部分压缩(Partial Compact) + +支持两个方向: + +| 方向 | 提示词 | 用途 | +|------|--------|------| +| `from`(默认) | `PARTIAL_COMPACT_PROMPT` | 保留旧消息,仅摘要"最近的消息" | +| `up_to` | `PARTIAL_COMPACT_UP_TO_PROMPT` | 摘要旧消息,保留新消息。摘要会放在开头,后续消息跟在后面 | + +`up_to` 模式的摘要包含一个特殊的第 9 章节 "Context for Continuing Work",专门为后续消息提供上下文。 + +--- + +## 5. 第4层:Session Memory 压缩 + +**源文件:** `sessionMemoryCompact.ts` + +### 核心思想 + +不调用 LLM 生成新摘要,而是直接使用已经通过后台记忆提取(`extractMemories`)积累的 Session Memory 作为"摘要"。 + +### 优势 + +- **速度快**:不需要 API 调用,<10ms +- **质量可预测**:Session Memory 是在每轮查询后渐进更新的,不是一次性压缩 +- **保留近期消息**:不像传统压缩那样替换所有消息 + +### 配置参数 + +```typescript +const DEFAULT_SM_COMPACT_CONFIG = { + minTokens: 10_000, // 至少保留 10K tokens 的最近消息 + minTextBlockMessages: 5, // 至少保留 5 条含文本的消息 + maxTokens: 40_000, // 最多保留 40K tokens(硬上限) +} +// 这些值通过 GrowthBook 远程配置,可动态调整 +``` + +### calculateMessagesToKeepIndex 算法 + +这是 Session Memory 压缩的核心算法——决定保留哪些最近的消息: + +``` +输入:messages[], lastSummarizedIndex(Session Memory 已覆盖到哪条消息) + +算法: +1. startIndex = lastSummarizedIndex + 1 + (即:从 Session Memory 尚未覆盖的消息开始) + +2. 计算当前 [startIndex, end] 范围的 token 总量和含文本消息数 + +3. 如果已经超过 maxTokens (40K) → 直接返回(不再扩展) + +4. 如果同时满足 ≥ minTokens (10K) AND ≥ minTextBlockMessages (5) + → 直接返回(已足够) + +5. 否则,从 startIndex 往前逐条扩展: + - 每加入一条消息,更新 token 和消息计数 + - 停止条件: + a. 达到 maxTokens (40K) + b. 同时满足 minTokens 和 minTextBlockMessages + c. 到达上一个 CompactBoundary(不跨越旧的压缩边界) + +6. adjustIndexToPreserveAPIInvariants(messages, startIndex) + → 确保不切断 tool_use/tool_result 配对 + → 确保不分离共享 message.id 的 thinking 块 +``` + +### adjustIndexToPreserveAPIInvariants 算法 + +这个算法解决一个棘手的问题——流式传输时,一个 API 响应会产生多条消息(thinking、tool_use 等),它们共享同一个 `message.id`。如果在中间切断,`normalizeMessagesForAPI` 合并时会丢失 thinking 块。 + +``` +输入:messages[], startIndex + +步骤 1:修复 tool_use/tool_result 配对 + 1a. 收集 [startIndex, end] 范围内所有 tool_result 的 tool_use_id + 1b. 收集范围内已有的 tool_use_id + 1c. 找出缺失的 tool_use_id(在范围外) + 1d. 向前搜索,把包含缺失 tool_use 的 assistant 消息纳入范围 + +步骤 2:修复 thinking 块分离 + 2a. 收集范围内所有 assistant 消息的 message.id + 2b. 向前搜索,把共享同一 message.id 的 assistant 消息纳入范围 + +返回:调整后的 startIndex +``` + +**源码注释中的真实 bug 场景:** + +``` +Session 存储(压缩前): + Index N: assistant, message.id: X, content: [thinking] + Index N+1: assistant, message.id: X, content: [tool_use: ORPHAN_ID] + Index N+2: assistant, message.id: X, content: [tool_use: VALID_ID] + Index N+3: user, content: [tool_result: ORPHAN_ID, tool_result: VALID_ID] + +如果 startIndex = N+2: + 旧代码:只检查 N+2 的 tool_results,找不到,返回 N+2 + normalizeMessagesForAPI 合并后: + msg[1]: assistant with [tool_use: VALID_ID] ← ORPHAN tool_use 被排除! + msg[2]: user with [tool_result: ORPHAN_ID, tool_result: VALID_ID] + API 报错:孤立的 tool_result 引用了不存在的 tool_use +``` + +### trySessionMemoryCompaction 完整流程 + +``` +trySessionMemoryCompaction(messages, agentId, autoCompactThreshold): + +1. shouldUseSessionMemoryCompaction()? + → 检查 tengu_session_memory AND tengu_sm_compact 特性开关 + → 支持 ENABLE_CLAUDE_CODE_SM_COMPACT / DISABLE_CLAUDE_CODE_SM_COMPACT 环境变量 + +2. 初始化远程配置(仅首次) + +3. 等待正在进行的 Session Memory 提取完成(带超时) + +4. 获取 lastSummarizedMessageId 和 sessionMemory 内容 + +5. Session Memory 文件不存在?→ 返回 null +6. Session Memory 是空模板?→ 返回 null + +7. 确定 lastSummarizedIndex: + a. 正常情况:在 messages 中查找 lastSummarizedMessageId 的索引 + b. 恢复的会话:设为 messages.length - 1(从末尾开始) + +8. calculateMessagesToKeepIndex() → startIndex +9. 过滤掉 messagesToKeep 中的旧 CompactBoundary + +10. 执行 SessionStart 钩子(恢复 CLAUDE.md 等上下文) + +11. 创建压缩结果: + - 截断过大的 Session Memory 章节 + - 生成摘要用户消息 + - 附加 Plan 文件(如果有) + +12. 检查压缩后 token 是否仍超过阈值 + → 是:返回 null(回退到传统压缩) + → 否:返回 CompactionResult +``` + +--- + +## 6. 消息分组算法 + +**源文件:** `grouping.ts` + +```typescript +function groupMessagesByApiRound(messages: Message[]): Message[][] { + const groups: Message[][] = [] + let current: Message[] = [] + let lastAssistantId: string | undefined + + for (const msg of messages) { + // 当出现新的 assistant message.id 时,开始新的一组 + if ( + msg.type === 'assistant' && + msg.message.id !== lastAssistantId && + current.length > 0 + ) { + groups.push(current) + current = [msg] + } else { + current.push(msg) + } + if (msg.type === 'assistant') { + lastAssistantId = msg.message.id + } + } + + if (current.length > 0) { + groups.push(current) + } + return groups +} +``` + +**设计细节:** + +- 同一 API 请求的流式块共享同一个 `message.id` +- `StreamingToolExecutor` 在流式输出期间交错插入 `tool_result`,但它们属于同一轮次 +- 只要 `message.id` 不变,所有消息都在同一组内 +- 不跟踪未解决的 `tool_use` ID——让分组边界自然形成,由 `ensureToolResultPairing` 在 API 层修复残留的配对问题 + +--- + +## 7. Token 估算算法 + +贯穿所有压缩路径的核心辅助: + +### 粗略估算 + +```typescript +function roughTokenCountEstimation(text: string): number { + return text.length / 4 // 经验法则:4 字符 ≈ 1 token +} +``` + +### 消息级估算 + +```typescript +function estimateMessageTokens(messages: Message[]): number { + let totalTokens = 0 + for (const message of messages) { + if (message.type !== 'user' && message.type !== 'assistant') continue + for (const block of message.message.content) { + switch (block.type) { + case 'text': + totalTokens += roughTokenCountEstimation(block.text) + break + case 'tool_result': + totalTokens += calculateToolResultTokens(block) + break + case 'image': + case 'document': + totalTokens += 2000 // 固定值,不论实际大小 + break + case 'thinking': + totalTokens += roughTokenCountEstimation(block.thinking) + // 注意:不计算签名(signature 是元数据,不被模型 tokenize) + break + case 'redacted_thinking': + totalTokens += roughTokenCountEstimation(block.data) + break + case 'tool_use': + totalTokens += roughTokenCountEstimation( + block.name + JSON.stringify(block.input ?? {}) + ) + // 不计算 JSON wrapper 和 id 字段 + break + default: + totalTokens += roughTokenCountEstimation(JSON.stringify(block)) + break + } + } + } + return Math.ceil(totalTokens * (4 / 3)) // 保守填充 33% +} +``` + +### 精确计算 + +```typescript +// 当有 API 响应的 usage 数据时,使用精确值 +function tokenCountFromLastAPIResponse(messages: Message[]): number | undefined { + // 从最后一条 assistant 消息的 usage 字段读取 + // usage.input_tokens 包含了 API 看到的实际 token 数 +} + +// 混合策略 +function tokenCountWithEstimation(messages: Message[]): number { + // 优先使用 API 返回的精确值 + // 不可用时回退到估算 +} +``` + +--- + +## 8. 压缩提示词工程 + +**源文件:** `prompt.ts` + +### 三种提示词模板 + +| 模板 | 变量名 | 用途 | +|------|--------|------| +| 完整压缩 | `BASE_COMPACT_PROMPT` | 摘要整个对话 | +| 部分压缩(from) | `PARTIAL_COMPACT_PROMPT` | 只摘要最近的消息 | +| 部分压缩(up_to) | `PARTIAL_COMPACT_UP_TO_PROMPT` | 摘要旧消息,作为后续消息的前导 | + +### 提示词结构 + +``` +[NO_TOOLS_PREAMBLE] ← 强力禁止工具调用 +[DETAILED_ANALYSIS_INSTRUCTION] ← 要求 思考草稿 +[MAIN_PROMPT] ← 9 章节结构化摘要要求 + 示例 +[Custom Instructions] ← 用户自定义指令(如果有) +[NO_TOOLS_TRAILER] ← 再次强调不要调用工具 +``` + +### 块的作用 + +``` +提示词要求模型先在 中"打草稿": +1. 按时间顺序分析每条消息 +2. 识别用户意图、技术决策、代码模式 +3. 特别关注用户反馈("用户让你做不同的事情") +4. 双重检查技术准确性和完整性 + +这类似于 Chain-of-Thought,但最终会被 formatCompactSummary() 删除, +只保留 部分进入压缩后的上下文。 +``` + +### 压缩后的摘要消息 + +```typescript +function getCompactUserSummaryMessage(summary, suppressFollowUp, transcriptPath, recentPreserved): string { + let msg = `This session is being continued from a previous conversation that ran out of context. +The summary below covers the earlier portion of the conversation. + +${formatCompactSummary(summary)}` + + // 如果有 transcript 路径,提供回溯指引 + if (transcriptPath) { + msg += `\n\nIf you need specific details from before compaction +(like exact code snippets, error messages, or content you generated), +read the full transcript at: ${transcriptPath}` + } + + // 如果保留了最近消息 + if (recentMessagesPreserved) { + msg += `\n\nRecent messages are preserved verbatim.` + } + + // 如果是自动压缩(suppressFollowUp = true) + if (suppressFollowUpQuestions) { + msg += `\nContinue the conversation from where it left off +without asking the user any further questions. +Resume directly — do not acknowledge the summary, +do not recap what was happening, +do not preface with "I'll continue" or similar. +Pick up the last task as if the break never happened.` + } + + // 如果是 Proactive/KAIROS 模式 + if (proactiveModule?.isProactiveActive()) { + msg += `\n\nYou are running in autonomous/proactive mode. +This is NOT a first wake-up — you were already working autonomously before compaction. +Continue your work loop: pick up where you left off based on the summary above. +Do not greet the user or ask what to work on.` + } + + return msg +} +``` + +--- + +## 9. 5层错误恢复中的压缩角色 + +在 `query.ts` 的 5 层错误恢复机制中,压缩系统承担了前 2 层: + +``` +API 调用失败 + ↓ +第1层:上下文折叠排水(Context Collapse Drain) + ↓ 失败 +第2层:响应式压缩(Reactive Compact) ← 使用 compact.ts + ↓ 失败 +第3层:最大输出升级(8K → 64K tokens) + ↓ 失败 +第4层:多轮恢复(注入"请继续"消息,最多 3 次) + ↓ 失败 +第5层:模型回退(切换到备用模型,剥离 thinking 签名块) + ↓ 失败 +暴露错误给用户 +``` + +**响应式压缩** vs **主动压缩**: + +| 属性 | 主动压缩(Auto-Compact) | 响应式压缩(Reactive Compact) | +|------|--------------------------|-------------------------------| +| 触发 | token 数超过阈值 | API 返回 prompt-too-long 错误 | +| 时机 | API 调用前 | API 调用失败后 | +| 路径 | `autoCompact.ts` → `compact.ts` | `query.ts` 错误恢复层 | +| 回退 | SM → 传统压缩 → 放弃 | 传统压缩 → 裁剪最旧消息 | + +--- + +## 10. 各层对比与设计哲学 + +### 横向对比 + +| 属性 | 微压缩 | Session Memory | 传统压缩 | PTL Recovery | +|------|--------|---------------|----------|-------------| +| **调用 LLM** | 否 | 否 | 是(Fork Agent) | 否 | +| **信息损失** | 最小(仅删工具输出) | 中等(靠已有摘要) | 中等(9章摘要) | 高(丢弃最旧消息) | +| **延迟** | <1ms | <10ms | 5-30s | ~0 | +| **触发** | 每轮自动 | 自动压缩优先路径 | 自动/手动 | 压缩自身超限 | +| **prompt cache** | 保留(缓存编辑) | 破坏 | 共享(Fork) | 破坏 | +| **最大输出** | — | — | 20K tokens | — | +| **断路器** | 无 | 无 | 有(3次) | 有(3次) | +| **token 预算** | — | 40K 最大保留 | 50K 文件重注入 | 按组裁剪 | + +### 设计哲学总结 + +1. **渐进式降级**:从无损操作(微压缩)到有损操作(传统压缩)到丢弃操作(PTL Recovery),每一层都比上一层代价更高 + +2. **缓存优先**:微压缩的缓存编辑路径专门设计为不破坏 prompt cache,传统压缩通过 Fork Agent 共享 cache + +3. **安全性保证**: + - 永远不切断 tool_use/tool_result 配对 + - 永远不分离共享 message.id 的消息块 + - 断路器防止失败时无限重试 + - 递归保护防止压缩代理触发自身压缩 + +4. **可观测性**:每个压缩操作都记录 `logEvent`(如 `tengu_compact`, `tengu_cached_microcompact`),包含压缩前后的 token 数、触发原因、重试次数等 + +5. **可配置性**:几乎所有阈值都支持环境变量覆盖(用于测试)或 GrowthBook 远程配置(用于动态调整) + +--- + +*来自:AI超元域 | B站频道:https://space.bilibili.com/3493277319825652* + +*基于 Claude Code 源码逆向分析,2026-03-31* diff --git a/docs/cc_analysis/analysis_20260401/Claude_Code模型切换机制深度分析.md b/docs/cc_analysis/analysis_20260401/Claude_Code模型切换机制深度分析.md new file mode 100644 index 0000000..a48ecd5 --- /dev/null +++ b/docs/cc_analysis/analysis_20260401/Claude_Code模型切换机制深度分析.md @@ -0,0 +1,441 @@ +# Claude Code 是否偷换模型?源码级逆向分析 + +> 基于 Claude Code 源码(2026-03-31 快照,512K 行 TypeScript)逆向分析 +> 核心文件:`src/utils/model/model.ts`、`src/services/api/withRetry.ts`、`src/query.ts`、`src/services/api/claude.ts` + +--- + +## 结论先行 + +**不存在"偷换模型"。你的主对话模型完全由你控制,不会被偷偷降级。** + +但 Claude Code 确实在后台使用 Haiku(弱模型)执行辅助任务(配额检查、摘要生成等),这些 Haiku 调用不会出现在你的对话中,也不会替代你与 Opus 的交互。 + +唯一可能改变主对话模型的场景是 **529 连续过载回退**(Opus → Sonnet),此时会显示明确的系统消息通知用户。 + +--- + +## 1. 主对话模型选择:完全由用户控制 + +### 模型选择优先级(源码确认) + +```typescript +// src/utils/model/model.ts:92 +function getMainLoopModel(): ModelName { + const model = getUserSpecifiedModelSetting() + if (model !== undefined && model !== null) { + return parseUserSpecifiedModel(model) + } + return getDefaultMainLoopModel() +} + +// src/utils/model/model.ts:61 +function getUserSpecifiedModelSetting(): ModelSetting | undefined { + // 优先级从高到低: + // 1. /model 命令(会话内切换)— 最高优先级 + const modelOverride = getMainLoopModelOverride() + if (modelOverride !== undefined) return modelOverride + + // 2. --model 启动参数 / ANTHROPIC_MODEL 环境变量 / settings.json + return process.env.ANTHROPIC_MODEL || settings.model || undefined +} +``` + +**你设了 Opus,主对话就用 Opus。没有任何代码在你不知情的情况下把它换掉。** + +### 默认模型 + +```typescript +// src/utils/model/model.ts:105 +function getDefaultOpusModel(): ModelName { + // 支持环境变量覆盖 + if (process.env.ANTHROPIC_DEFAULT_OPUS_MODEL) { + return process.env.ANTHROPIC_DEFAULT_OPUS_MODEL + } + return getModelStrings().opus46 // 默认 Opus 4.6 +} + +function getDefaultSonnetModel(): ModelName { + if (process.env.ANTHROPIC_DEFAULT_SONNET_MODEL) { + return process.env.ANTHROPIC_DEFAULT_SONNET_MODEL + } + // 3P 提供商默认 Sonnet 4.5(可能尚未支持 4.6) + if (getAPIProvider() !== 'firstParty') return getModelStrings().sonnet45 + return getModelStrings().sonnet46 +} + +function getDefaultHaikuModel(): ModelName { + if (process.env.ANTHROPIC_DEFAULT_HAIKU_MODEL) { + return process.env.ANTHROPIC_DEFAULT_HAIKU_MODEL + } + return getModelStrings().haiku45 // Haiku 4.5(所有平台通用) +} +``` + +--- + +## 2. 唯一会改变主对话模型的场景:529 过载回退 + +### 触发条件(极其严格) + +```typescript +// src/services/api/withRetry.ts:326-350 + +// 必须同时满足以下所有条件: +// 1. 收到 529 错误(服务器过载) +// 2. 连续 529 次数 >= MAX_529_RETRIES +// 3. 配置了 fallbackModel +// 4. 以下二选一: +// a. 设置了 FALLBACK_FOR_ALL_PRIMARY_MODELS 环境变量 +// b. 非 Claude.ai 订阅用户 且 使用非自定义 Opus 模型 + +if (is529Error(error) && + (process.env.FALLBACK_FOR_ALL_PRIMARY_MODELS || + (!isClaudeAISubscriber() && isNonCustomOpusModel(options.model)))) { + consecutive529Errors++ + if (consecutive529Errors >= MAX_529_RETRIES) { + if (options.fallbackModel) { + // 触发回退 + throw new FallbackTriggeredError(options.model, options.fallbackModel) + } + } +} +``` + +**关键限制:** + +| 条件 | 说明 | +|------|------| +| `!isClaudeAISubscriber()` | **Claude.ai 订阅用户默认不触发回退** | +| `isNonCustomOpusModel()` | 仅对标准 Opus 模型生效(自定义模型 ID 不触发) | +| `MAX_529_RETRIES` | 需要连续多次 529 才触发 | + +### 回退执行过程(透明可见) + +```typescript +// src/query.ts:894-946 + +if (innerError instanceof FallbackTriggeredError && fallbackModel) { + // 1. 切换模型 + currentModel = fallbackModel // Opus → Sonnet(不是 Haiku) + + // 2. 清理孤立的 assistant 消息 + yield* yieldMissingToolResultBlocks(assistantMessages, 'Model fallback triggered') + assistantMessages.length = 0 + + // 3. 剥离 thinking 签名块(签名绑定原模型,发给回退模型会导致 400) + // 源码注释: + // "Strip before retry so the fallback model gets clean history." + + // 4. 更新工具上下文中的模型引用 + toolUseContext.options.mainLoopModel = fallbackModel + + // 5. 记录分析事件 + logEvent('tengu_api_opus_fallback_triggered', { + original_model: options.model, + fallback_model: options.fallbackModel, + }) + + // 6. 显示系统消息通知用户(在 UI 中可见) + `Switched to ${renderModelName(fallbackModel)} due to high demand + for ${renderModelName(originalModel)}` +} +``` + +### 回退目标 + +```typescript +// src/main.tsx:1336-1337 +// 回退模型必须与主模型不同 +if (fallbackModel && options.model && fallbackModel === options.model) { + // 验证失败,不设置回退 +} + +// 回退目标是 Sonnet(不是 Haiku) +// 通过 getDefaultSonnetModel() 获取 +``` + +### 用户如何知道发生了回退 + +回退时,UI 中会显示一条系统消息: + +``` +Switched to Claude Sonnet 4.6 due to high demand for Claude Opus 4.6 +``` + +同时在 `AssistantTextMessage.tsx:178` 中提供操作建议: + +``` +To continue immediately, use /model to switch to Sonnet and continue coding. +``` + +--- + +## 3. 后台辅助任务使用 Haiku — 不影响主对话 + +Claude Code 在后台使用 Haiku(`getSmallFastModel()`)执行多种轻量辅助任务。这些调用**完全独立于你的对话**,不会出现在消息流中。 + +### 所有 Haiku 使用场景(逐一列出) + +#### 3.1 配额检查 + +```typescript +// src/services/claudeAiLimits.ts:199-218 +async function makeTestQuery() { + const model = getSmallFastModel() // → Haiku + const anthropic = await getAnthropicClient({ maxRetries: 0, model, source: 'quota_check' }) + // 发送 max_tokens: 1 的最小请求,仅读取响应头的配额信息 + return anthropic.beta.messages.create({ + model, + max_tokens: 1, + messages: [{ role: 'user', content: 'quota' }], + }).asResponse() +} +``` + +**用途:** 检测用户是否已达配额限制。只读取 HTTP 响应头,不使用模型输出。 + +#### 3.2 离开摘要(Away Summary) + +```typescript +// src/services/awaySummary.ts:44-52 +// 当用户长时间离开后返回时,生成一个简短摘要 +{ + thinkingConfig: { type: 'disabled' }, // 关闭思考 + tools: [], // 不给工具 + model: getSmallFastModel(), // → Haiku +} +``` + +**用途:** 用户长时间不操作后返回时,快速生成"你离开期间发生了什么"的简要摘要。 + +#### 3.3 Token 计数 + +```typescript +// src/services/api/claude.ts:540-543 +// WARNING: if you change this to use a non-Haiku model, +// this request will fail in 1P unless it uses getCLISyspromptPrefix. +const model = getSmallFastModel() // → Haiku +// 调用 countTokens API 获取精确 token 数 +``` + +**用途:** 调用 Anthropic 的 `countTokens` 端点获取精确 token 计数,用于自动压缩阈值判断。 + +#### 3.4 工具使用摘要 + +```typescript +// src/services/api/claude.ts:3273-3280 +// 在主模型流式输出期间,并行用 Haiku 生成工具调用的简短描述 +{ + model: getSmallFastModel(), // → Haiku + enablePromptCaching: false, +} +``` + +**用途:** 当模型连续调用多个工具时,生成简短的工具使用描述(如"读取了 config.ts 并修改了第 42 行")。 + +源码注释明确说明了并行关系: +```typescript +// src/query.ts:1054 +// Yield tool use summary from previous turn — +// haiku (~1s) resolved during model streaming (5-30s) +``` +Haiku 在后台 1 秒内完成摘要,同时 Opus 还在处理你的主对话。 + +#### 3.5 记忆相关性评分 + +```typescript +// src/memdir/findRelevantMemories.ts +// 用 Sonnet(非 Haiku)评估记忆文件的相关性 +// 从标题中选择最多 5 条与当前查询相关的记忆 +``` + +#### 3.6 Web 搜索预处理 + +```typescript +// src/tools/WebSearchTool/WebSearchTool.ts:280 +model: useHaiku ? getSmallFastModel() : context.options.mainLoopModel, +toolChoice: useHaiku ? { type: 'tool', name: 'web_search' } : undefined, +``` + +**用途:** 当条件满足时(`useHaiku` 标志),用 Haiku 预处理搜索查询。 + +#### 3.7 其他辅助调用 + +| 调用者 | 模型 | 用途 | +|--------|------|------| +| `tokenEstimation.ts` | Haiku | Vertex 全局区域 token 计数回退 | +| `analyzeContext.ts` | Haiku | 上下文分析的 countTokens 回退 | +| `claudeAiLimits.ts` | Haiku | 配额状态检查 | +| Bedrock `client.ts` | Haiku | 小模型可指定不同 AWS 区域 | + +### 辅助任务 vs 主对话的隔离 + +``` +你的对话: + 用户 → [Opus 4.6] → 助手响应 → [Opus 4.6] → 助手响应 → ... + +后台并行: + [Haiku] → 配额检查(1 token) + [Haiku] → 工具使用摘要(~1s) + [Haiku] → token 计数 + +两条线完全隔离,Haiku 的输出不进入你的对话消息流。 +``` + +--- + +## 4. Fast Mode:同模型加速,不换模型 + +这是一个常见误解,源码中有明确声明: + +```typescript +// src/constants/prompts.ts:702 +`Fast mode for Claude Code uses the same ${FRONTIER_MODEL_NAME} model + with faster output. It does NOT switch to a different model. + It can be toggled with /fast.` +``` + +Fast Mode 通过 API 的 speed 参数请求更快的输出,模型不变。如果 API 拒绝 fast mode(如组织未启用),会自动降回标准速度: + +```typescript +// src/services/api/withRetry.ts:310-313 +if (wasFastModeActive && isFastModeNotEnabledError(error)) { + handleFastModeRejectedByAPI() + retryContext.fastMode = false + continue // 重试,同一模型,标准速度 +} +``` + +--- + +## 5. Plan Mode 临时模型切换 — 透明可见 + +```typescript +// src/commands/model/model.tsx:214 +// Do not update fast mode in settings since this is an automatic downgrade + +// src/commands/model/model.tsx:256 +onDone(`Current model: ${chalk.bold(renderModelLabel(mainLoopModelForSession))} + (session override from plan mode)\nBase model: ${displayModel}`) +``` + +Plan Mode 可能临时使用 Sonnet 执行实现步骤,但: +- UI 显示 "session override from plan mode" +- 显示 Base model 让你知道原始模型 +- 不修改 settings 中的模型设置 + +--- + +## 6. Effort 降级 — 不换模型,换参数 + +```typescript +// src/utils/effort.ts:162 +// API rejects 'max' on non-Opus-4.6 models — downgrade to 'high'. +``` + +如果设置了 `effort: max` 但模型不支持,会降级 effort 参数(max → high),不换模型。 + +--- + +## 7. 全局搜索:不存在的场景 + +通过对 512K 行源码的全局搜索,以下场景**被确认不存在**: + +| 假设的偷换场景 | 搜索结果 | 证据 | +|----------------|----------|------| +| 根据订阅层级偷换弱模型 | **不存在** | `getMainLoopModel()` 不检查订阅类型 | +| 根据额度用量偷换弱模型 | **不存在** | 额度用完返回 429 错误,不换模型 | +| 随机/概率性降级到弱模型 | **不存在** | 无 `Math.random()` 与模型选择相关的代码 | +| A/B 测试使用弱模型回答 | **不存在** | GrowthBook 控制功能开关,不控制模型 | +| 静默替换后不通知用户 | **不存在** | 529 回退有系统消息,其他场景不换主模型 | +| 把 Opus 对话路由到 Haiku | **不存在** | Haiku 仅用于辅助任务 | +| 根据问题复杂度选择弱模型 | **不存在** | 主循环始终使用用户指定的模型 | +| 根据时间段降级(如高峰期) | **不存在** | 无时间相关的模型选择逻辑 | +| 首次用户用弱模型 | **不存在** | 默认模型是 Opus 4.6 | + +--- + +## 8. 模型使用全景图 + +``` +┌─────────────────────────────────────────────────────────┐ +│ 你的主对话 │ +│ │ +│ 模型:你指定的(默认 Opus 4.6) │ +│ 控制:/model 命令 > --model 参数 > 环境变量 > 设置 │ +│ 唯一例外:529 连续过载 → Sonnet 回退(有通知) │ +│ │ +├─────────────────────────────────────────────────────────┤ +│ 后台辅助任务(Haiku) │ +│ │ +│ 配额检查 → 1 token 请求,只读响应头 │ +│ Token 计数 → countTokens API 调用 │ +│ 工具摘要 → 并行于主对话,~1s 完成 │ +│ 离开摘要 → 用户回来时显示简要信息 │ +│ Web 搜索 → 搜索查询预处理(条件触发) │ +│ │ +│ 这些不进入你的对话消息流 │ +│ │ +├─────────────────────────────────────────────────────────┤ +│ Fork Agent 任务 │ +│ │ +│ 上下文压缩 → 使用你的主模型(共享 prompt cache) │ +│ 记忆提取 → 使用你的主模型 │ +│ 权限分类器 → Haiku(auto 模式下的命令安全分类) │ +│ │ +│ 压缩结果进入上下文,但不替代你的对话 │ +└─────────────────────────────────────────────────────────┘ +``` + +--- + +## 9. 如何验证你正在使用的模型 + +### 方法 1:查看 API 响应 + +每条助手消息的内部结构中包含模型信息: + +```typescript +// AssistantMessage.message.model 字段包含实际使用的模型 ID +// 例如:"claude-opus-4-6-20260301" +``` + +### 方法 2:使用 /cost 命令 + +`/cost` 命令显示按模型分类的 token 使用量,你可以看到每个模型消耗了多少 token。 + +### 方法 3:检查 status line + +状态栏显示当前模型名称。如果发生回退,会更新为回退后的模型。 + +### 方法 4:环境变量调试 + +```bash +# 查看所有 API 调用的模型信息 +export CLAUDE_CODE_DEBUG=1 + +# 强制禁用回退 +unset FALLBACK_FOR_ALL_PRIMARY_MODELS +``` + +--- + +## 10. 总结 + +| 问题 | 答案 | 依据 | +|------|------|------| +| Opus 会被偷偷换成 Haiku 吗? | **不会** | Haiku 仅用于辅助任务,不进入主对话 | +| Opus 会被偷偷换成 Sonnet 吗? | **仅在 529 连续过载时,且会通知** | `withRetry.ts:347` + `query.ts:946` | +| Claude.ai 订阅用户会被降级吗? | **默认不会** | `!isClaudeAISubscriber()` 条件排除 | +| 后台有 Haiku 调用吗? | **有,但不影响你的对话** | 6 种辅助任务,全部隔离 | +| Fast Mode 换模型吗? | **不换** | 源码明确声明 "does NOT switch to a different model" | +| 有没有根据问题难度选模型? | **没有** | 主循环始终使用用户指定模型 | + +**一句话:你付费用 Opus,对话就用 Opus。Haiku 只在幕后干杂活。** + +--- + +*来自:AI超元域 | B站频道:https://space.bilibili.com/3493277319825652* + +*基于 Claude Code 源码逆向分析,2026-03-31* diff --git a/docs/cc_analysis/analysis_20260401/Claude_Code记忆系统深度分析.md b/docs/cc_analysis/analysis_20260401/Claude_Code记忆系统深度分析.md new file mode 100644 index 0000000..49523ff --- /dev/null +++ b/docs/cc_analysis/analysis_20260401/Claude_Code记忆系统深度分析.md @@ -0,0 +1,764 @@ +# Claude Code Memory 记忆系统深度分析 + +> 基于 Claude Code 源码(2026-03-31 快照,512K 行 TypeScript)逆向分析 +> 核心文件:`src/memdir/`(8个文件)、`src/services/extractMemories/`(2个)、`src/services/SessionMemory/`(3个)、`src/services/teamMemorySync/`(5个) + +--- + +## 1. 三层记忆架构总览 + +Claude Code 的记忆系统不是一个简单的文件存储,而是一个**三层递进的知识管理体系**: + +``` +┌─────────────────────────────────────────────────────────────┐ +│ 第3层:团队记忆(Team Memory) │ +│ 跨用户、按仓库 │ 服务端同步 │ REST API + 乐观锁 │ +│ 秘密扫描保护 │ 文件监听器 │ 30种凭证检测规则 │ +├─────────────────────────────────────────────────────────────┤ +│ 第2层:持久记忆(Persistent Memory / extractMemories) │ +│ 跨会话、按项目 │ 本地文件 │ 后台 Fork Agent 自动提取 │ +│ 4种类型分类 │ MEMORY.md 索引 │ AI 相关性召回 │ +├─────────────────────────────────────────────────────────────┤ +│ 第1层:会话记忆(Session Memory) │ +│ 仅当前会话 │ 单个文件 │ 10个固定章节 │ 服务于压缩 │ +└─────────────────────────────────────────────────────────────┘ +``` + +| 层级 | 范围 | 持久性 | 触发方式 | 存储位置 | +|------|------|--------|----------|----------| +| 会话记忆 | 当前会话 | 临时(会话结束即停用) | 后采样钩子(token+工具调用阈值) | 会话目录下单文件 | +| 持久记忆 | 跨会话、按项目 | 永久(直到手动删除) | 每轮查询结束时(Stop Hook) | `~/.claude/projects/<项目>/memory/` | +| 团队记忆 | 跨用户、按仓库 | 远程服务器 + 本地镜像 | 文件监听器(2秒去抖) | `memory/team/` + Anthropic API | + +--- + +## 2. 持久记忆系统(核心) + +### 2.1 记忆文件格式 + +每条记忆是一个独立的 Markdown 文件,带 YAML frontmatter: + +```markdown +--- +name: 用户偏好-简洁回复 +description: 用户不希望在每次回复末尾加总结 +type: feedback +--- + +不要在回复末尾总结刚做了什么,用户能看到 diff。 + +**Why:** 用户明确要求过"stop summarizing what you just did" +**How to apply:** 所有回复结束时,直接结束,不加回顾性总结。 +``` + +### 2.2 四种记忆类型 + +```typescript +const MEMORY_TYPES = ['user', 'feedback', 'project', 'reference'] as const +``` + +| 类型 | 用途 | 保存时机 | 作用域(团队模式) | +|------|------|----------|:------------------:| +| **user** | 用户角色、目标、知识背景 | 了解到用户的身份信息时 | 始终私有 | +| **feedback** | 用户对工作方式的指导 | 用户纠正或确认做法时 | 默认私有 | +| **project** | 项目进展、目标、事件 | 了解到工作计划、截止日期时 | 偏向团队 | +| **reference** | 外部系统指针 | 了解到 Linear/Grafana/Slack 等资源时 | 通常团队 | + +**每种类型要求的结构:** +- feedback 和 project 类型必须包含 `**Why:**` 和 `**How to apply:**` 行 +- 相对日期必须转换为绝对日期(如"周四"→"2026-03-05") +- user 类型的示例:"深度 Go 经验,React 新手——用后端类比解释前端概念" + +### 2.3 明确不保存的内容 + +源码中硬编码了 6 类排除项(即使用户明确要求也不保存): + +1. 代码模式、架构、文件路径、项目结构——可从代码推导 +2. Git 历史、最近变更——`git log`/`git blame` 是权威来源 +3. 调试方案或修复步骤——修复在代码中,commit message 有上下文 +4. CLAUDE.md 中已有的内容——不重复 +5. 临时任务细节:进行中的工作、当前对话上下文 +6. 如果用户要求保存 PR 列表或活动摘要,要追问"什么是令人意外的或不明显的?"——只保存那部分 + +### 2.4 MEMORY.md 索引机制 + +``` +MEMORY.md 是索引,不是记忆本身 +``` + +**格式:** 纯 Markdown,无 frontmatter。每条一行,约 150 字符以内: +```markdown +- [用户偏好-简洁回复](feedback_concise.md) — 不在回复末尾加总结 +- [项目-合并冻结](project_freeze.md) — 3月5日起移动端发布冻结 +``` + +**限制:** +- 最大 200 行(`MAX_ENTRYPOINT_LINES`) +- 最大 25,000 字节(`MAX_ENTRYPOINT_BYTES`) +- 超出时在末尾追加截断警告 +- 截断按行边界执行(不会切断一行的中间) + +**两步保存流程:** +1. 写入记忆文件(如 `feedback_concise.md`) +2. 在 MEMORY.md 中添加索引行 + +可选的 `skipIndex` 模式(通过 `tengu_moth_copse` 特性开关)可跳过第2步。 + +### 2.5 存储目录结构 + +``` +~/.claude/ + projects/ + -Users-charlesqin-Desktop-myproject/ ← sanitizePath(项目根目录) + memory/ ← AUTO_MEM_DIRNAME + MEMORY.md ← 索引文件 + user_role.md ← 私有记忆文件 + feedback_testing.md + project_deadline.md + reference_linear.md + team/ ← 团队记忆子目录 + MEMORY.md ← 团队索引 + project_api_migration.md + reference_oncall_board.md + logs/ ← KAIROS 每日日志 + 2026/ + 03/ + 2026-03-31.md +``` + +### 2.6 路径安全机制 + +`paths.ts` 包含多层安全验证: + +| 安全层 | 检查内容 | 防御目标 | +|--------|----------|----------| +| 绝对路径检查 | 拒绝相对路径 | 路径遍历 | +| 根路径检查 | 拒绝长度 < 3 的路径 | 写入系统根目录 | +| UNC 路径检查 | 拒绝 `\\server\share` 和 `//server/share` | NTLM 凭证泄露 | +| Null 字节检查 | 拒绝包含 `\0` 的路径 | 路径截断攻击 | +| Tilde 展开限制 | 拒绝 `~`、`~/`、`~/.`、`~/..` | 匹配整个 HOME | +| **项目设置排除** | `.claude/settings.json` 不能设 `autoMemoryDirectory` | **恶意仓库重定向写入到 `~/.ssh`** | +| NFC 规范化 | Unicode NFC 标准化 | macOS 路径不一致 | +| Git 根规范化 | worktree 共享主仓库的记忆 | 避免重复记忆目录 | + +**关键安全设计:** `projectSettings`(提交到仓库的 `.claude/settings.json`)被有意排除在 `autoMemoryDirectory` 的信任来源之外。源码注释明确说明原因: + +```typescript +// SECURITY: projectSettings is intentionally excluded — +// a malicious repo could set autoMemoryDirectory: "~/.ssh" +// and gain write access to sensitive directories +``` + +--- + +## 3. 自动记忆提取(extractMemories) + +### 3.1 触发时机 + +``` +用户输入 → 模型响应 → 工具执行 → 循环... → 模型最终响应(无工具调用) + ↓ + Stop Hooks 执行 + ↓ + executeExtractMemories() ← 即发即忘 +``` + +**前置条件(全部满足才执行):** +1. `EXTRACT_MEMORIES` 编译时特性开关开启 +2. `isExtractModeActive()` = true(`tengu_passport_quail` GrowthBook 开关) +3. `isAutoMemoryEnabled()` = true +4. 非子代理(`!toolUseContext.agentId`) +5. 非 bare 模式 +6. 自上次提取以来有新的模型可见消息 + +### 3.2 互斥机制 + +```typescript +// 如果主代理在对话中已经直接写了记忆文件,跳过自动提取 +if (hasMemoryWritesSince(lastMemoryMessageUuid)) { + // 推进游标但不执行提取——避免覆盖用户显式保存的记忆 + lastMemoryMessageUuid = messages[messages.length - 1].uuid + return +} +``` + +这防止了自动提取和用户手动保存之间的冲突。 + +### 3.3 Fork Agent 执行 + +```typescript +const result = await runForkedAgent({ + messages: conversationMessages, + system: extractionPrompt, + maxTurns: 5, // 硬上限:5轮(读1轮 + 写1-4轮) + canUseTool: createAutoMemCanUseTool(autoMemPath), + // ... 共享主会话的 prompt cache +}) +``` + +**工具权限白名单:** + +| 工具 | 权限 | +|------|------| +| FileRead | 允许(无限制) | +| Grep | 允许(无限制) | +| Glob | 允许(无限制) | +| Bash | 仅只读命令(ls, find, grep, cat, stat, wc, head, tail) | +| FileEdit | 仅限 `autoMemPath` 内 | +| FileWrite | 仅限 `autoMemPath` 内 | +| MCP / Agent / 写入型 Bash | **全部禁止** | + +### 3.4 提取节流 + +```typescript +let turnsSinceLastExtraction = 0 +const turnsBeforeExtraction = getFeatureValue('tengu_bramble_lintel', 1) + +// 每 N 轮查询才执行一次提取(默认每轮都执行) +if (turnsSinceLastExtraction < turnsBeforeExtraction) { + turnsSinceLastExtraction++ + return +} +``` + +### 3.5 提取后通知 + +成功提取后,将写入的文件路径列表作为 `SystemMemorySavedMessage` 追加到主对话中,这样主代理知道记忆已更新。 + +--- + +## 4. AI 驱动的记忆召回(findRelevantMemories) + +### 4.1 召回流程 + +``` +用户发送消息 + ↓ +scanMemoryFiles(memoryDir) + ↓ 递归读取所有 .md 文件(排除 MEMORY.md) + ↓ 解析前 30 行 frontmatter(description + type) + ↓ 按 mtime 降序排序,上限 200 个文件 + ↓ +过滤掉 alreadySurfaced(之前轮次已展示的路径) + ↓ +selectRelevantMemories(query, memories, signal, recentTools) + ↓ 构建文本清单:每行 "- [type] filename (timestamp): description" + ↓ 附加 "Recently used tools: ..." 部分 + ↓ +sideQuery → Sonnet 模型 + ↓ 系统提示:SELECT_MEMORIES_SYSTEM_PROMPT + ↓ 用户消息:Query + Available memories + ↓ 结构化输出:{ selected_memories: string[] } + ↓ max_tokens: 256 + ↓ +返回最多 5 条 RelevantMemory(路径 + mtime) +``` + +### 4.2 选择提示词的关键规则 + +``` +- 返回最多 5 个文件名 +- 要有选择性和辨别力;如果不确定,不要包含 +- 如果提供了最近使用的工具列表: + ✗ 不要选择这些工具的使用参考/API 文档(已经在用了) + ✓ 仍然选择这些工具的警告/陷阱/已知问题 +``` + +### 4.3 记忆新鲜度标注 + +```typescript +function memoryAge(mtimeMs: number): string { + const days = memoryAgeDays(mtimeMs) + if (days === 0) return 'today' + if (days === 1) return 'yesterday' + return `${days} days ago` +} + +function memoryFreshnessText(mtimeMs: number): string { + if (memoryAgeDays(mtimeMs) <= 1) return '' // 新鲜记忆不加警告 + return `This memory is ${days} days old. +Memories are point-in-time observations, not live state — +claims about code behavior or file:line citations may be outdated. +Verify against current code before asserting as fact.` +} +``` + +**设计动机(源码注释):** 用户报告过过期的记忆被模型当作事实断言。"47 days ago" 比 ISO 时间戳更能触发模型的过期推理。 + +### 4.4 召回前的验证要求 + +源码中注入的系统提示明确要求模型在使用记忆前验证: + +``` +- 如果记忆提到一个文件路径 → 检查文件是否存在 +- 如果记忆提到一个函数或标志 → grep 搜索它 +- 如果用户即将根据你的建议行动 → 先验证 +- "记忆说 X 存在"不等于"X 现在存在" +- 活动日志、架构快照是冻结在某个时间点的,优先使用 git log +``` + +--- + +## 5. 会话记忆(Session Memory) + +### 5.1 与持久记忆的关键区别 + +| 维度 | 会话记忆 | 持久记忆 | +|------|----------|----------| +| 生命周期 | 当前会话 | 跨会话永久 | +| 文件数量 | 1 个 | 多个 | +| 触发频率 | 每次后采样(需满足阈值) | 每轮查询结束 | +| 主要用途 | **服务于上下文压缩** | 跨会话知识保留 | +| 记忆提取者 | Fork Agent(仅 FileEdit) | Fork Agent(Read/Write/Edit/Grep) | +| 格式 | 10个固定章节 | 自由格式 + frontmatter | + +### 5.2 触发阈值 + +```typescript +// 默认配置(可通过 GrowthBook tengu_sm_config 远程调整) +const defaults = { + minimumMessageTokensToInit: 10_000, // 上下文达到 10K tokens 才激活 + minimumTokensBetweenUpdate: 5_000, // 每增长 5K tokens 更新一次 + toolCallsBetweenUpdates: 3, // 且至少 3 次工具调用 +} + +// 触发条件: +// (token 阈值满足 AND 工具调用阈值满足) +// OR (token 阈值满足 AND 最后一轮助手消息无工具调用) +``` + +### 5.3 会话记忆模板(10 个固定章节) + +```markdown +# Session Memory + +## Session Title +_Brief description of what this session is about_ + +## Current State +_What is the current status of the work_ + +## Task specification +_Detailed description of the current task requirements_ + +## Files and Functions +_Key files and functions being worked on_ + +## Workflow +_Steps being followed or processes in use_ + +## Errors & Corrections +_Errors encountered and how they were resolved_ + +## Codebase and System Documentation +_Important codebase patterns, conventions, and system behavior_ + +## Learnings +_Insights gained during this session_ + +## Key results +_Important outputs, measurements, or achievements_ + +## Worklog +_Chronological log of actions taken_ +``` + +### 5.4 大小限制 + +```typescript +const MAX_SECTION_LENGTH = 2_000 // 每个章节最大 2K tokens +const MAX_TOTAL_SESSION_MEMORY_TOKENS = 12_000 // 整个文件最大 12K tokens +``` + +### 5.5 与压缩的集成 + +Session Memory 是上下文压缩的优先路径(参见 `sessionMemoryCompact.ts`): +- 自动压缩触发时,优先尝试用 Session Memory 作为摘要 +- 保留最近 10K-40K tokens 的消息 +- 比传统压缩快得多(不调用 LLM),且摘要质量更可预测 + +### 5.6 自定义 + +用户可以自定义模板和提示词: +- 模板:`~/.claude/session-memory/config/template.md` +- 提示词:`~/.claude/session-memory/config/prompt.md`(支持 `{{variableName}}` 替换) + +--- + +## 6. 团队记忆同步 + +### 6.1 前置条件 + +同时满足才启用: +1. `TEAMMEM` 编译时特性开关开启 +2. `isTeamMemoryEnabled()` = true(`tengu_herring_clock` GrowthBook 开关) +3. 第一方 OAuth + 同时拥有 `INFERENCE_SCOPE` 和 `PROFILE_SCOPE` +4. Git remote 是 github.com(非 GitHub 仓库不同步) + +### 6.2 API 端点 + +``` +基础 URL: {baseUrl}/api/claude_code/team_memory?repo={owner/repo} +``` + +| 方法 | 参数 | 用途 | 关键状态码 | +|------|------|------|:----------:| +| GET | `repo={slug}` | 拉取全部团队记忆 | 200 / 304 / 404 | +| GET | `repo={slug}&view=hashes` | 仅拉取哈希(轻量探测) | 200 / 404 | +| PUT | `repo={slug}` | 上传记忆条目(upsert) | 200 / 412 / 413 | + +**认证头:** `Authorization: Bearer {oauthToken}` + +### 6.3 同步协议 + +#### 拉取(Pull) + +``` +1. GET 请求,携带 ETag(条件请求) +2. 304 = 未变化,跳过 +3. 200 = 有更新: + a. 对每个条目验证路径(防遍历攻击) + b. 跳过 >250KB 的文件 + c. 跳过本地内容已匹配的文件 + d. 并行写入本地文件系统 + e. 刷新 serverChecksums +``` + +#### 推送(Push — Delta 上传 + 乐观锁) + +``` +1. readLocalTeamMemory(): 遍历 team/ 目录 + → 每个文件扫描秘密(30种规则) + → 跳过 >250KB 的文件 + → 计算 SHA-256 哈希 + +2. 计算 delta:仅本地哈希与 serverChecksums 不同的 key + +3. 分批上传:贪心装箱,每批 ≤ 200KB (MAX_PUT_BODY_BYTES) + +4. 每批携带 If-Match 头(乐观锁) + +5. 冲突处理: + 412 Conflict → 探测 ?view=hashes → 刷新 serverChecksums → 重算 delta → 重试(最多 2 次) + 413 Too Many → 学习 serverMaxEntries 上限,截断后重试 + +6. 冲突策略:本地优先(local-wins) + → 当前用户正在编辑的内容覆盖服务端 +``` + +### 6.4 文件监听器 + +```typescript +// watcher.ts +fs.watch(teamMemDir, { recursive: true }) +// macOS: FSEvents(O(1) 文件描述符) +// Linux: inotify(O(子目录数)) + +// 2 秒去抖:最后一次变更后 2 秒才触发推送 +``` + +**推送抑制:** 永久失败(no_oauth、no_repo、4xx 非 409/429)后,抑制后续推送,直到发生文件删除(恢复动作)或会话重启。 + +### 6.5 团队 vs 私有的作用域划分 + +源码注入的系统提示中,每种记忆类型都带有 XML 作用域标签: + +```xml + + user + always private + ... + + + + project + strongly bias toward team + ... + +``` + +额外安全要求: +``` +MUST avoid saving sensitive data within shared team memories +``` + +--- + +## 7. 秘密扫描保护 + +### 7.1 双层防护 + +| 层 | 时机 | 文件 | 行为 | +|----|------|------|------| +| 写入时拦截 | FileWrite/FileEdit 调用 | `teamMemSecretGuard.ts` | 阻止写入,返回错误 | +| 上传前扫描 | pushTeamMemory 读取本地文件 | `secretScanner.ts` | 跳过该文件,不上传 | + +### 7.2 检测的 30 种秘密模式 + +**云服务商:** +- AWS Access Token(A3T/AKIA/ASIA/ABIA/ACCA 前缀) +- GCP API Key(AIza 前缀) +- Azure AD Client Secret +- DigitalOcean PAT / Access Token + +**AI API:** +- Anthropic API Key(`sk-ant-api03-` 前缀,运行时拼接避免自身匹配) +- Anthropic Admin API Key(`sk-ant-admin01-`) +- OpenAI API Key(`sk-proj`/`svcacct`/`admin` + `T3BlbkFJ` 标记) +- HuggingFace Access Token(`hf_`) + +**版本控制:** +- GitHub PAT / Fine-grained PAT / App Token / OAuth / Refresh Token +- GitLab PAT / Deploy Token + +**通信平台:** +- Slack Bot/User/App Token(`xoxb-`/`xoxp-`/`xapp-`) +- Twilio API Key +- SendGrid API Token + +**开发工具:** +- npm Access Token +- PyPI Upload Token +- Databricks API Token +- HashiCorp Terraform API Token +- Pulumi API Token +- Postman API Token + +**可观测性:** +- Grafana API Key / Cloud API Token / Service Account Token +- Sentry User/Org Token + +**支付:** +- Stripe Access Token(`sk_test`/`live`/`prod` 或 `rk_`) +- Shopify Access Token / Shared Secret + +**密码学:** +- PEM 格式私钥(`BEGIN/END PRIVATE KEY` 块) + +### 7.3 设计原则 + +```typescript +// 匹配到的秘密值永远不被记录或返回——只返回规则 ID 和人类可读标签 +// Anthropic API key 前缀在运行时拼接,避免匹配自身的 excluded-strings 检查: +const prefix = ['sk', 'ant', 'api'].join('-') +``` + +--- + +## 8. KAIROS 模式的记忆(每日日志) + +当 KAIROS(AI 助手模式)激活时,记忆范式完全改变: + +``` +普通模式:写独立文件 + 更新 MEMORY.md 索引 +KAIROS 模式:追加到 logs/YYYY/MM/YYYY-MM-DD.md 日志文件 +``` + +- 只追加,不编辑已有内容 +- 每日一个文件,按日期路径组织 +- 由单独的 `/dream` 技能在夜间蒸馏日志为主题文件和 MEMORY.md +- 提示词中使用 `YYYY-MM-DD` 占位符而非实际日期(因为提示词被缓存,日期变化不会触发失效) + +--- + +## 9. 记忆系统注入的完整系统提示词 + +### 9.1 个人模式(auto memory) + +``` +# auto memory + +You have a persistent, file-based memory system at ``. +This directory already exists — write to it directly with the Write tool. + +You should build up this memory system over time so that future conversations +can have a complete picture of who the user is, how they'd like to collaborate +with you, what behaviors to avoid or repeat, and the context behind the work. + +[如果用户要求记住 → 立即保存] +[如果用户要求忘记 → 找到并删除] + +## Types of memory +[4种类型,每种含 description/when_to_save/how_to_use/examples] + +## What NOT to save in memory +[6类排除项] + +## How to save memories +Step 1: 写入文件(带 frontmatter) +Step 2: 在 MEMORY.md 中添加索引行 + +## When to access memories +- 看起来相关时 +- 用户明确要求时(必须访问) +- 用户说"忽略记忆"时,假装 MEMORY.md 为空 + +## Before recommending from memory +- 提到文件路径 → 检查文件是否存在 +- 提到函数/标志 → grep 搜索 +- 用户要行动 → 先验证 + +## Memory and other forms of persistence +- 实施方案 → 用 Plan,不用记忆 +- 当前对话步骤 → 用 Tasks,不用记忆 + +## Searching past context +[grep 记忆文件,或搜索会话 transcript] + +## MEMORY.md +[实际的 MEMORY.md 内容,或"当前为空"] +``` + +### 9.2 团队模式额外内容 + +在个人模式基础上增加: + +``` +## Memory scope +- 私有记忆: — 你的个人偏好、反馈 +- 团队记忆: — 项目上下文、外部引用 + +[类型定义中增加 XML 标签] + +额外安全规则:不得在团队记忆中保存敏感数据 +``` + +--- + +## 10. 团队记忆的路径安全(深度防御) + +`teamMemPaths.ts` 是整个记忆系统中安全措施最密集的文件: + +### 10.1 自定义错误类 + +```typescript +class PathTraversalError extends Error { + name = 'PathTraversalError' +} +``` + +### 10.2 sanitizePathKey — 服务端提供的相对路径清理 + +```typescript +function sanitizePathKey(key: string): string { + // 1. 拒绝 null 字节 + // 2. URL 编码遍历检测:解码 %2e%2e%2f 等 + // 3. Unicode 规范化攻击:全角字符 \uFF0E\uFF0E\uFF0F → ../ + // 4. 拒绝反斜杠(Windows 路径遍历) + // 5. 拒绝绝对路径 +} +``` + +### 10.3 realpathDeepestExisting — 符号链接解析 + +```typescript +async function realpathDeepestExisting(path: string): Promise { + // 逐级向上 realpath() 直到成功 + // 检测悬挂符号链接(link 存在但 target 不存在) + // → 安全威胁:writeFile 会跟随链接在 team 目录外创建文件 + // 检测符号链接循环(ELOOP)→ PathTraversalError + // 不可恢复的错误(EACCES, EIO)→ fail-closed +} +``` + +### 10.4 validateTeamMemWritePath — 双重验证 + +``` +写入团队记忆文件前的完整验证链: + +第1遍:字符串级 + 1. Null 字节检查 + 2. path.resolve() 解析 + 3. startsWith(teamDir) 验证 + +第2遍:符号链接级 + 4. realpathDeepestExisting() 解析实际路径 + 5. isRealPathWithinTeamDir() 验证实际路径在团队目录内 + +通过 → 返回解析后的路径 +失败 → 抛出 PathTraversalError +``` + +### 10.5 前缀攻击防护 + +```typescript +// 团队目录路径以分隔符结尾:/foo/team/ +// 这样 /foo/team-evil/ 不会通过 startsWith 检查 +``` + +--- + +## 11. 记忆的生命周期全景 + +``` +会话开始 + │ + ├─ loadMemoryPrompt() + │ → 读取 MEMORY.md → 注入系统提示 + │ + ├─ initSessionMemory() + │ → 注册后采样钩子 + │ + ├─ startTeamMemoryWatcher() + │ → 拉取服务端 → 启动文件监听 + │ + ▼ +每轮对话 + │ + ├─ findRelevantMemories(query) + │ → 扫描文件 → Sonnet 选择 → 返回最多 5 条 + │ → 新鲜度标注(>1天 → 加过期警告) + │ + ├─ [模型可能直接读/写记忆文件] + │ → 写入团队记忆 → checkTeamMemSecrets() 拦截秘密 + │ → 文件监听触发 → 2秒后推送到服务端 + │ + ├─ Session Memory 后采样钩子 + │ → 检查阈值 → Fork Agent 更新单文件 + │ + ├─ 模型最终响应(无工具调用) + │ → Stop Hooks → executeExtractMemories() + │ → 互斥检查 → Fork Agent 提取 → 更新持久记忆文件 + │ + ▼ +自动压缩触发时 + │ + ├─ trySessionMemoryCompaction()(优先路径) + │ → 用 Session Memory 内容作为摘要 + │ → 保留最近 10K-40K tokens 的消息 + │ + ├─ compactConversation()(回退路径) + │ → Fork Agent 生成 9 章节结构化摘要 + │ + ▼ +会话结束 + │ + ├─ drainPendingExtraction() + │ → 等待进行中的记忆提取完成 + │ + └─ 团队记忆推送最后一批变更 +``` + +--- + +## 12. 关键设计决策总结 + +| 决策 | 原因 | +|------|------| +| 记忆文件是独立的 .md 文件,不是数据库 | 用户可以直接编辑、Git 追踪、跨工具使用 | +| MEMORY.md 是索引不是记忆 | 防止单文件膨胀,支持截断不丢核心内容 | +| 自动提取在 Stop Hook 中即发即忘 | 不阻塞主对话循环 | +| 互斥检查(主代理 vs 自动提取) | 防止覆盖用户显式保存的内容 | +| 召回用 Sonnet 不用 Haiku | 需要理解语义相关性,Haiku 不够准确 | +| 新鲜度用"X days ago"不用 ISO 时间 | 模型对相对时间的推理比绝对时间好 | +| 项目设置排除 autoMemoryDirectory | 防止恶意仓库劫持写入路径 | +| 团队记忆双重路径验证 | 符号链接攻击需要在文件系统层面检查 | +| 秘密模式在运行时拼接 | 避免自身的 excluded-strings 检查 | +| 本地优先的冲突策略 | 用户正在编辑 = 最新意图 | +| 断路器(MAX_CONFLICT_RETRIES = 2) | 防止推送冲突的无限重试 | + +--- + +*来自:AI超元域 | B站频道:https://space.bilibili.com/3493277319825652* + +*基于 Claude Code 源码逆向分析,2026-03-31* diff --git a/docs/cc_analysis/analysis_20260401/Claude_Code隐藏高级功能全景图.md b/docs/cc_analysis/analysis_20260401/Claude_Code隐藏高级功能全景图.md new file mode 100644 index 0000000..d72556c --- /dev/null +++ b/docs/cc_analysis/analysis_20260401/Claude_Code隐藏高级功能全景图.md @@ -0,0 +1,805 @@ +# Claude Code 隐藏高级功能全景图 + +> 基于 Claude Code 源码(2026-03-31 快照,512K 行 TypeScript)逆向分析 +> 发现 55+ 个特性开关、20+ 个隐藏命令、以及多个未公开系统 + +--- + +## 目录 + +1. [你现在就能用的隐藏功能](#1-你现在就能用的隐藏功能) +2. [55+ 个编译时特性开关完整清单](#2-55-个编译时特性开关完整清单) +3. [KAIROS — AI 助手守护进程](#3-kairos--ai-助手守护进程) +4. [CHICAGO_MCP — Computer Use 电脑控制](#4-chicago_mcp--computer-use-电脑控制) +5. [投机执行系统(Speculation)](#5-投机执行系统speculation) +6. [Undercover 隐身模式](#6-undercover-隐身模式) +7. [Dream Mode 记忆梦境整合](#7-dream-mode-记忆梦境整合) +8. [Voice Mode 语音输入](#8-voice-mode-语音输入) +9. [Proactive 自主代理模式](#9-proactive-自主代理模式) +10. [伴侣精灵系统(Buddy)](#10-伴侣精灵系统buddy) +11. [多代理团队/Swarm 系统](#11-多代理团队swarm-系统) +12. [Anthropic 内部专用命令](#12-anthropic-内部专用命令) +13. [隐藏键盘快捷键](#13-隐藏键盘快捷键) +14. [隐藏环境变量](#14-隐藏环境变量) +15. [其他彩蛋与冷知识](#15-其他彩蛋与冷知识) + +--- + +## 1. 你现在就能用的隐藏功能 + +这些功能在当前公开版本中存在,但很少被提及: + +| 功能 | 触发方式 | 说明 | +|------|----------|------| +| 实体贴纸 | `/stickers` | 打开 Claude Code 实体贴纸购买页 | +| 堆转储 | `/heapdump` | 把 JS 堆转储到 `~/Desktop`(隐藏命令,不在 /help 中) | +| 裸模式 | `--bare` 或 `CLAUDE_CODE_SIMPLE=1` | 极简模式:只保留 Bash、Read、Edit 三个工具 | +| 自定义加载词 | `settings.json` → `spinnerVerbs` | 替换或追加 200+ 个花式加载词("Clauding"、"Flibbertigibbeting"…) | +| 输出风格切换 | `/config` → output style | **Explanatory**(教学模式,附 Insight 解说)或 **Learning**(动手练习,含 TODO(human) 标记) | +| Vim 模式 | `/vim` | 完整状态机:d/c/y 操作符、h/l/w/b/e/$ 移动、f/t 查找、文本对象、dot-repeat | +| 消息动作选择器 | `shift+up` | 进入消息导航:j/k 上下移动,c 复制消息,p 固定消息 | +| 年度回顾 | `/think-back` | "Your 2025 Claude Code Year in Review" 动画回顾 | +| 转储系统提示 | `--dump-system-prompt` | 打印完整系统提示词后退出(需特性开关) | +| 多代理团队 | `--agent-teams` 或 `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1` | 多 Claude 实例在 tmux/iTerm 分屏中并行工作 | +| 后台任务 | `ctrl+b` | 把当前运行中的任务转入后台 | +| 历史搜索 | `ctrl+r` | 反向搜索命令历史 | +| Transcript 模式 | `ctrl+o` | 查看完整对话记录,支持搜索 | +| Todo 面板 | `ctrl+t` | 任务追踪面板 | +| 速率限制选项 | `/rate-limit-options` | 隐藏命令,速率限制时显示选项 | +| 使用洞察 | `/insights` | 分析你的 Claude Code 使用历史(113KB 延迟加载模块) | +| 全局文件搜索 | `ctrl+shift+f` | 跨文件搜索(需 QUICK_SEARCH 开关) | +| 快速打开 | `ctrl+shift+p` | 快速打开文件 | + +--- + +## 2. 55+ 个编译时特性开关完整清单 + +所有开关通过 `feature('FLAG')` 在 Bun 编译时评估。外部构建中为 `false` 的分支会被彻底删除(死代码消除)。 + +### 核心功能开关 + +| 开关 | 功能 | 状态 | +|------|------|:----:| +| `VOICE_MODE` | 语音输入(push-to-talk) | 内部 | +| `KAIROS` | AI 助手守护进程模式 | 内部 | +| `KAIROS_DREAM` | 梦境记忆整合 | 内部 | +| `KAIROS_BRIEF` | Brief 摘要工具 | 内部 | +| `KAIROS_CHANNELS` | 频道通知系统 | 内部 | +| `KAIROS_PUSH_NOTIFICATION` | 推送通知工具 | 内部 | +| `KAIROS_GITHUB_WEBHOOKS` | GitHub PR 订阅 | 内部 | +| `CHICAGO_MCP` | Computer Use 电脑控制 | 付费用户 | +| `PROACTIVE` | 自主代理模式(Sleep + 自动唤醒) | 内部 | +| `BRIDGE_MODE` | Remote Control 桥接 | 公开 | +| `DAEMON` | 后台守护进程 | 内部 | + +### 上下文管理开关 + +| 开关 | 功能 | +|------|------| +| `CONTEXT_COLLAPSE` | 智能上下文折叠(替代暴力压缩) | +| `REACTIVE_COMPACT` | 响应式压缩(API 报 prompt-too-long 时触发) | +| `CACHED_MICROCOMPACT` | 缓存感知微压缩(不破坏 prompt cache) | +| `HISTORY_SNIP` | 历史裁剪(SnipTool + /force-snip) | +| `TOKEN_BUDGET` | Token 预算追踪和递减收益检测 | +| `EXTRACT_MEMORIES` | 自动记忆提取 | +| `TEAMMEM` | 团队记忆同步 | + +### 工具和代理开关 + +| 开关 | 功能 | +|------|------| +| `COORDINATOR_MODE` | 多代理协调者模式 | +| `AGENT_TRIGGERS` | 定时触发器(Cron) | +| `AGENT_TRIGGERS_REMOTE` | 远程触发器 | +| `MONITOR_TOOL` | 监控工具和任务 | +| `WEB_BROWSER_TOOL` | 网页浏览器自动化工具 | +| `TERMINAL_PANEL` | 终端面板(meta+j) | +| `WORKFLOW_SCRIPTS` | 工作流脚本系统 | +| `BG_SESSIONS` | 后台会话(claude ps/logs/attach/kill) | +| `OVERFLOW_TEST_TOOL` | 上下文溢出测试工具 | +| `BUDDY` | 伴侣精灵系统 | + +### UI 和体验开关 + +| 开关 | 功能 | +|------|------| +| `MESSAGE_ACTIONS` | 消息动作选择器(shift+up) | +| `QUICK_SEARCH` | 全局搜索和快速打开 | +| `AUTO_THEME` | 自动主题检测 | +| `STREAMLINED_OUTPUT` | 流式 JSON 模式优化输出 | +| `CONNECTOR_TEXT` | 连接器文本摘要 beta | + +### 安全和调试开关 + +| 开关 | 功能 | +|------|------| +| `BASH_CLASSIFIER` | Bash 命令 ML 安全分类器 | +| `TRANSCRIPT_CLASSIFIER` | 基于 transcript 的权限模式分类 | +| `NATIVE_CLIENT_ATTESTATION` | 客户端证明头(cch=) | +| `HARD_FAIL` | 硬失败模式(任何错误都崩溃) | +| `DUMP_SYSTEM_PROMPT` | --dump-system-prompt 隐藏 CLI 标志 | +| `ABLATION_BASELINE` | A/B 测试消融基线 | +| `PROMPT_CACHE_BREAK_DETECTION` | Prompt 缓存失效检测 | + +### 基础设施开关 + +| 开关 | 功能 | +|------|------| +| `LODESTONE` | `cc://` 深度链接协议注册 | +| `DIRECT_CONNECT` | 通过 cc:// 深度链接直连 | +| `SSH_REMOTE` | SSH 远程会话 | +| `BYOC_ENVIRONMENT_RUNNER` | 自带计算环境运行器 | +| `SELF_HOSTED_RUNNER` | 自托管运行器 | +| `CCR_REMOTE_SETUP` | /remote-setup 命令 | +| `FORK_SUBAGENT` | /fork 子代理分叉命令 | +| `MCP_SKILLS` | MCP 提供的技能 | +| `EXPERIMENTAL_SKILL_SEARCH` | 实验性技能模糊搜索 | +| `REVIEW_ARTIFACT` | 审查工件工具 | +| `BUILDING_CLAUDE_APPS` | /claude-api 技能 | +| `RUN_SKILL_GENERATOR` | 技能生成器 | +| `ULTRAPLAN` | 超级计划模式 | +| `TORCH` | /torch 命令 | +| `TEMPLATES` | 任务分类器/模板系统 | +| `VERIFICATION_AGENT` | 验证代理 | +| `COMMIT_ATTRIBUTION` | 提交归属(Co-Authored-By) | +| `AWAY_SUMMARY` | 离开摘要生成 | +| `FILE_PERSISTENCE` | 跨轮次文件持久化追踪 | +| `MEMORY_SHAPE_TELEMETRY` | 记忆文件形状遥测 | +| `COWORKER_TYPE_TELEMETRY` | Coworker 类型遥测 | +| `DOWNLOAD_USER_SETTINGS` | 设置下载同步 | +| `UPLOAD_USER_SETTINGS` | 设置上传同步 | +| `BREAK_CACHE_COMMAND` | /break-cache 命令 | + +--- + +## 3. KAIROS — AI 助手守护进程 + +**代号含义:** KAIROS(希腊语,"决定性时刻") + +### 架构 + +``` +claude assistant ← 启动入口 + ↓ +永驻守护进程 + ├── 每日日志: logs/YYYY/MM/YYYY-MM-DD.md(仅追加) + ├── SleepTool: 休眠 → 定时自主唤醒 + ├── PushNotificationTool: 向用户推送通知 + ├── SendUserFileTool: 发送文件给用户 + ├── SubscribePRTool: 订阅 GitHub PR webhook + ├── BriefTool: 简短状态报告(ctrl+shift+b) + ├── 频道通知: 通过 MCP 接收外部消息 + └── /dream: 夜间记忆蒸馏 +``` + +### 关键特性 + +- **记忆范式不同**:不写独立记忆文件,而是追加到每日日志 +- **夜间梦境**:`/dream` 技能自动将日志蒸馏为主题文件 +- **自主循环**:SleepTool + 自动唤醒 = 完全自主的工作循环 +- **压缩后行为**:提示词包含 "你正在自主模式中运行,这不是首次唤醒——继续工作循环" +- **涉及 6 个子特性开关**:KAIROS + KAIROS_DREAM + KAIROS_BRIEF + KAIROS_CHANNELS + KAIROS_PUSH_NOTIFICATION + KAIROS_GITHUB_WEBHOOKS + +--- + +## 4. CHICAGO_MCP — Computer Use 电脑控制 + +**代号:** Chicago(子门控以芝加哥地标命名:`tengu_malort_pedway`) + +### 功能 + +- 创建**进程内 MCP 服务器**控制屏幕 +- macOS 上使用**原生 Swift 执行器** +- 支持 `pixels` 和 `normalized` 两种坐标模式 +- 自动列出已安装应用供模型参考 +- **ESC 热键**紧急中断电脑控制 +- 可通过 `--computer-use-mcp` 独立启动 MCP 服务器 + +### 子功能门控 + +| 子门控 | 功能 | +|--------|------| +| `pixelValidation` | 像素坐标验证 | +| `clipboardPasteMultiline` | 多行剪贴板粘贴 | +| `mouseAnimation` | 鼠标移动动画 | +| `hideBeforeAction` | 操作前隐藏 UI | +| `autoTargetDisplay` | 自动目标显示器选择 | +| `clipboardGuard` | 剪贴板保护 | + +### 访问控制 + +- 需要 **Max 或 Pro 订阅**(外部用户) +- Anthropic 员工可直接使用(绕过订阅检查) +- 有专用权限 UI(`ComputerUseApproval.tsx`) + +--- + +## 5. 投机执行系统(Speculation) + +**仅 Anthropic 内部**(`USER_TYPE === 'ant'`) + +### 工作原理 + +``` +1. 系统生成"下一步建议"提示词 + ↓ +2. 用户看到建议的同时,后台 Fork Agent 已开始执行 + ↓ +3. 文件写入到 overlay 目录(copy-on-write 隔离) + ~/.claude/tmp/speculation/// + ↓ +4. 用户接受建议 → 投机结果注入对话 + overlay 文件复制到真实文件系统 + 用户拒绝/修改 → 丢弃投机结果 +``` + +### 安全约束 + +| 工具类型 | 投机执行中的权限 | +|----------|:----------------:| +| 只读工具(Read, Glob, Grep) | 允许 | +| 写入工具(Edit, Write) | 仅在权限模式允许自动接受时 | +| Bash 命令 | 必须通过只读验证 | +| 未知工具 | 遇到即暂停(boundary) | + +### 限制 + +- 最多 **20 轮**、**100 条消息** +- 可以**流水线化**:当前投机完成后预生成下一步建议 + +### 用户反馈(内部可见) + +``` +Speculated 5 tool uses — +12s saved (47s this session) +``` + +--- + +## 6. Undercover 隐身模式 + +**仅 Anthropic 员工**,自动激活条件:在公开/开源仓库工作。 + +### 激活后的行为 + +| 行为 | 说明 | +|------|------| +| 剥除归属 | 所有 `Co-Authored-By` 行被移除 | +| 禁止提及内部代号 | Capybara、Tengu 等动物代号 | +| 禁止提及版本号 | 未发布的模型版本 | +| 禁止提及内部仓库 | Anthropic 内部仓库名 | +| 禁止提及 Slack 频道 | 内部 Slack 频道名 | +| 禁止提及 "Claude Code" | commit 消息必须看起来像人类写的 | + +### 控制 + +- 自动检测:仓库 remote 不在内部白名单 → 自动开启 +- 强制开启:`CLAUDE_CODE_UNDERCOVER=1` +- 无强制关闭选项 +- 首次激活有说明对话框 + +--- + +## 7. Dream Mode 记忆梦境整合 + +### 自动触发条件 + +同时满足: +- 距上次整合 ≥ **24 小时**(`minHours`,可配置) +- 且触及 ≥ **5 个会话**(`minSessions`,可配置) +- 无其他进程正在整合(锁机制) + +### 四阶段执行 + +``` +阶段 1 — 定向 + 读取记忆目录和 MEMORY.md 索引 + +阶段 2 — 收集 + 从每日日志和会话 transcript 中提取新信号 + +阶段 3 — 整合 + 将新信息合并到已有主题文件 + 转换相对日期为绝对日期 + 删除被新事实矛盾的旧事实 + +阶段 4 — 修剪 + 保持索引在 25KB 以内 + 清理过时条目 +``` + +### 工具限制 + +Dream 运行期间,Bash 被限制为只读命令。 + +### 手动触发 + +`/dream` 技能在前台执行同样的整合流程(需 KAIROS 或 KAIROS_DREAM 开关)。 + +--- + +## 8. Voice Mode 语音输入 + +### 激活方式 + +**按住空格键说话**(push-to-talk),通过 `/voice` 命令开启。 + +### 技术栈 + +``` +原生音频捕获 + macOS → CoreAudio (audio-capture-napi / cpal) + Linux → ALSA + 回退 → SoX rec / arecord + +录音参数 + 采样率: 16kHz + 声道: 单声道 + 静音检测: 2.0秒,3% 阈值(SoX 回退路径) + +STT 流式传输 + → claude.ai voice_stream 端点 + → 需要 Anthropic OAuth(API key/Bedrock/Vertex 不支持) +``` + +### UI 元素 + +- 音频电平指示器 +- 语音状态显示 +- 语言选择器(`LanguagePicker.tsx`) +- 自定义关键词词汇表(`voiceKeyterms.ts`) + +### 控制 + +- GrowthBook 杀开关:`tengu_amber_quartz_disabled` +- 默认 fail-open(新安装时启用) + +--- + +## 9. Proactive 自主代理模式 + +### 核心工具:SleepTool + +```typescript +// 代理可以休眠指定时间后自主唤醒 +SleepTool.call({ duration: '5m' }) +// 5 分钟后自动恢复执行 +``` + +### 与 KAIROS 结合 + +``` +claude assistant + ↓ +[工作] → [SleepTool 5m] → [自动唤醒] → [继续工作] → [SleepTool 10m] → ... + ↑ | + └──────────────────────────── 无限循环 ────────────────────────────────┘ +``` + +### 压缩后的特殊行为 + +普通模式压缩后:提示用户是否有问题 + +自主模式压缩后: +``` +你正在自主/主动模式中运行。这不是首次唤醒——你在压缩前已经在自主工作。 +继续你的工作循环:根据上面的摘要从离开的地方接续。不要问候用户或询问要做什么。 +``` + +--- + +## 10. 伴侣精灵系统(Buddy) + +`feature('BUDDY')` — 一个完整的收藏型宠物系统。 + +### 物种(18种) + +duck、goose、blob、cat、dragon、octopus、owl、penguin、turtle、snail、ghost、axolotl、capybara、cactus、robot、rabbit、mushroom、chonk + +### 外观组合 + +``` +18 种物种 × 6 种眼睛 × 8 种帽子 = 864 种外观组合 +× 5 种稀有度 = 4,320 种可能的精灵 +``` + +### 稀有度 + +| 等级 | 概率 | 显示 | 属性底值 | +|------|:----:|------|:--------:| +| Common | 60% | 1星 | 低 | +| Uncommon | 25% | 2星 | 略高 | +| Rare | 10% | 3星 | 中等 | +| Epic | 4% | 4星 | 高 | +| Legendary | 1% | 5星 | 最高 | + +### 5种属性 + +- **DEBUGGING** — 调试力 +- **PATIENCE** — 耐心 +- **CHAOS** — 混沌 +- **WISDOM** — 智慧 +- **SNARK** — 吐槽力 + +每只精灵有一个巅峰属性和一个低谷属性。 + +### 确定性生成 + +```typescript +// 种子 = hash(userId + 'friend-2026-401') +// PRNG = Mulberry32 +// 无论何时重新生成,同一用户始终得到同一只精灵 +// 编辑配置文件无法伪造稀有度——bones 从 hash 重新计算 +``` + +### ASCII 动画 + +``` +每种物种 3 帧动画,5行×12字符 +500ms tick 的闲置动画 +包含眨眼帧和特殊效果(烟雾、天线发光、墨水泡泡等) +``` + +### 互动 + +``` +/buddy pet → 触发 2.5 秒心形粒子特效(❤ 字符向上飘浮) +模型可以让精灵"说话"(通过 speech bubble 系统) +用户对精灵说话时,模型会自动退让 +``` + +### 反作弊 + +``` +物种名中某些与模型代号冲突的名称用 String.fromCharCode 十六进制编码 +配置文件编辑不影响生成结果(bones 从 hash 重算) +``` + +### 启动引导 + +首次未孵化时显示彩虹色 `/buddy` 提示文本。 + +--- + +## 11. 多代理团队/Swarm 系统 + +### 激活方式 + +```bash +# 外部用户 +claude --agent-teams +# 或 +export CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1 +``` + +### 三种后端 + +| 后端 | 实现 | 特点 | +|------|------|------| +| **TmuxBackend** | 隔离 tmux socket `claude-swarm-` | 彩色边框,不影响用户的 tmux | +| **ITermBackend** | iTerm2 的 `it2` CLI + Python API | 原生分屏 | +| **InProcessBackend** | 进程内运行 | 无终端 UI | + +自动检测选择最佳可用后端。 + +### Tmux 隔离 + +```typescript +// Claude 创建自己的 tmux socket,防止命令影响用户会话 +const SWARM_SESSION_NAME = 'claude-swarm' +const HIDDEN_SESSION_NAME = 'claude-hidden' +// socket 名: claude- +``` + +### Coordinator 模式 + +```bash +export CLAUDE_CODE_COORDINATOR_MODE=1 +``` + +Claude 变为协调者,通过 `AgentTool`、`SendMessageTool`、`TaskStopTool` 编排 worker 代理。Worker 拥有独立工具访问权限。 + +### 团队工具 + +| 工具 | 功能 | +|------|------| +| `TeamCreateTool` | 创建代理团队 | +| `TeamDeleteTool` | 删除代理团队 | +| `SendMessageTool` | 代理间消息传递 | +| `TaskStopTool` | 停止子任务 | +| `TungstenTool`(内部) | tmux 会话管理 | + +### 共享便签本 + +当 `tengu_scratch` 门控开启时,worker 获得共享 scratchpad 目录,跨 worker 知识交换绕过权限提示。 + +--- + +## 12. Anthropic 内部专用命令(20+个) + +全部在外部构建中被替换为 `{ isEnabled: false, isHidden: true }` 存根。 + +### 调试和诊断 + +| 命令 | 用途 | +|------|------| +| `/bridge-kick` | 注入 bridge 故障(close/poll/register/heartbeat 等 10+ 子命令) | +| `/mock-limits` | 模拟各种速率限制场景 | +| `/reset-limits` | 重置速率限制状态 | +| `/ctx-viz` | 上下文窗口可视化 | +| `/debug-tool-call` | 工具调用调试 | +| `/ant-trace` | Anthropic 内部追踪 | +| `/perf-issue` | 性能问题报告 | +| `/env` | 显示环境变量 | +| `/stuck` | 诊断冻结/慢会话(扫描 CPU、进程状态、内存泄漏) | +| `/force-snip` | 强制历史裁剪 | +| `/break-cache` | 使 prompt cache 失效 | + +### 工作流和自动化 + +| 命令 | 用途 | +|------|------| +| `/autofix-pr` | 自动修复 PR 问题 | +| `/bughunter` | 远程审查的 bug 猎人模式 | +| `/teleport` | 远程传送到其他环境 | +| `/backfill-sessions` | 回填会话数据 | +| `/agents-platform` | 代理平台管理 | +| `/ultraplan` | 超级计划模式 | +| `/share` | 分享对话 | + +### 开发辅助 + +| 命令 | 用途 | +|------|------| +| `/lorem-ipsum` | 精确 token 计数的填充文本生成(用 1-token 词汇实现精确计数) | +| `/oauth-refresh` | OAuth token 手动刷新 | +| `/good-claude` | (已存根化) | +| `/onboarding` | 引导流程 | +| `/init-verifiers` | 初始化验证器 | + +### 内部专用工具 + +| 工具 | 用途 | +|------|------| +| `ConfigTool` | 直接修改配置 | +| `TungstenTool` | tmux 会话管理 | +| `REPLTool` | 沙盒 REPL VM | +| `SuggestBackgroundPRTool` | 建议后台 PR | +| `CtxInspectTool` | 上下文检查 | +| `OverflowTestTool` | 溢出测试 | +| `SnipTool` | 历史裁剪 | +| `MonitorTool` | 监控 | + +### Bridge Kick 详细子命令 + +``` +/bridge-kick close — 触发 WebSocket close +/bridge-kick poll 404 [type] — 下次轮询 404 +/bridge-kick poll transient — 下次轮询 5xx +/bridge-kick register fail [N] — 下 N 次注册失败 +/bridge-kick register fatal — 注册 403(终端失败) +/bridge-kick reconnect-session fail — 重连失败 +/bridge-kick heartbeat — 心跳致命错误 +/bridge-kick reconnect — 直接触发重连 +/bridge-kick status — 打印 bridge 状态 +``` + +支持**组合故障序列**,复现真实生产故障链。 + +--- + +## 13. 隐藏键盘快捷键 + +### 全局快捷键 + +| 快捷键 | 动作 | +|--------|------| +| `ctrl+t` | 切换 Todo 面板 | +| `ctrl+o` | 切换 Transcript 查看器 | +| `ctrl+r` | 历史反向搜索 | +| `ctrl+l` | 重绘屏幕 | +| `ctrl+b` | 后台运行当前任务 | +| `shift+tab` | 循环切换权限模式 | +| `meta+p` | 模型选择器 | +| `meta+o` | Fast Mode 切换 | +| `meta+t` | Thinking 模式切换 | + +### Chord 快捷键(两步组合) + +| 快捷键 | 动作 | +|--------|------| +| `ctrl+x ctrl+k` | 终止所有代理 | +| `ctrl+x ctrl+e` 或 `ctrl+g` | 打开外部编辑器 | + +### 编辑快捷键 + +| 快捷键 | 动作 | +|--------|------| +| `ctrl+_` / `ctrl+shift+-` | 撤销文本输入 | +| `ctrl+s` | 暂存当前输入 | +| `ctrl+v` / `alt+v`(Win) | 粘贴图片 | + +### 特性门控快捷键 + +| 快捷键 | 动作 | 需要 | +|--------|------|------| +| `shift+up` | 消息动作选择器 | `MESSAGE_ACTIONS` | +| `ctrl+shift+f` / `cmd+shift+f` | 全局文件搜索 | `QUICK_SEARCH` | +| `ctrl+shift+p` / `cmd+shift+p` | 快速打开 | `QUICK_SEARCH` | +| `meta+j` | 终端面板 | `TERMINAL_PANEL` | +| `ctrl+shift+b` | Brief 模式切换 | `KAIROS` | +| `ctrl+shift+o` | 队友预览切换 | 团队模式 | +| `space`(按住) | Push-to-talk 语音 | `VOICE_MODE` | + +### 滚动快捷键 + +| 快捷键 | 动作 | +|--------|------| +| `pageup` / `pagedown` | 翻页滚动 | +| `wheelup` / `wheeldown` | 鼠标滚轮滚动 | +| `ctrl+shift+c` / `cmd+c` | 复制选中内容(滚动模式中) | + +### 保留快捷键(不可重绑定) + +- `ctrl+c` — 中断 +- `ctrl+d` — 退出 + +--- + +## 14. 隐藏环境变量 + +### 核心配置 + +| 变量 | 效果 | +|------|------| +| `CLAUDE_CODE_SIMPLE=1` | 极简模式(3个工具) | +| `CLAUDE_CODE_DISABLE_AUTO_MEMORY=1` | 禁用自动记忆提取 | +| `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1` | 禁用所有非必要网络流量 | +| `CLAUDE_CODE_DISABLE_THINKING=1` | 禁用 thinking 模式 | +| `CLAUDE_CODE_DISABLE_FAST_MODE=1` | 禁用 fast mode | +| `CLAUDE_CODE_DISABLE_CLAUDE_MDS=1` | 禁用 CLAUDE.md 加载 | +| `CLAUDE_CODE_DISABLE_COMMAND_INJECTION_CHECK=1` | 禁用命令注入检查 | +| `CLAUDE_CODE_DISABLE_BACKGROUND_TASKS=1` | 禁用后台任务 | + +### 性能调优 + +| 变量 | 效果 | +|------|------| +| `CLAUDE_CODE_MAX_TOOL_USE_CONCURRENCY=N` | 最大并发工具数(默认10) | +| `CLAUDE_CODE_AUTO_COMPACT_WINDOW=N` | 覆盖自动压缩窗口 | +| `CLAUDE_CODE_MAX_OUTPUT_TOKENS=N` | 覆盖最大输出 token | +| `CLAUDE_CODE_MAX_RETRIES=N` | 覆盖最大重试次数 | +| `CLAUDE_CODE_BLOCKING_LIMIT_OVERRIDE=N` | 覆盖阻塞限制 | +| `CLAUDE_AUTOCOMPACT_PCT_OVERRIDE=80` | 在 80% 上下文时触发压缩 | + +### API 控制 + +| 变量 | 效果 | +|------|------| +| `ANTHROPIC_MODEL=model-id` | 覆盖默认模型 | +| `ANTHROPIC_SMALL_FAST_MODEL=model-id` | 覆盖后台辅助任务模型 | +| `ANTHROPIC_BASE_URL=url` | 自定义 API 基础 URL | +| `ANTHROPIC_CUSTOM_HEADERS='{"k":"v"}'` | 注入自定义 HTTP 头 | +| `CLAUDE_CODE_EXTRA_BODY='{"k":"v"}'` | 注入额外 API 请求体 | +| `CLAUDE_CODE_EXTRA_METADATA='{"k":"v"}'` | 注入额外元数据 | +| `CLAUDE_CODE_USE_BEDROCK=1` | 使用 AWS Bedrock | +| `CLAUDE_CODE_USE_VERTEX=1` | 使用 Google Vertex | +| `CLAUDE_CODE_USE_FOUNDRY=1` | 使用 Azure Foundry | +| `CLAUDE_CODE_UNATTENDED_RETRY=1` | 无人值守无限重试 | + +### 调试 + +| 变量 | 效果 | +|------|------| +| `CLAUDE_CODE_DEBUG=1` | 启用调试日志 | +| `CLAUDE_CODE_FRAME_TIMING_LOG=/path` | 记录帧渲染时间 | +| `CLAUDE_CODE_BASH_SANDBOX_SHOW_INDICATOR=1` | 显示沙盒指示器 | +| `CLAUDE_CODE_SYNTAX_HIGHLIGHT=1` | 启用语法高亮 | +| `OTEL_LOG_TOOL_DETAILS=1` | 遥测中记录工具详情 | +| `OTEL_LOG_USER_PROMPTS=1` | 遥测中记录用户提示词(默认 REDACTED) | +| `DISABLE_COMPACT=1` | 完全禁用压缩 | +| `DISABLE_AUTO_COMPACT=1` | 仅禁用自动压缩 | + +### 代理团队 + +| 变量 | 效果 | +|------|------| +| `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1` | 启用多代理团队 | +| `CLAUDE_CODE_COORDINATOR_MODE=1` | 协调者模式 | +| `CLAUDE_CODE_TEAMMATE_COMMAND=cmd` | 队友启动命令 | +| `CLAUDE_CODE_AGENT_COLOR=color` | 代理颜色 | +| `CLAUDE_CODE_PLAN_MODE_REQUIRED=1` | 强制 plan 模式 | + +--- + +## 15. 其他彩蛋与冷知识 + +### 200+ 花式加载词 + +`spinnerVerbs.ts` 中的精选: + +``` +Clauding, Boondoggling, Canoodling, Flibbertigibbeting, +Hullaballooing, Moonwalking, Prestidigitating, Razzmatazzing, +Shenaniganing, Tomfoolering, Whatchamacalliting, Beboppin' +``` + +完成时的过去式: +``` +Baked for 5s, Cogitated for 12s, Noodled for 8s +``` + +### VCR 录制/回放模式 + +`FORCE_VCR=1`(内部):录制和回放 API 交互,用于测试。 + +### Lorem Ipsum 精确 token 生成 + +`/lorem-ipsum 50000`(内部):使用经过验证的 1-token 单词生成精确 token 数的填充文本。 + +### Fennec 代号 + +内部模型迁移中有 `migrateFennecToOpus.ts`:将 `fennec-latest` 映射到 `opus`,`fennec-fast-latest` 映射到 `opus[1m]` + fast mode。 + +### 消融基线模式 + +`CLAUDE_CODE_ABLATION_BASELINE`:降级功能用于 A/B 测试,验证各功能的实际贡献。 + +### 深度链接协议 + +`feature('LODESTONE')`:注册 `cc://` URL 协议处理器,其他应用可直接打开 Claude Code 会话。 + +### 物种名编码 + +```typescript +// 伴侣精灵的某个物种名与内部模型代号冲突 +// 用 String.fromCharCode 十六进制编码避免 excluded-strings 检查 +``` + +### 权限模式循环 + +`shift+tab` 在以下模式间循环: +``` +default → acceptEdits → plan → auto → default → ... +``` + +### Anthropic API Key 自检回避 + +```typescript +// 秘密扫描器中 Anthropic API key 前缀在运行时拼接 +// 避免匹配自身源码的 excluded-strings 检查 +const prefix = ['sk', 'ant', 'api'].join('-') +``` + +--- + +## 总结:冰山模型 + +``` + ╭───────────────╮ + 公开版本 │ CLI + 43工具 │ ← 你看到的 + │ /命令 + UI │ + ╰───────┬───────╯ + │ + ╭───────────────┼───────────────╮ + 编译时剥离 │ KAIROS守护进程 │ Computer Use │ ← 源码中存在 + │ Voice Mode │ Speculation │ 但外部构建被删除 + │ Dream Mode │ Undercover │ + │ Buddy Pet │ Swarm/Teams │ + │ Workflows │ BG Sessions │ + │ Deep Links │ Cron Triggers │ + ╰───────────────┴───────────────╯ + │ + ╭───────────────┼───────────────╮ + 内部专用 │ 20+ 调试命令 │ bridge-kick │ ← 仅 Anthropic 员工 + │ mock-limits │ VCR 录放 │ + │ lorem-ipsum │ 消融基线 │ + │ stuck 诊断 │ ctx-viz │ + ╰───────────────┴───────────────╯ +``` + +**Claude Code 不只是一个 CLI 工具——它的源码中隐藏着一个完整的 AI 助手操作系统的雏形。** 守护进程、语音输入、电脑控制、投机执行、梦境记忆整合、多代理协作、隐身模式——当前公开版本只暴露了冰山一角。 + +--- + +*来自:AI超元域 | B站频道:https://space.bilibili.com/3493277319825652* + +*基于 Claude Code 源码逆向分析,2026-03-31* diff --git a/docs/cc_analysis/analysis_20260401/README.md b/docs/cc_analysis/analysis_20260401/README.md new file mode 100644 index 0000000..8aff408 --- /dev/null +++ b/docs/cc_analysis/analysis_20260401/README.md @@ -0,0 +1 @@ +Image diff --git a/docs/cc_analysis/analysis_20260401/claude-code-multi-agent-architecture.md b/docs/cc_analysis/analysis_20260401/claude-code-multi-agent-architecture.md new file mode 100644 index 0000000..2fd86c6 --- /dev/null +++ b/docs/cc_analysis/analysis_20260401/claude-code-multi-agent-architecture.md @@ -0,0 +1,682 @@ +# Claude Code 多 Agent 架构深度分析 + +> 基于源码约 **13,700 行、37+ 文件**的深度探索,涵盖 `src/utils/swarm/`、`src/tools/AgentTool/`、`src/coordinator/` 等核心模块。 + +--- + +## 一、整体架构 + +系统采用 **Leader-Worker 模型**,核心有三种执行后端和两种编排模式: + +``` + ┌─────────────────────────────┐ + │ 用户请求 (REPL/CLI) │ + └──────────┬──────────────────┘ + │ + ┌──────────▼──────────────────┐ + │ Leader Agent (team-lead) │ + │ QueryEngine → query() │ + └──┬────────┬────────┬────────┘ + │ │ │ + ┌────────────▼┐ ┌────▼─────┐ ┌▼────────────┐ + │ InProcess │ │ Tmux │ │ iTerm2 │ + │ (同进程) │ │ (分屏) │ │ (分屏) │ + └──────────────┘ └──────────┘ └─────────────┘ + │ │ │ + AsyncLocalStorage 独立进程 独立进程 + 隔离上下文 文件邮箱通信 文件邮箱通信 +``` + +**两种编排模式:** + +- **Team Mode** — 通过 `TeamCreate` 创建团队,手动管理 Worker +- **Coordinator Mode** — 自动化编排,Coordinator 只能用 Agent/TaskStop/SendMessage 三个工具 + +--- + +## 二、Agent Tool — 统一入口 + +`src/tools/AgentTool/AgentTool.tsx` 是所有子 Agent 的统一入口。 + +### 2.1 输入参数 + +| 参数 | 类型 | 作用 | +|------|------|------| +| `prompt` | string | 任务描述 | +| `subagent_type` | string | Agent 类型 (Explore, Plan, 自定义等) | +| `model` | `'sonnet'\|'opus'\|'haiku'` | 模型覆盖 | +| `run_in_background` | boolean | 后台异步执行 | +| `name` | string | 命名,使其可通过 SendMessage 寻址 | +| `team_name` | string | 团队名 | +| `mode` | string | 权限模式 (plan, acceptEdits 等) | +| `isolation` | `'worktree'` | Git worktree 隔离 | + +### 2.2 路由决策树 + +``` +call() 入口 (AgentTool.tsx:284) + ├── team_name + name → spawnTeammate() → 生成持久化队友 + ├── isolation === 'remote' → Cloud Code Runner + ├── run_in_background === true → runAsyncAgentLifecycle() (fire-and-forget) + └── 默认 → runAgent() 同步执行,阻塞父 Agent +``` + +### 2.3 关键常量 + +```typescript +// src/tools/AgentTool/constants.ts +AGENT_TOOL_NAME = 'Agent' +LEGACY_AGENT_TOOL_NAME = 'Task' // 向后兼容 +ONE_SHOT_BUILTIN_AGENT_TYPES = {'Explore', 'Plan'} +``` + +--- + +## 三、Agent 定义系统 + +`src/tools/AgentTool/loadAgentsDir.ts` (755行) 负责从多来源加载 Agent 定义。 + +### 3.1 AgentDefinition 类型 + +```typescript +type AgentDefinition = { + agentType: string // 唯一标识 + whenToUse: string // 何时使用的描述 + tools: string[] // 可用工具列表,['*'] 表示全部 + disallowedTools: string[] // 禁用工具 + model: string // 模型选择 + permissionMode: string // 权限模式 + maxTurns: number // 最大对话轮次 + background: boolean // 是否后台运行 + isolation: 'worktree' // 隔离方式 + getSystemPrompt() // 系统提示词生成函数 +} +``` + +### 3.2 三种来源 + +- `BuiltInAgentDefinition` — 内置 Agent (source: 'built-in') +- `CustomAgentDefinition` — 用户自定义 `.claude/agents/*.md` 文件 +- `PluginAgentDefinition` — 插件提供的 Agent + +### 3.3 内置 Agent 类型 + +| Agent | 模型 | 工具 | 特点 | +|-------|------|------|------| +| `general-purpose` | 默认 | `['*']` | 全能通用 | +| `Explore` | haiku | 只读工具 | 代码探索,禁止编辑/写入/嵌套 Agent | +| `Plan` | inherit | 只读工具 | 架构规划,禁止编辑 | +| `verification` | inherit | 只读 | 后台对抗性测试,红色标识 | +| `statusline-setup` | sonnet | Read, Edit | 状态栏配置专用 | +| `fork` (合成) | inherit | `['*']` | 继承父上下文,权限冒泡 | +| `claude-code-guide` | — | 有限工具 | Claude Code 使用指南 | + +### 3.4 加载优先级 + +``` +built-in < plugin < userSettings < projectSettings < flagSettings < policySettings +``` + +后加载的同名 Agent 覆盖先加载的。 + +--- + +## 四、三大执行后端 + +### 4.1 InProcess 后端 (同进程) + +**文件**: `src/utils/swarm/backends/InProcessBackend.ts` (339行) + +最轻量的方式——所有 Worker 运行在**同一个 Node.js 进程**中: + +``` +Leader 进程 + ├── AsyncLocalStorage #1 → Worker A + ├── AsyncLocalStorage #2 → Worker B + └── AsyncLocalStorage #3 → Worker C +``` + +**核心流程:** + +1. `spawn()` → `spawnInProcessTeammate()` 注册到 AppState +2. `startInProcessTeammate()` fire-and-forget 启动 Agent 循环 +3. 通过 `runWithTeammateContext(context, fn)` 包裹执行,实现上下文隔离 +4. 权限请求直接使用 Leader 的 `ToolUseConfirm` 对话框(通过 `leaderPermissionBridge.ts`) + +**上下文隔离** (`src/utils/teammateContext.ts`, 96行): + +```typescript +// AsyncLocalStorage 实现进程内隔离 +const teammateContextStorage = new AsyncLocalStorage() + +function runWithTeammateContext(context: TeammateContext, fn: () => T): T { + return teammateContextStorage.run(context, fn) +} + +function isInProcessTeammate(): boolean { + return teammateContextStorage.getStore() !== undefined +} +``` + +### 4.2 Tmux 后端 (终端分屏) + +**文件**: `src/utils/swarm/backends/TmuxBackend.ts` (764行) + +每个 Worker 是一个**独立的 Claude CLI 进程**,运行在 tmux 分屏中。 + +**两种模式:** + +**Leader 在 tmux 内:** +``` +┌─────────────────┬──────────────────┐ +│ │ Worker A │ +│ Leader ├──────────────────┤ +│ (30%) │ Worker B │ +│ ├──────────────────┤ +│ │ Worker C │ +└─────────────────┴──────────────────┘ + main-vertical 布局 +``` + +**Leader 在 tmux 外:** 创建 `claude-swarm-{pid}` 独立 session,tiled 布局。 + +**关键细节:** +- `acquirePaneCreationLock()` 序列化并行 spawn,防止竞态 +- 200ms shell 初始化延迟等待 rc 文件加载 +- 通过 `tmux send-keys` 向 pane 发送命令 +- 支持 `hidePane()` (break-pane) / `showPane()` (join-pane) + +### 4.3 iTerm2 后端 + +**文件**: `src/utils/swarm/backends/ITermBackend.ts` (370行) + +通过 `it2` CLI (Python 自动化工具) 控制 iTerm2: + +- 首个 Worker 垂直分割,后续水平分割 +- 死会话恢复:split 失败时验证 session list 并重试 +- `setPaneBorderColor`/`rebalancePanes` 为 no-op(避免频繁启动 Python 进程) +- `supportsHideShow = false`(无等效的 tmux break-pane) + +### 4.4 后端选择逻辑 + +`src/utils/swarm/backends/registry.ts` 的 `detectAndGetBackend()` (line 136): + +``` +优先级瀑布: +1. 在 tmux 内? → TmuxBackend (原生) +2. 在 iTerm2 且有 it2 CLI? → ITermBackend +3. 在 iTerm2 无 it2? → 回退 tmux,提示安装 it2 +4. 有 tmux? → TmuxBackend (外部 session) +5. 非交互模式 (-p)? → InProcessBackend +6. teammateMode 配置为 'in-process'? → InProcessBackend +7. 都没有 → 报错并给出平台特定安装指引 +``` + +--- + +## 五、核心通信机制 — 文件邮箱 + +### 5.1 邮箱结构 + +`src/utils/teammateMailbox.ts` (1,183行) + +每个 Agent 拥有一个 JSON 文件邮箱: + +``` +~/.claude/teams/{team_name}/inboxes/{agent_name}.json +``` + +```typescript +type TeammateMessage = { + from: string // 发送者名称 + text: string // 消息内容 + timestamp: string // 时间戳 + read: boolean // 是否已读 + color?: string // 发送者 UI 颜色 + summary?: string // 5-10 字摘要 +} +``` + +### 5.2 核心操作 + +| 函数 | 作用 | +|------|------| +| `writeToMailbox()` | 原子写入 + `proper-lockfile` 文件锁(10次重试,5-100ms 退避) | +| `readMailbox()` | 读取所有消息 | +| `readUnreadMessages()` | 过滤未读消息 | +| `markMessagesAsRead()` | 标记已读 | + +### 5.3 结构化消息类型 + +邮箱不仅传递文本,还定义了一套协议消息: + +- `createShutdownRequestMessage` / `createShutdownApprovedMessage` — 关闭协议 +- `createPermissionRequestMessage` / `createPermissionResponseMessage` — 权限请求/响应 +- `createSandboxPermissionRequestMessage` / `createSandboxPermissionResponseMessage` — 沙箱网络权限 +- `createIdleNotification` — Worker 空闲通知 + +### 5.4 轮询机制 + +| Agent 类型 | 轮询方式 | +|------------|----------| +| Pane 类 Worker | `useInboxPoller` React hook 定期轮询 | +| InProcess Worker | `waitForNextPromptOrShutdown()` 直接读邮箱 | +| Leader | `useInboxPoller` 处理权限请求、空闲通知等 | + +--- + +## 六、SendMessage Tool — Agent 间通信 + +`src/tools/SendMessageTool/SendMessageTool.ts` (917行) + +### 6.1 输入参数 + +```typescript +{ + to: string // 收件人名称、"*" 广播、"uds:"、"bridge:" + summary: string // 5-10 字摘要 + message: string | ShutdownRequest | ShutdownResponse | PlanApprovalResponse +} +``` + +### 6.2 路由逻辑 + +``` +SendMessage.call() (line 741) + ├── to: "bridge:" → postInterClaudeMessage (跨机器 Remote Control) + ├── to: "uds:" → sendToUdsSocket (Unix Domain Socket 本地通信) + ├── to: "*" → 广播到所有团队成员邮箱 + ├── to: "" → 查 agentNameRegistry: + │ ├── InProcess Agent running → queuePendingMessage (内存队列) + │ ├── InProcess Agent stopped → resumeAgentBackground (自动唤醒!) + │ └── Pane Agent → writeToMailbox (文件邮箱) + └── 结构化消息 → 分发到对应处理器 +``` + +**关键设计:Agent 的纯文本输出对其他 Agent 不可见,必须通过 SendMessage 通信。** + +--- + +## 七、权限委托系统 + +### 7.1 完整流程 + +`src/utils/swarm/permissionSync.ts` (928行) + +``` +Worker 遇到需要权限的操作 + ↓ +检查 bash classifier 能否自动批准 (swarmWorkerHandler.ts:52) + ↓ 不能 +创建 PermissionRequest 对象 (line 71) + ↓ +注册回调 BEFORE 发送请求 (避免竞态, line 79) + ↓ +发送 permission_request 到 Leader 邮箱 (line 123) + ↓ +显示 "waiting for leader" 状态 (line 126) + ↓ +Leader 轮询检测到请求 + ↓ +在 Leader UI 展示权限对话框(带 worker badge) + ↓ +用户批准/拒绝 + ↓ +Leader 发送 permission_response 到 Worker 邮箱 + ↓ +Worker 轮询获取响应(每 500ms),继续执行 +``` + +### 7.2 InProcess 优化路径 + +通过 `leaderPermissionBridge.ts` (54行),InProcess Worker 直接使用 Leader 的 `ToolUseConfirm` 队列: + +```typescript +// REPL 注册权限 UI +registerLeaderPermissionQueue(setToolUseConfirmQueue, setToolPermissionContext) + +// Worker 推送权限请求到 Leader UI,带 worker badge +pushToLeaderPermissionQueue(request, workerBadge) +``` + +跳过文件邮箱的 I/O 开销,提供与 Leader 自身工具相同的 UI 体验。 + +### 7.3 SwarmPermissionRequest 结构 + +```typescript +type SwarmPermissionRequest = { + id: string + workerId: string + workerName: string + toolName: string + toolUseId: string + description: string + input: any + permissionSuggestions: any + status: 'pending' | 'approved' | 'rejected' + resolvedBy?: string + feedback?: string + updatedInput?: any + permissionUpdates?: any +} +``` + +### 7.4 权限模式继承 + +当 Leader 批准 Worker 的 plan 时: + +```typescript +const modeToInherit = leaderMode === 'plan' ? 'default' : leaderMode +``` + +团队文件中每个成员有独立的 `mode` 字段,支持通过 `setMemberMode()` / `setMultipleMemberModes()` 动态调整。 + +--- + +## 八、Agent 执行引擎 + +### 8.1 runAgent() — 子 Agent 核心驱动 + +`src/tools/AgentTool/runAgent.ts` (973行) + +`runAgent()` 是一个 `AsyncGenerator`: + +``` +runAgent() (line 248) + 1. 解析模型 → getAgentModel() + 2. 创建隔离上下文 → createSubagentContext() + - fork 父消息到独立上下文 + - 隔离的文件状态缓存 + 3. 初始化 Agent 专属 MCP servers → initializeAgentMcpServers() + 4. 解析工具集 → resolveAgentTools() + 5. 构建系统提示词 (Agent 自身 + 环境信息) + 6. 注册 Perfetto 追踪, hooks + 7. 调用 query() — 与主 REPL 完全相同的 agentic 循环 + 8. yield 每条消息回父 Agent + 9. 清理 MCP servers, shell tasks, agent tracking +``` + +**核心设计:子 Agent 复用完全相同的 `query()` 循环**,递归地拥有完整的工具执行能力。 + +### 8.2 异步 Agent 生命周期 + +`agentToolUtils.ts` 的 `runAsyncAgentLifecycle()` (line 508): + +``` +registerAsyncAgent() → 创建 task entry + ↓ +注册 name 到 agentNameRegistry (用于 SendMessage 路由) + ↓ +void runWithAgentContext(...) → fire-and-forget + ↓ +迭代 runAgent() 的消息流 + ↓ +ProgressTracker 追踪: + - tool use 次数 + - token 消耗 + - 最近活动 + ↓ +完成时 finalizeAgentTool(): + - 提取最终文本内容 + - 计算 totalDurationMs, totalTokens, totalToolUseCount + ↓ +completeAsyncAgent() → 标记 task 完成 + ↓ +发送 XML 通知父 Agent +``` + +**多个异步 Agent 是真正并行的**——每个都是独立的 async 操作。 + +### 8.3 工具过滤 + +`agentToolUtils.ts` 的 `filterToolsForAgent()` (line 70) 使用多层禁用列表: + +| 禁用列表 | 适用范围 | 禁用的工具 | +|----------|----------|-----------| +| `ALL_AGENT_DISALLOWED_TOOLS` | 所有 Agent | TaskOutput, ExitPlanMode, EnterPlanMode, AskUserQuestion, TaskStop | +| `CUSTOM_AGENT_DISALLOWED_TOOLS` | 非内置 Agent | 同上 + AgentTool | +| `ASYNC_AGENT_ALLOWED_TOOLS` | 后台 Agent | 白名单制:Read, Grep, Glob, Edit, Write, WebSearch 等 | +| `COORDINATOR_MODE_ALLOWED_TOOLS` | Coordinator | 仅 Agent, TaskStop, SendMessage, SyntheticOutput | + +--- + +## 九、Coordinator Mode — 自动化编排 + +`src/coordinator/coordinatorMode.ts` (369行) + +### 9.1 激活条件 + +```typescript +function isCoordinatorMode(): boolean { + return feature('COORDINATOR_MODE') && process.env.CLAUDE_CODE_COORDINATOR_MODE === '1' +} +``` + +### 9.2 工作流程 + +``` +Coordinator (只能用 Agent + TaskStop + SendMessage) + │ + ├── Research Phase → 并行派出多个 Worker 调研 + │ ├── Worker A: 搜索代码 + │ ├── Worker B: 读取文档 + │ └── Worker C: 分析依赖 + │ + ├── Synthesis Phase → Coordinator 综合分析研究结果 + │ + ├── Implementation Phase → Worker 执行代码实现 + │ + └── Verification Phase → Worker 验证实现结果 +``` + +### 9.3 Coordinator 系统提示词要点 + +`getCoordinatorSystemPrompt()` (line 111) 的 369 行提示词规定: + +- Coordinator 通过 `Agent` 工具派出 Worker,通过 `SendMessage` 继续对话,通过 `TaskStop` 终止 +- Worker 完成时发送 `` XML +- 明确的 spawn vs continue 决策指导 +- 并发管理策略 + +--- + +## 十、Team Mode — 持久化协作 + +### 10.1 团队文件结构 + +`~/.claude/teams/{team-name}/config.json`: + +```typescript +type TeamFile = { + name: string + description: string + createdAt: string + leadAgentId: string + leadSessionId: string + hiddenPaneIds: string[] + teamAllowedPaths: string[] + members: [{ + agentId: string // 格式: name@teamName + name: string + agentType: string + model: string + color: string + planModeRequired: boolean + joinedAt: string + tmuxPaneId?: string + cwd: string + worktreePath?: string + sessionId: string + subscriptions: string[] + backendType: 'tmux' | 'iterm2' | 'in-process' + isActive: boolean + mode: string + }] +} +``` + +### 10.2 团队工作流 + +``` +1. TeamCreate → 创建 config.json + tasks 目录 +2. TaskCreate → 在 ~/.claude/tasks/{team-name}/ 创建任务 +3. Agent tool (team_name + name) → 派出 teammate +4. TaskUpdate → 分配任务 owner +5. Teammate 工作 → 完成后自动发送 idle notification +6. SendMessage(shutdown_request) → 优雅关闭 teammate +``` + +### 10.3 Teammate 系统提示词附加 + +`src/utils/swarm/teammatePromptAddendum.ts`: + +> "You are running as an agent in a team. To communicate with anyone on your team, use the SendMessage tool. Just writing a response in text is not visible to others." + +--- + +## 十一、Worktree 隔离 + +`src/utils/worktree.ts` 的 `createAgentWorktree()` (line 902) + +### 11.1 创建流程 + +``` +Agent spawn 时 isolation='worktree' + → 生成 slug: agent-{agentId前8字符} + → validateWorktreeSlug() — 拒绝路径穿越,限制64字符 + → 路径: .claude/worktrees/ + → 分支: worktree- + → git worktree add -B worktree- + → performPostCreationSetup(): + - 复制 settings.local.json + - 配置 git hooks + - 符号链接目录 + - 复制 .worktreeinclude 文件 +``` + +### 11.2 清理流程 + +``` +Agent 完成时: + → hasWorktreeChanges()? + ├── 有改动 → 保留 worktree,在 中报告路径和分支 + └── 无改动 → removeAgentWorktree() 自动清理 +``` + +--- + +## 十二、Worker 完整生命周期 + +### 12.1 诞生 + +``` +Leader 调用 Agent tool + ├── InProcess: spawnInProcessTeammate() + │ 1. 生成 agentId = name@teamName + │ 2. 创建独立 AbortController(不链接到 Leader) + │ 3. 创建 TeammateContext for AsyncLocalStorage + │ 4. 注册 InProcessTeammateTaskState 到 AppState + │ 5. startInProcessTeammate() → runWithTeammateContext() → runAgent() + │ + └── Pane: PaneBackendExecutor.spawn() + 1. assignTeammateColor() 分配颜色 + 2. createTeammatePaneInSwarmView() 创建分屏 + 3. 构建 CLI 命令: + claude --agent-id X --agent-name Y --team-name Z + --agent-color C --parent-session-id P + 4. sendCommandToPane() 发送命令 + 5. writeToMailbox() 写入初始 prompt +``` + +### 12.2 执行 + +``` +InProcess Runner (inProcessRunner.ts, 1552行): + runWithTeammateContext() 包裹: + → runAgent() → query() 循环 + → 轮询邮箱获取新消息(注入为 user-turn messages) + → 权限请求转发到 Leader UI + → ProgressTracker 追踪工具使用和 token + → 支持 auto-compact(上下文过大时自动压缩) +``` + +### 12.3 通信 + +``` +Worker → Leader: writeToMailbox() 或 queuePendingMessage (InProcess) +Leader → Worker: writeToMailbox() 或 SendMessage +Worker → Worker: SendMessage tool → writeToMailbox() +广播: SendMessage(to: "*") → 写入所有成员邮箱 +``` + +### 12.4 终止 + +三条终止路径: + +| 方式 | 触发 | 流程 | +|------|------|------| +| 优雅关闭 | `shutdown_request` via mailbox | Worker 处理请求 → 退出 → idle notification | +| 强制终止 (InProcess) | `killInProcessTeammate()` | abort AbortController → 更新 task 状态为 'killed' → 移出团队文件 | +| 强制终止 (Pane) | `backend.killPane(paneId)` | 杀死 tmux/iTerm2 pane | + +### 12.5 会话清理 + +`cleanupSessionTeams()` 在 Leader 退出时运行: +- 杀死所有孤立 pane +- 移除团队和任务目录 +- 销毁 git worktree + +--- + +## 十三、Feature Gates 汇总 + +| Feature Flag | 控制内容 | 激活方式 | +|-------------|---------|---------| +| `COORDINATOR_MODE` | Coordinator 自动编排 | 编译时 + `CLAUDE_CODE_COORDINATOR_MODE=1` | +| `FORK_SUBAGENT` | Fork 模式子 Agent | 编译时 feature gate | +| `BUILTIN_EXPLORE_PLAN_AGENTS` | Explore/Plan 内置 Agent | 编译时 + A/B test `tengu_amber_stoat` | +| `VERIFICATION_AGENT` | 验证 Agent | 编译时 + gate `tengu_hive_evidence` | +| Agent Teams (Swarm) | 团队协作模式 | `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS` + GrowthBook gate | + +--- + +## 十四、通信协议总览 + +| 通道 | 机制 | 方向 | 用途 | +|------|------|------|------| +| 文件邮箱 `inboxes/{name}.json` | JSON + 文件锁 | 任意 Agent → 任意 Agent | 所有 teammate 消息、权限、关闭协议 | +| 内存队列 `pendingUserMessages` | AppState | 父 → InProcess 子 | 运行中 Agent 的即时消息 | +| Leader Permission Bridge | 模块级函数注册 | InProcess Worker → Leader UI | 权限弹窗 | +| UDS Socket | Unix Domain Socket | Session → Session (本地) | 跨 session 消息 | +| Bridge (Remote Control) | HTTP via Anthropic | Session → Session (跨机器) | 远程控制消息 | +| Task 系统 | 文件 `~/.claude/tasks/` | 所有团队成员 | 工作项追踪、状态更新 | +| `` XML | 注入对话 | 后台 Agent → 父 Agent | 完成/失败通知 | + +--- + +## 十五、架构规模统计 + +| 层次 | 核心组件 | 代码规模 | +|------|---------|---------| +| 入口 | AgentTool | ~800行 | +| 执行引擎 | runAgent + query() 循环 | ~2,700行 | +| Agent 定义 | loadAgentsDir + builtInAgents | ~850行 | +| 后端管理 | registry + 3个后端实现 | ~1,800行 | +| 通信 | teammateMailbox + SendMessageTool | ~2,100行 | +| 权限委托 | permissionSync + leaderPermissionBridge | ~1,000行 | +| 团队管理 | teamHelpers + spawnMultiAgent | ~1,800行 | +| 上下文隔离 | agentContext + teammateContext | ~300行 | +| Coordinator | coordinatorMode + workerAgent | ~400行 | +| InProcess Runner | inProcessRunner | ~1,550行 | +| **总计** | **37+ 文件** | **~13,700行** | + +--- + +## 十六、核心设计理念 + +1. **query() 循环复用** — 子 Agent 使用与主 REPL 完全相同的 agentic 循环,天然具备完整工具能力 +2. **AsyncLocalStorage 隔离** — InProcess 模式下零 IPC 开销,通过 Node.js 原生机制隔离上下文 +3. **文件邮箱统一通信** — 跨进程、跨后端的统一消息协议,用文件锁保证并发安全 +4. **权限始终由人类把控** — 无论多少层 Agent 嵌套,危险操作最终都路由到 Leader 的 UI 由用户决定 +5. **优雅降级** — 从 iTerm2 → tmux → InProcess,自动选择最佳可用后端 +6. **Fire-and-forget 异步** — 后台 Agent 通过 `` XML 非侵入式通知父 Agent diff --git a/docs/cc_analysis/analysis_20260401/清除Claude_Code追踪数据指南.md b/docs/cc_analysis/analysis_20260401/清除Claude_Code追踪数据指南.md new file mode 100644 index 0000000..9635b77 --- /dev/null +++ b/docs/cc_analysis/analysis_20260401/清除Claude_Code追踪数据指南.md @@ -0,0 +1,274 @@ +# macOS 上清除 Claude Code 追踪数据指南 + +> 基于 Claude Code 源码(2026-03-31 泄露快照,512K 行 TypeScript)的逆向分析。 +> 所有文件路径和字段名均从源码中确认。 + +--- + +## 背景:Claude Code 追踪了什么? + +Claude Code **不收集硬件指纹**(无 MAC 地址、CPU 型号、内存大小、GPU 信息),但通过以下机制实现用户追踪: + +| 追踪标识 | 持久性 | 存储位置 | 说明 | +|----------|--------|----------|------| +| `userID` | 永久(直到手动删除) | `~/.claude.json` | 随机生成的 64 位十六进制字符串,跨会话追踪主键 | +| `anonymousId` | 永久 | `~/.claude.json` | 格式 `claudecode.v1.`,备用追踪 ID | +| `accountUuid` | 永久(绑定账号) | `~/.claude.json` → `oauthAccount` | OAuth 登录后直接关联身份 | +| `emailAddress` | 永久 | `~/.claude.json` → `oauthAccount` | 登录邮箱 | +| `rh` (仓库哈希) | 按仓库 | 每次 API 请求 Header | git remote URL 的 SHA256 前 16 位 | +| Statsig Stable ID | 永久 | `~/.claude/statsig/` | 特性开关系统的设备标识 | + +**数据流向:** +- **Anthropic 1P** → `/api/event_logging/batch`(完整环境数据 + Auth) +- **Datadog** → `https://http-intake.logs.us5.datadoghq.com`(白名单事件,已脱敏) +- **OTLP**(可选)→ 用户自配的端点(默认关闭) + +--- + +## 第一级:重置设备标识(最重要) + +这是跨会话追踪的主键。清除后你对 Anthropic 来说就是一台"全新设备"。 + +```bash +# 查看当前的追踪 ID +grep -E '"userID"|"anonymousId"|"firstStartTime"|"claudeCodeFirstTokenDate"' ~/.claude.json +``` + +```bash +# 删除追踪标识(保留其他配置不变) +python3 -c " +import json, os +p = os.path.expanduser('~/.claude.json') +with open(p, 'r') as f: d = json.load(f) +removed = [] +for k in ['userID', 'anonymousId', 'firstStartTime', 'claudeCodeFirstTokenDate']: + if k in d: + removed.append(k) + del d[k] +with open(p, 'w') as f: json.dump(d, f, indent=2) +print(f'已删除: {removed}') +print('下次启动 Claude Code 会自动生成新的 userID') +" +``` + +**效果:** 下次启动时会生成全新的 `userID`,之前的使用记录无法与你关联。 + +--- + +## 第二级:清除遥测和分析数据 + +```bash +# 未成功上报的分析事件(包含完整的环境信息、会话数据) +rm -rf ~/.claude/telemetry/ + +# Statsig/GrowthBook 特性开关缓存(包含 stable_id 设备标识) +rm -rf ~/.claude/statsig/ + +# 统计缓存 +rm -f ~/.claude/stats-cache.json +``` + +**效果:** 清除本地缓存的遥测数据和特性开关系统的设备标识。 + +--- + +## 第三级:清除会话和历史记录 + +```bash +# 完整命令历史(你输入过的所有提示词) +rm -f ~/.claude/history.jsonl + +# 会话快照 +rm -rf ~/.claude/sessions/ + +# 大段粘贴内容的哈希缓存 +rm -rf ~/.claude/paste-cache/ + +# Shell 环境快照 +rm -rf ~/.claude/shell-snapshots/ + +# 会话环境变量 +rm -rf ~/.claude/session-env/ + +# 文件编辑历史(Claude 做过的每次文件修改) +rm -rf ~/.claude/file-history/ + +# 调试日志 +rm -rf ~/.claude/debug/ +``` + +**效果:** 清除所有本地会话痕迹。不影响 Claude Code 正常使用。 + +--- + +## 第四级:清除 OAuth 账号关联 + +```bash +# 查看当前关联的账号信息 +python3 -c " +import json, os +with open(os.path.expanduser('~/.claude.json')) as f: d = json.load(f) +oa = d.get('oauthAccount', {}) +print(f\"账号 UUID: {oa.get('accountUuid', '无')}\") +print(f\"邮箱: {oa.get('emailAddress', '无')}\") +" +``` + +```bash +# 从 macOS Keychain 删除 OAuth Token +security delete-generic-password -s "claude-code" 2>/dev/null +security delete-generic-password -s "claude-code-credentials" 2>/dev/null + +# 清除配置文件中的账号缓存 +python3 -c " +import json, os +p = os.path.expanduser('~/.claude.json') +with open(p, 'r') as f: d = json.load(f) +removed = [] +for k in ['oauthAccount', 's1mAccessCache', 'groveConfigCache', + 'passesEligibilityCache', 'clientDataCache', + 'cachedExtraUsageDisabledReason', 'githubRepoPaths']: + if k in d: + removed.append(k) + del d[k] +with open(p, 'w') as f: json.dump(d, f, indent=2) +print(f'已删除: {removed}') +" +``` + +**效果:** 断开与 Claude.ai 账号的本地关联。下次使用需要重新登录。 + +--- + +## 第五级:完全重置(核弹选项) + +```bash +# 1. 备份你的自定义配置 +mkdir -p ~/Desktop/claude-backup +cp ~/.claude/CLAUDE.md ~/Desktop/claude-backup/ 2>/dev/null +cp ~/.claude/settings.json ~/Desktop/claude-backup/ 2>/dev/null +cp -r ~/.claude/skills ~/Desktop/claude-backup/ 2>/dev/null +cp -r ~/.claude/hooks ~/Desktop/claude-backup/ 2>/dev/null +echo "已备份到 ~/Desktop/claude-backup/" + +# 2. 删除所有 Claude Code 数据 +rm -rf ~/.claude/ +rm -f ~/.claude.json + +# 3. 清除 Keychain 中的 Token +security delete-generic-password -s "claude-code" 2>/dev/null +security delete-generic-password -s "claude-code-credentials" 2>/dev/null + +echo "已完全重置。下次启动 Claude Code 会重新初始化。" +``` + +**效果:** 等同于全新安装。所有配置、历史、记忆、技能、插件设置全部清除。 + +--- + +## 防止未来追踪 + +### 方法 1:禁用非必要网络流量 + +在 `~/.claude/settings.json` 中添加: + +```json +{ + "env": { + "CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": "1" + } +} +``` + +**效果:** 禁用分析上报、GrowthBook 特性开关拉取、配额预检等非必要请求。 + +### 方法 2:使用第三方云后端 + +源码确认:使用 Bedrock 或 Vertex 时,`isAnalyticsDisabled()` 返回 `true`,完全跳过所有分析代码。 + +```bash +# 使用 AWS Bedrock(需要 AWS 凭证) +export CLAUDE_CODE_USE_BEDROCK=1 + +# 或使用 Google Vertex AI(需要 GCP 凭证) +export CLAUDE_CODE_USE_VERTEX=1 +``` + +### 方法 3:定期自动清理 + +创建一个定时清理脚本 `~/claude-privacy-clean.sh`: + +```bash +#!/bin/bash +# 清除 Claude Code 追踪数据(保留配置) + +rm -rf ~/.claude/telemetry/ +rm -rf ~/.claude/statsig/ +rm -f ~/.claude/stats-cache.json +rm -f ~/.claude/history.jsonl +rm -rf ~/.claude/sessions/ +rm -rf ~/.claude/paste-cache/ +rm -rf ~/.claude/shell-snapshots/ +rm -rf ~/.claude/session-env/ +rm -rf ~/.claude/debug/ + +# 重置 device ID +python3 -c " +import json, os +p = os.path.expanduser('~/.claude.json') +if os.path.exists(p): + with open(p,'r') as f: d = json.load(f) + for k in ['userID','anonymousId','firstStartTime','claudeCodeFirstTokenDate']: + d.pop(k, None) + with open(p,'w') as f: json.dump(d,f,indent=2) +" + +echo "[$(date)] Claude Code 追踪数据已清除" +``` + +```bash +# 添加执行权限 +chmod +x ~/claude-privacy-clean.sh + +# 可选:添加到 crontab 每天自动执行 +# crontab -e +# 0 3 * * * ~/claude-privacy-clean.sh >> ~/claude-clean.log 2>&1 +``` + +--- + +## 各级清理的影响对比 + +| 操作 | 对 Anthropic 的效果 | 对你的影响 | +|------|---------------------|------------| +| **重置 Device ID** | 无法关联历史使用数据 | 无感知,自动生成新 ID | +| **清除遥测** | 本地缓存的分析事件不会补发 | 无感知 | +| **清除历史** | — | 丢失命令历史和 ctrl+r 搜索 | +| **清除 OAuth** | 断开账号关联 | 需要重新登录 | +| **完全重置** | 等同全新用户 | 丢失所有自定义配置 | +| **禁用非必要流量** | 不再接收分析数据 | 特性开关可能不更新 | +| **使用 Bedrock/Vertex** | 分析代码完全不执行 | 需要云厂商凭证 | + +--- + +## 附:数据文件速查表 + +| 文件/目录 | 包含的追踪数据 | +|-----------|----------------| +| `~/.claude.json` | userID, anonymousId, oauthAccount, 首次使用时间, GitHub 仓库映射 | +| `~/.claude/telemetry/` | 未上报的 1P 分析事件(JSON,含完整 EnvContext) | +| `~/.claude/statsig/` | Statsig stable_id, 特性开关缓存 | +| `~/.claude/stats-cache.json` | 统计数据缓存 | +| `~/.claude/history.jsonl` | 完整命令历史(你输入的每一条提示词) | +| `~/.claude/sessions/` | 会话元数据 | +| `~/.claude/paste-cache/` | 粘贴内容的哈希地址缓存 | +| `~/.claude/file-history/` | Claude 做过的文件修改记录 | +| `~/.claude/debug/` | 调试日志 | +| `~/.claude/shell-snapshots/` | Shell 环境快照 | +| macOS Keychain → `claude-code` | OAuth access/refresh token | + +--- + +*来自:AI超元域 | B站频道:https://space.bilibili.com/3493277319825652* + +*基于 Claude Code 源码逆向分析,2026-03-31*