@ggui-ai/mcp-server 0.1.0-rc.3 → 0.2.0-alpha.3

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 (59) hide show
  1. package/README.md +1 -1
  2. package/dist/build-mcp.d.ts +8 -8
  3. package/dist/build-mcp.d.ts.map +1 -1
  4. package/dist/build-mcp.js +53 -12
  5. package/dist/console-auth.d.ts +7 -7
  6. package/dist/console-auth.d.ts.map +1 -1
  7. package/dist/console-auth.js +4 -4
  8. package/dist/console-cache.d.ts +2 -2
  9. package/dist/console-cache.d.ts.map +1 -1
  10. package/dist/console-cache.js +10 -10
  11. package/dist/console-headers.d.ts +2 -2
  12. package/dist/console-headers.js +2 -2
  13. package/dist/console-payloads.d.ts +3 -3
  14. package/dist/console-payloads.d.ts.map +1 -1
  15. package/dist/console-payloads.js +10 -10
  16. package/dist/console-timeline.d.ts +12 -30
  17. package/dist/console-timeline.d.ts.map +1 -1
  18. package/dist/console-timeline.js +44 -45
  19. package/dist/index.d.ts +7 -7
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +5 -5
  22. package/dist/instructions-presets.d.ts +1 -1
  23. package/dist/instructions-presets.d.ts.map +1 -1
  24. package/dist/instructions-presets.js +40 -22
  25. package/dist/llm-backed-negotiator.d.ts +6 -6
  26. package/dist/llm-backed-negotiator.d.ts.map +1 -1
  27. package/dist/llm-backed-negotiator.js +92 -91
  28. package/dist/mcp-apps-inbound.d.ts +3 -3
  29. package/dist/mcp-apps-inbound.d.ts.map +1 -1
  30. package/dist/mcp-apps-inbound.js +28 -23
  31. package/dist/mcp-apps-outbound.d.ts +136 -121
  32. package/dist/mcp-apps-outbound.d.ts.map +1 -1
  33. package/dist/mcp-apps-outbound.js +384 -414
  34. package/dist/mcp-mounts.d.ts +21 -20
  35. package/dist/mcp-mounts.d.ts.map +1 -1
  36. package/dist/mcp-mounts.js +25 -29
  37. package/dist/{session-channel.d.ts → render-channel.d.ts} +145 -102
  38. package/dist/render-channel.d.ts.map +1 -0
  39. package/dist/{session-channel.js → render-channel.js} +481 -462
  40. package/dist/schema-compat.d.ts +11 -11
  41. package/dist/schema-compat.d.ts.map +1 -1
  42. package/dist/schema-compat.js +6 -6
  43. package/dist/server.d.ts +224 -219
  44. package/dist/server.d.ts.map +1 -1
  45. package/dist/server.js +1498 -1963
  46. package/dist/storage.d.ts +7 -7
  47. package/dist/storage.d.ts.map +1 -1
  48. package/dist/storage.js +12 -12
  49. package/package.json +12 -11
  50. package/dist/render-gate.d.ts +0 -87
  51. package/dist/render-gate.d.ts.map +0 -1
  52. package/dist/render-gate.js +0 -77
  53. package/dist/render-rate-limit.d.ts +0 -59
  54. package/dist/render-rate-limit.d.ts.map +0 -1
  55. package/dist/render-rate-limit.js +0 -73
  56. package/dist/render-signing.d.ts +0 -98
  57. package/dist/render-signing.d.ts.map +0 -1
  58. package/dist/render-signing.js +0 -113
  59. package/dist/session-channel.d.ts.map +0 -1
@@ -4,7 +4,7 @@
4
4
  * The live channel is where the typed-channel contract is enforced on
5
5
  * live traffic between the server and the user. It co-hosts on the
6
6
  * same Express server as `/mcp` and reuses the same
7
- * `@ggui-ai/mcp-server-handlers/session-mutations` helpers that the
7
+ * `@ggui-ai/mcp-server-handlers/renders` helpers that the
8
8
  * closed hosted server consumes.
9
9
  *
10
10
  * Scope:
@@ -14,15 +14,15 @@
14
14
  * - `action` → inbound user action carried as an {@link ActionEnvelope}.
15
15
  * Gated through `assertEventAllowed` (allowlist) +
16
16
  * `assertActionContract` (payload, for data:submit). Persisted to
17
- * SessionStore as a typed session event.
17
+ * RenderStore as a typed session event.
18
18
  * - `ping`/`pong` → heartbeat parity with hosted.
