@ggui-ai/mcp-server 0.2.0-alpha.4 → 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 (152) hide show
  1. package/dist/admin-blueprints-transport.d.ts.map +1 -1
  2. package/dist/admin-blueprints-transport.js +2 -1
  3. package/dist/admin-oauth-providers-transport.d.ts.map +1 -1
  4. package/dist/admin-oauth-providers-transport.js +7 -5
  5. package/dist/api-renders-routes.d.ts +85 -0
  6. package/dist/api-renders-routes.d.ts.map +1 -0
  7. package/dist/api-renders-routes.js +372 -0
  8. package/dist/build-mcp.d.ts +1 -1
  9. package/dist/build-mcp.d.ts.map +1 -1
  10. package/dist/build-mcp.js +39 -5
  11. package/dist/code-routes.d.ts +47 -0
  12. package/dist/code-routes.d.ts.map +1 -0
  13. package/dist/code-routes.js +81 -0
  14. package/dist/code-store-fs.js +2 -2
  15. package/dist/console-auth.d.ts +10 -10
  16. package/dist/console-auth.d.ts.map +1 -1
  17. package/dist/console-auth.js +5 -5
  18. package/dist/console-blueprint-routes.d.ts +71 -0
  19. package/dist/console-blueprint-routes.d.ts.map +1 -0
  20. package/dist/console-blueprint-routes.js +348 -0
  21. package/dist/console-chat-routes.d.ts +80 -0
  22. package/dist/console-chat-routes.d.ts.map +1 -0
  23. package/dist/console-chat-routes.js +182 -0
  24. package/dist/console-config-routes.d.ts +37 -0
  25. package/dist/console-config-routes.d.ts.map +1 -0
  26. package/dist/console-config-routes.js +91 -0
  27. package/dist/console-headers.d.ts +1 -1
  28. package/dist/console-info-routes.d.ts +84 -0
  29. package/dist/console-info-routes.d.ts.map +1 -0
  30. package/dist/console-info-routes.js +135 -0
  31. package/dist/console-keys-routes.d.ts +50 -0
  32. package/dist/console-keys-routes.d.ts.map +1 -0
  33. package/dist/console-keys-routes.js +222 -0
  34. package/dist/console-llm-keys-routes.d.ts +47 -0
  35. package/dist/console-llm-keys-routes.d.ts.map +1 -0
  36. package/dist/console-llm-keys-routes.js +443 -0
  37. package/dist/console-mcp-tools-routes.d.ts +41 -0
  38. package/dist/console-mcp-tools-routes.d.ts.map +1 -0
  39. package/dist/console-mcp-tools-routes.js +60 -0
  40. package/dist/console-registry-routes.d.ts +66 -0
  41. package/dist/console-registry-routes.d.ts.map +1 -0
  42. package/dist/console-registry-routes.js +276 -0
  43. package/dist/console-session-routes.d.ts +89 -0
  44. package/dist/console-session-routes.d.ts.map +1 -0
  45. package/dist/console-session-routes.js +385 -0
  46. package/dist/console-sessions-routes.d.ts +52 -0
  47. package/dist/console-sessions-routes.d.ts.map +1 -0
  48. package/dist/console-sessions-routes.js +106 -0
  49. package/dist/console-static-routes.d.ts +54 -0
  50. package/dist/console-static-routes.d.ts.map +1 -0
  51. package/dist/console-static-routes.js +190 -0
  52. package/dist/console-theme-routes.d.ts +3 -3
  53. package/dist/console-theme-routes.js +1 -1
  54. package/dist/console-timeline.d.ts +5 -5
  55. package/dist/console-timeline.d.ts.map +1 -1
  56. package/dist/console-timeline.js +27 -26
  57. package/dist/console-welcome.js +2 -2
  58. package/dist/email-login.d.ts.map +1 -1
  59. package/dist/email-login.js +2 -3
  60. package/dist/ggui-session-channel/action-ingress.d.ts +54 -0
  61. package/dist/ggui-session-channel/action-ingress.d.ts.map +1 -0
  62. package/dist/ggui-session-channel/action-ingress.js +228 -0
  63. package/dist/ggui-session-channel/channel-subscriptions.d.ts +97 -0
  64. package/dist/ggui-session-channel/channel-subscriptions.d.ts.map +1 -0
  65. package/dist/ggui-session-channel/channel-subscriptions.js +224 -0
  66. package/dist/ggui-session-channel/internal-types.d.ts +102 -0
  67. package/dist/ggui-session-channel/internal-types.d.ts.map +1 -0
  68. package/dist/ggui-session-channel/internal-types.js +6 -0
  69. package/dist/ggui-session-channel/outbound.d.ts +81 -0
  70. package/dist/ggui-session-channel/outbound.d.ts.map +1 -0
  71. package/dist/ggui-session-channel/outbound.js +174 -0
  72. package/dist/ggui-session-channel/socket-router.d.ts +38 -0
  73. package/dist/ggui-session-channel/socket-router.d.ts.map +1 -0
  74. package/dist/ggui-session-channel/socket-router.js +213 -0
  75. package/dist/ggui-session-channel/subscribe.d.ts +165 -0
  76. package/dist/ggui-session-channel/subscribe.d.ts.map +1 -0
  77. package/dist/ggui-session-channel/subscribe.js +370 -0
  78. package/dist/ggui-session-channel/subscriber-lifecycle.d.ts +40 -0
  79. package/dist/ggui-session-channel/subscriber-lifecycle.d.ts.map +1 -0
  80. package/dist/ggui-session-channel/subscriber-lifecycle.js +123 -0
  81. package/dist/ggui-session-channel.d.ts +425 -0
  82. package/dist/ggui-session-channel.d.ts.map +1 -0
  83. package/dist/ggui-session-channel.js +262 -0
  84. package/dist/health-routes.d.ts +76 -0
  85. package/dist/health-routes.d.ts.map +1 -0
  86. package/dist/health-routes.js +145 -0
  87. package/dist/index.d.ts +10 -11
  88. package/dist/index.d.ts.map +1 -1
  89. package/dist/index.js +8 -9
  90. package/dist/instructions-presets.d.ts +3 -3
  91. package/dist/instructions-presets.js +24 -24
  92. package/dist/llm-backed-negotiator.d.ts +68 -67
  93. package/dist/llm-backed-negotiator.d.ts.map +1 -1
  94. package/dist/llm-backed-negotiator.js +82 -221
  95. package/dist/mcp-apps-outbound.d.ts +47 -48
  96. package/dist/mcp-apps-outbound.d.ts.map +1 -1
  97. package/dist/mcp-apps-outbound.js +154 -177
  98. package/dist/mcp-endpoint-routes.d.ts +88 -0
  99. package/dist/mcp-endpoint-routes.d.ts.map +1 -0
  100. package/dist/mcp-endpoint-routes.js +359 -0
  101. package/dist/mcp-mounts.d.ts +2 -76
  102. package/dist/mcp-mounts.d.ts.map +1 -1
  103. package/dist/mcp-mounts.js +0 -76
  104. package/dist/oauth-as-routes.d.ts +60 -0
  105. package/dist/oauth-as-routes.d.ts.map +1 -0
  106. package/dist/oauth-as-routes.js +82 -0
  107. package/dist/oauth-clients-routes.d.ts +39 -0
  108. package/dist/oauth-clients-routes.d.ts.map +1 -0
  109. package/dist/oauth-clients-routes.js +87 -0
  110. package/dist/oauth-login-types.d.ts +1 -20
  111. package/dist/oauth-login-types.d.ts.map +1 -1
  112. package/dist/oauth-login-types.js +30 -7
  113. package/dist/oauth-login.d.ts.map +1 -1
  114. package/dist/oauth-login.js +3 -2
  115. package/dist/oauth-providers-store.d.ts.map +1 -1
  116. package/dist/oauth-providers-store.js +5 -5
  117. package/dist/oauth.d.ts +9 -8
  118. package/dist/oauth.d.ts.map +1 -1
  119. package/dist/oauth.js +41 -19
  120. package/dist/pairing-transport.d.ts.map +1 -1
  121. package/dist/pairing-transport.js +2 -1
  122. package/dist/request-context.d.ts +2 -2
  123. package/dist/request-context.js +2 -2
  124. package/dist/reserved-validators.d.ts.map +1 -1
  125. package/dist/reserved-validators.js +9 -1
  126. package/dist/route-param.d.ts +9 -0
  127. package/dist/route-param.d.ts.map +1 -0
  128. package/dist/route-param.js +10 -0
  129. package/dist/runtime-bundle-route.d.ts +43 -0
  130. package/dist/runtime-bundle-route.d.ts.map +1 -0
  131. package/dist/runtime-bundle-route.js +80 -0
  132. package/dist/schema-compat.d.ts +64 -62
  133. package/dist/schema-compat.d.ts.map +1 -1
  134. package/dist/schema-compat.js +23 -51
  135. package/dist/server.d.ts +179 -193
  136. package/dist/server.d.ts.map +1 -1
  137. package/dist/server.js +644 -3759
  138. package/dist/storage.d.ts +5 -5
  139. package/dist/storage.d.ts.map +1 -1
  140. package/dist/storage.js +5 -5
  141. package/dist/thread-transport.d.ts.map +1 -1
  142. package/dist/thread-transport.js +4 -3
  143. package/dist/user-session-auth.d.ts +7 -21
  144. package/dist/user-session-auth.d.ts.map +1 -1
  145. package/dist/user-session-auth.js +7 -28
  146. package/package.json +16 -15
  147. package/dist/mcp-apps-inbound.d.ts +0 -86
  148. package/dist/mcp-apps-inbound.d.ts.map +0 -1
  149. package/dist/mcp-apps-inbound.js +0 -283
  150. package/dist/render-channel.d.ts +0 -694
  151. package/dist/render-channel.d.ts.map +0 -1
  152. package/dist/render-channel.js +0 -1775
