@ggui-ai/mcp-server-handlers 0.24.0 → 0.26.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 (196) hide show
  1. package/README.md +2 -2
  2. package/dist/blueprints/search-blueprints.d.ts +5 -5
  3. package/dist/blueprints/search-blueprints.js +5 -5
  4. package/dist/credits/index.d.ts +2 -2
  5. package/dist/credits/index.d.ts.map +1 -1
  6. package/dist/credits/index.js +1 -1
  7. package/dist/credits/list-credit-transactions.d.ts +21 -7
  8. package/dist/credits/list-credit-transactions.d.ts.map +1 -1
  9. package/dist/credits/list-credit-transactions.js +12 -2
  10. package/dist/index.d.ts +2 -5
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +5 -9
  13. package/dist/ops-apps/create-app.d.ts +10 -11
  14. package/dist/ops-apps/create-app.d.ts.map +1 -1
  15. package/dist/ops-apps/create-app.js +12 -13
  16. package/dist/ops-apps/identity.d.ts +9 -8
  17. package/dist/ops-apps/identity.d.ts.map +1 -1
  18. package/dist/ops-apps/identity.js +2 -1
  19. package/dist/ops-apps/index.d.ts +2 -2
  20. package/dist/ops-apps/index.js +2 -2
  21. package/dist/ops-apps/list-apps.d.ts +5 -6
  22. package/dist/ops-apps/list-apps.d.ts.map +1 -1
  23. package/dist/ops-apps/list-apps.js +6 -7
  24. package/dist/ops-apps/set-app-theme.d.ts +12 -8
  25. package/dist/ops-apps/set-app-theme.d.ts.map +1 -1
  26. package/dist/ops-apps/set-app-theme.js +11 -7
  27. package/dist/ops-apps/set-default-app.d.ts +1 -2
  28. package/dist/ops-apps/set-default-app.d.ts.map +1 -1
  29. package/dist/ops-apps/set-default-app.js +1 -2
  30. package/dist/ops-apps/types.d.ts +20 -23
  31. package/dist/ops-apps/types.d.ts.map +1 -1
  32. package/dist/ops-apps/types.js +4 -6
  33. package/dist/ops-blueprint/cache-mirror.d.ts +41 -0
  34. package/dist/ops-blueprint/cache-mirror.d.ts.map +1 -0
  35. package/dist/ops-blueprint/cache-mirror.js +19 -0
  36. package/dist/ops-blueprint/delete.d.ts +18 -0
  37. package/dist/ops-blueprint/delete.d.ts.map +1 -1
  38. package/dist/ops-blueprint/delete.js +21 -2
  39. package/dist/ops-blueprint/errors.d.ts +23 -0
  40. package/dist/ops-blueprint/errors.d.ts.map +1 -1
  41. package/dist/ops-blueprint/errors.js +29 -0
  42. package/dist/ops-blueprint/generate.d.ts +1 -3
  43. package/dist/ops-blueprint/generate.d.ts.map +1 -1
  44. package/dist/ops-blueprint/generate.js +47 -58
  45. package/dist/ops-blueprint/index.d.ts +1 -1
  46. package/dist/ops-blueprint/index.d.ts.map +1 -1
  47. package/dist/ops-blueprint/index.js +1 -1
  48. package/dist/ops-blueprint/list.d.ts.map +1 -1
  49. package/dist/ops-blueprint/list.js +1 -2
  50. package/dist/ops-blueprint/register.d.ts +83 -6
  51. package/dist/ops-blueprint/register.d.ts.map +1 -1
  52. package/dist/ops-blueprint/register.js +261 -158
  53. package/dist/ops-blueprint/update.d.ts +3 -2
  54. package/dist/ops-blueprint/update.d.ts.map +1 -1
  55. package/dist/ops-blueprint/update.js +4 -3
  56. package/dist/renders/blueprint-coverage.d.ts +23 -4
  57. package/dist/renders/blueprint-coverage.d.ts.map +1 -1
  58. package/dist/renders/blueprint-coverage.js +45 -5
  59. package/dist/renders/blueprint-durability.d.ts.map +1 -1
  60. package/dist/renders/blueprint-durability.js +36 -21
  61. package/dist/renders/blueprint-matcher.d.ts +57 -7
  62. package/dist/renders/blueprint-matcher.d.ts.map +1 -1
  63. package/dist/renders/blueprint-matcher.js +154 -36
  64. package/dist/renders/blueprint-registry.d.ts +154 -1
  65. package/dist/renders/blueprint-registry.d.ts.map +1 -1
  66. package/dist/renders/blueprint-registry.js +337 -10
  67. package/dist/renders/build-seed-pool.d.ts.map +1 -1
  68. package/dist/renders/build-seed-pool.js +6 -0
  69. package/dist/renders/cache-trace-sink.d.ts +12 -0
  70. package/dist/renders/cache-trace-sink.d.ts.map +1 -1
  71. package/dist/renders/cache-trace-sink.js +15 -1
  72. package/dist/renders/consume.d.ts +3 -2
  73. package/dist/renders/consume.d.ts.map +1 -1
  74. package/dist/renders/consume.js +50 -15
  75. package/dist/renders/cross-app-refused.d.ts +17 -0
  76. package/dist/renders/cross-app-refused.d.ts.map +1 -0
  77. package/dist/renders/cross-app-refused.js +23 -0
  78. package/dist/renders/decide-handshake.d.ts +47 -1
  79. package/dist/renders/decide-handshake.d.ts.map +1 -1
  80. package/dist/renders/decide-handshake.js +74 -6
  81. package/dist/renders/declare-tool-catalog.d.ts.map +1 -1
  82. package/dist/renders/declare-tool-catalog.js +20 -17
  83. package/dist/renders/export-registry.d.ts +8 -1
  84. package/dist/renders/export-registry.d.ts.map +1 -1
  85. package/dist/renders/export-registry.js +3 -0
  86. package/dist/renders/fits.d.ts +60 -0
  87. package/dist/renders/fits.d.ts.map +1 -0
  88. package/dist/renders/fits.js +114 -0
  89. package/dist/renders/handshake-generation-inputs.d.ts +89 -0
  90. package/dist/renders/handshake-generation-inputs.d.ts.map +1 -0
  91. package/dist/renders/handshake-generation-inputs.js +65 -0
  92. package/dist/renders/handshake.d.ts +6 -42
  93. package/dist/renders/handshake.d.ts.map +1 -1
  94. package/dist/renders/handshake.js +1 -1
  95. package/dist/renders/index.d.ts +10 -5
  96. package/dist/renders/index.d.ts.map +1 -1
  97. package/dist/renders/index.js +10 -3
  98. package/dist/renders/installed-blueprints-provider.d.ts.map +1 -1
  99. package/dist/renders/installed-blueprints-provider.js +3 -1
  100. package/dist/renders/match-precision/pairs.d.ts +6 -5
  101. package/dist/renders/match-precision/pairs.d.ts.map +1 -1
  102. package/dist/renders/match-precision/run-probe.js +1 -2
  103. package/dist/renders/match-precision/topk-probe.js +13 -8
  104. package/dist/renders/record-committed-one-shot.d.ts +18 -3
  105. package/dist/renders/record-committed-one-shot.d.ts.map +1 -1
  106. package/dist/renders/record-committed-one-shot.js +14 -7
  107. package/dist/renders/refresh-ws-token.d.ts +66 -87
  108. package/dist/renders/refresh-ws-token.d.ts.map +1 -1
  109. package/dist/renders/refresh-ws-token.js +93 -89
  110. package/dist/renders/render-identity.d.ts +16 -5
  111. package/dist/renders/render-identity.d.ts.map +1 -1
  112. package/dist/renders/render-identity.js +26 -10
  113. package/dist/renders/render-read-gate.d.ts +85 -0
  114. package/dist/renders/render-read-gate.d.ts.map +1 -0
  115. package/dist/renders/render-read-gate.js +57 -0
  116. package/dist/renders/render.d.ts +77 -27
  117. package/dist/renders/render.d.ts.map +1 -1
  118. package/dist/renders/render.js +269 -95
  119. package/dist/renders/report-render-failure.d.ts +69 -0
  120. package/dist/renders/report-render-failure.d.ts.map +1 -0
  121. package/dist/renders/report-render-failure.js +83 -0
  122. package/dist/renders/runtime-pull.d.ts.map +1 -1
  123. package/dist/renders/runtime-pull.js +6 -0
  124. package/dist/renders/slice-meta-derivation.d.ts +8 -1
  125. package/dist/renders/slice-meta-derivation.d.ts.map +1 -1
  126. package/dist/renders/slice-meta-derivation.js +1 -0
  127. package/dist/renders/submit-action.d.ts +47 -78
  128. package/dist/renders/submit-action.d.ts.map +1 -1
  129. package/dist/renders/submit-action.js +312 -84
  130. package/dist/renders/sync-context.d.ts +3 -1
  131. package/dist/renders/sync-context.d.ts.map +1 -1
  132. package/dist/renders/sync-context.js +53 -20
  133. package/dist/types.d.ts +71 -1
  134. package/dist/types.d.ts.map +1 -1
  135. package/dist/types.js +32 -0
  136. package/package.json +8 -20
  137. package/dist/ops-connector-keys/in-memory-fake.test-util.d.ts +0 -26
  138. package/dist/ops-connector-keys/in-memory-fake.test-util.d.ts.map +0 -1
  139. package/dist/ops-connector-keys/in-memory-fake.test-util.js +0 -60
  140. package/dist/ops-connector-keys/index.d.ts +0 -19
  141. package/dist/ops-connector-keys/index.d.ts.map +0 -1
  142. package/dist/ops-connector-keys/index.js +0 -14
  143. package/dist/ops-connector-keys/issue-connector-key.d.ts +0 -76
  144. package/dist/ops-connector-keys/issue-connector-key.d.ts.map +0 -1
  145. package/dist/ops-connector-keys/issue-connector-key.js +0 -84
  146. package/dist/ops-connector-keys/list-connector-keys.d.ts +0 -58
  147. package/dist/ops-connector-keys/list-connector-keys.d.ts.map +0 -1
  148. package/dist/ops-connector-keys/list-connector-keys.js +0 -39
  149. package/dist/ops-connector-keys/revoke-connector-key.d.ts +0 -32
  150. package/dist/ops-connector-keys/revoke-connector-key.d.ts.map +0 -1
  151. package/dist/ops-connector-keys/revoke-connector-key.js +0 -49
  152. package/dist/ops-connector-keys/types.d.ts +0 -91
  153. package/dist/ops-connector-keys/types.d.ts.map +0 -1
  154. package/dist/ops-connector-keys/types.js +0 -24
  155. package/dist/ops-coupon/in-memory-fake.test-util.d.ts +0 -28
  156. package/dist/ops-coupon/in-memory-fake.test-util.d.ts.map +0 -1
  157. package/dist/ops-coupon/in-memory-fake.test-util.js +0 -66
  158. package/dist/ops-coupon/index.d.ts +0 -15
  159. package/dist/ops-coupon/index.d.ts.map +0 -1
  160. package/dist/ops-coupon/index.js +0 -12
  161. package/dist/ops-coupon/redeem-coupon.d.ts +0 -51
  162. package/dist/ops-coupon/redeem-coupon.d.ts.map +0 -1
  163. package/dist/ops-coupon/redeem-coupon.js +0 -61
  164. package/dist/ops-coupon/types.d.ts +0 -59
  165. package/dist/ops-coupon/types.d.ts.map +0 -1
  166. package/dist/ops-coupon/types.js +0 -38
  167. package/dist/ops-orgs/create-org.d.ts +0 -45
  168. package/dist/ops-orgs/create-org.d.ts.map +0 -1
  169. package/dist/ops-orgs/create-org.js +0 -55
  170. package/dist/ops-orgs/get-org-balance.d.ts +0 -29
  171. package/dist/ops-orgs/get-org-balance.d.ts.map +0 -1
  172. package/dist/ops-orgs/get-org-balance.js +0 -45
  173. package/dist/ops-orgs/in-memory-fake.test-util.d.ts +0 -84
  174. package/dist/ops-orgs/in-memory-fake.test-util.d.ts.map +0 -1
  175. package/dist/ops-orgs/in-memory-fake.test-util.js +0 -209
  176. package/dist/ops-orgs/index.d.ts +0 -33
  177. package/dist/ops-orgs/index.d.ts.map +0 -1
  178. package/dist/ops-orgs/index.js +0 -24
  179. package/dist/ops-orgs/invite-to-org.d.ts +0 -56
  180. package/dist/ops-orgs/invite-to-org.d.ts.map +0 -1
  181. package/dist/ops-orgs/invite-to-org.js +0 -72
  182. package/dist/ops-orgs/list-orgs.d.ts +0 -53
  183. package/dist/ops-orgs/list-orgs.d.ts.map +0 -1
  184. package/dist/ops-orgs/list-orgs.js +0 -38
  185. package/dist/ops-orgs/remove-org-member.d.ts +0 -42
  186. package/dist/ops-orgs/remove-org-member.d.ts.map +0 -1
  187. package/dist/ops-orgs/remove-org-member.js +0 -67
  188. package/dist/ops-orgs/rename-org.d.ts +0 -33
  189. package/dist/ops-orgs/rename-org.d.ts.map +0 -1
  190. package/dist/ops-orgs/rename-org.js +0 -57
  191. package/dist/ops-orgs/revoke-invite.d.ts +0 -38
  192. package/dist/ops-orgs/revoke-invite.d.ts.map +0 -1
  193. package/dist/ops-orgs/revoke-invite.js +0 -53
  194. package/dist/ops-orgs/types.d.ts +0 -209
  195. package/dist/ops-orgs/types.d.ts.map +0 -1
  196. package/dist/ops-orgs/types.js +0 -60