19
19
  * - `close`/socket-close → clean subscriber teardown.
20
- * - `sendToSession(sessionId, data)` → outbound fan-out API for
20
+ * - `sendToSession(renderId, data)` → outbound fan-out API for
21
21
  * mutation handlers (ggui_emit / connector `ctx.send`). Validated
22
22
  * through `assertStreamContract` before delivery.
23
23
  *
24
24
  * `props_update`: mount handlers dispatched through the wired-action
25
- * router can call `ctx.sendPropsUpdate(stackItemId, props)` to fan a
25
+ * router can call `ctx.sendPropsUpdate(renderId, props)` to fan a
26
26
  * `{type:'props_update'}` frame to live subscribers without going
27
27
  * through a refresh-stream path. Reaches the renderer's existing
28
28
  * `props_update` branch in `iframe-runtime` and applies new props
@@ -32,26 +32,27 @@
32
32
  * Not handled here:
33
33
  *
34
34
  * - Pattern-B daemon-agent notification stream (GET /mcp SSE).
35
- * - Short-lived session-token mint/consume — that's ggui_push-gated.
35
+ * - Short-lived session-token mint/consume — that's ggui_render-gated.
36
36
  * Dev-mode auth (any bearer via existing AuthAdapter) matches the
37
37
  * `/mcp` endpoint's shape and is operator-replaceable.
38
38
  */
39
- import type { IncomingMessage } from 'node:http';
40
- import type { Duplex } from 'node:stream';
41
- import type { JsonObject, ReservedChannelValidator, SanitizeCausedBy, SessionStackEntry } from '@ggui-ai/protocol';
42
- import type { AuthAdapter, SessionStore, SessionStreamBuffer, StreamEnvelopeInput, StreamFanout, TelemetrySink } from '@ggui-ai/mcp-server-core';
43
- import type { Logger } from './logger.js';
39
+ import type { AuthAdapter, RenderStore, SessionStreamBuffer, StreamEnvelopeInput, StreamFanout, TelemetrySink } from "@ggui-ai/mcp-server-core";
40
+ import type { JsonObject, Render, ReservedChannelValidator, SanitizeCausedBy } from "@ggui-ai/protocol";
41
+ import type { WebSocketMessage } from "@ggui-ai/protocol/transport/websocket";
42
+ import type { IncomingMessage } from "node:http";
43
+ import type { Duplex } from "node:stream";
44
+ import type { Logger } from "./logger.js";
44
45
  /** Default URL path for the channel endpoint. Operators can override. */
45
- export declare const DEFAULT_SESSION_CHANNEL_PATH = "/ws";
46
+ export declare const DEFAULT_RENDER_CHANNEL_PATH = "/ws";
46
47
  /**
47
48
  * Opt-in plumbing for the `channel_subscribe` polling loop. When this
48
- * field is set on {@link SessionChannelOptions}, channel subscribes
49
+ * field is set on {@link RenderChannelOptions}, channel subscribes
49
50
  * whose `source.tool` is in {@link allowlist} are accepted and the
50
51
  * server begins polling. When absent, every `channel_subscribe`
51
52
  * returns `CHANNEL_NOT_LOCAL` so the iframe falls back to direct
52
53
  * polling via the MCP host proxy.
53
54
  */
