dsh-delete-session 0.1.9 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 DDDMUC
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2026 DDDMUC
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,6 +1,9 @@
1
1
  # dsh-delete-session
2
2
 
3
- **DeepSeek Harness 会话删除插件 —— 在侧边栏会话菜单里直接删除会话。** 给 DeepSeek Harness (dsh) Web 侧边栏的会话 `...` 菜单(重命名 / 分叉 / 归档)补上「删除会话」:确认一次后可直接删除,真正删除会话日志、工作区记账与宿主内存中的会话状态。
3
+ **DeepSeek Harness 会话删除插件 —— 在侧边栏会话菜单里直接删除会话。**
4
+
5
+ 给 DSH Web 侧边栏的会话 `...` 菜单(重命名 / 分叉 / 归档)补上「删除会话」,并可选地把这个对话产出的文件一起收走。
6
+ 删除默认进**系统回收站**,点错了能还原;想彻底抹掉就在设置里关掉那个开关。
4
7
 
5
8
  [中文](#中文) · [English](#english)
6
9
 
@@ -8,30 +11,24 @@
8
11
 
9
12
  ## 中文
10
13
 
11
- <div align="center">
12
- <a href="https://raw.githubusercontent.com/DDDMUC/dsh-delete-session/main/assets/menu.jpg">
13
- <img src="https://raw.githubusercontent.com/DDDMUC/dsh-delete-session/main/assets/menu.jpg" alt="侧边栏会话菜单里的删除会话" width="820" />
14
- </a>
15
- <br>
16
- <sub>▲ 侧边栏会话菜单里的「删除会话」(红色)</sub>
17
- </div>
18
-
19
14
  ### 为什么需要它
20
15
 
21
- DSH 侧边栏的会话菜单只有**重命名 / 分叉 / 归档**——归档只是把会话藏起来,**不删数据**;而插件dsh-web里的全家桶里的「会话归档管理」在设置深处,删一个会话要跳好几个界面。
16
+ DSH 侧边栏的会话菜单只有**重命名 / 分叉 / 归档**——归档只是把会话藏起来,**不删数据**;而插件全家桶里的「会话归档管理」在设置深处,删一个会话要跳好几个界面。
22
17
 
23
18
  这个插件把「删除」放回你最常点的位置:会话行的 `...` 菜单。
24
19
 
25
20
  ### 特性
26
21
 
27
- - **菜单内删除** —— 侧边栏会话 `...` 菜单直接多出「删除会话」;会话 ID 从菜单的 React fiber 读取,永远作用于你点的那一行,**不会切换当前会话**
28
- - **风险确认** —— 首次删除弹出确认对话框:显示会话标题与 ID,勾选「我已了解,永久删除」后确认按钮才可用
29
- - **确认一次后可跳过** —— 对话框里勾选「不再询问,以后直接删除」,之后点菜单项直接删除(左下角 toast 提示);**按住 Shift 点击**可临时恢复弹窗;清除 `localStorage['dsh-delete-session:skip-confirm']` 可永久恢复
30
- - **多选批量删除** —— 会话菜单里的「多选」进入选择模式:每行左侧出现勾选圈,点整行即勾选(不会打开会话),底部操作条显示「已选 N 个 / 取消 / 删除」;点删除**立即批量执行**(无确认弹窗、无全选、所有行都可选):被选中的行**乐观隐藏**、操作条实时显示「正在删除 x/N…」、客户端以 6 路并发请求宿主(不同会话并行、工作区记账串行),完成后 toast 汇总并刷新列表;失败的行立即恢复显示并保持选中,方便直接重试;Esc 或「取消」退出
31
- - **真正的删除** —— 停止运行中的 agent(cancel + 等待空闲)→ flush / detach 活跃会话 → 删除磁盘会话目录(原始 ID 与 `session-` 前缀两种写法)→ 持久化服务复核 → 清理工作区记账,侧栏不会残留「未分组」孤儿,并连带清理项目缓存行(`$DSH_HOME/storages/*/sessions/<id>.json`),不留无法再显示的幽灵记录
32
- - **删除后回到新会话** —— 若删除的正是当前打开的会话,自动切到一个全新会话(不再落到已不可用的邻居页——那种页面 composer 会被禁用、侧栏也列不出来,看起来像「没删掉」)
22
+ - **菜单内删除** —— 注册进宿主官方槽位 `sidebar.workspaces.session.menu.item`,紧挨「归档会话」下方多出「删除会话」;会话 ID 由宿主直接注入,永远作用于你点的那一行,**不会切换当前会话**
23
+ - **删除至系统回收站(默认开)** —— 会话日志、工作区记账与投影缓存一并进**系统回收站**:Windows 回收站、macOS 废纸篓、Linux 的 `gio trash`。删错了从回收站还原即可。设置页可以关掉它,关掉后就是**直接从硬盘移除,不可恢复**
24
+ - **对话框文案跟随开关** —— 勾选时写「并放进系统回收站」,取消勾选时写「永久删除…不可恢复」。你看到的永远是你实际会得到的
25
+ - **风险确认** —— 每次删除弹出确认对话框,显示会话标题与 ID。要不要弹这个框**只在设置页改**,弹窗里没有快捷勾选;**按住 Shift 点击**可以临时强制弹窗
26
+ - **多选批量删除** —— 会话菜单「多选」进入选择模式:勾选多行后底部操作条显示「已选 N 个 / 取消 / 删除」;点删除立即批量执行,被选中的行乐观隐藏、操作条实时显示进度,客户端 6 路并发、工作区记账串行;失败的行恢复显示并保持选中,方便重试;Esc 退出
27
+ - **产出文件隔离(可选,默认关闭)** —— 打开后,删除会话时还能把这个对话在工作目录里产出的文件**移进隔离夹**,而不是直接删掉;范围可选「仅新建」或「新建的和改过的」,模式可选「全部直接隔离」或「先列清单由你勾选」
28
+ - **跳过 git 仓库内的文件(默认开)** —— 在项目里干过活的对话,产出文件里往往就是项目源码。开启后仓库内的文件一律不碰,避免删一个对话把项目搬空
29
+ - **删除后回到新会话** —— 若删除的正是当前打开的会话,自动切到一个全新会话,而不是落到已不可用的邻居页
33
30
  - **默认安全** —— 宿主路由仅限本机回环(回环 socket + 回环 Host 头 + 同源校验);被 DSH 打开占用的会话会被明确拒绝(409),绝不与活跃写者竞争
34
- - **中英双语** —— 菜单项与对话框跟随 DSH 界面语言(zh / en)
31
+ - **中英双语** —— 菜单项、对话框与设置页跟随 DSH 界面语言(zh / en)
35
32
  - **零依赖** —— 不引入任何 DSH SDK,所有服务(`sessions` / `sessionPersistence` / `agents` / `workspaceRegistry` / `webServer`)都在调用时通过 cordis 上下文获取
36
33
 
37
34
  ### 安装
@@ -42,7 +39,7 @@ npm(推荐):
42
39
  dsh plugin --profile web add dsh-delete-session
43
40
  ```
44
41
 
45
- 或从 GitHub 仓库直接安装:
42
+ 或从 GitHub 直接安装:
46
43
 
47
44
  ```sh
48
45
  dsh plugin --profile web add github:DDDMUC/dsh-delete-session
@@ -52,47 +49,105 @@ dsh plugin --profile web add github:DDDMUC/dsh-delete-session
52
49
 
53
50
  ### 使用
54
51
 
55
- 1. 把鼠标移到侧边栏的会话行,点 `...`(会话操作)
56
- 2. 选择底部的「删除会话」
57
- 3. 首次:勾选「我已了解,永久删除」(想省事可同时勾选「不再询问,以后直接删除」)→ 点「删除」
58
- 4. 之后:直接删除,左下角出现 toast 提示
59
- 5. 批量删除:会话菜单 →「多选」→ 勾选多个会话 → 底部「删除」;Esc 或「取消」退出选择模式
52
+ 1. 鼠标移到侧边栏的会话行,点 `...`
53
+ 2. 选择「删除会话」
54
+ 3. 首次会弹确认框;不想再看到它,去插件管理页关掉「删除时询问」
55
+ 4. 删错了:打开系统回收站,找到 `session-<id>` 与 `session-<id>.json`,还原即可
56
+ 5. 批量:会话菜单 →「多选」→ 勾选多个 → 底部「删除」;Esc 或「取消」退出
57
+
58
+ ### 设置项一览
59
+
60
+ 插件管理页 → dsh-delete-session:
61
+
62
+ | 设置 | 默认 | 作用 |
63
+ | --- | --- | --- |
64
+ | 删除时询问 | 开 | 关掉后点菜单项直接删,不弹框(但产出文件选「先列清单」时仍会弹,清单只在对话框里) |
65
+ | **删除至回收站** | **开** | 开:进系统回收站,可还原。关:直接从硬盘移除,不可恢复 |
66
+ | 不处理产出文件 | 开 | 打开本项表示**不**处理产出文件,与旧版行为一致 |
67
+ | 产出文件默认全部进隔离夹 | 关 | 关:先列清单由你勾选;开:整批自动移走,不列清单也不扫描 |
68
+ | 范围:仅新建 / 新建的和改过的 | 仅新建 | 只动对话自己创建的文件,还是连它改写过的既有文件一起 |
69
+ | **跳过 git 仓库内的文件** | **开** | 仓库内的文件一律不碰 |
70
+ | 隔离夹保存位置 | 空(= `$DSH_HOME`) | 可手输、可点「选择文件夹…」调系统对话框、可点「打开」在资源管理器里定位 |
71
+
72
+ ### 删除之后文件在哪
73
+
74
+ | 平台 | 去处 |
75
+ | --- | --- |
76
+ | Windows | 系统回收站(`Microsoft.VisualBasic.FileIO.FileSystem` 的 SendToRecycleBin) |
77
+ | macOS | 废纸篓(Finder,经 `osascript`) |
78
+ | Linux | 桌面回收站(`gio trash`) |
79
+ | 以上不可用时 | `$DSH_HOME/session-trash-<时间戳>-<pid>-<序号>/`,手工搬回 |
80
+
81
+ 顺序是**先 rename 到暂存区,再在后台送进回收站**:改路径是微秒级,而系统回收站操作要 ~600ms,所以界面不会等你。后台失败时文件仍在暂存区,不会丢。
82
+
83
+ **取消勾选「删除至回收站」后**,目录用 `rm` 直接移除,暂存区不留副本,回收站里也没有。
84
+
85
+ ### 产出文件隔离
86
+
87
+ 这一档**默认关闭**。打开后(插件管理页 → dsh-delete-session),删除会话时除了对话记录,还会处理**这个对话在工作目录里碰过的文件**:
60
88
 
61
- <div align="center">
62
- <a href="https://raw.githubusercontent.com/DDDMUC/dsh-delete-session/main/assets/dialog.jpg">
63
- <img src="https://raw.githubusercontent.com/DDDMUC/dsh-delete-session/main/assets/dialog.jpg" alt="删除确认对话框" width="560" />
64
- </a>
65
- <br>
66
- <sub>▲ 删除确认对话框(首次删除)</sub>
67
- </div>
89
+ | 范围 | 处理什么 |
90
+ | --- | --- |
91
+ | 仅新建的文件(默认) | 本对话自己创建的文件 |
92
+ | 新建的和改过的文件 | 再加上被本对话改写的**既有**文件 —— 移走它们会改变工作目录里原本的内容 |
68
93
 
69
- ### 工作原理
94
+ - **移动,不删除**:文件进 `$DSH_HOME/purge-quarantine-<时间戳>/`,保留相对于工作目录的路径。每次删除**独占**一个新的隔离目录(同名已存在时自动让位),且**永不覆盖**已有文件
95
+ - **先算后动**:删除前由宿主端从会话日志重新推导清单;界面里勾选的结果只能缩小范围,不能扩大
96
+ - **「新建」怎么判定**:依据 `fs-observation-policy` 的不变量 —— `write` 覆盖既有文件前必须先成功 `read` 它,所以"首次写之前没有成功读过"就是新建。判定按**文件身份**而非路径拼写:软链接别名、Windows 上仅大小写不同的写法,都算同一个文件
97
+ - **四道安全过滤**:必须是普通文件、真实路径严格在工作目录内、不在 `$DSH_HOME` 内、必须仍然存在;被跳过的一律在对话框里列出原因
98
+ - **批量删除不走这一档**:只有单条删除会处理产出文件
70
99
 
71
- - `src/index.js`(宿主):注册一条仅限回环的路由 `POST /dsh-delete-session/delete` `{ sessionId }`,执行删除链路
72
- - `src/client.js`(浏览器):经典客户端 bundle——监听 portal 到 body 的会话菜单(`body > [role="menu"]`),沿 React fiber 链读取会话节点(`props.node.id`),克隆原生菜单行保持样式,弹出确认对话框
73
- - 删除链路:停 agent → flush / detach 活跃会话 → 写所有权探测(被占用 → 409)→ 删除目录(两种 ID 写法 + 复核)→ 清理工作区记账
100
+ ### 与 dsh-session-relocate 协作(可选)
101
+
102
+ 装了 [dsh-session-relocate](https://github.com/DDDMUC/dsh-session-relocate) 时,多选底栏会多出「移动」,可以把选中的会话整批搬到另一个工作区。
103
+
104
+ 两边靠一组公开能力位对接,缺任一侧都只是少一个按钮,不影响删除:
105
+
106
+ - 本插件在 `apply()` 时设置 `window.__DSH_DELETE_SESSION__ = { supportsBulkMove: true }`,并派发 `dsh-session-delete:ready`
107
+ - relocate 据此隐藏自己那套批量入口,并暴露 `window.__DSH_SESSION_RELOCATE__.moveSelection(ids, { onProgress })`
108
+ - 返回值固定为 `{ canceled?, moved, failed, skipped, total, results }`;缺字段会被当作错误报出来,不做兜底猜测
109
+
110
+ ### 速度
111
+
112
+ | 操作 | 耗时 |
113
+ | --- | --- |
114
+ | 删除(勾选回收站) | ~60 ms 返回,回收站在后台完成 |
115
+ | 删除(不勾选) | ~10 ms |
116
+ | 打开系统文件夹对话框 | ~120 ms(编译过的 helper),首次编译在插件加载时后台完成 |
117
+ | 产出文件扫描 | 1000 次工具调用的长会话约 20 ms |
118
+
119
+ 两个 helper(选目录、送回收站)是首次使用时用 `csc.exe` 编译成小 exe 缓存在 `$DSH_HOME/cache/dsh-delete-session/`;插件加载时就后台编译好,第一次点击不用等。没有 `csc` 的机器自动回退到 PowerShell 路径,只是慢一些。
74
120
 
75
121
  ### 测试
76
122
 
77
- - `test/checkbox.dom.test.js` —— 勾选框回归测试(`node --test`,零依赖):把真实的 `src/client.js` 跑在一个可读的 DOM 实现上,只经由用户真正会碰的入口(注入的会话菜单项、插件自己生成的 DOM)驱动,断言 **「存在 `.dsdel-check` ⟺ 多选模式开启」**
78
- - 覆盖的回归:单条删除(`deleteNow` / 对话框确认)会调用 `scheduleDecorate()`,而 v0.1.8 之前 `decorateRows()` **无条件**给每一行插入勾选框,于是删掉一个会话后所有行都长出了圆勾选框、底部操作条却仍然隐藏——一个「半开」的多选状态
79
- - 这个测试是有意义的,不是空跑:把 `decorateRows()` 改回旧写法时其中 5 项会失败;改成「永不插入勾选框」时另有 4 项会失败
80
- - 运行:`npm test`
81
- - **尚未验证的一环(诚实说明)**:与真实 DSH 界面的耦合——真实 DOM 结构、真实 CSS 布局、以及 React 调和器在重渲染会话行时是否会丢弃注入的勾选框——只能由真实浏览器验证。上述测试覆盖的是插件的点击与装饰状态机本身
123
+ ```
124
+ node test/artifacts.host.test.js # 59 项,宿主半
125
+ node test/checkbox.dom.test.js # 67 项,客户端半
126
+ ```
127
+
128
+ 客户端测试把真实的 `src/client.js` 跑在一个可读的 DOM 实现上,经由**注册进槽位的组件**、它们渲染出的元素树、以及插件自己生成的 DOM 来驱动。宿主测试驱动真实的 `src/index.js`,覆盖"新建 vs 改过"的判定、每一道安全过滤、隔离移动保留目录结构、git 跳过、回收站与直接删除的分支。
129
+
130
+ 两条测试都做过**变异验证**:把实现改回旧写法或改坏,对应断言必须变红。这一步抓出过几条"看起来测了、其实没测到"的地方(例如扫描请求里的字段、宿主侧的开关分支)。
82
131
 
83
132
  ### 已知限制
84
133
 
85
- - 被 DSH 当前打开占用的会话:删除会自动等待写所有权释放(通常几秒)后完成;极少数仍被占用的情况会提示稍后重试或重启 DSH
86
- - 菜单注入是 DOM shim:核心 UI 若调整菜单结构或行 fiber 形状,可能不再注入菜单项(不影响其他功能)
87
- - 删除不可恢复:没有回收站,也没有撤销
88
- - 批量删除为客户端 6 路并发 + 乐观隐藏:不同会话并行删除,宿主端写所有权等待缩短为 200ms × 12(单条最长约 2.2s),多个被占用的会话也并行等待;同一工作区记账变更仍串行,避免并发写交错
134
+ - **取消勾选「删除至回收站」后不可恢复**,没有撤销
135
+ - **系统回收站有每盘容量上限**:超出时 Windows 会永久删除而不是报错。会话只有 KB~MB,实际碰不到
136
+ - **macOS 首次使用会请求 Finder 自动化权限**,拒绝后文件留在暂存目录,不会假装删掉
137
+ - 被 DSH 打开占用的会话:删除会自动等待写所有权释放(通常几秒)后完成;极少数仍被占用时提示稍后重试或重启 DSH
138
+ - 多选勾选框仍是对会话行的 DOM 装饰,依赖宿主 `data-row-key="session:<id>"`;核心 UI 若改名,多选会静默失效(**菜单与对话框不受影响**,它们走官方槽位)
139
+ - 产出文件清理默认关闭,打开后仍有几条边界:`bash` / `pwsh` 里创建的文件(`npm run build`、`git clone`、`> file`)**永远检测不到**;**目录永不移走**;清单上限 500 条;**没有还原界面**,隔离夹靠手工搬回
140
+ - 「新建 vs 改过」的判定依赖 `fs-observation-policy` 保持启用
141
+ - 批量删除(多选)**不**处理产出文件
142
+ - 暂存目录与隔离夹**没有自动清理**,会一直堆在 `$DSH_HOME` 下
89
143
 
90
144
  ### 兼容性
91
145
 
92
- - DSH `0.1.5-rc.1` / `0.1.7-alpha.2` 实测通过(0.1.7 起不再导出 `SettingsProvider`;本插件不依赖 DSH SDK,无需改动)
146
+ - **宿主半**在 DSH `0.2.1-rc.2` 上实测通过:回环 socket、Host 头、跨站 Origin 守卫、会话 ID 校验、405/404 路径
147
+ - **客户端半**在 DSH `0.2.1-rc.2` 上实测通过;槽位迁移后的版本已在浏览器里实跑
93
148
  - 宿主链路对缺少服务的旧版本会退化为纯文件系统清理
94
149
 
95
- 如果这个插件帮到了你,欢迎给仓库点个 ⭐([GitHub](https://github.com/DDDMUC/dsh-delete-session))——星标是开发者继续维护的最大动力,感谢支持!
150
+ 如果这个插件帮到了你,欢迎给仓库点个 ⭐([GitHub](https://github.com/DDDMUC/dsh-delete-session))。
96
151
 
97
152
  ### License
98
153
 
@@ -102,91 +157,141 @@ MIT
102
157
 
103
158
  ## English
104
159
 
105
- <div align="center">
106
- <a href="https://raw.githubusercontent.com/DDDMUC/dsh-delete-session/main/assets/menu.jpg">
107
- <img src="https://raw.githubusercontent.com/DDDMUC/dsh-delete-session/main/assets/menu.jpg" alt="Delete session in the sidebar menu" width="820" />
108
- </a>
109
- <br>
110
- <sub>▲ "Delete session" (red) in the sidebar session menu</sub>
111
- </div>
112
-
113
160
  ### Why you need it
114
161
 
115
- The DSH sidebar session menu only offers **Rename / Fork / Archive** — archive merely hides a session and keeps every byte on disk, while the session archive manager inside the dsh-web plugin suite lives deep in Settings and takes several screens to reach.
162
+ The DSH sidebar session menu offers **Rename / Fork / Archive** — and archiving only hides a session, it does not delete data. The "session archive manager" in the settings deep-dive takes several screens per session.
116
163
 
117
- This plugin puts Delete back where you already click: the session row's `...` menu.
164
+ This plugin puts **Delete** back where you already click: the session row's `...` menu.
118
165
 
119
166
  ### Features
120
167
 
121
- - **Delete from the menu** — a "Delete session" row appears in the sidebar session `...` menu; the session id is read from the menu's React fiber, so the action always targets the row you clicked and **never switches the active conversation**
122
- - **Risk consent** — the first delete opens a confirmation dialog with the session title and id; the confirm button unlocks only after ticking "I understand"
123
- - **Confirm once, then skip** — tick "Don't ask again" and later deletes run directly from the menu (a toast reports the result); **hold Shift while clicking** to force the dialog back, or clear `localStorage['dsh-delete-session:skip-confirm']` to restore it permanently
124
- - **Multi-select batch delete** — "Select multiple" in the session menu enters selection mode: every row gets a round checkbox, clicking a row toggles it (it never opens the conversation), and a bottom bar shows "N selected / Cancel / Delete". Delete runs **immediately** (no confirm dialog, no select-all, every row selectable): selected rows are hidden optimistically, the bar shows live progress ("Deleting x/N..."), and the client runs 6 concurrent requests (independent sessions in parallel, workspace accounting serialized), then reports a toast summary; failed rows reappear immediately and stay selected for a quick retry; Esc or Cancel exits
125
- - **Real deletion** — stops a running agent (cancel + quiescence), flushes and detaches the live session, removes the on-disk session directories (both the raw and `session-` prefixed id), verifies through the persistence service, then cleans workspace accounting so no orphan shows up under "Ungrouped", and removes the session's projection-cache rows (`$DSH_HOME/storages/*/sessions/<id>.json`) so no ghost the sidebar can no longer show is left behind
126
- - **Leaves a fresh session behind** — when the deleted session is the one the main view is showing, the app switches to a brand-new session instead of a stale neighbour (an unavailable row with a disabled composer that the sidebar cannot even list, which reads as "the delete did not work")
127
- - **Safe by default** — the host route is loopback-only (loopback socket, loopback Host header, same-origin check); a session still held open by DSH is refused with 409 instead of racing the active writer
128
- - **Bilingual** — the menu item and dialog follow the DSH interface language (zh / en)
129
- - **Zero dependencies** — no DSH SDK imports; every service (`sessions`, `sessionPersistence`, `agents`, `workspaceRegistry`, `webServer`) is resolved through the cordis context at call time
168
+ - **Delete from the menu** — registered through the official `sidebar.workspaces.session.menu.item` slot, right under "Archive session". The session id is injected by the host, so it always targets the row you clicked and **never switches the open session**
169
+ - **Delete to the system trash (on by default)** — the session log, workspace accounting and projection cache go to the **system trash**: the Windows Recycle Bin, the macOS Trash, or `gio trash` on Linux. A wrong click is restored from the trash. The settings page can turn it off, and then the delete removes the files from the disk for good
170
+ - **The sentence matches the switch** — checked says "…and puts them in the system trash"; unchecked says "permanently deletes… cannot be undone". What you read is what you get
171
+ - **Confirmation dialog** — shows the session title and id. Whether a delete asks is changed on the plugin page only, never from the dialog; Shift-click forces the dialog back
172
+ - **Multi-select batch delete** — enter selection mode from the session menu, tick rows, then Delete: rows hide optimistically, the bar reports progress, six requests run in parallel while workspace accounting stays serial, failures come back visible and still selected, Esc leaves
173
+ - **Produced-file quarantine (optional, off by default)** — when enabled, a delete also moves the files this conversation produced in its working directory into a quarantine folder instead of destroying them. Scope: files it created, or those plus files it rewrote. Mode: list them for you to tick, or move the whole set
174
+ - **Skip files inside git repositories (on by default)** — a conversation that worked in a project produces that project's source. With this on, nothing inside a repository is touched
175
+ - **Lands on a fresh session** — deleting the session you are looking at switches to a new one instead of an unusable neighbour page
176
+ - **Safe by default** — host routes are loopback-only (loopback socket, loopback Host header, same-origin check); a session the host still holds open is refused with 409 rather than raced
177
+ - **Bilingual** — menu rows, dialog and settings follow the DSH UI language (zh / en)
178
+ - **Zero dependencies** — no DSH SDK import; `sessions`, `sessionPersistence`, `agents`, `workspaceRegistry` and `webServer` are all resolved from the cordis context at call time
130
179
 
131
180
  ### Install
132
181
 
133
- From npm (recommended):
134
-
135
182
  ```sh
136
183
  dsh plugin --profile web add dsh-delete-session
137
184
  ```
138
185
 
139
- Or straight from the GitHub repository:
186
+ or straight from GitHub:
140
187
 
141
188
  ```sh
142
189
  dsh plugin --profile web add github:DDDMUC/dsh-delete-session
143
190
  ```
144
191
 
145
- Restart `dsh web` to apply.
192
+ Restart `dsh web` afterwards.
146
193
 
147
194
  ### Usage
148
195
 
149
- 1. Hover a session row in the sidebar and click `...` (session actions)
150
- 2. Pick "Delete session" at the bottom
151
- 3. First time: tick "I understand - delete permanently" (tick "Don't ask again" too if you like) and confirm
152
- 4. Afterwards: the session is deleted directly, with a toast
153
- 5. Batch delete: session menu -> "Select multiple" -> tick rows -> "Delete" in the bottom bar; Esc or Cancel exits
196
+ 1. Hover a session row in the sidebar and click `...`
197
+ 2. Pick "Delete session"
198
+ 3. Confirm; to stop seeing the dialog, turn off "Ask before deleting" on the plugin page
199
+ 4. Deleted by mistake? Open the system trash, restore `session-<id>` and `session-<id>.json`
200
+ 5. Batch: session menu → "Select multiple" → tick rows → Delete; Esc leaves
201
+
202
+ ### Settings
203
+
204
+ Plugin manager → dsh-delete-session:
205
+
206
+ | Setting | Default | Effect |
207
+ | --- | --- | --- |
208
+ | Ask before deleting | On | Off deletes straight from the menu. Ask mode for produced files still opens the dialog, because the list lives there |
209
+ | **Delete to the system trash** | **On** | On: the trash, restorable. Off: removed from the disk for good |
210
+ | Do not handle produced files | On | Ticked means the produced-file feature is off and a delete behaves like the old version |
211
+ | Move every produced file automatically | Off | Off lists them for you to tick; on moves the whole set with no listing and no scan |
212
+ | Scope: created only / created and changed | created only | Whether files this conversation rewrote are included |
213
+ | **Skip files inside git repositories** | **On** | Nothing inside a repository is touched |
214
+ | Quarantine folder | empty (= `$DSH_HOME`) | Typed, chosen with the system folder dialog, or opened in the file manager |
215
+
216
+ ### Where the files go
217
+
218
+ | Platform | Destination |
219
+ | --- | --- |
220
+ | Windows | The Recycle Bin (`Microsoft.VisualBasic.FileIO.FileSystem`, SendToRecycleBin) |
221
+ | macOS | The Trash, through Finder via `osascript` |
222
+ | Linux | The desktop trash, through `gio trash` |
223
+ | None available | `$DSH_HOME/session-trash-<stamp>-<pid>-<n>/`, moved back by hand |
224
+
225
+ The order is **rename into a staging folder, then hand it to the trash in the background**: a rename costs microseconds while the shell's trash operation costs ~600 ms, so the UI never waits for it. If the background call fails the files simply stay staged.
226
+
227
+ **With "Delete to the system trash" unchecked**, the directory is removed outright: nothing staged, nothing in the trash.
228
+
229
+ ### Produced-file quarantine
230
+
231
+ Off by default. When enabled, a delete also handles the files this conversation touched inside its working directory:
154
232
 
155
- <div align="center">
156
- <a href="https://raw.githubusercontent.com/DDDMUC/dsh-delete-session/main/assets/dialog.jpg">
157
- <img src="https://raw.githubusercontent.com/DDDMUC/dsh-delete-session/main/assets/dialog.jpg" alt="Delete confirmation dialog" width="560" />
158
- </a>
159
- <br>
160
- <sub>▲ The confirmation dialog (first delete)</sub>
161
- </div>
233
+ | Scope | What moves |
234
+ | --- | --- |
235
+ | Only files it created (default) | Files this conversation created |
236
+ | Created and changed | Plus pre-existing files it rewrote — moving them changes what was already there |
162
237
 
163
- ### How it works
238
+ - **Moved, never deleted**: into `$DSH_HOME/purge-quarantine-<stamp>/`, keeping the path relative to the working directory. Every delete claims its own folder and never overwrites an existing file
239
+ - **Derived first, then moved**: the host re-derives the list from the session log; ticking in the dialog can only narrow it
240
+ - **How "created" is decided**: from the `fs-observation-policy` invariant — a `write` that overwrites must be preceded by a successful `read`, so "first write with no successful read before it" means created. Identity, not spelling: symlink aliases and case-only differences on Windows are the same file
241
+ - **Four safety filters**: a regular file, its real path strictly inside the working directory, outside `$DSH_HOME`, and still present. Anything skipped is listed with its reason
242
+ - **Batch delete never touches produced files**
164
243
 
165
- - `src/index.js` (host): one loopback-only route `POST /dsh-delete-session/delete` `{ sessionId }` runs the deletion pipeline
166
- - `src/client.js` (browser): a classic client bundle — watches for the portalled session menu (`body > [role="menu"]`), walks the React fiber chain to the session node (`props.node.id`), clones a native menu row for styling, and opens the confirmation dialog
167
- - Pipeline: stop agent → flush / detach live session → write-lease probe (busy → 409) → remove directories (both id spellings + verification) → clean workspace accounting
244
+ ### Interop with dsh-session-relocate (optional)
245
+
246
+ With [dsh-session-relocate](https://github.com/DDDMUC/dsh-session-relocate) installed, the multi-select bar gains a "Move" button that relocates the selected sessions to another workspace.
247
+
248
+ The two sides meet over public capability bits; either side missing just means one button fewer:
249
+
250
+ - This plugin sets `window.__DSH_DELETE_SESSION__ = { supportsBulkMove: true }` in `apply()` and dispatches `dsh-session-delete:ready`
251
+ - Relocate uses that to hide its own bulk entry and exposes `window.__DSH_SESSION_RELOCATE__.moveSelection(ids, { onProgress })`
252
+ - The result is fixed at `{ canceled?, moved, failed, skipped, total, results }`; missing fields are reported as an error rather than guessed
253
+
254
+ ### Speed
255
+
256
+ | Operation | Cost |
257
+ | --- | --- |
258
+ | Delete (trash on) | returns in ~60 ms, the trash finishes in the background |
259
+ | Delete (trash off) | ~10 ms |
260
+ | System folder dialog | ~120 ms with the compiled helper; the first compile runs at plugin load |
261
+ | Produced-file scan | ~20 ms for a 1000-tool-call session |
262
+
263
+ Both helpers (folder picker, trash) are compiled once with `csc.exe` into `$DSH_HOME/cache/dsh-delete-session/` and reused. The compilation starts in the background when the plugin loads, so the first click does not pay for it. A machine without `csc` falls back to the PowerShell path — slower, same behaviour.
168
264
 
169
265
  ### Tests
170
266
 
171
- - `test/checkbox.dom.test.js` — the multi-select checkbox regression test (`node --test`, zero dependencies): it runs the real `src/client.js` against a readable DOM implementation and drives it only through surfaces a user touches (the injected session-menu items and the DOM the plugin builds), asserting that **a `.dsdel-check` exists if and only if multi-select is on**
172
- - The regression it covers: a plain single delete (`deleteNow` / the dialog's confirm handler) calls `scheduleDecorate()`, and before v0.1.8 `decorateRows()` planted a checkbox on **every** row unconditionally — so after deleting one session every row grew a round checkbox while the batch bar stayed hidden, a "half-on" multi-select state
173
- - The test earns its keep: reverting `decorateRows()` to the old shape fails 5 of its cases, and a fix that never plants checkboxes fails 4
174
- - Run it with `npm test`
175
- - **Not verified (honest note):** the coupling to the real DSH UI — real DOM structure, real CSS layout, and whether React's reconciler drops injected checkboxes when it re-renders a session row — can only be checked in a real browser. What these tests cover is the plugin's own click and decoration state machine
267
+ ```
268
+ node test/artifacts.host.test.js # 59, host half
269
+ node test/checkbox.dom.test.js # 67, client half
270
+ ```
271
+
272
+ The client tests run the real `src/client.js` against a readable DOM, driven through the components it registers into slots, the element tree they render and the DOM the plugin produces. The host tests drive the real `src/index.js`: created-vs-changed classification, every safety filter, quarantine moving with directory structure, git skipping, and the trash/permanent branches.
273
+
274
+ Both suites are **mutation-checked**: reverting an implementation or breaking it must turn the matching assertions red. That step has caught several "looks tested, isn't" spots — the scan request field, and the host-side switch branch among them.
176
275
 
177
276
  ### Known limitations
178
277
 
179
- - A session DSH currently holds open: the delete waits for the write lease to be released (usually a few seconds) and then completes; in the rare case it stays busy, the dialog asks you to retry later or restart DSH
180
- - The menu injection is a DOM shim: core UI changes to the menu markup or row fiber shape may stop the item from being injected (nothing else breaks)
181
- - Deletion is permanent: there is no trash bin and no undo
182
- - Batch delete uses 6-way client concurrency with optimistic hiding: independent sessions delete in parallel and the host write-lease wait is shortened to 200ms x 12 (max ~2.2s per item), so busy sessions wait in parallel too; workspace accounting stays serialized so concurrent deletions never interleave on the same store
278
+ - **Unchecking "Delete to the system trash" is not recoverable** — there is no undo
279
+ - **The system trash has a per-volume size cap**: over it Windows deletes permanently instead of complaining. Sessions are KB–MB, so this does not come up in practice
280
+ - **macOS asks for Finder automation permission the first time**; denied, the files stay in the staging folder rather than pretending the delete happened
281
+ - A session the host holds open is waited for (usually seconds); in rare cases it asks you to retry or restart DSH
282
+ - The multi-select tick is still a DOM decoration on session rows and depends on `data-row-key="session:<id>"`. If core UI renames it, multi-select goes quiet (the menu and dialog are unaffected — they use official slots)
283
+ - Produced-file handling is off by default and still has edges: files created inside `bash` / `pwsh` (`npm run build`, `git clone`, `> file`) are **never detected**; **directories are never moved**; the list caps at 500; there is **no restore UI** — move files back from the quarantine by hand
284
+ - Created-vs-changed depends on `fs-observation-policy` staying enabled
285
+ - Batch delete does **not** handle produced files
286
+ - Staging and quarantine folders **are never cleaned up automatically**
183
287
 
184
288
  ### Compatibility
185
289
 
186
- - Tested on DSH `0.1.5-rc.1` and `0.1.7-alpha.2`
187
- - On older versions without the services, the host pipeline degrades to filesystem cleanup
290
+ - **Host half** verified on DSH `0.2.1-rc.2`: loopback socket, Host header, cross-origin guard, session id validation, 405/404 paths
291
+ - **Client half** verified on DSH `0.2.1-rc.2`; the post-migration version has been exercised in a real browser
292
+ - Against older hosts that lack a service, the delete path degrades to plain filesystem cleanup
188
293
 
189
- If this plugin has been helpful, a ⭐ on [GitHub](https://github.com/DDDMUC/dsh-delete-session) would mean a lot — it is the biggest motivation for the developer to keep maintaining it. Thank you!
294
+ If this plugin helped, a ⭐ on [GitHub](https://github.com/DDDMUC/dsh-delete-session) is appreciated.
190
295
 
191
296
  ### License
192
297
 
package/cordis.patch.yml CHANGED
@@ -1,7 +1,7 @@
1
- # dsh-delete-session bundle patch: inserts the plugin row into the profile
2
- # roster. The node half serves the /dsh-delete-session/* JSON routes; the
3
- # browser half (exports "./client") injects the sidebar session-row menu item
4
- # and the confirmation dialog.
5
- - insert:
6
- - id: dsh-delete-session
7
- name: 'dsh-delete-session'
1
+ # dsh-delete-session bundle patch: inserts the plugin row into the profile
2
+ # roster. The node half serves the /dsh-delete-session/* JSON routes; the
3
+ # browser half (exports "./client") injects the sidebar session-row menu item
4
+ # and the confirmation dialog.
5
+ - insert:
6
+ - id: dsh-delete-session
7
+ name: 'dsh-delete-session'
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-delete-session",
3
- "version": "0.1.9",
4
- "description": "Delete sessions from the DSH Web sidebar: adds a Delete action to the session row menu with a risk-consent dialog, then removes the session log, workspace accounting and live session state.",
3
+ "version": "0.2.0",
4
+ "description": "Delete sessions from the DSH Web sidebar: adds a Delete action to the session row menu with a risk-consent dialog, sends the session log, workspace accounting and live session state to the system trash (or removes them outright), and, when opted in, quarantines the files the conversation produced.",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
7
7
  "exports": {
@@ -11,6 +11,7 @@
11
11
  },
12
12
  "files": [
13
13
  "src",
14
+ "test",
14
15
  "cordis.patch.yml",
15
16
  "README.md",
16
17
  "LICENSE"