Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions docs/03-配置体系与企业MDM.md
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -574,7 +576,7 @@ export function startBackgroundPolling(): void {

---

## 五点五、PolicyLimits:与 Settings 平行的组织级开关
## 、PolicyLimits:与 Settings 平行的组织级开关

读到这里,你已经看到 Settings 系统通过 Policy 层把"企业管控"塞进了同一条优先级链。但还有一类管控不适合走 Settings —— 它不是"应该用哪个模型"或"允许哪些权限",而是"这个组织的用户能不能用某个产品特性"。`services/policyLimits/` 就是为这类组织级开关单独构建的一条服务。

Expand Down Expand Up @@ -646,7 +648,7 @@ export function initializePolicyLimitsLoadingPromise(): void {

---

## 五点六、SettingsSync:跨设备一致性的双向通道
## 、SettingsSync:跨设备一致性的双向通道

如果说 PolicyLimits 是"自上而下下发",`services/settingsSync/` 就是"自机器之间互相搬运"。它解决的痛点是:用户在笔记本上配的偏好(模型、Hook、Permission、`CLAUDE.md` 记忆),怎么在另一台机器(甚至是托管在云端的 Claude Code Remote)上自然出现。

Expand Down Expand Up @@ -724,7 +726,7 @@ export function redownloadUserSettings(): Promise<boolean> {

---

## 、变更检测与热更新
## 、变更检测与热更新

Settings 系统不是"启动时读一次就完了"—— 它支持运行时的配置文件变更检测和热更新。

Expand Down Expand Up @@ -870,7 +872,7 @@ export function applySettingsChange(

---

## 、安全设计:防止恶意项目配置
## 、安全设计:防止恶意项目配置

Settings 系统中有多处安全防线,防止恶意项目通过配置文件获取过高权限:

Expand Down Expand Up @@ -925,7 +927,7 @@ export type EditableSettingSource = Exclude<

---

## 、MDM 轮询:注册表和 plist 的变更检测
## 、MDM 轮询:注册表和 plist 的变更检测

文件系统的变更可以用 chokidar 监听,但 macOS plist 和 Windows 注册表的变更无法通过文件系统事件捕获。系统通过 30 分钟间隔的轮询来解决:

Expand Down Expand Up @@ -958,7 +960,7 @@ function startMdmPoll(): void {

---

## 、可迁移的设计模式
## 十一、可迁移的设计模式

### 模式 1:多层配置合并 + 类型安全

Expand Down
9 changes: 8 additions & 1 deletion docs/05-QueryEngine与对话主循环.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 驱动的状态机
Expand Down Expand Up @@ -742,7 +749,7 @@ const contextCollapse = feature('CONTEXT_COLLAPSE')

---

## 十一、回到门面:QueryEngine 这一层在做什么?
## 十一、门面层 QueryEngine.ts:会话级状态与 SDK 翻译

前面九节讲的都是"一次 turn 里发生了什么",但 `query()` 这个内核并不是 SDK 调用方直接面对的入口。介于 SDK 与 `query()` 之间,还有一层 1295 行的门面——`QueryEngine.ts`。这一层的存在感不像 `query.ts` 那么强,但它承担了几件 `query()` 故意不管的事。

Expand Down
41 changes: 37 additions & 4 deletions docs/06-SystemPrompt与OutputStyle注入.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
2. **静态与动态内容如何分离以优化缓存?** — `SYSTEM_PROMPT_DYNAMIC_BOUNDARY` 机制
3. **提示词中编码了哪些关键的行为引导技巧?** — 从安全指令到代码风格约束

章节大纲:§一 整体架构 → §二 DYNAMIC_BOUNDARY 缓存 → §三 Context 注入 → §四 行为引导 → **§五 Output Style:用户接管 system prompt 末尾** → §六 优先级体系 → §七 Subagent 增强 → §八 预取策略 → §九 可迁移模式。

---

## 一、整体架构:分段组装的 System Prompt
Expand Down Expand Up @@ -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()` 定义了一个清晰的优先级体系:

Expand Down Expand Up @@ -557,7 +590,7 @@ export function buildEffectiveSystemPrompt({

---

## 、Subagent 的 Prompt 增强
## 、Subagent 的 Prompt 增强

当一个 Agent(subagent)被创建时,它的 System Prompt 通常会经过 `enhanceSystemPromptWithEnvDetails()` 函数(`prompts.ts:760-791`)的增强:

Expand Down Expand Up @@ -608,7 +641,7 @@ Fork subagent 的 prompt 策略是**直接复用父线程已经渲染好的 syst

---

## 、预取策略:在用户打字时准备好 Prompt
## 、预取策略:在用户打字时准备好 Prompt

System Prompt 的计算不是等到用户发送消息才开始的。`main.tsx` 中的 `startDeferredPrefetches()` 会在 REPL 渲染后立即开始预取:

Expand Down Expand Up @@ -641,7 +674,7 @@ function prefetchSystemContextIfSafe(): void {

---

## 、可迁移的设计模式
## 、可迁移的设计模式

### 模式 1:静态/动态分界线 + 缓存作用域

Expand Down
15 changes: 15 additions & 0 deletions docs/07-上下文压缩家族.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 预算管理:三个关键函数
Expand Down
67 changes: 40 additions & 27 deletions docs/11-BashTool-PowerShellTool-双shell.md
Original file line number Diff line number Diff line change
Expand Up @@ -265,43 +265,56 @@ 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/<pid>/environ 读取
MALFORMED_TOKEN_INJECTION: 14, // 畸形 token 注入(绕过 shell-quote 解析)
BACKSLASH_ESCAPED_WHITESPACE: 15, // \\<space> 被用来伪装连续 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)内部的第 8 步调用。整个函数的 8 步骨架(**每一步的行号区间已校对到源码**);其后还有一条 passthrough 兜底分支,不计入这 8 步:

| 步骤 | 名称 | 行号区间 | 命中后行为 |
| --- | --- | --- | --- |
| 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 立即返回 |
| 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'`) |

1. 精确匹配 deny/ask 规则
2. 前缀/通配符 deny/ask 规则
3. 路径约束检查(`checkPathConstraints`)
4. 精确匹配 allow 规则
5. 前缀/通配符 allow 规则
6. sed 约束 + 模式检查
7. **只读验证**:`BashTool.isReadOnly(input)` → `checkReadOnlyConstraints()`
> **兜底分支(在 8 步之外)**:passthrough(1165-1177)—— 前 8 步均未命中时触发权限确认对话框,附带 exact-match 建议。

只有当前面所有规则都没有匹配时,才轮到只读验证。这意味着如果用户设置了 `Bash(git status)` 的 deny 规则,即使 `git status` 是只读命令,也会被拒绝——deny 规则的优先级高于只读放行
deny 规则的优先级高于只读放行:如果用户设置了 `Bash(git status)` 的 deny 规则,即使 `git status` 是只读命令也会被拒绝。同时注意第 6/7 步在第 8 步之前——sed 危险写入和 plan 模式都能在只读放行之前先把命令拦下

`readOnlyValidation.ts` 长达 1,990 行,其中大部分是命令配置。它为 100+ 个常用命令定义了"安全标志白名单"。但在判定一条命令是否只读之前,`checkReadOnlyConstraints()` 还有**一串关键的安全前置条件**(readOnlyValidation.ts:1882-1966):

Expand Down Expand Up @@ -575,7 +588,7 @@ if (initialResult !== null) {

### 5.3 前台/后台任务转换

BashTool 支持三种后台化方式
BashTool 支持**四种**后台化方式

1. **AI 主动后台化**:`run_in_background: true`(BashTool.tsx:989-1001)
2. **超时自动后台化**:超过默认超时时间后自动转入后台(BashTool.tsx:967-971)
Expand Down
Loading
Loading