dsh-rewind-plugin 0.10.0-alpha.2 → 0.11.0-beta.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 +1 -1
- package/README.md +1 -1
- package/SECURITY.md +10 -7
- package/docs/architecture.md +1 -1
- package/docs/compat/audit.md +20 -10
- package/docs/release/release.md +11 -11
- package/docs/release/release.zh.md +7 -7
- package/docs/rewind-fix.md +9 -8
- package/docs/rewind-fix.zh.md +9 -8
- package/lib/client.js +11 -5
- package/lib/index.js +98 -2
- package/lib/types/client/portals.d.ts +14 -0
- package/lib/types/client/styles.d.ts +1 -1
- package/lib/types/locales.d.ts +5 -0
- package/lib/types/rewind.d.ts +22 -2
- package/lib/types/snapshot.d.ts +51 -0
- package/package.json +45 -45
package/README.en.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# dsh-rewind
|
|
2
2
|
|
|
3
3
|
> [!WARNING]
|
|
4
|
-
> **
|
|
4
|
+
> **Install a `v0.9.x` release on DSH `0.1.2-rc.1` as early as possible, and run `/dsh-rewind-fix` to update old sessions' 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
|
|
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# dsh-rewind
|
|
2
2
|
|
|
3
3
|
> [!WARNING]
|
|
4
|
-
>
|
|
4
|
+
> **请尽早在 DSH `0.1.2-rc.1` 安装 `v0.9.x` 版本,运行 `/dsh-rewind-fix` 更新旧会话的回退标记**([更新指南](docs/rewind-fix.zh.md))。
|
|
5
5
|
|
|
6
6
|
DeepSeek Harness 插件:**一键就地回退对话到任意更早的用户消息**——同窗口内完成,不新建分支、不换窗口,可一并还原工作区文件(完整 Claude Code `/rewind` 语义)。
|
|
7
7
|
|
package/SECURITY.md
CHANGED
|
@@ -59,13 +59,16 @@ than hidden.
|
|
|
59
59
|
A failed gate fails closed: an invalid target, a missing store, an absent
|
|
60
60
|
backup, or a cancelled invocation aborts the rewind with an error.
|
|
61
61
|
|
|
62
|
-
**Automatic store deletion
|
|
63
|
-
`snapshot-auto-cleanup`
|
|
64
|
-
**long-inactive** sessions
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
62
|
+
**Automatic store deletion**: two things delete session snapshots. The opt-in
|
|
63
|
+
`snapshot-auto-cleanup` sweep (default off) removes the whole directories of
|
|
64
|
+
**long-inactive** sessions, unrelated to restores. Separately, a session's
|
|
65
|
+
snapshots are cleared automatically when the conversation log's session format
|
|
66
|
+
has changed (a DSH upgrade), because those backups are anchored to message
|
|
67
|
+
positions that no longer line up. Both stay confined to the store root, use
|
|
68
|
+
`lstat` (so they never follow a symlink out of the root), the sweep never targets
|
|
69
|
+
the active session, and neither touches the conversation log. When auto-cleanup
|
|
70
|
+
is disabled (the default), only the format-change clear runs, and only for an
|
|
71
|
+
affected session.
|
|
69
72
|
|
|
70
73
|
## Conversation integrity
|
|
71
74
|
|
package/docs/architecture.md
CHANGED
|
@@ -56,7 +56,7 @@ machine channels (see [Compatibility strategy](#compatibility-strategy)).
|
|
|
56
56
|
→ agent.cancel({ keepInbox: true }) if running; waitForAgentIdle
|
|
57
57
|
→ dropPendingSteering (next-step inbox only; queued messages untouched)
|
|
58
58
|
→ append the rewind marker = a user/message with surfaceOp
|
|
59
|
-
{ op: 'replace',
|
|
59
|
+
{ op: 'replace', startSeq, endSeq } over every surface node after the
|
|
60
60
|
target (+ sourceEventSeqs = shadowed seqs) [a single event]
|
|
61
61
|
→ if mode 'both': store.restoreAfter(targetSeq) + syncRestoreObservations
|
|
62
62
|
→ result text carries machine tokens (impact=<n>, restore:/delete: lines)
|
package/docs/compat/audit.md
CHANGED
|
@@ -7,27 +7,28 @@
|
|
|
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.
|
|
10
|
+
> Targeted version: npm `@deepseek-ai/*@0.1.5-rc.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
|
|
14
14
|
> (e.g. `^0.1.2-rc.1`). npm's prerelease matching rules require a candidate to
|
|
15
15
|
> share the range comparator's `[major, minor, patch]` tuple, so a new DSH tuple
|
|
16
|
-
> replaces the peer tuple (single-line model)
|
|
17
|
-
>
|
|
16
|
+
> replaces the peer tuple (single-line model). The range is conservative — it
|
|
17
|
+
> declares only what was verified, so a release already inside it changes
|
|
18
|
+
> nothing. Signal: `npm view @deepseek-ai/dsh dist-tags`;
|
|
18
19
|
> flow: `scripts/check-dsh-version.mjs` (it reads the `latest` dist-tag only; a
|
|
19
20
|
> prerelease published under another tag is a manual pre-release check).
|
|
20
21
|
>
|
|
21
|
-
> The plugin targets a single DSH version line
|
|
22
|
-
>
|
|
22
|
+
> The plugin targets a single DSH version line; compatibility with earlier
|
|
23
|
+
> lines is not kept.
|
|
23
24
|
|
|
24
|
-
### Single channel
|
|
25
|
+
### Single channel
|
|
25
26
|
|
|
26
|
-
The plugin targets one DSH channel. Each seam below reads the
|
|
27
|
-
only (no `Session.events` / `[data-time-hover-root]` /
|
|
28
|
-
legacy branch).
|
|
27
|
+
The plugin targets one DSH channel. Each seam below reads the targeted
|
|
28
|
+
version's shape only (no `Session.events` / `[data-time-hover-root]` /
|
|
29
|
+
`<textarea>` / face-`chat` legacy branch).
|
|
29
30
|
|
|
30
|
-
| Seam |
|
|
31
|
+
| Seam | Implementation at the targeted version |
|
|
31
32
|
| --- | --- |
|
|
32
33
|
| Host session log | `session.snapshotEvents()` |
|
|
33
34
|
| Client chat snapshot | `uiConversation` `chat` view (`chatSnapshotOf`) |
|
|
@@ -66,6 +67,7 @@ legacy branch).
|
|
|
66
67
|
- **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
68
|
- **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
69
|
- **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`).
|
|
70
|
+
- **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.
|
|
69
71
|
- **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).
|
|
70
72
|
- **Session title auto-regeneration**: the title derives from the surface, so an automatically-derived title may change after a rewind.
|
|
71
73
|
- **Files written but uncommitted in a cancelled turn**: a `both` rewind cannot restore them (tool side-effect timing; same as Claude Code).
|
|
@@ -101,6 +103,14 @@ The plugin treats these as harness-side defects it does not compensate for. Each
|
|
|
101
103
|
are English for **every** host command — system plugins also pass raw English `description`
|
|
102
104
|
strings, e.g. `/goal` — so this behavior is consistent with the ecosystem, not a plugin
|
|
103
105
|
deviation.)
|
|
106
|
+
- **Client-side command-description i18n is first-party-only**: DSH also localizes host command
|
|
107
|
+
descriptions through the client `locale` binding (`ui-commands`), but the description keys come
|
|
108
|
+
from a **closed allowlist** (`HOST_DESCRIPTION_KEYS`: compact, export, feedback, goal,
|
|
109
|
+
permission, plan). A command outside that set — every third-party plugin — is passed through
|
|
110
|
+
verbatim, never translated (`hostDescription` only rewrites a description that equals the
|
|
111
|
+
first-party English copy). So the plugin's own `/rewind` command description can never ride this
|
|
112
|
+
channel either; it is authored in the host's tongue (English by default), same as the host
|
|
113
|
+
runtime copy above.
|
|
104
114
|
|
|
105
115
|
### R-OPENSTEP (rewind part resolved): an unclosed `step` in the log breaks token-meter replay; the rewind no longer compounds it
|
|
106
116
|
|
package/docs/release/release.md
CHANGED
|
@@ -73,28 +73,28 @@ Each release is `git push <branch>` followed by `git push <branch> --tags`.
|
|
|
73
73
|
|
|
74
74
|
DSH is still in rc; npm's prerelease matching rules require a peer range to
|
|
75
75
|
share the host version's `[major, minor, patch]` tuple. So `peerDependencies`
|
|
76
|
-
uses one peer tuple per DSH line (e.g. `^0.1.2-rc.1`)
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
76
|
+
uses one peer tuple per DSH line (e.g. `^0.1.2-rc.1`). The range is
|
|
77
|
+
conservative: it declares only what was verified, and guarantees nothing
|
|
78
|
+
outside it.
|
|
79
|
+
|
|
80
|
+
- **When to update**: when the verified range changes — a new DSH tuple, or a
|
|
81
|
+
deliberate narrowing (e.g. dropping the internal `alpha` series); a release
|
|
82
|
+
already inside the range changes nothing. All `@deepseek-ai/*` packages
|
|
83
|
+
release together; `npm view @deepseek-ai/dsh dist-tags` is the signal.
|
|
83
84
|
- **Published-tuple check (optional)**: `node scripts/check-dsh-version.mjs`
|
|
84
85
|
compares the `latest` dist-tag version against the tuple the peers cover
|
|
85
86
|
(exit 0 = nothing to do, exit 1 = update). It reads the `latest` tag only; a
|
|
86
87
|
pre-release published under another tag or bundled without
|
|
87
88
|
going to npm is a manual pre-release check — see the "Before bumping" step above.
|
|
88
|
-
- **Update steps**:
|
|
89
|
-
|
|
90
|
-
`npm run check` → release.
|
|
89
|
+
- **Update steps**: point every `@deepseek-ai/dsh-*` peer and devDependency at
|
|
90
|
+
the verified range → `npm install` → `npm run check` → release.
|
|
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
94
|
- **Declared minimum (`dsh.engines.dsh`)**: alongside the peer tuple, each
|
|
95
95
|
release declares the DSH runtime floor under `dsh.engines.dsh` (e.g.
|
|
96
96
|
`>=0.1.2-rc.1`), consumed by the plugin-manager update guard. Bump it in
|
|
97
|
-
the same release that
|
|
97
|
+
the same release that changes the peer range; never leave code raised while
|
|
98
98
|
the declared floor stays behind. Only the `>=X.Y.Z[-pre]` form is
|
|
99
99
|
supported (`^`/`~`/multi-range are treated as "cannot verify" and
|
|
100
100
|
fail closed).
|
|
@@ -66,22 +66,22 @@ npm publish --access public
|
|
|
66
66
|
|
|
67
67
|
DSH 仍在 rc 阶段,npm 的 prerelease 匹配规则要求 peer 范围与宿主版本
|
|
68
68
|
**同 `[major, minor, patch]` 元组**才能匹配。因此 peerDependencies 采用
|
|
69
|
-
**每一条 DSH 线一个 peer 元组**(如 `^0.1.2-rc.1
|
|
69
|
+
**每一条 DSH 线一个 peer 元组**(如 `^0.1.2-rc.1`);范围是保守的,只声明实际验证过的内容。
|
|
70
70
|
|
|
71
|
-
-
|
|
72
|
-
|
|
73
|
-
`npm view @deepseek-ai/dsh
|
|
71
|
+
- **何时需要更新**:**已验证范围**变化时——DSH 发新元组,或主动收窄(例如不再
|
|
72
|
+
声明内测的 DSH `alpha` 系列);落在已声明范围内的发布不改变任何东西。DSH 所有包
|
|
73
|
+
同版本发布;信号是 `npm view @deepseek-ai/dsh dist-tags`。
|
|
74
74
|
- **已发布元组检查(可选)**:`node scripts/check-dsh-version.mjs` 用 npm `latest`
|
|
75
75
|
dist-tag 版本对比 peer 覆盖的元组(exit 0 无需动作,exit 1 需要)。它**只读
|
|
76
76
|
`latest` tag**;发布在其它 tag 的 pre-release 走**手动发布前检查**
|
|
77
77
|
——见上文"升版前手动确认"。
|
|
78
|
-
- **更新步骤**:把每个 `@deepseek-ai/dsh-*` peer
|
|
79
|
-
|
|
78
|
+
- **更新步骤**:把每个 `@deepseek-ai/dsh-*` peer 与 devDependency 指向已验证
|
|
79
|
+
范围 → `npm install` → `npm run check` → 发版。
|
|
80
80
|
- **正式版后收敛**:DSH 发布 final 版本后,正式版不受 prerelease 元组规则
|
|
81
81
|
限制,peer 可收敛为稳定的 `^0.1.x` 单范围,此节即可删除。
|
|
82
82
|
- **声明的最低运行时(`dsh.engines.dsh`)**:与 peer 元组一起,每个发布在
|
|
83
83
|
`dsh.engines.dsh` 声明 DSH 运行时下限(如 `>=0.1.2-rc.1`),供插件管理器
|
|
84
|
-
更新守卫读取。**与 peer
|
|
84
|
+
更新守卫读取。**与 peer 范围持平的同一发布里一并 bump**;不可只升代码、
|
|
85
85
|
声明下限停留在旧值。仅支持 `>=X.Y.Z[-pre]` 形式(`^`/`~`/多范围会被视为
|
|
86
86
|
「无法校验」而 fail-closed)。
|
|
87
87
|
|
package/docs/rewind-fix.md
CHANGED
|
@@ -4,18 +4,19 @@
|
|
|
4
4
|
|
|
5
5
|
## Applies to
|
|
6
6
|
|
|
7
|
-
- **DSH**: `0.1.2-rc.1
|
|
8
|
-
- **
|
|
7
|
+
- **DSH version**: `0.1.2-rc.1`.
|
|
8
|
+
- **Plugin version**: `v0.9.0-alpha.1`, `v0.9.0-alpha.2`, `v0.9.0`, `v0.9.1` (recommended).
|
|
9
|
+
- **Audience**: users planning to use a future DSH version who used an early plugin (≤ 0.8.0).
|
|
9
10
|
|
|
10
11
|
## Background
|
|
11
12
|
|
|
12
|
-
`dsh-rewind` rewinds **within the same session** — it never forks a branch
|
|
13
|
+
`dsh-rewind` rewinds **within the same session** — it never forks a branch, and the session log stays the single source. The plugin follows a conservative policy: it deletes no history, and only **appends a rewind marker** telling DSH "the model-visible conversation continues from this message; everything after it is rolled back". Older plugin versions wrote that **rewind marker** as a "ghost step frame", which worked reliably on DSH `v0.1.2-rc.1` and earlier.
|
|
13
14
|
|
|
14
|
-
But DSH v0.1.3
|
|
15
|
+
But starting with DSH `v0.1.3-alpha.1`, a stricter **session-format validation** is introduced. We discovered ahead of time that the **rewind markers** written by older plugin versions **cannot be validated** under those stricter checks, so **a session that was rewound may fail to open**.
|
|
15
16
|
|
|
16
17
|
The plugin is prepared in two parts, both shipped in the new version:
|
|
17
18
|
|
|
18
|
-
1. **New rewind markers use the new shape** (forward) — this is a **correct, low-risk**, long-term change (see the [README](../README.en.md), the "How it works" section), and the new shape is fully compatible with **both old and new** DSH.
|
|
19
|
+
1. **New rewind markers use the new shape** (forward) — this is a **correct, low-risk**, long-term change aligned with the official `/compact` design (see the [README](../README.en.md), the "How it works" section), and the new shape is fully compatible with **both old and new** DSH.
|
|
19
20
|
|
|
20
21
|
2. **The `/dsh-rewind-fix` command** (backward) — for **already-existing** old sessions, the plugin ships a convenient update command that translates those old markers into the new shape so the sessions are usable again.
|
|
21
22
|
|
|
@@ -54,7 +55,7 @@ Then run the command **in this new session**, and it will update the old session
|
|
|
54
55
|
|
|
55
56
|
### Step 4 · Preview the update scope
|
|
56
57
|
|
|
57
|
-
Type `/dsh-rewind-fix` in the composer and send it. It only scans, doesn't write. The editor locks while it runs, which is normal. **Don't switch session windows midway
|
|
58
|
+
Type `/dsh-rewind-fix` in the composer and send it. It only scans, doesn't write. The editor locks while it runs, which is normal. **Don't switch session windows midway**. When it finishes, it reports how many sessions were scanned, how many will be updated, how many skipped, and how many failed. Confirm those are what you expect before moving on.
|
|
58
59
|
```
|
|
59
60
|
/dsh-rewind-fix
|
|
60
61
|
```
|
|
@@ -73,7 +74,7 @@ After the run, restart DSH, then **preview once more**:
|
|
|
73
74
|
```
|
|
74
75
|
/dsh-rewind-fix
|
|
75
76
|
```
|
|
76
|
-
If it says there are no more sessions to update, the markers are all current and the old sessions will open normally.
|
|
77
|
+
If it says there are no more sessions to update, the markers are all current and the old sessions will open normally. If there are still sessions to update, they may have been loaded during the run — restart in a temporary session window and retry.
|
|
77
78
|
|
|
78
79
|
### Step 7 · Delete the backup after confirming (optional)
|
|
79
80
|
|
|
@@ -84,5 +85,5 @@ rm -rf ~/.dsh/sessions.backup ~/.dsh/rewind-snapshots.backup
|
|
|
84
85
|
|
|
85
86
|
## Notes
|
|
86
87
|
|
|
87
|
-
- The session logs are still v0 format; when DSH's later `v0 → v1 → v2` migration
|
|
88
|
+
- The session logs are still v0 format; when DSH's later `v0 → v1 → v2 → v3` migration arrives, if you've completed the `rewind` marker update, the markers themselves won't block it.
|
|
88
89
|
- Some sessions may still be blocked by an **unclosed turn** (a turn that was interrupted/cancelled and never wrote `turn/end`). Per the empirical analysis these are widespread and **unrelated to rewind** — they're a DSH-side issue and this tool doesn't handle them. **So updating the markers doesn't guarantee you can upgrade to the next DSH line.**
|
package/docs/rewind-fix.zh.md
CHANGED
|
@@ -4,18 +4,19 @@
|
|
|
4
4
|
|
|
5
5
|
## 适配
|
|
6
6
|
|
|
7
|
-
- **DSH
|
|
8
|
-
-
|
|
7
|
+
- **DSH 版本**:`v0.1.2-rc.1`。
|
|
8
|
+
- **插件版本**:`v0.9.0-alpha.1`,`v0.9.0-alpha.2`,`v0.9.0`,`v0.9.1`(推荐)。
|
|
9
|
+
- **适用人群**:计划使用未来版本 DSH,且使用过早期版本插件(≤ 0.8.0)的用户。
|
|
9
10
|
|
|
10
11
|
## 背景
|
|
11
12
|
|
|
12
|
-
`dsh-rewind`
|
|
13
|
+
`dsh-rewind` 的回退是**同一会话**的消息回退。它从不新建分支,会话日志保持同一份。插件遵循保守策略:不删除任何历史,只在会话日志里**追加一条 rewind 标记**,表示“模型可见的对话从这条消息继续,其后内容回卷”。旧版插件的 **rewind 标记**用的是「幽灵步骤框架」写法,在 DSH `v0.1.2-rc.1` 及之前的版本长期验证有效。
|
|
13
14
|
|
|
14
|
-
|
|
15
|
+
但从 `v0.1.3-alpha.1` 开始,DSH 引入了更严格的**会话格式校验**。我们提前发现:旧版本往会话日志里写入的 **rewind 标记**,在这种严格校验下**无法自证合法**,届时会导致**回退过的旧会话打不开**。
|
|
15
16
|
|
|
16
17
|
插件做了两阶段准备,已在新版本中包含:
|
|
17
18
|
|
|
18
|
-
1. **新产生的 rewind
|
|
19
|
+
1. **新产生的 rewind 标记换成新格式**(向前)——这一步是**正确、低风险**的长期改动(对标官方 `/compact` 设计,见 [README](../README.md)「原理」一节),新格式对**新旧版本** DSH 均完全兼容。
|
|
19
20
|
|
|
20
21
|
2. **提供 `/dsh-rewind-fix` 命令**(向后)——对于**已经存在**的旧会话,本插件提供便捷更新命令,把旧标记「翻译」成新格式,让会话重新可用。
|
|
21
22
|
|
|
@@ -54,7 +55,7 @@ cp -r ~/.dsh/rewind-snapshots ~/.dsh/rewind-snapshots.backup
|
|
|
54
55
|
|
|
55
56
|
### 第四步 · 预览更新范围
|
|
56
57
|
|
|
57
|
-
在编辑区域输入 `/dsh-rewind-fix`
|
|
58
|
+
在编辑区域输入 `/dsh-rewind-fix` 并发送。它只扫描、不写盘。执行时编辑区会正常锁定。**不要中途切换会话窗口**。完成后会告诉你:扫描了几个会话、其中几个将被更新、几个跳过、几个失败。确认是你预期的那几项,再进下一步。
|
|
58
59
|
```
|
|
59
60
|
/dsh-rewind-fix
|
|
60
61
|
```
|
|
@@ -73,7 +74,7 @@ cp -r ~/.dsh/rewind-snapshots ~/.dsh/rewind-snapshots.backup
|
|
|
73
74
|
```
|
|
74
75
|
/dsh-rewind-fix
|
|
75
76
|
```
|
|
76
|
-
|
|
77
|
+
如果提示已经没有待更新的会话,说明标记已全部更新到位,旧会话就能正常打开了。如果还有未更新的会话,可能是执行时被加载过,在临时会话窗口重启并重试即可。
|
|
77
78
|
|
|
78
79
|
### 第七步 · 确认正常后删除备份(可选)
|
|
79
80
|
|
|
@@ -84,5 +85,5 @@ rm -rf ~/.dsh/sessions.backup ~/.dsh/rewind-snapshots.backup
|
|
|
84
85
|
|
|
85
86
|
## 补充说明
|
|
86
87
|
|
|
87
|
-
- 当前会话日志仍为 v0 格式;DSH
|
|
88
|
+
- 当前会话日志仍为 v0 格式;DSH 后续的 `v0 → v1 → v2 → v3` 迁移到来时,如果你完成了 `rewind` 标记更新,标记本身就不会阻断它。
|
|
88
89
|
- 个别会话还可能因**未闭合的 turn**(某次回合被打断/取消、没写 `turn/end`)等问题而卡住迁移——根据实测分析,这些问题普遍存在,**与 rewind 无关**,属 DSH 侧自身问题,本工具不处理。**所以更新完标记并不保证一定能升到下一 DSH 线。**
|
package/lib/client.js
CHANGED
|
@@ -399,7 +399,7 @@ var STYLE = `
|
|
|
399
399
|
.dsh-rewind-cleanup-body {
|
|
400
400
|
border-top: 0.5px solid var(--dsw-alias-border-l2);
|
|
401
401
|
margin: 0 16px;
|
|
402
|
-
padding:
|
|
402
|
+
padding: 0 0 8px;
|
|
403
403
|
}
|
|
404
404
|
.dsh-rewind-cleanup-readonly {
|
|
405
405
|
margin: 12px 0 0;
|
|
@@ -421,6 +421,12 @@ var STYLE = `
|
|
|
421
421
|
.dsh-rewind-cleanup-field + .dsh-rewind-cleanup-field {
|
|
422
422
|
border-top: 0.5px solid var(--dsw-alias-border-l2);
|
|
423
423
|
}
|
|
424
|
+
/* Auto-cleanup (permission) block and the max-age field are consecutive control
|
|
425
|
+
* sections of the open card \u2014 give them the same 0.5px divider the harness gives
|
|
426
|
+
* every pair of config fields (fields.module.css field + field). */
|
|
427
|
+
.dsh-rewind-cleanup-permission + .dsh-rewind-cleanup-field {
|
|
428
|
+
border-top: 0.5px solid var(--dsw-alias-border-l2);
|
|
429
|
+
}
|
|
424
430
|
.dsh-rewind-cleanup-head {
|
|
425
431
|
display: flex;
|
|
426
432
|
align-items: center;
|
|
@@ -505,7 +511,7 @@ var STYLE = `
|
|
|
505
511
|
box-sizing: border-box;
|
|
506
512
|
height: 34px;
|
|
507
513
|
padding: 0 12px;
|
|
508
|
-
border:
|
|
514
|
+
border: 0.5px solid var(--dsw-alias-border-l4);
|
|
509
515
|
border-radius: 8px;
|
|
510
516
|
background: var(--dsw-alias-bg-layer-3);
|
|
511
517
|
font: inherit;
|
|
@@ -530,7 +536,7 @@ var STYLE = `
|
|
|
530
536
|
justify-content: flex-end;
|
|
531
537
|
gap: 8px;
|
|
532
538
|
padding: 12px 0 4px;
|
|
533
|
-
border-top:
|
|
539
|
+
border-top: 0.5px solid var(--dsw-alias-border-l2);
|
|
534
540
|
}
|
|
535
541
|
.dsh-rewind-cleanup-failed {
|
|
536
542
|
flex: 1;
|
|
@@ -1339,8 +1345,8 @@ function createRewindBridge(deps) {
|
|
|
1339
1345
|
}
|
|
1340
1346
|
|
|
1341
1347
|
// src/client/build-info.ts
|
|
1342
|
-
var PLUGIN_VERSION = true ? "0.
|
|
1343
|
-
var BUILD_HASH = true ? "
|
|
1348
|
+
var PLUGIN_VERSION = true ? "0.11.0-beta.1" : "dev";
|
|
1349
|
+
var BUILD_HASH = true ? "d770983e" : "dev";
|
|
1344
1350
|
|
|
1345
1351
|
// src/client/locales.ts
|
|
1346
1352
|
var zh = {
|
package/lib/index.js
CHANGED
|
@@ -119,6 +119,7 @@ var RewindError = class extends Error {
|
|
|
119
119
|
}
|
|
120
120
|
code;
|
|
121
121
|
};
|
|
122
|
+
var REWIND_MARKER_SOURCE = Object.freeze({ kind: "plugin", plugin: "dsh-rewind" });
|
|
122
123
|
var CANDIDATE_PREVIEW_CHARS = 80;
|
|
123
124
|
var DEFAULT_CANDIDATE_LIMIT = 100;
|
|
124
125
|
function isUserMessageEvent(event) {
|
|
@@ -356,6 +357,13 @@ var SnapshotStore = class _SnapshotStore {
|
|
|
356
357
|
lastEntry = /* @__PURE__ */ new Map();
|
|
357
358
|
/** Sessions whose dedup state has been seeded from disk this process. */
|
|
358
359
|
seededSessions = /* @__PURE__ */ new Set();
|
|
360
|
+
/**
|
|
361
|
+
* Session-format version snapshots are anchored under, stamped into each
|
|
362
|
+
* session's `format` marker when an entry is recorded. `null` until the host
|
|
363
|
+
* sets it (from `agent/session-start`), so a session that never records is
|
|
364
|
+
* never materialized and a marker is only written where snapshots exist.
|
|
365
|
+
*/
|
|
366
|
+
formatVersion = null;
|
|
359
367
|
constructor(root, opts) {
|
|
360
368
|
this.dedup = opts?.dedup ?? true;
|
|
361
369
|
this.root = root ?? process.env[SNAPSHOT_ROOT_ENV] ?? join(resolveDshHome(opts?.dshHome), SNAPSHOT_DIR_NAME);
|
|
@@ -435,6 +443,9 @@ var SnapshotStore = class _SnapshotStore {
|
|
|
435
443
|
await writeJsonAtomic(file, committed, () => opts?.crash?.("after-temp-write"));
|
|
436
444
|
this.lastEntry.set(key, { content: entry.before, ref: selfRef });
|
|
437
445
|
}
|
|
446
|
+
if (this.formatVersion !== null) {
|
|
447
|
+
await this.markFormatVersion(sessionId, this.formatVersion);
|
|
448
|
+
}
|
|
438
449
|
const now = Date.now();
|
|
439
450
|
if (now - this.lastPruneAt >= _SnapshotStore.PRUNE_INTERVAL_MS) {
|
|
440
451
|
this.lastPruneAt = now;
|
|
@@ -631,6 +642,8 @@ var SnapshotStore = class _SnapshotStore {
|
|
|
631
642
|
}
|
|
632
643
|
/** Prefix of one restore-op journal file inside the session dir. */
|
|
633
644
|
static JOURNAL_PREFIX = "restore-journal-";
|
|
645
|
+
/** Session-format-version marker file inside the session dir. Non-`.json`, so it never counts as a checkpoint entry. */
|
|
646
|
+
static FORMAT_FILE = "format";
|
|
634
647
|
/** Absolute path of one restore-op journal file. */
|
|
635
648
|
journalPath(sessionId, opId) {
|
|
636
649
|
return join(this.sessionDir(sessionId), `${_SnapshotStore.JOURNAL_PREFIX}${safeFileId(opId)}.json`);
|
|
@@ -1275,6 +1288,75 @@ var SnapshotStore = class _SnapshotStore {
|
|
|
1275
1288
|
}
|
|
1276
1289
|
return { sessionId, ...stats, dryRun };
|
|
1277
1290
|
}
|
|
1291
|
+
/**
|
|
1292
|
+
* Read the session-format version marker recorded for a session, or `null`
|
|
1293
|
+
* when there is no marker — a pre-marker, legacy snapshot dir, or a session
|
|
1294
|
+
* that never materialized a dir.
|
|
1295
|
+
*/
|
|
1296
|
+
async readFormatVersion(sessionId) {
|
|
1297
|
+
try {
|
|
1298
|
+
const raw = await readFile(join(this.sessionDir(sessionId), _SnapshotStore.FORMAT_FILE), "utf8");
|
|
1299
|
+
const parsed = Number(raw.trim());
|
|
1300
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
1301
|
+
} catch {
|
|
1302
|
+
return null;
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
/**
|
|
1306
|
+
* Record the session-format version a session's snapshots are anchored under
|
|
1307
|
+
* (`session.header.version`: 2 for the v2 format, 3 for the v3 format). The
|
|
1308
|
+
* marker is a tiny non-`.json` file, so it never counts as a checkpoint
|
|
1309
|
+
* entry in `sessionStats`/`clearSession`. Written atomically (temp + rename)
|
|
1310
|
+
* like every other persisted marker, so a crash mid-write can only leave an
|
|
1311
|
+
* inert `format.tmp` — never a partial marker that a later reconcile could
|
|
1312
|
+
* misread as a version mismatch and wrongly clear.
|
|
1313
|
+
*/
|
|
1314
|
+
async markFormatVersion(sessionId, sessionVersion) {
|
|
1315
|
+
const dir = this.sessionDir(sessionId);
|
|
1316
|
+
await mkdir(dir, { recursive: true });
|
|
1317
|
+
const file = join(dir, _SnapshotStore.FORMAT_FILE);
|
|
1318
|
+
const tmp = `${file}.tmp`;
|
|
1319
|
+
await writeFile(tmp, `${sessionVersion}`, "utf8");
|
|
1320
|
+
await rename(tmp, file);
|
|
1321
|
+
}
|
|
1322
|
+
/**
|
|
1323
|
+
* Set the session-format version the store stamps onto every snapshot it
|
|
1324
|
+
* records. The host sets this once per process from `agent/session-start`
|
|
1325
|
+
* (`agent.session.header.version`), so a marker is only materialized for a
|
|
1326
|
+
* session that actually records a snapshot.
|
|
1327
|
+
*/
|
|
1328
|
+
setFormatVersion(sessionVersion) {
|
|
1329
|
+
this.formatVersion = sessionVersion;
|
|
1330
|
+
}
|
|
1331
|
+
/**
|
|
1332
|
+
* Session-format-version guard: clear a session's snapshot dir when the
|
|
1333
|
+
* format its snapshots were anchored under differs from the current session
|
|
1334
|
+
* format, so seq-anchored references can never survive a format migration
|
|
1335
|
+
* mis-mapped. Runs at `agent/session-start` — after DSH has migrated/loaded
|
|
1336
|
+
* the session, so `sessionVersion` is the post-migration value.
|
|
1337
|
+
*
|
|
1338
|
+
* Conservative rule (per the "delete stale snapshots" policy): a session
|
|
1339
|
+
* with no recorded marker but with snapshot content is treated as legacy and
|
|
1340
|
+
* cleared; a session whose marker differs from `sessionVersion` is cleared.
|
|
1341
|
+
* A matching version — or an untouched session with nothing to protect — is
|
|
1342
|
+
* left alone. The marker is re-stamped to the current version afterward so a
|
|
1343
|
+
* FUTURE format change is detected on the next start.
|
|
1344
|
+
*
|
|
1345
|
+
* @returns whether a session snapshot dir was cleared.
|
|
1346
|
+
*/
|
|
1347
|
+
async reconcileFormatVersion(sessionId, sessionVersion) {
|
|
1348
|
+
const stored = await this.readFormatVersion(sessionId);
|
|
1349
|
+
if (stored === sessionVersion) return { cleared: false };
|
|
1350
|
+
const stats = await this.sessionStats(sessionId);
|
|
1351
|
+
const hasContent = stats.anchorGroups > 0 || stats.journals > 0;
|
|
1352
|
+
if (hasContent) {
|
|
1353
|
+
await this.clearSession(sessionId);
|
|
1354
|
+
}
|
|
1355
|
+
if (hasContent || stored !== null) {
|
|
1356
|
+
await this.markFormatVersion(sessionId, sessionVersion);
|
|
1357
|
+
}
|
|
1358
|
+
return { cleared: hasContent };
|
|
1359
|
+
}
|
|
1278
1360
|
};
|
|
1279
1361
|
function hashPath(path) {
|
|
1280
1362
|
return createHash("sha256").update(path).digest("hex").slice(0, 8);
|
|
@@ -1511,7 +1593,6 @@ async function commitEntry(store, pending, anchorCache, trackedBySession, exec,
|
|
|
1511
1593
|
}
|
|
1512
1594
|
tracked.add(capture.path);
|
|
1513
1595
|
}
|
|
1514
|
-
var REWIND_MARKER_SOURCE = { kind: "plugin", plugin: "dsh-rewind" };
|
|
1515
1596
|
var REWIND_MARKER_CONTENT = [{ type: "text", text: "(empty message)" }];
|
|
1516
1597
|
function buildMarker() {
|
|
1517
1598
|
return createUserMessage({
|
|
@@ -1632,7 +1713,7 @@ async function executeRewind(ctx, store, fs, invocation, rawTarget, mode, inflig
|
|
|
1632
1713
|
let event;
|
|
1633
1714
|
try {
|
|
1634
1715
|
event = agent.session.append("user/message", marker, {
|
|
1635
|
-
surfaceOp: { op: "replace",
|
|
1716
|
+
surfaceOp: { op: "replace", startSeq: plan.surfaceStart, endSeq: plan.surfaceEnd },
|
|
1636
1717
|
sourceEventSeqs: [...plan.shadowedSeqs]
|
|
1637
1718
|
});
|
|
1638
1719
|
} catch (error) {
|
|
@@ -1874,6 +1955,21 @@ function apply(ctx, config) {
|
|
|
1874
1955
|
handler: (invocation) => handleSnapshotCleanup(store, invocation, dshHome, trackedBySession)
|
|
1875
1956
|
});
|
|
1876
1957
|
}, "dsh-rewind command");
|
|
1958
|
+
ctx.on("agent/session-start", ({ agent }) => {
|
|
1959
|
+
const session = agent.session;
|
|
1960
|
+
if (session.header.origin === "subagent" || (session.header.delegationDepth ?? 0) > 0) return;
|
|
1961
|
+
void (async () => {
|
|
1962
|
+
try {
|
|
1963
|
+
store.setFormatVersion(session.header.version);
|
|
1964
|
+
const result = await store.reconcileFormatVersion(session.id, session.header.version);
|
|
1965
|
+
if (result.cleared) {
|
|
1966
|
+
ctx.logger.warn(`[dsh-rewind] cleared snapshots for ${session.id}: session format changed (v${session.header.version})`);
|
|
1967
|
+
}
|
|
1968
|
+
} catch (error) {
|
|
1969
|
+
ctx.logger.warn(`[dsh-rewind] session-format reconcile failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
1970
|
+
}
|
|
1971
|
+
})();
|
|
1972
|
+
}, { global: true });
|
|
1877
1973
|
ctx.on("session/event", (session, event) => {
|
|
1878
1974
|
if (event.type !== "user/message") return;
|
|
1879
1975
|
const header = session.header;
|
|
@@ -20,6 +20,20 @@
|
|
|
20
20
|
* typed structurally (see `SlotsLike`) so the plugin never imports the
|
|
21
21
|
* conversation UI package's types and survives its version drift.
|
|
22
22
|
*
|
|
23
|
+
* COUPLING NOTE — HIGH. There is no harness interface for a per-user-message
|
|
24
|
+
* action (see docs/compat/audit.md): `MessageIconActions.extraActions` and the
|
|
25
|
+
* `conversation.chat.assistant-actions` slot are wired for assistant messages
|
|
26
|
+
* only, and no per-user-message action slot exists. So this portal targets
|
|
27
|
+
* undocumented internal structure — the `data-chat-flow-kind`,
|
|
28
|
+
* `data-chat-anchor-key`, `data-composer-input`, `data-composer-card`,
|
|
29
|
+
* `data-pending-steering` and `data-time-hover-root` attributes plus the
|
|
30
|
+
* `anchorSeq` field read in `client/hidden.ts`. Those are harness-internal and
|
|
31
|
+
* may change with the UI; this module (and `hidden.ts`) must be re-adapted to
|
|
32
|
+
* follow, and is the migration target when a first-class user-action slot or
|
|
33
|
+
* an official renderer hook surface appears. The coupling is accepted
|
|
34
|
+
* deliberately because a standards-conformant alternative does not exist
|
|
35
|
+
* today; it is not a defect to be removed while the DOM-portal approach stands.
|
|
36
|
+
*
|
|
23
37
|
* @module dsh-rewind/client/portals
|
|
24
38
|
*/
|
|
25
39
|
import { type ReactNode } from 'react';
|
|
@@ -23,4 +23,4 @@ export declare const CLASS: {
|
|
|
23
23
|
/** The ↶ glyph, drawn inline so the bundle stays dependency-free. */
|
|
24
24
|
export declare const REWIND_ICON_SVG: string;
|
|
25
25
|
/** One injected stylesheet (scoped under `.dsh-rewind-*`). */
|
|
26
|
-
export declare const STYLE = "\n.dsh-rewind-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n padding: 6px;\n border: none;\n border-radius: 28px;\n background: transparent;\n color: var(--dsw-alias-label-tertiary);\n cursor: pointer;\n}\n.dsh-rewind-btn:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n color: var(--dsw-alias-label-secondary);\n}\n\n.dsh-rewind-popover {\n position: fixed;\n z-index: 1000;\n width: 288px;\n padding: 12px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 12px;\n background: var(--dsw-specific-menu, var(--dsw-alias-bg-layer-3));\n box-shadow: var(--dsw-shadow-lv3);\n font-size: 14px;\n line-height: 20px;\n color: var(--dsw-alias-label-primary);\n}\n.dsh-rewind-popover-title {\n font-size: 14px;\n font-weight: 600;\n line-height: 20px;\n}\n.dsh-rewind-popover-target {\n margin: 4px 0 10px;\n font-size: 12px;\n line-height: 16px;\n color: var(--dsw-alias-label-tertiary);\n word-break: break-all;\n}\n.dsh-rewind-popover-option {\n display: flex;\n flex-direction: column;\n gap: 2px;\n width: 100%;\n margin: 0 0 6px;\n padding: 8px 10px;\n border: 1px solid transparent;\n border-radius: 8px;\n background: transparent;\n color: inherit;\n font: inherit;\n text-align: left;\n cursor: pointer;\n}\n.dsh-rewind-popover-option:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n.dsh-rewind-popover-option:disabled {\n opacity: 0.5;\n cursor: default;\n}\n.dsh-rewind-popover-option-label {\n font-weight: 500;\n}\n.dsh-rewind-popover-option-hint {\n font-size: 12px;\n line-height: 16px;\n color: var(--dsw-alias-label-tertiary);\n}\n.dsh-rewind-popover-impact {\n margin: 4px 0 10px;\n padding: 8px 10px;\n border-radius: 8px;\n background: var(--dsw-alias-interactive-bg-hover);\n font-size: 12px;\n line-height: 16px;\n color: var(--dsw-alias-label-secondary);\n white-space: pre-wrap;\n max-height: 160px;\n overflow: auto;\n}\n.dsh-rewind-popover-actions {\n display: flex;\n justify-content: flex-end;\n gap: 8px;\n}\n.dsh-rewind-popover-primary,\n.dsh-rewind-popover-ghost {\n padding: 5px 12px;\n border: none;\n border-radius: 8px;\n font: inherit;\n font-size: 13px;\n line-height: 18px;\n cursor: pointer;\n}\n.dsh-rewind-popover-primary {\n background: var(--dsw-alias-button-primary-fill);\n color: var(--dsw-alias-label-primary-foreground);\n}\n.dsh-rewind-popover-primary:hover:not(:disabled) {\n background: var(--dsw-alias-button-primary-hover);\n}\n.dsh-rewind-popover-primary:disabled {\n opacity: 0.5;\n cursor: default;\n}\n.dsh-rewind-popover-ghost {\n background: transparent;\n color: var(--dsw-alias-label-secondary);\n}\n.dsh-rewind-popover-ghost:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.dsh-rewind-guard-hint {\n position: fixed;\n z-index: 1000;\n max-width: min(440px, calc(100vw - 24px));\n padding: 8px 12px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 10px;\n background: var(--dsw-specific-menu, var(--dsw-alias-bg-layer-3));\n box-shadow: var(--dsw-shadow-lv3);\n font-size: 13px;\n line-height: 18px;\n color: var(--dsw-alias-label-primary);\n pointer-events: none;\n}\n\n/* ---- Snapshot-cleanup settings card (mirrors the harness PluginCard look) ---- */\n/* Standardized on the 0.1.2 harness card look (border-radius 16px + 0.5px\n border-l4, 0.5px internal separators). */\n.dsh-rewind-cleanup-card {\n list-style: none;\n border: 0.5px solid var(--dsw-alias-border-l4);\n border-radius: 16px;\n background: var(--dsw-alias-bg-layer-3);\n transition: border-color .16s, background .16s;\n}\n.dsh-rewind-cleanup-card:hover {\n border-color: var(--dsw-alias-label-dimmed);\n}\n.dsh-rewind-cleanup-card-open {\n background: var(--dsw-alias-bg-layer-2);\n border-color: var(--dsw-alias-label-dimmed);\n}\n.dsh-rewind-cleanup-header {\n width: 100%;\n appearance: none;\n border: 0;\n background: none;\n font: inherit;\n color: inherit;\n text-align: left;\n cursor: pointer;\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 14px 16px;\n border-radius: 12px;\n}\n.dsh-rewind-cleanup-header:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: -2px;\n}\n.dsh-rewind-cleanup-head-text {\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n.dsh-rewind-cleanup-name {\n font-size: 15px;\n font-weight: 600;\n line-height: 1.4;\n color: var(--dsw-alias-label-primary);\n}\n.dsh-rewind-cleanup-desc {\n font-size: 13px;\n line-height: 1.5;\n color: var(--dsw-alias-label-tertiary);\n}\n.dsh-rewind-cleanup-chevron {\n flex: none;\n color: var(--dsw-alias-label-tertiary);\n transition: transform .16s;\n}\n.dsh-rewind-cleanup-chevron-open {\n transform: rotate(180deg);\n}\n.dsh-rewind-cleanup-pending {\n flex: none;\n border-radius: 999px;\n padding: 1px 8px;\n font-size: 11px;\n line-height: 17px;\n font-weight: 500;\n white-space: nowrap;\n background: var(--dsw-alias-bg-module-platform);\n color: var(--dsw-alias-label-secondary);\n}\n.dsh-rewind-cleanup-body {\n border-top: 0.5px solid var(--dsw-alias-border-l2);\n margin: 0 16px;\n padding: 4px 0 8px;\n}\n.dsh-rewind-cleanup-readonly {\n margin: 12px 0 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-tertiary);\n}\n.dsh-rewind-cleanup-permission {\n display: grid;\n gap: 6px;\n padding: 12px 0;\n}\n.dsh-rewind-cleanup-field {\n display: flex;\n flex-direction: column;\n gap: 6px;\n padding: 12px 0;\n}\n.dsh-rewind-cleanup-field + .dsh-rewind-cleanup-field {\n border-top: 0.5px solid var(--dsw-alias-border-l2);\n}\n.dsh-rewind-cleanup-head {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n.dsh-rewind-cleanup-label {\n flex: 1;\n min-width: 0;\n font-size: 13px;\n font-weight: 500;\n line-height: 1.5;\n color: var(--dsw-alias-label-primary);\n}\n.dsh-rewind-cleanup-hint {\n margin: 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-tertiary);\n}\n.dsh-rewind-cleanup-error {\n margin: 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-error);\n}\n/* Switch row: label left, role=switch button right, hint below (Subagent module).\n * The track mirrors the harness ui-primitives Switch (0.1.3-alpha.2 line): the\n * on/off appearance keys off aria-checked rather than a parallel class, so the\n * visual state cannot disagree with the state assistive technology reads, and\n * corner-shape: round opts the capsule track out of the global superellipse\n * (which would square the capsule ends off against the round thumb inside). */\n.dsh-rewind-cleanup-toggle-row {\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n gap: 16px;\n font-size: 13px;\n line-height: 1.5;\n color: var(--dsw-alias-label-primary);\n}\n.dsh-rewind-cleanup-toggle-label {\n flex: 1;\n min-width: 0;\n}\n.dsh-rewind-cleanup-switch {\n box-sizing: border-box;\n position: relative;\n flex: 0 0 auto;\n width: 36px;\n height: 20px;\n padding: 2px;\n border: 0;\n border-radius: 10px;\n corner-shape: round;\n background: var(--dsw-alias-border-l3);\n cursor: pointer;\n}\n.dsh-rewind-cleanup-switch[aria-checked='true'] {\n background: var(--dsw-alias-brand-primary);\n}\n.dsh-rewind-cleanup-switch:disabled {\n cursor: default;\n opacity: 0.5;\n}\n.dsh-rewind-cleanup-switch:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 2px;\n}\n.dsh-rewind-cleanup-thumb {\n display: block;\n width: 16px;\n height: 16px;\n border-radius: 50%;\n corner-shape: round;\n background: var(--dsw-alias-label-primary-foreground);\n transition: transform 120ms ease;\n}\n.dsh-rewind-cleanup-switch[aria-checked='true'] .dsh-rewind-cleanup-thumb {\n transform: translateX(16px);\n}\n.dsh-rewind-cleanup-input {\n box-sizing: border-box;\n height: 34px;\n padding: 0 12px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 8px;\n background: var(--dsw-alias-bg-layer-3);\n font: inherit;\n font-size: 13px;\n line-height: 1.5;\n color: var(--dsw-alias-label-primary);\n}\n.dsh-rewind-cleanup-input:focus-visible {\n outline: none;\n border-color: var(--dsw-alias-brand-primary);\n}\n.dsh-rewind-cleanup-input:disabled {\n color: var(--dsw-alias-label-tertiary);\n cursor: default;\n}\n.dsh-rewind-cleanup-input-invalid {\n border-color: var(--dsw-alias-label-error);\n}\n.dsh-rewind-cleanup-footer {\n display: flex;\n align-items: center;\n justify-content: flex-end;\n gap: 8px;\n padding: 12px 0 4px;\n border-top: 1px solid var(--dsw-alias-border-l2);\n}\n.dsh-rewind-cleanup-failed {\n flex: 1;\n min-width: 0;\n margin: 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-error);\n}\n.dsh-rewind-cleanup-discard,\n.dsh-rewind-cleanup-save {\n appearance: none;\n border: 1px solid transparent;\n border-radius: 8px;\n padding: 5px 14px;\n font: inherit;\n font-size: 13px;\n line-height: 1.5;\n cursor: pointer;\n}\n.dsh-rewind-cleanup-discard {\n border-color: var(--dsw-alias-border-l2);\n background: none;\n color: var(--dsw-alias-label-secondary);\n}\n.dsh-rewind-cleanup-discard:hover:not(:disabled) {\n color: var(--dsw-alias-label-primary);\n border-color: var(--dsw-alias-label-dimmed);\n}\n.dsh-rewind-cleanup-save {\n background: var(--dsw-alias-label-primary);\n color: var(--dsw-alias-bg-layer-3);\n}\n.dsh-rewind-cleanup-discard:disabled,\n.dsh-rewind-cleanup-save:disabled {\n opacity: 0.4;\n cursor: default;\n}\n.dsh-rewind-cleanup-discard:focus-visible,\n.dsh-rewind-cleanup-save:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n";
|
|
26
|
+
export declare const STYLE = "\n.dsh-rewind-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n padding: 6px;\n border: none;\n border-radius: 28px;\n background: transparent;\n color: var(--dsw-alias-label-tertiary);\n cursor: pointer;\n}\n.dsh-rewind-btn:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n color: var(--dsw-alias-label-secondary);\n}\n\n.dsh-rewind-popover {\n position: fixed;\n z-index: 1000;\n width: 288px;\n padding: 12px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 12px;\n background: var(--dsw-specific-menu, var(--dsw-alias-bg-layer-3));\n box-shadow: var(--dsw-shadow-lv3);\n font-size: 14px;\n line-height: 20px;\n color: var(--dsw-alias-label-primary);\n}\n.dsh-rewind-popover-title {\n font-size: 14px;\n font-weight: 600;\n line-height: 20px;\n}\n.dsh-rewind-popover-target {\n margin: 4px 0 10px;\n font-size: 12px;\n line-height: 16px;\n color: var(--dsw-alias-label-tertiary);\n word-break: break-all;\n}\n.dsh-rewind-popover-option {\n display: flex;\n flex-direction: column;\n gap: 2px;\n width: 100%;\n margin: 0 0 6px;\n padding: 8px 10px;\n border: 1px solid transparent;\n border-radius: 8px;\n background: transparent;\n color: inherit;\n font: inherit;\n text-align: left;\n cursor: pointer;\n}\n.dsh-rewind-popover-option:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n.dsh-rewind-popover-option:disabled {\n opacity: 0.5;\n cursor: default;\n}\n.dsh-rewind-popover-option-label {\n font-weight: 500;\n}\n.dsh-rewind-popover-option-hint {\n font-size: 12px;\n line-height: 16px;\n color: var(--dsw-alias-label-tertiary);\n}\n.dsh-rewind-popover-impact {\n margin: 4px 0 10px;\n padding: 8px 10px;\n border-radius: 8px;\n background: var(--dsw-alias-interactive-bg-hover);\n font-size: 12px;\n line-height: 16px;\n color: var(--dsw-alias-label-secondary);\n white-space: pre-wrap;\n max-height: 160px;\n overflow: auto;\n}\n.dsh-rewind-popover-actions {\n display: flex;\n justify-content: flex-end;\n gap: 8px;\n}\n.dsh-rewind-popover-primary,\n.dsh-rewind-popover-ghost {\n padding: 5px 12px;\n border: none;\n border-radius: 8px;\n font: inherit;\n font-size: 13px;\n line-height: 18px;\n cursor: pointer;\n}\n.dsh-rewind-popover-primary {\n background: var(--dsw-alias-button-primary-fill);\n color: var(--dsw-alias-label-primary-foreground);\n}\n.dsh-rewind-popover-primary:hover:not(:disabled) {\n background: var(--dsw-alias-button-primary-hover);\n}\n.dsh-rewind-popover-primary:disabled {\n opacity: 0.5;\n cursor: default;\n}\n.dsh-rewind-popover-ghost {\n background: transparent;\n color: var(--dsw-alias-label-secondary);\n}\n.dsh-rewind-popover-ghost:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.dsh-rewind-guard-hint {\n position: fixed;\n z-index: 1000;\n max-width: min(440px, calc(100vw - 24px));\n padding: 8px 12px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 10px;\n background: var(--dsw-specific-menu, var(--dsw-alias-bg-layer-3));\n box-shadow: var(--dsw-shadow-lv3);\n font-size: 13px;\n line-height: 18px;\n color: var(--dsw-alias-label-primary);\n pointer-events: none;\n}\n\n/* ---- Snapshot-cleanup settings card (mirrors the harness PluginCard look) ---- */\n/* Standardized on the 0.1.2 harness card look (border-radius 16px + 0.5px\n border-l4, 0.5px internal separators). */\n.dsh-rewind-cleanup-card {\n list-style: none;\n border: 0.5px solid var(--dsw-alias-border-l4);\n border-radius: 16px;\n background: var(--dsw-alias-bg-layer-3);\n transition: border-color .16s, background .16s;\n}\n.dsh-rewind-cleanup-card:hover {\n border-color: var(--dsw-alias-label-dimmed);\n}\n.dsh-rewind-cleanup-card-open {\n background: var(--dsw-alias-bg-layer-2);\n border-color: var(--dsw-alias-label-dimmed);\n}\n.dsh-rewind-cleanup-header {\n width: 100%;\n appearance: none;\n border: 0;\n background: none;\n font: inherit;\n color: inherit;\n text-align: left;\n cursor: pointer;\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 14px 16px;\n border-radius: 12px;\n}\n.dsh-rewind-cleanup-header:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: -2px;\n}\n.dsh-rewind-cleanup-head-text {\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n.dsh-rewind-cleanup-name {\n font-size: 15px;\n font-weight: 600;\n line-height: 1.4;\n color: var(--dsw-alias-label-primary);\n}\n.dsh-rewind-cleanup-desc {\n font-size: 13px;\n line-height: 1.5;\n color: var(--dsw-alias-label-tertiary);\n}\n.dsh-rewind-cleanup-chevron {\n flex: none;\n color: var(--dsw-alias-label-tertiary);\n transition: transform .16s;\n}\n.dsh-rewind-cleanup-chevron-open {\n transform: rotate(180deg);\n}\n.dsh-rewind-cleanup-pending {\n flex: none;\n border-radius: 999px;\n padding: 1px 8px;\n font-size: 11px;\n line-height: 17px;\n font-weight: 500;\n white-space: nowrap;\n background: var(--dsw-alias-bg-module-platform);\n color: var(--dsw-alias-label-secondary);\n}\n.dsh-rewind-cleanup-body {\n border-top: 0.5px solid var(--dsw-alias-border-l2);\n margin: 0 16px;\n padding: 0 0 8px;\n}\n.dsh-rewind-cleanup-readonly {\n margin: 12px 0 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-tertiary);\n}\n.dsh-rewind-cleanup-permission {\n display: grid;\n gap: 6px;\n padding: 12px 0;\n}\n.dsh-rewind-cleanup-field {\n display: flex;\n flex-direction: column;\n gap: 6px;\n padding: 12px 0;\n}\n.dsh-rewind-cleanup-field + .dsh-rewind-cleanup-field {\n border-top: 0.5px solid var(--dsw-alias-border-l2);\n}\n/* Auto-cleanup (permission) block and the max-age field are consecutive control\n * sections of the open card \u2014 give them the same 0.5px divider the harness gives\n * every pair of config fields (fields.module.css field + field). */\n.dsh-rewind-cleanup-permission + .dsh-rewind-cleanup-field {\n border-top: 0.5px solid var(--dsw-alias-border-l2);\n}\n.dsh-rewind-cleanup-head {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n.dsh-rewind-cleanup-label {\n flex: 1;\n min-width: 0;\n font-size: 13px;\n font-weight: 500;\n line-height: 1.5;\n color: var(--dsw-alias-label-primary);\n}\n.dsh-rewind-cleanup-hint {\n margin: 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-tertiary);\n}\n.dsh-rewind-cleanup-error {\n margin: 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-error);\n}\n/* Switch row: label left, role=switch button right, hint below (Subagent module).\n * The track mirrors the harness ui-primitives Switch (0.1.3-alpha.2 line): the\n * on/off appearance keys off aria-checked rather than a parallel class, so the\n * visual state cannot disagree with the state assistive technology reads, and\n * corner-shape: round opts the capsule track out of the global superellipse\n * (which would square the capsule ends off against the round thumb inside). */\n.dsh-rewind-cleanup-toggle-row {\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n gap: 16px;\n font-size: 13px;\n line-height: 1.5;\n color: var(--dsw-alias-label-primary);\n}\n.dsh-rewind-cleanup-toggle-label {\n flex: 1;\n min-width: 0;\n}\n.dsh-rewind-cleanup-switch {\n box-sizing: border-box;\n position: relative;\n flex: 0 0 auto;\n width: 36px;\n height: 20px;\n padding: 2px;\n border: 0;\n border-radius: 10px;\n corner-shape: round;\n background: var(--dsw-alias-border-l3);\n cursor: pointer;\n}\n.dsh-rewind-cleanup-switch[aria-checked='true'] {\n background: var(--dsw-alias-brand-primary);\n}\n.dsh-rewind-cleanup-switch:disabled {\n cursor: default;\n opacity: 0.5;\n}\n.dsh-rewind-cleanup-switch:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 2px;\n}\n.dsh-rewind-cleanup-thumb {\n display: block;\n width: 16px;\n height: 16px;\n border-radius: 50%;\n corner-shape: round;\n background: var(--dsw-alias-label-primary-foreground);\n transition: transform 120ms ease;\n}\n.dsh-rewind-cleanup-switch[aria-checked='true'] .dsh-rewind-cleanup-thumb {\n transform: translateX(16px);\n}\n.dsh-rewind-cleanup-input {\n box-sizing: border-box;\n height: 34px;\n padding: 0 12px;\n border: 0.5px solid var(--dsw-alias-border-l4);\n border-radius: 8px;\n background: var(--dsw-alias-bg-layer-3);\n font: inherit;\n font-size: 13px;\n line-height: 1.5;\n color: var(--dsw-alias-label-primary);\n}\n.dsh-rewind-cleanup-input:focus-visible {\n outline: none;\n border-color: var(--dsw-alias-brand-primary);\n}\n.dsh-rewind-cleanup-input:disabled {\n color: var(--dsw-alias-label-tertiary);\n cursor: default;\n}\n.dsh-rewind-cleanup-input-invalid {\n border-color: var(--dsw-alias-label-error);\n}\n.dsh-rewind-cleanup-footer {\n display: flex;\n align-items: center;\n justify-content: flex-end;\n gap: 8px;\n padding: 12px 0 4px;\n border-top: 0.5px solid var(--dsw-alias-border-l2);\n}\n.dsh-rewind-cleanup-failed {\n flex: 1;\n min-width: 0;\n margin: 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-error);\n}\n.dsh-rewind-cleanup-discard,\n.dsh-rewind-cleanup-save {\n appearance: none;\n border: 1px solid transparent;\n border-radius: 8px;\n padding: 5px 14px;\n font: inherit;\n font-size: 13px;\n line-height: 1.5;\n cursor: pointer;\n}\n.dsh-rewind-cleanup-discard {\n border-color: var(--dsw-alias-border-l2);\n background: none;\n color: var(--dsw-alias-label-secondary);\n}\n.dsh-rewind-cleanup-discard:hover:not(:disabled) {\n color: var(--dsw-alias-label-primary);\n border-color: var(--dsw-alias-label-dimmed);\n}\n.dsh-rewind-cleanup-save {\n background: var(--dsw-alias-label-primary);\n color: var(--dsw-alias-bg-layer-3);\n}\n.dsh-rewind-cleanup-discard:disabled,\n.dsh-rewind-cleanup-save:disabled {\n opacity: 0.4;\n cursor: default;\n}\n.dsh-rewind-cleanup-discard:focus-visible,\n.dsh-rewind-cleanup-save:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n";
|
package/lib/types/locales.d.ts
CHANGED
|
@@ -15,6 +15,11 @@
|
|
|
15
15
|
* renders through machine tokens (`impact=<n>`, `args` @seq), never by parsing
|
|
16
16
|
* host prose.
|
|
17
17
|
*
|
|
18
|
+
* DSH localizes only its OWN command descriptions via the client locale — a
|
|
19
|
+
* closed `HOST_DESCRIPTION_KEYS` allowlist; third-party plugin descriptions are
|
|
20
|
+
* not in that channel (see docs/compat/audit.md, RU-I18N), so this plugin's host
|
|
21
|
+
* command copy stays in the host's tongue (English by default).
|
|
22
|
+
*
|
|
18
23
|
* English is the key-set source of truth; zh is checked complete against it.
|
|
19
24
|
*
|
|
20
25
|
* @module dsh-rewind/locales
|
package/lib/types/rewind.d.ts
CHANGED
|
@@ -9,10 +9,10 @@
|
|
|
9
9
|
* transcript) is untouched; only the model-visible surface is cut, so the
|
|
10
10
|
* next request derives its context from the target message onward.
|
|
11
11
|
*
|
|
12
|
-
* Marker shape (v0.1.
|
|
12
|
+
* Marker shape (v0.1.5/v3): the marker is a `user/message` carrying a replace
|
|
13
13
|
* `surfaceOp` — a single event:
|
|
14
14
|
*
|
|
15
|
-
* user/message (marker content) → { surfaceOp {replace,
|
|
15
|
+
* user/message (marker content) → { surfaceOp {replace, startSeq, endSeq} }
|
|
16
16
|
*
|
|
17
17
|
* v2 reserves surface `replace` to a node that cites every shadowed seq via
|
|
18
18
|
* `sourceEventSeqs`, and `assistant/message` can no longer carry
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
*
|
|
27
27
|
* @module dsh-rewind/rewind
|
|
28
28
|
*/
|
|
29
|
+
import type { MessageSource } from '@deepseek-ai/dsh-llm/message';
|
|
29
30
|
import type { SessionEvent, UserMessage } from '@deepseek-ai/dsh-session';
|
|
30
31
|
/** Which of the two rewind modes a rewind executes. */
|
|
31
32
|
export type RewindMode = 'chat' | 'both';
|
|
@@ -73,6 +74,25 @@ export interface RewindPlan {
|
|
|
73
74
|
/** Last surface node — the replace range end (inclusive). */
|
|
74
75
|
readonly surfaceEnd: number;
|
|
75
76
|
}
|
|
77
|
+
/**
|
|
78
|
+
* The rewind-marker source: the backend-independent identity carried by every
|
|
79
|
+
* marker the plugin appends. It is the plain third-party plugin source shape
|
|
80
|
+
* `{ kind: 'plugin', plugin: 'dsh-rewind' }` — no extra fields, because a
|
|
81
|
+
* plugin source is a CLOSED shape in the harness (only `kind`/`plugin`, plus
|
|
82
|
+
* the context-injection `form`/`sections`/`summary`; see `MessageSourceMap` and
|
|
83
|
+
* the released-format source validator). A plugin extends the source map by
|
|
84
|
+
* adding a new `kind`, never by hanging private fields off `plugin`.
|
|
85
|
+
*/
|
|
86
|
+
export declare const REWIND_MARKER_SOURCE: Readonly<{
|
|
87
|
+
readonly kind: 'plugin';
|
|
88
|
+
readonly plugin: 'dsh-rewind';
|
|
89
|
+
}>;
|
|
90
|
+
/**
|
|
91
|
+
* Test whether a persisted message source identifies a rewind marker.
|
|
92
|
+
* @param source - source restored from a surface user message.
|
|
93
|
+
* @returns whether the source carries the backend-independent rewind brand.
|
|
94
|
+
*/
|
|
95
|
+
export declare function isRewindMarker(source: MessageSource): boolean;
|
|
76
96
|
/** Preview length cap for candidate listings. */
|
|
77
97
|
export declare const CANDIDATE_PREVIEW_CHARS = 80;
|
|
78
98
|
/**
|
package/lib/types/snapshot.d.ts
CHANGED
|
@@ -317,6 +317,13 @@ export declare class SnapshotStore {
|
|
|
317
317
|
private readonly lastEntry;
|
|
318
318
|
/** Sessions whose dedup state has been seeded from disk this process. */
|
|
319
319
|
private readonly seededSessions;
|
|
320
|
+
/**
|
|
321
|
+
* Session-format version snapshots are anchored under, stamped into each
|
|
322
|
+
* session's `format` marker when an entry is recorded. `null` until the host
|
|
323
|
+
* sets it (from `agent/session-start`), so a session that never records is
|
|
324
|
+
* never materialized and a marker is only written where snapshots exist.
|
|
325
|
+
*/
|
|
326
|
+
private formatVersion;
|
|
320
327
|
constructor(root?: string, opts?: {
|
|
321
328
|
readonly dedup?: boolean;
|
|
322
329
|
readonly dshHome?: string;
|
|
@@ -423,6 +430,8 @@ export declare class SnapshotStore {
|
|
|
423
430
|
restoreAfter(sessionId: string, targetSeq: number, deleteFile: DeleteFile, probe?: DiskProbe, opts?: RestoreRunOptions): Promise<RestoreOutcome>;
|
|
424
431
|
/** Prefix of one restore-op journal file inside the session dir. */
|
|
425
432
|
private static readonly JOURNAL_PREFIX;
|
|
433
|
+
/** Session-format-version marker file inside the session dir. Non-`.json`, so it never counts as a checkpoint entry. */
|
|
434
|
+
private static readonly FORMAT_FILE;
|
|
426
435
|
/** Absolute path of one restore-op journal file. */
|
|
427
436
|
private journalPath;
|
|
428
437
|
/**
|
|
@@ -622,6 +631,48 @@ export declare class SnapshotStore {
|
|
|
622
631
|
clearSession(sessionId: string, opts?: {
|
|
623
632
|
readonly dryRun?: boolean;
|
|
624
633
|
}): Promise<ClearSessionReport>;
|
|
634
|
+
/**
|
|
635
|
+
* Read the session-format version marker recorded for a session, or `null`
|
|
636
|
+
* when there is no marker — a pre-marker, legacy snapshot dir, or a session
|
|
637
|
+
* that never materialized a dir.
|
|
638
|
+
*/
|
|
639
|
+
private readFormatVersion;
|
|
640
|
+
/**
|
|
641
|
+
* Record the session-format version a session's snapshots are anchored under
|
|
642
|
+
* (`session.header.version`: 2 for the v2 format, 3 for the v3 format). The
|
|
643
|
+
* marker is a tiny non-`.json` file, so it never counts as a checkpoint
|
|
644
|
+
* entry in `sessionStats`/`clearSession`. Written atomically (temp + rename)
|
|
645
|
+
* like every other persisted marker, so a crash mid-write can only leave an
|
|
646
|
+
* inert `format.tmp` — never a partial marker that a later reconcile could
|
|
647
|
+
* misread as a version mismatch and wrongly clear.
|
|
648
|
+
*/
|
|
649
|
+
markFormatVersion(sessionId: string, sessionVersion: number): Promise<void>;
|
|
650
|
+
/**
|
|
651
|
+
* Set the session-format version the store stamps onto every snapshot it
|
|
652
|
+
* records. The host sets this once per process from `agent/session-start`
|
|
653
|
+
* (`agent.session.header.version`), so a marker is only materialized for a
|
|
654
|
+
* session that actually records a snapshot.
|
|
655
|
+
*/
|
|
656
|
+
setFormatVersion(sessionVersion: number): void;
|
|
657
|
+
/**
|
|
658
|
+
* Session-format-version guard: clear a session's snapshot dir when the
|
|
659
|
+
* format its snapshots were anchored under differs from the current session
|
|
660
|
+
* format, so seq-anchored references can never survive a format migration
|
|
661
|
+
* mis-mapped. Runs at `agent/session-start` — after DSH has migrated/loaded
|
|
662
|
+
* the session, so `sessionVersion` is the post-migration value.
|
|
663
|
+
*
|
|
664
|
+
* Conservative rule (per the "delete stale snapshots" policy): a session
|
|
665
|
+
* with no recorded marker but with snapshot content is treated as legacy and
|
|
666
|
+
* cleared; a session whose marker differs from `sessionVersion` is cleared.
|
|
667
|
+
* A matching version — or an untouched session with nothing to protect — is
|
|
668
|
+
* left alone. The marker is re-stamped to the current version afterward so a
|
|
669
|
+
* FUTURE format change is detected on the next start.
|
|
670
|
+
*
|
|
671
|
+
* @returns whether a session snapshot dir was cleared.
|
|
672
|
+
*/
|
|
673
|
+
reconcileFormatVersion(sessionId: string, sessionVersion: number): Promise<{
|
|
674
|
+
cleared: boolean;
|
|
675
|
+
}>;
|
|
625
676
|
}
|
|
626
677
|
/**
|
|
627
678
|
* Re-check every tracked file at a user-message boundary and record the
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-rewind-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0-beta.1",
|
|
4
4
|
"description": "DSH 插件:真正便捷无感的同窗口内对话回退,从不新建分支;自带轻量工作区备份,可一并还原文件(完整 Claude Code /rewind 语义)。 · DSH plugin: genuinely effortless in-window conversation rewind — never forking a new session; ships a lightweight workspace backup that restores files together with the rewind (full Claude Code /rewind semantics).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"deepseek-harness",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"patch": "./cordis.patch.yml"
|
|
51
51
|
},
|
|
52
52
|
"engines": {
|
|
53
|
-
"dsh": ">=0.1.
|
|
53
|
+
"dsh": ">=0.1.5-rc.1"
|
|
54
54
|
},
|
|
55
55
|
"client": {
|
|
56
56
|
"inject": [
|
|
@@ -76,18 +76,18 @@
|
|
|
76
76
|
"peerDependencies": {
|
|
77
77
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
78
78
|
"@deepseek-ai/schemastery": "^3.18.1",
|
|
79
|
-
"@deepseek-ai/dsh-client-locale": "^0.1.
|
|
80
|
-
"@deepseek-ai/dsh-client-ui-commands": "^0.1.
|
|
81
|
-
"@deepseek-ai/dsh-client-ui-settings": "^0.1.
|
|
82
|
-
"@deepseek-ai/dsh-client-ui-slots": "^0.1.
|
|
83
|
-
"@deepseek-ai/dsh-commands": "^0.1.
|
|
84
|
-
"@deepseek-ai/dsh-fs": "^0.1.
|
|
85
|
-
"@deepseek-ai/dsh-home-paths": "^0.1.
|
|
86
|
-
"@deepseek-ai/dsh-llm": "^0.1.
|
|
87
|
-
"@deepseek-ai/dsh-sandbox": "^0.1.
|
|
88
|
-
"@deepseek-ai/dsh-session": "^0.1.
|
|
89
|
-
"@deepseek-ai/dsh-settings": "^0.1.
|
|
90
|
-
"@deepseek-ai/dsh-tools": "^0.1.
|
|
79
|
+
"@deepseek-ai/dsh-client-locale": "^0.1.5-rc.1",
|
|
80
|
+
"@deepseek-ai/dsh-client-ui-commands": "^0.1.5-rc.1",
|
|
81
|
+
"@deepseek-ai/dsh-client-ui-settings": "^0.1.5-rc.1",
|
|
82
|
+
"@deepseek-ai/dsh-client-ui-slots": "^0.1.5-rc.1",
|
|
83
|
+
"@deepseek-ai/dsh-commands": "^0.1.5-rc.1",
|
|
84
|
+
"@deepseek-ai/dsh-fs": "^0.1.5-rc.1",
|
|
85
|
+
"@deepseek-ai/dsh-home-paths": "^0.1.5-rc.1",
|
|
86
|
+
"@deepseek-ai/dsh-llm": "^0.1.5-rc.1",
|
|
87
|
+
"@deepseek-ai/dsh-sandbox": "^0.1.5-rc.1",
|
|
88
|
+
"@deepseek-ai/dsh-session": "^0.1.5-rc.1",
|
|
89
|
+
"@deepseek-ai/dsh-settings": "^0.1.5-rc.1",
|
|
90
|
+
"@deepseek-ai/dsh-tools": "^0.1.5-rc.1"
|
|
91
91
|
},
|
|
92
92
|
"peerDependenciesMeta": {
|
|
93
93
|
"@deepseek-ai/cordis": {
|
|
@@ -136,37 +136,37 @@
|
|
|
136
136
|
"devDependencies": {
|
|
137
137
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
138
138
|
"@deepseek-ai/schemastery": "^3.18.1",
|
|
139
|
-
"@deepseek-ai/dsh-agent": "^0.1.
|
|
140
|
-
"@deepseek-ai/dsh-attachment": "^0.1.
|
|
141
|
-
"@deepseek-ai/dsh-client-locale": "^0.1.
|
|
142
|
-
"@deepseek-ai/dsh-client-connection": "^0.1.
|
|
143
|
-
"@deepseek-ai/dsh-client-store": "^0.1.
|
|
144
|
-
"@deepseek-ai/dsh-client-ui-primitives": "^0.1.
|
|
145
|
-
"@deepseek-ai/dsh-api-session-controller": "^0.1.
|
|
146
|
-
"@deepseek-ai/dsh-client-ui-conversation": "^0.1.
|
|
147
|
-
"@deepseek-ai/dsh-scope": "^0.1.
|
|
148
|
-
"@deepseek-ai/dsh-typert-protocol": "^0.1.
|
|
149
|
-
"@deepseek-ai/dsh-user-questions": "^0.1.
|
|
150
|
-
"@deepseek-ai/dsh-client-ui-commands": "^0.1.
|
|
151
|
-
"@deepseek-ai/dsh-client-ui-input-trigger": "^0.1.
|
|
152
|
-
"@deepseek-ai/dsh-client-ui-slots": "^0.1.
|
|
153
|
-
"@deepseek-ai/dsh-command-compact": "^0.1.
|
|
154
|
-
"@deepseek-ai/dsh-commands": "^0.1.
|
|
155
|
-
"@deepseek-ai/dsh-compaction": "^0.1.
|
|
156
|
-
"@deepseek-ai/dsh-compaction-basic": "^0.1.
|
|
157
|
-
"@deepseek-ai/dsh-fs": "^0.1.
|
|
158
|
-
"@deepseek-ai/dsh-goal": "^0.1.
|
|
159
|
-
"@deepseek-ai/dsh-home-paths": "^0.1.
|
|
160
|
-
"@deepseek-ai/dsh-llm": "^0.1.
|
|
161
|
-
"@deepseek-ai/dsh-plan-mode": "^0.1.
|
|
162
|
-
"@deepseek-ai/dsh-sandbox": "^0.1.
|
|
163
|
-
"@deepseek-ai/dsh-session": "^0.1.
|
|
164
|
-
"@deepseek-ai/dsh-session-projection": "^0.1.
|
|
165
|
-
"@deepseek-ai/dsh-session-stats": "^0.1.
|
|
166
|
-
"@deepseek-ai/dsh-session-title": "^0.1.
|
|
167
|
-
"@deepseek-ai/dsh-settings": "^0.1.
|
|
168
|
-
"@deepseek-ai/dsh-token-meter": "^0.1.
|
|
169
|
-
"@deepseek-ai/dsh-tools": "^0.1.
|
|
139
|
+
"@deepseek-ai/dsh-agent": "^0.1.5-rc.1",
|
|
140
|
+
"@deepseek-ai/dsh-attachment": "^0.1.5-rc.1",
|
|
141
|
+
"@deepseek-ai/dsh-client-locale": "^0.1.5-rc.1",
|
|
142
|
+
"@deepseek-ai/dsh-client-connection": "^0.1.5-rc.1",
|
|
143
|
+
"@deepseek-ai/dsh-client-store": "^0.1.5-rc.1",
|
|
144
|
+
"@deepseek-ai/dsh-client-ui-primitives": "^0.1.5-rc.1",
|
|
145
|
+
"@deepseek-ai/dsh-api-session-controller": "^0.1.5-rc.1",
|
|
146
|
+
"@deepseek-ai/dsh-client-ui-conversation": "^0.1.5-rc.1",
|
|
147
|
+
"@deepseek-ai/dsh-scope": "^0.1.5-rc.1",
|
|
148
|
+
"@deepseek-ai/dsh-typert-protocol": "^0.1.5-rc.1",
|
|
149
|
+
"@deepseek-ai/dsh-user-questions": "^0.1.5-rc.1",
|
|
150
|
+
"@deepseek-ai/dsh-client-ui-commands": "^0.1.5-rc.1",
|
|
151
|
+
"@deepseek-ai/dsh-client-ui-input-trigger": "^0.1.5-rc.1",
|
|
152
|
+
"@deepseek-ai/dsh-client-ui-slots": "^0.1.5-rc.1",
|
|
153
|
+
"@deepseek-ai/dsh-command-compact": "^0.1.5-rc.1",
|
|
154
|
+
"@deepseek-ai/dsh-commands": "^0.1.5-rc.1",
|
|
155
|
+
"@deepseek-ai/dsh-compaction": "^0.1.5-rc.1",
|
|
156
|
+
"@deepseek-ai/dsh-compaction-basic": "^0.1.5-rc.1",
|
|
157
|
+
"@deepseek-ai/dsh-fs": "^0.1.5-rc.1",
|
|
158
|
+
"@deepseek-ai/dsh-goal": "^0.1.5-rc.1",
|
|
159
|
+
"@deepseek-ai/dsh-home-paths": "^0.1.5-rc.1",
|
|
160
|
+
"@deepseek-ai/dsh-llm": "^0.1.5-rc.1",
|
|
161
|
+
"@deepseek-ai/dsh-plan-mode": "^0.1.5-rc.1",
|
|
162
|
+
"@deepseek-ai/dsh-sandbox": "^0.1.5-rc.1",
|
|
163
|
+
"@deepseek-ai/dsh-session": "^0.1.5-rc.1",
|
|
164
|
+
"@deepseek-ai/dsh-session-projection": "^0.1.5-rc.1",
|
|
165
|
+
"@deepseek-ai/dsh-session-stats": "^0.1.5-rc.1",
|
|
166
|
+
"@deepseek-ai/dsh-session-title": "^0.1.5-rc.1",
|
|
167
|
+
"@deepseek-ai/dsh-settings": "^0.1.5-rc.1",
|
|
168
|
+
"@deepseek-ai/dsh-token-meter": "^0.1.5-rc.1",
|
|
169
|
+
"@deepseek-ai/dsh-tools": "^0.1.5-rc.1",
|
|
170
170
|
"@types/node": "^24.0.0",
|
|
171
171
|
"@types/react": "^18.3.31",
|
|
172
172
|
"@types/react-dom": "^18.3.7",
|