@ggui-ai/mcp-server 0.7.0 → 0.9.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 (56) hide show
  1. package/dist/api-renders-routes.d.ts.map +1 -1
  2. package/dist/api-renders-routes.js +32 -23
  3. package/dist/api-renders-stream-route.d.ts +80 -0
  4. package/dist/api-renders-stream-route.d.ts.map +1 -0
  5. package/dist/api-renders-stream-route.js +311 -0
  6. package/dist/build-mcp.d.ts +10 -0
  7. package/dist/build-mcp.d.ts.map +1 -1
  8. package/dist/build-mcp.js +64 -4
  9. package/dist/console-session-routes.d.ts +10 -5
  10. package/dist/console-session-routes.d.ts.map +1 -1
  11. package/dist/console-session-routes.js +21 -5
  12. package/dist/ggui-session-channel/action-ingress.d.ts +2 -2
  13. package/dist/ggui-session-channel/action-ingress.d.ts.map +1 -1
  14. package/dist/ggui-session-channel/channel-subscriptions.d.ts +4 -4
  15. package/dist/ggui-session-channel/channel-subscriptions.d.ts.map +1 -1
  16. package/dist/ggui-session-channel/internal-types.d.ts +63 -9
  17. package/dist/ggui-session-channel/internal-types.d.ts.map +1 -1
  18. package/dist/ggui-session-channel/outbound.d.ts +15 -5
  19. package/dist/ggui-session-channel/outbound.d.ts.map +1 -1
  20. package/dist/ggui-session-channel/outbound.js +64 -24
  21. package/dist/ggui-session-channel/socket-router.d.ts +8 -3
  22. package/dist/ggui-session-channel/socket-router.d.ts.map +1 -1
  23. package/dist/ggui-session-channel/socket-router.js +6 -1
  24. package/dist/ggui-session-channel/subscribe.d.ts +48 -3
  25. package/dist/ggui-session-channel/subscribe.d.ts.map +1 -1
  26. package/dist/ggui-session-channel/subscribe.js +97 -36
  27. package/dist/ggui-session-channel/subscriber-lifecycle.d.ts +23 -13
  28. package/dist/ggui-session-channel/subscriber-lifecycle.d.ts.map +1 -1
  29. package/dist/ggui-session-channel/subscriber-lifecycle.js +24 -11
  30. package/dist/ggui-session-channel.d.ts +58 -11
  31. package/dist/ggui-session-channel.d.ts.map +1 -1
  32. package/dist/ggui-session-channel.js +55 -19
  33. package/dist/index.d.ts +4 -3
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +7 -1
  36. package/dist/instructions-presets.js +10 -10
  37. package/dist/mcp-apps-outbound.d.ts +93 -4
  38. package/dist/mcp-apps-outbound.d.ts.map +1 -1
  39. package/dist/mcp-apps-outbound.js +460 -105
  40. package/dist/mcp-endpoint-routes.d.ts +17 -5
  41. package/dist/mcp-endpoint-routes.d.ts.map +1 -1
  42. package/dist/mcp-endpoint-routes.js +2 -0
  43. package/dist/oauth-as-routes.d.ts.map +1 -1
  44. package/dist/oauth-as-routes.js +36 -0
  45. package/dist/oauth.d.ts.map +1 -1
  46. package/dist/oauth.js +8 -1
  47. package/dist/runtime-bundle-hash.d.ts +43 -0
  48. package/dist/runtime-bundle-hash.d.ts.map +1 -0
  49. package/dist/runtime-bundle-hash.js +66 -0
  50. package/dist/runtime-bundle-route.d.ts +15 -0
  51. package/dist/runtime-bundle-route.d.ts.map +1 -1
  52. package/dist/runtime-bundle-route.js +21 -5
  53. package/dist/server.d.ts +83 -14
  54. package/dist/server.d.ts.map +1 -1
  55. package/dist/server.js +167 -15
  56. package/package.json +12 -12
@@ -8,11 +8,16 @@
8
8
  *
9
9
  * GET /ggui/console/session-resource?session=<sessionId>
10
10
  * — production thin-shell HTML, wrapped as a ResourceContents
11
- * blob (`text` IS `GGUI_RENDER_SHELL_HTML`, byte-identical to
12
- * what Claude Desktop fetches via MCP `resources/read
13
- * ui://ggui/render`). NO inlined bootstrap — console fetches
14
- * the bootstrap separately (meta route below) and replies to
15
- * the iframe's `ui/initialize` postMessage with it.
11
+ * blob (`text` IS the `GGUI_RENDER_SHELL_HTML` constant). On a
12
+ * default mount this matches what a host fetches via MCP
13
+ * `resources/read ui://ggui/render`; a mount opted into
14
+ * `mcpApps.inlineRuntimeShell` (or a custom `shellHtml`) serves
15
+ * a different body on the MCP path while THIS route keeps the
16
+ * thin shell — its first-party srcdoc/WebView consumers load
17
+ * external scripts fine and rely on the pinned CSP hash. NO
18
+ * inlined bootstrap — console fetches the bootstrap separately
19
+ * (meta route below) and replies to the iframe's
20
+ * `ui/initialize` postMessage with it.
16
21
  *
17
22
  * GET /ggui/console/sessions/:sessionId/meta
