@ggui-ai/mcp-server-handlers 0.8.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 (41) hide show
  1. package/dist/renders/amend.d.ts +57 -0
  2. package/dist/renders/amend.d.ts.map +1 -0
  3. package/dist/renders/amend.js +64 -0
  4. package/dist/renders/apply-ggui-session-patch.d.ts +4 -0
  5. package/dist/renders/apply-ggui-session-patch.d.ts.map +1 -1
  6. package/dist/renders/apply-ggui-session-patch.js +1 -1
  7. package/dist/renders/assert-props-contract.d.ts +5 -4
  8. package/dist/renders/assert-props-contract.d.ts.map +1 -1
  9. package/dist/renders/assert-props-contract.js +6 -5
  10. package/dist/renders/consume.js +1 -1
  11. package/dist/renders/decide-handshake.d.ts +15 -0
  12. package/dist/renders/decide-handshake.d.ts.map +1 -1
  13. package/dist/renders/decide-handshake.js +18 -0
  14. package/dist/renders/declare-tool-catalog.js +1 -1
  15. package/dist/renders/handshake.d.ts.map +1 -1
  16. package/dist/renders/handshake.js +58 -12
  17. package/dist/renders/index.d.ts +5 -1
  18. package/dist/renders/index.d.ts.map +1 -1
  19. package/dist/renders/index.js +5 -1
  20. package/dist/renders/props-mutation-core.d.ts +60 -0
  21. package/dist/renders/props-mutation-core.d.ts.map +1 -0
  22. package/dist/renders/props-mutation-core.js +339 -0
  23. package/dist/renders/render.d.ts +2 -0
  24. package/dist/renders/render.d.ts.map +1 -1
  25. package/dist/renders/render.js +35 -30
  26. package/dist/renders/runtime-pull.d.ts +122 -0
  27. package/dist/renders/runtime-pull.d.ts.map +1 -0
  28. package/dist/renders/runtime-pull.js +178 -0
  29. package/dist/renders/runtime-telemetry.d.ts +60 -0
  30. package/dist/renders/runtime-telemetry.d.ts.map +1 -0
  31. package/dist/renders/runtime-telemetry.js +76 -0
  32. package/dist/renders/slice-meta-derivation.d.ts +57 -0
  33. package/dist/renders/slice-meta-derivation.d.ts.map +1 -1
  34. package/dist/renders/slice-meta-derivation.js +72 -1
  35. package/dist/renders/submit-action.d.ts +9 -0
  36. package/dist/renders/submit-action.d.ts.map +1 -1
  37. package/dist/renders/submit-action.js +70 -1
  38. package/dist/renders/update.d.ts +33 -42
  39. package/dist/renders/update.d.ts.map +1 -1
  40. package/dist/renders/update.js +98 -233
  41. 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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"}
