dsh-rewind-plugin 0.4.1 → 0.5.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/CONTRIBUTING.md +72 -0
- package/README.en.md +174 -0
- package/README.md +105 -101
- package/SECURITY.md +154 -0
- package/docs/README.md +29 -0
- package/docs/architecture.md +128 -0
- package/docs/compat/tracking-boundary.md +103 -0
- package/docs/compat/tracking-boundary.zh.md +55 -0
- package/docs/compat/troubleshooting.md +41 -0
- package/docs/{troubleshooting.zh.md → compat/troubleshooting.zh.md} +15 -0
- package/docs/format.md +150 -0
- package/docs/harness-reference.md +7 -3
- package/docs/release/release.md +69 -0
- package/docs/{release.md → release/release.zh.md} +6 -52
- package/docs/snapshot-auto-cleanup.md +71 -0
- package/docs/snapshot-auto-cleanup.zh.md +42 -0
- package/lib/client.js +1 -1
- package/lib/index.js +982 -50
- package/lib/types/client/candidates.d.ts +5 -4
- package/lib/types/client/index.d.ts +1 -1
- package/lib/types/index.d.ts +3 -1
- package/lib/types/locales.d.ts +17 -0
- package/lib/types/rewind.d.ts +3 -2
- package/lib/types/snapshot-cleanup.d.ts +125 -0
- package/lib/types/snapshot.d.ts +374 -14
- package/package.json +6 -3
- package/README.zh.md +0 -159
- package/docs/troubleshooting.md +0 -44
- /package/docs/{compat-audit.md → compat/audit.md} +0 -0
- /package/docs/{client-contract.md → contract/client-contract.md} +0 -0
- /package/docs/{client-contract.zh.md → contract/client-contract.zh.md} +0 -0
package/CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
Thanks for considering a contribution to dsh-rewind. This file is short on
|
|
4
|
+
purpose: the authoritative spec for how the repo works is `AGENTS.md` (read it
|
|
5
|
+
first), and the docs live under `docs/` with an index in `docs/README.md`.
|
|
6
|
+
|
|
7
|
+
## Project principles
|
|
8
|
+
|
|
9
|
+
- **Focused on purpose** — one thing: in-window rewind to any earlier user
|
|
10
|
+
message, never forking a session.
|
|
11
|
+
- **Security first** — session logs are append-only; file restores stay inside
|
|
12
|
+
the plugin's own backup directory. See `SECURITY.md`.
|
|
13
|
+
- **Minimal** — avoid over-abstraction; keep the plugin light and maintainable.
|
|
14
|
+
|
|
15
|
+
## Prerequisites
|
|
16
|
+
|
|
17
|
+
- Node `^22.19.0 || >=24.0.0` (see `engines` in `package.json`), npm.
|
|
18
|
+
|
|
19
|
+
## Setup and commands
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
npm install # devDeps from the npm registry
|
|
23
|
+
npm run build # esbuild → lib/ (host ESM + client closure + types)
|
|
24
|
+
npm run check # one-shot full gate: typecheck + test + build + verify:host + pack --dry-run
|
|
25
|
+
npm run typecheck # tsc --noEmit (host / client / client-test)
|
|
26
|
+
npm test # vitest: unit + compatibility suites
|
|
27
|
+
npm run verify:host # end-to-end host verification (full check suite)
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
`prepare` runs the build, so `npm pack` / `npm publish` always carry a fresh
|
|
31
|
+
`lib/` and `LICENSE`.
|
|
32
|
+
|
|
33
|
+
## Before you open a PR
|
|
34
|
+
|
|
35
|
+
- **Every change must pass** `npm run check`.
|
|
36
|
+
- Commit messages use conventional commits with **English** subjects
|
|
37
|
+
(`feat` / `fix` / `docs` / `test` / `refactor` / `chore` / …). Code comments
|
|
38
|
+
are written in English.
|
|
39
|
+
- Keep `rewind.ts` pure and `snapshot.ts` host-independent — if a change needs
|
|
40
|
+
I/O or harness types in the planning layer, that is a design smell.
|
|
41
|
+
|
|
42
|
+
## Documentation rules
|
|
43
|
+
|
|
44
|
+
- New/changed behavior that is durable (formats, contracts, compatibility
|
|
45
|
+
findings) must update the relevant doc in the same PR:
|
|
46
|
+
- **`docs/format.md`** — any change to the on-disk format (bump version,
|
|
47
|
+
migrate, or move the state root; no silent re-interpretation).
|
|
48
|
+
- **`docs/contract/client-contract.md`** — any change to the meaning of
|
|
49
|
+
`@<seq>` / `sourceEventSeq` / `data-dsh-rewind-hidden`; breaking a listed
|
|
50
|
+
stability tier is a minor/major version bump.
|
|
51
|
+
- **`docs/compat/audit.md`** — new compatibility findings (it is the single
|
|
52
|
+
source of truth; other docs link to it instead of restating).
|
|
53
|
+
- **`SECURITY.md`** — any change to the security model (trust boundary,
|
|
54
|
+
mutation gates, containment, crash handling).
|
|
55
|
+
- Bilingual docs use the `.md` / `.zh.md` file split; keep the two mirrors in
|
|
56
|
+
sync.
|
|
57
|
+
|
|
58
|
+
## Testing expectations
|
|
59
|
+
|
|
60
|
+
- Pure planning (`rewind.ts`, `hidden.ts`) → unit tests in `tests/`.
|
|
61
|
+
- Store behavior (`snapshot.ts`) → `tests/snapshot.test.ts`, plus crash-safety
|
|
62
|
+
scenarios in `tests/crash-safety.test.ts` via the test-only `crash` seam
|
|
63
|
+
(`RestoreRunOptions.crash`).
|
|
64
|
+
- Harness interaction → the compatibility suites
|
|
65
|
+
(`compat-invariants` / `compat-interop` / `compat-gaps`) and
|
|
66
|
+
`scripts/verify-host.mjs`.
|
|
67
|
+
|
|
68
|
+
## Releasing
|
|
69
|
+
|
|
70
|
+
Releases are CI-driven via GitHub Actions Trusted Publishing (OIDC, no stored
|
|
71
|
+
token): push a `v<version>` tag and CI publishes with Sigstore provenance. Full
|
|
72
|
+
details: `docs/release/release.md`.
|
package/README.en.md
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# dsh-rewind
|
|
2
|
+
|
|
3
|
+
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).
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/dsh-rewind-plugin)
|
|
6
|
+
[](https://www.npmjs.com/package/dsh-rewind-plugin)
|
|
7
|
+
[](https://github.com/SiriLee/dsh-rewind/actions/workflows/ci.yml)
|
|
8
|
+
|
|
9
|
+
> English | [中文](README.md)
|
|
10
|
+
|
|
11
|
+
A deliberately focused plugin with one job: **rewind to any user message, no matter how far back, in place** — and conveniently **restore the files it changed** along the way.
|
|
12
|
+
|
|
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
|
+
- **Lightweight workspace backup** — Claude Code-aligned behavior: only file-writing tools are tracked, a lightweight before-backup is **persisted on disk**, and your git repository is never touched or relied on. 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).
|
|
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
|
+
|
|
18
|
+
## Preview
|
|
19
|
+
|
|
20
|
+
Every user message carries a **↶ rewind** button in its action row. Clicking it opens a mode-selection popover — "**rewind conversation only**" or "**rewind conversation and code**", the latter showing the file-change list for confirmation first. You can also rewind conveniently via the **`/rewind` command** or a **keyboard shortcut**.
|
|
21
|
+
|
|
22
|
+
<table>
|
|
23
|
+
<tr>
|
|
24
|
+
<td align="center"><img src="assets/screenshots/rewind-button.png" width="440" alt="Per-message ↶ rewind button"><br><sub>Per-message ↶ rewind button</sub></td>
|
|
25
|
+
<td align="center"><img src="assets/screenshots/mode-popover.png" width="440" alt="Mode-selection popover"><br><sub>Mode-selection popover</sub></td>
|
|
26
|
+
</tr>
|
|
27
|
+
<tr>
|
|
28
|
+
<td align="center"><img src="assets/screenshots/impact-list.png" width="440" alt="Impact list"><br><sub>"Conversation and code" impact list</sub></td>
|
|
29
|
+
<td align="center"><img src="assets/screenshots/rewind-candidates.png" width="440" alt="/rewind candidate picker"><br><sub>/rewind candidate picker</sub></td>
|
|
30
|
+
</tr>
|
|
31
|
+
</table>
|
|
32
|
+
|
|
33
|
+
## Install
|
|
34
|
+
|
|
35
|
+
```sh
|
|
36
|
+
dsh plugin --profile web add dsh-rewind-plugin
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
> ⚠️ The npm name `dsh-rewind` belongs to another author's package — install with `dsh-rewind-plugin`.
|
|
40
|
+
|
|
41
|
+
## Usage
|
|
42
|
+
|
|
43
|
+
1. Find the user message you want to rewind to in the conversation, or type `/rewind` to open the candidate picker.
|
|
44
|
+
2. **Select it.** A small popover offers the two modes ("conversation and code" is only shown when there are restorable changes after the target).
|
|
45
|
+
3. The rewind takes effect immediately: the conversation returns to how it looked at the target message, and the withdrawn message's text is filled back into the composer — edit and re-send.
|
|
46
|
+
|
|
47
|
+
**Keyboard**: both the candidate picker and the mode popover support ↑↓ to move, Enter to confirm, Esc to cancel/back.
|
|
48
|
+
|
|
49
|
+
<details>
|
|
50
|
+
<summary><b>Edge notes</b></summary>
|
|
51
|
+
|
|
52
|
+
- Rewinds can be repeated — with no limit on stage or count.
|
|
53
|
+
- A rewind itself **cannot be undone**, but the withdrawn content stays in the session log and can be recovered by manually editing it.
|
|
54
|
+
- **Interruptions rewind too** — a `steering` interruption message the model hasn't read yet is also a valid rewind target.
|
|
55
|
+
- **A rewind interrupts the running turn** — to execute the rewind safely.
|
|
56
|
+
|
|
57
|
+
</details>
|
|
58
|
+
|
|
59
|
+
## Why it stands out
|
|
60
|
+
|
|
61
|
+
Compared with the common approaches, here is the trade-off this plugin makes on "rewind":
|
|
62
|
+
|
|
63
|
+
| Dimension | Common approach | This plugin |
|
|
64
|
+
| --- | --- | --- |
|
|
65
|
+
| Conversation rewind | Fork / branch a new conversation | **In-place rewind** — no new session, no window switch |
|
|
66
|
+
| File restore | No restore feature / git-managed or whole-tree snapshot | **Lightweight before-backups** — auto-captured before writes, one-click restore (aligned with Claude Code) |
|
|
67
|
+
| Dependencies | Often needs a Git repo or a full snapshot engine | **None** — no git required, works on any directory |
|
|
68
|
+
| Storage footprint | Whole-tree snapshots take space | **Lightweight** — only files touched by write tools are stored, persisted on disk |
|
|
69
|
+
|
|
70
|
+
## How it works
|
|
71
|
+
|
|
72
|
+
The whole design rests on two principles, simple but deliberate: **the conversation half "masks, never deletes", and the file half "backs up before the write, reconciles against the real disk before restoring."** It shares lineage with Claude Code's checkpointing — Claude Code's file history is also per-file records plus a re-scan of tracked files at every message, not a whole-tree snapshot. This plugin brings the same semantics to dsh, and makes them lighter and more robust.
|
|
73
|
+
|
|
74
|
+
### 1. Conversation rewind: a single "mask", not a delete
|
|
75
|
+
|
|
76
|
+
`append-only` is a hard rule: the session log only grows and is never rewritten — the foundation of auditability and privacy. A rewind never touches history; it makes a single move: append **one empty-content message marker** to the end of the log and use it to "mask out" everything after the target message, so the model and the UI see only the part before it.
|
|
77
|
+
|
|
78
|
+
- The marker is **empty** — it never enters the model context and never renders as conversation content; what you and the model see is exactly how the conversation looked at the target. True "in place".
|
|
79
|
+
- Because this is **masking, not deleting**, every withdrawn event stays in the log — auditable, traceable, and in principle manually recoverable.
|
|
80
|
+
- The marker is deeply **aware of dsh internals**: it reuses the **last-started turn** number (never "last turn + 1") and carries its own **ghost step frame**. So the harness's own log replay, `/compact`, and resume preflight all recognize it and never mistake it for a real message.
|
|
81
|
+
|
|
82
|
+
> **Design highlight**: the entire conversation rewind is **a single append**. It's deterministic, auditable, and — because the log was never broken — a "clean" time-travel. Minimal action, complete semantics. The compatibility subtleties with the harness (ghost step frame, reused turn number) are where this plugin is genuinely professional — each is pinned by a dedicated probe test.
|
|
83
|
+
|
|
84
|
+
### 2. File restore: lightweight checkpointing, "back up before the change"
|
|
85
|
+
|
|
86
|
+
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:
|
|
87
|
+
|
|
88
|
+
- **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.
|
|
89
|
+
- **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".
|
|
90
|
+
- **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".
|
|
91
|
+
- **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.
|
|
92
|
+
|
|
93
|
+
> **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.
|
|
94
|
+
|
|
95
|
+
### Design highlights
|
|
96
|
+
|
|
97
|
+
| Design | Why it matters |
|
|
98
|
+
| --- | --- |
|
|
99
|
+
| A single append is a whole rewind | Minimal action, maximal semantics; the log is never mutated |
|
|
100
|
+
| Mask, never delete | History is always auditable and in principle recoverable |
|
|
101
|
+
| Before-backup, grouped by turn, persisted on disk | Space-efficient, survives restarts, Claude Code-aligned |
|
|
102
|
+
| Identical content stored as a link (dedup) | Hundreds of repeated writes cost almost nothing; links are materialized before their group is evicted, never left dangling |
|
|
103
|
+
| Session-level auto-cleanup | Removes only long-inactive sessions' snapshots; the active session and the chat log are never touched |
|
|
104
|
+
| Reconcile against the real disk before restoring | Idempotent, zero side effects, no collateral damage |
|
|
105
|
+
| Ghost step frame + reused turn number | Deeply compatible with the host, pinned by probe tests |
|
|
106
|
+
| Crash safety (atomic writes + restore journal) | Continue or roll back cleanly after a crash |
|
|
107
|
+
| Pure-function planning + probed store | Fully unit-testable without a host; test-driven |
|
|
108
|
+
|
|
109
|
+
## What it deliberately does NOT do
|
|
110
|
+
|
|
111
|
+
This plugin deliberately stays lightweight and focused on one thing — "conversation rewind". The following are **out of its scope**:
|
|
112
|
+
|
|
113
|
+
- **Whole-tree / Git-level snapshots** — only write-class tool edits plus external changes to already-tracked files are backed up; files never touched by a tool are not restored. For whole-worktree snapshot rollback, use a dedicated snapshot tool (or your git).
|
|
114
|
+
- **Subagent edits** — not tracked (same as Claude Code): a subagent runs its own session, so its backups could never be restored by a rewind of the parent session.
|
|
115
|
+
- **Fork / branch rewind** — the harness already provides this ("branch in new chat"); no need to reinvent it.
|
|
116
|
+
|
|
117
|
+
## Compatibility
|
|
118
|
+
|
|
119
|
+
- Node.js `^22.19.0 || >=24.0.0`.
|
|
120
|
+
- DeepSeek Harness web profile (`dsh --profile web`); peer `@deepseek-ai/*` packages are resolved by the harness at runtime.
|
|
121
|
+
|
|
122
|
+
> [!WARNING]
|
|
123
|
+
> This project and DeepSeek Harness are both in developer preview. Pin exact
|
|
124
|
+
> versions in reproducible environments and review the behavior notes above.
|
|
125
|
+
|
|
126
|
+
## Client contract
|
|
127
|
+
|
|
128
|
+
Third-party DOM plugins that need to know which transcript rows a rewind
|
|
129
|
+
withdrew should consume the stable, locale-independent helpers exported from
|
|
130
|
+
`dsh-rewind-plugin/client` — never parse
|
|
131
|
+
`outcome.text`. The `data-dsh-rewind-hidden` attribute marks withdrawn rows
|
|
132
|
+
(observational only). Details: [docs/contract/client-contract.md](docs/contract/client-contract.md).
|
|
133
|
+
|
|
134
|
+
## Known issues
|
|
135
|
+
|
|
136
|
+
1. **Exported logs are complete** — a rewind only removes messages from the model context and the view; the exported session log (`/export`) still contains **withdrawn messages**. This plugin cannot alter exports.
|
|
137
|
+
2. **Lightweight file rewind has a cost** — in specific cases not all changes can be rewound. Consistent with Claude Code. See: [File-rewind tracking boundary](docs/compat/tracking-boundary.md).
|
|
138
|
+
3. **Rewinds from `≤ v0.2.4`** — sessions rewound with these versions may **fail to load history** after more conversation. Install a v0.3.3-or-earlier release and use its bundled repair tool ([docs/compat/troubleshooting.md](docs/compat/troubleshooting.md)).
|
|
139
|
+
4. **Rewinds from `≤ v0.3.3`** — compaction (`/compact`) is unavailable for those sessions. Newer versions are compatible; for affected old sessions, start a new session.
|
|
140
|
+
|
|
141
|
+
## Security
|
|
142
|
+
|
|
143
|
+
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/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. Full security model: [SECURITY.md](SECURITY.md).
|
|
144
|
+
|
|
145
|
+
## Development
|
|
146
|
+
|
|
147
|
+
```sh
|
|
148
|
+
npm install # devDeps from the npm registry
|
|
149
|
+
npm run check # one-shot full gate: typecheck + test + build + verify:host + pack --dry-run
|
|
150
|
+
npm run typecheck # tsc on all three surfaces (host + client + client-test)
|
|
151
|
+
npm test # vitest: all unit and compatibility suites
|
|
152
|
+
npm run build # esbuild: lib/index.js (host ESM) + lib/client.js (loader closure) + .d.ts
|
|
153
|
+
node scripts/verify-host.mjs # end-to-end verification of the built artifact
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
`prepare` runs the full build, so git installs and `npm pack` / `npm publish` always produce a complete `lib/` and the `LICENSE`.
|
|
157
|
+
|
|
158
|
+
Maintainers: the module map and harness interface reference live in [docs/harness-reference.md](docs/harness-reference.md).
|
|
159
|
+
|
|
160
|
+
Contributing guide: [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
161
|
+
|
|
162
|
+
## Release
|
|
163
|
+
|
|
164
|
+
Releases go out through GitHub Actions Trusted Publishing (OIDC, no stored `NPM_TOKEN`): push a `v<version>` tag and CI publishes with Sigstore provenance.
|
|
165
|
+
|
|
166
|
+
```sh
|
|
167
|
+
npm version patch && git push origin main --tags
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
One-time npm-side setup and the full workflow details: [docs/release/release.md](docs/release/release.md).
|
|
171
|
+
|
|
172
|
+
## License
|
|
173
|
+
|
|
174
|
+
[MIT](LICENSE)
|
package/README.md
CHANGED
|
@@ -1,173 +1,177 @@
|
|
|
1
1
|
# dsh-rewind
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
DeepSeek Harness 插件:**一键就地回退对话到任意更早的用户消息**——同窗口内完成,不新建分支、不换窗口,可一并还原工作区文件(完整 Claude Code `/rewind` 语义)。
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/dsh-rewind-plugin)
|
|
6
|
-
[](https://www.npmjs.com/package/dsh-rewind-plugin)
|
|
7
|
+
[](https://github.com/SiriLee/dsh-rewind/actions/workflows/ci.yml)
|
|
7
8
|
|
|
8
|
-
> English
|
|
9
|
+
> [English](README.en.md) | 中文
|
|
9
10
|
|
|
10
|
-
|
|
11
|
+
刻意聚焦、保持极简,只做一件事:**就地回退到任意远的用户消息**,还能**顺手还原改过的文件**。
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
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 — no new session, no window switch — and the target's text is offered back in the composer so you can edit and re-send it.
|
|
18
|
-
|
|
19
|
-
The plugin never rewrites the append-only session log and never touches your git repository.
|
|
13
|
+
- **回退 = 时间回溯**——目标消息及其之后的全部内容(agent 回复、工具调用)同时从**模型上下文**和**渲染对话**中撤回,不新建会话、不切换窗口;目标消息文本会回填输入框,改完可重发。**在原理上就真正无感、便捷**。
|
|
14
|
+
- **轻量工作区备份**——行为对齐 Claude Code:只跟踪写文件的工具,写前做轻量备份并**落盘持久化**,不依赖、也不触碰 git 仓库。一个轻型插件,即拥有**完备的智能体回退能力**。
|
|
15
|
+
- **信息安全优先**——插件从不删改会话日志(append-only),从不真正删除你的任何对话;文件还原限定在插件自己的备份目录。完整安全模型:[SECURITY.md](SECURITY.md)。
|
|
16
|
+
- **完备测试系统**——单元、探针、端到端主机验证,覆盖兼容性探测、日志重放、续接、跨重启等场景;随 harness 升级持续维护,确保功能稳定。
|
|
20
17
|
|
|
21
|
-
##
|
|
18
|
+
## 效果预览
|
|
22
19
|
|
|
23
|
-
|
|
20
|
+
每条用户消息的操作行都有一个 **↶ 回退** 按钮。点击后弹出模式选择浮层——「**仅回退对话**」或「**回退对话和代码**」,后者会先展示文件变更清单再确认。还可以通过 **`/rewind` 命令**和**快捷键**便捷地选择和回退。
|
|
24
21
|
|
|
25
22
|
<table>
|
|
26
23
|
<tr>
|
|
27
|
-
<td align="center"><img src="assets/screenshots/rewind-button.png" width="440" alt="
|
|
28
|
-
<td align="center"><img src="assets/screenshots/mode-popover.png" width="440" alt="
|
|
24
|
+
<td align="center"><img src="assets/screenshots/rewind-button.png" width="440" alt="用户消息旁的 ↶ 回退按钮"><br><sub>用户消息旁的 ↶ 回退按钮</sub></td>
|
|
25
|
+
<td align="center"><img src="assets/screenshots/mode-popover.png" width="440" alt="模式选择浮层"><br><sub>模式选择浮层</sub></td>
|
|
29
26
|
</tr>
|
|
30
27
|
<tr>
|
|
31
|
-
<td align="center"><img src="assets/screenshots/impact-list.png" width="440" alt="
|
|
32
|
-
<td align="center"><img src="assets/screenshots/rewind-candidates.png" width="440" alt="/rewind
|
|
28
|
+
<td align="center"><img src="assets/screenshots/impact-list.png" width="440" alt="影响清单"><br><sub>「回退对话和代码」影响清单</sub></td>
|
|
29
|
+
<td align="center"><img src="assets/screenshots/rewind-candidates.png" width="440" alt="/rewind 候选面板"><br><sub>/rewind 候选面板</sub></td>
|
|
33
30
|
</tr>
|
|
34
31
|
</table>
|
|
35
32
|
|
|
36
|
-
##
|
|
33
|
+
## 安装
|
|
37
34
|
|
|
38
35
|
```sh
|
|
39
36
|
dsh plugin --profile web add dsh-rewind-plugin
|
|
40
37
|
```
|
|
41
38
|
|
|
42
|
-
|
|
39
|
+
> ⚠️ npm 上的 `dsh-rewind` 属于其他作者,请用 `dsh-rewind-plugin` 安装。
|
|
43
40
|
|
|
44
|
-
|
|
41
|
+
## 使用
|
|
45
42
|
|
|
46
|
-
|
|
43
|
+
1. 在对话中找到要回退的那条用户消息,或输入 `/rewind` 打开候选列表选择。
|
|
44
|
+
2. **选中它。** 小浮层提供两种模式(「回退对话和代码」仅在目标之后有可还原的变更时显示)。
|
|
45
|
+
3. 回退立即生效:对话回到目标消息当时的样子,被撤回消息的文本自动填入输入框——改完直接重发。
|
|
47
46
|
|
|
48
|
-
|
|
47
|
+
**键盘操作**:候选列表与模式浮层均支持 ↑↓ 移动、Enter 确认、Esc 取消/返回。
|
|
49
48
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
3. The rewind executes as an in-session command; a result message confirms, and the withdrawn message's text is filled back into the composer for editing and re-sending.
|
|
49
|
+
<details>
|
|
50
|
+
<summary><b>边界说明</b></summary>
|
|
53
51
|
|
|
54
|
-
|
|
52
|
+
- 回退可以反复进行——没有阶段或次数限制。
|
|
53
|
+
- 回退本身**无法撤销**,但被撤回的内容仍保留在会话日志中,可手动编辑日志恢复。
|
|
54
|
+
- **插话也能回退**——模型尚未读取的 `steering` 插话消息,同样可作为回退目标。
|
|
55
|
+
- **回退会打断当前正在运行的回合**——确保回退安全执行。
|
|
55
56
|
|
|
56
|
-
|
|
57
|
+
</details>
|
|
57
58
|
|
|
58
|
-
|
|
59
|
+
## 存储管理
|
|
59
60
|
|
|
60
|
-
|
|
61
|
+
快照(写前备份)存储于 `~/.dsh/rewind-snapshots/`。插件对**同一会话**的快照做内容去重(内容未变则存为链接)并保留最近 100 组锚点;**手动删除该目录**仅清除文件备份(对话回退不受影响),插件会自动重建。
|
|
61
62
|
|
|
62
|
-
|
|
63
|
+
另提供**全局自动清理**(默认关闭):把**长期不活跃**的会话快照整目录移除,不影响活动会话与对话日志。用 `/snapshot-auto-cleanup` 命令**查看与设置**,配置写入 `~/.dsh/snapshot-cleanup.json`。详见:[快照自动清理](docs/snapshot-auto-cleanup.zh.md)。
|
|
63
64
|
|
|
64
|
-
|
|
65
|
+
## 本插件的优势
|
|
65
66
|
|
|
66
|
-
|
|
67
|
-
- Because the marker is **empty**, the harness derives it to `null` — it never enters the model context and never renders as conversation content. Agent and user both see the conversation exactly as it was at the target.
|
|
68
|
-
- The marker's **turn number reuses the last started turn** (`markerTurnOf`), never `lastTurn + 1`: the harness numbers its next real turn exactly `last turn/start + 1`, so a `maxTurn + 1` marker would sit *before* that `turn/start` — the client conversation builder rejects the ordering (`…turn-tail… received an update before its start Match`) and history load fails. Reusing an already-consumed turn makes the marker a harmless trailing update on the previous completed turn's tail — it can never collide with a future turn.
|
|
69
|
-
- The marker rides a **ghost step frame** — its own `step/start` … `step/end` with a fresh step number (`markerStepOf`) — because the harness token-meter requires every `assistant/message` to sit inside an open step of the same turn/step; a bare idle-time marker would fail its replay and break `/compact` for the session.
|
|
70
|
-
- The append-only log is **untouched** — every withdrawn event stays in the audit trail; only the model-visible surface is cut, so the next request derives its context from the target onward.
|
|
67
|
+
和常见的几种做法相比,本插件在"回退"这件事上的取舍:
|
|
71
68
|
|
|
72
|
-
|
|
69
|
+
| 维度 | 常见做法 | 本插件 |
|
|
70
|
+
| --- | --- | --- |
|
|
71
|
+
| 对话回退 | Fork 分支新建对话 | **就地回退**——不新建会话、不切窗口,便捷回退 |
|
|
72
|
+
| 文件还原 | 无还原功能 / git 管理或完整快照 | **写前轻量备份**——写文件前自动存原内容,一键还原(对齐 Claude Code) |
|
|
73
|
+
| 依赖 | 常依赖 Git 仓库或完整快照引擎 | **无依赖**——不依赖 git,普通目录即可用 |
|
|
74
|
+
| 存储开销 | 整树快照占空间大 | **轻量**——只存被写工具改动过的文件,落盘持久化 |
|
|
73
75
|
|
|
74
|
-
|
|
76
|
+
## 原理
|
|
75
77
|
|
|
76
|
-
|
|
78
|
+
整套设计只有两条主线,核心哲学朴素却克制:**对话部分「只遮蔽、不删除」,文件部分「改前先备份,还原时对照真实磁盘」**。机制与 Claude Code 的 checkpointing 同源——Claude Code 的文件历史也是逐文件记录 + 每条消息重扫已跟踪文件,并非整树快照;本插件把同一套语义落在 dsh 上,并做得更轻、更稳。
|
|
77
79
|
|
|
78
|
-
1.
|
|
79
|
-
2. **Disk commit** at `tools/post-execute`: the before-backup is written under the turn's anchor message seq (`~/.dsh/rewind-snapshots/<session>/<anchor seq>/<callId>.json`).
|
|
80
|
-
3. **Restore** (`/rewind @<seq> both`): every backup anchored at or after the target applies once reconciled with the current disk — modified files are written back to their **earliest** captured before-state, files created after the target are deleted, files already matching the target state are left untouched (idempotent). Symbolic / hard links are skipped (they share an inode with another name; restoring through one would clobber both). Writes go through plain `node:fs`, independent of the fs service — under sandbox / remote backends, path resolution may be restricted.
|
|
81
|
-
4. A tool body that **throws** skips `tools/post-execute`; a `tools/result` safety net clears the pending capture so nothing leaks in memory.
|
|
80
|
+
### 1. 对话回退:一次「遮蔽」,而不是「删除」
|
|
82
81
|
|
|
83
|
-
|
|
82
|
+
`append-only` 是铁律:会话日志只追加、从不改写——这是可审计与信息安全的地基。回退从不动历史,它只做一步:往日志末尾追加**一条内容为空的消息标记**,用它把目标消息之后的全部内容「遮蔽」掉,让模型和界面都只看得到目标之前的部分。
|
|
84
83
|
|
|
85
|
-
|
|
84
|
+
- 标记本身是**空的**——不进入模型上下文、不渲染成任何对话内容,模型和你看到的对话就是目标消息当时的样子,真正的「就地」;
|
|
85
|
+
- 因为是「遮蔽」而非「删除」,**被撤回的每一条事件都完整留在日志里**,可审计、可追溯,原则上也随时能手动恢复;
|
|
86
|
+
- 标记非常「懂」dsh——它复用**最后一个已开始的回合**的编号(而不是「最后回合 + 1」),并自带一个独立的**幽灵步骤框架**。于是 harness 自己的日志重放、`/compact` 压缩、续接检查都能正确识别它,绝不会把它误认为真实对话。
|
|
86
87
|
|
|
87
|
-
|
|
88
|
-
- **Subagent edits** — not tracked (same as Claude Code): a subagent runs its own session, so its backups could never be restored by a rewind of the parent session.
|
|
89
|
-
- **Fork / branch rewind and `/compact`** — the harness already provides these ("branch in new chat", compact).
|
|
88
|
+
> **设计点睛**:整个对话回退就是**一条**追加。它确定、可审计,且因为日志从未被破坏,回溯是「干净的」——用最小的动作,实现最完整的语义。那些与 harness 内部的兼容细节(幽灵步骤框架、复用回合号)正是插件的专业所在,每一条都由专门的探针测试固化。
|
|
90
89
|
|
|
91
|
-
|
|
90
|
+
### 2. 文件还原:轻量检查点,「改前备份」
|
|
92
91
|
|
|
93
|
-
|
|
92
|
+
文件部分对齐 Claude Code 的检查点语义——**逐文件、写前备份 + 每条消息重扫已跟踪文件**,而不是整树快照。这项取舍既省空间,又更完整:
|
|
94
93
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
| Tracked-change scope | Only write-class tool edits (like Claude Code) | Any Git-managed file (Git worktree required) |
|
|
100
|
-
| Public service API | No — a focused single-purpose plugin | Yes — `ctx.changeLedger` service + `/turn-rewind` HTTP endpoint |
|
|
94
|
+
- **改前备份**:追踪写类工具(`write`、`edit`、`str_replace_editor`),在**每次写文件之前**先把原内容存下来。关键在时机——在审批门放行之后捕获:审批短路不会漏备份,被拒绝的调用也不会留下记录;读取失败只警告、从不阻塞写操作。备份按对话轮次分组锚定,**落盘持久化**,重启也还在。
|
|
95
|
+
- **外部变更也追**:每条用户消息边界,插件重新检查所有已跟踪文件——那些从没经过写工具、被外部改过或删掉的文件,同样被记录,回退时一并还原。这让「轻量」却不「残缺」。
|
|
96
|
+
- **还原前对照真实磁盘**:这是最值得说的一点。回退时插件实时读取文件当前内容,与目标状态逐一比对——**只操作真正不一致的文件**:改过的写回最早备份、目标之后新建的删除、已经一致的跳过。重复回退因此**零副作用、幂等**,绝不会出现「幽灵影响」。
|
|
97
|
+
- **安全边界**:符号/硬链接跳过,避免透过一个还原误伤另一个名字;路径经安全化处理,**绝不越出备份根目录**;单个文件失败绝不中止整轮还原。
|
|
101
98
|
|
|
102
|
-
|
|
99
|
+
> **设计点睛**:**「对照真实磁盘再动手」** 是这套检查点里最有洞察力的决定——它从不盲目假设,而是以磁盘为准,该做的做、不该做的跳过。
|
|
103
100
|
|
|
104
|
-
|
|
101
|
+
### 设计亮点一览
|
|
105
102
|
|
|
106
|
-
|
|
107
|
-
|
|
103
|
+
| 设计 | 为什么值得 |
|
|
104
|
+
| --- | --- |
|
|
105
|
+
| 一次追加即一次回退 | 极小动作、极大语义,且日志从不被破坏 |
|
|
106
|
+
| 只遮蔽、不删除 | 历史永远可审计,原则上可恢复 |
|
|
107
|
+
| 改前备份 + 按轮分组 + 落盘 | 省空间、跨重启、对齐 Claude Code |
|
|
108
|
+
| 同内容存为链接(去重) | 上百次重复写入几乎不占空间;淘汰组前先落地链接,绝不悬空 |
|
|
109
|
+
| 会话级自动清理 | 只移除长期不活跃会话的快照,活动会话与对话日志永不触及 |
|
|
110
|
+
| 对照真实磁盘再还原 | 幂等、零副作用、不误伤 |
|
|
111
|
+
| 幽灵步骤框架 + 复用回合号 | 与宿主深度兼容,且被探针测试固化 |
|
|
112
|
+
| 崩溃安全(原子写 + 还原日志) | 断电/崩溃后仍可续做或回滚 |
|
|
113
|
+
| 纯函数规划 + 注入探针的存储 | 无需宿主即可单测,测试驱动 |
|
|
108
114
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
115
|
+
## 明确不做的事
|
|
116
|
+
|
|
117
|
+
本插件刻意保持轻量、聚焦"对话回退"这一件事,以下场景**不属于它的职责**:
|
|
118
|
+
|
|
119
|
+
- **整树 / Git 级快照**——只跟踪写类工具编辑 + 已跟踪文件的外部改动,从未被工具碰过的文件不还原。需要 Git 工作树级的完整快照回退时,请交给专门的快照工具(或你的 git)。
|
|
120
|
+
- **子代理的编辑**——不追踪(同 Claude Code):子代理运行在自己的会话里,其备份无法由父会话的回退还原,只会在磁盘上残留。
|
|
121
|
+
- **fork / 分支回退**——harness 已内置「在新对话中分支」,不重复造轮子。
|
|
112
122
|
|
|
113
|
-
##
|
|
123
|
+
## 兼容性
|
|
114
124
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
125
|
+
- Node.js `^22.19.0 || >=24.0.0`。
|
|
126
|
+
- DeepSeek Harness web 配置档(`dsh --profile web`);peer `@deepseek-ai/*` 包由 harness 运行时解析。
|
|
127
|
+
|
|
128
|
+
> [!WARNING]
|
|
129
|
+
> 本项目与 DeepSeek Harness 均处于开发者预览阶段。可复现环境请 pin 精确版本,
|
|
130
|
+
> 并阅读上述行为说明。
|
|
120
131
|
|
|
121
|
-
##
|
|
132
|
+
## 客户端契约
|
|
122
133
|
|
|
123
|
-
|
|
134
|
+
需要获知哪些转录行被回退撤回的第三方 DOM 插件,应使用 `dsh-rewind-plugin/client` 导出的稳定、与本地化无关的纯函数,切勿解析 `outcome.text`。`data-dsh-rewind-hidden` 属性标记被撤回的行(仅观测性)。
|
|
135
|
+
详见:[docs/contract/client-contract.zh.md](docs/contract/client-contract.zh.md)。
|
|
124
136
|
|
|
125
|
-
|
|
137
|
+
## 已知问题
|
|
126
138
|
|
|
127
|
-
|
|
139
|
+
1. **导出的日志是完整内容**——回退只是把消息从模型上下文和视图中移除,`/export` 导出的会话日志包含**已撤回的消息**。本插件无法改动导出。
|
|
140
|
+
2. **轻量文件回退存在代价**——特定情况可能无法回退所有修改。行为与 Claude Code 一致。详见:[文件回退的追踪边界](docs/compat/tracking-boundary.zh.md)。
|
|
141
|
+
3. **v0.2.4 及更早版本**回退过的会话,继续对话后可能加载历史失败。可安装 v0.3.3 及之前版本的随附修复工具处理([完整步骤](docs/compat/troubleshooting.zh.md))。
|
|
142
|
+
4. **v0.3.3 及更早版本**回退过的会话,压缩对话(compact)不可用。新版本已兼容;受影响的旧会话建议新建会话。
|
|
128
143
|
|
|
129
|
-
|
|
144
|
+
## 安全
|
|
130
145
|
|
|
131
|
-
|
|
146
|
+
本插件只向会话日志追加回退标记事件,从不删除或改写已记录的历史。工作区文件仅在「回退对话和代码」时被改写,备份与还原都限定在 `~/.dsh/rewind-snapshots/` 内。不触碰你的 git 仓库,无网络请求,不访问任何凭据。对**长期不活跃**的会话,另有默认关闭的全局自动清理可整目录移除其快照,不影响活动会话与对话日志。完整安全模型:[SECURITY.md](SECURITY.md)。
|
|
132
147
|
|
|
133
|
-
##
|
|
148
|
+
## 开发
|
|
134
149
|
|
|
135
150
|
```sh
|
|
136
|
-
npm install # devDeps
|
|
137
|
-
npm run
|
|
138
|
-
npm
|
|
139
|
-
npm
|
|
140
|
-
|
|
151
|
+
npm install # devDeps 来自 npm registry
|
|
152
|
+
npm run check # 一键全检:typecheck + test + build + verify:host + pack --dry-run
|
|
153
|
+
npm run typecheck # tsc 三面编译(host + client + client-test)
|
|
154
|
+
npm test # vitest:全部单元与兼容性测试套件
|
|
155
|
+
npm run build # esbuild:lib/index.js(host ESM)+ lib/client.js(loader 闭包)+ .d.ts
|
|
156
|
+
node scripts/verify-host.mjs # 端到端验证构建产物
|
|
141
157
|
```
|
|
142
158
|
|
|
143
|
-
`npm
|
|
144
|
-
([docs/compat-audit.md](docs/compat-audit.md)): scenario-generated logs drive the
|
|
145
|
-
real harness packages (token-meter, compaction, session-stats/title/goal folds,
|
|
146
|
-
resume preflight) through rewind markers and assert the compatibility
|
|
147
|
-
invariants. A failing probe is a discovered incompatibility, not a mock
|
|
148
|
-
artifact. One finding is recorded: **R-OPENSTEP** — a log carrying an
|
|
149
|
-
unclosed `step/start` (crash leftover) makes any later step activity,
|
|
150
|
-
including a rewind's ghost-step frame, break token-meter replay (and
|
|
151
|
-
/compact). Harness `0.1.1-rc.2` fixes the crash path (`interruptedTurnClosers`
|
|
152
|
-
closes leftover step/turn boundaries on load). A plugin-side guard was tried
|
|
153
|
-
and reverted: it produced false positives on real session logs (rewind
|
|
154
|
-
feature broken), so the plugin deliberately ships no guard — the residual
|
|
155
|
-
risk (runtime-produced unclosed steps) is accepted.
|
|
159
|
+
`prepare` 执行完整构建,所以 git 安装与 `npm pack` / `npm publish` 总会产出完整的 `lib/` 与 `LICENSE`。
|
|
156
160
|
|
|
157
|
-
|
|
161
|
+
维护者:模块地图与 harness 接口参考见 [docs/harness-reference.md](docs/harness-reference.md)
|
|
158
162
|
|
|
159
|
-
|
|
163
|
+
贡献指南:[CONTRIBUTING.md](CONTRIBUTING.md)
|
|
160
164
|
|
|
161
|
-
##
|
|
165
|
+
## 发布
|
|
162
166
|
|
|
163
|
-
|
|
167
|
+
通过 GitHub Actions Trusted Publishing(OIDC,无存储 `NPM_TOKEN`)发布:推送 `v<版本>` tag,CI 即带 Sigstore provenance 发布。
|
|
164
168
|
|
|
165
169
|
```sh
|
|
166
170
|
npm version patch && git push origin main --tags
|
|
167
171
|
```
|
|
168
172
|
|
|
169
|
-
|
|
173
|
+
一次性 npm 侧配置与完整流程:见 [docs/release/release.zh.md](docs/release/release.zh.md)。
|
|
170
174
|
|
|
171
|
-
##
|
|
175
|
+
## 许可
|
|
172
176
|
|
|
173
177
|
[MIT](LICENSE)
|