@@ -1,694 +0,0 @@
1
- /**
2
- * OSS live channel — live session plane over WebSocket.
3
- *
4
- * The live channel is where the typed-channel contract is enforced on
5
- * live traffic between the server and the user. It co-hosts on the
6
- * same Express server as `/mcp` and reuses the same
7
- * `@ggui-ai/mcp-server-handlers/renders` helpers that the
8
- * closed hosted server consumes.
9
- *
10
- * Scope:
11
- *
12
- * - `subscribe` → auth, resolve-or-create session, register subscriber,
13
- * reply `ack` with the session's current stack + sequence.
14
- * - `action` → inbound user action carried as an {@link ActionEnvelope}.
15
- * Gated through `assertEventAllowed` (allowlist) +
16
- * `assertActionContract` (payload, for data:submit). Persisted to
17
- * RenderStore as a typed session event.
18
- * - `ping`/`pong` → heartbeat parity with hosted.
19
- * - `close`/socket-close → clean subscriber teardown.
20
- * - `sendToSession(renderId, data)` → outbound fan-out API for
21
- * mutation handlers (ggui_emit / connector `ctx.send`). Validated
22
- * through `assertStreamContract` before delivery.
23
- *
24
- * `props_update`: mount handlers dispatched through the wired-action
25
- * router can call `ctx.sendPropsUpdate(renderId, props)` to fan a
26
- * `{type:'props_update'}` frame to live subscribers without going
27
- * through a refresh-stream path. Reaches the renderer's existing
28
- * `props_update` branch in `iframe-runtime` and applies new props
29
- * in-place. This seam is scoped to mount tools; the agent-driven
30
- * `ggui_update` path is handled separately.
31
- *
32
- * Not handled here:
33
- *
34
- * - Pattern-B daemon-agent notification stream (GET /mcp SSE).
35
- * - Short-lived session-token mint/consume — that's ggui_render-gated.
36
- * Dev-mode auth (any bearer via existing AuthAdapter) matches the
37
- * `/mcp` endpoint's shape and is operator-replaceable.
38
- */
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";
45
- /** Default URL path for the channel endpoint. Operators can override. */
46
- export declare const DEFAULT_RENDER_CHANNEL_PATH = "/ws";
47
- /**
48
- * Opt-in plumbing for the `channel_subscribe` polling loop. When this
49
- * field is set on {@link RenderChannelOptions}, channel subscribes
50
- * whose `source.tool` is in {@link allowlist} are accepted and the
51
- * server begins polling. When absent, every `channel_subscribe`
52
- * returns `CHANNEL_NOT_LOCAL` so the iframe falls back to direct
53
- * polling via the MCP host proxy.
54
- */
55
- export interface RenderChannelLocalToolsOptions {
56
- /**
57
- * Whitelist of `source.tool` names this channel can poll. Must mirror
58
- * the value the host advertises on
59
- * `handshake.serverCapabilities.streamWebSocketLocalTools` so the
60
- * iframe + server agree on which channels use the WS fan-out path.
61
- * Tools NOT in this list are rejected with `CHANNEL_NOT_LOCAL` (the
62
- * iframe falls back to direct polling).
63
- */
64
- readonly allowlist: readonly string[];
65
- /**
66
- * Synchronous resolver invoked at poll time. Returns the tool's
67
- * structured output (validated against `streamSpec[ch].schema`
68
- * client-side; server-side schema validation is deferred to the
69
- * future `validateContract` slice). Implementations typically
70
- * delegate to the same in-process tool registry that backs `/mcp`.
71
- *
72
- * Throwing surfaces `POLL_FAILED` on the subscriber's
73
- * `channel_error` channel without canceling the poll loop —
74
- * transient tool failures are recoverable.
75
- */
76
- invoke(name: string, input: unknown): Promise<unknown>;
77
- /**
78
- * Optional poll cadence policy. `defaultMs` applies when the client
79
- * doesn't supply a `pollIntervalMs`; `floorMs`/`ceilingMs` clamp
80
- * client-supplied values. Defaults:
81
- * `{floorMs: 1000, ceilingMs: 60000, defaultMs: 10000}`.
82
- */
83
- readonly pollCadence?: {
84
- readonly floorMs?: number;
85
- readonly ceilingMs?: number;
86
- readonly defaultMs?: number;
87
- };
88
- }
89
- /**
90
- * Bootstrap-auth plumbing for the live-channel endpoint.
91
- *
92
- * The channel accepts a bootstrap credential on the `subscribe`
93
- * message (`SubscribePayload.bootstrap`). When present:
94
- *
95
- * 1. `verify(token)` is called. Must return the bound
96
- * `{renderId, appId}` on success, or `null` on any failure
97
- * (invalid sig, expired, wrong kind, replayed, etc.).
98
- * 2. The bound `renderId` MUST match the one on the subscribe
99
- * payload. Mismatches are rejected with a clean error.
100
- * 3. On success, the server mints a reconnect credential via
101
- * `issueSessionToken(renderId, appId)` and returns it in
102
- * `AckPayload.renderToken`. The iframe stores this for WS
103
- * reconnects via the normal bearer path.
104
- *
105
- * Bootstrap auth is MUTUALLY EXCLUSIVE with the upstream `AuthAdapter`
106
- * bearer path at subscribe time — when a bootstrap token is present,
107
- * the identity resolved at the HTTP upgrade is IGNORED in favor of
108
- * the bootstrap-derived identity. This is intentional: MCP Apps
109
- * iframes don't have a long-lived bearer; the bootstrap IS the auth.
110
- */
111
- /**
112
- * Verify failure shape — distinguished so the channel server can map
113
- * `'expired'` to `BOOTSTRAP_EXPIRED` (client SHOULD refresh) vs
114
- * `'invalid'` to `BOOTSTRAP_INVALID` (client MUST re-handshake).
115
- *
116
- * G14 (2026-05-23): bootstrap envelopes are no longer single-use. A
117
- * signature-valid + unexpired token authenticates EVERY subscribe
118
- * within the TTL window; transient WS drops reconnect without a fresh
119
- * handshake. Past expiry, the iframe MAY refresh via the
120
- * {@link refresh} surface; past the refresh window, fresh handshake.
121
- */
122
- export type RenderChannelBootstrapVerifyResult = {
123
- readonly ok: true;
124
- readonly renderId: string;
125
- readonly appId: string;
126
- } | {
127
- readonly ok: false;
128
- readonly reason: "expired" | "invalid";
129
- };
130
- /**
131
- * Result of {@link RenderChannelBootstrap.refresh}.
132
- *
133
- * - `ok: true`: caller swaps the old envelope for `token` and resumes.
134
- * - `ok: false`: caller MUST re-handshake (refresh window closed,
135
- * tampered envelope, etc.).
136
- */
137
- export type SessionChannelBootstrapRefreshResult = {
138
- readonly ok: true;
139
- readonly token: string;
140
- readonly expiresAt: string;
141
- } | {
142
- readonly ok: false;
143
- readonly reason: "window_closed" | "invalid";
144
- };
145
- export interface RenderChannelBootstrap {
146
- /**
147
- * Verify a `SubscribePayload.bootstrap` token.
148
- *
149
- * Returns the bound identity on success, or a discriminated failure.
150
- * The channel server maps `'expired'` to `BOOTSTRAP_EXPIRED` so the
151
- * iframe can branch on refresh-vs-rehandshake, and `'invalid'` to
152
- * `BOOTSTRAP_INVALID` for tamper / format / kind failures (no
153
- * refresh on those).
154
- */
155
- verify(token: string): RenderChannelBootstrapVerifyResult;
156
- /**
157
- * Mint a longer-lived reconnect credential to return in
158
- * `AckPayload.renderToken`. Called only after a successful
159
- * `verify()` on a bootstrap subscribe.
160
- */
161
- issueSessionToken(renderId: string, appId: string): string;
162
- /**
163
- * Refresh a (possibly-expired-but-signature-valid) bootstrap envelope
164
- * into a new envelope with a fresh TTL. Used by the
165
- * `ggui_runtime_refresh_bootstrap` MCP tool — iframes that see their
166
- * bootstrap drift out of the TTL window swap in the refreshed
167
- * envelope without going back through `ggui_render`.
168
- *
169
- * Stateless: verifies HMAC against the same secret used at mint,
170
- * checks the refresh window against the ORIGINAL `iat`, and mints
171
- * a fresh bootstrap envelope bound to the SAME `(renderId, appId)`.
172
- * Past the refresh window the result is `{ok:false, reason:
173
- * 'window_closed'}`; tampered envelopes are `{ok:false, reason:
174
- * 'invalid'}`.
175
- */
176
- refresh(token: string): SessionChannelBootstrapRefreshResult;
177
- }
178
- /**
179
- * Default timeout for a single wired-tool invocation, in ms. Operators
180
- * override via {@link RenderChannelOptions.wiredActionTimeoutMs}; the
181
- * 30 s ceiling is a honest non-promise: long-running tools MUST design
182
- * their own completion path (streaming, polling).
183
- */
184
- export declare const DEFAULT_WIRED_TOOL_TIMEOUT_MS = 30000;
185
- /**
186
- * Opt-in wired-action dispatch surface. When present on a session
187
- * channel, `data:submit` envelopes whose declared `actionSpec
188
- * [name].tool` resolves against this router fire the named tool
189
- * in-process after validation, and the router's return value flows
190
- * back onto the session through a refresh-stream emission (see the
191
- * {@link import('@ggui-ai/protocol').StreamChannelEntry.tool} field).
192
- *
193
- * This is the agent-free contract-execution path — "zero agent code"
194
- * lands here. In OSS `ggui serve`, the CLI composes a router that
195
- * delegates to the same handler bundle `/mcp` uses (ggui-native +
196
- * mounts). Hosted deployments that want the same behavior compose
197
- * their own.
198
- *
199
- * Intentionally minimal surface:
200
- * - `has(name)` separates "tool absent" (TOOL_NOT_FOUND envelope,
201
- * recoverable) from "tool handler threw" (TOOL_THREW envelope,
202
- * isolated). The channel server uses the split to pick the error
203
- * code BEFORE invoking.
204
- * - `invoke(name, input)` returns `unknown` — the router doesn't
205
- * impose a shape, the refresh emission validates against the
206
- * declared `streamSpec[*].schema`.
207
- *
208
- * Thread-safety: implementations MUST tolerate concurrent invocations
209
- * for the same or different tools. The session channel fires no retry;
210
- * the router's handler is the sole execution.
211
- */
212
- /**
213
- * Per-invocation context the wired-action dispatcher hands the router.
214
- * The session-channel server constructs this at dispatch time from
215
- * the active session + stack item, then closes
216
- * `sendPropsUpdate` over the channel's outbound fan-out so the mount
217
- * handler can push a `props_update` frame to live subscribers without
218
- * routing through a refresh-stream tool.
219
- *
220
- * Why this is its own type, not threaded through `HandlerContext`:
221
- * `HandlerContext` (in `@ggui-ai/mcp-server-handlers`) is the canonical
222
- * shape every shared handler — ggui-native AND mounted — accepts. It
223
- * stays narrow on purpose (`appId`, `requestId`, optional `apiKeyHash`)
224
- * so the surface a host implements is stable. Wired-action runtime
225
- * fields (`renderId`, `renderId`, `sendPropsUpdate`) are dispatcher-
226
- * specific — only mount tools invoked through the wired-action router
227
- * see them, only at dispatch time. Passing them as a third arg to
228
- * `invoke` keeps the canonical handler shape untouched and makes
229
- * "this code reaches a wired dispatch path" syntactically obvious.
230
- *
231
- * The composer in `mcp-mounts.ts::composeWiredActionRouterFromMounts`
232
- * synthesizes a runtime ctx for the mount handler that satisfies
233
- * `HandlerContext` AND structurally carries these wired fields, so a
234
- * mount fixture can read `ctx.sendPropsUpdate` / `ctx.renderId` from the
235
- * same `ctx` argument the canonical `HandlerContext` sig types — no
236
- * cast, no widening of the static type.
237
- */
238
- export interface WiredActionContext {
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.
247
- *
248
- * Best-effort: per-subscriber send failures are swallowed; a closed
249
- * socket is a no-op.
250
- */
251
- sendPropsUpdate(props: JsonObject): void;
252
- }
253
- export interface WiredActionRouter {
254
- /** Returns `true` when the named tool has a registered handler. Used
255
- * to emit a clean `TOOL_NOT_FOUND` envelope before invoking — an
256
- * "invoke unknown tool" would throw through router internals, but
257
- * the error surface would be less specific. */
258
- has(toolName: string): boolean;
259
- /**
260
- * Invoke the named tool with the given input + per-dispatch wired
261
- * context. The channel server wraps this call in a timeout +
262
- * try/catch; implementations SHOULD NOT add their own retry or
263
- * timeout layer on top.
264
- *
265
- * `ctx.sendPropsUpdate` is closed over the active session — mounts
266
- * fire it to push props to live subscribers without an extra refresh
267
- * round-trip. Refresh-stream invocations (the post-action pass that
268
- * fires every declared `streamSpec[*].tool`) reuse the SAME ctx —
269
- * a refresh tool that wants to emit a props_update can do so, though
270
- * the canonical surface is the action tool itself.
271
- */
272
- invoke(toolName: string, input: Record<string, unknown>, ctx: WiredActionContext): Promise<unknown>;
273
- }
274
- export interface RenderChannelOptions {
275
- /** Required — the render backing store (typically `InMemoryRenderStore`). */
276
- readonly renderStore: RenderStore;
277
- /**
278
- * Required — the same `AuthAdapter` the `/mcp` endpoint uses. Any
279
- * failure during `subscribe` rejects the upgrade with HTTP 401.
280
- */
281
- readonly auth: AuthAdapter;
282
- /**
283
- * Maps resolved identity → tenant appId. Defaults to
284
- * `defaultAppIdFromIdentity` — same mapping the `/mcp` endpoint uses.
285
- */
286
- /** Structured logger. */
287
- readonly logger: Logger;
288
- /** URL path to mount on. Defaults to `/ws`. */
289
- readonly path?: string;
290
- /**
291
- * Outbound stream replay buffer. Defaults to a fresh
292
- * `InMemorySessionStreamBuffer` when omitted — fine for OSS
293
- * zero-config / dev. Persistent adapters bind via the same
294
- * `SessionStreamBuffer` interface when they land.
295
- *
296
- * Each channel instance owns its own seq cursor space; sharing a
297
- * buffer across two channels in the same process would couple their
298
- * sequences in confusing ways.
299
- */
300
- readonly streamBuffer?: SessionStreamBuffer;
301
- /**
302
- * Live-tail pub/sub for outbound live-channel frames. Defaults to a
303
- * fresh `InProcessStreamFanout` (in-memory, single-process). Hosted
304
- * deployments bind a `RedisPubSubFanout` here for multi-process
305
- * fan-out.
306
- *
307
- * The channel server uses the seam to publish every fanout-eligible
308
- * envelope and to subscribe one async iterator per WebSocket
309
- * subscriber — no in-process Map walk; the seam owns routing.
310
- */
311
- readonly streamFanout?: StreamFanout;
312
- /**
313
- * Optional bootstrap-auth plumbing. When present, the channel
314
- * accepts `SubscribePayload.bootstrap` and issues reconnect
315
- * credentials in `AckPayload.renderToken`. When absent, bootstrap
316
- * tokens are rejected with `BOOTSTRAP_NOT_SUPPORTED`.
317
- */
318
- readonly bootstrap?: RenderChannelBootstrap;
319
- /**
320
- * Optional console cookie-auth plumbing. When present, the
321
- * channel upgrade looks for the configured cookie on the incoming
322
- * request. A valid cookie binds the identity as a `builder` and
323
- * scopes the subscriber to the cookie's `renderId` — any
324
- * `subscribe.renderId` mismatch is rejected with
325
- * `DEVTOOL_COOKIE_SESSION_MISMATCH`.
326
- *
327
- * Absent = cookie auth disabled on this channel. Cookies are never
328
- * auto-enabled; the server's console composition decides.
329
- *
330
- * Design boundary: this auth plane is MUTUALLY EXCLUSIVE with
331
- * bootstrap auth at upgrade time. When both are configured, the
332
- * bootstrap path (via `?bootstrap=` query) wins; cookie is only
333
- * consulted for standard upgrades.
334
- */
335
- readonly cookieAuth?: RenderChannelCookieAuth;
336
- /**
337
- * Opt-in wired-action dispatch router. When present, validated
338
- * `data:submit` envelopes whose declared `actionSpec[name]
339
- * .tool` names a tool the router knows fire the tool in-process and
340
- * emit any declared refresh on the session. See
341
- * {@link WiredActionRouter}.
342
- *
343
- * Absent (the default) = the server relays inbound actions to the
344
- * session store for agent pickup and emits no synthetic stream
345
- * frames. Matches pre-Slice-11.5 behavior — no regression surface.
346
- */
347
- readonly wiredActionRouter?: WiredActionRouter;
348
- /**
349
- * Per-call timeout for wired-tool invocations, in milliseconds.
350
- * Defaults to {@link DEFAULT_WIRED_TOOL_TIMEOUT_MS} (30 s). Applied
351
- * identically to the initial action tool AND to each refresh-stream
352
- * tool. On timeout, a `TOOL_TIMEOUT` envelope emits on
353
- * `_ggui:contract-error` and the session channel keeps running.
354
- */
355
- readonly wiredActionTimeoutMs?: number;
356
- /**
357
- * Override the default sanitizer applied to the stringified original
358
- * error before it's written to `ContractErrorPayload.error.causedBy`.
359
- *
360
- * Defaults to `@ggui-ai/protocol::sanitizeCausedBy` (redacts Bearer
361
- * tokens, query-param secrets, common env-var patterns, truncates at
362
- * 2KB). Operators running in locked-down environments can pass a
363
- * stricter function — e.g., one that returns an empty string to
364
- * disable `causedBy` entirely, or one that layers additional
365
- * patterns on top of the defaults.
366
- *
367
- * The contract-error envelope flows on `_ggui:contract-error` with
368
- * `replay: 'all'`, so anything that lands in `causedBy` persists in
369
- * the session ring buffer and surfaces in RenderInspector. Accepting
370
- * raw `err.stack` verbatim is a credential-leak footgun; the default
371
- * sanitizer is load-bearing.
372
- */
373
- readonly sanitizeCausedBy?: SanitizeCausedBy;
374
- /**
375
- * Reserved-channel payload validators for channels whose shape is
376
- * NOT protocol-owned (Item 4 injection pattern). The primary
377
- * consumer is `_ggui:preview` — the server composes a
378
- * {@link ReservedChannelValidator} adapting
379
- * `@ggui-ai/preview-a2ui::parseServerMessage` so malformed A2UI
380
- * frames emitted on the preview channel reject at the fan-out
381
- * boundary instead of landing in the subscriber's renderer.
382
- *
383
- * Lookup inside {@link validateStreamData} consults this map FIRST,
384
- * then the protocol-shipped `BUILTIN_RESERVED_VALIDATORS` (which
385
- * validates `_ggui:contract-error`), then falls through to
386
- * `{valid: true}` when a known reserved channel has no validator.
387
- *
388
- * Absent = no `_ggui:preview` validation (documented degradation for
389
- * implementations without the preview package); `_ggui:contract-
390
- * error` is always validated via the built-in.
391
- */
392
- readonly extraReservedValidators?: ReadonlyMap<string, ReservedChannelValidator>;
393
- /**
394
- * Optional {@link TelemetrySink} for live-channel operational signals
395
- * (C12). When present, the channel emits `wired-tool.invoked` events
396
- * on successful wired-tool dispatches — operational counts +
397
- * durations for OTLP / CloudWatch / Datadog forwarders. Defaults
398
- * to {@link NoopTelemetrySink} (swallow silently).
399
- *
400
- * Deliberately separate from the renderer's client-side
401
- * `ObservabilityEvent` surface: same event name, two independent
402
- * consumers (backend metrics vs host inspector UI). See C12 plan +
403
- * the TelemetrySink docstring for the sync/lossy contract.
404
- */
405
- readonly telemetry?: TelemetrySink;
406
- /**
407
- * Opt-in `channel_subscribe` plumbing for `streamSpec[*].source.tool`
408
- * fan-out. When present, channel subscribes whose
409
- * `source.tool` is in `allowlist` are accepted and the server begins
410
- * polling. When absent, every `channel_subscribe` returns
411
- * `CHANNEL_NOT_LOCAL` so the iframe falls back to direct polling via
412
- * the MCP host proxy.
413
- *
414
- * Same `allowlist` MUST be advertised on
415
- * `handshake.serverCapabilities.streamWebSocketLocalTools` so iframe
416
- * + server agree on which channels use the WS fan-out path.
417
- */
418
- readonly streamWebSocketLocalTools?: RenderChannelLocalToolsOptions;
419
- /**
420
- * Protocol-version handshake policy. Governs server behavior when a
421
- * subscribe declares a `supportedVersions` list that does NOT
422
- * contain this server's {@link PROTOCOL_SCHEMA_VERSION}.
423
- *
424
- * - `'reject'` (default): server emits
425
- * `{type:'error', payload.code: 'UPGRADE_REQUIRED'}` AND closes
426
- * the underlying WebSocket. The caller cannot accidentally
427
- * proceed against a version-mismatched session. This is the
428
- * canonical posture for first-party servers.
429
- * - `'advisory'` (opt-out): server emits `UPGRADE_REQUIRED`
430
- * but keeps the connection open — the subscribe stops (no ack,
431
- * no stack, no replay). Existing clients that ignore the error
432
- * code continue to interoperate exactly as pre-handshake.
433
- * Use only for controlled migration windows during which
434
- * legacy-version clients must remain attached.
435
- *
436
- * Absent `payload.supportedVersions` always passes through — the
437
- * handshake is fully opt-in on the client side. `serverVersion` is
438
- * stamped into every successful ack regardless of policy.
439
- *
440
- * Switching between `'advisory'` and `'reject'` is a config change,
441
- * not a schema change — the wire fields and error code ship
442
- * identically in both modes.
443
- */
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;
477
- }
478
- /**
479
- * Cookie-based authentication for the live-channel upgrade. Used
480
- * exclusively by the same-origin console viewer; see
481
- * `console-auth.ts` for the single consumer today.
482
- */
483
- export interface RenderChannelCookieAuth {
484
- /**
485
- * Read the raw cookie value for THIS server's console cookie
486
- * from the incoming request headers. Returns `null` when the
487
- * cookie is absent or malformed.
488
- */
489
- readCookie(headers: import("node:http").IncomingHttpHeaders): string | null;
490
- /**
491
- * Verify a cookie value and return the bound session/app. Returns
492
- * `null` on any failure (signature, expiry, wrong kind). Never
493
- * throws.
494
- */
495
- verify(cookieValue: string): {
496
- renderId: string;
497
- appId: string;
498
- } | null;
499
- }
500
- export interface RenderChannelServer {
501
- /** The URL path the channel accepts upgrade requests on. */
502
- readonly path: string;
503
- /**
504
- * Wire this into the HTTP server's `upgrade` event. Rejects with 401
505
- * on auth failure; otherwise completes the WS handshake and wires
506
- * the subscriber.
507
- */
508
- handleUpgrade(req: IncomingMessage, socket: Duplex, head: Buffer): void;
509
- /**
510
- * Deliver a stream envelope to every subscriber of `delivery.renderId`.
511
- *
512
- * Outbound fan-out enforcement point — the delivery's `payload` is
513
- * validated against the active stack item's streamSpec via
514
- * `assertStreamContract` before any subscriber receives it.
515
- *
516
- * Sequencing + replay behavior:
517
- * 1. Payload validated against the active stack item's streamSpec.
518
- * 2. The replay buffer assigns a session-scoped monotonic `seq` and
519
- * (conditionally) stores the stamped envelope per the channel's
520
- * replay policy (`'none'` skip / `'latest'` single-slot /
521
- * `'all'` FIFO ring).
522
- * 3. The stamped envelope fans out to every subscriber for the
523
- * session, skipping any whose initial replay already covered
524
- * this seq (prevents double delivery on reconnect).
525
- *
526
- * The caller supplies `StreamEnvelopeInput` (no seq); the server
527
- * stamps. Returns the stamped `seq` for callers that need to thread
528
- * ordering back to their own response (e.g., `ggui_emit`'s wire
529
- * output). When the channel's replay policy was `'none'`, seq is
530
- * still assigned (so fan-out has a stable cursor) but nothing is
531
- * stored — the seq still surfaces here so the caller has the same
532
- * shape regardless of replay policy.
533
- *
534
- * Throws `ContractViolationError` on payload mismatch; transport
535
- * errors are logged but not propagated (per-subscriber best-effort).
536
- */
537
- sendToSession(delivery: StreamEnvelopeInput): Promise<{
538
- seq: number;
539
- }>;
540
- /**
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
545
- * covers state at subscribe time only, so without an explicit notify
546
- * a second-turn `commit` is invisible to the live client.
547
- *
548
- * The `B1` regression context (2026-04-22 QA pass): the chat surface
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
552
- * subscriber never heard about the new entry, the inline UI slot
553
- * stayed in "Waiting for render channel replay…" indefinitely.
554
- * `notifyRenderPush` closes that gap. Best-effort: per-subscriber
555
- * send failures are swallowed (same posture as `sendToSession`).
556
- *
557
- * NOT durable. Frames are not stamped through the replay buffer —
558
- * fresh subscribers still get the current render via `ack.render` on
559
- * subscribe. A new tab opening mid-render reads the latest render
560
- * from the snapshot; live tabs read the delta from this notify.
561
- *
562
- * Subscribers received via `register()` are tracked in
563
- * `subscribersBySession`; this helper iterates the bound set and
564
- * skips closed sockets via the `send()` helper's existing guard.
565
- * Callers ARE responsible for ordering — call after the underlying
566
- * `renderStore.commit` resolves so the snapshot a
567
- * concurrent fresh subscriber observes still includes the entry.
568
- */
569
- notifyRenderPush(renderId: string, render: Render, matchType?: string): void;
570
- /**
571
- * Prime every declared streamSpec channel on `render` that carries a
572
- * `tool` refresh hint. Invokes each refresh tool via the bound
573
- * wiredActionRouter with the empty refresh input, validates the result
574
- * against the channel's schema, and fans it out so subscribers see an
575
- * initial value instead of `latest = undefined`.
576
- *
577
- * Intended for seed-a-render mount paths (console try-live, agent-
578
- * initiated bootstraps that mint a render without a driving
579
- * action). Without this, blueprints with `streamSpec[channel].tool`
580
- * render their empty-state branch because the channel has no live
581
- * delivery — operators see "waiting for data" UI even when the
582
- * refresh tool would have produced initial content.
583
- *
584
- * Same isolation posture as the action-driven refresh pass: one
585
- * broken refresh MUST NOT block others; per-channel failures log a
586
- * warning but don't throw. No-op when no wiredActionRouter is
587
- * configured, when `render.streamSpec` is absent, or when every
588
- * channel lacks a `.tool` hint.
589
- *
590
- * Ordering: callers should invoke AFTER the render is persisted
591
- * so `sendToSession`'s active-render lookup resolves. The
592
- * `try-live` endpoint awaits this before returning the shortCode so
593
- * the viewer SPA subscribes with the initial envelope already
594
- * buffered on the render's stream-buffer replay state.
595
- */
596
- primeStreams(renderId: string, render: Render): Promise<void>;
597
- /**
598
- * Fan a `{type:'props_update', payload:{renderId, props}}` wire frame
599
- * to every subscriber currently bound to `renderId`. Mount tools
600
- * dispatched through {@link WiredActionRouter} call this via
601
- * `WiredActionContext.sendPropsUpdate` so a wired action that
602
- * mutates server-side state can replace renderer props in-place
603
- * without going through a refresh-stream tool.
604
- *
605
- * Validation posture (mirrors `notifyRenderPush`'s "best-effort orphan
606
- * no-op"):
607
- * 1. Look up the session via `renderStore.get`. Absent → log
608
- * `session_channel_props_update_orphan` and return — the wire
609
- * validator on the renderer side would reject a frame for an
610
- * unknown session anyway.
611
- * 2. Look up the target stack entry by `renderId` in the loaded
612
- * session's stack. Absent → log
613
- * `session_channel_props_update_pageid_unknown` and return.
614
- * 3. Iterate the flat WS-subscriber set, filter to subscribers
615
- * whose `renderId` matches, and `send()` the frame. Closed
616
- * sockets are skipped silently by `send()`.
617
- *
618
- * NOT routed through StreamFanout — `type: 'props_update'` is a
619
- * distinct WebSocket message type, not a stream envelope. Stream
620
- * envelopes flow on `data` frames and have a `seq` cursor; props
621
- * updates are ephemeral and follow `notifyRenderPush`'s pattern
622
- * (live-only, no replay-buffer stamping). A new subscriber that
623
- * connects mid-session reads current `props` from the stack
624
- * snapshot delivered in `ack.stack`.
625
- *
626
- * Schema validation against `propsSpec`: NOT enforced server-side
627
- * here. The renderer validates inbound props via
628
- * `validateInboundPropsPayload` against the cached
629
- * `render.propsSpec` before applying — defense-in-depth at the
630
- * receiving boundary. Server-side enforcement is reserved for the
631
- * future agent-driven `ggui_update` path; the mount-tool seam is
632
- * trusted-runtime today (mounts execute in-process, same trust
633
- * boundary as ggui-native handlers).
634
- */
635
- sendPropsUpdate(renderId: string, props: JsonObject): Promise<void>;
636
- /**
637
- * Fan a `{type:'drain_ack', payload:{renderId, appId, renderId,
638
- * eventId, drainedAt}}` wire frame to every subscriber currently
639
- * bound to `renderId`.
640
- *
641
- * Fired by `createGguiConsumeHandler` once per drained
642
- * `PendingEvent` so the iframe-runtime can cancel the matching
643
- * per-action 10s claim timer + resolve the toast as `consumed`.
644
- * Implements the `DrainAckNotifier` contract from
645
- * `@ggui-ai/mcp-server-handlers`.
646
- *
647
- * Same posture as `notifyRenderPush` / `sendPropsUpdate` — live-only,
648
- * no replay-buffer stamping. Subscribers that connect AFTER the
649
- * drain see the next consume's snapshot rather than the missed
650
- * frame; the iframe's claim timer + atomic-pop primitive backstop
651
- * any frame loss.
652
- */
653
- sendDrainAck(args: {
654
- readonly renderId: string;
655
- readonly appId: string;
656
- readonly eventId: string;
657
- readonly drainedAt: string;
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;
678
- /** Number of live subscribers. Useful for health / debug introspection. */
679
- readonly subscriberCount: number;
680
- /** Number of distinct sessions with at least one subscriber. */
681
- readonly sessionCount: number;
682
- /**
683
- * Close every live subscriber + the underlying ws server. Idempotent.
684
- */
685
- close(): Promise<void>;
686
- }
687
- /**
688
- * Build an OSS live-channel server. The returned object is designed to be
689
- * composed into `createGguiServer` — see `server.ts` for the wire-up.
690
- */
691
- export declare function createRenderChannelServer(opts: RenderChannelOptions): RenderChannelServer;
692
- /** Fabricate a request id for live-channel ops so logs correlate. */
693
- export declare function newRequestId(): string;
694
- //# sourceMappingURL=render-channel.d.ts.map