dsh-rewind-plugin 0.13.0-alpha.2 → 0.14.0-alpha.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/SECURITY.md CHANGED
@@ -128,9 +128,9 @@ transcript.
128
128
  bounded by the cap plus the number of unresolved journals (the exact cap is
129
129
  pinned in `docs/format.md`). Across sessions, the opt-in `pruneStale` sweep
130
130
  removes whole **long-inactive** session directories (measured by the newest
131
- member being idle past `maxAgeDays`); it uses `lstat` (no symlink following),
132
- skips dot-prefixed temp files, and never targets the active session
133
- (`keepActiveId`).
131
+ member being idle past the configured `autoCleanupMaxAgeDays`); it uses
132
+ `lstat` (no symlink following), skips dot-prefixed temp files, and never
133
+ targets the active session (`keepActiveId`).
134
134
 
135
135
  ## Crash safety
136
136
 
@@ -0,0 +1,24 @@
1
+ <svg width="36" height="36" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <!-- Layout follows the terminal card glyph (packages/client/ui-sidebar-terminal):
3
+ a rounded tile inset from the canvas (28/36 here, 22/28 there) with a
4
+ white round-capped prompt. The fill follows the Subagent plugin artwork
5
+ (packages/client/ui-primitives/src/plugin-artwork.tsx), whose blue square
6
+ carries the same vertical light-to-dark gradient: #69B9FF at the top,
7
+ #324DE2 at the bottom.
8
+ The rewind mark is the in-app back-arrow (src/client/styles.ts, 16-unit
9
+ grid) scaled 1.4x. Its ink box including the 0.75 stroke reaches
10
+ (1.75,1.75)-(14.25,12.75) on that grid, so the translate lands its centre
11
+ on the tile centre (18,18), and the ink spans 62.5% of the tile width —
12
+ the terminal glyph's own ratio. -->
13
+ <rect x="4" y="4" width="28" height="28" rx="8" fill="url(#rewind_blue)" />
14
+ <g transform="translate(6.8 7.85) scale(1.4)" stroke="#FFFFFF" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
15
+ <path d="M6.5 2.5 2.5 6.5l4 4" />
16
+ <path d="M2.5 6.5h7a4 4 0 0 1 4 4v1.5" />
17
+ </g>
18
+ <defs>
19
+ <linearGradient id="rewind_blue" x1="18" y1="4" x2="18" y2="32" gradientUnits="userSpaceOnUse">
20
+ <stop stop-color="#69B9FF" />
21
+ <stop offset="1" stop-color="#324DE2" />
22
+ </linearGradient>
23
+ </defs>
24
+ </svg>
@@ -7,7 +7,7 @@
7
7
  > compatibility invariants. A probe failure is a finding; it enters the
8
8
  > fix/pin/record loop.
9
9
  >
10
- > Targeted version: npm `@deepseek-ai/*@0.1.6-alpha.2` (the range the peers and `dsh.engines.dsh` declare).
10
+ > Targeted version: npm `@deepseek-ai/*@0.1.7-alpha.1` (the range the peers and `dsh.engines.dsh` declare).
11
11
  > Source reference: the upstream [github.com/deepseek-ai/deepseek-harness](https://github.com/deepseek-ai/deepseek-harness).
12
12
  >
13
13
  > Version alignment: `peerDependencies` use one tuple per DSH line
@@ -16,8 +16,11 @@
16
16
  > replaces the peer tuple (single-line model). The range is conservative — it
17
17
  > declares only what was verified, so a release already inside it changes
18
18
  > nothing — unless it changes interfaces inside the tuple: `0.1.6-alpha.2`
19
- > removed the client session-ownership and queue-mirror APIs, so the peer floor
20
- > moved to it. Signal: `npm view @deepseek-ai/dsh dist-tags`;
19
+ > removed the client session-ownership and queue-mirror APIs, and
20
+ > `0.1.7-alpha.1` moved the session writer to format v4 (producer-owned message
21
+ > sources, tool-role results, `developer/message`) and replaced the settings
22
+ > namespace registry with each entry's volatile `Config`, so the peer floor and
23
+ > both plugin seams moved with them. Signal: `npm view @deepseek-ai/dsh dist-tags`;
21
24
  > flow: `scripts/check-dsh-version.mjs` (it reads the `latest` dist-tag only; a
22
25
  > prerelease published under another tag is a manual pre-release check).
23
26
  >
@@ -35,7 +38,7 @@ legacy branch).
35
38
  | Host session log | `session.snapshotEvents()` |
36
39
  | Client chat snapshot | `uiConversation` `chat` view (`chatSnapshotOf`) |
