@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.
- package/README.md +2 -2
- package/dist/blueprints/search-blueprints.d.ts +5 -5
- package/dist/blueprints/search-blueprints.js +5 -5
- package/dist/credits/index.d.ts +2 -2
- package/dist/credits/index.d.ts.map +1 -1
- package/dist/credits/index.js +1 -1
- package/dist/credits/list-credit-transactions.d.ts +21 -7
- package/dist/credits/list-credit-transactions.d.ts.map +1 -1
- package/dist/credits/list-credit-transactions.js +12 -2
- package/dist/index.d.ts +2 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -9
- package/dist/ops-apps/create-app.d.ts +10 -11
- package/dist/ops-apps/create-app.d.ts.map +1 -1
- package/dist/ops-apps/create-app.js +12 -13
- package/dist/ops-apps/identity.d.ts +9 -8
- package/dist/ops-apps/identity.d.ts.map +1 -1
- package/dist/ops-apps/identity.js +2 -1
- package/dist/ops-apps/index.d.ts +2 -2
- package/dist/ops-apps/index.js +2 -2
- package/dist/ops-apps/list-apps.d.ts +5 -6
- package/dist/ops-apps/list-apps.d.ts.map +1 -1
- package/dist/ops-apps/list-apps.js +6 -7
- package/dist/ops-apps/set-app-theme.d.ts +12 -8
- package/dist/ops-apps/set-app-theme.d.ts.map +1 -1
- package/dist/ops-apps/set-app-theme.js +11 -7
- package/dist/ops-apps/set-default-app.d.ts +1 -2
- package/dist/ops-apps/set-default-app.d.ts.map +1 -1
- package/dist/ops-apps/set-default-app.js +1 -2
- package/dist/ops-apps/types.d.ts +20 -23
- package/dist/ops-apps/types.d.ts.map +1 -1
- package/dist/ops-apps/types.js +4 -6
- package/dist/ops-blueprint/cache-mirror.d.ts +41 -0
- package/dist/ops-blueprint/cache-mirror.d.ts.map +1 -0
- package/dist/ops-blueprint/cache-mirror.js +19 -0
- package/dist/ops-blueprint/delete.d.ts +18 -0
- package/dist/ops-blueprint/delete.d.ts.map +1 -1
- package/dist/ops-blueprint/delete.js +21 -2
- package/dist/ops-blueprint/errors.d.ts +23 -0
- package/dist/ops-blueprint/errors.d.ts.map +1 -1
- package/dist/ops-blueprint/errors.js +29 -0
- package/dist/ops-blueprint/generate.d.ts +1 -3
- package/dist/ops-blueprint/generate.d.ts.map +1 -1
- package/dist/ops-blueprint/generate.js +47 -58
- package/dist/ops-blueprint/index.d.ts +1 -1
- package/dist/ops-blueprint/index.d.ts.map +1 -1
- package/dist/ops-blueprint/index.js +1 -1
- package/dist/ops-blueprint/list.d.ts.map +1 -1
- package/dist/ops-blueprint/list.js +1 -2
- package/dist/ops-blueprint/register.d.ts +83 -6
- package/dist/ops-blueprint/register.d.ts.map +1 -1
- package/dist/ops-blueprint/register.js +261 -158
- package/dist/ops-blueprint/update.d.ts +3 -2
- package/dist/ops-blueprint/update.d.ts.map +1 -1
- package/dist/ops-blueprint/update.js +4 -3
- package/dist/renders/blueprint-coverage.d.ts +23 -4
- package/dist/renders/blueprint-coverage.d.ts.map +1 -1
- package/dist/renders/blueprint-coverage.js +45 -5
- package/dist/renders/blueprint-durability.d.ts.map +1 -1
- package/dist/renders/blueprint-durability.js +36 -21
- package/dist/renders/blueprint-matcher.d.ts +57 -7
- package/dist/renders/blueprint-matcher.d.ts.map +1 -1
- package/dist/renders/blueprint-matcher.js +154 -36
- package/dist/renders/blueprint-registry.d.ts +154 -1
- package/dist/renders/blueprint-registry.d.ts.map +1 -1
- package/dist/renders/blueprint-registry.js +337 -10
- package/dist/renders/build-seed-pool.d.ts.map +1 -1
- package/dist/renders/build-seed-pool.js +6 -0
- package/dist/renders/cache-trace-sink.d.ts +12 -0
- package/dist/renders/cache-trace-sink.d.ts.map +1 -1
- package/dist/renders/cache-trace-sink.js +15 -1
- package/dist/renders/consume.d.ts +3 -2
- package/dist/renders/consume.d.ts.map +1 -1
- package/dist/renders/consume.js +50 -15
- package/dist/renders/cross-app-refused.d.ts +17 -0
- package/dist/renders/cross-app-refused.d.ts.map +1 -0
- package/dist/renders/cross-app-refused.js +23 -0
- package/dist/renders/decide-handshake.d.ts +47 -1
- package/dist/renders/decide-handshake.d.ts.map +1 -1
- package/dist/renders/decide-handshake.js +74 -6
- package/dist/renders/declare-tool-catalog.d.ts.map +1 -1
- package/dist/renders/declare-tool-catalog.js +20 -17
- package/dist/renders/export-registry.d.ts +8 -1
- package/dist/renders/export-registry.d.ts.map +1 -1
- package/dist/renders/export-registry.js +3 -0
- package/dist/renders/fits.d.ts +60 -0
- package/dist/renders/fits.d.ts.map +1 -0
- package/dist/renders/fits.js +114 -0
- package/dist/renders/handshake-generation-inputs.d.ts +89 -0
- package/dist/renders/handshake-generation-inputs.d.ts.map +1 -0
- package/dist/renders/handshake-generation-inputs.js +65 -0
- package/dist/renders/handshake.d.ts +6 -42
- package/dist/renders/handshake.d.ts.map +1 -1
- package/dist/renders/handshake.js +1 -1
- package/dist/renders/index.d.ts +10 -5
- package/dist/renders/index.d.ts.map +1 -1
- package/dist/renders/index.js +10 -3
- package/dist/renders/installed-blueprints-provider.d.ts.map +1 -1
- package/dist/renders/installed-blueprints-provider.js +3 -1
- package/dist/renders/match-precision/pairs.d.ts +6 -5
- package/dist/renders/match-precision/pairs.d.ts.map +1 -1
- package/dist/renders/match-precision/run-probe.js +1 -2
- package/dist/renders/match-precision/topk-probe.js +13 -8
- package/dist/renders/record-committed-one-shot.d.ts +18 -3
- package/dist/renders/record-committed-one-shot.d.ts.map +1 -1
- package/dist/renders/record-committed-one-shot.js +14 -7
- package/dist/renders/refresh-ws-token.d.ts +66 -87
- package/dist/renders/refresh-ws-token.d.ts.map +1 -1
- package/dist/renders/refresh-ws-token.js +93 -89
- package/dist/renders/render-identity.d.ts +16 -5
- package/dist/renders/render-identity.d.ts.map +1 -1
- package/dist/renders/render-identity.js +26 -10
- package/dist/renders/render-read-gate.d.ts +85 -0
- package/dist/renders/render-read-gate.d.ts.map +1 -0
- package/dist/renders/render-read-gate.js +57 -0
- package/dist/renders/render.d.ts +77 -27
- package/dist/renders/render.d.ts.map +1 -1
- package/dist/renders/render.js +269 -95
- package/dist/renders/report-render-failure.d.ts +69 -0
- package/dist/renders/report-render-failure.d.ts.map +1 -0
- package/dist/renders/report-render-failure.js +83 -0
- package/dist/renders/runtime-pull.d.ts.map +1 -1
- package/dist/renders/runtime-pull.js +6 -0
- package/dist/renders/slice-meta-derivation.d.ts +8 -1
- package/dist/renders/slice-meta-derivation.d.ts.map +1 -1
- package/dist/renders/slice-meta-derivation.js +1 -0
- package/dist/renders/submit-action.d.ts +47 -78
- package/dist/renders/submit-action.d.ts.map +1 -1
- package/dist/renders/submit-action.js +312 -84
- package/dist/renders/sync-context.d.ts +3 -1
- package/dist/renders/sync-context.d.ts.map +1 -1
- package/dist/renders/sync-context.js +53 -20
- package/dist/types.d.ts +71 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +32 -0
- package/package.json +8 -20
- package/dist/ops-connector-keys/in-memory-fake.test-util.d.ts +0 -26
- package/dist/ops-connector-keys/in-memory-fake.test-util.d.ts.map +0 -1
- package/dist/ops-connector-keys/in-memory-fake.test-util.js +0 -60
- package/dist/ops-connector-keys/index.d.ts +0 -19
- package/dist/ops-connector-keys/index.d.ts.map +0 -1
- package/dist/ops-connector-keys/index.js +0 -14
- package/dist/ops-connector-keys/issue-connector-key.d.ts +0 -76
- package/dist/ops-connector-keys/issue-connector-key.d.ts.map +0 -1
- package/dist/ops-connector-keys/issue-connector-key.js +0 -84
- package/dist/ops-connector-keys/list-connector-keys.d.ts +0 -58
- package/dist/ops-connector-keys/list-connector-keys.d.ts.map +0 -1
- package/dist/ops-connector-keys/list-connector-keys.js +0 -39
- package/dist/ops-connector-keys/revoke-connector-key.d.ts +0 -32
- package/dist/ops-connector-keys/revoke-connector-key.d.ts.map +0 -1
- package/dist/ops-connector-keys/revoke-connector-key.js +0 -49
- package/dist/ops-connector-keys/types.d.ts +0 -91
- package/dist/ops-connector-keys/types.d.ts.map +0 -1
- package/dist/ops-connector-keys/types.js +0 -24
- package/dist/ops-coupon/in-memory-fake.test-util.d.ts +0 -28
- package/dist/ops-coupon/in-memory-fake.test-util.d.ts.map +0 -1
- package/dist/ops-coupon/in-memory-fake.test-util.js +0 -66
- package/dist/ops-coupon/index.d.ts +0 -15
- package/dist/ops-coupon/index.d.ts.map +0 -1
- package/dist/ops-coupon/index.js +0 -12
- package/dist/ops-coupon/redeem-coupon.d.ts +0 -51
- package/dist/ops-coupon/redeem-coupon.d.ts.map +0 -1
- package/dist/ops-coupon/redeem-coupon.js +0 -61
- package/dist/ops-coupon/types.d.ts +0 -59
- package/dist/ops-coupon/types.d.ts.map +0 -1
- package/dist/ops-coupon/types.js +0 -38
- package/dist/ops-orgs/create-org.d.ts +0 -45
- package/dist/ops-orgs/create-org.d.ts.map +0 -1
- package/dist/ops-orgs/create-org.js +0 -55
- package/dist/ops-orgs/get-org-balance.d.ts +0 -29
- package/dist/ops-orgs/get-org-balance.d.ts.map +0 -1
- package/dist/ops-orgs/get-org-balance.js +0 -45
- package/dist/ops-orgs/in-memory-fake.test-util.d.ts +0 -84
- package/dist/ops-orgs/in-memory-fake.test-util.d.ts.map +0 -1
- package/dist/ops-orgs/in-memory-fake.test-util.js +0 -209
- package/dist/ops-orgs/index.d.ts +0 -33
- package/dist/ops-orgs/index.d.ts.map +0 -1
- package/dist/ops-orgs/index.js +0 -24
- package/dist/ops-orgs/invite-to-org.d.ts +0 -56
- package/dist/ops-orgs/invite-to-org.d.ts.map +0 -1
- package/dist/ops-orgs/invite-to-org.js +0 -72
- package/dist/ops-orgs/list-orgs.d.ts +0 -53
- package/dist/ops-orgs/list-orgs.d.ts.map +0 -1
- package/dist/ops-orgs/list-orgs.js +0 -38
- package/dist/ops-orgs/remove-org-member.d.ts +0 -42
- package/dist/ops-orgs/remove-org-member.d.ts.map +0 -1
- package/dist/ops-orgs/remove-org-member.js +0 -67
- package/dist/ops-orgs/rename-org.d.ts +0 -33
- package/dist/ops-orgs/rename-org.d.ts.map +0 -1
- package/dist/ops-orgs/rename-org.js +0 -57
- package/dist/ops-orgs/revoke-invite.d.ts +0 -38
- package/dist/ops-orgs/revoke-invite.d.ts.map +0 -1
- package/dist/ops-orgs/revoke-invite.js +0 -53
- package/dist/ops-orgs/types.d.ts +0 -209
- package/dist/ops-orgs/types.d.ts.map +0 -1
- package/dist/ops-orgs/types.js +0 -60
package/dist/renders/consume.js
CHANGED
|
@@ -68,27 +68,36 @@ export function createGguiConsumeHandler(deps) {
|
|
|
68
68
|
name: 'ggui_consume',
|
|
69
69
|
title: 'Consume',
|
|
70
70
|
audience: ['agent'],
|
|
71
|
-
description:
|
|
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
|
-
//
|
|
77
|
-
//
|
|
78
|
-
//
|
|
79
|
-
//
|
|
80
|
-
//
|
|
81
|
-
//
|
|
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":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AAGH,OAAO,
|
|
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
|
-
|
|
425
|
-
|
|
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
|
|
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":"
|
|
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,
|
|
26
|
-
* (`@ggui-ai/mcp-server`
|
|
27
|
-
* `/mcp` path
|
|
28
|
-
*
|
|
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
|
|
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 (
|
|
38
|
-
*
|
|
39
|
-
*
|
|
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
|
|
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
|
|
46
|
-
* visibility gate (
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
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,
|
|
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"}
|