← 返回列表
未验证
面向 DeepSeek Harness 的 对话 ↔ 工作树绑定…
尚未跑自动兼容性验证,可查看页面内的依赖与入口分析。 · 最近上游提交 2026/8/15 · 已提供中文文档
DeepSeek Harness 插件:一键将每个对话绑定到其独立的 git worktree + 分支,让并行 agent 在同一个仓库上互不干扰地工作——每个项目的 worktree 都在侧边栏工作区树中自动管理,并由 9 个 git 工具提供支持。
综合分
28.1
GitHub 分
28.1
用户评分
—
★ Stars
2
周下载量
—
安装插件(需先安装 dsh CLI 引擎:npm install -g @deepseek-ai/dsh)
dsh plugin --profile web add KHG420/git-worktree该插件未发布到 npm,走 GitHub 源安装(pnpm 若拦截 prepare 脚本,按其提示在 pnpm-workspace.yaml 的 allowBuilds 中放行后重跑)
数据截至 2026/9/18(元数据每日更新 · 实装验证按队列轮转,单条结论的验证时间见上方)
依赖的 DSH / Cordis 模块
@deepseek-ai/dsh-tools@deepseek-ai/schemastery@deepseek-ai/cordis@deepseek-ai/dsh-host-webserver@deepseek-ai/dsh-subprocess用户评分
还没有人投票,来当第一个
订阅周报,不错过优质插件更新
每周一封 · 高评分插件 + 新用户活动
README
dsh-git-worktree
面向 DeepSeek Harness 的 对话 ↔ 工作树绑定 插件:让多个会话在同一项目上并行工作,每个会话绑定一个独立 git 工作树 + 分支,一键创建、互不干扰。
DeepSeek Harness 插件:一键将每个对话绑定到其独立的 git 工作树 + 分支,使多个 agent 可以在同一个仓库上并行工作而互不干扰——绑定管理面板 + 9 个 git 工具。
- 绑定 / Binding — 会话在创建时即绑定到专属工作树:工作树 + 工作区 + 会话一次成型(绑定关系可推导、可展示、可清理)。
- Agent 工具 / Agent tools — 9 个工具(git_session_binding、git_repo_status、git_worktree_、git_branch_),每个会话都可以调用。9 个工具(git_session_binding、git_repo_status、git_worktree_、git_branch_),每个对话都可以调用。
- 左侧工作区树 / The sidebar workspace tree — 唯一的管理界面:每个项目自动检测全部 git 工作树并显示为嵌套子文件夹(主工作树 = 项目文件夹本身,标记 主工作树),工作树副文件夹上可新建会话(出生即绑定)或 删除工作树(含绑定会话提示/归档)。唯一的管理界面:每个项目自动检测其所有 git 工作树并显示为嵌套文件夹(主工作树就是项目文件夹,标记为 主工作树);工作树子文件夹可以启动一个对话(出生即绑定)或 删除工作树(带绑定对话警告/归档)。
绑定是什么 / What a binding is
在 DSH 中,会话的工作目录(cwd)在创建时冻结,工作区归属要求“会话 cwd == 工作区路径”。因此绑定只能在创建会话的那一刻发生——把会话建在专属工作树里,绑定关系即成立:
DSH 会话的 cwd 在创建时冻结,工作区成员资格要求会话的规范 cwd 等于工作区路径。因此绑定发生在创建时:对话诞生于其自己的工作树中:
~/projects/foo/ ← 主工作树 / primary worktree (main)
├── .dsh-wt/
│ ├── feature-a/ ← 会话 A 绑定 / conversation A is bound here (branch feature-a)
│ └── bugfix-b/ ← 会话 B 绑定 / conversation B is bound here (branch bugfix-b)
└── (共享同一个 .git / one shared .git)
绑定关系无需额外存储,由 sessions.list(cwd)× git worktree list(路径→分支)× workspaces.list 推导而来——左侧树与 agent 工具看到的都是实时事实。
绑定是推导出来的,而非存储的:sessions.list(cwd)× git worktree list(路径→分支)× workspaces.list——侧边栏树和 agent 工具显示的都是实时事实。
一键创建绑定会话 / One-click bound conversation
点击项目文件夹(仓库)行的 +,输入功能名,点 创建绑定会话:
1. git worktree add -b /.dsh-wt/(重名自动加后缀 -2、-3…)
2. workspaces.create({ path })(幂等注册工作区)
3. connectWorkspace → 会话直接诞生在该工作树(绑定成立)
4. 自动打开该会话
在侧边栏树上,点击项目(仓库)文件夹上的 +,输入功能名,点击 创建绑定会话 (Create bound conversation):
1. git worktree add -b /.dsh-wt/(名称冲突自动加后缀 -2、-3……)
2. workspaces.create({ path })(幂等注册工作区)
3. connectWorkspace → 会话诞生于该工作树中(即绑定)
4. 新对话自动打开
已存在的工作树副文件夹上的 + 是「在该工作树新建会话」——会话出生即绑定(agent 先建好工作树、开发者一键确认的场景)。副文件夹行还有 删除工作树 按钮:确认框列出绑定的会话、可一并归档(日志保留,侧边栏隐藏)、删除 git 工作树并注销其文件夹。
工作树子文件夹的 + 会在该工作树中开启新对话——生来即绑定(agent 准备 / 开发者确认流程)。该子文件夹行还带有 删除工作树 (Remove worktree):确认时会列出已绑定的对话,可以将其归档(日志保留;侧边栏隐藏它们),移除 git 工作树并注销其文件夹。
安装 / Installation
在插件目录下执行 / from the plugin checkout
dsh plugin --profile web add /path/to/dsh-git-worktree
重启 dsh web — 工具与树界面同时生效 / restart dsh web — tools and the tree surface activate together
卸载 / Removal: dsh plugin --profile web remove dsh-git-worktree。
工具 / Tools
所有工具都接受 repo 参数(默认取会话工作区;相对路径会基于它解析)并返回 JSON。git 分支/工作树是共享可变状态,故都不声明并发安全。
All tools accept repo (default: the session workspace; a relative path resolves against it) and return JSON. None is concurrency-safe by design — branch/worktree state is shared mutable state.
| 工具 | 用途 |
|---|---|
| git_session_binding | 开场必查:本会话的绑定——仓库、所在工作树、检出分支、peer 工作树。bound 仅在拥有专属(非主)工作树时为 true。 |
| git_repo_status | 分支、领先/落后、脏文件;查询本会话所在仓库时附带 binding 块。 |
| git_worktree_list | 列出所有工作树(含分支/HEAD),标记 主工作树 与 当前会话。 |
| git_worktree_add | 创建工作树。name → /.dsh-wt/(锚定主仓库根,从工作树内创建也不会嵌套);或显式 path。unique 自动去重(-2、-3…);显式 newBranch 冲突则报 git 原错。⚠️ 显式 path 建在仓库外时,侧边栏不会把它嵌套在项目文件夹下(见下文"工作树目录位置决定树形")。 |
| git_worktree_remove | 删除工作树(分支保留)。只接受已注册的工作树;主工作树会被拒绝;force 可删除含未提交改动的工作树。 |
| git_branch_list | 列出分支:短 sha、检出标记(调用者所在工作树的 HEAD)、上游;all 包含远程分支。 |
| git_branch_create | 创建分支;switch 立即检出;from 指定基于哪个提交/分支。 |
| git_branch_switch | 切换当前工作树的分支;create 表示不存在时先创建。 |
| git_branch_delete | 删除分支;force = -D。git 会拒绝删除在任何工作树中已检出的分支——这是保护机制,直接呈现而非绕过。 |
| Tool | Purpose |
|---|---|
| git_session_binding | Call first: this conversation's binding — repo, the worktree its workspace lives in, the checked-out branch, peer worktrees. bound is true only with a dedicated (non-primary) worktree. |
| git_repo_status | Branch, ahead/behind, dirty entries; attaches the session's binding when querying its own repo. |
| git_worktree_list | Every worktree with branch/HEAD, marked primary and this session. |
| git_worktree_add | Create a worktree. name → /.dsh-wt/ (anchored to the main repo root, never nested inside the caller's worktree); or explicit path. unique auto-dedupes (-2, -3, …); explicit newBranch collisions surface git's error. ⚠️ A worktree created outside the repo via explicit path will not nest under the project folder in the sidebar (see "Placement decides the tree shape" below). |
| git_worktree_remove | Remove a worktree (branch kept). Only registered worktrees accepted; the primary worktree is refused; force removes uncommitted changes. |
| git_branch_list | Branches with short sha, checked-out marker (the caller worktree's HEAD), upstream; all includes remotes. |
| git_branch_create | 创建分支;switch 将其检出;from 选择基础分支。 |
| git_branch_switch | 切换当前工作树的分支;create 会先创建它。 |
| git_branch_delete | 删除分支;force = -D。Git 会拒绝删除在任何地方已检出的分支——这一保护会被呈现出来,而非被绕过。 |
左侧工作区树:自动检测全部工作树 / The sidebar tree: every worktree auto-detected
右下角的 Bindings 面板已移除:工作区树是唯一的管理界面。浏览器端注册两个 slot:
The sidebar footer Bindings panel is gone: the workspace tree is the single surface. The browser half registers two slots:
- sidebar.footer.action(渲染为空 / renders nothing) — 一个无 UI 的同步挂载点。监听工作区列表,对每个工作区调用 /dsh-git-worktree/list:确保仓库根(主工作树)工作区存在并标记 (主工作树),确保每个工作树路径都注册为工作区(树按路径自动嵌套成子文件夹);失效清扫——auto 注册的、工作树已从 git 消失且无会话的文件夹会被自动注销(跟随 git)。20 秒静默轮询覆盖 agent 工具/CLI 在工作树层面的改动。A renderless sync mount. Watches the workspace list, queries /dsh-git-worktree/list per workspace: ensures the repo-root (main worktree) workspace exists and is marked (主工作树), ensures every worktree path is registered as a workspace (the tree nests them by path); stale sweep — auto-registered folders whose worktree disappeared from git and hold no sessions are unregistered again (the tree follows git). A 20s quiet poll covers worktree-level changes made by agent tools/CLI.
- sidebar.workspaces.create(每行 + 链 / per-row + chain) —
- 主文件夹(仓库)+ → 「新增工作树」小窗:功能名 → 创建绑定会话 或 仅创建工作树;非 git 文件夹回落为默认「新建会话」。Repo folder + → "add worktree" popover (create bound conversation / worktree only); non-git folders fall back to the default new-session +.
- 工作树副文件夹 → 默认 +(在该工作树新建会话,出生即绑定)+ 删除工作树 按钮(确认框:绑定会话列表、可一并归档、删除 git 工作树并注销文件夹)。Worktree subfolder → default + (new conversation born bound) + 删除工作树 (Remove worktree) button (confirm: bound conversations, optional archive, removes the worktree and unregisters the folder).
- 其他嵌套文件夹 → 保持默认 +。Other nested folders keep the default +.
浏览器端通过 ctx.webServer 以同源方式调用 /dsh-git-worktree 前缀下的宿主路由(list / status / branches / add / remove / bindings)。读路由(list / status)对不在任何 git 仓库内的路径返回 { notARepo: true }(200);bindings 对每个输入路径逐条标记 notARepo/工作树归属;写操作与 agent 工具仍保持严格报错。按 dsh-host-webserver 的文档约定,这些路由没有鉴权——请保持默认的 loopback 绑定。
The browser talks to host routes under /dsh-git-worktree (list / status / branches / add / remove / bindings), served same-origin by ctx.webServer. The read routes (status, list) answer a path that is not inside a git repository with { notARepo: true } (200); bindings flags each input path per-row; mutations and the agent tools keep failing strict. Per dsh-host-webserver's documented posture these routes have no auth — keep the bind host on the loopback default.
同步是合并式的:同一时刻至多一次扫描在途,扫描期间的变更折叠为一次收尾重扫;仅当检测到的工作树集合真正变化时才发布给行级 UI(避免无谓重渲染)。
同步是合并执行的:最多只有一个扫描在进行中,后续请求会合并到末尾的一次重跑中;只有当检测到的工作树键集合确实发生变化时,才会重新发布行级 UI。
工作区树中的工作树 / Worktrees in the workspace tree
插件还注册到 sidebar.workspaces.create(工作区浏览器每行「+」的可替换链),并结合 sidebar.footer.action 的自动同步,让左侧工作区树成为完整的绑定管理界面:
- 树结构:核心 ui-workspace 按目录包含关系把工作区嵌套渲染——/.dsh-wt/ 自动成为 主文件夹下的副文件夹,其会话显示在副文件夹里。自动同步保证每个工作树(包括从未打开过会话的)都注册成副文件夹;主工作树的路径就是仓库根,因此项目文件夹即主工作树,标题标记为 (主工作树)。
- ⚠️ 工作树目录位置决定树形:核心按路径包含关系嵌套渲染(工作区路径还是 realpath 规范化的),所以"显示在主工作树子目录下"的效果只有工作树目录位于主仓库根之内(/.dsh-wt/)时才成立。若用 git_worktree_add 的显式 path 把工作树建在仓库外面(如同级目录 /),该工作树仍会被自动检测、注册并显示在侧边栏,但只能作为独立的顶层文件夹(会话绑定、分支显示、删除工作树等其余能力不受影响)——它不会嵌套在项目文件夹下。需要嵌套展示时,请把工作树放在 /.dsh-wt/ 下。
- 主文件夹 +(仓库):弹出「新增工作树」小窗,输入功能名 → 创建绑定会话(创建 .dsh-wt/ 工作树并立即创建/打开绑定会话,一键)或 仅创建工作树;非 git 目录的文件夹自动回落为默认「新建会话」。
- 副文件夹 +(工作树):保持核心默认行为——在该工作树新建会话(会话出生即绑定)。
- 副文件夹 删除工作树(仅已检测到的工作树):确认框列出绑定的会话,勾选"一并归档这些会话"后删除 git 工作树并注销其文件夹;工作树从 git 消失(agent/CLI 删除)后,无会话的文件夹由同步自动清理。
插件还注册到 sidebar.workspaces.create(工作区浏览器每行「+」的可替换链),并结合 sidebar.footer.action 的自动同步,让侧边栏树成为完整的绑定管理界面:
- 树形:核心 ui-workspace 按目录包含关系嵌套工作区——/.dsh-wt/ 渲染为 主文件夹下的子文件夹,其会话位于其中。自动同步保证每个工作树(即使从未打开过)都注册为子文件夹;主工作树的路径就是仓库根,因此项目文件夹即主工作树,标题为 (主工作树)。
- ⚠️ 放置位置决定树形:核心按路径包含关系嵌套(工作区路径经过 realpath 规范化),因此"显示在主工作树下"的效果仅当工作树目录位于主仓库根之内(/.dsh-wt/)时才成立。通过显式 git_worktree_add path 在仓库外部创建的工作树(例如同级目录 /)仍会被自动检测、注册并显示在侧边栏中——但作为其自身的顶层文件夹(绑定、分支显示和删除工作树均正常工作);它不会嵌套在项目文件夹下。要实现嵌套显示,请将工作树放在 /.dsh-wt/ 下。
- 主文件夹 +(仓库):打开一个小的"新增工作树"弹出窗口——功能名 → 创建绑定会话 (create bound conversation)(创建 .dsh-wt/ 并立即创建/打开绑定会话,一键完成)或 仅创建工作树 (worktree only);非 git 文件夹自动回退到默认的新建会话 +。
- 子文件夹 +(工作树):保持核心默认行为——新建一个出生即绑定到该工作树的会话。
- Subfolder 删除工作树 (Remove worktree)(仅检测到的工作树):确认框会列出已绑定的会话,勾选框用于将其归档,然后移除 git 工作树并注销该文件夹;在 git 侧(agent/CLI)已移除的工作树,当其文件夹中不包含任何会话时,会在同步过程中被清理。
配置 / Configuration
profile 行支持以下配置项(均可选) / The profile row accepts (all optional):
- id: git-worktree
name: dsh-git-worktree
config:
worktreesDir: .dsh-wt # 新工作树的默认父目录 / default parent dir for new worktrees
timeoutMs: 30000 # 每条 git 命令的超时时间 / per-command git timeout
stdoutMaxBytes: 1000000 # 捕获 stdout 的上限 / captured stdout cap
stderrMaxBytes: 65536 # 保留 stderr 摘要的上限 / retained stderr excerpt cap
架构 / Architecture
dsh-git-worktree/
├── index.js # Cordis 插件 / Cordis plugin: name / inject / Config / apply
├── client.js # 浏览器端 / browser half (hand-written __ModuleLoader__ bundle)
├── lib/
│ ├── git.js # 基于 ctx.subprocess 的 git 执行(无 shell 层)、路径规范化 / git runner via ctx.subprocess (no shell layer), path canon
│ ├── parse.js # worktree --porcelain / for-each-ref / status 的纯解析器 / pure parsers
│ ├── operations.js # 工具与路由共用的核心操作(含绑定解析)/ shared ops incl. binding resolution
│ ├── tools.js # defineTool 封装(面向 agent)/ defineTool wrappers (agent-facing)
│ └── routes.js # /dsh-git-worktree REST 处理器(面向浏览器端)/ REST handlers (browser-facing)
├── cordis.patch.yml # bundle patch:插入插件行 / inserts the plugin row
└── test/ # 独立功能测试 + 客户端冒烟测试 / standalone functional + client smoke tests
开发过程中确认的关键事实 / Key facts discovered while building:
- 工具注册在 host 平面(该行位于 profile 组合中),因此所有 agent 都能看到(agent → preset → global)。Tools register on the host plane (this row lives in the profile composition), so every agent sees them (agent → preset → global).
- 浏览器端 bundle 无需前端重新构建:宿主直接服务 /plugins/dsh-git-worktree/client.js,启动图加载它即可。The browser bundle needs no frontend rebuild: the host serves /plugins/dsh-git-worktree/client.js and the boot graph loads it.
- 扩展客户端 RPC API(UNARY_VALUE_SCHEMAS)需要重新构建前端——这正是浏览器端改用普通 webServer 路由的原因。Extending the client RPC API (UNARY_VALUE_SCHEMAS) would require a frontend rebuild — that is why the browser half uses plain webServer routes.
- ctx.get('webServer') 在 apply 时是 undefined(异步初始化);应改用 ctx.inject(['webServer'], cb) 等待。无头(headless)profile 下工具仍可用,只是路由不会挂载。ctx.get('webServer') is undefined at apply time (async init); wait with ctx.inject(['webServer'], cb) instead. In a headless profile the tools still work and the routes simply never mount.
- git 会用 realpath 规范化路径(例如 macOS 上 /var → /private/var);所有相等性判断都基于规范化形式。git realpath-canonicalizes paths (e.g. /var → /private/var on macOS); all equality checks run on canonical forms.
- 会话 cwd 在创建时冻结(session.header.cwd 只读),工作区归属要求 cwd == 工作区路径——绑定只能发生在创建会话时,无法迁移已存在的会话。会话的 cwd 在创建时冻结(session.header.cwd 只读),工作区归属要求 cwd == 工作区路径——绑定只发生在会话创建时;已存在的会话无法重新挂载根目录。
- git rev-parse --show-toplevel 在 linked worktree 内返回工作树根而非主仓库根;主工作树判定必须基于 --git-common-dir(主工作树路径 = common dir 的父目录)。主工作树路径是仓库根,因此 .dsh-wt/ 下的目录同时匹配主工作树和自身——取最长匹配。git rev-parse --show-toplevel 在 linked worktree 内返回工作树根;主工作树比较必须使用 --git-common-dir(主工作树 = common dir 的父目录)。由于主工作树的路径是仓库根,.dsh-wt/ 下的目录会同时匹配两者——最长匹配路径胜出。
测试 / Tests
node test/run-all.js # 全部套件一次跑完(任一失败即非零退出)/ 每个套件,失败时非零退出
node test/unit-parse.js # 纯解析器:worktree/branch/status 输出(含 unborn、detached、prunable、ahead/behind、[gone])
node test/unit-git.js # git 运行器与路径助手:exit codes、abort、信号杀死、字节上限、cwd 分类
node test/test.js # 原功能测试:9 工具 + 路由处理器(bindings、unique 去重、绑定解析)
node test/tools-edge.js # 9 工具边界矩阵:遍历防护、detach/commitIsh/force、脏工作树、上游/远程、unborn 仓库、
外部路径工作树绑定、嵌套仓库、并发 unique 竞态、already-registered 去重、脏状态矩阵
node test/routes-http.js # 真实 HTTP:方法校验(含 HEAD/OPTIONS)、坏 JSON/超大 body、空 repo 参数、+ 号编码、
严格 vs 宽容 notARepo、超时 abort
node test/client-unit.js # 客户端纯函数:sanitizeName 边界(切片尾点、HEAD、代理对、check-ref-format 性质测试)、
sessionsSame、api() 错误映射
node test/client-dom.js # jsdom 交互:自动同步(注册工作树/主工作树标记/失效清扫)、树链组件
(repo + 新建工作树、工作树副文件夹 +/删除、非工作树回落、点击外部关闭)
node test/flows.js # 真实用户操作流:一键绑定会话、agent 准备/开发者打开、全生命周期、跨仓库、
unborn 仓库首绑、双工作树同名竞态
node test/schema-conformance.js # 工具输出 vs 声明 schema(harness 的 validateJsonSchemaValue 原样复放):
9 工具全部输出必须通过 additionalProperties:false 校验——防止
遗漏 absolutePath / 可空 branch 之类的 schema 漂移在真实会话里炸掉
client-dom.js 需要 jsdom(插件本身不依赖它):从 DeepSeek Harness checkout 解析(DSH_HARNESS 指向其根目录,默认 /Users/aq/deepseek-harness),找不到时该套件自动跳过。KEEP_SCRATCH=1 可保留测试用临时仓库以便检查。扫码进群