dsh-recall-plugin 2.4.7 → 2.4.9
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/CHANGELOG.md +30 -0
- package/README.en.md +117 -36
- package/README.md +121 -42
- package/lib/client.js +54 -0
- package/lib/index.js +4 -1
- package/lib/routes-core.js +67 -1
- package/package.json +11 -10
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,36 @@
|
|
|
2
2
|
|
|
3
3
|
本文件格式遵循 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/),版本号遵循语义化版本。
|
|
4
4
|
|
|
5
|
+
## [2.4.9] - 2026-10-03
|
|
6
|
+
|
|
7
|
+
### 变更
|
|
8
|
+
|
|
9
|
+
- **dsh 0.2.1-alpha.1 兼容**(升级核查见 [docs/upgrade-assessments/dsh-0.2.1-alpha.1.md](docs/upgrade-assessments/dsh-0.2.1-alpha.1.md) 与 [docs/compat-audit.md](docs/compat-audit.md) 头部 0.2.1-alpha.1 段):全树内容级 diff(0.2.0-rc.2 整包残留作基线)确认插件注册/消费契约(chat.node 槽位与 props、`ChatNodeKind` 全集、`sessions.fork`/binding、`updateQueue`、设置双 slot)字节级相同——**无需改码**;peer 窗口 6 条 `dsh-*` 追加 `>=0.2.1-alpha.1 <0.3.0` 段(npm prerelease 门槛要求显式同 tuple 段;上限按发版决定放宽,覆盖 0.2 全线正式版),cordis 追加 `>=4.0.5-alpha.1 <4.0.6`、schemastery 追加 `>=3.18.5-alpha.1 <3.18.6`(两者产物除版本号外逐字节相同),`dsh.compatibility.dshReleases` 补 `0.2.1-alpha.1: compatible`;`docs/reference/` 镜像按 tag 重拉(13 源中 4 份实质差异:反向代理发布链接、桌面端系统分配端口 + invariant 移除文案、工具 delta 匹配语义与 `ConversationNodeDefinitionInput`、定时任务投递语义)。正向受益:官方修复插件启停时样式被误移除(`claimStyles`)与队列滞留(惠及 G1 周边时序)。门禁:`test:probe` 52/52、`verify:host` 装配断言全过、`check:dsh` 全绿。
|
|
10
|
+
|
|
11
|
+
### 文档
|
|
12
|
+
|
|
13
|
+
- README 双语:兼容范围标注与 peer 窗口折叠块同步至 dsh 0.2.1-alpha.1(窗口上限放宽至 `<0.3.0` 的说明一并落地)。
|
|
14
|
+
- `docs/dsh-contract.md`:版本核验记录只保留最新(0.2.1-alpha.1),历次版本记录归口 compat-audit 与 upgrade-assessments,消除双写。
|
|
15
|
+
- 新增待实施计划 `docs/plans/pending/plan-refill-references.md`:引用 chip 回填(撤回后把被撤回消息里的 @文件/目录引用以可点击 chip 形式放回输入框;依赖 dsh 0.2.1-alpha.1 结构化草稿能力)。
|
|
16
|
+
|
|
17
|
+
## [2.4.8] - 2026-10-03
|
|
18
|
+
|
|
19
|
+
撤回完成事件/回调(issue #19,方案见 [docs/plans/completed/plan-recall-event.md](docs/plans/completed/plan-recall-event.md)):撤回到达终态时广播 cordis 事件 `dsh-recall/complete` / `dsh-recall/failed`,供同宿主任意插件监听(如长时记忆插件清理被撤回回合的记忆)。纯增量 opt-in:不订阅的插件零感知,撤回链路行为逐字节不变(事件发送失败、监听器抛错、下游慢执行均不影响主流程)。
|
|
20
|
+
|
|
21
|
+
### 新增
|
|
22
|
+
|
|
23
|
+
- **事件契约(`src/types/events.ts`,单一事实源)**:`dsh-recall/complete`(`sessionId`/`childSessionId`/`scope`/`cutSeq`/`messageId`/`root`/`count`/`chatReverted`/`archiveRequested`/`time`,`version: 1`)与 `dsh-recall/failed`(`stage: 'execute' | 'fork'`/`sessionId`/`messageId`/`scope`/`cutSeq`/`root`/`code?`/`error`/`time`)。首发即稳定公共契约(非 experimental):新增字段走 minor,破坏性变更走 major 且 `version` 升 2。payload 不含消息正文;事件名与契约文件的绑定由 `tests/types/events-contract.test.ts` 编译期双向断言维持(host 产物因 build-host 逐文件转译不覆盖 `types/`,事件名在 routes-core 本地字面量声明)。
|
|
24
|
+
- **host `notify` 端点(routes-core.ts)**:client 撤回终态上报的接缝——校验(缺 `status`/`sessionId`/`messageId` 复用 `RECALL_BAD_TYPE`,不加新错误码)→ `resolveRoot` 尽力 enrich(失败落 `null` 不阻断)→ `queueMicrotask` + try/catch 调 `ctx.emit`(下游抛错/慢执行不反噬端点)→ 恒 `{ ok: true }`。不进串行队列(零 git 操作)、不依赖 store(避开 lineage-record 的 NO_STORE 早退会吞事件的缺陷);index.ts 以闭包注入 `emitEvent: (e, p) => ctx.emit(e, p)` 保持 ctx 不解构纪律。verify-host 端点白名单同步至 13 项(未同步时两处断言红灯,反向验证实测)。
|
|
25
|
+
- **client 终态上报(recall-node.ts + util.ts)**:`buildRecallNotify` 纯函数按语义决策表组装载荷(六场景矩阵单测直钉),`executeRecall` 五处上报点 fire-and-forget(`.catch` 静默)——execute 拒(透传 `code`)/ execute 抛 → `failed(stage:'execute')`;fork 抛错/返空 → `failed(stage:'fork')`(**绝不发 complete**,防下游误清该回合记忆);撤回链完整走完 → `complete`(`chatReverted` 为对话是否真回退的判据)。STALE 自动重预览是中间态,不上报。版本错位容错:旧 host 无此端点时上报静默失败(404),撤回不受影响;旧版 client 从不上报。
|
|
26
|
+
- **README 双语「事件契约」章节**:事件名、payload 字段表、语义要点(STALE 不发、fork 失败只发 failed、`(sessionId, messageId, cutSeq)` 三元组幂等、隐私面)、semver 演进规则与版本错位矩阵、可直接粘贴的监听示例。
|
|
27
|
+
|
|
28
|
+
### 测试
|
|
29
|
+
|
|
30
|
+
- `tests/unit/routes-notify.test.js`(11 例):参数校验缺失码 / root enrich 成功·抛错·返空 / emit 异步广播与 payload 字段 / 监听器抛错仍恒 `ok:true` 且 `recordError` 留痕 / `stage`-`code` 语义。
|
|
31
|
+
- `tests/unit/recall-notify.test.js`(8 例):`buildRecallNotify` 规格第一节六场景 + scope 透传与字段兜底。
|
|
32
|
+
- `tests/client/recall-node.test.ts` 追加 7 例:五处接线点的调用时机与载荷(含 STALE 不发 notify、上报失败静默不反噬主链)。
|
|
33
|
+
- `tests/types/events-contract.test.ts`:事件名字面量钉死、payload 形状断言、host 本地常量 ↔ 契约常量双向绑定、notify 端点返回类型双向绑定。基线 478 → 497(39 文件),client 83 → 90。
|
|
34
|
+
|
|
5
35
|
## [2.4.7] - 2026-09-30
|
|
6
36
|
|
|
7
37
|
质量加固专项批次([docs/plans/completed/plan-quality-hardening.md](docs/plans/completed/plan-quality-hardening.md) A1–A8,八项分四波:质量基建 → 数据安全 → 测试基建 → i18n)。2026-09-30 该计划连同 `plan-warmup-unhandled-rejection` 已归档到 `docs/plans/completed/`(活体冒烟第九节 R-1〜R-6 全过)。
|
package/README.en.md
CHANGED
|
@@ -7,13 +7,12 @@
|
|
|
7
7
|

|
|
8
8
|

|
|
9
9
|
|
|
10
|
-
[](https://github.com/deepseek-ai/deepseek-harness/releases/tag/dsh-v0.2.1-alpha.1)
|
|
11
11
|

|
|
12
|
-
|
|
12
|
+
|
|
13
13
|
Under any message you've sent, click "↶ Recall" — **your workspace files and the conversation history roll back to just before that message was sent**.
|
|
14
|
-
---
|
|
15
14
|
|
|
16
|
-
|
|
15
|
+
As each message is sent, the workspace is first snapshotted into an independent shadow git repository (your project's own git is never touched); a recall restores files from that snapshot and rewinds the conversation through DSH's official `sessions.fork` to the turn boundary before the message, with the original session archived and recoverable. The recalled message's text and attachments are placed back into the input box, ready to edit and resend. Snapshot storage lives under `$DSH_HOME` by default. The main boundary: snapshots are created only **when a message is sent** — messages from before the plugin was enabled have no snapshot and show no recall button.
|
|
17
16
|
|
|
18
17
|
[Changelog](CHANGELOG.md)
|
|
19
18
|
|
|
@@ -27,6 +26,7 @@ Files and conversation roll back together: as each message is sent, the workspac
|
|
|
27
26
|
- [Configuration](#configuration)
|
|
28
27
|
- [Snapshot Maintenance & Cleanup](#snapshot-maintenance--cleanup)
|
|
29
28
|
- [How It Works](#how-it-works)
|
|
29
|
+
- [Event Contract (for same-host plugins)](#event-contract-for-same-host-plugins)
|
|
30
30
|
- [Local Development (without publishing)](#local-development-without-publishing)
|
|
31
31
|
- [License](#license)
|
|
32
32
|
|
|
@@ -45,26 +45,26 @@ Files and conversation roll back together: as each message is sent, the workspac
|
|
|
45
45
|
Abilities with a version in parentheses require that version or later; the rest have no special version requirement.
|
|
46
46
|
|
|
47
47
|
- **Files + conversation, rolled back together**: recalling isn't just about chat history — files the agent modified go back to their original state too; immune to your project's `.gitattributes` conversion, with byte-level fidelity for line endings and binary content (2.1.1+).
|
|
48
|
-
- **Just want a fresh conversation? Leave the files alone** (2.3.24+): the confirmation panel offers a recall-scope choice — the default "Roll back files & conversation" is the full rollback;
|
|
48
|
+
- **Just want a fresh conversation? Leave the files alone** (2.3.24+): the confirmation panel offers a recall-scope choice — the default "Roll back files & conversation" is the full rollback; "Conversation only" keeps your project files byte-for-byte untouched (no safety snapshot either), ideal when you dislike the reply but the file changes are exactly what you want.
|
|
49
49
|
- **Never touches your project's own git, and keeps it clean**: snapshots live in an independent shadow git repository — branches, staging area, and uncommitted changes are untouched; storage stays under `$DSH_HOME` regardless of the session's sandbox permission (workspace-write / read-only sessions work as usual), falling back to an in-project `.dsh-recall-snapshots` only when home itself is unwritable.
|
|
50
|
-
- **See the list before you act — and change your mind as often as you like**: recall first shows the list of files that will change (modified / restored / deleted); nothing runs until you confirm. After a recall you can recall again to an even earlier point, and files overwritten during a recall always remain recoverable
|
|
51
|
-
- **Resend right after a recall** (2.3.15+):
|
|
52
|
-
- **A guarded recall path** (2.0+; auto-rescue 2.1+):
|
|
53
|
-
- **Disk-friendly, self-maintaining**: snapshots use git delta compression and large files are skipped automatically (threshold configurable); periodic lossless `git gc`, session-deletion cleanup, and
|
|
50
|
+
- **See the list before you act — and change your mind as often as you like**: recall first shows the list of files that will change (modified / restored / deleted); nothing runs until you confirm. After a recall you can recall again to an even earlier point, and files overwritten during a recall always remain recoverable.
|
|
51
|
+
- **Resend right after a recall** (2.3.15+): the message's text and attachments (images, files) return to the input box, so you can edit and send again without picking the attachments over.
|
|
52
|
+
- **A guarded recall path** (2.0+; auto-rescue 2.1+): a new snapshot after the preview forces a fresh preview; a "pre-rollback" safety snapshot is taken before every recall, a failed rollback is rescued automatically, and a failed rescue gives you a copy-paste-ready manual recovery command.
|
|
53
|
+
- **Disk-friendly, self-maintaining**: snapshots use git delta compression and large files are skipped automatically (threshold configurable); periodic lossless `git gc`, session-deletion cleanup, and cleanup by cap or retention age — plus a **workspace → session → snapshot** tree manager on the settings page with search and per-level deletion.
|
|
54
54
|
- **Failures speak up and heal** (self-healing 2.1+): failures are classified by root cause (git missing / disk full / permission denied / lock conflict / directory conflict) with actionable guidance (the same fault only bothers you once per 10 minutes; reasons land in the settings card's "Recent errors"); leftover objects are pruned, 3 consecutive failures trigger exponential backoff, concurrent instances yield via heartbeats, and unindexable paths are skipped with a notice instead of failing the whole snapshot (they are neither restored nor deleted on recall).
|
|
55
55
|
|
|
56
56
|
## Known Limitations
|
|
57
57
|
|
|
58
58
|
Boundaries accepted by design and edge cases not yet covered — worth checking before you rely on them.
|
|
59
59
|
|
|
60
|
-
- Snapshots are created **when a message is sent
|
|
60
|
+
- Snapshots are created **when a message is sent**: messages from before the plugin was enabled have no snapshot and show no recall button.
|
|
61
61
|
- Snapshots are **best-effort**: there is a 0.5–1.5 second window between the message arriving and `git add` (on Windows, a single PowerShell start alone costs about 0.4s). If a trivial task finishes editing files inside that window, the snapshot captures the turn's own changes — the file rollback then becomes a no-op (the preview panel reports "0 files will change") while the conversation rollback is unaffected.
|
|
62
62
|
- The first user message of a session cannot roll back the conversation (files only), because fork requires an earlier turn boundary.
|
|
63
63
|
- Recall cannot be initiated while an agent is running in the target workspace (by design — stop the agent first).
|
|
64
64
|
- Supports Windows (PowerShell 5.1/7 + git CLI) and Linux/macOS (bash + git CLI). Windows is thoroughly verified on real machines; Linux has been fully tested on WSL2 (Ubuntu 26.04, bash 5.3 + git 2.53), including Chinese paths, home fallback, session cleanup, and gc; the macOS side is written to be bash 3.2 compatible but has not been tested on real hardware yet.
|
|
65
|
-
- Nested git repositories inside the workspace (subdirectories with their own `.git`) cannot be indexed: the snapshot proceeds for everything else (fail-open, with a
|
|
65
|
+
- Nested git repositories inside the workspace (subdirectories with their own `.git`) cannot be indexed: the snapshot proceeds for everything else (fail-open, with a notice listing the skipped paths), but their contents do not participate in recalls.
|
|
66
66
|
- Extreme cases like filenames containing newlines/TAB are beyond the diff list's parsing capability (negligible probability).
|
|
67
|
-
- **Interplay with dsh-routing-suite (progressive tool-disclosure router)**: if you also run
|
|
67
|
+
- **Interplay with dsh-routing-suite (progressive tool-disclosure router)**: if you also run its router-standard preset, a recall forks a new session via `sessions.fork`, which resets the router's stage to its default (the tool surface temporarily narrows). Symptoms, root cause and the fix are documented in [docs/routing-interplay.md](docs/routing-interplay.md) (Chinese).
|
|
68
68
|
|
|
69
69
|
## Installation
|
|
70
70
|
|
|
@@ -72,35 +72,59 @@ Prerequisites:
|
|
|
72
72
|
|
|
73
73
|
- git CLI: without it the recall button won't appear (a notice shows at the top of the page); DSH itself keeps running.
|
|
74
74
|
- Shell: PowerShell 5.1 / 7 on Windows; bash on Linux/macOS.
|
|
75
|
-
- DSH version: `0.1.2-alpha.1` through `0.2.
|
|
76
|
-
- **0.1.7-alpha.1 is a breaking release** (
|
|
75
|
+
- DSH version: `0.1.2-alpha.1` through `0.2.1-alpha.1`; later versions within a verified minor line are admitted automatically, while unverified new minor lines are blocked by the startup compatibility gate (window mechanics in the fold below).
|
|
76
|
+
- **0.1.7-alpha.1 is a breaking release** (the shell execution and settings interfaces changed); the plugin ships both seams side by side — the same release works on every 0.1.2–0.1.6 line and on 0.1.7+, with unchanged behaviour on older DSH versions.
|
|
77
77
|
- `0.1.1-rc.2` and earlier are not supported: their client runtime lacks the `sessions`/`workspaces`/`uiWorkspace` services, so the plugin UI silently fails to render.
|
|
78
78
|
|
|
79
|
-
|
|
79
|
+
<details>
|
|
80
|
+
<summary>peer window mechanics (read before upgrading dsh)</summary>
|
|
81
|
+
|
|
82
|
+
peerDependencies open a window per minor line, consistent with the `dsh.compatibility.dshReleases` declaration:
|
|
80
83
|
|
|
81
|
-
- Official DSH plugin command: install and auto-mount into the web profile
|
|
82
|
-
```powershell
|
|
83
|
-
dsh plugin --profile web add dsh-recall-plugin
|
|
84
|
-
```
|
|
85
|
-
- Or install directly from git:
|
|
86
|
-
```powershell
|
|
87
|
-
dsh plugin --profile web add github:limbo947/dsh-recall-plugin
|
|
88
84
|
```
|
|
89
|
-
-
|
|
90
|
-
```powershell
|
|
91
|
-
dsh web # run in the foreground
|
|
92
|
-
pm2 restart <your-dsh-name> # if managed by pm2
|
|
85
|
+
>=0.1.2-alpha.1 <0.1.3 || >=0.1.3-alpha.1 <0.1.4 || >=0.1.5-alpha.1 <0.1.6 || >=0.1.6-alpha.1 <0.1.7 || >=0.1.7-alpha.1 <0.1.8 || >=0.2.0-rc.1 <0.2.1 || >=0.2.1-alpha.1 <0.3.0
|
|
93
86
|
```
|
|
94
87
|
|
|
95
|
-
|
|
88
|
+
Each line is anchored at its first verified version with an upper bound at the next minor (the 0.2.1 window's upper bound is widened to `<0.3.0`, admitting every 0.2 release), so later prereleases/releases within a verified line are admitted without touching the peer declaration. Both the 0.2.0 and 0.2.1 lines need their own tuple: npm's prerelease gate only admits a prerelease from a comparator with the same tuple, so the existing `<0.1.8` segment cannot admit `0.2.0-rc.1`, nor `<0.2.1` admit `0.2.1-alpha.1` (prereleases of later tuples such as 0.2.2 obey the same gate and need their own segment once verified).
|
|
89
|
+
|
|
90
|
+
</details>
|
|
91
|
+
|
|
92
|
+
Install & verify:
|
|
93
|
+
|
|
94
|
+
- Official DSH plugin command (pick the profile for your frontend); installs and auto-mounts into that profile:
|
|
95
|
+
|
|
96
|
+
```powershell
|
|
97
|
+
dsh plugin --profile web add dsh-recall-plugin # Web UI
|
|
98
|
+
dsh plugin --profile desktop add dsh-recall-plugin # Desktop
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
- Or install directly from git:
|
|
102
|
+
|
|
103
|
+
```powershell
|
|
104
|
+
dsh plugin --profile web add github:limbo947/dsh-recall-plugin
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
- Restart the DSH process (pick whichever matches how you start it):
|
|
108
|
+
|
|
109
|
+
```powershell
|
|
110
|
+
dsh web # run in the foreground
|
|
111
|
+
pm2 restart <your-dsh-name> # if managed by pm2
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
- **Verify**: after restarting, hard-refresh the page (Ctrl+Shift+R) and hover over any user message sent after the plugin was enabled — the "↶" appearing next to the copy button means it works. No button? Nine times out of ten the DSH process wasn't restarted, or git CLI isn't on PATH.
|
|
115
|
+
- **Uninstall** (removes both the dependency and the mount layer):
|
|
96
116
|
|
|
97
|
-
|
|
117
|
+
```powershell
|
|
118
|
+
dsh plugin --profile web remove dsh-recall-plugin
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Snapshot data is kept under `dsh-recall-snapshots/` in home; delete that directory manually if you want it fully gone.
|
|
98
122
|
|
|
99
123
|
## Usage
|
|
100
124
|
|
|
101
|
-
A full recall of one user message sent after the plugin was enabled goes like this
|
|
125
|
+
A full recall of one user message sent after the plugin was enabled goes like this:
|
|
102
126
|
|
|
103
|
-
1. Hover over
|
|
127
|
+
1. Hover over the message (including steering messages inserted while the agent is running) — "↶ Recall" appears to the left of the copy button.
|
|
104
128
|
2. Click it → the confirmation panel shows the list of files that will change (modified / restored / deleted), plus a scope choice: "Roll back files & conversation" (default) or "Conversation only" (files stay as they are).
|
|
105
129
|
3. Click "Confirm rollback" (or "Confirm conversation recall") → files are restored to their state before that message was sent (files are untouched in conversation-only mode); the view switches to a new session (that message and everything after it is removed), while the original session is archived and can be recovered anytime.
|
|
106
130
|
|
|
@@ -127,14 +151,14 @@ The settings card also offers "Restore defaults" (one-click reset of all fields)
|
|
|
127
151
|
|
|
128
152
|
Every UI surface the plugin draws itself (recall button and confirmation panel, toasts, the three settings cards, the snapshot manager tree, recent errors) is bilingual and driven by `locale` — switch it in the "Interface" group of the config card; saving applies immediately (the settings page switches at once, chat-side wording follows on the next page reload).
|
|
129
153
|
|
|
130
|
-
Two explicit
|
|
154
|
+
Two explicit limits: (1) field descriptions rendered by the host's own settings form (i.e. the `Schema.description()` texts) stay in Chinese — the plugin cannot localize those; the plugin's own config card is unaffected. (2) Host-side errors that carry dynamic detail (a failed rollback's rescue outcome, a specific validation reason, a raw exception) are shown verbatim in the panel and "Recent errors" instead of a localized short phrase — we prefer mixed-language output over swallowing the detail you need to troubleshoot.
|
|
131
155
|
|
|
132
156
|
## Snapshot Maintenance & Cleanup
|
|
133
157
|
|
|
134
158
|
The plugin manages disk usage automatically — no manual housekeeping needed:
|
|
135
159
|
|
|
136
|
-
- **Periodic gc**: every 50 snapshots or 24 hours since the last gc (whichever comes first, thresholds configurable), `git gc` runs in the background to pack loose objects
|
|
137
|
-
- **Cap & retention**: up to 500 snapshots per workspace by default (oldest pruned beyond the cap)
|
|
160
|
+
- **Periodic gc**: every 50 snapshots or 24 hours since the last gc (whichever comes first, thresholds configurable), `git gc` runs in the background to pack loose objects — lossless, every snapshot remains recallable. The throttle token lives in `gc.stamp` inside the shadow repository, so restarting DSH does not reset the cycle.
|
|
161
|
+
- **Cap & retention**: up to 500 snapshots per workspace by default (oldest pruned beyond the cap), plus optional age-based retention via `retentionDays`; the two triggers work independently and can both be adjusted or disabled in the config card.
|
|
138
162
|
- **Session-deletion cleanup**: once a session is permanently deleted (its log gone from disk), the next maintenance pass automatically removes all of its snapshots and frees the space. **Archiving is not deletion** — logs of sessions archived by the recall feature itself still exist, so their snapshots are kept and recoverable from the archive. The check is conservative: a session that is merely cold (not in memory) is never cleaned, and when the log's state cannot be verified, it is left alone.
|
|
139
163
|
- **User-defined exclusions**: open the "**Settings → Plugin Config → Recall Plugin**" card (collapsed by default; click the header to expand) to edit snapshot exclusions visually — type a path or pattern and press Enter to add it, one-click append for common patterns (`dist/`, `*.log`, `.env`, …), and saved changes take effect on the very next snapshot/recall, no restart needed. Alternatively, edit `$DSH_HOME/dsh-recall-snapshots/exclude.txt` directly (or `~/.dsh/dsh-recall-snapshots/exclude.txt` when unset; UTF-8): one gitignore-style pattern per line, lines starting with `#` are comments — both paths edit the same configuration. For example:
|
|
140
164
|
|
|
@@ -146,7 +170,7 @@ The plugin manages disk usage automatically — no manual housekeeping needed:
|
|
|
146
170
|
```
|
|
147
171
|
|
|
148
172
|
This applies to all projects (when home is unwritable and a workspace falls back to in-project storage, it gets its own independent exclusion config, listed as a separate card in the settings tab). New exclusions only affect future snapshots; **when recalling to an earlier snapshot, files that weren't excluded at that time are still restored** (returning to the state as it was — that's exactly what recall means). To fully purge a directory that already made it into snapshots, manually delete the corresponding hash directory under `dsh-recall-snapshots/` in home.
|
|
149
|
-
- **Tree-view snapshot manager**: open "**Settings → Plugin Config → Recall Plugin → Snapshot Manager**"
|
|
173
|
+
- **Tree-view snapshot manager**: open "**Settings → Plugin Config → Recall Plugin → Snapshot Manager**" for a three-level tree — workspace (folder name) → session (session title, recall chains grouped into version families) → snapshot (time + message content summary, hover for the full content). Search and "load more" are supported; workspace and session nodes expand/collapse; every level has a delete button on its right, with a confirmation before deletion — deleting a workspace clears all of its snapshots, deleting a session clears all of that session's snapshots, deleting a leaf removes just that single snapshot; a confirmed "Delete all" button sits at the top.
|
|
150
174
|
|
|
151
175
|
## How It Works
|
|
152
176
|
|
|
@@ -163,6 +187,63 @@ When each user message is sent (before the agent touches any files), the workspa
|
|
|
163
187
|
|
|
164
188
|
- The format and version-compatibility rules for every file in the store directory (index, recall chain, format marker, intent journal, etc.) live in [docs/format.md](docs/format.md).
|
|
165
189
|
|
|
190
|
+
## Event Contract (for same-host plugins)
|
|
191
|
+
|
|
192
|
+
When a recall reaches its terminal state, the plugin broadcasts two public cordis events (2.4.8+). Any plugin on the same host can subscribe — for example, a long-term-memory plugin can purge memories written by the recalled turn:
|
|
193
|
+
|
|
194
|
+
```js
|
|
195
|
+
// inside a same-host plugin
|
|
196
|
+
ctx.on('dsh-recall/complete', (e) => {
|
|
197
|
+
if (e.chatReverted) memory.purgeTurn(e.root, e.sessionId, e.cutSeq)
|
|
198
|
+
})
|
|
199
|
+
ctx.on('dsh-recall/failed', (e) => {
|
|
200
|
+
if (e.stage === 'fork') log.warn('files reverted but the conversation did not', e)
|
|
201
|
+
})
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
`dsh-recall/complete` — recall succeeded:
|
|
205
|
+
|
|
206
|
+
| Field | Type | Meaning |
|
|
207
|
+
| --- | --- | --- |
|
|
208
|
+
| `version` | `1` | Contract version (see evolution rules below) |
|
|
209
|
+
| `sessionId` | `string` | The original session being recalled |
|
|
210
|
+
| `childSessionId` | `string \| null` | The forked session; `null` for a files-only recall |
|
|
211
|
+
| `scope` | `'both' \| 'session-only'` | Recall scope |
|
|
212
|
+
| `cutSeq` | `number \| null` | Conversation cut point; `null` when the message was the session's first |
|
|
213
|
+
| `messageId` | `string` | ID of the recalled message (snapshot primary key) |
|
|
214
|
+
| `root` | `string \| null` | Workspace root path; `null` if host resolution failed |
|
|
215
|
+
| `count` | `number` | Number of files reverted (always `0` for `session-only`) |
|
|
216
|
+
| `chatReverted` | `boolean` | Whether the conversation actually reverted (downstream gate, see below) |
|
|
217
|
+
| `archiveRequested` | `boolean` | Whether archiving of the original session was initiated (fire-and-forget, not completion) |
|
|
218
|
+
| `time` | `number` | When the host received the report (ms) |
|
|
219
|
+
|
|
220
|
+
`dsh-recall/failed` — recall failed:
|
|
221
|
+
|
|
222
|
+
| Field | Type | Meaning |
|
|
223
|
+
| --- | --- | --- |
|
|
224
|
+
| `version` | `1` | Contract version |
|
|
225
|
+
| `stage` | `'execute' \| 'fork'` | Failure stage: file revert rejected/errored, or files reverted but conversation revert failed |
|
|
226
|
+
| `sessionId` | `string \| null` | The original session being recalled |
|
|
227
|
+
| `messageId` | `string` | ID of the recalled message |
|
|
228
|
+
| `scope` | `'both' \| 'session-only'` | Recall scope |
|
|
229
|
+
| `cutSeq` | `number \| null` | Cut point known to the client (may not have been used) |
|
|
230
|
+
| `root` | `string \| null` | Workspace root path; `null` if resolution failed |
|
|
231
|
+
| `code` | `string?` | Only for `stage='execute'`: host error code (`AGENT_BUSY` / `NO_SNAPSHOT` / …) |
|
|
232
|
+
| `error` | `string` | Error description |
|
|
233
|
+
| `time` | `number` | When the host received the report (ms) |
|
|
234
|
+
|
|
235
|
+
Semantics every consumer should know:
|
|
236
|
+
|
|
237
|
+
- **Only terminal states emit events.** The "preview went stale, auto re-previewing" (STALE) path is an intermediate state and emits nothing; neither does a cancelled/closed panel.
|
|
238
|
+
- **A fork failure emits `failed(stage:'fork')` and never `complete`** — at that point files are reverted but the conversation is not; emitting `complete` would make downstream purge memories of a still-intact conversation. Gate "the conversation actually reverted" on `complete.chatReverted` (a files-only `complete` has `chatReverted:false`).
|
|
239
|
+
- For `failed(stage:'execute')` originating from an exception (rather than a guard rejection), **whether files were reverted is unknown** — treat it conservatively.
|
|
240
|
+
- Retries/repeated recalls may emit multiple events for the same `(sessionId, messageId, cutSeq)` triple; downstream should deduplicate on it.
|
|
241
|
+
- Payloads contain no message bodies — only IDs / seq / paths — and are visible only to plugins on the same host.
|
|
242
|
+
|
|
243
|
+
Versioning: shipped as a stable public contract from day one (not experimental). Additive changes ride minor releases; breaking changes ride a major release with `version` bumped to `2`, so downstream can branch on it.
|
|
244
|
+
|
|
245
|
+
Version skew: an old plugin frontend (<2.4.8) never reports, so events never fire; a new frontend with an old host (<2.4.8) gets a missing endpoint and silently ignores it — recall itself is unaffected.
|
|
246
|
+
|
|
166
247
|
## Local Development (without publishing)
|
|
167
248
|
|
|
168
249
|
Point the profile's dependency for this package at your clone via `link:`; DSH loads the built `lib/` artifacts from the workspace (source lives in `src/`), so run `npm run build` after editing `src/`, then restart DSH — no copying or publishing needed:
|
|
@@ -181,8 +262,8 @@ Note: all source lives in `src/` (host in `src/host/`, browser side in `src/clie
|
|
|
181
262
|
|
|
182
263
|
### Tests
|
|
183
264
|
|
|
184
|
-
- `npm test`: pure-logic unit tests (vitest,
|
|
185
|
-
- `npm run test:client`: client component tests (vitest + jsdom, 6 files /
|
|
265
|
+
- `npm test`: pure-logic unit tests (vitest, 39 files / 497 cases, no DSH dependency, runs identically in CI and locally) — config parsing, snapshot parsers, rescue orchestration, error classification, the disk-format guard, the recall intent journal, recall terminal-event payload assembly, i18n dictionaries plus a missing-key scan, script-template same-name-export contract, shell execution dual-channel split and failure grading, settings bridge across both generations, client pure functions, published-package layout, snapshot index persistence, storage caps and retention, etc.;
|
|
266
|
+
- `npm run test:client`: client component tests (vitest + jsdom, 6 files / 90 cases, runs in CI) — the recall node main chain (preview→execute→fork→refill) and terminal-event reporting, snapshot-manager tree and delete flows, config/exclude card error paths, the logger switch matrix, and the zh/en rendering chains; assertions pin behaviour and structure only (className / aria / request payloads), never literal copy;
|
|
186
267
|
- `npm run test:probe`: official-API field probes (requires a local dsh installation; **must run after any dsh upgrade**) — pins fields like `renderMessageImages`/`node`/`cwd`, `atSeq`/`increaseTitle` and the fork-cut anchors of `sessions.fork`, `listSessions` record shape, `AgentRegistry`, the shell execution seam (`execute`/`ShellExecution.result`), the settings surface (`SettingsForms`/profile entry id/volatile gate) plus `loader/volatile-update` and the `Fiber.entry` shape, and goes red on violation;
|
|
187
268
|
- `npm run verify:host`: assembly gate (requires a local dsh installation) — boots the plugin with a real cordis context in two passes (legacy settings stub and a modern-face-only stub), asserting inject declarations, endpoint registration, Config schema, teardown cleanliness and settings-face dispatch, catching assembly regressions before release;
|
|
188
269
|
- `npm run build`: full host+client build (mandatory after any `src/` change); `npm run check:dsh`: dsh version inspection (pre-release).
|
package/README.md
CHANGED
|
@@ -5,12 +5,11 @@
|
|
|
5
5
|
[](https://www.npmjs.com/package/dsh-recall-plugin)
|
|
6
6
|
[](https://www.npmjs.com/package/dsh-recall-plugin)
|
|
7
7
|

|
|
8
|
-
[](https://github.com/deepseek-ai/deepseek-harness/releases/tag/dsh-v0.2.1-alpha.1)
|
|
9
|
+
|
|
10
10
|
在任意一条你发过的消息下方点「↶ 撤回」——**工作区文件和对话历史一起回到那条消息发出之前的状态**。
|
|
11
|
-
---
|
|
12
11
|
|
|
13
|
-
|
|
12
|
+
每条消息发送时,工作区先被快照进一个独立的影子 git 仓库(不碰项目自身的 git);撤回时按快照恢复文件,并经 DSH 官方 `sessions.fork` 把对话切回该消息之前的 turn 边界,原会话归档保留、随时可找回。被撤回消息的文本与附件自动放回输入框,改完即可重发。快照存储默认落在 `$DSH_HOME` 下。最主要的边界:快照只在**消息发送时**创建,插件启用前的历史消息没有快照、不显示撤回按钮。
|
|
14
13
|
|
|
15
14
|
[更新日志](CHANGELOG.md)
|
|
16
15
|
|
|
@@ -24,6 +23,7 @@
|
|
|
24
23
|
- [配置项](#配置项)
|
|
25
24
|
- [快照维护与清理](#快照维护与清理)
|
|
26
25
|
- [工作原理](#工作原理)
|
|
26
|
+
- [事件契约(供同宿主插件监听)](#事件契约供同宿主插件监听)
|
|
27
27
|
- [本地开发(无需发布)](#本地开发无需发布)
|
|
28
28
|
- [License](#license)
|
|
29
29
|
|
|
@@ -42,26 +42,26 @@
|
|
|
42
42
|
带括号版本号的能力需要该版本或更高;其余无特殊版本要求。
|
|
43
43
|
|
|
44
44
|
- **文件 + 对话,整段回退**:撤回的不只是聊天记录,agent 改过的文件也一并回到原样;不受项目 `.gitattributes` 转换影响,换行与二进制内容字节级保真(2.1.1+)。
|
|
45
|
-
- **只想重来对话?文件可以不动**(2.3.24
|
|
46
|
-
-
|
|
47
|
-
- **先看清单再动手,可反复后悔**:撤回前展示将变更的文件清单(修改 / 恢复 /
|
|
48
|
-
- **撤回完就能重发**(2.3.15
|
|
49
|
-
- **撤回全程有防护**(2.0+,救援 2.1
|
|
50
|
-
- **磁盘友好、自动维护**:快照走 git delta 增量压缩,大文件自动跳过(阈值可配);定期无损 `git gc
|
|
51
|
-
- **失败不静默、能自愈**(自愈 2.1+):失败按根因分类(git 缺失 / 磁盘满 / 无权限 / 锁冲突 / 目录冲突)给出可行动提示,同类故障 10
|
|
45
|
+
- **只想重来对话?文件可以不动**(2.3.24+):确认面板可二选一撤回范围——默认「回退文件与对话」为完整回退;「仅撤回对话」则项目文件一个字节都不动(也不打安全快照),适合对回复不满意、但文件改动正是想要内容的场景。
|
|
46
|
+
- **不碰项目自身的 git,目录保持干净**:快照存在独立的影子 git 仓库,分支、暂存区、未提交改动统统不受影响;存储默认落在 `$DSH_HOME` 下、与会话沙箱权限无关(workspace-write / read-only 照常工作),仅当 home 不可写才降级到项目内 `.dsh-recall-snapshots`。
|
|
47
|
+
- **先看清单再动手,可反复后悔**:撤回前展示将变更的文件清单(修改 / 恢复 / 删除),确认后才执行;撤回后还能再撤到更早,被覆盖的文件一直找得回来。
|
|
48
|
+
- **撤回完就能重发**(2.3.15+):消息的文本与附件(图片、文件)一起放回输入框,改完直接发送,不必重新挑一遍附件。
|
|
49
|
+
- **撤回全程有防护**(2.0+,救援 2.1+):预览后出现新快照会强制重新预览;执行前自动打「回退前」安全快照,回退失败自动救援,救援失败给出可直接复制执行的手动恢复命令。
|
|
50
|
+
- **磁盘友好、自动维护**:快照走 git delta 增量压缩,大文件自动跳过(阈值可配);定期无损 `git gc`、会话删除联动清理、按条数与保留天数自动清理;设置页提供**工作区 → 会话 → 快照**三级树形管理,支持搜索与分级删除。
|
|
51
|
+
- **失败不静默、能自愈**(自愈 2.1+):失败按根因分类(git 缺失 / 磁盘满 / 无权限 / 锁冲突 / 目录冲突)给出可行动提示,同类故障 10 分钟只打扰一次,原因进设置卡片「最近错误」;自动清理残骸、连续 3 次失败指数退避、多实例按心跳互让;无法索引的路径跳过并告知,不中断整条快照(撤回时也不触碰它们)。
|
|
52
52
|
|
|
53
53
|
## 已知限制
|
|
54
54
|
|
|
55
55
|
设计上接受的边界与尚未覆盖的极端情形,使用前值得先确认。
|
|
56
56
|
|
|
57
|
-
-
|
|
58
|
-
- 快照是**尽力捕获**:从收到消息到 `git add` 之间有约 0.5–1.5 秒窗口(Windows 上仅一次 PowerShell 启动就占约 0.4
|
|
57
|
+
- 快照在**消息发送时**创建:插件启用前的历史消息没有快照,不显示撤回按钮。
|
|
58
|
+
- 快照是**尽力捕获**:从收到消息到 `git add` 之间有约 0.5–1.5 秒窗口(Windows 上仅一次 PowerShell 启动就占约 0.4 秒)。秒级任务若在这个窗口内改完文件,该快照会连带捕获本轮改动——撤回时文件回退成为空操作(预览面板显示「共 0 个文件将变更」),对话回退不受影响。
|
|
59
59
|
- 会话第一条用户消息无法回退对话(仅文件回退),因为 fork 需要更早的 turn 边界。
|
|
60
60
|
- 目标工作区的 agent 正在运行时无法发起撤回(防护设计,先停止 agent 再撤回)。
|
|
61
61
|
- 支持 Windows(PowerShell 5.1/7 + git CLI)与 Linux/macOS(bash + git CLI)。Windows 真机验证充分;Linux 已在 WSL2(Ubuntu 26.04,bash 5.3 + git 2.53)实测全流程(含中文路径、home 降级、会话清理、gc);macOS 侧脚本按 bash 3.2 兼容编写,尚未真机实测。
|
|
62
|
-
- 工作区内嵌套的其他 git 仓库(子目录自带 `.git
|
|
63
|
-
- 文件名含换行/TAB
|
|
64
|
-
- **与 dsh-routing-suite
|
|
62
|
+
- 工作区内嵌套的其他 git 仓库(子目录自带 `.git`)无法索引:其余部分快照照常(fail-open,页面会提示跳过了哪些路径),但其内容不参与回退。
|
|
63
|
+
- 文件名含换行/TAB 的极端情形超出 diff 清单的解析能力(概率可忽略)。
|
|
64
|
+
- **与 dsh-routing-suite(渐进式工具披露路由)的交互**:同时启用其 router-standard 预设时,撤回经 `sessions.fork` 出新会话会把路由阶段重置为默认(工具面临时收窄)。现象、成因与解决方案见 [docs/routing-interplay.md](docs/routing-interplay.md)。
|
|
65
65
|
|
|
66
66
|
## 安装
|
|
67
67
|
|
|
@@ -69,37 +69,59 @@
|
|
|
69
69
|
|
|
70
70
|
- git CLI:未安装时撤回按钮不出现(页面顶部会提示安装 git),不影响 DSH 运行。
|
|
71
71
|
- shell:Windows 上 PowerShell 5.1 / 7 均可;Linux/macOS 需 bash。
|
|
72
|
-
- DSH 版本:`0.1.2-alpha.1` 至 `0.2.
|
|
73
|
-
- **0.1.7-alpha.1
|
|
72
|
+
- DSH 版本:`0.1.2-alpha.1` 至 `0.2.1-alpha.1`;已核验 minor 线内的后续版本自动放行,未核验的新 minor 线会被启动期兼容门禁拦截(开窗机制见下方折叠块)。
|
|
73
|
+
- **0.1.7-alpha.1 是破坏性版本**(shell 执行与 settings 面接口更换),插件内置双分支共存适配:同一发布同时兼容 0.1.2–0.1.6 各线与 0.1.7+,老版本 DSH 上行为不变。
|
|
74
74
|
- `0.1.1-rc.2` 及更早不支持:那条线的客户端运行时没有 `sessions`/`workspaces`/`uiWorkspace` 服务,插件 UI 会静默不渲染。
|
|
75
75
|
|
|
76
|
-
|
|
76
|
+
<details>
|
|
77
|
+
<summary>peer 版本开窗机制(升级 dsh 前可看)</summary>
|
|
77
78
|
|
|
78
|
-
|
|
79
|
+
peerDependencies 按 minor 版本线开窗,与 `dsh.compatibility.dshReleases` 声明一致:
|
|
79
80
|
|
|
80
|
-
Web UI
|
|
81
|
-
```
|
|
82
|
-
dsh plugin --profile web add dsh-recall-plugin
|
|
83
81
|
```
|
|
84
|
-
|
|
85
|
-
```
|
|
86
|
-
dsh plugin --profile desktop add dsh-recall-plugin
|
|
82
|
+
>=0.1.2-alpha.1 <0.1.3 || >=0.1.3-alpha.1 <0.1.4 || >=0.1.5-alpha.1 <0.1.6 || >=0.1.6-alpha.1 <0.1.7 || >=0.1.7-alpha.1 <0.1.8 || >=0.2.0-rc.1 <0.2.1 || >=0.2.1-alpha.1 <0.3.0
|
|
87
83
|
```
|
|
84
|
+
|
|
85
|
+
每条线以首个核验版本为下限、上界开到下一 minor(0.2.1 段上限放宽到 `<0.3.0`,0.2 全线正式版自动放行),同线内后续 prerelease/正式版自动放行、无需改 peer 声明。0.2.0 与 0.2.1 两线都必须显式开窗:npm 的 prerelease 门槛要求同 tuple 且带 prerelease 的比较器,既有 `<0.1.8` 段放行不了 `0.2.0-rc.1`、`<0.2.1` 段放行不了 `0.2.1-alpha.1`(0.2.2 及以后 tuple 的 prerelease 同样受此门槛约束,待核验后另加段)。
|
|
86
|
+
|
|
87
|
+
</details>
|
|
88
|
+
|
|
89
|
+
安装与验证:
|
|
90
|
+
|
|
91
|
+
- 官方命令安装(按所用前端选 profile),安装后自动挂载进该 profile:
|
|
92
|
+
|
|
93
|
+
```powershell
|
|
94
|
+
dsh plugin --profile web add dsh-recall-plugin # Web UI
|
|
95
|
+
dsh plugin --profile desktop add dsh-recall-plugin # Desktop
|
|
96
|
+
```
|
|
97
|
+
|
|
88
98
|
- 也可从 git 直接安装:
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
99
|
+
|
|
100
|
+
```powershell
|
|
101
|
+
dsh plugin --profile web add github:limbo947/dsh-recall-plugin
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
- 重启 DSH 进程(按你的启动方式选择):
|
|
105
|
+
|
|
106
|
+
```powershell
|
|
107
|
+
dsh web # 前台运行
|
|
108
|
+
pm2 restart <你的-dsh-名称> # pm2 托管时
|
|
109
|
+
```
|
|
92
110
|
|
|
93
111
|
- 验证:重启后硬刷新页面(Ctrl+Shift+R),悬停任意一条插件启用后发送的用户消息——复制按钮旁出现「↶」即生效。没有按钮?九成是没重启 DSH 进程,或 git CLI 不在 PATH 里。
|
|
112
|
+
- 卸载(同时移除依赖与挂载):
|
|
94
113
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
114
|
+
```powershell
|
|
115
|
+
dsh plugin --profile web remove dsh-recall-plugin
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
快照数据保留在 home 下 `dsh-recall-snapshots/`,想彻底清除手动删掉该目录即可。
|
|
99
119
|
|
|
100
120
|
## 使用
|
|
101
121
|
|
|
102
|
-
|
|
122
|
+
对一条插件启用后发送的用户消息做完整撤回,流程如下:
|
|
123
|
+
|
|
124
|
+
1. 鼠标悬停该消息(含 agent 运行中插入的转向指令消息),复制按钮左侧出现「↶ 撤回」。
|
|
103
125
|
2. 点击 → 确认面板展示将变更的文件清单(修改 / 恢复 / 删除),并可选撤回范围:「回退文件与对话」(默认)或「仅撤回对话」(文件保持当前状态)。
|
|
104
126
|
3. 点「确认回退」(或「确认撤回对话」)→ 文件恢复到该消息发送前的状态(仅撤回对话时文件不动);视图切到新会话(该消息及之后的对话移除),原会话归档、随时可找回。
|
|
105
127
|
|
|
@@ -124,17 +146,17 @@ dsh plugin --profile web remove dsh-recall-plugin
|
|
|
124
146
|
|
|
125
147
|
### 界面语言
|
|
126
148
|
|
|
127
|
-
插件自绘的界面(撤回按钮与确认面板、toast 提示、设置卡片三张卡、快照管理树、最近错误)全部双语,由 `locale`
|
|
149
|
+
插件自绘的界面(撤回按钮与确认面板、toast 提示、设置卡片三张卡、快照管理树、最近错误)全部双语,由 `locale` 控制:在配置卡片的「界面」分组下拉切换、保存即生效(设置页立刻切换;聊天页的会话文案在下次刷新页面后跟进)。
|
|
128
150
|
|
|
129
|
-
|
|
151
|
+
两项明示限制:① 宿主官方设置表单自己渲染的字段说明(即 `Schema.description()` 文本)仍是中文,插件无法本地化——插件自绘的配置卡片不受影响;② 带动态细节的宿主侧错误(回退失败的救援结果、具体校验失败原因、原始异常文本)在错误面板与「最近错误」里原样展示宿主返回的中文/英文原文,不套本地化短句——宁可中英混排,也不吞掉排障需要的细节。
|
|
130
152
|
|
|
131
153
|
## 快照维护与清理
|
|
132
154
|
|
|
133
155
|
插件自动控制磁盘占用,无需手动管理:
|
|
134
156
|
|
|
135
|
-
- **定期 gc**:每 50 条快照或距上次 gc 24 小时(先到先触发,阈值可配),后台执行 `git gc` 把 loose 对象压成 pack
|
|
136
|
-
-
|
|
137
|
-
-
|
|
157
|
+
- **定期 gc**:每 50 条快照或距上次 gc 24 小时(先到先触发,阈值可配),后台执行 `git gc` 把 loose 对象压成 pack——无损操作,所有快照照常可回退。节流凭据写在影子仓库内的 `gc.stamp`,重启 DSH 不会重置周期。
|
|
158
|
+
- **条数上限与保留天数**:每工作区默认上限 500 条(超限清最旧),`retentionDays` 按保留天数清理;两者独立触发,都可在配置卡片调整或关闭。
|
|
159
|
+
- **会话删除联动清理**:会话被彻底删除(会话日志从磁盘消失)后,下一次维护会自动删除该会话的全部快照并释放空间。**归档不算删除**——撤回功能归档的原会话日志仍在,快照保留、随时可从归档找回。判断很保守:会话只是冷着(不在内存)不会误清;无法核实日志状态时宁可不清。
|
|
138
160
|
- **用户自定义排除**:打开「**设置 → 插件配置 → 撤回插件**」卡片(默认收起,点卡片头展开)即可可视化编辑快照排除项——输入路径或模式回车即加、常用模式(`dist/`、`*.log`、`.env` 等)一键追加、保存后下一次快照/回退立即生效,无需重启。也可以直接编辑 `$DSH_HOME/dsh-recall-snapshots/exclude.txt`(未设置时为 `~/.dsh/dsh-recall-snapshots/exclude.txt`;UTF-8):一行一条 gitignore 风格 pattern(`#` 开头为注释),两种方式编辑的是同一份配置。例如:
|
|
139
161
|
|
|
140
162
|
```gitignore
|
|
@@ -145,7 +167,7 @@ dsh plugin --profile web remove dsh-recall-plugin
|
|
|
145
167
|
```
|
|
146
168
|
|
|
147
169
|
对所有项目生效(home 不可写而降级到项目内存储时,该工作区有独立的排除配置,设置页会分卡片列出)。新增排除只影响之后的快照;**回退到更早的快照时,当时尚未排除的文件仍会被恢复**(回到当时的状态,这正是回退语义)。想彻底清掉已进快照的目录,可手动删除 home 下 `dsh-recall-snapshots/` 里对应项目的哈希目录。
|
|
148
|
-
- **树形快照管理**:打开「**设置 → 插件配置 → 撤回插件 →
|
|
170
|
+
- **树形快照管理**:打开「**设置 → 插件配置 → 撤回插件 → 快照管理**」可看到三级树——工作区(文件夹名)→ 会话(会话标题,撤回链聚成版本家族)→ 快照(时间 + 消息内容摘要,悬停看完整内容)。支持搜索与「加载更多」,工作区与会话节点可展开/折叠;每一级右侧都有删除按钮,删除前二次确认——删工作区 = 清掉该工作区全部快照,删会话 = 清掉该会话全部快照,删叶子 = 只删那一条;顶部另有带确认的「全部删除」。
|
|
149
171
|
|
|
150
172
|
## 工作原理
|
|
151
173
|
|
|
@@ -162,6 +184,63 @@ dsh plugin --profile web remove dsh-recall-plugin
|
|
|
162
184
|
|
|
163
185
|
- 存储目录里每个文件(索引、撤回链、格式标记、意图记录等)的格式与版本兼容约定见 [docs/format.md](docs/format.md)。
|
|
164
186
|
|
|
187
|
+
## 事件契约(供同宿主插件监听)
|
|
188
|
+
|
|
189
|
+
撤回到达终态时,插件经 cordis 事件广播两个公开事件(2.4.8+),同宿主任意插件可直接监听——例如长时记忆插件据此清理「被撤回回合」写入的记忆:
|
|
190
|
+
|
|
191
|
+
```js
|
|
192
|
+
// 同宿主插件内
|
|
193
|
+
ctx.on('dsh-recall/complete', (e) => {
|
|
194
|
+
if (e.chatReverted) memory.purgeTurn(e.root, e.sessionId, e.cutSeq)
|
|
195
|
+
})
|
|
196
|
+
ctx.on('dsh-recall/failed', (e) => {
|
|
197
|
+
if (e.stage === 'fork') log.warn('文件已回退但对话未回退', e)
|
|
198
|
+
})
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
`dsh-recall/complete`——撤回成功终态:
|
|
202
|
+
|
|
203
|
+
| 字段 | 类型 | 说明 |
|
|
204
|
+
| --- | --- | --- |
|
|
205
|
+
| `version` | `1` | 契约版本(演进规则见下) |
|
|
206
|
+
| `sessionId` | `string` | 被撤回的原会话 |
|
|
207
|
+
| `childSessionId` | `string \| null` | fork 出的新会话;纯文件回退时为 `null` |
|
|
208
|
+
| `scope` | `'both' \| 'session-only'` | 撤回范围 |
|
|
209
|
+
| `cutSeq` | `number \| null` | 对话回退切点;该消息是会话首条时为 `null` |
|
|
210
|
+
| `messageId` | `string` | 被撤回消息 ID(快照主键) |
|
|
211
|
+
| `root` | `string \| null` | 工作区根路径;host 解析失败为 `null` |
|
|
212
|
+
| `count` | `number` | 回退文件数(`session-only` 恒 `0`) |
|
|
213
|
+
| `chatReverted` | `boolean` | 对话是否真的回退(下游清理判据,见下) |
|
|
214
|
+
| `archiveRequested` | `boolean` | 原会话归档是否已发起(fire-and-forget,不代表已落定) |
|
|
215
|
+
| `time` | `number` | host 收到上报的时间(ms) |
|
|
216
|
+
|
|
217
|
+
`dsh-recall/failed`——撤回失败终态:
|
|
218
|
+
|
|
219
|
+
| 字段 | 类型 | 说明 |
|
|
220
|
+
| --- | --- | --- |
|
|
221
|
+
| `version` | `1` | 契约版本 |
|
|
222
|
+
| `stage` | `'execute' \| 'fork'` | 失败阶段:文件回退被拒/异常,或文件已回退但对话回退失败 |
|
|
223
|
+
| `sessionId` | `string \| null` | 被撤回的原会话 |
|
|
224
|
+
| `messageId` | `string` | 被撤回消息 ID |
|
|
225
|
+
| `scope` | `'both' \| 'session-only'` | 撤回范围 |
|
|
226
|
+
| `cutSeq` | `number \| null` | client 已知的对话切点(可能尚未使用) |
|
|
227
|
+
| `root` | `string \| null` | 工作区根路径;解析失败为 `null` |
|
|
228
|
+
| `code` | `string?` | 仅 `stage='execute'`:透传 host 错误码(`AGENT_BUSY` / `NO_SNAPSHOT` 等) |
|
|
229
|
+
| `error` | `string` | 错误描述 |
|
|
230
|
+
| `time` | `number` | host 收到上报的时间(ms) |
|
|
231
|
+
|
|
232
|
+
语义要点(下游必读):
|
|
233
|
+
|
|
234
|
+
- **只有终态才发事件**。撤回确认后「预览已过期、自动重新拉清单」(STALE)是中间态,不发任何事件;用户取消、面板关闭同样不发。
|
|
235
|
+
- **fork 失败只发 `failed(stage:'fork')`,绝不发 `complete`**——此刻文件已回退但对话未回退,若发 `complete` 会导致下游误清该回合记忆。判断「对话确实回退了」请以 `complete` 事件的 `chatReverted` 为准(纯文件回退的 `complete` 是 `chatReverted:false`)。
|
|
236
|
+
- `failed(stage:'execute')` 若来自执行异常(而非护栏拒绝),**文件是否已回退不确定**,下游宜保守处理。
|
|
237
|
+
- 重复撤回/重试可能对同一 `(sessionId, messageId, cutSeq)` 三元组多次发事件,下游按三元组做幂等。
|
|
238
|
+
- payload 不含消息正文,只有 ID / seq / 路径;事件只对同宿主插件可见。
|
|
239
|
+
|
|
240
|
+
版本演进:首发即稳定公共契约(非 experimental)。新增字段(additive)走 minor;破坏性变更走 major 且 `version` 升 `2`,下游可按 `version` 分流。
|
|
241
|
+
|
|
242
|
+
版本错位:旧版插件前端(<2.4.8)不上报、事件永不触发;新版前端 + 旧版 host(<2.4.8)时上报端点不存在,前端静默忽略,撤回功能不受任何影响。
|
|
243
|
+
|
|
165
244
|
## 本地开发(无需发布)
|
|
166
245
|
|
|
167
246
|
把 profile 对本包的依赖改成 `link:` 指向克隆目录;DSH 加载的是工作区 `lib/` 构建产物(源码在 `src/`),改 `src/` 后先 `npm run build` 再重启 DSH 生效,无需复制或发布:
|
|
@@ -180,8 +259,8 @@ pnpm install
|
|
|
180
259
|
|
|
181
260
|
### 测试
|
|
182
261
|
|
|
183
|
-
- `npm test`:纯逻辑单测(vitest,
|
|
184
|
-
- `npm run test:client`:client 组件测试(vitest + jsdom,6 个文件
|
|
262
|
+
- `npm test`:纯逻辑单测(vitest,39 个文件 497 例,无 DSH 依赖,CI 与本地同跑)——配置解析、快照解析器、救援编排、错误分类、磁盘格式守卫、操作意图 journal、撤回终态事件上报组装、i18n 词典与 key 漏配扫描、脚本模板同名导出契约、执行通道双分支与失败分级、settings 双代桥接、客户端纯函数、发布包内容布局、快照索引持久化、存储上限与保留天数等;
|
|
263
|
+
- `npm run test:client`:client 组件测试(vitest + jsdom,6 个文件 90 例,CI 同跑)——撤回节点主链(preview→execute→fork→回填)与终态事件上报、快照管理树与删除流、配置/排除卡片错误路径、logger 开关矩阵、zh/en 双语渲染链;断言只锁行为与结构(className / aria / 请求载荷),不锁文案字面量;
|
|
185
264
|
- `npm run test:probe`:官方 API 字段探针(依赖本机 dsh 安装;dsh 升级后本地必跑)——钉住 `renderMessageImages`/`node`/`cwd`、`sessions.fork` 的 `atSeq`/`increaseTitle` 与切点锚点、`listSessions` 记录结构、`AgentRegistry`、shell 执行接缝(`execute`/`ShellExecution.result`)、settings 面(`SettingsForms`/profile 条目 id/volatile 门槛)与 `loader/volatile-update`/`Fiber.entry` 形状等字段,违反即红;
|
|
186
265
|
- `npm run verify:host`:装配门禁(依赖本机 dsh 安装)——用真实 cordis 起插件跑两个 pass(旧面桩 + 只给新面的桩),断言 inject 声明、端点注册、Config schema、卸载清理与 settings 面分派,装配回归发版前即可拦截;
|
|
187
266
|
- `npm run build`:host+client 全量打包(改任何 `src/` 后必跑);`npm run check:dsh`:dsh 版本巡检(发布前)。
|
package/lib/client.js
CHANGED
|
@@ -820,6 +820,33 @@
|
|
|
820
820
|
return "/api/recall/" + name;
|
|
821
821
|
}
|
|
822
822
|
}
|
|
823
|
+
function buildRecallNotify(base, o) {
|
|
824
|
+
if (o.outcome === "complete") {
|
|
825
|
+
return {
|
|
826
|
+
status: "complete",
|
|
827
|
+
sessionId: base.sessionId,
|
|
828
|
+
messageId: base.messageId,
|
|
829
|
+
scope: base.scope,
|
|
830
|
+
cutSeq: o.cutSeq,
|
|
831
|
+
childSessionId: o.childSessionId,
|
|
832
|
+
count: o.count,
|
|
833
|
+
chatReverted: o.chatReverted,
|
|
834
|
+
archiveRequested: o.archiveRequested
|
|
835
|
+
};
|
|
836
|
+
}
|
|
837
|
+
const stage = o.outcome === "fork-failed" ? "fork" : "execute";
|
|
838
|
+
const code = o.outcome === "execute-rejected" && typeof o.code === "string" && o.code ? o.code : void 0;
|
|
839
|
+
return {
|
|
840
|
+
status: "failed",
|
|
841
|
+
stage,
|
|
842
|
+
sessionId: base.sessionId,
|
|
843
|
+
messageId: base.messageId,
|
|
844
|
+
scope: base.scope,
|
|
845
|
+
cutSeq: o.cutSeq,
|
|
846
|
+
...code ? { code } : {},
|
|
847
|
+
error: typeof o.error === "string" && o.error ? o.error : String(o.error || "recall failed")
|
|
848
|
+
};
|
|
849
|
+
}
|
|
823
850
|
function useAutoDismissMessage(React, state, setState) {
|
|
824
851
|
React.useEffect(() => {
|
|
825
852
|
if (!state.message || state.error || state.busy) return;
|
|
@@ -1432,6 +1459,7 @@
|
|
|
1432
1459
|
if (recall.stage !== "confirm") return;
|
|
1433
1460
|
const changes = recall.changes || [];
|
|
1434
1461
|
const previewCut = typeof recall.cutSeq === "number" ? recall.cutSeq : null;
|
|
1462
|
+
const notifyBase = { sessionId: String(sessionId || ""), messageId, scope };
|
|
1435
1463
|
const previewTotal = typeof recall.total === "number" ? recall.total : changes.length;
|
|
1436
1464
|
const attachmentFiles = pluginConfig.refillDraft && attachmentRefs.length > 0 && sessionId ? preloadAttachmentFiles(sessionId, attachmentRefs) : null;
|
|
1437
1465
|
setRecall({ stage: "executing", changes });
|
|
@@ -1458,12 +1486,20 @@
|
|
|
1458
1486
|
});
|
|
1459
1487
|
return;
|
|
1460
1488
|
}
|
|
1489
|
+
api("notify", buildRecallNotify(notifyBase, {
|
|
1490
|
+
outcome: "execute-rejected",
|
|
1491
|
+
cutSeq: previewCut,
|
|
1492
|
+
code: res && typeof res.code === "string" ? res.code : void 0,
|
|
1493
|
+
error: messageFor(res, "")
|
|
1494
|
+
})).catch(() => {
|
|
1495
|
+
});
|
|
1461
1496
|
setRecall({ stage: "error", message: messageFor(res, t("fallback.rollback")) });
|
|
1462
1497
|
return;
|
|
1463
1498
|
}
|
|
1464
1499
|
const cutSeq = typeof res.cutSeq === "number" ? res.cutSeq : previewCut;
|
|
1465
1500
|
let chatReverted = false;
|
|
1466
1501
|
let chatError = "";
|
|
1502
|
+
let archiveRequested = false;
|
|
1467
1503
|
let fillTarget = sessionId;
|
|
1468
1504
|
if (cutSeq !== null && sessionsSvc && typeof sessionsSvc.fork === "function") {
|
|
1469
1505
|
try {
|
|
@@ -1477,15 +1513,20 @@
|
|
|
1477
1513
|
api("lineage-record", { childId, parentId: sessionId }).catch(() => {
|
|
1478
1514
|
});
|
|
1479
1515
|
if (pluginConfig.archiveOriginal && workspacesSvc && typeof workspacesSvc.archiveSession === "function") {
|
|
1516
|
+
archiveRequested = true;
|
|
1480
1517
|
workspacesSvc.archiveSession(sessionId, { stopActivity: true }).catch((error) => {
|
|
1481
1518
|
recallNodeLog.warn("archive original session failed:", error);
|
|
1482
1519
|
});
|
|
1483
1520
|
}
|
|
1484
1521
|
} else {
|
|
1485
1522
|
chatError = t("recall.chat.noChild");
|
|
1523
|
+
api("notify", buildRecallNotify(notifyBase, { outcome: "fork-failed", cutSeq, error: chatError })).catch(() => {
|
|
1524
|
+
});
|
|
1486
1525
|
}
|
|
1487
1526
|
} catch (error) {
|
|
1488
1527
|
chatError = String(error);
|
|
1528
|
+
api("notify", buildRecallNotify(notifyBase, { outcome: "fork-failed", cutSeq, error: chatError })).catch(() => {
|
|
1529
|
+
});
|
|
1489
1530
|
}
|
|
1490
1531
|
}
|
|
1491
1532
|
if (pluginConfig.refillDraft) {
|
|
@@ -1494,8 +1535,21 @@
|
|
|
1494
1535
|
}
|
|
1495
1536
|
setHasSnapshot(false);
|
|
1496
1537
|
setRecall({ stage: "done", count: typeof res.count === "number" ? res.count : changes.length, chatReverted, chatError });
|
|
1538
|
+
if (chatError === "") {
|
|
1539
|
+
api("notify", buildRecallNotify(notifyBase, {
|
|
1540
|
+
outcome: "complete",
|
|
1541
|
+
cutSeq,
|
|
1542
|
+
childSessionId: chatReverted ? String(fillTarget || "") || null : null,
|
|
1543
|
+
count: typeof res.count === "number" ? res.count : changes.length,
|
|
1544
|
+
chatReverted,
|
|
1545
|
+
archiveRequested
|
|
1546
|
+
})).catch(() => {
|
|
1547
|
+
});
|
|
1548
|
+
}
|
|
1497
1549
|
}).catch((error) => {
|
|
1498
1550
|
setRecall({ stage: "error", message: String(error) });
|
|
1551
|
+
api("notify", buildRecallNotify(notifyBase, { outcome: "execute-threw", cutSeq: previewCut, error: String(error) })).catch(() => {
|
|
1552
|
+
});
|
|
1499
1553
|
});
|
|
1500
1554
|
};
|
|
1501
1555
|
const closePanel = () => {
|
package/lib/index.js
CHANGED
|
@@ -267,7 +267,10 @@ function apply(ctx, config) {
|
|
|
267
267
|
DEFAULTS,
|
|
268
268
|
rescueRollback,
|
|
269
269
|
intentJournal,
|
|
270
|
-
E
|
|
270
|
+
E,
|
|
271
|
+
// issue #19 撤回终态事件广播:闭包注入保持 ctx 不解构纪律,routes 域
|
|
272
|
+
// 不直接持有 ctx;单测可注入桩断言事件名与 payload
|
|
273
|
+
emitEvent: (event, payload) => ctx.emit(event, payload)
|
|
271
274
|
};
|
|
272
275
|
const endpoints = {
|
|
273
276
|
...createRoutesCore(deps),
|
package/lib/routes-core.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { ENV_HINTS } from "./diagnostics.js";
|
|
2
2
|
import { parseTreeId } from "./snapshots.js";
|
|
3
3
|
import { buildArtifactRootSegment, buildRootNotice } from "./exclude-patterns.js";
|
|
4
|
+
const RECALL_EVENT_COMPLETE_HOST = "dsh-recall/complete";
|
|
5
|
+
const RECALL_EVENT_FAILED_HOST = "dsh-recall/failed";
|
|
4
6
|
function createRoutesCore(deps) {
|
|
5
|
-
const { rt, snaps, state, cfg, supported, enqueue, agentBusy, rescueRollback, intentJournal, E } = deps;
|
|
7
|
+
const { rt, snaps, state, cfg, supported, enqueue, agentBusy, rescueRollback, intentJournal, emitEvent, E } = deps;
|
|
6
8
|
return {
|
|
7
9
|
"init": async (args) => {
|
|
8
10
|
if (!supported) {
|
|
@@ -159,9 +161,73 @@ function createRoutesCore(deps) {
|
|
|
159
161
|
if (!store) return { ok: false, code: E.RECALL_NO_STORE, message: "\u5FEB\u7167\u5B58\u50A8\u4E0D\u53EF\u7528" };
|
|
160
162
|
await snaps.recordLineage(root, childId, parentId);
|
|
161
163
|
return { ok: true };
|
|
164
|
+
},
|
|
165
|
+
// issue #19:client 撤回终态上报 → cordis 事件广播(dsh-recall/complete
|
|
166
|
+
// | dsh-recall/failed)。不进串行队列(零 git 操作)、不依赖 store——
|
|
167
|
+
// lineage-record 的 NO_STORE 早退在这里是缺陷(原会话已归档时解析不到
|
|
168
|
+
// store 会吞掉事件),端点只做 enrich 与转发。恒 { ok: true }:事件
|
|
169
|
+
// 发送失败不影响撤回主流程(client 侧 fire-and-forget,连响应都不等)。
|
|
170
|
+
"notify": async (args) => {
|
|
171
|
+
const status = args && args.status;
|
|
172
|
+
const sessionId = args && args.sessionId ? String(args.sessionId) : "";
|
|
173
|
+
const messageId = args && args.messageId ? String(args.messageId) : "";
|
|
174
|
+
if (status !== "complete" && status !== "failed" || !sessionId || !messageId) {
|
|
175
|
+
return { ok: false, code: E.RECALL_BAD_TYPE, message: "\u64A4\u56DE\u4E0A\u62A5\u7F3A\u5C11\u5FC5\u8981\u53C2\u6570" };
|
|
176
|
+
}
|
|
177
|
+
const scope = args && args.scope === "session-only" ? "session-only" : "both";
|
|
178
|
+
const cutSeq = args && typeof args.cutSeq === "number" ? args.cutSeq : null;
|
|
179
|
+
let root = null;
|
|
180
|
+
try {
|
|
181
|
+
root = await rt.resolveRoot(sessionId) || null;
|
|
182
|
+
} catch {
|
|
183
|
+
root = null;
|
|
184
|
+
}
|
|
185
|
+
let eventName;
|
|
186
|
+
let payload;
|
|
187
|
+
if (status === "complete") {
|
|
188
|
+
eventName = RECALL_EVENT_COMPLETE_HOST;
|
|
189
|
+
payload = {
|
|
190
|
+
version: 1,
|
|
191
|
+
sessionId,
|
|
192
|
+
childSessionId: args && args.childSessionId ? String(args.childSessionId) : null,
|
|
193
|
+
scope,
|
|
194
|
+
cutSeq,
|
|
195
|
+
messageId,
|
|
196
|
+
root,
|
|
197
|
+
count: args && typeof args.count === "number" ? args.count : 0,
|
|
198
|
+
chatReverted: Boolean(args && args.chatReverted),
|
|
199
|
+
archiveRequested: Boolean(args && args.archiveRequested),
|
|
200
|
+
time: Date.now()
|
|
201
|
+
};
|
|
202
|
+
} else {
|
|
203
|
+
eventName = RECALL_EVENT_FAILED_HOST;
|
|
204
|
+
const code = args && typeof args.code === "string" && args.code ? args.code : void 0;
|
|
205
|
+
payload = {
|
|
206
|
+
version: 1,
|
|
207
|
+
stage: args && args.stage === "fork" ? "fork" : "execute",
|
|
208
|
+
sessionId,
|
|
209
|
+
messageId,
|
|
210
|
+
scope,
|
|
211
|
+
cutSeq,
|
|
212
|
+
root,
|
|
213
|
+
...code ? { code } : {},
|
|
214
|
+
error: args && typeof args.error === "string" && args.error ? args.error : "unknown recall failure",
|
|
215
|
+
time: Date.now()
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
queueMicrotask(() => {
|
|
219
|
+
try {
|
|
220
|
+
emitEvent(eventName, payload);
|
|
221
|
+
} catch (error) {
|
|
222
|
+
rt.recordError("recall event emit failed: " + String(error));
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
return { ok: true };
|
|
162
226
|
}
|
|
163
227
|
};
|
|
164
228
|
}
|
|
165
229
|
export {
|
|
230
|
+
RECALL_EVENT_COMPLETE_HOST,
|
|
231
|
+
RECALL_EVENT_FAILED_HOST,
|
|
166
232
|
createRoutesCore
|
|
167
233
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-recall-plugin",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.9",
|
|
4
4
|
"description": "在 DSH 中撤回自己发过的消息:工作区文件与对话历史一起回到那条消息发出之前,消息的文本与附件自动放回输入框,改完即可重发。文件走独立影子 git 快照(不碰项目自身的 git),对话走官方 sessions.fork,原会话归档可找回。",
|
|
5
5
|
"icon": "assets/icon.svg",
|
|
6
6
|
"type": "module",
|
|
@@ -67,19 +67,20 @@
|
|
|
67
67
|
"0.1.7-rc.1": "compatible",
|
|
68
68
|
"0.1.7-rc.2": "compatible",
|
|
69
69
|
"0.2.0-rc.1": "compatible",
|
|
70
|
-
"0.2.0-rc.2": "compatible"
|
|
70
|
+
"0.2.0-rc.2": "compatible",
|
|
71
|
+
"0.2.1-alpha.1": "compatible"
|
|
71
72
|
}
|
|
72
73
|
}
|
|
73
74
|
},
|
|
74
75
|
"peerDependencies": {
|
|
75
|
-
"@deepseek-ai/cordis": "^4.0.1",
|
|
76
|
-
"@deepseek-ai/dsh-host-webserver": ">=0.1.2-alpha.1 <0.1.3 || >=0.1.3-alpha.1 <0.1.4 || >=0.1.5-alpha.1 <0.1.6 || >=0.1.6-alpha.1 <0.1.7 || >=0.1.7-alpha.1 <0.1.8 || >=0.2.0-rc.1 <0.2.1",
|
|
77
|
-
"@deepseek-ai/dsh-sandbox-policy": ">=0.1.2-alpha.1 <0.1.3 || >=0.1.3-alpha.1 <0.1.4 || >=0.1.5-alpha.1 <0.1.6 || >=0.1.6-alpha.1 <0.1.7 || >=0.1.7-alpha.1 <0.1.8 || >=0.2.0-rc.1 <0.2.1",
|
|
78
|
-
"@deepseek-ai/dsh-session": ">=0.1.2-alpha.1 <0.1.3 || >=0.1.3-alpha.1 <0.1.4 || >=0.1.5-alpha.1 <0.1.6 || >=0.1.6-alpha.1 <0.1.7 || >=0.1.7-alpha.1 <0.1.8 || >=0.2.0-rc.1 <0.2.1",
|
|
79
|
-
"@deepseek-ai/dsh-session-query": ">=0.1.2-alpha.1 <0.1.3 || >=0.1.3-alpha.1 <0.1.4 || >=0.1.5-alpha.1 <0.1.6 || >=0.1.6-alpha.1 <0.1.7 || >=0.1.7-alpha.1 <0.1.8 || >=0.2.0-rc.1 <0.2.1",
|
|
80
|
-
"@deepseek-ai/dsh-settings": ">=0.1.2-alpha.1 <0.1.3 || >=0.1.3-alpha.1 <0.1.4 || >=0.1.5-alpha.1 <0.1.6 || >=0.1.6-alpha.1 <0.1.7 || >=0.1.7-alpha.1 <0.1.8 || >=0.2.0-rc.1 <0.2.1",
|
|
81
|
-
"@deepseek-ai/dsh-shell": ">=0.1.2-alpha.1 <0.1.3 || >=0.1.3-alpha.1 <0.1.4 || >=0.1.5-alpha.1 <0.1.6 || >=0.1.6-alpha.1 <0.1.7 || >=0.1.7-alpha.1 <0.1.8 || >=0.2.0-rc.1 <0.2.1",
|
|
82
|
-
"@deepseek-ai/schemastery": "^3.18.1",
|
|
76
|
+
"@deepseek-ai/cordis": "^4.0.1 || >=4.0.5-alpha.1 <4.0.6",
|
|
77
|
+
"@deepseek-ai/dsh-host-webserver": ">=0.1.2-alpha.1 <0.1.3 || >=0.1.3-alpha.1 <0.1.4 || >=0.1.5-alpha.1 <0.1.6 || >=0.1.6-alpha.1 <0.1.7 || >=0.1.7-alpha.1 <0.1.8 || >=0.2.0-rc.1 <0.2.1 || >=0.2.1-alpha.1 <0.3.0",
|
|
78
|
+
"@deepseek-ai/dsh-sandbox-policy": ">=0.1.2-alpha.1 <0.1.3 || >=0.1.3-alpha.1 <0.1.4 || >=0.1.5-alpha.1 <0.1.6 || >=0.1.6-alpha.1 <0.1.7 || >=0.1.7-alpha.1 <0.1.8 || >=0.2.0-rc.1 <0.2.1 || >=0.2.1-alpha.1 <0.3.0",
|
|
79
|
+
"@deepseek-ai/dsh-session": ">=0.1.2-alpha.1 <0.1.3 || >=0.1.3-alpha.1 <0.1.4 || >=0.1.5-alpha.1 <0.1.6 || >=0.1.6-alpha.1 <0.1.7 || >=0.1.7-alpha.1 <0.1.8 || >=0.2.0-rc.1 <0.2.1 || >=0.2.1-alpha.1 <0.3.0",
|
|
80
|
+
"@deepseek-ai/dsh-session-query": ">=0.1.2-alpha.1 <0.1.3 || >=0.1.3-alpha.1 <0.1.4 || >=0.1.5-alpha.1 <0.1.6 || >=0.1.6-alpha.1 <0.1.7 || >=0.1.7-alpha.1 <0.1.8 || >=0.2.0-rc.1 <0.2.1 || >=0.2.1-alpha.1 <0.3.0",
|
|
81
|
+
"@deepseek-ai/dsh-settings": ">=0.1.2-alpha.1 <0.1.3 || >=0.1.3-alpha.1 <0.1.4 || >=0.1.5-alpha.1 <0.1.6 || >=0.1.6-alpha.1 <0.1.7 || >=0.1.7-alpha.1 <0.1.8 || >=0.2.0-rc.1 <0.2.1 || >=0.2.1-alpha.1 <0.3.0",
|
|
82
|
+
"@deepseek-ai/dsh-shell": ">=0.1.2-alpha.1 <0.1.3 || >=0.1.3-alpha.1 <0.1.4 || >=0.1.5-alpha.1 <0.1.6 || >=0.1.6-alpha.1 <0.1.7 || >=0.1.7-alpha.1 <0.1.8 || >=0.2.0-rc.1 <0.2.1 || >=0.2.1-alpha.1 <0.3.0",
|
|
83
|
+
"@deepseek-ai/schemastery": "^3.18.1 || >=3.18.5-alpha.1 <3.18.6",
|
|
83
84
|
"react": "^18.2.0"
|
|
84
85
|
},
|
|
85
86
|
"peerDependenciesMeta": {
|