@ethisyscore/extension-runtime 1.9.2 → 1.10.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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/host/worker/offscreen.ts","../../src/host/worker/transport.ts","../../src/host/worker/component-registry.ts","../../src/mock-host/cli.ts"],"names":["fs"],"mappings":"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","file":"cli.js","sourcesContent":["/**\n * OffscreenCanvas transfer helpers + pointer/keyboard coalescing for\n * Contract B (worker remote-runtime) extensions.\n *\n * **Architecture.** Hosts that expose a {@link import(\"./component-registry\").SemanticComponentRegistry}'s\n * `CanvasSurface` / `WebGLSurface` primitive create a host-side `<canvas>`,\n * transfer control to the worker via {@link createOffscreenCanvasTransfer}, and\n * the plugin (running inside the worker realm) calls `offscreen.getContext()`\n * to render. The `<canvas>` is owned by the host DOM tree (so layout, theming,\n * and accessibility properties stay on the host side) but every pixel is\n * produced inside the worker — no per-frame `postMessage` cost, no main-thread\n * blocking.\n *\n * **Pointer / keyboard delivery.** Input events still originate on the host\n * `<canvas>`. To avoid spraying the worker port with per-event traffic the\n * host should wrap pointer-move / wheel / scroll callbacks in\n * {@link createInputEventCoalescer}, matching the trailing-edge coalescer\n * already documented in {@link import(\"./transport\").WorkerRemoteDomTransport}\n * (default 16ms ≈ one rAF). Keyboard events MUST use `discrete: true` —\n * coalescing them would drop characters.\n *\n * **Security.** No capability token, no MCP traffic, and no host-only globals\n * cross via the offscreen transfer. The transfer envelope is plain JSON with a\n * single `OffscreenCanvas` handle in the transfer list — the worker side\n * receives an opaque drawing surface and nothing else.\n */\n\n// ─── Public constants ────────────────────────────────────────────────────────\n\n/**\n * Default trailing-edge coalesce window for pointer / wheel / scroll events,\n * in milliseconds. 16ms ≈ one animation frame at 60Hz. Matches the default\n * documented in {@link import(\"./transport\").WorkerRemoteDomTransport}.\n */\nexport const DEFAULT_OFFSCREEN_COALESCE_MS = 16;\n\n// ─── Transfer envelope ───────────────────────────────────────────────────────\n\n/**\n * Host → worker envelope shape for an `OffscreenCanvas` transfer. Stable wire\n * contract — the worker side decodes by `type` and uses `surfaceId` to bind\n * the offscreen to the right host slot when a plugin renders multiple\n * canvases concurrently.\n */\nexport interface OffscreenTransferMessage\n{\n type: \"ethisys:offscreen:transfer\";\n surfaceId: string;\n width: number;\n height: number;\n offscreen: OffscreenCanvas;\n}\n\n/**\n * Construction options for {@link createOffscreenCanvasTransfer}.\n */\nexport interface OffscreenCanvasTransferOptions\n{\n /**\n * The host-owned `<canvas>` element. Its `width` / `height` are captured\n * before transfer so the worker sees the intended pixel dimensions even if\n * the host element resizes later (host should send a separate resize\n * message in that case).\n */\n canvas: HTMLCanvasElement;\n\n /**\n * Stable identifier the worker uses to route the offscreen to the matching\n * surface slot. Typically the SDUI node id of the `CanvasSurface` /\n * `WebGLSurface` primitive that the worker will draw into.\n */\n surfaceId: string;\n\n /**\n * Host-side postMessage function — typically the\n * {@link import(\"./transport\").WorkerRemoteDomTransport}'s port-side\n * `postMessage`. Tests inject a spy.\n */\n postMessage(message: OffscreenTransferMessage, transfer: Transferable[]): void;\n}\n\n/**\n * Successful transfer result. The caller retains a reference to the\n * `OffscreenCanvas` only for observability (e.g., to wire to the worker's\n * reply handshake) — direct rendering from the host side is forbidden because\n * control has already been transferred and any host-side draw call would\n * throw.\n */\nexport interface OffscreenCanvasTransferResult\n{\n offscreen: OffscreenCanvas;\n}\n\n/**\n * Module-scoped WeakSet tracking canvases this SDK has already transferred.\n *\n * Stored OUTSIDE the DOM node deliberately:\n *\n * - Writing an SDK expando (e.g. `__ethisysOffscreenTransferred`) onto a host-\n * owned `<canvas>` mutates state the host doesn't own. A WeakSet keeps the\n * bookkeeping on the SDK side and lets the canvas object stay pristine.\n * - WeakSet entries are collected when the canvas is GC'd, so the guard does\n * not leak memory for plugins that mount and unmount many surfaces.\n * - WeakSet membership is identity-based, so React/concurrent remount paths\n * that re-use the SAME node still see \"already transferred\"; a fresh DOM\n * node (always allocated by React when the key changes) is a fresh entry.\n */\nconst transferredCanvases: WeakSet<HTMLCanvasElement> = new WeakSet<HTMLCanvasElement>();\n\n/**\n * Transfer control of a host-owned `<canvas>` to the worker.\n *\n * Throws if:\n * - the environment lacks `HTMLCanvasElement.prototype.transferControlToOffscreen`\n * (the helper does NOT silently fall back — Contract B clients must require\n * the API and surface a clear error in unsupported browsers);\n * - the same canvas has already had control transferred (idempotency is the\n * caller's responsibility — re-transferring would throw in the browser\n * anyway, but we raise a clearer, diagnosable message ahead of that).\n */\nexport function createOffscreenCanvasTransfer(\n options: OffscreenCanvasTransferOptions,\n): OffscreenCanvasTransferResult\n{\n const { canvas, surfaceId, postMessage } = options;\n const proto = HTMLCanvasElement.prototype as unknown as {\n transferControlToOffscreen?: () => OffscreenCanvas;\n };\n if (typeof proto.transferControlToOffscreen !== \"function\")\n {\n throw new Error(\n \"[offscreen-canvas] OffscreenCanvas is not supported in this environment. \" +\n \"Contract B canvas surfaces require a browser with HTMLCanvasElement.transferControlToOffscreen.\",\n );\n }\n if (transferredCanvases.has(canvas))\n {\n throw new Error(\n `[offscreen-canvas] canvas already transferred (surfaceId='${surfaceId}'). ` +\n \"Create a fresh <canvas> for each surface instead of re-transferring.\",\n );\n }\n\n const offscreen = canvas.transferControlToOffscreen();\n transferredCanvases.add(canvas);\n\n const envelope: OffscreenTransferMessage = {\n type: \"ethisys:offscreen:transfer\",\n surfaceId,\n width: canvas.width,\n height: canvas.height,\n offscreen,\n };\n postMessage(envelope, [offscreen]);\n\n return { offscreen };\n}\n\n// ─── Coalescing ──────────────────────────────────────────────────────────────\n\n/**\n * Construction options for {@link createInputEventCoalescer}.\n */\nexport interface InputEventCoalescerOptions\n{\n /**\n * Coalesce window in milliseconds. Ignored when {@link discrete} is true.\n * Defaults to {@link DEFAULT_OFFSCREEN_COALESCE_MS} (16ms — one rAF).\n */\n coalesceMs?: number;\n\n /**\n * When `true`, forward every payload synchronously without coalescing.\n * Use this for keyboard events — coalescing would drop characters by\n * collapsing multiple key strokes into one trailing delivery.\n */\n discrete?: boolean;\n}\n\n/**\n * Build a trailing-edge input-event coalescer. The pattern matches\n * {@link import(\"./transport\").WorkerRemoteDomTransport.createCoalescer} —\n * factored out here so OffscreenCanvas callers don't need to construct a full\n * transport just to coalesce pointer-move events.\n *\n * Trailing-edge semantics: every burst within a coalesce window collapses to\n * a single delivery carrying the **last** payload seen in the window. A new\n * payload arriving after a window has flushed starts a fresh window.\n */\nexport function createInputEventCoalescer<T>(\n sink: (payload: T) => void,\n options: InputEventCoalescerOptions = {},\n): (payload: T) => void\n{\n if (options.discrete === true)\n {\n // Discrete mode: pass through unchanged. Keyboard input belongs here.\n return (payload: T) =>\n {\n sink(payload);\n };\n }\n const coalesceMs = options.coalesceMs ?? DEFAULT_OFFSCREEN_COALESCE_MS;\n let pending: T | undefined;\n let pendingSet = false;\n let timer: ReturnType<typeof setTimeout> | undefined;\n\n const flush = (): void =>\n {\n timer = undefined;\n if (!pendingSet)\n {\n return;\n }\n const value = pending as T;\n pending = undefined;\n pendingSet = false;\n sink(value);\n };\n\n return (payload: T) =>\n {\n pending = payload;\n pendingSet = true;\n if (timer === undefined)\n {\n timer = setTimeout(flush, coalesceMs);\n }\n };\n}\n","/**\n * Worker-side host transport for `renderMode: remote-runtime` extensions\n * (Contract B). Owns the lifecycle of:\n *\n * 1. A path-pinned module `Worker` spawned from a host-issued bootstrap URL.\n * 2. A `MessagePort`-based RPC channel (handed to the worker on spawn).\n * 3. A Shopify Remote DOM root receiver — wired here so the worker's UI\n * tree mounts into a host React tree.\n * 4. Event coalescing (default ≤16ms, one rAF) for high-frequency input\n * events before they cross the port.\n * 5. MCP tool/resource calls forwarded through the port — but **the\n * capability token never crosses the boundary**. The token is bound\n * to the worker scope on the HOST side: the host transport intercepts\n * the plugin's MCP request, attaches the token to the outbound HTTP\n * call, and forwards ONLY the response payload back through the port.\n *\n * This module is intentionally framework-thin: callers wire `mount(root)`\n * onto a React tree (typically inside a `<WorkerSurfaceMount>`), and the\n * Remote DOM receiver is responsible for translating worker-side\n * RemoteRoot mutations into host-side component renders.\n */\n\nimport { createOffscreenCanvasTransfer } from \"./offscreen\";\n\n// ─── Public types ─────────────────────────────────────────────────────────────\n\n/**\n * Structural subset of the DOM `Worker` interface that the transport needs.\n * Tests inject a fake; production callers pass the global `Worker`.\n */\nexport interface WorkerLike\n{\n onmessage: ((ev: MessageEvent) => void) | null;\n onerror: ((ev: ErrorEvent) => void) | null;\n onmessageerror: ((ev: MessageEvent) => void) | null;\n postMessage(message: unknown, transfer?: Transferable[]): void;\n terminate(): void;\n}\n\n/**\n * Constructor signature compatible with the DOM `Worker` constructor. The\n * `workerCtor` option exists exclusively so tests can swap in a fake — in\n * production the caller passes the global `Worker`.\n */\nexport type WorkerCtor = new (url: string | URL, options?: WorkerOptions) => WorkerLike;\n\n/**\n * Discriminated union of MCP fetch shapes the host transport issues. The\n * `capabilityToken` is attached on the host side ONLY — it is never present\n * in any message that crosses the worker port.\n */\nexport type McpHttpRequest =\n | {\n kind: \"invokeTool\";\n name: string;\n args: unknown;\n capabilityToken: string;\n signal?: AbortSignal;\n }\n | {\n kind: \"getResource\";\n uri: string;\n capabilityToken: string;\n signal?: AbortSignal;\n };\n\n/**\n * Host-side HTTP client for MCP calls. Implementations are responsible for\n * carrying the supplied `capabilityToken` on the outbound request (typically\n * via `Authorization: Bearer`).\n */\nexport interface McpHttpClient\n{\n fetch(request: McpHttpRequest): Promise<{ ok: boolean; data?: unknown; error?: string }>;\n}\n\n/**\n * Construction options for {@link WorkerRemoteDomTransport}.\n */\nexport interface WorkerRemoteDomTransportOptions\n{\n /**\n * Canonical host-origin URL for the worker bootstrap script. This MUST\n * be supplied by the host — never built from user input or extension\n * manifest values — so the worker spawn is path-pinned.\n */\n bootstrapUrl: string;\n\n /**\n * Mints (or fetches a cached) capability token. The host transport\n * resolves this for each outbound MCP HTTP call. The token NEVER leaves\n * host scope — plugin code (the worker side) cannot read it.\n */\n capabilityToken: () => Promise<string>;\n\n /**\n * Host-side MCP HTTP client. The transport bridges port-side plugin\n * MCP requests to this client and returns only the response payload\n * back through the port.\n */\n mcpClient: McpHttpClient;\n\n /**\n * Injectable Worker constructor (tests use a fake). Defaults to the\n * global `Worker`.\n */\n workerCtor?: WorkerCtor;\n\n /**\n * Coalescing window in milliseconds for high-frequency events. Defaults\n * to 16ms (~one animation frame). Trailing-edge: the most recent payload\n * within the window is delivered after the window elapses.\n */\n coalesceMs?: number;\n\n /**\n * Maximum number of MCP requests (combined `invokeTool` + `getResource`)\n * the transport will forward to {@link mcpClient} concurrently. Defaults\n * to 8. Requests above the cap are rejected with a deterministic error\n * reply over the port — the worker MUST handle that response shape.\n *\n * Bounded concurrency is a back-pressure boundary, not a quota: it stops\n * a compromised or buggy worker from saturating the host's HTTP / token\n * pool. Production hosts may want to lower this in multi-tenant pools\n * where many extensions share one host process.\n */\n maxConcurrentMcpRequests?: number;\n}\n\n/**\n * Wire shape of a host-sourced input event forwarded to the worker over the\n * port. Pointer / wheel / keyboard payloads share a single envelope type so\n * the worker has one inbound dispatch site. The transport is structurally\n * agnostic to the payload — it forwards verbatim.\n *\n * `kind` discriminates the event family. Coordinates are CSS pixels relative\n * to the host canvas; coalescing happens host-side before the call to\n * {@link WorkerRemoteDomTransport.postInputEvent}.\n */\nexport type InputEventPayload =\n | {\n kind: \"pointermove\" | \"pointerdown\" | \"pointerup\" | \"pointercancel\";\n surfaceId: string;\n x: number;\n y: number;\n buttons: number;\n pointerType: string;\n }\n | {\n kind: \"wheel\";\n surfaceId: string;\n deltaX: number;\n deltaY: number;\n deltaMode: number;\n }\n | {\n kind: \"keydown\" | \"keyup\";\n surfaceId: string;\n key: string;\n code: string;\n ctrlKey: boolean;\n shiftKey: boolean;\n altKey: boolean;\n metaKey: boolean;\n };\n\n/**\n * Documented wire protocol identifier embedded in handshake / message\n * envelopes. Bumped when the worker ↔ host message shape changes in a\n * backward-incompatible way.\n */\nexport const WORKER_TRANSPORT_PROTOCOL = \"ethisys.worker.remotedom.v1\";\n\n/**\n * Wire shape of the handshake message the host posts to the worker on\n * {@link WorkerRemoteDomTransport.connect}. Locked here so the SDK side and the\n * API-hosted bootstrap script (`WorkerBootstrapScriptProvider`) read from the\n * same field names — see the API tests pinning `event.data.moduleUrl` and\n * `event.data.importMap`.\n *\n * The handshake is the SOLE message that ever carries `moduleUrl` or\n * `importMap`; subsequent port traffic uses the discriminated message types\n * declared below. The capability token NEVER appears in this payload (or any\n * other postMessage payload) — it is bound on the host side via\n * {@link WorkerRemoteDomTransportOptions.capabilityToken}.\n */\nexport interface WorkerHandshakePayload\n{\n readonly type: \"ethisys:worker:handshake\";\n readonly protocol: string;\n /** Host-origin URL of the plugin's worker bundle entry. */\n readonly moduleUrl: string;\n /**\n * Frozen bare-specifier → host-origin URL map that the bootstrap installs\n * before importing {@link moduleUrl}. Pulled from the plugin's\n * `worker-bundle.import-map.json` at runtime by the host mount.\n */\n readonly importMap: Readonly<Record<string, string>>;\n}\n\n// ─── Implementation ───────────────────────────────────────────────────────────\n\ninterface InvokeToolMessage\n{\n id: string;\n type: \"ethisys:mcp:invokeTool\";\n name: string;\n args: unknown;\n}\n\ninterface GetResourceMessage\n{\n id: string;\n type: \"ethisys:mcp:getResource\";\n uri: string;\n}\n\ninterface RemoteDomMessage\n{\n type: \"ethisys:remotedom\";\n payload: unknown;\n}\n\ntype InboundMessage = InvokeToolMessage | GetResourceMessage | RemoteDomMessage | { type: string; [k: string]: unknown };\n\n/**\n * Worker-side host transport for Contract B extensions.\n *\n * Construction immediately spawns the worker and transfers one end of a\n * fresh `MessageChannel`; the host retains `port1`, the worker receives\n * `port2`. Both sides exchange messages via their port and the worker port\n * is the *only* channel for plugin ↔ host traffic after handshake.\n */\n/**\n * Default cap for in-flight MCP requests forwarded by the transport. Chosen to\n * cover typical interactive UI traffic (dashboards, list views, detail panels)\n * without letting a runaway worker saturate the host's HTTP / token pool.\n */\nexport const DEFAULT_MAX_CONCURRENT_MCP_REQUESTS = 8;\n\nexport class WorkerRemoteDomTransport\n{\n private readonly worker: WorkerLike;\n private readonly hostPort: MessagePort;\n private readonly workerPort: MessagePort;\n private readonly mcpClient: McpHttpClient;\n private readonly capabilityTokenProvider: () => Promise<string>;\n private readonly coalesceMs: number;\n private readonly maxConcurrentMcpRequests: number;\n private readonly abortController: AbortController;\n private inFlightMcpRequests = 0;\n private remoteDomConsumer: ((payload: unknown) => void) | undefined;\n private disposed = false;\n private connected = false;\n\n public constructor(options: WorkerRemoteDomTransportOptions)\n {\n const WorkerCtor = options.workerCtor ?? (globalThis as unknown as { Worker: WorkerCtor }).Worker;\n if (!WorkerCtor)\n {\n throw new Error(\"WorkerRemoteDomTransport: no Worker constructor available\");\n }\n\n this.mcpClient = options.mcpClient;\n this.capabilityTokenProvider = options.capabilityToken;\n this.coalesceMs = options.coalesceMs ?? 16;\n this.maxConcurrentMcpRequests = Math.max(\n 1,\n options.maxConcurrentMcpRequests ?? DEFAULT_MAX_CONCURRENT_MCP_REQUESTS,\n );\n // Aborted from dispose() — propagates into the mcpClient.fetch path so\n // already-in-flight HTTP calls can short-circuit instead of resolving\n // into a closed port.\n this.abortController = new AbortController();\n\n // Spawn the worker FIRST so the constructor's spawn shape is\n // observable to tests even if MessageChannel construction throws.\n this.worker = new WorkerCtor(options.bootstrapUrl, { type: \"module\" });\n\n const channel = new MessageChannel();\n this.hostPort = channel.port1;\n this.workerPort = channel.port2;\n\n // Wire host-side port handling before {@link connect} posts the\n // handshake — this closes a race where the worker could reply faster\n // than we wire up.\n this.hostPort.onmessage = (ev) => this.handlePortMessage(ev.data as InboundMessage);\n this.hostPort.start();\n\n this.worker.onerror = (ev) =>\n {\n // Defensive: do NOT echo worker errors back to the worker — they\n // may carry plugin-side details. Host-side observability is the\n // platform's responsibility.\n void ev;\n };\n }\n\n /**\n * Post the handshake to the worker. Idempotent — only the FIRST call\n * transfers the {@link MessagePort} and posts the handshake payload;\n * subsequent calls are silent no-ops. Splitting this off from the\n * constructor lets the host mount fetch the per-plugin\n * `worker-bundle.import-map.json` (and resolve the bundle's module URL)\n * before the bootstrap script consumes them.\n *\n * Wire shape: {@link WorkerHandshakePayload}. The capability token NEVER\n * appears in the payload — it's bound on the host side via the\n * {@link WorkerRemoteDomTransportOptions.capabilityToken} provider.\n *\n * The `moduleUrl` and `importMap` are forwarded to the worker so the\n * bootstrap script (served from the host-pinned\n * `/extensions/runtime/worker-bootstrap.js`) can:\n * 1. Compose the frozen, same-origin-validated `IMPORT_MAP` from the\n * handshake payload (rejecting any cross-origin entries).\n * 2. `safeImport(moduleUrl)` the plugin's entry module.\n *\n * Same-origin enforcement is the bootstrap's job — this transport is\n * structurally agnostic to the host origin and only forwards what it's\n * handed.\n */\n public connect(moduleUrl: string, importMap: Record<string, string>): void\n {\n if (this.connected)\n {\n return;\n }\n this.connected = true;\n\n // Snapshot the import map into a fresh object so the worker handshake\n // is decoupled from caller-side mutation. `Object.freeze` here is\n // defence-in-depth — the worker rebuilds with `Object.create(null)`\n // anyway, but freezing the host-side payload makes mutation a typed\n // error for any host code that hangs onto the reference.\n const handshake: WorkerHandshakePayload = Object.freeze({\n type: \"ethisys:worker:handshake\",\n protocol: WORKER_TRANSPORT_PROTOCOL,\n moduleUrl,\n importMap: Object.freeze({ ...importMap }),\n });\n this.worker.postMessage(handshake, [this.workerPort]);\n }\n\n /**\n * Bind a consumer for Remote DOM mutation payloads emitted by the worker.\n * The Remote DOM receiver wiring is owned by the caller (typically a host\n * React component); this method exposes the raw stream so the receiver\n * can integrate cleanly without a circular package import.\n */\n public onRemoteDom(consumer: (payload: unknown) => void): void\n {\n this.remoteDomConsumer = consumer;\n }\n\n /**\n * Transfer control of a host-owned `<canvas>` to the worker so plugin code\n * can render via `OffscreenCanvas`. The host retains the `<canvas>` for\n * layout / accessibility purposes only — every pixel is produced inside the\n * worker, so the main thread is never blocked per frame.\n *\n * The transfer rides the established MessagePort (not the worker global\n * `postMessage`) so it is multiplexed with the rest of the host ↔ worker\n * traffic on the same channel. The `OffscreenCanvas` handle is the sole\n * `Transferable` in the envelope; no capability token, MCP context, or\n * other host-only data crosses the boundary.\n *\n * Throws if the environment lacks `transferControlToOffscreen()` or if the\n * canvas has already been transferred — see {@link createOffscreenCanvasTransfer}\n * for the exact diagnostics.\n */\n public transferCanvas(\n canvas: HTMLCanvasElement,\n options: { surfaceId: string },\n ): { offscreen: OffscreenCanvas }\n {\n return createOffscreenCanvasTransfer({\n canvas,\n surfaceId: options.surfaceId,\n postMessage: (message, transfer) =>\n {\n this.hostPort.postMessage(message, transfer);\n },\n });\n }\n\n /**\n * Forward a host-sourced input event to the worker over the port. The\n * payload shape is opaque to the transport — pointer / wheel / keyboard\n * envelopes share the same wire type. Callers are expected to wrap\n * high-frequency (pointer-move, wheel, scroll) events in\n * {@link createCoalescer} or {@link createInputEventCoalescer} BEFORE\n * calling this method so the port never sees the un-coalesced flood.\n *\n * Keyboard / pointer-down / pointer-up events are discrete and should be\n * delivered without coalescing — pass them straight through.\n */\n public postInputEvent(payload: InputEventPayload): void\n {\n this.hostPort.postMessage({\n type: \"ethisys:input:event\",\n payload,\n });\n }\n\n /**\n * Build a trailing-edge coalescer keyed to {@link WorkerRemoteDomTransportOptions.coalesceMs}.\n *\n * Use it to wrap pointer-move / scroll / resize callbacks **before**\n * they cross the port. The last payload in any coalescing window is the\n * one that wins.\n */\n public createCoalescer<T>(consumer: (payload: T) => void): (payload: T) => void\n {\n let pending: T | undefined;\n let timer: ReturnType<typeof setTimeout> | undefined;\n const flush = () =>\n {\n timer = undefined;\n const value = pending;\n pending = undefined;\n if (value !== undefined)\n {\n consumer(value);\n }\n };\n return (payload: T) =>\n {\n pending = payload;\n if (timer === undefined)\n {\n timer = setTimeout(flush, this.coalesceMs);\n }\n };\n }\n\n /**\n * Tear down the worker and port. Idempotent.\n *\n * Order matters: we abort BEFORE closing the port so any in-flight\n * `mcpClient.fetch` that observes the signal short-circuits and the\n * subsequent attempt to post a reply lands in the disposed-guard branch\n * (which silently drops the post) instead of throwing on a closed port.\n */\n public dispose(): void\n {\n if (this.disposed)\n {\n return;\n }\n this.disposed = true;\n try\n {\n this.abortController.abort();\n }\n catch\n {\n // AbortController.abort() shouldn't throw, but never let dispose\n // crash the caller — teardown must be best-effort across all\n // resources.\n }\n try\n {\n this.hostPort.close();\n }\n catch\n {\n // ignore — port may already be closed\n }\n try\n {\n this.worker.terminate();\n }\n catch\n {\n // ignore\n }\n }\n\n /**\n * Best-effort wrapper around `hostPort.postMessage` that tolerates posts\n * arriving after {@link dispose}. The browser throws on a closed port and\n * the async handlers below can race dispose, so any post initiated by an\n * awaited continuation must be guarded.\n */\n private safePostMessage(message: unknown): void\n {\n if (this.disposed)\n {\n return;\n }\n try\n {\n this.hostPort.postMessage(message);\n }\n catch\n {\n // Port may have been closed between the disposed check and the\n // post call (browser closes ports asynchronously). Swallowing the\n // throw here is safe because we are already on a teardown path.\n }\n }\n\n // ─── Port message handling ──────────────────────────────────────────────\n\n private handlePortMessage(message: InboundMessage): void\n {\n // Post-dispose port traffic is dropped silently. The browser closes\n // MessagePorts asynchronously; any handler invocation after dispose\n // is a race we observe-and-skip rather than try to service.\n if (this.disposed)\n {\n return;\n }\n switch (message.type)\n {\n case \"ethisys:mcp:invokeTool\":\n this.dispatchMcp(message as InvokeToolMessage, \"ethisys:mcp:invokeTool:result\", (m) => this.handleInvokeTool(m));\n return;\n\n case \"ethisys:mcp:getResource\":\n this.dispatchMcp(message as GetResourceMessage, \"ethisys:mcp:getResource:result\", (m) => this.handleGetResource(m));\n return;\n\n case \"ethisys:remotedom\":\n this.remoteDomConsumer?.((message as RemoteDomMessage).payload);\n return;\n\n default:\n // Unknown messages are ignored. The host MUST NOT echo unknown\n // shapes back to the worker — that would create an attacker-\n // controlled reflection surface.\n return;\n }\n }\n\n /**\n * Gate inbound MCP requests through the bounded concurrency window,\n * reject with a deterministic error reply when the cap is exceeded, and\n * decrement the counter unconditionally when the underlying handler\n * settles (regardless of resolution/rejection shape).\n */\n private dispatchMcp<T extends { id: string }>(\n message: T,\n resultType: string,\n handler: (m: T) => Promise<void>,\n ): void\n {\n if (this.inFlightMcpRequests >= this.maxConcurrentMcpRequests)\n {\n // Back-pressure boundary. The worker MUST surface this to plugin\n // code so it can retry / degrade gracefully rather than silently\n // hang on a never-resolving promise.\n this.safePostMessage({\n id: message.id,\n type: resultType,\n ok: false,\n error: `MCP back-pressure: in-flight cap of ${this.maxConcurrentMcpRequests} reached.`,\n });\n return;\n }\n this.inFlightMcpRequests++;\n handler(message).finally(() =>\n {\n this.inFlightMcpRequests = Math.max(0, this.inFlightMcpRequests - 1);\n });\n }\n\n private async handleInvokeTool(message: InvokeToolMessage): Promise<void>\n {\n let token: string;\n try\n {\n token = await this.capabilityTokenProvider();\n }\n catch (err)\n {\n this.replyError(message.id, \"ethisys:mcp:invokeTool:result\", err);\n return;\n }\n // Disposed-before-token: short-circuit. The token mint may have\n // resolved seconds after dispose; posting the reply now would race\n // a closed port.\n if (this.disposed)\n {\n return;\n }\n try\n {\n const result = await this.mcpClient.fetch({\n kind: \"invokeTool\",\n name: message.name,\n args: message.args,\n capabilityToken: token,\n signal: this.abortController.signal,\n });\n // CRITICAL: response payload must never include the token. We\n // forward ONLY the public-facing fields the plugin needs.\n this.safePostMessage({\n id: message.id,\n type: \"ethisys:mcp:invokeTool:result\",\n ok: result.ok,\n data: result.data,\n error: result.error,\n });\n }\n catch (err)\n {\n this.replyError(message.id, \"ethisys:mcp:invokeTool:result\", err);\n }\n }\n\n private async handleGetResource(message: GetResourceMessage): Promise<void>\n {\n let token: string;\n try\n {\n token = await this.capabilityTokenProvider();\n }\n catch (err)\n {\n this.replyError(message.id, \"ethisys:mcp:getResource:result\", err);\n return;\n }\n if (this.disposed)\n {\n return;\n }\n try\n {\n const result = await this.mcpClient.fetch({\n kind: \"getResource\",\n uri: message.uri,\n capabilityToken: token,\n signal: this.abortController.signal,\n });\n this.safePostMessage({\n id: message.id,\n type: \"ethisys:mcp:getResource:result\",\n ok: result.ok,\n data: result.data,\n error: result.error,\n });\n }\n catch (err)\n {\n this.replyError(message.id, \"ethisys:mcp:getResource:result\", err);\n }\n }\n\n private replyError(id: string, type: string, err: unknown): void\n {\n // Surface a sanitised error string — never the raw error object\n // (which may carry stack traces or token fragments from a misbehaving\n // mcpClient implementation). Routed through safePostMessage so a\n // post-dispose reply does not crash on a closed port.\n const message = err instanceof Error ? err.message : \"MCP request failed\";\n this.safePostMessage({ id, type, ok: false, error: message });\n }\n}\n","/**\n * Semantic component registry for Contract B (worker remote-runtime)\n * extensions.\n *\n * Worker-side plugin code references UI primitives by name — e.g.\n * `<DataTable />`, `<Drawer />`, `<CanvasSurface />`. The host owns rendering:\n * a {@link SemanticComponentRegistry} maps each name to a concrete component\n * type, and the Remote DOM host receiver consults the registry on every\n * worker-emitted mutation. The closed primitive vocabulary {@link CONTRACT_B_PRIMITIVES}\n * is intentionally narrow — adding a primitive is a coordinated host change so\n * plugins cannot extend the contract ad hoc.\n *\n * **Alignment with SDUI:** Where the Contract B name overlaps with the\n * `@ethisyscore/protocol` SDUI primitive vocabulary (`DataTable`, `Form`), the\n * names are identical to avoid synonym drift. SDUI's `Action` is intentionally\n * NOT reused — Contract B's interactive primitive is named `Button` so the\n * worker authoring API stays close to React DOM idioms. The two vocabularies\n * may converge as they evolve, but the host registries (Contract A SDUI vs\n * Contract B Semantic) remain distinct surfaces today.\n *\n * The registry is renderer-agnostic — the generic `TComponent` parameter is\n * whatever component type the host supplies (React `ComponentType` in\n * production; plain objects in tests). This keeps the registry free of a\n * React peer-dep requirement and lets tests assert behaviour without rendering.\n */\n\n// ─── Closed v1 vocabulary ─────────────────────────────────────────────────────\n\n/**\n * The mandatory Contract B (worker remote-runtime) semantic primitive vocabulary.\n *\n * Adding a primitive requires a coordinated host change + protocol bump —\n * plugins cannot extend this set. Each name has a host-supplied concrete\n * component (gogo-ui in production); the registry enforces 1:1 substitution.\n */\nexport const CONTRACT_B_PRIMITIVES = [\n \"Button\",\n \"DataTable\",\n \"Form\",\n \"EntityPicker\",\n \"CommandBar\",\n \"Drawer\",\n \"Modal\",\n \"CanvasSurface\",\n \"WebGLSurface\",\n // Phase 1 additions — mirror the vite-plugin's CONTRACT_B_SEMANTIC_PRIMITIVES\n // list verbatim; drift between the two is caught by the\n // contract-b.test.ts \"import-map-allowlist matches extension-runtime\n // CONTRACT_B_PRIMITIVES\" test.\n \"Card\",\n \"Tabs\",\n \"Select\",\n \"Alert\",\n] as const;\n\n/**\n * Union type of the Contract B semantic primitive names — exported so callers\n * (tests, hosts) get full IntelliSense and compile-time exhaustiveness checks.\n */\nexport type SemanticPrimitiveName = typeof CONTRACT_B_PRIMITIVES[number];\n\nconst KNOWN_PRIMITIVE_SET: ReadonlySet<string> = new Set(CONTRACT_B_PRIMITIVES);\n\n// ─── Registry ─────────────────────────────────────────────────────────────────\n\n/**\n * Map of semantic primitive name → concrete component for Contract B\n * extensions.\n *\n * Construction is open-ended (callers register primitives one at a time) or\n * eager via {@link SemanticComponentRegistry.fromMap} which forces a complete\n * map up front and aborts if any primitive is missing.\n *\n * @typeParam TComponent - The host's concrete component shape. React hosts pass\n * `ComponentType<P>`; tests pass any tagged object.\n */\nexport class SemanticComponentRegistry<TComponent>\n{\n private readonly entries = new Map<SemanticPrimitiveName, TComponent>();\n\n /**\n * Register a primitive → component binding.\n *\n * Throws if `name` is not in {@link CONTRACT_B_PRIMITIVES} (drift prevention)\n * or if `name` is already registered (silent-override prevention).\n */\n public register(name: SemanticPrimitiveName, component: TComponent): void\n {\n if (!KNOWN_PRIMITIVE_SET.has(name))\n {\n throw new Error(\n `[component-registry] unknown semantic primitive '${name}'. ` +\n `Allowed: ${CONTRACT_B_PRIMITIVES.join(\", \")}.`,\n );\n }\n if (this.entries.has(name))\n {\n throw new Error(\n `[component-registry] primitive '${name}' is already registered. ` +\n `Construct a new registry instead of overriding a binding.`,\n );\n }\n this.entries.set(name, component);\n }\n\n /**\n * Resolve a primitive name to its concrete component. Throws if the name is\n * outside the Contract B vocabulary OR if the registry has no binding —\n * silent fallthrough would let typos render blank components, which is\n * worse than a fast failure for plugin authors.\n */\n public resolve(name: SemanticPrimitiveName): TComponent\n {\n if (!KNOWN_PRIMITIVE_SET.has(name))\n {\n throw new Error(\n `[component-registry] unknown semantic primitive '${name}'. ` +\n `Allowed: ${CONTRACT_B_PRIMITIVES.join(\", \")}.`,\n );\n }\n const found = this.entries.get(name);\n if (found === undefined)\n {\n const registered = this.listRegistered().join(\", \") || \"<none>\";\n throw new Error(\n `[component-registry] primitive '${name}' is not registered. ` +\n `Registered primitives: ${registered}.`,\n );\n }\n return found;\n }\n\n /**\n * Cheap presence probe — never throws. Use this when callers want to\n * fall back to a default component instead of erroring.\n */\n public has(name: SemanticPrimitiveName): boolean\n {\n return this.entries.has(name);\n }\n\n /**\n * Sorted list of registered primitive names. Stable output makes registry\n * diagnostics and snapshot tests deterministic.\n */\n public listRegistered(): SemanticPrimitiveName[]\n {\n return [...this.entries.keys()].sort() as SemanticPrimitiveName[];\n }\n\n /**\n * Build a registry from a complete `Record<SemanticPrimitiveName, T>` map.\n *\n * The `Record` type forces TypeScript to refuse incomplete literals at\n * compile time, AND we re-check at runtime so callers who type-erase the\n * record (e.g., via `as any`) still get a fast failure.\n */\n public static fromMap<T>(map: Record<SemanticPrimitiveName, T>): SemanticComponentRegistry<T>\n {\n const registry = new SemanticComponentRegistry<T>();\n const missing: SemanticPrimitiveName[] = [];\n for (const name of CONTRACT_B_PRIMITIVES)\n {\n const component = (map as Partial<Record<SemanticPrimitiveName, T>>)[name];\n if (component === undefined)\n {\n missing.push(name);\n continue;\n }\n registry.register(name, component);\n }\n if (missing.length > 0)\n {\n throw new Error(\n `[component-registry] fromMap is missing semantic primitive(s): ${missing.join(\", \")}.`,\n );\n }\n return registry;\n }\n}\n","/**\n * `npx mock-host <dir>` — standalone declarative-mock-host dev server.\n *\n * Reads SDUI resources from `<dir>/**\\/*.json` (recursive), wires them into the\n * {@link InMemoryMcpTransport}, and serves a Vite dev page that mounts\n * {@link DeclarativeMockHost} so plugin authors can iterate against the same\n * declarative pipeline the real host uses — without standing up the platform.\n *\n * Resource JSON shape (two accepted forms):\n *\n * 1. Tagged: `{ \"uri\": \"books://catalog\", \"tree\": <SduiNode> }`\n * 2. Untagged: a bare SDUI node. The URI defaults to the file's relative\n * path under the resources directory (with `\\\\` normalised to `/` and the\n * `.json` extension stripped).\n *\n * The CLI's pure resource-loading core is exported as {@link loadResources}\n * for unit testing; the Vite-boot side effect is gated behind {@link run}.\n */\n\nimport { promises as fs } from \"node:fs\";\nimport * as path from \"node:path\";\nimport { KNOWN_PRIMITIVES } from \"@ethisyscore/protocol\";\nimport type { SduiNode } from \"../host/declarative/types\";\nimport {\n WorkerRemoteDomTransport,\n type McpHttpClient,\n type WorkerCtor,\n} from \"../host/worker/transport\";\nimport { CONTRACT_B_PRIMITIVES } from \"../host/worker/component-registry\";\n\n/** Map of MCP resource URI -> SDUI tree, as fed into the mock host. */\nexport type ResourceMap = Record<string, SduiNode>;\n\n/**\n * Map of MCP tool name -> canned result, as fed into the mock host. Each\n * value is returned verbatim when the tool is invoked from the rendered tree\n * (Form.submitTool, Action.tool, Field.lookupTool). Plugin authors who need\n * per-call dynamic behaviour can switch from the CLI's tools.json to a\n * hand-rolled Vite app that mounts DeclarativeMockHost with a typed tools\n * object directly.\n */\nexport type ToolResultMap = Record<string, unknown>;\n\n/**\n * Recursively load every `*.json` file under {@link dir} as a SDUI resource.\n *\n * - Tagged form (`{ uri, tree }`) wins when both fields are present.\n * - Untagged form uses the file's relative POSIX-normalised path (minus\n * extension) as the resource URI.\n *\n * Throws a single aggregated error if any file fails to parse — plugin authors\n * see one failure list instead of giving up at the first bad file.\n */\nexport async function loadResources(dir: string): Promise<ResourceMap>\n{\n const absDir = path.resolve(dir);\n const files = await walkJson(absDir);\n const out: ResourceMap = {};\n const errors: string[] = [];\n\n for (const file of files)\n {\n try\n {\n const raw = await fs.readFile(file, \"utf8\");\n const parsed = JSON.parse(raw) as unknown;\n const { uri, tree } = normaliseEntry(parsed, file, absDir);\n if (out[uri] !== undefined)\n {\n errors.push(`Duplicate resource URI '${uri}' (from ${file})`);\n continue;\n }\n out[uri] = tree;\n }\n catch (e)\n {\n errors.push(`${file}: ${(e as Error).message}`);\n }\n }\n\n if (errors.length > 0)\n {\n throw new Error(\n `Failed to load ${errors.length} mock-host resource(s):\\n - ${errors.join(\"\\n - \")}`,\n );\n }\n\n return out;\n}\n\n/**\n * Read `<dir>/tools.json` (if present) and return a `Record<toolName, canned>`\n * map for the mock transport. Missing file → empty map. Malformed file →\n * throws a single descriptive error.\n *\n * Expected shape: `{ \"<tool-name>\": <result-literal>, ... }`. The literal is\n * returned verbatim from `InMemoryMcpTransport.invokeTool(name, args)`.\n */\nexport async function loadTools(dir: string): Promise<ToolResultMap>\n{\n const toolsPath = path.resolve(dir, \"tools.json\");\n try\n {\n const raw = await fs.readFile(toolsPath, \"utf8\");\n const parsed = JSON.parse(raw) as unknown;\n if (parsed === null || typeof parsed !== \"object\" || Array.isArray(parsed))\n {\n throw new Error(`Expected an object map, got ${typeof parsed}`);\n }\n return parsed as ToolResultMap;\n }\n catch (e)\n {\n const err = e as NodeJS.ErrnoException;\n if (err.code === \"ENOENT\")\n {\n return {};\n }\n throw new Error(`Failed to load tools.json from ${toolsPath}: ${err.message}`);\n }\n}\n\n/**\n * Convert a parsed JSON value into a `{ uri, tree }` pair.\n * Exported for tests; not part of the public API.\n */\nexport function normaliseEntry(\n parsed: unknown,\n absFile: string,\n absDir: string,\n): { uri: string; tree: SduiNode }\n{\n if (parsed === null || typeof parsed !== \"object\")\n {\n throw new Error(\"Resource JSON must be an object\");\n }\n const obj = parsed as Record<string, unknown>;\n if (typeof obj.uri === \"string\" && obj.tree !== undefined)\n {\n return { uri: obj.uri, tree: obj.tree as SduiNode };\n }\n // Untagged form: derive URI from the file's relative path.\n const rel = path.relative(absDir, absFile).replace(/\\\\/g, \"/\");\n const uri = rel.replace(/\\.json$/i, \"\");\n return { uri, tree: parsed as SduiNode };\n}\n\nasync function walkJson(root: string): Promise<string[]>\n{\n const out: string[] = [];\n\n async function recurse(dir: string): Promise<void>\n {\n let entries;\n try\n {\n entries = await fs.readdir(dir, { withFileTypes: true });\n }\n catch (e)\n {\n const err = e as NodeJS.ErrnoException;\n if (err.code === \"ENOENT\")\n {\n throw new Error(`Resources directory not found: ${root}`);\n }\n throw e;\n }\n for (const entry of entries)\n {\n const full = path.join(dir, entry.name);\n if (entry.isDirectory())\n {\n await recurse(full);\n }\n else if (entry.isFile() && entry.name.toLowerCase().endsWith(\".json\"))\n {\n out.push(full);\n }\n }\n }\n\n await recurse(root);\n out.sort();\n return out;\n}\n\n/**\n * Render a self-contained HTML page that mounts {@link DeclarativeMockHost}\n * with a debug registry (every primitive renders to a labelled `<div>`).\n *\n * Returned as a string so the Vite middleware can serve it from memory without\n * touching the consumer's filesystem.\n */\nexport function renderIndexHtml(resourceUris: string[]): string\n{\n const defaultUri = resourceUris[0] ?? \"\";\n return `<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"utf-8\" />\n <title>mock-host (${resourceUris.length} resources)</title>\n <style>\n body { font-family: system-ui, sans-serif; margin: 1rem; }\n header { border-bottom: 1px solid #ccc; padding-bottom: 0.5rem; margin-bottom: 1rem; }\n select { padding: 0.25rem 0.5rem; }\n [data-mock-primitive] { padding: 0.5rem; margin: 0.25rem 0; border: 1px dashed #aaa; }\n [data-mock-primitive]::before { content: attr(data-mock-primitive); font-size: 0.75rem; color: #666; display: block; }\n </style>\n </head>\n <body>\n <header>\n <strong>mock-host</strong>\n &nbsp;|&nbsp;\n <label>Resource: <select id=\"resource-picker\">\n ${resourceUris.map(u => `<option value=\"${escapeHtml(u)}\">${escapeHtml(u)}</option>`).join(\"\")}\n </select></label>\n </header>\n <main id=\"root\"></main>\n <script type=\"module\" src=\"/@mock-host/entry.tsx\"></script>\n <script>\n window.__MOCK_HOST_DEFAULT_URI__ = ${safeJsonForScript(defaultUri)};\n window.__MOCK_HOST_RESOURCE_URIS__ = ${safeJsonForScript(resourceUris)};\n </script>\n </body>\n</html>`;\n}\n\nfunction escapeHtml(s: string): string\n{\n return s.replace(/[&<>\"']/g, c => ({\n \"&\": \"&amp;\",\n \"<\": \"&lt;\",\n \">\": \"&gt;\",\n \"\\\"\": \"&quot;\",\n \"'\": \"&#39;\",\n }[c] as string));\n}\n\n/**\n * Safely serialise a value for embedding inside an inline `<script>` block.\n *\n * Plain `JSON.stringify` leaves `</script>` unescaped, which lets attacker-\n * controlled resource URIs break out of the script context. We replace the\n * dangerous character pairs with their unicode-escaped equivalents — still\n * valid JSON the browser eagerly parses, but inert inside HTML.\n */\nfunction safeJsonForScript(v: unknown): string\n{\n return JSON.stringify(v)\n .replace(/</g, \"\\\\u003c\")\n .replace(/>/g, \"\\\\u003e\")\n .replace(/&/g, \"\\\\u0026\");\n}\n\n/**\n * Build the virtual entry module the browser loads. Embeds the resource map\n * inline so the dev server does not have to serve JSON over a separate\n * endpoint — Vite HMR re-evaluates this module whenever resources change.\n */\nexport function renderEntryModule(\n resources: ResourceMap,\n tools: ToolResultMap = {},\n): string\n{\n const json = JSON.stringify(resources);\n const toolsJson = JSON.stringify(tools);\n // PR #16 review (Medium: mock-host primitive registry hardcoded). Derive the\n // primitive list from the protocol's KNOWN_PRIMITIVES so the mock host can\n // never accept primitives the real protocol rejects, or omit primitives the\n // real protocol added. JSON.stringify ensures the array is emitted as a JS\n // literal in the generated module — identical wire shape to the prior\n // hardcoded list, just sourced from the protocol package.\n const primitives = JSON.stringify([...KNOWN_PRIMITIVES]);\n return `\nimport { createRoot } from \"react-dom/client\";\nimport { createElement, useState } from \"react\";\nimport { DeclarativeMockHost } from \"@ethisyscore/extension-runtime/mock-host\";\n\nconst PRIMITIVES = ${primitives};\n\nconst registry = Object.fromEntries(PRIMITIVES.map(name => [\n name,\n ({ children }) => createElement(\"div\", { \"data-mock-primitive\": name }, children),\n]));\n\nconst resources = ${json};\nconst toolResults = ${toolsJson};\n// Each canned tool result is wrapped in a sync handler. Authors that need\n// per-call behaviour drop tools.json and hand-roll a Vite app that mounts\n// DeclarativeMockHost with a typed tools object directly.\nconst tools = Object.fromEntries(Object.entries(toolResults).map(([name, value]) => [\n name,\n () => value,\n]));\nconst uris = Object.keys(resources);\n\nfunction App() {\n const [uri, setUri] = useState(window.__MOCK_HOST_DEFAULT_URI__ || uris[0] || \"\");\n // Wire the header picker to React state so authors can switch trees live.\n const picker = document.getElementById(\"resource-picker\");\n if (picker && !picker.__wired) {\n picker.__wired = true;\n picker.value = uri;\n picker.addEventListener(\"change\", e => setUri(e.target.value));\n }\n return createElement(DeclarativeMockHost, {\n resources,\n tools,\n registry,\n defaultResourceUri: uri,\n });\n}\n\ncreateRoot(document.getElementById(\"root\")).render(createElement(App));\n`;\n}\n\n// ─── Contract B (Wave 1) CLI augment ──────────────────────────────────────────\n//\n// Wave 0 only supported Contract A (host-rendered) — a directory of SDUI JSON\n// fed into {@link DeclarativeMockHost}. Wave 1 adds Contract B (remote-runtime):\n// plugin authors run `npx mock-host --render-mode remote-runtime ./worker.js`\n// to spawn an in-process Worker via {@link WorkerRemoteDomTransport} against\n// the same Vite dev page workflow. The semantic component registry from E2.S4\n// is referenced by name — each primitive renders to a labelled placeholder so\n// the receiver's substitution behaviour is observable without standing up\n// gogo-ui.\n\n/** Discriminated union of the two render modes the CLI knows about. */\nexport type RenderMode = \"host-rendered\" | \"remote-runtime\";\n\n/**\n * Parsed CLI arguments. `path` is the positional argument — its meaning\n * depends on {@link renderMode}:\n *\n * - `host-rendered`: a directory of SDUI resource JSON files.\n * - `remote-runtime`: a path (or URL) to a built worker bundle.\n */\nexport interface ParsedArgs\n{\n renderMode: RenderMode;\n path: string;\n}\n\nconst RENDER_MODES: ReadonlySet<RenderMode> = new Set<RenderMode>([\n \"host-rendered\",\n \"remote-runtime\",\n]);\n\n/**\n * Parse the CLI's argv-slice. Exported so the test suite can assert flag\n * handling without booting a Vite server.\n *\n * Accepts both `--render-mode <value>` and `--render-mode=<value>`. Defaults\n * to `host-rendered` to preserve Wave 0 behaviour for existing users.\n */\nexport function parseArgs(args: readonly string[]): ParsedArgs\n{\n let renderMode: RenderMode = \"host-rendered\";\n const positional: string[] = [];\n\n for (let i = 0; i < args.length; i++)\n {\n const arg = args[i]!;\n if (arg === \"--render-mode\")\n {\n const next = args[i + 1];\n if (next === undefined)\n {\n throw new Error(\"Usage: mock-host [--render-mode <host-rendered|remote-runtime>] <path>\");\n }\n if (!RENDER_MODES.has(next as RenderMode))\n {\n throw new Error(\n `Invalid --render-mode '${next}'. Allowed: ${[...RENDER_MODES].join(\", \")}.`,\n );\n }\n renderMode = next as RenderMode;\n i++;\n continue;\n }\n if (arg.startsWith(\"--render-mode=\"))\n {\n const value = arg.slice(\"--render-mode=\".length);\n if (!RENDER_MODES.has(value as RenderMode))\n {\n throw new Error(\n `Invalid --render-mode '${value}'. Allowed: ${[...RENDER_MODES].join(\", \")}.`,\n );\n }\n renderMode = value as RenderMode;\n continue;\n }\n positional.push(arg);\n }\n\n if (positional.length === 0)\n {\n throw new Error(\"Usage: mock-host [--render-mode <host-rendered|remote-runtime>] <path>\");\n }\n\n return { renderMode, path: positional[0]! };\n}\n\n/**\n * Render the HTML shell for the Contract B mock host. The page hosts a\n * receiver that mounts the Remote DOM tree produced by the worker — each\n * Contract B primitive renders to a labelled placeholder.\n */\nexport function renderWorkerBundleIndexHtml(bundlePath: string): string\n{\n const safePath = escapeHtml(bundlePath);\n return `<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"utf-8\" />\n <title>mock-host (remote-runtime: ${safePath})</title>\n <style>\n body { font-family: system-ui, sans-serif; margin: 1rem; }\n header { border-bottom: 1px solid #ccc; padding-bottom: 0.5rem; margin-bottom: 1rem; }\n [data-mock-primitive] { padding: 0.5rem; margin: 0.25rem 0; border: 1px dashed #aaa; }\n [data-mock-primitive]::before { content: attr(data-mock-primitive); font-size: 0.75rem; color: #666; display: block; }\n </style>\n </head>\n <body>\n <header><strong>mock-host</strong> &nbsp;|&nbsp; <em>remote-runtime: ${safePath}</em></header>\n <main id=\"root\"></main>\n <script type=\"module\" src=\"/@mock-host/worker-bundle-entry.tsx\"></script>\n </body>\n</html>`;\n}\n\n/**\n * Build the inline entry module the browser loads for Contract B.\n *\n * The module:\n * 1. Constructs a {@link WorkerRemoteDomTransport} pointing at the bundle.\n * 2. Registers a placeholder for every Contract B semantic primitive — the\n * test-injectable seam from E2.S4. Production hosts would substitute\n * gogo-ui components here.\n * 3. Wires the receiver onto the host's `<main id=\"root\">` slot.\n *\n * In-page wiring is deliberately minimal — the goal is observability, not a\n * production receiver implementation. Authors iterate against the same\n * surface their plugins will mount into when the platform host is finished.\n */\nexport function renderWorkerBundleEntryModule(bundleUrl: string): string\n{\n const safePrimitives = JSON.stringify([...CONTRACT_B_PRIMITIVES]);\n const safeUrl = JSON.stringify(bundleUrl);\n return `\nimport { createRoot } from \"react-dom/client\";\nimport { createElement } from \"react\";\nimport {\n WorkerRemoteDomTransport,\n SemanticComponentRegistry,\n} from \"@ethisyscore/extension-runtime/host\";\n\nconst PRIMITIVES = ${safePrimitives};\nconst bundleUrl = ${safeUrl};\n\n// Build the test-injectable component map — every Contract B primitive\n// renders to a labelled placeholder so plugin authors can see what their\n// worker is emitting before plugging in gogo-ui.\nconst componentMap = Object.fromEntries(PRIMITIVES.map(name => [\n name,\n ({ children }) => createElement(\"div\", { \"data-mock-primitive\": name }, children),\n]));\nconst registry = SemanticComponentRegistry.fromMap(componentMap);\n\n// Mock-host MCP client: no platform, so every tool/resource call resolves\n// to a friendly placeholder. Authors who need real MCP traffic should run\n// against the full platform host.\nconst mcpClient = {\n fetch: async (req) => ({\n ok: true,\n data: { mockHost: true, kind: req.kind, name: req.name ?? req.uri },\n }),\n};\n\n// The capability token never crosses the worker boundary — supply a marker\n// here so the host-side fetcher can still attach an Authorization header.\nconst transport = new WorkerRemoteDomTransport({\n bootstrapUrl: bundleUrl,\n capabilityToken: async () => \"mock-host-capability-token\",\n mcpClient,\n});\n\nconst root = createRoot(document.getElementById(\"root\"));\ntransport.onRemoteDom(() => {\n // The full Remote DOM receiver belongs to a separate concern (E3.S2's\n // WorkerSurfaceMount). For mock-host purposes the registry is the\n // observable surface — log the receiver payload for now.\n root.render(createElement(\"div\", { \"data-mock-host-primitives\": PRIMITIVES.join(\",\") }));\n});\n\n// Expose the transport + registry on window so authors can poke at them from\n// devtools (e.g., to terminate the worker between iteration cycles).\nwindow.__MOCK_HOST_TRANSPORT__ = transport;\nwindow.__MOCK_HOST_REGISTRY__ = registry;\n`;\n}\n\n/**\n * Construction options for {@link bootContractBHost}.\n */\nexport interface BootContractBHostOptions\n{\n /**\n * Path or canonical URL to the plugin's worker bundle. The mock host\n * serves it from the configured Vite root — production hosts always use\n * a path-pinned host-origin URL.\n */\n bundleUrl: string;\n /**\n * Injectable Worker constructor. Tests pass a fake; the production CLI\n * path uses the global `Worker`. The transport itself defaults to\n * `globalThis.Worker` when omitted — but the mock-host CLI passes through\n * the caller's choice so test seams compose cleanly.\n */\n workerCtor?: WorkerCtor;\n /**\n * Optional capability token provider. Defaults to a marker string so\n * authors don't need to wire token plumbing into local-dev.\n */\n capabilityToken?: () => Promise<string>;\n /**\n * Optional MCP HTTP client. Defaults to a stub that echoes the request\n * shape — authors who need real MCP traffic compose the platform host.\n */\n mcpClient?: McpHttpClient;\n}\n\nconst defaultMockMcpClient: McpHttpClient = {\n fetch: async (req) =>\n {\n const echo = req.kind === \"invokeTool\" ? { tool: req.name, args: req.args } : { resource: req.uri };\n return { ok: true, data: { mockHost: true, ...echo } };\n },\n};\n\n/**\n * Spawn the in-process worker that drives the Contract B mock host.\n *\n * Returns the constructed transport so callers (production CLI: the dev page;\n * tests: assertions) can observe spawn shape, dispose the worker on shutdown,\n * and inspect coalescing behaviour. Reuses\n * {@link WorkerRemoteDomTransport} so the capability-token isolation rules\n * documented in E2.S3 apply without re-implementation.\n */\nexport function bootContractBHost(options: BootContractBHostOptions): WorkerRemoteDomTransport\n{\n return new WorkerRemoteDomTransport({\n bootstrapUrl: options.bundleUrl,\n capabilityToken: options.capabilityToken ?? (async () => \"mock-host-capability-token\"),\n mcpClient: options.mcpClient ?? defaultMockMcpClient,\n workerCtor: options.workerCtor,\n });\n}\n\n/**\n * Wave 1 Contract B dev server. Boots Vite, serves the virtual index page that\n * runs {@link renderWorkerBundleEntryModule} (which spawns the worker), and\n * exposes the bundle on a host-origin URL so the worker handshake can succeed.\n */\nasync function runContractBHost(bundlePathOrUrl: string): Promise<{ url: string; close: () => Promise<void> }>\n{\n // Lazy import so the pure exports stay decoupled from Vite at type-time.\n /* eslint-disable @typescript-eslint/no-explicit-any */\n let viteMod: any;\n try\n {\n viteMod = await import(\"vite\" as any);\n }\n catch\n {\n throw new Error(\n \"vite is required to run `mock-host`. Install with: npm install --save-dev vite\",\n );\n }\n /* eslint-enable @typescript-eslint/no-explicit-any */\n\n // Two flavours of bundle reference are accepted: an absolute URL (rare —\n // useful for testing a deployed bundle) and a filesystem path that the\n // dev server exposes as a host-origin URL.\n const isUrl = /^https?:\\/\\//i.test(bundlePathOrUrl);\n const absBundle = isUrl ? bundlePathOrUrl : path.resolve(bundlePathOrUrl);\n\n if (!isUrl)\n {\n try\n {\n await fs.access(absBundle);\n }\n catch\n {\n throw new Error(`mock-host: worker bundle not found at ${absBundle}`);\n }\n }\n\n // The dev page references the bundle via a stable virtual path. The Vite\n // middleware serves the file's bytes directly so the worker spawn URL is\n // host-origin (matching the production CSP path-pinning shape).\n const bundleServePath = \"/@mock-host/worker-bundle.js\";\n const indexHtml = renderWorkerBundleIndexHtml(isUrl ? bundlePathOrUrl : bundleServePath);\n const entryModule = renderWorkerBundleEntryModule(isUrl ? bundlePathOrUrl : bundleServePath);\n\n const server = await viteMod.createServer({\n root: process.cwd(),\n configFile: false,\n server: { port: 0, host: \"127.0.0.1\" },\n plugins: [\n {\n name: \"mock-host-virtual-worker\",\n resolveId(id: string)\n {\n if (id === \"/@mock-host/worker-bundle-entry.tsx\")\n {\n return \"\\0mock-host:worker-bundle-entry\";\n }\n return null;\n },\n load(id: string)\n {\n if (id === \"\\0mock-host:worker-bundle-entry\")\n {\n return entryModule;\n }\n return null;\n },\n /* eslint-disable @typescript-eslint/no-explicit-any */\n configureServer(devServer: any)\n {\n devServer.middlewares.use(async (req: any, res: any, next: () => void) =>\n {\n if (req.url === \"/\" || req.url === \"/index.html\")\n {\n res.setHeader(\"Content-Type\", \"text/html\");\n res.end(indexHtml);\n return;\n }\n if (!isUrl && req.url === bundleServePath)\n {\n try\n {\n const bytes = await fs.readFile(absBundle);\n res.setHeader(\"Content-Type\", \"text/javascript; charset=utf-8\");\n res.end(bytes);\n }\n catch (e)\n {\n res.statusCode = 500;\n res.end(`mock-host: failed to read worker bundle: ${(e as Error).message}`);\n }\n return;\n }\n next();\n });\n },\n /* eslint-enable @typescript-eslint/no-explicit-any */\n },\n ],\n });\n\n await server.listen();\n const addr = server.httpServer?.address();\n let port = 5173;\n if (addr && typeof addr === \"object\")\n {\n port = addr.port;\n }\n const url = `http://127.0.0.1:${port}/`;\n process.stdout.write(`mock-host listening on ${url} (remote-runtime: ${absBundle})\\n`);\n\n return { url, close: () => server.close() };\n}\n\n/**\n * Boot the Vite dev server with an in-memory entry + virtual page.\n *\n * Side-effectful: imports `vite` lazily so the pure `loadResources` export\n * stays usable from environments that lack a Vite install (e.g., the unit\n * tests in this package).\n *\n * Dispatches on `--render-mode`:\n * - `host-rendered` (default): Wave 0 declarative-mock-host behaviour.\n * - `remote-runtime`: Wave 1 Contract B — spawns a worker pointing at the\n * supplied bundle path and serves the placeholder dev page.\n */\nexport async function run(args: string[]): Promise<{ url: string; close: () => Promise<void> }>\n{\n const parsed = parseArgs(args);\n\n if (parsed.renderMode === \"remote-runtime\")\n {\n return runContractBHost(parsed.path);\n }\n\n const dir = parsed.path;\n const resources = await loadResources(dir);\n const tools = await loadTools(dir);\n const uris = Object.keys(resources).sort();\n if (uris.length === 0)\n {\n // Allow boot anyway — authors might want to scaffold-then-add resources\n // with HMR-watched edits.\n process.stderr.write(`mock-host: no resources found in ${dir} (HMR will pick up new files)\\n`);\n }\n\n // Lazy import so the pure exports stay decoupled from Vite at type-time.\n // Vite is declared as an OPTIONAL peer dependency — type as `any` here so\n // building the package doesn't require Vite's type surface to be present.\n /* eslint-disable @typescript-eslint/no-explicit-any */\n let viteMod: any;\n try\n {\n viteMod = await import(\"vite\" as any);\n }\n catch\n {\n throw new Error(\n \"vite is required to run `mock-host`. Install with: npm install --save-dev vite\",\n );\n }\n /* eslint-enable @typescript-eslint/no-explicit-any */\n\n const indexHtml = renderIndexHtml(uris);\n const entryModule = renderEntryModule(resources, tools);\n const absDir = path.resolve(dir);\n\n const server = await viteMod.createServer({\n root: process.cwd(),\n configFile: false,\n server: { port: 0, host: \"127.0.0.1\" },\n plugins: [\n {\n name: \"mock-host-virtual\",\n resolveId(id: string)\n {\n if (id === \"/@mock-host/entry.tsx\")\n {\n return \"\\0mock-host:entry\";\n }\n return null;\n },\n load(id: string)\n {\n if (id === \"\\0mock-host:entry\")\n {\n return entryModule;\n }\n return null;\n },\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n configureServer(devServer: any)\n {\n devServer.watcher.add(absDir);\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n devServer.middlewares.use((req: any, res: any, next: () => void) =>\n {\n if (req.url === \"/\" || req.url === \"/index.html\")\n {\n res.setHeader(\"Content-Type\", \"text/html\");\n res.end(indexHtml);\n return;\n }\n next();\n });\n },\n },\n ],\n });\n\n await server.listen();\n const addr = server.httpServer?.address();\n let port = 5173;\n if (addr && typeof addr === \"object\")\n {\n port = addr.port;\n }\n const url = `http://127.0.0.1:${port}/`;\n process.stdout.write(`mock-host listening on ${url} (resources: ${uris.length})\\n`);\n\n return { url, close: () => server.close() };\n}\n\n// No top-level bin guard here: the `mock-host` bin entry is a thin\n// CJS wrapper (`bin/mock-host.cjs`, hand-written, kept out of tsup) that\n// requires the compiled `cli.cjs` and calls `run(process.argv.slice(2))`.\n// That keeps this module side-effect free so unit tests can import the pure\n// `loadResources` / `normaliseEntry` / `renderEntryModule` exports without\n// booting Vite.\n"]}
1
+ {"version":3,"sources":["../../src/host/worker/offscreen.ts","../../src/host/worker/bridge-envelopes.ts","../../src/host/worker/transport.ts","../../src/host/worker/component-registry.ts","../../src/mock-host/cli.ts"],"names":["fs"],"mappings":"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","file":"cli.js","sourcesContent":["/**\n * OffscreenCanvas transfer helpers + pointer/keyboard coalescing for\n * Contract B (worker remote-runtime) extensions.\n *\n * **Architecture.** Hosts that expose a {@link import(\"./component-registry\").SemanticComponentRegistry}'s\n * `CanvasSurface` / `WebGLSurface` primitive create a host-side `<canvas>`,\n * transfer control to the worker via {@link createOffscreenCanvasTransfer}, and\n * the plugin (running inside the worker realm) calls `offscreen.getContext()`\n * to render. The `<canvas>` is owned by the host DOM tree (so layout, theming,\n * and accessibility properties stay on the host side) but every pixel is\n * produced inside the worker — no per-frame `postMessage` cost, no main-thread\n * blocking.\n *\n * **Pointer / keyboard delivery.** Input events still originate on the host\n * `<canvas>`. To avoid spraying the worker port with per-event traffic the\n * host should wrap pointer-move / wheel / scroll callbacks in\n * {@link createInputEventCoalescer}, matching the trailing-edge coalescer\n * already documented in {@link import(\"./transport\").WorkerRemoteDomTransport}\n * (default 16ms ≈ one rAF). Keyboard events MUST use `discrete: true` —\n * coalescing them would drop characters.\n *\n * **Security.** No capability token, no MCP traffic, and no host-only globals\n * cross via the offscreen transfer. The transfer envelope is plain JSON with a\n * single `OffscreenCanvas` handle in the transfer list — the worker side\n * receives an opaque drawing surface and nothing else.\n */\n\n// ─── Public constants ────────────────────────────────────────────────────────\n\n/**\n * Default trailing-edge coalesce window for pointer / wheel / scroll events,\n * in milliseconds. 16ms ≈ one animation frame at 60Hz. Matches the default\n * documented in {@link import(\"./transport\").WorkerRemoteDomTransport}.\n */\nexport const DEFAULT_OFFSCREEN_COALESCE_MS = 16;\n\n// ─── Transfer envelope ───────────────────────────────────────────────────────\n\n/**\n * Host → worker envelope shape for an `OffscreenCanvas` transfer. Stable wire\n * contract — the worker side decodes by `type` and uses `surfaceId` to bind\n * the offscreen to the right host slot when a plugin renders multiple\n * canvases concurrently.\n */\nexport interface OffscreenTransferMessage\n{\n type: \"ethisys:offscreen:transfer\";\n surfaceId: string;\n width: number;\n height: number;\n offscreen: OffscreenCanvas;\n}\n\n/**\n * Construction options for {@link createOffscreenCanvasTransfer}.\n */\nexport interface OffscreenCanvasTransferOptions\n{\n /**\n * The host-owned `<canvas>` element. Its `width` / `height` are captured\n * before transfer so the worker sees the intended pixel dimensions even if\n * the host element resizes later (host should send a separate resize\n * message in that case).\n */\n canvas: HTMLCanvasElement;\n\n /**\n * Stable identifier the worker uses to route the offscreen to the matching\n * surface slot. Typically the SDUI node id of the `CanvasSurface` /\n * `WebGLSurface` primitive that the worker will draw into.\n */\n surfaceId: string;\n\n /**\n * Host-side postMessage function — typically the\n * {@link import(\"./transport\").WorkerRemoteDomTransport}'s port-side\n * `postMessage`. Tests inject a spy.\n */\n postMessage(message: OffscreenTransferMessage, transfer: Transferable[]): void;\n}\n\n/**\n * Successful transfer result. The caller retains a reference to the\n * `OffscreenCanvas` only for observability (e.g., to wire to the worker's\n * reply handshake) — direct rendering from the host side is forbidden because\n * control has already been transferred and any host-side draw call would\n * throw.\n */\nexport interface OffscreenCanvasTransferResult\n{\n offscreen: OffscreenCanvas;\n}\n\n/**\n * Module-scoped WeakSet tracking canvases this SDK has already transferred.\n *\n * Stored OUTSIDE the DOM node deliberately:\n *\n * - Writing an SDK expando (e.g. `__ethisysOffscreenTransferred`) onto a host-\n * owned `<canvas>` mutates state the host doesn't own. A WeakSet keeps the\n * bookkeeping on the SDK side and lets the canvas object stay pristine.\n * - WeakSet entries are collected when the canvas is GC'd, so the guard does\n * not leak memory for plugins that mount and unmount many surfaces.\n * - WeakSet membership is identity-based, so React/concurrent remount paths\n * that re-use the SAME node still see \"already transferred\"; a fresh DOM\n * node (always allocated by React when the key changes) is a fresh entry.\n */\nconst transferredCanvases: WeakSet<HTMLCanvasElement> = new WeakSet<HTMLCanvasElement>();\n\n/**\n * Transfer control of a host-owned `<canvas>` to the worker.\n *\n * Throws if:\n * - the environment lacks `HTMLCanvasElement.prototype.transferControlToOffscreen`\n * (the helper does NOT silently fall back — Contract B clients must require\n * the API and surface a clear error in unsupported browsers);\n * - the same canvas has already had control transferred (idempotency is the\n * caller's responsibility — re-transferring would throw in the browser\n * anyway, but we raise a clearer, diagnosable message ahead of that).\n */\nexport function createOffscreenCanvasTransfer(\n options: OffscreenCanvasTransferOptions,\n): OffscreenCanvasTransferResult\n{\n const { canvas, surfaceId, postMessage } = options;\n const proto = HTMLCanvasElement.prototype as unknown as {\n transferControlToOffscreen?: () => OffscreenCanvas;\n };\n if (typeof proto.transferControlToOffscreen !== \"function\")\n {\n throw new Error(\n \"[offscreen-canvas] OffscreenCanvas is not supported in this environment. \" +\n \"Contract B canvas surfaces require a browser with HTMLCanvasElement.transferControlToOffscreen.\",\n );\n }\n if (transferredCanvases.has(canvas))\n {\n throw new Error(\n `[offscreen-canvas] canvas already transferred (surfaceId='${surfaceId}'). ` +\n \"Create a fresh <canvas> for each surface instead of re-transferring.\",\n );\n }\n\n const offscreen = canvas.transferControlToOffscreen();\n transferredCanvases.add(canvas);\n\n const envelope: OffscreenTransferMessage = {\n type: \"ethisys:offscreen:transfer\",\n surfaceId,\n width: canvas.width,\n height: canvas.height,\n offscreen,\n };\n postMessage(envelope, [offscreen]);\n\n return { offscreen };\n}\n\n// ─── Coalescing ──────────────────────────────────────────────────────────────\n\n/**\n * Construction options for {@link createInputEventCoalescer}.\n */\nexport interface InputEventCoalescerOptions\n{\n /**\n * Coalesce window in milliseconds. Ignored when {@link discrete} is true.\n * Defaults to {@link DEFAULT_OFFSCREEN_COALESCE_MS} (16ms — one rAF).\n */\n coalesceMs?: number;\n\n /**\n * When `true`, forward every payload synchronously without coalescing.\n * Use this for keyboard events — coalescing would drop characters by\n * collapsing multiple key strokes into one trailing delivery.\n */\n discrete?: boolean;\n}\n\n/**\n * Build a trailing-edge input-event coalescer. The pattern matches\n * {@link import(\"./transport\").WorkerRemoteDomTransport.createCoalescer} —\n * factored out here so OffscreenCanvas callers don't need to construct a full\n * transport just to coalesce pointer-move events.\n *\n * Trailing-edge semantics: every burst within a coalesce window collapses to\n * a single delivery carrying the **last** payload seen in the window. A new\n * payload arriving after a window has flushed starts a fresh window.\n */\nexport function createInputEventCoalescer<T>(\n sink: (payload: T) => void,\n options: InputEventCoalescerOptions = {},\n): (payload: T) => void\n{\n if (options.discrete === true)\n {\n // Discrete mode: pass through unchanged. Keyboard input belongs here.\n return (payload: T) =>\n {\n sink(payload);\n };\n }\n const coalesceMs = options.coalesceMs ?? DEFAULT_OFFSCREEN_COALESCE_MS;\n let pending: T | undefined;\n let pendingSet = false;\n let timer: ReturnType<typeof setTimeout> | undefined;\n\n const flush = (): void =>\n {\n timer = undefined;\n if (!pendingSet)\n {\n return;\n }\n const value = pending as T;\n pending = undefined;\n pendingSet = false;\n sink(value);\n };\n\n return (payload: T) =>\n {\n pending = payload;\n pendingSet = true;\n if (timer === undefined)\n {\n timer = setTimeout(flush, coalesceMs);\n }\n };\n}\n","/**\n * Wire-shape constants and type guards for host→plugin bridge push messages\n * (WI 4858 sub-plan 2). These types ride the existing MessagePort channel\n * alongside the `ethisys:mcp:*` and `ethisys:remotedom` envelopes — the\n * `type` discriminant keeps them fully separate at the dispatch site.\n *\n * The host posts bridge pushes through `WorkerRemoteDomTransport.hostPort`;\n * the plugin side reads them in `createPortBridgeClient` (plugin-ui package).\n * In-realm (Tier T) plugins use `InMemoryBridgeTransport` which calls the\n * subscriber callbacks directly — no serialisation needed.\n */\n\n// ── Type constants ───────────────────────────────────────────────────────────\n\nexport const BRIDGE_PUSH_THEME = \"ethisys:bridge:theme\" as const;\nexport const BRIDGE_PUSH_LOCALE = \"ethisys:bridge:locale\" as const;\nexport const BRIDGE_PUSH_DENSITY = \"ethisys:bridge:density\" as const;\nexport const BRIDGE_PUSH_A11Y = \"ethisys:bridge:a11y\" as const;\nexport const BRIDGE_NAV_PUSH = \"ethisys:bridge:nav\" as const;\nexport const BRIDGE_CHROME_REQUEST = \"ethisys:bridge:chrome:request\" as const;\nexport const BRIDGE_CHROME_RESPONSE = \"ethisys:bridge:chrome:response\" as const;\nexport const BRIDGE_SESSION_TOKEN_PUSH = \"ethisys:bridge:token\" as const;\nexport const BRIDGE_LIFECYCLE_EVENT = \"ethisys:bridge:lifecycle\" as const;\nexport const BRIDGE_A11Y_ANNOUNCE = \"ethisys:bridge:a11y:announce\" as const;\n\n// ── Envelope shapes ──────────────────────────────────────────────────────────\n\n/** Theme push: host → plugin whenever the host theme changes. */\nexport interface BridgePushThemeEnvelope {\n readonly type: typeof BRIDGE_PUSH_THEME;\n /** \"light\" | \"dark\" | \"high-contrast\" */\n readonly mode: string;\n /** Flat design-token map (CSS-variable-name → value). May be empty. */\n readonly tokens: Readonly<Record<string, string>>;\n}\n\n/** Locale push: host → plugin whenever the active locale changes. */\nexport interface BridgePushLocaleEnvelope {\n readonly type: typeof BRIDGE_PUSH_LOCALE;\n /** BCP 47 tag, e.g. \"en-GB\". */\n readonly locale: string;\n /** \"ltr\" | \"rtl\" */\n readonly dir: \"ltr\" | \"rtl\";\n}\n\n/** Density push: host → plugin when the UI density preference changes. */\nexport interface BridgePushDensityEnvelope {\n readonly type: typeof BRIDGE_PUSH_DENSITY;\n /** \"comfortable\" | \"compact\" */\n readonly density: string;\n}\n\n/** A11y push: host → plugin when accessibility prefs change. */\nexport interface BridgePushA11yEnvelope {\n readonly type: typeof BRIDGE_PUSH_A11Y;\n readonly reducedMotion: boolean;\n readonly highContrast: boolean;\n}\n\n/** Nav push: host → plugin when the SPA location changes. */\nexport interface BridgeNavPushEnvelope {\n readonly type: typeof BRIDGE_NAV_PUSH;\n /** Current SPA path (pathname + search). */\n readonly path: string;\n /** `window.history.length` at the time of push. */\n readonly historyLength: number;\n}\n\n/** Plugin → host: request a chrome action (e.g. toast, modal). */\nexport interface BridgeChromeRequestEnvelope {\n readonly type: typeof BRIDGE_CHROME_REQUEST;\n /** Correlation id — matched against the response. */\n readonly id: string;\n /** \"toast\" | \"confirm\" | \"openUrl\" */\n readonly action: string;\n readonly payload: Readonly<Record<string, unknown>>;\n}\n\n/** Host → plugin: response to a chrome request. */\nexport interface BridgeChromeResponseEnvelope {\n readonly type: typeof BRIDGE_CHROME_RESPONSE;\n readonly id: string;\n readonly ok: boolean;\n readonly result?: unknown;\n readonly error?: string;\n}\n\n/** Host → plugin: push a short-lived frontend-session token. */\nexport interface BridgeSessionTokenPushEnvelope {\n readonly type: typeof BRIDGE_SESSION_TOKEN_PUSH;\n /** Opaque JWT string — audience-restricted to the plugin's own backend. */\n readonly token: string;\n /** Absolute epoch-ms at which the token expires. Refresh fires 30 s before. */\n readonly expiresAtMs: number;\n}\n\n/** Host ↔ plugin lifecycle event. */\nexport type BridgeLifecyclePhase = \"load\" | \"ready\" | \"error\" | \"unload\";\n\nexport interface BridgeLifecycleEventEnvelope {\n readonly type: typeof BRIDGE_LIFECYCLE_EVENT;\n readonly phase: BridgeLifecyclePhase;\n /** Present when phase === \"error\". */\n readonly message?: string;\n}\n\n/** Plugin → host: a11y live-region announcement request. */\nexport interface BridgeA11yAnnounceEnvelope {\n readonly type: typeof BRIDGE_A11Y_ANNOUNCE;\n readonly message: string;\n /** \"polite\" | \"assertive\" */\n readonly politeness: \"polite\" | \"assertive\";\n}\n\n// ── Type guards ──────────────────────────────────────────────────────────────\n\nfunction isObj(v: unknown): v is Record<string, unknown> {\n return v !== null && typeof v === \"object\";\n}\n\nexport function isBridgePushTheme(v: unknown): v is BridgePushThemeEnvelope {\n return isObj(v) && v[\"type\"] === BRIDGE_PUSH_THEME && typeof v[\"mode\"] === \"string\";\n}\n\nexport function isBridgePushLocale(v: unknown): v is BridgePushLocaleEnvelope {\n return isObj(v) && v[\"type\"] === BRIDGE_PUSH_LOCALE && typeof v[\"locale\"] === \"string\";\n}\n\nexport function isBridgePushDensity(v: unknown): v is BridgePushDensityEnvelope {\n return isObj(v) && v[\"type\"] === BRIDGE_PUSH_DENSITY && typeof v[\"density\"] === \"string\";\n}\n\nexport function isBridgePushA11y(v: unknown): v is BridgePushA11yEnvelope {\n return isObj(v) && v[\"type\"] === BRIDGE_PUSH_A11Y\n && typeof v[\"reducedMotion\"] === \"boolean\"\n && typeof v[\"highContrast\"] === \"boolean\";\n}\n\nexport function isBridgeNavPush(v: unknown): v is BridgeNavPushEnvelope {\n return isObj(v) && v[\"type\"] === BRIDGE_NAV_PUSH && typeof v[\"path\"] === \"string\";\n}\n\nexport function isBridgeChromeRequest(v: unknown): v is BridgeChromeRequestEnvelope {\n return isObj(v) && v[\"type\"] === BRIDGE_CHROME_REQUEST && typeof v[\"id\"] === \"string\";\n}\n\nexport function isBridgeChromeResponse(v: unknown): v is BridgeChromeResponseEnvelope {\n return isObj(v) && v[\"type\"] === BRIDGE_CHROME_RESPONSE && typeof v[\"id\"] === \"string\";\n}\n\nexport function isBridgeSessionTokenPush(v: unknown): v is BridgeSessionTokenPushEnvelope {\n return isObj(v) && v[\"type\"] === BRIDGE_SESSION_TOKEN_PUSH\n && typeof v[\"token\"] === \"string\"\n && typeof v[\"expiresAtMs\"] === \"number\";\n}\n\nexport function isBridgeLifecycleEvent(v: unknown): v is BridgeLifecycleEventEnvelope {\n return isObj(v) && v[\"type\"] === BRIDGE_LIFECYCLE_EVENT && typeof v[\"phase\"] === \"string\";\n}\n\nexport function isBridgeA11yAnnounce(v: unknown): v is BridgeA11yAnnounceEnvelope {\n return isObj(v) && v[\"type\"] === BRIDGE_A11Y_ANNOUNCE && typeof v[\"message\"] === \"string\";\n}\n","/**\n * Worker-side host transport for `renderMode: remote-runtime` extensions\n * (Contract B). Owns the lifecycle of:\n *\n * 1. A path-pinned module `Worker` spawned from a host-issued bootstrap URL.\n * 2. A `MessagePort`-based RPC channel (handed to the worker on spawn).\n * 3. A Shopify Remote DOM root receiver — wired here so the worker's UI\n * tree mounts into a host React tree.\n * 4. Event coalescing (default ≤16ms, one rAF) for high-frequency input\n * events before they cross the port.\n * 5. MCP tool/resource calls forwarded through the port — but **the\n * capability token never crosses the boundary**. The token is bound\n * to the worker scope on the HOST side: the host transport intercepts\n * the plugin's MCP request, attaches the token to the outbound HTTP\n * call, and forwards ONLY the response payload back through the port.\n *\n * This module is intentionally framework-thin: callers wire `mount(root)`\n * onto a React tree (typically inside a `<WorkerSurfaceMount>`), and the\n * Remote DOM receiver is responsible for translating worker-side\n * RemoteRoot mutations into host-side component renders.\n */\n\nimport { createOffscreenCanvasTransfer } from \"./offscreen\";\nimport {\n BRIDGE_PUSH_THEME,\n BRIDGE_PUSH_LOCALE,\n BRIDGE_PUSH_DENSITY,\n BRIDGE_PUSH_A11Y,\n BRIDGE_NAV_PUSH,\n BRIDGE_CHROME_REQUEST,\n BRIDGE_A11Y_ANNOUNCE,\n BRIDGE_LIFECYCLE_EVENT,\n type BridgePushThemeEnvelope,\n type BridgePushLocaleEnvelope,\n type BridgePushDensityEnvelope,\n type BridgePushA11yEnvelope,\n type BridgeNavPushEnvelope,\n} from \"./bridge-envelopes\";\n\n// ─── Public types ─────────────────────────────────────────────────────────────\n\n/**\n * Structural subset of the DOM `Worker` interface that the transport needs.\n * Tests inject a fake; production callers pass the global `Worker`.\n */\nexport interface WorkerLike\n{\n onmessage: ((ev: MessageEvent) => void) | null;\n onerror: ((ev: ErrorEvent) => void) | null;\n onmessageerror: ((ev: MessageEvent) => void) | null;\n postMessage(message: unknown, transfer?: Transferable[]): void;\n terminate(): void;\n}\n\n/**\n * Constructor signature compatible with the DOM `Worker` constructor. The\n * `workerCtor` option exists exclusively so tests can swap in a fake — in\n * production the caller passes the global `Worker`.\n */\nexport type WorkerCtor = new (url: string | URL, options?: WorkerOptions) => WorkerLike;\n\n/**\n * Discriminated union of MCP fetch shapes the host transport issues. The\n * `capabilityToken` is attached on the host side ONLY — it is never present\n * in any message that crosses the worker port.\n */\nexport type McpHttpRequest =\n | {\n kind: \"invokeTool\";\n name: string;\n args: unknown;\n capabilityToken: string;\n signal?: AbortSignal;\n }\n | {\n kind: \"getResource\";\n uri: string;\n capabilityToken: string;\n signal?: AbortSignal;\n };\n\n/**\n * Host-side HTTP client for MCP calls. Implementations are responsible for\n * carrying the supplied `capabilityToken` on the outbound request (typically\n * via `Authorization: Bearer`).\n */\nexport interface McpHttpClient\n{\n fetch(request: McpHttpRequest): Promise<{ ok: boolean; data?: unknown; error?: string }>;\n}\n\n/**\n * Construction options for {@link WorkerRemoteDomTransport}.\n */\nexport interface WorkerRemoteDomTransportOptions\n{\n /**\n * Canonical host-origin URL for the worker bootstrap script. This MUST\n * be supplied by the host — never built from user input or extension\n * manifest values — so the worker spawn is path-pinned.\n */\n bootstrapUrl: string;\n\n /**\n * Mints (or fetches a cached) capability token. The host transport\n * resolves this for each outbound MCP HTTP call. The token NEVER leaves\n * host scope — plugin code (the worker side) cannot read it.\n */\n capabilityToken: () => Promise<string>;\n\n /**\n * Host-side MCP HTTP client. The transport bridges port-side plugin\n * MCP requests to this client and returns only the response payload\n * back through the port.\n */\n mcpClient: McpHttpClient;\n\n /**\n * Injectable Worker constructor (tests use a fake). Defaults to the\n * global `Worker`.\n */\n workerCtor?: WorkerCtor;\n\n /**\n * Coalescing window in milliseconds for high-frequency events. Defaults\n * to 16ms (~one animation frame). Trailing-edge: the most recent payload\n * within the window is delivered after the window elapses.\n */\n coalesceMs?: number;\n\n /**\n * Maximum number of MCP requests (combined `invokeTool` + `getResource`)\n * the transport will forward to {@link mcpClient} concurrently. Defaults\n * to 8. Requests above the cap are rejected with a deterministic error\n * reply over the port — the worker MUST handle that response shape.\n *\n * Bounded concurrency is a back-pressure boundary, not a quota: it stops\n * a compromised or buggy worker from saturating the host's HTTP / token\n * pool. Production hosts may want to lower this in multi-tenant pools\n * where many extensions share one host process.\n */\n maxConcurrentMcpRequests?: number;\n}\n\n/**\n * Wire shape of a host-sourced input event forwarded to the worker over the\n * port. Pointer / wheel / keyboard payloads share a single envelope type so\n * the worker has one inbound dispatch site. The transport is structurally\n * agnostic to the payload — it forwards verbatim.\n *\n * `kind` discriminates the event family. Coordinates are CSS pixels relative\n * to the host canvas; coalescing happens host-side before the call to\n * {@link WorkerRemoteDomTransport.postInputEvent}.\n */\nexport type InputEventPayload =\n | {\n kind: \"pointermove\" | \"pointerdown\" | \"pointerup\" | \"pointercancel\";\n surfaceId: string;\n x: number;\n y: number;\n buttons: number;\n pointerType: string;\n }\n | {\n kind: \"wheel\";\n surfaceId: string;\n deltaX: number;\n deltaY: number;\n deltaMode: number;\n }\n | {\n kind: \"keydown\" | \"keyup\";\n surfaceId: string;\n key: string;\n code: string;\n ctrlKey: boolean;\n shiftKey: boolean;\n altKey: boolean;\n metaKey: boolean;\n };\n\n/**\n * Documented wire protocol identifier embedded in handshake / message\n * envelopes. Bumped when the worker ↔ host message shape changes in a\n * backward-incompatible way.\n */\nexport const WORKER_TRANSPORT_PROTOCOL = \"ethisys.worker.remotedom.v1\";\n\n/**\n * Wire shape of the handshake message the host posts to the worker on\n * {@link WorkerRemoteDomTransport.connect}. Locked here so the SDK side and the\n * API-hosted bootstrap script (`WorkerBootstrapScriptProvider`) read from the\n * same field names — see the API tests pinning `event.data.moduleUrl` and\n * `event.data.importMap`.\n *\n * The handshake is the SOLE message that ever carries `moduleUrl` or\n * `importMap`; subsequent port traffic uses the discriminated message types\n * declared below. The capability token NEVER appears in this payload (or any\n * other postMessage payload) — it is bound on the host side via\n * {@link WorkerRemoteDomTransportOptions.capabilityToken}.\n */\nexport interface WorkerHandshakePayload\n{\n readonly type: \"ethisys:worker:handshake\";\n readonly protocol: string;\n /** Host-origin URL of the plugin's worker bundle entry. */\n readonly moduleUrl: string;\n /**\n * Frozen bare-specifier → host-origin URL map that the bootstrap installs\n * before importing {@link moduleUrl}. Pulled from the plugin's\n * `worker-bundle.import-map.json` at runtime by the host mount.\n */\n readonly importMap: Readonly<Record<string, string>>;\n}\n\n// ─── Implementation ───────────────────────────────────────────────────────────\n\ninterface InvokeToolMessage\n{\n id: string;\n type: \"ethisys:mcp:invokeTool\";\n name: string;\n args: unknown;\n}\n\ninterface GetResourceMessage\n{\n id: string;\n type: \"ethisys:mcp:getResource\";\n uri: string;\n}\n\ninterface RemoteDomMessage\n{\n type: \"ethisys:remotedom\";\n payload: unknown;\n}\n\ntype InboundMessage = InvokeToolMessage | GetResourceMessage | RemoteDomMessage | { type: string; [k: string]: unknown };\n\n/**\n * Worker-side host transport for Contract B extensions.\n *\n * Construction immediately spawns the worker and transfers one end of a\n * fresh `MessageChannel`; the host retains `port1`, the worker receives\n * `port2`. Both sides exchange messages via their port and the worker port\n * is the *only* channel for plugin ↔ host traffic after handshake.\n */\n/**\n * Default cap for in-flight MCP requests forwarded by the transport. Chosen to\n * cover typical interactive UI traffic (dashboards, list views, detail panels)\n * without letting a runaway worker saturate the host's HTTP / token pool.\n */\nexport const DEFAULT_MAX_CONCURRENT_MCP_REQUESTS = 8;\n\nexport class WorkerRemoteDomTransport\n{\n private readonly worker: WorkerLike;\n private readonly hostPort: MessagePort;\n private readonly workerPort: MessagePort;\n private readonly mcpClient: McpHttpClient;\n private readonly capabilityTokenProvider: () => Promise<string>;\n private readonly coalesceMs: number;\n private readonly maxConcurrentMcpRequests: number;\n private readonly abortController: AbortController;\n private inFlightMcpRequests = 0;\n private remoteDomConsumer: ((payload: unknown) => void) | undefined;\n private bridgeMessageConsumer: ((msg: unknown) => void) | undefined;\n private disposed = false;\n private connected = false;\n\n public constructor(options: WorkerRemoteDomTransportOptions)\n {\n const WorkerCtor = options.workerCtor ?? (globalThis as unknown as { Worker: WorkerCtor }).Worker;\n if (!WorkerCtor)\n {\n throw new Error(\"WorkerRemoteDomTransport: no Worker constructor available\");\n }\n\n this.mcpClient = options.mcpClient;\n this.capabilityTokenProvider = options.capabilityToken;\n this.coalesceMs = options.coalesceMs ?? 16;\n this.maxConcurrentMcpRequests = Math.max(\n 1,\n options.maxConcurrentMcpRequests ?? DEFAULT_MAX_CONCURRENT_MCP_REQUESTS,\n );\n // Aborted from dispose() — propagates into the mcpClient.fetch path so\n // already-in-flight HTTP calls can short-circuit instead of resolving\n // into a closed port.\n this.abortController = new AbortController();\n\n // Spawn the worker FIRST so the constructor's spawn shape is\n // observable to tests even if MessageChannel construction throws.\n this.worker = new WorkerCtor(options.bootstrapUrl, { type: \"module\" });\n\n const channel = new MessageChannel();\n this.hostPort = channel.port1;\n this.workerPort = channel.port2;\n\n // Wire host-side port handling before {@link connect} posts the\n // handshake — this closes a race where the worker could reply faster\n // than we wire up.\n this.hostPort.onmessage = (ev) => this.handlePortMessage(ev.data as InboundMessage);\n this.hostPort.start();\n\n this.worker.onerror = (ev) =>\n {\n // Defensive: do NOT echo worker errors back to the worker — they\n // may carry plugin-side details. Host-side observability is the\n // platform's responsibility.\n void ev;\n };\n }\n\n /**\n * Post the handshake to the worker. Idempotent — only the FIRST call\n * transfers the {@link MessagePort} and posts the handshake payload;\n * subsequent calls are silent no-ops. Splitting this off from the\n * constructor lets the host mount fetch the per-plugin\n * `worker-bundle.import-map.json` (and resolve the bundle's module URL)\n * before the bootstrap script consumes them.\n *\n * Wire shape: {@link WorkerHandshakePayload}. The capability token NEVER\n * appears in the payload — it's bound on the host side via the\n * {@link WorkerRemoteDomTransportOptions.capabilityToken} provider.\n *\n * The `moduleUrl` and `importMap` are forwarded to the worker so the\n * bootstrap script (served from the host-pinned\n * `/extensions/runtime/worker-bootstrap.js`) can:\n * 1. Compose the frozen, same-origin-validated `IMPORT_MAP` from the\n * handshake payload (rejecting any cross-origin entries).\n * 2. `safeImport(moduleUrl)` the plugin's entry module.\n *\n * Same-origin enforcement is the bootstrap's job — this transport is\n * structurally agnostic to the host origin and only forwards what it's\n * handed.\n */\n public connect(moduleUrl: string, importMap: Record<string, string>): void\n {\n if (this.connected)\n {\n return;\n }\n this.connected = true;\n\n // Snapshot the import map into a fresh object so the worker handshake\n // is decoupled from caller-side mutation. `Object.freeze` here is\n // defence-in-depth — the worker rebuilds with `Object.create(null)`\n // anyway, but freezing the host-side payload makes mutation a typed\n // error for any host code that hangs onto the reference.\n const handshake: WorkerHandshakePayload = Object.freeze({\n type: \"ethisys:worker:handshake\",\n protocol: WORKER_TRANSPORT_PROTOCOL,\n moduleUrl,\n importMap: Object.freeze({ ...importMap }),\n });\n this.worker.postMessage(handshake, [this.workerPort]);\n }\n\n /**\n * Bind a consumer for Remote DOM mutation payloads emitted by the worker.\n * The Remote DOM receiver wiring is owned by the caller (typically a host\n * React component); this method exposes the raw stream so the receiver\n * can integrate cleanly without a circular package import.\n */\n public onRemoteDom(consumer: (payload: unknown) => void): void\n {\n this.remoteDomConsumer = consumer;\n }\n\n /**\n * Transfer control of a host-owned `<canvas>` to the worker so plugin code\n * can render via `OffscreenCanvas`. The host retains the `<canvas>` for\n * layout / accessibility purposes only — every pixel is produced inside the\n * worker, so the main thread is never blocked per frame.\n *\n * The transfer rides the established MessagePort (not the worker global\n * `postMessage`) so it is multiplexed with the rest of the host ↔ worker\n * traffic on the same channel. The `OffscreenCanvas` handle is the sole\n * `Transferable` in the envelope; no capability token, MCP context, or\n * other host-only data crosses the boundary.\n *\n * Throws if the environment lacks `transferControlToOffscreen()` or if the\n * canvas has already been transferred — see {@link createOffscreenCanvasTransfer}\n * for the exact diagnostics.\n */\n public transferCanvas(\n canvas: HTMLCanvasElement,\n options: { surfaceId: string },\n ): { offscreen: OffscreenCanvas }\n {\n return createOffscreenCanvasTransfer({\n canvas,\n surfaceId: options.surfaceId,\n postMessage: (message, transfer) =>\n {\n this.hostPort.postMessage(message, transfer);\n },\n });\n }\n\n /**\n * Forward a host-sourced input event to the worker over the port. The\n * payload shape is opaque to the transport — pointer / wheel / keyboard\n * envelopes share the same wire type. Callers are expected to wrap\n * high-frequency (pointer-move, wheel, scroll) events in\n * {@link createCoalescer} or {@link createInputEventCoalescer} BEFORE\n * calling this method so the port never sees the un-coalesced flood.\n *\n * Keyboard / pointer-down / pointer-up events are discrete and should be\n * delivered without coalescing — pass them straight through.\n */\n public postInputEvent(payload: InputEventPayload): void\n {\n this.hostPort.postMessage({\n type: \"ethisys:input:event\",\n payload,\n });\n }\n\n /**\n * Build a trailing-edge coalescer keyed to {@link WorkerRemoteDomTransportOptions.coalesceMs}.\n *\n * Use it to wrap pointer-move / scroll / resize callbacks **before**\n * they cross the port. The last payload in any coalescing window is the\n * one that wins.\n */\n public createCoalescer<T>(consumer: (payload: T) => void): (payload: T) => void\n {\n let pending: T | undefined;\n let timer: ReturnType<typeof setTimeout> | undefined;\n const flush = () =>\n {\n timer = undefined;\n const value = pending;\n pending = undefined;\n if (value !== undefined)\n {\n consumer(value);\n }\n };\n return (payload: T) =>\n {\n pending = payload;\n if (timer === undefined)\n {\n timer = setTimeout(flush, this.coalesceMs);\n }\n };\n }\n\n // ─── Bridge push methods (WI 4858 sub-plan 2) ─────────────────────────────\n\n /**\n * Register a consumer for inbound plugin→host bridge messages\n * (chrome requests, a11y announce, lifecycle events). Only the most recently\n * registered consumer is kept — the host mount wires exactly one.\n */\n public onBridgeMessage(consumer: (msg: unknown) => void): void\n {\n this.bridgeMessageConsumer = consumer;\n }\n\n /**\n * Push the current host theme to the plugin worker. Safe to call on every\n * host theme-context change — the transport coalesces nothing here; rate\n * limiting at the call site is the host's responsibility.\n */\n public pushTheme(payload: Omit<BridgePushThemeEnvelope, \"type\">): void\n {\n this.safePostMessage({ type: BRIDGE_PUSH_THEME, ...payload });\n }\n\n /** Push the current host locale and text direction to the plugin worker. */\n public pushLocale(payload: Omit<BridgePushLocaleEnvelope, \"type\">): void\n {\n this.safePostMessage({ type: BRIDGE_PUSH_LOCALE, ...payload });\n }\n\n /** Push the current UI density preference to the plugin worker. */\n public pushDensity(payload: Omit<BridgePushDensityEnvelope, \"type\">): void\n {\n this.safePostMessage({ type: BRIDGE_PUSH_DENSITY, ...payload });\n }\n\n /** Push updated accessibility preferences to the plugin worker. */\n public pushA11y(payload: Omit<BridgePushA11yEnvelope, \"type\">): void\n {\n this.safePostMessage({ type: BRIDGE_PUSH_A11Y, ...payload });\n }\n\n /** Push the current SPA navigation state to the plugin worker. */\n public pushNav(payload: Omit<BridgeNavPushEnvelope, \"type\">): void\n {\n this.safePostMessage({ type: BRIDGE_NAV_PUSH, ...payload });\n }\n\n /**\n * Tear down the worker and port. Idempotent.\n *\n * Order matters: we abort BEFORE closing the port so any in-flight\n * `mcpClient.fetch` that observes the signal short-circuits and the\n * subsequent attempt to post a reply lands in the disposed-guard branch\n * (which silently drops the post) instead of throwing on a closed port.\n */\n public dispose(): void\n {\n if (this.disposed)\n {\n return;\n }\n this.disposed = true;\n try\n {\n this.abortController.abort();\n }\n catch\n {\n // AbortController.abort() shouldn't throw, but never let dispose\n // crash the caller — teardown must be best-effort across all\n // resources.\n }\n try\n {\n this.hostPort.close();\n }\n catch\n {\n // ignore — port may already be closed\n }\n try\n {\n this.worker.terminate();\n }\n catch\n {\n // ignore\n }\n }\n\n /**\n * Best-effort wrapper around `hostPort.postMessage` that tolerates posts\n * arriving after {@link dispose}. The browser throws on a closed port and\n * the async handlers below can race dispose, so any post initiated by an\n * awaited continuation must be guarded.\n */\n private safePostMessage(message: unknown): void\n {\n if (this.disposed)\n {\n return;\n }\n try\n {\n this.hostPort.postMessage(message);\n }\n catch\n {\n // Port may have been closed between the disposed check and the\n // post call (browser closes ports asynchronously). Swallowing the\n // throw here is safe because we are already on a teardown path.\n }\n }\n\n // ─── Port message handling ──────────────────────────────────────────────\n\n private handlePortMessage(message: InboundMessage): void\n {\n // Post-dispose port traffic is dropped silently. The browser closes\n // MessagePorts asynchronously; any handler invocation after dispose\n // is a race we observe-and-skip rather than try to service.\n if (this.disposed)\n {\n return;\n }\n switch (message.type)\n {\n case \"ethisys:mcp:invokeTool\":\n this.dispatchMcp(message as InvokeToolMessage, \"ethisys:mcp:invokeTool:result\", (m) => this.handleInvokeTool(m));\n return;\n\n case \"ethisys:mcp:getResource\":\n this.dispatchMcp(message as GetResourceMessage, \"ethisys:mcp:getResource:result\", (m) => this.handleGetResource(m));\n return;\n\n case \"ethisys:remotedom\":\n this.remoteDomConsumer?.((message as RemoteDomMessage).payload);\n return;\n\n case BRIDGE_CHROME_REQUEST:\n case BRIDGE_A11Y_ANNOUNCE:\n case BRIDGE_LIFECYCLE_EVENT:\n this.bridgeMessageConsumer?.(message);\n return;\n\n default:\n // Unknown messages are ignored. The host MUST NOT echo unknown\n // shapes back to the worker — that would create an attacker-\n // controlled reflection surface.\n return;\n }\n }\n\n /**\n * Gate inbound MCP requests through the bounded concurrency window,\n * reject with a deterministic error reply when the cap is exceeded, and\n * decrement the counter unconditionally when the underlying handler\n * settles (regardless of resolution/rejection shape).\n */\n private dispatchMcp<T extends { id: string }>(\n message: T,\n resultType: string,\n handler: (m: T) => Promise<void>,\n ): void\n {\n if (this.inFlightMcpRequests >= this.maxConcurrentMcpRequests)\n {\n // Back-pressure boundary. The worker MUST surface this to plugin\n // code so it can retry / degrade gracefully rather than silently\n // hang on a never-resolving promise.\n this.safePostMessage({\n id: message.id,\n type: resultType,\n ok: false,\n error: `MCP back-pressure: in-flight cap of ${this.maxConcurrentMcpRequests} reached.`,\n });\n return;\n }\n this.inFlightMcpRequests++;\n handler(message).finally(() =>\n {\n this.inFlightMcpRequests = Math.max(0, this.inFlightMcpRequests - 1);\n });\n }\n\n private async handleInvokeTool(message: InvokeToolMessage): Promise<void>\n {\n let token: string;\n try\n {\n token = await this.capabilityTokenProvider();\n }\n catch (err)\n {\n this.replyError(message.id, \"ethisys:mcp:invokeTool:result\", err);\n return;\n }\n // Disposed-before-token: short-circuit. The token mint may have\n // resolved seconds after dispose; posting the reply now would race\n // a closed port.\n if (this.disposed)\n {\n return;\n }\n try\n {\n const result = await this.mcpClient.fetch({\n kind: \"invokeTool\",\n name: message.name,\n args: message.args,\n capabilityToken: token,\n signal: this.abortController.signal,\n });\n // CRITICAL: response payload must never include the token. We\n // forward ONLY the public-facing fields the plugin needs.\n this.safePostMessage({\n id: message.id,\n type: \"ethisys:mcp:invokeTool:result\",\n ok: result.ok,\n data: result.data,\n error: result.error,\n });\n }\n catch (err)\n {\n this.replyError(message.id, \"ethisys:mcp:invokeTool:result\", err);\n }\n }\n\n private async handleGetResource(message: GetResourceMessage): Promise<void>\n {\n let token: string;\n try\n {\n token = await this.capabilityTokenProvider();\n }\n catch (err)\n {\n this.replyError(message.id, \"ethisys:mcp:getResource:result\", err);\n return;\n }\n if (this.disposed)\n {\n return;\n }\n try\n {\n const result = await this.mcpClient.fetch({\n kind: \"getResource\",\n uri: message.uri,\n capabilityToken: token,\n signal: this.abortController.signal,\n });\n this.safePostMessage({\n id: message.id,\n type: \"ethisys:mcp:getResource:result\",\n ok: result.ok,\n data: result.data,\n error: result.error,\n });\n }\n catch (err)\n {\n this.replyError(message.id, \"ethisys:mcp:getResource:result\", err);\n }\n }\n\n private replyError(id: string, type: string, err: unknown): void\n {\n // Surface a sanitised error string — never the raw error object\n // (which may carry stack traces or token fragments from a misbehaving\n // mcpClient implementation). Routed through safePostMessage so a\n // post-dispose reply does not crash on a closed port.\n const message = err instanceof Error ? err.message : \"MCP request failed\";\n this.safePostMessage({ id, type, ok: false, error: message });\n }\n}\n","/**\n * Semantic component registry for Contract B (worker remote-runtime)\n * extensions.\n *\n * Worker-side plugin code references UI primitives by name — e.g.\n * `<DataTable />`, `<Drawer />`, `<CanvasSurface />`. The host owns rendering:\n * a {@link SemanticComponentRegistry} maps each name to a concrete component\n * type, and the Remote DOM host receiver consults the registry on every\n * worker-emitted mutation. The closed primitive vocabulary {@link CONTRACT_B_PRIMITIVES}\n * is intentionally narrow — adding a primitive is a coordinated host change so\n * plugins cannot extend the contract ad hoc.\n *\n * **Alignment with SDUI:** Where the Contract B name overlaps with the\n * `@ethisyscore/protocol` SDUI primitive vocabulary (`DataTable`, `Form`), the\n * names are identical to avoid synonym drift. SDUI's `Action` is intentionally\n * NOT reused — Contract B's interactive primitive is named `Button` so the\n * worker authoring API stays close to React DOM idioms. The two vocabularies\n * may converge as they evolve, but the host registries (Contract A SDUI vs\n * Contract B Semantic) remain distinct surfaces today.\n *\n * The registry is renderer-agnostic — the generic `TComponent` parameter is\n * whatever component type the host supplies (React `ComponentType` in\n * production; plain objects in tests). This keeps the registry free of a\n * React peer-dep requirement and lets tests assert behaviour without rendering.\n */\n\n// ─── Closed v1 vocabulary ─────────────────────────────────────────────────────\n\n/**\n * The mandatory Contract B (worker remote-runtime) semantic primitive vocabulary.\n *\n * Adding a primitive requires a coordinated host change + protocol bump —\n * plugins cannot extend this set. Each name has a host-supplied concrete\n * component (gogo-ui in production); the registry enforces 1:1 substitution.\n */\nexport const CONTRACT_B_PRIMITIVES = [\n \"Button\",\n \"DataTable\",\n \"Form\",\n \"EntityPicker\",\n \"CommandBar\",\n \"Drawer\",\n \"Modal\",\n \"CanvasSurface\",\n \"WebGLSurface\",\n // Phase 1 additions — mirror the vite-plugin's CONTRACT_B_SEMANTIC_PRIMITIVES\n // list verbatim; drift between the two is caught by the\n // contract-b.test.ts \"import-map-allowlist matches extension-runtime\n // CONTRACT_B_PRIMITIVES\" test.\n \"Card\",\n \"Tabs\",\n \"Select\",\n \"Alert\",\n] as const;\n\n/**\n * Union type of the Contract B semantic primitive names — exported so callers\n * (tests, hosts) get full IntelliSense and compile-time exhaustiveness checks.\n */\nexport type SemanticPrimitiveName = typeof CONTRACT_B_PRIMITIVES[number];\n\nconst KNOWN_PRIMITIVE_SET: ReadonlySet<string> = new Set(CONTRACT_B_PRIMITIVES);\n\n// ─── Registry ─────────────────────────────────────────────────────────────────\n\n/**\n * Map of semantic primitive name → concrete component for Contract B\n * extensions.\n *\n * Construction is open-ended (callers register primitives one at a time) or\n * eager via {@link SemanticComponentRegistry.fromMap} which forces a complete\n * map up front and aborts if any primitive is missing.\n *\n * @typeParam TComponent - The host's concrete component shape. React hosts pass\n * `ComponentType<P>`; tests pass any tagged object.\n */\nexport class SemanticComponentRegistry<TComponent>\n{\n private readonly entries = new Map<SemanticPrimitiveName, TComponent>();\n\n /**\n * Register a primitive → component binding.\n *\n * Throws if `name` is not in {@link CONTRACT_B_PRIMITIVES} (drift prevention)\n * or if `name` is already registered (silent-override prevention).\n */\n public register(name: SemanticPrimitiveName, component: TComponent): void\n {\n if (!KNOWN_PRIMITIVE_SET.has(name))\n {\n throw new Error(\n `[component-registry] unknown semantic primitive '${name}'. ` +\n `Allowed: ${CONTRACT_B_PRIMITIVES.join(\", \")}.`,\n );\n }\n if (this.entries.has(name))\n {\n throw new Error(\n `[component-registry] primitive '${name}' is already registered. ` +\n `Construct a new registry instead of overriding a binding.`,\n );\n }\n this.entries.set(name, component);\n }\n\n /**\n * Resolve a primitive name to its concrete component. Throws if the name is\n * outside the Contract B vocabulary OR if the registry has no binding —\n * silent fallthrough would let typos render blank components, which is\n * worse than a fast failure for plugin authors.\n */\n public resolve(name: SemanticPrimitiveName): TComponent\n {\n if (!KNOWN_PRIMITIVE_SET.has(name))\n {\n throw new Error(\n `[component-registry] unknown semantic primitive '${name}'. ` +\n `Allowed: ${CONTRACT_B_PRIMITIVES.join(\", \")}.`,\n );\n }\n const found = this.entries.get(name);\n if (found === undefined)\n {\n const registered = this.listRegistered().join(\", \") || \"<none>\";\n throw new Error(\n `[component-registry] primitive '${name}' is not registered. ` +\n `Registered primitives: ${registered}.`,\n );\n }\n return found;\n }\n\n /**\n * Cheap presence probe — never throws. Use this when callers want to\n * fall back to a default component instead of erroring.\n */\n public has(name: SemanticPrimitiveName): boolean\n {\n return this.entries.has(name);\n }\n\n /**\n * Sorted list of registered primitive names. Stable output makes registry\n * diagnostics and snapshot tests deterministic.\n */\n public listRegistered(): SemanticPrimitiveName[]\n {\n return [...this.entries.keys()].sort() as SemanticPrimitiveName[];\n }\n\n /**\n * Build a registry from a complete `Record<SemanticPrimitiveName, T>` map.\n *\n * The `Record` type forces TypeScript to refuse incomplete literals at\n * compile time, AND we re-check at runtime so callers who type-erase the\n * record (e.g., via `as any`) still get a fast failure.\n */\n public static fromMap<T>(map: Record<SemanticPrimitiveName, T>): SemanticComponentRegistry<T>\n {\n const registry = new SemanticComponentRegistry<T>();\n const missing: SemanticPrimitiveName[] = [];\n for (const name of CONTRACT_B_PRIMITIVES)\n {\n const component = (map as Partial<Record<SemanticPrimitiveName, T>>)[name];\n if (component === undefined)\n {\n missing.push(name);\n continue;\n }\n registry.register(name, component);\n }\n if (missing.length > 0)\n {\n throw new Error(\n `[component-registry] fromMap is missing semantic primitive(s): ${missing.join(\", \")}.`,\n );\n }\n return registry;\n }\n}\n","/**\n * `npx mock-host <dir>` — standalone declarative-mock-host dev server.\n *\n * Reads SDUI resources from `<dir>/**\\/*.json` (recursive), wires them into the\n * {@link InMemoryMcpTransport}, and serves a Vite dev page that mounts\n * {@link DeclarativeMockHost} so plugin authors can iterate against the same\n * declarative pipeline the real host uses — without standing up the platform.\n *\n * Resource JSON shape (two accepted forms):\n *\n * 1. Tagged: `{ \"uri\": \"books://catalog\", \"tree\": <SduiNode> }`\n * 2. Untagged: a bare SDUI node. The URI defaults to the file's relative\n * path under the resources directory (with `\\\\` normalised to `/` and the\n * `.json` extension stripped).\n *\n * The CLI's pure resource-loading core is exported as {@link loadResources}\n * for unit testing; the Vite-boot side effect is gated behind {@link run}.\n */\n\nimport { promises as fs } from \"node:fs\";\nimport * as path from \"node:path\";\nimport { KNOWN_PRIMITIVES } from \"@ethisyscore/protocol\";\nimport type { SduiNode } from \"../host/declarative/types\";\nimport {\n WorkerRemoteDomTransport,\n type McpHttpClient,\n type WorkerCtor,\n} from \"../host/worker/transport\";\nimport { CONTRACT_B_PRIMITIVES } from \"../host/worker/component-registry\";\n\n/** Map of MCP resource URI -> SDUI tree, as fed into the mock host. */\nexport type ResourceMap = Record<string, SduiNode>;\n\n/**\n * Map of MCP tool name -> canned result, as fed into the mock host. Each\n * value is returned verbatim when the tool is invoked from the rendered tree\n * (Form.submitTool, Action.tool, Field.lookupTool). Plugin authors who need\n * per-call dynamic behaviour can switch from the CLI's tools.json to a\n * hand-rolled Vite app that mounts DeclarativeMockHost with a typed tools\n * object directly.\n */\nexport type ToolResultMap = Record<string, unknown>;\n\n/**\n * Recursively load every `*.json` file under {@link dir} as a SDUI resource.\n *\n * - Tagged form (`{ uri, tree }`) wins when both fields are present.\n * - Untagged form uses the file's relative POSIX-normalised path (minus\n * extension) as the resource URI.\n *\n * Throws a single aggregated error if any file fails to parse — plugin authors\n * see one failure list instead of giving up at the first bad file.\n */\nexport async function loadResources(dir: string): Promise<ResourceMap>\n{\n const absDir = path.resolve(dir);\n const files = await walkJson(absDir);\n const out: ResourceMap = {};\n const errors: string[] = [];\n\n for (const file of files)\n {\n try\n {\n const raw = await fs.readFile(file, \"utf8\");\n const parsed = JSON.parse(raw) as unknown;\n const { uri, tree } = normaliseEntry(parsed, file, absDir);\n if (out[uri] !== undefined)\n {\n errors.push(`Duplicate resource URI '${uri}' (from ${file})`);\n continue;\n }\n out[uri] = tree;\n }\n catch (e)\n {\n errors.push(`${file}: ${(e as Error).message}`);\n }\n }\n\n if (errors.length > 0)\n {\n throw new Error(\n `Failed to load ${errors.length} mock-host resource(s):\\n - ${errors.join(\"\\n - \")}`,\n );\n }\n\n return out;\n}\n\n/**\n * Read `<dir>/tools.json` (if present) and return a `Record<toolName, canned>`\n * map for the mock transport. Missing file → empty map. Malformed file →\n * throws a single descriptive error.\n *\n * Expected shape: `{ \"<tool-name>\": <result-literal>, ... }`. The literal is\n * returned verbatim from `InMemoryMcpTransport.invokeTool(name, args)`.\n */\nexport async function loadTools(dir: string): Promise<ToolResultMap>\n{\n const toolsPath = path.resolve(dir, \"tools.json\");\n try\n {\n const raw = await fs.readFile(toolsPath, \"utf8\");\n const parsed = JSON.parse(raw) as unknown;\n if (parsed === null || typeof parsed !== \"object\" || Array.isArray(parsed))\n {\n throw new Error(`Expected an object map, got ${typeof parsed}`);\n }\n return parsed as ToolResultMap;\n }\n catch (e)\n {\n const err = e as NodeJS.ErrnoException;\n if (err.code === \"ENOENT\")\n {\n return {};\n }\n throw new Error(`Failed to load tools.json from ${toolsPath}: ${err.message}`);\n }\n}\n\n/**\n * Convert a parsed JSON value into a `{ uri, tree }` pair.\n * Exported for tests; not part of the public API.\n */\nexport function normaliseEntry(\n parsed: unknown,\n absFile: string,\n absDir: string,\n): { uri: string; tree: SduiNode }\n{\n if (parsed === null || typeof parsed !== \"object\")\n {\n throw new Error(\"Resource JSON must be an object\");\n }\n const obj = parsed as Record<string, unknown>;\n if (typeof obj.uri === \"string\" && obj.tree !== undefined)\n {\n return { uri: obj.uri, tree: obj.tree as SduiNode };\n }\n // Untagged form: derive URI from the file's relative path.\n const rel = path.relative(absDir, absFile).replace(/\\\\/g, \"/\");\n const uri = rel.replace(/\\.json$/i, \"\");\n return { uri, tree: parsed as SduiNode };\n}\n\nasync function walkJson(root: string): Promise<string[]>\n{\n const out: string[] = [];\n\n async function recurse(dir: string): Promise<void>\n {\n let entries;\n try\n {\n entries = await fs.readdir(dir, { withFileTypes: true });\n }\n catch (e)\n {\n const err = e as NodeJS.ErrnoException;\n if (err.code === \"ENOENT\")\n {\n throw new Error(`Resources directory not found: ${root}`);\n }\n throw e;\n }\n for (const entry of entries)\n {\n const full = path.join(dir, entry.name);\n if (entry.isDirectory())\n {\n await recurse(full);\n }\n else if (entry.isFile() && entry.name.toLowerCase().endsWith(\".json\"))\n {\n out.push(full);\n }\n }\n }\n\n await recurse(root);\n out.sort();\n return out;\n}\n\n/**\n * Render a self-contained HTML page that mounts {@link DeclarativeMockHost}\n * with a debug registry (every primitive renders to a labelled `<div>`).\n *\n * Returned as a string so the Vite middleware can serve it from memory without\n * touching the consumer's filesystem.\n */\nexport function renderIndexHtml(resourceUris: string[]): string\n{\n const defaultUri = resourceUris[0] ?? \"\";\n return `<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"utf-8\" />\n <title>mock-host (${resourceUris.length} resources)</title>\n <style>\n body { font-family: system-ui, sans-serif; margin: 1rem; }\n header { border-bottom: 1px solid #ccc; padding-bottom: 0.5rem; margin-bottom: 1rem; }\n select { padding: 0.25rem 0.5rem; }\n [data-mock-primitive] { padding: 0.5rem; margin: 0.25rem 0; border: 1px dashed #aaa; }\n [data-mock-primitive]::before { content: attr(data-mock-primitive); font-size: 0.75rem; color: #666; display: block; }\n </style>\n </head>\n <body>\n <header>\n <strong>mock-host</strong>\n &nbsp;|&nbsp;\n <label>Resource: <select id=\"resource-picker\">\n ${resourceUris.map(u => `<option value=\"${escapeHtml(u)}\">${escapeHtml(u)}</option>`).join(\"\")}\n </select></label>\n </header>\n <main id=\"root\"></main>\n <script type=\"module\" src=\"/@mock-host/entry.tsx\"></script>\n <script>\n window.__MOCK_HOST_DEFAULT_URI__ = ${safeJsonForScript(defaultUri)};\n window.__MOCK_HOST_RESOURCE_URIS__ = ${safeJsonForScript(resourceUris)};\n </script>\n </body>\n</html>`;\n}\n\nfunction escapeHtml(s: string): string\n{\n return s.replace(/[&<>\"']/g, c => ({\n \"&\": \"&amp;\",\n \"<\": \"&lt;\",\n \">\": \"&gt;\",\n \"\\\"\": \"&quot;\",\n \"'\": \"&#39;\",\n }[c] as string));\n}\n\n/**\n * Safely serialise a value for embedding inside an inline `<script>` block.\n *\n * Plain `JSON.stringify` leaves `</script>` unescaped, which lets attacker-\n * controlled resource URIs break out of the script context. We replace the\n * dangerous character pairs with their unicode-escaped equivalents — still\n * valid JSON the browser eagerly parses, but inert inside HTML.\n */\nfunction safeJsonForScript(v: unknown): string\n{\n return JSON.stringify(v)\n .replace(/</g, \"\\\\u003c\")\n .replace(/>/g, \"\\\\u003e\")\n .replace(/&/g, \"\\\\u0026\");\n}\n\n/**\n * Build the virtual entry module the browser loads. Embeds the resource map\n * inline so the dev server does not have to serve JSON over a separate\n * endpoint — Vite HMR re-evaluates this module whenever resources change.\n */\nexport function renderEntryModule(\n resources: ResourceMap,\n tools: ToolResultMap = {},\n): string\n{\n const json = JSON.stringify(resources);\n const toolsJson = JSON.stringify(tools);\n // PR #16 review (Medium: mock-host primitive registry hardcoded). Derive the\n // primitive list from the protocol's KNOWN_PRIMITIVES so the mock host can\n // never accept primitives the real protocol rejects, or omit primitives the\n // real protocol added. JSON.stringify ensures the array is emitted as a JS\n // literal in the generated module — identical wire shape to the prior\n // hardcoded list, just sourced from the protocol package.\n const primitives = JSON.stringify([...KNOWN_PRIMITIVES]);\n return `\nimport { createRoot } from \"react-dom/client\";\nimport { createElement, useState } from \"react\";\nimport { DeclarativeMockHost } from \"@ethisyscore/extension-runtime/mock-host\";\n\nconst PRIMITIVES = ${primitives};\n\nconst registry = Object.fromEntries(PRIMITIVES.map(name => [\n name,\n ({ children }) => createElement(\"div\", { \"data-mock-primitive\": name }, children),\n]));\n\nconst resources = ${json};\nconst toolResults = ${toolsJson};\n// Each canned tool result is wrapped in a sync handler. Authors that need\n// per-call behaviour drop tools.json and hand-roll a Vite app that mounts\n// DeclarativeMockHost with a typed tools object directly.\nconst tools = Object.fromEntries(Object.entries(toolResults).map(([name, value]) => [\n name,\n () => value,\n]));\nconst uris = Object.keys(resources);\n\nfunction App() {\n const [uri, setUri] = useState(window.__MOCK_HOST_DEFAULT_URI__ || uris[0] || \"\");\n // Wire the header picker to React state so authors can switch trees live.\n const picker = document.getElementById(\"resource-picker\");\n if (picker && !picker.__wired) {\n picker.__wired = true;\n picker.value = uri;\n picker.addEventListener(\"change\", e => setUri(e.target.value));\n }\n return createElement(DeclarativeMockHost, {\n resources,\n tools,\n registry,\n defaultResourceUri: uri,\n });\n}\n\ncreateRoot(document.getElementById(\"root\")).render(createElement(App));\n`;\n}\n\n// ─── Contract B (Wave 1) CLI augment ──────────────────────────────────────────\n//\n// Wave 0 only supported Contract A (host-rendered) — a directory of SDUI JSON\n// fed into {@link DeclarativeMockHost}. Wave 1 adds Contract B (remote-runtime):\n// plugin authors run `npx mock-host --render-mode remote-runtime ./worker.js`\n// to spawn an in-process Worker via {@link WorkerRemoteDomTransport} against\n// the same Vite dev page workflow. The semantic component registry from E2.S4\n// is referenced by name — each primitive renders to a labelled placeholder so\n// the receiver's substitution behaviour is observable without standing up\n// gogo-ui.\n\n/** Discriminated union of the two render modes the CLI knows about. */\nexport type RenderMode = \"host-rendered\" | \"remote-runtime\";\n\n/**\n * Parsed CLI arguments. `path` is the positional argument — its meaning\n * depends on {@link renderMode}:\n *\n * - `host-rendered`: a directory of SDUI resource JSON files.\n * - `remote-runtime`: a path (or URL) to a built worker bundle.\n */\nexport interface ParsedArgs\n{\n renderMode: RenderMode;\n path: string;\n}\n\nconst RENDER_MODES: ReadonlySet<RenderMode> = new Set<RenderMode>([\n \"host-rendered\",\n \"remote-runtime\",\n]);\n\n/**\n * Parse the CLI's argv-slice. Exported so the test suite can assert flag\n * handling without booting a Vite server.\n *\n * Accepts both `--render-mode <value>` and `--render-mode=<value>`. Defaults\n * to `host-rendered` to preserve Wave 0 behaviour for existing users.\n */\nexport function parseArgs(args: readonly string[]): ParsedArgs\n{\n let renderMode: RenderMode = \"host-rendered\";\n const positional: string[] = [];\n\n for (let i = 0; i < args.length; i++)\n {\n const arg = args[i]!;\n if (arg === \"--render-mode\")\n {\n const next = args[i + 1];\n if (next === undefined)\n {\n throw new Error(\"Usage: mock-host [--render-mode <host-rendered|remote-runtime>] <path>\");\n }\n if (!RENDER_MODES.has(next as RenderMode))\n {\n throw new Error(\n `Invalid --render-mode '${next}'. Allowed: ${[...RENDER_MODES].join(\", \")}.`,\n );\n }\n renderMode = next as RenderMode;\n i++;\n continue;\n }\n if (arg.startsWith(\"--render-mode=\"))\n {\n const value = arg.slice(\"--render-mode=\".length);\n if (!RENDER_MODES.has(value as RenderMode))\n {\n throw new Error(\n `Invalid --render-mode '${value}'. Allowed: ${[...RENDER_MODES].join(\", \")}.`,\n );\n }\n renderMode = value as RenderMode;\n continue;\n }\n positional.push(arg);\n }\n\n if (positional.length === 0)\n {\n throw new Error(\"Usage: mock-host [--render-mode <host-rendered|remote-runtime>] <path>\");\n }\n\n return { renderMode, path: positional[0]! };\n}\n\n/**\n * Render the HTML shell for the Contract B mock host. The page hosts a\n * receiver that mounts the Remote DOM tree produced by the worker — each\n * Contract B primitive renders to a labelled placeholder.\n */\nexport function renderWorkerBundleIndexHtml(bundlePath: string): string\n{\n const safePath = escapeHtml(bundlePath);\n return `<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"utf-8\" />\n <title>mock-host (remote-runtime: ${safePath})</title>\n <style>\n body { font-family: system-ui, sans-serif; margin: 1rem; }\n header { border-bottom: 1px solid #ccc; padding-bottom: 0.5rem; margin-bottom: 1rem; }\n [data-mock-primitive] { padding: 0.5rem; margin: 0.25rem 0; border: 1px dashed #aaa; }\n [data-mock-primitive]::before { content: attr(data-mock-primitive); font-size: 0.75rem; color: #666; display: block; }\n </style>\n </head>\n <body>\n <header><strong>mock-host</strong> &nbsp;|&nbsp; <em>remote-runtime: ${safePath}</em></header>\n <main id=\"root\"></main>\n <script type=\"module\" src=\"/@mock-host/worker-bundle-entry.tsx\"></script>\n </body>\n</html>`;\n}\n\n/**\n * Build the inline entry module the browser loads for Contract B.\n *\n * The module:\n * 1. Constructs a {@link WorkerRemoteDomTransport} pointing at the bundle.\n * 2. Registers a placeholder for every Contract B semantic primitive — the\n * test-injectable seam from E2.S4. Production hosts would substitute\n * gogo-ui components here.\n * 3. Wires the receiver onto the host's `<main id=\"root\">` slot.\n *\n * In-page wiring is deliberately minimal — the goal is observability, not a\n * production receiver implementation. Authors iterate against the same\n * surface their plugins will mount into when the platform host is finished.\n */\nexport function renderWorkerBundleEntryModule(bundleUrl: string): string\n{\n const safePrimitives = JSON.stringify([...CONTRACT_B_PRIMITIVES]);\n const safeUrl = JSON.stringify(bundleUrl);\n return `\nimport { createRoot } from \"react-dom/client\";\nimport { createElement } from \"react\";\nimport {\n WorkerRemoteDomTransport,\n SemanticComponentRegistry,\n} from \"@ethisyscore/extension-runtime/host\";\n\nconst PRIMITIVES = ${safePrimitives};\nconst bundleUrl = ${safeUrl};\n\n// Build the test-injectable component map — every Contract B primitive\n// renders to a labelled placeholder so plugin authors can see what their\n// worker is emitting before plugging in gogo-ui.\nconst componentMap = Object.fromEntries(PRIMITIVES.map(name => [\n name,\n ({ children }) => createElement(\"div\", { \"data-mock-primitive\": name }, children),\n]));\nconst registry = SemanticComponentRegistry.fromMap(componentMap);\n\n// Mock-host MCP client: no platform, so every tool/resource call resolves\n// to a friendly placeholder. Authors who need real MCP traffic should run\n// against the full platform host.\nconst mcpClient = {\n fetch: async (req) => ({\n ok: true,\n data: { mockHost: true, kind: req.kind, name: req.name ?? req.uri },\n }),\n};\n\n// The capability token never crosses the worker boundary — supply a marker\n// here so the host-side fetcher can still attach an Authorization header.\nconst transport = new WorkerRemoteDomTransport({\n bootstrapUrl: bundleUrl,\n capabilityToken: async () => \"mock-host-capability-token\",\n mcpClient,\n});\n\nconst root = createRoot(document.getElementById(\"root\"));\ntransport.onRemoteDom(() => {\n // The full Remote DOM receiver belongs to a separate concern (E3.S2's\n // WorkerSurfaceMount). For mock-host purposes the registry is the\n // observable surface — log the receiver payload for now.\n root.render(createElement(\"div\", { \"data-mock-host-primitives\": PRIMITIVES.join(\",\") }));\n});\n\n// Expose the transport + registry on window so authors can poke at them from\n// devtools (e.g., to terminate the worker between iteration cycles).\nwindow.__MOCK_HOST_TRANSPORT__ = transport;\nwindow.__MOCK_HOST_REGISTRY__ = registry;\n`;\n}\n\n/**\n * Construction options for {@link bootContractBHost}.\n */\nexport interface BootContractBHostOptions\n{\n /**\n * Path or canonical URL to the plugin's worker bundle. The mock host\n * serves it from the configured Vite root — production hosts always use\n * a path-pinned host-origin URL.\n */\n bundleUrl: string;\n /**\n * Injectable Worker constructor. Tests pass a fake; the production CLI\n * path uses the global `Worker`. The transport itself defaults to\n * `globalThis.Worker` when omitted — but the mock-host CLI passes through\n * the caller's choice so test seams compose cleanly.\n */\n workerCtor?: WorkerCtor;\n /**\n * Optional capability token provider. Defaults to a marker string so\n * authors don't need to wire token plumbing into local-dev.\n */\n capabilityToken?: () => Promise<string>;\n /**\n * Optional MCP HTTP client. Defaults to a stub that echoes the request\n * shape — authors who need real MCP traffic compose the platform host.\n */\n mcpClient?: McpHttpClient;\n}\n\nconst defaultMockMcpClient: McpHttpClient = {\n fetch: async (req) =>\n {\n const echo = req.kind === \"invokeTool\" ? { tool: req.name, args: req.args } : { resource: req.uri };\n return { ok: true, data: { mockHost: true, ...echo } };\n },\n};\n\n/**\n * Spawn the in-process worker that drives the Contract B mock host.\n *\n * Returns the constructed transport so callers (production CLI: the dev page;\n * tests: assertions) can observe spawn shape, dispose the worker on shutdown,\n * and inspect coalescing behaviour. Reuses\n * {@link WorkerRemoteDomTransport} so the capability-token isolation rules\n * documented in E2.S3 apply without re-implementation.\n */\nexport function bootContractBHost(options: BootContractBHostOptions): WorkerRemoteDomTransport\n{\n return new WorkerRemoteDomTransport({\n bootstrapUrl: options.bundleUrl,\n capabilityToken: options.capabilityToken ?? (async () => \"mock-host-capability-token\"),\n mcpClient: options.mcpClient ?? defaultMockMcpClient,\n workerCtor: options.workerCtor,\n });\n}\n\n/**\n * Wave 1 Contract B dev server. Boots Vite, serves the virtual index page that\n * runs {@link renderWorkerBundleEntryModule} (which spawns the worker), and\n * exposes the bundle on a host-origin URL so the worker handshake can succeed.\n */\nasync function runContractBHost(bundlePathOrUrl: string): Promise<{ url: string; close: () => Promise<void> }>\n{\n // Lazy import so the pure exports stay decoupled from Vite at type-time.\n /* eslint-disable @typescript-eslint/no-explicit-any */\n let viteMod: any;\n try\n {\n viteMod = await import(\"vite\" as any);\n }\n catch\n {\n throw new Error(\n \"vite is required to run `mock-host`. Install with: npm install --save-dev vite\",\n );\n }\n /* eslint-enable @typescript-eslint/no-explicit-any */\n\n // Two flavours of bundle reference are accepted: an absolute URL (rare —\n // useful for testing a deployed bundle) and a filesystem path that the\n // dev server exposes as a host-origin URL.\n const isUrl = /^https?:\\/\\//i.test(bundlePathOrUrl);\n const absBundle = isUrl ? bundlePathOrUrl : path.resolve(bundlePathOrUrl);\n\n if (!isUrl)\n {\n try\n {\n await fs.access(absBundle);\n }\n catch\n {\n throw new Error(`mock-host: worker bundle not found at ${absBundle}`);\n }\n }\n\n // The dev page references the bundle via a stable virtual path. The Vite\n // middleware serves the file's bytes directly so the worker spawn URL is\n // host-origin (matching the production CSP path-pinning shape).\n const bundleServePath = \"/@mock-host/worker-bundle.js\";\n const indexHtml = renderWorkerBundleIndexHtml(isUrl ? bundlePathOrUrl : bundleServePath);\n const entryModule = renderWorkerBundleEntryModule(isUrl ? bundlePathOrUrl : bundleServePath);\n\n const server = await viteMod.createServer({\n root: process.cwd(),\n configFile: false,\n server: { port: 0, host: \"127.0.0.1\" },\n plugins: [\n {\n name: \"mock-host-virtual-worker\",\n resolveId(id: string)\n {\n if (id === \"/@mock-host/worker-bundle-entry.tsx\")\n {\n return \"\\0mock-host:worker-bundle-entry\";\n }\n return null;\n },\n load(id: string)\n {\n if (id === \"\\0mock-host:worker-bundle-entry\")\n {\n return entryModule;\n }\n return null;\n },\n /* eslint-disable @typescript-eslint/no-explicit-any */\n configureServer(devServer: any)\n {\n devServer.middlewares.use(async (req: any, res: any, next: () => void) =>\n {\n if (req.url === \"/\" || req.url === \"/index.html\")\n {\n res.setHeader(\"Content-Type\", \"text/html\");\n res.end(indexHtml);\n return;\n }\n if (!isUrl && req.url === bundleServePath)\n {\n try\n {\n const bytes = await fs.readFile(absBundle);\n res.setHeader(\"Content-Type\", \"text/javascript; charset=utf-8\");\n res.end(bytes);\n }\n catch (e)\n {\n res.statusCode = 500;\n res.end(`mock-host: failed to read worker bundle: ${(e as Error).message}`);\n }\n return;\n }\n next();\n });\n },\n /* eslint-enable @typescript-eslint/no-explicit-any */\n },\n ],\n });\n\n await server.listen();\n const addr = server.httpServer?.address();\n let port = 5173;\n if (addr && typeof addr === \"object\")\n {\n port = addr.port;\n }\n const url = `http://127.0.0.1:${port}/`;\n process.stdout.write(`mock-host listening on ${url} (remote-runtime: ${absBundle})\\n`);\n\n return { url, close: () => server.close() };\n}\n\n/**\n * Boot the Vite dev server with an in-memory entry + virtual page.\n *\n * Side-effectful: imports `vite` lazily so the pure `loadResources` export\n * stays usable from environments that lack a Vite install (e.g., the unit\n * tests in this package).\n *\n * Dispatches on `--render-mode`:\n * - `host-rendered` (default): Wave 0 declarative-mock-host behaviour.\n * - `remote-runtime`: Wave 1 Contract B — spawns a worker pointing at the\n * supplied bundle path and serves the placeholder dev page.\n */\nexport async function run(args: string[]): Promise<{ url: string; close: () => Promise<void> }>\n{\n const parsed = parseArgs(args);\n\n if (parsed.renderMode === \"remote-runtime\")\n {\n return runContractBHost(parsed.path);\n }\n\n const dir = parsed.path;\n const resources = await loadResources(dir);\n const tools = await loadTools(dir);\n const uris = Object.keys(resources).sort();\n if (uris.length === 0)\n {\n // Allow boot anyway — authors might want to scaffold-then-add resources\n // with HMR-watched edits.\n process.stderr.write(`mock-host: no resources found in ${dir} (HMR will pick up new files)\\n`);\n }\n\n // Lazy import so the pure exports stay decoupled from Vite at type-time.\n // Vite is declared as an OPTIONAL peer dependency — type as `any` here so\n // building the package doesn't require Vite's type surface to be present.\n /* eslint-disable @typescript-eslint/no-explicit-any */\n let viteMod: any;\n try\n {\n viteMod = await import(\"vite\" as any);\n }\n catch\n {\n throw new Error(\n \"vite is required to run `mock-host`. Install with: npm install --save-dev vite\",\n );\n }\n /* eslint-enable @typescript-eslint/no-explicit-any */\n\n const indexHtml = renderIndexHtml(uris);\n const entryModule = renderEntryModule(resources, tools);\n const absDir = path.resolve(dir);\n\n const server = await viteMod.createServer({\n root: process.cwd(),\n configFile: false,\n server: { port: 0, host: \"127.0.0.1\" },\n plugins: [\n {\n name: \"mock-host-virtual\",\n resolveId(id: string)\n {\n if (id === \"/@mock-host/entry.tsx\")\n {\n return \"\\0mock-host:entry\";\n }\n return null;\n },\n load(id: string)\n {\n if (id === \"\\0mock-host:entry\")\n {\n return entryModule;\n }\n return null;\n },\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n configureServer(devServer: any)\n {\n devServer.watcher.add(absDir);\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n devServer.middlewares.use((req: any, res: any, next: () => void) =>\n {\n if (req.url === \"/\" || req.url === \"/index.html\")\n {\n res.setHeader(\"Content-Type\", \"text/html\");\n res.end(indexHtml);\n return;\n }\n next();\n });\n },\n },\n ],\n });\n\n await server.listen();\n const addr = server.httpServer?.address();\n let port = 5173;\n if (addr && typeof addr === \"object\")\n {\n port = addr.port;\n }\n const url = `http://127.0.0.1:${port}/`;\n process.stdout.write(`mock-host listening on ${url} (resources: ${uris.length})\\n`);\n\n return { url, close: () => server.close() };\n}\n\n// No top-level bin guard here: the `mock-host` bin entry is a thin\n// CJS wrapper (`bin/mock-host.cjs`, hand-written, kept out of tsup) that\n// requires the compiled `cli.cjs` and calls `run(process.argv.slice(2))`.\n// That keeps this module side-effect free so unit tests can import the pure\n// `loadResources` / `normaliseEntry` / `renderEntryModule` exports without\n// booting Vite.\n"]}
@@ -68,7 +68,91 @@ function DeclarativeMockHost(props) {
68
68
  ] });
