@ggui-ai/iframe-runtime 0.7.0 → 0.8.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.
@@ -11,9 +11,9 @@ import type { McpAppAiGguiMetaParseResult } from './types.js';
11
11
  * direct-call callers like `parseMetaFromGlobal` / tests; the
12
12
  * envelope-driven extractors already enforce this in the protocol-
13
13
  * level parser).
14
- * - At least one of `{live mode (wsUrl+wsToken), codeUrl, kind}` MUST
15
- * be present. Without any mode discriminator the iframe has nothing
16
- * to mount.
14
+ * - At least one of `{live mode (wsUrl+wsToken), codeUrl, codeB64,
15
+ * kind}` MUST be present. Without any mode discriminator the iframe
16
+ * has nothing to mount.
17
17
  */
18
18
  export declare function validateMeta(meta: McpAppAiGguiRenderMeta): McpAppAiGguiMetaParseResult;
19
19
  /**
@@ -40,9 +40,10 @@
40
40
  *
41
41
  * - **live** — slice has `wsUrl` + `wsToken`; runtime opens
42
42
  * live-channel WS and renders agent-driven frames.
43
- * - **static-component** — slice has `codeUrl`; runtime fetches
44
- * the content-addressable URL, dynamic-imports the React
45
- * component, mounts it, never opens WS.
43
+ * - **static-component** — slice has `codeUrl` (runtime fetches the
44
+ * content-addressable URL) and/or `codeB64` (runtime decodes the
45
+ * inline bytes, no fetch); mounts the React component, never
46
+ * opens WS.
46
47
  * - **system-card** — slice has `kind`; runtime maps to a
47
48
  * built-in component via the system-card registry.
48
49
  *
@@ -177,9 +178,9 @@ function projectPublicEnv(raw) {
177
178
  *
178
179
  * Invariants enforced here that the combiner doesn't:
179
180
  * - `expiresAt` (when present) MUST parse to a valid timestamp. If
180
- * in the past AND the slice carries static content (codeUrl / kind),
181
- * DEGRADE to static-only (drop `wsUrl` / `wsToken` / `expiresAt`);
182
- * otherwise return EXPIRED_BOOTSTRAP.
181
+ * in the past AND the slice carries static content (codeUrl /
182
+ * codeB64 / kind), DEGRADE to static-only (drop `wsUrl` /
183
+ * `wsToken` / `expiresAt`); otherwise return EXPIRED_BOOTSTRAP.
183
184
  * - `gadgets` / `publicEnv` / `permissionsPolicy` /
184
185
  * `streamWebSocketLocalTools` / `contextSlots` get defensive
185
186
  * entry-level validation; malformed → defaulted.
@@ -260,6 +261,7 @@ function projectMeta(meta, hasStaticContent) {
260
261
  : {}),
261
262
  ...(meta.codeUrl !== undefined ? { codeUrl: meta.codeUrl } : {}),
262
263
  ...(meta.codeHash !== undefined ? { codeHash: meta.codeHash } : {}),
264
+ ...(meta.codeB64 !== undefined ? { codeB64: meta.codeB64 } : {}),
263
265
  ...(meta.kind !== undefined ? { kind: meta.kind } : {}),
264
266
  };
265
267
  return { ok: true, meta: projected };
@@ -275,9 +277,9 @@ function projectMeta(meta, hasStaticContent) {
275
277
  * direct-call callers like `parseMetaFromGlobal` / tests; the
276
278
  * envelope-driven extractors already enforce this in the protocol-
277
279
  * level parser).
278
- * - At least one of `{live mode (wsUrl+wsToken), codeUrl, kind}` MUST
279
- * be present. Without any mode discriminator the iframe has nothing
280
- * to mount.
280
+ * - At least one of `{live mode (wsUrl+wsToken), codeUrl, codeB64,
281
+ * kind}` MUST be present. Without any mode discriminator the iframe
282
+ * has nothing to mount.
281
283
  */
