dsh-pause-session 0.1.0 → 0.2.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/CHANGELOG.md +51 -20
- package/DEVELOPMENT.md +25 -2
- package/README.md +41 -31
- package/README.zh.md +31 -21
- package/REHEARSAL.md +6 -6
- package/lib/client.js +1125 -1375
- package/lib/client.js.map +1 -1
- package/lib/index.js +1107 -1081
- package/lib/index.js.map +1 -1
- package/lib/typert.host.d.ts +3 -0
- package/lib/typert.host.js +339 -0
- package/lib/typert.remote-client.d.ts +38 -0
- package/lib/typert.remote-client.d.ts.map +1 -0
- package/lib/typert.remote-client.js +244 -0
- package/lib/types/client/control.d.ts +25 -158
- package/lib/types/client/global.d.ts +39 -239
- package/lib/types/client/index.d.ts +6 -5
- package/lib/types/client/locales.d.ts +3 -4
- package/lib/types/client/pill.d.ts +5 -5
- package/lib/types/client/remote.d.ts +77 -0
- package/lib/types/client/view.d.ts +19 -5
- package/lib/types/coldlog.d.ts +1 -1
- package/lib/types/composition.d.ts +35 -61
- package/lib/types/control.d.ts +41 -60
- package/lib/types/global.d.ts +6 -4
- package/lib/types/index.d.ts +2 -2
- package/lib/types/park-state.d.ts +51 -0
- package/lib/types/registry.d.ts +1 -1
- package/lib/types/release-order.d.ts +25 -0
- package/lib/types/remote-adapter.d.ts +22 -0
- package/lib/types/remote.d.ts +158 -0
- package/lib/types/resume-activation.d.ts +36 -0
- package/lib/types/resume-text.d.ts +67 -0
- package/lib/types/resume.d.ts +7 -104
- package/lib/types/shared.d.ts +20 -123
- package/lib/types/snapshot.d.ts +29 -0
- package/lib/types/triage.d.ts +8 -9
- package/package.json +38 -19
- package/lib/types/client/burst.d.ts +0 -253
package/lib/client.js
CHANGED
|
@@ -7,711 +7,40 @@ window.__ModuleLoader__.load({
|
|
|
7
7
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
8
8
|
let react = require("react");
|
|
9
9
|
let _deepseek_ai_dsh_client_store = require("@deepseek-ai/dsh-client-store");
|
|
10
|
-
//#region lib/types/shared.js
|
|
11
|
-
/**
|
|
12
|
-
* The identities both halves of dsh-pause-session name, the status encodings they
|
|
13
|
-
* share, and the one spelling a host log line gives a caught value.
|
|
14
|
-
*
|
|
15
|
-
* The browser bundle may value-import only the shell's baseline module table,
|
|
16
|
-
* so this module imports nothing: the client half reads command names and the
|
|
17
|
-
* decode functions from it directly, and the host half derives the branded
|
|
18
|
-
* identities from the same strings.
|
|
19
|
-
*
|
|
20
|
-
* A third-party plugin's only live channel to the browser is the command
|
|
21
|
-
* registry — it can add neither a `ctx.remote` namespace nor a forwarded event,
|
|
22
|
-
* both of which are compiled into the harness's own API assembly. A command
|
|
23
|
-
* descriptor's only free-form field is its `description`, and a command
|
|
24
|
-
* result's only free-form field is its `text`, so the park state travels as a
|
|
25
|
-
* prefix plus `;`-separated fields on one of those. The text stays
|
|
26
|
-
* human-readable: the same words are what the slash-command menu shows.
|
|
27
|
-
*
|
|
28
|
-
* Every value inside a field is escaped, so a decoded name is the exact name
|
|
29
|
-
* that was encoded. Session ids, tool names, and call ids are the host's own
|
|
30
|
-
* data and may contain the `;`, `,`, `/`, or newline a field is separated by; a
|
|
31
|
-
* folded spelling would decode as a different name — a different row, looked up
|
|
32
|
-
* again.
|
|
33
|
-
* @module dsh-pause-session/shared
|
|
34
|
-
*/
|
|
35
|
-
/** Command that pauses the receiving session before its next model request. */
|
|
36
|
-
const PAUSE_COMMAND = "pause";
|
|
37
|
-
/** Command that resumes the receiving session; registered only while that session is paused. */
|
|
38
|
-
const RESUME_COMMAND = "pause-resume";
|
|
39
|
-
/** Command that resumes every parked session the registry remembers. */
|
|
40
|
-
const RESUME_ALL_COMMAND = "pause-resume-all";
|
|
41
|
-
/** Command that registers and parks every running session tree in one action. */
|
|
42
|
-
const PAUSE_ALL_COMMAND = "pause-all";
|
|
43
|
-
/** Command that reports the whole-app park state; registered globally, always. */
|
|
44
|
-
const STATE_ALL_COMMAND = "pause-state-all";
|
|
45
|
-
/** Command that reports the receiving session's park state; registered only while it is paused. */
|
|
46
|
-
const STATUS_COMMAND = "pause-state";
|
|
47
|
-
/** Command that turns the receiving session's own goal re-arm on or off; registered only while it is paused. */
|
|
48
|
-
const GOAL_COMMAND = "pause-goal";
|
|
49
|
-
/** Command that reports every session that did not stop cleanly. */
|
|
50
|
-
const TRIAGE_COMMAND = "pause-triage";
|
|
51
|
-
/** Command that resumes one session from the decision list exactly as the clean path would. */
|
|
52
|
-
const TRIAGE_CONTINUE_COMMAND = "pause-triage-continue";
|
|
53
|
-
/** Command that resumes one session from the decision list with the unknown-outcome note. */
|
|
54
|
-
const TRIAGE_NOTE_COMMAND = "pause-triage-note";
|
|
55
|
-
/** Command that opens one session from the decision list without spending a request. */
|
|
56
|
-
const TRIAGE_REVIEW_COMMAND = "pause-triage-review";
|
|
57
|
-
/** Command that leaves one session from the decision list exactly as it is. */
|
|
58
|
-
const TRIAGE_LEAVE_COMMAND = "pause-triage-leave";
|
|
59
|
-
/** Command that turns one listed session's goal re-arm on or off. */
|
|
60
|
-
const TRIAGE_GOAL_COMMAND = "pause-triage-goal";
|
|
61
|
-
/**
|
|
62
|
-
* One list field's text when the list is empty. A value that would itself spell
|
|
63
|
-
* this marker is escaped by {@link escapeField}, so an empty list and a list
|
|
64
|
-
* holding the marker stay different texts.
|
|
65
|
-
*/
|
|
66
|
-
const EMPTY_FIELD = "-";
|
|
67
|
-
/**
|
|
68
|
-
* One value previously written by {@link escapeField}.
|
|
69
|
-
* @param text - the encoded value, escapes included.
|
|
70
|
-
* @returns the value the host holds.
|
|
71
|
-
*/
|
|
72
|
-
function unescapeField(text) {
|
|
73
|
-
return text.replace(/\\(.)/g, (_escape, character) => character === "n" ? "\n" : character);
|
|
74
|
-
}
|
|
75
|
-
/**
|
|
76
|
-
* Split one encoded text on the separators that are not escaped, keeping each
|
|
77
|
-
* escape beside the character it protects so {@link unescapeField} can read it.
|
|
78
|
-
* @param text - an encoded text, without its prefix.
|
|
79
|
-
* @param separator - the single character to split on.
|
|
80
|
-
* @returns the parts, in order; always at least one.
|
|
81
|
-
*/
|
|
82
|
-
function splitFields(text, separator) {
|
|
83
|
-
const parts = [];
|
|
84
|
-
let part = "";
|
|
85
|
-
for (let at = 0; at < text.length; at += 1) {
|
|
86
|
-
const character = text[at] ?? "";
|
|
87
|
-
if (character === "\\") {
|
|
88
|
-
part += character + (text[at + 1] ?? "");
|
|
89
|
-
at += 1;
|
|
90
|
-
continue;
|
|
91
|
-
}
|
|
92
|
-
if (character === separator) {
|
|
93
|
-
parts.push(part);
|
|
94
|
-
part = "";
|
|
95
|
-
continue;
|
|
96
|
-
}
|
|
97
|
-
part += character;
|
|
98
|
-
}
|
|
99
|
-
parts.push(part);
|
|
100
|
-
return parts;
|
|
101
|
-
}
|
|
102
|
-
/**
|
|
103
|
-
* One field of an encoded text, by its key.
|
|
104
|
-
* @param fields - the separated fields of one encoded text.
|
|
105
|
-
* @param key - the field's key, without its `:`.
|
|
106
|
-
* @returns the field's value with its escapes still in place, or `undefined`
|
|
107
|
-
* when the text does not carry that field.
|
|
108
|
-
*/
|
|
109
|
-
function fieldOf(fields, key) {
|
|
110
|
-
const prefix = `${key}:`;
|
|
111
|
-
const at = fields.findIndex((field) => field.trim().startsWith(prefix));
|
|
112
|
-
return at < 0 ? void 0 : fields[at]?.trim().slice(prefix.length).trim();
|
|
113
|
-
}
|
|
114
|
-
/** The one word each phase is published as, and the whole decoding vocabulary. */
|
|
115
|
-
const PHASE_WORD = {
|
|
116
|
-
draining: "pausing",
|
|
117
|
-
parking: "saving",
|
|
118
|
-
safe: "paused",
|
|
119
|
-
"flush-failed": "save failed"
|
|
120
|
-
};
|
|
121
|
-
/**
|
|
122
|
-
* The two states of one record's goal re-arm field, in the one spelling both
|
|
123
|
-
* the per-session status and the decision list publish it with.
|
|
124
|
-
*/
|
|
125
|
-
const GOAL_ON = "on";
|
|
126
|
-
const GOAL_OFF = "off";
|
|
127
|
-
/**
|
|
128
|
-
* Decode a status previously written by {@link encodeParkStatus}.
|
|
129
|
-
* @param text - a command descriptor's description.
|
|
130
|
-
* @returns the status, or `undefined` when this text carries none — a foreign
|
|
131
|
-
* command, or a reading taken before the host published anything.
|
|
132
|
-
*/
|
|
133
|
-
function decodeParkStatus(text) {
|
|
134
|
-
if (!text.startsWith("pause: ")) return void 0;
|
|
135
|
-
const [head = "", ...rest] = splitFields(text.slice(7), ";");
|
|
136
|
-
const word = head.trim();
|
|
137
|
-
const phase = Object.keys(PHASE_WORD).find((candidate) => PHASE_WORD[candidate] === word);
|
|
138
|
-
if (phase === void 0) return void 0;
|
|
139
|
-
const goal = fieldOf(rest, "goal");
|
|
140
|
-
return {
|
|
141
|
-
phase,
|
|
142
|
-
safe: phase === "safe",
|
|
143
|
-
runningTools: decodeNames(fieldOf(rest, "tools")),
|
|
144
|
-
...goal === GOAL_ON ? { rearmGoal: true } : goal === GOAL_OFF ? { rearmGoal: false } : {}
|
|
145
|
-
};
|
|
146
|
-
}
|
|
147
|
-
/**
|
|
148
|
-
* Decode one id previously written by {@link encodeId}.
|
|
149
|
-
* @param field - the encoded field, escapes included.
|
|
150
|
-
* @returns the id; `undefined` when the text carries none — an absent field and
|
|
151
|
-
* the empty marker both mean the answer names no session, and a client that
|
|
152
|
-
* read either as one would address a session the host never named.
|
|
153
|
-
*/
|
|
154
|
-
function decodeId(field) {
|
|
155
|
-
const text = field?.trim();
|
|
156
|
-
if (text === void 0 || text.length === 0 || text === EMPTY_FIELD) return void 0;
|
|
157
|
-
return unescapeField(text);
|
|
158
|
-
}
|
|
159
|
-
/** Decode one list field. */
|
|
160
|
-
function decodeNames(field) {
|
|
161
|
-
const text = field?.trim();
|
|
162
|
-
if (text === void 0 || text.length === 0 || text === EMPTY_FIELD) return [];
|
|
163
|
-
return splitFields(text, ",").map((name) => unescapeField(name.trim())).filter((name) => name.length > 0);
|
|
164
|
-
}
|
|
165
|
-
/**
|
|
166
|
-
* Decode a whole-app status previously written by {@link encodeGlobalStatus}.
|
|
167
|
-
* @param text - a command result's text.
|
|
168
|
-
* @returns the status, or `undefined` when this text carries none.
|
|
169
|
-
*/
|
|
170
|
-
function decodeGlobalStatus(text) {
|
|
171
|
-
if (!text.startsWith("pause all: ")) return void 0;
|
|
172
|
-
const [head = "", ...fields] = splitFields(text.slice(11), ";");
|
|
173
|
-
const verdict = head.trim();
|
|
174
|
-
if (verdict !== "idle" && verdict !== "waiting" && verdict !== "safe") return void 0;
|
|
175
|
-
const byKey = /* @__PURE__ */ new Map();
|
|
176
|
-
for (const field of fields) {
|
|
177
|
-
const at = field.indexOf(":");
|
|
178
|
-
if (at < 0) continue;
|
|
179
|
-
byKey.set(field.slice(0, at).trim(), field.slice(at + 1).trim());
|
|
180
|
-
}
|
|
181
|
-
const count = (key) => {
|
|
182
|
-
const value = Number(byKey.get(key));
|
|
183
|
-
return Number.isSafeInteger(value) && value >= 0 ? value : 0;
|
|
184
|
-
};
|
|
185
|
-
const parkedAt = decodeId(byKey.get("parked-at"));
|
|
186
|
-
return {
|
|
187
|
-
verdict,
|
|
188
|
-
registered: count("registered"),
|
|
189
|
-
safe: count("safe"),
|
|
190
|
-
...parkedAt === void 0 ? {} : { parkedAt },
|
|
191
|
-
draining: decodeNames(byKey.get("draining")),
|
|
192
|
-
parking: count("parking"),
|
|
193
|
-
stalled: decodeNames(byKey.get("stalled")),
|
|
194
|
-
unresumable: decodeNames(byKey.get("unresumable")),
|
|
195
|
-
unreadTrees: decodeNames(byKey.get("unread-trees")),
|
|
196
|
-
jobs: decodeNames(byKey.get("jobs")),
|
|
197
|
-
blind: decodeNames(byKey.get("blind")).filter((spot) => spot === "trees" || spot === "jobs")
|
|
198
|
-
};
|
|
199
|
-
}
|
|
200
|
-
/** How one row is spelled in the published list; the wake field's state when the host can hand over the owed request. */
|
|
201
|
-
const WAKE_YES = "yes";
|
|
202
|
-
/** Decode {@link encodeTraces}; an item without a tool name is not a trace. */
|
|
203
|
-
function decodeTraces(field) {
|
|
204
|
-
const text = field?.trim();
|
|
205
|
-
if (text === void 0 || text.length === 0 || text === EMPTY_FIELD) return [];
|
|
206
|
-
return splitFields(text, ",").flatMap((item) => {
|
|
207
|
-
const [tool = "", callId = ""] = splitFields(item.trim(), "/");
|
|
208
|
-
if (tool.length === 0) return [];
|
|
209
|
-
return [{
|
|
210
|
-
tool: unescapeField(tool),
|
|
211
|
-
callId: unescapeField(callId)
|
|
212
|
-
}];
|
|
213
|
-
});
|
|
214
|
-
}
|
|
215
|
-
/**
|
|
216
|
-
* Decode a decision list previously written by {@link encodeTriageList}.
|
|
217
|
-
* @param text - a command result's text.
|
|
218
|
-
* @returns the list, or `undefined` when this text carries none.
|
|
219
|
-
*/
|
|
220
|
-
function decodeTriageList(text) {
|
|
221
|
-
if (!text.startsWith("pause triage: ")) return void 0;
|
|
222
|
-
const [head = "", ...rest] = splitFields(text.slice(14), ";");
|
|
223
|
-
if (!Number.isSafeInteger(Number(head.trim())) || Number(head.trim()) < 0) return void 0;
|
|
224
|
-
const rows = [];
|
|
225
|
-
let fields = [];
|
|
226
|
-
const flush = () => {
|
|
227
|
-
if (fields.length === 0) return;
|
|
228
|
-
const sessionId = fieldOf(fields, "row");
|
|
229
|
-
const phase = fieldOf(fields, "phase");
|
|
230
|
-
if (sessionId === void 0 || phase === void 0 || phase === "safe") return;
|
|
231
|
-
const stopped = fieldOf(fields, "stopped");
|
|
232
|
-
const reading = stopped === "unreadable" ? "unreadable" : "read";
|
|
233
|
-
const position = reading === "unreadable" || stopped === void 0 || stopped === EMPTY_FIELD ? null : /^(.*?)(?:\/turn (\d+)(?: step (\d+))?)?$/.exec(stopped);
|
|
234
|
-
rows.push({
|
|
235
|
-
sessionId: unescapeField(sessionId),
|
|
236
|
-
phase,
|
|
237
|
-
updatedAt: 0,
|
|
238
|
-
reading,
|
|
239
|
-
...position === null ? {} : {
|
|
240
|
-
lastEvent: unescapeField(position[1] ?? ""),
|
|
241
|
-
...position[2] === void 0 ? {} : { turn: Number(position[2]) },
|
|
242
|
-
...position[3] === void 0 ? {} : { step: Number(position[3]) }
|
|
243
|
-
},
|
|
244
|
-
traces: reading === "unreadable" ? [] : decodeTraces(fieldOf(fields, "traces")),
|
|
245
|
-
owed: fieldOf(fields, "owed") === WAKE_YES,
|
|
246
|
-
wakeable: fieldOf(fields, "wake") === WAKE_YES,
|
|
247
|
-
rearmGoal: fieldOf(fields, "goal") !== GOAL_OFF
|
|
248
|
-
});
|
|
249
|
-
fields = [];
|
|
250
|
-
};
|
|
251
|
-
for (const field of rest) {
|
|
252
|
-
if (field.trim().startsWith("row:")) flush();
|
|
253
|
-
fields.push(field);
|
|
254
|
-
}
|
|
255
|
-
flush();
|
|
256
|
-
return { rows };
|
|
257
|
-
}
|
|
258
|
-
//#endregion
|
|
259
|
-
//#region lib/types/client/burst.js
|
|
260
|
-
/**
|
|
261
|
-
* The re-read burst both browser controls share: a handful of reads after
|
|
262
|
-
* something moved, and silence once it settles.
|
|
263
|
-
*
|
|
264
|
-
* What a control reads is published through the host's command channel, and
|
|
265
|
-
* every read is two durable `command/run|done` records in the address session's
|
|
266
|
-
* log plus an aggregate read on the host. Reading is therefore something the
|
|
267
|
-
* host announces and the user asks for, never a heartbeat:
|
|
268
|
-
*
|
|
269
|
-
* - the host announces a transition by registering or unregistering the paused
|
|
270
|
-
* session's marker commands, and — for a phase change, which no registration
|
|
271
|
-
* of its own would show — by registering that session's status verb again.
|
|
272
|
-
* Each of those emits `commands/change`, and that event, plus the user's own
|
|
273
|
-
* press or decision, is what starts a burst;
|
|
274
|
-
* - one transition announces itself several times: a park registers two or three
|
|
275
|
-
* marker commands per session and a phase change re-registers one, and the host
|
|
276
|
-
* emits one `commands/change` per step. The burst therefore waits out a short
|
|
277
|
-
* `windowMs` before its first read and any trigger inside that window
|
|
278
|
-
* postpones it, so the announcements of one transition start one burst rather
|
|
279
|
-
* than one each;
|
|
280
|
-
* - a change that arrives within `ACTION_ABSORB_MS` of the user's own action is
|
|
281
|
-
* that action's own consequence, not news: the action already read the host,
|
|
282
|
-
* so the answer is on screen, and the unregistrations it caused — one event
|
|
283
|
-
* per marker command, measured seconds apart, which the window above cannot
|
|
284
|
-
* merge — would only be read back. Such a change is absorbed, and each one
|
|
285
|
-
* absorbed pushes the window out, because a storm that keeps arriving is
|
|
286
|
-
* still the same storm. The window always closes: the first change after
|
|
287
|
-
* `ACTION_ABSORB_MS` of quiet is news again, and a user action reads on its
|
|
288
|
-
* own whatever the window says;
|
|
289
|
-
* - an absorbed change is deferred rather than lost: a control whose action left
|
|
290
|
-
* a phase still moving asks for one look at the moment the window closes
|
|
291
|
-
* ({@link ReadBurst.recheckAfterEcho}), which is the read that finds a
|
|
292
|
-
* transition the window hid — the park can reach its final phase inside that
|
|
293
|
-
* window and never announce anything again. One look per action, and only
|
|
294
|
-
* while the state it left is still moving;
|
|
295
|
-
* - a burst is a fixed short list of waits, so at most that many reads answer
|
|
296
|
-
* one transition, however long the page stays open;
|
|
297
|
-
* - a read that finds the verdict settled ends the burst at once, and the last
|
|
298
|
-
* reading stays on screen — "the park is settled" is not a state change, so
|
|
299
|
-
* it must not be re-asked;
|
|
300
|
-
* - a read that finds a phase still moving ends it just as fast. Every phase
|
|
301
|
-
* transition is announced at its source, the flush that resolves `parking`
|
|
302
|
-
* into `safe` included, and that announcement is the very event that starts
|
|
303
|
-
* the next burst — so watching a moving phase from here spends records on a
|
|
304
|
-
* transition that will speak for itself;
|
|
305
|
-
* - what the burst still owes after a read is delivery: the read one
|
|
306
|
-
* announcement bought, and one more when a change lands while a pass is in
|
|
307
|
-
* flight, which that pass cannot have answered. The bound is a bound, not a
|
|
308
|
-
* heartbeat: a phase that never settles is left on screen unread until
|
|
309
|
-
* something else moves;
|
|
310
|
-
* - the bound is a trade, taken knowingly: a reading left on screen when the
|
|
311
|
-
* burst runs out is the host's own last answer, and the next trigger — a
|
|
312
|
-
* forwarded change, a press, or the shell handing a control its session again
|
|
313
|
-
* — reads the truth again. A reading nobody answered is never rendered as a
|
|
314
|
-
* confident state. A longer watch is a longer list of waits, at a linear price
|
|
315
|
-
* in the address session's log;
|
|
316
|
-
* - while the page is hidden no read is armed at all, and the wait a burst
|
|
317
|
-
* still owed is armed again when the page comes back: hidden time is neither
|
|
318
|
-
* charged to the burst nor lost from it.
|
|
319
|
-
*
|
|
320
|
-
* A burst is not restarted by a trigger that lands while one is running: every
|
|
321
|
-
* read it still owes happens after that trigger, so absorbing it loses nothing
|
|
322
|
-
* and a storm of registry changes costs one burst rather than one each.
|
|
323
|
-
* @module dsh-pause-session/client/burst
|
|
324
|
-
*/
|
|
325
|
-
/** The policy every control uses unless a caller says otherwise. */
|
|
326
|
-
const DEFAULT_BURST_POLICY = {
|
|
327
|
-
windowMs: 400,
|
|
328
|
-
waitsMs: [
|
|
329
|
-
400,
|
|
330
|
-
1e3,
|
|
331
|
-
2400,
|
|
332
|
-
4800
|
|
333
|
-
],
|
|
334
|
-
visible: defaultVisibility
|
|
335
|
-
};
|
|
336
|
-
/**
|
|
337
|
-
* How long after a client action a registry change is that action's own echo.
|
|
338
|
-
*
|
|
339
|
-
* A press, a release, a decision, a goal switch and an opened panel each read
|
|
340
|
-
* the host as they run, so their answer is already on screen when the host
|
|
341
|
-
* announces what they did — one `commands/change` per marker command, measured
|
|
342
|
-
* 0.44 s to 2.04 s apart, a cadence the coalescing window cannot merge. Reading
|
|
343
|
-
* those back is paying for an answer the user already has.
|
|
344
|
-
*
|
|
345
|
-
* The window is what makes `commands/change` mean "something moved that this
|
|
346
|
-
* browser did not do". It is refreshed by every change it absorbs, so a storm
|
|
347
|
-
* that keeps arriving stays absorbed rather than leaking one read per change,
|
|
348
|
-
* and it ends for good `ACTION_ABSORB_MS` after the last change: a change after
|
|
349
|
-
* that is news, and so is the next user action, which reads on its own.
|
|
350
|
-
*
|
|
351
|
-
* The margin over the measured 2.04 s gap is deliberate: a transition's own
|
|
352
|
-
* storm has to fit inside the window for the whole storm to stay absorbed.
|
|
353
|
-
*/
|
|
354
|
-
const ACTION_ABSORB_MS = 2500;
|
|
355
|
-
/**
|
|
356
|
-
* The waits one burst uses when its first wait is `first`: four reads spread
|
|
357
|
-
* over roughly ten times that. Long enough that a read a hidden page or an
|
|
358
|
-
* in-flight pass deferred is still taken, short enough that a state which never
|
|
359
|
-
* settles costs a handful of log records rather than a night of them.
|
|
360
|
-
* @param first - milliseconds before the burst's first read, after its window; `<= 0` disables bursting.
|
|
361
|
-
* @returns the waits, in order; empty when bursting is disabled.
|
|
362
|
-
*/
|
|
363
|
-
function burstWaits(first) {
|
|
364
|
-
return first > 0 ? [
|
|
365
|
-
first,
|
|
366
|
-
first * 2.5,
|
|
367
|
-
first * 6,
|
|
368
|
-
first * 12
|
|
369
|
-
] : [];
|
|
370
|
-
}
|
|
371
|
-
/**
|
|
372
|
-
* Whether the browser reports the page as on screen. A shell without a
|
|
373
|
-
* `document` (a test harness, a non-browser host) counts as visible: a control
|
|
374
|
-
* that could never read would be a worse lie than one that reads.
|
|
375
|
-
* @returns whether the page is visible now.
|
|
376
|
-
*/
|
|
377
|
-
function defaultVisibility() {
|
|
378
|
-
return (typeof document === "undefined" ? void 0 : document.visibilityState) !== "hidden";
|
|
379
|
-
}
|
|
380
|
-
/** The real clock: `setTimeout` clearing itself. */
|
|
381
|
-
const SYSTEM_CLOCK = {
|
|
382
|
-
set: (callback, ms) => setTimeout(callback, ms),
|
|
383
|
-
clear: (timer) => {
|
|
384
|
-
clearTimeout(timer);
|
|
385
|
-
}
|
|
386
|
-
};
|
|
387
|
-
/**
|
|
388
|
-
* One control's re-read burst. Every method is safe to call in any order and as
|
|
389
|
-
* often as the caller likes: a trigger starts at most one burst, and one burst
|
|
390
|
-
* never arms more than one read at a time.
|
|
391
|
-
*/
|
|
392
|
-
var ReadBurst = class {
|
|
393
|
-
onRead;
|
|
394
|
-
policy;
|
|
395
|
-
clock;
|
|
396
|
-
/** The armed wait, when one is counting down. */
|
|
397
|
-
timer;
|
|
398
|
-
/** Whether the burst is waiting out its collection window rather than a wait between reads. */
|
|
399
|
-
window = false;
|
|
400
|
-
/** Waits this burst has not spent yet, front first. */
|
|
401
|
-
left = [];
|
|
402
|
-
/** Whether a burst is in progress: it may be collecting, waiting, reading, or reading again. */
|
|
403
|
-
running = false;
|
|
404
|
-
/** The absorption window a client action opened, while one is open. */
|
|
405
|
-
absorbed;
|
|
406
|
-
/** Whether a control asked for the one look owed when the echo window closes. */
|
|
407
|
-
recheck = false;
|
|
408
|
-
/**
|
|
409
|
-
* @param onRead - one more read. Called only for a read the burst owes and the page can show.
|
|
410
|
-
* @param policy - the collection window, the waits, and the visibility source.
|
|
411
|
-
* @param clock - the timer implementation.
|
|
412
|
-
*/
|
|
413
|
-
constructor(onRead, policy, clock = SYSTEM_CLOCK) {
|
|
414
|
-
this.onRead = onRead;
|
|
415
|
-
this.policy = policy;
|
|
416
|
-
this.clock = clock;
|
|
417
|
-
}
|
|
418
|
-
/** Whether this control bursts at all. */
|
|
419
|
-
get enabled() {
|
|
420
|
-
return this.policy.waitsMs.length > 0;
|
|
421
|
-
}
|
|
422
|
-
/** Whether one more read is already counting down. */
|
|
423
|
-
get armed() {
|
|
424
|
-
return this.timer !== void 0;
|
|
425
|
-
}
|
|
426
|
-
/** Whether a burst is in progress, with or without a read counting down. */
|
|
427
|
-
get live() {
|
|
428
|
-
return this.running;
|
|
429
|
-
}
|
|
430
|
-
/** Whether a change right now is a client action's own echo. */
|
|
431
|
-
get absorbing() {
|
|
432
|
-
return this.absorbed !== void 0;
|
|
433
|
-
}
|
|
434
|
-
/**
|
|
435
|
-
* The user acted on this control and the action reads: a press, a release, a
|
|
436
|
-
* decision, a goal switch, an opened panel. Its read is the answer, and the
|
|
437
|
-
* registry changes it sets off announce themselves for a while afterwards, so
|
|
438
|
-
* until the window closes a trigger is that echo rather than news.
|
|
439
|
-
*
|
|
440
|
-
* Called as the action is taken, before its read lands, because the echo can
|
|
441
|
-
* arrive while the action is still running.
|
|
442
|
-
*/
|
|
443
|
-
absorb() {
|
|
444
|
-
if (!this.enabled) return;
|
|
445
|
-
this.armAbsorb();
|
|
446
|
-
}
|
|
447
|
-
/**
|
|
448
|
-
* Ask for one read at the moment the echo window closes, because the action's
|
|
449
|
-
* own read left a state that is still moving.
|
|
450
|
-
*
|
|
451
|
-
* The echo window swallows a transition announced inside it — the forwarded
|
|
452
|
-
* event carries no payload, so nothing distinguishes a marker registration
|
|
453
|
-
* from a phase change — and after the window no change is guaranteed to come:
|
|
454
|
-
* the transition that was hidden may be the last one this park makes. The
|
|
455
|
-
* look owed here is the read that finds it, one per action, and it is not a
|
|
456
|
-
* watch: once the window has closed, every further change is news and buys its
|
|
457
|
-
* own read. A control whose action left a settled state owes nothing and asks
|
|
458
|
-
* for nothing.
|
|
459
|
-
*/
|
|
460
|
-
recheckAfterEcho() {
|
|
461
|
-
if (!this.enabled) return;
|
|
462
|
-
this.recheck = true;
|
|
463
|
-
}
|
|
464
|
-
/**
|
|
465
|
-
* Something moved — a registry change. The burst first waits out its
|
|
466
|
-
* collection window, and every trigger that lands inside it restarts that
|
|
467
|
-
* window: the two or three registrations of one park start one burst rather
|
|
468
|
-
* than one each. A burst already past its window takes this as covered —
|
|
469
|
-
* every read it still owes happens after the change.
|
|
470
|
-
*
|
|
471
|
-
* @returns whether the change is news rather than a client action's own echo.
|
|
472
|
-
* The echo announces what the action's read already answered, so only a news
|
|
473
|
-
* trigger is a change a control still owes a read for; a change that lands
|
|
474
|
-
* while a burst is under way is covered by the read that burst already owes.
|
|
475
|
-
* `false` also for a disabled burst, which reads nothing at all.
|
|
476
|
-
*/
|
|
477
|
-
trigger() {
|
|
478
|
-
if (!this.enabled) return false;
|
|
479
|
-
if (this.absorbing) {
|
|
480
|
-
this.armAbsorb();
|
|
481
|
-
return false;
|
|
482
|
-
}
|
|
483
|
-
if (!this.running) {
|
|
484
|
-
this.running = true;
|
|
485
|
-
this.left = this.policy.waitsMs;
|
|
486
|
-
this.armWindow();
|
|
487
|
-
return true;
|
|
488
|
-
}
|
|
489
|
-
if (this.window) this.armWindow();
|
|
490
|
-
return true;
|
|
491
|
-
}
|
|
492
|
-
/**
|
|
493
|
-
* The control knows it still owes a read: a change that is news has arrived,
|
|
494
|
-
* or a reading just revealed something only a read can answer. A call outside
|
|
495
|
-
* any burst starts one. Absorption never applies here — this is not a change
|
|
496
|
-
* some action caused, it is a read the control already knows it needs.
|
|
497
|
-
*/
|
|
498
|
-
next() {
|
|
499
|
-
if (this.armed || this.window) return;
|
|
500
|
-
if (!this.running) {
|
|
501
|
-
this.running = true;
|
|
502
|
-
this.left = this.policy.waitsMs;
|
|
503
|
-
}
|
|
504
|
-
this.owe();
|
|
505
|
-
}
|
|
506
|
-
/**
|
|
507
|
-
* Nothing is moving: end the burst and forget it. The last reading stays
|
|
508
|
-
* published, and the next trigger starts a fresh burst. Two things outlive
|
|
509
|
-
* this: an action's absorption window, which is about what the action caused
|
|
510
|
-
* rather than about the state, and the one look it may still owe — see
|
|
511
|
-
* {@link recheckAfterEcho}.
|
|
512
|
-
*/
|
|
513
|
-
stop() {
|
|
514
|
-
this.running = false;
|
|
515
|
-
this.left = [];
|
|
516
|
-
this.window = false;
|
|
517
|
-
this.cancel();
|
|
518
|
-
}
|
|
519
|
-
/** Drop the armed wait. A burst that is still owed its read survives it; see {@link checkVisibility}. */
|
|
520
|
-
cancel() {
|
|
521
|
-
if (this.timer === void 0) return;
|
|
522
|
-
this.clock.clear(this.timer);
|
|
523
|
-
this.timer = void 0;
|
|
524
|
-
}
|
|
525
|
-
/**
|
|
526
|
-
* Re-evaluate one page-visibility change. Going off screen drops the armed
|
|
527
|
-
* wait — a hidden page is not being read — and coming back arms it again, so
|
|
528
|
-
* the burst resumes where it stopped instead of being spent while nobody was
|
|
529
|
-
* looking. A burst still collecting resumes its window, restarted from the
|
|
530
|
-
* moment the page came back: a trigger that arrived while it was hidden still
|
|
531
|
-
* postpones the read.
|
|
532
|
-
*/
|
|
533
|
-
checkVisibility() {
|
|
534
|
-
if (this.visibleNow()) {
|
|
535
|
-
this.arm();
|
|
536
|
-
return;
|
|
537
|
-
}
|
|
538
|
-
this.cancel();
|
|
539
|
-
}
|
|
540
|
-
/** Spend the next wait of the burst, or end it when the bound is reached. */
|
|
541
|
-
owe() {
|
|
542
|
-
const wait = this.left[0];
|
|
543
|
-
if (wait === void 0) {
|
|
544
|
-
this.running = false;
|
|
545
|
-
return;
|
|
546
|
-
}
|
|
547
|
-
this.armWait(wait);
|
|
548
|
-
}
|
|
549
|
-
/**
|
|
550
|
-
* (Re)start the collection window. While the page is hidden nothing is armed
|
|
551
|
-
* — hidden time is not charged to the burst — and a trigger that lands then
|
|
552
|
-
* is still collected, because coming back starts a whole window.
|
|
553
|
-
*/
|
|
554
|
-
armWindow() {
|
|
555
|
-
const wait = Math.max(this.policy.windowMs, 0);
|
|
556
|
-
if (!this.visibleNow()) {
|
|
557
|
-
this.window = true;
|
|
558
|
-
return;
|
|
559
|
-
}
|
|
560
|
-
this.cancel();
|
|
561
|
-
this.window = true;
|
|
562
|
-
this.timer = this.clock.set(() => {
|
|
563
|
-
this.timer = void 0;
|
|
564
|
-
this.window = false;
|
|
565
|
-
this.owe();
|
|
566
|
-
}, wait);
|
|
567
|
-
}
|
|
568
|
-
/** Put back whatever the burst still owed: its window, or the wait after a read. */
|
|
569
|
-
arm() {
|
|
570
|
-
if (this.window) {
|
|
571
|
-
this.armWindow();
|
|
572
|
-
return;
|
|
573
|
-
}
|
|
574
|
-
const wait = this.left[0];
|
|
575
|
-
if (wait !== void 0) this.armWait(wait);
|
|
576
|
-
}
|
|
577
|
-
/**
|
|
578
|
-
* (Re)start the absorption window a client action opened. A hidden page keeps
|
|
579
|
-
* absorbing: this window is about what the action caused, not about what the
|
|
580
|
-
* page is doing. When the window closes, the one look an unsettled action owes
|
|
581
|
-
* is taken; the window is restarted by every change it absorbs, so that look
|
|
582
|
-
* waits for the echo to actually stop.
|
|
583
|
-
*/
|
|
584
|
-
armAbsorb() {
|
|
585
|
-
this.cancelAbsorb();
|
|
586
|
-
this.absorbed = this.clock.set(() => {
|
|
587
|
-
this.absorbed = void 0;
|
|
588
|
-
if (!this.recheck) return;
|
|
589
|
-
this.recheck = false;
|
|
590
|
-
this.next();
|
|
591
|
-
}, Math.max(ACTION_ABSORB_MS, 0));
|
|
592
|
-
}
|
|
593
|
-
/** Close the absorption window: changes are news again. */
|
|
594
|
-
cancelAbsorb() {
|
|
595
|
-
if (this.absorbed === void 0) return;
|
|
596
|
-
this.clock.clear(this.absorbed);
|
|
597
|
-
this.absorbed = void 0;
|
|
598
|
-
}
|
|
599
|
-
/**
|
|
600
|
-
* Arm one wait, spending it when it fires. The wait stays in `left` until
|
|
601
|
-
* then, so a page hidden mid-wait takes the whole wait again: hidden time is
|
|
602
|
-
* neither charged to the burst nor lost from it.
|
|
603
|
-
*/
|
|
604
|
-
armWait(wait) {
|
|
605
|
-
if (this.timer !== void 0 || !this.visibleNow()) return;
|
|
606
|
-
this.timer = this.clock.set(() => {
|
|
607
|
-
this.timer = void 0;
|
|
608
|
-
this.left = this.left.slice(1);
|
|
609
|
-
this.onRead();
|
|
610
|
-
}, Math.max(wait, 0));
|
|
611
|
-
}
|
|
612
|
-
/** Whether the page counts as on screen now, with an absent `document` read as on screen. */
|
|
613
|
-
visibleNow() {
|
|
614
|
-
try {
|
|
615
|
-
return this.policy.visible();
|
|
616
|
-
} catch {
|
|
617
|
-
return true;
|
|
618
|
-
}
|
|
619
|
-
}
|
|
620
|
-
};
|
|
621
|
-
//#endregion
|
|
622
10
|
//#region lib/types/client/control.js
|
|
623
11
|
/**
|
|
624
12
|
* Browser-side reading and driving of the host's park state for one session.
|
|
625
13
|
*
|
|
626
|
-
* The
|
|
627
|
-
*
|
|
628
|
-
*
|
|
629
|
-
*
|
|
630
|
-
*
|
|
631
|
-
* definition's presence in that list IS the host's "this session is paused"
|
|
632
|
-
* fact, and the companion status definition answers with the park state as of
|
|
633
|
-
* the read; this module never derives either from its own press. That one
|
|
634
|
-
* answer carries the record's goal opt-out as well, so the switch beside the
|
|
635
|
-
* pause button can show it without a second host round-trip — and without it,
|
|
636
|
-
* the control renders no switch at all.
|
|
14
|
+
* The browser reads the native `pauseSession` stream; actions travel through
|
|
15
|
+
* the same Remote namespace. No slash command is involved. The stream is
|
|
16
|
+
* host-authoritative, so this module never derives a state from its own press.
|
|
17
|
+
* The snapshot also carries the record's goal opt-out, so the switch beside the
|
|
18
|
+
* pause button can show it without a second host round-trip.
|
|
637
19
|
*
|
|
638
|
-
*
|
|
639
|
-
*
|
|
640
|
-
*
|
|
641
|
-
* produced it rather than a reason to keep asking (`./burst.ts`). One transition
|
|
642
|
-
* arrives as several registry changes — parking a session also registers its
|
|
643
|
-
* three commands — so the burst's own collection window is what turns a tick of
|
|
644
|
-
* changes into one read per shown session. A press or a goal switch reads as it
|
|
645
|
-
* runs, and the marker changes it causes are that action's own echo: those are
|
|
646
|
-
* absorbed for a short window (`./burst.ts`) rather than read back — except for
|
|
647
|
-
* the one look an action owes when that window closes, if the reading it left
|
|
648
|
-
* behind is still moving, because a transition announced inside the window would
|
|
649
|
-
* otherwise never be read at all.
|
|
650
|
-
*
|
|
651
|
-
* The whole-app control reads the same park and publishes what it found
|
|
652
|
-
* (`./global.ts`), and a header consults that answer before spending a read of
|
|
653
|
-
* its own: with nothing registered, no session can be paused, so a header that
|
|
654
|
-
* already says "running" is left saying it rather than paying a round-trip to
|
|
655
|
-
* hear it again. A header the host last showed on its way to a park is never
|
|
656
|
-
* answered that way — that reading is the newer fact — and the derivation is
|
|
657
|
-
* voided by the next whole-app answer that names a registered session, which
|
|
658
|
-
* the forwarded change a park causes is what brings in.
|
|
20
|
+
* The stream is the change feed. A pushed snapshot triggers one cached read per
|
|
21
|
+
* shown header; a press or goal switch reads the host's answer once after the
|
|
22
|
+
* action.
|
|
659
23
|
* @module dsh-pause-session/client/control
|
|
660
24
|
*/
|
|
661
25
|
const INITIAL$1 = { bySession: {} };
|
|
662
|
-
/** Phases that name a moment on the way to a park rather than a state it reached. */
|
|
663
|
-
const MOVING = /* @__PURE__ */ new Set(["draining", "parking"]);
|
|
664
26
|
/** Owns the published reading per session; nothing else writes that state. */
|
|
665
27
|
var PauseControl = class {
|
|
666
|
-
|
|
28
|
+
transport;
|
|
667
29
|
lifetime;
|
|
668
|
-
aggregate;
|
|
669
30
|
/** State source the Session-header contribution binds its selector hook to. */
|
|
670
31
|
store = (0, _deepseek_ai_dsh_client_store.createSnapshotStore)(INITIAL$1);
|
|
671
|
-
/** Sessions this control has shown; a
|
|
32
|
+
/** Sessions this control has shown; a pushed snapshot applies to every one. */
|
|
672
33
|
shown = /* @__PURE__ */ new Set();
|
|
673
34
|
/** Highest read sequence requested per session: an answer older than it is dropped. */
|
|
674
35
|
latest = /* @__PURE__ */ new Map();
|
|
675
|
-
/**
|
|
676
|
-
* Sessions a change has put back on the list of ones to read once — a
|
|
677
|
-
* transition this browser has been told about and has not answered yet.
|
|
678
|
-
*/
|
|
679
|
-
owed = /* @__PURE__ */ new Set();
|
|
680
|
-
/**
|
|
681
|
-
* Whether the look owed at the close of an echo window is still to be taken.
|
|
682
|
-
* It reads every shown session, exactly as a change does: the look exists
|
|
683
|
-
* because a change may have been swallowed, and a swallowed change carries no
|
|
684
|
-
* payload either.
|
|
685
|
-
*/
|
|
686
|
-
trailing = false;
|
|
687
|
-
/**
|
|
688
|
-
* Sessions whose header was answered from the whole-app "nothing is
|
|
689
|
-
* registered" instead of a read of their own, and which therefore have to be
|
|
690
|
-
* read for real if that answer stops holding.
|
|
691
|
-
*/
|
|
692
|
-
derived = /* @__PURE__ */ new Set();
|
|
693
|
-
/** The bounded burst of reads one transition buys; see `./burst.ts`. */
|
|
694
|
-
burst;
|
|
695
36
|
reads = 0;
|
|
696
37
|
/**
|
|
697
|
-
* @param
|
|
698
|
-
* @param lifetime - aborts in-flight
|
|
699
|
-
* @param windowMs - how long the burst collects the registrations of one
|
|
700
|
-
* transition before its first read; also the wait after a press.
|
|
701
|
-
* @param policy - the burst waits and visibility source; defaults to the real page.
|
|
702
|
-
* @param aggregate - the whole-app control's published answer; absent means it is never consulted.
|
|
38
|
+
* @param transport - the native namespace source and actions.
|
|
39
|
+
* @param lifetime - aborts in-flight reads when the plugin unloads.
|
|
703
40
|
*/
|
|
704
|
-
constructor(
|
|
705
|
-
this.
|
|
41
|
+
constructor(transport, lifetime) {
|
|
42
|
+
this.transport = transport;
|
|
706
43
|
this.lifetime = lifetime;
|
|
707
|
-
this.aggregate = aggregate;
|
|
708
|
-
this.burst = new ReadBurst(() => {
|
|
709
|
-
this.readAll();
|
|
710
|
-
}, {
|
|
711
|
-
...policy,
|
|
712
|
-
windowMs,
|
|
713
|
-
waitsMs: burstWaits(windowMs)
|
|
714
|
-
});
|
|
715
44
|
}
|
|
716
45
|
/**
|
|
717
46
|
* Read the host's state for one session.
|
|
@@ -720,50 +49,40 @@ window.__ModuleLoader__.load({
|
|
|
720
49
|
*/
|
|
721
50
|
async refresh(sessionId) {
|
|
722
51
|
await this.readOne(sessionId);
|
|
723
|
-
this.follow();
|
|
724
52
|
}
|
|
725
|
-
/**
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
53
|
+
/** Apply one pushed snapshot without spending a read per shown header. */
|
|
54
|
+
apply(snapshot) {
|
|
55
|
+
for (const sessionId of this.shown) {
|
|
56
|
+
if (snapshot === void 0) {
|
|
57
|
+
this.publish(sessionId, {
|
|
58
|
+
phase: "unavailable",
|
|
59
|
+
runningTools: []
|
|
60
|
+
});
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
const status = snapshot.sessions[String(sessionId)];
|
|
64
|
+
this.publish(sessionId, status === void 0 ? {
|
|
65
|
+
phase: "active",
|
|
66
|
+
runningTools: []
|
|
67
|
+
} : {
|
|
68
|
+
phase: status.phase,
|
|
69
|
+
runningTools: status.runningTools,
|
|
70
|
+
...status.rearmGoal === void 0 ? {} : { rearmGoal: status.rearmGoal }
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/** A new pushed snapshot may have moved every header; read them once. */
|
|
734
75
|
refreshAll() {
|
|
735
76
|
if (this.shown.size === 0 || this.lifetime.aborted) return;
|
|
736
|
-
|
|
737
|
-
for (const sessionId of this.shown) this.owed.add(sessionId);
|
|
77
|
+
this.readAll();
|
|
738
78
|
}
|
|
739
|
-
/**
|
|
740
|
-
* One pass of the burst: every shown session a change still owes a read. The
|
|
741
|
-
* forwarded event carries no payload, so a change may have touched any of
|
|
742
|
-
* them and each is read once. A session the whole-app answer has already
|
|
743
|
-
* spoken for is answered from it instead of being asked about; see
|
|
744
|
-
* {@link answerIsKnown}.
|
|
745
|
-
*/
|
|
746
79
|
async readAll() {
|
|
747
|
-
if (this.lifetime.aborted)
|
|
748
|
-
|
|
749
|
-
return;
|
|
750
|
-
}
|
|
751
|
-
const pass = this.owed.size > 0 ? [...this.owed] : this.trailing ? [...this.shown] : [];
|
|
752
|
-
this.owed.clear();
|
|
753
|
-
this.trailing = false;
|
|
754
|
-
for (const sessionId of pass) {
|
|
755
|
-
if (this.answerIsKnown(sessionId)) {
|
|
756
|
-
this.deriveActive(sessionId);
|
|
757
|
-
continue;
|
|
758
|
-
}
|
|
759
|
-
await this.readOne(sessionId);
|
|
760
|
-
}
|
|
761
|
-
this.follow();
|
|
80
|
+
if (this.lifetime.aborted) return;
|
|
81
|
+
for (const sessionId of [...this.shown]) await this.readOne(sessionId);
|
|
762
82
|
}
|
|
763
83
|
/** One session's read, published unless a newer read of that session supersedes it. */
|
|
764
84
|
async readOne(sessionId) {
|
|
765
85
|
this.shown.add(sessionId);
|
|
766
|
-
this.derived.delete(sessionId);
|
|
767
86
|
const sequence = ++this.reads;
|
|
768
87
|
this.latest.set(sessionId, sequence);
|
|
769
88
|
const reading = await this.read(sessionId);
|
|
@@ -779,178 +98,66 @@ window.__ModuleLoader__.load({
|
|
|
779
98
|
*/
|
|
780
99
|
async toggle(sessionId) {
|
|
781
100
|
const current = this.store.getSnapshot().bySession[String(sessionId)]?.phase;
|
|
782
|
-
const
|
|
783
|
-
this.
|
|
784
|
-
|
|
101
|
+
const action = current === void 0 || current === "active" ? "pause" : "resume";
|
|
102
|
+
if (!(await this.transport.act({
|
|
103
|
+
action,
|
|
104
|
+
sessionId: String(sessionId)
|
|
105
|
+
}, this.lifetime)).ok) {
|
|
785
106
|
this.publish(sessionId, {
|
|
786
107
|
phase: "unavailable",
|
|
787
108
|
runningTools: []
|
|
788
109
|
});
|
|
789
|
-
this.follow();
|
|
790
110
|
return;
|
|
791
111
|
}
|
|
792
112
|
await this.refresh(sessionId);
|
|
793
113
|
}
|
|
794
114
|
/**
|
|
795
115
|
* Flip this session's goal opt-out. The verb belongs to the session it
|
|
796
|
-
* changes
|
|
797
|
-
* this press needs no listed row. What the switch shows afterwards is the
|
|
798
|
-
* host's next answer, never a local flip: a refused switch leaves the record
|
|
799
|
-
* exactly as it was, and the re-read is what says so.
|
|
116
|
+
* changes; the switch shows the host's next answer, never a local flip.
|
|
800
117
|
* @param sessionId - session whose switch was pressed.
|
|
801
118
|
* @returns after the control re-reads the host.
|
|
802
119
|
*/
|
|
803
120
|
async toggleGoal(sessionId) {
|
|
804
|
-
this.
|
|
805
|
-
|
|
121
|
+
if (!(await this.transport.act({
|
|
122
|
+
action: "goal",
|
|
123
|
+
sessionId: String(sessionId)
|
|
124
|
+
}, this.lifetime)).ok) {
|
|
125
|
+
this.publish(sessionId, {
|
|
126
|
+
phase: "unavailable",
|
|
127
|
+
runningTools: []
|
|
128
|
+
});
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
806
131
|
await this.refresh(sessionId);
|
|
807
132
|
}
|
|
808
|
-
/** Ask the
|
|
133
|
+
/** Ask the native stream what this session's park state is. */
|
|
809
134
|
async read(sessionId) {
|
|
810
|
-
const
|
|
811
|
-
if (
|
|
135
|
+
const snapshot = await this.transport.source.read(this.lifetime);
|
|
136
|
+
if (snapshot === void 0) return {
|
|
812
137
|
phase: "unavailable",
|
|
813
138
|
runningTools: []
|
|
814
139
|
};
|
|
815
|
-
|
|
140
|
+
const status = snapshot.sessions[String(sessionId)];
|
|
141
|
+
if (status === void 0) return {
|
|
816
142
|
phase: "active",
|
|
817
143
|
runningTools: []
|
|
818
144
|
};
|
|
819
|
-
if (listed.value.find((descriptor) => descriptor.definitionId === "dsh-pause-session/status") === void 0) return {
|
|
820
|
-
phase: "parking",
|
|
821
|
-
runningTools: []
|
|
822
|
-
};
|
|
823
|
-
const answered = await this.commands.execute(sessionId, `/${STATUS_COMMAND}`, [], this.lifetime);
|
|
824
|
-
if (!answered.ok || answered.value === void 0) return {
|
|
825
|
-
phase: "unavailable",
|
|
826
|
-
runningTools: []
|
|
827
|
-
};
|
|
828
|
-
const text = answered.value.result.kind === "success" ? answered.value.result.text : void 0;
|
|
829
|
-
const decoded = text === void 0 ? void 0 : decodeParkStatus(text);
|
|
830
|
-
if (decoded === void 0) return {
|
|
831
|
-
phase: "parking",
|
|
832
|
-
runningTools: []
|
|
833
|
-
};
|
|
834
145
|
return {
|
|
835
|
-
phase:
|
|
836
|
-
runningTools:
|
|
837
|
-
...
|
|
146
|
+
phase: status.phase,
|
|
147
|
+
runningTools: status.runningTools,
|
|
148
|
+
...status.rearmGoal === void 0 ? {} : { rearmGoal: status.rearmGoal }
|
|
838
149
|
};
|
|
839
150
|
}
|
|
840
151
|
publish(sessionId, reading) {
|
|
841
152
|
this.store.update((state) => {
|
|
153
|
+
const previous = state.bySession[String(sessionId)];
|
|
154
|
+
if (previous !== void 0 && previous.phase === reading.phase && previous.rearmGoal === reading.rearmGoal && previous.runningTools.length === reading.runningTools.length && previous.runningTools.every((tool, index) => tool === reading.runningTools[index])) return;
|
|
842
155
|
state.bySession = {
|
|
843
156
|
...state.bySession,
|
|
844
157
|
[String(sessionId)]: reading
|
|
845
158
|
};
|
|
846
159
|
});
|
|
847
160
|
}
|
|
848
|
-
/**
|
|
849
|
-
* Whether the park has already answered for this header: with nothing
|
|
850
|
-
* registered, no session can be paused, so a header that says this session is
|
|
851
|
-
* running carries the whole fact and the read that would confirm it is a host
|
|
852
|
-
* round-trip plus two records in that session's log for nothing.
|
|
853
|
-
*
|
|
854
|
-
* A header the host last showed on its way to a park is never answered this
|
|
855
|
-
* way: that reading is newer than the aggregate the derivation rests on, and
|
|
856
|
-
* only a fresh host answer may replace it.
|
|
857
|
-
*/
|
|
858
|
-
answerIsKnown(sessionId) {
|
|
859
|
-
const phase = this.store.getSnapshot().bySession[String(sessionId)]?.phase;
|
|
860
|
-
if (phase !== void 0 && phase !== "active") return false;
|
|
861
|
-
return this.nothingParked();
|
|
862
|
-
}
|
|
863
|
-
/**
|
|
864
|
-
* Whether the whole-app answer says the park holds nothing at all: the one
|
|
865
|
-
* predicate the derivation below and its revocation share.
|
|
866
|
-
*/
|
|
867
|
-
nothingParked() {
|
|
868
|
-
const aggregate = this.aggregate();
|
|
869
|
-
return aggregate?.kind === "reported" && aggregate.status.registered === 0;
|
|
870
|
-
}
|
|
871
|
-
/**
|
|
872
|
-
* Answer one header from the whole-app verdict, and remember that it was that
|
|
873
|
-
* kind of answer: the sessions to read for real are exactly the ones the
|
|
874
|
-
* aggregate may have been wrong about.
|
|
875
|
-
*/
|
|
876
|
-
deriveActive(sessionId) {
|
|
877
|
-
this.derived.add(sessionId);
|
|
878
|
-
this.publish(sessionId, {
|
|
879
|
-
phase: "active",
|
|
880
|
-
runningTools: []
|
|
881
|
-
});
|
|
882
|
-
}
|
|
883
|
-
/**
|
|
884
|
-
* The whole-app answer moved. Whatever was answered from "nothing is
|
|
885
|
-
* registered" is read for real the moment that stops holding — the session a
|
|
886
|
-
* header stood in for may be the one the park just took, possibly parked from
|
|
887
|
-
* another window whose change this browser only sees as the forwarded event.
|
|
888
|
-
* Called on every published aggregate, so it must do nothing while the
|
|
889
|
-
* derivation still holds.
|
|
890
|
-
*/
|
|
891
|
-
reconsider() {
|
|
892
|
-
if (this.derived.size === 0 || this.lifetime.aborted || this.nothingParked()) return;
|
|
893
|
-
for (const sessionId of this.derived) this.owed.add(sessionId);
|
|
894
|
-
this.derived.clear();
|
|
895
|
-
this.burst.next();
|
|
896
|
-
}
|
|
897
|
-
/**
|
|
898
|
-
* Decide what the burst owes after a read: another look while a change this
|
|
899
|
-
* browser has not answered is still outstanding, and nothing once none is.
|
|
900
|
-
*
|
|
901
|
-
* A phase that is still moving is left alone. The host announces every
|
|
902
|
-
* transition on its command channel — the flush that settles a park included,
|
|
903
|
-
* which changes no registration of its own — so a read that finds `draining`
|
|
904
|
-
* or `parking` is the answer to the transition that produced it, and watching
|
|
905
|
-
* that phase from here would spend reads on a move that is going to speak for
|
|
906
|
-
* itself. What the burst still owes is the delivery of an announcement: a read
|
|
907
|
-
* still armed when the change arrived, and a read for a change that landed
|
|
908
|
-
* while the last pass was in flight.
|
|
909
|
-
*
|
|
910
|
-
* The one transition that cannot speak for itself is the one this browser's
|
|
911
|
-
* own echo window swallowed: a press reads the host, the park moves while the
|
|
912
|
-
* window is still open, and the changes that would have announced it look
|
|
913
|
-
* exactly like the press's own registrations. So an action that left a phase
|
|
914
|
-
* still moving asks for one look when that window closes, and only then — the
|
|
915
|
-
* read is owed at a moment, not watched. Once nothing is owed, the last
|
|
916
|
-
* reading stays on screen; the next change, the next user action, or the shell
|
|
917
|
-
* handing this control a session again is what asks next.
|
|
918
|
-
*/
|
|
919
|
-
follow() {
|
|
920
|
-
if (this.lifetime.aborted) {
|
|
921
|
-
this.burst.stop();
|
|
922
|
-
return;
|
|
923
|
-
}
|
|
924
|
-
if (this.owed.size === 0) {
|
|
925
|
-
const hidden = this.burst.absorbing && this.moving();
|
|
926
|
-
if (hidden) this.burst.recheckAfterEcho();
|
|
927
|
-
this.trailing = hidden;
|
|
928
|
-
this.burst.stop();
|
|
929
|
-
return;
|
|
930
|
-
}
|
|
931
|
-
this.burst.next();
|
|
932
|
-
}
|
|
933
|
-
/**
|
|
934
|
-
* Whether any shown session's last reading is a phase the host has not
|
|
935
|
-
* finished walking — the only states an action's own echo could be hiding a
|
|
936
|
-
* transition behind.
|
|
937
|
-
*/
|
|
938
|
-
moving() {
|
|
939
|
-
const shown = this.store.getSnapshot().bySession;
|
|
940
|
-
for (const sessionId of this.shown) {
|
|
941
|
-
const phase = shown[String(sessionId)]?.phase;
|
|
942
|
-
if (phase !== void 0 && MOVING.has(phase)) return true;
|
|
943
|
-
}
|
|
944
|
-
return false;
|
|
945
|
-
}
|
|
946
|
-
/**
|
|
947
|
-
* Follow the page in and out of view. A hidden page is not being read — and
|
|
948
|
-
* the read the burst was about to take is not lost, it waits for the page to
|
|
949
|
-
* come back.
|
|
950
|
-
*/
|
|
951
|
-
watchVisibility() {
|
|
952
|
-
this.burst.checkVisibility();
|
|
953
|
-
}
|
|
954
161
|
};
|
|
955
162
|
//#endregion
|
|
956
163
|
//#region lib/types/client/view.js
|
|
@@ -994,11 +201,11 @@ window.__ModuleLoader__.load({
|
|
|
994
201
|
* @param reading - the host-reported reading, or `undefined` before the first answer.
|
|
995
202
|
* @returns the view for that reading.
|
|
996
203
|
*/
|
|
997
|
-
function pauseView(reading) {
|
|
204
|
+
function pauseView(reading, running = true) {
|
|
998
205
|
switch (reading?.phase) {
|
|
999
206
|
case "active": return {
|
|
1000
207
|
paused: false,
|
|
1001
|
-
enabled:
|
|
208
|
+
enabled: running,
|
|
1002
209
|
ariaKey: "pause.aria",
|
|
1003
210
|
runningTools: []
|
|
1004
211
|
};
|
|
@@ -1007,16 +214,14 @@ window.__ModuleLoader__.load({
|
|
|
1007
214
|
enabled: true,
|
|
1008
215
|
ariaKey: "draining.aria",
|
|
1009
216
|
labelKey: "state.draining",
|
|
1010
|
-
runningTools: reading.runningTools
|
|
1011
|
-
...goalOf(reading)
|
|
217
|
+
runningTools: reading.runningTools
|
|
1012
218
|
};
|
|
1013
219
|
case "parking": return {
|
|
1014
220
|
paused: true,
|
|
1015
221
|
enabled: true,
|
|
1016
222
|
ariaKey: "parking.aria",
|
|
1017
223
|
labelKey: "state.parking",
|
|
1018
|
-
runningTools: []
|
|
1019
|
-
...goalOf(reading)
|
|
224
|
+
runningTools: []
|
|
1020
225
|
};
|
|
1021
226
|
case "safe": return {
|
|
1022
227
|
paused: true,
|
|
@@ -1031,8 +236,7 @@ window.__ModuleLoader__.load({
|
|
|
1031
236
|
enabled: true,
|
|
1032
237
|
ariaKey: "flushFailed.aria",
|
|
1033
238
|
labelKey: "state.flushFailed",
|
|
1034
|
-
runningTools: []
|
|
1035
|
-
...goalOf(reading)
|
|
239
|
+
runningTools: []
|
|
1036
240
|
};
|
|
1037
241
|
case "unavailable": return {
|
|
1038
242
|
paused: false,
|
|
@@ -1072,6 +276,16 @@ window.__ModuleLoader__.load({
|
|
|
1072
276
|
}
|
|
1073
277
|
];
|
|
1074
278
|
/**
|
|
279
|
+
* The decisions one row may offer. Continue and note both deliver the owed
|
|
280
|
+
* request, so they are absent when the log is unreadable or the host says it
|
|
281
|
+
* cannot wake the entry; the remaining decisions still give the user a way out.
|
|
282
|
+
* @param row - the rendered row.
|
|
283
|
+
* @returns the actions that can be taken on it.
|
|
284
|
+
*/
|
|
285
|
+
function triageActionsFor(row) {
|
|
286
|
+
return TRIAGE_ACTION_VIEWS.filter((action) => action.action !== "continue" && action.action !== "note" ? true : row.readable && row.wakeable);
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
1075
289
|
* The fifth listed verb, rendered apart from the four decisions: it is a
|
|
1076
290
|
* switch on what the NEXT resume may spend, not a way to decide this row.
|
|
1077
291
|
*/
|
|
@@ -1092,12 +306,19 @@ window.__ModuleLoader__.load({
|
|
|
1092
306
|
* @returns the rows as the panel renders them.
|
|
1093
307
|
*/
|
|
1094
308
|
function triageView(reading) {
|
|
1095
|
-
if (reading === void 0
|
|
309
|
+
if (reading === void 0) return {
|
|
1096
310
|
visible: false,
|
|
311
|
+
unavailable: false,
|
|
312
|
+
rows: []
|
|
313
|
+
};
|
|
314
|
+
if (reading.kind === "unavailable") return {
|
|
315
|
+
visible: true,
|
|
316
|
+
unavailable: true,
|
|
1097
317
|
rows: []
|
|
1098
318
|
};
|
|
1099
319
|
return {
|
|
1100
320
|
visible: true,
|
|
321
|
+
unavailable: false,
|
|
1101
322
|
rows: reading.rows.map((row) => ({
|
|
1102
323
|
sessionId: row.sessionId,
|
|
1103
324
|
phaseKey: PHASE_KEY[row.phase],
|
|
@@ -1114,7 +335,7 @@ window.__ModuleLoader__.load({
|
|
|
1114
335
|
* @param reading - the host-reported reading, or `undefined` before the first answer.
|
|
1115
336
|
* @returns the view for that reading.
|
|
1116
337
|
*/
|
|
1117
|
-
function globalPauseView(reading) {
|
|
338
|
+
function globalPauseView(reading, sessions) {
|
|
1118
339
|
if (reading === void 0) return {
|
|
1119
340
|
visible: false,
|
|
1120
341
|
enabled: false,
|
|
@@ -1125,9 +346,9 @@ window.__ModuleLoader__.load({
|
|
|
1125
346
|
unclean: 0
|
|
1126
347
|
};
|
|
1127
348
|
if (reading.kind === "unavailable") return {
|
|
1128
|
-
visible:
|
|
349
|
+
visible: true,
|
|
1129
350
|
enabled: false,
|
|
1130
|
-
ariaKey: "unavailable.aria",
|
|
351
|
+
ariaKey: "all.unavailable.aria",
|
|
1131
352
|
action: "none",
|
|
1132
353
|
detail: "",
|
|
1133
354
|
caveats: [],
|
|
@@ -1182,10 +403,14 @@ window.__ModuleLoader__.load({
|
|
|
1182
403
|
};
|
|
1183
404
|
case "waiting": return status.draining.length > 0 || status.parking > 0 ? {
|
|
1184
405
|
...base,
|
|
1185
|
-
ariaKey: "all.
|
|
1186
|
-
action: "
|
|
406
|
+
ariaKey: "all.cancel.aria",
|
|
407
|
+
action: "cancel-all",
|
|
1187
408
|
labelKey: "all.finishing",
|
|
1188
|
-
detail: `${String(status.safe)}/${String(status.registered)}
|
|
409
|
+
detail: `${String(status.safe)}/${String(status.registered)}`,
|
|
410
|
+
running: status.draining.map((id) => {
|
|
411
|
+
const tools = sessions?.[id]?.runningTools ?? [];
|
|
412
|
+
return tools.length === 0 ? id : `${id}: ${tools.join(", ")}`;
|
|
413
|
+
}).join(", ") || void 0
|
|
1189
414
|
} : {
|
|
1190
415
|
...base,
|
|
1191
416
|
enabled: false,
|
|
@@ -1201,67 +426,38 @@ window.__ModuleLoader__.load({
|
|
|
1201
426
|
/**
|
|
1202
427
|
* Browser-side reading and driving of the host's whole-app park state.
|
|
1203
428
|
*
|
|
1204
|
-
* The
|
|
1205
|
-
*
|
|
1206
|
-
*
|
|
1207
|
-
*
|
|
1208
|
-
* asks: `pause-state-all` is registered globally and its RESULT carries the
|
|
1209
|
-
* whole-app status as of the read.
|
|
1210
|
-
*
|
|
1211
|
-
* The command channel is session-addressed, so the control needs one session
|
|
1212
|
-
* to talk through. It picks a user session the host can serve (never a
|
|
1213
|
-
* subagent's own session), preferring the one on screen; with no session at
|
|
1214
|
-
* all there is nothing to ask, and the control claims nothing rather than
|
|
1215
|
-
* inventing an answer. Once the host's own answer names the session the park
|
|
1216
|
-
* belongs to, reads are addressed there instead: a read writes two records into
|
|
1217
|
-
* the address session's transcript, and they belong in the session being parked
|
|
1218
|
-
* rather than in whatever the user happens to be looking at. That name outlives
|
|
1219
|
-
* the phases — a session that drained and parked cleanly names nothing else — so
|
|
1220
|
-
* a clean park is addressed like a moving one. Failing that, the session the
|
|
1221
|
-
* last answer reported as registered is remembered, then the one a read was last
|
|
1222
|
-
* served through — an address change is the user looking somewhere else, not
|
|
1223
|
-
* news about the park, and a park that has emptied out is no reason to start
|
|
1224
|
-
* writing rows into every session the user opens next. An address that stops
|
|
1225
|
-
* answering (its agent is gone) is forgotten and the read falls back to the next
|
|
1226
|
-
* one, the session the shell asks with last, so a stale address costs an honest
|
|
1227
|
-
* answer rather than a control that can never speak again.
|
|
1228
|
-
*
|
|
1229
|
-
* Every phase a session reaches announces itself through the registry — a
|
|
1230
|
-
* transition re-registers that session's status verb — so a change is what buys
|
|
1231
|
-
* the next read, and the flush that settles a park is announced like every other
|
|
1232
|
-
* step (`./burst.ts`). A read that finds work still in flight is therefore the
|
|
1233
|
-
* answer to the transition that produced it rather than a reason to keep asking;
|
|
1234
|
-
* what the burst still owes is the delivery of a change, and the rows of a panel
|
|
1235
|
-
* whose own read did not answer. A settled answer is left on screen unread: the
|
|
1236
|
-
* panel stays readable and the pill keeps saying what it says, but the browser
|
|
1237
|
-
* stops spending host round-trips and log records on a state that has stopped
|
|
1238
|
-
* moving. What owes a read is the host's forwarded registry change, the user's
|
|
1239
|
-
* own press or decision, and the first read at mount; once an answer is on
|
|
1240
|
-
* screen, the shell handing this control another session to ask through is none
|
|
1241
|
-
* of those. A press, a decision and an opened panel each read the host as they
|
|
1242
|
-
* run, and the changes they set off are that action's own echo: those are
|
|
1243
|
-
* absorbed for a short window (`./burst.ts`), so a forwarded change owes a read
|
|
1244
|
-
* exactly when something moved that this browser did not do — with one look owed
|
|
1245
|
-
* at the close of that window when the reading the action left is still moving,
|
|
1246
|
-
* which is how a park that reaches its final phase inside the echo window is
|
|
1247
|
-
* still read.
|
|
429
|
+
* The aggregate arrives from the native `pauseSession` stream. The stream is
|
|
430
|
+
* process-wide and writes nothing into any Session; actions travel through the
|
|
431
|
+
* same Remote namespace. A pushed snapshot is applied directly; a settled park
|
|
432
|
+
* costs nothing while its last answer stays on screen.
|
|
1248
433
|
* @module dsh-pause-session/client/global
|
|
1249
434
|
*/
|
|
435
|
+
/** Map one Remote row to the list's structured row. */
|
|
436
|
+
function wireTriageRow(row) {
|
|
437
|
+
return {
|
|
438
|
+
sessionId: row.sessionId,
|
|
439
|
+
phase: row.phase,
|
|
440
|
+
updatedAt: row.updatedAt,
|
|
441
|
+
reading: row.reading,
|
|
442
|
+
...row.lastEvent === void 0 ? {} : { lastEvent: row.lastEvent },
|
|
443
|
+
...row.turn === void 0 ? {} : { turn: row.turn },
|
|
444
|
+
...row.step === void 0 ? {} : { step: row.step },
|
|
445
|
+
traces: row.traces.map((trace) => ({
|
|
446
|
+
tool: trace.tool,
|
|
447
|
+
callId: trace.callId ?? ""
|
|
448
|
+
})),
|
|
449
|
+
owed: row.owed,
|
|
450
|
+
wakeable: row.wakeable,
|
|
451
|
+
rearmGoal: row.rearmGoal
|
|
452
|
+
};
|
|
453
|
+
}
|
|
1250
454
|
const INITIAL = {
|
|
1251
455
|
global: void 0,
|
|
1252
456
|
panelOpen: false,
|
|
1253
457
|
triage: void 0
|
|
1254
458
|
};
|
|
1255
|
-
/** The command one listed verb runs, named from the one vocabulary the host registers. */
|
|
1256
|
-
const TRIAGE_DECISION_COMMAND = {
|
|
1257
|
-
continue: TRIAGE_CONTINUE_COMMAND,
|
|
1258
|
-
note: TRIAGE_NOTE_COMMAND,
|
|
1259
|
-
review: TRIAGE_REVIEW_COMMAND,
|
|
1260
|
-
leave: TRIAGE_LEAVE_COMMAND,
|
|
1261
|
-
goal: TRIAGE_GOAL_COMMAND
|
|
1262
|
-
};
|
|
1263
459
|
/**
|
|
1264
|
-
* The session a whole-app
|
|
460
|
+
* The session a whole-app action is addressed to.
|
|
1265
461
|
*
|
|
1266
462
|
* A subagent's own session is never addressed: its agent belongs to a parent
|
|
1267
463
|
* and is not the host's business to drive from the shell. Among the rest, the
|
|
@@ -1277,410 +473,955 @@ window.__ModuleLoader__.load({
|
|
|
1277
473
|
});
|
|
1278
474
|
return usable.find((id) => (state.byId[String(id)]?.retainedBy?.mainView ?? 0) > 0) ?? usable.find((id) => state.byId[String(id)]?.running === true) ?? usable[0];
|
|
1279
475
|
}
|
|
1280
|
-
/**
|
|
1281
|
-
* The registered sessions one whole-app answer names, best address first: the
|
|
1282
|
-
* one still draining is the one this host is executing against right now, the
|
|
1283
|
-
* rest is the host's own vocabulary for a registered session it is not going to
|
|
1284
|
-
* move any further.
|
|
1285
|
-
*
|
|
1286
|
-
* A stalled or unreadable record is deliberately not an address for a read —
|
|
1287
|
-
* this host may not hold that session at all, and a read that could not be
|
|
1288
|
-
* delivered would report "unavailable" for a transport reason rather than a
|
|
1289
|
-
* state — but it is still a registered session, which is all the remembered
|
|
1290
|
-
* address is for. The host's own answer is JSON: the branded ids arrive as the
|
|
1291
|
-
* strings they were encoded as, exactly like the ids the shell hands the pill.
|
|
1292
|
-
* @param status - the host's last whole-app answer.
|
|
1293
|
-
* @returns the registered sessions the answer names, best first.
|
|
1294
|
-
*/
|
|
1295
|
-
function registeredSessions(status) {
|
|
1296
|
-
return [
|
|
1297
|
-
...status.draining,
|
|
1298
|
-
...status.stalled,
|
|
1299
|
-
...status.unresumable,
|
|
1300
|
-
...status.unreadTrees
|
|
1301
|
-
];
|
|
1302
|
-
}
|
|
1303
|
-
/**
|
|
1304
|
-
* The session a registered park owns, when the host's answer names one that is
|
|
1305
|
-
* still moving. This is how a read stops landing in whatever session the user
|
|
1306
|
-
* happens to be looking at.
|
|
1307
|
-
* @param status - the host's last whole-app answer.
|
|
1308
|
-
* @returns a draining session, or `undefined` when the answer names none.
|
|
1309
|
-
*/
|
|
1310
|
-
function registeredAddress(status) {
|
|
1311
|
-
const draining = status.draining[0];
|
|
1312
|
-
return draining === void 0 ? void 0 : draining;
|
|
1313
|
-
}
|
|
1314
|
-
/**
|
|
1315
|
-
* The session the host names as the park's own, whatever phase the park
|
|
1316
|
-
* reached — the address a whole-app read belongs at.
|
|
1317
|
-
*
|
|
1318
|
-
* This is the fact {@link registeredAddress} cannot carry once a park is clean:
|
|
1319
|
-
* a session that reached `safe` has nothing left to do, so no bucket names it,
|
|
1320
|
-
* and a reader in another window would fall back to whatever session it is
|
|
1321
|
-
* showing. An answer written before this field existed names no session here:
|
|
1322
|
-
* an absent address, never one to guess at.
|
|
1323
|
-
* @param status - the host's last whole-app answer.
|
|
1324
|
-
* @returns the session the park belongs to, or `undefined` when the answer names none.
|
|
1325
|
-
*/
|
|
1326
|
-
function parkedAddress(status) {
|
|
1327
|
-
return status.parkedAt === void 0 ? void 0 : status.parkedAt;
|
|
1328
|
-
}
|
|
1329
476
|
/** Owns the published whole-app reading; nothing else writes that state. */
|
|
1330
477
|
var PauseGlobalControl = class {
|
|
1331
|
-
|
|
478
|
+
transport;
|
|
1332
479
|
lifetime;
|
|
1333
480
|
/** State source the whole-app contribution binds its selector hook to. */
|
|
1334
481
|
store = (0, _deepseek_ai_dsh_client_store.createSnapshotStore)(INITIAL);
|
|
1335
|
-
/** The session the shell asked this control through; reads
|
|
482
|
+
/** The session the shell asked this control through; list reads use it. */
|
|
1336
483
|
address;
|
|
1337
|
-
/**
|
|
1338
|
-
* The last session an answer reported as registered — the address a read
|
|
1339
|
-
* falls back to when the host's own answer names no parked session, which is
|
|
1340
|
-
* every reading written before that field existed.
|
|
1341
|
-
*
|
|
1342
|
-
* The buckets that feed it name only what is still moving, so they say
|
|
1343
|
-
* nothing about a park that finished cleanly; the park's own name is the live
|
|
1344
|
-
* fact, and this memory is what keeps the rows in the park's transcript for an
|
|
1345
|
-
* answer that predates it. An empty registry does not clear it: a park whose
|
|
1346
|
-
* sessions stopped moving has not moved out of its sessions.
|
|
1347
|
-
*/
|
|
1348
|
-
lastRegistered;
|
|
1349
|
-
/**
|
|
1350
|
-
* The session an aggregate read was last served through. Later reads keep
|
|
1351
|
-
* going there: every read is two records in the address session's log, and
|
|
1352
|
-
* the shell's address follows whatever the user is looking at, which is not
|
|
1353
|
-
* where a row about the park belongs.
|
|
1354
|
-
*/
|
|
1355
|
-
served;
|
|
1356
484
|
/** Highest decision-list read requested: an answer older than it is dropped. */
|
|
1357
485
|
latestTriage = 0;
|
|
1358
|
-
/** Highest read sequence requested: an answer older than it is dropped. */
|
|
1359
|
-
latest = 0;
|
|
1360
|
-
/**
|
|
1361
|
-
* Whether a registry change has arrived that no completed read pass has
|
|
1362
|
-
* answered. Set only for a change that is news — an absorbed echo of this
|
|
1363
|
-
* browser's own action is already answered by that action's read.
|
|
1364
|
-
*/
|
|
1365
|
-
owed = false;
|
|
1366
|
-
/** The bounded burst of reads one transition buys; see `./burst.ts`. */
|
|
1367
|
-
burst;
|
|
1368
486
|
/**
|
|
1369
|
-
* @param
|
|
1370
|
-
* @param lifetime - aborts in-flight
|
|
1371
|
-
* @param windowMs - how long the burst collects the registrations of one
|
|
1372
|
-
* transition before its first read; also the wait after a press.
|
|
1373
|
-
* @param policy - the burst waits and visibility source; defaults to the real page.
|
|
487
|
+
* @param transport - the native namespace source and actions.
|
|
488
|
+
* @param lifetime - aborts in-flight reads when the plugin unloads.
|
|
1374
489
|
*/
|
|
1375
|
-
constructor(
|
|
1376
|
-
this.
|
|
490
|
+
constructor(transport, lifetime) {
|
|
491
|
+
this.transport = transport;
|
|
1377
492
|
this.lifetime = lifetime;
|
|
1378
|
-
this.burst = new ReadBurst(() => {
|
|
1379
|
-
this.readAll();
|
|
1380
|
-
}, {
|
|
1381
|
-
...policy,
|
|
1382
|
-
windowMs,
|
|
1383
|
-
waitsMs: burstWaits(windowMs)
|
|
1384
|
-
});
|
|
1385
493
|
}
|
|
1386
494
|
/**
|
|
1387
|
-
* The shell handed this control a session
|
|
1388
|
-
*
|
|
1389
|
-
* here — the view moving is not the park moving, and after the first answer
|
|
1390
|
-
* the forwarded registry change and the user's own press are what ask again.
|
|
1391
|
-
* A read that came back with nothing is not an answer, so moving to another
|
|
1392
|
-
* session is one more chance to reach the host rather than a control that
|
|
1393
|
-
* stays mute until something else happens to change. The address is kept
|
|
1394
|
-
* either way: it is where this control's presses and its decision list go, and
|
|
1395
|
-
* the fallback a remembered address falls back to.
|
|
495
|
+
* The shell handed this control a session. The first call reads the aggregate;
|
|
496
|
+
* later calls only move where list reads and actions are addressed.
|
|
1396
497
|
* @param sessionId - the session the shell asks through.
|
|
1397
|
-
* @returns after this read's answer is published or superseded by a newer one.
|
|
1398
498
|
*/
|
|
1399
499
|
async refresh(sessionId) {
|
|
1400
500
|
this.address = sessionId;
|
|
1401
|
-
if (this.answered()) return;
|
|
1402
501
|
await this.readAggregate();
|
|
1403
|
-
this.follow();
|
|
1404
502
|
}
|
|
1405
|
-
/**
|
|
1406
|
-
|
|
1407
|
-
|
|
503
|
+
/** Apply one pushed snapshot directly. */
|
|
504
|
+
apply(snapshot) {
|
|
505
|
+
this.store.update((state) => {
|
|
506
|
+
state.sessions = snapshot?.sessions;
|
|
507
|
+
});
|
|
508
|
+
this.publish(snapshot === void 0 ? { kind: "unavailable" } : {
|
|
509
|
+
kind: "reported",
|
|
510
|
+
status: snapshot.global
|
|
511
|
+
});
|
|
1408
512
|
}
|
|
1409
|
-
/**
|
|
1410
|
-
* The host's command registry changed: something may have moved, so a burst
|
|
1411
|
-
* is owed. The reads run on the burst's own schedule rather than here, which
|
|
1412
|
-
* is what coalesces a tick's worth of changes — parking N sessions announces
|
|
1413
|
-
* three registrations each, and a phase change re-registers one more — into
|
|
1414
|
-
* one burst. A change that is this browser's own action's echo owes nothing:
|
|
1415
|
-
* the action read the host as it ran.
|
|
1416
|
-
*/
|
|
513
|
+
/** A pushed snapshot may have moved the aggregate and the open list. */
|
|
1417
514
|
refreshAll() {
|
|
1418
|
-
if (this.
|
|
1419
|
-
|
|
1420
|
-
this.
|
|
1421
|
-
}
|
|
1422
|
-
/**
|
|
1423
|
-
* One read of the whole screen: the aggregate, plus the decision list while
|
|
1424
|
-
* the user has it open. A closed panel spends no command calls on rows nobody
|
|
1425
|
-
* is reading.
|
|
1426
|
-
*/
|
|
1427
|
-
async readAll() {
|
|
1428
|
-
if (this.target() === void 0 || this.lifetime.aborted) {
|
|
1429
|
-
this.owed = false;
|
|
1430
|
-
this.burst.stop();
|
|
1431
|
-
return;
|
|
1432
|
-
}
|
|
1433
|
-
this.owed = false;
|
|
1434
|
-
await this.readAggregate();
|
|
1435
|
-
if (this.store.getSnapshot().panelOpen) await this.readTriage();
|
|
1436
|
-
this.follow();
|
|
1437
|
-
}
|
|
1438
|
-
/**
|
|
1439
|
-
* The session a read goes through: the one the park is still draining, else
|
|
1440
|
-
* the one the host names as the park's own, else the one a read was last
|
|
1441
|
-
* served through, else the session the shell is on now. The park's own address
|
|
1442
|
-
* comes first because a read's rows land in the transcript it was taken
|
|
1443
|
-
* through, and the park — not the reader — is what they are about; its name
|
|
1444
|
-
* survives the clean case, where every bucket above is empty and a park that
|
|
1445
|
-
* finished would otherwise have no address at all.
|
|
1446
|
-
* @param skip - an address this read already tried and could not reach. The
|
|
1447
|
-
* fallback is the next candidate rather than the same one again.
|
|
1448
|
-
*/
|
|
1449
|
-
target(skip) {
|
|
1450
|
-
const current = this.store.getSnapshot().global;
|
|
1451
|
-
const named = current?.kind === "reported" ? current.status : void 0;
|
|
1452
|
-
return [
|
|
1453
|
-
named === void 0 ? void 0 : registeredAddress(named),
|
|
1454
|
-
named === void 0 ? void 0 : parkedAddress(named),
|
|
1455
|
-
this.lastRegistered,
|
|
1456
|
-
this.served,
|
|
1457
|
-
this.address
|
|
1458
|
-
].find((candidate) => candidate !== void 0 && candidate !== skip);
|
|
1459
|
-
}
|
|
1460
|
-
/**
|
|
1461
|
-
* Ask the host for the aggregate through the session a read goes through,
|
|
1462
|
-
* with the one fallback an address that stopped answering is worth: the
|
|
1463
|
-
* remembered address is forgotten, and the read is taken again through the
|
|
1464
|
-
* next one — the session the shell is on at the latest. A memory of a session
|
|
1465
|
-
* this host no longer holds would otherwise leave the control unable to say
|
|
1466
|
-
* anything at all, and a single retry is the whole price of keeping it honest.
|
|
1467
|
-
*/
|
|
1468
|
-
async readAggregate() {
|
|
1469
|
-
const address = this.target();
|
|
1470
|
-
if (address === void 0) return;
|
|
1471
|
-
if (!await this.readAt(address)) return;
|
|
1472
|
-
const fallback = this.target(address);
|
|
1473
|
-
if (fallback === void 0) return;
|
|
1474
|
-
await this.readAt(fallback);
|
|
515
|
+
if (this.lifetime.aborted) return;
|
|
516
|
+
this.readAggregate();
|
|
517
|
+
if (this.store.getSnapshot().panelOpen) this.refreshTriage();
|
|
1475
518
|
}
|
|
1476
519
|
/**
|
|
1477
|
-
*
|
|
1478
|
-
*
|
|
1479
|
-
* A served address is remembered, and one that did not answer is forgotten —
|
|
1480
|
-
* that is what makes the read fall back to the next address rather than ask
|
|
1481
|
-
* it again.
|
|
1482
|
-
* @param sessionId - the session this read goes through.
|
|
1483
|
-
* @returns whether this read came back without an answer; a superseded read is
|
|
1484
|
-
* not one, because the newer read already owns the outcome.
|
|
1485
|
-
*/
|
|
1486
|
-
async readAt(sessionId) {
|
|
1487
|
-
const sequence = ++this.latest;
|
|
1488
|
-
const reading = await this.read(sessionId);
|
|
1489
|
-
if (this.latest !== sequence) return false;
|
|
1490
|
-
if (reading.kind === "reported") {
|
|
1491
|
-
this.served = sessionId;
|
|
1492
|
-
const registered = registeredSessions(reading.status)[0];
|
|
1493
|
-
if (registered !== void 0) this.lastRegistered = registered;
|
|
1494
|
-
} else {
|
|
1495
|
-
if (this.served === sessionId) this.served = void 0;
|
|
1496
|
-
if (this.lastRegistered === sessionId) this.lastRegistered = void 0;
|
|
1497
|
-
}
|
|
1498
|
-
this.publish(reading);
|
|
1499
|
-
return reading.kind === "unavailable";
|
|
1500
|
-
}
|
|
1501
|
-
/**
|
|
1502
|
-
* Open or close the decision list. Opening asks the host for the rows; there
|
|
1503
|
-
* is no state to show without that answer. Opening is also a client action
|
|
1504
|
-
* that reads, so the changes it sets off are absorbed rather than read back,
|
|
1505
|
-
* and whether the burst owes anything is what its own read decides.
|
|
520
|
+
* Open or close the decision list. Opening asks for the rows; there is no
|
|
521
|
+
* state to show without that answer.
|
|
1506
522
|
* @param open - whether the panel should be showing.
|
|
1507
523
|
*/
|
|
1508
524
|
setPanel(open) {
|
|
1509
525
|
this.store.update((state) => {
|
|
1510
526
|
state.panelOpen = open;
|
|
1511
527
|
});
|
|
1512
|
-
if (open)
|
|
1513
|
-
this.burst.absorb();
|
|
1514
|
-
this.refreshTriage();
|
|
1515
|
-
return;
|
|
1516
|
-
}
|
|
1517
|
-
this.follow();
|
|
528
|
+
if (open) this.refreshTriage();
|
|
1518
529
|
}
|
|
1519
|
-
/**
|
|
1520
|
-
* Ask the host for the decision list through the session the user is on. The
|
|
1521
|
-
* list is recorded per calling session and the decisions act only on rows
|
|
1522
|
-
* that session was shown, so this read — unlike the aggregate — stays on the
|
|
1523
|
-
* shell's own address.
|
|
1524
|
-
*/
|
|
530
|
+
/** Ask the host for the decision list for the session the user is on. */
|
|
1525
531
|
async refreshTriage() {
|
|
1526
532
|
await this.readTriage();
|
|
1527
|
-
this.follow();
|
|
1528
|
-
}
|
|
1529
|
-
/**
|
|
1530
|
-
* Read the aggregate again because the user asked this control to act: the
|
|
1531
|
-
* address is the session the shell is on, since a press is a user action on
|
|
1532
|
-
* the session the user is looking at, and the host's answer is what the
|
|
1533
|
-
* control shows next.
|
|
1534
|
-
* @param sessionId - the session the shell is on.
|
|
1535
|
-
*/
|
|
1536
|
-
async reread(sessionId) {
|
|
1537
|
-
this.address = sessionId;
|
|
1538
|
-
await this.readAggregate();
|
|
1539
|
-
this.follow();
|
|
1540
|
-
}
|
|
1541
|
-
/** One decision-list read, addressed at the session the user is on. */
|
|
1542
|
-
async readTriage() {
|
|
1543
|
-
const address = this.address;
|
|
1544
|
-
if (address === void 0) return;
|
|
1545
|
-
const sequence = ++this.latestTriage;
|
|
1546
|
-
const reading = await this.readTriageRows(address);
|
|
1547
|
-
if (this.latestTriage !== sequence) return;
|
|
1548
|
-
this.store.update((state) => {
|
|
1549
|
-
state.triage = reading;
|
|
1550
|
-
});
|
|
1551
533
|
}
|
|
1552
534
|
/**
|
|
1553
535
|
* Take one listed verb on one listed session, addressed through the session
|
|
1554
|
-
* the user is on.
|
|
1555
|
-
*
|
|
1556
|
-
* host's answer — not this control's assumption — is what the next render
|
|
1557
|
-
* shows: a decision can release a session, and the goal switch changes what
|
|
1558
|
-
* the row itself says.
|
|
536
|
+
* the user is on. A refusal is published verbatim rather than silently
|
|
537
|
+
* leaving the previous rows in place.
|
|
1559
538
|
* @param sessionId - the listed session to act on.
|
|
1560
539
|
* @param action - which listed verb was taken.
|
|
1561
540
|
*/
|
|
1562
541
|
async decide(sessionId, action) {
|
|
1563
542
|
const address = this.address;
|
|
1564
543
|
if (address === void 0) return;
|
|
1565
|
-
this.
|
|
1566
|
-
|
|
1567
|
-
|
|
544
|
+
const answer = await this.transport.decide(String(address), sessionId, action, this.lifetime);
|
|
545
|
+
if (!answer.ok) {
|
|
546
|
+
this.store.update((state) => {
|
|
547
|
+
state.error = answer.message;
|
|
548
|
+
});
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
this.store.update((state) => {
|
|
552
|
+
state.error = void 0;
|
|
553
|
+
});
|
|
1568
554
|
await this.refreshTriage();
|
|
1569
555
|
await this.reread(address);
|
|
1570
556
|
}
|
|
1571
557
|
/**
|
|
1572
558
|
* Press the pill: park everything, or release everything the park owns.
|
|
1573
559
|
* Which one it is comes from the host's own last answer, never from a local
|
|
1574
|
-
* flip; the state shown afterwards is the host's answer again.
|
|
1575
|
-
* view does not turn into a verb — every registered session is one the host
|
|
1576
|
-
* will not wake — is not pressed at all.
|
|
560
|
+
* flip; the state shown afterwards is the host's answer again.
|
|
1577
561
|
* @param sessionId - the session to address the verb to.
|
|
1578
|
-
* @returns after the control re-reads the host.
|
|
1579
562
|
*/
|
|
1580
563
|
async toggle(sessionId) {
|
|
1581
564
|
const current = this.store.getSnapshot().global;
|
|
1582
565
|
if (current === void 0 || current.kind !== "reported") return;
|
|
1583
566
|
const action = globalPauseView(current).action;
|
|
1584
567
|
if (action === "none") return;
|
|
1585
|
-
const
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
568
|
+
const answer = await this.transport.act({
|
|
569
|
+
action,
|
|
570
|
+
sessionId: String(sessionId)
|
|
571
|
+
}, this.lifetime);
|
|
572
|
+
if (!answer.ok) {
|
|
573
|
+
this.store.update((state) => {
|
|
574
|
+
state.error = answer.message;
|
|
575
|
+
});
|
|
1590
576
|
return;
|
|
1591
577
|
}
|
|
578
|
+
this.store.update((state) => {
|
|
579
|
+
state.error = void 0;
|
|
580
|
+
});
|
|
1592
581
|
await this.reread(sessionId);
|
|
1593
582
|
}
|
|
1594
|
-
/**
|
|
1595
|
-
async
|
|
1596
|
-
const
|
|
1597
|
-
if (
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
583
|
+
/** Read the aggregate once and publish it. */
|
|
584
|
+
async readAggregate() {
|
|
585
|
+
const snapshot = await this.transport.source.read(this.lifetime);
|
|
586
|
+
if (snapshot === void 0) {
|
|
587
|
+
this.publish({ kind: "unavailable" });
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
590
|
+
this.store.update((state) => {
|
|
591
|
+
state.sessions = snapshot.sessions;
|
|
592
|
+
});
|
|
593
|
+
this.publish({
|
|
1601
594
|
kind: "reported",
|
|
1602
|
-
|
|
1603
|
-
};
|
|
595
|
+
status: snapshot.global
|
|
596
|
+
});
|
|
1604
597
|
}
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
return
|
|
598
|
+
async reread(sessionId) {
|
|
599
|
+
this.address = sessionId;
|
|
600
|
+
await this.readAggregate();
|
|
601
|
+
}
|
|
602
|
+
/** One decision-list read, addressed at the session the user is on. */
|
|
603
|
+
async readTriage() {
|
|
604
|
+
const address = this.address;
|
|
605
|
+
if (address === void 0) return;
|
|
606
|
+
const sequence = ++this.latestTriage;
|
|
607
|
+
const reading = await this.readTriageRows(address);
|
|
608
|
+
if (this.latestTriage !== sequence) return;
|
|
609
|
+
this.store.update((state) => {
|
|
610
|
+
state.triage = reading;
|
|
611
|
+
});
|
|
612
|
+
}
|
|
613
|
+
/** Ask the native namespace for the decision list. */
|
|
614
|
+
async readTriageRows(sessionId) {
|
|
615
|
+
const rows = await this.transport.triage(String(sessionId), this.lifetime);
|
|
616
|
+
return rows === void 0 ? { kind: "unavailable" } : {
|
|
1613
617
|
kind: "reported",
|
|
1614
|
-
|
|
618
|
+
rows: rows.map(wireTriageRow)
|
|
1615
619
|
};
|
|
1616
620
|
}
|
|
1617
621
|
publish(reading) {
|
|
1618
622
|
this.store.update((state) => {
|
|
623
|
+
if (state.global?.kind === reading.kind && (reading.kind === "unavailable" || state.global?.kind === "reported" && JSON.stringify(state.global.status) === JSON.stringify(reading.status))) return;
|
|
1619
624
|
state.global = reading;
|
|
1620
625
|
});
|
|
1621
626
|
}
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
627
|
+
};
|
|
628
|
+
//#endregion
|
|
629
|
+
//#region lib/types/client/remote.js
|
|
630
|
+
/**
|
|
631
|
+
* Client-side bridge from the generated `pauseSession` Remote namespace to the
|
|
632
|
+
* control state machines.
|
|
633
|
+
*
|
|
634
|
+
* The native namespace is the only transport: the stream supplies snapshots and
|
|
635
|
+
* the action methods drive every button. No slash command is involved.
|
|
636
|
+
* @module dsh-pause-session/client/remote
|
|
637
|
+
*/
|
|
638
|
+
/** Convert one wire snapshot to the host-status vocabulary the controls already use. */
|
|
639
|
+
function snapshotOf(wire) {
|
|
640
|
+
return {
|
|
641
|
+
global: {
|
|
642
|
+
...wire.global,
|
|
643
|
+
parkedAt: wire.global.parkedAt,
|
|
644
|
+
draining: wire.global.draining,
|
|
645
|
+
stalled: wire.global.stalled,
|
|
646
|
+
unresumable: wire.global.unresumable,
|
|
647
|
+
unreadTrees: wire.global.unreadTrees,
|
|
648
|
+
jobs: wire.global.jobs,
|
|
649
|
+
blind: wire.global.blind
|
|
650
|
+
},
|
|
651
|
+
sessions: Object.fromEntries(Object.entries(wire.sessions).map(([id, status]) => [id, status]))
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
/** First retry delay after the stream drops, in milliseconds. */
|
|
655
|
+
const RECONNECT_BASE_MS = 300;
|
|
656
|
+
/** Ceiling for the reconnect backoff, in milliseconds. */
|
|
657
|
+
const RECONNECT_MAX_MS = 5e3;
|
|
658
|
+
/**
|
|
659
|
+
* Open the native status stream and expose its latest snapshot to both controls.
|
|
660
|
+
*
|
|
661
|
+
* The stream owns no retry of its own: a carrier can drop it on a mux reconnect,
|
|
662
|
+
* a host reload, or a network blip, and the controls must not stay unavailable
|
|
663
|
+
* until the page is refreshed. This source reconnects with a bounded backoff and
|
|
664
|
+
* republishes the new snapshot; while it is disconnected the last value is
|
|
665
|
+
* discarded, so readers see "unavailable" rather than a stale claim.
|
|
666
|
+
* @param remote - the generated Remote namespace.
|
|
667
|
+
* @param lifetime - client lifetime; aborting it releases the stream.
|
|
668
|
+
* @returns the cached source and its change subscription.
|
|
669
|
+
*/
|
|
670
|
+
function openRemoteSource(remote, lifetime) {
|
|
671
|
+
let latest;
|
|
672
|
+
let attempt = 0;
|
|
673
|
+
let timer;
|
|
674
|
+
/** Readers waiting for the first snapshot of any attempt. */
|
|
675
|
+
const waiters = /* @__PURE__ */ new Set();
|
|
676
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
677
|
+
const snapshotListeners = /* @__PURE__ */ new Set();
|
|
678
|
+
const notify = () => {
|
|
679
|
+
for (const wake of [...waiters]) wake();
|
|
680
|
+
waiters.clear();
|
|
681
|
+
for (const listener of [...listeners]) listener();
|
|
682
|
+
for (const listener of [...snapshotListeners]) listener(latest);
|
|
683
|
+
};
|
|
684
|
+
const schedule = () => {
|
|
685
|
+
if (lifetime.aborted || timer !== void 0) return;
|
|
686
|
+
const delay = Math.min(RECONNECT_BASE_MS * 2 ** attempt, RECONNECT_MAX_MS);
|
|
687
|
+
attempt += 1;
|
|
688
|
+
timer = setTimeout(() => {
|
|
689
|
+
timer = void 0;
|
|
690
|
+
pump();
|
|
691
|
+
}, delay);
|
|
692
|
+
};
|
|
693
|
+
const pump = async () => {
|
|
694
|
+
if (lifetime.aborted) return;
|
|
695
|
+
try {
|
|
696
|
+
for await (const snapshot of remote.observe(lifetime)) {
|
|
697
|
+
attempt = 0;
|
|
698
|
+
latest = snapshotOf(snapshot);
|
|
699
|
+
notify();
|
|
700
|
+
}
|
|
701
|
+
} catch {}
|
|
702
|
+
if (lifetime.aborted) return;
|
|
703
|
+
latest = void 0;
|
|
704
|
+
notify();
|
|
705
|
+
schedule();
|
|
706
|
+
};
|
|
707
|
+
pump();
|
|
708
|
+
return {
|
|
709
|
+
onChange: (listener) => {
|
|
710
|
+
listeners.add(listener);
|
|
711
|
+
return () => {
|
|
712
|
+
listeners.delete(listener);
|
|
713
|
+
};
|
|
714
|
+
},
|
|
715
|
+
onSnapshot: (listener) => {
|
|
716
|
+
snapshotListeners.add(listener);
|
|
717
|
+
return () => {
|
|
718
|
+
snapshotListeners.delete(listener);
|
|
719
|
+
};
|
|
720
|
+
},
|
|
721
|
+
read: async (signal) => {
|
|
722
|
+
if (latest === void 0 && !lifetime.aborted) await new Promise((resolve) => {
|
|
723
|
+
const wake = () => {
|
|
724
|
+
waiters.delete(wake);
|
|
725
|
+
signal?.removeEventListener("abort", wake);
|
|
726
|
+
resolve();
|
|
727
|
+
};
|
|
728
|
+
waiters.add(wake);
|
|
729
|
+
signal?.addEventListener("abort", wake, { once: true });
|
|
730
|
+
});
|
|
731
|
+
signal?.throwIfAborted();
|
|
732
|
+
return latest;
|
|
1647
733
|
}
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
734
|
+
};
|
|
735
|
+
}
|
|
736
|
+
/**
|
|
737
|
+
* Build the direct transport over one mounted Remote namespace.
|
|
738
|
+
* @param remote - the generated `pauseSession` namespace.
|
|
739
|
+
* @param lifetime - client lifetime.
|
|
740
|
+
* @returns the source and action methods the controls use.
|
|
741
|
+
*/
|
|
742
|
+
function remoteTransport(remote, lifetime) {
|
|
743
|
+
const source = openRemoteSource(remote, lifetime);
|
|
744
|
+
const failed = () => ({
|
|
745
|
+
ok: false,
|
|
746
|
+
message: "pause action failed"
|
|
747
|
+
});
|
|
748
|
+
return {
|
|
749
|
+
source,
|
|
750
|
+
act: async (request, signal) => {
|
|
751
|
+
const answer = await remote.act(request, signal);
|
|
752
|
+
return answer.ok ? answer.value ?? failed() : failed();
|
|
753
|
+
},
|
|
754
|
+
triage: async (caller, signal) => {
|
|
755
|
+
const answer = await remote.triage({ caller }, signal);
|
|
756
|
+
return answer.ok ? answer.value : void 0;
|
|
757
|
+
},
|
|
758
|
+
decide: async (caller, target, decision, signal) => {
|
|
759
|
+
const answer = await remote.decide({
|
|
760
|
+
caller,
|
|
761
|
+
target,
|
|
762
|
+
decision
|
|
763
|
+
}, signal);
|
|
764
|
+
return answer.ok ? answer.value ?? failed() : failed();
|
|
765
|
+
}
|
|
766
|
+
};
|
|
767
|
+
}
|
|
768
|
+
//#endregion
|
|
769
|
+
//#region src/client/zod-lite.ts
|
|
770
|
+
/** Build one identity codec node. */
|
|
771
|
+
function node() {
|
|
772
|
+
return {
|
|
773
|
+
parse: (value) => value,
|
|
774
|
+
safeParse: (value) => ({
|
|
775
|
+
success: true,
|
|
776
|
+
data: value
|
|
777
|
+
}),
|
|
778
|
+
readonly() {
|
|
779
|
+
return this;
|
|
780
|
+
},
|
|
781
|
+
optional() {
|
|
782
|
+
return this;
|
|
783
|
+
},
|
|
784
|
+
describe() {
|
|
785
|
+
return this;
|
|
786
|
+
}
|
|
787
|
+
};
|
|
788
|
+
}
|
|
789
|
+
/** The `z` namespace the generated client contribution expects. */
|
|
790
|
+
const z = {
|
|
791
|
+
string: node,
|
|
792
|
+
number: node,
|
|
793
|
+
boolean: node,
|
|
794
|
+
undefined: node,
|
|
795
|
+
literal: node,
|
|
796
|
+
union: node,
|
|
797
|
+
array: node,
|
|
798
|
+
record: node,
|
|
799
|
+
object: node,
|
|
800
|
+
strictObject: node,
|
|
801
|
+
enum: node
|
|
802
|
+
};
|
|
803
|
+
//#endregion
|
|
804
|
+
//#region ../../deepseek-harness/packages/typert/protocol/lib/types/remote-info.js
|
|
805
|
+
/** Lazy assembly of generated Remote definitions using caller-supplied Zod; no runtime imports. */
|
|
806
|
+
/**
|
|
807
|
+
* Retain one generated definition per endpoint without constructing parsers or projections.
|
|
808
|
+
* The class instance builds its shared descriptor/method index on first access;
|
|
809
|
+
* input schemas and JSON Schema projections are cached only when requested.
|
|
810
|
+
* @param spec - compiler-produced method facts and Zod factories.
|
|
811
|
+
* @param zod - the generated artifact's Zod runtime for named-input parsers.
|
|
812
|
+
* @returns a lazy, mountable info instance with no registration or transport effects.
|
|
813
|
+
*/
|
|
814
|
+
function createTypertRemoteInfo(spec, zod) {
|
|
815
|
+
return new RemoteInfo(spec, zod);
|
|
816
|
+
}
|
|
817
|
+
var RemoteInfo = class {
|
|
818
|
+
spec;
|
|
819
|
+
zod;
|
|
820
|
+
index;
|
|
821
|
+
constructor(spec, zod) {
|
|
822
|
+
this.spec = spec;
|
|
823
|
+
this.zod = zod;
|
|
824
|
+
}
|
|
825
|
+
get package() {
|
|
826
|
+
return this.spec.package;
|
|
827
|
+
}
|
|
828
|
+
get descriptors() {
|
|
829
|
+
return this.resolve().descriptors;
|
|
830
|
+
}
|
|
831
|
+
get methods() {
|
|
832
|
+
return this.resolve().methods;
|
|
833
|
+
}
|
|
834
|
+
resolve() {
|
|
835
|
+
if (this.index !== void 0) return this.index;
|
|
836
|
+
const zod = this.zod;
|
|
837
|
+
const descriptors = [];
|
|
838
|
+
const methods = Object.create(null);
|
|
839
|
+
for (const [endpoint, definition] of Object.entries(this.spec.methods)) {
|
|
840
|
+
const separator = endpoint.indexOf("/");
|
|
841
|
+
const namespace = endpoint.slice(0, separator);
|
|
842
|
+
const method = endpoint.slice(separator + 1);
|
|
843
|
+
const parameters = definition.parameters.map((parameter) => ({
|
|
844
|
+
...parameter,
|
|
845
|
+
wire: parameter.wire ?? parameter.name
|
|
846
|
+
}));
|
|
847
|
+
const descriptor = {
|
|
848
|
+
...definition,
|
|
849
|
+
id: `${this.spec.package}#${endpoint}`,
|
|
850
|
+
namespace,
|
|
851
|
+
method,
|
|
852
|
+
parameters,
|
|
853
|
+
result: definition.result.description === void 0 ? definition.result : new ResultValue(definition.result)
|
|
854
|
+
};
|
|
855
|
+
if (definition.invocation.kind === "direct") {
|
|
856
|
+
const inputs = [];
|
|
857
|
+
methods[endpoint] = Object.assign(descriptor, {
|
|
858
|
+
input(options) {
|
|
859
|
+
const strict = options?.strict === true;
|
|
860
|
+
return inputs[strict ? 1 : 0] ??= new NamedInput(parameters, strict, zod);
|
|
861
|
+
},
|
|
862
|
+
invoke(remote, input, signal) {
|
|
863
|
+
const named = input;
|
|
864
|
+
const values = parameters.map((parameter) => named[parameter.wire]);
|
|
865
|
+
if (definition.cancellation !== void 0) values.push(signal);
|
|
866
|
+
const receiver = Reflect.get(remote, namespace);
|
|
867
|
+
return Reflect.get(receiver, method).apply(receiver, values);
|
|
868
|
+
}
|
|
869
|
+
});
|
|
870
|
+
}
|
|
871
|
+
descriptors.push(descriptor);
|
|
1652
872
|
}
|
|
1653
|
-
this.
|
|
873
|
+
return this.index = {
|
|
874
|
+
descriptors,
|
|
875
|
+
methods
|
|
876
|
+
};
|
|
1654
877
|
}
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
878
|
+
};
|
|
879
|
+
function optional(parameter) {
|
|
880
|
+
return parameter.source === "json" && parameter.acceptsUndefined === true;
|
|
881
|
+
}
|
|
882
|
+
var NamedInput = class {
|
|
883
|
+
parameters;
|
|
884
|
+
strict;
|
|
885
|
+
zod;
|
|
886
|
+
schema;
|
|
887
|
+
projection;
|
|
888
|
+
constructor(parameters, strict, zod) {
|
|
889
|
+
this.parameters = parameters;
|
|
890
|
+
this.strict = strict;
|
|
891
|
+
this.zod = zod;
|
|
1664
892
|
}
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
* so this holds only after a read that did not answer — the one debt no change
|
|
1668
|
-
* created, and the one place a retry is the difference between an empty panel
|
|
1669
|
-
* and the facts the host has. It is bounded by the burst like everything else.
|
|
1670
|
-
*/
|
|
1671
|
-
unansweredPanel() {
|
|
1672
|
-
const state = this.store.getSnapshot();
|
|
1673
|
-
return state.panelOpen && (state.triage === void 0 || state.triage.kind === "unavailable");
|
|
893
|
+
codec(parameter) {
|
|
894
|
+
return this.strict ? parameter.strictCodec : parameter.codec;
|
|
1674
895
|
}
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
896
|
+
create = () => this.schema ??= this.zod.strictObject(Object.fromEntries(this.parameters.map((parameter) => {
|
|
897
|
+
const value = this.codec(parameter).create();
|
|
898
|
+
return [parameter.wire, optional(parameter) ? value.optional() : value.nonoptional()];
|
|
899
|
+
})));
|
|
900
|
+
get jsonSchema() {
|
|
901
|
+
return this.project().jsonSchema;
|
|
902
|
+
}
|
|
903
|
+
get jsonSchemaError() {
|
|
904
|
+
return this.project().jsonSchemaError;
|
|
905
|
+
}
|
|
906
|
+
project() {
|
|
907
|
+
if (this.projection !== void 0) return this.projection;
|
|
908
|
+
const unavailable = this.parameters.find((parameter) => this.codec(parameter).jsonSchema === void 0);
|
|
909
|
+
if (unavailable !== void 0) return this.projection = { jsonSchemaError: this.codec(unavailable).jsonSchemaError ?? `No JSON Schema for parameter ${unavailable.wire}` };
|
|
910
|
+
return this.projection = { jsonSchema: {
|
|
911
|
+
type: "object",
|
|
912
|
+
properties: Object.fromEntries(this.parameters.map((parameter) => [parameter.wire, {
|
|
913
|
+
...this.codec(parameter).jsonSchema,
|
|
914
|
+
...parameter.description === void 0 ? {} : { description: parameter.description }
|
|
915
|
+
}])),
|
|
916
|
+
required: this.parameters.filter((parameter) => !optional(parameter)).map((parameter) => parameter.wire),
|
|
917
|
+
additionalProperties: false
|
|
918
|
+
} };
|
|
1682
919
|
}
|
|
1683
920
|
};
|
|
921
|
+
var ResultValue = class {
|
|
922
|
+
spec;
|
|
923
|
+
projection;
|
|
924
|
+
constructor(spec) {
|
|
925
|
+
this.spec = spec;
|
|
926
|
+
}
|
|
927
|
+
get mode() {
|
|
928
|
+
return this.spec.mode;
|
|
929
|
+
}
|
|
930
|
+
get typeSymbol() {
|
|
931
|
+
return this.spec.typeSymbol;
|
|
932
|
+
}
|
|
933
|
+
get create() {
|
|
934
|
+
return this.spec.create;
|
|
935
|
+
}
|
|
936
|
+
get decode() {
|
|
937
|
+
return this.spec.decode;
|
|
938
|
+
}
|
|
939
|
+
get encode() {
|
|
940
|
+
return this.spec.encode;
|
|
941
|
+
}
|
|
942
|
+
get description() {
|
|
943
|
+
return this.spec.description;
|
|
944
|
+
}
|
|
945
|
+
get jsonSchemaError() {
|
|
946
|
+
return this.spec.jsonSchemaError;
|
|
947
|
+
}
|
|
948
|
+
get jsonSchema() {
|
|
949
|
+
if (this.projection === void 0) {
|
|
950
|
+
const schema = this.spec.jsonSchema;
|
|
951
|
+
this.projection = { value: schema === void 0 ? void 0 : {
|
|
952
|
+
...schema,
|
|
953
|
+
description: this.spec.description
|
|
954
|
+
} };
|
|
955
|
+
}
|
|
956
|
+
return this.projection.value;
|
|
957
|
+
}
|
|
958
|
+
};
|
|
959
|
+
//#endregion
|
|
960
|
+
//#region lib/typert.remote-client.js
|
|
961
|
+
let dsh_pause_session_pauseSession_act_parameter_0$schema$value;
|
|
962
|
+
const dsh_pause_session_pauseSession_act_parameter_0$schema = () => dsh_pause_session_pauseSession_act_parameter_0$schema$value ??= z.object({
|
|
963
|
+
"action": z.union([
|
|
964
|
+
z.literal("pause"),
|
|
965
|
+
z.literal("resume"),
|
|
966
|
+
z.literal("pause-all"),
|
|
967
|
+
z.literal("resume-all"),
|
|
968
|
+
z.literal("cancel-all"),
|
|
969
|
+
z.literal("goal")
|
|
970
|
+
]).readonly(),
|
|
971
|
+
"sessionId": z.string().readonly().describe("The Session the control is addressed through.")
|
|
972
|
+
});
|
|
973
|
+
let dsh_pause_session_pauseSession_act_result$schema$value;
|
|
974
|
+
const dsh_pause_session_pauseSession_act_result$schema = () => dsh_pause_session_pauseSession_act_result$schema$value ??= z.object({
|
|
975
|
+
"ok": z.boolean().readonly(),
|
|
976
|
+
"message": z.string().readonly()
|
|
977
|
+
});
|
|
978
|
+
let dsh_pause_session_pauseSession_decide_parameter_0$schema$value;
|
|
979
|
+
const dsh_pause_session_pauseSession_decide_parameter_0$schema = () => dsh_pause_session_pauseSession_decide_parameter_0$schema$value ??= z.object({
|
|
980
|
+
"caller": z.string().readonly(),
|
|
981
|
+
"target": z.string().readonly(),
|
|
982
|
+
"decision": z.union([
|
|
983
|
+
z.literal("goal"),
|
|
984
|
+
z.literal("continue"),
|
|
985
|
+
z.literal("note"),
|
|
986
|
+
z.literal("review"),
|
|
987
|
+
z.literal("leave")
|
|
988
|
+
]).readonly()
|
|
989
|
+
});
|
|
990
|
+
let dsh_pause_session_pauseSession_decide_result$schema$value;
|
|
991
|
+
const dsh_pause_session_pauseSession_decide_result$schema = () => dsh_pause_session_pauseSession_decide_result$schema$value ??= z.object({
|
|
992
|
+
"ok": z.boolean().readonly(),
|
|
993
|
+
"message": z.string().readonly()
|
|
994
|
+
});
|
|
995
|
+
let dsh_pause_session_pauseSession_observe_result$schema$value;
|
|
996
|
+
const dsh_pause_session_pauseSession_observe_result$schema = () => dsh_pause_session_pauseSession_observe_result$schema$value ??= z.object({
|
|
997
|
+
"global": z.object({
|
|
998
|
+
"verdict": z.union([
|
|
999
|
+
z.literal("idle"),
|
|
1000
|
+
z.literal("waiting"),
|
|
1001
|
+
z.literal("safe")
|
|
1002
|
+
]).readonly(),
|
|
1003
|
+
"registered": z.number().readonly(),
|
|
1004
|
+
"safe": z.number().readonly(),
|
|
1005
|
+
"parkedAt": z.union([z.undefined(), z.string()]).readonly().optional(),
|
|
1006
|
+
"draining": z.array(z.string()).readonly(),
|
|
1007
|
+
"parking": z.number().readonly(),
|
|
1008
|
+
"stalled": z.array(z.string()).readonly(),
|
|
1009
|
+
"unresumable": z.array(z.string()).readonly(),
|
|
1010
|
+
"unreadTrees": z.array(z.string()).readonly(),
|
|
1011
|
+
"jobs": z.array(z.string()).readonly(),
|
|
1012
|
+
"blind": z.array(z.string()).readonly()
|
|
1013
|
+
}).readonly(),
|
|
1014
|
+
"sessions": z.record(z.string(), z.object({
|
|
1015
|
+
"phase": z.union([
|
|
1016
|
+
z.literal("safe"),
|
|
1017
|
+
z.literal("active"),
|
|
1018
|
+
z.literal("draining"),
|
|
1019
|
+
z.literal("parking"),
|
|
1020
|
+
z.literal("flush-failed")
|
|
1021
|
+
]).readonly(),
|
|
1022
|
+
"runningTools": z.array(z.string()).readonly(),
|
|
1023
|
+
"rearmGoal": z.union([
|
|
1024
|
+
z.undefined(),
|
|
1025
|
+
z.literal(false),
|
|
1026
|
+
z.literal(true)
|
|
1027
|
+
]).readonly().optional()
|
|
1028
|
+
})).readonly().readonly()
|
|
1029
|
+
});
|
|
1030
|
+
let dsh_pause_session_pauseSession_snapshot_result$schema$value;
|
|
1031
|
+
const dsh_pause_session_pauseSession_snapshot_result$schema = () => dsh_pause_session_pauseSession_snapshot_result$schema$value ??= z.object({
|
|
1032
|
+
"global": z.object({
|
|
1033
|
+
"verdict": z.union([
|
|
1034
|
+
z.literal("idle"),
|
|
1035
|
+
z.literal("waiting"),
|
|
1036
|
+
z.literal("safe")
|
|
1037
|
+
]).readonly(),
|
|
1038
|
+
"registered": z.number().readonly(),
|
|
1039
|
+
"safe": z.number().readonly(),
|
|
1040
|
+
"parkedAt": z.union([z.undefined(), z.string()]).readonly().optional(),
|
|
1041
|
+
"draining": z.array(z.string()).readonly(),
|
|
1042
|
+
"parking": z.number().readonly(),
|
|
1043
|
+
"stalled": z.array(z.string()).readonly(),
|
|
1044
|
+
"unresumable": z.array(z.string()).readonly(),
|
|
1045
|
+
"unreadTrees": z.array(z.string()).readonly(),
|
|
1046
|
+
"jobs": z.array(z.string()).readonly(),
|
|
1047
|
+
"blind": z.array(z.string()).readonly()
|
|
1048
|
+
}).readonly(),
|
|
1049
|
+
"sessions": z.record(z.string(), z.object({
|
|
1050
|
+
"phase": z.union([
|
|
1051
|
+
z.literal("safe"),
|
|
1052
|
+
z.literal("active"),
|
|
1053
|
+
z.literal("draining"),
|
|
1054
|
+
z.literal("parking"),
|
|
1055
|
+
z.literal("flush-failed")
|
|
1056
|
+
]).readonly(),
|
|
1057
|
+
"runningTools": z.array(z.string()).readonly(),
|
|
1058
|
+
"rearmGoal": z.union([
|
|
1059
|
+
z.undefined(),
|
|
1060
|
+
z.literal(false),
|
|
1061
|
+
z.literal(true)
|
|
1062
|
+
]).readonly().optional()
|
|
1063
|
+
})).readonly().readonly()
|
|
1064
|
+
});
|
|
1065
|
+
let dsh_pause_session_pauseSession_triage_parameter_0$schema$value;
|
|
1066
|
+
const dsh_pause_session_pauseSession_triage_parameter_0$schema = () => dsh_pause_session_pauseSession_triage_parameter_0$schema$value ??= z.object({ "caller": z.string().readonly() });
|
|
1067
|
+
let dsh_pause_session_pauseSession_triage_result$schema$value;
|
|
1068
|
+
const dsh_pause_session_pauseSession_triage_result$schema = () => dsh_pause_session_pauseSession_triage_result$schema$value ??= z.array(z.object({
|
|
1069
|
+
"sessionId": z.string().readonly(),
|
|
1070
|
+
"phase": z.union([
|
|
1071
|
+
z.literal("draining"),
|
|
1072
|
+
z.literal("parking"),
|
|
1073
|
+
z.literal("flush-failed")
|
|
1074
|
+
]).readonly(),
|
|
1075
|
+
"updatedAt": z.number().readonly(),
|
|
1076
|
+
"reading": z.union([z.literal("read"), z.literal("unreadable")]).readonly(),
|
|
1077
|
+
"lastEvent": z.union([z.undefined(), z.string()]).readonly().optional(),
|
|
1078
|
+
"turn": z.union([z.undefined(), z.number()]).readonly().optional(),
|
|
1079
|
+
"step": z.union([z.undefined(), z.number()]).readonly().optional(),
|
|
1080
|
+
"traces": z.array(z.object({
|
|
1081
|
+
"tool": z.string().readonly(),
|
|
1082
|
+
"callId": z.string().readonly()
|
|
1083
|
+
})).readonly(),
|
|
1084
|
+
"owed": z.boolean().readonly(),
|
|
1085
|
+
"wakeable": z.boolean().readonly(),
|
|
1086
|
+
"rearmGoal": z.boolean().readonly()
|
|
1087
|
+
}));
|
|
1088
|
+
let dsh_pause_session_pauseSession_act_parameter_0_strict$schema$value;
|
|
1089
|
+
const dsh_pause_session_pauseSession_act_parameter_0_strict$schema = () => dsh_pause_session_pauseSession_act_parameter_0_strict$schema$value ??= z.strictObject({
|
|
1090
|
+
"action": z.union([
|
|
1091
|
+
z.literal("pause"),
|
|
1092
|
+
z.literal("resume"),
|
|
1093
|
+
z.literal("pause-all"),
|
|
1094
|
+
z.literal("resume-all"),
|
|
1095
|
+
z.literal("cancel-all"),
|
|
1096
|
+
z.literal("goal")
|
|
1097
|
+
]).readonly(),
|
|
1098
|
+
"sessionId": z.string().readonly().describe("The Session the control is addressed through.")
|
|
1099
|
+
});
|
|
1100
|
+
let dsh_pause_session_pauseSession_decide_parameter_0_strict$schema$value;
|
|
1101
|
+
const dsh_pause_session_pauseSession_decide_parameter_0_strict$schema = () => dsh_pause_session_pauseSession_decide_parameter_0_strict$schema$value ??= z.strictObject({
|
|
1102
|
+
"caller": z.string().readonly(),
|
|
1103
|
+
"target": z.string().readonly(),
|
|
1104
|
+
"decision": z.union([
|
|
1105
|
+
z.literal("goal"),
|
|
1106
|
+
z.literal("continue"),
|
|
1107
|
+
z.literal("note"),
|
|
1108
|
+
z.literal("review"),
|
|
1109
|
+
z.literal("leave")
|
|
1110
|
+
]).readonly()
|
|
1111
|
+
});
|
|
1112
|
+
let dsh_pause_session_pauseSession_triage_parameter_0_strict$schema$value;
|
|
1113
|
+
const dsh_pause_session_pauseSession_triage_parameter_0_strict$schema = () => dsh_pause_session_pauseSession_triage_parameter_0_strict$schema$value ??= z.strictObject({ "caller": z.string().readonly() });
|
|
1114
|
+
const TYPERT_REMOTE = createTypertRemoteInfo({
|
|
1115
|
+
package: "dsh-pause-session",
|
|
1116
|
+
methods: {
|
|
1117
|
+
"pauseSession/act": {
|
|
1118
|
+
service: "pauseSession",
|
|
1119
|
+
description: "Pause, resume, press every tree, resume every clean park, or flip one Session's goal re-arm.",
|
|
1120
|
+
invocation: { kind: "direct" },
|
|
1121
|
+
parameters: [{
|
|
1122
|
+
name: "request",
|
|
1123
|
+
description: "the button action.",
|
|
1124
|
+
source: "json",
|
|
1125
|
+
codec: {
|
|
1126
|
+
mode: "strict",
|
|
1127
|
+
typeSymbol: "dsh-pause-session/types#PauseActionRequest",
|
|
1128
|
+
create: dsh_pause_session_pauseSession_act_parameter_0$schema,
|
|
1129
|
+
jsonSchema: {
|
|
1130
|
+
"type": "object",
|
|
1131
|
+
"properties": {
|
|
1132
|
+
"action": {
|
|
1133
|
+
"type": "string",
|
|
1134
|
+
"enum": [
|
|
1135
|
+
"pause",
|
|
1136
|
+
"resume",
|
|
1137
|
+
"pause-all",
|
|
1138
|
+
"resume-all",
|
|
1139
|
+
"cancel-all",
|
|
1140
|
+
"goal"
|
|
1141
|
+
]
|
|
1142
|
+
},
|
|
1143
|
+
"sessionId": {
|
|
1144
|
+
"type": "string",
|
|
1145
|
+
"description": "The Session the control is addressed through."
|
|
1146
|
+
}
|
|
1147
|
+
},
|
|
1148
|
+
"required": ["action", "sessionId"],
|
|
1149
|
+
"additionalProperties": true
|
|
1150
|
+
}
|
|
1151
|
+
},
|
|
1152
|
+
strictCodec: {
|
|
1153
|
+
mode: "strict",
|
|
1154
|
+
typeSymbol: "dsh-pause-session/types#PauseActionRequest",
|
|
1155
|
+
create: dsh_pause_session_pauseSession_act_parameter_0_strict$schema,
|
|
1156
|
+
jsonSchema: {
|
|
1157
|
+
"type": "object",
|
|
1158
|
+
"properties": {
|
|
1159
|
+
"action": {
|
|
1160
|
+
"type": "string",
|
|
1161
|
+
"enum": [
|
|
1162
|
+
"pause",
|
|
1163
|
+
"resume",
|
|
1164
|
+
"pause-all",
|
|
1165
|
+
"resume-all",
|
|
1166
|
+
"cancel-all",
|
|
1167
|
+
"goal"
|
|
1168
|
+
]
|
|
1169
|
+
},
|
|
1170
|
+
"sessionId": {
|
|
1171
|
+
"type": "string",
|
|
1172
|
+
"description": "The Session the control is addressed through."
|
|
1173
|
+
}
|
|
1174
|
+
},
|
|
1175
|
+
"required": ["action", "sessionId"],
|
|
1176
|
+
"additionalProperties": false
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
}],
|
|
1180
|
+
cancellation: { parameter: "signal" },
|
|
1181
|
+
result: {
|
|
1182
|
+
mode: "strict",
|
|
1183
|
+
typeSymbol: "dsh-pause-session/types#PauseActionResultWire",
|
|
1184
|
+
create: dsh_pause_session_pauseSession_act_result$schema,
|
|
1185
|
+
description: "the user-facing outcome.",
|
|
1186
|
+
jsonSchema: {
|
|
1187
|
+
"type": "object",
|
|
1188
|
+
"properties": {
|
|
1189
|
+
"ok": { "type": "boolean" },
|
|
1190
|
+
"message": { "type": "string" }
|
|
1191
|
+
},
|
|
1192
|
+
"required": ["ok", "message"],
|
|
1193
|
+
"additionalProperties": true
|
|
1194
|
+
}
|
|
1195
|
+
},
|
|
1196
|
+
sourceLocation: {
|
|
1197
|
+
"file": "packages/dsh-pause-session/src/remote.ts",
|
|
1198
|
+
"line": 239,
|
|
1199
|
+
"column": 9
|
|
1200
|
+
}
|
|
1201
|
+
},
|
|
1202
|
+
"pauseSession/decide": {
|
|
1203
|
+
service: "pauseSession",
|
|
1204
|
+
description: "Take one decision on one listed Session.",
|
|
1205
|
+
invocation: { kind: "direct" },
|
|
1206
|
+
parameters: [{
|
|
1207
|
+
name: "request",
|
|
1208
|
+
description: "the caller, target, and decision.",
|
|
1209
|
+
source: "json",
|
|
1210
|
+
codec: {
|
|
1211
|
+
mode: "strict",
|
|
1212
|
+
typeSymbol: "dsh-pause-session/types#PauseDecisionRequest",
|
|
1213
|
+
create: dsh_pause_session_pauseSession_decide_parameter_0$schema,
|
|
1214
|
+
jsonSchema: {
|
|
1215
|
+
"type": "object",
|
|
1216
|
+
"properties": {
|
|
1217
|
+
"caller": { "type": "string" },
|
|
1218
|
+
"target": { "type": "string" },
|
|
1219
|
+
"decision": {
|
|
1220
|
+
"type": "string",
|
|
1221
|
+
"enum": [
|
|
1222
|
+
"goal",
|
|
1223
|
+
"continue",
|
|
1224
|
+
"note",
|
|
1225
|
+
"review",
|
|
1226
|
+
"leave"
|
|
1227
|
+
]
|
|
1228
|
+
}
|
|
1229
|
+
},
|
|
1230
|
+
"required": [
|
|
1231
|
+
"caller",
|
|
1232
|
+
"target",
|
|
1233
|
+
"decision"
|
|
1234
|
+
],
|
|
1235
|
+
"additionalProperties": true
|
|
1236
|
+
}
|
|
1237
|
+
},
|
|
1238
|
+
strictCodec: {
|
|
1239
|
+
mode: "strict",
|
|
1240
|
+
typeSymbol: "dsh-pause-session/types#PauseDecisionRequest",
|
|
1241
|
+
create: dsh_pause_session_pauseSession_decide_parameter_0_strict$schema,
|
|
1242
|
+
jsonSchema: {
|
|
1243
|
+
"type": "object",
|
|
1244
|
+
"properties": {
|
|
1245
|
+
"caller": { "type": "string" },
|
|
1246
|
+
"target": { "type": "string" },
|
|
1247
|
+
"decision": {
|
|
1248
|
+
"type": "string",
|
|
1249
|
+
"enum": [
|
|
1250
|
+
"goal",
|
|
1251
|
+
"continue",
|
|
1252
|
+
"note",
|
|
1253
|
+
"review",
|
|
1254
|
+
"leave"
|
|
1255
|
+
]
|
|
1256
|
+
}
|
|
1257
|
+
},
|
|
1258
|
+
"required": [
|
|
1259
|
+
"caller",
|
|
1260
|
+
"target",
|
|
1261
|
+
"decision"
|
|
1262
|
+
],
|
|
1263
|
+
"additionalProperties": false
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
}],
|
|
1267
|
+
cancellation: { parameter: "signal" },
|
|
1268
|
+
result: {
|
|
1269
|
+
mode: "strict",
|
|
1270
|
+
typeSymbol: "dsh-pause-session/types#PauseActionResultWire",
|
|
1271
|
+
create: dsh_pause_session_pauseSession_decide_result$schema,
|
|
1272
|
+
description: "the user-facing outcome.",
|
|
1273
|
+
jsonSchema: {
|
|
1274
|
+
"type": "object",
|
|
1275
|
+
"properties": {
|
|
1276
|
+
"ok": { "type": "boolean" },
|
|
1277
|
+
"message": { "type": "string" }
|
|
1278
|
+
},
|
|
1279
|
+
"required": ["ok", "message"],
|
|
1280
|
+
"additionalProperties": true
|
|
1281
|
+
}
|
|
1282
|
+
},
|
|
1283
|
+
sourceLocation: {
|
|
1284
|
+
"file": "packages/dsh-pause-session/src/remote.ts",
|
|
1285
|
+
"line": 264,
|
|
1286
|
+
"column": 9
|
|
1287
|
+
}
|
|
1288
|
+
},
|
|
1289
|
+
"pauseSession/observe": {
|
|
1290
|
+
service: "pauseSession",
|
|
1291
|
+
description: "Live status: the opening snapshot, then one new snapshot whenever the host reports a change.",
|
|
1292
|
+
mode: "stream",
|
|
1293
|
+
invocation: { kind: "direct" },
|
|
1294
|
+
parameters: [],
|
|
1295
|
+
cancellation: { parameter: "signal" },
|
|
1296
|
+
result: {
|
|
1297
|
+
mode: "strict",
|
|
1298
|
+
typeSymbol: "dsh-pause-session/types#PauseSnapshotWire",
|
|
1299
|
+
create: dsh_pause_session_pauseSession_observe_result$schema,
|
|
1300
|
+
description: "the snapshot stream.",
|
|
1301
|
+
jsonSchemaError: "typert tool schema: selected dictionaries require string keys and unconstrained JSON values"
|
|
1302
|
+
},
|
|
1303
|
+
sourceLocation: {
|
|
1304
|
+
"file": "packages/dsh-pause-session/src/remote.ts",
|
|
1305
|
+
"line": 201,
|
|
1306
|
+
"column": 10
|
|
1307
|
+
}
|
|
1308
|
+
},
|
|
1309
|
+
"pauseSession/snapshot": {
|
|
1310
|
+
service: "pauseSession",
|
|
1311
|
+
description: "One exact snapshot, used by tests and by a reconnect that cannot wait for the first stream item.",
|
|
1312
|
+
invocation: { kind: "direct" },
|
|
1313
|
+
parameters: [],
|
|
1314
|
+
cancellation: { parameter: "signal" },
|
|
1315
|
+
result: {
|
|
1316
|
+
mode: "strict",
|
|
1317
|
+
typeSymbol: "dsh-pause-session/types#PauseSnapshotWire",
|
|
1318
|
+
create: dsh_pause_session_pauseSession_snapshot_result$schema,
|
|
1319
|
+
description: "the current aggregate and session states.",
|
|
1320
|
+
jsonSchemaError: "typert tool schema: selected dictionaries require string keys and unconstrained JSON values"
|
|
1321
|
+
},
|
|
1322
|
+
sourceLocation: {
|
|
1323
|
+
"file": "packages/dsh-pause-session/src/remote.ts",
|
|
1324
|
+
"line": 175,
|
|
1325
|
+
"column": 9
|
|
1326
|
+
}
|
|
1327
|
+
},
|
|
1328
|
+
"pauseSession/triage": {
|
|
1329
|
+
service: "pauseSession",
|
|
1330
|
+
description: "Read the morning decision list.",
|
|
1331
|
+
invocation: { kind: "direct" },
|
|
1332
|
+
parameters: [{
|
|
1333
|
+
name: "request",
|
|
1334
|
+
description: "the calling Session whose list is read.",
|
|
1335
|
+
source: "json",
|
|
1336
|
+
codec: {
|
|
1337
|
+
mode: "strict",
|
|
1338
|
+
typeSymbol: "dsh-pause-session/types#PauseTriageRequest",
|
|
1339
|
+
create: dsh_pause_session_pauseSession_triage_parameter_0$schema,
|
|
1340
|
+
jsonSchema: {
|
|
1341
|
+
"type": "object",
|
|
1342
|
+
"properties": { "caller": { "type": "string" } },
|
|
1343
|
+
"required": ["caller"],
|
|
1344
|
+
"additionalProperties": true
|
|
1345
|
+
}
|
|
1346
|
+
},
|
|
1347
|
+
strictCodec: {
|
|
1348
|
+
mode: "strict",
|
|
1349
|
+
typeSymbol: "dsh-pause-session/types#PauseTriageRequest",
|
|
1350
|
+
create: dsh_pause_session_pauseSession_triage_parameter_0_strict$schema,
|
|
1351
|
+
jsonSchema: {
|
|
1352
|
+
"type": "object",
|
|
1353
|
+
"properties": { "caller": { "type": "string" } },
|
|
1354
|
+
"required": ["caller"],
|
|
1355
|
+
"additionalProperties": false
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
}],
|
|
1359
|
+
cancellation: { parameter: "signal" },
|
|
1360
|
+
result: {
|
|
1361
|
+
mode: "strict",
|
|
1362
|
+
typeSymbol: "dsh-pause-session#pauseSession/triage:result",
|
|
1363
|
+
create: dsh_pause_session_pauseSession_triage_result$schema,
|
|
1364
|
+
description: "the listed rows.",
|
|
1365
|
+
jsonSchema: {
|
|
1366
|
+
"type": "array",
|
|
1367
|
+
"items": {
|
|
1368
|
+
"type": "object",
|
|
1369
|
+
"properties": {
|
|
1370
|
+
"sessionId": { "type": "string" },
|
|
1371
|
+
"phase": {
|
|
1372
|
+
"type": "string",
|
|
1373
|
+
"enum": [
|
|
1374
|
+
"draining",
|
|
1375
|
+
"parking",
|
|
1376
|
+
"flush-failed"
|
|
1377
|
+
]
|
|
1378
|
+
},
|
|
1379
|
+
"updatedAt": { "type": "number" },
|
|
1380
|
+
"reading": {
|
|
1381
|
+
"type": "string",
|
|
1382
|
+
"enum": ["read", "unreadable"]
|
|
1383
|
+
},
|
|
1384
|
+
"lastEvent": { "type": "string" },
|
|
1385
|
+
"turn": { "type": "number" },
|
|
1386
|
+
"step": { "type": "number" },
|
|
1387
|
+
"traces": {
|
|
1388
|
+
"type": "array",
|
|
1389
|
+
"items": {
|
|
1390
|
+
"type": "object",
|
|
1391
|
+
"properties": {
|
|
1392
|
+
"tool": { "type": "string" },
|
|
1393
|
+
"callId": { "type": "string" }
|
|
1394
|
+
},
|
|
1395
|
+
"required": ["tool", "callId"],
|
|
1396
|
+
"additionalProperties": true
|
|
1397
|
+
}
|
|
1398
|
+
},
|
|
1399
|
+
"owed": { "type": "boolean" },
|
|
1400
|
+
"wakeable": { "type": "boolean" },
|
|
1401
|
+
"rearmGoal": { "type": "boolean" }
|
|
1402
|
+
},
|
|
1403
|
+
"required": [
|
|
1404
|
+
"sessionId",
|
|
1405
|
+
"phase",
|
|
1406
|
+
"updatedAt",
|
|
1407
|
+
"reading",
|
|
1408
|
+
"traces",
|
|
1409
|
+
"owed",
|
|
1410
|
+
"wakeable",
|
|
1411
|
+
"rearmGoal"
|
|
1412
|
+
],
|
|
1413
|
+
"additionalProperties": true
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
},
|
|
1417
|
+
sourceLocation: {
|
|
1418
|
+
"file": "packages/dsh-pause-session/src/remote.ts",
|
|
1419
|
+
"line": 252,
|
|
1420
|
+
"column": 9
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
}, z);
|
|
1684
1425
|
//#endregion
|
|
1685
1426
|
//#region lib/types/client/icons.js
|
|
1686
1427
|
/** 16px stroke icon (currentColor): the two bars of a pause. */
|
|
@@ -1784,7 +1525,7 @@ window.__ModuleLoader__.load({
|
|
|
1784
1525
|
}
|
|
1785
1526
|
//#endregion
|
|
1786
1527
|
//#region \0dsh-css:src/client/button.module.css.mjs
|
|
1787
|
-
const css = ".FCO1_q_dsp{align-items:center;display:inline-flex;position:relative}.FCO1_q_dspBtn{width:26px;height:26px;color:var(--dsw-alias-label-secondary);cursor:pointer;user-select:none;background:0 0;border:1px solid #0000;border-radius:6px;justify-content:center;align-items:center;padding:0;transition:color .12s,background .12s,border-color .12s;display:inline-flex}.FCO1_q_dspBtn:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-border-l1)}.FCO1_q_dspBtn:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:1px}.FCO1_q_dspBtn:disabled{cursor:default;opacity:.5}.FCO1_q_dspBtn:disabled:hover{color:var(--dsw-alias-label-secondary);background:0 0;border-color:#0000}.FCO1_q_dspGoal{width:22px;height:22px;color:var(--dsw-alias-label-tertiary);cursor:pointer;user-select:none;background:0 0;border:1px solid #0000;border-radius:6px;justify-content:center;align-items:center;margin-left:2px;padding:0;transition:color .12s,background .12s,border-color .12s;display:inline-flex}.FCO1_q_dspGoal[aria-pressed=true]{color:var(--dsw-alias-label-secondary)}.FCO1_q_dspGoal:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-border-l1)}.FCO1_q_dspGoal:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:1px}@media (prefers-reduced-motion:reduce){.FCO1_q_dspGoal{transition:none}}.FCO1_q_dspState{color:var(--dsw-alias-label-secondary);white-space:nowrap;user-select:none;padding-left:6px;font-size:12px;line-height:18px}
|
|
1528
|
+
const css = ".FCO1_q_dsp{align-items:center;display:inline-flex;position:relative}.FCO1_q_dspBtn{width:26px;height:26px;color:var(--dsw-alias-label-secondary);cursor:pointer;user-select:none;background:0 0;border:1px solid #0000;border-radius:6px;justify-content:center;align-items:center;padding:0;transition:color .12s,background .12s,border-color .12s;display:inline-flex}.FCO1_q_dspBtn:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-border-l1)}.FCO1_q_dspBtn:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:1px}.FCO1_q_dspBtn:disabled{cursor:default;opacity:.5}.FCO1_q_dspBtn:disabled:hover{color:var(--dsw-alias-label-secondary);background:0 0;border-color:#0000}.FCO1_q_dspGoal{width:22px;height:22px;color:var(--dsw-alias-label-tertiary);cursor:pointer;user-select:none;background:0 0;border:1px solid #0000;border-radius:6px;justify-content:center;align-items:center;margin-left:2px;padding:0;transition:color .12s,background .12s,border-color .12s;display:inline-flex}.FCO1_q_dspGoal[aria-pressed=true]{color:var(--dsw-alias-label-secondary)}.FCO1_q_dspGoal:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-border-l1)}.FCO1_q_dspGoal:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:1px}@media (prefers-reduced-motion:reduce){.FCO1_q_dspGoal{transition:none}}.FCO1_q_dspState{color:var(--dsw-alias-label-secondary);white-space:nowrap;user-select:none;padding-left:6px;font-size:12px;line-height:18px}@media (prefers-reduced-motion:reduce){.FCO1_q_dspBtn{transition:none}}.FCO1_q_dspPill{align-items:center;display:inline-flex;position:relative}.FCO1_q_dspPillBtn{max-width:min(38ch,100vw - 48px);color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);cursor:pointer;user-select:none;border-radius:999px;align-items:center;gap:6px;padding:5px 10px;font-size:12px;line-height:18px;transition:color .12s,background .12s,border-color .12s;display:inline-flex;box-shadow:0 4px 12px #00000024}.FCO1_q_dspPillBtn:hover{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-border-l2)}.FCO1_q_dspPillIcon{justify-content:center;width:28px;height:28px;padding:0}.FCO1_q_dspPillBtn:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:1px}.FCO1_q_dspPillBtn:disabled{cursor:default;opacity:.5}.FCO1_q_dspPillText{white-space:nowrap;text-overflow:ellipsis;overflow:hidden}.FCO1_q_dspPillDetail{font-variant-numeric:tabular-nums;color:var(--dsw-alias-label-tertiary)}.FCO1_q_dspPillWarn{color:var(--dsw-alias-state-warn-primary,var(--dsw-alias-label-primary));font-weight:600}@media (prefers-reduced-motion:reduce){.FCO1_q_dspPillBtn{transition:none}}.FCO1_q_dspPillTriage{color:var(--dsw-alias-state-warn-primary,var(--dsw-alias-label-primary));background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);cursor:pointer;user-select:none;border-radius:999px;align-items:center;gap:2px;margin-right:4px;padding:4px 8px;font-size:12px;line-height:18px;display:inline-flex;box-shadow:0 4px 12px #00000024}.FCO1_q_dspPillTriage:hover{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-border-l2)}.FCO1_q_dspPillTriage:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:1px}.FCO1_q_dspPillPanel{z-index:30;text-align:left;background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;flex-direction:column;gap:8px;width:min(52ch,100vw - 48px);max-height:min(60vh,420px);padding:10px 12px;display:flex;position:absolute;top:calc(100% + 6px);right:0;overflow-y:auto;box-shadow:0 8px 24px #00000038}.FCO1_q_dspPillPanelHead{justify-content:space-between;align-items:center;gap:8px;font-weight:600;display:flex}.FCO1_q_dspPillError{color:var(--dsw-alias-state-error-primary);margin:0 0 8px;font-size:12px;line-height:18px}.FCO1_q_dspPillPanelNote{color:var(--dsw-alias-label-secondary);margin:0;font-size:12px;line-height:18px}.FCO1_q_dspPillRows{flex-direction:column;gap:8px;margin:0;padding:0;list-style:none;display:flex}.FCO1_q_dspPillRow{border:1px solid var(--dsw-alias-border-l1);border-radius:6px;flex-direction:column;gap:4px;padding:8px;display:flex}.FCO1_q_dspPillRowHead{justify-content:space-between;align-items:baseline;gap:8px;display:flex}.FCO1_q_dspPillRowId{word-break:break-all;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px}.FCO1_q_dspPillRowPhase{color:var(--dsw-alias-state-warn-primary,var(--dsw-alias-label-primary));flex:none;font-size:12px}.FCO1_q_dspPillRowFacts{color:var(--dsw-alias-label-secondary);flex-direction:column;gap:2px;font-size:12px;line-height:18px;display:flex}.FCO1_q_dspPillRowActions{flex-wrap:wrap;gap:6px;padding-top:2px;display:flex}.FCO1_q_dspPillAction{color:var(--dsw-alias-label-secondary);border:1px solid var(--dsw-alias-border-l1);cursor:pointer;user-select:none;background:0 0;border-radius:4px;padding:3px 8px;font-size:12px;line-height:16px}.FCO1_q_dspPillAction:hover{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-border-l2)}.FCO1_q_dspPillAction:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:1px}";
|
|
1788
1529
|
const tagId = "dsh-pause-session/button.module.css";
|
|
1789
1530
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
1790
1531
|
const tag = document.createElement("style");
|
|
@@ -1801,6 +1542,7 @@ window.__ModuleLoader__.load({
|
|
|
1801
1542
|
"dspPillAction": "FCO1_q_dspPillAction",
|
|
1802
1543
|
"dspPillBtn": "FCO1_q_dspPillBtn",
|
|
1803
1544
|
"dspPillDetail": "FCO1_q_dspPillDetail",
|
|
1545
|
+
"dspPillError": "FCO1_q_dspPillError",
|
|
1804
1546
|
"dspPillIcon": "FCO1_q_dspPillIcon",
|
|
1805
1547
|
"dspPillPanel": "FCO1_q_dspPillPanel",
|
|
1806
1548
|
"dspPillPanelHead": "FCO1_q_dspPillPanelHead",
|
|
@@ -1815,8 +1557,7 @@ window.__ModuleLoader__.load({
|
|
|
1815
1557
|
"dspPillText": "FCO1_q_dspPillText",
|
|
1816
1558
|
"dspPillTriage": "FCO1_q_dspPillTriage",
|
|
1817
1559
|
"dspPillWarn": "FCO1_q_dspPillWarn",
|
|
1818
|
-
"dspState": "FCO1_q_dspState"
|
|
1819
|
-
"dspTip": "FCO1_q_dspTip"
|
|
1560
|
+
"dspState": "FCO1_q_dspState"
|
|
1820
1561
|
};
|
|
1821
1562
|
//#endregion
|
|
1822
1563
|
//#region lib/types/client/pill.js
|
|
@@ -1832,11 +1573,11 @@ window.__ModuleLoader__.load({
|
|
|
1832
1573
|
* above the whole frame. The decision list hangs from the control into the
|
|
1833
1574
|
* conversation area below.
|
|
1834
1575
|
*
|
|
1835
|
-
* The pill reads the
|
|
1836
|
-
*
|
|
1837
|
-
* than a state it would have to invent. The decision list is
|
|
1838
|
-
* part of that first read
|
|
1839
|
-
*
|
|
1576
|
+
* The pill reads the native status stream through `./global.ts`. With no
|
|
1577
|
+
* answer, or with no session to address an action through, it renders nothing
|
|
1578
|
+
* rather than a state it would have to invent. The decision list is
|
|
1579
|
+
* deliberately NOT part of that first read; the panel asks for its own rows
|
|
1580
|
+
* when it opens.
|
|
1840
1581
|
* @module dsh-pause-session/client/pill
|
|
1841
1582
|
*/
|
|
1842
1583
|
/** One listed session's row, with the four decisions. */
|
|
@@ -1865,7 +1606,7 @@ window.__ModuleLoader__.load({
|
|
|
1865
1606
|
}),
|
|
1866
1607
|
(0, react_jsx_runtime.jsxs)("div", {
|
|
1867
1608
|
className: button_module_css_default.dspPillRowActions,
|
|
1868
|
-
children: [
|
|
1609
|
+
children: [triageActionsFor(row).map((action) => (0, react_jsx_runtime.jsx)("button", {
|
|
1869
1610
|
type: "button",
|
|
1870
1611
|
className: button_module_css_default.dspPillAction,
|
|
1871
1612
|
"aria-label": t(action.ariaKey),
|
|
@@ -1899,8 +1640,10 @@ window.__ModuleLoader__.load({
|
|
|
1899
1640
|
const reading = props.usePauseGlobal((state) => state.global);
|
|
1900
1641
|
const triage = props.usePauseGlobal((state) => state.triage);
|
|
1901
1642
|
const open = props.usePauseGlobal((state) => state.panelOpen);
|
|
1643
|
+
const error = props.usePauseGlobal((state) => state.error);
|
|
1644
|
+
const sessions = props.usePauseGlobal((state) => state.sessions);
|
|
1902
1645
|
const address = useSessions(addressingSession);
|
|
1903
|
-
const view = globalPauseView(reading);
|
|
1646
|
+
const view = globalPauseView(reading, sessions);
|
|
1904
1647
|
const list = triageView(triage);
|
|
1905
1648
|
const visible = showsPauseControl(useSession((snapshot) => snapshot.subagent));
|
|
1906
1649
|
(0, react.useEffect)(() => {
|
|
@@ -1910,7 +1653,8 @@ window.__ModuleLoader__.load({
|
|
|
1910
1653
|
address,
|
|
1911
1654
|
visible
|
|
1912
1655
|
]);
|
|
1913
|
-
if (!visible || !view.visible
|
|
1656
|
+
if (!visible || !view.visible) return null;
|
|
1657
|
+
const actionable = view.enabled && address !== void 0;
|
|
1914
1658
|
const tip = view.caveats.map((caveat) => caveat.names.length === 0 ? t(caveat.key) : `${t(caveat.key)} (${caveat.names.join(", ")})`).join("\n");
|
|
1915
1659
|
return (0, react_jsx_runtime.jsxs)("span", {
|
|
1916
1660
|
className: button_module_css_default.dspPill,
|
|
@@ -1919,34 +1663,49 @@ window.__ModuleLoader__.load({
|
|
|
1919
1663
|
className: button_module_css_default.dspPillPanel,
|
|
1920
1664
|
role: "dialog",
|
|
1921
1665
|
"aria-label": t("triage.title"),
|
|
1922
|
-
children: [
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1666
|
+
children: [
|
|
1667
|
+
(0, react_jsx_runtime.jsxs)("div", {
|
|
1668
|
+
className: button_module_css_default.dspPillPanelHead,
|
|
1669
|
+
children: [(0, react_jsx_runtime.jsx)("span", {
|
|
1670
|
+
className: button_module_css_default.dspPillText,
|
|
1671
|
+
children: t("triage.title")
|
|
1672
|
+
}), (0, react_jsx_runtime.jsx)("button", {
|
|
1673
|
+
type: "button",
|
|
1674
|
+
className: button_module_css_default.dspPillAction,
|
|
1675
|
+
"aria-label": t("triage.refresh.aria"),
|
|
1676
|
+
onClick: () => {
|
|
1677
|
+
setPanel(true);
|
|
1678
|
+
},
|
|
1679
|
+
children: t("triage.refresh.aria")
|
|
1680
|
+
})]
|
|
1681
|
+
}),
|
|
1682
|
+
error === void 0 ? null : (0, react_jsx_runtime.jsx)("p", {
|
|
1683
|
+
className: button_module_css_default.dspPillError,
|
|
1684
|
+
role: "status",
|
|
1685
|
+
children: error
|
|
1686
|
+
}),
|
|
1687
|
+
view.running === void 0 ? null : (0, react_jsx_runtime.jsx)("p", {
|
|
1688
|
+
className: button_module_css_default.dspPillPanelNote,
|
|
1689
|
+
children: view.running
|
|
1690
|
+
}),
|
|
1691
|
+
!list.visible ? (0, react_jsx_runtime.jsx)("p", {
|
|
1692
|
+
className: button_module_css_default.dspPillPanelNote,
|
|
1693
|
+
children: t("triage.reading")
|
|
1694
|
+
}) : list.unavailable ? (0, react_jsx_runtime.jsx)("p", {
|
|
1695
|
+
className: button_module_css_default.dspPillPanelNote,
|
|
1696
|
+
children: t("triage.unavailable")
|
|
1697
|
+
}) : list.rows.length === 0 ? (0, react_jsx_runtime.jsx)("p", {
|
|
1698
|
+
className: button_module_css_default.dspPillPanelNote,
|
|
1699
|
+
children: t("triage.empty")
|
|
1700
|
+
}) : (0, react_jsx_runtime.jsx)("ul", {
|
|
1701
|
+
className: button_module_css_default.dspPillRows,
|
|
1702
|
+
children: list.rows.map((row) => (0, react_jsx_runtime.jsx)(TriageSessionRow, {
|
|
1703
|
+
row,
|
|
1704
|
+
decide,
|
|
1705
|
+
t
|
|
1706
|
+
}, row.sessionId))
|
|
1707
|
+
})
|
|
1708
|
+
]
|
|
1950
1709
|
}),
|
|
1951
1710
|
view.unclean === 0 ? null : (0, react_jsx_runtime.jsxs)("button", {
|
|
1952
1711
|
type: "button",
|
|
@@ -1966,14 +1725,14 @@ window.__ModuleLoader__.load({
|
|
|
1966
1725
|
type: "button",
|
|
1967
1726
|
className: view.labelKey === void 0 && view.detail.length === 0 ? `${button_module_css_default.dspPillBtn} ${button_module_css_default.dspPillIcon}` : button_module_css_default.dspPillBtn,
|
|
1968
1727
|
"aria-label": t(view.ariaKey),
|
|
1969
|
-
"aria-pressed": view.action === "resume-all",
|
|
1970
|
-
disabled: !
|
|
1728
|
+
"aria-pressed": view.action === "resume-all" || view.action === "cancel-all",
|
|
1729
|
+
disabled: !actionable,
|
|
1971
1730
|
...tip.length === 0 ? {} : { title: tip },
|
|
1972
1731
|
onClick: () => {
|
|
1973
|
-
toggle(address);
|
|
1732
|
+
if (address !== void 0) toggle(address);
|
|
1974
1733
|
},
|
|
1975
1734
|
children: [
|
|
1976
|
-
view.action === "resume-all" ? (0, react_jsx_runtime.jsx)(ResumeIcon, {}) : (0, react_jsx_runtime.jsx)(PauseIcon, {}),
|
|
1735
|
+
view.action === "resume-all" || view.action === "cancel-all" ? (0, react_jsx_runtime.jsx)(ResumeIcon, {}) : (0, react_jsx_runtime.jsx)(PauseIcon, {}),
|
|
1977
1736
|
view.labelKey === void 0 ? null : (0, react_jsx_runtime.jsx)("span", {
|
|
1978
1737
|
className: button_module_css_default.dspPillText,
|
|
1979
1738
|
children: t(view.labelKey)
|
|
@@ -1997,14 +1756,11 @@ window.__ModuleLoader__.load({
|
|
|
1997
1756
|
/** Simplified Chinese dictionary (the key-set source of truth). */
|
|
1998
1757
|
const zh = {
|
|
1999
1758
|
"pause.aria": "暂停这个会话及其子代理:下一条模型请求不会发出",
|
|
2000
|
-
"pause.tip": "暂停这个会话及其子代理",
|
|
2001
|
-
"resume.aria": "恢复这个会话及其子代理",
|
|
2002
1759
|
"resume.tip": "恢复这个会话及其子代理",
|
|
2003
1760
|
"draining.aria": "正在暂停:还有工具在运行,工具运行完之后、下一条模型请求发出之前,会话会停住;按下可取消这次暂停",
|
|
2004
1761
|
"parking.aria": "正在暂停:正在保存待发输入;按下可取消这次暂停",
|
|
2005
1762
|
"safe.aria": "已暂停:待发输入已保存;按下可恢复",
|
|
2006
1763
|
"flushFailed.aria": "已暂停,但保存待发输入失败;按下可恢复",
|
|
2007
|
-
"state.paused": "已暂停",
|
|
2008
1764
|
"state.draining": "正在暂停",
|
|
2009
1765
|
"state.parking": "正在保存",
|
|
2010
1766
|
"state.safe": "已暂停",
|
|
@@ -2020,10 +1776,13 @@ window.__ModuleLoader__.load({
|
|
|
2020
1776
|
"all.resume.aria": "恢复所有已暂停的会话",
|
|
2021
1777
|
"all.idle": "暂停所有会话",
|
|
2022
1778
|
"all.finishing": "正在暂停",
|
|
1779
|
+
"all.cancel": "取消这次暂停",
|
|
1780
|
+
"all.cancel.aria": "取消这次暂停并恢复会话",
|
|
2023
1781
|
"all.unclean": "有会话未暂停",
|
|
2024
1782
|
"all.unclean.aria": "有会话未暂停,不能在这里恢复;打开清单逐行处理",
|
|
2025
|
-
"all.safe": "
|
|
1783
|
+
"all.safe": "已暂停",
|
|
2026
1784
|
"all.checking.aria": "正在读取暂停状态",
|
|
1785
|
+
"all.unavailable.aria": "暂停状态暂不可用,正在重新连接",
|
|
2027
1786
|
"all.caveat.jobs": "后台任务仍在运行",
|
|
2028
1787
|
"all.caveat.unresumable": "含无法恢复的子代理",
|
|
2029
1788
|
"all.caveat.stalled": "未暂停,需要你决定",
|
|
@@ -2049,7 +1808,6 @@ window.__ModuleLoader__.load({
|
|
|
2049
1808
|
"triage.continue.aria": "继续:不添加消息,直接恢复",
|
|
2050
1809
|
"triage.note": "继续并附说明",
|
|
2051
1810
|
"triage.note.aria": "继续并附说明:告诉模型工具结果未知,不要盲目重试",
|
|
2052
|
-
"triage.note.hint": "会加入这句话:",
|
|
2053
1811
|
"triage.review": "只看不运行",
|
|
2054
1812
|
"triage.review.aria": "只看不运行:不启动会话、不发请求;需要查看时在会话历史里打开",
|
|
2055
1813
|
"triage.leave": "保持暂停",
|
|
@@ -2061,14 +1819,11 @@ window.__ModuleLoader__.load({
|
|
|
2061
1819
|
/** English dictionary, checked complete against the zh key set. */
|
|
2062
1820
|
const en = {
|
|
2063
1821
|
"pause.aria": "Pause this session and its subagents: their next model request will not be sent",
|
|
2064
|
-
"pause.tip": "Pause this session and its subagents",
|
|
2065
|
-
"resume.aria": "Resume this session and its subagents",
|
|
2066
1822
|
"resume.tip": "Resume this session and its subagents",
|
|
2067
1823
|
"draining.aria": "Pausing: a tool is still running. The session pauses after the tool finishes and before its next model request; press to cancel the pause",
|
|
2068
1824
|
"parking.aria": "Pausing: saving pending input; press to cancel the pause",
|
|
2069
1825
|
"safe.aria": "Paused: pending input is saved; press to resume",
|
|
2070
1826
|
"flushFailed.aria": "Paused, but saving pending input failed; press to resume",
|
|
2071
|
-
"state.paused": "Paused",
|
|
2072
1827
|
"state.draining": "Pausing",
|
|
2073
1828
|
"state.parking": "Saving",
|
|
2074
1829
|
"state.safe": "Paused",
|
|
@@ -2084,10 +1839,13 @@ window.__ModuleLoader__.load({
|
|
|
2084
1839
|
"all.resume.aria": "Resume all paused sessions",
|
|
2085
1840
|
"all.idle": "Pause all sessions",
|
|
2086
1841
|
"all.finishing": "Pausing",
|
|
1842
|
+
"all.cancel": "Cancel this pause",
|
|
1843
|
+
"all.cancel.aria": "Cancel this pause and resume the sessions",
|
|
2087
1844
|
"all.unclean": "Some sessions are not paused",
|
|
2088
1845
|
"all.unclean.aria": "Some sessions are not paused and cannot be resumed here; open the list to decide",
|
|
2089
|
-
"all.safe": "
|
|
1846
|
+
"all.safe": "Paused",
|
|
2090
1847
|
"all.checking.aria": "Reading pause state",
|
|
1848
|
+
"all.unavailable.aria": "Pause state unavailable; reconnecting",
|
|
2091
1849
|
"all.caveat.jobs": "background jobs are still running",
|
|
2092
1850
|
"all.caveat.unresumable": "contains a subagent that cannot be resumed",
|
|
2093
1851
|
"all.caveat.stalled": "did not pause; needs your decision",
|
|
@@ -2113,7 +1871,6 @@ window.__ModuleLoader__.load({
|
|
|
2113
1871
|
"triage.continue.aria": "Continue: resume without adding a message",
|
|
2114
1872
|
"triage.note": "Continue with a note",
|
|
2115
1873
|
"triage.note.aria": "Continue with a note: tell the model the tool result is unknown and not to retry blindly",
|
|
2116
|
-
"triage.note.hint": "This text is added:",
|
|
2117
1874
|
"triage.review": "View only",
|
|
2118
1875
|
"triage.review.aria": "View only: do not start the session or send a request; open it from your session history to read it",
|
|
2119
1876
|
"triage.leave": "Leave it paused",
|
|
@@ -2140,11 +1897,12 @@ window.__ModuleLoader__.load({
|
|
|
2140
1897
|
* frame-wide overlay layer: it is read while looking at one conversation, and
|
|
2141
1898
|
* a sidebar or drawer that covers that area covers the control with it.
|
|
2142
1899
|
*
|
|
2143
|
-
* Both controls are host-authoritative
|
|
2144
|
-
*
|
|
2145
|
-
*
|
|
2146
|
-
*
|
|
2147
|
-
*
|
|
1900
|
+
* Both controls are host-authoritative. On a browser Host they read the
|
|
1901
|
+
* native `pauseSession` stream, which keeps status reads out of the Session
|
|
1902
|
+
* log. The same path serves Web and Desktop.
|
|
1903
|
+
* User actions and status both use the generated native Remote namespace;
|
|
1904
|
+
* no slash command is involved. Each control stays inert wherever no
|
|
1905
|
+
* round-trip is available rather than rendering a state it cannot support. The
|
|
2148
1906
|
* header control also reads the whole-app answer the whole-app control
|
|
2149
1907
|
* publishes, so a park that holds nothing does not cost a host round-trip per
|
|
2150
1908
|
* header.
|
|
@@ -2156,8 +1914,7 @@ window.__ModuleLoader__.load({
|
|
|
2156
1914
|
const inject = [
|
|
2157
1915
|
"slots",
|
|
2158
1916
|
"locale",
|
|
2159
|
-
"remote"
|
|
2160
|
-
"remote.commands"
|
|
1917
|
+
"remote"
|
|
2161
1918
|
];
|
|
2162
1919
|
/**
|
|
2163
1920
|
* The session's pause control: pause the session and its subagents before
|
|
@@ -2175,7 +1932,7 @@ window.__ModuleLoader__.load({
|
|
|
2175
1932
|
*/
|
|
2176
1933
|
function PauseButton(props) {
|
|
2177
1934
|
const { sessionId, refresh, toggle, toggleGoal, t, useSession } = props;
|
|
2178
|
-
const view = pauseView(props.usePause((state) => state.bySession[String(sessionId)]));
|
|
1935
|
+
const view = pauseView(props.usePause((state) => state.bySession[String(sessionId)]), useSession((snapshot) => snapshot.running) ?? false);
|
|
2179
1936
|
const visible = showsPauseControl(useSession((snapshot) => snapshot.subagent));
|
|
2180
1937
|
(0, react.useEffect)(() => {
|
|
2181
1938
|
refresh(sessionId);
|
|
@@ -2228,65 +1985,58 @@ window.__ModuleLoader__.load({
|
|
|
2228
1985
|
ctx.effect(() => () => {
|
|
2229
1986
|
lifetime.abort(/* @__PURE__ */ new Error("pause: client unloaded"));
|
|
2230
1987
|
}, "pause: lifetime");
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
globalControl.setPanel(open);
|
|
2284
|
-
},
|
|
2285
|
-
decide: (sessionId, action) => {
|
|
2286
|
-
globalControl.decide(sessionId, action);
|
|
2287
|
-
}
|
|
2288
|
-
})
|
|
2289
|
-
}, PausePill));
|
|
1988
|
+
if (typeof ctx.remote.$mount === "function") ctx.effect(async () => {
|
|
1989
|
+
return await ctx.remote.$mount(TYPERT_REMOTE);
|
|
1990
|
+
}, "pause: remote transport");
|
|
1991
|
+
ctx.inject(["remote.pauseSession"], (remoteCtx) => {
|
|
1992
|
+
const remote = remoteCtx.remote.pauseSession;
|
|
1993
|
+
const transport = remoteTransport(remote, lifetime.signal);
|
|
1994
|
+
const globalControl = new PauseGlobalControl(transport, lifetime.signal);
|
|
1995
|
+
const control = new PauseControl(transport, lifetime.signal);
|
|
1996
|
+
remoteCtx.effect(() => transport.source.onSnapshot((snapshot) => {
|
|
1997
|
+
control.apply(snapshot);
|
|
1998
|
+
globalControl.apply(snapshot);
|
|
1999
|
+
}), "pause: native status stream");
|
|
2000
|
+
remoteCtx.slots.inject("conversation.session.header.actions", () => remoteCtx.slots.register({
|
|
2001
|
+
name: "conversation.session.header.actions",
|
|
2002
|
+
id: NS,
|
|
2003
|
+
order: 40,
|
|
2004
|
+
locale: NS,
|
|
2005
|
+
inject: () => ({
|
|
2006
|
+
hooks: { pause: control.store },
|
|
2007
|
+
refresh: (sessionId) => {
|
|
2008
|
+
control.refresh(sessionId);
|
|
2009
|
+
},
|
|
2010
|
+
toggle: (sessionId) => {
|
|
2011
|
+
control.toggle(sessionId);
|
|
2012
|
+
},
|
|
2013
|
+
toggleGoal: (sessionId) => {
|
|
2014
|
+
control.toggleGoal(sessionId);
|
|
2015
|
+
}
|
|
2016
|
+
})
|
|
2017
|
+
}, PauseButton));
|
|
2018
|
+
remoteCtx.slots.inject("conversation.session.header.utilities", () => remoteCtx.slots.register({
|
|
2019
|
+
name: "conversation.session.header.utilities",
|
|
2020
|
+
id: "pause-all",
|
|
2021
|
+
order: -11,
|
|
2022
|
+
locale: NS,
|
|
2023
|
+
inject: () => ({
|
|
2024
|
+
hooks: { pauseGlobal: globalControl.store },
|
|
2025
|
+
refresh: (sessionId) => {
|
|
2026
|
+
globalControl.refresh(sessionId);
|
|
2027
|
+
},
|
|
2028
|
+
toggle: (sessionId) => {
|
|
2029
|
+
globalControl.toggle(sessionId);
|
|
2030
|
+
},
|
|
2031
|
+
setPanel: (open) => {
|
|
2032
|
+
globalControl.setPanel(open);
|
|
2033
|
+
},
|
|
2034
|
+
decide: (sessionId, action) => {
|
|
2035
|
+
globalControl.decide(sessionId, action);
|
|
2036
|
+
}
|
|
2037
|
+
})
|
|
2038
|
+
}, PausePill));
|
|
2039
|
+
});
|
|
2290
2040
|
}
|
|
2291
2041
|
//#endregion
|
|
2292
2042
|
exports.PauseButton = PauseButton;
|