dsh-rewind-plugin 0.9.1 → 0.10.0-alpha.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/README.en.md +2 -2
- package/README.md +2 -2
- package/docs/architecture.md +24 -16
- package/docs/compat/audit.md +4 -3
- package/docs/format.md +3 -4
- package/docs/harness-reference.md +1 -4
- package/docs/release/release.md +7 -0
- package/docs/release/release.zh.md +5 -0
- package/docs/rewind-fix.md +1 -1
- package/docs/rewind-fix.zh.md +1 -1
- package/lib/client.js +12 -6
- package/lib/index.js +12 -646
- package/lib/types/client/styles.d.ts +1 -1
- package/lib/types/locales.d.ts +0 -20
- package/lib/types/snapshot-cleanup.d.ts +12 -48
- package/package.json +47 -44
- package/lib/types/rewind-fix.d.ts +0 -111
- package/lib/types/rewind-marker-repair.d.ts +0 -129
- package/lib/types/session-log-io.d.ts +0 -57
package/README.en.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# dsh-rewind
|
|
2
2
|
|
|
3
3
|
> [!WARNING]
|
|
4
|
-
> **Planning to use DSH `0.1.3`?
|
|
4
|
+
> **Planning to use DSH `0.1.3`? Install a `v0.9.x` release as soon as possible and run `/dsh-rewind-fix` to update old rewind markers** ([update guide](docs/rewind-fix.md)).
|
|
5
5
|
|
|
6
6
|
Conversation rewind for DeepSeek Harness: **rewind the conversation to any earlier user message in one click, in the same window** — no new branch, no window switch, with optional workspace-file restore (full Claude Code `/rewind` semantics).
|
|
7
7
|
|
|
@@ -106,7 +106,7 @@ The whole design rests on two principles, simple but deliberate: **the conversat
|
|
|
106
106
|
|
|
107
107
|
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:
|
|
108
108
|
|
|
109
|
-
- **Before-write backup**: tracks only the write-class tools (`write`, `edit
|
|
109
|
+
- **Before-write backup**: tracks only the write-class tools (`write`, `edit`) — 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.
|
|
110
110
|
- **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".
|
|
111
111
|
- **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.
|
|
112
112
|
- **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".
|
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# dsh-rewind
|
|
2
2
|
|
|
3
3
|
> [!WARNING]
|
|
4
|
-
> **计划使用 DSH `0.1.3`
|
|
4
|
+
> **计划使用 DSH `0.1.3` 的用户:请尽早安装 `v0.9.x` 版本,并运行 `/dsh-rewind-fix` 更新旧回退标记**([更新指南](docs/rewind-fix.zh.md))。
|
|
5
5
|
|
|
6
6
|
DeepSeek Harness 插件:**一键就地回退对话到任意更早的用户消息**——同窗口内完成,不新建分支、不换窗口,可一并还原工作区文件(完整 Claude Code `/rewind` 语义)。
|
|
7
7
|
|
|
@@ -96,7 +96,7 @@ dsh plugin --profile web add dsh-rewind-plugin
|
|
|
96
96
|
|
|
97
97
|
文件部分对齐 Claude Code 的检查点语义——**局部追踪、写前备份 + 每条消息重扫已跟踪文件**,而不是整树快照。这项取舍既省空间,又更完整:
|
|
98
98
|
|
|
99
|
-
- **写前备份**:只追踪写类工具(`write`、`edit
|
|
99
|
+
- **写前备份**:只追踪写类工具(`write`、`edit`),写前**备份原内容**,并**记录、追踪**被处理的文件——从不备份整个工作区,因此轻量。
|
|
100
100
|
- **外部变更也追**:每条用户消息边界,插件重新检查所有已跟踪文件——命令执行、手动修改等外部变更同样被记录,回退时一并还原。这让「轻量」却不「残缺」。
|
|
101
101
|
- **不变不存、同内容存链接**:记录只在有变化时发生——消息边界重扫时无变更的不备份(不留记录);写前备份时若与前一条记录一致,只存**指向它的链接**(`ref`)而非复制内容。重复写入几乎不占空间,链接也先落地、绝不悬空。
|
|
102
102
|
- **还原时对照真实磁盘**:先取每条路径的**最早**记录,再实时读取文件当前内容与之比对——**只操作真正不一致的文件**:改过的写回最早期内容、目标之后新建的删除、已经一致的跳过。重复回退因此**零副作用、幂等**,不会出现「幽灵影响」。
|
package/docs/architecture.md
CHANGED
|
@@ -17,12 +17,9 @@ credentials access.
|
|
|
17
17
|
|
|
18
18
|
```
|
|
19
19
|
src/
|
|
20
|
-
├── index.ts host plugin: /rewind, /undo, /snapshot-auto-cleanup
|
|
21
|
-
│
|
|
20
|
+
├── index.ts host plugin: /rewind, /undo, /snapshot-auto-cleanup
|
|
21
|
+
│ commands + checkpoint pipeline
|
|
22
22
|
├── rewind.ts pure planning: target parsing, surface-range plan, candidate listing
|
|
23
|
-
├── rewind-fix.ts /dsh-rewind-fix orchestration (repair pipeline, locks, rollback)
|
|
24
|
-
├── rewind-marker-repair.ts pure legacy-marker transform (A/B → C)
|
|
25
|
-
├── session-log-io.ts session-log zstd codec + lossless re-encoder (rewind-fix write-back)
|
|
26
23
|
├── snapshot.ts checkpoint store: disk before-backups, journaled restore,
|
|
27
24
|
│ reconcile / continue / rollback, bounded prune
|
|
28
25
|
├── snapshot-cleanup.ts cleanup policy + dsh-settings persistence + auto-sweep throttle
|
|
@@ -33,6 +30,11 @@ src/
|
|
|
33
30
|
pending interaction, locales, styles
|
|
34
31
|
```
|
|
35
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**.
|
|
37
|
+
|
|
36
38
|
Two dependency rules keep the design testable:
|
|
37
39
|
|
|
38
40
|
1. **`rewind.ts` is pure** — no I/O, no `Session` dependency; everything
|
|
@@ -53,7 +55,7 @@ machine channels (see [Compatibility strategy](#compatibility-strategy)).
|
|
|
53
55
|
user/message currently on the surface)
|
|
54
56
|
→ agent.cancel({ keepInbox: true }) if running; waitForAgentIdle
|
|
55
57
|
→ dropPendingSteering (next-step inbox only; queued messages untouched)
|
|
56
|
-
→ append the rewind marker =
|
|
58
|
+
→ append the rewind marker = a user/message with surfaceOp
|
|
57
59
|
{ op: 'replace', start, end } over every surface node after the
|
|
58
60
|
target (+ sourceEventSeqs = shadowed seqs) [a single event]
|
|
59
61
|
→ if mode 'both': store.restoreAfter(targetSeq) + syncRestoreObservations
|
|
@@ -66,11 +68,14 @@ Key invariants:
|
|
|
66
68
|
|
|
67
69
|
- **The log is append-only.** The marker is the *only* mutation: it cuts the
|
|
68
70
|
model-visible surface, never the raw history (search/export still see it).
|
|
69
|
-
- **The marker is
|
|
70
|
-
source
|
|
71
|
-
shadowed seqs (`sourceEventSeqs`) — `assistant/message` can
|
|
72
|
-
them (v2). It derives to itself, so it stays as a present
|
|
73
|
-
turn at the surface tail
|
|
71
|
+
- **The marker content is a constant `(empty message)` placeholder.** It carries
|
|
72
|
+
the dsh-rewind plugin source and is a `user/message`, the only surface type
|
|
73
|
+
that can cite the shadowed seqs (`sourceEventSeqs`) — `assistant/message` can
|
|
74
|
+
no longer carry them (v2). It derives to itself, so it stays as a present
|
|
75
|
+
user turn at the surface tail. The content is provider-independent and never
|
|
76
|
+
empty: the session log is immutable but the model serving a session may
|
|
77
|
+
change later, and a strict OpenAI-compatible gateway rejects an empty user
|
|
78
|
+
message (HTTP 400, Issue #21).
|
|
74
79
|
- **No ghost step frame is needed**: the token-meter step machine ignores
|
|
75
80
|
`user/message`, and the session invariant imposes no open-turn requirement
|
|
76
81
|
on it, so the marker is appended while idle, outside any turn, as one event.
|
|
@@ -80,8 +85,9 @@ Key invariants:
|
|
|
80
85
|
|
|
81
86
|
### Marker format history
|
|
82
87
|
|
|
83
|
-
The rewind marker is written as form C (
|
|
84
|
-
`surfaceOp.replace` over the shadowed range
|
|
88
|
+
The rewind marker is written as form C (a `user/message` with a
|
|
89
|
+
`surfaceOp.replace` over the shadowed range, `sourceEventSeqs`, and the
|
|
90
|
+
constant `(empty message)` content). Earlier
|
|
85
91
|
plugin versions wrote shapes a newer harness no longer accepts:
|
|
86
92
|
|
|
87
93
|
- **form A** — a bare `assistant/message(turn=N, step=0)` with no frame.
|
|
@@ -89,13 +95,15 @@ plugin versions wrote shapes a newer harness no longer accepts:
|
|
|
89
95
|
inside a closed turn.
|
|
90
96
|
|
|
91
97
|
A/B became unreadable once v2 reserved surface `replace` to a node that cites
|
|
92
|
-
`sourceEventSeqs` (`assistant/message` can no longer carry them).
|
|
93
|
-
|
|
98
|
+
`sourceEventSeqs` (`assistant/message` can no longer carry them). The
|
|
99
|
+
`/dsh-rewind-fix` command rewrote form A/B in closed sessions to form C so a
|
|
100
|
+
newer harness accepts the log — it was a **temporary migration tool** (kept on
|
|
101
|
+
the `0.9.x` line) and is **removed in the `0.10.x` line**.
|
|
94
102
|
|
|
95
103
|
## Checkpoint pipeline (Claude Code before-backup model)
|
|
96
104
|
|
|
97
105
|
```
|
|
98
|
-
tools/execute captureBefore: for write / edit
|
|
106
|
+
tools/execute captureBefore: for write / edit
|
|
99
107
|
(mutating commands only), read the file's BEFORE state;
|
|
100
108
|
subagent edits are NOT tracked (Claude Code alignment).
|
|
101
109
|
tools/post-execute commitEntry: anchor = latest user/message seq; skip
|
package/docs/compat/audit.md
CHANGED
|
@@ -50,7 +50,7 @@ legacy branch).
|
|
|
50
50
|
|
|
51
51
|
## Verified-compatible surfaces (probes pass)
|
|
52
52
|
|
|
53
|
-
- **token-meter replay** (the
|
|
53
|
+
- **token-meter replay** (the `user/message` marker + multiple rewinds + interleaved real turns + compact stacking).
|
|
54
54
|
- **compaction transactions**: `toolPairingBalancedBefore/After` stays balanced after a marker cut; the real `/compact` command (`command-compact` + `compaction-basic`, stub summarizer) can land `compaction/start…end` on top of a rewind marker and stay replayable; `/compact` is a legal no-op on a small surface.
|
|
55
55
|
- **resume replay**: `Session.create(id, events)` replays a rewind/compact-bearing log.
|
|
56
56
|
- **session-stats**: the `user/message` marker adds no step (the step count stays at the real turns' steps), no phantom turn.
|
|
@@ -63,8 +63,9 @@ legacy branch).
|
|
|
63
63
|
|
|
64
64
|
## Known behavior boundaries (deterministic differences, non-crash, documented)
|
|
65
65
|
|
|
66
|
-
- **session-stats / session-telemetry fold the full log**: post-rewind stats do **not** rewind — `turns`/`steps`/`llmMs` still include withdrawn content; the
|
|
67
|
-
- **token-meter usage anchor stays stable** (G3): the
|
|
66
|
+
- **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.
|
|
67
|
+
- **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.
|
|
68
|
+
- **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`).
|
|
68
69
|
- **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).
|
|
69
70
|
- **Session title auto-regeneration**: the title derives from the surface, so an automatically-derived title may change after a rewind.
|
|
70
71
|
- **Files written but uncommitted in a cancelled turn**: a `both` rewind cannot restore them (tool side-effect timing; same as Claude Code).
|
package/docs/format.md
CHANGED
|
@@ -158,8 +158,7 @@ Old-format data is never silently re-interpreted.
|
|
|
158
158
|
The snapshot auto-cleanup policy (the `enabled` switch and the `maxAgeDays`
|
|
159
159
|
idle cutoff) is no longer a file: it lives in the **dsh-settings document** under
|
|
160
160
|
the `dsh-rewind-snapshot-cleanup` namespace (validated by a schemastery schema;
|
|
161
|
-
defaults are the `base` layer).
|
|
162
|
-
`<dsh home>/snapshot-cleanup.json`
|
|
163
|
-
|
|
164
|
-
default and is dropped). The last-sweep clock stays in its own
|
|
161
|
+
defaults are the `base` layer). The policy was previously persisted in a legacy
|
|
162
|
+
`<dsh home>/snapshot-cleanup.json` file; that file store and its one-time
|
|
163
|
+
startup migration have been removed. The last-sweep clock stays in its own
|
|
165
164
|
`<dsh home>/snapshot-cleanup-last-sweep.json` state file, which is unchanged.
|
|
@@ -49,12 +49,9 @@ 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
|
|
52
|
+
src/index.ts host plugin: /rewind|/undo|/snapshot-auto-cleanup
|
|
53
53
|
+ checkpoint pipeline (tools/execute|post-execute)
|
|
54
54
|
src/rewind.ts pure planning: target resolution, surface range, candidate listing
|
|
55
|
-
src/rewind-fix.ts /dsh-rewind-fix orchestration (repair pipeline, locks, rollback)
|
|
56
|
-
src/rewind-marker-repair.ts pure legacy-marker transform (A/B → C)
|
|
57
|
-
src/session-log-io.ts session-log zstd codec + lossless re-encoder (rewind-fix write-back)
|
|
58
55
|
src/snapshot.ts checkpoint store (disk before-backups, restore/preview, bounded prune)
|
|
59
56
|
src/snapshot-cleanup.ts cleanup policy + dsh-settings persistence + auto-sweep throttle
|
|
60
57
|
src/session-cwd.ts session-cwd resolution (fs-tools rule)
|
package/docs/release/release.md
CHANGED
|
@@ -91,6 +91,13 @@ appended) when DSH releases a new tuple.
|
|
|
91
91
|
- **After DSH goes final**: final releases are not bound by the prerelease
|
|
92
92
|
tuple rule, so the peers can converge to a single stable range (e.g.
|
|
93
93
|
`^0.1.x`); this section can then be deleted.
|
|
94
|
+
- **Declared minimum (`dsh.engines.dsh`)**: alongside the peer tuple, each
|
|
95
|
+
release declares the DSH runtime floor under `dsh.engines.dsh` (e.g.
|
|
96
|
+
`>=0.1.2-rc.1`), consumed by the plugin-manager update guard. Bump it in
|
|
97
|
+
the same release that raises the peer tuple; never leave code raised while
|
|
98
|
+
the declared floor stays behind. Only the `>=X.Y.Z[-pre]` form is
|
|
99
|
+
supported (`^`/`~`/multi-range are treated as "cannot verify" and
|
|
100
|
+
fail closed).
|
|
94
101
|
|
|
95
102
|
## Versioned-line release model
|
|
96
103
|
|
|
@@ -79,6 +79,11 @@ DSH 仍在 rc 阶段,npm 的 prerelease 匹配规则要求 peer 范围与宿
|
|
|
79
79
|
→ devDependencies 同步升到最新 → `npm install` → `npm run check` → 发版。
|
|
80
80
|
- **正式版后收敛**:DSH 发布 final 版本后,正式版不受 prerelease 元组规则
|
|
81
81
|
限制,peer 可收敛为稳定的 `^0.1.x` 单范围,此节即可删除。
|
|
82
|
+
- **声明的最低运行时(`dsh.engines.dsh`)**:与 peer 元组一起,每个发布在
|
|
83
|
+
`dsh.engines.dsh` 声明 DSH 运行时下限(如 `>=0.1.2-rc.1`),供插件管理器
|
|
84
|
+
更新守卫读取。**与 peer 元组持平的同一发布里一并 bump**;不可只升代码、
|
|
85
|
+
声明下限停留在旧值。仅支持 `>=X.Y.Z[-pre]` 形式(`^`/`~`/多范围会被视为
|
|
86
|
+
「无法校验」而 fail-closed)。
|
|
82
87
|
|
|
83
88
|
## 发布版本线模型
|
|
84
89
|
|
package/docs/rewind-fix.md
CHANGED
package/docs/rewind-fix.zh.md
CHANGED
package/lib/client.js
CHANGED
|
@@ -446,7 +446,12 @@ var STYLE = `
|
|
|
446
446
|
line-height: 1.5;
|
|
447
447
|
color: var(--dsw-alias-label-error);
|
|
448
448
|
}
|
|
449
|
-
/* Switch row: label left, role=switch button right, hint below (Subagent module).
|
|
449
|
+
/* Switch row: label left, role=switch button right, hint below (Subagent module).
|
|
450
|
+
* The track mirrors the harness ui-primitives Switch (0.1.3-alpha.2 line): the
|
|
451
|
+
* on/off appearance keys off aria-checked rather than a parallel class, so the
|
|
452
|
+
* visual state cannot disagree with the state assistive technology reads, and
|
|
453
|
+
* corner-shape: round opts the capsule track out of the global superellipse
|
|
454
|
+
* (which would square the capsule ends off against the round thumb inside). */
|
|
450
455
|
.dsh-rewind-cleanup-toggle-row {
|
|
451
456
|
display: flex;
|
|
452
457
|
align-items: flex-start;
|
|
@@ -469,10 +474,11 @@ var STYLE = `
|
|
|
469
474
|
padding: 2px;
|
|
470
475
|
border: 0;
|
|
471
476
|
border-radius: 10px;
|
|
477
|
+
corner-shape: round;
|
|
472
478
|
background: var(--dsw-alias-border-l3);
|
|
473
479
|
cursor: pointer;
|
|
474
480
|
}
|
|
475
|
-
.dsh-rewind-cleanup-switch-
|
|
481
|
+
.dsh-rewind-cleanup-switch[aria-checked='true'] {
|
|
476
482
|
background: var(--dsw-alias-brand-primary);
|
|
477
483
|
}
|
|
478
484
|
.dsh-rewind-cleanup-switch:disabled {
|
|
@@ -492,7 +498,7 @@ var STYLE = `
|
|
|
492
498
|
background: var(--dsw-alias-label-primary-foreground);
|
|
493
499
|
transition: transform 120ms ease;
|
|
494
500
|
}
|
|
495
|
-
.dsh-rewind-cleanup-switch-
|
|
501
|
+
.dsh-rewind-cleanup-switch[aria-checked='true'] .dsh-rewind-cleanup-thumb {
|
|
496
502
|
transform: translateX(16px);
|
|
497
503
|
}
|
|
498
504
|
.dsh-rewind-cleanup-input {
|
|
@@ -1333,8 +1339,8 @@ function createRewindBridge(deps) {
|
|
|
1333
1339
|
}
|
|
1334
1340
|
|
|
1335
1341
|
// src/client/build-info.ts
|
|
1336
|
-
var PLUGIN_VERSION = true ? "0.
|
|
1337
|
-
var BUILD_HASH = true ? "
|
|
1342
|
+
var PLUGIN_VERSION = true ? "0.10.0-alpha.2" : "dev";
|
|
1343
|
+
var BUILD_HASH = true ? "cc039b5d" : "dev";
|
|
1338
1344
|
|
|
1339
1345
|
// src/client/locales.ts
|
|
1340
1346
|
var zh = {
|
|
@@ -1531,7 +1537,7 @@ function SettingsCleanupCard({ api, t }) {
|
|
|
1531
1537
|
{
|
|
1532
1538
|
type: "button",
|
|
1533
1539
|
role: "switch",
|
|
1534
|
-
className:
|
|
1540
|
+
className: "dsh-rewind-cleanup-switch",
|
|
1535
1541
|
"aria-checked": draft.enabled,
|
|
1536
1542
|
"aria-labelledby": "dsh-rewind-cleanup-enabled-label",
|
|
1537
1543
|
disabled,
|