dsh-rewind-plugin 0.6.1 → 0.6.3

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
@@ -12,7 +12,7 @@ A deliberately focused plugin with one job: **rewind to any user message, no mat
12
12
 
13
13
  - **Rewinding is time-travel** — the target message and everything after it (agent replies, tool calls) are withdrawn from the model context *and* the rendered transcript at once, with no new session and no window switch; the target's text is offered back in the composer so you can edit and re-send it — **truly seamless and convenient by design**.
14
14
  - **Lightweight workspace backup** — Claude Code-aligned behavior: tracks files edited by the write-class tools, and external changes to **already-tracked** files are restorable too. Selective tracking, before-write backup, store only on change. One lightweight plugin gives you a **complete** agentic rewind capability.
15
- - **Privacy-first** — the plugin never deletes or rewrites the session log (append-only) and never actually deletes any of your conversation; file restores stay inside the plugin's own backup directory. Full security model: [SECURITY.md](SECURITY.md).
15
+ - **Privacy-first** — the plugin never deletes or rewrites the session log (append-only) and never actually deletes any of your conversation; backups live in the plugin's own snapshot directory; restores draw only from those backups. Full security model: [SECURITY.md](SECURITY.md).
16
16
  - **A complete test system** — unit, probe, and end-to-end host verification, covering compatibility probing, log replay, resume, cross-restart and other scenarios; maintained continuously as the harness evolves to ensure feature stability.
17
17
 
18
18
  ## Preview
@@ -152,10 +152,11 @@ withdrew should consume the stable, locale-independent helpers exported from
152
152
  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).
153
153
  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)).
154
154
  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.
155
+ 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.
155
156
 
156
157
  ## Security
157
158
 
158
- This plugin only appends rewind-marker events to the session log; it never deletes or rewrites logged history. Workspace files are written only when you choose "conversation and code"; backups and restores stay under `<dsh home>/rewind-snapshots/`. It never touches your git repository, makes no network requests, and accesses no credentials. Delete `~/.dsh/rewind-snapshots/` to wipe file backups only (chat rewinds are unaffected); the plugin rebuilds automatically. For sessions you've left inactive for a long time, a global auto-cleanup (off by default) can remove their snapshot directory in whole, leaving the active session and the chat log untouched. Full security model: [SECURITY.md](SECURITY.md).
159
+ This plugin only appends rewind-marker events to the session log; it never deletes or rewrites logged history. Workspace files are written only when you choose "conversation and code"; backups are stored under `<dsh home>/rewind-snapshots/`; restores draw only from those backups. It never touches your git repository, makes no network requests, and accesses no credentials. Delete `~/.dsh/rewind-snapshots/` to wipe file backups only (chat rewinds are unaffected); the plugin rebuilds automatically. For sessions you've left inactive for a long time, a global auto-cleanup (off by default) can remove their snapshot directory in whole, leaving the active session and the chat log untouched. Full security model: [SECURITY.md](SECURITY.md).
159
160
 
160
161
  ## Development
161
162
 
package/README.md CHANGED
@@ -12,7 +12,7 @@ DeepSeek Harness 插件:**一键就地回退对话到任意更早的用户消
12
12
 
13
13
  - **回退 = 时间回溯**——目标消息及其之后的全部内容(agent 回复、工具调用)同时从**模型上下文**和**渲染对话**中撤回,不新建会话、不切换窗口;目标消息文本会回填输入框,改完可重发。**在原理上就真正无感、便捷**。
14
14
  - **轻量工作区备份**——对齐 Claude Code:追踪写类工具编辑过的文件,**已跟踪文件的外部变更也能还原**。局部追踪、写前备份、不变不存。一个轻型插件,即拥有**完备的智能体回退能力**。
15
- - **信息安全优先**——插件从不删改会话日志(append-only),从不真正删除你的任何对话;文件还原限定在插件自己的备份目录。完整安全模型:[SECURITY.md](SECURITY.md)。
15
+ - **信息安全优先**——插件从不删改会话日志(append-only),从不真正删除你的任何对话;备份存于自己的快照目录;还原只用这些备份。完整安全模型:[SECURITY.md](SECURITY.md)。
16
16
  - **完备测试系统**——单元、探针、端到端主机验证,覆盖兼容性探测、日志重放、续接、跨重启等场景;随 harness 升级持续维护,确保功能稳定。
17
17
 
18
18
  ## 效果预览
@@ -140,10 +140,11 @@ dsh plugin --profile web add dsh-rewind-plugin
140
140
  2. **轻量文件回退存在代价**——特定情况可能无法回退所有修改。行为与 Claude Code 一致。详见:[文件回退的追踪边界](docs/compat/tracking-boundary.zh.md)。
141
141
  3. **v0.2.4 及更早版本**回退过的会话,继续对话后可能加载历史失败。可安装 v0.3.3 及之前版本的随附修复工具处理([完整步骤](docs/compat/troubleshooting.zh.md))。
142
142
  4. **v0.3.3 及更早版本**回退过的会话,压缩对话(compact)不可用。新版本已兼容;受影响的旧会话建议新建会话。
143
+ 5. **导轨显示已回退轮次**——DSH `v0.1.2-alpha.1` 新增的右侧导轨,会为已撤回消息保留刻度:点击不跳转、悬浮显示已撤回正文。仅显示差异,无功能影响。
143
144
 
144
145
  ## 安全
145
146
 
146
- 本插件只向会话日志追加回退标记事件,从不删除或改写已记录的历史。工作区文件仅在「回退对话和代码」时被改写,备份与还原都限定在 `~/.dsh/rewind-snapshots/` 内。不触碰你的 git 仓库,无网络请求,不访问任何凭据。对**长期不活跃**的会话,另有默认关闭的全局自动清理可整目录移除其快照,不影响活动会话与对话日志。完整安全模型:[SECURITY.md](SECURITY.md)。
147
+ 本插件只向会话日志追加回退标记事件,从不删除或改写已记录的历史。工作区文件仅在「回退对话和代码」时被改写,备份存储于 `~/.dsh/rewind-snapshots/`;还原以备份为唯一来源。不触碰你的 git 仓库,无网络请求,不访问任何凭据。对**长期不活跃**的会话,另有默认关闭的全局自动清理可整目录移除其快照,不影响活动会话与对话日志。完整安全模型:[SECURITY.md](SECURITY.md)。
147
148
 