37
40
  | Client composer refill | `conversation.input.setDraft` facade |
38
- | Client settings card | nested `ctx.inject(['settingsScope'])` |
41
+ | Client settings card | entry form via `ctx.configForms.get(entryId)` (`plugins.bundle.config`) |
39
42
  | Client seat-button DOM | structural locate of the copy-`<button>` container (`actionsContainerOf`) |
40
43
 
41
44
  ## Definition of "fully compatible" (invariants)
@@ -62,10 +65,11 @@ legacy branch).
62
65
  - **rewind across a compact checkpoint**: `RewindError('not-on-surface')` refuses cleanly, no crash.
63
66
  - **plan-mode**: the marker is a turn-less `user/message` (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
67
  - **agent-loop cancellation**: `finally` guarantees step/turn closure; the rewind force-stop path leaves no dangling frame.
65
- - **settings-card registration**: the Snapshot cleanup card must be registered through a **nested** `ctx.inject(['settingsScope'], …)` — naming `settingsScope` in the module-level inject would keep the whole client plugin unmounted on a host without that service (card and rewind button would disappear). It reads `getSnapshot().value` + `set`, never the `mutate` write API.
68
+ - **settings-card registration (`0.1.7-alpha.1`)**: `settingsScope` is gone with the namespace registry. 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 the entry's form snapshot and writes through its revision-fenced `mutate`, and disposes the model's subscription with the fiber.
66
69
  - **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`.
67
70
  - **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'`). Pins: `tests/pending.test.ts`, `tests/client-retract.test.ts`.
68
71
  - **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`.
72
+ - **client settings form (`0.1.7-alpha.1`)**: the same release retired the `settingsScope` bind (namespace registry) in favour of the shared per-entry form; the card now reads that form's snapshot and stages edits through `mutate`. Pin: `tests/client-settings-card.test.ts` (staged plan, refusal, and the fiber releasing the model's subscription), `tests/client-lifecycle.test.ts`.
69
73
 
70
74
  - **`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.
71
75
  - **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`.
@@ -76,14 +80,14 @@ legacy branch).
76
80
 
77
81
  - **session-stats / session-telemetry fold the full log**: post-rewind stats do **not** rewind — `turns`/`steps`/`llmMs` still include withdrawn content; the `user/message` marker is folded as a present user turn (it adds no step). This is the intended "fold the full log" semantics, pinned by probe.
78
82
  - **token-meter usage anchor stays stable** (G3): the `user/message` marker carries no usage, but because it is not an `assistant/message`, the baseline anchor does not drop to a heuristic estimate — it stays `usage` across a rewind. Pinned by `compat-gaps` G3.
79
- - **marker content is the constant `(empty message)` placeholder**: never empty, because the session log is immutable but the model serving a session may change later — a strict OpenAI-compatible gateway rejects an empty user message (HTTP 400, Issue #21). The marker is a small visible user turn in derived history. Pin: `verify-host` (`marker is a user/message with the dsh-rewind plugin source and the (empty message) placeholder`).
80
- - **marker shape (v3)**: the replace `surfaceOp` is `{ op: 'replace', startSeq, endSeq }` (renamed from `start`/`end` in session-format v3); the dsh-rewind source is the plain CLOSED third-party plugin shape `{ kind: 'plugin', plugin: 'dsh-rewind' }` (no private field — the released-format source validator rejects any member outside `kind`/`plugin` + the context-injection `form`/`sections`/`summary`, and a plugin extends the source map by adding a `kind`, not fields); the `user/message` + `sourceEventSeqs` shape and the `(empty message)` content are unchanged.
83
+ - **marker content is the constant `(empty message)` placeholder**: never empty, because the session log is immutable but the model serving a session may change later — a strict OpenAI-compatible gateway rejects an empty user message (HTTP 400, Issue #21). The marker is a small visible user turn in derived history. Pin: `verify-host` (`marker is a user/message with the dsh-rewind producer source and the (empty message) placeholder`).
84
+ - **marker shape (v4)**: the replace `surfaceOp` is `{ op: 'replace', startSeq, endSeq }` (renamed from `start`/`end` in session-format v3); the dsh-rewind source is the single producer-owned kind `{ kind: 'dsh-rewind' }`, declared by merging `MessageSourceMap` (v4 removed the catch-all `plugin` kind and rejects it on write). Two older shapes stay readable, because a stored marker is immutable: the released v3 wrapper `{ kind: 'plugin', plugin: 'dsh-rewind' }`, and the `plugin:dsh-rewind` form the v3→v4 conversion gives a producer its rename table does not know. The `user/message` + `sourceEventSeqs` shape and the `(empty message)` content are unchanged.
81
85
  - **Withdrawn content stays searchable/exportable**: session-query full-text and `/export` read the raw log; a rewind cuts only the surface, so withdrawn messages remain (declared in the README).
82
86
  - **Session title auto-regeneration**: the title derives from the surface, so an automatically-derived title may change after a rewind.
83
87
  - **Files written but uncommitted in a cancelled turn**: a `both` rewind cannot restore them (tool side-effect timing; same as Claude Code).
84
88
  - **Attachment files left after a message is shadowed**: attachment storage is not cleaned with the surface (`dsh-attachment-local` not installed, not automatically verified).
85
89
  - **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`.
