@ggui-ai/iframe-runtime 0.1.0-rc.1

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 (92) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +33 -0
  3. package/dist/bootstrap.d.ts +72 -0
  4. package/dist/bootstrap.js +500 -0
  5. package/dist/canvas/animator/animator-host.d.ts +63 -0
  6. package/dist/canvas/animator/animator-host.js +105 -0
  7. package/dist/canvas/animator/animator-pill.d.ts +42 -0
  8. package/dist/canvas/animator/animator-pill.js +119 -0
  9. package/dist/canvas/animator/state-machine.d.ts +106 -0
  10. package/dist/canvas/animator/state-machine.js +150 -0
  11. package/dist/canvas/canvas-shell.d.ts +77 -0
  12. package/dist/canvas/canvas-shell.js +117 -0
  13. package/dist/canvas/display-mode-policy.d.ts +104 -0
  14. package/dist/canvas/display-mode-policy.js +98 -0
  15. package/dist/canvas/mount.d.ts +150 -0
  16. package/dist/canvas/mount.js +202 -0
  17. package/dist/canvas/nav-stack.d.ts +133 -0
  18. package/dist/canvas/nav-stack.js +172 -0
  19. package/dist/channel-transport.d.ts +182 -0
  20. package/dist/channel-transport.js +344 -0
  21. package/dist/channels/channel-payload.d.ts +20 -0
  22. package/dist/channels/channel-payload.js +34 -0
  23. package/dist/channels/data.d.ts +48 -0
  24. package/dist/channels/data.js +103 -0
  25. package/dist/channels/drain-ack.d.ts +13 -0
  26. package/dist/channels/drain-ack.js +15 -0
  27. package/dist/channels/feedback.d.ts +14 -0
  28. package/dist/channels/feedback.js +19 -0
  29. package/dist/channels/index.d.ts +7 -0
  30. package/dist/channels/index.js +7 -0
  31. package/dist/channels/props-update.d.ts +53 -0
  32. package/dist/channels/props-update.js +130 -0
  33. package/dist/channels/push.d.ts +47 -0
  34. package/dist/channels/push.js +54 -0
  35. package/dist/channels/system.d.ts +26 -0
  36. package/dist/channels/system.js +44 -0
  37. package/dist/compiled-validators.d.ts +40 -0
  38. package/dist/compiled-validators.js +74 -0
  39. package/dist/context-observer.d.ts +205 -0
  40. package/dist/context-observer.js +312 -0
  41. package/dist/gadget-loader.d.ts +118 -0
  42. package/dist/gadget-loader.js +136 -0
  43. package/dist/globals.d.ts +234 -0
  44. package/dist/globals.js +101 -0
  45. package/dist/host-context-emitter.d.ts +91 -0
  46. package/dist/host-context-emitter.js +213 -0
  47. package/dist/iframe-runtime.js +261 -0
  48. package/dist/index.d.ts +38 -0
  49. package/dist/index.js +4 -0
  50. package/dist/lifecycle.d.ts +66 -0
  51. package/dist/lifecycle.js +49 -0
  52. package/dist/mcp-app-iframe-host.d.ts +69 -0
  53. package/dist/mcp-app-iframe-host.js +214 -0
  54. package/dist/observability.d.ts +225 -0
  55. package/dist/observability.js +28 -0
  56. package/dist/protocol-error.d.ts +199 -0
  57. package/dist/protocol-error.js +116 -0
  58. package/dist/provisional-renderer.d.ts +62 -0
  59. package/dist/provisional-renderer.js +323 -0
  60. package/dist/react-renderer.d.ts +95 -0
  61. package/dist/react-renderer.js +343 -0
  62. package/dist/registry-subscribe.d.ts +102 -0
  63. package/dist/registry-subscribe.js +319 -0
  64. package/dist/runtime.d.ts +531 -0
  65. package/dist/runtime.js +2623 -0
  66. package/dist/server.d.ts +36 -0
  67. package/dist/server.js +72 -0
  68. package/dist/stack-item-renderer.d.ts +103 -0
  69. package/dist/stack-item-renderer.js +270 -0
  70. package/dist/stack.d.ts +90 -0
  71. package/dist/stack.js +118 -0
  72. package/dist/status-dom.d.ts +72 -0
  73. package/dist/status-dom.js +51 -0
  74. package/dist/system-cards/NoCredentialsCard.d.ts +33 -0
  75. package/dist/system-cards/NoCredentialsCard.js +189 -0
  76. package/dist/system-cards/ProtocolProbeCard.d.ts +64 -0
  77. package/dist/system-cards/ProtocolProbeCard.js +463 -0
  78. package/dist/system-cards/QrCode.d.ts +46 -0
  79. package/dist/system-cards/QrCode.js +55 -0
  80. package/dist/system-cards/host-detect.d.ts +31 -0
  81. package/dist/system-cards/host-detect.js +108 -0
  82. package/dist/system-cards/host-intents.d.ts +85 -0
  83. package/dist/system-cards/host-intents.js +170 -0
  84. package/dist/system-cards/index.d.ts +70 -0
  85. package/dist/system-cards/index.js +134 -0
  86. package/dist/types.d.ts +69 -0
  87. package/dist/types.js +3 -0
  88. package/dist/validation.d.ts +83 -0
  89. package/dist/validation.js +106 -0
  90. package/dist/wire-config.d.ts +214 -0
  91. package/dist/wire-config.js +290 -0
  92. package/package.json +81 -0
