@ggui-ai/iframe-runtime 0.25.0 → 0.27.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 (40) hide show
  1. package/README.md +2 -2
  2. package/dist/channels/data.d.ts +8 -0
  3. package/dist/channels/data.js +2 -0
  4. package/dist/credential-controller.d.ts +71 -0
  5. package/dist/credential-controller.js +135 -0
  6. package/dist/iframe-runtime.js +97 -94
  7. package/dist/iframe-runtime.meta.json +1 -1
  8. package/dist/index.d.ts +2 -2
  9. package/dist/ladders.d.ts +77 -0
  10. package/dist/ladders.js +383 -0
  11. package/dist/meta-parse.js +38 -8
  12. package/dist/observability.d.ts +22 -1
  13. package/dist/react-renderer.d.ts +20 -0
  14. package/dist/react-renderer.js +22 -0
  15. package/dist/registry-subscribe.d.ts +37 -1
  16. package/dist/registry-subscribe.js +34 -8
  17. package/dist/render-failure-report.d.ts +15 -0
  18. package/dist/render-failure-report.js +61 -0
  19. package/dist/render-item.d.ts +3 -0
  20. package/dist/render-item.js +2 -0
  21. package/dist/runtime-telemetry.d.ts +28 -18
  22. package/dist/runtime-telemetry.js +68 -29
  23. package/dist/runtime.d.ts +114 -35
  24. package/dist/runtime.js +556 -309
  25. package/dist/stream-seq.d.ts +56 -0
  26. package/dist/stream-seq.js +46 -0
  27. package/dist/system-cards/ProtocolProbeCard.d.ts +16 -0
  28. package/dist/system-cards/ProtocolProbeCard.js +17 -14
  29. package/dist/tool-result-error.d.ts +6 -0
  30. package/dist/tool-result-error.js +32 -0
  31. package/dist/types.d.ts +49 -1
  32. package/dist/view-origin.d.ts +29 -0
  33. package/dist/view-origin.js +33 -0
  34. package/dist/view-proof-signer.d.ts +31 -0
  35. package/dist/view-proof-signer.js +99 -0
  36. package/dist/view-root.d.ts +51 -0
  37. package/dist/view-root.js +56 -0
  38. package/dist/wire-config.d.ts +13 -0
  39. package/dist/wire-config.js +39 -1
  40. package/package.json +8 -7
package/dist/runtime.d.ts CHANGED
@@ -29,14 +29,16 @@ import type { DrainAckPayload, JsonValue, JsonObject, GguiSession } from '@ggui-
29
29
  import type { WebSocketMessage } from '@ggui-ai/protocol/transport/websocket';
30
30
  import { type McpAppAiGguiRenderMeta, type McpAppBootstrapFailedMessage, type McpAppRendererReadyMessage } from '@ggui-ai/protocol/integrations/mcp-apps';
31
31
  import type { GguiSessionSeedInput } from './types.js';
32
- import type { McpAppAiGguiMetaParseFailureReason } from './types.js';
32
+ import type { HeldCredential, McpAppAiGguiMetaParseFailureReason, ViewRoot } from './types.js';
33
+ import type { ViewRootHolder } from './view-root.js';
34
+ import { type StreamSeqTracker } from './stream-seq.js';
33
35
  import { App } from '@modelcontextprotocol/ext-apps';
34
36
  import type { McpUiDisplayMode } from '@modelcontextprotocol/ext-apps';
35
37
  import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
36
38
  import type { GadgetPackageRegistry } from './globals.js';
37
39
  import { StreamBus } from './wire-config.js';
38
40
  import { type ChannelTransportRouter } from './channel-transport.js';
39
- import { ChannelRegistry } from '@ggui-ai/live-channel';
41
+ import type { ChannelHandler } from '@ggui-ai/live-channel';
40
42
  import { type TelemetrySink } from './runtime-telemetry.js';
41
43
  import { type ConnectFn } from './registry-subscribe.js';
42
44
  import { type StatusRefs } from './status-dom.js';
@@ -45,6 +47,7 @@ import type { WireConfig } from '@ggui-ai/wire';
45
47
  import { type ProtocolErrorEmitter } from './protocol-error.js';
46
48
  import { type ObservabilityEmitter } from './observability.js';
47
49
  import { type LifecycleEmitter } from './lifecycle.js';
