docs: 文档大重组,对齐 README 入口

以 README 为单一事实来源,重构整个 docs/ 目录。

最终结构(3 大组、15 篇文档):
- 开始: installation / quickstart / model-providers
- 核心功能: pipes-and-lan、acp、channels、chrome-control、computer-use、
  voice-mode、web-browser-tool、auto-dream、remote-control-self-hosting、
  langfuse-monitoring
- 内部机制: growthbook-adapter、sentry-setup

主要变更:
- 删除 56 个 README 未提及的文档(architecture 全部 / guides 全部 /
  features 中未在 README 出现的 20 篇 / internals 中的 5 篇)
- 合并 6 组重复文档(pipes-and-lan、chrome-control、acp、computer-use、
  auto-dream、coordinator-mode 简化为入口)
- features 子组从 5 → 4,ui/ 合并入 tools/
- 所有保留文档加上人性化 frontmatter(title/description/keywords)
- docs.json navigation 简化为 3 大组,redirects 重新过滤为 7 条合并跳转
- 新增 docs.md 工作大纲与验证脚本(verify-docs / check-docs-orphans /
  dump-docs-outline)

总计 130 文件改动,从约 35000 行精简到约 2000 行。

Co-Authored-By: glm-5.2 <zai-org@claude-code-best.win>
This commit is contained in:
claude-code-best
2026-06-14 20:42:51 +08:00
parent 2714bbf812
commit 37dac682b9
130 changed files with 2074 additions and 33533 deletions

View File

@@ -1,564 +0,0 @@
# Agent 通讯修复 Jira Task
- 版本v1.0
- 生成日期2026-04-25
- 来源由按文件执行清单、Claude 交叉验证意见整理合并
- 范围ACP Agent / Bridge / Remote Control Server / REPL Hook 生命周期
- 使用方式:这是唯一执行任务文档;每个 `JIRA-*` 小节可直接拆成一个 Jira issue字段保持统一便于复制或二次导入。
---
## 方案性质
本文档是目标状态式执行方案,不是临时补丁清单。每张 ticket 必须交付明确的代码终态、测试覆盖和回归边界;不得只用局部 workaround 掩盖问题。
---
## 执行总则
1. 先边界安全,后内部优化:先修 WS 入站大小与输入校验,避免线上风险扩大。
2. 单文件可回滚:每个文件内修改保持内聚,便于回滚与 bisect。
3. 不改协议语义,只修实现缺陷:除 `resource_link` 表达形式统一外,不改变主流程契约。
4. 每个文件必须有验收输出:要么测试用例,要么日志/指标验证。
5. 发布前必须确认协议层行为无回归:`stopReason` 决策与 `sessionUpdate` 发送顺序保持稳定。
---
## Epic
### JIRA-EPIC-001提升 Agent 通讯链路稳定性与边界安全
- Issue TypeEpic
- PriorityP0
- Owner核心通讯 / 后端网关 / QA
- ScopeACP Agent、ACP Bridge、Remote Control Server、REPL 初始化生命周期
- Goal修复长会话资源泄漏、补齐 WebSocket 入站边界、统一 prompt 转换、收敛类型风险,并补充关键回归测试。
#### Epic 验收标准
- `bun run typecheck` 0 error。
- P0 WebSocket 超大消息拒绝逻辑已实现并覆盖测试。
- ACP bridge abort listener 生命周期无累积。
- prompt 转换实现单源化。
- settings/defaultMode 能真实影响 ACP permission mode`_meta.permissionMode` 保持最高优先级。
- REPL 目标 hook suppress 清理完成timer cleanup 完整。
---
## P0 Tickets
### JIRA-001为 session ingress WebSocket 补齐消息大小限制
- Issue TypeBug
- PriorityP0
- Story Points3
- Owner后端/网关
- Files
- `packages/remote-control-server/src/routes/v1/session-ingress.ts`
- 后续票JIRA-008同文件 P1 类型与 decode path 收尾)
#### 参考代码位置
- `packages/remote-control-server/src/routes/v1/session-ingress.ts:100-106`
#### 背景
`session-ingress` 当前缺少 WebSocket message size limit。ACP 路由已有类似限制,两个入口边界不一致,可能导致大包占用内存或绕过入口保护。
#### 实施要求
- 新增 `MAX_WS_MESSAGE_SIZE = 10 * 1024 * 1024`,与 ACP 路由的 10MB 上限保持一致。
-`onMessage` decode 后优先检查 payload size。
- 超限时执行 `ws.close(1009, "message too large")`
- 日志记录 `sessionId`、payload size、limit。
-`string``ArrayBuffer``Uint8Array` 进行统一 decode 分流。
- 非支持类型直接拒绝并记录,不进入业务 handler。
#### 验收标准
- 11MB payload 被 1009 close。
- 1KB 合法 payload 仍正常进入 handler。
- 非支持类型 payload 不进入 handler。
- 不改变 URL、auth、session 解析逻辑。
#### 回归范围
- Remote Control Server session ingress WebSocket。
- 正常会话消息转发。
- WebSocket close code 行为。
#### 风险等级
- 中。入口逻辑变更可能影响特殊客户端 payload 类型。
#### 必须验证
-`packages/remote-control-server/src/__tests__/routes.test.ts` 增加 session-ingress WebSocket 大包、小包、坏类型 payload 用例。
- 运行 `bun run typecheck`
---
### JIRA-002修复 ACP bridge abort listener 生命周期泄漏
- Issue TypeBug
- PriorityP0
- Story Points3
- Owner核心通讯
- Files
- `src/services/acp/bridge.ts`
#### 参考代码位置
- `src/services/acp/bridge.ts:576-585`
#### 背景
ACP bridge 的 `Promise.race` abort 分支注册 listener 后缺少完整 cleanup。长会话或高频 next 场景可能出现 listener 累积。
#### 实施要求
- 将 abort race 改为可清理监听器写法。
- 注册 listener 后保留 handler 引用。
- `sdkMessages.next()` 先返回时必须 `removeEventListener`
- abort、throw、return 等路径都在 `finally` 中清理。
- 不改变 `stopReason` 决策逻辑。
- 不改变 `sessionUpdate` 发送顺序。
#### 验收标准
- 模拟 10k 次 next 且不 abortlistener 不增长。
- abort 场景仍返回 `cancelled`
- 原有 streaming/session update 行为无回归。
#### 回归范围
- ACP bridge streaming loop。
- 用户取消请求。
- SDK generator 异常路径。
#### 风险等级
- 中。异步控制流变更需要覆盖取消与异常路径。
#### 必须验证
- 新增 listener cleanup 单元测试。
- 运行 `bun run typecheck`
---
## P1 Tickets
### JIRA-003优化 ACP agent pending prompt 队列为 O(1) 出队
- Issue TypeTask
- PriorityP1
- Story Points5
- Owner核心通讯
- Files
- `src/services/acp/agent.ts`
#### 参考代码位置
- `src/services/acp/agent.ts:332-339`
#### 背景
当前 pending prompt 队列使用 `Map + sort` 获取下一项,排队量上升时会带来不必要的排序成本。
#### 实施要求
- 改为 `queue: string[]` + `pendingMap: Map<string, PendingPrompt>` 组合。
- 入队执行 `queue.push(id)``pendingMap.set(id, prompt)`
- 出队从队首惰性跳过已取消项。
- 取消只从 `pendingMap` 删除,不做数组中间删除。
- 保持现有取消语义和出队顺序。
#### 验收标准
- 1000 pending prompt 场景下出队顺序正确。
- 已取消 prompt 不会被 resolve。
- 出队不再依赖全量 sort。
- 1000 排队场景下出队耗时低于旧实现;测试记录旧实现复杂度风险和新实现 O(1) 出队路径。
- 行为与旧实现兼容。
#### 回归范围
- ACP prompt queue。
- 并发 prompt 请求。
- prompt cancel / resolve 边界。
#### 风险等级
- 中。队列结构变更可能引入取消边界问题。
#### 必须验证
- 新增 queue 顺序与取消测试。
- 对 1000 prompt 场景做性能断言或日志记录。
---
### JIRA-004接入真实 settings 读取并校验 ACP permission mode
- Issue TypeBug
- PriorityP1
- Story Points3
- Owner核心通讯
- Files
- `src/services/acp/agent.ts`
#### 参考代码位置
- `src/services/acp/agent.ts:465-467`
#### 背景
`getSetting()` 当前未真正接入项目配置,导致默认 permission mode 配置无法按预期生效。
#### 实施要求
- 接入项目现有 settings/config 读取逻辑。
- 仅接受合法 permission mode 枚举值。
- 非法值 fallback 到 `default`
- `_meta.permissionMode` 继续保持最高优先级。
- 不改变外部协议字段。
#### 验收标准
- settings/defaultMode 能影响默认 permission mode。
- `_meta.permissionMode` 能覆盖 settings。
- 非法 settings 值不会传播到运行时。
- 类型检查通过。
#### 回归范围
- ACP agent session 初始化。
- 权限模式同步。
- 客户端 `_meta` 覆盖逻辑。
#### 风险等级
- 中。配置优先级错误会影响权限行为。
#### 必须验证
- 新增 defaultMode / `_meta.permissionMode` 优先级测试。
- 运行 `bun run typecheck`
---
### JIRA-005单源化 ACP prompt 转换逻辑
- Issue TypeRefactor
- PriorityP1
- Story Points5
- Owner核心通讯
- Files
- `src/services/acp/agent.ts`
- `src/services/acp/bridge.ts`
- `src/services/acp/promptConversion.ts`(新增)
#### 参考代码位置
- `src/services/acp/agent.ts:754-758`
- `src/services/acp/agent.ts:764-785`
- `src/services/acp/bridge.ts:522-537`
#### 背景
ACP agent 与 bridge 存在重复 prompt 转换逻辑,`resource_link` 等 block 的输出策略容易分叉。
#### 实施要求
- 新增共享转换模块 `src/services/acp/promptConversion.ts`
- `agent.ts``bridge.ts` 改为调用共享转换函数。
- 删除 `bridge.ts``promptToQueryContent` 的真实实现;如导出仍需保留,则只允许保留调用共享函数的 wrapper。
- `resource_link` 输出改为稳定纯文本元信息,禁止 markdown link。
- 保持其他 block 转换语义不变。
#### 验收标准
- 全仓库仅保留一个真实 prompt 转换实现。
- 相同 input block 在 agent/bridge 输出一致。
- `resource_link` 不再输出 `[name](uri)` 形式。
- 相关测试覆盖转换一致性。
#### 回归范围
- ACP prompt input。
- bridge query content。
- resource link prompt 表达。
#### 风险等级
- 中。文本格式变化可能影响下游 prompt 快照或断言。
#### 必须验证
- 新增 shared conversion 单元测试。
- 全仓库搜索重复转换函数。
- 运行 `bun run typecheck`
---
### JIRA-006治理 REPL onInit effect 依赖并补齐 timer cleanup
- Issue TypeTask
- PriorityP1
- Story Points3
- Owner终端 UI
- Files
- `src/screens/REPL.tsx`
#### 参考代码位置
- `src/screens/REPL.tsx:654-662`
- `src/screens/REPL.tsx:4996-5005`
#### 背景
REPL 中目标初始化 effect 存在 hook dependency suppresswarm-up timer 也需要显式 cleanup避免频繁挂载/卸载时留下悬挂任务。
#### 实施要求
- 整理 `onInit` 生命周期,使用稳定引用或 effect 内联。
- 移除目标段 `exhaustive-deps` suppress。
- 保持 unmount cleanup 行为不变。
- warm-up effect 中记录 timeout id。
- cleanup 中执行 `clearTimeout(timeoutId)`
- 保留 `alive` 判定作为并发保护。
#### 验收标准
- 目标段不再需要 hooks lint suppress。
- 高频打开/关闭搜索栏无悬挂 timer 增长。
- REPL 初始化行为无回归。
#### 回归范围
- REPL 初始化。
- 搜索栏 warm-up。
- 组件卸载 cleanup。
#### 风险等级
- 中。React effect 依赖治理可能改变初始化时机。
#### 必须验证
- 运行 lint/typecheck。
- 手动或测试覆盖 REPL mount/unmount。
---
### JIRA-007收敛 ACP route WebSocket 事件 any 类型
- Issue TypeTask
- PriorityP1
- Story Points2
- Owner后端/网关
- Files
- `packages/remote-control-server/src/routes/acp/index.ts`
#### 参考代码位置
- `packages/remote-control-server/src/routes/acp/index.ts:108-146`
#### 背景
ACP route 中 WebSocket 事件和 socket 参数存在 `any`,降低编译期保护。
#### 实施要求
- 定义最小 WebSocket 事件类型open/message/close/error。
-`_evt: any``evt: any``ws: any` 替换为窄类型。
- 不改变 payload decode 与大小检查策略。
- 不改变现有 handler 行为。
#### 验收标准
- 编译期能捕获错误事件字段访问。
- 现有 WebSocket 行为不变。
- `bun run typecheck` 通过。
#### 回归范围
- ACP WebSocket route。
- message decode。
- close/error handler。
#### 风险等级
- 低。类型收敛为主。
#### 必须验证
- 运行 `bun run typecheck`
- 保留现有测试通过。
---
### JIRA-008收敛 session ingress WebSocket 事件类型与 decode path
- Issue TypeTask
- PriorityP1
- Story Points3
- Owner后端/网关
- Files
- `packages/remote-control-server/src/routes/v1/session-ingress.ts`
- 前置依赖JIRA-001 已合并
#### 参考代码位置
- `packages/remote-control-server/src/routes/v1/session-ingress.ts:100-106`
#### 背景
在完成 P0 size guard 后session ingress 仍需要进一步收敛事件类型与 decode path减少隐式类型风险。
#### 实施要求
- 定义或复用最小 WebSocket message event 类型。
- 将 message decode 分支集中到一个小函数。
- 保持 P0 size guard 与 close code 语义。
- 不改变 auth/session 解析。
#### 验收标准
- decode path 单一清晰。
- 不支持 payload 类型有明确拒绝路径。
- `bun run typecheck` 通过。
#### 回归范围
- Session ingress WebSocket message handling。
- P0 大包拒绝逻辑。
#### 风险等级
- 低到中。与 P0 同文件,注意避免重复改动冲突。
#### 必须验证
- 与 JIRA-001 同批测试。
- 运行 `bun run typecheck`
---
## QA Tickets
### JIRA-009补充 ACP 通讯回归测试
- Issue TypeTest
- PriorityP1
- Story Points5
- OwnerQA/核心通讯
- Files
- `src/services/acp/agent.ts`
- `src/services/acp/bridge.ts`
- `src/services/acp/promptConversion.ts`
- `src/services/acp/__tests__/agent.test.ts`
- `src/services/acp/__tests__/bridge.test.ts`
- `src/services/acp/__tests__/promptConversion.test.ts`
#### 覆盖场景
- 长会话 10k turn无 abort listener 累积。
- prompt queue 1000 并发排队,取消/出队顺序正确。
- settings/defaultMode 与 `_meta.permissionMode` 优先级正确。
- `resource_link` 转换在 agent 与 bridge 输出一致。
#### 验收标准
- 新增测试在本地稳定通过。
- 不依赖真实网络或外部服务。
- 测试 mock 遵守仓库规范,只 mock 有副作用链路。
#### 回归范围
- ACP bridge。
- ACP agent。
- prompt conversion。
- permission mode resolution。
#### 风险等级
- 中。异步测试可能有稳定性问题,需要避免时间敏感断言。
#### 必须验证
- 运行相关 `bun test`
- 运行 `bun run typecheck`
---
### JIRA-010补充 Remote Control Server WebSocket 入站回归测试
- Issue TypeTest
- PriorityP1
- Story Points3
- OwnerQA/后端
- Files
- `packages/remote-control-server/src/__tests__/routes.test.ts`
- `packages/remote-control-server/src/routes/v1/session-ingress.ts`
#### 覆盖场景
- 11MB session ingress payload 被 1009 close与 10MB 上限对齐)。
- 合法小 payload 正常进入 handler。
- 非支持 payload 类型被拒绝。
- 日志或可观测输出包含 sessionId、payload size、limit。
#### 验收标准
- 11MB payload 被 1009 close与 10MB 上限对齐)。
- 新增测试稳定通过。
- 不启动真实外部服务。
- 不改变现有 route public contract。
#### 回归范围
- RCS session ingress route。
- WebSocket message handling。
- close code 行为。
#### 风险等级
- 中。测试需要适配现有 WebSocket/mock 基础设施。
#### 必须验证
- 运行 RCS package 相关测试。
- 运行 `bun run typecheck`
---
## 推荐执行顺序
执行节奏与原计划保持一致:先完成 P0 全部改动和冒烟验证,再启动 P1 改造;测试票可穿插执行,但不得绕过 P0 gate。
1. JIRA-001先封入口大包风险。
2. JIRA-002修长会话 listener 生命周期。
3. JIRA-010补 RCS 入站测试,锁住 P0 行为。
4. JIRA-003优化 pending prompt queue。
5. JIRA-004接入 settings/defaultMode。
6. JIRA-005单源化 prompt 转换。
7. JIRA-009补 ACP 回归测试。
8. JIRA-006治理 REPL effect/timer。
9. JIRA-007收敛 ACP route 类型。
10. JIRA-008收敛 session ingress 类型与 decode path。
---
## Release Checklist
- [ ] `bun run typecheck` 0 error
- [ ] P0 tickets 已合并并测试通过
- [ ] ACP 回归测试通过
- [ ] RCS WebSocket 入站测试通过
- [ ] prompt conversion 单源化已通过代码搜索确认
- [ ] permission mode 优先级测试通过
- [ ] 协议层行为无回归stopReason 决策、sessionUpdate 发送顺序)
- [ ] REPL hook/timer 改动通过 lint/typecheck
- [ ] 最终变更说明包含风险与未覆盖项