282
284
  export function validateMeta(meta) {
283
285
  // `runtimeUrl` is a hard wire-required field on every render slice
@@ -292,8 +294,9 @@ export function validateMeta(meta) {
292
294
  }
293
295
  const hasLive = isNonEmptyString(meta.wsUrl) && isNonEmptyString(meta.wsToken);
294
296
  const hasCodeUrl = isNonEmptyString(meta.codeUrl);
297
+ const hasCodeB64 = isNonEmptyString(meta.codeB64);
295
298
  const hasKind = isNonEmptyString(meta.kind);
296
- const hasStaticContent = hasCodeUrl || hasKind;
299
+ const hasStaticContent = hasCodeUrl || hasCodeB64 || hasKind;
297
300
  if (!hasLive && !hasStaticContent) {
298
301
  return { ok: false, reason: 'MALFORMED_BOOTSTRAP' };
299
302
  }
@@ -32,7 +32,18 @@
32
32
  import React, { Component, createElement, Fragment, } from 'react';
33
33
  import { createRoot } from 'react-dom/client';
34
34
  import { stripMarkers, rewriteImports, getScopedThemeCss, getScopedCssTokens, getThemeCss, getCssTokens, } from '@ggui-ai/design/rendering';
35
- import { hoistImports, loadModule } from '@ggui-ai/design/module-loader';
35
+ import { hoistImports, loadModule, loadModuleInline, probeUrlModuleLoad, } from '@ggui-ai/design/module-loader';
36
+ /**
37
+ * Document-lifetime verdict: URL-scheme module loading (`blob:` main
38
+ * module + `data:` import shims) is blocked by the host CSP. Set only
39
+ * when the inline path succeeded where the URL path failed AND the
40
+ * known-good `probeUrlModuleLoad()` module ALSO fails — an arbitrary
41
+ * module's failure is not CSP evidence (an import-shim allowlist miss
42
+ * rejects blob-instantiation of that one module while URL loading is
43
+ * healthy, and latching on it would force every later evaluation in
44
+ * the document onto the inline path's narrower semantics).
45
+ */
46
+ let urlModuleLoadBlocked = false;
36
47
  // =============================================================================
37
48
  // Scope class helpers — port of RCR's `makeScopeClass` + counter.
38
49
  // =============================================================================
@@ -177,25 +188,66 @@ export async function mountReactRoot(container, opts) {
177
188
  }
178
189
  // 1. Strip metadata markers (__GGUI_META__ / __GGUI_STREAM_SPEC__).
179
190
  const cleaned = stripMarkers(resolved);
180
- // 2. Hoist imports above var declarations.
191
+ // 2. Hoist imports above var declarations. Load-bearing for BOTH
192
+ // execution paths: the inline transform places its bindings at
193
+ // the import statement's position, so a pre-import statement
194
+ // referencing an imported name needs the hoist exactly as the
195
+ // real module semantics do.
181
196
  const hoisted = hoistImports(cleaned);
182
- // 3. Rewrite bare specifiers to data-url shims reading from
183
- // `globalThis.__ggui__`. The __ggui__ registry is installed
184
- // by `runtime.ts::bootSequence` BEFORE any mountReactRoot call
185
- // — see globals.ts docstring on TOCTOU ordering.
186
- const rewritten = rewriteImports(hoisted, {
187
- mode: 'data-url',
188
- gadgetPackages: currentOpts.gadgetPackages,
189
- });
190
- // 4. Dynamically import the module.
191
- const mod = await loadModule(rewritten);
192
- // 5. Extract default export (or first function export).
193
- const Comp = (mod.default ??
194
- Object.values(mod).find((v) => typeof v === 'function'));
195
- if (Comp === undefined) {
196
- throw new Error('Module does not export a default component');
197
+ const pick = (mod) => {
198
+ const Comp = (mod.default ??
199
+ Object.values(mod).find((v) => typeof v === 'function'));
200
+ if (Comp === undefined) {
201
+ throw new Error('Module does not export a default component');
202
+ }
203
+ return Comp;
204
+ };
205
+ const evaluateInline = () => pick(loadModuleInline(hoisted, {
206
+ ...(currentOpts.gadgetPackages !== undefined
207
+ ? { gadgetPackages: currentOpts.gadgetPackages }
208
+ : {}),
209
+ }));
210
+ // Once URL-scheme module loading has been proven blocked in this
211
+ // document, go straight to inline execution — the CSP verdict
212
+ // cannot change within the document's lifetime.
213
+ if (urlModuleLoadBlocked) {
214
+ return evaluateInline();
215
+ }
216
+ try {
217
+ // 3. Rewrite bare specifiers to data-url shims reading from
218
+ // `globalThis.__ggui__`. The __ggui__ registry is installed
219
+ // by `runtime.ts::bootSequence` BEFORE any mountReactRoot call
220
+ // — see globals.ts docstring on TOCTOU ordering.
221
+ const rewritten = rewriteImports(hoisted, {
222
+ mode: 'data-url',
223
+ gadgetPackages: currentOpts.gadgetPackages,
224
+ });
225
+ // 4. Dynamically import the module.
226
+ const mod = await loadModule(rewritten);
227
+ // 5. Extract default export (or first function export).
228
+ return pick(mod);
229
+ }
230
+ catch (urlError) {
231
+ // A host CSP granting only 'unsafe-inline' rejects the blob:/
232
+ // data: imports above. Attempt the inline classic-script path;
233
+ // a buggy component fails on both paths, and the original error
234
+ // is the honest one to surface.
235
+ try {
236
+ const Comp = evaluateInline();
237
+ // Latch the inline path for the rest of the document ONLY on
238
+ // proven CSP evidence: the known-good probe module must also
239
+ // fail. A module-specific blob failure (import-shim allowlist
240
+ // drift) leaves the latch off so the next evaluation gets real
241
+ // module semantics again.
242
+ if (!(await probeUrlModuleLoad())) {
243
+ urlModuleLoadBlocked = true;
244
+ }
245
+ return Comp;
246
+ }
247
+ catch {
248
+ throw urlError;
249
+ }
197
250
  }
198
- return Comp;
199
251
  }
200
252
  function renderTree(opts) {
201
253
  const themeCss = opts.themeId
package/dist/runtime.d.ts CHANGED
@@ -342,18 +342,39 @@ export declare function readSelfContainedMeta(): McpAppAiGguiRenderMeta | null;
342
342
  * no WS at all for spec-compliant MCP-Apps hosts that expose no ggui
343
343
  * live channel (claude.ai / ChatGPT / Claude Desktop).
344
344
  *
345
- * Two static-content shapes (the autostart discriminator):
345
+ * Three static-content shapes (the autostart discriminator), in
346
+ * priority order:
346
347
  * - `kind` → a system-card seed (`type:'system'`); no fetch.
348
+ * - `codeB64` → a compiled-component seed carried inline; DECODED,
349
+ * never fetched. Preferred over `codeUrl` when both are present —
350
+ * the inline bytes exist precisely for hosts whose iframe CSP
351
+ * blocks the fetch.
347
352
  * - `codeUrl` → a compiled-component seed; fetches the
348
353
  * content-addressable component bytes.
349
354
  *
350
- * Returns `null` when the meta carries NEITHER (a live-only meta has
355
+ * Returns `null` when the meta carries NONE (a live-only meta has
351
356
  * nothing to seed — the first WS ack mounts it). Throws when a `codeUrl`
352
- * fetch fails so the caller can surface a typed boot failure. The four
353
- * server-assigned ledger fields are intentionally absent — the first
354
- * ack reconciles the seed to a full `GguiSession` (no fabrication).
357
+ * fetch fails or `codeB64` is not valid base64 so the caller can
358
+ * surface a typed boot failure. The four server-assigned ledger fields
359
+ * are intentionally absent — the first ack reconciles the seed to a
360
+ * full `GguiSession` (no fabrication).
355
361
  */
356
362
  export declare function buildGguiSessionSeedInput(meta: McpAppAiGguiRenderMeta): Promise<GguiSessionSeedInput | null>;
363
+ /**
364
+ * Does this slice carry STATIC content the mount surface can paint
365
+ * without any network round-trip authorization — `codeUrl` (fetched),
366
+ * `codeB64` (decoded inline), or `kind` (built-in registry)?
367
+ *
368
+ * The ONE discriminator `bootSequence` consults when deciding whether
369
+ * the static seed-mount path runs. `codeB64` counting here is
370
+ * load-bearing for fetch-blocked hosts: their meta carries codeB64
371
+ * PLUS a live trio the iframe can never connect, and missing the
372
+ * inline arm would skip the only paint path and die on the dead WS.
373
+ * Mirrors `validateMeta`'s static-content arm (meta-parse.ts) and the
374
+ * protocol host-helper's `hasMountModeDiscriminator` — drift among
375
+ * the three IS the historical bug class, hence the named export.
376
+ */
377
+ export declare function hasStaticContentMeta(meta: McpAppAiGguiRenderMeta): boolean;
357
378
  /**
358
379
  * Extract a {@link McpAppAiGguiRenderMeta} from a
359
380
  * `ui/notifications/tool-result` JSON-RPC params payload — the
@@ -369,6 +390,25 @@ export declare function buildGguiSessionSeedInput(meta: McpAppAiGguiRenderMeta):
369
390
  * impossible because the shell never inspects the slice-meta shape.
370
391
  */
371
392
  export declare function extractMetaFromToolResult(params: unknown): McpAppAiGguiRenderMeta | null;
393
+ /**
394
+ * Drain `window.__GGUI_PENDING_TOOL_RESULTS__` — the buffer the
395
+ * minimal shell populates while messages arrive between
396
+ * shell-load and runtime-load. Returns the NEWEST valid slice meta
397
+ * (each new tool-result supersedes the previous — every emission
398
+ * carries complete state, so last-wins loses nothing). Newest-first
399
+ * matters on the inline-runtime shell: its preflight completes the
400
+ * handshake BEFORE the large bundle parses, so a render AND a
401
+ * follow-up update can both land in the buffer during parse — booting
402
+ * the oldest would paint stale props and drop the update forever
403
+ * (buffered entries never reach the post-mount listener; it hears
404
+ * only post-App arrivals).
405
+ *
406
+ * The shell-side buffer contract: an array whose ELEMENTS are the raw
407
+ * JSON-RPC `params` values of `ui/notifications/tool-result`
408
+ * notifications, in arrival order (shells cap it newest-biased). The
409
+ * runtime reads it once at autostart.
410
+ */
411
+ export declare function readPendingToolResults(): McpAppAiGguiRenderMeta | null;
372
412
  /**
373
413
  * Render a submit-action's `data` payload as a short inline string
374
414
  * for embedding in a `ui/message` consent prompt. Goal: human-
package/dist/runtime.js CHANGED
@@ -461,12 +461,12 @@ export async function bootSequence(opts) {
461
461
  onLifecycle?.(makeLifecycleEvent('code-ready', { sessionId: meta.sessionId }));
462
462
  };
463
463
  // Mode discriminators. The mount surface is DECOUPLED from the live
464
- // channel: static content (codeUrl/kind) paints immediately with no WS;
465
- // the live trio (wsUrl+wsToken) is an OPTIONAL enhancement that
466
- // delivers props_update / data / re-render frames. A bootstrap with
467
- // NEITHER has nothing to show and nowhere to subscribe.
468
- const hasStaticContent = (typeof meta.codeUrl === 'string' && meta.codeUrl.length > 0) ||
469
- (typeof meta.kind === 'string' && meta.kind.length > 0);
464
+ // channel: static content (codeUrl/codeB64/kind) paints immediately
465
+ // with no WS; the live trio (wsUrl+wsToken) is an OPTIONAL
466
+ // enhancement that delivers props_update / data / re-render frames.
467
+ // A bootstrap with NEITHER has nothing to show and nowhere to
468
+ // subscribe.
469
+ const hasStaticContent = hasStaticContentMeta(meta);
470
470
  const hasLiveTrio = typeof meta.wsUrl === 'string' &&
471
471
  meta.wsUrl.length > 0 &&
472
472
  typeof meta.wsToken === 'string' &&
@@ -517,7 +517,7 @@ export async function bootSequence(opts) {
517
517
  // misconfiguration — surface a typed boot failure.
518
518
  if (!hasLiveTrio) {
519
519
  if (mountedRender === null) {
520
- const message = 'bootstrap carries neither static content (codeUrl/kind) nor a live trio (wsUrl/wsToken)';
520
+ const message = 'bootstrap carries neither static content (codeUrl/codeB64/kind) nor a live trio (wsUrl/wsToken)';
521
521
  setStatus(refs, message, 'error');
522
522
  emitBootFailure('MISSING_META_GGUI_BOOTSTRAP', message);
523
523
  return { ok: false, mountedRender };
@@ -801,16 +801,22 @@ function parseSeedProps(propsJson) {
801
801
  * no WS at all for spec-compliant MCP-Apps hosts that expose no ggui
802
802
  * live channel (claude.ai / ChatGPT / Claude Desktop).
803
803
  *
804
- * Two static-content shapes (the autostart discriminator):
804
+ * Three static-content shapes (the autostart discriminator), in
805
+ * priority order:
805
806
  * - `kind` → a system-card seed (`type:'system'`); no fetch.
807
+ * - `codeB64` → a compiled-component seed carried inline; DECODED,
808
+ * never fetched. Preferred over `codeUrl` when both are present —
809
+ * the inline bytes exist precisely for hosts whose iframe CSP
810
+ * blocks the fetch.
806
811
  * - `codeUrl` → a compiled-component seed; fetches the
807
812
  * content-addressable component bytes.
808
813
  *
809
- * Returns `null` when the meta carries NEITHER (a live-only meta has
814
+ * Returns `null` when the meta carries NONE (a live-only meta has
810
815
  * nothing to seed — the first WS ack mounts it). Throws when a `codeUrl`
811
- * fetch fails so the caller can surface a typed boot failure. The four
812
- * server-assigned ledger fields are intentionally absent — the first
813
- * ack reconciles the seed to a full `GguiSession` (no fabrication).
816
+ * fetch fails or `codeB64` is not valid base64 so the caller can
817
+ * surface a typed boot failure. The four server-assigned ledger fields
818
+ * are intentionally absent — the first ack reconciles the seed to a
819
+ * full `GguiSession` (no fabrication).
814
820
  */
815
821
  export async function buildGguiSessionSeedInput(meta) {
816
822
  const props = parseSeedProps(meta.propsJson);
@@ -824,6 +830,15 @@ export async function buildGguiSessionSeedInput(meta) {
824
830
  ...(props !== undefined ? { props } : {}),
825
831
  };
826
832
  }
833
+ // Inline compiled-component mode — decode, no network.
834
+ if (meta.codeB64 !== undefined) {
835
+ return {
836
+ id: meta.sessionId,
837
+ appId: meta.appId,
838
+ componentCode: decodeCodeB64(meta.codeB64),
839
+ ...(props !== undefined ? { props } : {}),
840
+ };
841
+ }
827
842
  // Compiled-component mode — fetch the content-addressable bytes.
828
843
  if (meta.codeUrl === undefined)
829
844
  return null;
@@ -839,6 +854,45 @@ export async function buildGguiSessionSeedInput(meta) {
839
854
  ...(props !== undefined ? { props } : {}),
840
855
  };
841
856
  }
857
+ /**
858
+ * Does this slice carry STATIC content the mount surface can paint
859
+ * without any network round-trip authorization — `codeUrl` (fetched),
860
+ * `codeB64` (decoded inline), or `kind` (built-in registry)?
861
+ *
862
+ * The ONE discriminator `bootSequence` consults when deciding whether
863
+ * the static seed-mount path runs. `codeB64` counting here is
864
+ * load-bearing for fetch-blocked hosts: their meta carries codeB64
865
+ * PLUS a live trio the iframe can never connect, and missing the
866
+ * inline arm would skip the only paint path and die on the dead WS.
867
+ * Mirrors `validateMeta`'s static-content arm (meta-parse.ts) and the
868
+ * protocol host-helper's `hasMountModeDiscriminator` — drift among
869
+ * the three IS the historical bug class, hence the named export.
870
+ */
871
+ export function hasStaticContentMeta(meta) {
872
+ return ((typeof meta.codeUrl === 'string' && meta.codeUrl.length > 0) ||
873
+ (typeof meta.codeB64 === 'string' && meta.codeB64.length > 0) ||
874
+ (typeof meta.kind === 'string' && meta.kind.length > 0));
875
+ }
876
+ /**
877
+ * Decode a `codeB64` slice field to UTF-8 component source. `atob`
878
+ * yields a byte string; routing through `TextDecoder` restores
879
+ * multi-byte characters (component source is UTF-8 — string literals
880
+ * carry emoji, quotes, non-Latin text). Throws a labeled error on
881
+ * malformed base64 so the boot-failure surface names the field.
882
+ */
883
+ function decodeCodeB64(codeB64) {
884
+ try {
885
+ const byteString = atob(codeB64);
886
+ const bytes = new Uint8Array(byteString.length);
887
+ for (let i = 0; i < byteString.length; i++) {
888
+ bytes[i] = byteString.charCodeAt(i);
889
+ }
890
+ return new TextDecoder().decode(bytes);
891
+ }
892
+ catch (cause) {
893
+ throw new Error('buildGguiSessionSeedInput: codeB64 is not valid base64', { cause });
894
+ }
895
+ }
842
896
  /**
843
897
  * Extract a {@link McpAppAiGguiRenderMeta} from a
844
898
  * `ui/notifications/tool-result` JSON-RPC params payload — the
@@ -860,23 +914,29 @@ export function extractMetaFromToolResult(params) {
860
914
  /**
861
915
  * Drain `window.__GGUI_PENDING_TOOL_RESULTS__` — the buffer the
862
916
  * minimal shell populates while messages arrive between
863
- * shell-load and runtime-load. Returns the first valid slice meta
864
- * found; later params (if any) are dropped (each new tool-result
865
- * supersedes the previous).
866
- *
867
- * The shell-side buffer contract: an array of
868
- * `{params: unknown}`-shaped JSON-RPC params from
869
- * `ui/notifications/tool-result` notifications, populated in
870
- * arrival order. The runtime drains it on first read.
871
- */
872
- function readPendingToolResults() {
917
+ * shell-load and runtime-load. Returns the NEWEST valid slice meta
918
+ * (each new tool-result supersedes the previous — every emission
919
+ * carries complete state, so last-wins loses nothing). Newest-first
920
+ * matters on the inline-runtime shell: its preflight completes the
921
+ * handshake BEFORE the large bundle parses, so a render AND a
922
+ * follow-up update can both land in the buffer during parse — booting
923
+ * the oldest would paint stale props and drop the update forever
924
+ * (buffered entries never reach the post-mount listener; it hears
925
+ * only post-App arrivals).
926
+ *
927
+ * The shell-side buffer contract: an array whose ELEMENTS are the raw
928
+ * JSON-RPC `params` values of `ui/notifications/tool-result`
929
+ * notifications, in arrival order (shells cap it newest-biased). The
930
+ * runtime reads it once at autostart.
931
+ */
932
+ export function readPendingToolResults() {
873
933
  if (typeof window === 'undefined')
874
934
  return null;
875
935
  const raw = window.__GGUI_PENDING_TOOL_RESULTS__;
876
936
  if (!Array.isArray(raw) || raw.length === 0)
877
937
  return null;
878
- for (const params of raw) {
879
- const meta = extractMetaFromToolResult(params);
938
+ for (let i = raw.length - 1; i >= 0; i--) {
939
+ const meta = extractMetaFromToolResult(raw[i]);
880
940
  if (meta !== null)
881
941
  return meta;
882
942
  }
@@ -2609,6 +2669,10 @@ function installPostMountListener() {
2609
2669
  meta.sessionId,
2610
2670
  meta.kind ?? '-',
2611
2671
  meta.codeUrl ?? '-',
2672
+ // Inline code participates like codeUrl: an update that ships new
2673
+ // component bytes (re-generation) must re-mount even when
2674
+ // sessionId + props are unchanged.
2675
+ meta.codeB64 ?? '-',
2612
2676
  meta.propsJson ?? '-',
2613
2677
  liveTrio,
2614
2678
  ].join('|');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ggui-ai/iframe-runtime",
3
- "version": "0.7.0",
3
+ "version": "0.8.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",
@@ -35,12 +35,12 @@
35
35
  "qrcode-generator": "^2.0.4",
36
36
  "react": "^19.0.0",
37
37
  "react-dom": "^19.0.0",
38
- "@ggui-ai/live-channel": "0.7.0",
39
- "@ggui-ai/gadgets": "0.7.0",
40
- "@ggui-ai/design": "0.7.0",
41
- "@ggui-ai/preview-a2ui": "0.7.0",
42
- "@ggui-ai/protocol": "0.7.0",
43
- "@ggui-ai/wire": "0.7.0"
38
+ "@ggui-ai/live-channel": "0.8.0",
39
+ "@ggui-ai/gadgets": "0.8.0",
40
+ "@ggui-ai/design": "0.8.0",
41
+ "@ggui-ai/protocol": "0.8.0",
42
+ "@ggui-ai/preview-a2ui": "0.8.0",
43
+ "@ggui-ai/wire": "0.8.0"
44
44
  },
45
45
  "peerDependencies": {
46
46
  "@modelcontextprotocol/sdk": "^1.27.0"