dsh-rewind-plugin 0.14.0 → 0.15.0-beta.2

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/SECURITY.md CHANGED
@@ -80,13 +80,13 @@ whose store is **newer** than this build understands is not cleared by it (see
80
80
  ## Conversation integrity
81
81
 
82
82
  The session log is **append-only** — the plugin never deletes or rewrites
83
- recorded history. A rewind appends a single marker event (an **empty**
84
- `user/message`) whose `surfaceOp` replaces every surface node after the target.
85
- The raw log (audit trail, search, `/export`) is untouched — only the
86
- model-visible surface is cut, so the next request derives its context from the
87
- target onward. The marker is empty, so it carries no untrusted text into the
88
- model context; the client hides the `[target, marker]` span from the rendered
89
- transcript.
83
+ recorded history. A rewind appends a single marker event (a `user/message` whose
84
+ content is the constant `(empty message)` placeholder) whose `surfaceOp`
85
+ replaces every surface node after the target. The raw log (audit trail, search,
86
+ `/export`) is untouched — only the model-visible surface is cut, so the next
87
+ request derives its context from the target onward. The marker's content is a
88
+ constant, so it carries no untrusted text into the model context; the client
89
+ hides the `[target, marker]` span from the rendered transcript.
90
90
 
91
91
  ## Filesystem containment
92
92
 
package/cordis.patch.yml CHANGED
@@ -1,10 +1,7 @@
1
- # dsh-rewind-plugin bundle patch: one row, dual-face.
2
- # The node half is the package's default export (host plugin: /rewind command
3
- # + tools/execute|post-execute ledger); the browser half is served from
4
- # ./client (dsh.client declaration in package.json) and injected into the web
5
- # roster. The row name MUST equal the package name — the client-modules node
6
- # half resolves `require.resolve('<row-name>/package.json')` to find the
7
- # package and its dsh.client declaration.
1
+ # dsh-rewind-plugin bundle patch: one row, dual-face (node half = the package's
2
+ # default export, browser half = ./client via the `dsh.client` declaration).
3
+ # The row name MUST equal the package name — the client-modules node half
4
+ # resolves `require.resolve('<row-name>/package.json')` to find the package.
8
5
  - insert:
9
6
  - id: dsh-rewind-plugin
10
7
  name: 'dsh-rewind-plugin'
package/docs/README.md CHANGED
@@ -25,7 +25,8 @@ Repo-root docs outside `docs/`: `SECURITY.md` (security model) and
25
25
  ## Conventions
26
26
 
27
27
  - Bilingual docs use the `.md` / `.zh.md` file split (e.g. `contract/client-contract.md`
28
- + `contract/client-contract.zh.md`, `release/release.md` + `release/release.zh.md`).
28
+ + `contract/client-contract.zh.md`); the root pair is the exception — `README.md` is
29
+ Chinese, `README.en.md` English.
29
30
  - `compat/audit.md` is the single source of truth for compatibility conclusions;
30
31
  other docs link to it instead of restating them.
31
32
  - Cross-links are relative so the whole `docs/` directory stays relocatable — and it
@@ -1,9 +1,9 @@
1
1
  # Architecture
2
2
 
3
3
  How dsh-rewind is built: module layering, the rewind pipeline, the checkpoint
4
- pipeline, the compatibility strategy, and the roadmap. The module map below is
5
- the same one in `AGENTS.md`; this document adds the wiring between the
6
- modules. The durable on-disk format is specified separately in
4
+ pipeline, the compatibility strategy, and the roadmap. The module map lives in
5
+ `AGENTS.md`; this document adds the wiring between the modules. The durable
6
+ on-disk format is specified separately in
7
7
  `docs/format.md`, the security model in `SECURITY.md`.
8
8
 
9
9
  ## Purpose
@@ -15,25 +15,8 @@ credentials access.
15
15
 
16
16
  ## Module layering
17
17
 
18
- ```
19
- src/
20
- ├── index.ts host plugin: /rewind, /undo, /snapshot-auto-cleanup
21
- │ commands + checkpoint pipeline
22
- ├── rewind.ts pure planning: target parsing, surface-range plan, candidate listing
23
- ├── snapshot.ts checkpoint store: disk before-backups, journaled restore,
24
- │ reconcile / continue / rollback, bounded prune
25
- ├── snapshot-cleanup.ts cleanup policy + dsh-settings persistence + auto-sweep throttle
26
- ├── session-cwd.ts session working-directory resolution (fs-tools rule)
27
- ├── locales.ts host i18n (t() renderer, HostKey)
28
- └── client/ browser half: per-message ↶ button (portal bridge),
29
- mode popover, hidden-span computation, candidate parsing,
30
- pending interaction, locales, styles
31
- ```
32
-
33
- The old marker-update repair line (`/dsh-rewind-fix` and its `rewind-fix.ts` /
34
- `rewind-marker-repair.ts` / `session-log-io.ts` modules) was a **temporary
35
- migration tool** for the DSH `0.1.2-rc.1` → `0.1.3` transition: it shipped
36
- through the `0.9.x` line and is **removed in the `0.10.x` line**.
18
+ The module map is the one in `AGENTS.md` (`Layout at a glance`); this document
19
+ adds the wiring between the modules and does not repeat it.
37
20
 
