mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-18 06:15:51 +00:00
claude-code with OpenAI mode fix
This commit is contained in:
196
docs/agent/coordinator-and-swarm.mdx
Normal file
196
docs/agent/coordinator-and-swarm.mdx
Normal file
@@ -0,0 +1,196 @@
|
||||
---
|
||||
title: "协调者与蜂群模式 - 多 Agent 高级编排"
|
||||
description: "从源码角度解析 Claude Code 多 Agent 协作:Coordinator Mode 的 System Prompt 设计、Worker 生命周期、Task 通信协议和 Swarm 蜂群的任务分配机制。"
|
||||
keywords: ["协调者模式", "蜂群模式", "Agent Swarm", "多 Agent 协作", "任务编排"]
|
||||
---
|
||||
|
||||
{/* 本章目标:从源码角度揭示 Coordinator Mode 和 Agent Swarms 的架构设计 */}
|
||||
|
||||
## 两种协作模式的架构差异
|
||||
|
||||
| 维度 | Coordinator Mode | Agent Swarms |
|
||||
|------|-----------------|--------------|
|
||||
| **门控** | `feature('COORDINATOR_MODE')` + `CLAUDE_CODE_COORDINATOR_MODE=1` | 任务系统 V2(默认启用) |
|
||||
| **拓扑** | 星型:Coordinator 居中,Worker 外围 | 网状:对等 Agent 共享任务列表 |
|
||||
| **角色** | 明确分工:Coordinator 编排、Worker 执行 | 模糊:每个 Agent 自主认领任务 |
|
||||
| **通信** | `SendMessage` 定向通信 + `<task-notification>` | 任务文件系统 + 邮箱广播 |
|
||||
| **适用** | 需要集中决策的复杂任务 | 并行度高的独立子任务 |
|
||||
|
||||
两者不是互斥的——Coordinator Mode 可以在 Swarm 架构之上运行,将 Coordinator 作为特殊的 Leader Agent。
|
||||
|
||||
## Coordinator Mode:星型编排架构
|
||||
|
||||
### 激活机制
|
||||
|
||||
```typescript
|
||||
// src/coordinator/coordinatorMode.ts:36
|
||||
export function isCoordinatorMode(): boolean {
|
||||
if (feature('COORDINATOR_MODE')) {
|
||||
return isEnvTruthy(process.env.CLAUDE_CODE_COORDINATOR_MODE)
|
||||
}
|
||||
return false // 外部构建始终 false
|
||||
}
|
||||
```
|
||||
|
||||
Coordinator Mode 需要双重门控:构建时 `feature('COORDINATOR_MODE')` 和运行时环境变量。`matchSessionMode()` 在会话恢复时自动同步模式状态——如果恢复的会话是 coordinator 模式,它会翻转环境变量以确保一致性。
|
||||
|
||||
### Coordinator 的工具集
|
||||
|
||||
Coordinator 被剥夺了所有"动手"工具,只保留编排能力:
|
||||
|
||||
| 工具 | 用途 |
|
||||
|------|------|
|
||||
| **Agent** | 启动新 Worker(`subagent_type: "worker"`) |
|
||||
| **SendMessage** | 向已有 Worker 发送后续指令 |
|
||||
| **TaskStop** | 中途停止走错方向的 Worker |
|
||||
| **subscribe_pr_activity** | 订阅 GitHub PR 事件(review comments、CI 结果) |
|
||||
|
||||
Coordinator **不写代码、不读文件、不执行命令**——它只做三件事:理解需求、分配任务、综合结果。
|
||||
|
||||
### Worker 的工具权限
|
||||
|
||||
Worker 的可用工具由 `getCoordinatorUserContext()`(`coordinatorMode.ts:80`)动态注入到 System Prompt:
|
||||
|
||||
```typescript
|
||||
// 简化模式下:只有 Bash + Read + Edit
|
||||
const workerTools = isEnvTruthy(process.env.CLAUDE_CODE_SIMPLE')
|
||||
? [BASH_TOOL_NAME, FILE_READ_TOOL_NAME, FILE_EDIT_TOOL_NAME]
|
||||
: Array.from(ASYNC_AGENT_ALLOWED_TOOLS)
|
||||
.filter(name => !INTERNAL_WORKER_TOOLS.has(name))
|
||||
```
|
||||
|
||||
`INTERNAL_WORKER_TOOLS`(TeamCreate、TeamDelete、SendMessage、SyntheticOutput)被显式排除——Worker 不能嵌套创建团队或发送消息,防止不可控的递归。
|
||||
|
||||
### Scratchpad:跨 Worker 的共享知识库
|
||||
|
||||
当 `tengu_scratch` feature flag 启用时,Coordinator 拥有一个 Scratchpad 目录:
|
||||
|
||||
```
|
||||
Scratchpad 目录:
|
||||
- Workers 可自由读写,无需权限审批
|
||||
- 用于持久化的跨 Worker 知识
|
||||
- 结构由 Coordinator 决定(无固定格式)
|
||||
```
|
||||
|
||||
这是一个关键的协作原语——Worker A 的研究结果可以写入 Scratchpad,Worker B 直接读取,无需通过 Coordinator 中转。
|
||||
|
||||
### `<task-notification>` 通信协议
|
||||
|
||||
Worker 完成后,Coordinator 收到 XML 格式的通知:
|
||||
|
||||
```xml
|
||||
<task-notification>
|
||||
<task-id>agent-a1b</task-id> ← Worker 的 agentId
|
||||
<status>completed|failed|killed</status>
|
||||
<summary>Agent "Investigate auth bug" completed</summary>
|
||||
<result>Found null pointer in src/auth/validate.ts:42...</result>
|
||||
<usage>
|
||||
<total_tokens>N</total_tokens>
|
||||
<tool_uses>N</tool_uses>
|
||||
<duration_ms>N</duration_ms>
|
||||
</usage>
|
||||
</task-notification>
|
||||
```
|
||||
|
||||
通知以 `user-role message` 形式送达,Coordinator 通过 `<task-notification>` 标签区分它和用户消息。`<task-id>` 用于 `SendMessage` 的 `to` 参数,实现定向续传。
|
||||
|
||||
### Coordinator 的核心职责:综合(Synthesis)
|
||||
|
||||
Coordinator System Prompt(`coordinatorMode.ts:111-369`,约 260 行)明确要求 Coordinator **不能懒惰地委派理解**:
|
||||
|
||||
```
|
||||
反模式(禁止):
|
||||
"Based on your findings, fix the auth bug"
|
||||
→ 把理解的责任推给了 Worker
|
||||
|
||||
正确做法:
|
||||
"Fix the null pointer in src/auth/validate.ts:42.
|
||||
The user field on Session (src/auth/types.ts:15) is
|
||||
undefined when sessions expire but the token remains cached.
|
||||
Add a null check before user.id access."
|
||||
→ Coordinator 自己理解了问题,给出精确指令
|
||||
```
|
||||
|
||||
这是 Coordinator Mode 最核心的设计约束:Coordinator 必须先理解,再分配。
|
||||
|
||||
## Agent Swarms:蜂群式协作
|
||||
|
||||
Swarm 模式基于任务系统 V2(详见[任务管理](../tools/task-management.mdx)),核心机制是**共享任务列表 + 竞争认领**:
|
||||
|
||||
### 团队初始化
|
||||
|
||||
```
|
||||
Leader 创建团队(TeamCreateTool)
|
||||
↓
|
||||
设置 teamName → setLeaderTeamName()
|
||||
↓
|
||||
所有 teammate 自动获得相同的 taskListId
|
||||
↓
|
||||
teammate 启动时:
|
||||
1. CLAUDE_CODE_TASK_LIST_ID 环境变量(显式覆盖)
|
||||
2. teammate 上下文的 teamName(共享 leader 的任务列表)
|
||||
3. CLAUDE_CODE_TEAM_NAME 环境变量
|
||||
4. leader 设置的 teamName
|
||||
5. getSessionId()(兜底)
|
||||
```
|
||||
|
||||
多级优先级确保了 Leader 和所有 Teammate 指向同一个任务列表,无需额外协调。
|
||||
|
||||
### 任务认领与竞争
|
||||
|
||||
`claimTask()` 是 Swarm 的核心并发原语:
|
||||
|
||||
```
|
||||
Teammate A 调用 TaskList → 发现 task #3 是 pending
|
||||
Teammate B 同时发现 task #3 是 pending
|
||||
↓
|
||||
两者同时尝试 TaskUpdate(task #3, {status: "in_progress"})
|
||||
↓
|
||||
文件锁 + 高水位标记保证原子性:
|
||||
- 第一个写入者获得 owner 锁定
|
||||
- 第二个写入者收到 already_claimed 错误
|
||||
↓
|
||||
获得任务的 teammate 执行工作
|
||||
↓
|
||||
完成后 TaskUpdate(task #3, {status: "completed"})
|
||||
→ 依赖此任务的其他任务自动解锁
|
||||
→ tool_result 提示 "Call TaskList to find your next task"
|
||||
```
|
||||
|
||||
### Teammate 的生命周期管理
|
||||
|
||||
```
|
||||
Teammate 异常退出
|
||||
↓
|
||||
unassignTeammateTasks()
|
||||
→ 扫描任务列表,找到 owner === teammateName 的未完成任务
|
||||
→ 重置为 pending + owner=undefined
|
||||
↓
|
||||
Leader 通过 mailbox 收到通知
|
||||
→ 重新分配或创建新 Teammate
|
||||
```
|
||||
|
||||
## 任务类型全景
|
||||
|
||||
支撑多 Agent 协作的是 7 种任务类型(`src/tasks/types.ts`):
|
||||
|
||||
| 任务类型 | 运行位置 | 状态管理 | 适用场景 |
|
||||
|----------|---------|---------|---------|
|
||||
| **LocalAgentTask** | 本地子进程 | `LocalAgentTaskState` | 标准子 Agent 任务 |
|
||||
| **LocalShellTask** | 本地 shell | `LocalShellTaskState` | 后台 shell 命令 |
|
||||
| **InProcessTeammateTask** | 同进程内 | `InProcessTeammateTaskState` | 轻量级进程内队友 |
|
||||
| **RemoteAgentTask** | 远程服务器 | `RemoteAgentTaskState` | 分布式 Agent(CCR) |
|
||||
| **DreamTask** | 后台静默 | `DreamTaskState` | 后台自主整理记忆 |
|
||||
| **LocalWorkflowTask** | 本地 | `LocalWorkflowTaskState` | 工作流编排 |
|
||||
| **MonitorMcpTask** | 本地 | `MonitorMcpTaskState` | MCP 监控任务 |
|
||||
|
||||
`InProcessTeammateTask` 与 `LocalAgentTask` 的关键差异:前者共享进程的内存空间和基础设施状态(如 MCP 连接池),但有独立的对话上下文和工具权限;后者是完全隔离的子进程,启动开销更大但更安全。
|
||||
|
||||
## Coordinator vs Swarm 的选择
|
||||
|
||||
| 场景 | 推荐模式 | 原因 |
|
||||
|------|---------|------|
|
||||
| "重构认证系统,需要多模块协调" | Coordinator | 需要集中决策,Worker 间有依赖 |
|
||||
| "修复 10 个独立的 lint 警告" | Swarm | 任务独立,可完全并行 |
|
||||
| "研究方案 A 和方案 B,然后选一个实现" | Coordinator | 先并行研究,再集中决策 |
|
||||
| "在大仓库中搜索所有 TODO 并分类" | Swarm | 无依赖,各自领任务即可 |
|
||||
194
docs/agent/sub-agents.mdx
Normal file
194
docs/agent/sub-agents.mdx
Normal file
@@ -0,0 +1,194 @@
|
||||
---
|
||||
title: "子 Agent 机制 - AgentTool 的执行链路与隔离架构"
|
||||
description: "从源码角度解析 Claude Code 子 Agent:AgentTool.call() 的完整执行链路、Fork 子进程的 Prompt Cache 共享、Worktree 隔离、工具池独立组装、以及结果回传的数据格式。"
|
||||
keywords: ["子 Agent", "AgentTool", "任务委派", "forkSubagent", "子进程隔离"]
|
||||
---
|
||||
|
||||
{/* 本章目标:从源码角度揭示子 Agent 的完整执行链路、工具隔离、通信协议和生命周期管理 */}
|
||||
|
||||
## 执行链路总览
|
||||
|
||||
一条 `Agent(prompt="修复 bug")` 调用的完整路径:
|
||||
|
||||
```
|
||||
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
|
||||
|
||||
`AgentTool.call()` 根据是否提供 `subagent_type` 走两条完全不同的路径(`AgentTool.tsx:322-356`):
|
||||
|
||||
| 维度 | 命名 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 命中率优化 |
|
||||
|
||||
Fork 路径的设计核心是 **Prompt Cache 共享**:所有 fork 子进程共享父 Agent 的完整 `assistant` 消息(所有 `tool_use` 块),用相同的占位符 `tool_result` 填充,只有最后一个 `text` 块包含各自的指令。这使得 API 请求前缀字节完全一致,最大化缓存命中。
|
||||
|
||||
```typescript
|
||||
// forkSubagent.ts:142 — 所有 fork 子进程的占位结果
|
||||
const FORK_PLACEHOLDER_RESULT = 'Fork started — processing in background'
|
||||
|
||||
// buildForkedMessages() 构建:
|
||||
// [assistant(全量 tool_use), user(placeholder_results..., 子进程指令)]
|
||||
```
|
||||
|
||||
### Fork 递归防护
|
||||
|
||||
Fork 子进程保留 Agent 工具(为了 cache-identical tool defs),但通过两道防线防止递归 fork(`AgentTool.tsx:332`):
|
||||
|
||||
1. **`querySource` 检查**(压缩安全):`context.options.querySource === 'agent:builtin:fork'`
|
||||
2. **消息扫描**(降级兜底):检测 `<fork-boilerplate>` 标签
|
||||
|
||||
## 工具池的独立组装
|
||||
|
||||
子 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` | 内容 + `<usage>` 块(token/tool_calls/duration) |
|
||||
| `async_launched` | agentId + outputFile 路径 + 操作指引 |
|
||||
| `teammate_spawned` | agent_id + name + team_name |
|
||||
| `remote_launched` | taskId + sessionUrl + outputFile |
|
||||
|
||||
对于一次性内置 Agent(Explore、Plan),`<usage>` 块被省略——每周节省约 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`)判断。
|
||||
|
||||
## 适用场景
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="并行研究" icon="magnifying-glass">
|
||||
多个 fork 子进程并行搜索不同方向,共享 Prompt Cache 前缀,只有指令不同
|
||||
</Card>
|
||||
<Card title="专业委派" icon="code-branch">
|
||||
使用命名 Agent(Explore/Plan/verification)执行专业任务,受限工具集 + 独立权限
|
||||
</Card>
|
||||
<Card title="隔离实验" icon="flask">
|
||||
`isolation: "worktree"` 在独立工作副本中尝试方案,不影响主分支
|
||||
</Card>
|
||||
<Card title="后台构建" icon="layer-group">
|
||||
`run_in_background: true` 启动长时间构建/测试任务,主 Agent 继续工作
|
||||
</Card>
|
||||
</CardGroup>
|
||||
180
docs/agent/worktree-isolation.mdx
Normal file
180
docs/agent/worktree-isolation.mdx
Normal file
@@ -0,0 +1,180 @@
|
||||
---
|
||||
title: "Worktree 隔离 - Git Worktree 实现文件级隔离"
|
||||
description: "揭秘 Claude Code 的 git worktree 隔离机制:子 Agent 如何获得独立工作空间,worktree 创建/销毁生命周期、路径命名规则和安全防护。"
|
||||
keywords: ["Worktree", "git worktree", "文件隔离", "多 Agent 隔离", "并行安全"]
|
||||
---
|
||||
|
||||
{/* 本章目标:揭示 worktree 的创建/销毁生命周期、路径命名规则、hook 机制和退出时的安全防护 */}
|
||||
|
||||
## 为什么需要文件级隔离
|
||||
|
||||
多 Agent 并行工作时,共享同一工作目录会导致三类冲突:
|
||||
|
||||
1. **写入冲突**:两个 Agent 同时编辑 `config.ts`,后写的覆盖前写的
|
||||
2. **状态干扰**:Agent A 的测试依赖某个环境状态,Agent B 的修改破坏了它
|
||||
3. **不可区分**:半完成的修改混在一起,无法分辨哪些是哪个 Agent 的
|
||||
|
||||
Git worktree 是 git 原生的解决方案——在同一个仓库中创建多个独立工作目录,每个在自己的分支上。
|
||||
|
||||
## 目录结构与命名规则
|
||||
|
||||
Worktree 文件统一存放在仓库根目录下的 `.claude/worktrees/`:
|
||||
|
||||
```
|
||||
<repo-root>/
|
||||
├── .claude/
|
||||
│ └── worktrees/
|
||||
│ ├── fix-auth-bug/ # worktree 工作目录
|
||||
│ │ ├── .git # 指向主仓库的链接文件
|
||||
│ │ └── src/... # 独立的文件系统视图
|
||||
│ └── add-dark-mode/ # 另一个 worktree
|
||||
│ └── ...
|
||||
├── src/ # 主工作目录(不受影响)
|
||||
└── .git/ # 主仓库
|
||||
```
|
||||
|
||||
分支命名规则为 `worktree/<slug>`,其中 slug 由 `validateWorktreeSlug()` 校验:每个 `/` 分隔的段只允许字母、数字、`.`、`_`、`-`,总长 ≤64 字符。未指定时使用 plan slug 自动生成。
|
||||
|
||||
## 创建流程:EnterWorktreeTool
|
||||
|
||||
`EnterWorktreeTool`(`src/tools/EnterWorktreeTool/EnterWorktreeTool.ts`)的执行链路:
|
||||
|
||||
```
|
||||
EnterWorktreeTool.call({ name? })
|
||||
↓
|
||||
1. 检查是否已在 worktree 中(防嵌套)
|
||||
↓
|
||||
2. 解析到主仓库根目录(findCanonicalGitRoot)
|
||||
如果当前已在 worktree 内,chdir 到主仓库
|
||||
↓
|
||||
3. 生成 slug(用户提供或 plan slug)
|
||||
↓
|
||||
4. createWorktreeForSession(sessionId, slug)
|
||||
├── 有 WorktreeCreate hook?
|
||||
│ └── 执行 hook,返回 hook 指定的路径(支持非 git VCS)
|
||||
└── 无 hook → git 原生路径:
|
||||
a. getOrCreateWorktree(repoRoot, slug)
|
||||
├── 快速恢复:检查 worktree 目录是否已存在
|
||||
│ └── 读取 .git 指针文件的 HEAD SHA(无子进程)
|
||||
└── 新建:
|
||||
i. mkdir .claude/worktrees/(recursive)
|
||||
ii. fetch origin/<default-branch>(有缓存则跳过)
|
||||
iii. git worktree add -b worktree/<slug> <path> <base>
|
||||
iv. performPostCreationSetup()(sparse checkout 等)
|
||||
↓
|
||||
5. 更新进程状态:
|
||||
- process.chdir(worktreePath)
|
||||
- setCwd(worktreePath)
|
||||
- setOriginalCwd(worktreePath)
|
||||
- saveWorktreeState(session) → 持久化到项目配置
|
||||
- clearSystemPromptSections() → 重新计算系统提示中的 cwd 信息
|
||||
- clearMemoryFileCaches() → 重新加载 worktree 中的 CLAUDE.md
|
||||
↓
|
||||
6. 返回 worktreePath 和 worktreeBranch
|
||||
```
|
||||
|
||||
### Hook 优先的架构
|
||||
|
||||
`createWorktreeForSession()` 首先检查 `hasWorktreeCreateHook()`——如果用户在 settings.json 中配置了 `WorktreeCreate` hook,系统完全不调用 git,而是执行 hook 命令并将返回的路径作为 worktree 路径。这允许非 git 版本控制系统(如 Pijul、Mercurial)通过 hook 接入。
|
||||
|
||||
### 快速恢复路径
|
||||
|
||||
`getOrCreateWorktree()` 有一个关键优化:如果目标路径已存在,直接读取 `.git` 指针文件获取 HEAD SHA(纯文件 I/O,无子进程),跳过整个 `fetch` + `worktree add` 流程。在大仓库中 `fetch` 需要 6-8 秒,这个优化将恢复场景的延迟降到接近 0。
|
||||
|
||||
## 退出流程:ExitWorktreeTool
|
||||
|
||||
`ExitWorktreeTool`(`src/tools/ExitWorktreeTool/ExitWorktreeTool.ts`)支持两种退出策略:
|
||||
|
||||
### keep:保留 worktree
|
||||
|
||||
```
|
||||
keepWorktree()
|
||||
↓
|
||||
1. chdir 回 originalCwd
|
||||
2. 清空 currentWorktreeSession
|
||||
3. 更新项目配置(activeWorktreeSession = undefined)
|
||||
4. worktree 目录和分支保留在磁盘上
|
||||
```
|
||||
|
||||
用户可以通过 `cd <worktreePath>` 继续工作,或稍后手动合并。
|
||||
|
||||
### remove:删除 worktree
|
||||
|
||||
有严格的**安全防护**:
|
||||
|
||||
```
|
||||
validateInput() — 第一道防线
|
||||
↓
|
||||
1. 检查是否在 EnterWorktree 创建的会话中
|
||||
(手动创建的 worktree 不会被删除)
|
||||
↓
|
||||
2. countWorktreeChanges(worktreePath, originalHeadCommit)
|
||||
├── git status --porcelain → 统计未提交文件数
|
||||
├── git rev-list --count <originalHead>..HEAD → 统计新提交数
|
||||
└── 返回 null(git 失败时)→ fail-closed(拒绝删除)
|
||||
↓
|
||||
3. 有未提交文件或新提交?
|
||||
→ 拒绝,要求 discard_changes: true 确认
|
||||
```
|
||||
|
||||
```
|
||||
call() — 实际执行
|
||||
↓
|
||||
1. 重新计数变更(validateInput 和 call 之间可能有新修改)
|
||||
2. 如果有 tmux session → killTmuxSession()
|
||||
3. cleanupWorktree()
|
||||
├── hook-based → 执行 WorktreeRemove hook
|
||||
└── git-based → git worktree remove --force + git branch -D
|
||||
4. restoreSessionToOriginalCwd()
|
||||
- setCwd(originalCwd)
|
||||
- setOriginalCwd(originalCwd)
|
||||
- 如果 projectRoot 是 worktree 时才恢复(防误触)
|
||||
- 更新 hooks config snapshot
|
||||
- 清空系统提示和 memory 缓存
|
||||
```
|
||||
|
||||
### fail-closed 设计
|
||||
|
||||
`countWorktreeChanges()` 在以下情况返回 `null`("未知,假设不安全"):
|
||||
- `git status` 或 `git rev-list` 退出非零(锁文件、损坏的索引)
|
||||
- `originalHeadCommit` 未定义(hook-based worktree 没有设置基线 commit)
|
||||
|
||||
返回 `null` 时,`validateInput` 拒绝删除——宁可让用户手动处理,也不冒险丢失工作。
|
||||
|
||||
## 与 Agent 工具的联动
|
||||
|
||||
Agent 工具(`AgentTool`)的 `isolation` 参数决定子 Agent 是否在 worktree 中运行:
|
||||
|
||||
- `isolation: "worktree"` → 调用 `createWorktreeForSession()`,子 Agent 在独立 worktree 中执行
|
||||
- 无 isolation → 子 Agent 共享主工作目录
|
||||
|
||||
子 Agent 结束时的处理:
|
||||
- **成功**:主 Agent 通过 `ExitWorktreeTool(action: "keep")` 保留 worktree,然后手动合并
|
||||
- **失败/放弃**:主 Agent 通过 `ExitWorktreeTool(action: "remove", discard_changes: true)` 清理
|
||||
|
||||
## Session 状态持久化
|
||||
|
||||
`WorktreeSession` 对象通过 `saveCurrentProjectConfig()` 持久化到磁盘,包含:
|
||||
|
||||
```typescript
|
||||
{
|
||||
originalCwd: string, // 进入 worktree 前的工作目录
|
||||
worktreePath: string, // worktree 的绝对路径
|
||||
worktreeName: string, // slug
|
||||
worktreeBranch?: string, // 分支名(如 worktree/fix-auth)
|
||||
originalBranch?: string, // 进入前的分支
|
||||
originalHeadCommit?: string, // 进入前的 HEAD commit(用于变更统计)
|
||||
sessionId: string, // 创建此 worktree 的会话 ID
|
||||
tmuxSessionName?: string, // 关联的 tmux session
|
||||
hookBased?: boolean, // 是否由 hook 创建
|
||||
creationDurationMs?: number, // 创建耗时(分析用)
|
||||
}
|
||||
```
|
||||
|
||||
这使得 session 恢复(`--resume`)时能正确还原 worktree 上下文——即使进程重启,`getCurrentWorktreeSession()` 从项目配置中读取状态。
|
||||
|
||||
## Sparse Checkout 优化
|
||||
|
||||
对于大型 monorepo,worktree 支持 `sparsePaths` 配置——只检出特定目录而非整个仓库。这在 210K 文件的仓库中将 worktree 创建时间从数十秒降到几秒。
|
||||
|
||||
配置位于 `getInitialSettings().worktree?.sparsePaths`,在 `performPostCreationSetup()` 中应用。
|
||||
Reference in New Issue
Block a user