69
69
  }
70
70
 
71
+ // src/mock-host/InMemoryBridgeTransport.ts
72
+ var InMemoryBridgeTransport = class {
73
+ _themeCb;
74
+ _localeCb;
75
+ _densityCb;
76
+ _a11yCb;
77
+ _navCb;
78
+ _tokenCb;
79
+ _chromeHandler;
80
+ // ── PortBridgeClient subscriber interface ──────────────────────────────────
81
+ onTheme(cb) {
82
+ this._themeCb = cb;
83
+ }
84
+ onLocale(cb) {
85
+ this._localeCb = cb;
86
+ }
87
+ onDensity(cb) {
88
+ this._densityCb = cb;
89
+ }
90
+ onA11y(cb) {
91
+ this._a11yCb = cb;
92
+ }
93
+ onNav(cb) {
94
+ this._navCb = cb;
95
+ }
96
+ onSessionToken(cb) {
97
+ this._tokenCb = cb;
98
+ }
99
+ requestChrome(action, payload) {
100
+ return this.simulateChromeRequest(action, payload);
101
+ }
102
+ announceA11y(_message, _politeness) {
103
+ }
104
+ // ── Test / dev control surface ─────────────────────────────────────────────
105
+ /** Push a theme update to the registered subscriber (synchronous). */
106
+ pushTheme(payload) {
107
+ this._themeCb?.(payload);
108
+ }
109
+ /** Push a locale update to the registered subscriber. */
110
+ pushLocale(payload) {
111
+ this._localeCb?.(payload);
112
+ }
113
+ /** Push a density update. */
114
+ pushDensity(payload) {
115
+ this._densityCb?.(payload);
116
+ }
117
+ /** Push a11y preference changes. */
118
+ pushA11y(payload) {
119
+ this._a11yCb?.(payload);
120
+ }
121
+ /** Push a nav state update. */
122
+ pushNav(payload) {
123
+ this._navCb?.(payload);
124
+ }
125
+ /** Push a frontend-session token. */
126
+ pushSessionToken(payload) {
127
+ this._tokenCb?.(payload);
128
+ }
129
+ /**
130
+ * Register a handler for plugin→host chrome requests (toast, confirm, etc).
131
+ * Called by `requestChrome` and by `simulateChromeRequest`.
132
+ */
133
+ onChromeRequest(handler) {
134
+ this._chromeHandler = handler;
135
+ }
136
+ /**
137
+ * Programmatically send a chrome request as if a plugin component called
138
+ * `PortBridgeClient.requestChrome(...)`. Useful for test assertions.
139
+ */
140
+ async simulateChromeRequest(action, payload) {
141
+ if (this._chromeHandler === void 0) {
142
+ return null;
143
+ }
144
+ return this._chromeHandler(action, payload);
145
+ }
146
+ };
147
+ var BridgeClientContext = react.createContext(null);
148
+ function WorkerMockHost(props) {
149
+ const { bridgeTransport, ...declarativeProps } = props;
150
+ return /* @__PURE__ */ jsxRuntime.jsx(BridgeClientContext.Provider, { value: bridgeTransport, children: /* @__PURE__ */ jsxRuntime.jsx(DeclarativeMockHost, { ...declarativeProps }) });
151
+ }
152
+
71
153
  exports.DeclarativeMockHost = DeclarativeMockHost;
154
+ exports.InMemoryBridgeTransport = InMemoryBridgeTransport;
72
155
  exports.InMemoryMcpTransport = InMemoryMcpTransport;
156
+ exports.WorkerMockHost = WorkerMockHost;
73
157
  //# sourceMappingURL=index.cjs.map
74
158
  //# sourceMappingURL=index.cjs.map