@north-light/crouter 0.3.154 → 0.3.157
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/dist/api/client.d.ts +1 -4
- package/dist/api/client.js +0 -5
- package/dist/api/dto/broker.d.ts +1 -1
- package/dist/api/dto/nodes.d.ts +0 -15
- package/dist/api/routes.d.ts +0 -1
- package/dist/api/routes.js +0 -1
- package/dist/builtin-views/chat/core.mjs +51 -6
- package/dist/builtin-views/chat/tui.mjs +14 -6
- package/dist/builtin-views/chat/web.jsx +7 -2
- package/dist/clients/attach/__tests__/oauth-dialog-lifecycle.test.js +1 -1
- package/dist/clients/attach/chrome/roster.d.ts +1 -1
- package/dist/clients/attach/chrome/roster.js +1 -9
- package/dist/clients/attach/command.js +5 -5
- package/dist/clients/attach/input/controller.d.ts +8 -23
- package/dist/clients/attach/input/controller.js +29 -59
- package/dist/clients/attach/overlays/dialogs.d.ts +2 -1
- package/dist/clients/attach/overlays/graph.d.ts +1 -4
- package/dist/clients/attach/overlays/graph.js +7 -25
- package/dist/clients/attach/session/context.d.ts +0 -7
- package/dist/clients/attach/session/frames.js +8 -1
- package/dist/clients/attach/session/input-wiring.d.ts +1 -1
- package/dist/clients/attach/session/input-wiring.js +11 -21
- package/dist/clients/attach/session/mode.d.ts +3 -4
- package/dist/clients/attach/session/mode.js +1 -6
- package/dist/clients/attach/session/reconnect.d.ts +3 -3
- package/dist/clients/attach/session/reconnect.js +7 -6
- package/dist/clients/attach/session/state-sync.d.ts +1 -1
- package/dist/clients/attach/session/state-sync.js +2 -2
- package/dist/clients/attach/slash/dispatch.d.ts +1 -13
- package/dist/clients/attach/slash/dispatch.js +17 -65
- package/dist/clients/attach/viewer.js +523 -523
- package/dist/clients/web/web-client/shared/protocol.d.ts +3 -5
- package/dist/core/__tests__/broker-sdk-wiring.test.js +18 -18
- package/dist/core/__tests__/chat-view-reconnect.test.js +23 -44
- package/dist/core/__tests__/full/broker-attach-limits.test.js +36 -60
- package/dist/core/__tests__/full/broker-attach-stream.test.js +4 -4
- package/dist/core/__tests__/full/broker-control-preempt.test.d.ts +1 -0
- package/dist/core/__tests__/full/broker-control-preempt.test.js +61 -0
- package/dist/core/__tests__/full/broker-dialogs.test.js +62 -121
- package/dist/core/__tests__/helpers/broker-clients.js +2 -2
- package/dist/core/__tests__/session-model.test.js +26 -15
- package/dist/core/keybindings/__tests__/resolve.test.js +1 -1
- package/dist/core/keybindings/catalog.d.ts +2 -2
- package/dist/core/keybindings/catalog.js +2 -0
- package/dist/core/runtime/auth-reload.d.ts +4 -4
- package/dist/core/runtime/auth-reload.js +13 -9
- package/dist/core/runtime/boot-root.d.ts +2 -2
- package/dist/core/runtime/boot-root.js +7 -7
- package/dist/core/runtime/broker-protocol.d.ts +27 -23
- package/dist/core/runtime/broker-protocol.js +1 -1
- package/dist/core/runtime/broker-request.js +11 -5
- package/dist/core/runtime/broker.d.ts +13 -21
- package/dist/core/runtime/broker.js +186 -151
- package/dist/core/runtime/interactive-deliver.js +5 -4
- package/dist/core/runtime/model-swap.d.ts +3 -2
- package/dist/core/runtime/model-swap.js +4 -3
- package/dist/core/runtime/node-read.d.ts +0 -20
- package/dist/core/runtime/node-read.js +1 -34
- package/dist/core/runtime/resume-root.d.ts +1 -1
- package/dist/core/runtime/resume-root.js +6 -6
- package/dist/core/runtime/spawn.js +3 -3
- package/dist/core/session-model/session-state.d.ts +6 -8
- package/dist/core/session-model/session-state.js +16 -6
- package/dist/daemon/api/handlers/nodes.js +1 -18
- package/dist/daemon/manage.js +2 -2
- package/dist/index.d.ts +1 -1
- package/dist/web-client/assets/index--SsQYcKu.js +79 -0
- package/dist/web-client/assets/{index-CpEl9LTS.css → index-DJhQZoAj.css} +1 -1
- package/dist/web-client/index.html +2 -2
- package/dist/web-client/sw.js +1 -1
- package/docs/compat/hearth-crtr-v1.md +1 -1
- package/docs/compat/hearth-crtr-v2.md +1 -1
- package/docs/compat/hearth-crtr-v3.md +1 -1
- package/docs/compat/hearth-crtr-v4.md +3 -1
- package/docs/public-api.md +2 -2
- package/package.json +4 -4
- package/runtime.lock.json +2 -2
- package/dist/web-client/assets/index-BpyZGBhI.js +0 -79
- package/docs/compat/hearth-crtr-v5.md +0 -175
|
@@ -5,10 +5,7 @@ import type { BrokerErrorFrame } from '../../api/dto/broker.js';
|
|
|
5
5
|
* import site for the wire union; the shapes themselves are pi's, versioned by
|
|
6
6
|
* pi, so a pi change is a compile error here instead of silent drift. */
|
|
7
7
|
export type { RpcExtensionUIRequest, RpcExtensionUIResponse, RpcSessionState, } from '@earendil-works/pi-coding-agent';
|
|
8
|
-
/**
|
|
9
|
-
* reassigned: `controller` may drive the engine, `observer` is read-only. This is
|
|
10
|
-
* a CAPABILITY, not ownership of a singleton slot — any number of clients may be
|
|
11
|
-
* controllers at the same time and all of them may write concurrently. */
|
|
8
|
+
/** Single controller (drives the engine) + N read-only observers (§5.3). */
|
|
12
9
|
export type ClientRole = 'controller' | 'observer';
|
|
13
10
|
/** The full state a (re)attaching client needs to catch up instantly — the
|
|
14
11
|
* broker's authoritative in-memory view (design §5.2: get_messages +
|
|
@@ -46,8 +43,6 @@ export interface BrokerSnapshot {
|
|
|
46
43
|
title: string | undefined;
|
|
47
44
|
};
|
|
48
45
|
}
|
|
49
|
-
/** Opens the connection and FIXES this client's role for its lifetime. A repeated
|
|
50
|
-
* `hello` on an already-helloed socket never changes the established role. */
|
|
51
46
|
export interface HelloFrame {
|
|
52
47
|
type: 'hello';
|
|
53
48
|
role: ClientRole;
|
|
@@ -58,7 +53,7 @@ export interface HelloFrame {
|
|
|
58
53
|
rows: number;
|
|
59
54
|
};
|
|
60
55
|
}
|
|
61
|
-
/** Drive the engine —
|
|
56
|
+
/** Drive the engine — controller only. Map 1:1 to session.prompt/steer/followUp/abort.
|
|
62
57
|
* `images` carries pasted/attached images to the engine (review M1): pi accepts
|
|
63
58
|
* `prompt(text,{images})` / `steer(text,images)` / `followUp(text,images)` at
|
|
64
59
|
* 0.78.1. The wire TYPE lives here; T3/T6 wire the runtime side. The BROKER read
|
|
@@ -92,7 +87,7 @@ export interface AbortFrame {
|
|
|
92
87
|
* `POST /v1/nodes/{id}/messages` with `delivery:'interactive'`). The broker
|
|
93
88
|
* routes it ITSELF — turn in flight → steer, idle → prompt — because only the
|
|
94
89
|
* broker authoritatively knows streaming state (viewers track it client-side;
|
|
95
|
-
* a one-shot client can't).
|
|
90
|
+
* a one-shot client can't). Controller-only, like the other drive frames.
|
|
96
91
|
* Acked once routed (`ack{for:'deliver', id}`, `detail` = 'prompt' | 'steer')
|
|
97
92
|
* so the daemon handler can return deterministically. */
|
|
98
93
|
export interface DeliverFrame {
|
|
@@ -101,7 +96,7 @@ export interface DeliverFrame {
|
|
|
101
96
|
text: string;
|
|
102
97
|
images?: ImageContent[];
|
|
103
98
|
}
|
|
104
|
-
/** Run a `!` bash command —
|
|
99
|
+
/** Run a `!` bash command — controller only. Maps to `session.executeBash()`,
|
|
105
100
|
* which runs the command, records a `bashExecution` message in context, and
|
|
106
101
|
* starts NO agent turn (pi's interactive `!`/`!!` semantics). `command` is the
|
|
107
102
|
* text AFTER the leading `!`/`!!`; `excludeFromContext` is the `!!` form (output
|
|
@@ -112,6 +107,13 @@ export interface BashFrame {
|
|
|
112
107
|
command: string;
|
|
113
108
|
excludeFromContext?: boolean;
|
|
114
109
|
}
|
|
110
|
+
/** Controller arbitration (§5.3). */
|
|
111
|
+
export interface RequestControlFrame {
|
|
112
|
+
type: 'request_control';
|
|
113
|
+
}
|
|
114
|
+
export interface ReleaseControlFrame {
|
|
115
|
+
type: 'release_control';
|
|
116
|
+
}
|
|
115
117
|
/** Detach this client — the engine runs on (distinct from `shutdown`). */
|
|
116
118
|
export interface ByeFrame {
|
|
117
119
|
type: 'bye';
|
|
@@ -297,13 +299,13 @@ export interface ListMemoryRefsFrame {
|
|
|
297
299
|
}
|
|
298
300
|
/** Answer a blocking extension dialog — pi's public RPC response type. */
|
|
299
301
|
export type ExtensionUIResponseFrame = RpcExtensionUIResponse;
|
|
300
|
-
/** Clone the current session to a new branch (`/clone`).
|
|
302
|
+
/** Clone the current session to a new branch (`/clone`). Controller-only.
|
|
301
303
|
* Broker handler: reads current leaf from sessionManager, creates a branched
|
|
302
304
|
* session file, switches to it via runReplacement. */
|
|
303
305
|
export interface CloneFrame {
|
|
304
306
|
type: 'clone';
|
|
305
307
|
}
|
|
306
|
-
/** Share the session as a secret GitHub gist (`/share`).
|
|
308
|
+
/** Share the session as a secret GitHub gist (`/share`). Controller-only.
|
|
307
309
|
* Broker handler: exports session to a temp HTML file, shells `gh gist create --secret`,
|
|
308
310
|
* returns the URL in ack.detail. */
|
|
309
311
|
export interface ShareFrame {
|
|
@@ -312,8 +314,8 @@ export interface ShareFrame {
|
|
|
312
314
|
/** Reload credentials + refresh model registry after a viewer-side auth change.
|
|
313
315
|
* Open to any client — reload_auth is an idempotent local re-read that doesn't
|
|
314
316
|
* steer the conversation, so the daemon's canvas-wide fan (one /login → every
|
|
315
|
-
* live broker) can trigger it
|
|
316
|
-
* services.authStorage.reload() +
|
|
317
|
+
* live broker) can trigger it without claiming controller and demoting an
|
|
318
|
+
* attached human. Broker handler: services.authStorage.reload() +
|
|
317
319
|
* services.modelRegistry.refresh(). */
|
|
318
320
|
export interface ReloadAuthFrame {
|
|
319
321
|
type: 'reload_auth';
|
|
@@ -325,22 +327,25 @@ export interface GetHumanForkCoordinatesFrame {
|
|
|
325
327
|
type: 'get_human_fork_coordinates';
|
|
326
328
|
id: string;
|
|
327
329
|
}
|
|
328
|
-
export type ClientToBroker = HelloFrame | PromptFrame | SteerFrame | FollowUpFrame | AbortFrame | DeliverFrame | BashFrame | ByeFrame | ShutdownFrame | SetModelFrame | DeliverCustomMessageFrame | CycleModelFrame | CycleLadderFrame | CycleThinkingFrame | SetThinkingLevelFrame | DequeueFrame | SetAutoRetryFrame | SetAutoCompactionFrame | CompactFrame | NewSessionFrame | SwitchSessionFrame | ForkFrame | SetSessionNameFrame | GetCommandsFrame | NavigateTreeFrame | ReloadFrame | ExportFrame | ListModelsFrame | ListSessionsFrame | GetTreeFrame | GetSettingsFrame | ListScopedModelsFrame | ListMemoryRefsFrame | ExtensionUIResponseFrame | CloneFrame | ShareFrame | ReloadAuthFrame | GetHumanForkCoordinatesFrame;
|
|
330
|
+
export type ClientToBroker = HelloFrame | PromptFrame | SteerFrame | FollowUpFrame | AbortFrame | DeliverFrame | BashFrame | RequestControlFrame | ReleaseControlFrame | ByeFrame | ShutdownFrame | SetModelFrame | DeliverCustomMessageFrame | CycleModelFrame | CycleLadderFrame | CycleThinkingFrame | SetThinkingLevelFrame | DequeueFrame | SetAutoRetryFrame | SetAutoCompactionFrame | CompactFrame | NewSessionFrame | SwitchSessionFrame | ForkFrame | SetSessionNameFrame | GetCommandsFrame | NavigateTreeFrame | ReloadFrame | ExportFrame | ListModelsFrame | ListSessionsFrame | GetTreeFrame | GetSettingsFrame | ListScopedModelsFrame | ListMemoryRefsFrame | ExtensionUIResponseFrame | CloneFrame | ShareFrame | ReloadAuthFrame | GetHumanForkCoordinatesFrame;
|
|
329
331
|
export interface WelcomeFrame {
|
|
330
332
|
type: 'welcome';
|
|
331
333
|
snapshot: BrokerSnapshot;
|
|
332
|
-
/** The COMPLETE statement of this client's authority — there is no other
|
|
333
|
-
* role/ownership field. Fixed at `hello` and never changed afterwards. */
|
|
334
334
|
role: ClientRole;
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
* mid-dialog
|
|
335
|
+
controller_id: string | null;
|
|
336
|
+
/** A dialog already in-flight when this client attached (Phase 4
|
|
337
|
+
* attach-mid-dialog). The field exists now; it is only ever populated in
|
|
338
|
+
* Phase 4 — Phase 3 always sends it absent/null. */
|
|
338
339
|
pending_dialog?: RpcExtensionUIRequest | null;
|
|
339
340
|
/** The pi agent dir (`~/.pi/agent`) from the broker's process — so the viewer
|
|
340
341
|
* can construct an AuthStorage/ModelRegistry pointing at the SAME auth.json.
|
|
341
342
|
* crtr surface attach is tmux-local only: broker + viewer share a filesystem. */
|
|
342
343
|
agentDir?: string;
|
|
343
344
|
}
|
|
345
|
+
export interface ControlChangedFrame {
|
|
346
|
+
type: 'control_changed';
|
|
347
|
+
controller_id: string | null;
|
|
348
|
+
}
|
|
344
349
|
/** Broadcast to EVERY client after a successful `set_model`/`cycle_model`. pi
|
|
345
350
|
* emits no AgentSessionEvent for a model switch, so without this the new model
|
|
346
351
|
* reaches no viewer at all (the requester gets only a bare ack) and footers
|
|
@@ -370,7 +375,7 @@ export interface ModelChangedFrame {
|
|
|
370
375
|
* promise instead of hanging it — absent on uncorrelated errors (engine drive
|
|
371
376
|
* errors, command-op failures, frame_overflow). There is NO `retryable` field. */
|
|
372
377
|
export type ErrorFrame = BrokerErrorFrame;
|
|
373
|
-
/** Result of a command op (§1.3): `for` echoes the op name, `ok` the
|
|
378
|
+
/** Result of a controller command op (§1.3): `for` echoes the op name, `ok` the
|
|
374
379
|
* outcome, `detail` an optional human-readable note. */
|
|
375
380
|
export interface AckFrame {
|
|
376
381
|
type: 'ack';
|
|
@@ -555,8 +560,7 @@ export type ExtensionUIRequestFrame = RpcExtensionUIRequest;
|
|
|
555
560
|
* WITHOUT a client answer. The broker sends this exactly when it resolves a pending
|
|
556
561
|
* dialog ITSELF rather than the client answering it — the extension aborted the
|
|
557
562
|
* request out-of-band (e.g. a local OAuth loopback callback won the race against a
|
|
558
|
-
* still-open manual-paste dialog)
|
|
559
|
-
* client answered the fan-out first and this one must close its copy. The client tears
|
|
563
|
+
* still-open manual-paste dialog) or the broker-side timeout fired. The client tears
|
|
560
564
|
* down ONLY the overlay whose `id` matches; every other dialog stays exactly as it
|
|
561
565
|
* was, so an unrelated request can never dismiss an unrelated blocking dialog. */
|
|
562
566
|
export interface ExtensionUIDismissFrame {
|
|
@@ -566,7 +570,7 @@ export interface ExtensionUIDismissFrame {
|
|
|
566
570
|
/** Everything the broker can send. Live `AgentSessionEvent`s are relayed
|
|
567
571
|
* verbatim (the broker adds nothing); the broker's own control frames carry
|
|
568
572
|
* non-colliding `type` discriminants. */
|
|
569
|
-
export type BrokerToClient = WelcomeFrame | ModelChangedFrame | ErrorFrame | AckFrame | BrokerDataFrame | BashStartFrame | BashOutputFrame | BashEndFrame | ExtensionUIRequestFrame | ExtensionUIDismissFrame | AgentSessionEvent;
|
|
573
|
+
export type BrokerToClient = WelcomeFrame | ControlChangedFrame | ModelChangedFrame | ErrorFrame | AckFrame | BrokerDataFrame | BashStartFrame | BashOutputFrame | BashEndFrame | ExtensionUIRequestFrame | ExtensionUIDismissFrame | AgentSessionEvent;
|
|
570
574
|
/** Encode one frame as a single newline-terminated JSON line. */
|
|
571
575
|
export declare function encodeFrame(frame: ClientToBroker | BrokerToClient): string;
|
|
572
576
|
/** Byte bounds for a {@link FrameDecoder} (C5). */
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// The transport is one unix socket per node (`nodeDir(id)/view.sock`) speaking
|
|
7
7
|
// newline-delimited JSON frames. Live engine events are relayed VERBATIM — the
|
|
8
8
|
// broker is a transparent multiplexer, so a broker→client frame is either one of
|
|
9
|
-
// the broker's own control frames (welcome/error) or a raw pi
|
|
9
|
+
// the broker's own control frames (welcome/control_changed/error) or a raw pi
|
|
10
10
|
// `AgentSessionEvent` / `extension_ui_request`. The `type` namespaces never
|
|
11
11
|
// collide, so the union below stays a clean discriminated union.
|
|
12
12
|
//
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
// broker-request.ts — the ONE one-shot
|
|
2
|
-
// node broker over its view.sock: connect → `hello`
|
|
3
|
-
// single frame → await its ack.
|
|
1
|
+
// broker-request.ts — the ONE one-shot controller round-trip against a LIVE
|
|
2
|
+
// node broker over its view.sock: connect → `hello` + `request_control` →
|
|
3
|
+
// send a single frame → await its ack.
|
|
4
4
|
//
|
|
5
5
|
// Every host-side "tell a booted engine something" path rides this: the model
|
|
6
6
|
// swap (model-swap.ts), interactive deliver/interrupt (interactive-deliver.ts),
|
|
@@ -8,6 +8,11 @@
|
|
|
8
8
|
// They differ only in the frame sent, the ack awaited, and what they read off
|
|
9
9
|
// the ack — so the dance itself lives here once instead of being re-copied per
|
|
10
10
|
// caller.
|
|
11
|
+
//
|
|
12
|
+
// `request_control` is sent unconditionally: `hello` admits us as controller
|
|
13
|
+
// only if none is held, and `request_control` preempts an attached viewer
|
|
14
|
+
// (idempotent when we already hold it). The broker processes both in order
|
|
15
|
+
// before the payload frame.
|
|
11
16
|
import { randomUUID } from 'node:crypto';
|
|
12
17
|
import { BrokerClient } from '../broker-client/index.js';
|
|
13
18
|
/** How long to wait for the broker's ack. Every frame this carries is local
|
|
@@ -62,6 +67,7 @@ export function oneShotControllerRequest(opts) {
|
|
|
62
67
|
client.on('connect', () => {
|
|
63
68
|
connected = true;
|
|
64
69
|
client.send({ type: 'hello', role: 'controller', client_id: clientId });
|
|
70
|
+
client.send({ type: 'request_control' });
|
|
65
71
|
client.send(opts.frame(frameId));
|
|
66
72
|
});
|
|
67
73
|
client.on('frame', (frame) => {
|
|
@@ -72,8 +78,8 @@ export function oneShotControllerRequest(opts) {
|
|
|
72
78
|
finish(() => reject(new Error(frame.detail ?? `broker rejected ${opts.what}`)));
|
|
73
79
|
}
|
|
74
80
|
else if (frame.type === 'error') {
|
|
75
|
-
// This connection only ever sent hello + one payload
|
|
76
|
-
// error frame here is a response to ours.
|
|
81
|
+
// This connection only ever sent hello/request_control + one payload
|
|
82
|
+
// frame, so any error frame here is a response to ours.
|
|
77
83
|
finish(() => reject(new Error(frame.message)));
|
|
78
84
|
}
|
|
79
85
|
else if (opts.observe !== undefined) {
|
|
@@ -22,8 +22,8 @@ export declare function isUnknownModel(model: {
|
|
|
22
22
|
api?: string;
|
|
23
23
|
} | null | undefined): boolean;
|
|
24
24
|
/**
|
|
25
|
-
* Route a
|
|
26
|
-
*
|
|
25
|
+
* Route a controller `prompt`/`follow_up` frame against the LIVE session state.
|
|
26
|
+
* The controller picks its frame type off a possibly-STALE `isStreaming`
|
|
27
27
|
* snapshot, so the broker is authoritative and the client's choice is a HINT:
|
|
28
28
|
*
|
|
29
29
|
* - m-B (streaming-safe prompt): a `prompt` arriving mid-stream needs
|
|
@@ -186,19 +186,15 @@ interface BrokerClient {
|
|
|
186
186
|
* high-water mark). */
|
|
187
187
|
queuedFrames: number;
|
|
188
188
|
}
|
|
189
|
-
/** A blocking dialog awaiting
|
|
190
|
-
*
|
|
191
|
-
* to every writable client at raise time; this entry is the single settlement
|
|
192
|
-
* point they race for. */
|
|
189
|
+
/** A blocking dialog awaiting the controller's response, the broker-side default
|
|
190
|
+
* timeout, or the engine's abort. */
|
|
193
191
|
interface PendingDialog {
|
|
194
|
-
/** The original request — retained so `welcome.pending_dialog` and the
|
|
195
|
-
*
|
|
196
|
-
*
|
|
192
|
+
/** The original request (T4) — retained so `welcome.pending_dialog` and the
|
|
193
|
+
* re-route-on-become-controller path can re-deliver a still-pending dialog to
|
|
194
|
+
* a (new) controller. The Wave-0 shape stored only the resolver. */
|
|
197
195
|
request: RpcExtensionUIRequest;
|
|
198
|
-
/**
|
|
199
|
-
*
|
|
200
|
-
* later answer for the same id finds nothing and is a no-op), and broadcasts
|
|
201
|
-
* `extension_ui_dismiss` so every peer closes its copy. */
|
|
196
|
+
/** The controller answered — resolve with its parsed response (also clears the
|
|
197
|
+
* broker-side timeout and removes the entry from the registry). */
|
|
202
198
|
resolve: (response: RpcExtensionUIResponse) => void;
|
|
203
199
|
}
|
|
204
200
|
/** Dispose the live engine session if one exists (idempotent). Called by
|
|
@@ -218,15 +214,11 @@ export declare function buildBrokerSession(engine: BrokerEngine, cfg: BrokerSdkC
|
|
|
218
214
|
}>;
|
|
219
215
|
/** Broker-side hooks the UI context needs to route (or noOp) extension dialogs. */
|
|
220
216
|
export interface BrokerDialogDeps {
|
|
221
|
-
/**
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
/** Forward a dialog request to one writable client (called once per client in
|
|
225
|
-
* the fan-out). */
|
|
217
|
+
/** The controller client, or null when ZERO viewers are attached. */
|
|
218
|
+
controller: () => BrokerClient | null;
|
|
219
|
+
/** Forward a dialog request to the (non-null) controller. */
|
|
226
220
|
forward: (client: BrokerClient, request: RpcExtensionUIRequest) => void;
|
|
227
|
-
/** Pending-dialog registry, keyed by request id
|
|
228
|
-
* how many clients were fanned it; the first `extension_ui_response` settles
|
|
229
|
-
* and removes it, so later answers for the same id are no-ops. */
|
|
221
|
+
/** Pending-dialog registry, keyed by request id (answered via extension_ui_response). */
|
|
230
222
|
pending: Map<string, PendingDialog>;
|
|
231
223
|
/** Broadcast a non-blocking display frame (setStatus/setWidget/setTitle) to ALL
|
|
232
224
|
* viewers — the relay path for pi's fire-and-forget extension-UI surface. */
|