86
- - **Bundle card metadata is not localizable (`0.1.6-alpha.2`)**: the manager titles a bundle with `shortName(pkg.name)` (`dsh-rewind-plugin` → `rewind-plugin`) and shows the raw `package.json` description; only its private `BUILTIN_COPY` (three official bundles) is translated, and `DshManifest` carries no title/description field. Its rows section renders for every bundle, official included. Only the description is the plugin's to set; nothing is compensated.
90
+ - **Bundle card metadata is declared by the plugin, not by the manager (`0.1.7-alpha.1`)**: the manager still titles a bundle with `shortName(pkg.name)` and its raw `package.json` description when nothing else is declared, but it now reads plugin-owned resources first — `locale/<lang>.json` (`meta.title`/`meta.description`) through the package exports map, and an `icon` file relative to the manifest. This plugin declares both, so its card shows an icon and copy that follows the active language; only the fallback description remains mixed-language. Pin: `tests/package-layout.test.ts` (icon path/media/size and both dictionaries).
87
91
 
88
92
  - **Synchronous Session history reads are deprecated** on this line (`snapshotEvents` / `eventAt` / `ownEvents`): existing calls may remain, new calls are prohibited. The plugin keeps its existing reads (turn anchor + candidate listing); the migration path is a `ctx.sessionProjections` projection unit or the async paged history read this line has not shipped yet — not a plugin-side index. Not migrated in this release.
89
93
 
@@ -91,39 +95,36 @@ legacy branch).
91
95
 
92
96
  The plugin treats these as harness-side defects it does not compensate for. Each entry records the harness issue, its current status, and the plugin's stance, so a future maintainer does not "fix the wrong direction."
93
97
 
94
- ### RU-I18N: host-side locale preference is not reliably readable at command registration (harness-side structural timing defect; plugin reads once, never retries)
98
+ ### RU-I18N (resolved on the 0.1.7 line): host command copy follows the user's language
95
99
 
96
- - **Root cause (harness-side)**: the plugin resolves `activeLocale` in an
97
- `ctx.inject(['settings'])` callback that reads `settings.get('locale').preference`
98
- **once, with no retry** (`src/index.ts`). `dsh-client-locale`'s host half
99
- registers that `locale` settings section **through its own `ctx.inject(['settings'])`**
100
+ - **History (0.1.2–0.1.6 lines)**: the plugin resolved `activeLocale` in an
101
+ `ctx.inject(['settings'])` callback that read `settings.get('locale').preference`
102
+ **once, with no retry** (`src/index.ts`), while `dsh-client-locale`'s host half registered that
103
+ `locale` settings section through its own `ctx.inject(['settings'])`
100
104
  (`packages/client/locale/src/index.ts`). Both callbacks wait only on `settings` and are
