From 3cb47795b4e529ea4faa419a2a7e295b6efdf765 Mon Sep 17 00:00:00 2001 From: Yao Lu Date: Wed, 27 May 2026 19:01:12 +0800 Subject: [PATCH 1/3] =?UTF-8?q?docs(s2):=20=E6=94=B6=E6=95=9B=E7=AB=A0?= =?UTF-8?q?=E8=8A=82=E8=BE=B9=E7=95=8C=E4=B8=8E=E6=A0=87=E9=A2=98=E9=94=99?= =?UTF-8?q?=E4=BD=8D=20=E2=80=94=20=E8=90=BD=E5=9C=B0=20OC-Dev/OC-PM=20?= =?UTF-8?q?=E6=96=B9=E6=A1=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 跨 10 章修复 OC-Dev 在 YAO-148 中盘点的章节边界 / "数字承诺" 错位问题, 全部改动均与 /Users/yao/work/code/awesome-project/claude-code-cli 源码核对。 - 03: §五点五 → §六(PolicyLimits),§五点六 → §七(SettingsSync), §六–§九 → §八–§十一;新增章内导读区分 5 SettingSource 与 2 旁路 service - 05: 新增"阅读地图(三层结构)"块,QueryEngine/query.ts/query/* 各自定位 - 06: 新增 §五 Output Style(注入点、frontmatter、合并优先级、与第 30 章分工) - 07: 新增 §零·6 条 compact 链路速查表 - 11: §2.3.4 14 → 23 个 BASH_SECURITY_CHECK_IDS 全列; §5.3 "三种" → "四种" 后台化方式; §2.4 bashToolCheckPermission 7 步骨架表,**每步带 bashPermissions.ts:行号区间** (OC-PM 硬要求 2) - 15: §二 标题 "五个" → "六个";新增 §2.6 Statusline-setup Agent - 16: §1.2 新增 7 TaskType vs 6 注册项对照表(含 in_process_teammate 的旁路 kill 路径); §附 A → §三点五;其 4.x → 3.5.x,消除与 §四 4.x 冲突 - 22: 89 个 feature flag 的 grep 统计命令补全;KAIROS 在 1.3 首次出现处定义; Top 15 之后新增 **74 个剩余 flag 的分类速查表**(按主题域分组、每条带次数与用途) (OC-PM 硬要求 1) - 30: 章标题改为 "screens/ 三屏 — Doctor、Output Style 与 ResumeConversation", §"为什么…放在同一章" → §"为什么把这三屏放在同一章" - 31: "五层" → "七层";表从 5 行 → 7 行(补 Background Extract Memories 与 Auto Dream); §十 → §附(明确为非记忆层) Issue: YAO-148 Co-authored-by: multica-agent --- ...344\270\216\344\274\201\344\270\232MDM.md" | 14 +- ...35\344\270\273\345\276\252\347\216\257.md" | 9 +- ...216OutputStyle\346\263\250\345\205\245.md" | 41 +++- ...13\347\274\251\345\256\266\346\227\217.md" | 15 ++ ...hTool-PowerShellTool-\345\217\214shell.md" | 66 +++--- ...76\350\256\241\346\250\241\345\274\217.md" | 38 +++- ...70\216TaskType\350\260\261\347\263\273.md" | 22 +- ...21\346\234\237\344\274\230\345\214\226.md" | 203 ++++++++++++++++-- ...216OutputStyle\344\275\223\351\252\214.md" | 4 +- ...73\347\273\237\345\205\250\346\231\257.md" | 14 +- 10 files changed, 360 insertions(+), 66 deletions(-) diff --git "a/docs/03-\351\205\215\347\275\256\344\275\223\347\263\273\344\270\216\344\274\201\344\270\232MDM.md" "b/docs/03-\351\205\215\347\275\256\344\275\223\347\263\273\344\270\216\344\274\201\344\270\232MDM.md" index 44cb673..4040235 100644 --- "a/docs/03-\351\205\215\347\275\256\344\275\223\347\263\273\344\270\216\344\274\201\344\270\232MDM.md" +++ "b/docs/03-\351\205\215\347\275\256\344\275\223\347\263\273\344\270\216\344\274\201\344\270\232MDM.md" @@ -116,6 +116,8 @@ function getSettingsForSourceUncached(source: SettingSource): SettingsJson | nul 注意这与外层的合并策略不同:外层是所有层级**逐层合并**(merge),Policy 内部是**第一个胜出**(first-source-wins)。设计意图很明确 —— 如果企业通过 Remote API 下发了策略,就不应该再考虑本地 managed-settings.json 的内容,避免策略冲突。 +> **章内导读**:§一 ~ §五 讲 5 个 `SettingSource` 的合并管线;§六(PolicyLimits)与 §七(SettingsSync)讲两条与 settings 平行的组织级旁路服务——它们**不进 `loadSettingsFromDisk()` 合并管线**(参见 `utils/settings/constants.ts:7-22` 中 `SETTING_SOURCES` 只有 5 个),但同样影响最终运行时配置。§八–§十一 回到合并体系本身,覆盖变更检测、安全设计、MDM 轮询与可迁移模式。 + --- ## 二、核心合并算法:loadSettingsFromDisk() @@ -574,7 +576,7 @@ export function startBackgroundPolling(): void { --- -## 五点五、PolicyLimits:与 Settings 平行的组织级开关 +## 六、PolicyLimits:与 Settings 平行的组织级开关 读到这里,你已经看到 Settings 系统通过 Policy 层把"企业管控"塞进了同一条优先级链。但还有一类管控不适合走 Settings —— 它不是"应该用哪个模型"或"允许哪些权限",而是"这个组织的用户能不能用某个产品特性"。`services/policyLimits/` 就是为这类组织级开关单独构建的一条服务。 @@ -646,7 +648,7 @@ export function initializePolicyLimitsLoadingPromise(): void { --- -## 五点六、SettingsSync:跨设备一致性的双向通道 +## 七、SettingsSync:跨设备一致性的双向通道 如果说 PolicyLimits 是"自上而下下发",`services/settingsSync/` 就是"自机器之间互相搬运"。它解决的痛点是:用户在笔记本上配的偏好(模型、Hook、Permission、`CLAUDE.md` 记忆),怎么在另一台机器(甚至是托管在云端的 Claude Code Remote)上自然出现。 @@ -724,7 +726,7 @@ export function redownloadUserSettings(): Promise { --- -## 六、变更检测与热更新 +## 八、变更检测与热更新 Settings 系统不是"启动时读一次就完了"—— 它支持运行时的配置文件变更检测和热更新。 @@ -870,7 +872,7 @@ export function applySettingsChange( --- -## 七、安全设计:防止恶意项目配置 +## 九、安全设计:防止恶意项目配置 Settings 系统中有多处安全防线,防止恶意项目通过配置文件获取过高权限: @@ -925,7 +927,7 @@ export type EditableSettingSource = Exclude< --- -## 八、MDM 轮询:注册表和 plist 的变更检测 +## 十、MDM 轮询:注册表和 plist 的变更检测 文件系统的变更可以用 chokidar 监听,但 macOS plist 和 Windows 注册表的变更无法通过文件系统事件捕获。系统通过 30 分钟间隔的轮询来解决: @@ -958,7 +960,7 @@ function startMdmPoll(): void { --- -## 九、可迁移的设计模式 +## 十一、可迁移的设计模式 ### 模式 1:多层配置合并 + 类型安全 diff --git "a/docs/05-QueryEngine\344\270\216\345\257\271\350\257\235\344\270\273\345\276\252\347\216\257.md" "b/docs/05-QueryEngine\344\270\216\345\257\271\350\257\235\344\270\273\345\276\252\347\216\257.md" index 8038f80..275c0db 100644 --- "a/docs/05-QueryEngine\344\270\216\345\257\271\350\257\235\344\270\273\345\276\252\347\216\257.md" +++ "b/docs/05-QueryEngine\344\270\216\345\257\271\350\257\235\344\270\273\345\276\252\347\216\257.md" @@ -20,6 +20,13 @@ 本章将从宏观到微观,层层展开这个循环的设计。 +> **阅读地图(三层结构)**: +> - **门面层**:`QueryEngine.ts`(1295 行)—— 一个 conversation 一个 `QueryEngine` 实例,负责会话级状态、SDK 翻译、终止分支(详见 §十一)。 +> - **内核层**:`query.ts`(1729 行)—— 无状态 AsyncGenerator,`query()` / `queryLoop()` 是真正驱动一次请求-工具-重试的核心(§一 ~ §十)。 +> - **横切层**:`query/` 子模块(4 个小文件、共 ≈ 652 行)—— `config.ts` / `deps.ts` / `stopHooks.ts` / `tokenBudget.ts`,是 `query.ts` 的横切关注点(§十二)。 +> +> 前 10 节看的是 `query.ts` 内核视角;§十一 回到门面 `QueryEngine`;§十二 收尾 `query/` 子模块。 + --- ## 一、全局视角:AsyncGenerator 驱动的状态机 @@ -742,7 +749,7 @@ const contextCollapse = feature('CONTEXT_COLLAPSE') --- -## 十一、回到门面:QueryEngine 这一层在做什么? +## 十一、门面层 QueryEngine.ts:会话级状态与 SDK 翻译 前面九节讲的都是"一次 turn 里发生了什么",但 `query()` 这个内核并不是 SDK 调用方直接面对的入口。介于 SDK 与 `query()` 之间,还有一层 1295 行的门面——`QueryEngine.ts`。这一层的存在感不像 `query.ts` 那么强,但它承担了几件 `query()` 故意不管的事。 diff --git "a/docs/06-SystemPrompt\344\270\216OutputStyle\346\263\250\345\205\245.md" "b/docs/06-SystemPrompt\344\270\216OutputStyle\346\263\250\345\205\245.md" index be6201a..10df8ce 100644 --- "a/docs/06-SystemPrompt\344\270\216OutputStyle\346\263\250\345\205\245.md" +++ "b/docs/06-SystemPrompt\344\270\216OutputStyle\346\263\250\345\205\245.md" @@ -16,6 +16,8 @@ 2. **静态与动态内容如何分离以优化缓存?** — `SYSTEM_PROMPT_DYNAMIC_BOUNDARY` 机制 3. **提示词中编码了哪些关键的行为引导技巧?** — 从安全指令到代码风格约束 +章节大纲:§一 整体架构 → §二 DYNAMIC_BOUNDARY 缓存 → §三 Context 注入 → §四 行为引导 → **§五 Output Style:用户接管 system prompt 末尾** → §六 优先级体系 → §七 Subagent 增强 → §八 预取策略 → §九 可迁移模式。 + --- ## 一、整体架构:分段组装的 System Prompt @@ -505,7 +507,38 @@ const providedToolSubitems = [ --- -## 五、System Prompt 的优先级体系 +## 五、Output Style:把 system prompt 的尾巴交给用户 + +前面四节讲的是 Claude Code 自己怎么组装 system prompt。本节聚焦另一个反方向问题:**用户如何介入这条管线**,让模型按自己的风格作答——甚至关掉默认的 coding-instructions。源码主入口在 `outputStyles/loadOutputStylesDir.ts:1-98`(98 行)和 `constants/outputStyles.ts`(内建样式 + 优先级合并)。 + +### 5.1 注入点:system prompt 末尾的可替换 tail + +`constants/prompts.ts:151-158` 的 `getOutputStyleSection()` 在 system prompt 拼装时被排到末尾(`prompts.ts:506` 在静态段被排入数组、`prompts.ts:562-565` 在 simple 路径里影响 intro / doingTasks)。它返回的不是固定文本,而是**当前激活 Output Style 的 `prompt` 字段**——也就是说,system prompt 的"尾巴"被设计成用户可注入的接缝。 + +### 5.2 frontmatter 字段与 `keep-coding-instructions` 归一化 + +用户在 `.claude/output-styles/*.md` 写一个 markdown 文件,文件名 = 样式名,正文 = 风格 prompt。frontmatter 支持几个关键字段(`outputStyles/loadOutputStylesDir.ts:34-78`): + +| 字段 | 类型 | 作用 | +|---|---|---| +| `name` | string | 样式显示名(缺省取文件名) | +| `description` | string | 列表展示用;缺省时从正文首段抽取 | +| `keep-coding-instructions` | bool / "true" / "false" / undefined | **关键开关**:是否保留默认的 coding-instructions 段;`undefined` 走默认行为 | +| `force-for-plugin` | -- | 仅对 plugin 样式生效,普通样式会被忽略并打 warn(line 64-70) | + +`keep-coding-instructions` 的归一化(line 52-62)显式把 `true` / `'true'` 都视为 `true`、`false` / `'false'` 都视为 `false`、其它值视为 `undefined`。这条小逻辑解释了为什么 Output Style 不只是"追加 prompt",它**还能反向「关掉」整段默认 coding instructions**——这条核心论点之前散在 §1.1 的脚注里,这里收拢说清。 + +### 5.3 优先级合并:built-in / user / project / plugin / policy + +`constants/outputStyles.ts` 的 `getAllOutputStyles()` 把内建样式(`Explanatory` / `Learning`)与 `getOutputStyleDirStyles()` 从磁盘扫描到的样式合并,按 `pluginStyles → userStyles → projectStyles → managedStyles` 的顺序定优先级;`getOutputStyleConfig()` 进一步实现"forced plugin > settings.outputStyle"的最终选择。 + +### 5.4 与第 30 章的分工 + +第 30 章 §二 也叫"Output Style:把 system prompt 的尾巴交给用户",但视角不同:**第 30 章 §二走"用户体验 / 选择器交互 / 优先级合并的体验后果"**,本节走"源码注入链路"。两节互引,避免重复展开 frontmatter 字段表。 + +--- + +## 六、System Prompt 的优先级体系 `getSystemPrompt()` 并不总是唯一的 prompt 来源。`utils/systemPrompt.ts` 中的 `buildEffectiveSystemPrompt()` 定义了一个清晰的优先级体系: @@ -557,7 +590,7 @@ export function buildEffectiveSystemPrompt({ --- -## 六、Subagent 的 Prompt 增强 +## 七、Subagent 的 Prompt 增强 当一个 Agent(subagent)被创建时,它的 System Prompt 通常会经过 `enhanceSystemPromptWithEnvDetails()` 函数(`prompts.ts:760-791`)的增强: @@ -608,7 +641,7 @@ Fork subagent 的 prompt 策略是**直接复用父线程已经渲染好的 syst --- -## 七、预取策略:在用户打字时准备好 Prompt +## 八、预取策略:在用户打字时准备好 Prompt System Prompt 的计算不是等到用户发送消息才开始的。`main.tsx` 中的 `startDeferredPrefetches()` 会在 REPL 渲染后立即开始预取: @@ -641,7 +674,7 @@ function prefetchSystemContextIfSafe(): void { --- -## 八、可迁移的设计模式 +## 九、可迁移的设计模式 ### 模式 1:静态/动态分界线 + 缓存作用域 diff --git "a/docs/07-\344\270\212\344\270\213\346\226\207\345\216\213\347\274\251\345\256\266\346\227\217.md" "b/docs/07-\344\270\212\344\270\213\346\226\207\345\216\213\347\274\251\345\256\266\346\227\217.md" index 7da6885..31ac457 100644 --- "a/docs/07-\344\270\212\344\270\213\346\226\207\345\216\213\347\274\251\345\256\266\346\227\217.md" +++ "b/docs/07-\344\270\212\344\270\213\346\226\207\345\216\213\347\274\251\345\256\266\346\227\217.md" @@ -17,6 +17,21 @@ LLM 的 context window 是有限的。Claude 的模型通常有 200K token 的 Claude Code 的解决方案是一套**多层次的上下文压缩与恢复体系**——从最轻量的 Microcompact(清理工具结果)到最重量级的 Full Compact(用模型总结整个对话),形成了一个完整的上下文压力梯度响应系统。 +本章覆盖 **6 条压缩/清理链路(5 条本地 + 1 条 API 层声明式)**,对应 `services/compact/` 目录下的核心文件。 + +### §零 · 6 条链路速查表 + +| # | 链路 | 触发场景 | 源码 | +|---|---|---|---| +| 1 | **Microcompact**(time-based / cached 两实现) | 工具结果累积、轻量清理 | `services/compact/microCompact.ts`(time-based: 446–530;cached: 305–399;入口: 253–293)| +| 2 | **API-level Microcompact**(声明式,委托给 Anthropic API) | 由 API 层注入,不本地裁剪 | `services/compact/apiMicrocompact.ts`;由 `services/api/claude.ts:1633` 注入 | +| 3 | **Auto Compact**(达阈值自动触发 full) | token 用量越界 | `services/compact/autoCompact.ts` | +| 4 | **Full Compact**(调用模型总结全对话) | 用户手动 `/compact` 或 auto 触发 | `services/compact/compact.ts` + `services/compact/prompt.ts` | +| 5 | **Session Memory Compact**(免调用,把 session memory 当压缩结果) | 已有 session memory 可复用 | `services/compact/sessionMemoryCompact.ts` | +| 6 | **Post-Compact Cleanup**(压缩后的缓存清理) | 任一 compact 完成后 | `services/compact/postCompactCleanup.ts` | + +辅助文件(**不**算独立链路、是支撑设施):`grouping.ts`、`compactWarningHook.ts`、`compactWarningState.ts`、`timeBasedMCConfig.ts`。本章 §四 / §五 把 `FileStateCache`、`compactWarningState` 定位为"支撑状态机",**不计入 6 条**。 + --- ## 一、Token 预算管理:三个关键函数 diff --git "a/docs/11-BashTool-PowerShellTool-\345\217\214shell.md" "b/docs/11-BashTool-PowerShellTool-\345\217\214shell.md" index 87ea3a8..894766f 100644 --- "a/docs/11-BashTool-PowerShellTool-\345\217\214shell.md" +++ "b/docs/11-BashTool-PowerShellTool-\345\217\214shell.md" @@ -265,43 +265,55 @@ type QuoteExtraction = { #### 2.3.4 安全检查清单 -`bashSecurity.ts:77-101` 定义了 23 种安全检查的数字标识符,每种对应一个检测器: +`bashSecurity.ts:77-101` 定义了 **23 种**安全检查的数字标识符(使用数字 ID 而不是字符串,是为了避免把检查名一并写入遥测日志)。这里逐条列全,避免读者再回 grep: ```typescript const BASH_SECURITY_CHECK_IDS = { - INCOMPLETE_COMMANDS: 1, // 不完整命令 - JQ_SYSTEM_FUNCTION: 2, // jq 的 system() 函数调用 - OBFUSCATED_FLAGS: 4, // 混淆的命令行标志 - SHELL_METACHARACTERS: 5, // Shell 元字符 - DANGEROUS_VARIABLES: 6, // 危险环境变量 - NEWLINES: 7, // 命令中的换行符 - IFS_INJECTION: 11, // IFS 注入攻击 - PROC_ENVIRON_ACCESS: 13, // /proc 环境变量访问 - MALFORMED_TOKEN_INJECTION: 14, // 畸形 token 注入 - BRACE_EXPANSION: 16, // 花括号展开 - CONTROL_CHARACTERS: 17, // 控制字符 - UNICODE_WHITESPACE: 18, // Unicode 空白字符 - ZSH_DANGEROUS_COMMANDS: 20, // Zsh 危险命令 - COMMENT_QUOTE_DESYNC: 22, // 注释/引号不同步 - // ... -}; + INCOMPLETE_COMMANDS: 1, // 不完整命令(含未闭合引号/管道等) + JQ_SYSTEM_FUNCTION: 2, // jq 表达式中 `system(...)` + JQ_FILE_ARGUMENTS: 3, // jq 的 -f/--from-file 等读取外部脚本的参数 + OBFUSCATED_FLAGS: 4, // 混淆的命令行标志(反斜杠/Unicode 拼接) + SHELL_METACHARACTERS: 5, // Shell 元字符(;、&、$ 等出现在敏感位置) + DANGEROUS_VARIABLES: 6, // 危险环境变量赋值(如 LD_PRELOAD) + NEWLINES: 7, // 命令中的裸换行符 + DANGEROUS_PATTERNS_COMMAND_SUBSTITUTION: 8, // $(…) / `…` 命令替换 + DANGEROUS_PATTERNS_INPUT_REDIRECTION: 9, // < / <<< / <(…) 等输入重定向 + DANGEROUS_PATTERNS_OUTPUT_REDIRECTION: 10, // > / >> 等输出重定向(带兜底白名单) + IFS_INJECTION: 11, // IFS 注入攻击 + GIT_COMMIT_SUBSTITUTION: 12, // git commit -m 中带命令替换 + PROC_ENVIRON_ACCESS: 13, // /proc//environ 读取 + MALFORMED_TOKEN_INJECTION: 14, // 畸形 token 注入(绕过 shell-quote 解析) + BACKSLASH_ESCAPED_WHITESPACE: 15, // \\ 被用来伪装连续 token + BRACE_EXPANSION: 16, // 花括号展开 {a,b} + CONTROL_CHARACTERS: 17, // ASCII 控制字符(\x00-\x1F 等) + UNICODE_WHITESPACE: 18, // Unicode 空白(U+00A0 等冒充空格) + MID_WORD_HASH: 19, // 词中 # 把后续 token 截成注释 + ZSH_DANGEROUS_COMMANDS: 20, // zsh 模块/builtin(zmodload、zf_* 等) + BACKSLASH_ESCAPED_OPERATORS: 21, // 用反斜杠遮蔽 ;、& 等操作符 + COMMENT_QUOTE_DESYNC: 22, // 注释/引号同步错位('x'# 用注释隐藏后续命令) + QUOTED_NEWLINE: 23, // 引号内塞入换行绕过单行匹配 +} as const ``` 每种检查都有对应的 validator 函数。检测到问题时,命令不会被直接拒绝,而是标记为"不安全",触发权限确认对话框。这体现了一个核心原则:**安全系统应该 fail-closed(检测到不确定性时默认询问用户),而不是 fail-open**。 ### 2.4 每子命令权限判定中的只读验证(readOnlyValidation.ts) -只读验证**并非主链路的独立层**,而是在每个子命令的权限判定函数 `bashToolCheckPermission()`(bashPermissions.ts:1050-1178)内部的第 7 步调用。判定顺序为: +只读验证**并非主链路的独立层**,而是在每个子命令的权限判定函数 `bashToolCheckPermission()`(bashPermissions.ts:1050-1178)内部的第 7 步调用。整个函数的 7 步骨架(**每一步的行号区间已校对到源码**): -1. 精确匹配 deny/ask 规则 -2. 前缀/通配符 deny/ask 规则 -3. 路径约束检查(`checkPathConstraints`) -4. 精确匹配 allow 规则 -5. 前缀/通配符 allow 规则 -6. sed 约束 + 模式检查 -7. **只读验证**:`BashTool.isReadOnly(input)` → `checkReadOnlyConstraints()` +| 步骤 | 名称 | 行号区间 | 命中后行为 | +| --- | --- | --- | --- | +| 1 | 精确匹配 deny/ask 规则 | 1058-1070 | deny / ask 立即返回 | +| 2 | 前缀/通配符 deny/ask 规则 | 1072-1104 | deny / ask 立即返回 | +| 3 | 路径约束检查(`checkPathConstraints`) | 1106-1122 | 非 passthrough 立即返回 | +| 4 | 精确匹配 allow 规则 | 1124-1127 | allow 立即返回 | +| 5 | 前缀/通配符 allow 规则 | 1129-1139 | allow 立即返回 | +| 5b | sed 约束检查(`checkSedConstraints`) | 1141-1145 | 非 passthrough 立即返回 | +| 6 | 权限模式分支(`checkPermissionMode`) | 1147-1151 | 非 passthrough 立即返回(acceptEdits/plan 等模式专用) | +| 7 | **只读验证**:`BashTool.isReadOnly(input)` → `checkReadOnlyConstraints()` | 1153-1163 | 命中即 allow(`reason: 'Read-only command is allowed'`) | +| 8 | passthrough 兜底 | 1165-1177 | 触发权限确认对话框,附带 exact-match 建议 | -只有当前面所有规则都没有匹配时,才轮到只读验证。这意味着如果用户设置了 `Bash(git status)` 的 deny 规则,即使 `git status` 是只读命令,也会被拒绝——deny 规则的优先级高于只读放行。 +deny 规则的优先级高于只读放行:如果用户设置了 `Bash(git status)` 的 deny 规则,即使 `git status` 是只读命令也会被拒绝。同时注意第 5b/6 步在第 7 步之前——sed 危险写入和 plan 模式都能在只读放行之前先把命令拦下。 `readOnlyValidation.ts` 长达 1,990 行,其中大部分是命令配置。它为 100+ 个常用命令定义了"安全标志白名单"。但在判定一条命令是否只读之前,`checkReadOnlyConstraints()` 还有**一串关键的安全前置条件**(readOnlyValidation.ts:1882-1966): @@ -575,7 +587,7 @@ if (initialResult !== null) { ### 5.3 前台/后台任务转换 -BashTool 支持三种后台化方式: +BashTool 支持**四种**后台化方式: 1. **AI 主动后台化**:`run_in_background: true`(BashTool.tsx:989-1001) 2. **超时自动后台化**:超过默认超时时间后自动转入后台(BashTool.tsx:967-971) diff --git "a/docs/15-\345\206\205\347\275\256Agent\350\256\276\350\256\241\346\250\241\345\274\217.md" "b/docs/15-\345\206\205\347\275\256Agent\350\256\276\350\256\241\346\250\241\345\274\217.md" index 7b4f12b..4113955 100644 --- "a/docs/15-\345\206\205\347\275\256Agent\350\256\276\350\256\241\346\250\241\345\274\217.md" +++ "b/docs/15-\345\206\205\347\275\256Agent\350\256\276\350\256\241\346\250\241\345\274\217.md" @@ -13,7 +13,7 @@ 3. **prompt 精度** —— 越专注的角色定义,模型的行为越可预测 4. **token 节约** —— 只读 Agent 不需要 CLAUDE.md 中的 commit/PR/lint 规则,省下 5-15 Gtok/周 -Claude Code 通过 6 个内置 Agent(General-purpose、Statusline-setup、Explore、Plan、Guide、Verification),展示了一套**角色化 prompt 设计模式** —— 同样的工具集合,通过不同的 System Prompt 约束,产生完全不同的行为。本章重点剖析其中 5 个设计最具代表性的 Agent(Statusline-setup 偏领域特化,仅在架构图中列出)。 +Claude Code 通过 6 个内置 Agent(General-purpose、Statusline-setup、Explore、Plan、Guide、Verification),展示了一套**角色化 prompt 设计模式** —— 同样的工具集合,通过不同的 System Prompt 约束,产生完全不同的行为。本章逐一剖析全部 6 个 Agent —— 其中 Explore/Plan/Verification/General-purpose/Guide 五个偏 prompt 工程,Statusline-setup 偏领域特化(一个专门改 `settings.json` 的小 Agent),放在 §2.6。 --- @@ -79,7 +79,7 @@ export function getBuiltInAgents(): AgentDefinition[] { --- -## 二、五个内置 Agent 逐一解析 +## 二、六个内置 Agent 逐一解析 ### 2.1 Explore Agent:只读搜索专家 @@ -420,6 +420,40 @@ export const CLAUDE_CODE_GUIDE_AGENT: BuiltInAgentDefinition = { 2. **`dontAsk` 权限模式** —— Guide Agent 只做搜索和 fetch,不需要用户确认 3. **动态 System Prompt** —— `getSystemPrompt` 接收 `toolUseContext` 参数,是所有内置 Agent 中唯一使用运行时上下文来构建 prompt 的 +### 2.6 Statusline-setup Agent:领域特化的小工 + +**文件**:`tools/AgentTool/built-in/statuslineSetup.ts` + +Statusline-setup 是 6 个内置 Agent 中**最专一**的一个——它只做一件事:根据用户的 shell PS1 把 `~/.claude/settings.json` 的 `statusLine.command` 字段写好。它没有 READ-ONLY 约束,也没有对抗性 prompt,全部 System Prompt 是一份"领域操作手册": + +```typescript +// tools/AgentTool/built-in/statuslineSetup.ts:134-144 +export const STATUSLINE_SETUP_AGENT: BuiltInAgentDefinition = { + agentType: 'statusline-setup', + whenToUse: + "Use this agent to configure the user's Claude Code status line setting.", + tools: ['Read', 'Edit'], // 仅 2 个工具:读 rc 文件 + 改 settings.json + source: 'built-in', + baseDir: 'built-in', + model: 'sonnet', // 文本变换任务,sonnet 足够 + color: 'orange', // 橙色 UI 标识 + getSystemPrompt: () => STATUSLINE_SYSTEM_PROMPT, +} +``` + +**最小工具白名单**:只有 `Read` 和 `Edit`——它需要读 `~/.zshrc`/`~/.bashrc` 等 rc 文件取 PS1,然后用 Edit 改 `~/.claude/settings.json`。没有 Bash、没有 Write、没有 Glob——这意味着它**改不了任何 settings.json 之外的东西,也不能用 Bash 隐式执行命令**。这种白名单设计比任何 prompt 约束都更可靠。 + +System Prompt 本身(`statuslineSetup.ts:3-132`,约 130 行)是一份高度领域化的指南: +1. **rc 文件读取顺序**:`~/.zshrc` → `~/.bashrc` → `~/.bash_profile` → `~/.profile` +2. **PS1 提取正则**:`/(?:^|\n)\s*(?:export\s+)?PS1\s*=\s*["']([^"']+)["']/m` +3. **PS1 转义到 shell 命令的映射表**:`\u → $(whoami)`、`\h → $(hostname -s)`、`\w → $(pwd)`、`\t → $(date +%H:%M:%S)` 等 12 条 +4. **statusLine stdin JSON schema 完整描述**:包含 `session_id`、`model`、`workspace`、`context_window`、`rate_limits`、`vim`、`agent`、`worktree` 等字段,附带 `jq` 取值示例 +5. **常用配方**:context 剩余百分比、5h/7d rate limit 显示等 + +最后一条 `IMPORTANT` 还会让 Agent 在结束时通知主 Agent:"以后改 statusLine 必须再调本 Agent"——这是一种 prompt 层面的自我宣传,把"专人专办"的契约刻进对话历史。 + +Statusline-setup 体现了**第 4 种工具约束模式**:当任务足够窄、副作用足够明确时,最有效的约束是把工具集裁到最小——不需要长篇 READ-ONLY 警告,也不需要对抗性 prompt,仅 `tools: ['Read', 'Edit']` 这一行就锁死了所有越界路径。 + --- ## 三、内置 Agent 的共性设计模式 diff --git "a/docs/16-\344\273\273\345\212\241\346\250\241\345\236\213\344\270\216TaskType\350\260\261\347\263\273.md" "b/docs/16-\344\273\273\345\212\241\346\250\241\345\236\213\344\270\216TaskType\350\260\261\347\263\273.md" index 1870f53..67ab6ea 100644 --- "a/docs/16-\344\273\273\345\212\241\346\250\241\345\236\213\344\270\216TaskType\350\260\261\347\263\273.md" +++ "b/docs/16-\344\273\273\345\212\241\346\250\241\345\236\213\344\270\216TaskType\350\260\261\347\263\273.md" @@ -79,7 +79,19 @@ graph TD ### 1.2 TaskType 与状态机 -任务系统定义了 7 种任务类型: +任务系统定义了 7 种 wire 任务类型,但只有 6 种被注册到 `tasks.ts` 的 kill 分发表里 —— 二者并非一一对应,这是读源码时容易踩坑的地方,先把对照关系一次说清楚: + +| TaskType | ID 前缀 | 注册到 `getAllTasks()` | 门控 | kill 路径 | +| --- | --- | --- | --- | --- | +| `local_bash` | `b` | ✅ | 默认 | `LocalShellTask.kill` | +| `local_agent` | `a` | ✅ | 默认 | `LocalAgentTask.kill` | +| `remote_agent` | `r` | ✅ | 默认 | `RemoteAgentTask.kill` | +| `dream` | `d` | ✅ | 默认 | `DreamTask.kill` | +| `local_workflow` | `w` | ✅(条件) | `feature('LOCAL_WORKFLOW')` | `LocalWorkflowTask.kill` | +| `monitor_mcp` | `m` | ✅(条件) | `feature('MONITOR_MCP')` | `MonitorMcpTask.kill` | +| `in_process_teammate` | `t` | ❌ | swarm 模式 | `killInProcessTeammate()` 直管,**不走** `getTaskByType()` | + +也就是:**TaskType 联合有 7 个字面量**,**`getAllTasks()` 最多返回 6 个**(4 默认 + 2 feature-gated)。`in_process_teammate` 落在中间地带——类型层、UI 层(pill / 面板 / 消息镜像)都完整实现,但 kill 由 `utils/swarm/spawnInProcess.ts:killInProcessTeammate()` 直接驱动,所以走 `stopTask.ts` 通用路径时会得到 `unsupported_type` 错误。 ```typescript // Task.ts:6-14 @@ -535,7 +547,7 @@ export function startBackgroundSession({ messages, queryParams, ... }): string { --- -## 附 A · 模型侧的入口:六个 Task*Tool 与"两种 task"之分 +## 三点五、模型侧的入口:六个 Task*Tool 与"两种 task"之分 读到这里,源码里有一个让人头疼的命名冲突需要先澄清:**"task" 这个词在 Claude Code 里指的是两件完全不同的事**。 @@ -554,7 +566,7 @@ export function startBackgroundSession({ messages, queryParams, ... }): string { | `tools/TaskListTool/` | TodoV2 | `tools/TaskListTool/TaskListTool.ts:33-116` | `isTodoV2Enabled()` | | `tools/TaskUpdateTool/` | TodoV2 | `tools/TaskUpdateTool/TaskUpdateTool.ts:88-406` | `isTodoV2Enabled()` | -### 4.1 TaskStop / TaskOutput:观察与终止 TaskState +### 3.5.1 TaskStop / TaskOutput:观察与终止 TaskState 这两个工具不创建任务——任务由业务工具(BashTool 后台化、AgentTool spawn 子 Agent 等)隐式创建——它们的职责只有两件:**让模型主动 kill 一个还在跑的后台任务,以及读它的输出**。 @@ -592,7 +604,7 @@ async call({ task_id, shell_id }, { getAppState, setAppState }) { 它仍然提供两种调用姿态(`tools/TaskOutputTool/TaskOutputTool.tsx:208-282`):`block=true` 阻塞模式等任务进入终态后再返回最终输出;`block=false` 轮询模式立即返回当前 status 和已累积的输出片段。 -### 4.2 TaskCreate / Get / List / Update:另一套并行的 TodoV2 体系 +### 3.5.2 TaskCreate / Get / List / Update:另一套并行的 TodoV2 体系 剩下 4 个工具名字里都带 task,但**完全不碰** `AppState.tasks`。它们调的是 `utils/tasks.ts` 暴露的 TodoV2 接口(`createTask` / `getTask` / `listTasks` / `updateTask` / `blockTask` / `deleteTask`),背后是按 `taskListId` 持久化的 JSON 文件,每条 task 的字段是 `subject` / `description` / `status` / `owner` / `blocks` / `blockedBy` / `metadata` 等。 @@ -629,7 +641,7 @@ if ( 触发条件是:主线程 agent(`!context.agentId`)一次性把一个 3+ 条的 todo list 全部关掉,而其中**没有任何一条**的 subject 命中 `/verif/i`。命中时工具结果里会被追加一段提示,要求 agent 在收尾前显式 spawn `subagent_type = VERIFICATION_AGENT_TYPE` 的子 Agent 来做验证。这是一个反"自评 summary"的结构性约束——它写在工具层而不是 prompt 里,模型逃不掉。 -### 4.3 为什么把两套体系放在同一章 +### 3.5.3 为什么把两套体系放在同一章 它们几乎不共享代码,但共享同一个**词**:task。读源码的第一道坎就是分清 `AppState.tasks` 和 `utils/tasks.ts` 不是一回事——前者由业务工具隐式创建、由 TaskStop / TaskOutput 显式观察;后者由模型自己拿着这 4 个 Task*Tool 主动写入。把它们放在同一章里讲,正是为了让这条线**只需要被穿过一次**。 diff --git "a/docs/22-FeatureFlag\344\270\216\347\274\226\350\257\221\346\234\237\344\274\230\345\214\226.md" "b/docs/22-FeatureFlag\344\270\216\347\274\226\350\257\221\346\234\237\344\274\230\345\214\226.md" index 34f47bb..597fade 100644 --- "a/docs/22-FeatureFlag\344\270\216\347\274\226\350\257\221\346\234\237\344\274\230\345\214\226.md" +++ "b/docs/22-FeatureFlag\344\270\216\347\274\226\350\257\221\346\234\237\344\274\230\345\214\226.md" @@ -85,27 +85,202 @@ const MonitorTool = feature('MONITOR_TOOL') ### 1.3 89 个 Feature Flag 的全景 -通过搜索整个代码库,共发现 **89 个**不同的 `feature()` 标识符。以使用频次排序,Top 15 为: +**89 这个数字是怎么数出来的?** —— 直接对源码仓库(`/Users/yao/work/code/awesome-project/claude-code-cli`)跑一次: + +```bash +grep -rhoE "feature\(['\"]([A-Z_0-9]+)['\"]\)" --include="*.ts" --include="*.tsx" . \ + | sed -E "s/feature\(['\"]([A-Z_0-9]+)['\"]\)/\1/" \ + | sort | uniq -c | sort -rn | wc -l +``` + +得到 **89 个**独立 flag。下面先看高频 Top 15(使用次数 ≥ 16),随后给出剩下 74 个 flag 的**分类速查表**——按主题域分组,每组给出 flag 名 + 使用次数,方便按特性家族而非字母序检索。 + +注:这里频繁出现的 `KAIROS`(希腊语「恰当时机」)出现 156 次,几乎是第二名的 1.5 倍——它对应的是 Claude Code 的**「Assistant 助手 / 聊天」模式**,一个内部大型实验功能(入口在 `assistant/index.ts`、`assistant/gate.ts`、`commands/assistantChat.tsx` 等),其下还派生出 5 个 `KAIROS_*` 子 flag(详见下面的速查表)。 | Feature Flag | 使用次数 | 功能领域 | |-------------|---------|---------| -| `KAIROS` | 154 | 助手/聊天模式 | -| `TRANSCRIPT_CLASSIFIER` | 107 | 权限自动分类 | -| `TEAMMEM` | 51 | 团队记忆 | -| `VOICE_MODE` | 46 | 语音交互 | -| `BASH_CLASSIFIER` | 45 | Bash 命令安全分类 | -| `KAIROS_BRIEF` | 39 | 简报模式 | -| `PROACTIVE` | 37 | 主动模式 | +| `KAIROS` | 156 | Assistant 助手/聊天模式 | +| `TRANSCRIPT_CLASSIFIER` | 110 | 权限自动分类 | +| `TEAMMEM` | 53 | 团队记忆 | +| `VOICE_MODE` | 49 | 语音交互 | +| `BASH_CLASSIFIER` | 49 | Bash 命令安全分类 | +| `KAIROS_BRIEF` | 39 | Assistant 简报模式 | +| `PROACTIVE` | 37 | 主动模式(SleepTool 等) | | `COORDINATOR_MODE` | 32 | 多 Agent 协调器 | -| `BRIDGE_MODE` | 28 | IDE 远程桥接 | +| `BRIDGE_MODE` | 29 | IDE 远程桥接 | +| `CONTEXT_COLLAPSE` | 23 | 上下文折叠 | +| `KAIROS_CHANNELS` | 21 | Assistant 频道 | | `EXPERIMENTAL_SKILL_SEARCH` | 21 | 实验性技能搜索 | -| `CONTEXT_COLLAPSE` | 20 | 上下文折叠 | -| `KAIROS_CHANNELS` | 19 | 频道功能 | -| `UDS_INBOX` | 17 | Unix 域套接字消息 | +| `UDS_INBOX` | 18 | Unix 域套接字消息 | +| `BUDDY` | 18 | Buddy 模式 | +| `HISTORY_SNIP` | 16 | 历史片段剪辑 | | `CHICAGO_MCP` | 16 | Computer Use MCP | -| `BUDDY` | 16 | Buddy 模式 | -这些 flag 中,`KAIROS`(希腊语「恰当时机」)出现 154 次,几乎是第二名的 1.5 倍 —— 它对应的是 Claude Code 的「助手」模式,这是一个内部大型实验功能。 +#### 1.3.1 剩余 74 个 flag 的分类速查表 + +按主题域分组(**逐个列出**,不再让读者自行 grep)。"次"指 `feature('X')` 在源码中的出现次数。 + +**Assistant / KAIROS 家族(除 KAIROS、KAIROS_BRIEF、KAIROS_CHANNELS 外的 3 个)** + +| Flag | 次 | 用途 | +|---|---|---| +| `KAIROS_PUSH_NOTIFICATION` | 4 | Assistant 推送通知 | +| `KAIROS_GITHUB_WEBHOOKS` | 4 | Assistant 监听 GitHub Webhook | +| `KAIROS_DREAM` | 1 | Assistant 模式下的 Dream/记忆整理 | + +**Coordinator / 多 Agent / Workflow(6 个)** + +| Flag | 次 | 用途 | +|---|---|---| +| `WORKFLOW_SCRIPTS` | 10 | 工作流脚本(LocalWorkflowTask) | +| `MONITOR_TOOL` | 13 | MonitorTool(与 MonitorMcpTask 配合) | +| `FORK_SUBAGENT` | 5 | Fork subagent 上下文分叉 | +| `VERIFICATION_AGENT` | 4 | Verification 内置 Agent | +| `BUILTIN_EXPLORE_PLAN_AGENTS` | 1 | Explore/Plan 内置 Agent 总开关 | +| `COWORKER_TYPE_TELEMETRY` | 2 | Coworker 类型遥测 | + +**Cron / 自动化触发(2 个)** + +| Flag | 次 | 用途 | +|---|---|---| +| `AGENT_TRIGGERS` | 11 | ScheduleCronTool 三件套 | +| `AGENT_TRIGGERS_REMOTE` | 2 | 远程触发器 | + +**Compact / 上下文管理(5 个)** + +| Flag | 次 | 用途 | +|---|---|---| +| `CACHED_MICROCOMPACT` | 12 | 缓存 microcompact 链路 | +| `REACTIVE_COMPACT` | 5 | 响应式压缩 | +| `TOKEN_BUDGET` | 9 | Token 预算管理 | +| `COMPACTION_REMINDERS` | 1 | 压缩提醒注入 | +| `PROMPT_CACHE_BREAK_DETECTION` | 9 | Prompt cache 断裂检测 | + +**Memory / 记忆(3 个)** + +| Flag | 次 | 用途 | +|---|---|---| +| `EXTRACT_MEMORIES` | 7 | 后台 extract memories | +| `MEMORY_SHAPE_TELEMETRY` | 3 | 记忆形态遥测 | +| `AGENT_MEMORY_SNAPSHOT` | 2 | Agent memory 快照 | + +**MCP / 工具扩展(3 个)** + +| Flag | 次 | 用途 | +|---|---|---| +| `MCP_SKILLS` | 9 | MCP server 暴露的 Skills | +| `MCP_RICH_OUTPUT` | 3 | MCP 富文本输出 | +| `WEB_BROWSER_TOOL` | 4 | WebBrowserTool 注册 | + +**Bridge / 远程会话 / 直连(5 个)** + +| Flag | 次 | 用途 | +|---|---|---| +| `DIRECT_CONNECT` | 5 | DirectConnect 上游代理 | +| `SSH_REMOTE` | 4 | SSH 远程会话 | +| `BG_SESSIONS` | 11 | 后台会话管理(ps/logs/attach) | +| `DAEMON` | 3 | daemon 子命令与 worker | +| `LODESTONE` | 6 | Lodestone 远程基础设施 | + +**CCR / 客户端连接(3 个)** + +| Flag | 次 | 用途 | +|---|---|---| +| `CCR_MIRROR` | 4 | CCR 镜像传输 | +| `CCR_AUTO_CONNECT` | 3 | CCR 自动连接 | +| `CCR_REMOTE_SETUP` | 1 | CCR 远程初始化 | + +**TREE_SITTER(2 个)** + +| Flag | 次 | 用途 | +|---|---|---| +| `TREE_SITTER_BASH` | 3 | tree-sitter Bash 解析(主路径) | +| `TREE_SITTER_BASH_SHADOW` | 5 | tree-sitter Bash 影子模式(diff 旧解析) | + +**用户设置同步(2 个)** + +| Flag | 次 | 用途 | +|---|---|---| +| `UPLOAD_USER_SETTINGS` | 2 | 用户设置上传 | +| `DOWNLOAD_USER_SETTINGS` | 5 | 用户设置下载 | + +**UI / 终端 / 输入输出(10 个)** + +| Flag | 次 | 用途 | +|---|---|---| +| `BUDDY` | (Top15) | Buddy 宠物 | +| `TERMINAL_PANEL` | 5 | 终端面板 | +| `QUICK_SEARCH` | 5 | 快速搜索面板 | +| `MESSAGE_ACTIONS` | 5 | 消息动作菜单 | +| `HISTORY_PICKER` | 4 | 历史选择器 UI | +| `CONNECTOR_TEXT` | 8 | 连接器文本渲染 | +| `TEMPLATES` | 6 | 模板系统 | +| `STREAMLINED_OUTPUT` | 1 | 精简输出 | +| `AUTO_THEME` | 3 | 自动主题 | +| `NATIVE_CLIPBOARD_IMAGE` | 2 | 原生剪贴板图片粘贴 | + +**Power user / Ultraplan / Review(3 个)** + +| Flag | 次 | 用途 | +|---|---|---| +| `ULTRAPLAN` | 10 | Ultraplan 远程深度规划 | +| `REVIEW_ARTIFACT` | 4 | Review artifact 渲染 | +| `ULTRATHINK` | 1 | UltraThink 深度思考模式 | + +**遥测 / 调试 / 实验(10 个)** + +| Flag | 次 | 用途 | +|---|---|---| +| `SHOT_STATS` | 10 | shot 统计 | +| `ENHANCED_TELEMETRY_BETA` | 2 | 增强遥测 beta | +| `PERFETTO_TRACING` | 1 | Perfetto 性能追踪 | +| `SLOW_OPERATION_LOGGING` | 1 | 慢操作日志 | +| `OVERFLOW_TEST_TOOL` | 2 | OverflowTestTool(压测用) | +| `BREAK_CACHE_COMMAND` | 2 | breakCache 命令 | +| `HARD_FAIL` | 2 | 硬失败模式 | +| `ANTI_DISTILLATION_CC` | 1 | 反蒸馏 | +| `DUMP_SYSTEM_PROMPT` | 1 | --dump-system-prompt 快速路径 | +| `ABLATION_BASELINE` | 1 | 消融实验基线(见 §1.5) | + +**命令归属 / 集成(5 个)** + +| Flag | 次 | 用途 | +|---|---|---| +| `COMMIT_ATTRIBUTION` | 12 | commit 自动归属 | +| `HOOK_PROMPTS` | 1 | Hook prompt 拼装 | +| `FILE_PERSISTENCE` | 3 | 文件持久化层 | +| `AWAY_SUMMARY` | 2 | 离开总结 | +| `SKIP_DETECTION_WHEN_AUTOUPDATES_DISABLED` | 1 | 自动更新关掉时跳过检测 | + +**Skill / 自定义(3 个)** + +| Flag | 次 | 用途 | +|---|---|---| +| `SKILL_IMPROVEMENT` | 1 | Skill 改进流 | +| `RUN_SKILL_GENERATOR` | 1 | Skill 生成器 | +| `NEW_INIT` | 2 | 新版 init 流 | + +**Power shell / 平台特化(5 个)** + +| Flag | 次 | 用途 | +|---|---|---| +| `POWERSHELL_AUTO_MODE` | 2 | PowerShell 自动模式 | +| `IS_LIBC_MUSL` | 1 | 检测 musl libc | +| `IS_LIBC_GLIBC` | 1 | 检测 glibc | +| `NATIVE_CLIENT_ATTESTATION` | 1 | 原生客户端认证头 | +| `ALLOW_TEST_VERSIONS` | 2 | 允许测试版本 | + +**杂项(5 个)** + +| Flag | 次 | 用途 | +|---|---|---| +| `BYOC_ENVIRONMENT_RUNNER` | 1 | Bring-Your-Own-Compute runner | +| `SELF_HOSTED_RUNNER` | 1 | 自托管 runner | +| `UNATTENDED_RETRY` | 1 | 无人值守重试 | +| `TORCH` | 1 | Torch 调试探针 | +| `BUILDING_CLAUDE_APPS` | 1 | 构建 Claude apps 工作流 | + +合计:Top 15 共 16 项(其中 `BUDDY` 与下方"UI"组重复出现,故按 16 - 1 = 15 主条目 + 16 行展示),加上以上 16 个主题组内 73 + 1(`BUDDY` 已计) = 74 个剩余 flag,正好覆盖完整的 89 个。这张速查表的目的是:**当你在源码里看到 `feature('XYZ')` 时,可以一眼定位它属于哪条产品线**,而不必把整张表重新 grep 一遍。 ### 1.4 feature() 的全栈影响 diff --git "a/docs/30-Doctor\345\261\217\344\270\216OutputStyle\344\275\223\351\252\214.md" "b/docs/30-Doctor\345\261\217\344\270\216OutputStyle\344\275\223\351\252\214.md" index b2283af..10df7e1 100644 --- "a/docs/30-Doctor\345\261\217\344\270\216OutputStyle\344\275\223\351\252\214.md" +++ "b/docs/30-Doctor\345\261\217\344\270\216OutputStyle\344\275\223\351\252\214.md" @@ -1,8 +1,8 @@ -# 第 30 章:Doctor 屏与 Output Style 体验 — 给一个 CLI 装上自检仪表和换装系统 +# 第 30 章:screens/ 三屏 — Doctor、Output Style 与 ResumeConversation > 本章是《深入 Claude Code 源码》系列对终端 UI 一族的最后一篇。前面几章把 Ink 怎么把 React 搬进终端、设计系统如何收敛颜色与边距、键盘事件如何注入 React 树都讲透了。这一篇换一个角度:当 CLI 真的出问题时,用户怎么自己看清"问题在哪里";当用户想换一种说话方式时,怎么用一份 markdown 把模型的开场白替换掉。 -## 为什么把 Doctor 和 Output Style 放在同一章? +## 为什么把这三屏放在同一章? `screens/` 目录下只有三个文件:`REPL.tsx`(主回合)、`ResumeConversation.tsx`(会话恢复)、`Doctor.tsx`(自检屏)。前面三十多章已经把 REPL 拆得很彻底,本章把镜头对准另外两块——一块是**给用户的自检仪表盘**,一块是**给用户的换装系统**。 diff --git "a/docs/31-Memory\345\255\220\347\263\273\347\273\237\345\205\250\346\231\257.md" "b/docs/31-Memory\345\255\220\347\263\273\347\273\237\345\205\250\346\231\257.md" index 84bb52f..33e0355 100644 --- "a/docs/31-Memory\345\255\220\347\263\273\347\273\237\345\205\250\346\231\257.md" +++ "b/docs/31-Memory\345\255\220\347\263\273\347\273\237\345\205\250\346\231\257.md" @@ -8,15 +8,19 @@ LLM 天生是"金鱼记忆" —— 每次对话都是从零开始。用户上次 这不仅仅是用户体验问题 —— 它直接影响 AI Agent 的工作效率。没有记忆的 Agent 永远是新手:每次都要重新了解用户偏好、重新踩同样的坑、重新探索同样的代码库。 -Claude Code 的解决方案是一个**五层记忆架构**: +Claude Code 的解决方案是一个**七层记忆架构**: | 层级 | 名称 | 生命周期 | 存储位置 | 核心职责 | |------|------|---------|---------|---------| | 1 | CLAUDE.md 指令文件 | 永久 | 项目目录 / 用户目录 | 静态指令与规则 | | 2 | Auto Memory(memdir) | 跨会话 | `~/.claude/projects//memory/` | 自动提取的持久化知识 | -| 3 | Session Memory | 单次会话 | `~/.claude/projects///session-memory/summary.md` | 当前会话的结构化笔记 | -| 4 | Agent Memory | 跨会话 | 三种 scope 目录 | 特定 Agent 的专属记忆 | -| 5 | Relevant Memories | 每用户 turn 按需注入 | 内存(Attachment) | 按需召回的相关记忆 | +| 3 | Background Extract Memories | 跨会话(异步抽取) | 同 memdir,触发自后台 worker | 在对话进行中**异步**从 transcript 提取候选记忆并落盘 | +| 4 | Session Memory | 单次会话 | `~/.claude/projects///session-memory/summary.md` | 当前会话的结构化笔记 | +| 5 | Agent Memory | 跨会话 | 三种 scope 目录 | 特定 Agent 的专属记忆 | +| 6 | Relevant Memories | 每用户 turn 按需注入 | 内存(Attachment) | 按需召回的相关记忆 | +| 7 | Auto Dream | 会话空闲时触发 | 写回 memdir / Agent memory | 把多次会话的记忆做巩固、去重、改写("做梦") | + +附(§十)会单独讲一块**不属于记忆层**但常被一并问到的东西:远程会话历史的分页回放。它读的是会话 transcript 而不是 memory,放在最后作为补充。 每一层解决不同时间尺度和不同粒度的记忆需求。接下来我们逐层深入。 @@ -688,7 +692,7 @@ graph TD --- -## 十、最后一块拼图:远程会话历史的分页回放 +## 附 · 最后一块拼图:远程会话历史的分页回放 前九节讲的是"本机文件系统上的记忆"。但 Claude Code 还支持把会话状态保存到云端,然后在另一台设备上接着聊(Resume Conversation 屏的远端模式就走这条路径)。负责把云端 session 的事件流捞回本地的,是 `assistant/sessionHistory.ts` —— 整个模块只有 87 行,干净到值得整段读完,但它在这里被点名是因为它定义了"什么算是这场会话的可访问历史"。 From b41ee6c65bc0a71c4fa232fd00919a96c70e7c44 Mon Sep 17 00:00:00 2001 From: Yao Lu Date: Wed, 27 May 2026 19:11:57 +0800 Subject: [PATCH 2/3] =?UTF-8?q?docs(s2):=20address=20OC-R=20blocking=20ite?= =?UTF-8?q?ms=20=E2=80=94=2011=20=C2=A72.4=20/=2022=20=C2=A71.3=20/=2030?= =?UTF-8?q?=20=E7=AB=A0=E9=A6=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 11 §2.4:消除 7 步 vs 8 行 + 5b 错位。把骨架表统一为 8 步(5b → 6、6→7、7→8),文案改为「8 步骨架」并把 passthrough 兜底(1165-1177)从编号中拆出为「8 步之外的兜底分支」 - 22 §1.3:消除 Top 15 vs 16 行错位。承诺改为 Top 16 + 剩余 73;§1.3.1 标题与合计句改为 73;BUDDY 在 UI 组明确「不重复计数」 - 30 章首:把 Doctor / ResumeConversation / Output Style 三块在「为什么把这三屏放在同一章?」节里正面列齐,导读不再只解释「另外两块」 Co-authored-by: multica-agent --- ...11-BashTool-PowerShellTool-\345\217\214shell.md" | 13 +++++++------ ...\257\221\346\234\237\344\274\230\345\214\226.md" | 12 ++++++------ ...4\270\216OutputStyle\344\275\223\351\252\214.md" | 4 ++-- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git "a/docs/11-BashTool-PowerShellTool-\345\217\214shell.md" "b/docs/11-BashTool-PowerShellTool-\345\217\214shell.md" index 894766f..e1f59d8 100644 --- "a/docs/11-BashTool-PowerShellTool-\345\217\214shell.md" +++ "b/docs/11-BashTool-PowerShellTool-\345\217\214shell.md" @@ -299,7 +299,7 @@ const BASH_SECURITY_CHECK_IDS = { ### 2.4 每子命令权限判定中的只读验证(readOnlyValidation.ts) -只读验证**并非主链路的独立层**,而是在每个子命令的权限判定函数 `bashToolCheckPermission()`(bashPermissions.ts:1050-1178)内部的第 7 步调用。整个函数的 7 步骨架(**每一步的行号区间已校对到源码**): +只读验证**并非主链路的独立层**,而是在每个子命令的权限判定函数 `bashToolCheckPermission()`(bashPermissions.ts:1050-1178)内部的第 8 步调用。整个函数的 8 步骨架(**每一步的行号区间已校对到源码**);其后还有一条 passthrough 兜底分支,不计入这 8 步: | 步骤 | 名称 | 行号区间 | 命中后行为 | | --- | --- | --- | --- | @@ -308,12 +308,13 @@ const BASH_SECURITY_CHECK_IDS = { | 3 | 路径约束检查(`checkPathConstraints`) | 1106-1122 | 非 passthrough 立即返回 | | 4 | 精确匹配 allow 规则 | 1124-1127 | allow 立即返回 | | 5 | 前缀/通配符 allow 规则 | 1129-1139 | allow 立即返回 | -| 5b | sed 约束检查(`checkSedConstraints`) | 1141-1145 | 非 passthrough 立即返回 | -| 6 | 权限模式分支(`checkPermissionMode`) | 1147-1151 | 非 passthrough 立即返回(acceptEdits/plan 等模式专用) | -| 7 | **只读验证**:`BashTool.isReadOnly(input)` → `checkReadOnlyConstraints()` | 1153-1163 | 命中即 allow(`reason: 'Read-only command is allowed'`) | -| 8 | passthrough 兜底 | 1165-1177 | 触发权限确认对话框,附带 exact-match 建议 | +| 6 | sed 约束检查(`checkSedConstraints`) | 1141-1145 | 非 passthrough 立即返回 | +| 7 | 权限模式分支(`checkPermissionMode`) | 1147-1151 | 非 passthrough 立即返回(acceptEdits/plan 等模式专用) | +| 8 | **只读验证**:`BashTool.isReadOnly(input)` → `checkReadOnlyConstraints()` | 1153-1163 | 命中即 allow(`reason: 'Read-only command is allowed'`) | -deny 规则的优先级高于只读放行:如果用户设置了 `Bash(git status)` 的 deny 规则,即使 `git status` 是只读命令也会被拒绝。同时注意第 5b/6 步在第 7 步之前——sed 危险写入和 plan 模式都能在只读放行之前先把命令拦下。 +> **兜底分支(在 8 步之外)**:passthrough(1165-1177)—— 前 8 步均未命中时触发权限确认对话框,附带 exact-match 建议。 + +deny 规则的优先级高于只读放行:如果用户设置了 `Bash(git status)` 的 deny 规则,即使 `git status` 是只读命令也会被拒绝。同时注意第 6/7 步在第 8 步之前——sed 危险写入和 plan 模式都能在只读放行之前先把命令拦下。 `readOnlyValidation.ts` 长达 1,990 行,其中大部分是命令配置。它为 100+ 个常用命令定义了"安全标志白名单"。但在判定一条命令是否只读之前,`checkReadOnlyConstraints()` 还有**一串关键的安全前置条件**(readOnlyValidation.ts:1882-1966): diff --git "a/docs/22-FeatureFlag\344\270\216\347\274\226\350\257\221\346\234\237\344\274\230\345\214\226.md" "b/docs/22-FeatureFlag\344\270\216\347\274\226\350\257\221\346\234\237\344\274\230\345\214\226.md" index 597fade..693b187 100644 --- "a/docs/22-FeatureFlag\344\270\216\347\274\226\350\257\221\346\234\237\344\274\230\345\214\226.md" +++ "b/docs/22-FeatureFlag\344\270\216\347\274\226\350\257\221\346\234\237\344\274\230\345\214\226.md" @@ -93,7 +93,7 @@ grep -rhoE "feature\(['\"]([A-Z_0-9]+)['\"]\)" --include="*.ts" --include="*.tsx | sort | uniq -c | sort -rn | wc -l ``` -得到 **89 个**独立 flag。下面先看高频 Top 15(使用次数 ≥ 16),随后给出剩下 74 个 flag 的**分类速查表**——按主题域分组,每组给出 flag 名 + 使用次数,方便按特性家族而非字母序检索。 +得到 **89 个**独立 flag。下面先看高频 Top 16(使用次数 ≥ 16),随后给出剩下 73 个 flag 的**分类速查表**——按主题域分组,每组给出 flag 名 + 使用次数,方便按特性家族而非字母序检索。 注:这里频繁出现的 `KAIROS`(希腊语「恰当时机」)出现 156 次,几乎是第二名的 1.5 倍——它对应的是 Claude Code 的**「Assistant 助手 / 聊天」模式**,一个内部大型实验功能(入口在 `assistant/index.ts`、`assistant/gate.ts`、`commands/assistantChat.tsx` 等),其下还派生出 5 个 `KAIROS_*` 子 flag(详见下面的速查表)。 @@ -116,9 +116,9 @@ grep -rhoE "feature\(['\"]([A-Z_0-9]+)['\"]\)" --include="*.ts" --include="*.tsx | `HISTORY_SNIP` | 16 | 历史片段剪辑 | | `CHICAGO_MCP` | 16 | Computer Use MCP | -#### 1.3.1 剩余 74 个 flag 的分类速查表 +#### 1.3.1 剩余 73 个 flag 的分类速查表 -按主题域分组(**逐个列出**,不再让读者自行 grep)。"次"指 `feature('X')` 在源码中的出现次数。 +按主题域分组(**逐个列出**,不再让读者自行 grep)。"次"指 `feature('X')` 在源码中的出现次数。下方各组合计 73 个唯一 flag(`BUDDY` 已在 Top 16 中、未在此重复计数)。 **Assistant / KAIROS 家族(除 KAIROS、KAIROS_BRIEF、KAIROS_CHANNELS 外的 3 个)** @@ -204,11 +204,11 @@ grep -rhoE "feature\(['\"]([A-Z_0-9]+)['\"]\)" --include="*.ts" --include="*.tsx | `UPLOAD_USER_SETTINGS` | 2 | 用户设置上传 | | `DOWNLOAD_USER_SETTINGS` | 5 | 用户设置下载 | -**UI / 终端 / 输入输出(10 个)** +**UI / 终端 / 输入输出(9 个,另含 `BUDDY` 已在 Top 16)** | Flag | 次 | 用途 | |---|---|---| -| `BUDDY` | (Top15) | Buddy 宠物 | +| `BUDDY` | (见 Top 16) | Buddy 宠物(跨主题域,归 UI 一族) | | `TERMINAL_PANEL` | 5 | 终端面板 | | `QUICK_SEARCH` | 5 | 快速搜索面板 | | `MESSAGE_ACTIONS` | 5 | 消息动作菜单 | @@ -280,7 +280,7 @@ grep -rhoE "feature\(['\"]([A-Z_0-9]+)['\"]\)" --include="*.ts" --include="*.tsx | `TORCH` | 1 | Torch 调试探针 | | `BUILDING_CLAUDE_APPS` | 1 | 构建 Claude apps 工作流 | -合计:Top 15 共 16 项(其中 `BUDDY` 与下方"UI"组重复出现,故按 16 - 1 = 15 主条目 + 16 行展示),加上以上 16 个主题组内 73 + 1(`BUDDY` 已计) = 74 个剩余 flag,正好覆盖完整的 89 个。这张速查表的目的是:**当你在源码里看到 `feature('XYZ')` 时,可以一眼定位它属于哪条产品线**,而不必把整张表重新 grep 一遍。 +合计:Top 16(16 个唯一 flag,其中 `BUDDY` 同时归入下方"UI"组但**不重复计数**) + 下方 16 个主题组共 73 个唯一 flag = 89,正好覆盖完整集合。这张速查表的目的是:**当你在源码里看到 `feature('XYZ')` 时,可以一眼定位它属于哪条产品线**,而不必把整张表重新 grep 一遍。 ### 1.4 feature() 的全栈影响 diff --git "a/docs/30-Doctor\345\261\217\344\270\216OutputStyle\344\275\223\351\252\214.md" "b/docs/30-Doctor\345\261\217\344\270\216OutputStyle\344\275\223\351\252\214.md" index 10df7e1..98066d2 100644 --- "a/docs/30-Doctor\345\261\217\344\270\216OutputStyle\344\275\223\351\252\214.md" +++ "b/docs/30-Doctor\345\261\217\344\270\216OutputStyle\344\275\223\351\252\214.md" @@ -4,9 +4,9 @@ ## 为什么把这三屏放在同一章? -`screens/` 目录下只有三个文件:`REPL.tsx`(主回合)、`ResumeConversation.tsx`(会话恢复)、`Doctor.tsx`(自检屏)。前面三十多章已经把 REPL 拆得很彻底,本章把镜头对准另外两块——一块是**给用户的自检仪表盘**,一块是**给用户的换装系统**。 +`screens/` 目录下只有三个文件:`REPL.tsx`(主回合)、`ResumeConversation.tsx`(会话恢复)、`Doctor.tsx`(自检屏)。前面三十多章已经把 REPL 拆得很彻底,本章把镜头对准剩下两块 `screens/` —— `Doctor.tsx`(**给用户的自检仪表盘**)与 `ResumeConversation.tsx`(**会话恢复拣选器**),再加上不在 `screens/` 目录、但同样面向最终用户、由一组 markdown 文件驱动的 **Output Style 换装系统**(`outputStyles/loadOutputStylesDir.ts` + `constants/outputStyles.ts`)。三块合起来构成本章标题所列的"三屏"承诺。 -它们看上去毫无关系:一个是 574 行的诊断面板(`screens/Doctor.tsx`),另一个是 markdown 驱动的 prompt 注入链路(`outputStyles/loadOutputStylesDir.ts` + `constants/outputStyles.ts`)。但拉远看,它们共用一种很少被单独强调的设计思路——**把 CLI 里那些"软参数"暴露成用户能直接看见或直接替换的东西**。Doctor 把"我是怎么被装上的、跟什么冲突、为什么自动更新不工作、MCP 工具吃了多少 token"这些藏在源码深处的运行期事实搬到一屏上;Output Style 则把"对模型说话用的那段 system prompt 的尾巴"做成了 `.claude/output-styles/*.md` 这种用户可以直接覆写的文件。两者都在回答同一个问题:**当一个 AI CLI 装得越来越复杂,怎么让用户在不读源码的前提下,自己看明白它、自己改造它**。 +它们看上去毫无关系:一个是 574 行的诊断面板(`screens/Doctor.tsx`),一个是会话拣选器(`screens/ResumeConversation.tsx`),还有一个是 markdown 驱动的 prompt 注入链路(`outputStyles/loadOutputStylesDir.ts` + `constants/outputStyles.ts`)。但拉远看,它们共用一种很少被单独强调的设计思路——**把 CLI 里那些"软参数"暴露成用户能直接看见或直接替换的东西**。Doctor 把"我是怎么被装上的、跟什么冲突、为什么自动更新不工作、MCP 工具吃了多少 token"这些藏在源码深处的运行期事实搬到一屏上;ResumeConversation 把"我之前哪些会话还能接着聊"摊到一屏让用户挑;Output Style 则把"对模型说话用的那段 system prompt 的尾巴"做成了 `.claude/output-styles/*.md` 这种用户可以直接覆写的文件。三者都在回答同一个问题:**当一个 AI CLI 装得越来越复杂,怎么让用户在不读源码的前提下,自己看明白它、自己改造它**。 本章按两条线索讲。第一节走完 Doctor 屏从 `getDoctorDiagnostic()` 到 React 树的整条路径,看一个自检屏背后实际上检了多少东西。第二节走完 Output Style 从 `.md` 文件被加载到最终拼进 system prompt 的注入链路,看一个"换装系统"真正改的是哪一段。第三节回到 `screens/` 目录本身,把 `ResumeConversation.tsx` 这条以前没在书里露过面的"会话拣选器"路径补上。最后两节把这一章的工程模式抽出来,给一个可以直接照搬的实战示例。 From 2a3b6422379e53d0bad849902d7ff140e9fc7d01 Mon Sep 17 00:00:00 2001 From: Yao Lu Date: Wed, 27 May 2026 19:21:44 +0800 Subject: [PATCH 3/3] =?UTF-8?q?docs(22):=20align=20=C2=A71.3=20flag=20coun?= =?UTF-8?q?t=20(89=E2=86=9290,=20remaining=2073=E2=86=9274)=20to=20match?= =?UTF-8?q?=20table?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit OC-R blocking: §1.3 prose said 89 unique flags / 73 remaining, but the classification table actually lists 74 (16 Top + 74 remaining = 90). Re-ran the source-side `grep | sort -u | wc -l` against `/Users/yao/work/code/awesome-project/claude-code-cli` and confirmed **90 unique flags**. Fix the prose side (total + section header + sum sentence) so all three numbers agree: 90 = 16 + 74. Co-authored-by: multica-agent --- ...0\257\221\346\234\237\344\274\230\345\214\226.md" | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git "a/docs/22-FeatureFlag\344\270\216\347\274\226\350\257\221\346\234\237\344\274\230\345\214\226.md" "b/docs/22-FeatureFlag\344\270\216\347\274\226\350\257\221\346\234\237\344\274\230\345\214\226.md" index 693b187..fce121d 100644 --- "a/docs/22-FeatureFlag\344\270\216\347\274\226\350\257\221\346\234\237\344\274\230\345\214\226.md" +++ "b/docs/22-FeatureFlag\344\270\216\347\274\226\350\257\221\346\234\237\344\274\230\345\214\226.md" @@ -83,9 +83,9 @@ const MonitorTool = feature('MONITOR_TOOL') : null ``` -### 1.3 89 个 Feature Flag 的全景 +### 1.3 90 个 Feature Flag 的全景 -**89 这个数字是怎么数出来的?** —— 直接对源码仓库(`/Users/yao/work/code/awesome-project/claude-code-cli`)跑一次: +**90 这个数字是怎么数出来的?** —— 直接对源码仓库(`/Users/yao/work/code/awesome-project/claude-code-cli`)跑一次: ```bash grep -rhoE "feature\(['\"]([A-Z_0-9]+)['\"]\)" --include="*.ts" --include="*.tsx" . \ @@ -93,7 +93,7 @@ grep -rhoE "feature\(['\"]([A-Z_0-9]+)['\"]\)" --include="*.ts" --include="*.tsx | sort | uniq -c | sort -rn | wc -l ``` -得到 **89 个**独立 flag。下面先看高频 Top 16(使用次数 ≥ 16),随后给出剩下 73 个 flag 的**分类速查表**——按主题域分组,每组给出 flag 名 + 使用次数,方便按特性家族而非字母序检索。 +得到 **90 个**独立 flag。下面先看高频 Top 16(使用次数 ≥ 16),随后给出剩下 74 个 flag 的**分类速查表**——按主题域分组,每组给出 flag 名 + 使用次数,方便按特性家族而非字母序检索。 注:这里频繁出现的 `KAIROS`(希腊语「恰当时机」)出现 156 次,几乎是第二名的 1.5 倍——它对应的是 Claude Code 的**「Assistant 助手 / 聊天」模式**,一个内部大型实验功能(入口在 `assistant/index.ts`、`assistant/gate.ts`、`commands/assistantChat.tsx` 等),其下还派生出 5 个 `KAIROS_*` 子 flag(详见下面的速查表)。 @@ -116,9 +116,9 @@ grep -rhoE "feature\(['\"]([A-Z_0-9]+)['\"]\)" --include="*.ts" --include="*.tsx | `HISTORY_SNIP` | 16 | 历史片段剪辑 | | `CHICAGO_MCP` | 16 | Computer Use MCP | -#### 1.3.1 剩余 73 个 flag 的分类速查表 +#### 1.3.1 剩余 74 个 flag 的分类速查表 -按主题域分组(**逐个列出**,不再让读者自行 grep)。"次"指 `feature('X')` 在源码中的出现次数。下方各组合计 73 个唯一 flag(`BUDDY` 已在 Top 16 中、未在此重复计数)。 +按主题域分组(**逐个列出**,不再让读者自行 grep)。"次"指 `feature('X')` 在源码中的出现次数。下方各组合计 74 个唯一 flag(`BUDDY` 已在 Top 16 中、未在此重复计数)。 **Assistant / KAIROS 家族(除 KAIROS、KAIROS_BRIEF、KAIROS_CHANNELS 外的 3 个)** @@ -280,7 +280,7 @@ grep -rhoE "feature\(['\"]([A-Z_0-9]+)['\"]\)" --include="*.ts" --include="*.tsx | `TORCH` | 1 | Torch 调试探针 | | `BUILDING_CLAUDE_APPS` | 1 | 构建 Claude apps 工作流 | -合计:Top 16(16 个唯一 flag,其中 `BUDDY` 同时归入下方"UI"组但**不重复计数**) + 下方 16 个主题组共 73 个唯一 flag = 89,正好覆盖完整集合。这张速查表的目的是:**当你在源码里看到 `feature('XYZ')` 时,可以一眼定位它属于哪条产品线**,而不必把整张表重新 grep 一遍。 +合计:Top 16(16 个唯一 flag,其中 `BUDDY` 同时归入下方"UI"组但**不重复计数**) + 下方各主题组共 74 个唯一 flag = 90,正好覆盖完整集合。这张速查表的目的是:**当你在源码里看到 `feature('XYZ')` 时,可以一眼定位它属于哪条产品线**,而不必把整张表重新 grep 一遍。 ### 1.4 feature() 的全栈影响