@ggui-ai/iframe-runtime 0.2.0-alpha.4 → 0.4.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.
Files changed (58) hide show
  1. package/dist/channel-transport.d.ts +20 -30
  2. package/dist/channel-transport.js +24 -24
  3. package/dist/channels/data.d.ts +5 -20
  4. package/dist/channels/data.js +4 -52
  5. package/dist/channels/index.d.ts +0 -1
  6. package/dist/channels/index.js +0 -1
  7. package/dist/channels/props-update.d.ts +9 -9
  8. package/dist/channels/props-update.js +9 -9
  9. package/dist/channels/render-frame.d.ts +5 -5
  10. package/dist/channels/render-frame.js +5 -5
  11. package/dist/compiled-validators.d.ts +2 -2
  12. package/dist/compiled-validators.js +1 -1
  13. package/dist/context-observer.d.ts +4 -4
  14. package/dist/context-observer.js +3 -3
  15. package/dist/events-polling.d.ts +7 -7
  16. package/dist/events-polling.js +5 -5
  17. package/dist/globals.d.ts +1 -1
  18. package/dist/host-context-emitter.d.ts +4 -4
  19. package/dist/host-context-emitter.js +9 -9
  20. package/dist/iframe-runtime.js +68 -78
  21. package/dist/iframe-runtime.meta.json +1 -0
  22. package/dist/index.d.ts +4 -4
  23. package/dist/index.js +1 -1
  24. package/dist/lifecycle.d.ts +3 -3
  25. package/dist/lifecycle.js +29 -6
  26. package/dist/mcp-app-iframe-host.d.ts +8 -8
  27. package/dist/mcp-app-iframe-host.js +4 -4
  28. package/dist/meta-parse.d.ts +3 -4
  29. package/dist/meta-parse.js +17 -44
  30. package/dist/observability.d.ts +14 -36
  31. package/dist/observability.js +2 -1
  32. package/dist/protocol-error.d.ts +38 -48
  33. package/dist/protocol-error.js +5 -23
  34. package/dist/provisional-renderer.js +3 -3
  35. package/dist/react-renderer.d.ts +16 -6
  36. package/dist/react-renderer.js +22 -40
  37. package/dist/registry-subscribe.d.ts +7 -7
  38. package/dist/registry-subscribe.js +28 -14
  39. package/dist/render-item.d.ts +22 -15
  40. package/dist/render-item.js +7 -14
  41. package/dist/runtime.d.ts +79 -105
  42. package/dist/runtime.js +219 -269
  43. package/dist/status-dom.d.ts +12 -12
  44. package/dist/status-dom.js +11 -11
  45. package/dist/system-cards/NoCredentialsCard.d.ts +1 -1
  46. package/dist/system-cards/NoCredentialsCard.js +1 -1
  47. package/dist/system-cards/ProtocolProbeCard.d.ts +13 -17
  48. package/dist/system-cards/ProtocolProbeCard.js +21 -25
  49. package/dist/system-cards/index.d.ts +5 -5
  50. package/dist/system-cards/index.js +5 -5
  51. package/dist/types.d.ts +29 -34
  52. package/dist/validation.d.ts +4 -5
  53. package/dist/validation.js +5 -6
  54. package/dist/wire-config.d.ts +43 -106
  55. package/dist/wire-config.js +29 -195
  56. package/package.json +14 -12
  57. package/dist/channels/feedback.d.ts +0 -14
  58. package/dist/channels/feedback.js +0 -19
package/dist/runtime.d.ts CHANGED
@@ -1,6 +1,7 @@
1
- import type { DrainAckPayload, Render } from '@ggui-ai/protocol';
1
+ import type { DrainAckPayload, GguiSession } from '@ggui-ai/protocol';
2
2
  import type { WebSocketMessage } from '@ggui-ai/protocol/transport/websocket';
3
- import type { ValidatedMcpAppAiGguiMeta, RenderSeedInput } from './types.js';
3
+ import { type McpAppAiGguiRenderMeta, type McpAppBootstrapFailedMessage, type McpAppRendererReadyMessage } from '@ggui-ai/protocol/integrations/mcp-apps';
4
+ import type { GguiSessionSeedInput } from './types.js';
4
5
  import type { McpAppAiGguiMetaParseFailureReason } from './types.js';