View File

@@ -1,74 +0,0 @@
# Agent 通讯修复问题文档
- 版本v1.0
- 生成日期2026-04-25
- 范围ACP Agent / Bridge / Remote Control Server / REPL Hook 生命周期
- 配套执行文档:`docs/internals/agent-comm-fix-jira-tasks.md`
- 目的:保留决策前要问的问题、交叉验证提示词和已确认结论;不要在这里写 Jira 执行步骤。
---
## 1. 当前已确认结论
- 只保留两份交付文档:本问题文档 + Jira Task 文档。
- Jira Task 文档是唯一执行入口,包含 Owner、优先级、文件范围、验收标准、风险和验证建议。
- Claude 交叉验证结论:整体通过,无 blocking findings建议补充协议回归 gate、JIRA-001/008 依赖、代码参考位置和阈值一致性,这些建议已合并到 Jira Task 文档。
- 本次已进入业务代码修复阶段,必须运行 `bun run typecheck` 和相关回归测试。
---
## 2. 执行前必须问清的问题
1. `session-ingress` 的 WebSocket 上限是否固定为 10MB并与 ACP route 保持一致?
2. 超限 close code 是否统一使用 `1009`close reason 是否固定为 `message too large`
3. `resource_link` 的纯文本格式是否已有下游依赖,能否替代当前 markdown link 表达?
4. ACP permission mode 的真实 settings key 是哪个,非法值 fallback 是否统一为 `default`
5. `_meta.permissionMode` 是否必须始终覆盖 settings/defaultMode
6. abort listener 测试中,是否能通过 mock signal 或计数器稳定证明 10k next 后无 listener 累积?
7. pending prompt queue 的取消语义是否允许惰性清理,而不是立刻从数组中删除?
8. REPL hook suppress 的清理范围是否只限目标段,不顺手改其他 decompiled React Compiler 结构?
9. RCS WebSocket 测试应放在现有哪个 `__tests__` 布局下,是否已有 route/mock 基础设施可复用?
10. 发布 gate 是否必须包含 `stopReason` 决策与 `sessionUpdate` 发送顺序不回归?
---
## 3. 给 Claude 或 Reviewer 的复核问题
```text
请作为外部审查者,复核 docs/internals/agent-comm-fix-jira-tasks.md。
请检查:
1. 是否仍满足“按文件分工的执行清单”和“Jira task 文档”要求。
2. 是否存在遗漏的文件、验收标准、风险或前置依赖。
3. 是否有重复、误导执行者、优先级不合理或测试不可落地的问题。
4. 是否还有必须阻断实施的 finding。
请用中文输出:
- Verdict
- Blocking Findings
- Non-blocking Findings
- Suggested Edits
- Final Recommendation
不要修改文件,只输出审查意见。
```
---
## 4. 已处理的复核建议
- Release Checklist 已补充协议层行为无回归 gate。
- JIRA-001 与 JIRA-008 已明确同文件前后置关系。
- JIRA-001 到 JIRA-008 已补充参考代码位置。
- JIRA-003 已补回 1000 排队场景下的出队耗时验收。
- JIRA-008 story points 已从 2 调整为 3。
- JIRA-010 已明确 11MB payload 对齐 10MB 上限并触发 1009 close。
- 推荐执行顺序已明确 P0 gateP0 全部改动和冒烟验证完成后,再启动 P1 改造。
---
## 5. 不在本文档维护的内容
- 不维护 Jira ticket 正文;统一在 `docs/internals/agent-comm-fix-jira-tasks.md` 修改。
- 不维护业务代码实现方案;实现时按具体 ticket 读取对应文件。
- 不维护历史中间稿;旧执行清单已合并进 Jira Task 文档。

View File