38
21
  Two dependency rules keep the design testable:
39
22
 
@@ -156,9 +139,6 @@ Ideas under consideration, not commitments:
156
139
  - **Multi-process identity/lock**: the current in-flight guard is
157
140
  per-process; a cross-process exclusive lock (like the change-ledger
158
141
  competitors) would cover multiple host processes on one worktree.
159
- - **Lazy-commit UX**: an explicit confirm-then-apply step (a pending state)
160
- to reduce mis-touch risk on in-window rewinds, which are inherently less
161
- reversible than forked branches.
162
142
  - **Locale expansion**: client/host copy is zh/en today; the copy layer
163
143
  (`src/locales.ts`, `src/client/locales.ts`) is already keyed for more.
164
144
  - **Composer re-send polish**: the target text is already refilled after a
@@ -8,7 +8,7 @@
8
8
  > compatibility invariants. A probe failure is a finding; it enters the
9
9
  > fix/pin/record loop.
10
10
  >
11
- > Targeted version: npm `@deepseek-ai/*@0.1.7-rc.2`, declared by the peers as `^0.1.7-rc.2`.
11
+ > Targeted version: npm `@deepseek-ai/*@0.2.0-rc.2` (the peers declare the matching range).
12
12
  > Source reference: the upstream [github.com/deepseek-ai/deepseek-harness](https://github.com/deepseek-ai/deepseek-harness).
13
13
  >
14
14
  > Version alignment: one peer tuple per DSH line, declaring only what was verified — the
@@ -46,7 +46,7 @@ legacy branch).
46
46
  | I6 tool pipeline | before-snapshot capture/commit/restore is correct (existing `snapshot.test.ts` + `verify-host` 4–8) for the tracked tools `write` / `edit` — `str_replace_editor` is an optional DSH package that stopped being a default tool in DSH 0.1.3, so it is not tracked; cancellation timing never hangs | `verify-host` 4–9 |
47
47
  | I7 client ordering | A log carrying tool turns and rewind markers (a single `user/message` replace) satisfies the client builder ordering | `compat-interop` I7 |
48
48
  | I8 runtime safety | `rewind`/`compact` combinations never leave a dangling step/turn frame | `verify-host` 13 |
49
- | I9 tool-update projection | rc.2 folds mid-conversation tool additions from the whole log while a rewind cuts only the surface: after a withdrawal no `tool-addition`/`tool-removal` block reaches the provider and the current declarations stay complete | `compat-tool-updates` I9 |
49
+ | I9 tool-update projection | `0.1.7-rc.2` folds mid-conversation tool additions from the whole log while a rewind cuts only the surface: after a withdrawal no `tool-addition`/`tool-removal` block reaches the provider and the current declarations stay complete | `compat-tool-updates` I9 |
50
50
 
51
51
  ## Verified-compatible surfaces (probes pass)
52
52
 
@@ -58,13 +58,13 @@ legacy branch).
58
58
  - **agent-loop cancellation**: `finally` guarantees step/turn closure; the rewind force-stop path leaves no dangling frame.
59
59
  - **settings card (`0.1.7-alpha.1`)**: the card declares `configForms` as a module-level inject (the web profile always composes `ui-settings`), stages through the harness's own `SettingsFormModel` + `SettingsForm`, and registers into `plugins.bundle.config` keyed by the bundle package name; it reads that shared per-entry form's snapshot and writes through its revision-fenced `mutate`, disposing the model's subscription with the fiber. The host half declares the bundle's page policy (`configure({ auto: false }, ctx.fiber)` inside a `ctx.inject(['settings'])` child, per the dsh-settings README), opting the entry out of a client-built schema page. Pins: `verify:host` (`the bundle declares its own config page`), `tests/client-settings-card.test.ts` (staged plan, refusal, subscription release), `tests/client-lifecycle.test.ts`.
60
60
  - **client session ownership (`0.1.6-alpha.2`)**: `SessionListState.current` / `currentAddress` are gone; main-view ownership is read from the row's `retainedBy.mainView` label — what `ui-workspace` retains the open session with. Pin: `tests/client-refill.test.ts`.
