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 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 user 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 **empty** — the model fully ignores the empty message, with no effect (verified theoretically and 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".
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` 则换成一条空用户消息。由于其规范性,harness 的日志重放、`/compact` 压缩、续接检查都能正确识别它,绝不会把它误认为真实对话;
90
- - **替换内容是空的**——模型对空消息完全忽略、无感(理论 + 实测验证)。配合插件对界面显示的处理,模型和你看到的对话就是目标消息当时的样子,真正的「就地」;
89
+ - **标记是规范的**——插件复刻 `/compact` 标准的「隐藏 + 替换」:`/compact` 把一段历史压缩成摘要,`/rewind` 则换成一条空标记消息。由于其规范性,harness 的日志重放、`/compact` 压缩、续接检查都能正确识别它,绝不会把它误认为真实对话;
90
+ - **替换内容无感**——模型对空标记忽略、无感(实测验证)。配合插件对界面显示的处理,模型和你看到的对话就是目标消息当时的样子,真正的「就地」;
91
91
  - 因为是「遮蔽」而非「删除」,**被撤回的每一条事件都完整留在日志里**,可审计、可追溯、可查看,原则上也随时能手动恢复。
92
92
 
93
93
  > **设计点睛**:整个对话回退就是**一条**追加。它确定、可审计,且因为日志从未被破坏,回溯是「干净的」——用最小的动作,实现最完整的语义。那些与 harness 内部的兼容细节(对 `/compact` 的复刻、空消息的遮蔽)正是插件的专业所在,每一条都由专门的探针测试固化。
@@ -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 = an empty user/message with surfaceOp
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 empty** (`content: []`) and carries the dsh-rewind plugin
72
- source. It is a `user/message`, the only surface type that can cite the
73
- shadowed seqs (`sourceEventSeqs`) — `assistant/message` can no longer carry
74
- them (v2). It derives to itself, so it stays as a present-but-empty user
75
- turn at the surface tail rather than entering model context as content.
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 (an empty `user/message` with a
86
- `surfaceOp.replace` over the shadowed range and `sourceEventSeqs`). Earlier
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.
@@ -50,7 +50,7 @@ legacy branch).
50
50
 
51
51
  ## Verified-compatible surfaces (probes pass)
52
52
 
53
- - **token-meter replay** (the empty `user/message` marker + multiple rewinds + interleaved real turns + compact stacking).
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 empty `user/message` marker is folded as a present-but-empty 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 empty `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.
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).
@@ -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
 
@@ -5,7 +5,7 @@
5
5
  ## Applies to
6
6
 
7
7
  - **DSH**: `0.1.2-rc.1`
8
- - **dsh-rewind**: `0.9.0-alpha.1`, `0.9.0-alpha.2`, `0.9.0`
8
+ - **dsh-rewind**: `0.9.0-alpha.1`, `0.9.0-alpha.2`, `0.9.0`, `0.9.1`
9
9
 
10
10
  ## Background
11
11
 
@@ -5,7 +5,7 @@
5
5
  ## 适配
6
6
 
7
7
  - **DSH**: `0.1.2-rc.1`
8
- - **dsh-rewind**: `0.9.0-alpha.1`, `0.9.0-alpha.2`, `0.9.0`
8
+ - **dsh-rewind**: `0.9.0-alpha.1`, `0.9.0-alpha.2`, `0.9.0`, `0.9.1`
9
9
 
10
10
  ## 背景
11
11
 
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.1" : "dev";
1343
- var BUILD_HASH = true ? "7022eaa4" : "dev";
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 (empty, unrendered) marker.
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
@@ -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 an EMPTY `user/message`: v2 reserves surface `replace` to a
12
- * node that cites every shadowed seq (`sourceEventSeqs`), and
13
- * `assistant/message` can no longer carry those — so the replacement node is
14
- * a `user/message`, exactly as /compact's checkpoint is. An empty
15
- * `user/message` derives to itself (a present-but-empty user turn), so the
16
- * marker stays as the surface-tail cut point rather than vanishing.
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
@@ -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 an EMPTY `user/message` carrying a
13
- * replace `surfaceOp` — a single event:
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, empty content) → { surfaceOp {replace, start, end} }
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. The empty content means the marker
25
- * carries no language; it sits at the surface tail as the model-visible
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.1",
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",