@ggui-ai/mcp-server-handlers 0.2.0-alpha.1 → 0.2.0-alpha.4
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/dist/renders/assert-generator.d.ts +12 -0
- package/dist/renders/assert-generator.d.ts.map +1 -1
- package/dist/renders/assert-generator.js +17 -3
- package/dist/renders/handshake.d.ts +17 -5
- package/dist/renders/handshake.d.ts.map +1 -1
- package/dist/renders/handshake.js +91 -38
- package/dist/renders/render.d.ts.map +1 -1
- package/dist/renders/render.js +56 -35
- package/package.json +6 -6
|
@@ -32,4 +32,16 @@
|
|
|
32
32
|
* non-undefined `requested` still fails (no allow-list).
|
|
33
33
|
*/
|
|
34
34
|
export declare function assertGeneratorRegistered(requested: string | undefined, defaultGenerator: string | undefined): void;
|
|
35
|
+
/**
|
|
36
|
+
* Non-throwing predicate form of {@link assertGeneratorRegistered}.
|
|
37
|
+
*
|
|
38
|
+
* Used by the FORGIVING `ggui_handshake` path: an unknown generator slug
|
|
39
|
+
* is DROPPED (the server default is used) + surfaced as a finding,
|
|
40
|
+
* rather than thrown — handshake never hard-fails on a fixable detail.
|
|
41
|
+
* The STRICT `ggui_render` override path keeps the throwing assert,
|
|
42
|
+
* because override commits the agent to its exact draft.
|
|
43
|
+
*
|
|
44
|
+
* `undefined` (use server default) is always registered.
|
|
45
|
+
*/
|
|
46
|
+
export declare function isGeneratorRegistered(requested: string | undefined, defaultGenerator: string | undefined): boolean;
|
|
35
47
|
//# sourceMappingURL=assert-generator.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"assert-generator.d.ts","sourceRoot":"","sources":["../../src/renders/assert-generator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,wBAAgB,yBAAyB,CACvC,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,gBAAgB,EAAE,MAAM,GAAG,SAAS,GACnC,IAAI,
|
|
1
|
+
{"version":3,"file":"assert-generator.d.ts","sourceRoot":"","sources":["../../src/renders/assert-generator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,wBAAgB,yBAAyB,CACvC,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,gBAAgB,EAAE,MAAM,GAAG,SAAS,GACnC,IAAI,CAQN;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,CACnC,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,gBAAgB,EAAE,MAAM,GAAG,SAAS,GACnC,OAAO,CAGT"}
|
|
@@ -32,12 +32,26 @@
|
|
|
32
32
|
* non-undefined `requested` still fails (no allow-list).
|
|
33
33
|
*/
|
|
34
34
|
export function assertGeneratorRegistered(requested, defaultGenerator) {
|
|
35
|
-
if (requested
|
|
36
|
-
return;
|
|
37
|
-
if (requested === defaultGenerator)
|
|
35
|
+
if (isGeneratorRegistered(requested, defaultGenerator))
|
|
38
36
|
return;
|
|
39
37
|
const registered = defaultGenerator
|
|
40
38
|
? `['${defaultGenerator}']`
|
|
41
39
|
: '[] (no generator registered)';
|
|
42
40
|
throw new Error(`unknown_generator: '${requested}' is not registered on this server. Registered generators: ${registered}. Omit \`blueprintDraft.generator\` to use the server default.`);
|
|
43
41
|
}
|
|
42
|
+
/**
|
|
43
|
+
* Non-throwing predicate form of {@link assertGeneratorRegistered}.
|
|
44
|
+
*
|
|
45
|
+
* Used by the FORGIVING `ggui_handshake` path: an unknown generator slug
|
|
46
|
+
* is DROPPED (the server default is used) + surfaced as a finding,
|
|
47
|
+
* rather than thrown — handshake never hard-fails on a fixable detail.
|
|
48
|
+
* The STRICT `ggui_render` override path keeps the throwing assert,
|
|
49
|
+
* because override commits the agent to its exact draft.
|
|
50
|
+
*
|
|
51
|
+
* `undefined` (use server default) is always registered.
|
|
52
|
+
*/
|
|
53
|
+
export function isGeneratorRegistered(requested, defaultGenerator) {
|
|
54
|
+
if (requested === undefined)
|
|
55
|
+
return true;
|
|
56
|
+
return requested === defaultGenerator;
|
|
57
|
+
}
|
|
@@ -10,11 +10,23 @@ import type { CanvasLifecycleEmitter } from './canvas-lifecycle.js';
|
|
|
10
10
|
* variance + optional generator hint). `forceCreate` short-circuits
|
|
11
11
|
* cache search so the paired render always cold-gens.
|
|
12
12
|
*/
|
|
13
|
+
/**
|
|
14
|
+
* The agent's draft as it ENTERS the handshake. `contract` is `unknown`
|
|
15
|
+
* because a FORGIVING handshake accepts a possibly-malformed proposal and
|
|
16
|
+
* lets the negotiator validate / repair it — it is not yet a guaranteed
|
|
17
|
+
* `DataContract`. (The protocol {@link BlueprintDraft} keeps
|
|
18
|
+
* `contract: DataContract` for the STRICT `ggui_render` override path.)
|
|
19
|
+
*/
|
|
20
|
+
export interface DraftInput {
|
|
21
|
+
readonly contract: unknown;
|
|
22
|
+
readonly variance?: BlueprintDraft['variance'];
|
|
23
|
+
readonly generator?: string;
|
|
24
|
+
}
|
|
13
25
|
export interface HandshakeStoredInput {
|
|
14
26
|
/** Concise semantic identity of the UI — drives intent-axis search keying. */
|
|
15
27
|
readonly intent: string;
|
|
16
|
-
/** Agent's draft — contract (
|
|
17
|
-
readonly blueprintDraft:
|
|
28
|
+
/** Agent's draft — contract (untrusted) + optional variance + generator. */
|
|
29
|
+
readonly blueprintDraft: DraftInput;
|
|
18
30
|
/**
|
|
19
31
|
* Skip blueprint search and route straight to validation (and on
|
|
20
32
|
* pass, agent-mode suggestion). Used after an earlier handshake
|
|
@@ -103,8 +115,8 @@ export interface HandshakeNegotiator {
|
|
|
103
115
|
decide(input: {
|
|
104
116
|
/** Agent-authored intent — drives search intent-axis keying. */
|
|
105
117
|
readonly intent: string;
|
|
106
|
-
/** Agent's draft — see {@link
|
|
107
|
-
readonly blueprintDraft:
|
|
118
|
+
/** Agent's draft — untrusted contract (see {@link DraftInput}). */
|
|
119
|
+
readonly blueprintDraft: DraftInput;
|
|
108
120
|
/** Force-skip cache search; route to validation + agent/synth path. */
|
|
109
121
|
readonly forceCreate?: boolean;
|
|
110
122
|
/** Per-app gadget catalog — synth uses to populate gadgets. */
|
|
@@ -285,7 +297,7 @@ declare const inputSchema: {
|
|
|
285
297
|
* judgement (contract validates? cache hits? synth amends?).
|
|
286
298
|
*/
|
|
287
299
|
readonly blueprintDraft: z.ZodObject<{
|
|
288
|
-
contract: z.
|
|
300
|
+
contract: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
289
301
|
variance: z.ZodOptional<z.ZodObject<{
|
|
290
302
|
persona: z.ZodOptional<z.ZodString>;
|
|
291
303
|
aesthetic: z.ZodOptional<z.ZodString>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handshake.d.ts","sourceRoot":"","sources":["../../src/renders/handshake.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"handshake.d.ts","sourceRoot":"","sources":["../../src/renders/handshake.ts"],"names":[],"mappings":"AAsCA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,EAML,KAAK,SAAS,EACd,KAAK,cAAc,EAEnB,KAAK,gBAAgB,EACrB,KAAK,YAAY,EACjB,KAAK,mBAAmB,EAExB,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EACvB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EACV,gBAAgB,EAChB,aAAa,EACb,aAAa,EACb,uBAAuB,EACvB,wBAAwB,EACzB,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACjE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAEpE;;;;;;GAMG;AACH;;;;;;GAMG;AACH,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,QAAQ,CAAC,EAAE,cAAc,CAAC,UAAU,CAAC,CAAC;IAC/C,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,oBAAoB;IACnC,8EAA8E;IAC9E,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,4EAA4E;IAC5E,QAAQ,CAAC,cAAc,EAAE,UAAU,CAAC;IACpC;;;;OAIG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,OAAO,CAAC;CAChC;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,QAAQ,GAAG,OAAO,GAAG,QAAQ,GAAG,SAAS,GAAG,UAAU,CAAC;IACxE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,+EAA+E;IAC/E,QAAQ,CAAC,KAAK,EAAE,oBAAoB,CAAC;IACrC,oBAAoB;IACpB,QAAQ,CAAC,MAAM,EAAE,qBAAqB,CAAC;IACvC,wEAAwE;IACxE,QAAQ,CAAC,UAAU,EAAE,mBAAmB,CAAC;IACzC;;;;;;;;;;;;OAYG;IACH,QAAQ,CAAC,iBAAiB,EAAE,YAAY,CAAC;IACzC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,WAAW,mBAAmB;IAClC,MAAM,CAAC,KAAK,EAAE;QACZ,gEAAgE;QAChE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;QACxB,mEAAmE;QACnE,QAAQ,CAAC,cAAc,EAAE,UAAU,CAAC;QACpC,uEAAuE;QACvE,QAAQ,CAAC,WAAW,CAAC,EAAE,OAAO,CAAC;QAC/B,+DAA+D;QAC/D,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,gBAAgB,EAAE,CAAC;QAC/C,QAAQ,CAAC,GAAG,EAAE,cAAc,CAAC;KAC9B,GAAG,OAAO,CAAC,yBAAyB,CAAC,GAAG,yBAAyB,CAAC;IAEnE;;;;;;;;;;;;;;;;;;;;;;;;;OAyBG;IACH,aAAa,CAAC,CAAC,KAAK,EAAE;QACpB,gFAAgF;QAChF,QAAQ,CAAC,UAAU,EAAE,SAAS,SAAS,EAAE,CAAC;QAC1C,6DAA6D;QAC7D,QAAQ,CAAC,OAAO,EAAE,uBAAuB,CAAC;QAC1C,QAAQ,CAAC,GAAG,EAAE,cAAc,CAAC;KAC9B,GAAG,OAAO,CAAC,wBAAwB,CAAC,CAAC;CACvC;AAED,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,MAAM,EAAE,QAAQ,GAAG,OAAO,GAAG,QAAQ,GAAG,SAAS,GAAG,UAAU,CAAC;IACxE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,UAAU,EAAE,mBAAmB,CAAC;IACzC;;;OAGG;IACH,QAAQ,CAAC,iBAAiB,EAAE,YAAY,CAAC;IACzC,oBAAoB;IACpB,QAAQ,CAAC,MAAM,CAAC,EAAE,qBAAqB,CAAC;IACxC,6DAA6D;IAC7D,QAAQ,CAAC,YAAY,CAAC,EAAE,SAAS,SAAS,EAAE,CAAC;CAC9C;AAED,MAAM,WAAW,wBAAwB;IACvC;;;;;OAKG;IACH,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC;IAChC;;;;;OAKG;IACH,QAAQ,CAAC,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IAC7C;;;;OAIG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,mBAAmB,CAAC;IAC1C;;;OAGG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B;;;OAGG;IACH,QAAQ,CAAC,mBAAmB,CAAC,EAAE,MAAM,MAAM,CAAC;IAC5C;;;;OAIG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,MAAM,CAAC;IACxC;;;OAGG;IACH,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IAC5B;;;OAGG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB;;;;OAIG;IACH,QAAQ,CAAC,kBAAkB,CAAC,EAAE,MAAM,kBAAkB,GAAG,SAAS,CAAC;IACnE;;;;OAIG;IACH,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IACnC;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,sBAAsB,CAAC;IAClD;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,aAAa,CAAC;CACxC;AAED,0EAA0E;AAC1E,eAAO,MAAM,wBAAwB,MAAM,CAAC;AAE5C,wEAAwE;AACxE,eAAO,MAAM,sBAAsB,6BAA6B,CAAC;AAEjE;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,MAAM,GAClB,MAAM,CAER;AAmBD;;;GAGG;AACH,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,aAAa,EACtB,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAGjC;AAED;;;GAGG;AACH,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,aAAa,EACtB,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAGjC;AAED,qEAAqE;AACrE,QAAA,MAAM,WAAW;;IAEf;;;;OAIG;;;;;;;;;;;;CA8BK,CAAC;AAEX,sEAAsE;AACtE,QAAA,MAAM,YAAY;;;;;;;;;;;;;;CAUR,CAAC;AAEX,UAAU,eAAe;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,QAAQ,GAAG,OAAO,GAAG,QAAQ,GAAG,SAAS,GAAG,UAAU,CAAC;IAC/D;;;;OAIG;IACH,MAAM,EAAE,MAAM,CAAC;IACf,8DAA8D;IAC9D,MAAM,EAAE,qBAAqB,CAAC;IAC9B,UAAU,EAAE,mBAAmB,CAAC;IAChC,0CAA0C;IAC1C,YAAY,CAAC,EAAE,SAAS,SAAS,EAAE,CAAC;IACpC,gDAAgD;IAChD,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE;QACT,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;QAC7B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;KAC1B,CAAC;IACF,yEAAyE;IACzE,kBAAkB,CAAC,EAAE,kBAAkB,CAAC;CACzC;AAED;;;GAGG;AACH,wBAAgB,0BAA0B,CACxC,IAAI,EAAE,wBAAwB,GAC7B,aAAa,CAAC,OAAO,WAAW,EAAE,OAAO,YAAY,EAAE,eAAe,CAAC,CAyLzE;AA8JD;;;GAGG;AACH,qBAAa,sBAAuB,SAAQ,KAAK;aAEnB,WAAW,EAAE,MAAM;IAD/C,QAAQ,CAAC,IAAI,EAAG,qBAAqB,CAAU;gBACnB,WAAW,EAAE,MAAM;CAMhD;AAGD,YAAY,EAAE,iBAAiB,EAAE,CAAC;AAElC;;;GAGG;AACH,eAAO,MAAM,uBAAuB,sBAAsB,CAAC;AAE3D;;GAEG;AACH,MAAM,WAAW,0BAA0B;IACzC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,QAAQ,GAAG,OAAO,GAAG,QAAQ,GAAG,SAAS,GAAG,UAAU,CAAC;IACxE,QAAQ,CAAC,MAAM,EAAE,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC;IAC7C,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;IACrC,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,mBAAmB,CAAC,EAAE,MAAM,CAAC;IACtC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED;;;GAGG;AACH,wBAAgB,0BAA0B,CACxC,MAAM,EAAE,MAAM,GAAG,SAAS,GACzB,MAAM,GAAG,SAAS,CAOpB"}
|
|
@@ -14,12 +14,15 @@
|
|
|
14
14
|
* 3. Delegates suggestion production to the bound
|
|
15
15
|
* {@link HandshakeNegotiator} — which produces a
|
|
16
16
|
* {@link HandshakeSuggestion} routed by `origin: cache | agent | synth`.
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
17
|
+
* FORGIVING posture: the input draft is NOT validated/thrown here.
|
|
18
|
+
* The negotiator owns validity — it cache-matches a registered
|
|
19
|
+
* blueprint (origin: cache) OR runs `ensureConformingContract` on
|
|
20
|
+
* the agent's draft (origin: agent when already clean, synth when
|
|
21
|
+
* the bounded repair loop had to fix it), and ALWAYS returns a
|
|
22
|
+
* contract that passes the deterministic `validateContract` gate.
|
|
21
23
|
* Absent negotiator → the seam stamps an `origin: 'agent'`
|
|
22
|
-
* suggestion using the agent's draft verbatim (no
|
|
24
|
+
* suggestion using the agent's draft verbatim (no repair; the
|
|
25
|
+
* backstop below validates it and a malformed draft fails closed).
|
|
23
26
|
* 4. Persists a {@link HandshakeRecord} under a TTL-bounded
|
|
24
27
|
* {@link KeyValueStore} key. Single-use: the paired `ggui_render`
|
|
25
28
|
* consumes it via `getAndDelete`.
|
|
@@ -34,10 +37,9 @@
|
|
|
34
37
|
*/
|
|
35
38
|
import { randomUUID } from 'node:crypto';
|
|
36
39
|
import { z } from 'zod';
|
|
37
|
-
import {
|
|
38
|
-
import { assertGeneratorRegistered } from './assert-generator.js';
|
|
40
|
+
import { isGeneratorRegistered } from './assert-generator.js';
|
|
39
41
|
import { blueprintKey } from '@ggui-ai/protocol/blueprint-key';
|
|
40
|
-
import { STDLIB_GADGETS,
|
|
42
|
+
import { STDLIB_GADGETS, validateContract, lintContract, dataContractSchema, handshakeSuggestionSchema, } from '@ggui-ai/protocol';
|
|
41
43
|
/** Default TTL (seconds). 10 minutes — same as the KV-store docstring. */
|
|
42
44
|
export const HANDSHAKE_RECORD_TTL_SEC = 600;
|
|
43
45
|
/** Default generator slug — matches the `GeneratorRegistry` default. */
|
|
@@ -94,7 +96,13 @@ const inputSchema = {
|
|
|
94
96
|
*/
|
|
95
97
|
blueprintDraft: z
|
|
96
98
|
.object({
|
|
97
|
-
|
|
99
|
+
// Loose ON PURPOSE: a FORGIVING handshake accepts a possibly-
|
|
100
|
+
// malformed proposal (any JSON object) and lets the negotiator
|
|
101
|
+
// validate + repair it (ensureConformingContract). Strict
|
|
102
|
+
// `dataContractSchema` here would hard-fail the two most common
|
|
103
|
+
// malformations (wrapper-nesting, type-spelling) at the Zod layer
|
|
104
|
+
// BEFORE the negotiator runs — reviving the handshake-retry loop.
|
|
105
|
+
contract: z.record(z.string(), z.unknown()),
|
|
98
106
|
variance: z
|
|
99
107
|
.object({
|
|
100
108
|
persona: z.string().optional(),
|
|
@@ -142,21 +150,35 @@ export function createGguiHandshakeHandler(deps) {
|
|
|
142
150
|
title: 'Handshake',
|
|
143
151
|
audience: ['agent'],
|
|
144
152
|
description: deps.description ??
|
|
145
|
-
"Negotiate a contract for a UI you want to deliver. Call BEFORE ggui_render. Input: {intent, blueprintDraft: {contract, variance?, generator?}}. CONTRACT SHAPE (DataContract) — every entry under propsSpec.properties / actionSpec / streamSpec / contextSpec is a WRAPPER that contains a JSON Schema in its `schema:` field; the JSON Schema does NOT sit flat at the entry level. ActionEntry uses OPTIONAL `nextStep: '<toolName>'` to hint the agent's intended next tool call — when present, the tool MUST also be declared in `agentCapabilities.tools`; OMIT it entirely when the agent should decide freely.
|
|
153
|
+
"Negotiate a contract for a UI you want to deliver. Call BEFORE ggui_render. Input: {intent, blueprintDraft: {contract, variance?, generator?}}. CONTRACT SHAPE (DataContract) — every entry under propsSpec.properties / actionSpec / streamSpec / contextSpec is a WRAPPER that contains a JSON Schema in its `schema:` field; the JSON Schema does NOT sit flat at the entry level. ActionEntry uses OPTIONAL `nextStep: '<toolName>'` to hint the agent's intended next tool call — when present, the tool MUST also be declared in `agentCapabilities.tools`; OMIT it entirely when the agent should decide freely. FORGIVING: handshake ALWAYS returns a conforming contract — if your draft is malformed the server repairs it (handshake never hard-fails on contract content). The `suggestion` carries origin = cache (matched an existing blueprint) | agent (your draft was already clean) | synth (server repaired your draft; what was wrong is listed in suggestion.validationFindings). A synth result is READY — accept it; do NOT re-call ggui_handshake in a loop hoping for a different origin. On the paired ggui_render you send `decision: {kind: 'accept'}` (use the suggestion verbatim — the normal path) or `decision: {kind: 'override', blueprintDraft}` (commit to a NEW draft of your own; STRICT — it must already conform, the server will not repair an override). Then ggui_consume → react → repeat. PLACEMENT RULE: actionSpec = events that drive the agent's next turn; contextSpec = observable state. Test: needs next-turn reasoning? actionSpec. No? contextSpec.",
|
|
146
154
|
inputSchema,
|
|
147
155
|
outputSchema,
|
|
148
156
|
async handler(input, ctx) {
|
|
149
157
|
const parsed = z.object(inputSchema).parse(input);
|
|
150
|
-
|
|
151
|
-
//
|
|
152
|
-
//
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
158
|
+
let normalizedInput = normalizeInput(parsed);
|
|
159
|
+
// Forgiving generator: an UNKNOWN generator slug is DROPPED (the
|
|
160
|
+
// server default is used) + surfaced as a finding, rather than
|
|
161
|
+
// thrown. Handshake never hard-fails on a fixable detail; the
|
|
162
|
+
// STRICT render-override path keeps the throwing assert.
|
|
163
|
+
const generatorFindings = [];
|
|
164
|
+
if (!isGeneratorRegistered(normalizedInput.blueprintDraft.generator, defaultGenerator)) {
|
|
165
|
+
generatorFindings.push({
|
|
166
|
+
code: 'GENERATOR_UNKNOWN',
|
|
167
|
+
severity: 'warn',
|
|
168
|
+
path: 'blueprintDraft.generator',
|
|
169
|
+
message: `generator '${normalizedInput.blueprintDraft.generator}' is not registered on this server; using the default '${defaultGenerator}'. Omit blueprintDraft.generator to silence.`,
|
|
170
|
+
});
|
|
171
|
+
const { generator: _droppedGenerator, ...draftWithoutGenerator } = normalizedInput.blueprintDraft;
|
|
172
|
+
normalizedInput = {
|
|
173
|
+
...normalizedInput,
|
|
174
|
+
blueprintDraft: draftWithoutGenerator,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
// The agent's draft is NOT validated/thrown here. The negotiator
|
|
178
|
+
// owns validity — it cache-matches OR repairs the draft
|
|
179
|
+
// (ensureConformingContract) and returns a contract that passes the
|
|
180
|
+
// deterministic gate. This is the forgiving-handshake posture: a
|
|
181
|
+
// malformed draft is a TRIGGER into repair, not a thrown error.
|
|
160
182
|
// Per-app gadget catalog.
|
|
161
183
|
const gadgets = deps.appMetadataStore
|
|
162
184
|
? ((await deps.appMetadataStore.get(ctx.appId))?.gadgets ??
|
|
@@ -176,16 +198,27 @@ export function createGguiHandshakeHandler(deps) {
|
|
|
176
198
|
ctx,
|
|
177
199
|
})
|
|
178
200
|
: buildDefaultAgentSuggestion(normalizedInput.blueprintDraft, mintBlueprintId, defaultGenerator);
|
|
179
|
-
//
|
|
180
|
-
//
|
|
181
|
-
//
|
|
182
|
-
//
|
|
183
|
-
//
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
201
|
+
// Backstop: the negotiator's effectiveContract MUST pass the
|
|
202
|
+
// single deterministic gate. For a bound negotiator this is
|
|
203
|
+
// GUARANTEED (ensureConformingContract loops until validateContract
|
|
204
|
+
// is green), so a throw here means the negotiator returned a
|
|
205
|
+
// non-conforming contract — a negotiator bug surfaced loudly rather
|
|
206
|
+
// than shipped downstream. The no-negotiator default path also
|
|
207
|
+
// lands here: an invalid draft with nothing bound to repair it
|
|
208
|
+
// fails closed with the deterministic findings (bind a negotiator
|
|
209
|
+
// to get the forgiving repair path).
|
|
210
|
+
validateContract(negotiated.effectiveContract);
|
|
211
|
+
// Merge handshake-level findings (e.g. a dropped generator) into
|
|
212
|
+
// the negotiator's suggestion so the agent sees every adjustment.
|
|
213
|
+
const finalSuggestion = generatorFindings.length > 0
|
|
214
|
+
? {
|
|
215
|
+
...negotiated.suggestion,
|
|
216
|
+
validationFindings: [
|
|
217
|
+
...(negotiated.suggestion.validationFindings ?? []),
|
|
218
|
+
...generatorFindings,
|
|
219
|
+
],
|
|
220
|
+
}
|
|
221
|
+
: negotiated.suggestion;
|
|
189
222
|
const handshakeId = mintHandshakeId();
|
|
190
223
|
// Emit handshake_started so the canvas animator transitions to
|
|
191
224
|
// its `handshake` state. Fire-and-forget; absent emitter is a
|
|
@@ -197,14 +230,17 @@ export function createGguiHandshakeHandler(deps) {
|
|
|
197
230
|
});
|
|
198
231
|
const target = negotiated.target ?? {};
|
|
199
232
|
// Canonical hash of the AGENT'S DRAFT contract (pre-amendment).
|
|
200
|
-
|
|
233
|
+
// Draft is untrusted (may be malformed) — hash only when it parses;
|
|
234
|
+
// blueprintKey tolerates `undefined`. Telemetry-only.
|
|
235
|
+
const draftHash = blueprintKey(dataContractSchema.safeParse(normalizedInput.blueprintDraft.contract)
|
|
236
|
+
.data);
|
|
201
237
|
const record = {
|
|
202
238
|
handshakeId,
|
|
203
239
|
action: negotiated.action,
|
|
204
240
|
reason: negotiated.reason,
|
|
205
241
|
input: normalizedInput,
|
|
206
242
|
target,
|
|
207
|
-
suggestion:
|
|
243
|
+
suggestion: finalSuggestion,
|
|
208
244
|
effectiveContract: negotiated.effectiveContract,
|
|
209
245
|
appId: ctx.appId,
|
|
210
246
|
createdAt: nowIso(),
|
|
@@ -254,13 +290,27 @@ export function createGguiHandshakeHandler(deps) {
|
|
|
254
290
|
};
|
|
255
291
|
}
|
|
256
292
|
/**
|
|
257
|
-
* Default `origin: 'agent'` suggestion when no negotiator is bound
|
|
258
|
-
*
|
|
259
|
-
*
|
|
260
|
-
*
|
|
293
|
+
* Default `origin: 'agent'` suggestion when no negotiator is bound (OSS
|
|
294
|
+
* zero-config). No negotiator ⇒ no LLM ⇒ nothing can REPAIR a malformed
|
|
295
|
+
* draft. The handshake backstop (validateContract) would throw on one,
|
|
296
|
+
* so this still honors "handshake never hard-fails": a clean draft is
|
|
297
|
+
* used verbatim; a malformed draft is deterministically replaced by the
|
|
298
|
+
* trivially-conforming empty contract + loud findings (bind a
|
|
299
|
+
* HandshakeNegotiator to get the forgiving repair path instead).
|
|
261
300
|
*/
|
|
262
301
|
function buildDefaultAgentSuggestion(blueprintDraft, mintBlueprintId, defaultGenerator) {
|
|
263
|
-
const
|
|
302
|
+
const lint = lintContract(blueprintDraft.contract);
|
|
303
|
+
const clean = lint.errors.length === 0;
|
|
304
|
+
// `clean` ⇒ shape phase passed ⇒ strict parse cannot throw.
|
|
305
|
+
const contract = clean
|
|
306
|
+
? dataContractSchema.parse(blueprintDraft.contract)
|
|
307
|
+
: {};
|
|
308
|
+
const findings = lint.errors.map((e) => ({
|
|
309
|
+
code: e.code,
|
|
310
|
+
severity: 'error',
|
|
311
|
+
path: e.path,
|
|
312
|
+
message: e.message,
|
|
313
|
+
}));
|
|
264
314
|
const generator = blueprintDraft.generator ?? defaultGenerator;
|
|
265
315
|
const blueprintMeta = {
|
|
266
316
|
blueprintId: mintBlueprintId(),
|
|
@@ -283,8 +333,11 @@ function buildDefaultAgentSuggestion(blueprintDraft, mintBlueprintId, defaultGen
|
|
|
283
333
|
};
|
|
284
334
|
const suggestion = {
|
|
285
335
|
origin: 'agent',
|
|
286
|
-
rationale:
|
|
336
|
+
rationale: clean
|
|
337
|
+
? 'no-negotiator-bound: OSS default routes the draft as origin=agent (no search, no repair). Bind a HandshakeNegotiator to enable cache/synth routing.'
|
|
338
|
+
: 'no-negotiator-bound: draft failed validation and no negotiator (LLM) is bound to repair it — returning a minimal conforming contract. Bind a HandshakeNegotiator to enable repair.',
|
|
287
339
|
blueprintMeta,
|
|
340
|
+
...(findings.length > 0 ? { validationFindings: findings } : {}),
|
|
288
341
|
};
|
|
289
342
|
return {
|
|
290
343
|
action: 'create',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../src/renders/render.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AAGH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAGL,KAAK,YAAY,EAEjB,KAAK,MAAM,EAGZ,MAAM,mBAAmB,CAAC;AAM3B,OAAO,KAAK,EACV,gBAAgB,EAChB,iBAAiB,EACjB,aAAa,EACb,YAAY,EACZ,oBAAoB,EACpB,cAAc,EACd,WAAW,EACX,WAAW,EACX,cAAc,EAEd,WAAW,EACZ,MAAM,0BAA0B,CAAC;AAElC,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAOjE,OAAO,EAIL,KAAK,sBAAsB,EAC5B,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EACV,mBAAmB,EAEpB,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../src/renders/render.ts"],"names":[],"mappings":"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"}
|
package/dist/renders/render.js
CHANGED
|
@@ -50,7 +50,6 @@ import { GGUI_RENDER_UI_META, toMcpAppEnvelope, } from '@ggui-ai/protocol/integr
|
|
|
50
50
|
import { RateLimitedError } from '@ggui-ai/mcp-server-core';
|
|
51
51
|
import { consumeHandshakeRecord, peekHandshakeRecord, HandshakeNotFoundError, } from './handshake.js';
|
|
52
52
|
import { evaluateProvisionalPreviewGate, finalizeProvisionalPreview, kickoffProvisionalPreview, } from './provisional-preview.js';
|
|
53
|
-
import { assertContractNoRetiredFields } from './assert-contract-no-retired-fields.js';
|
|
54
53
|
import { assertGeneratorRegistered } from './assert-generator.js';
|
|
55
54
|
import { assertNoDuplicateGadgetHooks } from './assert-no-duplicate-gadget-hooks.js';
|
|
56
55
|
import { matchBlueprint } from './blueprint-matcher.js';
|
|
@@ -59,7 +58,7 @@ import { assertGadgetsRegistered, filterDescriptorsToContract, } from './assert-
|
|
|
59
58
|
import { fetchGadgetTypes } from './fetch-gadget-types.js';
|
|
60
59
|
import { assertPublicEnvSatisfied } from './assert-public-env.js';
|
|
61
60
|
import { blueprintKey } from '@ggui-ai/protocol/blueprint-key';
|
|
62
|
-
import { validatePropsData, ContractViolationError,
|
|
61
|
+
import { validatePropsData, ContractViolationError, validateContract, dataContractSchema, STDLIB_GADGETS, renderOutputSchema, } from '@ggui-ai/protocol';
|
|
63
62
|
import { emitCacheTraceEvent, newCacheTraceId, truncateCacheTraceIntent, } from './cache-trace-sink.js';
|
|
64
63
|
import { emitPayloadTraceEvent } from './payload-trace-sink.js';
|
|
65
64
|
import { deriveRenderMeta, derivePublicEnvProjection, deriveContractBundle, } from './slice-meta-derivation.js';
|
|
@@ -365,21 +364,30 @@ export function createGguiRenderHandler(deps) {
|
|
|
365
364
|
throw new RateLimitedError(`ggui_render:${ctx.appId}`, decision);
|
|
366
365
|
}
|
|
367
366
|
}
|
|
368
|
-
//
|
|
369
|
-
//
|
|
370
|
-
//
|
|
371
|
-
//
|
|
372
|
-
|
|
373
|
-
//
|
|
374
|
-
// (
|
|
375
|
-
//
|
|
367
|
+
// Single deterministic contract gate — the SAME `validateContract`
|
|
368
|
+
// the handshake backstop runs (retired fields, inner-schema
|
|
369
|
+
// validity, cross-references, name invariants, schema-compat). On
|
|
370
|
+
// the ACCEPT path this re-checks an already-validated contract
|
|
371
|
+
// (defense-in-depth; never fires). On the OVERRIDE path it is the
|
|
372
|
+
// STRICT commit gate: a forced contract MUST conform — the server
|
|
373
|
+
// does not repair it ("use mine verbatim"). A failure is rethrown
|
|
374
|
+
// with a pointer back to ggui_handshake (which DOES repair), so the
|
|
375
|
+
// agent recovers instead of looping on override.
|
|
376
|
+
try {
|
|
377
|
+
validateContract(story.contract);
|
|
378
|
+
}
|
|
379
|
+
catch (err) {
|
|
380
|
+
if (acceptanceClassification === 'override') {
|
|
381
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
382
|
+
throw new Error(`override_contract_invalid: the forced contract failed validation — ${detail} Override COMMITS you to your exact contract; the server does not repair it. To get an auto-repaired or cache-matched contract, call ggui_handshake({intent, blueprintDraft}) and send decision:{kind:'accept'} — do NOT retry override with the same draft.`);
|
|
383
|
+
}
|
|
384
|
+
throw err;
|
|
385
|
+
}
|
|
386
|
+
// Duplicate-gadget-hook gate (gadget-specific; not part of the
|
|
387
|
+
// contract gate). Two bindings with the same (package, hook)
|
|
388
|
+
// double-mount the wrapper; hard reject so the violation is
|
|
376
389
|
// observable rather than silently tolerated.
|
|
377
390
|
assertNoDuplicateGadgetHooks(story.contract);
|
|
378
|
-
assertContractSchemasValid(story.contract);
|
|
379
|
-
// Cross-reference invariants — every `actionSpec[*].nextStep` and
|
|
380
|
-
// `streamSpec[*].source.tool` MUST resolve to a key in the
|
|
381
|
-
// contract's own `agentCapabilities.tools` catalog.
|
|
382
|
-
assertCrossReferences(story.contract);
|
|
383
391
|
// Gadget registry gate + enrichment. First: every
|
|
384
392
|
// `(package, export name)` the contract references on
|
|
385
393
|
// `clientCapabilities.gadgets` MUST resolve in `App.gadgets`.
|
|
@@ -394,13 +402,8 @@ export function createGguiRenderHandler(deps) {
|
|
|
394
402
|
assertPublicEnvSatisfied(story.contract, appGadgets, appRecord?.publicEnv);
|
|
395
403
|
resolvedAppLibraries = filterDescriptorsToContract(story.contract, appGadgets);
|
|
396
404
|
}
|
|
397
|
-
// Name-invariant
|
|
398
|
-
//
|
|
399
|
-
// and no `_ggui:` reserved-prefix keys on actionSpec / contextSpec.
|
|
400
|
-
assertNameInvariants(story.contract);
|
|
401
|
-
// Protocol-level schema-compat invariant against the contract's
|
|
402
|
-
// OWN catalog.
|
|
403
|
-
assertSchemaCompat(story.contract);
|
|
405
|
+
// (Name-invariant + schema-compat invariants are covered by the
|
|
406
|
+
// single `validateContract` gate above — no separate asserts here.)
|
|
404
407
|
// Schema-compat validation against the AUTHORED contract via the
|
|
405
408
|
// server's registered tool registry. Defensive backstops at gen
|
|
406
409
|
// and cache-hit commit phases (see `runGenerationIntoRender` +
|
|
@@ -420,7 +423,7 @@ export function createGguiRenderHandler(deps) {
|
|
|
420
423
|
});
|
|
421
424
|
}
|
|
422
425
|
// Props validation against the agreed contract's propsSpec.
|
|
423
|
-
|
|
426
|
+
let runtimeProps = parsed.props;
|
|
424
427
|
if (effectiveContract.propsSpec) {
|
|
425
428
|
const propsToValidate = (runtimeProps ?? {});
|
|
426
429
|
const propsValidation = validatePropsData(propsToValidate, effectiveContract.propsSpec);
|
|
@@ -434,18 +437,36 @@ export function createGguiRenderHandler(deps) {
|
|
|
434
437
|
}
|
|
435
438
|
else if (runtimeProps !== undefined &&
|
|
436
439
|
Object.keys(runtimeProps).length > 0) {
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
440
|
+
if (acceptanceClassification === 'accept') {
|
|
441
|
+
// Forgiving ACCEPT: the negotiator may have RESHAPED the
|
|
442
|
+
// contract (e.g. synth moves a mutable collection like `todos`
|
|
443
|
+
// from propsSpec → contextSpec), so the agent's accept-path
|
|
444
|
+
// props — authored against its ORIGINAL draft — no longer fit.
|
|
445
|
+
// The agreed contract declares no propsSpec, so the props are
|
|
446
|
+
// unusable; DROP them (the UI starts from contextSpec defaults)
|
|
447
|
+
// rather than hard-failing. The agent populates live state via
|
|
448
|
+
// ggui_update after render. Override stays STRICT (below).
|
|
449
|
+
const droppedKeys = Object.keys(runtimeProps).join(', ');
|
|
450
|
+
// eslint-disable-next-line no-console -- operator-visible signal
|
|
451
|
+
console.warn(`[ggui_render] accept-path props dropped — the agreed contract declares no propsSpec ` +
|
|
452
|
+
`(synth likely reshaped propsSpec → contextSpec). Dropped keys: ${droppedKeys}. ` +
|
|
453
|
+
`Populate live state with ggui_update after render.`);
|
|
454
|
+
runtimeProps = undefined;
|
|
455
|
+
}
|
|
456
|
+
else {
|
|
457
|
+
throw new ContractViolationError({
|
|
458
|
+
tool: 'ggui_render',
|
|
459
|
+
violations: [
|
|
460
|
+
{
|
|
461
|
+
field: 'props',
|
|
462
|
+
message: 'props supplied but your override contract declares no propsSpec. Drop the `props` field, or add a propsSpec covering these fields.',
|
|
463
|
+
expected: 'no props (contract has no propsSpec)',
|
|
464
|
+
received: `props with keys: ${Object.keys(runtimeProps).join(', ')}`,
|
|
465
|
+
},
|
|
466
|
+
],
|
|
467
|
+
hint: 'Your OVERRIDE draft has no propsSpec, so it takes no props. Drop `props`, add a propsSpec — or re-handshake and use decision:accept (the accept path tolerates mismatched props instead of failing). The handshake record is preserved; retry on the SAME handshakeId.',
|
|
468
|
+
});
|
|
469
|
+
}
|
|
449
470
|
}
|
|
450
471
|
// Atomically consume the handshake record now that input
|
|
451
472
|
// validation has succeeded.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ggui-ai/mcp-server-handlers",
|
|
3
|
-
"version": "0.2.0-alpha.
|
|
3
|
+
"version": "0.2.0-alpha.4",
|
|
4
4
|
"description": "MCP tool handler logic for the ggui protocol. Pure over @ggui-ai/mcp-server-core seams and consumed by @ggui-ai/mcp-server. Never imports AWS, HTTP transports, or CLI concerns.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ggui",
|
|
@@ -72,11 +72,11 @@
|
|
|
72
72
|
}
|
|
73
73
|
},
|
|
74
74
|
"dependencies": {
|
|
75
|
-
"@ggui-ai/mcp-server-core": "0.2.0-alpha.
|
|
76
|
-
"@ggui-ai/
|
|
77
|
-
"@ggui-ai/
|
|
78
|
-
"@ggui-ai/
|
|
79
|
-
"@ggui-ai/
|
|
75
|
+
"@ggui-ai/mcp-server-core": "0.2.0-alpha.4",
|
|
76
|
+
"@ggui-ai/ui-gen": "0.2.0-alpha.4",
|
|
77
|
+
"@ggui-ai/ui-registry": "0.2.0-alpha.4",
|
|
78
|
+
"@ggui-ai/negotiator": "0.2.0-alpha.4",
|
|
79
|
+
"@ggui-ai/protocol": "0.2.0-alpha.4"
|
|
80
80
|
},
|
|
81
81
|
"peerDependencies": {
|
|
82
82
|
"zod": ">=3.0.0"
|