dsh-rewind-plugin 0.18.1 → 0.19.0
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 +0 -8
- package/README.md +0 -5
- package/lib/client.js +372 -270
- package/lib/types/client/candidates.d.ts +4 -6
- package/lib/types/client/hidden.d.ts +30 -36
- package/lib/types/client/index.d.ts +1 -7
- package/lib/types/client/portals.d.ts +3 -65
- package/lib/types/client/visibility.d.ts +89 -0
- package/package.json +1 -1
package/README.en.md
CHANGED
|
@@ -159,14 +159,6 @@ This plugin deliberately stays lightweight and focused on one thing — "convers
|
|
|
159
159
|
> This project and DeepSeek Harness are both in developer preview. Pin exact
|
|
160
160
|
> versions in reproducible environments and review the behavior notes above.
|
|
161
161
|
|
|
162
|
-
## Client contract
|
|
163
|
-
|
|
164
|
-
Third-party DOM plugins that need to know which transcript rows a rewind
|
|
165
|
-
withdrew should consume the stable, locale-independent helpers exported from
|
|
166
|
-
`dsh-rewind-plugin/client` — never parse
|
|
167
|
-
`outcome.text`. The `data-dsh-rewind-hidden` attribute marks withdrawn rows
|
|
168
|
-
(observational only). Details: [docs/contract/client-contract.md](docs/contract/client-contract.md).
|
|
169
|
-
|
|
170
162
|
## Known issues
|
|
171
163
|
|
|
172
164
|
1. **Exported logs are complete** — a rewind only removes messages from the model context and the view; the exported session log (`/export`) still contains **withdrawn messages**. This plugin cannot alter exports.
|
package/README.md
CHANGED
|
@@ -143,11 +143,6 @@ rm <dsh home>/snapshot-cleanup-last-sweep.json
|
|
|
143
143
|
> 本项目与 DeepSeek Harness 均处于开发者预览阶段。可复现环境请 pin 精确版本,
|
|
144
144
|
> 并阅读上述行为说明。
|
|
145
145
|
|
|
146
|
-
## 客户端契约
|
|
147
|
-
|
|
148
|
-
需要获知哪些转录行被回退撤回的第三方 DOM 插件,应使用 `dsh-rewind-plugin/client` 导出的稳定、与本地化无关的纯函数,切勿解析 `outcome.text`。`data-dsh-rewind-hidden` 属性标记被撤回的行(仅观测性)。
|
|
149
|
-
详见:[docs/contract/client-contract.zh.md](docs/contract/client-contract.zh.md)。
|
|
150
|
-
|
|
151
146
|
## 已知问题
|
|
152
147
|
|
|
153
148
|
1. **导出的日志是完整内容**——回退只是把消息从模型上下文和视图中移除,`/export` 导出的会话日志包含**已撤回的消息**。本插件无法改动导出。
|
package/lib/client.js
CHANGED
|
@@ -9,6 +9,7 @@ var __defProp = Object.defineProperty;
|
|
|
9
9
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
10
10
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
11
11
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
12
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
12
13
|
var __export = (target, all) => {
|
|
13
14
|
for (var name2 in all)
|
|
14
15
|
__defProp(target, name2, { get: all[name2], enumerable: true });
|
|
@@ -22,22 +23,72 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
22
23
|
return to;
|
|
23
24
|
};
|
|
24
25
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
26
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
25
27
|
|
|
26
28
|
// src/client/index.ts
|
|
27
29
|
var index_exports = {};
|
|
28
30
|
__export(index_exports, {
|
|
29
31
|
apply: () => apply,
|
|
30
|
-
hiddenSeqsOf: () => hiddenSeqsOf,
|
|
31
32
|
inject: () => inject,
|
|
32
|
-
name: () => name
|
|
33
|
-
targetSeqOfArgs: () => targetSeqOfArgs
|
|
33
|
+
name: () => name
|
|
34
34
|
});
|
|
35
35
|
module.exports = __toCommonJS(index_exports);
|
|
36
36
|
|
|
37
|
+
// src/client/candidates.ts
|
|
38
|
+
var PREVIEW_CHARS = 80;
|
|
39
|
+
var DEFAULT_CANDIDATE_LIMIT = 100;
|
|
40
|
+
function messagePreviewOf(message) {
|
|
41
|
+
const text = message.content.map((block) => block.type === "text" && typeof block.text === "string" ? block.text : "").join("").replace(/\s+/g, " ").trim();
|
|
42
|
+
return text.length <= PREVIEW_CHARS ? text : `${text.slice(0, PREVIEW_CHARS - 1)}\u2026`;
|
|
43
|
+
}
|
|
44
|
+
function formatCandidateTime(time) {
|
|
45
|
+
const d = new Date(time);
|
|
46
|
+
const hh = String(d.getHours()).padStart(2, "0");
|
|
47
|
+
const mm = String(d.getMinutes()).padStart(2, "0");
|
|
48
|
+
return `${hh}:${mm}`;
|
|
49
|
+
}
|
|
50
|
+
function rewindCandidatesOf(snap, limit = DEFAULT_CANDIDATE_LIMIT) {
|
|
51
|
+
const candidates = [];
|
|
52
|
+
for (let i = snap.order.length - 1; i >= 0 && candidates.length < limit; i--) {
|
|
53
|
+
const key = snap.order[i];
|
|
54
|
+
if (key === void 0) continue;
|
|
55
|
+
const node = snap.nodes.get(key);
|
|
56
|
+
if (node === void 0 || node.kind !== "user" && node.kind !== "steering") continue;
|
|
57
|
+
candidates.push({
|
|
58
|
+
seq: node.data.seq,
|
|
59
|
+
time: node.data.time,
|
|
60
|
+
preview: messagePreviewOf(node.data)
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
return candidates;
|
|
64
|
+
}
|
|
65
|
+
function rewindCandidatesOfChat(snap) {
|
|
66
|
+
return rewindCandidatesOf(snap);
|
|
67
|
+
}
|
|
68
|
+
function rewindOptionsFromCandidates(candidates, t) {
|
|
69
|
+
return candidates.map((candidate) => ({
|
|
70
|
+
id: String(candidate.seq),
|
|
71
|
+
label: candidate.preview || t("popover.noText"),
|
|
72
|
+
detail: formatCandidateTime(candidate.time)
|
|
73
|
+
}));
|
|
74
|
+
}
|
|
75
|
+
|
|
37
76
|
// src/client/hidden.ts
|
|
38
77
|
var ENGINE_COMMAND = "rewind-plugin";
|
|
39
78
|
var LEGACY_ENGINE_COMMAND = "rewind";
|
|
40
79
|
var ENGINE_COMMANDS = /* @__PURE__ */ new Set([ENGINE_COMMAND, LEGACY_ENGINE_COMMAND]);
|
|
80
|
+
function chatNodesOf(chat) {
|
|
81
|
+
if (chat === void 0) return EMPTY_NODES;
|
|
82
|
+
const values = chat.nodes.values;
|
|
83
|
+
if (typeof values === "function") return [...values.call(chat.nodes)];
|
|
84
|
+
const nodes = [];
|
|
85
|
+
for (const key of chat.order) {
|
|
86
|
+
const node = chat.nodes.get(key);
|
|
87
|
+
if (node !== void 0) nodes.push(node);
|
|
88
|
+
}
|
|
89
|
+
return nodes;
|
|
90
|
+
}
|
|
91
|
+
var EMPTY_NODES = [];
|
|
41
92
|
function resolveChatWatch(resolveView, sessionId, cb) {
|
|
42
93
|
const view = resolveView(sessionId);
|
|
43
94
|
return view?.subscribe?.(cb) ?? (() => {
|
|
@@ -48,9 +99,8 @@ function chatSnapshotOf(chatView) {
|
|
|
48
99
|
}
|
|
49
100
|
function messageTextAt(chat, seq) {
|
|
50
101
|
if (chat === void 0) return void 0;
|
|
51
|
-
for (const
|
|
52
|
-
|
|
53
|
-
if (node === void 0 || node.kind !== "user" && node.kind !== "steering") continue;
|
|
102
|
+
for (const node of chatNodesOf(chat)) {
|
|
103
|
+
if (node.kind !== "user" && node.kind !== "steering") continue;
|
|
54
104
|
const data = node.data;
|
|
55
105
|
if (data.seq === seq) {
|
|
56
106
|
return data.content?.map((block) => block.type === "text" && typeof block.text === "string" ? block.text : "").join("");
|
|
@@ -60,9 +110,8 @@ function messageTextAt(chat, seq) {
|
|
|
60
110
|
}
|
|
61
111
|
function messageAttachmentsAt(chat, seq) {
|
|
62
112
|
if (chat === void 0) return void 0;
|
|
63
|
-
for (const
|
|
64
|
-
|
|
65
|
-
if (node === void 0 || node.kind !== "user" && node.kind !== "steering") continue;
|
|
113
|
+
for (const node of chatNodesOf(chat)) {
|
|
114
|
+
if (node.kind !== "user" && node.kind !== "steering") continue;
|
|
66
115
|
const data = node.data;
|
|
67
116
|
if (data.seq !== seq) continue;
|
|
68
117
|
const images = [];
|
|
@@ -100,141 +149,21 @@ function isPreviewCommand(command) {
|
|
|
100
149
|
function isCandidateCommand(command) {
|
|
101
150
|
return (command.args ?? "").includes("__candidates");
|
|
102
151
|
}
|
|
103
|
-
function
|
|
104
|
-
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
for (const span of sorted) {
|
|
108
|
-
const last = merged[merged.length - 1];
|
|
109
|
-
if (last !== void 0 && span.start <= last.end) {
|
|
110
|
-
if (span.end > last.end) last.end = span.end;
|
|
111
|
-
continue;
|
|
112
|
-
}
|
|
113
|
-
merged.push({ start: span.start, end: span.end });
|
|
114
|
-
}
|
|
115
|
-
return merged;
|
|
116
|
-
}
|
|
117
|
-
function cutsContain(cuts, seq) {
|
|
118
|
-
let low = 0;
|
|
119
|
-
let high = cuts.length - 1;
|
|
120
|
-
while (low <= high) {
|
|
121
|
-
const middle = low + high >> 1;
|
|
122
|
-
const cut = cuts[middle];
|
|
123
|
-
if (seq < cut.start) high = middle - 1;
|
|
124
|
-
else if (seq > cut.end) low = middle + 1;
|
|
125
|
-
else return true;
|
|
126
|
-
}
|
|
127
|
-
return false;
|
|
128
|
-
}
|
|
129
|
-
function liveAnchorKeys(snap, cuts) {
|
|
130
|
-
const live = /* @__PURE__ */ new Map();
|
|
131
|
-
for (const key of snap.order) {
|
|
132
|
-
const node = snap.nodes.get(key);
|
|
133
|
-
if (node === void 0 || cutsContain(cuts, node.anchorSeq)) continue;
|
|
134
|
-
const keys = live.get(node.anchorSeq);
|
|
135
|
-
if (keys === void 0) live.set(node.anchorSeq, [key]);
|
|
136
|
-
else keys.push(key);
|
|
137
|
-
}
|
|
138
|
-
return live;
|
|
139
|
-
}
|
|
140
|
-
var EMPTY_KEYS = [];
|
|
141
|
-
function addWithdrawnTurnChrome(snap, cuts, hidden) {
|
|
142
|
-
const locations = snap.locations;
|
|
143
|
-
if (locations === void 0 || cuts.length === 0) return;
|
|
144
|
-
let live;
|
|
145
|
-
for (const key of snap.order) {
|
|
146
|
-
const chrome = snap.nodes.get(key);
|
|
147
|
-
if (chrome === void 0 || chrome.kind !== "turn-process") continue;
|
|
148
|
-
const located = chrome.location?.turn;
|
|
149
|
-
if (located?.start === void 0) continue;
|
|
150
|
-
const turn = located.turn;
|
|
151
|
-
if (typeof turn !== "number") continue;
|
|
152
|
-
const keys = locations.getTurn(turn);
|
|
153
|
-
if (keys.length === 0) continue;
|
|
154
|
-
const rows = keys.map((member) => snap.nodes.get(member)).filter((member) => member !== void 0);
|
|
155
|
-
if (rows.length !== keys.length) continue;
|
|
156
|
-
if (!rows.every((member) => member.key === chrome.key || cutsContain(cuts, member.anchorSeq))) continue;
|
|
157
|
-
live ?? (live = liveAnchorKeys(snap, cuts));
|
|
158
|
-
if ((live.get(chrome.anchorSeq) ?? EMPTY_KEYS).some((holder) => holder !== chrome.key)) continue;
|
|
159
|
-
hidden.add(chrome.anchorSeq);
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
var hiddenSeqsMemo = /* @__PURE__ */ new WeakMap();
|
|
163
|
-
function hiddenSeqsOf(snap) {
|
|
164
|
-
const memo = hiddenSeqsMemo.get(snap);
|
|
165
|
-
if (memo !== void 0) return new Set(memo);
|
|
166
|
-
const hidden = /* @__PURE__ */ new Set();
|
|
167
|
-
const spans = [];
|
|
168
|
-
for (const key of snap.order) {
|
|
169
|
-
const node = snap.nodes.get(key);
|
|
170
|
-
if (node === void 0 || node.kind !== "command") continue;
|
|
152
|
+
function rewindCutsOf(nodes) {
|
|
153
|
+
const cuts = [];
|
|
154
|
+
for (const node of nodes) {
|
|
155
|
+
if (node.kind !== "command") continue;
|
|
171
156
|
const command = node.data;
|
|
172
157
|
if (command.name === null || !ENGINE_COMMANDS.has(command.name)) continue;
|
|
173
|
-
if (isPreviewCommand(command) || isCandidateCommand(command))
|
|
174
|
-
hidden.add(command.seq);
|
|
175
|
-
continue;
|
|
176
|
-
}
|
|
158
|
+
if (isPreviewCommand(command) || isCandidateCommand(command)) continue;
|
|
177
159
|
if (command.outcome?.kind !== "success") continue;
|
|
178
160
|
const marker = command.outcome.sourceEventSeq;
|
|
179
161
|
if (marker === void 0) continue;
|
|
180
|
-
hidden.add(command.seq);
|
|
181
162
|
const target = targetSeqOfArgs(command.args);
|
|
182
|
-
if (target
|
|
183
|
-
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
const cuts = coalesceCuts(spans);
|
|
187
|
-
for (const key of snap.order) {
|
|
188
|
-
const node = snap.nodes.get(key);
|
|
189
|
-
if (node === void 0) continue;
|
|
190
|
-
if (cutsContain(cuts, node.anchorSeq)) hidden.add(node.anchorSeq);
|
|
191
|
-
}
|
|
192
|
-
try {
|
|
193
|
-
addWithdrawnTurnChrome(snap, cuts, hidden);
|
|
194
|
-
} catch {
|
|
163
|
+
if (target === void 0 || target > marker) continue;
|
|
164
|
+
cuts.push({ target, marker });
|
|
195
165
|
}
|
|
196
|
-
|
|
197
|
-
return hidden;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
// src/client/candidates.ts
|
|
201
|
-
var PREVIEW_CHARS = 80;
|
|
202
|
-
var DEFAULT_CANDIDATE_LIMIT = 100;
|
|
203
|
-
function messagePreviewOf(message) {
|
|
204
|
-
const text = message.content.map((block) => block.type === "text" && typeof block.text === "string" ? block.text : "").join("").replace(/\s+/g, " ").trim();
|
|
205
|
-
return text.length <= PREVIEW_CHARS ? text : `${text.slice(0, PREVIEW_CHARS - 1)}\u2026`;
|
|
206
|
-
}
|
|
207
|
-
function formatCandidateTime(time) {
|
|
208
|
-
const d = new Date(time);
|
|
209
|
-
const hh = String(d.getHours()).padStart(2, "0");
|
|
210
|
-
const mm = String(d.getMinutes()).padStart(2, "0");
|
|
211
|
-
return `${hh}:${mm}`;
|
|
212
|
-
}
|
|
213
|
-
function rewindCandidatesOf(snap, hidden, limit = DEFAULT_CANDIDATE_LIMIT) {
|
|
214
|
-
const candidates = [];
|
|
215
|
-
for (let i = snap.order.length - 1; i >= 0 && candidates.length < limit; i--) {
|
|
216
|
-
const key = snap.order[i];
|
|
217
|
-
if (key === void 0) continue;
|
|
218
|
-
const node = snap.nodes.get(key);
|
|
219
|
-
if (node === void 0 || node.kind !== "user" && node.kind !== "steering") continue;
|
|
220
|
-
if (hidden.has(node.anchorSeq ?? node.data.seq)) continue;
|
|
221
|
-
candidates.push({
|
|
222
|
-
seq: node.data.seq,
|
|
223
|
-
time: node.data.time,
|
|
224
|
-
preview: messagePreviewOf(node.data)
|
|
225
|
-
});
|
|
226
|
-
}
|
|
227
|
-
return candidates;
|
|
228
|
-
}
|
|
229
|
-
function rewindCandidatesOfChat(snap) {
|
|
230
|
-
return rewindCandidatesOf(snap, hiddenSeqsOf(snap));
|
|
231
|
-
}
|
|
232
|
-
function rewindOptionsFromCandidates(candidates, t) {
|
|
233
|
-
return candidates.map((candidate) => ({
|
|
234
|
-
id: String(candidate.seq),
|
|
235
|
-
label: candidate.preview || t("popover.noText"),
|
|
236
|
-
detail: formatCandidateTime(candidate.time)
|
|
237
|
-
}));
|
|
166
|
+
return cuts;
|
|
238
167
|
}
|
|
239
168
|
|
|
240
169
|
// src/client/styles.ts
|
|
@@ -503,12 +432,11 @@ function closePopover() {
|
|
|
503
432
|
function findCommand(chat, match) {
|
|
504
433
|
if (chat === void 0) return void 0;
|
|
505
434
|
let found;
|
|
506
|
-
for (const
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
}
|
|
435
|
+
for (const node of chatNodesOf(chat)) {
|
|
436
|
+
if (node.kind !== "command") continue;
|
|
437
|
+
const command = node.data;
|
|
438
|
+
if (!match(command)) continue;
|
|
439
|
+
if (found === void 0 || command.seq >= found.seq) found = command;
|
|
512
440
|
}
|
|
513
441
|
return found;
|
|
514
442
|
}
|
|
@@ -516,12 +444,10 @@ function knownCommandSeqs(session, chatOf, match) {
|
|
|
516
444
|
const known = /* @__PURE__ */ new Set();
|
|
517
445
|
const chat = chatOf(session);
|
|
518
446
|
if (chat === void 0) return known;
|
|
519
|
-
for (const
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
if (match(command)) known.add(command.seq);
|
|
524
|
-
}
|
|
447
|
+
for (const node of chatNodesOf(chat)) {
|
|
448
|
+
if (node.kind !== "command") continue;
|
|
449
|
+
const command = node.data;
|
|
450
|
+
if (match(command)) known.add(command.seq);
|
|
525
451
|
}
|
|
526
452
|
return known;
|
|
527
453
|
}
|
|
@@ -850,6 +776,293 @@ function openPopover(opts) {
|
|
|
850
776
|
});
|
|
851
777
|
}
|
|
852
778
|
|
|
779
|
+
// src/client/log.ts
|
|
780
|
+
var NS = "dsh-rewind";
|
|
781
|
+
var DEBUG_KEY = "dsh-rewind.debug";
|
|
782
|
+
var ALWAYS_ON = /* @__PURE__ */ new Set(["error", "warn"]);
|
|
783
|
+
function switchValue() {
|
|
784
|
+
try {
|
|
785
|
+
return window.localStorage.getItem(DEBUG_KEY) ?? "";
|
|
786
|
+
} catch {
|
|
787
|
+
return "";
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
function matches(value, ns) {
|
|
791
|
+
for (const entry of value.split(",")) {
|
|
792
|
+
const part = entry.trim();
|
|
793
|
+
if (part === "") continue;
|
|
794
|
+
if (part === "*" || part === `${NS}*`) return true;
|
|
795
|
+
if (part.endsWith("*")) {
|
|
796
|
+
if (ns.startsWith(part.slice(0, -1))) return true;
|
|
797
|
+
} else if (ns === part) {
|
|
798
|
+
return true;
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
return false;
|
|
802
|
+
}
|
|
803
|
+
function tag(scope) {
|
|
804
|
+
return `[${NS}:${scope}]`;
|
|
805
|
+
}
|
|
806
|
+
function log(level, scope, message, data) {
|
|
807
|
+
if (ALWAYS_ON.has(level)) {
|
|
808
|
+
console[level](tag(scope), message, data);
|
|
809
|
+
return;
|
|
810
|
+
}
|
|
811
|
+
if (!matches(switchValue(), `${NS}:${scope}`)) return;
|
|
812
|
+
console.info(tag(scope), message, data);
|
|
813
|
+
}
|
|
814
|
+
var rewindLog = {
|
|
815
|
+
error: (scope, message, data) => log("error", scope, message, data),
|
|
816
|
+
warn: (scope, message, data) => log("warn", scope, message, data),
|
|
817
|
+
info: (scope, message, data) => log("info", scope, message, data),
|
|
818
|
+
debug: (scope, message, data) => log("debug", scope, message, data)
|
|
819
|
+
};
|
|
820
|
+
|
|
821
|
+
// src/client/visibility.ts
|
|
822
|
+
var patches = /* @__PURE__ */ new Map();
|
|
823
|
+
var currentCleanup;
|
|
824
|
+
function installChatViewVisibility(ctx) {
|
|
825
|
+
let stopped = false;
|
|
826
|
+
const start = () => {
|
|
827
|
+
const scope = ctx.inject?.(["uiConversation"], (child) => {
|
|
828
|
+
if (!stopped) installOn(child.get("uiConversation"), ctx.sessions);
|
|
829
|
+
});
|
|
830
|
+
if (ctx.inject === void 0) {
|
|
831
|
+
installOn(ctx.get("uiConversation"), ctx.sessions);
|
|
832
|
+
}
|
|
833
|
+
return () => {
|
|
834
|
+
stopped = true;
|
|
835
|
+
currentCleanup?.();
|
|
836
|
+
currentCleanup = void 0;
|
|
837
|
+
if (typeof scope === "function") scope();
|
|
838
|
+
};
|
|
839
|
+
};
|
|
840
|
+
if (ctx.effect === void 0) {
|
|
841
|
+
start();
|
|
842
|
+
return;
|
|
843
|
+
}
|
|
844
|
+
ctx.effect(start, "dsh-rewind chat visibility");
|
|
845
|
+
}
|
|
846
|
+
function installOn(ui, sessions) {
|
|
847
|
+
currentCleanup?.();
|
|
848
|
+
currentCleanup = void 0;
|
|
849
|
+
if (ui?.views === void 0) return;
|
|
850
|
+
const views = ui.views;
|
|
851
|
+
const ensure = () => {
|
|
852
|
+
try {
|
|
853
|
+
const definition = views.entries().find((candidate) => candidate.target === "chat");
|
|
854
|
+
if (definition === void 0 || typeof definition.create !== "function") return;
|
|
855
|
+
const current = patches.get(definition);
|
|
856
|
+
const create = definition.create;
|
|
857
|
+
if (current !== void 0 && create === current.wrapper) return;
|
|
858
|
+
const wrapper = function wrappedChatBuilder() {
|
|
859
|
+
return new RewindVisibilityBuilder(create.call(definition));
|
|
860
|
+
};
|
|
861
|
+
const mutable = definition;
|
|
862
|
+
mutable.create = wrapper;
|
|
863
|
+
patches.set(definition, { original: create, wrapper });
|
|
864
|
+
rewindLog.info("visibility", "installed Chat snapshot visibility wrapper");
|
|
865
|
+
rebuildOpenSessions(ui, sessions);
|
|
866
|
+
} catch (error) {
|
|
867
|
+
rewindLog.error("visibility", "Chat visibility install probe failed", error);
|
|
868
|
+
}
|
|
869
|
+
};
|
|
870
|
+
let unsubscribe;
|
|
871
|
+
try {
|
|
872
|
+
unsubscribe = views.subscribe?.(ensure);
|
|
873
|
+
} catch (error) {
|
|
874
|
+
rewindLog.warn("visibility", "Chat view subscription failed", error);
|
|
875
|
+
}
|
|
876
|
+
ensure();
|
|
877
|
+
currentCleanup = () => {
|
|
878
|
+
unsubscribe?.();
|
|
879
|
+
restorePatches();
|
|
880
|
+
rebuildOpenSessions(ui, sessions);
|
|
881
|
+
};
|
|
882
|
+
}
|
|
883
|
+
function restorePatches() {
|
|
884
|
+
for (const [definition, record] of patches) {
|
|
885
|
+
const mutable = definition;
|
|
886
|
+
if (mutable.create === record.wrapper) mutable.create = record.original;
|
|
887
|
+
}
|
|
888
|
+
patches = /* @__PURE__ */ new Map();
|
|
889
|
+
}
|
|
890
|
+
function rebuildOpenSessions(ui, sessions) {
|
|
891
|
+
const state = sessions?.list?.getSnapshot();
|
|
892
|
+
if (state === void 0) return;
|
|
893
|
+
const ids = /* @__PURE__ */ new Set([...state.ids ?? [], ...Object.keys(state.byId ?? {})]);
|
|
894
|
+
for (const id of ids) {
|
|
895
|
+
const row = state.byId?.[id];
|
|
896
|
+
const retained = row?.retainedBy;
|
|
897
|
+
if (retained === void 0 || !Object.values(retained).some((count) => (count ?? 0) > 0)) continue;
|
|
898
|
+
try {
|
|
899
|
+
ui.binding?.(id)?.rebuild?.();
|
|
900
|
+
} catch (error) {
|
|
901
|
+
rewindLog.warn("visibility", `chat rebuild failed for ${id}`, error);
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
var RewindVisibilityBuilder = class {
|
|
906
|
+
/** @param inner - the real Chat builder. */
|
|
907
|
+
constructor(inner) {
|
|
908
|
+
__publicField(this, "inner", inner);
|
|
909
|
+
__publicField(this, "originals", /* @__PURE__ */ new Map());
|
|
910
|
+
__publicField(this, "commands", /* @__PURE__ */ new Map());
|
|
911
|
+
__publicField(this, "byTurn", /* @__PURE__ */ new Map());
|
|
912
|
+
__publicField(this, "turnProcess", /* @__PURE__ */ new Map());
|
|
913
|
+
__publicField(this, "derived", /* @__PURE__ */ new Map());
|
|
914
|
+
__publicField(this, "cuts", []);
|
|
915
|
+
__publicField(this, "cutSignature", "[]");
|
|
916
|
+
__publicField(this, "lastSignature", "[]");
|
|
917
|
+
}
|
|
918
|
+
get empty() {
|
|
919
|
+
return this.inner.empty;
|
|
920
|
+
}
|
|
921
|
+
replace(input) {
|
|
922
|
+
this.setAll(input.nodes);
|
|
923
|
+
this.lastSignature = this.cutSignature;
|
|
924
|
+
return this.inner.replace({ nodes: this.deriveAll(), timeline: input.timeline, ...changed(input) });
|
|
925
|
+
}
|
|
926
|
+
apply(input) {
|
|
927
|
+
const affected = /* @__PURE__ */ new Set();
|
|
928
|
+
let commandsChanged = false;
|
|
929
|
+
for (const node of input.upserts) {
|
|
930
|
+
const previous = this.originals.get(node.key);
|
|
931
|
+
const previousTurn = previous === void 0 ? void 0 : turnOf(previous);
|
|
932
|
+
const nextTurn = turnOf(node);
|
|
933
|
+
if (previousTurn !== void 0) affected.add(previousTurn);
|
|
934
|
+
if (nextTurn !== void 0) affected.add(nextTurn);
|
|
935
|
+
if (previous?.kind === "command" || node.kind === "command") commandsChanged = true;
|
|
936
|
+
this.setOriginal(node);
|
|
937
|
+
}
|
|
938
|
+
if (commandsChanged) this.refreshCuts();
|
|
939
|
+
if (this.cutSignature !== this.lastSignature) {
|
|
940
|
+
this.lastSignature = this.cutSignature;
|
|
941
|
+
return this.inner.replace({ nodes: this.deriveAll(), timeline: input.timeline, ...changed(input) });
|
|
942
|
+
}
|
|
943
|
+
const cuts = this.cuts;
|
|
944
|
+
const upserts = /* @__PURE__ */ new Map();
|
|
945
|
+
for (const node of input.upserts) upserts.set(node.key, this.deriveOne(node, cuts));
|
|
946
|
+
for (const turn of affected) {
|
|
947
|
+
const processKey = this.turnProcess.get(turn);
|
|
948
|
+
const base = processKey === void 0 ? void 0 : this.originals.get(processKey);
|
|
949
|
+
if (base === void 0) continue;
|
|
950
|
+
const next = this.deriveTurnProcess(base, cuts, turn, this.deriveOne(base, cuts));
|
|
951
|
+
if (this.derived.get(processKey) !== next) upserts.set(processKey, next);
|
|
952
|
+
}
|
|
953
|
+
const list = [...upserts.values()];
|
|
954
|
+
for (const node of list) this.derived.set(node.key, node);
|
|
955
|
+
return this.inner.apply({ upserts: list, timeline: input.timeline, ...changed(input) });
|
|
956
|
+
}
|
|
957
|
+
groupInput() {
|
|
958
|
+
return this.inner.groupInput?.();
|
|
959
|
+
}
|
|
960
|
+
publish() {
|
|
961
|
+
this.inner.publish?.();
|
|
962
|
+
}
|
|
963
|
+
setAll(nodes) {
|
|
964
|
+
this.originals.clear();
|
|
965
|
+
this.commands.clear();
|
|
966
|
+
this.byTurn.clear();
|
|
967
|
+
this.turnProcess.clear();
|
|
968
|
+
this.derived.clear();
|
|
969
|
+
for (const node of nodes) this.setOriginal(node);
|
|
970
|
+
this.refreshCuts();
|
|
971
|
+
}
|
|
972
|
+
setOriginal(node) {
|
|
973
|
+
const previous = this.originals.get(node.key);
|
|
974
|
+
if (previous !== void 0) this.unindex(previous);
|
|
975
|
+
this.originals.set(node.key, node);
|
|
976
|
+
if (node.kind === "command") this.commands.set(node.key, node);
|
|
977
|
+
const turn = turnOf(node);
|
|
978
|
+
if (turn === void 0) return;
|
|
979
|
+
const members = this.byTurn.get(turn) ?? /* @__PURE__ */ new Map();
|
|
980
|
+
members.set(node.key, node);
|
|
981
|
+
this.byTurn.set(turn, members);
|
|
982
|
+
if (node.kind === "turn-process") this.turnProcess.set(turn, node.key);
|
|
983
|
+
}
|
|
984
|
+
unindex(node) {
|
|
985
|
+
if (node.kind === "command") this.commands.delete(node.key);
|
|
986
|
+
const turn = turnOf(node);
|
|
987
|
+
if (turn === void 0) return;
|
|
988
|
+
const members = this.byTurn.get(turn);
|
|
989
|
+
if (members === void 0) return;
|
|
990
|
+
members.delete(node.key);
|
|
991
|
+
if (members.size === 0) this.byTurn.delete(turn);
|
|
992
|
+
if (node.kind === "turn-process" && this.turnProcess.get(turn) === node.key) {
|
|
993
|
+
this.turnProcess.delete(turn);
|
|
994
|
+
for (const member of members.values()) {
|
|
995
|
+
if (member.kind === "turn-process") {
|
|
996
|
+
this.turnProcess.set(turn, member.key);
|
|
997
|
+
break;
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
refreshCuts() {
|
|
1003
|
+
this.cuts = rewindCutsOf(this.commands.values());
|
|
1004
|
+
this.cutSignature = JSON.stringify(
|
|
1005
|
+
this.cuts.map((cut) => `${cut.target}:${cut.marker}`).sort()
|
|
1006
|
+
);
|
|
1007
|
+
}
|
|
1008
|
+
deriveAll() {
|
|
1009
|
+
const cuts = this.cuts;
|
|
1010
|
+
const derived = /* @__PURE__ */ new Map();
|
|
1011
|
+
for (const node of this.originals.values()) derived.set(node.key, this.deriveOne(node, cuts));
|
|
1012
|
+
for (const [turn, key] of this.turnProcess) {
|
|
1013
|
+
const base = this.originals.get(key);
|
|
1014
|
+
if (base === void 0) continue;
|
|
1015
|
+
const current = derived.get(key) ?? this.deriveOne(base, cuts);
|
|
1016
|
+
derived.set(key, this.deriveTurnProcess(base, cuts, turn, current));
|
|
1017
|
+
}
|
|
1018
|
+
const result = [...derived.values()];
|
|
1019
|
+
this.derived = derived;
|
|
1020
|
+
return result;
|
|
1021
|
+
}
|
|
1022
|
+
deriveOne(node, cuts) {
|
|
1023
|
+
const base = this.originals.get(node.key) ?? node;
|
|
1024
|
+
if (base.kind === "command") {
|
|
1025
|
+
const command = base.data;
|
|
1026
|
+
if (command.name !== void 0 && command.name !== null && ENGINE_COMMANDS.has(command.name)) {
|
|
1027
|
+
const args = command.args ?? "";
|
|
1028
|
+
const probe = args.includes("preview") || args.includes("__candidates");
|
|
1029
|
+
const executed = command.outcome?.kind === "success" && command.outcome.sourceEventSeq !== void 0;
|
|
1030
|
+
if (probe || executed) return hidden(base);
|
|
1031
|
+
}
|
|
1032
|
+
return base;
|
|
1033
|
+
}
|
|
1034
|
+
const contained = cuts.some((cut) => cut.target <= base.anchorSeq && base.anchorSeq <= cut.marker);
|
|
1035
|
+
return contained ? hidden(base) : base;
|
|
1036
|
+
}
|
|
1037
|
+
deriveTurnProcess(base, cuts, turn = turnOf(base), current = this.deriveOne(base, cuts)) {
|
|
1038
|
+
if (turn === void 0 || current.visibility === "hidden") return current;
|
|
1039
|
+
const members = this.byTurn.get(turn);
|
|
1040
|
+
if (members === void 0) return current;
|
|
1041
|
+
let count = 0;
|
|
1042
|
+
let allHidden = true;
|
|
1043
|
+
for (const member of members.values()) {
|
|
1044
|
+
if (member.key === base.key) continue;
|
|
1045
|
+
count += 1;
|
|
1046
|
+
if (this.deriveOne(member, cuts).visibility !== "hidden") {
|
|
1047
|
+
allHidden = false;
|
|
1048
|
+
break;
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
if (count === 0) return current;
|
|
1052
|
+
return allHidden ? hidden(base) : current;
|
|
1053
|
+
}
|
|
1054
|
+
};
|
|
1055
|
+
function hidden(node) {
|
|
1056
|
+
return node.visibility === "hidden" ? node : { ...node, visibility: "hidden" };
|
|
1057
|
+
}
|
|
1058
|
+
function turnOf(node) {
|
|
1059
|
+
const turn = node.location?.turn?.turn;
|
|
1060
|
+
return typeof turn === "number" ? turn : void 0;
|
|
1061
|
+
}
|
|
1062
|
+
function changed(input) {
|
|
1063
|
+
return input.changedTurns === void 0 ? {} : { changedTurns: input.changedTurns };
|
|
1064
|
+
}
|
|
1065
|
+
|
|
853
1066
|
// src/client/portals.tsx
|
|
854
1067
|
var import_react = require("react");
|
|
855
1068
|
var import_react_dom = require("react-dom");
|
|
@@ -885,11 +1098,11 @@ function matchPendingRows(rows, steering) {
|
|
|
885
1098
|
for (let i = 0; i < rows.length; i++) {
|
|
886
1099
|
const row = rows[i];
|
|
887
1100
|
const item = steering[i];
|
|
888
|
-
matched.push(item !== void 0 &&
|
|
1101
|
+
matched.push(item !== void 0 && matches2(item, row) ? item.id : null);
|
|
889
1102
|
}
|
|
890
1103
|
return matched;
|
|
891
1104
|
}
|
|
892
|
-
function
|
|
1105
|
+
function matches2(item, row) {
|
|
893
1106
|
if (item.attachments > 0) return row.attachments === item.attachments;
|
|
894
1107
|
return row.text === (item.text ?? "");
|
|
895
1108
|
}
|
|
@@ -899,48 +1112,6 @@ function retractSpan(steering, targetId) {
|
|
|
899
1112
|
return steering.slice(index).map((item) => item.id);
|
|
900
1113
|
}
|
|
901
1114
|
|
|
902
|
-
// src/client/log.ts
|
|
903
|
-
var NS = "dsh-rewind";
|
|
904
|
-
var DEBUG_KEY = "dsh-rewind.debug";
|
|
905
|
-
var ALWAYS_ON = /* @__PURE__ */ new Set(["error", "warn"]);
|
|
906
|
-
function switchValue() {
|
|
907
|
-
try {
|
|
908
|
-
return window.localStorage.getItem(DEBUG_KEY) ?? "";
|
|
909
|
-
} catch {
|
|
910
|
-
return "";
|
|
911
|
-
}
|
|
912
|
-
}
|
|
913
|
-
function matches2(value, ns) {
|
|
914
|
-
for (const entry of value.split(",")) {
|
|
915
|
-
const part = entry.trim();
|
|
916
|
-
if (part === "") continue;
|
|
917
|
-
if (part === "*" || part === `${NS}*`) return true;
|
|
918
|
-
if (part.endsWith("*")) {
|
|
919
|
-
if (ns.startsWith(part.slice(0, -1))) return true;
|
|
920
|
-
} else if (ns === part) {
|
|
921
|
-
return true;
|
|
922
|
-
}
|
|
923
|
-
}
|
|
924
|
-
return false;
|
|
925
|
-
}
|
|
926
|
-
function tag(scope) {
|
|
927
|
-
return `[${NS}:${scope}]`;
|
|
928
|
-
}
|
|
929
|
-
function log(level, scope, message, data) {
|
|
930
|
-
if (ALWAYS_ON.has(level)) {
|
|
931
|
-
console[level](tag(scope), message, data);
|
|
932
|
-
return;
|
|
933
|
-
}
|
|
934
|
-
if (!matches2(switchValue(), `${NS}:${scope}`)) return;
|
|
935
|
-
console.info(tag(scope), message, data);
|
|
936
|
-
}
|
|
937
|
-
var rewindLog = {
|
|
938
|
-
error: (scope, message, data) => log("error", scope, message, data),
|
|
939
|
-
warn: (scope, message, data) => log("warn", scope, message, data),
|
|
940
|
-
info: (scope, message, data) => log("info", scope, message, data),
|
|
941
|
-
debug: (scope, message, data) => log("debug", scope, message, data)
|
|
942
|
-
};
|
|
943
|
-
|
|
944
1115
|
// src/client/portals.tsx
|
|
945
1116
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
946
1117
|
function fillComposerEditable(text) {
|
|
@@ -1072,7 +1243,6 @@ function showHint(text) {
|
|
|
1072
1243
|
}
|
|
1073
1244
|
var COMPOSER_EDITABLE_SELECTOR = "[data-composer-input]";
|
|
1074
1245
|
var USER_SEAT_SELECTOR = '[data-chat-flow-kind="user"][data-chat-anchor-key], [data-chat-flow-kind="steering"][data-chat-anchor-key]';
|
|
1075
|
-
var CHAT_SEAT_SELECTOR = "[data-chat-anchor-key]";
|
|
1076
1246
|
var IGNORED_PORTAL_SCOPE = [
|
|
1077
1247
|
"[data-composer-seat]",
|
|
1078
1248
|
"[data-composer-input]",
|
|
@@ -1080,14 +1250,6 @@ var IGNORED_PORTAL_SCOPE = [
|
|
|
1080
1250
|
"[data-sidebar-chat]",
|
|
1081
1251
|
"[data-plugin-panel]"
|
|
1082
1252
|
].join(", ");
|
|
1083
|
-
var GROUP_SHELL_SELECTOR = '[data-chat-anchor-key^="group:"]';
|
|
1084
|
-
function resolveSeatAnchorSeq(element, chat) {
|
|
1085
|
-
if (chat === void 0) return void 0;
|
|
1086
|
-
const flowKey = element.dataset.chatAnchorKey;
|
|
1087
|
-
const nodeKey = element.dataset.chatNodeKey;
|
|
1088
|
-
const node = (flowKey !== void 0 ? chat.nodes.get(flowKey) : void 0) ?? (nodeKey !== void 0 ? chat.nodes.get(nodeKey) : void 0);
|
|
1089
|
-
return node?.anchorSeq;
|
|
1090
|
-
}
|
|
1091
1253
|
var PENDING_SEAT_SELECTOR = "[data-pending-steering]";
|
|
1092
1254
|
var MESSAGE_ATTACHMENT_SELECTOR = "[data-message-attachments]";
|
|
1093
1255
|
function actionsContainerOf(row) {
|
|
@@ -1097,47 +1259,7 @@ function actionsContainerOf(row) {
|
|
|
1097
1259
|
if (structural instanceof HTMLElement && structural.querySelector("button") !== null) return structural;
|
|
1098
1260
|
return void 0;
|
|
1099
1261
|
}
|
|
1100
|
-
function
|
|
1101
|
-
if (chat === void 0) return;
|
|
1102
|
-
const shells = /* @__PURE__ */ new Map();
|
|
1103
|
-
for (const seat of seats) {
|
|
1104
|
-
if (seat.matches(GROUP_SHELL_SELECTOR)) continue;
|
|
1105
|
-
const anchor = resolveSeatAnchorSeq(seat, chat);
|
|
1106
|
-
const withdrawn = anchor !== void 0 && hiddenSeqs.has(anchor);
|
|
1107
|
-
if (withdrawn) {
|
|
1108
|
-
seat.style.display = "none";
|
|
1109
|
-
seat.dataset.dshRewindHidden = "true";
|
|
1110
|
-
hidden.add(seat);
|
|
1111
|
-
} else if (hidden.has(seat)) {
|
|
1112
|
-
seat.style.display = "";
|
|
1113
|
-
delete seat.dataset.dshRewindHidden;
|
|
1114
|
-
hidden.delete(seat);
|
|
1115
|
-
}
|
|
1116
|
-
if (seat.dataset.chatNodeKey === void 0) continue;
|
|
1117
|
-
const shell = seat.parentElement?.closest(GROUP_SHELL_SELECTOR);
|
|
1118
|
-
if (shell === null || shell === void 0) continue;
|
|
1119
|
-
shells.set(shell, (shells.get(shell) ?? true) && withdrawn);
|
|
1120
|
-
}
|
|
1121
|
-
for (const [shell, allWithdrawn] of shells) {
|
|
1122
|
-
if (allWithdrawn) {
|
|
1123
|
-
shell.style.display = "none";
|
|
1124
|
-
shell.dataset.dshRewindHidden = "true";
|
|
1125
|
-
hidden.add(shell);
|
|
1126
|
-
} else if (hidden.has(shell)) {
|
|
1127
|
-
shell.style.display = "";
|
|
1128
|
-
delete shell.dataset.dshRewindHidden;
|
|
1129
|
-
hidden.delete(shell);
|
|
1130
|
-
}
|
|
1131
|
-
}
|
|
1132
|
-
}
|
|
1133
|
-
function showHiddenSeats(hidden) {
|
|
1134
|
-
for (const element of hidden) {
|
|
1135
|
-
element.style.display = "";
|
|
1136
|
-
delete element.dataset.dshRewindHidden;
|
|
1137
|
-
}
|
|
1138
|
-
hidden.clear();
|
|
1139
|
-
}
|
|
1140
|
-
function collectTargets(chat, hiddenSeqs) {
|
|
1262
|
+
function collectTargets(chat) {
|
|
1141
1263
|
const rows = /* @__PURE__ */ new Map();
|
|
1142
1264
|
for (const element of document.querySelectorAll(USER_SEAT_SELECTOR)) {
|
|
1143
1265
|
const key = element.dataset.chatAnchorKey;
|
|
@@ -1148,7 +1270,6 @@ function collectTargets(chat, hiddenSeqs) {
|
|
|
1148
1270
|
const node = chat.nodes.get(key);
|
|
1149
1271
|
if (node === void 0 || node.kind !== "user" && node.kind !== "steering") continue;
|
|
1150
1272
|
const user = node.data;
|
|
1151
|
-
if (hiddenSeqs.has(node.anchorSeq ?? user.seq)) continue;
|
|
1152
1273
|
const row = rows.get(key);
|
|
1153
1274
|
const actions = actionsContainerOf(row);
|
|
1154
1275
|
if (actions === void 0) continue;
|
|
@@ -1159,9 +1280,9 @@ function collectTargets(chat, hiddenSeqs) {
|
|
|
1159
1280
|
function isRewindInertSession(snapshot) {
|
|
1160
1281
|
return snapshot.subagent !== null && snapshot.subagent !== void 0;
|
|
1161
1282
|
}
|
|
1162
|
-
function collectDurableTargets(snapshot, chat
|
|
1283
|
+
function collectDurableTargets(snapshot, chat) {
|
|
1163
1284
|
if (chat === void 0 || isRewindInertSession(snapshot)) return [];
|
|
1164
|
-
return collectTargets(chat
|
|
1285
|
+
return collectTargets(chat);
|
|
1165
1286
|
}
|
|
1166
1287
|
function inboxOf(session) {
|
|
1167
1288
|
return session.projections.faceOf(INBOX_PROJECTION).getSnapshot();
|
|
@@ -1210,7 +1331,6 @@ function sameTargets(left, right) {
|
|
|
1210
1331
|
}
|
|
1211
1332
|
function RewindPortals({ sessionId, sessionOf, chatOf, isMainViewSession, watchChat, t, subscribeLocale, setComposerText, restoreImages }) {
|
|
1212
1333
|
const [targets, setTargets] = (0, import_react.useState)([]);
|
|
1213
|
-
const hidden = (0, import_react.useRef)(/* @__PURE__ */ new Set());
|
|
1214
1334
|
const [, forceRender] = (0, import_react.useReducer)((count) => count + 1, 0);
|
|
1215
1335
|
(0, import_react.useEffect)(() => subscribeLocale(() => {
|
|
1216
1336
|
forceRender();
|
|
@@ -1230,17 +1350,7 @@ function RewindPortals({ sessionId, sessionOf, chatOf, isMainViewSession, watchC
|
|
|
1230
1350
|
}
|
|
1231
1351
|
const snapshot = session.getSnapshot();
|
|
1232
1352
|
const chat = chatOf(session);
|
|
1233
|
-
const
|
|
1234
|
-
for (const element of hidden.current) if (!element.isConnected) hidden.current.delete(element);
|
|
1235
|
-
if (hiddenSeqs.size > 0 || hidden.current.size > 0) {
|
|
1236
|
-
hideWithdrawnSeats(
|
|
1237
|
-
chat,
|
|
1238
|
-
chat === void 0 ? [] : document.querySelectorAll(CHAT_SEAT_SELECTOR),
|
|
1239
|
-
hiddenSeqs,
|
|
1240
|
-
hidden.current
|
|
1241
|
-
);
|
|
1242
|
-
}
|
|
1243
|
-
const durable = collectDurableTargets(snapshot, chat, hiddenSeqs);
|
|
1353
|
+
const durable = collectDurableTargets(snapshot, chat);
|
|
1244
1354
|
const next = [...durable, ...collectPendingTargets(snapshot, steeringItemsOf(inboxOf(session)?.["next-step"]))];
|
|
1245
1355
|
setTargets((current) => sameTargets(current, next) ? current : next);
|
|
1246
1356
|
};
|
|
@@ -1268,26 +1378,17 @@ function RewindPortals({ sessionId, sessionOf, chatOf, isMainViewSession, watchC
|
|
|
1268
1378
|
const unsubscribeChat = watchChat(sessionId, queueRefresh);
|
|
1269
1379
|
const observer = new MutationObserver((records) => {
|
|
1270
1380
|
for (const record of records) {
|
|
1271
|
-
if (record.type === "attributes") {
|
|
1272
|
-
const target = record.target;
|
|
1273
|
-
if (target instanceof HTMLElement && hidden.current.has(target) && target.style.display !== "none") {
|
|
1274
|
-
queueRefresh();
|
|
1275
|
-
return;
|
|
1276
|
-
}
|
|
1277
|
-
continue;
|
|
1278
|
-
}
|
|
1279
1381
|
const node = record.target instanceof Element ? record.target : record.target.parentElement;
|
|
1280
1382
|
if (node !== null && node.closest(IGNORED_PORTAL_SCOPE) !== null) continue;
|
|
1281
1383
|
queueRefresh();
|
|
1282
1384
|
return;
|
|
1283
1385
|
}
|
|
1284
1386
|
});
|
|
1285
|
-
observer.observe(document.body, { childList: true, subtree: true
|
|
1387
|
+
observer.observe(document.body, { childList: true, subtree: true });
|
|
1286
1388
|
return () => {
|
|
1287
1389
|
active = false;
|
|
1288
1390
|
observer.disconnect();
|
|
1289
1391
|
unsubscribeChat();
|
|
1290
|
-
showHiddenSeats(hidden.current);
|
|
1291
1392
|
unsubscribeInbox?.();
|
|
1292
1393
|
unsubscribeInbox = void 0;
|
|
1293
1394
|
inboxOwner = void 0;
|
|
@@ -1475,9 +1576,9 @@ async function candidatesOverRpc(connection, sessionId) {
|
|
|
1475
1576
|
}
|
|
1476
1577
|
|
|
1477
1578
|
// src/client/build-info.ts
|
|
1478
|
-
var PLUGIN_VERSION = true ? "0.
|
|
1579
|
+
var PLUGIN_VERSION = true ? "0.19.0" : "dev";
|
|
1479
1580
|
var PLUGIN_PACKAGE = "dsh-rewind-plugin";
|
|
1480
|
-
var BUILD_HASH = true ? "
|
|
1581
|
+
var BUILD_HASH = true ? "bccc8c9b" : "dev";
|
|
1481
1582
|
|
|
1482
1583
|
// src/client/locales.ts
|
|
1483
1584
|
var zh = {
|
|
@@ -1707,6 +1808,7 @@ function apply(ctx) {
|
|
|
1707
1808
|
style.dataset.plugin = PLUGIN_PACKAGE;
|
|
1708
1809
|
style.textContent = STYLE;
|
|
1709
1810
|
document.head.appendChild(style);
|
|
1811
|
+
installChatViewVisibility(ctx);
|
|
1710
1812
|
const sessionOf = (sessionId) => ctx.sessions.binding(sessionId)?.session;
|
|
1711
1813
|
const isMainViewSession = (sessionId) => (ctx.sessions.list.getSnapshot().byId[sessionId]?.retainedBy.mainView ?? 0) > 0;
|
|
1712
1814
|
const subscribeLocale = (cb) => ctx.locale.subscribe(cb);
|
|
@@ -43,7 +43,6 @@ export interface CandidateChat {
|
|
|
43
43
|
/** A user/steering row subset; only fields the listing reads are real. */
|
|
44
44
|
export interface CandidateUserNode {
|
|
45
45
|
readonly kind: string;
|
|
46
|
-
readonly anchorSeq?: number;
|
|
47
46
|
readonly data: {
|
|
48
47
|
readonly seq: number;
|
|
49
48
|
readonly time: number;
|
|
@@ -64,15 +63,14 @@ export declare function messagePreviewOf(message: {
|
|
|
64
63
|
export declare function formatCandidateTime(time: number): string;
|
|
65
64
|
/**
|
|
66
65
|
* List the selectable rewind candidates of a session chat snapshot: user and
|
|
67
|
-
* steering rows still on the surface (
|
|
68
|
-
* newest `limit` kept, newest first — the top row is the default highlight,
|
|
66
|
+
* steering rows still on the surface (withdrawn rows are absent from `order`),
|
|
67
|
+
* the newest `limit` kept, newest first — the top row is the default highlight,
|
|
69
68
|
* i.e. the most recent message and the most common rewind target.
|
|
70
69
|
* @param snap - the session chat snapshot.
|
|
71
|
-
* @param hidden - anchor seqs withdrawn by rewinds (from `hiddenSeqsOf`).
|
|
72
70
|
* @param limit - maximum number of candidates to return.
|
|
73
71
|
*/
|
|
74
|
-
export declare function rewindCandidatesOf(snap: CandidateChat,
|
|
75
|
-
/** The candidates of a live chat snapshot
|
|
72
|
+
export declare function rewindCandidatesOf(snap: CandidateChat, limit?: number): RewindCandidate[];
|
|
73
|
+
/** The candidates of a live chat snapshot; withdrawn rows are already excluded from `order`. */
|
|
76
74
|
export declare function rewindCandidatesOfChat(snap: CandidateChat): RewindCandidate[];
|
|
77
75
|
/**
|
|
78
76
|
* Map typed candidates to popupSelect rows. The list comes from the host over
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Pure
|
|
3
|
-
* transcript. Extracted from the client plugin (`src/client/index.ts`) so the
|
|
4
|
-
* multi-rewind cut logic stays unit-testable without a DOM.
|
|
2
|
+
* Pure Chat-snapshot readers and executed-rewind cut derivation.
|
|
5
3
|
*
|
|
6
4
|
* @module dsh-rewind/client/hidden
|
|
7
5
|
*/
|
|
@@ -15,35 +13,39 @@ export declare const ENGINE_COMMAND = "rewind-plugin";
|
|
|
15
13
|
export declare const LEGACY_ENGINE_COMMAND = "rewind";
|
|
16
14
|
/** Engine command names the client recognizes. */
|
|
17
15
|
export declare const ENGINE_COMMANDS: ReadonlySet<string>;
|
|
18
|
-
/** A
|
|
19
|
-
* The harness's view node shape plus the `anchorSeq` the chat snapshot carries
|
|
20
|
-
* (the plugin reads `anchorSeq` off each node; it is not declared on the
|
|
21
|
-
* harness's `ConversationViewNode`). */
|
|
16
|
+
/** A Chat-snapshot view node as the client readers see it. */
|
|
22
17
|
export interface ChatConversationViewNode {
|
|
23
18
|
readonly key: string;
|
|
24
19
|
readonly kind?: string;
|
|
25
20
|
readonly data?: unknown;
|
|
26
21
|
readonly anchorSeq: number;
|
|
27
|
-
/** Owning Turn
|
|
28
|
-
* (see `addWithdrawnTurnChrome`). */
|
|
22
|
+
/** Owning Turn, when the node has one (used by the visibility summary pass). */
|
|
29
23
|
readonly location?: {
|
|
30
24
|
readonly turn?: {
|
|
31
25
|
readonly turn?: unknown;
|
|
32
26
|
readonly start?: unknown;
|
|
33
27
|
};
|
|
34
28
|
};
|
|
29
|
+
/** Chat-only row visibility; absent on older test doubles. */
|
|
30
|
+
readonly visibility?: 'visible' | 'hidden';
|
|
35
31
|
}
|
|
36
|
-
/** Minimal
|
|
32
|
+
/** Minimal Chat-snapshot reader the client helpers need. */
|
|
37
33
|
export interface HiddenChat {
|
|
38
34
|
readonly order: readonly string[];
|
|
39
35
|
readonly nodes: {
|
|
40
36
|
get(key: string): ChatConversationViewNode | undefined;
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
readonly locations?: {
|
|
44
|
-
getTurn(turn: number): readonly string[];
|
|
37
|
+
/** Every materialized node, including hidden ones. Absent on older snapshots. */
|
|
38
|
+
values?(): readonly ChatConversationViewNode[];
|
|
45
39
|
};
|
|
46
40
|
}
|
|
41
|
+
/**
|
|
42
|
+
* Every materialized Chat node, preferring the store's own values (which also
|
|
43
|
+
* includes rows the visibility layer hid) and falling back to the visible
|
|
44
|
+
* order for older snapshots / test doubles.
|
|
45
|
+
* @param chat - the live chat snapshot, when available.
|
|
46
|
+
* @returns all nodes in store insertion order.
|
|
47
|
+
*/
|
|
48
|
+
export declare function chatNodesOf(chat: HiddenChat | undefined): readonly ChatConversationViewNode[];
|
|
47
49
|
/**
|
|
48
50
|
* Reader for one session's live chat snapshot: the chat is served by the
|
|
49
51
|
* `uiConversation` service's named "chat" view (contributed by
|
|
@@ -71,7 +73,7 @@ export declare function resolveChatWatch(resolveView: (sessionId: string) => {
|
|
|
71
73
|
/**
|
|
72
74
|
* Resolve the chat snapshot from the `uiConversation` "chat" view. The view's
|
|
73
75
|
* `getSnapshot()` returns undefined until the named view is registered, which
|
|
74
|
-
* degrades to `undefined` (no targets
|
|
76
|
+
* degrades to `undefined` (no targets — never a crash).
|
|
75
77
|
*/
|
|
76
78
|
export declare function chatSnapshotOf(chatView: {
|
|
77
79
|
getSnapshot(): unknown;
|
|
@@ -131,26 +133,18 @@ export declare function targetSeqOfArgs(args: string | null | undefined): number
|
|
|
131
133
|
* never trigger a fill.
|
|
132
134
|
*/
|
|
133
135
|
export declare function isExecutedRewindCommand(node: CommandNode, seq: number): boolean;
|
|
136
|
+
/** One executed rewind's cut: the target seq through the marker's seq, inclusive. */
|
|
137
|
+
export interface RewindCut {
|
|
138
|
+
/** First withdrawn anchor seq. */
|
|
139
|
+
readonly target: number;
|
|
140
|
+
/** Marker log seq, the last withdrawn anchor seq. */
|
|
141
|
+
readonly marker: number;
|
|
142
|
+
}
|
|
134
143
|
/**
|
|
135
|
-
*
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* Anchor seqs that must be hidden from the rendered transcript so the user sees
|
|
142
|
-
* the conversation as the agent sees it: every legacy probe flow node (it only
|
|
143
|
-
* exists to feed the popover) and every SUCCESSFUL executed `/rewind` command
|
|
144
|
-
* row, plus every message a rewind withdrew — the target, everything after it,
|
|
145
|
-
* and the (unrendered) marker.
|
|
146
|
-
*
|
|
147
|
-
* Each executed rewind cuts ONE span `[target, marker]`, and callers must keep
|
|
148
|
-
* the spans SEPARATE: collapsing them to `[min target, max marker]` would hide a
|
|
149
|
-
* still-on-surface gap of new traffic between an earlier marker and a later
|
|
150
|
-
* target. Endpoints come from the command nodes (`sourceEventSeq` is the
|
|
151
|
-
* marker's log seq; the outcome text carries the target seq).
|
|
152
|
-
*
|
|
153
|
-
* A Turn whose rows were ALL withdrawn also contributes its Turn-process summary
|
|
154
|
-
* row, whose own anchor can precede the target (see `addWithdrawnTurnChrome`).
|
|
144
|
+
* Read every executed rewind cut from a node set. Internal probes never
|
|
145
|
+
* contribute; a success without a marker rewound nothing; a command whose
|
|
146
|
+
* args carry no `@<seq>` target has no recoverable span.
|
|
147
|
+
* @param nodes - every materialized Chat node.
|
|
148
|
+
* @returns one cut per marker-carrying successful engine command, in input order.
|
|
155
149
|
*/
|
|
156
|
-
export declare function
|
|
150
|
+
export declare function rewindCutsOf(nodes: Iterable<ChatConversationViewNode>): RewindCut[];
|
|
@@ -9,8 +9,7 @@
|
|
|
9
9
|
* The command decoration (`ctx.commandUi.decorate`) makes a bare `/rewind` (or
|
|
10
10
|
* its alias `/undo`) open the harness's own popupSelect instead of executing;
|
|
11
11
|
* the parameterized `@<seq>` form stays the execution channel, while the
|
|
12
|
-
* popover's impact and candidate reads go over the host RPC channel.
|
|
13
|
-
* exported contract is specified in `docs/contract/client-contract.md`.
|
|
12
|
+
* popover's impact and candidate reads go over the host RPC channel.
|
|
14
13
|
*
|
|
15
14
|
* @module dsh-rewind/client
|
|
16
15
|
*/
|
|
@@ -46,8 +45,3 @@ export interface ClientContext {
|
|
|
46
45
|
* @param ctx - client root context carrying `slots`, `sessions`, `locale` and `commandUi`.
|
|
47
46
|
*/
|
|
48
47
|
export declare function apply(ctx: ClientContext): void;
|
|
49
|
-
/**
|
|
50
|
-
* Public contract — rewind visibility. Stable, semver-protected; the rest of
|
|
51
|
-
* this module is internal. See `docs/contract/client-contract.md`.
|
|
52
|
-
*/
|
|
53
|
-
export { hiddenSeqsOf, targetSeqOfArgs, type HiddenChat } from './hidden.ts';
|
|
@@ -170,22 +170,6 @@ export declare function writeComposer(text: string, facade: ComposerDraftWriter
|
|
|
170
170
|
* after switching sessions or restarting dsh.
|
|
171
171
|
*/
|
|
172
172
|
export declare function runRewindAndFill(session: SessionFace, seq: number, mode: 'chat' | 'both', isMainViewSession: (sessionId: string) => boolean, chatOf: ChatOf, watchChat: ChatWatch, setComposerText: (sessionId: string, text: string) => boolean, restoreImages?: (sessionId: string, images: readonly MessageImageRef[]) => Promise<void>): Promise<void>;
|
|
173
|
-
/**
|
|
174
|
-
* The snapshot seq a rendered seat stands for.
|
|
175
|
-
*
|
|
176
|
-
* A seat carries two keys: `data-chat-anchor-key` (the FLOW key, which for a
|
|
177
|
-
* step/process part is the composite `["<nodeKey>", "<part>"]`) and
|
|
178
|
-
* `data-chat-node-key` (always the plain node key). Resolving only the flow key
|
|
179
|
-
* made every grouped process row — the "正在分析/已完成分析" step rows, tool
|
|
180
|
-
* groups — unresolvable, so a withdrawn one could never be hidden and stayed on
|
|
181
|
-
* screen after a rewind. This mirrors the official viewport's own identity read
|
|
182
|
-
* (`anchorKey === key || nodeKey === key`).
|
|
183
|
-
*
|
|
184
|
-
* @param element - one rendered seat.
|
|
185
|
-
* @param chat - the live chat snapshot the seat belongs to.
|
|
186
|
-
* @returns the node's `anchorSeq`, or undefined when the seat has no resolvable node.
|
|
187
|
-
*/
|
|
188
|
-
export declare function resolveSeatAnchorSeq(element: HTMLElement, chat: HiddenChat | undefined): number | undefined;
|
|
189
173
|
/**
|
|
190
174
|
* Locate the actions container of a user/steering seat row — the element the
|
|
191
175
|
* ↶ button portals into (the copy/branch IconActions row).
|
|
@@ -203,57 +187,12 @@ export declare function resolveSeatAnchorSeq(element: HTMLElement, chat: HiddenC
|
|
|
203
187
|
* both durable and pending row shapes without a full React portal render.
|
|
204
188
|
*/
|
|
205
189
|
export declare function actionsContainerOf(row: HTMLElement | undefined): HTMLElement | undefined;
|
|
206
|
-
/**
|
|
207
|
-
* Hide every rendered seat a rewind withdrew, plus the step/process GROUP shells
|
|
208
|
-
* those seats empty.
|
|
209
|
-
*
|
|
210
|
-
* A seat's seq comes from `resolveSeatAnchorSeq`: `data-chat-anchor-key` (the
|
|
211
|
-
* FLOW key, composite for a step/process part) with `data-chat-node-key` (always
|
|
212
|
-
* the plain node key) as the fallback. A group shell is different: it carries
|
|
213
|
-
* the composite `group:<json>` anchor, which resolves to no chat node — the
|
|
214
|
-
* group is a view construct, not a message. It is therefore judged by its
|
|
215
|
-
* members, which THIS batch already holds: a shell whose every member seat is
|
|
216
|
-
* withdrawn has no live content left, so it is hidden too. That is what stops a
|
|
217
|
-
* fully withdrawn "analysis completed" / "command executed" group from staying
|
|
218
|
-
* on screen as an expandable empty row. A shell keeps one live member only if
|
|
219
|
-
* that member is genuinely on the surface, in which case the shell stays; a
|
|
220
|
-
* shell the batch carries no member of is left alone.
|
|
221
|
-
*
|
|
222
|
-
* A MEMBER is a real chat node seat — `ChatNodeSeat` is the one shape that
|
|
223
|
-
* writes `data-chat-node-key` beside the anchor key. An anchor-only descendant
|
|
224
|
-
* (the tool call tree's own `call:<callId>` row, or a third-party anchor) is
|
|
225
|
-
* nested decoration: it resolves to no chat node, so it does NOT vote on the
|
|
226
|
-
* shell's emptiness. Counting such a row as "still live" is what left a fully
|
|
227
|
-
* withdrawn "command executed" tool group on screen as an empty expandable row
|
|
228
|
-
* after every member below it had been hidden.
|
|
229
|
-
*
|
|
230
|
-
* Every hidden element carries `data-dsh-rewind-hidden` (see
|
|
231
|
-
* `docs/contract/client-contract.md`).
|
|
232
|
-
*
|
|
233
|
-
* @param chat - the live chat snapshot the seats belong to.
|
|
234
|
-
* @param seats - this refresh's seat batch (every `[data-chat-anchor-key]`).
|
|
235
|
-
* @param hiddenSeqs - anchor seqs withdrawn by the executed rewinds.
|
|
236
|
-
* @param hidden - elements this pass has hidden, so a re-created row is re-judged.
|
|
237
|
-
*/
|
|
238
|
-
export declare function hideWithdrawnSeats(chat: HiddenChat | undefined, seats: Iterable<HTMLElement>, hiddenSeqs: ReadonlySet<number>, hidden: Set<HTMLElement>): void;
|
|
239
|
-
/**
|
|
240
|
-
* Undo every hide the given set recorded, so a live disable (or a session-slot
|
|
241
|
-
* collapse) cannot leave the transcript truncated.
|
|
242
|
-
*
|
|
243
|
-
* The hide is the plugin's OWN mutation of host rows ({@link hideWithdrawnSeats}),
|
|
244
|
-
* and nothing else would ever revert it: a disposed bridge runs no further
|
|
245
|
-
* refresh, so rows it hid would stay hidden with no affordance to bring them
|
|
246
|
-
* back.
|
|
247
|
-
*
|
|
248
|
-
* @param hidden - the elements this bridge currently hides; emptied.
|
|
249
|
-
*/
|
|
250
|
-
export declare function showHiddenSeats(hidden: Set<HTMLElement>): void;
|
|
251
190
|
/**
|
|
252
191
|
* Collect the portal targets of one session: user rows × snapshot nodes.
|
|
253
192
|
* Exported as a test seam — the DOM→targets pairing that drives the ↶ button
|
|
254
193
|
* is otherwise only reachable through a full React portal render.
|
|
255
194
|
*/
|
|
256
|
-
export declare function collectTargets(chat: HiddenChat
|
|
195
|
+
export declare function collectTargets(chat: HiddenChat): readonly PortalTarget[];
|
|
257
196
|
/**
|
|
258
197
|
* The session-kind slice the durable-target gate reads — the session
|
|
259
198
|
* snapshot's `subagent` cell, the Harness's own runtime signal for a
|
|
@@ -285,9 +224,8 @@ export declare function isRewindInertSession(snapshot: SessionKindLike): boolean
|
|
|
285
224
|
* pure, directly testable decision.
|
|
286
225
|
* @param snapshot - the session snapshot carrying the `subagent` cell.
|
|
287
226
|
* @param chat - the session's chat snapshot, or undefined while unavailable.
|
|
288
|
-
* @param hiddenSeqs - anchor seqs withdrawn by previous rewinds.
|
|
289
227
|
*/
|
|
290
|
-
export declare function collectDurableTargets(snapshot: SessionKindLike, chat: HiddenChat | undefined
|
|
228
|
+
export declare function collectDurableTargets(snapshot: SessionKindLike, chat: HiddenChat | undefined): readonly PortalTarget[];
|
|
291
229
|
/**
|
|
292
230
|
* The pending bubble row's compared text and its message-attachment block
|
|
293
231
|
* count, read from a CLONE (the live row is never touched).
|
|
@@ -320,7 +258,7 @@ interface RewindPortalsProps extends RewindBridgeDeps {
|
|
|
320
258
|
* synchronous full-transcript scan inside a commit microtask.
|
|
321
259
|
*
|
|
322
260
|
* The refresh is driven by the three inputs its targets are made of: the chat
|
|
323
|
-
* snapshot
|
|
261
|
+
* snapshot's visible order, the session's `inbox` projection (the pending
|
|
324
262
|
* occurrences — see `syncInboxSubscription`), and the DOM (row/actions mounts,
|
|
325
263
|
* via the scoped observer).
|
|
326
264
|
*/
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Snapshot-level rewind visibility.
|
|
3
|
+
*
|
|
4
|
+
* DSH's Chat builder already supports hidden rows: a node stays in the node
|
|
5
|
+
* store (so the composer can read its text/images) but leaves `order`,
|
|
6
|
+
* `locations`, Turn navigation, and grouping whenever its `visibility` is
|
|
7
|
+
* `'hidden'`. This module wraps the registered `chat` view builder and marks
|
|
8
|
+
* the nodes inside an executed rewind's `[target, marker]` span hidden before
|
|
9
|
+
* the builder sees them.
|
|
10
|
+
*
|
|
11
|
+
* The seam is private: `uiConversation.views` is a first-party registry and
|
|
12
|
+
* `definition.create()` is not an extension point. Its shape is checked and it
|
|
13
|
+
* is restored on unload.
|
|
14
|
+
*
|
|
15
|
+
* @module dsh-rewind/client/visibility
|
|
16
|
+
*/
|
|
17
|
+
import { type ChatConversationViewNode } from './hidden.ts';
|
|
18
|
+
interface BuilderLike {
|
|
19
|
+
readonly empty: unknown;
|
|
20
|
+
replace(input: BuilderInput): unknown;
|
|
21
|
+
apply(input: BuilderApplyInput): unknown;
|
|
22
|
+
groupInput?(): unknown;
|
|
23
|
+
publish?(): void;
|
|
24
|
+
}
|
|
25
|
+
interface BuilderInput {
|
|
26
|
+
readonly nodes: readonly ChatConversationViewNode[];
|
|
27
|
+
readonly timeline: unknown;
|
|
28
|
+
readonly changedTurns?: readonly number[];
|
|
29
|
+
}
|
|
30
|
+
interface BuilderApplyInput {
|
|
31
|
+
readonly upserts: readonly ChatConversationViewNode[];
|
|
32
|
+
readonly timeline: unknown;
|
|
33
|
+
readonly changedTurns?: readonly number[];
|
|
34
|
+
}
|
|
35
|
+
interface SessionsLike {
|
|
36
|
+
readonly list?: {
|
|
37
|
+
getSnapshot(): {
|
|
38
|
+
readonly ids?: readonly string[];
|
|
39
|
+
readonly byId?: Readonly<Record<string, {
|
|
40
|
+
readonly retainedBy?: Readonly<Record<string, number>>;
|
|
41
|
+
}>>;
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/** Context subset the installer needs; structurally satisfied by the client root. */
|
|
46
|
+
export interface VisibilityContext {
|
|
47
|
+
get(name: string): unknown;
|
|
48
|
+
effect?(execute: () => (() => void) | void, label?: string): unknown;
|
|
49
|
+
inject?(names: readonly string[], callback: (scope: VisibilityContext) => void): unknown;
|
|
50
|
+
readonly sessions?: SessionsLike;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Wrap the registered `chat` view builder so rewind spans enter the builder as
|
|
54
|
+
* `visibility: 'hidden'`. Idempotent across registry changes and HMR.
|
|
55
|
+
* @param ctx - client root context.
|
|
56
|
+
*/
|
|
57
|
+
export declare function installChatViewVisibility(ctx: VisibilityContext): void;
|
|
58
|
+
/**
|
|
59
|
+
* Structural wrapper over the registered Chat builder. It forwards every
|
|
60
|
+
* builder method, keeps the original node objects, and only changes
|
|
61
|
+
* `visibility`. A full replacement is used whenever the cut set changes;
|
|
62
|
+
* otherwise it stays on the builder's incremental `apply` path.
|
|
63
|
+
*/
|
|
64
|
+
export declare class RewindVisibilityBuilder implements BuilderLike {
|
|
65
|
+
private readonly inner;
|
|
66
|
+
private originals;
|
|
67
|
+
private commands;
|
|
68
|
+
private byTurn;
|
|
69
|
+
private turnProcess;
|
|
70
|
+
private derived;
|
|
71
|
+
private cuts;
|
|
72
|
+
private cutSignature;
|
|
73
|
+
private lastSignature;
|
|
74
|
+
/** @param inner - the real Chat builder. */
|
|
75
|
+
constructor(inner: BuilderLike);
|
|
76
|
+
get empty(): unknown;
|
|
77
|
+
replace(input: BuilderInput): unknown;
|
|
78
|
+
apply(input: BuilderApplyInput): unknown;
|
|
79
|
+
groupInput(): unknown;
|
|
80
|
+
publish(): void;
|
|
81
|
+
private setAll;
|
|
82
|
+
private setOriginal;
|
|
83
|
+
private unindex;
|
|
84
|
+
private refreshCuts;
|
|
85
|
+
private deriveAll;
|
|
86
|
+
private deriveOne;
|
|
87
|
+
private deriveTurnProcess;
|
|
88
|
+
}
|
|
89
|
+
export {};
|