dsh-rewind-plugin 0.6.2 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.en.md CHANGED
@@ -12,7 +12,7 @@ A deliberately focused plugin with one job: **rewind to any user message, no mat
12
12
 
13
13
  - **Rewinding is time-travel** — the target message and everything after it (agent replies, tool calls) are withdrawn from the model context *and* the rendered transcript at once, with no new session and no window switch; the target's text is offered back in the composer so you can edit and re-send it — **truly seamless and convenient by design**.
14
14
  - **Lightweight workspace backup** — Claude Code-aligned behavior: tracks files edited by the write-class tools, and external changes to **already-tracked** files are restorable too. Selective tracking, before-write backup, store only on change. One lightweight plugin gives you a **complete** agentic rewind capability.
15
- - **Privacy-first** — the plugin never deletes or rewrites the session log (append-only) and never actually deletes any of your conversation; file restores stay inside the plugin's own backup directory. Full security model: [SECURITY.md](SECURITY.md).
15
+ - **Privacy-first** — the plugin never deletes or rewrites the session log (append-only) and never actually deletes any of your conversation; backups live in the plugin's own snapshot directory; restores draw only from those backups. Full security model: [SECURITY.md](SECURITY.md).
16
16
  - **A complete test system** — unit, probe, and end-to-end host verification, covering compatibility probing, log replay, resume, cross-restart and other scenarios; maintained continuously as the harness evolves to ensure feature stability.
17
17
 
18
18
  ## Preview
@@ -66,10 +66,13 @@ clears the file backups (chat rewinds are unaffected) and the plugin rebuilds th
66
66
  automatically.
67
67
 
68
68
  A **global auto-cleanup** (off by default) removes the snapshot directories of
69
- **long-inactive** sessions, leaving the active session and chat log untouched. Use
70
- `/snapshot-auto-cleanup` to **view, configure, and run** it; the settings live in
71
- `<dsh home>/snapshot-cleanup.json` and the last-sweep time in
72
- `<dsh home>/snapshot-cleanup-last-sweep.json`. See: [Snapshot cleanup](docs/snapshot-auto-cleanup.md).
69
+ long-inactive sessions, leaving the active session and chat log untouched. Configure
70
+ and review it in the **Settings &gt; Plugins &gt; Plugin configuration &gt; Snapshot
71
+ cleanup** panel (the auto-cleanup switch and the idle-day cutoff), or use the
72
+ `/snapshot-auto-cleanup` command to **view, configure, and run** it. See:
73
+ [Snapshot cleanup](docs/snapshot-auto-cleanup.md).
74
+
75
+ <img src="assets/screenshots/cleanup-setting.png" alt="Snapshot cleanup settings: auto-cleanup and idle days" width="600">
73
76
 
74
77
  ## Why it stands out
75
78
 
@@ -98,14 +101,15 @@ The whole design rests on two principles, simple but deliberate: **the conversat
98
101
 
99
102
  ### 2. File restore: lightweight checkpointing, "back up before the change"
100
103
 
101
- The file half follows Claude Code's checkpoint semantics — **per-file before-backups plus a re-scan of tracked files at each message**, not a whole-tree snapshot. This trade-off saves space, and it's actually more complete:
104
+ The file half follows Claude Code's checkpoint semantics — **partial tracking + before-write backup, plus a re-scan of tracked files at each message**, not a whole-tree snapshot. This trade-off saves space, and it's actually more complete:
102
105
 
103
- - **Before-backup**: tracks the write-class tools (`write`, `edit`, `str_replace_editor`) and stores the original content **before** each write. Timing is the key it captures after any approval gate lets the call through: an approval short-circuit can't skip the backup, and a denied call never records; a read failure only warns, never blocks the write. Backups are grouped by conversation turn and **persist on disk** across restarts.
104
- - **External changes count too**: at every user-message boundary the plugin re-checks all tracked files — edits or deletions made outside the write tools are recorded as well and restored by a later rewind. "Lightweight" but not "incomplete".
105
- - **Reconcile against the real disk before restoring**: the most interesting decision. At rewind time the plugin reads each file's current content and compares it to the target state **only files that actually differ are touched**: modified files are written back to their earliest backup, files created after the target are deleted, files already matching are skipped. Repeated rewinds are therefore **idempotent with zero side effects** and never produce "ghost impact".
106
+ - **Before-write backup**: tracks only the write-class tools (`write`, `edit`, `str_replace_editor`) backs up the original content before a write and records/tracks the files it touches; it never backs up the whole workspace, so it's lightweight.
107
+ - **External changes count too**: at every user-message boundary the plugin re-checks all tracked files — external changes such as a command run or a manual edit are recorded as well and restored by a later rewind. "Lightweight" but not "incomplete".
108
+ - **Unchanged-not-recorded, identical-content-as-link**: an entry is written only when something changed at the message-boundary re-check, an unchanged file is never backed up (no record); at before-write time, when the new content matches the path's prior record, only a **link to it** (`ref`) is stored instead of a copy. Repeated writes cost almost nothing, and a link is materialized before its group is evicted never left dangling.
109
+ - **Reconcile against the real disk before restoring**: restore takes each path's **earliest** record, then reads the live file and compares — **only files that actually differ are touched**: modified files are written back to the earliest backup, files created after the target are deleted, already-matching files are skipped. Repeated rewinds are therefore **idempotent with zero side effects** and never produce "ghost impact".
106
110
  - **Safety boundary**: symlinks / hard links are skipped so one restore can't clobber another name of the same file; paths are sanitized so nothing ever escapes the backup root; a per-file failure never aborts the pass.
