dsh-session-admin
DSH 网页插件:会话管理。为会话增加删除入口,并在设置中提供已归档的聊天管理页,可查看 / 恢复 / 导出 / 永久删除。
当前版本 0.2.0(见 CHANGELOG:修复了 4 个在当前 harness 上会让功能静默失效的兼容性 bug)。
功能
删除会话
会话标题栏会出现一个删除按钮(垃圾桶图标)。点击并确认后,会话被删除(移入归档,记录保留)。
删除走 DSH 运行时原生的归档通道(workspace.archiveSession),与官方「归档会话」菜单共用同一份状态,因此不会和其他插件冲突。
删除会话
已归档的聊天管理
在 设置 → 已归档的聊天 中管理所有已删除(归档)的会话:
已归档的聊天
- 查看:弹窗阅读完整会话记录 —— 用户 / 助手消息、工具调用(含参数)、工具结果(含失败标记);工具名通过日志中的
tool/call 事件与 tool/result 关联。
- 恢复:把会话移出归档,回到原工作区位置(走 workspace 存储域的原生状态写入,多标签页实时同步)。
- 导出:导出为 Markdown(人类可读转写)或 JSONL(完整原始事件日志,每行一个事件)。标题与「用户 / 助手 / 工具结果」等抬头跟随当前界面语言。
- 永久删除:确认后移除持久化日志(文件级删除,不可撤销),并清理工作区账目、投影缓存与归档集合引用。
运行中判断
只有正在运行(agent 执行中,存在未闭合的 turn)的会话会被拒绝删除;空闲会话——包括本次运行中打开过、仍在内存里的——都可直接删除 / 恢复。「运行中」标记优先读 turnBoundary 投影,缺失时回退到会话日志快照(snapshotEvents()),因此两种 harness 组合下都准确。
文案与语言
所有可见文案(含角色名、条目计数、导出抬头)统一走 DSH 语言服务,跟随「设置 → 语言」切换;插件不再读 navigator.language,同一界面内不会出现中英混排。
安装
dsh plugin --profile web add dsh-session-admin
也可以直接从 GitHub 安装(会执行本包的 prepare 构建;pnpm ≥10 首次需要把 pnpm 提示的包名写进 profile 的 pnpm-workspace.yaml 的 allowBuilds):
dsh plugin --profile web add github:Jemius/dsh-session-admin#v0.2.0
插件通过 dsh.bundle.patch 追加到 profile 的 bundle 栈;该行是纯新增(不改写任何官方行),所以不会和其他插件或未来的 harness 升级冲突。
兼容性
插件只通过 cordis 服务与 slot 接入,所有跨包访问都经过 src/host/types.ts 里的结构镜像 + 形状适配器,而不是直接按某个版本的属性名取值 —— 因为后者失效时不报错,只是功能静默失灵。
0.2.0 修掉的就是这一类问题(均在 dsh 0.1.5-rc.1 上实测复现):
| 现象 | 原因 | 现在的处理 |
|---|
| 「已归档的聊天」永远是空列表 | sessionPersistence.list() 已从 SessionHeader[] 改为 {header, revision}[] 快照 | headerOfEntry() 同时接受两种形状 |
| 「永久删除」显示成功但磁盘文件还在 | 同一处形状不匹配让 locate() 分支被整段跳过 | 同上,并有单测覆盖真实目录删除 |
| 删除正在运行的会话不被拒绝、「运行中」标记从不出现 | 活跃 Session 没有 events 成员(旧代码读它) | 优先 turnBoundary 投影,回退公开的 snapshotEvents() |
永久删除必定报 unsafe-path | 安全校验按 session.jsonl + session-<id> 目录判断,而真实路径是 session.v3.jsonl.zstd + 编码后的 id | assertSafeSessionArtifact() 按当前命名规则校验 |
peer 依赖范围写作 ^0.1.0-rc.6 || >=0.1.5-rc.1 <0.2.0:semver 规定「带 prerelease 的版本只有在同 major.minor.patch 的范围内才会被接受」,所以后续 prerelease(如 0.1.5-rc.1)必须显式列出,否则 pnpm 会一直报 peer 警告。
依赖的上游私有 API(已知脆弱点)
以下能力 harness 目前没有公开 API,插件用「可探测 + 失败即响亮报错」的方式使用,并在上游提供正式 seam 前保持兼容:
workspaceRegistry.requireState() / setState()(TS-private,但目前是唯一的取消归档写入路径)。插件的读-改-写自带串行化 + 提交后校验重试,以降低与官方 archiveSession 并发时的丢失更新概率。建议上游补 unarchiveSession(sessionId)。
sessionPersistence.locate(meta)(TS-private,jsonl 后端私有方法)。缺失时永久删除返回 unavailable,不会猜路径。建议上游补 remove(sessionId)。
- 活跃会话的 store 内部
store / detachEntered:优先调用 entry 自身的公开 detach(),仅在缺失时回退私有方法并记警告。
架构
- Host 半(
lib/index.js):挂载带防火墙的 /session-manager/api/{list,read,restore,export,delete} 路由(回环 / 可信主机校验,与官方 /api 网关和 dsh-better-sidebar 一致)。归档集合读自 ctx.workspaceRegistry,标题与会话表面读自 ctx.sessionQuery(批量 readTitleSnapshots 一次观测取全部标题),持久化工件读自 ctx.sessionPersistence。永久删除先移除会话工件目录(这是 seam 没有删除 API 时约定的外部维护路径),再清理工作区账目、投影缓存行,最后移除归档集合引用;JSONL 后端下次列表时会重新发现目录布局,FTS 索引自行对账。传输层只接受 POST,统一 JSON envelope,带 cache-control: no-store,超大请求体返回 413。
- Client 半(
lib/client.js):通过 slot 系统注册 settings.section 设置页、conversation.session.header.actions 头部按钮与 shell.overlay 提示条队列(ctx.slots.inject 会等待声明方就绪)。不做 DOM 拦截、不替换服务、不改官方行。样式全部走 CSS Module 作用域和共享的 --dsw-* 主题变量;文案接入 DSH 语言包(中 / 英)。
构建与测试
npm install # 或 pnpm install
npm run build # 生成 lib/index.js 和 lib/client.js
npm test # vitest:兼容性 / 删除安全 / 防火墙 / 路由 / 转写映射
npm run typecheck
prepare 也是构建脚本,因此从 git 安装时会自动构建。
tsconfig.json 只覆盖 host 半与测试:client 半的导入目标(@deepseek-ai/dsh-client-ui-primitives 等)由宿主在运行时提供,本仓库不安装它们,因此 client 半靠构建器与运行时验证。
lib/ 是产物但随仓库提交,方便直接安装与审阅;改动 src/ 后请重新构建并一并提交。
自测
仓库外随插件提供 test-ui.mjs,用 puppeteer-core + 系统 Chromium 驱动真实浏览器做端到端验收:删除按钮、归档、设置页归档列表、转写查看(含工具调用)、导出、恢复、永久删除、磁盘清理,以及与官方会话菜单和 dsh-better-sidebar 的共存。运行方式:
# 1. 用隔离的 DSH_HOME 启动测试实例(共享真实 profiles,隔离数据):
# $env:DSH_HOME=<temp-home>,把真实 profiles 目录 junction 到 <temp-home>/profiles
# $env:DSH_HOME=...; dsh web --port 3099
# 2. node test-ui.mjs