@ggui-ai/iframe-runtime 0.2.0-alpha.3 → 0.3.0-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/dist/channel-transport.d.ts +20 -30
  2. package/dist/channel-transport.js +24 -24
  3. package/dist/channels/data.d.ts +5 -20
  4. package/dist/channels/data.js +4 -52
  5. package/dist/channels/index.d.ts +0 -1
  6. package/dist/channels/index.js +0 -1
  7. package/dist/channels/props-update.d.ts +9 -9
  8. package/dist/channels/props-update.js +9 -9
  9. package/dist/channels/render-frame.d.ts +5 -5
  10. package/dist/channels/render-frame.js +5 -5
  11. package/dist/compiled-validators.d.ts +2 -2
  12. package/dist/compiled-validators.js +1 -1
  13. package/dist/context-observer.d.ts +4 -4
  14. package/dist/context-observer.js +3 -3
  15. package/dist/events-polling.d.ts +7 -7
  16. package/dist/events-polling.js +5 -5
  17. package/dist/globals.d.ts +1 -1
  18. package/dist/host-context-emitter.d.ts +4 -4
  19. package/dist/host-context-emitter.js +9 -9
  20. package/dist/iframe-runtime.js +94 -76
  21. package/dist/index.d.ts +4 -4
  22. package/dist/index.js +1 -1
  23. package/dist/lifecycle.d.ts +3 -3
  24. package/dist/lifecycle.js +29 -6
  25. package/dist/mcp-app-iframe-host.d.ts +8 -8
  26. package/dist/mcp-app-iframe-host.js +4 -4
  27. package/dist/meta-parse.d.ts +3 -4
  28. package/dist/meta-parse.js +17 -44
  29. package/dist/observability.d.ts +14 -36
  30. package/dist/observability.js +2 -1
  31. package/dist/protocol-error.d.ts +38 -48
  32. package/dist/protocol-error.js +5 -23
  33. package/dist/provisional-renderer.js +3 -3
  34. package/dist/react-renderer.d.ts +16 -6
  35. package/dist/react-renderer.js +22 -40
  36. package/dist/registry-subscribe.d.ts +7 -7
  37. package/dist/registry-subscribe.js +28 -14
  38. package/dist/render-item.d.ts +22 -15
  39. package/dist/render-item.js +7 -14
  40. package/dist/runtime.d.ts +79 -105
  41. package/dist/runtime.js +219 -269
  42. package/dist/status-dom.d.ts +12 -12
  43. package/dist/status-dom.js +11 -11
  44. package/dist/system-cards/NoCredentialsCard.d.ts +1 -1
  45. package/dist/system-cards/NoCredentialsCard.js +1 -1
  46. package/dist/system-cards/ProtocolProbeCard.d.ts +13 -17
  47. package/dist/system-cards/ProtocolProbeCard.js +21 -25
  48. package/dist/system-cards/index.d.ts +5 -5
  49. package/dist/system-cards/index.js +5 -5
  50. package/dist/types.d.ts +29 -34
  51. package/dist/validation.d.ts +4 -5
  52. package/dist/validation.js +5 -6
  53. package/dist/wire-config.d.ts +43 -106
  54. package/dist/wire-config.js +29 -195
  55. package/package.json +12 -11
  56. package/dist/channels/feedback.d.ts +0 -14
  57. package/dist/channels/feedback.js +0 -19
package/dist/index.d.ts CHANGED
@@ -19,7 +19,7 @@
19
19
  * postMessage envelope shape parents observe on boot failure.
20
20
  * - `ProtocolError` — canonical typed union for every failure
21
21
  * the renderer classifies outward, wired to the postMessage
22
- * envelope and the `_ggui:contract-error` WS channel.
22
+ * envelope.
23
23
  * - `BootstrapFailureReason` — extensibly-closed union of every
24
24
  * reason the 'bootstrap' variant can carry. Consolidates
