@deepseek-ai/dsh-cordis-host-runner 0.0.1-rc.3

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/lib/index.js ADDED
@@ -0,0 +1,2573 @@
1
+ import z from "@deepseek-ai/schemastery";
2
+ import { createUserMessage } from "@deepseek-ai/dsh-llm";
3
+ import { Remote, TypertRemoteService } from "@deepseek-ai/dsh-typert-protocol";
4
+ import { Context, Service } from "@deepseek-ai/cordis";
5
+ import { scopeOf } from "@deepseek-ai/dsh-scope";
6
+ import { assertSupportedJsonSchema, defineTool, validateJsonSchemaValue } from "@deepseek-ai/dsh-tools";
7
+ import { snapshotJsonValue } from "@deepseek-ai/dsh-session";
8
+ import { Script, createContext, runInContext } from "node:vm";
9
+ //#region lib/types/guard.js
10
+ /**
11
+ * The registration boundary between a sandboxed host half and the real runtime: ParameterSchemaSpec
12
+ * normalization + validation with teaching errors, the marker-guarded `harness.defineTool` /
13
+ * `harness.registerTool` pair, the `harness.handle` invoke-handler normalizer, the SANDBOX CONTEXT
14
+ * FAÇADE a running plugin's `apply` receives in place of the real `ctx`, and the plugin-shape
15
+ * helpers the run lifecycle narrows sandbox return values with. The façade is a whitelist of
16
+ * lifecycle-safe verbs and declared services; framework internals and context-valued service
17
+ * returns are denied.
18
+ *
19
+ * VM-realm schemas and canonical values are rebuilt as host objects, while rendered content and
20
+ * presentation metadata are shape-checked before entering the registry. Common JSON-Schema spellings are normalized when they
21
+ * have one meaning; invalid vocabulary fails during registration with a teaching error.
22
+ * @module @deepseek-ai/dsh-cordis-host-runner/guard
23
+ */
24
+ const DYNAMIC_TOOL = Symbol("cordis-host-runner.dynamic-tool");
25
+ const SCHEMA_TYPES = new Set([
26
+ "string",
27
+ "number",
28
+ "integer",
29
+ "boolean",
30
+ "null",
31
+ "object",
32
+ "array",
33
+ "json"
34
+ ]);
35
+ const VALID_TYPES = "'string' | 'number' | 'integer' | 'boolean' | 'null' | 'object' | 'array' | 'json'";
36
+ const ANNOTATION_KEYS = [
37
+ "description",
38
+ "title",
39
+ "default",
40
+ "examples"
41
+ ];
42
+ function isPlainRecord(value) {
43
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
44
+ const prototype = Object.getPrototypeOf(value);
45
+ return prototype === null || typeof prototype === "object" && Object.getPrototypeOf(prototype) === null && hasIntrinsicConstructor(prototype, "Object");
46
+ }
47
+ /** Whether a realm-owned intrinsic prototype is backed by its native constructor. */
48
+ function hasIntrinsicConstructor(prototype, name) {
49
+ const constructor = Object.getOwnPropertyDescriptor(prototype, "constructor")?.value;
50
+ if (typeof constructor !== "function") return false;
51
+ try {
52
+ return constructor.name === name && constructor.prototype === prototype && Function.prototype.toString.call(constructor) === `function ${name}() { [native code] }`;
53
+ } catch {
54
+ return false;
55
+ }
56
+ }
57
+ /** Whether an array uses one realm's intrinsic Array prototype rather than a subclass. */
58
+ function hasPlainArrayPrototype(value) {
59
+ const prototype = Object.getPrototypeOf(value);
60
+ if (!Array.isArray(prototype) || !hasIntrinsicConstructor(prototype, "Array")) return false;
61
+ const objectPrototype = Object.getPrototypeOf(prototype);
62
+ return typeof objectPrototype === "object" && objectPrototype !== null && Object.getPrototypeOf(objectPrototype) === null && hasIntrinsicConstructor(objectPrototype, "Object");
63
+ }
64
+ /** Whether a schema list is a dense intrinsic array with no JSON-invisible decorations. */
65
+ function isDensePlainArray(value) {
66
+ if (!Array.isArray(value) || !hasPlainArrayPrototype(value) || Reflect.ownKeys(value).length !== value.length + 1) return false;
67
+ for (let index = 0; index < value.length; index++) if (!Object.hasOwn(value, index)) return false;
68
+ return true;
69
+ }
70
+ /** Reject schema records whose declarations would disappear from object enumeration. */
71
+ function assertSchemaContainerKeys(value, path) {
72
+ if (Reflect.ownKeys(value).some((key) => typeof key !== "string" || !Object.prototype.propertyIsEnumerable.call(value, key))) throw new Error(`harness.defineTool ${path} must contain only own enumerable string keys`);
73
+ }
74
+ /** Materialize realm-foreign lossless JSON without allowing JSON.stringify coercions; `path` carries the caller's own error prefix. */
75
+ function cloneJson(value, path) {
76
+ const ancestors = /* @__PURE__ */ new Set();
77
+ let root;
78
+ const assign = (destination, item) => {
79
+ if (destination.kind === "root") {
80
+ root = item;
81
+ return;
82
+ }
83
+ if (destination.kind === "array") {
84
+ destination.target[destination.index] = item;
85
+ return;
86
+ }
87
+ Object.defineProperty(destination.target, destination.key, {
88
+ value: item,
89
+ enumerable: true,
90
+ configurable: true,
91
+ writable: true
92
+ });
93
+ };
94
+ const reject = (at) => {
95
+ throw new Error(`${at} must be lossless JSON data (objects, arrays, strings, numbers, booleans, null) — not a class instance, function, Map/Set, Date, or undefined. Return a plain object built from the values you need, or \`return null\` when the caller needs no value back.`);
96
+ };
97
+ const tasks = [{
98
+ kind: "visit",
99
+ value,
100
+ path,
101
+ destination: { kind: "root" }
102
+ }];
103
+ for (let task = tasks.pop(); task !== void 0; task = tasks.pop()) {
104
+ if (task.kind === "leave") {
105
+ ancestors.delete(task.source);
106
+ continue;
107
+ }
108
+ if (task.kind === "array-item") {
109
+ if (!Object.hasOwn(task.source, task.index)) reject(task.path);
110
+ tasks.push({
111
+ kind: "visit",
112
+ value: task.source[task.index],
113
+ path: `${task.path}[${task.index}]`,
114
+ destination: {
115
+ kind: "array",
116
+ target: task.target,
117
+ index: task.index
118
+ }
119
+ });
120
+ continue;
121
+ }
122
+ const current = task.value;
123
+ if (current === null || typeof current === "string" || typeof current === "boolean") {
124
+ assign(task.destination, current);
125
+ continue;
126
+ }
127
+ if (typeof current === "number") {
128
+ if (!Number.isFinite(current) || Object.is(current, -0)) reject(task.path);
129
+ assign(task.destination, current);
130
+ continue;
131
+ }
132
+ if (typeof current !== "object" || ancestors.has(current)) reject(task.path);
133
+ if (Array.isArray(current)) {
134
+ if (!hasPlainArrayPrototype(current) || Reflect.ownKeys(current).length !== current.length + 1) reject(task.path);
135
+ const output = [];
136
+ assign(task.destination, output);
137
+ ancestors.add(current);
138
+ tasks.push({
139
+ kind: "leave",
140
+ source: current
141
+ });
142
+ for (let index = current.length - 1; index >= 0; index--) tasks.push({
143
+ kind: "array-item",
144
+ source: current,
145
+ index,
146
+ path: task.path,
147
+ target: output
148
+ });
149
+ continue;
150
+ }
151
+ if (!isPlainRecord(current)) reject(task.path);
152
+ const record = current;
153
+ if (Reflect.ownKeys(record).some((key) => typeof key !== "string" || !Object.prototype.propertyIsEnumerable.call(record, key))) reject(task.path);
154
+ const output = {};
155
+ assign(task.destination, output);
156
+ ancestors.add(record);
157
+ tasks.push({
158
+ kind: "leave",
159
+ source: record
160
+ });
161
+ const entries = Object.entries(record);
162
+ for (let index = entries.length - 1; index >= 0; index--) {
163
+ const entry = entries[index];
164
+ /* v8 ignore next -- the loop is bounded by the captured entry count. */
165
+ if (entry === void 0) continue;
166
+ tasks.push({
167
+ kind: "visit",
168
+ value: entry[1],
169
+ path: `${task.path}.${entry[0]}`,
170
+ destination: {
171
+ kind: "object",
172
+ target: output,
173
+ key: entry[0]
174
+ }
175
+ });
176
+ }
177
+ }
178
+ return root;
179
+ }
180
+ /** Copy and realm-materialize the shared annotation vocabulary. */
181
+ function copyAnnotations(value, output, path) {
182
+ if (Object.hasOwn(value, "description")) output.description = value.description;
183
+ if (Object.hasOwn(value, "title")) output.title = value.title;
184
+ if (Object.hasOwn(value, "default")) output.default = cloneJson(value.default, `harness.defineTool ${path}.default`);
185
+ if (Object.hasOwn(value, "examples")) output.examples = cloneJson(value.examples, `harness.defineTool ${path}.examples`);
186
+ }
187
+ /** Reject sandbox schema keys that the unified DSL would otherwise ignore. */
188
+ function assertSchemaKeys(value, path, allowed) {
189
+ assertSchemaContainerKeys(value, path);
190
+ for (const key of Object.keys(value)) if (!allowed.includes(key)) throw new Error(`harness.defineTool ${path}.${key} is not supported by the unified schema DSL`);
191
+ }
192
+ /**
193
+ * Normalize a sandbox-provided `parameters` value into a fresh host-realm
194
+ * ParameterSchemaSpec. A raw JSON-Schema object wrapper retains its open root
195
+ * default, while the direct DSL is already an implicit open property map.
196
+ */
197
+ function normalizeParameterSchemaSpec(value, path = "parameters") {
198
+ if (!isPlainRecord(value)) throw new Error(`harness.defineTool ${path} must be a ParameterSchemaSpec object`);
199
+ if (value.type === "object") {
200
+ assertSchemaKeys(value, path, [
201
+ "type",
202
+ "properties",
203
+ "required",
204
+ "additionalProperties",
205
+ ...ANNOTATION_KEYS
206
+ ]);
207
+ if (!isPlainRecord(value.properties)) throw new Error(`harness.defineTool ${path}.properties must be an object of schemas`);
208
+ if (Object.hasOwn(value, "additionalProperties") && value.additionalProperties !== true) throw new Error(`harness.defineTool ${path}.additionalProperties must be true or omitted because the implicit parameter root is open`);
209
+ if (Object.hasOwn(value, "required") && value.required === void 0) throw new Error(`harness.defineTool ${path}.required must be an array of declared property names`);
210
+ const required = normalizeRequiredNames(value.required, value.properties, `${path}.required`);
211
+ const rootAnnotations = {};
212
+ copyAnnotations(value, rootAnnotations, path);
213
+ return {
214
+ spec: normalizePropertyMap(value.properties, path, required, true),
215
+ ...Object.keys(rootAnnotations).length === 0 ? {} : { rootAnnotations }
216
+ };
217
+ }
218
+ return { spec: normalizePropertyMap(value, path, /* @__PURE__ */ new Set(), false) };
219
+ }
220
+ /** Validate raw required names and return their lookup set. */
221
+ function normalizeRequiredNames(value, properties, path) {
222
+ if (value === void 0) return /* @__PURE__ */ new Set();
223
+ if (!isDensePlainArray(value)) throw new Error(`harness.defineTool ${path} must be an array of declared property names`);
224
+ const names = /* @__PURE__ */ new Set();
225
+ for (let index = 0; index < value.length; index++) {
226
+ const name = value[index];
227
+ if (typeof name !== "string") throw new Error(`harness.defineTool ${path} must be an array of declared property names`);
228
+ names.add(name);
229
+ if (!Object.hasOwn(properties, name)) throw new Error(`harness.defineTool ${path} names undeclared property ${JSON.stringify(name)}`);
230
+ }
231
+ return names;
232
+ }
233
+ /** Install one normalized node without `__proto__` assignment semantics. */
234
+ function assignNormalizedValue(destination, value) {
235
+ if (destination.kind === "property") Object.defineProperty(destination.target, destination.key, {
236
+ value,
237
+ enumerable: true,
238
+ configurable: true,
239
+ writable: true
240
+ });
241
+ else if (destination.kind === "item") destination.target.items = value;
242
+ else destination.target[destination.index] = value;
243
+ }
244
+ /** Install one normalized property map at its root or containing object. */
245
+ function assignNormalizedMap(destination, value) {
246
+ if (destination.kind === "root") destination.holder.value = value;
247
+ else destination.target.properties = value;
248
+ }
249
+ /** Normalize one implicit property map and all descendants with explicit work frames. */
250
+ function normalizePropertyMap(entries, path, requiredNames, raw) {
251
+ const holder = {};
252
+ const ancestors = /* @__PURE__ */ new Set();
253
+ const tasks = [{
254
+ kind: "map",
255
+ entries,
256
+ path,
257
+ requiredNames,
258
+ raw,
259
+ destination: {
260
+ kind: "root",
261
+ holder
262
+ }
263
+ }];
264
+ for (let task = tasks.pop(); task !== void 0; task = tasks.pop()) {
265
+ if (task.kind === "leave") {
266
+ ancestors.delete(task.value);
267
+ continue;
268
+ }
269
+ if (task.kind === "map") {
270
+ if (ancestors.has(task.entries)) throw new Error(`harness.defineTool ${task.path} is circular`);
271
+ assertSchemaContainerKeys(task.entries, task.path);
272
+ ancestors.add(task.entries);
273
+ const spec = {};
274
+ assignNormalizedMap(task.destination, spec);
275
+ tasks.push({
276
+ kind: "leave",
277
+ value: task.entries
278
+ });
279
+ const mapEntries = Object.entries(task.entries);
280
+ for (let index = mapEntries.length - 1; index >= 0; index--) {
281
+ const entry = mapEntries[index];
282
+ /* v8 ignore next -- the loop is bounded by the captured entry count. */
283
+ if (entry === void 0) continue;
284
+ tasks.push({
285
+ kind: "value",
286
+ value: entry[1],
287
+ path: `${task.path}.${entry[0]}`,
288
+ forceRequired: task.requiredNames.has(entry[0]),
289
+ raw: task.raw,
290
+ parameterProperty: true,
291
+ destination: {
292
+ kind: "property",
293
+ target: spec,
294
+ key: entry[0]
295
+ }
296
+ });
297
+ }
298
+ continue;
299
+ }
300
+ const { value, path } = task;
301
+ if (!isPlainRecord(value)) throw new Error(`harness.defineTool ${path} must be a ParameterSchemaSpec property object`);
302
+ assertSchemaContainerKeys(value, path);
303
+ if (ancestors.has(value)) throw new Error(`harness.defineTool ${path} is circular`);
304
+ ancestors.add(value);
305
+ const requiredKey = task.parameterProperty && !task.raw ? ["required"] : [];
306
+ if (task.parameterProperty && task.raw && Object.hasOwn(value, "required") && value.type !== "object") throw new Error(`harness.defineTool ${path}.required belongs to the containing raw object schema`);
307
+ if (task.parameterProperty && !task.raw && Object.hasOwn(value, "required") && value.required !== true) throw new Error(`harness.defineTool ${path}.required must be true when present`);
308
+ const prop = {};
309
+ assignNormalizedValue(task.destination, prop);
310
+ tasks.push({
311
+ kind: "leave",
312
+ value
313
+ });
314
+ if (task.forceRequired || value.required === true) prop.required = true;
315
+ copyAnnotations(value, prop, path);
316
+ if (Object.hasOwn(value, "oneOf")) {
317
+ assertSchemaKeys(value, path, [
318
+ "oneOf",
319
+ ...requiredKey,
320
+ ...ANNOTATION_KEYS
321
+ ]);
322
+ if (!isDensePlainArray(value.oneOf) || value.oneOf.length < 2) throw new Error(`harness.defineTool ${path}.oneOf must contain at least two schemas`);
323
+ const oneOf = [];
324
+ prop.oneOf = oneOf;
325
+ for (let index = value.oneOf.length - 1; index >= 0; index--) tasks.push({
326
+ kind: "value",
327
+ value: value.oneOf[index],
328
+ path: `${path}.oneOf[${index}]`,
329
+ forceRequired: false,
330
+ raw: task.raw,
331
+ parameterProperty: false,
332
+ destination: {
333
+ kind: "one-of",
334
+ target: oneOf,
335
+ index
336
+ }
337
+ });
338
+ continue;
339
+ }
340
+ if (task.raw && !Object.hasOwn(value, "type")) {
341
+ assertSchemaKeys(value, path, ANNOTATION_KEYS);
342
+ prop.type = "json";
343
+ continue;
344
+ }
345
+ if (!SCHEMA_TYPES.has(value.type) || task.raw && value.type === "json") throw new Error(`harness.defineTool ${path} must declare a valid type: ${VALID_TYPES} (got ${JSON.stringify(value.type)})`);
346
+ const type = value.type;
347
+ prop.type = type;
348
+ switch (type) {
349
+ case "object":
350
+ assertSchemaKeys(value, path, [
351
+ "type",
352
+ "properties",
353
+ "additionalProperties",
354
+ ...requiredKey,
355
+ ...task.raw ? ["required"] : [],
356
+ ...ANNOTATION_KEYS
357
+ ]);
358
+ if (!task.raw && (!Object.hasOwn(value, "additionalProperties") || typeof value.additionalProperties !== "boolean")) throw new Error(`harness.defineTool ${path}.additionalProperties must be explicitly true or false`);
359
+ if (task.raw && Object.hasOwn(value, "additionalProperties") && typeof value.additionalProperties !== "boolean") throw new Error(`harness.defineTool ${path}.additionalProperties must be a boolean`);
360
+ if (task.raw && Object.hasOwn(value, "required") && value.required === void 0) throw new Error(`harness.defineTool ${path}.required must be an array of declared property names`);
361
+ prop.additionalProperties = task.raw ? value.additionalProperties ?? true : value.additionalProperties;
362
+ if (Object.hasOwn(value, "properties")) {
363
+ const properties = value.properties;
364
+ if (!isPlainRecord(properties)) throw new Error(`harness.defineTool ${path}.properties must be an object of schemas`);
365
+ const nestedRequired = task.raw ? normalizeRequiredNames(value.required, properties, `${path}.required`) : /* @__PURE__ */ new Set();
366
+ tasks.push({
367
+ kind: "map",
368
+ entries: properties,
369
+ path: `${path}.properties`,
370
+ requiredNames: nestedRequired,
371
+ raw: task.raw,
372
+ destination: {
373
+ kind: "properties",
374
+ target: prop
375
+ }
376
+ });
377
+ } else if (task.raw && value.required !== void 0) normalizeRequiredNames(value.required, {}, `${path}.required`);
378
+ break;
379
+ case "array":
380
+ assertSchemaKeys(value, path, [
381
+ "type",
382
+ "items",
383
+ ...requiredKey,
384
+ ...ANNOTATION_KEYS
385
+ ]);
386
+ if (Object.hasOwn(value, "items")) tasks.push({
387
+ kind: "value",
388
+ value: value.items,
389
+ path: `${path}.items`,
390
+ forceRequired: false,
391
+ raw: task.raw,
392
+ parameterProperty: false,
393
+ destination: {
394
+ kind: "item",
395
+ target: prop
396
+ }
397
+ });
398
+ break;
399
+ case "string":
400
+ case "number":
401
+ case "integer":
402
+ case "boolean":
403
+ case "null":
404
+ assertSchemaKeys(value, path, [
405
+ "type",
406
+ "enum",
407
+ "const",
408
+ ...requiredKey,
409
+ ...ANNOTATION_KEYS
410
+ ]);
411
+ if (Object.hasOwn(value, "enum")) {
412
+ if (!isDensePlainArray(value.enum) || value.enum.length === 0) throw new Error(`harness.defineTool ${path}.enum must be a non-empty array`);
413
+ prop.enum = cloneJson(value.enum, `harness.defineTool ${path}.enum`);
414
+ }
415
+ if (Object.hasOwn(value, "const")) prop.const = cloneJson(value.const, `harness.defineTool ${path}.const`);
416
+ break;
417
+ case "json":
418
+ assertSchemaKeys(value, path, [
419
+ "type",
420
+ ...requiredKey,
421
+ ...ANNOTATION_KEYS
422
+ ]);
423
+ break;
424
+ /* v8 ignore next 2 -- SCHEMA_TYPES narrows this closed switch before dispatch. */
425
+ default: throw new Error(`harness.defineTool ${path} must declare a valid type: ${VALID_TYPES}`);
426
+ }
427
+ }
428
+ /* v8 ignore next -- the root map task assigns before scheduling descendants. */
429
+ return holder.value ?? {};
430
+ }
431
+ function markDynamicTool(tool) {
432
+ Object.defineProperty(tool, DYNAMIC_TOOL, { value: true });
433
+ return tool;
434
+ }
435
+ function assertDynamicTool(tool) {
436
+ if (!isPlainRecord(tool) || tool[DYNAMIC_TOOL] !== true) throw new Error("dynamic tool registration must use a tool returned by harness.defineTool(...)");
437
+ }
438
+ /**
439
+ * Structurally a content block, checked AFTER the JSON round-trip: a plain
440
+ * object carrying a string `type` tag. Deliberately nothing deeper — the
441
+ * ContentBlock union is merge-extensible (an unknown tag must pass), and every
442
+ * downstream consumer dispatches on `type` and falls through unknowns.
443
+ */
444
+ function isContentBlockShape(value) {
445
+ return isPlainRecord(value) && typeof value.type === "string";
446
+ }
447
+ /**
448
+ * How much of an invalid execute return the teaching error echoes back — a
449
+ * huge blob would burn the model turn the error is trying to save.
450
+ */
451
+ const RETURN_PREVIEW_LIMIT = 120;
452
+ /**
453
+ * Compact JSON preview of an invalid execute return for the teaching error
454
+ * (`String(…)` for the un-stringifiable undefined case), truncated to
455
+ * {@link RETURN_PREVIEW_LIMIT}.
456
+ */
457
+ function describeReturn(value) {
458
+ const json = JSON.stringify(value);
459
+ return json.length > RETURN_PREVIEW_LIMIT ? `${json.slice(0, RETURN_PREVIEW_LIMIT)}…` : json;
460
+ }
461
+ /**
462
+ * Validate and host-materialize a sandbox renderer's content blocks.
463
+ */
464
+ function assertRenderedContent(value) {
465
+ if (Array.isArray(value) && value.every(isContentBlockShape)) return value;
466
+ throw new Error(`output.render returned ${describeReturn(value)} — it must return an ARRAY of content blocks:\n ✓ return [{ type: 'text', text: String(value) }]`);
467
+ }
468
+ /**
469
+ * The `harness.defineTool` handed into the sandbox: the real DSL, with `parameters` normalized
470
+ * into a fresh host-realm ParameterSchemaSpec (raw object wrappers unwrapped,
471
+ * required arrays mapped, and explicit DSL object openness enforced) and the tool's `execute` return normalized into the host realm
472
+ * via a JSON round-trip. Non-JSON or wrong-shape output fails that call instead of poisoning
473
+ * the session log.
474
+ * @param options - the standard `defineTool` options; `parameters` may be the ParameterSchemaSpec DSL or a JSON-Schema-style wrapper.
475
+ * @returns the marker-tagged definition `harness.registerTool` (and the guarded `ctx.tools.register`) accepts.
476
+ */
477
+ function sandboxDefineTool(options) {
478
+ if (!isPlainRecord(options)) throw new Error("harness.defineTool options must be an object");
479
+ const normalized = normalizeParameterSchemaSpec(options.parameters);
480
+ if (!isPlainRecord(options.output)) throw new Error("harness.defineTool output must declare { schema, render, presentationMeta? }");
481
+ const output = options.output;
482
+ if (typeof output.render !== "function") throw new Error("harness.defineTool output.render must be a function");
483
+ if (output.presentationMeta !== void 0 && typeof output.presentationMeta !== "function") throw new Error("harness.defineTool output.presentationMeta must be a function when present");
484
+ if (typeof options.execute !== "function") throw new Error("harness.defineTool execute must be a function");
485
+ const schema = cloneJson(output.schema, "harness.defineTool output.schema");
486
+ const rawExecute = options.execute;
487
+ const rawRender = output.render;
488
+ const rawPresentationMeta = output.presentationMeta;
489
+ const tool = defineTool({
490
+ ...options,
491
+ parameters: normalized.spec,
492
+ output: {
493
+ schema,
494
+ render(args, value) {
495
+ return assertRenderedContent(cloneJson(rawRender(args, value), "harness.defineTool output.render result"));
496
+ },
497
+ ...rawPresentationMeta !== void 0 ? { presentationMeta(args, value) {
498
+ return cloneJson(rawPresentationMeta(args, value), "harness.defineTool output.presentationMeta result");
499
+ } } : {}
500
+ },
501
+ async execute(args, exec) {
502
+ return cloneJson(await rawExecute(args, exec), "harness.defineTool execute result");
503
+ }
504
+ });
505
+ const parameters = {
506
+ ...tool.parameters,
507
+ ...normalized.rootAnnotations
508
+ };
509
+ assertSupportedJsonSchema(parameters);
510
+ return markDynamicTool({
511
+ ...tool,
512
+ parameters
513
+ });
514
+ }
515
+ /**
516
+ * Normalize one `harness.handle` registration at the sandbox boundary: the
517
+ * method name must be a non-empty string and the handler a function whose
518
+ * result is host-materialized through the same cross-realm JSON clone as tool
519
+ * `execute` returns (a VM-realm object would otherwise escape the wire's
520
+ * plain-object contract).
521
+ * @param method - handler name the package's browser half calls through `host.call`.
522
+ * @param fn - sandbox handler receiving the wire-decoded JSON arguments.
523
+ * @returns the validated name and the clone-wrapped handler.
524
+ */
525
+ function normalizeHandler(method, fn) {
526
+ if (typeof method !== "string" || method.length === 0) throw new Error("harness.handle(method, fn) needs a non-empty string method name");
527
+ if (typeof fn !== "function") throw new Error(`harness.handle("${method}") needs a handler function as its second argument`);
528
+ const rawHandler = fn;
529
+ return {
530
+ method,
531
+ handler: async (args) => cloneJson(await rawHandler(args), `harness.handle("${method}") result`)
532
+ };
533
+ }
534
+ /**
535
+ * The `harness.registerTool` handed into the sandbox: registers a
536
+ * marker-verified dynamic tool on the given context's registry.
537
+ * @param ctx - the (guarded) context whose `tools` service receives the tool.
538
+ * @param tool - a definition produced by {@link sandboxDefineTool}; anything else is rejected.
539
+ * @returns the registry disposer for the registration.
540
+ */
541
+ function sandboxRegisterTool(ctx, tool) {
542
+ assertDynamicTool(tool);
543
+ return ctx.tools.register(tool);
544
+ }
545
+ /**
546
+ * The verbs a running host half may reach through the sandbox `ctx` façade, beyond its injected
547
+ * services. `on`/`once` observe events, `provide` exposes a service to other packages, and the
548
+ * timer helpers schedule work — each a fiber effect that unwinds when the package stops.
549
+ */
550
+ const CTX_VERBS = new Set([
551
+ "effect",
552
+ "on",
553
+ "once",
554
+ "provide",
555
+ "timeout",
556
+ "interval",
557
+ "setTimeout",
558
+ "setInterval",
559
+ "throttle",
560
+ "debounce"
561
+ ]);
562
+ const TIMER_VERBS = new Set([
563
+ "timeout",
564
+ "interval",
565
+ "setTimeout",
566
+ "setInterval",
567
+ "throttle",
568
+ "debounce"
569
+ ]);
570
+ /**
571
+ * The tool-registry façade: `register` (marker-guarded) plus READ-ONLY
572
+ * metadata (`schemas`, and `get` returning a schema view, never the live
573
+ * `ToolDefinition`). Exposing the raw definition would hand package code the
574
+ * tool's `execute` function, letting it call another tool directly and bypass
575
+ * `ToolRuntime.execute` — identity protection, pre-policy, monotonic guards,
576
+ * around dispatch, post-policy, final observation, and result normalization. So `get` returns the same
577
+ * name/description/parameters view as `schemas()`, and nothing invocable.
578
+ */
579
+ function sandboxTools(ctx) {
580
+ return {
581
+ register: (tool) => sandboxRegisterTool(ctx, tool),
582
+ schemas: () => ctx.tools.schemas(scopeOf(ctx)),
583
+ get: (name) => ctx.tools.schemas(scopeOf(ctx)).find((schema) => schema.name === name)
584
+ };
585
+ }
586
+ /**
587
+ * Reject any injected-service return that is a cordis `Context`. Harness
588
+ * services return data, never a context; a value that is one would be a
589
+ * fresh, unguarded handle back into the runtime — the exact escape the façade
590
+ * exists to close — so it fails loud instead of reaching sandbox code.
591
+ */
592
+ function denyContext(value, service, reportFailure) {
593
+ if (value instanceof Context) return rejectGuard(reportFailure, `service "${service}" returned a cordis Context, which the sandbox does not expose. Operate through your own plugin ctx (ctx.on / ctx.provide / ctx.tools.register) and the services you inject — never another context.`);
594
+ return value;
595
+ }
596
+ /**
597
+ * Wrap an injected service so its methods forward to the real instance but
598
+ * their return values pass through {@link denyContext}. Non-function members
599
+ * (plain data) pass through as-is; a returned Promise is guarded on resolve.
600
+ */
601
+ function guardedService(service, name, reportFailure) {
602
+ return new Proxy(service, { get(target, prop) {
603
+ const value = Reflect.get(target, prop, target);
604
+ if (typeof value !== "function") return denyContext(value, name, reportFailure);
605
+ return (...args) => {
606
+ const result = Reflect.apply(value, target, args);
607
+ if (result instanceof Promise) return result.then((v) => denyContext(v, name, reportFailure));
608
+ return denyContext(result, name, reportFailure);
609
+ };
610
+ } });
611
+ }
612
+ /**
613
+ * The service names a plugin declared in `inject`, as a lookup set. Whatever
614
+ * declaration style the plugin used — an `inject: ['bash', 'tools']` array or
615
+ * the `{ required, optional }` object form — cordis resolves it into a single
616
+ * name-keyed map on the fiber before `apply` runs (`{ bash: null, tools: null }`),
617
+ * so the gate just reads that map's keys. A host half may reach only the services
618
+ * it declared — that is what lets cordis park it when a declared provider
619
+ * goes away.
620
+ */
621
+ function declaredInjects(ctx) {
622
+ return new Set(Object.keys(ctx.fiber.inject));
623
+ }
624
+ /**
625
+ * Whitelist context for running host halves: lifecycle-safe verbs, guarded
626
+ * tools, optional `ctx.get()` lookup, and declared-service property access.
627
+ * Framework plumbing is denied, and service methods cannot return a Context.
628
+ */
629
+ function sandboxContext(ctx, reportFailure) {
630
+ const tools = sandboxTools(ctx);
631
+ const declared = declaredInjects(ctx);
632
+ const denyRead = (prop) => {
633
+ if (ctx.get(prop) !== void 0) return rejectGuard(reportFailure, `service "${prop}" is not injected. Declare it: inject: ['${prop}', …] on your plugin, so cordis parks this dynamic package if the provider later goes away.`);
634
+ return rejectGuard(reportFailure, `sandbox ctx does not expose "${prop}". Available: ctx.tools.register / ctx.on / ctx.provide / the timer helpers after injecting timer, and any service you declared in inject. Framework internals (root, fiber, registry, extend, plugin, …) are withheld by design.`);
635
+ };
636
+ const readService = (name, requireDeclaration) => {
637
+ if (name === "tools") return tools;
638
+ if (requireDeclaration && !declared.has(name)) return denyRead(name);
639
+ const service = denyContext(ctx.get(name), name, reportFailure);
640
+ if (service === null || typeof service !== "object" && typeof service !== "function") return service;
641
+ return guardedService(service, name, reportFailure);
642
+ };
643
+ const get = (name) => readService(name, false);
644
+ return new Proxy({}, {
645
+ get(_target, prop) {
646
+ if (prop === "tools") return tools;
647
+ if (prop === "get") return get;
648
+ if (typeof prop !== "string") return void 0;
649
+ if (CTX_VERBS.has(prop)) return (...args) => {
650
+ if (TIMER_VERBS.has(prop) && !declared.has("timer")) return denyRead("timer");
651
+ const method = ctx[prop];
652
+ return Reflect.apply(method, ctx, args);
653
+ };
654
+ return readService(prop, true);
655
+ },
656
+ set(_target, prop) {
657
+ return rejectGuard(reportFailure, `sandbox ctx is read-only; cannot assign "${String(prop)}"`);
658
+ },
659
+ has: (_target, prop) => prop === "tools" || prop === "get" || typeof prop === "string" && (CTX_VERBS.has(prop) && (!TIMER_VERBS.has(prop) || declared.has("timer")) || declared.has(prop))
660
+ });
661
+ }
662
+ /**
663
+ * Narrow an arbitrary sandbox return value to a runnable cordis plugin: a
664
+ * function, or an object with an `apply` function. (A bare function passes the
665
+ * first arm, so the object arm never sees `Function.prototype.apply`.)
666
+ * @param value - whatever the host half returned.
667
+ * @returns whether the value can be started via `ctx.plugin`.
668
+ */
669
+ function isPlugin(value) {
670
+ if (typeof value === "function") return true;
671
+ return typeof value === "object" && value !== null && typeof value.apply === "function";
672
+ }
673
+ /**
674
+ * Wrap a plugin so `apply` receives the sandbox context while preserving injection metadata.
675
+ * @param plugin - the plugin the host half returned.
676
+ * @param reportFailure - reports a guard rejection to the owning Agent.
677
+ * @returns an equivalent plugin whose `apply` sees the sandbox context façade.
678
+ */
679
+ function guardedPlugin(plugin, reportFailure) {
680
+ if (typeof plugin === "function") {
681
+ const functionPlugin = plugin;
682
+ return {
683
+ name: pluginName(plugin),
684
+ apply(ctx, config) {
685
+ return functionPlugin(sandboxContext(ctx, reportFailure), config);
686
+ }
687
+ };
688
+ }
689
+ const objectPlugin = plugin;
690
+ return {
691
+ ...plugin,
692
+ apply(ctx, config) {
693
+ return objectPlugin.apply(sandboxContext(ctx, reportFailure), config);
694
+ }
695
+ };
696
+ }
697
+ function rejectGuard(reportFailure, message) {
698
+ const error = new Error(message);
699
+ reportFailure(error);
700
+ throw error;
701
+ }
702
+ /**
703
+ * Display name for a running plugin: its `name` property, else anonymous.
704
+ * @param plugin - the plugin the host half returned.
705
+ * @returns the human-readable name used in run results and inspect output.
706
+ */
707
+ function pluginName(plugin) {
708
+ const named = plugin.name;
709
+ if (typeof named === "string" && named.length > 0) return named;
710
+ return "<anonymous>";
711
+ }
712
+ //#endregion
713
+ //#region lib/types/inspect-registry.js
714
+ /** Host registry for model-visible, read-only Cordis capability queries. */
715
+ /** Registry and cross-page router behind the two model-facing inspect tools. */
716
+ var CordisInspectRegistryService = class extends Service {
717
+ providers = /* @__PURE__ */ new Map();
718
+ pending = /* @__PURE__ */ new Map();
719
+ clientManifest;
720
+ nextRequest = 1;
721
+ /** Register the process-global Host registry. */
722
+ constructor(ctx) {
723
+ super(ctx, "cordisInspect");
724
+ }
725
+ /**
726
+ * Register one Host provider.
727
+ * @param registration - manifest and local query handler.
728
+ * @returns idempotent disposer.
729
+ */
730
+ register(registration) {
731
+ const manifest = validateManifest(registration.manifest);
732
+ if (this.providers.has(manifest.id)) throw new Error(`Host Cordis inspect provider "${manifest.id}" is already registered`);
733
+ const stored = {
734
+ ...registration,
735
+ manifest
736
+ };
737
+ this.providers.set(manifest.id, stored);
738
+ return () => {
739
+ if (this.providers.get(manifest.id) === stored) this.providers.delete(manifest.id);
740
+ };
741
+ }
742
+ /**
743
+ * Replace the mirrored Client provider directory.
744
+ * @param providers - complete Client manifest snapshot.
745
+ */
746
+ syncClientManifest(providers) {
747
+ const ids = /* @__PURE__ */ new Set();
748
+ const validated = providers.map((provider) => {
749
+ const manifest = validateManifest(provider);
750
+ if (ids.has(manifest.id)) throw new Error(`Client Cordis inspect manifest repeats provider "${manifest.id}"`);
751
+ ids.add(manifest.id);
752
+ return manifest;
753
+ });
754
+ this.clientManifest = Object.freeze(validated);
755
+ }
756
+ /**
757
+ * Return the complete known Host and Client provider directory.
758
+ * @returns Host providers followed by the Client providers.
759
+ */
760
+ list() {
761
+ return [...[...this.providers.values()].map((provider) => view("host", provider.manifest)), ...(this.clientManifest ?? []).map((provider) => view("client", provider))];
762
+ }
763
+ /**
764
+ * Execute one provider query on its owning platform.
765
+ * @param platform - Host or Client runtime.
766
+ * @param providerId - provider selected from {@link list}.
767
+ * @param methodName - declared method name.
768
+ * @param input - optional lossless JSON input.
769
+ * @param agent - requesting Agent and scope.
770
+ * @param signal - tool-call cancellation.
771
+ * @returns provider JSON data.
772
+ */
773
+ async query(platform, providerId, methodName, input, agent, signal) {
774
+ if (platform === "host") {
775
+ const registration = this.providers.get(providerId);
776
+ if (registration === void 0) throw new Error(`Host Cordis inspect provider "${providerId}" is not registered`);
777
+ const method = findMethod(registration.manifest, methodName);
778
+ validateInput("Host", providerId, method, input);
779
+ signal.throwIfAborted();
780
+ const data = await registration.query(methodName, input, {
781
+ agent,
782
+ signal
783
+ });
784
+ signal.throwIfAborted();
785
+ return validateOutput("Host", providerId, method, data);
786
+ }
787
+ return await this.queryClient(providerId, methodName, input, agent, signal);
788
+ }
789
+ /**
790
+ * Accept the first valid Client response for a pending query.
791
+ * @param agent - Agent whose Session owns the query.
792
+ * @param requestId - Pending Client query identity.
793
+ * @param resolution - Client provider result or failure.
794
+ * @returns whether this response settled the still-pending query.
795
+ */
796
+ resolveClientQuery(agent, requestId, resolution) {
797
+ const pending = this.pending.get(requestId);
798
+ if (pending === void 0 || pending.request.agentId !== agent.id) return { accepted: false };
799
+ if (!resolution.ok) return { accepted: false };
800
+ try {
801
+ resolution = {
802
+ ok: true,
803
+ data: validateOutput("Client", pending.request.provider, pending.method, resolution.data)
804
+ };
805
+ } catch {
806
+ return { accepted: false };
807
+ }
808
+ this.pending.delete(requestId);
809
+ pending.settle(resolution);
810
+ this.ctx.emit("cordis/inspect-query-resolved", { requestId });
811
+ return { accepted: true };
812
+ }
813
+ async queryClient(providerId, methodName, input, agent, signal) {
814
+ const provider = this.clientManifest?.find((candidate) => candidate.id === providerId);
815
+ if (provider === void 0) throw new Error(`Client Cordis inspect provider "${providerId}" is not registered`);
816
+ const method = findMethod(provider, methodName);
817
+ validateInput("Client", providerId, method, input);
818
+ signal.throwIfAborted();
819
+ const requestId = `inspect-${this.nextRequest++}`;
820
+ const request = {
821
+ requestId,
822
+ agentId: agent.id,
823
+ provider: providerId,
824
+ method: methodName,
825
+ ...input === void 0 ? {} : { input }
826
+ };
827
+ const result = new Promise((resolve) => {
828
+ this.pending.set(requestId, {
829
+ request,
830
+ method,
831
+ settle: resolve
832
+ });
833
+ });
834
+ const onAbort = () => {
835
+ const pending = this.pending.get(requestId);
836
+ if (pending === void 0) return;
837
+ this.pending.delete(requestId);
838
+ pending.settle({
839
+ ok: false,
840
+ reason: "cancelled",
841
+ message: `Client inspect query ${providerId}.${methodName} was cancelled`
842
+ });
843
+ this.ctx.emit("cordis/inspect-query-resolved", { requestId });
844
+ };
845
+ signal.addEventListener("abort", onAbort, { once: true });
846
+ if (signal.aborted) onAbort();
847
+ else this.ctx.emit("cordis/inspect-query", request);
848
+ try {
849
+ const resolution = await result;
850
+ if (!resolution.ok) throw new Error(`${providerId}.${methodName}: ${resolution.message}`);
851
+ return resolution.data;
852
+ } finally {
853
+ signal.removeEventListener("abort", onAbort);
854
+ }
855
+ }
856
+ };
857
+ function view(platform, manifest) {
858
+ return {
859
+ platform,
860
+ ...manifest,
861
+ methods: [...manifest.methods]
862
+ };
863
+ }
864
+ function validateManifest(manifest) {
865
+ if (manifest.id.trim() === "") throw new Error("Cordis inspect provider id must not be empty");
866
+ if (manifest.description.trim() === "") throw new Error(`Cordis inspect provider "${manifest.id}" needs a description`);
867
+ const names = /* @__PURE__ */ new Set();
868
+ const methods = manifest.methods.map((method) => {
869
+ if (method.name.trim() === "") throw new Error(`Cordis inspect provider "${manifest.id}" has an empty method name`);
870
+ if (names.has(method.name)) throw new Error(`Cordis inspect provider "${manifest.id}" repeats method "${method.name}"`);
871
+ if (method.description.trim() === "") throw new Error(`Cordis inspect method ${manifest.id}.${method.name} needs a description`);
872
+ assertSupportedJsonSchema(method.inputSchema);
873
+ assertSupportedJsonSchema(method.outputSchema);
874
+ names.add(method.name);
875
+ return Object.freeze({ ...method });
876
+ });
877
+ return Object.freeze({
878
+ ...manifest,
879
+ methods: Object.freeze(methods)
880
+ });
881
+ }
882
+ function findMethod(manifest, name) {
883
+ const method = manifest.methods.find((candidate) => candidate.name === name);
884
+ if (method === void 0) throw new Error(`Cordis inspect provider "${manifest.id}" has no method "${name}"`);
885
+ return method;
886
+ }
887
+ function validateInput(platform, provider, method, input) {
888
+ const violations = validateJsonSchemaValue(method.inputSchema, input ?? {}, "input");
889
+ if (violations.length > 0) throw new Error(`${platform} Cordis inspect ${provider}.${method.name} rejected input: ${violations.join("; ")}`);
890
+ }
891
+ function validateOutput(platform, provider, method, data) {
892
+ const snapshot = snapshotJsonValue(data);
893
+ if (snapshot === void 0) throw new Error(`${platform} Cordis inspect ${provider}.${method.name} returned a non-JSON value`);
894
+ const violations = validateJsonSchemaValue(method.outputSchema, snapshot, "output");
895
+ if (violations.length > 0) throw new Error(`${platform} Cordis inspect ${provider}.${method.name} returned invalid output: ${violations.join("; ")}`);
896
+ return snapshot;
897
+ }
898
+ //#endregion
899
+ //#region lib/types/lifecycle.js
900
+ /**
901
+ * Host-half fiber lifecycle over the `cordis-dynamic` group: settle a
902
+ * sandbox-produced plugin as a child fiber (never leaving a failed fiber
903
+ * mounted), and report the services a settled-but-pending fiber still waits
904
+ * for. Stopping needs no helper — a host half unwinds through an ordinary
905
+ * awaited `fiber.dispose()`, because everything the plugin registered is an
906
+ * effect on its fiber.
907
+ * @module @deepseek-ai/dsh-cordis-host-runner/lifecycle
908
+ */
909
+ /**
910
+ * Await the group, start and settle one guarded child, and dispose it before rethrowing any
911
+ * startup failure so a failed run never lingers. A valid unresolved inject may remain pending.
912
+ * @param group - the `cordis-dynamic` group fiber every host half hangs under.
913
+ * @param plugin - the plugin the sandbox returned; wrapped with the registration guard before starting.
914
+ * @param reportGuardFailure - reports post-activation Host guard rejections to the owning Agent.
915
+ * @returns the settled child fiber (possibly pending on unsatisfied `inject`).
916
+ */
917
+ async function startHostHalf(group, plugin, reportGuardFailure) {
918
+ await group.await();
919
+ const fiber = group.ctx.plugin(guardedPlugin(plugin, reportGuardFailure));
920
+ try {
921
+ await fiber.await();
922
+ } catch (error) {
923
+ await fiber.dispose();
924
+ const message = error instanceof Error ? error.message : String(error);
925
+ if (message.includes("already registered")) throw new Error(`${message} — to REPLACE something an earlier dynamic package registered, first cordis_stop that package's id (find it with cordis_runtime_inspect what:"temporary"), then run the new version.`);
926
+ throw error instanceof Error ? error : new Error(message);
927
+ }
928
+ return fiber;
929
+ }
930
+ /**
931
+ * The services a fiber declared in `inject` that do not exist yet — a settled
932
+ * fiber that is not active is waiting on exactly these (legal cordis
933
+ * semantics: it activates when the service appears).
934
+ * @param ctx - the context to resolve service existence against.
935
+ * @param fiber - the host-half fiber whose `inject` declarations are checked.
936
+ * @returns the missing service names, in declaration order.
937
+ */
938
+ function missingServices(ctx, fiber) {
939
+ return Object.keys(fiber.inject).filter((service) => ctx.get(service) === void 0);
940
+ }
941
+ //#endregion
942
+ //#region lib/types/registry.js
943
+ /**
944
+ * Process-local dynamic Plugin registry and its opaque identity mints.
945
+ * @module @deepseek-ai/dsh-cordis-host-runner/registry
946
+ */
947
+ /** Registry, identity mints, and pending approval index. */
948
+ var DynamicCordisRegistry = class {
949
+ plugins = /* @__PURE__ */ new Map();
950
+ pendingRequests = /* @__PURE__ */ new Map();
951
+ nextPlugin = 1;
952
+ nextPackage = 1;
953
+ nextRun = 1;
954
+ nextApproval = 1;
955
+ /**
956
+ * Mint a semantic plugin ID without reusing a prior suffix.
957
+ * @param prefix - validated lowercase semantic prefix proposed by the model.
958
+ * @returns a process-unique Plugin ID.
959
+ */
960
+ mintPluginId(prefix) {
961
+ let id;
962
+ do
963
+ id = `${prefix}-${this.nextPlugin++}`;
964
+ while (this.plugins.has(id));
965
+ return id;
966
+ }
967
+ /**
968
+ * Mint an immutable package ID.
969
+ * @returns a process-unique Package ID.
970
+ */
971
+ mintPackageId() {
972
+ return `pkg-${this.nextPackage++}`;
973
+ }
974
+ /**
975
+ * Mint an activation ID.
976
+ * @returns a process-unique Plugin Run ID.
977
+ */
978
+ mintPluginRunId() {
979
+ return `run-${this.nextRun++}`;
980
+ }
981
+ /**
982
+ * Mint an approval ID.
983
+ * @returns a process-unique approval request ID.
984
+ */
985
+ mintApprovalRequestId() {
986
+ return `approval-${this.nextApproval++}`;
987
+ }
988
+ /**
989
+ * Add one stable plugin.
990
+ * @param plugin - Plugin record to retain under its stable ID.
991
+ */
992
+ add(plugin) {
993
+ this.plugins.set(plugin.pluginId, plugin);
994
+ }
995
+ /**
996
+ * Read one plugin.
997
+ * @param id - stable Plugin ID.
998
+ * @returns the Plugin record, or `undefined` when absent.
999
+ */
1000
+ get(id) {
1001
+ return this.plugins.get(id);
1002
+ }
1003
+ /**
1004
+ * Delete one plugin and all package versions.
1005
+ * @param id - stable Plugin ID to remove.
1006
+ * @returns whether a Plugin record was removed.
1007
+ */
1008
+ delete(id) {
1009
+ return this.plugins.delete(id);
1010
+ }
1011
+ /**
1012
+ * Read all plugins in creation order.
1013
+ * @returns a snapshot of every Plugin record.
1014
+ */
1015
+ all() {
1016
+ return [...this.plugins.values()];
1017
+ }
1018
+ /**
1019
+ * Read one session's plugins in creation order.
1020
+ * @param sessionId - owning session to filter by.
1021
+ * @returns a snapshot of matching Plugin records.
1022
+ */
1023
+ ofSession(sessionId) {
1024
+ return this.all().filter((plugin) => plugin.sessionId === sessionId);
1025
+ }
1026
+ /**
1027
+ * Publish one pending approval.
1028
+ * @param id - approval request ID.
1029
+ * @param pending - resolver and Plugin metadata retained until settlement.
1030
+ */
1031
+ armRequest(id, pending) {
1032
+ this.pendingRequests.set(id, pending);
1033
+ }
1034
+ /**
1035
+ * Read one pending approval without claiming it.
1036
+ * @param id - approval request ID.
1037
+ * @returns the pending request, or `undefined` when absent.
1038
+ */
1039
+ peekRequest(id) {
1040
+ return this.pendingRequests.get(id);
1041
+ }
1042
+ /**
1043
+ * Claim one pending approval; first answer wins.
1044
+ * @param id - approval request ID.
1045
+ * @returns the claimed request, or `undefined` when already settled.
1046
+ */
1047
+ claimRequest(id) {
1048
+ const pending = this.pendingRequests.get(id);
1049
+ if (pending !== void 0) this.pendingRequests.delete(id);
1050
+ return pending;
1051
+ }
1052
+ /**
1053
+ * Cancel one pending approval.
1054
+ * @param id - approval request ID to remove.
1055
+ */
1056
+ disarmRequest(id) {
1057
+ this.pendingRequests.delete(id);
1058
+ }
1059
+ /**
1060
+ * Find a pending approval for one Plugin.
1061
+ * @param pluginId - stable Plugin ID.
1062
+ * @returns its approval request ID, or `undefined` when none is pending.
1063
+ */
1064
+ pendingRequestFor(pluginId) {
1065
+ for (const [requestId, request] of this.pendingRequests) if (request.pluginId === pluginId) return requestId;
1066
+ }
1067
+ };
1068
+ //#endregion
1069
+ //#region lib/types/sandbox.js
1070
+ /**
1071
+ * The `node:vm` sandbox a dynamic package's HOST half evaluates in: a fresh realm whose globals
1072
+ * are a tagged write-through console, the `harness` registration helpers, the encoding primitives
1073
+ * a bare vm context lacks, and callable traps over the Node APIs the sandbox deliberately
1074
+ * withholds. Traps steer filesystem, network, process, and timer work to `ctx.fs`, `ctx.web`,
1075
+ * `ctx.bash`, and Cordis timers. This keeps cooperative packages inspectable and disposable but
1076
+ * is not containment: host-realm helper functions remain an escape route.
1077
+ *
1078
+ * The browser half never reaches this module — it is evaluated by the client-side runner in a
1079
+ * closure, with its own facade.
1080
+ * @module @deepseek-ai/dsh-cordis-host-runner/sandbox
1081
+ */
1082
+ /** Exact Host closure symbols exposed by the sandbox and guarded Context. */
1083
+ const HOST_BUILTIN_INSPECTION = [
1084
+ {
1085
+ name: "ctx",
1086
+ description: "Restricted Cordis Context. Prefer ctx.get(name) with an undefined check; use inject for hard dependencies.",
1087
+ signatures: [
1088
+ "ctx.get(name: string): unknown | undefined",
1089
+ "ctx.on(name: string, listener: Function): () => void",
1090
+ "ctx.provide(name: string, value: unknown): () => void",
1091
+ "ctx.effect(callback: Function, label?: string): () => void"
1092
+ ]
1093
+ },
1094
+ {
1095
+ name: "harness",
1096
+ description: "Host helpers for Package-private Client RPC and model-visible dynamic Tools.",
1097
+ signatures: [
1098
+ "harness.handle(method: string, handler: (args: JsonValue) => JsonValue | Promise<JsonValue>): () => void",
1099
+ "harness.defineTool(definition: ToolDefinition): ToolDefinition",
1100
+ "harness.registerTool(ctx: Context, tool: ToolDefinition): () => void"
1101
+ ]
1102
+ },
1103
+ {
1104
+ name: "console",
1105
+ description: "Package-tagged Host logging.",
1106
+ signatures: ["console.log(...values): void", "console.error(...values): void"]
1107
+ },
1108
+ {
1109
+ name: "btoa",
1110
+ description: "Encode UTF-8 text as base64.",
1111
+ signatures: ["btoa(value: string): string"]
1112
+ },
1113
+ {
1114
+ name: "atob",
1115
+ description: "Decode base64 as UTF-8 text.",
1116
+ signatures: ["atob(value: string): string"]
1117
+ },
1118
+ {
1119
+ name: "TextEncoder",
1120
+ description: "Standard UTF-8 encoder constructor.",
1121
+ signatures: ["new TextEncoder()"]
1122
+ },
1123
+ {
1124
+ name: "TextDecoder",
1125
+ description: "Standard text decoder constructor.",
1126
+ signatures: ["new TextDecoder(label?: string)"]
1127
+ }
1128
+ ];
1129
+ /**
1130
+ * A write-through console for one package, tagging every line with the package
1131
+ * id. Write-through (host stdout/stderr), NOT buffered into the tool result:
1132
+ * a registered listener fires long after the run call returned, and its output
1133
+ * must land somewhere the user can see — for a terminal entry point, the host terminal.
1134
+ */
1135
+ function taggedConsole(id) {
1136
+ const tag = `[cordis:${id}]`;
1137
+ const log = (...args) => {
1138
+ console.log(tag, ...args);
1139
+ };
1140
+ const error = (...args) => {
1141
+ console.error(tag, ...args);
1142
+ };
1143
+ return {
1144
+ log,
1145
+ info: log,
1146
+ warn: log,
1147
+ debug: log,
1148
+ error
1149
+ };
1150
+ }
1151
+ /**
1152
+ * Patch only VM constructors so `instanceof` accepts both VM values and host values passed as
1153
+ * arguments, events, or service results; host intrinsics remain untouched.
1154
+ */
1155
+ const DUAL_REALM_INSTANCEOF_PRELUDE = `
1156
+ (hostIntrinsics) => {
1157
+ 'use strict'
1158
+ const ordinary = Function.prototype[Symbol.hasInstance]
1159
+ for (const name of Object.keys(hostIntrinsics)) {
1160
+ const VmCtor = globalThis[name]
1161
+ const HostCtor = hostIntrinsics[name]
1162
+ if (typeof VmCtor !== 'function' || typeof HostCtor !== 'function') continue
1163
+ Object.defineProperty(VmCtor, Symbol.hasInstance, {
1164
+ value: (instance) => ordinary.call(VmCtor, instance) || ordinary.call(HostCtor, instance),
1165
+ configurable: true,
1166
+ })
1167
+ }
1168
+ }
1169
+ `;
1170
+ /** Run {@link DUAL_REALM_INSTANCEOF_PRELUDE} in a freshly created sandbox, handing it the host intrinsics to pair up. */
1171
+ function patchDualRealmInstanceof(sandbox) {
1172
+ runInContext(DUAL_REALM_INSTANCEOF_PRELUDE, sandbox)({
1173
+ Object,
1174
+ Array,
1175
+ Function,
1176
+ Error,
1177
+ TypeError,
1178
+ RangeError,
1179
+ SyntaxError,
1180
+ Promise,
1181
+ RegExp,
1182
+ Date,
1183
+ Map,
1184
+ Set
1185
+ });
1186
+ }
1187
+ const TIMER_REDIRECT = "Node timers are unavailable. Use the cordis timer service instead: declare inject: ['timer'] on your plugin and call ctx.timeout / ctx.interval after querying Host Service.listService for the exact overloads. Those calls are fiber effects, cleaned up automatically when stopped.";
1188
+ /**
1189
+ * The callable Node APIs the sandbox deliberately disables, each mapped to the
1190
+ * cordis alternative its trap error names. Only function-valued globals are
1191
+ * trapped; a data-valued global such as `process` stays `undefined`, because a
1192
+ * throwing accessor would detonate the common `typeof process` feature probe
1193
+ * at resolution time.
1194
+ */
1195
+ const NODE_API_REDIRECTS = {
1196
+ require: "Node modules are unavailable. Use the cordis services on ctx instead — e.g. inject: ['fs'] for files, ['web'] for HTTP, ['bash'] for processes; query Service.listService with cordis_inspect_query first.",
1197
+ setTimeout: TIMER_REDIRECT,
1198
+ setInterval: TIMER_REDIRECT,
1199
+ setImmediate: TIMER_REDIRECT,
1200
+ clearTimeout: TIMER_REDIRECT,
1201
+ clearInterval: TIMER_REDIRECT,
1202
+ fetch: "Network access goes through the cordis web service: declare inject: ['web'] and call ctx.web (query Host Service.listService with cordis_inspect_query for its methods)."
1203
+ };
1204
+ /** Build the trap functions for {@link NODE_API_REDIRECTS}: calling one throws the redirect. */
1205
+ function nodeApiTraps() {
1206
+ const traps = {};
1207
+ for (const [name, redirect] of Object.entries(NODE_API_REDIRECTS)) traps[name] = () => {
1208
+ throw new Error(`${name} is not available in the dynamic package sandbox — ${redirect}`);
1209
+ };
1210
+ return traps;
1211
+ }
1212
+ /**
1213
+ * Build the vm context one host half evaluates in: the tagged console, the
1214
+ * `harness` registration helpers, the encoding primitives, the Node-API traps,
1215
+ * and the dual-realm `instanceof` patch, already `createContext`-ed.
1216
+ * @param id - the package id (`dyn-<n>`), used as the console tag and filename stem.
1217
+ * @param harnessExtras - per-package `harness` verbs beyond the registration pair (`handle`).
1218
+ * @returns the contextified sandbox object to pass to {@link evaluateHostCode}.
1219
+ */
1220
+ function createSandbox(id, harnessExtras = {}) {
1221
+ const sandbox = {
1222
+ ...nodeApiTraps(),
1223
+ console: taggedConsole(id),
1224
+ harness: {
1225
+ defineTool: sandboxDefineTool,
1226
+ registerTool: sandboxRegisterTool,
1227
+ ...harnessExtras
1228
+ },
1229
+ btoa: (s) => Buffer.from(s, "utf-8").toString("base64"),
1230
+ atob: (s) => Buffer.from(s, "base64").toString("utf-8"),
1231
+ TextEncoder,
1232
+ TextDecoder
1233
+ };
1234
+ createContext(sandbox);
1235
+ patchDualRealmInstanceof(sandbox);
1236
+ return sandbox;
1237
+ }
1238
+ /**
1239
+ * Cross-realm SyntaxError detection: a compile failure inside `runInContext`
1240
+ * constructs its error in the SANDBOX realm, so a host `instanceof
1241
+ * SyntaxError` is silently false — the `name` property is the realm-safe tag.
1242
+ */
1243
+ function isSyntaxError(error) {
1244
+ return typeof error === "object" && error !== null && error.name === "SyntaxError";
1245
+ }
1246
+ /**
1247
+ * The parse-failure context a vm `SyntaxError` carries: the vm prints the
1248
+ * offending source line and a caret before the message, which is exactly what
1249
+ * a model needs to self-correct — surface it instead of the bare message.
1250
+ * Falls back to `String(error)` when the stack carries no such prelude.
1251
+ * @param error - the `SyntaxError` (host- or sandbox-realm) thrown while compiling package code.
1252
+ * @returns the stack prefix up to and including the `SyntaxError: …` line.
1253
+ */
1254
+ function syntaxErrorContext(error) {
1255
+ const lines = (error.stack ?? "").split("\n");
1256
+ const messageIndex = lines.findIndex((line) => line.startsWith("SyntaxError"));
1257
+ if (messageIndex === -1) return String(error);
1258
+ return lines.slice(0, messageIndex + 1).join("\n");
1259
+ }
1260
+ /**
1261
+ * The teaching text one parse failure produces, shared by the define-time
1262
+ * precheck and the run-time evaluation so a model reads the same diagnosis
1263
+ * whichever verb caught it.
1264
+ * @param half - which half failed to parse, named as the define argument that carried it.
1265
+ * @param context - the {@link syntaxErrorContext} of the failure.
1266
+ * @returns the model-facing error message.
1267
+ */
1268
+ function parseErrorMessage(half, context) {
1269
+ const offendingLine = context.split("\n")[1] ?? "";
1270
+ if (/\bas\b/.test(offendingLine)) return `dynamic package \`${half}\` failed to parse:\n${context}\nThe sandbox runs plain JavaScript, not TypeScript. Remove type annotations:
1271
+ ✗ { type: 'text' as const, text: x }
1272
+ ✓ { type: 'text', text: x }`;
1273
+ return `dynamic package \`${half}\` failed to parse:\n${context}\nNote: it runs as the BODY of an async function (line numbers are offset by the 1-line wrapper). Check bracket balance — ending the returned plugin object with \`});\` closes a call that was never opened; a plain \`return { … }\` ends with \`}\` (an optional \`;\`), never \`)\`.`;
1274
+ }
1275
+ /**
1276
+ * Parse one half's source without running it: the define-time precheck that
1277
+ * keeps unparseable code out of the registry, so a model fixes it and defines
1278
+ * again instead of discovering the failure at run time. Compiling through `vm`
1279
+ * rather than `new Function` is what makes the two agree — same wrapper, same
1280
+ * compiler, and the same source-line-and-caret prelude in the failure.
1281
+ * @param code - the model-written function body.
1282
+ * @param half - which define argument carried it, for the error text.
1283
+ * @throws when the body does not parse, with the offending line and a teaching hint.
1284
+ */
1285
+ function precheckCode(code, half) {
1286
+ try {
1287
+ new Script(`(async () => {\n${code}\n})()`, { filename: `cordis-dyn-${half}.js` });
1288
+ } catch (error) {
1289
+ if (!isSyntaxError(error)) throw error;
1290
+ throw new Error(parseErrorMessage(half, syntaxErrorContext(error)));
1291
+ }
1292
+ }
1293
+ /**
1294
+ * Evaluate a host half as the body of an async function inside the sandbox. `vmTimeoutMs` only
1295
+ * bounds the SYNCHRONOUS portion; an async body escapes it — acceptable under the module's
1296
+ * trust stance. Parse errors include the offending line and a TypeScript-removal or bracket-
1297
+ * balance hint.
1298
+ * @param sandbox - the contextified object from {@link createSandbox}.
1299
+ * @param code - the model-written function body; must `return` a plugin.
1300
+ * @param id - the package id, used as the vm filename (`cordis-dyn-<id>.js`).
1301
+ * @param vmTimeoutMs - the synchronous evaluation bound in milliseconds.
1302
+ * @returns whatever the code returned, still un-narrowed (the run lifecycle checks plugin shape).
1303
+ */
1304
+ async function evaluateHostCode(sandbox, code, id, vmTimeoutMs) {
1305
+ try {
1306
+ return await runInContext(`(async () => {\n${code}\n})()`, sandbox, {
1307
+ filename: `cordis-dyn-${id}.js`,
1308
+ timeout: vmTimeoutMs
1309
+ });
1310
+ } catch (error) {
1311
+ if (!isSyntaxError(error)) throw error;
1312
+ throw new Error(parseErrorMessage("code.host", syntaxErrorContext(error)));
1313
+ }
1314
+ }
1315
+ //#endregion
1316
+ //#region lib/types/index.js
1317
+ /**
1318
+ * Dynamic Cordis Plugin service: immutable package definitions, one active run
1319
+ * per Plugin, human-approved Client activation, and Host/Client invocation.
1320
+ * @module @deepseek-ai/dsh-cordis-host-runner
1321
+ */
1322
+ var __runInitializers = function(thisArg, initializers, value) {
1323
+ var useValue = arguments.length > 2;
1324
+ for (var i = 0; i < initializers.length; i++) value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
1325
+ return useValue ? value : void 0;
1326
+ };
1327
+ var __esDecorate = function(ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
1328
+ function accept(f) {
1329
+ if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected");
1330
+ return f;
1331
+ }
1332
+ var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
1333
+ var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
1334
+ var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
1335
+ var _, done = false;
1336
+ for (var i = decorators.length - 1; i >= 0; i--) {
1337
+ var context = {};
1338
+ for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
1339
+ for (var p in contextIn.access) context.access[p] = contextIn.access[p];
1340
+ context.addInitializer = function(f) {
1341
+ if (done) throw new TypeError("Cannot add initializers after decoration has completed");
1342
+ extraInitializers.push(accept(f || null));
1343
+ };
1344
+ var result = (0, decorators[i])(kind === "accessor" ? {
1345
+ get: descriptor.get,
1346
+ set: descriptor.set
1347
+ } : descriptor[key], context);
1348
+ if (kind === "accessor") {
1349
+ if (result === void 0) continue;
1350
+ if (result === null || typeof result !== "object") throw new TypeError("Object expected");
1351
+ if (_ = accept(result.get)) descriptor.get = _;
1352
+ if (_ = accept(result.set)) descriptor.set = _;
1353
+ if (_ = accept(result.init)) initializers.unshift(_);
1354
+ } else if (_ = accept(result)) if (kind === "field") initializers.unshift(_);
1355
+ else descriptor[key] = _;
1356
+ }
1357
+ if (target) Object.defineProperty(target, contextIn.name, descriptor);
1358
+ done = true;
1359
+ };
1360
+ /**
1361
+ * Brand a Host-minted Plugin ID.
1362
+ * @param id - opaque identifier minted by the Host registry.
1363
+ * @returns the branded Plugin identifier.
1364
+ */
1365
+ function CordisDynamicPluginId(id) {
1366
+ return id;
1367
+ }
1368
+ /**
1369
+ * Brand a Host-minted Package ID.
1370
+ * @param id - opaque identifier minted by the Host registry.
1371
+ * @returns the branded Package identifier.
1372
+ */
1373
+ function CordisDynamicPackageId(id) {
1374
+ return id;
1375
+ }
1376
+ /**
1377
+ * Brand a Host-minted Plugin Run ID.
1378
+ * @param id - opaque identifier minted by the Host registry.
1379
+ * @returns the branded Plugin Run identifier.
1380
+ */
1381
+ function CordisDynamicPluginRunId(id) {
1382
+ return id;
1383
+ }
1384
+ /**
1385
+ * Brand a Host-minted approval request ID.
1386
+ * @param id - opaque identifier minted by the Host registry.
1387
+ * @returns the branded approval request identifier.
1388
+ */
1389
+ function ApprovalRequestId(id) {
1390
+ return id;
1391
+ }
1392
+ /** Dynamic Plugin registry and Host-half lifecycle. */
1393
+ let DynamicCordisRunnerService = (() => {
1394
+ let _classSuper = TypertRemoteService;
1395
+ let _instanceExtraInitializers = [];
1396
+ let _undefineFromPanel_decorators;
1397
+ let _runHostHalf_decorators;
1398
+ let _getClientCode_decorators;
1399
+ let _resolveRequestRun_decorators;
1400
+ let _settleUserRun_decorators;
1401
+ let _stopFromPanel_decorators;
1402
+ let _syncInspectManifest_decorators;
1403
+ let _resolveInspectQuery_decorators;
1404
+ let _inventory_decorators;
1405
+ let _reportRenderFailure_decorators;
1406
+ let _reportClientGuardFailure_decorators;
1407
+ let _invoke_decorators;
1408
+ return class DynamicCordisRunnerService extends _classSuper {
1409
+ static {
1410
+ const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
1411
+ _undefineFromPanel_decorators = [Remote("undefineFromPanel")];
1412
+ _runHostHalf_decorators = [Remote("runHostHalf")];
1413
+ _getClientCode_decorators = [Remote("getClientCode")];
1414
+ _resolveRequestRun_decorators = [Remote("resolveRequestRun")];
1415
+ _settleUserRun_decorators = [Remote("settleUserRun")];
1416
+ _stopFromPanel_decorators = [Remote("stopFromPanel")];
1417
+ _syncInspectManifest_decorators = [Remote("syncInspectManifest")];
1418
+ _resolveInspectQuery_decorators = [Remote("resolveInspectQuery")];
1419
+ _inventory_decorators = [Remote("inventory")];
1420
+ _reportRenderFailure_decorators = [Remote("reportRenderFailure")];
1421
+ _reportClientGuardFailure_decorators = [Remote("reportClientGuardFailure")];
1422
+ _invoke_decorators = [Remote("invoke")];
1423
+ __esDecorate(this, null, _undefineFromPanel_decorators, {
1424
+ kind: "method",
1425
+ name: "undefineFromPanel",
1426
+ static: false,
1427
+ private: false,
1428
+ access: {
1429
+ has: (obj) => "undefineFromPanel" in obj,
1430
+ get: (obj) => obj.undefineFromPanel
1431
+ },
1432
+ metadata: _metadata
1433
+ }, null, _instanceExtraInitializers);
1434
+ __esDecorate(this, null, _runHostHalf_decorators, {
1435
+ kind: "method",
1436
+ name: "runHostHalf",
1437
+ static: false,
1438
+ private: false,
1439
+ access: {
1440
+ has: (obj) => "runHostHalf" in obj,
1441
+ get: (obj) => obj.runHostHalf
1442
+ },
1443
+ metadata: _metadata
1444
+ }, null, _instanceExtraInitializers);
1445
+ __esDecorate(this, null, _getClientCode_decorators, {
1446
+ kind: "method",
1447
+ name: "getClientCode",
1448
+ static: false,
1449
+ private: false,
1450
+ access: {
1451
+ has: (obj) => "getClientCode" in obj,
1452
+ get: (obj) => obj.getClientCode
1453
+ },
1454
+ metadata: _metadata
1455
+ }, null, _instanceExtraInitializers);
1456
+ __esDecorate(this, null, _resolveRequestRun_decorators, {
1457
+ kind: "method",
1458
+ name: "resolveRequestRun",
1459
+ static: false,
1460
+ private: false,
1461
+ access: {
1462
+ has: (obj) => "resolveRequestRun" in obj,
1463
+ get: (obj) => obj.resolveRequestRun
1464
+ },
1465
+ metadata: _metadata
1466
+ }, null, _instanceExtraInitializers);
1467
+ __esDecorate(this, null, _settleUserRun_decorators, {
1468
+ kind: "method",
1469
+ name: "settleUserRun",
1470
+ static: false,
1471
+ private: false,
1472
+ access: {
1473
+ has: (obj) => "settleUserRun" in obj,
1474
+ get: (obj) => obj.settleUserRun
1475
+ },
1476
+ metadata: _metadata
1477
+ }, null, _instanceExtraInitializers);
1478
+ __esDecorate(this, null, _stopFromPanel_decorators, {
1479
+ kind: "method",
1480
+ name: "stopFromPanel",
1481
+ static: false,
1482
+ private: false,
1483
+ access: {
1484
+ has: (obj) => "stopFromPanel" in obj,
1485
+ get: (obj) => obj.stopFromPanel
1486
+ },
1487
+ metadata: _metadata
1488
+ }, null, _instanceExtraInitializers);
1489
+ __esDecorate(this, null, _syncInspectManifest_decorators, {
1490
+ kind: "method",
1491
+ name: "syncInspectManifest",
1492
+ static: false,
1493
+ private: false,
1494
+ access: {
1495
+ has: (obj) => "syncInspectManifest" in obj,
1496
+ get: (obj) => obj.syncInspectManifest
1497
+ },
1498
+ metadata: _metadata
1499
+ }, null, _instanceExtraInitializers);
1500
+ __esDecorate(this, null, _resolveInspectQuery_decorators, {
1501
+ kind: "method",
1502
+ name: "resolveInspectQuery",
1503
+ static: false,
1504
+ private: false,
1505
+ access: {
1506
+ has: (obj) => "resolveInspectQuery" in obj,
1507
+ get: (obj) => obj.resolveInspectQuery
1508
+ },
1509
+ metadata: _metadata
1510
+ }, null, _instanceExtraInitializers);
1511
+ __esDecorate(this, null, _inventory_decorators, {
1512
+ kind: "method",
1513
+ name: "inventory",
1514
+ static: false,
1515
+ private: false,
1516
+ access: {
1517
+ has: (obj) => "inventory" in obj,
1518
+ get: (obj) => obj.inventory
1519
+ },
1520
+ metadata: _metadata
1521
+ }, null, _instanceExtraInitializers);
1522
+ __esDecorate(this, null, _reportRenderFailure_decorators, {
1523
+ kind: "method",
1524
+ name: "reportRenderFailure",
1525
+ static: false,
1526
+ private: false,
1527
+ access: {
1528
+ has: (obj) => "reportRenderFailure" in obj,
1529
+ get: (obj) => obj.reportRenderFailure
1530
+ },
1531
+ metadata: _metadata
1532
+ }, null, _instanceExtraInitializers);
1533
+ __esDecorate(this, null, _reportClientGuardFailure_decorators, {
1534
+ kind: "method",
1535
+ name: "reportClientGuardFailure",
1536
+ static: false,
1537
+ private: false,
1538
+ access: {
1539
+ has: (obj) => "reportClientGuardFailure" in obj,
1540
+ get: (obj) => obj.reportClientGuardFailure
1541
+ },
1542
+ metadata: _metadata
1543
+ }, null, _instanceExtraInitializers);
1544
+ __esDecorate(this, null, _invoke_decorators, {
1545
+ kind: "method",
1546
+ name: "invoke",
1547
+ static: false,
1548
+ private: false,
1549
+ access: {
1550
+ has: (obj) => "invoke" in obj,
1551
+ get: (obj) => obj.invoke
1552
+ },
1553
+ metadata: _metadata
1554
+ }, null, _instanceExtraInitializers);
1555
+ if (_metadata) Object.defineProperty(this, Symbol.metadata, {
1556
+ enumerable: true,
1557
+ configurable: true,
1558
+ writable: true,
1559
+ value: _metadata
1560
+ });
1561
+ }
1562
+ static inject = ["tools"];
1563
+ static Config = z.object({ vmTimeoutMs: z.number().min(1).default(5e3) });
1564
+ rootCtx = __runInitializers(this, _instanceExtraInitializers);
1565
+ registry = new DynamicCordisRegistry();
1566
+ inspectRegistry;
1567
+ starting = /* @__PURE__ */ new Map();
1568
+ resolved;
1569
+ group;
1570
+ /** Create the service under the Host composition. */
1571
+ constructor(ctx, config) {
1572
+ super(ctx, "dynamicCordisRunner");
1573
+ this.rootCtx = ctx;
1574
+ this.resolved = config;
1575
+ this.inspectRegistry = new CordisInspectRegistryService(ctx);
1576
+ }
1577
+ /**
1578
+ * Define a new Plugin's first Package or append a Package to an existing Plugin.
1579
+ * @param request - Session ownership, Plugin selection, metadata, and source code.
1580
+ * @returns Host-minted Plugin and Package identities with declared-half metadata.
1581
+ */
1582
+ define(request) {
1583
+ const name = request.name.trim();
1584
+ const purpose = request.purpose.trim();
1585
+ if (name.length === 0) throw new Error("cordis_define needs a non-empty `name`");
1586
+ if (purpose.length === 0) throw new Error("cordis_define needs a non-empty `purpose`");
1587
+ if (request.code.host === void 0 && request.code.client === void 0) throw new Error("cordis_define needs `code.host`, `code.client`, or both");
1588
+ if (request.code.host !== void 0) precheckCode(request.code.host, "code.host");
1589
+ if (request.code.client !== void 0) precheckCode(request.code.client, "code.client");
1590
+ let plugin;
1591
+ if (request.plugin.kind === "new") {
1592
+ const prefix = request.plugin.idPrefix.trim();
1593
+ if (!/^[a-z]{3,6}$/.test(prefix)) throw new Error("cordis_define `plugin.idPrefix` must contain 3–6 lowercase English letters");
1594
+ plugin = {
1595
+ pluginId: CordisDynamicPluginId(this.registry.mintPluginId(prefix)),
1596
+ sessionId: request.sessionId,
1597
+ packages: /* @__PURE__ */ new Map(),
1598
+ approvedClientPackages: /* @__PURE__ */ new Set(),
1599
+ clientVersionUpdatesApproved: false
1600
+ };
1601
+ this.registry.add(plugin);
1602
+ } else {
1603
+ const found = this.registry.get(request.plugin.pluginId);
1604
+ if (found === void 0 || found.sessionId !== request.sessionId) throw new Error(missingPluginMessage(request.plugin.pluginId));
1605
+ plugin = found;
1606
+ }
1607
+ const packageId = CordisDynamicPackageId(this.registry.mintPackageId());
1608
+ const definition = {
1609
+ packageId,
1610
+ name,
1611
+ purpose,
1612
+ ...request.code.host === void 0 ? {} : { hostCode: request.code.host },
1613
+ ...request.code.client === void 0 ? {} : { clientCode: request.code.client }
1614
+ };
1615
+ plugin.packages.set(packageId, definition);
1616
+ return {
1617
+ pluginId: plugin.pluginId,
1618
+ packageId,
1619
+ name,
1620
+ purpose,
1621
+ hasHostHalf: definition.hostCode !== void 0,
1622
+ hasClientHalf: definition.clientCode !== void 0
1623
+ };
1624
+ }
1625
+ /**
1626
+ * Remove a Plugin, its active run, and all immutable Packages.
1627
+ * @param agent - Agent whose Session must own the Plugin.
1628
+ * @param pluginId - Stable Plugin identity to remove.
1629
+ * @returns Whether removal succeeded and whether it stopped an active run.
1630
+ */
1631
+ async undefine(agent, pluginId) {
1632
+ const plugin = this.owned(agent, pluginId);
1633
+ if (plugin === void 0) return {
1634
+ ok: false,
1635
+ reason: "plugin-missing",
1636
+ message: missingPluginMessage(pluginId)
1637
+ };
1638
+ const wasRunning = plugin.run !== void 0;
1639
+ this.cancelPending(pluginId, `dynamic plugin "${pluginId}" was removed before approval`);
1640
+ if (plugin.run !== void 0) await this.retract(plugin);
1641
+ this.registry.delete(pluginId);
1642
+ return {
1643
+ ok: true,
1644
+ wasRunning
1645
+ };
1646
+ }
1647
+ /**
1648
+ * Remove a Plugin from the user panel and queue the resulting state change for the model's next step.
1649
+ * @param agent - Agent whose Session owns the Plugin and receives the context.
1650
+ * @param pluginId - Stable Plugin identity to remove.
1651
+ * @returns Whether removal succeeded and whether it stopped an active run.
1652
+ */
1653
+ async undefineFromPanel(agent, pluginId) {
1654
+ const result = await this.undefine(agent, pluginId);
1655
+ if (result.ok) this.injectUserContext(agent, `The user removed Cordis Plugin ${pluginId} and all of its Packages. The Plugin no longer exists.`);
1656
+ return result;
1657
+ }
1658
+ /**
1659
+ * Start or update one Package for a model tool call. An unauthorized Client
1660
+ * Package waits for approval; Plugin-wide authorization covers later versions.
1661
+ * @param agent - Agent whose Session must own the Plugin.
1662
+ * @param pluginId - Stable Plugin identity to activate.
1663
+ * @param packageId - Immutable Package version to activate.
1664
+ * @param mode - Whether to run the current version or switch versions.
1665
+ * @param signal - Tool-call cancellation signal while the activation request is being created.
1666
+ * @returns The successful activation identity or an actionable refusal.
1667
+ */
1668
+ async run(agent, pluginId, packageId, mode, signal) {
1669
+ const plan = this.resolvePlan(agent, pluginId, packageId, mode);
1670
+ if (!plan.ok) return plan.response;
1671
+ if (signal?.aborted === true) return {
1672
+ ok: false,
1673
+ reason: "cancelled",
1674
+ message: `the run request for dynamic plugin "${pluginId}" was cancelled before activation`
1675
+ };
1676
+ if (this.registry.pendingRequestFor(pluginId) !== void 0) return {
1677
+ ok: false,
1678
+ reason: "transition-in-flight",
1679
+ message: `dynamic plugin "${pluginId}" already has a pending run request`
1680
+ };
1681
+ const attempt = this.createAttempt(plan);
1682
+ plan.plugin.nextPackageId = packageId;
1683
+ plan.plugin.latestRun = attempt;
1684
+ if (plan.definition.clientCode === void 0) {
1685
+ const started = await this.activate(plan, void 0, false, attempt);
1686
+ if (started.ok) return this.runResponse(plan.plugin, started);
1687
+ this.failAttempt(plan.plugin, attempt, "host-load", started);
1688
+ return {
1689
+ ...started,
1690
+ reason: "host-half-failed"
1691
+ };
1692
+ }
1693
+ const requestId = ApprovalRequestId(this.registry.mintApprovalRequestId());
1694
+ const requiresApproval = !plan.plugin.clientVersionUpdatesApproved && !plan.plugin.approvedClientPackages.has(packageId);
1695
+ attempt.approvalRequestId = requestId;
1696
+ attempt.requiresApproval = requiresApproval;
1697
+ attempt.status = requiresApproval ? "awaiting-approval" : "starting-host";
1698
+ this.registry.armRequest(requestId, {
1699
+ agentId: agent.id,
1700
+ pluginId,
1701
+ packageId,
1702
+ pluginRunId: attempt.pluginRunId,
1703
+ mode,
1704
+ requiresApproval
1705
+ });
1706
+ this.ctx.emit("cordis/request-run", {
1707
+ requestId,
1708
+ agentId: agent.id,
1709
+ pluginId,
1710
+ packageId,
1711
+ mode,
1712
+ name: plan.definition.name,
1713
+ purpose: plan.definition.purpose,
1714
+ requiresApproval
1715
+ });
1716
+ return {
1717
+ ok: true,
1718
+ status: requiresApproval ? "awaiting-approval" : "starting",
1719
+ pluginId,
1720
+ packageId,
1721
+ pluginRunId: attempt.pluginRunId,
1722
+ mode,
1723
+ waitingFor: [],
1724
+ ...plan.plugin.currentPackageId === void 0 ? {} : { currentPackageId: plan.plugin.currentPackageId },
1725
+ nextPackageId: packageId
1726
+ };
1727
+ }
1728
+ /**
1729
+ * Start Host code for an approved request or a direct panel gesture.
1730
+ * @param agent - Agent whose Session must own the Plugin.
1731
+ * @param pluginId - Stable Plugin identity to activate.
1732
+ * @param packageId - Immutable Package version to activate.
1733
+ * @param mode - Whether to run the current version or switch versions.
1734
+ * @param requestId - Model-driven request identity, or null for a direct user gesture.
1735
+ * @param approveFutureVersions - Whether this approval covers later Packages of the same Plugin.
1736
+ * @returns The exact Host activation or a failure message.
1737
+ */
1738
+ async runHostHalf(agent, pluginId, packageId, mode, requestId, approveFutureVersions) {
1739
+ const plan = this.resolvePlan(agent, pluginId, packageId, mode, requestId === null);
1740
+ if (!plan.ok) return {
1741
+ ok: false,
1742
+ message: plan.response.message
1743
+ };
1744
+ let attempt;
1745
+ if (requestId !== null) {
1746
+ const pending = this.registry.peekRequest(requestId);
1747
+ if (pending === void 0 || pending.pluginId !== pluginId || pending.packageId !== packageId || pending.mode !== mode) return {
1748
+ ok: false,
1749
+ message: `run request "${requestId}" does not authorize ${pluginId}/${packageId}`
1750
+ };
1751
+ const latest = plan.plugin.latestRun;
1752
+ const expectedStatus = pending.requiresApproval ? "awaiting-approval" : "starting-host";
1753
+ if (latest === void 0 || latest.pluginRunId !== pending.pluginRunId || latest.status !== expectedStatus && !pending.requiresApproval && latest.status !== "client-pending") return {
1754
+ ok: false,
1755
+ message: `run request "${requestId}" no longer identifies the latest run of ${pluginId}`
1756
+ };
1757
+ attempt = latest;
1758
+ if (pending.requiresApproval) {
1759
+ plan.plugin.approvedClientPackages.add(packageId);
1760
+ if (approveFutureVersions) plan.plugin.clientVersionUpdatesApproved = true;
1761
+ }
1762
+ } else {
1763
+ const pending = this.registry.pendingRequestFor(pluginId);
1764
+ if (pending !== void 0) return {
1765
+ ok: false,
1766
+ message: `dynamic plugin "${pluginId}" has pending run request ${pending}`
1767
+ };
1768
+ const attached = plan.plugin.run?.packageId === packageId && plan.plugin.latestRun?.pluginRunId === plan.plugin.run.pluginRunId ? plan.plugin.latestRun : void 0;
1769
+ attempt = attached ?? this.createAttempt(plan);
1770
+ if (attached === void 0) {
1771
+ plan.plugin.nextPackageId = packageId;
1772
+ plan.plugin.latestRun = attempt;
1773
+ }
1774
+ if (plan.definition.clientCode !== void 0) plan.plugin.approvedClientPackages.add(packageId);
1775
+ }
1776
+ const attaching = attempt.pluginRunId === plan.plugin.run?.pluginRunId;
1777
+ if (!attaching) {
1778
+ attempt.status = "starting-host";
1779
+ if (attempt.host.status !== "absent") attempt.host = {
1780
+ status: "pending",
1781
+ waitingFor: []
1782
+ };
1783
+ }
1784
+ const started = await this.activate(plan, requestId ?? void 0, attaching, attempt);
1785
+ if (!started.ok) this.failAttempt(plan.plugin, attempt, "host-load", started);
1786
+ return started;
1787
+ }
1788
+ /**
1789
+ * Fetch Client code for the exact active run.
1790
+ * @param agent - Agent whose Session must own the Plugin.
1791
+ * @param pluginId - Stable Plugin identity to read.
1792
+ * @param pluginRunId - Exact active run authorized to receive source.
1793
+ * @returns Client source and its Plugin, Package, and run identities.
1794
+ */
1795
+ getClientCode(agent, pluginId, pluginRunId) {
1796
+ const plugin = this.owned(agent, pluginId);
1797
+ if (plugin === void 0) throw new Error(missingPluginMessage(pluginId));
1798
+ const run = plugin.run;
1799
+ if (run === void 0 || run.pluginRunId !== pluginRunId) throw new Error(`dynamic plugin "${pluginId}" is not running activation "${pluginRunId}"`);
1800
+ const definition = plugin.packages.get(run.packageId);
1801
+ if (definition?.clientCode === void 0) throw new Error(`package "${run.packageId}" has no Client half`);
1802
+ return {
1803
+ code: definition.clientCode,
1804
+ name: definition.name,
1805
+ pluginId,
1806
+ packageId: run.packageId,
1807
+ pluginRunId
1808
+ };
1809
+ }
1810
+ /**
1811
+ * Resolve one model-driven Client activation request.
1812
+ * @param requestId - Request identity to settle once.
1813
+ * @param resolution - Browser refusal or exact Client activation result.
1814
+ * @returns Whether the still-pending request accepted this resolution.
1815
+ */
1816
+ async resolveRequestRun(requestId, resolution) {
1817
+ const pending = this.registry.peekRequest(requestId);
1818
+ if (pending === void 0) return { accepted: false };
1819
+ const plugin = this.registry.get(pending.pluginId);
1820
+ if (resolution.ok && plugin?.run?.pluginRunId !== resolution.pluginRunId) return { accepted: false };
1821
+ if (!resolution.ok && resolution.pluginRunId !== void 0 && plugin?.run?.pluginRunId !== resolution.pluginRunId) return { accepted: false };
1822
+ this.registry.claimRequest(requestId);
1823
+ const settled = await this.settleActivation(plugin, resolution, requestId);
1824
+ this.announceResolved(requestId, resolution, pending.requiresApproval ? void 0 : "completed");
1825
+ this.steerRunOutcome(pending, settled);
1826
+ return { accepted: true };
1827
+ }
1828
+ /**
1829
+ * Settle a direct panel run after this page loaded or failed its Client half.
1830
+ * @param agent - Agent whose Session must own the Plugin.
1831
+ * @param pluginId - Stable Plugin identity being settled.
1832
+ * @param resolution - Exact Client activation result from the acting page.
1833
+ * @returns The committed activation or its failure.
1834
+ */
1835
+ async settleUserRun(agent, pluginId, resolution) {
1836
+ const plugin = this.owned(agent, pluginId);
1837
+ if (plugin === void 0) return {
1838
+ ok: false,
1839
+ reason: "plugin-missing",
1840
+ message: missingPluginMessage(pluginId)
1841
+ };
1842
+ const settled = await this.settleActivation(plugin, resolution);
1843
+ this.injectUserRunOutcome(agent, pluginId, settled);
1844
+ return settled;
1845
+ }
1846
+ /**
1847
+ * Stop the active run while retaining every Package version.
1848
+ * @param agent - Agent whose Session must own the Plugin.
1849
+ * @param pluginId - Stable Plugin identity to stop.
1850
+ * @returns Success or the reason no run was stopped.
1851
+ */
1852
+ async stop(agent, pluginId) {
1853
+ const plugin = this.owned(agent, pluginId);
1854
+ if (plugin === void 0) return {
1855
+ ok: false,
1856
+ reason: "plugin-missing",
1857
+ message: missingPluginMessage(pluginId)
1858
+ };
1859
+ const pending = this.registry.pendingRequestFor(pluginId);
1860
+ if (plugin.run === void 0 && pending === void 0) return {
1861
+ ok: false,
1862
+ reason: "not-running",
1863
+ message: `dynamic plugin "${pluginId}" is not running`
1864
+ };
1865
+ if (pending !== void 0) this.cancelPending(pluginId, `dynamic plugin "${pluginId}" was stopped before approval`);
1866
+ if (plugin.run !== void 0) await this.retract(plugin);
1867
+ if (plugin.latestRun !== void 0) {
1868
+ plugin.latestRun.status = "stopped";
1869
+ if (plugin.latestRun.host.status !== "absent") plugin.latestRun.host = {
1870
+ status: "stopped",
1871
+ waitingFor: []
1872
+ };
1873
+ if (plugin.latestRun.client.status !== "absent") plugin.latestRun.client = {
1874
+ status: "stopped",
1875
+ waitingFor: []
1876
+ };
1877
+ }
1878
+ return { ok: true };
1879
+ }
1880
+ /**
1881
+ * Stop a Plugin from the user panel and queue the resulting state change for the model's next step.
1882
+ * @param agent - Agent whose Session owns the Plugin and receives the context.
1883
+ * @param pluginId - Stable Plugin identity to stop.
1884
+ * @returns Success or the reason no run was stopped.
1885
+ */
1886
+ async stopFromPanel(agent, pluginId) {
1887
+ const result = await this.stop(agent, pluginId);
1888
+ if (!result.ok) return result;
1889
+ const plugin = this.owned(agent, pluginId);
1890
+ this.injectUserContext(agent, `The user stopped Cordis Plugin ${pluginId}. Its Packages remain defined; currentPackageId is ${plugin?.currentPackageId ?? "none"}.`);
1891
+ return result;
1892
+ }
1893
+ /**
1894
+ * Replace the Host mirror of the Client inspect provider directory.
1895
+ * @param providers - complete Client provider manifest.
1896
+ * @returns null after accepting the manifest.
1897
+ */
1898
+ syncInspectManifest(providers) {
1899
+ this.inspectRegistry.syncClientManifest(providers);
1900
+ return null;
1901
+ }
1902
+ /**
1903
+ * Claim one pending Client inspect query with its live result.
1904
+ * @param agent - Session that owns the query.
1905
+ * @param requestId - exact pending query identity.
1906
+ * @param resolution - provider result or structured refusal.
1907
+ * @returns whether this answer won the query.
1908
+ */
1909
+ resolveInspectQuery(agent, requestId, resolution) {
1910
+ return this.inspectRegistry.resolveClientQuery(agent, requestId, resolution);
1911
+ }
1912
+ /**
1913
+ * Frame-wide inventory, grouped as one row per stable Plugin.
1914
+ * @returns Source-free metadata for every process-local Plugin.
1915
+ */
1916
+ inventory() {
1917
+ return this.registry.all().map((plugin) => ({
1918
+ pluginId: plugin.pluginId,
1919
+ agentId: plugin.sessionId,
1920
+ packages: [...plugin.packages.values()].map((definition) => ({
1921
+ packageId: definition.packageId,
1922
+ name: definition.name,
1923
+ purpose: definition.purpose,
1924
+ hasHostHalf: definition.hostCode !== void 0,
1925
+ hasClientHalf: definition.clientCode !== void 0
1926
+ })),
1927
+ ...plugin.currentPackageId === void 0 ? {} : { currentPackageId: plugin.currentPackageId },
1928
+ ...plugin.nextPackageId === void 0 ? {} : { nextPackageId: plugin.nextPackageId },
1929
+ ...plugin.run === void 0 ? {} : { activeRun: {
1930
+ pluginRunId: plugin.run.pluginRunId,
1931
+ packageId: plugin.run.packageId
1932
+ } },
1933
+ ...plugin.latestRun === void 0 ? {} : { latestRun: cloneAttempt(plugin.latestRun) }
1934
+ }));
1935
+ }
1936
+ /**
1937
+ * Read one Session's Host-rich state for inspection and result rendering.
1938
+ * @param agent - Agent whose Session selects visible Plugins.
1939
+ * @returns Plugin versions, active runs, Host fibers, and render failures.
1940
+ */
1941
+ snapshot(agent) {
1942
+ return this.registry.ofSession(agent.id).map((plugin) => ({
1943
+ pluginId: plugin.pluginId,
1944
+ ...plugin.currentPackageId === void 0 ? {} : { currentPackageId: plugin.currentPackageId },
1945
+ ...plugin.nextPackageId === void 0 ? {} : { nextPackageId: plugin.nextPackageId },
1946
+ packages: [...plugin.packages.values()].map((definition) => ({
1947
+ packageId: definition.packageId,
1948
+ name: definition.name,
1949
+ purpose: definition.purpose,
1950
+ hasHostHalf: definition.hostCode !== void 0,
1951
+ hasClientHalf: definition.clientCode !== void 0
1952
+ })),
1953
+ ...plugin.run === void 0 ? {} : { activeRun: {
1954
+ pluginRunId: plugin.run.pluginRunId,
1955
+ packageId: plugin.run.packageId,
1956
+ ...plugin.run.fiber === void 0 ? {} : { fiber: plugin.run.fiber },
1957
+ handlers: [...plugin.run.handlers.keys()],
1958
+ ...plugin.run.renderFailure === void 0 ? {} : { renderFailure: plugin.run.renderFailure }
1959
+ } },
1960
+ ...plugin.latestRun === void 0 ? {} : { latestRun: cloneAttempt(plugin.latestRun) }
1961
+ }));
1962
+ }
1963
+ /**
1964
+ * Read source-free context for an explicit `@pluginId` user gesture.
1965
+ * @param agent - Agent whose Session must own the Plugin.
1966
+ * @param pluginId - Stable Plugin identity referenced by the user.
1967
+ * @returns The preferred modification base, or undefined when unavailable.
1968
+ */
1969
+ reference(agent, pluginId) {
1970
+ const plugin = this.owned(agent, pluginId);
1971
+ if (plugin === void 0) return void 0;
1972
+ const packageId = plugin.nextPackageId ?? plugin.currentPackageId ?? [...plugin.packages.keys()].at(-1);
1973
+ if (packageId === void 0) return void 0;
1974
+ const definition = plugin.packages.get(packageId);
1975
+ if (definition === void 0) return void 0;
1976
+ return {
1977
+ pluginId,
1978
+ packageId,
1979
+ name: definition.name,
1980
+ purpose: definition.purpose,
1981
+ ...plugin.currentPackageId === void 0 ? {} : { currentPackageId: plugin.currentPackageId },
1982
+ ...plugin.nextPackageId === void 0 ? {} : { nextPackageId: plugin.nextPackageId },
1983
+ ...plugin.run === void 0 ? {} : { activeRun: {
1984
+ pluginRunId: plugin.run.pluginRunId,
1985
+ packageId: plugin.run.packageId
1986
+ } },
1987
+ ...plugin.latestRun === void 0 ? {} : { latestRun: cloneAttempt(plugin.latestRun) }
1988
+ };
1989
+ }
1990
+ /**
1991
+ * List source-free Plugin summaries owned by one Session.
1992
+ * @param agent - Agent whose Session selects visible Plugins.
1993
+ * @returns one summary per Plugin in creation order.
1994
+ */
1995
+ listPlugins(agent) {
1996
+ return this.registry.ofSession(agent.id).map((plugin) => this.inspectPlugin(agent, plugin.pluginId));
1997
+ }
1998
+ /**
1999
+ * Inspect one Plugin without returning Package source.
2000
+ * @param agent - Agent whose Session must own the Plugin.
2001
+ * @param pluginId - stable Plugin identity.
2002
+ * @returns version pointers, latest run, and all Package summaries.
2003
+ */
2004
+ inspectPlugin(agent, pluginId) {
2005
+ const plugin = this.owned(agent, pluginId);
2006
+ if (plugin === void 0) throw new Error(missingPluginMessage(pluginId));
2007
+ const reference = this.reference(agent, pluginId);
2008
+ if (reference === void 0) throw new Error(`dynamic plugin "${pluginId}" has no package`);
2009
+ return {
2010
+ ...reference,
2011
+ packages: [...plugin.packages.values()].map((definition) => ({
2012
+ packageId: definition.packageId,
2013
+ name: definition.name,
2014
+ purpose: definition.purpose,
2015
+ hasHostHalf: definition.hostCode !== void 0,
2016
+ hasClientHalf: definition.clientCode !== void 0
2017
+ }))
2018
+ };
2019
+ }
2020
+ /**
2021
+ * Read one exact immutable Package and its Host and Client source.
2022
+ * @param agent - Agent whose Session must own the Plugin.
2023
+ * @param pluginId - Stable Plugin identity that owns the Package.
2024
+ * @param packageId - Exact immutable Package identity to inspect.
2025
+ * @returns Package metadata, source, and the Plugin's lifecycle pointers.
2026
+ */
2027
+ inspectPackage(agent, pluginId, packageId) {
2028
+ const plugin = this.owned(agent, pluginId);
2029
+ if (plugin === void 0) throw new Error(missingPluginMessage(pluginId));
2030
+ const definition = plugin.packages.get(packageId);
2031
+ if (definition === void 0) throw new Error(`dynamic package "${packageId}" does not exist on plugin "${pluginId}"`);
2032
+ return {
2033
+ pluginId,
2034
+ packageId,
2035
+ name: definition.name,
2036
+ purpose: definition.purpose,
2037
+ code: {
2038
+ ...definition.hostCode === void 0 ? {} : { host: definition.hostCode },
2039
+ ...definition.clientCode === void 0 ? {} : { client: definition.clientCode }
2040
+ },
2041
+ ...plugin.currentPackageId === void 0 ? {} : { currentPackageId: plugin.currentPackageId },
2042
+ ...plugin.nextPackageId === void 0 ? {} : { nextPackageId: plugin.nextPackageId },
2043
+ ...plugin.run === void 0 ? {} : { activeRun: {
2044
+ pluginRunId: plugin.run.pluginRunId,
2045
+ packageId: plugin.run.packageId
2046
+ } },
2047
+ ...plugin.latestRun === void 0 ? {} : { latestRun: cloneAttempt(plugin.latestRun) }
2048
+ };
2049
+ }
2050
+ /**
2051
+ * Record a post-load render failure for the exact active run.
2052
+ * @param agent - Agent whose Session must own the Plugin.
2053
+ * @param pluginId - Stable Plugin identity that rendered.
2054
+ * @param pluginRunId - Exact active run that produced the failure.
2055
+ * @param failure - Slot, message, and entry-retirement result.
2056
+ * @returns Null after recording or ignoring a stale report.
2057
+ */
2058
+ async reportRenderFailure(agent, pluginId, pluginRunId, failure) {
2059
+ const plugin = this.owned(agent, pluginId);
2060
+ if (plugin?.run?.pluginRunId === pluginRunId) {
2061
+ const run = plugin.run;
2062
+ const definition = plugin.packages.get(plugin.run.packageId);
2063
+ const shouldSteer = run.renderFailure === void 0;
2064
+ run.renderFailure = failure;
2065
+ const attempt = plugin.latestRun;
2066
+ if (attempt?.pluginRunId === pluginRunId) {
2067
+ attempt.error = this.diagnostic(plugin, attempt, "client-render", failure);
2068
+ attempt.client = {
2069
+ status: "failed",
2070
+ waitingFor: attempt.client.waitingFor,
2071
+ error: failure.message
2072
+ };
2073
+ attempt.status = "failed";
2074
+ }
2075
+ if (definition !== void 0 && shouldSteer) this.steerRenderFailure(agent, plugin, definition, pluginRunId, failure);
2076
+ }
2077
+ return await Promise.resolve(null);
2078
+ }
2079
+ /**
2080
+ * Report a Client guard rejection that happened after the Package completed activation.
2081
+ * @param agent - Agent whose Session must own the Plugin.
2082
+ * @param pluginId - Stable Plugin identity whose Client code was rejected.
2083
+ * @param pluginRunId - Exact active run that produced the rejection.
2084
+ * @param failure - Original guard message and stack.
2085
+ * @returns Null after reporting or ignoring a stale/startup failure.
2086
+ */
2087
+ async reportClientGuardFailure(agent, pluginId, pluginRunId, failure) {
2088
+ const plugin = this.owned(agent, pluginId);
2089
+ const run = plugin?.run;
2090
+ if (plugin !== void 0 && run?.pluginRunId === pluginRunId) this.steerGuardFailure(plugin, run, "Client", failure);
2091
+ return await Promise.resolve(null);
2092
+ }
2093
+ /**
2094
+ * Invoke an active Host method while rejecting stale Client runs.
2095
+ * @param pluginId - Stable Plugin identity that owns the method.
2096
+ * @param pluginRunId - Exact active run authorizing the call.
2097
+ * @param method - Registered Host handler name.
2098
+ * @param args - JSON argument delivered to the handler.
2099
+ * @returns The JSON result or a typed invocation failure.
2100
+ */
2101
+ async invoke(pluginId, pluginRunId, method, args) {
2102
+ const plugin = this.registry.get(pluginId);
2103
+ if (plugin === void 0 || plugin.run === void 0) return {
2104
+ ok: false,
2105
+ code: "plugin-not-running",
2106
+ message: `dynamic plugin "${pluginId}" is not running`
2107
+ };
2108
+ const run = plugin.run;
2109
+ if (run.pluginRunId !== pluginRunId) return {
2110
+ ok: false,
2111
+ code: "stale-run",
2112
+ message: `activation "${pluginRunId}" is no longer active`
2113
+ };
2114
+ const handler = run.handlers.get(method);
2115
+ if (handler === void 0) return {
2116
+ ok: false,
2117
+ code: "method-not-found",
2118
+ message: `dynamic plugin "${pluginId}" registered no Host method "${method}"`
2119
+ };
2120
+ try {
2121
+ return {
2122
+ ok: true,
2123
+ value: await handler(args)
2124
+ };
2125
+ } catch (error) {
2126
+ const failure = errorDetails(error);
2127
+ this.steerHostHandlerFailure(plugin, run, method, failure);
2128
+ return {
2129
+ ok: false,
2130
+ code: "handler-error",
2131
+ ...failure
2132
+ };
2133
+ }
2134
+ }
2135
+ resolvePlan(agent, pluginId, packageId, mode, allowActiveAttach = false) {
2136
+ const plugin = this.owned(agent, pluginId);
2137
+ if (plugin === void 0) return {
2138
+ ok: false,
2139
+ response: {
2140
+ ok: false,
2141
+ reason: "plugin-missing",
2142
+ message: missingPluginMessage(pluginId)
2143
+ }
2144
+ };
2145
+ const definition = plugin.packages.get(packageId);
2146
+ if (definition === void 0) return {
2147
+ ok: false,
2148
+ response: {
2149
+ ok: false,
2150
+ reason: "package-missing",
2151
+ message: `plugin "${pluginId}" has no package "${packageId}"`
2152
+ }
2153
+ };
2154
+ const current = plugin.currentPackageId;
2155
+ if (mode === "update" && (current === void 0 || current === packageId)) return {
2156
+ ok: false,
2157
+ response: {
2158
+ ok: false,
2159
+ reason: "invalid-mode",
2160
+ message: current === void 0 ? `plugin "${pluginId}" has no successful version yet; start "${packageId}" with mode "run"` : `package "${packageId}" is already current; use mode "run"`
2161
+ }
2162
+ };
2163
+ if (mode === "run" && current !== void 0 && current !== packageId) return {
2164
+ ok: false,
2165
+ response: {
2166
+ ok: false,
2167
+ reason: "invalid-mode",
2168
+ message: `package "${packageId}" differs from current "${current}"; use mode "update"`
2169
+ }
2170
+ };
2171
+ if (!allowActiveAttach && this.starting.has(pluginId)) return {
2172
+ ok: false,
2173
+ response: {
2174
+ ok: false,
2175
+ reason: "transition-in-flight",
2176
+ message: `plugin "${pluginId}" is already starting`
2177
+ }
2178
+ };
2179
+ return {
2180
+ ok: true,
2181
+ plugin,
2182
+ definition,
2183
+ mode
2184
+ };
2185
+ }
2186
+ activate(plan, requestId, allowActiveAttach, attempt) {
2187
+ const inFlight = this.starting.get(plan.plugin.pluginId);
2188
+ if (inFlight !== void 0) return inFlight;
2189
+ const starting = this.startFresh(plan, requestId, allowActiveAttach, attempt);
2190
+ this.starting.set(plan.plugin.pluginId, starting);
2191
+ return starting.finally(() => {
2192
+ this.starting.delete(plan.plugin.pluginId);
2193
+ });
2194
+ }
2195
+ async startFresh(plan, requestId, allowActiveAttach, attempt) {
2196
+ const { plugin, definition, mode } = plan;
2197
+ if (allowActiveAttach && plugin.run?.packageId === definition.packageId && plugin.run.pluginRunId === attempt.pluginRunId) return {
2198
+ ok: true,
2199
+ pluginId: plugin.pluginId,
2200
+ packageId: definition.packageId,
2201
+ pluginRunId: plugin.run.pluginRunId,
2202
+ waitingFor: missingFor(this.ctx, plugin.run),
2203
+ startedHere: false
2204
+ };
2205
+ if (plugin.run !== void 0) await this.retract(plugin);
2206
+ if (mode === "update" || plugin.currentPackageId === void 0) plugin.nextPackageId = definition.packageId;
2207
+ const run = {
2208
+ pluginRunId: attempt.pluginRunId,
2209
+ packageId: definition.packageId,
2210
+ handlers: /* @__PURE__ */ new Map(),
2211
+ handlerDisposers: [],
2212
+ reportedRuntimeErrors: /* @__PURE__ */ new Set(),
2213
+ ...requestId === void 0 ? {} : { startedForRequest: requestId }
2214
+ };
2215
+ if (definition.hostCode !== void 0) {
2216
+ const failure = await this.startHost(plugin, definition.hostCode, run);
2217
+ if (failure !== void 0) return {
2218
+ ok: false,
2219
+ ...failure
2220
+ };
2221
+ }
2222
+ plugin.run = run;
2223
+ this.ctx.emit("cordis/dynamic-package", {
2224
+ pluginId: plugin.pluginId,
2225
+ packageId: definition.packageId,
2226
+ pluginRunId: run.pluginRunId,
2227
+ name: definition.name
2228
+ });
2229
+ attempt.host = {
2230
+ status: run.fiber === void 0 ? "absent" : missingFor(this.ctx, run).length === 0 ? "running" : "waiting",
2231
+ waitingFor: missingFor(this.ctx, run)
2232
+ };
2233
+ if (definition.clientCode === void 0) this.commitActivation(plugin, run);
2234
+ else {
2235
+ attempt.status = "client-pending";
2236
+ attempt.client = {
2237
+ status: "pending",
2238
+ waitingFor: []
2239
+ };
2240
+ }
2241
+ return {
2242
+ ok: true,
2243
+ pluginId: plugin.pluginId,
2244
+ packageId: definition.packageId,
2245
+ pluginRunId: run.pluginRunId,
2246
+ waitingFor: missingFor(this.ctx, run),
2247
+ startedHere: true
2248
+ };
2249
+ }
2250
+ async startHost(plugin, hostCode, run) {
2251
+ const handle = (method, fn) => {
2252
+ const normalized = normalizeHandler(method, fn);
2253
+ run.handlers.set(normalized.method, normalized.handler);
2254
+ const dispose = () => {
2255
+ if (run.handlers.get(normalized.method) === normalized.handler) run.handlers.delete(normalized.method);
2256
+ };
2257
+ run.handlerDisposers.push(dispose);
2258
+ return dispose;
2259
+ };
2260
+ try {
2261
+ const evaluated = await evaluateHostCode(createSandbox(plugin.pluginId, { handle }), hostCode, plugin.pluginId, this.resolved.vmTimeoutMs);
2262
+ if (!isPlugin(evaluated)) throw new Error(evaluated === void 0 ? "the Host half returned `undefined` — did you forget `return`?" : "the Host half must return a Plugin function or an object with apply(ctx)");
2263
+ run.fiber = await startHostHalf(this.requireGroup(), evaluated, (error) => {
2264
+ this.steerGuardFailure(plugin, run, "Host", errorDetails(error));
2265
+ });
2266
+ return;
2267
+ } catch (error) {
2268
+ for (const dispose of run.handlerDisposers.splice(0)) dispose();
2269
+ return errorDetails(error);
2270
+ }
2271
+ }
2272
+ async settleActivation(plugin, resolution, requestId) {
2273
+ if (plugin === void 0) return {
2274
+ ok: false,
2275
+ reason: "plugin-missing",
2276
+ message: "the dynamic plugin was removed during activation"
2277
+ };
2278
+ const attempt = plugin.latestRun;
2279
+ if (!resolution.ok) {
2280
+ if (resolution.reason === "rejected") {
2281
+ if (attempt !== void 0) {
2282
+ attempt.status = "rejected";
2283
+ attempt.error = this.diagnostic(plugin, attempt, "approval", resolution.message ?? "the run request was declined");
2284
+ attempt.client = {
2285
+ status: "stopped",
2286
+ waitingFor: []
2287
+ };
2288
+ }
2289
+ return {
2290
+ ok: false,
2291
+ reason: "rejected",
2292
+ message: resolution.message ?? "the run request was declined"
2293
+ };
2294
+ }
2295
+ const run = plugin.run;
2296
+ if (run !== void 0 && resolution.pluginRunId === run.pluginRunId && (requestId === void 0 || run.startedForRequest === requestId) && resolution.startedHere !== false) await this.retract(plugin);
2297
+ if (attempt !== void 0 && (resolution.pluginRunId === void 0 || attempt.pluginRunId === resolution.pluginRunId)) this.failAttempt(plugin, attempt, resolution.reason === "host-half-failed" ? "host-apply" : "client-apply", {
2298
+ message: resolution.message ?? resolution.reason,
2299
+ ...resolution.stack === void 0 ? {} : { stack: resolution.stack }
2300
+ });
2301
+ return {
2302
+ ok: false,
2303
+ reason: resolution.reason,
2304
+ message: resolution.message ?? resolution.reason,
2305
+ ...resolution.stack === void 0 ? {} : { stack: resolution.stack }
2306
+ };
2307
+ }
2308
+ const run = plugin.run;
2309
+ if (run === void 0 || run.pluginRunId !== resolution.pluginRunId) return {
2310
+ ok: false,
2311
+ reason: "client-half-failed",
2312
+ message: `activation "${resolution.pluginRunId}" is no longer active`
2313
+ };
2314
+ if (attempt !== void 0 && attempt.pluginRunId === run.pluginRunId) attempt.client = {
2315
+ status: resolution.waitingFor === void 0 || resolution.waitingFor.length === 0 ? "running" : "waiting",
2316
+ waitingFor: resolution.waitingFor ?? []
2317
+ };
2318
+ this.commitActivation(plugin, run);
2319
+ return {
2320
+ ...this.runResponse(plugin, {
2321
+ ok: true,
2322
+ pluginId: plugin.pluginId,
2323
+ packageId: run.packageId,
2324
+ pluginRunId: run.pluginRunId,
2325
+ waitingFor: missingFor(this.ctx, run),
2326
+ startedHere: false
2327
+ }),
2328
+ ...resolution.waitingFor === void 0 ? {} : { clientWaitingFor: resolution.waitingFor }
2329
+ };
2330
+ }
2331
+ commitActivation(plugin, run) {
2332
+ plugin.currentPackageId = run.packageId;
2333
+ delete plugin.nextPackageId;
2334
+ delete run.startedForRequest;
2335
+ const attempt = plugin.latestRun;
2336
+ if (attempt?.pluginRunId === run.pluginRunId) {
2337
+ attempt.status = attempt.host.status === "waiting" || attempt.client.status === "waiting" ? "waiting" : "running";
2338
+ delete attempt.approvalRequestId;
2339
+ delete attempt.requiresApproval;
2340
+ delete attempt.error;
2341
+ }
2342
+ }
2343
+ runResponse(plugin, started) {
2344
+ return {
2345
+ ok: true,
2346
+ status: "running",
2347
+ pluginId: plugin.pluginId,
2348
+ packageId: started.packageId,
2349
+ pluginRunId: started.pluginRunId,
2350
+ waitingFor: started.waitingFor,
2351
+ currentPackageId: started.packageId,
2352
+ mode: plugin.latestRun?.pluginRunId === started.pluginRunId ? plugin.latestRun.mode : "run"
2353
+ };
2354
+ }
2355
+ announceResolved(requestId, resolution, override) {
2356
+ const outcome = override ?? (resolution.ok ? "approved" : resolution.reason === "rejected" ? "rejected" : "failed");
2357
+ this.ctx.emit("cordis/request-run-resolved", {
2358
+ requestId,
2359
+ outcome
2360
+ });
2361
+ }
2362
+ steerRunOutcome(pending, settled) {
2363
+ const agent = this.rootCtx.get("agents")?.get(pending.agentId);
2364
+ if (agent === void 0) return;
2365
+ const plugin = this.registry.get(pending.pluginId);
2366
+ const identity = `${pending.pluginId}/${pending.packageId} (${pending.pluginRunId})`;
2367
+ let text;
2368
+ if (settled.ok) text = `Cordis ${pending.mode} ${identity} completed successfully. currentPackageId is ${settled.currentPackageId ?? pending.packageId}. Continue using the running Plugin.`;
2369
+ else if (settled.reason === "rejected") text = `The user rejected Cordis ${pending.mode} ${identity}. Do not request the same activation again unless the user asks.`;
2370
+ else {
2371
+ const returnedStatus = pending.requiresApproval ? "awaiting-approval" : "starting";
2372
+ text = `Cordis ${pending.mode} ${identity} failed after cordis_run returned ${returnedStatus}: ${settled.reason}\n${formatErrorDetails(settled)}\ncurrentPackageId: ${plugin?.currentPackageId ?? "none"}\nnextPackageId: ${plugin?.nextPackageId ?? pending.packageId}\nInspect the failed Package, correct it on the same Plugin when needed, and retry the activation autonomously.`;
2373
+ }
2374
+ agent.steer(createUserMessage({
2375
+ content: [{
2376
+ type: "text",
2377
+ text
2378
+ }],
2379
+ source: {
2380
+ kind: "plugin",
2381
+ plugin: "cordis-host-runner"
2382
+ }
2383
+ }));
2384
+ }
2385
+ steerRenderFailure(agent, plugin, definition, pluginRunId, failure) {
2386
+ agent.steer(createUserMessage({
2387
+ content: [{
2388
+ type: "text",
2389
+ text: `Cordis Client UI ${plugin.pluginId}/${definition.packageId} (${pluginRunId}) failed while rendering Slot "${failure.slot}" after activation.\n${formatErrorDetails(failure)}\nentryAbdicated: ${failure.abdicated}\nInspect the failed Package, fix the Client code by defining a new Package on the same Plugin, and activate that Package autonomously with cordis_run mode:"update".`
2390
+ }],
2391
+ source: {
2392
+ kind: "plugin",
2393
+ plugin: "cordis-host-runner"
2394
+ }
2395
+ }));
2396
+ }
2397
+ steerHostHandlerFailure(plugin, run, method, failure) {
2398
+ const reportKey = `Host\u0000handler\u0000${method}\u0000${failure.message}`;
2399
+ if (!this.claimRuntimeFailure(plugin, run, reportKey)) return;
2400
+ const agent = this.rootCtx.get("agents")?.get(plugin.sessionId);
2401
+ if (agent === void 0) return;
2402
+ agent.steer(createUserMessage({
2403
+ content: [{
2404
+ type: "text",
2405
+ text: `Cordis Host handler ${plugin.pluginId}/${run.packageId} (${run.pluginRunId}) failed when the Client called host.call(${JSON.stringify(method)}).\n${formatErrorDetails(failure)}\nThe Plugin remains running. Inspect this Package, correct the Host code on the same Plugin, and activate the new Package autonomously with cordis_run mode:"update". If the handler needs a Service, either declare that Service in the returned Plugin inject list or read it with ctx.get(name) and handle undefined.`
2406
+ }],
2407
+ source: {
2408
+ kind: "plugin",
2409
+ plugin: "cordis-host-runner"
2410
+ }
2411
+ }));
2412
+ }
2413
+ steerGuardFailure(plugin, run, platform, failure) {
2414
+ const reportKey = `${platform}\u0000guard\u0000${failure.message}`;
2415
+ if (!this.claimRuntimeFailure(plugin, run, reportKey)) return;
2416
+ const agent = this.rootCtx.get("agents")?.get(plugin.sessionId);
2417
+ if (agent === void 0) return;
2418
+ agent.steer(createUserMessage({
2419
+ content: [{
2420
+ type: "text",
2421
+ text: `Cordis ${platform} guard rejected runtime code in ${plugin.pluginId}/${run.packageId} (${run.pluginRunId}) after activation.\n${formatErrorDetails(failure)}\nThe Plugin remains running. Inspect this Package, define a corrected Package on the same Plugin, and activate it autonomously with cordis_run mode:"update".`
2422
+ }],
2423
+ source: {
2424
+ kind: "plugin",
2425
+ plugin: "cordis-host-runner"
2426
+ }
2427
+ }));
2428
+ }
2429
+ claimRuntimeFailure(plugin, run, key) {
2430
+ const attempt = plugin.latestRun;
2431
+ if (plugin.run !== run || attempt?.pluginRunId !== run.pluginRunId || attempt.status !== "running" && attempt.status !== "waiting") return false;
2432
+ if (run.reportedRuntimeErrors.has(key)) return false;
2433
+ run.reportedRuntimeErrors.add(key);
2434
+ return true;
2435
+ }
2436
+ injectUserRunOutcome(agent, pluginId, settled) {
2437
+ const plugin = this.owned(agent, pluginId);
2438
+ let text;
2439
+ if (settled.ok) text = `The user manually ran Cordis Plugin ${pluginId}, Package ${settled.packageId}, as ${settled.pluginRunId}. The activation succeeded; currentPackageId is ${settled.currentPackageId}.`;
2440
+ else {
2441
+ const attempt = plugin?.latestRun;
2442
+ text = `The user manually ran Cordis Plugin ${pluginId}${attempt === void 0 ? "" : `, Package ${attempt.packageId}, as ${attempt.pluginRunId}`}, but it failed: ${settled.reason}\n${formatErrorDetails(settled)}\ncurrentPackageId: ${plugin?.currentPackageId ?? "none"}\nnextPackageId: ${plugin?.nextPackageId ?? "none"}`;
2443
+ }
2444
+ this.injectUserContext(agent, text);
2445
+ }
2446
+ injectUserContext(agent, text) {
2447
+ if (this.rootCtx.get("agents")?.get(agent.id) !== agent) return;
2448
+ agent.inject(createUserMessage({
2449
+ content: [{
2450
+ type: "text",
2451
+ text
2452
+ }],
2453
+ source: {
2454
+ kind: "plugin",
2455
+ plugin: "cordis-host-runner"
2456
+ }
2457
+ }));
2458
+ }
2459
+ cancelPending(pluginId, message) {
2460
+ const requestId = this.registry.pendingRequestFor(pluginId);
2461
+ if (requestId === void 0) return;
2462
+ const pending = this.registry.claimRequest(requestId);
2463
+ if (pending === void 0) return;
2464
+ const plugin = this.registry.get(pluginId);
2465
+ if (plugin?.latestRun?.pluginRunId === pending.pluginRunId) {
2466
+ plugin.latestRun.status = "cancelled";
2467
+ plugin.latestRun.error = this.diagnostic(plugin, plugin.latestRun, "approval", message);
2468
+ delete plugin.latestRun.approvalRequestId;
2469
+ delete plugin.latestRun.requiresApproval;
2470
+ }
2471
+ this.announceResolved(requestId, {
2472
+ ok: false,
2473
+ reason: "rejected"
2474
+ }, "cancelled");
2475
+ }
2476
+ createAttempt(plan) {
2477
+ return {
2478
+ pluginRunId: CordisDynamicPluginRunId(this.registry.mintPluginRunId()),
2479
+ packageId: plan.definition.packageId,
2480
+ mode: plan.mode,
2481
+ status: "starting-host",
2482
+ host: {
2483
+ status: plan.definition.hostCode === void 0 ? "absent" : "pending",
2484
+ waitingFor: []
2485
+ },
2486
+ client: {
2487
+ status: plan.definition.clientCode === void 0 ? "absent" : "pending",
2488
+ waitingFor: []
2489
+ }
2490
+ };
2491
+ }
2492
+ failAttempt(plugin, attempt, phase, failure) {
2493
+ attempt.status = "failed";
2494
+ attempt.error = this.diagnostic(plugin, attempt, phase, failure);
2495
+ if (phase.startsWith("host")) attempt.host = {
2496
+ status: "failed",
2497
+ waitingFor: [],
2498
+ error: failure.message
2499
+ };
2500
+ else attempt.client = {
2501
+ status: "failed",
2502
+ waitingFor: [],
2503
+ error: failure.message
2504
+ };
2505
+ }
2506
+ diagnostic(plugin, attempt, phase, failure) {
2507
+ return {
2508
+ phase,
2509
+ ...typeof failure === "string" ? { message: failure } : failure,
2510
+ pluginId: plugin.pluginId,
2511
+ packageId: attempt.packageId,
2512
+ pluginRunId: attempt.pluginRunId
2513
+ };
2514
+ }
2515
+ async retract(plugin) {
2516
+ const run = plugin.run;
2517
+ if (run === void 0) return;
2518
+ delete plugin.run;
2519
+ for (const dispose of run.handlerDisposers.splice(0)) dispose();
2520
+ if (run.fiber !== void 0) await run.fiber.dispose();
2521
+ this.ctx.emit("cordis/dynamic-retract", {
2522
+ pluginId: plugin.pluginId,
2523
+ packageId: run.packageId,
2524
+ pluginRunId: run.pluginRunId
2525
+ });
2526
+ }
2527
+ owned(agent, pluginId) {
2528
+ const plugin = this.registry.get(pluginId);
2529
+ return plugin?.sessionId === agent.id ? plugin : void 0;
2530
+ }
2531
+ requireGroup() {
2532
+ this.group ??= this.rootCtx.plugin({
2533
+ name: "cordis-dynamic",
2534
+ apply: () => {}
2535
+ });
2536
+ return this.group;
2537
+ }
2538
+ };
2539
+ })();
2540
+ function missingFor(ctx, run) {
2541
+ return run.fiber === void 0 ? [] : missingServices(ctx, run.fiber);
2542
+ }
2543
+ function missingPluginMessage(id) {
2544
+ return `no dynamic plugin "${id}" in this process — it may have been removed or lost on DSH restart`;
2545
+ }
2546
+ function errorDetails(error) {
2547
+ if (typeof error !== "object" || error === null) return { message: String(error) };
2548
+ const message = "message" in error && typeof error.message === "string" ? error.message : Object.prototype.toString.call(error);
2549
+ const stack = "stack" in error && typeof error.stack === "string" ? error.stack : void 0;
2550
+ return {
2551
+ message,
2552
+ ...stack === void 0 ? {} : { stack }
2553
+ };
2554
+ }
2555
+ function formatErrorDetails(failure) {
2556
+ return `message: ${failure.message}` + (failure.stack === void 0 ? "" : `\nstack:\n${failure.stack}`);
2557
+ }
2558
+ function cloneAttempt(attempt) {
2559
+ return {
2560
+ ...attempt,
2561
+ host: {
2562
+ ...attempt.host,
2563
+ waitingFor: [...attempt.host.waitingFor]
2564
+ },
2565
+ client: {
2566
+ ...attempt.client,
2567
+ waitingFor: [...attempt.client.waitingFor]
2568
+ },
2569
+ ...attempt.error === void 0 ? {} : { error: { ...attempt.error } }
2570
+ };
2571
+ }
2572
+ //#endregion
2573
+ export { ApprovalRequestId, CordisDynamicPackageId, CordisDynamicPluginId, CordisDynamicPluginRunId, CordisInspectRegistryService, DynamicCordisRunnerService, DynamicCordisRunnerService as default, HOST_BUILTIN_INSPECTION };