Skip to content

dsh-worktree-mode

Verified

dsh-worktree-mode · v0.1.0 · MIT · Web UI

Worktree isolation as a session mode for DeepSeek Harness: one switch at task start buys an independent copy, an independent branch and a new session — no management panel to open. · 把 git worktree 做成 DSH 的「隔离运行」开关:发起任务时自动建独立副本 + 独立分支 + 新会话。

Install

dsh plugin add dsh-worktree-mode

Confirm the layer applied with dsh --profile default --dump-config — see the install guide.

Source

Tags

Readme

dsh-worktree-mode

把 git worktree 做成 DSH 里「发起任务时的一个隔离开关」。 用户不需要知道 worktree 是什么 —— 在侧栏会话菜单里点一下「隔离运行」, 就同时拿到:一份独立副本、一个独立分支、一个新的 DSH 会话。

设计前提:注册表里已有 22 个 worktree 相关插件(最高下载 2,956), 它们几乎都在做「面板 / 管理器 / 视角」。没有一个胜出,共同点是都需要用户主动打开一个面板。 所以本插件不提供需要先打开的管理面板:所有入口都是一点即生效的动作 (侧栏会话菜单、侧栏底部按钮、/worktree 命令),面板只是可选的一览视图。


30 秒上手

  1. 安装(profile bundle 方式):

    # profiles/<name>/package.json
    "dependencies": { "dsh-worktree-mode": "link:D:/agent/dsh-worktree-mode" }
    # 并把 "dsh-worktree-mode" 加进 dsh.profile.bundles
    

    行由包内 cordis.patch.yml 插入,id: worktree-mode。

  2. 在任意会话上打开 「⋯」菜单 → 隔离运行(本会话目录)。

    插件会:

    • 在 <仓库>/.dsh-worktrees/<id> 建一份 worktree(新分支 wt/<标题>+<id>);
    • 把该目录注册成 DSH 工作区(侧栏可见,会话挂在它下面);
    • 新建一个会话,cwd 指向副本,并把标题改成「标题 @ wt_xxxxxxxx」。
  3. 收尾同样在这个菜单里:合并回主分支 / 丢弃 worktree / 复制 worktree 路径。 会话行上还会出现一个 ⎇ 徽标,会话头显示分支 + worktree 名。

想选别的仓库?菜单里的 「隔离运行(选择仓库…)」 会调用宿主原生目录选择器。


五个入口,一个语义

位置 插槽 行为
侧栏会话「⋯」菜单 sidebar.workspaces.session.menu.item 隔离运行 / 选择仓库隔离 / 合并 / 丢弃 / 复制路径
侧栏会话行 sidebar.workspaces.session.row.action ⎇ <分支> 徽标(有副本时才出现)
会话头 conversation.session.header.actions 显示分支 + worktree 名
侧栏底部 sidebar.footer.action 打开「隔离运行」面板(列表 + 新建 + 设置)
命令 /worktree new [路径] [标题] / list / status [id] / merge [id] / discard [id] [--force] / reconcile

使用的插槽全部是 replaceRisk: "none"。官方标注 shadows-shipped-ui 的 sidebar.workspaces 与 conversation.hero.workspace.directoryFlow 一律不碰。


Agent 工具

工具 作用
wt_create 建 worktree + 分支 + 新会话;非 git / 无 git 时返回 degraded 并说明「继续在原目录执行」
wt_list 列出受管副本(可按仓库 / 状态过滤)
wt_status 实时状态:未提交修改数、领先基线的提交、主工作区是否干净、会话是否在跑
wt_merge --no-ff 合并回主分支;主工作区脏则拒绝;冲突则 merge --abort 回滚并列出冲突文件
wt_discard 删 worktree + 分支;副本脏则拒绝并列出前 10 条,需显式 force
wt_reconcile 台账 / git worktree list / DSH 工作区三方对账,只报告

