dsh-client-ui-branch-map:DSH 工作区对话分支图

前言

DeepSeek Harness(DSH)的对话视图通常以列表或画布形式呈现。当对话轮次较多、分支较多时,列表难以一眼看清整体结构,画布又没有明确的分支语义。dsh-client-ui-branch-map 提供一张对话分支图:把当前工作区的对话画成一张图,每条消息是一个节点,每个 Session 是节点上的分支引用,可以像 git 分支一样从任意节点切出去。

这是什么

dsh-client-ui-branch-map 是一个 DSH 客户端视图插件,由维护者 AnichHaveatry 维护。它的核心是把 DSH 工作区里的对话渲染成一张分支图:根节点代表 Workspace,消息节点代表一轮对话,Session 是指向某个节点的引用。归档的对话不会进入这张图,子代理运行也不会进入。

核心功能

分支图视图

视图包含四个基本元素:

  • 作用域:恰好一个 Workspace
  • 根节点:一个,代表 Workspace 本身
  • 消息节点:用户发出的一条消息(一轮对话)
  • 边:节点 → 在该会话里紧随其后的下一条消息
  • Session:不是节点,而是指向某个节点的引用,和 git 分支指向 commit 一样

一个 Session 的引用停在它自己拥有的最后一条消息上;还没有自己消息的 Session 指向它被切出来的那个节点。两个 Session 停在同一个节点,读起来就像两个 git 分支指向同一个 commit。

行布局与根节点

行按轮号上升排列。轮号小的一定在前,所以这始终是一个合法的拓扑序;兄弟分支落在同一行带,分叉就读成两条并排的 lane。

根节点在树中水平居中:根级会话向左右两侧展开,新增会话不会把所有根线堆在左边。

不画什么

  • 子代理运行:子代理是代理自己派出去的工作,不是用户与它的交互;子代理 Session、以及从子代理再分出来的 Session,都不进入图,也不占节点。过滤按血缘做:isOwnConversation 一路走到根,只要祖先是 origin: "subagent" 就整条排除。
  • 归档的对话:归档集由宿主单独给出:useWorkspaces 快照的 archivedSessionIds。归档的 Session 仍然是它工作区的成员,所以不能靠成员表过滤,必须显式减掉。减掉的是它的引用,以及只由它引入的节点。
  • 当前打开的那条会话是例外:它被归档时照样画。工具栏的 已隐藏 N 条归档 数的就是被减掉的其它分支。
  • 存储细节:没有事件序号、没有 Session id、没有加载/刷新控件、没有子代理计数。

操作控件

控件 效果
+分支 on a message ctx.sessions.fork({ sessionId, atSeq }) — 打开一个新 Session,带上从根到这条消息的整条路径,然后切过去。始终显示,不藏在 hover 里
改写 on a message 在消息之前分叉,然后把改写后的文本作为新分支的第一条 prompt 发出去;原分支不受影响。轮次起点在日志第一条事件时没有”之前”可切,这个按钮不出现(从根开新分支即可)
a ref chip on a node 点它打开那条会话现在所在的位置(它的 tip)。这和”从这个节点开新分支”是两个不同的问题,标签也分开写
从根开新分支 on the root 一个从工作区根开始的空 Session

两件容易混淆的事,界面刻意分开:

  • 打开分支去它的 tip,因为 ref 记录的是这条线走到了哪里;不会把你送到中间的某个节点。
  • 从节点开分支才是”站在这个节点上”:新 Session 的上下文是从根到它的路径,下一句话从这里继续。

atSeq 是包含式的事件切点。一轮自己的 turn/start seq 是包含的,所以下一轮 turn/start 之前的那条事件就是这一轮的末尾——因此切点不会落在轮中间,新 Session 继承节点的所有祖先、不会多带一点。

数据与行为边界

这个插件不读私有数据、不改对话、不联网、不落盘。

  • 读:只用 conversation.view 的标准客户端 props:useSessions、useWorkspaces(含它的 archivedSessionIds 归档集)、useProjection("turnOutline");以及 ctx.sessions.refreshProjections(id)(宿主语义:只读投影,不启动迁移)。
  • 写:视图自身不写任何东西。+分支 / 改写 是用户点击后才调用的 ctx.sessions.fork(...),和 DSH 自带的分支按钮同一语义;不点就什么也不发生。
  • 会话历史:从不 retain 其它 Session、从不打开别人的历史;不读 useChat,不读子代理目录。
  • 存储:无。没有 localStorage / sessionStorage / IndexedDB / cookie / 插件配置文件。
  • 文件:插件自身不读写任何路径。宿主可能按自己的投影缓存策略缓存派生值,但不会改会话日志。
  • 网络:无。没有 fetch、WebSocket、遥测或外部服务。
  • 宿主半边:lib/index.js 的 apply 是空的。不注册服务、投影、路由或工具。
  • 停用 / 卸载:Tab 消失;样式表通过 effect 清理,locale 注册随 fiber 卸载;没有需要清理的插件数据。
  • 迁移:refreshProjections 不启动日志迁移;视图也不打开别的会话,所以不会触发格式迁移写盘。

唯一”碰数据”的地方是用户主动点分支产生的 Session,以及 openSession 的界面导航——这两件事和 DSH 自带的同名操作完全一样。

安装与启用

这个包本身就是一个 DSH bundle:声明了 dsh.bundle.patch,自带的 cordis.patch.yml 按包名插入一行,不写任何绝对路径。

# cordis.patch.yml,随包发布
- insert:
    - id: ui-branch-map
      name: dsh-client-ui-branch-map

从 GitHub 安装(仓库自带 lib/,安装时不需要构建):

dsh plugin --profile desktop add github:AnichHaveatry/dsh-client-ui-branch-map

从 tarball 安装:

dsh plugin --profile desktop add .\dsh-client-ui-branch-map-0.3.0.tgz

从目录安装(开发时用,link: 是活链接):

dsh plugin --profile desktop add C:\path\to\dsh-client-ui-branch-map

装好之后重启 DSH并刷新页面。两个缓存要到重启才失效:

  • dsh-client-modules 会按 Loader specifier 缓存每个包的 client bundle 路径;
  • bundle 字节在激活时快照,改 lib/client.js 不会热更新。

兼容性:peerDependencies 声明 @deepseek-ai/dsh: ^0.2.0-rc.2。不兼容的 DSH 会跳过这个 bundle 并点名,而不是挂上一个坏掉的视图。pnpm 安装时出现 peer 依赖警告是预期的:peer 由 DSH 安装提供,不在 profile 里。

这个包和 dsh-client-ui-canvas 是两个独立包、两个独立 Tab(canvas 与 branch-map),可以并存;想只留一个就卸掉另一个。

适用场景与注意

适合需要清晰查看对话分支结构的场景:多轮对话、多次分支、需要快速定位某个分支的当前位置。

插件以当前 dsh 进程权限运行,安装前应检查源码与许可证。插件本身不落盘、不联网、不读私有数据,只通过标准客户端 props 和 ctx.sessions.fork 与宿主交互。

短结尾

dsh-client-ui-branch-map 提供了一张清晰的对话分支图,让 DSH 工作区的分支结构一目了然。它像 git 一样,用 Session 作为分支引用,让你可以方便地从任意消息节点切出新分支。

  • 目录页:https://www.skillhub.cn/plugins/AnichHaveatry/dsh-client-ui-branch-map
  • GitHub:https://github.com/AnichHaveatry/dsh-client-ui-branch-map
羽毛球分组比赛记分
小程序二维码

欢迎使用《羽毛球分组比赛记分》微信小程序

Xiaoye