@ggui-ai/iframe-runtime 0.21.0 → 0.23.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/host-fonts.d.ts +7 -0
- package/dist/host-fonts.js +29 -5
- package/dist/iframe-runtime.js +62 -62
- package/dist/iframe-runtime.meta.json +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/meta-parse.d.ts +31 -0
- package/dist/meta-parse.js +52 -9
- package/dist/observability.d.ts +51 -1
- package/dist/protocol-error.d.ts +5 -2
- package/dist/react-renderer.js +5 -0
- package/dist/runtime.d.ts +11 -0
- package/dist/runtime.js +22 -5
- package/dist/types.d.ts +10 -6
- package/dist/wire-config.js +37 -0
- package/package.json +7 -7
package/dist/index.d.ts
CHANGED
|
@@ -37,7 +37,7 @@ export { parseMetaFromGlobal, parseMetaFromToolResult, validateMeta, } from './m
|
|
|
37
37
|
export type { RendererBootFailureReason, RendererBootFailedMessage, } from './runtime.js';
|
|
38
38
|
export type { ProtocolError, ProtocolErrorEmitter, BootstrapFailureReason, } from './protocol-error.js';
|
|
39
39
|
export { defaultProtocolErrorEmitter, fromBootstrapFailure, fromClientContractViolation, fromTransportFailure, fromAuthFailure, fromUpgradeRequired, fromUnknown, } from './protocol-error.js';
|
|
40
|
-
export type { ObservabilityEvent, ObservabilityEmitter, ObservabilityMessage, SchemaVersionMismatchEvent, FontFaceBlockedEvent, AppThemeInvalidEvent, AppThemeMemberStrippedEvent, ThemeReinjectFailedEvent, SubscribeFailedEvent, ChannelTransportPickedEvent, ChannelTransportFallbackEvent, ChannelTransportResubscribedEvent, ChannelPollDegradedEvent, ChannelPollRecoveredEvent, UiFeedbackEvent, RelayDeadTapEvent, RelayIncapabilityEvent, RelayLatchTrigger, UnknownObservabilityEvent, } from './observability.js';
|
|
40
|
+
export type { ObservabilityEvent, ObservabilityEmitter, ObservabilityMessage, SchemaVersionMismatchEvent, FontFaceBlockedEvent, AppThemeInvalidEvent, AppThemeMemberStrippedEvent, ActionSpecInvalidEvent, ActionSpecMemberStrippedEvent, SpentOneShotsInvalidEvent, ThemeReinjectFailedEvent, SubscribeFailedEvent, ChannelTransportPickedEvent, ChannelTransportFallbackEvent, ChannelTransportResubscribedEvent, ChannelPollDegradedEvent, ChannelPollRecoveredEvent, UiFeedbackEvent, RelayDeadTapEvent, RelayIncapabilityEvent, RelayLatchTrigger, ComponentEmptyEvent, OneShotUnenforceableEvent, UnknownObservabilityEvent, } from './observability.js';
|
|
41
41
|
export { isObservabilityMessage, isRelayDeadTapEvent, isRelayIncapabilityEvent, postObservabilityToParent, } from './observability.js';
|
|
42
42
|
export type { LifecycleEmitter } from './lifecycle.js';
|
|
43
43
|
export { postLifecycleToParent, makeLifecycleEvent } from './lifecycle.js';
|
package/dist/meta-parse.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { McpAppAiGguiRenderMeta } from '@ggui-ai/protocol/integrations/mcp-apps';
|
|
2
|
+
import { type ParseMcpAppAiGguiRenderMetaOptions } from '@ggui-ai/protocol/integrations/mcp-apps';
|
|
2
3
|
import type { McpAppAiGguiMetaParseResult } from './types.js';
|
|
3
4
|
/**
|
|
4
5
|
* Validate an {@link McpAppAiGguiRenderMeta} slice. Enforces mode
|
|
@@ -33,6 +34,36 @@ export declare function reportInvalidAppTheme(issues: readonly string[]): void;
|
|
|
33
34
|
* on every `parseMcpAppAiGguiRenderMeta` call in this package.
|
|
34
35
|
*/
|
|
35
36
|
export declare function reportStrippedThemeMembers(keys: readonly string[]): void;
|
|
37
|
+
/**
|
|
38
|
+
* The wire parser refused the slice's `actionSpec` (ggui#1178): report it
|
|
39
|
+
* to the embedding host as `action-spec-invalid` and mount without the
|
|
40
|
+
* action contract. Passed as `onInvalidActionSpec` on every
|
|
41
|
+
* `parseMcpAppAiGguiRenderMeta` call in this package.
|
|
42
|
+
*/
|
|
43
|
+
export declare function reportInvalidActionSpec(issues: readonly string[]): void;
|
|
44
|
+
/**
|
|
45
|
+
* The read door KEPT the slice's `actionSpec` but stripped entry members
|
|
46
|
+
* this release does not name (ggui#1178, VERSION-POLICY §3.6): report the
|
|
47
|
+
* `<action>.<member>` paths so a newer writer's member is visible, not
|
|
48
|
+
* swallowed. Passed as `onStrippedActionSpecMembers` on every
|
|
49
|
+
* `parseMcpAppAiGguiRenderMeta` call in this package.
|
|
50
|
+
*/
|
|
51
|
+
export declare function reportStrippedActionSpecMembers(keys: readonly string[]): void;
|
|
52
|
+
/**
|
|
53
|
+
* The wire parser refused the slice's `spentOneShots` (ggui#1223): report it
|
|
54
|
+
* to the embedding host as `spent-one-shots-invalid` and mount without it.
|
|
55
|
+
* The runtime keeps its in-memory guard alone, so a consumed action would
|
|
56
|
+
* look live again after a reload — never silently.
|
|
57
|
+
*/
|
|
58
|
+
export declare function reportInvalidSpentOneShots(issues: readonly string[]): void;
|
|
59
|
+
/**
|
|
60
|
+
* Every degradation the wire parser's read door can report, bound to its
|
|
61
|
+
* observability event — the ONE options object every
|
|
62
|
+
* `parseMcpAppAiGguiRenderMeta` call in this package passes, so a door
|
|
63
|
+
* that learns a new report is wired everywhere at once rather than at
|
|
64
|
+
* whichever call site someone remembered.
|
|
65
|
+
*/
|
|
66
|
+
export declare const READ_DOOR_REPORTERS: ParseMcpAppAiGguiRenderMetaOptions;
|
|
36
67
|
/**
|
|
37
68
|
* Parse the render slice from `globalThis.__GGUI_META__` — the
|
|
38
69
|
* synchronous self-contained shell delivery channel.
|
package/dist/meta-parse.js
CHANGED
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
* is structurally rejected by the combiner.
|
|
52
52
|
*/
|
|
53
53
|
import { PUBLIC_ENV_APP_KEY_RE } from '@ggui-ai/protocol/wire';
|
|
54
|
-
import { parseMcpAppAiGguiRenderMeta } from '@ggui-ai/protocol/integrations/mcp-apps';
|
|
54
|
+
import { parseMcpAppAiGguiRenderMeta, } from '@ggui-ai/protocol/integrations/mcp-apps';
|
|
55
55
|
import { postObservabilityToParent } from './observability.js';
|
|
56
56
|
/**
|
|
57
57
|
* Type guard — true iff `value` is a non-null, non-array plain object.
|
|
@@ -251,6 +251,13 @@ function projectMeta(meta, hasStaticContent) {
|
|
|
251
251
|
// Per-app theme overlay carried through verbatim. Already validated
|
|
252
252
|
// via `appThemeSchema` by the wire parser, so no re-validation here.
|
|
253
253
|
...(meta.theme !== undefined ? { theme: meta.theme } : {}),
|
|
254
|
+
// The slice's action contract (ggui#1178), carried through whole.
|
|
255
|
+
// Already validated at the wire parser's tolerant read door, so no
|
|
256
|
+
// re-validation here.
|
|
257
|
+
...(meta.actionSpec !== undefined ? { actionSpec: meta.actionSpec } : {}),
|
|
258
|
+
// The card's spent oneShot names (ggui#1223), carried through whole —
|
|
259
|
+
// validated at the wire parser's tolerant read door.
|
|
260
|
+
...(meta.spentOneShots !== undefined ? { spentOneShots: meta.spentOneShots } : {}),
|
|
254
261
|
...(gadgets !== undefined ? { gadgets } : {}),
|
|
255
262
|
...(publicEnv !== undefined ? { publicEnv } : {}),
|
|
256
263
|
...(streamWebSocketLocalTools !== undefined
|
|
@@ -345,6 +352,48 @@ export function reportInvalidAppTheme(issues) {
|
|
|
345
352
|
export function reportStrippedThemeMembers(keys) {
|
|
346
353
|
postObservabilityToParent({ kind: 'app-theme-member-stripped', keys: [...keys] });
|
|
347
354
|
}
|
|
355
|
+
/**
|
|
356
|
+
* The wire parser refused the slice's `actionSpec` (ggui#1178): report it
|
|
357
|
+
* to the embedding host as `action-spec-invalid` and mount without the
|
|
358
|
+
* action contract. Passed as `onInvalidActionSpec` on every
|
|
359
|
+
* `parseMcpAppAiGguiRenderMeta` call in this package.
|
|
360
|
+
*/
|
|
361
|
+
export function reportInvalidActionSpec(issues) {
|
|
362
|
+
postObservabilityToParent({ kind: 'action-spec-invalid', issues: [...issues] });
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* The read door KEPT the slice's `actionSpec` but stripped entry members
|
|
366
|
+
* this release does not name (ggui#1178, VERSION-POLICY §3.6): report the
|
|
367
|
+
* `<action>.<member>` paths so a newer writer's member is visible, not
|
|
368
|
+
* swallowed. Passed as `onStrippedActionSpecMembers` on every
|
|
369
|
+
* `parseMcpAppAiGguiRenderMeta` call in this package.
|
|
370
|
+
*/
|
|
371
|
+
export function reportStrippedActionSpecMembers(keys) {
|
|
372
|
+
postObservabilityToParent({ kind: 'action-spec-member-stripped', keys: [...keys] });
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* The wire parser refused the slice's `spentOneShots` (ggui#1223): report it
|
|
376
|
+
* to the embedding host as `spent-one-shots-invalid` and mount without it.
|
|
377
|
+
* The runtime keeps its in-memory guard alone, so a consumed action would
|
|
378
|
+
* look live again after a reload — never silently.
|
|
379
|
+
*/
|
|
380
|
+
export function reportInvalidSpentOneShots(issues) {
|
|
381
|
+
postObservabilityToParent({ kind: 'spent-one-shots-invalid', issues: [...issues] });
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Every degradation the wire parser's read door can report, bound to its
|
|
385
|
+
* observability event — the ONE options object every
|
|
386
|
+
* `parseMcpAppAiGguiRenderMeta` call in this package passes, so a door
|
|
387
|
+
* that learns a new report is wired everywhere at once rather than at
|
|
388
|
+
* whichever call site someone remembered.
|
|
389
|
+
*/
|
|
390
|
+
export const READ_DOOR_REPORTERS = {
|
|
391
|
+
onInvalidTheme: reportInvalidAppTheme,
|
|
392
|
+
onStrippedThemeMembers: reportStrippedThemeMembers,
|
|
393
|
+
onInvalidActionSpec: reportInvalidActionSpec,
|
|
394
|
+
onStrippedActionSpecMembers: reportStrippedActionSpecMembers,
|
|
395
|
+
onInvalidSpentOneShots: reportInvalidSpentOneShots,
|
|
396
|
+
};
|
|
348
397
|
/**
|
|
349
398
|
* Parse the render slice from `globalThis.__GGUI_META__` — the
|
|
350
399
|
* synchronous self-contained shell delivery channel.
|
|
@@ -367,10 +416,7 @@ export function parseMetaFromGlobal() {
|
|
|
367
416
|
if (!isPlainObject(raw)) {
|
|
368
417
|
return { ok: false, reason: 'MALFORMED_BOOTSTRAP' };
|
|
369
418
|
}
|
|
370
|
-
const parsed = parseMcpAppAiGguiRenderMeta(raw,
|
|
371
|
-
onInvalidTheme: reportInvalidAppTheme,
|
|
372
|
-
onStrippedThemeMembers: reportStrippedThemeMembers,
|
|
373
|
-
});
|
|
419
|
+
const parsed = parseMcpAppAiGguiRenderMeta(raw, READ_DOOR_REPORTERS);
|
|
374
420
|
if (!parsed.ok) {
|
|
375
421
|
return { ok: false, reason: 'MALFORMED_BOOTSTRAP' };
|
|
376
422
|
}
|
|
@@ -421,10 +467,7 @@ export function parseMetaFromToolResult(params) {
|
|
|
421
467
|
return { ok: false, reason: 'MISSING_META_GGUI_BOOTSTRAP' };
|
|
422
468
|
}
|
|
423
469
|
const meta = topMeta;
|
|
424
|
-
const parsed = parseMcpAppAiGguiRenderMeta(meta,
|
|
425
|
-
onInvalidTheme: reportInvalidAppTheme,
|
|
426
|
-
onStrippedThemeMembers: reportStrippedThemeMembers,
|
|
427
|
-
});
|
|
470
|
+
const parsed = parseMcpAppAiGguiRenderMeta(meta, READ_DOOR_REPORTERS);
|
|
428
471
|
if (!parsed.ok) {
|
|
429
472
|
return { ok: false, reason: 'MALFORMED_BOOTSTRAP' };
|
|
430
473
|
}
|
package/dist/observability.d.ts
CHANGED
|
@@ -33,7 +33,7 @@ import { MCP_APP_OBSERVE_TYPE } from '@ggui-ai/protocol/integrations/mcp-apps';
|
|
|
33
33
|
*
|
|
34
34
|
* @public
|
|
35
35
|
*/
|
|
36
|
-
export type ObservabilityEvent = SchemaVersionMismatchEvent | FontFaceBlockedEvent | AppThemeInvalidEvent | AppThemeMemberStrippedEvent | ThemeReinjectFailedEvent | SubscribeFailedEvent | ChannelTransportPickedEvent | ChannelTransportFallbackEvent | ChannelTransportResubscribedEvent | ChannelPollDegradedEvent | ChannelPollRecoveredEvent | UiFeedbackEvent | RelayIncapabilityEvent | RelayDeadTapEvent | ComponentEmptyEvent | UnknownObservabilityEvent;
|
|
36
|
+
export type ObservabilityEvent = SchemaVersionMismatchEvent | FontFaceBlockedEvent | AppThemeInvalidEvent | AppThemeMemberStrippedEvent | ActionSpecInvalidEvent | ActionSpecMemberStrippedEvent | OneShotUnenforceableEvent | SpentOneShotsInvalidEvent | ThemeReinjectFailedEvent | SubscribeFailedEvent | ChannelTransportPickedEvent | ChannelTransportFallbackEvent | ChannelTransportResubscribedEvent | ChannelPollDegradedEvent | ChannelPollRecoveredEvent | UiFeedbackEvent | RelayIncapabilityEvent | RelayDeadTapEvent | ComponentEmptyEvent | UnknownObservabilityEvent;
|
|
37
37
|
/**
|
|
38
38
|
* Fired when the protocol-version handshake rejects the connection.
|
|
39
39
|
* Parallel to `UpgradeRequiredError` — the host can choose to render
|
|
@@ -364,6 +364,56 @@ export interface AppThemeMemberStrippedEvent {
|
|
|
364
364
|
readonly kind: 'app-theme-member-stripped';
|
|
365
365
|
readonly keys: readonly string[];
|
|
366
366
|
}
|
|
367
|
+
/**
|
|
368
|
+
* The render slice carried an `actionSpec` the wire schema refused
|
|
369
|
+
* (ggui#1178): the parser dropped it and the card mounted without the
|
|
370
|
+
* action contract. `issues` are the schema's own messages — the
|
|
371
|
+
* operator's pointer to the writer that projected a malformed contract.
|
|
372
|
+
*
|
|
373
|
+
* @public
|
|
374
|
+
*/
|
|
375
|
+
export interface ActionSpecInvalidEvent {
|
|
376
|
+
readonly kind: 'action-spec-invalid';
|
|
377
|
+
readonly issues: readonly string[];
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* The render slice's `actionSpec` carried entry members THIS release does
|
|
381
|
+
* not name (ggui#1178, VERSION-POLICY §3.6): the read door kept every
|
|
382
|
+
* action and stripped those members, and the card says which. `keys` are
|
|
383
|
+
* `<action>.<member>` paths only, never their content.
|
|
384
|
+
*
|
|
385
|
+
* @public
|
|
386
|
+
*/
|
|
387
|
+
export interface ActionSpecMemberStrippedEvent {
|
|
388
|
+
readonly kind: 'action-spec-member-stripped';
|
|
389
|
+
readonly keys: readonly string[];
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* A component render dispatched with no action contract on either carrier
|
|
393
|
+
* (ggui#1178, SPEC §5.5.1): the one-shot guard cannot know whether the
|
|
394
|
+
* action is `oneShot`, so the dispatch proceeds unguarded. Posted once per
|
|
395
|
+
* render, on its first such dispatch. `actionName` is the action that
|
|
396
|
+
* dispatched, never its payload.
|
|
397
|
+
*
|
|
398
|
+
* @public
|
|
399
|
+
*/
|
|
400
|
+
export interface OneShotUnenforceableEvent {
|
|
401
|
+
readonly kind: 'one-shot-unenforceable';
|
|
402
|
+
readonly renderId: string;
|
|
403
|
+
readonly actionName: string;
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* The render slice carried a `spentOneShots` the wire schema refused
|
|
407
|
+
* (ggui#1223): the parser dropped it, and the card mounted with only its
|
|
408
|
+
* in-memory one-shot guard — a consumed action reads as live again after a
|
|
409
|
+
* reload. `issues` are the schema's own messages.
|
|
410
|
+
*
|
|
411
|
+
* @public
|
|
412
|
+
*/
|
|
413
|
+
export interface SpentOneShotsInvalidEvent {
|
|
414
|
+
readonly kind: 'spent-one-shots-invalid';
|
|
415
|
+
readonly issues: readonly string[];
|
|
416
|
+
}
|
|
367
417
|
/**
|
|
368
418
|
* The live re-injection on a host theme flip (ggui#987 §4) failed: the
|
|
369
419
|
* renderer's update rejected, so the card kept its previous overlay /
|
package/dist/protocol-error.d.ts
CHANGED
|
@@ -38,8 +38,11 @@ import { ClientContractViolationError } from '@ggui-ai/wire';
|
|
|
38
38
|
* `McpAppAiGguiMetaParseFailureReason` (`MISSING_TOOL_OUTPUT` /
|
|
39
39
|
* `MISSING_META_GGUI_BOOTSTRAP` / `MALFORMED_BOOTSTRAP` /
|
|
40
40
|
* `EXPIRED_BOOTSTRAP`). `MISSING_META_GGUI_BOOTSTRAP` and
|
|
41
|
-
* `BOOTSTRAP_META_MISSING`
|
|
42
|
-
*
|
|
41
|
+
* `BOOTSTRAP_META_MISSING` name one condition. The runtime and the
|
|
42
|
+
* reference shell emit `MISSING_META_GGUI_BOOTSTRAP` on
|
|
43
|
+
* `ggui:bootstrap-failed`; `BOOTSTRAP_META_MISSING` stays a member
|
|
44
|
+
* so a host classifies either spelling as that condition
|
|
45
|
+
* (SPEC §5.5.2).
|
|
43
46
|
*
|
|
44
47
|
* 2. **Post-parse orchestration failures** — `UI_INITIALIZE_FAILED`
|
|
45
48
|
* / `WS_HANDSHAKE_FAILED`. Matches `RendererBootFailureReason`.
|
package/dist/react-renderer.js
CHANGED
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
import React, { Component, createElement, Fragment, } from 'react';
|
|
33
33
|
import { createRoot } from 'react-dom/client';
|
|
34
34
|
import { postObservabilityToParent } from './observability.js';
|
|
35
|
+
import { trackThemeFontFaces } from './host-fonts.js';
|
|
35
36
|
import { stripMarkers, rewriteImports, composeThemeCss, } from '@ggui-ai/design/rendering';
|
|
36
37
|
import { hoistImports, loadModule, loadModuleInline, probeUrlModuleLoad, } from '@ggui-ai/design/module-loader';
|
|
37
38
|
/**
|
|
@@ -367,6 +368,10 @@ export async function mountReactRoot(container, opts) {
|
|
|
367
368
|
if (styleEl.textContent !== rootCss) {
|
|
368
369
|
styleEl.textContent = rootCss;
|
|
369
370
|
}
|
|
371
|
+
// ggui#1147: the chrome CSS carries the stored theme's @font-face rules,
|
|
372
|
+
// mid-session included, while `font-src` was fixed at mount. Track them so
|
|
373
|
+
// a blocked face is reported as `font-face-blocked`, not lost silently.
|
|
374
|
+
trackThemeFontFaces(rootCss);
|
|
370
375
|
}
|
|
371
376
|
const componentElement = currentComponent === null
|
|
372
377
|
? null
|
package/dist/runtime.d.ts
CHANGED
|
@@ -524,6 +524,17 @@ export declare function readSelfContainedMeta(): McpAppAiGguiRenderMeta | null;
|
|
|
524
524
|
* surface a typed boot failure. The four server-assigned ledger fields
|
|
525
525
|
* are intentionally absent — the first ack reconciles the seed to a
|
|
526
526
|
* full `GguiSession` (no fabrication).
|
|
527
|
+
*
|
|
528
|
+
* A component seed carries the slice's `actionSpec` when the slice has one
|
|
529
|
+
* (ggui#1178): a mount with no live session frame paints from this seed, and
|
|
530
|
+
* the one-shot guard reads the current render's spec, so a seed without it
|
|
531
|
+
* could not enforce `oneShot`. A system seed never carries one.
|
|
532
|
+
*
|
|
533
|
+
* It also carries the card's `epoch` and, when the slice lists them, the
|
|
534
|
+
* `oneShot` names this card already spent, as the render's record
|
|
535
|
+
* `{ epoch, actions }` (ggui#1223). `deriveRenderMeta` projected those names
|
|
536
|
+
* for this card only, so the record's epoch is the card's own and the guard
|
|
537
|
+
* stops the first gesture on a spent action after a reload.
|
|
527
538
|
*/
|
|
528
539
|
export declare function buildGguiSessionSeedInput(meta: McpAppAiGguiRenderMeta): Promise<GguiSessionSeedInput | null>;
|
|
529
540
|
/**
|
package/dist/runtime.js
CHANGED
|
@@ -30,7 +30,7 @@ import './zod-jitless.js';
|
|
|
30
30
|
import { runtimeConnectionWriter } from './connection.js';
|
|
31
31
|
import { MCP_APP_BOOTSTRAP_FAILED_TYPE, MCP_APP_RENDERER_READY_TYPE, parseMcpAppAiGguiRenderMeta, readGguiShellEnvelope, } from '@ggui-ai/protocol/integrations/mcp-apps';
|
|
32
32
|
import { effectiveThemeId, effectiveThemeMode, } from '@ggui-ai/protocol/integrations/theme-binding';
|
|
33
|
-
import { extractLocatorFromToolResult, parseMetaFromGlobal, parseMetaFromToolResult, validateMeta,
|
|
33
|
+
import { extractLocatorFromToolResult, parseMetaFromGlobal, parseMetaFromToolResult, validateMeta, READ_DOOR_REPORTERS, } from './meta-parse.js';
|
|
34
34
|
import { projectHostContext } from '@ggui-ai/protocol/wire';
|
|
35
35
|
import { installDismissIntentListener } from './dismiss-intent.js';
|
|
36
36
|
import { App, PostMessageTransport } from '@modelcontextprotocol/ext-apps';
|
|
@@ -1175,9 +1175,27 @@ function parseSeedProps(propsJson) {
|
|
|
1175
1175
|
* surface a typed boot failure. The four server-assigned ledger fields
|
|
1176
1176
|
* are intentionally absent — the first ack reconciles the seed to a
|
|
1177
1177
|
* full `GguiSession` (no fabrication).
|
|
1178
|
+
*
|
|
1179
|
+
* A component seed carries the slice's `actionSpec` when the slice has one
|
|
1180
|
+
* (ggui#1178): a mount with no live session frame paints from this seed, and
|
|
1181
|
+
* the one-shot guard reads the current render's spec, so a seed without it
|
|
1182
|
+
* could not enforce `oneShot`. A system seed never carries one.
|
|
1183
|
+
*
|
|
1184
|
+
* It also carries the card's `epoch` and, when the slice lists them, the
|
|
1185
|
+
* `oneShot` names this card already spent, as the render's record
|
|
1186
|
+
* `{ epoch, actions }` (ggui#1223). `deriveRenderMeta` projected those names
|
|
1187
|
+
* for this card only, so the record's epoch is the card's own and the guard
|
|
1188
|
+
* stops the first gesture on a spent action after a reload.
|
|
1178
1189
|
*/
|
|
1179
1190
|
export async function buildGguiSessionSeedInput(meta) {
|
|
1180
1191
|
const props = parseSeedProps(meta.propsJson);
|
|
1192
|
+
const componentFields = {
|
|
1193
|
+
...(meta.actionSpec !== undefined ? { actionSpec: meta.actionSpec } : {}),
|
|
1194
|
+
...(typeof meta.epoch === 'number' ? { epoch: meta.epoch } : {}),
|
|
1195
|
+
...(meta.spentOneShots !== undefined
|
|
1196
|
+
? { spentOneShots: { epoch: meta.epoch ?? 0, actions: meta.spentOneShots } }
|
|
1197
|
+
: {}),
|
|
1198
|
+
};
|
|
1181
1199
|
// System-card mode — `kind` keyed against the built-in registry.
|
|
1182
1200
|
if (meta.kind !== undefined) {
|
|
1183
1201
|
return {
|
|
@@ -1195,6 +1213,7 @@ export async function buildGguiSessionSeedInput(meta) {
|
|
|
1195
1213
|
appId: meta.appId,
|
|
1196
1214
|
componentCode: decodeCodeB64(meta.codeB64),
|
|
1197
1215
|
...(props !== undefined ? { props } : {}),
|
|
1216
|
+
...componentFields,
|
|
1198
1217
|
};
|
|
1199
1218
|
}
|
|
1200
1219
|
// Compiled-component mode — fetch the content-addressable bytes.
|
|
@@ -1210,6 +1229,7 @@ export async function buildGguiSessionSeedInput(meta) {
|
|
|
1210
1229
|
appId: meta.appId,
|
|
1211
1230
|
componentCode,
|
|
1212
1231
|
...(props !== undefined ? { props } : {}),
|
|
1232
|
+
...componentFields,
|
|
1213
1233
|
};
|
|
1214
1234
|
}
|
|
1215
1235
|
/**
|
|
@@ -1398,10 +1418,7 @@ export async function resolveMetaViaReadDoor(app, locator) {
|
|
|
1398
1418
|
const envelope = readGguiShellEnvelope(text);
|
|
1399
1419
|
if (envelope === undefined)
|
|
1400
1420
|
return null;
|
|
1401
|
-
const parsed = parseMcpAppAiGguiRenderMeta(envelope,
|
|
1402
|
-
onInvalidTheme: reportInvalidAppTheme,
|
|
1403
|
-
onStrippedThemeMembers: reportStrippedThemeMembers,
|
|
1404
|
-
});
|
|
1421
|
+
const parsed = parseMcpAppAiGguiRenderMeta(envelope, READ_DOOR_REPORTERS);
|
|
1405
1422
|
if (!parsed.ok || parsed.meta === undefined)
|
|
1406
1423
|
return null;
|
|
1407
1424
|
const validated = validateMeta(parsed.meta);
|
package/dist/types.d.ts
CHANGED
|
@@ -37,9 +37,12 @@ type GguiSessionLedgerFields = 'eventSequence' | 'createdAt' | 'lastActivityAt'
|
|
|
37
37
|
* the seed with the authoritative `GguiSession`.
|
|
38
38
|
*
|
|
39
39
|
* Carries the SAME contract-spec fields a full `ComponentGguiSession` does
|
|
40
|
-
* (`propsSpec` / `streamSpec` / `actionSpec`),
|
|
41
|
-
*
|
|
42
|
-
*
|
|
40
|
+
* (`propsSpec` / `streamSpec` / `actionSpec`), so the channel handlers that
|
|
41
|
+
* read those after the `type !== 'mcpApps' && type !== 'system'` narrowing
|
|
42
|
+
* stay type-clean. On a seed, `propsSpec` and `streamSpec` are `undefined`;
|
|
43
|
+
* `actionSpec` is the slice's own when the slice carried one (ggui#1178), and
|
|
44
|
+
* so are `epoch` and the spent-`oneShot` record built from the slice's
|
|
45
|
+
* `spentOneShots` (ggui#1223).
|
|
43
46
|
*/
|
|
44
47
|
export type GguiSessionSeedInput = Omit<ComponentGguiSession, GguiSessionLedgerFields> | Omit<SystemGguiSession, GguiSessionLedgerFields>;
|
|
45
48
|
/**
|
|
@@ -58,9 +61,10 @@ export type GguiSessionSeedInput = Omit<ComponentGguiSession, GguiSessionLedgerF
|
|
|
58
61
|
* deferred.)
|
|
59
62
|
* - `MISSING_META_GGUI_BOOTSTRAP` — `params` is an object but
|
|
60
63
|
* `params._meta` is absent or carries no `ai.ggui/render` slice.
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
+
* This is the spelling emitted on `ggui:bootstrap-failed`;
|
|
65
|
+
* `BOOTSTRAP_META_MISSING` is a synonym a host classifies alike
|
|
66
|
+
* (SPEC §5.5.2). (Name retained pre-R4 for the observability +
|
|
67
|
+
* host-postMessage protocol; cosmetic rename is deferred.)
|
|
64
68
|
* - `MALFORMED_BOOTSTRAP` — slice is present but fails business
|
|
65
69
|
* rules (no live mode and no static content, mode discriminators
|
|
66
70
|
* mutually exclusive, structural rejection by the combiner).
|
package/dist/wire-config.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { buildWireConfig, StreamBus, } from '@ggui-ai/wire';
|
|
2
2
|
import { validateOutboundActionEnvelope } from './validation.js';
|
|
3
3
|
import { defaultProtocolErrorEmitter, fromClientContractViolation, } from './protocol-error.js';
|
|
4
|
+
import { postObservabilityToParent } from './observability.js';
|
|
4
5
|
// `StreamBus` (and its bounded reserved-channel replay ring) lives in
|
|
5
6
|
// `@ggui-ai/wire` (one implementation for every first-party renderer).
|
|
6
7
|
// It was originally hoisted to share with `<GguiRender>` — that
|
|
@@ -45,6 +46,27 @@ export function buildRootWireConfig(opts) {
|
|
|
45
46
|
}
|
|
46
47
|
emitProtocolError(fromClientContractViolation(err));
|
|
47
48
|
}
|
|
49
|
+
// ggui#1178 — a card paints and takes gestures even when no action spec
|
|
50
|
+
// reached it (a mount past the WS token's TTL, a host with no live channel,
|
|
51
|
+
// an older server that never put the spec on the render slice). The one-shot
|
|
52
|
+
// guard then cannot know an action is `oneShot` and fails open. The runtime's
|
|
53
|
+
// obligation is to NAME that, never silently: once per component render, as
|
|
54
|
+
// the `one-shot-unenforceable` observability event posted to the embedding
|
|
55
|
+
// host (the channel every other degradation in this runtime uses), and the
|
|
56
|
+
// dispatch proceeds. It is named on the first spec-less DISPATCH, not at
|
|
57
|
+
// paint: an absent spec also means "this card declares no actions", so a
|
|
58
|
+
// paint-time name would fire on every display-only card. System and mcpApps
|
|
59
|
+
// renders carry no spec by design and are never named.
|
|
60
|
+
let namedUnenforceableFor = null;
|
|
61
|
+
const nameUnenforceableOnce = (actionName) => {
|
|
62
|
+
const currentRender = opts.getCurrentGguiSession();
|
|
63
|
+
if (currentRender === null || currentRender.type === 'mcpApps' || currentRender.type === 'system')
|
|
64
|
+
return;
|
|
65
|
+
if (namedUnenforceableFor === currentRender.id)
|
|
66
|
+
return;
|
|
67
|
+
namedUnenforceableFor = currentRender.id;
|
|
68
|
+
postObservabilityToParent({ kind: 'one-shot-unenforceable', renderId: currentRender.id, actionName });
|
|
69
|
+
};
|
|
48
70
|
return buildWireConfig({
|
|
49
71
|
app: { appId: opts.appId, appName: opts.appId },
|
|
50
72
|
// `isConnected` here is the static config field; `useRender()`
|
|
@@ -64,6 +86,21 @@ export function buildRootWireConfig(opts) {
|
|
|
64
86
|
? currentRender.actionSpec
|
|
65
87
|
: undefined;
|
|
66
88
|
},
|
|
89
|
+
onActionSpecAbsent: nameUnenforceableOnce,
|
|
90
|
+
// ggui#1223 — the card's persisted spent `oneShot` names. A re-served card
|
|
91
|
+
// boots a new iframe, so the guard's in-memory set starts empty; the
|
|
92
|
+
// render's record says what this card already spent. It counts only when
|
|
93
|
+
// written on THIS card (its epoch is the render's own; absent ⇒ 0), the
|
|
94
|
+
// same gate `deriveRenderMeta` applies, since a `ggui_update` mints a fresh
|
|
95
|
+
// card whose record would otherwise carry over.
|
|
96
|
+
getSpentOneShots: () => {
|
|
97
|
+
const currentRender = opts.getCurrentGguiSession();
|
|
98
|
+
if (currentRender === null || currentRender.type === 'mcpApps' || currentRender.type === 'system') {
|
|
99
|
+
return undefined;
|
|
100
|
+
}
|
|
101
|
+
const record = currentRender.spentOneShots;
|
|
102
|
+
return record !== undefined && record.epoch === (currentRender.epoch ?? 0) ? record.actions : undefined;
|
|
103
|
+
},
|
|
67
104
|
// The iframe's precompiled-validator variant — the dispatch never
|
|
68
105
|
// trips the iframe's no-`unsafe-eval` CSP.
|
|
69
106
|
validateEnvelope: validateOutboundActionEnvelope,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ggui-ai/iframe-runtime",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.23.0",
|
|
4
4
|
"description": "The iframe-local ggui runtime: a self-contained ESM bundle that boots inside an MCP Apps host iframe, runs the protocol-version handshake, opens the live-channel WebSocket, and mounts the rendered UI. Bundles React, ReactDOM, and the ggui design system so generated component code has a single seam to read from.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ggui",
|
|
@@ -41,12 +41,12 @@
|
|
|
41
41
|
"react": "^19.0.0",
|
|
42
42
|
"react-dom": "^19.0.0",
|
|
43
43
|
"zod": "^4.3.6",
|
|
44
|
-
"@ggui-ai/
|
|
45
|
-
"@ggui-ai/
|
|
46
|
-
"@ggui-ai/
|
|
47
|
-
"@ggui-ai/
|
|
48
|
-
"@ggui-ai/
|
|
49
|
-
"@ggui-ai/
|
|
44
|
+
"@ggui-ai/live-channel": "0.23.0",
|
|
45
|
+
"@ggui-ai/preview-a2ui": "0.23.0",
|
|
46
|
+
"@ggui-ai/protocol": "0.23.0",
|
|
47
|
+
"@ggui-ai/gadgets": "0.23.0",
|
|
48
|
+
"@ggui-ai/design": "0.23.0",
|
|
49
|
+
"@ggui-ai/wire": "0.23.0"
|
|
50
50
|
},
|
|
51
51
|
"peerDependencies": {
|
|
52
52
|
"@modelcontextprotocol/sdk": "^1.27.0"
|