@ggui-ai/mcp-server-handlers 0.7.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/blueprints/index.d.ts +2 -2
- package/dist/blueprints/index.d.ts.map +1 -1
- package/dist/blueprints/index.js +1 -1
- package/dist/blueprints/search-blueprints.d.ts +64 -12
- package/dist/blueprints/search-blueprints.d.ts.map +1 -1
- package/dist/blueprints/search-blueprints.js +166 -16
- package/dist/renders/amend.d.ts +57 -0
- package/dist/renders/amend.d.ts.map +1 -0
- package/dist/renders/amend.js +64 -0
- package/dist/renders/apply-ggui-session-patch.d.ts +4 -0
- package/dist/renders/apply-ggui-session-patch.d.ts.map +1 -1
- package/dist/renders/apply-ggui-session-patch.js +1 -1
- package/dist/renders/assert-props-contract.d.ts +5 -4
- package/dist/renders/assert-props-contract.d.ts.map +1 -1
- package/dist/renders/assert-props-contract.js +6 -5
- package/dist/renders/code-delivery-events.d.ts +45 -29
- package/dist/renders/code-delivery-events.d.ts.map +1 -1
- package/dist/renders/code-delivery-events.js +42 -25
- package/dist/renders/consume.js +1 -1
- package/dist/renders/decide-handshake.d.ts +15 -0
- package/dist/renders/decide-handshake.d.ts.map +1 -1
- package/dist/renders/decide-handshake.js +18 -0
- package/dist/renders/declare-tool-catalog.js +1 -1
- package/dist/renders/handshake.d.ts.map +1 -1
- package/dist/renders/handshake.js +58 -12
- package/dist/renders/index.d.ts +5 -1
- package/dist/renders/index.d.ts.map +1 -1
- package/dist/renders/index.js +5 -1
- package/dist/renders/props-mutation-core.d.ts +60 -0
- package/dist/renders/props-mutation-core.d.ts.map +1 -0
- package/dist/renders/props-mutation-core.js +339 -0
- package/dist/renders/render.d.ts +2 -0
- package/dist/renders/render.d.ts.map +1 -1
- package/dist/renders/render.js +61 -32
- package/dist/renders/runtime-pull.d.ts +122 -0
- package/dist/renders/runtime-pull.d.ts.map +1 -0
- package/dist/renders/runtime-pull.js +178 -0
- package/dist/renders/runtime-telemetry.d.ts +60 -0
- package/dist/renders/runtime-telemetry.d.ts.map +1 -0
- package/dist/renders/runtime-telemetry.js +76 -0
- package/dist/renders/slice-meta-derivation.d.ts +146 -6
- package/dist/renders/slice-meta-derivation.d.ts.map +1 -1
- package/dist/renders/slice-meta-derivation.js +190 -8
- package/dist/renders/submit-action.d.ts +9 -0
- package/dist/renders/submit-action.d.ts.map +1 -1
- package/dist/renders/submit-action.js +70 -1
- package/dist/renders/update.d.ts +33 -42
- package/dist/renders/update.d.ts.map +1 -1
- package/dist/renders/update.js +99 -230
- package/package.json +7 -7
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PROPS-MUTATION CORE — the single flow behind BOTH mutation tools
|
|
3
|
+
* (#483 tool split): `ggui_update` (advances the history epoch,
|
|
4
|
+
* mints a new record) and `ggui_amend` (in-place, epoch untouched).
|
|
5
|
+
* Extracted verbatim from the pre-split ggui_update handler; the
|
|
6
|
+
* per-tool deltas are parameterized on `tool`, everything else is
|
|
7
|
+
* byte-identical behavior.
|
|
8
|
+
*/
|
|
9
|
+
import { z } from 'zod';
|
|
10
|
+
import { ContractViolationError, } from '@ggui-ai/protocol';
|
|
11
|
+
import { GGUI_RENDER_UI_META } from '@ggui-ai/protocol/integrations/mcp-apps';
|
|
12
|
+
import { refreshRenderIdentity } from './render-identity.js';
|
|
13
|
+
import { applyGguiSessionPatch, } from './apply-ggui-session-patch.js';
|
|
14
|
+
import { GguiSessionNotFoundError } from './errors.js';
|
|
15
|
+
import { emitPayloadTraceEvent } from './payload-trace-sink.js';
|
|
16
|
+
/**
|
|
17
|
+
* Input raw-shape shared by BOTH mutation tools — discriminated on
|
|
18
|
+
* `kind` (`'replace'` + `props` | `'merge'` + `patch`); identical wire
|
|
19
|
+
* grammar for `ggui_update` and `ggui_amend` (#483). Both validate the
|
|
20
|
+
* FINAL props (post-merge for `merge`) against the render's
|
|
21
|
+
* `propsSpec`.
|
|
22
|
+
*/
|
|
23
|
+
export const mutationInputSchema = {
|
|
24
|
+
/**
|
|
25
|
+
* Globally-unique render id. Optional on the wire so an in-process
|
|
26
|
+
* dispatcher (live-channel dispatch / threaded mount) can populate it
|
|
27
|
+
* via `HandlerContext.sessionId` instead. Required at the core
|
|
28
|
+
* level — see the resolve step inside `runPropsMutation`.
|
|
29
|
+
*/
|
|
30
|
+
sessionId: z.string().optional(),
|
|
31
|
+
/**
|
|
32
|
+
* Mode discriminator. `'replace'` requires `props`; `'merge'`
|
|
33
|
+
* requires `patch`. The narrowing step inside the core enforces
|
|
34
|
+
* both presence + mutual exclusion.
|
|
35
|
+
*/
|
|
36
|
+
kind: z.enum(['replace', 'merge']),
|
|
37
|
+
/**
|
|
38
|
+
* Full new props map. Required when `kind === 'replace'`; rejected
|
|
39
|
+
* otherwise. Validated against the GguiSession's `propsSpec` after
|
|
40
|
+
* applying.
|
|
41
|
+
*/
|
|
42
|
+
props: z.record(z.string(), z.unknown()).optional(),
|
|
43
|
+
/**
|
|
44
|
+
* RFC 7396 JSON Merge Patch. Required when `kind === 'merge'`;
|
|
45
|
+
* rejected otherwise. The core applies the patch to the existing
|
|
46
|
+
* props, then validates the merged result against `propsSpec`.
|
|
47
|
+
* `null` values in the patch DELETE the corresponding key.
|
|
48
|
+
*/
|
|
49
|
+
patch: z.record(z.string(), z.unknown()).optional(),
|
|
50
|
+
};
|
|
51
|
+
const inputSchema = mutationInputSchema;
|
|
52
|
+
/**
|
|
53
|
+
* Canonical JSON stringify — object keys sorted recursively so two
|
|
54
|
+
* semantically-equal JsonObjects compare equal regardless of key
|
|
55
|
+
* insertion order. Arrays keep positional order (order is meaning
|
|
56
|
+
* there). Used by the no-op gate; props are validated JsonObjects, so
|
|
57
|
+
* no cycles by construction.
|
|
58
|
+
*/
|
|
59
|
+
function stableStringify(value) {
|
|
60
|
+
if (Array.isArray(value)) {
|
|
61
|
+
return `[${value.map(stableStringify).join(',')}]`;
|
|
62
|
+
}
|
|
63
|
+
if (value !== null && typeof value === 'object') {
|
|
64
|
+
const entries = Object.entries(value)
|
|
65
|
+
.filter(([, v]) => v !== undefined)
|
|
66
|
+
.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
|
|
67
|
+
.map(([k, v]) => `${JSON.stringify(k)}:${stableStringify(v)}`);
|
|
68
|
+
return `{${entries.join(',')}}`;
|
|
69
|
+
}
|
|
70
|
+
return JSON.stringify(value) ?? 'null';
|
|
71
|
+
}
|
|
72
|
+
export async function runPropsMutation(deps, tool, input, ctx) {
|
|
73
|
+
// Strict: in-process dispatchers face exactly the protocol
|
|
74
|
+
// contract (unknown keys reject, as `updateInputSchema` does). On
|
|
75
|
+
// the MCP wire path this is a no-op by construction — the SDK's
|
|
76
|
+
// own arg validation strips unknown keys before the handler runs.
|
|
77
|
+
const parsed = z.object(inputSchema).strict().parse(input);
|
|
78
|
+
// Narrow on kind FIRST. Each branch enforces required-field +
|
|
79
|
+
// mutual-exclusion semantics that the flat raw-shape can't
|
|
80
|
+
// express. Runs before the billing gate and before any store
|
|
81
|
+
// read, so a malformed call costs nothing: no gate evaluation,
|
|
82
|
+
// no DynamoDB read, no trace emission — and the caller gets the
|
|
83
|
+
// contract error rather than whatever the gate would have said.
|
|
84
|
+
const kind = parsed.kind;
|
|
85
|
+
let patchInput;
|
|
86
|
+
if (kind === 'replace') {
|
|
87
|
+
if (parsed.props === undefined) {
|
|
88
|
+
throw new ContractViolationError({
|
|
89
|
+
tool,
|
|
90
|
+
violations: [
|
|
91
|
+
{
|
|
92
|
+
code: 'CTR_UPDATE_MISSING_PROPS',
|
|
93
|
+
field: 'props',
|
|
94
|
+
message: 'kind:"replace" requires `props` — the full new props map.',
|
|
95
|
+
},
|
|
96
|
+
],
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
if (parsed.patch !== undefined) {
|
|
100
|
+
throw new ContractViolationError({
|
|
101
|
+
tool,
|
|
102
|
+
violations: [
|
|
103
|
+
{
|
|
104
|
+
code: 'CTR_UPDATE_MIXED_FIELDS',
|
|
105
|
+
field: 'patch',
|
|
106
|
+
message: 'kind:"replace" must not include `patch`. Pick one mode per call.',
|
|
107
|
+
},
|
|
108
|
+
],
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
patchInput = { mode: 'replace', props: parsed.props };
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
// kind === 'merge'
|
|
115
|
+
if (parsed.patch === undefined) {
|
|
116
|
+
throw new ContractViolationError({
|
|
117
|
+
tool,
|
|
118
|
+
violations: [
|
|
119
|
+
{
|
|
120
|
+
code: 'CTR_UPDATE_MISSING_PATCH',
|
|
121
|
+
field: 'patch',
|
|
122
|
+
message: 'kind:"merge" requires `patch` — the RFC 7396 JSON Merge Patch delta.',
|
|
123
|
+
},
|
|
124
|
+
],
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
if (parsed.props !== undefined) {
|
|
128
|
+
throw new ContractViolationError({
|
|
129
|
+
tool,
|
|
130
|
+
violations: [
|
|
131
|
+
{
|
|
132
|
+
code: 'CTR_UPDATE_MIXED_FIELDS',
|
|
133
|
+
field: 'props',
|
|
134
|
+
message: 'kind:"merge" must not include `props`. Pick one mode per call.',
|
|
135
|
+
},
|
|
136
|
+
],
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
patchInput = { mode: 'merge', patch: parsed.patch };
|
|
140
|
+
}
|
|
141
|
+
// Pre-mutation gate. Throws to abort BEFORE any state change.
|
|
142
|
+
// OSS default: no gate bound, no-op. Cloud binds a traffic-class
|
|
143
|
+
// gate here.
|
|
144
|
+
if (deps.billingGate) {
|
|
145
|
+
await deps.billingGate.preCheck({ ctx, tool });
|
|
146
|
+
}
|
|
147
|
+
// Resolve sessionId from wire OR threaded HandlerContext.
|
|
148
|
+
const sessionId = parsed.sessionId ?? ctx.sessionId;
|
|
149
|
+
if (!sessionId) {
|
|
150
|
+
throw new GguiSessionNotFoundError('', `${tool}: sessionId is required on the wire (or threaded via HandlerContext for in-process dispatchers).`);
|
|
151
|
+
}
|
|
152
|
+
// Tenancy gate. Cross-tenant + missing surface uniformly as
|
|
153
|
+
// GguiSessionNotFoundError so cross-tenant existence is not leaked.
|
|
154
|
+
const stored = await deps.renderStore.get(sessionId);
|
|
155
|
+
if (!stored || stored.appId !== ctx.appId) {
|
|
156
|
+
throw new GguiSessionNotFoundError(sessionId);
|
|
157
|
+
}
|
|
158
|
+
// Mount-URI identity (#471 round-4 live finding): `ggui_render`
|
|
159
|
+
// stamped `.../<sessionId>/<contractKey>` as the mounted iframe's
|
|
160
|
+
// resource URI. Hosts that key result→iframe routing on URI
|
|
161
|
+
// equality only deliver this update's result to the live card
|
|
162
|
+
// when the update reproduces that EXACT URI — a bare
|
|
163
|
+
// `.../<sessionId>` routes fine on OUR server but is a different
|
|
164
|
+
// string to the host, so the forwarded result misses the mount
|
|
165
|
+
// and the card never repaints. The contract key cannot be
|
|
166
|
+
// recomputed from the render row (it hashes the handshake-agreed
|
|
167
|
+
// contract — see render-identity.ts); read it from the durable
|
|
168
|
+
// identity record. Absent store/record ⇒ bare session URI
|
|
169
|
+
// (pre-identity renders keep the old shape).
|
|
170
|
+
let contractSegment = '';
|
|
171
|
+
if (deps.renderIdentityStore) {
|
|
172
|
+
try {
|
|
173
|
+
const identity = await deps.renderIdentityStore.get(sessionId);
|
|
174
|
+
if (identity?.contractKey) {
|
|
175
|
+
contractSegment = `/${identity.contractKey}`;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
catch {
|
|
179
|
+
// Best-effort, matching every identity-store touch in this
|
|
180
|
+
// handler: a failed read must not fail the tool call, and the
|
|
181
|
+
// bare URI below stays routable on our server.
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
const mountResourceUri = `${GGUI_RENDER_UI_META.resourceUri}/${sessionId}${contractSegment}`;
|
|
185
|
+
// Devtools payload trace. No-op when no sink is registered.
|
|
186
|
+
// Fires AFTER the tenancy gate so cross-tenant probes never leak
|
|
187
|
+
// into the trace. Payload is the validated wire shape.
|
|
188
|
+
emitPayloadTraceEvent({
|
|
189
|
+
direction: 'outbound-update',
|
|
190
|
+
sessionId,
|
|
191
|
+
appId: ctx.appId,
|
|
192
|
+
tool,
|
|
193
|
+
payload: parsed,
|
|
194
|
+
});
|
|
195
|
+
// applyGguiSessionPatch throws ContractViolationError{tool:'ggui_update'}
|
|
196
|
+
// on propsSpec fail (validated against the FINAL props — post-merge
|
|
197
|
+
// for `merge` mode). Propagates verbatim — transport layer maps.
|
|
198
|
+
//
|
|
199
|
+
// Pull renderTarget from `stored.render` — both ComponentGguiSession and
|
|
200
|
+
// SystemGguiSession satisfy `GguiSessionTarget` (id + optional propsSpec +
|
|
201
|
+
// optional props). McpAppsGguiSession has no propsSpec — the helper's
|
|
202
|
+
// assertPropsContract no-ops on absent spec, so MCP Apps renders
|
|
203
|
+
// accept any patch shape (the iframe owns its own validation).
|
|
204
|
+
const renderTarget = stored.render;
|
|
205
|
+
const { updatedSession, finalProps } = applyGguiSessionPatch({
|
|
206
|
+
render: renderTarget,
|
|
207
|
+
tool,
|
|
208
|
+
...patchInput,
|
|
209
|
+
});
|
|
210
|
+
// No-op gate (#471 round-3 live finding): a CONFORMING patch that
|
|
211
|
+
// leaves the final props semantically identical to the current
|
|
212
|
+
// state is almost always an agent error — the live failure mode
|
|
213
|
+
// was an LLM echoing the card's existing props back (believing it
|
|
214
|
+
// had "switched" the UI) and telling the user the card changed
|
|
215
|
+
// while nothing on screen could. The contract validated it, the
|
|
216
|
+
// commit would apply it, and no signal existed anywhere. Make the
|
|
217
|
+
// non-change OBSERVABLE instead of silently committing: skip the
|
|
218
|
+
// write + fan-out (there is nothing to deliver) and return an
|
|
219
|
+
// honest `updated: false` with a model-visible warning telling
|
|
220
|
+
// the agent what to do instead.
|
|
221
|
+
const priorProps = 'props' in renderTarget && renderTarget.props !== undefined
|
|
222
|
+
? renderTarget.props
|
|
223
|
+
: {};
|
|
224
|
+
if (stableStringify(finalProps) === stableStringify(priorProps)) {
|
|
225
|
+
return {
|
|
226
|
+
sessionId,
|
|
227
|
+
updated: false,
|
|
228
|
+
mountResourceUri,
|
|
229
|
+
// No record minted on a no-op — the head epoch is untouched.
|
|
230
|
+
epoch: stored.render.epoch ?? 0,
|
|
231
|
+
warning: 'NO-OP: this patch left the props identical to the current state — nothing changed on screen. If you meant to refresh the UI, send values that actually differ; if the user’s gesture asks for a DIFFERENT surface (menu selection, navigation), run ggui_handshake + ggui_render for the next UI instead of updating this one.',
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
// Epoch (#483): the row is the AUTHORITY, advanced in the SAME
|
|
235
|
+
// commit as the props write — update mints a new history record
|
|
236
|
+
// (epoch + 1); amend leaves the head untouched. The reminted
|
|
237
|
+
// ledger event below is the wire SIGNAL frames latch on, not the
|
|
238
|
+
// counter (the event ring is horizon-bounded).
|
|
239
|
+
const priorEpoch = stored.render.epoch ?? 0;
|
|
240
|
+
const nextEpoch = tool === 'ggui_update' ? priorEpoch + 1 : priorEpoch;
|
|
241
|
+
const sessionToCommit = tool === 'ggui_update'
|
|
242
|
+
? { ...updatedSession, epoch: nextEpoch }
|
|
243
|
+
: updatedSession;
|
|
244
|
+
// Persist via the commit seam — first-write mints, re-write
|
|
245
|
+
// replaces visible-bits in place. Lifecycle fields owned by the
|
|
246
|
+
// store (createdAt, eventSequence, hostSession) preserved across
|
|
247
|
+
// the upsert.
|
|
248
|
+
const committed = await deps.renderStore.commit({
|
|
249
|
+
render: sessionToCommit,
|
|
250
|
+
appId: stored.appId,
|
|
251
|
+
...(stored.userId !== undefined ? { userId: stored.userId } : {}),
|
|
252
|
+
...(stored.endUserIdentity !== undefined
|
|
253
|
+
? { endUserIdentity: stored.endUserIdentity }
|
|
254
|
+
: {}),
|
|
255
|
+
...(stored.themeId !== undefined ? { themeId: stored.themeId } : {}),
|
|
256
|
+
...(stored.hostSession !== undefined
|
|
257
|
+
? { hostSession: stored.hostSession }
|
|
258
|
+
: {}),
|
|
259
|
+
});
|
|
260
|
+
// Ledger append — the delivery substrate for the PULL rungs of
|
|
261
|
+
// the failover ladder (HTTP `/events` polling and the
|
|
262
|
+
// `ggui_runtime_pull` bridge rung read ONLY the event ledger).
|
|
263
|
+
// Without this append a props update is invisible to every
|
|
264
|
+
// cursor-based reader: live evidence (#471 round-3 forensics)
|
|
265
|
+
// showed eventSequence stuck at 0 across real updates — pull
|
|
266
|
+
// clients would poll politely forever and receive nothing.
|
|
267
|
+
// Best-effort: persistence already succeeded, and the push
|
|
268
|
+
// planes (WS/SSE fan-out, forwarded tool-result slice) deliver
|
|
269
|
+
// independently of the ledger.
|
|
270
|
+
let committedForIdentity = committed;
|
|
271
|
+
try {
|
|
272
|
+
// `'ui.updated'` is the canonical LEDGER taxonomy name; the
|
|
273
|
+
// live-channel FRAME namespace calls the same thing
|
|
274
|
+
// `props_update`. The translation lives at the client parse
|
|
275
|
+
// core (events-polling), never here — the ledger speaks only
|
|
276
|
+
// taxonomy types.
|
|
277
|
+
// The props event carries the epoch it belongs to (#483) so
|
|
278
|
+
// frames can ignore props from a NEWER epoch than their own
|
|
279
|
+
// instead of flashing the new state before the freeze lands.
|
|
280
|
+
const seq = await deps.renderStore.appendEvent({
|
|
281
|
+
sessionId,
|
|
282
|
+
type: 'ui.updated',
|
|
283
|
+
data: { sessionId, props: finalProps, epoch: nextEpoch },
|
|
284
|
+
});
|
|
285
|
+
// The append advanced the row's high-water mark past the
|
|
286
|
+
// commit-time snapshot — thread the returned seq so the
|
|
287
|
+
// identity refresh records the ledger state INCLUDING this
|
|
288
|
+
// update's own event (record.seqAtLastCommit tracks the row).
|
|
289
|
+
committedForIdentity = { ...committed, eventSequence: seq };
|
|
290
|
+
// Epoch boundary AFTER the props event (#483): pinned #N
|
|
291
|
+
// reconstruction replays props UP TO AND INCLUDING the N-th
|
|
292
|
+
// remint boundary, so the boundary must follow its own props.
|
|
293
|
+
// This is also the freeze signal older-epoch frames latch on.
|
|
294
|
+
if (tool === 'ggui_update') {
|
|
295
|
+
const remintSeq = await deps.renderStore.appendEvent({
|
|
296
|
+
sessionId,
|
|
297
|
+
type: 'ui.reminted',
|
|
298
|
+
data: { epoch: nextEpoch },
|
|
299
|
+
});
|
|
300
|
+
committedForIdentity = {
|
|
301
|
+
...committedForIdentity,
|
|
302
|
+
eventSequence: remintSeq,
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
catch {
|
|
307
|
+
// Silent — a ledger hiccup must not fail the tool call; the
|
|
308
|
+
// pull rungs degrade to the ack/mount snapshot on next
|
|
309
|
+
// (re)subscribe, same recovery every reader already has.
|
|
310
|
+
}
|
|
311
|
+
// Keep the durable identity record's view of this row current.
|
|
312
|
+
// Reads the row the commit RETURNED (seq-adjusted above) so the
|
|
313
|
+
// record can't disagree with what was persisted.
|
|
314
|
+
await refreshRenderIdentity(deps.renderIdentityStore, committedForIdentity);
|
|
315
|
+
// Best-effort live delivery. Persistence is the source of truth;
|
|
316
|
+
// the live-channel fan-out is a latency optimization. Errors are
|
|
317
|
+
// swallowed — a failed notify must not fail the tool call (the
|
|
318
|
+
// renderer reads canonical state via `ack.render` on next
|
|
319
|
+
// (re)subscribe).
|
|
320
|
+
if (deps.propsUpdateNotifier) {
|
|
321
|
+
try {
|
|
322
|
+
await deps.propsUpdateNotifier.sendPropsUpdate(sessionId, finalProps, nextEpoch);
|
|
323
|
+
}
|
|
324
|
+
catch {
|
|
325
|
+
// Silent: stay aligned with `safelyNotifyGguiSessionCommit`'s
|
|
326
|
+
// posture in render.ts. A throwing notifier is a host-side
|
|
327
|
+
// bug, not a tool-call failure.
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
// Callers own presentation: ggui_update composes the pinned
|
|
331
|
+
// `#epoch` form from this bare mount URI; ggui_amend returns it
|
|
332
|
+
// bare (the live head).
|
|
333
|
+
return {
|
|
334
|
+
sessionId,
|
|
335
|
+
updated: true,
|
|
336
|
+
mountResourceUri,
|
|
337
|
+
epoch: nextEpoch,
|
|
338
|
+
};
|
|
339
|
+
}
|
package/dist/renders/render.d.ts
CHANGED
|
@@ -704,6 +704,7 @@ declare const outputSchema: {
|
|
|
704
704
|
VALIDATION_ERROR: "VALIDATION_ERROR";
|
|
705
705
|
NO_PLATFORM_KEY: "NO_PLATFORM_KEY";
|
|
706
706
|
NO_CREDENTIALS: "NO_CREDENTIALS";
|
|
707
|
+
GENERATION_QUEUE_OVERLOADED: "GENERATION_QUEUE_OVERLOADED";
|
|
707
708
|
}>;
|
|
708
709
|
message: z.ZodString;
|
|
709
710
|
}, z.core.$strip>>;
|
|
@@ -713,6 +714,7 @@ declare const outputSchema: {
|
|
|
713
714
|
example: z.ZodString;
|
|
714
715
|
args: z.ZodObject<{
|
|
715
716
|
sessionId: z.ZodString;
|
|
717
|
+
timeout: z.ZodNumber;
|
|
716
718
|
}, z.core.$strip>;
|
|
717
719
|
}, z.core.$strip>>;
|
|
718
720
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../src/renders/render.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AAGH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAEL,KAAK,iBAAiB,EAEtB,KAAK,YAAY,EAGjB,KAAK,WAAW,EAGjB,MAAM,mBAAmB,CAAC;AAM3B,OAAO,KAAK,EACV,gBAAgB,EAChB,iBAAiB,EACjB,aAAa,EACb,YAAY,EACZ,oBAAoB,EACpB,cAAc,EACd,WAAW,EACX,mBAAmB,EACnB,gBAAgB,EAChB,cAAc,EAGd,WAAW,EACZ,MAAM,0BAA0B,CAAC;AAElC,OAAO,EAGL,KAAK,cAAc,EACnB,KAAK,cAAc,EACnB,KAAK,aAAa,EACnB,MAAM,aAAa,CAAC;AAOrB,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
|
@@ -53,7 +53,7 @@ import { handlerFailure, isHandlerFailure, } from '../types.js';
|
|
|
53
53
|
import { consumeHandshakeRecord, peekHandshakeRecord, HandshakeNotFoundError, } from './handshake.js';
|
|
54
54
|
import { evaluateProvisionalPreviewGate, finalizeProvisionalPreview, kickoffProvisionalPreview, } from './provisional-preview.js';
|
|
55
55
|
import { assertNoDuplicateGadgetHooks } from './assert-no-duplicate-gadget-hooks.js';
|
|
56
|
-
import { reportRenderCodeWriteFailed } from './code-delivery-events.js';
|
|
56
|
+
import { reportRenderCodeB64OverCap, reportRenderCodeWriteFailed, } from './code-delivery-events.js';
|
|
57
57
|
import { writeRenderIdentity } from './render-identity.js';
|
|
58
58
|
import { findBlueprintExact, readBlueprintById, registerBlueprint, } from './blueprint-registry.js';
|
|
59
59
|
import { assertGadgetsRegistered, filterDescriptorsToContract, } from './assert-gadgets.js';
|
|
@@ -64,7 +64,7 @@ import { validatePropsData, ContractViolationError, validateContract, renderInpu
|
|
|
64
64
|
import { emitCacheTraceEvent, newCacheTraceId, truncateCacheTraceIntent, } from './cache-trace-sink.js';
|
|
65
65
|
import { emitPayloadTraceEvent } from './payload-trace-sink.js';
|
|
66
66
|
import { isVisibleToCaller } from './tenancy.js';
|
|
67
|
-
import { assembleRenderSliceBase, deriveRenderMeta, derivePublicEnvProjection, deriveContractBundle, } from './slice-meta-derivation.js';
|
|
67
|
+
import { assembleRenderSliceBase, deriveRenderMeta, derivePublicEnvProjection, deriveContractBundle, rewritePrivateBundleUrls, spreadRenderMetaViewOntoSlice, } from './slice-meta-derivation.js';
|
|
68
68
|
/**
|
|
69
69
|
* Input raw-shape.
|
|
70
70
|
*
|
|
@@ -122,6 +122,7 @@ const RENDER_FAILURE_GUIDANCE = {
|
|
|
122
122
|
VALIDATION_ERROR: 'Fix the configuration or input named in the message',
|
|
123
123
|
NO_PLATFORM_KEY: 'Have the operator set a provider key for this app, or switch the app to a model whose key is configured',
|
|
124
124
|
NO_CREDENTIALS: 'Have the operator configure provider credentials on the server (environment variable or credentials file)',
|
|
125
|
+
GENERATION_QUEUE_OVERLOADED: 'No attempt was made — this is safe to retry immediately, or after a short backoff if it recurs',
|
|
125
126
|
};
|
|
126
127
|
/**
|
|
127
128
|
* Model-visible content text for the failure envelope. Pinned format:
|
|
@@ -138,7 +139,7 @@ function buildRenderFailureText(failure) {
|
|
|
138
139
|
* Project a generator-result {@link GenerationError} code onto the
|
|
139
140
|
* canonical render-failure enum. `COMPILATION_ERROR` is a
|
|
140
141
|
* finer-grained production failure, not a distinct wire class — it
|
|
141
|
-
* folds into `PRODUCTION_FAILED`; the other
|
|
142
|
+
* folds into `PRODUCTION_FAILED`; the other five members map 1:1.
|
|
142
143
|
*/
|
|
143
144
|
function toCanonicalRenderErrorCode(code) {
|
|
144
145
|
switch (code) {
|
|
@@ -148,6 +149,8 @@ function toCanonicalRenderErrorCode(code) {
|
|
|
148
149
|
return 'NO_PLATFORM_KEY';
|
|
149
150
|
case 'NO_CREDENTIALS':
|
|
150
151
|
return 'NO_CREDENTIALS';
|
|
152
|
+
case 'GENERATION_QUEUE_OVERLOADED':
|
|
153
|
+
return 'GENERATION_QUEUE_OVERLOADED';
|
|
151
154
|
case 'PRODUCTION_FAILED':
|
|
152
155
|
case 'COMPILATION_ERROR':
|
|
153
156
|
return 'PRODUCTION_FAILED';
|
|
@@ -210,10 +213,15 @@ export function createGguiRenderHandler(deps) {
|
|
|
210
213
|
inputSchema,
|
|
211
214
|
outputSchema,
|
|
212
215
|
_meta: {
|
|
213
|
-
// §2.4.1 entry-point lock: `_meta.ui.resourceUri`
|
|
214
|
-
// `_meta.ui.visibility` per the MCP Apps spec. Exactly
|
|
215
|
-
//
|
|
216
|
-
//
|
|
216
|
+
// §2.4.1 entry-point lock, REVISED 2026-08-11: `_meta.ui.resourceUri`
|
|
217
|
+
// + `_meta.ui.visibility` per the MCP Apps spec. Exactly TWO ggui
|
|
218
|
+
// tools carry these — ggui_render (the entry point) and
|
|
219
|
+
// ggui_update (same template). The lock originally said one, but
|
|
220
|
+
// live claude.ai evidence (#471 round-2 retest) showed hosts
|
|
221
|
+
// forward a tool's RESULTS to the mounted iframe only when the
|
|
222
|
+
// tool's declaration carries the binding — without it every
|
|
223
|
+
// ggui_update landed server-side and the iframe never repainted.
|
|
224
|
+
// Expanding beyond these two still requires revisiting this note.
|
|
217
225
|
//
|
|
218
226
|
// Legacy flat-key (`_meta["ui/resourceUri"]`) is stamped
|
|
219
227
|
// automatically by `registerAppTool` in `build-mcp.ts` — we
|
|
@@ -1225,9 +1233,15 @@ export function createGguiRenderHandler(deps) {
|
|
|
1225
1233
|
const nextStep = hasActions
|
|
1226
1234
|
? {
|
|
1227
1235
|
tool: 'ggui_consume',
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1236
|
+
// Imperative + honest: `timeout` MUST ride the hint —
|
|
1237
|
+
// consume's own default is 0 (single non-blocking drain),
|
|
1238
|
+
// so an agent copying a timeout-less example gets an
|
|
1239
|
+
// instant empty result and reasonably ends its turn (the
|
|
1240
|
+
// first live claude.ai test died exactly this way). 25 is
|
|
1241
|
+
// the server-enforced per-call cap (SPEC §7.3).
|
|
1242
|
+
description: "The UI has interactive actions. After this turn's other tool calls, you may long-poll once (waits up to 25s) to catch an immediate gesture; if `events` comes back empty, end your turn — later gestures arrive as new user messages carrying their own consume directive.",
|
|
1243
|
+
example: `ggui_consume({ sessionId: "${sessionId}", timeout: 25 })`,
|
|
1244
|
+
args: { sessionId, timeout: 25 },
|
|
1231
1245
|
}
|
|
1232
1246
|
: undefined;
|
|
1233
1247
|
// Render response architecture (2026-05-13):
|
|
@@ -1340,7 +1354,25 @@ export function createGguiRenderHandler(deps) {
|
|
|
1340
1354
|
lastSequence = stored.eventSequence;
|
|
1341
1355
|
}
|
|
1342
1356
|
if (top) {
|
|
1343
|
-
|
|
1357
|
+
// H1 ruling 1b — presign-rewrite PRIVATE bundle URLs before
|
|
1358
|
+
// projection so CSP origins + iframe registrations both see
|
|
1359
|
+
// the fetchable (presigned) URL. Passthrough when the
|
|
1360
|
+
// deployment wires no presigner (OSS same-origin story).
|
|
1361
|
+
view = deriveRenderMeta(await rewritePrivateBundleUrls(top, deps.presignPrivateBundleUrl));
|
|
1362
|
+
// Over-cap inline-channel omission is a named event, not a
|
|
1363
|
+
// silent degrade: on a host whose iframe CSP blocks fetches
|
|
1364
|
+
// AND WebSockets, a slice without `codeB64` cannot paint.
|
|
1365
|
+
if (top.type !== 'mcpApps' &&
|
|
1366
|
+
top.type !== 'system' &&
|
|
1367
|
+
typeof top.componentCode === 'string' &&
|
|
1368
|
+
top.componentCode.length > 0 &&
|
|
1369
|
+
view.codeB64 === undefined) {
|
|
1370
|
+
reportRenderCodeB64OverCap({
|
|
1371
|
+
sessionId: output.sessionId,
|
|
1372
|
+
appId: ctx.appId,
|
|
1373
|
+
sourceBytes: Buffer.byteLength(top.componentCode, 'utf8'),
|
|
1374
|
+
});
|
|
1375
|
+
}
|
|
1344
1376
|
// Project the App's publicEnv down to the union of declared
|
|
1345
1377
|
// wrappers' `requires`.
|
|
1346
1378
|
if (deps.appMetadataStore) {
|
|
@@ -1367,7 +1399,7 @@ export function createGguiRenderHandler(deps) {
|
|
|
1367
1399
|
// resolution (operator-debug-wins: live console pick beats the
|
|
1368
1400
|
// per-render `ggui_render.themeId` beats static deps) all live
|
|
1369
1401
|
// in ONE helper shared with `ggui_update`.
|
|
1370
|
-
const { runtimeUrl, authFields, themeId: resolvedThemeId, themeMode: resolvedThemeMode, } = assembleRenderSliceBase(deps, {
|
|
1402
|
+
const { runtimeUrl, authFields, channelUrls, themeId: resolvedThemeId, themeMode: resolvedThemeMode, } = assembleRenderSliceBase(deps, {
|
|
1371
1403
|
sessionId: output.sessionId,
|
|
1372
1404
|
appId: ctx.appId,
|
|
1373
1405
|
renderThemeId,
|
|
@@ -1418,17 +1450,14 @@ export function createGguiRenderHandler(deps) {
|
|
|
1418
1450
|
appId: ctx.appId,
|
|
1419
1451
|
runtimeUrl,
|
|
1420
1452
|
...authFields,
|
|
1453
|
+
// Token-bearing HTTP fallback rungs (pollingUrl + sseUrl),
|
|
1454
|
+
// composed by the ONE shared helper — present exactly when
|
|
1455
|
+
// the live trio above was minted and a session-API base
|
|
1456
|
+
// resolved. See `assembleRenderSliceBase`.
|
|
1457
|
+
...channelUrls,
|
|
1421
1458
|
...(streamWebSocketLocalTools !== undefined
|
|
1422
1459
|
? { streamWebSocketLocalTools }
|
|
1423
1460
|
: {}),
|
|
1424
|
-
// Operator-registered wrappers ride the render slice so the
|
|
1425
|
-
// runtime can dynamic-import each before mounting. Projected
|
|
1426
|
-
// by `deriveRenderMeta` from the (enriched) render contract;
|
|
1427
|
-
// only emitted when wrappers are actually declared so
|
|
1428
|
-
// pure-STDLIB apps stay byte-identical.
|
|
1429
|
-
...(view.gadgets !== undefined && view.gadgets.length > 0
|
|
1430
|
-
? { gadgets: view.gadgets }
|
|
1431
|
-
: {}),
|
|
1432
1461
|
// Minimum-disclosure subset of App.publicEnv (union of
|
|
1433
1462
|
// declared wrappers' `requires`). Filtered above by
|
|
1434
1463
|
// `derivePublicEnvProjection`.
|
|
@@ -1442,19 +1471,13 @@ export function createGguiRenderHandler(deps) {
|
|
|
1442
1471
|
...(resolvedThemeMode !== undefined
|
|
1443
1472
|
? { themeMode: resolvedThemeMode }
|
|
1444
1473
|
: {}),
|
|
1445
|
-
// Resolved per-app theme overlay (mode + `--ggui-*` variable map)
|
|
1446
|
-
// projected by `deriveRenderMeta` from the render's `theme`
|
|
1447
|
-
// sidecar. Only emitted when the App declared a theme so
|
|
1448
|
-
// theme-less apps stay byte-identical.
|
|
1449
|
-
...(view.theme !== undefined ? { theme: view.theme } : {}),
|
|
1450
|
-
...(view.permissionsPolicy !== undefined
|
|
1451
|
-
? { permissionsPolicy: [...view.permissionsPolicy] }
|
|
1452
|
-
: {}),
|
|
1453
1474
|
...(lastSequence !== undefined ? { lastSequence } : {}),
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1475
|
+
// State + policy view fields (gadgets, theme overlay,
|
|
1476
|
+
// permissionsPolicy, propsJson, contextSlots, and the #483
|
|
1477
|
+
// freeze-latch self-epoch — a fresh render is epoch 0) — ONE
|
|
1478
|
+
// shared spread so this emitter cannot drift from ggui_update /
|
|
1479
|
+
// the shell / the /state route.
|
|
1480
|
+
...spreadRenderMetaViewOntoSlice(view),
|
|
1458
1481
|
// Content-addressable contract validators. Both fields present
|
|
1459
1482
|
// together or absent together — iframe-runtime treats absence
|
|
1460
1483
|
// as "no validators".
|
|
@@ -1468,6 +1491,12 @@ export function createGguiRenderHandler(deps) {
|
|
|
1468
1491
|
...(outputWithCode.codeHash
|
|
1469
1492
|
? { codeHash: outputWithCode.codeHash }
|
|
1470
1493
|
: {}),
|
|
1494
|
+
// Inline compiled source (size-capped, projected by
|
|
1495
|
+
// `deriveRenderMeta`). Coexists with codeUrl: fetch-capable
|
|
1496
|
+
// hosts keep the cache-addressable URL, fetch-blocked hosts
|
|
1497
|
+
// (iframe CSP without connect-src/script-src to this server)
|
|
1498
|
+
// decode the inline bytes. Never emitted alongside `kind`.
|
|
1499
|
+
...(view.codeB64 !== undefined ? { codeB64: view.codeB64 } : {}),
|
|
1471
1500
|
};
|
|
1472
1501
|
const uiMeta = {
|
|
1473
1502
|
resourceUri: perCallResourceUri,
|