61
- - **client pending input (`0.1.6-alpha.2`)**: `SessionSnapshot.queue` and the host `queue-mirror` are gone; pending steering rows come from the session's own `inbox` projection, and only USER-sourced `next-step` rows are retractable (the removed mapping was `source.kind === 'user' ? 'steering' : 'context'`). The rendered bubble also stays the client's own submission echo until the durable node arrives (`ChatView.pendingInputs` merges the Host occurrence into that echo by `rpcId`; `QueueDock` mirrors only `next-turn`), so the occurrence lands with NO DOM mutation — the portal refresh therefore follows the `inbox` face's own `subscribe` beside the DOM observer. Pins: `tests/pending.test.ts`, `tests/client-retract.test.ts`, `tests/client-portal-refresh.test.ts`.
61
+ - **client pending input (`0.1.6-alpha.2`)**: `SessionSnapshot.queue` and the host `queue-mirror` are gone; pending steering rows come from the session's own `inbox` projection, and only USER-sourced `next-step` rows are retractable (the removed mapping was `source.kind === 'user' ? 'steering' : 'context'`). The rendered bubble also stays the client's own submission echo until the durable node arrives (`ChatView.pendingInputs` merges the Host occurrence into that echo by `rpcId`; `QueueDock` mirrors only `next-turn`), so the occurrence lands with NO DOM mutation — the portal refresh therefore follows the `inbox` face's own `subscribe` beside the DOM observer. An attachment-bearing row is identified by its message-attachment block (`data-message-attachments`) and matched by block count, not by rendered text (the block renders a file name or an image's loading label, which no inbox row reproduces); a missing attribute degrades to "no button". Pins: `tests/pending.test.ts`, `tests/client-retract.test.ts`, `tests/client-portal-refresh.test.ts`, `tests/client-dom.test.ts`.
62
62
  - **client settings slot (`0.1.6-alpha.2`)**: `settings.plugin.item` is gone; the configuration form registers into `plugins.bundle.config`, keyed by the bundle package name. Pin: `tests/client-contract.test.ts`.
63
63
  - **`agent/created` lifecycle guard**: the session-format reconcile runs on the alpha line's `agent/created` (fire-and-forget); pin: `verify-host` 4e dispatches it.
64
64
  - **marker vs `/compact`**: the checkpoint shape is unchanged on this line — a `user/message` replace (`surfaceOp {replace, startSeq, endSeq}` + `sourceEventSeqs`); `assistant/message` still cannot carry `sourceEventSeqs`.
65
65
  - **`image/offload` projection**: the alpha line's first `SessionMessageProjection` changes derived content without touching surface node membership; candidate listing and target resolution tolerate it. Pin: `tests/image-offload-projection.test.ts`.
66
66
  - **session-cwd**: the fs tools no longer canonicalize a parent-traversing cwd, so `src/session-cwd.ts` returns `header.cwd` verbatim. Pin: `tests/session-cwd.test.ts`.
67
- - **startup admission (`0.1.7-rc.1`)**: the gate reads the manifest's own `@deepseek-ai/dsh-*` peers and requires each to satisfy the runtime with `includePrerelease`; it runs for a profile bundle and again for every row its patch inserts, and a denial skips the bundle or disables the row. This plugin's declarations all satisfy `0.1.7-rc.2`, so it is admitted with no exemption — a skipped bundle on a later line is a peer mismatch, not a load failure.
67
+ - **startup admission (`0.1.7-rc.1`)**: the gate reads the manifest's own `@deepseek-ai/dsh-*` peers and requires each to satisfy the runtime with `includePrerelease`; it runs for a profile bundle and again for every row its patch inserts, and a denial skips the bundle or disables the row. This plugin's declarations all satisfy the targeted version above, so it is admitted with no exemption — a skipped bundle on a later line is a peer mismatch, not a load failure.
68
68
 
69
69
  ## Known behavior boundaries (deterministic differences, non-crash, documented)
70
70
 
@@ -112,26 +112,17 @@ the harness issue, its current status, and the plugin's stance, so a future main
112
112
 
113
113
  ### R-OPENSTEP (rewind part resolved): an unclosed `step` in the log breaks token-meter replay
114
114
 
