dsh-rewind-plugin 0.7.4 → 0.8.0-alpha.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 CHANGED
@@ -156,7 +156,8 @@ withdrew should consume the stable, locale-independent helpers exported from
156
156
  2. **Lightweight file rewind has a cost** — in specific cases not all changes can be rewound. Consistent with Claude Code. See: [File-rewind tracking boundary](docs/compat/tracking-boundary.md).
157
157
  3. **Rewinds from `≤ v0.2.4`** — sessions rewound with these versions may **fail to load history** after more conversation. Install a v0.3.3-or-earlier release and use its bundled repair tool ([docs/compat/troubleshooting.md](docs/compat/troubleshooting.md)).
158
158
  4. **Rewinds from `≤ v0.3.3`** — compaction (`/compact`) is unavailable for those sessions. Newer versions are compatible; for affected old sessions, start a new session.
159
- 5. **The turn-rail shows rewound turns** — the right-side rail added in DSH `v0.1.2-alpha.1` keeps ticks for withdrawn messages: clicking does not jump and hovering shows the withdrawn text. Only a display difference; no functional impact.
159
+ 5. **The turn-rail shows rewound turns** — the right-side rail added in DSH `v0.1.2` keeps ticks for withdrawn messages, and hovering shows the withdrawn text. Only a display difference; no functional impact.
160
+ 6. **The system prompt is re-displayed after a rewind** — in DSH `v0.1.2`, rewinding and resending a message shows the "System prompt" component again, just like `/compact`. Only a display difference; no functional impact.
160
161
 
161
162
  > [!NOTE]
162
163
  > Browser diagnostics are available; see [Browser diagnostics](docs/compat/diagnostics.md).
package/README.md CHANGED
@@ -143,7 +143,8 @@ dsh plugin --profile web add dsh-rewind-plugin
143
143
  2. **轻量文件回退存在代价**——特定情况可能无法回退所有修改。行为与 Claude Code 一致。详见:[文件回退的追踪边界](docs/compat/tracking-boundary.zh.md)。
144
144
  3. **v0.2.4 及更早版本**回退过的会话,继续对话后可能加载历史失败。可安装 v0.3.3 及之前版本的随附修复工具处理([完整步骤](docs/compat/troubleshooting.zh.md))。
145
145
  4. **v0.3.3 及更早版本**回退过的会话,压缩对话(compact)不可用。新版本已兼容;受影响的旧会话建议新建会话。
146
- 5. **导轨显示已回退轮次**——DSH `v0.1.2-alpha.1` 新增的右侧导轨,会为已撤回消息保留刻度:点击不跳转、悬浮显示已撤回正文。仅显示差异,无功能影响。
146
+ 5. **导轨显示已回退轮次**——DSH `v0.1.2` 新增右侧导轨,为已撤回消息保留刻度,悬浮显示已撤回正文。仅显示差异,无功能影响。
147
+ 6. **回退重显系统提示词**——DSH `v0.1.2` 回退重发消息时,与 `/compact` 一样重显“系统提示词”组件。仅显示差异,无功能影响。
147
148
 
148
149
  > [!NOTE]
149
150
  > 本插件提供浏览器端诊断输出;详见 [浏览器诊断](docs/compat/diagnostics.zh.md)。
@@ -98,10 +98,11 @@ pruneStale cross-session auto-cleanup (default off): whole
98
98
 
99
99
  ## Compatibility strategy
100
100
 
101
- - **Peer ranges as OR-union of DSH version tuples** (`^0.1.0-rc.6 || ^0.1.1-rc.2 || ^0.1.2-alpha.2`):
102
- npm's prerelease rules require the peer range to share the host's
103
- `[major, minor, patch]` tuple; `scripts/check-dsh-version.mjs` detects when
104
- a new tuple requires appending. See `docs/release/release.md`.
101
+ - **Peer ranges as one tuple per DSH line** (`^0.1.2-rc.1`): npm's prerelease
102
+ rules require the peer range to share the host's `[major, minor, patch]`
103
+ tuple, so a new DSH tuple replaces the peer tuple (the single-line model);
104
+ `scripts/check-dsh-version.mjs` flags when a new tuple arrives. See
105
+ `docs/release/release.md`.
105
106
  - **Test-driven investigation**: `tests/compat-invariants.ts` /
106
107
  `compat-interop` / `compat-gaps` probe harness behavior and pin findings in
107
108
  `docs/compat/audit.md`; `scripts/verify-host.mjs` runs a real end-to-end
