@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/wire-config.d.ts
CHANGED
|
@@ -1,26 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Per-
|
|
2
|
+
* Per-render `WireConfig` factory for the renderer iframe.
|
|
3
3
|
*
|
|
4
|
-
* The renderer iframe
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* Why per-item scoping:
|
|
11
|
-
* - A stacked/chat-style UI keeps older cards rendered beneath the
|
|
12
|
-
* top one. A `useAction('submit')` call inside an older card MUST
|
|
13
|
-
* emit with THAT card's `stackItemId` + `tool` + `actionSpec` — not the
|
|
14
|
-
* top-of-stack's. Without per-item scoping, contract enforcement
|
|
15
|
-
* would reject cross-stack dispatches as "wrong contract" OR the
|
|
16
|
-
* wrong tool would be invoked server-side.
|
|
17
|
-
*
|
|
18
|
-
* Construction: the renderer builds ONE top-level bundle at boot
|
|
19
|
-
* (`buildRootWireConfig`), which returns `{config, buildScopedConfig}`.
|
|
20
|
-
* The runtime's `stackCtx.getScopedWireConfig` calls
|
|
21
|
-
* `buildScopedConfig(item)` per stack entry; each
|
|
22
|
-
* `<GguiWireProvider>` wraps a stack item with the resulting scoped
|
|
23
|
-
* config.
|
|
4
|
+
* The renderer iframe mounts EXACTLY ONE {@link Render} per iframe
|
|
5
|
+
* post-render-identity-collapse (2026-05-27). There is no per-item
|
|
6
|
+
* scoping factory anymore — the WireConfig is built once at boot,
|
|
7
|
+
* keyed by the bootstrap's `renderId`, and the active render's
|
|
8
|
+
* `actionSpec` is wired in via the {@link buildRootWireConfig}'s
|
|
9
|
+
* `getCurrentRender` thunk.
|
|
24
10
|
*
|
|
25
11
|
* Outbound dispatch uses `buildActionEnvelope` (from
|
|
26
12
|
* `@ggui-ai/wire`) + `validateOutboundActionEnvelope` + a direct
|
|
@@ -29,16 +15,17 @@
|
|
|
29
15
|
* The `WireConfig` shape lives in `@ggui-ai/wire`. This module
|
|
30
16
|
* implements against that interface.
|
|
31
17
|
*/
|
|
32
|
-
import type {
|
|
18
|
+
import type { ActionEnvelope, Render, StreamEnvelope } from '@ggui-ai/protocol';
|
|
19
|
+
import type { RenderSeedInput } from './types.js';
|
|
33
20
|
import { ClientContractViolationError, type WireConfig } from '@ggui-ai/wire';
|
|
34
21
|
import type { WebSocketMessage } from '@ggui-ai/protocol/transport/websocket';
|
|
35
22
|
/**
|
|
36
23
|
* Outbound send surface — the minimal shape wire-config calls on the
|
|
37
24
|
* WS transport. Pre-B3b this read off `Pick<RendererWebSocketManager,
|
|
38
|
-
* 'send'>`; post-B3b the WS lives inside `@ggui-ai/channel
|
|
25
|
+
* 'send'>`; post-B3b the WS lives inside `@ggui-ai/live-channel`,
|
|
39
26
|
* but wire-config doesn't import the gadget directly — it consumes
|
|
40
27
|
* the shape via this local type so tests can stub `{send: vi.fn()}`
|
|
41
|
-
* without dragging in the channel
|
|
28
|
+
* without dragging in the live-channel types.
|
|
42
29
|
*
|
|
43
30
|
* Frames sent here are always `{type: 'action', payload: ...}` or
|
|
44
31
|
* `{type: 'feedback', payload: ...}` envelopes — wire-config never
|
|
@@ -61,7 +48,7 @@ import type { ObservabilityEmitter } from './observability.js';
|
|
|
61
48
|
* `subscribe()` → first `ack` → render stack items → mount
|
|
62
49
|
* `mountProvisional` → it then calls `streamBus.subscribe('_ggui:
|
|
63
50
|
* preview', …)`. Server-side replay frames for `_ggui:preview` (the
|
|
64
|
-
* provisional A2UI preamble fired during the agent's `
|
|
51
|
+
* provisional A2UI preamble fired during the agent's `ggui_render`
|
|
65
52
|
* BEFORE the user's browser navigated to the viewer) arrive over the
|
|
66
53
|
* WS in the window between ack-resolve and the provisional mount —
|
|
67
54
|
* they hit `emit()` before any listener has subscribed. Without a
|
|
@@ -98,15 +85,16 @@ export declare class StreamBus {
|
|
|
98
85
|
emit(envelope: StreamEnvelope): void;
|
|
99
86
|
}
|
|
100
87
|
export interface BuildRootWireConfigOptions {
|
|
101
|
-
readonly
|
|
88
|
+
readonly renderId: string;
|
|
102
89
|
readonly appId: string;
|
|
103
90
|
/**
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
91
|
+
* Read the currently-mounted {@link Render}. The config's `dispatch`
|
|
92
|
+
* resolves the active render's `actionSpec` through this thunk so
|
|
93
|
+
* the `tool` + outbound validator stay coherent across props_update
|
|
94
|
+
* patches (which replace the render reference) without rebuilding
|
|
95
|
+
* the WireConfig.
|
|
108
96
|
*/
|
|
109
|
-
readonly
|
|
97
|
+
readonly getCurrentRender: () => Render | RenderSeedInput | null;
|
|
110
98
|
/** Handle to the renderer's WS manager; used for outbound `action` + `feedback` frames. */
|
|
111
99
|
readonly manager: RendererSendSurface;
|
|
112
100
|
/** Shared bus for inbound stream deliveries. */
|
|
@@ -154,61 +142,45 @@ export interface BuildRootWireConfigOptions {
|
|
|
154
142
|
* (host inspector vs operator telemetry).
|
|
155
143
|
*/
|
|
156
144
|
readonly onObserve?: ObservabilityEmitter;
|
|
157
|
-
}
|
|
158
|
-
/**
|
|
159
|
-
* Paired root config + scope factory returned by `buildRootWireConfig`.
|
|
160
|
-
*
|
|
161
|
-
* {@link WireConfig} itself carries no `scope(item)` method — the
|
|
162
|
-
* interface is the minimum contract generated code needs, and
|
|
163
|
-
* per-item scoping is a RENDERER concern. Callers that need
|
|
164
|
-
* per-item scoping — currently only the renderer's `bootProduction`
|
|
165
|
-
* via `stackCtx.getScopedWireConfig` — use `buildScopedConfig(item)`
|
|
166
|
-
* on the returned `RootWireConfigBundle`.
|
|
167
|
-
*
|
|
168
|
-
* The scope-factory is PAIRED with the root construction so the
|
|
169
|
-
* `dispatchByItem` / `getStack` internals stay encapsulated — they
|
|
170
|
-
* aren't exported from this module and never leak into the public
|
|
171
|
-
* WireConfig shape.
|
|
172
|
-
*/
|
|
173
|
-
export interface RootWireConfigBundle {
|
|
174
|
-
readonly config: WireConfig;
|
|
175
145
|
/**
|
|
176
|
-
*
|
|
177
|
-
*
|
|
178
|
-
*
|
|
146
|
+
* Optional outbound action sink. When provided, REPLACES the
|
|
147
|
+
* default WS-frame send (`manager.send({type:'action', payload})`).
|
|
148
|
+
*
|
|
149
|
+
* The WS live-channel exists for streamSpec subscriptions (inbound
|
|
150
|
+
* `ggui_emit` fanout + `props_update` + `push` + `data` + `feedback`
|
|
151
|
+
* + `drain_ack` + `channel_payload`). Outbound user actions belong
|
|
152
|
+
* on a different pipe — per MCP-Apps spec §401, the iframe relays
|
|
153
|
+
* `tools/call:ggui_runtime_submit_action` through the host (the
|
|
154
|
+
* `_meta.ui.visibility:['app']` channel), and the server's MCP
|
|
155
|
+
* handler appends to `pendingEventConsumer` so `ggui_consume`
|
|
156
|
+
* wakes the agent.
|
|
157
|
+
*
|
|
158
|
+
* The default `manager.send({type:'action'})` path writes to the
|
|
159
|
+
* session ledger only — it has no downstream consumer in OSS (no
|
|
160
|
+
* `wiredActionRouter`, no bridge to `pendingEventConsumer`).
|
|
161
|
+
* Production callers MUST supply this option to reach the agent.
|
|
162
|
+
*
|
|
163
|
+
* Called AFTER outbound validation passes. Receives the validated
|
|
164
|
+
* {@link ActionEnvelope}; caller extracts payload.action +
|
|
165
|
+
* payload.data + slice meta to route via the host's tools/call
|
|
166
|
+
* relay (see iframe-runtime's `routeDispatch`).
|
|
167
|
+
*
|
|
168
|
+
* Tests omit this opt to exercise the legacy WS-frame path.
|
|
179
169
|
*/
|
|
180
|
-
readonly
|
|
181
|
-
readonly stackItemId?: string;
|
|
182
|
-
readonly contractHash?: string;
|
|
183
|
-
readonly actionSpec?: ActionSpec;
|
|
184
|
-
}) => WireConfig;
|
|
170
|
+
readonly onDispatchEnvelope?: (envelope: ActionEnvelope) => void;
|
|
185
171
|
}
|
|
186
172
|
/**
|
|
187
|
-
* Build the
|
|
173
|
+
* Build the per-render `WireConfig` for the iframe runtime.
|
|
188
174
|
* Bootstraps the renderer's outbound emission + inbound subscription
|
|
189
|
-
* seams.
|
|
175
|
+
* seams. Returns a `WireConfig` keyed by the bootstrap's `renderId`;
|
|
176
|
+
* the active render's `actionSpec` is resolved through the
|
|
177
|
+
* {@link BuildRootWireConfigOptions.getCurrentRender} thunk on every
|
|
178
|
+
* dispatch so props_update patches don't require rebuilding the
|
|
179
|
+
* config.
|
|
190
180
|
*
|
|
191
|
-
*
|
|
192
|
-
*
|
|
193
|
-
* `
|
|
194
|
-
*
|
|
195
|
-
* primitive; `buildScopedConfig` is the pre-bound convenience.
|
|
196
|
-
*/
|
|
197
|
-
export declare function buildRootWireConfig(opts: BuildRootWireConfigOptions): RootWireConfigBundle;
|
|
198
|
-
/**
|
|
199
|
-
* Produce a per-item scoped `WireConfig`. The only override is
|
|
200
|
-
* `dispatch` — every other seam (subscribe) stays shared.
|
|
181
|
+
* Post-render-identity-collapse (2026-05-27): no per-item scope
|
|
182
|
+
* factory — each iframe mounts exactly one render. The earlier
|
|
183
|
+
* `RootWireConfigBundle` / `scopeWireConfig` / `buildScopedConfig`
|
|
184
|
+
* indirection collapsed to a single `WireConfig`.
|
|
201
185
|
*/
|
|
202
|
-
export declare function
|
|
203
|
-
stackItemId?: string;
|
|
204
|
-
contractHash?: string;
|
|
205
|
-
actionSpec?: ActionSpec;
|
|
206
|
-
}, internals: {
|
|
207
|
-
getStack: () => readonly SessionStackEntry[];
|
|
208
|
-
dispatchByItem: (actionName: string, data: unknown, ctx: {
|
|
209
|
-
stackItemId?: string;
|
|
210
|
-
stackIndex: number;
|
|
211
|
-
tool?: string;
|
|
212
|
-
actionSpec?: ActionSpec;
|
|
213
|
-
}) => void;
|
|
214
|
-
}): WireConfig;
|
|
186
|
+
export declare function buildRootWireConfig(opts: BuildRootWireConfigOptions): WireConfig;
|
package/dist/wire-config.js
CHANGED
|
@@ -36,7 +36,7 @@ const RESERVED_CHANNEL_REPLAY_MAX = 256;
|
|
|
36
36
|
* `subscribe()` → first `ack` → render stack items → mount
|
|
37
37
|
* `mountProvisional` → it then calls `streamBus.subscribe('_ggui:
|
|
38
38
|
* preview', …)`. Server-side replay frames for `_ggui:preview` (the
|
|
39
|
-
* provisional A2UI preamble fired during the agent's `
|
|
39
|
+
* provisional A2UI preamble fired during the agent's `ggui_render`
|
|
40
40
|
* BEFORE the user's browser navigated to the viewer) arrive over the
|
|
41
41
|
* WS in the window between ack-resolve and the provisional mount —
|
|
42
42
|
* they hit `emit()` before any listener has subscribed. Without a
|
|
@@ -123,15 +123,18 @@ export class StreamBus {
|
|
|
123
123
|
}
|
|
124
124
|
}
|
|
125
125
|
/**
|
|
126
|
-
* Build the
|
|
126
|
+
* Build the per-render `WireConfig` for the iframe runtime.
|
|
127
127
|
* Bootstraps the renderer's outbound emission + inbound subscription
|
|
128
|
-
* seams.
|
|
128
|
+
* seams. Returns a `WireConfig` keyed by the bootstrap's `renderId`;
|
|
129
|
+
* the active render's `actionSpec` is resolved through the
|
|
130
|
+
* {@link BuildRootWireConfigOptions.getCurrentRender} thunk on every
|
|
131
|
+
* dispatch so props_update patches don't require rebuilding the
|
|
132
|
+
* config.
|
|
129
133
|
*
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
* `
|
|
133
|
-
*
|
|
134
|
-
* primitive; `buildScopedConfig` is the pre-bound convenience.
|
|
134
|
+
* Post-render-identity-collapse (2026-05-27): no per-item scope
|
|
135
|
+
* factory — each iframe mounts exactly one render. The earlier
|
|
136
|
+
* `RootWireConfigBundle` / `scopeWireConfig` / `buildScopedConfig`
|
|
137
|
+
* indirection collapsed to a single `WireConfig`.
|
|
135
138
|
*/
|
|
136
139
|
export function buildRootWireConfig(opts) {
|
|
137
140
|
const clientSeqBox = { current: 0 };
|
|
@@ -162,67 +165,63 @@ export function buildRootWireConfig(opts) {
|
|
|
162
165
|
}
|
|
163
166
|
emitProtocolError(fromClientContractViolation(err));
|
|
164
167
|
}
|
|
165
|
-
|
|
166
|
-
const envelope = buildActionEnvelope({
|
|
167
|
-
sessionId: opts.sessionId,
|
|
168
|
-
type: 'data:submit',
|
|
169
|
-
payload: {
|
|
170
|
-
action: actionName,
|
|
171
|
-
data: data,
|
|
172
|
-
...(ctx.tool ? { tool: ctx.tool } : {}),
|
|
173
|
-
},
|
|
174
|
-
stackIndex: ctx.stackIndex,
|
|
175
|
-
...(ctx.stackItemId ? { stackItemId: ctx.stackItemId } : {}),
|
|
176
|
-
clientSeq: nextSeq(),
|
|
177
|
-
});
|
|
178
|
-
const result = validateOutboundActionEnvelope(ctx.actionSpec, envelope);
|
|
179
|
-
if (!result.valid) {
|
|
180
|
-
surfaceViolation(new ClientContractViolationError('outbound-action', result.violations));
|
|
181
|
-
return;
|
|
182
|
-
}
|
|
183
|
-
sendActionEnvelope(opts.manager, envelope);
|
|
184
|
-
// Observability: a wired-tool dispatch is a `data:submit` whose
|
|
185
|
-
// resolved action has a `tool` binding. Plain agent-routed actions
|
|
186
|
-
// (no `tool` on the actionSpec) fire a `dispatch` activity row in
|
|
187
|
-
// the console via the session-side ring buffer; only wired tools
|
|
188
|
-
// get this observability event. The server separately emits the
|
|
189
|
-
// operational telemetry variant on its TelemetrySink.
|
|
190
|
-
if (ctx.tool !== undefined) {
|
|
191
|
-
emitObserve({
|
|
192
|
-
kind: 'wired-tool-invoked',
|
|
193
|
-
toolName: ctx.tool,
|
|
194
|
-
actionName,
|
|
195
|
-
dispatchedAt: new Date().toISOString(),
|
|
196
|
-
});
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
const root = {
|
|
168
|
+
return {
|
|
200
169
|
app: { appId: opts.appId, appName: opts.appId },
|
|
201
|
-
|
|
170
|
+
render: { renderId: opts.renderId, isConnected: true },
|
|
202
171
|
auth: { isAuthenticated: false },
|
|
203
172
|
dispatch: (actionName, data) => {
|
|
204
|
-
//
|
|
205
|
-
//
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
const
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
activeItem.type !== 'system'
|
|
214
|
-
? activeItem.actionSpec
|
|
173
|
+
// Resolve the active render's actionSpec on every dispatch.
|
|
174
|
+
// Per-render lifecycle: props_update patches replace the render
|
|
175
|
+
// reference; reading through the thunk keeps the tool binding
|
|
176
|
+
// + outbound validator coherent without rebuilding the config.
|
|
177
|
+
const currentRender = opts.getCurrentRender();
|
|
178
|
+
const activeActionSpec = currentRender !== null &&
|
|
179
|
+
currentRender.type !== 'mcpApps' &&
|
|
180
|
+
currentRender.type !== 'system'
|
|
181
|
+
? currentRender.actionSpec
|
|
215
182
|
: undefined;
|
|
216
183
|
const entry = activeActionSpec?.[actionName];
|
|
217
184
|
const tool = entry?.nextStep;
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
: {}),
|
|
185
|
+
const envelope = buildActionEnvelope({
|
|
186
|
+
renderId: opts.renderId,
|
|
187
|
+
type: 'data:submit',
|
|
188
|
+
payload: {
|
|
189
|
+
action: actionName,
|
|
190
|
+
data: data,
|
|
191
|
+
...(tool ? { tool } : {}),
|
|
192
|
+
},
|
|
193
|
+
clientSeq: nextSeq(),
|
|
225
194
|
});
|
|
195
|
+
const result = validateOutboundActionEnvelope(activeActionSpec, envelope);
|
|
196
|
+
if (!result.valid) {
|
|
197
|
+
surfaceViolation(new ClientContractViolationError('outbound-action', result.violations));
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
if (opts.onDispatchEnvelope !== undefined) {
|
|
201
|
+
// Spec-canonical path — the iframe-runtime's LIVE-mode boot
|
|
202
|
+
// wires this to `routeDispatch`, which postMessages
|
|
203
|
+
// `tools/call:ggui_runtime_submit_action` through the MCP-Apps
|
|
204
|
+
// host relay. The default WS-frame send below is retained for
|
|
205
|
+
// tests + legacy callers that don't relay tools/call.
|
|
206
|
+
opts.onDispatchEnvelope(envelope);
|
|
207
|
+
}
|
|
208
|
+
else {
|
|
209
|
+
sendActionEnvelope(opts.manager, envelope);
|
|
210
|
+
}
|
|
211
|
+
// Observability: a wired-tool dispatch is a `data:submit` whose
|
|
212
|
+
// resolved action has a `tool` binding. Plain agent-routed actions
|
|
213
|
+
// (no `tool` on the actionSpec) fire a `dispatch` activity row in
|
|
214
|
+
// the console via the session-side ring buffer; only wired tools
|
|
215
|
+
// get this observability event. The server separately emits the
|
|
216
|
+
// operational telemetry variant on its TelemetrySink.
|
|
217
|
+
if (tool !== undefined) {
|
|
218
|
+
emitObserve({
|
|
219
|
+
kind: 'wired-tool-invoked',
|
|
220
|
+
toolName: tool,
|
|
221
|
+
actionName,
|
|
222
|
+
dispatchedAt: new Date().toISOString(),
|
|
223
|
+
});
|
|
224
|
+
}
|
|
226
225
|
},
|
|
227
226
|
subscribe: (channelName, handler) => {
|
|
228
227
|
return opts.streamBus.subscribe(channelName, (env) => {
|
|
@@ -244,37 +243,6 @@ export function buildRootWireConfig(opts) {
|
|
|
244
243
|
// `actionSpec[*].nextStep` (event metadata) and
|
|
245
244
|
// `streamSpec[*].source.tool` (channel data source).
|
|
246
245
|
};
|
|
247
|
-
// `WireConfig` carries no `scope(item)` method.
|
|
248
|
-
// Callers that want a per-item config use `buildScopedConfig(item)`
|
|
249
|
-
// on the returned bundle, which bakes in the `dispatchByItem` +
|
|
250
|
-
// `getStack` internals so they never leak onto the public
|
|
251
|
-
// WireConfig shape.
|
|
252
|
-
const buildScopedConfig = (item) => scopeWireConfig(root, item, {
|
|
253
|
-
getStack: opts.getStack,
|
|
254
|
-
dispatchByItem,
|
|
255
|
-
});
|
|
256
|
-
return { config: root, buildScopedConfig };
|
|
257
|
-
}
|
|
258
|
-
/**
|
|
259
|
-
* Produce a per-item scoped `WireConfig`. The only override is
|
|
260
|
-
* `dispatch` — every other seam (subscribe) stays shared.
|
|
261
|
-
*/
|
|
262
|
-
export function scopeWireConfig(root, item, internals) {
|
|
263
|
-
return {
|
|
264
|
-
...root,
|
|
265
|
-
dispatch: (actionName, data) => {
|
|
266
|
-
const entry = item.actionSpec?.[actionName];
|
|
267
|
-
const tool = entry?.nextStep;
|
|
268
|
-
const stack = internals.getStack();
|
|
269
|
-
const idx = stack.findIndex((s) => s.id === item.stackItemId);
|
|
270
|
-
internals.dispatchByItem(actionName, data, {
|
|
271
|
-
...(item.stackItemId !== undefined ? { stackItemId: item.stackItemId } : {}),
|
|
272
|
-
stackIndex: idx >= 0 ? idx : 0,
|
|
273
|
-
...(tool !== undefined ? { tool } : {}),
|
|
274
|
-
...(item.actionSpec !== undefined ? { actionSpec: item.actionSpec } : {}),
|
|
275
|
-
});
|
|
276
|
-
},
|
|
277
|
-
};
|
|
278
246
|
}
|
|
279
247
|
// =============================================================================
|
|
280
248
|
// Outbound envelope emission
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ggui-ai/iframe-runtime",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "The iframe-local ggui runtime: a self-contained ESM bundle that boots inside an MCP Apps host iframe, runs the protocol-version handshake, opens the live-channel WebSocket, and
|
|
3
|
+
"version": "0.2.0-alpha.3",
|
|
4
|
+
"description": "The iframe-local ggui runtime: a self-contained ESM bundle that boots inside an MCP Apps host iframe, runs the protocol-version handshake, opens the live-channel WebSocket, and mounts the rendered UI. Bundles React, ReactDOM, and the ggui design system so generated component code has a single seam to read from.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ggui",
|
|
7
7
|
"mcp",
|
|
@@ -34,15 +34,16 @@
|
|
|
34
34
|
}
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
+
"@modelcontextprotocol/ext-apps": "^1.7.2",
|
|
37
38
|
"qrcode-generator": "^2.0.4",
|
|
38
39
|
"react": "^19.0.0",
|
|
39
40
|
"react-dom": "^19.0.0",
|
|
40
|
-
"@ggui-ai/channel
|
|
41
|
-
"@ggui-ai/gadgets": "0.
|
|
42
|
-
"@ggui-ai/design": "0.
|
|
43
|
-
"@ggui-ai/preview-a2ui": "0.
|
|
44
|
-
"@ggui-ai/protocol": "0.
|
|
45
|
-
"@ggui-ai/wire": "0.
|
|
41
|
+
"@ggui-ai/live-channel": "0.2.0-alpha.3",
|
|
42
|
+
"@ggui-ai/gadgets": "0.2.0-alpha.3",
|
|
43
|
+
"@ggui-ai/design": "0.2.0-alpha.3",
|
|
44
|
+
"@ggui-ai/preview-a2ui": "0.2.0-alpha.3",
|
|
45
|
+
"@ggui-ai/protocol": "0.2.0-alpha.3",
|
|
46
|
+
"@ggui-ai/wire": "0.2.0-alpha.3"
|
|
46
47
|
},
|
|
47
48
|
"devDependencies": {
|
|
48
49
|
"@testing-library/react": "^16.0.0",
|
package/dist/bootstrap.d.ts
DELETED
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import type { BootstrapParseResult } from './types.js';
|
|
2
|
-
/**
|
|
3
|
-
* Shared validator core. Accepts an unwrapped bootstrap object (NOT
|
|
4
|
-
* the full `_meta.ggui.bootstrap` envelope — the caller has already
|
|
5
|
-
* stripped envelopes down to this layer).
|
|
6
|
-
*
|
|
7
|
-
* Validates the three-mode discriminator + the optional fields per
|
|
8
|
-
* the {@link GguiBootstrapMeta} contract. Returns a discriminated
|
|
9
|
-
* {@link BootstrapParseResult} the caller surfaces verbatim.
|
|
10
|
-
*
|
|
11
|
-
* Invariants enforced here (see module docblock for the wire contract):
|
|
12
|
-
* - `runtimeUrl` MUST be a non-empty string. All three modes need
|
|
13
|
-
* it because the shell dynamic-script-loads the runtime bundle
|
|
14
|
-
* from this URL.
|
|
15
|
-
* - `sessionId` + `appId` MUST be non-empty strings.
|
|
16
|
-
* - At least one of `{wsUrl-with-token, codeUrl, kind}` MUST be
|
|
17
|
-
* present as a non-empty string. Half-live (wsUrl without token)
|
|
18
|
-
* is MALFORMED.
|
|
19
|
-
* - `expiresAt`, when present + relevant (live mode), MUST parse
|
|
20
|
-
* to a future timestamp.
|
|
21
|
-
*/
|
|
22
|
-
export declare function validateBootstrapMeta(raw: unknown): BootstrapParseResult;
|
|
23
|
-
/**
|
|
24
|
-
* Parse a bootstrap from a `ui/initialize` postMessage response. The
|
|
25
|
-
* argument is the JSON-RPC `result` field — typically
|
|
26
|
-
* `{ toolOutput: { _meta: { ggui: { bootstrap: {...} } }, structuredContent: ... } }`.
|
|
27
|
-
*
|
|
28
|
-
* Used by the runtime's legacy `bootProduction` path (live-mode WS
|
|
29
|
-
* subscription). Spec-canonical for first-party `<McpAppIframe>`
|
|
30
|
-
* Reading-B hosts (Studio, Portal, console).
|
|
31
|
-
*/
|
|
32
|
-
export declare function parseBootstrapFromUiInitialize(uiInitializeResult: unknown): BootstrapParseResult;
|
|
33
|
-
/**
|
|
34
|
-
* Back-compat alias for {@link parseBootstrapFromUiInitialize}.
|
|
35
|
-
*
|
|
36
|
-
* `parseBootstrap` was the original name when the iframe-runtime
|
|
37
|
-
* accepted only the ui/initialize envelope. The parser was later
|
|
38
|
-
* generalized to three envelope shapes (ui/initialize,
|
|
39
|
-
* `__GGUI_BOOTSTRAP__` global, postMessage tool-result), each with its
|
|
40
|
-
* own thin extractor; the shared {@link validateBootstrapMeta} core
|
|
41
|
-
* does the per-envelope shape validation. The original function name
|
|
42
|
-
* is kept exported so downstream consumers (`@ggui-ai/react`, runtime
|
|
43
|
-
* call sites) don't need a sweep — the name still resolves to the
|
|
44
|
-
* same behavior.
|
|
45
|
-
*/
|
|
46
|
-
export declare const parseBootstrap: typeof parseBootstrapFromUiInitialize;
|
|
47
|
-
/**
|
|
48
|
-
* Parse a bootstrap from `globalThis.__GGUI_BOOTSTRAP__` — the
|
|
49
|
-
* synchronous self-contained shell delivery channel. Returns
|
|
50
|
-
* `MALFORMED_BOOTSTRAP` when the global is absent OR not an object;
|
|
51
|
-
* the caller falls through to a postMessage path on either signal.
|
|
52
|
-
*
|
|
53
|
-
* Used by the runtime's autostart resolver as the highest-priority
|
|
54
|
-
* boot source: per-session shells (`/r/<shortCode>`,
|
|
55
|
-
* `ui://ggui/session/<sessionId>`) populate this synchronously
|
|
56
|
-
* BEFORE the runtime bundle's `<script type="module">` evaluates,
|
|
57
|
-
* so the runtime can mount without any postMessage round-trip.
|
|
58
|
-
*/
|
|
59
|
-
export declare function parseBootstrapFromGlobal(): BootstrapParseResult;
|
|
60
|
-
/**
|
|
61
|
-
* Parse a bootstrap from a `ui/notifications/tool-result` postMessage
|
|
62
|
-
* params payload. Looks at BOTH the spec-canonical location
|
|
63
|
-
* (`params._meta.ggui.bootstrap`) and the Reading-B `<McpAppIframe>`
|
|
64
|
-
* convention (`params.toolOutput._meta.ggui.bootstrap`) — Claude
|
|
65
|
-
* Desktop / claude.ai Connector use the first; first-party hosts the
|
|
66
|
-
* second.
|
|
67
|
-
*
|
|
68
|
-
* Used by the runtime's autostart resolver as the second / third
|
|
69
|
-
* boot source (drained from the pre-runtime-load buffer or caught by
|
|
70
|
-
* the live message listener).
|
|
71
|
-
*/
|
|
72
|
-
export declare function parseBootstrapFromToolResult(params: unknown): BootstrapParseResult;
|