@ggui-ai/iframe-runtime 0.14.0 → 0.15.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 +1 -1
- package/dist/channels/channel-payload.d.ts +1 -1
- package/dist/channels/data.d.ts +1 -1
- package/dist/channels/drain-ack.d.ts +1 -1
- package/dist/channels/props-update.d.ts +1 -1
- package/dist/channels/render-frame.d.ts +1 -1
- package/dist/compiled-validators.d.ts +1 -1
- package/dist/context-observer.d.ts +1 -1
- package/dist/context-observer.js +1 -1
- package/dist/events-polling.d.ts +1 -1
- package/dist/host-context-emitter.d.ts +1 -1
- package/dist/host-context-emitter.js +1 -1
- package/dist/iframe-runtime.js +93 -93
- package/dist/iframe-runtime.meta.json +1 -1
- package/dist/meta-parse.js +1 -1
- package/dist/observability.d.ts +32 -13
- package/dist/provisional-renderer.d.ts +1 -1
- package/dist/react-renderer.d.ts +1 -1
- package/dist/registry-subscribe.d.ts +1 -1
- package/dist/render-item.d.ts +1 -1
- package/dist/runtime-telemetry.js +1 -1
- package/dist/runtime.d.ts +25 -17
- package/dist/runtime.js +114 -47
- package/dist/types.d.ts +1 -1
- package/dist/validation.d.ts +1 -1
- package/dist/validation.js +1 -1
- package/dist/wire-config.d.ts +1 -1
- package/package.json +7 -7
package/dist/meta-parse.js
CHANGED
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
* At least one mode MUST be present. Half-live (wsUrl without wsToken)
|
|
51
51
|
* is structurally rejected by the combiner.
|
|
52
52
|
*/
|
|
53
|
-
import { PUBLIC_ENV_APP_KEY_RE } from '@ggui-ai/protocol';
|
|
53
|
+
import { PUBLIC_ENV_APP_KEY_RE } from '@ggui-ai/protocol/wire';
|
|
54
54
|
import { parseMcpAppAiGguiRenderMeta } from '@ggui-ai/protocol/integrations/mcp-apps';
|
|
55
55
|
/**
|
|
56
56
|
* Type guard — true iff `value` is a non-null, non-array plain object.
|
package/dist/observability.d.ts
CHANGED
|
@@ -179,15 +179,29 @@ export interface UiFeedbackEvent {
|
|
|
179
179
|
readonly toolName?: string;
|
|
180
180
|
}
|
|
181
181
|
/**
|
|
182
|
-
* The evidence that latched a relay dead zone
|
|
183
|
-
* `'confirmed-refusal'` — the relay answered a declared refusal code
|
|
184
|
-
*
|
|
185
|
-
*
|
|
186
|
-
*
|
|
182
|
+
* The evidence that latched a relay dead zone:
|
|
183
|
+
* - `'confirmed-refusal'` — the relay answered a declared refusal code
|
|
184
|
+
* (helper-minted; outranks any positive advertisement) (ggui#599
|
|
185
|
+
* cycle-2);
|
|
186
|
+
* - `'advert-silent'` — the host never advertised `serverTools` and
|
|
187
|
+
* the attempt failed relay-shaped;
|
|
188
|
+
* - `'boot-failed'` — the mandatory `ui/initialize` handshake did not
|
|
189
|
+
* complete (refused, timed out, or the transport failed), so
|
|
190
|
+
* nothing can ever be delivered in this document (ggui#830). Not a
|
|
191
|
+
* probe the runtime minted: the outcome of the one request every
|
|
192
|
+
* session begins with — the strongest confirmed outcome there is.
|
|
193
|
+
* It clears the only way a handshake failure can: the next boot's
|
|
194
|
+
* close edge.
|
|
195
|
+
*
|
|
196
|
+
* The union is closed per renderer version; nothing in a host branches
|
|
197
|
+
* on the trigger (the first-party host passes it through untouched) —
|
|
198
|
+
* the edge is the fact, the trigger is why. A host that does branch on
|
|
199
|
+
* it should treat a value it does not know as "latched, reason
|
|
200
|
+
* unknown", never as "not latched".
|
|
187
201
|
*
|
|
188
202
|
* @public
|
|
189
203
|
*/
|
|
190
|
-
export type RelayLatchTrigger = 'confirmed-refusal' | 'advert-silent';
|
|
204
|
+
export type RelayLatchTrigger = 'confirmed-refusal' | 'advert-silent' | 'boot-failed';
|
|
191
205
|
/**
|
|
192
206
|
* Fired at the two transition edges of the renderer's
|
|
193
207
|
* relay-incapability latch — the runtime's confirmed determination
|
|
@@ -195,7 +209,9 @@ export type RelayLatchTrigger = 'confirmed-refusal' | 'advert-silent';
|
|
|
195
209
|
*
|
|
196
210
|
* - `'latched'` — a real user gesture just failed relay-shaped on a
|
|
197
211
|
* host whose captured capability handshake never advertised
|
|
198
|
-
* `serverTools`, or the relay answered a declared refusal code
|
|
212
|
+
* `serverTools`, or the relay answered a declared refusal code, or
|
|
213
|
+
* the host refused `ui/initialize` itself (`'boot-failed'`,
|
|
214
|
+
* ggui#830 — no gesture is needed to know nothing can be delivered).
|
|
199
215
|
* The runtime now treats relay as confirmed-unavailable: it shows
|
|
200
216
|
* one persistent explanation, arms the dead-zone cue for every
|
|
201
217
|
* later gesture, and channel polls fail fast without a transport
|
|
@@ -214,9 +230,11 @@ export type RelayLatchTrigger = 'confirmed-refusal' | 'advert-silent';
|
|
|
214
230
|
* a latch standing from a prior mount closes it with a `'cleared'`
|
|
215
231
|
* edge before the new session starts.
|
|
216
232
|
*
|
|
217
|
-
* Always emitted via the postMessage-to-parent default — the
|
|
218
|
-
*
|
|
219
|
-
* graph
|
|
233
|
+
* Always emitted via the postMessage-to-parent default, by rule — the
|
|
234
|
+
* gesture-dispatch emission sites run outside any given boot call, and
|
|
235
|
+
* the boot-graph site (the `boot-failed` edge, ggui#830) follows the
|
|
236
|
+
* same rule — so these edges never flow through an injected
|
|
237
|
+
* `onObserve` sink and a host reads every edge the same way.
|
|
220
238
|
*
|
|
221
239
|
* @public
|
|
222
240
|
*/
|
|
@@ -249,9 +267,10 @@ export type RelayIncapabilityEvent = {
|
|
|
249
267
|
* document (pulse, spoken cue, fallback toast); this event makes the
|
|
250
268
|
* attempt COUNTABLE by the host.
|
|
251
269
|
*
|
|
252
|
-
* Contract: exactly one per gesture
|
|
253
|
-
*
|
|
254
|
-
*
|
|
270
|
+
* Contract: exactly one per gesture whose UNDELIVERED outcome
|
|
271
|
+
* (relay-shaped failure or a declared refusal) lands while the latch
|
|
272
|
+
* stands — an in-flight gesture that raced the latching one counts;
|
|
273
|
+
* never emitted off-latch, and never from the channel
|
|
255
274
|
* router's fail-fast ticks. Counted at the outcome, not the tap: the
|
|
256
275
|
* gesture that latched the zone is the `'latched'` edge and the gesture
|
|
257
276
|
* that heals it is the `'cleared'` edge — neither is a dead tap.
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
* while suspended, matching host-SDK behavior.
|
|
32
32
|
*/
|
|
33
33
|
import React, { type ReactNode } from 'react';
|
|
34
|
-
import type { StreamEnvelope } from '@ggui-ai/protocol';
|
|
34
|
+
import type { StreamEnvelope } from '@ggui-ai/protocol/wire';
|
|
35
35
|
export interface ProvisionalMount {
|
|
36
36
|
/**
|
|
37
37
|
* Feed a stream envelope. Ignores non-PREVIEW_CHANNEL envelopes
|
package/dist/react-renderer.d.ts
CHANGED
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
* TypeScript config doesn't need a JSX runtime plumbing.
|
|
31
31
|
*/
|
|
32
32
|
import React, { type ReactNode } from 'react';
|
|
33
|
-
import type { AppTheme } from '@ggui-ai/protocol';
|
|
33
|
+
import type { AppTheme } from '@ggui-ai/protocol/wire';
|
|
34
34
|
export interface ReactRootMountOptions {
|
|
35
35
|
/**
|
|
36
36
|
* GguiSession whose componentCode + props drive the mount. Only
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AckPayload } from '@ggui-ai/protocol';
|
|
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
4
|
import type { AnyTransportHandle, ChannelLogger, ChannelRegistry, RegistryPollingOptions, RegistrySseOptions, WsTransportHandle } from '@ggui-ai/live-channel';
|
package/dist/render-item.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ReactNode } from 'react';
|
|
2
|
-
import type { AppTheme, GguiSession } from '@ggui-ai/protocol';
|
|
2
|
+
import type { AppTheme, GguiSession } from '@ggui-ai/protocol/wire';
|
|
3
3
|
import type { GguiSessionSeedInput } from './types.js';
|
|
4
4
|
import { type WireConfig } from '@ggui-ai/wire';
|
|
5
5
|
import type { StreamBus } from './wire-config.js';
|
package/dist/runtime.d.ts
CHANGED
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
* wrapper, which routes it to the host's `onError` callback.
|
|
26
26
|
*/
|
|
27
27
|
import './zod-jitless.js';
|
|
28
|
-
import type { DrainAckPayload, JsonValue, JsonObject, GguiSession } from '@ggui-ai/protocol';
|
|
28
|
+
import type { DrainAckPayload, JsonValue, JsonObject, GguiSession } from '@ggui-ai/protocol/wire';
|
|
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';
|
|
@@ -279,10 +279,12 @@ export interface BootSequenceOptions {
|
|
|
279
279
|
* entirely (matches the ProtocolError posture; the `<McpAppIframe>`
|
|
280
280
|
* host wrapper decides whether to bind this via its `onObserve`
|
|
281
281
|
* prop). Exception: `relay-incapability` events do NOT flow through
|
|
282
|
-
* this seam — they
|
|
283
|
-
*
|
|
284
|
-
*
|
|
285
|
-
*
|
|
282
|
+
* this seam — by rule they always ride the postMessage-to-parent
|
|
283
|
+
* default, whether or not this option is bound: most emission sites
|
|
284
|
+
* are module-level gesture-dispatch code that runs independently of
|
|
285
|
+
* any given boot call, and the one inside the boot graph (the
|
|
286
|
+
* `boot-failed` edge, ggui#830) follows the same rule so a host reads
|
|
287
|
+
* every edge the same way.
|
|
286
288
|
*/
|
|
287
289
|
readonly onObserve?: ObservabilityEmitter;
|
|
288
290
|
/**
|
|
@@ -704,19 +706,22 @@ export declare function dispatchSubmitAction(args: {
|
|
|
704
706
|
/**
|
|
705
707
|
* Channel-transport router's `tools/call` invoker (ggui#440) —
|
|
706
708
|
* iframe-polling transport, `tools/call` against the parent MCP host
|
|
707
|
-
* via `app.callServerTool` (spec-canonical)
|
|
708
|
-
*
|
|
709
|
+
* via `app.callServerTool` (spec-canonical); with no App bound the
|
|
710
|
+
* call answers a code-less error envelope — never a raw postMessage.
|
|
709
711
|
* Direct call (no LLM consent loop). Returns the tool's
|
|
710
712
|
* structuredContent (or `content[0]` if that's where the payload
|
|
711
713
|
* landed) as a `JsonValue`. On RPC error we throw — the router
|
|
712
714
|
* catches and silently retries on the next tick.
|
|
713
715
|
*
|
|
714
|
-
* Once
|
|
715
|
-
*
|
|
716
|
-
*
|
|
717
|
-
* capabilities already captured
|
|
718
|
-
*
|
|
719
|
-
*
|
|
716
|
+
* Once this document has confirmed it cannot relay (the latch above —
|
|
717
|
+
* set through the ONE writer on exactly three paths: a REAL gesture
|
|
718
|
+
* that failed relay-shaped on a host that never advertised
|
|
719
|
+
* `serverTools`, with capabilities already captured (`advert-silent`);
|
|
720
|
+
* a declared refusal code answered under a positive advertisement
|
|
721
|
+
* (`confirmed-refusal`); or the host's own failure of the mandatory
|
|
722
|
+
* `ui/initialize` handshake, before any gesture (`boot-failed`,
|
|
723
|
+
* ggui#830) — never a well-formed `{ok:false}` result; see
|
|
724
|
+
* `isRelayShapedFailure`), every subsequent poll will fail identically.
|
|
720
725
|
*
|
|
721
726
|
* This guard does NOT stop the router's poll loop, and nothing here
|
|
722
727
|
* should ever be written so that it could (ggui#443). It does two
|
|
@@ -742,10 +747,13 @@ export declare function dispatchSubmitAction(args: {
|
|
|
742
747
|
* (ggui's own embed host pre-Task-2 is exactly this case) — silently
|
|
743
748
|
* killing working channel polls forever. That is "absence of a
|
|
744
749
|
* capability blocks an attempt", which the fail-safe constraint
|
|
745
|
-
* forbids. The latch
|
|
746
|
-
*
|
|
747
|
-
*
|
|
748
|
-
*
|
|
750
|
+
* forbids. The latch is set only by a CONFIRMED OUTCOME — a failed
|
|
751
|
+
* real gesture (the advert-silent leg, or a confirmed refusal under a
|
|
752
|
+
* positive advertisement, ggui#599 cycle-2) or a failed handshake
|
|
753
|
+
* (ggui#830), never by an advertisement's absence — so testing the
|
|
754
|
+
* latch alone is sufficient: a host that answers its handshake and
|
|
755
|
+
* never fails a real gesture never latches, and its channels keep
|
|
756
|
+
* working.
|
|
749
757
|
*
|
|
750
758
|
* Extracted to a named export (rather than an inline closure inside
|
|
751
759
|
* `bootProduction`) so unit tests can drive it directly through the
|
package/dist/runtime.js
CHANGED
|
@@ -31,7 +31,7 @@ import { connectionStore } from './connection.js';
|
|
|
31
31
|
import { MCP_APP_BOOTSTRAP_FAILED_TYPE, MCP_APP_RENDERER_READY_TYPE, parseMcpAppAiGguiRenderMeta, readGguiShellEnvelope, } from '@ggui-ai/protocol/integrations/mcp-apps';
|
|
32
32
|
import { effectiveThemeId, effectiveThemeMode, } from '@ggui-ai/protocol/integrations/theme-binding';
|
|
33
33
|
import { extractLocatorFromToolResult, parseMetaFromGlobal, parseMetaFromToolResult, validateMeta, } from './meta-parse.js';
|
|
34
|
-
import { projectHostContext } from '@ggui-ai/protocol';
|
|
34
|
+
import { projectHostContext } from '@ggui-ai/protocol/wire';
|
|
35
35
|
import { App, PostMessageTransport } from '@modelcontextprotocol/ext-apps';
|
|
36
36
|
import { applyHostContextStyling, attachListener as attachHostContextListener, seed as seedHostContext, } from './host-context-emitter.js';
|
|
37
37
|
import { mapHostPaletteToGguiVars } from './host-palette-bridge.js';
|
|
@@ -421,11 +421,35 @@ export async function bootSequence(opts) {
|
|
|
421
421
|
// covers every boot path; arrivals before a renderer publishes
|
|
422
422
|
// `applyRender` no-op inside the listener.
|
|
423
423
|
installPersistentToolResultListener(app);
|
|
424
|
+
// Fresh boot ⇒ the relay latch and the connection store start
|
|
425
|
+
// aligned (ggui#670): a standing latch from a prior mount in a
|
|
426
|
+
// re-booted document must not outlive the store it was written to.
|
|
427
|
+
// It runs BEFORE the handshake so that every boot passes the close
|
|
428
|
+
// edge before it can latch — the boot-failed write below is then
|
|
429
|
+
// always on-edge (ggui#830 fold): a prior zone ends with its
|
|
430
|
+
// 'cleared' edge and its summary, never buried under a second
|
|
431
|
+
// 'latched' with its tally erased.
|
|
432
|
+
resetRelayLatchForBoot();
|
|
424
433
|
const initResult = await connectApp(app, transport);
|
|
425
434
|
if (!initResult.ok) {
|
|
426
435
|
setStatus(refs, `ui/initialize failed: ${initResult.message}`, 'error');
|
|
427
436
|
retireShellLoadingIndicator(doc);
|
|
428
437
|
emitBootFailure('UI_INITIALIZE_FAILED', initResult.message);
|
|
438
|
+
// The maximal dead zone (ggui#830): the host refused the handshake
|
|
439
|
+
// every session begins with, so nothing can be delivered here — the
|
|
440
|
+
// relay latch sets through its one writer (store false, 'latched'
|
|
441
|
+
// edge, standing notice) instead of leaving this document with
|
|
442
|
+
// `isConnected === true`, no explanation and no count. No gesture
|
|
443
|
+
// is needed for that knowledge, and no gesture is suppressed by it:
|
|
444
|
+
// every later tap still attempts (attempt-always) and lands as a
|
|
445
|
+
// counted dead tap. The scope is empty because no bootstrap has
|
|
446
|
+
// named a session yet; it clears the only way a handshake failure
|
|
447
|
+
// can — the next boot's close edge, which this boot itself already
|
|
448
|
+
// passed above, so this write is on-edge by construction. The
|
|
449
|
+
// gesture gate's pre-capture rule ("never latch before the host has
|
|
450
|
+
// been asked") does not apply here: the host HAS been asked — this
|
|
451
|
+
// is its answer to `ui/initialize`, resolved as failure.
|
|
452
|
+
announceRelayIncapability('boot-failed', {});
|
|
429
453
|
return { ok: false, mountedRender };
|
|
430
454
|
}
|
|
431
455
|
// Record what this host said it can do (ggui#440). Earliest valid
|
|
@@ -678,10 +702,6 @@ export async function bootSequence(opts) {
|
|
|
678
702
|
// Fresh boot ⇒ not superseded (matters only for test re-runs that
|
|
679
703
|
// reuse the module; a real iframe boots once).
|
|
680
704
|
mountSuperseded = false;
|
|
681
|
-
// Fresh boot ⇒ the relay latch and the connection store start
|
|
682
|
-
// aligned (ggui#670): a standing latch from a prior mount in a
|
|
683
|
-
// re-booted document must not outlive the store it was written to.
|
|
684
|
-
resetRelayLatchForBoot();
|
|
685
705
|
telemetry?.record('boot.path', JSON.stringify({
|
|
686
706
|
hasStaticContent,
|
|
687
707
|
hasLiveTrio,
|
|
@@ -1657,7 +1677,11 @@ async function callServerToolSpec(toolName, args) {
|
|
|
1657
1677
|
*
|
|
1658
1678
|
* Unknown codes NEVER confirm — they may be proxy- or server-minted
|
|
1659
1679
|
* (e.g. `-32603` internal error), and classifying them would misbrand
|
|
1660
|
-
* capable mounts. They
|
|
1680
|
+
* capable mounts. They never CONFIRM (they can latch only through
|
|
1681
|
+
* the advert-silent leg, never on an advertising host); off-latch they
|
|
1682
|
+
* stay on the per-gesture transient path, and inside a standing zone
|
|
1683
|
+
* they count as dead taps like every other undelivered outcome
|
|
1684
|
+
* (ggui#826).
|
|
1661
1685
|
*/
|
|
1662
1686
|
const CONFIRMED_RELAY_REFUSAL_CODES = new Set([-32601]);
|
|
1663
1687
|
/**
|
|
@@ -1775,6 +1799,20 @@ function classifySubmitActionResponse(resp) {
|
|
|
1775
1799
|
* carries a result envelope, so neither is evidence the relay worked.
|
|
1776
1800
|
*/
|
|
1777
1801
|
function isRelayShapedFailure(resp) {
|
|
1802
|
+
// "Relay-shaped" IS "undelivered" on this leg, by construction — not
|
|
1803
|
+
// by hope (ggui#832, ruled from the hosts' source): every JSON-RPC
|
|
1804
|
+
// error the iframe sees here is host- or transport-minted. The MCP
|
|
1805
|
+
// SDK server turns a tool handler's throw into an `isError` RESULT and
|
|
1806
|
+
// mints JSON-RPC codes only for its own gate; the guuey view-host
|
|
1807
|
+
// answers a rejecting relay with an in-band unavailable RESULT, never
|
|
1808
|
+
// an error; ggui's embed host proxies over HTTP and mints its OWN
|
|
1809
|
+
// codes for a failed proxy. So a server-side outcome always arrives
|
|
1810
|
+
// as a `CallToolResult` (`ok:true` and `ok:false` alike clear the
|
|
1811
|
+
// latch), and an error envelope always means the call did not round
|
|
1812
|
+
// trip. A third-party host that forwarded a server's JSON-RPC error
|
|
1813
|
+
// verbatim would be indistinguishable by code — that is the
|
|
1814
|
+
// host-helper conformance catalog's business (H3 / R1), not this
|
|
1815
|
+
// predicate's.
|
|
1778
1816
|
// Null-tolerant on `error`, matching `classifySubmitActionResponse`'s
|
|
1779
1817
|
// own check: an `{error:null, result:{...}}` envelope classifies as
|
|
1780
1818
|
// success there, so it must also read as NOT relay-shaped-failure
|
|
@@ -2308,6 +2346,22 @@ function transitionRelayLatch(next, trigger, scope = {}) {
|
|
|
2308
2346
|
relayLatchTrigger = undefined;
|
|
2309
2347
|
relayLatchScope = {};
|
|
2310
2348
|
}
|
|
2349
|
+
/**
|
|
2350
|
+
* Set the latch AND present it — the three steps every latch-set path
|
|
2351
|
+
* takes, in one place: the transition through the ONE writer (store
|
|
2352
|
+
* flip + `'latched'` edge), a fresh dead zone (unspent cue throttles —
|
|
2353
|
+
* the state belongs to THIS notice, so a second latch-set path never
|
|
2354
|
+
* has to know the clear edge already reset it), and the standing
|
|
2355
|
+
* explanation, marked so the per-gesture cue arms on its dismissal.
|
|
2356
|
+
* Callers: the dispatch outcome gate (a gesture's confirmed relay
|
|
2357
|
+
* failure) and the boot sequence (a refused `ui/initialize`, ggui#830).
|
|
2358
|
+
*/
|
|
2359
|
+
function announceRelayIncapability(trigger, scope) {
|
|
2360
|
+
transitionRelayLatch(true, trigger, scope);
|
|
2361
|
+
resetRelayCueThrottles();
|
|
2362
|
+
showActionToast('This host cannot relay actions to the agent — interactive controls will not work here.', 'action_required');
|
|
2363
|
+
markRelayNotice();
|
|
2364
|
+
}
|
|
2311
2365
|
/**
|
|
2312
2366
|
* One {@link RelayDeadTapEvent} per gesture attempted inside a standing
|
|
2313
2367
|
* dead zone that came back UNDELIVERED (ggui#670 Phase 3). Called from
|
|
@@ -2891,39 +2945,46 @@ export function dispatchSubmitAction(args) {
|
|
|
2891
2945
|
// helper-minted refusal code, which outranks any positive
|
|
2892
2946
|
// advertisement (the advertises-but-refuses host previously got a
|
|
2893
2947
|
// fresh transient toast per tap, forever). Timeouts and unknown
|
|
2894
|
-
// codes never reach the second leg
|
|
2895
|
-
//
|
|
2896
|
-
//
|
|
2897
|
-
//
|
|
2948
|
+
// codes never reach the second leg — they never CONFIRM (the
|
|
2949
|
+
// advert-silent leg alone can latch on them); inside a standing
|
|
2950
|
+
// zone they are counted like every other undelivered outcome by
|
|
2951
|
+
// the branch below (ggui#826). The un-classify edge for
|
|
2952
|
+
// BOTH legs is the same response-arrival clear guard above
|
|
2953
|
+
// (attempt-always preserved — nothing here suppresses dispatch,
|
|
2954
|
+
// per the #443 doctrine: the attempt is the self-heal sensor).
|
|
2898
2955
|
const confirmedRefusal = isConfirmedRelayRefusal(resp);
|
|
2956
|
+
if (relayIncapabilityAnnounced && isRelayShapedFailure(resp)) {
|
|
2957
|
+
// A gesture whose undelivered outcome landed inside a standing
|
|
2958
|
+
// dead zone — a dead tap, counted for the host (ggui#670 Phase 3);
|
|
2959
|
+
// an in-flight gesture that raced the latching one counts too.
|
|
2960
|
+
// Counted on the OUTCOME and keyed on the LATCH, never on
|
|
2961
|
+
// advertisement (ggui#826): the advertises-but-refuses host
|
|
2962
|
+
// latches by a confirmed refusal, and its later code-less or
|
|
2963
|
+
// timeout failures are exactly the undelivered gestures the
|
|
2964
|
+
// RelayDeadTapEvent contract promises to count. The eligibility
|
|
2965
|
+
// predicate below is the LATCH-SET rule — a timeout or unknown
|
|
2966
|
+
// code never CONFIRMS; it can latch only through the advert-silent
|
|
2967
|
+
// leg, never on an advertising host — and is the wrong key for
|
|
2968
|
+
// counting. Every outcome that
|
|
2969
|
+
// reaches here while the latch stands is relay-shaped by
|
|
2970
|
+
// construction (the response-arrival guard above cleared on any
|
|
2971
|
+
// well-formed result); the conjunct states the contract instead
|
|
2972
|
+
// of trusting that. The latching gesture is the 'latched' edge
|
|
2973
|
+
// and a gesture that heals the zone is the 'cleared' edge —
|
|
2974
|
+
// neither is a dead tap.
|
|
2975
|
+
emitRelayDeadTap(intent, { sessionId, appId });
|
|
2976
|
+
return;
|
|
2977
|
+
}
|
|
2899
2978
|
if (hostCapabilitiesCaptured() &&
|
|
2900
2979
|
((!hostCanRelayToolCalls() && isRelayShapedFailure(resp)) ||
|
|
2901
2980
|
confirmedRefusal)) {
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
// is created: a freshly-shown notice is undismissed, and its
|
|
2910
|
-
// dead zone starts with an unspent throttle. Today every
|
|
2911
|
-
// re-latch already passes through the clear edge above (which
|
|
2912
|
-
// resets both), so this is the same value arriving by a second
|
|
2913
|
-
// route — kept because the state belongs to THIS notice, and a
|
|
2914
|
-
// future second latch-set path shouldn't have to know that.
|
|
2915
|
-
resetRelayCueThrottles();
|
|
2916
|
-
showActionToast('This host cannot relay actions to the agent — interactive controls will not work here.', 'action_required');
|
|
2917
|
-
markRelayNotice();
|
|
2918
|
-
}
|
|
2919
|
-
else {
|
|
2920
|
-
// A gesture attempted inside a standing dead zone came back
|
|
2921
|
-
// undelivered — a dead tap, counted for the host (ggui#670
|
|
2922
|
-
// Phase 3). The latching gesture above is the 'latched' edge,
|
|
2923
|
-
// and a gesture that heals the zone is the 'cleared' edge —
|
|
2924
|
-
// neither is a dead tap.
|
|
2925
|
-
emitRelayDeadTap(intent, { sessionId, appId });
|
|
2926
|
-
}
|
|
2981
|
+
// Transition edge — the paired 'cleared' edge lives at the
|
|
2982
|
+
// response-arrival guard above. NEVER emit off-edge (e.g. per
|
|
2983
|
+
// channel poll tick): the router ticks on an interval and
|
|
2984
|
+
// would spam; the two edges carry the full information. A
|
|
2985
|
+
// standing latch never reaches here: its undelivered outcomes
|
|
2986
|
+
// were counted above, and a delivered one cleared it.
|
|
2987
|
+
announceRelayIncapability(confirmedRefusal ? 'confirmed-refusal' : 'advert-silent', { sessionId, appId });
|
|
2927
2988
|
return;
|
|
2928
2989
|
}
|
|
2929
2990
|
showActionToast(`⚠ ${intent} — could not reach the agent`, 'error');
|
|
@@ -2932,19 +2993,22 @@ export function dispatchSubmitAction(args) {
|
|
|
2932
2993
|
/**
|
|
2933
2994
|
* Channel-transport router's `tools/call` invoker (ggui#440) —
|
|
2934
2995
|
* iframe-polling transport, `tools/call` against the parent MCP host
|
|
2935
|
-
* via `app.callServerTool` (spec-canonical)
|
|
2936
|
-
*
|
|
2996
|
+
* via `app.callServerTool` (spec-canonical); with no App bound the
|
|
2997
|
+
* call answers a code-less error envelope — never a raw postMessage.
|
|
2937
2998
|
* Direct call (no LLM consent loop). Returns the tool's
|
|
2938
2999
|
* structuredContent (or `content[0]` if that's where the payload
|
|
2939
3000
|
* landed) as a `JsonValue`. On RPC error we throw — the router
|
|
2940
3001
|
* catches and silently retries on the next tick.
|
|
2941
3002
|
*
|
|
2942
|
-
* Once
|
|
2943
|
-
*
|
|
2944
|
-
*
|
|
2945
|
-
* capabilities already captured
|
|
2946
|
-
*
|
|
2947
|
-
*
|
|
3003
|
+
* Once this document has confirmed it cannot relay (the latch above —
|
|
3004
|
+
* set through the ONE writer on exactly three paths: a REAL gesture
|
|
3005
|
+
* that failed relay-shaped on a host that never advertised
|
|
3006
|
+
* `serverTools`, with capabilities already captured (`advert-silent`);
|
|
3007
|
+
* a declared refusal code answered under a positive advertisement
|
|
3008
|
+
* (`confirmed-refusal`); or the host's own failure of the mandatory
|
|
3009
|
+
* `ui/initialize` handshake, before any gesture (`boot-failed`,
|
|
3010
|
+
* ggui#830) — never a well-formed `{ok:false}` result; see
|
|
3011
|
+
* `isRelayShapedFailure`), every subsequent poll will fail identically.
|
|
2948
3012
|
*
|
|
2949
3013
|
* This guard does NOT stop the router's poll loop, and nothing here
|
|
2950
3014
|
* should ever be written so that it could (ggui#443). It does two
|
|
@@ -2970,10 +3034,13 @@ export function dispatchSubmitAction(args) {
|
|
|
2970
3034
|
* (ggui's own embed host pre-Task-2 is exactly this case) — silently
|
|
2971
3035
|
* killing working channel polls forever. That is "absence of a
|
|
2972
3036
|
* capability blocks an attempt", which the fail-safe constraint
|
|
2973
|
-
* forbids. The latch
|
|
2974
|
-
*
|
|
2975
|
-
*
|
|
2976
|
-
*
|
|
3037
|
+
* forbids. The latch is set only by a CONFIRMED OUTCOME — a failed
|
|
3038
|
+
* real gesture (the advert-silent leg, or a confirmed refusal under a
|
|
3039
|
+
* positive advertisement, ggui#599 cycle-2) or a failed handshake
|
|
3040
|
+
* (ggui#830), never by an advertisement's absence — so testing the
|
|
3041
|
+
* latch alone is sufficient: a host that answers its handshake and
|
|
3042
|
+
* never fails a real gesture never latches, and its channels keep
|
|
3043
|
+
* working.
|
|
2977
3044
|
*
|
|
2978
3045
|
* Extracted to a named export (rather than an inline closure inside
|
|
2979
3046
|
* `bootProduction`) so unit tests can drive it directly through the
|
package/dist/types.d.ts
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* declared the manager's constructor bag has been retired.
|
|
16
16
|
*/
|
|
17
17
|
import type { McpAppAiGguiRenderMeta } from '@ggui-ai/protocol/integrations/mcp-apps';
|
|
18
|
-
import type { HostContextProjection, ComponentGguiSession, SystemGguiSession } from '@ggui-ai/protocol';
|
|
18
|
+
import type { HostContextProjection, ComponentGguiSession, SystemGguiSession } from '@ggui-ai/protocol/wire';
|
|
19
19
|
/**
|
|
20
20
|
* The four server-assigned ledger fields on every wire-delivered
|
|
21
21
|
* {@link GguiSession}. They're stamped by the server's per-render event
|
package/dist/validation.d.ts
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
* host wrapper explicitly does NOT run this path — its only contract is
|
|
27
27
|
* the postMessage bridge to the renderer iframe.
|
|
28
28
|
*/
|
|
29
|
-
import type { ActionSpec, ActionEnvelope, ContextSpec, JsonObject, PropsSpec, ReservedChannelValidator, StreamSpec, ValidationResult } from '@ggui-ai/protocol';
|
|
29
|
+
import type { ActionSpec, ActionEnvelope, ContextSpec, JsonObject, PropsSpec, ReservedChannelValidator, StreamSpec, ValidationResult } from '@ggui-ai/protocol/wire';
|
|
30
30
|
import type { CompiledValidatorSet } from './compiled-validators.js';
|
|
31
31
|
/**
|
|
32
32
|
* Install the precompiled validators loaded from the bootstrap. Called
|
package/dist/validation.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isKnownReservedChannel, validateActionEnvelope, validateContextData, validatePropsData, validateStreamData, } from '@ggui-ai/protocol';
|
|
1
|
+
import { isKnownReservedChannel, validateActionEnvelope, validateContextData, validatePropsData, validateStreamData, } from '@ggui-ai/protocol/wire';
|
|
2
2
|
// =============================================================================
|
|
3
3
|
// Active precompiled-validator set
|
|
4
4
|
// =============================================================================
|
package/dist/wire-config.d.ts
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* ProtocolError dual-emission policy, and the tools/call-vs-WS
|
|
17
17
|
* dispatch transport.
|
|
18
18
|
*/
|
|
19
|
-
import type { ActionEnvelope, GguiSession } from '@ggui-ai/protocol';
|
|
19
|
+
import type { ActionEnvelope, GguiSession } from '@ggui-ai/protocol/wire';
|
|
20
20
|
import type { GguiSessionSeedInput } from './types.js';
|
|
21
21
|
import { ClientContractViolationError, StreamBus, type WireConfig } from '@ggui-ai/wire';
|
|
22
22
|
import type { WebSocketMessage } from '@ggui-ai/protocol/transport/websocket';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ggui-ai/iframe-runtime",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"description": "The iframe-local ggui runtime: a self-contained ESM bundle that boots inside an MCP Apps host iframe, runs the protocol-version handshake, opens the live-channel WebSocket, and mounts the rendered UI. Bundles React, ReactDOM, and the ggui design system so generated component code has a single seam to read from.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ggui",
|
|
@@ -41,12 +41,12 @@
|
|
|
41
41
|
"react": "^19.0.0",
|
|
42
42
|
"react-dom": "^19.0.0",
|
|
43
43
|
"zod": "^4.3.6",
|
|
44
|
-
"@ggui-ai/
|
|
45
|
-
"@ggui-ai/
|
|
46
|
-
"@ggui-ai/live-channel": "0.
|
|
47
|
-
"@ggui-ai/
|
|
48
|
-
"@ggui-ai/
|
|
49
|
-
"@ggui-ai/wire": "0.
|
|
44
|
+
"@ggui-ai/design": "0.15.0",
|
|
45
|
+
"@ggui-ai/gadgets": "0.15.0",
|
|
46
|
+
"@ggui-ai/live-channel": "0.15.0",
|
|
47
|
+
"@ggui-ai/preview-a2ui": "0.15.0",
|
|
48
|
+
"@ggui-ai/protocol": "0.15.0",
|
|
49
|
+
"@ggui-ai/wire": "0.15.0"
|
|
50
50
|
},
|
|
51
51
|
"peerDependencies": {
|
|
52
52
|
"@modelcontextprotocol/sdk": "^1.27.0"
|