Memory

dsh-session-delete-ui

ant404/dsh-session-delete-ui · ★ 0

ant404

DeepSeek Harness plugin: adds a permanent 「删除会话」 / Delete session action to the sidebar session row menu, with a themed confirmation dialog and a running-session guard.

README body is shown in Chinese.

License
MIT
Added
2026-10-30
Maintenance
active
Install kind
github
Manifest details
Manifest status
Declared
Manifest path
package.json#dsh
Manifest checked
2026-10-30T09:38:38.642Z
Lifecycle scripts
None found

Static manifest checks report declarations only; they are not compatibility or security review.

安装

dsh plugin --profile web add 'github:ant404/dsh-session-delete-ui#e8087c948f716ca2cbbb4d4dd7b06c48ec8e087c'

GitHub 源码安装:首次需按提示配置 allowBuilds 构建授权后重试

For install and environment setup, plus plugin development tutorials, see DSH community docs ↗

README

Source README · body: zh · Read Chinese README

Table of contents
  1. 功能
  2. 安装
  3. 工作原理
  4. Host 半(index.js)
  5. Client 半(client.js)
  6. 安全设计
  7. 验证
  8. 同类项目调研
  9. 已知限制
  10. 目录
  11. License
  12. English

English | 中文

DSH(DeepSeek Harness)插件:在左侧会话行的「⋯」菜单里补上官方的「删除会话」能力。

官方 Harness 只提供归档(Archive),没有删除:dsh-session-persistence 的文档明确写着 「No deletion or retention API — pruning stored sessions is out-of-band backend maintenance」。 本插件就是把这个 out-of-band 维护做进 UI,并且做得安全、可确认、可回退。

会话行 ⋯ 菜单
  ├─ 置顶 / 重命名 / 分叉 / 归档      ← 官方内置
  └─ 删除会话                        ← 本插件(order 500,独立分组,红色危险项)
        ↓
   确认对话框:会话名 + 「不可恢复」警告 + 将删除的子代理数量 + 占用空间
        ↓
   永久删除:会话日志目录 + 投影缓存 + 工作区记录(含子代理会话级联)

功能

入口 文案 行为
会话行「⋯」菜单 删除会话 永久删除该会话的本地日志目录、session_projcache 投影缓存、工作区账目(成员关系 / 归档 / 置顶标记)
同上 → 确认对话框 — 明示不可恢复、列出将级联删除的子代理会话数量与占用空间;只在会话确有在跑的工作时才需要先停止

「在跑的工作」≠「进程里还留着这个会话」:官方 session store 会保留打开过的会话实例, 一个已归档、完全空闲的会话也可能是 resident。插件只把真正在跑的工作 (进行中的回合 / 运行中的子代理 / 后台任务 / 定时提醒)当作阻断条件,并把这些工作列在对话框里, 提供**「停止并删除」**(等价于官方「停止并归档」的停止路径:workspace/session-stop)。 resident 但空闲的会话直接可删。

子代理会话(origin: 'subagent')会随其父会话一起删除;分叉出来的会话不会被牵连 (分叉会话没有 origin: 'subagent',只有 parentSession)。

安装

本插件是一个标准 DSH bundle(dsh.bundle.patch + dsh.client 双半包)。

方式一:在图形界面里安装(推荐,profile 由应用托管时唯一受支持的方式)

设置 → 插件 → 安装 bundle,目标填本目录的绝对路径:

D:\path\to\dsh-session-delete-ui

方式二:手动接入 profile(本仓库当前采用的方式)

desktop profile 被 Electron 应用独占,dsh plugin --profile desktop 会被拒绝 (profile "desktop" is managed exclusively by the Electron application)。手动接入等价于 install_bundle 的两步:

# 1. 在 profile 的 node_modules 里建立软链(hoisted 布局,直接 junction 即可)
New-Item -ItemType Junction `
  -Path "$env:USERPROFILE\.dsh\profiles\desktop\node_modules\dsh-session-delete-ui" `
  -Target "D:\path\to\dsh-session-delete-ui"

# 2. 在 profile 的 package.json 里登记依赖 + bundle
#    dependencies: "dsh-session-delete-ui": "link:D:/.../dsh-session-delete-ui"
#    dsh.profile.bundles: 追加 "dsh-session-delete-ui"

应用会实时重组 profile:保存 package.json 后无需重启,Host 半立即挂载 (可访问 GET /session-delete/api/health 验证)。浏览器半需要页面刷新一次才能加载新的模块行。

安装不写入 cordis.patch.yml:挂载行由包内的 cordis.patch.yml 随 bundle 层生效。

工作原理

Host 半(index.js)

注册同源 JSON 路由:

路由 作用
POST /session-delete/api/preview 只读预检:是否已无记录、active(在跑的工作) 与 resident(进程内仍驻留)、活动工作清单、残留记账(归档/置顶/成员关系)、日志目录、占用空间、级联子代理列表
POST /session-delete/api/delete 执行删除;stopActivity: true 时先走官方停止路径;日志已不存在时转为残留清理;返回 deleted / cleaned / skippedActive / missing / failed 明细
GET /session-delete/api/health 挂载自检(返回 sessions 根目录、持久化服务可用性、存活会话数)

删除顺序(先文件、后账目,任何失败都不会留下「已隐藏但没删掉」的半删状态):

  1. 活动判断:用 Harness 自己的归档准入问题 ctx.waterfall('workspace/session-activity', …) 取工作族(turn / job / subagent / schedule, 与官方「停止并归档」用同一套 provider);有工作且未要求停止 → 409 active, 要求停止则 ctx.parallel('workspace/session-stop', …) 后短暂等待收敛,仍不收敛 → 409 still-active。 ctx.sessions.get(id) 只用来报告 resident,不再作为拒绝依据;
  2. 定位:通过 ctx.sessionPersistence.list() 取全部 header,再用后端的 locate(header) 得到绝对路径; 退化路径是扫描 <sessions>/<project>/<id>;
  3. 路径校验(assertRemovable):必须是 <sessionsRoot>/<project>/<id> 恰好两段、目录名等于会话 id、 不是符号链接/junction、realpath 后仍在 sessions 根内 —— 校验通过才进入删除;
  4. rmSync(目录, { recursive: true }) 删除日志目录;
  5. 删除 ~/.dsh/storages/session_projcache/sessions/<id>.json;
  6. 工作区账目:unarchiveSession / unpinSession / detachSession(仅显示与记账,失败只记日志), resident 与否都清。曾经为了保住归档闸门而保留归档标记,结果是日志删掉后 id 仍留在 archivedSessionIds 里,形成用户清不掉的「幽灵行」,该做法已回退;
  7. 残留清理路径:日志目录已经不存在时,不删文件、只清第 6 步的三种记账,返回 cleanupOnly: true 与 cleaned 明细,不再用 404 把请求打死。删除一个「进程里还开着、但磁盘上已经没了」的会话时, 对话框会说明这一行会在关闭该会话或重启 DSH 后消失,并提供**「清除残留记录」**。

子代理级联由 header 的 origin === 'subagent' + parentSession 推导,深度上限 6 层。 比较 id 时同时接受带与不带 session- 前缀的写法(DSH 两种都在用)。

Client 半(client.js)

window.__ModuleLoader__.load({ id, factory }) 形式的浏览器 bundle,只用官方 Slot 扩展点:

  • sidebar.workspaces.session.menu.item(order: 500)→ 菜单项「删除会话」;
  • shell.overlay → 确认对话框与结果提示。

删除成功后调用 ctx.get('sessions').refresh() 让会话目录重新拉取,行随即消失,无需刷新页面。 样式从官方 primitives 的 Menu.module.css / Modal.module.css / Button.module.css 复制后改名为 dsd_ 前缀,只引用 --dsw-alias-* 主题 token,因此亮/暗主题自动跟随。

不 require 任何 Harness Client 包(@deepseek-ai/dsh-client-ui-*),只用浏览器模块表里的 react。

安全设计

  • 双重确认:菜单项 → 确认对话框;对话框中的删除按钮在预检完成、且会话可删时才会启用;
  • 只在有活时拦截:有在跑的工作时列出工作族并走「停止并删除」;resident 但空闲的会话不再被误判;
  • 路径围栏:删除前逐项校验路径,绝不跟随符号链接离开 sessions 根;
  • 级联有界:只级联 origin: 'subagent' 的子会话,分叉会话不受影响;
  • 先删文件后改账目,失败时行保持可见并把原因回给对话框。

验证

已执行(本机实测):

检查 结果
Host 行为测试 node test/host-behavior.test.mjs 35/35 通过:真实 sessions 根只读预检 + 隔离夹具上的真实删除与级联;resident 空闲可删 / 有活拒绝 / 停止后放行 / 清残留记账 / 幽灵行可清理 / 路径穿越拒绝 / 重复删除 404
Client 渲染测试 node test/client-render.test.mjs 46/46 通过:模块注册、两个 slot 的 id/order/locale、菜单项与对话框渲染、活动工作清单与「停止并删除」、resident 提示、幽灵行「清除残留记录」、无可清理时禁用、旧版 Host 半的降级提示、确认→刷新→提示
运行中的应用(Host) Host 已挂载:GET /session-delete/api/health → 200
真实路由 普通会话 preview → deletable: true 并给出真实目录与体积;有活会话 delete → 409
浏览器 Bundle 按 dsh-client-modules 的 rev 规则计算 combo URL 后请求 → 200,说明模块图已包含本插件的行

