@ggui-ai/iframe-runtime 0.1.0-rc.3 → 0.2.0-alpha.3
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 +7 -6
- package/dist/channel-transport.d.ts +15 -15
- package/dist/channel-transport.js +32 -35
- package/dist/channels/channel-payload.d.ts +2 -2
- package/dist/channels/channel-payload.js +1 -1
- package/dist/channels/data.d.ts +14 -19
- package/dist/channels/data.js +13 -31
- package/dist/channels/drain-ack.d.ts +2 -2
- package/dist/channels/drain-ack.js +1 -1
- package/dist/channels/feedback.d.ts +1 -1
- package/dist/channels/index.d.ts +1 -1
- package/dist/channels/index.js +1 -1
- package/dist/channels/props-update.d.ts +33 -37
- package/dist/channels/props-update.js +30 -106
- package/dist/channels/render-frame.d.ts +52 -0
- package/dist/channels/render-frame.js +53 -0
- package/dist/channels/system.d.ts +1 -1
- package/dist/compiled-validators.d.ts +15 -0
- package/dist/compiled-validators.js +31 -0
- package/dist/context-observer.d.ts +68 -8
- package/dist/context-observer.js +25 -45
- package/dist/events-polling.d.ts +95 -0
- package/dist/events-polling.js +143 -0
- package/dist/gadget-loader.d.ts +1 -1
- package/dist/globals.d.ts +4 -4
- package/dist/host-context-emitter.d.ts +84 -20
- package/dist/host-context-emitter.js +159 -29
- package/dist/iframe-runtime.js +98 -59
- package/dist/index.d.ts +18 -13
- package/dist/index.js +1 -1
- package/dist/lifecycle.d.ts +2 -2
- package/dist/lifecycle.js +5 -5
- package/dist/mcp-app-iframe-host.d.ts +9 -9
- package/dist/mcp-app-iframe-host.js +16 -17
- package/dist/meta-parse.d.ts +44 -0
- package/dist/meta-parse.js +390 -0
- package/dist/observability.d.ts +6 -6
- package/dist/protocol-error.d.ts +7 -7
- package/dist/provisional-renderer.d.ts +1 -1
- package/dist/provisional-renderer.js +1 -1
- package/dist/react-renderer.d.ts +10 -11
- package/dist/react-renderer.js +8 -8
- package/dist/registry-subscribe.d.ts +27 -9
- package/dist/registry-subscribe.js +32 -27
- package/dist/render-item.d.ts +83 -0
- package/dist/{stack-item-renderer.js → render-item.js} +106 -106
- package/dist/runtime.d.ts +183 -131
- package/dist/runtime.js +1264 -1330
- package/dist/status-dom.d.ts +52 -47
- package/dist/status-dom.js +51 -34
- package/dist/system-cards/NoCredentialsCard.d.ts +1 -1
- package/dist/system-cards/NoCredentialsCard.js +1 -1
- package/dist/system-cards/ProtocolProbeCard.js +2 -2
- package/dist/system-cards/index.d.ts +4 -4
- package/dist/system-cards/index.js +4 -4
- package/dist/types.d.ts +70 -34
- package/dist/types.js +1 -1
- package/dist/validation.d.ts +2 -2
- package/dist/validation.js +2 -2
- package/dist/wire-config.d.ts +54 -82
- package/dist/wire-config.js +61 -93
- package/package.json +9 -8
- package/dist/bootstrap.d.ts +0 -72
- package/dist/bootstrap.js +0 -500
- package/dist/canvas/animator/animator-host.d.ts +0 -63
- package/dist/canvas/animator/animator-host.js +0 -105
- package/dist/canvas/animator/animator-pill.d.ts +0 -42
- package/dist/canvas/animator/animator-pill.js +0 -119
- package/dist/canvas/animator/state-machine.d.ts +0 -106
- package/dist/canvas/animator/state-machine.js +0 -150
- package/dist/canvas/canvas-shell.d.ts +0 -77
- package/dist/canvas/canvas-shell.js +0 -117
- package/dist/canvas/display-mode-policy.d.ts +0 -104
- package/dist/canvas/display-mode-policy.js +0 -98
- package/dist/canvas/mount.d.ts +0 -150
- package/dist/canvas/mount.js +0 -202
- package/dist/canvas/nav-stack.d.ts +0 -133
- package/dist/canvas/nav-stack.js +0 -172
- package/dist/channels/push.d.ts +0 -47
- package/dist/channels/push.js +0 -54
- package/dist/stack-item-renderer.d.ts +0 -103
- package/dist/stack.d.ts +0 -90
- package/dist/stack.js +0 -118
package/dist/observability.d.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* protocol violation or transport error).
|
|
8
8
|
* - `ObservabilityEvent` fires on HAPPY PATHS + failures alike
|
|
9
9
|
* (telemetry + inspection signal the host can display in a
|
|
10
|
-
*
|
|
10
|
+
* RenderInspector-style view).
|
|
11
11
|
*
|
|
12
12
|
* The host wrapper passes events through to `onObserve` opaquely.
|
|
13
13
|
*
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
export type ObservabilityEvent = WiredToolInvokedEvent | ContractErrorEmittedEvent | SchemaVersionMismatchEvent | SubscribeFailedEvent | AuthRequiredEvent | ChannelTransportPickedEvent | ChannelTransportFallbackEvent | ChannelTransportResubscribedEvent | UnknownObservabilityEvent;
|
|
31
31
|
/**
|
|
32
32
|
* Fired when a wired action successfully dispatched to a registered
|
|
33
|
-
* MCP tool. Hosts surface this as a dispatch row in
|
|
33
|
+
* MCP tool. Hosts surface this as a dispatch row in RenderInspector.
|
|
34
34
|
* Emitted by the server-side router (session-channel.ts) via
|
|
35
35
|
* postMessage relay through the renderer.
|
|
36
36
|
*
|
|
@@ -137,14 +137,14 @@ export interface AuthRequiredEvent {
|
|
|
137
137
|
/**
|
|
138
138
|
* Fired by the channel-transport router when it picks a transport
|
|
139
139
|
* for a `streamSpec[ch].source.tool` channel. Hosts can inspect the
|
|
140
|
-
* WS-vs-poll decision per channel in the
|
|
140
|
+
* WS-vs-poll decision per channel in the RenderInspector activity
|
|
141
141
|
* feed.
|
|
142
142
|
*
|
|
143
143
|
* @public
|
|
144
144
|
*/
|
|
145
145
|
export interface ChannelTransportPickedEvent {
|
|
146
146
|
readonly kind: 'channel-transport-picked';
|
|
147
|
-
readonly
|
|
147
|
+
readonly renderId: string;
|
|
148
148
|
readonly channelName: string;
|
|
149
149
|
readonly transport: 'ws' | 'poll';
|
|
150
150
|
}
|
|
@@ -159,7 +159,7 @@ export interface ChannelTransportPickedEvent {
|
|
|
159
159
|
*/
|
|
160
160
|
export interface ChannelTransportFallbackEvent {
|
|
161
161
|
readonly kind: 'channel-transport-fallback';
|
|
162
|
-
readonly
|
|
162
|
+
readonly renderId: string;
|
|
163
163
|
readonly channelName: string;
|
|
164
164
|
readonly reason: 'ws-disconnect' | 'channel-not-local';
|
|
165
165
|
}
|
|
@@ -171,7 +171,7 @@ export interface ChannelTransportFallbackEvent {
|
|
|
171
171
|
*/
|
|
172
172
|
export interface ChannelTransportResubscribedEvent {
|
|
173
173
|
readonly kind: 'channel-transport-resubscribed';
|
|
174
|
-
readonly
|
|
174
|
+
readonly renderId: string;
|
|
175
175
|
readonly channelName: string;
|
|
176
176
|
}
|
|
177
177
|
/**
|
package/dist/protocol-error.d.ts
CHANGED
|
@@ -33,7 +33,7 @@ import { ClientContractViolationError } from '@ggui-ai/wire';
|
|
|
33
33
|
* Consolidates three sources:
|
|
34
34
|
*
|
|
35
35
|
* 1. **Parse-time failures** — every member of
|
|
36
|
-
* `
|
|
36
|
+
* `McpAppAiGguiMetaParseFailureReason` (`MISSING_TOOL_OUTPUT` /
|
|
37
37
|
* `MISSING_META_GGUI_BOOTSTRAP` / `MALFORMED_BOOTSTRAP` /
|
|
38
38
|
* `EXPIRED_BOOTSTRAP`). `MISSING_META_GGUI_BOOTSTRAP` and
|
|
39
39
|
* `BOOTSTRAP_META_MISSING` are synonyms — the latter is the
|
|
@@ -43,15 +43,15 @@ import { ClientContractViolationError } from '@ggui-ai/wire';
|
|
|
43
43
|
* / `WS_HANDSHAKE_FAILED`. Matches `RendererBootFailureReason`.
|
|
44
44
|
*
|
|
45
45
|
* 3. **Transport-observable failures** — `BUNDLE_FETCH_FAILED` /
|
|
46
|
-
* `CSP_VIOLATION` / `
|
|
46
|
+
* `CSP_VIOLATION` / `RENDER_NOT_FOUND` / `AUTH_REJECTED`. These
|
|
47
47
|
* fire before the renderer parse path runs, so they're authored
|
|
48
|
-
* here rather than inside `
|
|
48
|
+
* here rather than inside `McpAppAiGguiMetaParseFailureReason`.
|
|
49
49
|
*
|
|
50
50
|
* `(string & {})` tail keeps the union extensibly-closed — new wire
|
|
51
51
|
* codes can land without a client bump, while IDE autocomplete
|
|
52
52
|
* surfaces the canonical set.
|
|
53
53
|
*/
|
|
54
|
-
export type BootstrapFailureReason = 'MISSING_TOOL_OUTPUT' | 'MISSING_META_GGUI_BOOTSTRAP' | 'BOOTSTRAP_META_MISSING' | 'MALFORMED_BOOTSTRAP' | 'EXPIRED_BOOTSTRAP' | 'UI_INITIALIZE_FAILED' | 'WS_HANDSHAKE_FAILED' | 'UPGRADE_REQUIRED' | 'BUNDLE_FETCH_FAILED' | 'CSP_VIOLATION' | '
|
|
54
|
+
export type BootstrapFailureReason = 'MISSING_TOOL_OUTPUT' | 'MISSING_META_GGUI_BOOTSTRAP' | 'BOOTSTRAP_META_MISSING' | 'MALFORMED_BOOTSTRAP' | 'EXPIRED_BOOTSTRAP' | 'UI_INITIALIZE_FAILED' | 'WS_HANDSHAKE_FAILED' | 'UPGRADE_REQUIRED' | 'BUNDLE_FETCH_FAILED' | 'CSP_VIOLATION' | 'RENDER_NOT_FOUND' | 'AUTH_REJECTED' | (string & {});
|
|
55
55
|
/**
|
|
56
56
|
* Every error the renderer surfaces outward flows through this union.
|
|
57
57
|
*
|
|
@@ -61,7 +61,7 @@ export type BootstrapFailureReason = 'MISSING_TOOL_OUTPUT' | 'MISSING_META_GGUI_
|
|
|
61
61
|
* Includes `retryable` so hosts can decide to reconnect
|
|
62
62
|
* vs. give up.
|
|
63
63
|
* - `auth` — session / token rejection pre-handshake.
|
|
64
|
-
* `
|
|
64
|
+
* `RENDER_NOT_FOUND` / `TOKEN_EXPIRED` /
|
|
65
65
|
* `AUTH_REJECTED` — all terminal; hosts typically
|
|
66
66
|
* escalate to a login flow.
|
|
67
67
|
* - `protocol` — envelope / session-mismatch failures post-handshake.
|
|
@@ -87,7 +87,7 @@ export type ProtocolError = {
|
|
|
87
87
|
readonly message?: string;
|
|
88
88
|
} | {
|
|
89
89
|
readonly kind: 'auth';
|
|
90
|
-
readonly code: '
|
|
90
|
+
readonly code: 'RENDER_NOT_FOUND' | 'TOKEN_EXPIRED' | 'AUTH_REJECTED';
|
|
91
91
|
readonly message?: string;
|
|
92
92
|
} | {
|
|
93
93
|
readonly kind: 'protocol';
|
|
@@ -196,4 +196,4 @@ export declare function fromTransportFailure(code: 'DISCONNECTED' | 'TIMEOUT', r
|
|
|
196
196
|
* Build a `ProtocolError` of kind 'auth' — session / token rejection
|
|
197
197
|
* pre-handshake.
|
|
198
198
|
*/
|
|
199
|
-
export declare function fromAuthFailure(code: '
|
|
199
|
+
export declare function fromAuthFailure(code: 'RENDER_NOT_FOUND' | 'TOKEN_EXPIRED' | 'AUTH_REJECTED', message?: string): ProtocolError;
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
* loading fallback until A2UI's `root` fragment arrives; then
|
|
26
26
|
* the adjacency tree paints.
|
|
27
27
|
* - `suspend()` hides the surface (used when the authoritative
|
|
28
|
-
* render takes over — the
|
|
28
|
+
* render takes over — the render dispatcher in Commit 4
|
|
29
29
|
* toggles this on ReactComponentRenderer mount).
|
|
30
30
|
* - `resume()` re-shows. Envelopes keep accumulating in state
|
|
31
31
|
* while suspended, matching host-SDK behavior.
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
* loading fallback until A2UI's `root` fragment arrives; then
|
|
26
26
|
* the adjacency tree paints.
|
|
27
27
|
* - `suspend()` hides the surface (used when the authoritative
|
|
28
|
-
* render takes over — the
|
|
28
|
+
* render takes over — the render dispatcher in Commit 4
|
|
29
29
|
* toggles this on ReactComponentRenderer mount).
|
|
30
30
|
* - `resume()` re-shows. Envelopes keep accumulating in state
|
|
31
31
|
* while suspended, matching host-SDK behavior.
|
package/dist/react-renderer.d.ts
CHANGED
|
@@ -10,12 +10,12 @@
|
|
|
10
10
|
*
|
|
11
11
|
* Lifecycle seams:
|
|
12
12
|
*
|
|
13
|
-
* - `mount(container,
|
|
13
|
+
* - `mount(container, render)` — replace the container's
|
|
14
14
|
* children with a React root rendering the compiled component.
|
|
15
15
|
* Returns a `ReactRootMount` handle; callers use `update()` to
|
|
16
16
|
* swap props without re-evaluating the module, `unmount()` to
|
|
17
17
|
* tear down.
|
|
18
|
-
* - `update(
|
|
18
|
+
* - `update(render)` — swap props (and/or componentCode). When
|
|
19
19
|
* componentCode changes, re-evaluates; when only props change,
|
|
20
20
|
* re-renders the existing component tree with new props.
|
|
21
21
|
* - `unmount()` — `root.unmount()` + detach observability.
|
|
@@ -23,8 +23,8 @@
|
|
|
23
23
|
* Error handling — ErrorBoundary with auto-retry (AUTO_RETRY_LIMIT +
|
|
24
24
|
* AUTO_RETRY_DELAY) matches the `ReactComponentRenderer` contract.
|
|
25
25
|
* On terminal error, the callback `onRequestRepair` fires; the
|
|
26
|
-
*
|
|
27
|
-
* live-channel envelope
|
|
26
|
+
* render dispatcher (`render-item.ts`) wires this to the
|
|
27
|
+
* `ggui:request-repair` live-channel envelope it dispatches.
|
|
28
28
|
*
|
|
29
29
|
* No JSX in this file — the mount-root code uses
|
|
30
30
|
* `React.createElement` + `React.Fragment` directly so the renderer's
|
|
@@ -33,13 +33,12 @@
|
|
|
33
33
|
import React, { type ReactNode } from 'react';
|
|
34
34
|
export interface ReactRootMountOptions {
|
|
35
35
|
/**
|
|
36
|
-
*
|
|
36
|
+
* Render whose componentCode + props drive the mount. Only
|
|
37
37
|
* `componentCode` + `props` are read here; the caller is
|
|
38
38
|
* responsible for wrapping this with a `<GguiWireProvider>` at the
|
|
39
|
-
* higher-level dispatcher (see `
|
|
40
|
-
* 4).
|
|
39
|
+
* higher-level dispatcher (see `render-item.ts`).
|
|
41
40
|
*/
|
|
42
|
-
readonly
|
|
41
|
+
readonly render: {
|
|
43
42
|
readonly id?: string;
|
|
44
43
|
readonly componentCode: string;
|
|
45
44
|
readonly props?: Record<string, unknown>;
|
|
@@ -56,9 +55,9 @@ export interface ReactRootMountOptions {
|
|
|
56
55
|
readonly onRequestRepair?: (error: Error) => void;
|
|
57
56
|
/**
|
|
58
57
|
* Children injected BETWEEN the mount DOM (scope + CSS) and the
|
|
59
|
-
* evaluated component element. The caller's
|
|
58
|
+
* evaluated component element. The caller's render dispatcher
|
|
60
59
|
* uses this to wrap the eval'd component in `<GguiWireProvider>`
|
|
61
|
-
* so wire hooks resolve per-
|
|
60
|
+
* so wire hooks resolve per-render contracts.
|
|
62
61
|
*
|
|
63
62
|
* When `undefined`, the evaluated component renders directly.
|
|
64
63
|
*/
|
|
@@ -87,7 +86,7 @@ export interface ReactRootMount {
|
|
|
87
86
|
readonly currentCode: string | null;
|
|
88
87
|
}
|
|
89
88
|
/**
|
|
90
|
-
* Mount a React root inside `container` and render `
|
|
89
|
+
* Mount a React root inside `container` and render `render`'s
|
|
91
90
|
* compiled componentCode. The container is fully owned after this
|
|
92
91
|
* call (existing children are replaced).
|
|
93
92
|
*/
|
package/dist/react-renderer.js
CHANGED
|
@@ -10,12 +10,12 @@
|
|
|
10
10
|
*
|
|
11
11
|
* Lifecycle seams:
|
|
12
12
|
*
|
|
13
|
-
* - `mount(container,
|
|
13
|
+
* - `mount(container, render)` — replace the container's
|
|
14
14
|
* children with a React root rendering the compiled component.
|
|
15
15
|
* Returns a `ReactRootMount` handle; callers use `update()` to
|
|
16
16
|
* swap props without re-evaluating the module, `unmount()` to
|
|
17
17
|
* tear down.
|
|
18
|
-
* - `update(
|
|
18
|
+
* - `update(render)` — swap props (and/or componentCode). When
|
|
19
19
|
* componentCode changes, re-evaluates; when only props change,
|
|
20
20
|
* re-renders the existing component tree with new props.
|
|
21
21
|
* - `unmount()` — `root.unmount()` + detach observability.
|
|
@@ -23,8 +23,8 @@
|
|
|
23
23
|
* Error handling — ErrorBoundary with auto-retry (AUTO_RETRY_LIMIT +
|
|
24
24
|
* AUTO_RETRY_DELAY) matches the `ReactComponentRenderer` contract.
|
|
25
25
|
* On terminal error, the callback `onRequestRepair` fires; the
|
|
26
|
-
*
|
|
27
|
-
* live-channel envelope
|
|
26
|
+
* render dispatcher (`render-item.ts`) wires this to the
|
|
27
|
+
* `ggui:request-repair` live-channel envelope it dispatches.
|
|
28
28
|
*
|
|
29
29
|
* No JSX in this file — the mount-root code uses
|
|
30
30
|
* `React.createElement` + `React.Fragment` directly so the renderer's
|
|
@@ -183,7 +183,7 @@ class RcrErrorBoundary extends Component {
|
|
|
183
183
|
}
|
|
184
184
|
}
|
|
185
185
|
/**
|
|
186
|
-
* Mount a React root inside `container` and render `
|
|
186
|
+
* Mount a React root inside `container` and render `render`'s
|
|
187
187
|
* compiled componentCode. The container is fully owned after this
|
|
188
188
|
* call (existing children are replaced).
|
|
189
189
|
*/
|
|
@@ -257,7 +257,7 @@ export async function mountReactRoot(container, opts) {
|
|
|
257
257
|
}
|
|
258
258
|
const componentElement = currentComponent === null
|
|
259
259
|
? null
|
|
260
|
-
: createElement(currentComponent, opts.
|
|
260
|
+
: createElement(currentComponent, opts.render.props ?? {});
|
|
261
261
|
const wrapped = opts.renderWrapper && componentElement !== null
|
|
262
262
|
? opts.renderWrapper(componentElement)
|
|
263
263
|
: componentElement;
|
|
@@ -271,7 +271,7 @@ export async function mountReactRoot(container, opts) {
|
|
|
271
271
|
}, createElement(Fragment, null, wrapped))));
|
|
272
272
|
}
|
|
273
273
|
async function initialEvaluate() {
|
|
274
|
-
const code = currentOpts.
|
|
274
|
+
const code = currentOpts.render.componentCode ?? '';
|
|
275
275
|
if (code.trim().length === 0) {
|
|
276
276
|
currentComponent = null;
|
|
277
277
|
currentCode = null;
|
|
@@ -305,7 +305,7 @@ export async function mountReactRoot(container, opts) {
|
|
|
305
305
|
async update(next) {
|
|
306
306
|
const prevCode = currentCode;
|
|
307
307
|
currentOpts = next;
|
|
308
|
-
const nextCode = next.
|
|
308
|
+
const nextCode = next.render.componentCode ?? '';
|
|
309
309
|
// Props-only change — skip module eval. Re-render with new props.
|
|
310
310
|
if (nextCode === prevCode && currentComponent !== null) {
|
|
311
311
|
renderTree(currentOpts);
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import type { AckPayload, ContractErrorPayload } from '@ggui-ai/protocol';
|
|
2
2
|
import type { ConnectionStatus } from '@ggui-ai/protocol/transport/websocket';
|
|
3
|
-
import type {
|
|
4
|
-
import type { ChannelRegistry, WsTransportHandle } from '@ggui-ai/channel
|
|
3
|
+
import type { McpAppAiGguiRenderMeta } from '@ggui-ai/protocol/integrations/mcp-apps';
|
|
4
|
+
import type { ChannelRegistry, RegistryPollingOptions, WsTransportHandle } from '@ggui-ai/live-channel';
|
|
5
5
|
import { type ProtocolErrorEmitter } from './protocol-error.js';
|
|
6
6
|
import type { ObservabilityEmitter } from './observability.js';
|
|
7
7
|
/**
|
|
8
8
|
* Result of a successful handshake — the live typed transport handle
|
|
9
9
|
* plus the resolved `AckPayload` from the first ack frame. The caller
|
|
10
|
-
* attaches `handle` to the
|
|
11
|
-
* BufferedManagerShim flush) and applies `ack.
|
|
12
|
-
*
|
|
10
|
+
* attaches `handle` to the renderer's outbound send surface (via the
|
|
11
|
+
* BufferedManagerShim flush) and applies `ack.render` to the mounted
|
|
12
|
+
* render slot.
|
|
13
13
|
*/
|
|
14
14
|
export interface RegistrySubscribeHandle {
|
|
15
15
|
readonly handle: WsTransportHandle;
|
|
@@ -20,7 +20,14 @@ export interface RegistrySubscribeHandle {
|
|
|
20
20
|
* concrete implementations; tests pass mocks via the `connectFn` seam.
|
|
21
21
|
*/
|
|
22
22
|
export interface ConnectViaRegistryOptions {
|
|
23
|
-
|
|
23
|
+
/**
|
|
24
|
+
* Render slice (`McpAppAiGguiRenderMeta`) — the live-channel
|
|
25
|
+
* credentials this subscribe path actually needs: `renderId`,
|
|
26
|
+
* `appId`, `wsUrl`, `wsToken`. The rest of the slice (codeUrl /
|
|
27
|
+
* propsJson / contextSlots / etc.) is load-bearing only for the
|
|
28
|
+
* renderer / mount layer.
|
|
29
|
+
*/
|
|
30
|
+
readonly meta: McpAppAiGguiRenderMeta;
|
|
24
31
|
readonly registry: ChannelRegistry;
|
|
25
32
|
/**
|
|
26
33
|
* Fires on every transport-status transition mapped onto the
|
|
@@ -61,17 +68,28 @@ export interface ConnectViaRegistryOptions {
|
|
|
61
68
|
* Optional post-handshake ack callback. Fires for every ack frame
|
|
62
69
|
* AFTER the initial handshake settled — i.e. on every reconnect's
|
|
63
70
|
* subscribe ack. The runtime uses this to reapply the server's
|
|
64
|
-
* authoritative `
|
|
71
|
+
* authoritative `render` snapshot so a render or update that landed
|
|
65
72
|
* during a WS dropout window flows back into the iframe on
|
|
66
73
|
* reconnect.
|
|
67
74
|
*
|
|
68
|
-
* Idempotent at the runtime layer: `
|
|
69
|
-
*
|
|
75
|
+
* Idempotent at the runtime layer: `applyRender(ack.render)` patches
|
|
76
|
+
* in place. Channels re-subscribed by
|
|
70
77
|
* `ChannelTransportRouter.onWsStatusChange('connected')` upstream
|
|
71
78
|
* of this callback, so the per-channel transport state is already
|
|
72
79
|
* coherent by the time the resubscribe-ack arrives.
|
|
73
80
|
*/
|
|
74
81
|
readonly onResubscribeAck?: (ack: AckPayload) => void;
|
|
82
|
+
/**
|
|
83
|
+
* Registry-level polling descriptor (R6). When supplied, the live-
|
|
84
|
+
* channel's `FailoverHandle` uses this as its polling fallback when
|
|
85
|
+
* `WSTransport` reaches `'failed'`. Absent → no polling fallback
|
|
86
|
+
* (WS-only mode; once WS is exhausted, handlers stay inert).
|
|
87
|
+
*
|
|
88
|
+
* Composed at the runtime layer from `meta.pollingUrl` +
|
|
89
|
+
* `meta.lastSequence` (cursor seed) — the live-channel itself is
|
|
90
|
+
* URL- and shape-agnostic.
|
|
91
|
+
*/
|
|
92
|
+
readonly polling?: RegistryPollingOptions;
|
|
75
93
|
}
|
|
76
94
|
/**
|
|
77
95
|
* Open the WS via the registry, send subscribe (composed by the
|
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Registry-driven subscribe orchestration — owns the renderer's
|
|
3
|
-
* "I have a parsed
|
|
4
|
-
* first ack" flow on top of `ChannelRegistry.bind()`.
|
|
3
|
+
* "I have a parsed render-meta, now drive the live-channel handshake
|
|
4
|
+
* to first ack" flow on top of `ChannelRegistry.bind()`.
|
|
5
5
|
*
|
|
6
6
|
* Replaces the pre-B3b `subscribe()` helper + the `RendererWebSocketManager`
|
|
7
7
|
* lifecycle class. The two retired modules paired a hand-rolled WS
|
|
8
8
|
* manager with a separate handshake helper; B3b unifies both behind
|
|
9
|
-
* the `@ggui-ai/channel
|
|
9
|
+
* the `@ggui-ai/live-channel` library so every WS frame type (push,
|
|
10
10
|
* data, props_update, drain_ack, channel_payload, channel_error,
|
|
11
11
|
* system, feedback) flows through one registry-owned transport.
|
|
12
12
|
*
|
|
13
13
|
* Composition:
|
|
14
14
|
*
|
|
15
15
|
* 1. Caller-supplied `ChannelRegistry` (populated by `runtime.ts`'s
|
|
16
|
-
*
|
|
17
|
-
* 2. Compose the WS URL with the
|
|
18
|
-
* `?
|
|
16
|
+
* renderer-hooks `setup()` with every handler the iframe routes).
|
|
17
|
+
* 2. Compose the WS URL with the WS auth token threaded as a
|
|
18
|
+
* `?wsToken=<encoded>` query string (mirrors the pre-B3b
|
|
19
19
|
* `composeWsUrl` shape so server-side upgrade-path auth keeps
|
|
20
20
|
* working).
|
|
21
21
|
* 3. Register `ack` + `error` handlers on the registry BEFORE bind
|
|
@@ -23,7 +23,9 @@
|
|
|
23
23
|
* handshake-resolver closure.
|
|
24
24
|
* 4. Call `registry.bind({bootstrap, onStatusChange})` and capture
|
|
25
25
|
* the returned typed `WsTransportHandle` (narrow on
|
|
26
|
-
* `handle.kind === 'ws'`).
|
|
26
|
+
* `handle.kind === 'ws'`). The live-channel's `bootstrap`
|
|
27
|
+
* argument is its own internal name; the field on the wire is
|
|
28
|
+
* `wsToken`.
|
|
27
29
|
*
|
|
28
30
|
* The handshake resolves on the first `ack` (with the symmetric
|
|
29
31
|
* `CLIENT_SUPPORTED_VERSIONS` check) or rejects on:
|
|
@@ -43,15 +45,15 @@ import { CLIENT_SUPPORTED_VERSIONS, UPGRADE_REQUIRED } from '@ggui-ai/protocol/v
|
|
|
43
45
|
import { UpgradeRequiredError } from '@ggui-ai/protocol/errors/version-mismatch';
|
|
44
46
|
import { fromAuthFailure, fromTransportFailure, fromUpgradeRequired, } from './protocol-error.js';
|
|
45
47
|
/**
|
|
46
|
-
* Compose the WebSocket URL with the
|
|
48
|
+
* Compose the WebSocket URL with the WS auth token threaded as a
|
|
47
49
|
* query parameter. Servers that authenticate the upgrade path read
|
|
48
50
|
* the token from here; the same value also rides inside the subscribe
|
|
49
51
|
* payload so server-side handlers that consume credentials on the
|
|
50
52
|
* subscribe frame see it too.
|
|
51
53
|
*/
|
|
52
|
-
function composeWsUrl(base,
|
|
54
|
+
function composeWsUrl(base, wsToken) {
|
|
53
55
|
const sep = base.indexOf('?') >= 0 ? '&' : '?';
|
|
54
|
-
return `${base}${sep}
|
|
56
|
+
return `${base}${sep}wsToken=${encodeURIComponent(wsToken)}`;
|
|
55
57
|
}
|
|
56
58
|
/**
|
|
57
59
|
* Lift a server-emitted `{type:'error'}` frame into the appropriate
|
|
@@ -92,12 +94,12 @@ function normalizeObservedVersion(value) {
|
|
|
92
94
|
}
|
|
93
95
|
/**
|
|
94
96
|
* Map a pre-ack `error` frame onto a non-UPGRADE_REQUIRED ProtocolError.
|
|
95
|
-
* `
|
|
97
|
+
* `RENDER_NOT_FOUND` / `AUTH_REJECTED` → `'auth'`; every other code
|
|
96
98
|
* → `'protocol'` with the extensibly-closed tail carrying the wire code
|
|
97
99
|
* verbatim.
|
|
98
100
|
*/
|
|
99
101
|
function classifyPreAckError(payload) {
|
|
100
|
-
if (payload.code === '
|
|
102
|
+
if (payload.code === 'RENDER_NOT_FOUND' || payload.code === 'AUTH_REJECTED') {
|
|
101
103
|
return fromAuthFailure(payload.code, payload.message);
|
|
102
104
|
}
|
|
103
105
|
if (payload.code === 'TOKEN_EXPIRED') {
|
|
@@ -130,14 +132,14 @@ export function connectViaRegistry(opts) {
|
|
|
130
132
|
let seenOpen = false;
|
|
131
133
|
const emitProtocolError = opts.onProtocolError ?? (() => { });
|
|
132
134
|
const emitObserve = opts.onObserve ?? (() => { });
|
|
133
|
-
// Defense-in-depth — live mode requires both wsUrl +
|
|
135
|
+
// Defense-in-depth — live mode requires both wsUrl + wsToken. The
|
|
134
136
|
// caller validates this before invoking; reject early with a
|
|
135
137
|
// typed transport failure if either is missing.
|
|
136
|
-
const wsUrl = opts.
|
|
137
|
-
const
|
|
138
|
+
const wsUrl = opts.meta.wsUrl;
|
|
139
|
+
const wsToken = opts.meta.wsToken;
|
|
138
140
|
if (typeof wsUrl !== 'string' || wsUrl.length === 0 ||
|
|
139
|
-
typeof
|
|
140
|
-
const err = new Error('connectViaRegistry:
|
|
141
|
+
typeof wsToken !== 'string' || wsToken.length === 0) {
|
|
142
|
+
const err = new Error('connectViaRegistry: render-meta missing wsUrl/wsToken — live-mode required');
|
|
141
143
|
emitProtocolError(fromTransportFailure('DISCONNECTED', false, err.message));
|
|
142
144
|
reject(err);
|
|
143
145
|
return;
|
|
@@ -265,29 +267,32 @@ export function connectViaRegistry(opts) {
|
|
|
265
267
|
reject(new Error('connectViaRegistry: transport went to failed state before ack'));
|
|
266
268
|
}
|
|
267
269
|
};
|
|
268
|
-
const composedUrl = composeWsUrl(wsUrl,
|
|
269
|
-
// `bind()` selects the transport (WS for wsUrl+
|
|
270
|
-
// the
|
|
271
|
-
//
|
|
272
|
-
//
|
|
270
|
+
const composedUrl = composeWsUrl(wsUrl, wsToken);
|
|
271
|
+
// `bind()` selects the transport (WS for wsUrl+wsToken sessions;
|
|
272
|
+
// the live-channel narrows on its `bootstrap` shape) and starts
|
|
273
|
+
// it. We override `session.wsUrl` with the composed URL so the
|
|
274
|
+
// registry's WS transport gets the wsToken-threaded variant. The
|
|
275
|
+
// live-channel's `bootstrap` arg is its internal name; the field
|
|
276
|
+
// on the wire is `wsToken`.
|
|
273
277
|
void opts.registry
|
|
274
278
|
.bind({
|
|
275
279
|
bootstrap: {
|
|
276
280
|
wsUrl: composedUrl,
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
appId: opts.
|
|
281
|
+
wsToken,
|
|
282
|
+
renderId: opts.meta.renderId,
|
|
283
|
+
appId: opts.meta.appId,
|
|
280
284
|
},
|
|
281
285
|
onStatusChange: mappedStatusCallback,
|
|
286
|
+
...(opts.polling !== undefined ? { polling: opts.polling } : {}),
|
|
282
287
|
})
|
|
283
288
|
.then((bound) => {
|
|
284
|
-
// bound is `AnyTransportHandle`. Composed URL +
|
|
289
|
+
// bound is `AnyTransportHandle`. Composed URL + wsToken means
|
|
285
290
|
// the registry selected WSTransport — `kind === 'ws'` is the
|
|
286
291
|
// structural invariant. Narrow defensively.
|
|
287
292
|
if (bound.kind !== 'ws') {
|
|
288
293
|
if (!settled) {
|
|
289
294
|
settled = true;
|
|
290
|
-
reject(new Error("connectViaRegistry: registry returned non-ws transport despite wsUrl+
|
|
295
|
+
reject(new Error("connectViaRegistry: registry returned non-ws transport despite wsUrl+wsToken render-meta"));
|
|
291
296
|
}
|
|
292
297
|
return;
|
|
293
298
|
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import type { Render } from '@ggui-ai/protocol';
|
|
3
|
+
import type { RenderSeedInput } from './types.js';
|
|
4
|
+
import { type WireConfig } from '@ggui-ai/wire';
|
|
5
|
+
import type { StreamBus } from './wire-config.js';
|
|
6
|
+
export interface RenderItemOptions {
|
|
7
|
+
/** The render to mount. */
|
|
8
|
+
readonly render: Render | RenderSeedInput;
|
|
9
|
+
/** Wire config for this render — caller pre-scopes via the runtime's
|
|
10
|
+
* `buildScopedConfig(render)` (see
|
|
11
|
+
* `wire-config.ts::RootWireConfigBundle`). `null` ⇒ the component
|
|
12
|
+
* mounts without a wire provider (standalone — matches today's
|
|
13
|
+
* DynamicComponent fallback when no GguiSession parent is present). */
|
|
14
|
+
readonly scopedWireConfig: WireConfig | null;
|
|
15
|
+
/** Shared stream bus — provisional renderer subscribes to
|
|
16
|
+
* `_ggui:preview` for this render. */
|
|
17
|
+
readonly streamBus: StreamBus;
|
|
18
|
+
/** Render id — used by the mcp-apps iframe host for proxy URLs. */
|
|
19
|
+
readonly renderId: string;
|
|
20
|
+
/**
|
|
21
|
+
* 3rd-party gadget package names whose direct imports the rewriter
|
|
22
|
+
* must resolve to per-package shims. FALLBACK source used only when
|
|
23
|
+
* the render carries no `gadgetDescriptors` sidecar — i.e. the static
|
|
24
|
+
* seed mount ({@link RenderSeedInput}, which has no descriptors). The
|
|
25
|
+
* caller threads the bootstrap's `meta.gadgets` package names here so a
|
|
26
|
+
* generated component importing a non-STDLIB gadget on a no-WS host
|
|
27
|
+
* (claude.ai / ChatGPT) still resolves its shim on first paint. A
|
|
28
|
+
* WS-delivered full `Render` carries `gadgetDescriptors` and ignores
|
|
29
|
+
* this. STDLIB `@ggui-ai/gadgets` is always rewritten regardless.
|
|
30
|
+
*/
|
|
31
|
+
readonly gadgetPackages?: readonly string[];
|
|
32
|
+
/** ggui-server base URL (for mcp-apps proxy). Empty = same-origin. */
|
|
33
|
+
readonly mcpAppsServerBaseUrl?: string;
|
|
34
|
+
/** Theme id passed to ReactComponentRenderer for scoped CSS. */
|
|
35
|
+
readonly themeId?: string;
|
|
36
|
+
/**
|
|
37
|
+
* Theme color mode. Resolves the dark variant of `themeId` when set
|
|
38
|
+
* to `'dark'`. Falls through to `'light'` if absent — matches the
|
|
39
|
+
* design registry's `getTheme(id, mode?)` default.
|
|
40
|
+
*/
|
|
41
|
+
readonly themeMode?: 'light' | 'dark';
|
|
42
|
+
readonly cssOverrides?: string;
|
|
43
|
+
readonly onError?: (err: Error) => void;
|
|
44
|
+
readonly onRequestRepair?: (err: Error) => void;
|
|
45
|
+
/**
|
|
46
|
+
* Optional outer wrapper applied AROUND the GguiWireProvider — used
|
|
47
|
+
* by `bootProduction` to install the `<ContextStateHost>` provider
|
|
48
|
+
* tree. Composition order:
|
|
49
|
+
*
|
|
50
|
+
* wrapOuter(<GguiWireProvider><userComponent/></GguiWireProvider>)
|
|
51
|
+
*
|
|
52
|
+
* `wrapOuter` returns the wrapped node; passing `undefined` is
|
|
53
|
+
* equivalent to the identity function (no extra wrapping). The
|
|
54
|
+
* self-contained boot path inlines its own ContextStateHost via the
|
|
55
|
+
* top-level `renderWrapper`; this seam exists for the WS-driven
|
|
56
|
+
* renderer path where the per-render React mount is the only mount
|
|
57
|
+
* surface.
|
|
58
|
+
*/
|
|
59
|
+
readonly wrapOuter?: (mountedTree: ReactNode) => ReactNode;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Discriminator for WHICH renderer is currently mounted under this
|
|
63
|
+
* render handle. Transitions: 'provisional' → 'react' when
|
|
64
|
+
* componentCode lands; 'react' → 'react' on code/prop updates;
|
|
65
|
+
* 'mcpApps' is terminal for its lifetime (mcp-apps renders never
|
|
66
|
+
* become components + vice versa).
|
|
67
|
+
*/
|
|
68
|
+
type MountedKind = 'provisional' | 'react' | 'mcpApps' | 'system' | 'none';
|
|
69
|
+
export interface RenderItemHandle {
|
|
70
|
+
/**
|
|
71
|
+
* Apply an update to the render handle. Kind changes (e.g.
|
|
72
|
+
* provisional → react when componentCode lands) tear down the old
|
|
73
|
+
* mount and stand up the replacement; same-kind updates route to
|
|
74
|
+
* the inner renderer's `update()`.
|
|
75
|
+
*/
|
|
76
|
+
update(next: RenderItemOptions): Promise<void>;
|
|
77
|
+
/** Tear down the active mount + unsubscribe from the StreamBus. */
|
|
78
|
+
unmount(): void;
|
|
79
|
+
/** Current render kind. Test-only — not load-bearing. */
|
|
80
|
+
readonly kind: MountedKind;
|
|
81
|
+
}
|
|
82
|
+
export declare function mountRender(container: HTMLElement, opts: RenderItemOptions): Promise<RenderItemHandle>;
|
|
83
|
+
export {};
|