107
111
 
108
- > **Design highlight**: **"reconcile against the real disk before acting"** is the most insightful decision in this checkpoint design — it never assumes blindly; it trusts the disk, doing what must be done and skipping what must not.
112
+ > **Design highlight**: this checkpoint's light footprint comes from **recording only what was actually touched and really changed**before-write backup makes it restorable, unchanged-not-recorded and content-as-link drop the repetition; which files to touch is decided against the real disk at restore time.
109
113
 
110
114
  ### Design highlights
111
115
 
@@ -132,7 +136,7 @@ This plugin deliberately stays lightweight and focused on one thing — "convers
132
136
  ## Compatibility
133
137
 
134
138
  - Node.js `^22.19.0 || >=24.0.0`.
135
- - DeepSeek Harness web profile (`dsh --profile web`); peer `@deepseek-ai/*` packages are resolved by the harness at runtime.
139
+ - Compatibility definition, verification method, and version alignment: see [docs/compat/audit.md](docs/compat/audit.md); supported DSH versions are declared by `package.json` `peerDependencies`.
136
140
 
137
141
  > [!WARNING]
138
142
  > This project and DeepSeek Harness are both in developer preview. Pin exact
@@ -156,7 +160,7 @@ withdrew should consume the stable, locale-independent helpers exported from
156
160
 
157
161
  ## Security
158
162
 
159
- This plugin only appends rewind-marker events to the session log; it never deletes or rewrites logged history. Workspace files are written only when you choose "conversation and code"; backups and restores stay under `<dsh home>/rewind-snapshots/`. It never touches your git repository, makes no network requests, and accesses no credentials. Delete `~/.dsh/rewind-snapshots/` to wipe file backups only (chat rewinds are unaffected); the plugin rebuilds automatically. For sessions you've left inactive for a long time, a global auto-cleanup (off by default) can remove their snapshot directory in whole, leaving the active session and the chat log untouched. Full security model: [SECURITY.md](SECURITY.md).
163
+ This plugin only appends rewind-marker events to the session log; it never deletes or rewrites logged history. Workspace files are written only when you choose "conversation and code"; backups are stored under `<dsh home>/rewind-snapshots/`; restores draw only from those backups. It never touches your git repository, makes no network requests, and accesses no credentials. Delete `~/.dsh/rewind-snapshots/` to wipe file backups only (chat rewinds are unaffected); the plugin rebuilds automatically. For sessions you've left inactive for a long time, a global auto-cleanup (off by default) can remove their snapshot directory in whole, leaving the active session and the chat log untouched. Full security model: [SECURITY.md](SECURITY.md).
160
164
 
161
165
  ## Development
162
166
 
package/README.md CHANGED
@@ -12,7 +12,7 @@ DeepSeek Harness 插件:**一键就地回退对话到任意更早的用户消
12
12
 
13
13
  - **回退 = 时间回溯**——目标消息及其之后的全部内容(agent 回复、工具调用)同时从**模型上下文**和**渲染对话**中撤回,不新建会话、不切换窗口;目标消息文本会回填输入框,改完可重发。**在原理上就真正无感、便捷**。
14
14
  - **轻量工作区备份**——对齐 Claude Code:追踪写类工具编辑过的文件,**已跟踪文件的外部变更也能还原**。局部追踪、写前备份、不变不存。一个轻型插件,即拥有**完备的智能体回退能力**。
15
- - **信息安全优先**——插件从不删改会话日志(append-only),从不真正删除你的任何对话;文件还原限定在插件自己的备份目录。完整安全模型:[SECURITY.md](SECURITY.md)。
15
+ - **信息安全优先**——插件从不删改会话日志(append-only),从不真正删除你的任何对话;备份存于自己的快照目录;还原只用这些备份。完整安全模型:[SECURITY.md](SECURITY.md)。
16
16
  - **完备测试系统**——单元、探针、端到端主机验证,覆盖兼容性探测、日志重放、续接、跨重启等场景;随 harness 升级持续维护,确保功能稳定。