5
6
  import { App } from '@modelcontextprotocol/ext-apps';
6
7
  import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
@@ -40,8 +41,8 @@ export type ConnectAppResult = {
40
41
  * a replace only happens in tests that reuse the module across
41
42
  * scenarios (and want each spec to bind its own App).
42
43
  *
43
- * @internal — production callers are the boot paths
44
- * (`bootProduction`, `bootSelfContained`); tests inject directly to
44
+ * @internal — the production caller is `bootSequence` (reached via
45
+ * `bootProduction`); tests inject directly to
45
46
  * drive outbound `tools/call` through a `MockTransport`-bound App
46
47
  * without invoking the full boot pipeline.
47
48
  */
@@ -60,10 +61,14 @@ export declare function __resetAppForTest(): void;
60
61
  * codes.
61
62
  */
62
63
  export type RendererBootFailureReason = McpAppAiGguiMetaParseFailureReason | 'UI_INITIALIZE_FAILED' | 'WS_HANDSHAKE_FAILED' | 'UPGRADE_REQUIRED';
63
- export interface RendererBootFailedMessage {
64
- readonly type: 'ggui:bootstrap-failed';
64
+ /**
65
+ * The renderer's emission-side narrowing of the protocol-owned
66
+ * {@link McpAppBootstrapFailedMessage} envelope: same wire shape, but
67
+ * `reason` is pinned to the closed {@link RendererBootFailureReason}
68
+ * union so every emission site stays exhaustive-checkable.
69
+ */
70
+ export interface RendererBootFailedMessage extends McpAppBootstrapFailedMessage {
65
71
  readonly reason: RendererBootFailureReason;
66
- readonly message: string;
67
72
  }
68
73
  export interface BootSequenceOptions {
69
74
  readonly doc: Document;
@@ -97,10 +102,7 @@ export interface BootSequenceOptions {
97
102
  * Notify-parent hook. Default posts to `window.parent`; tests
98
103
  * inject a recorder.
99
104
  */
100
- readonly notifyParent: (msg: RendererBootFailedMessage | {
101
- type: 'ggui:renderer-ready';
102
- version: string;
103
- }) => void;
105
+ readonly notifyParent: (msg: RendererBootFailedMessage | McpAppRendererReadyMessage) => void;
104
106
  /**
105
107
  * Optional typed {@link import('./protocol-error.js').ProtocolError}
106
108
  * sink. Every bootstrap-failure path fires this in parallel with
@@ -113,17 +115,14 @@ export interface BootSequenceOptions {
113
115
  * Optional {@link ObservabilityEmitter} sink. Fires for every
114
116
  * renderer-observed event flow the host inspector cares about:
115
117
  *
116
- * - `contract-error-emitted` — whenever a `_ggui:contract-error`
117
- * envelope arrives on the live channel.
118
118
  * - `schema-version-mismatch` — forwarded from subscribe's own
119
119
  * emission on UPGRADE_REQUIRED.
120
120
  * - `subscribe-failed` — forwarded from subscribe's emission on
121
121
  * transient reconnect transitions.
122
- *
123
- * `wired-tool-invoked` is emitted from the wire config's outbound
124
- * dispatch path (see `buildRootWireConfig`'s `onObserve`), NOT from
125
- * here — the `data:submit` envelope hasn't passed through this
126
- * handler when it fires.
122
+ * - `auth-required` — fired by the system channel handler when an
123
+ * `auth_required` system frame arrives.
124
+ * - `channel-transport-*` — fired by the channel-transport router
125
+ * on per-channel transport picks / fallbacks / resubscribes.
127
126
  *
128
127
  * Absent = observability emission skipped entirely (matches the
129
128
  * ProtocolError posture; the `<McpAppIframe>` host wrapper decides
@@ -143,7 +142,7 @@ export interface BootSequenceOptions {
143
142
  *
144
143
  * Sequence the renderer guarantees when this is bound:
145
144
  * 1. `mounting` — before any IO, paired with `ggui:renderer-ready`.
146
- * 2a. `code-ready` — after first ack folds the initial stack.
145
+ * 2a. `code-ready` — after first ack folds the initial render.
147
146
  * 2b. `error` — paired with every `ggui:bootstrap-failed` emission;
148
147
  * `error.code` mirrors the legacy envelope's `reason`.
149
148
  *
@@ -153,9 +152,9 @@ export interface BootSequenceOptions {
153
152
  /**
154
153
  * Optional renderer hook. When present, the boot sequence:
155
154
  * (1) calls `renderer.setup()` after bootstrap parse;
156
- * (2) mounts the single stack entry into the renderer's slot on
157
- * first ack + re-applies on every subsequent push; (3) routes
158
- * inbound `data` / `props_update` / `feedback` frames through the
155
+ * (2) mounts the single render into the renderer's slot on
156
+ * first ack + re-applies on every subsequent render; (3) routes
157
+ * inbound `data` / `props_update` frames through the
159
158
  * supplied wire config + StreamBus.
160
159
  *
161
160
  * When absent (default), the boot sequence runs the placeholder
@@ -177,7 +176,7 @@ export interface BootSequenceOptions {
177
176
  * runs — spec mandates `ui/initialize` regardless of how slice meta
178
177
  * arrives, and `hostContext` is captured from `app.getHostContext()`.
179
178
  */
180
- readonly preResolvedMeta?: ValidatedMcpAppAiGguiMeta;
179
+ readonly preResolvedMeta?: McpAppAiGguiRenderMeta;
181
180
  /**
182
181
  * How long to wait for the spec-canonical `ui/notifications/tool-result`
183
182
  * notification (Tier 2 of the resolver chain) before failing with
@@ -205,14 +204,14 @@ export interface RendererHooks {
205
204
  * with `StackRenderer`.
206
205
  *
207
206
  * `onObserve` — optional observability emitter threaded down to
208
- * the root wire config so `wired-tool-invoked` events fire on
209
- * every successful outbound wired-action dispatch. Absent = the
210
- * wire config runs with a no-op observer (the other emission sites
211
- * — subscribe.ts, handleObservableMessage — still fire via their
212
- * own emitters).
207
+ * the channel handlers (system `auth-required`) and the
208
+ * channel-transport router (`channel-transport-*`). Absent = those
209
+ * sites run silent (the connect-time emission sites —
210
+ * `connectViaRegistry`'s version + subscribe events — still fire
211
+ * via their own emitters).
213
212
  */
214
213
  setup(params: {
215
- readonly meta: ValidatedMcpAppAiGguiMeta;
214
+ readonly meta: McpAppAiGguiRenderMeta;
216
215
  readonly renderInto: HTMLElement;
217
216
  readonly statusRefs: StatusRefs;
218
217
  readonly onObserve?: ObservabilityEmitter;
@@ -241,14 +240,14 @@ export interface RendererHandle {
241
240
  * Shared by the `render` and `props_update` channel handlers so React
242
241
  * updates flow through one path.
243
242
  */
244
- applyRender(render: Render | RenderSeedInput): Promise<void>;
243
+ applyRender(render: GguiSession | GguiSessionSeedInput): Promise<void>;
245
244
  /**
246
245
  * Read the currently-mounted render. `null` until the first
247
246
  * render frame lands. Read by the `props_update` + `data` channel
248
247
  * handlers to validate inbound payloads against the active render's
249
248
  * `propsSpec` / `streamSpec`.
250
249
  */
251
- getCurrentRender(): Render | RenderSeedInput | null;
250
+ getCurrentGguiSession(): GguiSession | GguiSessionSeedInput | null;
252
251
  readonly validatorCtx: RendererValidatorContext;
253
252
  /**
254
253
  * Send surface for outbound frames. Wired by `setup()` to the WS
@@ -272,7 +271,7 @@ export interface RendererHandle {
272
271
  /**
273
272
  * Channel-client registry holding handlers for every WS frame type
274
273
  * the iframe routes (`render`, `data`, `props_update`, `drain_ack`,
275
- * `channel_payload`, `channel_error`, `system`, `feedback`). The
274
+ * `channel_payload`, `channel_error`, `system`). The
276
275
  * registry-bound transport is the sole dispatch surface — frames
277
276
  * arrive directly through registered handlers, no longer through a
278
277
  * separate `onMessage` callback.
@@ -306,20 +305,9 @@ export interface BootSequenceResult {
306
305
  * render; this replaces the earlier `StackModel` return that wrapped
307
306
  * a multi-item model.
308
307
  */
309
- readonly mountedRender: Render | RenderSeedInput | null;
308
+ readonly mountedRender: GguiSession | GguiSessionSeedInput | null;
310
309
  }
311
310
  export declare function bootSequence(opts: BootSequenceOptions): Promise<BootSequenceResult>;
312
- /**
313
- * Self-contained slice-meta shape — alias for {@link
314
- * ValidatedMcpAppAiGguiMeta}. Post-Phase-B every delivery channel
315
- * produces a single `McpAppAiGguiRenderMeta` slice.
316
- *
317
- * The export is kept so downstream consumers (`@ggui-ai/iframe-runtime`
318
- * barrel, dependent test suites) don't break.
319
- *
320
- * @public
321
- */
322
- export type SelfContainedMcpAppAiGguiMeta = ValidatedMcpAppAiGguiMeta;
323
311
  /**
324
312
  * Read `globalThis.__GGUI_META__` synchronously, validate against
325
313
  * the unified slice-meta shape, and return the typed meta (or null
@@ -331,11 +319,11 @@ export type SelfContainedMcpAppAiGguiMeta = ValidatedMcpAppAiGguiMeta;
331
319
  * reader contract — the autostart resolver only needs the "valid
332
320
  * meta or fall through" signal.
333
321
  */
334
- export declare function readSelfContainedMeta(): SelfContainedMcpAppAiGguiMeta | null;
322
+ export declare function readSelfContainedMeta(): McpAppAiGguiRenderMeta | null;
335
323
  /**
336
324
  * Project the inline `__GGUI_META__` bootstrap into a
337
- * {@link RenderSeedInput} the mount surface can paint immediately,
338
- * BEFORE the authoritative wire `Render` arrives over the WS — and with
325
+ * {@link GguiSessionSeedInput} the mount surface can paint immediately,
326
+ * BEFORE the authoritative wire `GguiSession` arrives over the WS — and with
339
327
  * no WS at all for spec-compliant MCP-Apps hosts that expose no ggui
340
328
  * live channel (claude.ai / ChatGPT / Claude Desktop).
341
329
  *
@@ -348,11 +336,11 @@ export declare function readSelfContainedMeta(): SelfContainedMcpAppAiGguiMeta |
348
336
  * nothing to seed — the first WS ack mounts it). Throws when a `codeUrl`
349
337
  * fetch fails so the caller can surface a typed boot failure. The four
350
338
  * server-assigned ledger fields are intentionally absent — the first
351
- * ack reconciles the seed to a full `Render` (no fabrication).
339
+ * ack reconciles the seed to a full `GguiSession` (no fabrication).
352
340
  */
353
- export declare function buildRenderSeedInput(meta: SelfContainedMcpAppAiGguiMeta): Promise<RenderSeedInput | null>;
341
+ export declare function buildGguiSessionSeedInput(meta: McpAppAiGguiRenderMeta): Promise<GguiSessionSeedInput | null>;
354
342
  /**
355
- * Extract a {@link SelfContainedMcpAppAiGguiMeta} from a
343
+ * Extract a {@link McpAppAiGguiRenderMeta} from a
356
344
  * `ui/notifications/tool-result` JSON-RPC params payload — the
357
345
  * postMessage delivery shape MCP Apps hosts (Claude Desktop, claude.ai
358
346
  * web) use to push the active tool's `_meta` to the iframe.
@@ -365,9 +353,9 @@ export declare function buildRenderSeedInput(meta: SelfContainedMcpAppAiGguiMeta
365
353
  * lagged the protocol" bug class: shell + runtime drift becomes
366
354
  * impossible because the shell never inspects the slice-meta shape.
367
355
  */
368
- export declare function extractMetaFromToolResult(params: unknown): SelfContainedMcpAppAiGguiMeta | null;
356
+ export declare function extractMetaFromToolResult(params: unknown): McpAppAiGguiRenderMeta | null;
369
357
  /**
370
- * Render a wired-action's `data` payload as a short inline string
358
+ * Render a submit-action's `data` payload as a short inline string
371
359
  * for embedding in a `ui/message` consent prompt. Goal: human-
372
360
  * readable, not a JSON dump. Falls back to truncated JSON for
373
361
  * nested values so the prompt doesn't drop information silently.
@@ -378,15 +366,15 @@ export declare function extractMetaFromToolResult(params: unknown): SelfContaine
378
366
  * the LLM think every dispatch was a contentless "Please proceed
379
367
  * with **<intent>**" request.
380
368
  */
381
- export declare function formatWiredActionDataInline(data: unknown): string;
369
+ export declare function formatSubmitActionDataInline(data: unknown): string;
382
370
  /**
383
371
  * Fire a single canonical action envelope (`tools/call
384
372
  * ggui_runtime_submit_action`) carrying the typed `{kind, payload, …}` shape
385
373
  * defined in `@ggui-ai/protocol/integrations/mcp-apps`. Every
386
- * user-driven gesture (Pattern α/β dispatch, native-idiom anchor click,
387
- * native-idiom fullscreen request) calls this alongside its primary
388
- * host effect, so operators get **uniform server-side observability**
389
- * across every gesture kind.
374
+ * user-driven gesture (submit-action dispatch, native-idiom anchor
375
+ * click, native-idiom fullscreen request) calls this alongside its
376
+ * primary host effect, so operators get **uniform server-side
377
+ * observability** across every gesture kind.
390
378
  *
391
379
  * Fail-soft: a rejected audit fire MUST NOT block the primary host
392
380
  * effect. Detached-parent / host-rejected audit envelopes are
@@ -398,72 +386,58 @@ export declare function emitAudit(args: {
398
386
  readonly toolName: string;
399
387
  readonly kind: 'dispatch' | 'openLink' | 'requestDisplayMode';
400
388
  readonly payload: Record<string, unknown>;
401
- readonly renderId: string;
389
+ readonly sessionId: string;
402
390
  readonly appId: string;
403
391
  readonly actionId: string;
404
392
  readonly firedAt: string;
405
393
  }): void;
406
- /** @internal — exported for unit tests. */
407
- export declare function dispatchWiredAction(args: {
408
- readonly toolName: string;
409
- readonly intent: string;
410
- readonly data: unknown;
411
- readonly renderId: string;
412
- readonly appId: string;
413
- }): void;
414
394
  /**
415
- * Fire a wired action via Pattern α — direct `tools/call` against a
416
- * same-server, app-visible target tool. The iframe is allowed to fire
417
- * the tool directly from this server connection per MCP-Apps spec
418
- * §2026-01-26 visibility rules. Skips the submit_action pipe entirely:
419
- * the tool fires without the agent's involvement (the whole point of
420
- * Pattern α), chat stays clean.
421
- *
422
- * PIPE-2 design note: Pattern α deliberately does NOT fire
423
- * `submit_action` — a pipe append would queue the gesture for the
424
- * agent's `ggui_consume` long-poll, causing double-processing
425
- * (host's tool relay AND agent's reaction). RenderInspector loses
426
- * Pattern α observability for now; re-add via a dedicated audit-only
427
- * gesture kind if operators need it.
395
+ * Dispatch a submit-action via the empirically-validated bridge
396
+ * chain (validated against claude.ai): a `tools/call` to
397
+ * {@link toolName} (the `ggui_runtime_submit_action` receiver)
398
+ * appends the gesture to the render's consume pipe, paired with a
399
+ * silent `ui/update-model-context` prime and — only when the server
400
+ * reports no active `ggui_consume` long-poll — a `ui/message`
401
+ * doorbell so a fresh agent turn drains the pipe. Hosts MUST honor
402
+ * `_meta.ui.visibility:['app']` per spec §401, otherwise the
403
+ * `tools/call` is silently rejected (probe found this empirically).
428
404
  *
429
405
  * @internal — exported for unit tests.
430
406
  */
431
- export declare function fireDirectToolCall(args: {
432
- readonly targetToolName: string;
407
+ export declare function dispatchSubmitAction(args: {
408
+ readonly toolName: string;
409
+ readonly intent: string;
433
410
  readonly data: unknown;
411
+ readonly sessionId: string;
412
+ readonly appId: string;
434
413
  }): void;
435
414
  /**
436
- * Pure routing helper for `WireConfig.dispatch` — chooses Pattern α
437
- * (direct `tools/call`) when the action's wired tool is in
438
- * `appCallableTools`, otherwise falls back to Pattern β (the 3-message
439
- * bridge).
440
- *
441
- * The routing logic was previously inlined as a closure body inside
442
- * `bootSelfContained`'s `wireConfig.dispatch`, with a verbatim
443
- * re-creation in `__tests__/dispatch-routing.test.ts`. Tests could
444
- * pass against stale code if either drifted. Extraction means
445
- * production + tests now exercise the same code path.
446
- *
447
- * Pattern α (same-server, app-visible target tool): chat stays clean,
448
- * no LLM consent loop. Pattern β (anything else: cross-server tool, no
449
- * wired tool, or tool not in `appCallableTools`): the canonical
450
- * 3-message workaround the LLM brokers when the tool isn't directly
451
- * callable from the iframe.
415
+ * Resolve the name of the spec-canonical submit-action receiver tool
416
+ * the dispatch path targets with `tools/call`.
452
417
  *
453
418
  * @internal — exported for unit tests + production reuse.
454
419
  */
455
420
  export declare function resolveDispatchToolName(): string;
456
421
  /**
422
+ * Routing helper for `WireConfig.dispatch` — every user action takes
423
+ * the same path: enqueue the gesture onto the render's pending-event
424
+ * pipe via `submit_action`. The agent retrieves it EXCLUSIVELY via
425
+ * `ggui_consume` — the gesture never travels inline, so there is no
426
+ * `nextStep` hint to forward client-side (the server derives the
427
+ * agent-facing hint from the render's `actionSpec[name].nextStep`
428
+ * when it builds the consume event).
429
+ *
430
+ * Lives as a named export (rather than a closure body inside the
431
+ * boot path) so production + tests exercise the same code path.
432
+ *
457
433
  * @internal — exported for unit tests + production reuse.
458
434
  */
459
435
  export declare function routeDispatch(args: {
460
436
  readonly actionName: string;
461
437
  readonly data: unknown;
462
438
  readonly meta: {
463
- readonly renderId: string;
439
+ readonly sessionId: string;
464
440
  readonly appId: string;
465
- readonly appCallableTools?: readonly string[];
466
- readonly actionNextSteps?: Readonly<Record<string, string>>;
467
441
  };
468
442
  readonly dispatchToolName: string;
469
443
  }): void;
@@ -476,7 +450,7 @@ export declare function routeDispatch(args: {
476
450
  export declare function openLinkInParent(args: {
477
451
  readonly toolName: string;
478
452
  readonly url: string;
479
- readonly renderId: string;
453
+ readonly sessionId: string;
480
454
  readonly appId: string;
481
455
  }): void;
482
456
  /**
@@ -489,7 +463,7 @@ export declare function openLinkInParent(args: {
489
463
  export declare function requestDisplayModeInParent(args: {
490
464
  readonly toolName: string;
491
465
  readonly mode: 'fullscreen' | 'pip' | 'inline';
492
- readonly renderId: string;
466
+ readonly sessionId: string;
493
467
  readonly appId: string;
494
468
  }): void;
495
469
  /**
@@ -529,7 +503,7 @@ export declare function requestDisplayModeInParent(args: {
529
503
  /** @internal — exported for unit tests. */
530
504
  export declare function installAnchorClickInterceptor(args: {
531
505
  readonly dispatchToolName: string;
532
- readonly renderId: string;
506
+ readonly sessionId: string;
533
507
  readonly appId: string;
534
508
  }): void;
535
509
  /**
@@ -565,12 +539,12 @@ export declare function installAnchorClickInterceptor(args: {
565
539
  *
566
540
  * Idempotent: the {@link fullscreenInterceptInstalled} guard prevents
567
541
  * re-mounts from chaining wrappers (which would also leak the prior
568
- * mount's `renderId`/`appId` if they ever differed).
542
+ * mount's `sessionId`/`appId` if they ever differed).
569
543
  */
570
544
  /** @internal — exported for unit tests. */
571
545
  export declare function installFullscreenInterceptors(args: {
572
546
  readonly dispatchToolName: string;
573
- readonly renderId: string;
547
+ readonly sessionId: string;
574
548
  readonly appId: string;
575
549
  }): void;
576
550
  /**