25
25
  * parse-time and post-parse codes in one place. (Wire-visible
@@ -34,10 +34,10 @@
34
34
  */
35
35
  export type { McpAppAiGguiMetaParseFailureReason, McpAppAiGguiMetaParseResult, } from './types.js';
36
36
  export { parseMetaFromGlobal, parseMetaFromToolResult, validateMeta, } from './meta-parse.js';
37
- export type { RendererBootFailureReason, RendererBootFailedMessage, SelfContainedMcpAppAiGguiMeta, } from './runtime.js';
37
+ export type { RendererBootFailureReason, RendererBootFailedMessage, } from './runtime.js';
38
38
  export type { ProtocolError, ProtocolErrorEmitter, BootstrapFailureReason, } from './protocol-error.js';
39
- export { defaultProtocolErrorEmitter, fromBootstrapFailure, fromClientContractViolation, fromContractErrorPayload, fromTransportFailure, fromAuthFailure, fromUpgradeRequired, fromUnknown, } from './protocol-error.js';
40
- export type { ObservabilityEvent, ObservabilityEmitter, ObservabilityMessage, WiredToolInvokedEvent, ContractErrorEmittedEvent, SchemaVersionMismatchEvent, SubscribeFailedEvent, AuthRequiredEvent, UnknownObservabilityEvent, } from './observability.js';
39
+ export { defaultProtocolErrorEmitter, fromBootstrapFailure, fromClientContractViolation, fromTransportFailure, fromAuthFailure, fromUpgradeRequired, fromUnknown, } from './protocol-error.js';
40
+ export type { ObservabilityEvent, ObservabilityEmitter, ObservabilityMessage, SchemaVersionMismatchEvent, SubscribeFailedEvent, AuthRequiredEvent, ChannelTransportPickedEvent, ChannelTransportFallbackEvent, ChannelTransportResubscribedEvent, UnknownObservabilityEvent, } from './observability.js';
41
41
  export { postObservabilityToParent } from './observability.js';
42
42
  export type { LifecycleEmitter } from './lifecycle.js';
43
43
  export { postLifecycleToParent, makeLifecycleEvent } from './lifecycle.js';
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
1
  export { parseMetaFromGlobal, parseMetaFromToolResult, validateMeta, } from './meta-parse.js';
2
- export { defaultProtocolErrorEmitter, fromBootstrapFailure, fromClientContractViolation, fromContractErrorPayload, fromTransportFailure, fromAuthFailure, fromUpgradeRequired, fromUnknown, } from './protocol-error.js';
2
+ export { defaultProtocolErrorEmitter, fromBootstrapFailure, fromClientContractViolation, fromTransportFailure, fromAuthFailure, fromUpgradeRequired, fromUnknown, } from './protocol-error.js';
3
3
  export { postObservabilityToParent } from './observability.js';
4
4
  export { postLifecycleToParent, makeLifecycleEvent } from './lifecycle.js';
@@ -20,7 +20,7 @@
20
20
  *
21
21
  * @public
22
22
  */
23
- import type { McpAppLifecycleEvent, McpAppLifecycleState } from '@ggui-ai/protocol/integrations/mcp-apps';
23
+ import { type McpAppLifecycleEvent, type McpAppLifecycleState } from '@ggui-ai/protocol/integrations/mcp-apps';
24
24
  /**
25
25
  * Caller sink for lifecycle events. Mirrors the
26
26
  * {@link import('./observability.js').ObservabilityEmitter} posture —
@@ -52,13 +52,13 @@ export declare function postLifecycleToParent(event: McpAppLifecycleEvent): void
52
52
  * Build a lifecycle event payload. Centralises the `state` →
53
53
  * `McpAppLifecycleEvent` projection so call sites in
54
54
  * `runtime.ts` don't repeat the `{state, ...optional}` shape and so
55
- * the additive-extension semantic of `renderId` / `error` is
55
+ * the additive-extension semantic of `sessionId` / `error` is
56
56
  * single-sourced.
57
57
  *
58
58
  * @public
59
59
  */
60
60
  export declare function makeLifecycleEvent(state: McpAppLifecycleState, options?: {
61
- readonly renderId?: string;
61
+ readonly sessionId?: string;
62
62
  readonly error?: {
63
63
  readonly code: string;
64
64
  readonly message: string;
package/dist/lifecycle.js CHANGED
@@ -1,3 +1,26 @@
1
+ /**
2
+ * Renderer-side emission of MCP App lifecycle events — the postMessage
3
+ * envelope the iframe posts to its parent on every mount-state
4
+ * transition. Mirrors the `<McpAppIframe>` host's outer-DOM mirror
5
+ * (`data-ggui-mcp-app-iframe-lifecycle="<state>"`); together they form
6
+ * a first-class wire surface so observers (E2E tests, accessibility
7
+ * scanners, third-party hosts, console inspectors) can read mount
8
+ * state without reaching into iframe-internal DOM.
9
+ *
10
+ * Lives here (not in `runtime.ts`) so spec-level tests can import the
11
+ * helper without dragging in the runtime's autostart side effects.
12
+ *
13
+ * **Producer obligations** (locked by
14
+ * {@link import('@ggui-ai/protocol/integrations/mcp-apps').McpAppLifecycleMessage}):
15
+ * - `mounting` MUST fire before bundle eval / first WS attempt.
16
+ * - Exactly one terminal state (`code-ready` or `error`) MUST follow
17
+ * for every successful boot attempt.
18
+ * - `disconnected` MAY follow `code-ready` on WS close; a subsequent
19
+ * `code-ready` MAY follow on successful reconnect (not required).
20
+ *
21
+ * @public
22
+ */
23
+ import { MCP_APP_LIFECYCLE_TYPE, } from '@ggui-ai/protocol/integrations/mcp-apps';
1
24
  /**
2
25
  * Default emitter — posts a {@link McpAppLifecycleMessage} envelope to
3
26
  * `window.parent`. Mirrors `postObservabilityToParent` /
@@ -13,7 +36,7 @@
13
36
  export function postLifecycleToParent(event) {
14
37
  if (typeof window === 'undefined' || window.parent === null)
15
38
  return;
16
- const message = { type: 'ggui:lifecycle', event };
39
+ const message = { type: MCP_APP_LIFECYCLE_TYPE, event };
17
40
  try {
18
41
  window.parent.postMessage(message, '*');
19
42
  }
@@ -26,7 +49,7 @@ export function postLifecycleToParent(event) {
26
49
  * Build a lifecycle event payload. Centralises the `state` →
27
50
  * `McpAppLifecycleEvent` projection so call sites in
28
51
  * `runtime.ts` don't repeat the `{state, ...optional}` shape and so
29
- * the additive-extension semantic of `renderId` / `error` is
52
+ * the additive-extension semantic of `sessionId` / `error` is
30
53
  * single-sourced.
31
54
  *
32
55
  * @public
@@ -34,15 +57,15 @@ export function postLifecycleToParent(event) {
34
57
  export function makeLifecycleEvent(state, options) {
35
58
  // Avoid emitting `undefined` keys — the protocol's structural lock
36
59
  // (mcp-apps.test.ts "shape lock") asserts a populated event has
37
- // exactly state + renderId + error keys, not an Object.keys-
60
+ // exactly state + sessionId + error keys, not an Object.keys-
38
61
  // observable `undefined` field.
39
62
  if (options === undefined)
40
63
  return { state };
41
- const { renderId, error } = options;
64
+ const { sessionId, error } = options;
42
65
  return {
43
66
  state,
44
- ...(typeof renderId === 'string' && renderId.length > 0
45
- ? { renderId }
67
+ ...(typeof sessionId === 'string' && sessionId.length > 0
68
+ ? { sessionId }
46
69
  : {}),
47
70
  ...(error !== undefined ? { error } : {}),
48
71
  };
@@ -15,10 +15,10 @@
15
15
  *
16
16
  * Adapter-boundary rule (ENFORCED):
17
17
  * - MCP Apps lifecycle messages from the embedded iframe do NOT
18
- * mutate the outer ggui session state / contracts — the host
19
- * never forwards them to ggui-server as session-level events.
18
+ * mutate the outer ggui render state / contracts — the host
19
+ * never forwards them to ggui-server as render-level events.
20
20
  * - `ui/initialize` intentionally DOES NOT forward outer ggui
21
- * session state (stack, actionSpec, streamSpec) to the iframe.
21
+ * render state (actionSpec, streamSpec) to the iframe.
22
22
  *
23
23
  * On detach, a host-initiated `ui/resource-teardown` notification
24
24
  * is posted to the iframe BEFORE the element leaves the DOM so the
@@ -30,16 +30,16 @@
30
30
  * iframe can in turn render a `ui://ggui/render` resource via its
31
31
  * own host implementation. That recursion stays bounded because
32
32
  * each nested iframe runs the same adapter-boundary rule — outer
33
- * ggui state never leaks into the inner renderer's session through
33
+ * ggui state never leaks into the inner renderer's render through
34
34
  * this host path. Host-to-host sandboxing is browser-enforced by
35
35
  * the iframe's `sandbox="allow-scripts"` attribute.
36
36
  */
37
- import type { McpAppsRender } from '@ggui-ai/protocol/integrations/mcp-apps';
37
+ import type { McpAppsGguiSession } from '@ggui-ai/protocol/integrations/mcp-apps';
38
38
  export interface McpAppIframeMountOptions {
39
- readonly render: McpAppsRender;
40
- /** Render id — threaded into the proxy URL so server-side
39
+ readonly render: McpAppsGguiSession;
40
+ /** GguiSession id — threaded into the proxy URL so server-side
41
41
  * connector scoping works. */
42
- readonly renderId: string;
42
+ readonly sessionId: string;
43
43
  /**
44
44
  * Base URL of the ggui server (origin). The host appends
45
45
  * `/mcp-apps/resource` + `/mcp-apps/tools-call` to this. Should
@@ -12,7 +12,7 @@ const DEFAULT_THEME = {
12
12
  function composeResourceUrl(opts) {
13
13
  const base = opts.serverBaseUrl.replace(/\/$/, '');
14
14
  const qs = new URLSearchParams({
15
- render: opts.renderId,
15
+ session: opts.sessionId,
16
16
  item: opts.itemId,
17
17
  });
18
18
  return `${base}/mcp-apps/resource?${qs.toString()}`;
@@ -58,7 +58,7 @@ export function mountMcpAppIframe(container, opts) {
58
58
  const serverBaseUrl = opts.serverBaseUrl ?? '';
59
59
  const resourceUrl = composeResourceUrl({
60
60
  serverBaseUrl,
61
- renderId: opts.renderId,
61
+ sessionId: opts.sessionId,
62
62
  itemId: opts.render.id,
63
63
  });
64
64
  const toolsCallUrl = composeToolsCallUrl(serverBaseUrl);
@@ -70,7 +70,7 @@ export function mountMcpAppIframe(container, opts) {
70
70
  };
71
71
  const iframe = container.ownerDocument.createElement('iframe');
72
72
  iframe.setAttribute('data-ggui-mcp-apps', 'iframe');
73
- iframe.setAttribute('data-ggui-render-id', opts.render.id);
73
+ iframe.setAttribute('data-ggui-session-id', opts.render.id);
74
74
  iframe.setAttribute('data-ggui-connector-id', opts.render.source.connectorId);
75
75
  iframe.src = resourceUrl;
76
76
  iframe.title = opts.render.description ?? 'MCP App';
@@ -139,7 +139,7 @@ export function mountMcpAppIframe(container, opts) {
139
139
  method: 'POST',
140
140
  headers: { 'Content-Type': 'application/json' },
141
141
  body: JSON.stringify({
142
- render: opts.renderId,
142
+ session: opts.sessionId,
143
143
  item: opts.render.id,
144
144
  tool,
145
145
  arguments: args,
@@ -32,10 +32,9 @@ export declare function validateMeta(meta: McpAppAiGguiRenderMeta): McpAppAiGgui
32
32
  export declare function parseMetaFromGlobal(): McpAppAiGguiMetaParseResult;
33
33
  /**
34
34
  * Parse the render slice from a `ui/notifications/tool-result`
35
- * postMessage params payload. Looks at BOTH the spec-canonical
36
- * location (`params._meta`) and the Reading-B `<McpAppIframe>`
37
- * convention (`params.toolOutput._meta`) — Claude Desktop / claude.ai
38
- * Connector use the first; first-party hosts the second.
35
+ * postMessage params payload. `params` is a `CallToolResult` per the
36
+ * MCP-Apps spec, so the slice envelope lives at the spec-canonical
37
+ * top-level `params._meta` — the only accepted location.
39
38
  *
40
39
  * Used by the runtime's autostart resolver as the second / third boot
41
40
  * source (drained from the pre-runtime-load buffer or caught by the
@@ -14,12 +14,10 @@
14
14
  * JSON-RPC round-trip.
15
15
  * 2. `ui/notifications/tool-result` postMessage — spec-canonical
16
16
  * per MCP-Apps SEP-1865. `params` is a `CallToolResult` per the
17
- * spec, so slices live under `params._meta`. We also accept
18
- * `params.toolOutput._meta` as a back-compat aliased shape
19
- * (some in-house emitters wrap the CallToolResult inside a
20
- * `toolOutput` field). Spec-strict hosts (`<AppRenderer>` from
21
- * `@mcp-ui/client`, ChatGPT MCP-Apps connector, claude.ai)
22
- * deliver slice meta through this channel exclusively.
17
+ * spec, so slices live under `params._meta`. Hosts (`<AppRenderer>`
18
+ * from `@mcp-ui/client`, the RN `<McpAppIframe>`, ChatGPT MCP-Apps
19
+ * connector, claude.ai) deliver slice meta through this channel
20
+ * exclusively.
23
21
  *
24
22
  * Each extractor reaches the `_meta` object and calls the protocol's
25
23
  * {@link parseMcpAppAiGguiRenderMeta} to produce a typed slice.
@@ -27,8 +25,7 @@
27
25
  * {@link validateMeta} layers in mode-discriminator + expiresAt
28
26
  * expiration checks and field-level defensive parsing for the
29
27
  * optional containers (`contextSlots`, `gadgets`, `publicEnv`,
30
- * `permissionsPolicy`, `appCallableTools`, `actionNextSteps`,
31
- * `streamWebSocketLocalTools`).
28
+ * `permissionsPolicy`, `streamWebSocketLocalTools`).
32
29
  *
33
30
  * Reading-B (`result.toolOutput._meta` on the `ui/initialize`
34
31
  * response) was retired in Phase 1.19b.3 — the
@@ -183,10 +180,9 @@ function projectPublicEnv(raw) {
183
180
  * in the past AND the slice carries static content (codeUrl / kind),
184
181
  * DEGRADE to static-only (drop `wsUrl` / `wsToken` / `expiresAt`);
185
182
  * otherwise return EXPIRED_BOOTSTRAP.
186
- * - `gadgets` / `publicEnv` / `appCallableTools` /
187
- * `permissionsPolicy` / `streamWebSocketLocalTools` /
188
- * `contextSlots` / `actionNextSteps` get defensive entry-level
189
- * validation; malformed → defaulted.
183
+ * - `gadgets` / `publicEnv` / `permissionsPolicy` /
184
+ * `streamWebSocketLocalTools` / `contextSlots` get defensive
185
+ * entry-level validation; malformed → defaulted.
190
186
  */
191
187
  function projectMeta(meta, hasStaticContent) {
192
188
  let expiresAt = meta.expiresAt;
@@ -208,11 +204,6 @@ function projectMeta(meta, hasStaticContent) {
208
204
  }
209
205
  const gadgets = projectGadgets(meta.gadgets);
210
206
  const publicEnv = projectPublicEnv(meta.publicEnv);
211
- const appCallableToolsRaw = meta.appCallableTools;
212
- const appCallableTools = Array.isArray(appCallableToolsRaw) &&
213
- appCallableToolsRaw.every((s) => typeof s === 'string')
214
- ? appCallableToolsRaw
215
- : [];
216
207
  const permissionsPolicyRaw = meta.permissionsPolicy;
217
208
  const permissionsPolicy = Array.isArray(permissionsPolicyRaw) &&
218
209
  permissionsPolicyRaw.every((s) => typeof s === 'string' && s.length > 0)
@@ -231,18 +222,11 @@ function projectMeta(meta, hasStaticContent) {
231
222
  const themeMode = themeModeRaw === 'light' || themeModeRaw === 'dark'
232
223
  ? themeModeRaw
233
224
  : undefined;
234
- const actionNextStepsRaw = meta.actionNextSteps;
235
- let actionNextSteps;
236
- if (isPlainObject(actionNextStepsRaw) &&
237
- Object.values(actionNextStepsRaw).every((v) => typeof v === 'string')) {
238
- actionNextSteps = actionNextStepsRaw;
239
- }
240
225
  const contextSlots = projectContextSlots(meta.contextSlots);
241
226
  const projected = {
242
- renderId: meta.renderId,
227
+ sessionId: meta.sessionId,
243
228
  appId: meta.appId,
244
229
  runtimeUrl: meta.runtimeUrl,
245
- appCallableTools,
246
230
  ...(dropLiveCreds
247
231
  ? {}
248
232
  : {
@@ -256,6 +240,9 @@ function projectMeta(meta, hasStaticContent) {
256
240
  : {}),
257
241
  ...(meta.themeId !== undefined ? { themeId: meta.themeId } : {}),
258
242
  ...(themeMode !== undefined ? { themeMode } : {}),
243
+ // Per-app theme overlay carried through verbatim. Already validated
244
+ // via `appThemeSchema` by the wire parser, so no re-validation here.
245
+ ...(meta.theme !== undefined ? { theme: meta.theme } : {}),
259
246
  ...(gadgets !== undefined ? { gadgets } : {}),
260
247
  ...(publicEnv !== undefined ? { publicEnv } : {}),
261
248
  ...(streamWebSocketLocalTools !== undefined
@@ -264,7 +251,6 @@ function projectMeta(meta, hasStaticContent) {
264
251
  ...(permissionsPolicy !== undefined ? { permissionsPolicy } : {}),
265
252
  ...(meta.lastSequence !== undefined ? { lastSequence: meta.lastSequence } : {}),
266
253
  ...(meta.propsJson !== undefined ? { propsJson: meta.propsJson } : {}),
267
- ...(actionNextSteps !== undefined ? { actionNextSteps } : {}),
268
254
  ...(contextSlots !== undefined ? { contextSlots } : {}),
269
255
  ...(meta.contractHash !== undefined && meta.validatorsUrl !== undefined
270
256
  ? {
@@ -349,10 +335,9 @@ export function parseMetaFromGlobal() {
349
335
  }
350
336
  /**
351
337
  * Parse the render slice from a `ui/notifications/tool-result`
352
- * postMessage params payload. Looks at BOTH the spec-canonical
353
- * location (`params._meta`) and the Reading-B `<McpAppIframe>`
354
- * convention (`params.toolOutput._meta`) — Claude Desktop / claude.ai
355
- * Connector use the first; first-party hosts the second.
338
+ * postMessage params payload. `params` is a `CallToolResult` per the
339
+ * MCP-Apps spec, so the slice envelope lives at the spec-canonical
340
+ * top-level `params._meta` — the only accepted location.
356
341
  *
357
342
  * Used by the runtime's autostart resolver as the second / third boot
358
343
  * source (drained from the pre-runtime-load buffer or caught by the
@@ -362,23 +347,11 @@ export function parseMetaFromToolResult(params) {
362
347
  if (!isPlainObject(params)) {
363
348
  return { ok: false, reason: 'MISSING_TOOL_OUTPUT' };
364
349
  }
365
- let meta;
366
350
  const topMeta = params['_meta'];
367
- if (isPlainObject(topMeta)) {
368
- meta = topMeta;
369
- }
370
- else {
371
- const toolOutput = params['toolOutput'];
372
- if (isPlainObject(toolOutput)) {
373
- const nestedMeta = toolOutput['_meta'];
374
- if (isPlainObject(nestedMeta)) {
375
- meta = nestedMeta;
376
- }
377
- }
378
- }
379
- if (meta === undefined) {
351
+ if (!isPlainObject(topMeta)) {
380
352
  return { ok: false, reason: 'MISSING_META_GGUI_BOOTSTRAP' };
381
353
  }
354
+ const meta = topMeta;
382
355
  const parsed = parseMcpAppAiGguiRenderMeta(meta);
383
356
  if (!parsed.ok) {
384
357
  return { ok: false, reason: 'MALFORMED_BOOTSTRAP' };
@@ -1,3 +1,4 @@
1
+ import { MCP_APP_OBSERVE_TYPE } from '@ggui-ai/protocol/integrations/mcp-apps';
1
2
  /**
2
3
  * Observability events emitted by the renderer iframe → surfaced to
3
4
  * the MCP Apps host via the `<McpAppIframe>` wrapper's `onObserve`
@@ -23,39 +24,16 @@
23
24
  * - `@ggui-ai/iframe-runtime` owns this union, NOT `@ggui-ai/protocol`.
24
25
  * Observability events are a renderer ↔ host implementation seam,
25
26
  * not a wire-format contract between arbitrary protocol peers, so
26
- * they stay out of the protocol package.
27
+ * they stay out of the protocol package. The ENVELOPE TAG
28
+ * (`ggui:observe`) IS protocol-owned, though — it belongs to the
29
+ * renderer → host postMessage envelope family
30
+ * (`MCP_APP_OBSERVE_TYPE` in
31
+ * `@ggui-ai/protocol/integrations/mcp-apps`), so hosts on every
32
+ * platform classify it from one vocabulary.
27
33
  *
28
34
  * @public
29
35
  */
30
- export type ObservabilityEvent = WiredToolInvokedEvent | ContractErrorEmittedEvent | SchemaVersionMismatchEvent | SubscribeFailedEvent | AuthRequiredEvent | ChannelTransportPickedEvent | ChannelTransportFallbackEvent | ChannelTransportResubscribedEvent | UnknownObservabilityEvent;
31
- /**
32
- * Fired when a wired action successfully dispatched to a registered
33
- * MCP tool. Hosts surface this as a dispatch row in RenderInspector.
34
- * Emitted by the server-side router (session-channel.ts) via
35
- * postMessage relay through the renderer.
36
- *
37
- * @public
38
- */
39
- export interface WiredToolInvokedEvent {
40
- readonly kind: 'wired-tool-invoked';
41
- readonly toolName: string;
42
- readonly actionName?: string;
43
- readonly dispatchedAt: string;
44
- readonly latencyMs?: number;
45
- }
46
- /**
47
- * Fired when the server emits a `_ggui:contract-error` envelope on
48
- * the live channel. Mirrors the envelope's `code` field for host-side
49
- * inspection without requiring the host to parse channel frames.
50
- *
51
- * @public
52
- */
53
- export interface ContractErrorEmittedEvent {
54
- readonly kind: 'contract-error-emitted';
55
- readonly code: string;
56
- readonly toolName: string;
57
- readonly actionName?: string;
58
- }
36
+ export type ObservabilityEvent = SchemaVersionMismatchEvent | SubscribeFailedEvent | AuthRequiredEvent | ChannelTransportPickedEvent | ChannelTransportFallbackEvent | ChannelTransportResubscribedEvent | UnknownObservabilityEvent;
59
37
  /**
60
38
  * Fired when the protocol-version handshake rejects the connection.
61
39
  * Parallel to `UpgradeRequiredError` — the host can choose to render
@@ -72,7 +50,7 @@ export interface SchemaVersionMismatchEvent {
72
50
  }
73
51
  /**
74
52
  * Fired when subscribe fails for any reason the renderer classifies
75
- * as non-fatal to the session (e.g., transient network jitter that
53
+ * as non-fatal to the render (e.g., transient network jitter that
76
54
  * the reconnect ladder is handling). Terminal subscribe failures
77
55
  * still route through `ProtocolError`.
78
56
  *
@@ -86,7 +64,7 @@ export interface SubscribeFailedEvent {
86
64
  /**
87
65
  * Fired when the server emits a `system` frame with
88
66
  * `action: 'auth_required'` — the agent needs the end-user to authorize
89
- * an OAuth service (Google, Slack, etc.) before a wired tool can
67
+ * an OAuth service (Google, Slack, etc.) before its tool call can
90
68
  * proceed. The host listens via `<McpAppIframe onObserve>` and renders
91
69
  * a consent overlay OUTSIDE the iframe, then redirects the user to
92
70
  * `authUrl` to complete the OAuth flow.
@@ -144,7 +122,7 @@ export interface AuthRequiredEvent {
144
122
  */
145
123
  export interface ChannelTransportPickedEvent {
146
124
  readonly kind: 'channel-transport-picked';
147
- readonly renderId: string;
125
+ readonly sessionId: string;
148
126
  readonly channelName: string;
149
127
  readonly transport: 'ws' | 'poll';
150
128
  }
@@ -159,7 +137,7 @@ export interface ChannelTransportPickedEvent {
159
137
  */
160
138
  export interface ChannelTransportFallbackEvent {
161
139
  readonly kind: 'channel-transport-fallback';
162
- readonly renderId: string;
140
+ readonly sessionId: string;
163
141
  readonly channelName: string;
164
142
  readonly reason: 'ws-disconnect' | 'channel-not-local';
165
143
  }
@@ -171,7 +149,7 @@ export interface ChannelTransportFallbackEvent {
171
149
  */
172
150
  export interface ChannelTransportResubscribedEvent {
173
151
  readonly kind: 'channel-transport-resubscribed';
174
- readonly renderId: string;
152
+ readonly sessionId: string;
175
153
  readonly channelName: string;
176
154
  }
177
155
  /**
@@ -194,7 +172,7 @@ export interface UnknownObservabilityEvent {
194
172
  * @public
195
173
  */
196
174
  export interface ObservabilityMessage {
197
- readonly type: 'ggui:observe';
175
+ readonly type: typeof MCP_APP_OBSERVE_TYPE;
198
176
  readonly event: ObservabilityEvent;
199
177
  }
200
178
  /**
@@ -1,3 +1,4 @@
1
+ import { MCP_APP_OBSERVE_TYPE } from '@ggui-ai/protocol/integrations/mcp-apps';
1
2
  /**
2
3
  * Default emitter — posts an {@link ObservabilityMessage} to
3
4
  * `window.parent`. Mirrors `postBootFailure` / `postRendererReady`
@@ -17,7 +18,7 @@ export function postObservabilityToParent(event) {
17
18
  // install.
18
19
  if (typeof window === 'undefined' || window.parent === null)
19
20
  return;
20
- const message = { type: 'ggui:observe', event };
21
+ const message = { type: MCP_APP_OBSERVE_TYPE, event };
21
22
  try {
22
23
  window.parent.postMessage(message, '*');
23
24
  }
@@ -1,16 +1,19 @@
1
1
  /**
2
2
  * `ProtocolError` typed union — canonical shape for every error the
3
- * renderer surfaces OUTWARD: to the host page via postMessage, to the
4
- * session via the `_ggui:contract-error` channel, and to the
5
- * in-iframe status DOM.
3
+ * renderer surfaces OUTWARD: to the host page via postMessage and to
4
+ * the in-iframe status DOM.
6
5
  *
7
6
  * The transport wiring around this union:
8
7
  *
9
- * - `postMessage({type:'ggui:protocol-error', error})` for every
10
- * boot-path failure; the `_ggui:contract-error` envelope on the
11
- * live channel for session-bound failures.
8
+ * - Boot-path failures reach the host page via the protocol-owned
9
+ * `postMessage({type:'ggui:bootstrap-failed', reason, message})`
10
+ * envelope (`MCP_APP_BOOTSTRAP_FAILED_TYPE`). There is NO
11
+ * `ggui:protocol-error`
12
+ * postMessage envelope — the typed union itself travels only
13
+ * through the in-process {@link ProtocolErrorEmitter} seam.
12
14
  * - `<McpAppIframe onError={(err: ProtocolError) => …}>` surfaces
13
- * these to embedding apps.
15
+ * these to embedding apps (the host wrapper bridges the emitter
16
+ * seam / the bootstrap-failed envelope to its `onError` prop).
14
17
  *
15
18
  * The union is the failure-mode axis of `WireConfig`'s contract:
16
19
  * named parties = renderer ↔ host; obligation = the renderer emits a
@@ -25,7 +28,6 @@
25
28
  * fields) is fully closed — drift there is a real shape change, not
26
29
  * a new code.
27
30
  */
28
- import type { ContractErrorPayload } from '@ggui-ai/protocol';
29
31
  import { ClientContractViolationError } from '@ggui-ai/wire';
30
32
  /**
31
33
  * Closed-or-extensibly-closed union of bootstrap-failure reasons.
@@ -43,7 +45,7 @@ import { ClientContractViolationError } from '@ggui-ai/wire';
43
45
  * / `WS_HANDSHAKE_FAILED`. Matches `RendererBootFailureReason`.
44
46
  *
45
47
  * 3. **Transport-observable failures** — `BUNDLE_FETCH_FAILED` /
46
- * `CSP_VIOLATION` / `RENDER_NOT_FOUND` / `AUTH_REJECTED`. These
48
+ * `CSP_VIOLATION` / `SESSION_NOT_FOUND` / `AUTH_REJECTED`. These
47
49
  * fire before the renderer parse path runs, so they're authored
48
50
  * here rather than inside `McpAppAiGguiMetaParseFailureReason`.
49
51
  *
@@ -51,7 +53,14 @@ import { ClientContractViolationError } from '@ggui-ai/wire';
51
53
  * codes can land without a client bump, while IDE autocomplete
52
54
  * surfaces the canonical set.
53
55
  */
54
- export type BootstrapFailureReason = 'MISSING_TOOL_OUTPUT' | 'MISSING_META_GGUI_BOOTSTRAP' | 'BOOTSTRAP_META_MISSING' | 'MALFORMED_BOOTSTRAP' | 'EXPIRED_BOOTSTRAP' | 'UI_INITIALIZE_FAILED' | 'WS_HANDSHAKE_FAILED' | 'UPGRADE_REQUIRED' | 'BUNDLE_FETCH_FAILED' | 'CSP_VIOLATION' | 'RENDER_NOT_FOUND' | 'AUTH_REJECTED' | (string & {});
56
+ export type BootstrapFailureReason = 'MISSING_TOOL_OUTPUT' | 'MISSING_META_GGUI_BOOTSTRAP' | 'BOOTSTRAP_META_MISSING' | 'MALFORMED_BOOTSTRAP' | 'EXPIRED_BOOTSTRAP' | 'UI_INITIALIZE_FAILED' | 'WS_HANDSHAKE_FAILED' | 'UPGRADE_REQUIRED' | 'BUNDLE_FETCH_FAILED' | 'CSP_VIOLATION' | 'SESSION_NOT_FOUND' | 'AUTH_REJECTED' | (string & {});
57
+ /**
58
+ * Pre-ack auth-class error codes — the codes first-party servers
59
+ * ACTUALLY emit on the WS error frame before `ack` (SPEC §12.2.3).
60
+ * The phantom `AUTH_REJECTED` / `TOKEN_EXPIRED` arms (no first-party
61
+ * server ever minted them) were deleted in draft-2026-06-12.
62
+ */
63
+ export type AuthFailureCode = 'SESSION_NOT_FOUND' | 'BOOTSTRAP_EXPIRED' | 'BOOTSTRAP_INVALID' | 'BOOTSTRAP_SESSION_MISMATCH' | 'BOOTSTRAP_APP_MISMATCH' | 'UNAUTHENTICATED';
55
64
  /**
56
65
  * Every error the renderer surfaces outward flows through this union.
57
66
  *
@@ -60,18 +69,20 @@ export type BootstrapFailureReason = 'MISSING_TOOL_OUTPUT' | 'MISSING_META_GGUI_
60
69
  * - `transport` — WebSocket-level failure (DISCONNECTED / TIMEOUT).
61
70
  * Includes `retryable` so hosts can decide to reconnect
62
71
  * vs. give up.
63
- * - `auth` — session / token rejection pre-handshake.
64
- * `RENDER_NOT_FOUND` / `TOKEN_EXPIRED` /
65
- * `AUTH_REJECTED` — all terminal; hosts typically
66
- * escalate to a login flow.
67
- * - `protocol` — envelope / session-mismatch failures post-handshake.
72
+ * - `auth` — session / ws-token rejection pre-ack, carrying
73
+ * the server's REAL §12.2.3 vocabulary:
74
+ * `SESSION_NOT_FOUND`, the `BOOTSTRAP_*` ws-token
75
+ * family, and `UNAUTHENTICATED`. `BOOTSTRAP_EXPIRED`
76
+ * has a refresh path (`ggui_runtime_refresh_ws_token`);
77
+ * the rest are terminal — hosts typically escalate
78
+ * to a re-render / login flow.
79
+ * - `protocol` — envelope / render-mismatch failures post-handshake.
68
80
  * Extensibly-closed on `code` because servers may
69
81
  * introduce new codes without a client bump.
70
82
  * `details` carries opaque structured context.
71
- * - `contract` — client-contract violation. Payload matches the
72
- * protocol-owned `ContractErrorPayload` so the
73
- * `_ggui:contract-error` envelope shape flows
74
- * unchanged through this variant.
83
+ * Client-side contract violations ride this variant
84
+ * as `code: 'CLIENT_CONTRACT_VIOLATION'` (see
85
+ * {@link fromClientContractViolation}).
75
86
  * - `bootstrap` — anything in {@link BootstrapFailureReason}.
76
87
  * - `version` — `UPGRADE_REQUIRED` path (handshake rejection).
77
88
  * `serverVersion` may be absent on the wire; callers
@@ -87,16 +98,13 @@ export type ProtocolError = {
87
98
  readonly message?: string;
88
99
  } | {
89
100
  readonly kind: 'auth';
90
- readonly code: 'RENDER_NOT_FOUND' | 'TOKEN_EXPIRED' | 'AUTH_REJECTED';
101
+ readonly code: AuthFailureCode;
91
102
  readonly message?: string;
92
103
  } | {
93
104
  readonly kind: 'protocol';
94
105
  readonly code: 'SESSION_MISMATCH' | 'APP_MISMATCH' | 'MALFORMED_ENVELOPE' | (string & {});
95
106
  readonly message?: string;
96
107
  readonly details?: unknown;
97
- } | {
98
- readonly kind: 'contract';
99
- readonly payload: ContractErrorPayload;
100
108
  } | {
101
109
  readonly kind: 'bootstrap';
102
110
  readonly reason: BootstrapFailureReason;
@@ -115,9 +123,7 @@ export type ProtocolError = {
115
123
  *
116
124
  * This is OPTIONAL on every emitter site — the default is a tagged
117
125
  * `console.warn` so operators see the error in dev. The
118
- * `<McpAppIframe>` host wrapper bridges this to its `onError` prop;
119
- * session-bound variants are ALSO routed to the
120
- * `_ggui:contract-error` envelope over the WebSocket.
126
+ * `<McpAppIframe>` host wrapper bridges this to its `onError` prop.
121
127
  *
122
128
  * Handlers MUST NOT throw — the emitter already fired the renderer's
123
129
  * own fallback path; a throwing handler would mask the real error.
@@ -127,7 +133,7 @@ export type ProtocolError = {
127
133
  export type ProtocolErrorEmitter = (err: ProtocolError) => void;
128
134
  /**
129
135
  * Default emitter — operator-visible `console.warn` with a grep-
130
- * friendly tag. Identical posture to `GguiSession.surfaceContractViolation`'s
136
+ * friendly tag. Identical posture to `GguiRender.surfaceContractViolation`'s
131
137
  * console.warn fallback. Callers override by injecting their own.
132
138
  */
133
139
  export declare function defaultProtocolErrorEmitter(err: ProtocolError): void;
@@ -137,28 +143,12 @@ export declare function defaultProtocolErrorEmitter(err: ProtocolError): void;
137
143
  * outbound action, or when an inbound stream / props payload fails the
138
144
  * local validator) onto a `ProtocolError`.
139
145
  *
140
- * Client-side violations do NOT ride the server-emitted
141
- * `_ggui:contract-error` envelope — that shape (`ContractErrorPayload`)
142
- * is tool-failure-scoped and authored by the session-channel router.
143
- * A client-side violation is a DIFFERENT category: nothing reached the
144
- * tool. We surface it as `{kind: 'protocol'; code:
146
+ * A client-side violation never left the renderer — nothing reached
147
+ * the server. We surface it as `{kind: 'protocol'; code:
145
148
  * 'CLIENT_CONTRACT_VIOLATION'}` with structured `details` so hosts can
146
149
  * render the same summary that `console.warn` would have produced.
147
- *
148
- * Server-emitted `ContractErrorPayload` landing on
149
- * `_ggui:contract-error` rides the separate `{kind: 'contract'}`
150
- * variant — that path activates when the renderer observes the
151
- * reserved channel.
152
150
  */
153
151
  export declare function fromClientContractViolation(err: ClientContractViolationError): ProtocolError;
154
- /**
155
- * Wrap a server-emitted `ContractErrorPayload` (received over the
156
- * reserved `_ggui:contract-error` channel) as the 'contract' variant.
157
- * Trivial wrapper — exists so call sites produce the union via a
158
- * factory rather than an object literal (keeps emission uniform and
159
- * makes grep for creation sites exact).
160
- */
161
- export declare function fromContractErrorPayload(payload: ContractErrorPayload): ProtocolError;
162
152
  /**
163
153
  * Build a `ProtocolError` of kind 'version' from the existing
164
154
  * `UpgradeRequiredError` class. Matches the `liftUpgradeRequiredFromError`
@@ -193,7 +183,7 @@ export declare function fromUnknown(raw: unknown): ProtocolError;
193
183
  */
194
184
  export declare function fromTransportFailure(code: 'DISCONNECTED' | 'TIMEOUT', retryable: boolean, message?: string): ProtocolError;
195
185
  /**
196
- * Build a `ProtocolError` of kind 'auth' — session / token rejection
197
- * pre-handshake.
186
+ * Build a `ProtocolError` of kind 'auth' — session / ws-token rejection
187
+ * pre-ack (see {@link AuthFailureCode}).
198
188
  */
199
- export declare function fromAuthFailure(code: 'RENDER_NOT_FOUND' | 'TOKEN_EXPIRED' | 'AUTH_REJECTED', message?: string): ProtocolError;
189
+ export declare function fromAuthFailure(code: AuthFailureCode, message?: string): ProtocolError;