17
17
 
18
18
  ## 效果预览
@@ -60,7 +60,9 @@ dsh plugin --profile web add dsh-rewind-plugin
60
60
 
61
61
  快照(写前备份)存储于 `<dsh home>/rewind-snapshots/`(未设 `$DSH_HOME` 时即 `~/.dsh/rewind-snapshots/`)。插件对**同一会话**的快照做内容去重(内容未变则存为链接)并保留最近 100 组锚点;**手动删除该目录**仅清除文件备份(对话回退不受影响),插件会自动重建。
62
62
 
63
- 另提供**全局自动清理**(默认关闭):把**长期不活跃**的会话快照整目录移除,不影响活动会话与对话日志。用 `/snapshot-auto-cleanup` 命令**查看、设置和运行**,配置写入 `<dsh home>/snapshot-cleanup.json`;最近一次自动清扫的时间记录在 `<dsh home>/snapshot-cleanup-last-sweep.json`。详见:[快照自动清理](docs/snapshot-auto-cleanup.zh.md)。
63
+ 另提供**全局自动清理**(默认关闭):把长期不活跃的会话快照整目录移除,不影响活动会话与对话日志。可在 `设置→插件→插件配置→快照清理` 面板查看与配置(自动清理开关、失活天数),也可用 `/snapshot-auto-cleanup` 命令查看、设置和运行。详见:[快照自动清理](docs/snapshot-auto-cleanup.zh.md)。
64
+
65
+ <img src="assets/screenshots/cleanup-setting.png" alt="快照清理设置:自动清理与失活天数" width="600">
64
66
 
65
67
  ## 本插件的优势
66
68
 
@@ -89,14 +91,15 @@ dsh plugin --profile web add dsh-rewind-plugin
89
91
 
90
92
  ### 2. 文件还原:轻量检查点,「改前备份」
91
93
 
92
- 文件部分对齐 Claude Code 的检查点语义——**逐文件、写前备份 + 每条消息重扫已跟踪文件**,而不是整树快照。这项取舍既省空间,又更完整:
94
+ 文件部分对齐 Claude Code 的检查点语义——**局部追踪、写前备份 + 每条消息重扫已跟踪文件**,而不是整树快照。这项取舍既省空间,又更完整:
93
95
 
94
- - **改前备份**:追踪写类工具(`write`、`edit`、`str_replace_editor`),在**每次写文件之前**先把原内容存下来。关键在时机——在审批门放行之后捕获:审批短路不会漏备份,被拒绝的调用也不会留下记录;读取失败只警告、从不阻塞写操作。备份按对话轮次分组锚定,**落盘持久化**,重启也还在。
95
- - **外部变更也追**:每条用户消息边界,插件重新检查所有已跟踪文件——那些从没经过写工具、被外部改过或删掉的文件,同样被记录,回退时一并还原。这让「轻量」却不「残缺」。
96
- - **还原前对照真实磁盘**:这是最值得说的一点。回退时插件实时读取文件当前内容,与目标状态逐一比对——**只操作真正不一致的文件**:改过的写回最早备份、目标之后新建的删除、已经一致的跳过。重复回退因此**零副作用、幂等**,绝不会出现「幽灵影响」。
96
+ - **写前备份**:只追踪写类工具(`write`、`edit`、`str_replace_editor`),写前**备份原内容**,并**记录、追踪**被处理的文件——从不备份整个工作区,因此轻量。
97
+ - **外部变更也追**:每条用户消息边界,插件重新检查所有已跟踪文件——命令执行、手动修改等外部变更同样被记录,回退时一并还原。这让「轻量」却不「残缺」。
98
+ - **不变不存、同内容存链接**:记录只在有变化时发生——消息边界重扫时无变更的不备份(不留记录);写前备份时若与前一条记录一致,只存**指向它的链接**(`ref`)而非复制内容。重复写入几乎不占空间,链接也先落地、绝不悬空。
99
+ - **还原时对照真实磁盘**:先取每条路径的**最早**记录,再实时读取文件当前内容与之比对——**只操作真正不一致的文件**:改过的写回最早期内容、目标之后新建的删除、已经一致的跳过。重复回退因此**零副作用、幂等**,不会出现「幽灵影响」。
97
100
  - **安全边界**:符号/硬链接跳过,避免透过一个还原误伤另一个名字;路径经安全化处理,**绝不越出备份根目录**;单个文件失败绝不中止整轮还原。
98
101
 
99
- > **设计点睛**:**「对照真实磁盘再动手」** 是这套检查点里最有洞察力的决定——它从不盲目假设,而是以磁盘为准,该做的做、不该做的跳过。
102
+ > **设计点睛**:这套检查点的「轻」,来自**只记录被工具动过、且确实变化的文件**——写前备份保证可还原,不变不存与存链接压掉重复;还原时再对照真实磁盘,只动不一致的文件。
100
103
 
