@qilin-agent/kylin-host-runner 0.0.0-stage → 3.1.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,2619 @@
1
+ import z from "@qilin-agent/schemastery";
2
+ import { createUserMessage } from "@qilin-agent/llm";
3
+ import { Remote, TypertRemoteService } from "@qilin-agent/typert-protocol";
4
+ import { Context, Service } from "@qilin-agent/kylin";
5
+ import { scopeOf } from "@qilin-agent/scope";
6
+ import { assertSupportedJsonSchema, defineTool, validateJsonSchemaValue } from "@qilin-agent/tools";
7
+ import { snapshotJsonValue } from "@qilin-agent/util-values";
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 @qilin-agent/kylin-host-runner/guard
23
+ */
24
+ const DYNAMIC_TOOL = Symbol("kylin-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
+ clientQueryTimeoutMs;
718
+ providers = /* @__PURE__ */ new Map();
719
+ pending = /* @__PURE__ */ new Map();
720
+ clientManifest;
721
+ nextRequest = 1;
722
+ /**
723
+ * Register the process-global Host registry.
724
+ * @param ctx - owning Host context.
725
+ * @param clientQueryTimeoutMs - maximum wait for a valid Client response, in milliseconds.
726
+ */
727
+ constructor(ctx, clientQueryTimeoutMs) {
728
+ super(ctx, "cordisInspect");
729
+ this.clientQueryTimeoutMs = clientQueryTimeoutMs;
730
+ ctx.effect(() => () => {
731
+ for (const pending of this.pending.values()) pending.settle({
732
+ ok: false,
733
+ reason: "cancelled",
734
+ message: "Client inspect registry was disposed"
735
+ });
736
+ }, "cordis-inspect: pending queries");
737
+ }
738
+ /**
739
+ * Register one Host provider.
740
+ * @param registration - manifest and local query handler.
741
+ * @returns idempotent disposer.
742
+ */
743
+ register(registration) {
744
+ const manifest = validateManifest(registration.manifest);
745
+ if (this.providers.has(manifest.id)) throw new Error(`Host Cordis inspect provider "${manifest.id}" is already registered`);
746
+ const stored = {
747
+ ...registration,
748
+ manifest
749
+ };
750
+ this.providers.set(manifest.id, stored);
751
+ return () => {
752
+ if (this.providers.get(manifest.id) === stored) this.providers.delete(manifest.id);
753
+ };
754
+ }
755
+ /**
756
+ * Replace the mirrored Client provider directory.
757
+ * @param providers - complete Client manifest snapshot.
758
+ */
759
+ syncClientManifest(providers) {
760
+ const ids = /* @__PURE__ */ new Set();
761
+ const validated = providers.map((provider) => {
762
+ const manifest = validateManifest(provider);
763
+ if (ids.has(manifest.id)) throw new Error(`Client Cordis inspect manifest repeats provider "${manifest.id}"`);
764
+ ids.add(manifest.id);
765
+ return manifest;
766
+ });
767
+ this.clientManifest = Object.freeze(validated);
768
+ }
769
+ /**
770
+ * Return the complete known Host and Client provider directory.
771
+ * @returns Host providers followed by the Client providers.
772
+ */
773
+ list() {
774
+ return [...[...this.providers.values()].map((provider) => view("host", provider.manifest)), ...(this.clientManifest ?? []).map((provider) => view("client", provider))];
775
+ }
776
+ /**
777
+ * Execute one provider query on its owning platform.
778
+ * @param platform - Host or Client runtime.
779
+ * @param providerId - provider selected from {@link list}.
780
+ * @param methodName - declared method name.
781
+ * @param input - optional lossless JSON input.
782
+ * @param agent - requesting Agent and scope.
783
+ * @param signal - tool-call cancellation.
784
+ * @returns provider JSON data; Client queries fail fast when Gateway has no live Client
785
+ * and retain only the first observed failure diagnostic for timeout reporting.
786
+ */
787
+ async query(platform, providerId, methodName, input, agent, signal) {
788
+ if (platform === "host") {
789
+ const registration = this.providers.get(providerId);
790
+ if (registration === void 0) throw new Error(`Host Cordis inspect provider "${providerId}" is not registered`);
791
+ const method = findMethod(registration.manifest, methodName);
792
+ validateInput("Host", providerId, method, input);
793
+ signal.throwIfAborted();
794
+ const data = await registration.query(methodName, input, {
795
+ agent,
796
+ signal
797
+ });
798
+ signal.throwIfAborted();
799
+ return validateOutput("Host", providerId, method, data);
800
+ }
801
+ return await this.queryClient(providerId, methodName, input, agent, signal);
802
+ }
803
+ /**
804
+ * Accept the first valid Client response for a pending query.
805
+ * @param agent - Agent whose Session owns the query.
806
+ * @param requestId - Pending Client query identity.
807
+ * @param resolution - Client provider result or failure.
808
+ * @returns acknowledgement with accepted true only for a success that settles the query; only the first failure diagnostic is retained.
809
+ */
810
+ resolveClientQuery(agent, requestId, resolution) {
811
+ const pending = this.pending.get(requestId);
812
+ if (pending === void 0 || pending.request.agentId !== agent.id) return { accepted: false };
813
+ if (!resolution.ok) {
814
+ pending.failure ??= `${resolution.reason}: ${resolution.message}`;
815
+ return { accepted: false };
816
+ }
817
+ try {
818
+ resolution = {
819
+ ok: true,
820
+ data: validateOutput("Client", pending.request.provider, pending.method, resolution.data)
821
+ };
822
+ } catch (error) {
823
+ pending.failure ??= error instanceof Error ? error.message : String(error);
824
+ return { accepted: false };
825
+ }
826
+ pending.settle(resolution);
827
+ return { accepted: true };
828
+ }
829
+ async queryClient(providerId, methodName, input, agent, signal) {
830
+ const provider = this.clientManifest?.find((candidate) => candidate.id === providerId);
831
+ if (provider === void 0) throw new Error(`Client Cordis inspect provider "${providerId}" is not registered`);
832
+ const method = findMethod(provider, methodName);
833
+ validateInput("Client", providerId, method, input);
834
+ signal.throwIfAborted();
835
+ const gateway = this.ctx.get("typertGateway");
836
+ if (gateway !== void 0 && !gateway.hasLiveClient()) throw new Error(`Client inspect query ${providerId}.${methodName} has no connected Harness page. Open or reconnect the Harness page, then retry.`);
837
+ const requestId = `inspect-${this.nextRequest++}`;
838
+ const request = {
839
+ requestId,
840
+ agentId: agent.id,
841
+ provider: providerId,
842
+ method: methodName,
843
+ ...input === void 0 ? {} : { input }
844
+ };
845
+ const result = new Promise((resolve) => {
846
+ this.pending.set(requestId, {
847
+ request,
848
+ method,
849
+ settle: (resolution) => {
850
+ this.pending.delete(requestId);
851
+ resolve(resolution);
852
+ try {
853
+ this.ctx.emit("cordis/inspect-query-resolved", { requestId });
854
+ } catch (error) {
855
+ console.error("[kylin-host-runner] notifying Client inspect completion failed:", error);
856
+ }
857
+ }
858
+ });
859
+ });
860
+ const onAbort = () => {
861
+ this.pending.get(requestId)?.settle({
862
+ ok: false,
863
+ reason: "cancelled",
864
+ message: `Client inspect query ${providerId}.${methodName} was cancelled`
865
+ });
866
+ };
867
+ const timer = setTimeout(() => {
868
+ const pending = this.pending.get(requestId);
869
+ if (pending === void 0) return;
870
+ const detail = pending.failure === void 0 ? "Open or reconnect the Harness page, then retry." : `Client failure: ${pending.failure}`;
871
+ pending.settle({
872
+ ok: false,
873
+ reason: "provider-error",
874
+ message: `Client inspect query ${providerId}.${methodName} timed out after ${this.clientQueryTimeoutMs}ms. ${detail}`
875
+ });
876
+ }, this.clientQueryTimeoutMs);
877
+ signal.addEventListener("abort", onAbort, { once: true });
878
+ try {
879
+ if (signal.aborted) onAbort();
880
+ else this.ctx.emit("cordis/inspect-query", request);
881
+ const resolution = await result;
882
+ if (!resolution.ok) throw new Error(`${providerId}.${methodName}: ${resolution.message}`);
883
+ return resolution.data;
884
+ } finally {
885
+ clearTimeout(timer);
886
+ signal.removeEventListener("abort", onAbort);
887
+ this.pending.delete(requestId);
888
+ }
889
+ }
890
+ };
891
+ function view(platform, manifest) {
892
+ return {
893
+ platform,
894
+ ...manifest,
895
+ methods: [...manifest.methods]
896
+ };
897
+ }
898
+ function validateManifest(manifest) {
899
+ if (manifest.id.trim() === "") throw new Error("Kylin inspect provider id must not be empty");
900
+ if (manifest.description.trim() === "") throw new Error(`Cordis inspect provider "${manifest.id}" needs a description`);
901
+ const names = /* @__PURE__ */ new Set();
902
+ const methods = manifest.methods.map((method) => {
903
+ if (method.name.trim() === "") throw new Error(`Cordis inspect provider "${manifest.id}" has an empty method name`);
904
+ if (names.has(method.name)) throw new Error(`Cordis inspect provider "${manifest.id}" repeats method "${method.name}"`);
905
+ if (method.description.trim() === "") throw new Error(`Cordis inspect method ${manifest.id}.${method.name} needs a description`);
906
+ assertSupportedJsonSchema(method.inputSchema);
907
+ assertSupportedJsonSchema(method.outputSchema);
908
+ names.add(method.name);
909
+ return Object.freeze({ ...method });
910
+ });
911
+ return Object.freeze({
912
+ ...manifest,
913
+ methods: Object.freeze(methods)
914
+ });
915
+ }
916
+ function findMethod(manifest, name) {
917
+ const method = manifest.methods.find((candidate) => candidate.name === name);
918
+ if (method === void 0) throw new Error(`Cordis inspect provider "${manifest.id}" has no method "${name}"`);
919
+ return method;
920
+ }
921
+ function validateInput(platform, provider, method, input) {
922
+ const violations = validateJsonSchemaValue(method.inputSchema, input ?? {}, "input");
923
+ if (violations.length > 0) throw new Error(`${platform} Cordis inspect ${provider}.${method.name} rejected input: ${violations.join("; ")}`);
924
+ }
925
+ function validateOutput(platform, provider, method, data) {
926
+ const snapshot = snapshotJsonValue(data);
927
+ if (snapshot === void 0) throw new Error(`${platform} Cordis inspect ${provider}.${method.name} returned a non-JSON value`);
928
+ const violations = validateJsonSchemaValue(method.outputSchema, snapshot, "output");
929
+ if (violations.length > 0) throw new Error(`${platform} Cordis inspect ${provider}.${method.name} returned invalid output: ${violations.join("; ")}`);
930
+ return snapshot;
931
+ }
932
+ //#endregion
933
+ //#region lib/types/lifecycle.js
934
+ /**
935
+ * Host-half fiber lifecycle over the `cordis-dynamic` group: settle a
936
+ * sandbox-produced plugin as a child fiber (never leaving a failed fiber
937
+ * mounted), and report the services a settled-but-pending fiber still waits
938
+ * for. Stopping needs no helper — a host half unwinds through an ordinary
939
+ * awaited `fiber.dispose()`, because everything the plugin registered is an
940
+ * effect on its fiber.
941
+ * @module @qilin-agent/kylin-host-runner/lifecycle
942
+ */
943
+ /**
944
+ * Await the group, start and settle one guarded child, and dispose it before rethrowing any
945
+ * startup failure so a failed run never lingers. A valid unresolved inject may remain pending.
946
+ * @param group - the `cordis-dynamic` group fiber every host half hangs under.
947
+ * @param plugin - the plugin the sandbox returned; wrapped with the registration guard before starting.
948
+ * @param reportGuardFailure - reports post-activation Host guard rejections to the owning Agent.
949
+ * @returns the settled child fiber (possibly pending on unsatisfied `inject`).
950
+ */
951
+ async function startHostHalf(group, plugin, reportGuardFailure) {
952
+ await group.await();
953
+ const fiber = group.ctx.plugin(guardedPlugin(plugin, reportGuardFailure));
954
+ try {
955
+ await fiber.await();
956
+ } catch (error) {
957
+ await fiber.dispose();
958
+ const message = error instanceof Error ? error.message : String(error);
959
+ if (message.includes("already registered")) throw new Error(`${message} — to REPLACE something an earlier dynamic package registered, first stop that package through its runner or the Cordis panel before running the new version.`);
960
+ throw error instanceof Error ? error : new Error(message);
961
+ }
962
+ return fiber;
963
+ }
964
+ /**
965
+ * The services a fiber declared in `inject` that do not exist yet — a settled
966
+ * fiber that is not active is waiting on exactly these (legal cordis
967
+ * semantics: it activates when the service appears).
968
+ * @param ctx - the context to resolve service existence against.
969
+ * @param fiber - the host-half fiber whose `inject` declarations are checked.
970
+ * @returns the missing service names, in declaration order.
971
+ */
972
+ function missingServices(ctx, fiber) {
973
+ return Object.keys(fiber.inject).filter((service) => ctx.get(service) === void 0);
974
+ }
975
+ //#endregion
976
+ //#region lib/types/registry.js
977
+ /**
978
+ * Process-local dynamic Plugin registry and its opaque identity mints.
979
+ * @module @qilin-agent/kylin-host-runner/registry
980
+ */
981
+ /** Registry, identity mints, and pending approval index. */
982
+ var DynamicCordisRegistry = class {
983
+ plugins = /* @__PURE__ */ new Map();
984
+ pendingRequests = /* @__PURE__ */ new Map();
985
+ nextPlugin = 1;
986
+ nextPackage = 1;
987
+ nextRun = 1;
988
+ nextApproval = 1;
989
+ /**
990
+ * Mint a semantic plugin ID without reusing a prior suffix.
991
+ * @param prefix - validated lowercase semantic prefix proposed by the model.
992
+ * @returns a process-unique Plugin ID.
993
+ */
994
+ mintPluginId(prefix) {
995
+ let id;
996
+ do
997
+ id = `${prefix}-${this.nextPlugin++}`;
998
+ while (this.plugins.has(id));
999
+ return id;
1000
+ }
1001
+ /**
1002
+ * Mint an immutable package ID.
1003
+ * @returns a process-unique Package ID.
1004
+ */
1005
+ mintPackageId() {
1006
+ return `pkg-${this.nextPackage++}`;
1007
+ }
1008
+ /**
1009
+ * Mint an activation ID.
1010
+ * @returns a process-unique Plugin Run ID.
1011
+ */
1012
+ mintPluginRunId() {
1013
+ return `run-${this.nextRun++}`;
1014
+ }
1015
+ /**
1016
+ * Mint an approval ID.
1017
+ * @returns a process-unique approval request ID.
1018
+ */
1019
+ mintApprovalRequestId() {
1020
+ return `approval-${this.nextApproval++}`;
1021
+ }
1022
+ /**
1023
+ * Add one stable plugin.
1024
+ * @param plugin - Plugin record to retain under its stable ID.
1025
+ */
1026
+ add(plugin) {
1027
+ this.plugins.set(plugin.pluginId, plugin);
1028
+ }
1029
+ /**
1030
+ * Read one plugin.
1031
+ * @param id - stable Plugin ID.
1032
+ * @returns the Plugin record, or `undefined` when absent.
1033
+ */
1034
+ get(id) {
1035
+ return this.plugins.get(id);
1036
+ }
1037
+ /**
1038
+ * Delete one plugin and all package versions.
1039
+ * @param id - stable Plugin ID to remove.
1040
+ * @returns whether a Plugin record was removed.
1041
+ */
1042
+ delete(id) {
1043
+ return this.plugins.delete(id);
1044
+ }
1045
+ /**
1046
+ * Read all plugins in creation order.
1047
+ * @returns a snapshot of every Plugin record.
1048
+ */
1049
+ all() {
1050
+ return [...this.plugins.values()];
1051
+ }
1052
+ /**
1053
+ * Read one session's plugins in creation order.
1054
+ * @param sessionId - owning session to filter by.
1055
+ * @returns a snapshot of matching Plugin records.
1056
+ */
1057
+ ofSession(sessionId) {
1058
+ return this.all().filter((plugin) => plugin.sessionId === sessionId);
1059
+ }
1060
+ /**
1061
+ * Publish one pending approval.
1062
+ * @param id - approval request ID.
1063
+ * @param pending - resolver and Plugin metadata retained until settlement.
1064
+ */
1065
+ armRequest(id, pending) {
1066
+ this.pendingRequests.set(id, pending);
1067
+ }
1068
+ /**
1069
+ * Read one pending approval without claiming it.
1070
+ * @param id - approval request ID.
1071
+ * @returns the pending request, or `undefined` when absent.
1072
+ */
1073
+ peekRequest(id) {
1074
+ return this.pendingRequests.get(id);
1075
+ }
1076
+ /**
1077
+ * Claim one pending approval; first answer wins.
1078
+ * @param id - approval request ID.
1079
+ * @returns the claimed request, or `undefined` when already settled.
1080
+ */
1081
+ claimRequest(id) {
1082
+ const pending = this.pendingRequests.get(id);
1083
+ if (pending !== void 0) this.pendingRequests.delete(id);
1084
+ return pending;
1085
+ }
1086
+ /**
1087
+ * Cancel one pending approval.
1088
+ * @param id - approval request ID to remove.
1089
+ */
1090
+ disarmRequest(id) {
1091
+ this.pendingRequests.delete(id);
1092
+ }
1093
+ /**
1094
+ * Find a pending approval for one Plugin.
1095
+ * @param pluginId - stable Plugin ID.
1096
+ * @returns its approval request ID, or `undefined` when none is pending.
1097
+ */
1098
+ pendingRequestFor(pluginId) {
1099
+ for (const [requestId, request] of this.pendingRequests) if (request.pluginId === pluginId) return requestId;
1100
+ }
1101
+ };
1102
+ //#endregion
1103
+ //#region lib/types/sandbox.js
1104
+ /**
1105
+ * The `node:vm` sandbox a dynamic package's HOST half evaluates in: a fresh realm whose globals
1106
+ * are a tagged write-through console, the `harness` registration helpers, the encoding primitives
1107
+ * a bare vm context lacks, and callable traps over the Node APIs the sandbox deliberately
1108
+ * withholds. Traps steer filesystem, network, process, and timer work to `ctx.fs`, `ctx.web`,
1109
+ * `ctx.bash`, and Cordis timers. This keeps cooperative packages inspectable and disposable but
1110
+ * is not containment: host-realm helper functions remain an escape route.
1111
+ *
1112
+ * The browser half never reaches this module — it is evaluated by the client-side runner in a
1113
+ * closure, with its own facade.
1114
+ * @module @qilin-agent/kylin-host-runner/sandbox
1115
+ */
1116
+ /** Exact Host closure symbols exposed by the sandbox and guarded Context. */
1117
+ const HOST_BUILTIN_INSPECTION = [
1118
+ {
1119
+ name: "ctx",
1120
+ description: "Restricted Cordis Context. Prefer ctx.get(name) with an undefined check; use inject for hard dependencies.",
1121
+ signatures: [
1122
+ "ctx.get(name: string): unknown | undefined",
1123
+ "ctx.on(name: string, listener: Function): () => void",
1124
+ "ctx.provide(name: string, value: unknown): () => void",
1125
+ "ctx.effect(callback: Function, label?: string): () => void"
1126
+ ]
1127
+ },
1128
+ {
1129
+ name: "harness",
1130
+ description: "Host helpers for Package-private Client RPC and model-visible dynamic Tools.",
1131
+ signatures: [
1132
+ "harness.handle(method: string, handler: (args: JsonValue) => JsonValue | Promise<JsonValue>): () => void",
1133
+ "harness.defineTool(definition: ToolDefinition): ToolDefinition",
1134
+ "harness.registerTool(ctx: Context, tool: ToolDefinition): () => void"
1135
+ ]
1136
+ },
1137
+ {
1138
+ name: "console",
1139
+ description: "Package-tagged Host logging.",
1140
+ signatures: ["console.log(...values): void", "console.error(...values): void"]
1141
+ },
1142
+ {
1143
+ name: "btoa",
1144
+ description: "Encode UTF-8 text as base64.",
1145
+ signatures: ["btoa(value: string): string"]
1146
+ },
1147
+ {
1148
+ name: "atob",
1149
+ description: "Decode base64 as UTF-8 text.",
1150
+ signatures: ["atob(value: string): string"]
1151
+ },
1152
+ {
1153
+ name: "TextEncoder",
1154
+ description: "Standard UTF-8 encoder constructor.",
1155
+ signatures: ["new TextEncoder()"]
1156
+ },
1157
+ {
1158
+ name: "TextDecoder",
1159
+ description: "Standard text decoder constructor.",
1160
+ signatures: ["new TextDecoder(label?: string)"]
1161
+ }
1162
+ ];
1163
+ /**
1164
+ * A write-through console for one package, tagging every line with the package
1165
+ * id. Write-through (host stdout/stderr), NOT buffered into the tool result:
1166
+ * a registered listener fires long after the run call returned, and its output
1167
+ * must land somewhere the user can see — for a terminal entry point, the host terminal.
1168
+ */
1169
+ function taggedConsole(id) {
1170
+ const tag = `[cordis:${id}]`;
1171
+ const log = (...args) => {
1172
+ console.log(tag, ...args);
1173
+ };
1174
+ const error = (...args) => {
1175
+ console.error(tag, ...args);
1176
+ };
1177
+ return {
1178
+ log,
1179
+ info: log,
1180
+ warn: log,
1181
+ debug: log,
1182
+ error
1183
+ };
1184
+ }
1185
+ /**
1186
+ * Patch only VM constructors so `instanceof` accepts both VM values and host values passed as
1187
+ * arguments, events, or service results; host intrinsics remain untouched.
1188
+ */
1189
+ const DUAL_REALM_INSTANCEOF_PRELUDE = `
1190
+ (hostIntrinsics) => {
1191
+ 'use strict'
1192
+ const ordinary = Function.prototype[Symbol.hasInstance]
1193
+ for (const name of Object.keys(hostIntrinsics)) {
1194
+ const VmCtor = globalThis[name]
1195
+ const HostCtor = hostIntrinsics[name]
1196
+ if (typeof VmCtor !== 'function' || typeof HostCtor !== 'function') continue
1197
+ Object.defineProperty(VmCtor, Symbol.hasInstance, {
1198
+ value: (instance) => ordinary.call(VmCtor, instance) || ordinary.call(HostCtor, instance),
1199
+ configurable: true,
1200
+ })
1201
+ }
1202
+ }
1203
+ `;
1204
+ /** Run {@link DUAL_REALM_INSTANCEOF_PRELUDE} in a freshly created sandbox, handing it the host intrinsics to pair up. */
1205
+ function patchDualRealmInstanceof(sandbox) {
1206
+ runInContext(DUAL_REALM_INSTANCEOF_PRELUDE, sandbox)({
1207
+ Object,
1208
+ Array,
1209
+ Function,
1210
+ Error,
1211
+ TypeError,
1212
+ RangeError,
1213
+ SyntaxError,
1214
+ Promise,
1215
+ RegExp,
1216
+ Date,
1217
+ Map,
1218
+ Set
1219
+ });
1220
+ }
1221
+ 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.";
1222
+ /**
1223
+ * The callable Node APIs the sandbox deliberately disables, each mapped to the
1224
+ * cordis alternative its trap error names. Only function-valued globals are
1225
+ * trapped; a data-valued global such as `process` stays `undefined`, because a
1226
+ * throwing accessor would detonate the common `typeof process` feature probe
1227
+ * at resolution time.
1228
+ */
1229
+ const NODE_API_REDIRECTS = {
1230
+ 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.",
1231
+ setTimeout: TIMER_REDIRECT,
1232
+ setInterval: TIMER_REDIRECT,
1233
+ setImmediate: TIMER_REDIRECT,
1234
+ clearTimeout: TIMER_REDIRECT,
1235
+ clearInterval: TIMER_REDIRECT,
1236
+ 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)."
1237
+ };
1238
+ /** Build the trap functions for {@link NODE_API_REDIRECTS}: calling one throws the redirect. */
1239
+ function nodeApiTraps() {
1240
+ const traps = {};
1241
+ for (const [name, redirect] of Object.entries(NODE_API_REDIRECTS)) traps[name] = () => {
1242
+ throw new Error(`${name} is not available in the dynamic package sandbox — ${redirect}`);
1243
+ };
1244
+ return traps;
1245
+ }
1246
+ /**
1247
+ * Build the vm context one host half evaluates in: the tagged console, the
1248
+ * `harness` registration helpers, the encoding primitives, the Node-API traps,
1249
+ * and the dual-realm `instanceof` patch, already `createContext`-ed.
1250
+ * @param id - the package id (`dyn-<n>`), used as the console tag and filename stem.
1251
+ * @param harnessExtras - per-package `harness` verbs beyond the registration pair (`handle`).
1252
+ * @returns the contextified sandbox object to pass to {@link evaluateHostCode}.
1253
+ */
1254
+ function createSandbox(id, harnessExtras = {}) {
1255
+ const sandbox = {
1256
+ ...nodeApiTraps(),
1257
+ console: taggedConsole(id),
1258
+ harness: {
1259
+ defineTool: sandboxDefineTool,
1260
+ registerTool: sandboxRegisterTool,
1261
+ ...harnessExtras
1262
+ },
1263
+ btoa: (s) => Buffer.from(s, "utf-8").toString("base64"),
1264
+ atob: (s) => Buffer.from(s, "base64").toString("utf-8"),
1265
+ TextEncoder,
1266
+ TextDecoder
1267
+ };
1268
+ createContext(sandbox);
1269
+ patchDualRealmInstanceof(sandbox);
1270
+ return sandbox;
1271
+ }
1272
+ /**
1273
+ * Cross-realm SyntaxError detection: a compile failure inside `runInContext`
1274
+ * constructs its error in the SANDBOX realm, so a host `instanceof
1275
+ * SyntaxError` is silently false — the `name` property is the realm-safe tag.
1276
+ */
1277
+ function isSyntaxError(error) {
1278
+ return typeof error === "object" && error !== null && error.name === "SyntaxError";
1279
+ }
1280
+ /**
1281
+ * The parse-failure context a vm `SyntaxError` carries: the vm prints the
1282
+ * offending source line and a caret before the message, which is exactly what
1283
+ * a model needs to self-correct — surface it instead of the bare message.
1284
+ * Falls back to `String(error)` when the stack carries no such prelude.
1285
+ * @param error - the `SyntaxError` (host- or sandbox-realm) thrown while compiling package code.
1286
+ * @returns the stack prefix up to and including the `SyntaxError: …` line.
1287
+ */
1288
+ function syntaxErrorContext(error) {
1289
+ const lines = (error.stack ?? "").split("\n");
1290
+ const messageIndex = lines.findIndex((line) => line.startsWith("SyntaxError"));
1291
+ if (messageIndex === -1) return String(error);
1292
+ return lines.slice(0, messageIndex + 1).join("\n");
1293
+ }
1294
+ /**
1295
+ * The teaching text one parse failure produces, shared by the define-time
1296
+ * precheck and the run-time evaluation so a model reads the same diagnosis
1297
+ * whichever verb caught it.
1298
+ * @param half - which half failed to parse, named as the define argument that carried it.
1299
+ * @param context - the {@link syntaxErrorContext} of the failure.
1300
+ * @returns the model-facing error message.
1301
+ */
1302
+ function parseErrorMessage(half, context) {
1303
+ const offendingLine = context.split("\n")[1] ?? "";
1304
+ if (/\bas\b/.test(offendingLine)) return `dynamic package \`${half}\` failed to parse:\n${context}\nThe sandbox runs plain JavaScript, not TypeScript. Remove type annotations:
1305
+ ✗ { type: 'text' as const, text: x }
1306
+ ✓ { type: 'text', text: x }`;
1307
+ 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 \`)\`.`;
1308
+ }
1309
+ /**
1310
+ * Parse one half's source without running it: the define-time precheck that
1311
+ * keeps unparseable code out of the registry, so a model fixes it and defines
1312
+ * again instead of discovering the failure at run time. `new Function` is the
1313
+ * gate — hosts without a real `node:vm` (the browser worker) still refuse
1314
+ * unparseable code — and `vm.Script` is only the best-effort prettifier: on a
1315
+ * Node host its failure carries the source-line-and-caret prelude the
1316
+ * teaching text builds on, and where the vm is a stub the message stays bare.
1317
+ * The two parsers' syntax faces differ at the margin (`new.target` parses in
1318
+ * a function body but not at the vm wrapper's top level), an accepted cost of
1319
+ * a vm-free gate; and under a page CSP without `'unsafe-eval'`, `new Function`
1320
+ * throws `EvalError`, which propagates unwrapped.
1321
+ * @param code - the model-written function body.
1322
+ * @param half - which define argument carried it, for the error text.
1323
+ * @throws when the body does not parse, with the offending line and a teaching hint.
1324
+ */
1325
+ function precheckCode(code, half) {
1326
+ const wrapped = `(async () => {\n${code}\n})()`;
1327
+ try {
1328
+ new Function(wrapped);
1329
+ } catch (error) {
1330
+ if (!isSyntaxError(error)) throw error;
1331
+ throw new Error(parseErrorMessage(half, prettyParseContext(wrapped, half, error)));
1332
+ }
1333
+ }
1334
+ /**
1335
+ * Best-effort vm recompile of a body `new Function` already refused, for the
1336
+ * source-line-and-caret prelude only.
1337
+ * @param wrapped - the wrapped source that failed to parse.
1338
+ * @param half - which define argument carried it, for the vm filename.
1339
+ * @param refusal - the gate's own `SyntaxError`, the fallback context source.
1340
+ * @returns the vm prelude when a real vm produced one, else the bare refusal.
1341
+ */
1342
+ function prettyParseContext(wrapped, half, refusal) {
1343
+ try {
1344
+ new Script(wrapped, { filename: `cordis-dyn-${half}.js` });
1345
+ } catch (vmError) {
1346
+ if (isSyntaxError(vmError)) return syntaxErrorContext(vmError);
1347
+ }
1348
+ return String(refusal);
1349
+ }
1350
+ /**
1351
+ * Evaluate a host half as the body of an async function inside the sandbox. `vmTimeoutMs` only
1352
+ * bounds the SYNCHRONOUS portion; an async body escapes it — acceptable under the module's
1353
+ * trust stance. Parse errors include the offending line and a TypeScript-removal or bracket-
1354
+ * balance hint.
1355
+ * @param sandbox - the contextified object from {@link createSandbox}.
1356
+ * @param code - the model-written function body; must `return` a plugin.
1357
+ * @param id - the package id, used as the vm filename (`cordis-dyn-<id>.js`).
1358
+ * @param vmTimeoutMs - the synchronous evaluation bound in milliseconds.
1359
+ * @returns whatever the code returned, still un-narrowed (the run lifecycle checks plugin shape).
1360
+ */
1361
+ async function evaluateHostCode(sandbox, code, id, vmTimeoutMs) {
1362
+ try {
1363
+ return await runInContext(`(async () => {\n${code}\n})()`, sandbox, {
1364
+ filename: `cordis-dyn-${id}.js`,
1365
+ timeout: vmTimeoutMs
1366
+ });
1367
+ } catch (error) {
1368
+ if (!isSyntaxError(error)) throw error;
1369
+ throw new Error(parseErrorMessage("code.host", syntaxErrorContext(error)));
1370
+ }
1371
+ }
1372
+ //#endregion
1373
+ //#region lib/types/index.js
1374
+ /**
1375
+ * Dynamic Cordis Plugin service: immutable package definitions, one active run
1376
+ * per Plugin, human-approved Client activation, and Host/Client invocation.
1377
+ * @module @qilin-agent/kylin-host-runner
1378
+ */
1379
+ var __runInitializers = function(thisArg, initializers, value) {
1380
+ var useValue = arguments.length > 2;
1381
+ for (var i = 0; i < initializers.length; i++) value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
1382
+ return useValue ? value : void 0;
1383
+ };
1384
+ var __esDecorate = function(ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
1385
+ function accept(f) {
1386
+ if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected");
1387
+ return f;
1388
+ }
1389
+ var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
1390
+ var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
1391
+ var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
1392
+ var _, done = false;
1393
+ for (var i = decorators.length - 1; i >= 0; i--) {
1394
+ var context = {};
1395
+ for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
1396
+ for (var p in contextIn.access) context.access[p] = contextIn.access[p];
1397
+ context.addInitializer = function(f) {
1398
+ if (done) throw new TypeError("Cannot add initializers after decoration has completed");
1399
+ extraInitializers.push(accept(f || null));
1400
+ };
1401
+ var result = (0, decorators[i])(kind === "accessor" ? {
1402
+ get: descriptor.get,
1403
+ set: descriptor.set
1404
+ } : descriptor[key], context);
1405
+ if (kind === "accessor") {
1406
+ if (result === void 0) continue;
1407
+ if (result === null || typeof result !== "object") throw new TypeError("Object expected");
1408
+ if (_ = accept(result.get)) descriptor.get = _;
1409
+ if (_ = accept(result.set)) descriptor.set = _;
1410
+ if (_ = accept(result.init)) initializers.unshift(_);
1411
+ } else if (_ = accept(result)) if (kind === "field") initializers.unshift(_);
1412
+ else descriptor[key] = _;
1413
+ }
1414
+ if (target) Object.defineProperty(target, contextIn.name, descriptor);
1415
+ done = true;
1416
+ };
1417
+ /**
1418
+ * Brand a Host-minted Plugin ID.
1419
+ * @param id - opaque identifier minted by the Host registry.
1420
+ * @returns the branded Plugin identifier.
1421
+ */
1422
+ function CordisDynamicPluginId(id) {
1423
+ return id;
1424
+ }
1425
+ /**
1426
+ * Brand a Host-minted Package ID.
1427
+ * @param id - opaque identifier minted by the Host registry.
1428
+ * @returns the branded Package identifier.
1429
+ */
1430
+ function CordisDynamicPackageId(id) {
1431
+ return id;
1432
+ }
1433
+ /**
1434
+ * Brand a Host-minted Plugin Run ID.
1435
+ * @param id - opaque identifier minted by the Host registry.
1436
+ * @returns the branded Plugin Run identifier.
1437
+ */
1438
+ function CordisDynamicPluginRunId(id) {
1439
+ return id;
1440
+ }
1441
+ /**
1442
+ * Brand a Host-minted approval request ID.
1443
+ * @param id - opaque identifier minted by the Host registry.
1444
+ * @returns the branded approval request identifier.
1445
+ */
1446
+ function ApprovalRequestId(id) {
1447
+ return id;
1448
+ }
1449
+ /** Dynamic Plugin registry and Host-half lifecycle. */
1450
+ let DynamicCordisRunnerService = (() => {
1451
+ let _classSuper = TypertRemoteService;
1452
+ let _instanceExtraInitializers = [];
1453
+ let _undefineFromPanel_decorators;
1454
+ let _runHostHalf_decorators;
1455
+ let _getClientCode_decorators;
1456
+ let _resolveRequestRun_decorators;
1457
+ let _settleUserRun_decorators;
1458
+ let _stopFromPanel_decorators;
1459
+ let _syncInspectManifest_decorators;
1460
+ let _resolveInspectQuery_decorators;
1461
+ let _inventory_decorators;
1462
+ let _reportRenderFailure_decorators;
1463
+ let _reportClientGuardFailure_decorators;
1464
+ let _invoke_decorators;
1465
+ return class DynamicCordisRunnerService extends _classSuper {
1466
+ static {
1467
+ const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
1468
+ _undefineFromPanel_decorators = [Remote("undefineFromPanel")];
1469
+ _runHostHalf_decorators = [Remote("runHostHalf")];
1470
+ _getClientCode_decorators = [Remote("getClientCode")];
1471
+ _resolveRequestRun_decorators = [Remote("resolveRequestRun")];
1472
+ _settleUserRun_decorators = [Remote("settleUserRun")];
1473
+ _stopFromPanel_decorators = [Remote("stopFromPanel")];
1474
+ _syncInspectManifest_decorators = [Remote("syncInspectManifest")];
1475
+ _resolveInspectQuery_decorators = [Remote("resolveInspectQuery")];
1476
+ _inventory_decorators = [Remote("inventory")];
1477
+ _reportRenderFailure_decorators = [Remote("reportRenderFailure")];
1478
+ _reportClientGuardFailure_decorators = [Remote("reportClientGuardFailure")];
1479
+ _invoke_decorators = [Remote("invoke")];
1480
+ __esDecorate(this, null, _undefineFromPanel_decorators, {
1481
+ kind: "method",
1482
+ name: "undefineFromPanel",
1483
+ static: false,
1484
+ private: false,
1485
+ access: {
1486
+ has: (obj) => "undefineFromPanel" in obj,
1487
+ get: (obj) => obj.undefineFromPanel
1488
+ },
1489
+ metadata: _metadata
1490
+ }, null, _instanceExtraInitializers);
1491
+ __esDecorate(this, null, _runHostHalf_decorators, {
1492
+ kind: "method",
1493
+ name: "runHostHalf",
1494
+ static: false,
1495
+ private: false,
1496
+ access: {
1497
+ has: (obj) => "runHostHalf" in obj,
1498
+ get: (obj) => obj.runHostHalf
1499
+ },
1500
+ metadata: _metadata
1501
+ }, null, _instanceExtraInitializers);
1502
+ __esDecorate(this, null, _getClientCode_decorators, {
1503
+ kind: "method",
1504
+ name: "getClientCode",
1505
+ static: false,
1506
+ private: false,
1507
+ access: {
1508
+ has: (obj) => "getClientCode" in obj,
1509
+ get: (obj) => obj.getClientCode
1510
+ },
1511
+ metadata: _metadata
1512
+ }, null, _instanceExtraInitializers);
1513
+ __esDecorate(this, null, _resolveRequestRun_decorators, {
1514
+ kind: "method",
1515
+ name: "resolveRequestRun",
1516
+ static: false,
1517
+ private: false,
1518
+ access: {
1519
+ has: (obj) => "resolveRequestRun" in obj,
1520
+ get: (obj) => obj.resolveRequestRun
1521
+ },
1522
+ metadata: _metadata
1523
+ }, null, _instanceExtraInitializers);
1524
+ __esDecorate(this, null, _settleUserRun_decorators, {
1525
+ kind: "method",
1526
+ name: "settleUserRun",
1527
+ static: false,
1528
+ private: false,
1529
+ access: {
1530
+ has: (obj) => "settleUserRun" in obj,
1531
+ get: (obj) => obj.settleUserRun
1532
+ },
1533
+ metadata: _metadata
1534
+ }, null, _instanceExtraInitializers);
1535
+ __esDecorate(this, null, _stopFromPanel_decorators, {
1536
+ kind: "method",
1537
+ name: "stopFromPanel",
1538
+ static: false,
1539
+ private: false,
1540
+ access: {
1541
+ has: (obj) => "stopFromPanel" in obj,
1542
+ get: (obj) => obj.stopFromPanel
1543
+ },
1544
+ metadata: _metadata
1545
+ }, null, _instanceExtraInitializers);
1546
+ __esDecorate(this, null, _syncInspectManifest_decorators, {
1547
+ kind: "method",
1548
+ name: "syncInspectManifest",
1549
+ static: false,
1550
+ private: false,
1551
+ access: {
1552
+ has: (obj) => "syncInspectManifest" in obj,
1553
+ get: (obj) => obj.syncInspectManifest
1554
+ },
1555
+ metadata: _metadata
1556
+ }, null, _instanceExtraInitializers);
1557
+ __esDecorate(this, null, _resolveInspectQuery_decorators, {
1558
+ kind: "method",
1559
+ name: "resolveInspectQuery",
1560
+ static: false,
1561
+ private: false,
1562
+ access: {
1563
+ has: (obj) => "resolveInspectQuery" in obj,
1564
+ get: (obj) => obj.resolveInspectQuery
1565
+ },
1566
+ metadata: _metadata
1567
+ }, null, _instanceExtraInitializers);
1568
+ __esDecorate(this, null, _inventory_decorators, {
1569
+ kind: "method",
1570
+ name: "inventory",
1571
+ static: false,
1572
+ private: false,
1573
+ access: {
1574
+ has: (obj) => "inventory" in obj,
1575
+ get: (obj) => obj.inventory
1576
+ },
1577
+ metadata: _metadata
1578
+ }, null, _instanceExtraInitializers);
1579
+ __esDecorate(this, null, _reportRenderFailure_decorators, {
1580
+ kind: "method",
1581
+ name: "reportRenderFailure",
1582
+ static: false,
1583
+ private: false,
1584
+ access: {
1585
+ has: (obj) => "reportRenderFailure" in obj,
1586
+ get: (obj) => obj.reportRenderFailure
1587
+ },
1588
+ metadata: _metadata
1589
+ }, null, _instanceExtraInitializers);
1590
+ __esDecorate(this, null, _reportClientGuardFailure_decorators, {
1591
+ kind: "method",
1592
+ name: "reportClientGuardFailure",
1593
+ static: false,
1594
+ private: false,
1595
+ access: {
1596
+ has: (obj) => "reportClientGuardFailure" in obj,
1597
+ get: (obj) => obj.reportClientGuardFailure
1598
+ },
1599
+ metadata: _metadata
1600
+ }, null, _instanceExtraInitializers);
1601
+ __esDecorate(this, null, _invoke_decorators, {
1602
+ kind: "method",
1603
+ name: "invoke",
1604
+ static: false,
1605
+ private: false,
1606
+ access: {
1607
+ has: (obj) => "invoke" in obj,
1608
+ get: (obj) => obj.invoke
1609
+ },
1610
+ metadata: _metadata
1611
+ }, null, _instanceExtraInitializers);
1612
+ if (_metadata) Object.defineProperty(this, Symbol.metadata, {
1613
+ enumerable: true,
1614
+ configurable: true,
1615
+ writable: true,
1616
+ value: _metadata
1617
+ });
1618
+ }
1619
+ static inject = ["tools"];
1620
+ static Config = z.object({
1621
+ vmTimeoutMs: z.number().min(1).default(5e3),
1622
+ clientInspectTimeoutMs: z.number().step(1).min(1).max(2147483647).default(1e4)
1623
+ });
1624
+ rootCtx = __runInitializers(this, _instanceExtraInitializers);
1625
+ registry = new DynamicCordisRegistry();
1626
+ inspectRegistry;
1627
+ starting = /* @__PURE__ */ new Map();
1628
+ resolved;
1629
+ group;
1630
+ /** Create the service under the Host composition. */
1631
+ constructor(ctx, config) {
1632
+ super(ctx, "dynamicCordisRunner");
1633
+ this.rootCtx = ctx;
1634
+ this.resolved = config;
1635
+ this.inspectRegistry = new CordisInspectRegistryService(ctx, this.resolved.clientInspectTimeoutMs);
1636
+ }
1637
+ /**
1638
+ * Define a new Plugin's first Package or append a Package to an existing Plugin.
1639
+ * @param request - Session ownership, Plugin selection, metadata, and source code.
1640
+ * @returns Host-minted Plugin and Package identities with declared-half metadata.
1641
+ */
1642
+ define(request) {
1643
+ const name = request.name.trim();
1644
+ const purpose = request.purpose.trim();
1645
+ if (name.length === 0) throw new Error("cordis_define needs a non-empty `name`");
1646
+ if (purpose.length === 0) throw new Error("cordis_define needs a non-empty `purpose`");
1647
+ if (request.code.host === void 0 && request.code.client === void 0) throw new Error("cordis_define needs `code.host`, `code.client`, or both");
1648
+ if (request.code.host !== void 0) precheckCode(request.code.host, "code.host");
1649
+ if (request.code.client !== void 0) precheckCode(request.code.client, "code.client");
1650
+ let plugin;
1651
+ if (request.plugin.kind === "new") {
1652
+ const prefix = request.plugin.idPrefix.trim();
1653
+ if (!/^[a-z]{3,6}$/.test(prefix)) throw new Error("cordis_define `plugin.idPrefix` must contain 3–6 lowercase English letters");
1654
+ plugin = {
1655
+ pluginId: CordisDynamicPluginId(this.registry.mintPluginId(prefix)),
1656
+ sessionId: request.sessionId,
1657
+ packages: /* @__PURE__ */ new Map(),
1658
+ approvedClientPackages: /* @__PURE__ */ new Set(),
1659
+ clientVersionUpdatesApproved: false
1660
+ };
1661
+ this.registry.add(plugin);
1662
+ } else {
1663
+ const found = this.registry.get(request.plugin.pluginId);
1664
+ if (found === void 0 || found.sessionId !== request.sessionId) throw new Error(missingPluginMessage(request.plugin.pluginId));
1665
+ plugin = found;
1666
+ }
1667
+ const packageId = CordisDynamicPackageId(this.registry.mintPackageId());
1668
+ const definition = {
1669
+ packageId,
1670
+ name,
1671
+ purpose,
1672
+ ...request.code.host === void 0 ? {} : { hostCode: request.code.host },
1673
+ ...request.code.client === void 0 ? {} : { clientCode: request.code.client }
1674
+ };
1675
+ plugin.packages.set(packageId, definition);
1676
+ return {
1677
+ pluginId: plugin.pluginId,
1678
+ packageId,
1679
+ name,
1680
+ purpose,
1681
+ hasHostHalf: definition.hostCode !== void 0,
1682
+ hasClientHalf: definition.clientCode !== void 0
1683
+ };
1684
+ }
1685
+ /**
1686
+ * Remove a Plugin, its active run, and all immutable Packages.
1687
+ * @param agent - Agent whose Session must own the Plugin.
1688
+ * @param pluginId - Stable Plugin identity to remove.
1689
+ * @returns Whether removal succeeded and whether it stopped an active run.
1690
+ */
1691
+ async undefine(agent, pluginId) {
1692
+ const plugin = this.owned(agent, pluginId);
1693
+ if (plugin === void 0) return {
1694
+ ok: false,
1695
+ reason: "plugin-missing",
1696
+ message: missingPluginMessage(pluginId)
1697
+ };
1698
+ const wasRunning = plugin.run !== void 0;
1699
+ this.cancelPending(pluginId, `dynamic plugin "${pluginId}" was removed before approval`);
1700
+ if (plugin.run !== void 0) await this.retract(plugin);
1701
+ this.registry.delete(pluginId);
1702
+ return {
1703
+ ok: true,
1704
+ wasRunning
1705
+ };
1706
+ }
1707
+ /**
1708
+ * Remove a Plugin from the user panel and queue the resulting state change for the model's next step.
1709
+ * @param agent - Agent whose Session owns the Plugin and receives the context.
1710
+ * @param pluginId - Stable Plugin identity to remove.
1711
+ * @returns Whether removal succeeded and whether it stopped an active run.
1712
+ */
1713
+ async undefineFromPanel(agent, pluginId) {
1714
+ const result = await this.undefine(agent, pluginId);
1715
+ if (result.ok) this.injectUserContext(agent, `The user removed Cordis Plugin ${pluginId} and all of its Packages. The Plugin no longer exists.`);
1716
+ return result;
1717
+ }
1718
+ /**
1719
+ * Start or update one Package for a model tool call. An unauthorized Client
1720
+ * Package waits for approval; Plugin-wide authorization covers later versions.
1721
+ * @param agent - Agent whose Session must own the Plugin.
1722
+ * @param pluginId - Stable Plugin identity to activate.
1723
+ * @param packageId - Immutable Package version to activate.
1724
+ * @param mode - Whether to run the current version or switch versions.
1725
+ * @param signal - Tool-call cancellation signal while the activation request is being created.
1726
+ * @returns The successful activation identity or an actionable refusal.
1727
+ */
1728
+ async run(agent, pluginId, packageId, mode, signal) {
1729
+ const plan = this.resolvePlan(agent, pluginId, packageId, mode);
1730
+ if (!plan.ok) return plan.response;
1731
+ if (signal?.aborted === true) return {
1732
+ ok: false,
1733
+ reason: "cancelled",
1734
+ message: `the run request for dynamic plugin "${pluginId}" was cancelled before activation`
1735
+ };
1736
+ if (this.registry.pendingRequestFor(pluginId) !== void 0) return {
1737
+ ok: false,
1738
+ reason: "transition-in-flight",
1739
+ message: `dynamic plugin "${pluginId}" already has a pending run request`
1740
+ };
1741
+ const attempt = this.createAttempt(plan);
1742
+ plan.plugin.nextPackageId = packageId;
1743
+ plan.plugin.latestRun = attempt;
1744
+ if (plan.definition.clientCode === void 0) {
1745
+ const started = await this.activate(plan, void 0, false, attempt);
1746
+ if (started.ok) return this.runResponse(plan.plugin, started);
1747
+ this.failAttempt(plan.plugin, attempt, "host-load", started);
1748
+ return {
1749
+ ...started,
1750
+ reason: "host-half-failed"
1751
+ };
1752
+ }
1753
+ const requestId = ApprovalRequestId(this.registry.mintApprovalRequestId());
1754
+ const requiresApproval = !plan.plugin.clientVersionUpdatesApproved && !plan.plugin.approvedClientPackages.has(packageId);
1755
+ attempt.approvalRequestId = requestId;
1756
+ attempt.requiresApproval = requiresApproval;
1757
+ attempt.status = requiresApproval ? "awaiting-approval" : "starting-host";
1758
+ this.registry.armRequest(requestId, {
1759
+ agentId: agent.id,
1760
+ pluginId,
1761
+ packageId,
1762
+ pluginRunId: attempt.pluginRunId,
1763
+ mode,
1764
+ requiresApproval
1765
+ });
1766
+ this.ctx.emit("cordis/request-run", {
1767
+ requestId,
1768
+ agentId: agent.id,
1769
+ pluginId,
1770
+ packageId,
1771
+ mode,
1772
+ name: plan.definition.name,
1773
+ purpose: plan.definition.purpose,
1774
+ requiresApproval
1775
+ });
1776
+ return {
1777
+ ok: true,
1778
+ status: requiresApproval ? "awaiting-approval" : "starting",
1779
+ pluginId,
1780
+ packageId,
1781
+ pluginRunId: attempt.pluginRunId,
1782
+ mode,
1783
+ waitingFor: [],
1784
+ ...plan.plugin.currentPackageId === void 0 ? {} : { currentPackageId: plan.plugin.currentPackageId },
1785
+ nextPackageId: packageId
1786
+ };
1787
+ }
1788
+ /**
1789
+ * Start Host code for an approved request or a direct panel gesture.
1790
+ * @param agent - Agent whose Session must own the Plugin.
1791
+ * @param pluginId - Stable Plugin identity to activate.
1792
+ * @param packageId - Immutable Package version to activate.
1793
+ * @param mode - Whether to run the current version or switch versions.
1794
+ * @param requestId - Model-driven request identity, or null for a direct user gesture.
1795
+ * @param approveFutureVersions - Whether this approval covers later Packages of the same Plugin.
1796
+ * @returns The exact Host activation or a failure message.
1797
+ */
1798
+ async runHostHalf(agent, pluginId, packageId, mode, requestId, approveFutureVersions) {
1799
+ const plan = this.resolvePlan(agent, pluginId, packageId, mode, requestId === null);
1800
+ if (!plan.ok) return {
1801
+ ok: false,
1802
+ message: plan.response.message
1803
+ };
1804
+ let attempt;
1805
+ if (requestId !== null) {
1806
+ const pending = this.registry.peekRequest(requestId);
1807
+ if (pending === void 0 || pending.pluginId !== pluginId || pending.packageId !== packageId || pending.mode !== mode) return {
1808
+ ok: false,
1809
+ message: `run request "${requestId}" does not authorize ${pluginId}/${packageId}`
1810
+ };
1811
+ const latest = plan.plugin.latestRun;
1812
+ const expectedStatus = pending.requiresApproval ? "awaiting-approval" : "starting-host";
1813
+ if (latest === void 0 || latest.pluginRunId !== pending.pluginRunId || latest.status !== expectedStatus && !pending.requiresApproval && latest.status !== "client-pending") return {
1814
+ ok: false,
1815
+ message: `run request "${requestId}" no longer identifies the latest run of ${pluginId}`
1816
+ };
1817
+ attempt = latest;
1818
+ if (pending.requiresApproval) {
1819
+ plan.plugin.approvedClientPackages.add(packageId);
1820
+ if (approveFutureVersions) plan.plugin.clientVersionUpdatesApproved = true;
1821
+ }
1822
+ } else {
1823
+ const pending = this.registry.pendingRequestFor(pluginId);
1824
+ if (pending !== void 0) return {
1825
+ ok: false,
1826
+ message: `dynamic plugin "${pluginId}" has pending run request ${pending}`
1827
+ };
1828
+ const attached = plan.plugin.run?.packageId === packageId && plan.plugin.latestRun?.pluginRunId === plan.plugin.run.pluginRunId ? plan.plugin.latestRun : void 0;
1829
+ attempt = attached ?? this.createAttempt(plan);
1830
+ if (attached === void 0) {
1831
+ plan.plugin.nextPackageId = packageId;
1832
+ plan.plugin.latestRun = attempt;
1833
+ }
1834
+ if (plan.definition.clientCode !== void 0) plan.plugin.approvedClientPackages.add(packageId);
1835
+ }
1836
+ const attaching = attempt.pluginRunId === plan.plugin.run?.pluginRunId;
1837
+ if (!attaching) {
1838
+ attempt.status = "starting-host";
1839
+ if (attempt.host.status !== "absent") attempt.host = {
1840
+ status: "pending",
1841
+ waitingFor: []
1842
+ };
1843
+ }
1844
+ const started = await this.activate(plan, requestId ?? void 0, attaching, attempt);
1845
+ if (!started.ok) this.failAttempt(plan.plugin, attempt, "host-load", started);
1846
+ return started;
1847
+ }
1848
+ /**
1849
+ * Fetch Client code for the exact active run.
1850
+ * @param agent - Agent whose Session must own the Plugin.
1851
+ * @param pluginId - Stable Plugin identity to read.
1852
+ * @param pluginRunId - Exact active run authorized to receive source.
1853
+ * @returns Client source and its Plugin, Package, and run identities.
1854
+ */
1855
+ getClientCode(agent, pluginId, pluginRunId) {
1856
+ const plugin = this.owned(agent, pluginId);
1857
+ if (plugin === void 0) throw new Error(missingPluginMessage(pluginId));
1858
+ const run = plugin.run;
1859
+ if (run === void 0 || run.pluginRunId !== pluginRunId) throw new Error(`dynamic plugin "${pluginId}" is not running activation "${pluginRunId}"`);
1860
+ const definition = plugin.packages.get(run.packageId);
1861
+ if (definition?.clientCode === void 0) throw new Error(`package "${run.packageId}" has no Client half`);
1862
+ return {
1863
+ code: definition.clientCode,
1864
+ name: definition.name,
1865
+ pluginId,
1866
+ packageId: run.packageId,
1867
+ pluginRunId
1868
+ };
1869
+ }
1870
+ /**
1871
+ * Resolve one model-driven Client activation request.
1872
+ * @param requestId - Request identity to settle once.
1873
+ * @param resolution - Browser refusal or exact Client activation result.
1874
+ * @returns Whether the still-pending request accepted this resolution.
1875
+ */
1876
+ async resolveRequestRun(requestId, resolution) {
1877
+ const pending = this.registry.peekRequest(requestId);
1878
+ if (pending === void 0) return { accepted: false };
1879
+ const plugin = this.registry.get(pending.pluginId);
1880
+ if (resolution.ok && plugin?.run?.pluginRunId !== resolution.pluginRunId) return { accepted: false };
1881
+ if (!resolution.ok && resolution.pluginRunId !== void 0 && plugin?.run?.pluginRunId !== resolution.pluginRunId) return { accepted: false };
1882
+ this.registry.claimRequest(requestId);
1883
+ const settled = await this.settleActivation(plugin, resolution, requestId);
1884
+ this.announceResolved(requestId, resolution, pending.requiresApproval ? void 0 : "completed");
1885
+ this.steerRunOutcome(pending, settled);
1886
+ return { accepted: true };
1887
+ }
1888
+ /**
1889
+ * Settle a direct panel run after this page loaded or failed its Client half.
1890
+ * @param agent - Agent whose Session must own the Plugin.
1891
+ * @param pluginId - Stable Plugin identity being settled.
1892
+ * @param resolution - Exact Client activation result from the acting page.
1893
+ * @returns The committed activation or its failure.
1894
+ */
1895
+ async settleUserRun(agent, pluginId, resolution) {
1896
+ const plugin = this.owned(agent, pluginId);
1897
+ if (plugin === void 0) return {
1898
+ ok: false,
1899
+ reason: "plugin-missing",
1900
+ message: missingPluginMessage(pluginId)
1901
+ };
1902
+ const settled = await this.settleActivation(plugin, resolution);
1903
+ this.injectUserRunOutcome(agent, pluginId, settled);
1904
+ return settled;
1905
+ }
1906
+ /**
1907
+ * Stop the active run while retaining every Package version.
1908
+ * @param agent - Agent whose Session must own the Plugin.
1909
+ * @param pluginId - Stable Plugin identity to stop.
1910
+ * @returns Success or the reason no run was stopped.
1911
+ */
1912
+ async stop(agent, pluginId) {
1913
+ const plugin = this.owned(agent, pluginId);
1914
+ if (plugin === void 0) return {
1915
+ ok: false,
1916
+ reason: "plugin-missing",
1917
+ message: missingPluginMessage(pluginId)
1918
+ };
1919
+ const pending = this.registry.pendingRequestFor(pluginId);
1920
+ if (plugin.run === void 0 && pending === void 0) return {
1921
+ ok: false,
1922
+ reason: "not-running",
1923
+ message: `dynamic plugin "${pluginId}" is not running`
1924
+ };
1925
+ if (pending !== void 0) this.cancelPending(pluginId, `dynamic plugin "${pluginId}" was stopped before approval`);
1926
+ if (plugin.run !== void 0) await this.retract(plugin);
1927
+ if (plugin.latestRun !== void 0) {
1928
+ plugin.latestRun.status = "stopped";
1929
+ if (plugin.latestRun.host.status !== "absent") plugin.latestRun.host = {
1930
+ status: "stopped",
1931
+ waitingFor: []
1932
+ };
1933
+ if (plugin.latestRun.client.status !== "absent") plugin.latestRun.client = {
1934
+ status: "stopped",
1935
+ waitingFor: []
1936
+ };
1937
+ }
1938
+ return { ok: true };
1939
+ }
1940
+ /**
1941
+ * Stop a Plugin from the user panel and queue the resulting state change for the model's next step.
1942
+ * @param agent - Agent whose Session owns the Plugin and receives the context.
1943
+ * @param pluginId - Stable Plugin identity to stop.
1944
+ * @returns Success or the reason no run was stopped.
1945
+ */
1946
+ async stopFromPanel(agent, pluginId) {
1947
+ const result = await this.stop(agent, pluginId);
1948
+ if (!result.ok) return result;
1949
+ const plugin = this.owned(agent, pluginId);
1950
+ this.injectUserContext(agent, `The user stopped Cordis Plugin ${pluginId}. Its Packages remain defined; currentPackageId is ${plugin?.currentPackageId ?? "none"}.`);
1951
+ return result;
1952
+ }
1953
+ /**
1954
+ * Replace the Host mirror of the Client inspect provider directory.
1955
+ * @param providers - complete Client provider manifest.
1956
+ * @returns null after accepting the manifest.
1957
+ */
1958
+ syncInspectManifest(providers) {
1959
+ this.inspectRegistry.syncClientManifest(providers);
1960
+ return null;
1961
+ }
1962
+ /**
1963
+ * Submit a Client inspect result or failure for a pending query.
1964
+ * @param agent - Session that owns the query.
1965
+ * @param requestId - exact pending query identity.
1966
+ * @param resolution - provider result or structured refusal.
1967
+ * @returns acknowledgement with accepted true only for a valid success that settles the query;
1968
+ * pending-query failures return { accepted: false } and retain only the first diagnostic.
1969
+ */
1970
+ resolveInspectQuery(agent, requestId, resolution) {
1971
+ return this.inspectRegistry.resolveClientQuery(agent, requestId, resolution);
1972
+ }
1973
+ /**
1974
+ * Frame-wide inventory, grouped as one row per stable Plugin.
1975
+ * @returns Source-free metadata for every process-local Plugin.
1976
+ */
1977
+ inventory() {
1978
+ return this.registry.all().map((plugin) => ({
1979
+ pluginId: plugin.pluginId,
1980
+ agentId: plugin.sessionId,
1981
+ packages: [...plugin.packages.values()].map((definition) => ({
1982
+ packageId: definition.packageId,
1983
+ name: definition.name,
1984
+ purpose: definition.purpose,
1985
+ hasHostHalf: definition.hostCode !== void 0,
1986
+ hasClientHalf: definition.clientCode !== void 0
1987
+ })),
1988
+ ...plugin.currentPackageId === void 0 ? {} : { currentPackageId: plugin.currentPackageId },
1989
+ ...plugin.nextPackageId === void 0 ? {} : { nextPackageId: plugin.nextPackageId },
1990
+ ...plugin.run === void 0 ? {} : { activeRun: {
1991
+ pluginRunId: plugin.run.pluginRunId,
1992
+ packageId: plugin.run.packageId
1993
+ } },
1994
+ ...plugin.latestRun === void 0 ? {} : { latestRun: cloneAttempt(plugin.latestRun) }
1995
+ }));
1996
+ }
1997
+ /**
1998
+ * Read one Session's Host-rich state for inspection and result rendering.
1999
+ * @param agent - Agent whose Session selects visible Plugins.
2000
+ * @returns Plugin versions, active runs, Host fibers, and render failures.
2001
+ */
2002
+ snapshot(agent) {
2003
+ return this.registry.ofSession(agent.id).map((plugin) => ({
2004
+ pluginId: plugin.pluginId,
2005
+ ...plugin.currentPackageId === void 0 ? {} : { currentPackageId: plugin.currentPackageId },
2006
+ ...plugin.nextPackageId === void 0 ? {} : { nextPackageId: plugin.nextPackageId },
2007
+ packages: [...plugin.packages.values()].map((definition) => ({
2008
+ packageId: definition.packageId,
2009
+ name: definition.name,
2010
+ purpose: definition.purpose,
2011
+ hasHostHalf: definition.hostCode !== void 0,
2012
+ hasClientHalf: definition.clientCode !== void 0
2013
+ })),
2014
+ ...plugin.run === void 0 ? {} : { activeRun: {
2015
+ pluginRunId: plugin.run.pluginRunId,
2016
+ packageId: plugin.run.packageId,
2017
+ ...plugin.run.fiber === void 0 ? {} : { fiber: plugin.run.fiber },
2018
+ handlers: [...plugin.run.handlers.keys()],
2019
+ ...plugin.run.renderFailure === void 0 ? {} : { renderFailure: plugin.run.renderFailure }
2020
+ } },
2021
+ ...plugin.latestRun === void 0 ? {} : { latestRun: cloneAttempt(plugin.latestRun) }
2022
+ }));
2023
+ }
2024
+ /**
2025
+ * Read source-free context for an explicit `@pluginId` user gesture.
2026
+ * @param agent - Agent whose Session must own the Plugin.
2027
+ * @param pluginId - Stable Plugin identity referenced by the user.
2028
+ * @returns The preferred modification base, or undefined when unavailable.
2029
+ */
2030
+ reference(agent, pluginId) {
2031
+ const plugin = this.owned(agent, pluginId);
2032
+ if (plugin === void 0) return void 0;
2033
+ const packageId = plugin.nextPackageId ?? plugin.currentPackageId ?? [...plugin.packages.keys()].at(-1);
2034
+ if (packageId === void 0) return void 0;
2035
+ const definition = plugin.packages.get(packageId);
2036
+ if (definition === void 0) return void 0;
2037
+ return {
2038
+ pluginId,
2039
+ packageId,
2040
+ name: definition.name,
2041
+ purpose: definition.purpose,
2042
+ ...plugin.currentPackageId === void 0 ? {} : { currentPackageId: plugin.currentPackageId },
2043
+ ...plugin.nextPackageId === void 0 ? {} : { nextPackageId: plugin.nextPackageId },
2044
+ ...plugin.run === void 0 ? {} : { activeRun: {
2045
+ pluginRunId: plugin.run.pluginRunId,
2046
+ packageId: plugin.run.packageId
2047
+ } },
2048
+ ...plugin.latestRun === void 0 ? {} : { latestRun: cloneAttempt(plugin.latestRun) }
2049
+ };
2050
+ }
2051
+ /**
2052
+ * List source-free Plugin summaries owned by one Session.
2053
+ * @param agent - Agent whose Session selects visible Plugins.
2054
+ * @returns one summary per Plugin in creation order.
2055
+ */
2056
+ listPlugins(agent) {
2057
+ return this.registry.ofSession(agent.id).map((plugin) => this.inspectPlugin(agent, plugin.pluginId));
2058
+ }
2059
+ /**
2060
+ * Inspect one Plugin without returning Package source.
2061
+ * @param agent - Agent whose Session must own the Plugin.
2062
+ * @param pluginId - stable Plugin identity.
2063
+ * @returns version pointers, latest run, and all Package summaries.
2064
+ */
2065
+ inspectPlugin(agent, pluginId) {
2066
+ const plugin = this.owned(agent, pluginId);
2067
+ if (plugin === void 0) throw new Error(missingPluginMessage(pluginId));
2068
+ const reference = this.reference(agent, pluginId);
2069
+ if (reference === void 0) throw new Error(`dynamic plugin "${pluginId}" has no package`);
2070
+ return {
2071
+ ...reference,
2072
+ packages: [...plugin.packages.values()].map((definition) => ({
2073
+ packageId: definition.packageId,
2074
+ name: definition.name,
2075
+ purpose: definition.purpose,
2076
+ hasHostHalf: definition.hostCode !== void 0,
2077
+ hasClientHalf: definition.clientCode !== void 0
2078
+ }))
2079
+ };
2080
+ }
2081
+ /**
2082
+ * Read one exact immutable Package and its Host and Client source.
2083
+ * @param agent - Agent whose Session must own the Plugin.
2084
+ * @param pluginId - Stable Plugin identity that owns the Package.
2085
+ * @param packageId - Exact immutable Package identity to inspect.
2086
+ * @returns Package metadata, source, and the Plugin's lifecycle pointers.
2087
+ */
2088
+ inspectPackage(agent, pluginId, packageId) {
2089
+ const plugin = this.owned(agent, pluginId);
2090
+ if (plugin === void 0) throw new Error(missingPluginMessage(pluginId));
2091
+ const definition = plugin.packages.get(packageId);
2092
+ if (definition === void 0) throw new Error(`dynamic package "${packageId}" does not exist on plugin "${pluginId}"`);
2093
+ return {
2094
+ pluginId,
2095
+ packageId,
2096
+ name: definition.name,
2097
+ purpose: definition.purpose,
2098
+ code: {
2099
+ ...definition.hostCode === void 0 ? {} : { host: definition.hostCode },
2100
+ ...definition.clientCode === void 0 ? {} : { client: definition.clientCode }
2101
+ },
2102
+ ...plugin.currentPackageId === void 0 ? {} : { currentPackageId: plugin.currentPackageId },
2103
+ ...plugin.nextPackageId === void 0 ? {} : { nextPackageId: plugin.nextPackageId },
2104
+ ...plugin.run === void 0 ? {} : { activeRun: {
2105
+ pluginRunId: plugin.run.pluginRunId,
2106
+ packageId: plugin.run.packageId
2107
+ } },
2108
+ ...plugin.latestRun === void 0 ? {} : { latestRun: cloneAttempt(plugin.latestRun) }
2109
+ };
2110
+ }
2111
+ /**
2112
+ * Record a post-load render failure for the exact active run.
2113
+ * @param agent - Agent whose Session must own the Plugin.
2114
+ * @param pluginId - Stable Plugin identity that rendered.
2115
+ * @param pluginRunId - Exact active run that produced the failure.
2116
+ * @param failure - Slot, message, and entry-retirement result.
2117
+ * @returns Null after recording or ignoring a stale report.
2118
+ */
2119
+ async reportRenderFailure(agent, pluginId, pluginRunId, failure) {
2120
+ const plugin = this.owned(agent, pluginId);
2121
+ if (plugin?.run?.pluginRunId === pluginRunId) {
2122
+ const run = plugin.run;
2123
+ const definition = plugin.packages.get(plugin.run.packageId);
2124
+ const shouldSteer = run.renderFailure === void 0;
2125
+ run.renderFailure = failure;
2126
+ const attempt = plugin.latestRun;
2127
+ if (attempt?.pluginRunId === pluginRunId) {
2128
+ attempt.error = this.diagnostic(plugin, attempt, "client-render", failure);
2129
+ attempt.client = {
2130
+ status: "failed",
2131
+ waitingFor: attempt.client.waitingFor,
2132
+ error: failure.message
2133
+ };
2134
+ attempt.status = "failed";
2135
+ }
2136
+ if (definition !== void 0 && shouldSteer) this.steerRenderFailure(agent, plugin, definition, pluginRunId, failure);
2137
+ }
2138
+ return await Promise.resolve(null);
2139
+ }
2140
+ /**
2141
+ * Report a Client guard rejection that happened after the Package completed activation.
2142
+ * @param agent - Agent whose Session must own the Plugin.
2143
+ * @param pluginId - Stable Plugin identity whose Client code was rejected.
2144
+ * @param pluginRunId - Exact active run that produced the rejection.
2145
+ * @param failure - Original guard message and stack.
2146
+ * @returns Null after reporting or ignoring a stale/startup failure.
2147
+ */
2148
+ async reportClientGuardFailure(agent, pluginId, pluginRunId, failure) {
2149
+ const plugin = this.owned(agent, pluginId);
2150
+ const run = plugin?.run;
2151
+ if (plugin !== void 0 && run?.pluginRunId === pluginRunId) this.steerGuardFailure(plugin, run, "Client", failure);
2152
+ return await Promise.resolve(null);
2153
+ }
2154
+ /**
2155
+ * Invoke an active Host method while rejecting stale Client runs.
2156
+ * @param pluginId - Stable Plugin identity that owns the method.
2157
+ * @param pluginRunId - Exact active run authorizing the call.
2158
+ * @param method - Registered Host handler name.
2159
+ * @param args - JSON argument delivered to the handler.
2160
+ * @returns The JSON result or a typed invocation failure.
2161
+ */
2162
+ async invoke(pluginId, pluginRunId, method, args) {
2163
+ const plugin = this.registry.get(pluginId);
2164
+ if (plugin === void 0 || plugin.run === void 0) return {
2165
+ ok: false,
2166
+ code: "plugin-not-running",
2167
+ message: `dynamic plugin "${pluginId}" is not running`
2168
+ };
2169
+ const run = plugin.run;
2170
+ if (run.pluginRunId !== pluginRunId) return {
2171
+ ok: false,
2172
+ code: "stale-run",
2173
+ message: `activation "${pluginRunId}" is no longer active`
2174
+ };
2175
+ const handler = run.handlers.get(method);
2176
+ if (handler === void 0) return {
2177
+ ok: false,
2178
+ code: "method-not-found",
2179
+ message: `dynamic plugin "${pluginId}" registered no Host method "${method}"`
2180
+ };
2181
+ try {
2182
+ return {
2183
+ ok: true,
2184
+ value: await handler(args)
2185
+ };
2186
+ } catch (error) {
2187
+ const failure = errorDetails(error);
2188
+ this.steerHostHandlerFailure(plugin, run, method, failure);
2189
+ return {
2190
+ ok: false,
2191
+ code: "handler-error",
2192
+ ...failure
2193
+ };
2194
+ }
2195
+ }
2196
+ resolvePlan(agent, pluginId, packageId, mode, allowActiveAttach = false) {
2197
+ const plugin = this.owned(agent, pluginId);
2198
+ if (plugin === void 0) return {
2199
+ ok: false,
2200
+ response: {
2201
+ ok: false,
2202
+ reason: "plugin-missing",
2203
+ message: missingPluginMessage(pluginId)
2204
+ }
2205
+ };
2206
+ const definition = plugin.packages.get(packageId);
2207
+ if (definition === void 0) return {
2208
+ ok: false,
2209
+ response: {
2210
+ ok: false,
2211
+ reason: "package-missing",
2212
+ message: `plugin "${pluginId}" has no package "${packageId}"`
2213
+ }
2214
+ };
2215
+ const current = plugin.currentPackageId;
2216
+ if (mode === "update" && (current === void 0 || current === packageId)) return {
2217
+ ok: false,
2218
+ response: {
2219
+ ok: false,
2220
+ reason: "invalid-mode",
2221
+ message: current === void 0 ? `plugin "${pluginId}" has no successful version yet; start "${packageId}" with mode "run"` : `package "${packageId}" is already current; use mode "run"`
2222
+ }
2223
+ };
2224
+ if (mode === "run" && current !== void 0 && current !== packageId) return {
2225
+ ok: false,
2226
+ response: {
2227
+ ok: false,
2228
+ reason: "invalid-mode",
2229
+ message: `package "${packageId}" differs from current "${current}"; use mode "update"`
2230
+ }
2231
+ };
2232
+ if (!allowActiveAttach && this.starting.has(pluginId)) return {
2233
+ ok: false,
2234
+ response: {
2235
+ ok: false,
2236
+ reason: "transition-in-flight",
2237
+ message: `plugin "${pluginId}" is already starting`
2238
+ }
2239
+ };
2240
+ return {
2241
+ ok: true,
2242
+ plugin,
2243
+ definition,
2244
+ mode
2245
+ };
2246
+ }
2247
+ activate(plan, requestId, allowActiveAttach, attempt) {
2248
+ const inFlight = this.starting.get(plan.plugin.pluginId);
2249
+ if (inFlight !== void 0) return inFlight;
2250
+ const starting = this.startFresh(plan, requestId, allowActiveAttach, attempt);
2251
+ this.starting.set(plan.plugin.pluginId, starting);
2252
+ return starting.finally(() => {
2253
+ this.starting.delete(plan.plugin.pluginId);
2254
+ });
2255
+ }
2256
+ async startFresh(plan, requestId, allowActiveAttach, attempt) {
2257
+ const { plugin, definition, mode } = plan;
2258
+ if (allowActiveAttach && plugin.run?.packageId === definition.packageId && plugin.run.pluginRunId === attempt.pluginRunId) return {
2259
+ ok: true,
2260
+ pluginId: plugin.pluginId,
2261
+ packageId: definition.packageId,
2262
+ pluginRunId: plugin.run.pluginRunId,
2263
+ waitingFor: missingFor(this.ctx, plugin.run),
2264
+ startedHere: false
2265
+ };
2266
+ if (plugin.run !== void 0) await this.retract(plugin);
2267
+ if (mode === "update" || plugin.currentPackageId === void 0) plugin.nextPackageId = definition.packageId;
2268
+ const run = {
2269
+ pluginRunId: attempt.pluginRunId,
2270
+ packageId: definition.packageId,
2271
+ handlers: /* @__PURE__ */ new Map(),
2272
+ handlerDisposers: [],
2273
+ reportedRuntimeErrors: /* @__PURE__ */ new Set(),
2274
+ ...requestId === void 0 ? {} : { startedForRequest: requestId }
2275
+ };
2276
+ if (definition.hostCode !== void 0) {
2277
+ const failure = await this.startHost(plugin, definition.hostCode, run);
2278
+ if (failure !== void 0) return {
2279
+ ok: false,
2280
+ ...failure
2281
+ };
2282
+ }
2283
+ plugin.run = run;
2284
+ this.ctx.emit("cordis/dynamic-package", {
2285
+ pluginId: plugin.pluginId,
2286
+ packageId: definition.packageId,
2287
+ pluginRunId: run.pluginRunId,
2288
+ name: definition.name
2289
+ });
2290
+ attempt.host = {
2291
+ status: run.fiber === void 0 ? "absent" : missingFor(this.ctx, run).length === 0 ? "running" : "waiting",
2292
+ waitingFor: missingFor(this.ctx, run)
2293
+ };
2294
+ if (definition.clientCode === void 0) this.commitActivation(plugin, run);
2295
+ else {
2296
+ attempt.status = "client-pending";
2297
+ attempt.client = {
2298
+ status: "pending",
2299
+ waitingFor: []
2300
+ };
2301
+ }
2302
+ return {
2303
+ ok: true,
2304
+ pluginId: plugin.pluginId,
2305
+ packageId: definition.packageId,
2306
+ pluginRunId: run.pluginRunId,
2307
+ waitingFor: missingFor(this.ctx, run),
2308
+ startedHere: true
2309
+ };
2310
+ }
2311
+ async startHost(plugin, hostCode, run) {
2312
+ const handle = (method, fn) => {
2313
+ const normalized = normalizeHandler(method, fn);
2314
+ run.handlers.set(normalized.method, normalized.handler);
2315
+ const dispose = () => {
2316
+ if (run.handlers.get(normalized.method) === normalized.handler) run.handlers.delete(normalized.method);
2317
+ };
2318
+ run.handlerDisposers.push(dispose);
2319
+ return dispose;
2320
+ };
2321
+ try {
2322
+ const evaluated = await evaluateHostCode(createSandbox(plugin.pluginId, { handle }), hostCode, plugin.pluginId, this.resolved.vmTimeoutMs);
2323
+ 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)");
2324
+ run.fiber = await startHostHalf(this.requireGroup(), evaluated, (error) => {
2325
+ this.steerGuardFailure(plugin, run, "Host", errorDetails(error));
2326
+ });
2327
+ return;
2328
+ } catch (error) {
2329
+ for (const dispose of run.handlerDisposers.splice(0)) dispose();
2330
+ return errorDetails(error);
2331
+ }
2332
+ }
2333
+ async settleActivation(plugin, resolution, requestId) {
2334
+ if (plugin === void 0) return {
2335
+ ok: false,
2336
+ reason: "plugin-missing",
2337
+ message: "the dynamic plugin was removed during activation"
2338
+ };
2339
+ const attempt = plugin.latestRun;
2340
+ if (!resolution.ok) {
2341
+ if (resolution.reason === "rejected") {
2342
+ if (attempt !== void 0) {
2343
+ attempt.status = "rejected";
2344
+ attempt.error = this.diagnostic(plugin, attempt, "approval", resolution.message ?? "the run request was declined");
2345
+ attempt.client = {
2346
+ status: "stopped",
2347
+ waitingFor: []
2348
+ };
2349
+ }
2350
+ return {
2351
+ ok: false,
2352
+ reason: "rejected",
2353
+ message: resolution.message ?? "the run request was declined"
2354
+ };
2355
+ }
2356
+ const run = plugin.run;
2357
+ if (run !== void 0 && resolution.pluginRunId === run.pluginRunId && (requestId === void 0 || run.startedForRequest === requestId) && resolution.startedHere !== false) await this.retract(plugin);
2358
+ 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", {
2359
+ message: resolution.message ?? resolution.reason,
2360
+ ...resolution.stack === void 0 ? {} : { stack: resolution.stack }
2361
+ });
2362
+ return {
2363
+ ok: false,
2364
+ reason: resolution.reason,
2365
+ message: resolution.message ?? resolution.reason,
2366
+ ...resolution.stack === void 0 ? {} : { stack: resolution.stack }
2367
+ };
2368
+ }
2369
+ const run = plugin.run;
2370
+ if (run === void 0 || run.pluginRunId !== resolution.pluginRunId) return {
2371
+ ok: false,
2372
+ reason: "client-half-failed",
2373
+ message: `activation "${resolution.pluginRunId}" is no longer active`
2374
+ };
2375
+ if (attempt !== void 0 && attempt.pluginRunId === run.pluginRunId) attempt.client = {
2376
+ status: resolution.waitingFor === void 0 || resolution.waitingFor.length === 0 ? "running" : "waiting",
2377
+ waitingFor: resolution.waitingFor ?? []
2378
+ };
2379
+ this.commitActivation(plugin, run);
2380
+ return {
2381
+ ...this.runResponse(plugin, {
2382
+ ok: true,
2383
+ pluginId: plugin.pluginId,
2384
+ packageId: run.packageId,
2385
+ pluginRunId: run.pluginRunId,
2386
+ waitingFor: missingFor(this.ctx, run),
2387
+ startedHere: false
2388
+ }),
2389
+ ...resolution.waitingFor === void 0 ? {} : { clientWaitingFor: resolution.waitingFor }
2390
+ };
2391
+ }
2392
+ commitActivation(plugin, run) {
2393
+ plugin.currentPackageId = run.packageId;
2394
+ delete plugin.nextPackageId;
2395
+ delete run.startedForRequest;
2396
+ const attempt = plugin.latestRun;
2397
+ if (attempt?.pluginRunId === run.pluginRunId) {
2398
+ attempt.status = attempt.host.status === "waiting" || attempt.client.status === "waiting" ? "waiting" : "running";
2399
+ delete attempt.approvalRequestId;
2400
+ delete attempt.requiresApproval;
2401
+ delete attempt.error;
2402
+ }
2403
+ }
2404
+ runResponse(plugin, started) {
2405
+ return {
2406
+ ok: true,
2407
+ status: "running",
2408
+ pluginId: plugin.pluginId,
2409
+ packageId: started.packageId,
2410
+ pluginRunId: started.pluginRunId,
2411
+ waitingFor: started.waitingFor,
2412
+ currentPackageId: started.packageId,
2413
+ mode: plugin.latestRun?.pluginRunId === started.pluginRunId ? plugin.latestRun.mode : "run"
2414
+ };
2415
+ }
2416
+ announceResolved(requestId, resolution, override) {
2417
+ const outcome = override ?? (resolution.ok ? "approved" : resolution.reason === "rejected" ? "rejected" : "failed");
2418
+ this.ctx.emit("cordis/request-run-resolved", {
2419
+ requestId,
2420
+ outcome
2421
+ });
2422
+ }
2423
+ steerRunOutcome(pending, settled) {
2424
+ const agent = this.rootCtx.get("agents")?.get(pending.agentId);
2425
+ if (agent === void 0) return;
2426
+ const plugin = this.registry.get(pending.pluginId);
2427
+ const identity = `${pending.pluginId}/${pending.packageId} (${pending.pluginRunId})`;
2428
+ let text;
2429
+ if (settled.ok) text = `Cordis ${pending.mode} ${identity} completed successfully. currentPackageId is ${settled.currentPackageId ?? pending.packageId}. Continue using the running Plugin.`;
2430
+ else if (settled.reason === "rejected") text = `The user rejected Cordis ${pending.mode} ${identity}. Do not request the same activation again unless the user asks.`;
2431
+ else {
2432
+ const returnedStatus = pending.requiresApproval ? "awaiting-approval" : "starting";
2433
+ text = `Cordis ${pending.mode} ${identity} failed after the runner returned ${returnedStatus}: ${settled.reason}\n${formatErrorDetails(settled)}\ncurrentPackageId: ${plugin?.currentPackageId ?? "none"}\nnextPackageId: ${plugin?.nextPackageId ?? pending.packageId}\nReport the failure to the user; the definition can be managed through the Cordis panel.`;
2434
+ }
2435
+ agent.steer(createUserMessage({
2436
+ content: [{
2437
+ type: "text",
2438
+ text
2439
+ }],
2440
+ source: { kind: "kylin-host-runner" }
2441
+ }));
2442
+ }
2443
+ steerRenderFailure(agent, plugin, definition, pluginRunId, failure) {
2444
+ agent.steer(createUserMessage({
2445
+ content: [{
2446
+ type: "text",
2447
+ text: `Cordis Client UI ${plugin.pluginId}/${definition.packageId} (${pluginRunId}) failed while rendering Slot "${failure.slot}" after activation.\n${formatErrorDetails(failure)}\nentryAbdicated: ${failure.abdicated}\nReport the Client render failure to the user; the definition can be stopped through the Cordis panel.`
2448
+ }],
2449
+ source: { kind: "kylin-host-runner" }
2450
+ }));
2451
+ }
2452
+ steerHostHandlerFailure(plugin, run, method, failure) {
2453
+ const reportKey = `Host\u0000handler\u0000${method}\u0000${failure.message}`;
2454
+ if (!this.claimRuntimeFailure(plugin, run, reportKey)) return;
2455
+ const agent = this.rootCtx.get("agents")?.get(plugin.sessionId);
2456
+ if (agent === void 0) return;
2457
+ agent.steer(createUserMessage({
2458
+ content: [{
2459
+ type: "text",
2460
+ 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. Report the Host handler failure to the user. 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.`
2461
+ }],
2462
+ source: { kind: "kylin-host-runner" }
2463
+ }));
2464
+ }
2465
+ steerGuardFailure(plugin, run, platform, failure) {
2466
+ const reportKey = `${platform}\u0000guard\u0000${failure.message}`;
2467
+ if (!this.claimRuntimeFailure(plugin, run, reportKey)) return;
2468
+ const agent = this.rootCtx.get("agents")?.get(plugin.sessionId);
2469
+ if (agent === void 0) return;
2470
+ agent.steer(createUserMessage({
2471
+ content: [{
2472
+ type: "text",
2473
+ text: `Cordis ${platform} guard rejected runtime code in ${plugin.pluginId}/${run.packageId} (${run.pluginRunId}) after activation.\n${formatErrorDetails(failure)}\nThe Plugin remains running. Report the guard rejection to the user; it can be stopped through the Cordis panel.`
2474
+ }],
2475
+ source: { kind: "kylin-host-runner" }
2476
+ }));
2477
+ }
2478
+ claimRuntimeFailure(plugin, run, key) {
2479
+ const attempt = plugin.latestRun;
2480
+ if (plugin.run !== run || attempt?.pluginRunId !== run.pluginRunId || attempt.status !== "running" && attempt.status !== "waiting") return false;
2481
+ if (run.reportedRuntimeErrors.has(key)) return false;
2482
+ run.reportedRuntimeErrors.add(key);
2483
+ return true;
2484
+ }
2485
+ injectUserRunOutcome(agent, pluginId, settled) {
2486
+ const plugin = this.owned(agent, pluginId);
2487
+ let text;
2488
+ if (settled.ok) text = `The user manually ran Cordis Plugin ${pluginId}, Package ${settled.packageId}, as ${settled.pluginRunId}. The activation succeeded; currentPackageId is ${settled.currentPackageId}.`;
2489
+ else {
2490
+ const attempt = plugin?.latestRun;
2491
+ 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"}`;
2492
+ }
2493
+ this.injectUserContext(agent, text);
2494
+ }
2495
+ injectUserContext(agent, text) {
2496
+ if (this.rootCtx.get("agents")?.get(agent.id) !== agent) return;
2497
+ agent.inject(createUserMessage({
2498
+ content: [{
2499
+ type: "text",
2500
+ text
2501
+ }],
2502
+ source: { kind: "kylin-host-runner" }
2503
+ }));
2504
+ }
2505
+ cancelPending(pluginId, message) {
2506
+ const requestId = this.registry.pendingRequestFor(pluginId);
2507
+ if (requestId === void 0) return;
2508
+ const pending = this.registry.claimRequest(requestId);
2509
+ if (pending === void 0) return;
2510
+ const plugin = this.registry.get(pluginId);
2511
+ if (plugin?.latestRun?.pluginRunId === pending.pluginRunId) {
2512
+ plugin.latestRun.status = "cancelled";
2513
+ plugin.latestRun.error = this.diagnostic(plugin, plugin.latestRun, "approval", message);
2514
+ delete plugin.latestRun.approvalRequestId;
2515
+ delete plugin.latestRun.requiresApproval;
2516
+ }
2517
+ this.announceResolved(requestId, {
2518
+ ok: false,
2519
+ reason: "rejected"
2520
+ }, "cancelled");
2521
+ }
2522
+ createAttempt(plan) {
2523
+ return {
2524
+ pluginRunId: CordisDynamicPluginRunId(this.registry.mintPluginRunId()),
2525
+ packageId: plan.definition.packageId,
2526
+ mode: plan.mode,
2527
+ status: "starting-host",
2528
+ host: {
2529
+ status: plan.definition.hostCode === void 0 ? "absent" : "pending",
2530
+ waitingFor: []
2531
+ },
2532
+ client: {
2533
+ status: plan.definition.clientCode === void 0 ? "absent" : "pending",
2534
+ waitingFor: []
2535
+ }
2536
+ };
2537
+ }
2538
+ failAttempt(plugin, attempt, phase, failure) {
2539
+ attempt.status = "failed";
2540
+ attempt.error = this.diagnostic(plugin, attempt, phase, failure);
2541
+ if (phase.startsWith("host")) attempt.host = {
2542
+ status: "failed",
2543
+ waitingFor: [],
2544
+ error: failure.message
2545
+ };
2546
+ else attempt.client = {
2547
+ status: "failed",
2548
+ waitingFor: [],
2549
+ error: failure.message
2550
+ };
2551
+ }
2552
+ diagnostic(plugin, attempt, phase, failure) {
2553
+ return {
2554
+ phase,
2555
+ ...typeof failure === "string" ? { message: failure } : failure,
2556
+ pluginId: plugin.pluginId,
2557
+ packageId: attempt.packageId,
2558
+ pluginRunId: attempt.pluginRunId
2559
+ };
2560
+ }
2561
+ async retract(plugin) {
2562
+ const run = plugin.run;
2563
+ if (run === void 0) return;
2564
+ delete plugin.run;
2565
+ for (const dispose of run.handlerDisposers.splice(0)) dispose();
2566
+ if (run.fiber !== void 0) await run.fiber.dispose();
2567
+ this.ctx.emit("cordis/dynamic-retract", {
2568
+ pluginId: plugin.pluginId,
2569
+ packageId: run.packageId,
2570
+ pluginRunId: run.pluginRunId
2571
+ });
2572
+ }
2573
+ owned(agent, pluginId) {
2574
+ const plugin = this.registry.get(pluginId);
2575
+ return plugin?.sessionId === agent.id ? plugin : void 0;
2576
+ }
2577
+ requireGroup() {
2578
+ this.group ??= this.rootCtx.plugin({
2579
+ name: "cordis-dynamic",
2580
+ apply: () => {}
2581
+ });
2582
+ return this.group;
2583
+ }
2584
+ };
2585
+ })();
2586
+ function missingFor(ctx, run) {
2587
+ return run.fiber === void 0 ? [] : missingServices(ctx, run.fiber);
2588
+ }
2589
+ function missingPluginMessage(id) {
2590
+ return `no dynamic plugin "${id}" in this process — it may have been removed or lost on QILIN restart`;
2591
+ }
2592
+ function errorDetails(error) {
2593
+ if (typeof error !== "object" || error === null) return { message: String(error) };
2594
+ const message = "message" in error && typeof error.message === "string" ? error.message : Object.prototype.toString.call(error);
2595
+ const stack = "stack" in error && typeof error.stack === "string" ? error.stack : void 0;
2596
+ return {
2597
+ message,
2598
+ ...stack === void 0 ? {} : { stack }
2599
+ };
2600
+ }
2601
+ function formatErrorDetails(failure) {
2602
+ return `message: ${failure.message}` + (failure.stack === void 0 ? "" : `\nstack:\n${failure.stack}`);
2603
+ }
2604
+ function cloneAttempt(attempt) {
2605
+ return {
2606
+ ...attempt,
2607
+ host: {
2608
+ ...attempt.host,
2609
+ waitingFor: [...attempt.host.waitingFor]
2610
+ },
2611
+ client: {
2612
+ ...attempt.client,
2613
+ waitingFor: [...attempt.client.waitingFor]
2614
+ },
2615
+ ...attempt.error === void 0 ? {} : { error: { ...attempt.error } }
2616
+ };
2617
+ }
2618
+ //#endregion
2619
+ export { ApprovalRequestId, CordisDynamicPackageId, CordisDynamicPluginId, CordisDynamicPluginRunId, CordisInspectRegistryService, DynamicCordisRunnerService, DynamicCordisRunnerService as default, HOST_BUILTIN_INSPECTION };