DSH 会话分叉树排列功能 · DSH Session Fork Tree Arrangement
30 秒理解:DSH 侧边栏把 fork 出来的会话挂到父会话下面,圆角导轨 + 绿色展开器 + 祖先链高亮,适合做分支实验。
兼容 DSH Desktop / Web;安装见下方「安装」,卸载见「卸载」。
会话管理示意
会话折叠
会话折叠演示
分叉树排列
分叉树演示
📖 会话管理指南(折叠 + 分叉树组合用法)
平台支持与路线图
| 平台 | 状态 |
|---|
| Windows 11(DSH Desktop) | ✅ 稳定运行(当前版本) |
| Ubuntu | 🧪 测试中,尚未承诺稳定 |
| macOS | 📋 计划中 |
修复与功能调整的优先级按评论 / 留言的反馈量排序——遇到问题请直接开 issue 或留言,
同类反馈越多越先修。欢迎提交你所在平台的环境信息。
相关插件
【DSH 会话分叉树排列功能】把 DSH 侧栏里"另起一行"的分叉会话,变成挂在父会话下面的树:缩进 + 圆角连接线 + 绿色 + 折叠按钮 + 悬停祖先链高亮。
DSH Session Fork Tree Arrangement turns forked sessions that used to appear as unrelated rows into a tree under their parent: indent, rounded connector, a green + expander, and ancestor-chain highlighting on hover.
中文 · English
中文
它解决什么问题
DSH 里"分叉"会新建一个会话,但它和父会话在侧栏里是两条互不相干的平行行,而且按最近活动排序,新分叉总是跳到最前面。会话一多,就分不清谁是谁的分支。
本插件把父子关系重新可视化:
- 归属:子会话紧跟父会话,可配置把顺序写回
workspace.sessionIds(持久化);
- 缩进 + 连接线:子行缩进 16px,父标题左缘起画
╰(竖线到行高一半后转横线,圆角 8px,绝对像素,窗口变化自动重算);
- 折叠:有分叉的会话,名字右侧会出现醒目的绿色
+;默认全部收起,点 + 展开、再点收起,逐层递归;展开时 + 显示绿色边框;
- 悬停高亮:鼠标划过子行,整条祖先链变亮;
- 自动命名:分叉的标题如果还是系统默认(
父名 (1)),会被改成 父名 · 分叉N(N 为同父分叉的创建顺序);你手动改过的标题不会被覆盖。
安装
npm(推荐)
# 桌面版(profile=desktop)
dsh plugin --profile desktop add dsh-session-fork-tree@0.9.0
# 或免全局安装
npx -y @deepseek-ai/dsh plugin --profile desktop add dsh-session-fork-tree@0.9.0
# Web UI(profile=web)
dsh plugin --profile web add dsh-session-fork-tree@0.9.0
安装后完全退出 DSH 再启动;卸载:
dsh plugin --profile desktop remove dsh-session-fork-tree
以下为源码方式(开发 / 离线环境):
前置:DSH 桌面版或 dsh web,一个可用的 profile(默认 desktop)。
# 1. 取代码
git clone https://github.com/ayuanxx-max/dsh-session-fork-tree.git
$repo = "<克隆目录的绝对路径>"
# 2. 作为本地包登记到 profile
dsh plugin --profile desktop add link:$repo
# 3. 让它成为一个 bundle:在 profile 的 dsh.profile.bundles 里追加
# %USERPROFILE%\.dsh\profiles\desktop\package.json
# "dsh": { "profile": { "bundles": [ … , "dsh-session-fork-tree" ] } }
# 4. 完全退出 DSH 再启动
顺序:先 dsh plugin add(装依赖),再改 bundles(声明组合),最后重启。只做前者插件不会加载;只做后者会因为包不存在而启动失败。
使用
- 打开侧栏:分叉会自动缩进到父会话下面;
- 有分叉的会话,名字右边出现绿色
+(默认收起);
- 点
+ 展开,按钮变为绿色边框;再点收起;
- 分叉自己还有分叉时,会再出现一层
+,逐层展开;
- 鼠标悬停在子行上,父行与祖先链同时变亮;
- 刷新页面/重启后回到"全部收起"。
可选配置
在 profile 的 cordis.patch.yml 里覆盖该 bundle 的 config(升级不丢):
- id: fork-tree
name: 'dsh-session-fork-tree'
config:
renames: # 同名分叉改名
- sessionId: session-…
title: '父会话 · 分叉1'
reorder: # 把"子会话紧跟父会话"写进 sessionIds
- sessionId: session-…
afterSessionId: session-…
不配置也能用:客户端会在渲染层按父链排序;reorder 只是把顺序持久化,避免下次渲染回退。
工作原理
| 层 | 做什么 |
|---|
| Host 半 | 从 sessionController.list() 读 SessionSummary.parentSessionId 生成谱系;提供 GET /fork-tree/lineage.json(谱系)、GET /fork-tree/style.css(样式,客户端每 15 秒重取,改样式无需重启)、POST /fork-tree/report(客户端自报,落盘 %TEMP%\fork-tree-report.json);可选执行改名与 insertSessionBefore 重排 |
| Client 半 | 注册官方槽位 sidebar.session.row.leading 与 sidebar.workspaces.session.row.action 拿到 sessionId;按父链先序排序;用行伪元素画 ╰;在标题后插入 + 按钮;MutationObserver 监听新增行,新建分叉 0.2–0.5 秒内归位 |
| 数据 | 只用官方 API(sessionController、workspaceController),不改 app.asar、不改核心渲染器 |
诊断
- 页面 Console:
__ftReport() → 返回当前行、缩进、连接线坐标、折叠状态;
- 自报文件:
%TEMP%\fork-tree-report.json(含 live、hidden、toggle、children 等字段);
- 结构认不出时插件不动作,只在 Console
warn。
已知限制
- 依赖 DSH 侧栏内部结构(行元素的类名/层级)与
SessionSummary.parentSessionId,属非公开契约,上游改版可能失效;失效时静默不动;
- 折叠状态存在内存中,刷新后复位(如需记忆可提 issue);
- 行顺序在渲染层纠正,极少数情况下可能与宿主下一次重排"抢一次",
MutationObserver 会再纠正;
- 只覆盖当前可见(已挂载)的会话行。
卸载
- 从 profile
dsh.profile.bundles 移除 dsh-session-fork-tree;
dsh plugin --profile desktop remove dsh-session-fork-tree;
- 重启 DSH。
Linux / macOS 安装
git clone https://github.com/ayuanxx-max/dsh-session-fork-tree.git
REPO="$(cd dsh-session-fork-tree && pwd)"
dsh plugin --profile desktop add "link:$REPO"
# 编辑 ~/.dsh/profiles/desktop/package.json,把 "dsh-session-fork-tree" 加入 dsh.profile.bundles
# 完全退出 DSH 后重启
故障排查:重启后 DSH 打不开
-
先恢复启动:不要动插件代码,在 profile 的 cordis.patch.yml 末尾追加
- id: fork-tree
disabled: true
重启 DSH 即可正常启动(插件被禁用,其余不受影响)。
-
确认版本与 profile:DSH 0.2.x 及以上;profile 名与安装时一致(desktop 或 web)。
-
自己改过包名时:client.js 里 window.__ModuleLoader__.load({ id: ... }) 的 id 必须等于 package.json 的 name。DSH 按包名匹配客户端模块,id 不一致会让客户端条目激活失败,进而导致启动报错。
-
看日志:~/.dsh/logs 下最新一份日志里的 boot / activate 报错能直接指出是哪个条目失败。
许可
MIT,见 LICENSE。
English
What it solves
Forking a session in DSH creates a new session, but the sidebar shows it as an unrelated row, sorted by recent activity — new forks jump to the top. With enough sessions, you lose track of which row belongs to which branch.
This plugin re-visualises the parent/child relation:
- Ownership — a fork sits directly under its parent; optional config can persist that order into
workspace.sessionIds;
- Indent + connector — child rows indent 16px and draw
╰ from the parent title's left edge (vertical down to half the row, then horizontal, 8px rounded corner, absolute pixels recomputed on resize);
- Collapse — any session that owns forks gets a green
+ right of its name; everything is collapsed by default, click to expand, click again to collapse, recursively; an open + shows a green border;
- Hover highlight — hovering a child brightens its whole ancestor chain;
- Auto naming — a fork whose title is still the generated default (
Parent (1)) becomes Parent · forkN; titles you renamed yourself are never overwritten.
Install
Requires DSH Desktop or dsh web with a working profile (default desktop).
git clone https://github.com/ayuanxx-max/dsh-session-fork-tree.git
$repo = "<absolute path of the clone>"
dsh plugin --profile desktop add link:$repo
# then append "dsh-session-fork-tree" to dsh.profile.bundles in
# %USERPROFILE%\.dsh\profiles\desktop\package.json
# quit DSH completely and start it again
Order matters: install the dependency first, declare it in bundles second, restart last.
Usage
- Open the sidebar — forks indent under their parent automatically;
- A session with forks shows a green
+ (collapsed by default);
- Click
+ to expand (button gains a green border), click again to collapse;
- A fork that owns forks gets its own
+, layer by layer;
- Hover a child row — its ancestors brighten;
- Reloading returns to "all collapsed".
Optional config
Override the bundle's config in the profile's cordis.patch.yml:
- id: fork-tree
name: 'dsh-session-fork-tree'
config:
renames:
- sessionId: session-…
title: 'Parent · fork 1'
reorder:
- sessionId: session-…
afterSessionId: session-…
How it works
| Layer | Role |
|---|
| Host | Reads SessionSummary.parentSessionId from sessionController.list(); serves GET /fork-tree/lineage.json, GET /fork-tree/style.css (refetched every 15 s — style changes need no restart), POST /fork-tree/report (writes %TEMP%\fork-tree-report.json); optional rename and insertSessionBefore reordering |
| Client | Registers the official slots sidebar.session.row.leading / sidebar.workspaces.session.row.action for the row's sessionId; orders rows as a tree; draws ╰ with row pseudo-elements; inserts the + button next to the title; a MutationObserver places a fresh fork within 0.2–0.5 s |
| Data | Official APIs only (sessionController, workspaceController); never patches app.asar |
Diagnostics
- Console:
__ftReport() → rows, indent, connector coordinates, collapse state;
- Report file:
%TEMP%\fork-tree-report.json;
- Fails safe: unknown structure means no action and a single
warn.
Limitations
- Depends on the sidebar's internal markup and on
SessionSummary.parentSessionId (a private contract); a redesign can break it, in which case it silently stops;
- Collapse state is in memory and resets on reload;
- Row order is corrected in the render layer; the
MutationObserver re-corrects if the host re-sorts;
- Only mounted (visible) rows are handled.
Uninstall
Remove dsh-session-fork-tree from dsh.profile.bundles, run dsh plugin --profile desktop remove dsh-session-fork-tree, restart DSH.
Install on Linux / macOS
git clone https://github.com/ayuanxx-max/dsh-session-fork-tree.git
REPO="$(cd dsh-session-fork-tree && pwd)"
dsh plugin --profile desktop add "link:$REPO"
# append "dsh-session-fork-tree" to dsh.profile.bundles in ~/.dsh/profiles/desktop/package.json
# quit DSH completely, then start it again
Troubleshooting: DSH does not start after a restart
-
Recover the boot first: append to the profile's cordis.patch.yml
- id: fork-tree
disabled: true
and restart; DSH boots with the plugin disabled.
-
Check version and profile: DSH 0.2.x or newer, and the same profile you installed into.
-
If you renamed the package: the id inside window.__ModuleLoader__.load({ id: ... }) in client.js must equal the name in package.json. DSH matches client modules by package name; a mismatch fails the client entry and DSH refuses to boot.
-
Read the log: the newest file under ~/.dsh/logs names the failing entry in its boot / activate error.
License
MIT, see LICENSE.