101
104
  ### 设计亮点一览
102
105
 
@@ -123,7 +126,7 @@ dsh plugin --profile web add dsh-rewind-plugin
123
126
  ## 兼容性
124
127
 
125
128
  - Node.js `^22.19.0 || >=24.0.0`。
126
- - DeepSeek Harness web 配置档(`dsh --profile web`);peer `@deepseek-ai/*` 包由 harness 运行时解析。
129
+ - 兼容性定义、验证方法与版本对齐详见 [docs/compat/audit.md](docs/compat/audit.md);支持的 DSH 版本由 `package.json` `peerDependencies` 声明。
127
130
 
128
131
  > [!WARNING]
129
132
  > 本项目与 DeepSeek Harness 均处于开发者预览阶段。可复现环境请 pin 精确版本,
@@ -144,7 +147,7 @@ dsh plugin --profile web add dsh-rewind-plugin
144
147
 
145
148
  ## 安全
146
149
 
147
- 本插件只向会话日志追加回退标记事件,从不删除或改写已记录的历史。工作区文件仅在「回退对话和代码」时被改写,备份与还原都限定在 `~/.dsh/rewind-snapshots/` 内。不触碰你的 git 仓库,无网络请求,不访问任何凭据。对**长期不活跃**的会话,另有默认关闭的全局自动清理可整目录移除其快照,不影响活动会话与对话日志。完整安全模型:[SECURITY.md](SECURITY.md)。
150
+ 本插件只向会话日志追加回退标记事件,从不删除或改写已记录的历史。工作区文件仅在「回退对话和代码」时被改写,备份存储于 `~/.dsh/rewind-snapshots/`;还原以备份为唯一来源。不触碰你的 git 仓库,无网络请求,不访问任何凭据。对**长期不活跃**的会话,另有默认关闭的全局自动清理可整目录移除其快照,不影响活动会话与对话日志。完整安全模型:[SECURITY.md](SECURITY.md)。
148
151
 
149
152
  ## 开发
150
153
 
package/SECURITY.md CHANGED
@@ -83,9 +83,8 @@ and is repaired offline (see `docs/compat/troubleshooting.md`).
83
83
 
84
84
  - **Store root**: `<harness home>/rewind-snapshots/` by default (resolved via
85
85
  `resolveDshHome`, so `~/.dsh/...` when `DSH_HOME` is unset); the `snapshotDir`
86
- config, then `DSH_REWIND_SNAPSHOT_DIR` env, override it. The store never
87
- overlaps the workspace; deleting it only removes file backups and the store
88
- rebuilds from scratch.
86
+ config, then `DSH_REWIND_SNAPSHOT_DIR` env, override it. Deleting it only
87
+ removes file backups and the store rebuilds from scratch.
89
88
  - **Path sanitization**: session ids and call ids are scrubbed to
90
89
  `[a-zA-Z0-9._-]` (`safeSessionId` / `safeFileId`) before joining the store
91
90
  root; `.` and `..` bare values are replaced — hostile ids cannot traverse
@@ -138,10 +137,18 @@ and is repaired offline (see `docs/compat/troubleshooting.md`).
138
137
  same operating-system user: state files are created with the process
139
138
  default permissions (no special modes are set — a standard umask applies),
140
139
  and the host user remains trusted.
141
- - It does **not** exclude paths from the store: recording and restore mirror the
142
- paths the model's file tools touched, so a sensitive file (e.g. `.env`) the
143
- model may read or edit will be backed up and restorable. Keeping one out is
144
- a **DSH model-permission** concern (a per-path deny), not a rewind feature.
140
+ - It does **not** exclude paths from the store, and it never oversteps: the
141
+ plugin only adds backup/restore on top of permissions DSH already holds. Two
142
+ distinct cases follow:
143
+ 1. **Sensitive / personal-information files** (e.g. a `.env` file, which may
144
+ sit inside the workspace) the model may read or edit are backed up and
145
+ restorable.
146
+ 2. **Files outside the session workspace** the model is allowed to touch are
147
+ likewise backed up and restored.
148
+ In both cases the plugin introduces no authority of its own — DSH already
149
+ granted the read/edit; the plugin merely records a before-backup and can
150
+ restore it. Keeping a path out is a **DSH model-permission** concern (a
151
+ per-path deny), not a rewind feature.
145
152
  - It does **not** touch git (no refs, index, or worktree operations), makes
146
153
  **no network requests**, and does **not** access credentials.
147
154
  - It does **not** roll back whole-log state: telemetry, search, and `/export`
@@ -98,7 +98,7 @@ pruneStale cross-session auto-cleanup (default off): whole
98
98
 