101
- independent of each other, so cordis makes **no ordering guarantee** between the
102
- section registration and the plugin's read.
103
- - **Observed failure**: a probe confirmed that when the plugin's read runs, `settings.get('locale')`
104
- returns `undefined` (the section is not yet registered), so `activeLocale` stays at the
105
- default `'en'` and — because the read is one-shot — is never corrected. The result is that
106
- **all host-side `t()` output (runtime messages and command descriptions alike) renders in
107
- English**, regardless of the user's language preference.
108
- - **Not a user-config problem**: `settings.yaml` correctly carries `locale.preference: zh`;
109
- reading it directly works. The defect is purely the host-side read racing the section
110
- registration.
111
- - **Plugin stance**: no compensation. The plugin does not add a lazy re-read, a retry loop, or a
112
- post-locale description re-registration as a workaround, because the guarantee belongs to the
113
- harness (make the locale preference available before plugin registration, or support per-locale
114
- command descriptions). The plugin's `t()` design is retained; localized host output is treated
115
- as an upstream capability to be restored when the harness provides it. (This is still not a
116
- plugin deviation: the client localizes only the six first-party descriptions in
117
- `HOST_FACES` — compact, export, feedback, goal, permission, plan — while every
118
- third-party description, this plugin's included, passes through verbatim.)
119
- - **Client-side command-description i18n is first-party-only**: DSH also localizes host command
105
+ independent of each other, so cordis made **no ordering guarantee** between the section
106
+ registration and the plugin's read. The plugin's own command descriptors were registered
107
+ **synchronously** in the same `apply`, i.e. before that microtask ran, so their `description`
108
+ strings were baked in the neutral English default — the defect reported as #28 — while the
109
+ runtime output, evaluated later at command execution from the then-updated `activeLocale`, did
110
+ follow the stored preference. A preference changed *after* the mount reached the output only on
111
+ the next mount, because the read was one-shot.
112
+ - **Resolution (0.1.7)**: the section is gone — the locale host half now only calls
113
+ `settings.configure({ auto: false })`, and the preference lives in the `locale` entry's own
114
+ Config. The plugin reads that same value through the settings service's descriptor read
115
+ (`readHostLocale` in `src/index.ts` calls `settings.describe({ redactSecrets: true })`, the route
116
+ the settings UI and the client's own locale runtime take): at mount, which is what the
117
+ once-registered command descriptions carry, and again before each command renders, so the output
118
+ follows a language switch without a remount (the descriptors keep the mount-time language until
119
+ the plugin remounts). The read is best-effort: a missing method, a missing entry, or a
120
+ throwing read stays on the neutral English default.
121
+ - **Client-side command-description i18n is still first-party-only**: DSH localizes host command
120
122
  descriptions through the client `locale` binding (`ui-commands`), but the description keys come
121
123
  from a **closed allowlist** (`HOST_FACES`: compact, export, feedback, goal,
122
124
  permission, plan). A command outside that set — every third-party plugin — is passed through
123
125
  verbatim, never translated (`builtinRowFace` only rewrites a description that equals the
124
- first-party English copy). So the plugin's own `/rewind` command description can never ride this
125
- channel either; it is authored in the host's tongue (English by default), same as the host
126
- runtime copy above.
126
+ first-party English copy), and no host command *output* is localized by the harness for anyone.
127
+ This plugin's host dictionary is what carries both, in the language read above.
127
128
 
128
129
  ### R-OPENSTEP (rewind part resolved): an unclosed `step` in the log breaks token-meter replay; the rewind no longer compounds it
129
130
 
@@ -161,6 +162,34 @@ new turn at `phase.turn + 1` (`agent.ts:277-283`) **without closing the leftover
161
162
  - **Automatic compaction silently stays disabled** (the `agent/pre-step` hook catches and warns; the conversation continues).
162
163
  - **rewind's role (resolved)**: the v2 `user/message` rewind marker appends no `step/start`, so a rewind no longer introduces the first trip-wire and the R-OPENSTEP rewind amplifier is closed. The harness-side root cause (an unclosed step after a crash, tripped by continuing the conversation) remains.
163
164
 
165
+ ### R-PROMPTCARD (resolved on the 0.1.7 line): the repeated `System prompt` card is filtered out of chat
166
+
167
+ > **History (0.1.2–0.1.6 lines)**: Chat rendered a collapsed `System prompt`
168
+ > card at every request-series start. Any surface mutation bumps `contentGeneration` (a positional
169
+ > `replace` — rewind, `/compact` — or a `SessionMessageProjection`, e.g. image offload), so the next
170
+ > request logs `request/header { reason: 'series' }`; a process restart logs `'resume'`. The client
171
+ > shows the card for every reason except `'change'`. The effective prompt is unchanged — only the
172
+ > card repeats. Triggers beyond rewind: `/compact`, restart/resume, projections, explicit
173
+ > `startsSeries`.
174
+ >
175
+ > **Upstream decision (deliberate, twice)**: `6ad01cbd8d` suppressed the repeat on `resume` (note
176
+ > `2026-09-03-resume-headers-do-not-repeat-system-prompts.md`, rejecting the card as a "lifecycle
177
+ > marker" that "incorrectly implies another prompt injection"); three days later `77b6b10c21`
178
+ > restored it — a resume/series header starts a visible request series. Locked by the `ui-chat` test
179
+ > `repeats an unchanged system prompt after a surface rewrite…`.
180
+ >
181
+ > **Resolution (0.1.7)**: upstream stopped rendering the card in chat at all. `isVisibleChatNode`
182
+ > (`packages/client/ui-chat/src/client/contract/chat-visibility.ts`, added by `b6f99fad55`
183
+ > "feat(ui-chat): filter system prompts and permission commands") drops `system-prompt` nodes — and
184
+ > the `permission` command — from the visible chat rows, while the durable events and the trajectory
185
+ > inspection keep them. The `showsPrompt` computation is unchanged and no longer matters: the node is
186
+ > filtered before the row is built, so a rewind on this line adds no prompt row at all. #22, #31 and
187
+ > README known issue #4 no longer reproduce.
188
+ >
189
+ > **Plugin stance**: still no compensation. The DOM row-hiding seam (`hiddenSeqsOf`) covers withdrawn
190
+ > messages, where hiding is essential, not cosmetic; nothing was added for the card, and the
191
+ > display-layer workup above stays valid history.
192
+
164
193
  ## Uncovered boundaries (need an additional e2e layer; non-blocking)
165
194
 
166
195
  - Real LLM streaming and auto title generation (L2 stubbed).
@@ -186,7 +215,8 @@ new turn at `phase.turn + 1` (`agent.ts:277-283`) **without closing the leftover
186
215
  | client ordering | — | — | ✓ | — | — | — | ✓ | — |
187
216
  | plan-mode | ✓ | — | ✓ | — | — | — | — | ✓ (static) |
188
217
 
189
- ✓ = probe passes; — = not applicable. RU-I18N and R-OPENSTEP are upstream issues the plugin
190
- does not compensate for (see above); G3 is a confirmed-non-defect behavior pinned in
218
+ ✓ = probe passes; — = not applicable. RU-I18N and R-PROMPTCARD are resolved on the 0.1.7 line (see
219
+ above); R-OPENSTEP is an upstream issue the
220
+ plugin does not compensate for (see above); G3 is a confirmed-non-defect behavior pinned in
191
221
  `compat-gaps.test.ts` (G1 surface classification via `foldSurface` and G2 projection checkpoint
192
222
  both pass).
@@ -35,7 +35,9 @@ dsh-chat-timeline#6).
35
35
  ## DOM attribute
36
36
 
37
37
  Each withdrawn row carries `data-dsh-rewind-hidden="true"` while hidden,
38
- removed on un-hide. Contract:
38
+ removed on un-hide. The same attribute also lands on a step/process GROUP shell
39
+ — the view container around a turn's process rows, which is not itself a message
40
+ — when every member it holds is withdrawn. Contract:
39
41
 
40
42
  - The **attribute name** is stable; treat the value as opaque.
41
43
  - It is **observational only** — rewind hides via `style.display`; the
package/docs/format.md CHANGED
@@ -261,10 +261,11 @@ migration tool. Old-format data is never silently re-interpreted.
261
261
 
262
262
  ## Cleanup policy persistence
263
263
 
264
- The snapshot auto-cleanup policy (the `enabled` switch and the `maxAgeDays`
265
- idle cutoff) is no longer a file: it lives in the **dsh-settings document** under
266
- the `dsh-rewind-snapshot-cleanup` namespace (validated by a schemastery schema;
267
- defaults are the `base` layer). The policy was previously persisted in a legacy
268
- `<dsh home>/snapshot-cleanup.json` file; that file store and its one-time
269
- startup migration have been removed. The last-sweep clock stays in its own
270
- `<dsh home>/snapshot-cleanup-last-sweep.json` state file, which is unchanged.
264
+ The snapshot auto-cleanup policy (the `autoCleanupEnabled` switch and the
265
+ `autoCleanupMaxAgeDays` idle cutoff) is no longer a file: it is this plugin
266
+ entry's own live configuration (both fields declared `.volatile()`, validated by
267
+ the entry's schemastery `Config` schema, defaults on the schema). The policy was
268
+ previously persisted in a legacy `<dsh home>/snapshot-cleanup.json` file; that
269
+ file store and its one-time startup migration have been removed. The last-sweep
270
+ clock stays in its own `<dsh home>/snapshot-cleanup-last-sweep.json` state file,
271
+ which is unchanged.
@@ -1,5 +1,7 @@
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
+
3
5
  The plugin saves a backup of each file before it's edited, so you can rewind your
4
6
  code to an earlier point. These backups are called **snapshots**, and they're
5
7
  grouped by message and stored per session.
@@ -1,5 +1,7 @@
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
+
3
5
  插件会在每次修改文件前保存一份备份,方便你把代码回退到之前的状态。这些备份叫**快照**,按消息分组、按会话存放。
4
6
 
5
7
  `/snapshot-auto-cleanup` 帮你管理这些快照——既可以回收旧会话占用的磁盘,也可以清空**当前**会话、让它从"现在"重新开始记录回退历史。