@@ -7,36 +7,33 @@
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.1-rc.2` (matches `package-lock.json`);
11
- > source reference: the `oss/deepseek-harness` local fork.
10
+ > Targeted version: npm `@deepseek-ai/*@0.1.2-rc.1` (matches `package-lock.json`).
11
+ > Source reference: the upstream [github.com/deepseek-ai/deepseek-harness](https://github.com/deepseek-ai/deepseek-harness).
12
12
  >
13
- > Version alignment: `peerDependencies` use an OR-union (e.g.
14
- > `^0.1.0-rc.6 || ^0.1.1-rc.2 || ^0.1.2-alpha.2`) covering each published tuple series. npm's
15
- > prerelease matching rules require a candidate to share the range comparator's
16
- > `[major, minor, patch]` tuple, so each new tuple series (e.g. a future
17
- > `0.1.2-rc.x`, `0.2.x`) requires appending a union member; rc rolling within a
18
- > tuple (`0.1.1-rc.2 → rc.3`) is a no-op. Signal: `npm view @deepseek-ai/dsh version`;
13
+ > Version alignment: `peerDependencies` use one tuple per DSH line
14
+ > (e.g. `^0.1.2-rc.1`). npm's prerelease matching rules require a candidate to
15
+ > share the range comparator's `[major, minor, patch]` tuple, so a new DSH tuple
16
+ > replaces the peer tuple (single-line model); any prerelease within the SAME
17
+ > tuple is a no-op. Signal: `npm view @deepseek-ai/dsh version`;
19
18
  > flow: `scripts/check-dsh-version.mjs` (it reads the `latest` dist-tag only; a
20
- > `-alpha` prerelease published under another tag, e.g. `0.1.2-alpha.2` under
21
- > `alpha`, is a manual pre-release check).
19
+ > prerelease published under another tag is a manual pre-release check).
22
20
  >
23
- > `0.1.2-alpha.1` was never published to npm, so the peer OR-union is declared only from `0.1.2-alpha.2`; the plugin code still supports the `alpha.1`+ client.
24
- >
25
- > `0.1.2-alpha.2` … `0.1.2-alpha.5` verified-compatible. Built/tested on the rc.2
26
- > baseline (the `latest` dist-tag); the same published build also drives the alpha
27
- > line, and `alpha.2`/`alpha.3`/`alpha.4`/`alpha.5` share the `0.1.2` tuple, so the
28
- > single `^0.1.2-alpha.2` peer member covers all four. `rc.2` (`latest`) remains the
29
- > primary baseline; five versions verified: rc.2, alpha.2, alpha.3, alpha.4, alpha.5.
30
-
31
- ### Channeled seams (version × channel)
32
-
33
- | Seam | rc.2 (`0.1.1-rc.2`) | alpha.2 / alpha.3 | alpha.4 / alpha.5 |
34
- | --- | --- | --- | --- |
35
- | Host session log (`eventsOf`) | `Session.events` | `Session.events` | `session.snapshotEvents()` |
36
- | Client chat snapshot (`chatSnapshotOf`) | session-face `chat` field | `uiConversation` `chat` view | `uiConversation` `chat` view |
37
- | Client composer refill (`writeComposer`) | `<textarea>` DOM write | `conversation.input.setDraft` | `conversation.input.setDraft` |
38
- | Client settings card | nested `ctx.inject(['settingsScope'])` | nested `ctx.inject(['settingsScope'])` | nested `ctx.inject(['settingsScope'])` |
39
- | Client seat-button DOM (`actionsContainerOf`) | `[data-time-hover-root]` | `[data-actions-reveal]` | structural locate of the copy-`<button>` container |
21
+ > The plugin targets a single DSH version line (`0.1.2-rc.1`); compatibility
22
+ > with earlier lines is not kept.
23
+
24
+ ### Single channel (`0.1.2-rc.1`)
25
+
26
+ The plugin targets one DSH channel. Each seam below reads the 0.1.2-rc.1 shape
27
+ only (no `Session.events` / `[data-time-hover-root]` / `<textarea>` / face-`chat`
28
+ legacy branch).
29
+
30
+ | Seam | 0.1.2-rc.1 implementation |
31
+ | --- | --- |
32
+ | Host session log | `session.snapshotEvents()` |
33
+ | Client chat snapshot | `uiConversation` `chat` view (`chatSnapshotOf`) |
34
+ | Client composer refill | `conversation.input.setDraft` facade |
35
+ | Client settings card | nested `ctx.inject(['settingsScope'])` |
36
+ | Client seat-button DOM | structural locate of the copy-`<button>` container (`actionsContainerOf`) |
40
37
 
41
38
  ## Definition of "fully compatible" (invariants)
42
39
 
@@ -62,7 +59,7 @@
62
59
  - **rewind across a compact checkpoint**: `RewindError('not-on-surface')` refuses cleanly, no crash.
63
60
  - **plan-mode**: a marker reuses the last started turn (no phantom turn); a rewind never touches the log-only `plan/mode` state (plan mode stays active; the user leaves it with `/plan off`) and the log stays replayable (`compat-invariants` I1/I3 marker + `plan/mode` probe, `verify-host`).
64
61
  - **agent-loop cancellation**: `finally` guarantees step/turn closure; the rewind force-stop path leaves no dangling frame.
65
- - **settings-card cross-version reach**: the Snapshot cleanup card must be registered through a **nested** `ctx.inject(['settingsScope'], …)` — naming `settingsScope` in the module-level inject unmounts the whole client plugin on rc.2 (card and rewind button disappear). It uses only the rc.2↔alpha-common scope subset (`getSnapshot().value` + `set`), never the alpha-only `mutate`.
62
+ - **settings-card registration**: the Snapshot cleanup card must be registered through a **nested** `ctx.inject(['settingsScope'], …)` — naming `settingsScope` in the module-level inject would keep the whole client plugin unmounted on a host without that service (card and rewind button would disappear). It reads `getSnapshot().value` + `set`, never the `mutate` write API.
66
63
 
67
64
  ## Known behavior boundaries (deterministic differences, non-crash, documented)
68
65
 
@@ -81,7 +78,7 @@ The plugin treats these as harness-side defects it does not compensate for. Each
81
78
  ### RU-I18N: host-side locale preference is not reliably readable at command registration (harness-side structural timing defect; plugin reads once, never retries)
82
79
 
83
80
  - **Root cause (harness-side)**: the plugin resolves `activeLocale` in an
84
- `ctx.inject(['settings'])` callback that reads `settings.get(settingsNamespace('locale')).preference`
81
+ `ctx.inject(['settings'])` callback that reads `settings.get('locale').preference`
85
82
  **once, with no retry** (`src/index.ts`). `dsh-client-locale`'s host half
86
83
  registers that `locale` settings section **through its own `ctx.inject(['settings'])`**
87
84
  (`packages/client/locale/src/index.ts`). Both callbacks wait only on `settings` and are
@@ -21,7 +21,7 @@ the plugin itself uses (it never reads the DOM or host copy):
21
21
  ```ts
22
22
  import { hiddenSeqsOf, type HiddenChat } from 'dsh-rewind-plugin/client'
23
23
 
24
- const chat = session.getSnapshot().chat // rc.2 source; 0.1.2-alpha.1+ uses the uiConversation "chat" view
24
+ const chat = uiConversation.binding(sessionId).target('chat')?.getSnapshot() // the 0.1.2-rc.1 "chat" view
25
25
  const hidden = hiddenSeqsOf(chat as HiddenChat) // Set<number> of anchor seqs
26
26
  ```
27
27
 
@@ -20,7 +20,7 @@
20
20
  ```ts
21
21
  import { hiddenSeqsOf, type HiddenChat } from 'dsh-rewind-plugin/client'
22
22
 
23
- const chat = session.getSnapshot().chat // rc.2 来源;0.1.2-alpha.1+ uiConversation "chat" 视图
23
+ const chat = uiConversation.binding(sessionId).target('chat')?.getSnapshot() // 0.1.2-rc.1 "chat" 视图
24
24
  const hidden = hiddenSeqsOf(chat as HiddenChat) // 被隐藏的 anchor seq 集合
25
25
  ```
26
26
 
@@ -1,8 +1,7 @@
1
1
  # DeepSeek Harness interface reference
2
2
 
3
3
  > Maintainer doc: the harness subsystems this plugin depends on, and the key
4
- > source files behind each interface. Local fork (if present):
5
- > `<workspace>/oss/deepseek-harness/` — official repo:
4
+ > source files behind each interface. Official repo:
6
5
  > [github.com/deepseek-ai/deepseek-harness](https://github.com/deepseek-ai/deepseek-harness)
7
6
 
8
7
  ## Subsystem docs (`docs/subsystems/`)
@@ -40,8 +39,8 @@ Also under `docs/` at the repo root: `persistence-catalog.md` (full
40
39
  | `tools/pre-execute` / `execute` / `post-execute` | [packages/core/tools/src/index.ts](https://github.com/deepseek-ai/deepseek-harness/blob/main/packages/core/tools/src/index.ts) |
41
40
  | Client DOM anchors (`data-chat-flow-kind` / `data-chat-anchor-key`) | [packages/client/ui-conversation/src/client/chat/ChatNodeSeat.tsx](https://github.com/deepseek-ai/deepseek-harness/blob/main/packages/client/ui-conversation/src/client/chat/ChatNodeSeat.tsx) |
42
41
  | User bubble rendering | [packages/client/ui-conversation/src/client/chat/MessageItem.tsx](https://github.com/deepseek-ai/deepseek-harness/blob/main/packages/client/ui-conversation/src/client/chat/MessageItem.tsx) |
43
- | Client `SessionFace` (`command` / `cancel`) | [packages/client/runtime/src/client/contract/session.ts](https://github.com/deepseek-ai/deepseek-harness/blob/main/packages/client/runtime/src/client/contract/session.ts) |
44
- | Client `PendingWait` (`respond`) | [packages/client/runtime/src/client/sessions/pending.ts](https://github.com/deepseek-ai/deepseek-harness/blob/main/packages/client/runtime/src/client/sessions/pending.ts) |
42
+ | Client `SessionFace` (`command` / `cancel`) | [packages/api/session-controller/src/client/contract/session.ts](https://github.com/deepseek-ai/deepseek-harness/blob/main/packages/api/session-controller/src/client/contract/session.ts) |
43
+ | Client pending interaction (SessionPendingInteraction) | [packages/client/ui-session/src/client/index.ts](https://github.com/deepseek-ai/deepseek-harness/blob/main/packages/client/ui-session/src/client/index.ts) |
45
44
 
46
45
  ## Plugin source layout
47
46
 
@@ -33,45 +33,101 @@ Open `https://www.npmjs.com/package/dsh-rewind-plugin` → package **settings**
33
33
 
34
34
  ## Subsequent releases (CI, automatic)
35
35
 
36
- ```sh
37
- npm version patch
38
- git push origin main --tags # triggers .github/workflows/publish.yml
39
- ```
36
+ The release workflow is **version-driven**: it derives the npm dist-tag from the
37
+ version in `package.json`, independent of branch. A stable version publishes to
38
+ `latest`; a pre-release publishes to the dist-tag named by its pre-release
39
+ identifier (e.g. `0.9.0-alpha.1` → `alpha`, `0.9.0-rc.1` → `rc`). The dist-tag is
40
+ never passed by hand.
41
+
42
+ The line being released determines the branch and the bump:
43
+
44
+ | Release | Branch | Bump | dist-tag |
45
+ | --- | --- | --- | --- |
46
+ | Stable patch (current line) | `release/0.8.x` | `npm version patch` | `latest` |
47
+ | Pre-release (next line) | `main` | `npm version prerelease --preid=alpha` | `alpha` |
48
+ | Stable (next line) | `main` | `npm version 0.9.0` | `latest` |
40
49
 
50
+ Each release is `git push <branch>` followed by `git push <branch> --tags`.
51
+
52
+ - The tag is the **lightweight `npm version` tag** (on the `chore: release …`
53
+ commit). Push it as-is — **do not** create the tag or the release beforehand
54
+ with `gh release create <tag>`: that tags the remote's current `main` head, so
55
+ an unpushed version bump leaves the tag on the wrong commit and the
56
+ tag/version match check fails.
41
57
  - **Before bumping, manually confirm there is no newer DSH version the plugin
42
58
  has not been verified against** (a pre-release can ship in DSH Desktop
43
- without being on npm, e.g. `0.1.2-alpha.1`; see docs/compat/audit.md).
59
+ without being on npm; see docs/compat/audit.md).
44
60
  - The workflow verifies the tag matches `package.json`, runs typecheck + tests +
45
- a full build + artifact verification, publishes with `--provenance`
46
- (Sigstore), and creates a GitHub Release. It is **idempotent** — an already
47
- published version is skipped.
61
+ a full build + artifact verification, publishes with `--provenance` (Sigstore)
62
+ to the version-derived dist-tag, and creates a GitHub Release (a pre-release
63
+ is created as a GitHub pre-release, not `latest`). It is **idempotent** — an
64
+ already published version is skipped.
48
65
  - CI (`.github/workflows/ci.yml`) runs `npm run check` — typecheck + tests +
49
66
  build + artifact verification + a `npm pack --dry-run` — on every push / PR
50
67
  across both Node engines boundary versions; the tarball layout is guarded by
51
68
  `tests/package-layout.test.ts`.
69
+ - The GitHub Release body is auto-created with `--generate-notes` as a
70
+ **placeholder** (`--latest` / `--prerelease` per version). After the publish
71
+ run succeeds, overwrite the body by hand in the repo's bilingual style
72
+ (Chinese first, then English) — never keep the auto text as the final note.
52
73
 
53
- ## DSH version alignment (peer range maintenance)
74
+ ## DSH version alignment (single peer tuple)
54
75
 
55
76
  DSH is still in rc; npm's prerelease matching rules require a peer range to
56
77
  share the host version's `[major, minor, patch]` tuple. So `peerDependencies`
57
- uses an OR-union covering every published tuple series
58
- (e.g. `^0.1.0-rc.6 || ^0.1.1-rc.2 || ^0.1.2-alpha.2`), extended as DSH releases new tuples.
59
-
60
- - **When to update**: only when DSH releases a new tuple
61
- (`0.1.1 → 0.1.2 → 0.2.x`); rc rolling within a tuple (`0.1.1-rc.2 → rc.3`)
62
- needs nothing. All `@deepseek-ai/*` packages release together;
63
- `npm view @deepseek-ai/dsh version` is the authoritative signal.
64
- - **Exception — `@deepseek-ai/dsh-client-runtime`**: it never published a
65
- `0.1.2-alpha.*` (npm `next` is `0.1.1-rc.2`) and is imported `import type`
66
- only, so keep it at `^0.1.0-rc.6 || ^0.1.1-rc.2` (no `0.1.2` member).
78
+ uses one peer tuple per DSH line (e.g. `^0.1.2-rc.1`), replaced (never
79
+ appended) when DSH releases a new tuple.
80
+
81
+ - **When to update**: only when DSH releases a new tuple (`0.1.2 → 0.1.3 →
82
+ 0.2.x`); rc rolling within a tuple (`0.1.2-rc.1 → rc.2`) needs nothing. All
83
+ `@deepseek-ai/*` packages release together; `npm view @deepseek-ai/dsh version`
84
+ is the authoritative signal.
67
85
  - **Published-tuple check (optional)**: `node scripts/check-dsh-version.mjs`
68
- compares the `latest` dist-tag version against the tuples the peers cover
86
+ compares the `latest` dist-tag version against the tuple the peers cover
69
87
  (exit 0 = nothing to do, exit 1 = update). It reads the `latest` tag only; a
70
- pre-release published under another tag (e.g. `alpha`) or bundled without
88
+ pre-release published under another tag or bundled without
71
89
  going to npm is a manual pre-release check — see the "Before bumping" step above.
72
- - **Update steps**: append `|| ^<new-tuple>-rc.<n>` to every
73
- `@deepseek-ai/dsh-*` peer → bump devDependencies to the latest → `npm
74
- install` → `npm run check` → release.
90
+ - **Update steps**: replace every `@deepseek-ai/dsh-*` peer's tuple with
91
+ `^<latest>` → bump devDependencies to the latest → `npm install` →
92
+ `npm run check` → release.
75
93
  - **After DSH goes final**: final releases are not bound by the prerelease
76
94
  tuple rule, so the peers can converge to a single stable range (e.g.
77
95
  `^0.1.x`); this section can then be deleted.
96
+
97
+ ## Versioned-line release model
98
+
99
+ **One release targets one DSH version line.** The plugin's own version is
100
+ independent of the host; a release declares its DSH line through the peer
101
+ constraint (a single companion tuple), never through the plugin version.
102
+
103
+ | Plugin version | DSH line | Notes |
104
+ | --- | --- | --- |
105
+ | `0.7.x` | `0.1.1` + `0.1.2` (broad) | frozen / EOL |
106
+ | `0.8.x` | `0.1.2-rc.1` (single) | current stable |
107
+ | `0.9.x` | `0.1.3` (single) | next line |
108
+
109
+ **Versioning.** A DSH version-line break is a MAJOR bump (incompatible with the
110
+ prior DSH line). Within a line, MINOR/PATCH remain compatible.
111
+
112
+ **Branching (trunk-based).** `main` is the single integration and release line
113
+ and is always releasable. The currently-shipped stable is cut into a short-lived
114
+ `release/<version>.x` maintenance branch from its release commit; that branch
115
+ receives backported fixes while `main` advances to the next line. The prior
116
+ (broad-compat) line is frozen as a tag, with no branch.
117
+
118
+ **dist-tag routing.** The release workflow derives the npm dist-tag from the
119
+ version: a stable version publishes to `latest`; a pre-release publishes to the
120
+ dist-tag named by its pre-release identifier (`0.9.0-alpha.1` → `alpha`,
121
+ `0.9.0-rc.1` → `rc`). A pre-release never occupies `latest`.
122
+
123
+ **Support window / EOL.** A DSH line is supported within a declared window. By
124
+ default the window runs until the next DSH line ships as `latest`; after that
125
+ the line is EOL, frozen, and receives no further patches. Here `0.8.x`
126
+ (`0.1.2-rc.1`) is supported until `0.9.x` (`0.1.3`) ships as `latest`.
127
+
128
+ **Bug-fix flow (forward-fix then backport).** A fix affecting multiple supported
129
+ lines is applied on `main` first, then backported to each still-supported
130
+ release branch. A fix specific to one line is applied only on that line.
131
+
132
+ The single-line model uses one peer tuple per release (see DSH version
133
+ alignment above); the OR-union multi-line practice it replaced is not used.
@@ -32,37 +32,82 @@ npm publish --access public
32
32
 
33
33
  ## 后续发布(CI 自动)
34
34
 
35
- ```sh
36
- npm version patch
37
- git push origin main --tags # 触发 .github/workflows/publish.yml
38
- ```
35
+ 发布 workflow 为**版本驱动**:npm dist-tag 由 `package.json` 中的版本号决定、与分支无关。
36
+ 稳定版发 `latest`;pre-release 发到与其 pre-release 标识符同名的 dist-tag
37
+ (如 `0.9.0-alpha.1` `alpha`、`0.9.0-rc.1` `rc`)。dist-tag 无需手传。
38
+
39
+ 按要发布的版本线选择分支与版本步进:
40
+
41
+ | 发布类型 | 分支 | 版本步进 | dist-tag |
42
+ | --- | --- | --- | --- |
43
+ | 当前线稳定补丁 | `release/0.8.x` | `npm version patch` | `latest` |
44
+ | 下一线 pre-release | `main` | `npm version prerelease --preid=alpha` | `alpha` |
45
+ | 下一线正式版 | `main` | `npm version 0.9.0` | `latest` |
39
46
 
40
- - **升版前手动确认**:确认无插件未针对其验证过的更新 DSH 版本(pre-release 可能只随 Desktop 捆绑、而不发到 npm,如 `0.1.2-alpha.1`;见 docs/compat/audit.md)。
47
+ 每次发布为 `git push <分支>`,再 `git push <分支> --tags`。
48
+
49
+ - tag 即 `npm version` 生成的**轻量 tag**(落在 `chore: release …` 提交上)。
50
+ 直接原样 push 即可——**不要**用 `gh release create <tag>` 事先建 tag 或
51
+ release:它会给远端当前 `main` 的 HEAD 打 tag,若版本 bump 尚未 push,tag
52
+ 就会落在错误提交上,导致 tag/版本一致性校验失败。
53
+ - **升版前手动确认**:确认无插件未针对其验证过的更新 DSH 版本(pre-release
54
+ 可能只随 Desktop 捆绑、而不发到 npm;见 docs/compat/audit.md)。
41
55
  - workflow 校验 tag 与 `package.json` 版本一致,跑 typecheck + 测试 + 完整
42
- 构建 + 产物验证,以 `--provenance`(Sigstore)发布并创建 GitHub Release。
56
+ 构建 + 产物验证,以 `--provenance`(Sigstore)发布到版本推导出的 dist-tag,
57
+ 并创建 GitHub Release(pre-release 会建成 GitHub pre-release,而非 `latest`)。
43
58
  **幂等**——已发布的版本会跳过。
44
59
  - CI(`.github/workflows/ci.yml`)在每次 push / PR 跑 `npm run check`——
45
60
  typecheck + 测试 + 构建 + 产物验证 + `npm pack --dry-run`,且覆盖
46
61
  engines 两个边界版本;tarball 布局由 `tests/package-layout.test.ts` 守护。
62
+ - GitHub Release 正文由 workflow 以 `--generate-notes` 自动生成,仅是**占位**
63
+ (按版本取 `--latest` / `--prerelease`)。发布运行成功后,请用**手写双语**
64
+ 正文(中文在前、英文在后)覆盖——切勿把自动文本当作最终发布说明。
47
65
 
48
- ## DSH 版本适配(peer 范围维护)
66
+ ## DSH 版本适配(单一 peer 元组)
49
67
 
50
68
  DSH 仍在 rc 阶段,npm 的 prerelease 匹配规则要求 peer 范围与宿主版本
51
69
  **同 `[major, minor, patch]` 元组**才能匹配。因此 peerDependencies 采用
52
- OR 并集覆盖 DSH 已发布的每个元组系列(如 `^0.1.0-rc.6 || ^0.1.1-rc.2 || ^0.1.2-alpha.2`),
53
- 并随 DSH 发版追加。
70
+ **每一条 DSH 线一个 peer 元组**(如 `^0.1.2-rc.1`),DSH 发新元组时**替换**而非追加。
54
71
 
55
- - **何时需要更新**:仅当 DSH 发布新元组(`0.1.1 → 0.1.2 → 0.2.x`)时;
56
- 同元组内 rc 滚动(`0.1.1-rc.2 → rc.3`)无需动作。DSH 所有包同版本发布,
72
+ - **何时需要更新**:仅当 DSH 发布新元组(`0.1.2 → 0.1.3 → 0.2.x`)时;
73
+ 同元组内 rc 滚动(`0.1.2-rc.1 → rc.2`)无需动作。DSH 所有包同版本发布,
57
74
  `npm view @deepseek-ai/dsh version` 即权威信号。
58
- - **例外 —— `@deepseek-ai/dsh-client-runtime`**:它从未发布 `0.1.2-alpha.*`
59
- (npm `next` 为 `0.1.1-rc.2`)且仅被 `import type` 引用,保持
60
- `^0.1.0-rc.6 || ^0.1.1-rc.2` 即可(无 `0.1.2` 元组项)。
61
75
  - **已发布元组检查(可选)**:`node scripts/check-dsh-version.mjs` 用 npm `latest`
62
76
  dist-tag 版本对比 peer 覆盖的元组(exit 0 无需动作,exit 1 需要)。它**只读
63
- `latest` tag**;发布在其它 tag(如 `alpha`)的 pre-release 走**手动发布前检查**
77
+ `latest` tag**;发布在其它 tag pre-release 走**手动发布前检查**
64
78
  ——见上文"升版前手动确认"。
65
- - **更新步骤**:给每个 `@deepseek-ai/dsh-*` peer 追加 `|| ^<新元组>-rc.<n>`
79
+ - **更新步骤**:把每个 `@deepseek-ai/dsh-*` peer 的元组**替换**为 `^<最新版>`
66
80
  → devDependencies 同步升到最新 → `npm install` → `npm run check` → 发版。
67
81
  - **正式版后收敛**:DSH 发布 final 版本后,正式版不受 prerelease 元组规则
68
82
  限制,peer 可收敛为稳定的 `^0.1.x` 单范围,此节即可删除。
83
+
84
+ ## 发布版本线模型
85
+
86
+ **一个发布对准一条 DSH 版本线。** 插件自身版本号与宿主解耦;某发布所对准的
87
+ DSH 线由 peer 约束(单一 companion 元组)声明,而非插件版本号。
88
+
89
+ | 插件版本 | DSH 线 | 说明 |
90
+ | --- | --- | --- |
91
+ | `0.7.x` | `0.1.1` + `0.1.2`(广兼容) | 冻结 / EOL |
92
+ | `0.8.x` | `0.1.2-rc.1`(单线) | 当前稳定 |
93
+ | `0.9.x` | `0.1.3`(单线) | 下一线 |
94
+
95
+ **版本号。** DSH 版本线破坏性变更 = MAJOR 升版(与更早 DSH 线向后不兼容)。
96
+ 同一线内 MINOR/PATCH 保持向后兼容。
97
+
98
+ **分支(主干开发)。** `main` 是唯一集成与发布线,始终可发版。当前已发布
99
+ 稳定版从发布提交点切出短命 `release/<版本>.x` 维护分支;该分支承接
100
+ backport 修复,同时 `main` 前进到下一线。更早的(广兼容)线冻结成 tag,无分支。
101
+
102
+ **dist-tag 路由。** 发布 workflow 按版本号取 npm dist-tag:稳定版发 `latest`;
103
+ pre-release 发到与其 pre-release 标识符同名的 dist-tag(`0.9.0-alpha.1` →
104
+ `alpha`、`0.9.0-rc.1` → `rc`)。pre-release 不占据 `latest`。
105
+
106
+ **支持窗口 / EOL。** 一条 DSH 线仅在声明的窗口内受支持。默认窗口到下一
107
+ DSH 线作为 `latest` 发布为止;此后该线 EOL、冻结、不再发补丁。此处 `0.8.x`
108
+ (`0.1.2-rc.1`)支持到 `0.9.x`(`0.1.3`)作为 `latest` 发布为止。
109
+
110
+ **Bug 修复流程(先向前修,再回迁)。** 跨多条支持线的修复,先在 `main` 上
111
+ 修复,再回迁到各仍受支持的 release 分支。仅特定线的修复,只在对应线修复。
112
+
113
+ 单线模型每个发布使用单一 peer 元组(见上文「DSH 版本适配」);其取代的 OR 并集多线做法已不使用。
package/lib/client.js CHANGED
@@ -35,16 +35,12 @@ __export(index_exports, {
35
35
  module.exports = __toCommonJS(index_exports);
36
36
 
37
37
  // src/client/hidden.ts
38
- function resolveChatWatch(resolveView, resolveFace, sessionId, cb) {
38
+ function resolveChatWatch(resolveView, sessionId, cb) {
39
39
  const view = resolveView(sessionId);
40
- if (view?.subscribe !== void 0) return view.subscribe(cb);
41
- const face = resolveFace(sessionId);
42
- return face?.subscribe(cb) ?? (() => {
40
+ return view?.subscribe?.(cb) ?? (() => {
43
41
  });
44
42
  }
45
- function chatSnapshotOf(face, chatView) {
46
- const legacy = face?.getSnapshot().chat;
47
- if (legacy !== void 0) return legacy;
43
+ function chatSnapshotOf(chatView) {
48
44
  return chatView?.getSnapshot() ?? void 0;
49
45
  }
50
46
  function messageTextAt(chat, seq) {
@@ -328,16 +324,12 @@ var STYLE = `
328
324
  }
329
325
 
330
326
  /* ---- Snapshot-cleanup settings card (mirrors the harness PluginCard look) ---- */
