@ggui-ai/iframe-runtime 0.24.0 → 0.25.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/iframe-runtime.js +66 -65
- package/dist/iframe-runtime.meta.json +1 -1
- package/dist/observability.d.ts +16 -10
- package/dist/react-renderer.d.ts +2 -0
- package/dist/react-renderer.js +96 -3
- package/dist/registry-subscribe.d.ts +16 -1
- package/dist/registry-subscribe.js +29 -0
- package/dist/render-item.d.ts +3 -1
- package/dist/render-item.js +5 -2
- package/dist/runtime.d.ts +56 -17
- package/dist/runtime.js +182 -73
- package/dist/shims/wire.js +1 -1
- package/dist/wire-config.d.ts +2 -1
- package/dist/wire-config.js +3 -0
- package/package.json +7 -7
package/dist/observability.d.ts
CHANGED
|
@@ -296,14 +296,20 @@ export interface RelayDeadTapEvent {
|
|
|
296
296
|
readonly appId?: string;
|
|
297
297
|
}
|
|
298
298
|
/**
|
|
299
|
-
* The mount painted NOTHING
|
|
300
|
-
*
|
|
301
|
-
*
|
|
302
|
-
*
|
|
303
|
-
*
|
|
304
|
-
*
|
|
305
|
-
*
|
|
306
|
-
*
|
|
299
|
+
* The mount painted NOTHING. The host is told which mount moment it was and
|
|
300
|
+
* why, because a card that renders nothing is invisible on every other
|
|
301
|
+
* channel. Two reasons are a renderer that held no component (ggui#1103), so
|
|
302
|
+
* the scope carries only its stylesheet and no error boundary engages:
|
|
303
|
+
* `'no-code'` is a payload that carried no component code, `'eval-failed'` a
|
|
304
|
+
* module that threw while evaluating. Three are a component that WAS held
|
|
305
|
+
* and still painted nothing (ggui#1104): `'rendered-nothing'` — the tree
|
|
306
|
+
* committed and the scope holds no element; `'detached'` — the tree
|
|
307
|
+
* committed into a scope no longer in the document (another mount took the
|
|
308
|
+
* container); `'no-commit'` — the tree never committed within the renderer's
|
|
309
|
+
* deadline (a component that suspends with no Suspense boundary). Names the
|
|
310
|
+
* moment only — never the code, the props or the error text, which stay in
|
|
311
|
+
* the operator-visible console line. A host MUST treat an unknown reason as
|
|
312
|
+
* a blank of unknown cause.
|
|
307
313
|
*
|
|
308
314
|
* @public
|
|
309
315
|
*/
|
|
@@ -311,8 +317,8 @@ export interface ComponentEmptyEvent {
|
|
|
311
317
|
readonly kind: 'component-empty';
|
|
312
318
|
/** Which lifecycle moment painted nothing. */
|
|
313
319
|
readonly where: 'mount' | 'update';
|
|
314
|
-
/** Why the
|
|
315
|
-
readonly reason: 'no-code' | 'eval-failed';
|
|
320
|
+
/** Why the moment painted nothing (see above). */
|
|
321
|
+
readonly reason: 'no-code' | 'eval-failed' | 'rendered-nothing' | 'detached' | 'no-commit';
|
|
316
322
|
}
|
|
317
323
|
/**
|
|
318
324
|
* Catch-all branch for event kinds the host's typings don't recognize.
|
package/dist/react-renderer.d.ts
CHANGED
|
@@ -31,6 +31,8 @@
|
|
|
31
31
|
*/
|
|
32
32
|
import React, { type ReactNode } from 'react';
|
|
33
33
|
import type { AppTheme } from '@ggui-ai/protocol/wire';
|
|
34
|
+
/** Test hook: shorten the no-commit deadline; `null` restores the default. */
|
|
35
|
+
export declare function __setNoCommitDeadlineForTest(ms: number | null): void;
|
|
34
36
|
export interface ReactRootMountOptions {
|
|
35
37
|
/**
|
|
36
38
|
* GguiSession whose componentCode + props drive the mount. Only
|
package/dist/react-renderer.js
CHANGED
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
* `React.createElement` + `React.Fragment` directly so the renderer's
|
|
30
30
|
* TypeScript config doesn't need a JSX runtime plumbing.
|
|
31
31
|
*/
|
|
32
|
-
import React, { Component, createElement, Fragment, } from 'react';
|
|
32
|
+
import React, { Component, createElement, Fragment, useEffect, } from 'react';
|
|
33
33
|
import { createRoot } from 'react-dom/client';
|
|
34
34
|
import { postObservabilityToParent } from './observability.js';
|
|
35
35
|
import { trackThemeFontFaces } from './host-fonts.js';
|
|
@@ -69,6 +69,39 @@ function makeScopeClass() {
|
|
|
69
69
|
* re-sanitize here.
|
|
70
70
|
*/
|
|
71
71
|
// =============================================================================
|
|
72
|
+
// Paint probe (ggui#1104) — the blanks a HELD component can still produce.
|
|
73
|
+
// =============================================================================
|
|
74
|
+
/**
|
|
75
|
+
* How long a render may go without committing before it is named a
|
|
76
|
+
* `no-commit` blank. A healthy commit lands within a frame or two of
|
|
77
|
+
* `root.render`; a component that suspends with no Suspense boundary never
|
|
78
|
+
* commits at all, and until now that blank said nothing on any channel.
|
|
79
|
+
*/
|
|
80
|
+
const NO_COMMIT_DEADLINE_MS = 5_000;
|
|
81
|
+
let noCommitDeadlineMs = NO_COMMIT_DEADLINE_MS;
|
|
82
|
+
/** Test hook: shorten the no-commit deadline; `null` restores the default. */
|
|
83
|
+
export function __setNoCommitDeadlineForTest(ms) {
|
|
84
|
+
noCommitDeadlineMs = ms ?? NO_COMMIT_DEADLINE_MS;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Rendered inside the scope; its PASSIVE effect runs once the whole commit is
|
|
88
|
+
* done, refs included. (A layout effect would not do: layout effects run
|
|
89
|
+
* child-first, before the parent scope `<div>`'s ref is attached, so the
|
|
90
|
+
* probe would read no scope and misname a healthy mount `detached`.) Renders
|
|
91
|
+
* nothing itself.
|
|
92
|
+
*/
|
|
93
|
+
function CommitProbe(props) {
|
|
94
|
+
useEffect(() => {
|
|
95
|
+
props.onCommit();
|
|
96
|
+
});
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
const HELD_BLANK_DETAIL = {
|
|
100
|
+
'rendered-nothing': 'the component rendered no element',
|
|
101
|
+
detached: 'the tree committed into a scope that is no longer in the document (another mount took the container)',
|
|
102
|
+
'no-commit': 'the tree did not commit (a component that suspends with no Suspense boundary never commits)',
|
|
103
|
+
};
|
|
104
|
+
// =============================================================================
|
|
72
105
|
// Error boundary — port of RCR's internal ErrorBoundary (L68–203).
|
|
73
106
|
// =============================================================================
|
|
74
107
|
const AUTO_RETRY_LIMIT = 1;
|
|
@@ -212,6 +245,52 @@ export async function mountReactRoot(container, opts) {
|
|
|
212
245
|
let currentOpts = opts;
|
|
213
246
|
let currentCode = null;
|
|
214
247
|
let currentComponent = null;
|
|
248
|
+
// ggui#1104 — the paint probe's state. `heldAtRender` is whether the render
|
|
249
|
+
// being committed carried a component (when it did not, ggui#1103's
|
|
250
|
+
// `reportNoComponent` has already named the blank). A blank is reported
|
|
251
|
+
// once per episode: `blankReported` resets when a commit shows paint.
|
|
252
|
+
let scopeEl = null;
|
|
253
|
+
let committedOnce = false;
|
|
254
|
+
let heldAtRender = false;
|
|
255
|
+
let blankReported = false;
|
|
256
|
+
let commitTimer = null;
|
|
257
|
+
function reportHeldBlank(where, reason) {
|
|
258
|
+
if (blankReported)
|
|
259
|
+
return;
|
|
260
|
+
blankReported = true;
|
|
261
|
+
// eslint-disable-next-line no-console -- operator-visible failure signal
|
|
262
|
+
console.warn(`[ggui] mountReactRoot: the ${where} painted nothing (${reason}) — ${HELD_BLANK_DETAIL[reason]}`);
|
|
263
|
+
try {
|
|
264
|
+
postObservabilityToParent({ kind: 'component-empty', where, reason });
|
|
265
|
+
}
|
|
266
|
+
catch {
|
|
267
|
+
// Best-effort seam, as in reportNoComponent: never turn a blank into a throw.
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
const setScopeEl = (el) => {
|
|
271
|
+
scopeEl = el;
|
|
272
|
+
};
|
|
273
|
+
const onCommit = () => {
|
|
274
|
+
if (commitTimer !== null) {
|
|
275
|
+
clearTimeout(commitTimer);
|
|
276
|
+
commitTimer = null;
|
|
277
|
+
}
|
|
278
|
+
const where = committedOnce ? 'update' : 'mount';
|
|
279
|
+
committedOnce = true;
|
|
280
|
+
if (!heldAtRender)
|
|
281
|
+
return;
|
|
282
|
+
const scope = scopeEl;
|
|
283
|
+
if (scope === null || !scope.isConnected) {
|
|
284
|
+
reportHeldBlank(where, 'detached');
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
const painted = Array.from(scope.children).some((child) => child.tagName !== 'STYLE');
|
|
288
|
+
if (painted) {
|
|
289
|
+
blankReported = false;
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
reportHeldBlank(where, 'rendered-nothing');
|
|
293
|
+
};
|
|
215
294
|
async function evaluate(code) {
|
|
216
295
|
// 0. If code is a URL (S3 presigned), fetch the bytes first.
|
|
217
296
|
// Matches RCR's first-step URL-fetch guard verbatim.
|
|
@@ -382,10 +461,21 @@ export async function mountReactRoot(container, opts) {
|
|
|
382
461
|
// The scope `<div>` + inline `<style>` + error-boundary-wrapped
|
|
383
462
|
// children match RCR L376–383. `key={currentCode?.length}` mirrors
|
|
384
463
|
// RCR's "reset boundary when code changes" heuristic.
|
|
385
|
-
root.render(createElement('div', { className: scopeClass }, createElement('style', null, themeCss), createElement(RcrErrorBoundary, {
|
|
464
|
+
root.render(createElement('div', { className: scopeClass, ref: setScopeEl }, createElement('style', null, themeCss), createElement(RcrErrorBoundary, {
|
|
386
465
|
key: currentCode?.length ?? 0,
|
|
387
466
|
...(opts.onError ? { onError: opts.onError } : {}),
|
|
388
|
-
}, createElement(Fragment, null, wrapped))));
|
|
467
|
+
}, createElement(Fragment, null, wrapped)), createElement(CommitProbe, { onCommit })));
|
|
468
|
+
// ggui#1104 — a held component must commit. Armed only when this render
|
|
469
|
+
// carries one; the probe's commit disarms it.
|
|
470
|
+
heldAtRender = currentComponent !== null;
|
|
471
|
+
if (commitTimer !== null)
|
|
472
|
+
clearTimeout(commitTimer);
|
|
473
|
+
commitTimer = heldAtRender
|
|
474
|
+
? setTimeout(() => {
|
|
475
|
+
commitTimer = null;
|
|
476
|
+
reportHeldBlank(committedOnce ? 'update' : 'mount', 'no-commit');
|
|
477
|
+
}, noCommitDeadlineMs)
|
|
478
|
+
: null;
|
|
389
479
|
}
|
|
390
480
|
/**
|
|
391
481
|
* The mount painted NOTHING (ggui#1103). `wrapped` is `null` whenever the
|
|
@@ -484,6 +574,9 @@ export async function mountReactRoot(container, opts) {
|
|
|
484
574
|
}
|
|
485
575
|
},
|
|
486
576
|
unmount() {
|
|
577
|
+
if (commitTimer !== null)
|
|
578
|
+
clearTimeout(commitTimer);
|
|
579
|
+
commitTimer = null;
|
|
487
580
|
root.unmount();
|
|
488
581
|
container.replaceChildren();
|
|
489
582
|
},
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { AckPayload } from '@ggui-ai/protocol/wire';
|
|
2
2
|
import type { ConnectionStatus } from '@ggui-ai/protocol/transport/websocket';
|
|
3
3
|
import type { McpAppAiGguiRenderMeta } from '@ggui-ai/protocol/integrations/mcp-apps';
|
|
4
|
-
import type { AnyTransportHandle, ChannelLogger, ChannelRegistry, RegistryPollingOptions, RegistrySseOptions, WsTransportHandle } from '@ggui-ai/live-channel';
|
|
4
|
+
import type { AnyTransportHandle, ChannelLogger, ChannelRegistry, FrameClassifier, RegistryPollingOptions, RegistrySseOptions, WsTransportHandle } from '@ggui-ai/live-channel';
|
|
5
5
|
import { type ProtocolErrorEmitter } from './protocol-error.js';
|
|
6
6
|
import type { ObservabilityEmitter } from './observability.js';
|
|
7
7
|
/**
|
|
@@ -130,6 +130,21 @@ export interface ConnectViaRegistryOptions {
|
|
|
130
130
|
*/
|
|
131
131
|
readonly logger?: ChannelLogger;
|
|
132
132
|
}
|
|
133
|
+
/**
|
|
134
|
+
* The iframe's verdict on channel frames for the WebSocket retry budget
|
|
135
|
+
* (ggui#1496), handed to every `ChannelRegistry` the runtime builds.
|
|
136
|
+
*
|
|
137
|
+
* - An `ack` is an accepted subscription, so the budget resets. It no
|
|
138
|
+
* longer resets when a socket merely opens: a socket the server opens
|
|
139
|
+
* and then refuses at subscribe used to reset it on every attempt, so
|
|
140
|
+
* the transport never reached `'failed'` and the failover never moved.
|
|
141
|
+
* - An `error` whose code is a {@link PRE_ACK_AUTH_CODES} member is a
|
|
142
|
+
* refusal no retry of the same credential can fix (the subscribe is
|
|
143
|
+
* rebuilt from the same `wsToken` each attempt), so the transport
|
|
144
|
+
* fails at once and the ladder moves to its next rung. The same list
|
|
145
|
+
* classifies the handshake's typed error, so the two cannot drift.
|
|
146
|
+
*/
|
|
147
|
+
export declare const classifyChannelFrame: FrameClassifier;
|
|
133
148
|
/**
|
|
134
149
|
* Open the WS via the registry, send subscribe (composed by the
|
|
135
150
|
* registry's `subscribeFrameBuilder`), await the first `ack`. Resolves
|
|
@@ -110,6 +110,35 @@ const PRE_ACK_AUTH_CODES = new Set([
|
|
|
110
110
|
function isAuthFailureCode(code) {
|
|
111
111
|
return PRE_ACK_AUTH_CODES.has(code);
|
|
112
112
|
}
|
|
113
|
+
/**
|
|
114
|
+
* The iframe's verdict on channel frames for the WebSocket retry budget
|
|
115
|
+
* (ggui#1496), handed to every `ChannelRegistry` the runtime builds.
|
|
116
|
+
*
|
|
117
|
+
* - An `ack` is an accepted subscription, so the budget resets. It no
|
|
118
|
+
* longer resets when a socket merely opens: a socket the server opens
|
|
119
|
+
* and then refuses at subscribe used to reset it on every attempt, so
|
|
120
|
+
* the transport never reached `'failed'` and the failover never moved.
|
|
121
|
+
* - An `error` whose code is a {@link PRE_ACK_AUTH_CODES} member is a
|
|
122
|
+
* refusal no retry of the same credential can fix (the subscribe is
|
|
123
|
+
* rebuilt from the same `wsToken` each attempt), so the transport
|
|
124
|
+
* fails at once and the ladder moves to its next rung. The same list
|
|
125
|
+
* classifies the handshake's typed error, so the two cannot drift.
|
|
126
|
+
*/
|
|
127
|
+
export const classifyChannelFrame = (frame) => {
|
|
128
|
+
if (frame.type === 'ack')
|
|
129
|
+
return 'accepted';
|
|
130
|
+
if (frame.type !== 'error')
|
|
131
|
+
return undefined;
|
|
132
|
+
const payload = frame.payload;
|
|
133
|
+
if (typeof payload === 'object' &&
|
|
134
|
+
payload !== null &&
|
|
135
|
+
'code' in payload &&
|
|
136
|
+
typeof payload.code === 'string' &&
|
|
137
|
+
isAuthFailureCode(payload.code)) {
|
|
138
|
+
return 'refused-terminal';
|
|
139
|
+
}
|
|
140
|
+
return undefined;
|
|
141
|
+
};
|
|
113
142
|
/**
|
|
114
143
|
* Map a pre-ack `error` frame onto a non-UPGRADE_REQUIRED ProtocolError.
|
|
115
144
|
* {@link PRE_ACK_AUTH_CODES} → `'auth'`; every other code → `'protocol'`
|
package/dist/render-item.d.ts
CHANGED
|
@@ -12,7 +12,9 @@ export interface RenderItemOptions {
|
|
|
12
12
|
* today's DynamicComponent fallback when no GguiRender parent is
|
|
13
13
|
* present). The config carries the card's `actionSpent` source
|
|
14
14
|
* (ggui#1223), and the component mounts inside `ActionSpentContext` too,
|
|
15
|
-
* so `useActionSpent` reads the one-shot guard's own spent set
|
|
15
|
+
* so `useActionSpent` reads the one-shot guard's own spent set; likewise
|
|
16
|
+
* `actionPending` inside `ActionPendingContext` (ggui#1398), so
|
|
17
|
+
* `useActionPending` reads the card's in-flight dispatches. */
|
|
16
18
|
readonly scopedWireConfig: BuiltWireConfig | null;
|
|
17
19
|
/** Shared stream bus — provisional renderer subscribes to
|
|
18
20
|
* `_ggui:preview` for this render. */
|
package/dist/render-item.js
CHANGED
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
*/
|
|
40
40
|
import { createElement } from 'react';
|
|
41
41
|
import { GguiWireProvider } from '@ggui-ai/wire';
|
|
42
|
-
import { ActionSpentContext } from '@ggui-ai/wire/internal';
|
|
42
|
+
import { ActionPendingContext, ActionSpentContext } from '@ggui-ai/wire/internal';
|
|
43
43
|
import { mountReactRoot } from './react-renderer.js';
|
|
44
44
|
import { mountProvisional, } from './provisional-renderer.js';
|
|
45
45
|
import { mountMcpAppIframe, } from './mcp-app-iframe-host.js';
|
|
@@ -214,7 +214,10 @@ export async function mountRender(container, opts) {
|
|
|
214
214
|
? mountedComponent
|
|
215
215
|
: createElement(GguiWireProvider, {
|
|
216
216
|
config,
|
|
217
|
-
children: createElement(ActionSpentContext.Provider, {
|
|
217
|
+
children: createElement(ActionSpentContext.Provider, {
|
|
218
|
+
value: config.actionSpent,
|
|
219
|
+
children: createElement(ActionPendingContext.Provider, { value: config.actionPending, children: mountedComponent }),
|
|
220
|
+
}),
|
|
218
221
|
});
|
|
219
222
|
return currentOpts.wrapOuter !== undefined
|
|
220
223
|
? currentOpts.wrapOuter(wireWrapped)
|
package/dist/runtime.d.ts
CHANGED
|
@@ -37,6 +37,7 @@ import type { GadgetPackageRegistry } from './globals.js';
|
|
|
37
37
|
import { StreamBus } from './wire-config.js';
|
|
38
38
|
import { type ChannelTransportRouter } from './channel-transport.js';
|
|
39
39
|
import { ChannelRegistry } from '@ggui-ai/live-channel';
|
|
40
|
+
import { type TelemetrySink } from './runtime-telemetry.js';
|
|
40
41
|
import { type ConnectFn } from './registry-subscribe.js';
|
|
41
42
|
import { type StatusRefs } from './status-dom.js';
|
|
42
43
|
import { type RendererValidatorContext } from './validation.js';
|
|
@@ -92,12 +93,10 @@ export declare function setCurrentApp(app: App): void;
|
|
|
92
93
|
* the spec's `'light' | 'dark'` literals (dropped, never guessed —
|
|
93
94
|
* the same tolerant posture as `projectHostContext`).
|
|
94
95
|
*
|
|
95
|
-
* This is the INPUT leg of host-theme adaptation
|
|
96
|
-
* (rnd/gen-ui/beauty/experiments/002-host-theme-adaptation.md, ggui#551):
|
|
96
|
+
* This is the INPUT leg of host-theme adaptation (ggui#551):
|
|
97
97
|
* `hostContext.theme` alone reaches only `data-theme` +
|
|
98
98
|
* `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`).
|
|
99
|
+
* pipeline keys off it, so a dark host painted the light ladder.
|
|
101
100
|
* Under ggui#987 §4 the embedding host OWNS runtime mode: when it
|
|
102
101
|
* announces one, `buildOpts` paints it over every stamped opinion; the
|
|
103
102
|
* stamped layers decide only when the host is silent. Read live from
|
|
@@ -609,19 +608,6 @@ export declare function readPendingToolResultLocator(): string | null;
|
|
|
609
608
|
* gone, no envelope) — the caller's other tiers still decide.
|
|
610
609
|
*/
|
|
611
610
|
export declare function resolveMetaViaReadDoor(app: App, locator: string): Promise<McpAppAiGguiRenderMeta | null>;
|
|
612
|
-
/**
|
|
613
|
-
* Render a submit-action's `data` payload as a short inline string
|
|
614
|
-
* for embedding in a `ui/message` consent prompt. Goal: human-
|
|
615
|
-
* readable, not a JSON dump. Falls back to truncated JSON for
|
|
616
|
-
* nested values so the prompt doesn't drop information silently.
|
|
617
|
-
*
|
|
618
|
-
* Exported for unit-testing — an earlier implementation returned `''`
|
|
619
|
-
* for primitive payloads (strings/numbers/booleans), which silently
|
|
620
|
-
* vaporised the chip's actual text from the consent prompt and made
|
|
621
|
-
* the LLM think every dispatch was a contentless "Please proceed
|
|
622
|
-
* with **<intent>**" request.
|
|
623
|
-
*/
|
|
624
|
-
export declare function formatSubmitActionDataInline(data: unknown): string;
|
|
625
611
|
/**
|
|
626
612
|
* Fire a single canonical action envelope (`tools/call
|
|
627
613
|
* ggui_runtime_submit_action`) carrying the typed `{kind, payload, …}` shape
|
|
@@ -683,6 +669,49 @@ interface ToastLiveRegions {
|
|
|
683
669
|
* @internal — exported for unit tests.
|
|
684
670
|
*/
|
|
685
671
|
export declare function ensureToastAnnouncer(doc: Document): ToastLiveRegions | null;
|
|
672
|
+
/**
|
|
673
|
+
* Lightweight toast UX surface for dispatched actions. Renders a
|
|
674
|
+
* fixed-position element at the bottom of the iframe so the user
|
|
675
|
+
* gets immediate visual feedback that their gesture was registered
|
|
676
|
+
* — "→ Sending action: archive" → "✓ Queued — agent will react"
|
|
677
|
+
* (or "💬 Sent to chat" on ui/message fallback).
|
|
678
|
+
*
|
|
679
|
+
* Without this, the iframe is silent during dispatch. User clicks
|
|
680
|
+
* and waits, with no way to distinguish "click was received but the
|
|
681
|
+
* agent is busy" from "click was lost". Especially load-bearing in
|
|
682
|
+
* the consume-pipe vs ui/message dual-path era — the toast tells
|
|
683
|
+
* the user which path the gesture actually took.
|
|
684
|
+
*
|
|
685
|
+
* Direct DOM (not React-managed) because:
|
|
686
|
+
* - Works during boot before React mounts.
|
|
687
|
+
* - Doesn't interfere with the component tree the generator owns.
|
|
688
|
+
* - Survives React mount transitions inside the iframe.
|
|
689
|
+
*
|
|
690
|
+
* Single global toast (per iframe). Auto-dismisses after 2.5s on
|
|
691
|
+
* `success` / `fallback` outcomes. A successful dispatch draws nothing
|
|
692
|
+
* here (ggui#1444): its "sending" is spoken only (see
|
|
693
|
+
* {@link GESTURE_COPY}).
|
|
694
|
+
*
|
|
695
|
+
* The visible element is only half the surface (ggui#447). Every
|
|
696
|
+
* message is also spoken through {@link ensureToastAnnouncer}'s live
|
|
697
|
+
* regions, and the element itself is hidden from the accessibility
|
|
698
|
+
* tree so the sentence is not read twice. The one exception is
|
|
699
|
+
* `action_required`, the only toast the user must OPERATE: it re-enters
|
|
700
|
+
* the tree as a named button with a tab stop, because a dismissal that
|
|
701
|
+
* needs a mouse is a dismissal some users cannot perform.
|
|
702
|
+
*
|
|
703
|
+
* @internal — runtime-layer concern.
|
|
704
|
+
*/
|
|
705
|
+
/**
|
|
706
|
+
* The `label` an action declares on the render's `actionSpec`, trimmed, or
|
|
707
|
+
* `undefined` when the render declares none (or is a system / MCP-Apps
|
|
708
|
+
* render, which carries no actionSpec). By contract the label is the
|
|
709
|
+
* control's own copy, so it is the one name for a tap a visitor may read
|
|
710
|
+
* (see {@link GESTURE_COPY}).
|
|
711
|
+
*
|
|
712
|
+
* @internal — exported for unit tests.
|
|
713
|
+
*/
|
|
714
|
+
export declare function declaredActionLabel(render: GguiSession | GguiSessionSeedInput | null, actionName: string): string | undefined;
|
|
686
715
|
/**
|
|
687
716
|
* Boot-time reset of the relay latch (ggui#670 Phase 3). A latch
|
|
688
717
|
* standing from a prior mount in a re-booted document is closed through
|
|
@@ -694,6 +723,12 @@ export declare function ensureToastAnnouncer(doc: Document): ToastLiveRegions |
|
|
|
694
723
|
*/
|
|
695
724
|
export declare function resetRelayLatchForBoot(): void;
|
|
696
725
|
/** @internal — exported for unit tests to reset module state. */
|
|
726
|
+
/**
|
|
727
|
+
* Test-only: install the telemetry sink the runtime records into (normally
|
|
728
|
+
* set once at boot), so a test can observe the events a code path posts.
|
|
729
|
+
* `null` restores the no-sink state.
|
|
730
|
+
*/
|
|
731
|
+
export declare function __setTelemetrySinkForTest(sink: TelemetrySink | null): void;
|
|
697
732
|
export declare function __resetRelayNoticeForTest(): void;
|
|
698
733
|
/**
|
|
699
734
|
* Dispatch a submit-action via the empirically-validated bridge
|
|
@@ -714,6 +749,8 @@ export declare function dispatchSubmitAction(args: {
|
|
|
714
749
|
readonly data: unknown;
|
|
715
750
|
readonly sessionId: string;
|
|
716
751
|
readonly appId: string;
|
|
752
|
+
/** The action's declared `label`, when it has one — see {@link GESTURE_COPY}. */
|
|
753
|
+
readonly label?: string;
|
|
717
754
|
}): void;
|
|
718
755
|
/**
|
|
719
756
|
* Channel-transport router's `tools/call` invoker (ggui#440) —
|
|
@@ -809,6 +846,8 @@ export declare function routeDispatch(args: {
|
|
|
809
846
|
readonly appId: string;
|
|
810
847
|
};
|
|
811
848
|
readonly dispatchToolName: string;
|
|
849
|
+
/** The action's declared `label` (visitor copy by contract), when it has one. */
|
|
850
|
+
readonly label?: string;
|
|
812
851
|
}): void;
|
|
813
852
|
/**
|
|
814
853
|
* `ui/open-link` direct dispatch helper — used by the anchor-click
|