@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.
Files changed (92) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +33 -0
  3. package/dist/bootstrap.d.ts +72 -0
  4. package/dist/bootstrap.js +500 -0
  5. package/dist/canvas/animator/animator-host.d.ts +63 -0
  6. package/dist/canvas/animator/animator-host.js +105 -0
  7. package/dist/canvas/animator/animator-pill.d.ts +42 -0
  8. package/dist/canvas/animator/animator-pill.js +119 -0
  9. package/dist/canvas/animator/state-machine.d.ts +106 -0
  10. package/dist/canvas/animator/state-machine.js +150 -0
  11. package/dist/canvas/canvas-shell.d.ts +77 -0
  12. package/dist/canvas/canvas-shell.js +117 -0
  13. package/dist/canvas/display-mode-policy.d.ts +104 -0
  14. package/dist/canvas/display-mode-policy.js +98 -0
  15. package/dist/canvas/mount.d.ts +150 -0
  16. package/dist/canvas/mount.js +202 -0
  17. package/dist/canvas/nav-stack.d.ts +133 -0
  18. package/dist/canvas/nav-stack.js +172 -0
  19. package/dist/channel-transport.d.ts +182 -0
  20. package/dist/channel-transport.js +344 -0
  21. package/dist/channels/channel-payload.d.ts +20 -0
  22. package/dist/channels/channel-payload.js +34 -0
  23. package/dist/channels/data.d.ts +48 -0
  24. package/dist/channels/data.js +103 -0
  25. package/dist/channels/drain-ack.d.ts +13 -0
  26. package/dist/channels/drain-ack.js +15 -0
  27. package/dist/channels/feedback.d.ts +14 -0
  28. package/dist/channels/feedback.js +19 -0
  29. package/dist/channels/index.d.ts +7 -0
  30. package/dist/channels/index.js +7 -0
  31. package/dist/channels/props-update.d.ts +53 -0
  32. package/dist/channels/props-update.js +130 -0
  33. package/dist/channels/push.d.ts +47 -0
  34. package/dist/channels/push.js +54 -0
  35. package/dist/channels/system.d.ts +26 -0
  36. package/dist/channels/system.js +44 -0
  37. package/dist/compiled-validators.d.ts +40 -0
  38. package/dist/compiled-validators.js +74 -0
  39. package/dist/context-observer.d.ts +205 -0
  40. package/dist/context-observer.js +312 -0
  41. package/dist/gadget-loader.d.ts +118 -0
  42. package/dist/gadget-loader.js +136 -0
  43. package/dist/globals.d.ts +234 -0
  44. package/dist/globals.js +101 -0
  45. package/dist/host-context-emitter.d.ts +91 -0
  46. package/dist/host-context-emitter.js +213 -0
  47. package/dist/iframe-runtime.js +261 -0
  48. package/dist/index.d.ts +38 -0
  49. package/dist/index.js +4 -0
  50. package/dist/lifecycle.d.ts +66 -0
  51. package/dist/lifecycle.js +49 -0
  52. package/dist/mcp-app-iframe-host.d.ts +69 -0
  53. package/dist/mcp-app-iframe-host.js +214 -0
  54. package/dist/observability.d.ts +225 -0
  55. package/dist/observability.js +28 -0
  56. package/dist/protocol-error.d.ts +199 -0
  57. package/dist/protocol-error.js +116 -0
  58. package/dist/provisional-renderer.d.ts +62 -0
  59. package/dist/provisional-renderer.js +323 -0
  60. package/dist/react-renderer.d.ts +95 -0
  61. package/dist/react-renderer.js +343 -0
  62. package/dist/registry-subscribe.d.ts +102 -0
  63. package/dist/registry-subscribe.js +319 -0
  64. package/dist/runtime.d.ts +531 -0
  65. package/dist/runtime.js +2623 -0
  66. package/dist/server.d.ts +36 -0
  67. package/dist/server.js +72 -0
  68. package/dist/stack-item-renderer.d.ts +103 -0
  69. package/dist/stack-item-renderer.js +270 -0
  70. package/dist/stack.d.ts +90 -0
  71. package/dist/stack.js +118 -0
  72. package/dist/status-dom.d.ts +72 -0
  73. package/dist/status-dom.js +51 -0
  74. package/dist/system-cards/NoCredentialsCard.d.ts +33 -0
  75. package/dist/system-cards/NoCredentialsCard.js +189 -0
  76. package/dist/system-cards/ProtocolProbeCard.d.ts +64 -0
  77. package/dist/system-cards/ProtocolProbeCard.js +463 -0
  78. package/dist/system-cards/QrCode.d.ts +46 -0
  79. package/dist/system-cards/QrCode.js +55 -0
  80. package/dist/system-cards/host-detect.d.ts +31 -0
  81. package/dist/system-cards/host-detect.js +108 -0
  82. package/dist/system-cards/host-intents.d.ts +85 -0
  83. package/dist/system-cards/host-intents.js +170 -0
  84. package/dist/system-cards/index.d.ts +70 -0
  85. package/dist/system-cards/index.js +134 -0
  86. package/dist/types.d.ts +69 -0
  87. package/dist/types.js +3 -0
  88. package/dist/validation.d.ts +83 -0
  89. package/dist/validation.js +106 -0
  90. package/dist/wire-config.d.ts +214 -0
  91. package/dist/wire-config.js +290 -0
  92. 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;