@qilin-agent/kylin-host-runner 0.0.0-stage → 3.1.4

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.
@@ -0,0 +1,1150 @@
1
+ /**
2
+ * Dynamic Cordis Plugin service: immutable package definitions, one active run
3
+ * per Plugin, human-approved Client activation, and Host/Client invocation.
4
+ * @module @qilin-agent/kylin-host-runner
5
+ */
6
+ var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
7
+ var useValue = arguments.length > 2;
8
+ for (var i = 0; i < initializers.length; i++) {
9
+ value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
10
+ }
11
+ return useValue ? value : void 0;
12
+ };
13
+ var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
14
+ function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
15
+ var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
16
+ var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
17
+ var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
18
+ var _, done = false;
19
+ for (var i = decorators.length - 1; i >= 0; i--) {
20
+ var context = {};
21
+ for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
22
+ for (var p in contextIn.access) context.access[p] = contextIn.access[p];
23
+ context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
24
+ var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
25
+ if (kind === "accessor") {
26
+ if (result === void 0) continue;
27
+ if (result === null || typeof result !== "object") throw new TypeError("Object expected");
28
+ if (_ = accept(result.get)) descriptor.get = _;
29
+ if (_ = accept(result.set)) descriptor.set = _;
30
+ if (_ = accept(result.init)) initializers.unshift(_);
31
+ }
32
+ else if (_ = accept(result)) {
33
+ if (kind === "field") initializers.unshift(_);
34
+ else descriptor[key] = _;
35
+ }
36
+ }
37
+ if (target) Object.defineProperty(target, contextIn.name, descriptor);
38
+ done = true;
39
+ };
40
+ import z from '@qilin-agent/schemastery';
41
+ import { createUserMessage } from '@qilin-agent/llm';
42
+ import { TypertRemoteService, Remote } from '@qilin-agent/typert-protocol';
43
+ import { isPlugin, normalizeHandler } from "./guard.js";
44
+ import { CordisInspectRegistryService } from "./inspect-registry.js";
45
+ import { missingServices, startHostHalf } from "./lifecycle.js";
46
+ import { DynamicCordisRegistry } from "./registry.js";
47
+ import { createSandbox, evaluateHostCode, precheckCode } from "./sandbox.js";
48
+ export { CordisInspectRegistryService } from "./inspect-registry.js";
49
+ export { HOST_BUILTIN_INSPECTION } from "./sandbox.js";
50
+ /**
51
+ * Brand a Host-minted Plugin ID.
52
+ * @param id - opaque identifier minted by the Host registry.
53
+ * @returns the branded Plugin identifier.
54
+ */
55
+ export function CordisDynamicPluginId(id) {
56
+ return id;
57
+ }
58
+ /**
59
+ * Brand a Host-minted Package ID.
60
+ * @param id - opaque identifier minted by the Host registry.
61
+ * @returns the branded Package identifier.
62
+ */
63
+ export function CordisDynamicPackageId(id) {
64
+ return id;
65
+ }
66
+ /**
67
+ * Brand a Host-minted Plugin Run ID.
68
+ * @param id - opaque identifier minted by the Host registry.
69
+ * @returns the branded Plugin Run identifier.
70
+ */
71
+ export function CordisDynamicPluginRunId(id) {
72
+ return id;
73
+ }
74
+ /**
75
+ * Brand a Host-minted approval request ID.
76
+ * @param id - opaque identifier minted by the Host registry.
77
+ * @returns the branded approval request identifier.
78
+ */
79
+ export function ApprovalRequestId(id) {
80
+ return id;
81
+ }
82
+ /** Dynamic Plugin registry and Host-half lifecycle. */
83
+ let DynamicCordisRunnerService = (() => {
84
+ let _classSuper = TypertRemoteService;
85
+ let _instanceExtraInitializers = [];
86
+ let _undefineFromPanel_decorators;
87
+ let _runHostHalf_decorators;
88
+ let _getClientCode_decorators;
89
+ let _resolveRequestRun_decorators;
90
+ let _settleUserRun_decorators;
91
+ let _stopFromPanel_decorators;
92
+ let _syncInspectManifest_decorators;
93
+ let _resolveInspectQuery_decorators;
94
+ let _inventory_decorators;
95
+ let _reportRenderFailure_decorators;
96
+ let _reportClientGuardFailure_decorators;
97
+ let _invoke_decorators;
98
+ return class DynamicCordisRunnerService extends _classSuper {
99
+ static {
100
+ const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
101
+ _undefineFromPanel_decorators = [Remote('undefineFromPanel')];
102
+ _runHostHalf_decorators = [Remote('runHostHalf')];
103
+ _getClientCode_decorators = [Remote('getClientCode')];
104
+ _resolveRequestRun_decorators = [Remote('resolveRequestRun')];
105
+ _settleUserRun_decorators = [Remote('settleUserRun')];
106
+ _stopFromPanel_decorators = [Remote('stopFromPanel')];
107
+ _syncInspectManifest_decorators = [Remote('syncInspectManifest')];
108
+ _resolveInspectQuery_decorators = [Remote('resolveInspectQuery')];
109
+ _inventory_decorators = [Remote('inventory')];
110
+ _reportRenderFailure_decorators = [Remote('reportRenderFailure')];
111
+ _reportClientGuardFailure_decorators = [Remote('reportClientGuardFailure')];
112
+ _invoke_decorators = [Remote('invoke')];
113
+ __esDecorate(this, null, _undefineFromPanel_decorators, { kind: "method", name: "undefineFromPanel", static: false, private: false, access: { has: obj => "undefineFromPanel" in obj, get: obj => obj.undefineFromPanel }, metadata: _metadata }, null, _instanceExtraInitializers);
114
+ __esDecorate(this, null, _runHostHalf_decorators, { kind: "method", name: "runHostHalf", static: false, private: false, access: { has: obj => "runHostHalf" in obj, get: obj => obj.runHostHalf }, metadata: _metadata }, null, _instanceExtraInitializers);
115
+ __esDecorate(this, null, _getClientCode_decorators, { kind: "method", name: "getClientCode", static: false, private: false, access: { has: obj => "getClientCode" in obj, get: obj => obj.getClientCode }, metadata: _metadata }, null, _instanceExtraInitializers);
116
+ __esDecorate(this, null, _resolveRequestRun_decorators, { kind: "method", name: "resolveRequestRun", static: false, private: false, access: { has: obj => "resolveRequestRun" in obj, get: obj => obj.resolveRequestRun }, metadata: _metadata }, null, _instanceExtraInitializers);
117
+ __esDecorate(this, null, _settleUserRun_decorators, { kind: "method", name: "settleUserRun", static: false, private: false, access: { has: obj => "settleUserRun" in obj, get: obj => obj.settleUserRun }, metadata: _metadata }, null, _instanceExtraInitializers);
118
+ __esDecorate(this, null, _stopFromPanel_decorators, { kind: "method", name: "stopFromPanel", static: false, private: false, access: { has: obj => "stopFromPanel" in obj, get: obj => obj.stopFromPanel }, metadata: _metadata }, null, _instanceExtraInitializers);
119
+ __esDecorate(this, null, _syncInspectManifest_decorators, { kind: "method", name: "syncInspectManifest", static: false, private: false, access: { has: obj => "syncInspectManifest" in obj, get: obj => obj.syncInspectManifest }, metadata: _metadata }, null, _instanceExtraInitializers);
120
+ __esDecorate(this, null, _resolveInspectQuery_decorators, { kind: "method", name: "resolveInspectQuery", static: false, private: false, access: { has: obj => "resolveInspectQuery" in obj, get: obj => obj.resolveInspectQuery }, metadata: _metadata }, null, _instanceExtraInitializers);
121
+ __esDecorate(this, null, _inventory_decorators, { kind: "method", name: "inventory", static: false, private: false, access: { has: obj => "inventory" in obj, get: obj => obj.inventory }, metadata: _metadata }, null, _instanceExtraInitializers);
122
+ __esDecorate(this, null, _reportRenderFailure_decorators, { kind: "method", name: "reportRenderFailure", static: false, private: false, access: { has: obj => "reportRenderFailure" in obj, get: obj => obj.reportRenderFailure }, metadata: _metadata }, null, _instanceExtraInitializers);
123
+ __esDecorate(this, null, _reportClientGuardFailure_decorators, { kind: "method", name: "reportClientGuardFailure", static: false, private: false, access: { has: obj => "reportClientGuardFailure" in obj, get: obj => obj.reportClientGuardFailure }, metadata: _metadata }, null, _instanceExtraInitializers);
124
+ __esDecorate(this, null, _invoke_decorators, { kind: "method", name: "invoke", static: false, private: false, access: { has: obj => "invoke" in obj, get: obj => obj.invoke }, metadata: _metadata }, null, _instanceExtraInitializers);
125
+ if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
126
+ }
127
+ static inject = ['tools'];
128
+ static Config = z.object({
129
+ vmTimeoutMs: z.number().min(1).default(5000),
130
+ clientInspectTimeoutMs: z.number().step(1).min(1).max(2_147_483_647).default(10_000),
131
+ });
132
+ rootCtx = __runInitializers(this, _instanceExtraInitializers);
133
+ registry = new DynamicCordisRegistry();
134
+ inspectRegistry;
135
+ starting = new Map();
136
+ resolved;
137
+ group;
138
+ /** Create the service under the Host composition. */
139
+ constructor(ctx, config) {
140
+ super(ctx, 'dynamicCordisRunner');
141
+ this.rootCtx = ctx;
142
+ this.resolved = config;
143
+ this.inspectRegistry = new CordisInspectRegistryService(ctx, this.resolved.clientInspectTimeoutMs);
144
+ }
145
+ /**
146
+ * Define a new Plugin's first Package or append a Package to an existing Plugin.
147
+ * @param request - Session ownership, Plugin selection, metadata, and source code.
148
+ * @returns Host-minted Plugin and Package identities with declared-half metadata.
149
+ */
150
+ define(request) {
151
+ const name = request.name.trim();
152
+ const purpose = request.purpose.trim();
153
+ if (name.length === 0)
154
+ throw new Error('cordis_define needs a non-empty `name`');
155
+ if (purpose.length === 0)
156
+ throw new Error('cordis_define needs a non-empty `purpose`');
157
+ if (request.code.host === undefined && request.code.client === undefined) {
158
+ throw new Error('cordis_define needs `code.host`, `code.client`, or both');
159
+ }
160
+ if (request.code.host !== undefined)
161
+ precheckCode(request.code.host, 'code.host');
162
+ if (request.code.client !== undefined)
163
+ precheckCode(request.code.client, 'code.client');
164
+ let plugin;
165
+ if (request.plugin.kind === 'new') {
166
+ const prefix = request.plugin.idPrefix.trim();
167
+ if (!/^[a-z]{3,6}$/.test(prefix)) {
168
+ throw new Error('cordis_define `plugin.idPrefix` must contain 3–6 lowercase English letters');
169
+ }
170
+ const pluginId = CordisDynamicPluginId(this.registry.mintPluginId(prefix));
171
+ plugin = {
172
+ pluginId,
173
+ sessionId: request.sessionId,
174
+ packages: new Map(),
175
+ approvedClientPackages: new Set(),
176
+ clientVersionUpdatesApproved: false,
177
+ };
178
+ this.registry.add(plugin);
179
+ }
180
+ else {
181
+ const found = this.registry.get(request.plugin.pluginId);
182
+ if (found === undefined || found.sessionId !== request.sessionId) {
183
+ throw new Error(missingPluginMessage(request.plugin.pluginId));
184
+ }
185
+ plugin = found;
186
+ }
187
+ const packageId = CordisDynamicPackageId(this.registry.mintPackageId());
188
+ const definition = {
189
+ packageId,
190
+ name,
191
+ purpose,
192
+ ...request.code.host === undefined ? {} : { hostCode: request.code.host },
193
+ ...request.code.client === undefined ? {} : { clientCode: request.code.client },
194
+ };
195
+ plugin.packages.set(packageId, definition);
196
+ return {
197
+ pluginId: plugin.pluginId,
198
+ packageId,
199
+ name,
200
+ purpose,
201
+ hasHostHalf: definition.hostCode !== undefined,
202
+ hasClientHalf: definition.clientCode !== undefined,
203
+ };
204
+ }
205
+ /**
206
+ * Remove a Plugin, its active run, and all immutable Packages.
207
+ * @param agent - Agent whose Session must own the Plugin.
208
+ * @param pluginId - Stable Plugin identity to remove.
209
+ * @returns Whether removal succeeded and whether it stopped an active run.
210
+ */
211
+ async undefine(agent, pluginId) {
212
+ const plugin = this.owned(agent, pluginId);
213
+ if (plugin === undefined)
214
+ return { ok: false, reason: 'plugin-missing', message: missingPluginMessage(pluginId) };
215
+ const wasRunning = plugin.run !== undefined;
216
+ this.cancelPending(pluginId, `dynamic plugin "${pluginId}" was removed before approval`);
217
+ if (plugin.run !== undefined)
218
+ await this.retract(plugin);
219
+ this.registry.delete(pluginId);
220
+ return { ok: true, wasRunning };
221
+ }
222
+ /**
223
+ * Remove a Plugin from the user panel and queue the resulting state change for the model's next step.
224
+ * @param agent - Agent whose Session owns the Plugin and receives the context.
225
+ * @param pluginId - Stable Plugin identity to remove.
226
+ * @returns Whether removal succeeded and whether it stopped an active run.
227
+ */
228
+ async undefineFromPanel(agent, pluginId) {
229
+ const result = await this.undefine(agent, pluginId);
230
+ if (result.ok) {
231
+ this.injectUserContext(agent, `The user removed Cordis Plugin ${pluginId} and all of its Packages. The Plugin no longer exists.`);
232
+ }
233
+ return result;
234
+ }
235
+ /**
236
+ * Start or update one Package for a model tool call. An unauthorized Client
237
+ * Package waits for approval; Plugin-wide authorization covers later versions.
238
+ * @param agent - Agent whose Session must own the Plugin.
239
+ * @param pluginId - Stable Plugin identity to activate.
240
+ * @param packageId - Immutable Package version to activate.
241
+ * @param mode - Whether to run the current version or switch versions.
242
+ * @param signal - Tool-call cancellation signal while the activation request is being created.
243
+ * @returns The successful activation identity or an actionable refusal.
244
+ */
245
+ async run(agent, pluginId, packageId, mode, signal) {
246
+ const plan = this.resolvePlan(agent, pluginId, packageId, mode);
247
+ if (!plan.ok)
248
+ return plan.response;
249
+ if (signal?.aborted === true) {
250
+ return {
251
+ ok: false,
252
+ reason: 'cancelled',
253
+ message: `the run request for dynamic plugin "${pluginId}" was cancelled before activation`,
254
+ };
255
+ }
256
+ if (this.registry.pendingRequestFor(pluginId) !== undefined) {
257
+ return { ok: false, reason: 'transition-in-flight', message: `dynamic plugin "${pluginId}" already has a pending run request` };
258
+ }
259
+ const attempt = this.createAttempt(plan);
260
+ plan.plugin.nextPackageId = packageId;
261
+ plan.plugin.latestRun = attempt;
262
+ if (plan.definition.clientCode === undefined) {
263
+ const started = await this.activate(plan, undefined, false, attempt);
264
+ if (started.ok)
265
+ return this.runResponse(plan.plugin, started);
266
+ this.failAttempt(plan.plugin, attempt, 'host-load', started);
267
+ return { ...started, reason: 'host-half-failed' };
268
+ }
269
+ const requestId = ApprovalRequestId(this.registry.mintApprovalRequestId());
270
+ const requiresApproval = !plan.plugin.clientVersionUpdatesApproved
271
+ && !plan.plugin.approvedClientPackages.has(packageId);
272
+ attempt.approvalRequestId = requestId;
273
+ attempt.requiresApproval = requiresApproval;
274
+ attempt.status = requiresApproval ? 'awaiting-approval' : 'starting-host';
275
+ this.registry.armRequest(requestId, {
276
+ agentId: agent.id,
277
+ pluginId,
278
+ packageId,
279
+ pluginRunId: attempt.pluginRunId,
280
+ mode,
281
+ requiresApproval,
282
+ });
283
+ this.ctx.emit('cordis/request-run', {
284
+ requestId,
285
+ agentId: agent.id,
286
+ pluginId,
287
+ packageId,
288
+ mode,
289
+ name: plan.definition.name,
290
+ purpose: plan.definition.purpose,
291
+ requiresApproval,
292
+ });
293
+ return {
294
+ ok: true,
295
+ status: requiresApproval ? 'awaiting-approval' : 'starting',
296
+ pluginId,
297
+ packageId,
298
+ pluginRunId: attempt.pluginRunId,
299
+ mode,
300
+ waitingFor: [],
301
+ ...plan.plugin.currentPackageId === undefined ? {} : { currentPackageId: plan.plugin.currentPackageId },
302
+ nextPackageId: packageId,
303
+ };
304
+ }
305
+ /**
306
+ * Start Host code for an approved request or a direct panel gesture.
307
+ * @param agent - Agent whose Session must own the Plugin.
308
+ * @param pluginId - Stable Plugin identity to activate.
309
+ * @param packageId - Immutable Package version to activate.
310
+ * @param mode - Whether to run the current version or switch versions.
311
+ * @param requestId - Model-driven request identity, or null for a direct user gesture.
312
+ * @param approveFutureVersions - Whether this approval covers later Packages of the same Plugin.
313
+ * @returns The exact Host activation or a failure message.
314
+ */
315
+ async runHostHalf(agent, pluginId, packageId, mode, requestId, approveFutureVersions) {
316
+ const plan = this.resolvePlan(agent, pluginId, packageId, mode, requestId === null);
317
+ if (!plan.ok)
318
+ return { ok: false, message: plan.response.message };
319
+ let attempt;
320
+ if (requestId !== null) {
321
+ const pending = this.registry.peekRequest(requestId);
322
+ if (pending === undefined || pending.pluginId !== pluginId || pending.packageId !== packageId || pending.mode !== mode) {
323
+ return { ok: false, message: `run request "${requestId}" does not authorize ${pluginId}/${packageId}` };
324
+ }
325
+ const latest = plan.plugin.latestRun;
326
+ const expectedStatus = pending.requiresApproval ? 'awaiting-approval' : 'starting-host';
327
+ if (latest === undefined || latest.pluginRunId !== pending.pluginRunId
328
+ || (latest.status !== expectedStatus && (!pending.requiresApproval && latest.status !== 'client-pending'))) {
329
+ return { ok: false, message: `run request "${requestId}" no longer identifies the latest run of ${pluginId}` };
330
+ }
331
+ attempt = latest;
332
+ if (pending.requiresApproval) {
333
+ plan.plugin.approvedClientPackages.add(packageId);
334
+ if (approveFutureVersions)
335
+ plan.plugin.clientVersionUpdatesApproved = true;
336
+ }
337
+ }
338
+ else {
339
+ const pending = this.registry.pendingRequestFor(pluginId);
340
+ if (pending !== undefined)
341
+ return { ok: false, message: `dynamic plugin "${pluginId}" has pending run request ${pending}` };
342
+ const attached = plan.plugin.run?.packageId === packageId
343
+ && plan.plugin.latestRun?.pluginRunId === plan.plugin.run.pluginRunId
344
+ ? plan.plugin.latestRun
345
+ : undefined;
346
+ attempt = attached ?? this.createAttempt(plan);
347
+ if (attached === undefined) {
348
+ plan.plugin.nextPackageId = packageId;
349
+ plan.plugin.latestRun = attempt;
350
+ }
351
+ if (plan.definition.clientCode !== undefined)
352
+ plan.plugin.approvedClientPackages.add(packageId);
353
+ }
354
+ const attaching = attempt.pluginRunId === plan.plugin.run?.pluginRunId;
355
+ if (!attaching) {
356
+ attempt.status = 'starting-host';
357
+ if (attempt.host.status !== 'absent')
358
+ attempt.host = { status: 'pending', waitingFor: [] };
359
+ }
360
+ const started = await this.activate(plan, requestId ?? undefined, attaching, attempt);
361
+ if (!started.ok)
362
+ this.failAttempt(plan.plugin, attempt, 'host-load', started);
363
+ return started;
364
+ }
365
+ /**
366
+ * Fetch Client code for the exact active run.
367
+ * @param agent - Agent whose Session must own the Plugin.
368
+ * @param pluginId - Stable Plugin identity to read.
369
+ * @param pluginRunId - Exact active run authorized to receive source.
370
+ * @returns Client source and its Plugin, Package, and run identities.
371
+ */
372
+ getClientCode(agent, pluginId, pluginRunId) {
373
+ const plugin = this.owned(agent, pluginId);
374
+ if (plugin === undefined)
375
+ throw new Error(missingPluginMessage(pluginId));
376
+ const run = plugin.run;
377
+ if (run === undefined || run.pluginRunId !== pluginRunId) {
378
+ throw new Error(`dynamic plugin "${pluginId}" is not running activation "${pluginRunId}"`);
379
+ }
380
+ const definition = plugin.packages.get(run.packageId);
381
+ if (definition?.clientCode === undefined)
382
+ throw new Error(`package "${run.packageId}" has no Client half`);
383
+ return {
384
+ code: definition.clientCode,
385
+ name: definition.name,
386
+ pluginId,
387
+ packageId: run.packageId,
388
+ pluginRunId,
389
+ };
390
+ }
391
+ /**
392
+ * Resolve one model-driven Client activation request.
393
+ * @param requestId - Request identity to settle once.
394
+ * @param resolution - Browser refusal or exact Client activation result.
395
+ * @returns Whether the still-pending request accepted this resolution.
396
+ */
397
+ async resolveRequestRun(requestId, resolution) {
398
+ const pending = this.registry.peekRequest(requestId);
399
+ if (pending === undefined)
400
+ return { accepted: false };
401
+ const plugin = this.registry.get(pending.pluginId);
402
+ if (resolution.ok && plugin?.run?.pluginRunId !== resolution.pluginRunId)
403
+ return { accepted: false };
404
+ if (!resolution.ok && resolution.pluginRunId !== undefined
405
+ && plugin?.run?.pluginRunId !== resolution.pluginRunId)
406
+ return { accepted: false };
407
+ this.registry.claimRequest(requestId);
408
+ const settled = await this.settleActivation(plugin, resolution, requestId);
409
+ this.announceResolved(requestId, resolution, pending.requiresApproval ? undefined : 'completed');
410
+ this.steerRunOutcome(pending, settled);
411
+ return { accepted: true };
412
+ }
413
+ /**
414
+ * Settle a direct panel run after this page loaded or failed its Client half.
415
+ * @param agent - Agent whose Session must own the Plugin.
416
+ * @param pluginId - Stable Plugin identity being settled.
417
+ * @param resolution - Exact Client activation result from the acting page.
418
+ * @returns The committed activation or its failure.
419
+ */
420
+ async settleUserRun(agent, pluginId, resolution) {
421
+ const plugin = this.owned(agent, pluginId);
422
+ if (plugin === undefined)
423
+ return { ok: false, reason: 'plugin-missing', message: missingPluginMessage(pluginId) };
424
+ const settled = await this.settleActivation(plugin, resolution);
425
+ this.injectUserRunOutcome(agent, pluginId, settled);
426
+ return settled;
427
+ }
428
+ /**
429
+ * Stop the active run while retaining every Package version.
430
+ * @param agent - Agent whose Session must own the Plugin.
431
+ * @param pluginId - Stable Plugin identity to stop.
432
+ * @returns Success or the reason no run was stopped.
433
+ */
434
+ async stop(agent, pluginId) {
435
+ const plugin = this.owned(agent, pluginId);
436
+ if (plugin === undefined)
437
+ return { ok: false, reason: 'plugin-missing', message: missingPluginMessage(pluginId) };
438
+ const pending = this.registry.pendingRequestFor(pluginId);
439
+ if (plugin.run === undefined && pending === undefined) {
440
+ return { ok: false, reason: 'not-running', message: `dynamic plugin "${pluginId}" is not running` };
441
+ }
442
+ if (pending !== undefined)
443
+ this.cancelPending(pluginId, `dynamic plugin "${pluginId}" was stopped before approval`);
444
+ if (plugin.run !== undefined)
445
+ await this.retract(plugin);
446
+ if (plugin.latestRun !== undefined) {
447
+ plugin.latestRun.status = 'stopped';
448
+ if (plugin.latestRun.host.status !== 'absent')
449
+ plugin.latestRun.host = { status: 'stopped', waitingFor: [] };
450
+ if (plugin.latestRun.client.status !== 'absent')
451
+ plugin.latestRun.client = { status: 'stopped', waitingFor: [] };
452
+ }
453
+ return { ok: true };
454
+ }
455
+ /**
456
+ * Stop a Plugin from the user panel and queue the resulting state change for the model's next step.
457
+ * @param agent - Agent whose Session owns the Plugin and receives the context.
458
+ * @param pluginId - Stable Plugin identity to stop.
459
+ * @returns Success or the reason no run was stopped.
460
+ */
461
+ async stopFromPanel(agent, pluginId) {
462
+ const result = await this.stop(agent, pluginId);
463
+ if (!result.ok)
464
+ return result;
465
+ const plugin = this.owned(agent, pluginId);
466
+ this.injectUserContext(agent, `The user stopped Cordis Plugin ${pluginId}. Its Packages remain defined; currentPackageId is `
467
+ + `${plugin?.currentPackageId ?? 'none'}.`);
468
+ return result;
469
+ }
470
+ /**
471
+ * Replace the Host mirror of the Client inspect provider directory.
472
+ * @param providers - complete Client provider manifest.
473
+ * @returns null after accepting the manifest.
474
+ */
475
+ syncInspectManifest(providers) {
476
+ this.inspectRegistry.syncClientManifest(providers);
477
+ return null;
478
+ }
479
+ /**
480
+ * Submit a Client inspect result or failure for a pending query.
481
+ * @param agent - Session that owns the query.
482
+ * @param requestId - exact pending query identity.
483
+ * @param resolution - provider result or structured refusal.
484
+ * @returns acknowledgement with accepted true only for a valid success that settles the query;
485
+ * pending-query failures return { accepted: false } and retain only the first diagnostic.
486
+ */
487
+ resolveInspectQuery(agent, requestId, resolution) {
488
+ return this.inspectRegistry.resolveClientQuery(agent, requestId, resolution);
489
+ }
490
+ /**
491
+ * Frame-wide inventory, grouped as one row per stable Plugin.
492
+ * @returns Source-free metadata for every process-local Plugin.
493
+ */
494
+ /* jscpd:ignore-start */
495
+ inventory() {
496
+ return this.registry.all().map(plugin => ({
497
+ pluginId: plugin.pluginId,
498
+ agentId: plugin.sessionId,
499
+ packages: [...plugin.packages.values()].map(definition => ({
500
+ packageId: definition.packageId,
501
+ name: definition.name,
502
+ purpose: definition.purpose,
503
+ hasHostHalf: definition.hostCode !== undefined,
504
+ hasClientHalf: definition.clientCode !== undefined,
505
+ })),
506
+ ...plugin.currentPackageId === undefined ? {} : { currentPackageId: plugin.currentPackageId },
507
+ ...plugin.nextPackageId === undefined ? {} : { nextPackageId: plugin.nextPackageId },
508
+ ...plugin.run === undefined ? {} : {
509
+ activeRun: { pluginRunId: plugin.run.pluginRunId, packageId: plugin.run.packageId },
510
+ },
511
+ ...plugin.latestRun === undefined ? {} : { latestRun: cloneAttempt(plugin.latestRun) },
512
+ }));
513
+ }
514
+ /* jscpd:ignore-end */
515
+ /**
516
+ * Read one Session's Host-rich state for inspection and result rendering.
517
+ * @param agent - Agent whose Session selects visible Plugins.
518
+ * @returns Plugin versions, active runs, Host fibers, and render failures.
519
+ */
520
+ snapshot(agent) {
521
+ return this.registry.ofSession(agent.id).map(plugin => ({
522
+ pluginId: plugin.pluginId,
523
+ ...plugin.currentPackageId === undefined ? {} : { currentPackageId: plugin.currentPackageId },
524
+ ...plugin.nextPackageId === undefined ? {} : { nextPackageId: plugin.nextPackageId },
525
+ packages: [...plugin.packages.values()].map(definition => ({
526
+ packageId: definition.packageId,
527
+ name: definition.name,
528
+ purpose: definition.purpose,
529
+ hasHostHalf: definition.hostCode !== undefined,
530
+ hasClientHalf: definition.clientCode !== undefined,
531
+ })),
532
+ ...plugin.run === undefined ? {} : {
533
+ activeRun: {
534
+ pluginRunId: plugin.run.pluginRunId,
535
+ packageId: plugin.run.packageId,
536
+ ...plugin.run.fiber === undefined ? {} : { fiber: plugin.run.fiber },
537
+ handlers: [...plugin.run.handlers.keys()],
538
+ ...plugin.run.renderFailure === undefined ? {} : { renderFailure: plugin.run.renderFailure },
539
+ },
540
+ },
541
+ ...plugin.latestRun === undefined ? {} : { latestRun: cloneAttempt(plugin.latestRun) },
542
+ }));
543
+ }
544
+ /**
545
+ * Read source-free context for an explicit `@pluginId` user gesture.
546
+ * @param agent - Agent whose Session must own the Plugin.
547
+ * @param pluginId - Stable Plugin identity referenced by the user.
548
+ * @returns The preferred modification base, or undefined when unavailable.
549
+ */
550
+ reference(agent, pluginId) {
551
+ const plugin = this.owned(agent, pluginId);
552
+ if (plugin === undefined)
553
+ return undefined;
554
+ const packageId = plugin.nextPackageId
555
+ ?? plugin.currentPackageId
556
+ ?? [...plugin.packages.keys()].at(-1);
557
+ if (packageId === undefined)
558
+ return undefined;
559
+ const definition = plugin.packages.get(packageId);
560
+ if (definition === undefined)
561
+ return undefined;
562
+ return {
563
+ pluginId,
564
+ packageId,
565
+ name: definition.name,
566
+ purpose: definition.purpose,
567
+ ...plugin.currentPackageId === undefined ? {} : { currentPackageId: plugin.currentPackageId },
568
+ ...plugin.nextPackageId === undefined ? {} : { nextPackageId: plugin.nextPackageId },
569
+ ...plugin.run === undefined ? {} : {
570
+ activeRun: { pluginRunId: plugin.run.pluginRunId, packageId: plugin.run.packageId },
571
+ },
572
+ ...plugin.latestRun === undefined ? {} : { latestRun: cloneAttempt(plugin.latestRun) },
573
+ };
574
+ }
575
+ /**
576
+ * List source-free Plugin summaries owned by one Session.
577
+ * @param agent - Agent whose Session selects visible Plugins.
578
+ * @returns one summary per Plugin in creation order.
579
+ */
580
+ listPlugins(agent) {
581
+ return this.registry.ofSession(agent.id).map(plugin => this.inspectPlugin(agent, plugin.pluginId));
582
+ }
583
+ /**
584
+ * Inspect one Plugin without returning Package source.
585
+ * @param agent - Agent whose Session must own the Plugin.
586
+ * @param pluginId - stable Plugin identity.
587
+ * @returns version pointers, latest run, and all Package summaries.
588
+ */
589
+ inspectPlugin(agent, pluginId) {
590
+ const plugin = this.owned(agent, pluginId);
591
+ if (plugin === undefined)
592
+ throw new Error(missingPluginMessage(pluginId));
593
+ const reference = this.reference(agent, pluginId);
594
+ if (reference === undefined)
595
+ throw new Error(`dynamic plugin "${pluginId}" has no package`);
596
+ return {
597
+ ...reference,
598
+ packages: [...plugin.packages.values()].map(definition => ({
599
+ packageId: definition.packageId,
600
+ name: definition.name,
601
+ purpose: definition.purpose,
602
+ hasHostHalf: definition.hostCode !== undefined,
603
+ hasClientHalf: definition.clientCode !== undefined,
604
+ })),
605
+ };
606
+ }
607
+ /**
608
+ * Read one exact immutable Package and its Host and Client source.
609
+ * @param agent - Agent whose Session must own the Plugin.
610
+ * @param pluginId - Stable Plugin identity that owns the Package.
611
+ * @param packageId - Exact immutable Package identity to inspect.
612
+ * @returns Package metadata, source, and the Plugin's lifecycle pointers.
613
+ */
614
+ inspectPackage(agent, pluginId, packageId) {
615
+ const plugin = this.owned(agent, pluginId);
616
+ if (plugin === undefined)
617
+ throw new Error(missingPluginMessage(pluginId));
618
+ const definition = plugin.packages.get(packageId);
619
+ if (definition === undefined) {
620
+ throw new Error(`dynamic package "${packageId}" does not exist on plugin "${pluginId}"`);
621
+ }
622
+ return {
623
+ pluginId,
624
+ packageId,
625
+ name: definition.name,
626
+ purpose: definition.purpose,
627
+ code: {
628
+ ...definition.hostCode === undefined ? {} : { host: definition.hostCode },
629
+ ...definition.clientCode === undefined ? {} : { client: definition.clientCode },
630
+ },
631
+ /* jscpd:ignore-start */
632
+ ...plugin.currentPackageId === undefined ? {} : { currentPackageId: plugin.currentPackageId },
633
+ ...plugin.nextPackageId === undefined ? {} : { nextPackageId: plugin.nextPackageId },
634
+ ...plugin.run === undefined ? {} : {
635
+ activeRun: { pluginRunId: plugin.run.pluginRunId, packageId: plugin.run.packageId },
636
+ },
637
+ ...plugin.latestRun === undefined ? {} : { latestRun: cloneAttempt(plugin.latestRun) },
638
+ /* jscpd:ignore-end */
639
+ };
640
+ }
641
+ /**
642
+ * Record a post-load render failure for the exact active run.
643
+ * @param agent - Agent whose Session must own the Plugin.
644
+ * @param pluginId - Stable Plugin identity that rendered.
645
+ * @param pluginRunId - Exact active run that produced the failure.
646
+ * @param failure - Slot, message, and entry-retirement result.
647
+ * @returns Null after recording or ignoring a stale report.
648
+ */
649
+ async reportRenderFailure(agent, pluginId, pluginRunId, failure) {
650
+ const plugin = this.owned(agent, pluginId);
651
+ if (plugin?.run?.pluginRunId === pluginRunId) {
652
+ const run = plugin.run;
653
+ const definition = plugin.packages.get(plugin.run.packageId);
654
+ const shouldSteer = run.renderFailure === undefined;
655
+ run.renderFailure = failure;
656
+ const attempt = plugin.latestRun;
657
+ if (attempt?.pluginRunId === pluginRunId) {
658
+ attempt.error = this.diagnostic(plugin, attempt, 'client-render', failure);
659
+ attempt.client = { status: 'failed', waitingFor: attempt.client.waitingFor, error: failure.message };
660
+ attempt.status = 'failed';
661
+ }
662
+ if (definition !== undefined && shouldSteer) {
663
+ this.steerRenderFailure(agent, plugin, definition, pluginRunId, failure);
664
+ }
665
+ }
666
+ return await Promise.resolve(null);
667
+ }
668
+ /**
669
+ * Report a Client guard rejection that happened after the Package completed activation.
670
+ * @param agent - Agent whose Session must own the Plugin.
671
+ * @param pluginId - Stable Plugin identity whose Client code was rejected.
672
+ * @param pluginRunId - Exact active run that produced the rejection.
673
+ * @param failure - Original guard message and stack.
674
+ * @returns Null after reporting or ignoring a stale/startup failure.
675
+ */
676
+ async reportClientGuardFailure(agent, pluginId, pluginRunId, failure) {
677
+ const plugin = this.owned(agent, pluginId);
678
+ const run = plugin?.run;
679
+ if (plugin !== undefined && run?.pluginRunId === pluginRunId) {
680
+ this.steerGuardFailure(plugin, run, 'Client', failure);
681
+ }
682
+ return await Promise.resolve(null);
683
+ }
684
+ /**
685
+ * Invoke an active Host method while rejecting stale Client runs.
686
+ * @param pluginId - Stable Plugin identity that owns the method.
687
+ * @param pluginRunId - Exact active run authorizing the call.
688
+ * @param method - Registered Host handler name.
689
+ * @param args - JSON argument delivered to the handler.
690
+ * @returns The JSON result or a typed invocation failure.
691
+ */
692
+ async invoke(pluginId, pluginRunId, method, args) {
693
+ const plugin = this.registry.get(pluginId);
694
+ if (plugin === undefined || plugin.run === undefined) {
695
+ return { ok: false, code: 'plugin-not-running', message: `dynamic plugin "${pluginId}" is not running` };
696
+ }
697
+ const run = plugin.run;
698
+ if (run.pluginRunId !== pluginRunId) {
699
+ return { ok: false, code: 'stale-run', message: `activation "${pluginRunId}" is no longer active` };
700
+ }
701
+ const handler = run.handlers.get(method);
702
+ if (handler === undefined) {
703
+ return { ok: false, code: 'method-not-found', message: `dynamic plugin "${pluginId}" registered no Host method "${method}"` };
704
+ }
705
+ try {
706
+ return { ok: true, value: await handler(args) };
707
+ }
708
+ catch (error) {
709
+ const failure = errorDetails(error);
710
+ this.steerHostHandlerFailure(plugin, run, method, failure);
711
+ return { ok: false, code: 'handler-error', ...failure };
712
+ }
713
+ }
714
+ resolvePlan(agent, pluginId, packageId, mode, allowActiveAttach = false) {
715
+ const plugin = this.owned(agent, pluginId);
716
+ if (plugin === undefined)
717
+ return { ok: false, response: { ok: false, reason: 'plugin-missing', message: missingPluginMessage(pluginId) } };
718
+ const definition = plugin.packages.get(packageId);
719
+ if (definition === undefined) {
720
+ return { ok: false, response: { ok: false, reason: 'package-missing', message: `plugin "${pluginId}" has no package "${packageId}"` } };
721
+ }
722
+ const current = plugin.currentPackageId;
723
+ if (mode === 'update' && (current === undefined || current === packageId)) {
724
+ return {
725
+ ok: false,
726
+ response: {
727
+ ok: false,
728
+ reason: 'invalid-mode',
729
+ message: current === undefined
730
+ ? `plugin "${pluginId}" has no successful version yet; start "${packageId}" with mode "run"`
731
+ : `package "${packageId}" is already current; use mode "run"`,
732
+ },
733
+ };
734
+ }
735
+ if (mode === 'run' && current !== undefined && current !== packageId) {
736
+ return {
737
+ ok: false,
738
+ response: {
739
+ ok: false,
740
+ reason: 'invalid-mode',
741
+ message: `package "${packageId}" differs from current "${current}"; use mode "update"`,
742
+ },
743
+ };
744
+ }
745
+ if (!allowActiveAttach && this.starting.has(pluginId)) {
746
+ return { ok: false, response: { ok: false, reason: 'transition-in-flight', message: `plugin "${pluginId}" is already starting` } };
747
+ }
748
+ return { ok: true, plugin, definition, mode };
749
+ }
750
+ activate(plan, requestId, allowActiveAttach, attempt) {
751
+ const inFlight = this.starting.get(plan.plugin.pluginId);
752
+ if (inFlight !== undefined)
753
+ return inFlight;
754
+ const starting = this.startFresh(plan, requestId, allowActiveAttach, attempt);
755
+ this.starting.set(plan.plugin.pluginId, starting);
756
+ return starting.finally(() => { this.starting.delete(plan.plugin.pluginId); });
757
+ }
758
+ async startFresh(plan, requestId, allowActiveAttach, attempt) {
759
+ const { plugin, definition, mode } = plan;
760
+ if (allowActiveAttach
761
+ && plugin.run?.packageId === definition.packageId
762
+ && plugin.run.pluginRunId === attempt.pluginRunId) {
763
+ return {
764
+ ok: true,
765
+ pluginId: plugin.pluginId,
766
+ packageId: definition.packageId,
767
+ pluginRunId: plugin.run.pluginRunId,
768
+ waitingFor: missingFor(this.ctx, plugin.run),
769
+ startedHere: false,
770
+ };
771
+ }
772
+ if (plugin.run !== undefined)
773
+ await this.retract(plugin);
774
+ if (mode === 'update' || plugin.currentPackageId === undefined)
775
+ plugin.nextPackageId = definition.packageId;
776
+ const run = {
777
+ pluginRunId: attempt.pluginRunId,
778
+ packageId: definition.packageId,
779
+ handlers: new Map(),
780
+ handlerDisposers: [],
781
+ reportedRuntimeErrors: new Set(),
782
+ ...requestId === undefined ? {} : { startedForRequest: requestId },
783
+ };
784
+ if (definition.hostCode !== undefined) {
785
+ const failure = await this.startHost(plugin, definition.hostCode, run);
786
+ if (failure !== undefined)
787
+ return { ok: false, ...failure };
788
+ }
789
+ plugin.run = run;
790
+ this.ctx.emit('cordis/dynamic-package', {
791
+ pluginId: plugin.pluginId,
792
+ packageId: definition.packageId,
793
+ pluginRunId: run.pluginRunId,
794
+ name: definition.name,
795
+ });
796
+ attempt.host = {
797
+ status: run.fiber === undefined ? 'absent' : missingFor(this.ctx, run).length === 0 ? 'running' : 'waiting',
798
+ waitingFor: missingFor(this.ctx, run),
799
+ };
800
+ if (definition.clientCode === undefined) {
801
+ this.commitActivation(plugin, run);
802
+ }
803
+ else {
804
+ attempt.status = 'client-pending';
805
+ attempt.client = { status: 'pending', waitingFor: [] };
806
+ }
807
+ return {
808
+ ok: true,
809
+ pluginId: plugin.pluginId,
810
+ packageId: definition.packageId,
811
+ pluginRunId: run.pluginRunId,
812
+ waitingFor: missingFor(this.ctx, run),
813
+ startedHere: true,
814
+ };
815
+ }
816
+ async startHost(plugin, hostCode, run) {
817
+ const handle = (method, fn) => {
818
+ const normalized = normalizeHandler(method, fn);
819
+ run.handlers.set(normalized.method, normalized.handler);
820
+ const dispose = () => {
821
+ if (run.handlers.get(normalized.method) === normalized.handler)
822
+ run.handlers.delete(normalized.method);
823
+ };
824
+ run.handlerDisposers.push(dispose);
825
+ return dispose;
826
+ };
827
+ try {
828
+ const sandbox = createSandbox(plugin.pluginId, { handle });
829
+ const evaluated = await evaluateHostCode(sandbox, hostCode, plugin.pluginId, this.resolved.vmTimeoutMs);
830
+ if (!isPlugin(evaluated)) {
831
+ throw new Error(evaluated === undefined
832
+ ? 'the Host half returned `undefined` — did you forget `return`?'
833
+ : 'the Host half must return a Plugin function or an object with apply(ctx)');
834
+ }
835
+ run.fiber = await startHostHalf(this.requireGroup(), evaluated, (error) => { this.steerGuardFailure(plugin, run, 'Host', errorDetails(error)); });
836
+ return undefined;
837
+ }
838
+ catch (error) {
839
+ for (const dispose of run.handlerDisposers.splice(0))
840
+ dispose();
841
+ return errorDetails(error);
842
+ }
843
+ }
844
+ async settleActivation(plugin, resolution, requestId) {
845
+ if (plugin === undefined)
846
+ return { ok: false, reason: 'plugin-missing', message: 'the dynamic plugin was removed during activation' };
847
+ const attempt = plugin.latestRun;
848
+ if (!resolution.ok) {
849
+ if (resolution.reason === 'rejected') {
850
+ if (attempt !== undefined) {
851
+ attempt.status = 'rejected';
852
+ attempt.error = this.diagnostic(plugin, attempt, 'approval', resolution.message ?? 'the run request was declined');
853
+ attempt.client = { status: 'stopped', waitingFor: [] };
854
+ }
855
+ return { ok: false, reason: 'rejected', message: resolution.message ?? 'the run request was declined' };
856
+ }
857
+ const run = plugin.run;
858
+ const ownsRun = run !== undefined
859
+ && resolution.pluginRunId === run.pluginRunId
860
+ && (requestId === undefined || run.startedForRequest === requestId)
861
+ && resolution.startedHere !== false;
862
+ if (ownsRun)
863
+ await this.retract(plugin);
864
+ if (attempt !== undefined && (resolution.pluginRunId === undefined || attempt.pluginRunId === resolution.pluginRunId)) {
865
+ this.failAttempt(plugin, attempt, resolution.reason === 'host-half-failed' ? 'host-apply' : 'client-apply', {
866
+ message: resolution.message ?? resolution.reason,
867
+ ...resolution.stack === undefined ? {} : { stack: resolution.stack },
868
+ });
869
+ }
870
+ return {
871
+ ok: false,
872
+ reason: resolution.reason,
873
+ message: resolution.message ?? resolution.reason,
874
+ ...resolution.stack === undefined ? {} : { stack: resolution.stack },
875
+ };
876
+ }
877
+ const run = plugin.run;
878
+ if (run === undefined || run.pluginRunId !== resolution.pluginRunId) {
879
+ return { ok: false, reason: 'client-half-failed', message: `activation "${resolution.pluginRunId}" is no longer active` };
880
+ }
881
+ if (attempt !== undefined && attempt.pluginRunId === run.pluginRunId) {
882
+ attempt.client = {
883
+ status: resolution.waitingFor === undefined || resolution.waitingFor.length === 0 ? 'running' : 'waiting',
884
+ waitingFor: resolution.waitingFor ?? [],
885
+ };
886
+ }
887
+ this.commitActivation(plugin, run);
888
+ return {
889
+ ...this.runResponse(plugin, {
890
+ ok: true,
891
+ pluginId: plugin.pluginId,
892
+ packageId: run.packageId,
893
+ pluginRunId: run.pluginRunId,
894
+ waitingFor: missingFor(this.ctx, run),
895
+ startedHere: false,
896
+ }),
897
+ ...resolution.waitingFor === undefined ? {} : { clientWaitingFor: resolution.waitingFor },
898
+ };
899
+ }
900
+ commitActivation(plugin, run) {
901
+ plugin.currentPackageId = run.packageId;
902
+ delete plugin.nextPackageId;
903
+ delete run.startedForRequest;
904
+ const attempt = plugin.latestRun;
905
+ if (attempt?.pluginRunId === run.pluginRunId) {
906
+ attempt.status = attempt.host.status === 'waiting' || attempt.client.status === 'waiting' ? 'waiting' : 'running';
907
+ delete attempt.approvalRequestId;
908
+ delete attempt.requiresApproval;
909
+ delete attempt.error;
910
+ }
911
+ }
912
+ runResponse(plugin, started) {
913
+ return {
914
+ ok: true,
915
+ status: 'running',
916
+ pluginId: plugin.pluginId,
917
+ packageId: started.packageId,
918
+ pluginRunId: started.pluginRunId,
919
+ waitingFor: started.waitingFor,
920
+ currentPackageId: started.packageId,
921
+ mode: plugin.latestRun?.pluginRunId === started.pluginRunId ? plugin.latestRun.mode : 'run',
922
+ };
923
+ }
924
+ announceResolved(requestId, resolution, override) {
925
+ const outcome = override ?? (resolution.ok ? 'approved' : resolution.reason === 'rejected' ? 'rejected' : 'failed');
926
+ this.ctx.emit('cordis/request-run-resolved', { requestId, outcome });
927
+ }
928
+ steerRunOutcome(pending, settled) {
929
+ const agents = this.rootCtx.get('agents');
930
+ const agent = agents?.get(pending.agentId);
931
+ if (agent === undefined)
932
+ return;
933
+ const plugin = this.registry.get(pending.pluginId);
934
+ const identity = `${pending.pluginId}/${pending.packageId} (${pending.pluginRunId})`;
935
+ let text;
936
+ if (settled.ok) {
937
+ text = `Cordis ${pending.mode} ${identity} completed successfully. `
938
+ + `currentPackageId is ${settled.currentPackageId ?? pending.packageId}. Continue using the running Plugin.`;
939
+ }
940
+ else if (settled.reason === 'rejected') {
941
+ text = `The user rejected Cordis ${pending.mode} ${identity}. `
942
+ + 'Do not request the same activation again unless the user asks.';
943
+ }
944
+ else {
945
+ const returnedStatus = pending.requiresApproval ? 'awaiting-approval' : 'starting';
946
+ text = `Cordis ${pending.mode} ${identity} failed after the runner returned ${returnedStatus}: `
947
+ + `${settled.reason}\n${formatErrorDetails(settled)}\n`
948
+ + `currentPackageId: ${plugin?.currentPackageId ?? 'none'}\n`
949
+ + `nextPackageId: ${plugin?.nextPackageId ?? pending.packageId}\n`
950
+ + 'Report the failure to the user; the definition can be managed through the Cordis panel.';
951
+ }
952
+ agent.steer(createUserMessage({
953
+ content: [{ type: 'text', text }],
954
+ source: { kind: 'kylin-host-runner' },
955
+ }));
956
+ }
957
+ steerRenderFailure(agent, plugin, definition, pluginRunId, failure) {
958
+ agent.steer(createUserMessage({
959
+ content: [{
960
+ type: 'text',
961
+ text: `Cordis Client UI ${plugin.pluginId}/${definition.packageId} (${pluginRunId}) failed while rendering `
962
+ + `Slot "${failure.slot}" after activation.\n`
963
+ + `${formatErrorDetails(failure)}\n`
964
+ + `entryAbdicated: ${failure.abdicated}\n`
965
+ + 'Report the Client render failure to the user; the definition can be stopped through the Cordis panel.',
966
+ }],
967
+ source: { kind: 'kylin-host-runner' },
968
+ }));
969
+ }
970
+ steerHostHandlerFailure(plugin, run, method, failure) {
971
+ const reportKey = `Host\u0000handler\u0000${method}\u0000${failure.message}`;
972
+ if (!this.claimRuntimeFailure(plugin, run, reportKey))
973
+ return;
974
+ const agents = this.rootCtx.get('agents');
975
+ const agent = agents?.get(plugin.sessionId);
976
+ if (agent === undefined)
977
+ return;
978
+ agent.steer(createUserMessage({
979
+ content: [{
980
+ type: 'text',
981
+ text: `Cordis Host handler ${plugin.pluginId}/${run.packageId} (${run.pluginRunId}) failed when the Client called `
982
+ + `host.call(${JSON.stringify(method)}).\n`
983
+ + `${formatErrorDetails(failure)}\n`
984
+ + 'The Plugin remains running. Report the Host handler failure to the user. If the handler needs a Service, either declare '
985
+ + 'that Service in the returned Plugin inject list or read it with ctx.get(name) and handle undefined.',
986
+ }],
987
+ source: { kind: 'kylin-host-runner' },
988
+ }));
989
+ }
990
+ /* jscpd:ignore-start */
991
+ steerGuardFailure(plugin, run, platform, failure) {
992
+ const reportKey = `${platform}\u0000guard\u0000${failure.message}`;
993
+ if (!this.claimRuntimeFailure(plugin, run, reportKey))
994
+ return;
995
+ const agents = this.rootCtx.get('agents');
996
+ const agent = agents?.get(plugin.sessionId);
997
+ if (agent === undefined)
998
+ return;
999
+ agent.steer(createUserMessage({
1000
+ content: [{
1001
+ type: 'text',
1002
+ text: `Cordis ${platform} guard rejected runtime code in ${plugin.pluginId}/${run.packageId} `
1003
+ + `(${run.pluginRunId}) after activation.\n${formatErrorDetails(failure)}\n`
1004
+ + 'The Plugin remains running. Report the guard rejection to the user; it can be stopped through the Cordis panel.',
1005
+ }],
1006
+ source: { kind: 'kylin-host-runner' },
1007
+ }));
1008
+ }
1009
+ /* jscpd:ignore-end */
1010
+ claimRuntimeFailure(plugin, run, key) {
1011
+ const attempt = plugin.latestRun;
1012
+ if (plugin.run !== run || attempt?.pluginRunId !== run.pluginRunId
1013
+ || (attempt.status !== 'running' && attempt.status !== 'waiting'))
1014
+ return false;
1015
+ if (run.reportedRuntimeErrors.has(key))
1016
+ return false;
1017
+ run.reportedRuntimeErrors.add(key);
1018
+ return true;
1019
+ }
1020
+ injectUserRunOutcome(agent, pluginId, settled) {
1021
+ const plugin = this.owned(agent, pluginId);
1022
+ let text;
1023
+ if (settled.ok) {
1024
+ text = `The user manually ran Cordis Plugin ${pluginId}, Package ${settled.packageId}, `
1025
+ + `as ${settled.pluginRunId}. The activation succeeded; currentPackageId is ${settled.currentPackageId}.`;
1026
+ }
1027
+ else {
1028
+ const attempt = plugin?.latestRun;
1029
+ text = `The user manually ran Cordis Plugin ${pluginId}`
1030
+ + `${attempt === undefined ? '' : `, Package ${attempt.packageId}, as ${attempt.pluginRunId}`}, but it failed: `
1031
+ + `${settled.reason}\n${formatErrorDetails(settled)}\n`
1032
+ + `currentPackageId: ${plugin?.currentPackageId ?? 'none'}\n`
1033
+ + `nextPackageId: ${plugin?.nextPackageId ?? 'none'}`;
1034
+ }
1035
+ this.injectUserContext(agent, text);
1036
+ }
1037
+ injectUserContext(agent, text) {
1038
+ const agents = this.rootCtx.get('agents');
1039
+ if (agents?.get(agent.id) !== agent)
1040
+ return;
1041
+ agent.inject(createUserMessage({
1042
+ content: [{ type: 'text', text }],
1043
+ source: { kind: 'kylin-host-runner' },
1044
+ }));
1045
+ }
1046
+ cancelPending(pluginId, message) {
1047
+ const requestId = this.registry.pendingRequestFor(pluginId);
1048
+ if (requestId === undefined)
1049
+ return;
1050
+ const pending = this.registry.claimRequest(requestId);
1051
+ if (pending === undefined)
1052
+ return;
1053
+ const plugin = this.registry.get(pluginId);
1054
+ if (plugin?.latestRun?.pluginRunId === pending.pluginRunId) {
1055
+ plugin.latestRun.status = 'cancelled';
1056
+ plugin.latestRun.error = this.diagnostic(plugin, plugin.latestRun, 'approval', message);
1057
+ delete plugin.latestRun.approvalRequestId;
1058
+ delete plugin.latestRun.requiresApproval;
1059
+ }
1060
+ this.announceResolved(requestId, { ok: false, reason: 'rejected' }, 'cancelled');
1061
+ }
1062
+ createAttempt(plan) {
1063
+ return {
1064
+ pluginRunId: CordisDynamicPluginRunId(this.registry.mintPluginRunId()),
1065
+ packageId: plan.definition.packageId,
1066
+ mode: plan.mode,
1067
+ status: 'starting-host',
1068
+ host: {
1069
+ status: plan.definition.hostCode === undefined ? 'absent' : 'pending',
1070
+ waitingFor: [],
1071
+ },
1072
+ client: {
1073
+ status: plan.definition.clientCode === undefined ? 'absent' : 'pending',
1074
+ waitingFor: [],
1075
+ },
1076
+ };
1077
+ }
1078
+ failAttempt(plugin, attempt, phase, failure) {
1079
+ attempt.status = 'failed';
1080
+ attempt.error = this.diagnostic(plugin, attempt, phase, failure);
1081
+ if (phase.startsWith('host'))
1082
+ attempt.host = { status: 'failed', waitingFor: [], error: failure.message };
1083
+ else
1084
+ attempt.client = { status: 'failed', waitingFor: [], error: failure.message };
1085
+ }
1086
+ diagnostic(plugin, attempt, phase, failure) {
1087
+ const details = typeof failure === 'string' ? { message: failure } : failure;
1088
+ return {
1089
+ phase,
1090
+ ...details,
1091
+ pluginId: plugin.pluginId,
1092
+ packageId: attempt.packageId,
1093
+ pluginRunId: attempt.pluginRunId,
1094
+ };
1095
+ }
1096
+ async retract(plugin) {
1097
+ const run = plugin.run;
1098
+ if (run === undefined)
1099
+ return;
1100
+ delete plugin.run;
1101
+ for (const dispose of run.handlerDisposers.splice(0))
1102
+ dispose();
1103
+ if (run.fiber !== undefined)
1104
+ await run.fiber.dispose();
1105
+ this.ctx.emit('cordis/dynamic-retract', {
1106
+ pluginId: plugin.pluginId,
1107
+ packageId: run.packageId,
1108
+ pluginRunId: run.pluginRunId,
1109
+ });
1110
+ }
1111
+ owned(agent, pluginId) {
1112
+ const plugin = this.registry.get(pluginId);
1113
+ return plugin?.sessionId === agent.id ? plugin : undefined;
1114
+ }
1115
+ requireGroup() {
1116
+ this.group ??= this.rootCtx.plugin({ name: 'cordis-dynamic', apply: () => { } });
1117
+ return this.group;
1118
+ }
1119
+ };
1120
+ })();
1121
+ export { DynamicCordisRunnerService };
1122
+ function missingFor(ctx, run) {
1123
+ return run.fiber === undefined ? [] : missingServices(ctx, run.fiber);
1124
+ }
1125
+ function missingPluginMessage(id) {
1126
+ return `no dynamic plugin "${id}" in this process — it may have been removed or lost on QILIN restart`;
1127
+ }
1128
+ function errorDetails(error) {
1129
+ if (typeof error !== 'object' || error === null)
1130
+ return { message: String(error) };
1131
+ const message = 'message' in error && typeof error.message === 'string'
1132
+ ? error.message
1133
+ : Object.prototype.toString.call(error);
1134
+ const stack = 'stack' in error && typeof error.stack === 'string' ? error.stack : undefined;
1135
+ return { message, ...stack === undefined ? {} : { stack } };
1136
+ }
1137
+ function formatErrorDetails(failure) {
1138
+ return `message: ${failure.message}`
1139
+ + (failure.stack === undefined ? '' : `\nstack:\n${failure.stack}`);
1140
+ }
1141
+ function cloneAttempt(attempt) {
1142
+ return {
1143
+ ...attempt,
1144
+ host: { ...attempt.host, waitingFor: [...attempt.host.waitingFor] },
1145
+ client: { ...attempt.client, waitingFor: [...attempt.client.waitingFor] },
1146
+ ...attempt.error === undefined ? {} : { error: { ...attempt.error } },
1147
+ };
1148
+ }
1149
+ export default DynamicCordisRunnerService;
1150
+ //# sourceMappingURL=index.js.map