@ggui-ai/iframe-runtime 0.1.0-rc.1
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/LICENSE +201 -0
- package/README.md +33 -0
- package/dist/bootstrap.d.ts +72 -0
- package/dist/bootstrap.js +500 -0
- package/dist/canvas/animator/animator-host.d.ts +63 -0
- package/dist/canvas/animator/animator-host.js +105 -0
- package/dist/canvas/animator/animator-pill.d.ts +42 -0
- package/dist/canvas/animator/animator-pill.js +119 -0
- package/dist/canvas/animator/state-machine.d.ts +106 -0
- package/dist/canvas/animator/state-machine.js +150 -0
- package/dist/canvas/canvas-shell.d.ts +77 -0
- package/dist/canvas/canvas-shell.js +117 -0
- package/dist/canvas/display-mode-policy.d.ts +104 -0
- package/dist/canvas/display-mode-policy.js +98 -0
- package/dist/canvas/mount.d.ts +150 -0
- package/dist/canvas/mount.js +202 -0
- package/dist/canvas/nav-stack.d.ts +133 -0
- package/dist/canvas/nav-stack.js +172 -0
- package/dist/channel-transport.d.ts +182 -0
- package/dist/channel-transport.js +344 -0
- package/dist/channels/channel-payload.d.ts +20 -0
- package/dist/channels/channel-payload.js +34 -0
- package/dist/channels/data.d.ts +48 -0
- package/dist/channels/data.js +103 -0
- package/dist/channels/drain-ack.d.ts +13 -0
- package/dist/channels/drain-ack.js +15 -0
- package/dist/channels/feedback.d.ts +14 -0
- package/dist/channels/feedback.js +19 -0
- package/dist/channels/index.d.ts +7 -0
- package/dist/channels/index.js +7 -0
- package/dist/channels/props-update.d.ts +53 -0
- package/dist/channels/props-update.js +130 -0
- package/dist/channels/push.d.ts +47 -0
- package/dist/channels/push.js +54 -0
- package/dist/channels/system.d.ts +26 -0
- package/dist/channels/system.js +44 -0
- package/dist/compiled-validators.d.ts +40 -0
- package/dist/compiled-validators.js +74 -0
- package/dist/context-observer.d.ts +205 -0
- package/dist/context-observer.js +312 -0
- package/dist/gadget-loader.d.ts +118 -0
- package/dist/gadget-loader.js +136 -0
- package/dist/globals.d.ts +234 -0
- package/dist/globals.js +101 -0
- package/dist/host-context-emitter.d.ts +91 -0
- package/dist/host-context-emitter.js +213 -0
- package/dist/iframe-runtime.js +261 -0
- package/dist/index.d.ts +38 -0
- package/dist/index.js +4 -0
- package/dist/lifecycle.d.ts +66 -0
- package/dist/lifecycle.js +49 -0
- package/dist/mcp-app-iframe-host.d.ts +69 -0
- package/dist/mcp-app-iframe-host.js +214 -0
- package/dist/observability.d.ts +225 -0
- package/dist/observability.js +28 -0
- package/dist/protocol-error.d.ts +199 -0
- package/dist/protocol-error.js +116 -0
- package/dist/provisional-renderer.d.ts +62 -0
- package/dist/provisional-renderer.js +323 -0
- package/dist/react-renderer.d.ts +95 -0
- package/dist/react-renderer.js +343 -0
- package/dist/registry-subscribe.d.ts +102 -0
- package/dist/registry-subscribe.js +319 -0
- package/dist/runtime.d.ts +531 -0
- package/dist/runtime.js +2623 -0
- package/dist/server.d.ts +36 -0
- package/dist/server.js +72 -0
- package/dist/stack-item-renderer.d.ts +103 -0
- package/dist/stack-item-renderer.js +270 -0
- package/dist/stack.d.ts +90 -0
- package/dist/stack.js +118 -0
- package/dist/status-dom.d.ts +72 -0
- package/dist/status-dom.js +51 -0
- package/dist/system-cards/NoCredentialsCard.d.ts +33 -0
- package/dist/system-cards/NoCredentialsCard.js +189 -0
- package/dist/system-cards/ProtocolProbeCard.d.ts +64 -0
- package/dist/system-cards/ProtocolProbeCard.js +463 -0
- package/dist/system-cards/QrCode.d.ts +46 -0
- package/dist/system-cards/QrCode.js +55 -0
- package/dist/system-cards/host-detect.d.ts +31 -0
- package/dist/system-cards/host-detect.js +108 -0
- package/dist/system-cards/host-intents.d.ts +85 -0
- package/dist/system-cards/host-intents.js +170 -0
- package/dist/system-cards/index.d.ts +70 -0
- package/dist/system-cards/index.js +134 -0
- package/dist/types.d.ts +69 -0
- package/dist/types.js +3 -0
- package/dist/validation.d.ts +83 -0
- package/dist/validation.js +106 -0
- package/dist/wire-config.d.ts +214 -0
- package/dist/wire-config.js +290 -0
- package/package.json +81 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Renderer-local contract-validation seam.
|
|
3
|
+
*
|
|
4
|
+
* Every wire-traffic check in the iframe-runtime routes through this
|
|
5
|
+
* module. Its two jobs:
|
|
6
|
+
*
|
|
7
|
+
* 1. **Precompiled-validator substitution (A4 / CSP fix).** The
|
|
8
|
+
* renderer iframe runs under a strict CSP with no `'unsafe-eval'`,
|
|
9
|
+
* so it cannot call `ajv.compile()` (codegen via `new Function`).
|
|
10
|
+
* The server precompiles each contract sub-schema into an
|
|
11
|
+
* eval-free ESM validator module and ships them on the bootstrap;
|
|
12
|
+
* {@link loadCompiledValidators} loads them into a
|
|
13
|
+
* {@link CompiledValidatorSet} which {@link setActiveValidatorSet}
|
|
14
|
+
* installs here. Each validator below passes the matching
|
|
15
|
+
* precompiled `ValidateFunction` into the protocol validator,
|
|
16
|
+
* which uses it INSTEAD of compiling the schema in-iframe.
|
|
17
|
+
* A defensive try/catch degrades to permissive if the protocol
|
|
18
|
+
* validator still throws (precompiled validator absent + CSP
|
|
19
|
+
* blocks the `ajv.compile()` fallback) — a dispatched action
|
|
20
|
+
* beats a silently-dropped one.
|
|
21
|
+
*
|
|
22
|
+
* 2. **Reserved-channel validator composition** — A2UI default for
|
|
23
|
+
* `_ggui:preview` composed with any bootstrap-supplied overrides.
|
|
24
|
+
*
|
|
25
|
+
* The renderer owns reserved-channel validation. The `<McpAppIframe>`
|
|
26
|
+
* host wrapper explicitly does NOT run this path — its only contract is
|
|
27
|
+
* the postMessage bridge to the renderer iframe.
|
|
28
|
+
*/
|
|
29
|
+
import type { ActionSpec, ActionEnvelope, ContextSpec, JsonObject, PropsSpec, ReservedChannelValidator, StreamSpec, ValidationResult } from '@ggui-ai/protocol';
|
|
30
|
+
import type { CompiledValidatorSet } from './compiled-validators.js';
|
|
31
|
+
/**
|
|
32
|
+
* Install the precompiled validators loaded from the bootstrap. Called
|
|
33
|
+
* once per boot (both `bootProduction` and `bootSelfContained`), after
|
|
34
|
+
* the bootstrap is parsed. A single set per iframe document — one
|
|
35
|
+
* session, one active contract.
|
|
36
|
+
*/
|
|
37
|
+
export declare function setActiveValidatorSet(set: CompiledValidatorSet | undefined): void;
|
|
38
|
+
/**
|
|
39
|
+
* Merge two reserved-validator maps. Override keys WIN on conflict.
|
|
40
|
+
* The renderer ships an A2UI default for `_ggui:preview`; the
|
|
41
|
+
* bootstrap MAY replace it by key (not yet wired on the producer
|
|
42
|
+
* side — reserved as an extensibility slot).
|
|
43
|
+
*/
|
|
44
|
+
export declare function mergeReservedValidators(base: ReadonlyMap<string, ReservedChannelValidator> | undefined, override: ReadonlyMap<string, ReservedChannelValidator> | undefined): ReadonlyMap<string, ReservedChannelValidator> | undefined;
|
|
45
|
+
export interface RendererValidatorContext {
|
|
46
|
+
/**
|
|
47
|
+
* Composed reserved-channel validators resolved once at boot.
|
|
48
|
+
* Passed through to `validateStreamData`'s `extraReservedValidators`
|
|
49
|
+
* param. `undefined` is honest-permissive (matches the pre-Item-4
|
|
50
|
+
* behavior; useful for test harnesses).
|
|
51
|
+
*/
|
|
52
|
+
readonly reservedValidators: ReadonlyMap<string, ReservedChannelValidator> | undefined;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Validate an outbound {@link ActionEnvelope} before emitting over the
|
|
56
|
+
* live channel. Symmetric with the server's inbound enforcement.
|
|
57
|
+
*
|
|
58
|
+
* `actionSpec === undefined` = permissive (the server's "no contract to
|
|
59
|
+
* enforce" posture). Uses the precompiled action validators when
|
|
60
|
+
* present so the dispatch never trips the iframe's no-`unsafe-eval` CSP.
|
|
61
|
+
*/
|
|
62
|
+
export declare function validateOutboundActionEnvelope(actionSpec: ActionSpec | undefined, envelope: ActionEnvelope): ValidationResult;
|
|
63
|
+
/**
|
|
64
|
+
* Validate an inbound stream-channel delivery before applying it to
|
|
65
|
+
* stack-item state. Known reserved channels (`_ggui:*`) validate even
|
|
66
|
+
* when the target stack item has no declared `streamSpec` — the
|
|
67
|
+
* payload shape is server-owned; user channels stay permissive without
|
|
68
|
+
* a contract.
|
|
69
|
+
*/
|
|
70
|
+
export declare function validateInboundStreamPayload(streamSpec: StreamSpec | undefined, channelName: string, payload: unknown, ctx: RendererValidatorContext): ValidationResult;
|
|
71
|
+
/**
|
|
72
|
+
* Validate an inbound `props_update` payload before patching a stack
|
|
73
|
+
* item's props. Server already runs `assertPropsContract` on the emit
|
|
74
|
+
* path; this client-side revalidation is defense-in-depth for
|
|
75
|
+
* spec-versioning drift (server snapshot ≠ client cached spec).
|
|
76
|
+
*/
|
|
77
|
+
export declare function validateInboundPropsPayload(propsSpec: PropsSpec | undefined, props: JsonObject): ValidationResult;
|
|
78
|
+
/**
|
|
79
|
+
* Validate a contextSpec slot value before the observer posts a
|
|
80
|
+
* `ui/update-model-context` envelope. Uses the precompiled context
|
|
81
|
+
* validators when present so the gate never trips the CSP.
|
|
82
|
+
*/
|
|
83
|
+
export declare function validateContextValue(spec: ContextSpec, slotName: string, value: unknown): ValidationResult;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { isKnownReservedChannel, validateActionEnvelope, validateContextData, validatePropsData, validateStreamData, } from '@ggui-ai/protocol';
|
|
2
|
+
// =============================================================================
|
|
3
|
+
// Active precompiled-validator set
|
|
4
|
+
// =============================================================================
|
|
5
|
+
/**
|
|
6
|
+
* Boot-installed precompiled validators for the active stack item's
|
|
7
|
+
* contract. `undefined` until {@link setActiveValidatorSet} runs (and
|
|
8
|
+
* in unit tests, which exercise the in-iframe compilation fallback).
|
|
9
|
+
*/
|
|
10
|
+
let activeValidatorSet;
|
|
11
|
+
/**
|
|
12
|
+
* Install the precompiled validators loaded from the bootstrap. Called
|
|
13
|
+
* once per boot (both `bootProduction` and `bootSelfContained`), after
|
|
14
|
+
* the bootstrap is parsed. A single set per iframe document — one
|
|
15
|
+
* session, one active contract.
|
|
16
|
+
*/
|
|
17
|
+
export function setActiveValidatorSet(set) {
|
|
18
|
+
activeValidatorSet = set;
|
|
19
|
+
}
|
|
20
|
+
const VALID = { valid: true, violations: [] };
|
|
21
|
+
/**
|
|
22
|
+
* Run a protocol validator, degrading to permissive if it throws. The
|
|
23
|
+
* only throw path is the CSP `EvalError` from an `ajv.compile()`
|
|
24
|
+
* fallback when no precompiled validator covered the surface — better a
|
|
25
|
+
* dispatched action than a crash that silently drops it.
|
|
26
|
+
*/
|
|
27
|
+
function guarded(label, run) {
|
|
28
|
+
try {
|
|
29
|
+
return run();
|
|
30
|
+
}
|
|
31
|
+
catch (err) {
|
|
32
|
+
if (typeof console !== 'undefined') {
|
|
33
|
+
console.warn(`[ggui:validation] ${label}: validator unavailable (no precompiled validator + CSP blocks in-iframe compile) — allowing through`, err);
|
|
34
|
+
}
|
|
35
|
+
return VALID;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
// =============================================================================
|
|
39
|
+
// Reserved validator map composition
|
|
40
|
+
// =============================================================================
|
|
41
|
+
/**
|
|
42
|
+
* Merge two reserved-validator maps. Override keys WIN on conflict.
|
|
43
|
+
* The renderer ships an A2UI default for `_ggui:preview`; the
|
|
44
|
+
* bootstrap MAY replace it by key (not yet wired on the producer
|
|
45
|
+
* side — reserved as an extensibility slot).
|
|
46
|
+
*/
|
|
47
|
+
export function mergeReservedValidators(base, override) {
|
|
48
|
+
if (!base && !override)
|
|
49
|
+
return undefined;
|
|
50
|
+
if (!base)
|
|
51
|
+
return override;
|
|
52
|
+
if (!override)
|
|
53
|
+
return base;
|
|
54
|
+
const merged = new Map(base);
|
|
55
|
+
for (const [key, validator] of override) {
|
|
56
|
+
merged.set(key, validator);
|
|
57
|
+
}
|
|
58
|
+
return merged;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Validate an outbound {@link ActionEnvelope} before emitting over the
|
|
62
|
+
* live channel. Symmetric with the server's inbound enforcement.
|
|
63
|
+
*
|
|
64
|
+
* `actionSpec === undefined` = permissive (the server's "no contract to
|
|
65
|
+
* enforce" posture). Uses the precompiled action validators when
|
|
66
|
+
* present so the dispatch never trips the iframe's no-`unsafe-eval` CSP.
|
|
67
|
+
*/
|
|
68
|
+
export function validateOutboundActionEnvelope(actionSpec, envelope) {
|
|
69
|
+
return guarded('outbound-action', () => validateActionEnvelope(envelope, actionSpec, activeValidatorSet?.actions));
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Validate an inbound stream-channel delivery before applying it to
|
|
73
|
+
* stack-item state. Known reserved channels (`_ggui:*`) validate even
|
|
74
|
+
* when the target stack item has no declared `streamSpec` — the
|
|
75
|
+
* payload shape is server-owned; user channels stay permissive without
|
|
76
|
+
* a contract.
|
|
77
|
+
*/
|
|
78
|
+
export function validateInboundStreamPayload(streamSpec, channelName, payload, ctx) {
|
|
79
|
+
return guarded('inbound-stream', () => {
|
|
80
|
+
if (isKnownReservedChannel(channelName)) {
|
|
81
|
+
return validateStreamData(channelName, payload, streamSpec ?? {}, ctx.reservedValidators, activeValidatorSet?.streams);
|
|
82
|
+
}
|
|
83
|
+
if (!streamSpec)
|
|
84
|
+
return VALID;
|
|
85
|
+
return validateStreamData(channelName, payload, streamSpec, ctx.reservedValidators, activeValidatorSet?.streams);
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Validate an inbound `props_update` payload before patching a stack
|
|
90
|
+
* item's props. Server already runs `assertPropsContract` on the emit
|
|
91
|
+
* path; this client-side revalidation is defense-in-depth for
|
|
92
|
+
* spec-versioning drift (server snapshot ≠ client cached spec).
|
|
93
|
+
*/
|
|
94
|
+
export function validateInboundPropsPayload(propsSpec, props) {
|
|
95
|
+
if (!propsSpec)
|
|
96
|
+
return VALID;
|
|
97
|
+
return guarded('inbound-props', () => validatePropsData(props, propsSpec, activeValidatorSet?.props));
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Validate a contextSpec slot value before the observer posts a
|
|
101
|
+
* `ui/update-model-context` envelope. Uses the precompiled context
|
|
102
|
+
* validators when present so the gate never trips the CSP.
|
|
103
|
+
*/
|
|
104
|
+
export function validateContextValue(spec, slotName, value) {
|
|
105
|
+
return guarded('context-slot', () => validateContextData(slotName, value, spec, activeValidatorSet?.context));
|
|
106
|
+
}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-stack-item `WireConfig` factory for the renderer iframe.
|
|
3
|
+
*
|
|
4
|
+
* The renderer iframe uses a standalone
|
|
5
|
+
* `scopeWireConfig(root, item, internals)` function plus the
|
|
6
|
+
* `RootWireConfigBundle.buildScopedConfig(item)` closure returned
|
|
7
|
+
* from `buildRootWireConfig` — there is no scoping method on the
|
|
8
|
+
* public `WireConfig` shape.
|
|
9
|
+
*
|
|
10
|
+
* Why per-item scoping:
|
|
11
|
+
* - A stacked/chat-style UI keeps older cards rendered beneath the
|
|
12
|
+
* top one. A `useAction('submit')` call inside an older card MUST
|
|
13
|
+
* emit with THAT card's `stackItemId` + `tool` + `actionSpec` — not the
|
|
14
|
+
* top-of-stack's. Without per-item scoping, contract enforcement
|
|
15
|
+
* would reject cross-stack dispatches as "wrong contract" OR the
|
|
16
|
+
* wrong tool would be invoked server-side.
|
|
17
|
+
*
|
|
18
|
+
* Construction: the renderer builds ONE top-level bundle at boot
|
|
19
|
+
* (`buildRootWireConfig`), which returns `{config, buildScopedConfig}`.
|
|
20
|
+
* The runtime's `stackCtx.getScopedWireConfig` calls
|
|
21
|
+
* `buildScopedConfig(item)` per stack entry; each
|
|
22
|
+
* `<GguiWireProvider>` wraps a stack item with the resulting scoped
|
|
23
|
+
* config.
|
|
24
|
+
*
|
|
25
|
+
* Outbound dispatch uses `buildActionEnvelope` (from
|
|
26
|
+
* `@ggui-ai/wire`) + `validateOutboundActionEnvelope` + a direct
|
|
27
|
+
* `manager.send({type:'action', payload})`.
|
|
28
|
+
*
|
|
29
|
+
* The `WireConfig` shape lives in `@ggui-ai/wire`. This module
|
|
30
|
+
* implements against that interface.
|
|
31
|
+
*/
|
|
32
|
+
import type { ActionSpec, SessionStackEntry, StreamEnvelope } from '@ggui-ai/protocol';
|
|
33
|
+
import { ClientContractViolationError, type WireConfig } from '@ggui-ai/wire';
|
|
34
|
+
import type { WebSocketMessage } from '@ggui-ai/protocol/transport/websocket';
|
|
35
|
+
/**
|
|
36
|
+
* Outbound send surface — the minimal shape wire-config calls on the
|
|
37
|
+
* WS transport. Pre-B3b this read off `Pick<RendererWebSocketManager,
|
|
38
|
+
* 'send'>`; post-B3b the WS lives inside `@ggui-ai/channel-client`,
|
|
39
|
+
* but wire-config doesn't import the gadget directly — it consumes
|
|
40
|
+
* the shape via this local type so tests can stub `{send: vi.fn()}`
|
|
41
|
+
* without dragging in the channel-client types.
|
|
42
|
+
*
|
|
43
|
+
* Frames sent here are always `{type: 'action', payload: ...}` or
|
|
44
|
+
* `{type: 'feedback', payload: ...}` envelopes — wire-config never
|
|
45
|
+
* sends transport-layer frames (`ping`, `subscribe`).
|
|
46
|
+
*/
|
|
47
|
+
export interface RendererSendSurface {
|
|
48
|
+
readonly send: (message: WebSocketMessage) => void;
|
|
49
|
+
}
|
|
50
|
+
import { type ProtocolErrorEmitter } from './protocol-error.js';
|
|
51
|
+
import type { ObservabilityEmitter } from './observability.js';
|
|
52
|
+
/**
|
|
53
|
+
* Tiny fan-out bus keyed by channel name. `subscribe.ts`'s inbound-
|
|
54
|
+
* message handler pushes validated `data` envelopes here; wire's
|
|
55
|
+
* `useStream(channel)` subscribes per-component via the config's
|
|
56
|
+
* `subscribe()` method.
|
|
57
|
+
*
|
|
58
|
+
* Late-subscriber replay for reserved channels:
|
|
59
|
+
*
|
|
60
|
+
* The renderer iframe boots in a strict ordering:
|
|
61
|
+
* `subscribe()` → first `ack` → render stack items → mount
|
|
62
|
+
* `mountProvisional` → it then calls `streamBus.subscribe('_ggui:
|
|
63
|
+
* preview', …)`. Server-side replay frames for `_ggui:preview` (the
|
|
64
|
+
* provisional A2UI preamble fired during the agent's `ggui_push`
|
|
65
|
+
* BEFORE the user's browser navigated to the viewer) arrive over the
|
|
66
|
+
* WS in the window between ack-resolve and the provisional mount —
|
|
67
|
+
* they hit `emit()` before any listener has subscribed. Without a
|
|
68
|
+
* bounded reserved-channel ring, those frames vanish and the preview
|
|
69
|
+
* surface stays stuck on the spinner.
|
|
70
|
+
*
|
|
71
|
+
* Server-owned `_ggui:*` channels are buffered in a per-channel ring
|
|
72
|
+
* capped at {@link RESERVED_CHANNEL_REPLAY_MAX}. New subscribers
|
|
73
|
+
* receive the buffered envelopes synchronously before the unsubscribe
|
|
74
|
+
* handle returns — same mental model as the server-side
|
|
75
|
+
* `SessionStreamBuffer.replay(...)` walk over reserved channels at
|
|
76
|
+
* ack time, but mirrored at the inner bus boundary so the host
|
|
77
|
+
* transport stays portable (Claude Desktop / ChatGPT / Cursor /
|
|
78
|
+
* `<McpAppIframe>` all behave the same).
|
|
79
|
+
*
|
|
80
|
+
* Agent-declared (non-reserved) channels are NOT buffered here. Their
|
|
81
|
+
* replay is the server's job — the renderer's `subscribe()` handshake
|
|
82
|
+
* pulls history per `streamSpec.replay` policy when reconnecting with
|
|
83
|
+
* `fromSeq`. Buffering them at this layer would double-count and
|
|
84
|
+
* change the semantics of agent contracts.
|
|
85
|
+
*
|
|
86
|
+
* No observable mutation outside the renderer boundary — the bus is
|
|
87
|
+
* internal plumbing.
|
|
88
|
+
*/
|
|
89
|
+
export declare class StreamBus {
|
|
90
|
+
private listeners;
|
|
91
|
+
/**
|
|
92
|
+
* Per-channel bounded ring of envelopes for reserved (`_ggui:*`)
|
|
93
|
+
* channels only. FIFO eviction at `RESERVED_CHANNEL_REPLAY_MAX`.
|
|
94
|
+
* Replayed to every new subscriber for that channel.
|
|
95
|
+
*/
|
|
96
|
+
private reservedReplay;
|
|
97
|
+
subscribe(channel: string, listener: (env: StreamEnvelope) => void): () => void;
|
|
98
|
+
emit(envelope: StreamEnvelope): void;
|
|
99
|
+
}
|
|
100
|
+
export interface BuildRootWireConfigOptions {
|
|
101
|
+
readonly sessionId: string;
|
|
102
|
+
readonly appId: string;
|
|
103
|
+
/**
|
|
104
|
+
* Stack snapshot reader. The config's default (unscoped) `dispatch`
|
|
105
|
+
* reads the top of stack; callers route most dispatches through
|
|
106
|
+
* `scopeWireConfig(root, item)` for per-item targeting, but the
|
|
107
|
+
* default is kept for standalone-render paths.
|
|
108
|
+
*/
|
|
109
|
+
readonly getStack: () => readonly SessionStackEntry[];
|
|
110
|
+
/** Handle to the renderer's WS manager; used for outbound `action` + `feedback` frames. */
|
|
111
|
+
readonly manager: RendererSendSurface;
|
|
112
|
+
/** Shared bus for inbound stream deliveries. */
|
|
113
|
+
readonly streamBus: StreamBus;
|
|
114
|
+
/**
|
|
115
|
+
* Optional sink for contract violations. Default: tagged
|
|
116
|
+
* `console.warn`. Tests inject a recorder. Matches
|
|
117
|
+
* `GguiSession.onError` semantics on the client-contract path.
|
|
118
|
+
*
|
|
119
|
+
* Prefer {@link onProtocolError} for new integrations — it receives
|
|
120
|
+
* the widened {@link ProtocolError} union that covers every error
|
|
121
|
+
* the renderer classifies (transport, auth, protocol, contract,
|
|
122
|
+
* bootstrap, version, unknown). The narrow `onContractViolation`
|
|
123
|
+
* sink stays for in-renderer tests that assert the raw class shape;
|
|
124
|
+
* every violation is ALSO forwarded through `onProtocolError` so
|
|
125
|
+
* external consumers can observe one seam.
|
|
126
|
+
*/
|
|
127
|
+
readonly onContractViolation?: (err: ClientContractViolationError) => void;
|
|
128
|
+
/**
|
|
129
|
+
* Optional sink for every typed {@link ProtocolError} the renderer
|
|
130
|
+
* classifies. Default: {@link defaultProtocolErrorEmitter}
|
|
131
|
+
* (`console.warn` with a grep-friendly tag). The `<McpAppIframe>`
|
|
132
|
+
* host wrapper wires this to its `onError` prop; session-bound
|
|
133
|
+
* variants are ALSO mirrored to the `_ggui:contract-error` WS
|
|
134
|
+
* envelope.
|
|
135
|
+
*
|
|
136
|
+
* The emitter fires for CLIENT-side contract violations (via
|
|
137
|
+
* `fromClientContractViolation`). Other sites (subscribe failures,
|
|
138
|
+
* upgrade-required, transport errors, server-emitted
|
|
139
|
+
* contract-error envelopes) plumb through the same emitter without
|
|
140
|
+
* a shape change.
|
|
141
|
+
*/
|
|
142
|
+
readonly onProtocolError?: ProtocolErrorEmitter;
|
|
143
|
+
/**
|
|
144
|
+
* Optional {@link ObservabilityEmitter} sink. Fires a
|
|
145
|
+
* `wired-tool-invoked` event every time {@link dispatchByItem}
|
|
146
|
+
* successfully sends a wired-action envelope (i.e. the ctx's
|
|
147
|
+
* resolved `tool` is populated AND outbound validation passed).
|
|
148
|
+
*
|
|
149
|
+
* The event is client-side — `dispatchedAt` records when the
|
|
150
|
+
* envelope left the renderer, NOT when the server's router
|
|
151
|
+
* completed the tool. The SERVER independently emits a
|
|
152
|
+
* `wired-tool.invoked` telemetry event on its own sink for
|
|
153
|
+
* operational metrics; the two surfaces are deliberately separate
|
|
154
|
+
* (host inspector vs operator telemetry).
|
|
155
|
+
*/
|
|
156
|
+
readonly onObserve?: ObservabilityEmitter;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Paired root config + scope factory returned by `buildRootWireConfig`.
|
|
160
|
+
*
|
|
161
|
+
* {@link WireConfig} itself carries no `scope(item)` method — the
|
|
162
|
+
* interface is the minimum contract generated code needs, and
|
|
163
|
+
* per-item scoping is a RENDERER concern. Callers that need
|
|
164
|
+
* per-item scoping — currently only the renderer's `bootProduction`
|
|
165
|
+
* via `stackCtx.getScopedWireConfig` — use `buildScopedConfig(item)`
|
|
166
|
+
* on the returned `RootWireConfigBundle`.
|
|
167
|
+
*
|
|
168
|
+
* The scope-factory is PAIRED with the root construction so the
|
|
169
|
+
* `dispatchByItem` / `getStack` internals stay encapsulated — they
|
|
170
|
+
* aren't exported from this module and never leak into the public
|
|
171
|
+
* WireConfig shape.
|
|
172
|
+
*/
|
|
173
|
+
export interface RootWireConfigBundle {
|
|
174
|
+
readonly config: WireConfig;
|
|
175
|
+
/**
|
|
176
|
+
* Build a per-stack-item scoped `WireConfig`. Only `dispatch` is
|
|
177
|
+
* overridden — every other seam (subscribe, app/session/auth) stays
|
|
178
|
+
* shared via spread from the root.
|
|
179
|
+
*/
|
|
180
|
+
readonly buildScopedConfig: (item: {
|
|
181
|
+
readonly stackItemId?: string;
|
|
182
|
+
readonly contractHash?: string;
|
|
183
|
+
readonly actionSpec?: ActionSpec;
|
|
184
|
+
}) => WireConfig;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Build the unscoped root `WireConfig` + its scope-factory closure.
|
|
188
|
+
* Bootstraps the renderer's outbound emission + inbound subscription
|
|
189
|
+
* seams.
|
|
190
|
+
*
|
|
191
|
+
* Returns a {@link RootWireConfigBundle} rather than a bare
|
|
192
|
+
* `WireConfig` — the scope factory is paired with the root so
|
|
193
|
+
* `dispatchByItem` internals don't leak onto the public WireConfig
|
|
194
|
+
* shape. `scopeWireConfig` (the exported standalone) is the low-level
|
|
195
|
+
* primitive; `buildScopedConfig` is the pre-bound convenience.
|
|
196
|
+
*/
|
|
197
|
+
export declare function buildRootWireConfig(opts: BuildRootWireConfigOptions): RootWireConfigBundle;
|
|
198
|
+
/**
|
|
199
|
+
* Produce a per-item scoped `WireConfig`. The only override is
|
|
200
|
+
* `dispatch` — every other seam (subscribe) stays shared.
|
|
201
|
+
*/
|
|
202
|
+
export declare function scopeWireConfig(root: WireConfig, item: {
|
|
203
|
+
stackItemId?: string;
|
|
204
|
+
contractHash?: string;
|
|
205
|
+
actionSpec?: ActionSpec;
|
|
206
|
+
}, internals: {
|
|
207
|
+
getStack: () => readonly SessionStackEntry[];
|
|
208
|
+
dispatchByItem: (actionName: string, data: unknown, ctx: {
|
|
209
|
+
stackItemId?: string;
|
|
210
|
+
stackIndex: number;
|
|
211
|
+
tool?: string;
|
|
212
|
+
actionSpec?: ActionSpec;
|
|
213
|
+
}) => void;
|
|
214
|
+
}): WireConfig;
|