dsh-branch-tree
yb313/dsh-branch-tree
把会话变成左侧栏里的文件夹:可分叉出独立分支(完整复制 / 压缩记忆 / 只继承开头),并以树状浏览。
安装
dsh plugin --profile web add github:yb313/dsh-branch-treeREADME
dsh-branch-tree
Treat a session as a folder: fork one conversation into independent branches and browse them as a real tree in the left workspace column.
把会话当文件夹:母会话是可展开的文件夹,分支作为缩进子行挂在下面,直接在左侧工作区里浏览。
v1.3.1 · MIT · DeepSeek Harness plugin (web client) · client.js only, no build step
English
What it does
dsh-branch-tree turns the left workspace session list into a tree. A master session becomes an expandable folder row, and every branch hangs underneath it as an indented, collapsible child row — the relationship is visible without opening another panel.
When you create a branch you choose how much memory it inherits:
| Mode | Fork point | What the branch inherits |
|---|---|---|
Full copy (full) — default | the master's last real exchange | the entire conversation up to that point, copied verbatim into the branch session |
Summary (inherit) | an empty session | a compressed digest of the whole master so far, written into the branch as its own first message — no raw history travels |
Opening only (opening) | the end of the master's first turn | only the original request, no later chatter |
The default is a verbatim copy, because a branch is meant to keep trying things: a summary is lossy and drops exactly the details that make a branch useful — an approach you already ruled out, a spec spelled out once, the exact wording of a constraint.
How the three modes actually behave:
- Fork point — the host's own injected blocks (runtime context, skill catalog, time context, memory preamble) are not conversation and are filtered out, so the cut lands on the last reply the assistant actually gave.
- Empty-fork guard — the host can silently "succeed" with a branch that copied 0 events. The plugin reads the child's inherited event count, retries once without an explicit boundary, and if the branch is still empty it marks the row
emptyForkinstead of pretending it worked. - Summary mode is non-polluting — the digest is requested in a throwaway scratch session, never in the master, so the master's own conversation stays clean. The digest is posted into the branch as a seed message carrying
<!-- dsh-branch-tree:seed ... -->. - Summary fallback — if the model times out or answers without the digest sections, the plugin writes a mechanical digest (per-turn clipped transcript, ≤12k chars) instead. A branch is never silently left empty.
- Later edits in the master are never pushed into the branch: it is a fully independent session from the moment it is created.
What it does not do
- No conversation merging. Dragging one session onto another only re-parents the row (and may create a sub-workspace folder when the host supports
workspaces.create). Two conversations are never concatenated, summarised together, or merged into one log. There is no merge dialog and no merge mode. - No live link to the master. Editing the master later does not sync anything into an existing branch.
- No copies of md files. Each branch reads the shared md files live — edit the master's requirements and every branch sees the new version on its next read.
Install
DSH loads plugins as profile packages. Pick one of:
# from a local checkout (what this repo is developed against)
dsh plugin --profile web add /absolute/path/to/dsh-branch-tree
# from a GitHub tag tarball (no git binary needed)
dsh plugin --profile web add https://github.com/YB313/dsh-branch-tree/archive/refs/tags/v1.3.1.tar.gz
Then restart the web GUI (or refresh the page) and open the left workspace column.
Uninstall:
dsh plugin --profile web remove dsh-branch-tree
Not published to npm — the GitHub tarball and local paths are the supported routes for now.
Verified against the
dshdeveloper preview. The host is still moving: if a client seat is renamed or throws, the plugin falls back to the shipped session list instead of breaking the workspace (see the takeover flag below).
Usage
- Create a branch from a session: open the session row's context menu → + New branch… (or + Sub-branch… on a branch), name it, pick one of the three inherit modes, Create.
- Click a folder row to expand or collapse its branches.
- Drag a session onto a folder row to re-parent it, or onto a workspace header to move it there. Dragging never merges content.
- Use the Branches panel for search, per-branch task status, rename, archive/unarchive, “remove from tree”, and diagnostics.
- Branch output goes to
制图/<master>/<branch>/.
The tree takes over the shipped sidebar session entry point. If that seat ever throws, the plugin sets dsh.branch-tree.takeover to off and leaves the native list alone — you can flip that key back to on in localStorage to retry.
Local state
Everything lives under localStorage keys prefixed dsh.branch-tree. (tree shape, collapse state, active-session hint, per-branch task status, branch-count visibility, archived/hidden markers). Clearing them resets the view without touching any session data.
Requirements
- DeepSeek Harness (
dsh) with a web-capable profile - Shared host client packages (declared in
dsh.client.inject):@deepseek-ai/dsh-client-ui-sidebar@deepseek-ai/dsh-client-ui-conversation@deepseek-ai/dsh-client-ui-primitives@deepseek-ai/dsh-api-session-controller@deepseek-ai/dsh-api-workspace-files
Development
The host half (index.js) is intentionally empty — all behaviour lives in the client module (client.js), loaded through window.__ModuleLoader__.
node --check client.js # syntax
node test/smoke.mjs # smoke test, zero dependencies
Both run in CI on every push (.github/workflows/smoke.yml).
License
MIT © Yige Bao (YB313)
简体中文
这个插件做什么
dsh-branch-tree 把左侧工作区的会话列表变成一棵树:母会话是可展开的文件夹行,每个分支是缩进、可折叠的子行——不用另开面板就能看清从属关系。
创建分支时可选三种记忆模式:
| 模式 | 分叉点 | 分支继承到什么 |
|---|---|---|
全景继承(full)— 默认 | 母会话最后一次真实对话处 | 到该点为止的全部对话,逐字复制进分支会话 |
压缩继承(inherit) | 空会话 | 母会话到目前为止的压缩摘要,作为分支自己的第一条消息写入(原始历史一条都不带) |
开头继承(opening) | 母会话第一轮结束处 | 只继承最初的需求消息,不含之后的对话 |
默认给全景,因为分支的意义就是接着试:摘要是有损的,丢掉的恰好是让分支有用的那些细节——已经否决的方案、只说过一次的需求、约束的原话。
三种模式的实际行为:
- 分叉点:宿主注入的块(运行上下文、技能目录、时间上下文、记忆前置说明)不算对话,判轮次时一律过滤,所以切口落在助手真正说过的最后一句回复上。
- 空壳检测:宿主有可能「成功」建出一个一条事件都没复制的分支。插件会读子会话的继承事件数,为 0 就先不带边界重 fork 一次;仍然为 0 就在行上标
emptyFork,不装作成功。 - 压缩继承不污染母会话:摘要在一次性的临时 scratch 会话里生成,母会话自己不会被插入提示语。摘要作为种子消息写进分支,带
<!-- dsh-branch-tree:seed ... -->标记。 - 压缩兜底:模型超时、或答得不含摘要小节时,自动退回机械摘要(逐轮截断的对话记录,≤12k 字),分支不会被静默留空。
- 母会话后续的修改永远不会推给分支——分支从创建那一刻起就是完全独立的会话。
这个插件不做什么
- 不合并对话。 把一个会话拖到另一个会话上只是改变树结构(重挂载;宿主支持
workspaces.create时还可能建子工作区目录)。两个会话的对话内容不会被拼接、共同摘要或合并成一份日志——没有合并对话框,也没有合并模式。 - 不与母会话保持联动。 母会话之后的编辑不会同步进已存在的分支。
- 不复制 md 文件。 每个分支都实时读取同一批 md:母会话改了需求,分支下次读取就生效。
安装
挑一种:
# 本地 checkout(开发时用的就是这条)
dsh plugin --profile web add /absolute/path/to/dsh-branch-tree
# GitHub tag 的 tarball(不需要本机 git)
dsh plugin --profile web add https://github.com/YB313/dsh-branch-tree/archive/refs/tags/v1.3.1.tar.gz
装完重启 web GUI(或刷新页面),再看左侧工作区。
卸载:
dsh plugin --profile web remove dsh-branch-tree
暂未发布到 npm——GitHub tarball 与本地路径是目前支持的两条路。
已在
dsh开发者预览版下验证。宿主仍在变动——若某个客户端挂载点被改名或抛异常,插件会自动退回原生会话列表,不会搞坏工作区(见下方 takeover 开关)。
使用
- 在会话行的右键菜单里点 + 派生分支…(分支行上是 + 子分支…),起名、选继承模式、创建。
- 点文件夹行展开/折叠它的分支。
- 把会话拖到文件夹行上即重挂载,拖到工作区标题即移过去。拖拽不合并任何对话内容。
- 分支树面板里可以搜索、看每个分支的任务状态、重命名、归档/取消归档、「移出树」、以及诊断。
- 分支产物落在
制图/<母会话>/<分支>/。
树接管了原生的侧边栏会话入口。若宿主挂载点抛出异常,插件会把 dsh.branch-tree.takeover 置为 off,让原生列表继续工作——你可以在 localStorage 里把它改回 on 重试。
本地状态
全部存放在以 dsh.branch-tree. 为前缀的 localStorage 键里(树结构、折叠状态、当前会话提示、分支任务状态、分支计数显隐、归档/隐藏标记)。清掉它们只会重置视图,不会动任何会话数据。
环境要求
- DeepSeek Harness(
dsh),profile 需支持 web - 宿主共享客户端包(已声明在
dsh.client.inject):@deepseek-ai/dsh-client-ui-sidebar@deepseek-ai/dsh-client-ui-conversation@deepseek-ai/dsh-client-ui-primitives@deepseek-ai/dsh-api-session-controller@deepseek-ai/dsh-api-workspace-files
开发
宿主半部(index.js)刻意留空——所有行为都在客户端模块(client.js)里,通过 window.__ModuleLoader__ 加载。
node --check client.js # 语法
node test/smoke.mjs # 冒烟测试,零依赖
CI 会在每次 push 时跑这两条(.github/workflows/smoke.yml)。
许可证
MIT © Yige Bao (YB313)