@ggui-ai/mcp-server 0.2.0-alpha.4 → 0.4.0-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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 +2 -2
  9. package/dist/build-mcp.d.ts.map +1 -1
  10. package/dist/build-mcp.js +64 -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 +204 -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 +1 @@
1
- {"version":3,"file":"pairing-transport.d.ts","sourceRoot":"","sources":["../src/pairing-transport.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,OAAO,KAAK,EAAE,OAAO,EAAqB,MAAM,SAAS,CAAC;AAC1D,OAAO,KAAK,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAE5E,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAE1C,mEAAmE;AACnE,eAAO,MAAM,oBAAoB,UAAU,CAAC;AAE5C;;;;GAIG;AACH,eAAO,MAAM,+BAA+B,qBAAqB,CAAC;AAElE;;;;;;GAMG;AACH,eAAO,MAAM,iCAAiC,kCACb,CAAC;AAElC,MAAM,WAAW,uBAAuB;IACtC,4DAA4D;IAC5D,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAC;IACjC;;;;OAIG;IACH,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAC3B,8DAA8D;IAC9D,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB;;OAEG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;OAKG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvC;;;;;;;OAOG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1C;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,OAAO,EACZ,IAAI,EAAE,uBAAuB,GAC5B,IAAI,CAmNN"}
1
+ {"version":3,"file":"pairing-transport.d.ts","sourceRoot":"","sources":["../src/pairing-transport.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,OAAO,KAAK,EAAE,OAAO,EAAqB,MAAM,SAAS,CAAC;AAE1D,OAAO,KAAK,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAE5E,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAE1C,mEAAmE;AACnE,eAAO,MAAM,oBAAoB,UAAU,CAAC;AAE5C;;;;GAIG;AACH,eAAO,MAAM,+BAA+B,qBAAqB,CAAC;AAElE;;;;;;GAMG;AACH,eAAO,MAAM,iCAAiC,kCACb,CAAC;AAElC,MAAM,WAAW,uBAAuB;IACtC,4DAA4D;IAC5D,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAC;IACjC;;;;OAIG;IACH,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAC3B,8DAA8D;IAC9D,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB;;OAEG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;OAKG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvC;;;;;;;OAOG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1C;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,OAAO,EACZ,IAAI,EAAE,uBAAuB,GAC5B,IAAI,CAmNN"}
@@ -1,3 +1,4 @@
1
+ import { isRecord } from '@ggui-ai/protocol';
1
2
  import { resolveIdentity, UnauthenticatedError } from './auth.js';
2
3
  /** Default URL path the pairing-completion route is mounted at. */
3
4
  export const DEFAULT_PAIRING_PATH = '/pair';
@@ -36,7 +37,7 @@ export function mountPairingTransport(app, opts) {
36
37
  // stores verbatim.
37
38
  app.post(path, async (req, res) => {
38
39
  const reqLogger = opts.logger.child({ route: 'POST ' + path });
39
- const body = (req.body ?? {});
40
+ const body = isRecord(req.body) ? req.body : {};
40
41
  const code = typeof body['code'] === 'string' ? body['code'] : undefined;
41
42
  const deviceName = typeof body['deviceName'] === 'string' ? body['deviceName'] : undefined;
42
43
  const remoteAddress = req.socket.remoteAddress ?? undefined;
@@ -2,7 +2,7 @@
2
2
  * Per-request context plumbing — AsyncLocalStorage backed.
3
3
  *
4
4
  * The capability-URL routes (`/r/<code>`, `/api/bootstrap/<code>`) and
5
- * the push/update tool result-meta builders all want to know the
5
+ * the render/update tool result-meta builders all want to know the
6
6
  * absolute public base URL of THIS server as seen by THIS client.
7
7
  * That can't come from a static config in two common dev/OSS scenarios:
8
8
  *
@@ -30,7 +30,7 @@
30
30
  *
31
31
  * Auto-derive applies ONLY to data the relevant route really should
32
32
  * derive from the request:
33
- * - runtimeUrl on push/update bootstrap meta (this slice).
33
+ * - runtimeUrl on render/update bootstrap meta (this slice).
34
34
  *
35
35
  * Auto-derive MUST NOT apply to:
36
36
  * - OAuth callback URLs (operator-config'd one-time per provider;
@@ -2,7 +2,7 @@
2
2
  * Per-request context plumbing — AsyncLocalStorage backed.
3
3
  *
4
4
  * The capability-URL routes (`/r/<code>`, `/api/bootstrap/<code>`) and
5
- * the push/update tool result-meta builders all want to know the
5
+ * the render/update tool result-meta builders all want to know the
6
6
  * absolute public base URL of THIS server as seen by THIS client.
7
7
  * That can't come from a static config in two common dev/OSS scenarios:
8
8
  *
@@ -30,7 +30,7 @@
30
30
  *
31
31
  * Auto-derive applies ONLY to data the relevant route really should
32
32
  * derive from the request:
33
- * - runtimeUrl on push/update bootstrap meta (this slice).
33
+ * - runtimeUrl on render/update bootstrap meta (this slice).
34
34
  *
35
35
  * Auto-derive MUST NOT apply to:
36
36
  * - OAuth callback URLs (operator-config'd one-time per provider;
@@ -1 +1 @@
1
- {"version":3,"file":"reserved-validators.d.ts","sourceRoot":"","sources":["../src/reserved-validators.ts"],"names":[],"mappings":"AA6BA,OAAO,EAGL,KAAK,wBAAwB,EAE9B,MAAM,mBAAmB,CAAC;AA0C3B;;;;;;;;;GASG;AACH,wBAAgB,+BAA+B,IAAI,WAAW,CAC5D,MAAM,EACN,wBAAwB,CACzB,CAEA;AAED;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,WAAW,CAAC,MAAM,EAAE,wBAAwB,CAAC,GAAG,SAAS,EAC/D,QAAQ,EAAE,WAAW,CAAC,MAAM,EAAE,wBAAwB,CAAC,GAAG,SAAS,GAClE,WAAW,CAAC,MAAM,EAAE,wBAAwB,CAAC,GAAG,SAAS,CAS3D"}
1
+ {"version":3,"file":"reserved-validators.d.ts","sourceRoot":"","sources":["../src/reserved-validators.ts"],"names":[],"mappings":"AAqCA,OAAO,EAGL,KAAK,wBAAwB,EAE9B,MAAM,mBAAmB,CAAC;AA0C3B;;;;;;;;;GASG;AACH,wBAAgB,+BAA+B,IAAI,WAAW,CAC5D,MAAM,EACN,wBAAwB,CACzB,CAEA;AAED;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,WAAW,CAAC,MAAM,EAAE,wBAAwB,CAAC,GAAG,SAAS,EAC/D,QAAQ,EAAE,WAAW,CAAC,MAAM,EAAE,wBAAwB,CAAC,GAAG,SAAS,GAClE,WAAW,CAAC,MAAM,EAAE,wBAAwB,CAAC,GAAG,SAAS,CAS3D"}
@@ -13,7 +13,7 @@
13
13
  * - {@link composePreviewReservedValidator} — returns a single-entry
14
14
  * map binding `_ggui:preview` to the A2UI adapter. Call at server
15
15
  * construction and feed into `extraReservedValidators` on
16
- * `SessionChannelOptions` / `CreateGguiServerOptions`.
16
+ * `GguiSessionChannelOptions` / `CreateGguiServerOptions`.
17
17
  * - {@link mergeReservedValidators} — combine multiple validator maps
18
18
  * when a caller provides their own extras AND the server wants to
19
19
  * layer A2UI on top. Caller-provided entries win on key conflict.
@@ -22,6 +22,14 @@
22
22
  * catalog filtering, no surface-id checking — those live in the A2UI
23
23
  * runtime consumer. The adapter enforces only what `parseServerMessage`
24
24
  * enforces: message-shape conformance to the V1 write-path union.
25
+ *
26
+ * The three copies (mcp-server / react / react-native) are documented
27
+ * structural mirrors: docstrings may differ per package, but the
28
+ * EXECUTABLE CODE must stay identical — guarded by the
29
+ * comment-stripped mirror gate in the SDK twin-parity suites
30
+ * (`ggui-react{,-native}/src/twin-parity.test.ts`,
31
+ * `CODE_IDENTICAL_MIRRORS`). Apply code changes to all three copies
32
+ * in the same slice.
25
33
  */
26
34
  import { parseServerMessage, } from '@ggui-ai/preview-a2ui';
27
35
  import { PREVIEW_CHANNEL, } from '@ggui-ai/protocol';
@@ -0,0 +1,9 @@
1
+ /**
2
+ * A single (non-splat) route param / query value is a string at runtime, but
3
+ * @types/express@5 widens `req.params[name]` to `string | string[]` (path-to-
4
+ * regexp v8 supports repeatable params). Narrow to the single-string case;
5
+ * an array (multi-value) collapses to undefined so callers' existing
6
+ * missing-param guards handle it.
7
+ */
8
+ export declare function singleParam(v: string | string[] | undefined): string | undefined;
9
+ //# sourceMappingURL=route-param.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"route-param.d.ts","sourceRoot":"","sources":["../src/route-param.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAEhF"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * A single (non-splat) route param / query value is a string at runtime, but
3
+ * @types/express@5 widens `req.params[name]` to `string | string[]` (path-to-
4
+ * regexp v8 supports repeatable params). Narrow to the single-string case;
5
+ * an array (multi-value) collapses to undefined so callers' existing
6
+ * missing-param guards handle it.
7
+ */
8
+ export function singleParam(v) {
9
+ return typeof v === 'string' ? v : undefined;
10
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Iframe-runtime bundle static mount (C8 — plan §C8 Deliverable 2).
3
+ *
4
+ * Serves the `@ggui-ai/iframe-runtime` iframe runtime bundle from
5
+ * `runtimePath` (default `/_ggui/iframe-runtime.js`). The thin-shell
6
+ * HTML served from `ui://ggui/render` dynamic-script-loads this
7
+ * URL on boot — the rendering runtime is OUT of the shell and IN
8
+ * this separately-served file (C8 pivot, shrinking the shell from
9
+ * ~175 LOC inline JS to ~30 LOC wrapper).
10
+ *
11
+ * Routing discipline: registered BEFORE the console block because
12
+ * console's default `path` is `/` and its `express.static`
13
+ * + SPA-fallback would otherwise match `/_ggui/iframe-runtime.js` first
14
+ * (Express route table is order-sensitive). Registering early keeps
15
+ * the runtime path from leaking into the console's `index.html`
16
+ * fallback on a missing-bundle day.
17
+ *
18
+ * Missing-bundle posture mirrors the console mount: 503 with a
19
+ * `pnpm --filter @ggui-ai/iframe-runtime build` remediation hint. Silent
20
+ * 404 would be mistaken for "renderer is broken" instead of
21
+ * "renderer bundle wasn't built" — same debugging trap console
22
+ * avoids.
23
+ */
24
+ import type { Express } from "express";
25
+ import type { Logger } from "./logger.js";
26
+ interface MountOptions {
27
+ /** Express app to mount onto. */
28
+ readonly app: Express;
29
+ /** HTTP route under which the bundle is mounted. */
30
+ readonly runtimePath: string;
31
+ /** Absolute path of the built bundle file on disk. */
32
+ readonly runtimeBundleFile: string;
33
+ /** Structured logger for the missing-bundle boot warning. */
34
+ readonly logger: Logger;
35
+ }
36
+ /**
37
+ * Mount `GET <runtimePath>` onto the express app. Returns nothing —
38
+ * the route self-registers. When the bundle file is missing on disk
39
+ * the mount degrades to a 503 with a build hint.
40
+ */
41
+ export declare function mountRuntimeBundleRoute(opts: MountOptions): void;
42
+ export {};
43
+ //# sourceMappingURL=runtime-bundle-route.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime-bundle-route.d.ts","sourceRoot":"","sources":["../src/runtime-bundle-route.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAEvC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAE1C,UAAU,YAAY;IACpB,iCAAiC;IACjC,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC;IACtB,oDAAoD;IACpD,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,sDAAsD;IACtD,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC,6DAA6D;IAC7D,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI,CAiDhE"}
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Iframe-runtime bundle static mount (C8 — plan §C8 Deliverable 2).
3
+ *
4
+ * Serves the `@ggui-ai/iframe-runtime` iframe runtime bundle from
5
+ * `runtimePath` (default `/_ggui/iframe-runtime.js`). The thin-shell
6
+ * HTML served from `ui://ggui/render` dynamic-script-loads this
7
+ * URL on boot — the rendering runtime is OUT of the shell and IN
8
+ * this separately-served file (C8 pivot, shrinking the shell from
9
+ * ~175 LOC inline JS to ~30 LOC wrapper).
10
+ *
11
+ * Routing discipline: registered BEFORE the console block because
12
+ * console's default `path` is `/` and its `express.static`
13
+ * + SPA-fallback would otherwise match `/_ggui/iframe-runtime.js` first
14
+ * (Express route table is order-sensitive). Registering early keeps
15
+ * the runtime path from leaking into the console's `index.html`
16
+ * fallback on a missing-bundle day.
17
+ *
18
+ * Missing-bundle posture mirrors the console mount: 503 with a
19
+ * `pnpm --filter @ggui-ai/iframe-runtime build` remediation hint. Silent
20
+ * 404 would be mistaken for "renderer is broken" instead of
21
+ * "renderer bundle wasn't built" — same debugging trap console
22
+ * avoids.
23
+ */
24
+ import { existsSync } from "node:fs";
25
+ /**
26
+ * Mount `GET <runtimePath>` onto the express app. Returns nothing —
27
+ * the route self-registers. When the bundle file is missing on disk
28
+ * the mount degrades to a 503 with a build hint.
29
+ */
30
+ export function mountRuntimeBundleRoute(opts) {
31
+ const { app, runtimePath, runtimeBundleFile, logger } = opts;
32
+ if (existsSync(runtimeBundleFile)) {
33
+ app.get(runtimePath, (_req, res) => {
34
+ res.setHeader("Content-Type", "application/javascript; charset=utf-8");
35
+ // Short cache — operators iterating on the renderer want
36
+ // fresh copies after rebuild. Production hardening (etag,
37
+ // long-term caching with hashed filenames) is a follow-on
38
+ // concern; same posture console takes.
39
+ res.setHeader("Cache-Control", "no-cache");
40
+ // CORS: the bundle MUST be loadable from `<script type="module"
41
+ // src=...>` inside a sandboxed `srcdoc` iframe (the
42
+ // `<McpAppIframe>` mount path — see `packages/ggui-react/src/
43
+ // McpAppIframe/dispatch.ts::deriveResourceMountSource`). Such an
44
+ // iframe has the `null` origin and module-script fetches always
45
+ // run in CORS mode; without a permissive header browsers reject
46
+ // the response and the renderer never executes (Lane 1 specs
47
+ // pinning `data-ggui-mcp-app-iframe-lifecycle="code-ready"`
48
+ // hang to timeout). The bundle is public — it ships unmodified
49
+ // to anyone who fetched the page, so `*` is the right shape;
50
+ // there's no auth state on the renderer route to protect via a
51
+ // narrower origin allowlist. This pairs with the production
52
+ // `/ui://ggui/render` shell HTML setting `s.type='module'`
53
+ // (`mcp-apps-outbound.ts::GGUI_RENDER_SHELL_SCRIPT_BODY`).
54
+ res.setHeader("Access-Control-Allow-Origin", "*");
55
+ // `dotfiles: 'allow'` — express@5's `res.sendFile` (send@1.x)
56
+ // splits the FULL absolute path into segments and applies its
57
+ // default `dotfiles: 'ignore'` policy, which 404s any file whose
58
+ // path crosses a dot-prefixed directory segment (e.g. a checkout
59
+ // under `~/.local/...` or a git worktree under `.../.git/...`).
60
+ // `runtimeBundleFile` is a fixed, server-controlled absolute path
61
+ // — never derived from the request — so there is no traversal
62
+ // surface to protect; allow the bundle to serve regardless of
63
+ // where the package install tree happens to live. express@4's
64
+ // `sendFile` did not subject the parent directories to this check.
65
+ res.sendFile(runtimeBundleFile, { dotfiles: "allow" });
66
+ });
67
+ }
68
+ else {
69
+ logger.warn("renderer_bundle_missing", {
70
+ bundleFile: runtimeBundleFile,
71
+ hint: "Run `pnpm --filter @ggui-ai/iframe-runtime build` to produce the bundle. Serving 503 from the mount point until it exists.",
72
+ });
73
+ app.get(runtimePath, (_req, res) => {
74
+ res
75
+ .status(503)
76
+ .type("text/plain")
77
+ .send("renderer bundle not built. Run:\n pnpm --filter @ggui-ai/iframe-runtime build\n");
78
+ });
79
+ }
80
+ }
@@ -1,59 +1,44 @@
1
1
  /**
2
- * Schema compatibility check — verifies that a Render's declared
3
- * `actionSpec` / `streamSpec` schemas line up with the input/output
4
- * schemas of the tools they reference. Wired at two canonical check
5
- * points: `ggui_render` validation (defensive, fires when the
6
- * generator eventually emits contract) and blueprint registration
7
- * (the console blueprint-try endpoint — the real-world site where a
8
- * Render with pre-declared `actionSpec` / `streamSpec` and tool
9
- * refs lands on the server).
2
+ * Schema compatibility check — verifies that a GguiSession's declared
3
+ * `actionSpec` schemas line up with the input schemas of the
4
+ * `nextStep` tools they hint at. Wired at two canonical check points:
5
+ * `ggui_render` validation (defensive, fires when the generator
6
+ * eventually emits contract) and blueprint registration (the console
7
+ * blueprint-try endpoint — the real-world site where a GguiSession
8
+ * with a pre-declared `actionSpec` and tool hints lands on the
9
+ * server).
10
10
  *
11
11
  * **Algorithmic primitive** lives in `@ggui-ai/protocol`:
12
12
  *
13
13
  * - `isSchemaSubset(superset, subset)` answers "can every value
14
14
  * subset accepts also pass superset?".
15
15
  * - `zodToJsonSchema(...)` converts a tool's ZodRawShape
16
- * `inputSchema` / `outputSchema` to the JsonSchema shape the
17
- * subset algorithm consumes.
16
+ * `inputSchema` to the JsonSchema shape the subset algorithm
17
+ * consumes.
18
18
  *
19
- * **What this module adds.** A small layer that walks a Render's
20
- * `actionSpec` + `streamSpec`, resolves each declared `tool` ref
21
- * against a toolName → ZodRawShape registry, runs the appropriate
22
- * subset check, and reports a stable {@link SchemaCompatReport}. The
23
- * caller (console endpoint, push handler — later) decides whether
24
- * a non-empty report should throw, warn, or be ignored per the
25
- * {@link SchemaCompatMode} policy flag.
19
+ * **What this module adds.** A small layer that walks a GguiSession's
20
+ * `actionSpec`, resolves each declared `nextStep` hint against a
21
+ * toolName → ZodRawShape registry, runs the subset check, and reports
22
+ * a stable {@link SchemaCompatReport}. The caller (console endpoint,
23
+ * render handler) decides whether a non-empty report should throw,
24
+ * warn, or be ignored per the {@link SchemaCompatMode} policy flag.
26
25
  *
27
- * **Direction semantics (load-bearing).**
28
- *
29
- * - For `actionSpec[name].schema` + `tool`: the action's payload
30
- * is sent INTO the tool, so the action schema must be a subset
31
- * of the tool's `inputSchema`. Compat relation:
32
- * `isSchemaSubset(toolInputSchema, actionSchema)`.
33
- * - For `streamSpec[channel].schema` + `tool`: the tool's return
34
- * value is emitted OUT on the channel. Every value the tool
35
- * returns MUST be accepted by the channel schema (otherwise a
36
- * tool-fired refresh would emit a payload that subscribers
37
- * reject). The channel schema is therefore the PERMISSIVE side
38
- * and the tool's return is the restricted side. Compat relation:
39
- * `isSchemaSubset(channelSchema, toolReturnSchema)` — i.e.
40
- * "every toolReturn-accepted value is also channelSchema-
41
- * accepted".
42
- *
43
- * Note: the `StreamChannelEntry.schema` docstring phrases this
44
- * as "the tool's returns MUST be a superset of channel schema".
45
- * That historical phrasing is author-facing and slightly
46
- * misleading if read literally as set-theoretic superset of
47
- * accepted-values; the semantic intent is the one encoded here
48
- * — EVERY tool return passes channel validation.
26
+ * **Direction semantics (load-bearing).** For `actionSpec[name]
27
+ * .schema` + `nextStep`: the action's payload is what the agent
28
+ * forwards INTO the hinted tool, so the action schema must be a
29
+ * subset of the tool's `inputSchema`. Compat relation:
30
+ * `isSchemaSubset(toolInputSchema, actionSchema)`. The check is
31
+ * advisory by nature — `nextStep` is a hint the agent owns — but a
32
+ * mismatch on a same-server tool is an author bug worth failing
33
+ * loudly at registration time.
49
34
  */
50
35
  import type { ZodRawShape } from 'zod';
51
- import { type SubsetViolation, type ActionSpec, type StreamSpec } from '@ggui-ai/protocol';
36
+ import { type SubsetViolation, type ActionSpec } from '@ggui-ai/protocol';
52
37
  /**
53
38
  * Stance the host takes when a check surfaces violations.
54
39
  *
55
40
  * - `'reject'` (default) — throw {@link SchemaCompatError} so the
56
- * containing request fails before the stack item commits /
41
+ * containing request fails before the render commits /
57
42
  * before the blueprint registers. The canonical enforcement
58
43
  * posture for launch.
59
44
  * - `'warn'` — return the report without throwing. The caller is
@@ -77,25 +62,33 @@ export declare const DEFAULT_SCHEMA_COMPAT_MODE: SchemaCompatMode;
77
62
  */
78
63
  export interface SchemaCompatFinding {
79
64
  /**
80
- * Which spec side produced this finding. `'action'` for an
81
- * `actionSpec[name]` tool ref; `'stream'` for a
82
- * `streamSpec[channel].tool` ref. Consumers can branch on this
83
- * when rendering the cause.
65
+ * Which spec side produced this finding. Always `'action'` — the
66
+ * only spec side carrying a tool reference is `actionSpec[name]
67
+ * .nextStep`. Kept as a literal field so consumers that render the
68
+ * cause have a stable discriminator.
84
69
  */
85
- readonly kind: 'action' | 'stream';
70
+ readonly kind: 'action';
86
71
  /** Action name or channel name. */
87
72
  readonly specName: string;
88
73
  /** Tool name the spec referenced. */
89
74
  readonly toolName: string;
90
75
  /** One of:
91
- * - `'tool-not-found'` — the ref's tool is not registered on
92
- * the composed handler set. (Author bug — matching the
93
- * existing `TOOL_NOT_FOUND` contract semantics, but surfaced
94
- * before any envelope reaches the agentic loop.)
76
+ * - `'tool-not-found'` — the hinted tool is not registered on
77
+ * the composed handler set (and not declared in the contract's
78
+ * `agentCapabilities.tools` cross-MCP catalog).
95
79
  * - `'schema-mismatch'` — the subset check failed with at least
96
80
  * one violation. See {@link SchemaCompatFinding.violations}.
97
81
  */
98
82
  readonly reason: 'tool-not-found' | 'schema-mismatch';
83
+ /**
84
+ * Whether this finding blocks the render (`'error'`) or is purely
85
+ * advisory (`'warn'`). Defaults to error severity when omitted — the
86
+ * throw gate treats any finding lacking `severity: 'warn'` as a
87
+ * hard error. An action `nextStep` tool-not-found is tagged `'warn'`
88
+ * because `nextStep` is a documented HINT the agent owns and ggui
89
+ * never dispatches; an unresolved one must not block the render.
90
+ */
91
+ readonly severity?: 'error' | 'warn';
99
92
  /** Subset violations carried through for rich error rendering.
100
93
  * Empty array when `reason: 'tool-not-found'`. */
101
94
  readonly violations: readonly SubsetViolation[];
@@ -111,18 +104,17 @@ export interface SchemaCompatReport {
111
104
  }
112
105
  /**
113
106
  * Narrow shape of what the check helper needs from each registered
114
- * tool. Satisfied by `SharedHandler<ZodRawShape, ZodRawShape>` — the
115
- * two schema fields are the only consumed inputs.
107
+ * tool. Satisfied by `SharedHandler<ZodRawShape, ZodRawShape>` —
108
+ * `name` + `inputSchema` are the only consumed inputs.
116
109
  */
117
110
  export interface ToolSchemaRef {
118
111
  readonly name: string;
119
112
  readonly inputSchema: ZodRawShape;
120
- readonly outputSchema: ZodRawShape;
121
113
  }
122
114
  /**
123
- * Narrow shape of the Render subset the checker consumes — the
124
- * two spec fields PLUS the contract's own tool catalog. Accepts any
125
- * object carrying them, so both `@ggui-ai/protocol::Render` and
115
+ * Narrow shape of the GguiSession subset the checker consumes — the
116
+ * `actionSpec` PLUS the contract's own tool catalog. Accepts any
117
+ * object carrying them, so both `@ggui-ai/protocol::GguiSession` and
126
118
  * the console endpoint's manifest contract shape work without a cast.
127
119
  *
128
120
  * `agentCapabilities.tools` is the contract author's declared catalog
@@ -133,9 +125,8 @@ export interface ToolSchemaRef {
133
125
  * schema check (we can't validate a remote server's tool schema from
134
126
  * here; the agent owns the cross-MCP call).
135
127
  */
136
- export interface RenderContractShape {
128
+ export interface GguiSessionContractShape {
137
129
  readonly actionSpec?: ActionSpec;
138
- readonly streamSpec?: StreamSpec;
139
130
  readonly agentCapabilities?: {
140
131
  readonly tools?: Readonly<Record<string, unknown>>;
141
132
  };
@@ -150,9 +141,8 @@ export declare class SchemaCompatError extends Error {
150
141
  constructor(report: SchemaCompatReport, context: string);
151
142
  }
152
143
  /**
153
- * Check a Render's `actionSpec` / `streamSpec` entries against
154
- * the tool registry. See {@link SchemaCompatMode} for policy
155
- * semantics.
144
+ * Check a GguiSession's `actionSpec` entries against the tool
145
+ * registry. See {@link SchemaCompatMode} for policy semantics.
156
146
  *
157
147
  * Returns a {@link SchemaCompatReport}. When `mode === 'reject'`
158
148
  * AND findings exist, throws {@link SchemaCompatError} instead —
@@ -163,5 +153,17 @@ export declare class SchemaCompatError extends Error {
163
153
  * up in thrown error messages so an operator reading logs sees
164
154
  * which ingress surfaced the mismatch.
165
155
  */
166
- export declare function checkRenderSchemaCompat(render: RenderContractShape, tools: Iterable<ToolSchemaRef>, mode: SchemaCompatMode, context: string): SchemaCompatReport;
156
+ export declare function checkRenderSchemaCompat(render: GguiSessionContractShape, tools: Iterable<ToolSchemaRef>, mode: SchemaCompatMode, context: string): SchemaCompatReport;
157
+ /**
158
+ * Does this report carry at least one finding that should BLOCK the
159
+ * render? A finding blocks unless it is explicitly advisory
160
+ * (`severity: 'warn'`) — i.e. anything lacking `severity: 'warn'`
161
+ * counts as a hard error (default-error semantics). The single
162
+ * source of truth for the throw gate, shared by the internal
163
+ * `'reject'`-mode gate above AND any external host that runs the
164
+ * check in `'warn'` mode and owns its own `ggui_render` enforcement.
165
+ * Keeping ONE predicate guarantees the gates of every deployment of
166
+ * this server family can never silently diverge.
167
+ */
168
+ export declare function hasErrorFinding(report: SchemaCompatReport): boolean;
167
169
  //# sourceMappingURL=schema-compat.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"schema-compat.d.ts","sourceRoot":"","sources":["../src/schema-compat.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AACH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,KAAK,CAAC;AACvC,OAAO,EAGL,KAAK,eAAe,EACpB,KAAK,UAAU,EACf,KAAK,UAAU,EAChB,MAAM,mBAAmB,CAAC;AAE3B;;;;;;;;;;;;;;GAcG;AACH,MAAM,MAAM,gBAAgB,GAAG,QAAQ,GAAG,MAAM,GAAG,KAAK,CAAC;AAEzD;;;GAGG;AACH,eAAO,MAAM,0BAA0B,EAAE,gBAA2B,CAAC;AAErE;;;;GAIG;AACH,MAAM,WAAW,mBAAmB;IAClC;;;;;OAKG;IACH,QAAQ,CAAC,IAAI,EAAE,QAAQ,GAAG,QAAQ,CAAC;IACnC,mCAAmC;IACnC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,qCAAqC;IACrC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B;;;;;;;OAOG;IACH,QAAQ,CAAC,MAAM,EAAE,gBAAgB,GAAG,iBAAiB,CAAC;IACtD;uDACmD;IACnD,QAAQ,CAAC,UAAU,EAAE,SAAS,eAAe,EAAE,CAAC;CACjD;AAED;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,QAAQ,CAAC,QAAQ,EAAE,SAAS,mBAAmB,EAAE,CAAC;CACnD;AAED;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC;IAClC,QAAQ,CAAC,YAAY,EAAE,WAAW,CAAC;CACpC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,UAAU,CAAC,EAAE,UAAU,CAAC;IACjC,QAAQ,CAAC,UAAU,CAAC,EAAE,UAAU,CAAC;IACjC,QAAQ,CAAC,iBAAiB,CAAC,EAAE;QAC3B,QAAQ,CAAC,KAAK,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;KACpD,CAAC;CACH;AAED;;;;GAIG;AACH,qBAAa,iBAAkB,SAAQ,KAAK;IAC1C,QAAQ,CAAC,MAAM,EAAE,kBAAkB,CAAC;gBACxB,MAAM,EAAE,kBAAkB,EAAE,OAAO,EAAE,MAAM;CAKxD;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,mBAAmB,EAC3B,KAAK,EAAE,QAAQ,CAAC,aAAa,CAAC,EAC9B,IAAI,EAAE,gBAAgB,EACtB,OAAO,EAAE,MAAM,GACd,kBAAkB,CA0HpB"}
1
+ {"version":3,"file":"schema-compat.d.ts","sourceRoot":"","sources":["../src/schema-compat.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,KAAK,CAAC;AACvC,OAAO,EAGL,KAAK,eAAe,EACpB,KAAK,UAAU,EAChB,MAAM,mBAAmB,CAAC;AAE3B;;;;;;;;;;;;;;GAcG;AACH,MAAM,MAAM,gBAAgB,GAAG,QAAQ,GAAG,MAAM,GAAG,KAAK,CAAC;AAEzD;;;GAGG;AACH,eAAO,MAAM,0BAA0B,EAAE,gBAA2B,CAAC;AAErE;;;;GAIG;AACH,MAAM,WAAW,mBAAmB;IAClC;;;;;OAKG;IACH,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,mCAAmC;IACnC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,qCAAqC;IACrC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B;;;;;;OAMG;IACH,QAAQ,CAAC,MAAM,EAAE,gBAAgB,GAAG,iBAAiB,CAAC;IACtD;;;;;;;OAOG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IACrC;uDACmD;IACnD,QAAQ,CAAC,UAAU,EAAE,SAAS,eAAe,EAAE,CAAC;CACjD;AAED;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,QAAQ,CAAC,QAAQ,EAAE,SAAS,mBAAmB,EAAE,CAAC;CACnD;AAED;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC;CACnC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,UAAU,CAAC,EAAE,UAAU,CAAC;IACjC,QAAQ,CAAC,iBAAiB,CAAC,EAAE;QAC3B,QAAQ,CAAC,KAAK,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;KACpD,CAAC;CACH;AAED;;;;GAIG;AACH,qBAAa,iBAAkB,SAAQ,KAAK;IAC1C,QAAQ,CAAC,MAAM,EAAE,kBAAkB,CAAC;gBACxB,MAAM,EAAE,kBAAkB,EAAE,OAAO,EAAE,MAAM;CAKxD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,wBAAwB,EAChC,KAAK,EAAE,QAAQ,CAAC,aAAa,CAAC,EAC9B,IAAI,EAAE,gBAAgB,EACtB,OAAO,EAAE,MAAM,GACd,kBAAkB,CAuFpB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAEnE"}
@@ -18,9 +18,8 @@ export class SchemaCompatError extends Error {
18
18
  }
19
19
  }
20
20
  /**
21
- * Check a Render's `actionSpec` / `streamSpec` entries against
22
- * the tool registry. See {@link SchemaCompatMode} for policy
23
- * semantics.
21
+ * Check a GguiSession's `actionSpec` entries against the tool
22
+ * registry. See {@link SchemaCompatMode} for policy semantics.
24
23
  *
25
24
  * Returns a {@link SchemaCompatReport}. When `mode === 'reject'`
26
25
  * AND findings exist, throws {@link SchemaCompatError} instead —
@@ -77,6 +76,10 @@ export function checkRenderSchemaCompat(render, tools, mode, context) {
77
76
  specName: actionName,
78
77
  toolName,
79
78
  reason: 'tool-not-found',
79
+ // Advisory: `nextStep` is a documented HINT the agent owns and
80
+ // ggui never dispatches, so an unresolved one must not block
81
+ // the render. Warn-only — the throw gate skips it.
82
+ severity: 'warn',
80
83
  violations: [],
81
84
  });
82
85
  continue;
@@ -102,71 +105,40 @@ export function checkRenderSchemaCompat(render, tools, mode, context) {
102
105
  });
103
106
  }
104
107
  }
105
- // streamSpec — each channel.tool's outputSchema must be a superset
106
- // of channel.schema (inverted direction from actions).
107
- const streamSpec = render.streamSpec ?? {};
108
- for (const [channelName, entry] of Object.entries(streamSpec)) {
109
- if (!entry || typeof entry !== 'object')
110
- continue;
111
- const toolName = entry.tool;
112
- if (typeof toolName !== 'string' || toolName.length === 0)
113
- continue;
114
- const channelSchema = entry.schema;
115
- if (!channelSchema)
116
- continue;
117
- const tool = byName.get(toolName);
118
- if (!tool) {
119
- // Cross-MCP escape hatch — symmetric with the actionSpec arm.
120
- if (contractDeclaredTools.has(toolName))
121
- continue;
122
- findings.push({
123
- kind: 'stream',
124
- specName: channelName,
125
- toolName,
126
- reason: 'tool-not-found',
127
- violations: [],
128
- });
129
- continue;
130
- }
131
- const toolOutput = zodToJsonSchema(tool.outputSchema);
132
- // Direction: every tool-returned value must be accepted by the
133
- // channel schema. Channel is the superset / permissive side,
134
- // tool-return is the restricted side.
135
- const result = isSchemaSubset(channelSchema, toolOutput);
136
- if (!result.compatible) {
137
- findings.push({
138
- kind: 'stream',
139
- specName: channelName,
140
- toolName,
141
- reason: 'schema-mismatch',
142
- violations: result.violations,
143
- });
144
- }
145
- }
146
108
  const report = {
147
109
  compatible: findings.length === 0,
148
110
  findings,
149
111
  };
150
- if (mode === 'reject' && !report.compatible) {
112
+ if (mode === 'reject' && hasErrorFinding(report)) {
151
113
  throw new SchemaCompatError(report, context);
152
114
  }
153
115
  return report;
154
116
  }
117
+ /**
118
+ * Does this report carry at least one finding that should BLOCK the
119
+ * render? A finding blocks unless it is explicitly advisory
120
+ * (`severity: 'warn'`) — i.e. anything lacking `severity: 'warn'`
121
+ * counts as a hard error (default-error semantics). The single
122
+ * source of truth for the throw gate, shared by the internal
123
+ * `'reject'`-mode gate above AND any external host that runs the
124
+ * check in `'warn'` mode and owns its own `ggui_render` enforcement.
125
+ * Keeping ONE predicate guarantees the gates of every deployment of
126
+ * this server family can never silently diverge.
127
+ */
128
+ export function hasErrorFinding(report) {
129
+ return report.findings.some((f) => f.severity !== 'warn');
130
+ }
155
131
  /**
156
132
  * Format a report into a human-readable message suitable for a
157
133
  * thrown error or a log line. Output is deterministic — sorted
158
- * by `kind` then `specName` — so tests can pattern-match on the
134
+ * by `specName` — so tests can pattern-match on the
159
135
  * exact string without ordering flakes.
160
136
  */
161
137
  function formatReport(report, context) {
162
138
  if (report.compatible) {
163
139
  return `${context}: SCHEMA_MISMATCH_ERROR (no findings — internal error)`;
164
140
  }
165
- const sorted = [...report.findings].sort((a, b) => {
166
- if (a.kind !== b.kind)
167
- return a.kind < b.kind ? -1 : 1;
168
- return a.specName < b.specName ? -1 : a.specName > b.specName ? 1 : 0;
169
- });
141
+ const sorted = [...report.findings].sort((a, b) => a.specName < b.specName ? -1 : a.specName > b.specName ? 1 : 0);
170
142
  const lines = sorted.map((f) => {
171
143
  if (f.reason === 'tool-not-found') {
172
144
  return `- ${f.kind} "${f.specName}" references tool "${f.toolName}" which is not registered`;