@ggui-ai/iframe-runtime 0.24.0 → 0.26.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/README.md +2 -2
- package/dist/channels/data.d.ts +8 -0
- package/dist/channels/data.js +2 -0
- package/dist/credential-controller.d.ts +71 -0
- package/dist/credential-controller.js +135 -0
- package/dist/iframe-runtime.js +97 -93
- package/dist/iframe-runtime.meta.json +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/ladders.d.ts +77 -0
- package/dist/ladders.js +383 -0
- package/dist/meta-parse.js +38 -8
- package/dist/observability.d.ts +38 -11
- package/dist/react-renderer.d.ts +22 -0
- package/dist/react-renderer.js +118 -3
- package/dist/registry-subscribe.d.ts +53 -2
- package/dist/registry-subscribe.js +63 -8
- package/dist/render-failure-report.d.ts +15 -0
- package/dist/render-failure-report.js +61 -0
- package/dist/render-item.d.ts +6 -1
- package/dist/render-item.js +7 -2
- package/dist/runtime-telemetry.d.ts +28 -18
- package/dist/runtime-telemetry.js +68 -29
- package/dist/runtime.d.ts +157 -39
- package/dist/runtime.js +724 -368
- package/dist/shims/wire.js +1 -1
- package/dist/stream-seq.d.ts +56 -0
- package/dist/stream-seq.js +46 -0
- package/dist/system-cards/ProtocolProbeCard.d.ts +16 -0
- package/dist/system-cards/ProtocolProbeCard.js +17 -14
- package/dist/tool-result-error.d.ts +6 -0
- package/dist/tool-result-error.js +32 -0
- package/dist/types.d.ts +49 -1
- package/dist/view-origin.d.ts +29 -0
- package/dist/view-origin.js +33 -0
- package/dist/view-proof-signer.d.ts +31 -0
- package/dist/view-proof-signer.js +99 -0
- package/dist/view-root.d.ts +51 -0
- package/dist/view-root.js +56 -0
- package/dist/wire-config.d.ts +15 -1
- package/dist/wire-config.js +42 -1
- package/package.json +8 -7
|
@@ -1,24 +1,58 @@
|
|
|
1
|
+
import { RUNTIME_TELEMETRY_KINDS, } from '@ggui-ai/protocol/runtime-telemetry';
|
|
1
2
|
import { RUNTIME_TELEMETRY_MAX_EVENTS } from '@ggui-ai/protocol/wire';
|
|
2
3
|
const FIRST_FLUSH_DELAY_MS = 4_000;
|
|
3
4
|
const FLUSH_THROTTLE_MS = 8_000;
|
|
4
|
-
const
|
|
5
|
+
const MAX_HEALTH_FLUSHES_PER_SESSION = 8;
|
|
6
|
+
const MAX_DIAGNOSTIC_FLUSHES_PER_SESSION = 4;
|
|
5
7
|
const MAX_DETAIL_CHARS = 500;
|
|
8
|
+
/** The kind vocabulary (`@ggui-ai/protocol/runtime-telemetry`, ggui#1381), indexed by kind. */
|
|
9
|
+
const SPEC_BY_KIND = new Map(RUNTIME_TELEMETRY_KINDS.map((s) => [s.kind, s]));
|
|
10
|
+
const ID_DETAIL = /^[A-Za-z0-9_.:-]{1,128}$/;
|
|
11
|
+
function isBooleansObject(detail) {
|
|
12
|
+
let parsed;
|
|
13
|
+
try {
|
|
14
|
+
parsed = JSON.parse(detail);
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed))
|
|
20
|
+
return false;
|
|
21
|
+
const values = Object.values(parsed);
|
|
22
|
+
return values.length > 0 && values.every((v) => typeof v === 'boolean');
|
|
23
|
+
}
|
|
24
|
+
/** The detail a kind may carry, or `undefined` when its shape does not admit this one. */
|
|
25
|
+
function admittedDetail(spec, detail) {
|
|
26
|
+
if (detail === undefined || spec === undefined)
|
|
27
|
+
return undefined;
|
|
28
|
+
switch (spec.detail) {
|
|
29
|
+
case 'none':
|
|
30
|
+
return undefined;
|
|
31
|
+
case 'id':
|
|
32
|
+
return ID_DETAIL.test(detail) ? detail : undefined;
|
|
33
|
+
case 'booleans':
|
|
34
|
+
return detail.length <= MAX_DETAIL_CHARS && isBooleansObject(detail) ? detail : undefined;
|
|
35
|
+
case 'fields':
|
|
36
|
+
return detail.slice(0, MAX_DETAIL_CHARS);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
6
39
|
export function createTelemetrySink(opts) {
|
|
7
40
|
const bootAt = Date.now();
|
|
8
|
-
const
|
|
9
|
-
|
|
41
|
+
const batches = {
|
|
42
|
+
health: { events: [], flushes: 0, maxFlushes: MAX_HEALTH_FLUSHES_PER_SESSION },
|
|
43
|
+
diagnostic: { events: [], flushes: 0, maxFlushes: MAX_DIAGNOSTIC_FLUSHES_PER_SESSION },
|
|
44
|
+
};
|
|
45
|
+
const order = ['health', 'diagnostic'];
|
|
10
46
|
let timer = null;
|
|
11
47
|
let disposed = false;
|
|
12
48
|
let firstFlushDone = false;
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
if (
|
|
16
|
-
return;
|
|
17
|
-
if (flushes >= MAX_FLUSHES_PER_SESSION)
|
|
49
|
+
const canFlush = (b) => batches[b].events.length > 0 && batches[b].flushes < batches[b].maxFlushes;
|
|
50
|
+
const send = (b) => {
|
|
51
|
+
if (!canFlush(b))
|
|
18
52
|
return;
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const events =
|
|
53
|
+
const batch = batches[b];
|
|
54
|
+
batch.flushes += 1;
|
|
55
|
+
const events = batch.events.splice(0, batch.events.length);
|
|
22
56
|
void opts
|
|
23
57
|
.callTool({
|
|
24
58
|
name: 'ggui_runtime_telemetry',
|
|
@@ -29,39 +63,44 @@ export function createTelemetrySink(opts) {
|
|
|
29
63
|
// cause the failures it exists to report.
|
|
30
64
|
});
|
|
31
65
|
};
|
|
66
|
+
const flush = () => {
|
|
67
|
+
timer = null;
|
|
68
|
+
if (disposed)
|
|
69
|
+
return;
|
|
70
|
+
firstFlushDone = true;
|
|
71
|
+
for (const b of order)
|
|
72
|
+
send(b);
|
|
73
|
+
};
|
|
32
74
|
const scheduleFlush = () => {
|
|
33
75
|
if (disposed || timer !== null)
|
|
34
76
|
return;
|
|
35
|
-
if (
|
|
77
|
+
if (!order.some(canFlush))
|
|
36
78
|
return;
|
|
37
79
|
timer = setTimeout(flush, firstFlushDone ? FLUSH_THROTTLE_MS : FIRST_FLUSH_DELAY_MS);
|
|
38
80
|
};
|
|
39
|
-
const
|
|
81
|
+
const append = (kind, detail) => {
|
|
40
82
|
if (disposed)
|
|
41
83
|
return;
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
84
|
+
const spec = SPEC_BY_KIND.get(kind);
|
|
85
|
+
// An unlisted kind (a caller past the types) travels as diagnostic, and without its detail.
|
|
86
|
+
const batch = batches[spec?.batch ?? 'diagnostic'];
|
|
87
|
+
if (batch.events.length >= RUNTIME_TELEMETRY_MAX_EVENTS)
|
|
88
|
+
batch.events.shift();
|
|
89
|
+
const kept = admittedDetail(spec, detail);
|
|
90
|
+
batch.events.push({
|
|
45
91
|
at: Math.max(0, Date.now() - bootAt),
|
|
46
92
|
kind: kind.slice(0, 64),
|
|
47
|
-
...(
|
|
93
|
+
...(kept !== undefined ? { detail: kept } : {}),
|
|
48
94
|
});
|
|
49
95
|
scheduleFlush();
|
|
50
96
|
};
|
|
51
|
-
const
|
|
52
|
-
try {
|
|
53
|
-
return JSON.stringify(fields).slice(0, MAX_DETAIL_CHARS);
|
|
54
|
-
}
|
|
55
|
-
catch {
|
|
56
|
-
return '[unserializable]';
|
|
57
|
-
}
|
|
58
|
-
};
|
|
97
|
+
const onChannelEvent = (event) => append(event, undefined);
|
|
59
98
|
return {
|
|
60
|
-
record,
|
|
99
|
+
record: (kind, detail) => append(kind, detail),
|
|
61
100
|
channelLogger: {
|
|
62
|
-
info:
|
|
63
|
-
warn:
|
|
64
|
-
debug:
|
|
101
|
+
info: onChannelEvent,
|
|
102
|
+
warn: onChannelEvent,
|
|
103
|
+
debug: onChannelEvent,
|
|
65
104
|
},
|
|
66
105
|
dispose() {
|
|
67
106
|
disposed = true;
|
package/dist/runtime.d.ts
CHANGED
|
@@ -29,14 +29,17 @@ import type { DrainAckPayload, JsonValue, JsonObject, GguiSession } from '@ggui-
|
|
|
29
29
|
import type { WebSocketMessage } from '@ggui-ai/protocol/transport/websocket';
|
|
30
30
|
import { type McpAppAiGguiRenderMeta, type McpAppBootstrapFailedMessage, type McpAppRendererReadyMessage } from '@ggui-ai/protocol/integrations/mcp-apps';
|
|
31
31
|
import type { GguiSessionSeedInput } from './types.js';
|
|
32
|
-
import type { McpAppAiGguiMetaParseFailureReason } from './types.js';
|
|
32
|
+
import type { HeldCredential, McpAppAiGguiMetaParseFailureReason, ViewRoot } from './types.js';
|
|
33
|
+
import type { ViewRootHolder } from './view-root.js';
|
|
34
|
+
import { type StreamSeqTracker } from './stream-seq.js';
|
|
33
35
|
import { App } from '@modelcontextprotocol/ext-apps';
|
|
34
36
|
import type { McpUiDisplayMode } from '@modelcontextprotocol/ext-apps';
|
|
35
37
|
import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
|
|
36
38
|
import type { GadgetPackageRegistry } from './globals.js';
|
|
37
39
|
import { StreamBus } from './wire-config.js';
|
|
38
40
|
import { type ChannelTransportRouter } from './channel-transport.js';
|
|
39
|
-
import {
|
|
41
|
+
import type { ChannelHandler } from '@ggui-ai/live-channel';
|
|
42
|
+
import { type TelemetrySink } from './runtime-telemetry.js';
|
|
40
43
|
import { type ConnectFn } from './registry-subscribe.js';
|
|
41
44
|
import { type StatusRefs } from './status-dom.js';
|
|
42
45
|
import { type RendererValidatorContext } from './validation.js';
|
|
@@ -44,6 +47,7 @@ import type { WireConfig } from '@ggui-ai/wire';
|
|
|
44
47
|
import { type ProtocolErrorEmitter } from './protocol-error.js';
|
|
45
48
|
import { type ObservabilityEmitter } from './observability.js';
|
|
46
49
|
import { type LifecycleEmitter } from './lifecycle.js';
|
|
50
|
+
import { type ContextSnapshotPoster } from './context-observer.js';
|
|
47
51
|
/**
|
|
48
52
|
* Drain-ack listener registry. Module-scoped so the dispatch path can
|
|
49
53
|
* register a frame-listener without threading it through the renderer.
|
|
@@ -92,12 +96,10 @@ export declare function setCurrentApp(app: App): void;
|
|
|
92
96
|
* the spec's `'light' | 'dark'` literals (dropped, never guessed —
|
|
93
97
|
* the same tolerant posture as `projectHostContext`).
|
|
94
98
|
*
|
|
95
|
-
* This is the INPUT leg of host-theme adaptation
|
|
96
|
-
* (rnd/gen-ui/beauty/experiments/002-host-theme-adaptation.md, ggui#551):
|
|
99
|
+
* This is the INPUT leg of host-theme adaptation (ggui#551):
|
|
97
100
|
* `hostContext.theme` alone reaches only `data-theme` +
|
|
98
101
|
* `color-scheme` via `applyDocumentTheme` — nothing in the token
|
|
99
|
-
* pipeline keys off it, so a dark host painted the light ladder
|
|
100
|
-
* (host-fit 46.0 vs 87.7 with the dark ladder, probe `cc728a8eb`).
|
|
102
|
+
* pipeline keys off it, so a dark host painted the light ladder.
|
|
101
103
|
* Under ggui#987 §4 the embedding host OWNS runtime mode: when it
|
|
102
104
|
* announces one, `buildOpts` paints it over every stamped opinion; the
|
|
103
105
|
* stamped layers decide only when the host is silent. Read live from
|
|
@@ -338,8 +340,10 @@ export interface BootSequenceOptions {
|
|
|
338
340
|
* canonical async toolresult) are skipped. The App handshake still
|
|
339
341
|
* runs — spec mandates `ui/initialize` regardless of how slice meta
|
|
340
342
|
* arrives, and `hostContext` is captured from `app.getHostContext()`.
|
|
343
|
+
* The slice's held credential, when it has one, is refreshed exactly
|
|
344
|
+
* as a slice this boot resolved itself (ggui#1496).
|
|
341
345
|
*/
|
|
342
|
-
readonly
|
|
346
|
+
readonly preResolved?: BootMeta;
|
|
343
347
|
/**
|
|
344
348
|
* Autostart-layer pre-resolution of the read-plane door (ggui#537):
|
|
345
349
|
* a tool result that arrived before `bootSequence` ran carried the
|
|
@@ -348,9 +352,17 @@ export interface BootSequenceOptions {
|
|
|
348
352
|
* before the App is connected, so the locator is threaded here and
|
|
349
353
|
* resolved via {@link resolveMetaViaReadDoor} right after the
|
|
350
354
|
* handshake, ahead of the Tier 2 tool-result wait. Ignored when
|
|
351
|
-
* `
|
|
355
|
+
* `preResolved` is set.
|
|
352
356
|
*/
|
|
353
357
|
readonly preResolvedLocator?: string;
|
|
358
|
+
/**
|
|
359
|
+
* Test seam for the boot refresh's waits (ggui#1496): the retry delay's
|
|
360
|
+
* `sleep` and `random`. Defaults to a `setTimeout` wait and `Math.random`.
|
|
361
|
+
*/
|
|
362
|
+
readonly credentialTiming?: {
|
|
363
|
+
readonly sleep?: (ms: number) => Promise<void>;
|
|
364
|
+
readonly random?: () => number;
|
|
365
|
+
};
|
|
354
366
|
/**
|
|
355
367
|
* How long to wait for the spec-canonical `ui/notifications/tool-result`
|
|
356
368
|
* notification (Tier 2 of the resolver chain) before failing with
|
|
@@ -359,6 +371,12 @@ export interface BootSequenceOptions {
|
|
|
359
371
|
* timeout so the spec doesn't hang.
|
|
360
372
|
*/
|
|
361
373
|
readonly toolResultTimeoutMs?: number;
|
|
374
|
+
/**
|
|
375
|
+
* Test seam for the view's key root (ggui#1415): the holder the boot and
|
|
376
|
+
* every later tool result offer their slice's root to. Defaults to the
|
|
377
|
+
* document's one holder.
|
|
378
|
+
*/
|
|
379
|
+
readonly viewRoots?: ViewRootHolder;
|
|
362
380
|
}
|
|
363
381
|
/**
|
|
364
382
|
* Renderer hooks. The real iframe boot plumbs these via
|
|
@@ -450,20 +468,23 @@ export interface RendererHandle {
|
|
|
450
468
|
*/
|
|
451
469
|
readonly channelTransport: ChannelTransportRouter;
|
|
452
470
|
/**
|
|
453
|
-
*
|
|
454
|
-
*
|
|
455
|
-
* `
|
|
456
|
-
* registry-bound transport is the sole dispatch surface — frames
|
|
457
|
-
* arrive directly through registered handlers, no longer through a
|
|
471
|
+
* The handlers for every frame type the iframe routes (`render`,
|
|
472
|
+
* `data`, `props_update`, `drain_ack`, `channel_payload`,
|
|
473
|
+
* `channel_error`). Frames arrive directly through them, not through a
|
|
458
474
|
* separate `onMessage` callback.
|
|
459
475
|
*
|
|
460
|
-
* `bootSequence`
|
|
461
|
-
*
|
|
462
|
-
*
|
|
463
|
-
* `subscribe-handshake` handlers (`ack`, `error`)
|
|
464
|
-
* `connectViaRegistry` and consumed during handshake resolution.
|
|
476
|
+
* `bootSequence`'s ladder set (ggui#1496) registers these same objects
|
|
477
|
+
* on each ladder's registry, one per credential, behind a gate that
|
|
478
|
+
* lets only the ladder holding the data deliver. `connectViaRegistry`
|
|
479
|
+
* adds the per-ladder `subscribe-handshake` handlers (`ack`, `error`).
|
|
465
480
|
*/
|
|
466
|
-
readonly
|
|
481
|
+
readonly channelHandlers: readonly ChannelHandler[];
|
|
482
|
+
/**
|
|
483
|
+
* The view's stream cursor (ggui#1496): the highest stamped `data`
|
|
484
|
+
* envelope `seq` applied. The `data` handler dedupes on it, and a new
|
|
485
|
+
* subscribe resumes the stream from it (`fromSeq`).
|
|
486
|
+
*/
|
|
487
|
+
readonly streamSeq: StreamSeqTracker;
|
|
467
488
|
/**
|
|
468
489
|
* 3rd-party gadget-package merge promise. `installGlobalRegistry`
|
|
469
490
|
* seeds `__ggui__.gadgets` synchronously with the STDLIB namespace;
|
|
@@ -489,18 +510,35 @@ export interface BootSequenceResult {
|
|
|
489
510
|
readonly mountedRender: GguiSession | GguiSessionSeedInput | null;
|
|
490
511
|
}
|
|
491
512
|
export declare function bootSequence(opts: BootSequenceOptions): Promise<BootSequenceResult>;
|
|
513
|
+
/**
|
|
514
|
+
* A resolved boot slice (ggui#1496): the meta to mount, and the expired
|
|
515
|
+
* live credential the parse held back from it, when there was one. Every
|
|
516
|
+
* boot resolver returns this, so the held credential survives every path
|
|
517
|
+
* to {@link bootSequence}, which refreshes it once through the host.
|
|
518
|
+
*/
|
|
519
|
+
export interface BootMeta {
|
|
520
|
+
readonly meta: McpAppAiGguiRenderMeta;
|
|
521
|
+
readonly held?: HeldCredential;
|
|
522
|
+
/** The slice's view key root, when it carries one (ggui#1415). */
|
|
523
|
+
readonly viewRoot?: ViewRoot;
|
|
524
|
+
}
|
|
525
|
+
/**
|
|
526
|
+
* The bridge rung's `tools/call` over the host relay (ggui#1415): every
|
|
527
|
+
* `ggui_runtime_pull` it sends goes through {@link withViewProof}.
|
|
528
|
+
*
|
|
529
|
+
* @internal — exported for unit tests + production reuse.
|
|
530
|
+
*/
|
|
531
|
+
export declare function bridgeCallToolVia(app: App): (name: string, args: Record<string, unknown>) => ReturnType<App['callServerTool']>;
|
|
492
532
|
/**
|
|
493
533
|
* Read `globalThis.__GGUI_META__` synchronously, validate against
|
|
494
|
-
* the unified slice-meta shape, and return the
|
|
534
|
+
* the unified slice-meta shape, and return the boot slice (or null
|
|
495
535
|
* on absence / malformation).
|
|
496
536
|
*
|
|
497
|
-
* Thin wrapper around {@link parseMetaFromGlobal}
|
|
498
|
-
*
|
|
499
|
-
*
|
|
500
|
-
* reader contract — the autostart resolver only needs the "valid
|
|
501
|
-
* meta or fall through" signal.
|
|
537
|
+
* Thin wrapper around {@link parseMetaFromGlobal}. Returns `null`
|
|
538
|
+
* instead of {@link McpAppAiGguiMetaParseResult} because the autostart
|
|
539
|
+
* resolver only needs the "a slice to boot, or fall through" signal.
|
|
502
540
|
*/
|
|
503
|
-
export declare function readSelfContainedMeta():
|
|
541
|
+
export declare function readSelfContainedMeta(): BootMeta | null;
|
|
504
542
|
/**
|
|
505
543
|
* Project the inline `__GGUI_META__` bootstrap into a
|
|
506
544
|
* {@link GguiSessionSeedInput} the mount surface can paint immediately,
|
|
@@ -566,7 +604,7 @@ export declare function hasStaticContentMeta(meta: McpAppAiGguiRenderMeta): bool
|
|
|
566
604
|
* lagged the protocol" bug class: shell + runtime drift becomes
|
|
567
605
|
* impossible because the shell never inspects the slice-meta shape.
|
|
568
606
|
*/
|
|
569
|
-
export declare function extractMetaFromToolResult(params: unknown):
|
|
607
|
+
export declare function extractMetaFromToolResult(params: unknown): BootMeta | null;
|
|
570
608
|
/**
|
|
571
609
|
* Drain `window.__GGUI_PENDING_TOOL_RESULTS__` — the buffer the
|
|
572
610
|
* minimal shell populates while messages arrive between
|
|
@@ -585,7 +623,7 @@ export declare function extractMetaFromToolResult(params: unknown): McpAppAiGgui
|
|
|
585
623
|
* notifications, in arrival order (shells cap it newest-biased). The
|
|
586
624
|
* runtime reads it once at autostart.
|
|
587
625
|
*/
|
|
588
|
-
export declare function readPendingToolResults():
|
|
626
|
+
export declare function readPendingToolResults(): BootMeta | null;
|
|
589
627
|
/**
|
|
590
628
|
* Sibling of {@link readPendingToolResults} for the read-plane-only
|
|
591
629
|
* posture (ggui#537): the NEWEST buffered tool result that carries a
|
|
@@ -608,20 +646,20 @@ export declare function readPendingToolResultLocator(): string | null;
|
|
|
608
646
|
* slice would be. `null` on any miss (host cannot proxy reads, resource
|
|
609
647
|
* gone, no envelope) — the caller's other tiers still decide.
|
|
610
648
|
*/
|
|
611
|
-
export declare function resolveMetaViaReadDoor(app: App, locator: string): Promise<
|
|
649
|
+
export declare function resolveMetaViaReadDoor(app: App, locator: string): Promise<BootMeta | null>;
|
|
612
650
|
/**
|
|
613
|
-
*
|
|
614
|
-
*
|
|
615
|
-
* readable, not a JSON dump. Falls back to truncated JSON for
|
|
616
|
-
* nested values so the prompt doesn't drop information silently.
|
|
651
|
+
* Production {@link ContextSnapshotPoster} — the seam the
|
|
652
|
+
* context-observer factories consume. Splits the two destinations:
|
|
617
653
|
*
|
|
618
|
-
*
|
|
619
|
-
*
|
|
620
|
-
*
|
|
621
|
-
*
|
|
622
|
-
*
|
|
654
|
+
* - `postUpdateModelContext` → spec-canonical
|
|
655
|
+
* `app.updateModelContext(...)` (via {@link callAppUpdateModelContext}).
|
|
656
|
+
* - `postContextMirror` → raw `tools/call ggui_runtime_sync_context`
|
|
657
|
+
* via {@link postToParent} (the host-relay mirror path; not yet
|
|
658
|
+
* migrated to `app.callServerTool` — see emitAudit for the same
|
|
659
|
+
* posture).
|
|
623
660
|
*/
|
|
624
|
-
|
|
661
|
+
/** @internal — exported for unit tests. */
|
|
662
|
+
export declare const productionContextSnapshotPoster: ContextSnapshotPoster;
|
|
625
663
|
/**
|
|
626
664
|
* Fire a single canonical action envelope (`tools/call
|
|
627
665
|
* ggui_runtime_submit_action`) carrying the typed `{kind, payload, …}` shape
|
|
@@ -683,6 +721,76 @@ interface ToastLiveRegions {
|
|
|
683
721
|
* @internal — exported for unit tests.
|
|
684
722
|
*/
|
|
685
723
|
export declare function ensureToastAnnouncer(doc: Document): ToastLiveRegions | null;
|
|
724
|
+
/**
|
|
725
|
+
* The drawn toast for a dispatched action: a fixed-position element at
|
|
726
|
+
* the bottom of the iframe, drawn only when the visitor has to know or do
|
|
727
|
+
* something (the copy is {@link GESTURE_COPY}). Two kinds reach it:
|
|
728
|
+
* - `'error'` — the gesture did not go through: the relay refused it or
|
|
729
|
+
* could not be reached, or the card's own contract refused it. Nothing
|
|
730
|
+
* reached the pipe, so no `ui/message` doorbell fires (SPEC §4.7
|
|
731
|
+
* "Failure-soft client posture");
|
|
732
|
+
* - `'action_required'` — the gesture is queued but no consumer is
|
|
733
|
+
* listening and this host cannot take (or refused) the wake-up, so the
|
|
734
|
+
* visitor must send a message to continue.
|
|
735
|
+
* A tap that went through, or a wake-up the host took, is spoken, never
|
|
736
|
+
* drawn.
|
|
737
|
+
*
|
|
738
|
+
* Without it, a failed gesture would be silent: the visitor could not tell
|
|
739
|
+
* "the agent is busy" from "the tap was lost".
|
|
740
|
+
*
|
|
741
|
+
* Direct DOM (not React-managed) because:
|
|
742
|
+
* - Works during boot before React mounts.
|
|
743
|
+
* - Doesn't interfere with the component tree the generator owns.
|
|
744
|
+
* - Survives React mount transitions inside the iframe.
|
|
745
|
+
*
|
|
746
|
+
* Single global toast (per iframe). Auto-dismisses after 2.5s on
|
|
747
|
+
* `success` / `fallback` outcomes. A successful dispatch draws nothing
|
|
748
|
+
* here (ggui#1444): its "sending" is spoken only (see
|
|
749
|
+
* {@link GESTURE_COPY}).
|
|
750
|
+
*
|
|
751
|
+
* The visible element is only half the surface (ggui#447). Every
|
|
752
|
+
* message is also spoken through {@link ensureToastAnnouncer}'s live
|
|
753
|
+
* regions, and the element itself is hidden from the accessibility
|
|
754
|
+
* tree so the sentence is not read twice. The one exception is
|
|
755
|
+
* `action_required`, the only toast the user must OPERATE: it re-enters
|
|
756
|
+
* the tree as a named button with a tab stop, because a dismissal that
|
|
757
|
+
* needs a mouse is a dismissal some users cannot perform.
|
|
758
|
+
*
|
|
759
|
+
* @internal — runtime-layer concern.
|
|
760
|
+
*/
|
|
761
|
+
/**
|
|
762
|
+
* The `label` an action declares on the render's `actionSpec`, trimmed, or
|
|
763
|
+
* `undefined` when the render declares none (or is a system / MCP-Apps
|
|
764
|
+
* render, which carries no actionSpec). By contract the label is the
|
|
765
|
+
* control's own copy, so it is the one name for a tap a visitor may read
|
|
766
|
+
* (see {@link GESTURE_COPY}).
|
|
767
|
+
*
|
|
768
|
+
* @internal — exported for unit tests.
|
|
769
|
+
*/
|
|
770
|
+
export declare function declaredActionLabel(render: GguiSession | GguiSessionSeedInput | null, actionName: string): string | undefined;
|
|
771
|
+
/** @internal — exported for unit tests. */
|
|
772
|
+
export declare function isActionSpentByRefusal(renderId: string, actionName: string): boolean;
|
|
773
|
+
/**
|
|
774
|
+
* ggui#1424 — a post-ack `error` frame from the live channel. The channel
|
|
775
|
+
* door refuses a second gesture on a spent one-shot with its
|
|
776
|
+
* CONTRACT_VIOLATION frame and a violation at `actionSpec.<name>.oneShot`;
|
|
777
|
+
* that one means done, the same as the tool answer. Any other error frame
|
|
778
|
+
* changes nothing here.
|
|
779
|
+
*
|
|
780
|
+
* @internal — exported for unit tests.
|
|
781
|
+
*/
|
|
782
|
+
export declare function handlePostAckErrorFrame(renderId: string, payload: {
|
|
783
|
+
readonly code: string;
|
|
784
|
+
readonly message?: string;
|
|
785
|
+
readonly details?: unknown;
|
|
786
|
+
}): void;
|
|
787
|
+
/**
|
|
788
|
+
* The toast for a tap the outbound contract check refused (ggui#1536): the
|
|
789
|
+
* ordinary error toast, named by the action's declared label.
|
|
790
|
+
*
|
|
791
|
+
* @internal — exported for unit tests + production reuse.
|
|
792
|
+
*/
|
|
793
|
+
export declare function showRefusedDispatchToast(label: string | undefined): void;
|
|
686
794
|
/**
|
|
687
795
|
* Boot-time reset of the relay latch (ggui#670 Phase 3). A latch
|
|
688
796
|
* standing from a prior mount in a re-booted document is closed through
|
|
@@ -694,6 +802,12 @@ export declare function ensureToastAnnouncer(doc: Document): ToastLiveRegions |
|
|
|
694
802
|
*/
|
|
695
803
|
export declare function resetRelayLatchForBoot(): void;
|
|
696
804
|
/** @internal — exported for unit tests to reset module state. */
|
|
805
|
+
/**
|
|
806
|
+
* Test-only: install the telemetry sink the runtime records into (normally
|
|
807
|
+
* set once at boot), so a test can observe the events a code path posts.
|
|
808
|
+
* `null` restores the no-sink state.
|
|
809
|
+
*/
|
|
810
|
+
export declare function __setTelemetrySinkForTest(sink: TelemetrySink | null): void;
|
|
697
811
|
export declare function __resetRelayNoticeForTest(): void;
|
|
698
812
|
/**
|
|
699
813
|
* Dispatch a submit-action via the empirically-validated bridge
|
|
@@ -714,6 +828,8 @@ export declare function dispatchSubmitAction(args: {
|
|
|
714
828
|
readonly data: unknown;
|
|
715
829
|
readonly sessionId: string;
|
|
716
830
|
readonly appId: string;
|
|
831
|
+
/** The action's declared `label`, when it has one — see {@link GESTURE_COPY}. */
|
|
832
|
+
readonly label?: string;
|
|
717
833
|
}): void;
|
|
718
834
|
/**
|
|
719
835
|
* Channel-transport router's `tools/call` invoker (ggui#440) —
|
|
@@ -809,6 +925,8 @@ export declare function routeDispatch(args: {
|
|
|
809
925
|
readonly appId: string;
|
|
810
926
|
};
|
|
811
927
|
readonly dispatchToolName: string;
|
|
928
|
+
/** The action's declared `label` (visitor copy by contract), when it has one. */
|
|
929
|
+
readonly label?: string;
|
|
812
930
|
}): void;
|
|
813
931
|
/**
|
|
814
932
|
* `ui/open-link` direct dispatch helper — used by the anchor-click
|