@ethisyscore/extension-runtime 1.6.3 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/host/index.cjs +9 -1
- package/dist/host/index.cjs.map +1 -1
- package/dist/host/index.d.cts +1 -1
- package/dist/host/index.d.ts +1 -1
- package/dist/host/index.js +9 -1
- package/dist/host/index.js.map +1 -1
- package/dist/mock-host/cli.cjs +40 -3
- package/dist/mock-host/cli.cjs.map +1 -1
- package/dist/mock-host/cli.d.cts +20 -2
- package/dist/mock-host/cli.d.ts +20 -2
- package/dist/mock-host/cli.js +40 -4
- package/dist/mock-host/cli.js.map +1 -1
- package/dist/plugin/index.cjs +426 -0
- package/dist/plugin/index.cjs.map +1 -1
- package/dist/plugin/index.d.cts +281 -1
- package/dist/plugin/index.d.ts +281 -1
- package/dist/plugin/index.js +419 -1
- package/dist/plugin/index.js.map +1 -1
- package/package.json +3 -1
package/dist/host/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/host/declarative/evaluator.ts","../../src/host/declarative/interpreter.ts","../../src/host/worker/component-registry.ts","../../src/host/worker/offscreen.ts","../../src/host/worker/transport.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Pure JsonLogic evaluator for the curated 14-operator subset declared in\n * `@ethisyscore/protocol`'s {@link KNOWN_OPERATORS}.\n *\n * The evaluator implements **only** the closed vocabulary:\n *\n * - Comparison: `==`, `!=`, `>`, `<`, `>=`, `<=`\n * - Boolean: `and`, `or`, `not`\n * - Arithmetic: `+`, `-`, `*`, `/`, `%`\n * - Reference: `var` (dot-path resolution against the supplied context)\n *\n * Design constraints:\n *\n * - **Pure function.** No I/O, no async, no `eval`, no `Function` construction.\n * - **Closed vocabulary.** Operators outside {@link KNOWN_OPERATORS} cause an\n * immediate throw — there is no extension mechanism for plugin authors.\n * - **No function values.** Function-typed inputs are rejected at every depth.\n * This keeps expression trees serialisable and free of host-side side effects.\n * - **Fail-loud.** Malformed expressions (e.g. an operator object with more or\n * fewer than one key) throw rather than silently returning `undefined`.\n */\nimport { KNOWN_OPERATORS } from \"@ethisyscore/protocol\";\n\n/**\n * Resolution context for `var` references. Keys are looked up via dot-path\n * traversal; missing paths resolve to `undefined`.\n */\nexport type EvaluatorContext = Record<string, unknown>;\n\nconst KNOWN_OPERATOR_SET = new Set<string>(KNOWN_OPERATORS);\n\n/**\n * Evaluate a JsonLogic expression in the closed operator subset against the\n * supplied context.\n *\n * @param expr The expression tree. Scalars (`string | number | boolean | null`)\n * evaluate to themselves; arrays map element-wise; operator\n * objects must contain exactly one key from\n * {@link KNOWN_OPERATORS}.\n * @param context The context bag for `var` resolution. Dot-paths traverse\n * nested objects.\n *\n * @throws Error if `expr` contains an unknown operator, a function-typed value,\n * an operator object with a number of keys other than one, or any\n * other unsupported shape (e.g. `undefined`, `Symbol`, `bigint`).\n */\nexport function evaluate(expr: unknown, context: EvaluatorContext): unknown\n{\n // Literal scalars — return as-is.\n if (expr === null)\n {\n return null;\n }\n\n const t = typeof expr;\n if (t === \"string\" || t === \"number\" || t === \"boolean\")\n {\n return expr;\n }\n\n if (t === \"function\")\n {\n throw new Error(\"Function-typed values are not allowed in expressions.\");\n }\n\n if (Array.isArray(expr))\n {\n return expr.map((e) => evaluate(e, context));\n }\n\n if (t !== \"object\")\n {\n throw new Error(`Unsupported expression type: ${t}.`);\n }\n\n // Operator object — must have exactly one key from the closed vocabulary.\n const obj = expr as Record<string, unknown>;\n const keys = Object.keys(obj);\n if (keys.length !== 1)\n {\n throw new Error(\n `Expression object must have exactly one operator key (got ${keys.length}).`,\n );\n }\n\n const op = keys[0];\n if (!KNOWN_OPERATOR_SET.has(op))\n {\n throw new Error(`Unknown operator: ${op}.`);\n }\n\n const rawArgs = obj[op];\n\n // `var` takes a single dot-path argument and does not recurse over its operand.\n if (op === \"var\")\n {\n const path = typeof rawArgs === \"string\"\n ? rawArgs\n : Array.isArray(rawArgs) && typeof rawArgs[0] === \"string\"\n ? rawArgs[0]\n : \"\";\n return resolveVar(path, context);\n }\n\n const evaluatedArgs: unknown[] = Array.isArray(rawArgs)\n ? rawArgs.map((a) => evaluate(a, context))\n : [evaluate(rawArgs, context)];\n\n switch (op)\n {\n case \"==\": return evaluatedArgs[0] === evaluatedArgs[1];\n case \"!=\": return evaluatedArgs[0] !== evaluatedArgs[1];\n case \">\": return (evaluatedArgs[0] as number) > (evaluatedArgs[1] as number);\n case \"<\": return (evaluatedArgs[0] as number) < (evaluatedArgs[1] as number);\n case \">=\": return (evaluatedArgs[0] as number) >= (evaluatedArgs[1] as number);\n case \"<=\": return (evaluatedArgs[0] as number) <= (evaluatedArgs[1] as number);\n case \"and\": return evaluatedArgs.every(Boolean);\n case \"or\": return evaluatedArgs.some(Boolean);\n case \"not\": return !evaluatedArgs[0];\n case \"+\": return evaluatedArgs.reduce((acc: number, n) => acc + (n as number), 0);\n case \"-\": return (evaluatedArgs[0] as number) - (evaluatedArgs[1] as number);\n case \"*\": return evaluatedArgs.reduce((acc: number, n) => acc * (n as number), 1);\n case \"/\": return safeDivide(evaluatedArgs[0] as number, evaluatedArgs[1] as number);\n case \"%\": return safeModulo(evaluatedArgs[0] as number, evaluatedArgs[1] as number);\n default:\n // Unreachable: gated by KNOWN_OPERATOR_SET above. Kept for exhaustiveness.\n throw new Error(`Unknown operator: ${op}.`);\n }\n}\n\n/**\n * Throw on zero or non-finite denominators in `/`. JavaScript silently coerces\n * `1 / 0` to `Infinity`; that value then leaks into host-rendered props and\n * surfaces as weird UI (or React `<input value={Infinity} />` warnings). The\n * closed evaluator's fail-loud contract demands a real error so the host\n * surface mount can fall back to an `ErrorState`.\n */\nfunction safeDivide(a: number, b: number): number\n{\n if (b === 0 || !Number.isFinite(b))\n {\n throw new Error(\n `Division by ${b === 0 ? \"zero\" : String(b)} is not allowed in the closed evaluator.`,\n );\n }\n return a / b;\n}\n\n/**\n * Throw on zero or non-finite denominators in `%`. JavaScript silently coerces\n * `1 % 0` to `NaN`; that value would compare unequal to itself and corrupt\n * downstream comparisons in reactive rules.\n */\nfunction safeModulo(a: number, b: number): number\n{\n if (b === 0 || !Number.isFinite(b))\n {\n throw new Error(\n `Modulo by ${b === 0 ? \"zero\" : String(b)} is not allowed in the closed evaluator.`,\n );\n }\n return a % b;\n}\n\n/**\n * Resolve a dot-separated path against the supplied context, returning the\n * leaf value or `undefined` if any segment is missing or non-traversable.\n *\n * Empty paths resolve to `undefined`.\n */\nfunction resolveVar(path: string, context: EvaluatorContext): unknown\n{\n if (path === \"\")\n {\n return undefined;\n }\n\n const parts = path.split(\".\");\n let curr: unknown = context;\n for (const part of parts)\n {\n if (curr === null || typeof curr !== \"object\")\n {\n return undefined;\n }\n curr = (curr as Record<string, unknown>)[part];\n }\n return curr;\n}\n","import { createElement, type ReactElement, type ReactNode } from \"react\";\nimport type { ComponentRegistry } from \"./registry\";\nimport type { SduiNode } from \"./types\";\n\n/**\n * Walk a parsed SDUI tree and render it as a React element by looking up each\n * node's `type` in the supplied {@link ComponentRegistry}.\n *\n * The interpreter is purely structural:\n *\n * - It owns no UI styling, layout, or data fetching.\n * - It never reads `node.props` — props are forwarded opaquely to the host\n * component, which owns interpretation per primitive.\n * - It does not evaluate `node.bindings` — reactive rules are handled in a\n * separate task (E2.S2). For v1 the interpreter passes through the static\n * tree only.\n *\n * Each child is given a stable React `key` derived from its position so that\n * React's reconciler can identify list items across renders. The key is a\n * sibling-local index; the registry consumer is responsible for opting into a\n * stable identity if it has a domain-meaningful `props.key`.\n *\n * @throws Error when `node.type` is not present in the registry. This is the\n * fail-loud behaviour required by the closed v1 vocabulary — unknown\n * primitives must not silently degrade.\n */\nexport function interpret(node: SduiNode, registry: ComponentRegistry): ReactElement\n{\n const Component = registry[node.type];\n if (!Component)\n {\n throw new Error(`Unknown SDUI primitive: ${String(node.type)}`);\n }\n\n const children: ReactNode[] | undefined = node.children?.map(\n (child, index) => interpretChild(child, registry, index),\n );\n\n return createElement(Component, { props: node.props }, children);\n}\n\nfunction interpretChild(node: SduiNode, registry: ComponentRegistry, index: number): ReactElement\n{\n const Component = registry[node.type];\n if (!Component)\n {\n throw new Error(`Unknown SDUI primitive: ${String(node.type)}`);\n }\n\n const children: ReactNode[] | undefined = node.children?.map(\n (child, childIndex) => interpretChild(child, registry, childIndex),\n );\n\n return createElement(Component, { props: node.props, key: index }, children);\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] 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 * 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"]}
|
|
1
|
+
{"version":3,"sources":["../../src/host/declarative/evaluator.ts","../../src/host/declarative/interpreter.ts","../../src/host/worker/component-registry.ts","../../src/host/worker/offscreen.ts","../../src/host/worker/transport.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Pure JsonLogic evaluator for the curated 14-operator subset declared in\n * `@ethisyscore/protocol`'s {@link KNOWN_OPERATORS}.\n *\n * The evaluator implements **only** the closed vocabulary:\n *\n * - Comparison: `==`, `!=`, `>`, `<`, `>=`, `<=`\n * - Boolean: `and`, `or`, `not`\n * - Arithmetic: `+`, `-`, `*`, `/`, `%`\n * - Reference: `var` (dot-path resolution against the supplied context)\n *\n * Design constraints:\n *\n * - **Pure function.** No I/O, no async, no `eval`, no `Function` construction.\n * - **Closed vocabulary.** Operators outside {@link KNOWN_OPERATORS} cause an\n * immediate throw — there is no extension mechanism for plugin authors.\n * - **No function values.** Function-typed inputs are rejected at every depth.\n * This keeps expression trees serialisable and free of host-side side effects.\n * - **Fail-loud.** Malformed expressions (e.g. an operator object with more or\n * fewer than one key) throw rather than silently returning `undefined`.\n */\nimport { KNOWN_OPERATORS } from \"@ethisyscore/protocol\";\n\n/**\n * Resolution context for `var` references. Keys are looked up via dot-path\n * traversal; missing paths resolve to `undefined`.\n */\nexport type EvaluatorContext = Record<string, unknown>;\n\nconst KNOWN_OPERATOR_SET = new Set<string>(KNOWN_OPERATORS);\n\n/**\n * Evaluate a JsonLogic expression in the closed operator subset against the\n * supplied context.\n *\n * @param expr The expression tree. Scalars (`string | number | boolean | null`)\n * evaluate to themselves; arrays map element-wise; operator\n * objects must contain exactly one key from\n * {@link KNOWN_OPERATORS}.\n * @param context The context bag for `var` resolution. Dot-paths traverse\n * nested objects.\n *\n * @throws Error if `expr` contains an unknown operator, a function-typed value,\n * an operator object with a number of keys other than one, or any\n * other unsupported shape (e.g. `undefined`, `Symbol`, `bigint`).\n */\nexport function evaluate(expr: unknown, context: EvaluatorContext): unknown\n{\n // Literal scalars — return as-is.\n if (expr === null)\n {\n return null;\n }\n\n const t = typeof expr;\n if (t === \"string\" || t === \"number\" || t === \"boolean\")\n {\n return expr;\n }\n\n if (t === \"function\")\n {\n throw new Error(\"Function-typed values are not allowed in expressions.\");\n }\n\n if (Array.isArray(expr))\n {\n return expr.map((e) => evaluate(e, context));\n }\n\n if (t !== \"object\")\n {\n throw new Error(`Unsupported expression type: ${t}.`);\n }\n\n // Operator object — must have exactly one key from the closed vocabulary.\n const obj = expr as Record<string, unknown>;\n const keys = Object.keys(obj);\n if (keys.length !== 1)\n {\n throw new Error(\n `Expression object must have exactly one operator key (got ${keys.length}).`,\n );\n }\n\n const op = keys[0];\n if (!KNOWN_OPERATOR_SET.has(op))\n {\n throw new Error(`Unknown operator: ${op}.`);\n }\n\n const rawArgs = obj[op];\n\n // `var` takes a single dot-path argument and does not recurse over its operand.\n if (op === \"var\")\n {\n const path = typeof rawArgs === \"string\"\n ? rawArgs\n : Array.isArray(rawArgs) && typeof rawArgs[0] === \"string\"\n ? rawArgs[0]\n : \"\";\n return resolveVar(path, context);\n }\n\n const evaluatedArgs: unknown[] = Array.isArray(rawArgs)\n ? rawArgs.map((a) => evaluate(a, context))\n : [evaluate(rawArgs, context)];\n\n switch (op)\n {\n case \"==\": return evaluatedArgs[0] === evaluatedArgs[1];\n case \"!=\": return evaluatedArgs[0] !== evaluatedArgs[1];\n case \">\": return (evaluatedArgs[0] as number) > (evaluatedArgs[1] as number);\n case \"<\": return (evaluatedArgs[0] as number) < (evaluatedArgs[1] as number);\n case \">=\": return (evaluatedArgs[0] as number) >= (evaluatedArgs[1] as number);\n case \"<=\": return (evaluatedArgs[0] as number) <= (evaluatedArgs[1] as number);\n case \"and\": return evaluatedArgs.every(Boolean);\n case \"or\": return evaluatedArgs.some(Boolean);\n case \"not\": return !evaluatedArgs[0];\n case \"+\": return evaluatedArgs.reduce((acc: number, n) => acc + (n as number), 0);\n case \"-\": return (evaluatedArgs[0] as number) - (evaluatedArgs[1] as number);\n case \"*\": return evaluatedArgs.reduce((acc: number, n) => acc * (n as number), 1);\n case \"/\": return safeDivide(evaluatedArgs[0] as number, evaluatedArgs[1] as number);\n case \"%\": return safeModulo(evaluatedArgs[0] as number, evaluatedArgs[1] as number);\n default:\n // Unreachable: gated by KNOWN_OPERATOR_SET above. Kept for exhaustiveness.\n throw new Error(`Unknown operator: ${op}.`);\n }\n}\n\n/**\n * Throw on zero or non-finite denominators in `/`. JavaScript silently coerces\n * `1 / 0` to `Infinity`; that value then leaks into host-rendered props and\n * surfaces as weird UI (or React `<input value={Infinity} />` warnings). The\n * closed evaluator's fail-loud contract demands a real error so the host\n * surface mount can fall back to an `ErrorState`.\n */\nfunction safeDivide(a: number, b: number): number\n{\n if (b === 0 || !Number.isFinite(b))\n {\n throw new Error(\n `Division by ${b === 0 ? \"zero\" : String(b)} is not allowed in the closed evaluator.`,\n );\n }\n return a / b;\n}\n\n/**\n * Throw on zero or non-finite denominators in `%`. JavaScript silently coerces\n * `1 % 0` to `NaN`; that value would compare unequal to itself and corrupt\n * downstream comparisons in reactive rules.\n */\nfunction safeModulo(a: number, b: number): number\n{\n if (b === 0 || !Number.isFinite(b))\n {\n throw new Error(\n `Modulo by ${b === 0 ? \"zero\" : String(b)} is not allowed in the closed evaluator.`,\n );\n }\n return a % b;\n}\n\n/**\n * Resolve a dot-separated path against the supplied context, returning the\n * leaf value or `undefined` if any segment is missing or non-traversable.\n *\n * Empty paths resolve to `undefined`.\n */\nfunction resolveVar(path: string, context: EvaluatorContext): unknown\n{\n if (path === \"\")\n {\n return undefined;\n }\n\n const parts = path.split(\".\");\n let curr: unknown = context;\n for (const part of parts)\n {\n if (curr === null || typeof curr !== \"object\")\n {\n return undefined;\n }\n curr = (curr as Record<string, unknown>)[part];\n }\n return curr;\n}\n","import { createElement, type ReactElement, type ReactNode } from \"react\";\nimport type { ComponentRegistry } from \"./registry\";\nimport type { SduiNode } from \"./types\";\n\n/**\n * Walk a parsed SDUI tree and render it as a React element by looking up each\n * node's `type` in the supplied {@link ComponentRegistry}.\n *\n * The interpreter is purely structural:\n *\n * - It owns no UI styling, layout, or data fetching.\n * - It never reads `node.props` — props are forwarded opaquely to the host\n * component, which owns interpretation per primitive.\n * - It does not evaluate `node.bindings` — reactive rules are handled in a\n * separate task (E2.S2). For v1 the interpreter passes through the static\n * tree only.\n *\n * Each child is given a stable React `key` derived from its position so that\n * React's reconciler can identify list items across renders. The key is a\n * sibling-local index; the registry consumer is responsible for opting into a\n * stable identity if it has a domain-meaningful `props.key`.\n *\n * @throws Error when `node.type` is not present in the registry. This is the\n * fail-loud behaviour required by the closed v1 vocabulary — unknown\n * primitives must not silently degrade.\n */\nexport function interpret(node: SduiNode, registry: ComponentRegistry): ReactElement\n{\n const Component = registry[node.type];\n if (!Component)\n {\n throw new Error(`Unknown SDUI primitive: ${String(node.type)}`);\n }\n\n const children: ReactNode[] | undefined = node.children?.map(\n (child, index) => interpretChild(child, registry, index),\n );\n\n return createElement(Component, { props: node.props }, children);\n}\n\nfunction interpretChild(node: SduiNode, registry: ComponentRegistry, index: number): ReactElement\n{\n const Component = registry[node.type];\n if (!Component)\n {\n throw new Error(`Unknown SDUI primitive: ${String(node.type)}`);\n }\n\n const children: ReactNode[] | undefined = node.children?.map(\n (child, childIndex) => interpretChild(child, registry, childIndex),\n );\n\n return createElement(Component, { props: node.props, key: index }, children);\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 * 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"]}
|
package/dist/mock-host/cli.cjs
CHANGED
|
@@ -360,7 +360,15 @@ var CONTRACT_B_PRIMITIVES = [
|
|
|
360
360
|
"Drawer",
|
|
361
361
|
"Modal",
|
|
362
362
|
"CanvasSurface",
|
|
363
|
-
"WebGLSurface"
|
|
363
|
+
"WebGLSurface",
|
|
364
|
+
// Phase 1 additions — mirror the vite-plugin's CONTRACT_B_SEMANTIC_PRIMITIVES
|
|
365
|
+
// list verbatim; drift between the two is caught by the
|
|
366
|
+
// contract-b.test.ts "import-map-allowlist matches extension-runtime
|
|
367
|
+
// CONTRACT_B_PRIMITIVES" test.
|
|
368
|
+
"Card",
|
|
369
|
+
"Tabs",
|
|
370
|
+
"Select",
|
|
371
|
+
"Alert"
|
|
364
372
|
];
|
|
365
373
|
new Set(CONTRACT_B_PRIMITIVES);
|
|
366
374
|
|
|
@@ -392,6 +400,23 @@ async function loadResources(dir) {
|
|
|
392
400
|
}
|
|
393
401
|
return out;
|
|
394
402
|
}
|
|
403
|
+
async function loadTools(dir) {
|
|
404
|
+
const toolsPath = path__namespace.resolve(dir, "tools.json");
|
|
405
|
+
try {
|
|
406
|
+
const raw = await fs.promises.readFile(toolsPath, "utf8");
|
|
407
|
+
const parsed = JSON.parse(raw);
|
|
408
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
409
|
+
throw new Error(`Expected an object map, got ${typeof parsed}`);
|
|
410
|
+
}
|
|
411
|
+
return parsed;
|
|
412
|
+
} catch (e) {
|
|
413
|
+
const err = e;
|
|
414
|
+
if (err.code === "ENOENT") {
|
|
415
|
+
return {};
|
|
416
|
+
}
|
|
417
|
+
throw new Error(`Failed to load tools.json from ${toolsPath}: ${err.message}`);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
395
420
|
function normaliseEntry(parsed, absFile, absDir) {
|
|
396
421
|
if (parsed === null || typeof parsed !== "object") {
|
|
397
422
|
throw new Error("Resource JSON must be an object");
|
|
@@ -474,8 +499,9 @@ function escapeHtml(s) {
|
|
|
474
499
|
function safeJsonForScript(v) {
|
|
475
500
|
return JSON.stringify(v).replace(/</g, "\\u003c").replace(/>/g, "\\u003e").replace(/&/g, "\\u0026");
|
|
476
501
|
}
|
|
477
|
-
function renderEntryModule(resources) {
|
|
502
|
+
function renderEntryModule(resources, tools = {}) {
|
|
478
503
|
const json = JSON.stringify(resources);
|
|
504
|
+
const toolsJson = JSON.stringify(tools);
|
|
479
505
|
const primitives = JSON.stringify([...protocol.KNOWN_PRIMITIVES]);
|
|
480
506
|
return `
|
|
481
507
|
import { createRoot } from "react-dom/client";
|
|
@@ -490,6 +516,14 @@ const registry = Object.fromEntries(PRIMITIVES.map(name => [
|
|
|
490
516
|
]));
|
|
491
517
|
|
|
492
518
|
const resources = ${json};
|
|
519
|
+
const toolResults = ${toolsJson};
|
|
520
|
+
// Each canned tool result is wrapped in a sync handler. Authors that need
|
|
521
|
+
// per-call behaviour drop tools.json and hand-roll a Vite app that mounts
|
|
522
|
+
// DeclarativeMockHost with a typed tools object directly.
|
|
523
|
+
const tools = Object.fromEntries(Object.entries(toolResults).map(([name, value]) => [
|
|
524
|
+
name,
|
|
525
|
+
() => value,
|
|
526
|
+
]));
|
|
493
527
|
const uris = Object.keys(resources);
|
|
494
528
|
|
|
495
529
|
function App() {
|
|
@@ -503,6 +537,7 @@ function App() {
|
|
|
503
537
|
}
|
|
504
538
|
return createElement(DeclarativeMockHost, {
|
|
505
539
|
resources,
|
|
540
|
+
tools,
|
|
506
541
|
registry,
|
|
507
542
|
defaultResourceUri: uri,
|
|
508
543
|
});
|
|
@@ -725,6 +760,7 @@ async function run(args) {
|
|
|
725
760
|
}
|
|
726
761
|
const dir = parsed.path;
|
|
727
762
|
const resources = await loadResources(dir);
|
|
763
|
+
const tools = await loadTools(dir);
|
|
728
764
|
const uris = Object.keys(resources).sort();
|
|
729
765
|
if (uris.length === 0) {
|
|
730
766
|
process.stderr.write(`mock-host: no resources found in ${dir} (HMR will pick up new files)
|
|
@@ -739,7 +775,7 @@ async function run(args) {
|
|
|
739
775
|
);
|
|
740
776
|
}
|
|
741
777
|
const indexHtml = renderIndexHtml(uris);
|
|
742
|
-
const entryModule = renderEntryModule(resources);
|
|
778
|
+
const entryModule = renderEntryModule(resources, tools);
|
|
743
779
|
const absDir = path__namespace.resolve(dir);
|
|
744
780
|
const server = await viteMod.createServer({
|
|
745
781
|
root: process.cwd(),
|
|
@@ -789,6 +825,7 @@ async function run(args) {
|
|
|
789
825
|
|
|
790
826
|
exports.bootContractBHost = bootContractBHost;
|
|
791
827
|
exports.loadResources = loadResources;
|
|
828
|
+
exports.loadTools = loadTools;
|
|
792
829
|
exports.normaliseEntry = normaliseEntry;
|
|
793
830
|
exports.parseArgs = parseArgs;
|
|
794
831
|
exports.renderEntryModule = renderEntryModule;
|