dsh-rewind-plugin 0.18.1 → 0.19.1

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/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 key of chat.order) {
52
- const node = chat.nodes.get(key);
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 key of chat.order) {
64
- const node = chat.nodes.get(key);
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 coalesceCuts(spans) {
104
- if (spans.length < 2) return spans;
105
- const sorted = [...spans].sort((left, right) => left.start - right.start || left.end - right.end);
106
- const merged = [];
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 !== void 0) {
183
- spans.push({ start: target, end: marker });
184
- }
163
+ if (target === void 0 || target > marker) continue;
164
+ cuts.push({ target, marker });
185
165
  }
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 {
195
- }
196
- hiddenSeqsMemo.set(snap, new Set(hidden));
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 key of chat.order) {
507
- const node = chat.nodes.get(key);
508
- if (node !== void 0 && node.kind === "command") {
509
- const command = node.data;
510
- if (match(command)) found = command;
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 key of chat.order) {
520
- const node = chat.nodes.get(key);
521
- if (node !== void 0 && node.kind === "command") {
522
- const command = node.data;
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 && matches(item, row) ? item.id : null);
1101
+ matched.push(item !== void 0 && matches2(item, row) ? item.id : null);
889
1102
  }
890
1103
  return matched;
891
1104
  }
892
- function matches(item, row) {
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 hideWithdrawnSeats(chat, seats, hiddenSeqs, hidden) {
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, hiddenSeqs) {
1283
+ function collectDurableTargets(snapshot, chat) {
1163
1284
  if (chat === void 0 || isRewindInertSession(snapshot)) return [];
1164
- return collectTargets(chat, hiddenSeqs);
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 hiddenSeqs = chat === void 0 ? /* @__PURE__ */ new Set() : hiddenSeqsOf(chat);
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, attributes: true, attributeFilter: ["style"] });
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,11 +1576,54 @@ async function candidatesOverRpc(connection, sessionId) {
1475
1576
  }
1476
1577
 
1477
1578
  // src/client/build-info.ts
1478
- var PLUGIN_VERSION = true ? "0.18.1" : "dev";
1579
+ var PLUGIN_VERSION = true ? "0.19.1" : "dev";
1479
1580
  var PLUGIN_PACKAGE = "dsh-rewind-plugin";
1480
- var BUILD_HASH = true ? "8a3779b4" : "dev";
1581
+ var BUILD_HASH = true ? "cc2bb5b8" : "dev";
1481
1582
 
1482
1583
  // src/client/locales.ts
1584
+ var en = {
1585
+ "button.aria": "Rewind to this message",
1586
+ "button.title": "Rewind",
1587
+ "button.retract.aria": "Rewind to this pending message",
1588
+ "button.retract.title": "Rewind",
1589
+ "popover.title": "Rewind to this message",
1590
+ "popover.noText": "(no text)",
1591
+ "popover.retract.title": "Rewind to this pending message",
1592
+ "popover.retract.target": "Pending \xB7 {preview}",
1593
+ "popover.retract.hint": "Retracts this pending message and the ones after it, without interrupting the run",
1594
+ "popover.retract.confirm": "Confirm rewind",
1595
+ "popover.chat": "Rewind conversation only",
1596
+ "popover.chat.hint": "Context only; workspace untouched",
1597
+ "popover.both": "Rewind conversation and files",
1598
+ "popover.both.hint": "Context and restore workspace files",
1599
+ "popover.checking": "Checking for file changes\u2026",
1600
+ "popover.noChanges": "No restorable file changes",
1601
+ "popover.backupDisabled": "Snapshots off; the restore may be incomplete",
1602
+ "popover.cancel": "Cancel",
1603
+ "popover.impact.loading": "Fetching impact list\u2026",
1604
+ "popover.impact.failed": "Could not fetch the impact list: {message}",
1605
+ "popover.impact.none": "No tracked file changes after the target; nothing to restore.",
1606
+ "popover.impact.restore": "Restore {path}",
1607
+ "popover.impact.delete": "Delete {path}",
1608
+ "popover.confirm": "Confirm rewind",
1609
+ "popover.back": "Back",
1610
+ "backup.title": "Snapshot backups",
1611
+ "backup.on": "When on, file snapshots are created automatically for file restore.",
1612
+ "backup.off": "When off, no file snapshots are created; file changes made while off cannot be restored.",
1613
+ "cleanup.auto": "Auto cleanup",
1614
+ "cleanup.auto.on": "When enabled, session snapshots idle past the cutoff are cleaned automatically.",
1615
+ "cleanup.auto.off": "When off, snapshots are not cleaned automatically.",
1616
+ "cleanup.maxAge": "Idle time (days)",
1617
+ "cleanup.maxAge.hint": "Snapshot backups of sessions idle longer than this many days are cleaned.",
1618
+ "cleanup.invalid": "Enter a positive integer, or leave blank to use the default",
1619
+ "cleanup.overridden": "Overridden",
1620
+ "cleanup.reset": "Reset to default",
1621
+ "cleanup.save": "Save",
1622
+ "cleanup.saving": "Saving\u2026",
1623
+ "cleanup.saveFailed": "The deployment did not accept these values; they were left for you to correct",
1624
+ "cleanup.readonly": "This deployment stores settings read-only",
1625
+ "cleanup.unavailable": "This plugin is not loaded, so it cannot be configured right now"
1626
+ };
1483
1627
  var zh = {
1484
1628
  "button.aria": "\u56DE\u9000\u5230\u6B64\u6D88\u606F",
1485
1629
  "button.title": "\u56DE\u9000",
@@ -1523,49 +1667,50 @@ var zh = {
1523
1667
  "cleanup.readonly": "\u672C\u90E8\u7F72\u7684\u8BBE\u7F6E\u4E3A\u53EA\u8BFB",
1524
1668
  "cleanup.unavailable": "\u8BE5\u63D2\u4EF6\u5F53\u524D\u672A\u52A0\u8F7D\uFF0C\u6682\u65F6\u65E0\u6CD5\u914D\u7F6E"
1525
1669
  };
1526
- var en = {
1527
- "button.aria": "Rewind to this message",
1528
- "button.title": "Rewind",
1529
- "button.retract.aria": "Rewind to this pending message",
1530
- "button.retract.title": "Rewind",
1531
- "popover.title": "Rewind to this message",
1532
- "popover.noText": "(no text)",
1533
- "popover.retract.title": "Rewind to this pending message",
1534
- "popover.retract.target": "Pending \xB7 {preview}",
1535
- "popover.retract.hint": "Retracts this pending message and the ones after it, without interrupting the run",
1536
- "popover.retract.confirm": "Confirm rewind",
1537
- "popover.chat": "Rewind conversation only",
1538
- "popover.chat.hint": "Context only; workspace untouched",
1539
- "popover.both": "Rewind conversation and files",
1540
- "popover.both.hint": "Context and restore workspace files",
1541
- "popover.checking": "Checking for file changes\u2026",
1542
- "popover.noChanges": "No restorable file changes",
1543
- "popover.backupDisabled": "Snapshots off; the restore may be incomplete",
1544
- "popover.cancel": "Cancel",
1545
- "popover.impact.loading": "Fetching impact list\u2026",
1546
- "popover.impact.failed": "Could not fetch the impact list: {message}",
1547
- "popover.impact.none": "No tracked file changes after the target; nothing to restore.",
1548
- "popover.impact.restore": "Restore {path}",
1549
- "popover.impact.delete": "Delete {path}",
1550
- "popover.confirm": "Confirm rewind",
1551
- "popover.back": "Back",
1552
- "backup.title": "Snapshot backups",
1553
- "backup.on": "When on, file snapshots are created automatically for file restore.",
1554
- "backup.off": "When off, no file snapshots are created; file changes made while off cannot be restored.",
1555
- "cleanup.auto": "Auto cleanup",
1556
- "cleanup.auto.on": "When enabled, session snapshots idle past the cutoff are cleaned automatically.",
1557
- "cleanup.auto.off": "When off, snapshots are not cleaned automatically.",
1558
- "cleanup.maxAge": "Idle time (days)",
1559
- "cleanup.maxAge.hint": "Snapshot backups of sessions idle longer than this many days are cleaned.",
1560
- "cleanup.invalid": "Enter a positive integer, or leave blank to use the default",
1561
- "cleanup.overridden": "Overridden",
1562
- "cleanup.reset": "Reset to default",
1563
- "cleanup.save": "Save",
1564
- "cleanup.saving": "Saving\u2026",
1565
- "cleanup.saveFailed": "The deployment did not accept these values; they were left for you to correct",
1566
- "cleanup.readonly": "This deployment stores settings read-only",
1567
- "cleanup.unavailable": "This plugin is not loaded, so it cannot be configured right now"
1670
+ var ja = {
1671
+ "button.aria": "\u3053\u306E\u30E1\u30C3\u30BB\u30FC\u30B8\u307E\u3067\u5DFB\u304D\u623B\u3059",
1672
+ "button.title": "\u5DFB\u304D\u623B\u3057",
1673
+ "button.retract.aria": "\u3053\u306E\u4FDD\u7559\u4E2D\u306E\u30E1\u30C3\u30BB\u30FC\u30B8\u307E\u3067\u5DFB\u304D\u623B\u3059",
1674
+ "button.retract.title": "\u5DFB\u304D\u623B\u3057",
1675
+ "popover.title": "\u3053\u306E\u30E1\u30C3\u30BB\u30FC\u30B8\u307E\u3067\u5DFB\u304D\u623B\u3059",
1676
+ "popover.noText": "\uFF08\u30C6\u30AD\u30B9\u30C8\u306A\u3057\uFF09",
1677
+ "popover.retract.title": "\u3053\u306E\u4FDD\u7559\u4E2D\u306E\u30E1\u30C3\u30BB\u30FC\u30B8\u307E\u3067\u5DFB\u304D\u623B\u3059",
1678
+ "popover.retract.target": "\u4FDD\u7559\u4E2D \xB7 {preview}",
1679
+ "popover.retract.hint": "\u3053\u306E\u4FDD\u7559\u4E2D\u306E\u30E1\u30C3\u30BB\u30FC\u30B8\u3068\u305D\u308C\u4EE5\u964D\u3092\u64A4\u56DE\u3057\u307E\u3059\u3002\u73FE\u5728\u306E\u751F\u6210\u306F\u4E2D\u65AD\u3057\u307E\u305B\u3093",
1680
+ "popover.retract.confirm": "\u5DFB\u304D\u623B\u3057\u3092\u5B9F\u884C",
1681
+ "popover.chat": "\u4F1A\u8A71\u306E\u307F\u5DFB\u304D\u623B\u3059",
1682
+ "popover.chat.hint": "\u30B3\u30F3\u30C6\u30AD\u30B9\u30C8\u306E\u307F\u5BFE\u8C61\u3067\u3001\u30EF\u30FC\u30AF\u30B9\u30DA\u30FC\u30B9\u306F\u5909\u66F4\u3057\u307E\u305B\u3093",
1683
+ "popover.both": "\u4F1A\u8A71\u3068\u30D5\u30A1\u30A4\u30EB\u3092\u5DFB\u304D\u623B\u3059",
1684
+ "popover.both.hint": "\u30B3\u30F3\u30C6\u30AD\u30B9\u30C8\u3092\u5DFB\u304D\u623B\u3057\u3001\u30EF\u30FC\u30AF\u30B9\u30DA\u30FC\u30B9\u306E\u30D5\u30A1\u30A4\u30EB\u3092\u5FA9\u5143\u3057\u307E\u3059",
1685
+ "popover.checking": "\u30D5\u30A1\u30A4\u30EB\u306E\u5909\u66F4\u3092\u78BA\u8A8D\u4E2D\u2026",
1686
+ "popover.noChanges": "\u5FA9\u5143\u3067\u304D\u308B\u30D5\u30A1\u30A4\u30EB\u306E\u5909\u66F4\u306F\u3042\u308A\u307E\u305B\u3093",
1687
+ "popover.backupDisabled": "\u30B9\u30CA\u30C3\u30D7\u30B7\u30E7\u30C3\u30C8\u304C\u7121\u52B9\u306A\u305F\u3081\u3001\u5FA9\u5143\u304C\u4E0D\u5B8C\u5168\u306B\u306A\u308B\u53EF\u80FD\u6027\u304C\u3042\u308A\u307E\u3059",
1688
+ "popover.cancel": "\u30AD\u30E3\u30F3\u30BB\u30EB",
1689
+ "popover.impact.loading": "\u5F71\u97FF\u30EA\u30B9\u30C8\u3092\u53D6\u5F97\u4E2D\u2026",
1690
+ "popover.impact.failed": "\u5F71\u97FF\u30EA\u30B9\u30C8\u3092\u53D6\u5F97\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\uFF1A{message}",
1691
+ "popover.impact.none": "\u5BFE\u8C61\u3088\u308A\u5F8C\u306B\u8FFD\u8DE1\u3055\u308C\u305F\u30D5\u30A1\u30A4\u30EB\u5909\u66F4\u306F\u3042\u308A\u307E\u305B\u3093\u3002\u5FA9\u5143\u3059\u308B\u3082\u306E\u306F\u3042\u308A\u307E\u305B\u3093\u3002",
1692
+ "popover.impact.restore": "{path} \u3092\u5FA9\u5143",
1693
+ "popover.impact.delete": "{path} \u3092\u524A\u9664",
1694
+ "popover.confirm": "\u5DFB\u304D\u623B\u3057\u3092\u5B9F\u884C",
1695
+ "popover.back": "\u623B\u308B",
1696
+ "backup.title": "\u30B9\u30CA\u30C3\u30D7\u30B7\u30E7\u30C3\u30C8\u306E\u30D0\u30C3\u30AF\u30A2\u30C3\u30D7",
1697
+ "backup.on": "\u6709\u52B9\u306B\u3059\u308B\u3068\u3001\u30D5\u30A1\u30A4\u30EB\u5FA9\u5143\u7528\u306E\u30B9\u30CA\u30C3\u30D7\u30B7\u30E7\u30C3\u30C8\u304C\u81EA\u52D5\u3067\u4F5C\u6210\u3055\u308C\u307E\u3059\u3002",
1698
+ "backup.off": "\u7121\u52B9\u306B\u3059\u308B\u3068\u3001\u30D5\u30A1\u30A4\u30EB\u306E\u30B9\u30CA\u30C3\u30D7\u30B7\u30E7\u30C3\u30C8\u306F\u4F5C\u6210\u3055\u308C\u307E\u305B\u3093\u3002\u7121\u52B9\u306A\u9593\u306E\u30D5\u30A1\u30A4\u30EB\u5909\u66F4\u306F\u5FA9\u5143\u3067\u304D\u307E\u305B\u3093\u3002",
1699
+ "cleanup.auto": "\u81EA\u52D5\u30AF\u30EA\u30FC\u30F3\u30A2\u30C3\u30D7",
1700
+ "cleanup.auto.on": "\u6709\u52B9\u306B\u3059\u308B\u3068\u3001\u3057\u304D\u3044\u5024\u3092\u8D85\u3048\u3066\u30A2\u30A4\u30C9\u30EB\u72B6\u614B\u306E\u30BB\u30C3\u30B7\u30E7\u30F3\u306E\u30B9\u30CA\u30C3\u30D7\u30B7\u30E7\u30C3\u30C8\u304C\u81EA\u52D5\u3067\u524A\u9664\u3055\u308C\u307E\u3059\u3002",
1701
+ "cleanup.auto.off": "\u7121\u52B9\u306B\u3059\u308B\u3068\u3001\u30B9\u30CA\u30C3\u30D7\u30B7\u30E7\u30C3\u30C8\u306F\u81EA\u52D5\u3067\u524A\u9664\u3055\u308C\u307E\u305B\u3093\u3002",
1702
+ "cleanup.maxAge": "\u30A2\u30A4\u30C9\u30EB\u6642\u9593\uFF08\u65E5\uFF09",
1703
+ "cleanup.maxAge.hint": "\u3053\u306E\u65E5\u6570\u3092\u8D85\u3048\u3066\u30A2\u30A4\u30C9\u30EB\u72B6\u614B\u306E\u30BB\u30C3\u30B7\u30E7\u30F3\u306E\u30B9\u30CA\u30C3\u30D7\u30B7\u30E7\u30C3\u30C8\u30D0\u30C3\u30AF\u30A2\u30C3\u30D7\u304C\u524A\u9664\u3055\u308C\u307E\u3059\u3002",
1704
+ "cleanup.invalid": "\u6B63\u306E\u6574\u6570\u3092\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u7A7A\u6B04\u306E\u5834\u5408\u306F\u65E2\u5B9A\u5024\u3092\u4F7F\u3044\u307E\u3059",
1705
+ "cleanup.overridden": "\u4E0A\u66F8\u304D\u6E08\u307F",
1706
+ "cleanup.reset": "\u65E2\u5B9A\u5024\u306B\u623B\u3059",
1707
+ "cleanup.save": "\u4FDD\u5B58",
1708
+ "cleanup.saving": "\u4FDD\u5B58\u4E2D\u2026",
1709
+ "cleanup.saveFailed": "\u3053\u306E\u30C7\u30D7\u30ED\u30A4\u30E1\u30F3\u30C8\u3067\u306F\u5024\u304C\u53D7\u3051\u4ED8\u3051\u3089\u308C\u307E\u305B\u3093\u3067\u3057\u305F\u3002\u4FEE\u6B63\u3067\u304D\u308B\u3088\u3046\u5165\u529B\u306F\u4FDD\u6301\u3055\u308C\u3066\u3044\u307E\u3059",
1710
+ "cleanup.readonly": "\u3053\u306E\u30C7\u30D7\u30ED\u30A4\u30E1\u30F3\u30C8\u3067\u306F\u8A2D\u5B9A\u304C\u8AAD\u307F\u53D6\u308A\u5C02\u7528\u3067\u3059",
1711
+ "cleanup.unavailable": "\u3053\u306E\u30D7\u30E9\u30B0\u30A4\u30F3\u306F\u73FE\u5728\u8AAD\u307F\u8FBC\u307E\u308C\u3066\u3044\u306A\u3044\u305F\u3081\u3001\u8A2D\u5B9A\u3067\u304D\u307E\u305B\u3093"
1568
1712
  };
1713
+ var CLIENT_DICTS = { en, zh, ja };
1569
1714
 
1570
1715
  // src/client/settings-card.tsx
1571
1716
  var import_dsh_client_ui_primitives2 = require("@deepseek-ai/dsh-client-ui-primitives");
@@ -1696,7 +1841,7 @@ var COMPOSER_EDITABLE_SELECTOR2 = "[data-composer-input]";
1696
1841
  function apply(ctx) {
1697
1842
  rewindLog.info("boot", `loaded v${PLUGIN_VERSION} (build ${BUILD_HASH})`);
1698
1843
  ctx.effect(function* () {
1699
- yield ctx.locale.register(NS2, { zh, en });
1844
+ yield ctx.locale.register(NS2, CLIENT_DICTS);
1700
1845
  const t = ctx.locale.bind(NS2);
1701
1846
  const connection = ctx.get("connection");
1702
1847
  registerPreviewCall((sessionId, seq) => previewOverRpc(connection, sessionId, seq));
@@ -1707,6 +1852,7 @@ function apply(ctx) {
1707
1852
  style.dataset.plugin = PLUGIN_PACKAGE;
1708
1853
  style.textContent = STYLE;
1709
1854
  document.head.appendChild(style);
1855
+ installChatViewVisibility(ctx);
1710
1856
  const sessionOf = (sessionId) => ctx.sessions.binding(sessionId)?.session;
1711
1857
  const isMainViewSession = (sessionId) => (ctx.sessions.list.getSnapshot().byId[sessionId]?.retainedBy.mainView ?? 0) > 0;
1712
1858
  const subscribeLocale = (cb) => ctx.locale.subscribe(cb);