50
+ import { type ContextSnapshotPoster } from './context-observer.js';
48
51
  /**
49
52
  * Drain-ack listener registry. Module-scoped so the dispatch path can
50
53
  * register a frame-listener without threading it through the renderer.
@@ -337,8 +340,10 @@ export interface BootSequenceOptions {
337
340
  * canonical async toolresult) are skipped. The App handshake still
338
341
  * runs — spec mandates `ui/initialize` regardless of how slice meta
339
342
  * arrives, and `hostContext` is captured from `app.getHostContext()`.
343
+ * The slice's held credential, when it has one, is refreshed exactly
344
+ * as a slice this boot resolved itself (ggui#1496).
340
345
  */
341
- readonly preResolvedMeta?: McpAppAiGguiRenderMeta;
346
+ readonly preResolved?: BootMeta;
342
347
  /**
343
348
  * Autostart-layer pre-resolution of the read-plane door (ggui#537):
344
349
  * a tool result that arrived before `bootSequence` ran carried the
@@ -347,9 +352,17 @@ export interface BootSequenceOptions {
347
352
  * before the App is connected, so the locator is threaded here and
348
353
  * resolved via {@link resolveMetaViaReadDoor} right after the
349
354
  * handshake, ahead of the Tier 2 tool-result wait. Ignored when
350
- * `preResolvedMeta` is set.
355
+ * `preResolved` is set.
351
356
  */
352
357
  readonly preResolvedLocator?: string;
358
+ /**
359
+ * Test seam for the boot refresh's waits (ggui#1496): the retry delay's
360
+ * `sleep` and `random`. Defaults to a `setTimeout` wait and `Math.random`.
361
+ */
362
+ readonly credentialTiming?: {
363
+ readonly sleep?: (ms: number) => Promise<void>;
364
+ readonly random?: () => number;
365
+ };
353
366
  /**
354
367
  * How long to wait for the spec-canonical `ui/notifications/tool-result`
355
368
  * notification (Tier 2 of the resolver chain) before failing with
@@ -358,6 +371,12 @@ export interface BootSequenceOptions {
358
371
  * timeout so the spec doesn't hang.
359
372
  */
360
373
  readonly toolResultTimeoutMs?: number;
374
+ /**
375
+ * Test seam for the view's key root (ggui#1415): the holder the boot and
376
+ * every later tool result offer their slice's root to. Defaults to the
377
+ * document's one holder.
378
+ */
379
+ readonly viewRoots?: ViewRootHolder;
361
380
  }
362
381
  /**
363
382
  * Renderer hooks. The real iframe boot plumbs these via
@@ -449,20 +468,23 @@ export interface RendererHandle {
449
468
  */
450
469
  readonly channelTransport: ChannelTransportRouter;
451
470
  /**
452
- * Channel-client registry holding handlers for every WS frame type
453
- * the iframe routes (`render`, `data`, `props_update`, `drain_ack`,
454
- * `channel_payload`, `channel_error`). The
455
- * registry-bound transport is the sole dispatch surface — frames
456
- * arrive directly through registered handlers, no longer through a
471
+ * The handlers for every frame type the iframe routes (`render`,
472
+ * `data`, `props_update`, `drain_ack`, `channel_payload`,
473
+ * `channel_error`). Frames arrive directly through them, not through a
457
474
  * separate `onMessage` callback.
458
475
  *
459
- * `bootSequence` calls `registry.bind(...)` indirectly through the
460
- * `connectFn` seam after `setup()` returns. Post-bind, registration
461
- * of new handlers is frozen (the registry guards against it); the
462
- * `subscribe-handshake` handlers (`ack`, `error`) are added by
463
- * `connectViaRegistry` and consumed during handshake resolution.
476
+ * `bootSequence`'s ladder set (ggui#1496) registers these same objects
477
+ * on each ladder's registry, one per credential, behind a gate that
478
+ * lets only the ladder holding the data deliver. `connectViaRegistry`
479
+ * adds the per-ladder `subscribe-handshake` handlers (`ack`, `error`).
480
+ */
481
+ readonly channelHandlers: readonly ChannelHandler[];
482
+ /**
483
+ * The view's stream cursor (ggui#1496): the highest stamped `data`
484
+ * envelope `seq` applied. The `data` handler dedupes on it, and a new
485
+ * subscribe resumes the stream from it (`fromSeq`).
464
486
  */