54
- export interface SessionChannelLocalToolsOptions {
55
+ export interface RenderChannelLocalToolsOptions {
55
56
  /**
56
57
  * Whitelist of `source.tool` names this channel can poll. Must mirror
57
58
  * the value the host advertises on
@@ -92,13 +93,13 @@ export interface SessionChannelLocalToolsOptions {
92
93
  * message (`SubscribePayload.bootstrap`). When present:
93
94
  *
94
95
  * 1. `verify(token)` is called. Must return the bound
95
- * `{sessionId, appId}` on success, or `null` on any failure
96
+ * `{renderId, appId}` on success, or `null` on any failure
96
97
  * (invalid sig, expired, wrong kind, replayed, etc.).
97
- * 2. The bound `sessionId` MUST match the one on the subscribe
98
+ * 2. The bound `renderId` MUST match the one on the subscribe
98
99
  * payload. Mismatches are rejected with a clean error.
99
100
  * 3. On success, the server mints a reconnect credential via
100
- * `issueSessionToken(sessionId, appId)` and returns it in
101
- * `AckPayload.sessionToken`. The iframe stores this for WS
101
+ * `issueSessionToken(renderId, appId)` and returns it in
102
+ * `AckPayload.renderToken`. The iframe stores this for WS
102
103
  * reconnects via the normal bearer path.
103
104
  *
104
105
  * Bootstrap auth is MUTUALLY EXCLUSIVE with the upstream `AuthAdapter`
@@ -118,16 +119,16 @@ export interface SessionChannelLocalToolsOptions {
118
119
  * handshake. Past expiry, the iframe MAY refresh via the
119
120
  * {@link refresh} surface; past the refresh window, fresh handshake.
120
121
  */
121
- export type SessionChannelBootstrapVerifyResult = {
122
+ export type RenderChannelBootstrapVerifyResult = {
122
123
  readonly ok: true;
123
- readonly sessionId: string;
124
+ readonly renderId: string;
124
125
  readonly appId: string;
125
126
  } | {
126
127
  readonly ok: false;
127
- readonly reason: 'expired' | 'invalid';
128
+ readonly reason: "expired" | "invalid";
128
129
  };
129
130
  /**
130
- * Result of {@link SessionChannelBootstrap.refresh}.
131
+ * Result of {@link RenderChannelBootstrap.refresh}.
131
132
  *
132
133
  * - `ok: true`: caller swaps the old envelope for `token` and resumes.
133
134
  * - `ok: false`: caller MUST re-handshake (refresh window closed,
@@ -139,9 +140,9 @@ export type SessionChannelBootstrapRefreshResult = {
139
140
  readonly expiresAt: string;
140
141
  } | {
141
142
  readonly ok: false;
142
- readonly reason: 'window_closed' | 'invalid';
143
+ readonly reason: "window_closed" | "invalid";
143
144
  };
144
- export interface SessionChannelBootstrap {
145
+ export interface RenderChannelBootstrap {
145
146
  /**
146
147
  * Verify a `SubscribePayload.bootstrap` token.
147
148
  *
@@ -151,23 +152,23 @@ export interface SessionChannelBootstrap {
151
152
  * `BOOTSTRAP_INVALID` for tamper / format / kind failures (no
152
153
  * refresh on those).
153
154
  */
154
- verify(token: string): SessionChannelBootstrapVerifyResult;
155
+ verify(token: string): RenderChannelBootstrapVerifyResult;
155
156
  /**
156
157
  * Mint a longer-lived reconnect credential to return in
157
- * `AckPayload.sessionToken`. Called only after a successful
158
+ * `AckPayload.renderToken`. Called only after a successful
158
159
  * `verify()` on a bootstrap subscribe.
159
160
  */
160
- issueSessionToken(sessionId: string, appId: string): string;
161
+ issueSessionToken(renderId: string, appId: string): string;
161
162
  /**
162
163
  * Refresh a (possibly-expired-but-signature-valid) bootstrap envelope
163
164
  * into a new envelope with a fresh TTL. Used by the
164
165
  * `ggui_runtime_refresh_bootstrap` MCP tool — iframes that see their
165
166
  * bootstrap drift out of the TTL window swap in the refreshed
166
- * envelope without going back through `ggui_push`.
167
+ * envelope without going back through `ggui_render`.
167
168
  *
168
169
  * Stateless: verifies HMAC against the same secret used at mint,
169
170
  * checks the refresh window against the ORIGINAL `iat`, and mints
170
- * a fresh bootstrap envelope bound to the SAME `(sessionId, appId)`.
171
+ * a fresh bootstrap envelope bound to the SAME `(renderId, appId)`.
171
172
  * Past the refresh window the result is `{ok:false, reason:
172
173
  * 'window_closed'}`; tampered envelopes are `{ok:false, reason:
173
174
  * 'invalid'}`.
@@ -176,7 +177,7 @@ export interface SessionChannelBootstrap {
176
177
  }
177
178
  /**
178
179
  * Default timeout for a single wired-tool invocation, in ms. Operators
179
- * override via {@link SessionChannelOptions.wiredActionTimeoutMs}; the
180
+ * override via {@link RenderChannelOptions.wiredActionTimeoutMs}; the
180
181
  * 30 s ceiling is a honest non-promise: long-running tools MUST design
181
182
  * their own completion path (streaming, polling).
182
183
  */
@@ -221,7 +222,7 @@ export declare const DEFAULT_WIRED_TOOL_TIMEOUT_MS = 30000;
221
222
  * shape every shared handler — ggui-native AND mounted — accepts. It
222
223
  * stays narrow on purpose (`appId`, `requestId`, optional `apiKeyHash`)
223
224
  * so the surface a host implements is stable. Wired-action runtime
224
- * fields (`sessionId`, `stackItemId`, `sendPropsUpdate`) are dispatcher-
225
+ * fields (`renderId`, `renderId`, `sendPropsUpdate`) are dispatcher-
225
226
  * specific — only mount tools invoked through the wired-action router
226
227
  * see them, only at dispatch time. Passing them as a third arg to
227
228
  * `invoke` keeps the canonical handler shape untouched and makes
@@ -230,32 +231,24 @@ export declare const DEFAULT_WIRED_TOOL_TIMEOUT_MS = 30000;
230
231
  * The composer in `mcp-mounts.ts::composeWiredActionRouterFromMounts`
231
232
  * synthesizes a runtime ctx for the mount handler that satisfies
232
233
  * `HandlerContext` AND structurally carries these wired fields, so a
233
- * mount fixture can read `ctx.sendPropsUpdate` / `ctx.stackItemId` from the
234
+ * mount fixture can read `ctx.sendPropsUpdate` / `ctx.renderId` from the
234
235
  * same `ctx` argument the canonical `HandlerContext` sig types — no
235
236
  * cast, no widening of the static type.
236
237
  */
237
238
  export interface WiredActionContext {
238
- /** The session this dispatch is bound to. Sourced from the live
239
- * subscriber + the action envelope's spoof-guarded `sessionId`. */
240
- readonly sessionId: string;
241
- /** The active stack item's stackItemId at dispatch time. Mounts that
242
- * fire `sendPropsUpdate` typically pass this verbatim — only a
243
- * mount that intentionally targets a sibling stack entry would
244
- * pick a different value. */
245
- readonly stackItemId: string;
246
- /**
247
- * Push a `{type:'props_update', payload:{stackItemId, props}}` frame to
248
- * every live subscriber bound to this dispatcher's `sessionId`. The
249
- * call closes over the `SessionChannelServer.sendPropsUpdate` method,
250
- * scoped to the active session for safety — even if a buggy mount
251
- * picks a `stackItemId` that doesn't exist on the session, the channel
252
- * server's own validation no-ops the fan-out (same posture as
253
- * `notifyStackPush` orphan handling).
239
+ /** The render this dispatch is bound to. Sourced from the live
240
+ * subscriber + the action envelope's spoof-guarded `renderId`. */
241
+ readonly renderId: string;
242
+ /**
243
+ * Push a `{type:'props_update', payload:{renderId, props}}` frame to
244
+ * every live subscriber bound to this dispatcher's `renderId`. The
245
+ * call closes over the `RenderChannelServer.sendPropsUpdate` method,
246
+ * scoped to the active render for safety.
254
247
  *
255
248
  * Best-effort: per-subscriber send failures are swallowed; a closed
256
249
  * socket is a no-op.
257
250
  */
258
- sendPropsUpdate(stackItemId: string, props: JsonObject): void;
251
+ sendPropsUpdate(props: JsonObject): void;
259
252
  }
260
253
  export interface WiredActionRouter {
261
254
  /** Returns `true` when the named tool has a registered handler. Used
@@ -278,9 +271,9 @@ export interface WiredActionRouter {
278
271
  */
279
272
  invoke(toolName: string, input: Record<string, unknown>, ctx: WiredActionContext): Promise<unknown>;
280
273
  }
281
- export interface SessionChannelOptions {
282
- /** Required — the session backing store (typically `InMemorySessionStore`). */
283
- readonly sessionStore: SessionStore;
274
+ export interface RenderChannelOptions {
275
+ /** Required — the render backing store (typically `InMemoryRenderStore`). */
276
+ readonly renderStore: RenderStore;
284
277
  /**
285
278
  * Required — the same `AuthAdapter` the `/mcp` endpoint uses. Any
286
279
  * failure during `subscribe` rejects the upgrade with HTTP 401.
@@ -319,16 +312,16 @@ export interface SessionChannelOptions {
319
312
  /**
320
313
  * Optional bootstrap-auth plumbing. When present, the channel
321
314
  * accepts `SubscribePayload.bootstrap` and issues reconnect
322
- * credentials in `AckPayload.sessionToken`. When absent, bootstrap
315
+ * credentials in `AckPayload.renderToken`. When absent, bootstrap
323
316
  * tokens are rejected with `BOOTSTRAP_NOT_SUPPORTED`.
324
317
  */
325
- readonly bootstrap?: SessionChannelBootstrap;
318
+ readonly bootstrap?: RenderChannelBootstrap;
326
319
  /**
327
320
  * Optional console cookie-auth plumbing. When present, the
328
321
  * channel upgrade looks for the configured cookie on the incoming
329
322
  * request. A valid cookie binds the identity as a `builder` and
330
- * scopes the subscriber to the cookie's `sessionId` — any
331
- * `subscribe.sessionId` mismatch is rejected with
323
+ * scopes the subscriber to the cookie's `renderId` — any
324
+ * `subscribe.renderId` mismatch is rejected with
332
325
  * `DEVTOOL_COOKIE_SESSION_MISMATCH`.
333
326
  *
334
327
  * Absent = cookie auth disabled on this channel. Cookies are never
@@ -339,7 +332,7 @@ export interface SessionChannelOptions {
339
332
  * bootstrap path (via `?bootstrap=` query) wins; cookie is only
340
333
  * consulted for standard upgrades.
341
334
  */
342
- readonly cookieAuth?: SessionChannelCookieAuth;
335
+ readonly cookieAuth?: RenderChannelCookieAuth;
343
336
  /**
344
337
  * Opt-in wired-action dispatch router. When present, validated
345
338
  * `data:submit` envelopes whose declared `actionSpec[name]
@@ -373,7 +366,7 @@ export interface SessionChannelOptions {
373
366
  *
374
367
  * The contract-error envelope flows on `_ggui:contract-error` with
375
368
  * `replay: 'all'`, so anything that lands in `causedBy` persists in
376
- * the session ring buffer and surfaces in SessionInspector. Accepting
369
+ * the session ring buffer and surfaces in RenderInspector. Accepting
377
370
  * raw `err.stack` verbatim is a credential-leak footgun; the default
378
371
  * sanitizer is load-bearing.
379
372
  */
@@ -422,7 +415,7 @@ export interface SessionChannelOptions {
422
415
  * `handshake.serverCapabilities.streamWebSocketLocalTools` so iframe
423
416
  * + server agree on which channels use the WS fan-out path.
424
417
  */
425
- readonly streamWebSocketLocalTools?: SessionChannelLocalToolsOptions;
418
+ readonly streamWebSocketLocalTools?: RenderChannelLocalToolsOptions;
426
419
  /**
427
420
  * Protocol-version handshake policy. Governs server behavior when a
428
421
  * subscribe declares a `supportedVersions` list that does NOT
@@ -448,31 +441,63 @@ export interface SessionChannelOptions {
448
441
  * not a schema change — the wire fields and error code ship
449
442
  * identically in both modes.
450
443
  */
451
- readonly versionPolicy?: 'advisory' | 'reject';
444
+ readonly versionPolicy?: "advisory" | "reject";
445
+ /**
446
+ * Optional hook fired synchronously when the local subscriber count
447
+ * for `renderId` transitions 0 → 1 (the first subscriber for that
448
+ * session connects to this server instance).
449
+ *
450
+ * Hosted deployments use this to lazily SUBSCRIBE to the per-session
451
+ * cross-pod broadcast channel (e.g. Redis pub/sub); OSS has no use
452
+ * for it (in-process broadcasts already route via
453
+ * {@link RenderChannelServer.sendPropsUpdate}). Bounding pubsub
454
+ * fan-in to only sessions a pod actually holds connections for is a
455
+ * correctness requirement, not an optimization — without it every
456
+ * pod receives every other pod's broadcast for every active session.
457
+ *
458
+ * Best-effort: a thrown callback is logged and swallowed.
459
+ * `register()` MUST NOT fail because of a hook error or the
460
+ * `wsSubscribers` set would drift out of sync with the real socket
461
+ * lifecycle.
462
+ *
463
+ * Concurrent register/unregister for the same renderId are serialized
464
+ * by the channel's single-threaded WS event loop; hook implementations
465
+ * do not need their own mutex for the 0↔1 transition.
466
+ */
467
+ readonly onFirstSubscriber?: (renderId: string) => void;
468
+ /**
469
+ * Optional hook fired synchronously when the local subscriber count
470
+ * for `renderId` transitions 1 → 0 (the last subscriber for that
471
+ * session disconnects).
472
+ *
473
+ * Symmetric with {@link onFirstSubscriber}; same best-effort posture
474
+ * and single-threaded serialization guarantee.
475
+ */
476
+ readonly onLastSubscriberGone?: (renderId: string) => void;
452
477
  }
453
478
  /**
454
479
  * Cookie-based authentication for the live-channel upgrade. Used
455
480
  * exclusively by the same-origin console viewer; see
456
481
  * `console-auth.ts` for the single consumer today.
457
482
  */
458
- export interface SessionChannelCookieAuth {
483
+ export interface RenderChannelCookieAuth {
459
484
  /**
460
485
  * Read the raw cookie value for THIS server's console cookie
461
486
  * from the incoming request headers. Returns `null` when the
462
487
  * cookie is absent or malformed.
463
488
  */
464
- readCookie(headers: import('node:http').IncomingHttpHeaders): string | null;
489
+ readCookie(headers: import("node:http").IncomingHttpHeaders): string | null;
465
490
  /**
466
491
  * Verify a cookie value and return the bound session/app. Returns
467
492
  * `null` on any failure (signature, expiry, wrong kind). Never
468
493
  * throws.
469
494
  */
470
495
  verify(cookieValue: string): {
471
- sessionId: string;
496
+ renderId: string;
472
497
  appId: string;
473
498
  } | null;
474
499
  }
475
- export interface SessionChannelServer {
500
+ export interface RenderChannelServer {
476
501
  /** The URL path the channel accepts upgrade requests on. */
477
502
  readonly path: string;
478
503
  /**
@@ -482,7 +507,7 @@ export interface SessionChannelServer {
482
507
  */
483
508
  handleUpgrade(req: IncomingMessage, socket: Duplex, head: Buffer): void;
484
509
  /**
485
- * Deliver a stream envelope to every subscriber of `delivery.sessionId`.
510
+ * Deliver a stream envelope to every subscriber of `delivery.renderId`.
486
511
  *
487
512
  * Outbound fan-out enforcement point — the delivery's `payload` is
488
513
  * validated against the active stack item's streamSpec via
@@ -513,44 +538,44 @@ export interface SessionChannelServer {
513
538
  seq: number;
514
539
  }>;
515
540
  /**
516
- * Fan a `{type:'push', payload:{stackItem, matchType?}}` wire frame
517
- * to every subscriber currently bound to `sessionId`. Use this to
518
- * notify already-subscribed clients about a stack mutation that
519
- * happened AFTER they subscribed — the initial `ack.stack` snapshot
541
+ * Fan a `{type:'render', payload:{render, matchType?}}` wire frame
542
+ * to every subscriber currently bound to `renderId`. Use this to
543
+ * notify already-subscribed clients about a render-commit that
544
+ * happened AFTER they subscribed — the initial `ack.render` snapshot
520
545
  * covers state at subscribe time only, so without an explicit notify
521
- * a second-turn `appendStackItem` is invisible to the live client.
546
+ * a second-turn `commit` is invisible to the live client.
522
547
  *
523
548
  * The `B1` regression context (2026-04-22 QA pass): the chat surface
524
- * in `/chat` reuses one session across turns. The first turn's push
525
- * subscribed AFTER `appendStackItem` ran, so the ack carried the
526
- * entry. The second turn's push appended on the live session — the
549
+ * in `/chat` reuses one render across turns. The first turn's render
550
+ * subscribed AFTER `commit` ran, so the ack carried the
551
+ * entry. The second turn's commit landed on the live render — the
527
552
  * subscriber never heard about the new entry, the inline UI slot
528
- * stayed in "Waiting for session channel replay…" indefinitely.
529
- * `notifyStackPush` closes that gap. Best-effort: per-subscriber
553
+ * stayed in "Waiting for render channel replay…" indefinitely.
554
+ * `notifyRenderPush` closes that gap. Best-effort: per-subscriber
530
555
  * send failures are swallowed (same posture as `sendToSession`).
531
556
  *
532
557
  * NOT durable. Frames are not stamped through the replay buffer —
533
- * fresh subscribers still get the current stack via `ack.stack` on
534
- * subscribe. A new tab opening mid-session reads the latest stack
558
+ * fresh subscribers still get the current render via `ack.render` on
559
+ * subscribe. A new tab opening mid-render reads the latest render
535
560
  * from the snapshot; live tabs read the delta from this notify.
536
561
  *
537
562
  * Subscribers received via `register()` are tracked in
538
563
  * `subscribersBySession`; this helper iterates the bound set and
539
564
  * skips closed sockets via the `send()` helper's existing guard.
540
565
  * Callers ARE responsible for ordering — call after the underlying
541
- * `sessionStore.appendStackItem` resolves so the snapshot a
566
+ * `renderStore.commit` resolves so the snapshot a
542
567
  * concurrent fresh subscriber observes still includes the entry.
543
568
  */
544
- notifyStackPush(sessionId: string, stackItem: SessionStackEntry, matchType?: string): void;
569
+ notifyRenderPush(renderId: string, render: Render, matchType?: string): void;
545
570
  /**
546
- * Prime every declared streamSpec channel on `stackItem` that carries a
571
+ * Prime every declared streamSpec channel on `render` that carries a
547
572
  * `tool` refresh hint. Invokes each refresh tool via the bound
548
573
  * wiredActionRouter with the empty refresh input, validates the result
549
574
  * against the channel's schema, and fans it out so subscribers see an
550
575
  * initial value instead of `latest = undefined`.
551
576
  *
552
- * Intended for seed-a-session mount paths (console try-live, agent-
553
- * initiated bootstraps that append a stack item without a driving
577
+ * Intended for seed-a-render mount paths (console try-live, agent-
578
+ * initiated bootstraps that mint a render without a driving
554
579
  * action). Without this, blueprints with `streamSpec[channel].tool`
555
580
  * render their empty-state branch because the channel has no live
556
581
  * delivery — operators see "waiting for data" UI even when the
@@ -559,41 +584,41 @@ export interface SessionChannelServer {
559
584
  * Same isolation posture as the action-driven refresh pass: one
560
585
  * broken refresh MUST NOT block others; per-channel failures log a
561
586
  * warning but don't throw. No-op when no wiredActionRouter is
562
- * configured, when `stackItem.streamSpec` is absent, or when every
587
+ * configured, when `render.streamSpec` is absent, or when every
563
588
  * channel lacks a `.tool` hint.
564
589
  *
565
- * Ordering: callers should invoke AFTER the stack item is persisted
566
- * so `sendToSession`'s active-stack-item lookup resolves. The
590
+ * Ordering: callers should invoke AFTER the render is persisted
591
+ * so `sendToSession`'s active-render lookup resolves. The
567
592
  * `try-live` endpoint awaits this before returning the shortCode so
568
593
  * the viewer SPA subscribes with the initial envelope already
569
- * buffered on the session's stream-buffer replay state.
594
+ * buffered on the render's stream-buffer replay state.
570
595
  */
571
- primeStreams(sessionId: string, stackItem: SessionStackEntry): Promise<void>;
596
+ primeStreams(renderId: string, render: Render): Promise<void>;
572
597
  /**
573
- * Fan a `{type:'props_update', payload:{stackItemId, props}}` wire frame
574
- * to every subscriber currently bound to `sessionId`. Mount tools
598
+ * Fan a `{type:'props_update', payload:{renderId, props}}` wire frame
599
+ * to every subscriber currently bound to `renderId`. Mount tools
575
600
  * dispatched through {@link WiredActionRouter} call this via
576
601
  * `WiredActionContext.sendPropsUpdate` so a wired action that
577
602
  * mutates server-side state can replace renderer props in-place
578
603
  * without going through a refresh-stream tool.
579
604
  *
580
- * Validation posture (mirrors `notifyStackPush`'s "best-effort orphan
605
+ * Validation posture (mirrors `notifyRenderPush`'s "best-effort orphan
581
606
  * no-op"):
582
- * 1. Look up the session via `sessionStore.get`. Absent → log
607
+ * 1. Look up the session via `renderStore.get`. Absent → log
583
608
  * `session_channel_props_update_orphan` and return — the wire
584
609
  * validator on the renderer side would reject a frame for an
585
610
  * unknown session anyway.
586
- * 2. Look up the target stack entry by `stackItemId` in the loaded
611
+ * 2. Look up the target stack entry by `renderId` in the loaded
587
612
  * session's stack. Absent → log
588
613
  * `session_channel_props_update_pageid_unknown` and return.
589
614
  * 3. Iterate the flat WS-subscriber set, filter to subscribers
590
- * whose `sessionId` matches, and `send()` the frame. Closed
615
+ * whose `renderId` matches, and `send()` the frame. Closed
591
616
  * sockets are skipped silently by `send()`.
592
617
  *
593
618
  * NOT routed through StreamFanout — `type: 'props_update'` is a
594
619
  * distinct WebSocket message type, not a stream envelope. Stream
595
620
  * envelopes flow on `data` frames and have a `seq` cursor; props
596
- * updates are ephemeral and follow `notifyStackPush`'s pattern
621
+ * updates are ephemeral and follow `notifyRenderPush`'s pattern
597
622
  * (live-only, no replay-buffer stamping). A new subscriber that
598
623
  * connects mid-session reads current `props` from the stack
599
624
  * snapshot delivered in `ack.stack`.
@@ -601,17 +626,17 @@ export interface SessionChannelServer {
601
626
  * Schema validation against `propsSpec`: NOT enforced server-side
602
627
  * here. The renderer validates inbound props via
603
628
  * `validateInboundPropsPayload` against the cached
604
- * `stackItem.propsSpec` before applying — defense-in-depth at the
629
+ * `render.propsSpec` before applying — defense-in-depth at the
605
630
  * receiving boundary. Server-side enforcement is reserved for the
606
631
  * future agent-driven `ggui_update` path; the mount-tool seam is
607
632
  * trusted-runtime today (mounts execute in-process, same trust
608
633
  * boundary as ggui-native handlers).
609
634
  */
610
- sendPropsUpdate(sessionId: string, stackItemId: string, props: JsonObject): Promise<void>;
635
+ sendPropsUpdate(renderId: string, props: JsonObject): Promise<void>;
611
636
  /**
612
- * Fan a `{type:'drain_ack', payload:{sessionId, appId, stackItemId,
637
+ * Fan a `{type:'drain_ack', payload:{renderId, appId, renderId,
613
638
  * eventId, drainedAt}}` wire frame to every subscriber currently
614
- * bound to `sessionId`.
639
+ * bound to `renderId`.
615
640
  *
616
641
  * Fired by `createGguiConsumeHandler` once per drained
617
642
  * `PendingEvent` so the iframe-runtime can cancel the matching
@@ -619,19 +644,37 @@ export interface SessionChannelServer {
619
644
  * Implements the `DrainAckNotifier` contract from
620
645
  * `@ggui-ai/mcp-server-handlers`.
621
646
  *
622
- * Same posture as `notifyStackPush` / `sendPropsUpdate` — live-only,
647
+ * Same posture as `notifyRenderPush` / `sendPropsUpdate` — live-only,
623
648
  * no replay-buffer stamping. Subscribers that connect AFTER the
624
649
  * drain see the next consume's snapshot rather than the missed
625
650
  * frame; the iframe's claim timer + atomic-pop primitive backstop
626
651
  * any frame loss.
627
652
  */
628
653
  sendDrainAck(args: {
629
- readonly sessionId: string;
654
+ readonly renderId: string;
630
655
  readonly appId: string;
631
- readonly stackItemId: string;
632
656
  readonly eventId: string;
633
657
  readonly drainedAt: string;
634
658
  }): void;
659
+ /**
660
+ * Fan a server-frame to every local WS subscriber bound to
661
+ * `renderId`. Skips replay-buffer stamping, RenderStore lookups,
662
+ * and contract validation — the caller is the one that originally
663
+ * validated + persisted the underlying mutation. This surface is the
664
+ * cloud adapter's path for delivering already-validated frames that
665
+ * arrived via an external pubsub layer (Redis from another pod).
666
+ *
667
+ * Internal adapter use only. NOT part of the published ggui
668
+ * protocol, NOT stable across versions, NOT exposed to MCP / wire
669
+ * callers. The publisher is responsible for ensuring `frame` is
670
+ * wire-valid; this method does not re-validate.
671
+ *
672
+ * No-op when no local subscriber is bound to `renderId`. Closed
673
+ * sockets are skipped silently by the underlying `send()` helper —
674
+ * same posture as `sendPropsUpdate` / `notifyRenderPush`. Per-
675
+ * subscriber send failures are logged but never propagated.
676
+ */
677
+ externalBroadcast(renderId: string, frame: WebSocketMessage): void;
635
678
  /** Number of live subscribers. Useful for health / debug introspection. */
636
679
  readonly subscriberCount: number;
637
680
  /** Number of distinct sessions with at least one subscriber. */
@@ -645,7 +688,7 @@ export interface SessionChannelServer {
645
688
  * Build an OSS live-channel server. The returned object is designed to be
646
689
  * composed into `createGguiServer` — see `server.ts` for the wire-up.
647
690
  */
648
- export declare function createSessionChannelServer(opts: SessionChannelOptions): SessionChannelServer;
691
+ export declare function createRenderChannelServer(opts: RenderChannelOptions): RenderChannelServer;
649
692
  /** Fabricate a request id for live-channel ops so logs correlate. */
650
693
  export declare function newRequestId(): string;
651
- //# sourceMappingURL=session-channel.d.ts.map
694
+ //# sourceMappingURL=render-channel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render-channel.d.ts","sourceRoot":"","sources":["../src/render-channel.ts"],"names":[],"mappings":"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"}