dsh-worktree-mode
Verifieddsh-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 秒上手
安装(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。在任意会话上打开 「⋯」菜单 → 隔离运行(本会话目录)。
插件会:
- 在
<仓库>/.dsh-worktrees/<id>建一份 worktree(新分支wt/<标题>+<id>); - 把该目录注册成 DSH 工作区(侧栏可见,会话挂在它下面);
- 新建一个会话,
cwd指向副本,并把标题改成「标题 @ wt_xxxxxxxx」。
- 在
收尾同样在这个菜单里:合并回主分支 / 丢弃 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|workspacesPOST /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