@ggui-ai/protocol 0.1.0-rc.3 → 0.2.0-alpha.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 (90) hide show
  1. package/README.md +3 -3
  2. package/dist/bridge/invoke-agent.d.ts +8 -8
  3. package/dist/bridge/invoke-agent.d.ts.map +1 -1
  4. package/dist/bridge/invoke-agent.js +12 -12
  5. package/dist/envelopes/builders.d.ts +2 -4
  6. package/dist/envelopes/builders.d.ts.map +1 -1
  7. package/dist/envelopes/builders.js +2 -6
  8. package/dist/index.d.ts +2 -1
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +4 -3
  11. package/dist/integrations/mcp-apps.d.ts +354 -711
  12. package/dist/integrations/mcp-apps.d.ts.map +1 -1
  13. package/dist/integrations/mcp-apps.js +266 -208
  14. package/dist/recommended-prompts.d.ts +1 -1
  15. package/dist/recommended-prompts.js +1 -1
  16. package/dist/schemas/data-contract.d.ts +4 -3
  17. package/dist/schemas/data-contract.d.ts.map +1 -1
  18. package/dist/schemas/data-contract.js +4 -3
  19. package/dist/schemas/invoke.d.ts +2 -2
  20. package/dist/schemas/invoke.js +2 -2
  21. package/dist/schemas/mcp.d.ts +60 -79
  22. package/dist/schemas/mcp.d.ts.map +1 -1
  23. package/dist/schemas/mcp.js +93 -116
  24. package/dist/schemas/sync-check.js +1 -1
  25. package/dist/stream/stream-parser.d.ts +4 -4
  26. package/dist/stream/stream-parser.d.ts.map +1 -1
  27. package/dist/stream/stream-parser.js +9 -9
  28. package/dist/transport/websocket.d.ts +14 -49
  29. package/dist/transport/websocket.d.ts.map +1 -1
  30. package/dist/types/app-config.d.ts +17 -32
  31. package/dist/types/app-config.d.ts.map +1 -1
  32. package/dist/types/canvas-lifecycle.d.ts +13 -13
  33. package/dist/types/canvas-lifecycle.d.ts.map +1 -1
  34. package/dist/types/canvas-lifecycle.js +3 -3
  35. package/dist/types/contract-inference.d.ts +4 -7
  36. package/dist/types/contract-inference.d.ts.map +1 -1
  37. package/dist/types/data-bindings.d.ts +1 -1
  38. package/dist/types/data-bindings.d.ts.map +1 -1
  39. package/dist/types/data-contract.d.ts +10 -10
  40. package/dist/types/data-contract.d.ts.map +1 -1
  41. package/dist/types/events.d.ts +14 -56
  42. package/dist/types/events.d.ts.map +1 -1
  43. package/dist/types/events.js +1 -8
  44. package/dist/types/feedback.d.ts +2 -4
  45. package/dist/types/feedback.d.ts.map +1 -1
  46. package/dist/types/handshake-suggestion.d.ts +1 -1
  47. package/dist/types/host-context.d.ts +5 -15
  48. package/dist/types/host-context.d.ts.map +1 -1
  49. package/dist/types/host-context.js +1 -1
  50. package/dist/types/live-channel.d.ts +128 -191
  51. package/dist/types/live-channel.d.ts.map +1 -1
  52. package/dist/types/llm-route.d.ts +233 -0
  53. package/dist/types/llm-route.d.ts.map +1 -0
  54. package/dist/types/llm-route.js +433 -0
  55. package/dist/types/llm.d.ts +34 -16
  56. package/dist/types/llm.d.ts.map +1 -1
  57. package/dist/types/llm.js +34 -38
  58. package/dist/types/mcp.d.ts +47 -137
  59. package/dist/types/mcp.d.ts.map +1 -1
  60. package/dist/types/mcp.js +2 -2
  61. package/dist/types/openrouter-models.d.ts +3 -2
  62. package/dist/types/openrouter-models.d.ts.map +1 -1
  63. package/dist/types/render-event.d.ts +119 -0
  64. package/dist/types/render-event.d.ts.map +1 -0
  65. package/dist/types/render-event.js +38 -0
  66. package/dist/types/session.d.ts +216 -342
  67. package/dist/types/session.d.ts.map +1 -1
  68. package/dist/types/ui-generator.d.ts +8 -9
  69. package/dist/types/ui-generator.d.ts.map +1 -1
  70. package/dist/validation/contract-validator.d.ts +60 -4
  71. package/dist/validation/contract-validator.d.ts.map +1 -1
  72. package/dist/validation/contract-validator.js +101 -5
  73. package/dist/validation/reserved-channels.d.ts +1 -1
  74. package/dist/validation/reserved-channels.js +5 -5
  75. package/dist/validation/sanitize-error.js +1 -1
  76. package/dist/validation/schema-compat-invariants.d.ts +2 -2
  77. package/dist/validation/schema-compat-invariants.js +2 -2
  78. package/dist/validation/schema-meta-validation.d.ts +1 -1
  79. package/dist/validation/schema-meta-validation.js +2 -2
  80. package/dist/validation/schema-subset.d.ts +1 -1
  81. package/dist/version.d.ts +253 -46
  82. package/dist/version.d.ts.map +1 -1
  83. package/dist/version.js +253 -46
  84. package/package.json +3 -2
  85. package/dist/navigation/index.d.ts +0 -3
  86. package/dist/navigation/index.d.ts.map +0 -1
  87. package/dist/navigation/index.js +0 -1
  88. package/dist/navigation/stack-navigation.d.ts +0 -55
  89. package/dist/navigation/stack-navigation.d.ts.map +0 -1
  90. package/dist/navigation/stack-navigation.js +0 -80
@@ -9,8 +9,9 @@
9
9
  * ```ts
10
10
  * import {
11
11
  * MCP_APPS_UI_CAPABILITY,
12
- * GGUI_SESSION_RESOURCE_URI,
13
- * type GguiBootstrapMeta,
12
+ * GGUI_RENDER_RESOURCE_URI,
13
+ * parseMcpAppAiGguiRenderMeta,
14
+ * type McpAppAiGguiRenderMeta,
14
15
  * } from '@ggui-ai/protocol/integrations/mcp-apps';
15
16
  * ```
16
17
  *
@@ -20,7 +21,7 @@
20
21
  * drift is bounded to callers that explicitly import from here.
21
22
  *
22
23
  * Core still carries two fields that make the bootstrap flow work —
23
- * `SubscribePayload.bootstrap?: string` and `AckPayload.sessionToken?:
24
+ * `SubscribePayload.bootstrap?: string` and `AckPayload.renderToken?:
24
25
  * string`. Those are deliberately framed as **general transport bootstrap
25
26
  * credentials** (opaque strings), not MCP-Apps-specific. Any future
26
27
  * bootstrap mechanism (short-code auto-login, signed-URL bootstrap, etc.)
@@ -35,26 +36,26 @@ import type { JsonObject, JsonSchema, JsonValue } from '../types/data-contract.j
35
36
  export declare const MCP_APPS_UI_CAPABILITY: "io.modelcontextprotocol/ui";
36
37
  /**
37
38
  * The single MCP Apps resource URI ggui exposes for outbound delivery.
38
- * `ggui_push` is the sole tool declaration that carries this in its
39
+ * `ggui_render` is the sole tool declaration that carries this in its
39
40
  * `_meta.ui.resourceUri`. No other ggui tool gets a resource URI —
40
- * `ggui_push` is the single outbound entry point.
41
+ * `ggui_render` is the single outbound entry point.
41
42
  */
42
- export declare const GGUI_SESSION_RESOURCE_URI: "ui://ggui/session";
43
+ export declare const GGUI_RENDER_RESOURCE_URI: "ui://ggui/render";
43
44
  /**
44
- * MIME type for the `ui://ggui/session` resource. Per MCP Apps spec, UI
45
+ * MIME type for the `ui://ggui/render` resource. Per MCP Apps spec, UI
45
46
  * resources carry the `text/html` base type with a `profile=mcp-app`
46
47
  * parameter so hosts that don't support MCP Apps don't accidentally
47
48
  * render them as plain HTML.
48
49
  */
49
- export declare const GGUI_SESSION_RESOURCE_MIME: "text/html;profile=mcp-app";
50
+ export declare const GGUI_RENDER_RESOURCE_MIME: "text/html;profile=mcp-app";
50
51
  /**
51
52
  * The single `_meta.ui.resourceUri` value ggui uses across every MCP Apps
52
53
  * host surface. Exposed as a named constant so tool-declaration code,
53
54
  * resource-serving code, and tests all agree on one spelling.
54
55
  */