@@ -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, rewritePrivateBundleUrls, } 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):
@@ -1385,7 +1399,7 @@ export function createGguiRenderHandler(deps) {
1385
1399
  // resolution (operator-debug-wins: live console pick beats the
1386
1400
  // per-render `ggui_render.themeId` beats static deps) all live
1387
1401
  // in ONE helper shared with `ggui_update`.
1388
- const { runtimeUrl, authFields, themeId: resolvedThemeId, themeMode: resolvedThemeMode, } = assembleRenderSliceBase(deps, {
1402
+ const { runtimeUrl, authFields, channelUrls, themeId: resolvedThemeId, themeMode: resolvedThemeMode, } = assembleRenderSliceBase(deps, {
1389
1403
  sessionId: output.sessionId,
1390
1404
  appId: ctx.appId,
1391
1405
  renderThemeId,
@@ -1436,17 +1450,14 @@ export function createGguiRenderHandler(deps) {
1436
1450
  appId: ctx.appId,
1437
1451
  runtimeUrl,
1438
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,
1439
1458
  ...(streamWebSocketLocalTools !== undefined
1440
1459
  ? { streamWebSocketLocalTools }
1441
1460
  : {}),
1442
- // Operator-registered wrappers ride the render slice so the
1443
- // runtime can dynamic-import each before mounting. Projected
1444
- // by `deriveRenderMeta` from the (enriched) render contract;
1445
- // only emitted when wrappers are actually declared so
1446
- // pure-STDLIB apps stay byte-identical.
1447
- ...(view.gadgets !== undefined && view.gadgets.length > 0
1448
- ? { gadgets: view.gadgets }
1449
- : {}),
1450
1461
  // Minimum-disclosure subset of App.publicEnv (union of
1451
1462
  // declared wrappers' `requires`). Filtered above by
1452
1463
  // `derivePublicEnvProjection`.
@@ -1460,19 +1471,13 @@ export function createGguiRenderHandler(deps) {
1460
1471
  ...(resolvedThemeMode !== undefined
1461
1472
  ? { themeMode: resolvedThemeMode }
1462
1473
  : {}),
1463
- // Resolved per-app theme overlay (mode + `--ggui-*` variable map)
1464
- // projected by `deriveRenderMeta` from the render's `theme`
1465
- // sidecar. Only emitted when the App declared a theme so
1466
- // theme-less apps stay byte-identical.
1467
- ...(view.theme !== undefined ? { theme: view.theme } : {}),
1468
- ...(view.permissionsPolicy !== undefined
1469
- ? { permissionsPolicy: [...view.permissionsPolicy] }
1470
- : {}),
1471
1474
  ...(lastSequence !== undefined ? { lastSequence } : {}),
1472
- ...(view.propsJson !== undefined ? { propsJson: view.propsJson } : {}),
1473
- ...(view.contextSlots !== undefined
1474
- ? { contextSlots: [...view.contextSlots] }
1475
- : {}),
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),
1476
1481
  // Content-addressable contract validators. Both fields present
1477
1482
  // together or absent together — iframe-runtime treats absence
1478
1483
  // as "no validators".
@@ -0,0 +1,122 @@
1
+ /**
2
+ * `ggui_runtime_pull` — terminal bridge-pull rung of the live-channel
3
+ * failover ladder (WS → SSE → HTTP polling → bridge-pull).
4
+ *
5
+ * In a CSP-jailed MCP Apps host (claude.ai) the iframe can reach NO
6
+ * network origin — no WS, no SSE, no HTTP polling. The one channel it
7
+ * always has is the host's `tools/call` postMessage bridge. This tool
8
+ * serves the GguiSessionEvent ledger over that bridge: the
9
+ * `@ggui-ai/iframe-runtime` bridge rung pulls it on a flat interval,
10
+ * reusing the SAME cursor-walk algorithm (and the same parse core) as
11
+ * its HTTP `/events` polling rung, with `tools/call` as the carrier.
12
+ *
13
+ * **Wire shape** (what iframe-runtime postMessages via `tools/call`
14
+ * and the host relays to the MCP server):
15
+ *
16
+ * ```jsonc
17
+ * {
18
+ * "method": "tools/call",
19
+ * "params": {
20
+ * "name": "ggui_runtime_pull",
21
+ * "arguments": {
22
+ * "sessionId": "rnd_…", // bootstrap.sessionId
23
+ * "sinceSequence": 12, // replay cursor; omit = 0
24
+ * "limit": 100 // page size; clamped to 100
25
+ * }
26
+ * }
27
+ * }
28
+ * ```
29
+ *
30
+ * **Output = EXACT `EventsResponse` parity** with
31
+ * `GET /api/sessions/:sessionId/events` (`@ggui-ai/mcp-server`'s
32
+ * `api-renders-routes.ts`) — both read the ledger through the same
33
+ * `GguiSessionStore.listEventsSince` and answer with the same two
34
+ * shapes, so the client reuses one parse core across carriers:
35
+ *
36
+ * - Normal page: `{events, lastSequence, hasMore}` — events strictly
37
+ * ascending by seq with `seq > sinceSequence`, `lastSequence` = the
38
+ * render's high-water mark (advances the cursor even on an empty
39
+ * page), `hasMore` = the page was truncated by `limit` (client
40
+ * SHOULD immediately re-pull with the advanced cursor).
41
+ * - Horizon: `{reason: 'REPLAY_HORIZON_PASSED', currentSequence}` — a
42
+ * NORMAL result arm, not an error (the bridge rung is terminal;
43
+ * this is a re-sync instruction, mirroring the route's 410 body).
44
+ * Returned when the cursor fell out of the replayable window on
45
+ * EITHER side: `sinceSequence > lastSequence` (cursor from a
46
+ * different deployment / reset render) or
47
+ * `sinceSequence < horizonSeq` (events evicted from the bounded
48
+ * retention window). Client recovery: re-mount from a fresh
49
+ * snapshot and reset the cursor to `currentSequence`.
50
+ *
51
+ * **Failure modes:**
52
+ * - Unknown sessionId, cross-tenant sessionId, and a render deleted
53
+ * mid-read all surface uniformly as
54
+ * {@link GguiSessionNotFoundError} — no cross-tenant existence
55
+ * leak (tenancy gate: `renderStore.get` + `ctx.appId`).
56
+ * - Malformed input (empty sessionId, negative cursor, `limit < 1`)
57
+ * rejects at the zod boundary.
58
+ *
59
+ * **Visibility.** Registered with `_meta.ui.visibility: ['app']` so
60
+ * MCP Apps hosts route iframe-issued `tools/call` to this handler per
61
+ * spec §401 — REQUIRED: without it hosts reject view-issued calls, and
62
+ * the terminal rung goes dark exactly where it is the only channel
63
+ * left. Outer agents don't see the tool — ledger replay is a runtime
64
+ * concern, not an agent gesture.
65
+ *
66
+ * Deliberate divergences from the `/events` route, per the bridge-pull
67
+ * ruling: `sinceSequence` is optional (`?? 0` — the bridge rung owns
68
+ * its cursor), and `limit` above {@link RUNTIME_PULL_MAX_LIMIT} is
69
+ * CLAMPED, not rejected.
70
+ */
71
+ import { z } from 'zod';
72
+ import { type GguiRuntimePullOutput } from '@ggui-ai/protocol';
73
+ import type { GguiSessionStore } from '@ggui-ai/mcp-server-core';
74
+ import type { SharedHandler } from '../types.js';
75
+ declare const inputSchema: {
76
+ readonly sessionId: z.ZodString;
77
+ readonly sinceSequence: z.ZodOptional<z.ZodNumber>;
78
+ readonly limit: z.ZodOptional<z.ZodNumber>;
79
+ readonly wait: z.ZodOptional<z.ZodNumber>;
80
+ };
81
+ declare const outputSchema: {
82
+ /** Normal page only — ledger rows with `seq > sinceSequence`, ascending. */
83
+ readonly events: z.ZodOptional<z.ZodArray<z.ZodObject<{
84
+ seq: z.ZodNumber;
85
+ type: z.ZodString;
86
+ timestamp: z.ZodString;
87
+ data: z.ZodUnknown;
88
+ }, z.core.$strip>>>;
89
+ /** Normal page only — the render's current high-water mark (cursor floor on empty pages). */
90
+ readonly lastSequence: z.ZodOptional<z.ZodNumber>;
91
+ /** Normal page only — `true` when `limit` truncated the page; re-pull immediately. */
92
+ readonly hasMore: z.ZodOptional<z.ZodBoolean>;
93
+ /** Horizon arm only — the cursor fell out of the replayable window (normal result, not an error). */
94
+ readonly reason: z.ZodOptional<z.ZodLiteral<"REPLAY_HORIZON_PASSED">>;
95
+ /** Horizon arm only — reset the cursor here after re-mounting from a snapshot. */
96
+ readonly currentSequence: z.ZodOptional<z.ZodNumber>;
97
+ };
98
+ export interface GguiRuntimePullHandlerDeps {
99
+ /**
100
+ * The same store `ggui_render` commits to and the `/events` route
101
+ * reads from — one ledger, two carriers. The handler needs both
102
+ * `get` (tenancy gate) and `listEventsSince` (cursor read).
103
+ */
104
+ readonly renderStore: GguiSessionStore;
105
+ /**
106
+ * Subscription-mode hold probe cadence, in ms. During a `wait` hold
107
+ * the handler re-reads the ledger at this interval — deliberately a
108
+ * store poll rather than an in-process wake seam, because the
109
+ * update that ends the hold may land on a DIFFERENT pod (the store
110
+ * is the only cross-pod truth this package may assume). Defaults to
111
+ * 1000. Test hook: inject a small value for deterministic hold
112
+ * tests without fake timers.
113
+ */
114
+ readonly waitProbeIntervalMs?: number;
115
+ }
116
+ /**
117
+ * Build the `ggui_runtime_pull` handler. Registers as app-visible so
118
+ * MCP Apps hosts route iframe-issued `tools/call` to it per spec §401.
119
+ */
120
+ export declare function createGguiRuntimePullHandler(deps: GguiRuntimePullHandlerDeps): SharedHandler<typeof inputSchema, typeof outputSchema, GguiRuntimePullOutput>;
121
+ export {};
122
+ //# sourceMappingURL=runtime-pull.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime-pull.d.ts","sourceRoot":"","sources":["../../src/renders/runtime-pull.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqEG;AACH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAKL,KAAK,qBAAqB,EAC3B,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,KAAK,EAAkB,aAAa,EAAE,MAAM,aAAa,CAAC;AAKjE,QAAA,MAAM,WAAW;;;;;CAAwB,CAAC;AAQ1C,QAAA,MAAM,YAAY;IAChB,4EAA4E;;;;;;;IAE5E,6FAA6F;;IAE7F,sFAAsF;;IAEtF,qGAAqG;;IAErG,kFAAkF;;CAE1E,CAAC;AAEX,MAAM,WAAW,0BAA0B;IACzC;;;;OAIG;IACH,QAAQ,CAAC,WAAW,EAAE,gBAAgB,CAAC;IACvC;;;;;;;;OAQG;IACH,QAAQ,CAAC,mBAAmB,CAAC,EAAE,MAAM,CAAC;CACvC;AAED;;;GAGG;AACH,wBAAgB,4BAA4B,CAC1C,IAAI,EAAE,0BAA0B,GAC/B,aAAa,CAAC,OAAO,WAAW,EAAE,OAAO,YAAY,EAAE,qBAAqB,CAAC,CAkG/E"}