@ggui-ai/protocol 0.1.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (222) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +46 -0
  3. package/dist/bridge/invoke-agent.d.ts +65 -0
  4. package/dist/bridge/invoke-agent.d.ts.map +1 -0
  5. package/dist/bridge/invoke-agent.js +113 -0
  6. package/dist/envelope-adapters.d.ts +24 -0
  7. package/dist/envelope-adapters.d.ts.map +1 -0
  8. package/dist/envelope-adapters.js +14 -0
  9. package/dist/envelopes/builders.d.ts +145 -0
  10. package/dist/envelopes/builders.d.ts.map +1 -0
  11. package/dist/envelopes/builders.js +113 -0
  12. package/dist/errors/unknown-permission-name.d.ts +12 -0
  13. package/dist/errors/unknown-permission-name.d.ts.map +1 -0
  14. package/dist/errors/unknown-permission-name.js +29 -0
  15. package/dist/errors/version-mismatch.d.ts +55 -0
  16. package/dist/errors/version-mismatch.d.ts.map +1 -0
  17. package/dist/errors/version-mismatch.js +52 -0
  18. package/dist/gadgets/resolve-contract-gadgets.d.ts +93 -0
  19. package/dist/gadgets/resolve-contract-gadgets.d.ts.map +1 -0
  20. package/dist/gadgets/resolve-contract-gadgets.js +119 -0
  21. package/dist/gadgets/stdlib-gadgets.d.ts +43 -0
  22. package/dist/gadgets/stdlib-gadgets.d.ts.map +1 -0
  23. package/dist/gadgets/stdlib-gadgets.js +161 -0
  24. package/dist/iframe-bridge.d.ts +63 -0
  25. package/dist/iframe-bridge.d.ts.map +1 -0
  26. package/dist/iframe-bridge.js +166 -0
  27. package/dist/index.d.ts +62 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +79 -0
  30. package/dist/integrations/mcp-apps.d.ts +1218 -0
  31. package/dist/integrations/mcp-apps.d.ts.map +1 -0
  32. package/dist/integrations/mcp-apps.js +427 -0
  33. package/dist/navigation/index.d.ts +3 -0
  34. package/dist/navigation/index.d.ts.map +1 -0
  35. package/dist/navigation/index.js +1 -0
  36. package/dist/navigation/stack-navigation.d.ts +55 -0
  37. package/dist/navigation/stack-navigation.d.ts.map +1 -0
  38. package/dist/navigation/stack-navigation.js +80 -0
  39. package/dist/recommended-prompts.d.ts +56 -0
  40. package/dist/recommended-prompts.d.ts.map +1 -0
  41. package/dist/recommended-prompts.js +55 -0
  42. package/dist/registry/blueprint-key.d.ts +9 -0
  43. package/dist/registry/blueprint-key.d.ts.map +1 -0
  44. package/dist/registry/blueprint-key.js +28 -0
  45. package/dist/registry/canonicalize-contract.d.ts +35 -0
  46. package/dist/registry/canonicalize-contract.d.ts.map +1 -0
  47. package/dist/registry/canonicalize-contract.js +166 -0
  48. package/dist/registry/summarize-contract.d.ts +46 -0
  49. package/dist/registry/summarize-contract.d.ts.map +1 -0
  50. package/dist/registry/summarize-contract.js +63 -0
  51. package/dist/schema-learning/derive-contract.d.ts +67 -0
  52. package/dist/schema-learning/derive-contract.d.ts.map +1 -0
  53. package/dist/schema-learning/derive-contract.js +117 -0
  54. package/dist/schema-learning/merge.d.ts +32 -0
  55. package/dist/schema-learning/merge.d.ts.map +1 -0
  56. package/dist/schema-learning/merge.js +146 -0
  57. package/dist/schemas/blueprint.d.ts +32 -0
  58. package/dist/schemas/blueprint.d.ts.map +1 -0
  59. package/dist/schemas/blueprint.js +92 -0
  60. package/dist/schemas/data-contract.d.ts +750 -0
  61. package/dist/schemas/data-contract.d.ts.map +1 -0
  62. package/dist/schemas/data-contract.js +663 -0
  63. package/dist/schemas/gadget-name-grammar.d.ts +29 -0
  64. package/dist/schemas/gadget-name-grammar.d.ts.map +1 -0
  65. package/dist/schemas/gadget-name-grammar.js +28 -0
  66. package/dist/schemas/handshake-suggestion.d.ts +46 -0
  67. package/dist/schemas/handshake-suggestion.d.ts.map +1 -0
  68. package/dist/schemas/handshake-suggestion.js +107 -0
  69. package/dist/schemas/invoke.d.ts +337 -0
  70. package/dist/schemas/invoke.d.ts.map +1 -0
  71. package/dist/schemas/invoke.js +169 -0
  72. package/dist/schemas/mcp.d.ts +301 -0
  73. package/dist/schemas/mcp.d.ts.map +1 -0
  74. package/dist/schemas/mcp.js +373 -0
  75. package/dist/schemas/ops-blueprint.d.ts +176 -0
  76. package/dist/schemas/ops-blueprint.d.ts.map +1 -0
  77. package/dist/schemas/ops-blueprint.js +259 -0
  78. package/dist/schemas/sync-check.d.ts +11 -0
  79. package/dist/schemas/sync-check.d.ts.map +1 -0
  80. package/dist/schemas/sync-check.js +60 -0
  81. package/dist/screen-blueprints/define.d.ts +22 -0
  82. package/dist/screen-blueprints/define.d.ts.map +1 -0
  83. package/dist/screen-blueprints/define.js +3 -0
  84. package/dist/screen-blueprints/index.d.ts +4 -0
  85. package/dist/screen-blueprints/index.d.ts.map +1 -0
  86. package/dist/screen-blueprints/index.js +3 -0
  87. package/dist/screen-blueprints/match.d.ts +35 -0
  88. package/dist/screen-blueprints/match.d.ts.map +1 -0
  89. package/dist/screen-blueprints/match.js +51 -0
  90. package/dist/screen-blueprints/types.d.ts +164 -0
  91. package/dist/screen-blueprints/types.d.ts.map +1 -0
  92. package/dist/screen-blueprints/types.js +1 -0
  93. package/dist/stream/stream-parser.d.ts +62 -0
  94. package/dist/stream/stream-parser.d.ts.map +1 -0
  95. package/dist/stream/stream-parser.js +199 -0
  96. package/dist/transport/websocket.d.ts +178 -0
  97. package/dist/transport/websocket.d.ts.map +1 -0
  98. package/dist/transport/websocket.js +1 -0
  99. package/dist/types/app-config.d.ts +61 -0
  100. package/dist/types/app-config.d.ts.map +1 -0
  101. package/dist/types/app-config.js +1 -0
  102. package/dist/types/auth.d.ts +61 -0
  103. package/dist/types/auth.d.ts.map +1 -0
  104. package/dist/types/auth.js +1 -0
  105. package/dist/types/blueprint.d.ts +206 -0
  106. package/dist/types/blueprint.d.ts.map +1 -0
  107. package/dist/types/blueprint.js +1 -0
  108. package/dist/types/canvas-lifecycle.d.ts +105 -0
  109. package/dist/types/canvas-lifecycle.d.ts.map +1 -0
  110. package/dist/types/canvas-lifecycle.js +38 -0
  111. package/dist/types/capabilities.d.ts +40 -0
  112. package/dist/types/capabilities.d.ts.map +1 -0
  113. package/dist/types/capabilities.js +19 -0
  114. package/dist/types/contract-inference.d.ts +401 -0
  115. package/dist/types/contract-inference.d.ts.map +1 -0
  116. package/dist/types/contract-inference.js +44 -0
  117. package/dist/types/credential.d.ts +41 -0
  118. package/dist/types/credential.d.ts.map +1 -0
  119. package/dist/types/credential.js +32 -0
  120. package/dist/types/data-bindings.d.ts +322 -0
  121. package/dist/types/data-bindings.d.ts.map +1 -0
  122. package/dist/types/data-bindings.js +29 -0
  123. package/dist/types/data-contract.d.ts +1296 -0
  124. package/dist/types/data-contract.d.ts.map +1 -0
  125. package/dist/types/data-contract.js +111 -0
  126. package/dist/types/events.d.ts +182 -0
  127. package/dist/types/events.d.ts.map +1 -0
  128. package/dist/types/events.js +8 -0
  129. package/dist/types/feedback.d.ts +24 -0
  130. package/dist/types/feedback.d.ts.map +1 -0
  131. package/dist/types/feedback.js +7 -0
  132. package/dist/types/gadget.d.ts +121 -0
  133. package/dist/types/gadget.d.ts.map +1 -0
  134. package/dist/types/gadget.js +24 -0
  135. package/dist/types/handshake-suggestion.d.ts +264 -0
  136. package/dist/types/handshake-suggestion.d.ts.map +1 -0
  137. package/dist/types/handshake-suggestion.js +70 -0
  138. package/dist/types/host-context.d.ts +163 -0
  139. package/dist/types/host-context.d.ts.map +1 -0
  140. package/dist/types/host-context.js +142 -0
  141. package/dist/types/interface-context.d.ts +105 -0
  142. package/dist/types/interface-context.d.ts.map +1 -0
  143. package/dist/types/interface-context.js +115 -0
  144. package/dist/types/invoke.d.ts +28 -0
  145. package/dist/types/invoke.d.ts.map +1 -0
  146. package/dist/types/invoke.js +7 -0
  147. package/dist/types/live-channel.d.ts +613 -0
  148. package/dist/types/live-channel.d.ts.map +1 -0
  149. package/dist/types/live-channel.js +1 -0
  150. package/dist/types/llm.d.ts +61 -0
  151. package/dist/types/llm.d.ts.map +1 -0
  152. package/dist/types/llm.js +186 -0
  153. package/dist/types/mcp-proxy.d.ts +67 -0
  154. package/dist/types/mcp-proxy.d.ts.map +1 -0
  155. package/dist/types/mcp-proxy.js +46 -0
  156. package/dist/types/mcp.d.ts +637 -0
  157. package/dist/types/mcp.d.ts.map +1 -0
  158. package/dist/types/mcp.js +30 -0
  159. package/dist/types/openrouter-models.d.ts +22 -0
  160. package/dist/types/openrouter-models.d.ts.map +1 -0
  161. package/dist/types/openrouter-models.js +4843 -0
  162. package/dist/types/region.d.ts +26 -0
  163. package/dist/types/region.d.ts.map +1 -0
  164. package/dist/types/region.js +36 -0
  165. package/dist/types/session.d.ts +419 -0
  166. package/dist/types/session.d.ts.map +1 -0
  167. package/dist/types/session.js +1 -0
  168. package/dist/types/thread.d.ts +207 -0
  169. package/dist/types/thread.d.ts.map +1 -0
  170. package/dist/types/thread.js +57 -0
  171. package/dist/types/ui-generator.d.ts +100 -0
  172. package/dist/types/ui-generator.d.ts.map +1 -0
  173. package/dist/types/ui-generator.js +53 -0
  174. package/dist/validation/ajv-runtime.d.ts +140 -0
  175. package/dist/validation/ajv-runtime.d.ts.map +1 -0
  176. package/dist/validation/ajv-runtime.js +452 -0
  177. package/dist/validation/content-hash.d.ts +3 -0
  178. package/dist/validation/content-hash.d.ts.map +1 -0
  179. package/dist/validation/content-hash.js +21 -0
  180. package/dist/validation/contract-validator.d.ts +244 -0
  181. package/dist/validation/contract-validator.d.ts.map +1 -0
  182. package/dist/validation/contract-validator.js +711 -0
  183. package/dist/validation/cross-references.d.ts +105 -0
  184. package/dist/validation/cross-references.d.ts.map +1 -0
  185. package/dist/validation/cross-references.js +164 -0
  186. package/dist/validation/hygiene-rules.d.ts +250 -0
  187. package/dist/validation/hygiene-rules.d.ts.map +1 -0
  188. package/dist/validation/hygiene-rules.js +564 -0
  189. package/dist/validation/lint-contract.d.ts +130 -0
  190. package/dist/validation/lint-contract.d.ts.map +1 -0
  191. package/dist/validation/lint-contract.js +225 -0
  192. package/dist/validation/name-invariants.d.ts +117 -0
  193. package/dist/validation/name-invariants.d.ts.map +1 -0
  194. package/dist/validation/name-invariants.js +172 -0
  195. package/dist/validation/reserved-channels.d.ts +156 -0
  196. package/dist/validation/reserved-channels.d.ts.map +1 -0
  197. package/dist/validation/reserved-channels.js +356 -0
  198. package/dist/validation/resolve-stream-channel.d.ts +78 -0
  199. package/dist/validation/resolve-stream-channel.d.ts.map +1 -0
  200. package/dist/validation/resolve-stream-channel.js +64 -0
  201. package/dist/validation/sanitize-error.d.ts +46 -0
  202. package/dist/validation/sanitize-error.d.ts.map +1 -0
  203. package/dist/validation/sanitize-error.js +88 -0
  204. package/dist/validation/schema-compat-invariants.d.ts +140 -0
  205. package/dist/validation/schema-compat-invariants.d.ts.map +1 -0
  206. package/dist/validation/schema-compat-invariants.js +220 -0
  207. package/dist/validation/schema-meta-validation.d.ts +60 -0
  208. package/dist/validation/schema-meta-validation.d.ts.map +1 -0
  209. package/dist/validation/schema-meta-validation.js +131 -0
  210. package/dist/validation/schema-subset.d.ts +165 -0
  211. package/dist/validation/schema-subset.d.ts.map +1 -0
  212. package/dist/validation/schema-subset.js +295 -0
  213. package/dist/validation/ui-security.d.ts +54 -0
  214. package/dist/validation/ui-security.d.ts.map +1 -0
  215. package/dist/validation/ui-security.js +138 -0
  216. package/dist/validation/zod-to-json-schema.d.ts +63 -0
  217. package/dist/validation/zod-to-json-schema.d.ts.map +1 -0
  218. package/dist/validation/zod-to-json-schema.js +126 -0
  219. package/dist/version.d.ts +1458 -0
  220. package/dist/version.d.ts.map +1 -0
  221. package/dist/version.js +1459 -0
  222. package/package.json +113 -0
