@ggui-ai/iframe-runtime 0.6.3 → 0.7.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/channel-transport.d.ts +76 -19
- package/dist/channel-transport.js +137 -29
- package/dist/channels/index.d.ts +0 -1
- package/dist/channels/index.js +0 -1
- package/dist/host-capabilities.d.ts +52 -0
- package/dist/host-capabilities.js +54 -0
- package/dist/iframe-runtime.js +81 -81
- package/dist/iframe-runtime.meta.json +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/mcp-app-iframe-host.js +8 -1
- package/dist/observability.d.ts +76 -52
- package/dist/relay-incapability.d.ts +64 -0
- package/dist/relay-incapability.js +69 -0
- package/dist/runtime.d.ts +123 -10
- package/dist/runtime.js +706 -52
- package/package.json +7 -7
- package/dist/channels/system.d.ts +0 -26
- package/dist/channels/system.js +0 -44
package/dist/index.d.ts
CHANGED
|
@@ -37,7 +37,7 @@ export { parseMetaFromGlobal, parseMetaFromToolResult, validateMeta, } from './m
|
|
|
37
37
|
export type { RendererBootFailureReason, RendererBootFailedMessage, } from './runtime.js';
|
|
38
38
|
export type { ProtocolError, ProtocolErrorEmitter, BootstrapFailureReason, } from './protocol-error.js';
|
|
39
39
|
export { defaultProtocolErrorEmitter, fromBootstrapFailure, fromClientContractViolation, fromTransportFailure, fromAuthFailure, fromUpgradeRequired, fromUnknown, } from './protocol-error.js';
|
|
40
|
-
export type { ObservabilityEvent, ObservabilityEmitter, ObservabilityMessage, SchemaVersionMismatchEvent, SubscribeFailedEvent,
|
|
40
|
+
export type { ObservabilityEvent, ObservabilityEmitter, ObservabilityMessage, SchemaVersionMismatchEvent, SubscribeFailedEvent, ChannelTransportPickedEvent, ChannelTransportFallbackEvent, ChannelTransportResubscribedEvent, ChannelPollDegradedEvent, ChannelPollRecoveredEvent, UiFeedbackEvent, RelayIncapabilityEvent, UnknownObservabilityEvent, } from './observability.js';
|
|
41
41
|
export { postObservabilityToParent } from './observability.js';
|
|
42
42
|
export type { LifecycleEmitter } from './lifecycle.js';
|
|
43
43
|
export { postLifecycleToParent, makeLifecycleEvent } from './lifecycle.js';
|
|
@@ -116,7 +116,14 @@ export function mountMcpAppIframe(container, opts) {
|
|
|
116
116
|
const result = {
|
|
117
117
|
protocolVersion: requested,
|
|
118
118
|
hostInfo: { name: 'ggui-iframe-runtime-embed-host', version: EMBED_HOST_VERSION },
|
|
119
|
-
|
|
119
|
+
// Advertise exactly what the `switch` below implements
|
|
120
|
+
// (ggui#440). `serverTools`: the `tools/call` case proxies
|
|
121
|
+
// guest tool calls to the MCP server. NOT `message`: there is
|
|
122
|
+
// no `ui/message` case here, so a doorbell would be dropped —
|
|
123
|
+
// and the runtime now surfaces that to the user rather than
|
|
124
|
+
// claiming the gesture reached the agent. If a `ui/message`
|
|
125
|
+
// case is ever added, advertise `message` in the same change.
|
|
126
|
+
hostCapabilities: { serverTools: {} },
|
|
120
127
|
hostContext: {
|
|
121
128
|
locale: opts.locale ??
|
|
122
129
|
(typeof navigator !== 'undefined' ? navigator.language : 'en-US'),
|
package/dist/observability.d.ts
CHANGED
|
@@ -33,7 +33,7 @@ import { MCP_APP_OBSERVE_TYPE } from '@ggui-ai/protocol/integrations/mcp-apps';
|
|
|
33
33
|
*
|
|
34
34
|
* @public
|
|
35
35
|
*/
|
|
36
|
-
export type ObservabilityEvent = SchemaVersionMismatchEvent | SubscribeFailedEvent |
|
|
36
|
+
export type ObservabilityEvent = SchemaVersionMismatchEvent | SubscribeFailedEvent | ChannelTransportPickedEvent | ChannelTransportFallbackEvent | ChannelTransportResubscribedEvent | ChannelPollDegradedEvent | ChannelPollRecoveredEvent | UiFeedbackEvent | RelayIncapabilityEvent | UnknownObservabilityEvent;
|
|
37
37
|
/**
|
|
38
38
|
* Fired when the protocol-version handshake rejects the connection.
|
|
39
39
|
* Parallel to `UpgradeRequiredError` — the host can choose to render
|
|
@@ -61,57 +61,6 @@ export interface SubscribeFailedEvent {
|
|
|
61
61
|
readonly reason: string;
|
|
62
62
|
readonly message?: string;
|
|
63
63
|
}
|
|
64
|
-
/**
|
|
65
|
-
* Fired when the server emits a `system` frame with
|
|
66
|
-
* `action: 'auth_required'` — the agent needs the end-user to authorize
|
|
67
|
-
* an OAuth service (Google, Slack, etc.) before its tool call can
|
|
68
|
-
* proceed. The host listens via `<McpAppIframe onObserve>` and renders
|
|
69
|
-
* a consent overlay OUTSIDE the iframe, then redirects the user to
|
|
70
|
-
* `authUrl` to complete the OAuth flow.
|
|
71
|
-
*
|
|
72
|
-
* Observability-only — the wire signal is already fully typed on
|
|
73
|
-
* `@ggui-ai/protocol`'s `SystemPayload` (live-channel.ts). This event is a
|
|
74
|
-
* renderer↔host projection of that payload, not a new protocol surface,
|
|
75
|
-
* matching the vendor-neutral-separation posture of every
|
|
76
|
-
* other `ObservabilityEvent` kind.
|
|
77
|
-
*
|
|
78
|
-
* Fields map 1:1 from `SystemPayload`:
|
|
79
|
-
* - `provider` ← `serviceId` (canonical identifier the credential
|
|
80
|
-
* store keys off — e.g., `"google"`, `"slack"`).
|
|
81
|
-
* - `authUrl` ← `consentUrl` (URL the host opens in a popup / new tab
|
|
82
|
-
* to initiate the OAuth consent flow).
|
|
83
|
-
* - `displayName` / `scopes` / `message` are optional hints the host
|
|
84
|
-
* can surface in the consent overlay; absent means "render with
|
|
85
|
-
* generic copy".
|
|
86
|
-
*
|
|
87
|
-
* @public
|
|
88
|
-
*/
|
|
89
|
-
export interface AuthRequiredEvent {
|
|
90
|
-
readonly kind: 'auth-required';
|
|
91
|
-
/**
|
|
92
|
-
* Canonical identifier of the service needing authorization (e.g.
|
|
93
|
-
* `"google"`, `"slack"`). Maps to `SystemPayload.serviceId`.
|
|
94
|
-
*/
|
|
95
|
-
readonly provider: string;
|
|
96
|
-
/**
|
|
97
|
-
* URL the host opens in a popup / new tab to initiate the OAuth
|
|
98
|
-
* consent flow. Maps to `SystemPayload.consentUrl`.
|
|
99
|
-
*/
|
|
100
|
-
readonly authUrl: string;
|
|
101
|
-
/**
|
|
102
|
-
* Human-readable service name (e.g. `"Google"`, `"Slack"`). Optional
|
|
103
|
-
* — absent when the server doesn't supply one.
|
|
104
|
-
*/
|
|
105
|
-
readonly displayName?: string;
|
|
106
|
-
/**
|
|
107
|
-
* OAuth scopes the agent is requesting. Optional.
|
|
108
|
-
*/
|
|
109
|
-
readonly scopes?: readonly string[];
|
|
110
|
-
/**
|
|
111
|
-
* Human-readable message explaining why access is needed. Optional.
|
|
112
|
-
*/
|
|
113
|
-
readonly message?: string;
|
|
114
|
-
}
|
|
115
64
|
/**
|
|
116
65
|
* Fired by the channel-transport router when it picks a transport
|
|
117
66
|
* for a `streamSpec[ch].source.tool` channel. Hosts can inspect the
|
|
@@ -152,6 +101,50 @@ export interface ChannelTransportResubscribedEvent {
|
|
|
152
101
|
readonly sessionId: string;
|
|
153
102
|
readonly channelName: string;
|
|
154
103
|
}
|
|
104
|
+
/**
|
|
105
|
+
* Fired by the channel-transport router when a polling channel's
|
|
106
|
+
* `tools/call` fails for a reason the router classifies as STRUCTURAL
|
|
107
|
+
* — the failure will repeat identically until the underlying
|
|
108
|
+
* condition changes, so spending a full-cadence poll on it is waste.
|
|
109
|
+
*
|
|
110
|
+
* The channel drops to a slower probe cadence; it never stops. That
|
|
111
|
+
* distinction is the fail-safe: recovery is a property of the running
|
|
112
|
+
* loop, not of an external nudge, so a host that regains the missing
|
|
113
|
+
* capability sees its channels come back on their own.
|
|
114
|
+
*
|
|
115
|
+
* Emitted ONCE per healthy→degraded transition, never per probe tick
|
|
116
|
+
* — a channel degraded for an hour emits one event, not 120.
|
|
117
|
+
*
|
|
118
|
+
* @public
|
|
119
|
+
*/
|
|
120
|
+
export interface ChannelPollDegradedEvent {
|
|
121
|
+
readonly kind: 'channel-poll-degraded';
|
|
122
|
+
readonly sessionId: string;
|
|
123
|
+
readonly channelName: string;
|
|
124
|
+
/**
|
|
125
|
+
* What the router classified. `'relay-incapable'` = the runtime has
|
|
126
|
+
* confirmed this host cannot relay `tools/call` to the MCP server
|
|
127
|
+
* (see `RelayIncapableError`), so every poll fails before reaching
|
|
128
|
+
* the transport.
|
|
129
|
+
*/
|
|
130
|
+
readonly reason: 'relay-incapable';
|
|
131
|
+
/** Cadence (ms) the channel probes at while degraded. */
|
|
132
|
+
readonly probeIntervalMs: number;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Fired by the channel-transport router when a previously-degraded
|
|
136
|
+
* channel's `tools/call` succeeds again. The channel returns to its
|
|
137
|
+
* normal cadence. Emitted once per degraded→healthy transition.
|
|
138
|
+
*
|
|
139
|
+
* @public
|
|
140
|
+
*/
|
|
141
|
+
export interface ChannelPollRecoveredEvent {
|
|
142
|
+
readonly kind: 'channel-poll-recovered';
|
|
143
|
+
readonly sessionId: string;
|
|
144
|
+
readonly channelName: string;
|
|
145
|
+
/** Cadence (ms) the channel returns to. */
|
|
146
|
+
readonly pollIntervalMs: number;
|
|
147
|
+
}
|
|
155
148
|
/**
|
|
156
149
|
* Fired when the end user submits the runtime's in-iframe UI-feedback
|
|
157
150
|
* affordance ("did this generated UI work for you?"). Field semantics
|
|
@@ -190,6 +183,37 @@ export interface UiFeedbackEvent {
|
|
|
190
183
|
/** Tool that produced the render (e.g. `ggui_render`). */
|
|
191
184
|
readonly toolName?: string;
|
|
192
185
|
}
|
|
186
|
+
/**
|
|
187
|
+
* Fired at the two transition edges of the renderer's
|
|
188
|
+
* relay-incapability latch — the runtime's confirmed determination
|
|
189
|
+
* that the host cannot relay `tools/call` to the MCP server:
|
|
190
|
+
*
|
|
191
|
+
* - `'latched'` — a real user gesture just failed relay-shaped (no
|
|
192
|
+
* well-formed result envelope came back at all) on a host whose
|
|
193
|
+
* captured capability handshake never advertised `serverTools`.
|
|
194
|
+
* The runtime now treats relay as confirmed-unavailable: it shows
|
|
195
|
+
* one persistent explanation instead of a per-gesture error toast,
|
|
196
|
+
* and channel polls fail fast without a transport round-trip.
|
|
197
|
+
* - `'cleared'` — a later well-formed result envelope arrived
|
|
198
|
+
* (`ok:true` and `ok:false` alike — either proves the host relayed
|
|
199
|
+
* the call there and back), so the determination no longer holds;
|
|
200
|
+
* per-gesture feedback and channel transport attempts resume.
|
|
201
|
+
*
|
|
202
|
+
* Emitted once per edge, never per channel poll tick: repeated failing
|
|
203
|
+
* gestures while latched emit nothing further, and the router's
|
|
204
|
+
* fail-fast ticks emit nothing at all — the edges carry the full
|
|
205
|
+
* information.
|
|
206
|
+
*
|
|
207
|
+
* Always emitted via the postMessage-to-parent default — the emission
|
|
208
|
+
* sites live in module-level gesture-dispatch code outside the boot
|
|
209
|
+
* graph, so they never flow through an injected `onObserve` sink.
|
|
210
|
+
*
|
|
211
|
+
* @public
|
|
212
|
+
*/
|
|
213
|
+
export interface RelayIncapabilityEvent {
|
|
214
|
+
readonly kind: 'relay-incapability';
|
|
215
|
+
readonly state: 'latched' | 'cleared';
|
|
216
|
+
}
|
|
193
217
|
/**
|
|
194
218
|
* Catch-all branch for event kinds the host's typings don't recognize.
|
|
195
219
|
* Lets the union stay extensible without forcing protocol-version
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Relay-incapability failure vocabulary.
|
|
3
|
+
*
|
|
4
|
+
* Two modules need to agree on ONE fact: whether a failed
|
|
5
|
+
* `tools/call` failed because this host structurally cannot relay
|
|
6
|
+
* calls to the MCP server, or because something transient went wrong
|
|
7
|
+
* on an otherwise-working relay.
|
|
8
|
+
*
|
|
9
|
+
* - `runtime.ts` is the THROWER. It owns the relay-incapability
|
|
10
|
+
* latch (set only after a real user gesture failed relay-shaped
|
|
11
|
+
* on a host that never advertised `serverTools`) and throws
|
|
12
|
+
* {@link RelayIncapableError} from `channelToolsCall` before
|
|
13
|
+
* spending a transport round-trip.
|
|
14
|
+
* - `channel-transport.ts` is the CLASSIFIER. Its poll tick reads
|
|
15
|
+
* the thrown value to decide between "back off to a probe
|
|
16
|
+
* cadence" and "stay quiet, next tick may succeed".
|
|
17
|
+
*
|
|
18
|
+
* The vocabulary lives in its own module rather than in either party
|
|
19
|
+
* because `runtime.ts` already imports `channel-transport.ts` — a
|
|
20
|
+
* reverse import would close a cycle. Naming it for the CONCEPT
|
|
21
|
+
* (relay incapability) rather than for either party keeps the
|
|
22
|
+
* dependency direction obvious.
|
|
23
|
+
*
|
|
24
|
+
* Classification is by type, never by message string: a thrown
|
|
25
|
+
* `Error` whose text happens to mention relaying is NOT a structural
|
|
26
|
+
* failure, and re-wording a message must never silently re-classify
|
|
27
|
+
* a channel.
|
|
28
|
+
*/
|
|
29
|
+
/**
|
|
30
|
+
* Canonical code carried by {@link RelayIncapableError}.
|
|
31
|
+
*
|
|
32
|
+
* Package-internal. Nothing in this module reaches a host: it is not
|
|
33
|
+
* re-exported from `index.ts`, so an embedding app can neither catch
|
|
34
|
+
* this class nor read this code. The host-facing surface for the same
|
|
35
|
+
* information is the `channel-poll-degraded` / `channel-poll-recovered`
|
|
36
|
+
* observability pair, which IS exported.
|
|
37
|
+
*/
|
|
38
|
+
export declare const RELAY_INCAPABLE = "RELAY_INCAPABLE";
|
|
39
|
+
/**
|
|
40
|
+
* Thrown when the runtime has CONFIRMED (not merely suspected) that
|
|
41
|
+
* the surrounding host cannot relay `tools/call` to the MCP server,
|
|
42
|
+
* so an attempt would be spent for a guaranteed failure.
|
|
43
|
+
*
|
|
44
|
+
* "Confirmed" is load-bearing. Absence of an advertised `serverTools`
|
|
45
|
+
* capability is NOT confirmation — plenty of hosts under-advertise
|
|
46
|
+
* and relay perfectly well. Only a real gesture that failed
|
|
47
|
+
* relay-shaped sets the latch this error reports, and any later
|
|
48
|
+
* well-formed result envelope clears it.
|
|
49
|
+
*
|
|
50
|
+
* Consumers MUST classify by `instanceof` (or by `code`), never by
|
|
51
|
+
* parsing {@link Error.message}.
|
|
52
|
+
*/
|
|
53
|
+
export declare class RelayIncapableError extends Error {
|
|
54
|
+
/** Canonical classification code. */
|
|
55
|
+
readonly code: typeof RELAY_INCAPABLE;
|
|
56
|
+
constructor();
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Type guard for the structural-failure class. Thrower and classifier
|
|
60
|
+
* are bundled together into one iframe artifact, so `instanceof` is
|
|
61
|
+
* exact here — there is no module-realm boundary between them to
|
|
62
|
+
* defend against.
|
|
63
|
+
*/
|
|
64
|
+
export declare function isRelayIncapableError(err: unknown): err is RelayIncapableError;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Relay-incapability failure vocabulary.
|
|
3
|
+
*
|
|
4
|
+
* Two modules need to agree on ONE fact: whether a failed
|
|
5
|
+
* `tools/call` failed because this host structurally cannot relay
|
|
6
|
+
* calls to the MCP server, or because something transient went wrong
|
|
7
|
+
* on an otherwise-working relay.
|
|
8
|
+
*
|
|
9
|
+
* - `runtime.ts` is the THROWER. It owns the relay-incapability
|
|
10
|
+
* latch (set only after a real user gesture failed relay-shaped
|
|
11
|
+
* on a host that never advertised `serverTools`) and throws
|
|
12
|
+
* {@link RelayIncapableError} from `channelToolsCall` before
|
|
13
|
+
* spending a transport round-trip.
|
|
14
|
+
* - `channel-transport.ts` is the CLASSIFIER. Its poll tick reads
|
|
15
|
+
* the thrown value to decide between "back off to a probe
|
|
16
|
+
* cadence" and "stay quiet, next tick may succeed".
|
|
17
|
+
*
|
|
18
|
+
* The vocabulary lives in its own module rather than in either party
|
|
19
|
+
* because `runtime.ts` already imports `channel-transport.ts` — a
|
|
20
|
+
* reverse import would close a cycle. Naming it for the CONCEPT
|
|
21
|
+
* (relay incapability) rather than for either party keeps the
|
|
22
|
+
* dependency direction obvious.
|
|
23
|
+
*
|
|
24
|
+
* Classification is by type, never by message string: a thrown
|
|
25
|
+
* `Error` whose text happens to mention relaying is NOT a structural
|
|
26
|
+
* failure, and re-wording a message must never silently re-classify
|
|
27
|
+
* a channel.
|
|
28
|
+
*/
|
|
29
|
+
/**
|
|
30
|
+
* Canonical code carried by {@link RelayIncapableError}.
|
|
31
|
+
*
|
|
32
|
+
* Package-internal. Nothing in this module reaches a host: it is not
|
|
33
|
+
* re-exported from `index.ts`, so an embedding app can neither catch
|
|
34
|
+
* this class nor read this code. The host-facing surface for the same
|
|
35
|
+
* information is the `channel-poll-degraded` / `channel-poll-recovered`
|
|
36
|
+
* observability pair, which IS exported.
|
|
37
|
+
*/
|
|
38
|
+
export const RELAY_INCAPABLE = 'RELAY_INCAPABLE';
|
|
39
|
+
/**
|
|
40
|
+
* Thrown when the runtime has CONFIRMED (not merely suspected) that
|
|
41
|
+
* the surrounding host cannot relay `tools/call` to the MCP server,
|
|
42
|
+
* so an attempt would be spent for a guaranteed failure.
|
|
43
|
+
*
|
|
44
|
+
* "Confirmed" is load-bearing. Absence of an advertised `serverTools`
|
|
45
|
+
* capability is NOT confirmation — plenty of hosts under-advertise
|
|
46
|
+
* and relay perfectly well. Only a real gesture that failed
|
|
47
|
+
* relay-shaped sets the latch this error reports, and any later
|
|
48
|
+
* well-formed result envelope clears it.
|
|
49
|
+
*
|
|
50
|
+
* Consumers MUST classify by `instanceof` (or by `code`), never by
|
|
51
|
+
* parsing {@link Error.message}.
|
|
52
|
+
*/
|
|
53
|
+
export class RelayIncapableError extends Error {
|
|
54
|
+
/** Canonical classification code. */
|
|
55
|
+
code = RELAY_INCAPABLE;
|
|
56
|
+
constructor() {
|
|
57
|
+
super('tools/call unavailable: host did not advertise serverTools');
|
|
58
|
+
this.name = 'RelayIncapableError';
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Type guard for the structural-failure class. Thrower and classifier
|
|
63
|
+
* are bundled together into one iframe artifact, so `instanceof` is
|
|
64
|
+
* exact here — there is no module-realm boundary between them to
|
|
65
|
+
* defend against.
|
|
66
|
+
*/
|
|
67
|
+
export function isRelayIncapableError(err) {
|
|
68
|
+
return err instanceof RelayIncapableError;
|
|
69
|
+
}
|
package/dist/runtime.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { DrainAckPayload, GguiSession } from '@ggui-ai/protocol';
|
|
1
|
+
import type { DrainAckPayload, JsonValue, JsonObject, GguiSession } from '@ggui-ai/protocol';
|
|
2
2
|
import type { WebSocketMessage } from '@ggui-ai/protocol/transport/websocket';
|
|
3
3
|
import { type McpAppAiGguiRenderMeta, type McpAppBootstrapFailedMessage, type McpAppRendererReadyMessage } from '@ggui-ai/protocol/integrations/mcp-apps';
|
|
4
4
|
import type { GguiSessionSeedInput } from './types.js';
|
|
@@ -71,6 +71,19 @@ export interface RendererBootFailedMessage extends McpAppBootstrapFailedMessage
|
|
|
71
71
|
readonly reason: RendererBootFailureReason;
|
|
72
72
|
}
|
|
73
73
|
export interface BootSequenceOptions {
|
|
74
|
+
/**
|
|
75
|
+
* Document the boot path mounts into — the render root, and the
|
|
76
|
+
* toast announcer's live regions.
|
|
77
|
+
*
|
|
78
|
+
* MUST be the global `document` in any real deployment. The toast +
|
|
79
|
+
* cue surface reads the global directly, and production passes the
|
|
80
|
+
* global here (see `runBootProduction`), so the two are one object.
|
|
81
|
+
* A caller handing a DIFFERENT document pre-registers the live
|
|
82
|
+
* regions somewhere no announcement will ever reach; the surface then
|
|
83
|
+
* falls back to creating them in the global document at the moment of
|
|
84
|
+
* the first message, which is precisely the same-tick creation that
|
|
85
|
+
* loses that first announcement.
|
|
86
|
+
*/
|
|
74
87
|
readonly doc: Document;
|
|
75
88
|
/**
|
|
76
89
|
* Spec-canonical {@link App} instance + its {@link Transport}.
|
|
@@ -119,14 +132,17 @@ export interface BootSequenceOptions {
|
|
|
119
132
|
* emission on UPGRADE_REQUIRED.
|
|
120
133
|
* - `subscribe-failed` — forwarded from subscribe's emission on
|
|
121
134
|
* transient reconnect transitions.
|
|
122
|
-
* - `auth-required` — fired by the system channel handler when an
|
|
123
|
-
* `auth_required` system frame arrives.
|
|
124
135
|
* - `channel-transport-*` — fired by the channel-transport router
|
|
125
136
|
* on per-channel transport picks / fallbacks / resubscribes.
|
|
126
137
|
*
|
|
127
|
-
* Absent = observability emission
|
|
128
|
-
* ProtocolError posture; the `<McpAppIframe>`
|
|
129
|
-
* whether to bind this via its `onObserve`
|
|
138
|
+
* Absent = observability emission for the events above is skipped
|
|
139
|
+
* entirely (matches the ProtocolError posture; the `<McpAppIframe>`
|
|
140
|
+
* host wrapper decides whether to bind this via its `onObserve`
|
|
141
|
+
* prop). Exception: `relay-incapability` events do NOT flow through
|
|
142
|
+
* this seam — they originate in module-level gesture-dispatch code
|
|
143
|
+
* outside the boot graph (it runs independently of any given boot
|
|
144
|
+
* call) and always ride the postMessage-to-parent default, whether
|
|
145
|
+
* or not this option is bound.
|
|
130
146
|
*/
|
|
131
147
|
readonly onObserve?: ObservabilityEmitter;
|
|
132
148
|
/**
|
|
@@ -204,9 +220,8 @@ export interface RendererHooks {
|
|
|
204
220
|
* with `StackRenderer`.
|
|
205
221
|
*
|
|
206
222
|
* `onObserve` — optional observability emitter threaded down to
|
|
207
|
-
* the channel
|
|
208
|
-
*
|
|
209
|
-
* sites run silent (the connect-time emission sites —
|
|
223
|
+
* the channel-transport router (`channel-transport-*`). Absent =
|
|
224
|
+
* those sites run silent (the connect-time emission sites —
|
|
210
225
|
* `connectViaRegistry`'s version + subscribe events — still fire
|
|
211
226
|
* via their own emitters).
|
|
212
227
|
*/
|
|
@@ -271,7 +286,7 @@ export interface RendererHandle {
|
|
|
271
286
|
/**
|
|
272
287
|
* Channel-client registry holding handlers for every WS frame type
|
|
273
288
|
* the iframe routes (`render`, `data`, `props_update`, `drain_ack`,
|
|
274
|
-
* `channel_payload`, `channel_error
|
|
289
|
+
* `channel_payload`, `channel_error`). The
|
|
275
290
|
* registry-bound transport is the sole dispatch surface — frames
|
|
276
291
|
* arrive directly through registered handlers, no longer through a
|
|
277
292
|
* separate `onMessage` callback.
|
|
@@ -391,6 +406,45 @@ export declare function emitAudit(args: {
|
|
|
391
406
|
readonly actionId: string;
|
|
392
407
|
readonly firedAt: string;
|
|
393
408
|
}): void;
|
|
409
|
+
/**
|
|
410
|
+
* The announcer's two live regions, keyed by politeness (ggui#447).
|
|
411
|
+
*
|
|
412
|
+
* Two regions rather than one with a swapped `aria-live`: politeness is
|
|
413
|
+
* read when a region is REGISTERED, so a screen reader that already
|
|
414
|
+
* mapped a node as polite is not guaranteed to notice it turning
|
|
415
|
+
* assertive in the same mutation that delivers the text. Fixed
|
|
416
|
+
* politeness per node removes the question.
|
|
417
|
+
*/
|
|
418
|
+
interface ToastLiveRegions {
|
|
419
|
+
/** `role="status"` — queued behind whatever is being read. */
|
|
420
|
+
readonly polite: HTMLElement;
|
|
421
|
+
/** `role="alert"` — interrupts. Failure + action-required classes. */
|
|
422
|
+
readonly assertive: HTMLElement;
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* Ensure the pair of live regions the toast surface announces through.
|
|
426
|
+
*
|
|
427
|
+
* MUST be called before the first message can land — a live region
|
|
428
|
+
* created in the same tick as its first content is a region the screen
|
|
429
|
+
* reader was never watching, and that first announcement is lost. The
|
|
430
|
+
* boot path calls this at the same point it mounts the render root, so
|
|
431
|
+
* every toast (at minimum a user gesture away) lands in a region that
|
|
432
|
+
* has been registered for many frames. The toast + cue paths call it
|
|
433
|
+
* again defensively; it is idempotent per document.
|
|
434
|
+
*
|
|
435
|
+
* Visually hidden rather than `display:none`: a region that is not
|
|
436
|
+
* rendered is not in the accessibility tree, and content added to it is
|
|
437
|
+
* never announced. The clip rectangle keeps it out of the visual layout
|
|
438
|
+
* while leaving it live.
|
|
439
|
+
*
|
|
440
|
+
* Returns `null` when the document has no body yet — nothing to attach
|
|
441
|
+
* to, and no announcement is possible.
|
|
442
|
+
*
|
|
443
|
+
* @internal — exported for unit tests.
|
|
444
|
+
*/
|
|
445
|
+
export declare function ensureToastAnnouncer(doc: Document): ToastLiveRegions | null;
|
|
446
|
+
/** @internal — exported for unit tests to reset module state. */
|
|
447
|
+
export declare function __resetRelayNoticeForTest(): void;
|
|
394
448
|
/**
|
|
395
449
|
* Dispatch a submit-action via the empirically-validated bridge
|
|
396
450
|
* chain (validated against claude.ai): a `tools/call` to
|
|
@@ -411,6 +465,65 @@ export declare function dispatchSubmitAction(args: {
|
|
|
411
465
|
readonly sessionId: string;
|
|
412
466
|
readonly appId: string;
|
|
413
467
|
}): void;
|
|
468
|
+
/**
|
|
469
|
+
* Channel-transport router's `tools/call` invoker (ggui#440) —
|
|
470
|
+
* iframe-polling transport, `tools/call` against the parent MCP host
|
|
471
|
+
* via `app.callServerTool` (spec-canonical) when the App handle is
|
|
472
|
+
* set; falls back to raw postMessage pre-handshake or in tests.
|
|
473
|
+
* Direct call (no LLM consent loop). Returns the tool's
|
|
474
|
+
* structuredContent (or `content[0]` if that's where the payload
|
|
475
|
+
* landed) as a `JsonValue`. On RPC error we throw — the router
|
|
476
|
+
* catches and silently retries on the next tick.
|
|
477
|
+
*
|
|
478
|
+
* Once a REAL gesture has confirmed this host can't relay (the latch
|
|
479
|
+
* above — set only after {@link dispatchSubmitAction} actually tried
|
|
480
|
+
* and failed on a host with no `serverTools` advertised, with
|
|
481
|
+
* capabilities already captured, AND the failure was relay-shaped —
|
|
482
|
+
* not a well-formed `{ok:false}` result; see `isRelayShapedFailure`),
|
|
483
|
+
* every subsequent poll will fail identically.
|
|
484
|
+
*
|
|
485
|
+
* This guard does NOT stop the router's poll loop, and nothing here
|
|
486
|
+
* should ever be written so that it could (ggui#443). It does two
|
|
487
|
+
* things. It makes each doomed tick CHEAP — the poll throws before
|
|
488
|
+
* the `callServerToolSpec` round-trip. And, by throwing the typed
|
|
489
|
+
* {@link RelayIncapableError} rather than a bare `Error`, it tells
|
|
490
|
+
* the router WHICH KIND of failure this is: `channel-transport.ts`'s
|
|
491
|
+
* tick classifies structural rejections apart from transient ones and
|
|
492
|
+
* drops the channel to a slow probe cadence, emitting one
|
|
493
|
+
* `channel-poll-degraded` event per transition. The first poll that
|
|
494
|
+
* succeeds after the latch clears restores the normal cadence and
|
|
495
|
+
* emits `channel-poll-recovered`.
|
|
496
|
+
*
|
|
497
|
+
* The channel keeps probing throughout, which is what makes that
|
|
498
|
+
* recovery possible: the latch clears only when a call succeeds, and
|
|
499
|
+
* only an attempt can discover that. A channel stopped on structural
|
|
500
|
+
* failure could never come back.
|
|
501
|
+
*
|
|
502
|
+
* Keyed on the LATCH, not on `hostCanRelayToolCalls()` directly: raw
|
|
503
|
+
* advertisement absence never changes after boot, so gating on it
|
|
504
|
+
* here would throw pre-attempt for the LIFE OF THE SESSION on any
|
|
505
|
+
* host that under-advertises but genuinely proxies `tools/call`
|
|
506
|
+
* (ggui's own embed host pre-Task-2 is exactly this case) — silently
|
|
507
|
+
* killing working channel polls forever. That is "absence of a
|
|
508
|
+
* capability blocks an attempt", which the fail-safe constraint
|
|
509
|
+
* forbids. The latch implies `!hostCanRelayToolCalls()` (it only
|
|
510
|
+
* ever sets inside that guard), so testing it alone is sufficient —
|
|
511
|
+
* a host that never fails a real gesture never latches, and its
|
|
512
|
+
* channels keep working.
|
|
513
|
+
*
|
|
514
|
+
* Extracted to a named export (rather than an inline closure inside
|
|
515
|
+
* `bootProduction`) so unit tests can drive it directly through the
|
|
516
|
+
* lightweight `MockTransport` harness — `bootProduction` dynamic-
|
|
517
|
+
* imports the full react/design/wire module graph, which is too
|
|
518
|
+
* heavy to exercise from a spec file (see
|
|
519
|
+
* `boot-production-context.test.ts`'s docstring for precedent).
|
|
520
|
+
*
|
|
521
|
+
* @internal — exported for unit tests + production reuse.
|
|
522
|
+
*/
|
|
523
|
+
export declare function channelToolsCall(args: {
|
|
524
|
+
readonly toolName: string;
|
|
525
|
+
readonly args: JsonObject;
|
|
526
|
+
}): Promise<JsonValue>;
|
|
414
527
|
/**
|
|
415
528
|
* Resolve the name of the spec-canonical submit-action receiver tool
|
|
416
529
|
* the dispatch path targets with `tools/call`.
|