@ggui-ai/iframe-runtime 0.1.0-rc.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/LICENSE +201 -0
- package/README.md +33 -0
- package/dist/bootstrap.d.ts +72 -0
- package/dist/bootstrap.js +500 -0
- package/dist/canvas/animator/animator-host.d.ts +63 -0
- package/dist/canvas/animator/animator-host.js +105 -0
- package/dist/canvas/animator/animator-pill.d.ts +42 -0
- package/dist/canvas/animator/animator-pill.js +119 -0
- package/dist/canvas/animator/state-machine.d.ts +106 -0
- package/dist/canvas/animator/state-machine.js +150 -0
- package/dist/canvas/canvas-shell.d.ts +77 -0
- package/dist/canvas/canvas-shell.js +117 -0
- package/dist/canvas/display-mode-policy.d.ts +104 -0
- package/dist/canvas/display-mode-policy.js +98 -0
- package/dist/canvas/mount.d.ts +150 -0
- package/dist/canvas/mount.js +202 -0
- package/dist/canvas/nav-stack.d.ts +133 -0
- package/dist/canvas/nav-stack.js +172 -0
- package/dist/channel-transport.d.ts +182 -0
- package/dist/channel-transport.js +344 -0
- package/dist/channels/channel-payload.d.ts +20 -0
- package/dist/channels/channel-payload.js +34 -0
- package/dist/channels/data.d.ts +48 -0
- package/dist/channels/data.js +103 -0
- package/dist/channels/drain-ack.d.ts +13 -0
- package/dist/channels/drain-ack.js +15 -0
- package/dist/channels/feedback.d.ts +14 -0
- package/dist/channels/feedback.js +19 -0
- package/dist/channels/index.d.ts +7 -0
- package/dist/channels/index.js +7 -0
- package/dist/channels/props-update.d.ts +53 -0
- package/dist/channels/props-update.js +130 -0
- package/dist/channels/push.d.ts +47 -0
- package/dist/channels/push.js +54 -0
- package/dist/channels/system.d.ts +26 -0
- package/dist/channels/system.js +44 -0
- package/dist/compiled-validators.d.ts +40 -0
- package/dist/compiled-validators.js +74 -0
- package/dist/context-observer.d.ts +205 -0
- package/dist/context-observer.js +312 -0
- package/dist/gadget-loader.d.ts +118 -0
- package/dist/gadget-loader.js +136 -0
- package/dist/globals.d.ts +234 -0
- package/dist/globals.js +101 -0
- package/dist/host-context-emitter.d.ts +91 -0
- package/dist/host-context-emitter.js +213 -0
- package/dist/iframe-runtime.js +261 -0
- package/dist/index.d.ts +38 -0
- package/dist/index.js +4 -0
- package/dist/lifecycle.d.ts +66 -0
- package/dist/lifecycle.js +49 -0
- package/dist/mcp-app-iframe-host.d.ts +69 -0
- package/dist/mcp-app-iframe-host.js +214 -0
- package/dist/observability.d.ts +225 -0
- package/dist/observability.js +28 -0
- package/dist/protocol-error.d.ts +199 -0
- package/dist/protocol-error.js +116 -0
- package/dist/provisional-renderer.d.ts +62 -0
- package/dist/provisional-renderer.js +323 -0
- package/dist/react-renderer.d.ts +95 -0
- package/dist/react-renderer.js +343 -0
- package/dist/registry-subscribe.d.ts +102 -0
- package/dist/registry-subscribe.js +319 -0
- package/dist/runtime.d.ts +531 -0
- package/dist/runtime.js +2623 -0
- package/dist/server.d.ts +36 -0
- package/dist/server.js +72 -0
- package/dist/stack-item-renderer.d.ts +103 -0
- package/dist/stack-item-renderer.js +270 -0
- package/dist/stack.d.ts +90 -0
- package/dist/stack.js +118 -0
- package/dist/status-dom.d.ts +72 -0
- package/dist/status-dom.js +51 -0
- package/dist/system-cards/NoCredentialsCard.d.ts +33 -0
- package/dist/system-cards/NoCredentialsCard.js +189 -0
- package/dist/system-cards/ProtocolProbeCard.d.ts +64 -0
- package/dist/system-cards/ProtocolProbeCard.js +463 -0
- package/dist/system-cards/QrCode.d.ts +46 -0
- package/dist/system-cards/QrCode.js +55 -0
- package/dist/system-cards/host-detect.d.ts +31 -0
- package/dist/system-cards/host-detect.js +108 -0
- package/dist/system-cards/host-intents.d.ts +85 -0
- package/dist/system-cards/host-intents.js +170 -0
- package/dist/system-cards/index.d.ts +70 -0
- package/dist/system-cards/index.js +134 -0
- package/dist/types.d.ts +69 -0
- package/dist/types.js +3 -0
- package/dist/validation.d.ts +83 -0
- package/dist/validation.js +106 -0
- package/dist/wire-config.d.ts +214 -0
- package/dist/wire-config.js +290 -0
- package/package.json +81 -0
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NavStackModel.
|
|
3
|
+
*
|
|
4
|
+
* Canvas-local navigation stack. **Distinct from the server's
|
|
5
|
+
* `StackModel`**:
|
|
6
|
+
*
|
|
7
|
+
* - Server's `StackModel` (`@ggui-ai/iframe-runtime/stack.ts`)
|
|
8
|
+
* tracks every stack item the agent has pushed in this session.
|
|
9
|
+
* The durable shape; mutated only by server-side push/pop/replace
|
|
10
|
+
* deliveries.
|
|
11
|
+
* - NavStackModel tracks the USER'S NAVIGATION HISTORY through
|
|
12
|
+
* those items. When the user back-navigates, the popped item
|
|
13
|
+
* leaves navStack but stays in server's StackModel.
|
|
14
|
+
*
|
|
15
|
+
* These can diverge:
|
|
16
|
+
*
|
|
17
|
+
* ```
|
|
18
|
+
* server StackModel: [a, b, c] ← all items the agent has pushed
|
|
19
|
+
* navStack: [a, b] ← user has back-navigated away from c
|
|
20
|
+
* activeItem: b ← top of navStack; the mounted screen
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* The server-side `activeStackItemId` field (on AckPayload +
|
|
24
|
+
* SessionRecord) follows navStack-top via `canvas_navigated` outbound
|
|
25
|
+
* envelopes, so `ggui_consume` returns actions for the active item
|
|
26
|
+
* regardless of what the agent has pushed since.
|
|
27
|
+
*
|
|
28
|
+
* Pure model — no DOM, no React. Methods return the new state (or
|
|
29
|
+
* mutate in-place; v1 mutates for simplicity since the canvas shell
|
|
30
|
+
* holds the only reference). The renderer consumes the model via a
|
|
31
|
+
* version counter (bumped on every mutation) so React picks up
|
|
32
|
+
* changes without deep equality.
|
|
33
|
+
*
|
|
34
|
+
* Two listener seams (distinct semantics):
|
|
35
|
+
*
|
|
36
|
+
* - `onMutation` fires on EVERY state mutation including in-place
|
|
37
|
+
* replace (ggui_update patch). The canvas shell uses this to
|
|
38
|
+
* re-read the version counter and re-render — without it, a
|
|
39
|
+
* props-only update on the active item bumps the version but
|
|
40
|
+
* React never schedules a render and the screen shows stale props.
|
|
41
|
+
* - `onNavChange` fires only when the active item actually changes
|
|
42
|
+
* (new push, pop, reset that lands on a different top). The
|
|
43
|
+
* live-channel `canvas_navigated` envelope is emitted from here, so
|
|
44
|
+
* an in-place replace doesn't spuriously tell the server the
|
|
45
|
+
* user back-navigated.
|
|
46
|
+
*/
|
|
47
|
+
export class NavStackModel {
|
|
48
|
+
items = [];
|
|
49
|
+
mutationListener = null;
|
|
50
|
+
navChangeListener = null;
|
|
51
|
+
versionCounter = 0;
|
|
52
|
+
/**
|
|
53
|
+
* Replace the entire nav stack. Used on subscribe ack to seed
|
|
54
|
+
* navStack from the server's `activeStackItemId` + the snapshot's
|
|
55
|
+
* stack array.
|
|
56
|
+
*/
|
|
57
|
+
reset(items) {
|
|
58
|
+
const previous = this.peek();
|
|
59
|
+
this.items = [...items];
|
|
60
|
+
this.versionCounter += 1;
|
|
61
|
+
this.mutationListener?.();
|
|
62
|
+
this.navChangeListener?.({
|
|
63
|
+
direction: 'forward',
|
|
64
|
+
activeItemId: this.peek()?.id ?? null,
|
|
65
|
+
previousActiveItemId: previous?.id ?? null,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Append an item to the top. Called when a `stack_item_appended`
|
|
70
|
+
* envelope arrives (server pushed a new item).
|
|
71
|
+
*
|
|
72
|
+
* If an item with the same id already exists, the existing entry is
|
|
73
|
+
* REPLACED IN PLACE (preserves nav position) — matches the server-
|
|
74
|
+
* side StackModel's upsert semantics so `ggui_update` patches don't
|
|
75
|
+
* shift the user's nav state. The mutation listener still fires (so
|
|
76
|
+
* React re-renders with the new props) but the nav-change listener
|
|
77
|
+
* is suppressed (active item hasn't changed).
|
|
78
|
+
*/
|
|
79
|
+
push(item) {
|
|
80
|
+
const existingIdx = this.items.findIndex((i) => i.id === item.id);
|
|
81
|
+
if (existingIdx >= 0) {
|
|
82
|
+
this.items[existingIdx] = item;
|
|
83
|
+
this.versionCounter += 1;
|
|
84
|
+
this.mutationListener?.();
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const previous = this.peek();
|
|
88
|
+
this.items.push(item);
|
|
89
|
+
this.versionCounter += 1;
|
|
90
|
+
this.mutationListener?.();
|
|
91
|
+
this.navChangeListener?.({
|
|
92
|
+
direction: 'forward',
|
|
93
|
+
activeItemId: item.id,
|
|
94
|
+
previousActiveItemId: previous?.id ?? null,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Pop the top item. Returns the new active item (or null if the
|
|
99
|
+
* stack is now empty). No-op if already empty.
|
|
100
|
+
*/
|
|
101
|
+
pop() {
|
|
102
|
+
if (this.items.length === 0)
|
|
103
|
+
return null;
|
|
104
|
+
const popped = this.items.pop() ?? null;
|
|
105
|
+
this.versionCounter += 1;
|
|
106
|
+
const active = this.peek();
|
|
107
|
+
this.mutationListener?.();
|
|
108
|
+
this.navChangeListener?.({
|
|
109
|
+
direction: 'back',
|
|
110
|
+
activeItemId: active?.id ?? null,
|
|
111
|
+
previousActiveItemId: popped?.id ?? null,
|
|
112
|
+
});
|
|
113
|
+
return active;
|
|
114
|
+
}
|
|
115
|
+
/** Top of the nav stack (the currently-mounted item), or null when empty. */
|
|
116
|
+
peek() {
|
|
117
|
+
return this.items[this.items.length - 1] ?? null;
|
|
118
|
+
}
|
|
119
|
+
/** Read-only snapshot of the full nav stack in order (root → top). */
|
|
120
|
+
snapshot() {
|
|
121
|
+
return this.items;
|
|
122
|
+
}
|
|
123
|
+
/** Item count. */
|
|
124
|
+
size() {
|
|
125
|
+
return this.items.length;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Monotonic mutation counter — bumped on every push/pop/reset.
|
|
129
|
+
* React consumers can use this as the `useSyncExternalStore`
|
|
130
|
+
* snapshot value: comparing version numbers is O(1).
|
|
131
|
+
*/
|
|
132
|
+
version() {
|
|
133
|
+
return this.versionCounter;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Install a listener that fires on every state mutation (push, pop,
|
|
137
|
+
* reset, in-place replace). React consumers bridge this to a
|
|
138
|
+
* version-counter `useState` so the component re-renders for every
|
|
139
|
+
* change — including in-place replaces from `ggui_update` patches
|
|
140
|
+
* where the active item is unchanged but its props have moved.
|
|
141
|
+
*
|
|
142
|
+
* Single-subscriber model — the canvas shell is the only caller.
|
|
143
|
+
* Returns an unsubscribe function.
|
|
144
|
+
*/
|
|
145
|
+
onMutation(listener) {
|
|
146
|
+
this.mutationListener = listener;
|
|
147
|
+
return () => {
|
|
148
|
+
if (this.mutationListener === listener)
|
|
149
|
+
this.mutationListener = null;
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Install a listener that fires only on nav-changing mutations
|
|
154
|
+
* (push of new id, pop, reset). Used by the canvas shell to emit
|
|
155
|
+
* the live-channel `canvas_navigated` envelope so the server can
|
|
156
|
+
* update `activeStackItemId` and abort in-flight cold-gen for the
|
|
157
|
+
* previous active item.
|
|
158
|
+
*
|
|
159
|
+
* In-place replaces from `ggui_update` patches do NOT fire this
|
|
160
|
+
* listener — the active item hasn't moved. Use {@link onMutation}
|
|
161
|
+
* for React re-render signaling.
|
|
162
|
+
*
|
|
163
|
+
* Single-subscriber model. Returns an unsubscribe function.
|
|
164
|
+
*/
|
|
165
|
+
onNavChange(listener) {
|
|
166
|
+
this.navChangeListener = listener;
|
|
167
|
+
return () => {
|
|
168
|
+
if (this.navChangeListener === listener)
|
|
169
|
+
this.navChangeListener = null;
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-channel transport router.
|
|
3
|
+
*
|
|
4
|
+
* Owns the runtime-side half of the dual-transport stream-channel
|
|
5
|
+
* negotiation. For each `streamSpec[channelName]` entry that
|
|
6
|
+
* declares a `source.tool`, the router picks ONE of two transports
|
|
7
|
+
* per channel:
|
|
8
|
+
*
|
|
9
|
+
* - **WS subscribe** — when `source.tool` is in the bootstrap's
|
|
10
|
+
* `streamWebSocketLocalTools` allowlist. The router sends a
|
|
11
|
+
* `channel_subscribe` WS frame; the server polls the tool and
|
|
12
|
+
* fans out `channel_payload` frames. Cheapest path: zero LLM
|
|
13
|
+
* traffic, zero parent-iframe postMessage round-trips per
|
|
14
|
+
* payload.
|
|
15
|
+
*
|
|
16
|
+
* - **Iframe polling** — fallback for every other channel. The
|
|
17
|
+
* router runs a setInterval that fires `tools/call` against
|
|
18
|
+
* the parent MCP host (Pattern α — direct, no bridge). Works
|
|
19
|
+
* for any tool the agent has access to, including third-party
|
|
20
|
+
* MCP servers the local pod can't subscribe-for.
|
|
21
|
+
*
|
|
22
|
+
* The dispatch decision is per-channel + per-render (each new push
|
|
23
|
+
* brings a potentially-new contract; we re-run the routing). The
|
|
24
|
+
* StreamBus + `useStream(channel)` API stay unchanged — the router
|
|
25
|
+
* adapts payloads from both transports into the same envelope shape
|
|
26
|
+
* the bus already accepts.
|
|
27
|
+
*
|
|
28
|
+
* **Reconnect handling** — when the WS drops, every WS-bound channel:
|
|
29
|
+
* 1. Immediately falls back to iframe polling (no delay). The user
|
|
30
|
+
* keeps seeing fresh data while the WS reconnect ladder runs.
|
|
31
|
+
* 2. On successful reconnect → re-send `channel_subscribe`. On the
|
|
32
|
+
* first `channel_payload` from that channel, cancel the polling
|
|
33
|
+
* fallback (don't double-deliver).
|
|
34
|
+
*
|
|
35
|
+
* The WS-level exponential backoff (1s → 2s → 4s → ... cap 60s) lives
|
|
36
|
+
* on the {@link RendererWebSocketManager}. The router only watches
|
|
37
|
+
* status transitions; it doesn't own the reconnect schedule.
|
|
38
|
+
*
|
|
39
|
+
* **Idempotence** — re-subscribing the same `(stackItemId, channelName)`
|
|
40
|
+
* pair is server-side idempotent (the server's `ChannelSubscriptionState`
|
|
41
|
+
* replaces in place). Clients can re-send on reconnect without bookkeeping
|
|
42
|
+
* a "did we already subscribe?" gate.
|
|
43
|
+
*
|
|
44
|
+
* **Boundary** — this module produces a `StreamEnvelope` for every payload
|
|
45
|
+
* (both transports) and emits on the supplied bus. It does NOT validate
|
|
46
|
+
* payloads against `streamSpec[ch].schema` — the existing
|
|
47
|
+
* `validateInboundStreamPayload` path in `runtime.ts` does that on the
|
|
48
|
+
* `data` frame side. For WS `channel_payload` frames we route directly
|
|
49
|
+
* to the bus because the SERVER has already validated server-side; for
|
|
50
|
+
* iframe-poll payloads we trust the tool's return value (matches today's
|
|
51
|
+
* un-routed channel posture pre-1c, where any payload that landed on the
|
|
52
|
+
* data frame was treated as authoritative).
|
|
53
|
+
*/
|
|
54
|
+
import type { JsonObject, JsonValue, StreamSpec } from '@ggui-ai/protocol';
|
|
55
|
+
import type { WebSocketMessage } from '@ggui-ai/protocol/transport/websocket';
|
|
56
|
+
import type { StreamBus } from './wire-config.js';
|
|
57
|
+
/**
|
|
58
|
+
* Default polling cadence for the iframe-polling transport (ms).
|
|
59
|
+
* Mirrors the server-side default on
|
|
60
|
+
* {@link DEFAULT_CHANNEL_POLL_DEFAULT_MS} in
|
|
61
|
+
* `packages/mcp-server/src/session-channel.ts`. Conservative: 10s
|
|
62
|
+
* matches a "data is fresh enough" bar without burning the parent
|
|
63
|
+
* host's `tools/call` quota.
|
|
64
|
+
*/
|
|
65
|
+
export declare const DEFAULT_IFRAME_POLL_INTERVAL_MS = 10000;
|
|
66
|
+
/**
|
|
67
|
+
* Sender for outbound WS frames. The renderer's
|
|
68
|
+
* {@link RendererWebSocketManager.send} fits structurally — pass
|
|
69
|
+
* `manager.send.bind(manager)` or the buffered manager shim.
|
|
70
|
+
*/
|
|
71
|
+
export type WsSender = (msg: WebSocketMessage) => void;
|
|
72
|
+
/**
|
|
73
|
+
* Iframe → parent `tools/call` invoker. Resolves with the tool's
|
|
74
|
+
* structured-content output (parsed `JsonValue`) or rejects on
|
|
75
|
+
* transport-level failure. The router will swallow the failure +
|
|
76
|
+
* keep the poll loop running (next tick may succeed).
|
|
77
|
+
*/
|
|
78
|
+
export type ToolsCallInvoker = (args: {
|
|
79
|
+
readonly toolName: string;
|
|
80
|
+
readonly args: JsonObject;
|
|
81
|
+
}) => Promise<JsonValue>;
|
|
82
|
+
/**
|
|
83
|
+
* Options for {@link createChannelTransportRouter}.
|
|
84
|
+
*/
|
|
85
|
+
export interface ChannelTransportRouterOptions {
|
|
86
|
+
/** Session id the router scopes channel_subscribe frames against. */
|
|
87
|
+
readonly sessionId: string;
|
|
88
|
+
/** App (tenant) id paired with sessionId on the subscribe frame. */
|
|
89
|
+
readonly appId: string;
|
|
90
|
+
/**
|
|
91
|
+
* Allowlist of `source.tool` names this server can subscribe-for
|
|
92
|
+
* over WS. From `bootstrap.streamWebSocketLocalTools`. Absent or
|
|
93
|
+
* empty ⇒ every channel routes through the iframe-poll fallback.
|
|
94
|
+
*/
|
|
95
|
+
readonly streamWebSocketLocalTools?: readonly string[];
|
|
96
|
+
/** Outbound WS sender. */
|
|
97
|
+
readonly send: WsSender;
|
|
98
|
+
/** Iframe → parent `tools/call` proxy. */
|
|
99
|
+
readonly toolsCall: ToolsCallInvoker;
|
|
100
|
+
/** StreamBus the router emits envelopes onto. */
|
|
101
|
+
readonly streamBus: StreamBus;
|
|
102
|
+
/**
|
|
103
|
+
* Default poll cadence (ms) for the iframe-polling transport.
|
|
104
|
+
* Overridden per-channel via `streamSpec[name].source.pollIntervalMs`
|
|
105
|
+
* when authored. Falls back to {@link DEFAULT_IFRAME_POLL_INTERVAL_MS}
|
|
106
|
+
* when neither the spec nor the option supplies one.
|
|
107
|
+
*/
|
|
108
|
+
readonly defaultPollIntervalMs?: number;
|
|
109
|
+
/**
|
|
110
|
+
* Observability sink — fires once per transport pick + once per
|
|
111
|
+
* disconnect/reconnect-fallback transition. Optional; absent ⇒
|
|
112
|
+
* silent.
|
|
113
|
+
*/
|
|
114
|
+
readonly onObserve?: (event: ChannelTransportEvent) => void;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Observability events the router emits. Hosts that wire `onObserve`
|
|
118
|
+
* see these alongside the renderer's other observability events
|
|
119
|
+
* (`subscribe-failed`, `wired-tool-invoked`, …). The shape stays
|
|
120
|
+
* narrow + non-breaking so future events can extend the union.
|
|
121
|
+
*/
|
|
122
|
+
export type ChannelTransportEvent = {
|
|
123
|
+
readonly kind: 'channel-transport-picked';
|
|
124
|
+
readonly stackItemId: string;
|
|
125
|
+
readonly channelName: string;
|
|
126
|
+
readonly transport: 'ws' | 'poll';
|
|
127
|
+
} | {
|
|
128
|
+
readonly kind: 'channel-transport-fallback';
|
|
129
|
+
readonly stackItemId: string;
|
|
130
|
+
readonly channelName: string;
|
|
131
|
+
readonly reason: 'ws-disconnect' | 'channel-not-local';
|
|
132
|
+
} | {
|
|
133
|
+
readonly kind: 'channel-transport-resubscribed';
|
|
134
|
+
readonly stackItemId: string;
|
|
135
|
+
readonly channelName: string;
|
|
136
|
+
};
|
|
137
|
+
/**
|
|
138
|
+
* Router handle returned by {@link createChannelTransportRouter}.
|
|
139
|
+
*/
|
|
140
|
+
export interface ChannelTransportRouter {
|
|
141
|
+
/**
|
|
142
|
+
* Apply a new stack item's `streamSpec`. Idempotent against
|
|
143
|
+
* re-applying the same shape (no churn). Channels added/removed
|
|
144
|
+
* across renders fire transport pick / teardown accordingly.
|
|
145
|
+
*
|
|
146
|
+
* The legacy `data` frame path on `streamSpec[ch]` entries
|
|
147
|
+
* WITHOUT `source.tool` is unaffected — the router only manages
|
|
148
|
+
* the source-fed subset.
|
|
149
|
+
*/
|
|
150
|
+
readonly applyStackItem: (item: {
|
|
151
|
+
readonly stackItemId: string;
|
|
152
|
+
readonly streamSpec?: StreamSpec;
|
|
153
|
+
}) => void;
|
|
154
|
+
/**
|
|
155
|
+
* Forward a single inbound WS frame. The router consumes
|
|
156
|
+
* `channel_payload` + `channel_error` types and ignores everything
|
|
157
|
+
* else. Returns `true` iff the frame was consumed (so the caller
|
|
158
|
+
* can short-circuit), `false` otherwise.
|
|
159
|
+
*/
|
|
160
|
+
readonly handleWsFrame: (msg: WebSocketMessage) => boolean;
|
|
161
|
+
/**
|
|
162
|
+
* Notify the router of a WS connection status transition. Used to:
|
|
163
|
+
* - On `'disconnected'` / `'reconnecting'`: start polling fallback
|
|
164
|
+
* for every WS-bound channel (no delay).
|
|
165
|
+
* - On `'connected'` AFTER a prior disconnect: re-send
|
|
166
|
+
* `channel_subscribe` for every WS-bound channel.
|
|
167
|
+
*/
|
|
168
|
+
readonly onWsStatusChange: (status: 'connecting' | 'connected' | 'disconnected' | 'reconnecting') => void;
|
|
169
|
+
/**
|
|
170
|
+
* Tear down every active subscription + timer. Called on
|
|
171
|
+
* `triadWiring.teardown`.
|
|
172
|
+
*/
|
|
173
|
+
readonly dispose: () => void;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Factory.
|
|
177
|
+
*
|
|
178
|
+
* Per-channel transport routing — owns the `(stackItemId, channelName)`
|
|
179
|
+
* registry, the WS-vs-poll decision per channel, and the cross-state
|
|
180
|
+
* transitions (disconnect → poll fallback, reconnect → re-subscribe).
|
|
181
|
+
*/
|
|
182
|
+
export declare function createChannelTransportRouter(opts: ChannelTransportRouterOptions): ChannelTransportRouter;
|