dsh-rewind-plugin 0.7.3 → 0.7.5
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 +1 -1
- package/README.md +1 -1
- package/docs/compat/audit.md +10 -11
- package/docs/contract/client-contract.md +1 -1
- package/docs/contract/client-contract.zh.md +1 -1
- package/docs/harness-reference.md +1 -2
- package/docs/release/release.md +3 -0
- package/docs/release/release.zh.md +3 -0
- package/lib/client.js +51 -20
- package/lib/types/client/hidden.d.ts +34 -10
- package/lib/types/client/popover.d.ts +12 -5
- package/lib/types/client/portals.d.ts +40 -29
- package/lib/types/client/settings-card.d.ts +5 -4
- package/lib/types/client/styles.d.ts +1 -1
- package/lib/types/session-events.d.ts +10 -7
- package/lib/types/settings-locale.d.ts +11 -10
- package/lib/types/snapshot-cleanup.d.ts +3 -3
- package/package.json +1 -1
package/README.en.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Conversation rewind for DeepSeek Harness: **rewind the conversation to any earlier user message in one click, in the same window** — no new branch, no window switch, with optional workspace-file restore (full Claude Code `/rewind` semantics).
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/dsh-rewind-plugin)
|
|
6
|
-
[](https://www.npmjs.com/package/dsh-rewind-plugin)
|
|
7
7
|
[](https://github.com/SiriLee/dsh-rewind/actions/workflows/ci.yml)
|
|
8
8
|
|
|
9
9
|
> English | [中文](README.md)
|
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
DeepSeek Harness 插件:**一键就地回退对话到任意更早的用户消息**——同窗口内完成,不新建分支、不换窗口,可一并还原工作区文件(完整 Claude Code `/rewind` 语义)。
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/dsh-rewind-plugin)
|
|
6
|
-
[](https://www.npmjs.com/package/dsh-rewind-plugin)
|
|
7
7
|
[](https://github.com/SiriLee/dsh-rewind/actions/workflows/ci.yml)
|
|
8
8
|
|
|
9
9
|
> [English](README.en.md) | 中文
|
package/docs/compat/audit.md
CHANGED
|
@@ -7,30 +7,29 @@
|
|
|
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
|
-
>
|
|
10
|
+
> Targeted version: npm `@deepseek-ai/*@0.1.1-rc.2` (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
13
|
> Version alignment: `peerDependencies` use an OR-union (e.g.
|
|
14
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
|
-
> `[major, minor, patch]` tuple, so
|
|
17
|
-
>
|
|
18
|
-
>
|
|
16
|
+
> `[major, minor, patch]` tuple, so appending is needed only for a genuinely new
|
|
17
|
+
> tuple (e.g. a future `0.2.x`); any prerelease within the SAME tuple (alpha → rc)
|
|
18
|
+
> is a no-op. Signal: `npm view @deepseek-ai/dsh version`;
|
|
19
19
|
> flow: `scripts/check-dsh-version.mjs` (it reads the `latest` dist-tag only; a
|
|
20
20
|
> `-alpha` prerelease published under another tag, e.g. `0.1.2-alpha.2` under
|
|
21
21
|
> `alpha`, is a manual pre-release check).
|
|
22
22
|
>
|
|
23
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
24
|
>
|
|
25
|
-
> `0.1.2-alpha.2` … `0.1.2-alpha.
|
|
26
|
-
>
|
|
27
|
-
>
|
|
28
|
-
>
|
|
29
|
-
> primary baseline; four versions verified: rc.2, alpha.2, alpha.3, alpha.4.
|
|
25
|
+
> `0.1.2-alpha.2` … `0.1.2-alpha.5` and `0.1.2-rc.1` verified-compatible; `rc.1` is
|
|
26
|
+
> source-identical to `alpha.5`. They share the `0.1.2` tuple, so the single
|
|
27
|
+
> `^0.1.2-alpha.2` peer member covers all six. `rc.2` (`latest`) stays the primary
|
|
28
|
+
> baseline; verified: rc.2, alpha.2, alpha.3, alpha.4, alpha.5, rc.1.
|
|
30
29
|
|
|
31
30
|
### Channeled seams (version × channel)
|
|
32
31
|
|
|
33
|
-
| Seam | rc.2 (`0.1.1-rc.2`) | alpha.2 / alpha.3 | alpha.4 |
|
|
32
|
+
| Seam | rc.2 (`0.1.1-rc.2`) | alpha.2 / alpha.3 | alpha.4 / alpha.5 / rc.1 |
|
|
34
33
|
| --- | --- | --- | --- |
|
|
35
34
|
| Host session log (`eventsOf`) | `Session.events` | `Session.events` | `session.snapshotEvents()` |
|
|
36
35
|
| Client chat snapshot (`chatSnapshotOf`) | session-face `chat` field | `uiConversation` `chat` view | `uiConversation` `chat` view |
|
|
@@ -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-
|
|
24
|
+
const chat = session.getSnapshot().chat // rc.2 source; 0.1.2-rc.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 // rc.2 来源;0.1.2-
|
|
23
|
+
const chat = session.getSnapshot().chat // rc.2 来源;0.1.2-rc.1 用 uiConversation "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.
|
|
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/`)
|
package/docs/release/release.md
CHANGED
|
@@ -61,6 +61,9 @@ uses an OR-union covering every published tuple series
|
|
|
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
|
+
- **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).
|
|
64
67
|
- **Published-tuple check (optional)**: `node scripts/check-dsh-version.mjs`
|
|
65
68
|
compares the `latest` dist-tag version against the tuples the peers cover
|
|
66
69
|
(exit 0 = nothing to do, exit 1 = update). It reads the `latest` tag only; a
|
|
@@ -55,6 +55,9 @@ OR 并集覆盖 DSH 已发布的每个元组系列(如 `^0.1.0-rc.6 || ^0.1.1-
|
|
|
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
|
+
- **例外 —— `@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` 元组项)。
|
|
58
61
|
- **已发布元组检查(可选)**:`node scripts/check-dsh-version.mjs` 用 npm `latest`
|
|
59
62
|
dist-tag 版本对比 peer 覆盖的元组(exit 0 无需动作,exit 1 需要)。它**只读
|
|
60
63
|
`latest` tag**;发布在其它 tag(如 `alpha`)的 pre-release 走**手动发布前检查**
|
package/lib/client.js
CHANGED
|
@@ -35,6 +35,13 @@ __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) {
|
|
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) ?? (() => {
|
|
43
|
+
});
|
|
44
|
+
}
|
|
38
45
|
function chatSnapshotOf(face, chatView) {
|
|
39
46
|
const legacy = face?.getSnapshot().chat;
|
|
40
47
|
if (legacy !== void 0) return legacy;
|
|
@@ -321,6 +328,12 @@ var STYLE = `
|
|
|
321
328
|
}
|
|
322
329
|
|
|
323
330
|
/* ---- Snapshot-cleanup settings card (mirrors the harness PluginCard look) ---- */
|
|
331
|
+
/* Dual-channel note: the harness card corner/border drifted across versions \u2014
|
|
332
|
+
0.1.1-rc.2 uses border-radius 12px + 1px solid border-l2, while 0.1.2-rc.1
|
|
333
|
+
uses 16px + 0.5px border-l4 (and adds corner-shape: round). We intentionally
|
|
334
|
+
keep the 0.1.1-rc.2 values here for now; if the 0.1.2-rc.1 look is wanted,
|
|
335
|
+
switch these on the host version like the other dual-channel surfaces (see the
|
|
336
|
+
host-version probe in src/client/index.ts). */
|
|
324
337
|
.dsh-rewind-cleanup-card {
|
|
325
338
|
list-style: none;
|
|
326
339
|
border: 1px solid var(--dsw-alias-border-l2);
|
|
@@ -619,7 +632,7 @@ function knownCommandSeqs(session, chatOf, match) {
|
|
|
619
632
|
}
|
|
620
633
|
return known;
|
|
621
634
|
}
|
|
622
|
-
function waitForCommand(session, chatOf, match, timeoutMs = 8e3) {
|
|
635
|
+
function waitForCommand(session, chatOf, match, timeoutMs = 8e3, watch) {
|
|
623
636
|
return new Promise((resolve) => {
|
|
624
637
|
let settled = false;
|
|
625
638
|
const settle = (value) => {
|
|
@@ -635,7 +648,7 @@ function waitForCommand(session, chatOf, match, timeoutMs = 8e3) {
|
|
|
635
648
|
settle({ kind: node.outcome.kind, text: node.outcome.text });
|
|
636
649
|
}
|
|
637
650
|
};
|
|
638
|
-
const unsubscribe = session.subscribe(check);
|
|
651
|
+
const unsubscribe = (watch ?? ((cb) => session.subscribe(cb)))(check);
|
|
639
652
|
const timer = setTimeout(() => settle(null), timeoutMs);
|
|
640
653
|
check();
|
|
641
654
|
});
|
|
@@ -644,11 +657,11 @@ function isPreviewFor(node, seq) {
|
|
|
644
657
|
const args = node.args ?? "";
|
|
645
658
|
return node.name === "rewind" && args.includes("preview") && new RegExp(`(?:^|\\s)@${seq}(?=\\s|$)`).test(args);
|
|
646
659
|
}
|
|
647
|
-
async function previewImpact(session, chatOf, seq) {
|
|
660
|
+
async function previewImpact(session, chatOf, seq, watch) {
|
|
648
661
|
const known = knownCommandSeqs(session, chatOf, (node) => isPreviewFor(node, seq));
|
|
649
662
|
const result = await session.command(`/rewind preview @${seq} both`);
|
|
650
663
|
if (!result.ok || result.value?.matched !== true) return null;
|
|
651
|
-
return waitForCommand(session, chatOf, (node) => isPreviewFor(node, seq) && !known.has(node.seq));
|
|
664
|
+
return waitForCommand(session, chatOf, (node) => isPreviewFor(node, seq) && !known.has(node.seq), 8e3, watch);
|
|
652
665
|
}
|
|
653
666
|
function el(tag2, className, text) {
|
|
654
667
|
const node = document.createElement(tag2);
|
|
@@ -699,7 +712,7 @@ function renderImpactStep(root, opts, back, cached) {
|
|
|
699
712
|
root.replaceChildren(impact, actions);
|
|
700
713
|
focusFirst(root);
|
|
701
714
|
void (async () => {
|
|
702
|
-
const outcome = cached ?? await previewImpact(session, opts.chatOf, seq);
|
|
715
|
+
const outcome = cached ?? await previewImpact(session, opts.chatOf, seq, (cb) => opts.watchChat(session.sessionId, cb));
|
|
703
716
|
if (outcome === null) {
|
|
704
717
|
impact.textContent = t("popover.impact.failed", { message: "preview command failed or timed out" });
|
|
705
718
|
return;
|
|
@@ -821,7 +834,7 @@ function openPopover(opts) {
|
|
|
821
834
|
const { session, seq, time, preview, anchor, t, chatOf } = opts;
|
|
822
835
|
const onRewind = opts.onRewind;
|
|
823
836
|
if (seq === void 0 || time === void 0 || onRewind === void 0) return;
|
|
824
|
-
const durableOpts = { session, seq, time, preview, anchor, t, chatOf, onRewind };
|
|
837
|
+
const durableOpts = { session, seq, time, preview, anchor, t, chatOf, watchChat: opts.watchChat, onRewind };
|
|
825
838
|
const root = el("div", CLASS.popover);
|
|
826
839
|
root.setAttribute("role", "dialog");
|
|
827
840
|
root.setAttribute("aria-label", t("popover.title"));
|
|
@@ -891,7 +904,7 @@ function openPopover(opts) {
|
|
|
891
904
|
popoverEl = root;
|
|
892
905
|
disposeOutside = shell.dispose;
|
|
893
906
|
void (async () => {
|
|
894
|
-
const outcome = await previewImpact(session, chatOf, seq);
|
|
907
|
+
const outcome = await previewImpact(session, chatOf, seq, (cb) => opts.watchChat(session.sessionId, cb));
|
|
895
908
|
impactOutcome = outcome;
|
|
896
909
|
if (outcome !== null && outcome.kind === "success") {
|
|
897
910
|
bothState = { state: hasFileImpact(outcome.text) ? "hasChanges" : "noChanges" };
|
|
@@ -1025,7 +1038,7 @@ function userNodeOf(chat, key) {
|
|
|
1025
1038
|
if (node === void 0 || node.kind !== "user" && node.kind !== "steering") return void 0;
|
|
1026
1039
|
return node.data;
|
|
1027
1040
|
}
|
|
1028
|
-
async function runRewindAndFill(session, seq, mode, currentSessionId, chatOf, setComposerText) {
|
|
1041
|
+
async function runRewindAndFill(session, seq, mode, currentSessionId, chatOf, watchChat, setComposerText) {
|
|
1029
1042
|
const known = knownCommandSeqs(session, chatOf, (node) => isExecutedRewindCommand(node, seq));
|
|
1030
1043
|
let result;
|
|
1031
1044
|
try {
|
|
@@ -1039,7 +1052,7 @@ async function runRewindAndFill(session, seq, mode, currentSessionId, chatOf, se
|
|
|
1039
1052
|
}
|
|
1040
1053
|
let outcome;
|
|
1041
1054
|
try {
|
|
1042
|
-
outcome = await waitForCommand(session, chatOf, (node) => isExecutedRewindCommand(node, seq) && !known.has(node.seq), 2e4);
|
|
1055
|
+
outcome = await waitForCommand(session, chatOf, (node) => isExecutedRewindCommand(node, seq) && !known.has(node.seq), 2e4, (cb) => watchChat(session.sessionId, cb));
|
|
1043
1056
|
} catch (error) {
|
|
1044
1057
|
rewindLog.warn("refill", `waiting for rewind @${seq} outcome threw`, error);
|
|
1045
1058
|
return;
|
|
@@ -1103,8 +1116,9 @@ function actionsContainerOf(row) {
|
|
|
1103
1116
|
const root = row?.matches(ACTIONS_ROOT_SELECTOR) ? row : row?.querySelector(ACTIONS_ROOT_SELECTOR);
|
|
1104
1117
|
const actions = root?.lastElementChild;
|
|
1105
1118
|
if (actions instanceof HTMLElement && actions.querySelector("button") !== null) return actions;
|
|
1106
|
-
const
|
|
1107
|
-
const
|
|
1119
|
+
const buttons = Array.from(row?.querySelectorAll("button") ?? []);
|
|
1120
|
+
const lastButton = buttons.filter((button) => !button.classList.contains(CLASS.button)).at(-1);
|
|
1121
|
+
const structural = lastButton?.parentElement;
|
|
1108
1122
|
if (structural instanceof HTMLElement && structural.querySelector("button") !== null) return structural;
|
|
1109
1123
|
return void 0;
|
|
1110
1124
|
}
|
|
@@ -1169,7 +1183,7 @@ function sameTargets(left, right) {
|
|
|
1169
1183
|
return other.kind === "pending" && target.itemId === other.itemId;
|
|
1170
1184
|
});
|
|
1171
1185
|
}
|
|
1172
|
-
function RewindPortals({ sessionId, sessionOf, chatOf, currentSessionId, t, subscribeLocale, setComposerText }) {
|
|
1186
|
+
function RewindPortals({ sessionId, sessionOf, chatOf, currentSessionId, watchChat, t, subscribeLocale, setComposerText }) {
|
|
1173
1187
|
const [targets, setTargets] = (0, import_react.useState)([]);
|
|
1174
1188
|
const hidden = (0, import_react.useRef)(/* @__PURE__ */ new WeakSet());
|
|
1175
1189
|
const [, forceRender] = (0, import_react.useReducer)((count) => count + 1, 0);
|
|
@@ -1230,6 +1244,7 @@ function RewindPortals({ sessionId, sessionOf, chatOf, currentSessionId, t, subs
|
|
|
1230
1244
|
sessionId,
|
|
1231
1245
|
sessionOf,
|
|
1232
1246
|
chatOf,
|
|
1247
|
+
watchChat,
|
|
1233
1248
|
setComposerText,
|
|
1234
1249
|
t
|
|
1235
1250
|
},
|
|
@@ -1241,6 +1256,7 @@ function RewindPortals({ sessionId, sessionOf, chatOf, currentSessionId, t, subs
|
|
|
1241
1256
|
sessionId,
|
|
1242
1257
|
sessionOf,
|
|
1243
1258
|
chatOf,
|
|
1259
|
+
watchChat,
|
|
1244
1260
|
currentSessionId,
|
|
1245
1261
|
setComposerText,
|
|
1246
1262
|
t
|
|
@@ -1251,7 +1267,7 @@ function RewindPortals({ sessionId, sessionOf, chatOf, currentSessionId, t, subs
|
|
|
1251
1267
|
target.key
|
|
1252
1268
|
));
|
|
1253
1269
|
}
|
|
1254
|
-
function RewindButton({ target, sessionId, sessionOf, chatOf, currentSessionId, setComposerText, t }) {
|
|
1270
|
+
function RewindButton({ target, sessionId, sessionOf, chatOf, watchChat, currentSessionId, setComposerText, t }) {
|
|
1255
1271
|
const onClick = (event) => {
|
|
1256
1272
|
event.stopPropagation();
|
|
1257
1273
|
const session = sessionOf(sessionId);
|
|
@@ -1264,13 +1280,14 @@ function RewindButton({ target, sessionId, sessionOf, chatOf, currentSessionId,
|
|
|
1264
1280
|
openPopover({
|
|
1265
1281
|
session,
|
|
1266
1282
|
chatOf,
|
|
1283
|
+
watchChat,
|
|
1267
1284
|
seq: node.seq,
|
|
1268
1285
|
time: node.time,
|
|
1269
1286
|
preview: messagePreviewOf(node),
|
|
1270
1287
|
anchor: event.currentTarget,
|
|
1271
1288
|
t,
|
|
1272
1289
|
onRewind: (mode) => {
|
|
1273
|
-
void runRewindAndFill(session, node.seq, mode, currentSessionId, chatOf, setComposerText);
|
|
1290
|
+
void runRewindAndFill(session, node.seq, mode, currentSessionId, chatOf, watchChat, setComposerText);
|
|
1274
1291
|
}
|
|
1275
1292
|
});
|
|
1276
1293
|
};
|
|
@@ -1303,7 +1320,7 @@ async function retractPending(session, itemId, text, setComposerText) {
|
|
|
1303
1320
|
setComposerText(session.sessionId, text);
|
|
1304
1321
|
}
|
|
1305
1322
|
}
|
|
1306
|
-
function RetractButton({ target, sessionId, sessionOf, chatOf, setComposerText, t }) {
|
|
1323
|
+
function RetractButton({ target, sessionId, sessionOf, chatOf, watchChat, setComposerText, t }) {
|
|
1307
1324
|
const onClick = (event) => {
|
|
1308
1325
|
event.stopPropagation();
|
|
1309
1326
|
const session = sessionOf(sessionId);
|
|
@@ -1311,6 +1328,7 @@ function RetractButton({ target, sessionId, sessionOf, chatOf, setComposerText,
|
|
|
1311
1328
|
openPopover({
|
|
1312
1329
|
session,
|
|
1313
1330
|
chatOf,
|
|
1331
|
+
watchChat,
|
|
1314
1332
|
preview: target.preview,
|
|
1315
1333
|
anchor: event.currentTarget,
|
|
1316
1334
|
t,
|
|
@@ -1339,8 +1357,8 @@ function createRewindBridge(deps) {
|
|
|
1339
1357
|
}
|
|
1340
1358
|
|
|
1341
1359
|
// src/client/build-info.ts
|
|
1342
|
-
var PLUGIN_VERSION = true ? "0.7.
|
|
1343
|
-
var BUILD_HASH = true ? "
|
|
1360
|
+
var PLUGIN_VERSION = true ? "0.7.5" : "dev";
|
|
1361
|
+
var BUILD_HASH = true ? "25e8b3ca" : "dev";
|
|
1344
1362
|
|
|
1345
1363
|
// src/client/locales.ts
|
|
1346
1364
|
var zh = {
|
|
@@ -1641,6 +1659,18 @@ function apply(ctx) {
|
|
|
1641
1659
|
return false;
|
|
1642
1660
|
}
|
|
1643
1661
|
};
|
|
1662
|
+
const watchChat = (sessionId, cb) => resolveChatWatch(
|
|
1663
|
+
(id) => {
|
|
1664
|
+
try {
|
|
1665
|
+
return uiConversation()?.binding(id).target(CHAT_VIEW);
|
|
1666
|
+
} catch {
|
|
1667
|
+
return void 0;
|
|
1668
|
+
}
|
|
1669
|
+
},
|
|
1670
|
+
(id) => sessionOf(id),
|
|
1671
|
+
sessionId,
|
|
1672
|
+
cb
|
|
1673
|
+
);
|
|
1644
1674
|
const slots = ctx.slots;
|
|
1645
1675
|
yield slots.inject(HEADER_ACTIONS_SLOT, () => slots.register(
|
|
1646
1676
|
{
|
|
@@ -1650,7 +1680,7 @@ function apply(ctx) {
|
|
|
1650
1680
|
id: "dsh-rewind-portals",
|
|
1651
1681
|
order: 1e3
|
|
1652
1682
|
},
|
|
1653
|
-
createRewindBridge({ sessionOf, chatOf, currentSessionId, setComposerText, t, subscribeLocale })
|
|
1683
|
+
createRewindBridge({ sessionOf, chatOf, currentSessionId, watchChat, setComposerText, t, subscribeLocale })
|
|
1654
1684
|
));
|
|
1655
1685
|
const clientCtx = ctx;
|
|
1656
1686
|
clientCtx.inject(["settingsScope"], (scoped) => {
|
|
@@ -1696,7 +1726,7 @@ function apply(ctx) {
|
|
|
1696
1726
|
const known = knownCommandSeqs(face, chatOf2, (node) => isCandidateCommand(node));
|
|
1697
1727
|
const result = await face.command("/rewind __candidates");
|
|
1698
1728
|
if (!result.ok || result.value?.matched !== true) return void 0;
|
|
1699
|
-
const outcome = await waitForCommand(face, chatOf2, (node) => isCandidateCommand(node) && !known.has(node.seq));
|
|
1729
|
+
const outcome = await waitForCommand(face, chatOf2, (node) => isCandidateCommand(node) && !known.has(node.seq), 8e3, (cb) => watchChat(face.sessionId, cb));
|
|
1700
1730
|
if (outcome === null || outcome.kind !== "success" || outcome.text === void 0) return void 0;
|
|
1701
1731
|
return rewindCandidatesFromHostText(outcome.text);
|
|
1702
1732
|
};
|
|
@@ -1731,13 +1761,14 @@ function apply(ctx) {
|
|
|
1731
1761
|
openPopover({
|
|
1732
1762
|
session: face,
|
|
1733
1763
|
chatOf,
|
|
1764
|
+
watchChat,
|
|
1734
1765
|
seq: candidate.seq,
|
|
1735
1766
|
time: candidate.time,
|
|
1736
1767
|
preview: candidate.preview,
|
|
1737
1768
|
anchor: composerAnchor(),
|
|
1738
1769
|
t,
|
|
1739
1770
|
onRewind: (mode) => {
|
|
1740
|
-
void runRewindAndFill(face, candidate.seq, mode, currentSessionId, chatOf, setComposerText);
|
|
1771
|
+
void runRewindAndFill(face, candidate.seq, mode, currentSessionId, chatOf, watchChat, setComposerText);
|
|
1741
1772
|
}
|
|
1742
1773
|
});
|
|
1743
1774
|
}
|
|
@@ -14,11 +14,12 @@ export interface HiddenChat {
|
|
|
14
14
|
};
|
|
15
15
|
}
|
|
16
16
|
/**
|
|
17
|
-
* Reader for one session's live chat snapshot.
|
|
18
|
-
*
|
|
19
|
-
* session face snapshot, while 0.1.2-
|
|
20
|
-
* `uiConversation` service's named "chat" view (contributed by
|
|
21
|
-
* dsh-client-ui-chat through the uiSession slot hook).
|
|
17
|
+
* Reader for one session's live chat snapshot. This is one of the plugin's
|
|
18
|
+
* "0.1.1-rc.2 ↔ 0.1.2-rc.1" dual channels: 0.1.1-rc.2 serves the chat from the
|
|
19
|
+
* session face snapshot, while 0.1.2-rc.1 (and the whole 0.1.2 line) serves it
|
|
20
|
+
* from the `uiConversation` service's named "chat" view (contributed by
|
|
21
|
+
* dsh-client-ui-chat through the uiSession slot hook). See
|
|
22
|
+
* SiriLee/dsh-rewind#7.
|
|
22
23
|
*/
|
|
23
24
|
export type ChatOf = (session: {
|
|
24
25
|
readonly sessionId: string;
|
|
@@ -26,13 +27,36 @@ export type ChatOf = (session: {
|
|
|
26
27
|
chat?: unknown;
|
|
27
28
|
};
|
|
28
29
|
} | undefined) => HiddenChat | undefined;
|
|
30
|
+
/**
|
|
31
|
+
* Subscribe to one session's live chat-update signal, for waiting on a chat
|
|
32
|
+
* snapshot change without polling. Dual-channel, mirroring `chatOf`: the
|
|
33
|
+
* 0.1.2-rc.1 `uiConversation` "chat" view when registered, else the session
|
|
34
|
+
* face (0.1.1-rc.2, whose snapshot still carries `chat`, so its own
|
|
35
|
+
* `subscribe` is the chat-update signal). `cb` fires whenever the chat
|
|
36
|
+
* snapshot invalidates.
|
|
37
|
+
*/
|
|
38
|
+
export type ChatWatch = (sessionId: string, cb: () => void) => () => void;
|
|
39
|
+
/**
|
|
40
|
+
* Choose the chat-update subscription for `waitForCommand`: prefer the
|
|
41
|
+
* `uiConversation` "chat" view's own `subscribe` when available (0.1.2-rc.1,
|
|
42
|
+
* where the chat-update signal lives), else the session face's `subscribe`
|
|
43
|
+
* (0.1.1-rc.2, whose snapshot still carries the chat, so its own subscribe is
|
|
44
|
+
* the chat-update signal). Extracted as a pure channel-selection step so the
|
|
45
|
+
* "view vs face" branch is unit-testable; the resolvers are injected by the
|
|
46
|
+
* caller (see `watchChat` in index.ts). Never throws.
|
|
47
|
+
*/
|
|
48
|
+
export declare function resolveChatWatch(resolveView: (sessionId: string) => {
|
|
49
|
+
subscribe?(cb: () => void): () => void;
|
|
50
|
+
} | undefined, resolveFace: (sessionId: string) => {
|
|
51
|
+
subscribe(cb: () => void): () => void;
|
|
52
|
+
} | undefined, sessionId: string, cb: () => void): () => void;
|
|
29
53
|
/**
|
|
30
54
|
* Resolve the chat snapshot across the two harness channels: the session-face
|
|
31
|
-
* snapshot first (rc.2 — on
|
|
32
|
-
* field reads `undefined`), then the `uiConversation` "chat"
|
|
33
|
-
* `getSnapshot()` returns undefined until the named view is
|
|
34
|
-
* both channels missing degrades to `undefined` (no targets, no
|
|
35
|
-
* never a crash).
|
|
55
|
+
* snapshot first (0.1.1-rc.2 — on 0.1.2-rc.1 the face no longer carries
|
|
56
|
+
* `chat`, so the field reads `undefined`), then the `uiConversation` "chat"
|
|
57
|
+
* view. The view's `getSnapshot()` returns undefined until the named view is
|
|
58
|
+
* registered, so both channels missing degrades to `undefined` (no targets, no
|
|
59
|
+
* hiding — never a crash).
|
|
36
60
|
*/
|
|
37
61
|
export declare function chatSnapshotOf(face: {
|
|
38
62
|
getSnapshot(): {
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
*/
|
|
16
16
|
import type { SessionFace } from '@deepseek-ai/dsh-client-runtime/client';
|
|
17
17
|
import type { CommandNode } from '@deepseek-ai/dsh-client-runtime/client';
|
|
18
|
-
import { type ChatOf } from './hidden.ts';
|
|
18
|
+
import { type ChatOf, type ChatWatch } from './hidden.ts';
|
|
19
19
|
import type { RewindKey } from './locales.ts';
|
|
20
20
|
type Translate = (key: RewindKey, params?: Record<string, unknown>) => string;
|
|
21
21
|
export interface PopoverOptions {
|
|
@@ -33,11 +33,18 @@ export interface PopoverOptions {
|
|
|
33
33
|
readonly onRetract?: () => void;
|
|
34
34
|
readonly preview: string;
|
|
35
35
|
/**
|
|
36
|
-
* Dual-channel chat reader (rc.2 session face /
|
|
37
|
-
* view): the durable variant's command probes scan the chat
|
|
38
|
-
* Unused by the pending-retract variant.
|
|
36
|
+
* Dual-channel chat reader (0.1.1-rc.2 session face / 0.1.2-rc.1
|
|
37
|
+
* uiConversation view): the durable variant's command probes scan the chat
|
|
38
|
+
* through it. Unused by the pending-retract variant.
|
|
39
39
|
*/
|
|
40
40
|
readonly chatOf: ChatOf;
|
|
41
|
+
/**
|
|
42
|
+
* Subscribe to one session's live chat-update signal, so a probe waiting on
|
|
43
|
+
* a command's chat node can be woken when the chat snapshot changes (0.1.2-rc.1
|
|
44
|
+
* the session face no longer fires on a chat update). Passed straight through
|
|
45
|
+
* to `waitForCommand`.
|
|
46
|
+
*/
|
|
47
|
+
readonly watchChat: ChatWatch;
|
|
41
48
|
/** The button that opened the popover (outside-click ignore target). */
|
|
42
49
|
readonly anchor: HTMLElement;
|
|
43
50
|
readonly t: Translate;
|
|
@@ -63,7 +70,7 @@ export declare function knownCommandSeqs(session: SessionFace, chatOf: ChatOf, m
|
|
|
63
70
|
* session snapshot (command/run + command/done land as one CommandNode).
|
|
64
71
|
* @returns the outcome text-bearing node, or null on timeout.
|
|
65
72
|
*/
|
|
66
|
-
export declare function waitForCommand(session: SessionFace, chatOf: ChatOf, match: (node: CommandNode) => boolean, timeoutMs?: number): Promise<{
|
|
73
|
+
export declare function waitForCommand(session: SessionFace, chatOf: ChatOf, match: (node: CommandNode) => boolean, timeoutMs?: number, watch?: (cb: () => void) => () => void): Promise<{
|
|
67
74
|
kind: 'success' | 'error';
|
|
68
75
|
text?: string;
|
|
69
76
|
} | null>;
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
*/
|
|
25
25
|
import { type ReactNode } from 'react';
|
|
26
26
|
import type { SessionFace } from '@deepseek-ai/dsh-client-runtime/client';
|
|
27
|
-
import { type ChatOf, type HiddenChat } from './hidden.ts';
|
|
27
|
+
import { type ChatOf, type ChatWatch, type HiddenChat } from './hidden.ts';
|
|
28
28
|
import type { RewindKey } from './locales.ts';
|
|
29
29
|
type Translate = (key: RewindKey, params?: Record<string, unknown>) => string;
|
|
30
30
|
/** One portal target: the actions row of a user/steering seat + its durable node. */
|
|
@@ -53,19 +53,26 @@ export type PortalTarget = {
|
|
|
53
53
|
export interface RewindBridgeDeps {
|
|
54
54
|
readonly sessionOf: (sessionId: string) => SessionFace | undefined;
|
|
55
55
|
/**
|
|
56
|
-
* Dual-channel chat reader (rc.2 session face /
|
|
57
|
-
* "chat" view): every chat snapshot read goes through it. See
|
|
56
|
+
* Dual-channel chat reader (0.1.1-rc.2 session face / 0.1.2-rc.1
|
|
57
|
+
* uiConversation "chat" view): every chat snapshot read goes through it. See
|
|
58
58
|
* `chatSnapshotOf` in hidden.ts for the channel precedence.
|
|
59
59
|
*/
|
|
60
60
|
readonly chatOf: ChatOf;
|
|
61
|
+
/**
|
|
62
|
+
* Subscribe to one session's live chat-update signal, so the composer refill
|
|
63
|
+
* waiting on an executed rewind's chat node can be woken when the chat
|
|
64
|
+
* snapshot changes (0.1.2-rc.1 the session face no longer fires on a chat
|
|
65
|
+
* update). Passed through to `waitForCommand`.
|
|
66
|
+
*/
|
|
67
|
+
readonly watchChat: ChatWatch;
|
|
61
68
|
readonly currentSessionId: () => string | undefined;
|
|
62
69
|
readonly t: Translate;
|
|
63
70
|
readonly subscribeLocale: (cb: () => void) => () => void;
|
|
64
71
|
/**
|
|
65
72
|
* Session-aware dual-channel composer writer (see `writeComposer`): the
|
|
66
|
-
*
|
|
67
|
-
* rc.2/
|
|
68
|
-
* session that just rewound.
|
|
73
|
+
* 0.1.2-rc.1 `conversation.input` facade `setDraft` when reachable, else the
|
|
74
|
+
* 0.1.1-rc.2/0.1.2-rc.1 DOM fill. Session-scoped so the refill only lands in
|
|
75
|
+
* the session that just rewound.
|
|
69
76
|
*/
|
|
70
77
|
readonly setComposerText: (sessionId: string, text: string) => boolean;
|
|
71
78
|
}
|
|
@@ -83,7 +90,7 @@ export interface SlotsLike {
|
|
|
83
90
|
}
|
|
84
91
|
/** Join the text blocks of a user message into one plain preview. */
|
|
85
92
|
/**
|
|
86
|
-
* The
|
|
93
|
+
* The 0.1.2-rc.1 session input facade's write face (structural, so the plugin
|
|
87
94
|
* never imports the conversation UI package). `setDraft` replaces the whole
|
|
88
95
|
* composer draft through the harness's own Lexical editor — the correct way
|
|
89
96
|
* to restore the withdrawn text.
|
|
@@ -92,8 +99,8 @@ interface ComposerDraftWriter {
|
|
|
92
99
|
setDraft(text: string): void;
|
|
93
100
|
}
|
|
94
101
|
/**
|
|
95
|
-
* Fill the dsh composer with `text`. DOM dual-channel fallback: the rc.2
|
|
96
|
-
* `<textarea>` path, then the
|
|
102
|
+
* Fill the dsh composer with `text`. DOM dual-channel fallback: the 0.1.1-rc.2
|
|
103
|
+
* `<textarea>` path, then the 0.1.2-rc.1 `contenteditable` path. Used by
|
|
97
104
|
* `setComposerText` (the harness-facade-aware writer) as the last-resort and
|
|
98
105
|
* by `runRewindAndFill` to put the withdrawn target message back into the
|
|
99
106
|
* composer after a rewind. Best-effort — no composer match means false,
|
|
@@ -102,12 +109,12 @@ interface ComposerDraftWriter {
|
|
|
102
109
|
export declare function fillComposer(text: string): boolean;
|
|
103
110
|
/**
|
|
104
111
|
* Dual-channel composer write, mirroring `chatSnapshotOf`: prefer the harness
|
|
105
|
-
* facade's `setDraft` (
|
|
106
|
-
* to the DOM `fillComposer` (rc.2 textarea /
|
|
107
|
-
* facade that throws (session teardown) is treated as absent so the DOM path
|
|
112
|
+
* facade's `setDraft` (0.1.2-rc.1, correct whole-draft replace), then degrade
|
|
113
|
+
* to the DOM `fillComposer` (0.1.1-rc.2 textarea / 0.1.2-rc.1 contenteditable).
|
|
114
|
+
* A facade that throws (session teardown) is treated as absent so the DOM path
|
|
108
115
|
* still restores the text. Never throws.
|
|
109
116
|
* @param text - the withdrawn target message text.
|
|
110
|
-
* @param facade - the
|
|
117
|
+
* @param facade - the 0.1.2-rc.1 session input draft writer, when reachable.
|
|
111
118
|
* @returns whether a channel applied the text.
|
|
112
119
|
*/
|
|
113
120
|
export declare function writeComposer(text: string, facade: ComposerDraftWriter | undefined): boolean;
|
|
@@ -124,22 +131,25 @@ export declare function writeComposer(text: string, facade: ComposerDraftWriter
|
|
|
124
131
|
* the old baseline heuristic refilled withdrawn text into the composer
|
|
125
132
|
* after switching sessions or restarting dsh.
|
|
126
133
|
*/
|
|
127
|
-
export declare function runRewindAndFill(session: SessionFace, seq: number, mode: 'chat' | 'both', currentSessionId: () => string | undefined, chatOf: ChatOf, setComposerText: (sessionId: string, text: string) => boolean): Promise<void>;
|
|
134
|
+
export declare function runRewindAndFill(session: SessionFace, seq: number, mode: 'chat' | 'both', currentSessionId: () => string | undefined, chatOf: ChatOf, watchChat: ChatWatch, setComposerText: (sessionId: string, text: string) => boolean): Promise<void>;
|
|
128
135
|
/**
|
|
129
136
|
* Locate the actions container of a user/steering seat row — the element the
|
|
130
137
|
* ↶ button portals into (the copy/branch IconActions row).
|
|
131
138
|
*
|
|
132
|
-
*
|
|
133
|
-
* -
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
* `MessageIconActions`
|
|
142
|
-
*
|
|
139
|
+
* @dualmode 0.1.1-rc.2 ↔ 0.1.2-rc.1
|
|
140
|
+
* - 0.1.1-rc.2: the hover-reveal root is attribute-marked
|
|
141
|
+
* `[data-time-hover-root]` and mounts the actions row as its LAST child. A
|
|
142
|
+
* pending row IS that root; a durable row CONTAINS it as a descendant.
|
|
143
|
+
* - 0.1.2-rc.1: the marker was removed from user rows (it now lives only on the
|
|
144
|
+
* per-turn tail footer, `TurnTailNodeView`), and the user action row is
|
|
145
|
+
* revealed via CSS `:has()`. The container is instead located structurally:
|
|
146
|
+
* the direct holder of the copy `<button>` (the `MessageIconActions`
|
|
147
|
+
* container, which mounts that button as a direct child —
|
|
148
|
+
* `MessageIconActions.tsx:83,86`).
|
|
149
|
+
*
|
|
150
|
+
* The 0.1.2-alpha.2/3 `[data-actions-reveal]` attribute-marked rows (see
|
|
151
|
+
* `ACTIONS_ROOT_SELECTOR`) are an intermediate state absorbed by the
|
|
152
|
+
* attribute branch below; they are not needed by either endpoint.
|
|
143
153
|
*
|
|
144
154
|
* Returns undefined when no qualifying container is found; the caller refuses
|
|
145
155
|
* to portal (never a crash, never a wrong attachment). Exported as a test seam
|
|
@@ -163,10 +173,11 @@ interface RewindPortalsProps extends RewindBridgeDeps {
|
|
|
163
173
|
* skipped when the target set is unchanged), so the plugin never runs a
|
|
164
174
|
* synchronous full-transcript scan inside a commit microtask.
|
|
165
175
|
*/
|
|
166
|
-
export declare function RewindPortals({ sessionId, sessionOf, chatOf, currentSessionId, t, subscribeLocale, setComposerText }: RewindPortalsProps): ReactNode;
|
|
167
|
-
/** The current composer draft, on whichever channel: rc.2 textarea
|
|
168
|
-
*
|
|
169
|
-
* Exported as a test seam (the empty-composer guard in
|
|
176
|
+
export declare function RewindPortals({ sessionId, sessionOf, chatOf, currentSessionId, watchChat, t, subscribeLocale, setComposerText }: RewindPortalsProps): ReactNode;
|
|
177
|
+
/** The current composer draft, on whichever channel: 0.1.1-rc.2 textarea
|
|
178
|
+
* `.value`, 0.1.2-rc.1 contenteditable `textContent`. Empty when the composer
|
|
179
|
+
* is absent. Exported as a test seam (the empty-composer guard in
|
|
180
|
+
* `retractPending`). */
|
|
170
181
|
export declare function composerText(): string;
|
|
171
182
|
/**
|
|
172
183
|
* Build the slot-entry component for the plugin apply(): a tiny bridge that
|
|
@@ -11,10 +11,11 @@
|
|
|
11
11
|
* host schema enforces). "Discard changes" restores the last-read baseline.
|
|
12
12
|
*
|
|
13
13
|
* It neither imports the client settings typed contract nor depends on the
|
|
14
|
-
*
|
|
15
|
-
* the `set(field, value)` method present on both rc.2 and
|
|
16
|
-
* receives a tiny structural `CleanupCardApi` supplied by
|
|
17
|
-
* so the component stays harness-agnostic and
|
|
14
|
+
* 0.1.2-rc.1-only `mutate` write API: it reads `getSnapshot().value` and writes
|
|
15
|
+
* via the `set(field, value)` method present on both 0.1.1-rc.2 and 0.1.2-rc.1,
|
|
16
|
+
* and the card receives a tiny structural `CleanupCardApi` supplied by
|
|
17
|
+
* `src/client/index.ts` so the component stays harness-agnostic and
|
|
18
|
+
* unit-testable in isolation.
|
|
18
19
|
*
|
|
19
20
|
* @module dsh-rewind/client/settings-card
|
|
20
21
|
*/
|
|
@@ -23,4 +23,4 @@ export declare const CLASS: {
|
|
|
23
23
|
/** The ↶ glyph, drawn inline so the bundle stays dependency-free. */
|
|
24
24
|
export declare const REWIND_ICON_SVG: string;
|
|
25
25
|
/** One injected stylesheet (scoped under `.dsh-rewind-*`). */
|
|
26
|
-
export declare const STYLE = "\n.dsh-rewind-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n padding: 6px;\n border: none;\n border-radius: 28px;\n background: transparent;\n color: var(--dsw-alias-label-tertiary);\n cursor: pointer;\n}\n.dsh-rewind-btn:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n color: var(--dsw-alias-label-secondary);\n}\n\n.dsh-rewind-popover {\n position: fixed;\n z-index: 1000;\n width: 288px;\n padding: 12px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 12px;\n background: var(--dsw-specific-menu, var(--dsw-alias-bg-layer-3));\n box-shadow: var(--dsw-shadow-lv3);\n font-size: 14px;\n line-height: 20px;\n color: var(--dsw-alias-label-primary);\n}\n.dsh-rewind-popover-title {\n font-size: 14px;\n font-weight: 600;\n line-height: 20px;\n}\n.dsh-rewind-popover-target {\n margin: 4px 0 10px;\n font-size: 12px;\n line-height: 16px;\n color: var(--dsw-alias-label-tertiary);\n word-break: break-all;\n}\n.dsh-rewind-popover-option {\n display: flex;\n flex-direction: column;\n gap: 2px;\n width: 100%;\n margin: 0 0 6px;\n padding: 8px 10px;\n border: 1px solid transparent;\n border-radius: 8px;\n background: transparent;\n color: inherit;\n font: inherit;\n text-align: left;\n cursor: pointer;\n}\n.dsh-rewind-popover-option:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n.dsh-rewind-popover-option:disabled {\n opacity: 0.5;\n cursor: default;\n}\n.dsh-rewind-popover-option-label {\n font-weight: 500;\n}\n.dsh-rewind-popover-option-hint {\n font-size: 12px;\n line-height: 16px;\n color: var(--dsw-alias-label-tertiary);\n}\n.dsh-rewind-popover-impact {\n margin: 4px 0 10px;\n padding: 8px 10px;\n border-radius: 8px;\n background: var(--dsw-alias-interactive-bg-hover);\n font-size: 12px;\n line-height: 16px;\n color: var(--dsw-alias-label-secondary);\n white-space: pre-wrap;\n max-height: 160px;\n overflow: auto;\n}\n.dsh-rewind-popover-actions {\n display: flex;\n justify-content: flex-end;\n gap: 8px;\n}\n.dsh-rewind-popover-primary,\n.dsh-rewind-popover-ghost {\n padding: 5px 12px;\n border: none;\n border-radius: 8px;\n font: inherit;\n font-size: 13px;\n line-height: 18px;\n cursor: pointer;\n}\n.dsh-rewind-popover-primary {\n background: var(--dsw-alias-button-primary-fill);\n color: var(--dsw-alias-label-primary-foreground);\n}\n.dsh-rewind-popover-primary:hover:not(:disabled) {\n background: var(--dsw-alias-button-primary-hover);\n}\n.dsh-rewind-popover-primary:disabled {\n opacity: 0.5;\n cursor: default;\n}\n.dsh-rewind-popover-ghost {\n background: transparent;\n color: var(--dsw-alias-label-secondary);\n}\n.dsh-rewind-popover-ghost:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.dsh-rewind-guard-hint {\n position: fixed;\n z-index: 1000;\n max-width: min(440px, calc(100vw - 24px));\n padding: 8px 12px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 10px;\n background: var(--dsw-specific-menu, var(--dsw-alias-bg-layer-3));\n box-shadow: var(--dsw-shadow-lv3);\n font-size: 13px;\n line-height: 18px;\n color: var(--dsw-alias-label-primary);\n pointer-events: none;\n}\n\n/* ---- Snapshot-cleanup settings card (mirrors the harness PluginCard look) ---- */\n.dsh-rewind-cleanup-card {\n list-style: none;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 12px;\n background: var(--dsw-alias-bg-layer-3);\n transition: border-color .16s, background .16s;\n}\n.dsh-rewind-cleanup-card:hover {\n border-color: var(--dsw-alias-label-dimmed);\n}\n.dsh-rewind-cleanup-card-open {\n background: var(--dsw-alias-bg-layer-2);\n border-color: var(--dsw-alias-label-dimmed);\n}\n.dsh-rewind-cleanup-header {\n width: 100%;\n appearance: none;\n border: 0;\n background: none;\n font: inherit;\n color: inherit;\n text-align: left;\n cursor: pointer;\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 14px 16px;\n border-radius: 12px;\n}\n.dsh-rewind-cleanup-header:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: -2px;\n}\n.dsh-rewind-cleanup-head-text {\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n.dsh-rewind-cleanup-name {\n font-size: 15px;\n font-weight: 600;\n line-height: 1.4;\n color: var(--dsw-alias-label-primary);\n}\n.dsh-rewind-cleanup-desc {\n font-size: 13px;\n line-height: 1.5;\n color: var(--dsw-alias-label-tertiary);\n}\n.dsh-rewind-cleanup-chevron {\n flex: none;\n color: var(--dsw-alias-label-tertiary);\n transition: transform .16s;\n}\n.dsh-rewind-cleanup-chevron-open {\n transform: rotate(180deg);\n}\n.dsh-rewind-cleanup-pending {\n flex: none;\n border-radius: 999px;\n padding: 1px 8px;\n font-size: 11px;\n line-height: 17px;\n font-weight: 500;\n white-space: nowrap;\n background: var(--dsw-alias-bg-module-platform);\n color: var(--dsw-alias-label-secondary);\n}\n.dsh-rewind-cleanup-body {\n border-top: 1px solid var(--dsw-alias-border-l2);\n margin: 0 16px;\n padding: 4px 0 8px;\n}\n.dsh-rewind-cleanup-readonly {\n margin: 12px 0 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-tertiary);\n}\n.dsh-rewind-cleanup-permission {\n display: grid;\n gap: 6px;\n padding: 12px 0;\n}\n.dsh-rewind-cleanup-field {\n display: flex;\n flex-direction: column;\n gap: 6px;\n padding: 12px 0;\n}\n.dsh-rewind-cleanup-field + .dsh-rewind-cleanup-field {\n border-top: 1px solid var(--dsw-alias-border-l2);\n}\n.dsh-rewind-cleanup-head {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n.dsh-rewind-cleanup-label {\n flex: 1;\n min-width: 0;\n font-size: 13px;\n font-weight: 500;\n line-height: 1.5;\n color: var(--dsw-alias-label-primary);\n}\n.dsh-rewind-cleanup-hint {\n margin: 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-tertiary);\n}\n.dsh-rewind-cleanup-error {\n margin: 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-error);\n}\n/* Switch row: label left, role=switch button right, hint below (Subagent module). */\n.dsh-rewind-cleanup-toggle-row {\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n gap: 16px;\n font-size: 13px;\n line-height: 1.5;\n color: var(--dsw-alias-label-primary);\n}\n.dsh-rewind-cleanup-toggle-label {\n flex: 1;\n min-width: 0;\n}\n.dsh-rewind-cleanup-switch {\n box-sizing: border-box;\n position: relative;\n flex: 0 0 auto;\n width: 36px;\n height: 20px;\n padding: 2px;\n border: 0;\n border-radius: 10px;\n background: var(--dsw-alias-border-l3);\n cursor: pointer;\n}\n.dsh-rewind-cleanup-switch-on {\n background: var(--dsw-alias-brand-primary);\n}\n.dsh-rewind-cleanup-switch:disabled {\n cursor: default;\n opacity: 0.5;\n}\n.dsh-rewind-cleanup-switch:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 2px;\n}\n.dsh-rewind-cleanup-thumb {\n display: block;\n width: 16px;\n height: 16px;\n border-radius: 50%;\n corner-shape: round;\n background: var(--dsw-alias-label-primary-foreground);\n transition: transform 120ms ease;\n}\n.dsh-rewind-cleanup-switch-on .dsh-rewind-cleanup-thumb {\n transform: translateX(16px);\n}\n.dsh-rewind-cleanup-input {\n box-sizing: border-box;\n height: 34px;\n padding: 0 12px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 8px;\n background: var(--dsw-alias-bg-layer-3);\n font: inherit;\n font-size: 13px;\n line-height: 1.5;\n color: var(--dsw-alias-label-primary);\n}\n.dsh-rewind-cleanup-input:focus-visible {\n outline: none;\n border-color: var(--dsw-alias-brand-primary);\n}\n.dsh-rewind-cleanup-input:disabled {\n color: var(--dsw-alias-label-tertiary);\n cursor: default;\n}\n.dsh-rewind-cleanup-input-invalid {\n border-color: var(--dsw-alias-label-error);\n}\n.dsh-rewind-cleanup-footer {\n display: flex;\n align-items: center;\n justify-content: flex-end;\n gap: 8px;\n padding: 12px 0 4px;\n border-top: 1px solid var(--dsw-alias-border-l2);\n}\n.dsh-rewind-cleanup-failed {\n flex: 1;\n min-width: 0;\n margin: 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-error);\n}\n.dsh-rewind-cleanup-discard,\n.dsh-rewind-cleanup-save {\n appearance: none;\n border: 1px solid transparent;\n border-radius: 8px;\n padding: 5px 14px;\n font: inherit;\n font-size: 13px;\n line-height: 1.5;\n cursor: pointer;\n}\n.dsh-rewind-cleanup-discard {\n border-color: var(--dsw-alias-border-l2);\n background: none;\n color: var(--dsw-alias-label-secondary);\n}\n.dsh-rewind-cleanup-discard:hover:not(:disabled) {\n color: var(--dsw-alias-label-primary);\n border-color: var(--dsw-alias-label-dimmed);\n}\n.dsh-rewind-cleanup-save {\n background: var(--dsw-alias-label-primary);\n color: var(--dsw-alias-bg-layer-3);\n}\n.dsh-rewind-cleanup-discard:disabled,\n.dsh-rewind-cleanup-save:disabled {\n opacity: 0.4;\n cursor: default;\n}\n.dsh-rewind-cleanup-discard:focus-visible,\n.dsh-rewind-cleanup-save:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n";
|
|
26
|
+
export declare const STYLE = "\n.dsh-rewind-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n padding: 6px;\n border: none;\n border-radius: 28px;\n background: transparent;\n color: var(--dsw-alias-label-tertiary);\n cursor: pointer;\n}\n.dsh-rewind-btn:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n color: var(--dsw-alias-label-secondary);\n}\n\n.dsh-rewind-popover {\n position: fixed;\n z-index: 1000;\n width: 288px;\n padding: 12px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 12px;\n background: var(--dsw-specific-menu, var(--dsw-alias-bg-layer-3));\n box-shadow: var(--dsw-shadow-lv3);\n font-size: 14px;\n line-height: 20px;\n color: var(--dsw-alias-label-primary);\n}\n.dsh-rewind-popover-title {\n font-size: 14px;\n font-weight: 600;\n line-height: 20px;\n}\n.dsh-rewind-popover-target {\n margin: 4px 0 10px;\n font-size: 12px;\n line-height: 16px;\n color: var(--dsw-alias-label-tertiary);\n word-break: break-all;\n}\n.dsh-rewind-popover-option {\n display: flex;\n flex-direction: column;\n gap: 2px;\n width: 100%;\n margin: 0 0 6px;\n padding: 8px 10px;\n border: 1px solid transparent;\n border-radius: 8px;\n background: transparent;\n color: inherit;\n font: inherit;\n text-align: left;\n cursor: pointer;\n}\n.dsh-rewind-popover-option:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n.dsh-rewind-popover-option:disabled {\n opacity: 0.5;\n cursor: default;\n}\n.dsh-rewind-popover-option-label {\n font-weight: 500;\n}\n.dsh-rewind-popover-option-hint {\n font-size: 12px;\n line-height: 16px;\n color: var(--dsw-alias-label-tertiary);\n}\n.dsh-rewind-popover-impact {\n margin: 4px 0 10px;\n padding: 8px 10px;\n border-radius: 8px;\n background: var(--dsw-alias-interactive-bg-hover);\n font-size: 12px;\n line-height: 16px;\n color: var(--dsw-alias-label-secondary);\n white-space: pre-wrap;\n max-height: 160px;\n overflow: auto;\n}\n.dsh-rewind-popover-actions {\n display: flex;\n justify-content: flex-end;\n gap: 8px;\n}\n.dsh-rewind-popover-primary,\n.dsh-rewind-popover-ghost {\n padding: 5px 12px;\n border: none;\n border-radius: 8px;\n font: inherit;\n font-size: 13px;\n line-height: 18px;\n cursor: pointer;\n}\n.dsh-rewind-popover-primary {\n background: var(--dsw-alias-button-primary-fill);\n color: var(--dsw-alias-label-primary-foreground);\n}\n.dsh-rewind-popover-primary:hover:not(:disabled) {\n background: var(--dsw-alias-button-primary-hover);\n}\n.dsh-rewind-popover-primary:disabled {\n opacity: 0.5;\n cursor: default;\n}\n.dsh-rewind-popover-ghost {\n background: transparent;\n color: var(--dsw-alias-label-secondary);\n}\n.dsh-rewind-popover-ghost:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.dsh-rewind-guard-hint {\n position: fixed;\n z-index: 1000;\n max-width: min(440px, calc(100vw - 24px));\n padding: 8px 12px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 10px;\n background: var(--dsw-specific-menu, var(--dsw-alias-bg-layer-3));\n box-shadow: var(--dsw-shadow-lv3);\n font-size: 13px;\n line-height: 18px;\n color: var(--dsw-alias-label-primary);\n pointer-events: none;\n}\n\n/* ---- Snapshot-cleanup settings card (mirrors the harness PluginCard look) ---- */\n/* Dual-channel note: the harness card corner/border drifted across versions \u2014\n 0.1.1-rc.2 uses border-radius 12px + 1px solid border-l2, while 0.1.2-rc.1\n uses 16px + 0.5px border-l4 (and adds corner-shape: round). We intentionally\n keep the 0.1.1-rc.2 values here for now; if the 0.1.2-rc.1 look is wanted,\n switch these on the host version like the other dual-channel surfaces (see the\n host-version probe in src/client/index.ts). */\n.dsh-rewind-cleanup-card {\n list-style: none;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 12px;\n background: var(--dsw-alias-bg-layer-3);\n transition: border-color .16s, background .16s;\n}\n.dsh-rewind-cleanup-card:hover {\n border-color: var(--dsw-alias-label-dimmed);\n}\n.dsh-rewind-cleanup-card-open {\n background: var(--dsw-alias-bg-layer-2);\n border-color: var(--dsw-alias-label-dimmed);\n}\n.dsh-rewind-cleanup-header {\n width: 100%;\n appearance: none;\n border: 0;\n background: none;\n font: inherit;\n color: inherit;\n text-align: left;\n cursor: pointer;\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 14px 16px;\n border-radius: 12px;\n}\n.dsh-rewind-cleanup-header:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: -2px;\n}\n.dsh-rewind-cleanup-head-text {\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n.dsh-rewind-cleanup-name {\n font-size: 15px;\n font-weight: 600;\n line-height: 1.4;\n color: var(--dsw-alias-label-primary);\n}\n.dsh-rewind-cleanup-desc {\n font-size: 13px;\n line-height: 1.5;\n color: var(--dsw-alias-label-tertiary);\n}\n.dsh-rewind-cleanup-chevron {\n flex: none;\n color: var(--dsw-alias-label-tertiary);\n transition: transform .16s;\n}\n.dsh-rewind-cleanup-chevron-open {\n transform: rotate(180deg);\n}\n.dsh-rewind-cleanup-pending {\n flex: none;\n border-radius: 999px;\n padding: 1px 8px;\n font-size: 11px;\n line-height: 17px;\n font-weight: 500;\n white-space: nowrap;\n background: var(--dsw-alias-bg-module-platform);\n color: var(--dsw-alias-label-secondary);\n}\n.dsh-rewind-cleanup-body {\n border-top: 1px solid var(--dsw-alias-border-l2);\n margin: 0 16px;\n padding: 4px 0 8px;\n}\n.dsh-rewind-cleanup-readonly {\n margin: 12px 0 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-tertiary);\n}\n.dsh-rewind-cleanup-permission {\n display: grid;\n gap: 6px;\n padding: 12px 0;\n}\n.dsh-rewind-cleanup-field {\n display: flex;\n flex-direction: column;\n gap: 6px;\n padding: 12px 0;\n}\n.dsh-rewind-cleanup-field + .dsh-rewind-cleanup-field {\n border-top: 1px solid var(--dsw-alias-border-l2);\n}\n.dsh-rewind-cleanup-head {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n.dsh-rewind-cleanup-label {\n flex: 1;\n min-width: 0;\n font-size: 13px;\n font-weight: 500;\n line-height: 1.5;\n color: var(--dsw-alias-label-primary);\n}\n.dsh-rewind-cleanup-hint {\n margin: 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-tertiary);\n}\n.dsh-rewind-cleanup-error {\n margin: 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-error);\n}\n/* Switch row: label left, role=switch button right, hint below (Subagent module). */\n.dsh-rewind-cleanup-toggle-row {\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n gap: 16px;\n font-size: 13px;\n line-height: 1.5;\n color: var(--dsw-alias-label-primary);\n}\n.dsh-rewind-cleanup-toggle-label {\n flex: 1;\n min-width: 0;\n}\n.dsh-rewind-cleanup-switch {\n box-sizing: border-box;\n position: relative;\n flex: 0 0 auto;\n width: 36px;\n height: 20px;\n padding: 2px;\n border: 0;\n border-radius: 10px;\n background: var(--dsw-alias-border-l3);\n cursor: pointer;\n}\n.dsh-rewind-cleanup-switch-on {\n background: var(--dsw-alias-brand-primary);\n}\n.dsh-rewind-cleanup-switch:disabled {\n cursor: default;\n opacity: 0.5;\n}\n.dsh-rewind-cleanup-switch:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 2px;\n}\n.dsh-rewind-cleanup-thumb {\n display: block;\n width: 16px;\n height: 16px;\n border-radius: 50%;\n corner-shape: round;\n background: var(--dsw-alias-label-primary-foreground);\n transition: transform 120ms ease;\n}\n.dsh-rewind-cleanup-switch-on .dsh-rewind-cleanup-thumb {\n transform: translateX(16px);\n}\n.dsh-rewind-cleanup-input {\n box-sizing: border-box;\n height: 34px;\n padding: 0 12px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 8px;\n background: var(--dsw-alias-bg-layer-3);\n font: inherit;\n font-size: 13px;\n line-height: 1.5;\n color: var(--dsw-alias-label-primary);\n}\n.dsh-rewind-cleanup-input:focus-visible {\n outline: none;\n border-color: var(--dsw-alias-brand-primary);\n}\n.dsh-rewind-cleanup-input:disabled {\n color: var(--dsw-alias-label-tertiary);\n cursor: default;\n}\n.dsh-rewind-cleanup-input-invalid {\n border-color: var(--dsw-alias-label-error);\n}\n.dsh-rewind-cleanup-footer {\n display: flex;\n align-items: center;\n justify-content: flex-end;\n gap: 8px;\n padding: 12px 0 4px;\n border-top: 1px solid var(--dsw-alias-border-l2);\n}\n.dsh-rewind-cleanup-failed {\n flex: 1;\n min-width: 0;\n margin: 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-error);\n}\n.dsh-rewind-cleanup-discard,\n.dsh-rewind-cleanup-save {\n appearance: none;\n border: 1px solid transparent;\n border-radius: 8px;\n padding: 5px 14px;\n font: inherit;\n font-size: 13px;\n line-height: 1.5;\n cursor: pointer;\n}\n.dsh-rewind-cleanup-discard {\n border-color: var(--dsw-alias-border-l2);\n background: none;\n color: var(--dsw-alias-label-secondary);\n}\n.dsh-rewind-cleanup-discard:hover:not(:disabled) {\n color: var(--dsw-alias-label-primary);\n border-color: var(--dsw-alias-label-dimmed);\n}\n.dsh-rewind-cleanup-save {\n background: var(--dsw-alias-label-primary);\n color: var(--dsw-alias-bg-layer-3);\n}\n.dsh-rewind-cleanup-discard:disabled,\n.dsh-rewind-cleanup-save:disabled {\n opacity: 0.4;\n cursor: default;\n}\n.dsh-rewind-cleanup-discard:focus-visible,\n.dsh-rewind-cleanup-save:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n";
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Cross-channel session event-log reader.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* (`get events(): readonly
|
|
4
|
+
* This is a "0.1.1-rc.2 ↔ 0.1.2-rc.1" dual-channel reader. Harness 0.1.1-rc.2
|
|
5
|
+
* exposes the full log as `Session.events` (`get events(): readonly
|
|
6
|
+
* SessionEvent[]`); the 0.1.2 line (alpha.4+, now 0.1.2-rc.1) removed that
|
|
6
7
|
* member and replaced it with the on-demand APIs `snapshotEvents(from?, to?)`
|
|
7
8
|
* (half-open range, internally cached), `eventAt(seq)`, `ownEvents()` and
|
|
8
9
|
* `seq`. This module reads the full log through whichever channel the host
|
|
9
|
-
* exposes, so the plugin keeps one code path across both.
|
|
10
|
+
* exposes, so the plugin keeps one code path across both endpoints.
|
|
10
11
|
*
|
|
11
12
|
* `snapshotEvents()` is deliberately preferred over `ownEvents()`:
|
|
12
13
|
* `snapshotEvents()` defaults to the whole log (including any fork-inherited
|
|
13
|
-
* prefix), which is exactly what `Session.events` returned on rc.2 — the
|
|
14
|
+
* prefix), which is exactly what `Session.events` returned on 0.1.1-rc.2 — the
|
|
14
15
|
* rewind semantics are preserved for fork-seeded sessions. `ownEvents()`
|
|
15
16
|
* would drop the inherited prefix and change behaviour.
|
|
16
17
|
*
|
|
@@ -20,15 +21,17 @@ import type { Session, SessionEvent } from '@deepseek-ai/dsh-session';
|
|
|
20
21
|
/**
|
|
21
22
|
* Read the full event log of a session, transparently across harness channels:
|
|
22
23
|
* - 0.1.1-rc.2 (`Session.events`): the live immutable log array.
|
|
23
|
-
* - 0.1.2-
|
|
24
|
-
* by the session until the next append.
|
|
24
|
+
* - 0.1.2-rc.1 (`Session.snapshotEvents()`): a deep-frozen snapshot, cached
|
|
25
|
+
* by the session until the next append. This is the single 0.1.2-line
|
|
26
|
+
* channel (alpha.2/3 still exposed `events` and thus fall into the
|
|
27
|
+
* 0.1.1-rc.2 branch below).
|
|
25
28
|
*
|
|
26
29
|
* Falls back to an empty log when the session exposes neither member (a future
|
|
27
30
|
* harness shape) rather than throwing — callers already treat the empty log as
|
|
28
31
|
* "no candidates".
|
|
29
32
|
*
|
|
30
33
|
* @param session - the session to read.
|
|
31
|
-
* @returns the full event log in log order (the same shape rc.2's `events`
|
|
34
|
+
* @returns the full event log in log order (the same shape 0.1.1-rc.2's `events`
|
|
32
35
|
* returned, so existing consumers are unchanged).
|
|
33
36
|
*/
|
|
34
37
|
export declare function eventsOf(session: Session): readonly SessionEvent[];
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Version-neutral settings-namespace reading for the host half.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* This is a "0.1.1-rc.2 ↔ 0.1.2-rc.1" dual channel. DSH 0.1.1-rc.2 exposes a
|
|
5
|
+
* `settingsNamespace(value)` brand helper, and `settings.get` is typed to
|
|
6
|
+
* require a branded `SettingsNamespace`. The 0.1.2 line (0.1.2-rc.1) removed
|
|
6
7
|
* that helper (the `SettingsNamespace` *type* remains) and `settings.get`
|
|
7
8
|
* 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-
|
|
9
|
+
* call: on 0.1.1-rc.2 the brand is a compile-time marker erased at runtime (so
|
|
10
|
+
* `settingsNamespace(ns)` returns `ns`), on 0.1.2-rc.1 the brand helper is
|
|
10
11
|
* absent and the raw `ns` string is used directly. That is what lets a single
|
|
11
12
|
* compiled host bundle link and run on both harness generations.
|
|
12
13
|
*
|
|
@@ -16,17 +17,17 @@
|
|
|
16
17
|
export interface SettingsProviderLike {
|
|
17
18
|
/**
|
|
18
19
|
* Read one registered settings section by namespace. Accepts whatever the
|
|
19
|
-
* running DSH generation passes: a branded `SettingsNamespace` (rc.2,
|
|
20
|
-
* erased) or the raw namespace string (0.1.2-
|
|
20
|
+
* running DSH generation passes: a branded `SettingsNamespace` (0.1.1-rc.2,
|
|
21
|
+
* brand erased) or the raw namespace string (0.1.2-rc.1).
|
|
21
22
|
*/
|
|
22
23
|
get(ns: string): unknown;
|
|
23
24
|
}
|
|
24
|
-
/** The `settingsNamespace(ns)` brand helper, or `undefined` when removed (0.1.2-
|
|
25
|
+
/** The `settingsNamespace(ns)` brand helper, or `undefined` when removed (0.1.2-rc.1). */
|
|
25
26
|
export type SettingsNamespaceBrand = ((value: string) => string) | undefined;
|
|
26
27
|
/**
|
|
27
28
|
* Read one settings section keyed by `ns`, tolerant of the settings-namespace
|
|
28
|
-
* brand across DSH generations. Pass the brand helper when available (the
|
|
29
|
-
* path); it is `undefined` on 0.1.2-
|
|
30
|
-
* directly. Never throws: an absent section simply returns `undefined`.
|
|
29
|
+
* brand across DSH generations. Pass the brand helper when available (the
|
|
30
|
+
* 0.1.1-rc.2 path); it is `undefined` on 0.1.2-rc.1, where the raw `ns` string
|
|
31
|
+
* is used directly. Never throws: an absent section simply returns `undefined`.
|
|
31
32
|
*/
|
|
32
33
|
export declare function readSettingsSection(provider: SettingsProviderLike, ns: string, brand: SettingsNamespaceBrand): unknown;
|
|
@@ -46,9 +46,9 @@ export declare const CleanupConfigSchema: z<CleanupConfig>;
|
|
|
46
46
|
/**
|
|
47
47
|
* Structural face of the settings scope the host needs for the policy: a
|
|
48
48
|
* resolved read and a validated write. Kept local (never imports the settings
|
|
49
|
-
* contract) so the host bundle links on both rc.2 and
|
|
50
|
-
* API drift (
|
|
51
|
-
* host passes in, never to this module.
|
|
49
|
+
* contract) so the host bundle links on both 0.1.1-rc.2 and 0.1.2-rc.1 — the
|
|
50
|
+
* settings API drift (0.1.2 adds `mutate`, 0.1.1 does not) is confined to the
|
|
51
|
+
* seam the host passes in, never to this module.
|
|
52
52
|
*/
|
|
53
53
|
export interface CleanupSettingsScope {
|
|
54
54
|
/** The resolved policy: schema defaults, then base, then the user layer. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-rewind-plugin",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.5",
|
|
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",
|