dsh-rewind-plugin 0.6.1 → 0.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +1 -0
- package/README.md +1 -0
- package/docs/compat/audit.md +5 -1
- package/docs/contract/client-contract.md +1 -1
- package/docs/contract/client-contract.zh.md +1 -1
- package/lib/client.js +81 -25
- package/lib/types/client/portals.d.ts +39 -7
- package/package.json +1 -1
package/README.en.md
CHANGED
|
@@ -152,6 +152,7 @@ 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
|
|
package/README.md
CHANGED
|
@@ -140,6 +140,7 @@ 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
|
|
package/docs/compat/audit.md
CHANGED
|
@@ -32,7 +32,11 @@
|
|
|
32
32
|
> `^0.1.0-rc.6 || ^0.1.1-rc.2` and will gain the `0.1.2` tuple when the harness
|
|
33
33
|
> publishes it. Pinned by `tests/chat-channel.test.ts` (channel precedence + the
|
|
34
34
|
> alpha.1 snapshot shape) and `tests/client-dom.test.ts` (the button-target
|
|
35
|
-
> pairing that consumes the chat).
|
|
35
|
+
> pairing that consumes the chat). The composer refill is dual-channel the same
|
|
36
|
+
> way: on alpha.1 the withdrawn text is written through the `conversation`
|
|
37
|
+
> service's `input` resolver's `setDraft` (the harness's own Lexical editor),
|
|
38
|
+
> else the rc.2 `<textarea>` / alpha.1 `contenteditable` DOM write (`writeComposer`
|
|
39
|
+
> in `src/client/portals.tsx`); pinned by `tests/client-composer.test.ts`.
|
|
36
40
|
|
|
37
41
|
## Definition of "fully compatible" (invariants)
|
|
38
42
|
|
|
@@ -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
|
|
package/lib/client.js
CHANGED
|
@@ -689,8 +689,8 @@ function userTextAt(chat, seq) {
|
|
|
689
689
|
}
|
|
690
690
|
return void 0;
|
|
691
691
|
}
|
|
692
|
-
function
|
|
693
|
-
const textarea = document.querySelector(
|
|
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;
|
|
@@ -716,24 +752,28 @@ async function runRewindAndFill(session, seq, mode, currentSessionId, chatOf) {
|
|
|
716
752
|
if (currentSessionId() !== session.sessionId) return;
|
|
717
753
|
const text = userTextAt(chatOf(session), seq);
|
|
718
754
|
if (text === void 0 || text === "") return;
|
|
719
|
-
|
|
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
|
|
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 (
|
|
729
|
-
const card =
|
|
730
|
-
const rect = card instanceof HTMLElement ? card.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
|
|
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
779
|
var ACTIONS_ROOT_SELECTOR = "[data-time-hover-root]";
|
|
@@ -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
|
|
928
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
1086
|
-
const card =
|
|
1087
|
-
return card ??
|
|
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
|
|
1185
|
+
const composerSurface2 = () => document.querySelector(COMPOSER_TEXTAREA_SELECTOR2) ?? document.querySelector(COMPOSER_EDITABLE_SELECTOR2);
|
|
1130
1186
|
yield () => {
|
|
1131
1187
|
style.remove();
|
|
1132
1188
|
};
|
|
@@ -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 {
|
|
@@ -74,13 +81,34 @@ export interface SlotsLike {
|
|
|
74
81
|
}) => ReactNode): () => void;
|
|
75
82
|
}
|
|
76
83
|
/**
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
|
|
84
|
+
* The alpha.1+ session input facade's write face (structural, so the plugin
|
|
85
|
+
* never imports the conversation UI package). `setDraft` replaces the whole
|
|
86
|
+
* composer draft through the harness's own Lexical editor — the correct way
|
|
87
|
+
* to restore the withdrawn text.
|
|
88
|
+
*/
|
|
89
|
+
interface ComposerDraftWriter {
|
|
90
|
+
setDraft(text: string): void;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Fill the dsh composer with `text`. DOM dual-channel fallback: the rc.2
|
|
94
|
+
* `<textarea>` path, then the alpha.1+ `contenteditable` path. Used by
|
|
95
|
+
* `setComposerText` (the harness-facade-aware writer) as the last-resort and
|
|
96
|
+
* by `runRewindAndFill` to put the withdrawn target message back into the
|
|
97
|
+
* composer after a rewind. Best-effort — no composer match means false,
|
|
98
|
+
* never a throw.
|
|
82
99
|
*/
|
|
83
100
|
export declare function fillComposer(text: string): boolean;
|
|
101
|
+
/**
|
|
102
|
+
* Dual-channel composer write, mirroring `chatSnapshotOf`: prefer the harness
|
|
103
|
+
* facade's `setDraft` (alpha.1+, correct whole-draft replace), then degrade
|
|
104
|
+
* to the DOM `fillComposer` (rc.2 textarea / alpha.1 contenteditable). A
|
|
105
|
+
* facade that throws (session teardown) is treated as absent so the DOM path
|
|
106
|
+
* still restores the text. Never throws.
|
|
107
|
+
* @param text - the withdrawn target message text.
|
|
108
|
+
* @param facade - the alpha.1+ session input draft writer, when reachable.
|
|
109
|
+
* @returns whether a channel applied the text.
|
|
110
|
+
*/
|
|
111
|
+
export declare function writeComposer(text: string, facade: ComposerDraftWriter | undefined): boolean;
|
|
84
112
|
/**
|
|
85
113
|
* Execute one rewind from the popover and, when it settles successfully,
|
|
86
114
|
* put the withdrawn target message's text back into the composer so the
|
|
@@ -94,7 +122,7 @@ export declare function fillComposer(text: string): boolean;
|
|
|
94
122
|
* the old baseline heuristic refilled withdrawn text into the composer
|
|
95
123
|
* after switching sessions or restarting dsh.
|
|
96
124
|
*/
|
|
97
|
-
export declare function runRewindAndFill(session: SessionFace, seq: number, mode: 'chat' | 'both', currentSessionId: () => string | undefined, chatOf: ChatOf): Promise<void>;
|
|
125
|
+
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
126
|
/**
|
|
99
127
|
* Collect the portal targets of one session: user rows × snapshot nodes.
|
|
100
128
|
* Exported as a test seam — the DOM→targets pairing that drives the ↶ button
|
|
@@ -111,7 +139,11 @@ interface RewindPortalsProps extends RewindBridgeDeps {
|
|
|
111
139
|
* skipped when the target set is unchanged), so the plugin never runs a
|
|
112
140
|
* synchronous full-transcript scan inside a commit microtask.
|
|
113
141
|
*/
|
|
114
|
-
export declare function RewindPortals({ sessionId, sessionOf, chatOf, currentSessionId, t, subscribeLocale }: RewindPortalsProps): ReactNode;
|
|
142
|
+
export declare function RewindPortals({ sessionId, sessionOf, chatOf, currentSessionId, t, subscribeLocale, setComposerText }: RewindPortalsProps): ReactNode;
|
|
143
|
+
/** The current composer draft, on whichever channel: rc.2 textarea `.value`,
|
|
144
|
+
* alpha.1+ contenteditable `textContent`. Empty when the composer is absent.
|
|
145
|
+
* Exported as a test seam (the empty-composer guard in `retractPending`). */
|
|
146
|
+
export declare function composerText(): string;
|
|
115
147
|
/**
|
|
116
148
|
* Build the slot-entry component for the plugin apply(): a tiny bridge that
|
|
117
149
|
* injects the apply-time capabilities (session resolution, locale, rewind
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-rewind-plugin",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.2",
|
|
4
4
|
"description": "DSH 插件:真正便捷无感的同窗口内对话回退,从不新建分支;自带轻量工作区备份,可一并还原文件(完整 Claude Code /rewind 语义)。 · DSH plugin: genuinely effortless in-window conversation rewind — never forking a new session; ships a lightweight workspace backup that restores files together with the rewind (full Claude Code /rewind semantics).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"deepseek-harness",
|