改完 Host 半需要重启 DSH:DSH 的插件 JS 是「一个进程一份模块代」,改了 index.js 后运行中的进程仍跑旧代码 (实测:改完后路由仍返回旧的 live 字段)。浏览器半会随文件变更重新下发,两个半版本不一致时, 对话框会明确提示「Host 半需要重启 DSH」,不会把旧版的误判当成结论。

尚未验证:浏览器里的视觉呈现(本会话没有浏览器控制能力)。菜单项与对话框的实际外观请在页面中确认; 若菜单里暂时看不到,刷新一次页面即可。

同类项目调研

动手前先看了 GitHub / npm 上已有的会话删除插件,取长补短:

项目 做法 本插件的取舍
vtxf/dsh-session-delete(npm dsh-session-delete) Host 路由 + ctx.shell 执行 rm/Remove-Item,客户端用 DOM 注入克隆菜单项(作者认为菜单没有 Slot) 保留「Host 路由 + 确认 + 存活拒绝」的思路;改用官方 Slot(sidebar.workspaces.session.menu.item 在现行版本已开放),并用 node:fs 直接删除(无 shell 转义面)
masknull/dsh-delete-session 侧栏「⋯」菜单加「删除会话」,不接管任何官方行 同思路,直接落在官方 Slot 上;额外做级联与预检
liuyu-f/dsh-plugin-session-cascade-delete 日志目录 + 投影缓存 + 工作区账目,级联子代理,先停再删,并提供 session_delete 工具 采纳级联边界(只删 subagent 子会话,分叉不碰)与「先删文件后记账」;工具入口留作后续
cckbc/dsh-delete-session / Seetraum/harness-session-delete(npm dsh-session-recycle-bin) 回收站:先移入回收站,默认保留 15 天,可还原 本插件定位是「永久删除 + 对话内确认」;回收站是另一条产品路线,未采用(如需可加 Config 开关)
hkkz9522/dsh-session-manager 完整的会话管理器:删除、归档、跨工作区移动、标签、批量操作 功能面大;本插件只做单一职责的删除,避免接管官方行
@linxin666/dsh-session-archive 集中式归档清单、批量归档/还原、物理删除、自动清理策略,含 node:sqlite 的 rdb 清理 其中「路径必须留在 sessions 根内、realpath 复核」的安全写法被本插件吸收

已知限制

  • 没有回收站:删除不可恢复(对话框已明示)。需要可恢复请改用回收站类插件;
  • 只清理本插件的关注面:dsh-cost-meter 等第三方插件自己的账本不会被清理;
  • 级联深度上限 6 层;
  • 改了 Host 半要重启 DSH:进程内的插件 JS 不会热替换;浏览器半改动需要刷新页面;
  • resident 会话的内存实例:删除后进程内仍持有该实例(应用重启后释放),因此那一行可能保留到关闭该会话或重启 DSH; 这时再点删除会走「清除残留记录」并明确告知,不会卡在「会话不存在」;
  • 删掉一个仍开着的会话会出现「幽灵行」:磁盘已空、内存还在。这是 Harness 没有会话释放接口导致的,插件只能清理记账并如实说明。

目录

dsh-session-delete-ui/
├── index.js                      Host 半:路由 + 删除实现
├── client.js                     浏览器半:菜单项 + 确认对话框
├── cordis.patch.yml              bundle 挂载行
├── package.json                  dsh.bundle.patch + dsh.client(platform/immediately/inject)
├── icon.svg / locale/{zh,en}.json  插件管理器卡片
└── test/
    ├── host-behavior.test.mjs    Host 行为测试(35 项)
    └── client-render.test.mjs    Client 渲染测试(46 项)

License

MIT


English

A DSH (DeepSeek Harness) plugin that adds the missing Delete session action to the sidebar session row menu (sidebar.workspaces.session.menu.item, order 500, its own danger group) plus a confirmation dialog in shell.overlay.

The shipped Harness has no deletion API, so the Host half performs that out-of-band maintenance: it refuses only running work (turn / subagent / job / schedule, asked through the Harness's own workspace/session-activity waterfall and stoppable through workspace/session-stop), resolves the artifact through ctx.sessionPersistence, validates the path (exactly <sessionsRoot>/<project>/<id>, never a symlink, still inside the real root), removes the log directory and the projection cache, and only then drops workspace accounting. A session that is merely still resident in the process — an archived, idle session, for example — is deletable; it keeps its archive gate so the in-memory instance cannot write a fresh log into the directory just removed. Subagent descendants cascade; forked sessions never do. Files first, bookkeeping second.

Verified locally: 35/35 host checks (real read-only planning + destructive run on an isolated fixture, including resident-idle, running, stop-then-delete, and ghost-cleanup cases), 46/46 client checks (registration, rendering, guards, confirm flow), the live Host route (GET /session-delete/api/health → 200, live session delete → 409), and the served browser bundle (combo URL computed from dsh-client-modules' revision rule → 200). Visual appearance in the page was not verified: this session has no browser control.