55
- export declare const GGUI_PUSH_UI_META: {
56
- /** Resource URI hosts fetch via `resources/read` on a `ggui_push` tool call. */
57
- readonly resourceUri: "ui://ggui/session";
56
+ export declare const GGUI_RENDER_UI_META: {
57
+ /** Resource URI hosts fetch via `resources/read` on a `ggui_render` tool call. */
58
+ readonly resourceUri: "ui://ggui/render";
58
59
  /** Only `"model"` — outer agent can call, iframe views cannot. */
59
60
  readonly visibility: readonly ["model"];
60
61
  };
@@ -66,525 +67,19 @@ export declare const GGUI_PUSH_UI_META: {
66
67
  */
67
68
  export type McpAppsToolVisibility = 'model' | 'app';
68
69
  /**
69
- * The bootstrap material an MCP Apps view (iframe) needs to mount a
70
- * ggui-rendered UI. Lives under `_meta.ggui.bootstrap` on the
71
- * `ggui_push` tool result — **not** on `structuredContent`.
72
- *
73
- * **Not model-visible.** Agents SHOULD NOT consume these fields. They are
74
- * scoped to the view lifecycle and carry credentials the model has no
75
- * legitimate use for. The split between `structuredContent` (model-facing)
76
- * and `_meta.ggui.bootstrap` (view-facing) is the protocol-level isolation
77
- * that keeps WebSocket credentials out of conversation transcripts.
78
- *
79
- * **Three boot modes share this single shape**, distinguished by which
80
- * of the three optional discriminator fields is populated:
81
- *
82
- * | Mode | Discriminator | What the view does |
83
- * | ---------------- | ----------------- | --------------------------------------------------- |
84
- * | live | `wsUrl` + `token` | Open live-channel WS, subscribe, render agent frames |
85
- * | static-component | `codeUrl` | Fetch + mount compiled React component, no WS |
86
- * | system-card | `kind` | Mount built-in system card by registry id, no WS |
87
- *
88
- * **Mutual-exclusion contract.** A consumer-acceptable bootstrap MUST
89
- * carry at least one of `{wsUrl-with-token, codeUrl, kind}`.
90
- * Multiple may coexist (e.g. a server emitting both `codeUrl`
91
- * AND `wsUrl+token` lets the runtime pick — current iframe-runtime
92
- * priority is code-then-system-then-live), but at least one MUST
93
- * be present. A bootstrap with none is MALFORMED.
94
- *
95
- * **Live-mode auth semantics** (only relevant when `wsUrl` + `token`
96
- * are present):
97
- * - `token` is session-scoped, short-TTL, single-use on initial
98
- * `subscribe`. Consumed at the first bootstrap-auth'd subscribe and
99
- * cannot be reused.
100
- * - `expiresAt` lets the view skip an obviously-stale bootstrap without
101
- * a round-trip. Servers reject expired tokens anyway; this is UX
102
- * sugar, not a security control.
103
- * - For reconnects, the view should use the longer-lived `sessionToken`
104
- * the server issues on the successful bootstrap-auth ack — see
105
- * {@link AckPayload.sessionToken}.
106
- *
107
- * **Live-mode example:**
108
- * ```json
109
- * {
110
- * "sessionId": "sess_001", "appId": "app_001",
111
- * "runtimeUrl": "/_ggui/iframe-runtime.js",
112
- * "wsUrl": "wss://server.example/ws",
113
- * "token": "tok_abc", "expiresAt": "2099-01-01T00:00:00.000Z"
114
- * }
115
- * ```
116
- *
117
- * **Static-component example:**
118
- * ```json
119
- * {
120
- * "sessionId": "sess_001", "appId": "app_001",
121
- * "runtimeUrl": "/_ggui/iframe-runtime.js",
122
- * "codeUrl": "https://server.example/code/sha256:abc.js",
123
- * "codeHash": "sha256:abc...",
124
- * "themeId": "indigo", "propsJson": "{\"name\":\"Ada\"}"
125
- * }
126
- * ```
70
+ * Phase B render-identity collapse — the previously two-slice wire
71
+ * (`ai.ggui/session` + `ai.ggui/stack-item`) is merged into ONE slice
72
+ * (`ai.ggui/render`). Consumers parse with {@link parseMcpAppAiGguiRenderMeta}
73
+ * and read fields directly off the {@link McpAppAiGguiRenderMeta} struct.
127
74
  *
128
- * **System-card example:**
129
- * ```json
130
- * {
131
- * "sessionId": "sess_001", "appId": "app_001",
132
- * "runtimeUrl": "/_ggui/iframe-runtime.js",
133
- * "kind": "loading"
134
- * }
135
- * ```
75
+ * Why the merge: every "session" wrapped exactly one stack item post-
76
+ * Phase-A, so the two slices were always activated in lock-step. The
77
+ * pair-holder added ceremony with no signal. Flat is the honest shape.
136
78
  */
137
- export interface GguiBootstrapMeta {
138
- /**
139
- * WebSocket URL the view opens for live mode (e.g.
140
- * `wss://server.example/ws`). REQUIRED in live mode; absent in
141
- * static-component and system-card modes. When present, MUST be
142
- * paired with a non-empty `token` — half-live (one without the
143
- * other) is MALFORMED.
144
- */
145
- readonly wsUrl?: string;
146
- /**
147
- * Short-TTL single-use bootstrap token, passed as
148
- * `SubscribePayload.bootstrap`. REQUIRED in live mode; absent in
149
- * the other modes.
150
- */
151
- readonly token?: string;
152
- /**
153
- * ISO 8601 UTC timestamp after which the token is no longer accepted.
154
- * Optional; meaningful only in live mode (the token it pairs with
155
- * may be unbounded TTL when this is absent).
156
- */
157
- readonly expiresAt?: string;
158
- /**
159
- * Session id the token binds to. Repeated here (also present on
160
- * `structuredContent.sessionId`) so the view has everything it needs
161
- * to subscribe from `_meta.ggui.bootstrap` alone — no need to cross-
162
- * reference structuredContent. Keeps the view-side boot code small.
163
- */
164
- readonly sessionId: string;
165
- /**
166
- * App (tenant) id the token binds to. Required in the subscribe
167
- * payload; not necessarily appropriate for structuredContent
168
- * (tenant identity is app-facing bootstrap metadata, not typically
169
- * part of an agent's typed output surface).
170
- */
171
- readonly appId: string;
172
- /**
173
- * URL of the iframe-runtime bundle the iframe should fetch. The
174
- * thin-shell HTML's inline JS dynamically appends
175
- * `<script src={runtimeUrl}>` to load the runtime. Server controls
176
- * this per-session so the shell works in `srcdoc` iframes (which
177
- * have no origin of their own) and across local / cloud
178
- * deployments.
179
- *
180
- * OSS `ggui serve` resolves this to `/_ggui/iframe-runtime.js` —
181
- * same-origin as the MCP server's HTTP listener. Hosted cloud
182
- * serves from a dedicated CDN route. Either way, the server OWNS
183
- * the string; the shell does not guess, concatenate, or fall back
184
- * to a bundled default.
185
- *
186
- * Named parties: **server** produces; **thin shell** consumes;
187
- * **iframe runtime bundle** is what the URL resolves to. Failure
188
- * mode on fetch error: runtime surfaces a `BUNDLE_FETCH_FAILED`
189
- * bootstrap failure via `postMessage({type:'ggui:bootstrap-failed',
190
- * reason, message})` to the parent (C8 commit 3). Absent or empty
191
- * at parse-time is `BOOTSTRAP_META_MISSING` — the shell rejects
192
- * the bootstrap without attempting a script load.
193
- *
194
- * C8 (2026-04-23) made this required. Pre-C8 servers that emit
195
- * bootstraps without `runtimeUrl` are incompatible with the
196
- * post-C8 thin-shell HTML — the shell pivot (`~175` → `~30` LOC
197
- * wrapper) moved all rendering logic out of the shell into the
198
- * separately-served iframe-runtime bundle, so the URL is now
199
- * load-bearing.
200
- * @public
201
- */
202
- readonly runtimeUrl: string;
203
- /**
204
- * Optional polling fallback URL the iframe-runtime fetches when its
205
- * WebSocket transport is unavailable or fails. Points at the same
206
- * `/api/bootstrap/<shortCode>` endpoint the iframe used to initially
207
- * load the bootstrap envelope — polling re-fetches it and the
208
- * iframe-runtime diffs the `propsJson` field to synthesize
209
- * `props_update` frames for live re-render.
210
- *
211
- * Empirically required for MCP-Apps hosts whose iframe sandbox
212
- * blocks `wss://` at the CSP layer regardless of our
213
- * `_meta.ui.csp.connectDomains` declaration (Claude Desktop is the
214
- * known case; claude.ai Connector honors WS). Without this field,
215
- * polling has no URL to hit and live updates silently no-op when
216
- * WS is unavailable.
217
- *
218
- * Producer: server's `/api/bootstrap/<shortCode>` endpoint stamps
219
- * this on the response body — same origin as the request, same
220
- * shortCode in the path. Consumer: iframe-runtime threads this
221
- * through `createPropsUpdateHandler({pollingUrl})` so
222
- * `PollingTransport` has a URL to fetch.
223
- *
224
- * Absent → no polling fallback. WS-only mode is fine for hosts
225
- * whose CSP permits `wss://`.
226
- *
227
- * @public
228
- */
229
- readonly pollingUrl?: string;
230
- /**
231
- * Names of same-server tools whose `_meta.ui.visibility` includes
232
- * `"app"` and are therefore directly callable from this iframe via
233
- * `tools/call` (per MCP-Apps spec §2026-01-26 Visibility rules:
234
- * "app" = callable by the app from the same server connection only;
235
- * cross-server tool calls are always blocked).
236
- *
237
- * Used by the iframe-runtime as a capability fingerprint — telemetry
238
- * and debug surfaces consult it to know which tool names are reachable
239
- * over the host's same-server `tools/call` path (e.g. the iframe-
240
- * internal `ggui_runtime_submit_action` and `ggui_runtime_sync_context`
241
- * relays). It is NOT consulted to choose a dispatch routing strategy:
242
- * every user gesture flows through `ggui_runtime_submit_action`, lands
243
- * on the per-stack-item pending-events pipe, and is drained by the
244
- * agent's `ggui_consume` long-poll on the next turn. Actions ALWAYS
245
- * drive turns through consume — there is no synchronous server-side
246
- * dispatch in agent-mediated deployments.
247
- *
248
- * Producers SHOULD include every same-server-app-visible tool;
249
- * consumers MUST treat an absent field as an empty list (legacy
250
- * bootstrap envelopes predating this addition).
251
- *
252
- * @public
253
- */
254
- readonly appCallableTools?: readonly string[];
255
- /**
256
- * Per-action `nextStep` hint mapping for the active stack item's
257
- * `actionSpec`. Maps `actionName → toolName` where `toolName` is the
258
- * value of `actionSpec[name].nextStep` — the optional hint naming the
259
- * tool the agent SHOULD call next when the action fires. Only entries
260
- * whose `actionSpec[name].nextStep` is declared are projected; actions
261
- * without a hint are omitted from the map.
262
- *
263
- * Producer: server's push handler at push time, sourced from the
264
- * resolved stack item's `actionSpec`. Consumer: iframe-runtime, which
265
- * mirrors the hint onto outbound `_meta.ggui.userAction` fall-through
266
- * envelopes (the inline variant in {@link InlineUserActionMeta.nextStep})
267
- * so the agent gets a strong tool-choice steer when chat-shortcut
268
- * fallback fires.
269
- *
270
- * Absent / empty mapping ⇒ no per-action hints; the iframe omits
271
- * `nextStep` from the fall-through envelope and the agent picks the
272
- * next tool freely.
273
- *
274
- * @public
275
- */
276
- readonly actionNextSteps?: Readonly<Record<string, string>>;
277
- /**
278
- * Per-slot data for the active stack item's `contextSpec`. Each entry
279
- * carries the slot name + the JsonSchema for runtime validation +
280
- * optional debounceMs override + optional default value.
281
- *
282
- * Producer: server's push handler at push time, derived from
283
- * `activeStackItem.contextSpec`. Consumer: iframe-runtime, which (at
284
- * boot) synthesizes one `React.createContext(default)` per entry and
285
- * registers it under `globalThis.__ggui__.contexts[contextName]`. The
286
- * boilerplate destructures the registered Contexts so the LLM has
287
- * them in scope without any import line.
288
- *
289
- * Absent → empty list; the runtime synthesizes no Contexts; the
290
- * `globalThis.__ggui__.contexts` registry is `{}`.
291
- *
292
- * @public
293
- */
294
- readonly contextSlots?: ReadonlyArray<{
295
- /** Slot key — camelCase JS identifier from `contextSpec`. */
296
- readonly name: string;
297
- /** PascalCase Context name auto-derived from `name`. The runtime
298
- * uses this as the key in `globalThis.__ggui__.contexts`. The
299
- * boilerplate uses it in destructuring lines. */
300
- readonly contextName: string;
301
- /** JsonSchema for the slot value — used by the runtime observer
302
- * to validate Provider values before posting `ui/update-model-context`. */
303
- readonly schema: JsonSchema;
304
- /** Initial value for the slot's React Context Provider. Always
305
- * populated by the server via {@link deriveContextDefault}: the
306
- * authored `entry.default` if present, otherwise a schema-typed
307
- * fallback (`''` / `0` / `false` / `[]` / `{}` / `null`). The
308
- * runtime owns useState per slot, so the Provider seed is load-
309
- * bearing — `undefined` here would mean
310
- * the iframe boots with an indeterminate Provider value. */
311
- readonly default: JsonValue;
312
- /** Per-slot debounce override in milliseconds. Omitted → runtime
313
- * applies `DEFAULT_CONTEXT_DEBOUNCE_MS` (300). `0` = immediate. */
314
- readonly debounceMs?: number;
315
- }>;
316
- /**
317
- * Optional stack-item pin. When present, the renderer binds to a
318
- * single `StackItem` (identified by `id`) instead of the full session
319
- * stack — enables per-card iframes (per-item session-resource
320
- * endpoint + renderer single-item mode).
321
- *
322
- * Absent → renderer renders the whole session stack (default).
323
- * Present → renderer filters to `session.stack.find(i => i.id === stackItemId)`
324
- * and ignores the rest. Subsequent live-channel updates for other stack
325
- * ids are delivered but not rendered.
326
- *
327
- * Resource URI convention: per-item shells are served at
328
- * `ggui://session/<sessionId>/item/<stackItemId>`; whole-session
329
- * shells remain at `ggui://session/<sessionId>`.
330
- *
331
- * The `stackItemId` is the opaque string MCP hosts receive on a
332
- * `ggui_push` tool result's `stackItemId` field (or equivalently the
333
- * `StackItem.id` in session state). Renderers that don't recognize
334
- * the field SHOULD ignore it (falls back to whole-session rendering)
335
- * — shape-preserving extensibility is the contract.
336
- *
337
- * @public
338
- */
339
- readonly stackItemId?: string;
340
- /**
341
- * Content-addressable URL the runtime fetches the compiled ES module
342
- * from. The discriminator for **static-component** boot mode (2026-05-13
343
- * — the inline base64 `componentCode` channel was retired in favor of
344
- * always-by-URL delivery).
345
- *
346
- * Producer: server. Computes `sha256(componentCode)`, writes the bytes
347
- * to its `CodeStore`, then emits the URL here. URL shape is
348
- * `<publicBaseUrl>/code/<hash>.js` for OSS or `<cdn>/code/<hash>.js`
349
- * for hosted cloud — the renderer doesn't care which origin,
350
- * `fetch(codeUrl)` works the same.
351
- *
352
- * Consumer: iframe runtime. Fetches + dynamic-imports the URL. The
353
- * response is `Cache-Control: immutable`, so subsequent pushes with
354
- * identical code hit the browser cache (and any CDN edge).
355
- *
356
- * Mutually exclusive at the discriminator level with `wsUrl+token`
357
- * (live mode) and `kind` (system-card mode). When `codeStore` isn't
358
- * wired, push falls back to live-mode (wsUrl+token) for delivery via
359
- * the live-channel stack update.
360
- *
361
- * @public
362
- */
363
- readonly codeUrl?: string;
364
- /**
365
- * Hex-encoded sha256 of the code bytes the URL serves.
366
- * Surfaced separately from `codeUrl` so consumers can verify
367
- * content integrity (the URL already encodes it, but parsing the
368
- * URL is fragile across CDN configurations) and so the agent can
369
- * inspect deduplication signals across pushes without parsing the
370
- * URL.
371
- *
372
- * Always paired with `codeUrl`: present together or absent together.
373
- *
374
- * @public
375
- */
376
- readonly codeHash?: string;
377
- /**
378
- * Discriminator for **system-card** boot mode. Stable identifier the
379
- * runtime maps via the system-card registry to a built-in component
380
- * (no ESM source on the wire). When present, the view does NOT open
381
- * a WebSocket — mounting is purely registry-lookup.
382
- *
383
- * Mutually exclusive at the discriminator level with `codeUrl`
384
- * (static-component mode) and `wsUrl+token` (live mode).
385
- *
386
- * @public
387
- */
388
- readonly kind?: string;
389
- /**
390
- * Theme preset id forwarded to the renderer (`getTheme(id)`).
391
- * Optional; absent → renderer uses its baked default theme. Used
392
- * by the self-contained shell so `ggui.json#theme` takes effect
393
- * across both the WS-driven and self-contained paths.
394
- *
395
- * @public
396
- */
397
- readonly themeId?: string;
398
- /**
399
- * Theme color mode (`'light'` | `'dark'`) forwarded to the renderer.
400
- * The runtime resolves the dark variant of {@link themeId} via
401
- * `getTheme(id, 'dark')` when set; absent / unknown value falls
402
- * back to `'light'`.
403
- *
404
- * @public
405
- */
406
- readonly themeMode?: 'light' | 'dark';
407
- /**
408
- * Pre-serialized props for the rendered component (JSON string).
409
- * Optional; absent → renderer falls back to empty props. Carried
410
- * as a string to sidestep XSS-defensive escape concerns when the
411
- * bootstrap is inlined as a JS literal in the self-contained shell.
412
- *
413
- * @public
414
- */
415
- readonly propsJson?: string;
416
- /**
417
- * Permissions-Policy directive list derived from the active stack item's
418
- * `DataContract.clientCapabilities.gadgets[*].permission` field.
419
- * Browser-capability names (`'camera'`, `'microphone'`,
420
- * `'geolocation'`, `'clipboard-write'`, `'clipboard-read'`,
421
- * `'notifications'`, …) or arbitrary identifiers for custom
422
- * platforms; the host union-deduplicates and emits these as the
423
- * iframe's `Permissions-Policy` HTTP header (public-render path)
424
- * or `_meta.ui.permissions` (MCP-Apps embedded path).
425
- *
426
- * The bootstrap MIRRORS the same list inline so the iframe-runtime
427
- * can surface the requested set to in-iframe debug overlays /
428
- * permission-aware UI. The BROWSER-enforced gate, however, comes
429
- * from the parent-page transport (HTTP header or iframe attribute)
430
- * — the iframe-runtime itself cannot change Permissions-Policy
431
- * post-load; it can only react to what the parent already granted.
432
- *
433
- * Absent / empty → no permissions requested (default-deny posture).
434
- *
435
- * @public
436
- */
437
- readonly permissionsPolicy?: readonly string[];
438
- /**
439
- * Mirror of
440
- * `handshakeOutput.serverCapabilities.streamWebSocketLocalTools` on
441
- * the bootstrap envelope, so the iframe-runtime's per-channel
442
- * transport router can decide WS-subscribe vs iframe-polling for
443
- * each `streamSpec[ch].source.tool` without re-querying the
444
- * handshake.
445
- *
446
- * Producer: the server's push handler at push time, sourced from
447
- * `GguiPushHandlerDeps.streamWebSocketLocalTools` (which mirrors the
448
- * resolver on the handshake handler). Consumer: iframe-runtime's
449
- * channel-transport module, which:
450
- *
451
- * - For each `streamSpec[ch]` with `source.tool` declared AND that
452
- * tool name is in this list → fire a `channel_subscribe` WS
453
- * frame; wait for `channel_payload` deliveries.
454
- * - Otherwise → start a per-channel iframe polling loop (default
455
- * 10s cadence) that invokes `tools/call` directly through the
456
- * MCP host proxy.
457
- *
458
- * Absent ⇒ universal iframe-polling fallback (no channel uses the
459
- * WS-subscribe path). Present + empty array ⇒ same behavior; the
460
- * empty list still says "the WS-subscribe path is supported but no
461
- * tool is local".
462
- *
463
- * @public
464
- */
465
- readonly streamWebSocketLocalTools?: readonly string[];
466
- /**
467
- * When `true`, this bootstrap describes a
468
- * SESSION-SCOPED canvas iframe (one per session) rather than a
469
- * per-stack-item iframe. The canvas:
470
- *
471
- * - Subscribes session-wide on the live channel (no `stackItemId` filter).
472
- * - Renders a navigable stack of items, not a single pinned entry.
473
- * - Owns its own chrome — the ggui animator pill + navbar.
474
- * - Requests `pip` / `fullscreen` display modes from the host based
475
- * on stack state.
476
- *
477
- * Mutually exclusive with `stackItemId`: a canvas iframe never pins
478
- * to a single item. Defensive parsers SHOULD reject bootstraps with
479
- * both fields set, but the protocol does not require them to.
480
- *
481
- * Absent / false ⇒ existing inline iframe behavior. Required to be
482
- * explicit (rather than overloading absent-`stackItemId`) because
483
- * legacy multi-item mode (Studio/Portal/console) ALSO uses
484
- * absent-`stackItemId`; the explicit flag disambiguates.
485
- */
486
- readonly canvasMode?: boolean;
487
- /**
488
- * Resolved gadget catalog the iframe-runtime dynamically
489
- * imports at boot to populate `globalThis.__ggui__.gadgets`.
490
- *
491
- * One entry per registered gadget **package** (GG.8.2 — the channel
492
- * is per-package, not per-hook: a package's whole module namespace
493
- * is loaded once and stored under `__ggui__.gadgets[package]`, so
494
- * every hook AND component export the package ships is reachable).
495
- * STDLIB exports (the 7 first-party browser-capability hooks shipped
496
- * by `@ggui-ai/gadgets`) are seeded unconditionally and need NOT
497
- * appear here — only operator-registered 3rd-party packages (Leaflet,
498
- * Mapbox, …) do. This list is what makes registered packages
499
- * reachable inside the iframe.
500
- *
501
- * Producer: server's bootstrap builder, sourced from
502
- * `App.gadgets`. Consumer: iframe-runtime's
503
- * `loadGadgetRegistry()` which `await import(target)`s each
504
- * package once and stores the module namespace under the
505
- * package-name slot.
506
- *
507
- * Absent or empty → only STDLIB exports are reachable. Generated
508
- * components that import an unregistered gadget package fail at the
509
- * iframe's ESM module-eval (the rewriter has no shim for it).
510
- *
511
- * @public
512
- */
513
- readonly gadgets?: ReadonlyArray<{
514
- /** Bare npm package name (e.g. `@my-org/leaflet`). REQUIRED — it
515
- * is the registry key the iframe-runtime stores the loaded module
516
- * namespace under at `globalThis.__ggui__.gadgets[package]`, and
517
- * the bare-specifier load source when `bundleUrl` is absent. */
518
- readonly package: string;
519
- /** ggui-hosted ESM bundle URL — preferred load source when present
520
- * (same-origin posture, CSP-friendly). The iframe
521
- * `await import(this)`; absent → the iframe imports the bare
522
- * `package` specifier. */
523
- readonly bundleUrl?: string;
524
- /** SHA-384 SRI hash of the bundle (`sha384-<base64>`).
525
- * When present alongside `bundleUrl`, iframe-runtime routes the
526
- * load through a `<link rel="modulepreload" integrity>` gate so
527
- * the browser refuses execution on hash mismatch. Absent → fall
528
- * back to integrity-less dynamic `import()` (back-compat for
529
- * in-tree packages and hand-authored ggui.json refs). */
530
- readonly bundleSri?: string;
531
- }>;
532
- /**
533
- * Public env values the iframe-runtime installs at
534
- * `globalThis.__ggui__.publicEnv` for wrapper hooks to read via
535
- * `getPublicEnv(key)`. Keys MUST match
536
- * `PUBLIC_ENV_APP_KEY_RE` (`^GGUI_PUBLIC_APP_[A-Z0-9_]+$` — exported
537
- * from `@ggui-ai/protocol`). The prefix is the security boundary:
538
- * "public" means visible to anyone with iframe-source access.
539
- *
540
- * Filtered by the producer (push handler) to the **union of
541
- * `wrapper.requires` across declared wrappers** — minimum-disclosure
542
- * principle. Keys an iframe's wrappers don't ask for never reach
543
- * the iframe.
544
- *
545
- * Producer: server's bootstrap builder, sourced from
546
- * `App.publicEnv` cross-referenced against the declared wrappers'
547
- * `requires`. Consumer: iframe-runtime's `installGlobalRegistry`
548
- * which plants the map verbatim at `__ggui__.publicEnv`.
549
- *
550
- * Defensive parse: `parseBootstrap` re-validates every key against
551
- * the regex; one bad key collapses the whole field to `undefined`
552
- * (matches the `gadgets` / `contextSlots` parser posture).
553
- *
554
- * Absent or empty → no wrapper-readable env values; only wrappers
555
- * with no `requires` declarations can mount.
556
- *
557
- * @public
558
- */
559
- readonly publicEnv?: Readonly<Record<string, string>>;
560
- /**
561
- * Precompiled, eval-free runtime validators for the active stack
562
- * item's contract specs. See {@link CompiledContractValidators}.
563
- *
564
- * Why precompiled: the renderer iframe runs under a strict CSP with
565
- * no `'unsafe-eval'`, so it cannot compile JSON Schemas at runtime
566
- * (`ajv.compile` builds validators via `new Function`, which the CSP
567
- * blocks with `EvalError`). Compilation happens server-side at push
568
- * time — where the contract schema is fixed and `eval` is legal —
569
- * and the iframe loads each module via a `blob:` dynamic import
570
- * (governed by `script-src`, not `unsafe-eval`) and only ever RUNS
571
- * the validator.
572
- *
573
- * Producer: server push handler, via `compileValidatorModule` from
574
- * `@ggui-ai/protocol`. Consumer: iframe-runtime's wire + channel
575
- * validation layer.
576
- *
577
- * Absent → no precompiled validators shipped; the iframe falls back
578
- * to the server as the sole contract authority (the push-time
579
- * `assertActionContract` gate still enforces outbound actions).
580
- *
581
- * @public
582
- */
583
- readonly compiledValidators?: CompiledContractValidators;
584
- }
585
79
  /**
586
80
  * Precompiled, eval-free validators for a contract's runtime-validated
587
- * specs — see {@link GguiBootstrapMeta.compiledValidators}.
81
+ * specs — served at `_meta["ai.ggui/contract"].validatorsUrl` as the
82
+ * `default` export of a content-addressable ES module.
588
83
  *
589
84
  * Each value is the SOURCE TEXT of an ES module whose `default` export
590
85
  * is an Ajv validator function (`(data) => boolean`, carrying
@@ -627,19 +122,260 @@ export interface CompiledContractValidators {
627
122
  */
628
123
  export declare function deriveContextName(slotKey: string): string;
629
124
  /**
630
- * Shape of the `_meta` field on a `ggui_push` tool result when the server
631
- * is the MCP Apps outbound host. Wrapping the bootstrap under `_meta.ggui.*`
632
- * keeps ggui's namespace distinct from MCP Apps' own `_meta.ui.*` on the
633
- * tool declaration side.
634
- *
635
- * Hosts forward `_meta` alongside `structuredContent` when they pass tool
636
- * output into `ui/initialize`; the view reads `_meta.ggui.bootstrap` from
637
- * the initialization context.
125
+ * `_meta` key carrying the full render slice. Single source of truth
126
+ * post-Phase-B: identity, boot wiring, live-channel auth, capability
127
+ * advertisements, current render state, contract pointer, and component-
128
+ * mode discriminator.
129
+ *
130
+ * @public
131
+ */
132
+ export declare const MCP_APP_AI_GGUI_RENDER_META_KEY: "ai.ggui/render";
133
+ /**
134
+ * Single entry in the {@link McpAppAiGguiRenderMeta.gadgets} catalog.
135
+ * One per registered gadget package — the iframe-runtime
136
+ * dynamic-imports each at boot and stores the loaded namespace under
137
+ * `globalThis.__ggui__.gadgets[package]`.
138
+ *
139
+ * @public
638
140
  */
639
- export interface PushResultMeta {
640
- readonly ggui: {
641
- readonly bootstrap: GguiBootstrapMeta;
642
- };
141
+ export interface McpAppGadgetRef {
142
+ /** Bare npm package name (e.g. `@my-org/leaflet`). REQUIRED — it
143
+ * is the registry key the iframe-runtime stores the loaded module
144
+ * namespace under at `globalThis.__ggui__.gadgets[package]`, and
145
+ * the bare-specifier load source when `bundleUrl` is absent. */
146
+ readonly package: string;
147
+ /** ggui-hosted ESM bundle URL — preferred load source when present
148
+ * (same-origin posture, CSP-friendly). The iframe `await import(this)`;
149
+ * absent → the iframe imports the bare `package` specifier. */
150
+ readonly bundleUrl?: string;
151
+ /** SHA-384 SRI hash of the bundle (`sha384-<base64>`).
152
+ * When present alongside `bundleUrl`, iframe-runtime routes the
153
+ * load through a `<link rel="modulepreload" integrity>` gate so
154
+ * the browser refuses execution on hash mismatch. */
155
+ readonly bundleSri?: string;
156
+ }
157
+ /**
158
+ * Single entry in {@link McpAppAiGguiRenderMeta.contextSlots}.
159
+ * One per `contextSpec` slot — the iframe-runtime synthesizes one
160
+ * `React.createContext(default)` per entry at boot.
161
+ *
162
+ * @public
163
+ */
164
+ export interface McpAppContextSlot {
165
+ /** Slot key — camelCase JS identifier from `contextSpec`. */
166
+ readonly name: string;
167
+ /** PascalCase Context name auto-derived from `name`. Used as the
168
+ * registry key in `globalThis.__ggui__.contexts`. */
169
+ readonly contextName: string;
170
+ /** JsonSchema for the slot value — used by the runtime observer to
171
+ * validate Provider values before posting `ui/update-model-context`. */
172
+ readonly schema: JsonSchema;
173
+ /** Initial Provider value. Always populated by the server. */
174
+ readonly default: JsonValue;
175
+ /** Per-slot debounce override in milliseconds. Omitted → runtime
176
+ * applies `DEFAULT_CONTEXT_DEBOUNCE_MS` (300). `0` = immediate. */
177
+ readonly debounceMs?: number;
178
+ }
179
+ /**
180
+ * The full render slice — flat post-Phase-B. Identity + boot wiring +
181
+ * live-channel auth + capability advertisements + render state +
182
+ * contract pointer + component-mode discriminator.
183
+ *
184
+ * **Identity.** `renderId` is the value an iframe's bootstrap meta and
185
+ * every wire reference (props_update, consume, update) keys by. The
186
+ * value is the same one stack items carried as `stackItemId` pre-Phase-B;
187
+ * the rename reflects the conceptual collapse (no enclosing vessel).
188
+ *
189
+ * **Live-channel auth.** `wsUrl` + `wsToken` are paired — both present
190
+ * or both absent; `expiresAt` is informational. `wsToken` is the opaque
191
+ * WS auth credential the iframe threads on the WebSocket upgrade as
192
+ * `?wsToken=<encoded>` and inside `SubscribePayload.wsToken`.
193
+ *
194
+ * **Polling-fallback URL.** When WS is blocked at the host CSP layer
195
+ * the iframe polls `pollingUrl` (server-stamped post-Phase-B as
196
+ * `/api/renders/<renderId>/events?wsToken=<token>`). The iframe-runtime
197
+ * composes per-tick `&sinceSequence=<cursor>&limit=<N>` against this
198
+ * base; companion to `lastSequence` which seeds the initial cursor.
199
+ *
200
+ * **Mode discriminator.** At least one of `{ codeUrl, kind, wsUrl-with-token }`
201
+ * MUST be present for the iframe to mount. `kind` and `codeUrl` are
202
+ * mutually exclusive (kind = system-card mode; codeUrl = static-component
203
+ * mode; live-channel = absent both).
204
+ *
205
+ * @public
206
+ */
207
+ export interface McpAppAiGguiRenderMeta {
208
+ readonly renderId: string;
209
+ readonly appId: string;
210
+ readonly runtimeUrl: string;
211
+ readonly wsUrl?: string;
212
+ readonly wsToken?: string;
213
+ readonly expiresAt?: string;
214
+ readonly pollingUrl?: string;
215
+ readonly themeId?: string;
216
+ readonly themeMode?: 'light' | 'dark';
217
+ readonly gadgets?: ReadonlyArray<McpAppGadgetRef>;
218
+ readonly publicEnv?: Readonly<Record<string, string>>;
219
+ readonly streamWebSocketLocalTools?: readonly string[];
220
+ readonly appCallableTools?: readonly string[];
221
+ readonly permissionsPolicy?: readonly string[];
222
+ /**
223
+ * Monotonic sequence number of the most-recent event applied to
224
+ * this render's event ledger. Stamped on every emission (render,
225
+ * update, `GET /api/renders/:id/state` read, MCP `resources/read`
226
+ * of `ui://ggui/render/<id>`). Consumers use it to initialize
227
+ * polling cursors aligned with the event ledger — see the
228
+ * `/api/renders/:id/events?sinceSequence=N` endpoint that reads
229
+ * from a cursor.
230
+ */
231
+ readonly lastSequence?: number;
232
+ readonly propsJson?: string;
233
+ readonly actionNextSteps?: Readonly<Record<string, string>>;
234
+ readonly contextSlots?: ReadonlyArray<McpAppContextSlot>;
235
+ readonly contractHash?: string;
236
+ readonly validatorsUrl?: string;
237
+ readonly codeUrl?: string;
238
+ readonly codeHash?: string;
239
+ readonly kind?: string;
240
+ }
241
+ /**
242
+ * Discriminated result of {@link parseMcpAppAiGguiRenderMeta}. The parser
243
+ * does structural slice-shape validation only — `MALFORMED_RENDER`
244
+ * surfaces a structurally-invalid slice (wrong type, missing required
245
+ * identity, paired fields half-present, mutually-exclusive fields both
246
+ * present). Missing key entirely is NOT a failure; the "is the slice
247
+ * required for THIS consumer" gate lives in consumer-side mount check.
248
+ *
249
+ * @public
250
+ */
251
+ export type ParseMcpAppAiGguiRenderMetaResult = {
252
+ readonly ok: true;
253
+ readonly meta?: McpAppAiGguiRenderMeta;
254
+ } | {
255
+ readonly ok: false;
256
+ readonly reason: 'MALFORMED_RENDER';
257
+ };
258
+ /**
259
+ * Read the `ai.ggui/render` slice off a parsed JSON-RPC `_meta` object.
260
+ *
261
+ * Structural validation only. Missing key returns `{ok: true, meta: undefined}`
262
+ * — not a failure. Required-fields gate (renderId / appId / runtimeUrl)
263
+ * fires only when the key is present. Field-level optional-field
264
+ * defensive parsing (e.g. context-slot schema narrowing, expiresAt date
265
+ * parse) lives downstream in the iframe-runtime's `validateMeta`.
266
+ *
267
+ * @public
268
+ */
269
+ export declare function parseMcpAppAiGguiRenderMeta(meta: unknown): ParseMcpAppAiGguiRenderMetaResult;
270
+ /**
271
+ * Emitter convenience — wrap a server-built {@link McpAppAiGguiRenderMeta}
272
+ * slice as the wire `_meta` envelope under the canonical key constant.
273
+ *
274
+ * @public
275
+ */
276
+ export declare function toMcpAppEnvelope(render: McpAppAiGguiRenderMeta): Record<string, unknown>;
277
+ /**
278
+ * `_meta` key carrying host-supplied conversation-grouping metadata on
279
+ * every inbound `tools/call` request. Captured ONCE on the first call
280
+ * that materializes a ggui render row and persisted as opt-in identity
281
+ * for later rehydration.
282
+ *
283
+ * Hosts that don't set this key produce one-shot renders — they work
284
+ * fine for a single chat turn but cannot be re-listed or restored after
285
+ * the host closes the conversation surface. Opt-in is the whole design:
286
+ * hosts that want resume thread their conversation id here; hosts that
287
+ * don't get the simple write-only path.
288
+ *
289
+ * @public
290
+ */
291
+ export declare const MCP_APP_AI_GGUI_HOST_SESSION_META_KEY: "ai.ggui/host-session";
292
+ /**
293
+ * Host-supplied conversation-grouping slice. Sent on the request `_meta`
294
+ * of the first `ggui_*` tool call that creates a ggui render; subsequent
295
+ * calls naming the same render ignore the field — set-at-creation,
296
+ * immutable.
297
+ *
298
+ * Opaque grouping key, NOT a credential. Auth still comes from the
299
+ * caller's identity (API key, OAuth bearer, cookie). `hostSessionId`
300
+ * scopes which renders the authenticated caller can rehydrate; it
301
+ * does NOT itself authorize access.
302
+ *
303
+ * Both fields are required when the slice is present. A slice with a
304
+ * missing/empty field is treated as absent (degrades to one-shot).
305
+ *
306
+ * @public
307
+ */
308
+ export interface McpAppAiGguiHostSessionMeta {
309
+ /**
310
+ * Stable host identifier — e.g. `'sample'`, `'claude.ai'`, `'chatgpt'`.
311
+ * Used to partition `hostSessionId` namespace so the same chat-id
312
+ * across two different hosts cannot alias.
313
+ */
314
+ readonly hostName: string;
315
+ /**
316
+ * Host's grouping key for "this conversation" — opaque to ggui.
317
+ * Typically: claude.ai thread id, ChatGPT chat id, sample-agent
318
+ * chatSessionId. The server treats it as an opaque string.
319
+ */
320
+ readonly hostSessionId: string;
321
+ }
322
+ /**
323
+ * Discriminated result of {@link parseMcpAppAiGguiHostSessionMeta}.
324
+ *
325
+ * Three outcomes:
326
+ * - `ok: true, hostSession: <slice>` — slice present + well-formed.
327
+ * - `ok: true, hostSession: undefined` — slice absent (host opted out
328
+ * of rehydration). Caller proceeds without it; the render it
329
+ * creates is one-shot.
330
+ * - `ok: false` — slice present but structurally invalid. Caller's
331
+ * choice whether to reject the request or proceed as "absent".
332
+ * The handler MAY log + proceed; this is host implementor error,
333
+ * not a security boundary.
334
+ *
335
+ * @public
336
+ */
337
+ export type ParseMcpAppAiGguiHostSessionMetaResult = {
338
+ readonly ok: true;
339
+ readonly hostSession?: McpAppAiGguiHostSessionMeta;
340
+ } | {
341
+ readonly ok: false;
342
+ readonly reason: 'MALFORMED_HOST_SESSION';
343
+ };
344
+ /**
345
+ * Read the `ai.ggui/host-session` slice off a parsed inbound `_meta`
346
+ * object. Structural validation only — `hostName` + `hostSessionId`
347
+ * both required and non-empty. Both missing entirely returns
348
+ * `{ok: true, hostSession: undefined}` (the documented opt-out path).
349
+ *
350
+ * @public
351
+ */
352
+ export declare function parseMcpAppAiGguiHostSessionMeta(meta: unknown): ParseMcpAppAiGguiHostSessionMetaResult;
353
+ /**
354
+ * Wire shape of one row in `ggui_list_renders` output. Mirrors the
355
+ * handler's Zod-described `renders[*]`. Surfaced at the protocol level
356
+ * so non-handler consumers (sample-agent's `/chat/restore` server, future
357
+ * host SDK helpers) can import a single typed shape instead of
358
+ * redeclaring it — preventing drift if the handler ever grows fields.
359
+ *
360
+ * `wsToken` + `wsTokenExpiresAt` are populated when the deployment
361
+ * wired a `mintWsToken` seam on the handler — otherwise the lean
362
+ * summary path returns them absent.
363
+ *
364
+ * Post-Phase-B: `sessionId` → `renderId`; the old `stackItemCount` is
365
+ * dropped (every render is exactly one item — Phase B collapsed the
366
+ * vessel).
367
+ *
368
+ * @public
369
+ */
370
+ export interface RenderSummaryWire {
371
+ readonly renderId: string;
372
+ readonly hostName?: string;
373
+ readonly hostSessionId?: string;
374
+ readonly createdAt: string;
375
+ readonly lastActivityAt: string;
376
+ readonly status: string;
377
+ readonly wsToken?: string;
378
+ readonly wsTokenExpiresAt?: string;
643
379
  }
644
380
  /**
645
381
  * CSP metadata copied from an MCP Apps resource declaration.
@@ -673,7 +409,7 @@ export interface McpAppsContainerDimensions {
673
409
  /**
674
410
  * Locator for the source of an embedded MCP App.
675
411
  *
676
- * Persists STABLE identity (not a raw URL) so session state survives
412
+ * Persists STABLE identity (not a raw URL) so render state survives
677
413
  * source-server endpoint changes. The runtime `ConnectorRegistry`
678
414
  * resolves `connectorId` to the actual endpoint at render time.
679
415
  */
@@ -688,22 +424,21 @@ export interface McpAppsSource {
688
424
  readonly resourceUri: string;
689
425
  }
690
426
  /**
691
- * Stack-item variant: an embedded third-party MCP App iframe.
427
+ * Render variant: an embedded third-party MCP App iframe.
692
428
  *
693
429
  * **Locator-oriented, not content-oriented.** Persisted state carries
694
430
  * `source` (connector identity) + declared CSP/permissions/dimensions
695
- * metadata; resource BYTES are not stored in session state by default.
431
+ * metadata; resource BYTES are not stored in render state by default.
696
432
  * The `@ggui-ai/mcp-server` resource-proxy route fetches the bytes
697
433
  * on-demand via `resources/read` against the source server.
698
434
  *
699
- * **Union safety.** Fields that exist on the {@link StackItem}
700
- * (generated / native component) variant are declared here as
701
- * `?: never` so consumers that access them via optional chaining on
702
- * `SessionStackEntry` still typecheck cleanly. Those fields semantically
703
- * DO NOT exist on McpAppsStackItem — the `?: never` typing encodes the
704
- * "structurally absent" guarantee.
435
+ * **Union safety.** Fields that exist on the {@link ComponentRender}
436
+ * variant are declared here as `?: never` so consumers that access them
437
+ * via optional chaining on `Render` still typecheck cleanly. Those
438
+ * fields semantically DO NOT exist on McpAppsRender — the `?: never`
439
+ * typing encodes the "structurally absent" guarantee.
705
440
  */
706
- export interface McpAppsStackItem {
441
+ export interface McpAppsRender {
707
442
  /** Discriminator — required on this variant. */
708
443
  readonly type: 'mcpApps';
709
444
  readonly id: string;
@@ -717,9 +452,9 @@ export interface McpAppsStackItem {
717
452
  readonly containerDimensions?: McpAppsContainerDimensions;
718
453
  /**
719
454
  * Optional integrity pin — sha256 of the resource bytes computed at
720
- * push time. The resource-proxy route verifies the re-fetched
721
- * content against this hash; a mismatch breaks the stack item
722
- * LOUDLY rather than silently serving mutated content.
455
+ * render time. The resource-proxy route verifies the re-fetched
456
+ * content against this hash; a mismatch breaks the render LOUDLY
457
+ * rather than silently serving mutated content.
723
458
  */
724
459
  readonly resourceHash?: string;
725
460
  /**
@@ -745,18 +480,18 @@ export interface McpAppsStackItem {
745
480
  readonly clientCapabilities?: never;
746
481
  }
747
482
  /**
748
- * Type guard: narrows a `SessionStackEntry` (or unknown) to
749
- * {@link McpAppsStackItem}. Uses the discriminator.
483
+ * Type guard: narrows a `Render` (or unknown) to {@link McpAppsRender}.
484
+ * Uses the discriminator.
750
485
  */
751
- export declare function isMcpAppsStackItem(entry: unknown): entry is McpAppsStackItem;
486
+ export declare function isMcpAppsRender(entry: unknown): entry is McpAppsRender;
752
487
  /**
753
- * Structural validator for an `McpAppsStackItem` — not a Zod schema
488
+ * Structural validator for an `McpAppsRender` — not a Zod schema
754
489
  * so we don't force a Zod dependency here. Returns null on failure
755
490
  * (caller maps to an appropriate error code). Required when accepting
756
491
  * one over the wire from an agent: the discriminator alone isn't
757
492
  * enough.
758
493
  */
759
- export declare function validateMcpAppsStackItem(input: unknown): McpAppsStackItem | null;
494
+ export declare function validateMcpAppsRender(input: unknown): McpAppsRender | null;
760
495
  /**
761
496
  * Lifecycle states the renderer transitions through inside an MCP Apps
762
497
  * iframe. Closed union — adding a new state is a protocol-version-
@@ -788,7 +523,7 @@ export declare function validateMcpAppsStackItem(input: unknown): McpAppsStackIt
788
523
  * A host that observes only `mounting` and never a follow-up state
789
524
  * has a renderer that crashed before posting code-ready/error.
790
525
  * - `code-ready` — happy-path terminal state. Bundle evaluated, React
791
- * tree mounted, WS connected, first stack ack folded. Equivalent of
526
+ * tree mounted, WS connected, first render ack folded. Equivalent of
792
527
  * the in-iframe `data-ggui-status="connected"`.
793
528
  * - `error` — terminal failure. Pairs with the existing
794
529
  * `ggui:bootstrap-failed` postMessage envelope which carries the
@@ -809,9 +544,9 @@ export type McpAppLifecycleState = 'mounting' | 'code-ready' | 'error' | 'discon
809
544
  *
810
545
  * Fields:
811
546
  * - `state` — required. The lifecycle state being entered.
812
- * - `stackItemId` — optional. When present, the lifecycle pertains
813
- * to a specific stack item (per-card iframes via single-item
814
- * mode). Absent → whole-renderer lifecycle.
547
+ * - `renderId` — optional. When present, the lifecycle pertains to a
548
+ * specific render (per-card iframes via single-item mode).
549
+ * Absent → whole-renderer lifecycle.
815
550
  * - `error` — optional, only meaningful when `state === 'error'`.
816
551
  * Mirrors the `ggui:bootstrap-failed` postMessage envelope's
817
552
  * `reason` + `message` so a single `ggui:lifecycle` listener can
@@ -830,7 +565,7 @@ export type McpAppLifecycleState = 'mounting' | 'code-ready' | 'error' | 'discon
830
565
  */
831
566
  export interface McpAppLifecycleEvent {
832
567
  readonly state: McpAppLifecycleState;
833
- readonly stackItemId?: string;
568
+ readonly renderId?: string;
834
569
  readonly error?: {
835
570
  readonly code: string;
836
571
  readonly message: string;
@@ -914,24 +649,13 @@ export declare const MCP_APP_LIFECYCLE_STATES: readonly McpAppLifecycleState[];
914
649
  * - Outer envelope is an object with `type === 'ggui:lifecycle'`.
915
650
  * - `event` is an object with `state` matching {@link
916
651
  * MCP_APP_LIFECYCLE_STATES}.
917
- * - If `stackItemId` is present, it is a non-empty string.
652
+ * - If `renderId` is present, it is a non-empty string.
918
653
  * - If `error` is present, it is an object with string `code` +
919
654
  * `message`.
920
655
  *
921
656
  * @public
922
657
  */
923
658
  export declare function isMcpAppLifecycleMessage(message: unknown): message is McpAppLifecycleMessage;
924
- /**
925
- * Type guard for recognizing a `ggui_push` result `_meta` that carries a
926
- * bootstrap block. Handy for views / tests that receive the raw result.
927
- *
928
- * Validates the three-mode discriminator: a well-formed bootstrap MUST
929
- * carry `runtimeUrl` + `sessionId` + `appId` AND at least one of
930
- * `{wsUrl, codeUrl, kind}` as a non-empty string. Live mode
931
- * additionally requires `token` to pair with `wsUrl` — half-live
932
- * (one without the other) is rejected as MALFORMED.
933
- */
934
- export declare function hasPushBootstrapMeta(meta: unknown): meta is PushResultMeta;
935
659
  /**
936
660
  * Discriminator for the user-action envelope delivered via
937
661
  * `ggui_runtime_submit_action` over the MCP Apps host-relay path
@@ -957,7 +681,7 @@ export declare function hasPushBootstrapMeta(meta: unknown): meta is PushResultM
957
681
  * Audit is **fail-soft** at the client: if the `tools/call` envelope
958
682
  * fails to deliver (host rejects, postMessage on detached parent), the
959
683
  * primary host effect MUST still proceed. The audit miss surfaces as a
960
- * diagnostic on the operator side (gap in the SessionInspector activity
684
+ * diagnostic on the operator side (gap in the RenderInspector activity
961
685
  * row), not as a user-facing failure. This mirrors today's `dispatch`
962
686
  * audit-fire posture so semantics stay uniform.
963
687
  *
@@ -1023,21 +747,26 @@ export type SubmitActionEnvelope = {
1023
747
  * Per-kind semantics:
1024
748
  *
1025
749
  * - `kind === 'dispatch'`: server appends a consume-entry onto the
1026
- * stackItem-keyed pending-events pipe (`{type:'action', stackItemId,
750
+ * render-keyed pending-events pipe (`{type:'action', renderId,
1027
751
  * intent, actionData, uiContext, actionId, firedAt}`) so the agent's
1028
- * `ggui_consume` long-poll unblocks in the same chat turn. When the
1029
- * pipe is closed/missing (popped/closed/never opened), the handler
1030
- * returns `{ok:false, code:'PIPE_NOT_FOUND'}` and the iframe-runtime
1031
- * falls through to a `ui/message` envelope carrying
1032
- * `_meta.ggui.userAction` (see {@link GguiUserActionMeta}) so the
1033
- * gesture still reaches the agent on its next turn.
752
+ * `ggui_consume` long-poll unblocks in the same chat turn. The
753
+ * handler's response carries `consumerPresent` — whether a
754
+ * `ggui_consume` long-poll is currently listening on this render's
755
+ * pipe. When `consumerPresent === false` (no loop is listening —
756
+ * e.g. the agent's persistent consume loop ended after a page
757
+ * reload), the iframe-runtime ALSO emits a `ui/message` doorbell
758
+ * carrying `content[0]._meta["ai.ggui/userAction"]` (see
759
+ * {@link GguiUserActionMeta}) so a fresh agent turn calls
760
+ * `ggui_consume({renderId})` to drain the just-enqueued gesture.
761
+ * The doorbell is a PURE POINTER — the gesture stays solely on the
762
+ * pipe, making the action exactly-once.
1034
763
  * - `kind ∈ {'openLink','requestDisplayMode'}`: pure audit — the
1035
764
  * user-visible host effect already fired iframe-side via
1036
765
  * `ui/open-link` / `ui/request-display-mode`. The server records
1037
- * the gesture for the SessionInspector feed.
766
+ * the gesture for the RenderInspector feed.
1038
767
  *
1039
768
  * Required fields:
1040
- * - `sessionId` / `appId`: bootstrap-issued; server cross-checks.
769
+ * - `renderId` / `appId`: bootstrap-issued; server cross-checks.
1041
770
  * - `actionId`: 8-hex correlation hash (FNV-1a of intent + data + firedAt
1042
771
  * for `dispatch`, kind + payload + firedAt for the host-control kinds).
1043
772
  * Lets the host LLM cross-verify a `[ggui:pending-action]` context entry
@@ -1050,17 +779,7 @@ export type SubmitActionEnvelope = {
1050
779
  * shape — see {@link SubmitActionEnvelope}.
1051
780
  */
1052
781
  export type GguiSubmitActionInput = SubmitActionEnvelope & {
1053
- readonly sessionId: string;
1054
- /**
1055
- * Active stack item id. Optional because the iframe-runtime boots
1056
- * into a stack-item context only when the host minted one via
1057
- * `ggui_push` — boot scenarios like system-cards or pre-push
1058
- * provisional previews don't carry one. Required when
1059
- * `kind === 'dispatch'` (the kind that needs to land in the
1060
- * stackItem-keyed pending-event pipe); the server-side handler
1061
- * rejects dispatch envelopes missing this field.
1062
- */
1063
- readonly stackItemId?: string;
782
+ readonly renderId: string;
1064
783
  readonly appId: string;
1065
784
  readonly actionId: string;
1066
785
  readonly firedAt: string;
@@ -1085,134 +804,58 @@ export declare const SUBMIT_ACTION_KINDS: readonly ["dispatch", "openLink", "req
1085
804
  */
1086
805
  export declare function isGguiSubmitActionInput(value: unknown): value is GguiSubmitActionInput;
1087
806
  /**
1088
- * Discriminator the iframe-runtime stamps on a `ui/message` envelope's
1089
- * `params._meta.ggui.userAction` when a user gesture inside a ggui-
1090
- * rendered iframe needs to flow to the agent through chat (rather than
1091
- * direct WS drain via `ggui_consume`).
1092
- *
1093
- * Discriminated by `kind`:
1094
- *
1095
- * - **`'queued'`** — pipe HAS the event; agent should call
1096
- * `ggui_consume({stackItemId})` to drain. The prepared call lives
1097
- * in `nextStep` as `{tool: 'ggui_consume', args: {stackItemId}}`
1098
- * so the SDK can dispatch verbatim without arg-construction.
1099
- *
1100
- * - **`'inline'`** — pipe is GONE (popped / session closed / never
1101
- * opened). Action data is carried inline in `payload`. The agent
1102
- * MUST act on this directly; calling `ggui_consume` for this
1103
- * `stackItemId` would return empty.
1104
- *
1105
- * The accompanying `ui/message` text mirrors the structure in human-
1106
- * readable form so agnostic LLMs without `_meta` awareness still get
1107
- * the right instruction.
1108
- *
1109
- * **Presence is the fingerprint** — agnostic hosts ignore the field;
1110
- * ggui-aware consumers route via {@link isGguiUserActionMeta}.
1111
- *
1112
- * Unified shape with a `kind` discriminator (`queued` | `inline`)
1113
- * parallels the protocol's other discriminated unions, e.g.
1114
- * {@link SubmitActionEnvelope}'s `kind`.
1115
- *
1116
- * @public
1117
- */
1118
- export type GguiUserActionMeta = QueuedUserActionMeta | InlineUserActionMeta;
1119
- /**
1120
- * `_meta.ggui.userAction` variant — pipe has the event; agent dispatches
1121
- * the prepared `ggui_consume` call to drain. See {@link GguiUserActionMeta}.
807
+ * `content[0]._meta["ai.ggui/userAction"]` — a PURE DOORBELL.
808
+ *
809
+ * Spec-canonical extension point: MCP Apps closes `params._meta` via
810
+ * `additionalProperties: false`, but each content block has its own
811
+ * open `_meta` record (per the base MCP spec). The `ai.ggui/*` key
812
+ * prefix matches our other protocol extensions
813
+ * (`ai.ggui/render`, `ai.ggui/bootstrap`, etc.).
814
+ *
815
+ * Stamped by the iframe-runtime on a `ui/message` envelope when a user
816
+ * gesture needs to wake the agent because no `ggui_consume` long-poll is
817
+ * currently listening (the agent's persistent consume loop has ended —
818
+ * e.g. after a page reload). The gesture itself was ALREADY enqueued onto
819
+ * the render's server-side pending-event pipe by the iframe's
820
+ * `ggui_runtime_submit_action` call (relayed by the host) BEFORE this
821
+ * notification fired; this slice's only job is to make a fresh agent turn
822
+ * call `ggui_consume({renderId})` to drain it.
823
+ *
824
+ * SINGLE SOURCE OF TRUTH: the pending-event queue. This slice carries ONLY
825
+ * a pointer to the render whose queue holds the gesture — never the action
826
+ * payload. The agent retrieves the action EXCLUSIVELY via `ggui_consume`.
827
+ * Carrying the payload here would let the agent both act on it AND drain
828
+ * the queue = a double-trigger; the pointer-only shape makes the action
829
+ * exactly-once.
830
+ *
831
+ * `intent` is metadata (which `actionSpec[*]` entry fired) — NOT the
832
+ * actionable data. The agent can't react meaningfully on `intent` alone
833
+ * (it lacks the `actionData` payload), so its presence doesn't tempt a
834
+ * pre-consume action.
835
+ *
836
+ * **The directive lives in the `ui/message` TEXT, not here.** The
837
+ * iframe-runtime authors a `ui/message` whose human-readable text
838
+ * carries the full "call `ggui_consume`" directive — that text is what
839
+ * EVERY host (claude.ai, chatgpt.com, ggui-aware SDKs) forwards to the
840
+ * model. This `_meta` slice is the OPTIONAL structured mirror for
841
+ * ggui-aware programmatic consumers; an `_meta`-agnostic host ignores
842
+ * it and acts on the text alone. No part of the loop depends on a
843
+ * server-side parse of this slice.
1122
844
  *
1123
845
  * @public
1124
846
  */
1125
- export interface QueuedUserActionMeta {
1126
- readonly kind: 'queued';
1127
- /**
1128
- * Human-readable one-liner summary for logs / SDK debug surfaces.
1129
- * Mirrors the chat-visible text but as a structured field so
1130
- * consumers don't need to parse natural language.
1131
- */
847
+ export interface GguiUserActionMeta {
848
+ readonly kind: 'user-action';
1132
849
  readonly description: string;
1133
- /** Stack item the gesture targeted. */
1134
- readonly stackItemId: string;
1135
- /** 8-hex FNV-1a correlation id of the gesture. */
850
+ readonly renderId: string;
1136
851
  readonly actionId: string;
1137
- /** ISO 8601 UTC timestamp of the gesture (iframe local clock). */
1138
852
  readonly submittedAt: string;
1139
- /** Which `actionSpec[*]` entry the iframe dispatched against. */
1140
853
  readonly intent: string;
1141
- /**
1142
- * Prepared tool call the agent SHOULD dispatch verbatim. Embeds the
1143
- * `stackItemId` so the SDK doesn't have to thread it manually —
1144
- * reduces "wrong/missing args" failure modes.
1145
- */
1146
854
  readonly nextStep: {
1147
855
  readonly tool: 'ggui_consume';
1148
856
  readonly args: {
1149
- readonly stackItemId: string;
857
+ readonly renderId: string;
1150
858
  };
1151
859
  };
1152
860
  }
1153
- /**
1154
- * `_meta.ggui.userAction` variant — pipe is gone; action + ui context
1155
- * delivered inline. Agent acts on `payload` directly; MUST NOT call
1156
- * `ggui_consume` for `stackItemId` (no pipe to drain).
1157
- *
1158
- * `nextStep` is optional — when the original `actionSpec[intent]` declared
1159
- * a `nextStep` (the bound agent tool), it's surfaced here as a string
1160
- * hint so the LLM has a strong steer toward the right tool. When the
1161
- * contract author left `nextStep` undeclared, the agent is fully free
1162
- * to choose how to react.
1163
- *
1164
- * @public
1165
- */
1166
- export interface InlineUserActionMeta {
1167
- readonly kind: 'inline';
1168
- /**
1169
- * Human-readable one-liner summary for logs / SDK debug surfaces.
1170
- */
1171
- readonly description: string;
1172
- /** Stack item the gesture targeted. */
1173
- readonly stackItemId: string;
1174
- /** 8-hex FNV-1a correlation id of the gesture. */
1175
- readonly actionId: string;
1176
- /** ISO 8601 UTC timestamp of the gesture (iframe local clock). */
1177
- readonly submittedAt: string;
1178
- /** Which `actionSpec[*]` entry the iframe dispatched against. */
1179
- readonly intent: string;
1180
- /**
1181
- * Both halves of the gesture, captured atomically at gesture time:
1182
- *
1183
- * - `actionData` — typed payload satisfying `actionSpec[intent].schema`.
1184
- * `null` for no-payload gestures (bare button click).
1185
- * - `uiContext` — snapshot of the iframe's contextSpec values at
1186
- * the moment the user fired the gesture. Typed by
1187
- * the contract's `contextSpec`.
1188
- *
1189
- * The pair is the SEMANTIC UNIT — what the user did AND what they
1190
- * were looking at when they did it. Captured at gesture time (not
1191
- * drain time) for honest history.
1192
- */
1193
- readonly payload: {
1194
- readonly actionData: JsonValue | null;
1195
- readonly uiContext: JsonObject;
1196
- };
1197
- /**
1198
- * Optional hint: the agent tool the original `actionSpec[intent].nextStep`
1199
- * declared. Present when the contract bound this intent to a specific
1200
- * tool; absent when the author left it free. The agent reads this as
1201
- * a strong suggestion, not a binding directive (in the inline case
1202
- * the LLM composes the call, including any context-derived args).
1203
- */
1204
- readonly nextStep?: string;
1205
- }
1206
- /**
1207
- * Type guard for {@link GguiUserActionMeta}. Validates the
1208
- * discriminated shape on a `ui/message` envelope's
1209
- * `params._meta.ggui.userAction` field.
1210
- *
1211
- * Designed for `_meta.ggui.userAction`-aware consumers (sample agent
1212
- * dispatcher, e2e assertions, future SDKs) to route deterministically
1213
- * without speculative shape coercion.
1214
- *
1215
- * @public
1216
- */
1217
- export declare function isGguiUserActionMeta(meta: unknown): meta is GguiUserActionMeta;
1218
861
  //# sourceMappingURL=mcp-apps.d.ts.map