@ggui-ai/iframe-runtime 0.2.0-alpha.3 → 0.3.0-rc.0
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/channel-transport.d.ts +20 -30
- package/dist/channel-transport.js +24 -24
- package/dist/channels/data.d.ts +5 -20
- package/dist/channels/data.js +4 -52
- package/dist/channels/index.d.ts +0 -1
- package/dist/channels/index.js +0 -1
- package/dist/channels/props-update.d.ts +9 -9
- package/dist/channels/props-update.js +9 -9
- package/dist/channels/render-frame.d.ts +5 -5
- package/dist/channels/render-frame.js +5 -5
- package/dist/compiled-validators.d.ts +2 -2
- package/dist/compiled-validators.js +1 -1
- package/dist/context-observer.d.ts +4 -4
- package/dist/context-observer.js +3 -3
- package/dist/events-polling.d.ts +7 -7
- package/dist/events-polling.js +5 -5
- package/dist/globals.d.ts +1 -1
- package/dist/host-context-emitter.d.ts +4 -4
- package/dist/host-context-emitter.js +9 -9
- package/dist/iframe-runtime.js +94 -76
- package/dist/index.d.ts +4 -4
- package/dist/index.js +1 -1
- package/dist/lifecycle.d.ts +3 -3
- package/dist/lifecycle.js +29 -6
- package/dist/mcp-app-iframe-host.d.ts +8 -8
- package/dist/mcp-app-iframe-host.js +4 -4
- package/dist/meta-parse.d.ts +3 -4
- package/dist/meta-parse.js +17 -44
- package/dist/observability.d.ts +14 -36
- package/dist/observability.js +2 -1
- package/dist/protocol-error.d.ts +38 -48
- package/dist/protocol-error.js +5 -23
- package/dist/provisional-renderer.js +3 -3
- package/dist/react-renderer.d.ts +16 -6
- package/dist/react-renderer.js +22 -40
- package/dist/registry-subscribe.d.ts +7 -7
- package/dist/registry-subscribe.js +28 -14
- package/dist/render-item.d.ts +22 -15
- package/dist/render-item.js +7 -14
- package/dist/runtime.d.ts +79 -105
- package/dist/runtime.js +219 -269
- package/dist/status-dom.d.ts +12 -12
- package/dist/status-dom.js +11 -11
- package/dist/system-cards/NoCredentialsCard.d.ts +1 -1
- package/dist/system-cards/NoCredentialsCard.js +1 -1
- package/dist/system-cards/ProtocolProbeCard.d.ts +13 -17
- package/dist/system-cards/ProtocolProbeCard.js +21 -25
- package/dist/system-cards/index.d.ts +5 -5
- package/dist/system-cards/index.js +5 -5
- package/dist/types.d.ts +29 -34
- package/dist/validation.d.ts +4 -5
- package/dist/validation.js +5 -6
- package/dist/wire-config.d.ts +43 -106
- package/dist/wire-config.js +29 -195
- package/package.json +12 -11
- package/dist/channels/feedback.d.ts +0 -14
- package/dist/channels/feedback.js +0 -19
package/dist/protocol-error.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Default emitter — operator-visible `console.warn` with a grep-
|
|
3
|
-
* friendly tag. Identical posture to `
|
|
3
|
+
* friendly tag. Identical posture to `GguiRender.surfaceContractViolation`'s
|
|
4
4
|
* console.warn fallback. Callers override by injecting their own.
|
|
5
5
|
*/
|
|
6
6
|
export function defaultProtocolErrorEmitter(err) {
|
|
@@ -17,18 +17,10 @@ export function defaultProtocolErrorEmitter(err) {
|
|
|
17
17
|
* outbound action, or when an inbound stream / props payload fails the
|
|
18
18
|
* local validator) onto a `ProtocolError`.
|
|
19
19
|
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* is tool-failure-scoped and authored by the session-channel router.
|
|
23
|
-
* A client-side violation is a DIFFERENT category: nothing reached the
|
|
24
|
-
* tool. We surface it as `{kind: 'protocol'; code:
|
|
20
|
+
* A client-side violation never left the renderer — nothing reached
|
|
21
|
+
* the server. We surface it as `{kind: 'protocol'; code:
|
|
25
22
|
* 'CLIENT_CONTRACT_VIOLATION'}` with structured `details` so hosts can
|
|
26
23
|
* render the same summary that `console.warn` would have produced.
|
|
27
|
-
*
|
|
28
|
-
* Server-emitted `ContractErrorPayload` landing on
|
|
29
|
-
* `_ggui:contract-error` rides the separate `{kind: 'contract'}`
|
|
30
|
-
* variant — that path activates when the renderer observes the
|
|
31
|
-
* reserved channel.
|
|
32
24
|
*/
|
|
33
25
|
export function fromClientContractViolation(err) {
|
|
34
26
|
return {
|
|
@@ -41,16 +33,6 @@ export function fromClientContractViolation(err) {
|
|
|
41
33
|
},
|
|
42
34
|
};
|
|
43
35
|
}
|
|
44
|
-
/**
|
|
45
|
-
* Wrap a server-emitted `ContractErrorPayload` (received over the
|
|
46
|
-
* reserved `_ggui:contract-error` channel) as the 'contract' variant.
|
|
47
|
-
* Trivial wrapper — exists so call sites produce the union via a
|
|
48
|
-
* factory rather than an object literal (keeps emission uniform and
|
|
49
|
-
* makes grep for creation sites exact).
|
|
50
|
-
*/
|
|
51
|
-
export function fromContractErrorPayload(payload) {
|
|
52
|
-
return { kind: 'contract', payload };
|
|
53
|
-
}
|
|
54
36
|
/**
|
|
55
37
|
* Build a `ProtocolError` of kind 'version' from the existing
|
|
56
38
|
* `UpgradeRequiredError` class. Matches the `liftUpgradeRequiredFromError`
|
|
@@ -104,8 +86,8 @@ export function fromTransportFailure(code, retryable, message) {
|
|
|
104
86
|
};
|
|
105
87
|
}
|
|
106
88
|
/**
|
|
107
|
-
* Build a `ProtocolError` of kind 'auth' — session / token rejection
|
|
108
|
-
* pre-
|
|
89
|
+
* Build a `ProtocolError` of kind 'auth' — session / ws-token rejection
|
|
90
|
+
* pre-ack (see {@link AuthFailureCode}).
|
|
109
91
|
*/
|
|
110
92
|
export function fromAuthFailure(code, message) {
|
|
111
93
|
return {
|
|
@@ -36,9 +36,9 @@ import { createRoot } from 'react-dom/client';
|
|
|
36
36
|
* Spec-canonical A2UI preview channel name (from
|
|
37
37
|
* `@ggui-ai/protocol::PREVIEW_CHANNEL`). Inlined here so this
|
|
38
38
|
* module's runtime imports stay off the protocol root barrel —
|
|
39
|
-
* importing the constant from `@ggui-ai/protocol` pulls in
|
|
40
|
-
*
|
|
41
|
-
*
|
|
39
|
+
* importing the constant from `@ggui-ai/protocol` pulls in the
|
|
40
|
+
* entire zod validation graph via the root re-export chain,
|
|
41
|
+
* adding hundreds of KB gz to the renderer bundle.
|
|
42
42
|
* The value is a stable string literal (`isKnownReservedChannel`
|
|
43
43
|
* and the spec both reference `_ggui:preview` directly); re-declaring
|
|
44
44
|
* it here is shape-preserving and covered by the structural-lock
|
package/dist/react-renderer.d.ts
CHANGED
|
@@ -22,9 +22,8 @@
|
|
|
22
22
|
*
|
|
23
23
|
* Error handling — ErrorBoundary with auto-retry (AUTO_RETRY_LIMIT +
|
|
24
24
|
* AUTO_RETRY_DELAY) matches the `ReactComponentRenderer` contract.
|
|
25
|
-
* On terminal error,
|
|
26
|
-
*
|
|
27
|
-
* `ggui:request-repair` live-channel envelope it dispatches.
|
|
25
|
+
* On terminal error, `onError` fires and the boundary paints the
|
|
26
|
+
* terminal error UI.
|
|
28
27
|
*
|
|
29
28
|
* No JSX in this file — the mount-root code uses
|
|
30
29
|
* `React.createElement` + `React.Fragment` directly so the renderer's
|
|
@@ -33,7 +32,7 @@
|
|
|
33
32
|
import React, { type ReactNode } from 'react';
|
|
34
33
|
export interface ReactRootMountOptions {
|
|
35
34
|
/**
|
|
36
|
-
*
|
|
35
|
+
* GguiSession whose componentCode + props drive the mount. Only
|
|
37
36
|
* `componentCode` + `props` are read here; the caller is
|
|
38
37
|
* responsible for wrapping this with a `<GguiWireProvider>` at the
|
|
39
38
|
* higher-level dispatcher (see `render-item.ts`).
|
|
@@ -50,9 +49,20 @@ export interface ReactRootMountOptions {
|
|
|
50
49
|
* registered theme paints when the operator selected it.
|
|
51
50
|
*/
|
|
52
51
|
readonly themeMode?: 'light' | 'dark';
|
|
52
|
+
/**
|
|
53
|
+
* Per-app theme overlay applied at the iframe's `:root` AFTER the base
|
|
54
|
+
* token block, so the operator-supplied `--ggui-*` values win the
|
|
55
|
+
* cascade (a partial set — unset vars keep their token defaults). A
|
|
56
|
+
* structural subset of protocol's `AppTheme` (`name` is display-only,
|
|
57
|
+
* not needed at render). `mode` drives `color-scheme` so native
|
|
58
|
+
* controls / scrollbars / canvas pick up dark vs light.
|
|
59
|
+
*/
|
|
60
|
+
readonly appTheme?: {
|
|
61
|
+
readonly mode: 'light' | 'dark';
|
|
62
|
+
readonly cssVariables: Record<string, string>;
|
|
63
|
+
};
|
|
53
64
|
readonly cssOverrides?: string;
|
|
54
65
|
readonly onError?: (error: Error) => void;
|
|
55
|
-
readonly onRequestRepair?: (error: Error) => void;
|
|
56
66
|
/**
|
|
57
67
|
* Children injected BETWEEN the mount DOM (scope + CSS) and the
|
|
58
68
|
* evaluated component element. The caller's render dispatcher
|
|
@@ -64,7 +74,7 @@ export interface ReactRootMountOptions {
|
|
|
64
74
|
readonly renderWrapper?: (mountedComponent: ReactNode) => ReactNode;
|
|
65
75
|
/**
|
|
66
76
|
* Bare npm package names of the operator-registered 3rd-party gadget
|
|
67
|
-
* packages reachable in this
|
|
77
|
+
* packages reachable in this render (GG.8.2). Forwarded to
|
|
68
78
|
* `rewriteImports` so each direct gadget import resolves to its
|
|
69
79
|
* per-package data-URL shim. Sourced from the bootstrap's `gadgets`
|
|
70
80
|
* channel; `@ggui-ai/gadgets` (STDLIB) is always rewritten and need
|
package/dist/react-renderer.js
CHANGED
|
@@ -22,9 +22,8 @@
|
|
|
22
22
|
*
|
|
23
23
|
* Error handling — ErrorBoundary with auto-retry (AUTO_RETRY_LIMIT +
|
|
24
24
|
* AUTO_RETRY_DELAY) matches the `ReactComponentRenderer` contract.
|
|
25
|
-
* On terminal error,
|
|
26
|
-
*
|
|
27
|
-
* `ggui:request-repair` live-channel envelope it dispatches.
|
|
25
|
+
* On terminal error, `onError` fires and the boundary paints the
|
|
26
|
+
* terminal error UI.
|
|
28
27
|
*
|
|
29
28
|
* No JSX in this file — the mount-root code uses
|
|
30
29
|
* `React.createElement` + `React.Fragment` directly so the renderer's
|
|
@@ -59,7 +58,6 @@ class RcrErrorBoundary extends Component {
|
|
|
59
58
|
error: null,
|
|
60
59
|
catchCount: 0,
|
|
61
60
|
autoRetrying: false,
|
|
62
|
-
repairRequested: false,
|
|
63
61
|
};
|
|
64
62
|
retryTimer = null;
|
|
65
63
|
static getDerivedStateFromError(error) {
|
|
@@ -76,20 +74,16 @@ class RcrErrorBoundary extends Component {
|
|
|
76
74
|
return;
|
|
77
75
|
}
|
|
78
76
|
this.props.onError?.(error);
|
|
79
|
-
if (this.props.onRequestRepair && !this.state.repairRequested) {
|
|
80
|
-
this.setState({ repairRequested: true });
|
|
81
|
-
this.props.onRequestRepair(error);
|
|
82
|
-
}
|
|
83
77
|
}
|
|
84
78
|
componentWillUnmount() {
|
|
85
79
|
if (this.retryTimer !== null)
|
|
86
80
|
clearTimeout(this.retryTimer);
|
|
87
81
|
}
|
|
88
82
|
render() {
|
|
89
|
-
const { error, autoRetrying,
|
|
83
|
+
const { error, autoRetrying, catchCount } = this.state;
|
|
90
84
|
if (error === null)
|
|
91
85
|
return this.props.children;
|
|
92
|
-
// The
|
|
86
|
+
// The two fallback UIs match the host-SDK error boundary
|
|
93
87
|
// verbatim (same inline styles so operator-visible DOM is
|
|
94
88
|
// identical between the host-SDK and iframe-renderer paths).
|
|
95
89
|
const FONT = '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
|
|
@@ -117,30 +111,6 @@ class RcrErrorBoundary extends Component {
|
|
|
117
111
|
},
|
|
118
112
|
}), 'Retrying...', createElement('style', null, '@keyframes ggui-err-spin { to { transform: rotate(360deg); } }'));
|
|
119
113
|
}
|
|
120
|
-
if (repairRequested) {
|
|
121
|
-
return createElement('div', {
|
|
122
|
-
style: {
|
|
123
|
-
display: 'flex',
|
|
124
|
-
flexDirection: 'column',
|
|
125
|
-
alignItems: 'center',
|
|
126
|
-
justifyContent: 'center',
|
|
127
|
-
padding: '32px 24px',
|
|
128
|
-
minHeight: 120,
|
|
129
|
-
gap: 10,
|
|
130
|
-
textAlign: 'center',
|
|
131
|
-
fontFamily: FONT,
|
|
132
|
-
},
|
|
133
|
-
}, createElement('div', {
|
|
134
|
-
style: {
|
|
135
|
-
width: 16,
|
|
136
|
-
height: 16,
|
|
137
|
-
border: '2px solid rgba(139,92,246,0.2)',
|
|
138
|
-
borderTopColor: 'rgba(139,92,246,0.7)',
|
|
139
|
-
borderRadius: '50%',
|
|
140
|
-
animation: 'ggui-err-spin 0.8s linear infinite',
|
|
141
|
-
},
|
|
142
|
-
}), createElement('div', { style: { fontSize: 13, color: 'rgba(255,255,255,0.5)' } }, 'Repairing component...'), createElement('style', null, '@keyframes ggui-err-spin { to { transform: rotate(360deg); } }'));
|
|
143
|
-
}
|
|
144
114
|
return createElement('div', {
|
|
145
115
|
style: {
|
|
146
116
|
display: 'flex',
|
|
@@ -165,7 +135,7 @@ class RcrErrorBoundary extends Component {
|
|
|
165
135
|
fontSize: 22,
|
|
166
136
|
},
|
|
167
137
|
}, '!'), createElement('div', { style: { fontSize: 14, fontWeight: 600, color: 'rgba(255,255,255,0.8)' } }, 'Something went wrong'), createElement('div', { style: { fontSize: 12, color: 'rgba(255,255,255,0.4)', maxWidth: 280, lineHeight: 1.4 } }, 'This component encountered an error. Try asking for a different view.'), createElement('button', {
|
|
168
|
-
onClick: () => this.setState({ error: null, catchCount: 0
|
|
138
|
+
onClick: () => this.setState({ error: null, catchCount: 0 }),
|
|
169
139
|
style: {
|
|
170
140
|
marginTop: 4,
|
|
171
141
|
padding: '8px 20px',
|
|
@@ -241,9 +211,22 @@ export async function mountReactRoot(container, opts) {
|
|
|
241
211
|
// `ThemeProvider` so a Studio/Portal context with both runtime + RP
|
|
242
212
|
// active doesn't double-stack.
|
|
243
213
|
if (typeof document !== 'undefined') {
|
|
244
|
-
|
|
214
|
+
let rootCss = opts.themeId
|
|
245
215
|
? getThemeCss(opts.themeId, opts.themeMode)
|
|
246
216
|
: getCssTokens(opts.themeMode);
|
|
217
|
+
if (opts.appTheme) {
|
|
218
|
+
const decls = Object.entries(opts.appTheme.cssVariables)
|
|
219
|
+
.map(([k, v]) => `${k}: ${v};`)
|
|
220
|
+
.join('');
|
|
221
|
+
// Appended AFTER the base block → the per-app overlay wins; it is a
|
|
222
|
+
// partial set (unset vars keep their token defaults). CSS custom
|
|
223
|
+
// properties at :root inherit into the scoped React tree, so this
|
|
224
|
+
// alone is sufficient for the component to resolve `var(--ggui-*)`.
|
|
225
|
+
// Values were validated injection-safe upstream (write-side
|
|
226
|
+
// `appThemeSchema` + the wire parser re-validates), so the
|
|
227
|
+
// string-join here is safe — do NOT re-sanitize.
|
|
228
|
+
rootCss += `:root{color-scheme:${opts.appTheme.mode};${decls}}`;
|
|
229
|
+
}
|
|
247
230
|
const styleId = 'ggui-theme-vars';
|
|
248
231
|
let styleEl = document.getElementById(styleId);
|
|
249
232
|
if (!styleEl) {
|
|
@@ -267,7 +250,6 @@ export async function mountReactRoot(container, opts) {
|
|
|
267
250
|
root.render(createElement('div', { className: scopeClass }, createElement('style', null, `${themeCss}${opts.cssOverrides ?? ''}`), createElement(RcrErrorBoundary, {
|
|
268
251
|
key: currentCode?.length ?? 0,
|
|
269
252
|
...(opts.onError ? { onError: opts.onError } : {}),
|
|
270
|
-
...(opts.onRequestRepair ? { onRequestRepair: opts.onRequestRepair } : {}),
|
|
271
253
|
}, createElement(Fragment, null, wrapped))));
|
|
272
254
|
}
|
|
273
255
|
async function initialEvaluate() {
|
|
@@ -286,9 +268,9 @@ export async function mountReactRoot(container, opts) {
|
|
|
286
268
|
catch (err) {
|
|
287
269
|
const e = err instanceof Error ? err : new Error(String(err));
|
|
288
270
|
// Fail-loud on eval errors. The OPTIONAL `onError` callback is
|
|
289
|
-
// not enough on its own —
|
|
290
|
-
//
|
|
291
|
-
//
|
|
271
|
+
// not enough on its own — not every mount path wires one, so
|
|
272
|
+
// without this log a mount with a broken component renders a
|
|
273
|
+
// blank iframe and emits nothing.
|
|
292
274
|
// eslint-disable-next-line no-console -- operator-visible failure signal
|
|
293
275
|
console.error('[ggui] mountReactRoot: component evaluation failed —', e);
|
|
294
276
|
currentComponent = null;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AckPayload
|
|
1
|
+
import type { AckPayload } from '@ggui-ai/protocol';
|
|
2
2
|
import type { ConnectionStatus } from '@ggui-ai/protocol/transport/websocket';
|
|
3
3
|
import type { McpAppAiGguiRenderMeta } from '@ggui-ai/protocol/integrations/mcp-apps';
|
|
4
4
|
import type { ChannelRegistry, RegistryPollingOptions, WsTransportHandle } from '@ggui-ai/live-channel';
|
|
@@ -8,7 +8,7 @@ import type { ObservabilityEmitter } from './observability.js';
|
|
|
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
10
|
* attaches `handle` to the renderer's outbound send surface (via the
|
|
11
|
-
* BufferedManagerShim flush) and applies `ack.
|
|
11
|
+
* BufferedManagerShim flush) and applies `ack.session` to the mounted
|
|
12
12
|
* render slot.
|
|
13
13
|
*/
|
|
14
14
|
export interface RegistrySubscribeHandle {
|
|
@@ -21,8 +21,8 @@ export interface RegistrySubscribeHandle {
|
|
|
21
21
|
*/
|
|
22
22
|
export interface ConnectViaRegistryOptions {
|
|
23
23
|
/**
|
|
24
|
-
*
|
|
25
|
-
* credentials this subscribe path actually needs: `
|
|
24
|
+
* GguiSession slice (`McpAppAiGguiRenderMeta`) — the live-channel
|
|
25
|
+
* credentials this subscribe path actually needs: `sessionId`,
|
|
26
26
|
* `appId`, `wsUrl`, `wsToken`. The rest of the slice (codeUrl /
|
|
27
27
|
* propsJson / contextSlots / etc.) is load-bearing only for the
|
|
28
28
|
* renderer / mount layer.
|
|
@@ -72,7 +72,7 @@ export interface ConnectViaRegistryOptions {
|
|
|
72
72
|
* during a WS dropout window flows back into the iframe on
|
|
73
73
|
* reconnect.
|
|
74
74
|
*
|
|
75
|
-
* Idempotent at the runtime layer: `applyRender(ack.
|
|
75
|
+
* Idempotent at the runtime layer: `applyRender(ack.session)` patches
|
|
76
76
|
* in place. Channels re-subscribed by
|
|
77
77
|
* `ChannelTransportRouter.onWsStatusChange('connected')` upstream
|
|
78
78
|
* of this callback, so the per-channel transport state is already
|
|
@@ -100,7 +100,7 @@ export interface ConnectViaRegistryOptions {
|
|
|
100
100
|
* error.
|
|
101
101
|
*
|
|
102
102
|
* Once the promise settles, subsequent server frames flow through the
|
|
103
|
-
* registered channel handlers (props_update, drain_ack,
|
|
103
|
+
* registered channel handlers (props_update, drain_ack, render, data,
|
|
104
104
|
* etc.) — there is no longer a separate `onMessage` callback fanning
|
|
105
105
|
* frames across multiple handler chains.
|
|
106
106
|
*/
|
|
@@ -117,4 +117,4 @@ export type ConnectFn = typeof connectViaRegistry;
|
|
|
117
117
|
* Type re-export for callers that want to thread the handle type
|
|
118
118
|
* through their own seams (e.g. `bootSequence`'s shim attach).
|
|
119
119
|
*/
|
|
120
|
-
export type { WsTransportHandle
|
|
120
|
+
export type { WsTransportHandle };
|
|
@@ -6,9 +6,9 @@
|
|
|
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/live-channel` library so every WS frame type (
|
|
9
|
+
* the `@ggui-ai/live-channel` library so every WS frame type (render,
|
|
10
10
|
* data, props_update, drain_ack, channel_payload, channel_error,
|
|
11
|
-
* system
|
|
11
|
+
* system) flows through one registry-owned transport.
|
|
12
12
|
*
|
|
13
13
|
* Composition:
|
|
14
14
|
*
|
|
@@ -92,19 +92,33 @@ function normalizeObservedVersion(value) {
|
|
|
92
92
|
return value;
|
|
93
93
|
return value.join(', ');
|
|
94
94
|
}
|
|
95
|
+
/**
|
|
96
|
+
* Auth-class pre-ack codes — the §12.2.3 vocabulary first-party servers
|
|
97
|
+
* actually emit on the pre-ack error frame (ws-token verification +
|
|
98
|
+
* upgrade-auth failures). Replaced the phantom `AUTH_REJECTED` /
|
|
99
|
+
* `TOKEN_EXPIRED` arms in draft-2026-06-12 — a real ws-token expiry
|
|
100
|
+
* previously fell into the generic `'protocol'` bucket.
|
|
101
|
+
*/
|
|
102
|
+
const PRE_ACK_AUTH_CODES = new Set([
|
|
103
|
+
'SESSION_NOT_FOUND',
|
|
104
|
+
'BOOTSTRAP_EXPIRED',
|
|
105
|
+
'BOOTSTRAP_INVALID',
|
|
106
|
+
'BOOTSTRAP_SESSION_MISMATCH',
|
|
107
|
+
'BOOTSTRAP_APP_MISMATCH',
|
|
108
|
+
'UNAUTHENTICATED',
|
|
109
|
+
]);
|
|
110
|
+
function isAuthFailureCode(code) {
|
|
111
|
+
return PRE_ACK_AUTH_CODES.has(code);
|
|
112
|
+
}
|
|
95
113
|
/**
|
|
96
114
|
* Map a pre-ack `error` frame onto a non-UPGRADE_REQUIRED ProtocolError.
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
* verbatim.
|
|
115
|
+
* {@link PRE_ACK_AUTH_CODES} → `'auth'`; every other code → `'protocol'`
|
|
116
|
+
* with the extensibly-closed tail carrying the wire code verbatim.
|
|
100
117
|
*/
|
|
101
118
|
function classifyPreAckError(payload) {
|
|
102
|
-
if (payload.code
|
|
119
|
+
if (isAuthFailureCode(payload.code)) {
|
|
103
120
|
return fromAuthFailure(payload.code, payload.message);
|
|
104
121
|
}
|
|
105
|
-
if (payload.code === 'TOKEN_EXPIRED') {
|
|
106
|
-
return fromAuthFailure('TOKEN_EXPIRED', payload.message);
|
|
107
|
-
}
|
|
108
122
|
return {
|
|
109
123
|
kind: 'protocol',
|
|
110
124
|
code: payload.code,
|
|
@@ -121,7 +135,7 @@ function classifyPreAckError(payload) {
|
|
|
121
135
|
* error.
|
|
122
136
|
*
|
|
123
137
|
* Once the promise settles, subsequent server frames flow through the
|
|
124
|
-
* registered channel handlers (props_update, drain_ack,
|
|
138
|
+
* registered channel handlers (props_update, drain_ack, render, data,
|
|
125
139
|
* etc.) — there is no longer a separate `onMessage` callback fanning
|
|
126
140
|
* frames across multiple handler chains.
|
|
127
141
|
*/
|
|
@@ -153,9 +167,9 @@ export function connectViaRegistry(opts) {
|
|
|
153
167
|
onMessage: (payload) => {
|
|
154
168
|
const ack = payload;
|
|
155
169
|
// Post-handshake ack — the WS reconnected and the server
|
|
156
|
-
// re-sent the subscribe ack with a fresh `
|
|
170
|
+
// re-sent the subscribe ack with a fresh `render` snapshot.
|
|
157
171
|
// Fan it onto the caller's resubscribe callback so the
|
|
158
|
-
// runtime can reapply the authoritative
|
|
172
|
+
// runtime can reapply the authoritative render (a render or
|
|
159
173
|
// update that landed during the WS dropout window now
|
|
160
174
|
// restores). Skip the version handshake / settle bookkeeping
|
|
161
175
|
// — that already fired on the original ack.
|
|
@@ -270,7 +284,7 @@ export function connectViaRegistry(opts) {
|
|
|
270
284
|
const composedUrl = composeWsUrl(wsUrl, wsToken);
|
|
271
285
|
// `bind()` selects the transport (WS for wsUrl+wsToken sessions;
|
|
272
286
|
// the live-channel narrows on its `bootstrap` shape) and starts
|
|
273
|
-
// it. We override `
|
|
287
|
+
// it. We override `bootstrap.wsUrl` with the composed URL so the
|
|
274
288
|
// registry's WS transport gets the wsToken-threaded variant. The
|
|
275
289
|
// live-channel's `bootstrap` arg is its internal name; the field
|
|
276
290
|
// on the wire is `wsToken`.
|
|
@@ -279,7 +293,7 @@ export function connectViaRegistry(opts) {
|
|
|
279
293
|
bootstrap: {
|
|
280
294
|
wsUrl: composedUrl,
|
|
281
295
|
wsToken,
|
|
282
|
-
|
|
296
|
+
sessionId: opts.meta.sessionId,
|
|
283
297
|
appId: opts.meta.appId,
|
|
284
298
|
},
|
|
285
299
|
onStatusChange: mappedStatusCallback,
|
package/dist/render-item.d.ts
CHANGED
|
@@ -1,36 +1,34 @@
|
|
|
1
1
|
import type { ReactNode } from 'react';
|
|
2
|
-
import type {
|
|
3
|
-
import type {
|
|
2
|
+
import type { GguiSession } from '@ggui-ai/protocol';
|
|
3
|
+
import type { GguiSessionSeedInput } from './types.js';
|
|
4
4
|
import { type WireConfig } from '@ggui-ai/wire';
|
|
5
5
|
import type { StreamBus } from './wire-config.js';
|
|
6
6
|
export interface RenderItemOptions {
|
|
7
7
|
/** The render to mount. */
|
|
8
|
-
readonly render:
|
|
9
|
-
/** Wire config for this render — caller
|
|
10
|
-
* `
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
8
|
+
readonly render: GguiSession | GguiSessionSeedInput;
|
|
9
|
+
/** Wire config for this render — caller builds it via the runtime's
|
|
10
|
+
* `buildRootWireConfig(...)` (see `wire-config.ts`). `null` ⇒ the
|
|
11
|
+
* component mounts without a wire provider (standalone — matches
|
|
12
|
+
* today's DynamicComponent fallback when no GguiRender parent is
|
|
13
|
+
* present). */
|
|
14
14
|
readonly scopedWireConfig: WireConfig | null;
|
|
15
15
|
/** Shared stream bus — provisional renderer subscribes to
|
|
16
16
|
* `_ggui:preview` for this render. */
|
|
17
17
|
readonly streamBus: StreamBus;
|
|
18
|
-
/**
|
|
19
|
-
readonly
|
|
18
|
+
/** GguiSession id — used by the mcp-apps iframe host for proxy URLs. */
|
|
19
|
+
readonly sessionId: string;
|
|
20
20
|
/**
|
|
21
21
|
* 3rd-party gadget package names whose direct imports the rewriter
|
|
22
22
|
* must resolve to per-package shims. FALLBACK source used only when
|
|
23
23
|
* the render carries no `gadgetDescriptors` sidecar — i.e. the static
|
|
24
|
-
* seed mount ({@link
|
|
24
|
+
* seed mount ({@link GguiSessionSeedInput}, which has no descriptors). The
|
|
25
25
|
* caller threads the bootstrap's `meta.gadgets` package names here so a
|
|
26
26
|
* generated component importing a non-STDLIB gadget on a no-WS host
|
|
27
27
|
* (claude.ai / ChatGPT) still resolves its shim on first paint. A
|
|
28
|
-
* WS-delivered full `
|
|
28
|
+
* WS-delivered full `GguiSession` carries `gadgetDescriptors` and ignores
|
|
29
29
|
* this. STDLIB `@ggui-ai/gadgets` is always rewritten regardless.
|
|
30
30
|
*/
|
|
31
31
|
readonly gadgetPackages?: readonly string[];
|
|
32
|
-
/** ggui-server base URL (for mcp-apps proxy). Empty = same-origin. */
|
|
33
|
-
readonly mcpAppsServerBaseUrl?: string;
|
|
34
32
|
/** Theme id passed to ReactComponentRenderer for scoped CSS. */
|
|
35
33
|
readonly themeId?: string;
|
|
36
34
|
/**
|
|
@@ -39,9 +37,18 @@ export interface RenderItemOptions {
|
|
|
39
37
|
* design registry's `getTheme(id, mode?)` default.
|
|
40
38
|
*/
|
|
41
39
|
readonly themeMode?: 'light' | 'dark';
|
|
40
|
+
/**
|
|
41
|
+
* Per-app theme overlay forwarded to {@link ReactRootMountOptions.appTheme}
|
|
42
|
+
* so the renderer injects the `--ggui-*` overrides + `color-scheme` at
|
|
43
|
+
* `:root`. Sourced from the bootstrap's `_meta["ai.ggui/render"].theme`.
|
|
44
|
+
* A structural subset of protocol's `AppTheme` (`name` is display-only).
|
|
45
|
+
*/
|
|
46
|
+
readonly appTheme?: {
|
|
47
|
+
readonly mode: 'light' | 'dark';
|
|
48
|
+
readonly cssVariables: Record<string, string>;
|
|
49
|
+
};
|
|
42
50
|
readonly cssOverrides?: string;
|
|
43
51
|
readonly onError?: (err: Error) => void;
|
|
44
|
-
readonly onRequestRepair?: (err: Error) => void;
|
|
45
52
|
/**
|
|
46
53
|
* Optional outer wrapper applied AROUND the GguiWireProvider — used
|
|
47
54
|
* by `bootProduction` to install the `<ContextStateHost>` provider
|
package/dist/render-item.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* GguiSession dispatcher — routes a single {@link GguiSession} to the
|
|
3
3
|
* appropriate renderer based on its discriminator.
|
|
4
4
|
*
|
|
5
5
|
* Post-render-identity-collapse (2026-05-27): the iframe-runtime mounts
|
|
@@ -101,21 +101,18 @@ export async function mountRender(container, opts) {
|
|
|
101
101
|
}
|
|
102
102
|
async function mountByKind(kind) {
|
|
103
103
|
if (kind === 'mcpApps') {
|
|
104
|
-
//
|
|
104
|
+
// GguiSession narrows to McpAppsGguiSession when type='mcpApps'.
|
|
105
105
|
const render = currentOpts.render;
|
|
106
106
|
mcpMount = mountMcpAppIframe(container, {
|
|
107
107
|
render,
|
|
108
|
-
|
|
109
|
-
...(currentOpts.mcpAppsServerBaseUrl !== undefined
|
|
110
|
-
? { serverBaseUrl: currentOpts.mcpAppsServerBaseUrl }
|
|
111
|
-
: {}),
|
|
108
|
+
sessionId: currentOpts.sessionId,
|
|
112
109
|
});
|
|
113
110
|
currentKind = 'mcpApps';
|
|
114
111
|
return;
|
|
115
112
|
}
|
|
116
113
|
if (kind === 'system') {
|
|
117
114
|
// System card — server-emitted `kind` mapped to a built-in
|
|
118
|
-
// `.tsx` via `SystemCardHost`.
|
|
115
|
+
// `.tsx` via `SystemCardHost`. GguiSession-only: no `__ggui__` shim,
|
|
119
116
|
// no GguiWireProvider, no contextSpec wrap (system cards don't
|
|
120
117
|
// dispatch wire actions or read contexts). The registry handles
|
|
121
118
|
// unknown kinds via a typed fallback. Dynamic-import keeps the
|
|
@@ -176,13 +173,11 @@ export async function mountRender(container, opts) {
|
|
|
176
173
|
...(gadgetPackages !== undefined ? { gadgetPackages } : {}),
|
|
177
174
|
...(currentOpts.themeId !== undefined ? { themeId: currentOpts.themeId } : {}),
|
|
178
175
|
...(currentOpts.themeMode !== undefined ? { themeMode: currentOpts.themeMode } : {}),
|
|
176
|
+
...(currentOpts.appTheme !== undefined ? { appTheme: currentOpts.appTheme } : {}),
|
|
179
177
|
...(currentOpts.cssOverrides !== undefined
|
|
180
178
|
? { cssOverrides: currentOpts.cssOverrides }
|
|
181
179
|
: {}),
|
|
182
180
|
...(currentOpts.onError !== undefined ? { onError: currentOpts.onError } : {}),
|
|
183
|
-
...(currentOpts.onRequestRepair !== undefined
|
|
184
|
-
? { onRequestRepair: currentOpts.onRequestRepair }
|
|
185
|
-
: {}),
|
|
186
181
|
renderWrapper: wrapInScopedProvider,
|
|
187
182
|
});
|
|
188
183
|
currentKind = 'react';
|
|
@@ -191,7 +186,7 @@ export async function mountRender(container, opts) {
|
|
|
191
186
|
* Wrap the mounted component in `<GguiWireProvider
|
|
192
187
|
* config={scopedConfig}>` when the caller supplied one. Without a
|
|
193
188
|
* scoped config, the component renders standalone — matches
|
|
194
|
-
* today's DynamicComponent fallback when no
|
|
189
|
+
* today's DynamicComponent fallback when no GguiRender parent is
|
|
195
190
|
* present.
|
|
196
191
|
*
|
|
197
192
|
* Composition order (outer → inner):
|
|
@@ -242,11 +237,9 @@ export async function mountRender(container, opts) {
|
|
|
242
237
|
},
|
|
243
238
|
...(next.themeId !== undefined ? { themeId: next.themeId } : {}),
|
|
244
239
|
...(next.themeMode !== undefined ? { themeMode: next.themeMode } : {}),
|
|
240
|
+
...(next.appTheme !== undefined ? { appTheme: next.appTheme } : {}),
|
|
245
241
|
...(next.cssOverrides !== undefined ? { cssOverrides: next.cssOverrides } : {}),
|
|
246
242
|
...(next.onError !== undefined ? { onError: next.onError } : {}),
|
|
247
|
-
...(next.onRequestRepair !== undefined
|
|
248
|
-
? { onRequestRepair: next.onRequestRepair }
|
|
249
|
-
: {}),
|
|
250
243
|
renderWrapper: wrapInScopedProvider,
|
|
251
244
|
});
|
|
252
245
|
return;
|