dsh-rewind-plugin 0.10.0-alpha.1 → 0.10.0-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +2 -2
- package/README.md +2 -2
- package/docs/architecture.md +12 -8
- package/docs/compat/audit.md +4 -3
- package/docs/release/release.md +7 -0
- package/docs/release/release.zh.md +5 -0
- package/docs/rewind-fix.md +1 -1
- package/docs/rewind-fix.zh.md +1 -1
- package/lib/client.js +2 -2
- package/lib/index.js +1 -1
- package/lib/types/client/hidden.d.ts +1 -1
- package/lib/types/index.d.ts +6 -6
- package/lib/types/rewind.d.ts +5 -7
- package/package.json +4 -1
package/README.en.md
CHANGED
|
@@ -96,8 +96,8 @@ The whole design rests on two principles, simple but deliberate: **the conversat
|
|
|
96
96
|
|
|
97
97
|
`append-only` is a hard rule: the session log only grows and is never rewritten — the foundation of auditability and privacy. A rewind never touches history; it makes a single move: append **one content-empty marker message** to the end of the log and use it to "mask + replace" everything after the target message, so the model and the UI see only the part before it.
|
|
98
98
|
|
|
99
|
-
- The marker is **canonical** — the plugin **replicates** `/compact`'s "hide + replace": `/compact` compresses a span of history into a summary, while `/rewind` swaps in an empty
|
|
100
|
-
- The replacement is **
|
|
99
|
+
- The marker is **canonical** — the plugin **replicates** `/compact`'s "hide + replace": `/compact` compresses a span of history into a summary, while `/rewind` swaps in an empty marker message. Because it's canonical, the harness's own log replay, `/compact` compaction, and resume preflight all recognize it and never mistake it for a real message.
|
|
100
|
+
- The replacement is **imperceptible** — the model ignores the empty marker, with no effect (verified empirically). Together with the plugin's UI handling, what you and the model see is exactly how the conversation looked at the target — truly "in place".
|
|
101
101
|
- Because this is **masking, not deleting**, every withdrawn event stays in the log — auditable, traceable, viewable, and in principle manually recoverable.
|
|
102
102
|
|
|
103
103
|
> **Design highlight**: the entire conversation rewind is **a single append**. It's deterministic, auditable, and — because the log was never broken — a "clean" time-travel. Minimal action, complete semantics. The compatibility subtleties with the harness (replicating `/compact`, the empty-message mask) are where this plugin is genuinely professional — each is pinned by a dedicated probe test.
|
package/README.md
CHANGED
|
@@ -86,8 +86,8 @@ dsh plugin --profile web add dsh-rewind-plugin
|
|
|
86
86
|
|
|
87
87
|
`append-only` 是铁律:会话日志只追加、从不改写——这是可审计与信息安全的地基。回退从不动历史,它只做一步:往日志末尾追加一条**内容为空的标记消息**,把目标消息之后的全部内容「遮蔽 + 替换」掉,让模型和界面都只看得到目标之前的部分。
|
|
88
88
|
|
|
89
|
-
- **标记是规范的**——插件复刻 `/compact` 标准的「隐藏 + 替换」:`/compact` 把一段历史压缩成摘要,`/rewind`
|
|
90
|
-
-
|
|
89
|
+
- **标记是规范的**——插件复刻 `/compact` 标准的「隐藏 + 替换」:`/compact` 把一段历史压缩成摘要,`/rewind` 则换成一条空标记消息。由于其规范性,harness 的日志重放、`/compact` 压缩、续接检查都能正确识别它,绝不会把它误认为真实对话;
|
|
90
|
+
- **替换内容无感**——模型对空标记忽略、无感(实测验证)。配合插件对界面显示的处理,模型和你看到的对话就是目标消息当时的样子,真正的「就地」;
|
|
91
91
|
- 因为是「遮蔽」而非「删除」,**被撤回的每一条事件都完整留在日志里**,可审计、可追溯、可查看,原则上也随时能手动恢复。
|
|
92
92
|
|
|
93
93
|
> **设计点睛**:整个对话回退就是**一条**追加。它确定、可审计,且因为日志从未被破坏,回溯是「干净的」——用最小的动作,实现最完整的语义。那些与 harness 内部的兼容细节(对 `/compact` 的复刻、空消息的遮蔽)正是插件的专业所在,每一条都由专门的探针测试固化。
|
package/docs/architecture.md
CHANGED
|
@@ -55,7 +55,7 @@ machine channels (see [Compatibility strategy](#compatibility-strategy)).
|
|
|
55
55
|
user/message currently on the surface)
|
|
56
56
|
→ agent.cancel({ keepInbox: true }) if running; waitForAgentIdle
|
|
57
57
|
→ dropPendingSteering (next-step inbox only; queued messages untouched)
|
|
58
|
-
→ append the rewind marker =
|
|
58
|
+
→ append the rewind marker = a user/message with surfaceOp
|
|
59
59
|
{ op: 'replace', start, end } over every surface node after the
|
|
60
60
|
target (+ sourceEventSeqs = shadowed seqs) [a single event]
|
|
61
61
|
→ if mode 'both': store.restoreAfter(targetSeq) + syncRestoreObservations
|
|
@@ -68,11 +68,14 @@ Key invariants:
|
|
|
68
68
|
|
|
69
69
|
- **The log is append-only.** The marker is the *only* mutation: it cuts the
|
|
70
70
|
model-visible surface, never the raw history (search/export still see it).
|
|
71
|
-
- **The marker is
|
|
72
|
-
source
|
|
73
|
-
shadowed seqs (`sourceEventSeqs`) — `assistant/message` can
|
|
74
|
-
them (v2). It derives to itself, so it stays as a present
|
|
75
|
-
turn at the surface tail
|
|
71
|
+
- **The marker content is a constant `(empty message)` placeholder.** It carries
|
|
72
|
+
the dsh-rewind plugin source and is a `user/message`, the only surface type
|
|
73
|
+
that can cite the shadowed seqs (`sourceEventSeqs`) — `assistant/message` can
|
|
74
|
+
no longer carry them (v2). It derives to itself, so it stays as a present
|
|
75
|
+
user turn at the surface tail. The content is provider-independent and never
|
|
76
|
+
empty: the session log is immutable but the model serving a session may
|
|
77
|
+
change later, and a strict OpenAI-compatible gateway rejects an empty user
|
|
78
|
+
message (HTTP 400, Issue #21).
|
|
76
79
|
- **No ghost step frame is needed**: the token-meter step machine ignores
|
|
77
80
|
`user/message`, and the session invariant imposes no open-turn requirement
|
|
78
81
|
on it, so the marker is appended while idle, outside any turn, as one event.
|
|
@@ -82,8 +85,9 @@ Key invariants:
|
|
|
82
85
|
|
|
83
86
|
### Marker format history
|
|
84
87
|
|
|
85
|
-
The rewind marker is written as form C (
|
|
86
|
-
`surfaceOp.replace` over the shadowed range
|
|
88
|
+
The rewind marker is written as form C (a `user/message` with a
|
|
89
|
+
`surfaceOp.replace` over the shadowed range, `sourceEventSeqs`, and the
|
|
90
|
+
constant `(empty message)` content). Earlier
|
|
87
91
|
plugin versions wrote shapes a newer harness no longer accepts:
|
|
88
92
|
|
|
89
93
|
- **form A** — a bare `assistant/message(turn=N, step=0)` with no frame.
|
package/docs/compat/audit.md
CHANGED
|
@@ -50,7 +50,7 @@ legacy branch).
|
|
|
50
50
|
|
|
51
51
|
## Verified-compatible surfaces (probes pass)
|
|
52
52
|
|
|
53
|
-
- **token-meter replay** (the
|
|
53
|
+
- **token-meter replay** (the `user/message` marker + multiple rewinds + interleaved real turns + compact stacking).
|
|
54
54
|
- **compaction transactions**: `toolPairingBalancedBefore/After` stays balanced after a marker cut; the real `/compact` command (`command-compact` + `compaction-basic`, stub summarizer) can land `compaction/start…end` on top of a rewind marker and stay replayable; `/compact` is a legal no-op on a small surface.
|
|
55
55
|
- **resume replay**: `Session.create(id, events)` replays a rewind/compact-bearing log.
|
|
56
56
|
- **session-stats**: the `user/message` marker adds no step (the step count stays at the real turns' steps), no phantom turn.
|
|
@@ -63,8 +63,9 @@ legacy branch).
|
|
|
63
63
|
|
|
64
64
|
## Known behavior boundaries (deterministic differences, non-crash, documented)
|
|
65
65
|
|
|
66
|
-
- **session-stats / session-telemetry fold the full log**: post-rewind stats do **not** rewind — `turns`/`steps`/`llmMs` still include withdrawn content; the
|
|
67
|
-
- **token-meter usage anchor stays stable** (G3): the
|
|
66
|
+
- **session-stats / session-telemetry fold the full log**: post-rewind stats do **not** rewind — `turns`/`steps`/`llmMs` still include withdrawn content; the `user/message` marker is folded as a present user turn (it adds no step). This is the intended "fold the full log" semantics, pinned by probe.
|
|
67
|
+
- **token-meter usage anchor stays stable** (G3): the `user/message` marker carries no usage, but because it is not an `assistant/message`, the baseline anchor does not drop to a heuristic estimate — it stays `usage` across a rewind. Pinned by `compat-gaps` G3.
|
|
68
|
+
- **marker content is the constant `(empty message)` placeholder**: never empty, because the session log is immutable but the model serving a session may change later — a strict OpenAI-compatible gateway rejects an empty user message (HTTP 400, Issue #21). The marker is a small visible user turn in derived history. Pin: `verify-host` (`marker is a user/message with the dsh-rewind plugin source and the (empty message) placeholder`).
|
|
68
69
|
- **Withdrawn content stays searchable/exportable**: session-query full-text and `/export` read the raw log; a rewind cuts only the surface, so withdrawn messages remain (declared in the README).
|
|
69
70
|
- **Session title auto-regeneration**: the title derives from the surface, so an automatically-derived title may change after a rewind.
|
|
70
71
|
- **Files written but uncommitted in a cancelled turn**: a `both` rewind cannot restore them (tool side-effect timing; same as Claude Code).
|
package/docs/release/release.md
CHANGED
|
@@ -91,6 +91,13 @@ appended) when DSH releases a new tuple.
|
|
|
91
91
|
- **After DSH goes final**: final releases are not bound by the prerelease
|
|
92
92
|
tuple rule, so the peers can converge to a single stable range (e.g.
|
|
93
93
|
`^0.1.x`); this section can then be deleted.
|
|
94
|
+
- **Declared minimum (`dsh.engines.dsh`)**: alongside the peer tuple, each
|
|
95
|
+
release declares the DSH runtime floor under `dsh.engines.dsh` (e.g.
|
|
96
|
+
`>=0.1.2-rc.1`), consumed by the plugin-manager update guard. Bump it in
|
|
97
|
+
the same release that raises the peer tuple; never leave code raised while
|
|
98
|
+
the declared floor stays behind. Only the `>=X.Y.Z[-pre]` form is
|
|
99
|
+
supported (`^`/`~`/multi-range are treated as "cannot verify" and
|
|
100
|
+
fail closed).
|
|
94
101
|
|
|
95
102
|
## Versioned-line release model
|
|
96
103
|
|
|
@@ -79,6 +79,11 @@ DSH 仍在 rc 阶段,npm 的 prerelease 匹配规则要求 peer 范围与宿
|
|
|
79
79
|
→ devDependencies 同步升到最新 → `npm install` → `npm run check` → 发版。
|
|
80
80
|
- **正式版后收敛**:DSH 发布 final 版本后,正式版不受 prerelease 元组规则
|
|
81
81
|
限制,peer 可收敛为稳定的 `^0.1.x` 单范围,此节即可删除。
|
|
82
|
+
- **声明的最低运行时(`dsh.engines.dsh`)**:与 peer 元组一起,每个发布在
|
|
83
|
+
`dsh.engines.dsh` 声明 DSH 运行时下限(如 `>=0.1.2-rc.1`),供插件管理器
|
|
84
|
+
更新守卫读取。**与 peer 元组持平的同一发布里一并 bump**;不可只升代码、
|
|
85
|
+
声明下限停留在旧值。仅支持 `>=X.Y.Z[-pre]` 形式(`^`/`~`/多范围会被视为
|
|
86
|
+
「无法校验」而 fail-closed)。
|
|
82
87
|
|
|
83
88
|
## 发布版本线模型
|
|
84
89
|
|
package/docs/rewind-fix.md
CHANGED
package/docs/rewind-fix.zh.md
CHANGED
package/lib/client.js
CHANGED
|
@@ -1339,8 +1339,8 @@ function createRewindBridge(deps) {
|
|
|
1339
1339
|
}
|
|
1340
1340
|
|
|
1341
1341
|
// src/client/build-info.ts
|
|
1342
|
-
var PLUGIN_VERSION = true ? "0.10.0-alpha.
|
|
1343
|
-
var BUILD_HASH = true ? "
|
|
1342
|
+
var PLUGIN_VERSION = true ? "0.10.0-alpha.2" : "dev";
|
|
1343
|
+
var BUILD_HASH = true ? "cc039b5d" : "dev";
|
|
1344
1344
|
|
|
1345
1345
|
// src/client/locales.ts
|
|
1346
1346
|
var zh = {
|
package/lib/index.js
CHANGED
|
@@ -1512,7 +1512,7 @@ async function commitEntry(store, pending, anchorCache, trackedBySession, exec,
|
|
|
1512
1512
|
tracked.add(capture.path);
|
|
1513
1513
|
}
|
|
1514
1514
|
var REWIND_MARKER_SOURCE = { kind: "plugin", plugin: "dsh-rewind" };
|
|
1515
|
-
var REWIND_MARKER_CONTENT = [];
|
|
1515
|
+
var REWIND_MARKER_CONTENT = [{ type: "text", text: "(empty message)" }];
|
|
1516
1516
|
function buildMarker() {
|
|
1517
1517
|
return createUserMessage({
|
|
1518
1518
|
content: REWIND_MARKER_CONTENT,
|
|
@@ -104,7 +104,7 @@ export declare function isCandidateCommand(command: CommandNode): boolean;
|
|
|
104
104
|
* (pending, succeeded, or errored — it only exists to feed the popover) and
|
|
105
105
|
* every SUCCESSFUL executed `/rewind` command row, plus every message
|
|
106
106
|
* withdrawn by a rewind — the target message itself, everything after it, and
|
|
107
|
-
* the (
|
|
107
|
+
* the (unrendered) marker.
|
|
108
108
|
*
|
|
109
109
|
* Each executed rewind cuts ONE span `[target, marker]`: the target message
|
|
110
110
|
* and everything after it, up to the marker appended at rewind time. Spans are
|
package/lib/types/index.d.ts
CHANGED
|
@@ -8,12 +8,12 @@
|
|
|
8
8
|
* surface node after the target message with the marker. The append-only log
|
|
9
9
|
* (and the rendered transcript) is untouched — only the model-visible surface
|
|
10
10
|
* is cut, so the next request derives its context from the target onward.
|
|
11
|
-
* The marker is
|
|
12
|
-
*
|
|
13
|
-
* `assistant/message` can no longer carry those — so
|
|
14
|
-
* a `user/message`, exactly as /compact's checkpoint
|
|
15
|
-
*
|
|
16
|
-
*
|
|
11
|
+
* The marker is a `user/message` carrying the shadowed-seq citations
|
|
12
|
+
* (`sourceEventSeqs`): v2 reserves surface `replace` to a node that cites
|
|
13
|
+
* every shadowed seq, and `assistant/message` can no longer carry those — so
|
|
14
|
+
* the replacement node is a `user/message`, exactly as /compact's checkpoint
|
|
15
|
+
* is. It derives to itself (a present user turn), so the marker stays as the
|
|
16
|
+
* surface-tail cut point rather than vanishing.
|
|
17
17
|
*
|
|
18
18
|
*
|
|
19
19
|
* File restore (mode `both`) follows Claude Code's checkpointing: the plugin
|
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.3/v2): the marker is
|
|
13
|
-
*
|
|
12
|
+
* Marker shape (v0.1.3/v2): the marker is a `user/message` carrying a replace
|
|
13
|
+
* `surfaceOp` — a single event:
|
|
14
14
|
*
|
|
15
|
-
* user/message (marker
|
|
15
|
+
* user/message (marker content) → { surfaceOp {replace, start, end} }
|
|
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
|
|
@@ -21,10 +21,8 @@
|
|
|
21
21
|
* is. No ghost `step/start`…`step/end` frame is needed: the token-meter's
|
|
22
22
|
* step state machine ignores `user/message`, and the session invariant
|
|
23
23
|
* (`invariant.ts`) imposes no open-turn requirement on it, so the marker is
|
|
24
|
-
* appended while idle, outside any turn.
|
|
25
|
-
*
|
|
26
|
-
* "cut point" (an empty `user/message` derives to itself, so it remains a
|
|
27
|
-
* present-but-empty user turn in derived history).
|
|
24
|
+
* appended while idle, outside any turn. It sits at the surface tail as the
|
|
25
|
+
* model-visible "cut point" — a present user turn in derived history.
|
|
28
26
|
*
|
|
29
27
|
* @module dsh-rewind/rewind
|
|
30
28
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-rewind-plugin",
|
|
3
|
-
"version": "0.10.0-alpha.
|
|
3
|
+
"version": "0.10.0-alpha.2",
|
|
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",
|
|
@@ -49,6 +49,9 @@
|
|
|
49
49
|
"bundle": {
|
|
50
50
|
"patch": "./cordis.patch.yml"
|
|
51
51
|
},
|
|
52
|
+
"engines": {
|
|
53
|
+
"dsh": ">=0.1.3-alpha.2"
|
|
54
|
+
},
|
|
52
55
|
"client": {
|
|
53
56
|
"inject": [
|
|
54
57
|
"@deepseek-ai/dsh-client-locale",
|