@@ -1,210 +0,0 @@
---
title: "Ant 特权世界 - Anthropic 员工专属功能"
description: "完整记录 Claude Code 身份门控层USER_TYPE === 'ant' 时解锁的专属工具、命令、API 和代号体系,揭示内外部构建的差异。"
keywords: ["Ant 特权", "USER_TYPE", "身份门控", "内部功能", "Anthropic 员工"]
---
{/* 本章目标完整记录身份门控层——ant 构建独享的一切 */}
## 什么是 Ant
`USER_TYPE` 是一个构建时常量,通过 Bun 打包器的 `--define` 注入。在 Anthropic 的内部构建中它被设为 `'ant'`,在公开发布的版本中是 `'external'`
```typescript
// 反编译版本src/types/global.d.ts 第 63 行)
// Build-time constants BUILD_TARGET/BUILD_ENV/INTERFACE_TYPE — removed (zero runtime usage)
```
`BUILD_TARGET` 等构建时常量在反编译版本中已被移除。`USER_TYPE` 通过 Bun 的 `--define` 或环境变量注入Bun 会进行**常量折叠**——所有 `process.env.USER_TYPE === 'ant'` 在外部构建中直接变为 `false`,后续代码被 DCE 移除。但在反编译版本中,这些代码保留完整。
`USER_TYPE === 'ant'` 在代码库中出现 **351+ 次**(跨 163 个文件),另有 `!== 'ant'` 59 次(跨 38 个文件),总计 **410+ 处引用**控制着工具、命令、API、UI 等方方面面。
## Ant-Only 工具
以下工具仅在内部构建中被加载到工具注册表:
| 工具 | 代码位置 | 用途 |
|------|---------|------|
| **REPLTool** | `packages/builtin-tools/src/tools/REPLTool/` | 高级 REPL 模式——在 VM 中包装 Bash/Read/Edit/Glob/Grep/Agent 等工具 |
| **SuggestBackgroundPRTool** | `packages/builtin-tools/src/tools/SuggestBackgroundPRTool/` | 建议在后台创建 PR |
| **ConfigTool** | `packages/builtin-tools/src/tools/ConfigTool/` | 交互式配置编辑器,包含 Gates 标签页用于覆盖 GrowthBook flags |
| **TungstenTool** | `packages/builtin-tools/src/tools/TungstenTool/` | 基于 tmux 的终端面板工具(反编译版中已 stub |
```typescript
// src/tools.ts 第 14-24 行——条件导入 + Dead Code Elimination 标记
// Dead code elimination: conditional import for ant-only tools
/* eslint-disable custom-rules/no-process-env-top-level, @typescript-eslint/no-require-imports */
const REPLTool =
process.env.USER_TYPE === 'ant'
? require('@claude-code-best/builtin-tools/tools/REPLTool/REPLTool.js').REPLTool
: null
const SuggestBackgroundPRTool =
process.env.USER_TYPE === 'ant'
? require('@claude-code-best/builtin-tools/tools/SuggestBackgroundPRTool/SuggestBackgroundPRTool.js')
.SuggestBackgroundPRTool
: null
```
## Ant-Only 命令
`src/commands.ts` 注册了 **24+** 个仅在内部构建中可用的斜杠命令(`INTERNAL_ONLY_COMMANDS`lines 267-295在 `USER_TYPE === 'ant' && !IS_DEMO` 时才加载line 400-401
<AccordionGroup>
<Accordion title="调试类">
- `breakCache` — 清除缓存
- `ctx_viz` — 可视化上下文窗口使用情况
- `debugToolCall` — 调试工具调用
- `env` — 显示环境变量
- `mockLimits` — 模拟速率限制
- `resetLimits` — 重置速率限制
- `resetLimitsNonInteractive` — 重置速率限制(非交互式)
</Accordion>
<Accordion title="实验类">
- `bughunter` — Bug 猎人模式
- `goodClaude` — 质量评估工具
- `antTrace` — 追踪分析
- `perfIssue` — 性能问题诊断
</Accordion>
<Accordion title="工作流类">
- `commit` — 快速提交
- `commitPushPr` — 一键提交+推送+创建 PR
- `issue` — 创建 GitHub Issue
- `autofixPr` — 自动修复 PR 中的问题
- `share` — 分享会话
- `summary` — 生成摘要
- `subscribePr` — 订阅 PR需要 `KAIROS_GITHUB_WEBHOOKS` feature flag
- `forceSnip` — 强制截断历史(需要 `HISTORY_SNIP` feature flag
- `ultraplan` — 超级规划(需要 `ULTRAPLAN` feature flag单独注册于 `commands.ts:396`
</Accordion>
<Accordion title="基础设施类">
- `backfillSessions` — 回填会话数据
- `bridgeKick` — 重启 Bridge 连接
- `oauthRefresh` — 刷新 OAuth Token
- `teleport` — 传送到指定上下文
- `onboarding` — 新手引导
- `agentsPlatform` — Agents 平台管理
- `version` — 内部版本详情
- `initVerifiers` — 初始化验证器
</Accordion>
</AccordionGroup>
<Note>
这些命令在 `IS_DEMO` 模式下也会被隐藏,防止在演示环境中暴露内部功能。
</Note>
## Beta API Headers
Claude Code 向 API 发送的 beta headers 分布在 `src/constants/betas.ts`(主注册表)和其他文件中,按可见性分为以下几类:
### 公开 Headers所有构建均发送
| Header | 功能 | 额外条件 |
|--------|------|----------|
| `claude-code-20250219` | Claude Code 标识 | 非 Haiku 时始终发送Haiku 在 agentic 模式下也发送 |
| `effort-2025-11-24` | 推理强度控制 | 动态注入 |
| `task-budgets-2026-03-13` | 任务预算 | 始终通过 `addAgenticBetas()` 注入 |
| `fast-mode-2026-02-01` | 快速模式 | 通过 sticky-on latch 动态注入 |
| `advisor-tool-2026-03-01` | 顾问工具 | 启用 advisor 时动态注入 |
| `advanced-tool-use-2025-11-20` | 工具搜索1P | Claude API / Foundry |
| `tool-search-tool-2025-10-19` | 工具搜索3P | Vertex / Bedrock |
### 模型能力相关(有条件发送)
| Header | 功能 | 条件 |
|--------|------|------|
| `interleaved-thinking-2025-05-14` | 交错思考模式 | 模型支持 ISP 且未禁用 |
| `context-1m-2025-08-07` | 1M 上下文窗口 | 模型支持 1M context |
| `context-management-2025-06-27` | 上下文管理 | Claude 4+ 或 ant 手动启用 |
| `structured-outputs-2025-12-15` | 结构化输出 | Claude 4.5/4.6 + GrowthBook `tengu_tool_pear` |
| `web-search-2025-03-05` | 网页搜索 | Vertex (Claude 4+) / Foundry |
| `redact-thinking-2026-02-12` | 思维摘要/脱敏 | ISP 模型 + 非交互 + 未强制显示思维 |
| `prompt-caching-scope-2026-01-05` | 提示缓存作用域 | firstParty/foundry + 全局缓存 |
### Ant-Only Headers
| Header | 功能 | 条件 |
|--------|------|------|
| **`cli-internal-2026-02-09`** | 内部 CLI 功能 | `USER_TYPE === 'ant'` + CLI 入口 |
| **`token-efficient-tools-2026-03-28`** | Token 高效工具 | `USER_TYPE === 'ant'` + GrowthBook `tengu_amber_json_tools` |
### Feature Flag Gated
| Header | 功能 | 条件 |
|--------|------|------|
| **`afk-mode-2026-01-31`** | AFK 模式(离开键盘自动审批) | `feature('TRANSCRIPT_CLASSIFIER')` |
### 其他特殊 Headers
| Header | 功能 | 来源 |
|--------|------|------|
| `oauth-2025-04-20` | OAuth 订阅者标识 | `src/constants/oauth.ts`Pro/Max/Team/Enterprise |
| `environments-2025-11-01` | Bridge 环境 API | `src/bridge/bridgeApi.ts`,仅 Bridge 模式 |
```typescript
// src/constants/betas.ts — 常量定义
export const TOKEN_EFFICIENT_TOOLS_BETA_HEADER =
'token-efficient-tools-2026-03-28'
export const CLI_INTERNAL_BETA_HEADER =
process.env.USER_TYPE === 'ant' ? 'cli-internal-2026-02-09' : ''
```
```typescript
// src/utils/betas.ts 第 315-321 行——TOKEN_EFFICIENT_TOOLS 的实际门控逻辑
if (
process.env.USER_TYPE === 'ant' &&
includeFirstPartyOnlyBetas &&
tokenEfficientToolsEnabled // GrowthBook 'tengu_amber_json_tools' flag
) {
betaHeaders.push(TOKEN_EFFICIENT_TOOLS_BETA_HEADER)
}
```
`cli-internal` header 意味着 Anthropic 的 API 服务端也维护着一套 ant-only 的服务端行为——这不仅仅是客户端的门控。`token-efficient-tools` 进一步需要 GrowthBook flag 开启,说明 Ant 员工内部也有分层灰度。
## 内部代号体系
Anthropic 有浓厚的"动物命名"文化:
| 代号 | 身份 | 出处 |
|------|------|------|
| **Tengu**(天狗) | Claude Code 项目代号 | 所有 GrowthBook flags 的 `tengu_` 前缀、分析事件名称 |
| **Capybara**(水豚) | 模型代号 | `src/constants/prompts.ts` 中被 Undercover Mode 屏蔽的名称 |
| **Fennec**(耳廓狐) | 已退役模型别名 | `src/migrations/migrateFennecToOpus.ts`——曾用名已迁移到 Opus |
这些代号通过 Undercover Mode 在公开仓库的 commit 中被严格过滤。
## 环境变量开关
除了 `USER_TYPE`,还有一系列精细的环境变量控制各项功能:
<AccordionGroup>
<Accordion title="功能禁用开关">
- `CLAUDE_CODE_SIMPLE` — 简化模式(禁用高级功能)
- `CLAUDE_CODE_DISABLE_THINKING` — 禁用 thinking
- `DISABLE_INTERLEAVED_THINKING` — 禁用交错思考
- `DISABLE_COMPACT` — 禁用消息压缩
- `DISABLE_AUTO_COMPACT` — 禁用自动压缩
- `CLAUDE_CODE_DISABLE_AUTO_MEMORY` — 禁用自动记忆
- `CLAUDE_CODE_DISABLE_BACKGROUND_TASKS` — 禁用后台任务
- `CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS` — 禁用实验性 beta headers
- `USE_API_CONTEXT_MANAGEMENT` — 上下文管理工具清除(需 ant
</Accordion>
<Accordion title="功能启用开关">
- `CLAUDE_CODE_VERIFY_PLAN` — 启用 VerifyPlanExecutionTool
- `ENABLE_LSP_TOOL` — 启用 LSP 语言服务器工具
- `CLAUDE_CODE_UNDERCOVER` — 强制启用 Undercover Mode
- `CLAUDE_CODE_TERMINAL_RECORDING` — 启用终端录制asciicast
- `CLAUDE_CODE_ABLATION_BASELINE` — 启用基线对照模式
</Accordion>
<Accordion title="环境配置">
- `CLAUDE_CODE_REMOTE` — 远程执行模式(自动增加堆内存限制)
- `CLAUDE_CODE_COORDINATOR_MODE` — 启用 Coordinator 模式
- `CLAUDE_INTERNAL_FC_OVERRIDES` — GrowthBook flag 覆盖ant-only
- `IS_DEMO` — 演示模式(隐藏内部命令和敏感信息)
- `CLAUDE_CODE_ENTRYPOINT` — 入口类型标识(`cli` | 其他)
</Accordion>
</AccordionGroup>
<Note>
`ABLATION_BASELINE` 特别有趣——它同时关闭 thinking、compaction、auto-memory 和 background tasks用于测量这些高级功能对 AI 表现的**因果影响**。这是一个严肃的"科学对照实验"工具。
</Note>

View File

@@ -1,314 +0,0 @@
# Autonomy Reliability Jira Drafts
These tickets are based on the call-chain audit of `/autonomy`, proactive
ticks, HEARTBEAT managed flows, cron scheduling, command queue consumption,
and daemon process supervision.
## AUT-001: Preserve autonomy lifecycle when queued commands are consumed mid-turn
Type: Bug
Priority: P0
Status: Draft
Patch status: Implemented in `fix/autonomy-lifecycle`.
Problem:
`query.ts` can drain queued prompt/task-notification commands as attachments
during an active turn. Autonomy prompts consumed this way were removed from the
in-memory queue without marking the persisted run as running/completed/failed,
so managed flows could stay stuck in `queued` and never advance.
Evidence:
- `src/query.ts` drains queued commands via `getCommandsByMaxPriority()`.
- `src/query.ts` removes consumed commands from the queue.
- Lifecycle updates existed only in the normal queued-submit path
`src/utils/handlePromptSubmit.ts` and headless `src/cli/print.ts`.
Acceptance criteria:
- Mid-turn consumed autonomy commands mark runs `running`.
- Normal query completion finalizes consumed runs and queues next managed-flow
steps.
- Query errors or abort terminal reasons mark consumed runs failed.
- Stale/cancelled autonomy commands are removed from the in-memory queue
without being sent to the model.
- Regression tests cover stale command filtering and managed-flow advancement.
## AUT-002: Make autonomy run lifecycle transitions terminal-safe
Type: Bug
Priority: P0
Status: Draft
Patch status: Implemented in `fix/autonomy-lifecycle`.
Problem:
Run lifecycle helpers rewrote status unconditionally. A stale in-memory command
could mark a cancelled/completed/failed run back to `running`, causing a
cancelled flow to execute or a terminal flow to be rewritten.
Evidence:
- `markAutonomyRunRunning`, `markAutonomyRunCompleted`,
`markAutonomyRunFailed`, and `markAutonomyRunCancelled` updated records
without checking current status.
- External CLI cancel cannot remove queued commands living inside another
process, so stale commands are a realistic input.
Acceptance criteria:
- `queued -> running/completed/failed/cancelled` remains allowed.
- `running -> completed/failed/cancelled` remains allowed.
- Any terminal status rejects later lifecycle updates.
- Rejected transitions do not update managed-flow step state.
- Regression tests cover stale lifecycle calls after cancellation.
## AUT-003: Prevent proactive and scheduled-task async fire failures from becoming invisible
Type: Bug
Priority: P1
Status: Draft
Patch status: Implemented in `fix/autonomy-lifecycle`.
Problem:
Proactive tick and cron fire callbacks launch detached async work. Failures in
prompt preparation or queue insertion could surface as unhandled rejections or
be lost from diagnostics. In one-shot cron paths, the scheduler has already
decided the task fired.
Evidence:
- `src/proactive/useProactive.ts` used a detached async IIFE without catch.
- `src/cli/print.ts` proactive and cron paths also detached async work.
- `src/hooks/useScheduledTasks.ts` cron callbacks detached async work.
Acceptance criteria:
- Detached proactive/cron fire work has explicit error logging.
- REPL proactive tick generation is non-reentrant.
- Tick generation stops queueing after hook unmount.
## AUT-004: Bound long-running daemon restart timers during shutdown
Type: Bug
Priority: P1
Status: Draft
Patch status: Implemented in `fix/autonomy-lifecycle`.
Problem:
The daemon supervisor scheduled worker restarts with `setTimeout()` but did
not store, clear, or `unref()` the timer. Shutdown during backoff could keep
the supervisor alive until the timer fired, forcing the stop path toward
SIGKILL.
Evidence:
- `src/daemon/main.ts` scheduled restart timers directly in the worker exit
handler.
- Shutdown only signaled child processes and did not clear restart timers.
Acceptance criteria:
- Worker restart timers are tracked per worker.
- Shutdown clears any pending restart timers.
- Restart and force-kill grace timers do not keep the supervisor alive alone.
## AUT-005: Release autonomy persistence lock bookkeeping after each chain
Type: Bug
Priority: P1
Status: Draft
Patch status: Implemented in `fix/autonomy-lifecycle`.
Problem:
`withAutonomyPersistenceLock` stored a chained promise in its map but compared
the map value against the raw current promise during cleanup. That condition
never matched, so root-level lock bookkeeping could accumulate in long-lived
processes that touch many workspaces.
Evidence:
- `src/utils/autonomyPersistence.ts` stored `previous.then(() => current)`.
- Cleanup compared `persistenceLocks.get(key) === current`.
Acceptance criteria:
- The stored chained promise is the value used for cleanup comparison.
- Existing serialization behavior for same-root calls remains unchanged.
- Tests directly assert same-root lock bookkeeping returns to zero after both
success and failure.
## AUT-006: Add active-record protection before persistence truncation
Type: Reliability
Priority: P2
Status: Draft
Patch status: Implemented in `fix/autonomy-lifecycle`.
Problem:
Autonomy runs and flows are capped by latest-created/updated order only.
Under high churn, active `queued` or `running` records can be truncated before
completion, which removes recovery evidence and can break managed-flow
advancement.
Evidence:
- `src/utils/autonomyRuns.ts` keeps the latest 200 runs by `createdAt`.
- `src/utils/autonomyFlows.ts` keeps the latest 100 flows by `updatedAt`.
Acceptance criteria:
- Active records are retained before completed historical records are trimmed.
- Tests cover trimming with more than the configured cap and active records
near the tail.
## AUT-007: Treat provider API-error responses as failed autonomy turns
Type: Bug
Priority: P0
Status: Draft
Patch status: Implemented in `fix/autonomy-lifecycle`.
Problem:
Third-party provider adapters can convert provider failures into synthetic
assistant API-error messages instead of throwing. `query.ts` treated
`isApiErrorMessage` terminal responses as `completed`, so an autonomy command
that had already been consumed as a queued attachment could be marked
completed and advance its managed flow even though the provider call failed.
Evidence:
- `src/services/api/openai/index.ts`, `src/services/api/gemini/index.ts`, and
`src/services/api/grok/index.ts` yield `createAssistantAPIErrorMessage()` on
adapter errors.
- `src/query.ts` skipped stop hooks for API-error assistant messages but
returned `reason: 'completed'`.
- Top-level autonomy finalization used terminal completion to decide whether
to mark consumed runs completed or failed.
Acceptance criteria:
- Provider API-error assistant messages terminate the query with
`reason: 'model_error'`.
- Any consumed autonomy run is marked failed rather than completed.
- Managed flows do not advance to the next step after provider API errors.
- A regression test simulates provider error after a queued autonomy attachment
has been consumed.
## AUT-008: Finalize consumed autonomy runs on async-generator close
Type: Bug
Priority: P0
Status: Draft
Patch status: Implemented in `fix/autonomy-lifecycle`.
Problem:
`query()` is an async generator. When its consumer calls `.return()` or breaks
out of iteration, JavaScript executes `finally` blocks and skips code after the
`try/finally`. The previous autonomy finalization ran after the `finally`, so
queued autonomy commands that had already been claimed as `running` could stay
persisted as `running` forever if the REPL/SDK consumer closed the generator.
Evidence:
- Claimed run IDs were collected during queued attachment injection.
- Completion/failure finalization happened only after `yield* queryLoop(...)`
returned normally or threw.
- Claude cross-validation flagged this as a durable run/flow leak.
Acceptance criteria:
- Consumed autonomy runs are finalized from a `finally` path.
- Normal completion marks consumed runs completed and enqueues next managed
flow steps.
- Provider/model errors mark consumed runs failed.
- Generator close and user abort terminals mark consumed runs cancelled.
- A regression test closes the generator after a queued autonomy attachment and
verifies the run/flow are cancelled, not left running.
## AUT-009: Claim queued autonomy runs before attachment injection
Type: Bug
Priority: P0
Status: Draft
Patch status: Implemented in `fix/autonomy-lifecycle`.
Problem:
The query loop filtered stale queued autonomy commands before attachment
generation, but it did not claim runs as `running` until after attachments were
already yielded. A concurrent cancellation between those steps could still send
a cancelled prompt into the model context.
Evidence:
- `partitionConsumableQueuedAutonomyCommands()` only checked persisted status.
- `markAutonomyRunRunning()` previously ran after `getAttachmentMessages()`.
- Reviewer cross-validation identified the check-then-act race.
Acceptance criteria:
- Query claims queued autonomy runs before passing commands to attachment
generation.
- Only successfully claimed commands are injected as queued-command
attachments.
- Failed claims are treated as stale and removed from the in-memory queue.
- Claiming reads persisted run state once per turn rather than once per
command.
## AUT-010: Cancel proactive and cron runs dropped before enqueue
Type: Bug
Priority: P1
Status: Draft
Patch status: Implemented in `fix/autonomy-lifecycle`.
Problem:
`/proactive` and scheduled-task producers persist autonomy runs before
returning queue commands. If the component is disposed or headless input closes
after persistence but before enqueue, the queued run is left on disk with no
in-memory command to consume it.
Evidence:
- `createProactiveAutonomyCommands()` commits runs before returning commands.
- `commitAutonomyQueuedPrompt()` persists scheduled-task runs before callers
enqueue them.
- Callers checked `disposed` / `inputClosed` after command creation and could
return without terminalizing the run.
Acceptance criteria:
- Proactive hook cancellation checks run both before commit and after command
creation.
- Headless proactive and cron paths cancel any already-created command that is
dropped due to input close.
- REPL scheduled-task cleanup cancels already-created commands when unmounted.
- A regression test verifies a proactive command created but dropped before
enqueue is marked cancelled.
## AUT-011: Replace query transition `any` stubs with typed contracts
Type: Test/Type Safety
Priority: P2
Status: Draft
Patch status: Implemented in `fix/autonomy-lifecycle`.
Problem:
`src/query/transitions.ts` defined both `Terminal` and `Continue` as `any`.
That allowed new terminal reasons such as `model_error` and continuation
reasons such as `collapse_drain_retry` to drift without compiler checks.
Evidence:
- Claude cross-validation flagged the `Terminal = any` contract as a remaining
issue.
- Tightening the type immediately caught that
`collapse_drain_retry.committed` is a `number`, not a `boolean`.
Acceptance criteria:
- `Terminal` is a concrete union of query terminal reasons.
- `Continue` is a concrete union of continuation reasons and payloads.
- `bun run typecheck` validates all query return sites against that contract.
## AUT-012: Avoid provider test settings-module mock pollution
Type: Test Reliability
Priority: P2
Status: Draft
Patch status: Implemented in `fix/autonomy-lifecycle`.
Problem:
The provider tests previously mocked `settings.js`. A minimal mock broke other
tests that imported additional settings exports in the same Bun process; the
expanded mock avoided the failure but over-coupled the provider test to
unrelated settings internals.
Evidence:
- Full test runs observed cross-file settings mock pollution.
- `src/utils/model/providers.ts` only needs the real `getInitialSettings()`
behavior.
Acceptance criteria:
- Provider tests do not mock `settings.js`.
- `modelType` precedence is exercised through an injected settings snapshot,
leaving global bootstrap state untouched.
- Provider tests pass when run alongside permissions tests and the provider
matrix.

View File

@@ -1,117 +0,0 @@
---
title: "88 个 Feature Flags - 构建时特性门控全解"
description: "深入剖析 Claude Code 的 88+ 个构建时 feature flagsbun:bundle 编译时门控机制,揭示被编译器删除的隐藏功能模块。"
keywords: ["feature flags", "特性标志", "构建时门控", "bun:bundle", "条件编译"]
---
{/* 本章目标:完整梳理构建时 feature flag 系统的机制和所有 flag 的分类 */}
## feature() 是什么
Claude Code 使用 Bun 打包器的 `bun:bundle` 模块提供编译时特性门控:
```typescript
// 源码中的用法src/tools.ts 等)
import { feature } from 'bun:bundle'
const SleepTool = feature('PROACTIVE') || feature('KAIROS')
? require('@claude-code-best/builtin-tools/tools/SleepTool/SleepTool.js').SleepTool
: null
```
在 Anthropic 的内部构建中,`feature()` 在打包时被求值——返回 `true` 的代码会被保留,返回 `false` 的代码会被 **Dead Code Elimination (DCE)** 彻底移除。
在我们的反编译版本中,`feature` 从 `bun:bundle` 导入(声明在 `src/types/internal-modules.d.ts`),在运行时始终返回 `false`
```typescript
// src/types/internal-modules.d.ts
declare module 'bun:bundle' {
export function feature(name: string): boolean;
}
```
这意味着所有 88+ 个 feature flag 后的代码**在运行时永远不会执行**,但代码本身完整保留,可以阅读和分析。
## Flags 分类全景
<CardGroup cols={2}>
<Card title="Agent / 自动化" icon="robot">
**15 个 flags** — 控制 AI 的自主能力边界
`KAIROS` · `KAIROS_BRIEF` · `KAIROS_CHANNELS` · `KAIROS_DREAM` · `KAIROS_GITHUB_WEBHOOKS` · `KAIROS_PUSH_NOTIFICATION` · `PROACTIVE` · `COORDINATOR_MODE` · `FORK_SUBAGENT` · `AGENT_MEMORY_SNAPSHOT` · `AGENT_TRIGGERS` · `AGENT_TRIGGERS_REMOTE` · `VERIFICATION_AGENT` · `BUILTIN_EXPLORE_PLAN_AGENTS` · `MONITOR_TOOL`
</Card>
<Card title="基础设施" icon="server">
**10 个 flags** — 控制运行环境和连接方式
`DAEMON` · `BG_SESSIONS` · `BRIDGE_MODE` · `CCR_AUTO_CONNECT` · `CCR_MIRROR` · `CCR_REMOTE_SETUP` · `DIRECT_CONNECT` · `SSH_REMOTE` · `SELF_HOSTED_RUNNER` · `BYOC_ENVIRONMENT_RUNNER`
</Card>
<Card title="安全 / 分类" icon="shield-halved">
**6 个 flags** — 增强权限判断的智能性
`TRANSCRIPT_CLASSIFIER` · `BASH_CLASSIFIER` · `TREE_SITTER_BASH` · `TREE_SITTER_BASH_SHADOW` · `NATIVE_CLIENT_ATTESTATION` · `ABLATION_BASELINE`
</Card>
<Card title="工具 / 能力" icon="toolbox">
**10 个 flags** — 新增的 AI 能力
`WEB_BROWSER_TOOL` · `TERMINAL_PANEL` · `CONTEXT_COLLAPSE` · `HISTORY_SNIP` · `OVERFLOW_TEST_TOOL` · `WORKFLOW_SCRIPTS` · `VOICE_MODE` · `MCP_RICH_OUTPUT` · `MCP_SKILLS` · `UDS_INBOX`
</Card>
<Card title="UI / 体验" icon="palette">
**8 个 flags** — 界面和交互改进
`MESSAGE_ACTIONS` · `QUICK_SEARCH` · `HISTORY_PICKER` · `AUTO_THEME` · `STREAMLINED_OUTPUT` · `COMPACTION_REMINDERS` · `TEMPLATES` · `BUDDY`
</Card>
<Card title="平台 / 实验" icon="flask-vial">
**10+ 个 flags** — 实验性和平台级功能
`DUMP_SYSTEM_PROMPT` · `UPLOAD_USER_SETTINGS` · `DOWNLOAD_USER_SETTINGS` · `EXPERIMENTAL_SKILL_SEARCH` · `ULTRAPLAN` · `ULTRATHINK` · `TORCH` · `LODESTONE` · `PERFETTO_TRACING` · `SLOW_OPERATION_LOGGING` · `HARD_FAIL` · `ALLOW_TEST_VERSIONS`
</Card>
</CardGroup>
## 代码中的典型模式
Feature flags 在代码中主要有三种使用模式:
### 模式一:条件加载工具
```typescript
// src/tools.ts — 最常见的模式
const MonitorTool = feature('MONITOR_TOOL')
? require('@claude-code-best/builtin-tools/tools/MonitorTool/MonitorTool.js').MonitorTool
: null
```
当 flag 为 `false` 时,`require()` 调用被 DCE 移除,工具不会出现在可用工具列表中。
### 模式二:条件注册命令
```typescript
// src/commands.ts — 注册斜杠命令
if (feature('VOICE_MODE')) {
commands.push({ name: 'voice', description: '...' })
}
```
### 模式三:条件启用 API 特性
```typescript
// src/constants/betas.ts — 控制发送给 API 的 beta header
export const AFK_MODE_BETA_HEADER = feature('TRANSCRIPT_CLASSIFIER')
? 'afk-mode-2026-01-31'
: ''
```
<Note>
由于 `feature()` 在构建时求值,被 DCE 移除的代码不会增加最终打包体积。但在反编译版本中,这些代码全部保留——这正是我们能够进行完整分析的原因。
</Note>
## 有趣的发现
- **KAIROS 家族**最庞大——6 个相关 flag 控制从核心功能到推送通知的方方面面
- **ABLATION_BASELINE** 是用于"科学对照实验"的——它会关闭 thinking、compaction、auto-memory 等高级功能,测量裸 API 调用的基线性能
- **BUDDY** 是一个 AI 吉祥物/精灵系统——在 `src/buddy/` 目录下有完整实现
- **ULTRAPLAN** 和 **ULTRATHINK** 暗示着比当前 extended thinking 更高级的推理模式

View File

@@ -1,120 +0,0 @@
---
title: "GrowthBook A/B 测试体系 - 运行时功能发布"
description: "揭秘 Claude Code 如何通过 GrowthBook 实现运行时 A/B 测试用户定向、tengu 命名文化和渐进式功能发布策略。"
keywords: ["GrowthBook", "A/B 测试", "运行时门控", "tengu", "渐进式发布"]
---
{/* 本章目标:深入运行时 A/B 测试层——GrowthBook 的集成架构、用户定向、tengu 命名文化 */}
## 为什么需要运行时 A/B 测试
构建时 `feature()` 是"全有或全无"的——要么所有用户都有,要么所有用户都没有。但产品团队需要更精细的控制:
- 只对 5% 的用户灰度发布新功能
- 按订阅类型Free / Pro / Team差异化体验
- 对特定组织静默开启实验性能力
- 随时远程关闭出问题的功能,无需发版
这就是 **GrowthBook** 的用武之地——一个运行时的、基于用户属性的功能门控和 A/B 测试系统。
## 集成架构
GrowthBook 的完整实现位于 `src/services/analytics/growthbook.ts`1258 行),工作流程如下:
<Steps>
<Step title="启动时获取远程配置">
CLI 启动时GrowthBook SDK 通过 `https://api.anthropic.com/` 的 API 端点获取当前的功能配置和实验分组规则。使用 `remoteEval: true` 模式——在服务端计算分组,客户端只拿结果。
</Step>
<Step title="计算用户属性">
SDK 收集当前用户的属性(设备 ID、订阅类型、组织 UUID 等),用于决定该用户属于哪些实验的哪个分组。
</Step>
<Step title="缓存到本地">
计算结果缓存到 `~/.claude.json` 的 `cachedGrowthBookFeatures` 字段。刷新间隔Anthropic 员工 20 分钟,外部用户 6 小时。
</Step>
<Step title="代码中查询 flag">
业务代码通过 `tengu_*` 前缀的 flag 名查询功能状态GrowthBook SDK 返回当前用户的分组值。
</Step>
</Steps>
## 用户定向属性
GrowthBook 根据以下用户属性决定实验分组:
| 属性 | 类型 | 来源 | 用途 |
|------|------|------|------|
| `id` | string | 会话 ID | 按会话粒度分组 |
| `deviceID` | string | 持久化设备标识 | 跨会话一致性 |
| `sessionId` | string | 当前会话 ID | 会话级实验 |
| `platform` | enum | `process.platform` | 按操作系统差异化 |
| `organizationUUID` | string | API 认证信息 | 按组织灰度 |
| `accountUUID` | string | API 认证信息 | 按个人账户灰度 |
| `subscriptionType` | string | API 认证信息 | Free / Pro / Team 差异化 |
| `rateLimitTier` | string | API 认证信息 | 按速率限制层级 |
| `email` | string | API 认证信息 | 精确定向特定用户 |
| `appVersion` | string | `MACRO.VERSION` | 按版本号灰度 |
| `github` | object | GitHub Actions 元数据 | CI 环境特殊处理 |
<Note>
这套定向系统意味着 Anthropic 可以做非常精细的实验——比如"只对 Mac 上的 Pro 订阅用户的 10% 开启新功能"。
</Note>
## 代号文化tengu_* 的世界
所有运行时 flag 都以 `tengu_` 为前缀——"Tengu"(天狗)是 Claude Code 的内部项目代号。flag 名采用**动物/植物/矿物 + 形容词**的命名约定,刻意保持不透明。
<AccordionGroup>
<Accordion title="tengu_kairos — Kairos 助手模式">
控制 KAIROS 功能的运行时开关。即使构建时 `feature('KAIROS')` 通过,仍需此 flag 命中才能激活。双重门控确保新功能可以分阶段发布。
</Accordion>
<Accordion title="tengu_amber_stoat — Explore Agent A/B 测试">
控制内置的 Explore 子 Agent 的行为变体。"amber stoat"(琥珀色白鼬)是随机生成的代号,与功能内容无关——这是为了防止通过 flag 名猜测功能。
</Accordion>
<Accordion title="tengu_auto_background_agents — 后台 Agent 自动化">
控制是否自动将某些任务分派给后台 Agent 执行,而不是在前台阻塞用户。
</Accordion>
<Accordion title="tengu_onyx_plover — Auto-Dream 后台记忆">
控制"自动做梦"功能——在空闲时后台整理和巩固 Agent 的记忆。"onyx plover"(玛瑙鸻)又是一个不透明代号。
</Accordion>
<Accordion title="tengu_glacier_2xr — 工具搜索行为">
控制 Tool Search 的行为变体,可能是搜索算法或排序策略的 A/B 测试。
</Accordion>
<Accordion title="tengu_birch_trellis — Bash 权限策略">
控制 BashTool 权限判断的策略变体——可能在测试更宽松或更严格的权限规则。
</Accordion>
<Accordion title="tengu_scratch — 草稿本功能">
控制一个实验性的"草稿本"功能,可能是让 AI 在处理复杂任务时使用中间暂存区。
</Accordion>
<Accordion title="tengu_quartz_lantern — Diff 计算策略">
控制文件写入和编辑时的 diff 计算方式。可能在 A/B 测试不同的 diff 算法对用户体验的影响。
</Accordion>
</AccordionGroup>
## Ant-Only 覆盖机制
Anthropic 员工拥有两种方式绕过 GrowthBook 的远程求值:
### 环境变量覆盖
```bash
# 仅在 USER_TYPE=ant 的构建中生效
CLAUDE_INTERNAL_FC_OVERRIDES='{"tengu_kairos": true}' claude
```
通过 `CLAUDE_INTERNAL_FC_OVERRIDES` 环境变量传入 JSON 对象,直接覆盖任意 flag 的值。
### Config 界面覆盖
在内部构建中,`/config` 命令的 Gates 标签页提供了图形化的 flag 管理界面,可以实时切换任意 GrowthBook flag。
## 实验追踪
GrowthBook 集成了完整的实验曝光追踪:
- 每次查询 flag 时记录实验曝光事件
- 通过 protobuf 格式的 `GrowthbookExperimentEvent` 上报
- 包含 `variation_id`0=对照组1+=实验组)和 `in_experiment` 标记
- 数据用于分析功能对用户行为的因果影响
<Note>
GrowthBook 正在从 Statsig 迁移而来——代码中仍保留着 `checkStatsigFeatureGate_CACHED_MAY_BE_STALE()` 这样的迁移兼容层。
</Note>

View File

@@ -1,133 +0,0 @@
---
title: "未公开功能巡礼 - 8 个隐藏功能深度解析"
description: "深度解析 Claude Code 中 8 个最令人兴奋的隐藏功能:从永不下线的 AI 助手到 AI 吉祥物,揭示 88+ flags 中最具代表性的未公开特性。"
keywords: ["隐藏功能", "未公开功能", "秘密功能", "Claude Code 彩蛋", "AI 助手"]
---
{/* 本章目标:逐一展示 8 个最重要的隐藏功能,分析它们背后的产品方向 */}
## 全景
从 88+ 个构建时 flags 和 500+ 个运行时 flags 中,我们挑选了 8 个最具代表性的未公开功能。它们不仅展示了 Claude Code 当前的技术深度,更勾勒出 Anthropic 对"AI 编程助手"的未来愿景。
<AccordionGroup>
<Accordion title="KAIROS永不下线的 AI 助手">
**门控**: `feature('KAIROS')` + `tengu_kairos`
KAIROS 是 Claude Code 最庞大的隐藏功能群——6 个独立 flag 控制着一个完整的"持久化 AI 助手"系统:
| Flag | 能力 |
|------|------|
| `KAIROS` | 核心助手模式——AI 不再随对话结束而"消失" |
| `KAIROS_BRIEF` | 精简输出模式 |
| `KAIROS_CHANNELS` | 基于频道的消息系统 |
| `KAIROS_DREAM` | 后台"做梦"——自主整理记忆 |
| `KAIROS_GITHUB_WEBHOOKS` | 订阅 GitHub PR 事件,自动响应 |
| `KAIROS_PUSH_NOTIFICATION` | 向移动端推送通知 |
KAIROS 的工具集包括 `SleepTool`(让 AI 主动"休眠"等待事件)、`SendUserFileTool`(向用户发送文件)、`PushNotificationTool`(推送通知)和 `SubscribePRTool`(监听 PR
**推测方向**: 一个 7x24 在线的 AI 团队成员,能自主监控代码库、响应事件、管理任务。
</Accordion>
<Accordion title="PROACTIVE自主行动模式">
**门控**: `feature('PROACTIVE')`
在标准模式中Claude Code 是被动的——等待你输入然后响应。PROACTIVE 模式颠覆了这一范式:
- AI 拥有 `SleepTool`,可以主动"打盹"一段时间
- 系统定期发送 `<tick>` 提示,触发 AI 检查是否有需要主动做的事
- AI 可以在没有用户输入的情况下自行决策和执行
**推测方向**: 从"问答式助手"进化为"自主式同事"——AI 在后台持续工作,偶尔需要你确认方向。
</Accordion>
<Accordion title="COORDINATOR_MODE多 Agent 指挥官">
**门控**: `feature('COORDINATOR_MODE')`
当前的 Claude Code 已经支持子 Agent`AgentTool`),但 Coordinator Mode 将其提升到新的层次:
- 一个"指挥官" Agent 分析任务并分解为子任务
- 多个"工人" Agent 并行执行子任务
- 指挥官协调结果、处理冲突、合并输出
完整实现位于 `src/coordinator/coordinatorMode.ts`。
**推测方向**: 大型编程任务的全自动并行处理——比如"重构整个认证系统"可以同时由多个 Agent 处理不同模块。
</Accordion>
<Accordion title="BRIDGE_MODE远程遥控">
**门控**: `feature('BRIDGE_MODE')`
Bridge Mode 让 Claude Code 可以通过 WebSocket 被远程控制:
- `src/bridge/` 目录包含完整的 WebSocket 桥接实现
- 支持 IDE 扩展作为远程前端
- 包含 ant-only 的故障注入测试(`bridgeDebug.ts`
- 配合 `DIRECT_CONNECT` flag 可通过 `cc://` URL 直连
**推测方向**: Claude Code 的 UI 前端与后端执行分离——你可以在 VS Code 中操作,但 AI 在远程服务器上执行。
</Accordion>
<Accordion title="WEB_BROWSER_TOOL内置浏览器">
**门控**: `feature('WEB_BROWSER_TOOL')`
当前的 Claude Code 只有简化的 `WebFetchTool`(获取网页内容),但代码中存在更强大的浏览器工具:
- 基于 Bun 的 WebView 实现
- 可以渲染和交互网页,而不仅仅是抓取文本
- 与 Computer Use 的 `@ant/` 包配合使用
**推测方向**: AI 能像人一样浏览网页——点击、填表、截图,用于测试 Web 应用或收集信息。
</Accordion>
<Accordion title="VOICE_MODE语音交互">
**门控**: `feature('VOICE_MODE')`
代码中存在语音输入模式的注册点,核心实现依赖 `audio-capture-napi` 包(已恢复):
- 通过 `/voice` 命令激活
- "按住说话"hold-to-talk交互模式
- 需要系统级音频 API 支持
**推测方向**: 不用打字,直接和 AI 对话编程。
</Accordion>
<Accordion title="BUDDYAI 吉祥物">
**门控**: `feature('BUDDY')`
`src/buddy/` 目录包含一个完整的"伙伴精灵"系统:
- 终端中的小型动画角色
- 可能根据 AI 的状态(思考中、执行中、完成)展示不同动画
- 纯 UI/趣味性功能
**推测方向**: 给冷冰冰的终端增加一点温度——让等待 AI 思考的过程不那么无聊。
</Accordion>
<Accordion title="Undercover Mode隐身贡献">
**门控**: `USER_TYPE === 'ant'`(自动激活)
这不是一个功能,而是一个**安全机制**——当 Anthropic 员工向公开仓库贡献代码时自动激活:
- 剥除所有 AI 归属标记(`Co-Authored-By` 行)
- 禁止在 commit 消息中提及模型代号Capybara、Tengu 等)
- 禁止暴露内部仓库名、Slack 频道、短链接
- 通过 `CLAUDE_CODE_UNDERCOVER=1` 强制开启,无法强制关闭
- 仅在仓库匹配内部白名单(~25 个私有仓库)时自动关闭
**意义**: 证实 Anthropic 员工确实在使用 Claude Code 进行日常开发,并且会向公开项目贡献代码。
</Accordion>
</AccordionGroup>
## 这些功能告诉我们什么
纵观这 8 个隐藏功能,一个清晰的产品愿景浮现:
1. **从被动到主动** — PROACTIVE、KAIROS 让 AI 不再只是等待指令
2. **从短暂到持久** — KAIROS 的持久化模式让 AI 成为"常驻团队成员"
3. **从单一到多感官** — VOICE_MODE、WEB_BROWSER_TOOL 拓展交互维度
4. **从单兵到协同** — COORDINATOR_MODE 让多个 AI 并行协作
5. **从本地到分布式** — BRIDGE_MODE、SSH_REMOTE 解耦前后端
Claude Code 正在从一个"终端里的聊天机器人"进化为一个**自主、持久、多模态的 AI 编程同事**。

View File

@@ -1,828 +0,0 @@
# JSONL Transcript 会话持久化与恢复机制
本文梳理 Claude Code 基于 JSONL transcript 的会话持久化、恢复、错误恢复、上下文压缩、分支、subagent、fork agent 和 remote agent 逻辑。
这不是按文件罗列的源码笔记,而是一份机制手册:先建立心智模型,再看数据结构、生命周期、异常路径和源码入口。
## 怎么读
| 如果你想看 | 建议先读 |
|---|---|
| 为什么 resume 能恢复到正确位置 | `总览``读取与链路重建``恢复入口` |
| 为什么 compact 后历史还在但模型看不到 | `上下文视图``Compact 与投影` |
| 为什么 subagent 不污染主会话 | `存储拓扑``Subagent 与 Fork Agent` |
| `/branch``--fork-session``/fork` 有什么区别 | `分支与 Fork 对比` |
| 崩溃、超限、取消后如何恢复 | `错误恢复矩阵` |
## 总览
Claude Code 的本地会话核心是 append-only JSONL。每一行是一个 `Entry`,但恢复时不会按文件顺序重放整个文件,而是:
1. 把 transcript message 放入 `uuid -> message` map。
2. 把 metadata entry 放入各自 map 或数组。
3. 选择最新 leaf。
4. 从 leaf 沿 `parentUuid` 回溯,得到当前有效链。
5. 应用 compact、snip、preserved segment、content replacement 等投影。
6. 恢复 sessionId、worktree、mode、agent setting、任务状态等内存状态。
核心不变量:
| 不变量 | 含义 |
|---|---|
| JSONL 尽量 append-only | compact、branch、sidechain 都优先追加新 entry不直接改旧历史。 |
| `uuid/parentUuid` 决定世界线 | 文件顺序只说明写入顺序,真正恢复靠链路回溯。 |
| metadata 不参与主链 | title、tag、worktree、content replacement 等通过 sessionId/messageId/agentId 合并。 |
| compact 不删除历史 | 它追加 boundary模型视图从最后一个 boundary 后开始。 |
| subagent 是 sidechain | 子 agent 的完整对话在独立 JSONL父会话只看到 Agent tool 的结果/通知。 |
| remote agent 不是 sidechain | remote agent 本地只保存 sidecar 身份,执行状态来自 CCR。 |
### 系统分层
```mermaid
flowchart TD
A[磁盘层<br/>append-only JSONL + sidecar metadata] --> B[链路层<br/>uuid / parentUuid / leaf]
B --> C[投影层<br/>compact / snip / tool_result budget / context-collapse]
C --> D[恢复层<br/>deserialize / interrupt detection / metadata restore]
D --> E[运行层<br/>REPL / QueryEngine / AgentTask / RemoteTask]
```
### 存储拓扑
```text
~/.claude/projects/<project-key>/
<sessionId>.jsonl
<sessionId>/
subagents/
agent-<agentId>.jsonl
agent-<agentId>.meta.json
<subdir>/
agent-<agentId>.jsonl
agent-<agentId>.meta.json
remote-agents/
remote-agent-<taskId>.meta.json
```
| 文件 | 生成函数 | 用途 |
|---|---|---|
| `<sessionId>.jsonl` | `getTranscriptPath()` | 主会话 transcript。 |
| `subagents/agent-<agentId>.jsonl` | `getAgentTranscriptPath(agentId)` | 本地 subagent / fork agent sidechain。 |
| `subagents/agent-<agentId>.meta.json` | `getAgentMetadataPath(agentId)` | agentType、worktreePath、description。 |
| `remote-agents/remote-agent-<taskId>.meta.json` | `getRemoteAgentMetadataPath(taskId)` | remote CCR session 身份,用于恢复 polling。 |
## 核心源码地图
| 机制 | 主要文件 |
|---|---|
| Entry 类型 | `src/types/logs.ts` |
| 路径、写入、读取、链路重建 | `src/utils/sessionStorage.ts` |
| 大文件流式读取 | `src/utils/sessionStoragePortable.ts` |
| CLI resume 加载和中断检测 | `src/utils/conversationRecovery.ts` |
| session 切换和状态恢复 | `src/utils/sessionRestore.ts` |
| SDK/headless query 写 transcript | `src/QueryEngine.ts` |
| API query loop、compact、错误恢复 | `src/query.ts` |
| compact 实现 | `src/services/compact/*` |
| context-collapse stub 与持久化接口 | `src/services/contextCollapse/*` |
| `/branch` | `src/commands/branch/branch.ts` |
| `/fork` | `src/commands/fork/fork.tsx` |
| AgentTool 和 subagent | `packages/builtin-tools/src/tools/AgentTool/*` |
| 通用 forked side query | `src/utils/forkedAgent.ts` |
| remote agent task | `src/tasks/RemoteAgentTask/RemoteAgentTask.tsx` |
## 数据模型
`Entry` 定义在 `src/types/logs.ts`,可以分为三大类。
| 类别 | 典型 type | 是否进入 `parentUuid` 链 | key | 恢复用途 |
|---|---|---:|---|---|
| transcript message | `user``assistant``attachment``system` | 是 | `uuid` | 重建对话链、模型上下文、UI scrollback。 |
| session metadata | `custom-title``tag``mode``worktree-state``pr-link``agent-setting` | 否 | `sessionId` | 恢复标题、标签、模式、worktree、PR、agent 设置。 |
| message metadata | `file-history-snapshot``attribution-snapshot``summary` | 否 | `messageId``leafUuid` | 恢复文件历史、归因、摘要。 |
| replacement metadata | `content-replacement` | 否 | `sessionId` + optional `agentId` | 恢复大 tool_result 的替换决策。 |
| context-collapse metadata | `marble-origami-commit``marble-origami-snapshot` | 否 | `sessionId` | 预留 context-collapse 恢复接口;当前实现为 stub。 |
| queue/task metadata | `queue-operation``task-summary``speculation-accept` | 否 | 各自字段 | 恢复队列、任务摘要、推测接受统计。 |
### TranscriptMessage 字段
真正参与链路的是 `TranscriptMessage`
| 字段 | 含义 |
|---|---|
| `uuid` | 当前消息 ID。 |
| `parentUuid` | 链路父节点,恢复时沿它回溯。 |
| `logicalParentUuid` | compact boundary 等断链场景保留逻辑父节点。 |
| `sessionId` | 所属主 session。 |
| `cwd` | 写入时工作目录。 |
| `timestamp` | 写入时间。 |
| `version` | CLI 版本。 |
| `gitBranch` | 写入时 git 分支。 |
| `isSidechain` | 是否是 subagent sidechain。 |
| `agentId` | sidechain 所属 agent。 |
| `teamName/agentName/agentColor` | swarm / teammate 展示元数据。 |
### JSONL 示例
主会话消息:
```jsonl
{"type":"user","uuid":"u1","parentUuid":null,"sessionId":"s1","isSidechain":false,"cwd":"D:\\vibe\\claude-code","message":{"role":"user","content":"修复测试"}}
{"type":"assistant","uuid":"a1","parentUuid":"u1","sessionId":"s1","isSidechain":false,"message":{"role":"assistant","content":[{"type":"text","text":"我来检查。"}]}}
```
sidechain 消息:
```jsonl
{"type":"user","uuid":"u2","parentUuid":null,"sessionId":"s1","isSidechain":true,"agentId":"ag1","message":{"role":"user","content":"分析 compact 路径"}}
```
agent 的 `content-replacement`
```jsonl
{"type":"content-replacement","sessionId":"s1","agentId":"ag1","replacements":[{"messageUuid":"u2","toolUseId":"toolu_...","blockIndex":0,"kind":"persisted"}]}
```
compact boundary
```jsonl
{"type":"system","subtype":"compact_boundary","uuid":"b1","parentUuid":"a9","logicalParentUuid":"a9","sessionId":"s1","compactMetadata":{"trigger":"auto","preTokens":182000,"messagesSummarized":94}}
```
## 写入生命周期
### 总流程
```mermaid
sequenceDiagram
participant User
participant QE as QueryEngine
participant SS as sessionStorage.Project
participant FS as JSONL
participant API as query()/API
User->>QE: ask(messages)
QE->>SS: recordTranscript(user messages)
SS->>SS: clean + dedup + insertMessageChain
SS->>SS: appendEntry / enqueueWrite
SS-->>FS: drain queue append JSONL
QE->>API: start query loop
API-->>QE: assistant/user/system compact_boundary
QE->>SS: recordTranscript(streamed messages)
QE->>SS: flushSessionStorage before result when needed
```
关键点:
| 设计 | 为什么 |
|---|---|
| 用户输入先写 transcript再进 API | 进程在 API 前崩溃时resume 仍能看到用户 prompt。 |
| assistant streaming 写入多为 fire-and-forget | 不阻塞 token streaming。 |
| result 前按需 flush | 避免 SDK/桌面端拿到 result 后立即杀进程导致尾部丢失。 |
| `progress` 不参与链路 | 高频 progress tick 不应该制造分叉或膨胀 transcript。 |
### 主会话写入
入口:`recordTranscript(messages, teamInfo?, startingParentUuidHint?, allMessages?)`
流程:
1. `cleanMessagesForLogging()` 过滤 UI-only 或不应持久化的消息。
2. `getSessionMessages(sessionId)` 读取当前 session 已有 UUID set。
3. 对未写过的消息调用 `insertMessageChain()`
4. `insertMessageChain()``parentUuid/sessionId/cwd/timestamp/version/gitBranch/isSidechain`
5. `appendEntry()` 进入 per-file queue。
去重不是简单丢弃所有重复:如果 prefix 中某些消息已写过,写入器会推进 `startingParentUuid`,确保后续新消息接在正确父节点后。
### 写队列、materialize 和 flush
`Project` 内部维护 per-file queue
| 机制 | 细节 |
|---|---|
| `writeQueues` | `Map<filePath, entry[]>`,按文件聚合写入。 |
| drain timer | 默认 100msCCR/remote persistence 场景约 10ms。 |
| queue 上限 | 单队列超过 1000 条会丢弃最老 queued entry 并 resolve防止内存无限增长。 |
| chunk 上限 | 单次 JSONL append chunk 约 100MB。 |
| `flushSessionStorage()` | 取消 timer等待 active drain 和 tracked writes。 |
`sessionFile` 初始为 `null`。这时 title、tag、mode、worktree 等 metadata 先存在内存或 `pendingEntries` 中。第一次出现 `user``assistant` 时,`materializeSessionFile()` 才创建 session 文件,然后:
1. 写入缓存 metadata。
2. 回放 pending entries。
3. 之后所有 entry 正常 append。
这样可以避免“只打开 CLI 没说话”也产生 metadata-only session污染 `/resume` 列表。
### sidechain 写入
subagent 使用 `recordSidechainTranscript(messages, agentId, startingParentUuid?)`
它底层仍走 `insertMessageChain()`,但写入字段不同:
```ts
isSidechain: true
agentId: agentId
```
`appendEntry()` 遇到 `isSidechain && agentId` 的 transcript message会把它路由到
```text
<project>/<sessionId>/subagents/agent-<agentId>.jsonl
```
如果 `content-replacement``agentId`,也会路由到该 agent 的 sidechain JSONL而不是主 session JSONL。
一个很重要的例外sidechain 写入不会用主 session UUID set 做去重。fork agent 会复用父会话消息 UUID 来继承上下文;如果按主 session 去重,会把继承上下文从 sidechain 中误删,导致 agent resume 时只剩子 prompt。
## 读取与链路重建
### 从 JSONL 到有效链
```mermaid
flowchart TD
A[loadTranscriptFile(file)] --> B[readTranscriptForLoad<br/>大文件按 chunk 读]
B --> C[parseJSONL Entry]
C --> D[messages Map uuid->TranscriptMessage]
C --> E[metadata maps/arrays]
D --> F[progress bridge / preserved relink / snip removal]
F --> G[select leaf]
G --> H[buildConversationChain]
H --> I[recoverOrphanedParallelToolResults]
I --> J[LogOption or agent transcript]
```
`loadTranscriptFile(filePath, opts?)` 产出:
| 输出 | 用途 |
|---|---|
| `messages` | `uuid -> TranscriptMessage`。 |
| `leafUuids` | 候选 leaf。 |
| title/tag/mode/worktree/PR maps | session metadata。 |
| `fileHistorySnapshots` / `attributionSnapshots` | 文件状态恢复。 |
| `contentReplacements` | 主线程 replacement records。 |
| `agentContentReplacements` | `agentId -> replacement records`。 |
| `contextCollapseCommits` / `contextCollapseSnapshot` | context-collapse 恢复输入。 |
### leaf 与 parent 链
`buildConversationChain(messages, leaf)`
1. 从 leaf 开始。
2. 读取 `parentUuid`
3. 找到父消息并继续回溯。
4. 检测 parent cycle避免无限循环。
5. reverse 成正序 transcript。
6. 补回并行 tool_use 形成的 DAG 分支。
一个简化例子:
```text
u1 <- a1 <- u2 <- a2
^
leaf
恢复链: a2 -> u2 -> a1 -> u1
正序链: u1, a1, u2, a2
```
文件顺序不等于有效链。branch、rewind、streaming fallback 都可能让 JSONL 里有死分支;恢复只选择当前 leaf 所在世界线。
### metadata 合并规则
| metadata | 合并方式 | 说明 |
|---|---|---|
| `custom-title``tag``mode``worktree-state``pr-link``agent-setting` | sessionId keyed通常 last-wins | 恢复最新 session 状态。 |
| `file-history-snapshot``attribution-snapshot` | messageId keyed / array | 恢复文件历史与归因。 |
| `content-replacement` | append array | 多轮 replacement 决策都要保留。 |
| `agentContentReplacements` | agentId keyed + append array | agent resume 重建 sidechain replacement state。 |
| `marble-origami-commit` | ordered array | 顺序有语义,后一个 commit 可能引用前一个 summary。 |
| `marble-origami-snapshot` | last-wins | staged snapshot 只恢复最新状态。 |
### 大文件读取优化
transcript 可增长到几百 MB 甚至 GB读取路径有几层防护。
| 优化 | 位置 | 目的 |
|---|---|---|
| chunk 读取 | `readTranscriptForLoad()` | 避免一次性读爆内存。 |
| fd 层跳过大 metadata | `readTranscriptForLoad()` | `attribution-snapshot` 等大 entry 不进入 buffer。 |
| compact 前缀跳过 | `readTranscriptForLoad()` | 遇到非 preserved compact boundary 后,只保留 boundary 后内容。 |
| pre-boundary metadata scan | `scanPreBoundaryMetadata()` | compact 前被跳过时,仍保留 title/tag/mode/worktree/PR 等展示信息。 |
| byte-level dead branch 裁剪 | `walkChainBeforeParse()` | JSON.parse 前只拼 active chain 和 metadata跳过 dead fork/rewind branch。 |
| lite read 限制 | `MAX_TRANSCRIPT_READ_BYTES` | 直接读 raw transcript 的调用超过约 50MB 要避开。 |
`walkChainBeforeParse()` 只有预计能丢掉至少一半 buffer 时才做 concat避免优化本身变成额外成本。
### preserved segment 与 snip
compact boundary 可以带 `compactMetadata.preservedSegment`。恢复时 `applyPreservedSegmentRelinks()` 会:
1. 验证 `tailUuid -> headUuid` 链是否完整。
2. 把 preserved segment 的 head 接到 compact anchor 后。
3. 把 anchor 的其他 children 接到 preserved tail。
4. 删除最后一个 boundary 前且不属于 preserved segment 的旧消息。
5. 清零 preserved assistant 的 usage避免恢复后马上又触发 autocompact。
示意:
```text
compact 前: old... -> anchor -> head -> ... -> tail -> next
compact 后: boundary/summary -> head -> ... -> tail -> next
```
`snip` 和 compact 不同compact 截断前缀snip 删除中段。JSONL 不能真的删除旧行,所以 `applySnipRemovals()` 在内存 map 中删除 `removedUuids`,再把 dangling `parentUuid` 重连到最近未删除祖先。
### 旧链路修复
| 问题 | 修复 |
|---|---|
| legacy `progress` 曾进入 parent 链 | `progressBridge` 把指向 progress 的 parent 改回 progress 的真实父节点。 |
| parent cycle | `buildConversationChain()` 检测 cycle记录并返回 partial chain。 |
| 并行 tool_use 形成 DAG | `recoverOrphanedParallelToolResults()` 按 assistant `message.id` 和 tool_result parent 关系补回 sibling。 |
| streaming fallback 孤儿尾巴 | tombstone 触发 `removeTranscriptMessage(uuid)` 删除失败 attempt。 |
## 恢复入口
### 入口矩阵
| 入口 | 加载源 | 是否复用原 sessionId | 是否 adopt 原 JSONL | 特点 |
|---|---|---:|---:|---|
| `--continue` | 当前目录最近 session | 是 | 是 | 跳过仍 live 的 bg/daemon 非 interactive session。 |
| `--resume <uuid>` | 指定 session | 是 | 是 | 也支持 custom title / 搜索词 / picker。 |
| `--resume <jsonl>` | 指定 JSONL 文件 | 是 | 是 | Ant 内部/print path 支持。 |
| `--fork-session` + resume | 旧 session messages | 否 | 否 | 保持新 sessionId把旧消息作为新 session 初始内容。 |
| `--resume-session-at <message.id>` | print/headless resume | 取决于 resume | 取决于 resume | 截断到指定 assistant message。 |
| REPL `/resume` | picker / log option | 是或 fork | 是或否 | 会跑 SessionEnd/SessionStart hooks切换 UI state。 |
### CLI resume 流程
```mermaid
flowchart TD
A[main.tsx --continue/--resume] --> B[loadConversationForResume]
B --> C[load log or transcript]
C --> D[deserializeMessagesWithInterruptDetection]
D --> E[processSessionStartHooks]
E --> F[processResumedConversation]
F --> G{fork session?}
G -- no --> H[switchSession + adoptResumedSessionFile]
G -- yes --> I[keep fresh sessionId + seed content replacement]
H --> J[restore mode/worktree/agent/context-collapse/cost]
I --> J
J --> K[start REPL or print]
```
核心函数:
| 函数 | 责任 |
|---|---|
| `loadConversationForResume()` | 统一加载最近 session、sessionId、LogOption 或 JSONL path补 lite log复制 plan/file history做 consistency check反序列化和中断检测返回 metadata。 |
| `processResumedConversation()` | CLI interactive 启动恢复;切换或 fork session恢复 cost、worktree、mode、agent setting、context-collapse、attribution。 |
| `restoreSessionStateFromLog()` | 恢复 AppState 侧状态file history、attribution、context-collapse、TodoWrite todos。 |
### REPL `/resume`
REPL 内 resume 比 CLI 启动路径多了“从当前 session 切换到另一个 session”的工作
1. 清理目标 log messages。
2. 当前 session 跑 SessionEnd hooks。
3. 目标 session 跑 SessionStart resume hooks。
4. 保存当前 session cost恢复目标 session cost。
5. `switchSession(sessionId, dirname(fullPath))` 原子切换 sessionId + project dir。
6. `resetSessionFilePointer()` 并恢复 metadata cache。
7. 非 fork 时退出上一次 worktree恢复目标 worktree`adoptResumedSessionFile()`
8. fork 时不接管原 transcript不退出当前 worktree。
9. 重建 content replacement state。
10. 恢复 remote/local task 状态。
11. 替换 messages、清 tool JSX、清输入框。
### 中断检测矩阵
`deserializeMessagesWithInterruptDetection()` 会先清理历史消息:
| 清理 | 目的 |
|---|---|
| legacy attachment 迁移 | 兼容旧 transcript。 |
| 非法 `permissionMode` 删除 | 防止跨 build 的无效枚举进入运行态。 |
| unresolved tool_use 过滤 | 避免 API 报 tool_use/tool_result 不配对。 |
| orphaned thinking-only assistant 过滤 | 避免中断 streaming 留下孤儿 thinking block。 |
| whitespace-only assistant 过滤 | 避免取消时留下空白 assistant。 |
然后看最后一个 turn-relevant message
| 最后有效消息 | 结果 | 额外动作 |
|---|---|---|
| assistant | `none` | streaming 持久化里 stop_reason 常为 null不能靠它判断未完成。 |
| 普通 user | `interrupted_prompt` | 插入 `NO_RESPONSE_REQUESTED` sentinel 保持 API-valid。 |
| meta user / compact summary user | `none` | 不把内部控制消息当用户新请求。 |
| tool_result user | 通常 `interrupted_turn` | 例外Brief/SendUserMessage/SendUserFile terminal tool_result 视为完成。 |
| attachment | `interrupted_turn` | 追加 meta user`Continue from where you left off.` |
| system/progress/API error assistant | 跳过 | 不作为 turn 完成判断依据。 |
`interrupted_turn` 会统一转换为 `interrupted_prompt`,让上层只处理一种“需要续跑”的状态。
## 错误恢复矩阵
| 场景 | 处理策略 | transcript 影响 |
|---|---|---|
| API 前进程崩溃 | 用户 prompt 已由 `QueryEngine.ask()` 先写入。 | resume 看到普通 user触发 `interrupted_prompt`。 |
| streaming fallback 产生孤儿 assistant | yield tombstoneREPL 移除 UI message 并调用 `removeTranscriptMessage(uuid)`。 | 优先只改 JSONL 尾部 64KB大文件目标不在尾部时跳过慢 rewrite。 |
| prompt-too-long / media-too-large | streaming 阶段先 withheld先 context-collapse drain再 reactive compact失败才暴露错误。 | compact 成功则写 boundary/summary 并重试;失败才写 API error message。 |
| max_output_tokens | 先提高 max output override仍失败则注入内部 recovery prompt 续写;耗尽才暴露错误。 | 内部 retry prompt 不一定成为普通 transcript取决于是否 yield 到外层。 |
| auto compact 关闭但到 blocking limit | 直接 yield prompt-too-long 风格 API error。 | 保留用户手动 `/compact` 空间。 |
| abort during streaming/tools | 补齐缺失 tool_result必要时 yield user interruption message。 | `reason === interrupt` 时跳过 interruption message因为后续 queued user message 已提供上下文。 |
| stop hook blocking | 把 hook blocking error 加入 state 后重试。 | 有 reactive compact guard避免 hook/error/compact 无限循环。 |
| compact boundary 指向未落盘 tail | QueryEngine 写 boundary 前强制补写 preserved tail 前的消息。 | 避免恢复时 boundary 引用不存在 UUID。 |
| subagent transcript 尾部不完整 | `resumeAgentBackground()` 再次过滤 unresolved tool_use、orphan thinking、空白 assistant。 | 避免恢复 agent 后 API 请求非法。 |
## 上下文视图
同一份消息在系统里有四种视图,不要混在一起:
| 视图 | 内容 | 谁使用 |
|---|---|---|
| Raw transcript | JSONL 中所有 entry包括旧历史、dead branch、metadata、sidechain。 | 磁盘持久化和审计。 |
| UI scrollback | REPL 当前展示的消息,可能保留 compact 前历史和 collapsed UI group。 | 终端 UI。 |
| Active query view | `getMessagesAfterCompactBoundary()` 后的消息,默认再投影 snip。 | `query.ts` 上下文管理。 |
| API wire view | `normalizeMessagesForAPI()` 后,过滤 system boundary、修复 tool pairing、插入 cache edits。 | Anthropic/OpenAI/Gemini 等 API client。 |
每轮 query 的 active context 顺序:
1. `getMessagesAfterCompactBoundary(messages)`:取最近 compact boundary 之后的 active slice默认叠加 snip 投影。
2. 删除旧 `toolUseResult` 原始 payload只保留 API 需要的 `message.content`
3. `applyToolResultBudget()`:过大的 tool_result 替换为 preview/stub并写 `content-replacement`
4. `snipCompactIfNeeded()``HISTORY_SNIP` 下删除中段历史。
5. `microcompactMessages()`time-based microcompact再 cached microcompact。
6. `contextCollapse.applyCollapsesIfNeeded()`:当前为 identity stub。
7. `autoCompactIfNeeded()`:主动 compact优先 session memory compact。
8. predictive autocompactAPI 前估算本 turn 增长,必要时提前 compact。
9. API 真实超限后context-collapse drain再 reactive compact。
## Compact 与投影
### Compact 类型对比
| 类型 | 触发 | 摘要来源 | 是否调用 compact API | 是否保留尾段 | 失败策略 |
|---|---|---|---:|---:|---|
| manual compact | `/compact` | compact summary API 或 session memory | 取决于路径 | 取决于 full/partial/SM | 显示失败或回退传统 compact。 |
| auto compact | token 阈值 | 先 session memory后 summary API | 取决于路径 | 取决于路径 | 连续失败 circuit breaker默认 3 次后停止自动 compact。 |
| predictive compact | API 前估算增长 | 同 auto compact | 取决于路径 | 取决于路径 | 失败则继续原请求或走后续错误恢复。 |
| reactive compact | API 真实 413/media error 后 | `compactConversation()` | 是 | 当前 wrapper 取决于 compact 实现 | `hasAttemptedReactiveCompact` 防循环。 |
| session memory compact | manual/auto 前置尝试 | session memory 文件 | 否 | 是 | 若 post-compact 仍超阈值,放弃并回退传统 compact。 |
| microcompact | time/cached 小型压缩 | 局部清理或 API cache edit | 不一定 | 不适用 | 通常不改变 JSONL 主历史。 |
| snip | `HISTORY_SNIP` | 删除中段 | 否 | 保留前后上下文 | 通过 snip metadata 投影,不物理删旧行。 |
### Compact 结果形态
传统 compact 会生成:
1. `compact_boundary` system message。
2. compact summary user message。
3. post-compact attachments例如当前文件、计划模式、技能、MCP/tool schema delta、hook 结果。
简化 before/after
```text
Raw/UI:
u1, a1, u2, a2, ... u99, a99,
system:compact_boundary,
user:compact summary,
attachment:current files,
u100
Active query view:
system:compact_boundary,
user:compact summary,
attachment:current files,
u100
API wire view:
user:compact summary,
attachment/content,
u100
```
boundary 本身是 system message最后会被 API normalization 过滤;它的价值主要在本地投影、恢复和统计。
### Boundary metadata
`createCompactBoundaryMessage()` 写:
| 字段 | 含义 |
|---|---|
| `compactMetadata.trigger` | `manual``auto`。 |
| `compactMetadata.preTokens` | compact 前 token 数。 |
| `compactMetadata.userContext` | 用户手动 compact 的额外说明。 |
| `compactMetadata.messagesSummarized` | 被总结消息数量。 |
| `logicalParentUuid` | compact 前最后消息,用于逻辑追踪。 |
后续路径还会补:
| 字段 | 来源 | 作用 |
|---|---|---|
| `preCompactDiscoveredTools` | traditional/SM compact | 恢复 deferred tool schema 可见性。 |
| `preservedSegment.{headUuid,anchorUuid,tailUuid}` | partial/SM compact | 恢复时把保留尾段接到 boundary 后。 |
### Tool result budget 与 content replacement
大 tool_result 不一定直接进入后续上下文。`applyToolResultBudget()` 会按 API-level user message 聚合预算,必要时把大块内容持久化并替换成较小 preview/stub。
关键点:
| 点 | 说明 |
|---|---|
| replacement decision 会落 JSONL | `recordContentReplacement()``content-replacement`。 |
| 主线程和 agent 分开 | 无 `agentId` 写主 JSONL`agentId` 写 sidechain JSONL。 |
| resume 会重建 replacement state | 避免恢复后同一大结果又变回完整内容,导致 token 暴涨或 prompt cache 失配。 |
| `--fork-session` 会 seed records | fork 新 session 时复制 replacement 决策到新 session。 |
### Session memory compact
`sessionMemoryCompact.ts` 是传统 summary compact 前的实验路径。流程:
1. 等待 session memory extraction 完成。
2. 读取 session memory 文件。
3.`lastSummarizedMessageId` 时,从其后保留安全尾段;否则把 resumed session 视为已有 memory summary。
4. 调整切点,避免断开 tool_use/tool_result 或 thinking blocks。
5. 创建标准 `compact_boundary` + summary user message。
6. 若 post-compact token count 仍超过阈值,放弃并回退传统 compact。
因为产物仍是标准 `CompactionResult`,下游写 transcript 和恢复逻辑与传统 compact 共用。
### Context-collapse 当前状态
本仓库保留了 context-collapse 的持久化接口,但核心实现是 stub
| 模块 | 当前行为 |
|---|---|
| `contextCollapse/index.ts` | `applyCollapsesIfNeeded()` 返回原 messages`recoverFromOverflow()` 返回 committed=0`isWithheldPromptTooLong()` 恒 false。 |
| `contextCollapse/operations.ts` | `projectView()` 是 identity。 |
| `contextCollapse/persist.ts` | `restoreFromEntries()` 是 no-op。 |
已预留 JSONL entry
| Entry | 写入接口 | 内容 |
|---|---|---|
| `marble-origami-commit` | `recordContextCollapseCommit()` | `collapseId`、summary UUID/content、archived span 边界。 |
| `marble-origami-snapshot` | `recordContextCollapseSnapshot()` | staged spans、armed、lastSpawnTokens。 |
loader 会收集这些 entry遇到 compact boundary 时会清空旧 commits/snapshot避免它们引用已被 compact 丢弃的 UUID。
所以当前真实生效的上下文缩减主要是 compact、session memory compact、tool_result budget、microcompact 和 snipcontext-collapse 只是接口已接好。
### Compact 后清理
`runPostCompactCleanup(querySource)` 总是清:
- microcompact state。
- system prompt sections。
- classifier approvals。
- speculative bash checks。
- beta tracing。
- session messages memo cache。
- compact cleanup callbacks。
- `COMMIT_ATTRIBUTION` 下异步 sweep file-content cache。
只在主线程 compact 清:
- context-collapse store。
- `getUserContext` cache。
- memory files cache。
原因subagent 和主线程同进程,共享模块级状态。`agent:*` compact 如果清主线程 context-collapse 或 memory cache会破坏父会话状态。
它明确不清 `resetSentSkillNames()`,避免 compact 后重新注入完整 skill listing浪费 token 和 prompt cache。
## 分支与 Fork 对比
| 入口 | 本质 | 是否新主 session | 是否 subagent | 持久化位置 | 父会话看到什么 | 恢复方式 |
|---|---|---:|---:|---|---|---|
| `/branch` | 复制当前主 transcript 成新 JSONL | 是 | 否 | `<newSessionId>.jsonl` | 直接切到新分支会话 | 普通 session resume。 |
| `--fork-session` | resume/continue 时把旧消息作为新 session 初始消息 | 是 | 否 | 新 session 首次写入时 materialize | 启动即在新 session 中继续 | 新 session resume。 |
| `/fork <directive>` | slash wrapper调用 AgentTool fork | 否 | 是 | `subagents/agent-<id>.jsonl` + `.meta.json` | fork started + task notification | `resumeAgentBackground()`。 |
| `AgentTool({ fork: true })` | Tool 层 fork 子 agent | 否 | 是 | `subagents/agent-<id>.jsonl` + `.meta.json` | sync final tool_result 或 async notification | `resumeAgentBackground()`。 |
| 普通 AgentTool async | 后台本地 subagent | 否 | 是 | `subagents/agent-<id>.jsonl` + `.meta.json` | `async_launched` + task notification | `resumeAgentBackground()`。 |
| remote AgentTool | CCR remote session | 否 | 远端 | `remote-agents/*.meta.json` | remote task output/notification | `restoreRemoteAgentTasks()` + CCR。 |
### `/branch`
`/branch` 创建新 session 文件,不是在原 JSONL 里追加 branch marker。
流程:
1. 生成新的 sessionId。
2. 读取当前 transcript 文件。
3. 过滤主会话消息,排除 `isSidechain` 和非 transcript entry。
4. 复制消息并重写 `sessionId`
5. 重新串 `parentUuid`
6. 添加 `forkedFrom: { sessionId, messageUuid }`
7. 复制原 session 的 `content-replacement` entry 并改成新 sessionId。
8. 写入 `<newSessionId>.jsonl`
9. 构造 `LogOption` 并让 REPL resume 到新分支。
### `--fork-session`
`--fork-session` 只改变 resume 的 ownership
| 非 fork resume | fork-session resume |
|---|---|
| 切到旧 sessionId。 | 保持启动时 fresh sessionId。 |
| `adoptResumedSessionFile()` 接管旧 JSONL。 | 不接管旧 JSONL。 |
| 后续继续 append 到旧 transcript。 | 后续 materialize 成新 transcript。 |
| 原 session 继续增长。 | 原 session 不被写入。 |
如果旧 session 有 `content-replacement`,会先把 records seed 到新 session避免大 tool_result 的替换状态丢失。
## Subagent 与 Fork Agent
### 普通 subagent
普通 AgentTool subagent 最终走 `runAgent()`
```mermaid
sequenceDiagram
participant Parent as 父会话
participant Tool as AgentTool
participant Agent as runAgent
participant Side as sidechain JSONL
participant Task as LocalAgentTask
Parent->>Tool: assistant tool_use Agent
Tool->>Agent: start sync or async
Agent->>Side: record initialMessages
Agent->>Side: record assistant/user/progress/compact_boundary
alt sync foreground
Agent-->>Tool: final result
Tool-->>Parent: Agent tool_result
else async/background
Tool-->>Parent: async_launched tool_result
Agent-->>Task: complete
Task-->>Parent: <task-notification>
end
```
父会话通常只记录:
- Agent tool_use。
- Agent tool_result。
- async launch result。
- task notification。
- 必要 progress。
完整子 agent 内部工具调用和消息在 sidechain JSONL 中,不会混进主会话 active context。
### Fork agent
fork agent 是 AgentTool 的一种特殊 subagent。它继承父上下文、system prompt、tools、model 和 thinking config目标是让多个子 agent 共享尽可能长的 byte-identical prompt cache prefix。
关键实现:
| 继承内容 | 实现 |
|---|---|
| system prompt | 优先使用 `toolUseContext.renderedSystemPrompt`,没有才 fallback 重建。 |
| tools | 使用父 `toolUseContext.options.tools``useExactTools: true`。 |
| model | `FORK_AGENT.model = "inherit"`。 |
| thinking/non-interactive | 通过 exact tool/options 继承,避免 cache key 分叉。 |
| messages | `forkContextMessages = toolUseContext.messages`。 |
`buildForkedMessages()` 负责构造 cache-friendly 尾部:
```text
parent history...
assistant: [text/thinking/tool_use A/tool_use B/...]
user:
tool_result for A = "Fork started — processing in background"
tool_result for B = "Fork started — processing in background"
directive = "<this fork's task>"
```
多个 fork child 的长前缀相同,只有最后 directive 不同。
限制:
| 限制 | 原因 |
|---|---|
| 需要 `FORK_SUBAGENT` feature。 | 功能门控。 |
| coordinator mode 禁用。 | coordinator 已有自己的编排模型。 |
| non-interactive session 禁用。 | fork subagent 偏交互式后台任务模型。 |
| fork child 禁止递归 fork。 | 防止无限 fork通过 querySource 和 boilerplate tag 检测。 |
| resume fork agent 不再传 `forkContextMessages`。 | sidechain 已包含父上下文切片,重复传会造成重复 tool_use id。 |
### `runForkedAgent()` 不是 AgentTool fork
`src/utils/forkedAgent.ts``runForkedAgent()` 是内部 cache-safe side query 工具,用于 session memory、prompt suggestion、summary 等。它复用父 system/user/system context、tools、messages可选 `skipTranscript`,但默认不写 AgentTool metadata也不是用户可继续对话的 AgentTool fork。
## Agent 恢复
本地 agent 恢复入口是 `resumeAgentBackground()`
流程:
```mermaid
flowchart TD
A[user continues agent] --> B[getAgentTranscript(agentId)]
B --> C[load sidechain JSONL + build chain]
C --> D[readAgentMetadata(agentId)]
D --> E[filter unresolved tool_use/thinking/blank assistant]
E --> F[reconstruct content replacement state]
F --> G{metadata.worktreePath exists?}
G -- yes --> H[runWithCwdOverride(worktreePath)]
G -- no --> I[parent cwd]
H --> J[register async LocalAgentTask]
I --> J
J --> K[continue query loop]
```
恢复时:
| 状态 | 来源 |
|---|---|
| agent transcript | `agent-<agentId>.jsonl`。 |
| agent type | `agent-<agentId>.meta.json`。 |
| fork/general agent 选择 | metadata `agentType`。 |
| worktree cwd | metadata `worktreePath`,目录不存在则回退父 cwd。 |
| content replacement | sidechain records + parent live state gap-fill。 |
| task UI | 重新注册 async task。 |
## Remote Agent 恢复
remote CCR agent 不靠本地 sidechain 继续执行。
```mermaid
sequenceDiagram
participant Tool as AgentTool
participant R as RemoteAgentTask
participant Sidecar as remote-agents meta
participant CCR as CCR session
participant REPL as REPL resume
Tool->>CCR: teleportToRemote()
Tool->>R: registerRemoteAgentTask()
R->>Sidecar: write remote-agent-<taskId>.meta.json
REPL->>Sidecar: restoreRemoteAgentTasks()
REPL->>CCR: fetchSession(sessionId)
alt running
REPL->>R: rebuild RemoteAgentTaskState + polling
else 404/archive
REPL->>Sidecar: delete sidecar
end
```
差异:
| 本地 subagent | remote agent |
|---|---|
| 有完整 sidechain JSONL。 | 没有本地执行 transcript。 |
| resume 可继续 API 对话。 | resume 只恢复 polling。 |
| 状态来自 JSONL + `.meta.json`。 | 状态来自 CCR session + local sidecar。 |
| 完成后本地 sidechain 仍可审计。 | 完成/archived 后 sidecar 会删除。 |
## 常见误区
| 误区 | 正确理解 |
|---|---|
| JSONL 顺序就是会话顺序 | 恢复靠 leaf + `parentUuid`,不是简单顺序 replay。 |
| compact 删除了旧历史 | compact 追加 boundary旧历史仍在 raw transcript。 |
| boundary 会发给模型 | boundary 是本地 system markerAPI normalization 会过滤。 |
| `/branch``/fork` 都是 fork | `/branch` 是新主 session`/fork` 是 fork subagent sidechain。 |
| `--fork-session` 等于 `/branch` | 它不是复制文件命令,而是 resume 时保持 fresh session ownership。 |
| subagent 消息会进入主上下文 | 父会话只看到 Agent tool result/notification完整内部消息在 sidechain。 |
| remote agent 有本地 sidechain | remote 只有 sidecar 身份,执行状态来自 CCR。 |
| context-collapse 已经真实压缩上下文 | 当前仓库中 context-collapse 核心实现是 stub。 |
## 源码入口索引
| 问题 | 从这里看 |
|---|---|
| Entry union 有哪些类型 | `src/types/logs.ts``Entry`。 |
| 主 transcript 路径 | `src/utils/sessionStorage.ts``getTranscriptPath()`。 |
| subagent transcript 路径 | `getAgentTranscriptPath(agentId)`。 |
| remote sidecar 路径 | `getRemoteAgentsDir()` / `getRemoteAgentMetadataPath()`。 |
| 主写入 | `recordTranscript()`。 |
| sidechain 写入 | `recordSidechainTranscript()`。 |
| write queue | `Project.enqueueWrite()` / `drainWriteQueue()` / `flush()`。 |
| lazy materialize | `Project.materializeSessionFile()`。 |
| tombstone 删除 | `removeTranscriptMessage()` / `Project.removeMessageByUuid()`。 |
| 读取 transcript | `loadTranscriptFile()`。 |
| 大文件读取 | `readTranscriptForLoad()` in `sessionStoragePortable.ts`。 |
| dead branch 裁剪 | `walkChainBeforeParse()`。 |
| parent 链重建 | `buildConversationChain()`。 |
| parallel tool_result 补回 | `recoverOrphanedParallelToolResults()`。 |
| preserved segment | `applyPreservedSegmentRelinks()`。 |
| snip removal | `applySnipRemovals()`。 |
| CLI resume 加载 | `loadConversationForResume()`。 |
| resume 状态切换 | `processResumedConversation()`。 |
| AppState 恢复 | `restoreSessionStateFromLog()`。 |
| 中断检测 | `deserializeMessagesWithInterruptDetection()`。 |
| active context | `getMessagesAfterCompactBoundary()`。 |
| query context pipeline | `src/query.ts`。 |
| compact boundary | `createCompactBoundaryMessage()`。 |
| auto compact | `autoCompactIfNeeded()` / `shouldAutoCompact()`。 |
| session memory compact | `src/services/compact/sessionMemoryCompact.ts`。 |
| reactive compact | `src/services/compact/reactiveCompact.ts`。 |
| post compact cleanup | `runPostCompactCleanup()`。 |
| context-collapse stub | `src/services/contextCollapse/*`。 |
| `/branch` | `src/commands/branch/branch.ts`。 |
| `/fork` | `src/commands/fork/fork.tsx`。 |
| AgentTool fork | `AgentTool.tsx` + `forkSubagent.ts`。 |
| 普通 subagent 运行 | `runAgent.ts`。 |
| agent resume | `resumeAgent.ts`。 |
| remote task restore | `restoreRemoteAgentTasks()`。 |

View File

@@ -1,87 +0,0 @@
---
title: "三层门禁系统 - 功能可见性控制架构"
description: "详解 Claude Code 三层门禁系统:构建时 feature()、运行时 GrowthBook 和身份层 USER_TYPE如何控制功能的可见性和灰度发布。"
keywords: ["门禁系统", "功能门控", "feature flag", "灰度发布", "可见性控制"]
---
{/* 本章目标:建立对三层门禁系统的全局认知,为后续四篇深入文章奠定坐标系 */}
## 冰山一角
你日常使用的 Claude Code只是完整代码库的冰山一角。
逆向工程揭示了一个事实:大量功能被精心"藏"在三层独立的门禁系统之后。有些是正在 A/B 测试的实验性功能,有些是仅限 Anthropic 员工使用的内部工具,还有些是尚未对外发布的下一代能力。
> 我们在 `src/` 中发现了 88+ 个构建时 feature flags、500+ 个运行时 A/B 测试标记,以及一整套身份门控机制。
## 三层门禁全景
| 维度 | 第一层:构建时 `feature()` | 第二层:运行时 GrowthBook | 第三层:身份 `USER_TYPE` |
|------|---------------------------|--------------------------|-------------------------|
| **控制方式** | `bun:bundle` 编译时宏 | GrowthBook SDK 远程求值 | 构建时 `--define` 常量 |
| **决策时机** | 打包时(代码直接被删除) | 启动时 + 定期刷新 | 打包时(常量折叠) |
| **粒度** | 全有或全无 | 按用户/设备/组织定向 | 按构建版本ant / external |
| **标记数量** | 88+ | 500+ (`tengu_*` 前缀) | 1`ant` vs `external` |
| **逆向可见性** | 代码残留,但永远走 `false` 分支 | 完整 SDK 代码可读 | 条件分支清晰可见 |
## 决策流程
当一个功能请求进入 Claude Code它会依次经过三层门禁的检查
```
功能请求
┌─────────────────────────┐
│ 第一层feature('X') │ ──── 编译时已决定 ──→ false → 代码被 DCE 移除
│ (构建时 Feature Flag) │
└─────────┬───────────────┘
│ true (仅内部构建)
┌─────────────────────────┐
│ 第二层tengu_xxx │ ──── 运行时按用户属性 ──→ 不在实验组 → 功能关闭
│ (GrowthBook A/B 测试) │
└─────────┬───────────────┘
│ 在实验组
┌─────────────────────────┐
│ 第三层USER_TYPE │ ──── ant? external? ──→ external → 功能不可用
│ (身份门控) │
└─────────┬───────────────┘
│ ant
功能可用 ✓
```
三层门禁**相互独立**一个功能可能同时受多层控制。例如KAIROS 助手模式同时需要 `feature('KAIROS')` 构建时开启 **和** `tengu_kairos` 运行时实验命中。
## 逆向工程揭示了什么
在这个反编译版本中:
- **第一层**完全透明——`feature()` 被兜底为 `() => false`,所有 88+ 个 flag 的代码路径都可以阅读,只是永远不会执行
- **第二层**完整保留——GrowthBook SDK 的 1156 行代码完整可读,包括用户定向属性、缓存策略、覆盖机制
- **第三层**清晰可见——`process.env.USER_TYPE === 'ant'` 出现在 60+ 个位置,每一处都标记着"仅限内部"的功能边界
<Note>
这三层门禁不是安全机制——它们是产品发布策略。目的是让 Anthropic 能够在不同用户群体中渐进式地测试和发布功能,而不是阻止逆向工程。
</Note>
## 接下来
后续四篇文章将分别深入每一层门禁的细节:
<CardGroup cols={2}>
<Card title="88 面旗帜" icon="flag" href="/docs/internals/feature-flags">
构建时 Feature Flags 的完整分类与解读
</Card>
<Card title="千面千人" icon="flask" href="/docs/internals/growthbook-ab-testing">
GrowthBook A/B 测试体系的运作机制
</Card>
<Card title="未公开功能巡礼" icon="eye" href="/docs/internals/hidden-features">
KAIROS、PROACTIVE 等 8 大隐藏功能深度解析
</Card>
<Card title="Ant 的特权世界" icon="shield" href="/docs/internals/ant-only-world">
Anthropic 员工专属的工具、命令与 API
</Card>
</CardGroup>