dsh-rewind-plugin 0.6.2 → 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 +2 -2
- package/README.md +2 -2
- package/SECURITY.md +14 -7
- package/docs/architecture.md +1 -1
- package/docs/compat/audit.md +26 -20
- package/docs/release/release.md +6 -6
- package/docs/release/release.zh.md +5 -4
- package/lib/client.js +15 -15
- package/lib/index.js +12 -2
- package/lib/types/client/hidden.d.ts +10 -0
- package/lib/types/client/portals.d.ts +1 -0
- package/lib/types/settings-locale.d.ts +32 -0
- package/package.json +13 -12
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;
|
|
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
|
|
@@ -156,7 +156,7 @@ withdrew should consume the stable, locale-independent helpers exported from
|
|
|
156
156
|
|
|
157
157
|
## Security
|
|
158
158
|
|
|
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
|
|
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).
|
|
160
160
|
|
|
161
161
|
## Development
|
|
162
162
|
|
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ DeepSeek Harness 插件:**一键就地回退对话到任意更早的用户消
|
|
|
12
12
|
|
|
13
13
|
- **回退 = 时间回溯**——目标消息及其之后的全部内容(agent 回复、工具调用)同时从**模型上下文**和**渲染对话**中撤回,不新建会话、不切换窗口;目标消息文本会回填输入框,改完可重发。**在原理上就真正无感、便捷**。
|
|
14
14
|
- **轻量工作区备份**——对齐 Claude Code:追踪写类工具编辑过的文件,**已跟踪文件的外部变更也能还原**。局部追踪、写前备份、不变不存。一个轻型插件,即拥有**完备的智能体回退能力**。
|
|
15
|
-
- **信息安全优先**——插件从不删改会话日志(append-only
|
|
15
|
+
- **信息安全优先**——插件从不删改会话日志(append-only),从不真正删除你的任何对话;备份存于自己的快照目录;还原只用这些备份。完整安全模型:[SECURITY.md](SECURITY.md)。
|
|
16
16
|
- **完备测试系统**——单元、探针、端到端主机验证,覆盖兼容性探测、日志重放、续接、跨重启等场景;随 harness 升级持续维护,确保功能稳定。
|
|
17
17
|
|
|
18
18
|
## 效果预览
|
|
@@ -144,7 +144,7 @@ dsh plugin --profile web add dsh-rewind-plugin
|
|
|
144
144
|
|
|
145
145
|
## 安全
|
|
146
146
|
|
|
147
|
-
|
|
147
|
+
本插件只向会话日志追加回退标记事件,从不删除或改写已记录的历史。工作区文件仅在「回退对话和代码」时被改写,备份存储于 `~/.dsh/rewind-snapshots/`;还原以备份为唯一来源。不触碰你的 git 仓库,无网络请求,不访问任何凭据。对**长期不活跃**的会话,另有默认关闭的全局自动清理可整目录移除其快照,不影响活动会话与对话日志。完整安全模型:[SECURITY.md](SECURITY.md)。
|
|
148
148
|
|
|
149
149
|
## 开发
|
|
150
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.
|
|
87
|
-
|
|
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
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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`
|
package/docs/architecture.md
CHANGED
|
@@ -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
|
|
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`.
|
package/docs/compat/audit.md
CHANGED
|
@@ -11,32 +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
|
|
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
|
|
22
|
-
>
|
|
23
|
-
>
|
|
24
|
-
>
|
|
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
|
|
30
|
-
> crash).
|
|
31
|
-
>
|
|
32
|
-
> `^0.1.0-rc.6 || ^0.1.1-rc.2
|
|
33
|
-
>
|
|
34
|
-
>
|
|
35
|
-
>
|
|
36
|
-
>
|
|
37
|
-
>
|
|
38
|
-
>
|
|
39
|
-
>
|
|
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`.
|
|
40
45
|
|
|
41
46
|
## Definition of "fully compatible" (invariants)
|
|
42
47
|
|
|
@@ -60,7 +65,7 @@
|
|
|
60
65
|
- **session-title / goal fold**: a marker does not disturb `foldSessionTitle` / `foldGoal`.
|
|
61
66
|
- **client ordering**: turn-tail ordering + `step/start` uniqueness hold for tool turns + marker logs.
|
|
62
67
|
- **rewind across a compact checkpoint**: `RewindError('not-on-surface')` refuses cleanly, no crash.
|
|
63
|
-
- **plan-mode**:
|
|
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`).
|
|
64
69
|
- **agent-loop cancellation**: `finally` guarantees step/turn closure; the rewind force-stop path leaves no dangling frame.
|
|
65
70
|
|
|
66
71
|
## Known behavior boundaries (deterministic differences, non-crash, documented)
|
|
@@ -71,6 +76,7 @@
|
|
|
71
76
|
- **Session title auto-regeneration**: the title derives from the surface, so an automatically-derived title may change after a rewind.
|
|
72
77
|
- **Files written but uncommitted in a cancelled turn**: a `both` rewind cannot restore them (tool side-effect timing; same as Claude Code).
|
|
73
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`.
|
|
74
80
|
|
|
75
81
|
## Upstream (harness) issues and the plugin's no-compensation stance
|
|
76
82
|
|
|
@@ -161,7 +167,7 @@ new turn at `phase.turn + 1` (`agent.ts:251-255`) **without closing the leftover
|
|
|
161
167
|
| resume / session-query replay | ✓ | — | ✓ | — | — | — | ✓ | — |
|
|
162
168
|
| tool pipeline (snapshot/restore) | — | — | — | — | — | ✓ | — | ✓ |
|
|
163
169
|
| client ordering | — | — | ✓ | — | — | — | ✓ | — |
|
|
164
|
-
| plan-mode |
|
|
170
|
+
| plan-mode | ✓ | — | ✓ | — | — | — | — | ✓ (static) |
|
|
165
171
|
|
|
166
172
|
✓ = probe passes; — = not applicable. RU-I18N and R-OPENSTEP are upstream issues the plugin
|
|
167
173
|
does not compensate for (see above); G3 is a confirmed-non-defect behavior pinned in
|
package/docs/release/release.md
CHANGED
|
@@ -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
|
|
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
|
|
66
|
-
nothing to do, exit 1 = update). It reads
|
|
67
|
-
|
|
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
|
|
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
|
-
|
|
60
|
-
|
|
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,18 +689,6 @@ 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
692
|
function fillComposerTextarea(text) {
|
|
693
693
|
const textarea = document.querySelector(COMPOSER_TEXTAREA_SELECTOR);
|
|
694
694
|
if (textarea === null) return false;
|
|
@@ -750,7 +750,7 @@ async function runRewindAndFill(session, seq, mode, currentSessionId, chatOf, se
|
|
|
750
750
|
return;
|
|
751
751
|
}
|
|
752
752
|
if (currentSessionId() !== session.sessionId) return;
|
|
753
|
-
const text =
|
|
753
|
+
const text = messageTextAt(chatOf(session), seq);
|
|
754
754
|
if (text === void 0 || text === "") return;
|
|
755
755
|
setComposerText(session.sessionId, text);
|
|
756
756
|
}
|
|
@@ -776,8 +776,8 @@ var COMPOSER_TEXTAREA_SELECTOR = "[data-input-scroll] textarea, textarea[data-ph
|
|
|
776
776
|
var COMPOSER_EDITABLE_SELECTOR = "[data-composer-input]";
|
|
777
777
|
var USER_SEAT_SELECTOR = '[data-chat-flow-kind="user"][data-chat-anchor-key], [data-chat-flow-kind="steering"][data-chat-anchor-key]';
|
|
778
778
|
var CHAT_SEAT_SELECTOR = "[data-chat-anchor-key]";
|
|
779
|
-
var ACTIONS_ROOT_SELECTOR = "[data-time-hover-root]";
|
|
780
|
-
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]";
|
|
781
781
|
function collectTargets(chat, hiddenSeqs) {
|
|
782
782
|
const rows = /* @__PURE__ */ new Map();
|
|
783
783
|
for (const element of document.querySelectorAll(USER_SEAT_SELECTOR)) {
|
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
|
|
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 =
|
|
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
|
|
@@ -80,6 +80,7 @@ export interface SlotsLike {
|
|
|
80
80
|
readonly sessionId: string;
|
|
81
81
|
}) => ReactNode): () => void;
|
|
82
82
|
}
|
|
83
|
+
/** Join the text blocks of a user message into one plain preview. */
|
|
83
84
|
/**
|
|
84
85
|
* The alpha.1+ session input facade's write face (structural, so the plugin
|
|
85
86
|
* never imports the conversation UI package). `setDraft` replaces the whole
|
|
@@ -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.
|
|
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",
|