@wangxiaocuo/dsh-session-trash 0.0.0-stage → 0.6.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 +21 -0
- package/README.en.md +204 -0
- package/README.md +202 -2
- package/client.js +2231 -0
- package/cordis.patch.yml +3 -0
- package/icon.svg +10 -0
- package/index.js +129 -0
- package/lib/cleanup.js +342 -0
- package/lib/eligibility.js +310 -0
- package/lib/errors.js +91 -0
- package/lib/fsops.js +433 -0
- package/lib/http.js +351 -0
- package/lib/inventory.js +426 -0
- package/lib/members.js +95 -0
- package/lib/paths.js +39 -0
- package/lib/state.js +155 -0
- package/lib/store.js +606 -0
- package/lib/trash-service.js +1591 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +68 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 wangxiaocuo
|
|
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.en.md
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
# dsh-session-trash
|
|
2
|
+
|
|
3
|
+
[简体中文](./README.md) | English
|
|
4
|
+
|
|
5
|
+
[GitHub](https://github.com/wangxiaocuo/dsh-session-trash) · [npm](https://www.npmjs.com/package/@wangxiaocuo/dsh-session-trash)
|
|
6
|
+
|
|
7
|
+
A session trash for DeepSeek Harness. Move cold sessions out of the sidebar without deleting their logs, restore them within the retention window, or delete the logs permanently. Built for DSH 0.2.2-rc.1 on Desktop and Web. Host and Client ship in one package, with no build step.
|
|
8
|
+
|
|
9
|
+
Status: feature complete and covered by the regression suite. macOS has been verified on a real DSH Host. Windows and Linux path, lock, and retry rules have unit coverage but have not been run on those systems.
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- Right-click a session in the sidebar and choose "Move to trash". It is available for cold sessions with no running turn, job, subagent, or scheduled task; hidden subagent children move with their parent and are restored together.
|
|
14
|
+
- For sessions that are still open, still running work, or own visible child sessions, the menu keeps a labelled explanatory "Move to trash" item that states why the move is unavailable instead of hiding the entry entirely.
|
|
15
|
+
- After the move, the shared DSH Toast offers one action: Undo. Undo tries a normal restore first and falls back to an orphan restore when the original workspace is gone.
|
|
16
|
+
- The Trash page in Settings shows title, Session ID, original workspace, trashed date, remaining days, size, and status. The list supports search, filters, and paging at 50 rows per page.
|
|
17
|
+
- Row actions include restore, restore as orphan, permanent deletion, removal from the index, copying the Session ID, and revealing the original workspace in Finder or Explorer.
|
|
18
|
+
- The toolbar's "Delete all permanently" empties the whole trash after one confirmation: it deletes every trashed session log and drops file-missing records from the index. Attachments, images, and search-index rows are not deleted.
|
|
19
|
+
- Cleanup keeps entries for 30 days by default and runs every 6 hours. It also runs shortly after activation and when the Trash page opens. Failed cleanup runs are retried with exponential backoff, and the page shows the failure.
|
|
20
|
+
- The index is committed atomically and the previous index is kept as `.bak`. If both the index and the backup are unreadable, the plugin enters protection mode and can rebuild the index from each object's `meta.json`.
|
|
21
|
+
- Rename, fork, archive, and copy Session ID stay in the shipped DSH sidebar menu. The plugin does not reimplement them.
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
The plugin is published on npm ([`@wangxiaocuo/dsh-session-trash`](https://www.npmjs.com/package/@wangxiaocuo/dsh-session-trash)); the plugin dialog also accepts a GitHub address or a local directory.
|
|
26
|
+
|
|
27
|
+
Installation and removal use the current profile's pnpm. Desktop bundles pnpm; a `dsh web` started from source needs pnpm on `PATH`, and installing from GitHub also needs git and working access to GitHub.
|
|
28
|
+
|
|
29
|
+
### Install from DSH (recommended)
|
|
30
|
+
|
|
31
|
+
1. Open the **Plugins** panel in the sidebar and click **Add plugin**.
|
|
32
|
+
2. In **Package name or address**, enter `@wangxiaocuo/dsh-session-trash`. To install the latest code on the current branch, enter `git+https://github.com/wangxiaocuo/dsh-session-trash.git` instead.
|
|
33
|
+
3. Leave the **Registry** at its default. The plugin and its dependencies come from npm; on a slow network, switch to **Mainland China mirror**. The mirror can lag the official registry by a few minutes.
|
|
34
|
+
4. Click **Install**. DSH resolves the package name, description, and version, then runs pnpm. The command and output are under **Show install details**.
|
|
35
|
+
5. Click **Enable now** when it finishes. Restart DSH if prompted and refresh the page in the Web app. The context menu and the Trash settings page appear afterwards.
|
|
36
|
+
|
|
37
|
+
Local directory: clone the repository, run `pnpm install` in the directory (a directory-link bundle uses its own `node_modules`), then enter the absolute path of that directory.
|
|
38
|
+
|
|
39
|
+
If an install fails, start with the pnpm output and the Host log path under **Show install details**:
|
|
40
|
+
|
|
41
|
+
- No such plugin was found: check the spelling, wait for the mirror, or install from a Git address or local directory instead.
|
|
42
|
+
- No registry could be reached: check the network or proxy, or install from a local directory.
|
|
43
|
+
- The package declares no bundle, or the path is not a valid plugin package: the Git address must point at the repository root, and a local path must be the package directory.
|
|
44
|
+
- The install finished but nothing shows up: make sure the bundle is enabled on the Plugins page; a profile without HMR needs a DSH restart, and the Web client needs a page refresh.
|
|
45
|
+
|
|
46
|
+
### Let the agent install it
|
|
47
|
+
|
|
48
|
+
Ask the agent to run one of these. The operation needs `danger-full-access` or a separate approval:
|
|
49
|
+
|
|
50
|
+
```text
|
|
51
|
+
plugin_manager install_bundle target=@wangxiaocuo/dsh-session-trash
|
|
52
|
+
plugin_manager install_bundle target=git+https://github.com/wangxiaocuo/dsh-session-trash.git
|
|
53
|
+
plugin_manager install_bundle target=/absolute/path/to/dsh-session-trash
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Installing the directory saves a download when a clone already exists. With the DSH CLI installed, `dsh plugin add <the same spec>` is equivalent.
|
|
57
|
+
|
|
58
|
+
### Link into a profile manually (development)
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
git clone https://github.com/wangxiaocuo/dsh-session-trash.git
|
|
62
|
+
cd dsh-session-trash
|
|
63
|
+
pnpm install
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
cd "${DSH_HOME:-$HOME/.dsh}/profiles/desktop"
|
|
68
|
+
pnpm add link:/absolute/path/to/dsh-session-trash
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Then add `@wangxiaocuo/dsh-session-trash` to `dsh.profile.bundles` in that profile's `package.json`, restart DSH to load the Host, and refresh the page to load the Client. Runtime dependencies are `@deepseek-ai/dsh-home-paths@0.2.2-rc.1` and `@deepseek-ai/schemastery@3.19.0`.
|
|
72
|
+
|
|
73
|
+
### Upgrade and uninstall
|
|
74
|
+
|
|
75
|
+
For an npm install, open Session trash on the Plugins panel and click **Update** in the **Versions** section; when no newer version is offered, uninstall and install again. A Git install only picks up new code after reinstallation. For a manual link, run `git pull` and `pnpm install` in the directory, restart DSH, then refresh the page.
|
|
76
|
+
|
|
77
|
+
To uninstall, open the same detail page, click **Uninstall**, and confirm; the profile dependency is removed. You can also remove it from the profile bundles and dependencies by hand and restart. Uninstalling does not delete `<DSH_HOME>/.session-trash`; delete that directory manually when its logs are no longer needed.
|
|
78
|
+
|
|
79
|
+
## Configuration
|
|
80
|
+
|
|
81
|
+
Configure the plugin row in the profile patch:
|
|
82
|
+
|
|
83
|
+
```yaml
|
|
84
|
+
- id: session-trash
|
|
85
|
+
name: '@wangxiaocuo/dsh-session-trash'
|
|
86
|
+
config:
|
|
87
|
+
retentionDays: 30 # 1-3650
|
|
88
|
+
sweepIntervalHours: 6 # 1-168
|
|
89
|
+
enableAutoCleanup: true
|
|
90
|
+
debug: false
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
| Field | Default | Range | Description |
|
|
94
|
+
|---|---|---|---|
|
|
95
|
+
| `retentionDays` | `30` | 1-3650 | Days a trashed session is kept before automatic deletion |
|
|
96
|
+
| `sweepIntervalHours` | `6` | 1-168 | Hours between automatic cleanup sweeps |
|
|
97
|
+
| `enableAutoCleanup` | `true` | boolean | When false, cleanup no longer deletes expired entries automatically |
|
|
98
|
+
| `debug` | `false` | boolean | Emit detailed diagnostic logs |
|
|
99
|
+
|
|
100
|
+
Defaults apply when `config` is omitted. Invalid values reject activation instead of falling back to defaults. Changing an ordinary field restarts the plugin.
|
|
101
|
+
|
|
102
|
+
## Usage
|
|
103
|
+
|
|
104
|
+
Right-click a cold session and choose "Move to trash". After the Toast disappears, open Settings, then Trash, and restore the session. If the original workspace directory is gone, the page asks for confirmation and restores the session as an orphan under "Ungrouped". To empty the trash at once, use "Delete all permanently" in the toolbar and confirm; entries that have not expired are deleted too.
|
|
105
|
+
|
|
106
|
+
Trash rows that own hidden subagent children show a badge with the child count; restore and permanent deletion always cover the whole tree.
|
|
107
|
+
|
|
108
|
+
The current DSH version keeps a live agent for a session after it is opened, until the Host process exits. An idle session is still live, so the menu item stays hidden. Restart DSH before trashing it, or archive it. A second DSH instance does not detect a session that is live in another process, so do not use one to bypass this check.
|
|
109
|
+
|
|
110
|
+
## HTTP API
|
|
111
|
+
|
|
112
|
+
All routes live under `/plugins/dsh-session-trash/api/` and are registered as exact routes on `ctx.webServer`.
|
|
113
|
+
|
|
114
|
+
| Method | Path | Description |
|
|
115
|
+
|---|---|---|
|
|
116
|
+
| `GET` | `/state` | Config, index state, entries, stats, and cleanup diagnostics; may schedule a throttled page-open cleanup |
|
|
117
|
+
| `POST` | `/preflight` | `{ sessionId }` returns `{ eligible: true, descendants }` or `{ eligible: false, code, message, details? }`; `descendants` is the number of hidden children that move along |
|
|
118
|
+
| `POST` | `/trash` | `{ sessionId }`; moves the whole session tree (parent plus hidden subagent children) and returns `{ ok, entry }` |
|
|
119
|
+
| `POST` | `/restore` | `{ sessionId, mode: 'normal' \| 'orphan' }` |
|
|
120
|
+
| `POST` | `/purge` | `{ sessionId }`; permanently deletes the session log |
|
|
121
|
+
| `POST` | `/purge-all` | No body; permanently deletes every entry in the trash, dropping `missing` index records only; returns `{ ok, purged, forgotten, failed }` |
|
|
122
|
+
| `POST` | `/forget` | `{ sessionId }`; removes only a `missing` index entry |
|
|
123
|
+
| `POST` | `/reveal` | `{ sessionId }`; opens the original workspace |
|
|
124
|
+
| `POST` | `/cleanup` | No body; runs one cleanup sweep |
|
|
125
|
+
| `POST` | `/rebuild` | No body; rebuilds the index only in protection mode |
|
|
126
|
+
|
|
127
|
+
Requests must pass `ctx.connection.requestRejection()` and `ctx.connection.admit()`. `POST` requests also carry `X-DSH-Session-Trash: 1`. JSON bodies use `Content-Type: application/json` and are limited to 64 KiB. Responses use `Cache-Control: no-store` and never expose the absolute trash root.
|
|
128
|
+
|
|
129
|
+
Failures use `{ ok: false, error: { code, message, details? } }`. Preflight refusals include `SESSION_NOT_FOUND`, `ALREADY_TRASHED`, `SESSION_NOT_MATERIALIZED`, `STORAGE_UNSUPPORTED`, `SESSION_SUBAGENT`, `SESSION_HAS_DESCENDANTS`, `SESSION_LINEAGE_UNRESOLVED`, `SESSION_LIVE`, `SESSION_ACTIVE`, `UNSAFE_SESSION_DIR`, `INDEX_CORRUPT`, and `IO_ERROR`.
|
|
130
|
+
|
|
131
|
+
Restore and maintenance refusals include `TRASH_ENTRY_NOT_FOUND`, `TRASH_STATE_INVALID`, `TRASH_DIR_MISSING`, `RESTORE_TARGET_EXISTS`, `WORKSPACE_MISSING`, `RESTORE_VERIFY_FAILED`, `UNSAFE_RESTORE_PATH`, `FORGET_NOT_ALLOWED`, `REVEAL_UNAVAILABLE`, `REVEAL_FAILED`, `INDEX_CORRUPT`, `INDEX_NOT_CORRUPT`, `LOCKED`, and `IO_ERROR`.
|
|
132
|
+
|
|
133
|
+
`/state.stats.cleanup` reports the last cleanup attempt, the next sweep, a pending retry, consecutive failures, and the last run summary.
|
|
134
|
+
|
|
135
|
+
`/state` entries gain `subagentCount` (hidden children moved along) and `members` (parent first, each with `sessionId`, `origin`, and `sizeBytes`); `sizeBytes`/`fileCount` cover the whole tree. The `entry` returned by `/trash` uses the same shape.
|
|
136
|
+
|
|
137
|
+
## Storage and recovery
|
|
138
|
+
|
|
139
|
+
```text
|
|
140
|
+
<DSH_HOME>/.session-trash/
|
|
141
|
+
trash-index.json # authoritative index
|
|
142
|
+
trash-index.json.bak # previous committed index
|
|
143
|
+
.lock # cross-process lock
|
|
144
|
+
objects/
|
|
145
|
+
<trashId>/
|
|
146
|
+
meta.json # object metadata
|
|
147
|
+
session/ # the moved parent session directory
|
|
148
|
+
children/
|
|
149
|
+
<sessionId>/ # hidden subagent child directory moved along
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
The index is committed atomically with `tmp`, `fsync`, and `rename`, and the previous commit is kept as `.bak`. On activation and before every sweep, the plugin settles unfinished `trashing`, `restoring`, and `purging` entries. It completes what can be completed, rolls back what can be rolled back, marks uncertain cases as `missing`, and never deletes a session log during that recovery pass.
|
|
153
|
+
|
|
154
|
+
A same-filesystem move uses `rename`. A cross-filesystem move copies into a temporary directory, verifies regular-file count and byte count, publishes with `rename`, then removes the source. Any failure cleans up the temporary directory and keeps the source. Symlinks are copied as symlinks, not followed.
|
|
155
|
+
|
|
156
|
+
On POSIX filesystems, the trash root uses `0700` permissions and the index and metadata files use `0600`. A lock older than 10 minutes is treated as stale.
|
|
157
|
+
|
|
158
|
+
The index schema is v2; a v1 index is upgraded automatically on startup and preserved as `.bak`. Older plugin releases cannot read v2 and enter protection mode, so empty the trash before downgrading.
|
|
159
|
+
|
|
160
|
+
## Limitations
|
|
161
|
+
|
|
162
|
+
- The current DSH version keeps an opened session live until the Host process exits. The context menu then shows the explanatory item instead of an actionable move; restart DSH before trashing it.
|
|
163
|
+
- Parent sessions with visible (non-subagent) children cannot be trashed on their own; archive the whole tree instead. Hidden subagent children move with their parent.
|
|
164
|
+
- A trashed tree is restored or deleted as a whole; there is no partial restore. If any member directory is missing, the entry is marked `missing` and can only be purged or forgotten.
|
|
165
|
+
- Orphan restore never recreates the original workspace directory and never reattaches the session to a workspace.
|
|
166
|
+
- Archive and pin state survive trash and restore. The Trash page does not manage either set.
|
|
167
|
+
- Permanent deletion removes the session log only. Attachments, images, projection-cache rows, and search-index rows are not deleted.
|
|
168
|
+
- `/state` returns all entry summaries in one response. Search, filtering, and paging happen in the Client.
|
|
169
|
+
- There is no cross-window live push. Another window's change appears after a refresh or when the window regains focus.
|
|
170
|
+
- Windows and Linux have not been run on real hosts. On Windows, long paths over 260 characters and symlink copying depend on system configuration.
|
|
171
|
+
- macOS path comparison is case-insensitive. On a case-sensitive APFS volume, paths that differ only by case are treated as the same location.
|
|
172
|
+
- Multiple DSH processes sharing one `DSH_HOME` serialize trash operations through `.lock`. A process paused for more than 10 minutes can lose the lock to another process.
|
|
173
|
+
|
|
174
|
+
## Development
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
pnpm install
|
|
178
|
+
pnpm test
|
|
179
|
+
npm run test:e2e
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
`npm run test:e2e` boots a real `dsh web` Host in a temporary `DSH_HOME` and runs the full flow. It uses `/Applications/DeepSeek Harness.app` by default; set `DSH_ELECTRON` to another Electron executable. Set `DSH_E2E_KEEP=1` to keep the temporary home and logs.
|
|
183
|
+
|
|
184
|
+
The Host smoke test needs a separate temporary `DSH_HOME`:
|
|
185
|
+
|
|
186
|
+
```bash
|
|
187
|
+
DSH_HOME=$(mktemp -d) ELECTRON_RUN_AS_NODE=1 \
|
|
188
|
+
"/Applications/DeepSeek Harness.app/Contents/MacOS/DeepSeek Harness" \
|
|
189
|
+
test/smoke-host.mjs
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Project layout:
|
|
193
|
+
|
|
194
|
+
```text
|
|
195
|
+
README.md / README.en.md Chinese / English docs
|
|
196
|
+
LICENSE MIT license
|
|
197
|
+
index.js Host entry, Config, and service wiring
|
|
198
|
+
client.js Client UI, sidebar menu, and Toast
|
|
199
|
+
lib/ index, lock, moves, eligibility, cleanup, HTTP, and state
|
|
200
|
+
locale/ plugin display names and descriptions
|
|
201
|
+
test/ unit tests, Host smoke test, real dsh web e2e
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
The trash icon uses the `trash-outline` artwork from the DeepSeek Harness product icon set, MIT licensed, © 2026 DeepSeek. The project itself is MIT licensed; see [LICENSE](./LICENSE).
|
package/README.md
CHANGED
|
@@ -1,3 +1,203 @@
|
|
|
1
|
-
#
|
|
1
|
+
# dsh-session-trash
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
简体中文 | [English](./README.en.md)
|
|
4
|
+
|
|
5
|
+
[GitHub](https://github.com/wangxiaocuo/dsh-session-trash) · [npm](https://www.npmjs.com/package/@wangxiaocuo/dsh-session-trash)
|
|
6
|
+
|
|
7
|
+
DeepSeek Harness 的会话回收站插件。冷会话可以从侧边栏移出,日志保留在本地;保留期内可以恢复,到期后自动删除日志。面向 DSH 0.2.2-rc.1 的 Desktop 和 Web,Host 和 Client 在同一个包里,不需要构建步骤。
|
|
8
|
+
|
|
9
|
+
当前状态:功能已实现,回归测试与真实 Host 端到端测试通过。macOS 已在真实 DSH Host 上验证;Windows 和 Linux 的路径、锁、重试规则有单测覆盖,尚未在真实主机上运行。
|
|
10
|
+
|
|
11
|
+
## 功能
|
|
12
|
+
|
|
13
|
+
- 侧边栏右键会话,选择「移入回收站」。只有冷会话、没有运行中的 turn、job、subagent 或定时任务时才能移入;会话跑过的隐藏 subagent 子会话会随父会话一起移入、一起恢复。
|
|
14
|
+
- 对仍打开的会话、正在运行的会话或含可见子会话的会话,菜单保留一个带原因的「移入回收站」说明项:点开解释为什么暂时不能移入,而不是把菜单项直接隐藏。
|
|
15
|
+
- 移入后显示 DSH 共享 Toast,只有「撤销」一个动作。撤销先尝试正常恢复,原工作区不存在时改为孤儿恢复。
|
|
16
|
+
- 设置页「回收站」显示标题、Session ID、原工作区、移入时间、剩余天数、大小和状态。列表支持搜索、筛选,每页 50 条。
|
|
17
|
+
- 含子会话的条目显示「含 N 个子会话」徽章;恢复和永久删除按整棵树处理,不做部分恢复。
|
|
18
|
+
- 行操作包括恢复、恢复为孤儿会话、永久删除、从索引移除、复制 Session ID,以及在 Finder 或资源管理器中显示原工作区。
|
|
19
|
+
- 工具栏「永久删除全部」在二次确认后清空回收站:删除所有已移入的会话日志,并把文件缺失的记录移出索引;附件、图片和搜索索引等衍生物不删除。
|
|
20
|
+
- 默认保留 30 天,每 6 小时扫描一次;插件激活后和打开回收站页面时也会检查。清理失败按指数退避重试,失败信息显示在页面上。
|
|
21
|
+
- 索引原子写入,旧索引保留为 `.bak`。索引和备份都损坏时进入保护模式,拒绝写操作,并可从每个对象的 `meta.json` 重建。
|
|
22
|
+
- 重命名、分叉、归档和复制 Session ID 继续使用 DSH 自带的侧边栏菜单,本插件不重复实现。
|
|
23
|
+
|
|
24
|
+
## 安装
|
|
25
|
+
|
|
26
|
+
插件发布在 npm([`@wangxiaocuo/dsh-session-trash`](https://www.npmjs.com/package/@wangxiaocuo/dsh-session-trash)),也可以在「添加插件」里填 GitHub 仓库地址或本地目录。
|
|
27
|
+
|
|
28
|
+
安装和卸载由当前 profile 的 pnpm 完成。Desktop 自带 pnpm;从源码运行 `dsh web` 时要求 `PATH` 里有 pnpm,从 GitHub 安装还需要 git 和可用的 GitHub 网络。
|
|
29
|
+
|
|
30
|
+
### 在 DSH 里安装(推荐)
|
|
31
|
+
|
|
32
|
+
1. 打开侧栏「插件」页,点右上角「添加插件」。
|
|
33
|
+
2. 在「包名或地址」里填 `@wangxiaocuo/dsh-session-trash`。要装当前分支最新代码时,改填 `git+https://github.com/wangxiaocuo/dsh-session-trash.git`。
|
|
34
|
+
3. 「安装源」保持默认。插件和依赖都从 npm 下载;国内网络可以切到「中国大陆镜像源」,镜像同步可能比官方源晚几分钟。
|
|
35
|
+
4. 点「安装」。DSH 先读取包名、简介和版本,再用 pnpm 安装;命令和输出在「查看安装详情」里。
|
|
36
|
+
5. 装好后点「立即启用」。提示需要重启时重启 DSH,Web 端刷新页面。右键菜单和设置页「回收站」随后出现。
|
|
37
|
+
|
|
38
|
+
本地目录安装:先克隆仓库,在目录里执行 `pnpm install`(目录链接形式的 bundle 使用自己的 `node_modules`),再填该目录的绝对路径。
|
|
39
|
+
|
|
40
|
+
安装失败时先看「查看安装详情」里的 pnpm 输出和 Host 日志路径:
|
|
41
|
+
|
|
42
|
+
- 未找到相关插件:检查包名拼写,或等镜像同步;也可以改用 Git 地址或本地目录。
|
|
43
|
+
- 所有安装源都无法连接:检查网络或代理,或改用本地目录。
|
|
44
|
+
- 包没有声明组合包,或路径不是有效的插件包:Git 地址要指向仓库根目录,本地路径要填插件包目录。
|
|
45
|
+
- 安装完成但功能没有出现:确认已在插件页启用;未开启 HMR 的 profile 需要重启 DSH,Web 端还要刷新页面。
|
|
46
|
+
|
|
47
|
+
### 让 Agent 安装
|
|
48
|
+
|
|
49
|
+
让 Agent 执行其中一条。这个操作需要 `danger-full-access` 或单独批准:
|
|
50
|
+
|
|
51
|
+
```text
|
|
52
|
+
plugin_manager install_bundle target=@wangxiaocuo/dsh-session-trash
|
|
53
|
+
plugin_manager install_bundle target=git+https://github.com/wangxiaocuo/dsh-session-trash.git
|
|
54
|
+
plugin_manager install_bundle target=/absolute/path/to/dsh-session-trash
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
本机已有克隆目录时直接装目录,可以省一次下载。装有 DSH CLI 的话,`dsh plugin add <同样的 spec>` 等价。
|
|
58
|
+
|
|
59
|
+
### 手动链接到 profile(开发用)
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
git clone https://github.com/wangxiaocuo/dsh-session-trash.git
|
|
63
|
+
cd dsh-session-trash
|
|
64
|
+
pnpm install
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
cd "${DSH_HOME:-$HOME/.dsh}/profiles/desktop"
|
|
69
|
+
pnpm add link:/absolute/path/to/dsh-session-trash
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
把 `@wangxiaocuo/dsh-session-trash` 加入该 profile `package.json` 的 `dsh.profile.bundles`,重启 DSH 加载 Host,刷新页面加载 Client。运行时依赖 `@deepseek-ai/dsh-home-paths@0.2.2-rc.1` 和 `@deepseek-ai/schemastery@3.19.0`。
|
|
73
|
+
|
|
74
|
+
### 升级和卸载
|
|
75
|
+
|
|
76
|
+
npm 安装的插件在「插件」页打开「会话回收站」的详情,展开「版本」区块后点「更新」;没有可更新的版本时卸载后重装。Git 安装需要卸载重装才会取到新代码;手动链接的在目录里 `git pull`、`pnpm install`,然后重启 DSH 并刷新页面。
|
|
77
|
+
|
|
78
|
+
卸载时打开同一个详情,点「卸载」并确认,profile 里的依赖会一并移除;也可以手动从 profile 的 bundles 和依赖中移除后重启。卸载不会删除 `<DSH_HOME>/.session-trash`,需要清理时手动删除该目录。
|
|
79
|
+
|
|
80
|
+
## 配置
|
|
81
|
+
|
|
82
|
+
在 profile patch 的插件行中配置:
|
|
83
|
+
|
|
84
|
+
```yaml
|
|
85
|
+
- id: session-trash
|
|
86
|
+
name: '@wangxiaocuo/dsh-session-trash'
|
|
87
|
+
config:
|
|
88
|
+
retentionDays: 30 # 1-3650
|
|
89
|
+
sweepIntervalHours: 6 # 1-168
|
|
90
|
+
enableAutoCleanup: true
|
|
91
|
+
debug: false
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
| 字段 | 默认 | 范围 | 说明 |
|
|
95
|
+
|---|---|---|---|
|
|
96
|
+
| `retentionDays` | `30` | 1-3650 | 回收站保留天数,到期自动删除 |
|
|
97
|
+
| `sweepIntervalHours` | `6` | 1-168 | 自动清理间隔 |
|
|
98
|
+
| `enableAutoCleanup` | `true` | boolean | 关闭后仍可手动永久删除,不自动清理 |
|
|
99
|
+
| `debug` | `false` | boolean | 输出更详细的诊断日志 |
|
|
100
|
+
|
|
101
|
+
省略 `config` 时使用默认值。配置值不合法时插件拒绝激活,不会回退到默认值。修改普通字段会重启插件。
|
|
102
|
+
|
|
103
|
+
## 使用
|
|
104
|
+
|
|
105
|
+
右键冷会话选择「移入回收站」。Toast 消失后,仍可从设置页「回收站」恢复;原工作区目录已不存在时,页面会先确认,确认后会话回到「未分组」。需要清空时点工具栏「永久删除全部」并确认,未过期的条目也会删除。
|
|
106
|
+
|
|
107
|
+
当前 DSH 版本会把打开过的会话保留为 live agent,直到该 Host 进程结束。会话即使空闲也算 live,此时右键菜单显示的是带原因的说明项、不能移入;需要回收时先重启 DSH,或直接归档。另一个 DSH 实例不会识别这种 live 状态,不要用它绕过限制。
|
|
108
|
+
|
|
109
|
+
## HTTP API
|
|
110
|
+
|
|
111
|
+
所有路由位于 `/plugins/dsh-session-trash/api/`,以精确路径注册在 `ctx.webServer` 上。
|
|
112
|
+
|
|
113
|
+
| 方法 | 路径 | 说明 |
|
|
114
|
+
|---|---|---|
|
|
115
|
+
| `GET` | `/state` | 配置、索引状态、条目、统计和清理诊断;打开页面时可能触发一次节流清理 |
|
|
116
|
+
| `POST` | `/preflight` | `{ sessionId }` 返回 `{ eligible: true, descendants }` 或 `{ eligible: false, code, message, details? }`;`descendants` 是一并移入的隐藏子会话数 |
|
|
117
|
+
| `POST` | `/trash` | `{ sessionId }`,移动整棵会话树(父会话 + 隐藏 subagent 子会话),返回 `{ ok, entry }` |
|
|
118
|
+
| `POST` | `/restore` | `{ sessionId, mode: 'normal' \| 'orphan' }` |
|
|
119
|
+
| `POST` | `/purge` | `{ sessionId }`,永久删除会话日志 |
|
|
120
|
+
| `POST` | `/purge-all` | 无请求体,永久删除回收站中的全部条目;`missing` 记录仅移除索引;返回 `{ ok, purged, forgotten, failed }` |
|
|
121
|
+
| `POST` | `/forget` | `{ sessionId }`,仅移除 `missing` 条目的索引记录 |
|
|
122
|
+
| `POST` | `/reveal` | `{ sessionId }`,打开原工作区 |
|
|
123
|
+
| `POST` | `/cleanup` | 无请求体,手动执行一次清理 |
|
|
124
|
+
| `POST` | `/rebuild` | 无请求体,仅在索引保护模式下重建 |
|
|
125
|
+
|
|
126
|
+
请求需要通过 `ctx.connection.requestRejection()` 和 `ctx.connection.admit()`,`POST` 还要带 `X-DSH-Session-Trash: 1`。请求体使用 `Content-Type: application/json`,上限 64 KiB。响应带 `Cache-Control: no-store`,不返回回收站根目录的绝对路径。
|
|
127
|
+
|
|
128
|
+
失败响应格式为 `{ ok: false, error: { code, message, details? } }`。预检拒绝码包括 `SESSION_NOT_FOUND`、`ALREADY_TRASHED`、`SESSION_NOT_MATERIALIZED`、`STORAGE_UNSUPPORTED`、`SESSION_SUBAGENT`、`SESSION_HAS_DESCENDANTS`、`SESSION_LINEAGE_UNRESOLVED`、`SESSION_LIVE`、`SESSION_ACTIVE`、`UNSAFE_SESSION_DIR`、`INDEX_CORRUPT` 和 `IO_ERROR`。
|
|
129
|
+
|
|
130
|
+
恢复和维护拒绝码包括 `TRASH_ENTRY_NOT_FOUND`、`TRASH_STATE_INVALID`、`TRASH_DIR_MISSING`、`RESTORE_TARGET_EXISTS`、`WORKSPACE_MISSING`、`RESTORE_VERIFY_FAILED`、`UNSAFE_RESTORE_PATH`、`FORGET_NOT_ALLOWED`、`REVEAL_UNAVAILABLE`、`REVEAL_FAILED`、`INDEX_CORRUPT`、`INDEX_NOT_CORRUPT`、`LOCKED` 和 `IO_ERROR`。
|
|
131
|
+
|
|
132
|
+
`/state` 的 `stats.cleanup` 记录上次清理时间、下次清理时间、重试时间、连续失败次数和上次运行摘要。
|
|
133
|
+
|
|
134
|
+
`/state` 的条目新增 `subagentCount`(一并移入的隐藏子会话数)和 `members`(父会话在前,含 `sessionId`、`origin`、`sizeBytes`);`sizeBytes`/`fileCount` 是整棵树的总量。`/trash` 返回的 `entry` 使用同一形状。
|
|
135
|
+
|
|
136
|
+
## 数据与恢复
|
|
137
|
+
|
|
138
|
+
```text
|
|
139
|
+
<DSH_HOME>/.session-trash/
|
|
140
|
+
trash-index.json # 主索引
|
|
141
|
+
trash-index.json.bak # 上一次成功提交的索引
|
|
142
|
+
.lock # 跨进程锁
|
|
143
|
+
objects/
|
|
144
|
+
<trashId>/
|
|
145
|
+
meta.json # 对象元数据
|
|
146
|
+
session/ # 被移动的父会话目录
|
|
147
|
+
children/
|
|
148
|
+
<sessionId>/ # 一并移入的隐藏 subagent 子会话目录
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
索引通过 `tmp`、`fsync`、`rename` 原子提交,旧版本保留为 `.bak`。启动时和每次清理前,插件会处理未完成的 `trashing`、`restoring`、`purging` 状态:能完成的完成,能回滚的回滚,无法判断的标记为 `missing`,不会删除会话日志。
|
|
152
|
+
|
|
153
|
+
同文件系统移动使用 `rename`。跨文件系统时先复制到临时目录,校验普通文件数量和字节数,再 rename 发布,最后删除源目录。任一步失败都会清理临时目录并保留源。符号链接按符号链接复制,不跟随。
|
|
154
|
+
|
|
155
|
+
在 POSIX 文件系统上,回收站目录权限为 `0700`,索引和元数据为 `0600`。锁文件超过 10 分钟未更新时视为陈旧。
|
|
156
|
+
|
|
157
|
+
索引 schema 为 v2,v1 索引会在启动时自动升级并保留为 `.bak`。旧版本插件读不懂 v2 索引,会进入保护模式;降级前请先清空回收站。
|
|
158
|
+
|
|
159
|
+
## 限制
|
|
160
|
+
|
|
161
|
+
- 当前 DSH 版本中,打开过的会话会保持 live 直到 Host 进程结束,因此不会显示「移入回收站」。需要回收时重启 DSH。
|
|
162
|
+
- 带可见子会话(非 subagent)的父会话不能回收,只能归档整棵树;subagent 的隐藏子会话随父会话一起移入。
|
|
163
|
+
- 整棵树按整体恢复或删除,不做部分恢复:任一成员目录缺失时条目会标记为 `missing`,只能永久删除或从索引移除。
|
|
164
|
+
- 孤儿恢复不会重建原工作区目录,也不会重新挂到工作区。
|
|
165
|
+
- 归档和置顶状态会跨回收、恢复保留,回收站页面不管理这两个集合。
|
|
166
|
+
- 永久删除只删除会话日志。附件、图片、投影缓存和搜索索引不会被删除。
|
|
167
|
+
- `/state` 一次返回全部条目摘要,页面在客户端搜索、筛选和分页。
|
|
168
|
+
- 没有跨窗口实时推送。另一个窗口的变更要等刷新或窗口重新获得焦点后出现。
|
|
169
|
+
- Windows 和 Linux 尚未在真实主机上验证。Windows 超过 260 字符的长路径和符号链接复制受系统设置影响。
|
|
170
|
+
- macOS 的路径比较按大小写不敏感处理。在大小写敏感的 APFS 卷上,仅大小写不同的两个路径会被视为同一位置。
|
|
171
|
+
- 多个 DSH 进程共享同一个 `DSH_HOME` 时,回收站操作通过 `.lock` 串行;进程暂停超过 10 分钟后,锁可能被另一个进程接管。
|
|
172
|
+
|
|
173
|
+
## 开发
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
pnpm install
|
|
177
|
+
pnpm test
|
|
178
|
+
npm run test:e2e
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
`npm run test:e2e` 会启动真实 `dsh web` Host,在临时 `DSH_HOME` 中跑完整流程。默认使用 `/Applications/DeepSeek Harness.app`,也可以用 `DSH_ELECTRON` 指向其他 Electron 可执行文件。设置 `DSH_E2E_KEEP=1` 可以保留临时目录和日志。
|
|
182
|
+
|
|
183
|
+
Host 冒烟测试需要单独的临时 `DSH_HOME`:
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
DSH_HOME=$(mktemp -d) ELECTRON_RUN_AS_NODE=1 \
|
|
187
|
+
"/Applications/DeepSeek Harness.app/Contents/MacOS/DeepSeek Harness" \
|
|
188
|
+
test/smoke-host.mjs
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
项目结构:
|
|
192
|
+
|
|
193
|
+
```text
|
|
194
|
+
README.md / README.en.md 中文 / English 文档
|
|
195
|
+
LICENSE MIT 许可证
|
|
196
|
+
index.js Host 入口、Config 和服务装配
|
|
197
|
+
client.js Client 界面、侧边栏菜单和 Toast
|
|
198
|
+
lib/ 索引、锁、移动、资格判断、清理、HTTP 和状态投影
|
|
199
|
+
locale/ 插件显示名称和描述
|
|
200
|
+
test/ 单测、Host 冒烟测试、真实 dsh web 端到端测试
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
回收站图标使用 DeepSeek Harness 产品图标集中的 `trash-outline` 图形,MIT 许可,© 2026 DeepSeek。项目整体为 MIT 许可,见 [LICENSE](./LICENSE)。
|