@@ -68,27 +68,36 @@ export function createGguiConsumeHandler(deps) {
68
68
  name: 'ggui_consume',
69
69
  title: 'Consume',
70
70
  audience: ['agent'],
71
- description: 'Long-poll for buffered events on a GguiSession. CALL THIS RIGHT AFTER EVERY `ggui_render` THAT RETURNS `nextStep.tool === "ggui_consume"` — that hint is your cue to start listening for the user\'s gesture. Keyed by sessionId (global UUID); app-scoped via ctx.appId. Inline long-poll: `timeout` is an integer in [0, 25] seconds (default 0 — an instant non-blocking drain; values outside reject INVALID_PARAMS — host MCP clients abort longer tool calls; pick 5-15s typical, 25 max). Returns `{events, status}` — each event carries `{type: "action", sessionId, intent, actionData, uiContext, actionId, firedAt}`: `actionData` is WHAT the user did, `uiContext` is the iframe-local snapshot of the contract\'s contextSpec slots AT THE MOMENT they did it. Both inform your reaction without a second round trip. Returns immediately when an action event arrives OR the render completes OR the timeout elapses. On timeout with no event you may re-call once, then end your turn — a later gesture arrives as a new user message. THE LOOP: when `events` is non-empty, REACT, then re-call `ggui_consume` for the next gesture; when `events` is empty, end your turn — a later gesture arrives as a new user message carrying its own consume directive. status:"expired" means the render is gone. IMPORTANT — the iframe state is independent of your backend state: after you mutate via domain tools (todo_toggle, cart_add, etc.), the UI still shows the OLD props until you call `ggui_amend`. If the events caused observable state changes the user is looking at, your reaction MUST include `ggui_amend` somewhere before re-consuming; otherwise the user sees stale props (the #1 wire compliance bug). Pure-info events that don\'t change displayed state can skip it. `ggui_amend` repaints the SAME card in place (the default move in this loop); `ggui_update` instead renders the state as a NEW card and advances the history number — reserve it for milestones worth a card in the transcript. HOSTS WITH PROGRESSIVE TOOL DISCOVERY (claude.ai-style connectors): if a call here errors with "tool not loaded yet" or "wrong parameter names," call `tool_search({query:"ggui_consume"})` once to warm the tool, then retry with the same args. DO NOT skip the consume — silent gesture drops are the worst protocol failure. IDEMPOTENCY: treat the `actionId` on each entry as an idempotency key and apply a side-effecting action AT MOST ONCE per `actionId` — the same id delivered twice is one gesture replayed, never two. This holds for EVERY action, not only those a contract marks `oneShot`: a card served mid-rollout may be running a runtime that suppresses nothing, and keying on the marker would leave a hole exactly there. Two DISTINCT ids are two distinct gestures and both are real.',
71
+ description: "Long-poll for buffered events on a GguiSession. CALL THIS RIGHT AFTER EVERY `ggui_render` THAT RETURNS `nextStep.tool === \"ggui_consume\"` \u2014 that hint is your cue to start listening for the user's gesture. DO NOT skip the consume \u2014 silent gesture drops are the worst protocol failure. `timeout` is an integer in [0, 25] seconds (default 0 \u2014 an instant non-blocking drain; values outside reject INVALID_PARAMS \u2014 hosts abort longer tool calls; pick 5-15s, 25 max). Returns `{events, status}`; each event is `{type: \"action\", sessionId, intent, actionData, uiContext, actionId, firedAt}` \u2014 `actionData` is WHAT the user did, `uiContext` the iframe-local snapshot of the contract's contextSpec slots AT THE MOMENT they did it. Returns immediately when an action event arrives OR the render completes OR the timeout elapses; status:\"expired\" means the render is gone. THE LOOP: when `events` is non-empty, REACT, then re-call `ggui_consume` for the next gesture; when `events` is empty, end your turn (on a timeout with no event you may re-call once first) \u2014 a later gesture arrives as a new user message carrying its own consume directive. IMPORTANT \u2014 the iframe state is independent of your backend state: after you mutate via domain tools (todo_toggle, cart_add, etc.), the UI still shows the OLD props until you call `ggui_amend`. If the events caused observable state changes the user is looking at, your reaction MUST include `ggui_amend` before re-consuming; otherwise the user sees stale props (the #1 wire compliance bug). Pure-info events that don't change displayed state can skip it. `ggui_amend` repaints the SAME card in place (the default move in this loop); `ggui_update` renders the state as a NEW card and advances the history number \u2014 reserve it for milestones worth a card in the transcript. IDEMPOTENCY: treat each entry's `actionId` as an idempotency key \u2014 apply a side-effecting action AT MOST ONCE per `actionId`; the same id delivered twice is one gesture replayed, never two; two DISTINCT ids are two real gestures. This holds for EVERY action, not only those a contract marks `oneShot`: a card served mid-rollout may be running a runtime that suppresses nothing, and keying on the marker would leave a hole exactly there. Both `actionData` and `uiContext` inform your reaction without a second round trip. Keyed by sessionId (global UUID); app-scoped via ctx.appId. HOSTS WITH PROGRESSIVE TOOL DISCOVERY (claude.ai-style connectors): if a call here errors with \"tool not loaded yet\" or \"wrong parameter names,\" call `tool_search({query:\"ggui_consume\"})` once to warm the tool, then retry with the same args. THE RESULT'S `nextStep`: when `events` is non-empty and status is active the result carries `nextStep` = ggui_amend({ sessionId, kind: 'replace', props }) with THIS sessionId filled in \u2014 the same-card repaint is the default move; absent when `events` is empty or the render has expired.",
72
72
  inputSchema,
73
73
  outputSchema,
74
74
  async handler(rawInput, ctx) {
75
75
  const { sessionId, timeout = 0 } = z.object(inputSchema).parse(rawInput);
76
- // Register this long-poll on the active-consumer registry IMMEDIATELY
77
- // — before the app-scope resolution awaits — so a concurrent
78
- // `submit-action.ts` append observes `hasActive: true` for the
79
- // earliest possible window. `exit` is paired in `finally` below so
80
- // every termination path (success, timeout, error, app-scope reject)
81
- // decrements the count exactly once.
76
+ // Resolve render FIRST (ggui#1485). Cross-app (appId) + cross-user +
77
+ // missing all surface uniformly as session_not_found (don't leak
78
+ // whether the id exists for another app / another user), and a
79
+ // refused call must leave no trace on the session it named. Entering
80
+ // the active-consumer registry before this gate let another app's
81
+ // consume count as the session's consumer while its store read ran
82
+ // (a dispatch in that moment reported a consumer present), and its
83
+ // `exit` stamped the session's lastExitAt, which changed the grace
84
+ // wait of that session's dispatches for the next minute.
85
+ const stored = await deps.renderStore.get(sessionId);
86
+ if (!isVisibleToCaller(stored, ctx)) {
87
+ // `stored` is narrowed to StoredGguiSession past this guard.
88
+ throw new GguiSessionNotFoundError(sessionId);
89
+ }
90
+ // Register this long-poll on the active-consumer registry once the
91
+ // caller is admitted, so a concurrent `submit-action.ts` append
92
+ // observes `hasActive: true`. A dispatch landing during the store
93
+ // read above waits its own grace (`waitForConsumer`), which this
94
+ // `enter` wakes; with the default policy that wait is at least
95
+ // 150 ms, far longer than one store read, while a deployment's
96
+ // `consumerGraceMs` sets its own. `exit` is paired in `finally` below
97
+ // so every termination path after admission (success, timeout,
98
+ // error, abort) decrements the count exactly once.
82
99
  deps.activeConsumerRegistry?.enter(sessionId);
83
100
  try {
84
- // Resolve render. Cross-app (appId) + cross-user + missing
85
- // all surface uniformly as session_not_found (don't leak whether
86
- // the id exists for another app / another user).
87
- const stored = await deps.renderStore.get(sessionId);
88
- if (!isVisibleToCaller(stored, ctx)) {
89
- // `stored` is narrowed to StoredGguiSession past this guard.
90
- throw new GguiSessionNotFoundError(sessionId);
91
- }
92
101
  // `timeout` is schema-bounded to [0, 25] (SPEC §7.3) —
93
102
  // out-of-range values rejected INVALID_PARAMS before this line.
94
103
  const deadline = Date.now() + timeout * 1000;
@@ -247,6 +256,14 @@ export function createGguiConsumeHandler(deps) {
247
256
  ...(stored.hostContext !== undefined
248
257
  ? { client: { hostContext: projectHostContext(stored.hostContext) } }
249
258
  : {}),
259
+ // The consume → amend hint (ggui#1399, step 2 — the emit; step 1
260
+ // declared it one release earlier so every host's cached schema
261
+ // names it). Present iff there is something to react to AND the
262
+ // render is still live: an amend on an expired render cannot
263
+ // succeed, so a late drain from one carries no hint.
264
+ ...(events.length > 0 && result.status === 'active'
265
+ ? { nextStep: buildConsumeNextStep(sessionId) }
266
+ : {}),
250
267
  };
251
268
  }
252
269
  finally {
@@ -260,6 +277,24 @@ export function createGguiConsumeHandler(deps) {
260
277
  * immutable seam state (the protocol types it `DeepReadonly`); the wire is
261
278
  * mutable JSON, so the one array is copied and every present field carried.
262
279
  */
280
+ /**
281
+ * The consume → amend hint (ggui#1399): the render → consume chain the
282
+ * agent follows is a hint on the result it just read, and this closes the
283
+ * loop from the drained gesture back to the card. `args.sessionId` is the
284
+ * input's session id verbatim — the literal the agent passes — and
285
+ * `example` carries it in copy-paste form, the same shape as the render
286
+ * hint. The server cannot know whether a gesture changed what the user
287
+ * sees, so the description carries that clause; the decision stays the
288
+ * agent's. Mirrors `gguiConsumeOutputSchema.nextStep` (declared in step 1).
289
+ */
290
+ function buildConsumeNextStep(sessionId) {
291
+ return {
292
+ tool: 'ggui_amend',
293
+ description: "Repaint THIS card in place with whatever the gestures changed — kind: 'replace' with the full props, or kind: 'merge' with a patch; the user keeps looking at the same card. A new render is the exception, for a milestone worth a card in the transcript. Then re-call ggui_consume for the next gesture.",
294
+ example: `ggui_amend({ sessionId: '${sessionId}', kind: 'replace', props: { /* the card's props with the change applied */ } })`,
295
+ args: { sessionId },
296
+ };
297
+ }
263
298
  function projectHostContext(h) {
264
299
  return {
265
300
  ...(h.availableDisplayModes !== undefined ? { availableDisplayModes: [...h.availableDisplayModes] } : {}),
@@ -0,0 +1,17 @@
1
+ /**
2
+ * ggui#1479 — the one line a refused cross-app call leaves: the tool, the
3
+ * session, the caller's app and the session's owning app. Ids only, never
4
+ * the payload. Operator-side: the caller's answer never names the owner.
5
+ * One physical line — a fixed prefix then a JSON body — so a log query
6
+ * matches the event on the prefix and reads its fields from the JSON.
7
+ */
8
+ export declare function logCrossAppRefused(tool: string, sessionId: string, callerAppId: string, ownerAppId: string): void;
9
+ /**
10
+ * ggui#1479 — the line a refusal leaves when a session's ownership could not
11
+ * be read at all (the store threw): the tool, the session, the caller's app
12
+ * and why. The call fails closed; this line is how an outage reads apart
13
+ * from a probe. A caller with no logger of its own passes the store's error
14
+ * text, so the cause rides the same line (ggui#1514).
15
+ */
16
+ export declare function logOwnershipUnverified(tool: string, sessionId: string, callerAppId: string, reason: 'read-failed', error?: string): void;
17
+ //# sourceMappingURL=cross-app-refused.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cross-app-refused.d.ts","sourceRoot":"","sources":["../../src/renders/cross-app-refused.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CAGjH;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,aAAa,EACrB,KAAK,CAAC,EAAE,MAAM,GACb,IAAI,CAIN"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * ggui#1479 — the one line a refused cross-app call leaves: the tool, the
3
+ * session, the caller's app and the session's owning app. Ids only, never
4
+ * the payload. Operator-side: the caller's answer never names the owner.
5
+ * One physical line — a fixed prefix then a JSON body — so a log query
6
+ * matches the event on the prefix and reads its fields from the JSON.
7
+ */
8
+ export function logCrossAppRefused(tool, sessionId, callerAppId, ownerAppId) {
9
+ // eslint-disable-next-line no-console -- operator-visible structured refusal line; handlers carry no logger
10
+ console.warn(`[ggui] runtime_cross_app_refused ${JSON.stringify({ tool, sessionId, callerAppId, ownerAppId })}`);
11
+ }
12
+ /**
13
+ * ggui#1479 — the line a refusal leaves when a session's ownership could not
14
+ * be read at all (the store threw): the tool, the session, the caller's app
15
+ * and why. The call fails closed; this line is how an outage reads apart
16
+ * from a probe. A caller with no logger of its own passes the store's error
17
+ * text, so the cause rides the same line (ggui#1514).
18
+ */
19
+ export function logOwnershipUnverified(tool, sessionId, callerAppId, reason, error) {
20
+ const fields = error === undefined ? { tool, sessionId, callerAppId, reason } : { tool, sessionId, callerAppId, reason, error };
21
+ // eslint-disable-next-line no-console -- operator-visible structured refusal line; handlers carry no logger
22
+ console.warn(`[ggui] runtime_ownership_unverified ${JSON.stringify(fields)}`);
23
+ }
@@ -46,7 +46,8 @@
46
46
  import { type BlueprintSource, type BlueprintVariance, type DataContract } from '@ggui-ai/protocol';
47
47
  import { type LLMCaller } from '@ggui-ai/negotiator';
48
48
  import type { HandlerContext } from '../types.js';
49
- import { type BlueprintMatchResult } from './blueprint-matcher.js';
49
+ import { type BlueprintMatchResult, type RerankPair } from './blueprint-matcher.js';
50
+ import type { RequestFitFacts } from './fits.js';
50
51
  import type { BlueprintRegistryDeps } from './blueprint-registry.js';
51
52
  import type { HandshakeNegotiator, HandshakeNegotiatorResult } from './handshake.js';
52
53
  import type { InstalledBlueprintsProvider } from './installed-blueprints-provider.js';
@@ -94,6 +95,17 @@ export interface BlueprintLookupKeys {
94
95
  readonly variantKey: string;
95
96
  readonly exactKey: string;
96
97
  }
98
+ /**
99
+ * ggui#1568 — a deployment's answer to "which rows may the semantic tier
100
+ * not offer for this request?" See {@link HandshakeDecisionAdapter.semanticExclusion}.
101
+ */
102
+ export type SemanticExclusion = {
103
+ readonly kind: 'rows';
104
+ readonly keys: ReadonlySet<string>;
105
+ } | {
106
+ readonly kind: 'all';
107
+ readonly reason: string;
108
+ };
97
109
  export interface HandshakeDecisionAdapter {
98
110
  /**
99
111
  * Resolve the LLM for this request — the find-similar judge AND the
@@ -104,6 +116,13 @@ export interface HandshakeDecisionAdapter {
104
116
  * fixed caller.
105
117
  */
106
118
  resolveLlm(ctx: HandlerContext): Promise<LLMCaller | undefined> | LLMCaller | undefined;
119
+ /**
120
+ * The rerank judge as a pair with its own threshold (ggui#1235), for a
121
+ * deployment whose judge is not the LLM caller above — passed to the
122
+ * matcher as `rerank`. Absent → the matcher's default (the resolved LLM
123
+ * at its calibrated threshold), unchanged.
124
+ */
125
+ resolveRerank?(ctx: HandlerContext): Promise<RerankPair | undefined> | RerankPair | undefined;
107
126
  /**
108
127
  * Ordered list of blueprint pools to search. Empty / absent ⇒ the
109
128
  * find-similar probe is skipped entirely (synth-only). Searched in
@@ -123,6 +142,33 @@ export interface HandshakeDecisionAdapter {
123
142
  * Resolved once per decision, before the pool loop.
124
143
  */
125
144
  readonly reuseMode?: (ctx: HandlerContext) => Promise<'full' | 'exact-only'> | 'full' | 'exact-only';
145
+ /**
146
+ * ggui#1427 — the request's declared fit facts (the canvas it will mount
147
+ * on, the aesthetic preset and direction digest it is served under), for
148
+ * `fits()` to compare against each candidate's row before the judge ranks
149
+ * it. Resolved once per decision and handed to every pool probe. Absent ⇒
150
+ * nothing declared: only data-shape (from the draft contract) evaluates.
151
+ * The server this package ships declares nothing; a deployment resolves
152
+ * these from wherever it keeps a request's presentation facts.
153
+ */
154
+ resolveRequestFit?(ctx: HandlerContext): Promise<RequestFitFacts | undefined> | RequestFitFacts | undefined;
155
+ /**
156
+ * ggui#1568 — rows the deployment reserves from the semantic tier for this
157
+ * request: rows that must be reached only by their exact key, never by
158
+ * similarity (an A/B test's arm, served only to the traffic assigned to
159
+ * it, is the first such row). Resolved once per decision and applied to
160
+ * every pool probe. Not asked when `reuseMode` is `'exact-only'`, since
161
+ * the semantic tier is already off.
162
+ *
163
+ * `{ kind: 'rows', keys }` excludes each row named by
164
+ * {@link semanticExclusionKey}. `{ kind: 'all', reason }` turns the
165
+ * semantic tier off for this request: the answer when the deployment
166
+ * cannot say which rows are reserved. It must never answer an empty set it
167
+ * has not read. A hook that throws an operational error is read as
168
+ * `'all'` and reported through `warn`. A programmer error (TypeError and
169
+ * kin) is rethrown, as for every hook here. Absent ⇒ nothing is reserved.
170
+ */
171
+ readonly semanticExclusion?: (ctx: HandlerContext) => Promise<SemanticExclusion> | SemanticExclusion;
126
172
  /**
127
173
  * Optional deployment-specific pre-match, run BEFORE the find-similar
128
174
  * probe so a curated / byte-exact hit wins over everything — e.g. a
@@ -1 +1 @@
1
- {"version":3,"file":"decide-handshake.d.ts","sourceRoot":"","sources":["../../src/renders/decide-handshake.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"decide-handshake.d.ts","sourceRoot":"","sources":["../../src/renders/decide-handshake.ts"],"names":[],"mappings":"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"}
@@ -44,7 +44,7 @@
44
44
  * RangeError / SyntaxError) re-throw so real bugs surface.
45
45
  */
46
46
  import { createHash } from 'node:crypto';
47
- import { dataContractSchema, lintContract, summarizeContract, } from '@ggui-ai/protocol';
47
+ import { MATCHED_INTENT_MAX_CHARS, dataContractSchema, lintContract, summarizeContract, } from '@ggui-ai/protocol';
48
48
  import { blueprintKey, variantKey } from '@ggui-ai/protocol/blueprint-key';
49
49
  import { ensureConformingContract, normalizeDraft, } from '@ggui-ai/negotiator';
50
50
  import { matchBlueprint, } from './blueprint-matcher.js';
@@ -53,6 +53,18 @@ import { composeExactKey } from './blueprint-registry.js';
53
53
  import { buildDeclined, buildSalvagedOrDeclined } from './handshake-fallbacks.js';
54
54
  import { emitAgentCaps } from './agentcaps-measurement.js';
55
55
  import { canonicalizeToolIdentity, } from './canonicalize-tool-identity.js';
56
+ /**
57
+ * `blueprintMeta.matchedIntent` (#1336) for a judged hit: the stored intent,
58
+ * cut the way the reuse judge cuts a candidate's intent (llm-rerank.ts), so
59
+ * the agent reads exactly the string the judge compared. Blank → absent.
60
+ */
61
+ function matchedIntentOf(intent) {
62
+ if (intent.trim().length === 0)
63
+ return undefined;
64
+ if (intent.length <= MATCHED_INTENT_MAX_CHARS)
65
+ return intent;
66
+ return `${intent.slice(0, MATCHED_INTENT_MAX_CHARS - 1)}…`;
67
+ }
56
68
  /** Operational vs programmer-error classifier — bugs re-throw, ops fail open. */
57
69
  function isOperationalError(err) {
58
70
  if (err instanceof TypeError)
@@ -76,7 +88,8 @@ function coverageHasGap(gap) {
76
88
  gap.props.length > 0 ||
77
89
  gap.context.length > 0 ||
78
90
  gap.streams.length > 0 ||
79
- gap.gadgets.length > 0);
91
+ gap.gadgets.length > 0 ||
92
+ gap.actionMembers.length > 0);
80
93
  }
81
94
  /**
82
95
  * Project a coverage gap into `severity:'warn'` validation findings — one
@@ -115,6 +128,19 @@ function coverageGapFindings(gap, request) {
115
128
  push('context', 'contextSpec');
116
129
  push('streams', 'streamSpec');
117
130
  push('gadgets', 'gadgets');
131
+ // ggui#1428 — a shared action whose stored entry lacks a load-bearing
132
+ // member the draft declares: the served card will not behave that way.
133
+ for (const entry of gap.actionMembers) {
134
+ const dot = entry.lastIndexOf('.');
135
+ const name = entry.slice(0, dot);
136
+ const member = entry.slice(dot + 1);
137
+ findings.push({
138
+ code: COVERAGE_GAP_CODE,
139
+ severity: 'warn',
140
+ path: `actionSpec.${entry}`,
141
+ message: `the proposed cached UI declares action '${name}' without the \`${member}\` your draft declares, so the served card will not carry that behaviour. Default to ACCEPT (reuse-and-refine) — override only if the user or your flow depends on it.`,
142
+ });
143
+ }
118
144
  return findings;
119
145
  }
120
146
  /** Stable code for the VARIANCE_GAP warn finding on a variance-divergent reuse. */
@@ -379,6 +405,7 @@ export async function decideHandshake(adapter, input) {
379
405
  // judge needs it at handshake time (parity with the render path), and
380
406
  // the synth/repair create path reuses it.
381
407
  const llm = await adapter.resolveLlm(ctx);
408
+ const rerank = adapter.resolveRerank ? await adapter.resolveRerank(ctx) : undefined;
382
409
  // Tier 1 — find-similar across pools (exact-key free + semantic
383
410
  // find+judge). Reuse the cached blueprint ATOMICALLY; a coverage gap is
384
411
  // informational (surfaced as COVERAGE_GAP warn findings, not a drop).
@@ -389,6 +416,31 @@ export async function decideHandshake(adapter, input) {
389
416
  // ggui#607 — resolve the reuse policy once; 'exact-only' threads
390
417
  // disableSemantic into every pool probe below.
391
418
  const reuseMode = adapter.reuseMode ? await adapter.reuseMode(ctx) : 'full';
419
+ // ggui#1568 — the reserved rows, resolved once. An operational failure
420
+ // reads as 'all': the tier stays off rather than offering a reserved row.
421
+ let exclusion;
422
+ if (reuseMode === 'full' && adapter.semanticExclusion) {
423
+ try {
424
+ exclusion = await adapter.semanticExclusion(ctx);
425
+ }
426
+ catch (err) {
427
+ if (!isOperationalError(err))
428
+ throw err;
429
+ exclusion = { kind: 'all', reason: `semanticExclusion threw: ${errMessage(err)}` };
430
+ }
431
+ if (exclusion.kind === 'all') {
432
+ adapter.warn?.(`[decideHandshake] semantic tier off for this request, the reserved rows are unknown: ${exclusion.reason}`);
433
+ }
434
+ }
435
+ const probeOptions = reuseMode === 'exact-only'
436
+ ? { disableSemantic: true }
437
+ : exclusion?.kind === 'all'
438
+ ? { disableSemantic: true, disableSemanticCause: 'exclusion-unavailable' }
439
+ : exclusion?.kind === 'rows'
440
+ ? { excludeFromSemantic: exclusion.keys }
441
+ : undefined;
442
+ // ggui#1427 — the request's fit facts, resolved once for every pool probe.
443
+ const requestFit = adapter.resolveRequestFit ? await adapter.resolveRequestFit(ctx) : undefined;
392
444
  // The requesting agent's declared MCP tools (a set keyed by bare
393
445
  // toolName) — the basis for the reuse fulfillability gate. A cached
394
446
  // blueprint is only proposed for reuse when these SUPERSET the
@@ -396,6 +448,10 @@ export async function decideHandshake(adapter, input) {
396
448
  // satisfiable (see {@link isFulfillable}).
397
449
  const agentCaps = parsedDraft.data.agentCapabilities?.tools;
398
450
  const semanticHits = [];
451
+ // Judged hits from the requesting app's OWN pool — the only ones whose
452
+ // stored intent may reach this agent (`blueprintMeta.matchedIntent`,
453
+ // #1336). A pool shared across apps never contributes one.
454
+ const ownPoolHits = new Set();
399
455
  // The keys every pool probe below reads the index at — computed ONCE from the
400
456
  // same arguments `matchBlueprint` receives (its `expectedKey` is this
401
457
  // `blueprintKey(contract)`; it composes the exact key with the default kind),
@@ -413,6 +469,7 @@ export async function decideHandshake(adapter, input) {
413
469
  const matchDeps = {
414
470
  registry: pool.registry,
415
471
  ...(llm ? { llm } : {}),
472
+ ...(rerank ? { rerank } : {}),
416
473
  ...(pool.installedBlueprints
417
474
  ? { installedBlueprints: pool.installedBlueprints }
418
475
  : {}),
@@ -421,9 +478,8 @@ export async function decideHandshake(adapter, input) {
421
478
  intent,
422
479
  contract: parsedDraft.data,
423
480
  ...(variance !== undefined ? { variance } : {}),
424
- }, ...(reuseMode === 'exact-only'
425
- ? [{ disableSemantic: true }]
426
- : []));
481
+ ...(requestFit !== undefined ? { fit: requestFit } : {}),
482
+ }, ...(probeOptions !== undefined ? [probeOptions] : []));
427
483
  // Isolated in its OWN try/catch, separate from the outer
428
484
  // pool-probe catch below: onBlueprintMatch's contract promises
429
485
  // non-blocking observation, so a throwing observer must never
@@ -460,6 +516,8 @@ export async function decideHandshake(adapter, input) {
460
516
  if (matchResult.strategy === 'semantic' &&
461
517
  isFulfillable(matchResult.blueprint.contract, agentCaps).ok) {
462
518
  semanticHits.push(matchResult);
519
+ if (scope === ctx.appId)
520
+ ownPoolHits.add(matchResult);
463
521
  }
464
522
  }
465
523
  catch (err) {
@@ -482,7 +540,17 @@ export async function decideHandshake(adapter, input) {
482
540
  return aGap ? b : a; // empty-gap wins
483
541
  return (b.judgeConfidence ?? 0) > (a.judgeConfidence ?? 0) ? b : a;
484
542
  });
485
- const reuse = buildCacheReuseResult(best.blueprint, best.reason, best.cosine, 'match-semantic');
543
+ const cacheReuse = buildCacheReuseResult(best.blueprint, best.reason, best.cosine, 'match-semantic');
544
+ const matchedIntent = ownPoolHits.has(best) ? matchedIntentOf(best.blueprint.intent) : undefined;
545
+ const reuse = matchedIntent === undefined
546
+ ? cacheReuse
547
+ : {
548
+ ...cacheReuse,
549
+ suggestion: {
550
+ ...cacheReuse.suggestion,
551
+ blueprintMeta: { ...cacheReuse.suggestion.blueprintMeta, matchedIntent },
552
+ },
553
+ };
486
554
  // A gapped reuse carries COVERAGE_GAP warn findings so the agent sees
487
555
  // what the cached UI lacks before accepting; a variance-divergent
488
556
  // reuse additionally carries a VARIANCE_GAP warn finding so the agent
@@ -1 +1 @@
1
- {"version":3,"file":"declare-tool-catalog.d.ts","sourceRoot":"","sources":["../../src/renders/declare-tool-catalog.ts"],"names":[],"mappings":"AA2DA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,kCAAkC,CAAC;AAKjF;;GAEG;AACH,MAAM,WAAW,0BAA0B;IACzC;;;;;OAKG;IACH,QAAQ,CAAC,YAAY,EAAE,wBAAwB,CAAC;CACjD;AAED,wBAAgB,mCAAmC,CACjD,IAAI,EAAE,0BAA0B;;;;;;;;;;;GAkCjC"}
1
+ {"version":3,"file":"declare-tool-catalog.d.ts","sourceRoot":"","sources":["../../src/renders/declare-tool-catalog.ts"],"names":[],"mappings":"AA8DA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,kCAAkC,CAAC;AAKjF;;GAEG;AACH,MAAM,WAAW,0BAA0B;IACzC;;;;;OAKG;IACH,QAAQ,CAAC,YAAY,EAAE,wBAAwB,CAAC;CACjD;AAED,wBAAgB,mCAAmC,CACjD,IAAI,EAAE,0BAA0B;;;;;;;;;;;GAkCjC"}
@@ -22,34 +22,37 @@
22
22
  *
23
23
  * Why `runtime` (vs `agent`) — ROUTE PLACEMENT:
24
24
  * - `runtime`-tagged handlers ARE routed on the agent endpoint:
25
- * `filterHandlersByAudience(handlers, ["agent", "runtime"])`
26
- * (`@ggui-ai/mcp-server` server.ts:4621), mounted at the universal
27
- * `/mcp` path (server.ts:4640-4641) with `appId` resolved from the
28
- * same auth identity (server.ts:4629-4632). So the runtime library's
25
+ * `filterHandlersByAudience(handlers, DATA_PLANE_AUDIENCES)`
26
+ * (`@ggui-ai/mcp-server` mcp-endpoint-routes.ts), mounted at the
27
+ * universal `/mcp` path with `appId` resolved from the same auth
28
+ * identity. So the runtime library's
29
29
  * call lands on the same route + resolves the same `appId` as the
30
30
  * agent's `ggui_handshake`.
31
31
  * - The `audience` tag controls ROUTE PLACEMENT ONLY. It does NOT
32
32
  * hide the tool from the LLM's `tools/list`, and there is NO
33
- * call-time gate rejecting any caller class (server.ts:4336-4343).
33
+ * call-time gate rejecting any caller class.
34
34
  *
35
35
  * Why `_meta.ui.visibility: ['app']` IS set here — LLM-LIST HIDING:
36
36
  * - LLM-`tools/list` hiding is done by `_meta.ui.visibility: ['app']`,
37
- * NOT by the `runtime` audience tag (types.ts:219-222; the two
38
- * sibling runtime handlers both carry it — submit-action.ts:237,
39
- * sync-context.ts:135). Without the marker this tool would appear on
37
+ * NOT by the `runtime` audience tag (the sibling runtime handlers
38
+ * carry it too — submit-action.ts, sync-context.ts, runtime-pull.ts,
39
+ * refresh-ws-token.ts, runtime-telemetry.ts). Without the marker this
40
+ * tool would appear on
40
41
  * the agent's `tools/list`, and an LLM could call it and poison the
41
42
  * per-app catalog. With it, spec-compliant hosts (claude.ai, Claude
42
43
  * Desktop) filter it out of the MODEL's list.
43
- * - The marker is ADVISORY for direct callers: ggui's own canonical
44
+ * - The marker is ADVISORY to the server on EVERY route: the canonical
44
45
  * `/mcp` dispatch executes a `visibility:['app']` tool for ANY
45
- * credentialed caller — it never rejects. The only server-ENFORCED
46
- * visibility gate (403 `visibility_denied`,
47
- * mcp-apps-inbound.ts:280-285) lives on the SEPARATE `/mcp-apps/
48
- * tools-call` iframe-proxy route, which gates iframe-originated
49
- * calls against a DOWNSTREAM connector's `tools/list` — never the
50
- * canonical `/mcp` route. So Task 4's backend-LIBRARY direct call
51
- * (its own credential, not an iframe relay) is NOT blocked by the
52
- * marker. It is set purely to keep the tool off the LLM's list.
46
+ * credentialed caller and never rejects, and there is no enforced
47
+ * visibility gate anywhere else (the iframe-proxy route that once
48
+ * carried a 403 `visibility_denied` was deleted in the orphan
49
+ * campaign, 34e2c7276). A view-issued and a model-issued call are
50
+ * indistinguishable on the wire, so the door is the HOST's (SPEC
51
+ * §4.7 "Host obligation", ggui#1414; the kit grades it as
52
+ * `M1-model-tool-set`), and a backend-library direct call with its
53
+ * own credential is NOT blocked by the marker. A server-checkable
54
+ * view-origin proof is ggui#1415. The marker is set purely to keep
55
+ * the tool off the LLM's list on conforming hosts.
53
56
  */
54
57
  import { declareToolCatalogInputSchema, declareToolCatalogOutputSchema, } from "@ggui-ai/protocol";
55
58
  import { defineHandler } from "../types.js";
@@ -1,5 +1,5 @@
1
1
  import { type VectorStore } from '@ggui-ai/mcp-server-core';
2
- import { type BlueprintSource, type DataContract, type BlueprintVariance } from '@ggui-ai/protocol';
2
+ import { type BlueprintSource, type DataContract, type BlueprintVariance, type GeneratorBuild } from '@ggui-ai/protocol';
3
3
  export interface ExportableBlueprint {
4
4
  readonly contract: DataContract;
5
5
  readonly componentCode: string;
@@ -19,6 +19,13 @@ export interface ExportableBlueprint {
19
19
  * artifactId fallback — useless prose for semantic matching.
20
20
  */
21
21
  readonly intent?: string;
22
+ /**
23
+ * ggui#1476 — the minting engine's build, when the row carries one, so
24
+ * the export codec (`toPortableBlueprint`) ships the portable
25
+ * blueprint's `build`. Read through the registry's one reader of the
26
+ * key; an unreadable stored value is absent.
27
+ */
28
+ readonly build?: GeneratorBuild;
22
29
  }
23
30
  /**
24
31
  * Enumerate every reusable TEMPLATE blueprint stored under `scope` in a
@@ -1 +1 @@
1
- {"version":3,"file":"export-registry.d.ts","sourceRoot":"","sources":["../../src/renders/export-registry.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,WAAW,EACjB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAEL,KAAK,eAAe,EACpB,KAAK,YAAY,EACjB,KAAK,iBAAiB,EACvB,MAAM,mBAAmB,CAAC;AAE3B,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAC;IAChC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,QAAQ,EAAE,iBAAiB,CAAC;IACrC;;;;;OAKG;IACH,QAAQ,CAAC,MAAM,EAAE,eAAe,CAAC;IACjC;;;;;;OAMG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,+BAA+B,CACnD,WAAW,EAAE,WAAW,EACxB,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,SAAS,mBAAmB,EAAE,CAAC,CA+CzC"}
1
+ {"version":3,"file":"export-registry.d.ts","sourceRoot":"","sources":["../../src/renders/export-registry.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,WAAW,EACjB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAEL,KAAK,eAAe,EACpB,KAAK,YAAY,EACjB,KAAK,iBAAiB,EACtB,KAAK,cAAc,EACpB,MAAM,mBAAmB,CAAC;AAG3B,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAC;IAChC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,QAAQ,EAAE,iBAAiB,CAAC;IACrC;;;;;OAKG;IACH,QAAQ,CAAC,MAAM,EAAE,eAAe,CAAC;IACjC;;;;;;OAMG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB;;;;;OAKG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,cAAc,CAAC;CACjC;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,+BAA+B,CACnD,WAAW,EAAE,WAAW,EACxB,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,SAAS,mBAAmB,EAAE,CAAC,CAiDzC"}
@@ -1,5 +1,6 @@
1
1
  import { isEnumerableVectorStore, } from '@ggui-ai/mcp-server-core';
2
2
  import { flatToBlueprintSource, } from '@ggui-ai/protocol';
3
+ import { readGeneratorBuild } from './blueprint-registry.js';
3
4
  /**
4
5
  * Enumerate every reusable TEMPLATE blueprint stored under `scope` in a
5
6
  * vector store, with its inline component code — the export source for a
@@ -52,6 +53,7 @@ export async function listRegistryBlueprintsForExport(vectorStore, scope) {
52
53
  }
53
54
  }
54
55
  const intentRaw = m['intent'];
56
+ const build = readGeneratorBuild(m, entry.key);
55
57
  out.push({
56
58
  contract,
57
59
  componentCode: codeRaw,
@@ -60,6 +62,7 @@ export async function listRegistryBlueprintsForExport(vectorStore, scope) {
60
62
  ...(typeof intentRaw === 'string' && intentRaw.length > 0
61
63
  ? { intent: intentRaw }
62
64
  : {}),
65
+ ...(build !== undefined ? { build } : {}),
63
66
  });
64
67
  }
65
68
  return out;
@@ -0,0 +1,60 @@
1
+ import type { DataContract, JsonValue } from '@ggui-ai/protocol';
2
+ export type FitKind = 'data-shape' | 'surface' | 'direction';
3
+ /**
4
+ * Which direction a candidate's `directionDigest` hashes: `app` — the
5
+ * direction the app's profile carries; `request` — a direction given with
6
+ * the request itself. A card built for a request-given direction never
7
+ * matches an app-level request by construction; the scope rides the verdict
8
+ * so a direction miss can be read by its cause.
9
+ */
10
+ export type DirectionScope = 'app' | 'request';
11
+ export type FitCheck = 'hit' | 'miss' | 'not-evaluated';
12
+ /** An aesthetic preset reference: the id, and the version when one is named. */
13
+ export interface AestheticPresetRef {
14
+ readonly id: string;
15
+ readonly version?: string | null;
16
+ }
17
+ /** The request's declared fit facts, beside its draft contract. */
18
+ export interface RequestFitFacts {
19
+ /** The canvas class the request will mount on, when the host declares one. */
20
+ readonly canvas?: string;
21
+ readonly aestheticPreset?: AestheticPresetRef;
22
+ readonly directionDigest?: string;
23
+ }
24
+ export interface FitRequest extends RequestFitFacts {
25
+ /** The request's draft contract. Absent = data-shape not evaluated. */
26
+ readonly contract?: DataContract;
27
+ }
28
+ export interface FitCandidate {
29
+ readonly contract?: DataContract;
30
+ /** The canvas classes the cached interface was judged on. */
31
+ readonly judgedCanvases?: readonly string[];
32
+ readonly aestheticPreset?: AestheticPresetRef;
33
+ readonly directionDigest?: string;
34
+ readonly directionScope?: DirectionScope;
35
+ }
36
+ export interface FitVerdict {
37
+ readonly fits: boolean;
38
+ /** The first miss in check order; absent when the candidate fits. */
39
+ readonly miss?: FitKind;
40
+ readonly checks: Readonly<Record<FitKind, FitCheck>>;
41
+ /** The candidate's declared direction scope, present whenever the direction check ran. */
42
+ readonly directionScope?: DirectionScope;
43
+ }
44
+ /** A candidate the pre-filter dropped, named with its first miss. */
45
+ export interface FitDeclined {
46
+ readonly id: string;
47
+ readonly miss: FitKind;
48
+ /** On a direction miss, the candidate's declared scope when it had one. */
49
+ readonly directionScope?: DirectionScope;
50
+ }
51
+ /** sha256 of the direction text trimmed, whitespace-collapsed and lowercased — a stray edit to spacing or case never flips a card. */
52
+ export declare function directionDigest(text: string): string;
53
+ /**
54
+ * JSON-Schema type compatibility, not string equality: `integer` satisfies a
55
+ * `number` spec, type arrays are compatible when they intersect, and an
56
+ * absent type on either side is compatible.
57
+ */
58
+ export declare function jsonSchemaTypesCompatible(candidateType: JsonValue | undefined, requestType: JsonValue | undefined): boolean;
59
+ export declare function fits(candidate: FitCandidate, request: FitRequest): FitVerdict;
60
+ //# sourceMappingURL=fits.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fits.d.ts","sourceRoot":"","sources":["../../src/renders/fits.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAEjE,MAAM,MAAM,OAAO,GAAG,YAAY,GAAG,SAAS,GAAG,WAAW,CAAC;AAC7D;;;;;;GAMG;AACH,MAAM,MAAM,cAAc,GAAG,KAAK,GAAG,SAAS,CAAC;AAC/C,MAAM,MAAM,QAAQ,GAAG,KAAK,GAAG,MAAM,GAAG,eAAe,CAAC;AAExD,gFAAgF;AAChF,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAClC;AAED,mEAAmE;AACnE,MAAM,WAAW,eAAe;IAC9B,8EAA8E;IAC9E,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,eAAe,CAAC,EAAE,kBAAkB,CAAC;IAC9C,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;CACnC;AAED,MAAM,WAAW,UAAW,SAAQ,eAAe;IACjD,uEAAuE;IACvE,QAAQ,CAAC,QAAQ,CAAC,EAAE,YAAY,CAAC;CAClC;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,QAAQ,CAAC,EAAE,YAAY,CAAC;IACjC,6DAA6D;IAC7D,QAAQ,CAAC,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5C,QAAQ,CAAC,eAAe,CAAC,EAAE,kBAAkB,CAAC;IAC9C,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,cAAc,CAAC,EAAE,cAAc,CAAC;CAC1C;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,qEAAqE;IACrE,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC;IACrD,0FAA0F;IAC1F,QAAQ,CAAC,cAAc,CAAC,EAAE,cAAc,CAAC;CAC1C;AAED,qEAAqE;AACrE,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,2EAA2E;IAC3E,QAAQ,CAAC,cAAc,CAAC,EAAE,cAAc,CAAC;CAC1C;AAID,sIAAsI;AACtI,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEpD;AASD;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,aAAa,EAAE,SAAS,GAAG,SAAS,EAAE,WAAW,EAAE,SAAS,GAAG,SAAS,GAAG,OAAO,CAK3H;AAuDD,wBAAgB,IAAI,CAAC,SAAS,EAAE,YAAY,EAAE,OAAO,EAAE,UAAU,GAAG,UAAU,CAY7E"}