跳到正文
dsh-market 浏览插件 GitHub EN

SunshineR04/dsh-session-manager

在设置页管理已归档会话:恢复或彻底删除(直接物理删除,无备份层)。会话菜单新增红色彻底删除项,附三个 agent 工具。已打开的会话也能立即删除,残留内存副本以归档墓碑隐藏、重启后自动清理。

Star 数 ★ 1 分类 会话与消息 收录于 2026-09-05

安装

在 DeepSeek Harness 里通过 dsh-market 安装

dsh plugin --profile web add dshmarket

或使用命令行

dsh plugin --profile web add "https://github.com/SunshineR04/dsh-session-manager/releases/download/v0.3.0/dsh-session-manager-0.3.0.tgz"

装任何插件都等于在你的机器上跑第三方代码,权限和你本人一样大——能读你的文件、用你的凭据、访问网络。请先审阅源码,并尽量锁定 commit(github:owner/repo#sha)。

README

DSH 插件:彻底删除会话(永久物理删除,回收磁盘),配套一个跨工作区的 已归档会话汇总列表,以及会话三点菜单里的红色「彻底删除」项。

官方已经做了什么,本插件补什么

DeepSeek Harness 从不删除会话文件。 官方的删除工作区文案写得很直白: *「这只会把「{name}」从工作区列表移除。**文件夹和会话日志都会保留。*它的 会话会出现在未分组下。」 于是一个长期使用的安装会为每段对话永久保留一个 会话目录,没有任何回收磁盘的入口。

归档/恢复这半边已经不再是缺口。 从 dsh 0.1.7 起,官方自带三点菜单里的 归档与取消归档、侧栏的显示已归档 / 仅显示已归档视图筛选、撤销 toast, 以及一道准入闸门(归档会话及其子代理谱系会被阻止继续执行模型步骤)。请直接 用官方那套 —— 本插件不再试图替代它们。

能力 官方(dsh 0.1.7) 本插件
归档 / 取消归档 ✅ 三点菜单,停止工作进行中任务的确认,撤销 toast ✅
找到已归档会话 ✅ 侧栏显示已归档筛选 ✅ 所有工作区汇成一张列表
删除会话文件 ❌ 从不 ✅ 红色彻底删除
批量操作 ❌ ✅ 全选 + 一次确认批量删除

每次删除都是直接物理删除,没有备份层。

功能

1. 设置页:设置 → 会话管理

  • 列出全部已归档会话:标题、所属工作区、项目目录、更新时间、运行状态
  • 每个会话提供 恢复(回到归档前在工作区中的位置)和红色 彻底删除
  • 全选 + 批量删除:勾选若干行(或表头全选框)后一次确认删除全部选中 会话。流程串行且容错——单个失败会完整列出原因而其余照常删除;正在运行 任务的会话会被跳过(确认框会先告知数量);每次批量给出统一结果:全部成功、 部分失败或全部因运行中被拒绝
  • 删除前弹出破坏性确认框——每次删除都是直接物理删除,没有备份层
  • 数据来自官方的 session / workspace 客户端 store,实时响应,无需手动刷新

2. 会话三点菜单:红色的「彻底删除」

左侧会话列表中,鼠标悬停会话行 → 右侧 ⋯ 菜单,在原有 重命名 / 分叉会话 / 归档会话 下方新增红色「彻底删除」项,点击后同样弹出确认框。

该行是官方插槽 sidebar.workspaces.session.menu.item 里的一个普通条目 (id session-manager-delete,order 500),用与内置 置顶 / 重命名 / 分叉 / 归档 行相同的 MenuItemButton 原语渲染 —— 危险红、分隔线、键盘行为都直接 来自官方菜单本身,而不是对它的样式模仿。

该插槽自 dsh 0.1.7-alpha.1 起提供。在此之前,这一行只能靠观测 DOM + React fiber 树反查会话来注入,于是官方菜单任何标记变化都可能让它静默消失 —— 0.1.7-rc.2 给每个菜单项加上快捷键提示,就正好如此。

3. Agent 工具(模型可直接调用)

工具 说明
session_list_archived 列出已归档会话(JSON)
session_restore_archived 按 id 恢复(可逆操作,无需确认)
session_delete_permanently 按 id 彻底删除已归档会话;必须 confirm: true;拒绝未知 id、未归档 id 与正在运行的会话

安装

需要 dsh 0.1.7 这条线(含 rc 版本)。它的两个硬依赖都自 0.1.7 起提供: 尺寸中性的产品图标,以及三点菜单那一行所注册的 sidebar.workspaces.session.menu.item 插槽。在更旧的版本上,三点菜单不会挂载 这一行,删除只剩下设置页一条路径。

git clone https://github.com/SunshineR04/dsh-session-manager.git
dsh plugin --profile <name> add "file:<克隆目录的绝对路径>"

dsh plugin add 会通过 pnpm 安装并自动把包加入 dsh.profile.bundles; 本包的 cordis.patch.yml(bundle layer)自动挂载插件行,无需手改配置。 刷新页面后若插件仍未出现,再重启 dsh 桌面端(运行中的 profile 会在刷新时重建 大部分 patch,但新加入的 bundle 不保证会被拾取)。

⚠ 不要用裸 npm 包名安装本插件。 npm 上的 dsh-session-manager 是另一个 无关项目(hkkz9522/dsh-session-manager, 已发布到 0.5.x,维护者不同),它同样会注册一个会话菜单行——所以装错也会"看起来能用"。 请按上面的方式从本仓库安装。

也可以在 profile 的 cordis.patch.yml 手工挂载(不推荐,bundle 通道更省事):

- insert:
    - id: session-manager
      name: dsh-session-manager

删除语义(重要)

⚠ 一个 DSH_HOME 只应跑一个 host。 待删队列是单个读-改-写文件,而串行化它的操作锁是 进程内的:两个 dsh 实例共用一个 home 时,双方可能读到同一份快照、后写者赢,静默丢掉对方的标记。 该标记的墓碑从此没有任何东西能扫除,而文件已经不在——这一行既无法恢复、也无法再删除。 scripts/twohost-race-probe.mjs 用真实 manager 复现了它(输出 RESULT: LOST 1 marker(s): …)。 忠实修法是改队列格式(按 id 的原子标记文件)或引入真正的跨进程锁,不是加重试循环。

「彻底删除」按经过设计的顺序执行:

  1. 仅已打开的会话:在只读存在性检查之后、任何变更之前先写入持久 的待删标记(崩溃安全——半途崩溃时下次启动总有标记可扫);从该队列 文件读回的 id 会先通过格式校验才允许触达文件系统。
  2. 登记清理(持久且广播):从所属工作区的 sessionIds 摘除 (detachSession),再从全局归档集移除。摘除时并不只信任工作区 sessionIds 的过滤视图——注册表的 canonical-cwd 头索引一旦失准, getter 会把该 id「隐身」,导致 detach 被跳过、已删会话以「未分组」 身份复活(0.1.4 及之前的真实 bug),因此同时对照工作区的原始记录兜底。
  3. 删除会话文件:~/.dsh/sessions/<编码项目目录>/<session-id>/( session.jsonl.zstd 日志本体)。目录解析走三级兜底:注册表头 + persistence locate → persistence 头清单 → sessions 根目录原始扫描 (目录名与已校验的会话 id 精确相等),头接缝失准不再会静默漏删。
  4. 删除元数据缓存:~/.dsh/storages/session_projcache/sessions/<id>.json 及其 .bak-* 检查点。
  5. 广播官方 api-session/removed 事件:所有已连接客户端的会话列表 立即移除该会话。(宿主自身只在 live 会话 dispose 时才发这个事件, 冷删除永远等不到。)

搜索索引(SQLite)会随源文件消失自动对账,无需处理;附件存储是内容寻址的, 保留不误伤其它会话,也不做删除。

  • 每次删除都是直接物理删除——没有备份层,确认框请看清会话标题。
  • 删除已打开的会话同样立即生效:登记、文件、元数据当场清除(后续 flush 不会复活任何东西——append 按路径打开日志,从不重建已删目录)。 由于 dsh 没有公开的「关闭会话」API,内存中的副本会存续到所属界面作用域 消失为止;此时该 id 以墓碑形式保留在归档集中,重启 dsh 后自动完成 收尾清理。有运行任务的会话会被拒绝;allowDeleteRunning 只跳过这道拒绝 ——强制删除同样打墓碑并入队收尾。
    • 删除当下:客户端不会再为这个 id 回拉会话列表(删除成功即加入本次 会话的待删集合;api-session/removed 到达后的去抖回拉同样会跳过,且 触发时与触发后各校验一次——删除响应与事件走不同通道,事件常常先到), 因此刚删掉的会话在任何视图里都不会立刻回到列表,包括 「全部对话(显示已归档)」。
    • 唯一残留形态:重新加载页面、或另一个客户端,会从宿主列表重新学到 这个 id——因为 dsh 仍在列出内存副本。该 id 是归档项,默认视图(隐藏 已归档)始终隐藏它;只有在「显示已归档 / 仅显示已归档」视图下,它会以 归档样式出现在「未分组」。banner 里的待删队列就是它,重启后彻底消失。
    • 修复钩子(0.4.1 起默认开启):每次读取删除队列时,宿主会为仍存活 的排队 id 重播官方 api-session/removed,所有已连接客户端随即把它从自己 的列表里移除——于是归档视图也能保持干净;代价是每次列表回拉后可能有一瞬间 的闪现(客户端按「一次残留一次修复」去抖,不会轮询)。客户端在宿主回应 ping 后就读取队列,读取失败会自动重试,因此一次瞬时失败不会让这个修复 在该页面整段失效。其他每一次显式回拉(「刷新」按钮、删除后的校验、取消删除) 都会在回拉之后重新检查残留,因此被回拉带回来的行同样会被修掉;而整批删除中 只要有一个是打开中的会话,就跳过整批回拉(这些 id 已由 api-session/removed 从 store 中摘除)。设为 reannouncePendingRemovals: false 可退回「仅墓碑」行为:此时重新加载页面 后,显示归档行的侧边栏会重新看到它。
  • 待清理横幅:设置页顶部只列出仍可操作的条目。文件仍在盘上的 条目(例如删除中途崩溃的残留)提供「取消删除」,取消时先读队列、先摘标记 再清墓碑——反过来会留下「墓碑已清、标记仍在」,那是数据丢失:下一次启动会 读到这个残留标记,把用户刚刚取消掉的会话删掉。注册表拒绝解除归档时会把标记 写回,因此取消仍可安全重试;队列读不出来时什么都不会被改动;而不在队列里 的 id 会被以 session/not-pending 拒绝(取消是唯一一个「把会话放回去」的操作, 不能去恢复一个没人排队的会话)。文件已删净的条目(打开 会话的正常删除)已无任何可执行动作,因此不再逐行占据横幅,而是折叠为 一行「已删除 · 重启后自动清理」汇总(附展开按钮可查看具体 id)—— 这也不仅是 UI 隐藏:通过 RPC/工具取消它同样会被宿主以 session/data-gone 拒绝(解除墓碑只会让无文件的内存残留以「未分组」复活)。
  • 队列文件读不出来时,宁可不做也不猜:待删队列是删除收尾的唯一凭据, 每个写入者都会把「刚读到的那份快照」写回文件——所以把一次读取失败当成 「队列为空」会在下一次写入时抹掉全部标记,而它们的墓碑会永远留在归档集里 (文件已删、无法恢复、也无法清除)。因此:文件不存在=正常的空队列; 读取失败或 JSON 损坏(含非原子回退写造成的撕裂文件)=拒绝执行,与删除队列 相关的操作(列表、恢复、取消、打开会话的删除、启动收尾)统一返回 session-manager/internal 并在宿主日志留痕,队列文件保持原样不动; 修复该文件后一切恢复正常。冷会话删除仍会继续执行(队列损坏时它是唯一 还能用的删除路径),但会在返回结果里明确写出「本次删除不具备崩溃安全」—— 没有标记,中途崩溃就没有任何东西能在下次启动时收尾。
  • 冷会话删除在同一次调用内收尾,且不再有崩溃窗口:它同样先写待删标记、同样 在操作期间保留归档墓碑,文件删净后再一起清掉。文件阶段失败(Windows EPERM/EBUSY、杀毒/索引器/外部句柄占用)时条目会同时保持墓碑与队列, 而不是报告「已删除」却让侧边栏把它当活会话重新渲染出来,下次启动的收尾流程会 重试。在此之前,冷会话删除不写标记,因此「归档集写入」到「rm」之间一旦崩溃, 该会话既没归档也没删除:下次启动会把它当未分组会话重新列出,而本插件再也 看不到、恢复不了、重试不了它。
  • 恢复操作只从归档集移除 id —— 归档本身保留工作区 sessionIds 槽位, 因此恢复后会话回到归档前的原位置。处于删除队列中的 id 会被以 session/pending 拒绝(文件仍在盘上时可先「取消删除」)——恢复它只会 暴露一个无文件的空壳。

配置

字段 默认 说明
sessionListLimit 500 单次列表返回上限
allowDeleteRunning false 强制删除正在运行任务的会话(仅跳过拒绝——强制删除同样打墓碑并入队收尾;危险)
toolDeleteRequiresConfirm true Agent 删除工具强制 confirm: true
menuDeleteAvailable true 是否在三点菜单挂红色删除项
reannouncePendingRemovals true 读取删除队列时,为仍存活的排队 id 重播官方 api-session/removed,让「显示已归档」视图也不再渲染残留墓碑(设为 false 退回「仅墓碑」行为,见「删除语义」)

开发

pnpm install
pnpm test   # 语法检查 + 宿主单测 + 客户端渲染冒烟测试

开发环境需要 Node ≥ 22.22.2——渲染测试在 jsdom 里挂载 React,而 jsdom 30 声明 ^22.22.2 || ^24.15.0 || >=26。插件本身在 Node ≥ 20 即可运行 (engines.node),只有测试套件需要更新的运行时。

渲染测试(test/client.render.test.mjs)用 React + jsdom 真实挂载设置页 组件,能抓住宿主单测覆盖不到的 UI 崩溃(如 hooks 顺序 / 变量提升错误)。

接入验证请走下方「浏览器 E2E」:scripts/e2e-seed.mjs 建一个带标记的一次性 DSH_HOME,scripts/e2e-check.mjs 在真实 UI 里做只读断言。

浏览器端到端验证(可选)

启动一个一次性 dsh web 实例,跑在隔离的 DSH_HOME 上(绝不碰真实数据),用 puppeteer-core + 本机 Chrome 驱动界面:

cp scripts/e2e-seed.local.example.json scripts/e2e-seed.local.json
#    ^ 填入你自己的会话/工作区数据(已 gitignore,不会提交)
node scripts/e2e-seed.mjs <e2e-home> ~/.dsh          # 1. 播种隔离测试 HOME
# 2. 在该 HOME 下建 profile 装本插件,再启动测试 web 实例。
#    ⚠ 桌面版 `dsh` 启动器把 DSH_HOME 写死为真实 home,因此前缀
#    DSH_HOME=<e2e-home> 并不能隔离(已实测:profile 会落到真实 home)。
#    改为用一个能指向该 home 的 Node CLI —— dsh npm 包里的那个入口:
#    ⚠ 不要再使用本文件早期版本推荐的 `--expose-internals "<app.asar>/lib/desktop-cli.js"`
#    形式:在某些桌面构建上该 asar 路径解析不了,CLI 会以 MODULE_NOT_FOUND 退出
#    (2026-09-30 针对已装桌面端实测)。
#      DSH=$(npm root -g)/@deepseek-ai/dsh/lib/bin.js
#      DSH_HOME=<e2e-home> node "$DSH" --profile sm-test --from-default-profile web --dump-config
#        ^ 从内置 web 模板建 profile 后直接退出(不启动)
#      DSH_HOME=<e2e-home> node "$DSH" plugin --profile sm-test add <tarball>
#      DSH_HOME=<e2e-home> node "$DSH" --profile sm-test --no-open --port 43123
#        ^ 启动;打印的那一行里带 token URL
node scripts/e2e-check.mjs <打印出的带 token 的 URL>  # 3. 只读检查:三点菜单/设置页
node scripts/e2e-mutations.mjs <URL> <e2e-home>       # 4. 闭环:恢复→归档→彻底删除
node scripts/e2e-residue.mjs <URL> --home <e2e-home>  # 5. 残留验收:两个视图都不该有该行
node scripts/e2e-contrast.mjs <URL> <e2e-home>        # 6. 危险红字在明暗两套主题下的 WCAG AA 对比度

⚠ 第 6 步要求该 home 的归档集非空(seed 会造一个已归档会话),并且真的测到那一行: 设置页头部的批量删除/刷新按钮在归档集为空时也会渲染,所以「测到了东西」不算验收, 没测到归档行仍然 exit 2。它是只读的:不归档、不删除任何会话。

⚠ 第 4、5 步会真的删除会话,因此有两道互相独立、缺一不可的守卫 (scripts/e2e-guard.mjs):

  1. --home(或第二个参数)必须是一个本仓库播种过的 home:标记文件必须是为这个路径 写的。真实 ~/.dsh 会按名字、按前缀(它内部的任何路径同样拒绝)被拒,大小写变体、 8.3 短名、junction/符号链接、\\?\ 路径也绕不过去(比较会解析真实路径,Windows 上 忽略大小写)。只有名字像标记的目录、从别的 home 复制来的标记、以及 0.4.8 之前 没记录 home 的标记同样被拒——请重新播种。
  2. URL 背后的实例必须正在使用这个 home。URL 本身证明不了这一点:脚本会在第一次点击前 询问页面能看到哪些会话(插件自己的 list 接口 + 页面上渲染出的 session:… 行), 只要有任何一个 id 不在播种 home 里就拒绝;页面什么都答不出来时按失败处理(fail closed)。 没有这道检查时,把一个破坏性脚本指向你自己正在运行的 dsh、再传一个合法的 --home, 可以通过全部检查并删掉你的真实会话。

两道守卫都有无需浏览器、无需 dsh 的单元测试(test/e2e-guard.test.mjs);seed 自身也会 先校验完整 spec、拒绝 源 == 目标,并在任何删除动作之前写下标记。

其余脚本是诊断工具,不是验收测试:e2e-bug2.mjs 与 e2e-live.mjs 复现已修 的现场 bug(两者都会真删会话,都有守卫),e2e-probe.mjs 是一次性的 DOM 侦察。 哪些脚本真的会失败很关键——一份永远以 0 退出的过程记录,无论读起来多让人放心, 都不是测试:

脚本 会失败吗
e2e-residue.mjs 会(抛错 → exit 1)
e2e-dialog-style.mjs 会(8 项样式检查,任一不过就抛)
e2e-realclick.mjs 会——它的 bug-1 hover/点击断言是真的 exit 1 路径,不再只是日志
e2e-contrast.mjs 会(危险红字在明暗两套主题下须达 WCAG AA)
e2e-guard.mjs 会(拒绝未播种的 home)
e2e-check.mjs、e2e-mutations.mjs、e2e-bug2.mjs、e2e-live.mjs、e2e-probe.mjs 不会——它们打印过程记录。真崩溃(缺 Chrome、超时)仍会非 0 退出,但断言失败在退出码里看不见,所以要看输出

Chrome 不在默认路径时设置 CHROME_PATH。

如果你想手写调用本插件的路由(这些脚本都走真实 UI,所以都没体现这一点):路由挂在共享的 /api 前缀下,并且要求 connection 插件那套信封 —— POST /api/session-manager/<endpoint>,content-type: application/json,body 为 { type: 'client-request', rpcId, method: 'session-manager/<endpoint>', payload }。 其中 method 必填且必须与端点一致;否则会得到 405 / 415 / 400 或 bad-request: invalid client-request message。

参考项目

本插件在开发前调研了以下 GitHub 项目(其中部分可直接在本地 ~/.dsh/profiles/desktop/node_modules 中对照源码):

  • omdsh-dev/DSH-better-sidebar —— dsh 社区插件标杆:bundle patch、client 注入、settings.section 注册、 /sidebar/api 宿主路由,都是本插件结构的直接参照。
  • ysr666/dsh-vision-router —— 同类「设置页 + 宿主 RPC + 手写无打包 client」插件,client 模块骨架完全照此。
  • deepseek-ai/deepseek-harness —— dsh 本体:dsh-api-workspace-controller(archiveSession / follow 流)、 dsh-api-session-controller、dsh-session-persistence-jsonl(目录编码、 zstd 日志)、dsh-workspace(归档集语义与恢复定位)等包是本文档所有行为 结论的依据。
  • koishijs/koishi —— dsh 的 Cordis 插件 运行时上游,ctx.effect / ctx.inject / patch 体系的理解来源。
  • tmux-plugins/tmux-resurrect —— 概念参照:终端会话的保存/恢复/清理产品形态。
  • opencode-ai/opencode —— 概念参照: 编码代理的会话持久化与恢复入口设计。

License

MIT

内容来自项目 README(GitHub)↗

评论

评论存放在 GitHub Discussions。用 GitHub 账号登录后可发表评论或点表情。