331
- /* Dual-channel note: the harness card corner/border drifted across versions \u2014
332
- rc.2 uses border-radius 12px + 1px solid border-l2, while alpha uses 16px +
333
- 0.5px border-l4 (and adds corner-shape: round). We intentionally keep the
334
- rc.2 values here for now; if the alpha look is wanted, switch these on the
335
- host version like the other dual-channel surfaces (see the host-version probe
336
- in src/client/index.ts). */
327
+ /* Standardized on the 0.1.2 harness card look (border-radius 16px + 0.5px
328
+ border-l4, 0.5px internal separators). */
337
329
  .dsh-rewind-cleanup-card {
338
330
  list-style: none;
339
- border: 1px solid var(--dsw-alias-border-l2);
340
- border-radius: 12px;
331
+ border: 0.5px solid var(--dsw-alias-border-l4);
332
+ border-radius: 16px;
341
333
  background: var(--dsw-alias-bg-layer-3);
342
334
  transition: border-color .16s, background .16s;
343
335
  }
@@ -405,7 +397,7 @@ var STYLE = `
405
397
  color: var(--dsw-alias-label-secondary);
406
398
  }
407
399
  .dsh-rewind-cleanup-body {
408
- border-top: 1px solid var(--dsw-alias-border-l2);
400
+ border-top: 0.5px solid var(--dsw-alias-border-l2);
409
401
  margin: 0 16px;
410
402
  padding: 4px 0 8px;
411
403
  }
@@ -427,7 +419,7 @@ var STYLE = `
427
419
  padding: 12px 0;
