@ggui-ai/iframe-runtime 0.9.0 → 0.11.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/compiled-validators.d.ts +16 -7
- package/dist/compiled-validators.js +41 -8
- package/dist/host-palette-bridge.d.ts +43 -0
- package/dist/host-palette-bridge.js +107 -0
- package/dist/iframe-runtime.js +75 -73
- package/dist/iframe-runtime.meta.json +1 -1
- package/dist/meta-parse.d.ts +13 -0
- package/dist/meta-parse.js +28 -0
- package/dist/react-renderer.d.ts +38 -6
- package/dist/react-renderer.js +75 -8
- package/dist/render-item.d.ts +17 -0
- package/dist/render-item.js +10 -0
- package/dist/runtime.d.ts +166 -1
- package/dist/runtime.js +349 -67
- package/dist/server.d.ts +19 -0
- package/dist/server.js +19 -0
- package/dist/shims/app-components.js +1 -0
- package/dist/shims/components.js +1 -0
- package/dist/shims/compositions.js +1 -0
- package/dist/shims/design.js +1 -0
- package/dist/shims/gadgets.js +1 -0
- package/dist/shims/interact.js +1 -0
- package/dist/shims/jsx-runtime.js +1 -0
- package/dist/shims/primitives.js +1 -0
- package/dist/shims/react-dom.js +1 -0
- package/dist/shims/react.js +1 -0
- package/dist/shims/templates.js +1 -0
- package/dist/shims/tokens.js +1 -0
- package/dist/shims/wire.js +1 -0
- package/dist/system-cards/ProtocolProbeCard.js +1 -1
- package/dist/zod-jitless.d.ts +1 -0
- package/dist/zod-jitless.js +18 -0
- package/package.json +8 -7
package/dist/meta-parse.d.ts
CHANGED
|
@@ -30,6 +30,19 @@ export declare function validateMeta(meta: McpAppAiGguiRenderMeta): McpAppAiGgui
|
|
|
30
30
|
* so the runtime can mount without any postMessage round-trip.
|
|
31
31
|
*/
|
|
32
32
|
export declare function parseMetaFromGlobal(): McpAppAiGguiMetaParseResult;
|
|
33
|
+
/**
|
|
34
|
+
* The `ui://ggui/render/…` LOCATOR a tool result publishes when a server
|
|
35
|
+
* runs the read-plane-only posture (`withholdResultMeta`, guuey#209 C3 /
|
|
36
|
+
* ggui#537): the bootstrap material is absent, but the view's durable
|
|
37
|
+
* identity rides `structuredContent.resourceUri` (ggui-aware hosts) and
|
|
38
|
+
* the spec-canonical `_meta.ui.resourceUri` (MCP Apps hosts) — same
|
|
39
|
+
* value, either slot. The runtime resolves such a result through the
|
|
40
|
+
* read-plane door (`app.readServerResource` → the per-render
|
|
41
|
+
* self-contained shell → its inlined envelope). `null` for anything else
|
|
42
|
+
* — a result that carries the slice inline never needs this, and a
|
|
43
|
+
* result with neither is not a view.
|
|
44
|
+
*/
|
|
45
|
+
export declare function extractLocatorFromToolResult(params: unknown): string | null;
|
|
33
46
|
/**
|
|
34
47
|
* Parse the render slice from a `ui/notifications/tool-result`
|
|
35
48
|
* postMessage params payload. `params` is a `CallToolResult` per the
|
package/dist/meta-parse.js
CHANGED
|
@@ -274,6 +274,11 @@ function projectMeta(meta, hasStaticContent) {
|
|
|
274
274
|
: {}),
|
|
275
275
|
...(meta.codeUrl !== undefined ? { codeUrl: meta.codeUrl } : {}),
|
|
276
276
|
...(meta.codeHash !== undefined ? { codeHash: meta.codeHash } : {}),
|
|
277
|
+
// Strict-CSP module-variant twin (ggui#522 slice 2). The protocol
|
|
278
|
+
// parser already enforces the with-a-raw-carrier pairing.
|
|
279
|
+
...(meta.codeModuleUrl !== undefined
|
|
280
|
+
? { codeModuleUrl: meta.codeModuleUrl }
|
|
281
|
+
: {}),
|
|
277
282
|
...(meta.codeB64 !== undefined ? { codeB64: meta.codeB64 } : {}),
|
|
278
283
|
...(meta.kind !== undefined ? { kind: meta.kind } : {}),
|
|
279
284
|
};
|
|
@@ -349,6 +354,29 @@ export function parseMetaFromGlobal() {
|
|
|
349
354
|
}
|
|
350
355
|
return validateMeta(parsed.meta);
|
|
351
356
|
}
|
|
357
|
+
/**
|
|
358
|
+
* The `ui://ggui/render/…` LOCATOR a tool result publishes when a server
|
|
359
|
+
* runs the read-plane-only posture (`withholdResultMeta`, guuey#209 C3 /
|
|
360
|
+
* ggui#537): the bootstrap material is absent, but the view's durable
|
|
361
|
+
* identity rides `structuredContent.resourceUri` (ggui-aware hosts) and
|
|
362
|
+
* the spec-canonical `_meta.ui.resourceUri` (MCP Apps hosts) — same
|
|
363
|
+
* value, either slot. The runtime resolves such a result through the
|
|
364
|
+
* read-plane door (`app.readServerResource` → the per-render
|
|
365
|
+
* self-contained shell → its inlined envelope). `null` for anything else
|
|
366
|
+
* — a result that carries the slice inline never needs this, and a
|
|
367
|
+
* result with neither is not a view.
|
|
368
|
+
*/
|
|
369
|
+
export function extractLocatorFromToolResult(params) {
|
|
370
|
+
if (!isPlainObject(params))
|
|
371
|
+
return null;
|
|
372
|
+
const sc = params['structuredContent'];
|
|
373
|
+
const fromStructured = isPlainObject(sc) && typeof sc['resourceUri'] === 'string' ? sc['resourceUri'] : null;
|
|
374
|
+
const meta = params['_meta'];
|
|
375
|
+
const ui = isPlainObject(meta) ? meta['ui'] : undefined;
|
|
376
|
+
const fromUi = isPlainObject(ui) && typeof ui['resourceUri'] === 'string' ? ui['resourceUri'] : null;
|
|
377
|
+
const uri = fromStructured ?? fromUi;
|
|
378
|
+
return uri !== null && uri.startsWith('ui://ggui/render/') ? uri : null;
|
|
379
|
+
}
|
|
352
380
|
/**
|
|
353
381
|
* Parse the render slice from a `ui/notifications/tool-result`
|
|
354
382
|
* postMessage params payload. `params` is a `CallToolResult` per the
|
package/dist/react-renderer.d.ts
CHANGED
|
@@ -41,6 +41,18 @@ export interface ReactRootMountOptions {
|
|
|
41
41
|
readonly id?: string;
|
|
42
42
|
readonly componentCode: string;
|
|
43
43
|
readonly props?: Record<string, unknown>;
|
|
44
|
+
/**
|
|
45
|
+
* Strict-CSP module variant of `componentCode` (ggui#522 slice 2):
|
|
46
|
+
* a server-side import-rewritten twin of the SAME bytes, directly
|
|
47
|
+
* `import()`able (its imports resolve to static shim assets on an
|
|
48
|
+
* allowed origin — no `data:`, no `blob:`, no eval). Tried FIRST;
|
|
49
|
+
* any failure falls through to the blob/inline ladder on the raw
|
|
50
|
+
* bytes. Callers MUST pass it only when it names exactly these
|
|
51
|
+
* `componentCode` bytes — a stale variant would paint the WRONG
|
|
52
|
+
* component, not fail (the runtime's dispatcher guards this by
|
|
53
|
+
* attaching it to the inline seed only).
|
|
54
|
+
*/
|
|
55
|
+
readonly codeModuleUrl?: string;
|
|
44
56
|
};
|
|
45
57
|
readonly themeId?: string;
|
|
46
58
|
/**
|
|
@@ -50,17 +62,37 @@ export interface ReactRootMountOptions {
|
|
|
50
62
|
*/
|
|
51
63
|
readonly themeMode?: 'light' | 'dark';
|
|
52
64
|
/**
|
|
53
|
-
* Per-app theme overlay
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
65
|
+
* Per-app theme overlay — a partial set of `--ggui-*` values; unset
|
|
66
|
+
* vars keep their token defaults. Merged INTO the scoped in-body
|
|
67
|
+
* token block after the base block (and after `hostPalette`), which
|
|
68
|
+
* is the only placement that can recolor tree content: a `:root`-only
|
|
69
|
+
* append is cascade-dead for components — the scoped block sits later
|
|
70
|
+
* in document order and re-specifies every token (browser-verified,
|
|
71
|
+
* see the scoped-merge comment in `renderTree`). The `:root` append
|
|
72
|
+
* still happens, but for body chrome only. A structural subset of
|
|
73
|
+
* protocol's `AppTheme` (`name` is display-only, not needed at
|
|
74
|
+
* render). `mode` drives `color-scheme` so native controls /
|
|
75
|
+
* scrollbars / canvas pick up dark vs light.
|
|
76
|
+
*
|
|
77
|
+
* Precedence (ggui#573 ruling): base tokens < `hostPalette` <
|
|
78
|
+
* `appTheme` < `cssOverrides` — the slice's stamped theme always
|
|
79
|
+
* wins; the host palette is the fallback layer beneath it.
|
|
59
80
|
*/
|
|
60
81
|
readonly appTheme?: {
|
|
61
82
|
readonly mode: 'light' | 'dark';
|
|
62
83
|
readonly cssVariables: Record<string, string>;
|
|
63
84
|
};
|
|
85
|
+
/**
|
|
86
|
+
* Host-announced palette, already translated onto `--ggui-*` tokens
|
|
87
|
+
* (see `host-palette-bridge.ts` — the spec `--color-*` vocabulary
|
|
88
|
+
* never reaches this layer). Merged into the scoped token block
|
|
89
|
+
* after the base block and BEFORE `appTheme`, so it repaints only
|
|
90
|
+
* the slots no operator theme re-specifies (ggui#572; precedence
|
|
91
|
+
* per the ggui#573 ruling above). Values are sanitized by the
|
|
92
|
+
* bridge before they get here — host input never reaches this
|
|
93
|
+
* string-join raw.
|
|
94
|
+
*/
|
|
95
|
+
readonly hostPalette?: Readonly<Record<string, string>>;
|
|
64
96
|
readonly cssOverrides?: string;
|
|
65
97
|
readonly onError?: (error: Error) => void;
|
|
66
98
|
/**
|
package/dist/react-renderer.js
CHANGED
|
@@ -59,6 +59,18 @@ function makeScopeClass() {
|
|
|
59
59
|
scopeCounter += 1;
|
|
60
60
|
return `ggui-rcr-${scopeCounter}`;
|
|
61
61
|
}
|
|
62
|
+
/**
|
|
63
|
+
* Join a custom-property record into CSS declarations. Values were
|
|
64
|
+
* validated injection-safe upstream (appTheme: write-side
|
|
65
|
+
* `appThemeSchema` + the wire parser; hostPalette: the host-palette
|
|
66
|
+
* bridge's sanitizer), so the string-join is safe — do NOT
|
|
67
|
+
* re-sanitize here.
|
|
68
|
+
*/
|
|
69
|
+
function toCssDecls(vars) {
|
|
70
|
+
return Object.entries(vars)
|
|
71
|
+
.map(([k, v]) => `${k}: ${v};`)
|
|
72
|
+
.join('');
|
|
73
|
+
}
|
|
62
74
|
// =============================================================================
|
|
63
75
|
// Error boundary — port of RCR's internal ErrorBoundary (L68–203).
|
|
64
76
|
// =============================================================================
|
|
@@ -235,6 +247,30 @@ export async function mountReactRoot(container, opts) {
|
|
|
235
247
|
? { gadgetPackages: currentOpts.gadgetPackages }
|
|
236
248
|
: {}),
|
|
237
249
|
}));
|
|
250
|
+
// Asset-module path (ggui#522 slice 2) — FIRST when the mount
|
|
251
|
+
// carries the strict-CSP variant URL: a plain https module import,
|
|
252
|
+
// legal under `script-src` origins alone, no scheme sources, no
|
|
253
|
+
// eval. Under a strict host CSP it is the ONLY path that works;
|
|
254
|
+
// under a permissive one it costs a CDN round-trip on cold cache
|
|
255
|
+
// and nothing on warm (the URL is immutable). Any failure — CSP
|
|
256
|
+
// block, network fault, a 404 from a redeployed variant family —
|
|
257
|
+
// falls through to the raw-bytes ladder below. No document latch
|
|
258
|
+
// on failure: unlike the per-evaluation blob probe, this branch
|
|
259
|
+
// runs only when componentCode changes (rare — usually once per
|
|
260
|
+
// mount), so the retry cost is one fetch, and the failure classes
|
|
261
|
+
// (transient network vs permanent CSP) are indistinguishable in
|
|
262
|
+
// the import error anyway.
|
|
263
|
+
const moduleUrl = currentOpts.render.codeModuleUrl;
|
|
264
|
+
if (moduleUrl !== undefined) {
|
|
265
|
+
try {
|
|
266
|
+
const mod = (await import(
|
|
267
|
+
/* webpackIgnore: true */ /* @vite-ignore */ moduleUrl));
|
|
268
|
+
return pick(mod);
|
|
269
|
+
}
|
|
270
|
+
catch {
|
|
271
|
+
// Fall through — the raw bytes are the authoritative carrier.
|
|
272
|
+
}
|
|
273
|
+
}
|
|
238
274
|
// Once URL-scheme module loading has been proven blocked in this
|
|
239
275
|
// document, go straight to inline execution — the CSP verdict
|
|
240
276
|
// cannot change within the document's lifetime.
|
|
@@ -278,9 +314,33 @@ export async function mountReactRoot(container, opts) {
|
|
|
278
314
|
}
|
|
279
315
|
}
|
|
280
316
|
function renderTree(opts) {
|
|
281
|
-
|
|
317
|
+
let themeCss = opts.themeId
|
|
282
318
|
? getScopedThemeCss(opts.themeId, scopeClass, opts.themeMode)
|
|
283
319
|
: getScopedCssTokens(scopeClass, opts.themeMode);
|
|
320
|
+
if (opts.hostPalette) {
|
|
321
|
+
// Host palette = the fallback layer (ggui#572, #573 ruling):
|
|
322
|
+
// after the base scoped block so it can repaint at all (the base
|
|
323
|
+
// re-specifies every token), BEFORE the appTheme overlay so the
|
|
324
|
+
// slice's stamped theme wins by document order. Keys + values
|
|
325
|
+
// arrive pre-sanitized from the host-palette bridge.
|
|
326
|
+
themeCss += `.${scopeClass}{${toCssDecls(opts.hostPalette)}}`;
|
|
327
|
+
}
|
|
328
|
+
if (opts.appTheme) {
|
|
329
|
+
const decls = toCssDecls(opts.appTheme.cssVariables);
|
|
330
|
+
// The overlay must be merged HERE, into the scoped style that
|
|
331
|
+
// mounts INSIDE the scope div — the `:root` append below is
|
|
332
|
+
// cascade-dead for tree content: the in-scope block sits later
|
|
333
|
+
// in document order than anything in `<head>`, so at equal
|
|
334
|
+
// specificity the base scoped tokens always win over a head-level
|
|
335
|
+
// override (browser-verified in rnd/gen-ui/beauty/experiments/001,
|
|
336
|
+
// the probe sequence that found this). Appended AFTER the base
|
|
337
|
+
// block + host palette and BEFORE `cssOverrides`, so: base <
|
|
338
|
+
// host palette < operator overlay < explicit overrides
|
|
339
|
+
// (ggui#573: slice wins, host fallback). Values validated
|
|
340
|
+
// injection-safe upstream (same guarantee the `:root` append
|
|
341
|
+
// cites).
|
|
342
|
+
themeCss += `.${scopeClass}{${decls}}`;
|
|
343
|
+
}
|
|
284
344
|
// Also inject theme CSS at `:root` on `document.head`. The scoped
|
|
285
345
|
// injection above isolates token resolution to the React tree, but
|
|
286
346
|
// the embedding shell's body styles (font-family, color, background)
|
|
@@ -294,14 +354,21 @@ export async function mountReactRoot(container, opts) {
|
|
|
294
354
|
let rootCss = opts.themeId
|
|
295
355
|
? getThemeCss(opts.themeId, opts.themeMode)
|
|
296
356
|
: getCssTokens(opts.themeMode);
|
|
357
|
+
if (opts.hostPalette) {
|
|
358
|
+
// BODY-CHROME ONLY, same as the appTheme `:root` append below —
|
|
359
|
+
// and beneath it, mirroring the scoped-block order (#573:
|
|
360
|
+
// slice wins, host fallback). Keeps the embedding shell's
|
|
361
|
+
// body font/color/background coherent with the tree.
|
|
362
|
+
rootCss += `:root{${toCssDecls(opts.hostPalette)}}`;
|
|
363
|
+
}
|
|
297
364
|
if (opts.appTheme) {
|
|
298
|
-
const decls =
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
//
|
|
302
|
-
//
|
|
303
|
-
//
|
|
304
|
-
//
|
|
365
|
+
const decls = toCssDecls(opts.appTheme.cssVariables);
|
|
366
|
+
// BODY-CHROME ONLY. This `:root` append themes the embedding
|
|
367
|
+
// shell's body styles (font/color/background outside the scope
|
|
368
|
+
// div). It does NOT reach tree content — the scoped in-body
|
|
369
|
+
// token block wins by document order (see the scoped merge in
|
|
370
|
+
// `renderTree`, and rnd/gen-ui/beauty/experiments/001 for the
|
|
371
|
+
// probe that falsified the old "inherits into the tree" claim).
|
|
305
372
|
// Values were validated injection-safe upstream (write-side
|
|
306
373
|
// `appThemeSchema` + the wire parser re-validates), so the
|
|
307
374
|
// string-join here is safe — do NOT re-sanitize.
|
package/dist/render-item.d.ts
CHANGED
|
@@ -29,6 +29,15 @@ export interface RenderItemOptions {
|
|
|
29
29
|
* this. STDLIB `@ggui-ai/gadgets` is always rewritten regardless.
|
|
30
30
|
*/
|
|
31
31
|
readonly gadgetPackages?: readonly string[];
|
|
32
|
+
/**
|
|
33
|
+
* Strict-CSP module variant of the render's `componentCode`
|
|
34
|
+
* (ggui#522 slice 2), forwarded to
|
|
35
|
+
* {@link ReactRootMountOptions.render.codeModuleUrl}. The CALLER
|
|
36
|
+
* owns the bytes↔URL association: pass it only when it names
|
|
37
|
+
* exactly `render.componentCode`'s bytes (the runtime attaches it
|
|
38
|
+
* to the inline seed mount only — a WS-delivered render drops it).
|
|
39
|
+
*/
|
|
40
|
+
readonly codeModuleUrl?: string;
|
|
32
41
|
/** Theme id passed to ReactComponentRenderer for scoped CSS. */
|
|
33
42
|
readonly themeId?: string;
|
|
34
43
|
/**
|
|
@@ -47,6 +56,14 @@ export interface RenderItemOptions {
|
|
|
47
56
|
readonly mode: 'light' | 'dark';
|
|
48
57
|
readonly cssVariables: Record<string, string>;
|
|
49
58
|
};
|
|
59
|
+
/**
|
|
60
|
+
* Host-announced palette, pre-mapped onto `--ggui-*` tokens by the
|
|
61
|
+
* host-palette bridge. Forwarded to
|
|
62
|
+
* {@link ReactRootMountOptions.hostPalette}: the fallback layer
|
|
63
|
+
* beneath `appTheme` in the scoped token block (ggui#572, merge
|
|
64
|
+
* order per the ggui#573 ruling — slice wins, host fallback).
|
|
65
|
+
*/
|
|
66
|
+
readonly hostPalette?: Readonly<Record<string, string>>;
|
|
50
67
|
readonly cssOverrides?: string;
|
|
51
68
|
readonly onError?: (err: Error) => void;
|
|
52
69
|
/**
|
package/dist/render-item.js
CHANGED
|
@@ -169,11 +169,17 @@ export async function mountRender(container, opts) {
|
|
|
169
169
|
...(render.props !== undefined
|
|
170
170
|
? { props: render.props }
|
|
171
171
|
: {}),
|
|
172
|
+
...(currentOpts.codeModuleUrl !== undefined
|
|
173
|
+
? { codeModuleUrl: currentOpts.codeModuleUrl }
|
|
174
|
+
: {}),
|
|
172
175
|
},
|
|
173
176
|
...(gadgetPackages !== undefined ? { gadgetPackages } : {}),
|
|
174
177
|
...(currentOpts.themeId !== undefined ? { themeId: currentOpts.themeId } : {}),
|
|
175
178
|
...(currentOpts.themeMode !== undefined ? { themeMode: currentOpts.themeMode } : {}),
|
|
176
179
|
...(currentOpts.appTheme !== undefined ? { appTheme: currentOpts.appTheme } : {}),
|
|
180
|
+
...(currentOpts.hostPalette !== undefined
|
|
181
|
+
? { hostPalette: currentOpts.hostPalette }
|
|
182
|
+
: {}),
|
|
177
183
|
...(currentOpts.cssOverrides !== undefined
|
|
178
184
|
? { cssOverrides: currentOpts.cssOverrides }
|
|
179
185
|
: {}),
|
|
@@ -234,10 +240,14 @@ export async function mountRender(container, opts) {
|
|
|
234
240
|
...(render.props !== undefined
|
|
235
241
|
? { props: render.props }
|
|
236
242
|
: {}),
|
|
243
|
+
...(next.codeModuleUrl !== undefined
|
|
244
|
+
? { codeModuleUrl: next.codeModuleUrl }
|
|
245
|
+
: {}),
|
|
237
246
|
},
|
|
238
247
|
...(next.themeId !== undefined ? { themeId: next.themeId } : {}),
|
|
239
248
|
...(next.themeMode !== undefined ? { themeMode: next.themeMode } : {}),
|
|
240
249
|
...(next.appTheme !== undefined ? { appTheme: next.appTheme } : {}),
|
|
250
|
+
...(next.hostPalette !== undefined ? { hostPalette: next.hostPalette } : {}),
|
|
241
251
|
...(next.cssOverrides !== undefined ? { cssOverrides: next.cssOverrides } : {}),
|
|
242
252
|
...(next.onError !== undefined ? { onError: next.onError } : {}),
|
|
243
253
|
renderWrapper: wrapInScopedProvider,
|
package/dist/runtime.d.ts
CHANGED
|
@@ -1,3 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@ggui-ai/iframe-runtime` iframe runtime entry.
|
|
3
|
+
*
|
|
4
|
+
* This is the file esbuild bundles into `dist/iframe-runtime.js`. The
|
|
5
|
+
* thin-shell HTML loads it via `<script type="module" src=".../iframe-runtime.js">`;
|
|
6
|
+
* on import the side-effects below take over: build a status DOM,
|
|
7
|
+
* postMessage `ui/initialize` to the parent, parse the bootstrap, open
|
|
8
|
+
* the WebSocket, run the version handshake, and mount the render
|
|
9
|
+
* placeholder.
|
|
10
|
+
*
|
|
11
|
+
* Boot sequence:
|
|
12
|
+
* - Boot from the `_meta["ai.ggui/render"]` slice received via
|
|
13
|
+
* `ui/initialize`.
|
|
14
|
+
* - Open the WebSocket, run the subscribe handshake.
|
|
15
|
+
* - Mount the render — either a structural placeholder or, when
|
|
16
|
+
* the renderer hooks are wired, a React mount of `componentCode`.
|
|
17
|
+
*
|
|
18
|
+
* The runtime advertises its build version via a post-`ui/initialize`
|
|
19
|
+
* notification (`ggui:renderer-ready`).
|
|
20
|
+
*
|
|
21
|
+
* Failure surfacing: every parse / handshake failure goes to two
|
|
22
|
+
* places — (1) the in-iframe status line (operator-visible) and
|
|
23
|
+
* (2) a parent-bound `postMessage({type:'ggui:bootstrap-failed', ...})`.
|
|
24
|
+
* The post-message envelope is consumed by the `<McpAppIframe>` host
|
|
25
|
+
* wrapper, which routes it to the host's `onError` callback.
|
|
26
|
+
*/
|
|
27
|
+
import './zod-jitless.js';
|
|
1
28
|
import type { DrainAckPayload, JsonValue, JsonObject, GguiSession } from '@ggui-ai/protocol';
|
|
2
29
|
import type { WebSocketMessage } from '@ggui-ai/protocol/transport/websocket';
|
|
3
30
|
import { type McpAppAiGguiRenderMeta, type McpAppBootstrapFailedMessage, type McpAppRendererReadyMessage } from '@ggui-ai/protocol/integrations/mcp-apps';
|
|
@@ -47,6 +74,110 @@ export type ConnectAppResult = {
|
|
|
47
74
|
* without invoking the full boot pipeline.
|
|
48
75
|
*/
|
|
49
76
|
export declare function setCurrentApp(app: App): void;
|
|
77
|
+
/**
|
|
78
|
+
* The host's announced color mode, read from the connected App's
|
|
79
|
+
* pre-merged hostContext (`ui/initialize` result, kept current by the
|
|
80
|
+
* App across `host-context-changed`). `undefined` when no App is
|
|
81
|
+
* connected, the host sent no `theme`, or it sent something outside
|
|
82
|
+
* the spec's `'light' | 'dark'` literals (dropped, never guessed —
|
|
83
|
+
* the same tolerant posture as `projectHostContext`).
|
|
84
|
+
*
|
|
85
|
+
* This is the INPUT leg of host-theme adaptation
|
|
86
|
+
* (rnd/gen-ui/beauty/experiments/002-host-theme-adaptation.md, ggui#551):
|
|
87
|
+
* `hostContext.theme` alone reaches only `data-theme` +
|
|
88
|
+
* `color-scheme` via `applyDocumentTheme` — nothing in the token
|
|
89
|
+
* pipeline keys off it, so a dark host painted the light ladder
|
|
90
|
+
* (host-fit 46.0 vs 87.7 with the dark ladder, probe `cc728a8eb`).
|
|
91
|
+
* `buildOpts` consumes this ONLY when the slice stamps no `themeMode`:
|
|
92
|
+
* every operator layer (live provider > per-render override > app
|
|
93
|
+
* default > server default) still wins by being stamped; the host
|
|
94
|
+
* fills the gap where no operator had an opinion.
|
|
95
|
+
*
|
|
96
|
+
* @internal — exported for unit tests (`setCurrentApp` injects the
|
|
97
|
+
* App); production caller is `buildOpts` inside `bootSequence`.
|
|
98
|
+
*/
|
|
99
|
+
export declare function hostAnnouncedThemeMode(): 'light' | 'dark' | undefined;
|
|
100
|
+
/**
|
|
101
|
+
* Resolve the mount's base-stylesheet color mode from the slice, with
|
|
102
|
+
* the host as the final fallback (ggui#589, completing the ggui#551
|
|
103
|
+
* precedence ruling — "slice wins, host is the fallback, absent ≠
|
|
104
|
+
* light"):
|
|
105
|
+
*
|
|
106
|
+
* stamped `themeMode` > `theme.mode` (the slice theme OBJECT) >
|
|
107
|
+
* host-announced theme > undefined
|
|
108
|
+
*
|
|
109
|
+
* The middle leg is the ggui#589 fix: a render envelope carrying a
|
|
110
|
+
* per-app theme (`theme: {mode, cssVariables}`) but no top-level
|
|
111
|
+
* `themeMode` is still a slice-stamped mode opinion — before this,
|
|
112
|
+
* the base token ladder ignored it and painted the LIGHT token set
|
|
113
|
+
* under a dark overlay whenever the host (correctly, per the
|
|
114
|
+
* adapter-boundary doctrine in the native host helpers) announced no
|
|
115
|
+
* `hostContext.theme`: a light skeleton in a dark skin. The theme
|
|
116
|
+
* object's own `mode` already drove `color-scheme`; now it also
|
|
117
|
+
* selects the ladder.
|
|
118
|
+
*
|
|
119
|
+
* `undefined` still means "no opinion anywhere" — the renderer's
|
|
120
|
+
* default applies; never coerced to `'light'`.
|
|
121
|
+
*
|
|
122
|
+
* @internal — exported for unit tests; production caller is
|
|
123
|
+
* `buildOpts` inside `bootSequence`.
|
|
124
|
+
*/
|
|
125
|
+
export declare function resolveMountThemeMode(meta: {
|
|
126
|
+
readonly themeMode?: 'light' | 'dark';
|
|
127
|
+
readonly theme?: {
|
|
128
|
+
readonly mode: 'light' | 'dark';
|
|
129
|
+
readonly cssVariables?: Record<string, string>;
|
|
130
|
+
};
|
|
131
|
+
}): 'light' | 'dark' | undefined;
|
|
132
|
+
/**
|
|
133
|
+
* Resolve the mount's base-theme id from the slice (ggui#589 ask 3):
|
|
134
|
+
*
|
|
135
|
+
* stamped `themeId` > `theme.name` (the slice theme OBJECT's name)
|
|
136
|
+
* > undefined (renderer default ladder)
|
|
137
|
+
*
|
|
138
|
+
* The name leg makes a slice theme whose `name` matches a REGISTERED
|
|
139
|
+
* theme id select that theme as the BASE token ladder — the overlay's
|
|
140
|
+
* cssVariables still apply above it, so the base carries the full
|
|
141
|
+
* brand ramp coverage a sparse overlay cannot (the "only the mapped
|
|
142
|
+
* vars carry brand" store-frame class). An unregistered name is
|
|
143
|
+
* harmless by construction: `getScopedThemeCss` falls back to the
|
|
144
|
+
* default theme, which is byte-identical to the no-themeId path
|
|
145
|
+
* (`getScopedCssTokens` ≡ `getScopedThemeCss(default)`), so a slice
|
|
146
|
+
* naming its theme "My Custom" renders exactly as before.
|
|
147
|
+
*
|
|
148
|
+
* @internal — exported for unit tests; production caller is
|
|
149
|
+
* `buildOpts` inside `bootSequence`.
|
|
150
|
+
*/
|
|
151
|
+
export declare function resolveMountThemeId(meta: {
|
|
152
|
+
readonly themeId?: string;
|
|
153
|
+
readonly theme?: {
|
|
154
|
+
readonly name?: string;
|
|
155
|
+
readonly mode?: 'light' | 'dark';
|
|
156
|
+
readonly cssVariables?: Record<string, string>;
|
|
157
|
+
};
|
|
158
|
+
}): string | undefined;
|
|
159
|
+
/**
|
|
160
|
+
* The host's announced palette, read from the connected App's
|
|
161
|
+
* pre-merged hostContext and translated onto `--ggui-*` tokens by the
|
|
162
|
+
* host-palette bridge. `undefined` when no App is connected, the host
|
|
163
|
+
* sent no `styles.variables`, or nothing in it maps/survives
|
|
164
|
+
* sanitization — callers spread-skip the option, exactly like
|
|
165
|
+
* {@link hostAnnouncedThemeMode}.
|
|
166
|
+
*
|
|
167
|
+
* This is the palette input leg of host-theme adaptation (ggui#572,
|
|
168
|
+
* the color analog of #551's mode leg above): the spec `--color-*`
|
|
169
|
+
* variables land as inline custom properties on `<html>` via the
|
|
170
|
+
* canonical ext-apps helper, but nothing ggui renders consumes that
|
|
171
|
+
* vocabulary, and the scoped token block shadows root inheritance
|
|
172
|
+
* anyway — so without this mapping a host palette repaints nothing.
|
|
173
|
+
* `buildOpts` threads it onto the mount options; the renderer merges
|
|
174
|
+
* it as the fallback layer BENEATH the slice's own theme (ggui#573
|
|
175
|
+
* ruling: slice wins, host fallback).
|
|
176
|
+
*
|
|
177
|
+
* @internal — exported for unit tests (`setCurrentApp` injects the
|
|
178
|
+
* App); production caller is `buildOpts` inside `bootSequence`.
|
|
179
|
+
*/
|
|
180
|
+
export declare function hostAnnouncedPalette(): Readonly<Record<string, string>> | undefined;
|
|
50
181
|
/**
|
|
51
182
|
* @internal — exported for unit tests to reset module state between
|
|
52
183
|
* scenarios.
|
|
@@ -193,6 +324,17 @@ export interface BootSequenceOptions {
|
|
|
193
324
|
* arrives, and `hostContext` is captured from `app.getHostContext()`.
|
|
194
325
|
*/
|
|
195
326
|
readonly preResolvedMeta?: McpAppAiGguiRenderMeta;
|
|
327
|
+
/**
|
|
328
|
+
* Autostart-layer pre-resolution of the read-plane door (ggui#537):
|
|
329
|
+
* a tool result that arrived before `bootSequence` ran carried the
|
|
330
|
+
* view's LOCATOR (`ui://ggui/render/…`) but no bootstrap material —
|
|
331
|
+
* the read-plane-only posture. The runtime cannot read the resource
|
|
332
|
+
* before the App is connected, so the locator is threaded here and
|
|
333
|
+
* resolved via {@link resolveMetaViaReadDoor} right after the
|
|
334
|
+
* handshake, ahead of the Tier 2 tool-result wait. Ignored when
|
|
335
|
+
* `preResolvedMeta` is set.
|
|
336
|
+
*/
|
|
337
|
+
readonly preResolvedLocator?: string;
|
|
196
338
|
/**
|
|
197
339
|
* How long to wait for the spec-canonical `ui/notifications/tool-result`
|
|
198
340
|
* notification (Tier 2 of the resolver chain) before failing with
|
|
@@ -409,6 +551,29 @@ export declare function extractMetaFromToolResult(params: unknown): McpAppAiGgui
|
|
|
409
551
|
* runtime reads it once at autostart.
|
|
410
552
|
*/
|
|
411
553
|
export declare function readPendingToolResults(): McpAppAiGguiRenderMeta | null;
|
|
554
|
+
/**
|
|
555
|
+
* Sibling of {@link readPendingToolResults} for the read-plane-only
|
|
556
|
+
* posture (ggui#537): the NEWEST buffered tool result that carries a
|
|
557
|
+
* `ui://ggui/render/…` locator but no bootstrap material. Consulted only
|
|
558
|
+
* when the buffer yielded no inline slice; the autostart threads it to
|
|
559
|
+
* `bootSequence` as `preResolvedLocator`, resolved after the handshake.
|
|
560
|
+
*/
|
|
561
|
+
export declare function readPendingToolResultLocator(): string | null;
|
|
562
|
+
/**
|
|
563
|
+
* The read-plane door (ggui#537). A server running the read-plane-only
|
|
564
|
+
* posture publishes only the view's identity on the tool result — the
|
|
565
|
+
* `ui://ggui/render/…` locator — and no bootstrap material; the
|
|
566
|
+
* per-render resource that locator names is the self-contained shell,
|
|
567
|
+
* whose document inlines the very envelope this runtime boots from.
|
|
568
|
+
* Ask the HOST to read it (`app.readServerResource` → `resources/read`,
|
|
569
|
+
* proxied by the host's own MCP client — no fetch from this sandbox, so
|
|
570
|
+
* a host CSP without `connect-src` to the server is not in the way),
|
|
571
|
+
* recover the envelope with the protocol's writer/reader pair
|
|
572
|
+
* ({@link readGguiShellEnvelope}), and validate it exactly as an inline
|
|
573
|
+
* slice would be. `null` on any miss (host cannot proxy reads, resource
|
|
574
|
+
* gone, no envelope) — the caller's other tiers still decide.
|
|
575
|
+
*/
|
|
576
|
+
export declare function resolveMetaViaReadDoor(app: App, locator: string): Promise<McpAppAiGguiRenderMeta | null>;
|
|
412
577
|
/**
|
|
413
578
|
* Render a submit-action's `data` payload as a short inline string
|
|
414
579
|
* for embedding in a `ui/message` consent prompt. Goal: human-
|
|
@@ -649,7 +814,7 @@ export declare function requestDisplayModeInParent(args: {
|
|
|
649
814
|
* or honor an upstream `defaultPrevented` from a higher-priority
|
|
650
815
|
* handler.
|
|
651
816
|
*
|
|
652
|
-
* Idempotent: re-mounts (via `
|
|
817
|
+
* Idempotent: re-mounts (via `installPersistentToolResultListener`) call this
|
|
653
818
|
* helper again; the {@link anchorClickInterceptInstalled} guard makes
|
|
654
819
|
* subsequent calls a no-op.
|
|
655
820
|
*/
|