@ggui-ai/iframe-runtime 0.6.3 → 0.8.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/runtime.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { DrainAckPayload, GguiSession } from '@ggui-ai/protocol';
1
+ import type { DrainAckPayload, JsonValue, JsonObject, GguiSession } from '@ggui-ai/protocol';
2
2
  import type { WebSocketMessage } from '@ggui-ai/protocol/transport/websocket';
3
3
  import { type McpAppAiGguiRenderMeta, type McpAppBootstrapFailedMessage, type McpAppRendererReadyMessage } from '@ggui-ai/protocol/integrations/mcp-apps';
4
4
  import type { GguiSessionSeedInput } from './types.js';
@@ -71,6 +71,19 @@ export interface RendererBootFailedMessage extends McpAppBootstrapFailedMessage
71
71
  readonly reason: RendererBootFailureReason;
72
72
  }
73
73
  export interface BootSequenceOptions {
74
+ /**
75
+ * Document the boot path mounts into — the render root, and the
76
+ * toast announcer's live regions.
77
+ *
78
+ * MUST be the global `document` in any real deployment. The toast +
79
+ * cue surface reads the global directly, and production passes the
80
+ * global here (see `runBootProduction`), so the two are one object.
81
+ * A caller handing a DIFFERENT document pre-registers the live
82
+ * regions somewhere no announcement will ever reach; the surface then
83
+ * falls back to creating them in the global document at the moment of
84
+ * the first message, which is precisely the same-tick creation that
85
+ * loses that first announcement.
86
+ */
74
87
  readonly doc: Document;
75
88
  /**
76
89
  * Spec-canonical {@link App} instance + its {@link Transport}.
@@ -119,14 +132,17 @@ export interface BootSequenceOptions {
119
132
  * emission on UPGRADE_REQUIRED.
120
133
  * - `subscribe-failed` — forwarded from subscribe's emission on
121
134
  * transient reconnect transitions.
122
- * - `auth-required` — fired by the system channel handler when an
123
- * `auth_required` system frame arrives.
124
135
  * - `channel-transport-*` — fired by the channel-transport router
125
136
  * on per-channel transport picks / fallbacks / resubscribes.
126
137
  *
127
- * Absent = observability emission skipped entirely (matches the
128
- * ProtocolError posture; the `<McpAppIframe>` host wrapper decides
129
- * whether to bind this via its `onObserve` prop).
138
+ * Absent = observability emission for the events above is skipped
139
+ * entirely (matches the ProtocolError posture; the `<McpAppIframe>`
140
+ * host wrapper decides whether to bind this via its `onObserve`
141
+ * prop). Exception: `relay-incapability` events do NOT flow through
142
+ * this seam — they originate in module-level gesture-dispatch code
143
+ * outside the boot graph (it runs independently of any given boot
144
+ * call) and always ride the postMessage-to-parent default, whether
145
+ * or not this option is bound.
130
146
  */
131
147
  readonly onObserve?: ObservabilityEmitter;
132
148
  /**
@@ -204,9 +220,8 @@ export interface RendererHooks {
204
220
  * with `StackRenderer`.
205
221
  *
206
222
  * `onObserve` — optional observability emitter threaded down to
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 —
223
+ * the channel-transport router (`channel-transport-*`). Absent =
224
+ * those sites run silent (the connect-time emission sites —
210
225
  * `connectViaRegistry`'s version + subscribe events — still fire
211
226
  * via their own emitters).
212
227
  */
@@ -271,7 +286,7 @@ export interface RendererHandle {
271
286
  /**
272
287
  * Channel-client registry holding handlers for every WS frame type
273
288
  * the iframe routes (`render`, `data`, `props_update`, `drain_ack`,
274
- * `channel_payload`, `channel_error`, `system`). The
289
+ * `channel_payload`, `channel_error`). The
275
290
  * registry-bound transport is the sole dispatch surface — frames
276
291
  * arrive directly through registered handlers, no longer through a
277
292
  * separate `onMessage` callback.
@@ -327,18 +342,39 @@ export declare function readSelfContainedMeta(): McpAppAiGguiRenderMeta | null;
327
342
  * no WS at all for spec-compliant MCP-Apps hosts that expose no ggui
328
343
  * live channel (claude.ai / ChatGPT / Claude Desktop).
329
344
  *
330
- * Two static-content shapes (the autostart discriminator):
345
+ * Three static-content shapes (the autostart discriminator), in
346
+ * priority order:
331
347
  * - `kind` → a system-card seed (`type:'system'`); no fetch.
348
+ * - `codeB64` → a compiled-component seed carried inline; DECODED,
349
+ * never fetched. Preferred over `codeUrl` when both are present —
350
+ * the inline bytes exist precisely for hosts whose iframe CSP
351
+ * blocks the fetch.
332
352
  * - `codeUrl` → a compiled-component seed; fetches the
333
353
  * content-addressable component bytes.
334
354
  *
335
- * Returns `null` when the meta carries NEITHER (a live-only meta has
355
+ * Returns `null` when the meta carries NONE (a live-only meta has
336
356
  * nothing to seed — the first WS ack mounts it). Throws when a `codeUrl`
337
- * fetch fails so the caller can surface a typed boot failure. The four
338
- * server-assigned ledger fields are intentionally absent — the first
339
- * ack reconciles the seed to a full `GguiSession` (no fabrication).
357
+ * fetch fails or `codeB64` is not valid base64 so the caller can
358
+ * surface a typed boot failure. The four server-assigned ledger fields
359
+ * are intentionally absent — the first ack reconciles the seed to a
360
+ * full `GguiSession` (no fabrication).
340
361
  */
341
362
  export declare function buildGguiSessionSeedInput(meta: McpAppAiGguiRenderMeta): Promise<GguiSessionSeedInput | null>;
363
+ /**
364
+ * Does this slice carry STATIC content the mount surface can paint
365
+ * without any network round-trip authorization — `codeUrl` (fetched),
366
+ * `codeB64` (decoded inline), or `kind` (built-in registry)?
367
+ *
368
+ * The ONE discriminator `bootSequence` consults when deciding whether
369
+ * the static seed-mount path runs. `codeB64` counting here is
370
+ * load-bearing for fetch-blocked hosts: their meta carries codeB64
371
+ * PLUS a live trio the iframe can never connect, and missing the
372
+ * inline arm would skip the only paint path and die on the dead WS.
373
+ * Mirrors `validateMeta`'s static-content arm (meta-parse.ts) and the
374
+ * protocol host-helper's `hasMountModeDiscriminator` — drift among
375
+ * the three IS the historical bug class, hence the named export.
376
+ */
377
+ export declare function hasStaticContentMeta(meta: McpAppAiGguiRenderMeta): boolean;
342
378
  /**
343
379
  * Extract a {@link McpAppAiGguiRenderMeta} from a
344
380
  * `ui/notifications/tool-result` JSON-RPC params payload — the
@@ -354,6 +390,25 @@ export declare function buildGguiSessionSeedInput(meta: McpAppAiGguiRenderMeta):
354
390
  * impossible because the shell never inspects the slice-meta shape.
355
391
  */
356
392
  export declare function extractMetaFromToolResult(params: unknown): McpAppAiGguiRenderMeta | null;
393
+ /**
394
+ * Drain `window.__GGUI_PENDING_TOOL_RESULTS__` — the buffer the
395
+ * minimal shell populates while messages arrive between
396
+ * shell-load and runtime-load. Returns the NEWEST valid slice meta
397
+ * (each new tool-result supersedes the previous — every emission
398
+ * carries complete state, so last-wins loses nothing). Newest-first
399
+ * matters on the inline-runtime shell: its preflight completes the
400
+ * handshake BEFORE the large bundle parses, so a render AND a
401
+ * follow-up update can both land in the buffer during parse — booting
402
+ * the oldest would paint stale props and drop the update forever
403
+ * (buffered entries never reach the post-mount listener; it hears
404
+ * only post-App arrivals).
405
+ *
406
+ * The shell-side buffer contract: an array whose ELEMENTS are the raw
407
+ * JSON-RPC `params` values of `ui/notifications/tool-result`
408
+ * notifications, in arrival order (shells cap it newest-biased). The
409
+ * runtime reads it once at autostart.
410
+ */
411
+ export declare function readPendingToolResults(): McpAppAiGguiRenderMeta | null;
357
412
  /**
358
413
  * Render a submit-action's `data` payload as a short inline string
359
414
  * for embedding in a `ui/message` consent prompt. Goal: human-
@@ -391,6 +446,45 @@ export declare function emitAudit(args: {
391
446
  readonly actionId: string;
392
447
  readonly firedAt: string;
393
448
  }): void;
449
+ /**
450
+ * The announcer's two live regions, keyed by politeness (ggui#447).
451
+ *
452
+ * Two regions rather than one with a swapped `aria-live`: politeness is
453
+ * read when a region is REGISTERED, so a screen reader that already
454
+ * mapped a node as polite is not guaranteed to notice it turning
455
+ * assertive in the same mutation that delivers the text. Fixed
456
+ * politeness per node removes the question.
457
+ */
458
+ interface ToastLiveRegions {
459
+ /** `role="status"` — queued behind whatever is being read. */
460
+ readonly polite: HTMLElement;
461
+ /** `role="alert"` — interrupts. Failure + action-required classes. */
462
+ readonly assertive: HTMLElement;
463
+ }
464
+ /**
465
+ * Ensure the pair of live regions the toast surface announces through.
466
+ *
467
+ * MUST be called before the first message can land — a live region
468
+ * created in the same tick as its first content is a region the screen
469
+ * reader was never watching, and that first announcement is lost. The
470
+ * boot path calls this at the same point it mounts the render root, so
471
+ * every toast (at minimum a user gesture away) lands in a region that
472
+ * has been registered for many frames. The toast + cue paths call it
473
+ * again defensively; it is idempotent per document.
474
+ *
475
+ * Visually hidden rather than `display:none`: a region that is not
476
+ * rendered is not in the accessibility tree, and content added to it is
477
+ * never announced. The clip rectangle keeps it out of the visual layout
478
+ * while leaving it live.
479
+ *
480
+ * Returns `null` when the document has no body yet — nothing to attach
481
+ * to, and no announcement is possible.
482
+ *
483
+ * @internal — exported for unit tests.
484
+ */
485
+ export declare function ensureToastAnnouncer(doc: Document): ToastLiveRegions | null;
486
+ /** @internal — exported for unit tests to reset module state. */
487
+ export declare function __resetRelayNoticeForTest(): void;
394
488
  /**
395
489
  * Dispatch a submit-action via the empirically-validated bridge
396
490
  * chain (validated against claude.ai): a `tools/call` to
@@ -411,6 +505,65 @@ export declare function dispatchSubmitAction(args: {
411
505
  readonly sessionId: string;
412
506
  readonly appId: string;
413
507
  }): void;
508
+ /**
509
+ * Channel-transport router's `tools/call` invoker (ggui#440) —
510
+ * iframe-polling transport, `tools/call` against the parent MCP host
511
+ * via `app.callServerTool` (spec-canonical) when the App handle is
512
+ * set; falls back to raw postMessage pre-handshake or in tests.
513
+ * Direct call (no LLM consent loop). Returns the tool's
514
+ * structuredContent (or `content[0]` if that's where the payload
515
+ * landed) as a `JsonValue`. On RPC error we throw — the router
516
+ * catches and silently retries on the next tick.
517
+ *
518
+ * Once a REAL gesture has confirmed this host can't relay (the latch
519
+ * above — set only after {@link dispatchSubmitAction} actually tried
520
+ * and failed on a host with no `serverTools` advertised, with
521
+ * capabilities already captured, AND the failure was relay-shaped —
522
+ * not a well-formed `{ok:false}` result; see `isRelayShapedFailure`),
523
+ * every subsequent poll will fail identically.
524
+ *
525
+ * This guard does NOT stop the router's poll loop, and nothing here
526
+ * should ever be written so that it could (ggui#443). It does two
527
+ * things. It makes each doomed tick CHEAP — the poll throws before
528
+ * the `callServerToolSpec` round-trip. And, by throwing the typed
529
+ * {@link RelayIncapableError} rather than a bare `Error`, it tells
530
+ * the router WHICH KIND of failure this is: `channel-transport.ts`'s
531
+ * tick classifies structural rejections apart from transient ones and
532
+ * drops the channel to a slow probe cadence, emitting one
533
+ * `channel-poll-degraded` event per transition. The first poll that
534
+ * succeeds after the latch clears restores the normal cadence and
535
+ * emits `channel-poll-recovered`.
536
+ *
537
+ * The channel keeps probing throughout, which is what makes that
538
+ * recovery possible: the latch clears only when a call succeeds, and
539
+ * only an attempt can discover that. A channel stopped on structural
540
+ * failure could never come back.
541
+ *
542
+ * Keyed on the LATCH, not on `hostCanRelayToolCalls()` directly: raw
543
+ * advertisement absence never changes after boot, so gating on it
544
+ * here would throw pre-attempt for the LIFE OF THE SESSION on any
545
+ * host that under-advertises but genuinely proxies `tools/call`
546
+ * (ggui's own embed host pre-Task-2 is exactly this case) — silently
547
+ * killing working channel polls forever. That is "absence of a
548
+ * capability blocks an attempt", which the fail-safe constraint
549
+ * forbids. The latch implies `!hostCanRelayToolCalls()` (it only
550
+ * ever sets inside that guard), so testing it alone is sufficient —
551
+ * a host that never fails a real gesture never latches, and its
552
+ * channels keep working.
553
+ *
554
+ * Extracted to a named export (rather than an inline closure inside
555
+ * `bootProduction`) so unit tests can drive it directly through the
556
+ * lightweight `MockTransport` harness — `bootProduction` dynamic-
557
+ * imports the full react/design/wire module graph, which is too
558
+ * heavy to exercise from a spec file (see
559
+ * `boot-production-context.test.ts`'s docstring for precedent).
560
+ *
561
+ * @internal — exported for unit tests + production reuse.
562
+ */
563
+ export declare function channelToolsCall(args: {
564
+ readonly toolName: string;
565
+ readonly args: JsonObject;
566
+ }): Promise<JsonValue>;
414
567
  /**
415
568
  * Resolve the name of the spec-canonical submit-action receiver tool
416
569
  * the dispatch path targets with `tools/call`.