工具的参数 Schema 与输出校验是本仓库自实现的(lib/tools.js): 本插件不 import 任何 @deepseek-ai/*(profile 里那是断链 junction,会让整个 fiber 加载失败)。


设置

设置 → 隔离运行:

键 默认 说明
worktreeRoot '' 空 = <仓库>/.dsh-worktrees;相对路径挂在仓库下;绝对路径集中存放
branchPrefix wt/ 分支命名空间
setupScript '' 在副本里执行一次(补依赖)。.gitignore 里的 node_modules / .env 不会跟过去,所以这通常是必需的
keepLast 15 已完成副本的保留上限,超出后自动清理(脏的不动,只报告)
permissionPreset workspace-write 新会话的权限预设
autoCleanup true 创建成功后是否跑保留清理
uiEnabled true 界面入口开关(/dsh-worktree 路由与工具始终可用)

保存写入 $DSH_HOME/dsh-worktree-mode/settings.json,立即生效,无需重启 DSH。


数据与对账

$DSH_HOME/dsh-worktree-mode/state.json(DSH_HOME 为空时回退 ~/.dsh):

{
  "version": 1,
  "entries": [{
    "id": "wt_ab12cd34",
    "repoRoot": "D:/code/app",
    "worktreePath": "D:/code/app/.dsh-worktrees/wt_ab12cd34",
    "branch": "wt/修复登录+wt_ab12cd34",
    "baseCommit": "…",
    "workspaceId": "…",
    "sessionId": "…",
    "createdAt": 1760000000000,
    "status": "active | merged | discarded | orphan"
  }]
}
  • id 进任何文件路径前都按 /^wt_[a-z0-9]{8}$/ 校验(防路径穿越)。
  • 所有写操作走单写队列;落盘是「临时文件 + rename」原子替换;文件损坏时备份成 state.json.corrupt-<时间戳> 并以空台账启动,不会卡住插件加载。
  • 启动对账:git worktree list --porcelain × registry.list() × state.json 三方比对, 消失的标 orphan,git 里有而台账没有的列为「未纳管」。只报告,不自动删。

失败模式(都有处理逻辑与测试)

场景 行为
非 git 仓库 拒绝隔离,提示先 git init,原任务继续在原目录执行(不静默失败)
无 git / 不可用 降级原目录 + 一次性提示
目标分支已存在 worktree remove --force → prune → branch -f → worktree add;任一步失败整体降级
分支被别的 worktree 占用 自动派生 <branch>-2 重试
目标目录被非我登记的目录占用 换一个新 id / 新路径(连续 6 次冲突才放弃)
主工作区脏时合并 拒绝,列出脏文件数(排除 .dsh-worktrees/)
worktree 脏时删除 拒绝并列出前 10 条;仅 force: true 才删
合并冲突 先读冲突文件清单,再 merge --abort 回滚
会话正在运行 拒绝合并 / 删除,提示先中断
多仓工作区 逐仓建 worktree;第一个仓库失败则整体降级,后续仓库失败只剔除该仓
并发操作同一仓库 每仓库 in-process mutex 串行所有结构操作(worktree add/remove、merge、branch)
workspaceRegistry.setState 并发 一律走 enqueueOperation/create(),绝不直接写 state

HTTP 接口

前缀固定 /dsh-worktree(webServer.register 对重复 prefix 会抛错,所以前缀是常量, 且 disposer 一律交给 ctx.effect,热重载不会占住路由)。

  • GET /dsh-worktree — 面板页面(新建 / 列表 / 设置)
  • GET /dsh-worktree/api/config、POST 保存设置
  • GET /dsh-worktree/api/list|status|entry|workspaces
  • POST /dsh-worktree/api/create|from-session|merge|discard|cleanup|reconcile

所有 /api/* 都要求 x-dsh-worktree: 1 请求头,并拒绝 Sec-Fetch-Site: cross-site: 自定义头会强制 CORS 预检,跨站页面无法满足。


已知限制

  • ctx.logger.info 在 dsh web 终端里看不到:host 插件日志进的是桌面端自己的日志汇, 不会出现在 CLI 的 stdout。需要确认「插件真的加载了」时,设 DSH_WORKTREE_DEBUG=1, 加载行会同时写进 $DSH_HOME/dsh-worktree-mode/boot.log。
  • discard 不会删除 DSH 工作区登记(默认)。副本目录消失后,那个工作区行会指向不存在的路径; wt_reconcile 会把它列进「danglingWorkspaces」。需要一起清掉时用 API 的 dropWorkspace: true。
  • 多仓镜像里,后续仓库失败只是「不镜像」,根仓库的副本仍然创建 —— 被剔除的仓库在原文里没有隔离保护, 返回值的 skipped 会点名。
  • 自动清理只处理干净的已完成副本;有未提交修改的一律保留并在返回值里报告。
  • 本插件不新建浏览器路由之外的常驻进程,也不上传任何数据。

开发

node --test          # 112 个测试:单测(注入 fake exec,不依赖真 git)+ 集成测(真 git 全流程)

零运行时依赖、零构建步骤:lib/client.js 是手写 lazy-CJS,直接被浏览器当普通脚本执行。

License

MIT © Assuredfive