@@ -0,0 +1,531 @@
1
+ import type { DrainAckPayload } from '@ggui-ai/protocol';
2
+ import type { WebSocketMessage } from '@ggui-ai/protocol/transport/websocket';
3
+ import type { GguiBootstrapMeta } from '@ggui-ai/protocol/integrations/mcp-apps';
4
+ import { StackModel } from './stack.js';
5
+ import type { BootstrapParseFailureReason } from './types.js';
6
+ import { StreamBus } from './wire-config.js';
7
+ import { type ChannelTransportRouter } from './channel-transport.js';
8
+ import { ChannelRegistry } from '@ggui-ai/channel-client';
9
+ import { type ConnectFn } from './registry-subscribe.js';
10
+ import { type StatusRefs } from './status-dom.js';
11
+ import { type RendererValidatorContext } from './validation.js';
12
+ import { StackRenderer } from './stack-item-renderer.js';
13
+ import type { WireConfig } from '@ggui-ai/wire';
14
+ import { type ProtocolErrorEmitter } from './protocol-error.js';
15
+ import { type ObservabilityEmitter } from './observability.js';
16
+ import { type LifecycleEmitter } from './lifecycle.js';
17
+ /**
18
+ * Drain-ack listener registry. Module-scoped so the dispatch path can
19
+ * register a frame-listener without threading it through the triad.
20
+ * Listeners are called in registration order on every inbound
21
+ * `{type:'drain_ack'}` WS frame; a listener returning `true` claims
22
+ * the frame (no further listeners fire). The active listener dismisses
23
+ * the matching per-action toast keyed on `eventId`.
24
+ *
25
+ * Listener faults are absorbed so a buggy subscriber can't take down
26
+ * the WS dispatch loop.
27
+ */
28
+ type DrainAckListener = (payload: DrainAckPayload) => boolean | void;
29
+ export declare function subscribeDrainAck(listener: DrainAckListener): () => void;
30
+ interface JsonRpcResponse {
31
+ readonly jsonrpc?: string;
32
+ readonly id?: number;
33
+ readonly result?: unknown;
34
+ readonly error?: {
35
+ readonly code?: number;
36
+ readonly message?: string;
37
+ };
38
+ }
39
+ /**
40
+ * Closed union of failure reasons surfaceable from the boot path.
41
+ * Maps the parse reasons (`BootstrapParseFailureReason`) plus three
42
+ * boot-orchestration reasons that don't have a canonical wire code.
43
+ * The broader `BootstrapFailureReason` extensibly-closed union in
44
+ * `protocol-error.ts` folds these together with transport-observable
45
+ * codes.
46
+ */
47
+ export type RendererBootFailureReason = BootstrapParseFailureReason | 'UI_INITIALIZE_FAILED' | 'WS_HANDSHAKE_FAILED' | 'UPGRADE_REQUIRED';
48
+ export interface RendererBootFailedMessage {
49
+ readonly type: 'ggui:bootstrap-failed';
50
+ readonly reason: RendererBootFailureReason;
51
+ readonly message: string;
52
+ }
53
+ export interface BootSequenceOptions {
54
+ readonly doc: Document;
55
+ /**
56
+ * Issue `ui/initialize` against the parent host and return its
57
+ * response. The default uses `makeJsonRpcCaller()` against
58
+ * `window.parent`; tests inject a mock that returns a canned
59
+ * `JsonRpcResponse`.
60
+ */
61
+ readonly callUiInitialize: () => Promise<JsonRpcResponse>;
62
+ /**
63
+ * Stand-in for the package's own `connectViaRegistry()`. Tests inject
64
+ * a mock so the boot smoke spec doesn't need a mock-WebSocket layer
65
+ * — the WS lifecycle is already covered by `registry-subscribe.test.ts`
66
+ * and `@ggui-ai/channel-client`'s transport tests.
67
+ *
68
+ * Default: `connectViaRegistry` from `./registry-subscribe.js`.
69
+ */
70
+ readonly connectFn?: ConnectFn;
71
+ /**
72
+ * Notify-parent hook. Default posts to `window.parent`; tests
73
+ * inject a recorder.
74
+ */
75
+ readonly notifyParent: (msg: RendererBootFailedMessage | {
76
+ type: 'ggui:renderer-ready';
77
+ version: string;
78
+ }) => void;
79
+ /**
80
+ * Optional typed {@link import('./protocol-error.js').ProtocolError}
81
+ * sink. Every bootstrap-failure path fires this in parallel with
82
+ * `notifyParent` so the `<McpAppIframe>` host wrapper's `onError`
83
+ * prop receives typed errors. Absent = typed emission is skipped
84
+ * (the narrow `RendererBootFailedMessage` path is unchanged).
85
+ */
86
+ readonly onProtocolError?: ProtocolErrorEmitter;
87
+ /**
88
+ * Optional {@link ObservabilityEmitter} sink. Fires for every
89
+ * renderer-observed event flow the host inspector cares about:
90
+ *
91
+ * - `contract-error-emitted` — whenever a `_ggui:contract-error`
92
+ * envelope arrives on the live channel.
93
+ * - `schema-version-mismatch` — forwarded from subscribe's own
94
+ * emission on UPGRADE_REQUIRED.
95
+ * - `subscribe-failed` — forwarded from subscribe's emission on
96
+ * transient reconnect transitions.
97
+ *
98
+ * `wired-tool-invoked` is emitted from the wire config's outbound
99
+ * dispatch path (see `buildRootWireConfig`'s `onObserve`), NOT from
100
+ * here — the `data:submit` envelope hasn't passed through this
101
+ * handler when it fires.
102
+ *
103
+ * Absent = observability emission skipped entirely (matches the
104
+ * ProtocolError posture; the `<McpAppIframe>` host wrapper decides
105
+ * whether to bind this via its `onObserve` prop).
106
+ */
107
+ readonly onObserve?: ObservabilityEmitter;
108
+ /**
109
+ * Optional {@link LifecycleEmitter} sink. Fires on every renderer
110
+ * mount-state transition (`mounting` → `code-ready` | `error`,
111
+ * later `disconnected`). Production binds the postMessage-to-parent
112
+ * default {@link postLifecycleToParent}; tests inject a recorder.
113
+ *
114
+ * Absent = lifecycle emission skipped entirely. The legacy
115
+ * postMessage envelopes (`ggui:renderer-ready`,
116
+ * `ggui:bootstrap-failed`) STAY emitted regardless — lifecycle is
117
+ * an additive surface, not a replacement.
118
+ *
119
+ * Sequence the renderer guarantees when this is bound:
120
+ * 1. `mounting` — before any IO, paired with `ggui:renderer-ready`.
121
+ * 2a. `code-ready` — after first ack folds the initial stack.
122
+ * 2b. `error` — paired with every `ggui:bootstrap-failed` emission;
123
+ * `error.code` mirrors the legacy envelope's `reason`.
124
+ *
125
+ * @public
126
+ */
127
+ readonly onLifecycle?: LifecycleEmitter;
128
+ /**
129
+ * Optional triad-wiring hook. When present, the boot sequence:
130
+ * (1) calls `triadWiring.setup()` after bootstrap parse;
131
+ * (2) replaces the placeholder `renderStackInto` path with the
132
+ * real `StackRenderer` on ack + subsequent pushes; (3) routes
133
+ * inbound `data` / `props_update` / `feedback` frames through the
134
+ * supplied wire config + StreamBus.
135
+ *
136
+ * When absent (default), the boot sequence runs the placeholder
137
+ * path — used by `boot.test.ts` to exercise the orchestration
138
+ * without pulling React + design + wire into the spec's import
139
+ * graph.
140
+ */
141
+ readonly triadWiring?: TriadWiringHooks;
142
+ /**
143
+ * Optional canvas-mount hook. When the parsed bootstrap carries
144
+ * `canvasMode: true` AND this hook is supplied, bootSequence mounts
145
+ * the React-tree {@link CanvasShell} into `refs.stack` via the hook
146
+ * and substitutes the hook's `contentEl` as the `renderInto`
147
+ * passed to `triadWiring.setup()` — so the existing per-stack-item
148
+ * renderer writes into the canvas's content slot rather than the
149
+ * raw status DOM. Subsequent ack/push frames sync into the returned
150
+ * {@link CanvasMountHandle.navStack}.
151
+ *
152
+ * Absent OR `bootstrap.canvasMode !== true` ⇒ the inline path
153
+ * (per-push iframe).
154
+ */
155
+ readonly canvasMount?: CanvasMountHook;
156
+ }
157
+ /**
158
+ * Hook signature for the canvas-mode mount. Production wires this
159
+ * through `bootProduction` (where React + canvas modules are already
160
+ * dynamic-imported); tests pass a stub that fakes the React tree.
161
+ */
162
+ export type CanvasMountHook = (params: {
163
+ readonly mountTarget: HTMLElement;
164
+ readonly callbacks: import('./canvas/mount.js').CanvasMountCallbacks;
165
+ }) => import('./canvas/mount.js').CanvasMountHandle;
166
+ /**
167
+ * Renderer-triad wiring hooks. The real iframe boot plumbs these via
168
+ * `autoBootSequence` below; tests may pass their own fakes when
169
+ * exercising the full flow.
170
+ */
171
+ export interface TriadWiringHooks {
172
+ /**
173
+ * Called after bootstrap parse succeeds. Return value threads the
174
+ * root `WireConfig` + `StackRenderer` back into the runtime so the
175
+ * server-message handler can route frames through them.
176
+ *
177
+ * `renderInto` — a DOM element the stack renderer owns. The
178
+ * placeholder `<ul data-ggui-stack>` is NOT reused; the triad
179
+ * renderer mounts React roots per stack item into dedicated
180
+ * containers (see `containerFor` below).
181
+ *
182
+ * `onObserve` — optional observability emitter threaded down to
183
+ * the root wire config so `wired-tool-invoked` events fire on
184
+ * every successful outbound wired-action dispatch. Absent = the
185
+ * wire config runs with a no-op observer (the other emission sites
186
+ * — subscribe.ts, handleObservableMessage — still fire via their
187
+ * own emitters).
188
+ */
189
+ setup(params: {
190
+ readonly bootstrap: GguiBootstrapMeta;
191
+ readonly stackModel: StackModel;
192
+ readonly renderInto: HTMLElement;
193
+ readonly statusRefs: StatusRefs;
194
+ readonly onObserve?: ObservabilityEmitter;
195
+ /**
196
+ * Canvas-mode lifecycle sink. When wired,
197
+ * the triad's `data` channel handler forwards every envelope on
198
+ * the reserved `_ggui:lifecycle` channel to this callback so the
199
+ * canvas animator state machine advances. Absent ⇒ envelopes
200
+ * still emit on `streamBus` for declarative consumers but no
201
+ * animator-side dispatch fires (non-canvas iframes).
202
+ */
203
+ readonly onCanvasLifecycle?: (payload: import('@ggui-ai/protocol').CanvasLifecyclePayload) => void;
204
+ }): TriadHandle;
205
+ /**
206
+ * Bind the real WS manager into the triad AFTER `connectFn` resolves.
207
+ * The `setup()` step supplies a buffering shim — this hook flushes
208
+ * the buffer + swaps in the real send surface. Optional: a triad
209
+ * that doesn't emit outbound frames can skip.
210
+ */
211
+ attachManager?(handle: TriadHandle, realManager: {
212
+ send: (msg: WebSocketMessage) => void;
213
+ }): void;
214
+ /** Optional cleanup — called on boot failure paths. */
215
+ teardown?(handle: TriadHandle): void;
216
+ }
217
+ export interface TriadHandle {
218
+ readonly rootWireConfig: WireConfig;
219
+ readonly streamBus: StreamBus;
220
+ readonly stackRenderer: StackRenderer;
221
+ readonly validatorCtx: RendererValidatorContext;
222
+ /**
223
+ * Send surface for outbound frames. Wired by `setup()` to the WS
224
+ * manager obtained AFTER subscribe; initial setup supplies a
225
+ * buffering shim that flushes on the first `send()`-ready moment.
226
+ */
227
+ readonly manager: {
228
+ send: (msg: WebSocketMessage) => void;
229
+ };
230
+ /**
231
+ * Per-channel transport router. When the bootstrap carries
232
+ * `streamWebSocketLocalTools` and the active
233
+ * stack item declares `streamSpec[ch].source.tool`, the router
234
+ * decides per-channel between WS subscribe + iframe-polling
235
+ * fallback. Updated on every push via the stackRenderer's
236
+ * `applyStack` hook.
237
+ *
238
+ * Always present — the router gracefully no-ops when no channel
239
+ * declares `source.tool` (legacy data-frame path is unaffected).
240
+ */
241
+ readonly channelTransport: ChannelTransportRouter;
242
+ /**
243
+ * Channel-client registry holding handlers for every WS frame type
244
+ * the iframe routes (`push`, `data`, `props_update`, `drain_ack`,
245
+ * `channel_payload`, `channel_error`, `system`, `feedback`). The
246
+ * registry-bound transport is the sole dispatch surface — frames
247
+ * arrive directly through registered handlers, no longer through a
248
+ * separate `onMessage` callback.
249
+ *
250
+ * `bootSequence` calls `registry.bind(...)` indirectly through the
251
+ * `connectFn` seam after `setup()` returns. Post-bind, registration
252
+ * of new handlers is frozen (the registry guards against it); the
253
+ * `subscribe-handshake` handlers (`ack`, `error`) are added by
254
+ * `connectViaRegistry` and consumed during handshake resolution.
255
+ */
256
+ readonly channelRegistry: ChannelRegistry;
257
+ }
258
+ export interface BootSequenceResult {
259
+ readonly ok: boolean;
260
+ readonly stack: StackModel;
261
+ }
262
+ export declare function bootSequence(opts: BootSequenceOptions): Promise<BootSequenceResult>;
263
+ /**
264
+ * Self-contained bootstrap shape — alias for the protocol's unified
265
+ * {@link GguiBootstrapMeta}. Earlier versions of this module had
266
+ * separate `SelfContainedComponentBootstrap` /
267
+ * `SelfContainedSystemBootstrap` narrowings; the protocol's
268
+ * `GguiBootstrapMeta` now carries `codeUrl` / `kind` / `themeId`
269
+ * / `themeMode` / `propsJson` directly (alongside the existing
270
+ * live-mode `wsUrl` / `token` / `expiresAt` fields), so a single
271
+ * shared validator ({@link validateBootstrapMeta} in `bootstrap.ts`)
272
+ * can produce the full envelope from any of three delivery channels.
273
+ *
274
+ * The export is kept so downstream consumers (`@ggui-ai/iframe-runtime`
275
+ * barrel, dependent test suites) don't break.
276
+ *
277
+ * @public
278
+ */
279
+ export type SelfContainedBootstrap = GguiBootstrapMeta;
280
+ /**
281
+ * Read `globalThis.__GGUI_BOOTSTRAP__` synchronously, validate against
282
+ * the unified bootstrap shape, and return the typed bootstrap (or null
283
+ * on absence / malformation).
284
+ *
285
+ * Thin wrapper around {@link parseBootstrapFromGlobal}; preserved for
286
+ * back-compat (downstream consumers + tests). Returns `null` instead
287
+ * of `BootstrapParseResult` to match the historical reader contract
288
+ * — the autostart resolver only needs the "valid bootstrap or fall
289
+ * through" signal.
290
+ */
291
+ export declare function readSelfContainedBootstrap(): SelfContainedBootstrap | null;
292
+ /**
293
+ * Extract a {@link SelfContainedBootstrap} from a
294
+ * `ui/notifications/tool-result` JSON-RPC params payload — the
295
+ * postMessage delivery shape MCP Apps hosts (Claude Desktop, claude.ai
296
+ * web) use to push the active tool's `_meta` to the iframe.
297
+ *
298
+ * Thin wrapper around {@link parseBootstrapFromToolResult}; same
299
+ * back-compat motivation as {@link readSelfContainedBootstrap}.
300
+ *
301
+ * Owning this extraction inside the runtime (instead of in the shell
302
+ * HTML) is the architectural cure for the "shell-side validator
303
+ * lagged the protocol" bug class: shell + runtime drift becomes
304
+ * impossible because the shell never inspects the bootstrap shape.
305
+ */
306
+ export declare function extractBootstrapFromToolResult(params: unknown): SelfContainedBootstrap | null;
307
+ /**
308
+ * Render a wired-action's `data` payload as a short inline string
309
+ * for embedding in a `ui/message` consent prompt. Goal: human-
310
+ * readable, not a JSON dump. Falls back to truncated JSON for
311
+ * nested values so the prompt doesn't drop information silently.
312
+ *
313
+ * Exported for unit-testing — an earlier implementation returned `''`
314
+ * for primitive payloads (strings/numbers/booleans), which silently
315
+ * vaporised the chip's actual text from the consent prompt and made
316
+ * the LLM think every dispatch was a contentless "Please proceed
317
+ * with **<intent>**" request.
318
+ */
319
+ export declare function formatWiredActionDataInline(data: unknown): string;
320
+ /**
321
+ * Fire a single canonical action envelope (`tools/call
322
+ * ggui_runtime_submit_action`) carrying the typed `{kind, payload, …}` shape
323
+ * defined in `@ggui-ai/protocol/integrations/mcp-apps`. Every
324
+ * user-driven gesture (Pattern α/β dispatch, native-idiom anchor click,
325
+ * native-idiom fullscreen request) calls this alongside its primary
326
+ * host effect, so operators get **uniform server-side observability**
327
+ * across every gesture kind.
328
+ *
329
+ * Fail-soft: a rejected audit fire MUST NOT block the primary host
330
+ * effect. Detached-parent / host-rejected audit envelopes are
331
+ * dropped silently here; the primary effect proceeds via its own
332
+ * `postToParent` call from the caller.
333
+ */
334
+ /** @internal — exported for unit tests. */
335
+ export declare function emitAudit(args: {
336
+ readonly toolName: string;
337
+ readonly kind: 'dispatch' | 'openLink' | 'requestDisplayMode';
338
+ readonly payload: Record<string, unknown>;
339
+ readonly sessionId: string;
340
+ readonly stackItemId?: string;
341
+ readonly appId: string;
342
+ readonly actionId: string;
343
+ readonly firedAt: string;
344
+ }): void;
345
+ /** @internal — exported for unit tests. */
346
+ export declare function dispatchWiredAction(args: {
347
+ readonly toolName: string;
348
+ readonly intent: string;
349
+ readonly data: unknown;
350
+ readonly sessionId: string;
351
+ readonly stackItemId?: string;
352
+ readonly appId: string;
353
+ /**
354
+ * Optional `actionSpec[intent].nextStep` hint forwarded by the
355
+ * caller. Surfaced on the inline-userAction envelope when the pipe
356
+ * isn't available, so the agent has a strong steer toward the right
357
+ * tool.
358
+ */
359
+ readonly nextStep?: string;
360
+ }): void;
361
+ /**
362
+ * Fire a wired action via Pattern α — direct `tools/call` against a
363
+ * same-server, app-visible target tool. The iframe is allowed to fire
364
+ * the tool directly from this server connection per MCP-Apps spec
365
+ * §2026-01-26 visibility rules. Skips the submit_action pipe entirely:
366
+ * the tool fires without the agent's involvement (the whole point of
367
+ * Pattern α), chat stays clean.
368
+ *
369
+ * PIPE-2 design note: Pattern α deliberately does NOT fire
370
+ * `submit_action` — a pipe append would queue the gesture for the
371
+ * agent's `ggui_consume` long-poll, causing double-processing
372
+ * (host's tool relay AND agent's reaction). SessionInspector loses
373
+ * Pattern α observability for now; re-add via a dedicated audit-only
374
+ * gesture kind if operators need it.
375
+ *
376
+ * @internal — exported for unit tests.
377
+ */
378
+ export declare function fireDirectToolCall(args: {
379
+ readonly targetToolName: string;
380
+ readonly data: unknown;
381
+ }): void;
382
+ /**
383
+ * Pure routing helper for `WireConfig.dispatch` — chooses Pattern α
384
+ * (direct `tools/call`) when the action's wired tool is in
385
+ * `appCallableTools`, otherwise falls back to Pattern β (the 3-message
386
+ * bridge).
387
+ *
388
+ * The routing logic was previously inlined as a closure body inside
389
+ * `bootSelfContained`'s `wireConfig.dispatch`, with a verbatim
390
+ * re-creation in `__tests__/dispatch-routing.test.ts`. Tests could
391
+ * pass against stale code if either drifted. Extraction means
392
+ * production + tests now exercise the same code path.
393
+ *
394
+ * Pattern α (same-server, app-visible target tool): chat stays clean,
395
+ * no LLM consent loop. Pattern β (anything else: cross-server tool, no
396
+ * wired tool, or tool not in `appCallableTools`): the canonical
397
+ * 3-message workaround the LLM brokers when the tool isn't directly
398
+ * callable from the iframe.
399
+ *
400
+ * @internal — exported for unit tests + production reuse.
401
+ */
402
+ export declare function routeDispatch(args: {
403
+ readonly actionName: string;
404
+ readonly data: unknown;
405
+ readonly bootstrap: {
406
+ readonly sessionId: string;
407
+ readonly stackItemId?: string;
408
+ readonly appId: string;
409
+ readonly appCallableTools?: readonly string[];
410
+ readonly actionNextSteps?: Readonly<Record<string, string>>;
411
+ };
412
+ readonly dispatchToolName: string;
413
+ }): void;
414
+ /**
415
+ * `ui/open-link` direct dispatch helper — used by the anchor-click
416
+ * interceptor. Fires a parallel `kind:'openLink'` audit so operators
417
+ * retain uniform observability across every gesture kind.
418
+ */
419
+ /** @internal — exported for unit tests. */
420
+ export declare function openLinkInParent(args: {
421
+ readonly toolName: string;
422
+ readonly url: string;
423
+ readonly sessionId: string;
424
+ readonly stackItemId?: string;
425
+ readonly appId: string;
426
+ }): void;
427
+ /**
428
+ * `ui/request-display-mode` direct dispatch helper — used by the
429
+ * `Element.requestFullscreen` / `Document.exitFullscreen`
430
+ * interceptors. Fires a parallel `kind:'requestDisplayMode'` audit so
431
+ * operators retain uniform observability across every gesture kind.
432
+ */
433
+ /** @internal — exported for unit tests. */
434
+ export declare function requestDisplayModeInParent(args: {
435
+ readonly toolName: string;
436
+ readonly mode: 'fullscreen' | 'pip' | 'inline';
437
+ readonly sessionId: string;
438
+ readonly stackItemId?: string;
439
+ readonly appId: string;
440
+ }): void;
441
+ /**
442
+ * Native-idiom interceptor for anchor clicks.
443
+ *
444
+ * Install a capture-phase `click` listener on `document` that traps
445
+ * clicks targeting an `<a href>` whose href is an external (cross-
446
+ * origin OR `target="_blank"`) http(s) URL. Intercepted clicks are
447
+ * routed through {@link openLinkInParent} — full audit envelope +
448
+ * `ui/open-link` postMessage. Generated components use plain
449
+ * `<a href="https://example.com" target="_blank">`; the runtime
450
+ * intercepts the click and routes it to the host.
451
+ *
452
+ * Decision rules (DOM event → routing):
453
+ * - No anchor in ancestor chain → skip (nothing to do).
454
+ * - `event.defaultPrevented` → skip (component handler already
455
+ * consumed the click; respect its choice).
456
+ * - href starts with `#` → skip (same-document fragment; preserve
457
+ * bookmark / scroll-into-view behavior).
458
+ * - href scheme is not `http(s):` → skip (`mailto:`, `tel:`,
459
+ * `javascript:`, `data:` are out of the spec's domain).
460
+ * - href origin === `window.location.origin` AND `target !== '_blank'`
461
+ * → skip (in-frame navigation; preserve SPA links).
462
+ * - Otherwise → preventDefault + fire {@link openLinkInParent}.
463
+ *
464
+ * Capture phase is load-bearing: component-defined `onClick` handlers
465
+ * may call `preventDefault()`, which would mask the native intercept
466
+ * if the listener were on the bubble phase. By running first, we
467
+ * either intercept (then component handlers see `defaultPrevented`)
468
+ * or honor an upstream `defaultPrevented` from a higher-priority
469
+ * handler.
470
+ *
471
+ * Idempotent: re-mounts (via `installPostMountListener`) call this
472
+ * helper again; the {@link anchorClickInterceptInstalled} guard makes
473
+ * subsequent calls a no-op.
474
+ */
475
+ /** @internal — exported for unit tests. */
476
+ export declare function installAnchorClickInterceptor(args: {
477
+ readonly dispatchToolName: string;
478
+ readonly sessionId: string;
479
+ readonly stackItemId?: string;
480
+ readonly appId: string;
481
+ }): void;
482
+ /**
483
+ * Native-idiom interceptor for the Fullscreen API.
484
+ *
485
+ * Override `Element.prototype.requestFullscreen` and
486
+ * `Document.prototype.exitFullscreen` so generated components calling
487
+ * either go through {@link requestDisplayModeInParent} (fires the
488
+ * `ui/request-display-mode` postMessage + paired audit envelope).
489
+ *
490
+ * The native fullscreen API would not work from inside the iframe
491
+ * regardless — sandboxed iframes need an explicit
492
+ * `allow="fullscreen"` permission, and even when granted the host
493
+ * (claude.ai, ggui demo shell) typically owns the chrome. Routing
494
+ * through the parent host lets it decide the actual presentation
495
+ * (true fullscreen, modal, expanded panel).
496
+ *
497
+ * Both overrides return `Promise.resolve()` so callers using the
498
+ * standard `.then()` / `await` form don't break. The native call is
499
+ * NOT delegated — there's no useful behavior to preserve and a real
500
+ * native fullscreen attempt would race with the host's handling.
501
+ *
502
+ * Mode mapping:
503
+ * - `requestFullscreen()` → `mode: 'fullscreen'`
504
+ * - `exitFullscreen()` → `mode: 'inline'` (symmetric inverse
505
+ * in the existing `requestDisplayModeInParent` mode union).
506
+ *
507
+ * Note: `'pip'` is YAGNI for v1. There's no clean native API for
508
+ * arbitrary-content Picture-in-Picture (Document PiP exists but is
509
+ * Chromium-only and requires an explicit container handoff); we
510
+ * surface the postMessage `mode: 'pip'` only when a future native
511
+ * idiom emerges.
512
+ *
513
+ * Idempotent: the {@link fullscreenInterceptInstalled} guard prevents
514
+ * re-mounts from chaining wrappers (which would also leak the prior
515
+ * mount's `sessionId`/`appId` if they ever differed).
516
+ */
517
+ /** @internal — exported for unit tests. */
518
+ export declare function installFullscreenInterceptors(args: {
519
+ readonly dispatchToolName: string;
520
+ readonly sessionId: string;
521
+ readonly stackItemId?: string;
522
+ readonly appId: string;
523
+ }): void;
524
+ /**
525
+ * @internal — exposed for tests to reset module-level interceptor
526
+ * guards between specs. Production code never calls this; resetting
527
+ * during a real iframe lifecycle would cause stacked click listeners
528
+ * (the prior listener is NOT removed by the reset, only forgotten).
529
+ */
530
+ export declare function __resetInterceptorsForTest(): void;
531
+ export {};