@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.
Files changed (50) hide show
  1. package/dist/blueprints/index.d.ts +2 -2
  2. package/dist/blueprints/index.d.ts.map +1 -1
  3. package/dist/blueprints/index.js +1 -1
  4. package/dist/blueprints/search-blueprints.d.ts +64 -12
  5. package/dist/blueprints/search-blueprints.d.ts.map +1 -1
  6. package/dist/blueprints/search-blueprints.js +166 -16
  7. package/dist/renders/amend.d.ts +57 -0
  8. package/dist/renders/amend.d.ts.map +1 -0
  9. package/dist/renders/amend.js +64 -0
  10. package/dist/renders/apply-ggui-session-patch.d.ts +4 -0
  11. package/dist/renders/apply-ggui-session-patch.d.ts.map +1 -1
  12. package/dist/renders/apply-ggui-session-patch.js +1 -1
  13. package/dist/renders/assert-props-contract.d.ts +5 -4
  14. package/dist/renders/assert-props-contract.d.ts.map +1 -1
  15. package/dist/renders/assert-props-contract.js +6 -5
  16. package/dist/renders/code-delivery-events.d.ts +45 -29
  17. package/dist/renders/code-delivery-events.d.ts.map +1 -1
  18. package/dist/renders/code-delivery-events.js +42 -25
  19. package/dist/renders/consume.js +1 -1
  20. package/dist/renders/decide-handshake.d.ts +15 -0
  21. package/dist/renders/decide-handshake.d.ts.map +1 -1
  22. package/dist/renders/decide-handshake.js +18 -0
  23. package/dist/renders/declare-tool-catalog.js +1 -1
  24. package/dist/renders/handshake.d.ts.map +1 -1
  25. package/dist/renders/handshake.js +58 -12
  26. package/dist/renders/index.d.ts +5 -1
  27. package/dist/renders/index.d.ts.map +1 -1
  28. package/dist/renders/index.js +5 -1
  29. package/dist/renders/props-mutation-core.d.ts +60 -0
  30. package/dist/renders/props-mutation-core.d.ts.map +1 -0
  31. package/dist/renders/props-mutation-core.js +339 -0
  32. package/dist/renders/render.d.ts +2 -0
  33. package/dist/renders/render.d.ts.map +1 -1
  34. package/dist/renders/render.js +61 -32
  35. package/dist/renders/runtime-pull.d.ts +122 -0
  36. package/dist/renders/runtime-pull.d.ts.map +1 -0
  37. package/dist/renders/runtime-pull.js +178 -0
  38. package/dist/renders/runtime-telemetry.d.ts +60 -0
  39. package/dist/renders/runtime-telemetry.d.ts.map +1 -0
  40. package/dist/renders/runtime-telemetry.js +76 -0
  41. package/dist/renders/slice-meta-derivation.d.ts +146 -6
  42. package/dist/renders/slice-meta-derivation.d.ts.map +1 -1
  43. package/dist/renders/slice-meta-derivation.js +190 -8
  44. package/dist/renders/submit-action.d.ts +9 -0
  45. package/dist/renders/submit-action.d.ts.map +1 -1
  46. package/dist/renders/submit-action.js +70 -1
  47. package/dist/renders/update.d.ts +33 -42
  48. package/dist/renders/update.d.ts.map +1 -1
  49. package/dist/renders/update.js +99 -230
  50. 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
+ }
@@ -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":"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"}
1
+ {"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../src/renders/render.ts"],"names":[],"mappings":"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"}
@@ -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 four members map 1:1.
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 one ggui
215
- // tool carries these; expanding this set without revisiting the
216
- // design lock is a boundary violation.
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
- description: 'Drain the action pipe for this render — long-polls until a user gesture arrives or 15s timeout.',
1229
- example: `ggui_consume({ sessionId: "${sessionId}" })`,
1230
- args: { sessionId },
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
- view = deriveRenderMeta(top);
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
- ...(view.propsJson !== undefined ? { propsJson: view.propsJson } : {}),
1455
- ...(view.contextSlots !== undefined
1456
- ? { contextSlots: [...view.contextSlots] }
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,