dsh-session-curator 0.1.0 → 0.1.1
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/README.md +22 -189
- package/README.zh-CN.md +18 -144
- package/lib/client.js +440 -21
- package/lib/index.js +156 -13
- package/package.json +1 -1
- package/scripts/activation.mjs +1 -1
- package/scripts/client.mjs +151 -0
- package/scripts/smoke.mjs +226 -25
package/README.md
CHANGED
|
@@ -2,214 +2,47 @@
|
|
|
2
2
|
|
|
3
3
|
**English** | [中文](README.zh-CN.md)
|
|
4
4
|
|
|
5
|
-
A
|
|
5
|
+
A DSH (DeepSeek Harness) plugin that adds **session deletion** and **archive management**.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
and
|
|
7
|
+
DSH can archive and pin a session but offers no API to delete one. This plugin does the file-level
|
|
8
|
+
work itself, and designs deletion as a **recoverable soft delete**: the session directory is moved to
|
|
9
|
+
a trash folder together with its metadata, and restoring it puts the session back into its original
|
|
10
|
+
workspace.
|
|
10
11
|
|
|
11
12
|
## Features
|
|
12
13
|
|
|
13
14
|
| Where | What |
|
|
14
15
|
| --- | --- |
|
|
15
|
-
| Session row `⋯` menu
|
|
16
|
-
| Settings → **Session manager**
|
|
17
|
-
|
|
|
16
|
+
| Session row `⋯` menu | **Delete session** — moves it to the trash after a confirmation |
|
|
17
|
+
| Settings → **Session manager** | Three tabs — **archived / pinned / trash** — grouped by the session's original workspace folder, with per-row and bulk actions |
|
|
18
|
+
| Settings → **Session manager** → **Settings** | Basics (default tab, grouping, **sidebar archived visibility**, row details, order), the installed version, and an upgrade hint with the exact command when npm has a newer release |
|
|
19
|
+
| Confirmation dialog | Delete and permanent delete (permanent delete needs an explicit "I cannot undo this" tick) |
|
|
18
20
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
whole workspace at once. Rows show title, log size and file count, and creation (or deletion) time.
|
|
21
|
+
Deleting is always reversible: every trashed entry keeps its original workspace folder, title and
|
|
22
|
+
deletion time, and running sessions are never touched.
|
|
22
23
|
|
|
23
24
|
## Install
|
|
24
25
|
|
|
25
26
|
```powershell
|
|
26
|
-
#
|
|
27
|
-
|
|
28
|
-
# or: dsh plugin add dsh-session-curator
|
|
27
|
+
# desktop profile
|
|
28
|
+
dsh plugin --profile desktop add dsh-session-curator
|
|
29
29
|
|
|
30
|
-
#
|
|
31
|
-
|
|
30
|
+
# web profile
|
|
31
|
+
dsh plugin --profile web add dsh-session-curator
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
From GitHub instead of npm:
|
|
35
35
|
|
|
36
36
|
```powershell
|
|
37
|
-
|
|
38
|
-
$dst = "$env:USERPROFILE\.dsh\profiles\desktop\node_modules\dsh-session-curator"
|
|
39
|
-
Remove-Item $dst -Recurse -Force -ErrorAction SilentlyContinue
|
|
40
|
-
Copy-Item $src $dst -Recurse -Force
|
|
41
|
-
# then append "dsh-session-curator" to dsh.profile.bundles in the profile package.json and restart DSH
|
|
37
|
+
dsh plugin --profile desktop add github:Motues/dsh-session-curator
|
|
42
38
|
```
|
|
43
39
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
40
|
+
The command runs pnpm inside that profile and records the package in both `dependencies` and
|
|
41
|
+
`dsh.profile.bundles`. Restart DSH to pick up the host half; the client half only needs a page
|
|
42
|
+
refresh.
|
|
47
43
|
|
|
48
|
-
The plugin is build-free: `lib/index.js` is a plain ESM host plugin and
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
## Safety model
|
|
52
|
-
|
|
53
|
-
- **Soft delete.** The session directory is moved wholesale to
|
|
54
|
-
`<DSH_HOME>/storages/session-manager/trash/<sessionId>/`, together with a `__trash.json`
|
|
55
|
-
(original slug, original cwd, title, deletion time) and the original projection cache
|
|
56
|
-
`__projcache.json`. Restore moves it back to `<DSH_HOME>/sessions/<slug>/<sessionId>/` and does its
|
|
57
|
-
best to re-attach the session to its original workspace via `registry.resolveByPath(cwd)` +
|
|
58
|
-
`attachSession`.
|
|
59
|
-
- **The trash directory deliberately keeps the old name `session-manager`.** It already holds real
|
|
60
|
-
data (sessions that were genuinely deleted); renaming the path would orphan them. There is a ⚠️
|
|
61
|
-
comment in the code — please don't "tidy it up".
|
|
62
|
-
- **Consistent bookkeeping.** Deletion calls `Workspace.detachSession(sessionId)`; archived sessions
|
|
63
|
-
are `unarchiveSession`-ed first and **pinned ones are `unpinSession`-ed first** (otherwise the id
|
|
64
|
-
stays in `pinnedSessionIds` and the session shows up under both *pinned* and *trash*), so a restored
|
|
65
|
-
session lands back where it was.
|
|
66
|
-
- **Not `<DSH_HOME>/sessions-trash`** — that name risks being swept up by the session scanner as a
|
|
67
|
-
`sessions*` directory.
|
|
68
|
-
- **Running sessions are refused.** Same activity predicate as official archive:
|
|
69
|
-
`ctx.waterfall("workspace/session-activity", …)`, plus `DSH_SESSION_ID` to block deleting the
|
|
70
|
-
session you are currently using.
|
|
71
|
-
- **Permanent delete is only available for trash entries**, and requires an explicit checkbox.
|
|
72
|
-
|
|
73
|
-
## Design constraints (read before changing)
|
|
74
|
-
|
|
75
|
-
1. **Zero bare-specifier imports.** `$DSH_HOME` → else `~/.dsh` resolution is inlined in
|
|
76
|
-
`lib/index.js`; it deliberately does **not** `import '@deepseek-ai/dsh-home-paths'`. That package
|
|
77
|
-
only exists inside DSH's own `node_modules`, and the plugin usually appears under a profile as a
|
|
78
|
-
junction/symlink — Node resolves bare specifiers through **realpath**, so the import would fail
|
|
79
|
-
with `Cannot find package …` and the plugin would never load. Zero imports means it runs from
|
|
80
|
-
anywhere.
|
|
81
|
-
2. **Use `ctx.connection.fetch.register()`, not `ctx.connection.rpc.handle()`.** The nastiest trap:
|
|
82
|
-
inside `rpc.handle()` the effect owner is **Connection's own ctx** (its inject is only
|
|
83
|
-
`["credentials"]`), so it always throws `cannot get property "webServer" without inject`; the
|
|
84
|
-
plugin fiber ends up `failed`, not a single route is mounted, every client POST falls through to
|
|
85
|
-
the SPA static fallback and answers **HTTP 405** — that was the original
|
|
86
|
-
`transport failure for /session-manager/trash: HTTP 405` (a real error message, kept verbatim).
|
|
87
|
-
Adding `webServer` to the plugin's own inject **does not fix it** — the failing ctx is not the
|
|
88
|
-
plugin's. Instead each endpoint registers one **exact fetch route**
|
|
89
|
-
`/api/session-curator/<endpoint>` under the shared `/api` channel: the Host/Origin/`sec-fetch-site`
|
|
90
|
-
trust checks, browser session-cookie auth and body-size limits still run first in Connection's
|
|
91
|
-
`/api` prefix route, so the plugin still carries zero auth code, while route registration hangs off
|
|
92
|
-
**the plugin's own fiber** and is torn down with it.
|
|
93
|
-
3. **The client depends on no Cordis service (but prefers the official one).** It first tries
|
|
94
|
-
`ctx.get('connection').rpc.call('/api', 'session-curator/<endpoint>', payload)`; failing that it
|
|
95
|
-
falls back to a hand-written `fetch` that replicates Connection's wire protocol verbatim. Bad
|
|
96
|
-
envelopes always answer **200 + an error envelope**, so the client reads prose instead of a
|
|
97
|
-
transport exception. **The trust fence and session auth always run host-side.**
|
|
98
|
-
4. **`shell.overlay` is a frame-wide overlay with no owner props** — the correct home for a global
|
|
99
|
-
confirmation dialog.
|
|
100
|
-
5. **Menu rows are `MenuItemButton` + `onSelect`** (not `onClick`), and closing the menu is the
|
|
101
|
-
owner's call, so the action must call the `setOpen(false)` it got from `useMenuOpenState()` itself.
|
|
102
|
-
|
|
103
|
-
## Why the settings nav icon is "borrowed"
|
|
104
|
-
|
|
105
|
-
The settings shell's `navIcon(id)` looks the icon up **by section id**; the table only has `account` /
|
|
106
|
-
`models` / `agent-presets` / `plugins` / `archived-sessions`, and everything else falls back to the
|
|
107
|
-
default gear. The only unused entry, `archived-sessions`, happens to map to the archive box and fits
|
|
108
|
-
this plugin, so it is borrowed — but only after consulting the **slot ledger**: once DSH registers
|
|
109
|
-
`archived-sessions` itself, the plugin automatically falls back to its own id (and the gear icon).
|
|
110
|
-
To stop borrowing entirely, change `id: sectionIdFor(ctx)` to `id: SECTION_ID` in `apply()` in
|
|
111
|
-
`lib/client.js`.
|
|
112
|
-
|
|
113
|
-
## Hot-reload boundaries (measured)
|
|
114
|
-
|
|
115
|
-
- **The client half hot-reloads**: `dsh-client-hmr` computes a revision from mtime/size, so editing
|
|
116
|
-
`lib/client.js` takes effect on page refresh.
|
|
117
|
-
- **The host half does not**: Node's ESM module cache is **keyed by URL**. Editing `lib/index.js`
|
|
118
|
-
**requires a DSH restart**.
|
|
119
|
-
- `cordis.patch.yml` intentionally stays a pure `- insert:` shape, because dshmarket's hot-mount only
|
|
120
|
-
accepts that shape.
|
|
121
|
-
|
|
122
|
-
## HTTP API
|
|
123
|
-
|
|
124
|
-
Shared `/api` channel, every route `POST` + `requestBody: 'buffered'`, at
|
|
125
|
-
`/api/session-curator/<endpoint>`:
|
|
126
|
-
|
|
127
|
-
| endpoint | payload | returns |
|
|
128
|
-
| --- | --- | --- |
|
|
129
|
-
| `snapshot` | `{}` | `{ dshHome, trashDir, sessionsDir, archived[], pinned[], trashed[], totals{} }` |
|
|
130
|
-
| `archive` | `{ ids, stopActivity }` | `{ results[], ...snapshot }` |
|
|
131
|
-
| `unarchive` / `pin` / `unpin` | `{ ids }` | `{ results[], ...snapshot }` |
|
|
132
|
-
| `trash` | `{ ids }` | `{ results[], ...snapshot }` |
|
|
133
|
-
| `restore` | `{ ids }` | `{ results[], ...snapshot }` |
|
|
134
|
-
| `purge` | `{ ids }` | `{ results[], ...snapshot }` |
|
|
135
|
-
|
|
136
|
-
The body is Connection's wire envelope
|
|
137
|
-
`{ type:'client-request', rpcId, method:'session-curator/<endpoint>', payload }`. A `method` that does
|
|
138
|
-
not match the route, or a broken envelope, answers **200 + an error envelope** (error codes
|
|
139
|
-
`session-curator/*`) instead of throwing a transport error. `results[]` holds per-item
|
|
140
|
-
`{ sessionId, ok }` or `{ sessionId, ok:false, reason }`; one failure does not affect the rest.
|
|
141
|
-
`ids` accepts only an array of strings — anything else is treated as empty.
|
|
142
|
-
|
|
143
|
-
## Troubleshooting
|
|
144
|
-
|
|
145
|
-
1. `plugin_manager` / `list_plugins`: check the `fiberPhase` of `include:dsh-session-curator` — it
|
|
146
|
-
must be `active`. `failed` means `apply()` threw (the stack only reaches the host logger, not
|
|
147
|
-
Electron's stdout) and **not a single route is mounted**.
|
|
148
|
-
2. Whether a route exists can only be asked from a **logged-in page** (the `/api` trust fence runs
|
|
149
|
-
before route matching, so a bare `curl`/`Invoke-RestMethod` against `/api/*` always gets 401 and
|
|
150
|
-
tells you nothing). In the browser console:
|
|
151
|
-
|
|
152
|
-
```js
|
|
153
|
-
fetch("/api/session-curator/snapshot", {
|
|
154
|
-
method: "POST",
|
|
155
|
-
headers: { "content-type": "application/json" },
|
|
156
|
-
body: JSON.stringify({ type: "client-request", rpcId: "probe", method: "session-curator/snapshot", payload: {} })
|
|
157
|
-
}).then(async (r) => [r.status, await r.json()]);
|
|
158
|
-
```
|
|
159
|
-
|
|
160
|
-
- **200 + `{type:'server-response', result:{ok:true, …}}`** = the route is there and working.
|
|
161
|
-
- **404 `not found`** = the route is not registered (the host half is `failed`, or you did not
|
|
162
|
-
restart DSH after editing it).
|
|
163
|
-
- Control: the same fetch against `/api/nope` is always 404 (a route-table miss, not a 404 page).
|
|
164
|
-
|
|
165
|
-
## Self-check (no running DSH needed)
|
|
166
|
-
|
|
167
|
-
```powershell
|
|
168
|
-
npm test # runs the three below, in order
|
|
169
|
-
node scripts/smoke.mjs # domain logic + wire protocol (fake ctx + real Request + real filesystem)
|
|
170
|
-
node scripts/activation.mjs # host-half activation (real cordis, extracted from app.asar) + manifest metadata
|
|
171
|
-
node scripts/client.mjs # client half: grouping logic + a mini-React render of the settings panel
|
|
172
|
-
```
|
|
173
|
-
|
|
174
|
-
Measured locally: `client.mjs` **59/59**, `smoke.mjs` **14/14**, `activation.mjs` **17/17**.
|
|
175
|
-
`activation.mjs` prints SKIP and exits when it cannot find `app.asar`; point `$env:DSH_APP_ASAR` at one
|
|
176
|
-
to force it.
|
|
177
|
-
|
|
178
|
-
## Layout
|
|
179
|
-
|
|
180
|
-
```
|
|
181
|
-
dsh-session-curator/
|
|
182
|
-
├── package.json # dsh.bundle.patch / dsh.client / icon / locale exports
|
|
183
|
-
├── cordis.patch.yml # pure - insert shape, hot-mountable by dshmarket
|
|
184
|
-
├── icon.svg # plugin artwork (36 grid, self-coloured; the card renders it via <img>)
|
|
185
|
-
├── locale/{zh,en}.json # card title / description
|
|
186
|
-
├── lib/index.js # host half: file-level soft delete + archive management + exact /api routes
|
|
187
|
-
├── lib/client.js # client half: menu item + confirm dialog + settings panel
|
|
188
|
-
└── scripts/*.mjs # the three self-checks
|
|
189
|
-
```
|
|
190
|
-
|
|
191
|
-
## Name history
|
|
192
|
-
|
|
193
|
-
The plugin started as `dsh-session-manager`, which is **taken on npm** (lesterq's 0.6.2 — same niche,
|
|
194
|
-
more features; `dsh-session-hub`, `dsh-sessions`, `dsh-session-trash` and `dsh-session-organizer` are
|
|
195
|
-
taken too). Before the first release it was renamed to **`dsh-session-curator`**: package name,
|
|
196
|
-
`cordis.patch.yml` `id`/`name`, RPC route prefix, error codes, locale namespace, client module ids and
|
|
197
|
-
slot ids all use the new name.
|
|
198
|
-
|
|
199
|
-
Two places **intentionally keep the old name**: the trash directory
|
|
200
|
-
`<DSH_HOME>/storages/session-manager/trash` (it holds real data) and the historical error message
|
|
201
|
-
`transport failure for /session-manager/trash: HTTP 405` (rewriting it would make it untrue).
|
|
202
|
-
|
|
203
|
-
## Compatibility
|
|
204
|
-
|
|
205
|
-
```json
|
|
206
|
-
"engines": { "node": ">=20", "dsh": ">=0.2.0-rc.2 <0.2.1-0" }
|
|
207
|
-
```
|
|
208
|
-
|
|
209
|
-
Compatibility is declared only for host versions actually tested. Peers list just the interface
|
|
210
|
-
surfaces this plugin really binds to, and **all of them are `optional: true`**: those `@deepseek-ai/*`
|
|
211
|
-
packages are injected by the runtime and never published, so without `optional` pnpm would look for
|
|
212
|
-
them on the registry, 404, and need a retry. Only tested on Windows, hence no `os` field.
|
|
44
|
+
The plugin is build-free and has no npm dependencies: `lib/index.js` is a plain ESM host plugin and
|
|
45
|
+
`lib/client.js` is a hand-written client bundle.
|
|
213
46
|
|
|
214
47
|
## License
|
|
215
48
|
|
package/README.zh-CN.md
CHANGED
|
@@ -4,168 +4,42 @@
|
|
|
4
4
|
|
|
5
5
|
给 DSH(DeepSeek Harness)补上**删除会话**与**归档会话管理**的插件。
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
DSH 能归档和置顶会话,却没有删除会话的 API。这个插件自己做文件级操作,并把"删除"做成
|
|
8
|
+
**可完整恢复的软删除**:会话目录连同元数据一起移入回收站,恢复时能回到原来的工作区。
|
|
9
9
|
|
|
10
10
|
## 能力
|
|
11
11
|
|
|
12
12
|
| 位置 | 内容 |
|
|
13
13
|
| --- | --- |
|
|
14
|
-
| 会话行 `⋯`
|
|
15
|
-
| 设置 →
|
|
16
|
-
|
|
|
14
|
+
| 会话行 `⋯` 菜单 | **删除会话** —— 确认后移入回收站 |
|
|
15
|
+
| 设置 → **会话管理** | 三个页签:**已归档 / 已置顶 / 回收站**,按会话原来的工作区文件夹分组,支持逐行与批量操作 |
|
|
16
|
+
| 设置 → **会话管理** → **配置** | 基础设置(默认页签 / 分组 / **侧边栏是否显示已归档** / 行内信息 / 排序)、当前版本号,以及 npm 上有新版本时的升级提示与命令 |
|
|
17
|
+
| 确认弹窗 | 删除与永久删除(永久删除需要勾选"我明白这无法撤销") |
|
|
17
18
|
|
|
18
|
-
|
|
19
|
-
|
|
19
|
+
删除始终可逆:回收站里的每条记录都保留原来的工作区文件夹、标题和删除时间;正在运行的会话
|
|
20
|
+
不会被删除。
|
|
20
21
|
|
|
21
22
|
## 安装
|
|
22
23
|
|
|
23
24
|
```powershell
|
|
24
|
-
#
|
|
25
|
-
|
|
26
|
-
# 或:dsh plugin add dsh-session-curator
|
|
25
|
+
# desktop profile
|
|
26
|
+
dsh plugin --profile desktop add dsh-session-curator
|
|
27
27
|
|
|
28
|
-
#
|
|
29
|
-
|
|
28
|
+
# web profile
|
|
29
|
+
dsh plugin --profile web add dsh-session-curator
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
从 GitHub 装(不走 npm):
|
|
33
33
|
|
|
34
34
|
```powershell
|
|
35
|
-
|
|
36
|
-
$dst = "$env:USERPROFILE\.dsh\profiles\desktop\node_modules\dsh-session-curator"
|
|
37
|
-
Remove-Item $dst -Recurse -Force -ErrorAction SilentlyContinue
|
|
38
|
-
Copy-Item $src $dst -Recurse -Force
|
|
39
|
-
# 再把 "dsh-session-curator" 追加进 profile package.json 的 dsh.profile.bundles,然后重启 DSH
|
|
35
|
+
dsh plugin --profile desktop add github:Motues/dsh-session-curator
|
|
40
36
|
```
|
|
41
37
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
> `dsh.profile.bundles` 里的那一行。
|
|
38
|
+
这条命令在那个 profile 目录里跑 pnpm,并把这个包同时记进 `dependencies` 和
|
|
39
|
+
`dsh.profile.bundles`。宿主半区需要重启 DSH 生效,客户端半区刷新页面即可。
|
|
45
40
|
|
|
46
|
-
|
|
47
|
-
`window.__ModuleLoader__.load(...)` 束,**零 npm 依赖**,装进去就能用。
|
|
41
|
+
插件免构建、零 npm 依赖:`lib/index.js` 是普通 ESM 宿主插件,`lib/client.js` 是手写的客户端束。
|
|
48
42
|
|
|
49
|
-
##
|
|
50
|
-
|
|
51
|
-
- **软删除**:会话目录整体搬到 `<DSH_HOME>/storages/session-manager/trash/<sessionId>/`,
|
|
52
|
-
并写入 `__trash.json`(原 slug、原 cwd、标题、删除时间)与原来的投影缓存 `__projcache.json`。
|
|
53
|
-
恢复时按 slug 搬回 `<DSH_HOME>/sessions/<slug>/<sessionId>/`,并尽力用
|
|
54
|
-
`registry.resolveByPath(cwd)` + `attachSession` 把会话挂回原工作区。
|
|
55
|
-
- **回收站目录故意沿用旧名 `session-manager`**:里面已有真实数据(真实删除过的会话),
|
|
56
|
-
改路径会把它们变成孤儿。代码里带 ⚠️ 注释;请勿"顺手改名"。
|
|
57
|
-
- **记账一致**:删除时调 `Workspace.detachSession(sessionId)`;归档中的先 `unarchiveSession`、
|
|
58
|
-
**置顶中的先 `unpinSession`**(否则 id 留在 `pinnedSessionIds` 里,会同时出现在"已置顶"和
|
|
59
|
-
"回收站"两个页签),所以恢复后能回到原位。
|
|
60
|
-
- **不用 `<DSH_HOME>/sessions-trash`**:那个名字有被会话扫描器以 `sessions*` 形态误收的风险。
|
|
61
|
-
- **拒绝删除正在运行的会话**:走与官方归档同一条活动判据
|
|
62
|
-
`ctx.waterfall("workspace/session-activity", …)`;另用 `DSH_SESSION_ID` 拦住"删除当前会话"。
|
|
63
|
-
- **永久删除只对回收站条目开放**,且需显式勾选确认。
|
|
64
|
-
|
|
65
|
-
## 实现上不得不这么做的几点(改之前先读)
|
|
66
|
-
|
|
67
|
-
1. **零裸标识符导入。** `$DSH_HOME` → 否则 `~/.dsh` 的解析在 `lib/index.js` 里内联实现,
|
|
68
|
-
刻意不 `import '@deepseek-ai/dsh-home-paths'`:那个包只存在于 DSH 自己的 `node_modules`,
|
|
69
|
-
而插件常以 junction / symlink 出现在 profile 下,此时 Node 按 **realpath** 解析裸标识符会找不到,
|
|
70
|
-
直接 `Cannot find package …`,插件加载不了。零导入后"放哪都能跑"。
|
|
71
|
-
2. **不用 `ctx.connection.rpc.handle()`,改用 `ctx.connection.fetch.register()`。** 踩得最狠的坑:
|
|
72
|
-
`rpc.handle()` 内部 `owner.effect(...)` 的 owner 是 **Connection 服务自己的 ctx**(它的 inject
|
|
73
|
-
只有 `["credentials"]`),必然抛 `cannot get property "webServer" without inject`;结果插件 fiber
|
|
74
|
-
`failed`、一条路由都没挂上,客户端每个 POST 落到 SPA 静态兜底,被回 **HTTP 405** ——
|
|
75
|
-
也就是当年那条 `transport failure for /session-manager/trash: HTTP 405`(真实报错原文,保留不改)。
|
|
76
|
-
在插件自己的 inject 里加 `webServer` **修不了**(报错的 ctx 不是插件的)。
|
|
77
|
-
现在每个端点在共享 `/api` 通道下注册一条**精确 Fetch 路由** `/api/session-curator/<endpoint>`:
|
|
78
|
-
Host/Origin/`sec-fetch-site` 信任检查、浏览器会话 cookie 鉴权和请求体上限仍由 Connection 的
|
|
79
|
-
`/api` 前缀路由先执行,插件依旧零鉴权代码;而路由注册挂在**插件自己的 fiber** 上,卸载即摘掉。
|
|
80
|
-
3. **客户端不依赖任何 Cordis 服务(但优先用官方的)。** 优先
|
|
81
|
-
`ctx.get('connection').rpc.call('/api', 'session-curator/<endpoint>', payload)`;拿不到时退回手写
|
|
82
|
-
`fetch`,逐字复刻 Connection 的线协议。坏信封一律回 **200 + 错误信封**,客户端读到的是人话而不是
|
|
83
|
-
传输层异常。**信任栅栏与会话鉴权始终在宿主侧生效。**
|
|
84
|
-
4. **`shell.overlay` 是无 owner props 的 frame-wide 浮层**,是全局确认弹窗的正确选址。
|
|
85
|
-
5. **菜单行是 `MenuItemButton` + `onSelect`**(不是 `onClick`),且"关不关菜单是 owner 的决定",
|
|
86
|
-
所以动作里要自己调 `useMenuOpenState()` 拿到的 `setOpen(false)`。
|
|
87
|
-
|
|
88
|
-
## 设置页导航图标为什么是"借"的
|
|
89
|
-
|
|
90
|
-
设置外壳的 `navIcon(id)` 是**按 section id 查表**,表里只有 `account` / `models` /
|
|
91
|
-
`agent-presets` / `plugins` / `archived-sessions`,其余回落默认齿轮。表里唯一没人用的
|
|
92
|
-
`archived-sessions` 语义刚好贴合本插件,所以借用了它,并且**先查槽位账本**:一旦 DSH 自己注册了
|
|
93
|
-
`archived-sessions`,插件会自动退回自己的 id(齿轮图标)。不借的话,把 `lib/client.js` 里
|
|
94
|
-
`apply()` 的 `id: sectionIdFor(ctx)` 改成 `id: SECTION_ID` 即可。
|
|
95
|
-
|
|
96
|
-
## 热更新的边界(实测)
|
|
97
|
-
|
|
98
|
-
- **客户端半区能热更新**:`dsh-client-hmr` 按 mtime/size 算 revision,改 `lib/client.js` 后刷新页面即可。
|
|
99
|
-
- **宿主半区不能热更新**:Node 的 ESM 模块缓存**按 URL 缓存**,改 `lib/index.js` **必须重启 DSH**。
|
|
100
|
-
- `cordis.patch.yml` 故意保持纯 `- insert:` 形状,因为 dshmarket 的热挂载只接受这种形状。
|
|
101
|
-
|
|
102
|
-
## HTTP API
|
|
103
|
-
|
|
104
|
-
共享 `/api` 通道,全部 `POST` + `requestBody: 'buffered'`,路由为 `/api/session-curator/<endpoint>`:
|
|
105
|
-
|
|
106
|
-
| endpoint | payload | 返回 |
|
|
107
|
-
| --- | --- | --- |
|
|
108
|
-
| `snapshot` | `{}` | `{ dshHome, trashDir, sessionsDir, archived[], pinned[], trashed[], totals{} }` |
|
|
109
|
-
| `archive` | `{ ids, stopActivity }` | `{ results[], ...snapshot }` |
|
|
110
|
-
| `unarchive` / `pin` / `unpin` | `{ ids }` | `{ results[], ...snapshot }` |
|
|
111
|
-
| `trash` | `{ ids }` | `{ results[], ...snapshot }` |
|
|
112
|
-
| `restore` | `{ ids }` | `{ results[], ...snapshot }` |
|
|
113
|
-
| `purge` | `{ ids }` | `{ results[], ...snapshot }` |
|
|
114
|
-
|
|
115
|
-
请求体是 Connection 的线协议信封
|
|
116
|
-
`{ type:'client-request', rpcId, method:'session-curator/<endpoint>', payload }`;
|
|
117
|
-
`method` 与路由不符或信封坏掉时回 **200 + 错误信封**(错误码 `session-curator/*`),不抛传输异常。
|
|
118
|
-
`results[]` 是逐项 `{ sessionId, ok }` 或 `{ sessionId, ok:false, reason }`,单项失败不影响其余项。
|
|
119
|
-
`ids` 只接受字符串数组,其余按空数组处理。
|
|
120
|
-
|
|
121
|
-
> 排障:命令行裸探 `/api/*` 一律 401(信任栅栏先于路由匹配),**分辨不出**路由是否存在;
|
|
122
|
-
> 路由在不在只能在已登录的页面里问(README 英文版的"Troubleshooting"一节有控制台片段)。
|
|
123
|
-
|
|
124
|
-
## 自检(不需要 DSH 在跑)
|
|
125
|
-
|
|
126
|
-
```powershell
|
|
127
|
-
npm test # = 下面三条按顺序跑
|
|
128
|
-
node scripts/smoke.mjs # 领域逻辑 + 线协议(假 ctx + 真 Request + 真文件系统)
|
|
129
|
-
node scripts/activation.mjs # 宿主半区激活(真 cordis,从 app.asar 抽出)+ manifest 元数据
|
|
130
|
-
node scripts/client.mjs # 客户端半区:分组纯逻辑 + 迷你 React 渲染设置面板
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
本机实测:`client.mjs` **59/59**、`smoke.mjs` **14/14**、`activation.mjs` **17/17**。
|
|
134
|
-
`activation.mjs` 找不到 `app.asar` 时打印 SKIP 退出,可用 `$env:DSH_APP_ASAR` 指定。
|
|
135
|
-
|
|
136
|
-
## 目录结构
|
|
137
|
-
|
|
138
|
-
```
|
|
139
|
-
dsh-session-curator/
|
|
140
|
-
├── package.json # dsh.bundle.patch / dsh.client / icon / locale exports
|
|
141
|
-
├── cordis.patch.yml # 纯 - insert 形状,可被 dshmarket 热挂载
|
|
142
|
-
├── icon.svg # 插件插画(36 网格、自带着色,卡片用 <img> 渲染)
|
|
143
|
-
├── locale/{zh,en}.json # 卡片标题 / 描述
|
|
144
|
-
├── lib/index.js # 宿主半区:文件级软删除 + 归档管理 + /api 精确 Fetch 路由
|
|
145
|
-
├── lib/client.js # 客户端半区:菜单项 + 确认弹窗 + 设置面板
|
|
146
|
-
└── scripts/*.mjs # 三个自检
|
|
147
|
-
```
|
|
148
|
-
|
|
149
|
-
## 名字的历史
|
|
150
|
-
|
|
151
|
-
原名 `dsh-session-manager`,在 npm 上已被占用(lesterq 的 0.6.2,同赛道且功能更多;
|
|
152
|
-
`dsh-session-hub` / `dsh-sessions` / `dsh-session-trash` / `dsh-session-organizer` 也都已被占),
|
|
153
|
-
因此正式发布前改名为 **`dsh-session-curator`**:包名、`cordis.patch.yml` 的 `id`/`name`、
|
|
154
|
-
RPC 路由前缀、错误码、locale NS、客户端模块 id、slot id 全部用新名。
|
|
155
|
-
|
|
156
|
-
两处**故意保留旧名**:回收站目录 `<DSH_HOME>/storages/session-manager/trash`(有真实数据),
|
|
157
|
-
以及历史报错原文 `transport failure for /session-manager/trash: HTTP 405`(改了就不真实了)。
|
|
158
|
-
|
|
159
|
-
## 兼容性
|
|
160
|
-
|
|
161
|
-
```json
|
|
162
|
-
"engines": { "node": ">=20", "dsh": ">=0.2.0-rc.2 <0.2.1-0" }
|
|
163
|
-
```
|
|
164
|
-
|
|
165
|
-
只在实测过的宿主版本上声明兼容;peer 只列真绑定到的接口面,且**全部 `optional: true`**
|
|
166
|
-
(这些 `@deepseek-ai/*` 由运行时注入、从不发 npm,不标 optional 会让 pnpm 去 registry 找它们并 404)。
|
|
167
|
-
只在 Windows 上实测过,所以没写 `os` 限制。
|
|
168
|
-
|
|
169
|
-
## License
|
|
43
|
+
## 许可
|
|
170
44
|
|
|
171
45
|
[MIT](LICENSE)
|