428
420
  }
429
421
  .dsh-rewind-cleanup-field + .dsh-rewind-cleanup-field {
430
- border-top: 1px solid var(--dsw-alias-border-l2);
422
+ border-top: 0.5px solid var(--dsw-alias-border-l2);
431
423
  }
432
424
  .dsh-rewind-cleanup-head {
433
425
  display: flex;
@@ -648,7 +640,7 @@ function waitForCommand(session, chatOf, match, timeoutMs = 8e3, watch) {
648
640
  settle({ kind: node.outcome.kind, text: node.outcome.text });
649
641
  }
650
642
  };
651
- const unsubscribe = (watch ?? ((cb) => session.subscribe(cb)))(check);
643
+ const unsubscribe = watch(check);
652
644
  const timer = setTimeout(() => settle(null), timeoutMs);
653
645
  check();
654
646
  });
@@ -988,15 +980,6 @@ var rewindLog = {
988
980
 
989
981
  // src/client/portals.tsx
990
982
  var import_jsx_runtime = require("react/jsx-runtime");
991
- function fillComposerTextarea(text) {
992
- const textarea = document.querySelector(COMPOSER_TEXTAREA_SELECTOR);
993
- if (textarea === null) return false;
994
- const setter = Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, "value")?.set;
995
- setter?.call(textarea, text);
996
- textarea.dispatchEvent(new Event("input", { bubbles: true }));
997
- textarea.focus();
998
- return true;
999
- }
1000
983
  function fillComposerEditable(text) {
1001
984
  const editable = document.querySelector(COMPOSER_EDITABLE_SELECTOR);
1002
985
  if (editable === null) return false;
@@ -1020,7 +1003,6 @@ function fillComposerEditable(text) {
1020
1003
  return true;
1021
1004
  }
1022
1005
  function fillComposer(text) {
1023
- if (fillComposerTextarea(text)) return true;
1024
1006
  return fillComposerEditable(text);
1025
1007
  }
1026
1008
  function writeComposer(text, facade) {
@@ -1089,7 +1071,7 @@ async function runRewindAndFill(session, seq, mode, currentSessionId, chatOf, wa
1089
1071
  }
1090
1072
  }
1091
1073
  function composerSurface() {
1092
- return document.querySelector(COMPOSER_TEXTAREA_SELECTOR) ?? document.querySelector(COMPOSER_EDITABLE_SELECTOR);
1074
+ return document.querySelector(COMPOSER_EDITABLE_SELECTOR);
1093
1075
  }
1094
1076
  function showHint(text) {
1095
1077
  const surface = composerSurface();
@@ -1106,18 +1088,14 @@ function showHint(text) {
1106
1088
  }
1107
1089
  window.setTimeout(() => hint.remove(), 3200);
1108
1090
  }
1109
- var COMPOSER_TEXTAREA_SELECTOR = "[data-input-scroll] textarea, textarea[data-phase]";
1110
1091
  var COMPOSER_EDITABLE_SELECTOR = "[data-composer-input]";
1111
1092
  var USER_SEAT_SELECTOR = '[data-chat-flow-kind="user"][data-chat-anchor-key], [data-chat-flow-kind="steering"][data-chat-anchor-key]';
1112
1093
  var CHAT_SEAT_SELECTOR = "[data-chat-anchor-key]";
1113
- var ACTIONS_ROOT_SELECTOR = "[data-time-hover-root], [data-actions-reveal]";
1114
1094
  var PENDING_SEAT_SELECTOR = "[data-pending-steering]";
1115
1095
  function actionsContainerOf(row) {
1116
- const root = row?.matches(ACTIONS_ROOT_SELECTOR) ? row : row?.querySelector(ACTIONS_ROOT_SELECTOR);
1117
- const actions = root?.lastElementChild;
1118
- if (actions instanceof HTMLElement && actions.querySelector("button") !== null) return actions;
1119
- const copy = row?.querySelector("button");
1120
- const structural = copy?.parentElement;
1096
+ const buttons = Array.from(row?.querySelectorAll("button") ?? []);
1097
+ const lastButton = buttons.filter((button) => !button.classList.contains(CLASS.button)).at(-1);
1098
+ const structural = lastButton?.parentElement;
1121
1099
  if (structural instanceof HTMLElement && structural.querySelector("button") !== null) return structural;
1122
1100
  return void 0;
1123
1101
  }
@@ -1305,7 +1283,6 @@ function RewindButton({ target, sessionId, sessionOf, chatOf, watchChat, current
1305
1283
  function composerText() {
1306
1284
  const surface = composerSurface();
1307
1285
  if (surface === null) return "";
1308
- if (surface instanceof HTMLTextAreaElement) return surface.value;
1309
1286
  return surface.textContent ?? "";
1310
1287
  }
1311
1288
  async function retractPending(session, itemId, text, setComposerText) {
@@ -1356,8 +1333,8 @@ function createRewindBridge(deps) {
1356
1333
  }
1357
1334
 
1358
1335
  // src/client/build-info.ts
1359
- var PLUGIN_VERSION = true ? "0.7.4" : "dev";
1360
- var BUILD_HASH = true ? "73e01232" : "dev";
1336
+ var PLUGIN_VERSION = true ? "0.8.0-alpha.1" : "dev";
1337
+ var BUILD_HASH = true ? "ca24fc4e" : "dev";
1361
1338
 
1362
1339
  // src/client/locales.ts
1363
1340
  var zh = {
@@ -1615,7 +1592,6 @@ var name = "dsh-rewind";
1615
1592
  var inject = ["slots", "sessions", "locale", "commandUi"];
1616
1593
  var NS2 = "rewind";
1617
1594
  var HEADER_ACTIONS_SLOT = "conversation.session.header.actions";
1618
- var COMPOSER_TEXTAREA_SELECTOR2 = "[data-input-scroll] textarea, textarea[data-phase]";
1619
1595
  var COMPOSER_EDITABLE_SELECTOR2 = "[data-composer-input]";
1620
1596
  function apply(ctx) {
1621
1597
  rewindLog.info("boot", `loaded v${PLUGIN_VERSION} (build ${BUILD_HASH})`);
@@ -1635,7 +1611,7 @@ function apply(ctx) {
1635
1611
  if (session === void 0) return void 0;
1636
1612
  try {
1637
1613
  const view = uiConversation()?.binding(session.sessionId).target(CHAT_VIEW);
1638
- return chatSnapshotOf(session, view);
1614
+ return chatSnapshotOf(view);
1639
1615
  } catch {
1640
1616
  return void 0;
1641
1617
  }
@@ -1666,7 +1642,6 @@ function apply(ctx) {
1666
1642
  return void 0;
1667
1643
  }
1668
1644
  },
1669
- (id) => sessionOf(id),
1670
1645
  sessionId,
1671
1646
  cb
1672
1647
  );
@@ -1776,7 +1751,7 @@ function apply(ctx) {
1776
1751
  for (const name2 of ["rewind", "undo"]) {
1777
1752
  yield commandUi.decorate({ name: name2, ...rewindPopupSpec });
1778
1753
  }
1779
- const composerSurface2 = () => document.querySelector(COMPOSER_TEXTAREA_SELECTOR2) ?? document.querySelector(COMPOSER_EDITABLE_SELECTOR2);
1754
+ const composerSurface2 = () => document.querySelector(COMPOSER_EDITABLE_SELECTOR2);
1780
1755
  yield () => {
1781
1756
  style.remove();
1782
1757
  };