115
- > **Situation**: once a step is left unclosed, replay rejects any later step activity, so
116
- > manual `/compact` fails permanently and automatic compaction silently stays disabled (the
117
- > `agent/pre-step` hook catches and warns). The conversation itself is unaffected — only
118
- > compaction-basic measures tree-wide. DSH now auto-closes crash-left step/turn/tool
119
- > boundaries at load (`interruptedTurnClosers`, consumed by
120
- > `packages/core/agent-loop/src/index.ts:907`), so **the crash path is fixed**. The tree has
121
- > exactly one `append('step/start')` producer, `agent-loop/src/agent.ts:303`; an unclosed step
122
- > otherwise comes from a third-party plugin appending `step/start` through the public
123
- > `Session.append`, or from a hand-edited log. After a crash resume, `turn()` opens a new turn
124
- > (`agent.ts:277-283`) without closing the leftover step, so continuing the conversation trips
125
- > the same check — a rewind is **not** the only trigger.
126
- >
127
- > **Rewind's part**: the marker is a `user/message` and appends no `step/start`, so a rewind
128
- > no longer introduces the first trip-wire and the R-OPENSTEP rewind amplifier is closed.
115
+ > **Status**: resolved upstream — DSH auto-closes crash-left step/turn/tool boundaries at load
116
+ > (`interruptedTurnClosers`, consumed by the agent-loop's `resumeWith` cold read). A leftover
117
+ > unclosed step can still come from a third-party plugin appending `step/start` through the public
118
+ > `Session.append`, or from a hand-edited log; when it does, replay rejects later step activity, so
119
+ > `/compact` fails and automatic compaction silently stays disabled. Rewind's part: the marker is a
120
+ > `user/message` and appends no `step/start`, so the rewind amplifier is closed.
129
121
  >
130
122
  > **Plugin stance**: no guard. A `hasOpenStep` + `planRewind` pre-refusal was implemented and
131
- > misjudged on **real session logs** (normal rewinds refused, GUI verification broken), so it
132
- > was reverted — do not re-attempt a plugin-side pre-refusal. The fix direction is upstream
133
- > (token-meter recovery for unclosed steps), and the residual risk is accepted: that log is
134
- > already abnormal.
123
+ > misjudged on **real session logs** (normal rewinds refused, GUI verification broken), so it was
124
+ > reverted — do not re-attempt a plugin-side pre-refusal. The fix direction is upstream (token-meter
125
+ > recovery for unclosed steps), and the residual risk is accepted: that log is already abnormal.
135
126
 
136
127
  ### R-PROMPTCARD (resolved on the 0.1.7 line): the repeated `System prompt` card is filtered out of chat
137
128
 
@@ -139,7 +130,8 @@ the harness issue, its current status, and the plugin's stance, so a future main
139
130
  > (`packages/client/ui-chat/src/client/contract/chat-visibility.ts`) drops `system-prompt`
140
131
  > nodes — and the `permission` command — from the visible chat rows before the row is built,
141
132
  > so a rewind on this line adds no prompt row at all; the durable events and the trajectory
142
- > inspection keep them. #22, #31 and README known issue #4 no longer reproduce.
133
+ > inspection keep them. #22, #31 and the (now removed) repeated `System prompt` card known issue
134
+ > no longer reproduce.
143
135
  >
144
136
  > **Plugin stance**: still no compensation. The DOM row-hiding seam (`hiddenSeqsOf`) covers
145
137
  > withdrawn messages, where hiding is essential, not cosmetic.
@@ -147,8 +139,9 @@ the harness issue, its current status, and the plugin's stance, so a future main
147
139
  ### R-PANELKEYS: the panel keyboard stays the plugin's own
148
140
 
149
141
  > **Situation**: on Web, Escape, Tab, the four arrows, and Enter without Alt are
150
- > platform-reserved — inline in the shortcuts `bindingIssue` list, with no exported form — so
151
- > neither a plugin nor a user may bind them and there is nothing to read and follow. `Menu`
142
+ > platform-reserved. The predicate is exported (`bindingIssue` from
143
+ > `@deepseek-ai/dsh-client-shortcuts/protocol`), but a reserved binding is still refused, so
144
+ > neither a plugin nor a user may bind them. `Menu`
152
145
  > is the dropdown primitive, while the harness's command-option panel (`ui-commands`'
153
146
  > popupSelect) renders `MenuSurface` plus its own controller with `role="listbox"`/
154
147
  > `role="option"`. `ui-commands`' `settle()` also calls `focusComposer()` unconditionally
@@ -172,21 +165,6 @@ the harness issue, its current status, and the plugin's stance, so a future main
172
165
  - telemetry pipeline (`dsh-session-telemetry-otel`) and the attachment provider (`dsh-attachment-local`).
173
166
  - Running workflow/jobs cancelled by a rewind: the tool contract requires observing `exec.signal` and settling (`packages/core/tools/src/index.ts`); a rewind triggers the harness's standard cancel, not plugin-specific — statically confirmed, real workflows untested.
174
167
 
175
- ## Audit matrix (subsystem × invariant)
176
-
177
- | DSH subsystem | I1 | I2 | I3 | I4 | I5 | I6 | I7 | I8 |
178
- |---|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
179
- | Session core (append/surface/deriveMessages) | ✓ | ✓ | ✓ | — | ✓ | — | ✓ | ✓ |
180
- | token-meter | ✓ | — | ✓ | — | ✓ | — | — | — |
181
- | compaction (transaction/command/tool-pairing) | ✓ | — | — | — | ✓ | — | — | ✓ |
182
- | session-stats / projection | — | — | ✓ | ✓ | — | — | — | — |
183
- | session-title | — | — | — | ✓ | — | — | — | — |
184
- | goal | — | — | — | ✓ | — | — | — | — |
185
- | resume / session-query replay | ✓ | — | ✓ | — | — | — | ✓ | — |
186
- | tool pipeline (snapshot/restore) | — | — | — | — | — | ✓ | — | ✓ |
187
- | client ordering | — | — | ✓ | — | — | — | ✓ | — |
188
- | plan-mode | ✓ | — | ✓ | — | — | — | — | ✓ (static) |
189
-
190
- ✓ = probe passes; — = not applicable. Statuses and stances for the named findings live in the
191
- sections above; G1 (surface classification via `foldSurface`), G2 (projection checkpoint) and
192
- G3 (the confirmed-non-defect usage anchor on token-meter) all pass in `compat-gaps.test.ts`.
168
+ Statuses and stances for the named findings live in the sections above; G1 (surface classification
169
+ via `foldSurface`), G2 (projection checkpoint) and G3 (the confirmed-non-defect usage anchor on
170
+ token-meter) all pass in `compat-gaps.test.ts`.
@@ -37,16 +37,14 @@ console filter:
37
37
  | `portals` | Per-message button mount issues (e.g. no session binding) |
38
38
  | `retract` | The pending-steering retract path (the remove lost the race, or the remove threw) |
39
39
  | `settings` | The snapshot-cleanup settings card |
40
- | `hiding` | **Reserved** — no active alert at present. If a future row-hiding diagnostic is added, it belongs in this region. |
41
40
 
