@ggui-ai/iframe-runtime 0.2.0-alpha.4 → 0.4.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 (58) 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 +68 -78
  21. package/dist/iframe-runtime.meta.json +1 -0
  22. package/dist/index.d.ts +4 -4
  23. package/dist/index.js +1 -1
  24. package/dist/lifecycle.d.ts +3 -3
  25. package/dist/lifecycle.js +29 -6
  26. package/dist/mcp-app-iframe-host.d.ts +8 -8
  27. package/dist/mcp-app-iframe-host.js +4 -4
  28. package/dist/meta-parse.d.ts +3 -4
  29. package/dist/meta-parse.js +17 -44
  30. package/dist/observability.d.ts +14 -36
  31. package/dist/observability.js +2 -1
  32. package/dist/protocol-error.d.ts +38 -48
  33. package/dist/protocol-error.js +5 -23
  34. package/dist/provisional-renderer.js +3 -3
  35. package/dist/react-renderer.d.ts +16 -6
  36. package/dist/react-renderer.js +22 -40
  37. package/dist/registry-subscribe.d.ts +7 -7
  38. package/dist/registry-subscribe.js +28 -14
  39. package/dist/render-item.d.ts +22 -15
  40. package/dist/render-item.js +7 -14
  41. package/dist/runtime.d.ts +79 -105
  42. package/dist/runtime.js +219 -269
  43. package/dist/status-dom.d.ts +12 -12
  44. package/dist/status-dom.js +11 -11
  45. package/dist/system-cards/NoCredentialsCard.d.ts +1 -1
  46. package/dist/system-cards/NoCredentialsCard.js +1 -1
  47. package/dist/system-cards/ProtocolProbeCard.d.ts +13 -17
  48. package/dist/system-cards/ProtocolProbeCard.js +21 -25
  49. package/dist/system-cards/index.d.ts +5 -5
  50. package/dist/system-cards/index.js +5 -5
  51. package/dist/types.d.ts +29 -34
  52. package/dist/validation.d.ts +4 -5
  53. package/dist/validation.js +5 -6
  54. package/dist/wire-config.d.ts +43 -106
  55. package/dist/wire-config.js +29 -195
  56. package/package.json +14 -12
  57. package/dist/channels/feedback.d.ts +0 -14
  58. package/dist/channels/feedback.js +0 -19
@@ -15,7 +15,7 @@
15
15
  *
16
16
  * - **Iframe polling** — fallback for every other channel. The
17
17
  * router runs a setInterval that fires `tools/call` against
18
- * the parent MCP host (Pattern α — direct, no bridge). Works
18
+ * the parent MCP host (direct, no bridge). Works
19
19
  * for any tool the agent has access to, including third-party
20
20
  * MCP servers the local pod can't subscribe-for.
21
21
  *
@@ -33,10 +33,11 @@
33
33
  * fallback (don't double-deliver).
34
34
  *
35
35
  * The WS-level exponential backoff (1s → 2s → 4s → ... cap 60s) lives
36
- * on the {@link RendererWebSocketManager}. The router only watches
37
- * status transitions; it doesn't own the reconnect schedule.
36
+ * on `@ggui-ai/live-channel`'s `WSTransport` (post-B3b owner of the
37
+ * reconnect ladder). The router only watches status transitions; it
38
+ * doesn't own the reconnect schedule.
38
39
  *
39
- * **Idempotence** — re-subscribing the same `(renderId, channelName)`
40
+ * **Idempotence** — re-subscribing the same `(sessionId, channelName)`
40
41
  * pair is server-side idempotent (the server's `ChannelSubscriptionState`
41
42
  * replaces in place). Clients can re-send on reconnect without bookkeeping
42
43
  * a "did we already subscribe?" gate.
@@ -53,20 +54,21 @@
53
54
  */
54
55
  import type { JsonObject, JsonValue, StreamSpec } from '@ggui-ai/protocol';
55
56
  import type { WebSocketMessage } from '@ggui-ai/protocol/transport/websocket';
57
+ import type { ChannelTransportFallbackEvent, ChannelTransportPickedEvent, ChannelTransportResubscribedEvent } from './observability.js';
56
58
  import type { StreamBus } from './wire-config.js';
57
59
  /**
58
60
  * Default polling cadence for the iframe-polling transport (ms).
59
61
  * Mirrors the server-side default on
60
62
  * {@link DEFAULT_CHANNEL_POLL_DEFAULT_MS} in
61
- * `packages/mcp-server/src/session-channel.ts`. Conservative: 10s
63
+ * `packages/mcp-server/src/render-channel.ts`. Conservative: 10s
62
64
  * matches a "data is fresh enough" bar without burning the parent
63
65
  * host's `tools/call` quota.
64
66
  */
