Files
session-delete/README.md
T
pyh da8e5e43a4 refactor!: align with the DSH plugin conventions and drop the npm publish workflow
Follow the cordis-plugin-development references instead of the ad-hoc choices
the first version made.

Client half:
- register the row button under this package's own id instead of shadowing the
  shipped `archive` action at a lower priority, so the official hover buttons
  keep their cells and archiving stays where the harness put it
- drop the synthetic `pointerout` dispatched at a `[data-row-key]` ancestor:
  a plugin does not read or drive another package's DOM, so the tooltip is now
  positioned from its own button alone
- keep the self-rendered primitives, the `--dsw-*` token-only styling, and the
  modal focus/Escape behavior, and document why

Host half:
- resolve a conversation's descendants with `sessionQuery.traceSession()`
  instead of listing every stored header and re-deriving `parentSession` edges
- decide liveness from the traced `SessionRecord.live` flag, which removes the
  `agents` and `sessions` dependencies from `inject`
- build the orphan-sweep corpus from `sessionQuery.listSessions()`, which
  already merges live and persisted sessions
- document that `sessionPersistence.locate()` is a JSONL-backend diagnostic
  hook, not part of the seam, and keep probing it explicitly
- record only tree roots in the deferred-deletion ledger: the activation sweep
  removes a root with everything under it, so a separately recorded child was
  deleted twice or needed a pass that never runs
- return the activation's sweep promise from `apply()` so a test can await it

Manifest and docs:
- version 2.0.0, `private`, `dsh.manifestVersion`, and `engines`
- delete the Gitea npm publish workflow and every npm-publishing task: the
  package is distributed only through the Git repository and tags
- rewrite the README around the current install paths and the plugin's limits
2026-09-30 15:30:39 +08:00