@@ -0,0 +1,1218 @@
1
+ /**
2
+ * MCP Apps integration — outbound delivery types for ggui.
3
+ *
4
+ * This module is the **boundary** for MCP Apps outbound delivery. Anything
5
+ * MCP-Apps-specific that ggui exposes to the rest of the codebase lives
6
+ * here — never in `types/live-channel.ts`, `types/mcp.ts`, `types/session.ts`,
7
+ * or any other core module. Consumers opt in via the subpath import:
8
+ *
9
+ * ```ts
10
+ * import {
11
+ * MCP_APPS_UI_CAPABILITY,
12
+ * GGUI_SESSION_RESOURCE_URI,
13
+ * type GguiBootstrapMeta,
14
+ * } from '@ggui-ai/protocol/integrations/mcp-apps';
15
+ * ```
16
+ *
17
+ * The root `@ggui-ai/protocol` barrel does NOT re-export this module.
18
+ * That's the isolation rule: core protocol consumers that don't integrate
19
+ * with MCP Apps pay none of its weight, and the blast radius of any spec
20
+ * drift is bounded to callers that explicitly import from here.
21
+ *
22
+ * Core still carries two fields that make the bootstrap flow work —
23
+ * `SubscribePayload.bootstrap?: string` and `AckPayload.sessionToken?:
24
+ * string`. Those are deliberately framed as **general transport bootstrap
25
+ * credentials** (opaque strings), not MCP-Apps-specific. Any future
26
+ * bootstrap mechanism (short-code auto-login, signed-URL bootstrap, etc.)
27
+ * reuses the same slots.
28
+ */
29
+ import type { JsonObject, JsonSchema, JsonValue } from '../types/data-contract.js';
30
+ /**
31
+ * MCP capability name ggui servers advertise in their MCP `initialize`
32
+ * response capabilities when they implement the MCP Apps outbound path.
33
+ * Spec-canonical; MUST match the string the MCP Apps protocol publishes.
34
+ */
35
+ export declare const MCP_APPS_UI_CAPABILITY: "io.modelcontextprotocol/ui";
36
+ /**
37
+ * 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
+ * `_meta.ui.resourceUri`. No other ggui tool gets a resource URI —
40
+ * `ggui_push` is the single outbound entry point.
41
+ */
42
+ export declare const GGUI_SESSION_RESOURCE_URI: "ui://ggui/session";
43
+ /**
44
+ * MIME type for the `ui://ggui/session` resource. Per MCP Apps spec, UI
45
+ * resources carry the `text/html` base type with a `profile=mcp-app`
46
+ * parameter so hosts that don't support MCP Apps don't accidentally
47
+ * render them as plain HTML.
48
+ */
49
+ export declare const GGUI_SESSION_RESOURCE_MIME: "text/html;profile=mcp-app";
50
+ /**
51
+ * The single `_meta.ui.resourceUri` value ggui uses across every MCP Apps
52
+ * host surface. Exposed as a named constant so tool-declaration code,
53
+ * resource-serving code, and tests all agree on one spelling.
54
+ */
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";
58
+ /** Only `"model"` — outer agent can call, iframe views cannot. */
59
+ readonly visibility: readonly ["model"];
60
+ };
61
+ /**
62
+ * Visibility tag carried in `_meta.ui.visibility` on a tool declaration.
63
+ * Per MCP Apps spec, controls who can invoke the tool:
64
+ * - `"model"` — outer agent can call (default in practice)
65
+ * - `"app"` — only an MCP Apps view (iframe) can call, hidden from agent
66
+ */
67
+ export type McpAppsToolVisibility = 'model' | 'app';
68
+ /**
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
+ * ```
127
+ *
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
+ * ```
136
+ */
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
+ /**
586
+ * Precompiled, eval-free validators for a contract's runtime-validated
587
+ * specs — see {@link GguiBootstrapMeta.compiledValidators}.
588
+ *
589
+ * Each value is the SOURCE TEXT of an ES module whose `default` export
590
+ * is an Ajv validator function (`(data) => boolean`, carrying
591
+ * `.errors` after a run) — the output of `compileValidatorModule`.
592
+ * The iframe-runtime loads each via a `blob:` dynamic import.
593
+ *
594
+ * @public
595
+ */
596
+ export interface CompiledContractValidators {
597
+ /** Validator for inbound runtime props (`DataContract.propsSpec`). */
598
+ readonly props?: string;
599
+ /**
600
+ * Validators for outbound action envelopes, keyed by action name
601
+ * (`DataContract.actionSpec`).
602
+ */
603
+ readonly actions?: Readonly<Record<string, string>>;
604
+ /**
605
+ * Validators for inbound stream payloads, keyed by channel name
606
+ * (`DataContract.streamSpec`).
607
+ */
608
+ readonly streams?: Readonly<Record<string, string>>;
609
+ /**
610
+ * Validators for inbound context-slot values, keyed by slot name
611
+ * (`DataContract.contextSpec`).
612
+ */
613
+ readonly context?: Readonly<Record<string, string>>;
614
+ }
615
+ /**
616
+ * Derives the PascalCase Context name from a contextSpec slot key.
617
+ * E.g., `currentStep` → `CurrentStepContext`. Consumed by the server
618
+ * (when populating bootstrap.contextSlots) and the iframe-runtime
619
+ * boilerplate (when generating destructuring lines).
620
+ *
621
+ * Edge cases:
622
+ * - Empty input → `'Context'` (caller-fault path; documented for
623
+ * determinism).
624
+ * - Single-character input → `<UPPER>Context` (e.g. `'a'` → `'AContext'`).
625
+ *
626
+ * @public
627
+ */
628
+ export declare function deriveContextName(slotKey: string): string;
629
+ /**
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.
638
+ */
639
+ export interface PushResultMeta {
640
+ readonly ggui: {
641
+ readonly bootstrap: GguiBootstrapMeta;
642
+ };
643
+ }
644
+ /**
645
+ * CSP metadata copied from an MCP Apps resource declaration.
646
+ * Spec-canonical field names — do NOT rename.
647
+ */
648
+ export interface McpAppsCsp {
649
+ readonly connectDomains?: string[];
650
+ readonly resourceDomains?: string[];
651
+ readonly frameDomains?: string[];
652
+ }
653
+ /**
654
+ * Permissions Policy metadata copied from an MCP Apps resource
655
+ * declaration. Spec-canonical field names — do NOT rename.
656
+ */
657
+ export interface McpAppsPermissions {
658
+ readonly camera?: boolean;
659
+ readonly microphone?: boolean;
660
+ readonly geolocation?: boolean;
661
+ readonly clipboardWrite?: boolean;
662
+ }
663
+ /**
664
+ * Container dimensions hint passed to the embedded iframe via the
665
+ * MCP Apps `ui/initialize` response.
666
+ */
667
+ export interface McpAppsContainerDimensions {
668
+ readonly height?: number;
669
+ readonly width?: number;
670
+ readonly maxHeight?: number;
671
+ readonly maxWidth?: number;
672
+ }
673
+ /**
674
+ * Locator for the source of an embedded MCP App.
675
+ *
676
+ * Persists STABLE identity (not a raw URL) so session state survives
677
+ * source-server endpoint changes. The runtime `ConnectorRegistry`
678
+ * resolves `connectorId` to the actual endpoint at render time.
679
+ */
680
+ export interface McpAppsSource {
681
+ /** Stable connector id declared in the app's connector registry. */
682
+ readonly connectorId: string;
683
+ /** Source-server tool whose call produced this UI; scope for
684
+ * `tools/call` proxying. */
685
+ readonly toolName: string;
686
+ /** `ui://` resource URI declared on the source tool's
687
+ * `_meta.ui.resourceUri`. */
688
+ readonly resourceUri: string;
689
+ }
690
+ /**
691
+ * Stack-item variant: an embedded third-party MCP App iframe.
692
+ *
693
+ * **Locator-oriented, not content-oriented.** Persisted state carries
694
+ * `source` (connector identity) + declared CSP/permissions/dimensions
695
+ * metadata; resource BYTES are not stored in session state by default.
696
+ * The `@ggui-ai/mcp-server` resource-proxy route fetches the bytes
697
+ * on-demand via `resources/read` against the source server.
698
+ *
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.
705
+ */
706
+ export interface McpAppsStackItem {
707
+ /** Discriminator — required on this variant. */
708
+ readonly type: 'mcpApps';
709
+ readonly id: string;
710
+ readonly createdAt: string;
711
+ readonly prompt?: string;
712
+ readonly description?: string;
713
+ readonly message?: string;
714
+ readonly source: McpAppsSource;
715
+ readonly csp?: McpAppsCsp;
716
+ readonly permissions?: McpAppsPermissions;
717
+ readonly containerDimensions?: McpAppsContainerDimensions;
718
+ /**
719
+ * 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.
723
+ */
724
+ readonly resourceHash?: string;
725
+ /**
726
+ * Bounded dev/cache optimization. When present, the proxy route MAY
727
+ * serve this inline instead of re-fetching via `resources/read`. NOT
728
+ * the canonical carrier — metadata persists, bytes don't. Use only
729
+ * for dev harnesses / offline replay.
730
+ */
731
+ readonly resourceContent?: string;
732
+ readonly componentCode?: never;
733
+ readonly props?: never;
734
+ readonly contentType?: never;
735
+ readonly schema?: never;
736
+ readonly subscription?: never;
737
+ readonly capabilities?: never;
738
+ readonly actions?: never;
739
+ readonly quality?: never;
740
+ readonly error?: never;
741
+ readonly streamSpec?: never;
742
+ readonly propsSpec?: never;
743
+ readonly actionSpec?: never;
744
+ readonly contextSpec?: never;
745
+ readonly clientCapabilities?: never;
746
+ }
747
+ /**
748
+ * Type guard: narrows a `SessionStackEntry` (or unknown) to
749
+ * {@link McpAppsStackItem}. Uses the discriminator.
750
+ */
751
+ export declare function isMcpAppsStackItem(entry: unknown): entry is McpAppsStackItem;
752
+ /**
753
+ * Structural validator for an `McpAppsStackItem` — not a Zod schema
754
+ * so we don't force a Zod dependency here. Returns null on failure
755
+ * (caller maps to an appropriate error code). Required when accepting
756
+ * one over the wire from an agent: the discriminator alone isn't
757
+ * enough.
758
+ */
759
+ export declare function validateMcpAppsStackItem(input: unknown): McpAppsStackItem | null;
760
+ /**
761
+ * Lifecycle states the renderer transitions through inside an MCP Apps
762
+ * iframe. Closed union — adding a new state is a protocol-version-
763
+ * eligible change. Hosts that don't recognise a state MUST treat it as
764
+ * a no-op (don't mirror it, don't crash).
765
+ *
766
+ * State machine:
767
+ *
768
+ * ```
769
+ * ┌────────────┐
770
+ * (iframe boot) │ mounting │
771
+ * └─────┬──────┘
772
+ * │ bundle evaluated +
773
+ * │ React tree mounted +
774
+ * │ WS handshake completed
775
+ * ▼
776
+ * ┌─────────────┐
777
+ * │ code-ready │◀────── (terminal happy state)
778
+ * └──┬─────┬────┘
779
+ * │ │
780
+ * (WS close) │ │ (eval / mount / handshake throw)
781
+ * ▼ ▼
782
+ * ┌──────────┐ ┌───────┐
783
+ * │disconnected│ │ error │
784
+ * └────────────┘ └───────┘
785
+ * ```
786
+ *
787
+ * - `mounting` — emitted ASAP after iframe boot (before bundle eval).
788
+ * A host that observes only `mounting` and never a follow-up state
789
+ * has a renderer that crashed before posting code-ready/error.
790
+ * - `code-ready` — happy-path terminal state. Bundle evaluated, React
791
+ * tree mounted, WS connected, first stack ack folded. Equivalent of
792
+ * the in-iframe `data-ggui-status="connected"`.
793
+ * - `error` — terminal failure. Pairs with the existing
794
+ * `ggui:bootstrap-failed` postMessage envelope which carries the
795
+ * typed reason; this lifecycle state is the COARSE outer-DOM signal
796
+ * ("renderer is not going to come up — give up waiting").
797
+ * - `disconnected` — non-terminal. WebSocket closed after a successful
798
+ * `code-ready`. The renderer MAY transition back to `code-ready` if
799
+ * reconnection succeeds (subscribe.ts owns the reconnect ladder);
800
+ * hosts that pin selectors on `code-ready` will re-resolve when it
801
+ * does.
802
+ *
803
+ * @public
804
+ */
805
+ export type McpAppLifecycleState = 'mounting' | 'code-ready' | 'error' | 'disconnected';
806
+ /**
807
+ * Lifecycle event payload shape. Carried inside an
808
+ * {@link McpAppLifecycleMessage} envelope (`type: 'ggui:lifecycle'`).
809
+ *
810
+ * Fields:
811
+ * - `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.
815
+ * - `error` — optional, only meaningful when `state === 'error'`.
816
+ * Mirrors the `ggui:bootstrap-failed` postMessage envelope's
817
+ * `reason` + `message` so a single `ggui:lifecycle` listener can
818
+ * surface both the coarse signal AND the typed cause without
819
+ * subscribing to two envelopes. Producers SHOULD set this when
820
+ * `state === 'error'`; it is OPTIONAL because legacy producers
821
+ * emitted no lifecycle event at all and we don't want to require
822
+ * a code change for the coarse signal alone.
823
+ *
824
+ * Producers MUST NOT add fields not enumerated here in this shape;
825
+ * additive evolution requires a new optional key + a doc revision so
826
+ * hosts know what they may observe. Consumers MUST ignore unknown
827
+ * fields (shape-preserving extensibility).
828
+ *
829
+ * @public
830
+ */
831
+ export interface McpAppLifecycleEvent {
832
+ readonly state: McpAppLifecycleState;
833
+ readonly stackItemId?: string;
834
+ readonly error?: {
835
+ readonly code: string;
836
+ readonly message: string;
837
+ };
838
+ }
839
+ /**
840
+ * postMessage envelope the renderer posts to its parent on every
841
+ * lifecycle transition. The string `'ggui:lifecycle'` is the protocol-
842
+ * canonical envelope tag — hosts filter `event.data.type` to subscribe.
843
+ *
844
+ * **Named parties:**
845
+ * - **Renderer** (producer) — running inside the MCP Apps iframe;
846
+ * emits one envelope per state transition.
847
+ * - **Host** (consumer) — running in the parent window (e.g.,
848
+ * `<McpAppIframe>`); listens on `window.message`, narrows
849
+ * `event.source` to the iframe's `contentWindow`, and mirrors
850
+ * `event.state` onto the outer iframe element.
851
+ * - **Observer** (downstream) — tests, accessibility scanners, dev
852
+ * inspectors; read the host-mirrored attribute on the outer DOM
853
+ * element. Observers DO NOT subscribe to postMessage directly —
854
+ * the host is the protocol-defined mirror point.
855
+ *
856
+ * **Obligations:**
857
+ * - Renderer MUST post `mounting` before evaluating the bundle.
858
+ * - Renderer MUST post exactly one terminal state (`code-ready`
859
+ * or `error`) for any successful boot attempt.
860
+ * - Renderer MAY post `disconnected` after a `code-ready` and MAY
861
+ * post `code-ready` again after a successful reconnect.
862
+ * - Host MUST mirror the latest received state onto the outer
863
+ * element via the `data-ggui-mcp-app-iframe-lifecycle="<state>"`
864
+ * attribute. Idempotent re-emission of the same state is a no-op.
865
+ * - Host MUST narrow `event.source` to the iframe's `contentWindow`
866
+ * before trusting the envelope (cross-frame postMessage is the
867
+ * attack surface; envelopes from other windows MUST be dropped).
868
+ *
869
+ * **Defined failure modes:**
870
+ * - Renderer never emits any lifecycle event → host's outer-element
871
+ * attribute is never set, observers timeout waiting for a state.
872
+ * This is the UN-INSTRUMENTED legacy case; not a violation.
873
+ * - Renderer emits `mounting` then no terminal state → host's
874
+ * attribute pins to `'mounting'`. Observers waiting for
875
+ * `'code-ready'` see a stuck attribute and fail their own timeout
876
+ * — the coarse-grained surfacing of "renderer crashed before
877
+ * declaring ready". Hosts MAY layer a watchdog on top to
878
+ * transition the attribute to a synthetic `'timeout'` state, but
879
+ * that is host policy, not protocol obligation.
880
+ * - Renderer emits `code-ready` and the WS later drops without a
881
+ * subsequent `disconnected` → host's attribute remains
882
+ * `'code-ready'`. This is shape-acceptable because reconnect
883
+ * attempts are still in flight; observers that need finer-
884
+ * grained connection state subscribe to `ggui:observe`'s
885
+ * `subscribe-failed` events instead.
886
+ *
887
+ * **Observable violation:**
888
+ * - The outer-element attribute. A renderer that posts envelopes
889
+ * the host can't classify (wrong shape, wrong type tag) does NOT
890
+ * update the attribute; the violation is observable as a stuck
891
+ * attribute relative to the inferred WS / DOM state of the
892
+ * iframe child.
893
+ *
894
+ * @public
895
+ */
896
+ export interface McpAppLifecycleMessage {
897
+ readonly type: 'ggui:lifecycle';
898
+ readonly event: McpAppLifecycleEvent;
899
+ }
900
+ /**
901
+ * The closed set of valid lifecycle states. Exposed as a `readonly`
902
+ * tuple so consumers (renderer host filters, conformance tests) can
903
+ * iterate without re-typing the union literally.
904
+ *
905
+ * @public
906
+ */
907
+ export declare const MCP_APP_LIFECYCLE_STATES: readonly McpAppLifecycleState[];
908
+ /**
909
+ * Type guard for {@link McpAppLifecycleMessage}. Trust-boundary helper
910
+ * — apps consuming raw postMessage data MUST narrow before reading
911
+ * `event.state` to avoid reaching into untyped property bags.
912
+ *
913
+ * Validation rules (all required for `true`):
914
+ * - Outer envelope is an object with `type === 'ggui:lifecycle'`.
915
+ * - `event` is an object with `state` matching {@link
916
+ * MCP_APP_LIFECYCLE_STATES}.
917
+ * - If `stackItemId` is present, it is a non-empty string.
918
+ * - If `error` is present, it is an object with string `code` +
919
+ * `message`.
920
+ *
921
+ * @public
922
+ */
923
+ 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
+ /**
936
+ * Discriminator for the user-action envelope delivered via
937
+ * `ggui_runtime_submit_action` over the MCP Apps host-relay path
938
+ * (postMessage `tools/call` → host MCP client → server). Every
939
+ * user-driven `WireConfig` method emits this envelope so operators get
940
+ * **uniform server-side observability** across every gesture kind
941
+ * regardless of which user-visible effect the iframe already fired
942
+ * locally (`ui/open-link` / `ui/request-display-mode`) before the audit.
943
+ *
944
+ * **Closed primary set, extensibly-closed forward-compat.** The three
945
+ * primary kinds correspond 1:1 to the `WireConfig` methods that emit
946
+ * gestures today. Forward additions land via the `(string & {})` slot
947
+ * — handlers MUST treat unknown values gracefully (log under an
948
+ * `'unknown'` bucket, never throw or hard-switch). Adding a new kind
949
+ * is additive and does NOT bump the protocol version.
950
+ *
951
+ * | kind | primary host effect | payload shape |
952
+ * | ----------------------- | -------------------------------- | ---------------------------------------------------------------------- |
953
+ * | `dispatch` | pipe append (single source) | `{ intent: string, actionData: JsonValue \| null, uiContext: JsonObject }` |
954
+ * | `openLink` | `ui/open-link` | `{ url: string }` |
955
+ * | `requestDisplayMode` | `ui/request-display-mode` | `{ mode: 'fullscreen' \| 'pip' \| 'inline' }` |
956
+ *
957
+ * Audit is **fail-soft** at the client: if the `tools/call` envelope
958
+ * fails to deliver (host rejects, postMessage on detached parent), the
959
+ * primary host effect MUST still proceed. The audit miss surfaces as a
960
+ * diagnostic on the operator side (gap in the SessionInspector activity
961
+ * row), not as a user-facing failure. This mirrors today's `dispatch`
962
+ * audit-fire posture so semantics stay uniform.
963
+ *
964
+ * Failure-mode note: a malformed envelope (unknown `kind` AND malformed
965
+ * `payload`) lands as `INVALID_ACTION_KIND` on `_ggui:contract-error`.
966
+ * See `ContractErrorCode` for the canonical extensibly-closed code set.
967
+ */
968
+ export type SubmitActionKind = 'dispatch' | 'openLink' | 'requestDisplayMode' | (string & {});
969
+ /**
970
+ * Per-kind payload schemas for {@link SubmitActionKind}. Keep this discriminated
971
+ * union narrow — adding a new gesture means adding both a kind variant AND
972
+ * its payload shape here, in lockstep, so the `ggui_runtime_submit_action`
973
+ * handler's input parser can validate exhaustively.
974
+ *
975
+ * `payload` for the unknown `(string & {})` extension slot widens to
976
+ * `Record<string, unknown>` — handlers MUST validate shape against their
977
+ * own schema before consuming, since the protocol type can't narrow it.
978
+ */
979
+ export type SubmitActionEnvelope = {
980
+ readonly kind: 'dispatch';
981
+ readonly payload: {
982
+ /** `actionSpec[*]` key the iframe dispatched against. */
983
+ readonly intent: string;
984
+ /**
985
+ * Typed payload satisfying `actionSpec[intent].schema`.
986
+ * `null` for no-payload gestures (bare button click).
987
+ */
988
+ readonly actionData: JsonValue | null;
989
+ /**
990
+ * Iframe-local snapshot of the contract's `contextSpec` slot
991
+ * values at the moment the user fired the gesture. Captured at
992
+ * gesture time so the agent can reason about WHAT the user did
993
+ * AND WHAT THEY WERE LOOKING AT atomically — without a second
994
+ * round trip to read state from the rendered UI.
995
+ *
996
+ * Empty object `{}` when the contract has no `contextSpec` or
997
+ * the iframe hasn't yet mirrored any slots.
998
+ */
999
+ readonly uiContext: JsonObject;
1000
+ };
1001
+ } | {
1002
+ readonly kind: 'openLink';
1003
+ readonly payload: {
1004
+ readonly url: string;
1005
+ };
1006
+ } | {
1007
+ readonly kind: 'requestDisplayMode';
1008
+ readonly payload: {
1009
+ readonly mode: 'fullscreen' | 'pip' | 'inline' | (string & {});
1010
+ };
1011
+ } | {
1012
+ readonly kind: string;
1013
+ readonly payload: Record<string, unknown>;
1014
+ };
1015
+ /**
1016
+ * Canonical input contract for the `ggui_runtime_submit_action` MCP tool.
1017
+ * The iframe-runtime delivers this via the MCP Apps host-relay path
1018
+ * (postMessage `tools/call` → host MCP client → server) — the iframe
1019
+ * has no auth credential of its own, so the host is the protocol-
1020
+ * defined relay party (per `_meta.ui.visibility: ['app']` on the
1021
+ * tool declaration).
1022
+ *
1023
+ * Per-kind semantics:
1024
+ *
1025
+ * - `kind === 'dispatch'`: server appends a consume-entry onto the
1026
+ * stackItem-keyed pending-events pipe (`{type:'action', stackItemId,
1027
+ * 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.
1034
+ * - `kind ∈ {'openLink','requestDisplayMode'}`: pure audit — the
1035
+ * user-visible host effect already fired iframe-side via
1036
+ * `ui/open-link` / `ui/request-display-mode`. The server records
1037
+ * the gesture for the SessionInspector feed.
1038
+ *
1039
+ * Required fields:
1040
+ * - `sessionId` / `appId`: bootstrap-issued; server cross-checks.
1041
+ * - `actionId`: 8-hex correlation hash (FNV-1a of intent + data + firedAt
1042
+ * for `dispatch`, kind + payload + firedAt for the host-control kinds).
1043
+ * Lets the host LLM cross-verify a `[ggui:pending-action]` context entry
1044
+ * against a `ui/message` consent prompt by id.
1045
+ * - `firedAt`: ISO-8601 client-monotonic timestamp; useful for ordering
1046
+ * and replay diagnostics. Server uses its own clock for authoritative
1047
+ * log ordering.
1048
+ *
1049
+ * The discriminated `kind` + `payload` pair carries the actual gesture
1050
+ * shape — see {@link SubmitActionEnvelope}.
1051
+ */
1052
+ 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;
1064
+ readonly appId: string;
1065
+ readonly actionId: string;
1066
+ readonly firedAt: string;
1067
+ };
1068
+ /**
1069
+ * The three canonical gesture kinds — useful for exhaustiveness checks
1070
+ * in `switch (kind) { ... }` blocks. Frozen so consumers can safely use
1071
+ * `as const` against the readonly tuple.
1072
+ */
1073
+ export declare const SUBMIT_ACTION_KINDS: readonly ["dispatch", "openLink", "requestDisplayMode"];
1074
+ /**
1075
+ * Type guard narrowing an unknown value to {@link GguiSubmitActionInput}.
1076
+ * Validates the `kind` discriminator + the per-kind `payload` shape.
1077
+ * Used by the server-side `ggui_runtime_submit_action` handler to reject
1078
+ * malformed envelopes with `INVALID_ACTION_KIND` instead of silently
1079
+ * coercing.
1080
+ *
1081
+ * Unknown extension kinds are accepted at this guard layer (the
1082
+ * `(string & {})` slot is part of the type) but the per-kind payload
1083
+ * narrowing collapses to `Record<string, unknown>` — extension-handlers
1084
+ * MUST validate shape before consuming.
1085
+ */
1086
+ export declare function isGguiSubmitActionInput(value: unknown): value is GguiSubmitActionInput;
1087
+ /**
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}.
1122
+ *
1123
+ * @public
1124
+ */
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
+ */
1132
+ readonly description: string;
1133
+ /** Stack item the gesture targeted. */
1134
+ readonly stackItemId: string;
1135
+ /** 8-hex FNV-1a correlation id of the gesture. */
1136
+ readonly actionId: string;
1137
+ /** ISO 8601 UTC timestamp of the gesture (iframe local clock). */
1138
+ readonly submittedAt: string;
1139
+ /** Which `actionSpec[*]` entry the iframe dispatched against. */
1140
+ 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
+ readonly nextStep: {
1147
+ readonly tool: 'ggui_consume';
1148
+ readonly args: {
1149
+ readonly stackItemId: string;
1150
+ };
1151
+ };
1152
+ }
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
+ //# sourceMappingURL=mcp-apps.d.ts.map