99
99
  ## Compatibility strategy
100
100
 
101
- - **Peer ranges as OR-union of DSH rc tuples** (`^0.1.0-rc.6 || ^0.1.1-rc.2`):
101
+ - **Peer ranges as OR-union of DSH version tuples** (`^0.1.0-rc.6 || ^0.1.1-rc.2 || ^0.1.2-alpha.2`):
102
102
  npm's prerelease rules require the peer range to share the host's
103
103
  `[major, minor, patch]` tuple; `scripts/check-dsh-version.mjs` detects when
104
104
  a new tuple requires appending. See `docs/release/release.md`.
@@ -11,32 +11,41 @@
11
11
  > source reference: the `oss/deepseek-harness` local fork.
12
12
  >
13
13
  > Version alignment: `peerDependencies` use an OR-union (e.g.
14
- > `^0.1.0-rc.6 || ^0.1.1-rc.2`) covering each published rc tuple series. npm's
14
+ > `^0.1.0-rc.6 || ^0.1.1-rc.2 || ^0.1.2-alpha.2`) covering each published tuple series. npm's
15
15
  > prerelease matching rules require a candidate to share the range comparator's
16
16
  > `[major, minor, patch]` tuple, so each new tuple series (e.g. a future
17
17
  > `0.1.2-rc.x`, `0.2.x`) requires appending a union member; rc rolling within a
18
18
  > tuple (`0.1.1-rc.2 → rc.3`) is a no-op. Signal: `npm view @deepseek-ai/dsh version`;
19
- > flow: `scripts/check-dsh-version.mjs`.
19
+ > flow: `scripts/check-dsh-version.mjs` (it reads the `latest` dist-tag only; a
20
+ > `-alpha` prerelease published under another tag, e.g. `0.1.2-alpha.2` under
21
+ > `alpha`, is a manual pre-release check).
20
22
  >
21
- > Client channel (harness `0.1.2-alpha.1`, a **bundled pre-release NOT published
22
- > to npm**): the plugin also reads the session chat when a DSH Desktop build ships
23
- > that version (`latest` on the registry is still `0.1.1-rc.2`; the alpha is only
24
- > in the bundled `app.asar.unpacked/node_modules/@deepseek-ai/*`). There
25
- > `uiConversation` exposes the chat as a per-session named `"chat"` view
23
+ > `0.1.2-alpha.1` was never published to npm, so the peer OR-union is declared only from `0.1.2-alpha.2`; the plugin code still supports the `alpha.1`+ client.
24
+ >
25
+ > `0.1.2-alpha.3` verified-compatible (`v0.6.3` runs without issues); `rc.2` (`latest`) remains the primary baseline.
26
+ >
27
+ > Client channel (harness `0.1.2-alpha.x`; `0.1.2-alpha.2` is published to npm
28
+ > under the `alpha` dist-tag while `latest` stays `0.1.1-rc.2`): the plugin reads
29
+ > the session chat through `uiConversation` whenever the DSH client exposes it.
30
+ > `uiConversation` surfaces the chat as a per-session named `"chat"` view
26
31
  > (contributed by `dsh-client-ui-chat` through the `uiSession` slot hook) instead
27
32
  > of the session-face `chat` field. `src/client/hidden.ts` (`chatSnapshotOf`) reads
28
33
  > the session-face snapshot first (rc.2 path), then the `uiConversation` `"chat"`