148
149
  ## 开发
149
150
 
package/SECURITY.md CHANGED
@@ -83,9 +83,8 @@ and is repaired offline (see `docs/compat/troubleshooting.md`).
83
83
 
84
84
  - **Store root**: `<harness home>/rewind-snapshots/` by default (resolved via
85
85
  `resolveDshHome`, so `~/.dsh/...` when `DSH_HOME` is unset); the `snapshotDir`
86
- config, then `DSH_REWIND_SNAPSHOT_DIR` env, override it. The store never
87
- overlaps the workspace; deleting it only removes file backups and the store
88
- rebuilds from scratch.
86
+ config, then `DSH_REWIND_SNAPSHOT_DIR` env, override it. Deleting it only
87
+ removes file backups and the store rebuilds from scratch.
89
88
  - **Path sanitization**: session ids and call ids are scrubbed to
90
89
  `[a-zA-Z0-9._-]` (`safeSessionId` / `safeFileId`) before joining the store
91
90
  root; `.` and `..` bare values are replaced — hostile ids cannot traverse
@@ -138,10 +137,18 @@ and is repaired offline (see `docs/compat/troubleshooting.md`).
138
137
  same operating-system user: state files are created with the process
139
138
  default permissions (no special modes are set — a standard umask applies),
140
139
  and the host user remains trusted.
141
- - It does **not** exclude paths from the store: recording and restore mirror the
142
- paths the model's file tools touched, so a sensitive file (e.g. `.env`) the
143
- model may read or edit will be backed up and restorable. Keeping one out is
144
- a **DSH model-permission** concern (a per-path deny), not a rewind feature.
140
+ - It does **not** exclude paths from the store, and it never oversteps: the
141
+ plugin only adds backup/restore on top of permissions DSH already holds. Two
142
+ distinct cases follow:
143
+ 1. **Sensitive / personal-information files** (e.g. a `.env` file, which may
144
+ sit inside the workspace) the model may read or edit are backed up and
145
+ restorable.
146
+ 2. **Files outside the session workspace** the model is allowed to touch are
147
+ likewise backed up and restored.
148
+ In both cases the plugin introduces no authority of its own — DSH already
149
+ granted the read/edit; the plugin merely records a before-backup and can
150
+ restore it. Keeping a path out is a **DSH model-permission** concern (a
151
+ per-path deny), not a rewind feature.
145
152
  - It does **not** touch git (no refs, index, or worktree operations), makes
146
153
  **no network requests**, and does **not** access credentials.
147
154
  - It does **not** roll back whole-log state: telemetry, search, and `/export`
@@ -98,7 +98,7 @@ pruneStale cross-session auto-cleanup (default off): whole
98
98
 
99
99
  ## Compatibility strategy
100
100
 
101
- - **Peer ranges as OR-union of DSH rc tuples** (`^0.1.0-rc.6 || ^0.1.1-rc.2`):
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
102
  npm's prerelease rules require the peer range to share the host's
103
103
  `[major, minor, patch]` tuple; `scripts/check-dsh-version.mjs` detects when
104
104
  a new tuple requires appending. See `docs/release/release.md`.
@@ -11,28 +11,37 @@
11
11
  > source reference: the `oss/deepseek-harness` local fork.
12
12
  >
13
13
  > Version alignment: `peerDependencies` use an OR-union (e.g.
14
- > `^0.1.0-rc.6 || ^0.1.1-rc.2`) covering each published rc tuple series. npm's
14
+ > `^0.1.0-rc.6 || ^0.1.1-rc.2 || ^0.1.2-alpha.2`) covering each published tuple series. npm's
15
15
  > prerelease matching rules require a candidate to share the range comparator's
16
16
  > `[major, minor, patch]` tuple, so each new tuple series (e.g. a future
17
17
  > `0.1.2-rc.x`, `0.2.x`) requires appending a union member; rc rolling within a
18
18
  > tuple (`0.1.1-rc.2 → rc.3`) is a no-op. Signal: `npm view @deepseek-ai/dsh version`;
19
- > flow: `scripts/check-dsh-version.mjs`.
19
+ > 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).
20
22
  >
21
- > Client channel (harness `0.1.2-alpha.1`, a **bundled pre-release — NOT published
22
- > to npm**): the plugin also reads the session chat when a DSH Desktop build ships
23
- > that version (`latest` on the registry is still `0.1.1-rc.2`; the alpha is only
24
- > in the bundled `app.asar.unpacked/node_modules/@deepseek-ai/*`). There
25
- > `uiConversation` exposes the chat as a per-session named `"chat"` view
23
+ > Client channel (harness `0.1.2-alpha.x`; `0.1.2-alpha.2` is published to npm
24
+ > under the `alpha` dist-tag while `latest` stays `0.1.1-rc.2`): the plugin reads
25
+ > the session chat through `uiConversation` whenever the DSH client exposes it.
26
+ > `uiConversation` surfaces the chat as a per-session named `"chat"` view
26
27
  > (contributed by `dsh-client-ui-chat` through the `uiSession` slot hook) instead
27
28
  > of the session-face `chat` field. `src/client/hidden.ts` (`chatSnapshotOf`) reads
28
29
  > the session-face snapshot first (rc.2 path), then the `uiConversation` `"chat"`
