@xneog/dsh-subagent 0.1.0 → 0.1.3-alpha.1
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/README.i18n.yaml +2 -2
- package/README.md +108 -76
- package/README.zh.md +112 -80
- package/lib/index.js +1258 -718
- package/lib/typert.host.d.ts +3 -0
- package/lib/typert.host.js +923 -0
- package/lib/typert.remote-client.d.ts +27 -0
- package/lib/typert.remote-client.js +159 -0
- package/lib/types/assistant-output.d.ts +3 -3
- package/lib/types/assistant-output.js +8 -4
- package/lib/types/child-agent.d.ts +16 -5
- package/lib/types/child-agent.js +51 -13
- package/lib/types/client.d.ts +2 -1
- package/lib/types/client.js +1 -1
- package/lib/types/continuation.d.ts +100 -72
- package/lib/types/continuation.js +439 -169
- package/lib/types/control-types.d.ts +144 -0
- package/lib/types/control-types.js +9 -0
- package/lib/types/control.d.ts +67 -0
- package/lib/types/control.js +115 -0
- package/lib/types/descriptor-seed.d.ts +1 -1
- package/lib/types/descriptor-seed.js +1 -1
- package/lib/types/descriptor.d.ts +6 -1
- package/lib/types/descriptor.js +6 -2
- package/lib/types/index.d.ts +103 -69
- package/lib/types/index.js +436 -287
- package/lib/types/internal.d.ts +59 -0
- package/lib/types/internal.js +58 -0
- package/lib/types/lifecycle.js +4 -3
- package/lib/types/list-children.d.ts +12 -59
- package/lib/types/list-children.js +166 -101
- package/lib/types/out-of-process.d.ts +5 -2
- package/lib/types/out-of-process.js +42 -4
- package/lib/types/projection-types.d.ts +4 -3
- package/lib/types/projection.d.ts +55 -8
- package/lib/types/projection.js +33 -17
- package/lib/types/run-settlement.js +17 -6
- package/lib/types/types.d.ts +25 -0
- package/package.json +67 -37
- package/lib/types/activation-setup-registry.d.ts +0 -57
- package/lib/types/activation-setup-registry.js +0 -148
package/lib/index.js
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
|
-
import { Service } from "@xneog/cordis";
|
|
2
1
|
import { scopeTarget } from "@xneog/dsh-scope";
|
|
3
2
|
import { assertObjectJsonSchema } from "@xneog/dsh-tools";
|
|
4
|
-
import {
|
|
3
|
+
import { canonicalClientTimeZone } from "@xneog/dsh-util-time";
|
|
4
|
+
import { Remote, RemoteError, TypertRemoteService } from "@xneog/dsh-typert-protocol";
|
|
5
|
+
import { AttachmentError } from "@xneog/dsh-attachment";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
import { HarnessError, ReasoningEffortId, boundContextSummary, contentHasImage, createUserMessage, errorChain, expandAssistantStream } from "@xneog/dsh-llm";
|
|
5
8
|
import { randomUUID } from "node:crypto";
|
|
6
9
|
import { foldConsumedWork } from "@xneog/dsh-agent";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
10
|
+
import { SessionLogOffset, SessionSeq } from "@xneog/dsh-session";
|
|
11
|
+
import { brandString } from "@xneog/dsh-brand";
|
|
12
|
+
import { snapshotJsonValue } from "@xneog/dsh-util-values";
|
|
9
13
|
import { accessSync, constants, statSync } from "node:fs";
|
|
10
14
|
import { isAbsolute, resolve } from "node:path";
|
|
11
15
|
//#region lib/types/error.js
|
|
@@ -22,6 +26,101 @@ var SubagentError = class extends HarnessError {
|
|
|
22
26
|
}
|
|
23
27
|
};
|
|
24
28
|
//#endregion
|
|
29
|
+
//#region lib/types/control.js
|
|
30
|
+
/**
|
|
31
|
+
* Browser-facing subagent control assembly: the catalog view sampled against
|
|
32
|
+
* the live Agent registry, one browser zone's validation, and the stable
|
|
33
|
+
* failure codes the Remote surface answers with.
|
|
34
|
+
*
|
|
35
|
+
* @module @xneog/dsh-subagent
|
|
36
|
+
*/
|
|
37
|
+
const SESSION_ID_SCHEMA = z.string().min(1);
|
|
38
|
+
const CONTROL_ID_SCHEMAS = {
|
|
39
|
+
"subagent.list": z.object({ parentSessionId: SESSION_ID_SCHEMA }),
|
|
40
|
+
"subagent.prompt": z.object({
|
|
41
|
+
parentSessionId: SESSION_ID_SCHEMA,
|
|
42
|
+
childSessionId: SESSION_ID_SCHEMA,
|
|
43
|
+
mode: z.literal("continuable")
|
|
44
|
+
}),
|
|
45
|
+
"subagent.interrupt": z.object({
|
|
46
|
+
parentSessionId: SESSION_ID_SCHEMA,
|
|
47
|
+
childSessionId: SESSION_ID_SCHEMA,
|
|
48
|
+
mode: z.literal("continuable")
|
|
49
|
+
})
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Apply the subagent payload checks that are stricter than generated
|
|
53
|
+
* branded-string codecs.
|
|
54
|
+
* @param method - method name carried in the failure message.
|
|
55
|
+
* @param payload - decoded control fields to validate.
|
|
56
|
+
* @throws {RemoteError} `gateway/bad-request` with the original Zod issues.
|
|
57
|
+
*/
|
|
58
|
+
function validateControlRequest(method, payload) {
|
|
59
|
+
const parsed = CONTROL_ID_SCHEMAS[method].safeParse(payload);
|
|
60
|
+
if (!parsed.success) throw new RemoteError("gateway/bad-request", `invalid payload for ${method}`, { issues: parsed.error.issues });
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Project one durable listing onto the catalog view, replacing each row's
|
|
64
|
+
* store-derived activity with the live Agent driver's status and reporting
|
|
65
|
+
* whether the exact parent Agent is live. Without an Agent registry no driver
|
|
66
|
+
* runs at all, so every row is inactive and the parent is unavailable.
|
|
67
|
+
* @param ctx - Host context that may carry the Agent registry.
|
|
68
|
+
* @param parentSessionId - the listed parent.
|
|
69
|
+
* @param entries - the durable direct-child listing.
|
|
70
|
+
* @returns the catalog view answered to one browser.
|
|
71
|
+
*/
|
|
72
|
+
function catalogView(ctx, parentSessionId, entries) {
|
|
73
|
+
const agents = ctx.get("agents");
|
|
74
|
+
return {
|
|
75
|
+
entries: entries.map((entry) => entry.kind === "child" ? {
|
|
76
|
+
...entry,
|
|
77
|
+
activity: agents?.get(entry.id)?.status === "running" ? "running" : "inactive"
|
|
78
|
+
} : entry),
|
|
79
|
+
parentAvailable: agents?.get(parentSessionId) !== void 0
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Refuse one catalog read while preserving cancellation and a missing
|
|
84
|
+
* projections registry as distinct failures.
|
|
85
|
+
* @param error - the thrown value.
|
|
86
|
+
* @param signal - the caller's cancellation.
|
|
87
|
+
* @returns Never — the refusal is thrown.
|
|
88
|
+
* @throws {RemoteError} always.
|
|
89
|
+
*/
|
|
90
|
+
function rejectCatalogRead(error, signal) {
|
|
91
|
+
if (isCancellation(error, signal)) throw new RemoteError("gateway/cancelled", "subagent catalog read was cancelled", {}, { cause: error });
|
|
92
|
+
if (error instanceof SubagentError && error.code === "SUBAGENT_CONTROL_PROJECTIONS_UNAVAILABLE") throw new RemoteError("subagent/projections-unavailable", "subagent catalog is unavailable: this deployment does not mount the sessionProjections registry (load @xneog/dsh-session-projection)", {}, { cause: error });
|
|
93
|
+
throw new RemoteError("gateway/internal", "subagent catalog read failed", {}, { cause: error });
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Refuse one continuation prompt without exposing provider detail: admission
|
|
97
|
+
* failures the caller can act on keep their own code, everything else is
|
|
98
|
+
* internal.
|
|
99
|
+
* @param error - the thrown value.
|
|
100
|
+
* @param childSessionId - the addressed child.
|
|
101
|
+
* @param signal - the caller's cancellation.
|
|
102
|
+
* @returns Never — the refusal is thrown.
|
|
103
|
+
* @throws {RemoteError} always.
|
|
104
|
+
*/
|
|
105
|
+
function rejectPrompt(error, childSessionId, signal) {
|
|
106
|
+
if (isCancellation(error, signal)) throw new RemoteError("gateway/cancelled", "subagent prompt was cancelled", {}, { cause: error });
|
|
107
|
+
if (error instanceof AttachmentError) throw new RemoteError("subagent/attachment-invalid", error.message, { reason: error.code }, { cause: error });
|
|
108
|
+
if (error instanceof SubagentError) switch (error.code) {
|
|
109
|
+
case "MODEL_DOES_NOT_SUPPORT_IMAGES": throw new RemoteError("subagent/attachment-invalid", error.message, { reason: error.code }, { cause: error });
|
|
110
|
+
case "NOT_RESUMABLE": throw new RemoteError("subagent/not-resumable", "subagent cannot be resumed", { childSessionId }, { cause: error });
|
|
111
|
+
case "UNAUTHORIZED": throw new RemoteError("subagent/unauthorized", "subagent does not belong to this parent", { childSessionId }, { cause: error });
|
|
112
|
+
case "DRAINING":
|
|
113
|
+
case "ACTIVATION_CLOSING":
|
|
114
|
+
case "CONTINUATION_UNAVAILABLE":
|
|
115
|
+
case "PERSISTENCE_UNAVAILABLE": throw new RemoteError("subagent/delivery-unavailable", "subagent follow-up is temporarily unavailable", { childSessionId }, { cause: error });
|
|
116
|
+
default: break;
|
|
117
|
+
}
|
|
118
|
+
throw new RemoteError("gateway/internal", "subagent prompt failed", {}, { cause: error });
|
|
119
|
+
}
|
|
120
|
+
function isCancellation(error, signal) {
|
|
121
|
+
return signal.aborted || error instanceof SubagentError && error.code === "CANCELLED";
|
|
122
|
+
}
|
|
123
|
+
//#endregion
|
|
25
124
|
//#region lib/types/depth.js
|
|
26
125
|
/**
|
|
27
126
|
* Delegation-depth accounting: the recursion budget a parent passes to its
|
|
@@ -76,15 +175,18 @@ var AssistantOutputFold = class {
|
|
|
76
175
|
partial = [];
|
|
77
176
|
/**
|
|
78
177
|
* Fold one session event: a non-empty assistant message becomes the
|
|
79
|
-
* candidate final answer,
|
|
80
|
-
* fallback; every other event contributes nothing.
|
|
178
|
+
* candidate final answer, while its embedded stream and any log-only attempt
|
|
179
|
+
* extend the streamed fallback; every other event contributes nothing.
|
|
81
180
|
* @param event - the next observed session event.
|
|
82
181
|
*/
|
|
83
182
|
push(event) {
|
|
84
183
|
if (event.type === "assistant/message") {
|
|
85
184
|
const content = event.data.message.content;
|
|
86
185
|
if (content.length > 0) this.message = content;
|
|
87
|
-
}
|
|
186
|
+
}
|
|
187
|
+
if (event.type === "assistant/message" || event.type === "assistant/attempt") {
|
|
188
|
+
for (const { chunk } of expandAssistantStream(event.data.stream)) if (chunk.type === "text-delta") this.pushText(chunk.text);
|
|
189
|
+
}
|
|
88
190
|
}
|
|
89
191
|
/**
|
|
90
192
|
* Extend the streamed fallback with text observed outside session events.
|
|
@@ -228,16 +330,16 @@ function createActivationObserver(emit, provider, childId, parent) {
|
|
|
228
330
|
id: childId,
|
|
229
331
|
local: true
|
|
230
332
|
};
|
|
231
|
-
let boundary = 0;
|
|
333
|
+
let boundary = SessionLogOffset(0);
|
|
232
334
|
let captured = { stopReason: "completed" };
|
|
233
335
|
const terminal = (failure) => failure === void 0 ? captured : { stopReason: "error" };
|
|
234
336
|
return {
|
|
235
337
|
start: (child) => {
|
|
236
|
-
boundary = child.session.
|
|
338
|
+
boundary = child.session.seq;
|
|
237
339
|
emit("subagent/start", identity, parent);
|
|
238
340
|
},
|
|
239
341
|
capture: (child) => {
|
|
240
|
-
const own = child.session.
|
|
342
|
+
const own = child.session.snapshotEvents(boundary);
|
|
241
343
|
const output = finalAssistantOutput(own);
|
|
242
344
|
captured = {
|
|
243
345
|
stopReason: epochStopReason(own),
|
|
@@ -325,7 +427,7 @@ function renderThrown(value) {
|
|
|
325
427
|
* Supporting another composition input is a deliberate version change, never
|
|
326
428
|
* an implicit extra field.
|
|
327
429
|
*/
|
|
328
|
-
const SUBAGENT_DESCRIPTOR_VERSION =
|
|
430
|
+
const SUBAGENT_DESCRIPTOR_VERSION = 3;
|
|
329
431
|
const DESCRIPTOR_BASE_KEYS = [
|
|
330
432
|
"version",
|
|
331
433
|
"mode",
|
|
@@ -337,6 +439,7 @@ const CONTINUABLE_DESCRIPTOR_KEYS = new Set([
|
|
|
337
439
|
...DESCRIPTOR_BASE_KEYS,
|
|
338
440
|
"agentProvider",
|
|
339
441
|
"agentModel",
|
|
442
|
+
"agentReasoningEffort",
|
|
340
443
|
"persona",
|
|
341
444
|
"toolFilter"
|
|
342
445
|
]);
|
|
@@ -383,7 +486,7 @@ function parseSubagentDescriptor(value) {
|
|
|
383
486
|
if (!isRecord(value)) throw new Error("persisted subagent descriptor payload must be an object");
|
|
384
487
|
const version = value["version"];
|
|
385
488
|
if (typeof version !== "number") throw new Error("persisted subagent descriptor version must be a number");
|
|
386
|
-
if (version !==
|
|
489
|
+
if (version !== 3) return void 0;
|
|
387
490
|
const mode = value["mode"];
|
|
388
491
|
if (mode !== "one-shot" && mode !== "continuable") throw new Error("persisted subagent descriptor mode must be \"one-shot\" or \"continuable\"");
|
|
389
492
|
assertKnownKeys(value, mode === "one-shot" ? ONE_SHOT_DESCRIPTOR_KEYS : CONTINUABLE_DESCRIPTOR_KEYS, "payload");
|
|
@@ -392,7 +495,7 @@ function parseSubagentDescriptor(value) {
|
|
|
392
495
|
if (mode === "one-shot") {
|
|
393
496
|
const label = optionalString(value, "label");
|
|
394
497
|
return {
|
|
395
|
-
version:
|
|
498
|
+
version: 3,
|
|
396
499
|
mode,
|
|
397
500
|
provider,
|
|
398
501
|
...label !== void 0 ? { label } : {}
|
|
@@ -402,32 +505,35 @@ function parseSubagentDescriptor(value) {
|
|
|
402
505
|
if (typeof label !== "string") throw new Error("persisted subagent descriptor label must be a string");
|
|
403
506
|
const agentProvider = optionalString(value, "agentProvider");
|
|
404
507
|
const agentModel = optionalString(value, "agentModel");
|
|
508
|
+
const agentReasoningEffort = optionalString(value, "agentReasoningEffort");
|
|
405
509
|
const persona = optionalString(value, "persona");
|
|
406
510
|
const toolFilter = Object.hasOwn(value, "toolFilter") ? parseToolFilter(value["toolFilter"]) : void 0;
|
|
407
511
|
return {
|
|
408
|
-
version:
|
|
512
|
+
version: 3,
|
|
409
513
|
mode,
|
|
410
514
|
provider,
|
|
411
515
|
label,
|
|
412
516
|
...agentProvider !== void 0 ? { agentProvider } : {},
|
|
413
517
|
...agentModel !== void 0 ? { agentModel } : {},
|
|
518
|
+
...agentReasoningEffort !== void 0 ? { agentReasoningEffort } : {},
|
|
414
519
|
...persona !== void 0 ? { persona } : {},
|
|
415
520
|
...toolFilter !== void 0 ? { toolFilter } : {}
|
|
416
521
|
};
|
|
417
522
|
}
|
|
418
523
|
function snapshotSubagentDescriptor(input) {
|
|
419
524
|
const snapshot = snapshotJsonValue(input.mode === "one-shot" ? {
|
|
420
|
-
version:
|
|
525
|
+
version: 3,
|
|
421
526
|
mode: input.mode,
|
|
422
527
|
provider: input.provider,
|
|
423
528
|
...input.label !== void 0 ? { label: input.label } : {}
|
|
424
529
|
} : {
|
|
425
|
-
version:
|
|
530
|
+
version: 3,
|
|
426
531
|
mode: input.mode,
|
|
427
532
|
provider: input.provider,
|
|
428
533
|
label: input.label,
|
|
429
534
|
...input.agentProvider !== void 0 ? { agentProvider: input.agentProvider } : {},
|
|
430
535
|
...input.agentModel !== void 0 ? { agentModel: input.agentModel } : {},
|
|
536
|
+
...input.agentReasoningEffort !== void 0 ? { agentReasoningEffort: input.agentReasoningEffort } : {},
|
|
431
537
|
...input.persona !== void 0 ? { persona: input.persona } : {},
|
|
432
538
|
...input.toolFilter !== void 0 ? { toolFilter: input.toolFilter } : {}
|
|
433
539
|
});
|
|
@@ -490,25 +596,51 @@ function resolveChildDepth(parent, maxDepth) {
|
|
|
490
596
|
return childDepth;
|
|
491
597
|
}
|
|
492
598
|
/**
|
|
493
|
-
* Resolve the child
|
|
494
|
-
*
|
|
495
|
-
*
|
|
599
|
+
* Resolve the parent values inherited by a child. The latest request header
|
|
600
|
+
* owns provider, model, and reasoning effort after request-time selection;
|
|
601
|
+
* creation options remain the fallback before the first request and retain
|
|
602
|
+
* the configured output-token limit.
|
|
603
|
+
* @param parent - delegating parent Agent.
|
|
604
|
+
* @returns detached Agent options for child-option merging.
|
|
605
|
+
*/
|
|
606
|
+
function parentAgentOptionsForDelegation(parent) {
|
|
607
|
+
const requestConfig = parent.session.requestHeader()?.config;
|
|
608
|
+
if (requestConfig === void 0) return { ...parent.options };
|
|
609
|
+
const { provider: _createdProvider, model: _createdModel, reasoningEffort: _createdReasoningEffort, ...createdOptions } = parent.options;
|
|
610
|
+
return {
|
|
611
|
+
...createdOptions,
|
|
612
|
+
provider: requestConfig.provider,
|
|
613
|
+
model: requestConfig.model,
|
|
614
|
+
...requestConfig.reasoningEffort === void 0 ? {} : { reasoningEffort: requestConfig.reasoningEffort }
|
|
615
|
+
};
|
|
616
|
+
}
|
|
617
|
+
/**
|
|
618
|
+
* Resolve the child's `AgentOptions`: the parent's provider/model,
|
|
619
|
+
* reasoning-effort, and maxTokens values unless the request overrides them,
|
|
620
|
+
* stamped with the child's own delegation depth. Changing the route without
|
|
621
|
+
* naming an effort clears the parent's route-owned effort so the selected
|
|
622
|
+
* model resolves its own default.
|
|
496
623
|
* @param parent - the delegating parent whose route the child inherits.
|
|
497
624
|
* @param requested - per-child overrides, if any.
|
|
498
625
|
* @param childDepth - the resolved delegation depth to stamp.
|
|
499
626
|
* @returns the resolved options for `ctx.agents.create()`.
|
|
500
627
|
*/
|
|
501
628
|
function resolveChildAgentOptions(parent, requested, childDepth) {
|
|
502
|
-
const
|
|
503
|
-
const
|
|
504
|
-
const
|
|
505
|
-
|
|
629
|
+
const parentOptions = parentAgentOptionsForDelegation(parent);
|
|
630
|
+
const parentProvider = parentOptions.provider;
|
|
631
|
+
const parentModel = parentOptions.model;
|
|
632
|
+
const parentReasoningEffort = parentOptions.reasoningEffort;
|
|
633
|
+
const parentMaxTokens = parentOptions.maxTokens;
|
|
634
|
+
const resolved = {
|
|
506
635
|
...parentProvider !== void 0 ? { provider: parentProvider } : {},
|
|
507
636
|
...parentModel !== void 0 ? { model: parentModel } : {},
|
|
637
|
+
...parentReasoningEffort !== void 0 ? { reasoningEffort: parentReasoningEffort } : {},
|
|
508
638
|
...parentMaxTokens !== void 0 ? { maxTokens: parentMaxTokens } : {},
|
|
509
639
|
...requested,
|
|
510
640
|
subagentDepth: childDepth
|
|
511
641
|
};
|
|
642
|
+
if ((resolved.provider !== parentProvider || resolved.model !== parentModel) && requested?.reasoningEffort === void 0) delete resolved.reasoningEffort;
|
|
643
|
+
return resolved;
|
|
512
644
|
}
|
|
513
645
|
/**
|
|
514
646
|
* Build the child session's durable creation metadata: the parent's workspace,
|
|
@@ -524,19 +656,19 @@ function resolveChildAgentOptions(parent, requested, childDepth) {
|
|
|
524
656
|
* child never had.
|
|
525
657
|
* @param parent - the delegating parent agent.
|
|
526
658
|
* @param childDepth - the resolved delegation depth to persist.
|
|
527
|
-
* @param
|
|
659
|
+
* @param isSeeded - whether this child inherits a parent-log prefix, including an explicitly empty one.
|
|
528
660
|
* @returns the `meta` for `ctx.agents.create()`.
|
|
529
661
|
*/
|
|
530
|
-
function childSessionMeta(parent, childDepth,
|
|
662
|
+
function childSessionMeta(parent, childDepth, isSeeded) {
|
|
531
663
|
const parentHeader = parent.session.header;
|
|
532
664
|
const agentPreset = parent.ctx.get("agentPresets")?.composedPreset(parent.ctx);
|
|
533
665
|
return {
|
|
534
666
|
...parentHeader.cwd !== void 0 ? { cwd: parentHeader.cwd } : {},
|
|
535
667
|
...agentPreset === void 0 ? {} : { agentPreset },
|
|
536
668
|
parentSession: parentHeader.id,
|
|
669
|
+
isSeeded,
|
|
537
670
|
origin: "subagent",
|
|
538
|
-
delegationDepth: childDepth
|
|
539
|
-
...lineageSeedLength > 0 ? { seedLength: lineageSeedLength } : {}
|
|
671
|
+
delegationDepth: childDepth
|
|
540
672
|
};
|
|
541
673
|
}
|
|
542
674
|
/**
|
|
@@ -571,12 +703,12 @@ function applyChildComposition(childCtx, parent, composition) {
|
|
|
571
703
|
childCtx.get("agentPresets")?.composeFrom(childCtx, parent.ctx);
|
|
572
704
|
childCtx.systemPrompt.context({
|
|
573
705
|
name: "subagent:delegation",
|
|
574
|
-
order:
|
|
706
|
+
order: childCtx.systemPrompt.getContextOrder("SUBAGENT_DELEGATION"),
|
|
575
707
|
text: SUBAGENT_DELEGATION_CONTEXT
|
|
576
708
|
});
|
|
577
709
|
if (composition.persona !== void 0) childCtx.systemPrompt.section({
|
|
578
710
|
name: "deployment:persona",
|
|
579
|
-
order:
|
|
711
|
+
order: childCtx.systemPrompt.getSectionOrder("DEPLOYMENT_PERSONA"),
|
|
580
712
|
text: composition.persona
|
|
581
713
|
});
|
|
582
714
|
if (composition.toolFilter !== void 0) childCtx.tools.restrict(composition.toolFilter);
|
|
@@ -617,29 +749,28 @@ function appendDelegatedPolicyOverrides(childSession, overrides) {
|
|
|
617
749
|
});
|
|
618
750
|
}
|
|
619
751
|
//#endregion
|
|
620
|
-
//#region lib/types/
|
|
752
|
+
//#region lib/types/internal.js
|
|
621
753
|
/**
|
|
622
|
-
*
|
|
623
|
-
*
|
|
624
|
-
*
|
|
625
|
-
*
|
|
626
|
-
* @module @xneog/dsh-subagent/descriptor-seed
|
|
754
|
+
* Continuation integration markers and host adapters outside the public
|
|
755
|
+
* Service Definition and model-facing Agent messaging contract.
|
|
756
|
+
* @module @xneog/dsh-subagent/internal
|
|
627
757
|
*/
|
|
758
|
+
/** Process-stable identity carried only by the standard adjacent-Agent messaging tool. */
|
|
759
|
+
const adjacentAgentSendMessageTool = Symbol.for("dsh.subagent.adjacentAgentSendMessageTool");
|
|
628
760
|
/**
|
|
629
|
-
*
|
|
630
|
-
*
|
|
631
|
-
*
|
|
632
|
-
* rules the durable log does.
|
|
633
|
-
* @param childId - the reserved child session id the staged log belongs to.
|
|
634
|
-
* @param seed - the inherited completed-turn prefix, or `undefined` for a fresh child.
|
|
635
|
-
* @param descriptor - the snapshotted composition record to persist.
|
|
636
|
-
* @returns the complete seed events, contiguous from sequence zero.
|
|
761
|
+
* Test whether one visible definition is the standard adjacent-Agent messaging tool.
|
|
762
|
+
* @param definition - the scope-resolved `send_message` candidate.
|
|
763
|
+
* @returns whether the definition carries the internal standard-tool identity.
|
|
637
764
|
*/
|
|
638
|
-
function
|
|
639
|
-
|
|
640
|
-
staged.append("subagent/descriptor", descriptor);
|
|
641
|
-
return [...staged.events];
|
|
765
|
+
function isAdjacentAgentSendMessageTool(definition) {
|
|
766
|
+
return definition !== void 0 && definition[adjacentAgentSendMessageTool] === true;
|
|
642
767
|
}
|
|
768
|
+
/**
|
|
769
|
+
* Process-stable symbol-keyed host delivery shared by the bundled runtime
|
|
770
|
+
* entry and this unbundled internal subpath.
|
|
771
|
+
* @internal
|
|
772
|
+
*/
|
|
773
|
+
const deliverSubagentPrompt = Symbol.for("dsh.subagent.deliverPrompt");
|
|
643
774
|
//#endregion
|
|
644
775
|
//#region lib/types/continuation.js
|
|
645
776
|
/**
|
|
@@ -664,6 +795,64 @@ function seedDescriptorTurn(childId, seed, descriptor) {
|
|
|
664
795
|
*
|
|
665
796
|
* @module @xneog/dsh-subagent
|
|
666
797
|
*/
|
|
798
|
+
var __addDisposableResource$1 = function(env, value, async) {
|
|
799
|
+
if (value !== null && value !== void 0) {
|
|
800
|
+
if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
|
|
801
|
+
var dispose, inner;
|
|
802
|
+
if (async) {
|
|
803
|
+
if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
|
|
804
|
+
dispose = value[Symbol.asyncDispose];
|
|
805
|
+
}
|
|
806
|
+
if (dispose === void 0) {
|
|
807
|
+
if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
|
|
808
|
+
dispose = value[Symbol.dispose];
|
|
809
|
+
if (async) inner = dispose;
|
|
810
|
+
}
|
|
811
|
+
if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
|
|
812
|
+
if (inner) dispose = function() {
|
|
813
|
+
try {
|
|
814
|
+
inner.call(this);
|
|
815
|
+
} catch (e) {
|
|
816
|
+
return Promise.reject(e);
|
|
817
|
+
}
|
|
818
|
+
};
|
|
819
|
+
env.stack.push({
|
|
820
|
+
value,
|
|
821
|
+
dispose,
|
|
822
|
+
async
|
|
823
|
+
});
|
|
824
|
+
} else if (async) env.stack.push({ async: true });
|
|
825
|
+
return value;
|
|
826
|
+
};
|
|
827
|
+
var __disposeResources$1 = (function(SuppressedError) {
|
|
828
|
+
return function(env) {
|
|
829
|
+
function fail(e) {
|
|
830
|
+
env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
|
|
831
|
+
env.hasError = true;
|
|
832
|
+
}
|
|
833
|
+
var r, s = 0;
|
|
834
|
+
function next() {
|
|
835
|
+
while (r = env.stack.pop()) try {
|
|
836
|
+
if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
|
|
837
|
+
if (r.dispose) {
|
|
838
|
+
var result = r.dispose.call(r.value);
|
|
839
|
+
if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) {
|
|
840
|
+
fail(e);
|
|
841
|
+
return next();
|
|
842
|
+
});
|
|
843
|
+
} else s |= 1;
|
|
844
|
+
} catch (e) {
|
|
845
|
+
fail(e);
|
|
846
|
+
}
|
|
847
|
+
if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
|
|
848
|
+
if (env.hasError) throw env.error;
|
|
849
|
+
}
|
|
850
|
+
return next();
|
|
851
|
+
};
|
|
852
|
+
})(typeof SuppressedError === "function" ? SuppressedError : function(error, suppressed, message) {
|
|
853
|
+
var e = new Error(message);
|
|
854
|
+
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
855
|
+
});
|
|
667
856
|
/**
|
|
668
857
|
* Read one Activation's current disposal transaction. This indirection exists
|
|
669
858
|
* because TypeScript would otherwise narrow repeated reads of the mutable field
|
|
@@ -674,6 +863,32 @@ function seedDescriptorTurn(childId, seed, descriptor) {
|
|
|
674
863
|
function disposalOf(activation) {
|
|
675
864
|
return activation.disposal;
|
|
676
865
|
}
|
|
866
|
+
/** Build durable attribution for one adjacent-Agent message. */
|
|
867
|
+
function agentMessageSource(sender) {
|
|
868
|
+
return {
|
|
869
|
+
kind: "agent-message",
|
|
870
|
+
form: "relay",
|
|
871
|
+
senderSessionId: sender.id
|
|
872
|
+
};
|
|
873
|
+
}
|
|
874
|
+
/** Build the model-visible and durable representation of one adjacent-Agent message. */
|
|
875
|
+
function agentMessage(sender, content) {
|
|
876
|
+
return createUserMessage({
|
|
877
|
+
content: [{
|
|
878
|
+
type: "text",
|
|
879
|
+
text: `Agent ${sender.id} sent a message:`
|
|
880
|
+
}, ...content],
|
|
881
|
+
source: agentMessageSource(sender)
|
|
882
|
+
});
|
|
883
|
+
}
|
|
884
|
+
/** Append adjacent-Agent return guidance to a continuable child's initial task. */
|
|
885
|
+
function continuableInitialPrompt(parentId, prompt) {
|
|
886
|
+
const encodedParentId = JSON.stringify(parentId);
|
|
887
|
+
return [...prompt, {
|
|
888
|
+
type: "text",
|
|
889
|
+
text: `Your parent agent id is ${encodedParentId}. Before you finish, send your result to that agent with send_message({ agent_id: ${encodedParentId}, message: "<self-contained result>" }). The parent shares your workspace but does not automatically receive your transcript, tool output, or reasoning. Send earlier messages as well when a finding changes what the parent should do next; sending a message does not end your turn.`
|
|
890
|
+
}];
|
|
891
|
+
}
|
|
677
892
|
/**
|
|
678
893
|
* One line telling a parent that a background child is finished and why, in
|
|
679
894
|
* the parent's own task vocabulary.
|
|
@@ -723,7 +938,6 @@ var ChildLock = class {
|
|
|
723
938
|
var SubagentContinuationManager = class {
|
|
724
939
|
ctx;
|
|
725
940
|
host;
|
|
726
|
-
setupRegistry;
|
|
727
941
|
/** Child session id → its live Activation. Process-local, never durable. */
|
|
728
942
|
activations = /* @__PURE__ */ new Map();
|
|
729
943
|
/** Materializations admitted before drain, tracked through publication or rollback. */
|
|
@@ -739,10 +953,9 @@ var SubagentContinuationManager = class {
|
|
|
739
953
|
*/
|
|
740
954
|
closingScopes = /* @__PURE__ */ new Map();
|
|
741
955
|
draining = false;
|
|
742
|
-
constructor(ctx, host
|
|
956
|
+
constructor(ctx, host) {
|
|
743
957
|
this.ctx = ctx;
|
|
744
958
|
this.host = host;
|
|
745
|
-
this.setupRegistry = setupRegistry;
|
|
746
959
|
const scope = ctx.plugin(function activationOwner() {});
|
|
747
960
|
this.ownerCtx = scope.ctx;
|
|
748
961
|
ctx.on("agent/disposed", ({ agent }) => {
|
|
@@ -772,83 +985,203 @@ var SubagentContinuationManager = class {
|
|
|
772
985
|
const request = spec.request;
|
|
773
986
|
const parent = request.parent;
|
|
774
987
|
this.assertAdmitting(parent);
|
|
775
|
-
this.requirePersistence();
|
|
988
|
+
const persistence = this.requirePersistence();
|
|
776
989
|
assertSubagentMaxDepth(request.maxDepth);
|
|
777
|
-
const childId =
|
|
990
|
+
const childId = spec.childId ?? brandString(randomUUID());
|
|
991
|
+
this.assertChildIdAvailable(childId);
|
|
778
992
|
const childDepth = resolveChildDepth(parent, request.maxDepth);
|
|
779
|
-
const
|
|
780
|
-
const
|
|
993
|
+
const agentOptions = resolveChildAgentOptions(parent, request.agentOptions, childDepth);
|
|
994
|
+
const agentProvider = agentOptions.provider;
|
|
995
|
+
const agentModel = agentOptions.model;
|
|
996
|
+
const agentReasoningEffort = agentOptions.reasoningEffort;
|
|
781
997
|
const descriptor = snapshotSubagentDescriptor({
|
|
782
998
|
mode: "continuable",
|
|
783
999
|
provider: spec.provider,
|
|
784
1000
|
label: spec.label,
|
|
785
1001
|
...agentProvider !== void 0 ? { agentProvider } : {},
|
|
786
1002
|
...agentModel !== void 0 ? { agentModel } : {},
|
|
1003
|
+
...agentReasoningEffort !== void 0 ? { agentReasoningEffort } : {},
|
|
787
1004
|
...request.persona !== void 0 ? { persona: request.persona } : {},
|
|
788
1005
|
...request.toolFilter !== void 0 ? { toolFilter: request.toolFilter } : {}
|
|
789
1006
|
});
|
|
790
1007
|
const delegatedPolicies = captureDelegatedPolicyOverrides(parent);
|
|
791
|
-
const
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
parent
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
1008
|
+
const releaseHold = this.holdOwnership(parent, childId);
|
|
1009
|
+
try {
|
|
1010
|
+
const prepared = await this.host.prepareContinuable(spec.provider, {
|
|
1011
|
+
sessionId: childId,
|
|
1012
|
+
parent,
|
|
1013
|
+
signal: spec.signal
|
|
1014
|
+
});
|
|
1015
|
+
spec.signal.throwIfAborted();
|
|
1016
|
+
this.assertAdmitting(parent);
|
|
1017
|
+
const inheritedEventCount = SessionLogOffset(prepared.seed?.length ?? 0);
|
|
1018
|
+
const seed = prepared.seed;
|
|
1019
|
+
return {
|
|
1020
|
+
childId,
|
|
1021
|
+
messageId: await this.locks.run(childId, async () => {
|
|
1022
|
+
spec.signal.throwIfAborted();
|
|
1023
|
+
this.assertAdmitting(parent);
|
|
1024
|
+
this.assertChildIdAvailable(childId);
|
|
1025
|
+
if (spec.childId !== void 0) {
|
|
1026
|
+
const persisted = await persistence.stat(childId, { signal: spec.signal });
|
|
1027
|
+
spec.signal.throwIfAborted();
|
|
1028
|
+
this.assertAdmitting(parent);
|
|
1029
|
+
this.assertChildIdAvailable(childId);
|
|
1030
|
+
if (persisted !== void 0) throw new SubagentError(`subagent "${childId}" already exists`, "DUPLICATE_CHILD");
|
|
1031
|
+
}
|
|
1032
|
+
const activation = await this.materialize({
|
|
1033
|
+
childId,
|
|
1034
|
+
provider: spec.provider,
|
|
1035
|
+
parent,
|
|
1036
|
+
create: {
|
|
1037
|
+
seed,
|
|
1038
|
+
meta: childSessionMeta(parent, childDepth, prepared.seed !== void 0),
|
|
1039
|
+
inheritedEventCount,
|
|
1040
|
+
delegatedPolicies,
|
|
1041
|
+
descriptor
|
|
1042
|
+
},
|
|
1043
|
+
agentOptions,
|
|
1044
|
+
composition: {
|
|
1045
|
+
persona: request.persona,
|
|
1046
|
+
toolFilter: request.toolFilter
|
|
1047
|
+
},
|
|
1048
|
+
signal: spec.signal
|
|
1049
|
+
});
|
|
1050
|
+
return this.submitMaterialized(activation, isAdjacentAgentSendMessageTool(this.ctx.get("tools")?.get("send_message", activation.handle.agent)) ? continuableInitialPrompt(parent.id, request.prompt) : request.prompt, {
|
|
1051
|
+
source: { kind: "user" },
|
|
1052
|
+
signal: spec.signal,
|
|
1053
|
+
delivery: "queue"
|
|
1054
|
+
}, parent);
|
|
1055
|
+
})
|
|
1056
|
+
};
|
|
1057
|
+
} catch (error) {
|
|
1058
|
+
releaseHold();
|
|
1059
|
+
throw error;
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
/**
|
|
1063
|
+
* Pre-register `childId` in a continuation-managed parent's owned set so the
|
|
1064
|
+
* parent cannot settle while a caller is still establishing or resuming that
|
|
1065
|
+
* child. Returns a releaser for the failure path; it removes only a hold
|
|
1066
|
+
* this call added, and leaves ownership in place once a live Activation for
|
|
1067
|
+
* the child exists (an admitted delivery owns it from then on). A parent
|
|
1068
|
+
* without an Activation needs no hold: only this manager settles parents.
|
|
1069
|
+
* @param parent - the live direct parent the operation is admitted under.
|
|
1070
|
+
* @param childId - the durable child the operation addresses.
|
|
1071
|
+
* @returns the failure-path releaser; a no-op when nothing was added.
|
|
1072
|
+
* @throws {SubagentError} `ACTIVATION_CLOSING` when the parent's own
|
|
1073
|
+
* disposal transaction is already open.
|
|
1074
|
+
*/
|
|
1075
|
+
holdOwnership(parent, childId) {
|
|
1076
|
+
const parentActivation = this.activations.get(parent.id);
|
|
1077
|
+
if (parentActivation === void 0 || parentActivation.handle.agent !== parent) return () => {};
|
|
1078
|
+
if (parentActivation.disposal !== void 0) throw new SubagentError(`subagent parent "${parent.id}" is being disposed; the child was not established`, "ACTIVATION_CLOSING");
|
|
1079
|
+
if (parentActivation.ownedChildren.has(childId)) return () => {};
|
|
1080
|
+
parentActivation.ownedChildren.add(childId);
|
|
1081
|
+
return () => {
|
|
1082
|
+
const live = this.activations.get(childId);
|
|
1083
|
+
/* v8 ignore next 4 -- reaching this arm needs another delivery to establish the child
|
|
1084
|
+
* between this operation's failure and its releaser running, which no test can schedule
|
|
1085
|
+
* deterministically: the ownership edge then belongs to that live Activation, so the
|
|
1086
|
+
* conservative keep leaves it for finishDisposal's releaseOwnership. */
|
|
1087
|
+
if (live !== void 0 && live.disposal === void 0) return;
|
|
1088
|
+
if (parentActivation.ownedChildren.delete(childId)) this.wake(parentActivation);
|
|
821
1089
|
};
|
|
822
1090
|
}
|
|
1091
|
+
/** Reject one child identity already owned by a live Agent or Session. */
|
|
1092
|
+
assertChildIdAvailable(childId) {
|
|
1093
|
+
if (this.ctx.agents.get(childId) !== void 0 || this.ctx.get("sessions")?.get(childId) !== void 0) throw new SubagentError(`subagent "${childId}" already exists`, "DUPLICATE_CHILD");
|
|
1094
|
+
}
|
|
823
1095
|
/**
|
|
824
|
-
* Deliver one
|
|
825
|
-
*
|
|
826
|
-
*
|
|
827
|
-
*
|
|
828
|
-
*
|
|
829
|
-
*
|
|
830
|
-
*
|
|
831
|
-
*
|
|
832
|
-
*
|
|
833
|
-
* @
|
|
834
|
-
* @
|
|
835
|
-
|
|
836
|
-
|
|
1096
|
+
* Deliver one model-authored message to a direct continuable child or to the
|
|
1097
|
+
* sender's direct parent. Both directions use Steer: a running target admits
|
|
1098
|
+
* the message at its nearest step boundary, while an idle target starts a
|
|
1099
|
+
* turn. A missing direct child cold-resumes through the ordinary continuation
|
|
1100
|
+
* lifecycle. The caller signal owns the operation only until inbox acceptance.
|
|
1101
|
+
* @param sender - exact live Agent authorizing and originating the message.
|
|
1102
|
+
* @param targetId - durable direct-parent or direct-child session id.
|
|
1103
|
+
* @param content - model-authored content to deliver.
|
|
1104
|
+
* @param options - caller cancellation before acceptance.
|
|
1105
|
+
* @returns the accepted message's inbox id.
|
|
1106
|
+
* @throws when adjacency, availability, or admission rejects delivery.
|
|
1107
|
+
*/
|
|
1108
|
+
async sendMessage(sender, targetId, content, options) {
|
|
1109
|
+
if (this.ctx.agents.get(sender.id) !== sender) throw new SubagentError("message delivery requires the exact live sender agent", "UNAUTHORIZED");
|
|
1110
|
+
this.assertAdmitting(sender);
|
|
1111
|
+
const senderActivation = this.activations.get(sender.id);
|
|
1112
|
+
if (senderActivation !== void 0 && senderActivation.handle.agent === sender && senderActivation.parentSession === targetId) {
|
|
1113
|
+
options.signal.throwIfAborted();
|
|
1114
|
+
return this.sendToParent(senderActivation, sender, content);
|
|
1115
|
+
}
|
|
1116
|
+
if (sender.session.header.parentSession === targetId) throw new SubagentError(`agent "${sender.id}" is not a resident continuable child and cannot send to parent "${targetId}"`, "UNAUTHORIZED");
|
|
1117
|
+
return this.deliverToChild(sender, targetId, content, {
|
|
1118
|
+
signal: options.signal,
|
|
1119
|
+
delivery: "steer"
|
|
1120
|
+
});
|
|
1121
|
+
}
|
|
1122
|
+
/**
|
|
1123
|
+
* Queue one human-authored prompt as a distinct direct-child turn.
|
|
1124
|
+
* @param parent - exact live direct parent authorizing delivery.
|
|
1125
|
+
* @param childId - durable direct-child session id.
|
|
1126
|
+
* @param content - human-authored content to deliver.
|
|
1127
|
+
* @param source - durable host-protocol provenance.
|
|
1128
|
+
* @param signal - caller cancellation before inbox acceptance.
|
|
1129
|
+
* @returns the accepted message's inbox id.
|
|
1130
|
+
*/
|
|
1131
|
+
async queuePrompt(parent, childId, content, source, signal) {
|
|
1132
|
+
return this.deliverToChild(parent, childId, content, {
|
|
1133
|
+
source,
|
|
1134
|
+
signal,
|
|
1135
|
+
delivery: "queue"
|
|
1136
|
+
});
|
|
1137
|
+
}
|
|
1138
|
+
/**
|
|
1139
|
+
* Steer one host-authored prompt to a direct continuable child.
|
|
1140
|
+
* @param parent - exact live direct parent authorizing delivery.
|
|
1141
|
+
* @param childId - durable direct-child session id.
|
|
1142
|
+
* @param content - host-authored content to deliver.
|
|
1143
|
+
* @param source - durable host-protocol provenance.
|
|
1144
|
+
* @param signal - caller cancellation before inbox acceptance.
|
|
837
1145
|
* @returns the accepted message's inbox id.
|
|
838
|
-
* @throws when parent authority, availability, or admission rejects the delivery.
|
|
839
1146
|
*/
|
|
840
|
-
async
|
|
1147
|
+
async steerPrompt(parent, childId, content, source, signal) {
|
|
1148
|
+
return this.deliverToChild(parent, childId, content, {
|
|
1149
|
+
source,
|
|
1150
|
+
signal,
|
|
1151
|
+
delivery: "steer"
|
|
1152
|
+
});
|
|
1153
|
+
}
|
|
1154
|
+
/** Route one parent-originated delivery through residency and cold resume. */
|
|
1155
|
+
async deliverToChild(parent, childId, content, options) {
|
|
841
1156
|
this.assertAdmitting(parent);
|
|
1157
|
+
const releaseHold = this.holdOwnership(parent, childId);
|
|
1158
|
+
try {
|
|
1159
|
+
return await this.deliverFollowup(parent, childId, content, options);
|
|
1160
|
+
} catch (error) {
|
|
1161
|
+
releaseHold();
|
|
1162
|
+
throw error;
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
/** The delivery loop behind {@link deliverToChild}, run under the parent hold. */
|
|
1166
|
+
async deliverFollowup(parent, childId, content, options) {
|
|
842
1167
|
while (true) {
|
|
843
1168
|
const live = await this.locks.run(childId, async () => {
|
|
844
1169
|
const activation = this.activations.get(childId);
|
|
845
1170
|
if (activation === void 0) return this.coldResume(parent, childId, content, options);
|
|
1171
|
+
const disposal = activation.disposal;
|
|
846
1172
|
/* v8 ignore next 3 -- the send-versus-dispose cutoff: reaching this arm needs a
|
|
847
1173
|
* delivery to observe the transaction inside the same critical section that opened it,
|
|
848
1174
|
* which no test can schedule deterministically. The behavior is covered end-to-end by
|
|
849
1175
|
* "cold-resumes a delivery that lost the race with final disposal". */
|
|
850
|
-
if (
|
|
851
|
-
|
|
1176
|
+
if (disposal !== void 0) return disposal.then(() => void 0, () => void 0);
|
|
1177
|
+
if (contentHasImage(content)) {
|
|
1178
|
+
await this.assertImageCapable(activation.handle.agent, options.signal);
|
|
1179
|
+
if (activation.disposal !== void 0) {
|
|
1180
|
+
await Promise.allSettled([activation.disposal]);
|
|
1181
|
+
return;
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
return this.submitAdmitted(activation, content, options, parent);
|
|
852
1185
|
});
|
|
853
1186
|
/* v8 ignore start -- only the lost-cutoff arm above returns undefined, so only that
|
|
854
1187
|
* race reaches the retry below, which then cold-resumes a new Activation. */
|
|
@@ -892,66 +1225,24 @@ var SubagentContinuationManager = class {
|
|
|
892
1225
|
if (activation.disposal !== void 0) return;
|
|
893
1226
|
activation.handle.agent.cancel(authority.kind === "user" ? { kind: "user" } : { kind: "parent" }, { keepInbox: true });
|
|
894
1227
|
}
|
|
895
|
-
/**
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
* live, or continuation admission is closing.
|
|
906
|
-
*/
|
|
907
|
-
async reportFrom(child, content, options) {
|
|
908
|
-
options.signal.throwIfAborted();
|
|
909
|
-
this.assertAdmitting(child);
|
|
910
|
-
const activation = this.authorizeReporter(child);
|
|
911
|
-
const parent = this.resolveReportParent(child);
|
|
912
|
-
return this.deliverReport(activation, parent, content, options.delivery);
|
|
913
|
-
}
|
|
914
|
-
/** Authorize only the exact Agent of one resident Activation. */
|
|
915
|
-
authorizeReporter(child) {
|
|
916
|
-
const activation = this.activations.get(child.id);
|
|
917
|
-
if (activation === void 0 || activation.handle.agent !== child) throw new SubagentError(`agent "${child.id}" is not a live continuable subagent and cannot report`, "UNAUTHORIZED");
|
|
918
|
-
/* v8 ignore next 6 -- only a synchronous re-entrant disposer can open this
|
|
919
|
-
* transaction between exact-agent authorization and this no-await cutoff. */
|
|
920
|
-
if (activation.disposal !== void 0) throw new SubagentError(`subagent "${child.id}" activation is being disposed; the report was not delivered`, "ACTIVATION_CLOSING");
|
|
921
|
-
return activation;
|
|
922
|
-
}
|
|
923
|
-
/** Resolve the reporting child's live direct parent from durable lineage. */
|
|
924
|
-
resolveReportParent(child) {
|
|
925
|
-
const parentId = child.session.header.parentSession;
|
|
926
|
-
/* v8 ignore next -- every continuation-managed child has direct-parent metadata. */
|
|
927
|
-
const parent = parentId === void 0 ? void 0 : this.ctx.agents.get(parentId);
|
|
928
|
-
if (parent === void 0) throw new SubagentError("direct parent is not live; report was not delivered", "PARENT_UNAVAILABLE");
|
|
929
|
-
return parent;
|
|
930
|
-
}
|
|
931
|
-
/** Deliver one framed report through the selected parent scheduling preset. */
|
|
932
|
-
deliverReport(activation, parent, content, delivery) {
|
|
933
|
-
const message = createUserMessage({
|
|
934
|
-
content: [{
|
|
935
|
-
type: "text",
|
|
936
|
-
text: `Background subagent ${activation.childId} reported:`
|
|
937
|
-
}, ...content],
|
|
938
|
-
source: {
|
|
939
|
-
kind: "subagent-report",
|
|
940
|
-
form: "relay",
|
|
941
|
-
senderSessionId: activation.childId
|
|
942
|
-
}
|
|
1228
|
+
/** Deliver one resident continuable child's message to its live direct parent. */
|
|
1229
|
+
sendToParent(activation, sender, content) {
|
|
1230
|
+
/* v8 ignore next 6 -- only synchronous re-entrant teardown can open this
|
|
1231
|
+
* transaction between exact-agent authorization and this no-await span. */
|
|
1232
|
+
if (activation.disposal !== void 0) throw new SubagentError(`subagent "${sender.id}" activation is being disposed; the message was not delivered`, "ACTIVATION_CLOSING");
|
|
1233
|
+
const parent = this.ctx.agents.get(activation.parentSession);
|
|
1234
|
+
if (parent === void 0) throw new SubagentError("direct parent is not live; the message was not delivered", "PARENT_UNAVAILABLE");
|
|
1235
|
+
const message = agentMessage(sender, content);
|
|
1236
|
+
this.sendWaking(parent, message, () => {
|
|
1237
|
+
this.sendAgentMessage(parent, message);
|
|
943
1238
|
});
|
|
944
|
-
if (delivery === "wakeup") this.sendWaking(parent, message, () => {
|
|
945
|
-
this.sendReport(parent, message, delivery);
|
|
946
|
-
});
|
|
947
|
-
else this.sendReport(parent, message, delivery);
|
|
948
1239
|
return message.id;
|
|
949
1240
|
}
|
|
950
1241
|
/**
|
|
951
1242
|
* Perform one waking send to a parent, accounted against that parent's own
|
|
952
1243
|
* Activation when it has one. Registering the id before the send is what
|
|
953
1244
|
* keeps a continuation-managed parent from being judged quiescent in the
|
|
954
|
-
* window between
|
|
1245
|
+
* window between a waking send and the microtask that admits it.
|
|
955
1246
|
* @param parent - the exact live parent receiving the waking message.
|
|
956
1247
|
* @param message - the message whose id is accounted.
|
|
957
1248
|
* @param send - the synchronous waking send to perform.
|
|
@@ -961,13 +1252,12 @@ var SubagentContinuationManager = class {
|
|
|
961
1252
|
if (parentActivation !== void 0 && parentActivation.handle.agent === parent) this.admitWaking(parentActivation, message.id, send);
|
|
962
1253
|
else send();
|
|
963
1254
|
}
|
|
964
|
-
/** Send one
|
|
965
|
-
|
|
1255
|
+
/** Send one Agent message while translating only the target's own rejection. */
|
|
1256
|
+
sendAgentMessage(parent, message) {
|
|
966
1257
|
try {
|
|
967
|
-
|
|
968
|
-
else parent.inject(message);
|
|
1258
|
+
parent.steer(message);
|
|
969
1259
|
} catch (error) {
|
|
970
|
-
throw new SubagentError("direct parent is not live;
|
|
1260
|
+
throw new SubagentError("direct parent is not live; the message was not delivered", "PARENT_UNAVAILABLE", { cause: error });
|
|
971
1261
|
}
|
|
972
1262
|
}
|
|
973
1263
|
/**
|
|
@@ -1027,6 +1317,28 @@ var SubagentContinuationManager = class {
|
|
|
1027
1317
|
await Promise.all(materializations.map((materialization) => materialization.settled));
|
|
1028
1318
|
await this.disposeRoots(targetRoots, "scoped activation(s)");
|
|
1029
1319
|
}
|
|
1320
|
+
/**
|
|
1321
|
+
* Release selected resident direct children of one exact live parent without
|
|
1322
|
+
* closing admission for the parent's other continuable children. Owned
|
|
1323
|
+
* descendants are released recursively through the same lifecycle.
|
|
1324
|
+
* @param parent - exact live direct parent authorizing the selected release.
|
|
1325
|
+
* @param childIds - durable direct-child ids to release when resident.
|
|
1326
|
+
* @returns once every selected Activation released its handle.
|
|
1327
|
+
* @throws {SubagentError} `UNAUTHORIZED` when a resident target is not the
|
|
1328
|
+
* parent's direct continuable child or the parent identity is stale.
|
|
1329
|
+
*/
|
|
1330
|
+
async drainChildren(parent, childIds) {
|
|
1331
|
+
if (this.ctx.agents.get(parent.id) !== parent) throw new SubagentError("selected child teardown requires the exact live parent agent", "UNAUTHORIZED");
|
|
1332
|
+
const targets = [];
|
|
1333
|
+
for (const childId of new Set(childIds)) {
|
|
1334
|
+
const activation = this.activations.get(childId);
|
|
1335
|
+
if (activation === void 0) continue;
|
|
1336
|
+
if (activation.parentSession !== parent.id || !activation.ancestry.has(parent)) throw new SubagentError(`subagent "${childId}" is not a direct child of agent "${parent.id}"`, "UNAUTHORIZED");
|
|
1337
|
+
targets.push(activation);
|
|
1338
|
+
}
|
|
1339
|
+
for (const activation of targets) this.dispose(activation).catch(() => void 0);
|
|
1340
|
+
await this.disposeRoots(targets, "selected activation(s)");
|
|
1341
|
+
}
|
|
1030
1342
|
/** Dispose independent roots and report every branch failure after all settle. */
|
|
1031
1343
|
async disposeRoots(roots, failureSubject) {
|
|
1032
1344
|
const reasons = (await Promise.all(roots.map(async (activation) => {
|
|
@@ -1098,61 +1410,77 @@ var SubagentContinuationManager = class {
|
|
|
1098
1410
|
return "settled";
|
|
1099
1411
|
}
|
|
1100
1412
|
/**
|
|
1101
|
-
* Cold-resume a persisted child:
|
|
1413
|
+
* Cold-resume a persisted child: retain and authorize its prepared Session, fold the
|
|
1102
1414
|
* generic descriptor, create the Activation through `ctx.agents.resume()`,
|
|
1103
1415
|
* and submit the waiting turn. This never dispatches through a subagent
|
|
1104
1416
|
* provider — the persisted Session already holds the initial prefix and the
|
|
1105
1417
|
* descriptor is the whole reconstruction input.
|
|
1106
1418
|
*/
|
|
1107
1419
|
async coldResume(parent, childId, content, options) {
|
|
1108
|
-
const
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
}
|
|
1113
|
-
options.signal.throwIfAborted();
|
|
1114
|
-
throw new SubagentError(`subagent "${childId}" is unavailable`, "NOT_RESUMABLE", { cause: error });
|
|
1115
|
-
}
|
|
1116
|
-
options.signal.throwIfAborted();
|
|
1117
|
-
this.assertAdmitting(parent);
|
|
1118
|
-
this.authorizeLineage(parent, childId, loaded.meta.parentSession);
|
|
1119
|
-
const descriptor = foldSubagentDescriptor(loaded.events.slice(loaded.meta.seedLength ?? 0));
|
|
1120
|
-
if (descriptor === void 0 || descriptor.mode !== "continuable") throw new SubagentError(`subagent "${childId}" has no supported continuation state and cannot be resumed; do not retry send_message with this id`, "NOT_RESUMABLE");
|
|
1121
|
-
let activation;
|
|
1420
|
+
const env_1 = {
|
|
1421
|
+
stack: [],
|
|
1422
|
+
error: void 0,
|
|
1423
|
+
hasError: false
|
|
1424
|
+
};
|
|
1122
1425
|
try {
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1426
|
+
const query = this.requireSessionQuery();
|
|
1427
|
+
let observation;
|
|
1428
|
+
try {
|
|
1429
|
+
observation = await query.observeSession(childId, { signal: options.signal });
|
|
1430
|
+
} catch (error) {
|
|
1431
|
+
options.signal.throwIfAborted();
|
|
1432
|
+
throw new SubagentError(`subagent "${childId}" is unavailable`, "NOT_RESUMABLE", { cause: error });
|
|
1433
|
+
}
|
|
1434
|
+
const source = __addDisposableResource$1(env_1, observation, false);
|
|
1435
|
+
this.assertAdmitting(parent);
|
|
1436
|
+
this.authorizeLineage(parent, childId, source.header.parentSession);
|
|
1437
|
+
const descriptor = foldSubagentDescriptor(source.events.slice(source.inheritedEventCount));
|
|
1438
|
+
if (descriptor === void 0 || descriptor.mode !== "continuable") throw new SubagentError(`subagent "${childId}" has no supported continuation state and cannot be resumed; choose a different target`, "NOT_RESUMABLE");
|
|
1439
|
+
let activation;
|
|
1440
|
+
try {
|
|
1441
|
+
activation = await this.materialize({
|
|
1442
|
+
childId,
|
|
1443
|
+
provider: descriptor.provider,
|
|
1444
|
+
parent,
|
|
1445
|
+
agentOptions: {
|
|
1446
|
+
...descriptor.agentProvider !== void 0 ? { provider: descriptor.agentProvider } : {},
|
|
1447
|
+
...descriptor.agentModel !== void 0 ? { model: descriptor.agentModel } : {},
|
|
1448
|
+
...descriptor.agentReasoningEffort !== void 0 ? { reasoningEffort: ReasoningEffortId(descriptor.agentReasoningEffort) } : {}
|
|
1449
|
+
},
|
|
1450
|
+
composition: {
|
|
1451
|
+
persona: descriptor.persona,
|
|
1452
|
+
toolFilter: descriptor.toolFilter
|
|
1453
|
+
},
|
|
1454
|
+
signal: options.signal
|
|
1455
|
+
});
|
|
1456
|
+
} catch (error) {
|
|
1457
|
+
options.signal.throwIfAborted();
|
|
1458
|
+
if (error instanceof SubagentError) throw error;
|
|
1459
|
+
throw new SubagentError(`subagent "${childId}" is unavailable`, "NOT_RESUMABLE", { cause: error });
|
|
1460
|
+
}
|
|
1461
|
+
return await this.submitMaterialized(activation, content, options, parent);
|
|
1462
|
+
} catch (e_1) {
|
|
1463
|
+
env_1.error = e_1;
|
|
1464
|
+
env_1.hasError = true;
|
|
1465
|
+
} finally {
|
|
1466
|
+
__disposeResources$1(env_1);
|
|
1141
1467
|
}
|
|
1142
|
-
return this.submitMaterialized(activation, content, options.source, parent, options.signal);
|
|
1143
1468
|
}
|
|
1144
1469
|
/**
|
|
1145
1470
|
* Submit to a freshly materialized Activation or roll it back completely.
|
|
1146
1471
|
* @param activation - the just-published Activation to admit or release.
|
|
1147
1472
|
* @param content - the initial or resumed message content.
|
|
1148
|
-
* @param
|
|
1473
|
+
* @param options - durable source, scheduling, and pre-acceptance cancellation.
|
|
1149
1474
|
* @param parent - the live direct parent authorizing admission.
|
|
1150
|
-
* @param signal - caller cancellation owning admission until acceptance.
|
|
1151
1475
|
* @returns the accepted inbox message id.
|
|
1152
1476
|
*/
|
|
1153
|
-
async submitMaterialized(activation, content,
|
|
1477
|
+
async submitMaterialized(activation, content, options, parent) {
|
|
1154
1478
|
try {
|
|
1155
|
-
|
|
1479
|
+
if (contentHasImage(content)) {
|
|
1480
|
+
await this.assertImageCapable(activation.handle.agent, options.signal);
|
|
1481
|
+
if (activation.disposal !== void 0) throw new SubagentError(`subagent "${activation.childId}" is closing`, "ACTIVATION_CLOSING");
|
|
1482
|
+
}
|
|
1483
|
+
return this.submitAdmitted(activation, content, options, parent);
|
|
1156
1484
|
} catch (error) {
|
|
1157
1485
|
/* v8 ignore next -- rollback disposal failures must not mask the
|
|
1158
1486
|
* pre-acceptance signal, drain, or lifecycle failure. */
|
|
@@ -1161,6 +1489,28 @@ var SubagentContinuationManager = class {
|
|
|
1161
1489
|
}
|
|
1162
1490
|
}
|
|
1163
1491
|
/**
|
|
1492
|
+
* Refuse image content addressed to a child whose model accepts text only.
|
|
1493
|
+
* Callers guard with `contentHasImage`, so text-only delivery never awaits.
|
|
1494
|
+
* The check runs inside the per-child delivery lock, before the message
|
|
1495
|
+
* exists, so a rejection leaves no partial user message. When the child's
|
|
1496
|
+
* route is not fixed by its options (a request-waterfall listener owns it)
|
|
1497
|
+
* or no LLM registry is composed, delivery proceeds and the LLM layer's
|
|
1498
|
+
* text-only projection replaces each image with its stable placeholder.
|
|
1499
|
+
* @param agent - the live or freshly materialized child agent.
|
|
1500
|
+
* @param signal - caller cancellation bounding the model-info read.
|
|
1501
|
+
* @throws {SubagentError} `MODEL_DOES_NOT_SUPPORT_IMAGES` when the child's resolved model declines image input.
|
|
1502
|
+
*/
|
|
1503
|
+
async assertImageCapable(agent, signal) {
|
|
1504
|
+
const { provider, model } = agent.options;
|
|
1505
|
+
if (provider === void 0 || model === void 0) return;
|
|
1506
|
+
const llm = this.ctx.get("llm");
|
|
1507
|
+
/* v8 ignore next -- a deployment without the LLM registry serves no model
|
|
1508
|
+
* to refuse against; delivery then defers to the text-only projection. */
|
|
1509
|
+
if (llm === void 0) return;
|
|
1510
|
+
const info = await llm.resolveModelInfo(provider, model, signal);
|
|
1511
|
+
if (info.inputModalities !== void 0 && !info.inputModalities.includes("image")) throw new SubagentError(`Model "${model}" does not support image input.`, "MODEL_DOES_NOT_SUPPORT_IMAGES");
|
|
1512
|
+
}
|
|
1513
|
+
/**
|
|
1164
1514
|
* Create or resume the child Agent through the private activation-owner
|
|
1165
1515
|
* scope, install the handle in a fresh Activation, and register ownership on
|
|
1166
1516
|
* a continuation-managed parent. Rejection leaves no Activation, no handle,
|
|
@@ -1189,9 +1539,12 @@ var SubagentContinuationManager = class {
|
|
|
1189
1539
|
const { childId, provider, parent, create } = inputs;
|
|
1190
1540
|
inputs.signal.throwIfAborted();
|
|
1191
1541
|
const setup = (childCtx) => {
|
|
1192
|
-
|
|
1542
|
+
const child = childCtx.agent;
|
|
1543
|
+
if (create !== void 0) {
|
|
1544
|
+
child.session.append("subagent/descriptor", create.descriptor);
|
|
1545
|
+
appendDelegatedPolicyOverrides(child.session, create.delegatedPolicies);
|
|
1546
|
+
}
|
|
1193
1547
|
applyChildComposition(childCtx, parent, inputs.composition);
|
|
1194
|
-
return this.setupRegistry.apply(childCtx);
|
|
1195
1548
|
};
|
|
1196
1549
|
const observer = this.host.observeActivation(provider, childId, parent);
|
|
1197
1550
|
const handle = create === void 0 ? await this.ownerCtx.agents.resume({
|
|
@@ -1202,7 +1555,8 @@ var SubagentContinuationManager = class {
|
|
|
1202
1555
|
}) : await this.ownerCtx.agents.create({
|
|
1203
1556
|
sessionId: childId,
|
|
1204
1557
|
meta: create.meta,
|
|
1205
|
-
seed: create.seed,
|
|
1558
|
+
...create.seed === void 0 ? {} : { seed: create.seed },
|
|
1559
|
+
inheritedEventCount: create.inheritedEventCount,
|
|
1206
1560
|
agentOptions: inputs.agentOptions,
|
|
1207
1561
|
signal: inputs.signal,
|
|
1208
1562
|
setup
|
|
@@ -1284,14 +1638,15 @@ var SubagentContinuationManager = class {
|
|
|
1284
1638
|
* inbox id. Acceptance is the operation's success boundary; the manager owns
|
|
1285
1639
|
* the Activation independently afterwards.
|
|
1286
1640
|
*/
|
|
1287
|
-
submit(activation, content,
|
|
1641
|
+
submit(activation, content, options, parent) {
|
|
1288
1642
|
this.acquireOwnership(parent, activation.childId);
|
|
1289
|
-
const message = createUserMessage({
|
|
1643
|
+
const message = options.source === void 0 ? agentMessage(parent, content) : createUserMessage({
|
|
1290
1644
|
content,
|
|
1291
|
-
source
|
|
1645
|
+
source: options.source
|
|
1292
1646
|
});
|
|
1293
1647
|
const accepted = this.admitWaking(activation, message.id, () => {
|
|
1294
|
-
activation.handle.agent.
|
|
1648
|
+
if (options.delivery === "steer") activation.handle.agent.steer(message);
|
|
1649
|
+
else activation.handle.agent.followup(message);
|
|
1295
1650
|
});
|
|
1296
1651
|
activation.announced = true;
|
|
1297
1652
|
return accepted;
|
|
@@ -1319,14 +1674,14 @@ var SubagentContinuationManager = class {
|
|
|
1319
1674
|
* manager drain, or Activation disposal that wins before this synchronous
|
|
1320
1675
|
* span rejects without inbox acceptance.
|
|
1321
1676
|
*/
|
|
1322
|
-
submitAdmitted(activation, content,
|
|
1323
|
-
signal.throwIfAborted();
|
|
1677
|
+
submitAdmitted(activation, content, options, parent) {
|
|
1678
|
+
options.signal.throwIfAborted();
|
|
1324
1679
|
this.assertAdmitting(parent);
|
|
1325
1680
|
/* v8 ignore next 6 -- only a synchronous re-entrant disposer can change
|
|
1326
1681
|
* this field between the caller's live check and this no-await boundary. */
|
|
1327
1682
|
if (disposalOf(activation) !== void 0) throw new SubagentError(`subagent "${activation.childId}" activation is being disposed; the message was not accepted`, "ACTIVATION_CLOSING");
|
|
1328
1683
|
this.authorizeLineage(parent, activation.childId, activation.handle.agent.session.header.parentSession);
|
|
1329
|
-
return this.submit(activation, content,
|
|
1684
|
+
return this.submit(activation, content, options, parent);
|
|
1330
1685
|
}
|
|
1331
1686
|
/**
|
|
1332
1687
|
* Authorize one operation against the durable direct-parent lineage. Other
|
|
@@ -1504,147 +1859,26 @@ var SubagentContinuationManager = class {
|
|
|
1504
1859
|
if (persistence === void 0) throw new SubagentError("continuable subagents require session persistence (load a dsh-session-persistence backend)", "PERSISTENCE_UNAVAILABLE");
|
|
1505
1860
|
return persistence;
|
|
1506
1861
|
}
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
* child's unpublished creation context.
|
|
1513
|
-
*
|
|
1514
|
-
* A contribution grants a child-scoped capability without teaching the
|
|
1515
|
-
* continuation manager which capabilities exist. The manager owns residency;
|
|
1516
|
-
* this registry owns the join between plugin lifetime, unpublished setup, and
|
|
1517
|
-
* Activation disposal, so no installation outlives either owner and no removed
|
|
1518
|
-
* contribution can be installed after revocation reports completion.
|
|
1519
|
-
*
|
|
1520
|
-
* @module @xneog/dsh-subagent/activation-setup-registry
|
|
1521
|
-
*/
|
|
1522
|
-
/** Re-read mutable removal state after a contribution may have revoked itself. */
|
|
1523
|
-
function isRemoved(registration) {
|
|
1524
|
-
return registration.removed;
|
|
1525
|
-
}
|
|
1526
|
-
/**
|
|
1527
|
-
* Owns continuable-child setup registrations, installations, rollback, child
|
|
1528
|
-
* cleanup, and immediate live revocation.
|
|
1529
|
-
*/
|
|
1530
|
-
var SubagentActivationSetupRegistry = class {
|
|
1531
|
-
/** Live contributions in installation order. */
|
|
1532
|
-
registrations = /* @__PURE__ */ new Set();
|
|
1533
|
-
/** Child context to its live installations. */
|
|
1534
|
-
byChild = /* @__PURE__ */ new Map();
|
|
1535
|
-
/**
|
|
1536
|
-
* Register one contribution.
|
|
1537
|
-
* @param contribution - synchronous child-scope installer.
|
|
1538
|
-
* @returns an idempotent registration undo.
|
|
1539
|
-
* @throws after attempting every installation when any disposer fails.
|
|
1540
|
-
*/
|
|
1541
|
-
register(contribution) {
|
|
1542
|
-
const registration = {
|
|
1543
|
-
contribution,
|
|
1544
|
-
removed: false,
|
|
1545
|
-
installations: /* @__PURE__ */ new Set()
|
|
1546
|
-
};
|
|
1547
|
-
this.registrations.add(registration);
|
|
1548
|
-
return () => {
|
|
1549
|
-
if (registration.removed) return;
|
|
1550
|
-
registration.removed = true;
|
|
1551
|
-
this.registrations.delete(registration);
|
|
1552
|
-
this.releaseAll([...registration.installations], "contribution removal");
|
|
1553
|
-
};
|
|
1554
|
-
}
|
|
1555
|
-
/**
|
|
1556
|
-
* Install every live contribution into one unpublished child context.
|
|
1557
|
-
* @param childCtx - the child's unpublished scoped context.
|
|
1558
|
-
* @returns the provisioning commit consumed at Agent publication.
|
|
1559
|
-
*/
|
|
1560
|
-
apply(childCtx) {
|
|
1561
|
-
const state = {
|
|
1562
|
-
installations: [],
|
|
1563
|
-
invalidated: false
|
|
1564
|
-
};
|
|
1565
|
-
try {
|
|
1566
|
-
for (const registration of [...this.registrations]) {
|
|
1567
|
-
/* v8 ignore next -- only a synchronous re-entrant revocation of an
|
|
1568
|
-
* already-snapshotted registration reaches this guard. */
|
|
1569
|
-
if (registration.removed) continue;
|
|
1570
|
-
const installation = {
|
|
1571
|
-
registration,
|
|
1572
|
-
childCtx,
|
|
1573
|
-
dispose: registration.contribution(childCtx),
|
|
1574
|
-
released: false,
|
|
1575
|
-
transaction: state
|
|
1576
|
-
};
|
|
1577
|
-
registration.installations.add(installation);
|
|
1578
|
-
state.installations.push(installation);
|
|
1579
|
-
let indexed = this.byChild.get(childCtx);
|
|
1580
|
-
if (indexed === void 0) {
|
|
1581
|
-
indexed = /* @__PURE__ */ new Set();
|
|
1582
|
-
this.byChild.set(childCtx, indexed);
|
|
1583
|
-
}
|
|
1584
|
-
indexed.add(installation);
|
|
1585
|
-
if (isRemoved(registration)) this.release(installation);
|
|
1586
|
-
}
|
|
1587
|
-
} catch (error) {
|
|
1588
|
-
try {
|
|
1589
|
-
this.releaseAll([...state.installations], "setup rollback");
|
|
1590
|
-
} catch (releaseFailure) {}
|
|
1591
|
-
throw error;
|
|
1592
|
-
}
|
|
1593
|
-
childCtx.effect(() => () => {
|
|
1594
|
-
this.releaseChild(childCtx);
|
|
1595
|
-
}, "subagents.activationSetup()");
|
|
1596
|
-
return { commit: () => {
|
|
1597
|
-
if (state.invalidated) throw new SubagentError("a continuable-subagent setup contribution was revoked while this child was being built; the child was not established", "ACTIVATION_SETUP_REVOKED");
|
|
1598
|
-
for (const installation of state.installations) installation.transaction = void 0;
|
|
1599
|
-
} };
|
|
1600
|
-
}
|
|
1601
|
-
/** Release every remaining installation owned by one disposed child scope. */
|
|
1602
|
-
releaseChild(childCtx) {
|
|
1603
|
-
const indexed = this.byChild.get(childCtx) ?? [];
|
|
1604
|
-
this.releaseAll([...indexed], "child scope disposal");
|
|
1605
|
-
}
|
|
1606
|
-
/**
|
|
1607
|
-
* Release a batch completely before reporting disposer failures.
|
|
1608
|
-
* @param installations - records to release.
|
|
1609
|
-
* @param during - operation name for diagnostics.
|
|
1610
|
-
*/
|
|
1611
|
-
releaseAll(installations, during) {
|
|
1612
|
-
const failures = [];
|
|
1613
|
-
for (const installation of installations) try {
|
|
1614
|
-
this.release(installation);
|
|
1615
|
-
} catch (error) {
|
|
1616
|
-
failures.push(error);
|
|
1617
|
-
}
|
|
1618
|
-
if (failures.length === 0) return;
|
|
1619
|
-
throw new SubagentError(`continuable-subagent setup ${during} failed to release ${failures.length} installation(s): ` + failures.map((failure) => errorChain(failure)).join("; "), "ACTIVATION_SETUP_RELEASE_FAILED");
|
|
1620
|
-
}
|
|
1621
|
-
/** Drop one installation from both indices and dispose it exactly once. */
|
|
1622
|
-
release(installation) {
|
|
1623
|
-
if (installation.released) return;
|
|
1624
|
-
installation.released = true;
|
|
1625
|
-
installation.registration.installations.delete(installation);
|
|
1626
|
-
const indexed = this.byChild.get(installation.childCtx);
|
|
1627
|
-
/* v8 ignore next 4 -- every live installation is indexed until this method removes it. */
|
|
1628
|
-
if (indexed !== void 0) {
|
|
1629
|
-
indexed.delete(installation);
|
|
1630
|
-
if (indexed.size === 0) this.byChild.delete(installation.childCtx);
|
|
1631
|
-
}
|
|
1632
|
-
if (installation.transaction !== void 0) installation.transaction.invalidated = true;
|
|
1633
|
-
installation.dispose();
|
|
1862
|
+
/** Resolve the Session query service used for cold child observations. */
|
|
1863
|
+
requireSessionQuery() {
|
|
1864
|
+
const query = this.ctx.get("sessionQuery");
|
|
1865
|
+
if (query === void 0) throw new SubagentError("continuable subagents require session query (load @xneog/dsh-session-query)", "CONTINUATION_UNAVAILABLE");
|
|
1866
|
+
return query;
|
|
1634
1867
|
}
|
|
1635
1868
|
};
|
|
1636
1869
|
//#endregion
|
|
1637
1870
|
//#region lib/types/list-children.js
|
|
1638
1871
|
/**
|
|
1639
1872
|
* Read-only enumeration of durable subagent children and descendant trees
|
|
1640
|
-
*
|
|
1641
|
-
*
|
|
1642
|
-
*
|
|
1873
|
+
* through the Session query service. Candidates come from one live-preferred
|
|
1874
|
+
* corpus; each child's mode/label is the registered `subagent` projection
|
|
1875
|
+
* unit's value, resolved
|
|
1643
1876
|
* down a three-rung ladder: the registry's watermark cache for a live child,
|
|
1644
|
-
*
|
|
1645
|
-
*
|
|
1646
|
-
*
|
|
1647
|
-
* is the single classification
|
|
1877
|
+
* an unseeded durable projection-cache row, and one shared Session observation
|
|
1878
|
+
* otherwise. A seeded header deliberately lacks its exact inherited cut, so
|
|
1879
|
+
* it takes the body-bearing observation path before classifying an identity.
|
|
1880
|
+
* The projection fold is the single classification
|
|
1881
|
+
* authority — this module parses no descriptor
|
|
1648
1882
|
* itself. Absent persistence, enumeration is live-only: a cold child is
|
|
1649
1883
|
* unreachable for resume anyway, so its absence is capability absence, not an
|
|
1650
1884
|
* error. The module owns no catalog state and does not consult Activation,
|
|
@@ -1652,10 +1886,68 @@ var SubagentActivationSetupRegistry = class {
|
|
|
1652
1886
|
*
|
|
1653
1887
|
* @module @xneog/dsh-subagent
|
|
1654
1888
|
*/
|
|
1889
|
+
var __addDisposableResource = function(env, value, async) {
|
|
1890
|
+
if (value !== null && value !== void 0) {
|
|
1891
|
+
if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
|
|
1892
|
+
var dispose, inner;
|
|
1893
|
+
if (async) {
|
|
1894
|
+
if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
|
|
1895
|
+
dispose = value[Symbol.asyncDispose];
|
|
1896
|
+
}
|
|
1897
|
+
if (dispose === void 0) {
|
|
1898
|
+
if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
|
|
1899
|
+
dispose = value[Symbol.dispose];
|
|
1900
|
+
if (async) inner = dispose;
|
|
1901
|
+
}
|
|
1902
|
+
if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
|
|
1903
|
+
if (inner) dispose = function() {
|
|
1904
|
+
try {
|
|
1905
|
+
inner.call(this);
|
|
1906
|
+
} catch (e) {
|
|
1907
|
+
return Promise.reject(e);
|
|
1908
|
+
}
|
|
1909
|
+
};
|
|
1910
|
+
env.stack.push({
|
|
1911
|
+
value,
|
|
1912
|
+
dispose,
|
|
1913
|
+
async
|
|
1914
|
+
});
|
|
1915
|
+
} else if (async) env.stack.push({ async: true });
|
|
1916
|
+
return value;
|
|
1917
|
+
};
|
|
1918
|
+
var __disposeResources = (function(SuppressedError) {
|
|
1919
|
+
return function(env) {
|
|
1920
|
+
function fail(e) {
|
|
1921
|
+
env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
|
|
1922
|
+
env.hasError = true;
|
|
1923
|
+
}
|
|
1924
|
+
var r, s = 0;
|
|
1925
|
+
function next() {
|
|
1926
|
+
while (r = env.stack.pop()) try {
|
|
1927
|
+
if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
|
|
1928
|
+
if (r.dispose) {
|
|
1929
|
+
var result = r.dispose.call(r.value);
|
|
1930
|
+
if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) {
|
|
1931
|
+
fail(e);
|
|
1932
|
+
return next();
|
|
1933
|
+
});
|
|
1934
|
+
} else s |= 1;
|
|
1935
|
+
} catch (e) {
|
|
1936
|
+
fail(e);
|
|
1937
|
+
}
|
|
1938
|
+
if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
|
|
1939
|
+
if (env.hasError) throw env.error;
|
|
1940
|
+
}
|
|
1941
|
+
return next();
|
|
1942
|
+
};
|
|
1943
|
+
})(typeof SuppressedError === "function" ? SuppressedError : function(error, suppressed, message) {
|
|
1944
|
+
var e = new Error(message);
|
|
1945
|
+
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
1946
|
+
});
|
|
1655
1947
|
/**
|
|
1656
|
-
* Concurrent cold
|
|
1657
|
-
*
|
|
1658
|
-
*
|
|
1948
|
+
* Concurrent cold observations per explicit catalog listing. Current Session
|
|
1949
|
+
* persistence providers are local; a networked provider must promote this to
|
|
1950
|
+
* a validated deployment setting.
|
|
1659
1951
|
*/
|
|
1660
1952
|
const COLD_READ_CONCURRENCY = 4;
|
|
1661
1953
|
/**
|
|
@@ -1663,9 +1955,8 @@ const COLD_READ_CONCURRENCY = 4;
|
|
|
1663
1955
|
* live-preferred merge of `ctx.sessions` and optional session persistence,
|
|
1664
1956
|
* serving each identity from the `subagent` projection unit: the registry's
|
|
1665
1957
|
* watermark snapshot for a live child; for a cold one, a durable
|
|
1666
|
-
* projection-cache
|
|
1667
|
-
*
|
|
1668
|
-
* registry.
|
|
1958
|
+
* projection-cache read for an unseeded lifecycle, else one bounded-concurrency
|
|
1959
|
+
* shared Session observation carrying the exact inherited cut.
|
|
1669
1960
|
* @see SubagentRuntime.listChildren for the public cancellation and failure contract.
|
|
1670
1961
|
* @param ctx - context carrying the session store, the projection registry,
|
|
1671
1962
|
* optional persistence, and the optional projection cache.
|
|
@@ -1714,32 +2005,30 @@ async function prepareListing(ctx, signal) {
|
|
|
1714
2005
|
const sessions = ctx.get("sessions");
|
|
1715
2006
|
if (sessions === void 0) throw new SubagentError("listing subagents requires the session store (load @xneog/dsh-session)", "SUBAGENT_CONTROL_SESSION_STORE_UNAVAILABLE");
|
|
1716
2007
|
assertListingNotCancelled(signal);
|
|
1717
|
-
const
|
|
2008
|
+
const query = ctx.get("sessionQuery");
|
|
2009
|
+
if (query === void 0) throw new SubagentError("listing subagents requires the sessionQuery service (load @xneog/dsh-session-query)", "SUBAGENT_CONTROL_QUERY_UNAVAILABLE");
|
|
1718
2010
|
const cache = ctx.get("sessionProjectionCache");
|
|
1719
|
-
let
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
} catch (error) {
|
|
1724
|
-
assertListingNotCancelled(signal);
|
|
1725
|
-
throw error;
|
|
1726
|
-
}
|
|
2011
|
+
let records;
|
|
2012
|
+
try {
|
|
2013
|
+
records = await query.listSessions(signal);
|
|
2014
|
+
} catch (error) {
|
|
1727
2015
|
assertListingNotCancelled(signal);
|
|
2016
|
+
throw error;
|
|
1728
2017
|
}
|
|
2018
|
+
assertListingNotCancelled(signal);
|
|
1729
2019
|
const corpus = /* @__PURE__ */ new Map();
|
|
1730
|
-
for (const
|
|
1731
|
-
header
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
});
|
|
2020
|
+
for (const record of records) {
|
|
2021
|
+
const live = sessions.get(record.header.id);
|
|
2022
|
+
corpus.set(record.header.id, {
|
|
2023
|
+
header: live?.header ?? record.header,
|
|
2024
|
+
live
|
|
2025
|
+
});
|
|
2026
|
+
}
|
|
1738
2027
|
const subagentParents = /* @__PURE__ */ new Set();
|
|
1739
2028
|
for (const record of corpus.values()) if (record.header.origin === "subagent" && record.header.parentSession !== void 0) subagentParents.add(record.header.parentSession);
|
|
1740
2029
|
return {
|
|
1741
2030
|
projections,
|
|
1742
|
-
|
|
2031
|
+
query,
|
|
1743
2032
|
cache,
|
|
1744
2033
|
corpus,
|
|
1745
2034
|
subagentParents
|
|
@@ -1747,7 +2036,7 @@ async function prepareListing(ctx, signal) {
|
|
|
1747
2036
|
}
|
|
1748
2037
|
/** Resolve projection-backed rows for aligned candidates with bounded cold reads. */
|
|
1749
2038
|
async function resolveCandidateRows(candidates, listing, signal) {
|
|
1750
|
-
const { projections,
|
|
2039
|
+
const { projections, query, cache, subagentParents } = listing;
|
|
1751
2040
|
const rows = Array.from({ length: candidates.length });
|
|
1752
2041
|
const coldReads = [];
|
|
1753
2042
|
candidates.forEach((candidate, index) => {
|
|
@@ -1761,7 +2050,7 @@ async function resolveCandidateRows(candidates, listing, signal) {
|
|
|
1761
2050
|
}
|
|
1762
2051
|
let identity;
|
|
1763
2052
|
try {
|
|
1764
|
-
identity = projections.snapshot(candidate.live).values.subagent;
|
|
2053
|
+
identity = projections.snapshot(candidate.live, ["subagent"]).values.subagent;
|
|
1765
2054
|
} catch {
|
|
1766
2055
|
rows[index] = {
|
|
1767
2056
|
kind: "diagnostic",
|
|
@@ -1770,13 +2059,13 @@ async function resolveCandidateRows(candidates, listing, signal) {
|
|
|
1770
2059
|
};
|
|
1771
2060
|
return;
|
|
1772
2061
|
}
|
|
1773
|
-
if (identity === void 0 || identity === null) return;
|
|
2062
|
+
if (identity === void 0 || identity === null || !candidate.live.isOwnSeq(identity.seq)) return;
|
|
1774
2063
|
rows[index] = childRow(childId, identity, "running", subagentParents.has(childId));
|
|
1775
2064
|
});
|
|
1776
|
-
if (
|
|
2065
|
+
if (coldReads.length > 0) {
|
|
1777
2066
|
const queue = [...coldReads];
|
|
1778
2067
|
await Promise.all(Array.from({ length: Math.min(COLD_READ_CONCURRENCY, queue.length) }, async () => {
|
|
1779
|
-
for (let job = queue.shift(); job !== void 0; job = queue.shift()) rows[job.index] = await resolveColdIdentity(
|
|
2068
|
+
for (let job = queue.shift(); job !== void 0; job = queue.shift()) rows[job.index] = await resolveColdIdentity(query, cache, job.header, subagentParents.has(job.header.id), signal);
|
|
1780
2069
|
}));
|
|
1781
2070
|
}
|
|
1782
2071
|
assertListingNotCancelled(signal);
|
|
@@ -1820,60 +2109,62 @@ function compareCorpusRecords(a, b) {
|
|
|
1820
2109
|
return a.header.createdAt - b.header.createdAt || a.header.id.localeCompare(b.header.id);
|
|
1821
2110
|
}
|
|
1822
2111
|
/**
|
|
1823
|
-
* Resolve one cold candidate down the remaining ladder:
|
|
1824
|
-
* projection-cache row
|
|
1825
|
-
*
|
|
1826
|
-
*
|
|
1827
|
-
* projections). A failed inspection is one transient `unavailable` row
|
|
1828
|
-
* retried on the next listing; an inspection naming another lifecycle, and a
|
|
2112
|
+
* Resolve one cold candidate down the remaining ladder: an unseeded durable
|
|
2113
|
+
* projection-cache row, otherwise one shared Session observation. An absent or transiently failed
|
|
2114
|
+
* observation is one `unavailable` row retried on the next listing; an observation
|
|
2115
|
+
* source naming another lifecycle, and a
|
|
1829
2116
|
* settled log the fold cannot identify — or that makes any registered unit
|
|
1830
2117
|
* throw — are final, so they report `corrupt`.
|
|
1831
2118
|
*/
|
|
1832
|
-
async function resolveColdIdentity(
|
|
1833
|
-
const
|
|
1834
|
-
|
|
1835
|
-
|
|
2119
|
+
async function resolveColdIdentity(query, cache, header, hasChildren, signal) {
|
|
2120
|
+
const env_1 = {
|
|
2121
|
+
stack: [],
|
|
2122
|
+
error: void 0,
|
|
2123
|
+
hasError: false
|
|
2124
|
+
};
|
|
2125
|
+
try {
|
|
2126
|
+
const childId = header.id;
|
|
2127
|
+
if (cache !== void 0 && !header.isSeeded) {
|
|
2128
|
+
let cached;
|
|
2129
|
+
try {
|
|
2130
|
+
cached = cache.cachedSnapshot(header, SessionLogOffset(0), ["subagent"])?.values.subagent;
|
|
2131
|
+
} catch {
|
|
2132
|
+
cached = void 0;
|
|
2133
|
+
}
|
|
2134
|
+
if (cached !== void 0 && cached !== null) return childRow(childId, cached, "inactive", hasChildren);
|
|
2135
|
+
}
|
|
2136
|
+
assertListingNotCancelled(signal);
|
|
2137
|
+
let observation;
|
|
1836
2138
|
try {
|
|
1837
|
-
|
|
1838
|
-
} catch {
|
|
1839
|
-
|
|
2139
|
+
observation = await query.observeSession(childId, { ...signal === void 0 ? {} : { signal } });
|
|
2140
|
+
} catch (error) {
|
|
2141
|
+
assertListingNotCancelled(signal);
|
|
2142
|
+
return {
|
|
2143
|
+
kind: "diagnostic",
|
|
2144
|
+
id: childId,
|
|
2145
|
+
reason: sessionQueryCode(error) === "SESSION_QUERY_CORRUPT_SESSION" || sessionQueryCode(error) === "SESSION_QUERY_SOURCE_CONFLICT" ? "corrupt" : "unavailable"
|
|
2146
|
+
};
|
|
1840
2147
|
}
|
|
1841
|
-
|
|
1842
|
-
}
|
|
1843
|
-
assertListingNotCancelled(signal);
|
|
1844
|
-
let inspected;
|
|
1845
|
-
try {
|
|
1846
|
-
inspected = await persistence.inspect(childId, signal);
|
|
1847
|
-
} catch {
|
|
2148
|
+
const ownedObservation = __addDisposableResource(env_1, observation, false);
|
|
1848
2149
|
assertListingNotCancelled(signal);
|
|
1849
|
-
return {
|
|
2150
|
+
if (!sameLifecycle(ownedObservation.header, header)) return {
|
|
1850
2151
|
kind: "diagnostic",
|
|
1851
2152
|
id: childId,
|
|
1852
|
-
reason: "
|
|
2153
|
+
reason: "corrupt"
|
|
1853
2154
|
};
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
if (!sameLifecycle(inspected.meta, header)) return {
|
|
1857
|
-
kind: "diagnostic",
|
|
1858
|
-
id: childId,
|
|
1859
|
-
reason: "corrupt"
|
|
1860
|
-
};
|
|
1861
|
-
let identity;
|
|
1862
|
-
try {
|
|
1863
|
-
identity = projections.restore({}, inspected.events, 0).snapshot.values.subagent;
|
|
1864
|
-
} catch {
|
|
1865
|
-
return {
|
|
2155
|
+
const identity = ownedObservation.projections?.values.subagent;
|
|
2156
|
+
if (identity === void 0 || identity === null || identity.seq < ownedObservation.inheritedEventCount) return {
|
|
1866
2157
|
kind: "diagnostic",
|
|
1867
2158
|
id: childId,
|
|
1868
2159
|
reason: "corrupt"
|
|
1869
2160
|
};
|
|
2161
|
+
return childRow(childId, identity, "inactive", hasChildren);
|
|
2162
|
+
} catch (e_1) {
|
|
2163
|
+
env_1.error = e_1;
|
|
2164
|
+
env_1.hasError = true;
|
|
2165
|
+
} finally {
|
|
2166
|
+
__disposeResources(env_1);
|
|
1870
2167
|
}
|
|
1871
|
-
if (identity === void 0 || identity === null) return {
|
|
1872
|
-
kind: "diagnostic",
|
|
1873
|
-
id: childId,
|
|
1874
|
-
reason: "corrupt"
|
|
1875
|
-
};
|
|
1876
|
-
return childRow(childId, identity, "inactive", hasChildren);
|
|
1877
2168
|
}
|
|
1878
2169
|
/** Materialize one served identity as its child row. */
|
|
1879
2170
|
function childRow(id, identity, activity, hasChildren) {
|
|
@@ -1900,8 +2191,10 @@ const LIFECYCLE_WITNESS_KEYS = [
|
|
|
1900
2191
|
"createdAt",
|
|
1901
2192
|
"cwd",
|
|
1902
2193
|
"parentSession",
|
|
1903
|
-
"
|
|
1904
|
-
"delegationDepth"
|
|
2194
|
+
"isSeeded",
|
|
2195
|
+
"delegationDepth",
|
|
2196
|
+
"origin",
|
|
2197
|
+
"agentPreset"
|
|
1905
2198
|
];
|
|
1906
2199
|
/** Whether an inspected log still belongs to the enumerated lifecycle. */
|
|
1907
2200
|
function sameLifecycle(meta, expected) {
|
|
@@ -1911,6 +2204,28 @@ function sameLifecycle(meta, expected) {
|
|
|
1911
2204
|
function assertListingNotCancelled(signal) {
|
|
1912
2205
|
if (signal?.aborted) throw new SubagentError("subagent listing was cancelled", "CANCELLED");
|
|
1913
2206
|
}
|
|
2207
|
+
function sessionQueryCode(error) {
|
|
2208
|
+
return error instanceof Error && "code" in error ? error.code : void 0;
|
|
2209
|
+
}
|
|
2210
|
+
//#endregion
|
|
2211
|
+
//#region lib/types/projection.js
|
|
2212
|
+
/**
|
|
2213
|
+
* Pure session projections for subagent identity (mode/label) and active-turn
|
|
2214
|
+
* duration.
|
|
2215
|
+
*
|
|
2216
|
+
* @module @xneog/dsh-subagent/projection
|
|
2217
|
+
*/
|
|
2218
|
+
const activeIntervalSchema = z.object({
|
|
2219
|
+
since: z.number().int().nonnegative(),
|
|
2220
|
+
through: z.number().int().nonnegative()
|
|
2221
|
+
}).strict();
|
|
2222
|
+
const projectionSchema = z.object({
|
|
2223
|
+
settledMs: z.number().int().nonnegative(),
|
|
2224
|
+
active: activeIntervalSchema.optional()
|
|
2225
|
+
}).strict().transform(({ settledMs, active }) => ({
|
|
2226
|
+
settledMs,
|
|
2227
|
+
...active === void 0 ? {} : { active }
|
|
2228
|
+
}));
|
|
1914
2229
|
/**
|
|
1915
2230
|
* Fold turn boundaries around the child's own durable descriptor.
|
|
1916
2231
|
*
|
|
@@ -1921,12 +2236,11 @@ function assertListingNotCancelled(signal) {
|
|
|
1921
2236
|
*/
|
|
1922
2237
|
const subagentTimingProjectionDefinition = {
|
|
1923
2238
|
key: "subagentTiming",
|
|
1924
|
-
|
|
2239
|
+
stateSchema: z.object({
|
|
1925
2240
|
settledMs: z.number().int().nonnegative(),
|
|
1926
|
-
active:
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
}).strict().optional()
|
|
2241
|
+
active: activeIntervalSchema.optional(),
|
|
2242
|
+
pendingTurnStart: z.number().int().nonnegative().optional(),
|
|
2243
|
+
descriptorSeen: z.boolean()
|
|
1930
2244
|
}).strict(),
|
|
1931
2245
|
init: () => ({
|
|
1932
2246
|
descriptorSeen: false,
|
|
@@ -1976,21 +2290,26 @@ const subagentTimingProjectionDefinition = {
|
|
|
1976
2290
|
}
|
|
1977
2291
|
};
|
|
1978
2292
|
},
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
2293
|
+
wire: {
|
|
2294
|
+
viewSchema: projectionSchema,
|
|
2295
|
+
view: (state) => ({
|
|
2296
|
+
settledMs: state.settledMs,
|
|
2297
|
+
...state.active === void 0 ? {} : { active: state.active }
|
|
2298
|
+
})
|
|
2299
|
+
},
|
|
1983
2300
|
stateVersion: 2
|
|
1984
2301
|
};
|
|
1985
|
-
const
|
|
2302
|
+
const identityValueSchema = z.discriminatedUnion("mode", [z.object({
|
|
1986
2303
|
mode: z.literal("one-shot"),
|
|
1987
2304
|
label: z.string().optional(),
|
|
1988
|
-
seq: z.number().int().nonnegative()
|
|
2305
|
+
seq: z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER).transform(SessionSeq)
|
|
1989
2306
|
}).strict(), z.object({
|
|
1990
2307
|
mode: z.literal("continuable"),
|
|
1991
2308
|
label: z.string(),
|
|
1992
|
-
seq: z.number().int().nonnegative()
|
|
1993
|
-
}).strict()])
|
|
2309
|
+
seq: z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER).transform(SessionSeq)
|
|
2310
|
+
}).strict()]);
|
|
2311
|
+
const identitySchema = identityValueSchema.nullable();
|
|
2312
|
+
const identityStateSchema = z.object({ identity: identityValueSchema.optional() }).strict();
|
|
1994
2313
|
/** Interpret one `subagent/descriptor` event's identity; no value when the payload cannot be trusted. */
|
|
1995
2314
|
function descriptorIdentity(event) {
|
|
1996
2315
|
let descriptor;
|
|
@@ -2023,14 +2342,17 @@ function descriptorIdentity(event) {
|
|
|
2023
2342
|
*/
|
|
2024
2343
|
const subagentIdentityProjectionDefinition = {
|
|
2025
2344
|
key: "subagent",
|
|
2026
|
-
|
|
2345
|
+
stateSchema: identityStateSchema,
|
|
2027
2346
|
init: () => ({}),
|
|
2028
2347
|
apply: (state, event) => {
|
|
2029
2348
|
if (event.type !== "subagent/descriptor") return state;
|
|
2030
2349
|
const identity = descriptorIdentity(event);
|
|
2031
2350
|
return identity === void 0 ? {} : { identity };
|
|
2032
2351
|
},
|
|
2033
|
-
|
|
2352
|
+
wire: {
|
|
2353
|
+
viewSchema: identitySchema,
|
|
2354
|
+
view: (state) => state.identity ?? null
|
|
2355
|
+
},
|
|
2034
2356
|
stateVersion: 2
|
|
2035
2357
|
};
|
|
2036
2358
|
//#endregion
|
|
@@ -2047,13 +2369,38 @@ const subagentIdentityProjectionDefinition = {
|
|
|
2047
2369
|
*
|
|
2048
2370
|
* @module @xneog/dsh-subagent/out-of-process
|
|
2049
2371
|
*/
|
|
2372
|
+
/** Maximum UTF-8 size of {@link SubagentResult.diagnostic}. */
|
|
2373
|
+
const MAX_SUBAGENT_DIAGNOSTIC_BYTES = 4096;
|
|
2374
|
+
const DIAGNOSTIC_TRUNCATION_SUFFIX = "\n[diagnostic truncated]";
|
|
2375
|
+
const utf8Encoder = new TextEncoder();
|
|
2376
|
+
const utf8Decoder = new TextDecoder();
|
|
2377
|
+
/**
|
|
2378
|
+
* Limit provider-authored failure detail without splitting a UTF-8 sequence.
|
|
2379
|
+
* @param diagnostic - safe diagnostic text produced by the provider.
|
|
2380
|
+
* @returns the original text, or a visibly truncated value within the limit.
|
|
2381
|
+
*/
|
|
2382
|
+
function limitSubagentDiagnostic(diagnostic) {
|
|
2383
|
+
const bytes = utf8Encoder.encode(diagnostic);
|
|
2384
|
+
if (bytes.byteLength <= MAX_SUBAGENT_DIAGNOSTIC_BYTES) return diagnostic;
|
|
2385
|
+
let prefixBytes = MAX_SUBAGENT_DIAGNOSTIC_BYTES - utf8Encoder.encode(DIAGNOSTIC_TRUNCATION_SUFFIX).byteLength;
|
|
2386
|
+
while ((bytes[prefixBytes] & 192) === 128) prefixBytes -= 1;
|
|
2387
|
+
return utf8Decoder.decode(bytes.subarray(0, prefixBytes)) + DIAGNOSTIC_TRUNCATION_SUFFIX;
|
|
2388
|
+
}
|
|
2389
|
+
/** Enforce the byte limit on a provider-returned diagnostic. */
|
|
2390
|
+
function normalizeSubagentDiagnostic(result) {
|
|
2391
|
+
return result.diagnostic === void 0 ? result : {
|
|
2392
|
+
...result,
|
|
2393
|
+
diagnostic: limitSubagentDiagnostic(result.diagnostic)
|
|
2394
|
+
};
|
|
2395
|
+
}
|
|
2050
2396
|
/**
|
|
2051
2397
|
* The capability advertisement of an out-of-process backend: NONE. A child in
|
|
2052
2398
|
* another process cannot honor parent-enforced start features
|
|
2053
|
-
* (`outputSchema`/`maxDepth`/`toolFilter`/`persona`), so the service rejects a
|
|
2399
|
+
* (`agentOptions`/`outputSchema`/`maxDepth`/`toolFilter`/`persona`), so the service rejects a
|
|
2054
2400
|
* request needing any of them before `start` runs — never accepted-then-ignored.
|
|
2055
2401
|
*/
|
|
2056
2402
|
const NO_START_CAPABILITIES = Object.freeze({
|
|
2403
|
+
agentOptions: false,
|
|
2057
2404
|
outputSchema: false,
|
|
2058
2405
|
depthLimit: false,
|
|
2059
2406
|
toolFilter: false,
|
|
@@ -2141,7 +2488,8 @@ function toError(value) {
|
|
|
2141
2488
|
* rejects after publication. A normally completed or rejected attempt resolves
|
|
2142
2489
|
* as `aborted` when cancellation already settled locally; another rejection is
|
|
2143
2490
|
* flattened to `stopReason: 'error'` through the contained diagnostic sink.
|
|
2144
|
-
* The abort listener is
|
|
2491
|
+
* Provider-returned diagnostics use the same byte limit. The abort listener is
|
|
2492
|
+
* removed on every path.
|
|
2145
2493
|
* @param parts - the attempt, output snapshot, cancellation state, sink, and signal wiring.
|
|
2146
2494
|
* @returns the terminal result (never a rejection).
|
|
2147
2495
|
*/
|
|
@@ -2151,7 +2499,7 @@ async function settleRunResult(parts) {
|
|
|
2151
2499
|
return parts.cancelled() ? {
|
|
2152
2500
|
output: parts.collectOutput(),
|
|
2153
2501
|
stopReason: "aborted"
|
|
2154
|
-
} : result;
|
|
2502
|
+
} : normalizeSubagentDiagnostic(result);
|
|
2155
2503
|
} catch (error) {
|
|
2156
2504
|
if (parts.cancelled()) return {
|
|
2157
2505
|
output: parts.collectOutput(),
|
|
@@ -2160,8 +2508,11 @@ async function settleRunResult(parts) {
|
|
|
2160
2508
|
try {
|
|
2161
2509
|
parts.onError?.(toError(error), "error");
|
|
2162
2510
|
} catch {}
|
|
2511
|
+
const collected = parts.collectDiagnostic?.();
|
|
2512
|
+
const diagnostic = collected === void 0 ? void 0 : limitSubagentDiagnostic(collected);
|
|
2163
2513
|
return {
|
|
2164
2514
|
output: parts.collectOutput(),
|
|
2515
|
+
...diagnostic === void 0 ? {} : { diagnostic },
|
|
2165
2516
|
stopReason: "error"
|
|
2166
2517
|
};
|
|
2167
2518
|
} finally {
|
|
@@ -2204,9 +2555,16 @@ function subprocessRunHandle(parts) {
|
|
|
2204
2555
|
function finalText(blocks) {
|
|
2205
2556
|
return blocks.filter((block) => block.type === "text").map((block) => block.text).join("");
|
|
2206
2557
|
}
|
|
2558
|
+
/** Render a failed stop reason with optional provider-authored detail. */
|
|
2559
|
+
function failureDetail(result) {
|
|
2560
|
+
const stopReason = result.stopReason;
|
|
2561
|
+
return result.diagnostic === void 0 ? stopReason : `${stopReason}; diagnostic: ${result.diagnostic}`;
|
|
2562
|
+
}
|
|
2207
2563
|
/**
|
|
2208
|
-
* Map a child result to the task outcome: completed carries final text,
|
|
2209
|
-
* aborted is killed, and
|
|
2564
|
+
* Map a child result to the task outcome: completed carries final text, local
|
|
2565
|
+
* cancellation (`aborted` without a diagnostic) is killed, and provider-
|
|
2566
|
+
* diagnosed remote aborts plus every other reason are failed without partial
|
|
2567
|
+
* output.
|
|
2210
2568
|
* @param result - child terminal result.
|
|
2211
2569
|
* @returns outcome for the `ctx.jobs` registration.
|
|
2212
2570
|
*/
|
|
@@ -2216,16 +2574,19 @@ function runOutcome(result) {
|
|
|
2216
2574
|
status: "completed",
|
|
2217
2575
|
output: finalText(result.output)
|
|
2218
2576
|
};
|
|
2219
|
-
case "aborted": return { status: "killed" }
|
|
2577
|
+
case "aborted": return result.diagnostic === void 0 ? { status: "killed" } : {
|
|
2578
|
+
status: "failed",
|
|
2579
|
+
detail: failureDetail(result)
|
|
2580
|
+
};
|
|
2220
2581
|
case "error":
|
|
2221
2582
|
case "max-tokens":
|
|
2222
2583
|
case "refusal": return {
|
|
2223
2584
|
status: "failed",
|
|
2224
|
-
detail: result
|
|
2585
|
+
detail: failureDetail(result)
|
|
2225
2586
|
};
|
|
2226
2587
|
default: return {
|
|
2227
2588
|
status: "failed",
|
|
2228
|
-
detail:
|
|
2589
|
+
detail: failureDetail(result)
|
|
2229
2590
|
};
|
|
2230
2591
|
}
|
|
2231
2592
|
}
|
|
@@ -2263,10 +2624,8 @@ async function settleRun(run) {
|
|
|
2263
2624
|
* child before returning its run, so fulfillment is the single publication and
|
|
2264
2625
|
* ownership-transfer boundary.
|
|
2265
2626
|
*
|
|
2266
|
-
*
|
|
2267
|
-
*
|
|
2268
|
-
* one by name. The shape mirrors the LLM adapter registry
|
|
2269
|
-
* (`LlmRuntime.registerAdapter`), not the single-service bash executor.
|
|
2627
|
+
* Multiple providers coexist: each registers under a unique name and callers
|
|
2628
|
+
* select one by name.
|
|
2270
2629
|
*
|
|
2271
2630
|
* This package owns the Service Definition role of the capability seam. Service Providers
|
|
2272
2631
|
* (`@xneog/dsh-subagent-spawn-in-process`, `-fork`, `-acp`) and the model-facing
|
|
@@ -2274,8 +2633,8 @@ async function settleRun(run) {
|
|
|
2274
2633
|
*
|
|
2275
2634
|
* Public operations express caller intent: `start` returns one published owned
|
|
2276
2635
|
* one-shot run, `startContinuable` establishes a durable continuable child, and
|
|
2277
|
-
* `
|
|
2278
|
-
* resident. Continuable children never become a {@link SubagentRun}: the
|
|
2636
|
+
* `sendMessage` steers between adjacent Agents without exposing whether a child
|
|
2637
|
+
* is resident. Continuable children never become a {@link SubagentRun}: the
|
|
2279
2638
|
* continuation manager holds their `AgentHandle` directly and orders every turn
|
|
2280
2639
|
* through the child's own inbox, so providers contribute only the detached
|
|
2281
2640
|
* creation spec and see no handle, turn, or teardown. Child and descendant
|
|
@@ -2289,284 +2648,465 @@ async function settleRun(run) {
|
|
|
2289
2648
|
*
|
|
2290
2649
|
* @module @xneog/dsh-subagent
|
|
2291
2650
|
*/
|
|
2651
|
+
var __runInitializers = function(thisArg, initializers, value) {
|
|
2652
|
+
var useValue = arguments.length > 2;
|
|
2653
|
+
for (var i = 0; i < initializers.length; i++) value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
|
|
2654
|
+
return useValue ? value : void 0;
|
|
2655
|
+
};
|
|
2656
|
+
var __esDecorate = function(ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
|
|
2657
|
+
function accept(f) {
|
|
2658
|
+
if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected");
|
|
2659
|
+
return f;
|
|
2660
|
+
}
|
|
2661
|
+
var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
|
|
2662
|
+
var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
|
|
2663
|
+
var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
|
|
2664
|
+
var _, done = false;
|
|
2665
|
+
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
2666
|
+
var context = {};
|
|
2667
|
+
for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
|
|
2668
|
+
for (var p in contextIn.access) context.access[p] = contextIn.access[p];
|
|
2669
|
+
context.addInitializer = function(f) {
|
|
2670
|
+
if (done) throw new TypeError("Cannot add initializers after decoration has completed");
|
|
2671
|
+
extraInitializers.push(accept(f || null));
|
|
2672
|
+
};
|
|
2673
|
+
var result = (0, decorators[i])(kind === "accessor" ? {
|
|
2674
|
+
get: descriptor.get,
|
|
2675
|
+
set: descriptor.set
|
|
2676
|
+
} : descriptor[key], context);
|
|
2677
|
+
if (kind === "accessor") {
|
|
2678
|
+
if (result === void 0) continue;
|
|
2679
|
+
if (result === null || typeof result !== "object") throw new TypeError("Object expected");
|
|
2680
|
+
if (_ = accept(result.get)) descriptor.get = _;
|
|
2681
|
+
if (_ = accept(result.set)) descriptor.set = _;
|
|
2682
|
+
if (_ = accept(result.init)) initializers.unshift(_);
|
|
2683
|
+
} else if (_ = accept(result)) if (kind === "field") initializers.unshift(_);
|
|
2684
|
+
else descriptor[key] = _;
|
|
2685
|
+
}
|
|
2686
|
+
if (target) Object.defineProperty(target, contextIn.name, descriptor);
|
|
2687
|
+
done = true;
|
|
2688
|
+
};
|
|
2292
2689
|
/** Named provider registry with one-shot runs, durable discovery, and continuable-child operations. */
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2690
|
+
let SubagentRuntime = (() => {
|
|
2691
|
+
let _classSuper = TypertRemoteService;
|
|
2692
|
+
let _instanceExtraInitializers = [];
|
|
2693
|
+
let _remoteExportList_decorators;
|
|
2694
|
+
let _prompt_decorators;
|
|
2695
|
+
let _interruptByParent_decorators;
|
|
2696
|
+
return class SubagentRuntime extends _classSuper {
|
|
2697
|
+
static {
|
|
2698
|
+
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
|
|
2699
|
+
_remoteExportList_decorators = [Remote("list")];
|
|
2700
|
+
_prompt_decorators = [Remote("prompt")];
|
|
2701
|
+
_interruptByParent_decorators = [Remote("interruptByParent")];
|
|
2702
|
+
__esDecorate(this, null, _remoteExportList_decorators, {
|
|
2703
|
+
kind: "method",
|
|
2704
|
+
name: "remoteExportList",
|
|
2705
|
+
static: false,
|
|
2706
|
+
private: false,
|
|
2707
|
+
access: {
|
|
2708
|
+
has: (obj) => "remoteExportList" in obj,
|
|
2709
|
+
get: (obj) => obj.remoteExportList
|
|
2710
|
+
},
|
|
2711
|
+
metadata: _metadata
|
|
2712
|
+
}, null, _instanceExtraInitializers);
|
|
2713
|
+
__esDecorate(this, null, _prompt_decorators, {
|
|
2714
|
+
kind: "method",
|
|
2715
|
+
name: "prompt",
|
|
2716
|
+
static: false,
|
|
2717
|
+
private: false,
|
|
2718
|
+
access: {
|
|
2719
|
+
has: (obj) => "prompt" in obj,
|
|
2720
|
+
get: (obj) => obj.prompt
|
|
2721
|
+
},
|
|
2722
|
+
metadata: _metadata
|
|
2723
|
+
}, null, _instanceExtraInitializers);
|
|
2724
|
+
__esDecorate(this, null, _interruptByParent_decorators, {
|
|
2725
|
+
kind: "method",
|
|
2726
|
+
name: "interruptByParent",
|
|
2727
|
+
static: false,
|
|
2728
|
+
private: false,
|
|
2729
|
+
access: {
|
|
2730
|
+
has: (obj) => "interruptByParent" in obj,
|
|
2731
|
+
get: (obj) => obj.interruptByParent
|
|
2732
|
+
},
|
|
2733
|
+
metadata: _metadata
|
|
2734
|
+
}, null, _instanceExtraInitializers);
|
|
2735
|
+
if (_metadata) Object.defineProperty(this, Symbol.metadata, {
|
|
2736
|
+
enumerable: true,
|
|
2737
|
+
configurable: true,
|
|
2738
|
+
writable: true,
|
|
2739
|
+
value: _metadata
|
|
2740
|
+
});
|
|
2741
|
+
}
|
|
2742
|
+
providers = (__runInitializers(this, _instanceExtraInitializers), /* @__PURE__ */ new Map());
|
|
2743
|
+
continuations;
|
|
2744
|
+
/**
|
|
2745
|
+
* The contained lifecycle-edge publisher. Built here because scoped dispatch
|
|
2746
|
+
* keys its carrier by this exact service instance, whose own context filter
|
|
2747
|
+
* composes into the carrier.
|
|
2748
|
+
*/
|
|
2749
|
+
emitLifecycle;
|
|
2750
|
+
constructor(ctx) {
|
|
2751
|
+
super(ctx, "subagents");
|
|
2752
|
+
this.emitLifecycle = createLifecycleEmitter(this.ctx, (parent) => scopeTarget(this, parent));
|
|
2753
|
+
ctx.inject(["agents"], (childCtx) => {
|
|
2754
|
+
const manager = new SubagentContinuationManager(childCtx, {
|
|
2755
|
+
prepareContinuable: (name, request) => this.prepareContinuable(name, request),
|
|
2756
|
+
observeActivation: (provider, childId, parent) => this.observeActivation(provider, childId, parent)
|
|
2757
|
+
});
|
|
2758
|
+
this.continuations = manager;
|
|
2759
|
+
childCtx.effect(() => () => {
|
|
2760
|
+
/* v8 ignore else -- one injected binding owns the slot until its fiber disposes. */
|
|
2761
|
+
if (this.continuations === manager) this.continuations = void 0;
|
|
2762
|
+
}, "subagents.continuationBinding()");
|
|
2763
|
+
});
|
|
2764
|
+
ctx.inject(["sessionProjections"], (projectionCtx) => {
|
|
2765
|
+
projectionCtx.sessionProjections.register(subagentTimingProjectionDefinition);
|
|
2766
|
+
projectionCtx.sessionProjections.register(subagentIdentityProjectionDefinition);
|
|
2767
|
+
});
|
|
2768
|
+
}
|
|
2769
|
+
/**
|
|
2770
|
+
* Establish one durable continuable child and deliver its initial prompt.
|
|
2771
|
+
* Resolves when the child's inbox accepts that prompt, without waiting for the
|
|
2772
|
+
* turn to start or for the message to reach the Session log; any earlier
|
|
2773
|
+
* failure rejects with no ids and rolls back the child entirely.
|
|
2774
|
+
* @param spec - provider, delegation request, and caller cancellation.
|
|
2775
|
+
* @returns the durable child id and the accepted prompt's message id.
|
|
2776
|
+
* @throws when continuation services are unavailable or materialization fails.
|
|
2777
|
+
*/
|
|
2778
|
+
async startContinuable(spec) {
|
|
2779
|
+
return this.requireContinuations().startContinuable(spec);
|
|
2780
|
+
}
|
|
2781
|
+
/**
|
|
2782
|
+
* Steer one model-authored message to the sender's direct parent or direct
|
|
2783
|
+
* continuable child. A running target admits it at the nearest step boundary;
|
|
2784
|
+
* an idle target starts a turn, and an absent direct child cold-resumes from
|
|
2785
|
+
* persistence. The service derives durable sender attribution from the exact
|
|
2786
|
+
* live sender. Caller cancellation stops only pre-acceptance work.
|
|
2787
|
+
* @param sender - exact live Agent authorizing and originating the message.
|
|
2788
|
+
* @param targetId - durable direct-parent or direct-child session id.
|
|
2789
|
+
* @param content - model-authored content to deliver.
|
|
2790
|
+
* @param options - caller cancellation before inbox acceptance.
|
|
2791
|
+
* @returns the accepted message's inbox id.
|
|
2792
|
+
* @throws when continuation services are unavailable, adjacency is rejected,
|
|
2793
|
+
* or the message was not admitted.
|
|
2794
|
+
*/
|
|
2795
|
+
async sendMessage(sender, targetId, content, options) {
|
|
2796
|
+
return this.requireContinuations().sendMessage(sender, targetId, content, options);
|
|
2797
|
+
}
|
|
2798
|
+
/**
|
|
2799
|
+
* Deliver one host-protocol message to a direct continuable child.
|
|
2800
|
+
* Symbol-keyed so host adapters can preserve their own provenance without
|
|
2801
|
+
* widening the public Service Definition or impersonating an Agent sender.
|
|
2802
|
+
* @param parent - exact live direct parent authorizing delivery.
|
|
2803
|
+
* @param childId - durable direct-child session id.
|
|
2804
|
+
* @param content - host-authored content to deliver.
|
|
2805
|
+
* @param source - durable host-protocol provenance.
|
|
2806
|
+
* @param signal - caller cancellation before inbox acceptance.
|
|
2807
|
+
* @param delivery - Queue as a distinct turn or Steer at the nearest step.
|
|
2808
|
+
* @returns the accepted message's inbox id.
|
|
2809
|
+
*/
|
|
2810
|
+
[deliverSubagentPrompt](parent, childId, content, source, signal, delivery) {
|
|
2811
|
+
return delivery === "steer" ? this.requireContinuations().steerPrompt(parent, childId, content, source, signal) : this.requireContinuations().queuePrompt(parent, childId, content, source, signal);
|
|
2812
|
+
}
|
|
2813
|
+
/**
|
|
2814
|
+
* Interrupt one live continuable child's current turn under a human parent
|
|
2815
|
+
* address or an exact live ancestor Agent. Fire-and-return: the cancel
|
|
2816
|
+
* signal is issued before this returns, but the target may keep running
|
|
2817
|
+
* until it observes the signal. Unclaimed pending inbox work, the Activation,
|
|
2818
|
+
* and published descendants are preserved; claimed work is not requeued.
|
|
2819
|
+
* Once the interrupted driver is idle, a waking send resumes the parked FIFO
|
|
2820
|
+
* queue. An absent target — including a one-shot or unknown id —
|
|
2821
|
+
* is an accepted no-op, as is a manager-less composition, which cannot own a
|
|
2822
|
+
* live Activation.
|
|
2823
|
+
* @param targetSessionId - the durable child session id to interrupt.
|
|
2824
|
+
* @param authority - the human parent address or exact live ancestor Agent.
|
|
2825
|
+
* @throws {SubagentError} `UNAUTHORIZED` when the authority does not own the
|
|
2826
|
+
* live target.
|
|
2827
|
+
*/
|
|
2828
|
+
interrupt(targetSessionId, authority) {
|
|
2829
|
+
this.continuations?.interrupt(targetSessionId, authority);
|
|
2830
|
+
}
|
|
2831
|
+
/**
|
|
2832
|
+
* Close continuable admission below exact live parent Agents, stop only their
|
|
2833
|
+
* visible descendant Activations synchronously, then await admitted scoped
|
|
2834
|
+
* materializations and release those forests child-first. The scoped cutoff
|
|
2835
|
+
* lasts until each exact parent leaves the registry; unrelated parent trees
|
|
2836
|
+
* remain live.
|
|
2837
|
+
* @param parents - exact host-owned parent Agents entering teardown.
|
|
2838
|
+
* @returns once every retained descendant Activation released its `AgentHandle`.
|
|
2839
|
+
* @throws an aggregate error after all branches settle when any failed.
|
|
2840
|
+
*/
|
|
2841
|
+
async drainContinuableDescendants(parents) {
|
|
2842
|
+
const manager = this.continuations;
|
|
2843
|
+
if (manager === void 0) return;
|
|
2844
|
+
await manager.drainDescendants(parents);
|
|
2845
|
+
}
|
|
2846
|
+
/**
|
|
2847
|
+
* Release selected resident continuable direct children of one exact live
|
|
2848
|
+
* parent. Other children of the same parent remain admitted and resident.
|
|
2849
|
+
* Absent targets and a manager-less composition are accepted no-ops.
|
|
2850
|
+
* @param parent - exact live direct parent authorizing the selected release.
|
|
2851
|
+
* @param childIds - durable direct-child ids to release when resident.
|
|
2852
|
+
* @returns once every selected Activation released its `AgentHandle`.
|
|
2853
|
+
* @throws {SubagentError} `UNAUTHORIZED` when a resident target belongs to a
|
|
2854
|
+
* different parent or the supplied parent identity is stale.
|
|
2855
|
+
*/
|
|
2856
|
+
async drainContinuableChildren(parent, childIds) {
|
|
2857
|
+
const manager = this.continuations;
|
|
2858
|
+
if (manager === void 0) return;
|
|
2859
|
+
await manager.drainChildren(parent, childIds);
|
|
2860
|
+
}
|
|
2861
|
+
/**
|
|
2862
|
+
* Enumerate the parent's direct session-backed subagents without loading or
|
|
2863
|
+
* resuming an Agent. The Session query service supplies one live-preferred
|
|
2864
|
+
* corpus and shared point observations; the projection cache supplies
|
|
2865
|
+
* immutable descriptor hits without opening cold logs. The registered
|
|
2866
|
+
* `subagent` projection remains the sole mode/label classifier.
|
|
2867
|
+
*
|
|
2868
|
+
* Every query receives `signal`, and the listing rechecks cancellation
|
|
2869
|
+
* around each await. Read rejections that settle
|
|
2870
|
+
* after an abort become a stable `SubagentError` with code `CANCELLED`.
|
|
2871
|
+
* @param parentSessionId - parent session whose direct children are listed.
|
|
2872
|
+
* @param signal - caller-owned cancellation forwarded to Session queries
|
|
2873
|
+
* and observed around every read await.
|
|
2874
|
+
* @returns children and per-child diagnostics ordered by `createdAt`, then id.
|
|
2875
|
+
* @throws {@link SubagentError} when the projection registry or the session
|
|
2876
|
+
* store is not mounted, or the caller cancels the listing.
|
|
2877
|
+
*/
|
|
2878
|
+
listChildren(parentSessionId, signal) {
|
|
2879
|
+
return listChildren(this.ctx, parentSessionId, signal);
|
|
2880
|
+
}
|
|
2881
|
+
/**
|
|
2882
|
+
* Enumerate the root's complete session-backed subagent tree in stable
|
|
2883
|
+
* pre-order from one live-preferred corpus, without loading or resuming an
|
|
2884
|
+
* Agent. Ordinary sessions and one-shot children remain traversal nodes so
|
|
2885
|
+
* continuable descendants below them are discovered; each returned entry
|
|
2886
|
+
* adds its durable `parentId` and root-relative `depth`. Identity resolution,
|
|
2887
|
+
* diagnostics, optional persistence, and cancellation follow the same
|
|
2888
|
+
* projection-backed contract as {@link listChildren}.
|
|
2889
|
+
* @param rootSessionId - session whose complete descendant tree is listed.
|
|
2890
|
+
* @param signal - caller-owned cancellation forwarded to persistence reads
|
|
2891
|
+
* and observed around every read await.
|
|
2892
|
+
* @returns children and per-candidate diagnostics with tree position, in
|
|
2893
|
+
* stable pre-order.
|
|
2894
|
+
* @throws {@link SubagentError} under the same conditions as {@link listChildren}.
|
|
2895
|
+
*/
|
|
2896
|
+
listDescendants(rootSessionId, signal) {
|
|
2897
|
+
return listDescendants(this.ctx, rootSessionId, signal);
|
|
2898
|
+
}
|
|
2899
|
+
/**
|
|
2900
|
+
* Remote face of {@link listChildren} for one browser: the durable listing
|
|
2901
|
+
* plus live Agent activity and the delivery-time parent availability hint.
|
|
2902
|
+
* Parent availability is a hint; {@link prompt} performs the authoritative
|
|
2903
|
+
* check. Named apart from the provider-name {@link list}, which owns the
|
|
2904
|
+
* member.
|
|
2905
|
+
* @param parentSessionId - parent session whose direct children are listed.
|
|
2906
|
+
* @param signal - carrier cancellation forwarded to Session queries.
|
|
2907
|
+
* @returns the catalog view for that parent.
|
|
2908
|
+
* @throws {RemoteError} `gateway/bad-request` for an empty parent id,
|
|
2909
|
+
* `gateway/cancelled` for an aborted read, `subagent/projections-unavailable` when
|
|
2910
|
+
* the deployment has no projection registry, otherwise `gateway/internal`.
|
|
2911
|
+
*/
|
|
2912
|
+
async remoteExportList(parentSessionId, signal) {
|
|
2913
|
+
validateControlRequest("subagent.list", { parentSessionId });
|
|
2914
|
+
try {
|
|
2915
|
+
return catalogView(this.ctx, parentSessionId, await this.listChildren(parentSessionId, signal));
|
|
2916
|
+
} catch (error) {
|
|
2917
|
+
return rejectCatalogRead(error, signal);
|
|
2918
|
+
}
|
|
2919
|
+
}
|
|
2920
|
+
/**
|
|
2921
|
+
* Deliver one browser-authored message to a continuable child through the
|
|
2922
|
+
* exact live direct parent, retaining the caller-minted request identity and
|
|
2923
|
+
* validated browser zone on the accepted message. Success identifies the
|
|
2924
|
+
* message the child's FIFO inbox accepted; later execution is independent of
|
|
2925
|
+
* this call.
|
|
2926
|
+
* Image parts are admitted and persisted through the attachment store
|
|
2927
|
+
* before delivery, and the child's model must accept image input.
|
|
2928
|
+
* @param request - durable address, minted identity, content, and optional browser zone.
|
|
2929
|
+
* @param signal - carrier cancellation, owning the call until inbox acceptance.
|
|
2930
|
+
* @returns the accepted message's inbox identity.
|
|
2931
|
+
* @throws {RemoteError} `gateway/bad-request`, `subagent/attachment-invalid`,
|
|
2932
|
+
* `subagent/invalid-time-zone`, `subagent/parent-unavailable`,
|
|
2933
|
+
* `subagent/not-resumable`, `subagent/unauthorized`,
|
|
2934
|
+
* `subagent/delivery-unavailable`, `gateway/cancelled`, or `gateway/internal`.
|
|
2935
|
+
*/
|
|
2936
|
+
async prompt(request, signal) {
|
|
2937
|
+
const { parentSessionId, childSessionId, clientTimeZone } = request;
|
|
2938
|
+
validateControlRequest("subagent.prompt", request);
|
|
2939
|
+
const canonicalTimeZone = clientTimeZone === void 0 ? void 0 : canonicalClientTimeZone(clientTimeZone);
|
|
2940
|
+
if (clientTimeZone !== void 0 && canonicalTimeZone === void 0) throw new RemoteError("subagent/invalid-time-zone", "clientTimeZone must be UTC or a valid IANA Area/Location name", { value: clientTimeZone });
|
|
2941
|
+
const parent = this.ctx.get("agents")?.get(parentSessionId);
|
|
2942
|
+
if (parent === void 0) throw new RemoteError("subagent/parent-unavailable", `parent session "${parentSessionId}" is not live`, { parentSessionId });
|
|
2943
|
+
const source = {
|
|
2944
|
+
kind: "user",
|
|
2945
|
+
rpcId: request.requestId,
|
|
2946
|
+
...canonicalTimeZone === void 0 ? {} : { clientTimeZone: canonicalTimeZone }
|
|
2475
2947
|
};
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
*/
|
|
2491
|
-
list() {
|
|
2492
|
-
return [...this.providers.keys()];
|
|
2493
|
-
}
|
|
2494
|
-
/**
|
|
2495
|
-
* Establish a published child on the named provider. Capability and semantic
|
|
2496
|
-
* checks run before delegation. Provider ownership lasts until its promise
|
|
2497
|
-
* fulfills; a rejection therefore has no run for the caller to dispose and
|
|
2498
|
-
* emits no run lifecycle events. Post-publication turn and infrastructure
|
|
2499
|
-
* failures settle through the returned run.
|
|
2500
|
-
* @param name - the provider to use.
|
|
2501
|
-
* @param request - child label, prompt, parent, signal, and optional capabilities.
|
|
2502
|
-
* @returns the published holder-owned run.
|
|
2503
|
-
*/
|
|
2504
|
-
async start(name, request) {
|
|
2505
|
-
const provider = this.expectProvider(name);
|
|
2506
|
-
this.assertCapabilities(provider, request);
|
|
2507
|
-
assertSubagentMaxDepth(request.maxDepth);
|
|
2508
|
-
if (request.outputSchema !== void 0) assertObjectJsonSchema(request.outputSchema);
|
|
2509
|
-
const descriptor = snapshotSubagentDescriptor({
|
|
2510
|
-
mode: "one-shot",
|
|
2511
|
-
provider: name,
|
|
2512
|
-
...request.label !== void 0 ? { label: request.label } : {}
|
|
2513
|
-
});
|
|
2514
|
-
const resolved = {
|
|
2515
|
-
...request,
|
|
2516
|
-
descriptor
|
|
2517
|
-
};
|
|
2518
|
-
return observeRun(this.emitLifecycle, name, request.parent, await provider.start(resolved));
|
|
2519
|
-
}
|
|
2520
|
-
/**
|
|
2521
|
-
* Resolve one provider's detached continuable-creation contribution. Method
|
|
2522
|
-
* presence on the provider IS the capability, so a provider without it is
|
|
2523
|
-
* rejected before the manager reserves any child resources.
|
|
2524
|
-
*/
|
|
2525
|
-
async prepareContinuable(name, request) {
|
|
2526
|
-
const provider = this.expectProvider(name);
|
|
2527
|
-
if (provider.prepareContinuable === void 0) throw new SubagentError(`subagent provider "${provider.name}" does not support continuable children (no prepareContinuable capability)`, "UNSUPPORTED_CAPABILITY");
|
|
2528
|
-
return provider.prepareContinuable(request);
|
|
2529
|
-
}
|
|
2530
|
-
/** Look up a provider for dispatch or fail loud. */
|
|
2531
|
-
expectProvider(name) {
|
|
2532
|
-
const provider = this.providers.get(name);
|
|
2533
|
-
if (provider === void 0) throw new SubagentError(`no subagent provider registered for "${name}"`, "NO_PROVIDER");
|
|
2534
|
-
return provider;
|
|
2535
|
-
}
|
|
2536
|
-
/** Resolve the optional continuable-subagent manager or fail loud. */
|
|
2537
|
-
requireContinuations() {
|
|
2538
|
-
if (this.continuations === void 0) throw new SubagentError("continuable subagents require the agents service", "CONTINUATION_UNAVAILABLE");
|
|
2539
|
-
return this.continuations;
|
|
2540
|
-
}
|
|
2541
|
-
/**
|
|
2542
|
-
* Build the lifecycle observer for one continuable Activation's residency
|
|
2543
|
-
* epoch, so the manager publishes its edges without owning event dispatch.
|
|
2544
|
-
*/
|
|
2545
|
-
observeActivation(provider, childId, parent) {
|
|
2546
|
-
return createActivationObserver(this.emitLifecycle, provider, childId, parent);
|
|
2547
|
-
}
|
|
2548
|
-
/** Reject the first requested capability that the provider lacks. */
|
|
2549
|
-
assertCapabilities(provider, request) {
|
|
2550
|
-
const needs = [
|
|
2551
|
-
{
|
|
2552
|
-
when: request.outputSchema !== void 0,
|
|
2553
|
-
cap: "outputSchema"
|
|
2554
|
-
},
|
|
2555
|
-
{
|
|
2556
|
-
when: request.maxDepth !== void 0,
|
|
2557
|
-
cap: "depthLimit"
|
|
2558
|
-
},
|
|
2559
|
-
{
|
|
2560
|
-
when: request.toolFilter !== void 0,
|
|
2561
|
-
cap: "toolFilter"
|
|
2562
|
-
},
|
|
2563
|
-
{
|
|
2564
|
-
when: request.persona !== void 0,
|
|
2565
|
-
cap: "persona"
|
|
2948
|
+
try {
|
|
2949
|
+
let content;
|
|
2950
|
+
if (request.content.every((part) => part.type === "text")) content = request.content.map((part) => ({
|
|
2951
|
+
type: "text",
|
|
2952
|
+
text: part.text
|
|
2953
|
+
}));
|
|
2954
|
+
else {
|
|
2955
|
+
const attachments = this.ctx.get("attachments");
|
|
2956
|
+
if (attachments === void 0) throw new Error("subagent image prompt requires an attachment store");
|
|
2957
|
+
content = await attachments.admitPromptContent(request.content);
|
|
2958
|
+
}
|
|
2959
|
+
return { messageId: await this[deliverSubagentPrompt](parent, childSessionId, content, source, signal, "queue") };
|
|
2960
|
+
} catch (error) {
|
|
2961
|
+
return rejectPrompt(error, childSessionId, signal);
|
|
2566
2962
|
}
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2963
|
+
}
|
|
2964
|
+
/**
|
|
2965
|
+
* Remote face of {@link interrupt} under one durable parent address. No
|
|
2966
|
+
* catalog, history, persistence, or parent Agent lookup runs: the core
|
|
2967
|
+
* primitive alone authorizes the address against the live Activation, which
|
|
2968
|
+
* is what keeps a live child interruptible while its parent Agent is offline.
|
|
2969
|
+
* Absent, idle, and already-completed targets are accepted no-ops there.
|
|
2970
|
+
* @param childSessionId - durable child session id to interrupt.
|
|
2971
|
+
* @param parentSessionId - durable direct parent whose authority is claimed.
|
|
2972
|
+
* @param mode - required continuable-address discriminator.
|
|
2973
|
+
* @returns acknowledgement that the cancel signal was admitted, not that the target is quiescent.
|
|
2974
|
+
* @throws {RemoteError} `gateway/bad-request` for an empty id,
|
|
2975
|
+
* `subagent/unauthorized` when the address does not own the live target,
|
|
2976
|
+
* otherwise `gateway/internal`.
|
|
2977
|
+
*/
|
|
2978
|
+
interruptByParent(childSessionId, parentSessionId, mode) {
|
|
2979
|
+
validateControlRequest("subagent.interrupt", {
|
|
2980
|
+
childSessionId,
|
|
2981
|
+
parentSessionId,
|
|
2982
|
+
mode
|
|
2983
|
+
});
|
|
2984
|
+
try {
|
|
2985
|
+
this.interrupt(childSessionId, {
|
|
2986
|
+
kind: "user",
|
|
2987
|
+
parentSessionId
|
|
2988
|
+
});
|
|
2989
|
+
} catch (error) {
|
|
2990
|
+
if (error instanceof SubagentError && error.code === "UNAUTHORIZED") throw new RemoteError("subagent/unauthorized", "subagent does not belong to this parent", { childSessionId }, { cause: error });
|
|
2991
|
+
throw new RemoteError("gateway/internal", "subagent interrupt failed", {}, { cause: error });
|
|
2992
|
+
}
|
|
2993
|
+
return { accepted: true };
|
|
2994
|
+
}
|
|
2995
|
+
/**
|
|
2996
|
+
* Register a provider under its name. Registration is effect-scoped and HMR
|
|
2997
|
+
* safe; removing a provider blocks new starts but does not revoke runs that
|
|
2998
|
+
* were already returned to their holders.
|
|
2999
|
+
* @param provider - the trusted provider implementation.
|
|
3000
|
+
* @returns the exact Cordis effect disposer.
|
|
3001
|
+
*/
|
|
3002
|
+
registerProvider(provider) {
|
|
3003
|
+
const name = provider.name;
|
|
3004
|
+
return this.ctx.effect(function* () {
|
|
3005
|
+
if (this.providers.has(name)) throw new SubagentError(`a subagent provider named "${name}" is already registered`, "DUPLICATE_PROVIDER");
|
|
3006
|
+
this.providers.set(name, provider);
|
|
3007
|
+
yield () => {
|
|
3008
|
+
this.providers.delete(name);
|
|
3009
|
+
this.emitLifecycle("subagent/provider-removed", name);
|
|
3010
|
+
};
|
|
3011
|
+
this.ctx.emit("subagent/provider-added", provider);
|
|
3012
|
+
}.bind(this), "subagents.registerProvider()");
|
|
3013
|
+
}
|
|
3014
|
+
/**
|
|
3015
|
+
* Look up a provider by name.
|
|
3016
|
+
* @param name - the provider name.
|
|
3017
|
+
* @returns the provider, or undefined when absent.
|
|
3018
|
+
*/
|
|
3019
|
+
getProvider(name) {
|
|
3020
|
+
return this.providers.get(name);
|
|
3021
|
+
}
|
|
3022
|
+
/**
|
|
3023
|
+
* List registered provider names in insertion order.
|
|
3024
|
+
* @returns the registered names.
|
|
3025
|
+
*/
|
|
3026
|
+
list() {
|
|
3027
|
+
return [...this.providers.keys()];
|
|
3028
|
+
}
|
|
3029
|
+
/**
|
|
3030
|
+
* Establish a published child on the named provider. Capability and semantic
|
|
3031
|
+
* checks run before delegation. Provider ownership lasts until its promise
|
|
3032
|
+
* fulfills; a rejection therefore has no run for the caller to dispose and
|
|
3033
|
+
* emits no run lifecycle events. Post-publication turn and infrastructure
|
|
3034
|
+
* failures settle through the returned run.
|
|
3035
|
+
* @param name - the provider to use.
|
|
3036
|
+
* @param request - child label, prompt, parent, signal, and optional capabilities.
|
|
3037
|
+
* @returns the published holder-owned run.
|
|
3038
|
+
*/
|
|
3039
|
+
async start(name, request) {
|
|
3040
|
+
const provider = this.expectProvider(name);
|
|
3041
|
+
this.assertCapabilities(provider, request);
|
|
3042
|
+
assertSubagentMaxDepth(request.maxDepth);
|
|
3043
|
+
if (request.outputSchema !== void 0) assertObjectJsonSchema(request.outputSchema);
|
|
3044
|
+
const descriptor = snapshotSubagentDescriptor({
|
|
3045
|
+
mode: "one-shot",
|
|
3046
|
+
provider: name,
|
|
3047
|
+
...request.label !== void 0 ? { label: request.label } : {}
|
|
3048
|
+
});
|
|
3049
|
+
const resolved = {
|
|
3050
|
+
...request,
|
|
3051
|
+
descriptor
|
|
3052
|
+
};
|
|
3053
|
+
return observeRun(this.emitLifecycle, name, request.parent, await provider.start(resolved));
|
|
3054
|
+
}
|
|
3055
|
+
/**
|
|
3056
|
+
* Resolve one provider's detached continuable-creation contribution. Method
|
|
3057
|
+
* presence on the provider IS the capability, so a provider without it is
|
|
3058
|
+
* rejected before the manager reserves any child resources.
|
|
3059
|
+
*/
|
|
3060
|
+
async prepareContinuable(name, request) {
|
|
3061
|
+
const provider = this.expectProvider(name);
|
|
3062
|
+
if (provider.prepareContinuable === void 0) throw new SubagentError(`subagent provider "${provider.name}" does not support continuable children (no prepareContinuable capability)`, "UNSUPPORTED_CAPABILITY");
|
|
3063
|
+
return provider.prepareContinuable(request);
|
|
3064
|
+
}
|
|
3065
|
+
/** Look up a provider for dispatch or fail loud. */
|
|
3066
|
+
expectProvider(name) {
|
|
3067
|
+
const provider = this.providers.get(name);
|
|
3068
|
+
if (provider === void 0) throw new SubagentError(`no subagent provider registered for "${name}"`, "NO_PROVIDER");
|
|
3069
|
+
return provider;
|
|
3070
|
+
}
|
|
3071
|
+
/** Resolve the optional continuable-subagent manager or fail loud. */
|
|
3072
|
+
requireContinuations() {
|
|
3073
|
+
if (this.continuations === void 0) throw new SubagentError("continuable subagents require the agents service", "CONTINUATION_UNAVAILABLE");
|
|
3074
|
+
return this.continuations;
|
|
3075
|
+
}
|
|
3076
|
+
/**
|
|
3077
|
+
* Build the lifecycle observer for one continuable Activation's residency
|
|
3078
|
+
* epoch, so the manager publishes its edges without owning event dispatch.
|
|
3079
|
+
*/
|
|
3080
|
+
observeActivation(provider, childId, parent) {
|
|
3081
|
+
return createActivationObserver(this.emitLifecycle, provider, childId, parent);
|
|
3082
|
+
}
|
|
3083
|
+
/** Reject the first requested capability that the provider lacks. */
|
|
3084
|
+
assertCapabilities(provider, request) {
|
|
3085
|
+
const needs = [
|
|
3086
|
+
{
|
|
3087
|
+
when: request.agentOptions !== void 0,
|
|
3088
|
+
cap: "agentOptions"
|
|
3089
|
+
},
|
|
3090
|
+
{
|
|
3091
|
+
when: request.outputSchema !== void 0,
|
|
3092
|
+
cap: "outputSchema"
|
|
3093
|
+
},
|
|
3094
|
+
{
|
|
3095
|
+
when: request.maxDepth !== void 0,
|
|
3096
|
+
cap: "depthLimit"
|
|
3097
|
+
},
|
|
3098
|
+
{
|
|
3099
|
+
when: request.toolFilter !== void 0,
|
|
3100
|
+
cap: "toolFilter"
|
|
3101
|
+
},
|
|
3102
|
+
{
|
|
3103
|
+
when: request.persona !== void 0,
|
|
3104
|
+
cap: "persona"
|
|
3105
|
+
}
|
|
3106
|
+
];
|
|
3107
|
+
for (const { when, cap } of needs) if (when && !provider.capabilities[cap]) throw new SubagentError(`subagent provider "${provider.name}" does not support the "${cap}" capability`, "UNSUPPORTED_CAPABILITY");
|
|
3108
|
+
}
|
|
3109
|
+
};
|
|
3110
|
+
})();
|
|
2571
3111
|
//#endregion
|
|
2572
|
-
export { AssistantOutputFold, NO_START_CAPABILITIES, SUBAGENT_DESCRIPTOR_VERSION, SubagentDepthError, SubagentError, SubagentRunId, SubagentRuntime, SubagentRuntime as default, appendDelegatedPolicyOverrides, applyChildComposition, assertPositiveFinite, assertSubagentMaxDepth, assertUsableCwd, captureDelegatedPolicyOverrides, childSessionMeta, delegationDepthOf, finalAssistantOutput, foldSubagentDescriptor, resolveChildAgentOptions, resolveChildCwd, resolveChildDepth,
|
|
3112
|
+
export { AssistantOutputFold, NO_START_CAPABILITIES, SUBAGENT_DESCRIPTOR_VERSION, SubagentDepthError, SubagentError, SubagentRunId, SubagentRuntime, SubagentRuntime as default, appendDelegatedPolicyOverrides, applyChildComposition, assertPositiveFinite, assertSubagentMaxDepth, assertUsableCwd, captureDelegatedPolicyOverrides, childSessionMeta, delegationDepthOf, finalAssistantOutput, foldSubagentDescriptor, parentAgentOptionsForDelegation, resolveChildAgentOptions, resolveChildCwd, resolveChildDepth, settleRun, settleRunResult, snapshotSubagentDescriptor, subprocessRunHandle, validateConfiguredCwd };
|