@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.
- package/LICENSE +201 -0
- package/README.md +33 -0
- package/dist/bootstrap.d.ts +72 -0
- package/dist/bootstrap.js +500 -0
- package/dist/canvas/animator/animator-host.d.ts +63 -0
- package/dist/canvas/animator/animator-host.js +105 -0
- package/dist/canvas/animator/animator-pill.d.ts +42 -0
- package/dist/canvas/animator/animator-pill.js +119 -0
- package/dist/canvas/animator/state-machine.d.ts +106 -0
- package/dist/canvas/animator/state-machine.js +150 -0
- package/dist/canvas/canvas-shell.d.ts +77 -0
- package/dist/canvas/canvas-shell.js +117 -0
- package/dist/canvas/display-mode-policy.d.ts +104 -0
- package/dist/canvas/display-mode-policy.js +98 -0
- package/dist/canvas/mount.d.ts +150 -0
- package/dist/canvas/mount.js +202 -0
- package/dist/canvas/nav-stack.d.ts +133 -0
- package/dist/canvas/nav-stack.js +172 -0
- package/dist/channel-transport.d.ts +182 -0
- package/dist/channel-transport.js +344 -0
- package/dist/channels/channel-payload.d.ts +20 -0
- package/dist/channels/channel-payload.js +34 -0
- package/dist/channels/data.d.ts +48 -0
- package/dist/channels/data.js +103 -0
- package/dist/channels/drain-ack.d.ts +13 -0
- package/dist/channels/drain-ack.js +15 -0
- package/dist/channels/feedback.d.ts +14 -0
- package/dist/channels/feedback.js +19 -0
- package/dist/channels/index.d.ts +7 -0
- package/dist/channels/index.js +7 -0
- package/dist/channels/props-update.d.ts +53 -0
- package/dist/channels/props-update.js +130 -0
- package/dist/channels/push.d.ts +47 -0
- package/dist/channels/push.js +54 -0
- package/dist/channels/system.d.ts +26 -0
- package/dist/channels/system.js +44 -0
- package/dist/compiled-validators.d.ts +40 -0
- package/dist/compiled-validators.js +74 -0
- package/dist/context-observer.d.ts +205 -0
- package/dist/context-observer.js +312 -0
- package/dist/gadget-loader.d.ts +118 -0
- package/dist/gadget-loader.js +136 -0
- package/dist/globals.d.ts +234 -0
- package/dist/globals.js +101 -0
- package/dist/host-context-emitter.d.ts +91 -0
- package/dist/host-context-emitter.js +213 -0
- package/dist/iframe-runtime.js +261 -0
- package/dist/index.d.ts +38 -0
- package/dist/index.js +4 -0
- package/dist/lifecycle.d.ts +66 -0
- package/dist/lifecycle.js +49 -0
- package/dist/mcp-app-iframe-host.d.ts +69 -0
- package/dist/mcp-app-iframe-host.js +214 -0
- package/dist/observability.d.ts +225 -0
- package/dist/observability.js +28 -0
- package/dist/protocol-error.d.ts +199 -0
- package/dist/protocol-error.js +116 -0
- package/dist/provisional-renderer.d.ts +62 -0
- package/dist/provisional-renderer.js +323 -0
- package/dist/react-renderer.d.ts +95 -0
- package/dist/react-renderer.js +343 -0
- package/dist/registry-subscribe.d.ts +102 -0
- package/dist/registry-subscribe.js +319 -0
- package/dist/runtime.d.ts +531 -0
- package/dist/runtime.js +2623 -0
- package/dist/server.d.ts +36 -0
- package/dist/server.js +72 -0
- package/dist/stack-item-renderer.d.ts +103 -0
- package/dist/stack-item-renderer.js +270 -0
- package/dist/stack.d.ts +90 -0
- package/dist/stack.js +118 -0
- package/dist/status-dom.d.ts +72 -0
- package/dist/status-dom.js +51 -0
- package/dist/system-cards/NoCredentialsCard.d.ts +33 -0
- package/dist/system-cards/NoCredentialsCard.js +189 -0
- package/dist/system-cards/ProtocolProbeCard.d.ts +64 -0
- package/dist/system-cards/ProtocolProbeCard.js +463 -0
- package/dist/system-cards/QrCode.d.ts +46 -0
- package/dist/system-cards/QrCode.js +55 -0
- package/dist/system-cards/host-detect.d.ts +31 -0
- package/dist/system-cards/host-detect.js +108 -0
- package/dist/system-cards/host-intents.d.ts +85 -0
- package/dist/system-cards/host-intents.js +170 -0
- package/dist/system-cards/index.d.ts +70 -0
- package/dist/system-cards/index.js +134 -0
- package/dist/types.d.ts +69 -0
- package/dist/types.js +3 -0
- package/dist/validation.d.ts +83 -0
- package/dist/validation.js +106 -0
- package/dist/wire-config.d.ts +214 -0
- package/dist/wire-config.js +290 -0
- 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 {};
|