465
- readonly channelRegistry: ChannelRegistry;
487
+ readonly streamSeq: StreamSeqTracker;
466
488
  /**
467
489
  * 3rd-party gadget-package merge promise. `installGlobalRegistry`
468
490
  * seeds `__ggui__.gadgets` synchronously with the STDLIB namespace;
@@ -488,18 +510,35 @@ export interface BootSequenceResult {
488
510
  readonly mountedRender: GguiSession | GguiSessionSeedInput | null;
489
511
  }
490
512
  export declare function bootSequence(opts: BootSequenceOptions): Promise<BootSequenceResult>;
513
+ /**
514
+ * A resolved boot slice (ggui#1496): the meta to mount, and the expired
515
+ * live credential the parse held back from it, when there was one. Every
516
+ * boot resolver returns this, so the held credential survives every path
517
+ * to {@link bootSequence}, which refreshes it once through the host.
518
+ */
519
+ export interface BootMeta {
520
+ readonly meta: McpAppAiGguiRenderMeta;
521
+ readonly held?: HeldCredential;
522
+ /** The slice's view key root, when it carries one (ggui#1415). */
523
+ readonly viewRoot?: ViewRoot;
524
+ }
525
+ /**
526
+ * The bridge rung's `tools/call` over the host relay (ggui#1415): every
527
+ * `ggui_runtime_pull` it sends goes through {@link withViewProof}.
528
+ *
529
+ * @internal — exported for unit tests + production reuse.
530
+ */
531
+ export declare function bridgeCallToolVia(app: App): (name: string, args: Record<string, unknown>) => ReturnType<App['callServerTool']>;
491
532
  /**
492
533
  * Read `globalThis.__GGUI_META__` synchronously, validate against
493
- * the unified slice-meta shape, and return the typed meta (or null
534
+ * the unified slice-meta shape, and return the boot slice (or null
494
535
  * on absence / malformation).
495
536
  *
496
- * Thin wrapper around {@link parseMetaFromGlobal}; preserved for
497
- * back-compat (downstream consumers + tests). Returns `null` instead
498
- * of {@link McpAppAiGguiMetaParseResult} to match the historical
499
- * reader contract — the autostart resolver only needs the "valid
500
- * meta or fall through" signal.
537
+ * Thin wrapper around {@link parseMetaFromGlobal}. Returns `null`
538
+ * instead of {@link McpAppAiGguiMetaParseResult} because the autostart
539
+ * resolver only needs the "a slice to boot, or fall through" signal.
501
540
  */
502
- export declare function readSelfContainedMeta(): McpAppAiGguiRenderMeta | null;
541
+ export declare function readSelfContainedMeta(): BootMeta | null;
503
542
  /**
504
543
  * Project the inline `__GGUI_META__` bootstrap into a
505
544
  * {@link GguiSessionSeedInput} the mount surface can paint immediately,
@@ -565,7 +604,7 @@ export declare function hasStaticContentMeta(meta: McpAppAiGguiRenderMeta): bool
565
604
  * lagged the protocol" bug class: shell + runtime drift becomes
566
605
  * impossible because the shell never inspects the slice-meta shape.
567
606
  */
568
- export declare function extractMetaFromToolResult(params: unknown): McpAppAiGguiRenderMeta | null;
607
+ export declare function extractMetaFromToolResult(params: unknown): BootMeta | null;
569
608
  /**
570
609
  * Drain `window.__GGUI_PENDING_TOOL_RESULTS__` — the buffer the
571
610
  * minimal shell populates while messages arrive between
@@ -584,7 +623,7 @@ export declare function extractMetaFromToolResult(params: unknown): McpAppAiGgui
584
623
  * notifications, in arrival order (shells cap it newest-biased). The
585
624
  * runtime reads it once at autostart.
586
625
  */
587
- export declare function readPendingToolResults(): McpAppAiGguiRenderMeta | null;
626
+ export declare function readPendingToolResults(): BootMeta | null;
588
627
  /**
589
628
  * Sibling of {@link readPendingToolResults} for the read-plane-only
590
629
  * posture (ggui#537): the NEWEST buffered tool result that carries a
@@ -607,7 +646,20 @@ export declare function readPendingToolResultLocator(): string | null;
607
646
  * slice would be. `null` on any miss (host cannot proxy reads, resource
608
647
  * gone, no envelope) — the caller's other tiers still decide.
609
648
  */