42
41
  ## Verbose switch
43
42
 
44
43
  The `info`/`debug` levels are gated by `localStorage['dsh-rewind.debug']`, read
45
- once per call and filtered by namespace. After the withdrawal of the restating
46
- verbose detail, exactly **one** verbose line remains: the `boot` scope's startup
47
- identity line. It is deliberately **off** by default (a normal user's console
48
- stays clean, and it is not an anomaly), so a reporter enables the scope to see
49
- it:
44
+ once per call and filtered by namespace. Two gated lines remain: the `boot`
45
+ startup identity line and the `retract` lost-race line. Both are deliberately
46
+ **off** by default (a normal user's console stays clean, and neither is an
47
+ anomaly), so a reporter enables the scope to see them:
50
48
 
51
49
  ```js
52
50
  // Just the startup identity line.
@@ -63,14 +61,12 @@ delete localStorage['dsh-rewind.debug']
63
61
  ```
64
62
 
65
63
  The `error`/`warn` anomaly alerts are **not** gated by this switch — they are
66
- always printed. The other verbose detail (used write channel, an empty
67
- hide-set, per-rewind lifecycle lines) has been withdrawn: it re-stated behavior
68
- the user already sees and carried no attribution.
64
+ always printed.
69
65
 
70
66
  ## Capturing a report
71
67
 
72
68
  1. Reproduce once on the affected page.
73
- 2. In DevTools, filter the Console for `[dsh-rewind]` and copy the output
69
+ 2. In DevTools, filter the Console for `dsh-rewind` and copy the output
74
70
  (with the plugin version and the DSH/kernel version).
75
71
 
76
72
  The `error`/`warn` anomaly alerts require no setup — they always print. To also
@@ -27,11 +27,10 @@
27
27
  | `portals` | 每条消息按钮的挂载问题(如无会话绑定) |
28
28
  | `retract` | 插话撤回路径(remove 竞争失败,或 remove 抛错) |
29
29
  | `settings` | 快照清理设置卡片 |
30
- | `hiding` | **保留区**——当前无活跃告警。若未来新增行隐藏诊断,应归属此区域。 |
31
30
 
32
31
  ## 详细输出开关
33
32
 
34
- `info`/`debug` 级别受 `localStorage['dsh-rewind.debug']` 控制,每次调用读取并按命名空间过滤。在撤下那些复述现象的详细输出后,**恰好剩一条** verbose 行:`boot` 作用域的启动身份行。它**默认关闭**(普通用户控制台保持干净,且它不是异常),排查者需先开启该作用域才能看到:
33
+ `info`/`debug` 级别受 `localStorage['dsh-rewind.debug']` 控制,每次调用读取并按命名空间过滤。仅剩两条受控行:`boot` 启动身份行与 `retract` 竞争失败行。两者**默认关闭**(普通用户控制台保持干净,且都不是异常),排查者需先开启对应作用域才能看到:
35
34
 
36
35
  ```js
37
36
  // 仅启动身份行。
@@ -47,12 +46,12 @@ localStorage['dsh-rewind.debug'] = 'dsh-rewind*'
47
46
  delete localStorage['dsh-rewind.debug']
48
47
  ```
49
48
 
50
- `error`/`warn` 异常告警**不受**该开关控制——它们总是打印。其余详细输出(所用写入通道、空的隐藏集、每次回退的生命周期行)已撤下:它们重复用户已经看到的现象、无归因增量。
49
+ `error`/`warn` 异常告警**不受**该开关控制——它们总是打印。
51
50
 
52
51
  ## 采集一段报告
53
52
 
54
53
  1. 在出问题的页面上复现一次。
55
- 2. 在 DevTools 里把 Console 按 `[dsh-rewind]` 过滤,复制输出(连同插件版本、DSH/内核版本)。
54
+ 2. 在 DevTools 里把 Console 按 `dsh-rewind` 过滤,复制输出(连同插件版本、DSH/内核版本)。
56
55
 
57
56
  `error`/`warn` 异常告警无需任何设置——它们总是打印。若要同时采集启动身份行(`boot`,用于排除「旧 bundle / 宿主未重启」),请先开启详细输出开关(见上),再刷新。
58
57
 
@@ -212,9 +212,6 @@ What the two limitations buy is: lightweight, predictable, and **only ever writi
212
212
  back content it actually backed up, never a guess**. For precise workspace-level
213
213
  history, use Git.
214
214
 
215
- ## Background
215
+ ## See also
216
216
 
