dsh-rewind-plugin 0.5.0 → 0.6.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/README.en.md +18 -3
- package/README.md +4 -4
- package/SECURITY.md +9 -3
- package/docs/README.md +1 -0
- package/docs/compat/audit.md +152 -124
- package/docs/format.md +5 -4
- package/docs/harness-reference.md +1 -1
- package/docs/release/release.md +8 -3
- package/docs/release/release.zh.md +4 -2
- package/docs/snapshot-auto-cleanup.md +75 -56
- package/docs/snapshot-auto-cleanup.zh.md +36 -21
- package/lib/client.js +75 -112
- package/lib/index.js +206 -39
- package/lib/types/client/hidden.d.ts +28 -0
- package/lib/types/client/index.d.ts +12 -12
- package/lib/types/client/locales.d.ts +0 -2
- package/lib/types/client/popover.d.ts +9 -2
- package/lib/types/client/portals.d.ts +15 -2
- package/lib/types/index.d.ts +6 -3
- package/lib/types/locales.d.ts +5 -0
- package/lib/types/snapshot-cleanup.d.ts +28 -8
- package/lib/types/snapshot.d.ts +87 -2
- package/package.json +7 -1
package/README.en.md
CHANGED
|
@@ -11,7 +11,7 @@ Conversation rewind for DeepSeek Harness: **rewind the conversation to any earli
|
|
|
11
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
12
|
|
|
13
13
|
- **Rewinding is time-travel** — the target message and everything after it (agent replies, tool calls) are withdrawn from the model context *and* the rendered transcript at once, with no new session and no window switch; the target's text is offered back in the composer so you can edit and re-send it — **truly seamless and convenient by design**.
|
|
14
|
-
- **Lightweight workspace backup** — Claude Code-aligned behavior:
|
|
14
|
+
- **Lightweight workspace backup** — Claude Code-aligned behavior: tracks files edited by the write-class tools, and external changes to **already-tracked** files are restorable too. Selective tracking, before-write backup, store only on change. One lightweight plugin gives you a **complete** agentic rewind capability.
|
|
15
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
16
|
- **A complete test system** — unit, probe, and end-to-end host verification, covering compatibility probing, log replay, resume, cross-restart and other scenarios; maintained continuously as the harness evolves to ensure feature stability.
|
|
17
17
|
|
|
@@ -40,7 +40,7 @@ dsh plugin --profile web add dsh-rewind-plugin
|
|
|
40
40
|
|
|
41
41
|
## Usage
|
|
42
42
|
|
|
43
|
-
1. Find the user message you want to rewind to in the conversation, or type `/rewind` to open the candidate picker.
|
|
43
|
+
1. Find the user message you want to rewind to in the conversation, or type `/rewind` (or its alias `/undo`) to open the candidate picker.
|
|
44
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
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
46
|
|
|
@@ -56,6 +56,21 @@ dsh plugin --profile web add dsh-rewind-plugin
|
|
|
56
56
|
|
|
57
57
|
</details>
|
|
58
58
|
|
|
59
|
+
## Snapshot management
|
|
60
|
+
|
|
61
|
+
Snapshots (the before-write backups) are stored under `<dsh home>/rewind-snapshots/`
|
|
62
|
+
(`~/.dsh/rewind-snapshots/` when `$DSH_HOME` is unset). For the **same session**,
|
|
63
|
+
the plugin deduplicates snapshots by content (an unchanged file is stored as a link)
|
|
64
|
+
and keeps the newest 100 anchor groups. **Deleting that directory manually** only
|
|
65
|
+
clears the file backups (chat rewinds are unaffected) and the plugin rebuilds them
|
|
66
|
+
automatically.
|
|
67
|
+
|
|
68
|
+
A **global auto-cleanup** (off by default) removes the snapshot directories of
|
|
69
|
+
**long-inactive** sessions, leaving the active session and chat log untouched. Use
|
|
70
|
+
`/snapshot-auto-cleanup` to **view, configure, and run** it; the settings live in
|
|
71
|
+
`<dsh home>/snapshot-cleanup.json` and the last-sweep time in
|
|
72
|
+
`<dsh home>/snapshot-cleanup-last-sweep.json`. See: [Snapshot cleanup](docs/snapshot-auto-cleanup.md).
|
|
73
|
+
|
|
59
74
|
## Why it stands out
|
|
60
75
|
|
|
61
76
|
Compared with the common approaches, here is the trade-off this plugin makes on "rewind":
|
|
@@ -140,7 +155,7 @@ withdrew should consume the stable, locale-independent helpers exported from
|
|
|
140
155
|
|
|
141
156
|
## Security
|
|
142
157
|
|
|
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
|
|
158
|
+
This plugin only appends rewind-marker events to the session log; it never deletes or rewrites logged history. Workspace files are written only when you choose "conversation and code"; backups and restores stay under `<dsh home>/rewind-snapshots/`. It never touches your git repository, makes no network requests, and accesses no credentials. Delete `~/.dsh/rewind-snapshots/` to wipe file backups only (chat rewinds are unaffected); the plugin rebuilds automatically. For sessions you've left inactive for a long time, a global auto-cleanup (off by default) can remove their snapshot directory in whole, leaving the active session and the chat log untouched. Full security model: [SECURITY.md](SECURITY.md).
|
|
144
159
|
|
|
145
160
|
## Development
|
|
146
161
|
|
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ DeepSeek Harness 插件:**一键就地回退对话到任意更早的用户消
|
|
|
11
11
|
刻意聚焦、保持极简,只做一件事:**就地回退到任意远的用户消息**,还能**顺手还原改过的文件**。
|
|
12
12
|
|
|
13
13
|
- **回退 = 时间回溯**——目标消息及其之后的全部内容(agent 回复、工具调用)同时从**模型上下文**和**渲染对话**中撤回,不新建会话、不切换窗口;目标消息文本会回填输入框,改完可重发。**在原理上就真正无感、便捷**。
|
|
14
|
-
-
|
|
14
|
+
- **轻量工作区备份**——对齐 Claude Code:追踪写类工具编辑过的文件,**已跟踪文件的外部变更也能还原**。局部追踪、写前备份、不变不存。一个轻型插件,即拥有**完备的智能体回退能力**。
|
|
15
15
|
- **信息安全优先**——插件从不删改会话日志(append-only),从不真正删除你的任何对话;文件还原限定在插件自己的备份目录。完整安全模型:[SECURITY.md](SECURITY.md)。
|
|
16
16
|
- **完备测试系统**——单元、探针、端到端主机验证,覆盖兼容性探测、日志重放、续接、跨重启等场景;随 harness 升级持续维护,确保功能稳定。
|
|
17
17
|
|
|
@@ -40,7 +40,7 @@ dsh plugin --profile web add dsh-rewind-plugin
|
|
|
40
40
|
|
|
41
41
|
## 使用
|
|
42
42
|
|
|
43
|
-
1. 在对话中找到要回退的那条用户消息,或输入 `/rewind
|
|
43
|
+
1. 在对话中找到要回退的那条用户消息,或输入 `/rewind`(或其别名 `/undo`)打开候选列表选择。
|
|
44
44
|
2. **选中它。** 小浮层提供两种模式(「回退对话和代码」仅在目标之后有可还原的变更时显示)。
|
|
45
45
|
3. 回退立即生效:对话回到目标消息当时的样子,被撤回消息的文本自动填入输入框——改完直接重发。
|
|
46
46
|
|
|
@@ -58,9 +58,9 @@ dsh plugin --profile web add dsh-rewind-plugin
|
|
|
58
58
|
|
|
59
59
|
## 存储管理
|
|
60
60
|
|
|
61
|
-
快照(写前备份)存储于 `~/.dsh/rewind-snapshots
|
|
61
|
+
快照(写前备份)存储于 `<dsh home>/rewind-snapshots/`(未设 `$DSH_HOME` 时即 `~/.dsh/rewind-snapshots/`)。插件对**同一会话**的快照做内容去重(内容未变则存为链接)并保留最近 100 组锚点;**手动删除该目录**仅清除文件备份(对话回退不受影响),插件会自动重建。
|
|
62
62
|
|
|
63
|
-
另提供**全局自动清理**(默认关闭):把**长期不活跃**的会话快照整目录移除,不影响活动会话与对话日志。用 `/snapshot-auto-cleanup`
|
|
63
|
+
另提供**全局自动清理**(默认关闭):把**长期不活跃**的会话快照整目录移除,不影响活动会话与对话日志。用 `/snapshot-auto-cleanup` 命令**查看、设置和运行**,配置写入 `<dsh home>/snapshot-cleanup.json`;最近一次自动清扫的时间记录在 `<dsh home>/snapshot-cleanup-last-sweep.json`。详见:[快照自动清理](docs/snapshot-auto-cleanup.zh.md)。
|
|
64
64
|
|
|
65
65
|
## 本插件的优势
|
|
66
66
|
|
package/SECURITY.md
CHANGED
|
@@ -81,9 +81,11 @@ and is repaired offline (see `docs/compat/troubleshooting.md`).
|
|
|
81
81
|
|
|
82
82
|
## Filesystem containment
|
|
83
83
|
|
|
84
|
-
- **Store root**:
|
|
85
|
-
|
|
86
|
-
|
|
84
|
+
- **Store root**: `<harness home>/rewind-snapshots/` by default (resolved via
|
|
85
|
+
`resolveDshHome`, so `~/.dsh/...` when `DSH_HOME` is unset); the `snapshotDir`
|
|
86
|
+
config, then `DSH_REWIND_SNAPSHOT_DIR` env, override it. The store never
|
|
87
|
+
overlaps the workspace; deleting it only removes file backups and the store
|
|
88
|
+
rebuilds from scratch.
|
|
87
89
|
- **Path sanitization**: session ids and call ids are scrubbed to
|
|
88
90
|
`[a-zA-Z0-9._-]` (`safeSessionId` / `safeFileId`) before joining the store
|
|
89
91
|
root; `.` and `..` bare values are replaced — hostile ids cannot traverse
|
|
@@ -136,6 +138,10 @@ and is repaired offline (see `docs/compat/troubleshooting.md`).
|
|
|
136
138
|
same operating-system user: state files are created with the process
|
|
137
139
|
default permissions (no special modes are set — a standard umask applies),
|
|
138
140
|
and the host user remains trusted.
|
|
141
|
+
- It does **not** exclude paths from the store: recording and restore mirror the
|
|
142
|
+
paths the model's file tools touched, so a sensitive file (e.g. `.env`) the
|
|
143
|
+
model may read or edit will be backed up and restorable. Keeping one out is
|
|
144
|
+
a **DSH model-permission** concern (a per-path deny), not a rewind feature.
|
|
139
145
|
- It does **not** touch git (no refs, index, or worktree operations), makes
|
|
140
146
|
**no network requests**, and does **not** access credentials.
|
|
141
147
|
- It does **not** roll back whole-log state: telemetry, search, and `/export`
|
package/docs/README.md
CHANGED
|
@@ -11,6 +11,7 @@ index/navigation entry point.
|
|
|
11
11
|
| `architecture.md` | Module layering, rewind/checkpoint pipelines, compatibility strategy, roadmap | maintainers |
|
|
12
12
|
| `format.md` | Durable on-disk format spec (checkpoint entries + restore journals) | maintainers |
|
|
13
13
|
| `harness-reference.md` | DeepSeek Harness interface reference + plugin source layout | maintainers |
|
|
14
|
+
| `snapshot-auto-cleanup.md` | Global snapshot auto-cleanup policy and command (`.zh` mirror) | users / maintainers |
|
|
14
15
|
| `contract/client-contract.md` | Rewind visibility contract for third-party DOM plugins (`.zh` mirror) | integrators |
|
|
15
16
|
| `compat/audit.md` | Compatibility audit: verified surfaces, recorded findings, probe matrix | maintainers |
|
|
16
17
|
| `compat/troubleshooting.md` | Known issues & repair steps (`.zh` mirror) | users / maintainers |
|
package/docs/compat/audit.md
CHANGED
|
@@ -1,137 +1,165 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
>
|
|
4
|
-
>
|
|
5
|
-
>
|
|
6
|
-
>
|
|
1
|
+
# Compatibility audit (compat-audit)
|
|
2
|
+
|
|
3
|
+
> Method: **the tests are the audit**. The probes in `tests/compat-invariants.test.ts`,
|
|
4
|
+
> `tests/compat-interop.test.ts`, `tests/compat-gaps.test.ts` and
|
|
5
|
+
> `scripts/verify-host.mjs` drive the plugin's real execution paths through the DSH
|
|
6
|
+
> subsystems' **real consumer paths** (real `@deepseek-ai/*` packages) and assert
|
|
7
|
+
> compatibility invariants. A probe failure is a finding; it enters the
|
|
8
|
+
> fix/pin/record loop.
|
|
7
9
|
>
|
|
8
|
-
>
|
|
9
|
-
>
|
|
10
|
+
> Targeted version: npm `@deepseek-ai/*@0.1.1-rc.2` (matches `package-lock.json`);
|
|
11
|
+
> source reference: the `oss/deepseek-harness` local fork.
|
|
10
12
|
>
|
|
11
|
-
>
|
|
12
|
-
>
|
|
13
|
-
>
|
|
14
|
-
> `
|
|
15
|
-
>
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
13
|
+
> Version alignment: `peerDependencies` use an OR-union (e.g.
|
|
14
|
+
> `^0.1.0-rc.6 || ^0.1.1-rc.2`) covering each published rc tuple series. npm's
|
|
15
|
+
> prerelease matching rules require a candidate to share the range comparator's
|
|
16
|
+
> `[major, minor, patch]` tuple, so each new tuple series (e.g. a future
|
|
17
|
+
> `0.1.2-rc.x`, `0.2.x`) requires appending a union member; rc rolling within a
|
|
18
|
+
> tuple (`0.1.1-rc.2 → rc.3`) is a no-op. Signal: `npm view @deepseek-ai/dsh version`;
|
|
19
|
+
> flow: `scripts/check-dsh-version.mjs`.
|
|
20
|
+
>
|
|
21
|
+
> Client channel (harness `0.1.2-alpha.1`, a **bundled pre-release — NOT published
|
|
22
|
+
> to npm**): the plugin also reads the session chat when a DSH Desktop build ships
|
|
23
|
+
> that version (`latest` on the registry is still `0.1.1-rc.2`; the alpha is only
|
|
24
|
+
> in the bundled `app.asar.unpacked/node_modules/@deepseek-ai/*`). There
|
|
25
|
+
> `uiConversation` exposes the chat as a per-session named `"chat"` view
|
|
26
|
+
> (contributed by `dsh-client-ui-chat` through the `uiSession` slot hook) instead
|
|
27
|
+
> of the session-face `chat` field. `src/client/hidden.ts` (`chatSnapshotOf`) reads
|
|
28
|
+
> the session-face snapshot first (rc.2 path), then the `uiConversation` `"chat"`
|
|
29
|
+
> view (alpha.1 path); both missing degrades to `undefined` (no targets, never a
|
|
30
|
+
> crash). Peers are intentionally **NOT** extended for `0.1.2-alpha.1` because it is
|
|
31
|
+
> not a registry version (see above); the OR-union stays
|
|
32
|
+
> `^0.1.0-rc.6 || ^0.1.1-rc.2` and will gain the `0.1.2` tuple when the harness
|
|
33
|
+
> publishes it. Pinned by `tests/chat-channel.test.ts` (channel precedence + the
|
|
34
|
+
> alpha.1 snapshot shape) and `tests/client-dom.test.ts` (the button-target
|
|
35
|
+
> pairing that consumes the chat).
|
|
36
|
+
|
|
37
|
+
## Definition of "fully compatible" (invariants)
|
|
38
|
+
|
|
39
|
+
| Invariant | Meaning | Probe location |
|
|
20
40
|
|---|---|---|
|
|
21
|
-
| I1
|
|
22
|
-
| I2 surface
|
|
23
|
-
| I3 step/turn
|
|
24
|
-
| I4
|
|
25
|
-
| I5 compact
|
|
26
|
-
| I6
|
|
27
|
-
| I7
|
|
28
|
-
| I8
|
|
29
|
-
|
|
30
|
-
##
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
41
|
+
| I1 log replayability | A rewound log passes token-meter replay and `Session.create` (the resume-preflight validation) without throwing | `compat-invariants` I1, `verify-host` 12/13 |
|
|
42
|
+
| I2 surface consistency | After a cut, the surface has no duplicate nodes, every node exists in the log, the withdrawn target never returns to the surface, and `deriveMessages()` is legal | `compat-invariants` I2 |
|
|
43
|
+
| I3 step/turn structure | Client turn-tail ordering, unique `step/start`, every `step/end` and `assistant/message` has a paired `step/start`, no ghost turns | `compat-invariants` I3, `helpers.assertTurnTailOrdering` |
|
|
44
|
+
| I4 fold-service safety | stats / title / goal / projection fold a marker-bearing log without throwing, with predictable values | `compat-invariants` I4 |
|
|
45
|
+
| I5 compact interop | A tool-call orphaned by a cancelled turn is pair-balanced once shadowed by a rewind; a rewind across a compaction checkpoint is explicitly refused; a rewind-then-compact transaction stays legal | `compat-interop` I5, `verify-host` 12/14 |
|
|
46
|
+
| I6 tool pipeline | before-snapshot capture/commit/restore is correct (existing `snapshot.test.ts` + `verify-host` 4–8); cancellation timing never hangs | `verify-host` 4–8, 15 |
|
|
47
|
+
| I7 client ordering | A log carrying tool turns, rewind markers, and ghost steps satisfies the client builder ordering | `compat-interop` I7 |
|
|
48
|
+
| I8 runtime safety | `rewind`/`compact` combinations never leave a dangling step/turn frame | `verify-host` 15 |
|
|
49
|
+
|
|
50
|
+
## Verified-compatible surfaces (probes pass)
|
|
51
|
+
|
|
52
|
+
- **token-meter replay** (marker + ghost-step frames + multiple rewinds + interleaved real turns + compact stacking).
|
|
53
|
+
- **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.
|
|
54
|
+
- **resume replay**: `Session.create(id, events)` replays a rewind/compact-bearing log.
|
|
55
|
+
- **session-stats**: a ghost-step frame adds one step but no phantom turn (reuses the turn number).
|
|
56
|
+
- **session-title / goal fold**: a marker does not disturb `foldSessionTitle` / `foldGoal`.
|
|
57
|
+
- **client ordering**: turn-tail ordering + `step/start` uniqueness hold for tool turns + marker logs.
|
|
58
|
+
- **rewind across a compact checkpoint**: `RewindError('not-on-surface')` refuses cleanly, no crash.
|
|
59
|
+
- **plan-mode**: `hasOpenTurn` pairs only `turn/start`/`turn/end`; a marker produces no `turn/start`, so a rewind during active plan is unaffected (static review confirmed).
|
|
60
|
+
- **agent-loop cancellation**: `finally` guarantees step/turn closure; the rewind force-stop path leaves no dangling frame.
|
|
61
|
+
|
|
62
|
+
## Known behavior boundaries (deterministic differences, non-crash, documented)
|
|
63
|
+
|
|
64
|
+
- **session-stats / session-telemetry fold the full log**: post-rewind stats do **not** rewind — `turns`/`steps`/`llmMs` still include withdrawn content; telemetry reports the marker and ghost-step frames one-by-one (under the reused old turn). This is the intended "fold the full log" semantics, pinned by probe.
|
|
65
|
+
- **token-meter usage anchor briefly reverts** (G3): after a rewind, the baseline falls back to the heuristic `estimated` until the next real-usage call restores it. Confirmed expected behavior (a marker inherently carries no usage); pinned by `compat-gaps` G3.
|
|
66
|
+
- **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).
|
|
67
|
+
- **Session title auto-regeneration**: the title derives from the surface, so an automatically-derived title may change after a rewind.
|
|
68
|
+
- **Files written but uncommitted in a cancelled turn**: a `both` rewind cannot restore them (tool side-effect timing; same as Claude Code).
|
|
69
|
+
- **Attachment files left after a message is shadowed**: attachment storage is not cleaned with the surface (`dsh-attachment-local` not installed, not automatically verified).
|
|
70
|
+
|
|
71
|
+
## Upstream (harness) issues and the plugin's no-compensation stance
|
|
72
|
+
|
|
73
|
+
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."
|
|
74
|
+
|
|
75
|
+
### RU-I18N: host-side locale preference is not reliably readable at command registration (harness-side structural timing defect; plugin reads once, never retries)
|
|
76
|
+
|
|
77
|
+
- **Root cause (harness-side)**: the plugin resolves `activeLocale` in an
|
|
78
|
+
`ctx.inject(['settings'])` callback that reads `settings.get(settingsNamespace('locale')).preference`
|
|
79
|
+
**once, with no retry** (`src/index.ts`). `dsh-client-locale`'s host half
|
|
80
|
+
registers that `locale` settings section **through its own `ctx.inject(['settings'])`**
|
|
81
|
+
(`packages/client/locale/src/index.ts`). Both callbacks wait only on `settings` and are
|
|
82
|
+
independent of each other, so cordis makes **no ordering guarantee** between the
|
|
83
|
+
section registration and the plugin's read.
|
|
84
|
+
- **Observed failure**: a probe confirmed that when the plugin's read runs, `settings.get('locale')`
|
|
85
|
+
returns `undefined` (the section is not yet registered), so `activeLocale` stays at the
|
|
86
|
+
default `'en'` and — because the read is one-shot — is never corrected. The result is that
|
|
87
|
+
**all host-side `t()` output (runtime messages and command descriptions alike) renders in
|
|
88
|
+
English**, regardless of the user's language preference.
|
|
89
|
+
- **Not a user-config problem**: `settings.yaml` correctly carries `locale.preference: zh`;
|
|
90
|
+
reading it directly works. The defect is purely the host-side read racing the section
|
|
91
|
+
registration.
|
|
92
|
+
- **Plugin stance**: no compensation. The plugin does not add a lazy re-read, a retry loop, or a
|
|
93
|
+
post-locale description re-registration as a workaround, because the guarantee belongs to the
|
|
94
|
+
harness (make the locale preference available before plugin registration, or support per-locale
|
|
95
|
+
command descriptions). The plugin's `t()` design is retained; localized host output is treated
|
|
96
|
+
as an upstream capability to be restored when the harness provides it. (Command descriptions
|
|
97
|
+
are English for **every** host command — system plugins also pass raw English `description`
|
|
98
|
+
strings, e.g. `/goal` — so this behavior is consistent with the ecosystem, not a plugin
|
|
99
|
+
deviation.)
|
|
100
|
+
|
|
101
|
+
### R-OPENSTEP: an unclosed `step` in the log lets a rewind break token-meter replay (harness-side; plugin guard attempted and reverted)
|
|
102
|
+
|
|
103
|
+
> **Root cause (harness-side)**: an unclosed step left by a crash makes token-meter replay
|
|
104
|
+
> reject any later step activity. DSH `0.1.1-rc.2` now auto-closes crash-left step/turn/tool
|
|
105
|
+
> boundaries at load via `interruptedTurnClosers` (`dsh-session`, consumed by
|
|
106
|
+
> `session-persistence/src/coordinator.ts`) — **the crash path is fixed**.
|
|
37
107
|
>
|
|
38
|
-
>
|
|
39
|
-
>
|
|
40
|
-
>
|
|
41
|
-
>
|
|
42
|
-
>
|
|
108
|
+
> **Plugin guard (attempted and reverted)**: a `hasOpenStep` + `planRewind` pre-refusal was
|
|
109
|
+
> implemented (`open-step`) but misjudged on **real session logs** (normal rewinds refused, GUI
|
|
110
|
+
> verification broken), and was reverted (`177ec14`). Conclusion: **the plugin sets no guard**,
|
|
111
|
+
> accepting residual risk (an unclosed step produced by a third-party plugin can break
|
|
112
|
+
> `/compact` — that log is already abnormal, and continuing the conversation triggers the same).
|
|
113
|
+
> The fix direction is in the harness (token-meter recovery for unclosed steps), not the plugin.
|
|
43
114
|
|
|
44
|
-
####
|
|
115
|
+
#### Concrete `step/start` trigger paths (source-confirmed)
|
|
45
116
|
|
|
46
|
-
|
|
47
|
-
|
|
117
|
+
The tree has exactly **one** `append('step/start')` producer: `packages/core/agent-loop/src/agent.ts:279`
|
|
118
|
+
(no other producer inside the official packages; `session/end-seed` etc. only truncate torn writes,
|
|
119
|
+
not logically-unclosed steps).
|
|
48
120
|
|
|
49
|
-
| # |
|
|
121
|
+
| # | Trigger path | Plausibility | Basis |
|
|
50
122
|
|---|---|---|---|
|
|
51
|
-
| P1 |
|
|
52
|
-
| P2 |
|
|
53
|
-
| P3 |
|
|
54
|
-
| P4 | **append
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
`phase.turn + 1`
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
-
|
|
61
|
-
-
|
|
62
|
-
-
|
|
63
|
-
- **rewind
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
- **token-meter 重放**(含 marker + 幽灵 step 帧 + 多次 rewind + 交错真实 turn + compact 叠加)。
|
|
79
|
-
- **compaction 事务**:`toolPairingBalancedBefore/After` 对 marker 切割后的 surface 恒平衡;
|
|
80
|
-
真实 `/compact` 命令(`command-compact` + `compaction-basic`,stub summarizer)可在 rewind
|
|
81
|
-
marker 之上落地 `compaction/start…end` 并保持可重放;小 surface 时 `/compact` 合法 no-op。
|
|
82
|
-
- **resume 重放**:`Session.create(id, events)` 对含 rewind/compact 的日志重放通过。
|
|
83
|
-
- **session-stats**:ghost step 帧只 +1 step、不新增 phantom turn(复用 turn 号)。
|
|
84
|
-
- **session-title / goal fold**:marker 不干扰 `foldSessionTitle` / `foldGoal`。
|
|
85
|
-
- **客户端顺序**:turn-tail ordering + step/start 唯一性对工具 turn + marker 日志成立。
|
|
86
|
-
- **跨 compact checkpoint 的 rewind**:`RewindError('not-on-surface')` 明确拒绝,不崩溃。
|
|
87
|
-
- **plan-mode**:`hasOpenTurn` 只配对 `turn/start`/`turn/end`,marker 不产生 `turn/start`,
|
|
88
|
-
激活 plan 期间 rewind 无影响(静态审查确认)。
|
|
89
|
-
- **agent-loop 取消**:`finally` 保证 step/turn 闭合,rewind 的 force-stop 路径不悬挂帧。
|
|
90
|
-
|
|
91
|
-
## 已知边界(行为差异,非崩溃,文档化)
|
|
92
|
-
|
|
93
|
-
- **session-stats / session-telemetry 折叠完整日志**:rewind 后统计**不回退**——`turns` /
|
|
94
|
-
`steps` / `llmMs` 仍含被撤内容;telemetry 逐条上报 marker 与幽灵 step 帧(归入被复用
|
|
95
|
-
的旧 turn)。这是「折叠完整日志」的预期语义,探针钉住该行为。
|
|
96
|
-
- **token-meter usage 锚点短暂失效**(G3):rewind 后 baseline 退回启发式 `estimated`,
|
|
97
|
-
下一条真实 usage 调用恢复(探针钉住)。
|
|
98
|
-
- **被撤内容仍可搜索、可导出**:session-query 全文搜索与 `/export` 基于原始日志,
|
|
99
|
-
rewind 只切 surface,被撤消息仍在其中(README 已声明)。
|
|
100
|
-
- **session-title 自动重生成**:标题由 surface 派生,rewind 后自动标题可能变化。
|
|
101
|
-
- **取消 turn 中已写盘但未提交快照的文件**:rewind both 无法恢复(工具副作用时序,
|
|
102
|
-
Claude Code 同限制)。
|
|
103
|
-
- **附件消息被遮蔽后文件残留**:attachment 存储不随 surface 清理(`dsh-attachment-local`
|
|
104
|
-
未装,未自动化验证)。
|
|
105
|
-
|
|
106
|
-
## 未覆盖边界(需要额外端到端层,不阻塞)
|
|
107
|
-
|
|
108
|
-
- 真实 LLM 流式与自动标题生成(L2 stub 化)。
|
|
109
|
-
- 真实 SQLite 索引生命周期(`dsh-session-query-sqlite` 未安装、含原生依赖)。
|
|
110
|
-
- 浏览器端实际渲染 replay(客户端契约已由 `client-contract.test.ts` 覆盖逻辑层)。
|
|
111
|
-
- 真实 JSONL 持久化往返(`dsh-session-persistence-jsonl` 依赖 native `koffi`;往返验证的是
|
|
112
|
-
harness 自身 zstd/JSON 编解码,插件不参与,价值/成本不划算,未安装)。
|
|
113
|
-
- session-reference 的 `SessionReferenceResolver.prepare` 需完整 session-query 服务;其数据
|
|
114
|
-
基础(current-surface 投影)已由 G1 探针(`foldSurface` 转换)覆盖。
|
|
115
|
-
- telemetry 管道(`dsh-session-telemetry-otel`)与附件 provider(`dsh-attachment-local`)。
|
|
116
|
-
- 运行中的 workflow/jobs 被 rewind 取消:工具契约要求 observe `exec.signal` 并 settle
|
|
117
|
-
(`packages/core/tools/src/index.ts`),rewind 触发的是 harness 标准取消,非插件特有——
|
|
118
|
-
静态确认,未实测真实 workflow。
|
|
119
|
-
|
|
120
|
-
## 排查矩阵(子系统 × 不变量)
|
|
121
|
-
|
|
122
|
-
| DSH 子系统 | I1 | I2 | I3 | I4 | I5 | I6 | I7 | I8 |
|
|
123
|
+
| P1 | **Abnormal process termination**: `step/start` is batched to disk (write-behind, `maxDelayMs` per batch) → the step is mid-execution (LLM stream/tool, seconds to minutes) → SIGKILL / OOM-kill / power loss / WSL hard-close → `step/end` (in `finally`, only runs while the process is alive) is never persisted | **Most realistic** | `agent.ts:292` finally; write-behind batching; torn-write fix truncates only a half-written line |
|
|
124
|
+
| P2 | **Third-party plugin bug**: only the official agent-loop produces one, but external plugins may `session.append('step/start', …)` and never close it | possible | public `Session.append` |
|
|
125
|
+
| P3 | **Manual session-file editing**: edit `~/.dsh/…/session.jsonl[.zstd]` (zstd needs decompress/recompress; plaintext config edits directly) | possible but laborious | `persistence-jsonl/format.ts` (`JsonlCompression = 'zstd' \| 'none'`) |
|
|
126
|
+
| P4 | **append itself failing**: `append('step/end')` in `finally` throws (payload is plain numbers, nearly impossible) | theoretical | `agent.ts:292` |
|
|
127
|
+
|
|
128
|
+
**Amplifier (rewind is not the only trigger)**: after a crash resume, agent-loop `turn()` opens a
|
|
129
|
+
new turn at `phase.turn + 1` (`agent.ts:251-255`) **without closing the leftover step** — so
|
|
130
|
+
"continue the conversation" (a new `step/start`) trips the same token-meter check. Scope:
|
|
131
|
+
|
|
132
|
+
- **The conversation itself is unaffected** (the request path does not call `tokenMeter.measure`; only compaction-basic does tree-wide).
|
|
133
|
+
- **Manual `/compact` fails permanently** (`compactNow`'s first `measure()` throws the raw error).
|
|
134
|
+
- **Automatic compaction silently stays disabled** (the `agent/pre-step` hook catches and warns; the conversation continues).
|
|
135
|
+
- **rewind's role**: if the user rewinds first (rather than continuing), the ghost `step/start` becomes the first trip-wire, and the plugin has no defensive detection — upgrading a "locally abnormal log" into a "user-visible `/compact` failure."
|
|
136
|
+
|
|
137
|
+
## Uncovered boundaries (need an additional e2e layer; non-blocking)
|
|
138
|
+
|
|
139
|
+
- Real LLM streaming and auto title generation (L2 stubbed).
|
|
140
|
+
- Real SQLite index lifetime (`dsh-session-query-sqlite` not installed; native deps).
|
|
141
|
+
- Actual browser rendering replay (the client contract's logic layer is covered by `client-contract.test.ts`).
|
|
142
|
+
- Real JSONL persistence round-trip (`dsh-session-persistence-jsonl` depends on native `koffi`; the round-trip validates the harness's own zstd/JSON codec, the plugin is not party to it, not worth the cost, not installed).
|
|
143
|
+
- `session-reference`'s `SessionReferenceResolver.prepare` needs a full session-query service; its data base (current-surface projection) is already covered by the G1 probe (`foldSurface`).
|
|
144
|
+
- telemetry pipeline (`dsh-session-telemetry-otel`) and the attachment provider (`dsh-attachment-local`).
|
|
145
|
+
- 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.
|
|
146
|
+
|
|
147
|
+
## Audit matrix (subsystem × invariant)
|
|
148
|
+
|
|
149
|
+
| DSH subsystem | I1 | I2 | I3 | I4 | I5 | I6 | I7 | I8 |
|
|
123
150
|
|---|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
|
|
124
|
-
|
|
|
151
|
+
| Session core (append/surface/deriveMessages) | ✓ | ✓ | ✓ | — | ✓ | — | ✓ | ✓ |
|
|
125
152
|
| token-meter | ✓ | — | ✓ | — | ✓ | — | — | — |
|
|
126
|
-
| compaction
|
|
153
|
+
| compaction (transaction/command/tool-pairing) | ✓ | — | — | — | ✓ | — | — | ✓ |
|
|
127
154
|
| session-stats / projection | — | — | ✓ | ✓ | — | — | — | — |
|
|
128
155
|
| session-title | — | — | — | ✓ | — | — | — | — |
|
|
129
156
|
| goal | — | — | — | ✓ | — | — | — | — |
|
|
130
|
-
| resume / session-query
|
|
131
|
-
|
|
|
132
|
-
|
|
|
133
|
-
| plan-mode | — | — | — | — | — | — | — |
|
|
134
|
-
|
|
135
|
-
✓ =
|
|
136
|
-
|
|
137
|
-
|
|
157
|
+
| resume / session-query replay | ✓ | — | ✓ | — | — | — | ✓ | — |
|
|
158
|
+
| tool pipeline (snapshot/restore) | — | — | — | — | — | ✓ | — | ✓ |
|
|
159
|
+
| client ordering | — | — | ✓ | — | — | — | ✓ | — |
|
|
160
|
+
| plan-mode | — | — | — | — | — | — | — | ✓ (static) |
|
|
161
|
+
|
|
162
|
+
✓ = probe passes; — = not applicable. RU-I18N and R-OPENSTEP are upstream issues the plugin
|
|
163
|
+
does not compensate for (see above); G3 is a confirmed-non-defect behavior pinned in
|
|
164
|
+
`compat-gaps.test.ts` (G1 surface classification via `foldSurface` and G2 projection checkpoint
|
|
165
|
+
both pass).
|
package/docs/format.md
CHANGED
|
@@ -7,10 +7,11 @@ and this spec disagree, the code wins and this spec is a bug.
|
|
|
7
7
|
|
|
8
8
|
## State root
|
|
9
9
|
|
|
10
|
-
The store root defaults to
|
|
11
|
-
directory
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
The store root defaults to `<harness home>/rewind-snapshots/` — the dsh data
|
|
11
|
+
directory (`~/.dsh/rewind-snapshots/` when `DSH_HOME` is unset) — overridable in
|
|
12
|
+
order by the `snapshotDir` plugin config, then the `DSH_REWIND_SNAPSHOT_DIR`
|
|
13
|
+
environment variable. It is a sibling of the workspace, never a subtree of it.
|
|
14
|
+
Deleting the root only removes file backups; the store rebuilds from scratch.
|
|
14
15
|
|
|
15
16
|
```
|
|
16
17
|
<root>/
|
|
@@ -54,7 +54,7 @@ src/index.ts host plugin: /rewind command + checkpoint pipeline (tool
|
|
|
54
54
|
src/rewind.ts pure planning: target resolution, surface range, candidate listing
|
|
55
55
|
src/snapshot.ts checkpoint store (disk before-backups, restore/preview, bounded prune)
|
|
56
56
|
src/session-cwd.ts session-cwd resolution (fs-tools rule)
|
|
57
|
-
src/client/index.ts client plugin: per-message ↶ button
|
|
57
|
+
src/client/index.ts client plugin: /rewind command decoration + per-message ↶ button portals
|
|
58
58
|
src/client/popover.ts mode-selection popover (both-mode impact confirm)
|
|
59
59
|
src/client/hidden.ts withdrawn-span computation (hiddenSeqsOf), pure
|
|
60
60
|
src/client/locales.ts zh / en copy (LocaleNamespaceMap)
|
package/docs/release/release.md
CHANGED
|
@@ -38,6 +38,9 @@ npm version patch
|
|
|
38
38
|
git push origin main --tags # triggers .github/workflows/publish.yml
|
|
39
39
|
```
|
|
40
40
|
|
|
41
|
+
- **Before bumping, manually confirm there is no newer DSH version the plugin
|
|
42
|
+
has not been verified against** (a pre-release can ship in DSH Desktop
|
|
43
|
+
without being on npm, e.g. `0.1.2-alpha.1`; see docs/compat/audit.md).
|
|
41
44
|
- The workflow verifies the tag matches `package.json`, runs typecheck + tests +
|
|
42
45
|
a full build + artifact verification, publishes with `--provenance`
|
|
43
46
|
(Sigstore), and creates a GitHub Release. It is **idempotent** — an already
|
|
@@ -58,9 +61,11 @@ uses an OR-union covering every published rc tuple series
|
|
|
58
61
|
(`0.1.1 → 0.1.2 → 0.2.x`); rc rolling within a tuple (`0.1.1-rc.2 → rc.3`)
|
|
59
62
|
needs nothing. All `@deepseek-ai/*` packages release together;
|
|
60
63
|
`npm view @deepseek-ai/dsh version` is the authoritative signal.
|
|
61
|
-
- **
|
|
62
|
-
latest npm version against the tuples the peers cover
|
|
63
|
-
|
|
64
|
+
- **Published-tuple check (optional)**: `node scripts/check-dsh-version.mjs`
|
|
65
|
+
compares the latest npm version against the tuples the peers cover (exit 0 =
|
|
66
|
+
nothing to do, exit 1 = update). It reads npm published versions only; a
|
|
67
|
+
newer-but-unpublished pre-release is a manual pre-release check — see the
|
|
68
|
+
"Before bumping" step above.
|
|
64
69
|
- **Update steps**: append `|| ^<new-tuple>-rc.<n>` to every
|
|
65
70
|
`@deepseek-ai/dsh-*` peer → bump devDependencies to the latest → `npm
|
|
66
71
|
install` → `npm run check` → release.
|
|
@@ -37,6 +37,7 @@ npm version patch
|
|
|
37
37
|
git push origin main --tags # 触发 .github/workflows/publish.yml
|
|
38
38
|
```
|
|
39
39
|
|
|
40
|
+
- **升版前手动确认**:确认无插件未针对其验证过的更新 DSH 版本(pre-release 可能只随 Desktop 捆绑、而不发到 npm,如 `0.1.2-alpha.1`;见 docs/compat/audit.md)。
|
|
40
41
|
- workflow 校验 tag 与 `package.json` 版本一致,跑 typecheck + 测试 + 完整
|
|
41
42
|
构建 + 产物验证,以 `--provenance`(Sigstore)发布并创建 GitHub Release。
|
|
42
43
|
**幂等**——已发布的版本会跳过。
|
|
@@ -54,8 +55,9 @@ OR 并集覆盖 DSH 已发布的每个 rc 元组系列(如 `^0.1.0-rc.6 || ^0.
|
|
|
54
55
|
- **何时需要更新**:仅当 DSH 发布新元组(`0.1.1 → 0.1.2 → 0.2.x`)时;
|
|
55
56
|
同元组内 rc 滚动(`0.1.1-rc.2 → rc.3`)无需动作。DSH 所有包同版本发布,
|
|
56
57
|
`npm view @deepseek-ai/dsh version` 即权威信号。
|
|
57
|
-
-
|
|
58
|
-
peer
|
|
58
|
+
- **已发布元组检查(可选)**:`node scripts/check-dsh-version.mjs` 用 npm 最新版本
|
|
59
|
+
对比 peer 覆盖的元组(exit 0 无需动作,exit 1 需要)。它**只读 npm 已发布版本**;
|
|
60
|
+
更新的未发布 pre-release 走**手动发布前检查**——见上文"升版前手动确认"。
|
|
59
61
|
- **更新步骤**:给每个 `@deepseek-ai/dsh-*` peer 追加 `|| ^<新元组>-rc.<n>`
|
|
60
62
|
→ devDependencies 同步升到最新 → `npm install` → `npm run check` → 发版。
|
|
61
63
|
- **正式版后收敛**:DSH 发布 final 版本后,正式版不受 prerelease 元组规则
|