前言¶
DSH 的会话日志就是磁盘上的一个目录,但 DSH 本身没有删除的能力:SessionPersistence 只有 create/open/flush/stat/list,WorkspaceRegistry 只有 archive/unarchive。归档只是软删除,日志目录、注册表条目和子会话(subagent)的日志都留在磁盘上。长期跑下来,会话越积越多,却没有从界面真正清掉的入口。
dsh-session-delete 就是解决这个问题的第三方插件,维护者 Neruviko,属于客户端类插件,目前在社区目录 SkillHub 上星标为 0。它给侧边栏会话行的「…」菜单加了「删除会话」,带确认对话框和备份引导。下面介绍它做了什么、怎么装、删除过程的安全设计,以及使用上的注意事项。
这是什么¶
一句话:在 DSH Web 界面里,把一个会话连同磁盘日志和它的子会话子树真正删掉,并清理工作区注册表。
它的目标运行环境是 DSH 0.2.0-rc.2 的 Web GUI(作者实测可用),Node 版本要求 ^22.19.0 || >=24.0.0,与 DSH 本体一致。插件包版本 0.2.2,MIT 许可,两个半身(Host 与 Browser)都是手写的,没有构建步骤。
核心功能¶
功能清单来自插件 README,已在 GitHub 与 SkillHub 目录页核对:
- 侧边栏任意会话行的「…」菜单里新增「删除会话」,位置在「归档会话」之后。
- 可以删除任意会话,包括正在运行或正打开着的会话;删除前会先派发停止事件,停掉它的运行中工作(轮次、子代理、任务与提醒都会被取消)。
- 删除父会话时连带删除整棵子会话子树。子会话在侧边栏不可见,无法单独删除,只删父会话会留下再也访问不到的孤儿日志。
- 确认对话框显示会话标题、创建时间、日志大小、子会话数量;有子会话时明示「将同时删除 N 个子会话及其后代」。
- 对话框内嵌「下载此会话的 ZIP 归档」链接,引导先备份。
- 必须勾选确认框才能点删除。
- 删除后侧边栏行立即消失,不需要刷新页面。
值得单独说明的是它不加隐藏扫描:会话目录不是枚举出来的,而是由 JSONL 后端的目录布局确定性算出来的:
<DSH_HOME>/sessions/<projectKey(cwd)>/<encodeSegment(sessionId)>/session.v4.jsonl.zstd
index.js 里逐字符复刻了两个路径变换函数,encodeSegment(把任意字符串编码成单个安全路径段)和 projectKey(把 cwd 变成项目目录名,分隔符折叠成 -,两端加 --,例如 /home/me/projects/demo → --home-me-projects-demo--)。子树则来自 DSH 自己的 traceSession(),或 sessionPersistence.list() 里 header 的 parentSession 链接,同样不碰磁盘目录枚举。
安装与启用¶
下面是 README 给出的安装命令,是手写 git+https 形式,不是 dsh plugin add github:owner/repo。装到 web profile(其他 profile 同理,把 web 换掉即可):
dsh plugin --profile web add git+https://github.com/TonyZiYuan/dsh-session-delete.git
dsh plugin add 转发给 pnpm,装完会自动把新装的 bundle 追加到 profile 的 dsh.profile.bundles(依据包里 dsh.bundle 字段),不需要手工改 package.json。
如果安装报错说某个依赖的构建脚本被拦截,pnpm 默认不运行已安装依赖的 prepare 脚本,而 git 依赖需要先构建。此时按提示把包加进 profile 的 pnpm-workspace.yaml(在 ~/.dsh/profiles/web/ 下):
packages:
- .
allowBuilds:
dsh-session-delete: true
然后重跑上面的安装命令。pnpm ≥ 11 把 onlyBuiltDependencies 等设置合并成了这个 allowBuilds 映射,详见 pnpm v10 → v11 迁移说明。
更新和卸载:
dsh plugin --profile web update dsh-session-delete
dsh plugin --profile web remove dsh-session-delete
也可以从本地目录安装,路径必须是绝对路径:
dsh plugin --profile web add link:/absolute/path/to/dsh-session-delete
link: 的相对路径是相对 profile 的 manifest 目录(即 ~/.dsh/profiles/web/)解析的,写成 ./dsh-session-delete 会指向 ~/.dsh/profiles/web/dsh-session-delete,装不上。
装完之后必须重启 dsh web,bundle 与浏览器半身只在启动时加载(patchReload: live 只能热更新 patch,加载不了新的浏览器代码)。用这条确认挂载成功:
dsh --profile web --dump-config # 应能看到 `dsh-session-delete` 条目
典型用法¶
插件不带命令行入口,装好后操作全在 Web 界面里。一个会话的删除流程,按 README 描述的顺序:
- 停止运行中工作。派发
workspace/session-stop(与 DSH 归档时stopActivity: true是同一事件),各 provider 取消该会话的轮次、子代理、任务与提醒,随后短暂等待让取消落地。provider 抛错只记日志,不阻断删除。 - flush 屏障。调用
sessionPersistence.flush(),确保没有缓冲事件。失败不阻断,后续的白名单校验仍然守住字节。 - 定位目录。从 header 自带的
cwd算出目录,不扫描 sessions 根目录。 - 枚举后代子树。用 Session Query 的
traceSession()拿到任意深度的子会话树;没有 query 服务时回退为sessionPersistence.list()里 header 的parentSession链接。默认连带删除整棵子树。 - 整树预检。对父会话与每个后代逐个定位目录、检查是否逃出 sessions root、校验内容白名单。全部通过才继续;任一失败则整体中止,一个都不删。
- 递归删除。逐个
fs.rm(dir, { recursive: true, force: true })。操作系统句柄锁挡住时(WindowsEPERM/EBUSY,POSIXEACCES/ETXTBSY)返回session/in-use,提示关掉该会话后重试。 - 清理注册表。对每个已删 id 执行
workspaceRegistry的unarchiveSession/unpinSession/detachSession,同时更新内存缓存、持久化表与已连接浏览器的推流。没有 registry 组成时回退为原子改写workspace.json(临时文件 + rename,留一份.bak)。此步失败不阻断删除,仅在响应里标记registryCleaned: false。 - 发布移除公告。对每个已删 id 发出
api-session/removed,DSH 既有的转发链路会把该行从所有已连接浏览器的侧边栏移除。不发这一步,行会一直留到下次刷新。 - 记入墓碑。被删 id(含整个子树)记入本 Host 代的内存墓碑集,供浏览器半身的「幽灵收割器」使用。
HTTP 层面有两个路由,写请求额外要求来源是回环地址:
| 方法 | 路径 | 作用 |
|---|---|---|
| GET | /api/session-delete/sessions |
可删会话清单 + tombstones(本 Host 代已删 id,供收割器用) |
| POST | /api/session-delete/delete |
删除,body {sessionId, cascade?},cascade 默认 true |
删除响应里有 descendants 摘要,便于确认级联结果:
{
"ok": true,
"deleted": true,
"absent": false,
"freedBytes": 8192,
"registryCleaned": true,
"descendants": { "requested": 3, "deleted": 3, "ids": ["child-1a", "child-1b", "grand-1a"] }
}
两条路由都套 ctx.connection.requestRejection() 信任栅栏(Host/Origin 伪造与未认证请求会被拒绝)。这是 README 里明确写的内容,可以直接核对。
有一个坑:刷新后幽灵会复活¶
这是 README 里一段比较关键的说明。聊过天的会话,其 Agent/Session 对象会常驻 Host 内存(空闲会话要能被继续对话,是 DSH 的既定设计)。删除销毁了磁盘日志,但没有公开 API 能销毁别的 fiber 持有的 live 会话对象;客户端列表基线合并 live 会话又是无条件的(dsh-session-query 的 listSessions)。于是刷新浏览器后,基线从内存重建,已删的行会复活:出现在「未分组」下(注册表成员已清理),点开仍有完整文本(历史读取 live 优先,不碰磁盘)。
插件用两层配合来对冲:
- Host 侧:每次成功删除把 id 记入内存墓碑集,
GET /sessions响应带tombstones数组。墓碑与幽灵同生共死,Host 重启后两者都不存在。 - Browser 侧:订阅客户端会话列表快照,任何 id 若「在墓碑里却出现在列表中」,自动再发一次删除,走 absent 幂等路径,触发注册表清理与重公告,行随即掉落。只收割墓碑内的 id,新开的 blank 会话不受影响。
效果是刷新后幽灵即使闪现,也会在亚秒级被收掉。README 也明说,毫秒级的闪现窗口无法在插件层根除,需要上游在基线合并处支持「已删除会话排除」。手动兜底方式是对幽灵行再点一次「删除会话」,对话框会显示「日志已不在磁盘」状态,确认即可清除。
安全设计¶
README 列出的安全设计直接转述如下:
- 目录必须在 sessions root 之内,且 basename 必须等于
encodeSegment(sessionId),父会话与每个后代都各自校验。 - sessionId 必须匹配
/^[A-Za-z0-9._-]{1,128}$/。 - 内容白名单:用户放在会话目录里的文件不属于本插件该销毁的对象,宁可拒绝。
- 级联也是全有或全无:整棵子树先全部预检,任一成员不通过则一个都不删。
- 先停后删:删除前派发
workspace/session-stop,避免在活跃写入者下面撕裂日志;真正被句柄挡住时操作系统自己会拒绝,映射为session/in-use。 - 循环血缘:枚举后代时带
seen集合,畸形血缘不会无限递归。 - 注册表原子写(仅回退路径):临时文件 + rename,崩溃不会截断
workspace.json,并留.bak。
插件明确不做的事:不删 attachments 目录(内容寻址的共享存储,多个会话可能引用同一张图),不做批量删除、不做回收站/软删除,不改 @deepseek-ai/* 本体,不全盘扫描 sessions 根目录。
适用场景与注意¶
适合已经在用 dsh web profile、长期积累会话日志、需要真正清理而非归档的用户。它依赖 DSH 的多个内部契约(sessionPersistence 的公开 root 字段与 list/stat/flush,encodeSegment / projectKey 目录布局,workspace/session-stop 与 api-session/removed 事件,workspaceRegistry 的 unarchiveSession / unpinSession / detachSession,sessionQuery 的 traceSession() 后代树形状,以及浏览器半身的 slot 名与 dsh-client-ui-primitives 的导出),这些都是在 0.2.0-rc.2 上核对过的,未在其他上游版本验证前不要假定兼容。升级 DSH 后如果删除开始对存在的会话报 session/absent,或对话框组件缺失,多半是某个契约变了 —— 失败方向是安全的(宁可拒删不误删),对照新版源码更新 index.js 顶部的路径函数或组件用法即可。
几个使用上的注意点:
- 插件以当前
dsh web进程的权限运行,装之前先读index.js与client.js,确认审计无异议。许可证是 MIT。 - 路径变换函数是复刻的,DSH 升级改了它们就要同步更新,否则删除会误报
session/absent。 - 删除不可逆,插件本身不给回收站,想留档要在确认框里先下载 ZIP。
- 子会话默认级联删除;确实只想删自身可以传
cascade: false(HTTP body),但默认是不留孤儿。 - 目录证明是来源:SkillHub 目录页返回内容极少,没有提供可核对的功能清单或安装命令,本文事实以 GitHub 仓库 README 和 package.json 为准。安装命令来自 README 原文,指向的仓库地址与插件名不一致(命令里是
TonyZiYuan/dsh-session-delete),装之前请自行确认仓库归属。
结尾¶
dsh-session-delete 的价值不在于加一个按钮,而在于替 DSH 补上「删除」这一层,同时把级联删除、注册表清理、不可逆操作的入口控制都做在设计里。没有仪式感的停删删净,比多一个菜单项更值得看。
目录页:https://www.skillhub.cn/plugins/Neruviko/dsh-session-delete
GitHub:https://github.com/Neruviko/dsh-session-delete