65
67
  export declare const DEFAULT_IFRAME_POLL_INTERVAL_MS = 10000;
66
68
  /**
67
- * Sender for outbound WS frames. The renderer's
68
- * {@link RendererWebSocketManager.send} fits structurally — pass
69
- * `manager.send.bind(manager)` or the buffered manager shim.
69
+ * Sender for outbound WS frames. Any structurally-fitting send fn
70
+ * works — post-B3b the registry wires the `@ggui-ai/live-channel`
71
+ * transport's send here.
70
72
  */
71
73
  export type WsSender = (msg: WebSocketMessage) => void;
72
74
  /**
@@ -83,9 +85,9 @@ export type ToolsCallInvoker = (args: {
83
85
  * Options for {@link createChannelTransportRouter}.
84
86
  */
85
87
  export interface ChannelTransportRouterOptions {
86
- /** Render id the router scopes channel_subscribe frames against. */
87
- readonly renderId: string;
88
- /** App (tenant) id paired with renderId on the subscribe frame. */
88
+ /** GguiSession id the router scopes channel_subscribe frames against. */
89
+ readonly sessionId: string;
90
+ /** App (tenant) id paired with sessionId on the subscribe frame. */
89
91
  readonly appId: string;
90
92
  /**
91
93
  * Allowlist of `source.tool` names this server can subscribe-for
@@ -116,24 +118,12 @@ export interface ChannelTransportRouterOptions {
116
118
  /**
117
119
  * Observability events the router emits. Hosts that wire `onObserve`
118
120
  * see these alongside the renderer's other observability events
119
- * (`subscribe-failed`, `wired-tool-invoked`, …). The shape stays
120
- * narrow + non-breaking so future events can extend the union.
121
+ * (`subscribe-failed`, `auth-required`, …). The arm interfaces live in
122
+ * `observability.ts` (they are members of the exported
123
+ * `ObservabilityEvent` union); this alias is the router-scoped subset,
124
+ * so the two surfaces cannot drift.
121
125
  */
122
- export type ChannelTransportEvent = {
123
- readonly kind: 'channel-transport-picked';
124
- readonly renderId: string;
125
- readonly channelName: string;
126
- readonly transport: 'ws' | 'poll';
127
- } | {
128
- readonly kind: 'channel-transport-fallback';
129
- readonly renderId: string;
130
- readonly channelName: string;
131
- readonly reason: 'ws-disconnect' | 'channel-not-local';
132
- } | {
133
- readonly kind: 'channel-transport-resubscribed';
134
- readonly renderId: string;
135
- readonly channelName: string;
136
- };
126
+ export type ChannelTransportEvent = ChannelTransportPickedEvent | ChannelTransportFallbackEvent | ChannelTransportResubscribedEvent;
137
127
  /**
138
128
  * Router handle returned by {@link createChannelTransportRouter}.
139
129
  */
@@ -148,7 +138,7 @@ export interface ChannelTransportRouter {
148
138
  * the source-fed subset.
149
139
  */
150
140
  readonly applyRender: (render: {
151
- readonly renderId: string;
141
+ readonly sessionId: string;
152
142
  readonly streamSpec?: StreamSpec;
153
143
  }) => void;
154
144
  /**
@@ -175,7 +165,7 @@ export interface ChannelTransportRouter {
175
165
  /**
176
166
  * Factory.
177
167
  *
178
- * Per-channel transport routing — owns the `(renderId, channelName)`
168
+ * Per-channel transport routing — owns the `(sessionId, channelName)`
179
169
  * registry, the WS-vs-poll decision per channel, and the cross-state
180
170
  * transitions (disconnect → poll fallback, reconnect → re-subscribe).
181
171
  */
@@ -2,7 +2,7 @@
2
2
  * Default polling cadence for the iframe-polling transport (ms).
3
3
  * Mirrors the server-side default on
4
4
  * {@link DEFAULT_CHANNEL_POLL_DEFAULT_MS} in
5
- * `packages/mcp-server/src/session-channel.ts`. Conservative: 10s
5
+ * `packages/mcp-server/src/render-channel.ts`. Conservative: 10s
6
6
  * matches a "data is fresh enough" bar without burning the parent
7
7
  * host's `tools/call` quota.
8
8
  */
@@ -10,13 +10,13 @@ export const DEFAULT_IFRAME_POLL_INTERVAL_MS = 10_000;
10
10
  /**
11
11
  * Factory.
12
12
  *
13
- * Per-channel transport routing — owns the `(renderId, channelName)`
13
+ * Per-channel transport routing — owns the `(sessionId, channelName)`
14
14
  * registry, the WS-vs-poll decision per channel, and the cross-state
15
15
  * transitions (disconnect → poll fallback, reconnect → re-subscribe).
16
16
  */
17
17
  export function createChannelTransportRouter(opts) {
18
18
  /**
19
- * Registry keyed by `${renderId}:${channelName}`. The composite
19
+ * Registry keyed by `${sessionId}:${channelName}`. The composite
20
20
  * key matches the server-side `channelSubs` map shape exactly, so
21
21
  * test snapshots line up.
22
22
  */
@@ -34,8 +34,8 @@ export function createChannelTransportRouter(opts) {
34
34
  let wsStatus = 'connected';
35
35
  let disposed = false;
36
36
  const observe = opts.onObserve ?? (() => { });
37
- function keyOf(renderId, channelName) {
38
- return `${renderId}:${channelName}`;
37
+ function keyOf(sessionId, channelName) {
38
+ return `${sessionId}:${channelName}`;
39
39
  }
40
40
  function stopPolling(state) {
41
41
  if (state.pollTimer !== null) {
@@ -45,7 +45,7 @@ export function createChannelTransportRouter(opts) {
45
45
  }
46
46
  function emitToBus(state, payload, complete) {
47
47
  const envelope = {
48
- renderId: state.renderId,
48
+ sessionId: state.sessionId,
49
49
  channel: state.channelName,
50
50
  mode: state.mode,
51
51
  payload,
@@ -90,14 +90,14 @@ export function createChannelTransportRouter(opts) {
90
90
  }
91
91
  /**
92
92
  * Send a `channel_subscribe` WS frame. Server-side bookkeeping is
93
- * idempotent on `(renderId, channelName)`, so re-sends on reconnect
93
+ * idempotent on `(sessionId, channelName)`, so re-sends on reconnect
94
94
  * are safe.
95
95
  */
96
96
  function sendSubscribe(state) {
97
97
  opts.send({
98
98
  type: 'channel_subscribe',
99
99
  payload: {
100
- renderId: state.renderId,
100
+ sessionId: state.sessionId,
101
101
  appId: opts.appId,
102
102
  channelName: state.channelName,
103
103
  ...(state.args !== undefined ? { args: { ...state.args } } : {}),
@@ -114,7 +114,7 @@ export function createChannelTransportRouter(opts) {
114
114
  opts.send({
115
115
  type: 'channel_unsubscribe',
116
116
  payload: {
117
- renderId: state.renderId,
117
+ sessionId: state.sessionId,
118
118
  appId: opts.appId,
119
119
  channelName: state.channelName,
120
120
  },
@@ -137,7 +137,7 @@ export function createChannelTransportRouter(opts) {
137
137
  sendSubscribe(state);
138
138
  observe({
139
139
  kind: 'channel-transport-picked',
140
- renderId: state.renderId,
140
+ sessionId: state.sessionId,
141
141
  channelName: state.channelName,
142
142
  transport: 'ws',
143
143
  });
@@ -148,7 +148,7 @@ export function createChannelTransportRouter(opts) {
148
148
  startPolling(state);
149
149
  observe({
150
150
  kind: 'channel-transport-picked',
151
- renderId: state.renderId,
151
+ sessionId: state.sessionId,
152
152
  channelName: state.channelName,
153
153
  transport: 'poll',
154
154
  });
@@ -157,7 +157,7 @@ export function createChannelTransportRouter(opts) {
157
157
  startPolling(state);
158
158
  observe({
159
159
  kind: 'channel-transport-picked',
160
- renderId: state.renderId,
160
+ sessionId: state.sessionId,
161
161
  channelName: state.channelName,
162
162
  transport: 'poll',
163
163
  });
@@ -184,18 +184,18 @@ export function createChannelTransportRouter(opts) {
184
184
  const channelArgs = source.args !== undefined && source.args !== null
185
185
  ? source.args
186
186
  : undefined;
187
- const k = keyOf(render.renderId, channelName);
187
+ const k = keyOf(render.sessionId, channelName);
188
188
  seenKeys.add(k);
189
189
  const existing = channels.get(k);
190
190
  if (existing !== undefined) {
191
- // Same (renderId, channelName) — leave the transport
191
+ // Same (sessionId, channelName) — leave the transport
192
192
  // bookkeeping alone. Spec changes that flip preferWs
193
193
  // mid-render are out of 1c scope (would require
194
194
  // re-handshake).
195
195
  continue;
196
196
  }
197
197
  const state = {
198
- renderId: render.renderId,
198
+ sessionId: render.sessionId,
199
199
  channelName,
200
200
  toolName,
201
201
  ...(channelArgs !== undefined ? { args: channelArgs } : {}),
@@ -213,7 +213,7 @@ export function createChannelTransportRouter(opts) {
213
213
  // mounts that haven't been re-applied yet. Close-render paths
214
214
  // call `dispose()` for the wholesale teardown.
215
215
  for (const [k, state] of channels) {
216
- if (state.renderId === render.renderId &&
216
+ if (state.sessionId === render.sessionId &&
217
217
  !seenKeys.has(k)) {
218
218
  stopPolling(state);
219
219
  sendUnsubscribe(state);
@@ -226,7 +226,7 @@ export function createChannelTransportRouter(opts) {
226
226
  return false;
227
227
  if (msg.type === 'channel_payload') {
228
228
  const p = msg.payload;
229
- const k = keyOf(p.renderId, p.channelName);
229
+ const k = keyOf(p.sessionId, p.channelName);
230
230
  const state = channels.get(k);
231
231
  if (state === undefined)
232
232
  return false;
@@ -244,7 +244,7 @@ export function createChannelTransportRouter(opts) {
244
244
  if (msg.type === 'channel_error') {
245
245
  const p = msg.payload;
246
246
  // Locate by `channelName` only — the error payload doesn't
247
- // carry `renderId`. We match by channel-name across active
247
+ // carry `sessionId`. We match by channel-name across active
248
248
  // items and apply the permanent-fallback flag to the matching
249
249
  // entries. Same channel name across renders is rare; the
250
250
  // policy is conservative (fall back ALL matches rather than
@@ -254,12 +254,12 @@ export function createChannelTransportRouter(opts) {
254
254
  continue;
255
255
  if (p.code === 'CHANNEL_NOT_LOCAL' ||
256
256
  p.code === 'CHANNEL_UNKNOWN' ||
257
- p.code === 'RENDER_NOT_FOUND') {
257
+ p.code === 'SESSION_NOT_FOUND') {
258
258
  state.permanentPollFallback = true;
259
259
  startPolling(state);
260
260
  observe({
261
261
  kind: 'channel-transport-fallback',
262
- renderId: state.renderId,
262
+ sessionId: state.sessionId,
263
263
  channelName: state.channelName,
264
264
  reason: 'channel-not-local',
265
265
  });
@@ -298,7 +298,7 @@ export function createChannelTransportRouter(opts) {
298
298
  startPolling(state);
299
299
  observe({
300
300
  kind: 'channel-transport-fallback',
301
- renderId: state.renderId,
301
+ sessionId: state.sessionId,
302
302
  channelName: state.channelName,
303
303
  reason: 'ws-disconnect',
304
304
  });
@@ -309,8 +309,8 @@ export function createChannelTransportRouter(opts) {
309
309
  if (status === 'connected' &&
310
310
  (prevStatus === 'disconnected' || prevStatus === 'reconnecting')) {
311
311
  // Reconnected. Re-send `channel_subscribe` for every
312
- // WS-preferring channel. Server is idempotent on the (session,
313
- // render, channel) tuple, so duplicates are safe. We
312
+ // WS-preferring channel. Server is idempotent on the
313
+ // (render, channel) tuple, so duplicates are safe. We
314
314
  // intentionally LEAVE the polling fallback running until the
315
315
  // first `channel_payload` lands — bridges the gap where the
316
316
  // server's first post-reconnect poll cycle hasn't fired yet.
@@ -322,7 +322,7 @@ export function createChannelTransportRouter(opts) {
322
322
  sendSubscribe(state);
323
323
  observe({
324
324
  kind: 'channel-transport-resubscribed',
325
- renderId: state.renderId,
325
+ sessionId: state.sessionId,
326
326
  channelName: state.channelName,
327
327
  });
328
328
  }
@@ -2,24 +2,17 @@
2
2
  * `data` channel handler — validates inbound stream-envelope payloads
3
3
  * against the active render's `streamSpec`, then fans the validated
4
4
  * envelope out via `streamBus`. Invalid payloads drop silently
5
- * (matching `GguiSession.handleServerMessage`'s surfacing policy —
5
+ * (matching `GguiRender.handleServerMessage`'s surfacing policy —
6
6
  * the violation is already logged by the validator).
7
7
  *
8
- * Also fires `contract-error-emitted` on the optional observability
9
- * sink when the envelope arrives on the reserved
10
- * `CONTRACT_ERROR_CHANNEL`. Absorbed from `handleObservableMessage`
11
- * in `runtime.ts` as part of the B3b cleanup — the handler is now the
12
- * sole dispatch surface for `data` frames.
13
- *
14
8
  * Post-stack-removal (2026-05-27): the active render is read through
15
- * the caller-supplied `getCurrentRender` thunk instead of via a
9
+ * the caller-supplied `getCurrentGguiSession` thunk instead of via a
16
10
  * `StackModel.snapshot()` walk — the iframe holds exactly one mounted
17
11
  * render, so the lookup is direct.
18
12
  */
19
13
  import type { ChannelHandler } from '@ggui-ai/live-channel';
20
- import { type Render, type StreamEnvelope } from '@ggui-ai/protocol';
21
- import type { RenderSeedInput } from '../types.js';
22
- import type { ObservabilityEmitter } from '../observability.js';
14
+ import type { GguiSession, StreamEnvelope } from '@ggui-ai/protocol';
15
+ import type { GguiSessionSeedInput } from '../types.js';
23
16
  import { type RendererValidatorContext } from '../validation.js';
24
17
  import type { StreamBus } from '../wire-config.js';
25
18
  export interface DataHandlerDeps {
@@ -28,16 +21,8 @@ export interface DataHandlerDeps {
28
21
  * has been mounted yet — data frames received pre-mount have no
29
22
  * streamSpec to validate against and silently drop.
30
23
  */
31
- readonly getCurrentRender: () => Render | RenderSeedInput | null;
24
+ readonly getCurrentGguiSession: () => GguiSession | GguiSessionSeedInput | null;
32
25
  readonly streamBus: StreamBus;
33
26
  readonly validatorCtx: RendererValidatorContext;
34
- /**
35
- * Optional observability sink. When present, every envelope on the
36
- * reserved `_ggui:contract-error` channel fires a
37
- * {@link ContractErrorEmittedEvent}. Absent = the observation skips
38
- * silently (matches the pre-B3b posture when no `onObserve` is
39
- * bound).
40
- */
41
- readonly onObserve?: ObservabilityEmitter;
42
27
  }
43
28
  export declare function createDataHandler(deps: DataHandlerDeps): ChannelHandler<StreamEnvelope>;
@@ -2,21 +2,14 @@
2
2
  * `data` channel handler — validates inbound stream-envelope payloads
3
3
  * against the active render's `streamSpec`, then fans the validated
4
4
  * envelope out via `streamBus`. Invalid payloads drop silently
5
- * (matching `GguiSession.handleServerMessage`'s surfacing policy —
5
+ * (matching `GguiRender.handleServerMessage`'s surfacing policy —
6
6
  * the violation is already logged by the validator).
7
7
  *
8
- * Also fires `contract-error-emitted` on the optional observability
9
- * sink when the envelope arrives on the reserved
10
- * `CONTRACT_ERROR_CHANNEL`. Absorbed from `handleObservableMessage`
11
- * in `runtime.ts` as part of the B3b cleanup — the handler is now the
12
- * sole dispatch surface for `data` frames.
13
- *
14
8
  * Post-stack-removal (2026-05-27): the active render is read through
15
- * the caller-supplied `getCurrentRender` thunk instead of via a
9
+ * the caller-supplied `getCurrentGguiSession` thunk instead of via a
16
10
  * `StackModel.snapshot()` walk — the iframe holds exactly one mounted
17
11
  * render, so the lookup is direct.
18
12
  */
19
- import { CONTRACT_ERROR_CHANNEL, } from '@ggui-ai/protocol';
20
13
  import { validateInboundStreamPayload, } from '../validation.js';
21
14
  export function createDataHandler(deps) {
22
15
  return {
@@ -27,19 +20,9 @@ export function createDataHandler(deps) {
27
20
  typeof envelope.channel !== 'string') {
28
21
  return;
29
22
  }
30
- // Observability-axis emission for the reserved contract-error
31
- // channel. Fires BEFORE validation because the envelope shape
32
- // is owned by the server's reserved validator
33
- // (`BUILTIN_RESERVED_VALIDATORS`) — invalid payloads at this
34
- // boundary are server-side bugs the host inspector should still
35
- // surface, not silently drop.
36
- if (envelope.channel === CONTRACT_ERROR_CHANNEL &&
37
- deps.onObserve !== undefined) {
38
- emitContractErrorFromDataFrame(envelope, deps.onObserve);
39
- }
40
23
  // Active render carries the streamSpec — mirrors
41
- // `GguiSession.handleServerMessage`.
42
- const activeRender = deps.getCurrentRender();
24
+ // `GguiRender.handleServerMessage`.
25
+ const activeRender = deps.getCurrentGguiSession();
43
26
  const streamSpec = activeRender !== null &&
44
27
  activeRender.type !== 'mcpApps' &&
45
28
  activeRender.type !== 'system'
@@ -52,34 +35,3 @@ export function createDataHandler(deps) {
52
35
  },
53
36
  };
54
37
  }
55
- /**
56
- * Narrow a contract-error envelope and emit
57
- * {@link ContractErrorEmittedEvent}. Malformed envelopes silently
58
- * skip (trust-boundary posture — host inspector gets a row only when
59
- * the wire payload carries the required discriminator fields).
60
- */
61
- function emitContractErrorFromDataFrame(envelope, emit) {
62
- const payload = envelope.payload;
63
- if (payload === null || typeof payload !== 'object' || Array.isArray(payload)) {
64
- return;
65
- }
66
- const shaped = payload;
67
- const code = shaped.error !== undefined &&
68
- shaped.error !== null &&
69
- typeof shaped.error === 'object' &&
70
- typeof shaped.error.code === 'string'
71
- ? shaped.error.code
72
- : undefined;
73
- const toolName = typeof shaped.toolName === 'string' ? shaped.toolName : undefined;
74
- if (code === undefined || toolName === undefined)
75
- return;
76
- const event = {
77
- kind: 'contract-error-emitted',
78
- code,
79
- toolName,
80
- ...(typeof shaped.actionName === 'string'
81
- ? { actionName: shaped.actionName }
82
- : {}),
83
- };
84
- emit(event);
85
- }
@@ -2,6 +2,5 @@ export { createPropsUpdateHandler, type PropsUpdateHandlerDeps, } from './props-
2
2
  export { createDrainAckHandler, type DrainAckHandlerDeps, } from './drain-ack.js';
3
3
  export { createRenderHandler, type RenderHandlerDeps } from './render-frame.js';
4
4
  export { createDataHandler, type DataHandlerDeps } from './data.js';
5
- export { createFeedbackHandler } from './feedback.js';
6
5
  export { createChannelPayloadHandler, createChannelErrorHandler, type ChannelRouterHandlerDeps, } from './channel-payload.js';
7
6
  export { createSystemHandler, type SystemHandlerDeps } from './system.js';
@@ -2,6 +2,5 @@ export { createPropsUpdateHandler, } from './props-update.js';
2
2
  export { createDrainAckHandler, } from './drain-ack.js';
3
3
  export { createRenderHandler } from './render-frame.js';
4
4
  export { createDataHandler } from './data.js';
5
- export { createFeedbackHandler } from './feedback.js';
6
5
  export { createChannelPayloadHandler, createChannelErrorHandler, } from './channel-payload.js';
7
6
  export { createSystemHandler } from './system.js';
@@ -5,17 +5,17 @@
5
5
  *
6
6
  * Post-stack-removal (2026-05-27): each iframe holds exactly one
7
7
  * mounted render. The handler reads the current render via
8
- * `getCurrentRender()`, validates the inbound props against its
8
+ * `getCurrentGguiSession()`, validates the inbound props against its
9
9
  * cached `propsSpec`, and re-applies the patched render through
10
10
  * `applyRender` — the same callback the render handler uses, so the
11
11
  * React update surface is unified.
12
12
  *
13
13
  * Skips when:
14
- * - `renderId` is empty or not a string.
14
+ * - `sessionId` is empty or not a string.
15
15
  * - `props` is null / not an object (defensive — server can't emit
16
16
  * this shape, but the dispatcher routes the frame on type alone).
17
- * - No render is currently mounted (`getCurrentRender` returns null).
18
- * - The current render's id doesn't match `payload.renderId` —
17
+ * - No render is currently mounted (`getCurrentGguiSession` returns null).
18
+ * - The current render's id doesn't match `payload.sessionId` —
19
19
  * the server may have raced ahead of an in-flight render swap.
20
20
  * - The current render is `mcpApps` / `system` (no `propsSpec`;
21
21
  * server should never emit `props_update` for these).
@@ -23,14 +23,14 @@
23
23
  *
24
24
  * R6 (2026-05-26) retired the per-handler polling descriptor. Polling
25
25
  * is now registry-level — the iframe-runtime composes the
26
- * `/api/renders/:id/state?wsToken=<token>` URL and a snapshot-parsing
26
+ * `/api/sessions/:id/state?wsToken=<token>` URL and a snapshot-parsing
27
27
  * function once at bind time (see `runtime.ts`); a single fetch per
28
28
  * tick projects the slice envelope into per-handler frames the
29
29
  * `PollingTransport` dispatches.
30
30
  */
31
31
  import type { ChannelHandler } from '@ggui-ai/live-channel';
32
- import type { PropsUpdatePayload, Render } from '@ggui-ai/protocol';
33
- import type { RenderSeedInput } from '../types.js';
32
+ import type { PropsUpdatePayload, GguiSession } from '@ggui-ai/protocol';
33
+ import type { GguiSessionSeedInput } from '../types.js';
34
34
  export interface PropsUpdateHandlerDeps {
35
35
  /**
36
36
  * Read the currently-mounted render. Returns `null` when no
@@ -39,11 +39,11 @@ export interface PropsUpdateHandlerDeps {
39
39
  * `props_update` before the first render frame has no React tree to
40
40
  * patch.
41
41
  */
42
- readonly getCurrentRender: () => Render | RenderSeedInput | null;
42
+ readonly getCurrentGguiSession: () => GguiSession | GguiSessionSeedInput | null;
43
43
  /**
44
44
  * Re-apply the patched render to the single mount slot. Shared with
45
45
  * the render-frame handler so React updates flow through one path.
46
46
  */
47
- readonly applyRender: (render: Render | RenderSeedInput) => Promise<void>;
47
+ readonly applyRender: (render: GguiSession | GguiSessionSeedInput) => Promise<void>;
48
48
  }
49
49
  export declare function createPropsUpdateHandler(deps: PropsUpdateHandlerDeps): ChannelHandler<PropsUpdatePayload>;
@@ -5,17 +5,17 @@
5
5
  *
6
6
  * Post-stack-removal (2026-05-27): each iframe holds exactly one
7
7
  * mounted render. The handler reads the current render via
8
- * `getCurrentRender()`, validates the inbound props against its
8
+ * `getCurrentGguiSession()`, validates the inbound props against its
9
9
  * cached `propsSpec`, and re-applies the patched render through
10
10
  * `applyRender` — the same callback the render handler uses, so the
11
11
  * React update surface is unified.
12
12
  *
13
13
  * Skips when:
14
- * - `renderId` is empty or not a string.
14
+ * - `sessionId` is empty or not a string.
15
15
  * - `props` is null / not an object (defensive — server can't emit
16
16
  * this shape, but the dispatcher routes the frame on type alone).
17
- * - No render is currently mounted (`getCurrentRender` returns null).
18
- * - The current render's id doesn't match `payload.renderId` —
17
+ * - No render is currently mounted (`getCurrentGguiSession` returns null).
18
+ * - The current render's id doesn't match `payload.sessionId` —
19
19
  * the server may have raced ahead of an in-flight render swap.
20
20
  * - The current render is `mcpApps` / `system` (no `propsSpec`;
21
21
  * server should never emit `props_update` for these).
@@ -23,7 +23,7 @@
23
23
  *
24
24
  * R6 (2026-05-26) retired the per-handler polling descriptor. Polling
25
25
  * is now registry-level — the iframe-runtime composes the
26
- * `/api/renders/:id/state?wsToken=<token>` URL and a snapshot-parsing
26
+ * `/api/sessions/:id/state?wsToken=<token>` URL and a snapshot-parsing
27
27
  * function once at bind time (see `runtime.ts`); a single fetch per
28
28
  * tick projects the slice envelope into per-handler frames the
29
29
  * `PollingTransport` dispatches.
@@ -33,15 +33,15 @@ export function createPropsUpdateHandler(deps) {
33
33
  return {
34
34
  type: 'props_update',
35
35
  onMessage: async (payload) => {
36
- const { renderId, props } = payload;
37
- if (typeof renderId !== 'string' || renderId.length === 0)
36
+ const { sessionId, props } = payload;
37
+ if (typeof sessionId !== 'string' || sessionId.length === 0)
38
38
  return;
39
39
  if (props === null || typeof props !== 'object')
40
40
  return;
41
- const current = deps.getCurrentRender();
41
+ const current = deps.getCurrentGguiSession();
42
42
  if (current === null)
43
43
  return;
44
- if (current.id !== renderId)
44
+ if (current.id !== sessionId)
45
45
  return;
46
46
  if (current.type === 'mcpApps' || current.type === 'system')
47
47
  return;
@@ -8,7 +8,7 @@
8
8
  * exactly one mounted render for its lifetime. The handler routes the
9
9
  * inbound `render` to a caller-supplied `applyRender` callback that
10
10
  * either mounts (first frame) or re-applies (subsequent frames to the
11
- * same id). Frames addressed to a different `renderId` are out-of-spec
11
+ * same id). Frames addressed to a different `sessionId` are out-of-spec
12
12
  * and drop with a console warning — the host spawns a fresh iframe
13
13
  * per unique render id.
14
14
  *
@@ -18,7 +18,7 @@
18
18
  * happens. The boot-without-renderer path remains testable.
19
19
  */
20
20
  import type { ChannelHandler } from '@ggui-ai/live-channel';
21
- import type { RenderPayload, Render } from '@ggui-ai/protocol';
21
+ import type { RenderPayload, GguiSession } from '@ggui-ai/protocol';
22
22
  import type { ChannelTransportRouter } from '../channel-transport.js';
23
23
  import { type StatusRefs } from '../status-dom.js';
24
24
  export interface RenderHandlerDeps {
@@ -29,19 +29,19 @@ export interface RenderHandlerDeps {
29
29
  */
30
30
  readonly statusRefs: StatusRefs;
31
31
  /**
32
- * Pin — when set, render frames with a different `renderId`
32
+ * Pin — when set, render frames with a different `sessionId`
33
33
  * are dropped with a console warning. Set by the renderer hook to
34
34
  * the render id the iframe was bootstrapped against (every
35
35
  * post-displayMode iframe is pinned to a single render).
36
36
  */
37
- readonly pinnedRenderId?: string;
37
+ readonly pinnedSessionId?: string;
38
38
  /**
39
39
  * Apply the inbound render to the single mount slot. Absent in
40
40
  * placeholder mode (no React mount). When present, the handler
41
41
  * `await`s the apply before activating the per-channel transport
42
42
  * so the router targets the render React already mounted.
43
43
  */
44
- readonly applyRender?: (render: Render) => Promise<void>;
44
+ readonly applyRender?: (render: GguiSession) => Promise<void>;
45
45
  /**
46
46
  * Renderer-mode hook — returns the per-channel transport router so
47
47
  * the render handler can activate `source.tool` channel subscriptions
@@ -8,7 +8,7 @@
8
8
  * exactly one mounted render for its lifetime. The handler routes the
9
9
  * inbound `render` to a caller-supplied `applyRender` callback that
10
10
  * either mounts (first frame) or re-applies (subsequent frames to the
11
- * same id). Frames addressed to a different `renderId` are out-of-spec
11
+ * same id). Frames addressed to a different `sessionId` are out-of-spec
12
12
  * and drop with a console warning — the host spawns a fresh iframe
13
13
  * per unique render id.
14
14
  *
@@ -22,15 +22,15 @@ export function createRenderHandler(deps) {
22
22
  return {
23
23
  type: 'render',
24
24
  onMessage: async (payload) => {
25
- const render = payload.render;
26
- if (deps.pinnedRenderId !== undefined && render.id !== deps.pinnedRenderId) {
25
+ const render = payload.session;
26
+ if (deps.pinnedSessionId !== undefined && render.id !== deps.pinnedSessionId) {
27
27
  // Out-of-spec: each iframe is pinned to exactly one render id
28
28
  // post-displayMode-divergence; the host spawns a fresh iframe
29
29
  // per render. A frame for some other id means the host wired
30
30
  // the wrong render or the server is broadcasting cross-render
31
31
  // frames.
32
32
  // eslint-disable-next-line no-console
33
- console.warn(`[ggui:render] ignoring render for ${render.id} — iframe pinned to ${deps.pinnedRenderId}`);
33
+ console.warn(`[ggui:render] ignoring render for ${render.id} — iframe pinned to ${deps.pinnedSessionId}`);
34
34
  return;
35
35
  }
36
36
  if (deps.applyRender !== undefined) {
@@ -43,7 +43,7 @@ export function createRenderHandler(deps) {
43
43
  if (render.type === 'mcpApps' || render.type === 'system')
44
44
  return;
45
45
  channelTransport.applyRender({
46
- renderId: render.id,
46
+ sessionId: render.id,
47
47
  ...(render.streamSpec !== undefined
48
48
  ? { streamSpec: render.streamSpec }
49
49
  : {}),
@@ -14,7 +14,7 @@
14
14
  import type { CompiledContractValidators } from '@ggui-ai/protocol/integrations/mcp-apps';
15
15
  import type { ValidateFunction } from '@ggui-ai/protocol';
16
16
  /**
17
- * Precompiled validators for the active stack item's contract, keyed to
17
+ * Precompiled validators for the active render's contract, keyed to
18
18
  * match the four runtime-validated surfaces. `props` is a single
19
19
  * validator (the synthesized props wrapper); the other three are keyed
20
20
  * by action name / channel name / slot name.
@@ -49,7 +49,7 @@ export declare function loadCompiledValidators(meta: CompiledContractValidators
49
49
  * Response shape: an ES module whose `default` export is a
50
50
  * {@link CompiledContractValidators} object (produced server-side by
51
51
  * `bundleCompiledValidatorsAsModule`). `Cache-Control: immutable` so
52
- * repeat pushes with the same contract hit browser cache with no
52
+ * repeat renders with the same contract hit browser cache with no
53
53
  * round-trip.
54
54
  */
55
55
  export declare function loadCompiledValidatorsFromUrl(url: string | undefined, warn?: (message: string, detail: unknown) => void): Promise<CompiledValidatorSet>;
@@ -83,7 +83,7 @@ export async function loadCompiledValidators(meta, warn) {
83
83
  * Response shape: an ES module whose `default` export is a
84
84
  * {@link CompiledContractValidators} object (produced server-side by
85
85
  * `bundleCompiledValidatorsAsModule`). `Cache-Control: immutable` so
86
- * repeat pushes with the same contract hit browser cache with no
86
+ * repeat renders with the same contract hit browser cache with no
87
87
  * round-trip.
88
88
  */
89
89
  export async function loadCompiledValidatorsFromUrl(url, warn) {