@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/plugin/index.cjs
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
var react = require('react');
|
|
4
4
|
var jsxRuntime = require('react/jsx-runtime');
|
|
5
|
+
var elements = require('@remote-dom/core/elements');
|
|
6
|
+
var ReactReconciler = require('react-reconciler');
|
|
7
|
+
var constants_js = require('react-reconciler/constants.js');
|
|
8
|
+
var core = require('@remote-dom/core');
|
|
9
|
+
|
|
10
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
11
|
+
|
|
12
|
+
var ReactReconciler__default = /*#__PURE__*/_interopDefault(ReactReconciler);
|
|
5
13
|
|
|
6
14
|
// src/plugin/ExtensionRuntimeProvider.tsx
|
|
7
15
|
var ExtensionRuntimeContext = react.createContext(null);
|
|
@@ -99,14 +107,432 @@ function useMcpTool(toolName, opts) {
|
|
|
99
107
|
);
|
|
100
108
|
return { invoke, loading, error };
|
|
101
109
|
}
|
|
110
|
+
function useMcpQuery(toolName, args, options) {
|
|
111
|
+
const enabled = options?.enabled ?? true;
|
|
112
|
+
const { invoke } = useMcpTool(toolName);
|
|
113
|
+
const [data, setData] = react.useState(void 0);
|
|
114
|
+
const [loading, setLoading] = react.useState(enabled);
|
|
115
|
+
const [error, setError] = react.useState(void 0);
|
|
116
|
+
const [tick, setTick] = react.useState(0);
|
|
117
|
+
const argsKey = JSON.stringify(args);
|
|
118
|
+
react.useEffect(() => {
|
|
119
|
+
if (!enabled) {
|
|
120
|
+
setLoading(false);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
let cancelled = false;
|
|
124
|
+
setLoading(true);
|
|
125
|
+
invoke(args).then((result) => {
|
|
126
|
+
if (cancelled) {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
setData(result);
|
|
130
|
+
setError(void 0);
|
|
131
|
+
setLoading(false);
|
|
132
|
+
}).catch((err) => {
|
|
133
|
+
if (cancelled) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
137
|
+
setLoading(false);
|
|
138
|
+
});
|
|
139
|
+
return () => {
|
|
140
|
+
cancelled = true;
|
|
141
|
+
};
|
|
142
|
+
}, [argsKey, tick, invoke, enabled]);
|
|
143
|
+
return {
|
|
144
|
+
data,
|
|
145
|
+
loading,
|
|
146
|
+
error,
|
|
147
|
+
refetch: () => setTick((t) => t + 1)
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
function unwrapItems(response) {
|
|
151
|
+
return response?.items ?? response?.rows ?? [];
|
|
152
|
+
}
|
|
102
153
|
|
|
103
154
|
// src/plugin/define.ts
|
|
104
155
|
var defineDeclarativePlugin = (cfg) => cfg;
|
|
105
156
|
var defineEthisysPlugin = (cfg) => cfg;
|
|
157
|
+
var nextId = 0;
|
|
158
|
+
function newId() {
|
|
159
|
+
nextId += 1;
|
|
160
|
+
return `r-${nextId.toString(36)}`;
|
|
161
|
+
}
|
|
162
|
+
function serialize(node) {
|
|
163
|
+
if (node.kind === "text") {
|
|
164
|
+
return { id: node.id, type: core.NODE_TYPE_TEXT, data: node.text };
|
|
165
|
+
}
|
|
166
|
+
return {
|
|
167
|
+
id: node.id,
|
|
168
|
+
type: core.NODE_TYPE_ELEMENT,
|
|
169
|
+
element: node.type,
|
|
170
|
+
properties: node.properties,
|
|
171
|
+
children: node.children.map(serialize)
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
function indexInParent(parent, child) {
|
|
175
|
+
return parent.children.indexOf(child);
|
|
176
|
+
}
|
|
177
|
+
function isEventListenerProp(key) {
|
|
178
|
+
return key.length > 2 && key.startsWith("on") && key[2] >= "A" && key[2] <= "Z";
|
|
179
|
+
}
|
|
180
|
+
function eventNameFromProp(propName) {
|
|
181
|
+
return propName.slice(2).toLowerCase();
|
|
182
|
+
}
|
|
183
|
+
function isReservedProp(key) {
|
|
184
|
+
return key === "children" || key === "key" || key === "ref";
|
|
185
|
+
}
|
|
186
|
+
function classifyProps(props) {
|
|
187
|
+
const properties = {};
|
|
188
|
+
const eventListeners = {};
|
|
189
|
+
for (const key of Object.keys(props)) {
|
|
190
|
+
if (isReservedProp(key)) {
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
const value = props[key];
|
|
194
|
+
if (isEventListenerProp(key) && typeof value === "function") {
|
|
195
|
+
eventListeners[eventNameFromProp(key)] = value;
|
|
196
|
+
} else if (value !== void 0) {
|
|
197
|
+
properties[key] = value;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return { properties, eventListeners };
|
|
201
|
+
}
|
|
202
|
+
function createReactReconciler(connection) {
|
|
203
|
+
const reconciler = ReactReconciler__default.default({
|
|
204
|
+
supportsMutation: true,
|
|
205
|
+
supportsPersistence: false,
|
|
206
|
+
supportsHydration: false,
|
|
207
|
+
isPrimaryRenderer: true,
|
|
208
|
+
noTimeout: -1,
|
|
209
|
+
scheduleTimeout: setTimeout,
|
|
210
|
+
cancelTimeout: clearTimeout,
|
|
211
|
+
getCurrentEventPriority: () => constants_js.DefaultEventPriority,
|
|
212
|
+
getRootHostContext: () => ({}),
|
|
213
|
+
getChildHostContext: () => ({}),
|
|
214
|
+
prepareForCommit: () => null,
|
|
215
|
+
resetAfterCommit: () => {
|
|
216
|
+
},
|
|
217
|
+
preparePortalMount: () => {
|
|
218
|
+
},
|
|
219
|
+
shouldSetTextContent: (_type, props) => typeof props.children === "string" || typeof props.children === "number",
|
|
220
|
+
getPublicInstance: (instance) => instance,
|
|
221
|
+
clearContainer: (container) => {
|
|
222
|
+
while (container.children.length > 0) {
|
|
223
|
+
connection.mutate([[core.MUTATION_TYPE_REMOVE_CHILD, container.id, 0]]);
|
|
224
|
+
container.children.shift();
|
|
225
|
+
}
|
|
226
|
+
},
|
|
227
|
+
// ── createInstance / createTextInstance ────────────────────────
|
|
228
|
+
createInstance: (type, props) => {
|
|
229
|
+
const { properties, eventListeners } = classifyProps(props);
|
|
230
|
+
return {
|
|
231
|
+
id: newId(),
|
|
232
|
+
kind: "element",
|
|
233
|
+
type,
|
|
234
|
+
properties,
|
|
235
|
+
eventListeners,
|
|
236
|
+
children: [],
|
|
237
|
+
parent: null
|
|
238
|
+
};
|
|
239
|
+
},
|
|
240
|
+
createTextInstance: (text) => ({
|
|
241
|
+
id: newId(),
|
|
242
|
+
kind: "text",
|
|
243
|
+
text,
|
|
244
|
+
parent: null
|
|
245
|
+
}),
|
|
246
|
+
// ── Initial mount: build the subtree before attaching to root ──
|
|
247
|
+
appendInitialChild: (parent, child) => {
|
|
248
|
+
parent.children.push(child);
|
|
249
|
+
child.parent = parent;
|
|
250
|
+
},
|
|
251
|
+
finalizeInitialChildren: () => false,
|
|
252
|
+
// ── Container-level mutations (root attach/detach) ─────────────
|
|
253
|
+
appendChildToContainer: (container, child) => {
|
|
254
|
+
const index = container.children.length;
|
|
255
|
+
container.children.push(child);
|
|
256
|
+
child.parent = container;
|
|
257
|
+
connection.mutate([
|
|
258
|
+
[core.MUTATION_TYPE_INSERT_CHILD, container.id, serialize(child), index]
|
|
259
|
+
]);
|
|
260
|
+
},
|
|
261
|
+
removeChildFromContainer: (container, child) => {
|
|
262
|
+
const index = indexInParent(container, child);
|
|
263
|
+
if (index < 0) {
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
container.children.splice(index, 1);
|
|
267
|
+
child.parent = null;
|
|
268
|
+
connection.mutate([[core.MUTATION_TYPE_REMOVE_CHILD, container.id, index]]);
|
|
269
|
+
},
|
|
270
|
+
insertInContainerBefore: (container, child, beforeChild) => {
|
|
271
|
+
const beforeIndex = indexInParent(container, beforeChild);
|
|
272
|
+
const insertAt = beforeIndex < 0 ? container.children.length : beforeIndex;
|
|
273
|
+
container.children.splice(insertAt, 0, child);
|
|
274
|
+
child.parent = container;
|
|
275
|
+
connection.mutate([
|
|
276
|
+
[core.MUTATION_TYPE_INSERT_CHILD, container.id, serialize(child), insertAt]
|
|
277
|
+
]);
|
|
278
|
+
},
|
|
279
|
+
// ── Sibling-level mutations (incremental tree updates) ─────────
|
|
280
|
+
appendChild: (parent, child) => {
|
|
281
|
+
const index = parent.children.length;
|
|
282
|
+
parent.children.push(child);
|
|
283
|
+
child.parent = parent;
|
|
284
|
+
connection.mutate([
|
|
285
|
+
[core.MUTATION_TYPE_INSERT_CHILD, parent.id, serialize(child), index]
|
|
286
|
+
]);
|
|
287
|
+
},
|
|
288
|
+
removeChild: (parent, child) => {
|
|
289
|
+
const index = indexInParent(parent, child);
|
|
290
|
+
if (index < 0) {
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
parent.children.splice(index, 1);
|
|
294
|
+
child.parent = null;
|
|
295
|
+
connection.mutate([[core.MUTATION_TYPE_REMOVE_CHILD, parent.id, index]]);
|
|
296
|
+
},
|
|
297
|
+
insertBefore: (parent, child, beforeChild) => {
|
|
298
|
+
const beforeIndex = indexInParent(parent, beforeChild);
|
|
299
|
+
const insertAt = beforeIndex < 0 ? parent.children.length : beforeIndex;
|
|
300
|
+
parent.children.splice(insertAt, 0, child);
|
|
301
|
+
child.parent = parent;
|
|
302
|
+
connection.mutate([
|
|
303
|
+
[core.MUTATION_TYPE_INSERT_CHILD, parent.id, serialize(child), insertAt]
|
|
304
|
+
]);
|
|
305
|
+
},
|
|
306
|
+
// ── Update path ────────────────────────────────────────────────
|
|
307
|
+
prepareUpdate: (_instance, _type, oldProps, newProps) => {
|
|
308
|
+
const changed = [];
|
|
309
|
+
const allKeys = /* @__PURE__ */ new Set([
|
|
310
|
+
...Object.keys(oldProps),
|
|
311
|
+
...Object.keys(newProps)
|
|
312
|
+
]);
|
|
313
|
+
for (const key of allKeys) {
|
|
314
|
+
if (isReservedProp(key)) {
|
|
315
|
+
continue;
|
|
316
|
+
}
|
|
317
|
+
if (oldProps[key] !== newProps[key]) {
|
|
318
|
+
changed.push(key);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
return changed.length > 0 ? changed : null;
|
|
322
|
+
},
|
|
323
|
+
commitUpdate: (instance, updatePayload, _type, _oldProps, newProps) => {
|
|
324
|
+
if (updatePayload === null) {
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
for (const key of updatePayload) {
|
|
328
|
+
const value = newProps[key];
|
|
329
|
+
if (isEventListenerProp(key) && (typeof value === "function" || value === void 0)) {
|
|
330
|
+
const eventName = eventNameFromProp(key);
|
|
331
|
+
if (typeof value === "function") {
|
|
332
|
+
instance.eventListeners[eventName] = value;
|
|
333
|
+
} else {
|
|
334
|
+
delete instance.eventListeners[eventName];
|
|
335
|
+
}
|
|
336
|
+
connection.mutate([
|
|
337
|
+
[
|
|
338
|
+
core.MUTATION_TYPE_UPDATE_PROPERTY,
|
|
339
|
+
instance.id,
|
|
340
|
+
eventName,
|
|
341
|
+
typeof value === "function" ? true : null,
|
|
342
|
+
3
|
|
343
|
+
]
|
|
344
|
+
]);
|
|
345
|
+
} else {
|
|
346
|
+
if (value === void 0) {
|
|
347
|
+
delete instance.properties[key];
|
|
348
|
+
} else {
|
|
349
|
+
instance.properties[key] = value;
|
|
350
|
+
}
|
|
351
|
+
connection.mutate([
|
|
352
|
+
[core.MUTATION_TYPE_UPDATE_PROPERTY, instance.id, key, value]
|
|
353
|
+
]);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
},
|
|
357
|
+
commitTextUpdate: (textInstance, _oldText, newText) => {
|
|
358
|
+
textInstance.text = newText;
|
|
359
|
+
connection.mutate([[core.MUTATION_TYPE_UPDATE_TEXT, textInstance.id, newText]]);
|
|
360
|
+
},
|
|
361
|
+
// ── No-op methods required by the HostConfig contract ──────────
|
|
362
|
+
detachDeletedInstance: () => {
|
|
363
|
+
},
|
|
364
|
+
beforeActiveInstanceBlur: () => {
|
|
365
|
+
},
|
|
366
|
+
afterActiveInstanceBlur: () => {
|
|
367
|
+
},
|
|
368
|
+
prepareScopeUpdate: () => {
|
|
369
|
+
},
|
|
370
|
+
getInstanceFromScope: () => null,
|
|
371
|
+
getInstanceFromNode: () => null,
|
|
372
|
+
// Microtask scheduling — use the platform's default queueing.
|
|
373
|
+
supportsMicrotasks: true,
|
|
374
|
+
scheduleMicrotask: typeof queueMicrotask === "function" ? queueMicrotask : (callback) => Promise.resolve().then(callback)
|
|
375
|
+
});
|
|
376
|
+
function createContainer() {
|
|
377
|
+
return { id: core.ROOT_ID, kind: "root", children: [], connection };
|
|
378
|
+
}
|
|
379
|
+
return { reconciler, createContainer };
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// src/plugin/createRemoteRoot.ts
|
|
383
|
+
function createRemoteRoot(port, options = {}) {
|
|
384
|
+
if (port === void 0 || port === null) {
|
|
385
|
+
throw new TypeError(
|
|
386
|
+
"[createRemoteRoot] `port` is required. Pass the MessagePort the host transferred via the worker's `activate(port)` entry point."
|
|
387
|
+
);
|
|
388
|
+
}
|
|
389
|
+
const baseConnection = (options.connectionFactory ?? defaultConnectionFactory)(port);
|
|
390
|
+
const connection = options.batchMutations === false ? baseConnection : new elements.BatchingRemoteConnection(baseConnection);
|
|
391
|
+
const { reconciler, createContainer } = createReactReconciler(connection);
|
|
392
|
+
let container = null;
|
|
393
|
+
let fiberRoot = null;
|
|
394
|
+
return {
|
|
395
|
+
render(element) {
|
|
396
|
+
if (container === null) {
|
|
397
|
+
container = createContainer();
|
|
398
|
+
fiberRoot = reconciler.createContainer(
|
|
399
|
+
container,
|
|
400
|
+
constants_js.LegacyRoot,
|
|
401
|
+
null,
|
|
402
|
+
// hydration callbacks
|
|
403
|
+
false,
|
|
404
|
+
// isStrictMode
|
|
405
|
+
null,
|
|
406
|
+
// concurrentUpdatesByDefaultOverride
|
|
407
|
+
"",
|
|
408
|
+
// identifierPrefix
|
|
409
|
+
(error) => {
|
|
410
|
+
console.error("[createRemoteRoot] render error", error);
|
|
411
|
+
},
|
|
412
|
+
null
|
|
413
|
+
// transitionCallbacks
|
|
414
|
+
);
|
|
415
|
+
}
|
|
416
|
+
reconciler.updateContainer(element, fiberRoot, null, null);
|
|
417
|
+
},
|
|
418
|
+
unmount() {
|
|
419
|
+
if (fiberRoot !== null) {
|
|
420
|
+
reconciler.updateContainer(null, fiberRoot, null, null);
|
|
421
|
+
fiberRoot = null;
|
|
422
|
+
container = null;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
function defaultConnectionFactory(port) {
|
|
428
|
+
return {
|
|
429
|
+
mutate(records) {
|
|
430
|
+
port.postMessage({ type: "ethisys:remotedom", payload: records.slice() });
|
|
431
|
+
},
|
|
432
|
+
call(_id, _method, ..._args) {
|
|
433
|
+
return void 0;
|
|
434
|
+
}
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// src/plugin/createPortMcpTransport.ts
|
|
439
|
+
var WIRE_INVOKE_TOOL_REQUEST = "ethisys:mcp:invokeTool";
|
|
440
|
+
var WIRE_INVOKE_TOOL_RESULT = "ethisys:mcp:invokeTool:result";
|
|
441
|
+
var WIRE_GET_RESOURCE_REQUEST = "ethisys:mcp:getResource";
|
|
442
|
+
var WIRE_GET_RESOURCE_RESULT = "ethisys:mcp:getResource:result";
|
|
443
|
+
var WIRE_ABORT = "ethisys:mcp:abort";
|
|
444
|
+
function isMcpResultEnvelope(value) {
|
|
445
|
+
if (value === null || typeof value !== "object") {
|
|
446
|
+
return false;
|
|
447
|
+
}
|
|
448
|
+
const v = value;
|
|
449
|
+
return (v.type === WIRE_INVOKE_TOOL_RESULT || v.type === WIRE_GET_RESOURCE_RESULT) && typeof v.id === "string";
|
|
450
|
+
}
|
|
451
|
+
function createPortMcpTransport(port, options = {}) {
|
|
452
|
+
if (port === void 0 || port === null) {
|
|
453
|
+
throw new TypeError(
|
|
454
|
+
"[createPortMcpTransport] `port` is required. Pass the MessagePort the host transferred via the worker's `activate(port)` entry point."
|
|
455
|
+
);
|
|
456
|
+
}
|
|
457
|
+
const shim = options.portShimForTests ?? port;
|
|
458
|
+
const factory = options.requestIdFactory ?? defaultRequestIdFactory();
|
|
459
|
+
const pending = /* @__PURE__ */ new Map();
|
|
460
|
+
function onMessage(event) {
|
|
461
|
+
if (!isMcpResultEnvelope(event.data)) {
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
const reply = event.data;
|
|
465
|
+
const resolver = pending.get(reply.id);
|
|
466
|
+
if (resolver === void 0) {
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
pending.delete(reply.id);
|
|
470
|
+
if (reply.ok === true) {
|
|
471
|
+
resolver.resolve(reply.data);
|
|
472
|
+
} else {
|
|
473
|
+
const error = new Error(
|
|
474
|
+
typeof reply.error === "string" ? reply.error : "Unknown host error"
|
|
475
|
+
);
|
|
476
|
+
error.name = "McpHostError";
|
|
477
|
+
resolver.reject(error);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
shim.addEventListener("message", onMessage);
|
|
481
|
+
const realPort = port;
|
|
482
|
+
if (!options.portShimForTests && typeof realPort.start === "function") {
|
|
483
|
+
realPort.start();
|
|
484
|
+
}
|
|
485
|
+
function send(requestType, body, signal) {
|
|
486
|
+
const id = factory();
|
|
487
|
+
return new Promise((resolve, reject) => {
|
|
488
|
+
if (signal?.aborted === true) {
|
|
489
|
+
reject(abortError());
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
pending.set(id, { resolve, reject });
|
|
493
|
+
const onAbort = () => {
|
|
494
|
+
if (!pending.has(id)) {
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
pending.delete(id);
|
|
498
|
+
shim.postMessage({ type: WIRE_ABORT, id });
|
|
499
|
+
reject(abortError());
|
|
500
|
+
};
|
|
501
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
502
|
+
shim.postMessage({ type: requestType, id, ...body });
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
return {
|
|
506
|
+
async getResource(uri, signal) {
|
|
507
|
+
const data = await send(WIRE_GET_RESOURCE_REQUEST, { uri }, signal);
|
|
508
|
+
return { uri, data };
|
|
509
|
+
},
|
|
510
|
+
async invokeTool(name, args, signal) {
|
|
511
|
+
const data = await send(WIRE_INVOKE_TOOL_REQUEST, { name, args }, signal);
|
|
512
|
+
return data;
|
|
513
|
+
}
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
function defaultRequestIdFactory() {
|
|
517
|
+
let counter = 0;
|
|
518
|
+
return () => {
|
|
519
|
+
counter += 1;
|
|
520
|
+
return `req-${counter}`;
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
function abortError() {
|
|
524
|
+
const e = new Error("Aborted");
|
|
525
|
+
e.name = "AbortError";
|
|
526
|
+
return e;
|
|
527
|
+
}
|
|
106
528
|
|
|
107
529
|
exports.ExtensionRuntimeProvider = ExtensionRuntimeProvider;
|
|
530
|
+
exports.createPortMcpTransport = createPortMcpTransport;
|
|
531
|
+
exports.createRemoteRoot = createRemoteRoot;
|
|
108
532
|
exports.defineDeclarativePlugin = defineDeclarativePlugin;
|
|
109
533
|
exports.defineEthisysPlugin = defineEthisysPlugin;
|
|
534
|
+
exports.unwrapItems = unwrapItems;
|
|
535
|
+
exports.useMcpQuery = useMcpQuery;
|
|
110
536
|
exports.useMcpResource = useMcpResource;
|
|
111
537
|
exports.useMcpTool = useMcpTool;
|
|
112
538
|
//# sourceMappingURL=index.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/plugin/ExtensionRuntimeProvider.tsx","../../src/plugin/useMcpResource.ts","../../src/plugin/useMcpTool.ts","../../src/plugin/define.ts"],"names":["createContext","useMemo","jsx","useContext","useState","useRef","useEffect","useCallback"],"mappings":";;;;;;AASA,IAAM,uBAAA,GAA0BA,oBAAmC,IAAI,CAAA;AAiBhE,SAAS,wBAAA,CAAyB,EAAE,SAAA,EAAW,QAAA,EAAS,EAC/D;AAEI,EAAA,MAAM,QAAQC,aAAA,CAAQ,MAAM,SAAA,EAAW,CAAC,SAAS,CAAC,CAAA;AAClD,EAAA,uBACIC,cAAA,CAAC,uBAAA,CAAwB,QAAA,EAAxB,EAAiC,OAC7B,QAAA,EACL,CAAA;AAER;AAQO,SAAS,6BAA6B,QAAA,EAC7C;AACI,EAAA,MAAM,WAAA,GAAcC,iBAAW,uBAAuB,CAAA;AACtD,EAAA,MAAM,WAAW,QAAA,IAAY,WAAA;AAC7B,EAAA,IAAI,CAAC,QAAA,EACL;AACI,IAAA,MAAM,IAAI,KAAA;AAAA,MACN;AAAA,KAEJ;AAAA,EACJ;AACA,EAAA,OAAO,QAAA;AACX;ACrBO,SAAS,cAAA,CACZ,KACA,IAAA,EAEJ;AACI,EAAA,MAAM,SAAA,GAAY,4BAAA,CAA6B,IAAA,EAAM,SAAS,CAAA;AAE9D,EAAA,MAAM,CAAC,IAAA,EAAM,OAAO,CAAA,GAAIC,eAAwB,MAAS,CAAA;AACzD,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAIA,eAA4B,MAAS,CAAA;AAC/D,EAAA,MAAM,CAAC,OAAA,EAAS,UAAU,CAAA,GAAIA,eAAkB,IAAI,CAAA;AAGpD,EAAA,MAAM,CAAC,WAAA,EAAa,cAAc,CAAA,GAAIA,eAAS,CAAC,CAAA;AAGhD,EAAA,MAAM,aAAA,GAAgBC,aAA+B,IAAI,CAAA;AAEzD,EAAAC,eAAA,CAAU,MACV;AACI,IAAA,MAAM,UAAA,GAAa,IAAI,eAAA,EAAgB;AACvC,IAAA,aAAA,CAAc,SAAS,KAAA,EAAM;AAC7B,IAAA,aAAA,CAAc,OAAA,GAAU,UAAA;AAExB,IAAA,IAAI,SAAA,GAAY,KAAA;AAChB,IAAA,UAAA,CAAW,IAAI,CAAA;AACf,IAAA,QAAA,CAAS,MAAS,CAAA;AAElB,IAAA,SAAA,CACK,YAAe,GAAA,EAAK,UAAA,CAAW,MAAM,CAAA,CACrC,IAAA,CAAK,CAAC,MAAA,KACP;AACI,MAAA,IAAI,SAAA,IAAa,UAAA,CAAW,MAAA,CAAO,OAAA,EACnC;AACI,QAAA;AAAA,MACJ;AACA,MAAA,OAAA,CAAQ,OAAO,IAAI,CAAA;AACnB,MAAA,UAAA,CAAW,KAAK,CAAA;AAAA,IACpB,CAAC,CAAA,CACA,KAAA,CAAM,CAAC,GAAA,KACR;AACI,MAAA,IAAI,SAAA,IAAa,UAAA,CAAW,MAAA,CAAO,OAAA,EACnC;AACI,QAAA;AAAA,MACJ;AACA,MAAA,QAAA,CAAS,GAAA,YAAe,QAAQ,GAAA,GAAM,IAAI,MAAM,MAAA,CAAO,GAAG,CAAC,CAAC,CAAA;AAC5D,MAAA,UAAA,CAAW,KAAK,CAAA;AAAA,IACpB,CAAC,CAAA;AAEL,IAAA,OAAO,MACP;AACI,MAAA,SAAA,GAAY,IAAA;AACZ,MAAA,UAAA,CAAW,KAAA,EAAM;AAAA,IACrB,CAAA;AAAA,EACJ,CAAA,EAAG,CAAC,GAAA,EAAK,SAAA,EAAW,WAAW,CAAC,CAAA;AAEhC,EAAA,MAAM,OAAA,GAAUC,kBAAY,MAC5B;AACI,IAAA,cAAA,CAAe,CAAC,IAAA,KAAS,IAAA,GAAO,CAAC,CAAA;AAAA,EACrC,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,OAAO,EAAE,IAAA,EAAM,KAAA,EAAO,OAAA,EAAS,OAAA,EAAQ;AAC3C;AC3DO,SAAS,UAAA,CACZ,UACA,IAAA,EAEJ;AACI,EAAA,MAAM,SAAA,GAAY,4BAAA,CAA6B,IAAA,EAAM,SAAS,CAAA;AAE9D,EAAA,MAAM,CAAC,OAAA,EAAS,UAAU,CAAA,GAAIH,eAAS,KAAK,CAAA;AAC5C,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAIA,eAA4B,MAAS,CAAA;AAG/D,EAAA,MAAM,aAAA,GAAgBC,aAA+B,IAAI,CAAA;AACzD,EAAA,MAAM,UAAA,GAAaA,aAAO,IAAI,CAAA;AAE9B,EAAAC,gBAAU,MACV;AACI,IAAA,UAAA,CAAW,OAAA,GAAU,IAAA;AACrB,IAAA,OAAO,MACP;AACI,MAAA,UAAA,CAAW,OAAA,GAAU,KAAA;AACrB,MAAA,aAAA,CAAc,SAAS,KAAA,EAAM;AAAA,IACjC,CAAA;AAAA,EACJ,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,MAAM,MAAA,GAASC,iBAAAA;AAAA,IACX,OAAO,GAAA,KACP;AACI,MAAA,MAAM,UAAA,GAAa,IAAI,eAAA,EAAgB;AACvC,MAAA,aAAA,CAAc,SAAS,KAAA,EAAM;AAC7B,MAAA,aAAA,CAAc,OAAA,GAAU,UAAA;AAExB,MAAA,UAAA,CAAW,IAAI,CAAA;AACf,MAAA,QAAA,CAAS,MAAS,CAAA;AAElB,MAAA,IACA;AACI,QAAA,MAAM,MAAA,GAAS,MAAM,SAAA,CAAU,UAAA;AAAA,UAC3B,QAAA;AAAA,UACA,GAAA;AAAA,UACA,UAAA,CAAW;AAAA,SACf;AACA,QAAA,IAAI,UAAA,CAAW,OAAA,IAAW,CAAC,UAAA,CAAW,OAAO,OAAA,EAC7C;AACI,UAAA,UAAA,CAAW,KAAK,CAAA;AAAA,QACpB;AACA,QAAA,OAAO,MAAA;AAAA,MACX,SACO,GAAA,EACP;AACI,QAAA,MAAM,OAAA,GAAU,eAAe,KAAA,GAAQ,GAAA,GAAM,IAAI,KAAA,CAAM,MAAA,CAAO,GAAG,CAAC,CAAA;AAClE,QAAA,IAAI,UAAA,CAAW,OAAA,IAAW,CAAC,UAAA,CAAW,OAAO,OAAA,EAC7C;AACI,UAAA,QAAA,CAAS,OAAO,CAAA;AAChB,UAAA,UAAA,CAAW,KAAK,CAAA;AAAA,QACpB;AACA,QAAA,MAAM,OAAA;AAAA,MACV;AAAA,IACJ,CAAA;AAAA,IACA,CAAC,WAAW,QAAQ;AAAA,GACxB;AAEA,EAAA,OAAO,EAAE,MAAA,EAAQ,OAAA,EAAS,KAAA,EAAM;AACpC;;;AC7DO,IAAM,uBAAA,GAA0B,CACnC,GAAA,KAC0B;AAUvB,IAAM,mBAAA,GAAsB,CAC/B,GAAA,KAC8B","file":"index.cjs","sourcesContent":["import { createContext, useContext, useMemo, type ReactNode } from \"react\";\nimport type { McpTransport } from \"./transport\";\n\n/**\n * React context carrying the {@link McpTransport} the plugin should use to\n * reach the host. `null` is the explicit \"not provided\" sentinel so the hooks\n * can disambiguate from a transport that was provided but is incidentally\n * falsy in some other dimension.\n */\nconst ExtensionRuntimeContext = createContext<McpTransport | null>(null);\n\nexport interface ExtensionRuntimeProviderProps\n{\n transport: McpTransport;\n children?: ReactNode;\n}\n\n/**\n * Wrap a plugin's React tree so descendant {@link useMcpResource} and\n * {@link useMcpTool} calls resolve a default transport without having to\n * thread it through every component.\n *\n * Hooks still accept a per-call `transport` override, which takes precedence\n * over the context value — useful for tests and for plugins that want to\n * shard work across multiple hosts.\n */\nexport function ExtensionRuntimeProvider({ transport, children }: ExtensionRuntimeProviderProps): ReactNode\n{\n // Memoise so swapping `children` doesn't churn the context identity.\n const value = useMemo(() => transport, [transport]);\n return (\n <ExtensionRuntimeContext.Provider value={value}>\n {children}\n </ExtensionRuntimeContext.Provider>\n );\n}\n\n/**\n * Internal helper used by the hooks. Returns the explicit override when\n * supplied, otherwise falls back to the context. Throws a deterministic\n * error if neither is available so misconfiguration fails loudly at the\n * first render rather than producing silent no-ops.\n */\nexport function useExtensionRuntimeTransport(override?: McpTransport): McpTransport\n{\n const fromContext = useContext(ExtensionRuntimeContext);\n const resolved = override ?? fromContext;\n if (!resolved)\n {\n throw new Error(\n \"No McpTransport available. Wrap your plugin in <ExtensionRuntimeProvider transport={...}> \"\n + \"or pass `transport` directly to the hook.\",\n );\n }\n return resolved;\n}\n","import { useCallback, useEffect, useRef, useState } from \"react\";\nimport { useExtensionRuntimeTransport } from \"./ExtensionRuntimeProvider\";\nimport type { McpTransport } from \"./transport\";\n\nexport interface UseMcpResourceOptions\n{\n /**\n * Override the transport resolved from {@link ExtensionRuntimeProvider}.\n * Primarily intended for tests and advanced multi-host scenarios.\n */\n transport?: McpTransport;\n}\n\nexport interface UseMcpResourceResult<T>\n{\n data: T | undefined;\n error: Error | undefined;\n loading: boolean;\n /** Re-run the fetch against the current URI. Stable across renders. */\n refetch: () => void;\n}\n\n/**\n * Subscribe to an MCP resource by URI.\n *\n * The hook fetches via the resolved {@link McpTransport} on mount and whenever\n * the URI changes. Each fetch is cancellable: when the component unmounts or\n * the URI changes, the in-flight call is aborted via an {@link AbortController}\n * so stale responses cannot overwrite later state.\n *\n * `refetch` re-runs the fetch against the latest URI. The returned callback is\n * stable across renders so it is safe to include in `useEffect` dependency\n * arrays.\n */\nexport function useMcpResource<T>(\n uri: string,\n opts?: UseMcpResourceOptions,\n): UseMcpResourceResult<T>\n{\n const transport = useExtensionRuntimeTransport(opts?.transport);\n\n const [data, setData] = useState<T | undefined>(undefined);\n const [error, setError] = useState<Error | undefined>(undefined);\n const [loading, setLoading] = useState<boolean>(true);\n\n // Bump this counter to force a re-fetch from `refetch()`.\n const [refetchTick, setRefetchTick] = useState(0);\n\n // Track the active controller so `refetch` and unmount can abort it.\n const controllerRef = useRef<AbortController | null>(null);\n\n useEffect(() =>\n {\n const controller = new AbortController();\n controllerRef.current?.abort();\n controllerRef.current = controller;\n\n let cancelled = false;\n setLoading(true);\n setError(undefined);\n\n transport\n .getResource<T>(uri, controller.signal)\n .then((result) =>\n {\n if (cancelled || controller.signal.aborted)\n {\n return;\n }\n setData(result.data);\n setLoading(false);\n })\n .catch((err: unknown) =>\n {\n if (cancelled || controller.signal.aborted)\n {\n return;\n }\n setError(err instanceof Error ? err : new Error(String(err)));\n setLoading(false);\n });\n\n return () =>\n {\n cancelled = true;\n controller.abort();\n };\n }, [uri, transport, refetchTick]);\n\n const refetch = useCallback(() =>\n {\n setRefetchTick((tick) => tick + 1);\n }, []);\n\n return { data, error, loading, refetch };\n}\n","import { useCallback, useEffect, useRef, useState } from \"react\";\nimport { useExtensionRuntimeTransport } from \"./ExtensionRuntimeProvider\";\nimport type { McpTransport } from \"./transport\";\n\nexport interface UseMcpToolOptions\n{\n /**\n * Override the transport resolved from {@link ExtensionRuntimeProvider}.\n * Primarily intended for tests and advanced multi-host scenarios.\n */\n transport?: McpTransport;\n}\n\nexport interface UseMcpToolResult<TReq, TRes>\n{\n /**\n * Invoke the tool. Each call gets a fresh {@link AbortController} that is\n * aborted on unmount so unresolved promises cannot keep state alive after\n * the component is gone.\n */\n invoke: (req: TReq) => Promise<TRes>;\n loading: boolean;\n error: Error | undefined;\n}\n\n/**\n * Hook returning a callback that invokes an MCP tool by name through the\n * resolved {@link McpTransport}.\n *\n * `loading` and `error` track the most recent in-flight invocation. Callers\n * may also `await` the returned promise directly — failures are both rejected\n * to the caller AND surfaced via `error` so component-level UI can react.\n *\n * `invoke` is a stable reference across renders for the same `toolName` and\n * transport, making it safe to use inside `useEffect`/`useCallback` deps.\n */\nexport function useMcpTool<TReq, TRes>(\n toolName: string,\n opts?: UseMcpToolOptions,\n): UseMcpToolResult<TReq, TRes>\n{\n const transport = useExtensionRuntimeTransport(opts?.transport);\n\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<Error | undefined>(undefined);\n\n // Track the latest controller so unmount can abort an in-flight call.\n const controllerRef = useRef<AbortController | null>(null);\n const mountedRef = useRef(true);\n\n useEffect(() =>\n {\n mountedRef.current = true;\n return () =>\n {\n mountedRef.current = false;\n controllerRef.current?.abort();\n };\n }, []);\n\n const invoke = useCallback(\n async (req: TReq): Promise<TRes> =>\n {\n const controller = new AbortController();\n controllerRef.current?.abort();\n controllerRef.current = controller;\n\n setLoading(true);\n setError(undefined);\n\n try\n {\n const result = await transport.invokeTool<TReq, TRes>(\n toolName,\n req,\n controller.signal,\n );\n if (mountedRef.current && !controller.signal.aborted)\n {\n setLoading(false);\n }\n return result;\n }\n catch (err)\n {\n const wrapped = err instanceof Error ? err : new Error(String(err));\n if (mountedRef.current && !controller.signal.aborted)\n {\n setError(wrapped);\n setLoading(false);\n }\n throw wrapped;\n }\n },\n [transport, toolName],\n );\n\n return { invoke, loading, error };\n}\n","import type { RenderMode, SduiNode } from \"@ethisyscore/protocol\";\n\n/**\n * Configuration accepted by {@link defineDeclarativePlugin}.\n *\n * A Contract A (host-rendered) plugin contributes a map of resource URIs to\n * declarative SDUI trees. The host fetches a resource by URI and renders it\n * against the v1 vocabulary.\n */\nexport interface DeclarativePluginConfig\n{\n resources: Record<string, SduiNode>;\n}\n\n/**\n * Configuration accepted by {@link defineEthisysPlugin}.\n *\n * `renderMode` is constrained to the protocol's {@link RenderMode} enum so\n * authoring mistakes (`\"iframe\"`, `\"webview\"`, …) are caught at compile time.\n * `mount` is generic so Contract B remote-runtime authors can attach their\n * own mount surface without losing type information at the call site.\n */\nexport interface EthisysPluginConfig<TMount>\n{\n renderMode: RenderMode;\n mount?: TMount;\n}\n\n/**\n * Author-facing identity helper for a Contract A (host-rendered) declarative\n * plugin definition.\n *\n * The helper performs no runtime work — it exists purely so authoring sites\n * receive precise type inference and editor tooling against the protocol's\n * {@link SduiNode} contract. The returned value is the exact same reference\n * the caller passed in.\n */\nexport const defineDeclarativePlugin = (\n cfg: DeclarativePluginConfig,\n): DeclarativePluginConfig => cfg;\n\n/**\n * Author-facing identity helper for a generic EthisysCore plugin definition.\n *\n * The helper performs no runtime work. It constrains `renderMode` to the\n * protocol's {@link RenderMode} enum and preserves the inferred type of an\n * optional `mount` surface, so Contract B authors can pass through their own\n * mount object without widening it to `unknown`.\n */\nexport const defineEthisysPlugin = <TMount>(\n cfg: EthisysPluginConfig<TMount>,\n): EthisysPluginConfig<TMount> => cfg;\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/plugin/ExtensionRuntimeProvider.tsx","../../src/plugin/useMcpResource.ts","../../src/plugin/useMcpTool.ts","../../src/plugin/useMcpQuery.ts","../../src/plugin/define.ts","../../src/plugin/reconciler.ts","../../src/plugin/createRemoteRoot.ts","../../src/plugin/createPortMcpTransport.ts"],"names":["createContext","useMemo","jsx","useContext","useState","useRef","useEffect","useCallback","NODE_TYPE_TEXT","NODE_TYPE_ELEMENT","ReactReconciler","DefaultEventPriority","MUTATION_TYPE_REMOVE_CHILD","MUTATION_TYPE_INSERT_CHILD","MUTATION_TYPE_UPDATE_PROPERTY","MUTATION_TYPE_UPDATE_TEXT","ROOT_ID","BatchingRemoteConnection","LegacyRoot"],"mappings":"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","file":"index.cjs","sourcesContent":["import { createContext, useContext, useMemo, type ReactNode } from \"react\";\nimport type { McpTransport } from \"./transport\";\n\n/**\n * React context carrying the {@link McpTransport} the plugin should use to\n * reach the host. `null` is the explicit \"not provided\" sentinel so the hooks\n * can disambiguate from a transport that was provided but is incidentally\n * falsy in some other dimension.\n */\nconst ExtensionRuntimeContext = createContext<McpTransport | null>(null);\n\nexport interface ExtensionRuntimeProviderProps\n{\n transport: McpTransport;\n children?: ReactNode;\n}\n\n/**\n * Wrap a plugin's React tree so descendant {@link useMcpResource} and\n * {@link useMcpTool} calls resolve a default transport without having to\n * thread it through every component.\n *\n * Hooks still accept a per-call `transport` override, which takes precedence\n * over the context value — useful for tests and for plugins that want to\n * shard work across multiple hosts.\n */\nexport function ExtensionRuntimeProvider({ transport, children }: ExtensionRuntimeProviderProps): ReactNode\n{\n // Memoise so swapping `children` doesn't churn the context identity.\n const value = useMemo(() => transport, [transport]);\n return (\n <ExtensionRuntimeContext.Provider value={value}>\n {children}\n </ExtensionRuntimeContext.Provider>\n );\n}\n\n/**\n * Internal helper used by the hooks. Returns the explicit override when\n * supplied, otherwise falls back to the context. Throws a deterministic\n * error if neither is available so misconfiguration fails loudly at the\n * first render rather than producing silent no-ops.\n */\nexport function useExtensionRuntimeTransport(override?: McpTransport): McpTransport\n{\n const fromContext = useContext(ExtensionRuntimeContext);\n const resolved = override ?? fromContext;\n if (!resolved)\n {\n throw new Error(\n \"No McpTransport available. Wrap your plugin in <ExtensionRuntimeProvider transport={...}> \"\n + \"or pass `transport` directly to the hook.\",\n );\n }\n return resolved;\n}\n","import { useCallback, useEffect, useRef, useState } from \"react\";\nimport { useExtensionRuntimeTransport } from \"./ExtensionRuntimeProvider\";\nimport type { McpTransport } from \"./transport\";\n\nexport interface UseMcpResourceOptions\n{\n /**\n * Override the transport resolved from {@link ExtensionRuntimeProvider}.\n * Primarily intended for tests and advanced multi-host scenarios.\n */\n transport?: McpTransport;\n}\n\nexport interface UseMcpResourceResult<T>\n{\n data: T | undefined;\n error: Error | undefined;\n loading: boolean;\n /** Re-run the fetch against the current URI. Stable across renders. */\n refetch: () => void;\n}\n\n/**\n * Subscribe to an MCP resource by URI.\n *\n * The hook fetches via the resolved {@link McpTransport} on mount and whenever\n * the URI changes. Each fetch is cancellable: when the component unmounts or\n * the URI changes, the in-flight call is aborted via an {@link AbortController}\n * so stale responses cannot overwrite later state.\n *\n * `refetch` re-runs the fetch against the latest URI. The returned callback is\n * stable across renders so it is safe to include in `useEffect` dependency\n * arrays.\n */\nexport function useMcpResource<T>(\n uri: string,\n opts?: UseMcpResourceOptions,\n): UseMcpResourceResult<T>\n{\n const transport = useExtensionRuntimeTransport(opts?.transport);\n\n const [data, setData] = useState<T | undefined>(undefined);\n const [error, setError] = useState<Error | undefined>(undefined);\n const [loading, setLoading] = useState<boolean>(true);\n\n // Bump this counter to force a re-fetch from `refetch()`.\n const [refetchTick, setRefetchTick] = useState(0);\n\n // Track the active controller so `refetch` and unmount can abort it.\n const controllerRef = useRef<AbortController | null>(null);\n\n useEffect(() =>\n {\n const controller = new AbortController();\n controllerRef.current?.abort();\n controllerRef.current = controller;\n\n let cancelled = false;\n setLoading(true);\n setError(undefined);\n\n transport\n .getResource<T>(uri, controller.signal)\n .then((result) =>\n {\n if (cancelled || controller.signal.aborted)\n {\n return;\n }\n setData(result.data);\n setLoading(false);\n })\n .catch((err: unknown) =>\n {\n if (cancelled || controller.signal.aborted)\n {\n return;\n }\n setError(err instanceof Error ? err : new Error(String(err)));\n setLoading(false);\n });\n\n return () =>\n {\n cancelled = true;\n controller.abort();\n };\n }, [uri, transport, refetchTick]);\n\n const refetch = useCallback(() =>\n {\n setRefetchTick((tick) => tick + 1);\n }, []);\n\n return { data, error, loading, refetch };\n}\n","import { useCallback, useEffect, useRef, useState } from \"react\";\nimport { useExtensionRuntimeTransport } from \"./ExtensionRuntimeProvider\";\nimport type { McpTransport } from \"./transport\";\n\nexport interface UseMcpToolOptions\n{\n /**\n * Override the transport resolved from {@link ExtensionRuntimeProvider}.\n * Primarily intended for tests and advanced multi-host scenarios.\n */\n transport?: McpTransport;\n}\n\nexport interface UseMcpToolResult<TReq, TRes>\n{\n /**\n * Invoke the tool. Each call gets a fresh {@link AbortController} that is\n * aborted on unmount so unresolved promises cannot keep state alive after\n * the component is gone.\n */\n invoke: (req: TReq) => Promise<TRes>;\n loading: boolean;\n error: Error | undefined;\n}\n\n/**\n * Hook returning a callback that invokes an MCP tool by name through the\n * resolved {@link McpTransport}.\n *\n * `loading` and `error` track the most recent in-flight invocation. Callers\n * may also `await` the returned promise directly — failures are both rejected\n * to the caller AND surfaced via `error` so component-level UI can react.\n *\n * `invoke` is a stable reference across renders for the same `toolName` and\n * transport, making it safe to use inside `useEffect`/`useCallback` deps.\n */\nexport function useMcpTool<TReq, TRes>(\n toolName: string,\n opts?: UseMcpToolOptions,\n): UseMcpToolResult<TReq, TRes>\n{\n const transport = useExtensionRuntimeTransport(opts?.transport);\n\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<Error | undefined>(undefined);\n\n // Track the latest controller so unmount can abort an in-flight call.\n const controllerRef = useRef<AbortController | null>(null);\n const mountedRef = useRef(true);\n\n useEffect(() =>\n {\n mountedRef.current = true;\n return () =>\n {\n mountedRef.current = false;\n controllerRef.current?.abort();\n };\n }, []);\n\n const invoke = useCallback(\n async (req: TReq): Promise<TRes> =>\n {\n const controller = new AbortController();\n controllerRef.current?.abort();\n controllerRef.current = controller;\n\n setLoading(true);\n setError(undefined);\n\n try\n {\n const result = await transport.invokeTool<TReq, TRes>(\n toolName,\n req,\n controller.signal,\n );\n if (mountedRef.current && !controller.signal.aborted)\n {\n setLoading(false);\n }\n return result;\n }\n catch (err)\n {\n const wrapped = err instanceof Error ? err : new Error(String(err));\n if (mountedRef.current && !controller.signal.aborted)\n {\n setError(wrapped);\n setLoading(false);\n }\n throw wrapped;\n }\n },\n [transport, toolName],\n );\n\n return { invoke, loading, error };\n}\n","import { useEffect, useState } from \"react\";\nimport { useMcpTool } from \"./useMcpTool\";\n\n/**\n * Result shape returned by {@link useMcpQuery}. Mirrors the host-app\n * `react-query`-style envelope so consumers can render loading / error /\n * data states declaratively. `refetch` is stable across renders.\n */\nexport interface UseMcpQueryResult<TRes>\n{\n data: TRes | undefined;\n loading: boolean;\n error: Error | undefined;\n /** Re-run the fetch against the current args. Stable across renders. */\n refetch: () => void;\n}\n\n/**\n * Options for {@link useMcpQuery}.\n */\nexport interface UseMcpQueryOptions\n{\n /**\n * Gate the fetch on a guard. When false, the hook returns idle state and\n * never invokes — important because hooks must be called unconditionally\n * at the top of the component, so an `enabled` flag is the standard way\n * to express \"fetch only when a selection is present\" without violating\n * the rules of hooks. Defaults to `true`.\n */\n enabled?: boolean;\n}\n\n/**\n * Auto-fetch wrapper over the imperative {@link useMcpTool}. Fires the tool\n * call on mount AND whenever the serialised `args` change. Use for\n * read-shaped MCP tool calls that back a component's render data; for\n * mutations, call {@link useMcpTool} directly and trigger\n * `invoke(...)` from event handlers.\n *\n * Why this lives alongside {@link useMcpTool}: many plugin MCP tools are\n * effectively queries (`list-tasks`, `get-pending-approvals`, …) — paginated\n * lookups that change with filter inputs. The base hook's imperative\n * `invoke` signature is correct for mutations but ergonomically wrong for\n * reads, where every consumer ends up writing the same `useEffect` +\n * AbortController + cancellation-on-unmount boilerplate. This hook absorbs\n * that boilerplate once.\n *\n * @template TArgs The request shape sent to the tool.\n * @template TRes The response shape the tool returns.\n */\nexport function useMcpQuery<TArgs, TRes>(\n toolName: string,\n args: TArgs,\n options?: UseMcpQueryOptions,\n): UseMcpQueryResult<TRes>\n{\n const enabled = options?.enabled ?? true;\n const { invoke } = useMcpTool<TArgs, TRes>(toolName);\n const [data, setData] = useState<TRes | undefined>(undefined);\n const [loading, setLoading] = useState<boolean>(enabled);\n const [error, setError] = useState<Error | undefined>(undefined);\n const [tick, setTick] = useState(0);\n\n // Stringify args for the effect's dependency key so an args object with\n // new identity but unchanged contents does not force a refetch. The\n // shape JSON-serialises trivially because tool args are wire-bound (JSON\n // round-trips on the transport anyway).\n const argsKey = JSON.stringify(args);\n\n useEffect(() =>\n {\n if (!enabled)\n {\n setLoading(false);\n return;\n }\n let cancelled = false;\n setLoading(true);\n invoke(args)\n .then((result) =>\n {\n if (cancelled)\n {\n return;\n }\n setData(result);\n setError(undefined);\n setLoading(false);\n })\n .catch((err: unknown) =>\n {\n if (cancelled)\n {\n return;\n }\n setError(err instanceof Error ? err : new Error(String(err)));\n setLoading(false);\n });\n return () =>\n {\n cancelled = true;\n };\n // `args` is intentionally tracked via `argsKey` (serialised contents)\n // so reference-only changes don't refetch. eslint sees this as a\n // missing dep but the effect is correct as-is.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [argsKey, tick, invoke, enabled]);\n\n return {\n data,\n loading,\n error,\n refetch: () => setTick((t) => t + 1),\n };\n}\n\n// ── Response-envelope helpers ────────────────────────────────────\n//\n// MCP tool responses across the platform's BE conventions arrive in either\n// `{ items: T[] }` (raw lists) or `{ rows: T[] }` (paginated envelopes). The\n// envelope shape is a BE-side detail plugin authors don't want to encode at\n// every read site. {@link ItemsResponse} types the union and\n// {@link unwrapItems} normalises to a plain array.\n\n/**\n * Envelope shape that covers both `{ items: T[] }` and `{ rows: T[] }`\n * conventions emitted by platform / plugin MCP read tools. Optional both\n * sides so empty responses still type-check.\n */\nexport interface ItemsResponse<T>\n{\n items?: T[];\n rows?: T[];\n}\n\n/**\n * Normalise an {@link ItemsResponse} envelope to a plain array.\n * Returns `[]` when the response is `undefined` (typical pre-first-fetch\n * state) or when neither field is populated.\n */\nexport function unwrapItems<T>(response: ItemsResponse<T> | undefined): T[]\n{\n return response?.items ?? response?.rows ?? [];\n}\n","import type { RenderMode, SduiNode } from \"@ethisyscore/protocol\";\n\n/**\n * Configuration accepted by {@link defineDeclarativePlugin}.\n *\n * A Contract A (host-rendered) plugin contributes a map of resource URIs to\n * declarative SDUI trees. The host fetches a resource by URI and renders it\n * against the v1 vocabulary.\n */\nexport interface DeclarativePluginConfig\n{\n resources: Record<string, SduiNode>;\n}\n\n/**\n * Configuration accepted by {@link defineEthisysPlugin}.\n *\n * `renderMode` is constrained to the protocol's {@link RenderMode} enum so\n * authoring mistakes (`\"iframe\"`, `\"webview\"`, …) are caught at compile time.\n * `mount` is generic so Contract B remote-runtime authors can attach their\n * own mount surface without losing type information at the call site.\n */\nexport interface EthisysPluginConfig<TMount>\n{\n renderMode: RenderMode;\n mount?: TMount;\n}\n\n/**\n * Author-facing identity helper for a Contract A (host-rendered) declarative\n * plugin definition.\n *\n * The helper performs no runtime work — it exists purely so authoring sites\n * receive precise type inference and editor tooling against the protocol's\n * {@link SduiNode} contract. The returned value is the exact same reference\n * the caller passed in.\n */\nexport const defineDeclarativePlugin = (\n cfg: DeclarativePluginConfig,\n): DeclarativePluginConfig => cfg;\n\n/**\n * Author-facing identity helper for a generic EthisysCore plugin definition.\n *\n * The helper performs no runtime work. It constrains `renderMode` to the\n * protocol's {@link RenderMode} enum and preserves the inferred type of an\n * optional `mount` surface, so Contract B authors can pass through their own\n * mount object without widening it to `unknown`.\n */\nexport const defineEthisysPlugin = <TMount>(\n cfg: EthisysPluginConfig<TMount>,\n): EthisysPluginConfig<TMount> => cfg;\n","import ReactReconciler from \"react-reconciler\";\nimport {\n ConcurrentRoot,\n LegacyRoot,\n DefaultEventPriority,\n} from \"react-reconciler/constants.js\";\nimport {\n MUTATION_TYPE_INSERT_CHILD,\n MUTATION_TYPE_REMOVE_CHILD,\n MUTATION_TYPE_UPDATE_TEXT,\n MUTATION_TYPE_UPDATE_PROPERTY,\n NODE_TYPE_ELEMENT,\n NODE_TYPE_TEXT,\n ROOT_ID,\n type RemoteConnection,\n type RemoteNodeSerialization,\n} from \"@remote-dom/core\";\n\n// ── Internal node representation ───────────────────────────────────\n//\n// Lightweight JS-object stand-ins for the Remote DOM tree. Each node carries\n// a stable string id that the host uses to address mutations. We do NOT use\n// `@remote-dom/core`'s `RemoteElement` Custom Element classes because:\n// 1. They require `customElements` registration which the worker realm\n// does not have by default (would force every plugin to bundle a DOM\n// polyfill).\n// 2. The plain-object form serialises identically over the connection's\n// `RemoteNodeSerialization` contract — the host's `RemoteReceiver`\n// reconstructs proper elements on its side.\n\ninterface RemoteElementInstance {\n readonly id: string;\n readonly kind: \"element\";\n readonly type: string;\n properties: Record<string, unknown>;\n eventListeners: Record<string, (...args: unknown[]) => unknown>;\n children: RemoteNode[];\n parent: RemoteParent | null;\n}\n\ninterface RemoteTextInstance {\n readonly id: string;\n readonly kind: \"text\";\n text: string;\n parent: RemoteParent | null;\n}\n\ninterface RemoteRootContainer {\n readonly id: typeof ROOT_ID;\n readonly kind: \"root\";\n children: RemoteNode[];\n readonly connection: RemoteConnection;\n}\n\ntype RemoteNode = RemoteElementInstance | RemoteTextInstance;\ntype RemoteParent = RemoteElementInstance | RemoteRootContainer;\n\nlet nextId = 0;\nfunction newId(): string {\n nextId += 1;\n return `r-${nextId.toString(36)}`;\n}\n\nfunction serialize(node: RemoteNode): RemoteNodeSerialization {\n if (node.kind === \"text\") {\n return { id: node.id, type: NODE_TYPE_TEXT, data: node.text };\n }\n // Event-listener functions are intentionally NOT included in the\n // serialised payload — they can't be structured-cloned across a\n // MessagePort and would throw `DataCloneError` on the first commit.\n // Listeners are retained locally on the RemoteElementInstance for the\n // future `ethisys:remotedom:call` round-trip (see createRemoteRoot.ts's\n // top-of-file \"What's NOT plumbed yet\" note); until that channel lands,\n // worker-side `onClick`-style handlers are no-ops on the host. The host\n // RemoteReceiver tolerates the missing field — `eventListeners` is\n // optional on `RemoteNodeSerialization`.\n return {\n id: node.id,\n type: NODE_TYPE_ELEMENT,\n element: node.type,\n properties: node.properties,\n children: node.children.map(serialize),\n };\n}\n\nfunction indexInParent(parent: RemoteParent, child: RemoteNode): number {\n return parent.children.indexOf(child);\n}\n\n// ── Prop classification ────────────────────────────────────────────\n//\n// Children (the JSX content) are handled by the reconciler tree walk;\n// `ref` and `key` are React-internal. Everything else is forwarded as a\n// remote property OR an event listener. Convention: any prop starting with\n// `on` followed by an uppercase letter is treated as an event listener.\n\nfunction isEventListenerProp(key: string): boolean {\n return key.length > 2 && key.startsWith(\"on\") && key[2] >= \"A\" && key[2] <= \"Z\";\n}\n\nfunction eventNameFromProp(propName: string): string {\n // onClick → click. The host's RemoteReceiver expects lowercase event names.\n return propName.slice(2).toLowerCase();\n}\n\nfunction isReservedProp(key: string): boolean {\n return key === \"children\" || key === \"key\" || key === \"ref\";\n}\n\nfunction classifyProps(props: Record<string, unknown>): {\n properties: Record<string, unknown>;\n eventListeners: Record<string, (...args: unknown[]) => unknown>;\n} {\n const properties: Record<string, unknown> = {};\n const eventListeners: Record<string, (...args: unknown[]) => unknown> = {};\n for (const key of Object.keys(props)) {\n if (isReservedProp(key)) {\n continue;\n }\n const value = props[key];\n if (isEventListenerProp(key) && typeof value === \"function\") {\n eventListeners[eventNameFromProp(key)] = value as (...args: unknown[]) => unknown;\n } else if (value !== undefined) {\n properties[key] = value;\n }\n }\n return { properties, eventListeners };\n}\n\n// ── HostConfig factory ─────────────────────────────────────────────\n\n/**\n * Build the react-reconciler instance bound to the supplied `RemoteConnection`.\n * Every mutation the reconciler applies translates into a single\n * `connection.mutate([record])` call; the connection forwards the record over\n * the `MessagePort` set up by `createRemoteRoot`.\n */\nexport function createReactReconciler(connection: RemoteConnection): {\n reconciler: ReactReconciler.Reconciler<\n RemoteParent,\n RemoteElementInstance,\n RemoteTextInstance,\n never,\n RemoteElementInstance\n >;\n createContainer: () => RemoteRootContainer;\n} {\n const reconciler = ReactReconciler<\n string, // type\n Record<string, unknown>, // props\n RemoteParent, // container\n RemoteElementInstance, // instance\n RemoteTextInstance, // text instance\n never, // suspense instance\n never, // hydratable instance\n RemoteElementInstance, // public instance (returned from ref)\n object, // host context\n string[], // update payload (list of changed prop names)\n never, // child set (persistent mode only)\n ReturnType<typeof setTimeout>, // timeout handle\n -1 // no timeout sentinel\n >({\n supportsMutation: true,\n supportsPersistence: false,\n supportsHydration: false,\n isPrimaryRenderer: true,\n noTimeout: -1,\n scheduleTimeout: setTimeout,\n cancelTimeout: clearTimeout,\n getCurrentEventPriority: () => DefaultEventPriority,\n getRootHostContext: () => ({}),\n getChildHostContext: () => ({}),\n prepareForCommit: () => null,\n resetAfterCommit: () => {},\n preparePortalMount: () => {},\n shouldSetTextContent: (_type, props) =>\n typeof props.children === \"string\" || typeof props.children === \"number\",\n getPublicInstance: (instance) => instance as RemoteElementInstance,\n clearContainer: (container) => {\n // Remove every direct child from the root. The host receiver mirrors\n // each removal on its side.\n while (container.children.length > 0) {\n connection.mutate([[MUTATION_TYPE_REMOVE_CHILD, container.id, 0]]);\n container.children.shift();\n }\n },\n\n // ── createInstance / createTextInstance ────────────────────────\n createInstance: (type, props): RemoteElementInstance => {\n const { properties, eventListeners } = classifyProps(props);\n return {\n id: newId(),\n kind: \"element\",\n type,\n properties,\n eventListeners,\n children: [],\n parent: null,\n };\n },\n createTextInstance: (text): RemoteTextInstance => ({\n id: newId(),\n kind: \"text\",\n text,\n parent: null,\n }),\n\n // ── Initial mount: build the subtree before attaching to root ──\n appendInitialChild: (parent, child) => {\n parent.children.push(child);\n child.parent = parent;\n },\n finalizeInitialChildren: () => false,\n\n // ── Container-level mutations (root attach/detach) ─────────────\n appendChildToContainer: (container, child) => {\n const index = container.children.length;\n container.children.push(child);\n child.parent = container;\n connection.mutate([\n [MUTATION_TYPE_INSERT_CHILD, container.id, serialize(child), index],\n ]);\n },\n removeChildFromContainer: (container, child) => {\n const index = indexInParent(container, child);\n if (index < 0) {\n return;\n }\n container.children.splice(index, 1);\n child.parent = null;\n connection.mutate([[MUTATION_TYPE_REMOVE_CHILD, container.id, index]]);\n },\n insertInContainerBefore: (container, child, beforeChild) => {\n const beforeIndex = indexInParent(container, beforeChild);\n const insertAt = beforeIndex < 0 ? container.children.length : beforeIndex;\n container.children.splice(insertAt, 0, child);\n child.parent = container;\n connection.mutate([\n [MUTATION_TYPE_INSERT_CHILD, container.id, serialize(child), insertAt],\n ]);\n },\n\n // ── Sibling-level mutations (incremental tree updates) ─────────\n appendChild: (parent, child) => {\n const index = parent.children.length;\n parent.children.push(child);\n child.parent = parent;\n connection.mutate([\n [MUTATION_TYPE_INSERT_CHILD, parent.id, serialize(child), index],\n ]);\n },\n removeChild: (parent, child) => {\n const index = indexInParent(parent, child);\n if (index < 0) {\n return;\n }\n parent.children.splice(index, 1);\n child.parent = null;\n connection.mutate([[MUTATION_TYPE_REMOVE_CHILD, parent.id, index]]);\n },\n insertBefore: (parent, child, beforeChild) => {\n const beforeIndex = indexInParent(parent, beforeChild);\n const insertAt = beforeIndex < 0 ? parent.children.length : beforeIndex;\n parent.children.splice(insertAt, 0, child);\n child.parent = parent;\n connection.mutate([\n [MUTATION_TYPE_INSERT_CHILD, parent.id, serialize(child), insertAt],\n ]);\n },\n\n // ── Update path ────────────────────────────────────────────────\n prepareUpdate: (_instance, _type, oldProps, newProps): string[] | null => {\n const changed: string[] = [];\n const allKeys = new Set<string>([\n ...Object.keys(oldProps),\n ...Object.keys(newProps),\n ]);\n for (const key of allKeys) {\n if (isReservedProp(key)) {\n continue;\n }\n if (oldProps[key] !== newProps[key]) {\n changed.push(key);\n }\n }\n return changed.length > 0 ? changed : null;\n },\n commitUpdate: (instance, updatePayload, _type, _oldProps, newProps) => {\n if (updatePayload === null) {\n return;\n }\n for (const key of updatePayload) {\n const value = newProps[key];\n if (isEventListenerProp(key) && (typeof value === \"function\" || value === undefined)) {\n const eventName = eventNameFromProp(key);\n if (typeof value === \"function\") {\n instance.eventListeners[eventName] = value as (...args: unknown[]) => unknown;\n } else {\n delete instance.eventListeners[eventName];\n }\n // Functions can't cross a MessagePort via structured clone, so the\n // wire payload only signals whether the listener is now present or\n // cleared — `true` for \"listener exists, wire it on the host once\n // the call channel lands\", `null` for \"listener removed\". See the\n // serialize() comment for the broader Phase 1 limitation.\n connection.mutate([\n [\n MUTATION_TYPE_UPDATE_PROPERTY,\n instance.id,\n eventName,\n typeof value === \"function\" ? true : null,\n 3 /* event listener */,\n ],\n ]);\n } else {\n if (value === undefined) {\n delete instance.properties[key];\n } else {\n instance.properties[key] = value;\n }\n connection.mutate([\n [MUTATION_TYPE_UPDATE_PROPERTY, instance.id, key, value],\n ]);\n }\n }\n },\n commitTextUpdate: (textInstance, _oldText, newText) => {\n textInstance.text = newText;\n connection.mutate([[MUTATION_TYPE_UPDATE_TEXT, textInstance.id, newText]]);\n },\n\n // ── No-op methods required by the HostConfig contract ──────────\n detachDeletedInstance: () => {},\n beforeActiveInstanceBlur: () => {},\n afterActiveInstanceBlur: () => {},\n prepareScopeUpdate: () => {},\n getInstanceFromScope: () => null,\n getInstanceFromNode: () => null,\n\n // Microtask scheduling — use the platform's default queueing.\n supportsMicrotasks: true,\n scheduleMicrotask:\n typeof queueMicrotask === \"function\"\n ? queueMicrotask\n : (callback) => Promise.resolve().then(callback),\n });\n\n function createContainer(): RemoteRootContainer {\n return { id: ROOT_ID, kind: \"root\", children: [], connection };\n }\n\n return { reconciler, createContainer };\n}\n\nexport { ConcurrentRoot, LegacyRoot };\nexport type { RemoteRootContainer, RemoteElementInstance, RemoteTextInstance };\n","/**\n * Contract B (worker remote-runtime) plugin-side React root.\n *\n * # The problem this solves\n *\n * A Contract B plugin runs in a sandboxed Web Worker — no `document`, no\n * `window`, no DOM. The host owns rendering: the worker constructs a\n * `RemoteElement` tree via `@remote-dom/core`, mutations forward over a\n * `MessagePort`, the host receives them and commits them to a real React tree\n * (see `coreconnect-web/src/extensions/runtime/WorkerSurfaceMount.tsx` for the\n * receiver side).\n *\n * Until now plugin authors had to hand-author the `RemoteElement` construction\n * + mutation forwarding manually. `@remote-dom/react` ships only the host-side\n * primitives (`createRemoteComponent`, `RemoteRootRenderer`, etc.) — there is\n * no worker-side React reconciler in the package. This helper provides one.\n *\n * # What this is\n *\n * The public surface (`createRemoteRoot(port, options)`) plus a working\n * `react-reconciler` HostConfig that commits to a Remote DOM tree and\n * forwards mutation records over the `MessagePort` to the host receiver.\n * Authors call `root.render(<App />)` and the host's `WorkerSurfaceMount`\n * sees the result.\n *\n * # What's NOT plumbed yet (Phase 1 limitation)\n *\n * Event-listener round-trip. The host transport currently has no\n * `ethisys:remotedom:call` channel — `WorkerRemoteDomTransport` only\n * forwards `ethisys:remotedom` payloads from worker → host, never the\n * other direction. So a worker-side `onClick={() => ...}` is **registered\n * locally** but the host can't call it. The reconciler retains every\n * listener on its in-memory `RemoteElementInstance` so the wiring is ready\n * the moment the call channel lands; until then, plugin authors should\n * keep interactive surfaces on Contract A.\n *\n * # The API\n *\n * ```ts\n * import { createRemoteRoot } from \"@ethisyscore/extension-runtime/plugin\";\n *\n * export async function activate(port: MessagePort): Promise<void> {\n * const root = createRemoteRoot(port);\n * root.render(<App />);\n * }\n * ```\n *\n * `<App />` is plain React. Any component shipped by the host's frozen\n * import-map allowlist (the closed Contract B primitive vocabulary —\n * `Button`, `DataTable`, `Form`, `Card`, `Tabs`, `Select`, `Alert`, etc.)\n * renders. The reconciler walks the React tree and commits to a\n * `RemoteRootElement`; mutation records forward over the port; the host's\n * `RemoteReceiver` commits the result into the real React tree.\n *\n * # Wire shape\n *\n * Every reconciler mutation is posted as\n * `{ type: \"ethisys:remotedom\", payload: RemoteMutationRecord[] }` to match\n * `WorkerRemoteDomTransport`'s `RemoteDomMessage` contract — the host\n * extracts `payload` and feeds it straight into `receiver.mutate(records)`.\n * `BatchingRemoteConnection` (controlled via `options.batchMutations`)\n * collapses contiguous mutations into a single port post per commit.\n */\nimport type { RemoteConnection, RemoteMutationRecord } from \"@remote-dom/core\";\nimport { BatchingRemoteConnection } from \"@remote-dom/core/elements\";\nimport type { ReactNode } from \"react\";\nimport {\n createReactReconciler,\n LegacyRoot,\n type RemoteRootContainer,\n} from \"./reconciler\";\n\n/**\n * Options for {@link createRemoteRoot}. Reserved for forward compatibility —\n * Phase 1 ships with zero required options. Adding fields here is additive;\n * removing them is a breaking change.\n */\nexport interface CreateRemoteRootOptions\n{\n /**\n * When supplied, the reconciler batches contiguous mutations into a single\n * `port.postMessage` rather than firing one per mutation. Default `true`\n * — measurably reduces host-side commit cost on the first render of a\n * non-trivial tree.\n */\n batchMutations?: boolean;\n\n /**\n * Override the connection factory. Reserved for tests; production callers\n * never pass this. The factory's contract is \"build a RemoteConnection\n * that forwards mutations over the supplied port\"; the default uses\n * `createRemoteConnection` from `@remote-dom/core`.\n */\n connectionFactory?: (port: MessagePort) => RemoteConnection;\n}\n\n/**\n * Plugin-side React root. Mirrors the shape of `ReactDOMClient.Root` so\n * authors familiar with `createRoot().render(...)` find the same API on the\n * worker side.\n */\nexport interface RemoteRoot\n{\n /**\n * Render a React element tree against the worker's `RemoteRootElement`.\n * The reconciler commits to the root, the mutation observer forwards\n * mutations over the port, the host re-renders. Idempotent — calling\n * `render` twice with the same element is fine; the reconciler dedupes.\n */\n render(element: ReactNode): void;\n\n /**\n * Tear down the reconciled tree and stop forwarding mutations. Authors\n * should call this from a `Symbol.dispose` or equivalent when the worker\n * is shutting down — leaking the reconciler holds the `MessagePort` open\n * and prevents the worker from being collected.\n */\n unmount(): void;\n}\n\n/**\n * Construct a plugin-side React root that commits to a `RemoteRootElement`\n * and forwards mutations over the supplied `MessagePort`.\n *\n * **API stability:** the function signature is stable for Phase 1. The\n * options bag is forward-compatible (additive only).\n *\n * **Implementation status:** the connection + root construction lands in this\n * commit. The `react-reconciler` `HostConfig` is a scaffold — `render()`\n * throws a structured error directing authors to the W1A tracking issue.\n * Authors should treat this commit as \"the API is locked, the reconciler is\n * being authored.\" See follow-on commits on the `feature/contract-b-create-remote-root-w1a`\n * branch.\n */\nexport function createRemoteRoot(\n port: MessagePort,\n options: CreateRemoteRootOptions = {},\n): RemoteRoot\n{\n if (port === undefined || port === null)\n {\n throw new TypeError(\n \"[createRemoteRoot] `port` is required. Pass the MessagePort the host transferred via the worker's `activate(port)` entry point.\",\n );\n }\n\n // Build the connection that forwards Remote DOM mutations over the port.\n // The default factory uses `@remote-dom/core`'s `createRemoteConnection`;\n // tests inject their own factory via `options.connectionFactory`.\n const baseConnection = (options.connectionFactory ?? defaultConnectionFactory)(port);\n\n const connection: RemoteConnection = options.batchMutations === false\n ? baseConnection\n : new BatchingRemoteConnection(baseConnection);\n\n // W1A — lazy reconciler + container construction. The reconciler is built\n // once on first render(); subsequent calls reuse the same container so React\n // commits behave like ReactDOM.createRoot(...).render(...) — call render\n // multiple times to update; call unmount to tear down.\n const { reconciler, createContainer } = createReactReconciler(connection);\n let container: RemoteRootContainer | null = null;\n let fiberRoot: ReturnType<typeof reconciler.createContainer> | null = null;\n\n return {\n render(element: ReactNode): void\n {\n if (container === null)\n {\n container = createContainer();\n fiberRoot = reconciler.createContainer(\n container,\n LegacyRoot,\n null, // hydration callbacks\n false, // isStrictMode\n null, // concurrentUpdatesByDefaultOverride\n \"\", // identifierPrefix\n (error: Error) =>\n {\n // The host's SurfaceErrorBoundary catches render-time\n // failures via the connection's call() path; mirroring\n // them locally as a console.error is the best we can do\n // from inside the worker.\n // eslint-disable-next-line no-console -- worker console is pumped to the host logger.\n console.error(\"[createRemoteRoot] render error\", error);\n },\n null, // transitionCallbacks\n );\n }\n reconciler.updateContainer(element, fiberRoot!, null, null);\n },\n unmount(): void\n {\n if (fiberRoot !== null)\n {\n reconciler.updateContainer(null, fiberRoot, null, null);\n fiberRoot = null;\n container = null;\n }\n // The connection itself does NOT close the port — plugins may have\n // side-channel consumers. Authors that want a hard tear-down call\n // `port.close()` themselves after unmount().\n },\n };\n}\n\n// ── Internals ──────────────────────────────────────────────────────────\n\n/**\n * Default factory: build a `RemoteConnection` that posts mutation records\n * as the `payload` of a single `ethisys:remotedom` port message — the\n * wire shape `WorkerRemoteDomTransport`'s `RemoteDomMessage` declares\n * (see `host/worker/transport.ts`'s `handlePortMessage`: it forwards\n * `(message as RemoteDomMessage).payload` straight into the host's\n * `RemoteReceiver.mutate(records)` call).\n *\n * Going through `createRemoteConnection` (which fires per-op callbacks)\n * was the wrong abstraction for this wire — the host expects the raw\n * record list under `payload`, not bespoke `op`-suffixed envelopes. By\n * implementing the connection directly we hand the reconciler's\n * mutation records straight to the host receiver without translation.\n *\n * Wrapping in `BatchingRemoteConnection` (controlled via\n * `options.batchMutations`) collapses contiguous mutation calls into a\n * single `port.postMessage` payload, which is the difference between \"one\n * postMessage per appended child\" and \"one postMessage per React commit\"\n * — measurably cheaper for the first paint of a non-trivial tree.\n */\nfunction defaultConnectionFactory(port: MessagePort): RemoteConnection\n{\n return {\n mutate(records: readonly RemoteMutationRecord[]): void\n {\n // Snapshot to a plain array so the host receiver isn't handed a\n // reference into the reconciler's internal buffer (the BatchingRemoteConnection\n // wrapper recycles its own array between flushes).\n port.postMessage({ type: \"ethisys:remotedom\", payload: records.slice() });\n },\n call(_id, _method, ..._args): unknown\n {\n // Worker → host method invocation. Reserved for the future\n // `ethisys:remotedom:call` channel — `WorkerRemoteDomTransport`\n // doesn't yet handle a `call`-shaped reply path, so event-\n // listener round-trip is still on the Phase 1 follow-on list\n // (see this file's top-of-file \"What's NOT plumbed yet\" note).\n return undefined;\n },\n };\n}\n\n","/**\n * W1B — MessagePort-backed McpTransport for Contract B (worker remote-runtime)\n * plugins.\n *\n * The plugin-side React hooks (`useMcpResource`, `useMcpTool`) consume an\n * {@link McpTransport} — an abstraction over the host call. Contract A\n * (host-rendered) plugins pick up the host-injected transport via\n * `ExtensionRuntimeProvider`. Contract B plugins run in a Web Worker with\n * no shared object surface — the only channel is the `MessagePort` the\n * host transferred via `activate(port)`. This helper bridges the two\n * worlds: it exposes the {@link McpTransport} contract on the worker side\n * and serialises every call into a request/response envelope over the\n * port.\n *\n * # The protocol on the wire\n *\n * The wire shape mirrors `WorkerRemoteDomTransport` (in `host/worker/transport.ts`)\n * one-for-one — that transport is the host receiver and decides what a \"well-\n * formed\" message looks like. Two request shapes, two `:result`-suffixed reply\n * shapes, one abort envelope. Each call mints a fresh request id (`req-{n}`).\n *\n * ```jsonc\n * // worker → host\n * { type: \"ethisys:mcp:getResource\", id: \"req-3\", uri: \"tickets://home\" }\n * { type: \"ethisys:mcp:invokeTool\", id: \"req-4\", name: \"tickets:open\", args: { ... } }\n *\n * // host → worker (matching id, suffixed type)\n * { type: \"ethisys:mcp:getResource:result\", id: \"req-3\", ok: true, data: { uri, data } }\n * { type: \"ethisys:mcp:invokeTool:result\", id: \"req-4\", ok: false, error: \"...\" }\n * ```\n *\n * Requests honour the optional `AbortSignal`. On abort, the transport posts\n * a `{ type: \"ethisys:mcp:abort\", id }` envelope so the host can cancel\n * in-flight work, then rejects the pending promise with an `AbortError`-shaped\n * `Error` so the consuming hooks see the same shape as native fetch cancellation.\n *\n * # Authoring shape\n *\n * ```ts\n * export async function activate(port: MessagePort): Promise<void> {\n * const transport = createPortMcpTransport(port);\n * const root = createRemoteRoot(port);\n * root.render(\n * <ExtensionRuntimeProvider transport={transport}>\n * <App />\n * </ExtensionRuntimeProvider>,\n * );\n * }\n * ```\n *\n * `<App />` uses `useMcpResource` / `useMcpTool` as it would on the host\n * side; the transport translates each call into the port envelope.\n */\nimport type { McpTransport } from \"./transport\";\n\n/**\n * Options for {@link createPortMcpTransport}. Reserved for forward\n * compatibility — the public surface is empty in Phase 1.\n */\nexport interface CreatePortMcpTransportOptions\n{\n /**\n * Override the request-id generator. Reserved for tests so they can\n * make request ids deterministic. Production callers never pass this.\n */\n requestIdFactory?: () => string;\n\n /**\n * Override the port's `addEventListener` / `removeEventListener` /\n * `postMessage` triple. Reserved for tests so the transport can be\n * exercised without instantiating a real MessageChannel.\n */\n portShimForTests?: PortShim;\n}\n\n/**\n * Minimal subset of the MessagePort surface the transport actually uses.\n * Exposed so tests can construct a polyfill without faking the full\n * MessagePort.\n */\nexport interface PortShim\n{\n addEventListener(\n type: \"message\",\n listener: (event: { data: unknown }) => void,\n ): void;\n removeEventListener(\n type: \"message\",\n listener: (event: { data: unknown }) => void,\n ): void;\n postMessage(value: unknown): void;\n}\n\n// ── Wire-shape constants ────────────────────────────────────────────\n//\n// These four strings are the contract with `WorkerRemoteDomTransport`. Any\n// rename here breaks the host receiver silently — the unknown message hits\n// the host's default branch and is dropped. Keep these in sync with\n// `host/worker/transport.ts`'s `handlePortMessage` switch / result-type\n// strings.\nconst WIRE_INVOKE_TOOL_REQUEST = \"ethisys:mcp:invokeTool\";\nconst WIRE_INVOKE_TOOL_RESULT = \"ethisys:mcp:invokeTool:result\";\nconst WIRE_GET_RESOURCE_REQUEST = \"ethisys:mcp:getResource\";\nconst WIRE_GET_RESOURCE_RESULT = \"ethisys:mcp:getResource:result\";\nconst WIRE_ABORT = \"ethisys:mcp:abort\";\n\ntype McpResultType = typeof WIRE_INVOKE_TOOL_RESULT | typeof WIRE_GET_RESOURCE_RESULT;\n\ninterface McpResultEnvelope\n{\n type: McpResultType;\n id: string;\n ok?: boolean;\n error?: string;\n data?: unknown;\n}\n\nfunction isMcpResultEnvelope(value: unknown): value is McpResultEnvelope\n{\n if (value === null || typeof value !== \"object\")\n {\n return false;\n }\n const v = value as { type?: unknown; id?: unknown };\n return (v.type === WIRE_INVOKE_TOOL_RESULT || v.type === WIRE_GET_RESOURCE_RESULT)\n && typeof v.id === \"string\";\n}\n\n/**\n * Construct an {@link McpTransport} backed by a MessagePort.\n *\n * @param port The host-transferred MessagePort for the worker surface.\n * @param options Reserved for forward compatibility / test injection.\n *\n * @returns A transport implementation honouring the {@link McpTransport}\n * contract — fetch a resource, invoke a tool, observe abort\n * signals, settle the promise on the host's reply envelope.\n */\nexport function createPortMcpTransport(\n port: MessagePort | PortShim,\n options: CreatePortMcpTransportOptions = {},\n): McpTransport\n{\n if (port === undefined || port === null)\n {\n throw new TypeError(\n \"[createPortMcpTransport] `port` is required. Pass the MessagePort the host transferred via the worker's `activate(port)` entry point.\",\n );\n }\n\n const shim: PortShim = options.portShimForTests ?? port as PortShim;\n const factory = options.requestIdFactory ?? defaultRequestIdFactory();\n\n type Resolver = {\n resolve: (value: unknown) => void;\n reject: (reason: unknown) => void;\n };\n const pending = new Map<string, Resolver>();\n\n function onMessage(event: { data: unknown }): void\n {\n if (!isMcpResultEnvelope(event.data))\n {\n return;\n }\n const reply = event.data;\n const resolver = pending.get(reply.id);\n if (resolver === undefined)\n {\n return;\n }\n pending.delete(reply.id);\n if (reply.ok === true)\n {\n // Host posts the payload under `data` (matching\n // `WorkerRemoteDomTransport.safePostMessage`). For getResource\n // calls the data is the resource body; for invokeTool calls it\n // is the tool's return value.\n resolver.resolve(reply.data);\n }\n else\n {\n const error = new Error(\n typeof reply.error === \"string\" ? reply.error : \"Unknown host error\",\n );\n error.name = \"McpHostError\";\n resolver.reject(error);\n }\n }\n\n shim.addEventListener(\"message\", onMessage);\n // MessagePort.addEventListener doesn't auto-start the port — without an\n // explicit `start()`, the message queue stays paused and the host's\n // replies never reach `onMessage`. This is a no-op on plain\n // EventTarget-shaped shims used in tests (the type guard guarantees\n // safety) but is required on real MessagePort instances.\n const realPort = port as Partial<MessagePort>;\n if (!options.portShimForTests && typeof realPort.start === \"function\")\n {\n realPort.start();\n }\n\n function send(\n requestType: typeof WIRE_INVOKE_TOOL_REQUEST | typeof WIRE_GET_RESOURCE_REQUEST,\n body: Record<string, unknown>,\n signal?: AbortSignal,\n ): Promise<unknown>\n {\n const id = factory();\n return new Promise<unknown>((resolve, reject) =>\n {\n if (signal?.aborted === true)\n {\n reject(abortError());\n return;\n }\n pending.set(id, { resolve, reject });\n\n const onAbort = (): void =>\n {\n if (!pending.has(id))\n {\n return;\n }\n pending.delete(id);\n shim.postMessage({ type: WIRE_ABORT, id });\n reject(abortError());\n };\n signal?.addEventListener(\"abort\", onAbort, { once: true });\n\n shim.postMessage({ type: requestType, id, ...body });\n });\n }\n\n return {\n async getResource<T>(uri: string, signal?: AbortSignal): Promise<{ uri: string; data: T }>\n {\n // Host sends only the resource body under `data`. Reconstruct the\n // `{ uri, data }` envelope the McpTransport contract returns —\n // `uri` is round-tripped from the request because the resolver\n // only sees the reply.\n const data = await send(WIRE_GET_RESOURCE_REQUEST, { uri }, signal);\n return { uri, data: data as T };\n },\n async invokeTool<TReq, TRes>(name: string, args: TReq, signal?: AbortSignal): Promise<TRes>\n {\n const data = await send(WIRE_INVOKE_TOOL_REQUEST, { name, args: args as unknown }, signal);\n return data as TRes;\n },\n };\n}\n\nfunction defaultRequestIdFactory(): () => string\n{\n let counter = 0;\n return (): string =>\n {\n counter += 1;\n return `req-${counter}`;\n };\n}\n\nfunction abortError(): Error\n{\n const e = new Error(\"Aborted\");\n e.name = \"AbortError\";\n return e;\n}\n"]}
|