217
- This document was originally written for issue
218
- [#5](https://github.com/SiriLee/dsh-rewind/issues/5) and completed for issue
219
- [#39](https://github.com/SiriLee/dsh-rewind/issues/39). See also:
220
217
  [SECURITY.md](../../SECURITY.md) · [Snapshot auto-cleanup](../snapshot-auto-cleanup.md)
@@ -126,6 +126,6 @@
126
126
 
127
127
  两项限制换来的是:轻量、可预测,以及**只写它确实备份过的内容、绝不做任何猜测**。需要工作区级别的精确历史时,请使用 Git。
128
128
 
129
- ## 背景
129
+ ## 相关
130
130
 
131
- 本文最初为 issue [#5](https://github.com/SiriLee/dsh-rewind/issues/5) 编写,并为 issue [#39](https://github.com/SiriLee/dsh-rewind/issues/39) 进行完善。相关:[SECURITY.md](../../SECURITY.md) · [快照自动清理](../snapshot-auto-cleanup.zh.md)
131
+ [SECURITY.md](../../SECURITY.md) · [快照自动清理](../snapshot-auto-cleanup.zh.md)
@@ -22,7 +22,7 @@ Compatibility probes against these subsystems (test-driven investigation, the
22
22
  `compat-invariants` / `compat-interop` vitest suites + the `verify-host` real
23
23
  `/compact` chain): [compat/audit.md](compat/audit.md).
24
24
 
25
- Also under `docs/` at the repo root: `persistence-catalog.md` (full
25
+ Also in the harness repo's `docs/`: `persistence-catalog.md` (full
26
26
  `SessionEventMap`), `tool-catalog.md` (tool inventory), `config-catalog.md`
27
27
  (configuration inventory).
28
28
 
@@ -49,13 +49,6 @@ the canonical source; this block only adds the finer-grained client-side files
49
49
  and packaging entries.
50
50
 
51
51
  ```
52
- src/index.ts host plugin: /rewind|/undo|/snapshot-auto-cleanup
53
- + checkpoint pipeline (tools/execute|post-execute)
54
- src/rewind.ts pure planning: target resolution, surface range, candidate listing
55
- src/snapshot.ts checkpoint store (disk before-backups, restore/preview, bounded prune)
56
- src/snapshot-cleanup.ts cleanup policy + plugin-entry config persistence + auto-sweep throttle
57
- src/session-cwd.ts session-cwd resolution (fs-tools rule)
58
- src/locales.ts host i18n (t() renderer, HostKey)
59
52
  src/client/index.ts client plugin: /rewind command decoration + per-message ↶ button portals;
60
53
  re-exports the client contract (hiddenSeqsOf / targetSeqOfArgs / HiddenChat)
61
54
  src/client/popover.ts mode-selection popover (both-mode impact confirm)
@@ -73,7 +73,7 @@ Each release is `git push <branch>` followed by `git push <branch> --tags`.
73
73
 
74
74
  DSH is still in rc; npm's prerelease matching rules require a peer range to
75
75
  share the host version's `[major, minor, patch]` tuple. So `peerDependencies`
76
- uses one peer tuple per DSH line (e.g. `^0.1.2-rc.1`). The range is
76
+ uses one peer tuple per DSH line (e.g. `^0.2.0-rc.1`). The range is
77
77
  conservative: it declares only what was verified, and guarantees nothing
78
78
  outside it.
79
79
 
@@ -93,10 +93,10 @@ outside it.
93
93
  - **After DSH goes final**: final releases are not bound by the prerelease
94
94
  tuple rule, so the peers can converge to a single stable range (e.g.
95
95
  `^0.1.x`); this section can then be deleted.
96
- - **Declared minimum (`dsh.engines.dsh`)**: alongside the peer tuple, each
97
- release declares the DSH runtime floor under `dsh.engines.dsh` (e.g.
98
- `>=0.1.2-rc.1`). Nothing reads it on the current line yet, so it is a
99
- forward-looking declaration rather than an enforced guard. Bump it in
96
+ - **Declared minimum (`engines.dsh`)**: alongside the peer tuple, each
97
+ release declares the DSH runtime floor under `engines.dsh` (e.g.
98
+ `>=0.2.0-rc.1`). No runtime consumer reads it; its form and its alignment with
99
+ the peer tuple are pinned by `tests/package-layout.test.ts`. Bump it in
100
100
  the same release that changes the peer range; never leave code raised while
101
101
  the declared floor stays behind. Only the `>=X.Y.Z[-pre]` form is used.
102
102
  - **The peers are enforced at startup (`0.1.7-rc.1`)**: DSH checks a profile
@@ -111,15 +111,9 @@ outside it.
111
111
  independent of the host; a release declares its DSH line through the peer
112
112
  constraint (a single companion tuple), never through the plugin version.
113
113
 
114
- | Plugin version | DSH line | Role (example) |
115
- | --- | --- | --- |
116
- | `0.7.x` | `0.1.1` + `0.1.2` (broad) | frozen / EOL |
117
- | `0.8.x` | `0.1.2-rc.1` (single) | current line |
118
- | `0.9.x` | `0.1.3` (single) | following line |
119
-
120
- The rows are illustrative — the DSH line a release targets is the peer
121
- constraint, and its npm dist-tag is derived from the version (see above), so
122
- this model does not track the plugin's own version number.
114
+ The DSH line a release targets is the peer constraint, and its npm dist-tag is
115
+ derived from the version (see above), so this model does not track the plugin's
116
+ own version number.
123
117
 
124
118
  **Versioning.** A DSH version-line break is a MAJOR bump (incompatible with the
125
119
  prior DSH line). Within a line, MINOR/PATCH remain compatible.
@@ -130,11 +124,6 @@ and is always releasable. The currently-shipped stable is cut into a short-lived
130
124
  receives backported fixes while `main` advances to the next line. The prior
131
125
  (broad-compat) line is frozen as a tag, with no branch.
132
126
 
133
- **dist-tag routing.** The release workflow derives the npm dist-tag from the
134
- version: a stable version publishes to `latest`; a pre-release publishes to the
135
- dist-tag named by its pre-release identifier (`0.9.0-alpha.1` → `alpha`,
136
- `0.9.0-rc.1` → `rc`). A pre-release never occupies `latest`.
137
-
138
127
  **Support window / EOL.** A DSH line is supported within a declared window. By
139
128
  default the window runs until the next DSH line ships as `latest`; after that
140
129
  the line is EOL, frozen, and receives no further patches.
@@ -81,8 +81,8 @@ DSH 仍在 rc 阶段,npm 的 prerelease 匹配规则要求 peer 范围与宿
81
81
  范围 → `npm install` → `npm run check` → 发版。
82
82
  - **正式版后收敛**:DSH 发布 final 版本后,正式版不受 prerelease 元组规则
83
83
  限制,peer 可收敛为稳定的 `^0.1.x` 单范围,此节即可删除。
84
- - **声明的最低运行时(`dsh.engines.dsh`)**:与 peer 元组一起,每个发布在
85
- `dsh.engines.dsh` 声明 DSH 运行时下限(如 `>=0.1.2-rc.1`)。当前线上还没有
84
+ - **声明的最低运行时(`engines.dsh`)**:与 peer 元组一起,每个发布在
85
+ `engines.dsh` 声明 DSH 运行时下限(如 `>=0.1.2-rc.1`)。当前线上还没有
86
86
  任何读取方,因此它只是前置声明、尚未被强制执行。**与 peer 范围持平的同一发布里
87
87
  一并 bump**;不可只升代码、声明下限停留在旧值。仅使用 `>=X.Y.Z[-pre]` 形式。
88
88
  - **peer 在启动时会被强制执行(`0.1.7-rc.1`)**:DSH 会拿 bundle 自身的
@@ -1,7 +1,5 @@
1
1
  # Snapshot cleanup
2
2
 
3
- <!-- docs-scope: applies to DSH <= 0.1.6, where the policy is a dsh-settings document namespace. DSH 0.1.7 stores it as the plugin entry's own live configuration; the 0.1.7 seam is recorded in docs/compat/audit.md and docs/format.md. -->
4
-
5
3
  The plugin saves a backup of each file before it's edited, so you can rewind your
6
4
  code to an earlier point. These backups are called **snapshots**, and they're
7
5
  grouped by message and stored per session.
@@ -17,14 +15,8 @@ you've stopped using, and now and then remove their snapshots to keep disk usage
17
15
  down. It never touches your active session, and it never touches your
18
16
  conversation.
19
17
 
20
- **Manual**: even with automatic cleanup off, you can run the cleanup yourself:
21
-
22
- - `/snapshot-auto-cleanup run [--apply]` — preview, then actually remove the
23
- snapshots of sessions you haven't used for a while.
24
- - `/snapshot-auto-cleanup run --current [--apply]` — preview, then actually
25
- clear the **current** session's snapshots. This resets its rewind history to
26
- "from now on" (your conversation is unaffected). If a turn is currently
27
- running, the plugin pauses it first, then clears.
18
+ **Manual**: even with automatic cleanup off, you can run the cleanup yourself —
19
+ see [Commands](#commands) below.
28
20
 
29
21
  ## Commands
30
22
 
@@ -43,12 +35,14 @@ whether or not automatic cleanup is on.
43
35
 
44
36
  ## Settings
45
37
 
46
- 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:
38
+ The auto-cleanup switch and the idle-day cutoff are this plugin entry's own
39
+ configuration. View and edit them on the **Plugins** page, in this plugin's
40
+ configuration card, or with the `/snapshot-auto-cleanup` command:
47
41
 
48
42
  <img src="../assets/screenshots/cleanup-setting.png" alt="Plugin configuration card: auto cleanup and idle time (days)" width="600">
49
43
 
50
- - `enabled` — whether automatic cleanup runs (default `false`).
51
- - `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.
44
+ - `autoCleanupEnabled` — whether automatic cleanup runs (default `false`).
45
+ - `autoCleanupMaxAgeDays` — 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.
52
46
 
53
47
  ## When automatic cleanup runs
54
48
 
@@ -67,16 +61,18 @@ can't make it run early. To clean up right now instead of waiting, use
67
61
  session history.
68
62
  - Automatic cleanup never removes your **active** session's snapshots — only
69
63
  sessions that have been idle past the cutoff.
70
- - `run --current` clears the current session's snapshots. This is one-way for
71
- that session's file-rewind history: you can't rewind code to before the clear,
72
- but your conversation stays intact, and the session starts recording fresh
64
+ - `run --current` clears the current session's snapshots; if a turn is currently
65
+ running, the plugin pauses it first, then clears. This is one-way for that
66
+ session's file-rewind history: you can't rewind code to before the clear, but
67
+ your conversation stays intact, and the session starts recording fresh
73
68
  snapshots from now on.
74
69
 
75
70
  ## Known limitation
76
71
 
77
- The plugin keeps the most recent **100 messages'** snapshots per session. If you
78
- rewind or compact a lot in one long session, those 100 slots can be taken up by
79
- messages that are no longer reachable, so you may find you can't rewind as far
80
- back as you'd like. (Claude Code behaves the same way.) To get back to a clean
81
- state, run `/snapshot-auto-cleanup run --current --apply` to clear the current
82
- session and start fresh.
72
+ The plugin keeps the most recent **100 anchor groups'** snapshots per session (one
73
+ group per user message). If you rewind or compact a lot in one long session,
74
+ those 100 slots can be taken up by messages that are no longer reachable, so you
75
+ may find you can't rewind as far back as you'd like. (Claude Code behaves the
76
+ same way.) To get back to a clean state, run
77
+ `/snapshot-auto-cleanup run --current --apply` to clear the current session and
78
+ start fresh.
@@ -1,7 +1,5 @@
1
1
  # 快照清理
2
2
 
3
- <!-- docs-scope: 适用于 DSH <= 0.1.6,此时策略存放在 dsh-settings 文档的命名空间;DSH 0.1.7 起改为本插件条目自身的实时配置,0.1.7 的接缝记录在 docs/compat/audit.md 与 docs/format.md。 -->
4
-
5
3
  插件会在每次修改文件前保存一份备份,方便你把代码回退到之前的状态。这些备份叫**快照**,按消息分组、按会话存放。
6
4
 
7
5
  `/snapshot-auto-cleanup` 帮你管理这些快照——既可以回收旧会话占用的磁盘,也可以清空**当前**会话、让它从"现在"重新开始记录回退历史。
@@ -10,10 +8,7 @@
10
8
 
11
9
  **自动清理**(默认关闭):插件会记住哪些会话你已不再使用,并定期移除它们的快照以节省磁盘。它**绝不**触碰你当前活跃的会话,也**绝不**触碰你的对话。
12
10
 
13
- **手动**:即使自动清理是关闭的,你也可以自己执行清理:
14
-
15
- - `/snapshot-auto-cleanup run [--apply]` — 先预演,再真正移除你已有一阵子没用的会话的快照。
16
- - `/snapshot-auto-cleanup run --current [--apply]` — 先预演,再真正清空**当前**会话的快照。这会让它从"现在"重新开始记录回退历史(你的对话不受影响)。若此刻有正在进行中的回合,插件会先暂停它,再清空。
11
+ **手动**:即使自动清理是关闭的,你也可以自己执行清理——见下[命令](#命令)。
17
12
 
18
13
  ## 命令
19
14
 
@@ -35,8 +30,8 @@
35
30
 
36
31
  <img src="../assets/screenshots/cleanup-setting.png" alt="插件配置卡片:自动清理与不活跃时间(天)" width="600">
37
32
 
38
- - `enabled` — 是否运行自动清理(默认 `false`)。
39
- - `maxAgeDays` — 会话失活多少天后移除其快照(默认 `30`)。只接受正整数,因此一个损坏的设置永远不可能"删掉一切"。
33
+ - `autoCleanupEnabled` — 是否运行自动清理(默认 `false`)。
34
+ - `autoCleanupMaxAgeDays` — 会话失活多少天后移除其快照(默认 `30`)。只接受正整数,因此一个损坏的设置永远不可能"删掉一切"。
40
35
 
41
36
  ## 自动清理何时运行
42
37
 
@@ -46,8 +41,8 @@
46
41
 
47
42
  - 只会移除回退用的**快照**:一个会话的快照目录里只有写前文件备份及其附属记录(暂存副本、救援副本、还原日志),清理也只动这些。你的对话绝不会被动到,插件也绝不改写或删除你的会话历史。
48
43
  - 自动清理**永不**移除你**当前活跃**会话的快照——只清理已失活超过阈值的会话。
49
- - `run --current` 会清空当前会话的快照。这对该会话的文件回退历史是**不可逆**的:你无法回退到清空之前的代码状态,但你的对话保持完整,会话会从"现在"开始重新记录快照。
44
+ - `run --current` 会清空当前会话的快照;若此刻有正在进行中的回合,插件会先暂停它再清空。这对该会话的文件回退历史是**不可逆**的:你无法回退到清空之前的代码状态,但你的对话保持完整,会话会从"现在"开始重新记录快照。
50
45
 
51
46
  ## 已知限制
52
47
 
53
- 插件每个会话只保留**最近 100 条消息**的快照。若你在一个很长的会话里多次回退或压缩,这 100 个名额常会被"已经无法再回退到"的消息占满,你可能无法回退到想要的位置。(Claude Code 也是同样行为。)想恢复到一个干净状态,运行 `/snapshot-auto-cleanup run --current --apply` 清空当前会话、重新开始。
48
+ 插件每个会话只保留**最近 100 组锚点**的快照(每条用户消息一组)。若你在一个很长的会话里多次回退或压缩,这 100 个名额常会被"已经无法再回退到"的消息占满,你可能无法回退到想要的位置。(Claude Code 也是同样行为。)想恢复到一个干净状态,运行 `/snapshot-auto-cleanup run --current --apply` 清空当前会话、重新开始。