dsh-recall-plugin 2.4.16 → 2.5.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/CHANGELOG.md +51 -0
- package/README.en.md +42 -9
- package/README.md +42 -9
- package/lib/client.js +3982 -2842
- package/lib/config.js +8 -3
- package/lib/routes-core.js +1 -1
- package/lib/routes-manage.js +2 -0
- package/lib/scripts.posix.js +33 -11
- package/lib/scripts.pwsh.js +33 -20
- package/lib/snapshots.js +1 -1
- package/package.json +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,57 @@
|
|
|
2
2
|
|
|
3
3
|
本文件格式遵循 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/),版本号遵循语义化版本。
|
|
4
4
|
|
|
5
|
+
## [Unreleased]
|
|
6
|
+
|
|
7
|
+
## [2.5.1] - 2026-10-27
|
|
8
|
+
|
|
9
|
+
### 兼容
|
|
10
|
+
|
|
11
|
+
- **dsh 0.2.1-rc.3 零破坏(无需改码)**:6 个 commit 全是纯加法与内部修复——新增客户端包 `dsh-client-ui-entry-customization`(左/右侧栏与会话视图的「更多」菜单)只声明并注册三个 root scope list 槽,与插件四个注册 key 零交集;新会话草稿修复落在 `ui-workspace.startSession` / `createInWorkspace` 的 blank 复用,插件不调用该入口;API Key 路由模型列表改跟官方 `/models`,客户端目录契约 `dsh-client-ui-model-selection` 零漂移。`sessions.fork`、`setDraft`/`createDrafts`/`addAttachments`、`openSession`/`unarchiveSession`、slot 定义方(`ui-chat`)、兼容门禁(`dsh-app-boot`)逐字节零变更;`test:probe` 58/58、`verify:host` 全过、六条 peer 对 rc.3 全满足,宿主启停冒烟零 compat/skip。评估见 [docs/upgrade-assessments/dsh-0.2.1-rc.3.md](./docs/upgrade-assessments/dsh-0.2.1-rc.3.md)。
|
|
12
|
+
|
|
13
|
+
### 变更
|
|
14
|
+
|
|
15
|
+
- **`check:dsh` 改按 `dist-tags` 判定「npm 最新版」**:官方 rc 线发在 `next` 上、`latest` 停在上一版(0.2.1-rc.3 实测 `latest=rc.2` / `next=rc.3`),原实现只读 `version`(= latest),会把已发布的候选版误报成「无新版本」。现在两标签取较新者作 peer 覆盖与升级提示的基准,并在该行注明判定依据;`dist-tags` 读取失败时回退单版本探测(fail-open)。新增 5 条单测(报告判定 2 + 取较新者 3)。
|
|
16
|
+
- **`test:probe` 补输入区写入链 4 条(54 → 58)**:回填与编辑重发依赖的官方入口此前只有运行时能力探测兜底、探针零覆盖——官方若把 `setDraft` 并入新的 `whenDraftExists` 策略模型或改签名,没有任何红灯。现钉住 `InputActions.setDraft/addAttachments/submit`、`InputState.draft`、`SessionInputShell.actions` + `get snapshot()`、`createDrafts`/`releaseDraftAttachments`。
|
|
17
|
+
|
|
18
|
+
### 修复
|
|
19
|
+
|
|
20
|
+
- **影子仓库写 index 的调用补 `--work-tree`(既有缺陷,与 issue #28 同批发现)**:影子仓库用 `git init --bare` 建,裸 `--git-dir` 下 git 认为没有工作树,`update-index` 直接 fatal `this operation must be run in a work tree`。后果按平台分叉:win32 上 git 非零退出不抛,**超大文件剔除(`maxFileBytes`)与 gitlink 剔除静默失效**——10 MB 文件照样进快照;POSIX 上 `set -e` 让「工作区里有带 `.git` 的子项目」这类场景**每条消息都快照失败**。三处调用(`dropGitlinks` / `oversize` / `excludeSync` 的已跟踪条目清理)统一补上,`excludeSync` 因建仓路径没有 `$root` 而走条件分支。契约单测钉住「写 index 的 `update-index` 一律带 `--work-tree`」。
|
|
21
|
+
- **快照总量护栏不再把 `.gitignore` 排除的目录算进去(issue #28)**:`maxWorkspaceBytes` 的前置统计改为按 `git ls-files -z -co --exclude-standard` 的清单求和,与 `add -A` 共用同一套排除引擎——`.gitignore`(每层继承)、`.git/info/exclude`、全局 excludesFile、`!` 反选与嵌套仓库的判定从此与真正进快照的口径一致。此前只认基础排除表与 `exclude.txt`,实测把 130 MB 的项目量成 297 MB 并整区停用快照(用户直接失去撤回能力),提示还会把人引向「调大上限」这个反向处置。顺带修好「父级工作区把带自己 `.git` 的子项目算进总量」。`ls-files` 失败时 fail-open 不判定(不回落目录遍历,避免两套口径随 git 状态跳变);停用提示与设置页 hint 补注「已按 .gitignore 与排除表过滤」。
|
|
22
|
+
|
|
23
|
+
### 文档
|
|
24
|
+
|
|
25
|
+
- 台账同步 dsh 0.2.1-rc.3:`dshReleases` 补 `0.2.1-rc.3`、`docs/reference/` 第十次重拉(13 源逐字节相同,连续第二轮零差异)、`docs/dsh-contract.md` 对应版本与沿革⑪、`docs/compat-audit.md` rc.3 核验段、README 双语徽章与版本范围。
|
|
26
|
+
|
|
27
|
+
## [2.5.0] - 2026-10-24
|
|
28
|
+
|
|
29
|
+
### 新增
|
|
30
|
+
|
|
31
|
+
- **「编辑并重发」:就地改完直接发出(issue #26)**:用户消息动作行在「↶ 撤回」旁新增「✎ 编辑」——点击就地展开编辑框并预填原文,改完提交即新建会话并把改后的文本自动发出。复用撤回的 fork 链(preview 取切点 → execute → `sessions.fork` → 归档原会话),但 scope 固定 `session-only`:**编辑语义不动项目文件**,也不打安全快照。原消息的图片/文件附件经 `createDrafts → addAttachments` 一并带入新消息。
|
|
32
|
+
- 配置项 `editSubmitsImmediately`(默认 `true`):提交后自动发送;关闭则退化为「只回填输入框」,由用户按回车发送(适合只想改稿不想立刻发)。设置页「插件配置」卡片可直接切换,热生效;老 Host 不下发该字段时按 `true` 处理。
|
|
33
|
+
- 发送时机按草稿落地校验:`setDraft` → `actions.submit()` 之间读输入态快照比对草稿文本,未落地则最多等 6×50ms 再提交——`submit` 读的是输入态快照,同一 tick 内投影未刷新会按空草稿静默返回。快照不可读的跨版本面降级为让出一拍直接提交(草稿为空时官方自身拒发,最坏是「没发」,不会「发错」)。
|
|
34
|
+
- **版本翻页器 `‹ X/N ›`**:每次撤回/编辑重发 fork 出的会话构成同一段对话的版本家族,最后一轮回答的操作区嵌入 `‹ X/N ›`——点击切换:归档当前版本 → 解档目标版本 → 打开目标会话,侧栏始终只保留一个活动版本;切换后按 `data-chat-anchor-key` 恢复视口位置(消息内容不同、行高不同,只靠 `scrollTop` 会跳位)。家族登记在客户端 `localStorage`(`dsh-recall:versions:<根会话>`),与 host 侧 `lineage.json` 互补:丢了只影响翻页器,会话本身不受影响。
|
|
35
|
+
|
|
36
|
+
### 变更
|
|
37
|
+
|
|
38
|
+
- **消息位置出现「官方同款」编辑卡**:点 ✎ 后气泡换成一张按官方 composer 配方画的卡片(panel 圆角 / `input-major` 底 / soft 阴影 / 14-24 字号行高),预填原文、随内容增高,**底部官方输入框保持原样**。卡内可用:Enter 发送(Shift+Enter 换行)、附件增删与粘贴/拖入、只读的权限 chip、可切换的模型与推理等级、`Esc` 取消。
|
|
39
|
+
- **为什么不是第二个官方输入框**:`conversation.composer.bar` 是 single 槽且已被宿主的 `conversation.content` 独占声明——插件渲染任何槽都要先在自己 children 里声明它,重复声明会在加载期抛错,组件实例也没有导出。因此这张卡是插件自绘的同款,官方样式变动时需要跟版。
|
|
40
|
+
- 附件语义改为显式:原消息附件渲染成可移除的 chip,提交时只带保留下来的那些;卡里新加的附件(粘贴/拖入/选择)与原图/文件一起交给 `fillDraft` 在子会话重建。图片附件显示为 56px 缩略图(原消息图片走官方 `loadImage` 通道、与消息里的大图同源;新加图片用 object URL,移除/关闭时回收),非图片仍按文件名 chip 展示。
|
|
41
|
+
- 编辑卡里的模型菜单按官方同构重建:主段两行「模型 / 推理等级」,点进去分别是模型列表(搜索 + 按供应商分组 + 当前项打勾)与档位列表(Default / Low / High / Xhigh)。选择走官方 `ctx.modelDirectories` 的 `directoryFor(session).select()`——与底部 chip、`/model` 命令同一条 `selectModel` 通道,选中后底部输入框的 chip 同步更新,fork 出的子会话沿用;换模型用该模型的默认档,重选同一模型保留当前档位。触发 chip 与官方同构:模型名用常规色、思考强度用更淡的字色,尾部带展开箭头(展开时翻转、给一层 hover 底)。服务缺失(旧宿主)时退回只读展示。
|
|
42
|
+
- 编辑态隐藏底部输入框的统计 dock(回合/步骤、tok/s、缓存命中、Trace):编辑焦点在消息处,dock 属全局概览,留着只会分心。规则挂在既有的 `data-editing` 上,不引入额外 JS 状态。
|
|
43
|
+
|
|
44
|
+
### 修复
|
|
45
|
+
|
|
46
|
+
- **编辑态重复触发不再重置已改文本**:编辑态下不再渲染「✎」(点了也是空操作),`openEdit` 对 editing 幂等、error 态保留改过的文本与附件——否则会用原文覆盖用户改到一半的内容。
|
|
47
|
+
- 输入区回填链(文本 + 附件 + 可选发送)从 `recall-node.ts` 抽到新模块 `src/client/draft-fill.ts`:撤回回填与编辑重发必须共用同一条官方写入通道,分叉必然漂移;`defaultAttachmentName` 随迁并保持 `recall-node.js` 的 re-export 路径不变。
|
|
48
|
+
- 编辑渲染层独立成 `src/client/edit-node.ts`(与撤回确认面板的 stage 语义不同),骨架与执行链仍在 `recall-node.ts`;版本翻页器拆为 `src/client/version-tree.ts`(家族登记/查询)与 `src/client/version-pager.ts`(`conversation.chat.assistant-actions` 槽组件)。
|
|
49
|
+
- 撤回执行链 `executeRecall` 参数化(scope / 切点 / 预览摘要 / 编辑文本),编辑与撤回共用同一段 fork 收尾;scope 判定改白名单,避免被当作 `onClick` 处理器时把 React 合成事件当 scope 透传。
|
|
50
|
+
|
|
51
|
+
### 边界
|
|
52
|
+
|
|
53
|
+
- 会话第一条用户消息没有更早的 `turn/end` 边界,编辑提交会被明确拒绝(错误面板),不静默降级为「仅回填」。
|
|
54
|
+
- fork 失败时不回填也不发送:原会话与原消息都还在,补发会变成同一会话里的重复消息。
|
|
55
|
+
|
|
5
56
|
## [2.4.16] - 2026-10-18
|
|
6
57
|
|
|
7
58
|
### 修复
|
package/README.en.md
CHANGED
|
@@ -7,12 +7,12 @@
|
|
|
7
7
|

|
|
8
8
|

|
|
9
9
|
|
|
10
|
-
[](https://github.com/deepseek-ai/deepseek-harness/releases/tag/dsh-v0.2.1-rc.3)
|
|
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
14
|
|
|
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.
|
|
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; you can also click "✎ Edit" next to a message to revise and resend it in place (files untouched), and flip between versions of the same stretch of conversation with the `‹ X/N ›` pager. 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.
|
|
16
16
|
|
|
17
17
|
[Changelog](CHANGELOG.md)
|
|
18
18
|
|
|
@@ -20,11 +20,13 @@ As each message is sent, the workspace is first snapshotted into an independent
|
|
|
20
20
|
|
|
21
21
|
- [UI Preview](#ui-preview)
|
|
22
22
|
- [Highlights](#highlights)
|
|
23
|
+
- [Tech Stack & Dependencies](#tech-stack--dependencies)
|
|
23
24
|
- [Known Limitations](#known-limitations)
|
|
24
25
|
- [Installation](#installation)
|
|
25
26
|
- [Usage](#usage)
|
|
26
27
|
- [Configuration](#configuration)
|
|
27
28
|
- [Snapshot Maintenance & Cleanup](#snapshot-maintenance--cleanup)
|
|
29
|
+
- [FAQ](#faq)
|
|
28
30
|
- [How It Works](#how-it-works)
|
|
29
31
|
- [Event Contract (for same-host plugins)](#event-contract-for-same-host-plugins)
|
|
30
32
|
- [Contributing](#contributing)
|
|
@@ -49,17 +51,29 @@ Abilities with a version in parentheses require that version or later; the rest
|
|
|
49
51
|
- **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
52
|
- **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
53
|
- **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.
|
|
54
|
+
- **Fixing one typo takes one click** (2.5.0+): the "✎ Edit" action grows an editor card at that message, styled after the official composer (prefilled with the original text, growing with content) while **the bottom composer stays exactly where it is**; edit inline, paste or drop attachments, then press Enter (or Send) to fork a new session and dispatch the edited text. It rides the same fork chain as a recall (the original session is archived and recoverable) but **only rolls back the conversation — project files stay untouched**. Original attachments appear as removable chips, permissions stay read-only, model and reasoning effort can be switched right there through an official-style menu (syncing to the bottom composer), and `Esc` cancels. Turn off "Send after editing" in the settings if you would rather review before pressing Enter.
|
|
55
|
+
- **Every version of the same turn is one click away** (2.5.0+): each recall or edit-and-resend forks a new session, and the action strip of the last answer gains a `‹ X/N ›` control — click to switch. Only the active version stays in the sidebar (the others are archived automatically), and the viewport returns to where you were.
|
|
52
56
|
- **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
57
|
- **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
58
|
- **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
59
|
|
|
60
|
+
## Tech Stack & Dependencies
|
|
61
|
+
|
|
62
|
+
This plugin is a DSH bundle: the host half runs inside the DSH process (Node ESM) and the client half inside the browser session page — both ship from the same npm package, which has zero runtime dependencies of its own.
|
|
63
|
+
|
|
64
|
+
- **Runtime**: Node.js ≥ 20 and git CLI; PowerShell 5.1/7 on Windows, bash on Linux/macOS (selected automatically by platform).
|
|
65
|
+
- **Language & build**: TypeScript + ESM; `npm run build` produces `lib/` via esbuild (per-file host transpilation, single-file client bundle), and the artifacts are committed with the source.
|
|
66
|
+
- **Tests & gates**: vitest (pure-logic unit tests, jsdom client component tests, official-API field probes) plus a `tsc --noEmit` type gate and an assembly-gate script — see "Contributing → Tests".
|
|
67
|
+
- **Host dependencies** (peerDependencies, provided by the DSH installation — nothing extra to install): `@deepseek-ai/cordis` 4, `@deepseek-ai/dsh-*` (host-webserver / sandbox-policy / session / session-query / settings / shell), `@deepseek-ai/schemastery` 3.18; `react` 18 is an optional peer (injected by the host for the client).
|
|
68
|
+
|
|
56
69
|
## Known Limitations
|
|
57
70
|
|
|
58
71
|
Boundaries accepted by design and edge cases not yet covered — worth checking before you rely on them.
|
|
59
72
|
|
|
60
73
|
- Snapshots are created **when a message is sent**: messages from before the plugin was enabled have no snapshot and show no recall button.
|
|
61
74
|
- 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
|
-
- The first user message of a session cannot roll back the conversation (files only), because fork requires an earlier turn boundary.
|
|
75
|
+
- The first user message of a session cannot roll back the conversation (files only), because fork requires an earlier turn boundary; "Edit and resend" hits the same limit — with no earlier cut point it refuses outright instead of silently degrading to a refill.
|
|
76
|
+
- The version pager only knows about versions this plugin created (recall / edit-and-resend); sessions forked by hand from the sidebar are not part of the family. The version registry lives in browser `localStorage`, so after switching browsers or clearing storage the pager no longer lists older versions (the sessions and archives themselves are unaffected).
|
|
63
77
|
- Recall cannot be initiated while an agent is running in the target workspace (by design — stop the agent first).
|
|
64
78
|
- 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
79
|
- 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.
|
|
@@ -73,7 +87,7 @@ Prerequisites:
|
|
|
73
87
|
|
|
74
88
|
- git CLI: without it the recall button won't appear (a notice shows at the top of the page); DSH itself keeps running.
|
|
75
89
|
- Shell: PowerShell 5.1 / 7 on Windows; bash on Linux/macOS.
|
|
76
|
-
- DSH version: `0.1.2-alpha.1` through `0.2.1-rc.
|
|
90
|
+
- DSH version: `0.1.2-alpha.1` through `0.2.1-rc.3`; 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).
|
|
77
91
|
- **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.
|
|
78
92
|
- `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.
|
|
79
93
|
|
|
@@ -129,6 +143,10 @@ A full recall of one user message sent after the plugin was enabled goes like th
|
|
|
129
143
|
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).
|
|
130
144
|
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.
|
|
131
145
|
|
|
146
|
+
To fix the wording only, click "✎ Edit" on the same row: an editor card styled after the official composer appears at that message, prefilled with its text — press Enter (or Send) to fork a new session with your edited text (project files untouched, attachments carried over); `Esc` cancels. The very first user message has no earlier cut point, so editing it is refused.
|
|
147
|
+
|
|
148
|
+
After a recall or an edit-and-resend, the last answer's action strip shows `‹ X/N ›`: step left or right to move between the versions of that stretch of conversation (the current one is archived and the target unarchived, so only one stays in the sidebar).
|
|
149
|
+
|
|
132
150
|
## Configuration
|
|
133
151
|
|
|
134
152
|
All options can be edited visually in the "**Settings → Plugin Config → Recall Plugin**" card (saved changes apply live, no restart needed), or by restating the insert line under `id: recall` in the profile's `cordis.patch.yml`. Environment variables only override the two gc options and take top priority (fields locked by env are marked and uneditable in the card).
|
|
@@ -138,14 +156,15 @@ All options can be edited visually in the "**Settings → Plugin Config → Reca
|
|
|
138
156
|
| `gcSnaps` | 50 | Run `git gc` after this many snapshots accumulate (env `DSH_RECALL_GC_SNAPS` force-overrides) |
|
|
139
157
|
| `gcHours` | 24 | Run gc when this many hours have passed since the last one (whichever trigger fires first; env `DSH_RECALL_GC_HOURS`) |
|
|
140
158
|
| `maxFileBytes` | 8388608 (8MB) | Files larger than this are neither snapshotted nor touched by recalls |
|
|
141
|
-
| `maxWorkspaceBytes` | 268435456 (256MB) | Total snapshot-eligible file size per workspace. Exceeding it **disables snapshots for that workspace** (no truncation — a truncated snapshot would make rollback delete the skipped files) and shows a notice on recent messages; raise the limit or add exclusions to recover automatically. 0 = unlimited |
|
|
142
|
-
| `scanBudgetMs` | 10000 (10s) | Time budget for the
|
|
159
|
+
| `maxWorkspaceBytes` | 268435456 (256MB) | Total snapshot-eligible file size per workspace, measured exactly as `git add` sees it (`.gitignore`, `.git/info/exclude` and the global excludesFile all apply). Exceeding it **disables snapshots for that workspace** (no truncation — a truncated snapshot would make rollback delete the skipped files) and shows a notice on recent messages; raise the limit or add exclusions to recover automatically. 0 = unlimited |
|
|
160
|
+
| `scanBudgetMs` | 10000 (10s) | Time budget for measuring the snapshot-eligible total. Exceeding it skips the snapshot with a notice (guards against pathological directories stalling startup). 0 = unlimited |
|
|
143
161
|
| `maxSnapshotsPerWorkspace` | 500 | Maximum snapshots kept per workspace; oldest pruned beyond the cap. 0 = unlimited |
|
|
144
162
|
| `retentionDays` | 0 | Keep snapshots for this many days; older ones are deleted. 0 = disabled (works independently of the cap) |
|
|
145
163
|
| `baseExcludes` | `.git`, `node_modules/`, `.dsh-recall-snapshots/`, `dsh-recall-snapshots/`, `target/`, `dist/`, `build/`, `out/`, `coverage/`, `.next/`, `.nuxt/`, `.output/`, `.cache/`, `.gradle/`, `*.exe`, `*.dll`, `*.pdb`, `*.so`, `*.dylib`, `*.msi`, `*.zip`, `*.7z`, `*.rar`, `*.tar`, `*.tar.gz`, `*.iso` | Base exclusion list (gitignore syntax, lower priority than exclude.txt); excluded large directories are skipped as whole subtrees during snapshot scans. Directory-form entries (e.g. `target/`) mean one more thing: **when a path segment of the workspace root itself matches, snapshots are disabled for that workspace** — patterns are relative to the root, so they can never exclude the root itself (i.e. a session opened inside a build-output directory), and such directories have no rollback value anyway; remove the entry to re-enable |
|
|
146
164
|
| `refillDraft` | true | Refill the recalled message (text and attachments) into the input box after a recall |
|
|
147
165
|
| `snapshotEnabled` | true | Master snapshot switch (off = no new snapshots; existing snapshots remain recallable) |
|
|
148
166
|
| `archiveOriginal` | true | Archive the original session after a recall (off = the original session stays in the session list) |
|
|
167
|
+
| `editSubmitsImmediately` | true | "Edit and resend" sends the edited message right away; off = only refill the input box so you can press Enter yourself |
|
|
149
168
|
| `locale` | auto | UI language: `auto` follows the system (`navigator.language` starting with `zh` → Chinese, otherwise English), `zh` / `en` pin it explicitly. See "UI language" below |
|
|
150
169
|
|
|
151
170
|
The settings card also offers "Restore defaults" (one-click reset of all fields) and a "Recent errors" viewer/clearer.
|
|
@@ -175,6 +194,20 @@ The plugin manages disk usage automatically — no manual housekeeping needed:
|
|
|
175
194
|
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.
|
|
176
195
|
- **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.
|
|
177
196
|
|
|
197
|
+
## FAQ
|
|
198
|
+
|
|
199
|
+
The most common questions and their troubleshooting paths; for the full set of design boundaries see "Known Limitations" above, and environment-class failures (git missing / disk full / permission denied / …) are explained in the settings card's "Recent errors".
|
|
200
|
+
|
|
201
|
+
**No "↶ Recall" button next to a message?** Check in order of likelihood: (1) the DSH process wasn't restarted after installing — restart it and hard-refresh (Ctrl+Shift+R); (2) git CLI isn't on PATH — a notice shows at the top of the page; (3) the message predates the plugin — snapshots are only created when a message is sent; (4) snapshots are disabled for that workspace — it sits under a directory-form exclusion (e.g. opened inside `dist/`) or exceeded the `maxWorkspaceBytes` / `scanBudgetMs` guardrails, in which case recent messages carry a recovery notice (raise the limit or extend the exclusion list and it recovers automatically).
|
|
202
|
+
|
|
203
|
+
**Some files weren't restored by a recall?** They weren't in the snapshot: larger than `maxFileBytes` (8MB by default), matched by the exclusion list, or inside a nested git repository / a win32 reparse point (see "Known Limitations"). The confirmation panel's file list before a recall is the exact rollback scope — trust it.
|
|
204
|
+
|
|
205
|
+
**Snapshots eating too much disk?** Open "Settings → Plugin Config → Recall Plugin → Snapshot Manager" to see per-workspace usage, run "gc now", or delete by workspace / session / single snapshot; longer-term, lower `maxSnapshotsPerWorkspace`, set `retentionDays`, or add big directories to the exclusion list.
|
|
206
|
+
|
|
207
|
+
**How do I delete all snapshot data for good?** After uninstalling the plugin, manually delete `dsh-recall-snapshots/` under home (`$DSH_HOME/dsh-recall-snapshots/`, or `~/.dsh/dsh-recall-snapshots/` when unset); for workspaces that fell back to in-project storage, also delete `.dsh-recall-snapshots/` inside the project.
|
|
208
|
+
|
|
209
|
+
**Can the session's first message be recalled or edited?** Files yes, conversation no — fork needs an earlier turn boundary; "Edit and resend" on the first message is refused outright (the original message is still there, so resending would duplicate it).
|
|
210
|
+
|
|
178
211
|
## How It Works
|
|
179
212
|
|
|
180
213
|
When each user message is sent (before the agent touches any files), the workspace is snapshotted into an independent shadow git repository; on recall, a "pre-rollback" safety snapshot is taken first, then files are restored via `git archive` and the conversation is rewound through DSH's official `sessions.fork` mechanism. Binary- and line-ending-safe, and your project's own git state is never touched.
|
|
@@ -284,9 +317,9 @@ All source lives in `src/`: the host (Node ESM TypeScript) in `src/host/`, the b
|
|
|
284
317
|
|
|
285
318
|
### Tests
|
|
286
319
|
|
|
287
|
-
- `npm test`: pure-logic unit tests (vitest,
|
|
288
|
-
- `npm run test:client`: client component tests (vitest + jsdom,
|
|
289
|
-
- `npm run test:probe`: official-API field probes (2 files /
|
|
320
|
+
- `npm test`: pure-logic unit tests (vitest, 41 files / 524 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, artifact-root and snapshot-volume guardrails, recall-scope dispatch, client pure functions, published-package layout, snapshot index persistence, storage caps and retention, etc.;
|
|
321
|
+
- `npm run test:client`: client component tests (vitest + jsdom, 9 files / 121 cases, runs in CI) — the recall node main chain (preview→execute→fork→refill) and terminal-event reporting, the editor-card submit chain and official-content projection, the version-family registry and pager, 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;
|
|
322
|
+
- `npm run test:probe`: official-API field probes (2 files / 58 cases, 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), the composer write chain (`setDraft`/`addAttachments`/`submit`/`snapshot`/`createDrafts`) plus `loader/volatile-update` and the `Fiber.entry` shape, and goes red on violation;
|
|
290
323
|
- `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;
|
|
291
324
|
- `npm run typecheck`: full `tsc --noEmit` type check (includes the `tests/types` compile-time contract assertions; CI runs it before the unit suite);
|
|
292
325
|
- `npm run build`: full host+client build (mandatory after any `src/` change); `npm run check:dsh`: dsh version inspection (pre-release); `npm run check:upgrade`: one-shot dsh-upgrade verification gate (chains check:dsh + test:probe + verify:host; mandatory after a dsh upgrade).
|
package/README.md
CHANGED
|
@@ -5,11 +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-rc.3)
|
|
9
9
|
|
|
10
10
|
在任意一条你发过的消息下方点「↶ 撤回」——**工作区文件和对话历史一起回到那条消息发出之前的状态**。
|
|
11
11
|
|
|
12
|
-
每条消息发送时,工作区先被快照进一个独立的影子 git 仓库(不碰项目自身的 git);撤回时按快照恢复文件,并经 DSH 官方 `sessions.fork` 把对话切回该消息之前的 turn
|
|
12
|
+
每条消息发送时,工作区先被快照进一个独立的影子 git 仓库(不碰项目自身的 git);撤回时按快照恢复文件,并经 DSH 官方 `sessions.fork` 把对话切回该消息之前的 turn 边界,原会话归档保留、随时可找回。被撤回消息的文本与附件自动放回输入框,改完即可重发;也可以点消息旁的「✎ 编辑」就地改完直接重发(不动文件),同一段对话的多个版本经 `‹ X/N ›` 翻页器来回切换。快照存储默认落在 `$DSH_HOME` 下。最主要的边界:快照只在**消息发送时**创建,插件启用前的历史消息没有快照、不显示撤回按钮。
|
|
13
13
|
|
|
14
14
|
[更新日志](CHANGELOG.md)
|
|
15
15
|
|
|
@@ -17,11 +17,13 @@
|
|
|
17
17
|
|
|
18
18
|
- [界面预览](#界面预览)
|
|
19
19
|
- [功能亮点](#功能亮点)
|
|
20
|
+
- [技术栈与依赖](#技术栈与依赖)
|
|
20
21
|
- [已知限制](#已知限制)
|
|
21
22
|
- [安装](#安装)
|
|
22
23
|
- [使用](#使用)
|
|
23
24
|
- [配置项](#配置项)
|
|
24
25
|
- [快照维护与清理](#快照维护与清理)
|
|
26
|
+
- [常见问题](#常见问题)
|
|
25
27
|
- [工作原理](#工作原理)
|
|
26
28
|
- [事件契约(供同宿主插件监听)](#事件契约供同宿主插件监听)
|
|
27
29
|
- [参与贡献](#参与贡献)
|
|
@@ -46,17 +48,29 @@
|
|
|
46
48
|
- **不碰项目自身的 git,目录保持干净**:快照存在独立的影子 git 仓库,分支、暂存区、未提交改动统统不受影响;存储默认落在 `$DSH_HOME` 下、与会话沙箱权限无关(workspace-write / read-only 照常工作),仅当 home 不可写才降级到项目内 `.dsh-recall-snapshots`。
|
|
47
49
|
- **先看清单再动手,可反复后悔**:撤回前展示将变更的文件清单(修改 / 恢复 / 删除),确认后才执行;撤回后还能再撤到更早,被覆盖的文件一直找得回来。
|
|
48
50
|
- **撤回完就能重发**(2.3.15+):消息的文本与附件(图片、文件)一起放回输入框,改完直接发送,不必重新挑一遍附件。
|
|
51
|
+
- **只改一个字也不用三连**(2.5.0+):消息旁的「✎ 编辑」在该消息位置长出一张与官方输入框同款的编辑卡(预填原文、随内容增高),**底部输入框保持原样**;卡内可改字、粘贴/拖入附件,按 Enter 或点发送即新建会话、把改后的文本自动发出(原会话照常归档可找回),但**只回退对话、不动项目文件**。原消息附件以可移除的标签显示,权限以只读标签展示,模型与推理等级可在卡内按官方同款菜单切换(同步到底部输入框);`Esc` 取消。不想立刻发可在设置里关掉「编辑后自动发送」,改为只回填输入框。
|
|
52
|
+
- **同一句话的多个版本可以来回翻**(2.5.0+):每次撤回或编辑重发都会 fork 出新会话,最后一轮回答的操作区出现 `‹ X/N ›`——点一下切到别的版本,侧栏始终只留当前那个(其余自动归档),切回时视口停回原位置。
|
|
49
53
|
- **撤回全程有防护**(2.0+,救援 2.1+):预览后出现新快照会强制重新预览;执行前自动打「回退前」安全快照,回退失败自动救援,救援失败给出可直接复制执行的手动恢复命令。
|
|
50
54
|
- **磁盘友好、自动维护**:快照走 git delta 增量压缩,大文件自动跳过(阈值可配);定期无损 `git gc`、会话删除联动清理、按条数与保留天数自动清理;设置页提供**工作区 → 会话 → 快照**三级树形管理,支持搜索与分级删除。
|
|
51
55
|
- **失败不静默、能自愈**(自愈 2.1+):失败按根因分类(git 缺失 / 磁盘满 / 无权限 / 锁冲突 / 目录冲突)给出可行动提示,同类故障 10 分钟只打扰一次,原因进设置卡片「最近错误」;自动清理残骸、连续 3 次失败指数退避、多实例按心跳互让;无法索引的路径跳过并告知,不中断整条快照(撤回时也不触碰它们)。
|
|
52
56
|
|
|
57
|
+
## 技术栈与依赖
|
|
58
|
+
|
|
59
|
+
本插件是 DSH bundle 形态:host 半跑在 DSH 进程内(Node ESM),client 半跑在浏览器会话页内,两侧同出一个 npm 包,包本体零运行时 dependencies。
|
|
60
|
+
|
|
61
|
+
- **运行时**:Node.js ≥ 20、git CLI;Windows 走 PowerShell 5.1/7,Linux/macOS 走 bash(按平台自动分叉)。
|
|
62
|
+
- **语言与构建**:TypeScript + ESM;`npm run build` 经 esbuild 产出 `lib/`(host 逐文件转译、client 打包为单文件),产物随源码提交。
|
|
63
|
+
- **测试与门禁**:vitest(纯逻辑单测、jsdom client 组件测试、官方 API 字段探针)+ `tsc --noEmit` 类型门禁 + 装配门禁脚本,详见「参与贡献 → 测试」。
|
|
64
|
+
- **宿主依赖**(peerDependencies,随 DSH 安装提供,无需单独安装):`@deepseek-ai/cordis` 4、`@deepseek-ai/dsh-*`(host-webserver / sandbox-policy / session / session-query / settings / shell)、`@deepseek-ai/schemastery` 3.18;`react` 18 为可选 peer(client 由宿主注入)。
|
|
65
|
+
|
|
53
66
|
## 已知限制
|
|
54
67
|
|
|
55
68
|
设计上接受的边界与尚未覆盖的极端情形,使用前值得先确认。
|
|
56
69
|
|
|
57
70
|
- 快照在**消息发送时**创建:插件启用前的历史消息没有快照,不显示撤回按钮。
|
|
58
71
|
- 快照是**尽力捕获**:从收到消息到 `git add` 之间有约 0.5–1.5 秒窗口(Windows 上仅一次 PowerShell 启动就占约 0.4 秒)。秒级任务若在这个窗口内改完文件,该快照会连带捕获本轮改动——撤回时文件回退成为空操作(预览面板显示「共 0 个文件将变更」),对话回退不受影响。
|
|
59
|
-
- 会话第一条用户消息无法回退对话(仅文件回退),因为 fork 需要更早的 turn
|
|
72
|
+
- 会话第一条用户消息无法回退对话(仅文件回退),因为 fork 需要更早的 turn 边界;「编辑并重发」同样受此限制——没有更早的切点时会明确拒绝,而不是静默降级成只回填。
|
|
73
|
+
- 版本翻页器只识别本插件产生的版本(撤回 / 编辑重发);会话列表里手动 fork 出来的会话不计入家族。版本登记存在浏览器 `localStorage`,换浏览器或清缓存后翻页器不再显示历史版本(会话本身与归档不受影响)。
|
|
60
74
|
- 目标工作区的 agent 正在运行时无法发起撤回(防护设计,先停止 agent 再撤回)。
|
|
61
75
|
- 支持 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
76
|
- 工作区内嵌套的其他 git 仓库(子目录自带 `.git`)无法索引:其余部分快照照常(fail-open,页面会提示跳过了哪些路径),但其内容不参与回退。
|
|
@@ -70,7 +84,7 @@
|
|
|
70
84
|
|
|
71
85
|
- git CLI:未安装时撤回按钮不出现(页面顶部会提示安装 git),不影响 DSH 运行。
|
|
72
86
|
- shell:Windows 上 PowerShell 5.1 / 7 均可;Linux/macOS 需 bash。
|
|
73
|
-
- DSH 版本:`0.1.2-alpha.1` 至 `0.2.1-rc.
|
|
87
|
+
- DSH 版本:`0.1.2-alpha.1` 至 `0.2.1-rc.3`;已核验 minor 线内的后续版本自动放行,未核验的新 minor 线会被启动期兼容门禁拦截(开窗机制见下方折叠块)。
|
|
74
88
|
- **0.1.7-alpha.1 是破坏性版本**(shell 执行与 settings 面接口更换),插件内置双分支共存适配:同一发布同时兼容 0.1.2–0.1.6 各线与 0.1.7+,老版本 DSH 上行为不变。
|
|
75
89
|
- `0.1.1-rc.2` 及更早不支持:那条线的客户端运行时没有 `sessions`/`workspaces`/`uiWorkspace` 服务,插件 UI 会静默不渲染。
|
|
76
90
|
|
|
@@ -126,6 +140,10 @@ peerDependencies 按 minor 版本线开窗,与 `dsh.compatibility.dshReleases`
|
|
|
126
140
|
2. 点击 → 确认面板展示将变更的文件清单(修改 / 恢复 / 删除),并可选撤回范围:「回退文件与对话」(默认)或「仅撤回对话」(文件保持当前状态)。
|
|
127
141
|
3. 点「确认回退」(或「确认撤回对话」)→ 文件恢复到该消息发送前的状态(仅撤回对话时文件不动);视图切到新会话(该消息及之后的对话移除),原会话归档、随时可找回。
|
|
128
142
|
|
|
143
|
+
只想改文字重发,点同一行的「✎ 编辑」:该消息位置出现同款编辑卡并预填原文,改完按 Enter(或点发送)——新建会话并把改后的消息发出(项目文件不动,附件一并带过去);`Esc` 取消。第一条用户消息没有可回退的对话切点,编辑会被明确拒绝。
|
|
144
|
+
|
|
145
|
+
撤回或编辑重发之后,最后一轮回答的操作区出现 `‹ X/N ›`:左右切换即在该段对话的各版本间跳转(当前版本自动归档、目标版本自动恢复,侧栏只留一个)。
|
|
146
|
+
|
|
129
147
|
## 配置项
|
|
130
148
|
|
|
131
149
|
全部配置可在「**设置 → 插件配置 → 撤回插件**」卡片可视化修改(保存即热生效,无需重启),也可在 profile 的 `cordis.patch.yml` 按 `id: recall` 重述 insert 行改写;env 变量仅覆盖 gc 两项且优先级最高(设了 env 的字段在卡片里锁定)。
|
|
@@ -135,14 +153,15 @@ peerDependencies 按 minor 版本线开窗,与 `dsh.compatibility.dshReleases`
|
|
|
135
153
|
| `gcSnaps` | 50 | 每积累多少条快照触发一次 `git gc`(env `DSH_RECALL_GC_SNAPS` 可强制覆盖) |
|
|
136
154
|
| `gcHours` | 24 | 距上次 gc 超过多少小时触发(与条数先到先触发;env `DSH_RECALL_GC_HOURS`) |
|
|
137
155
|
| `maxFileBytes` | 8388608(8MB) | 超过该大小的文件不进快照、不被回退触碰 |
|
|
138
|
-
| `maxWorkspaceBytes` | 268435456(256MB) |
|
|
139
|
-
| `scanBudgetMs` | 10000(10 秒) |
|
|
156
|
+
| `maxWorkspaceBytes` | 268435456(256MB) | 单工作区可纳入快照的文件总量上限,口径与 `git add` 一致(`.gitignore`、`.git/info/exclude`、全局 excludesFile 都算数)。超过时**停用该工作区快照**(不截断——截断快照回退时会误删未纳入的文件),并在最近消息上提示;调大该值或补充排除表后自动恢复;0 = 不限制 |
|
|
157
|
+
| `scanBudgetMs` | 10000(10 秒) | 快照前统计纳入总量的时间预算。超过时停用本次快照并提示(防病态大目录拖死启动);0 = 不限制 |
|
|
140
158
|
| `maxSnapshotsPerWorkspace` | 500 | 每个工作区保留的最大快照数,超限自动删除最旧的;0 = 不限制 |
|
|
141
159
|
| `retentionDays` | 0 | 按天数保留快照,超期自动删除;0 = 不启用(与条数上限各自独立生效) |
|
|
142
160
|
| `baseExcludes` | `.git`、`node_modules/`、`.dsh-recall-snapshots/`、`dsh-recall-snapshots/`、`target/`、`dist/`、`build/`、`out/`、`coverage/`、`.next/`、`.nuxt/`、`.output/`、`.cache/`、`.gradle/`、`*.exe`、`*.dll`、`*.pdb`、`*.so`、`*.dylib`、`*.msi`、`*.zip`、`*.7z`、`*.rar`、`*.tar`、`*.tar.gz`、`*.iso` | 基础排除表(gitignore 语法,优先级低于 exclude.txt);被排除的大目录整棵子树不进快照扫描。其中「目录形态」项(如 `target/`)另有一层含义:**工作区根自身的路径段命中时,该工作区不启用快照**——在构建产物目录里开会话时排除表本来就管不到它自己(模式是相对该 root 的),这类目录也没有回退价值;删掉对应项即恢复 |
|
|
143
161
|
| `refillDraft` | true | 撤回后把被撤回的消息(文本与附件)回填到输入框 |
|
|
144
162
|
| `snapshotEnabled` | true | 快照总开关(关闭只冻结新建,已有快照仍可撤回) |
|
|
145
163
|
| `archiveOriginal` | true | 撤回后归档原会话(关闭后原会话保留在会话列表中) |
|
|
164
|
+
| `editSubmitsImmediately` | true | 「编辑并重发」提交后自动发出改后的消息;关闭则只回填输入框,由你按回车发送 |
|
|
146
165
|
| `locale` | auto | 界面语言:`auto` 跟随系统(`navigator.language` 以 `zh` 开头用中文,否则英文)、`zh` / `en` 显式锁定。见下方「界面语言」 |
|
|
147
166
|
|
|
148
167
|
设置卡片另提供「恢复默认」(一键重置全部字段)与「最近错误」查看/清空。
|
|
@@ -172,6 +191,20 @@ peerDependencies 按 minor 版本线开窗,与 `dsh.compatibility.dshReleases`
|
|
|
172
191
|
对所有项目生效(home 不可写而降级到项目内存储时,该工作区有独立的排除配置,设置页会分卡片列出)。新增排除只影响之后的快照;**回退到更早的快照时,当时尚未排除的文件仍会被恢复**(回到当时的状态,这正是回退语义)。想彻底清掉已进快照的目录,可手动删除 home 下 `dsh-recall-snapshots/` 里对应项目的哈希目录。
|
|
173
192
|
- **树形快照管理**:打开「**设置 → 插件配置 → 撤回插件 → 快照管理**」可看到三级树——工作区(文件夹名)→ 会话(会话标题,撤回链聚成版本家族)→ 快照(时间 + 消息内容摘要,悬停看完整内容)。支持搜索与「加载更多」,工作区与会话节点可展开/折叠;每一级右侧都有删除按钮,删除前二次确认——删工作区 = 清掉该工作区全部快照,删会话 = 清掉该会话全部快照,删叶子 = 只删那一条;顶部另有带确认的「全部删除」。
|
|
174
193
|
|
|
194
|
+
## 常见问题
|
|
195
|
+
|
|
196
|
+
使用中最常遇到的几类问题与排查路径;更完整的设计边界见上文「已知限制」,环境类失败(git 缺失 / 磁盘满 / 无权限等)的原因在设置卡片「最近错误」里可查。
|
|
197
|
+
|
|
198
|
+
**消息旁没有「↶ 撤回」按钮?** 按概率排查:① 装完没重启 DSH 进程——重启后硬刷新页面(Ctrl+Shift+R);② git CLI 不在 PATH——页面顶部会有安装提示;③ 该消息发送于插件启用之前——快照只在消息发送时创建,历史消息无快照;④ 该工作区被停用快照——工作区开在 `dist/`、`target/` 这类命中基础排除表目录形态的位置,或超出 `maxWorkspaceBytes` / `scanBudgetMs` 护栏,最近消息上会给出恢复提示(调大上限或补充排除表后自动恢复)。
|
|
199
|
+
|
|
200
|
+
**撤回后个别文件没有恢复?** 该文件不在快照里:超过 `maxFileBytes`(默认 8MB)、命中排除表,或位于嵌套 git 仓库 / win32 目录重解析点内(见「已知限制」)。撤回前确认面板的清单就是实际回退范围,以它为准。
|
|
201
|
+
|
|
202
|
+
**快照占用磁盘太多?** 打开「设置 → 插件配置 → 撤回插件 → 快照管理」查看各工作区占用,可「立即 gc」或按工作区 / 会话 / 单条分级删除;长期可压低 `maxSnapshotsPerWorkspace`、设置 `retentionDays`,或把大目录补进排除表。
|
|
203
|
+
|
|
204
|
+
**如何彻底删除全部快照数据?** 卸载插件后手动删除 home 下 `dsh-recall-snapshots/`(即 `$DSH_HOME/dsh-recall-snapshots/`,未设置时为 `~/.dsh/dsh-recall-snapshots/`);曾降级存储的工作区另删项目内 `.dsh-recall-snapshots/`。
|
|
205
|
+
|
|
206
|
+
**会话第一条消息能撤回或编辑吗?** 文件可以回退,对话不能——fork 需要该消息之前的 turn 边界;「编辑并重发」对首条消息会被明确拒绝(原消息还在,补发即成重复消息)。
|
|
207
|
+
|
|
175
208
|
## 工作原理
|
|
176
209
|
|
|
177
210
|
每条用户消息发送时(agent 动文件之前),工作区被快照进一个独立的影子 git 仓库;撤回时先打「回退前」安全快照、再用 `git archive` 恢复文件、通过 DSH 官方 `sessions.fork` 机制把会话切到该消息之前。二进制与换行符安全,全程不触碰项目自身的 git 状态。
|
|
@@ -281,9 +314,9 @@ pnpm install
|
|
|
281
314
|
|
|
282
315
|
### 测试
|
|
283
316
|
|
|
284
|
-
- `npm test`:纯逻辑单测(vitest,
|
|
285
|
-
- `npm run test:client`:client 组件测试(vitest + jsdom,
|
|
286
|
-
- `npm run test:probe`:官方 API 字段探针(2 个文件
|
|
317
|
+
- `npm test`:纯逻辑单测(vitest,41 个文件 524 例,无 DSH 依赖,CI 与本地同跑)——配置解析、快照解析器、救援编排、错误分类、磁盘格式守卫、操作意图 journal、撤回终态事件上报组装、i18n 词典与 key 漏配扫描、脚本模板同名导出契约、执行通道双分支与失败分级、settings 双代桥接、产物 root 与快照总量护栏、撤回 scope 分流、客户端纯函数、发布包内容布局、快照索引持久化、存储上限与保留天数等;
|
|
318
|
+
- `npm run test:client`:client 组件测试(vitest + jsdom,9 个文件 121 例,CI 同跑)——撤回节点主链(preview→execute→fork→回填)与终态事件上报、编辑卡提交链与官方内容投影、版本家族登记与翻页、快照管理树与删除流、配置/排除卡片错误路径、logger 开关矩阵、zh/en 双语渲染链;断言只锁行为与结构(className / aria / 请求载荷),不锁文案字面量;
|
|
319
|
+
- `npm run test:probe`:官方 API 字段探针(2 个文件 58 例,依赖本机 dsh 安装;dsh 升级后本地必跑)——钉住 `renderMessageImages`/`node`/`cwd`、`sessions.fork` 的 `atSeq`/`increaseTitle` 与切点锚点、`listSessions` 记录结构、`AgentRegistry`、shell 执行接缝(`execute`/`ShellExecution.result`)、settings 面(`SettingsForms`/profile 条目 id/volatile 门槛)、输入区写入链(`setDraft`/`addAttachments`/`submit`/`snapshot`/`createDrafts`)与 `loader/volatile-update`/`Fiber.entry` 形状等字段,违反即红;
|
|
287
320
|
- `npm run verify:host`:装配门禁(依赖本机 dsh 安装)——用真实 cordis 起插件跑两个 pass(旧面桩 + 只给新面的桩),断言 inject 声明、端点注册、Config schema、卸载清理与 settings 面分派,装配回归发版前即可拦截;
|
|
288
321
|
- `npm run typecheck`:`tsc --noEmit` 全量类型检查(含 `tests/types` 编译期契约断言,CI 中置于单测之前);
|
|
289
322
|
- `npm run build`:host+client 全量打包(改任何 `src/` 后必跑);`npm run check:dsh`:dsh 版本巡检(发布前);`npm run check:upgrade`:dsh 升级一键核验门禁(串联 check:dsh + test:probe + verify:host,dsh 升级后必跑)。
|