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/index.js CHANGED
@@ -4,10 +4,6 @@ import { copyFile as copyFile2, rm as rm2, stat as stat2, unlink } from "node:fs
4
4
  import z from "@deepseek-ai/schemastery";
5
5
 
6
6
  // src/locales.ts
7
- function preferredLocale(preference) {
8
- if (typeof preference !== "string") return "en";
9
- return preference.toLowerCase().split("-")[0] === "zh" ? "zh" : "en";
10
- }
11
7
  var en = {
12
8
  "plan.skip": " not restored: {path} ({reason})",
13
9
  "skipReason.oversized": "over the {limit} backup limit, so the recorded state predates untracked edits",
@@ -100,7 +96,64 @@ var zh = {
100
96
  "cleanup.clearFailed": "\u65E0\u6CD5\u6E05\u9664\u4F1A\u8BDD {sessionId}\uFF1A{detail}\u3002",
101
97
  "cleanup.usage": "\u7528\u6CD5\uFF1A\n /snapshot-auto-cleanup \u67E5\u770B\u72B6\u6001\n /snapshot-auto-cleanup on|off \u5F00\u542F/\u5173\u95ED\u81EA\u52A8\u6E05\u7406\n /snapshot-auto-cleanup max-age <\u5929\u6570> \u8BBE\u7F6E\u5931\u6D3B\u9608\u503C\uFF08\u5929\uFF09\n /snapshot-auto-cleanup run [--apply] \u9884\u6F14\uFF0C\u6216\u52A0 --apply \u6267\u884C\n /snapshot-auto-cleanup run --current [--apply] \u9884\u6F14/\u6E05\u9664\u672C\u4F1A\u8BDD\u5FEB\u7167"
102
98
  };
103
- var HOST_DICTS = { en, zh };
99
+ var ja = {
100
+ "plan.skip": " \u5FA9\u5143\u5BFE\u8C61\u5916\uFF1A{path}\uFF08{reason}\uFF09",
101
+ "skipReason.oversized": "{limit} \u306E\u30D0\u30C3\u30AF\u30A2\u30C3\u30D7\u4E0A\u9650\u3092\u8D85\u3048\u3066\u3044\u308B\u305F\u3081\u3001\u8A18\u9332\u3055\u308C\u305F\u72B6\u614B\u306F\u672A\u8FFD\u8DE1\u306E\u7DE8\u96C6\u3088\u308A\u524D\u306E\u3082\u306E\u3067\u3059",
102
+ "error.invalidTarget": '\u5BFE\u8C61 "{raw}" \u3092\u89E3\u6790\u3067\u304D\u307E\u305B\u3093\uFF08<index> \u307E\u305F\u306F @<seq> \u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\uFF09',
103
+ "sep.list": "\u3001",
104
+ "sep.clause": "\uFF1B",
105
+ "failures.suffix": "\uFF1B{count} \u4EF6\u306E\u30D5\u30A1\u30A4\u30EB\u3092\u5FA9\u5143\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\uFF1A{list}",
106
+ "failures.item": "{path}\uFF08{message}\uFF09",
107
+ "inflight": "\u3053\u306E\u30BB\u30C3\u30B7\u30E7\u30F3\u3067\u306F\u65E2\u306B\u5DFB\u304D\u623B\u3057\u304C\u5B9F\u884C\u4E2D\u3067\u3059\u3002\u304A\u5F85\u3061\u304F\u3060\u3055\u3044\u3002",
108
+ "stopFailed": "\u5B9F\u884C\u4E2D\u306E\u30A8\u30FC\u30B8\u30A7\u30F3\u30C8\u3092\u505C\u6B62\u3067\u304D\u306A\u304B\u3063\u305F\u305F\u3081\u3001\u5DFB\u304D\u623B\u3057\u3092\u30AD\u30E3\u30F3\u30BB\u30EB\u3057\u307E\u3057\u305F\u3002\u3082\u3046\u4E00\u5EA6\u304A\u8A66\u3057\u304F\u3060\u3055\u3044\u3002",
109
+ "cancelled": "\u5DFB\u304D\u623B\u3057\u3092\u30AD\u30E3\u30F3\u30BB\u30EB\u3057\u307E\u3057\u305F\u3002",
110
+ "failed": "\u5DFB\u304D\u623B\u3057\u306B\u5931\u6557\u3057\u307E\u3057\u305F\uFF1A{error}\u3002\u30BB\u30C3\u30B7\u30E7\u30F3\u306F\u5909\u66F4\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002",
111
+ "restore.count": "{count} \u4EF6\u306E\u30D5\u30A1\u30A4\u30EB\u3092\u5FA9\u5143\u3057\u307E\u3057\u305F",
112
+ "delete.count": "{count} \u4EF6\u306E\u30D5\u30A1\u30A4\u30EB\u3092\u524A\u9664\u3057\u307E\u3057\u305F",
113
+ "skip.count": "{count} \u4EF6\u306E\u30D1\u30B9\u3092\u30B9\u30AD\u30C3\u30D7\u3057\u307E\u3057\u305F",
114
+ "noRestorable": "\uFF1B\u5BFE\u8C61\u3088\u308A\u5F8C\u306B\u5FA9\u5143\u3067\u304D\u308B\u66F8\u8FBC\u307F\u7CFB\u306E\u5909\u66F4\u306F\u3042\u308A\u307E\u305B\u3093",
115
+ "backupOff": "\u30B9\u30CA\u30C3\u30D7\u30B7\u30E7\u30C3\u30C8\u306E\u30D0\u30C3\u30AF\u30A2\u30C3\u30D7\u304C\u7121\u52B9\u306A\u305F\u3081\u3001\u5FA9\u5143\u304C\u4E0D\u5B8C\u5168\u306B\u306A\u308B\u53EF\u80FD\u6027\u304C\u3042\u308A\u307E\u3059",
116
+ "storeUnsupported": "\u30D5\u30A1\u30A4\u30EB\u3092\u5FA9\u5143\u3067\u304D\u307E\u305B\u3093\uFF1A\u3053\u308C\u3089\u306E\u30B9\u30CA\u30C3\u30D7\u30B7\u30E7\u30C3\u30C8\u306F\u3001\u3053\u306E\u30D7\u30E9\u30B0\u30A4\u30F3\u304C\u7406\u89E3\u3067\u304D\u308B\u5F62\u5F0F\u3088\u308A\u65B0\u3057\u3044\u4FDD\u5B58\u5F62\u5F0F\uFF08v{version}\uFF09\u3092\u4F7F\u3063\u3066\u3044\u307E\u3059\u3002\u4F55\u3082\u5909\u66F4\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002\u30D7\u30E9\u30B0\u30A4\u30F3\u3092\u66F4\u65B0\u3059\u308B\u304B\u3001\u3053\u306E\u30BB\u30C3\u30B7\u30E7\u30F3\u306E\u30B9\u30CA\u30C3\u30D7\u30B7\u30E7\u30C3\u30C8\u3092\u6D88\u53BB\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
117
+ "success": "seq {targetSeq} \u3068\u305D\u308C\u4EE5\u964D\u3092\u64A4\u56DE\u3057\u307E\u3057\u305F\uFF08\u4F1A\u8A71\u306F\u4EE5\u524D\u306E\u72B6\u614B\u306B\u623B\u308A\u307E\u3057\u305F\uFF09{restore}\u3002",
118
+ "noUserMessages": "\u3053\u306E\u30BB\u30C3\u30B7\u30E7\u30F3\u306B\u306F\u307E\u3060\u5DFB\u304D\u623B\u3057\u3067\u304D\u308B\u30E6\u30FC\u30B6\u30FC\u30E1\u30C3\u30BB\u30FC\u30B8\u304C\u3042\u308A\u307E\u305B\u3093\u3002",
119
+ "command.description": "\u4F1A\u8A71\u3084\u30D5\u30A1\u30A4\u30EB\u3092\u4EE5\u524D\u306E\u6642\u70B9\u307E\u3067\u5DFB\u304D\u623B\u3057\u307E\u3059",
120
+ "command.undoDescription": "/rewind \u306E\u30A8\u30A4\u30EA\u30A2\u30B9",
121
+ "engine.description": "dsh-rewind \u306E\u5185\u90E8\u30B3\u30DE\u30F3\u30C9",
122
+ "engine.rejected": "\u5185\u90E8\u30B3\u30DE\u30F3\u30C9\u3067\u3059\u3002/rewind \u307E\u305F\u306F /undo \u3092\u4F7F\u3063\u3066\u304F\u3060\u3055\u3044\u3002",
123
+ "engine.inputHint": "@<seq> chat|both",
124
+ "cleanup.description": "\u30BB\u30C3\u30B7\u30E7\u30F3\u306E\u30B9\u30CA\u30C3\u30D7\u30B7\u30E7\u30C3\u30C8\u30D0\u30C3\u30AF\u30A2\u30C3\u30D7\u306E\u81EA\u52D5\u30AF\u30EA\u30FC\u30F3\u30A2\u30C3\u30D7\u3092\u7BA1\u7406\u3057\u307E\u3059",
125
+ "cleanup.inputHint": "on | off | max-age <\u65E5\u6570> | run [--apply] [--current]",
126
+ "cleanup.status": "\u81EA\u52D5\u30AF\u30EA\u30FC\u30F3\u30A2\u30C3\u30D7\uFF1A{state}\u3002\u6700\u5927\u4FDD\u6301\u671F\u9593\uFF1A{days} \u65E5\u3002",
127
+ "cleanup.enabled": "\u6709\u52B9",
128
+ "cleanup.disabled": "\u7121\u52B9",
129
+ "cleanup.onOk": "\u81EA\u52D5\u30AF\u30EA\u30FC\u30F3\u30A2\u30C3\u30D7\u3092\u6709\u52B9\u306B\u3057\u307E\u3057\u305F\u3002",
130
+ "cleanup.offOk": "\u81EA\u52D5\u30AF\u30EA\u30FC\u30F3\u30A2\u30C3\u30D7\u3092\u7121\u52B9\u306B\u3057\u307E\u3057\u305F \u2014 \u3059\u3079\u3066\u306E\u30B9\u30CA\u30C3\u30D7\u30B7\u30E7\u30C3\u30C8\u304C\u4FDD\u6301\u3055\u308C\u307E\u3059\u3002",
131
+ "cleanup.maxAgeOk": "\u81EA\u52D5\u30AF\u30EA\u30FC\u30F3\u30A2\u30C3\u30D7\u306E\u6700\u5927\u4FDD\u6301\u671F\u9593\u3092 {days} \u65E5\u306B\u8A2D\u5B9A\u3057\u307E\u3057\u305F\u3002",
132
+ "cleanup.cfgInvalid": '\u30B9\u30CA\u30C3\u30D7\u30B7\u30E7\u30C3\u30C8\u306E\u30AF\u30EA\u30FC\u30F3\u30A2\u30C3\u30D7\u8A2D\u5B9A\u304C\u7121\u52B9\u3067\u3059\uFF1A{detail}\u3002\u4F55\u3082\u5B9F\u884C\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002"on|off|max-age" \u3067\u8A2D\u5B9A\u3092\u30EA\u30BB\u30C3\u30C8\u3057\u3066\u304F\u3060\u3055\u3044\u3002',
133
+ "cleanup.saveFailed": "\u30AF\u30EA\u30FC\u30F3\u30A2\u30C3\u30D7\u8A2D\u5B9A\u3092\u4FDD\u5B58\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\uFF1A{detail}\u3002",
134
+ "cleanup.runDry": "\u30C9\u30E9\u30A4\u30E9\u30F3\uFF1A{deleted} \u4EF6\u306E\u30BB\u30C3\u30B7\u30E7\u30F3\u306E\u30B9\u30CA\u30C3\u30D7\u30B7\u30E7\u30C3\u30C8\u30D0\u30C3\u30AF\u30A2\u30C3\u30D7\u3092\u524A\u9664\u3057\u3001{freed} \u30D0\u30A4\u30C8\u3092\u89E3\u653E\u3057\u307E\u3059\u3002\u5B9F\u969B\u306B\u524A\u9664\u3059\u308B\u306B\u306F --apply \u3092\u4ED8\u3051\u3066\u518D\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
135
+ "cleanup.runApply": "{deleted} \u4EF6\u306E\u30BB\u30C3\u30B7\u30E7\u30F3\u306E\u30B9\u30CA\u30C3\u30D7\u30B7\u30E7\u30C3\u30C8\u30D0\u30C3\u30AF\u30A2\u30C3\u30D7\u3092\u524A\u9664\u3057\u3001{freed} \u30D0\u30A4\u30C8\u3092\u89E3\u653E\u3057\u307E\u3057\u305F\u3002{kept} \u4EF6\u3092\u4FDD\u6301\u3057\u3001\u6B8B\u308A\u306F {remaining} \u30D0\u30A4\u30C8\u3067\u3059\u3002",
136
+ "cleanup.runFailed": "\u30AF\u30EA\u30FC\u30F3\u30A2\u30C3\u30D7\u306B\u5931\u6557\u3057\u307E\u3057\u305F\uFF1A{detail}\u3002",
137
+ "cleanup.skipped": "\uFF08\u30A2\u30AF\u30C6\u30A3\u30D6\u306A\u30BB\u30C3\u30B7\u30E7\u30F3 {skipped} \u4EF6\u3092\u30B9\u30AD\u30C3\u30D7\u3057\u307E\u3057\u305F\u3002\uFF09",
138
+ "cleanup.clearDry": "\u30C9\u30E9\u30A4\u30E9\u30F3\uFF1A\u73FE\u5728\u306E\u30BB\u30C3\u30B7\u30E7\u30F3\u306E\u30B9\u30CA\u30C3\u30D7\u30B7\u30E7\u30C3\u30C8 {entries} \u4EF6\u3092\u6D88\u53BB\u3057\u3001{bytes} \u30D0\u30A4\u30C8\u3092\u89E3\u653E\u3057\u307E\u3059\u3002\u5B9F\u969B\u306B\u524A\u9664\u3059\u308B\u306B\u306F --apply \u3092\u4ED8\u3051\u3066\u518D\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
139
+ "cleanup.clearApply": "\u73FE\u5728\u306E\u30BB\u30C3\u30B7\u30E7\u30F3\u306E\u30B9\u30CA\u30C3\u30D7\u30B7\u30E7\u30C3\u30C8 {entries} \u4EF6\u3092\u6D88\u53BB\u3057\u3001{bytes} \u30D0\u30A4\u30C8\u3092\u89E3\u653E\u3057\u307E\u3057\u305F\u3002\u3053\u306E\u30BB\u30C3\u30B7\u30E7\u30F3\u306F\u73FE\u5728\u306E\u72B6\u614B\u304B\u3089\u65B0\u3057\u304F\u30B9\u30CA\u30C3\u30D7\u30B7\u30E7\u30C3\u30C8\u3092\u8A18\u9332\u3057\u307E\u3059\u3002",
140
+ "cleanup.clearActive": "\u30BB\u30C3\u30B7\u30E7\u30F3 {sessionId} \u3092\u6D88\u53BB\u3067\u304D\u307E\u305B\u3093\uFF1A\u30BB\u30C3\u30B7\u30E7\u30F3\u306F\u307E\u3060\u5B9F\u884C\u4E2D\u3067\u3001\u505C\u6B62\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\u3002\u30A2\u30A4\u30C9\u30EB\u72B6\u614B\u306B\u306A\u3063\u3066\u304B\u3089\u3082\u3046\u4E00\u5EA6\u304A\u8A66\u3057\u304F\u3060\u3055\u3044\u3002",
141
+ "cleanup.clearCancelled": "\u6D88\u53BB\u3092\u30AD\u30E3\u30F3\u30BB\u30EB\u3057\u307E\u3057\u305F\u3002",
142
+ "cleanup.clearFailed": "\u30BB\u30C3\u30B7\u30E7\u30F3 {sessionId} \u3092\u6D88\u53BB\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\uFF1A{detail}\u3002",
143
+ "cleanup.usage": "\u4F7F\u3044\u65B9\uFF1A\n /snapshot-auto-cleanup \u72B6\u614B\u3092\u8868\u793A\n /snapshot-auto-cleanup on|off \u81EA\u52D5\u30AF\u30EA\u30FC\u30F3\u30A2\u30C3\u30D7\u306E\u6709\u52B9/\u7121\u52B9\n /snapshot-auto-cleanup max-age <\u65E5\u6570> \u30A2\u30A4\u30C9\u30EB\u3057\u304D\u3044\u5024\u3092\u8A2D\u5B9A\n /snapshot-auto-cleanup run [--apply] \u30C9\u30E9\u30A4\u30E9\u30F3\u3001\u307E\u305F\u306F --apply \u3067\u5B9F\u884C\n /snapshot-auto-cleanup run --current [--apply] \u30C9\u30E9\u30A4\u30E9\u30F3\uFF0F\u73FE\u5728\u306E\u30BB\u30C3\u30B7\u30E7\u30F3\u306E\u30B9\u30CA\u30C3\u30D7\u30B7\u30E7\u30C3\u30C8\u3092\u6D88\u53BB"
144
+ };
145
+ var HOST_DICTS = { en, zh, ja };
146
+ var DEFAULT_HOST_LOCALE = "en";
147
+ function isHostLocale(tag) {
148
+ return Object.hasOwn(HOST_DICTS, tag);
149
+ }
150
+ function preferredLocale(preference) {
151
+ if (typeof preference !== "string") return DEFAULT_HOST_LOCALE;
152
+ const tag = preference.toLowerCase();
153
+ if (isHostLocale(tag)) return tag;
154
+ const primary = tag.split("-")[0];
155
+ return primary !== void 0 && isHostLocale(primary) ? primary : DEFAULT_HOST_LOCALE;
156
+ }
104
157
  function translate(lang, key, params = {}) {
105
158
  const dict = HOST_DICTS[lang] ?? en;
106
159
  let text = dict[key] ?? key;
@@ -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 (not hidden by a previous rewind), the
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, hidden: ReadonlySet<number>, limit?: number): RewindCandidate[];
75
- /** The candidates of a live chat snapshot, withdrawn rows already excluded. */
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 computation of the chat rows a rewind hides from the rendered
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 chat-snapshot view node as the hiding / composer-refill logic reads it.
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 as the chat snapshot carries it; its `turn` and `start` are read
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 chat snapshot reader the hiding logic needs. */
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
- /** Turn-keyed node index; absent on older snapshots, which the chrome pass tolerates. */
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, no hiding — never a crash).
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
- * True when an engine command node is a legacy candidate-list probe
136
- * (`__candidates`). New candidate lists travel over RPC; this only keeps
137
- * probe rows in historical sessions hidden.
138
- */
139
- export declare function isCandidateCommand(command: CommandNode): boolean;
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 hiddenSeqsOf(snap: HiddenChat): Set<number>;
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. The
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';
@@ -1,6 +1,6 @@
1
1
  /** `rewind` namespace dictionaries for the client plugin. */
