diff --git a/docs/agent/sub-agents.mdx b/docs/agent/sub-agents.mdx
index 016b2f5..a0afc6c 100644
--- a/docs/agent/sub-agents.mdx
+++ b/docs/agent/sub-agents.mdx
@@ -1,70 +1,194 @@
---
-title: "子 Agent 机制 - AI 分身术与任务委派"
-description: "深入解析 Claude Code 子 Agent 机制:主 Agent 如何通过 AgentTool 委派子任务,子 Agent 的生命周期管理、工具继承和结果回传。"
-keywords: ["子 Agent", "Agent 分身", "任务委派", "AgentTool", "多 Agent"]
+title: "子 Agent 机制 - AgentTool 的执行链路与隔离架构"
+description: "从源码角度解析 Claude Code 子 Agent:AgentTool.call() 的完整执行链路、Fork 子进程的 Prompt Cache 共享、Worktree 隔离、工具池独立组装、以及结果回传的数据格式。"
+keywords: ["子 Agent", "AgentTool", "任务委派", "forkSubagent", "子进程隔离"]
---
-{/* 本章目标:解释子 Agent 机制的设计和应用场景 */}
+{/* 本章目标:从源码角度揭示子 Agent 的完整执行链路、工具隔离、通信协议和生命周期管理 */}
-## 为什么需要子 Agent
+## 执行链路总览
-有些任务太大,一个 AI 实例忙不过来:
+一条 `Agent(prompt="修复 bug")` 调用的完整路径:
-- "在 5 个不同的文件中分别找到并修复同类 bug"
-- "一边重构后端 API,一边更新前端调用"
-- "研究这个库的用法,同时修改我们的代码"
+```
+AI 生成 tool_use: { prompt: "修复 bug", subagent_type: "Explore" }
+ ↓
+AgentTool.call() ← 入口(AgentTool.tsx:239)
+ ├── 解析 effectiveType(fork vs 命名 agent)
+ ├── filterDeniedAgents() ← 权限过滤
+ ├── 检查 requiredMcpServers ← MCP 依赖验证(最长等 30s)
+ ├── assembleToolPool(workerPermissionContext) ← 独立组装工具池
+ ├── createAgentWorktree() ← 可选 worktree 隔离
+ ↓
+runAgent() ← 核心执行(runAgent.ts:248)
+ ├── getAgentSystemPrompt() ← 构建 agent 专属 system prompt
+ ├── initializeAgentMcpServers() ← agent 级 MCP 服务器
+ ├── executeSubagentStartHooks() ← Hook 注入
+ ├── query() ← 进入标准 agentic loop
+ │ ├── 消息流逐条 yield
+ │ └── recordSidechainTranscript() ← JSONL 持久化
+ ↓
+finalizeAgentTool() ← 结果汇总
+ ├── 提取文本内容 + usage 统计
+ └── mapToolResultToToolResultBlockParam() ← 格式化为 tool_result
+```
-## 分身术的运作方式
+## 两种子 Agent 路径:命名 Agent vs Fork
-Claude Code 中的 Agent 工具让 AI 能够**启动另一个 AI 实例**来处理子任务:
+`AgentTool.call()` 根据是否提供 `subagent_type` 走两条完全不同的路径(`AgentTool.tsx:322-356`):
-
-
- 主 Agent 判断任务可以被拆解为独立的子任务
-
-
- 通过 Agent 工具创建一个或多个子 Agent,每个子 Agent 收到一个清晰的子任务描述
-
-
- 多个子 Agent 可以同时工作,互不干扰
-
-
- 子 Agent 完成后,结果返回给主 Agent,主 Agent 汇总并呈现给用户
-
-
+| 维度 | 命名 Agent(`subagent_type` 指定) | Fork 子进程(`subagent_type` 省略) |
+|------|-------------------------------------|--------------------------------------|
+| **触发条件** | `subagent_type` 有值 | `isForkSubagentEnabled()` && 未指定类型 |
+| **System Prompt** | Agent 自身的 `getSystemPrompt()` | 继承父 Agent 的完整 System Prompt |
+| **工具池** | `assembleToolPool()` 独立组装 | 父 Agent 的原始工具池(`useExactTools: true`) |
+| **上下文** | 仅任务描述 | 父 Agent 的完整对话历史(`forkContextMessages`) |
+| **模型** | 可独立指定 | 继承父模型(`model: 'inherit'`) |
+| **权限模式** | Agent 定义的 `permissionMode` | `'bubble'`(上浮到父终端) |
+| **目的** | 专业任务委派 | Prompt Cache 命中率优化 |
-## 子 Agent 的边界
+Fork 路径的设计核心是 **Prompt Cache 共享**:所有 fork 子进程共享父 Agent 的完整 `assistant` 消息(所有 `tool_use` 块),用相同的占位符 `tool_result` 填充,只有最后一个 `text` 块包含各自的指令。这使得 API 请求前缀字节完全一致,最大化缓存命中。
-子 Agent 不是和主 Agent 完全一样的——它有明确的能力边界:
+```typescript
+// forkSubagent.ts:142 — 所有 fork 子进程的占位结果
+const FORK_PLACEHOLDER_RESULT = 'Fork started — processing in background'
-| 特性 | 主 Agent | 子 Agent |
-|------|---------|---------|
-| 可用工具 | 全部工具 | 受限子集(不能再启动子 Agent 等) |
-| 上下文 | 完整的会话历史 | 只有主 Agent 给的任务描述 |
-| 权限 | 用户设定 | 继承主 Agent 的权限,或更严格 |
-| 状态 | 可修改全局状态 | 隔离的状态空间 |
+// buildForkedMessages() 构建:
+// [assistant(全量 tool_use), user(placeholder_results..., 子进程指令)]
+```
-## 通信方式
+### Fork 递归防护
-主 Agent 和子 Agent 之间通过**消息邮箱**通信:
+Fork 子进程保留 Agent 工具(为了 cache-identical tool defs),但通过两道防线防止递归 fork(`AgentTool.tsx:332`):
-- 主 Agent 通过 `Agent` 工具启动子 Agent
-- 子 Agent 通过 `SendMessage` 工具向主 Agent 报告进度
-- 这种松耦合的通信方式让 Agent 可以异步协作
+1. **`querySource` 检查**(压缩安全):`context.options.querySource === 'agent:builtin:fork'`
+2. **消息扫描**(降级兜底):检测 `` 标签
+
+## 工具池的独立组装
+
+子 Agent 不继承父 Agent 的工具限制——它的工具池完全独立组装(`AgentTool.tsx:573-577`):
+
+```typescript
+const workerPermissionContext = {
+ ...appState.toolPermissionContext,
+ mode: selectedAgent.permissionMode ?? 'acceptEdits'
+}
+const workerTools = assembleToolPool(workerPermissionContext, appState.mcp.tools)
+```
+
+关键设计决策:
+- **权限模式独立**:子 Agent 使用 `selectedAgent.permissionMode`(默认 `acceptEdits`),不受父 Agent 当前模式的限制
+- **MCP 工具继承**:`appState.mcp.tools` 包含所有已连接的 MCP 工具,子 Agent 自动获得
+- **Agent 级 MCP 服务器**:`runAgent()` 中的 `initializeAgentMcpServers()` 可以为特定 Agent 额外连接专属 MCP 服务器
+
+### 工具过滤的 resolveAgentTools
+
+`runAgent.ts:500-502` 在工具组装后进一步过滤:
+
+```typescript
+const resolvedTools = useExactTools
+ ? availableTools // Fork: 直接使用父工具
+ : resolveAgentTools(agentDefinition, availableTools, isAsync).resolvedTools
+```
+
+`resolveAgentTools()` 会根据 Agent 定义中的 `tools` 字段过滤可用工具,将 `['*']` 映射为全量工具。
+
+## Worktree 隔离机制
+
+`isolation: "worktree"` 参数让子 Agent 在独立的 git worktree 中工作(`AgentTool.tsx:590-593`):
+
+```typescript
+const slug = `agent-${earlyAgentId.slice(0, 8)}`
+worktreeInfo = await createAgentWorktree(slug)
+```
+
+Worktree 生命周期:
+1. **创建**:在 `.git/worktrees/` 下创建独立工作副本
+2. **CWD 覆盖**:`runWithCwdOverride(worktreePath, fn)` 让所有文件操作在 worktree 中执行
+3. **路径翻译**:Fork + worktree 时注入路径翻译通知(`buildWorktreeNotice`)
+4. **清理**(`cleanupWorktreeIfNeeded`):
+ - Hook-based worktree → 始终保留
+ - 有变更 → 保留,返回 `worktreePath`
+ - 无变更 → 自动删除
+
+## 生命周期管理:同步 vs 异步
+
+### 异步 Agent(后台运行)
+
+当 `run_in_background=true` 或 `selectedAgent.background=true` 时,Agent 立即返回 `async_launched` 状态(`AgentTool.tsx:686-764`):
+
+```
+registerAsyncAgent(agentId, ...) ← 注册到 AppState.tasks
+ ↓ (void — 火后不管)
+runAsyncAgentLifecycle() ← 后台执行
+ ├── runAgent().onCacheSafeParams ← 进度摘要初始化
+ ├── 消息流迭代
+ ├── completeAsyncAgent() ← 标记完成
+ ├── classifyHandoffIfNeeded() ← 安全检查
+ └── enqueueAgentNotification() ← 通知主 Agent
+```
+
+异步 Agent 获得独立的 `AbortController`,不与父 Agent 共享——用户按 ESC 取消主线程不会杀掉后台 Agent。
+
+### 同步 Agent(前台运行)
+
+同步 Agent 的关键特性是 **可后台化**(`AgentTool.tsx:818-833`):
+
+```typescript
+const registration = registerAgentForeground({
+ autoBackgroundMs: getAutoBackgroundMs() || undefined // 默认 120s
+})
+backgroundPromise = registration.backgroundSignal.then(...)
+```
+
+在 agentic loop 的每次迭代中,系统用 `Promise.race` 竞争下一条消息和后台化信号:
+
+```typescript
+const raceResult = await Promise.race([
+ nextMessagePromise.then(r => ({ type: 'message', result: r })),
+ backgroundPromise // 超过 autoBackgroundMs 触发
+])
+```
+
+后台化后,前台迭代器被终止(`agentIterator.return()`),新的 `runAgent()` 以 `isAsync: true` 重新启动,当前台的输出文件继续写入。
+
+## 结果回传格式
+
+`mapToolResultToToolResultBlockParam()` 根据状态返回不同格式(`AgentTool.tsx:1298-1375`):
+
+| 状态 | 返回内容 |
+|------|---------|
+| `completed` | 内容 + `` 块(token/tool_calls/duration) |
+| `async_launched` | agentId + outputFile 路径 + 操作指引 |
+| `teammate_spawned` | agent_id + name + team_name |
+| `remote_launched` | taskId + sessionUrl + outputFile |
+
+对于一次性内置 Agent(Explore、Plan),`` 块被省略——每周节省约 1-2 Gtok 的上下文窗口。
+
+## MCP 依赖的等待机制
+
+如果 Agent 声明了 `requiredMcpServers`,`call()` 会等待这些服务器连接完成(`AgentTool.tsx:371-410`):
+
+```typescript
+const MAX_WAIT_MS = 30_000 // 最长等 30 秒
+const POLL_INTERVAL_MS = 500 // 每 500ms 轮询
+```
+
+早期退出条件:任何必需服务器进入 `failed` 状态时立即停止等待。工具可用性通过 `mcp__` 前缀工具名解析(`mcp__serverName__toolName`)判断。
## 适用场景
- 多个子 Agent 同时搜索不同方向的信息
+ 多个 fork 子进程并行搜索不同方向,共享 Prompt Cache 前缀,只有指令不同
-
- 把大规模修改拆分到多个子 Agent 并行执行
-
-
- 一个子 Agent 在后台运行测试,主 Agent 继续写代码
+
+ 使用命名 Agent(Explore/Plan/verification)执行专业任务,受限工具集 + 独立权限
- 在 worktree 中启动子 Agent 尝试一个方案,不影响主分支
+ `isolation: "worktree"` 在独立工作副本中尝试方案,不影响主分支
+
+
+ `run_in_background: true` 启动长时间构建/测试任务,主 Agent 继续工作
diff --git a/docs/extensibility/hooks.mdx b/docs/extensibility/hooks.mdx
index cc82b3b..7fa62e7 100644
--- a/docs/extensibility/hooks.mdx
+++ b/docs/extensibility/hooks.mdx
@@ -1,73 +1,239 @@
---
-title: "Hooks 生命周期钩子 - 自定义行为注入"
-description: "解析 Claude Code Hooks 系统:在 AI 工具调用的关键节点(PreToolUse、PostToolUse、Notification 等)插入自定义 shell 命令,实现行为定制。"
-keywords: ["Hooks", "生命周期钩子", "自定义 Hook", "行为注入", "PreToolUse"]
+title: "Hooks 生命周期钩子 - 执行引擎与拦截协议"
+description: "从源码角度解析 Claude Code Hooks 系统:22 种 Hook 事件、6 种 Hook 类型、同步/异步执行协议、JSON 输出 schema、if 条件匹配、以及 Hook 如何注入上下文和拦截工具调用。"
+keywords: ["Hooks", "生命周期钩子", "拦截器", "PreToolUse", "Hook 协议"]
---
-{/* 本章目标:解释 Hooks 系统的设计和应用场景 */}
+{/* 本章目标:从源码角度揭示 Hook 的执行引擎、匹配机制、返回值协议和生命周期管理 */}
-## 什么是 Hooks
+## 22 种 Hook 事件
-Hooks 是用户定义的 shell 命令,在 Claude Code 生命周期的特定时刻自动执行。
+Claude Code 定义了 22 种 Hook 事件(`coreTypes.ts:25-53`),覆盖完整的 Agent 生命周期:
-类比:React 的 `useEffect` 让你在组件渲染后执行自定义逻辑。Claude Code 的 Hooks 让你在 AI 的关键行为前后执行自定义脚本。
+| 阶段 | 事件 | 触发时机 | 匹配字段 |
+|------|------|---------|---------|
+| **会话** | `SessionStart` | 会话启动 | `source` |
+| | `SessionEnd` | 会话结束 | `reason` |
+| | `Setup` | 初始化完成 | `trigger` |
+| **用户交互** | `UserPromptSubmit` | 用户提交消息 | — |
+| | `Stop` | Agent 停止响应 | — |
+| | `StopFailure` | Agent 停止失败 | `error` |
+| **工具执行** | `PreToolUse` | 工具调用前 | `tool_name` |
+| | `PostToolUse` | 工具调用后(成功) | `tool_name` |
+| | `PostToolUseFailure` | 工具调用后(失败) | `tool_name` |
+| **权限** | `PermissionRequest` | 权限请求 | `tool_name` |
+| | `PermissionDenied` | 权限被拒 | `tool_name` |
+| **子 Agent** | `SubagentStart` | 子 Agent 启动 | `agent_type` |
+| | `SubagentStop` | 子 Agent 停止 | `agent_type` |
+| **压缩** | `PreCompact` | 上下文压缩前 | `trigger` |
+| | `PostCompact` | 上下文压缩后 | `trigger` |
+| **协作** | `TeammateIdle` | Teammate 空闲 | — |
+| | `TaskCreated` | 任务创建 | — |
+| | `TaskCompleted` | 任务完成 | — |
+| **MCP** | `Elicitation` | MCP 服务器请求用户输入 | `mcp_server_name` |
+| | `ElicitationResult` | Elicitation 结果返回 | `mcp_server_name` |
+| **环境** | `ConfigChange` | 配置变更 | `source` |
+| | `CwdChanged` | 工作目录变更 | — |
+| | `FileChanged` | 文件变更 | `file_path` |
+| | `InstructionsLoaded` | 指令加载 | `load_reason` |
+| | `WorktreeCreate` / `WorktreeRemove` | Worktree 操作 | — |
-## 可用的 Hook 事件
+## 6 种 Hook 类型
-| 事件 | 触发时机 | 典型用途 |
+Hooks 配置支持 6 种执行方式(`src/types/hooks.ts`):
+
+| 类型 | 执行方式 | 适用场景 |
|------|---------|---------|
-| **PreToolUse** | 工具调用前 | 拦截危险操作、自定义审批逻辑 |
-| **PostToolUse** | 工具调用后 | 记录日志、触发通知、自动格式化 |
-| **PreCompact** | 上下文压缩前 | 标记不可丢失的信息 |
-| **PostCompact** | 上下文压缩后 | 验证关键信息是否保留 |
-| **Notification** | AI 发出通知时 | 自定义通知渠道(Slack、邮件等) |
-| **StopFailure** | AI 循环异常停止时 | 自定义错误处理 |
+| `command` | Shell 命令(bash/PowerShell) | 通用脚本、CI 检查 |
+| `prompt` | 注入到 AI 上下文 | 代码规范提醒 |
+| `agent` | 启动子 Agent 执行 | 复杂分析任务 |
+| `http` | HTTP 请求 | 远程服务、Webhook |
+| `callback` | 内部 JS 函数 | 系统内置 Hook |
+| `function` | 运行时注册的函数 Hook | Agent/Skill 内部使用 |
-## Hook 的能力
+## 执行引擎:execCommandHook
-Hook 脚本不仅能"观察",还能"干预":
+`execCommandHook()`(`src/utils/hooks.ts:829-1417`)是命令型 Hook 的执行核心:
-
-
- 返回特定信号可以阻止工具调用执行
-
-
- 返回结构化的 JSON 输出,影响 Claude Code 的后续行为
-
-
- 向 AI 的对话中注入额外信息
-
-
- 调用 CI/CD、发送通知、更新 Issue tracker
-
-
+```
+execCommandHook(hook, hookEvent, hookName, jsonInput, signal)
+ ├── Shell 选择: hook.shell ?? DEFAULT_HOOK_SHELL
+ │ ├── bash: spawn(cmd, [], { shell: gitBashPath | true })
+ │ └── powershell: spawn(pwsh, ['-NoProfile', '-NonInteractive', '-Command', cmd])
+ ├── 变量替换
+ │ ├── ${CLAUDE_PLUGIN_ROOT} → pluginRoot 路径
+ │ ├── ${CLAUDE_PLUGIN_DATA} → plugin 数据目录
+ │ └── ${user_config.X} → 用户配置值
+ ├── 环境变量注入
+ │ ├── CLAUDE_PROJECT_DIR
+ │ ├── CLAUDE_ENV_FILE(SessionStart/Setup/CwdChanged/FileChanged)
+ │ └── CLAUDE_PLUGIN_OPTION_*(plugin options)
+ ├── stdin 写入: jsonInput + '\n'
+ ├── 超时: hook.timeout * 1000 ?? 600000ms(10分钟)
+ └── 异步检测: 检查 stdout 首行是否为 {"async":true}
+```
-## 配置方式
+### 异步 Hook 的检测协议
-Hooks 在 `settings.json` 中配置:
+Hook 进程的 stdout 第一行如果是 `{"async":true}`,系统将其转为后台任务(`hooks.ts:1199-1246`):
+
+```typescript
+const firstLine = firstLineOf(stdout).trim()
+if (isAsyncHookJSONOutput(parsed)) {
+ executeInBackground({
+ processId: `async_hook_${child.pid}`,
+ asyncResponse: parsed,
+ ...
+ })
+}
+```
+
+后台 Hook 通过 `registerPendingAsyncHook()` 注册到 `AsyncHookRegistry`,完成后通过 `enqueuePendingNotification()` 通知主线程。
+
+### asyncRewake:Hook 唤醒模型
+
+`asyncRewake` 模式的 Hook 绕过 `AsyncHookRegistry`。当 Hook 退出码为 2 时,通过 `enqueuePendingNotification()` 以 `task-notification` 模式注入消息,唤醒空闲的模型(通过 `useQueueProcessor`)或在忙碌时注入 `queued_command` 附件。
+
+## Hook 输出的 JSON Schema
+
+同步 Hook 的输出遵循严格的 Zod schema(`src/types/hooks.ts:49-567`):
```json
{
- "hooks": {
- "PostToolUse": [
- {
- "matcher": { "tool_name": "Write" },
- "hooks": [
- {
- "type": "command",
- "command": "npx prettier --write $CLAUDE_FILE_PATH"
- }
- ]
- }
- ]
+ "continue": false, // 是否继续执行
+ "suppressOutput": true, // 隐藏 stdout
+ "stopReason": "安全检查失败", // continue=false 时的原因
+ "decision": "approve" | "block", // 全局决策
+ "reason": "原因说明", // 决策原因
+ "systemMessage": "警告内容", // 注入到上下文的系统消息
+ "hookSpecificOutput": {
+ "hookEventName": "PreToolUse",
+ "permissionDecision": "allow" | "deny" | "ask",
+ "permissionDecisionReason": "匹配了安全规则",
+ "updatedInput": { ... }, // 修改后的工具输入
+ "additionalContext": "额外上下文" // 注入到对话
}
}
```
-这个例子:每当 AI 写入一个文件后,自动用 Prettier 格式化。
+### 各事件的 hookSpecificOutput
-## 安全控制
+| 事件 | 专有字段 | 作用 |
+|------|---------|------|
+| `PreToolUse` | `permissionDecision`, `updatedInput`, `additionalContext` | 拦截/修改工具输入 |
+| `UserPromptSubmit` | `additionalContext` | 注入额外上下文 |
+| `PostToolUse` | `additionalContext`, `updatedMCPToolOutput` | 修改 MCP 工具输出 |
+| `SessionStart` | `initialUserMessage`, `watchPaths` | 设置初始消息和文件监控 |
+| `PermissionDenied` | `retry` | 指示是否重试 |
+| `Elicitation` | `action`, `content` | 控制用户输入对话框 |
-- 托管设置(企业管理员)的 Hooks 优先级最高,用户不能覆盖
-- Hook 执行有超时限制
-- Hook 的输出会被解析和验证,防止注入攻击
+## Hook 匹配机制:getMatchingHooks
+
+`getMatchingHooks()`(`hooks.ts:1685-1956`)负责从所有来源中查找匹配的 Hook:
+
+### 多来源合并
+
+```
+getHooksConfig()
+ ├── getHooksConfigFromSnapshot() ← settings.json 中的 Hook(user/project/local)
+ ├── getRegisteredHooks() ← SDK 注册的 callback Hook
+ ├── getSessionHooks() ← Agent/Skill 前置注册的 session Hook
+ └── getSessionFunctionHooks() ← 运行时 function Hook
+```
+
+### 匹配规则
+
+`matcher` 字段支持三种模式(`matchesPattern()`, `hooks.ts:1428-1463`):
+
+```
+"Write" → 精确匹配
+"Write|Edit" → 管道分隔的多值匹配
+"^Bash(git.*)" → 正则匹配
+"*" 或 "" → 通配(匹配所有)
+```
+
+### if 条件过滤
+
+Hook 可以指定 `if` 条件,只在特定输入时触发。`prepareIfConditionMatcher()`(`hooks.ts:1472-1503`)预编译匹配器:
+
+```json
+{
+ "hooks": [{
+ "command": "check-git-branch.sh",
+ "if": "Bash(git push*)"
+ }]
+}
+```
+
+`if` 条件使用 `permissionRuleValueFromString` 解析,支持与权限规则相同的语法(工具名 + 参数模式)。Bash 工具还会使用 tree-sitter 进行 AST 级别的命令解析。
+
+### Hook 去重
+
+同一个 Hook 命令在不同配置层级(user/project/local)可能重复。系统按 `pluginRoot\0command` 做 Map 去重,保留**最后合并的层级**。
+
+## 工作区信任检查
+
+**所有 Hook 都要求工作区信任**(`shouldSkipHookDueToTrust()`, `hooks.ts:286-296`)。这是纵深防御措施——防止恶意仓库的 `.claude/settings.json` 在未信任的情况下执行任意命令。
+
+```typescript
+// 交互模式下,所有 Hook 要求信任
+const hasTrust = checkHasTrustDialogAccepted()
+return !hasTrust
+```
+
+SDK 非交互模式下信任是隐式的(`getIsNonInteractiveSession()` 为 true 时跳过检查)。
+
+## 四种 Hook 能力的源码映射
+
+### 1. 拦截操作(PreToolUse)
+
+```json
+{
+ "hookSpecificOutput": {
+ "hookEventName": "PreToolUse",
+ "permissionDecision": "deny"
+ }
+}
+```
+
+`processHookJSONOutput()` 将 `permissionDecision` 映射为 `result.permissionBehavior = 'deny'`,并设置 `blockingError`,阻止工具执行。
+
+### 2. 修改行为(updatedInput / updatedMCPToolOutput)
+
+```json
+{
+ "hookSpecificOutput": {
+ "hookEventName": "PreToolUse",
+ "updatedInput": { "command": "npm test -- --bail" }
+ }
+}
+```
+
+`updatedInput` 替换原始工具输入;`updatedMCPToolOutput`(PostToolUse 事件)替换 MCP 工具的返回值——可用于过滤敏感数据。
+
+### 3. 注入上下文(additionalContext / systemMessage)
+
+- `additionalContext` → 通过 `createAttachmentMessage({ type: 'hook_additional_context' })` 注入为用户消息
+- `systemMessage` → 注入为系统警告,直接显示给用户
+
+### 4. 控制流程(continue / stopReason)
+
+```json
+{ "continue": false, "stopReason": "构建失败,停止执行" }
+```
+
+`continue: false` 设置 `preventContinuation = true`,阻止 Agent 继续执行后续操作。
+
+## Session Hook 的生命周期
+
+Agent 和 Skill 的前置 Hook 通过 `registerFrontmatterHooks()` 注册(`runAgent.ts:567-575`),绑定到 agent 的 session ID。Agent 结束时通过 `clearSessionHooks()` 清理。
+
+```typescript
+// runAgent.ts:567 — 注册 agent 的前置 Hook
+registerFrontmatterHooks(rootSetAppState, agentId, agentDefinition.hooks, ...)
+
+// runAgent.ts:820 — finally 块清理
+clearSessionHooks(rootSetAppState, agentId)
+```
+
+这确保 Agent A 的 Hook 不会泄漏到 Agent B 的执行中。
diff --git a/docs/extensibility/mcp-protocol.mdx b/docs/extensibility/mcp-protocol.mdx
index 52a24db..126fcd4 100644
--- a/docs/extensibility/mcp-protocol.mdx
+++ b/docs/extensibility/mcp-protocol.mdx
@@ -1,68 +1,175 @@
---
-title: "MCP 协议 - 开放的工具生态扩展"
-description: "深入解析 Claude Code 的 MCP(Model Context Protocol)集成:通过标准协议对接数据库、API 和自定义服务,突破内置工具的能力边界。"
-keywords: ["MCP", "Model Context Protocol", "工具扩展", "外部集成", "API 对接"]
+title: "MCP 协议 - 连接管理、工具发现与执行链路"
+description: "从源码角度解析 Claude Code 的 MCP 集成:7 种传输层实现、connectToServer 的 memoize 缓存、工具发现的 LRU 策略、认证状态机、以及 MCP 工具如何进入权限检查链路。"
+keywords: ["MCP", "Model Context Protocol", "工具扩展", "MCP 客户端", "工具发现"]
---
-{/* 本章目标:解释 MCP 协议如何扩展 AI 的能力边界 */}
+{/* 本章目标:从源码角度揭示 MCP 客户端的连接管理、工具发现协议和执行链路 */}
-## 内置工具的局限
+## 架构总览:从配置到可用工具
-Claude Code 内置了 50+ 工具,覆盖了通用的软件开发需求。但每个团队都有特殊需求:
+```
+settings.json: { mcpServers: { "my-db": { command: "npx", args: [...] } } }
+ ↓
+getAllMcpConfigs() ← 合并 user/project/local 三级配置
+ ↓
+useManageMCPConnections() ← React Hook 管理连接生命周期
+ ↓
+connectToServer(name, config) ← memoize 缓存(lodash memoize)
+ ├── 创建 Transport(stdio/sse/http/...)
+ ├── new Client() ← @modelcontextprotocol/sdk
+ ├── client.connect(transport) ← 超时控制(MCP_TIMEOUT, 默认 30s)
+ └── 返回 MCPServerConnection ← { connected | failed | needs-auth | pending }
+ ↓
+fetchToolsForClient(client) ← LRU(20) 缓存
+ ├── client.request({ method: 'tools/list' })
+ └── 每个工具包装为 MCPTool ← 统一 Tool 接口
+ ↓
+assembleToolPool() ← 合并内置工具 + MCP 工具
+ ↓
+工具名格式: mcp____ ← buildMcpToolName()
+```
-- 连接内部数据库查询数据
-- 调用公司内部 API
-- 操作特定的 DevOps 工具
-- 访问私有的知识库
+## 7 种传输层实现
-不可能把所有人的需求都内置进去。
+`connectToServer()`(`client.ts:596-1643`)根据 `config.type` 分发到不同的 Transport 实现:
-## MCP:一个标准的"插头"
+| 传输类型 | Transport 类 | 适用场景 | 认证方式 |
+|----------|-------------|---------|---------|
+| `stdio`(默认) | `StdioClientTransport` | 本地子进程 | 无 |
+| `sse` | `SSEClientTransport` | 远程 SSE 服务 | `ClaudeAuthProvider` + OAuth |
+| `http` | `StreamableHTTPClientTransport` | HTTP 流 | `ClaudeAuthProvider` + OAuth |
+| `sse-ide` | `SSEClientTransport` | IDE 集成 | lockfile token |
+| `ws-ide` | `WebSocketTransport` | IDE WebSocket | `X-Claude-Code-Ide-Authorization` |
+| `ws` | `WebSocketTransport` | WebSocket 服务 | session ingress token |
+| `claudeai-proxy` | `StreamableHTTPClientTransport` | claude.ai 代理 | OAuth bearer + 401 重试 |
-**Model Context Protocol**(模型上下文协议)是 Anthropic 提出的开放标准,定义了 AI 与外部工具之间的通信方式。
+### stdio 传输的进程管理
-
-
-
+stdio 类型的 MCP 服务器作为子进程运行,cleanup 时采用 **信号升级策略**(`client.ts:1431-1564`):
-类比:USB 是电脑连接外设的标准接口。MCP 是 AI 连接外部能力的标准接口。
+```
+SIGINT (100ms) → SIGTERM (400ms) → SIGKILL
+```
-## 工作原理
+总清理时间上限 600ms,防止 MCP 服务器关闭阻塞 CLI 退出。
-
-
- 开发者编写一个 MCP Server,暴露自定义工具(比如"查询数据库"、"发送 Slack 消息")
-
-
- 在配置文件中声明要连接的 MCP Server
-
-
- 连接后,MCP Server 提供的工具自动出现在 AI 的可用工具列表中
-
-
- AI 像使用内置工具一样使用 MCP 工具——无需知道底层实现
-
-
+### 远程传输的认证状态机
-## 三种连接方式
+SSE/HTTP 类型使用 `ClaudeAuthProvider` 实现 OAuth 认证流程。认证失败时进入 `needs-auth` 状态,并写入 15 分钟 TTL 的缓存文件(`mcp-needs-auth-cache.json`),避免重复弹出认证提示。
-| 方式 | 适用场景 |
-|------|---------|
-| **stdio** | MCP Server 作为子进程运行,通过标准输入/输出通信。最简单 |
-| **SSE** | 通过 HTTP Server-Sent Events 通信。适合远程服务 |
-| **StreamableHTTP** | 基于 HTTP 流的双向通信。适合复杂的交互场景 |
+```
+连接尝试 → 401 Unauthorized
+ ↓
+handleRemoteAuthFailure()
+ ├── logEvent('tengu_mcp_server_needs_auth')
+ ├── setMcpAuthCacheEntry(name) ← 写入 15min TTL 缓存
+ └── return { type: 'needs-auth' } ← UI 显示认证提示
+```
-## 权限一视同仁
+## 连接缓存与重连机制
-MCP 提供的工具和内置工具一样受权限系统管控:
+`connectToServer` 使用 lodash `memoize` 缓存连接对象,缓存 key 为 `${name}-${JSON.stringify(config)}`。
-- 需要用户确认才能调用
-- 可以设置 allow/deny 规则
-- 支持沙箱限制
+### 缓存失效触发
-这确保了第三方工具不会绕过安全边界。
+当连接关闭时(`client.onclose`),清除所有相关缓存(`client.ts:1376-1404`):
-## 实际例子
+```typescript
+client.onclose = () => {
+ const key = getServerCacheKey(name, serverRef)
+ fetchToolsForClient.cache.delete(name) // 工具缓存
+ fetchResourcesForClient.cache.delete(name) // 资源缓存
+ fetchCommandsForClient.cache.delete(name) // 命令缓存
+ connectToServer.cache.delete(key) // 连接缓存
+}
+```
+
+### 连接降级检测
+
+远程传输有 **连续错误计数器**(`client.ts:1229`):
+
+```typescript
+let consecutiveConnectionErrors = 0
+const MAX_ERRORS_BEFORE_RECONNECT = 3
+```
+
+遇到终端错误(ECONNRESET、ETIMEDOUT、EPIPE 等)连续 3 次后,主动关闭 transport 触发重连。对于 HTTP 传输,还检测 session 过期(404 + JSON-RPC code -32001)。
+
+### 请求级超时保护
+
+每个 HTTP 请求使用独立的 `setTimeout` 超时(`wrapFetchWithTimeout`,`client.ts:493`),而非共享 `AbortSignal.timeout()`。原因是 Bun 对 AbortSignal.timeout 的 GC 是惰性的——每个请求约 2.4KB 原生内存,即使请求毫秒级完成也要等 60s 才回收。
+
+```typescript
+const controller = new AbortController()
+const timer = setTimeout(c => c.abort(...), MCP_REQUEST_TIMEOUT_MS, controller)
+timer.unref?.() // 不阻止进程退出
+```
+
+## 工具发现:从 MCP 到 Tool 接口
+
+`fetchToolsForClient()`(`client.ts:1745-2000`)使用 `memoizeWithLRU` 缓存(上限 20),将 MCP 工具转换为 Claude Code 的统一 Tool 接口:
+
+```typescript
+const fullyQualifiedName = buildMcpToolName(client.name, tool.name)
+// 结果: "mcp__my-db__query"
+```
+
+### 工具描述截断
+
+MCP 工具描述上限 2048 字符(`MAX_MCP_DESCRIPTION_LENGTH`)。OpenAPI 生成的 MCP 服务器曾观察到 15-60KB 的描述文档。
+
+### 工具能力标注
+
+每个 MCP 工具根据 `tool.annotations` 自动标注:
+
+| 注解 | 映射到 | 含义 |
+|------|--------|------|
+| `readOnlyHint` | `isReadOnly()` + `isConcurrencySafe()` | 只读,可并行 |
+| `destructiveHint` | `isDestructive()` | 破坏性操作 |
+| `openWorldHint` | `isOpenWorld()` | 开放世界(不可枚举) |
+| `title` | `userFacingName()` | 显示名称 |
+
+### MCP 工具的权限检查
+
+MCP 工具默认返回 `{ behavior: 'passthrough' }`(`client.ts:1816-1834`),意味着它们始终进入权限确认流程。工具名使用 `mcp__` 前缀精确匹配权限规则。
+
+## MCP 工具的执行链路
+
+```
+AI 生成 tool_use: { name: "mcp__my-db__query", input: { sql: "..." } }
+ ↓
+MCPTool.call() ← client.ts:1835
+ ├── ensureConnectedClient() ← 确保连接有效(重连)
+ ├── callMCPToolWithUrlElicitationRetry() ← 带 Elicitation 重试
+ │ ├── client.request({ method: 'tools/call' })
+ │ ├── 处理图片结果(resize + persist)
+ │ └── 内容截断(mcpContentNeedsTruncation)
+ ├── McpSessionExpiredError → 重试一次
+ └── 返回 { data: content, mcpMeta }
+```
+
+### Session 过期自动重试
+
+HTTP 传输的 MCP session 可能过期。检测到 `McpSessionExpiredError` 后自动重试一次(`client.ts:1862`),因为 `ensureConnectedClient()` 已经清除了缓存并建立了新连接。
+
+### 内容截断与持久化
+
+大型 MCP 工具输出通过 `truncateMcpContentIfNeeded` 截断,二进制内容(图片)通过 `persistBinaryContent` 写入文件并返回文件路径。图片自动 resize(`maybeResizeAndDownsampleImageBuffer`)。
+
+## MCP 连接的并发控制
+
+```typescript
+// 本地服务器并发连接数
+getMcpServerConnectionBatchSize() // 默认 3
+
+// 远程服务器并发连接数
+getRemoteMcpServerConnectionBatchSize() // 默认 20
+```
+
+本地 MCP 服务器(stdio)是重量级的子进程,默认限制 3 个并发连接。远程服务器是轻量级 HTTP 请求,允许 20 个并发。
+
+## 实际配置示例
```json
// settings.json 中的 MCP 配置
@@ -72,9 +179,13 @@ MCP 提供的工具和内置工具一样受权限系统管控:
"command": "npx",
"args": ["@my-org/db-mcp-server"],
"env": { "DB_URL": "postgres://..." }
+ },
+ "remote-api": {
+ "type": "http",
+ "url": "https://api.example.com/mcp"
}
}
}
```
-配置后,AI 就多了"查询数据库"这个能力——用自然语言描述需求,AI 自动生成查询并执行。
+配置后,AI 的工具列表中会出现 `mcp__my-database__query` 和 `mcp__remote-api__*` 工具——与内置工具使用相同的权限检查链路和 UI 渲染。