88 lines
6.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 会话彻底删除
DeepSeek Harness(DSH)插件:给会话加上**真正不可逆的删除**,并清理已删除会话遗留的临时文件。
DSH 原本只能归档会话——归档只是把会话从列表里移开,日志仍然留在磁盘上(DSH 官方的 JSONL 会话存储后端明确写着「没有任何东西会删除会话文件,日志会一直堆在 root 下直到被外部移除」,会话存储契约里也没有删除 API)。这个插件补上的正是这一块:两处删除入口,以及设置页里的一次性孤儿文件清理。
## 功能
### 1. 删除会话
两个入口,行为一致,都会先弹出确认弹窗(复刻系统 Modal 的样式与键盘行为:Esc 关闭、Tab 焦点圈定、打开时焦点进入、关闭后焦点归位):
- 会话行右侧 **⋯** 菜单 → **删除会话**
- 会话行悬停时的 🗑 按钮(在官方的「归档」「置顶」之前,是悬停区的第一个按钮)
确认后一次性处理:
| 对象 | 处理方式 |
| --- | --- |
| 会话日志目录 | 整个删除,仅保留 `session.lock` |
| 子智能体会话 | 按血缘递归收集,连同日志与溢出文件一并删除 |
| 工具输出溢出文件(spill) | 删除该会话(含子会话)对应的 spill 目录 |
| 工作区登记项 | 解除绑定、取消置顶、取消归档 |
| 客户端列表 | 广播 `api-session/removed`,列表行立即消失 |
有意**不**处理:
- **附件对象**(`$DSH_HOME/attachments/v1/objects`):按内容 sha256 寻址,同一份内容可能被多个会话共享,而 DSH 没有引用计数,跟着一个会话删除会破坏别的会话;
- **投影缓存**(`$DSH_HOME/storages/session_projcache`):由日志派生的缓存,读取时带生命周期身份校验,永远不会串到别的会话。
### 2. 清理临时文件
设置 → 通用设置 → **清理临时文件**,清理 spill 后端留下的**孤儿**目录。判定偏保守:
1. 目录名必须精确等于 `session-<sha256(会话 id) 前 12 位>`——这是 spill 后端唯一会生成的形状;
2. 该名字不属于任何已知会话(`sessionQuery.listSessions()` 已经把存量会话与内存中正在运行的会话合并成同一份语料,所以「已知」只需要一个来源);
3. 只在 spill 的 root 内操作(后端当前 root + 系统临时目录下的 `dsh-spill*`);
4. 符号链接一律跳过、不跟随;单项失败只记警告。
结果直接显示在该行(清理了几个目录、几个文件、释放多少空间),可反复点击。
## 行为与边界
- **仍在运行的会话**无法被插件从内存里移除:它会先被归档并停止,日志与溢出文件在**下次启动 DSH 时**删除(界面会明确提示);在此之前取消归档就会取消这次删除。
- 删除**不可恢复**,但**不是安全擦除**:按普通文件删除处理,底层介质上可能有残留。
- 工作区里的代码文件不受影响。
- 溢出文件被删除后,fork 出来的会话日志里若仍保留指向它的路径文本,那些路径会失效。
- 行内按钮的悬停气泡由按钮自身定位。指针停在会话行上时,行自己的悬停预览卡可能和这个气泡同时出现——插件不会去动宿主的行 DOM 来避免这一点(那是插件不该依赖的自有渲染结构)。若观感不能接受,可把气泡改为只在键盘 focus 时显示:删掉 `DeleteSessionRowButton` 里的 `onMouseEnter: show` / `onMouseLeave: hide` 两行即可。
## 安装
DSH 的 **插件 → 添加插件** 里有两个输入,含义不同:
- **上面那个输入框**:装什么(包名 / Git 地址 / 本地 tgz 的绝对路径);
- **下面的「安装源」**:只决定**按包名安装时**去哪个注册表查。填 Git 地址或 tgz 时它不参与拉取,保持默认(官方源)即可,不必切到「自定义地址」。
### 1. Git 仓库地址(推荐)
在上面的输入框里填:
```text
https://gitea.iwake.top/dsh-plugin/session-delete.git#v2.0.0
```
`#` 后面可以跟标签或提交,用来锁定版本;不写则取默认分支。仓库是公开的,不需要凭据,也不需要改安装源。
想跟随 2.x 的最新版本,可以把后缀写成 `#semver:^2.0.0`——pnpm 会按仓库里的标签挑最高的 2.x,以后发了 v2.0.1,移除旧版本再装一次就能拿到。
### 2. 本地 tgz
把仓库打包成 tgz(例如在仓库目录执行 `npm pack`,得到 `dsh-plugin-session-delete-2.0.0.tgz`),把它在运行 DSH 那台机器上的绝对路径填进上面的输入框。
### 升级
DSH 暂不支持插件自动更新,升级就是用新标签重装一次(建议先移除旧版本)。安装或升级后:**宿主半变更需要重启 DSH**(完全退出再打开,仅关闭窗口不一定退出进程);只改浏览器半时刷新页面即可。
## 兼容性
基于 **DeepSeek Harness 0.2.0-rc.2** 编写。DSH 仍在演进,其它版本与平台组合未逐一验证。
- **浏览器半**不 import 任何 Harness 客户端包(上游规范如此要求:这些包随时可能变化,渲染抛错会让整个插槽条目变空白),而是复刻 `dsh-client-ui-primitives` 的 markup、CSS 声明、图标 path 数据与键盘行为,只保留 `--dsw-*` 主题 token 引用。它**不读写宿主的 DOM**,也不占用官方槽位:行内按钮用自己的 id 与官方「归档」「置顶」并列,不是顶替。上游若改动这些内部结构,界面细节可能失配(例如悬停气泡不再出现),但不会导致插件加载失败。
- **宿主半**只依赖稳定的服务契约:`sessionQuery`(`traceSession` 取血缘、`listSessions` 取语料)、`sessionPersistence`(含其 JSONL 后端的 `locate()` 诊断钩子,用于定位会话目录)、`workspaceRegistry`、`connection.fetch`(注册受认证的 exact 路由),以及 spill 后端的目录规则。
- **跨半边通信**走 `connection.fetch` 注册的 `POST /api/plugin/session-delete/delete` 与 `.../orphans` 两条路由,浏览器半用文档相对路径请求(支持挂载前缀)。这是静态客户端包与宿主半通信的正规通道;`host.call` 属于动态客户端运行器,不适用于包形式的插件。
## License
[MIT](LICENSE)