18
23
  * — slice-envelope JSON (`{ "ai.ggui/render": {...} }`, the same
@@ -1 +1 @@
1
- {"version":3,"file":"console-session-routes.d.ts","sourceRoot":"","sources":["../src/console-session-routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiDG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAQ5F,OAAO,KAAK,EAAE,OAAO,EAAqB,MAAM,SAAS,CAAC;AAQ1D,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,2BAA2B,CAAC;AAC9E,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAI1C,UAAU,YAAY;IACpB,iCAAiC;IACjC,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC;IACtB,sEAAsE;IACtE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,yEAAyE;IACzE,QAAQ,CAAC,cAAc,EAAE,cAAc,CAAC;IACxC,4DAA4D;IAC5D,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,4DAA4D;IAC5D,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;IAC/B,4DAA4D;IAC5D,QAAQ,CAAC,WAAW,CAAC,EAAE,gBAAgB,CAAC;IACxC,qEAAqE;IACrE,QAAQ,CAAC,aAAa,CAAC,EAAE,CACvB,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,KACV;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;IACzD,uEAAuE;IACvE,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;IACrC,mEAAmE;IACnE,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,CAAC;IAC/B,sEAAsE;IACtE,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,mEAAmE;IACnE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,YAAY,GAAG,4BAA4B,CA0V1F"}
1
+ {"version":3,"file":"console-session-routes.d.ts","sourceRoot":"","sources":["../src/console-session-routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsDG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAQ5F,OAAO,KAAK,EAAE,OAAO,EAAqB,MAAM,SAAS,CAAC;AAQ1D,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,2BAA2B,CAAC;AAC9E,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAI1C,UAAU,YAAY;IACpB,iCAAiC;IACjC,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC;IACtB,sEAAsE;IACtE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,yEAAyE;IACzE,QAAQ,CAAC,cAAc,EAAE,cAAc,CAAC;IACxC,4DAA4D;IAC5D,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,4DAA4D;IAC5D,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;IAC/B,4DAA4D;IAC5D,QAAQ,CAAC,WAAW,CAAC,EAAE,gBAAgB,CAAC;IACxC,qEAAqE;IACrE,QAAQ,CAAC,aAAa,CAAC,EAAE,CACvB,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,KACV;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;IACzD,uEAAuE;IACvE,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;IACrC,mEAAmE;IACnE,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,CAAC;IAC/B,sEAAsE;IACtE,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,mEAAmE;IACnE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,YAAY,GAAG,4BAA4B,CAqW1F"}
@@ -8,11 +8,16 @@
8
8
  *
9
9
  * GET /ggui/console/session-resource?session=<sessionId>
10
10
  * — production thin-shell HTML, wrapped as a ResourceContents
11
- * blob (`text` IS `GGUI_RENDER_SHELL_HTML`, byte-identical to
12
- * what Claude Desktop fetches via MCP `resources/read
13
- * ui://ggui/render`). NO inlined bootstrap — console fetches
14
- * the bootstrap separately (meta route below) and replies to
15
- * the iframe's `ui/initialize` postMessage with it.
11
+ * blob (`text` IS the `GGUI_RENDER_SHELL_HTML` constant). On a
12
+ * default mount this matches what a host fetches via MCP
13
+ * `resources/read ui://ggui/render`; a mount opted into
14
+ * `mcpApps.inlineRuntimeShell` (or a custom `shellHtml`) serves
15
+ * a different body on the MCP path while THIS route keeps the
16
+ * thin shell — its first-party srcdoc/WebView consumers load
17
+ * external scripts fine and rely on the pinned CSP hash. NO
18
+ * inlined bootstrap — console fetches the bootstrap separately
19
+ * (meta route below) and replies to the iframe's
20
+ * `ui/initialize` postMessage with it.
16
21
  *
17
22
  * GET /ggui/console/sessions/:sessionId/meta
18
23
  * — slice-envelope JSON (`{ "ai.ggui/render": {...} }`, the same
@@ -262,9 +267,19 @@ export function mountConsoleSessionRoutes(opts) {
262
267
  // remains authoritative.
263
268
  let renderContractHash;
264
269
  let renderValidatorsUrl;
270
+ // Freeze-latch self-epoch (#483): the console inspector views the
271
+ // LIVE HEAD, so its boot meta carries the row's current epoch —
272
+ // without it the viewer boots believing epoch 0 and the first
273
+ // update on the session would freeze the inspection surface.
274
+ // (Follow-up: on a `ui.reminted` boundary the viewer should
275
+ // re-mount to the new head rather than stay frozen.)
276
+ let renderEpoch;
265
277
  if (renderStore && codeStore) {
266
278
  try {
267
279
  const stored = await renderStore.get(verified.sessionId);
280
+ if (stored !== null) {
281
+ renderEpoch = stored.render.epoch;
282
+ }
268
283
  if (stored !== null &&
269
284
  stored.render.type !== "mcpApps" &&
270
285
  stored.render.type !== "system" &&
@@ -299,6 +314,7 @@ export function mountConsoleSessionRoutes(opts) {
299
314
  wsUrl: resolvedWsUrl,
300
315
  wsToken: minted.token,
301
316
  expiresAt: minted.expiresAt,
317
+ ...(renderEpoch !== undefined ? { epoch: renderEpoch } : {}),
302
318
  ...(renderContractHash !== undefined && renderValidatorsUrl !== undefined
303
319
  ? {
304
320
  contractHash: renderContractHash,
@@ -9,7 +9,7 @@ import type { GguiSessionStore, PendingEventConsumer } from "@ggui-ai/mcp-server
9
9
  import type { WebSocketMessage } from "@ggui-ai/protocol/transport/websocket";
10
10
  import type { WebSocket } from "ws";
11
11
  import type { Logger } from "../logger.js";
12
- import type { Subscriber } from "./internal-types.js";
12
+ import type { WsSubscriber } from "./internal-types.js";
13
13
  import type { Outbound } from "./outbound.js";
14
14
  export interface ActionIngressDeps {
15
15
  readonly logger: Logger;
@@ -46,7 +46,7 @@ export interface ActionIngress {
46
46
  * mid-turn. Pipe failure degrades to ledger-only with a warn;
47
47
  * it never changes the ack.
48
48
  */
49
- handleInboundAction(ws: WebSocket, sub: Subscriber, message: WebSocketMessage & {
49
+ handleInboundAction(ws: WebSocket, sub: WsSubscriber, message: WebSocketMessage & {
50
50
  type: "action";
51
51
  }): Promise<void>;
52
52
  }
@@ -1 +1 @@
1
- {"version":3,"file":"action-ingress.d.ts","sourceRoot":"","sources":["../../src/ggui-session-channel/action-ingress.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAIvF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AAE9E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AACpC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AA+G9C,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,WAAW,EAAE,gBAAgB,CAAC;IACvC;;;;OAIG;IACH,QAAQ,CAAC,oBAAoB,CAAC,EAAE,oBAAoB,CAAC;IACrD,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IAChC,QAAQ,CAAC,SAAS,EAAE,QAAQ,CAAC,WAAW,CAAC,CAAC;CAC3C;AAED,MAAM,WAAW,aAAa;IAC5B;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,mBAAmB,CACjB,EAAE,EAAE,SAAS,EACb,GAAG,EAAE,UAAU,EACf,OAAO,EAAE,gBAAgB,GAAG;QAAE,IAAI,EAAE,QAAQ,CAAA;KAAE,GAC7C,OAAO,CAAC,IAAI,CAAC,CAAC;CAClB;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,iBAAiB,GAAG,aAAa,CAiJ1E"}
1
+ {"version":3,"file":"action-ingress.d.ts","sourceRoot":"","sources":["../../src/ggui-session-channel/action-ingress.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAIvF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AAE9E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AACpC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AA+G9C,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,WAAW,EAAE,gBAAgB,CAAC;IACvC;;;;OAIG;IACH,QAAQ,CAAC,oBAAoB,CAAC,EAAE,oBAAoB,CAAC;IACrD,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IAChC,QAAQ,CAAC,SAAS,EAAE,QAAQ,CAAC,WAAW,CAAC,CAAC;CAC3C;AAED,MAAM,WAAW,aAAa;IAC5B;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,mBAAmB,CACjB,EAAE,EAAE,SAAS,EACb,GAAG,EAAE,YAAY,EACjB,OAAO,EAAE,gBAAgB,GAAG;QAAE,IAAI,EAAE,QAAQ,CAAA;KAAE,GAC7C,OAAO,CAAC,IAAI,CAAC,CAAC;CAClB;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,iBAAiB,GAAG,aAAa,CAiJ1E"}
@@ -10,7 +10,7 @@ import type { GguiSessionStore } from "@ggui-ai/mcp-server-core";
10
10
  import type { WebSocketMessage } from "@ggui-ai/protocol/transport/websocket";
11
11
  import type { WebSocket } from "ws";
12
12
  import type { Logger } from "../logger.js";
13
- import type { Subscriber } from "./internal-types.js";
13
+ import type { WsSubscriber } from "./internal-types.js";
14
14
  import type { Outbound } from "./outbound.js";
15
15
  /**
16
16
  * Opt-in plumbing for the `channel_subscribe` polling loop. When this
@@ -68,7 +68,7 @@ export interface ChannelSubscriptionsDeps {
68
68
  * callback fire-time to self-clean intervals that outlived their
69
69
  * subscriber.
70
70
  */
71
- readonly subscribersByWs: WeakMap<WebSocket, Subscriber>;
71
+ readonly subscribersByWs: WeakMap<WebSocket, WsSubscriber>;
72
72
  readonly send: Outbound["send"];
73
73
  readonly sendChannelError: Outbound["sendChannelError"];
74
74
  }
@@ -80,7 +80,7 @@ export interface ChannelSubscriptions {
80
80
  * `${sessionId}:${channelName}` — a re-subscribe replaces any
81
81
  * existing interval rather than running two in parallel.
82
82
  */
83
- handleChannelSubscribe(ws: WebSocket, sub: Subscriber, message: WebSocketMessage & {
83
+ handleChannelSubscribe(ws: WebSocket, sub: WsSubscriber, message: WebSocketMessage & {
84
84
  type: "channel_subscribe";
85
85
  }): Promise<void>;
86
86
  /**
@@ -89,7 +89,7 @@ export interface ChannelSubscriptions {
89
89
  * implicitly unsubscribes every channel; this message is for
90
90
  * mid-session cancellation.
91
91
  */
92
- handleChannelUnsubscribe(ws: WebSocket, sub: Subscriber, message: WebSocketMessage & {
92
+ handleChannelUnsubscribe(ws: WebSocket, sub: WsSubscriber, message: WebSocketMessage & {
93
93
  type: "channel_unsubscribe";
94
94
  }): void;
95
95
  }
@@ -1 +1 @@
1
- {"version":3,"file":"channel-subscriptions.d.ts","sourceRoot":"","sources":["../../src/ggui-session-channel/channel-subscriptions.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAGjE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AAC9E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AACpC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,KAAK,EAA4B,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAChF,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAa9C;;;;;;;GAOG;AACH,MAAM,WAAW,mCAAmC;IAClD;;;;;;;OAOG;IACH,QAAQ,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;IACtC;;;;;;;;;;OAUG;IACH,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACvD;;;;;OAKG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE;QACrB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAC1B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;QAC5B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;KAC7B,CAAC;CACH;AAED,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,WAAW,EAAE,gBAAgB,CAAC;IACvC;;;;OAIG;IACH,QAAQ,CAAC,UAAU,EAAE,mCAAmC,GAAG,SAAS,CAAC;IACrE;;;;OAIG;IACH,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC;IACzD,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IAChC,QAAQ,CAAC,gBAAgB,EAAE,QAAQ,CAAC,kBAAkB,CAAC,CAAC;CACzD;AAED,MAAM,WAAW,oBAAoB;IACnC;;;;;;OAMG;IACH,sBAAsB,CACpB,EAAE,EAAE,SAAS,EACb,GAAG,EAAE,UAAU,EACf,OAAO,EAAE,gBAAgB,GAAG;QAAE,IAAI,EAAE,mBAAmB,CAAA;KAAE,GACxD,OAAO,CAAC,IAAI,CAAC,CAAC;IACjB;;;;;OAKG;IACH,wBAAwB,CACtB,EAAE,EAAE,SAAS,EACb,GAAG,EAAE,UAAU,EACf,OAAO,EAAE,gBAAgB,GAAG;QAAE,IAAI,EAAE,qBAAqB,CAAA;KAAE,GAC1D,IAAI,CAAC;CACT;AAED,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,wBAAwB,GAAG,oBAAoB,CAiQ/F"}
1
+ {"version":3,"file":"channel-subscriptions.d.ts","sourceRoot":"","sources":["../../src/ggui-session-channel/channel-subscriptions.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAGjE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AAC9E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AACpC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,KAAK,EAA4B,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAClF,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAa9C;;;;;;;GAOG;AACH,MAAM,WAAW,mCAAmC;IAClD;;;;;;;OAOG;IACH,QAAQ,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;IACtC;;;;;;;;;;OAUG;IACH,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACvD;;;;;OAKG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE;QACrB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAC1B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;QAC5B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;KAC7B,CAAC;CACH;AAED,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,WAAW,EAAE,gBAAgB,CAAC;IACvC;;;;OAIG;IACH,QAAQ,CAAC,UAAU,EAAE,mCAAmC,GAAG,SAAS,CAAC;IACrE;;;;OAIG;IACH,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC;IAC3D,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IAChC,QAAQ,CAAC,gBAAgB,EAAE,QAAQ,CAAC,kBAAkB,CAAC,CAAC;CACzD;AAED,MAAM,WAAW,oBAAoB;IACnC;;;;;;OAMG;IACH,sBAAsB,CACpB,EAAE,EAAE,SAAS,EACb,GAAG,EAAE,YAAY,EACjB,OAAO,EAAE,gBAAgB,GAAG;QAAE,IAAI,EAAE,mBAAmB,CAAA;KAAE,GACxD,OAAO,CAAC,IAAI,CAAC,CAAC;IACjB;;;;;OAKG;IACH,wBAAwB,CACtB,EAAE,EAAE,SAAS,EACb,GAAG,EAAE,YAAY,EACjB,OAAO,EAAE,gBAAgB,GAAG;QAAE,IAAI,EAAE,qBAAqB,CAAA;KAAE,GAC1D,IAAI,CAAC;CACT;AAED,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,wBAAwB,GAAG,oBAAoB,CAiQ/F"}
@@ -5,14 +5,44 @@
5
5
  */
6
6
  import type { AuthResult, BufferedStreamEnvelope } from "@ggui-ai/mcp-server-core";
7
7
  import type { JsonObject } from "@ggui-ai/protocol";
8
+ import type { WebSocketMessage } from "@ggui-ai/protocol/transport/websocket";
8
9
  import type { WebSocket } from "ws";
9
10
  /**
10
- * A single connected subscriber (one client, one render). Held live in
11
- * the per-channel subscriber map; torn down on socket close or explicit
12
- * `close` message.
11
+ * Transport-neutral write surface for one subscriber's outbound plane.
12
+ * The fan-out walks (`props_update` / `render` / `drain_ack` / external
13
+ * broadcasts), the live-tail pump, and the subscribe tail all write
14
+ * through this seam so a subscriber can ride WS (`createWsSink`) or SSE
15
+ * (`SseSink` in `../api-renders-stream-route.ts`) without per-frame
16
+ * transport branching.
13
17
  */
14
- export interface Subscriber {
15
- readonly ws: WebSocket;
18
+ export interface SubscriberSink {
19
+ /** WS: `ws.readyState === OPEN`; SSE: `!res.writableEnded && !res.destroyed`. */
20
+ isOpen(): boolean;
21
+ /**
22
+ * Write one wire frame. `resumeId` is the durable-cursor stamp for
23
+ * ledger-backed replay frames: SSE emits it as the `id:` field ahead
24
+ * of `data:` (so the browser's `Last-Event-ID` lands on the
25
+ * GguiSessionEvent ledger cursor); WS ignores it — WS resume rides
26
+ * `SubscribePayload.sinceSequence` instead.
27
+ */
28
+ write(frame: WebSocketMessage, opts?: {
29
+ readonly resumeId?: string;
30
+ }): void;
31
+ /**
32
+ * Terminate the transport. WS maps `service_restart` → close 1012
33
+ * (reconnect immediately — same server family is still up) and
34
+ * `session_expired` → close 1008 (policy violation — do not blind-
35
+ * reconnect); SSE ends the response, letting `EventSource`
36
+ * auto-reconnect hit the HTTP pre-gate for the authoritative verdict.
37
+ */
38
+ end(reason: "service_restart" | "session_expired"): void;
39
+ }
40
+ /**
41
+ * Fields shared by every subscriber regardless of transport (one
42
+ * client, one render). Held live in the channel's flat subscriber set;
43
+ * torn down on transport close or explicit `close` message.
44
+ */
45
+ interface SubscriberBase {
16
46
  readonly sessionId: string;
17
47
  readonly appId: string;
18
48
  readonly identity: AuthResult;
@@ -32,7 +62,7 @@ export interface Subscriber {
32
62
  * Per-subscriber live-tail iterator from `streamFanout.subscribe`.
33
63
  * Owned by the subscriber for its full lifetime; ending it (via
34
64
  * `iter.return()`) terminates the pump loop AND unregisters from
35
- * the StreamFanout. `unregister(ws)` is the single point that
65
+ * the StreamFanout. `unregister(sub)` is the single point that
36
66
  * does this teardown.
37
67
  */
38
68
  readonly iter: AsyncIterator<BufferedStreamEnvelope>;
@@ -41,15 +71,38 @@ export interface Subscriber {
41
71
  * Keyed by `${sessionId}:${channelName}` so a reconnect that
42
72
  * re-subscribes to the same (render, channel) pair replaces the
43
73
  * existing timer rather than minting a duplicate (idempotent
44
- * semantics on the wire). Torn down en masse by `unregister(ws)` on
45
- * WS close.
74
+ * semantics on the wire). Torn down en masse by `unregister(sub)`
75
+ * on transport close.
46
76
  *
47
77
  * Populated by the `channel_subscribe` handler when the composing
48
78
  * host wired a `streamWebSocketLocalTools` allowlist; empty
49
- * otherwise.
79
+ * otherwise. SSE subscribers have no inbound channel, so theirs
80
+ * stays empty structurally.
50
81
  */
51
82
  readonly channelSubs: Map<string, ChannelSubscriptionState>;
83
+ /** Outbound write surface — the ONLY transport coupling on the fan-out path. */
84
+ readonly sink: SubscriberSink;
85
+ }
86
+ /**
87
+ * WebSocket-attached subscriber. The `ws` handle stays for the inbound
88
+ * / control planes (socket-router dispatch, action acks, subscribe
89
+ * rejects) which remain WS-only — SSE has no inbound channel.
90
+ */
91
+ export interface WsSubscriber extends SubscriberBase {
92
+ readonly transport: "ws";
93
+ readonly ws: WebSocket;
94
+ }
95
+ /**
96
+ * SSE-attached subscriber (`GET /api/sessions/:sessionId/stream`),
97
+ * registered via `GguiSessionChannelServer.attachExternalSubscriber`.
98
+ * Outbound-only: it shares the fan-out planes with WS subscribers but
99
+ * never appears in the ws→subscriber reverse index.
100
+ */
101
+ export interface SseSubscriber extends SubscriberBase {
102
+ readonly transport: "sse";
52
103
  }
104
+ /** A single connected subscriber, discriminated on `transport`. */
105
+ export type Subscriber = WsSubscriber | SseSubscriber;
53
106
  /**
54
107
  * Per-(subscriber, sessionId, channelName) polling-loop state.
55
108
  * Created on `channel_subscribe` accept, torn down on `channel_unsubscribe`
@@ -99,4 +152,5 @@ export interface UpgradeBindings {
99
152
  appId: string;
100
153
  };
101
154
  }
155
+ export {};
102
156
  //# sourceMappingURL=internal-types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"internal-types.d.ts","sourceRoot":"","sources":["../../src/ggui-session-channel/internal-types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AACnF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AAEpC;;;;GAIG;AACH,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,EAAE,EAAE,SAAS,CAAC;IACvB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,UAAU,CAAC;IAC9B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B;;;;;;;;;OASG;IACH,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC;;;;;;OAMG;IACH,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC,sBAAsB,CAAC,CAAC;IACrD;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,wBAAwB,CAAC,CAAC;CAC7D;AAED;;;;;;;;;;GAUG;AACH,MAAM,WAAW,wBAAwB;IACvC,2EAA2E;IAC3E,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,4EAA4E;IAC5E,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,uEAAuE;IACvE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,4CAA4C;IAC5C,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B;;;;OAIG;IACH,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B;;;;OAIG;IACH,GAAG,EAAE,MAAM,CAAC;IACZ,yDAAyD;IACzD,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;CAChD;AAED;;;;;;GAMG;AACH,MAAM,WAAW,eAAe;IAC9B,cAAc,CAAC,EAAE,UAAU,CAAC;IAC5B,iBAAiB,CAAC,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;CAC1D"}
1
+ {"version":3,"file":"internal-types.d.ts","sourceRoot":"","sources":["../../src/ggui-session-channel/internal-types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AACnF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AAC9E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AAEpC;;;;;;;GAOG;AACH,MAAM,WAAW,cAAc;IAC7B,iFAAiF;IACjF,MAAM,IAAI,OAAO,CAAC;IAClB;;;;;;OAMG;IACH,KAAK,CAAC,KAAK,EAAE,gBAAgB,EAAE,IAAI,CAAC,EAAE;QAAE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAC5E;;;;;;OAMG;IACH,GAAG,CAAC,MAAM,EAAE,iBAAiB,GAAG,iBAAiB,GAAG,IAAI,CAAC;CAC1D;AAED;;;;GAIG;AACH,UAAU,cAAc;IACtB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,UAAU,CAAC;IAC9B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B;;;;;;;;;OASG;IACH,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC;;;;;;OAMG;IACH,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC,sBAAsB,CAAC,CAAC;IACrD;;;;;;;;;;;;OAYG;IACH,QAAQ,CAAC,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,wBAAwB,CAAC,CAAC;IAC5D,gFAAgF;IAChF,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC;CAC/B;AAED;;;;GAIG;AACH,MAAM,WAAW,YAAa,SAAQ,cAAc;IAClD,QAAQ,CAAC,SAAS,EAAE,IAAI,CAAC;IACzB,QAAQ,CAAC,EAAE,EAAE,SAAS,CAAC;CACxB;AAED;;;;;GAKG;AACH,MAAM,WAAW,aAAc,SAAQ,cAAc;IACnD,QAAQ,CAAC,SAAS,EAAE,KAAK,CAAC;CAC3B;AAED,mEAAmE;AACnE,MAAM,MAAM,UAAU,GAAG,YAAY,GAAG,aAAa,CAAC;AAEtD;;;;;;;;;;GAUG;AACH,MAAM,WAAW,wBAAwB;IACvC,2EAA2E;IAC3E,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,4EAA4E;IAC5E,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,uEAAuE;IACvE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,4CAA4C;IAC5C,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B;;;;OAIG;IACH,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B;;;;OAIG;IACH,GAAG,EAAE,MAAM,CAAC;IACZ,yDAAyD;IACzD,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;CAChD;AAED;;;;;;GAMG;AACH,MAAM,WAAW,eAAe;IAC9B,cAAc,CAAC,EAAE,UAAU,CAAC;IAC5B,iBAAiB,CAAC,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;CAC1D"}
@@ -16,7 +16,7 @@ import type { ErrorPayload, GguiSession, JsonObject, ReservedChannelValidator }
16
16
  import type { WebSocketMessage } from "@ggui-ai/protocol/transport/websocket";
17
17
  import type { WebSocket } from "ws";
18
18
  import type { Logger } from "../logger.js";
19
- import type { Subscriber } from "./internal-types.js";
19
+ import type { Subscriber, SubscriberSink } from "./internal-types.js";
20
20
  /** Channel-scoped error codes carried on `channel_error` frames. */
21
21
  export type ChannelErrorCode = "CHANNEL_UNKNOWN" | "CHANNEL_NOT_LOCAL" | "SESSION_NOT_FOUND" | "SUBSCRIBE_UNAUTHORIZED" | "POLL_FAILED";
22
22
  export interface OutboundDeps {
@@ -28,10 +28,11 @@ export interface OutboundDeps {
28
28
  /** Live-tail pub/sub seam for stream-envelope fan-out. */
29
29
  readonly streamFanout: StreamFanout;
30
30
  /**
31
- * Flat set of all live WS subscribers — shared with the
31
+ * Flat set of all live subscribers (WS + SSE) — shared with the
32
32
  * subscriber-lifecycle module (which owns membership); read-only
33
- * here. Direct-to-WS frames (`props_update`, `render`, `drain_ack`,
34
- * external broadcasts) iterate + filter by `sessionId`.
33
+ * here. Direct frames (`props_update`, `render`, `drain_ack`,
34
+ * external broadcasts) iterate + filter by `sessionId`, writing
35
+ * through each subscriber's transport-neutral `sink`.
35
36
  */
36
37
  readonly wsSubscribers: ReadonlySet<Subscriber>;
37
38
  /**
@@ -41,6 +42,15 @@ export interface OutboundDeps {
41
42
  */
42
43
  readonly extraReservedValidators?: ReadonlyMap<string, ReservedChannelValidator>;
43
44
  }
45
+ /**
46
+ * WS-backed {@link SubscriberSink}. `write` wraps the existing send
47
+ * guard (readyState check + warn-log on failure); `resumeId` is ignored
48
+ * — WS resume rides `SubscribePayload.sinceSequence`, not a per-frame
49
+ * id. `end` maps `service_restart` → 1012 (RFC 6455 "Service Restart":
50
+ * reconnect immediately) and `session_expired` → 1008 (policy
51
+ * violation: do not blind-reconnect).
52
+ */
53
+ export declare function createWsSink(ws: WebSocket, logger: Logger): SubscriberSink;
44
54
  /** The send/fan-out helpers, bound to one channel instance. */
45
55
  export interface Outbound {
46
56
  /** Low-level wire write — skips closed sockets, warn-logs send failures. */
@@ -66,7 +76,7 @@ export interface Outbound {
66
76
  /** Impl behind {@link GguiSessionChannelServer.notifyGguiSessionCommit}. */
67
77
  notifyGguiSessionCommit(sessionId: string, render: GguiSession, matchType?: string): void;
68
78
  /** Impl behind {@link GguiSessionChannelServer.sendPropsUpdate}. */
69
- sendPropsUpdate(sessionId: string, props: JsonObject): Promise<void>;
79
+ sendPropsUpdate(sessionId: string, props: JsonObject, epoch: number): Promise<void>;
70
80
  /** Impl behind {@link GguiSessionChannelServer.sendDrainAck}. */
71
81
  sendDrainAck(args: {
72
82
  readonly sessionId: string;
@@ -1 +1 @@
1
- {"version":3,"file":"outbound.d.ts","sourceRoot":"","sources":["../../src/ggui-session-channel/outbound.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EACV,gBAAgB,EAChB,uBAAuB,EACvB,mBAAmB,EACnB,YAAY,EACb,MAAM,0BAA0B,CAAC;AAElC,OAAO,KAAK,EACV,YAAY,EACZ,WAAW,EACX,UAAU,EACV,wBAAwB,EAEzB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AAC9E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AACpC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAEtD,oEAAoE;AACpE,MAAM,MAAM,gBAAgB,GACxB,iBAAiB,GACjB,mBAAmB,GACnB,mBAAmB,GACnB,wBAAwB,GACxB,aAAa,CAAC;AAElB,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,6EAA6E;IAC7E,QAAQ,CAAC,WAAW,EAAE,gBAAgB,CAAC;IACvC,oEAAoE;IACpE,QAAQ,CAAC,YAAY,EAAE,uBAAuB,CAAC;IAC/C,0DAA0D;IAC1D,QAAQ,CAAC,YAAY,EAAE,YAAY,CAAC;IACpC;;;;;OAKG;IACH,QAAQ,CAAC,aAAa,EAAE,WAAW,CAAC,UAAU,CAAC,CAAC;IAChD;;;;OAIG;IACH,QAAQ,CAAC,uBAAuB,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,wBAAwB,CAAC,CAAC;CAClF;AAED,+DAA+D;AAC/D,MAAM,WAAW,QAAQ;IACvB,4EAA4E;IAC5E,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,GAAG,EAAE,gBAAgB,GAAG,IAAI,CAAC;IACjD,8DAA8D;IAC9D,SAAS,CACP,EAAE,EAAE,SAAS,EACb,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,SAAS,CAAC,EAAE,MAAM,EAClB,OAAO,CAAC,EAAE,YAAY,CAAC,SAAS,CAAC,GAChC,IAAI,CAAC;IACR;;;;;;;;;;OAUG;IACH,gBAAgB,CACd,EAAE,EAAE,SAAS,EACb,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,gBAAgB,EACtB,OAAO,EAAE,MAAM,EACf,SAAS,CAAC,EAAE,MAAM,EAClB,OAAO,CAAC,EAAE,YAAY,CAAC,SAAS,CAAC,GAChC,IAAI,CAAC;IACR,sEAAsE;IACtE,iBAAiB,CAAC,QAAQ,EAAE,mBAAmB,GAAG,OAAO,CAAC;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC3E,4EAA4E;IAC5E,uBAAuB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1F,oEAAoE;IACpE,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACrE,iEAAiE;IACjE,YAAY,CAAC,IAAI,EAAE;QACjB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;QAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;QACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QACzB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;KAC5B,GAAG,IAAI,CAAC;IACT,sEAAsE;IACtE,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,gBAAgB,GAAG,IAAI,CAAC;CACrE;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,YAAY,GAAG,QAAQ,CAoN3D"}
1
+ {"version":3,"file":"outbound.d.ts","sourceRoot":"","sources":["../../src/ggui-session-channel/outbound.ts"],"names":[],"mappings":"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"}
@@ -12,16 +12,48 @@
12
12
  * inline comments here cover impl-level decisions only.
13
13
  */
14
14
  import { assertStreamContract } from "@ggui-ai/mcp-server-handlers/renders";
15
+ /**
16
+ * Low-level ws wire write shared by the closure-bound `send` helper and
17
+ * {@link createWsSink} — skips closed sockets, warn-logs send failures.
18
+ * One definition so the guard cannot drift between the two surfaces.
19
+ */
20
+ function wsWrite(ws, logger, msg) {
21
+ if (ws.readyState !== ws.OPEN)
22
+ return;
23
+ try {
24
+ ws.send(JSON.stringify(msg));
25
+ }
26
+ catch (err) {
27
+ logger.warn("render_channel_send_failed", { error: String(err) });
28
+ }
29
+ }
30
+ /**
31
+ * WS-backed {@link SubscriberSink}. `write` wraps the existing send
32
+ * guard (readyState check + warn-log on failure); `resumeId` is ignored
33
+ * — WS resume rides `SubscribePayload.sinceSequence`, not a per-frame
34
+ * id. `end` maps `service_restart` → 1012 (RFC 6455 "Service Restart":
35
+ * reconnect immediately) and `session_expired` → 1008 (policy
36
+ * violation: do not blind-reconnect).
37
+ */
38
+ export function createWsSink(ws, logger) {
39
+ return {
40
+ isOpen: () => ws.readyState === ws.OPEN,
41
+ write: (frame) => {
42
+ wsWrite(ws, logger, frame);
43
+ },
44
+ end: (reason) => {
45
+ try {
46
+ ws.close(reason === "service_restart" ? 1012 : 1008, reason);
47
+ }
48
+ catch {
49
+ /* best-effort — socket may already be closing */
50
+ }
51
+ },
52
+ };
53
+ }
15
54
  export function createOutbound(deps) {
16
55
  function send(ws, msg) {
17
- if (ws.readyState !== ws.OPEN)
18
- return;
19
- try {
20
- ws.send(JSON.stringify(msg));
21
- }
22
- catch (err) {
23
- deps.logger.warn("render_channel_send_failed", { error: String(err) });
24
- }
56
+ wsWrite(ws, deps.logger, msg);
25
57
  }
26
58
  function sendError(ws, code, message, requestId, details) {
27
59
  send(ws, {
@@ -88,20 +120,22 @@ export function createOutbound(deps) {
88
120
  // `notifyGguiSessionCommit` JSDoc on the public interface for why
89
121
  // fresh subscribers rely on `ack.render` instead of a replay frame.
90
122
  // NOT routed through StreamFanout either — `type: 'render'` is a
91
- // distinct WebSocket message type. Filter the flat WS-subscriber
123
+ // distinct WebSocket message type. Filter the flat subscriber
92
124
  // set by sessionId; N is typically 1-2 (multi-tab render sharing).
125
+ // `sink.write` is the transport-neutral seam — WS and SSE
126
+ // subscribers receive the identical frame.
93
127
  const payload = matchType !== undefined ? { session: render, matchType } : { session: render };
94
128
  for (const sub of deps.wsSubscribers) {
95
129
  if (sub.sessionId !== sessionId)
96
130
  continue;
97
- send(sub.ws, { type: "render", payload });
131
+ sub.sink.write({ type: "render", payload });
98
132
  }
99
133
  }
100
134
  /**
101
135
  * Best-effort + orphan-tolerant per the docstring on the public
102
136
  * `sendPropsUpdate` method.
103
137
  */
104
- async function sendPropsUpdate(sessionId, props) {
138
+ async function sendPropsUpdate(sessionId, props, epoch) {
105
139
  let stored;
106
140
  try {
107
141
  stored = await deps.renderStore.get(sessionId);
@@ -119,29 +153,35 @@ export function createOutbound(deps) {
119
153
  });
120
154
  return;
121
155
  }
122
- // Filter the flat WS-subscriber set by sessionId; same posture as
123
- // `notifyGguiSessionCommit`. `send()` already silently skips closed sockets
124
- // and logs (but doesn't throw on) per-subscriber send failures, so
125
- // the calling handler can't be made to fail by a dead WebSocket.
156
+ // Filter the flat subscriber set by sessionId; same posture as
157
+ // `notifyGguiSessionCommit`. `sink.write` already silently skips
158
+ // closed transports and logs (but doesn't throw on) per-subscriber
159
+ // send failures, so the calling handler can't be made to fail by a
160
+ // dead transport.
161
+ // Freeze-latch epoch (#483): the caller passes the COMMIT-TIME
162
+ // epoch — an update fans its freshly-advanced value, an amend the
163
+ // unchanged head. Deliberately not re-read from the row here: a
164
+ // re-read races a concurrent update and could stamp an amend's
165
+ // frame with a newer epoch, freezing the live head it belongs to.
126
166
  for (const sub of deps.wsSubscribers) {
127
167
  if (sub.sessionId !== sessionId)
128
168
  continue;
129
- send(sub.ws, {
169
+ sub.sink.write({
130
170
  type: "props_update",
131
- payload: { sessionId, props },
171
+ payload: { sessionId, props, epoch },
132
172
  });
133
173
  }
134
174
  }
135
175
  function sendDrainAck({ sessionId, appId, eventId, drainedAt, }) {
136
176
  // Server-side fan-out for the action-drain ack.
137
- // Filter the flat WS-subscriber set by sessionId (same posture
177
+ // Filter the flat subscriber set by sessionId (same posture
138
178
  // as `sendPropsUpdate`). No persistence; subscribers that
139
179
  // missed the frame fall back to their 10s claim timer, which
140
180
  // the atomic pop resolves cleanly.
141
181
  for (const sub of deps.wsSubscribers) {
142
182
  if (sub.sessionId !== sessionId)
143
183
  continue;
144
- send(sub.ws, {
184
+ sub.sink.write({
145
185
  type: "drain_ack",
146
186
  payload: { sessionId, appId, eventId, drainedAt },
147
187
  });
@@ -149,10 +189,10 @@ export function createOutbound(deps) {
149
189
  }
150
190
  function externalBroadcast(sessionId, frame) {
151
191
  // Walk the flat subscriber set; filter to matching sessionId.
152
- // `send()` already guards closed sockets and logs (but doesn't
153
- // throw on) per-subscriber failures, so the caller (an external
154
- // pubsub on-message handler) can't be made to fail by a dead
155
- // WebSocket. No GguiSessionStore lookup — the publisher already
192
+ // `sink.write` already guards closed transports and logs (but
193
+ // doesn't throw on) per-subscriber failures, so the caller (an
194
+ // external pubsub on-message handler) can't be made to fail by a
195
+ // dead transport. No GguiSessionStore lookup — the publisher already
156
196
  // validated; this seam is the cross-process delivery path, not
157
197
  // the re-validation point.
158
198
  for (const sub of deps.wsSubscribers) {
@@ -170,7 +210,7 @@ export function createOutbound(deps) {
170
210
  frame.payload.seq <= sub.replayCompletedSeq) {
171
211
  continue;
172
212
  }
173
- send(sub.ws, frame);
213
+ sub.sink.write(frame);
174
214
  }
175
215
  }
176
216
  return {
@@ -10,7 +10,7 @@ import type { WebSocket, WebSocketServer } from "ws";
10
10
  import type { Logger } from "../logger.js";
11
11
  import type { ActionIngress } from "./action-ingress.js";
12
12
  import type { ChannelSubscriptions } from "./channel-subscriptions.js";
13
- import type { Subscriber } from "./internal-types.js";
13
+ import type { WsSubscriber } from "./internal-types.js";
14
14
  import type { Outbound } from "./outbound.js";
15
15
  import type { SubscribeHandlers } from "./subscribe.js";
16
16
  import type { SubscriberLifecycle } from "./subscriber-lifecycle.js";
@@ -47,8 +47,13 @@ export interface SocketRouterDeps {
47
47
  readonly logger: Logger;
48
48
  /** GguiSession backing store — observation-message patches persist here. */
49
49
  readonly renderStore: GguiSessionStore;
50
- /** ws → subscriber reverse index — the dispatcher's per-frame lookup. */
51
- readonly subscribersByWs: WeakMap<WebSocket, Subscriber>;
50
+ /**
51
+ * ws → subscriber reverse index — the dispatcher's per-frame lookup.
52
+ * WS-only by construction (the lifecycle module populates it for
53
+ * `transport: 'ws'` subscribers only); SSE subscribers have no
54
+ * inbound socket to route.
55
+ */
56
+ readonly subscribersByWs: WeakMap<WebSocket, WsSubscriber>;
52
57
  readonly send: Outbound["send"];
53
58
  readonly sendError: Outbound["sendError"];
54
59
  readonly unregister: SubscriberLifecycle["unregister"];
@@ -1 +1 @@
1
- {"version":3,"file":"socket-router.d.ts","sourceRoot":"","sources":["../../src/ggui-session-channel/socket-router.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAoB,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAInF,OAAO,KAAK,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,IAAI,CAAC;AACrD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AACvE,OAAO,KAAK,EAAE,UAAU,EAAmB,MAAM,qBAAqB,CAAC;AACvE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACxD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAErE;;;;;GAKG;AACH,MAAM,WAAW,6BAA6B;IAC5C,sDAAsD;IACtD,OAAO,EAAE,MAAM,CAAC;IAChB,yEAAyE;IACzE,IAAI,EAAE,MAAM,CAAC;IACb,kFAAkF;IAClF,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,gBAAgB;IAC/B,oEAAoE;IACpE,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,iFAAiF;IACjF,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,uEAAuE;IACvE,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;CACjC;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,4EAA4E;IAC5E,QAAQ,CAAC,WAAW,EAAE,gBAAgB,CAAC;IACvC,yEAAyE;IACzE,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC;IACzD,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IAChC,QAAQ,CAAC,SAAS,EAAE,QAAQ,CAAC,WAAW,CAAC,CAAC;IAC1C,QAAQ,CAAC,UAAU,EAAE,mBAAmB,CAAC,YAAY,CAAC,CAAC;IACvD,QAAQ,CAAC,eAAe,EAAE,iBAAiB,CAAC,iBAAiB,CAAC,CAAC;IAC/D,QAAQ,CAAC,mBAAmB,EAAE,aAAa,CAAC,qBAAqB,CAAC,CAAC;IACnE,QAAQ,CAAC,sBAAsB,EAAE,oBAAoB,CAAC,wBAAwB,CAAC,CAAC;IAChF,QAAQ,CAAC,wBAAwB,EAAE,oBAAoB,CAAC,0BAA0B,CAAC,CAAC;IACpF,wDAAwD;IACxD,QAAQ,CAAC,gBAAgB,EAAE,gBAAgB,CAAC;IAC5C,kEAAkE;IAClE,QAAQ,CAAC,sBAAsB,EAAE,6BAA6B,CAAC;CAChE;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,eAAe,EAAE,IAAI,EAAE,gBAAgB,GAAG,IAAI,CAmarF"}
1
+ {"version":3,"file":"socket-router.d.ts","sourceRoot":"","sources":["../../src/ggui-session-channel/socket-router.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAoB,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAInF,OAAO,KAAK,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,IAAI,CAAC;AACrD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AACvE,OAAO,KAAK,EAAmB,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACzE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACxD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAErE;;;;;GAKG;AACH,MAAM,WAAW,6BAA6B;IAC5C,sDAAsD;IACtD,OAAO,EAAE,MAAM,CAAC;IAChB,yEAAyE;IACzE,IAAI,EAAE,MAAM,CAAC;IACb,kFAAkF;IAClF,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,gBAAgB;IAC/B,oEAAoE;IACpE,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,iFAAiF;IACjF,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,uEAAuE;IACvE,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;CACjC;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,4EAA4E;IAC5E,QAAQ,CAAC,WAAW,EAAE,gBAAgB,CAAC;IACvC;;;;;OAKG;IACH,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC;IAC3D,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IAChC,QAAQ,CAAC,SAAS,EAAE,QAAQ,CAAC,WAAW,CAAC,CAAC;IAC1C,QAAQ,CAAC,UAAU,EAAE,mBAAmB,CAAC,YAAY,CAAC,CAAC;IACvD,QAAQ,CAAC,eAAe,EAAE,iBAAiB,CAAC,iBAAiB,CAAC,CAAC;IAC/D,QAAQ,CAAC,mBAAmB,EAAE,aAAa,CAAC,qBAAqB,CAAC,CAAC;IACnE,QAAQ,CAAC,sBAAsB,EAAE,oBAAoB,CAAC,wBAAwB,CAAC,CAAC;IAChF,QAAQ,CAAC,wBAAwB,EAAE,oBAAoB,CAAC,0BAA0B,CAAC,CAAC;IACpF,wDAAwD;IACxD,QAAQ,CAAC,gBAAgB,EAAE,gBAAgB,CAAC;IAC5C,kEAAkE;IAClE,QAAQ,CAAC,sBAAsB,EAAE,6BAA6B,CAAC;CAChE;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,eAAe,EAAE,IAAI,EAAE,gBAAgB,GAAG,IAAI,CAuarF"}
@@ -353,7 +353,12 @@ export function attachSocketRouter(wss, deps) {
353
353
  }));
354
354
  });
355
355
  ws.on("close", () => {
356
- deps.unregister(ws);
356
+ // Resolve the subscriber bound to this socket (if any) before
357
+ // tearing it down — `unregister` is subscriber-keyed now that
358
+ // the lifecycle module is transport-neutral.
359
+ const closedSub = deps.subscribersByWs.get(ws);
360
+ if (closedSub)
361
+ deps.unregister(closedSub);
357
362
  pendingIdentity.delete(ws);
358
363
  // A pending socket that closes before subscribing frees its idle
359
364
  // timer + ceiling slot. For a socket that already subscribed,
@@ -6,12 +6,13 @@
6
6
  * reconnect-credential mint, identity-default appId resolution,
7
7
  * dev-mode render provisioning, replay, registration, and the ack.
8
8
  */
9
- import type { AuthAdapter, AuthResult, GguiSessionStore, GguiSessionStreamBuffer, StreamFanout } from "@ggui-ai/mcp-server-core";
9
+ import type { AuthAdapter, AuthResult, GguiSessionStore, GguiSessionStreamBuffer, StoredGguiSession, StreamFanout } from "@ggui-ai/mcp-server-core";
10
10
  import type { WebSocketMessage } from "@ggui-ai/protocol/transport/websocket";
11
11
  import type { IncomingMessage } from "node:http";
12
12
  import type { WebSocket } from "ws";
13
13
  import type { Logger } from "../logger.js";
14
- import type { Outbound } from "./outbound.js";
14
+ import type { SubscriberSink } from "./internal-types.js";
15
+ import { type Outbound } from "./outbound.js";
15
16
  import type { SubscriberLifecycle } from "./subscriber-lifecycle.js";
16
17
  /**
17
18
  * Bootstrap-auth plumbing for the live-channel endpoint.
@@ -141,10 +142,39 @@ export interface SubscribeDeps {
141
142
  readonly appIdFromIdentity?: (result: AuthResult) => string;
142
143
  /** Version-handshake policy — see `GguiSessionChannelOptions.versionPolicy`. */
143
144
  readonly versionPolicy?: "advisory" | "reject";
144
- readonly send: Outbound["send"];
145
145
  readonly sendError: Outbound["sendError"];
146
146
  readonly register: SubscriberLifecycle["register"];
147
+ readonly unregister: SubscriberLifecycle["unregister"];
147
148
  }
149
+ /**
150
+ * Arguments for {@link SubscribeHandlers.completeSubscribe} — the
151
+ * transport-neutral subscribe tail. The auth head (WS: version
152
+ * handshake + bootstrap verify; SSE: the HTTP wsToken pre-gate on
153
+ * `/api/sessions/:sessionId/stream`) runs BEFORE this; the tail trusts
154
+ * `stored` + `identity` as already-authorized.
155
+ */
156
+ export type CompleteSubscribeArgs = {
157
+ /** Resolved render row the subscriber binds to. */
158
+ readonly stored: StoredGguiSession;
159
+ /** Already-verified identity for the subscriber row. */
160
+ readonly identity: AuthResult;
161
+ /** Transport write surface the tail (and the pump) emits through. */
162
+ readonly sink: SubscriberSink;
163
+ /** GguiSessionEvent-ledger cursor — replays `render_event` frames when set. */
164
+ readonly sinceSequence?: number;
165
+ /** Stream-buffer cursor — replays `data` frames per policy when set. */
166
+ readonly fromSeq?: number;
167
+ /** Correlates the ack / error frames to a WS request. SSE has none. */
168
+ readonly requestId?: string;
169
+ /** Bootstrap-minted reconnect credential to stamp on the ack. */
170
+ readonly sessionToken?: string;
171
+ } & ({
172
+ readonly transport: "ws";
173
+ /** The socket — kept on the subscriber row for inbound dispatch. */
174
+ readonly ws: WebSocket;
175
+ } | {
176
+ readonly transport: "sse";
177
+ });
148
178
  export interface SubscribeHandlers {
149
179
  /**
150
180
  * Resolve the connecting client's identity at HTTP-upgrade time,
@@ -160,6 +190,21 @@ export interface SubscribeHandlers {
160
190
  readonly sessionId: string;
161
191
  readonly appId: string;
162
192
  }): Promise<void>;
193
+ /**
194
+ * The transport-neutral subscribe tail, order preserved exactly:
195
+ * stream-cursor snapshot → stream-buffer replay read → StreamFanout
196
+ * iterator → register (pump starts) → ack → GguiSessionEvent-ledger
197
+ * replay (`render_event` frames, `resumeId` = ledger seq) →
198
+ * stream-replay `data` frames. WS `handleSubscribe` calls this after
199
+ * its auth/provision head; SSE attaches call it via
200
+ * `GguiSessionChannelServer.attachExternalSubscriber`.
201
+ *
202
+ * Returns a `detach` that idempotently unregisters the subscriber
203
+ * (ends the pump, unhooks the StreamFanout, clears polling loops).
204
+ */
205
+ completeSubscribe(args: CompleteSubscribeArgs): Promise<{
206
+ readonly detach: () => void;
207
+ }>;
163
208
  }
164
209
  export declare function createSubscribeHandlers(deps: SubscribeDeps): SubscribeHandlers;
165
210
  //# sourceMappingURL=subscribe.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"subscribe.d.ts","sourceRoot":"","sources":["../../src/ggui-session-channel/subscribe.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"subscribe.d.ts","sourceRoot":"","sources":["../../src/ggui-session-channel/subscribe.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EACV,WAAW,EACX,UAAU,EACV,gBAAgB,EAChB,uBAAuB,EACvB,iBAAiB,EACjB,YAAY,EACb,MAAM,0BAA0B,CAAC;AAGlC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AAC9E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AACjD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AAMpC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,KAAK,EAGV,cAAc,EAEf,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAgB,KAAK,QAAQ,EAAE,MAAM,eAAe,CAAC;AAC5D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAErE;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH;;;;;;;;;;GAUG;AACH,MAAM,MAAM,uCAAuC,GAC/C;IACE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAClB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB,GACD;IAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,SAAS,GAAG,SAAS,CAAA;CAAE,CAAC;AAEnE;;;;;;GAMG;AACH,MAAM,MAAM,wCAAwC,GAChD;IACE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAClB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B,GACD;IAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,eAAe,GAAG,SAAS,CAAA;CAAE,CAAC;AAEzE,MAAM,WAAW,2BAA2B;IAC1C;;;;;;;;OAQG;IACH,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,uCAAuC,CAAC;IAC/D;;;;OAIG;IACH,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;IAC5D;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,wCAAwC,CAAC;CAClE;AAED;;;;GAIG;AACH,MAAM,WAAW,4BAA4B;IAC3C;;;;OAIG;IACH,UAAU,CAAC,OAAO,EAAE,OAAO,WAAW,EAAE,mBAAmB,GAAG,MAAM,GAAG,IAAI,CAAC;IAC5E;;;;OAIG;IACH,MAAM,CAAC,WAAW,EAAE,MAAM,GAAG;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;CAC1E;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,kEAAkE;IAClE,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAC3B,QAAQ,CAAC,WAAW,EAAE,gBAAgB,CAAC;IACvC,sEAAsE;IACtE,QAAQ,CAAC,YAAY,EAAE,uBAAuB,CAAC;IAC/C,mEAAmE;IACnE,QAAQ,CAAC,YAAY,EAAE,YAAY,CAAC;IACpC,iFAAiF;IACjF,QAAQ,CAAC,SAAS,CAAC,EAAE,2BAA2B,CAAC;IACjD,4EAA4E;IAC5E,QAAQ,CAAC,UAAU,CAAC,EAAE,4BAA4B,CAAC;IACnD,2FAA2F;IAC3F,QAAQ,CAAC,iBAAiB,CAAC,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,MAAM,CAAC;IAC5D,gFAAgF;IAChF,QAAQ,CAAC,aAAa,CAAC,EAAE,UAAU,GAAG,QAAQ,CAAC;IAC/C,QAAQ,CAAC,SAAS,EAAE,QAAQ,CAAC,WAAW,CAAC,CAAC;IAC1C,QAAQ,CAAC,QAAQ,EAAE,mBAAmB,CAAC,UAAU,CAAC,CAAC;IACnD,QAAQ,CAAC,UAAU,EAAE,mBAAmB,CAAC,YAAY,CAAC,CAAC;CACxD;AAED;;;;;;GAMG;AACH,MAAM,MAAM,qBAAqB,GAAG;IAClC,mDAAmD;IACnD,QAAQ,CAAC,MAAM,EAAE,iBAAiB,CAAC;IACnC,wDAAwD;IACxD,QAAQ,CAAC,QAAQ,EAAE,UAAU,CAAC;IAC9B,qEAAqE;IACrE,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC;IAC9B,+EAA+E;IAC/E,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,wEAAwE;IACxE,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,uEAAuE;IACvE,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,iEAAiE;IACjE,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;CAChC,GAAG,CACA;IACE,QAAQ,CAAC,SAAS,EAAE,IAAI,CAAC;IACzB,oEAAoE;IACpE,QAAQ,CAAC,EAAE,EAAE,SAAS,CAAC;CACxB,GACD;IAAE,QAAQ,CAAC,SAAS,EAAE,KAAK,CAAA;CAAE,CAChC,CAAC;AAEF,MAAM,WAAW,iBAAiB;IAChC;;;;;OAKG;IACH,0BAA0B,CAAC,GAAG,EAAE,eAAe,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IACtE,iEAAiE;IACjE,eAAe,CACb,EAAE,EAAE,SAAS,EACb,QAAQ,EAAE,UAAU,EACpB,OAAO,EAAE,gBAAgB,GAAG;QAAE,IAAI,EAAE,WAAW,CAAA;KAAE,EACjD,WAAW,CAAC,EAAE;QAAE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GACnE,OAAO,CAAC,IAAI,CAAC,CAAC;IACjB;;;;;;;;;;;OAWG;IACH,iBAAiB,CAAC,IAAI,EAAE,qBAAqB,GAAG,OAAO,CAAC;QAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,IAAI,CAAA;KAAE,CAAC,CAAC;CAC1F;AAkBD,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,aAAa,GAAG,iBAAiB,CA+gB9E"}