dsh-left-panel
DeepSeek Harness(DSH)Web 侧边栏的 git 多工作树(worktree)增强:把左侧边栏的工作区列表重组为**项目(git 仓库,仅分组)→ worktree(真实工作区,可开会话)**的两层结构,并自动跟随 git worktree 的增删注册/注销工作区。
dsh-left-panel 界面演示
当前版本 0.2.8 面向 DSH 0.2.1-rc.5 / Cordis 4.0.5;不再兼容 0.1.6-alpha.2。依赖与锁文件已整体升级,发布包包含更新后的 Host / Web 构建产物。
- 底层语义不变:每个 worktree 就是一个真实的 DSH Workspace(会话 cwd = worktree 目录),会话、日志、归档行为与原生完全一致;本插件只做注册/注销与展示分组。
- 自动同步是唯一行为:向某个 git 仓库的任意工作树添加工作区后,插件扫描
git worktree list,把该仓库其余 worktree 自动注册为工作区(以分支名命名,detached 用短 hash);worktree 被移除后自动注销(5 秒宽限,避免抖动)。主工作树永不自动注销。没有"自动/手动"开关,也不需要手动注册。
- 安全移除:普通工作区、主工作树和 linked worktree 都可删除注册记录;项目菜单的“移除项目”一次移除其全部工作区。目录、Git 分支和会话日志均保留。已移除的工作树记录保存为墓碑,不会被自动重建;项目仍存在时可从“已移除的工作区”恢复,也可用“添加工作区”重新加入指定目录,仅清除该目录的墓碑。
- 界面克制:仓库行只显示项目名(不显示数量、不显示同步状态、主工作树不加标记);仓库行仅在 git 扫描失败时出现一个
!;菜单提供重命名、刷新、移除项目及恢复已移除工作区。分组、单列表和搜索列表均不渲染底部渐变遮罩。
- 项目与工作树都能重命名:worktree 标题仍写入 DSH Workspace,但重名只在所属项目内校验,因此不同项目都能使用
main;仓库节点不是工作区记录,所以项目名存在插件自身状态里(repoNames),菜单里「重命名」改它、清空即恢复默认(主工作树目录名)。
- 复制分支路径:项目下每个 worktree(含主工作树)的操作菜单提供“复制绝对路径”,复制该工作区的完整绝对目录,不缩写为
~,也不替换成项目主目录;普通工作区和会话菜单不受影响。
- 项目分区:可创建多个界面分区,把完整项目拖入分区;分区支持独立折叠,也可拖动分区标题调整整组顺序。悬停后的操作菜单可重命名或解散。解散只移除界面分区,项目、工作区和会话都不会删除;未分区项目留在底部「项目」区。
- 刷新:仓库菜单的「刷新」立即重扫该仓库的 worktree 并回读最新快照;日常无需手动——启动、工作区变动、git 文件系统事件与 30 秒轮询都会自动对齐。
- 新会话选择器:新会话页的工作区菜单同样按「项目 → worktree」分组;项目可独立展开/收起,默认只展开当前工作区所属项目。普通工作区保持单行,添加工作区入口固定在底部。
- 拖动排序:项目节点、普通工作区行与项目内 worktree 子行都能拖动。项目节点拖动其名下整组工作区;worktree 子行只允许在所属项目内移动。两层顺序都复用 DSH 的同一条持久 Workspace 顺序,卸载插件后仍然成立。
- 既有会话迁移:注册某个 worktree 时,cwd 指向该目录的既有会话会被自动挂入这个新工作区(走官方
attachSession,cwd 校验由官方保证无法挂错)。因此你过去在没有该工作区时于 worktree 目录里创建、一直躺在"未分组"里的会话,安装后会直接出现在对应的 worktree 下——会话日志不动,只是归属到正确分组。
- 保留既有交互:客户端最初派生自官方
@deepseek-ai/dsh-client-ui-workspace 0.1.6-alpha.2(MIT),现已适配 0.2.1-rc.5 的图标、Remote 和会话导航接口;保留展开/折叠、搜索、会话打开、重命名/删除、fork、归档、拖拽排序、单列表模式。新版官方新增的行操作不会自动进入本插件的替换界面。
- 会话菜单一致:分组、单列表和搜索结果都提供右侧操作菜单。未发送消息的空白会话可重命名、归档,但不提供无内容可分叉的 fork;命名后的空白会话在切换会话后仍保留显示并可搜索。菜单支持悬停和键盘聚焦。
实现方式(遮蔽说明)
本插件以更低优先级(-1)**遮蔽(shadow)**官方 sidebar.workspaces 与 conversation.hero.workspace 槽位。官方 ui-workspace 插件仍正常加载,插件复用它的 uiWorkspace 服务和 useWorkspaces 数据钩子,只替换左侧栏浏览区和新会话工作区选择器。升级 DSH 后若官方行为变化,需要同步派生新版本。
安装 / 卸载对既有数据的影响
安装(dsh plugin --profile web add … 或 --patch 加载)前,插件只调用官方 workspaceRegistry.create(path, title)、Workspace.attachSession/ setTitle、workspaceRegistry.delete(id) 与 insertBefore,不写会话日志、不改会话 cwd、不动归档集合。具体影响:
| 动作 | 对既有数据的影响 |
|---|
| 安装 | 新增 worktree 工作区记录;既有工作区与会话原样保留。新记录插到各自仓库主工作区之后,其它工作区的相对顺序不变(绝对位置可能后移)。 |
| 迁移 | cwd 指向新注册 worktree 的既有会话被挂入该工作区(原先显示在"未分组")。会话 id、日志、cwd 都不变。 |
| 改名 | 仅在"该工作区标题仍等于插件当初写入的分支名"且分支确实变了时跟随改名;你手动改过的标题永不被覆盖。 |
| 删除 | 自动同步只注销已消失的 linked worktree,主工作树永不自动注销;用户明确删除可移除任何工作区或整个项目的注册记录,不删除目录或会话日志。 |
| 卸载 | 插件从 boot graph 移除、RPC 路由 404;所有工作区、会话日志、归属账本全部保留。自动注册的 worktree 工作区成为普通平铺条目(不自动删除,需自行清理或保留)。~/.dsh/storages/dsh-left-panel.json(墓碑、项目显示名、worktree 关联)与浏览器里的插件视图状态(分区、折叠状态)残留但完全惰性,可安全删除。工作区顺序不会回滚。 |
唯一的有损路径:worktree 目录临时不可见(如外置卷未挂载)超过 5 秒宽限,会被当作"已删除"注销;重新可见时按新工作区注册,其旧会话落回"未分组"且不会自动回来(官方限制:重新添加路径不恢复旧会话分组)。主工作树与普通工作区不受影响。需要更保守时可以把宽限调大或改为"仅 git 枚举不再列出时才注销"。
以上行为在隔离 DSH_HOME 中用真实宿主实测过三阶段:装插件前在 worktree 目录建会话(落在未分组)→ 装插件后该会话被挂入 worktree 工作区 → 卸载后工作区、账本、会话日志全部保留。
已验证(0.2.1-rc.5,真实宿主)
pnpm typecheck、pnpm build、pnpm test 全部通过,61 个测试覆盖同步、普通/主工作树/整项目移除与重新添加、命名空白会话的可见性和搜索、定时任务目录快照的异步生命周期。
- 隔离
DSH_HOME 启动本机 0.2.1-rc.5:注册主目录自动发现并注册两个 linked worktree;普通非 git 工作区保持独立。
- 真实
git worktree add / remove 后主动同步;新增工作区出现,删除工作树经 5 秒宽限后注销。
- 仓库/工作树重命名、删除墓碑、恢复注册均通过真实 RPC 验证;worktree 目录保留,注册前创建的会话已进入工作区账本。
- 插件 RPC 带 cookie 返回 200,无 cookie 返回 401,继续复用官方
/api 鉴权。
- 真实浏览器验证项目 → worktree 左侧分组、新会话工作区选择器、选择后导航与 fork 后自动打开子会话;未出现槽位异常。
node demo/dsh.mjs 自动发现新版 Desktop 启动器,成功启动含两个仓库、五个 worktree 的隔离演示;Ctrl+C 后退出码为 0,临时数据已删除。
- 隔离原生 Desktop(
data-platform=darwin)实测普通工作区删除、主工作树删除、整项目移除后确认框关闭且记录消失,目录与会话存储保留;空白会话重命名、搜索结果菜单及保留的重命名输入值正常。分组、单列表、搜索列表的 DOM 均没有底部遮罩节点,运行时未出现页面异常。
新版定时任务标记从 Host 的 schedule/catalog 读取,跟随 schedule/changed 刷新;切换连接时先清空旧 Host 标记,不再读取已移除的 Session schedule projection。此轮验证了真实 catalog 读取与异步生命周期测试,未创建会实际执行的提醒。
安装
# 本地路径(需要 pnpm)
dsh plugin --profile web add /abs/path/to/left-panel
# 未全局安装 dsh CLI 时
npx @deepseek-ai/dsh@0.2.1-rc.5 plugin --profile web add /abs/path/to/left-panel
Desktop 使用 --profile desktop。安装或首次加入 bundle 图后重启对应 DSH 实例;client 更新后浏览器硬刷新。没有全局 dsh 时,macOS 可使用 Desktop 自带命令:
"/Applications/DeepSeek Harness.app/Contents/Resources/runtime/cli/bin/dsh" plugin --profile desktop add /abs/path/to/left-panel --ignore-scripts
使用 --ignore-scripts 前先在插件目录运行 pnpm install && pnpm build。
本地开发
pnpm install
pnpm typecheck && pnpm build && pnpm test
启动一套使用临时 DSH_HOME 的真实隔离 DSH,并自动创建两个虚构 Git 仓库和 5 个 worktree:
pnpm demo
终端会输出带一次性本地 token 的访问地址。演示使用真实 DSH、真实插件和真实 Git worktree;按 Ctrl+C 后会停止服务并删除全部临时数据。macOS 自动发现 /Applications 或 ~/Applications 下 DeepSeek Harness 的 runtime/cli/bin/dsh 启动器;其他位置或环境可通过 DSH_BIN=/path/to/dsh pnpm demo 指定。
--patch 覆盖层开发(无需安装到 profile):
dsh web --patch ./dev.patch.yml
dev.patch.yml 指向 lib/index.js(宿主会读取最近的 package.json 获得两端声明),已加入 .gitignore。入口的裸导入由 profile 的 node_modules 解析;若报模块找不到,软链 profile 的包:
mkdir -p node_modules
ln -s ~/.dsh/profiles/node_modules/@deepseek-ai node_modules/@deepseek-ai
架构
浏览器 client 半 宿主 host 半
┌─────────────────────────┐ /api RPC ┌──────────────────────────┐
│ 遮蔽侧栏与新会话选择器 │ ────────────▶ │ WorktreeSyncService │
│ 项目→worktree→会话 树渲染 │ ◀──────────── │ · git worktree 扫描/监听 │
│ useWorkspaces/useSessions│ 全量快照 │ · 同步引擎(注册/注销/改名) │
└─────────────────────────┘ │ · 墓碑与每仓库开关(持久化) │
│ workspaceRegistry(官方服务) │
└──────────────────────────┘
- RPC:宿主在共享
/api 通道上注册精确 Fetch 路由(/api/left-panel/<endpoint>,Connection RPC 信封),浏览器端继续用 connection.rpc.call('/api', 'left-panel/<endpoint>')。鉴权与 Host/Origin 围栏完全复用官方通道,插件零自建鉴权。端点为 list、sync、setRepoName 和按项目校验重名的 setWorkspaceTitle。
- 同步引擎(
src/sync.ts,纯函数):决定 create/delete/retitle;分支切换会跟随改名,用户手工改名后不再覆盖;主工作树不自动删除;移除走 REMOVAL_GRACE_MS 宽限防抖。
- 状态:
~/.dsh/storages/dsh-left-panel.json(原子写;DSH_HOME 可重定位)保存墓碑、项目显示名、已知 worktree 与插件命名记录;项目分区和折叠状态保存在浏览器侧插件视图状态,不修改 DSH Workspace 或 Git。
- 触发:启动、
domain/changed(区分插件自删与用户删除)、fs.watch 仓库 .git 目录(去抖 400ms)、30s 轮询兜底、客户端主动 sync。
- git 访问(
src/git.ts):固定 argv spawn,环境变量清洗(剔除 *KEY*/*SECRET*/*TOKEN*/*PASSWORD*),15s 超时,只读命令。
边界与限制
- 一个会话仍只属于一个工作区(DSH 领域不变量);worktree 会话不会计入父仓库工作区,也不会跨 worktree 迁移。
- 官方已知限制:删除工作区注册后重新添加,旧会话不会自动回到该工作区(显示在"未分组"下)。
- 「添加工作区」走宿主原生目录选择器(
uiWorkspace.pickDirectory);无选择器环境(远程浏览器访问)降级为手输绝对路径。
- 会话正文搜索(250ms debounce 后的 Host 内容搜索)依赖官方
session.search 服务,未起时降级为名称匹配提示。
发布前检查表
致谢
客户端浏览器基于 deepseek-harness 的 @deepseek-ai/dsh-client-ui-workspace(MIT,Copyright (c) 2026 DeepSeek)派生,派生文件头部均已标注。