29
- > view (alpha.1 path); both missing degrades to `undefined` (no targets, never a
30
- > crash). Peers are intentionally **NOT** extended for `0.1.2-alpha.1` because it is
31
- > not a registry version (see above); the OR-union stays
32
- > `^0.1.0-rc.6 || ^0.1.1-rc.2` and will gain the `0.1.2` tuple when the harness
33
- > publishes it. Pinned by `tests/chat-channel.test.ts` (channel precedence + the
34
- > alpha.1 snapshot shape) and `tests/client-dom.test.ts` (the button-target
35
- > pairing that consumes the chat). The composer refill is dual-channel the same
36
- > way: on alpha.1 the withdrawn text is written through the `conversation`
37
- > service's `input` resolver's `setDraft` (the harness's own Lexical editor),
38
- > else the rc.2 `<textarea>` / alpha.1 `contenteditable` DOM write (`writeComposer`
39
- > in `src/client/portals.tsx`); pinned by `tests/client-composer.test.ts`.
34
+ > view (alpha path); both missing degrades to `undefined` (no targets, never a
35
+ > crash). The channel is held via a lazy `ctx.get` (never a declared `inject`), so
36
+ > the plugin keeps the rc.2 type baseline while the OR-union covers the published
37
+ > `0.1.2` tuple: `^0.1.0-rc.6 || ^0.1.1-rc.2 || ^0.1.2-alpha.2`. Pinned by
38
+ > `tests/chat-channel.test.ts` (channel precedence + the alpha snapshot shape) and
39
+ > `tests/client-dom.test.ts` (the button-target pairing that consumes the chat).
40
+ > The composer refill is dual-channel the same way: on alpha the withdrawn text is
41
+ > written through the `conversation` service's `input` resolver's `setDraft` (the
42
+ > harness's own Lexical editor), else the rc.2 `<textarea>` / alpha `contenteditable`
43
+ > DOM write (`writeComposer` in `src/client/portals.tsx`); pinned by
44
+ > `tests/client-composer.test.ts`. The session-seat button DOM is dual-channel
45
+ > the same way: the actions (copy/branch) row was located by `[data-time-hover-root]`
46
+ > on ≤ 0.1.1-rc.x and by `[data-actions-reveal]` on 0.1.2-alpha.2
47
+ > (`ACTIONS_ROOT_SELECTOR` / `PENDING_SEAT_SELECTOR` in `src/client/portals.tsx`);
48
+ > pinned by `tests/client-dom.test.ts`.
40
49
 
41
50
  ## Definition of "fully compatible" (invariants)
42
51
 
@@ -60,8 +69,9 @@
60
69
  - **session-title / goal fold**: a marker does not disturb `foldSessionTitle` / `foldGoal`.
61
70
  - **client ordering**: turn-tail ordering + `step/start` uniqueness hold for tool turns + marker logs.
62
71
  - **rewind across a compact checkpoint**: `RewindError('not-on-surface')` refuses cleanly, no crash.
63
- - **plan-mode**: `hasOpenTurn` pairs only `turn/start`/`turn/end`; a marker produces no `turn/start`, so a rewind during active plan is unaffected (static review confirmed).
72
+ - **plan-mode**: a marker reuses the last started turn (no phantom turn); a rewind never touches the log-only `plan/mode` state (plan mode stays active; the user leaves it with `/plan off`) and the log stays replayable (`compat-invariants` I1/I3 marker + `plan/mode` probe, `verify-host`).
64
73
  - **agent-loop cancellation**: `finally` guarantees step/turn closure; the rewind force-stop path leaves no dangling frame.
74
+ - **settings-card cross-version reach**: the Snapshot cleanup card must be registered through a **nested** `ctx.inject(['settingsScope'], …)` — naming `settingsScope` in the module-level inject unmounts the whole client plugin on rc.2 (card and rewind button disappear). It uses only the rc.2↔alpha-common scope subset (`getSnapshot().value` + `set`), never the alpha-only `mutate`.
65
75
 
66
76
  ## Known behavior boundaries (deterministic differences, non-crash, documented)
67
77
 
@@ -71,6 +81,7 @@
71
81
  - **Session title auto-regeneration**: the title derives from the surface, so an automatically-derived title may change after a rewind.
72
82
  - **Files written but uncommitted in a cancelled turn**: a `both` rewind cannot restore them (tool side-effect timing; same as Claude Code).
73
83
  - **Attachment files left after a message is shadowed**: attachment storage is not cleaned with the surface (`dsh-attachment-local` not installed, not automatically verified).
84
+ - **Rewind leaves plan mode untouched**: `/plan text` is two independent actions (enter plan mode + steer the message). Rewinding the message undoes only the message — the log-only `plan/mode` state stays active, and the user leaves plan mode with `/plan off`, which still commits after a rewind (the marker creates no open turn). Pin: `verify-host` plan checks (`plan rewind leaves plan mode active`, `/plan off after rewind turns plan mode off`), `tests/hidden.test.ts` `messageTextAt`.
74
85
 
75
86
  ## Upstream (harness) issues and the plugin's no-compensation stance
76
87
 
@@ -161,7 +172,7 @@ new turn at `phase.turn + 1` (`agent.ts:251-255`) **without closing the leftover
161
172
  | resume / session-query replay | ✓ | — | ✓ | — | — | — | ✓ | — |
162
173
  | tool pipeline (snapshot/restore) | — | — | — | — | — | ✓ | — | ✓ |
163
174
  | client ordering | — | — | ✓ | — | — | — | ✓ | — |
164
- | plan-mode | | — | | — | — | — | — | ✓ (static) |
175
+ | plan-mode | | — | | — | — | — | — | ✓ (static) |
165
176
 
166
177
  ✓ = probe passes; — = not applicable. RU-I18N and R-OPENSTEP are upstream issues the plugin
167
178
  does not compensate for (see above); G3 is a confirmed-non-defect behavior pinned in
package/docs/format.md CHANGED
@@ -149,3 +149,14 @@ version field. A future incompatible format must either bump the journal
149
149
  `version` (readers reject unknown values — there is no best-effort fallback or
150
150
  legacy coercion) or move the state root (e.g. `rewind-snapshots/v2`) and ship
151
151
  an explicit migration tool. Old-format data is never silently re-interpreted.
152
+
153
+ ## Cleanup policy persistence
154
+
155
+ The snapshot auto-cleanup policy (the `enabled` switch and the `maxAgeDays`
156
+ idle cutoff) is no longer a file: it lives in the **dsh-settings document** under
157
+ the `dsh-rewind-snapshot-cleanup` namespace (validated by a schemastery schema;
158
+ defaults are the `base` layer). On first run after this change, a legacy
159
+ `<dsh home>/snapshot-cleanup.json` is imported into the document **once** and
160
+ then deleted (an absent file is a no-op; an invalid file imports the safe
161
+ default and is dropped). The last-sweep clock stays in its own
162
+ `<dsh home>/snapshot-cleanup-last-sweep.json` state file, which is unchanged.
@@ -54,18 +54,18 @@ git push origin main --tags # triggers .github/workflows/publish.yml
54
54
 
55
55
  DSH is still in rc; npm's prerelease matching rules require a peer range to
56
56
  share the host version's `[major, minor, patch]` tuple. So `peerDependencies`
57
- uses an OR-union covering every published rc tuple series
58
- (e.g. `^0.1.0-rc.6 || ^0.1.1-rc.2`), extended as DSH releases new tuples.
57
+ uses an OR-union covering every published tuple series
58
+ (e.g. `^0.1.0-rc.6 || ^0.1.1-rc.2 || ^0.1.2-alpha.2`), extended as DSH releases new tuples.
59
59
 
60
60
  - **When to update**: only when DSH releases a new tuple
61
61
  (`0.1.1 → 0.1.2 → 0.2.x`); rc rolling within a tuple (`0.1.1-rc.2 → rc.3`)
62
62
  needs nothing. All `@deepseek-ai/*` packages release together;
63
63
  `npm view @deepseek-ai/dsh version` is the authoritative signal.
64
64
  - **Published-tuple check (optional)**: `node scripts/check-dsh-version.mjs`
65
- compares the latest npm version against the tuples the peers cover (exit 0 =
66
- nothing to do, exit 1 = update). It reads npm published versions only; a
67
- newer-but-unpublished pre-release is a manual pre-release check see the
68
- "Before bumping" step above.
65
+ compares the `latest` dist-tag version against the tuples the peers cover
66
+ (exit 0 = nothing to do, exit 1 = update). It reads the `latest` tag only; a
67
+ pre-release published under another tag (e.g. `alpha`) or bundled without
68
+ going to npm is a manual pre-release check — see the "Before bumping" step above.
69
69
  - **Update steps**: append `|| ^<new-tuple>-rc.<n>` to every
70
70
  `@deepseek-ai/dsh-*` peer → bump devDependencies to the latest → `npm
71
71
  install` → `npm run check` → release.
@@ -49,15 +49,16 @@ git push origin main --tags # 触发 .github/workflows/publish.yml
49
49
 
50
50
  DSH 仍在 rc 阶段,npm 的 prerelease 匹配规则要求 peer 范围与宿主版本
51
51
  **同 `[major, minor, patch]` 元组**才能匹配。因此 peerDependencies 采用
52
- OR 并集覆盖 DSH 已发布的每个 rc 元组系列(如 `^0.1.0-rc.6 || ^0.1.1-rc.2`),
52
+ OR 并集覆盖 DSH 已发布的每个元组系列(如 `^0.1.0-rc.6 || ^0.1.1-rc.2 || ^0.1.2-alpha.2`),
53
53
  并随 DSH 发版追加。
54
54
 
55
55
  - **何时需要更新**:仅当 DSH 发布新元组(`0.1.1 → 0.1.2 → 0.2.x`)时;
56
56
  同元组内 rc 滚动(`0.1.1-rc.2 → rc.3`)无需动作。DSH 所有包同版本发布,
57
57
  `npm view @deepseek-ai/dsh version` 即权威信号。
58
- - **已发布元组检查(可选)**:`node scripts/check-dsh-version.mjs` 用 npm 最新版本
59
- 对比 peer 覆盖的元组(exit 0 无需动作,exit 1 需要)。它**只读 npm 已发布版本**;
60
- 更新的未发布 pre-release 走**手动发布前检查**——见上文"升版前手动确认"。
58
+ - **已发布元组检查(可选)**:`node scripts/check-dsh-version.mjs` 用 npm `latest`
59
+ dist-tag 版本对比 peer 覆盖的元组(exit 0 无需动作,exit 1 需要)。它**只读
60
+ `latest` tag**;发布在其它 tag(如 `alpha`)的 pre-release 走**手动发布前检查**
61
+ ——见上文"升版前手动确认"。
61
62
  - **更新步骤**:给每个 `@deepseek-ai/dsh-*` peer 追加 `|| ^<新元组>-rc.<n>`
62
63
  → devDependencies 同步升到最新 → `npm install` → `npm run check` → 发版。
63
64
  - **正式版后收敛**:DSH 发布 final 版本后,正式版不受 prerelease 元组规则
@@ -15,7 +15,7 @@ you've stopped using, and now and then remove their snapshots to keep disk usage
15
15
  down. It never touches your active session, and it never touches your
16
16
  conversation.
17
17
 
18
- **Manual:** even with automatic cleanup off, you can run the cleanup yourself:
18
+ **Manual**: even with automatic cleanup off, you can run the cleanup yourself:
19
19
 
20
20
  - `/snapshot-auto-cleanup run [--apply]` — preview, then actually remove the
21
21
  snapshots of sessions you haven't used for a while.
@@ -41,20 +41,12 @@ whether or not automatic cleanup is on.
41
41
 
42
42
  ## Settings
43
43
 
44
- The settings live in `<dsh home>/snapshot-cleanup.json`:
44
+ The auto-cleanup switch and the idle-day cutoff live in the **dsh-settings configuration document**. View and edit them in the **Settings &gt; Plugins &gt; Plugin configuration &gt; Snapshot cleanup** panel (the auto-cleanup switch and the idle days), or view and set them with the `/snapshot-auto-cleanup` command:
45
45
 
46
- ```json
47
- { "enabled": false, "maxAgeDays": 30 }
48
- ```
46
+ <img src="../assets/screenshots/cleanup-setting.png" alt="Snapshot cleanup settings: auto-cleanup and idle days" width="600">
49
47
 
50
48
  - `enabled` — whether automatic cleanup runs (default `false`).
51
- - `maxAgeDays` — how many idle days before a session's snapshots are removed
52
- (default `30`). Only positive numbers are accepted, so a broken setting can
53
- never delete everything.
54
-
55
- You can point the plugin at a different file with the `DSH_SNAPSHOT_CLEANUP_CONFIG`
56
- environment variable. The file is changed only when you turn cleanup on/off or
57
- set `max-age`; a missing file reads as the safe default (off).
49
+ - `maxAgeDays` — how many idle days before a session's snapshots are removed (default `30`). Only positive integers are accepted, so a broken setting can never delete everything.
58
50
 
59
51
  ## When automatic cleanup runs
60
52
 
@@ -8,7 +8,7 @@
8
8
 
9
9
  **自动清理**(默认关闭):插件会记住哪些会话你已不再使用,并定期移除它们的快照以节省磁盘。它**绝不**触碰你当前活跃的会话,也**绝不**触碰你的对话。
10
10
 
11
- **手动:**即使自动清理是关闭的,你也可以自己执行清理:
11
+ **手动**:即使自动清理是关闭的,你也可以自己执行清理:
12
12
 
13
13
  - `/snapshot-auto-cleanup run [--apply]` — 先预演,再真正移除你已有一阵子没用的会话的快照。
14
14
  - `/snapshot-auto-cleanup run --current [--apply]` — 先预演,再真正清空**当前**会话的快照。这会让它从"现在"重新开始记录回退历史(你的对话不受影响)。若此刻有正在进行中的回合,插件会先暂停它,再清空。
@@ -29,16 +29,12 @@
29
29
 
30
30
  ## 设置
31
31
 
32
- 设置保存在 `<dsh home>/snapshot-cleanup.json`:
32
+ 自动清理开关与失活天数保存在 **dsh-settings 配置文档**。可在 **设置→插件→插件配置→快照清理** 面板查看与修改(自动清理开关、失活天数),也可用 `/snapshot-auto-cleanup` 命令查看和设置:
33
33
 
34
- ```json
35
- { "enabled": false, "maxAgeDays": 30 }
36
- ```
34
+ <img src="../assets/screenshots/cleanup-setting.png" alt="快照清理设置:自动清理与失活天数" width="600">
37
35
 
38
36
  - `enabled` — 是否运行自动清理(默认 `false`)。
39
- - `maxAgeDays` — 会话失活多少天后移除其快照(默认 `30`)。只接受正数,因此一个损坏的设置永远不可能"删掉一切"。
40
-
41
- 你可以用环境变量 `DSH_SNAPSHOT_CLEANUP_CONFIG` 指向别的文件。该文件只在运行 `/snapshot-auto-cleanup` 时写入;文件缺失按安全默认值(关闭)处理。
37
+ - `maxAgeDays` — 会话失活多少天后移除其快照(默认 `30`)。只接受正整数,因此一个损坏的设置永远不可能"删掉一切"。
42
38
 
43
39
  ## 自动清理何时运行
44
40