610
- export declare function resolveMetaViaReadDoor(app: App, locator: string): Promise<McpAppAiGguiRenderMeta | null>;
649
+ export declare function resolveMetaViaReadDoor(app: App, locator: string): Promise<BootMeta | null>;
650
+ /**
651
+ * Production {@link ContextSnapshotPoster} — the seam the
652
+ * context-observer factories consume. Splits the two destinations:
653
+ *
654
+ * - `postUpdateModelContext` → spec-canonical
655
+ * `app.updateModelContext(...)` (via {@link callAppUpdateModelContext}).
656
+ * - `postContextMirror` → raw `tools/call ggui_runtime_sync_context`
657
+ * via {@link postToParent} (the host-relay mirror path; not yet
658
+ * migrated to `app.callServerTool` — see emitAudit for the same
659
+ * posture).
660
+ */
661
+ /** @internal — exported for unit tests. */
662
+ export declare const productionContextSnapshotPoster: ContextSnapshotPoster;
611
663
  /**
612
664
  * Fire a single canonical action envelope (`tools/call
613
665
  * ggui_runtime_submit_action`) carrying the typed `{kind, payload, …}` shape
@@ -670,17 +722,21 @@ interface ToastLiveRegions {
670
722
  */
671
723
  export declare function ensureToastAnnouncer(doc: Document): ToastLiveRegions | null;
672
724
  /**
673
- * Lightweight toast UX surface for dispatched actions. Renders a
674
- * fixed-position element at the bottom of the iframe so the user
675
- * gets immediate visual feedback that their gesture was registered
676
- * — "→ Sending action: archive" → "✓ Queued — agent will react"
677
- * (or "💬 Sent to chat" on ui/message fallback).
725
+ * The drawn toast for a dispatched action: a fixed-position element at
726
+ * the bottom of the iframe, drawn only when the visitor has to know or do
727
+ * something (the copy is {@link GESTURE_COPY}). Two kinds reach it:
728
+ * - `'error'` — the gesture did not go through: the relay refused it or
729
+ * could not be reached, or the card's own contract refused it. Nothing
730
+ * reached the pipe, so no `ui/message` doorbell fires (SPEC §4.7
731
+ * "Failure-soft client posture");
732
+ * - `'action_required'` — the gesture is queued but no consumer is
733
+ * listening and this host cannot take (or refused) the wake-up, so the
734
+ * visitor must send a message to continue.
735
+ * A tap that went through, or a wake-up the host took, is spoken, never
736
+ * drawn.
678
737
  *
679
- * Without this, the iframe is silent during dispatch. User clicks
680
- * and waits, with no way to distinguish "click was received but the
681
- * agent is busy" from "click was lost". Especially load-bearing in
682
- * the consume-pipe vs ui/message dual-path era — the toast tells
683
- * the user which path the gesture actually took.
738
+ * Without it, a failed gesture would be silent: the visitor could not tell
739
+ * "the agent is busy" from "the tap was lost".
684
740
  *
685
741
  * Direct DOM (not React-managed) because:
686
742
  * - Works during boot before React mounts.
@@ -712,6 +768,29 @@ export declare function ensureToastAnnouncer(doc: Document): ToastLiveRegions |
712
768
  * @internal — exported for unit tests.
713
769
  */
714
770
  export declare function declaredActionLabel(render: GguiSession | GguiSessionSeedInput | null, actionName: string): string | undefined;
771
+ /** @internal — exported for unit tests. */
772
+ export declare function isActionSpentByRefusal(renderId: string, actionName: string): boolean;
773
+ /**
774
+ * ggui#1424 — a post-ack `error` frame from the live channel. The channel
775
+ * door refuses a second gesture on a spent one-shot with its
776
+ * CONTRACT_VIOLATION frame and a violation at `actionSpec.<name>.oneShot`;
777
+ * that one means done, the same as the tool answer. Any other error frame
778
+ * changes nothing here.
779
+ *
780
+ * @internal — exported for unit tests.
781
+ */
782
+ export declare function handlePostAckErrorFrame(renderId: string, payload: {
783
+ readonly code: string;
784
+ readonly message?: string;
785
+ readonly details?: unknown;
786
+ }): void;
787
+ /**
788
+ * The toast for a tap the outbound contract check refused (ggui#1536): the
789
+ * ordinary error toast, named by the action's declared label.
790
+ *
791
+ * @internal — exported for unit tests + production reuse.
792
+ */
793
+ export declare function showRefusedDispatchToast(label: string | undefined): void;
715
794
  /**
716
795
  * Boot-time reset of the relay latch (ggui#670 Phase 3). A latch
717
796
  * standing from a prior mount in a re-booted document is closed through