@omdp/dsh-archived-sessions
v0.3.10
Published
Archived-sessions manager for the DeepSeek Harness web UI: list / unarchive / delete / detail from Settings. · DSH 归档会话管理:设置页查看、释放、删除已归档会话(含按树删除子会话与孤儿清理)。
Maintainers
Readme
@omdp/dsh-archived-sessions
Archived-sessions manager for the DeepSeek Harness web UI — fork of
@muwinds/dsh-archived-sessions(0.2.0), adapted for DSH 0.1.5-rc.1 through 0.2.0-rc.1.
DSH 归档会话管理:在 设置 → 归档会话管理 中查看、释放、删除已归档会话(支持按树删除子会话、清理孤儿会话)。自 0.3.4 起与 DSH 0.1.6 内置的「已归档会话」设置页共存、互不冲突。0.3.6 起 ctx.shell 双时代自适应(run() / execute()),0.1.5-rc.1 → 0.2.0-rc.1 全区间删除可用(0.3.7 追加 rc.2 声明、0.3.8 追加 0.2.0-rc.1 声明)。
中文
这是什么
DSH 的会话可以归档(移到「归档云店」),本插件在设置页提供归档会话的管理界面:
- 列表:标题、会话 ID、所属工作区、磁盘占用、创建时间、是否运行中;
- 释放:把会话从归档集合移回活动列表(不删数据);
- 删除:从硬盘删除会话目录 + 从归档集合移除(两步确认);
- 按树删除:删除主会话时,其下的 subagent 子会话(
parentSession链)一并删除,不再留孤儿(修复上游 issue #2); - 孤儿清理:一键扫描并清理「父会话已删除、自己还在盘上」的残留子会话目录;
- 详情:展开查看会话内容(前 100 条消息)。
为什么有这个 fork
上游 @muwinds/dsh-archived-sessions 0.2.0 与 DSH 0.1.5-rc.1 不兼容(作者已一个月未维护):
| 症状 | 根因 |
| --- | --- |
| 归档列表全部显示「文件缺失」 | 0.1.5-rc.1 的 sessionPersistence.list() 返回 SessionPersistenceSnapshot[]({header, revision, eventCount?, sizeBytes?}),不再是裸 SessionHeader[];插件按旧形状取值,header.id 变 undefined |
| 「删除」和「释放」行为相同(都只移除归档标记) | 0.1.5-rc.1 抽象服务移除了 locate(meta);插件 persistence.locate(header) 返回 undefined → .path 抛 TypeError → 被 catch 吞掉 → 走 no-artifact 分支,跳过删目录 |
本 fork 的修复:
list()改为识别快照形状,从snapshot.header取值;优先用快照自带的sizeBytes;- 不再依赖
locate(),改用 DSH JSONL 后端同款路径编码(encodeSegment/projectKey)自行解析会话目录,并经fs.resolve确认存在后才操作; - 删除前校验目录名必须是会话目录(
session-<uuid>或裸 UUID),拒绝删除任何非会话路径,杜绝误删; - 删除按
parentSession递归收集整棵子树,连同子会话一起删 + 一起移出归档集合; - 新增
/dsh-archived/orphans与/dsh-archived/sweep:扫描/清理孤儿子会话(父会话已不在 persistence 中)。
安装
pnpm add @omdp/dsh-archived-sessions -w若从
@muwinds/dsh-archived-sessions迁移:先在 profile 的 package.json 中移除旧依赖(github:MuWinds/dsh-archived-sessions),再安装本包。旧包与新版 API 路由相同(/dsh-archived/*),安装后刷新页面即可。
要求 / Requirements
- DeepSeek Harness 0.2.0-rc.1 / 0.2.0-rc.2(实测版本;0.3.9 起改用三元组区间
>=0.2.0-rc.1 <0.2.1-0,自动覆盖同三元组后续 rc 与正式版0.2.0)。更早的 0.1.5~0.1.7 系列曾支持(0.3.6 起全兼容),但自 0.3.9 起不再声明——需要在 0.1.7 上运行请装 0.3.8 或更早 @deepseek-ai/dsh>=0.2.0-rc.1 <0.2.1-0(peer,三元组区间;0.3.9 起;语义见下方「兼容性门禁」)@deepseek-ai/cordis4.0.1/4.0.2/4.0.4(peer,逐版本枚举)@deepseek-ai/dsh-session-persistence-jsonl(可选,随 DSH 自带;缺失时回退到内置路径编码)
ctx.shell 双时代自适应(0.3.6)
删目录那一步是本插件唯一会「真正碰磁盘」的操作,而 ctx.shell 的契约在 DSH 0.1.7 改过名——
两侧方法名不同、且旧方法在新版被整个移除(解包 npm tarball 核对 @deepseek-ai/dsh-shell 的
lib/types/index.d.ts:0.1.5-rc.1/0.1.5-rc.2/0.1.5-rc.3/0.1.6-alpha.1 里
abstract run(...) 存在、execute 为 0 次;0.1.7-rc.1 里 abstract execute(spec): Promise<ShellExecution>
存在、run 为 0 次):
| DSH | 调用链 | 结果 |
|---|---|---|
| ≤ 0.1.6(rc.x / alpha) | resolve(request) → spec → run(spec) → Promise<ShellRunResult> | 只有 run |
| ≥ 0.1.7 | resolve(request) → spec → execute(spec) → ShellExecution → await execution.result() | 只有 execute |
所以 removeDir() 现在运行期探测:优先 execute()(0.1.7+),否则回退 run()(≤0.1.6),
两者都没有才报错。这样同一个包在整条版本线上都能删。教训:0.3.4 只调 run()(rc.x 可用、
0.1.7 全废),0.3.5 只调 execute()(0.1.7 可用、rc.x 静默失效——typeof shell.run !== "function"
守卫直接抛错,被 UI 收敛成红字「删除失败」),两次都是「只赌一边」。
兼容性门禁(0.3.6 起声明,0.3.9 起改用三元组区间)
当前声明为 "@deepseek-ai/dsh": ">=0.2.0-rc.1 <0.2.1-0"。
该声明由 DSH 自己的
evaluatePluginCompatibility()(dsh-app-boot 的公开导出)在安装与每次启动时校验:命中未实测
的新版本时会整包优雅跳过(stderr 打 skipping profile bundle,DSH 照常启动),命中实测版本
则正常加载。声明从 0.1.7-rc.1 起是因为这个门禁本身是 0.1.7 才引入的(逐版解包核对
dsh-app-boot:0.1.5-rc.2/0.1.5-rc.3/0.1.6-alpha.1/0.1.6-alpha.2/0.1.7-alpha.1/0.1.7-alpha.2
里 evaluatePluginCompatibility 出现 0 次)——旧运行时读到这条 peer
只是不认识的声明,不影响加载,所以老用户不会因为这条声明而失去插件。
0.3.9 的语义变化:从逐版本枚举(0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1)改为三元组区间。
逐版本枚举下每发一个 rc 就要改声明 + 重发包(0.2.0-rc.1 → rc.2 已连续踩两轮,每轮都表现为
「设置项消失、路由 405/404」);而同一 [major,minor,patch] 内的 rc 属同一 API 契约迭代
(实测 rc.1→rc.2 全量 20 包逐文件 SHA256:本插件依赖面只有 package.json 版本号变化),
故实测该三元组首个 rc 即可放行其后续 rc 与正式版。区间最多宽到一个三元组,跨 patch/minor
(如 0.2.1-rc.1)仍需重新核查后新增区间——见 AGENTS.md 规范 3。
⚠️ 上界必须写
<0.2.1-0而非<0.2.1:semver 里预发布排在正式版之前,0.2.1-rc.1 < 0.2.1成立,写<0.2.1会漏放行下一个三元组的 rc。 ⚠️ 0.3.9 不再声明0.1.7线:旧运行时上会被门禁跳过,需要 0.1.7 请装 0.3.8 或更早。
0.3.7 追加 0.1.7-rc.2 的依据:rc.1→rc.2 tarball 逐文件 diff 显示 dsh-shell(本插件删除功能的
唯一 shell 依赖面)的 lib/ 逐字节零变化;再用 rc.2 的门禁跑新声明 → 放行;并在 scratch
profile([email protected] + 插件 0.3.6 + 精确版本豁免)上真机实测完整删除链路:
POST /dsh-archived/delete → {"ok":true} + 磁盘目录消失 + 归档集合清空。
API
| 方法 | 请求 | 响应 |
| --- | --- | --- |
| POST /dsh-archived/list | {} | { items, totalBytes } |
| POST /dsh-archived/unarchive | { sessionId } | { ok, changed, archivedSessionIds } |
| POST /dsh-archived/delete | { sessionId } | { ok, deleted, sessionId, alsoDeleted[], sizeBytes, warnings[]?, reason? } |
| POST /dsh-archived/detail | { sessionId } | { id, createdAt, cwd, parentSession, totalEvents, messageCount, truncated, messages } |
| POST /dsh-archived/orphans | {} | { items, totalBytes } |
| POST /dsh-archived/sweep | {} | { removed, freedBytes, items, warnings[]? } |
删除(含 sweep)成功后,Host 会向所有客户端广播 api-session/removed,让 DSH 自己的会话列表
立刻丢弃该行;同时释放归档集合与工作区 sessionIds 记账,并删除磁盘目录。若某个清理子步骤
失败,删除本身仍然成功完成,失败原因放在 warnings[] 里返回(避免「磁盘已删但报失败」的误判)。
变更记录
0.3.10(2026-10-01):修复「删除归档会话后,它们又回到对话列表,点进去报
session/not-found」。根因(全链路实测确认)是客户端陈旧行,不是 Host 端删除失败:归档在 DSH 里只是 可见性标记(
archivedSessionIds+ 客户端sessionVisible()),它从不出现在会话列表的 过滤逻辑里(dsh-session-query全库 0 处archiv*)。客户端manager.summaries会一直保留 该行,只被归档集合遮住。删除时旧代码只做「从归档集合移除」——遮罩一撤,幸存的行立刻重新 可见,于是看起来就是「已删除的归档会话又出现了」;点它去读历史,Host 已无该会话 ⇒session "…" not found (session/not-found)。 旧代码唯一的客户端通知是evictSessionFromMemory()里entry.detach()顺带触发的session/disposed → api-session/removed,只有会话仍驻留内存(live)时才会发生; 已归档且未打开的会话通常非 live ⇒ 一个事件都不发,行永久残留到重启。修复(
lib/index.js):- 新增
announceSessionRemoved(),在每个删除成功分支显式ctx.emit("api-session/removed", id)——与 DSH 自己在session/disposed上做的完全一致(dsh-api-session-controller对api-session/added/removed的转发即此模式),该事件在dsh-api-remotes的API_REMOTE_FORWARDED_EVENTS白名单内,客户端handleSessionRemoved会走recordMutation({kind:'remove'})真正删行。 因 cordis 的emit同步且不隔离(一个监听器抛错会打断后续),emit 用 try/catch 包住, 抛错只记入warnings,绝不回滚已完成的删除。 - 四个删除分支(无 header / 无路径 /
found:false/ 删盘成功)统一收口到finishRemoval(), 保证「归档集合 + 工作区记账 + 内存驻留 + 客户端通知」四件事不漏。 - 修
location.found被忽略的 bug:原:544只判断path非空,而resolveSessionLocation在没有任何候选根命中时返回的仍是拼出来的path(found:false,其文档注释已写明此时 "deletion then prunes the archive id only")。旧代码会拿这个猜测路径去rm -rf,必然失败 ⇒ 报「删除失败」且归档 id 永远卡住。现按found判定。 - 释放工作区记账(新增
pruneWorkspaceAccounting()):dsh-workspace明确写着归档 故意不碰sessionIds("Archiving never touches workspace accounting"),所以删除必须自己 释放,否则注册表永久记账一个盘上已无、header 也读不回来的 id(bootstrap对sessionPaths缺失的 id 直接continue,只能手改workspace.json才能清)。 走registry.list()→entity.detachSession(),它是竞态安全的官方写路径:在域写链上判成员、 无变化时用内部哨兵中止(不写盘、不发变更),并按 canonical-cwd 头索引重滤sessionIds——正是validateStoredState要求的「一个 id 不得被两个工作区同时记账」。 /dsh-archived/sweep同样收口(原实现删了目录却从不清归档集合、也从不通知客户端, 会造出与 1 完全相同的鬼行);返回体新增可选warnings[]。
修复(
lib/client.js):ArchivedSessionsPage()原本不接收任何参数,函数体里的ctx.workspaces/ctx.sessions/ctx.timer全是自由变量 ⇒ 组件作用域内根本没有ctx⇒ReferenceError,又被静默try/catch吞掉、promise 直接丢弃。也就是说refreshViews()从未真正执行过(连带reloadAfterAction()的刷新腿、armDelete的 5 秒 自动取消也全是死代码)。现由apply()通过 slot props 把ctx传入组件(与 DSH 官方客户端插件 把t/renderSlot等按 props 注入的写法一致),refreshViews()改为真正awaitctx.sessions.refresh()并在失败时console.warn(不再静默),同时删掉客户端并不存在的ctx.workspaces.refresh()调用。它作为兜底与 Host 事件互补:refreshList()的mergeOrderedBaseline会丢弃 baseline 中不存在的 id,且拉取期间记录的 mutation 会在基线到达后 重放(removedSincePull),所以补拉不会把已删除的行复活。其它:
package.json的dsh.client.inject移除了@deepseek-ai/dsh-client-runtime——该包在 DSH 里根本不存在(289 个@deepseek-ai/*包中无此目录,仅dsh-invariants的 README 正文提到过这个名字)。该字段按官方文档只用于激活顺序/预取元数据,不参与 cordis 注入;保留无效名只会误导。验证(scratch 独立
DSH_HOME,真机dsh scratchweb,非 live 的归档会话=报告场景): | | 旧代码 | 新代码 | |---|---|---| | 发给客户端的api-session/removed| 0(行残留,即本 bug) | 1 | | 归档集合 | 已清 | 已清 | | 工作区记账(workspace.json落盘复核) | 仍记账已删 id | 已释放 | | 磁盘目录 | 已删 | 已删 |另用真实
mergeOrderedBaseline跑客户端逻辑仿真,确认「remove 事件」与「await refresh」两条腿 都能删掉该行,且拉取期间到达的 remove 不会被基线覆盖。- 新增
0.3.9(2026-09-30):peer 声明改用三元组区间
>=0.2.0-rc.1 <0.2.1-0(代码零改动)。 背景:0.2.0-rc.2发布后门禁又把只声明到0.2.0-rc.1的 0.3.8 拦下——连续第三轮同型故障 (设置项消失、POST /dsh-archived/list→ 405),根因不是不兼容,而是逐版本枚举在 rc 迭代下必然过时。 核查:0.2.0-rc.1 → rc.2全量 20 包逐文件 SHA256——dsh-shell/dsh-session/dsh-session-persistence-jsonl/dsh-session-query/dsh-workspace的lib/全部逐字节零变化 (仅package.json版本号)。门禁矩阵实测:0.2.0-rc.1/rc.2/rc.3/rc.9/正式版0.2.0全 PASS,0.2.1-rc.1/0.3.0-rc.1全 BLOCK。 ⚠️ 本条同时收窄支持面:不再声明0.1.7线,需要在 0.1.7 上运行请装 0.3.8。0.3.8(2026-09-28):追加 DSH
0.2.0-rc.1支持(peer 枚举0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1,代码零改动)。 背景:DSH 桌面端升到0.2.0-rc.1后,门禁把只声明到0.1.7-rc.2的 0.3.7 拦下(设置页里本插件整项消失、POST /dsh-archived/list→ 405 且/dsh-archived/前缀未注册;是优雅跳过而非崩溃)。 核查:0.1.7-rc.2 → 0.2.0-rc.1全量 tarball 逐文件 diff——本插件依赖面 零破坏:dsh-shell(唯一 shell 依赖面,execute/resolve/result三件套)lib/逐字节零变化;dsh-session的 5 个变化文件为附加式(新增ToolCallRecovery/TOOL_NOT_STARTED/TOOL_OUTCOME_UNKNOWN,SessionStore等既有导出全保留,导出集合比对只有「新增」无「删除」)。 另:0.2.0-rc.1门禁对新声明判放行、对 0.3.7 旧声明判拦截;沙箱安装官方@deepseek-ai/[email protected]+ 独立DSH_HOME跑--dump-config→ 插件入树、零拦截;npm pack产物复核含新枚举。0.3.7(2026-09-25):追加 DSH
0.1.7-rc.2支持(peer 枚举0.1.7-rc.1 || 0.1.7-rc.2,代码零改动)。 背景:DSH 桌面版 0.1.7-rc.2 上线后,门禁把只声明0.1.7-rc.1的 0.3.6 拦下(插件列表「异常」)。 核查:dsh-shell的lib/在 rc.1→rc.2 逐字节零变化(本插件唯一 shell 依赖面);rc.2 门禁执行 新声明 → 放行;scratch profile([email protected]+ 0.3.6 + 豁免)真机删除实测通过——POST /dsh-archived/delete {"sessionId":"session-f71d25a2-…"}→{"ok":true,"deleted":true}+ 磁盘目录消失 +archivedSessionIds清空 +list回{items:[]}(测试会话已从备份还原)。0.3.6(2026-09-24):
ctx.shell双时代自适应 + 声明 DSH 版本支持。- 修复 0.3.5 对 rc.x 的静默回归:0.3.5 改用
execute()修好了 0.1.7,但run()在 0.1.5/0.1.6 上才是唯一存在的方法,于是 0.3.5 在那些版本上每次删除都抛shell executor unavailable; cannot delete from disk(0.3.4 用run()反而是 rc.x 可用、 0.1.7 全废)。0.3.6 改为运行期探测:优先execute()(0.1.7+,需再await execution.result()), 否则回退run()(≤0.1.6),两者皆无才报错 ⇒ 0.1.5-rc.1 → 0.1.7-rc.1 全区间删除可用。 依据:逐版解包@deepseek-ai/dsh-shell的lib/types/index.d.ts(见上方「ctx.shell双时代自适应」)。 - 新增
@deepseek-ai/dshpeer 声明0.1.7-rc.1(逐版本枚举,语义见上方「兼容性门禁」)。 - 文档同步说明 0.3.5 的 rc.x 回归——避免有人把 0.3.5 当作「rc.x 也能用」的版本。
- 修复 0.3.5 对 rc.x 的静默回归:0.3.5 改用
0.3.5(2026-09-24):适配 DSH 0.1.7-rc.1 —— 修复"删除失败"。0.1.7 的
ctx.shell是「resolve(request) → spec/execute(spec) → ShellExecution/execution.result()」三件套, 没有run()(0.1.5/0.1.6 的run(spec)已移除)。旧代码removeDir()用typeof shell.run !== "function"做前置检查,在 0.1.7 上必然抛出shell executor unavailable; cannot delete from disk—— 每个会话目录都在真正碰磁盘之前就被拒, UI 表现为红色「删除失败 N 个会话: session-…」。修复:改用resolve+execute+result(); 任一执行异常都包成明确的「删除失败」错误。同时把 peer 的@deepseek-ai/cordis: ^4.0.1范围改为逐版本枚举4.0.1 || 4.0.2 || 4.0.4(本仓库规范 3:只声明实测过的版本,不用开放范围)。⚠️ 本版的代价:
execute()在 0.1.5/0.1.6 上不存在 ⇒ rc.x 上删除静默失效,由 0.3.6 的 双时代探测修回。0.3.4(2026-09-15):适配 DSH 0.1.6-alpha.1——DSH 0.1.6 起在 web-app 内置了原生「已归档会话」设置页(
@deepseek-ai/dsh-client-ui-settings-unarchive-sessions),它在settings.section槽位注册的 id 恰为archived-sessions,与本插件旧 id 相同 → slot 冲突,整个 Web UI 启动报「Failed to load plugins」被拦截(运行时二分实测:去掉本插件即恢复)。修复:本插件 slot id 改为唯一的omdp-archived-sessions、导航标签改为「归档会话管理」,与原生项共存(原生只有查看+恢复,删除/按树删除/孤儿清理仍是本插件能力)。其余 API 面(sessionPersistence.list/workspaceRegistry/sessionQuery/shell.run/fs/jsonl 路径编码)对 0.1.6-alpha.1 源码核查零差异。0.3.3(2026-09-10):修复 0.3.2 的删除回归——0.3.2 把会话根目录改为
DSH_HOME推导时拼出了混合分隔符路径(C:\Users\xj\.dsh/sessions/...),而删除前校验assertSessionDirName的 basename 提取对混合分隔符失效(先按/切再按\切,把名字切成残缺片段),导致所有删除被"拒绝删除非会话目录"拦截。修复:① basename 提取改为按分隔符整体切分;② 根目录统一为/。删除/孤儿清理恢复正常。0.3.2(2026-09-10):修两处 fork 遗留——① client 半区的模块 id 仍是
@muwinds/dsh-archived-sessions(未随包名改),浏览器端按@omdp/...找不到模块、设置页不显示;② 会话根目录改为从DSH_HOME环境变量推导(<DSH_HOME>/sessions,缺省~/.dsh/sessions),不再硬编码本机路径。0.3.1(2026-09-10):修复 0.3.0 的发布事故——0.3.0 的 tarball 里没有
lib/(仓库.gitignore的**/lib/规则把源码吞了,npm 只打包到 4 个文件),安装后插件加载失败会拖垮 DSH;0.3.1 补回lib/index.js+lib/client.js(发布前已核对 tarball 内容)。0.3.0(2026-09-10):fork 自 0.2.0;适配 DSH 0.1.5-rc.1(list 快照形状 + 路径自解析);按树删除子会话;孤儿清理;删除路径安全校验。
English
What is this
DSH sessions can be archived; this plugin adds an archived-session manager under Settings → 归档会话管理 (Archived Sessions Manager):
- List: title, session ID, workspace, disk usage, created time, running state;
- Release (释放): move a session out of the archive set back to active (no data deleted);
- Delete (删除): delete the session directory from disk + remove from the archive set (two-step confirm);
- Tree delete: deleting a main session also deletes its subagent children (
parentSessionchain) — fixes upstream issue #2; - Orphan sweep: scan and clean leftover subagent dirs whose parent session is already gone;
- Detail: expand to read the session content (first 100 messages).
Why this fork
Upstream @muwinds/dsh-archived-sessions 0.2.0 is incompatible with DSH 0.1.5-rc.1 (author unmaintained for a month):
| Symptom | Root cause |
| --- | --- |
| All archived items show 文件缺失 (missing) | 0.1.5-rc.1 sessionPersistence.list() returns SessionPersistenceSnapshot[] ({header, revision, eventCount?, sizeBytes?}), not bare SessionHeader[]; the plugin read the old shape, so header.id was undefined |
| Delete behaves like Release (both only prune the archive id) | 0.1.5-rc.1 removed locate(meta) from the abstract service; persistence.locate(header) returned undefined → .path threw TypeError → swallowed by catch → no-artifact branch skipped the disk deletion |
Fixes in this fork:
list()recognizes the snapshot shape and readssnapshot.header; prefers the snapshot'ssizeBytes;- No longer uses
locate(); resolves the session directory with the same path encoding as the DSH JSONL backend (encodeSegment/projectKey), confirmed viafs.resolve; - Refuses to delete anything whose directory name is not a session dir (
session-<uuid>or bare UUID) — no accidental deletions; - Delete collects the whole
parentSessionsubtree recursively and removes children together (dirs + archive set); - New
/dsh-archived/orphansand/dsh-archived/sweepto scan/clean orphan subagent sessions (parent no longer in persistence).
Install
pnpm add @omdp/dsh-archived-sessions -wMigrating from
@muwinds/dsh-archived-sessions: remove the old dependency (github:MuWinds/dsh-archived-sessions) from the profile package.json first, then install this package. Same API routes (/dsh-archived/*); refresh the page after installing.
Requirements
- DeepSeek Harness 0.2.0-rc.1 / 0.2.0-rc.2 (tested; since 0.3.9 the peer uses the triple range
>=0.2.0-rc.1 <0.2.1-0, which automatically covers later rc builds in the same triple and the stable0.2.0). The earlier 0.1.5–0.1.7 line was supported (0.3.6+) but is no longer declared as of 0.3.9 — use 0.3.8 or older if you need it on 0.1.7. @deepseek-ai/dsh>=0.2.0-rc.1 <0.2.1-0(peer, triple range; since 0.3.9)@deepseek-ai/cordis4.0.1/4.0.2/4.0.4(peer, enumerated per version)@deepseek-ai/dsh-session-persistence-jsonl(optional, ships with DSH; falls back to the built-in path encoding when absent)
Dual-era ctx.shell adaptation (0.3.6)
The directory-removal step is the only place this plugin actually touches the disk, and ctx.shell's
contract was renamed in DSH 0.1.7 — the two eras expose different methods, and the old one is removed
outright in the new one (verified by unpacking each @deepseek-ai/dsh-shell npm tarball and reading
lib/types/index.d.ts: abstract run(...) exists and execute appears 0 times in
0.1.5-rc.1/0.1.5-rc.2/0.1.5-rc.3/0.1.6-alpha.1; 0.1.7-rc.1 has
abstract execute(spec): Promise<ShellExecution> and run appears 0 times):
| DSH | Call chain | Result |
|---|---|---|
| ≤ 0.1.6 (rc.x / alpha) | resolve(request) → spec → run(spec) → Promise<ShellRunResult> | only run |
| ≥ 0.1.7 | resolve(request) → spec → execute(spec) → ShellExecution → await execution.result() | only execute |
removeDir() now probes at runtime: prefer execute() (0.1.7+), fall back to run() (≤0.1.6), and only
error out when neither exists — so one build deletes across the entire line. The lesson: 0.3.4 called
only run() (fine on rc.x, dead on 0.1.7) and 0.3.5 called only execute() (fine on 0.1.7, silently
dead on rc.x, where the typeof shell.run !== "function" guard threw immediately and the UI surfaced
it as the red "删除失败" banner) — both were bets on a single era.
Compatibility gate (declared since 0.3.6, triple range since 0.3.9)
The current declaration is "@deepseek-ai/dsh": ">=0.2.0-rc.1 <0.2.1-0". DSH's own
evaluatePluginCompatibility() (a public export of dsh-app-boot) checks it at install time and on
every boot: on an untested newer runtime the whole bundle is gracefully skipped (stderr prints
skipping profile bundle; DSH still boots), and on the tested runtime it loads normally.
0.3.9 semantic change: the peer moved from per-version enumeration
(0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1) to a triple range. Enumeration forces a declaration change
plus a republish for every new rc (0.2.0-rc.1 → rc.2 hit this twice, each time as "settings item
gone, route 404/405"), whereas rc builds inside one [major,minor,patch] are iterations of the same
API contract (verified: rc.1 → rc.2 across 20 packages is byte-identical except package.json
version strings for everything this plugin touches). So testing the first rc of a triple clears its
later rc builds and the stable release. A range never spans more than one triple — crossing a
patch/minor (e.g. 0.2.1-rc.1) still requires a fresh check and a new segment (AGENTS.md rule 3).
⚠️ The upper bound must be
<0.2.1-0, never<0.2.1: semver sorts prereleases before the stable release, so0.2.1-rc.1 < 0.2.1holds and<0.2.1would leak the next triple's rc. ⚠️ 0.3.9 drops the0.1.7line — it will be gated off there; use 0.3.8 or older on 0.1.7.
The 0.1.7-rc.2 addition (0.3.7) is based on: a file-by-file tarball diff showing dsh-shell's lib/
(this plugin's only shell surface) is byte-identical rc.1 → rc.2; running the rc.2 gate against the
new declaration → pass; and a live end-to-end delete test on a scratch profile
([email protected] + 0.3.6 + exact-version exemption): POST /dsh-archived/delete
{"sessionId":"session-f71d25a2-…"} → {"ok":true,"deleted":true} + directory gone from disk +
archive set emptied + list returning {items:[]} (the test session was restored from backup).
API
| Method | Request | Response |
| --- | --- | --- |
| POST /dsh-archived/list | {} | { items, totalBytes } |
| POST /dsh-archived/unarchive | { sessionId } | { ok, changed, archivedSessionIds } |
| POST /dsh-archived/delete | { sessionId } | { ok, deleted, sessionId, alsoDeleted[], reason? } |
| POST /dsh-archived/detail | { sessionId } | { id, createdAt, cwd, parentSession, totalEvents, messageCount, truncated, messages } |
| POST /dsh-archived/orphans | {} | { items, totalBytes } |
| POST /dsh-archived/sweep | {} | { removed, freedBytes, items } |
Changelog
0.3.9 (2026-09-30): peer declaration switched to the triple range
>=0.2.0-rc.1 <0.2.1-0(zero code changes). Background: after0.2.0-rc.2shipped, the gate blocked 0.3.8 the same way it had blocked 0.3.7 on0.2.0-rc.1— the third consecutive round of the same failure (settings item gone,POST /dsh-archived/list→ 405). The root cause was never incompatibility but per-version enumeration going stale on every rc. Verification: full 20-package file-by-file SHA256 diff0.2.0-rc.1 → rc.2—dsh-shell/dsh-session/dsh-session-persistence-jsonl/dsh-session-query/dsh-workspaceare all byte-identical (onlypackage.jsonversion strings changed); the real gate over the full matrix gives0.2.0-rc.1/rc.2/rc.3/rc.9/ stable0.2.0all PASS and0.2.1-rc.1/0.3.0-rc.1all BLOCK. ⚠️ This narrows support: the0.1.7line is no longer declared — install 0.3.8 if you need it there.0.3.8 (2026-09-28): adds DSH
0.2.0-rc.1support (peer enumeration0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1, zero code changes). Background: after the desktop app moved to0.2.0-rc.1the gate skipped the bundle declared only up to0.1.7-rc.2(the settings-page item disappeared andPOST /dsh-archived/listreturned 405 with the/dsh-archived/prefix unregistered — a graceful skip, not a crash). Verification: full file-by-file tarball diff0.1.7-rc.2 → 0.2.0-rc.1over everylib/file — nothing this plugin uses broke:dsh-shell(its only shell surface: theexecute/resolve/resulttrio) is byte-identical, anddsh-session's 5 changed files are additive (new exportsToolCallRecovery/TOOL_NOT_STARTED/TOOL_OUTCOME_UNKNOWN;SessionStoreand every pre-existing export kept — the export-set diff shows additions only, no removals). Also: the0.2.0-rc.1gate passes the new manifest and blocks the old 0.3.7 one; a sandbox with the official@deepseek-ai/[email protected]+ isolatedDSH_HOMEran--dump-config→ plugin present in the tree, no gate skip; thenpm packartifact was re-checked to carry the new enumeration.0.3.7 (2026-09-25): adds DSH
0.1.7-rc.2support (peer enumeration0.1.7-rc.1 || 0.1.7-rc.2, zero code changes). Background: the DSH desktop app ships runtime0.1.7-rc.2, so the gate skipped 0.3.6's exact-rc.1declaration (plugin list badge 异常). Verification:dsh-shell'slib/is byte-identical rc.1 → rc.2 (this plugin's only shell surface); the rc.2 gate passes the new declaration; and a live delete test on a scratch profile ([email protected]+ 0.3.6 + exemption) succeeded end-to-end —POST /dsh-archived/delete {"sessionId":"session-f71d25a2-…"}→{"ok":true,"deleted":true}+ directory gone + archive set emptied +list→{items:[]}(test session restored from backup).0.3.6 (2026-09-24): dual-era
ctx.shelladaptation + declared DSH version support.- Fixes a silent rc.x regression from 0.3.5: 0.3.5 switched to
execute()and thereby fixed 0.1.7, butrun()is the only method that exists on 0.1.5/0.1.6, so on those versions 0.3.5 threwshell executor unavailable; cannot delete from diskon every delete (0.3.4's use ofrun()was conversely fine on rc.x and dead on 0.1.7). 0.3.6 probes at runtime: preferexecute()(0.1.7+, awaitingexecution.result()), else fall back torun()(≤0.1.6), and only error when neither exists ⇒ deletes work across 0.1.5-rc.1 → 0.1.7-rc.1. Basis: unpacked@deepseek-ai/dsh-shelllib/types/index.d.tsper version (see "Dual-eractx.shelladaptation" above). - Added the
@deepseek-ai/dshpeer declaration0.1.7-rc.1(enumerated per version; see "Compatibility gate" above). - Documented 0.3.5's rc.x regression so nobody treats 0.3.5 as "fine on rc.x".
- Fixes a silent rc.x regression from 0.3.5: 0.3.5 switched to
0.3.5 (2026-09-24): DSH 0.1.7-rc.1 support — fixes the "删除失败" (delete failed) banner. 0.1.7's
ctx.shellis the trioresolve(request) → spec/execute(spec) → ShellExecution/execution.result(); there is norun()any more (0.1.5/0.1.6 hadrun(spec)). The oldremoveDir()guarded ontypeof shell.run !== "function"and therefore always threwshell executor unavailable; cannot delete from diskon 0.1.7 — every session directory was rejected before touching the disk, surfacing in the UI as the red banner "删除失败 N 个会话: session-…". Fix: useresolve+execute+result(); any execution error is wrapped into an explicit delete-failure error. Also changed the peer from the range@deepseek-ai/cordis: ^4.0.1to per-version enumeration4.0.1 || 4.0.2 || 4.0.4(repo rule 3: only declare actually-tested versions, never open ranges).⚠️ What this version cost:
execute()does not exist on 0.1.5/0.1.6 ⇒ deletes silently stopped working on rc.x, fixed again by 0.3.6's dual-era probe.0.3.4 (2026-09-15): adapted to DSH 0.1.6-alpha.1 — since DSH 0.1.6 the web-app ships a native archived-sessions settings page (
@deepseek-ai/dsh-client-ui-settings-unarchive-sessions) that registers thesettings.sectionslot with idarchived-sessions— exactly the id this plugin used → slot collision, and the whole Web UI boot failed with a "Failed to load plugins" screen (verified at runtime by bisecting plugin subsets: removing this plugin restores boot). Fix: this plugin's slot id is now the uniqueomdp-archived-sessionsand its nav label is "归档会话管理", coexisting with the native entry (the native page only lists/restores; delete, tree-delete and orphan sweep remain this plugin's features). All other DSH surfaces used by the plugin (sessionPersistence.list/workspaceRegistry/sessionQuery/shell.run/fs/jsonl path encoding) verified byte- or removal-free against 0.1.6-alpha.1.0.3.3 (2026-09-10): fixes a 0.3.2 delete regression — 0.3.2 derived the session root from
DSH_HOMEwith a mixed-separator path (C:\Users\xj\.dsh/sessions/...), and the pre-delete guardassertSessionDirName's basename extraction broke on mixed separators (it sliced by/then by\, producing a truncated fragment), so every delete was rejected with "拒绝删除非会话目录". Fixed: ① basename extraction now splits on either separator; ② roots are normalized to/. Delete and orphan sweep work again.0.3.2 (2026-09-10): fixes two fork leftovers — ① the client half's module id was still
@muwinds/dsh-archived-sessions(not renamed with the package), so the browser could not find the module and the settings page never rendered; ② the session root is now derived from theDSH_HOMEenvironment variable (<DSH_HOME>/sessions, falling back to~/.dsh/sessions) instead of a hard-coded local path.0.3.1 (2026-09-10): fixes a 0.3.0 release accident — the 0.3.0 tarball contained no
lib/(the repo's**/lib/gitignore rule swallowed the source, so npm packed only 4 files); installing it broke the plugin load and could take DSH down. 0.3.1 restoreslib/index.js+lib/client.js(tarball contents verified before publishing).0.3.0 (2026-09-10): forked from 0.2.0; DSH 0.1.5-rc.1 support (snapshot list shape + self path resolution); tree delete; orphan sweep; deletion path safety check.
License
MIT — original by MuWinds (upstream package), fork maintained by XJungit.
