docs/design/worktree.md
qwen-code 目前仅有面向 Arena 多模型对比场景的内部 worktree 实现(GitWorktreeService),用户无法在普通会话中使用 worktree 隔离工作,AgentTool 也不支持为 subagent 创建隔离的 worktree 环境。
目标是将 worktree 做成通用能力,支持用户会话级隔离和 Agent 级隔离,同时保证现有 Arena 功能体验完全不变。
| 功能 | qwen-code | claude-code | 阶段 |
|---|---|---|---|
EnterWorktree 工具 | ✅(Phase A) | ✅ | — |
ExitWorktree 工具 | ✅(Phase A) | ✅ | — |
AgentTool isolation: 'worktree' | ✅(Phase B) | ✅ | — |
| 过期 worktree 自动清理 | ✅(Phase B) | ✅ | — |
| worktree 会话状态持久化与恢复 | ❌ | ✅ | Phase C |
| Post-creation setup(hooks 配置) | ❌ | ✅ | Phase C |
| StatusLine worktree 状态展示 | ❌ | ✅ | Phase C |
| WorktreeExitDialog(退出提示) | ❌ | ✅ | Phase C |
--worktree CLI 启动标志 | ❌ | ✅ | Phase D |
| 符号链接目录(node_modules 等) | ❌ | ✅ | Phase D |
| sparse checkout | ❌ | ✅ | Future |
| tmux 集成 | ❌ | ✅ | Future |
| Arena 多模型 worktree 隔离 | ✅(qwen 独有) | ❌ | — |
| 脏状态覆盖(stash + copy) | ✅ | ✅ | — |
| Baseline commit 追踪 | ✅(qwen 独有) | ❌ | — |
worktree 是通用能力,Arena 是其上层应用。
EnterWorktree/ExitWorktree 工具、AgentTool isolation 参数、会话状态管理、自动清理worktreeBaseDir 自定义路径、批量创建与 diff 对比,继续使用 GitWorktreeService.setupWorktrees() 的现有逻辑,不受通用层改动影响AgentTool 的 isolation: 'worktree' 只走通用路径,Arena 内部不经过这个参数创建 worktree,两者路径独立。
由 EnterWorktree 工具或 AgentTool isolation: 'worktree' 创建的 worktree 固定存放在:
{git 仓库根}/.qwen/worktrees/{slug}
路径不可配置。slug 命名规则:
{形容词}-{名词}-{4位随机})agent-{7位随机 hex}Arena 的 worktree 路径由 agents.arena.worktreeBaseDir 控制,默认 ~/.qwen/arena(ArenaManager.ts:125),与通用路径完全独立,不做任何改动。
| 配置项 | 类型 | 用途 | 阶段 |
|---|---|---|---|
worktree.symlinkDirectories | string[] | 符号链接指定目录(如 node_modules)到 worktree,避免磁盘浪费 | Phase D |
worktree.sparsePaths | string[] | git sparse-checkout cone 模式,大型 monorepo 只写入指定路径 | Future |
Phase A / B / C 不新增任何配置项。
触发条件: 用户明确说 "start a worktree"、"use a worktree"、"create a worktree" 等词语。不应在用户说"修复 bug"、"开发功能"时自动触发。
输入 schema:
name?: string // 可选,slug 格式:字母/数字/点/下划线/破折号,最大 64 字符
行为:
GitWorktreeService 创建 worktree,路径为 .qwen/worktrees/{slug}SessionService输出: worktreePath、worktreeBranch、message
触发条件: 用户说 "exit the worktree"、"leave the worktree"、"go back" 等。
输入 schema:
action: 'keep' | 'remove'
discard_changes?: boolean // 仅 action='remove' 时有效
安全守卫:
EnterWorktree 创建的 worktreeaction='remove' 且存在未提交变更时,拒绝执行(除非 discard_changes: true)行为:
keep:清空会话中的 worktree 状态,保留 worktree 目录和分支,恢复原始工作目录remove:删除 worktree 目录,删除对应 git 分支,清空会话状态,恢复原始工作目录输出: action、originalCwd、worktreePath、worktreeBranch
| 方式 | 示例 | 实现阶段 |
|---|---|---|
| 会话中明确请求 | 用户说 "在 worktree 中开始工作" → 模型调用 EnterWorktree | Phase A |
| Agent 隔离 | 模型为 subagent 设置 isolation: 'worktree' | Phase B |
| CLI 启动标志 | qwen --worktree my-feature | Phase D |
无斜杠命令。会话中 worktree 的触发依赖用户明确提及,isolation: 'worktree' 才是模型自主决策的场景。
目标: 用户能在会话中进入 / 退出 worktree。
要实现的功能:
EnterWorktree 工具:创建 worktree,切换工作目录,记录会话状态ExitWorktree 工具:keep / remove 两种退出方式,安全守卫GitWorktreeService 扩展:新增面向单用户会话的 createUserWorktree() / removeUserWorktree() 方法,复用现有 git 操作逻辑,不改动 Arena 使用的批量接口SessionService 扩展:新增 WorktreeSession 字段,记录 { slug, worktreePath, worktreeBranch, originalCwd, originalBranch };--resume 时恢复 worktree 工作目录影响文件:
| 文件 | 变更类型 |
|---|---|
packages/core/src/tools/tool-names.ts | 新增 ENTER_WORKTREE、EXIT_WORKTREE 常量 |
packages/core/src/tools/EnterWorktreeTool/ | 新建目录:EnterWorktreeTool.ts、prompt.ts |
packages/core/src/tools/ExitWorktreeTool/ | 新建目录:ExitWorktreeTool.ts、prompt.ts |
packages/core/src/services/gitWorktreeService.ts | 新增用户会话级接口(不改动 Arena 接口) |
packages/core/src/services/sessionService.ts | 新增 WorktreeSession 字段及读写方法 |
packages/core/src/tools/ 注册入口 | 注册新工具 |
不在 Phase A 范围内:
isolation: 'worktree')+ 描述更新目标: 模型可为 subagent 创建临时隔离 worktree,agent 结束后自动清理;同步更新受影响的工具描述和提示词。
要实现的功能:
Agent 隔离核心:
AgentTool 新增 isolation?: 'worktree' 参数agent-{7hex},路径:.qwen/worktrees/agent-{7hex}).qwen/worktrees/,匹配 agent-{7hex} 模式,超过 30 天且无未推送提交则删除,fail-closed 策略描述与提示词更新:
AgentTool description 补充 isolation: 'worktree' 参数说明(参考 claude-code AgentTool/prompt.ts:272)buildWorktreeNotice():当 fork subagent 在 worktree 中运行时,向其注入上下文提示,说明其处于隔离 worktree、路径继承自父 agent、编辑前需重新读取文件(参考 claude-code forkSubagent.ts:buildWorktreeNotice)无需改动:
SKILL.md):review 使用独立机制(路径 .qwen/tmp/review-pr-<n>,通过 qwen review fetch-pr 命令创建),与通用 worktree 路径和机制完全不同,不存在混淆Arena 兼容保证: Arena 内部不经过 isolation 参数创建 worktree,此改动不触碰 Arena 代码路径。
影响文件:
| 文件 | 变更类型 |
|---|---|
packages/core/src/tools/agent/agent.ts | 新增 isolation 参数及 worktree 创建/清理逻辑 |
packages/core/src/tools/agent/fork-subagent.ts | 新增 buildWorktreeNotice() 并在 worktree 模式下注入 |
packages/core/src/services/gitWorktreeService.ts | 新增 createAgentWorktree() / removeAgentWorktree() |
packages/core/src/services/worktreeCleanup.ts | 新建:过期 worktree 自动清理逻辑 |
目标: worktree 状态在会话中断后可恢复,用户在界面上始终知道自己在哪个 worktree 里,退出会话时有安全提示。
要实现的功能:
SessionService worktree 状态持久化 + --resume 恢复:
SessionService 扩展 WorktreeSession 字段,记录 { slug, worktreePath, worktreeBranch, originalCwd, originalBranch }EnterWorktreeTool 调用 sessionService.setWorktreeSession() 写入状态ExitWorktreeTool 调用 sessionService.clearWorktreeSession() 清除状态--resume 启动路径读取该字段,恢复 targetDir 并向模型注入上下文提示Post-creation setup:
git config core.hooksPath <mainRepo>/.git/hooks,确保 worktree 内的提交与主仓库 hooks 行为一致StatusLine worktree 展示:
UIStateContext 新增 activeWorktree 字段(从 session 状态读取),在会话进入 / 退出 worktree 时更新StatusLineCommandInput payload 新增 worktree?: { slug: string; branch: string } 字段,供用户 statusline 脚本使用Footer 在 activeWorktree 非空时内置展示一行 ⎇ <branch> (<slug>),无需用户配置 statusline 脚本即可获得基本可见性WorktreeExitDialog:
WorktreeExitDialog.tsx 组件,参考现有 Dialog 写法activeWorktree 非空时,拦截第二次确认,展示 Dialog 提示用户选择 keep 或 removeExitWorktreeTool 的现有路径影响文件:
| 文件 | 变更类型 |
|---|---|
packages/core/src/services/sessionService.ts | 新增 WorktreeSession 字段及读写方法 |
packages/core/src/tools/enter-worktree.ts | 调用 sessionService.setWorktreeSession() |
packages/core/src/tools/exit-worktree.ts | 调用 sessionService.clearWorktreeSession() |
packages/core/src/services/gitWorktreeService.ts | createUserWorktree() / createAgentWorktree() 后追加 core.hooksPath 配置 |
packages/cli/src/ui/contexts/UIStateContext.tsx | 新增 activeWorktree 字段及 set/clear action |
packages/cli/src/ui/hooks/useStatusLine.ts | StatusLineCommandInput 新增 worktree 字段 |
packages/cli/src/ui/components/Footer.tsx | 内置 worktree 行展示 |
packages/cli/src/ui/components/WorktreeExitDialog.tsx | 新建 |
packages/cli/src/ui/components/DialogManager.tsx | 注册 WorktreeExitDialog |
packages/cli/src/ui/components/ExitWarning.tsx 或退出键处理 | 检测 activeWorktree 并拦截退出 |
--worktree CLI 标志 + 目录符号链接)目标: 支持在启动时直接进入 worktree,并通过目录符号链接减少大型项目的磁盘开销。
要实现的功能:
--worktree [name] CLI 启动标志:
packages/cli/src/args.ts 新增 --worktree [name] 参数createUserWorktree(),将 targetDir 设为 worktree 路径,并写入 SessionService 状态worktree.symlinkDirectories 配置项:
worktree.symlinkDirectories: string[]createUserWorktree() 后遍历配置,调用 fs.symlink() 将主仓库目录链接进 worktree影响文件:
| 文件 | 变更类型 |
|---|---|
packages/cli/src/args.ts | 新增 --worktree [name] 参数 |
packages/cli/src/main.ts(或启动入口) | 解析 --worktree 并在主循环前创建 worktree |
packages/core/src/services/gitWorktreeService.ts | createUserWorktree() 后追加 symlink 逻辑 |
packages/core/src/config/(settings schema) | 新增 worktree.symlinkDirectories 字段 |
以下功能面向更特定的使用场景,当前阶段不纳入排期,待用户需求明确后再评估实现。
| 功能 | 说明 |
|---|---|
| sparse checkout | worktree.sparsePaths 配置项,大型 monorepo 只 checkout 指定路径,缩短创建时间和磁盘占用 |
.worktreeinclude 文件 | 将 gitignore 的文件(.env、secrets.json 等)自动复制进 worktree |
| tmux 集成 | --worktree --tmux 在新 tmux 窗口启动 worktree 会话 |
| PR 引用解析 | --worktree=#123 自动 fetch PR 分支并基于它创建 worktree(依赖 Phase D --worktree 标志) |