@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/channel-transport.d.ts +76 -19
- package/dist/channel-transport.js +137 -29
- package/dist/channels/index.d.ts +0 -1
- package/dist/channels/index.js +0 -1
- package/dist/host-capabilities.d.ts +52 -0
- package/dist/host-capabilities.js +54 -0
- package/dist/iframe-runtime.js +97 -85
- package/dist/iframe-runtime.meta.json +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/mcp-app-iframe-host.js +8 -1
- package/dist/meta-parse.d.ts +3 -3
- package/dist/meta-parse.js +13 -10
- package/dist/observability.d.ts +76 -52
- package/dist/react-renderer.js +70 -18
- package/dist/relay-incapability.d.ts +64 -0
- package/dist/relay-incapability.js +69 -0
- package/dist/runtime.d.ts +168 -15
- package/dist/runtime.js +792 -74
- package/package.json +7 -7
- package/dist/channels/system.d.ts +0 -26
- package/dist/channels/system.js +0 -44
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
|
|
128
|
-
* ProtocolError posture; the `<McpAppIframe>`
|
|
129
|
-
* whether to bind this via its `onObserve`
|
|
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
|
|
208
|
-
*
|
|
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
|
|
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
|
-
*
|
|
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
|
|
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
|
|
338
|
-
*
|
|
339
|
-
* ack reconciles the seed to a
|
|
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`.
|