@ethisyscore/extension-runtime 1.6.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.
Files changed (39) hide show
  1. package/README.md +42 -0
  2. package/bin/mock-host.cjs +14 -0
  3. package/dist/host/index.cjs +600 -0
  4. package/dist/host/index.cjs.map +1 -0
  5. package/dist/host/index.d.cts +260 -0
  6. package/dist/host/index.d.ts +260 -0
  7. package/dist/host/index.js +577 -0
  8. package/dist/host/index.js.map +1 -0
  9. package/dist/index.cjs +16 -0
  10. package/dist/index.cjs.map +1 -0
  11. package/dist/index.d.cts +6 -0
  12. package/dist/index.d.ts +6 -0
  13. package/dist/index.js +10 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/mock-host/cli.cjs +800 -0
  16. package/dist/mock-host/cli.cjs.map +1 -0
  17. package/dist/mock-host/cli.d.cts +155 -0
  18. package/dist/mock-host/cli.d.ts +155 -0
  19. package/dist/mock-host/cli.js +770 -0
  20. package/dist/mock-host/cli.js.map +1 -0
  21. package/dist/mock-host/index.cjs +74 -0
  22. package/dist/mock-host/index.cjs.map +1 -0
  23. package/dist/mock-host/index.d.cts +95 -0
  24. package/dist/mock-host/index.d.ts +95 -0
  25. package/dist/mock-host/index.js +71 -0
  26. package/dist/mock-host/index.js.map +1 -0
  27. package/dist/plugin/index.cjs +113 -0
  28. package/dist/plugin/index.cjs.map +1 -0
  29. package/dist/plugin/index.d.cts +120 -0
  30. package/dist/plugin/index.d.ts +120 -0
  31. package/dist/plugin/index.js +107 -0
  32. package/dist/plugin/index.js.map +1 -0
  33. package/dist/registry-DpCx_LxF.d.cts +25 -0
  34. package/dist/registry-DpCx_LxF.d.ts +25 -0
  35. package/dist/transport-73otePiw.d.cts +307 -0
  36. package/dist/transport-73otePiw.d.ts +307 -0
  37. package/dist/transport-DVn2GVZh.d.cts +32 -0
  38. package/dist/transport-DVn2GVZh.d.ts +32 -0
  39. package/package.json +78 -0
