dsh-session-inherit
会话菜单新增「继承」:把长会话里的具体锚点(任务原文、最近的用户指令、改过的文件、最近的失败、命令、待办)机械提取成交接单,在干净的新会话里接着干——预设默认一并继承,不复制历史,也不调用模型。
安装
dsh plugin --profile web add dsh-session-inherit
沙箱测试结果
| 阶段 | 结果 |
|---|---|
| 1 安装 | ✓ |
| 2 列装 | ✓ |
| 3 挂载 | ✓ |
| 4 启动 | — |
四阶段在隔离沙箱里跑:安装(有网)→ 列装 → 挂载 → 启动(后三步断网)。
README
dsh-session-inherit(继承)

[English](README.md) | [简体中文](README.zh-CN.md)
把一段跑了很久的会话,交接给一个干净的新会话。
在会话行的「…」菜单里多出一行 继承。点它 → 预览一份从源会话机械提取出来的
交接单 → 确认 → 新建一个不带旧历史的会话,把这份交接单作为它的第一条消息发出,
工作在新会话里继续。原会话原样保留。
<p>
<img src="docs/session-menu.png" width="170" alt="会话菜单里多出的「继承」">
<img src="docs/inherit-dialog.png" width="330" alt="继承预览对话框">
</p>
/inherit 命令是同一个入口(指令提示里能搜到):
<p>
<img src="docs/inherit-command.png" width="700" alt="/inherit 命令提示">
</p>
置顶 ← DSH 原版(Pin) 重命名 ← DSH 原版(Rename) 分叉 ← DSH 原版(Fork) 继承 ← 本插件(order 350) 归档 ← DSH 原版(Archive)
除「继承」外的四行都是 DSH 原版自带的条目(sidebar.workspaces.session.menu.item
slot 的官方内容)。如果装了别的会话菜单插件,你的列表里还会多出它们的条目——
本插件只负责「继承」这一行。
为什么不是 /compact、也不是「分叉」
现在的 DSH 里已经有两条"继续干"的路,但这个需求是第三条:
做法 · 它搬过去的东西 · 对本场景的问题
/compact · 把历史换成摘要,会话不变 · 实测把 541,227 token 压到 25,602,4 步之内退化又犯了,重复词还从「做。」变成了「好。」——可见既不是长度的问题,也不是上下文内容的问题
官方分叉 · 复制源会话的整段历史前缀 · 等于把退化诱因一起带走,新会话一睁眼就是几十万 token 的历史
本插件的继承 · 只搬可核对的具体锚点,历史一律不带 · 新会话的上下文从几千字符起步
为什么不用 LLM 做摘要
/compact 那次的证据很直接:摘要留下的是"要做验收、要写报告"这类抽象意图,
丢掉的恰恰是文件路径、下一步参数这类具体锚点。模型攥着几句模糊意图,在抽象层反复
自我告诫,而每次告诫的句式几乎一模一样——这本身就是在喂重复退化。
所以本插件的交接单一次模型调用都不发,只做机械提取:任务原文、最近的用户指令
(原话,不改写)、动过的文件、跑过的命令、最后一次待办状态、源会话最后一条回复。
每条都能追回源会话去核对。
交接单开头还写死三条执行规约:
不要复述本单,不要总结本单,不要回应"收到/了解了"。
读完直接做「下一步」。第一步只做一个具体动作,做完停下汇报;不要同时铺开多个方向。
交接单长什么样
交接单正文的语言跟随界面语言(客户端在 preview/commit 请求里带 lang):中文界面
生成中文交接单,英文界面生成英文交接单——不然英文用户拿到的正文,他自己和模型都读不顺。
下面这份是中文输出。
📋 会话继承单 > 来源:`session-xxx`(标题) · 共 15 轮 · 最后活动 2026-10-08 19:37 > **执行规则**:不要复述本单… ## 任务目标 ← 源会话第一条真实用户消息 ## 改过的文件(实际写入/修改过) ← 来自 edit/write 的 file_path,按最近改动排序 ## 最近失败 ← 源会话最后几次工具报错(带轮次与错误码) ## 最近的用户指令(由旧到新) ← source.kind === 'user' 的原话 ## 读过的文件(只读取过,未修改) ## 文中提到的路径 ← 工具参数正文里扫出来的,可能是噪音 ## 项目内相对引用 ← 基准目录未确定,需要时用文件搜索确认 ## 最近执行的命令 ## 待办(源会话最后一次 todo 状态) ## 源会话最后一次回复 ## 下一步 ← 你在预览框里写
章节标题不带序号。序号是逐节写死的字面量,会随"某一节有没有内容"漂移——
早先版本里客户端提示写死了「## 7. 下一步」而实际生成的是「## 8.」,用户照着找
永远找不到。
「最近的用户指令」只认 source.kind === 'user'。实测某个真实会话的 42 条
user/message 里,有 27 条是 agent-instructions / runtime-context /
skill-catalog / time-context / 记忆提示 / 模型选择 / 压缩检查点 这类运行时注入
——全是噪音,现在被挡在门外。
两个高价值锚点
改过的文件只取 edit / write 这类写入工具的结构化 file_path。实测某真实
会话:写入类调用 85 次、读取类 58 次,改过 17 个文件、读过 43 个——两类混在
一起时,读过的文件会把真正改过的挤出上限。write 的 content、edit 的
new_string 正文里出现的路径只算「文中提到」。
最近失败来自 tool/result 的 isError。实测某真实会话 374 条 tool/result
里 11 条失败,形如:
- (t5) `edit` FS_STALE_VERSION:cannot write "…\inject-codex.ps1": file changed since it was read — re-read the file, then retry
每条都带轮次——同一节里的失败可能横跨好几轮,不标轮次的话新会话分不清哪些是
陈年旧账。这是"接着干什么"最直接的线索,而且纯机械可提取。成功的 363 条、以及
用户主动打断(AbortError)和自动审查被中断这类噪音会被丢掉;同一条报错在多个
工具上重复出现时按文本合并。
绝对路径锚点还会做一次存在性检查,已被删除或改名的会标上 (已不存在)——省掉
新会话去翻一个不存在的文件。
用法
入口一:会话行菜单
1. 鼠标移到侧边栏任一会话,点行尾的「…」;
对哪个会话点都行,包括已经冷掉的会话(读取走 sessionQuery.readSession,不激活 agent)。
2. 点 继承。
3. 弹出预览,从上到下是:源会话信息(标题 / 轮次 / 改过几个文件 / 失败几条 / 命令数 / 待办数)
→ 「下一步」输入框(可以不写)→「创建后立即开始工作」勾选框 →「继承当前对话的
预设」勾选框 → 一行折叠着的交接单。
4. 在「下一步」里写要它做的事——越具体越好(点名哪个文件、做哪一个动作),
然后点「创建并继承」。要改交接单全文时,点「▸ 展开交接单全文(可编辑)」。
交接单默认折叠是因为多数时候你只想写一句话,不想逐字审阅几千字。
新会话会自动打开,标题是 继承: <源标题>,工作目录、模型路由、agent preset 默认
都跟源会话一致;不想继承预设就在预览框里取消那个勾,新会话改用 DSH 默认预设。
勾选框「创建后立即开始工作」默认勾上:交接单会经
sessionController.prompt 作为一条正常用户消息发出并立刻开始。
取消勾选时改为直接写入会话日志(session:append),只记录、不触发首轮,
打开后你自己说要做什么。
「下一步」输入框和交接单是两个独立来源:宿主收到 commit 时,如果两者都在,
会把输入框的内容写进那份交接单的「## 下一步」一节——所以即使你展开改过交接单,
也不会有哪一边被悄悄丢掉。
降级会明说。 如果宿主没能自动开始(prompt 失败或不可用),对话框不会静默
关掉,而是停在原地显示原因和新会话 id,并给一个「打开新会话」按钮——以前这里只
写 console.warn,界面静默消失,用户会以为新会话已经在跑了。
重复继承会被提醒。 源会话(常常是冷的)改不了标题:
sessionTitle.rename(session, title) 的契约要求传入活着的 session 对象(内部断言
ctx.sessions.get(id) === session),而 sessions.get 的文档写明"冷会话返回
undefined"。所以"这个会话被继承到哪儿去了"记在
%DSH_HOME%\session-inherit\history.json,在你再次点开预览框时提示
「这个会话已经被继承过 N 次,最近一次 …→ …」——正好是你准备再次继承的那一刻。
记录只存会话 id 和时间戳,最多保留 200 个源会话、每个 10 条。
入口二:/inherit 命令
/inherit 继承当前会话 /inherit <sessionId> 继承指定会话 /inherit --next 先跑一次 npm test 继承当前会话,并指定第一步 /inherit <sessionId> --next 换个文件继续 两者一起用
不带 --next 的裸文本只可能是会话 id,不合法就如实报错,不会被猜成"下一步"。
入口三:session_inherit 工具
注册给 agent 的同名工具,参数 sessionId(必填)、nextStep、start。
用途是让 agent 自己在上下文变长时提议换会话,或让用户通过 agent 表达意图。
三条入口最终走同一条链路:读源会话 → 机械提取 → 新建会话 → 挂工作区 → 注入交接单。
回查源会话:session_read 工具
交接单首行写着源会话编号,但新会话没法直接读它:会话日志是
%DSH_HOME%\sessions\<工作区转义>\session-<id>\session.vN.jsonl.zstd,多帧 zstd 拼接
(实测 2.7MB 文件切成 1067 帧,Node 的 zstdDecompressSync 只解第一帧就停)。
所以本插件另外注册了一个只读工具补上这一环:
session_read { sessionId, mode: 'search' | 'summary' | 'tail', query?, limit? }
search(默认):按关键词搜源会话,返回命中处前后各一段上下文summary:结构化摘要(任务目标 / 改过的文件 / 最近失败 / 待办 / 最后一次回复)tail:最后 N 个有内容的事件——看"上次到底停在哪"
新会话遇到"交接单里那个文件是干什么的"这类问题时可以直接回查,不用你回去翻。
它只读,不创建也不修改任何东西。
「附上原对话编号、让新对话顺着编号去找」这个想法的闭环就在这儿:光有编号没用,
得让它读得到。
安装
从 npm 装(推荐,一条命令):
dsh plugin --profile <profile> add dsh-session-inherit
也可以不经过 npm,直接从仓库装。改 profile 的 package.json,两处都要加:
{
"dependencies": {
"dsh-session-inherit": "link:/path/to/dsh-session-inherit"
},
"dsh": {
"profile": {
"bundles": [
"dsh-session-inherit"
]
}
}
}
bundles 才是加载权威——只写进 dependencies 不会挂载。装完重启 DSH。
桌面端的 profile 由 Electron 独占管理,全局 CLI 会拒绝操作它;
那种情况直接改该 profile 的 package.json 再重启即可。
版本适配
在 DSH 0.2.0-rc.2(Windows)上验证通过。插件依赖若干 DSH 内部服务与菜单
slot 名,DSH 升级后可能失配。如果菜单项不出现、或插件页显示「异常」,先查
profile 的 compatibility.json——profile 启动时会校验插件对 @deepseek-ai/dsh
与 dsh-* 的 peerDependencies,不满足的 bundle 会被整体跳过(日志里打印
skipping profile bundle)。把
…(truncated)
以上内容来自 npm 上的 README,我们只做格式化,不改内容。
版本
npm 上共发布:3,最后更新 2026-10-12