2
- /** Simplified Chinese dictionary (the key-set source of truth). */
3
- export declare const zh: {
2
+ /** English dictionary — the canonical key set (the harness fallback language). */
3
+ export declare const en: {
4
4
  'button.aria': string;
5
5
  'button.title': string;
6
6
  'button.retract.aria': string;
@@ -44,54 +44,66 @@ export declare const zh: {
44
44
  'cleanup.unavailable': string;
45
45
  };
46
46
  /** The rewind namespace key union. */
47
- export type RewindKey = keyof typeof zh;
47
+ export type RewindKey = keyof typeof en;
48
48
  declare module '@deepseek-ai/dsh-client-ui-slots' {
49
49
  interface LocaleNamespaceMap {
50
50
  /** The in-place rewind controls' copy. */
51
51
  rewind: RewindKey;
52
52
  }
53
53
  }
54
- /** English dictionary, checked complete against the zh key set. */
55
- export declare const en: {
56
- 'button.aria': string;
57
- 'button.title': string;
58
- 'button.retract.aria': string;
59
- 'button.retract.title': string;
60
- 'popover.title': string;
61
- 'popover.noText': string;
62
- 'popover.retract.title': string;
63
- 'popover.retract.target': string;
64
- 'popover.retract.hint': string;
65
- 'popover.retract.confirm': string;
66
- 'popover.chat': string;
67
- 'popover.chat.hint': string;
68
- 'popover.both': string;
69
- 'popover.both.hint': string;
70
- 'popover.checking': string;
71
- 'popover.noChanges': string;
72
- 'popover.backupDisabled': string;
73
- 'popover.cancel': string;
74
- 'popover.impact.loading': string;
75
- 'popover.impact.failed': string;
76
- 'popover.impact.none': string;
77
- 'popover.impact.restore': string;
78
- 'popover.impact.delete': string;
79
- 'popover.confirm': string;
80
- 'popover.back': string;
81
- 'backup.title': string;
82
- 'backup.on': string;
83
- 'backup.off': string;
84
- 'cleanup.auto': string;
85
- 'cleanup.auto.on': string;
86
- 'cleanup.auto.off': string;
87
- 'cleanup.maxAge': string;
88
- 'cleanup.maxAge.hint': string;
89
- 'cleanup.invalid': string;
90
- 'cleanup.overridden': string;
91
- 'cleanup.reset': string;
92
- 'cleanup.save': string;
93
- 'cleanup.saving': string;
94
- 'cleanup.saveFailed': string;
95
- 'cleanup.readonly': string;
96
- 'cleanup.unavailable': string;
54
+ /** Chinese dictionary, checked complete against the en key set. */
55
+ export declare const zh: Record<RewindKey, string>;
56
+ /** Japanese dictionary, checked complete against the en key set. */
57
+ export declare const ja: Record<RewindKey, string>;
58
+ /**
59
+ * Every dictionary this plugin ships, canonical first. Registration takes this
60
+ * map as a whole, so shipping another language is one dictionary above plus one
61
+ * entry here — no wiring change in `index.ts`.
62
+ */
63
+ export declare const CLIENT_DICTS: {
64
+ en: {
65
+ 'button.aria': string;
66
+ 'button.title': string;
67
+ 'button.retract.aria': string;
68
+ 'button.retract.title': string;
69
+ 'popover.title': string;
70
+ 'popover.noText': string;
71
+ 'popover.retract.title': string;
72
+ 'popover.retract.target': string;
73
+ 'popover.retract.hint': string;
74
+ 'popover.retract.confirm': string;
75
+ 'popover.chat': string;
76
+ 'popover.chat.hint': string;
77
+ 'popover.both': string;
78
+ 'popover.both.hint': string;
79
+ 'popover.checking': string;
80
+ 'popover.noChanges': string;
81
+ 'popover.backupDisabled': string;
82
+ 'popover.cancel': string;
83
+ 'popover.impact.loading': string;
84
+ 'popover.impact.failed': string;
85
+ 'popover.impact.none': string;
86
+ 'popover.impact.restore': string;
87
+ 'popover.impact.delete': string;
88
+ 'popover.confirm': string;
89
+ 'popover.back': string;
90
+ 'backup.title': string;
91
+ 'backup.on': string;
92
+ 'backup.off': string;
93
+ 'cleanup.auto': string;
94
+ 'cleanup.auto.on': string;
95
+ 'cleanup.auto.off': string;
96
+ 'cleanup.maxAge': string;
97
+ 'cleanup.maxAge.hint': string;
98
+ 'cleanup.invalid': string;
99
+ 'cleanup.overridden': string;
100
+ 'cleanup.reset': string;
101
+ 'cleanup.save': string;
102
+ 'cleanup.saving': string;
103
+ 'cleanup.saveFailed': string;
104
+ 'cleanup.readonly': string;
105
+ 'cleanup.unavailable': string;
106
+ };
107
+ zh: Record<"backup.off" | "backup.on" | "backup.title" | "button.aria" | "button.retract.aria" | "button.retract.title" | "button.title" | "cleanup.auto" | "cleanup.auto.off" | "cleanup.auto.on" | "cleanup.invalid" | "cleanup.maxAge" | "cleanup.maxAge.hint" | "cleanup.overridden" | "cleanup.readonly" | "cleanup.reset" | "cleanup.save" | "cleanup.saveFailed" | "cleanup.saving" | "cleanup.unavailable" | "popover.back" | "popover.backupDisabled" | "popover.both" | "popover.both.hint" | "popover.cancel" | "popover.chat" | "popover.chat.hint" | "popover.checking" | "popover.confirm" | "popover.impact.delete" | "popover.impact.failed" | "popover.impact.loading" | "popover.impact.none" | "popover.impact.restore" | "popover.noChanges" | "popover.noText" | "popover.retract.confirm" | "popover.retract.hint" | "popover.retract.target" | "popover.retract.title" | "popover.title", string>;
108
+ ja: Record<"backup.off" | "backup.on" | "backup.title" | "button.aria" | "button.retract.aria" | "button.retract.title" | "button.title" | "cleanup.auto" | "cleanup.auto.off" | "cleanup.auto.on" | "cleanup.invalid" | "cleanup.maxAge" | "cleanup.maxAge.hint" | "cleanup.overridden" | "cleanup.readonly" | "cleanup.reset" | "cleanup.save" | "cleanup.saveFailed" | "cleanup.saving" | "cleanup.unavailable" | "popover.back" | "popover.backupDisabled" | "popover.both" | "popover.both.hint" | "popover.cancel" | "popover.chat" | "popover.chat.hint" | "popover.checking" | "popover.confirm" | "popover.impact.delete" | "popover.impact.failed" | "popover.impact.loading" | "popover.impact.none" | "popover.impact.restore" | "popover.noChanges" | "popover.noText" | "popover.retract.confirm" | "popover.retract.hint" | "popover.retract.target" | "popover.retract.title" | "popover.title", string>;
97
109
  };
@@ -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, hiddenSeqs: ReadonlySet<number>): readonly PortalTarget[];
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, hiddenSeqs: ReadonlySet<number>): readonly PortalTarget[];
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 (`hiddenSeqsOf`), the session's `inbox` projection (the pending
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 {};