@@ -0,0 +1,577 @@
1
+ import { KNOWN_OPERATORS } from '@ethisyscore/protocol';
2
+ import { createElement } from 'react';
3
+ export { RemoteReceiver } from '@remote-dom/core/receivers';
4
+ export { RemoteRootRenderer, createRemoteComponentRenderer } from '@remote-dom/react/host';
5
+
6
+ // src/host/declarative/evaluator.ts
7
+ var KNOWN_OPERATOR_SET = new Set(KNOWN_OPERATORS);
8
+ function evaluate(expr, context) {
9
+ if (expr === null) {
10
+ return null;
11
+ }
12
+ const t = typeof expr;
13
+ if (t === "string" || t === "number" || t === "boolean") {
14
+ return expr;
15
+ }
16
+ if (t === "function") {
17
+ throw new Error("Function-typed values are not allowed in expressions.");
18
+ }
19
+ if (Array.isArray(expr)) {
20
+ return expr.map((e) => evaluate(e, context));
21
+ }
22
+ if (t !== "object") {
23
+ throw new Error(`Unsupported expression type: ${t}.`);
24
+ }
25
+ const obj = expr;
26
+ const keys = Object.keys(obj);
27
+ if (keys.length !== 1) {
28
+ throw new Error(
29
+ `Expression object must have exactly one operator key (got ${keys.length}).`
30
+ );
31
+ }
32
+ const op = keys[0];
33
+ if (!KNOWN_OPERATOR_SET.has(op)) {
34
+ throw new Error(`Unknown operator: ${op}.`);
35
+ }
36
+ const rawArgs = obj[op];
37
+ if (op === "var") {
38
+ const path = typeof rawArgs === "string" ? rawArgs : Array.isArray(rawArgs) && typeof rawArgs[0] === "string" ? rawArgs[0] : "";
39
+ return resolveVar(path, context);
40
+ }
41
+ const evaluatedArgs = Array.isArray(rawArgs) ? rawArgs.map((a) => evaluate(a, context)) : [evaluate(rawArgs, context)];
42
+ switch (op) {
43
+ case "==":
44
+ return evaluatedArgs[0] === evaluatedArgs[1];
45
+ case "!=":
46
+ return evaluatedArgs[0] !== evaluatedArgs[1];
47
+ case ">":
48
+ return evaluatedArgs[0] > evaluatedArgs[1];
49
+ case "<":
50
+ return evaluatedArgs[0] < evaluatedArgs[1];
51
+ case ">=":
52
+ return evaluatedArgs[0] >= evaluatedArgs[1];
53
+ case "<=":
54
+ return evaluatedArgs[0] <= evaluatedArgs[1];
55
+ case "and":
56
+ return evaluatedArgs.every(Boolean);
57
+ case "or":
58
+ return evaluatedArgs.some(Boolean);
59
+ case "not":
60
+ return !evaluatedArgs[0];
61
+ case "+":
62
+ return evaluatedArgs.reduce((acc, n) => acc + n, 0);
63
+ case "-":
64
+ return evaluatedArgs[0] - evaluatedArgs[1];
65
+ case "*":
66
+ return evaluatedArgs.reduce((acc, n) => acc * n, 1);
67
+ case "/":
68
+ return safeDivide(evaluatedArgs[0], evaluatedArgs[1]);
69
+ case "%":
70
+ return safeModulo(evaluatedArgs[0], evaluatedArgs[1]);
71
+ default:
72
+ throw new Error(`Unknown operator: ${op}.`);
73
+ }
74
+ }
75
+ function safeDivide(a, b) {
76
+ if (b === 0 || !Number.isFinite(b)) {
77
+ throw new Error(
78
+ `Division by ${b === 0 ? "zero" : String(b)} is not allowed in the closed evaluator.`
79
+ );
80
+ }
81
+ return a / b;
82
+ }
83
+ function safeModulo(a, b) {
84
+ if (b === 0 || !Number.isFinite(b)) {
85
+ throw new Error(
86
+ `Modulo by ${b === 0 ? "zero" : String(b)} is not allowed in the closed evaluator.`
87
+ );
88
+ }
89
+ return a % b;
90
+ }
91
+ function resolveVar(path, context) {
92
+ if (path === "") {
93
+ return void 0;
94
+ }
95
+ const parts = path.split(".");
96
+ let curr = context;
97
+ for (const part of parts) {
98
+ if (curr === null || typeof curr !== "object") {
99
+ return void 0;
100
+ }
101
+ curr = curr[part];
102
+ }
103
+ return curr;
104
+ }
105
+ function interpret(node, registry) {
106
+ const Component = registry[node.type];
107
+ if (!Component) {
108
+ throw new Error(`Unknown SDUI primitive: ${String(node.type)}`);
109
+ }
110
+ const children = node.children?.map(
111
+ (child, index) => interpretChild(child, registry, index)
112
+ );
113
+ return createElement(Component, { props: node.props }, children);
114
+ }
115
+ function interpretChild(node, registry, index) {
116
+ const Component = registry[node.type];
117
+ if (!Component) {
118
+ throw new Error(`Unknown SDUI primitive: ${String(node.type)}`);
119
+ }
120
+ const children = node.children?.map(
121
+ (child, childIndex) => interpretChild(child, registry, childIndex)
122
+ );
123
+ return createElement(Component, { props: node.props, key: index }, children);
124
+ }
125
+
126
+ // src/host/worker/component-registry.ts
127
+ var CONTRACT_B_PRIMITIVES = [
128
+ "Button",
129
+ "DataTable",
130
+ "Form",
131
+ "EntityPicker",
132
+ "CommandBar",
133
+ "Drawer",
134
+ "Modal",
135
+ "CanvasSurface",
136
+ "WebGLSurface"
137
+ ];
138
+ var KNOWN_PRIMITIVE_SET = new Set(CONTRACT_B_PRIMITIVES);
139
+ var SemanticComponentRegistry = class _SemanticComponentRegistry {
140
+ entries = /* @__PURE__ */ new Map();
141
+ /**
142
+ * Register a primitive → component binding.
143
+ *
144
+ * Throws if `name` is not in {@link CONTRACT_B_PRIMITIVES} (drift prevention)
145
+ * or if `name` is already registered (silent-override prevention).
146
+ */
147
+ register(name, component) {
148
+ if (!KNOWN_PRIMITIVE_SET.has(name)) {
149
+ throw new Error(
150
+ `[component-registry] unknown semantic primitive '${name}'. Allowed: ${CONTRACT_B_PRIMITIVES.join(", ")}.`
151
+ );
152
+ }
153
+ if (this.entries.has(name)) {
154
+ throw new Error(
155
+ `[component-registry] primitive '${name}' is already registered. Construct a new registry instead of overriding a binding.`
156
+ );
157
+ }
158
+ this.entries.set(name, component);
159
+ }
160
+ /**
161
+ * Resolve a primitive name to its concrete component. Throws if the name is
162
+ * outside the Contract B vocabulary OR if the registry has no binding —
163
+ * silent fallthrough would let typos render blank components, which is
164
+ * worse than a fast failure for plugin authors.
165
+ */
166
+ resolve(name) {
167
+ if (!KNOWN_PRIMITIVE_SET.has(name)) {
168
+ throw new Error(
169
+ `[component-registry] unknown semantic primitive '${name}'. Allowed: ${CONTRACT_B_PRIMITIVES.join(", ")}.`
170
+ );
171
+ }
172
+ const found = this.entries.get(name);
173
+ if (found === void 0) {
174
+ const registered = this.listRegistered().join(", ") || "<none>";
175
+ throw new Error(
176
+ `[component-registry] primitive '${name}' is not registered. Registered primitives: ${registered}.`
177
+ );
178
+ }
179
+ return found;
180
+ }
181
+ /**
182
+ * Cheap presence probe — never throws. Use this when callers want to
183
+ * fall back to a default component instead of erroring.
184
+ */
185
+ has(name) {
186
+ return this.entries.has(name);
187
+ }
188
+ /**
189
+ * Sorted list of registered primitive names. Stable output makes registry
190
+ * diagnostics and snapshot tests deterministic.
191
+ */
192
+ listRegistered() {
193
+ return [...this.entries.keys()].sort();
194
+ }
195
+ /**
196
+ * Build a registry from a complete `Record<SemanticPrimitiveName, T>` map.
197
+ *
198
+ * The `Record` type forces TypeScript to refuse incomplete literals at
199
+ * compile time, AND we re-check at runtime so callers who type-erase the
200
+ * record (e.g., via `as any`) still get a fast failure.
201
+ */
202
+ static fromMap(map) {
203
+ const registry = new _SemanticComponentRegistry();
204
+ const missing = [];
205
+ for (const name of CONTRACT_B_PRIMITIVES) {
206
+ const component = map[name];
207
+ if (component === void 0) {
208
+ missing.push(name);
209
+ continue;
210
+ }
211
+ registry.register(name, component);
212
+ }
213
+ if (missing.length > 0) {
214
+ throw new Error(
215
+ `[component-registry] fromMap is missing semantic primitive(s): ${missing.join(", ")}.`
216
+ );
217
+ }
218
+ return registry;
219
+ }
220
+ };
221
+
222
+ // src/host/worker/offscreen.ts
223
+ var DEFAULT_OFFSCREEN_COALESCE_MS = 16;
224
+ var transferredCanvases = /* @__PURE__ */ new WeakSet();
225
+ function createOffscreenCanvasTransfer(options) {
226
+ const { canvas, surfaceId, postMessage } = options;
227
+ const proto = HTMLCanvasElement.prototype;
228
+ if (typeof proto.transferControlToOffscreen !== "function") {
229
+ throw new Error(
230
+ "[offscreen-canvas] OffscreenCanvas is not supported in this environment. Contract B canvas surfaces require a browser with HTMLCanvasElement.transferControlToOffscreen."
231
+ );
232
+ }
233
+ if (transferredCanvases.has(canvas)) {
234
+ throw new Error(
235
+ `[offscreen-canvas] canvas already transferred (surfaceId='${surfaceId}'). Create a fresh <canvas> for each surface instead of re-transferring.`
236
+ );
237
+ }
238
+ const offscreen = canvas.transferControlToOffscreen();
239
+ transferredCanvases.add(canvas);
240
+ const envelope = {
241
+ type: "ethisys:offscreen:transfer",
242
+ surfaceId,
243
+ width: canvas.width,
244
+ height: canvas.height,
245
+ offscreen
246
+ };
247
+ postMessage(envelope, [offscreen]);
248
+ return { offscreen };
249
+ }
250
+ function createInputEventCoalescer(sink, options = {}) {
251
+ if (options.discrete === true) {
252
+ return (payload) => {
253
+ sink(payload);
254
+ };
255
+ }
256
+ const coalesceMs = options.coalesceMs ?? DEFAULT_OFFSCREEN_COALESCE_MS;
257
+ let pending;
258
+ let pendingSet = false;
259
+ let timer;
260
+ const flush = () => {
261
+ timer = void 0;
262
+ if (!pendingSet) {
263
+ return;
264
+ }
265
+ const value = pending;
266
+ pending = void 0;
267
+ pendingSet = false;
268
+ sink(value);
269
+ };
270
+ return (payload) => {
271
+ pending = payload;
272
+ pendingSet = true;
273
+ if (timer === void 0) {
274
+ timer = setTimeout(flush, coalesceMs);
275
+ }
276
+ };
277
+ }
278
+
279
+ // src/host/worker/transport.ts
280
+ var WORKER_TRANSPORT_PROTOCOL = "ethisys.worker.remotedom.v1";
281
+ var DEFAULT_MAX_CONCURRENT_MCP_REQUESTS = 8;
282
+ var WorkerRemoteDomTransport = class {
283
+ worker;
284
+ hostPort;
285
+ workerPort;
286
+ mcpClient;
287
+ capabilityTokenProvider;
288
+ coalesceMs;
289
+ maxConcurrentMcpRequests;
290
+ abortController;
291
+ inFlightMcpRequests = 0;
292
+ remoteDomConsumer;
293
+ disposed = false;
294
+ connected = false;
295
+ constructor(options) {
296
+ const WorkerCtor = options.workerCtor ?? globalThis.Worker;
297
+ if (!WorkerCtor) {
298
+ throw new Error("WorkerRemoteDomTransport: no Worker constructor available");
299
+ }
300
+ this.mcpClient = options.mcpClient;
301
+ this.capabilityTokenProvider = options.capabilityToken;
302
+ this.coalesceMs = options.coalesceMs ?? 16;
303
+ this.maxConcurrentMcpRequests = Math.max(
304
+ 1,
305
+ options.maxConcurrentMcpRequests ?? DEFAULT_MAX_CONCURRENT_MCP_REQUESTS
306
+ );
307
+ this.abortController = new AbortController();
308
+ this.worker = new WorkerCtor(options.bootstrapUrl, { type: "module" });
309
+ const channel = new MessageChannel();
310
+ this.hostPort = channel.port1;
311
+ this.workerPort = channel.port2;
312
+ this.hostPort.onmessage = (ev) => this.handlePortMessage(ev.data);
313
+ this.hostPort.start();
314
+ this.worker.onerror = (ev) => {
315
+ };
316
+ }
317
+ /**
318
+ * Post the handshake to the worker. Idempotent — only the FIRST call
319
+ * transfers the {@link MessagePort} and posts the handshake payload;
320
+ * subsequent calls are silent no-ops. Splitting this off from the
321
+ * constructor lets the host mount fetch the per-plugin
322
+ * `worker-bundle.import-map.json` (and resolve the bundle's module URL)
323
+ * before the bootstrap script consumes them.
324
+ *
325
+ * Wire shape: {@link WorkerHandshakePayload}. The capability token NEVER
326
+ * appears in the payload — it's bound on the host side via the
327
+ * {@link WorkerRemoteDomTransportOptions.capabilityToken} provider.
328
+ *
329
+ * The `moduleUrl` and `importMap` are forwarded to the worker so the
330
+ * bootstrap script (served from the host-pinned
331
+ * `/extensions/runtime/worker-bootstrap.js`) can:
332
+ * 1. Compose the frozen, same-origin-validated `IMPORT_MAP` from the
333
+ * handshake payload (rejecting any cross-origin entries).
334
+ * 2. `safeImport(moduleUrl)` the plugin's entry module.
335
+ *
336
+ * Same-origin enforcement is the bootstrap's job — this transport is
337
+ * structurally agnostic to the host origin and only forwards what it's
338
+ * handed.
339
+ */
340
+ connect(moduleUrl, importMap) {
341
+ if (this.connected) {
342
+ return;
343
+ }
344
+ this.connected = true;
345
+ const handshake = Object.freeze({
346
+ type: "ethisys:worker:handshake",
347
+ protocol: WORKER_TRANSPORT_PROTOCOL,
348
+ moduleUrl,
349
+ importMap: Object.freeze({ ...importMap })
350
+ });
351
+ this.worker.postMessage(handshake, [this.workerPort]);
352
+ }
353
+ /**
354
+ * Bind a consumer for Remote DOM mutation payloads emitted by the worker.
355
+ * The Remote DOM receiver wiring is owned by the caller (typically a host
356
+ * React component); this method exposes the raw stream so the receiver
357
+ * can integrate cleanly without a circular package import.
358
+ */
359
+ onRemoteDom(consumer) {
360
+ this.remoteDomConsumer = consumer;
361
+ }
362
+ /**
363
+ * Transfer control of a host-owned `<canvas>` to the worker so plugin code
364
+ * can render via `OffscreenCanvas`. The host retains the `<canvas>` for
365
+ * layout / accessibility purposes only — every pixel is produced inside the
366
+ * worker, so the main thread is never blocked per frame.
367
+ *
368
+ * The transfer rides the established MessagePort (not the worker global
369
+ * `postMessage`) so it is multiplexed with the rest of the host ↔ worker
370
+ * traffic on the same channel. The `OffscreenCanvas` handle is the sole
371
+ * `Transferable` in the envelope; no capability token, MCP context, or
372
+ * other host-only data crosses the boundary.
373
+ *
374
+ * Throws if the environment lacks `transferControlToOffscreen()` or if the
375
+ * canvas has already been transferred — see {@link createOffscreenCanvasTransfer}
376
+ * for the exact diagnostics.
377
+ */
378
+ transferCanvas(canvas, options) {
379
+ return createOffscreenCanvasTransfer({
380
+ canvas,
381
+ surfaceId: options.surfaceId,
382
+ postMessage: (message, transfer) => {
383
+ this.hostPort.postMessage(message, transfer);
384
+ }
385
+ });
386
+ }
387
+ /**
388
+ * Forward a host-sourced input event to the worker over the port. The
389
+ * payload shape is opaque to the transport — pointer / wheel / keyboard
390
+ * envelopes share the same wire type. Callers are expected to wrap
391
+ * high-frequency (pointer-move, wheel, scroll) events in
392
+ * {@link createCoalescer} or {@link createInputEventCoalescer} BEFORE
393
+ * calling this method so the port never sees the un-coalesced flood.
394
+ *
395
+ * Keyboard / pointer-down / pointer-up events are discrete and should be
396
+ * delivered without coalescing — pass them straight through.
397
+ */
398
+ postInputEvent(payload) {
399
+ this.hostPort.postMessage({
400
+ type: "ethisys:input:event",
401
+ payload
402
+ });
403
+ }
404
+ /**
405
+ * Build a trailing-edge coalescer keyed to {@link WorkerRemoteDomTransportOptions.coalesceMs}.
406
+ *
407
+ * Use it to wrap pointer-move / scroll / resize callbacks **before**
408
+ * they cross the port. The last payload in any coalescing window is the
409
+ * one that wins.
410
+ */
411
+ createCoalescer(consumer) {
412
+ let pending;
413
+ let timer;
414
+ const flush = () => {
415
+ timer = void 0;
416
+ const value = pending;
417
+ pending = void 0;
418
+ if (value !== void 0) {
419
+ consumer(value);
420
+ }
421
+ };
422
+ return (payload) => {
423
+ pending = payload;
424
+ if (timer === void 0) {
425
+ timer = setTimeout(flush, this.coalesceMs);
426
+ }
427
+ };
428
+ }
429
+ /**
430
+ * Tear down the worker and port. Idempotent.
431
+ *
432
+ * Order matters: we abort BEFORE closing the port so any in-flight
433
+ * `mcpClient.fetch` that observes the signal short-circuits and the
434
+ * subsequent attempt to post a reply lands in the disposed-guard branch
435
+ * (which silently drops the post) instead of throwing on a closed port.
436
+ */
437
+ dispose() {
438
+ if (this.disposed) {
439
+ return;
440
+ }
441
+ this.disposed = true;
442
+ try {
443
+ this.abortController.abort();
444
+ } catch {
445
+ }
446
+ try {
447
+ this.hostPort.close();
448
+ } catch {
449
+ }
450
+ try {
451
+ this.worker.terminate();
452
+ } catch {
453
+ }
454
+ }
455
+ /**
456
+ * Best-effort wrapper around `hostPort.postMessage` that tolerates posts
457
+ * arriving after {@link dispose}. The browser throws on a closed port and
458
+ * the async handlers below can race dispose, so any post initiated by an
459
+ * awaited continuation must be guarded.
460
+ */
461
+ safePostMessage(message) {
462
+ if (this.disposed) {
463
+ return;
464
+ }
465
+ try {
466
+ this.hostPort.postMessage(message);
467
+ } catch {
468
+ }
469
+ }
470
+ // ─── Port message handling ──────────────────────────────────────────────
471
+ handlePortMessage(message) {
472
+ if (this.disposed) {
473
+ return;
474
+ }
475
+ switch (message.type) {
476
+ case "ethisys:mcp:invokeTool":
477
+ this.dispatchMcp(message, "ethisys:mcp:invokeTool:result", (m) => this.handleInvokeTool(m));
478
+ return;
479
+ case "ethisys:mcp:getResource":
480
+ this.dispatchMcp(message, "ethisys:mcp:getResource:result", (m) => this.handleGetResource(m));
481
+ return;
482
+ case "ethisys:remotedom":
483
+ this.remoteDomConsumer?.(message.payload);
484
+ return;
485
+ default:
486
+ return;
487
+ }
488
+ }
489
+ /**
490
+ * Gate inbound MCP requests through the bounded concurrency window,
491
+ * reject with a deterministic error reply when the cap is exceeded, and
492
+ * decrement the counter unconditionally when the underlying handler
493
+ * settles (regardless of resolution/rejection shape).
494
+ */
495
+ dispatchMcp(message, resultType, handler) {
496
+ if (this.inFlightMcpRequests >= this.maxConcurrentMcpRequests) {
497
+ this.safePostMessage({
498
+ id: message.id,
499
+ type: resultType,
500
+ ok: false,
501
+ error: `MCP back-pressure: in-flight cap of ${this.maxConcurrentMcpRequests} reached.`
502
+ });
503
+ return;
504
+ }
505
+ this.inFlightMcpRequests++;
506
+ handler(message).finally(() => {
507
+ this.inFlightMcpRequests = Math.max(0, this.inFlightMcpRequests - 1);
508
+ });
509
+ }
510
+ async handleInvokeTool(message) {
511
+ let token;
512
+ try {
513
+ token = await this.capabilityTokenProvider();
514
+ } catch (err) {
515
+ this.replyError(message.id, "ethisys:mcp:invokeTool:result", err);
516
+ return;
517
+ }
518
+ if (this.disposed) {
519
+ return;
520
+ }
521
+ try {
522
+ const result = await this.mcpClient.fetch({
523
+ kind: "invokeTool",
524
+ name: message.name,
525
+ args: message.args,
526
+ capabilityToken: token,
527
+ signal: this.abortController.signal
528
+ });
529
+ this.safePostMessage({
530
+ id: message.id,
531
+ type: "ethisys:mcp:invokeTool:result",
532
+ ok: result.ok,
533
+ data: result.data,
534
+ error: result.error
535
+ });
536
+ } catch (err) {
537
+ this.replyError(message.id, "ethisys:mcp:invokeTool:result", err);
538
+ }
539
+ }
540
+ async handleGetResource(message) {
541
+ let token;
542
+ try {
543
+ token = await this.capabilityTokenProvider();
544
+ } catch (err) {
545
+ this.replyError(message.id, "ethisys:mcp:getResource:result", err);
546
+ return;
547
+ }
548
+ if (this.disposed) {
549
+ return;
550
+ }
551
+ try {
552
+ const result = await this.mcpClient.fetch({
553
+ kind: "getResource",
554
+ uri: message.uri,
555
+ capabilityToken: token,
556
+ signal: this.abortController.signal
557
+ });
558
+ this.safePostMessage({
559
+ id: message.id,
560
+ type: "ethisys:mcp:getResource:result",
561
+ ok: result.ok,
562
+ data: result.data,
563
+ error: result.error
564
+ });
565
+ } catch (err) {
566
+ this.replyError(message.id, "ethisys:mcp:getResource:result", err);
567
+ }
568
+ }
569
+ replyError(id, type, err) {
570
+ const message = err instanceof Error ? err.message : "MCP request failed";
571
+ this.safePostMessage({ id, type, ok: false, error: message });
572
+ }
573
+ };
574
+
575
+ export { CONTRACT_B_PRIMITIVES, DEFAULT_MAX_CONCURRENT_MCP_REQUESTS, DEFAULT_OFFSCREEN_COALESCE_MS, SemanticComponentRegistry, WORKER_TRANSPORT_PROTOCOL, WorkerRemoteDomTransport, createInputEventCoalescer, createOffscreenCanvasTransfer, evaluate, interpret };
576
+ //# sourceMappingURL=index.js.map
577
+ //# sourceMappingURL=index.js.map