29
- > view (alpha.1 path); both missing degrades to `undefined` (no targets, never a
30
- > crash). Peers are intentionally **NOT** extended for `0.1.2-alpha.1` because it is
31
- > not a registry version (see above); the OR-union stays
32
- > `^0.1.0-rc.6 || ^0.1.1-rc.2` and will gain the `0.1.2` tuple when the harness
33
- > publishes it. Pinned by `tests/chat-channel.test.ts` (channel precedence + the
34
- > alpha.1 snapshot shape) and `tests/client-dom.test.ts` (the button-target
35
- > pairing that consumes the chat).
30
+ > view (alpha path); both missing degrades to `undefined` (no targets, never a
31
+ > crash). The channel is held via a lazy `ctx.get` (never a declared `inject`), so
32
+ > the plugin keeps the rc.2 type baseline while the OR-union covers the published
33
+ > `0.1.2` tuple: `^0.1.0-rc.6 || ^0.1.1-rc.2 || ^0.1.2-alpha.2`. Pinned by
34
+ > `tests/chat-channel.test.ts` (channel precedence + the alpha snapshot shape) and
35
+ > `tests/client-dom.test.ts` (the button-target pairing that consumes the chat).
36
+ > The composer refill is dual-channel the same way: on alpha the withdrawn text is
37
+ > written through the `conversation` service's `input` resolver's `setDraft` (the
38
+ > harness's own Lexical editor), else the rc.2 `<textarea>` / alpha `contenteditable`
39
+ > DOM write (`writeComposer` in `src/client/portals.tsx`); pinned by
40
+ > `tests/client-composer.test.ts`. The session-seat button DOM is dual-channel
41
+ > the same way: the actions (copy/branch) row was located by `[data-time-hover-root]`
42
+ > on ≤ 0.1.1-rc.x and by `[data-actions-reveal]` on 0.1.2-alpha.2
43
+ > (`ACTIONS_ROOT_SELECTOR` / `PENDING_SEAT_SELECTOR` in `src/client/portals.tsx`);
44
+ > pinned by `tests/client-dom.test.ts`.
36
45
 
37
46
  ## Definition of "fully compatible" (invariants)
38
47
 
@@ -56,7 +65,7 @@
56
65
  - **session-title / goal fold**: a marker does not disturb `foldSessionTitle` / `foldGoal`.
57
66
  - **client ordering**: turn-tail ordering + `step/start` uniqueness hold for tool turns + marker logs.
58
67
  - **rewind across a compact checkpoint**: `RewindError('not-on-surface')` refuses cleanly, no crash.
59
- - **plan-mode**: `hasOpenTurn` pairs only `turn/start`/`turn/end`; a marker produces no `turn/start`, so a rewind during active plan is unaffected (static review confirmed).
68
+ - **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`).
60
69
  - **agent-loop cancellation**: `finally` guarantees step/turn closure; the rewind force-stop path leaves no dangling frame.
61
70
 
62
71
  ## Known behavior boundaries (deterministic differences, non-crash, documented)
@@ -67,6 +76,7 @@
67
76
  - **Session title auto-regeneration**: the title derives from the surface, so an automatically-derived title may change after a rewind.
68
77
  - **Files written but uncommitted in a cancelled turn**: a `both` rewind cannot restore them (tool side-effect timing; same as Claude Code).
69
78
  - **Attachment files left after a message is shadowed**: attachment storage is not cleaned with the surface (`dsh-attachment-local` not installed, not automatically verified).
79
+ - **Rewind leaves plan mode untouched**: `/plan text` is two independent actions (enter plan mode + steer the message). Rewinding the message undoes only the message — the log-only `plan/mode` state stays active, and the user leaves plan mode with `/plan off`, which still commits after a rewind (the marker creates no open turn). Pin: `verify-host` plan checks (`plan rewind leaves plan mode active`, `/plan off after rewind turns plan mode off`), `tests/hidden.test.ts` `messageTextAt`.
70
80
 
71
81
  ## Upstream (harness) issues and the plugin's no-compensation stance
72
82
 
@@ -157,7 +167,7 @@ new turn at `phase.turn + 1` (`agent.ts:251-255`) **without closing the leftover
157
167
  | resume / session-query replay | ✓ | — | ✓ | — | — | — | ✓ | — |
158
168
  | tool pipeline (snapshot/restore) | — | — | — | — | — | ✓ | — | ✓ |
159
169
  | client ordering | — | — | ✓ | — | — | — | ✓ | — |
160
- | plan-mode | | — | | — | — | — | — | ✓ (static) |
170
+ | plan-mode | | — | | — | — | — | — | ✓ (static) |
161
171
 
162
172
  ✓ = probe passes; — = not applicable. RU-I18N and R-OPENSTEP are upstream issues the plugin
163
173
  does not compensate for (see above); G3 is a confirmed-non-defect behavior pinned in
@@ -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
24
+ const chat = session.getSnapshot().chat // rc.2 source; 0.1.2-alpha.1+ uses the uiConversation "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
23
+ const chat = session.getSnapshot().chat // rc.2 来源;0.1.2-alpha.1+ 用 uiConversation "chat" 视图
24
24
  const hidden = hiddenSeqsOf(chat as HiddenChat) // 被隐藏的 anchor seq 集合
25
25
  ```
26
26
 
@@ -54,18 +54,18 @@ git push origin main --tags # triggers .github/workflows/publish.yml
54
54
 
55
55
  DSH is still in rc; npm's prerelease matching rules require a peer range to
56
56
  share the host version's `[major, minor, patch]` tuple. So `peerDependencies`
57
- uses an OR-union covering every published rc tuple series
58
- (e.g. `^0.1.0-rc.6 || ^0.1.1-rc.2`), extended as DSH releases new tuples.
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
59
 
60
60
  - **When to update**: only when DSH releases a new tuple
61
61
  (`0.1.1 → 0.1.2 → 0.2.x`); rc rolling within a tuple (`0.1.1-rc.2 → rc.3`)
62
62
  needs nothing. All `@deepseek-ai/*` packages release together;
63
63
  `npm view @deepseek-ai/dsh version` is the authoritative signal.
64
64
  - **Published-tuple check (optional)**: `node scripts/check-dsh-version.mjs`
65
- compares the latest npm version against the tuples the peers cover (exit 0 =
66
- nothing to do, exit 1 = update). It reads npm published versions only; a
67
- newer-but-unpublished pre-release is a manual pre-release check see the
68
- "Before bumping" step above.
65
+ compares the `latest` dist-tag version against the tuples the peers cover
66
+ (exit 0 = nothing to do, exit 1 = update). It reads the `latest` tag only; a
67
+ pre-release published under another tag (e.g. `alpha`) or bundled without
68
+ going to npm is a manual pre-release check — see the "Before bumping" step above.
69
69
  - **Update steps**: append `|| ^<new-tuple>-rc.<n>` to every
70
70
  `@deepseek-ai/dsh-*` peer → bump devDependencies to the latest → `npm
71
71
  install` → `npm run check` → release.
@@ -49,15 +49,16 @@ git push origin main --tags # 触发 .github/workflows/publish.yml
49
49
 
50
50
  DSH 仍在 rc 阶段,npm 的 prerelease 匹配规则要求 peer 范围与宿主版本
51
51
  **同 `[major, minor, patch]` 元组**才能匹配。因此 peerDependencies 采用
52
- OR 并集覆盖 DSH 已发布的每个 rc 元组系列(如 `^0.1.0-rc.6 || ^0.1.1-rc.2`),
52
+ OR 并集覆盖 DSH 已发布的每个元组系列(如 `^0.1.0-rc.6 || ^0.1.1-rc.2 || ^0.1.2-alpha.2`),
53
53
  并随 DSH 发版追加。
54
54
 
55
55
  - **何时需要更新**:仅当 DSH 发布新元组(`0.1.1 → 0.1.2 → 0.2.x`)时;
56
56
  同元组内 rc 滚动(`0.1.1-rc.2 → rc.3`)无需动作。DSH 所有包同版本发布,
57
57
  `npm view @deepseek-ai/dsh version` 即权威信号。
58
- - **已发布元组检查(可选)**:`node scripts/check-dsh-version.mjs` 用 npm 最新版本
59
- 对比 peer 覆盖的元组(exit 0 无需动作,exit 1 需要)。它**只读 npm 已发布版本**;
60
- 更新的未发布 pre-release 走**手动发布前检查**——见上文"升版前手动确认"。
58
+ - **已发布元组检查(可选)**:`node scripts/check-dsh-version.mjs` 用 npm `latest`
59
+ dist-tag 版本对比 peer 覆盖的元组(exit 0 无需动作,exit 1 需要)。它**只读
60
+ `latest` tag**;发布在其它 tag(如 `alpha`)的 pre-release 走**手动发布前检查**
61
+ ——见上文"升版前手动确认"。
61
62
  - **更新步骤**:给每个 `@deepseek-ai/dsh-*` peer 追加 `|| ^<新元组>-rc.<n>`
62
63
  → devDependencies 同步升到最新 → `npm install` → `npm run check` → 发版。
63
64
  - **正式版后收敛**:DSH 发布 final 版本后,正式版不受 prerelease 元组规则
package/lib/client.js CHANGED
@@ -40,6 +40,18 @@ function chatSnapshotOf(face, chatView) {
40
40
  if (legacy !== void 0) return legacy;
41
41
  return chatView?.getSnapshot() ?? void 0;
42
42
  }
43
+ function messageTextAt(chat, seq) {
44
+ if (chat === void 0) return void 0;
45
+ for (const key of chat.order) {
46
+ const node = chat.nodes.get(key);
47
+ if (node === void 0 || node.kind !== "user" && node.kind !== "steering") continue;
48
+ const data = node.data;
49
+ if (data.seq === seq) {
50
+ return data.content?.map((block) => block.type === "text" && typeof block.text === "string" ? block.text : "").join("");
51
+ }
52
+ }
53
+ return void 0;
54
+ }
43
55
  function targetSeqOfArgs(args) {
44
56
  if (args === void 0 || args === null) return void 0;
45
57
  const match = args.match(/@(\d+)/);
@@ -677,20 +689,8 @@ function retractSpan(steering, targetId) {
677
689
 
678
690
  // src/client/portals.tsx
679
691
  var import_jsx_runtime = require("react/jsx-runtime");
680
- function userTextAt(chat, seq) {
681
- if (chat === void 0) return void 0;
682
- for (const key of chat.order) {
683
- const node = chat.nodes.get(key);
684
- if (node === void 0 || node.kind !== "user") continue;
685
- const user = node.data;
686
- if (user.seq === seq) {
687
- return user.content.map((block) => block.type === "text" && typeof block.text === "string" ? block.text : "").join("");
688
- }
689
- }
690
- return void 0;
691
- }
692
- function fillComposer(text) {
693
- const textarea = document.querySelector(COMPOSER_SELECTOR);
692
+ function fillComposerTextarea(text) {
693
+ const textarea = document.querySelector(COMPOSER_TEXTAREA_SELECTOR);
694
694
  if (textarea === null) return false;
695
695
  const setter = Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, "value")?.set;
696
696
  setter?.call(textarea, text);
@@ -698,12 +698,48 @@ function fillComposer(text) {
698
698
  textarea.focus();
699
699
  return true;
700
700
  }
701
+ function fillComposerEditable(text) {
702
+ const editable = document.querySelector(COMPOSER_EDITABLE_SELECTOR);
703
+ if (editable === null) return false;
704
+ editable.focus();
705
+ const selection = document.getSelection();
706
+ if (selection !== null) {
707
+ const range = document.createRange();
708
+ range.selectNodeContents(editable);
709
+ selection.removeAllRanges();
710
+ selection.addRange(range);
711
+ }
712
+ let ok = false;
713
+ try {
714
+ ok = document.execCommand("insertText", false, text);
715
+ } catch {
716
+ ok = false;
717
+ }
718
+ if (ok) return true;
719
+ editable.textContent = text;
720
+ editable.dispatchEvent(new Event("input", { bubbles: true }));
721
+ return true;
722
+ }
723
+ function fillComposer(text) {
724
+ if (fillComposerTextarea(text)) return true;
725
+ return fillComposerEditable(text);
726
+ }
727
+ function writeComposer(text, facade) {
728
+ if (facade !== void 0) {
729
+ try {
730
+ facade.setDraft(text);
731
+ return true;
732
+ } catch {
733
+ }
734
+ }
735
+ return fillComposer(text);
736
+ }
701
737
  function userNodeOf(chat, key) {
702
738
  const node = chat?.nodes.get(key);
703
739
  if (node === void 0 || node.kind !== "user" && node.kind !== "steering") return void 0;
704
740
  return node.data;
705
741
  }
706
- async function runRewindAndFill(session, seq, mode, currentSessionId, chatOf) {
742
+ async function runRewindAndFill(session, seq, mode, currentSessionId, chatOf, setComposerText) {
707
743
  const known = knownCommandSeqs(session, chatOf, (node) => isExecutedRewindCommand(node, seq));
708
744
  const result = await session.command(`/rewind @${seq} ${mode}`);
709
745
  if (!result.ok || result.value?.matched !== true) return;
@@ -714,30 +750,34 @@ async function runRewindAndFill(session, seq, mode, currentSessionId, chatOf) {
714
750
  return;
715
751
  }
716
752
  if (currentSessionId() !== session.sessionId) return;
717
- const text = userTextAt(chatOf(session), seq);
753
+ const text = messageTextAt(chatOf(session), seq);
718
754
  if (text === void 0 || text === "") return;
719
- fillComposer(text);
755
+ setComposerText(session.sessionId, text);
756
+ }
757
+ function composerSurface() {
758
+ return document.querySelector(COMPOSER_TEXTAREA_SELECTOR) ?? document.querySelector(COMPOSER_EDITABLE_SELECTOR);
720
759
  }
721
760
  function showHint(text) {
722
- const textarea = document.querySelector(COMPOSER_SELECTOR);
761
+ const surface = composerSurface();
723
762
  const hint = document.createElement("div");
724
763
  hint.className = CLASS.guardHint;
725
764
  hint.setAttribute("role", "status");
726
765
  hint.textContent = text;
727
766
  document.body.appendChild(hint);
728
- if (textarea !== null) {
729
- const card = textarea.closest("[data-composer-card]");
730
- const rect = card instanceof HTMLElement ? card.getBoundingClientRect() : textarea.getBoundingClientRect();
767
+ if (surface !== null) {
768
+ const card = surface.closest("[data-composer-card]");
769
+ const rect = card instanceof HTMLElement ? card.getBoundingClientRect() : surface.getBoundingClientRect();
731
770
  hint.style.left = `${Math.round(rect.left)}px`;
732
771
  hint.style.bottom = `${Math.round(window.innerHeight - rect.top + 8)}px`;
733
772
  }
734
773
  window.setTimeout(() => hint.remove(), 3200);
735
774
  }
736
- var COMPOSER_SELECTOR = "[data-input-scroll] textarea, textarea[data-phase]";
775
+ var COMPOSER_TEXTAREA_SELECTOR = "[data-input-scroll] textarea, textarea[data-phase]";
776
+ var COMPOSER_EDITABLE_SELECTOR = "[data-composer-input]";
737
777
  var USER_SEAT_SELECTOR = '[data-chat-flow-kind="user"][data-chat-anchor-key], [data-chat-flow-kind="steering"][data-chat-anchor-key]';
738
778
  var CHAT_SEAT_SELECTOR = "[data-chat-anchor-key]";
739
- var ACTIONS_ROOT_SELECTOR = "[data-time-hover-root]";
740
- var PENDING_SEAT_SELECTOR = "[data-pending-steering][data-time-hover-root]";
779
+ var ACTIONS_ROOT_SELECTOR = "[data-time-hover-root], [data-actions-reveal]";
780
+ var PENDING_SEAT_SELECTOR = "[data-pending-steering][data-time-hover-root], [data-pending-steering][data-actions-reveal]";
741
781
  function collectTargets(chat, hiddenSeqs) {
742
782
  const rows = /* @__PURE__ */ new Map();
743
783
  for (const element of document.querySelectorAll(USER_SEAT_SELECTOR)) {
@@ -801,7 +841,7 @@ function sameTargets(left, right) {
801
841
  return other.kind === "pending" && target.itemId === other.itemId;
802
842
  });
803
843
  }
804
- function RewindPortals({ sessionId, sessionOf, chatOf, currentSessionId, t, subscribeLocale }) {
844
+ function RewindPortals({ sessionId, sessionOf, chatOf, currentSessionId, t, subscribeLocale, setComposerText }) {
805
845
  const [targets, setTargets] = (0, import_react.useState)([]);
806
846
  const hidden = (0, import_react.useRef)(/* @__PURE__ */ new WeakSet());
807
847
  const [, forceRender] = (0, import_react.useReducer)((count) => count + 1, 0);
@@ -869,6 +909,7 @@ function RewindPortals({ sessionId, sessionOf, chatOf, currentSessionId, t, subs
869
909
  sessionId,
870
910
  sessionOf,
871
911
  chatOf,
912
+ setComposerText,
872
913
  t
873
914
  },
874
915
  target.key
@@ -880,6 +921,7 @@ function RewindPortals({ sessionId, sessionOf, chatOf, currentSessionId, t, subs
880
921
  sessionOf,
881
922
  chatOf,
882
923
  currentSessionId,
924
+ setComposerText,
883
925
  t
884
926
  },
885
927
  target.key
@@ -888,7 +930,7 @@ function RewindPortals({ sessionId, sessionOf, chatOf, currentSessionId, t, subs
888
930
  target.key
889
931
  ));
890
932
  }
891
- function RewindButton({ target, sessionId, sessionOf, chatOf, currentSessionId, t }) {
933
+ function RewindButton({ target, sessionId, sessionOf, chatOf, currentSessionId, setComposerText, t }) {
892
934
  const onClick = (event) => {
893
935
  event.stopPropagation();
894
936
  const session = sessionOf(sessionId);
@@ -907,7 +949,7 @@ function RewindButton({ target, sessionId, sessionOf, chatOf, currentSessionId,
907
949
  anchor: event.currentTarget,
908
950
  t,
909
951
  onRewind: (mode) => {
910
- void runRewindAndFill(session, node.seq, mode, currentSessionId, chatOf);
952
+ void runRewindAndFill(session, node.seq, mode, currentSessionId, chatOf, setComposerText);
911
953
  }
912
954
  });
913
955
  };
@@ -924,10 +966,12 @@ function RewindButton({ target, sessionId, sessionOf, chatOf, currentSessionId,
924
966
  );
925
967
  }
926
968
  function composerText() {
927
- const textarea = document.querySelector(COMPOSER_SELECTOR);
928
- return textarea === null ? "" : textarea.value;
969
+ const surface = composerSurface();
970
+ if (surface === null) return "";
971
+ if (surface instanceof HTMLTextAreaElement) return surface.value;
972
+ return surface.textContent ?? "";
929
973
  }
930
- async function retractPending(session, itemId, text) {
974
+ async function retractPending(session, itemId, text, setComposerText) {
931
975
  await session.cancel();
932
976
  const queue = session.getSnapshot().queue;
933
977
  const steering = queue.filter((item) => item.placement === "steering");
@@ -935,10 +979,10 @@ async function retractPending(session, itemId, text) {
935
979
  await session.updateQueue(id, { kind: "remove" });
936
980
  }
937
981
  if (text !== null && text !== "" && composerText().trim() === "") {
938
- fillComposer(text);
982
+ setComposerText(session.sessionId, text);
939
983
  }
940
984
  }
941
- function RetractButton({ target, sessionId, sessionOf, chatOf, t }) {
985
+ function RetractButton({ target, sessionId, sessionOf, chatOf, setComposerText, t }) {
942
986
  const onClick = (event) => {
943
987
  event.stopPropagation();
944
988
  const session = sessionOf(sessionId);
@@ -951,7 +995,7 @@ function RetractButton({ target, sessionId, sessionOf, chatOf, t }) {
951
995
  t,
952
996
  retract: { itemId: target.itemId, text: target.text },
953
997
  onRetract: () => {
954
- void retractPending(session, target.itemId, target.text);
998
+ void retractPending(session, target.itemId, target.text, setComposerText);
955
999
  }
956
1000
  });
957
1001
  };
@@ -1032,7 +1076,8 @@ var name = "dsh-rewind";
1032
1076
  var inject = ["slots", "sessions", "locale", "commandUi"];
1033
1077
  var NS = "rewind";
1034
1078
  var HEADER_ACTIONS_SLOT = "conversation.session.header.actions";
1035
- var COMPOSER_SELECTOR2 = "[data-input-scroll] textarea, textarea[data-phase]";
1079
+ var COMPOSER_TEXTAREA_SELECTOR2 = "[data-input-scroll] textarea, textarea[data-phase]";
1080
+ var COMPOSER_EDITABLE_SELECTOR2 = "[data-composer-input]";
1036
1081
  function apply(ctx) {
1037
1082
  ctx.effect(function* () {
1038
1083
  yield ctx.locale.register(NS, { zh, en });
@@ -1055,6 +1100,17 @@ function apply(ctx) {
1055
1100
  return void 0;
1056
1101
  }
1057
1102
  };
1103
+ const setComposerText = (sessionId, text) => {
1104
+ const conversation = ctx.get("conversation");
1105
+ const input = conversation?.input;
1106
+ const scope = ctx.sessions.scope?.(sessionId);
1107
+ return writeComposer(
1108
+ text,
1109
+ input !== void 0 && scope !== void 0 ? { setDraft: (draft) => {
1110
+ input.for(scope).setDraft(draft);
1111
+ } } : void 0
1112
+ );
1113
+ };
1058
1114
  const slots = ctx.slots;
1059
1115
  yield slots.inject(HEADER_ACTIONS_SLOT, () => slots.register(
1060
1116
  {
@@ -1064,7 +1120,7 @@ function apply(ctx) {
1064
1120
  id: "dsh-rewind-portals",
1065
1121
  order: 1e3
1066
1122
  },
1067
- createRewindBridge({ sessionOf, chatOf, currentSessionId, t, subscribeLocale })
1123
+ createRewindBridge({ sessionOf, chatOf, currentSessionId, setComposerText, t, subscribeLocale })
1068
1124
  ));
1069
1125
  const commandUi = ctx.get("commandUi");
1070
1126
  const hasCandidates = (sessionId) => {
@@ -1082,9 +1138,9 @@ function apply(ctx) {
1082
1138
  };
1083
1139
  const hostCandidatesCache = /* @__PURE__ */ new Map();
1084
1140
  const composerAnchor = () => {
1085
- const textarea = composerTextarea();
1086
- const card = textarea?.closest("[data-composer-card]");
1087
- return card ?? textarea ?? document.body;
1141
+ const surface = composerSurface2();
1142
+ const card = surface?.closest("[data-composer-card]");
1143
+ return card ?? surface ?? document.body;
1088
1144
  };
1089
1145
  const rewindPopupSpec = {
1090
1146
  // The picker exists exactly while the surface has a reachable user
@@ -1117,7 +1173,7 @@ function apply(ctx) {
1117
1173
  anchor: composerAnchor(),
1118
1174
  t,
1119
1175
  onRewind: (mode) => {
1120
- void runRewindAndFill(face, candidate.seq, mode, currentSessionId, chatOf);
1176
+ void runRewindAndFill(face, candidate.seq, mode, currentSessionId, chatOf, setComposerText);
1121
1177
  }
1122
1178
  });
1123
1179
  }
@@ -1126,7 +1182,7 @@ function apply(ctx) {
1126
1182
  for (const name2 of ["rewind", "undo"]) {
1127
1183
  yield commandUi.decorate({ name: name2, ...rewindPopupSpec });
1128
1184
  }
1129
- const composerTextarea = () => document.querySelector(COMPOSER_SELECTOR2);
1185
+ const composerSurface2 = () => document.querySelector(COMPOSER_TEXTAREA_SELECTOR2) ?? document.querySelector(COMPOSER_EDITABLE_SELECTOR2);
1130
1186
  yield () => {
1131
1187
  style.remove();
1132
1188
  };
package/lib/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // src/index.ts
2
2
  import { createAssistantMessage } from "@deepseek-ai/dsh-llm";
3
3
  import { unlink } from "node:fs/promises";
4
- import { settingsNamespace } from "@deepseek-ai/dsh-settings";
4
+ import * as dshSettings from "@deepseek-ai/dsh-settings";
5
5
 
6
6
  // src/locales.ts
7
7
  var en = {
@@ -114,6 +114,12 @@ function translate(lang, key, params = {}) {
114
114
  return text;
115
115
  }
116
116
 
117
+ // src/settings-locale.ts
118
+ function readSettingsSection(provider, ns, brand) {
119
+ const key = brand?.(ns) ?? ns;
120
+ return provider.get(key);
121
+ }
122
+
117
123
  // src/rewind.ts
118
124
  var RewindError = class extends Error {
119
125
  constructor(code, message) {
@@ -1887,7 +1893,11 @@ function apply(ctx, config) {
1887
1893
  const trackedBySession = /* @__PURE__ */ new Map();
1888
1894
  let fsService;
1889
1895
  ctx.inject(["settings"], (settingsCtx) => {
1890
- const section = settingsCtx.settings.get(settingsNamespace("locale"));
1896
+ const section = readSettingsSection(
1897
+ settingsCtx.settings,
1898
+ "locale",
1899
+ dshSettings.settingsNamespace
1900
+ );
1891
1901
  if (section?.preference === "zh" || section?.preference === "en") {
1892
1902
  activeLocale = section.preference;
1893
1903
  }
@@ -41,6 +41,16 @@ export declare function chatSnapshotOf(face: {
41
41
  } | undefined, chatView: {
42
42
  getSnapshot(): unknown;
43
43
  } | undefined): HiddenChat | undefined;
44
+ /**
45
+ * The plain text of the human message at `seq` in the chat snapshot, for
46
+ * filling the composer after a withdraw. Accepts BOTH `user` and `steering`
47
+ * nodes: a plan-mode (`/plan <text>`) input is delivered through the agent
48
+ * inbox next-step and claimed, so it renders as `steering`, and its text must
49
+ * still return to the composer (`portals.tsx` `runRewindAndFill`) — the old
50
+ * `user`-only read silently left it empty. State absent → undefined; a message
51
+ * with no text blocks → ''. Same text-blocks join the candidate side uses.
52
+ */
53
+ export declare function messageTextAt(chat: HiddenChat | undefined, seq: number): string | undefined;
44
54
  /**
45
55
  * Extract the rewind target seq from a `/rewind` command's structured `args`
46
56
  * (e.g. `@5 chat`, `preview @5 both`). Locale-independent — never parses the
@@ -61,6 +61,13 @@ export interface RewindBridgeDeps {
61
61
  readonly currentSessionId: () => string | undefined;
62
62
  readonly t: Translate;
63
63
  readonly subscribeLocale: (cb: () => void) => () => void;
64
+ /**
65
+ * Session-aware dual-channel composer writer (see `writeComposer`): the
66
+ * alpha.1+ `conversation.input` facade `setDraft` when reachable, else the
67
+ * rc.2/alpha.1 DOM fill. Session-scoped so the refill only lands in the
68
+ * session that just rewound.
69
+ */
70
+ readonly setComposerText: (sessionId: string, text: string) => boolean;
64
71
  }
65
72
  /** Structural face of the runtime slot service (see the module doc). */
66
73
  export interface SlotsLike {
@@ -73,14 +80,36 @@ export interface SlotsLike {
73
80
  readonly sessionId: string;
74
81
  }) => ReactNode): () => void;
75
82
  }
83
+ /** Join the text blocks of a user message into one plain preview. */
76
84
  /**
77
- * Fill the dsh composer with `text` (React-controlled textarea: use the
78
- * native setter so the value change is seen, then dispatch an input event).
79
- * Best-effort no composer match means the fill is skipped. Used by
80
- * `runRewindAndFill` to put the withdrawn target message back into the
81
- * composer after a rewind.
85
+ * The alpha.1+ session input facade's write face (structural, so the plugin
86
+ * never imports the conversation UI package). `setDraft` replaces the whole
87
+ * composer draft through the harness's own Lexical editor the correct way
88
+ * to restore the withdrawn text.
89
+ */
90
+ interface ComposerDraftWriter {
91
+ setDraft(text: string): void;
92
+ }
93
+ /**
94
+ * Fill the dsh composer with `text`. DOM dual-channel fallback: the rc.2
95
+ * `<textarea>` path, then the alpha.1+ `contenteditable` path. Used by
96
+ * `setComposerText` (the harness-facade-aware writer) as the last-resort and
97
+ * by `runRewindAndFill` to put the withdrawn target message back into the
98
+ * composer after a rewind. Best-effort — no composer match means false,
99
+ * never a throw.
82
100
  */
83
101
  export declare function fillComposer(text: string): boolean;
102
+ /**
103
+ * Dual-channel composer write, mirroring `chatSnapshotOf`: prefer the harness
104
+ * facade's `setDraft` (alpha.1+, correct whole-draft replace), then degrade
105
+ * to the DOM `fillComposer` (rc.2 textarea / alpha.1 contenteditable). A
106
+ * facade that throws (session teardown) is treated as absent so the DOM path
107
+ * still restores the text. Never throws.
108
+ * @param text - the withdrawn target message text.
109
+ * @param facade - the alpha.1+ session input draft writer, when reachable.
110
+ * @returns whether a channel applied the text.
111
+ */
112
+ export declare function writeComposer(text: string, facade: ComposerDraftWriter | undefined): boolean;
84
113
  /**
85
114
  * Execute one rewind from the popover and, when it settles successfully,
86
115
  * put the withdrawn target message's text back into the composer so the
@@ -94,7 +123,7 @@ export declare function fillComposer(text: string): boolean;
94
123
  * the old baseline heuristic refilled withdrawn text into the composer
95
124
  * after switching sessions or restarting dsh.
96
125
  */
97
- export declare function runRewindAndFill(session: SessionFace, seq: number, mode: 'chat' | 'both', currentSessionId: () => string | undefined, chatOf: ChatOf): Promise<void>;
126
+ export declare function runRewindAndFill(session: SessionFace, seq: number, mode: 'chat' | 'both', currentSessionId: () => string | undefined, chatOf: ChatOf, setComposerText: (sessionId: string, text: string) => boolean): Promise<void>;
98
127
  /**
99
128
  * Collect the portal targets of one session: user rows × snapshot nodes.
100
129
  * Exported as a test seam — the DOM→targets pairing that drives the ↶ button
@@ -111,7 +140,11 @@ interface RewindPortalsProps extends RewindBridgeDeps {
111
140
  * skipped when the target set is unchanged), so the plugin never runs a
112
141
  * synchronous full-transcript scan inside a commit microtask.
113
142
  */
114
- export declare function RewindPortals({ sessionId, sessionOf, chatOf, currentSessionId, t, subscribeLocale }: RewindPortalsProps): ReactNode;
143
+ export declare function RewindPortals({ sessionId, sessionOf, chatOf, currentSessionId, t, subscribeLocale, setComposerText }: RewindPortalsProps): ReactNode;
144
+ /** The current composer draft, on whichever channel: rc.2 textarea `.value`,
145
+ * alpha.1+ contenteditable `textContent`. Empty when the composer is absent.
146
+ * Exported as a test seam (the empty-composer guard in `retractPending`). */
147
+ export declare function composerText(): string;
115
148
  /**
116
149
  * Build the slot-entry component for the plugin apply(): a tiny bridge that
117
150
  * injects the apply-time capabilities (session resolution, locale, rewind
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Version-neutral settings-namespace reading for the host half.
3
+ *
4
+ * DSH rc.2 exposes a `settingsNamespace(value)` brand helper, and `settings.get`
5
+ * is typed to require a branded `SettingsNamespace`. DSH 0.1.2-alpha.2 removed
6
+ * that helper (the `SettingsNamespace` *type* remains) and `settings.get`
7
+ * accepts the raw namespace string. This module collapses both into one runtime
8
+ * call: on rc.2 the brand is a compile-time marker erased at runtime (so
9
+ * `settingsNamespace(ns)` returns `ns`), on 0.1.2-alpha.2 the brand helper is
10
+ * absent and the raw `ns` string is used directly. That is what lets a single
11
+ * compiled host bundle link and run on both harness generations.
12
+ *
13
+ * @module dsh-rewind/settings-locale
14
+ */
15
+ /** Minimal structural face of the settings provider the host reads from. */
16
+ export interface SettingsProviderLike {
17
+ /**
18
+ * Read one registered settings section by namespace. Accepts whatever the
19
+ * running DSH generation passes: a branded `SettingsNamespace` (rc.2, brand
20
+ * erased) or the raw namespace string (0.1.2-alpha.2).
21
+ */
22
+ get(ns: string): unknown;
23
+ }
24
+ /** The `settingsNamespace(ns)` brand helper, or `undefined` when removed (0.1.2-alpha.2). */
25
+ export type SettingsNamespaceBrand = ((value: string) => string) | undefined;
26
+ /**
27
+ * Read one settings section keyed by `ns`, tolerant of the settings-namespace
28
+ * brand across DSH generations. Pass the brand helper when available (the rc.2
29
+ * path); it is `undefined` on 0.1.2-alpha.2, where the raw `ns` string is used
30
+ * directly. Never throws: an absent section simply returns `undefined`.
31
+ */
32
+ export declare function readSettingsSection(provider: SettingsProviderLike, ns: string, brand: SettingsNamespaceBrand): unknown;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-rewind-plugin",
3
- "version": "0.6.1",
3
+ "version": "0.6.3",
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",
@@ -72,18 +72,18 @@
72
72
  },
73
73
  "peerDependencies": {
74
74
  "@deepseek-ai/cordis": "^4.0.1",
75
- "@deepseek-ai/dsh-client-locale": "^0.1.0-rc.6 || ^0.1.1-rc.2",
75
+ "@deepseek-ai/dsh-client-locale": "^0.1.0-rc.6 || ^0.1.1-rc.2 || ^0.1.2-alpha.2",
76
76
  "@deepseek-ai/dsh-client-runtime": "^0.1.0-rc.6 || ^0.1.1-rc.2",
77
- "@deepseek-ai/dsh-client-ui-commands": "^0.1.0-rc.6 || ^0.1.1-rc.2",
78
- "@deepseek-ai/dsh-client-ui-slots": "^0.1.0-rc.6 || ^0.1.1-rc.2",
79
- "@deepseek-ai/dsh-commands": "^0.1.0-rc.6 || ^0.1.1-rc.2",
80
- "@deepseek-ai/dsh-fs": "^0.1.0-rc.6 || ^0.1.1-rc.2",
81
- "@deepseek-ai/dsh-home-paths": "^0.1.0-rc.6 || ^0.1.1-rc.2",
82
- "@deepseek-ai/dsh-llm": "^0.1.0-rc.6 || ^0.1.1-rc.2",
83
- "@deepseek-ai/dsh-sandbox": "^0.1.0-rc.6 || ^0.1.1-rc.2",
84
- "@deepseek-ai/dsh-session": "^0.1.0-rc.6 || ^0.1.1-rc.2",
85
- "@deepseek-ai/dsh-settings": "^0.1.0-rc.8 || ^0.1.1-rc.2",
86
- "@deepseek-ai/dsh-tools": "^0.1.0-rc.6 || ^0.1.1-rc.2"
77
+ "@deepseek-ai/dsh-client-ui-commands": "^0.1.0-rc.6 || ^0.1.1-rc.2 || ^0.1.2-alpha.2",
78
+ "@deepseek-ai/dsh-client-ui-slots": "^0.1.0-rc.6 || ^0.1.1-rc.2 || ^0.1.2-alpha.2",
79
+ "@deepseek-ai/dsh-commands": "^0.1.0-rc.6 || ^0.1.1-rc.2 || ^0.1.2-alpha.2",
80
+ "@deepseek-ai/dsh-fs": "^0.1.0-rc.6 || ^0.1.1-rc.2 || ^0.1.2-alpha.2",
81
+ "@deepseek-ai/dsh-home-paths": "^0.1.0-rc.6 || ^0.1.1-rc.2 || ^0.1.2-alpha.2",
82
+ "@deepseek-ai/dsh-llm": "^0.1.0-rc.6 || ^0.1.1-rc.2 || ^0.1.2-alpha.2",
83
+ "@deepseek-ai/dsh-sandbox": "^0.1.0-rc.6 || ^0.1.1-rc.2 || ^0.1.2-alpha.2",
84
+ "@deepseek-ai/dsh-session": "^0.1.0-rc.6 || ^0.1.1-rc.2 || ^0.1.2-alpha.2",
85
+ "@deepseek-ai/dsh-settings": "^0.1.0-rc.8 || ^0.1.1-rc.2 || ^0.1.2-alpha.2",
86
+ "@deepseek-ai/dsh-tools": "^0.1.0-rc.6 || ^0.1.1-rc.2 || ^0.1.2-alpha.2"
87
87
  },
88
88
  "peerDependenciesMeta": {
89
89
  "@deepseek-ai/cordis": {
@@ -142,6 +142,7 @@
142
142
  "@deepseek-ai/dsh-goal": "^0.1.1-rc.2",
143
143
  "@deepseek-ai/dsh-home-paths": "^0.1.1-rc.2",
144
144
  "@deepseek-ai/dsh-llm": "^0.1.1-rc.2",
145
+ "@deepseek-ai/dsh-plan-mode": "^0.1.1-rc.2",
145
146
  "@deepseek-ai/dsh-sandbox": "^0.1.1-rc.2",
146
147
  "@deepseek-ai/dsh-session": "^0.1.1-rc.2",
147
148
  "@deepseek-ai/dsh-session-projection": "^0.1.1-rc.2",