@ggui-ai/iframe-runtime 0.6.3 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +97 -85
- 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/meta-parse.d.ts +3 -3
- package/dist/meta-parse.js +13 -10
- package/dist/observability.d.ts +76 -52
- package/dist/react-renderer.js +70 -18
- package/dist/relay-incapability.d.ts +64 -0
- package/dist/relay-incapability.js +69 -0
- package/dist/runtime.d.ts +168 -15
- package/dist/runtime.js +792 -74
- 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/meta-parse.d.ts
CHANGED
|
@@ -11,9 +11,9 @@ import type { McpAppAiGguiMetaParseResult } from './types.js';
|
|
|
11
11
|
* direct-call callers like `parseMetaFromGlobal` / tests; the
|
|
12
12
|
* envelope-driven extractors already enforce this in the protocol-
|
|
13
13
|
* level parser).
|
|
14
|
-
* - At least one of `{live mode (wsUrl+wsToken), codeUrl,
|
|
15
|
-
* be present. Without any mode discriminator the iframe
|
|
16
|
-
* to mount.
|
|
14
|
+
* - At least one of `{live mode (wsUrl+wsToken), codeUrl, codeB64,
|
|
15
|
+
* kind}` MUST be present. Without any mode discriminator the iframe
|
|
16
|
+
* has nothing to mount.
|
|
17
17
|
*/
|
|
18
18
|
export declare function validateMeta(meta: McpAppAiGguiRenderMeta): McpAppAiGguiMetaParseResult;
|
|
19
19
|
/**
|
package/dist/meta-parse.js
CHANGED
|
@@ -40,9 +40,10 @@
|
|
|
40
40
|
*
|
|
41
41
|
* - **live** — slice has `wsUrl` + `wsToken`; runtime opens
|
|
42
42
|
* live-channel WS and renders agent-driven frames.
|
|
43
|
-
* - **static-component** — slice has `codeUrl
|
|
44
|
-
*
|
|
45
|
-
*
|
|
43
|
+
* - **static-component** — slice has `codeUrl` (runtime fetches the
|
|
44
|
+
* content-addressable URL) and/or `codeB64` (runtime decodes the
|
|
45
|
+
* inline bytes, no fetch); mounts the React component, never
|
|
46
|
+
* opens WS.
|
|
46
47
|
* - **system-card** — slice has `kind`; runtime maps to a
|
|
47
48
|
* built-in component via the system-card registry.
|
|
48
49
|
*
|
|
@@ -177,9 +178,9 @@ function projectPublicEnv(raw) {
|
|
|
177
178
|
*
|
|
178
179
|
* Invariants enforced here that the combiner doesn't:
|
|
179
180
|
* - `expiresAt` (when present) MUST parse to a valid timestamp. If
|
|
180
|
-
* in the past AND the slice carries static content (codeUrl /
|
|
181
|
-
* DEGRADE to static-only (drop `wsUrl` /
|
|
182
|
-
* otherwise return EXPIRED_BOOTSTRAP.
|
|
181
|
+
* in the past AND the slice carries static content (codeUrl /
|
|
182
|
+
* codeB64 / kind), DEGRADE to static-only (drop `wsUrl` /
|
|
183
|
+
* `wsToken` / `expiresAt`); otherwise return EXPIRED_BOOTSTRAP.
|
|
183
184
|
* - `gadgets` / `publicEnv` / `permissionsPolicy` /
|
|
184
185
|
* `streamWebSocketLocalTools` / `contextSlots` get defensive
|
|
185
186
|
* entry-level validation; malformed → defaulted.
|
|
@@ -260,6 +261,7 @@ function projectMeta(meta, hasStaticContent) {
|
|
|
260
261
|
: {}),
|
|
261
262
|
...(meta.codeUrl !== undefined ? { codeUrl: meta.codeUrl } : {}),
|
|
262
263
|
...(meta.codeHash !== undefined ? { codeHash: meta.codeHash } : {}),
|
|
264
|
+
...(meta.codeB64 !== undefined ? { codeB64: meta.codeB64 } : {}),
|
|
263
265
|
...(meta.kind !== undefined ? { kind: meta.kind } : {}),
|
|
264
266
|
};
|
|
265
267
|
return { ok: true, meta: projected };
|
|
@@ -275,9 +277,9 @@ function projectMeta(meta, hasStaticContent) {
|
|
|
275
277
|
* direct-call callers like `parseMetaFromGlobal` / tests; the
|
|
276
278
|
* envelope-driven extractors already enforce this in the protocol-
|
|
277
279
|
* level parser).
|
|
278
|
-
* - At least one of `{live mode (wsUrl+wsToken), codeUrl,
|
|
279
|
-
* be present. Without any mode discriminator the iframe
|
|
280
|
-
* to mount.
|
|
280
|
+
* - At least one of `{live mode (wsUrl+wsToken), codeUrl, codeB64,
|
|
281
|
+
* kind}` MUST be present. Without any mode discriminator the iframe
|
|
282
|
+
* has nothing to mount.
|
|
281
283
|
*/
|
|
282
284
|
export function validateMeta(meta) {
|
|
283
285
|
// `runtimeUrl` is a hard wire-required field on every render slice
|
|
@@ -292,8 +294,9 @@ export function validateMeta(meta) {
|
|
|
292
294
|
}
|
|
293
295
|
const hasLive = isNonEmptyString(meta.wsUrl) && isNonEmptyString(meta.wsToken);
|
|
294
296
|
const hasCodeUrl = isNonEmptyString(meta.codeUrl);
|
|
297
|
+
const hasCodeB64 = isNonEmptyString(meta.codeB64);
|
|
295
298
|
const hasKind = isNonEmptyString(meta.kind);
|
|
296
|
-
const hasStaticContent = hasCodeUrl || hasKind;
|
|
299
|
+
const hasStaticContent = hasCodeUrl || hasCodeB64 || hasKind;
|
|
297
300
|
if (!hasLive && !hasStaticContent) {
|
|
298
301
|
return { ok: false, reason: 'MALFORMED_BOOTSTRAP' };
|
|
299
302
|
}
|
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
|
package/dist/react-renderer.js
CHANGED
|
@@ -32,7 +32,18 @@
|
|
|
32
32
|
import React, { Component, createElement, Fragment, } from 'react';
|
|
33
33
|
import { createRoot } from 'react-dom/client';
|
|
34
34
|
import { stripMarkers, rewriteImports, getScopedThemeCss, getScopedCssTokens, getThemeCss, getCssTokens, } from '@ggui-ai/design/rendering';
|
|
35
|
-
import { hoistImports, loadModule } from '@ggui-ai/design/module-loader';
|
|
35
|
+
import { hoistImports, loadModule, loadModuleInline, probeUrlModuleLoad, } from '@ggui-ai/design/module-loader';
|
|
36
|
+
/**
|
|
37
|
+
* Document-lifetime verdict: URL-scheme module loading (`blob:` main
|
|
38
|
+
* module + `data:` import shims) is blocked by the host CSP. Set only
|
|
39
|
+
* when the inline path succeeded where the URL path failed AND the
|
|
40
|
+
* known-good `probeUrlModuleLoad()` module ALSO fails — an arbitrary
|
|
41
|
+
* module's failure is not CSP evidence (an import-shim allowlist miss
|
|
42
|
+
* rejects blob-instantiation of that one module while URL loading is
|
|
43
|
+
* healthy, and latching on it would force every later evaluation in
|
|
44
|
+
* the document onto the inline path's narrower semantics).
|
|
45
|
+
*/
|
|
46
|
+
let urlModuleLoadBlocked = false;
|
|
36
47
|
// =============================================================================
|
|
37
48
|
// Scope class helpers — port of RCR's `makeScopeClass` + counter.
|
|
38
49
|
// =============================================================================
|
|
@@ -177,25 +188,66 @@ export async function mountReactRoot(container, opts) {
|
|
|
177
188
|
}
|
|
178
189
|
// 1. Strip metadata markers (__GGUI_META__ / __GGUI_STREAM_SPEC__).
|
|
179
190
|
const cleaned = stripMarkers(resolved);
|
|
180
|
-
// 2. Hoist imports above var declarations.
|
|
191
|
+
// 2. Hoist imports above var declarations. Load-bearing for BOTH
|
|
192
|
+
// execution paths: the inline transform places its bindings at
|
|
193
|
+
// the import statement's position, so a pre-import statement
|
|
194
|
+
// referencing an imported name needs the hoist exactly as the
|
|
195
|
+
// real module semantics do.
|
|
181
196
|
const hoisted = hoistImports(cleaned);
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
+
const pick = (mod) => {
|
|
198
|
+
const Comp = (mod.default ??
|
|
199
|
+
Object.values(mod).find((v) => typeof v === 'function'));
|
|
200
|
+
if (Comp === undefined) {
|
|
201
|
+
throw new Error('Module does not export a default component');
|
|
202
|
+
}
|
|
203
|
+
return Comp;
|
|
204
|
+
};
|
|
205
|
+
const evaluateInline = () => pick(loadModuleInline(hoisted, {
|
|
206
|
+
...(currentOpts.gadgetPackages !== undefined
|
|
207
|
+
? { gadgetPackages: currentOpts.gadgetPackages }
|
|
208
|
+
: {}),
|
|
209
|
+
}));
|
|
210
|
+
// Once URL-scheme module loading has been proven blocked in this
|
|
211
|
+
// document, go straight to inline execution — the CSP verdict
|
|
212
|
+
// cannot change within the document's lifetime.
|
|
213
|
+
if (urlModuleLoadBlocked) {
|
|
214
|
+
return evaluateInline();
|
|
215
|
+
}
|
|
216
|
+
try {
|
|
217
|
+
// 3. Rewrite bare specifiers to data-url shims reading from
|
|
218
|
+
// `globalThis.__ggui__`. The __ggui__ registry is installed
|
|
219
|
+
// by `runtime.ts::bootSequence` BEFORE any mountReactRoot call
|
|
220
|
+
// — see globals.ts docstring on TOCTOU ordering.
|
|
221
|
+
const rewritten = rewriteImports(hoisted, {
|
|
222
|
+
mode: 'data-url',
|
|
223
|
+
gadgetPackages: currentOpts.gadgetPackages,
|
|
224
|
+
});
|
|
225
|
+
// 4. Dynamically import the module.
|
|
226
|
+
const mod = await loadModule(rewritten);
|
|
227
|
+
// 5. Extract default export (or first function export).
|
|
228
|
+
return pick(mod);
|
|
229
|
+
}
|
|
230
|
+
catch (urlError) {
|
|
231
|
+
// A host CSP granting only 'unsafe-inline' rejects the blob:/
|
|
232
|
+
// data: imports above. Attempt the inline classic-script path;
|
|
233
|
+
// a buggy component fails on both paths, and the original error
|
|
234
|
+
// is the honest one to surface.
|
|
235
|
+
try {
|
|
236
|
+
const Comp = evaluateInline();
|
|
237
|
+
// Latch the inline path for the rest of the document ONLY on
|
|
238
|
+
// proven CSP evidence: the known-good probe module must also
|
|
239
|
+
// fail. A module-specific blob failure (import-shim allowlist
|
|
240
|
+
// drift) leaves the latch off so the next evaluation gets real
|
|
241
|
+
// module semantics again.
|
|
242
|
+
if (!(await probeUrlModuleLoad())) {
|
|
243
|
+
urlModuleLoadBlocked = true;
|
|
244
|
+
}
|
|
245
|
+
return Comp;
|
|
246
|
+
}
|
|
247
|
+
catch {
|
|
248
|
+
throw urlError;
|
|
249
|
+
}
|
|
197
250
|
}
|
|
198
|
-
return Comp;
|
|
199
251
|
}
|
|
200
252
|
function renderTree(opts) {
|
|
201
253
|
const themeCss = opts.themeId
|
|
@@ -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
|
+
}
|