@north-light/crouter 0.3.157 → 0.3.159
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/dist/api/client.d.ts +4 -1
- package/dist/api/client.js +5 -0
- package/dist/api/dto/broker.d.ts +1 -1
- package/dist/api/dto/nodes.d.ts +15 -0
- package/dist/api/routes.d.ts +1 -0
- package/dist/api/routes.js +1 -0
- package/dist/builtin-memory/internal/storage-tiers.md +2 -0
- package/dist/builtin-views/chat/core.mjs +6 -51
- package/dist/builtin-views/chat/tui.mjs +6 -14
- package/dist/builtin-views/chat/web.jsx +2 -7
- package/dist/clients/attach/__tests__/oauth-dialog-lifecycle.test.js +1 -1
- package/dist/clients/attach/chrome/bash-jobs.d.ts +0 -5
- package/dist/clients/attach/chrome/bash-jobs.js +5 -24
- package/dist/clients/attach/chrome/roster.d.ts +4 -3
- package/dist/clients/attach/chrome/roster.js +20 -36
- package/dist/clients/attach/command.js +5 -5
- package/dist/clients/attach/input/controller.d.ts +23 -8
- package/dist/clients/attach/input/controller.js +59 -29
- package/dist/clients/attach/overlays/dialogs.d.ts +1 -2
- package/dist/clients/attach/overlays/graph.d.ts +4 -1
- package/dist/clients/attach/overlays/graph.js +25 -7
- package/dist/clients/attach/session/context.d.ts +7 -0
- package/dist/clients/attach/session/frames.js +1 -8
- package/dist/clients/attach/session/input-wiring.d.ts +1 -1
- package/dist/clients/attach/session/input-wiring.js +21 -11
- package/dist/clients/attach/session/mode.d.ts +4 -3
- package/dist/clients/attach/session/mode.js +6 -1
- package/dist/clients/attach/session/reconnect.d.ts +3 -3
- package/dist/clients/attach/session/reconnect.js +6 -7
- 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 +13 -1
- package/dist/clients/attach/slash/dispatch.js +65 -17
- package/dist/clients/attach/viewer.js +497 -499
- package/dist/clients/web/web-client/shared/protocol.d.ts +5 -3
- package/dist/commands/node.js +142 -4
- package/dist/commands/surface-inspect.js +4 -3
- package/dist/core/__tests__/broker-sdk-wiring.test.js +18 -18
- package/dist/core/__tests__/canvas-inbox-watcher.test.js +5 -3
- package/dist/core/__tests__/chat-view-reconnect.test.js +44 -23
- package/dist/core/__tests__/full/broker-attach-limits.test.js +60 -36
- package/dist/core/__tests__/full/broker-attach-stream.test.js +4 -4
- package/dist/core/__tests__/full/broker-dialogs.test.js +121 -62
- package/dist/core/__tests__/helpers/broker-clients.js +2 -2
- package/dist/core/__tests__/session-model.test.js +15 -26
- package/dist/core/bash-jobs.d.ts +11 -0
- package/dist/core/bash-jobs.js +41 -1
- package/dist/core/feed/inbox.d.ts +3 -3
- package/dist/core/feed/inbox.js +10 -6
- package/dist/core/inspector/core.d.ts +17 -2
- package/dist/core/inspector/core.js +172 -23
- package/dist/core/inspector/model.d.ts +30 -1
- package/dist/core/inspector/model.js +39 -0
- package/dist/core/inspector/text.js +14 -1
- package/dist/core/inspector/tui.js +71 -3
- package/dist/core/keybindings/__tests__/resolve.test.js +2 -2
- package/dist/core/keybindings/catalog.d.ts +2 -2
- package/dist/core/keybindings/catalog.js +3 -4
- package/dist/core/runtime/auth-reload.d.ts +4 -4
- package/dist/core/runtime/auth-reload.js +9 -13
- 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 +23 -27
- package/dist/core/runtime/broker-protocol.js +1 -1
- package/dist/core/runtime/broker-request.js +5 -11
- package/dist/core/runtime/broker.d.ts +21 -13
- package/dist/core/runtime/broker.js +151 -186
- package/dist/core/runtime/interactive-deliver.js +4 -5
- package/dist/core/runtime/model-swap.d.ts +2 -3
- package/dist/core/runtime/model-swap.js +3 -4
- package/dist/core/runtime/node-read.d.ts +20 -0
- package/dist/core/runtime/node-read.js +34 -1
- 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 +8 -6
- package/dist/core/session-model/session-state.js +6 -16
- package/dist/core/tui/host.js +21 -6
- package/dist/daemon/api/handlers/nodes.js +18 -1
- package/dist/daemon/manage.js +2 -2
- package/dist/index.d.ts +1 -1
- package/dist/pi-extensions/canvas-bash-valve.js +9 -3
- package/dist/web-client/assets/{index-DJhQZoAj.css → index-CpEl9LTS.css} +1 -1
- package/dist/web-client/assets/{index--SsQYcKu.js → index-CsuwzlcQ.js} +19 -19
- package/dist/web-client/index.html +2 -2
- package/dist/web-client/sw.js +1 -1
- package/docs/compat/hearth-crtr-v5.md +175 -0
- package/docs/public-api.md +2 -2
- package/package.json +2 -2
- package/runtime.lock.json +6 -6
- package/dist/core/__tests__/full/broker-control-preempt.test.d.ts +0 -1
- package/dist/core/__tests__/full/broker-control-preempt.test.js +0 -61
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
/** Reload a LIVE node's credentials over its view.sock. Connects as an
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
1
|
+
/** Reload a LIVE node's credentials over its view.sock. Connects as an
|
|
2
|
+
* OBSERVER, sends `reload_auth`, and resolves on its ack. The handler is open
|
|
3
|
+
* to any role, but observer accurately describes this role-independent operation.
|
|
4
|
+
* Rejects on the broker's `error` frame,
|
|
5
5
|
* connect failure, a premature close, or timeout — the caller (the daemon fan)
|
|
6
6
|
* isolates each broker so one failure never aborts the rest. */
|
|
7
7
|
export declare function reloadAuthLive(nodeId: string): Promise<void>;
|
|
@@ -9,11 +9,9 @@
|
|
|
9
9
|
// live broker, so the whole canvas follows one account switch with no per-broker
|
|
10
10
|
// re-login.
|
|
11
11
|
//
|
|
12
|
-
// connect → hello → reload_auth → await ack.
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
// request_control ALWAYS preempts, which would silently demote any human
|
|
16
|
-
// attached-and-driving a node every time /login fans canvas-wide.
|
|
12
|
+
// connect → observer hello → reload_auth → await ack. The broker's reload_auth
|
|
13
|
+
// handler is open to any client (it is an idempotent local re-read that does not
|
|
14
|
+
// steer the conversation), so this deliberately remains an observer operation.
|
|
17
15
|
import { randomUUID } from 'node:crypto';
|
|
18
16
|
import { BrokerClient } from '../broker-client/index.js';
|
|
19
17
|
/** How long to wait for the broker's `reload_auth` ack before giving up. The
|
|
@@ -21,10 +19,10 @@ import { BrokerClient } from '../broker-client/index.js';
|
|
|
21
19
|
* supervision loop awaits this on a real change — so the timeout is kept tight
|
|
22
20
|
* to cap the worst-case stall if a broker connects but never acks. */
|
|
23
21
|
const ACK_TIMEOUT_MS = 3_000;
|
|
24
|
-
/** Reload a LIVE node's credentials over its view.sock. Connects as an
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
22
|
+
/** Reload a LIVE node's credentials over its view.sock. Connects as an
|
|
23
|
+
* OBSERVER, sends `reload_auth`, and resolves on its ack. The handler is open
|
|
24
|
+
* to any role, but observer accurately describes this role-independent operation.
|
|
25
|
+
* Rejects on the broker's `error` frame,
|
|
28
26
|
* connect failure, a premature close, or timeout — the caller (the daemon fan)
|
|
29
27
|
* isolates each broker so one failure never aborts the rest. */
|
|
30
28
|
export function reloadAuthLive(nodeId) {
|
|
@@ -51,10 +49,8 @@ export function reloadAuthLive(nodeId) {
|
|
|
51
49
|
client.on('error', (err) => finish(() => reject(err)));
|
|
52
50
|
client.on('close', () => finish(() => reject(new Error('broker connection closed before it acked the auth reload'))));
|
|
53
51
|
client.on('connect', () => {
|
|
54
|
-
// Hello as OBSERVER — reload_auth is
|
|
55
|
-
//
|
|
56
|
-
// control through the fan. (A `controller` hello also only takes control
|
|
57
|
-
// when none is held, which is safe; observer is the more honest signal.)
|
|
52
|
+
// Hello as OBSERVER — reload_auth is role-independent, and this helper
|
|
53
|
+
// does not drive the conversation.
|
|
58
54
|
client.send({ type: 'hello', role: 'observer', client_id: clientId });
|
|
59
55
|
client.send({ type: 'reload_auth' });
|
|
60
56
|
});
|
|
@@ -17,8 +17,8 @@ export interface BootRootOpts {
|
|
|
17
17
|
}
|
|
18
18
|
/** Boot the front-door root: create the resident root through crtrd (broker
|
|
19
19
|
* launched + view socket ready server-side), register THIS terminal's pane as
|
|
20
|
-
* its
|
|
21
|
-
* becomes
|
|
20
|
+
* its foreground viewer, then exec `crtr surface attach` inline so this terminal
|
|
21
|
+
* becomes a writable viewer. Does not return — it process.exit()s
|
|
22
22
|
* when the inline attach exits (detach leaves the resident broker running, to
|
|
23
23
|
* be reconnected later). */
|
|
24
24
|
export declare function bootRoot(opts: BootRootOpts): Promise<never>;
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// bootRoot creates the resident root through crtrd (`createNode({root:true})`,
|
|
4
4
|
// which the daemon serves at readiness 'await' — node birth, broker launch, and
|
|
5
5
|
// view-socket readiness ALL happen server-side), then execs `crtr surface
|
|
6
|
-
// attach` inline so THIS terminal becomes the broker's
|
|
6
|
+
// attach` inline so THIS terminal becomes the broker's writable viewer.
|
|
7
7
|
//
|
|
8
8
|
// crtrd is the sole owner of canvas state (see crtrd-sole-state-owner), so this
|
|
9
9
|
// module opens NO canvas.db: node creation and the viewer-focus row both route
|
|
@@ -18,13 +18,13 @@ import { cliClient } from '../../commands/api-client.js';
|
|
|
18
18
|
import { mark } from '../timing.js';
|
|
19
19
|
/** Boot the front-door root: create the resident root through crtrd (broker
|
|
20
20
|
* launched + view socket ready server-side), register THIS terminal's pane as
|
|
21
|
-
* its
|
|
22
|
-
* becomes
|
|
21
|
+
* its foreground viewer, then exec `crtr surface attach` inline so this terminal
|
|
22
|
+
* becomes a writable viewer. Does not return — it process.exit()s
|
|
23
23
|
* when the inline attach exits (detach leaves the resident broker running, to
|
|
24
24
|
* be reconnected later). */
|
|
25
25
|
export async function bootRoot(opts) {
|
|
26
26
|
// crtr is tmux-only: the front door execs `crtr surface attach` inline in THIS
|
|
27
|
-
// pane to become the root's
|
|
27
|
+
// pane to become the root's writable viewer, so there must be a tmux pane to
|
|
28
28
|
// anchor on. Outside tmux there is no viewer surface — throw with a friendly
|
|
29
29
|
// message rather than boot a root nobody can see.
|
|
30
30
|
if (!inTmux()) {
|
|
@@ -71,8 +71,8 @@ export async function bootRoot(opts) {
|
|
|
71
71
|
// so its window stays where the user already is. The node row carries NO
|
|
72
72
|
// presence — a broker node's pane/window/tmux_session stay NULL; the viewer
|
|
73
73
|
// pane lives in the focuses table. Register the FOREGROUND front-door pane as
|
|
74
|
-
//
|
|
75
|
-
//
|
|
74
|
+
// this node's focus (canvas write, routed through crtrd) so focus/nav can find
|
|
75
|
+
// it. Best-effort — attach works regardless.
|
|
76
76
|
const here = currentTmux();
|
|
77
77
|
if (here !== null) {
|
|
78
78
|
try {
|
|
@@ -82,7 +82,7 @@ export async function bootRoot(opts) {
|
|
|
82
82
|
}
|
|
83
83
|
// Broker is the only host: the root runs as a detached broker engine (already
|
|
84
84
|
// launched above), and THIS terminal execs `crtr surface attach` inline to
|
|
85
|
-
// become its
|
|
85
|
+
// become its writable viewer. The attach's own ensureAttach is an idempotent
|
|
86
86
|
// no-op on the already-live broker. CRTR_ATTACH_WAIT_MS tells the viewer to
|
|
87
87
|
// poll view.sock until it accepts instead of failing fast on a not-yet-bound
|
|
88
88
|
// socket. CRTR_ATTACH_BORROWED_PANE tells the viewer that its pane is the
|
|
@@ -5,7 +5,10 @@ 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
|
-
/**
|
|
8
|
+
/** A socket's fixed, per-client capability, stated once in its `hello` and never
|
|
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. */
|
|
9
12
|
export type ClientRole = 'controller' | 'observer';
|
|
10
13
|
/** The full state a (re)attaching client needs to catch up instantly — the
|
|
11
14
|
* broker's authoritative in-memory view (design §5.2: get_messages +
|
|
@@ -43,6 +46,8 @@ export interface BrokerSnapshot {
|
|
|
43
46
|
title: string | undefined;
|
|
44
47
|
};
|
|
45
48
|
}
|
|
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. */
|
|
46
51
|
export interface HelloFrame {
|
|
47
52
|
type: 'hello';
|
|
48
53
|
role: ClientRole;
|
|
@@ -53,7 +58,7 @@ export interface HelloFrame {
|
|
|
53
58
|
rows: number;
|
|
54
59
|
};
|
|
55
60
|
}
|
|
56
|
-
/** Drive the engine — controller only. Map 1:1 to session.prompt/steer/followUp/abort.
|
|
61
|
+
/** Drive the engine — writable (`controller`) clients only. Map 1:1 to session.prompt/steer/followUp/abort.
|
|
57
62
|
* `images` carries pasted/attached images to the engine (review M1): pi accepts
|
|
58
63
|
* `prompt(text,{images})` / `steer(text,images)` / `followUp(text,images)` at
|
|
59
64
|
* 0.78.1. The wire TYPE lives here; T3/T6 wire the runtime side. The BROKER read
|
|
@@ -87,7 +92,7 @@ export interface AbortFrame {
|
|
|
87
92
|
* `POST /v1/nodes/{id}/messages` with `delivery:'interactive'`). The broker
|
|
88
93
|
* routes it ITSELF — turn in flight → steer, idle → prompt — because only the
|
|
89
94
|
* broker authoritatively knows streaming state (viewers track it client-side;
|
|
90
|
-
* a one-shot client can't).
|
|
95
|
+
* a one-shot client can't). Writable-only, like the other drive frames.
|
|
91
96
|
* Acked once routed (`ack{for:'deliver', id}`, `detail` = 'prompt' | 'steer')
|
|
92
97
|
* so the daemon handler can return deterministically. */
|
|
93
98
|
export interface DeliverFrame {
|
|
@@ -96,7 +101,7 @@ export interface DeliverFrame {
|
|
|
96
101
|
text: string;
|
|
97
102
|
images?: ImageContent[];
|
|
98
103
|
}
|
|
99
|
-
/** Run a `!` bash command —
|
|
104
|
+
/** Run a `!` bash command — writable clients only. Maps to `session.executeBash()`,
|
|
100
105
|
* which runs the command, records a `bashExecution` message in context, and
|
|
101
106
|
* starts NO agent turn (pi's interactive `!`/`!!` semantics). `command` is the
|
|
102
107
|
* text AFTER the leading `!`/`!!`; `excludeFromContext` is the `!!` form (output
|
|
@@ -107,13 +112,6 @@ export interface BashFrame {
|
|
|
107
112
|
command: string;
|
|
108
113
|
excludeFromContext?: boolean;
|
|
109
114
|
}
|
|
110
|
-
/** Controller arbitration (§5.3). */
|
|
111
|
-
export interface RequestControlFrame {
|
|
112
|
-
type: 'request_control';
|
|
113
|
-
}
|
|
114
|
-
export interface ReleaseControlFrame {
|
|
115
|
-
type: 'release_control';
|
|
116
|
-
}
|
|
117
115
|
/** Detach this client — the engine runs on (distinct from `shutdown`). */
|
|
118
116
|
export interface ByeFrame {
|
|
119
117
|
type: 'bye';
|
|
@@ -299,13 +297,13 @@ export interface ListMemoryRefsFrame {
|
|
|
299
297
|
}
|
|
300
298
|
/** Answer a blocking extension dialog — pi's public RPC response type. */
|
|
301
299
|
export type ExtensionUIResponseFrame = RpcExtensionUIResponse;
|
|
302
|
-
/** Clone the current session to a new branch (`/clone`).
|
|
300
|
+
/** Clone the current session to a new branch (`/clone`). Writable-only.
|
|
303
301
|
* Broker handler: reads current leaf from sessionManager, creates a branched
|
|
304
302
|
* session file, switches to it via runReplacement. */
|
|
305
303
|
export interface CloneFrame {
|
|
306
304
|
type: 'clone';
|
|
307
305
|
}
|
|
308
|
-
/** Share the session as a secret GitHub gist (`/share`).
|
|
306
|
+
/** Share the session as a secret GitHub gist (`/share`). Writable-only.
|
|
309
307
|
* Broker handler: exports session to a temp HTML file, shells `gh gist create --secret`,
|
|
310
308
|
* returns the URL in ack.detail. */
|
|
311
309
|
export interface ShareFrame {
|
|
@@ -314,8 +312,8 @@ export interface ShareFrame {
|
|
|
314
312
|
/** Reload credentials + refresh model registry after a viewer-side auth change.
|
|
315
313
|
* Open to any client — reload_auth is an idempotent local re-read that doesn't
|
|
316
314
|
* steer the conversation, so the daemon's canvas-wide fan (one /login → every
|
|
317
|
-
* live broker) can trigger it
|
|
318
|
-
*
|
|
315
|
+
* live broker) can trigger it from an observer connection. Broker handler:
|
|
316
|
+
* services.authStorage.reload() +
|
|
319
317
|
* services.modelRegistry.refresh(). */
|
|
320
318
|
export interface ReloadAuthFrame {
|
|
321
319
|
type: 'reload_auth';
|
|
@@ -327,25 +325,22 @@ export interface GetHumanForkCoordinatesFrame {
|
|
|
327
325
|
type: 'get_human_fork_coordinates';
|
|
328
326
|
id: string;
|
|
329
327
|
}
|
|
330
|
-
export type ClientToBroker = HelloFrame | PromptFrame | SteerFrame | FollowUpFrame | AbortFrame | DeliverFrame | BashFrame |
|
|
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;
|
|
331
329
|
export interface WelcomeFrame {
|
|
332
330
|
type: 'welcome';
|
|
333
331
|
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
|
-
*
|
|
338
|
-
* Phase 4 — Phase 3 always sends it absent/null. */
|
|
335
|
+
/** A dialog already in-flight when this client attached. Populated only for a
|
|
336
|
+
* writable client (an observer can never answer one), so a viewer joining
|
|
337
|
+
* mid-dialog can answer it alongside its peers. */
|
|
339
338
|
pending_dialog?: RpcExtensionUIRequest | null;
|
|
340
339
|
/** The pi agent dir (`~/.pi/agent`) from the broker's process — so the viewer
|
|
341
340
|
* can construct an AuthStorage/ModelRegistry pointing at the SAME auth.json.
|
|
342
341
|
* crtr surface attach is tmux-local only: broker + viewer share a filesystem. */
|
|
343
342
|
agentDir?: string;
|
|
344
343
|
}
|
|
345
|
-
export interface ControlChangedFrame {
|
|
346
|
-
type: 'control_changed';
|
|
347
|
-
controller_id: string | null;
|
|
348
|
-
}
|
|
349
344
|
/** Broadcast to EVERY client after a successful `set_model`/`cycle_model`. pi
|
|
350
345
|
* emits no AgentSessionEvent for a model switch, so without this the new model
|
|
351
346
|
* reaches no viewer at all (the requester gets only a bare ack) and footers
|
|
@@ -375,7 +370,7 @@ export interface ModelChangedFrame {
|
|
|
375
370
|
* promise instead of hanging it — absent on uncorrelated errors (engine drive
|
|
376
371
|
* errors, command-op failures, frame_overflow). There is NO `retryable` field. */
|
|
377
372
|
export type ErrorFrame = BrokerErrorFrame;
|
|
378
|
-
/** Result of a
|
|
373
|
+
/** Result of a command op (§1.3): `for` echoes the op name, `ok` the
|
|
379
374
|
* outcome, `detail` an optional human-readable note. */
|
|
380
375
|
export interface AckFrame {
|
|
381
376
|
type: 'ack';
|
|
@@ -560,7 +555,8 @@ export type ExtensionUIRequestFrame = RpcExtensionUIRequest;
|
|
|
560
555
|
* WITHOUT a client answer. The broker sends this exactly when it resolves a pending
|
|
561
556
|
* dialog ITSELF rather than the client answering it — the extension aborted the
|
|
562
557
|
* request out-of-band (e.g. a local OAuth loopback callback won the race against a
|
|
563
|
-
* still-open manual-paste dialog)
|
|
558
|
+
* still-open manual-paste dialog), the broker-side timeout fired, or ANOTHER writable
|
|
559
|
+
* client answered the fan-out first and this one must close its copy. The client tears
|
|
564
560
|
* down ONLY the overlay whose `id` matches; every other dialog stays exactly as it
|
|
565
561
|
* was, so an unrelated request can never dismiss an unrelated blocking dialog. */
|
|
566
562
|
export interface ExtensionUIDismissFrame {
|
|
@@ -570,7 +566,7 @@ export interface ExtensionUIDismissFrame {
|
|
|
570
566
|
/** Everything the broker can send. Live `AgentSessionEvent`s are relayed
|
|
571
567
|
* verbatim (the broker adds nothing); the broker's own control frames carry
|
|
572
568
|
* non-colliding `type` discriminants. */
|
|
573
|
-
export type BrokerToClient = WelcomeFrame |
|
|
569
|
+
export type BrokerToClient = WelcomeFrame | ModelChangedFrame | ErrorFrame | AckFrame | BrokerDataFrame | BashStartFrame | BashOutputFrame | BashEndFrame | ExtensionUIRequestFrame | ExtensionUIDismissFrame | AgentSessionEvent;
|
|
574
570
|
/** Encode one frame as a single newline-terminated JSON line. */
|
|
575
571
|
export declare function encodeFrame(frame: ClientToBroker | BrokerToClient): string;
|
|
576
572
|
/** 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/
|
|
9
|
+
// the broker's own control frames (welcome/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
|
-
//
|
|
1
|
+
// broker-request.ts — the ONE one-shot writable round-trip against a LIVE
|
|
2
|
+
// node broker over its view.sock: connect → `hello` as controller → send a
|
|
3
|
+
// 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,11 +8,6 @@
|
|
|
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.
|
|
16
11
|
import { randomUUID } from 'node:crypto';
|
|
17
12
|
import { BrokerClient } from '../broker-client/index.js';
|
|
18
13
|
/** How long to wait for the broker's ack. Every frame this carries is local
|
|
@@ -67,7 +62,6 @@ export function oneShotControllerRequest(opts) {
|
|
|
67
62
|
client.on('connect', () => {
|
|
68
63
|
connected = true;
|
|
69
64
|
client.send({ type: 'hello', role: 'controller', client_id: clientId });
|
|
70
|
-
client.send({ type: 'request_control' });
|
|
71
65
|
client.send(opts.frame(frameId));
|
|
72
66
|
});
|
|
73
67
|
client.on('frame', (frame) => {
|
|
@@ -78,8 +72,8 @@ export function oneShotControllerRequest(opts) {
|
|
|
78
72
|
finish(() => reject(new Error(frame.detail ?? `broker rejected ${opts.what}`)));
|
|
79
73
|
}
|
|
80
74
|
else if (frame.type === 'error') {
|
|
81
|
-
// This connection only ever sent hello
|
|
82
|
-
//
|
|
75
|
+
// This connection only ever sent hello + one payload frame, so any
|
|
76
|
+
// error frame here is a response to ours.
|
|
83
77
|
finish(() => reject(new Error(frame.message)));
|
|
84
78
|
}
|
|
85
79
|
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
|
-
* The
|
|
25
|
+
* Route a writable client's `prompt`/`follow_up` frame against the LIVE session
|
|
26
|
+
* state. The client 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,15 +186,19 @@ interface BrokerClient {
|
|
|
186
186
|
* high-water mark). */
|
|
187
187
|
queuedFrames: number;
|
|
188
188
|
}
|
|
189
|
-
/** A blocking dialog awaiting
|
|
190
|
-
* timeout, or the engine's abort.
|
|
189
|
+
/** A blocking dialog awaiting a response from ANY writable client, the
|
|
190
|
+
* broker-side default timeout, or the engine's abort. The dialog is fanned out
|
|
191
|
+
* to every writable client at raise time; this entry is the single settlement
|
|
192
|
+
* point they race for. */
|
|
191
193
|
interface PendingDialog {
|
|
192
|
-
/** The original request
|
|
193
|
-
*
|
|
194
|
-
*
|
|
194
|
+
/** The original request — retained so `welcome.pending_dialog` and the
|
|
195
|
+
* attach-mid-dialog replay can hand a still-pending dialog to a writable
|
|
196
|
+
* client that attaches after the fan-out. */
|
|
195
197
|
request: RpcExtensionUIRequest;
|
|
196
|
-
/**
|
|
197
|
-
* broker-side timeout
|
|
198
|
+
/** Some writable client answered — resolve with its parsed response. Settling
|
|
199
|
+
* clears the broker-side timeout, removes the entry from the registry (so a
|
|
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. */
|
|
198
202
|
resolve: (response: RpcExtensionUIResponse) => void;
|
|
199
203
|
}
|
|
200
204
|
/** Dispose the live engine session if one exists (idempotent). Called by
|
|
@@ -214,11 +218,15 @@ export declare function buildBrokerSession(engine: BrokerEngine, cfg: BrokerSdkC
|
|
|
214
218
|
}>;
|
|
215
219
|
/** Broker-side hooks the UI context needs to route (or noOp) extension dialogs. */
|
|
216
220
|
export interface BrokerDialogDeps {
|
|
217
|
-
/**
|
|
218
|
-
|
|
219
|
-
|
|
221
|
+
/** Every client that may answer a dialog right now — empty when no writable
|
|
222
|
+
* viewer is attached, which is the noOp fallback path. */
|
|
223
|
+
writable: () => BrokerClient[];
|
|
224
|
+
/** Forward a dialog request to one writable client (called once per client in
|
|
225
|
+
* the fan-out). */
|
|
220
226
|
forward: (client: BrokerClient, request: RpcExtensionUIRequest) => void;
|
|
221
|
-
/** Pending-dialog registry, keyed by request id
|
|
227
|
+
/** Pending-dialog registry, keyed by request id. ONE entry per dialog no matter
|
|
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. */
|
|
222
230
|
pending: Map<string, PendingDialog>;
|
|
223
231
|
/** Broadcast a non-blocking display frame (setStatus/setWidget/setTitle) to ALL
|
|
224
232
|
* viewers — the relay path for pi's fire-and-forget extension-UI surface. */
|