@deepseek-ai/dsh-subagent 0.2.0-rc.2 → 0.2.1-alpha.2
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 +36 -36
- package/README.md +33 -27
- package/README.zh.md +33 -27
- package/lib/index.js +1436 -1304
- package/lib/typert.host.js +73 -60
- package/lib/typert.remote-client.js +2 -2
- package/lib/types/activation.d.ts +114 -0
- package/lib/types/activation.js +112 -0
- package/lib/types/catalog.d.ts +14 -6
- package/lib/types/catalog.js +34 -30
- package/lib/types/child-agent.d.ts +1 -3
- package/lib/types/child-agent.js +1 -3
- package/lib/types/continuation-messages.d.ts +4 -3
- package/lib/types/continuation-messages.js +13 -5
- package/lib/types/control-types.d.ts +7 -0
- package/lib/types/descriptor.d.ts +7 -46
- package/lib/types/descriptor.js +20 -21
- package/lib/types/index.d.ts +52 -53
- package/lib/types/index.js +75 -95
- package/lib/types/internal.d.ts +1 -1
- package/lib/types/lifecycle.d.ts +28 -58
- package/lib/types/lifecycle.js +9 -74
- package/lib/types/list-children.js +1 -1
- package/lib/types/manager.d.ts +247 -0
- package/lib/types/manager.js +1294 -0
- package/lib/types/out-of-process.d.ts +2 -28
- package/lib/types/out-of-process.js +3 -44
- package/lib/types/projection-types.d.ts +3 -0
- package/lib/types/structured.d.ts +49 -0
- package/lib/types/structured.js +130 -0
- package/lib/types/types.d.ts +57 -81
- package/lib/types/types.js +1 -1
- package/package.json +53 -63
- package/lib/invariant.js +0 -76
- package/lib/types/continuation-activation.d.ts +0 -267
- package/lib/types/continuation-activation.js +0 -700
- package/lib/types/continuation.d.ts +0 -124
- package/lib/types/continuation.js +0 -455
- package/lib/types/inbox.d.ts +0 -43
- package/lib/types/inbox.js +0 -61
- package/lib/types/invariant.d.ts +0 -13
- package/lib/types/invariant.js +0 -91
- package/lib/types/run-settlement.d.ts +0 -17
- package/lib/types/run-settlement.js +0 -70
package/lib/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import z from "@deepseek-ai/schemastery";
|
|
2
2
|
import { scopeTarget } from "@deepseek-ai/dsh-scope";
|
|
3
|
-
import { assertObjectJsonSchema } from "@deepseek-ai/dsh-tools";
|
|
3
|
+
import { ToolArgsError, assertObjectJsonSchema, validateJsonSchemaValue } from "@deepseek-ai/dsh-tools";
|
|
4
4
|
import { canonicalClientTimeZone } from "@deepseek-ai/dsh-util-time";
|
|
5
5
|
import { Remote, RemoteError, TypertRemoteService } from "@deepseek-ai/dsh-typert-protocol";
|
|
6
6
|
import { AttachmentError } from "@deepseek-ai/dsh-attachment";
|
|
@@ -8,12 +8,12 @@ import { z as z$1 } from "zod";
|
|
|
8
8
|
import { HarnessError, ReasoningEffortId, boundContextSummary, contentHasImage, createUserMessage, errorChain, joinAssistantStreamText } from "@deepseek-ai/dsh-llm";
|
|
9
9
|
import { randomUUID } from "node:crypto";
|
|
10
10
|
import { foldConsumedWork } from "@deepseek-ai/dsh-agent";
|
|
11
|
-
import {
|
|
11
|
+
import { isAbsolute, resolve } from "node:path";
|
|
12
12
|
import { brandString } from "@deepseek-ai/dsh-brand";
|
|
13
|
-
import {
|
|
13
|
+
import { SessionLogOffset, SessionSeq } from "@deepseek-ai/dsh-session";
|
|
14
|
+
import { assertNever, snapshotJsonValue } from "@deepseek-ai/dsh-util-values";
|
|
14
15
|
import { appendChunkedList, chunkedListSchema, iterateChunkedList } from "@deepseek-ai/dsh-chunked-list";
|
|
15
16
|
import { accessSync, constants, statSync } from "node:fs";
|
|
16
|
-
import { isAbsolute, resolve } from "node:path";
|
|
17
17
|
//#region lib/types/error.js
|
|
18
18
|
/**
|
|
19
19
|
* Typed failures shared by subagent service and provider operations.
|
|
@@ -121,79 +121,13 @@ function assertSubagentMaxDepth(maxDepth) {
|
|
|
121
121
|
if (maxDepth !== void 0 && (typeof maxDepth !== "number" || !Number.isSafeInteger(maxDepth) || maxDepth < 0 || Object.is(maxDepth, -0))) throw new TypeError("subagent maxDepth must be a non-negative safe integer");
|
|
122
122
|
}
|
|
123
123
|
//#endregion
|
|
124
|
-
//#region lib/types/assistant-output.js
|
|
125
|
-
/**
|
|
126
|
-
* Canonical selection of a child's final assistant output. Backend run results
|
|
127
|
-
* and `subagent/end.lastAssistantMessage` apply the same rule: select the last
|
|
128
|
-
* non-empty assistant message. An empty-content message records usage only
|
|
129
|
-
* when the loop appends it after a max-tokens step with no executable blocks,
|
|
130
|
-
* so it does not replace earlier output. If no non-empty message exists,
|
|
131
|
-
* select the accumulated assistant text. Selection is independent of the
|
|
132
|
-
* run's stop reason.
|
|
133
|
-
*
|
|
134
|
-
* @module @deepseek-ai/dsh-subagent/assistant-output
|
|
135
|
-
*/
|
|
136
|
-
/**
|
|
137
|
-
* Incremental fold of the selection rule, for backends that observe a child's
|
|
138
|
-
* output as it streams: session-event backends {@link push} each event, and
|
|
139
|
-
* transports without session events (ACP content chunks) {@link pushText} raw
|
|
140
|
-
* text into the same streamed fallback.
|
|
141
|
-
*/
|
|
142
|
-
var AssistantOutputFold = class {
|
|
143
|
-
message;
|
|
144
|
-
partial = [];
|
|
145
|
-
/**
|
|
146
|
-
* Fold one session event: a non-empty assistant message becomes the
|
|
147
|
-
* candidate final answer, while its embedded stream and any log-only attempt
|
|
148
|
-
* extend the streamed fallback; every other event contributes nothing.
|
|
149
|
-
* @param event - the next observed session event.
|
|
150
|
-
*/
|
|
151
|
-
push(event) {
|
|
152
|
-
if (event.type === "assistant/message") {
|
|
153
|
-
const content = event.data.message.content;
|
|
154
|
-
if (content.length > 0) this.message = content;
|
|
155
|
-
}
|
|
156
|
-
if (event.type === "assistant/message" || event.type === "assistant/attempt") this.pushText(joinAssistantStreamText(event.data.stream));
|
|
157
|
-
}
|
|
158
|
-
/**
|
|
159
|
-
* Extend the streamed fallback with text observed outside session events.
|
|
160
|
-
* @param text - the next streamed text piece (an empty piece is a no-op).
|
|
161
|
-
*/
|
|
162
|
-
pushText(text) {
|
|
163
|
-
if (text.length > 0) this.partial.push(text);
|
|
164
|
-
}
|
|
165
|
-
/**
|
|
166
|
-
* Select the final output folded so far.
|
|
167
|
-
* @returns the last non-empty assistant message, else the accumulated
|
|
168
|
-
* streamed text, or `undefined` when the child produced neither.
|
|
169
|
-
*/
|
|
170
|
-
collect() {
|
|
171
|
-
if (this.message !== void 0) return this.message;
|
|
172
|
-
const text = this.partial.join("");
|
|
173
|
-
return text.length > 0 ? [{
|
|
174
|
-
type: "text",
|
|
175
|
-
text
|
|
176
|
-
}] : void 0;
|
|
177
|
-
}
|
|
178
|
-
};
|
|
179
|
-
/**
|
|
180
|
-
* Apply the selection rule to one complete child-owned event suffix.
|
|
181
|
-
* @param events - the child-owned events (after any seed or epoch boundary).
|
|
182
|
-
* @returns the selected output, or `undefined` when the child produced none.
|
|
183
|
-
*/
|
|
184
|
-
function finalAssistantOutput(events) {
|
|
185
|
-
const fold = new AssistantOutputFold();
|
|
186
|
-
for (const event of events) fold.push(event);
|
|
187
|
-
return fold.collect();
|
|
188
|
-
}
|
|
189
|
-
//#endregion
|
|
190
124
|
//#region lib/types/types.js
|
|
191
125
|
/**
|
|
192
126
|
* The seam's consumer-facing contracts: request, result, and capability types
|
|
193
127
|
* for {@link SubagentProvider}, plus the `subagent/start` and `subagent/end`
|
|
194
128
|
* payloads that plugins and hosts observe. Internal control interfaces belong
|
|
195
129
|
* with their implementation — the lifecycle observer in `./lifecycle.ts`, the
|
|
196
|
-
*
|
|
130
|
+
* manager host in `./manager.ts` — so this module stays the published
|
|
197
131
|
* surface rather than a bag of everything type-shaped.
|
|
198
132
|
*
|
|
199
133
|
* @module @deepseek-ai/dsh-subagent/types
|
|
@@ -209,17 +143,10 @@ function SubagentRunId(id) {
|
|
|
209
143
|
//#endregion
|
|
210
144
|
//#region lib/types/lifecycle.js
|
|
211
145
|
/**
|
|
212
|
-
* Lifecycle-edge publication for
|
|
213
|
-
* the one-shot run observer, and the continuable Activation observer.
|
|
146
|
+
* Lifecycle-edge publication for managed subagent activations.
|
|
214
147
|
*
|
|
215
|
-
*
|
|
216
|
-
*
|
|
217
|
-
* consumer-facing types; this module owns only the implementation and the
|
|
218
|
-
* package-private {@link ActivationObserver} the continuation manager consumes.
|
|
219
|
-
* Keeping the internal control interface out of the published surface is
|
|
220
|
-
* deliberate: the observer's `start`/`capture`/`settle` ordering is a contract
|
|
221
|
-
* between this module and one in-package caller, not something a plugin may
|
|
222
|
-
* depend on.
|
|
148
|
+
* Public event payloads live in `./types.ts`; the package-private observer
|
|
149
|
+
* retains only the identity shared by each start/end pair.
|
|
223
150
|
*
|
|
224
151
|
* @module @deepseek-ai/dsh-subagent/lifecycle
|
|
225
152
|
*/
|
|
@@ -250,40 +177,7 @@ function createLifecycleEmitter(ctx, carrier) {
|
|
|
250
177
|
};
|
|
251
178
|
}
|
|
252
179
|
/**
|
|
253
|
-
*
|
|
254
|
-
* @param emit - the contained lifecycle emitter.
|
|
255
|
-
* @param provider - the provider that established the run.
|
|
256
|
-
* @param parent - the delegating parent keying scoped dispatch.
|
|
257
|
-
* @param run - the published run whose settlement closes the pair.
|
|
258
|
-
* @returns the same run, unchanged.
|
|
259
|
-
*/
|
|
260
|
-
function observeRun(emit, provider, parent, run) {
|
|
261
|
-
const identity = {
|
|
262
|
-
runId: SubagentRunId(randomUUID()),
|
|
263
|
-
provider,
|
|
264
|
-
id: run.id,
|
|
265
|
-
local: run.localAgent !== void 0
|
|
266
|
-
};
|
|
267
|
-
run.result.then((result) => {
|
|
268
|
-
emit("subagent/end", {
|
|
269
|
-
...identity,
|
|
270
|
-
stopReason: result.stopReason,
|
|
271
|
-
...result.output.length === 0 ? {} : { lastAssistantMessage: result.output }
|
|
272
|
-
}, parent);
|
|
273
|
-
}, () => {
|
|
274
|
-
emit("subagent/end", {
|
|
275
|
-
...identity,
|
|
276
|
-
stopReason: "error"
|
|
277
|
-
}, parent);
|
|
278
|
-
});
|
|
279
|
-
emit("subagent/start", identity, parent);
|
|
280
|
-
return run;
|
|
281
|
-
}
|
|
282
|
-
/**
|
|
283
|
-
* Build the observer for one continuable Activation's residency epoch. Observers
|
|
284
|
-
* see the same vocabulary as a one-shot run, so a child's start and settlement
|
|
285
|
-
* remain observable without exposing whether the manager materialized, woke, or
|
|
286
|
-
* cold-resumed it. Creation failure before residency emits no lifecycle edge.
|
|
180
|
+
* Build the observer for one local or external activation's residency epoch. Creation failure before residency emits no lifecycle edge.
|
|
287
181
|
* @param emit - the contained lifecycle emitter.
|
|
288
182
|
* @param provider - the provider name recorded in the durable descriptor.
|
|
289
183
|
* @param childId - the durable child session id.
|
|
@@ -295,31 +189,18 @@ function createActivationObserver(emit, provider, childId, parent) {
|
|
|
295
189
|
runId: SubagentRunId(randomUUID()),
|
|
296
190
|
provider,
|
|
297
191
|
id: childId,
|
|
298
|
-
local:
|
|
192
|
+
local: false
|
|
299
193
|
};
|
|
300
|
-
let boundary = SessionLogOffset(0);
|
|
301
|
-
let captured = { stopReason: "completed" };
|
|
302
|
-
const terminal = (failure) => failure === void 0 ? captured : { stopReason: "error" };
|
|
303
194
|
return {
|
|
304
195
|
start: (child) => {
|
|
305
|
-
|
|
196
|
+
identity.local = child !== void 0;
|
|
306
197
|
emit("subagent/start", identity, parent);
|
|
307
198
|
},
|
|
308
|
-
|
|
309
|
-
const own = child.session.snapshotEvents(boundary);
|
|
310
|
-
const output = finalAssistantOutput(own);
|
|
311
|
-
captured = {
|
|
312
|
-
stopReason: epochStopReason(own),
|
|
313
|
-
...output === void 0 ? {} : { output }
|
|
314
|
-
};
|
|
315
|
-
},
|
|
316
|
-
terminal,
|
|
317
|
-
settle: (failure) => {
|
|
318
|
-
const { stopReason, output } = terminal(failure);
|
|
199
|
+
settle: ({ stopReason, output }) => {
|
|
319
200
|
emit("subagent/end", {
|
|
320
201
|
...identity,
|
|
321
202
|
stopReason,
|
|
322
|
-
...output ===
|
|
203
|
+
...output.length === 0 ? {} : { lastAssistantMessage: output }
|
|
323
204
|
}, parent);
|
|
324
205
|
}
|
|
325
206
|
};
|
|
@@ -370,9 +251,7 @@ function renderThrown(value) {
|
|
|
370
251
|
/**
|
|
371
252
|
* Shared in-process child composition: the delegation-depth budget, the
|
|
372
253
|
* durable session metadata, the resolved child `AgentOptions`, the delegated
|
|
373
|
-
* policy seed, and the scoped setup
|
|
374
|
-
* provider driver and the continuation manager compose children this way, so
|
|
375
|
-
* depth accounting, lineage stamping, and delegation policy have one home.
|
|
254
|
+
* policy seed, and the scoped setup used by local activations.
|
|
376
255
|
*
|
|
377
256
|
* @module @deepseek-ai/dsh-subagent/child-agent
|
|
378
257
|
*/
|
|
@@ -608,12 +487,13 @@ function withContinuableReturnGuidance(parentId, prompt) {
|
|
|
608
487
|
* the parent's own task vocabulary.
|
|
609
488
|
* @param childId - the durable child the parent knows by id.
|
|
610
489
|
* @param stopReason - how the child's last ordinary turn ended.
|
|
490
|
+
* @param continuable - whether the child accepts a later message.
|
|
611
491
|
* @returns the model-facing opening line of the settlement notice.
|
|
612
492
|
*/
|
|
613
|
-
function settlementSummary(childId, stopReason) {
|
|
493
|
+
function settlementSummary(childId, stopReason, continuable) {
|
|
614
494
|
const subject = `Background subagent ${childId}`;
|
|
615
495
|
switch (stopReason) {
|
|
616
|
-
case "completed": return `${subject} finished and will do no further work unless you send it more.`;
|
|
496
|
+
case "completed": return continuable ? `${subject} finished and will do no further work unless you send it more.` : `${subject} finished. It cannot receive follow-up messages.`;
|
|
617
497
|
case "aborted": return `${subject} was stopped before it finished.`;
|
|
618
498
|
case "max-tokens": return `${subject} ran out of room before it finished.`;
|
|
619
499
|
case "refusal": return `${subject} declined the task.`;
|
|
@@ -628,22 +508,34 @@ function settlementSummary(childId, stopReason) {
|
|
|
628
508
|
* Build the runtime-owned settlement notice from the child's nonempty closing text.
|
|
629
509
|
* @param childId - durable child session id named in the notice.
|
|
630
510
|
* @param terminal - recorded terminal state for the settled Activation.
|
|
511
|
+
* @param continuable - whether the child can accept another task.
|
|
631
512
|
* @returns the durable user-message representation delivered to the parent.
|
|
632
513
|
*/
|
|
633
|
-
function createSettlementMessage(childId, terminal) {
|
|
634
|
-
const summary = settlementSummary(childId, terminal.stopReason);
|
|
635
|
-
const closingText =
|
|
514
|
+
function createSettlementMessage(childId, terminal, continuable = true) {
|
|
515
|
+
const summary = settlementSummary(childId, terminal.stopReason, continuable);
|
|
516
|
+
const closingText = terminal.output.flatMap((block) => block.type === "text" && block.text.length > 0 ? [block] : []);
|
|
636
517
|
return createUserMessage({
|
|
637
|
-
content: [
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
518
|
+
content: [
|
|
519
|
+
{
|
|
520
|
+
type: "text",
|
|
521
|
+
text: summary
|
|
522
|
+
},
|
|
523
|
+
...closingText.length === 0 ? [{
|
|
524
|
+
type: "text",
|
|
525
|
+
text: "It left no closing message."
|
|
526
|
+
}] : [{
|
|
527
|
+
type: "text",
|
|
528
|
+
text: "Its closing message:"
|
|
529
|
+
}, ...closingText],
|
|
530
|
+
...terminal.structured !== void 0 ? [{
|
|
531
|
+
type: "text",
|
|
532
|
+
text: `Structured result: ${JSON.stringify(terminal.structured)}`
|
|
533
|
+
}] : [],
|
|
534
|
+
...terminal.diagnostic !== void 0 ? [{
|
|
535
|
+
type: "text",
|
|
536
|
+
text: terminal.diagnostic
|
|
537
|
+
}] : []
|
|
538
|
+
],
|
|
647
539
|
source: {
|
|
648
540
|
kind: "subagent-settled",
|
|
649
541
|
form: "notice",
|
|
@@ -653,731 +545,107 @@ function createSettlementMessage(childId, terminal) {
|
|
|
653
545
|
});
|
|
654
546
|
}
|
|
655
547
|
//#endregion
|
|
656
|
-
//#region lib/types/
|
|
548
|
+
//#region lib/types/descriptor.js
|
|
657
549
|
/**
|
|
658
|
-
*
|
|
550
|
+
* The durable subagent-child descriptor: the versioned, model-hidden
|
|
551
|
+
* `subagent/descriptor` session event that identifies every session-backed
|
|
552
|
+
* subagent, including historical one-shot records. The manager appends each
|
|
553
|
+
* continuable child's descriptor inside its initial turn, preserving the
|
|
554
|
+
* declared composition required for cold resume.
|
|
555
|
+
*
|
|
556
|
+
* The descriptor deliberately snapshots explicit fields rather than the
|
|
557
|
+
* merge-extensible `AgentOptions` object: an unrelated extension value cannot
|
|
558
|
+
* make continuation fail merely because it is not JSON, and later composition
|
|
559
|
+
* inputs require a deliberate {@link SUBAGENT_DESCRIPTOR_VERSION} change. It
|
|
560
|
+
* omits `subagentDepth` — cold resume trusts the persisted header's
|
|
561
|
+
* `delegationDepth` as the monotone floor — and `outputSchema`, which belongs
|
|
562
|
+
* to one activation's result contract rather than durable child composition.
|
|
563
|
+
* Per-activation knobs such as `maxTokens` are omitted for the same reason as
|
|
564
|
+
* `outputSchema`: they budget one activation. Cold resume requires the exact
|
|
565
|
+
* live parent for authorization but reconstructs child options only from the
|
|
566
|
+
* durable descriptor, so it neither restores the prior budget nor inherits
|
|
567
|
+
* the parent's current one; the resumed route's defaults apply instead.
|
|
659
568
|
*
|
|
660
|
-
* @module @deepseek-ai/dsh-subagent/
|
|
569
|
+
* @module @deepseek-ai/dsh-subagent/descriptor
|
|
661
570
|
*/
|
|
662
|
-
/** Delegate Queue and Steer to one live Agent until its Activation starts closing. */
|
|
663
|
-
var SubagentInbox = class {
|
|
664
|
-
agent;
|
|
665
|
-
closingPromise;
|
|
666
|
-
/**
|
|
667
|
-
* Wrap one live continuable Agent.
|
|
668
|
-
* @param agent - the Agent whose inbox receives accepted deliveries.
|
|
669
|
-
*/
|
|
670
|
-
constructor(agent) {
|
|
671
|
-
this.agent = agent;
|
|
672
|
-
}
|
|
673
|
-
/**
|
|
674
|
-
* Read the Activation's close transaction.
|
|
675
|
-
* @returns the memoized transaction, or `undefined` while delivery remains open.
|
|
676
|
-
*/
|
|
677
|
-
get closing() {
|
|
678
|
-
return this.closingPromise;
|
|
679
|
-
}
|
|
680
|
-
/**
|
|
681
|
-
* Read whether the underlying Agent still has accepted work to claim.
|
|
682
|
-
* @returns whether either Agent inbox destination is non-empty.
|
|
683
|
-
*/
|
|
684
|
-
get hasPending() {
|
|
685
|
-
return this.agent.inbox.nextTurn.length > 0 || this.agent.inbox.nextStep.length > 0;
|
|
686
|
-
}
|
|
687
|
-
/**
|
|
688
|
-
* Submit through the Agent only while its Activation remains resident.
|
|
689
|
-
* @param message - the accepted input to submit.
|
|
690
|
-
* @param delivery - whether to queue a distinct turn or steer the nearest step.
|
|
691
|
-
*/
|
|
692
|
-
deliver(message, delivery) {
|
|
693
|
-
if (this.closingPromise !== void 0) throw new SubagentError(`subagent "${this.agent.id}" activation is being disposed; the message was not accepted`, "ACTIVATION_CLOSING");
|
|
694
|
-
if (delivery === "steer") this.agent.steer(message);
|
|
695
|
-
else this.agent.followup(message);
|
|
696
|
-
}
|
|
697
|
-
/**
|
|
698
|
-
* Close delivery synchronously and share one asynchronous release.
|
|
699
|
-
* @param release - the one release operation to start after closing admission.
|
|
700
|
-
* @returns the memoized release transaction.
|
|
701
|
-
*/
|
|
702
|
-
close(release) {
|
|
703
|
-
const existing = this.closingPromise;
|
|
704
|
-
if (existing !== void 0) return existing;
|
|
705
|
-
const completion = Promise.withResolvers();
|
|
706
|
-
this.closingPromise = completion.promise;
|
|
707
|
-
release().then(completion.resolve, completion.reject);
|
|
708
|
-
return completion.promise;
|
|
709
|
-
}
|
|
710
|
-
};
|
|
711
|
-
//#endregion
|
|
712
|
-
//#region lib/types/continuation-activation.js
|
|
713
571
|
/**
|
|
714
|
-
*
|
|
715
|
-
*
|
|
716
|
-
*
|
|
717
|
-
*
|
|
718
|
-
* every mutable residency decision to this registry, so delivery and teardown
|
|
719
|
-
* share one child lock and one Activation map.
|
|
720
|
-
*
|
|
721
|
-
* @module @deepseek-ai/dsh-subagent/continuation-activation
|
|
572
|
+
* The current descriptor format version, stamped into every appended
|
|
573
|
+
* `subagent/descriptor` event and required verbatim by {@link foldSubagentDescriptor}.
|
|
574
|
+
* Supporting another composition input is a deliberate version change, never
|
|
575
|
+
* an implicit extra field.
|
|
722
576
|
*/
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
}
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
}.bind(this), "subagents.continuations()");
|
|
796
|
-
}
|
|
797
|
-
/**
|
|
798
|
-
* Return the live Activation for a durable child id, if resident.
|
|
799
|
-
* @param childId - durable child session id to look up.
|
|
800
|
-
* @returns the process-local Activation, or `undefined` when it is not resident.
|
|
801
|
-
*/
|
|
802
|
-
get(childId) {
|
|
803
|
-
return this.resident.get(childId);
|
|
804
|
-
}
|
|
805
|
-
/**
|
|
806
|
-
* Reject one child identity already owned by a live Agent or Session.
|
|
807
|
-
* @param childId - proposed durable child session id.
|
|
808
|
-
*/
|
|
809
|
-
assertChildIdAvailable(childId) {
|
|
810
|
-
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");
|
|
811
|
-
}
|
|
812
|
-
/**
|
|
813
|
-
* Pre-register `childId` in a continuation-managed parent's owned set so the
|
|
814
|
-
* parent cannot settle while a caller is still establishing or resuming that
|
|
815
|
-
* child. Returns a releaser for the failure path; it removes only a hold
|
|
816
|
-
* this call added, and leaves ownership in place once a live Activation for
|
|
817
|
-
* the child exists.
|
|
818
|
-
* @param parent - the live direct parent the operation is admitted under.
|
|
819
|
-
* @param childId - the durable child the operation addresses.
|
|
820
|
-
* @returns the failure-path releaser; a no-op when nothing was added.
|
|
821
|
-
*/
|
|
822
|
-
holdOwnership(parent, childId) {
|
|
823
|
-
const parentActivation = this.resident.get(parent.id);
|
|
824
|
-
if (parentActivation === void 0 || parentActivation.handle.agent !== parent) return () => {};
|
|
825
|
-
if (parentActivation.inbox.closing !== void 0) throw new SubagentError(`subagent parent "${parent.id}" is being disposed; the child was not established`, "ACTIVATION_CLOSING");
|
|
826
|
-
if (parentActivation.ownedChildren.has(childId)) return () => {};
|
|
827
|
-
parentActivation.ownedChildren.add(childId);
|
|
828
|
-
return () => {
|
|
829
|
-
const live = this.resident.get(childId);
|
|
830
|
-
/* v8 ignore next 4 -- reaching this arm needs another delivery to establish the child
|
|
831
|
-
* between this operation's failure and its releaser running, which no test can schedule
|
|
832
|
-
* deterministically: the ownership edge then belongs to that live Activation, so the
|
|
833
|
-
* conservative keep leaves it for finishDisposal's releaseOwnership. */
|
|
834
|
-
if (live !== void 0 && live.inbox.closing === void 0) return;
|
|
835
|
-
if (parentActivation.ownedChildren.delete(childId)) this.wake(parentActivation);
|
|
836
|
-
};
|
|
837
|
-
}
|
|
838
|
-
/**
|
|
839
|
-
* Interrupt one live continuable child's current turn under the supplied authority.
|
|
840
|
-
* @param targetSessionId - the durable child session id to interrupt.
|
|
841
|
-
* @param authority - the human parent address or exact live ancestor Agent.
|
|
842
|
-
*/
|
|
843
|
-
interrupt(targetSessionId, authority) {
|
|
844
|
-
if (authority.kind === "ancestor") {
|
|
845
|
-
const caller = authority.agent;
|
|
846
|
-
if (this.ctx.agents.get(caller.id) !== caller) throw new SubagentError(`interrupting "${targetSessionId}" requires the exact live ancestor agent`, "UNAUTHORIZED");
|
|
847
|
-
if (caller.id === targetSessionId) throw new SubagentError(`agent "${caller.id}" cannot interrupt itself`, "UNAUTHORIZED");
|
|
848
|
-
}
|
|
849
|
-
const activation = this.resident.get(targetSessionId);
|
|
850
|
-
if (activation === void 0) return;
|
|
851
|
-
if (authority.kind === "user") {
|
|
852
|
-
if (activation.handle.agent.session.header.parentSession !== authority.parentSessionId) throw new SubagentError(`subagent "${targetSessionId}" belongs to another parent session`, "UNAUTHORIZED");
|
|
853
|
-
} else if (!activation.ancestry.has(authority.agent)) throw new SubagentError(`subagent "${targetSessionId}" is not a live descendant of agent "${authority.agent.id}"`, "UNAUTHORIZED");
|
|
854
|
-
if (activation.inbox.closing !== void 0) return;
|
|
855
|
-
activation.handle.agent.cancel(authority.kind === "user" ? { kind: "user" } : { kind: "parent" }, { keepInbox: true });
|
|
856
|
-
}
|
|
857
|
-
/**
|
|
858
|
-
* Send through a receiving parent's Activation inbox when it has one.
|
|
859
|
-
* @param parent - exact live Agent receiving the message.
|
|
860
|
-
* @param message - durable user message to deliver.
|
|
861
|
-
* @param delivery - receiving inbox destination.
|
|
862
|
-
*/
|
|
863
|
-
sendWaking(parent, message, delivery) {
|
|
864
|
-
const parentActivation = this.resident.get(parent.id);
|
|
865
|
-
if (parentActivation !== void 0 && parentActivation.handle.agent === parent) {
|
|
866
|
-
try {
|
|
867
|
-
parentActivation.inbox.deliver(message, delivery);
|
|
868
|
-
} finally {
|
|
869
|
-
this.wake(parentActivation);
|
|
870
|
-
}
|
|
871
|
-
return;
|
|
872
|
-
}
|
|
873
|
-
if (delivery === "steer") parent.steer(message);
|
|
874
|
-
else parent.followup(message);
|
|
875
|
-
}
|
|
876
|
-
/**
|
|
877
|
-
* Close admission, await every already-admitted materialization through
|
|
878
|
-
* publication or rollback, then dispose the stable live Activation graph
|
|
879
|
-
* child-first.
|
|
880
|
-
*/
|
|
881
|
-
async drain() {
|
|
882
|
-
this.draining = true;
|
|
883
|
-
await Promise.all([...this.materializations].map((materialization) => materialization.settled));
|
|
884
|
-
const owned = /* @__PURE__ */ new Set();
|
|
885
|
-
for (const activation of this.resident.values()) for (const child of activation.ownedChildren) owned.add(child);
|
|
886
|
-
const roots = [...this.resident.values()].filter((activation) => !owned.has(activation.childId));
|
|
887
|
-
await this.disposeRoots(roots, "activation(s)");
|
|
888
|
-
}
|
|
889
|
-
/**
|
|
890
|
-
* Stop only the continuable descendants of exact live host-owned parents.
|
|
891
|
-
* @param parents - exact live roots whose continuable descendants must stop.
|
|
892
|
-
*/
|
|
893
|
-
async drainDescendants(parents) {
|
|
894
|
-
const roots = new Set(parents.filter((parent) => this.ctx.agents.get(parent.id) === parent));
|
|
895
|
-
if (roots.size === 0) return;
|
|
896
|
-
for (const root of roots) this.closingMembers(root).add(root);
|
|
897
|
-
const targets = [];
|
|
898
|
-
for (const activation of this.resident.values()) {
|
|
899
|
-
const lineage = this.liveLineage(activation.handle.agent);
|
|
900
|
-
const owners = [...roots].filter((root) => activation.handle.agent !== root && activation.ancestry.has(root));
|
|
901
|
-
if (owners.length === 0) continue;
|
|
902
|
-
targets.push(activation);
|
|
903
|
-
for (const owner of owners) {
|
|
904
|
-
const members = this.closingMembers(owner);
|
|
905
|
-
members.add(activation.handle.agent);
|
|
906
|
-
for (const agent of lineage) members.add(agent);
|
|
907
|
-
}
|
|
908
|
-
}
|
|
909
|
-
const materializations = [...this.materializations].filter((materialization) => {
|
|
910
|
-
const owners = [...roots].filter((root) => materialization.lineage.includes(root));
|
|
911
|
-
for (const owner of owners) {
|
|
912
|
-
const members = this.closingMembers(owner);
|
|
913
|
-
for (const agent of materialization.lineage) members.add(agent);
|
|
914
|
-
}
|
|
915
|
-
return owners.length > 0;
|
|
916
|
-
});
|
|
917
|
-
const ownedTargets = /* @__PURE__ */ new Set();
|
|
918
|
-
for (const activation of targets) for (const child of activation.ownedChildren) ownedTargets.add(child);
|
|
919
|
-
const targetRoots = targets.filter((activation) => !ownedTargets.has(activation.childId));
|
|
920
|
-
for (const activation of targets) this.dispose(activation).catch(() => void 0);
|
|
921
|
-
await Promise.all(materializations.map((materialization) => materialization.settled));
|
|
922
|
-
await this.disposeRoots(targetRoots, "scoped activation(s)");
|
|
923
|
-
}
|
|
924
|
-
/**
|
|
925
|
-
* Release selected resident direct children of one exact live parent.
|
|
926
|
-
* @param parent - exact live direct parent authorizing the selected release.
|
|
927
|
-
* @param childIds - durable direct-child ids to release when resident.
|
|
928
|
-
*/
|
|
929
|
-
async drainChildren(parent, childIds) {
|
|
930
|
-
if (this.ctx.agents.get(parent.id) !== parent) throw new SubagentError("selected child teardown requires the exact live parent agent", "UNAUTHORIZED");
|
|
931
|
-
const targets = [];
|
|
932
|
-
for (const childId of new Set(childIds)) {
|
|
933
|
-
const activation = this.resident.get(childId);
|
|
934
|
-
if (activation === void 0) continue;
|
|
935
|
-
if (activation.parentSession !== parent.id || !activation.ancestry.has(parent)) throw new SubagentError(`subagent "${childId}" is not a direct child of agent "${parent.id}"`, "UNAUTHORIZED");
|
|
936
|
-
targets.push(activation);
|
|
937
|
-
}
|
|
938
|
-
for (const activation of targets) this.dispose(activation).catch(() => void 0);
|
|
939
|
-
await this.disposeRoots(targets, "selected activation(s)");
|
|
940
|
-
}
|
|
941
|
-
/**
|
|
942
|
-
* Reject new admission once the registry or this exact parent tree began draining.
|
|
943
|
-
* @param agent - exact live Agent whose lineage determines admission.
|
|
944
|
-
*/
|
|
945
|
-
assertAdmitting(agent) {
|
|
946
|
-
const closing = this.closingTeardownFor(agent);
|
|
947
|
-
if (closing === void 0) return;
|
|
948
|
-
throw new SubagentError(closing === "manager" ? "continuable subagents are draining; the operation was not admitted" : `continuable subagents below parent "${closing.id}" are draining; the operation was not admitted`, "DRAINING");
|
|
949
|
-
}
|
|
950
|
-
/**
|
|
951
|
-
* Authorize one operation against the durable direct-parent lineage.
|
|
952
|
-
* @param parent - exact live Agent claiming direct-parent authority.
|
|
953
|
-
* @param childId - durable child session id addressed by the operation.
|
|
954
|
-
* @param parentSession - durable direct-parent id recorded by the child.
|
|
955
|
-
*/
|
|
956
|
-
authorizeLineage(parent, childId, parentSession) {
|
|
957
|
-
if (this.ctx.agents.get(parent.id) !== parent) throw new SubagentError(`subagent "${childId}" delivery requires the exact live parent agent`, "UNAUTHORIZED");
|
|
958
|
-
if (parentSession !== parent.id) throw new SubagentError(`subagent "${childId}" belongs to another parent session`, "UNAUTHORIZED");
|
|
959
|
-
}
|
|
960
|
-
/**
|
|
961
|
-
* Create or resume one child Agent and publish its Activation.
|
|
962
|
-
* @param inputs - reconstruction and admission inputs for the residency epoch.
|
|
963
|
-
* @returns the published process-local Activation.
|
|
964
|
-
*/
|
|
965
|
-
materialize(inputs) {
|
|
966
|
-
this.assertAdmitting(inputs.parent);
|
|
967
|
-
inputs.signal.throwIfAborted();
|
|
968
|
-
const lineage = this.liveLineage(inputs.parent);
|
|
969
|
-
const pool = this.resident.get(inputs.parent.id)?.pool ?? this.rootPool(inputs.parent);
|
|
970
|
-
const releaseSlot = pool.reserve(this.maxActiveSubagents());
|
|
971
|
-
const settled = Promise.withResolvers();
|
|
972
|
-
const materialization = {
|
|
973
|
-
lineage,
|
|
974
|
-
settled: settled.promise
|
|
975
|
-
};
|
|
976
|
-
this.materializations.add(materialization);
|
|
977
|
-
return this.materializeTracked(inputs, lineage, pool, releaseSlot).catch((error) => {
|
|
978
|
-
releaseSlot();
|
|
979
|
-
throw error;
|
|
980
|
-
}).finally(() => {
|
|
981
|
-
this.materializations.delete(materialization);
|
|
982
|
-
settled.resolve();
|
|
983
|
-
});
|
|
984
|
-
}
|
|
985
|
-
/**
|
|
986
|
-
* Cross the final admission cutoff and submit without yielding.
|
|
987
|
-
* @param activation - the exact resident child receiving the message.
|
|
988
|
-
* @param message - the already-built durable user message.
|
|
989
|
-
* @param delivery - the Agent inbox destination.
|
|
990
|
-
* @param parent - exact live direct parent authorizing admission.
|
|
991
|
-
* @param signal - caller cancellation before inbox acceptance.
|
|
992
|
-
* @returns the accepted durable message id.
|
|
993
|
-
*/
|
|
994
|
-
submitAdmitted(activation, message, delivery, parent, signal) {
|
|
995
|
-
signal.throwIfAborted();
|
|
996
|
-
this.assertAdmitting(parent);
|
|
997
|
-
this.authorizeLineage(parent, activation.childId, activation.handle.agent.session.header.parentSession);
|
|
998
|
-
this.acquireOwnership(parent, activation.childId);
|
|
999
|
-
try {
|
|
1000
|
-
activation.inbox.deliver(message, delivery);
|
|
1001
|
-
} finally {
|
|
1002
|
-
this.wake(activation);
|
|
1003
|
-
}
|
|
1004
|
-
return message.id;
|
|
1005
|
-
}
|
|
1006
|
-
/**
|
|
1007
|
-
* Stop and release one Activation through its memoized close transaction.
|
|
1008
|
-
* @param activation - exact residency epoch to close.
|
|
1009
|
-
* @param finalStateFlushed - whether natural settlement already flushed final state.
|
|
1010
|
-
* @returns the shared close transaction.
|
|
1011
|
-
*/
|
|
1012
|
-
dispose(activation, finalStateFlushed = false) {
|
|
1013
|
-
return activation.inbox.close(() => this.finishDisposal(activation, finalStateFlushed));
|
|
1014
|
-
}
|
|
1015
|
-
/** Dispose independent roots and report every branch failure after all settle. */
|
|
1016
|
-
async disposeRoots(roots, failureSubject) {
|
|
1017
|
-
const reasons = (await Promise.all(roots.map(async (activation) => {
|
|
1018
|
-
try {
|
|
1019
|
-
await this.dispose(activation);
|
|
1020
|
-
return;
|
|
1021
|
-
} catch (error) {
|
|
1022
|
-
return error;
|
|
1023
|
-
}
|
|
1024
|
-
}))).filter((failure) => failure !== void 0);
|
|
1025
|
-
if (reasons.length > 0) throw new SubagentError(`continuable subagent teardown failed for ${reasons.length} ${failureSubject}: ` + reasons.map((reason) => errorChain(reason)).join("; "), "ACTIVATION_TEARDOWN_FAILED");
|
|
1026
|
-
}
|
|
1027
|
-
/** Return the retained member set for one exact scoped-teardown root. */
|
|
1028
|
-
closingMembers(root) {
|
|
1029
|
-
const existing = this.closingScopes.get(root);
|
|
1030
|
-
if (existing !== void 0) return existing;
|
|
1031
|
-
const members = /* @__PURE__ */ new Set();
|
|
1032
|
-
this.closingScopes.set(root, members);
|
|
1033
|
-
return members;
|
|
1034
|
-
}
|
|
1035
|
-
/** Return the exact currently resolvable ancestry from `agent` upward. */
|
|
1036
|
-
liveLineage(agent) {
|
|
1037
|
-
const lineage = [agent];
|
|
1038
|
-
const seen = new Set([agent.id]);
|
|
1039
|
-
let parentSession = agent.session.header.parentSession;
|
|
1040
|
-
while (parentSession !== void 0) {
|
|
1041
|
-
const parent = this.ctx.agents.get(parentSession);
|
|
1042
|
-
if (parent === void 0 || seen.has(parent.id)) break;
|
|
1043
|
-
lineage.push(parent);
|
|
1044
|
-
seen.add(parent.id);
|
|
1045
|
-
parentSession = parent.session.header.parentSession;
|
|
1046
|
-
}
|
|
1047
|
-
return lineage;
|
|
1048
|
-
}
|
|
1049
|
-
/** Return the teardown that closed continuable admission for this agent's lineage. */
|
|
1050
|
-
closingTeardownFor(agent) {
|
|
1051
|
-
if (this.draining) return "manager";
|
|
1052
|
-
const lineage = this.liveLineage(agent);
|
|
1053
|
-
for (const [root, members] of this.closingScopes) if (members.has(agent) || lineage.includes(root)) return root;
|
|
1054
|
-
}
|
|
1055
|
-
/** Resolve a root's pool once; descendants inherit their resident parent's pool directly. */
|
|
1056
|
-
rootPool(parent) {
|
|
1057
|
-
let pool = this.rootPools.get(parent);
|
|
1058
|
-
if (pool === void 0) {
|
|
1059
|
-
pool = new ActivationPool();
|
|
1060
|
-
this.rootPools.set(parent, pool);
|
|
1061
|
-
}
|
|
1062
|
-
return pool;
|
|
1063
|
-
}
|
|
1064
|
-
/** Perform one tracked materialization through publication or rollback. */
|
|
1065
|
-
async materializeTracked(inputs, parentLineage, pool, releaseSlot) {
|
|
1066
|
-
const { childId, provider, parent, create } = inputs;
|
|
1067
|
-
inputs.signal.throwIfAborted();
|
|
1068
|
-
const setup = (childCtx, child) => {
|
|
1069
|
-
if (create !== void 0) {
|
|
1070
|
-
child.session.append("subagent/descriptor", create.descriptor);
|
|
1071
|
-
appendDelegatedPolicyOverrides(child.session, create.delegatedPolicies);
|
|
1072
|
-
}
|
|
1073
|
-
applyChildComposition(childCtx, parent, inputs.composition);
|
|
1074
|
-
};
|
|
1075
|
-
const observer = this.observeActivation(provider, childId, parent);
|
|
1076
|
-
const handle = create === void 0 ? await this.ownerCtx.agents.resume({
|
|
1077
|
-
resumeSessionId: childId,
|
|
1078
|
-
parentAgent: parent,
|
|
1079
|
-
agentOptions: inputs.agentOptions,
|
|
1080
|
-
signal: inputs.signal,
|
|
1081
|
-
setup
|
|
1082
|
-
}) : await this.ownerCtx.agents.create({
|
|
1083
|
-
sessionId: childId,
|
|
1084
|
-
parentAgent: parent,
|
|
1085
|
-
meta: create.meta,
|
|
1086
|
-
...create.seed === void 0 ? {} : { seed: create.seed },
|
|
1087
|
-
inheritedEventCount: create.inheritedEventCount,
|
|
1088
|
-
agentOptions: inputs.agentOptions,
|
|
1089
|
-
signal: inputs.signal,
|
|
1090
|
-
setup
|
|
1091
|
-
});
|
|
1092
|
-
const activation = {
|
|
1093
|
-
pool,
|
|
1094
|
-
releaseSlot,
|
|
1095
|
-
childId,
|
|
1096
|
-
parentSession: parent.id,
|
|
1097
|
-
provider,
|
|
1098
|
-
handle,
|
|
1099
|
-
inbox: new SubagentInbox(handle.agent),
|
|
1100
|
-
ancestry: new WeakSet([handle.agent, ...parentLineage]),
|
|
1101
|
-
ownedChildren: /* @__PURE__ */ new Set(),
|
|
1102
|
-
observer,
|
|
1103
|
-
announced: false,
|
|
1104
|
-
poke: Promise.withResolvers()
|
|
1105
|
-
};
|
|
1106
|
-
this.resident.set(childId, activation);
|
|
1107
|
-
try {
|
|
1108
|
-
inputs.signal.throwIfAborted();
|
|
1109
|
-
this.assertAdmitting(parent);
|
|
1110
|
-
this.acquireOwnership(parent, childId);
|
|
1111
|
-
const wakeOnInboxRemoval = () => {
|
|
1112
|
-
this.wake(activation);
|
|
1113
|
-
};
|
|
1114
|
-
handle.agent.ctx.on("agent/inbox/claimed", wakeOnInboxRemoval);
|
|
1115
|
-
handle.agent.ctx.on("agent/inbox/discarded", wakeOnInboxRemoval);
|
|
1116
|
-
observer.start(handle.agent);
|
|
1117
|
-
} catch (error) {
|
|
1118
|
-
/* v8 ignore next -- rollback failure must not mask the admission failure
|
|
1119
|
-
* that prevented this operation from returning an accepted message id. */
|
|
1120
|
-
await this.rollbackUnpublished(activation).catch(() => void 0);
|
|
1121
|
-
throw error;
|
|
1122
|
-
}
|
|
1123
|
-
this.watchSettlement(activation);
|
|
1124
|
-
return activation;
|
|
1125
|
-
}
|
|
1126
|
-
/** Release an Activation whose start edge was not published. */
|
|
1127
|
-
rollbackUnpublished(activation) {
|
|
1128
|
-
return activation.inbox.close(async () => {
|
|
1129
|
-
try {
|
|
1130
|
-
await activation.handle.dispose();
|
|
1131
|
-
} finally {
|
|
1132
|
-
this.resident.delete(activation.childId);
|
|
1133
|
-
activation.releaseSlot();
|
|
1134
|
-
this.releaseOwnership(activation.childId);
|
|
1135
|
-
}
|
|
1136
|
-
});
|
|
1137
|
-
}
|
|
1138
|
-
/** Register the child in a continuation-managed parent's owned set. */
|
|
1139
|
-
acquireOwnership(parent, childId) {
|
|
1140
|
-
const parentActivation = this.resident.get(parent.id);
|
|
1141
|
-
if (parentActivation === void 0) return;
|
|
1142
|
-
if (parentActivation.inbox.closing !== void 0) throw new SubagentError(`subagent parent "${parent.id}" is being disposed; the child was not established`, "ACTIVATION_CLOSING");
|
|
1143
|
-
parentActivation.ownedChildren.add(childId);
|
|
1144
|
-
}
|
|
1145
|
-
/** Remove one child from its live owner's set and let that owner re-check settlement. */
|
|
1146
|
-
releaseOwnership(childId) {
|
|
1147
|
-
for (const candidate of this.resident.values()) if (candidate.ownedChildren.delete(childId)) this.wake(candidate);
|
|
1148
|
-
}
|
|
1149
|
-
/** Let a settlement watcher re-check residency after relevant state changes. */
|
|
1150
|
-
wake(activation) {
|
|
1151
|
-
activation.poke.resolve();
|
|
1152
|
-
activation.poke = Promise.withResolvers();
|
|
1153
|
-
}
|
|
1154
|
-
/** Follow one Activation to natural settlement. */
|
|
1155
|
-
watchSettlement(activation) {
|
|
1156
|
-
(async () => {
|
|
1157
|
-
while (true) {
|
|
1158
|
-
const idleObservation = activation.poke;
|
|
1159
|
-
await activation.handle.agent.whenIdle();
|
|
1160
|
-
if (activation.inbox.closing !== void 0) return;
|
|
1161
|
-
const readiness = await this.locks.run(activation.childId, () => Promise.resolve(this.settlementState(activation, idleObservation)));
|
|
1162
|
-
if (readiness === "closed") return;
|
|
1163
|
-
if (readiness === "retry") continue;
|
|
1164
|
-
if (readiness === "wait") {
|
|
1165
|
-
await idleObservation.promise;
|
|
1166
|
-
continue;
|
|
1167
|
-
}
|
|
1168
|
-
const finalSeq = activation.handle.agent.session.seq;
|
|
1169
|
-
await this.flushFinalState(activation);
|
|
1170
|
-
const attempt = await this.locks.run(activation.childId, () => {
|
|
1171
|
-
const state = this.settlementState(activation, idleObservation);
|
|
1172
|
-
if (state !== "ready") return Promise.resolve(state);
|
|
1173
|
-
if (activation.handle.agent.session.seq !== finalSeq) return Promise.resolve("retry");
|
|
1174
|
-
let done;
|
|
1175
|
-
try {
|
|
1176
|
-
activation.handle.agent.runMaintenance(() => {
|
|
1177
|
-
done = this.dispose(activation, true);
|
|
1178
|
-
return Promise.resolve();
|
|
1179
|
-
});
|
|
1180
|
-
} catch {
|
|
1181
|
-
return Promise.resolve("retry");
|
|
1182
|
-
}
|
|
1183
|
-
return Promise.resolve({ done });
|
|
1184
|
-
});
|
|
1185
|
-
if (attempt === "closed") return;
|
|
1186
|
-
if (attempt === "retry") continue;
|
|
1187
|
-
if (attempt === "wait") {
|
|
1188
|
-
await idleObservation.promise;
|
|
1189
|
-
continue;
|
|
1190
|
-
}
|
|
1191
|
-
try {
|
|
1192
|
-
await attempt.done;
|
|
1193
|
-
} catch (error) {
|
|
1194
|
-
this.ctx.logger.warn(`subagent "${activation.childId}" activation teardown failed: ${errorChain(error)}`);
|
|
1195
|
-
}
|
|
1196
|
-
return;
|
|
1197
|
-
}
|
|
1198
|
-
})();
|
|
1199
|
-
}
|
|
1200
|
-
/** Classify one Inbox and owned-child observation without reading Agent execution state. */
|
|
1201
|
-
settlementState(activation, observation) {
|
|
1202
|
-
if (activation.inbox.closing !== void 0) return "closed";
|
|
1203
|
-
if (activation.poke !== observation) return "retry";
|
|
1204
|
-
if (activation.inbox.hasPending || activation.ownedChildren.size > 0) return "wait";
|
|
1205
|
-
return "ready";
|
|
1206
|
-
}
|
|
1207
|
-
/** Propagate stop synchronously, then finish the child-first release. */
|
|
1208
|
-
async finishDisposal(activation, finalStateFlushed) {
|
|
1209
|
-
this.wake(activation);
|
|
1210
|
-
const { childId } = activation;
|
|
1211
|
-
const failures = [];
|
|
1212
|
-
if (finalStateFlushed) try {
|
|
1213
|
-
activation.observer.capture(activation.handle.agent);
|
|
1214
|
-
} catch (error) {
|
|
1215
|
-
failures.push(new SubagentError(`subagent "${childId}" activation teardown failed: ${errorChain(error)}`, "ACTIVATION_TEARDOWN_FAILED", { cause: error }));
|
|
1216
|
-
}
|
|
1217
|
-
else {
|
|
1218
|
-
activation.handle.agent.cancel({ kind: "parent" });
|
|
1219
|
-
const idle = activation.handle.agent.whenIdle();
|
|
1220
|
-
const childDisposals = [...activation.ownedChildren].map((child) => this.resident.get(child)).filter((child) => child !== void 0).map((child) => this.dispose(child));
|
|
1221
|
-
try {
|
|
1222
|
-
const reasons = (await Promise.all(childDisposals.map(async (disposal) => {
|
|
1223
|
-
try {
|
|
1224
|
-
await disposal;
|
|
1225
|
-
return;
|
|
1226
|
-
} catch (error) {
|
|
1227
|
-
return error;
|
|
1228
|
-
}
|
|
1229
|
-
}))).filter((reason) => reason !== void 0);
|
|
1230
|
-
if (reasons.length > 0) failures.push(new SubagentError(`subagent "${childId}" child teardown failed: ${reasons.map((reason) => errorChain(reason)).join("; ")}`, "ACTIVATION_TEARDOWN_FAILED"));
|
|
1231
|
-
await idle;
|
|
1232
|
-
await this.flushFinalState(activation);
|
|
1233
|
-
activation.observer.capture(activation.handle.agent);
|
|
1234
|
-
} catch (error) {
|
|
1235
|
-
failures.push(new SubagentError(`subagent "${childId}" activation teardown failed: ${errorChain(error)}`, "ACTIVATION_TEARDOWN_FAILED", { cause: error }));
|
|
1236
|
-
}
|
|
1237
|
-
}
|
|
1238
|
-
try {
|
|
1239
|
-
await activation.handle.dispose();
|
|
1240
|
-
} catch (error) {
|
|
1241
|
-
failures.push(new SubagentError(`subagent "${childId}" activation handle disposal failed: ${errorChain(error)}`, "ACTIVATION_TEARDOWN_FAILED", { cause: error }));
|
|
1242
|
-
}
|
|
1243
|
-
let failure;
|
|
1244
|
-
if (failures.length === 1) failure = failures[0];
|
|
1245
|
-
else if (failures.length > 1) failure = new SubagentError(`subagent "${childId}" activation teardown failed at ${failures.length} boundaries: ` + failures.map((item) => errorChain(item)).join("; "), "ACTIVATION_TEARDOWN_FAILED", { cause: new AggregateError(failures) });
|
|
1246
|
-
this.resident.delete(childId);
|
|
1247
|
-
activation.releaseSlot();
|
|
1248
|
-
this.notifySettlement(activation, activation.observer.terminal(failure));
|
|
1249
|
-
this.releaseOwnership(childId);
|
|
1250
|
-
activation.observer.settle(failure);
|
|
1251
|
-
if (failure !== void 0) throw failure;
|
|
1252
|
-
}
|
|
1253
|
-
/** Tell the durable direct parent how this Activation ended. */
|
|
1254
|
-
notifySettlement(activation, terminal) {
|
|
1255
|
-
if (!activation.announced) return;
|
|
1256
|
-
try {
|
|
1257
|
-
const parent = this.ctx.agents.get(activation.parentSession);
|
|
1258
|
-
if (parent === void 0) return;
|
|
1259
|
-
const message = createSettlementMessage(activation.childId, terminal);
|
|
1260
|
-
if (this.closingTeardownFor(parent) !== void 0) {
|
|
1261
|
-
parent.inject(message);
|
|
1262
|
-
return;
|
|
1263
|
-
}
|
|
1264
|
-
this.sendWaking(parent, message, parent.status === "idle" ? "queue" : "steer");
|
|
1265
|
-
} catch (error) {
|
|
1266
|
-
this.ctx.logger.warn(`subagent "${activation.childId}" settlement notice was not delivered to its parent: ` + errorChain(error));
|
|
1267
|
-
}
|
|
1268
|
-
}
|
|
1269
|
-
/** Request a best-effort final session flush before closing natural-settlement admission. */
|
|
1270
|
-
async flushFinalState(activation) {
|
|
1271
|
-
const child = activation.handle.agent;
|
|
1272
|
-
try {
|
|
1273
|
-
await child.ctx.sessions.flush(child.session);
|
|
1274
|
-
} catch (error) {
|
|
1275
|
-
this.ctx.logger.warn(`subagent "${activation.childId}" best-effort final session flush failed; the persisted state may be unavailable or stale on resume: ${errorChain(error)}`);
|
|
1276
|
-
}
|
|
1277
|
-
}
|
|
1278
|
-
};
|
|
1279
|
-
//#endregion
|
|
1280
|
-
//#region lib/types/descriptor.js
|
|
1281
|
-
/**
|
|
1282
|
-
* The durable subagent-child descriptor: the versioned, model-hidden
|
|
1283
|
-
* `subagent/descriptor` session event that identifies every session-backed
|
|
1284
|
-
* subagent and records whether it is one-shot or continuable. Continuable
|
|
1285
|
-
* descriptors additionally preserve the declared composition required for
|
|
1286
|
-
* cold resume. Providers append it turn-enclosed in the child's initial turn.
|
|
1287
|
-
*
|
|
1288
|
-
* The descriptor deliberately snapshots explicit fields rather than the
|
|
1289
|
-
* merge-extensible `AgentOptions` object: an unrelated extension value cannot
|
|
1290
|
-
* make continuation fail merely because it is not JSON, and later composition
|
|
1291
|
-
* inputs require a deliberate {@link SUBAGENT_DESCRIPTOR_VERSION} change. It
|
|
1292
|
-
* omits `subagentDepth` — cold resume trusts the persisted header's
|
|
1293
|
-
* `delegationDepth` as the monotone floor — and `outputSchema`, which belongs
|
|
1294
|
-
* to one activation's result contract rather than durable child composition.
|
|
1295
|
-
* Per-activation knobs such as `maxTokens` are omitted for the same reason as
|
|
1296
|
-
* `outputSchema`: they budget one activation. Cold resume requires the exact
|
|
1297
|
-
* live parent for authorization but reconstructs child options only from the
|
|
1298
|
-
* durable descriptor, so it neither restores the prior budget nor inherits
|
|
1299
|
-
* the parent's current one; the resumed route's defaults apply instead.
|
|
1300
|
-
*
|
|
1301
|
-
* @module @deepseek-ai/dsh-subagent/descriptor
|
|
1302
|
-
*/
|
|
1303
|
-
/**
|
|
1304
|
-
* The current descriptor format version, stamped into every appended
|
|
1305
|
-
* `subagent/descriptor` event and required verbatim by {@link foldSubagentDescriptor}.
|
|
1306
|
-
* Supporting another composition input is a deliberate version change, never
|
|
1307
|
-
* an implicit extra field.
|
|
1308
|
-
*/
|
|
1309
|
-
const SUBAGENT_DESCRIPTOR_VERSION = 3;
|
|
1310
|
-
const DESCRIPTOR_BASE_KEYS = [
|
|
1311
|
-
"version",
|
|
1312
|
-
"mode",
|
|
1313
|
-
"provider",
|
|
1314
|
-
"label"
|
|
1315
|
-
];
|
|
1316
|
-
const ONE_SHOT_DESCRIPTOR_KEYS = new Set(DESCRIPTOR_BASE_KEYS);
|
|
1317
|
-
const CONTINUABLE_DESCRIPTOR_KEYS = new Set([
|
|
1318
|
-
...DESCRIPTOR_BASE_KEYS,
|
|
1319
|
-
"agentProvider",
|
|
1320
|
-
"agentModel",
|
|
1321
|
-
"agentReasoningEffort",
|
|
1322
|
-
"persona",
|
|
1323
|
-
"toolFilter"
|
|
1324
|
-
]);
|
|
1325
|
-
const TOOL_FILTER_KEYS = new Set(["allow", "deny"]);
|
|
1326
|
-
/** Whether a persisted JSON value is an object record. */
|
|
1327
|
-
function isRecord(value) {
|
|
1328
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1329
|
-
}
|
|
1330
|
-
/** Reject fields outside one versioned record's declared schema. */
|
|
1331
|
-
function assertKnownKeys(value, keys, path) {
|
|
1332
|
-
const unknown = Object.keys(value).find((key) => !keys.has(key));
|
|
1333
|
-
if (unknown !== void 0) throw new Error(`persisted subagent descriptor ${path} has unknown field "${unknown}"`);
|
|
1334
|
-
}
|
|
1335
|
-
/** Read one optional string field from a persisted descriptor record. */
|
|
1336
|
-
function optionalString(value, key) {
|
|
1337
|
-
if (!Object.hasOwn(value, key)) return void 0;
|
|
1338
|
-
const field = value[key];
|
|
1339
|
-
if (typeof field !== "string") throw new Error(`persisted subagent descriptor ${key} must be a string`);
|
|
1340
|
-
return field;
|
|
1341
|
-
}
|
|
1342
|
-
/** Read one optional string-array field from a persisted tool restriction. */
|
|
1343
|
-
function optionalStringArray(value, key) {
|
|
1344
|
-
if (!Object.hasOwn(value, key)) return void 0;
|
|
1345
|
-
const field = value[key];
|
|
1346
|
-
if (!Array.isArray(field)) throw new Error(`persisted subagent descriptor toolFilter.${key} must be an array of strings`);
|
|
1347
|
-
const items = field;
|
|
1348
|
-
if (items.some((item) => typeof item !== "string")) throw new Error(`persisted subagent descriptor toolFilter.${key} must be an array of strings`);
|
|
1349
|
-
return items;
|
|
1350
|
-
}
|
|
1351
|
-
/** Validate and reconstruct a persisted tool restriction. */
|
|
1352
|
-
function parseToolFilter(value) {
|
|
1353
|
-
if (!isRecord(value)) throw new Error("persisted subagent descriptor toolFilter must be an object");
|
|
1354
|
-
assertKnownKeys(value, TOOL_FILTER_KEYS, "toolFilter");
|
|
1355
|
-
const allow = optionalStringArray(value, "allow");
|
|
1356
|
-
const deny = optionalStringArray(value, "deny");
|
|
1357
|
-
if (allow === void 0 && deny === void 0) throw new Error("persisted subagent descriptor toolFilter must declare allow and/or deny");
|
|
1358
|
-
return {
|
|
1359
|
-
...allow !== void 0 ? { allow } : {},
|
|
1360
|
-
...deny !== void 0 ? { deny } : {}
|
|
1361
|
-
};
|
|
1362
|
-
}
|
|
1363
|
-
/** Validate one persisted descriptor payload for the current runtime. */
|
|
1364
|
-
function parseSubagentDescriptor(value) {
|
|
1365
|
-
if (!isRecord(value)) throw new Error("persisted subagent descriptor payload must be an object");
|
|
1366
|
-
const version = value["version"];
|
|
1367
|
-
if (typeof version !== "number") throw new Error("persisted subagent descriptor version must be a number");
|
|
1368
|
-
if (version !== 3) return void 0;
|
|
1369
|
-
const mode = value["mode"];
|
|
1370
|
-
if (mode !== "one-shot" && mode !== "continuable") throw new Error("persisted subagent descriptor mode must be \"one-shot\" or \"continuable\"");
|
|
1371
|
-
assertKnownKeys(value, mode === "one-shot" ? ONE_SHOT_DESCRIPTOR_KEYS : CONTINUABLE_DESCRIPTOR_KEYS, "payload");
|
|
1372
|
-
const provider = value["provider"];
|
|
1373
|
-
if (typeof provider !== "string") throw new Error("persisted subagent descriptor provider must be a string");
|
|
1374
|
-
if (mode === "one-shot") {
|
|
1375
|
-
const label = optionalString(value, "label");
|
|
1376
|
-
return {
|
|
1377
|
-
version: 3,
|
|
1378
|
-
mode,
|
|
1379
|
-
provider,
|
|
1380
|
-
...label !== void 0 ? { label } : {}
|
|
577
|
+
const SUBAGENT_DESCRIPTOR_VERSION = 3;
|
|
578
|
+
const DESCRIPTOR_BASE_KEYS = [
|
|
579
|
+
"version",
|
|
580
|
+
"mode",
|
|
581
|
+
"provider",
|
|
582
|
+
"label"
|
|
583
|
+
];
|
|
584
|
+
const ONE_SHOT_DESCRIPTOR_KEYS = new Set(DESCRIPTOR_BASE_KEYS);
|
|
585
|
+
const CONTINUABLE_DESCRIPTOR_KEYS = new Set([
|
|
586
|
+
...DESCRIPTOR_BASE_KEYS,
|
|
587
|
+
"agentProvider",
|
|
588
|
+
"agentModel",
|
|
589
|
+
"agentReasoningEffort",
|
|
590
|
+
"persona",
|
|
591
|
+
"toolFilter"
|
|
592
|
+
]);
|
|
593
|
+
const TOOL_FILTER_KEYS = new Set(["allow", "deny"]);
|
|
594
|
+
/** Whether a persisted JSON value is an object record. */
|
|
595
|
+
function isRecord(value) {
|
|
596
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
597
|
+
}
|
|
598
|
+
/** Reject fields outside one versioned record's declared schema. */
|
|
599
|
+
function assertKnownKeys(value, keys, path) {
|
|
600
|
+
const unknown = Object.keys(value).find((key) => !keys.has(key));
|
|
601
|
+
if (unknown !== void 0) throw new Error(`persisted subagent descriptor ${path} has unknown field "${unknown}"`);
|
|
602
|
+
}
|
|
603
|
+
/** Read one optional string field from a persisted descriptor record. */
|
|
604
|
+
function optionalString(value, key) {
|
|
605
|
+
if (!Object.hasOwn(value, key)) return void 0;
|
|
606
|
+
const field = value[key];
|
|
607
|
+
if (typeof field !== "string") throw new Error(`persisted subagent descriptor ${key} must be a string`);
|
|
608
|
+
return field;
|
|
609
|
+
}
|
|
610
|
+
/** Read one optional string-array field from a persisted tool restriction. */
|
|
611
|
+
function optionalStringArray(value, key) {
|
|
612
|
+
if (!Object.hasOwn(value, key)) return void 0;
|
|
613
|
+
const field = value[key];
|
|
614
|
+
if (!Array.isArray(field)) throw new Error(`persisted subagent descriptor toolFilter.${key} must be an array of strings`);
|
|
615
|
+
const items = field;
|
|
616
|
+
if (items.some((item) => typeof item !== "string")) throw new Error(`persisted subagent descriptor toolFilter.${key} must be an array of strings`);
|
|
617
|
+
return items;
|
|
618
|
+
}
|
|
619
|
+
/** Validate and reconstruct a persisted tool restriction. */
|
|
620
|
+
function parseToolFilter(value) {
|
|
621
|
+
if (!isRecord(value)) throw new Error("persisted subagent descriptor toolFilter must be an object");
|
|
622
|
+
assertKnownKeys(value, TOOL_FILTER_KEYS, "toolFilter");
|
|
623
|
+
const allow = optionalStringArray(value, "allow");
|
|
624
|
+
const deny = optionalStringArray(value, "deny");
|
|
625
|
+
if (allow === void 0 && deny === void 0) throw new Error("persisted subagent descriptor toolFilter must declare allow and/or deny");
|
|
626
|
+
return {
|
|
627
|
+
...allow !== void 0 ? { allow } : {},
|
|
628
|
+
...deny !== void 0 ? { deny } : {}
|
|
629
|
+
};
|
|
630
|
+
}
|
|
631
|
+
/** Validate one persisted descriptor payload for the current runtime. */
|
|
632
|
+
function parseSubagentDescriptor(value) {
|
|
633
|
+
if (!isRecord(value)) throw new Error("persisted subagent descriptor payload must be an object");
|
|
634
|
+
const version = value["version"];
|
|
635
|
+
if (typeof version !== "number") throw new Error("persisted subagent descriptor version must be a number");
|
|
636
|
+
if (version !== 3) return void 0;
|
|
637
|
+
const mode = value["mode"];
|
|
638
|
+
if (mode !== "one-shot" && mode !== "continuable") throw new Error("persisted subagent descriptor mode must be \"one-shot\" or \"continuable\"");
|
|
639
|
+
assertKnownKeys(value, mode === "one-shot" ? ONE_SHOT_DESCRIPTOR_KEYS : CONTINUABLE_DESCRIPTOR_KEYS, "payload");
|
|
640
|
+
const provider = value["provider"];
|
|
641
|
+
if (typeof provider !== "string") throw new Error("persisted subagent descriptor provider must be a string");
|
|
642
|
+
if (mode === "one-shot") {
|
|
643
|
+
const label = optionalString(value, "label");
|
|
644
|
+
return {
|
|
645
|
+
version: 3,
|
|
646
|
+
mode,
|
|
647
|
+
provider,
|
|
648
|
+
...label !== void 0 ? { label } : {}
|
|
1381
649
|
};
|
|
1382
650
|
}
|
|
1383
651
|
const label = value["label"];
|
|
@@ -1399,13 +667,14 @@ function parseSubagentDescriptor(value) {
|
|
|
1399
667
|
...toolFilter !== void 0 ? { toolFilter } : {}
|
|
1400
668
|
};
|
|
1401
669
|
}
|
|
670
|
+
/**
|
|
671
|
+
* Validate and detach a continuable descriptor input.
|
|
672
|
+
* @param input - the caller-collected continuable composition fields.
|
|
673
|
+
* @returns the versioned, detached continuable descriptor payload.
|
|
674
|
+
* @throws when a field is not losslessly JSON-serializable.
|
|
675
|
+
*/
|
|
1402
676
|
function snapshotSubagentDescriptor(input) {
|
|
1403
|
-
const snapshot = snapshotJsonValue(
|
|
1404
|
-
version: 3,
|
|
1405
|
-
mode: input.mode,
|
|
1406
|
-
provider: input.provider,
|
|
1407
|
-
...input.label !== void 0 ? { label: input.label } : {}
|
|
1408
|
-
} : {
|
|
677
|
+
const snapshot = snapshotJsonValue({
|
|
1409
678
|
version: 3,
|
|
1410
679
|
mode: input.mode,
|
|
1411
680
|
provider: input.provider,
|
|
@@ -1451,12 +720,17 @@ const continuableCatalogSchema = z$1.object({
|
|
|
1451
720
|
mode: z$1.literal("continuable"),
|
|
1452
721
|
label: z$1.string()
|
|
1453
722
|
}).strict();
|
|
723
|
+
const externalCatalogSchema = oneShotCatalogSchema.extend({
|
|
724
|
+
version: z$1.literal(2),
|
|
725
|
+
mode: z$1.literal("external")
|
|
726
|
+
});
|
|
1454
727
|
const unknownCatalogSchema = oneShotCatalogSchema.extend({
|
|
1455
728
|
version: z$1.literal(1),
|
|
1456
729
|
mode: z$1.literal("unknown")
|
|
1457
730
|
});
|
|
1458
731
|
const eventDataSchema = z$1.union([
|
|
1459
732
|
oneShotCatalogSchema,
|
|
733
|
+
externalCatalogSchema,
|
|
1460
734
|
continuableCatalogSchema,
|
|
1461
735
|
unknownCatalogSchema
|
|
1462
736
|
]);
|
|
@@ -1469,6 +743,14 @@ const viewSchema = z$1.array(z$1.union([
|
|
|
1469
743
|
id: sessionIdSchema,
|
|
1470
744
|
createdAt: oneShotCatalogSchema.shape.childCreatedAt
|
|
1471
745
|
}),
|
|
746
|
+
externalCatalogSchema.omit({
|
|
747
|
+
version: true,
|
|
748
|
+
childId: true,
|
|
749
|
+
childCreatedAt: true
|
|
750
|
+
}).extend({
|
|
751
|
+
id: sessionIdSchema,
|
|
752
|
+
createdAt: externalCatalogSchema.shape.childCreatedAt
|
|
753
|
+
}),
|
|
1472
754
|
continuableCatalogSchema.omit({
|
|
1473
755
|
version: true,
|
|
1474
756
|
childId: true,
|
|
@@ -1491,102 +773,383 @@ const stateSchema = z$1.object({
|
|
|
1491
773
|
head: chunkedListSchema(eventDataSchema).optional()
|
|
1492
774
|
}).strict();
|
|
1493
775
|
/**
|
|
1494
|
-
* Materialize complete and unknown-mode child identities from parent catalog events.
|
|
1495
|
-
* @param state - parent catalog fold state.
|
|
1496
|
-
* @returns current direct-child rows in parent catalog event order.
|
|
776
|
+
* Materialize complete and unknown-mode child identities from parent catalog events.
|
|
777
|
+
* @param state - parent catalog fold state.
|
|
778
|
+
* @returns current direct-child rows in parent catalog event order.
|
|
779
|
+
*/
|
|
780
|
+
function subagentCatalogEntries(state) {
|
|
781
|
+
const entries = [];
|
|
782
|
+
for (const data of iterateChunkedList(state.head)) {
|
|
783
|
+
const { version: _version, childId, childCreatedAt, ...descriptor } = data;
|
|
784
|
+
entries.push({
|
|
785
|
+
id: childId,
|
|
786
|
+
createdAt: childCreatedAt,
|
|
787
|
+
...descriptor
|
|
788
|
+
});
|
|
789
|
+
}
|
|
790
|
+
return entries;
|
|
791
|
+
}
|
|
792
|
+
/** Parent-owned direct-child catalog projection; invalid own facts reject restoration. */
|
|
793
|
+
const subagentCatalogProjectionDefinition = {
|
|
794
|
+
key: "subagentCatalog",
|
|
795
|
+
stateSchema,
|
|
796
|
+
init: (_header, inheritedEventCount) => ({ inheritedEventCount }),
|
|
797
|
+
apply: (state, event) => {
|
|
798
|
+
if (event.type !== "subagent/catalog" || event.seq < state.inheritedEventCount) return state;
|
|
799
|
+
return {
|
|
800
|
+
...state,
|
|
801
|
+
head: appendChunkedList(state.head, eventDataSchema.parse(event.data))
|
|
802
|
+
};
|
|
803
|
+
},
|
|
804
|
+
stateVersion: 6,
|
|
805
|
+
wire: {
|
|
806
|
+
viewSchema,
|
|
807
|
+
view: subagentCatalogEntries
|
|
808
|
+
}
|
|
809
|
+
};
|
|
810
|
+
/**
|
|
811
|
+
* Append a complete direct-child discovery fact to its parent Session.
|
|
812
|
+
* @param parent - durable direct parent receiving the discovery fact.
|
|
813
|
+
* @param child - established child's immutable Session metadata.
|
|
814
|
+
* @param descriptor - mode-discriminated creation label frozen with the child.
|
|
815
|
+
*/
|
|
816
|
+
function establishCatalogChild(parent, child, descriptor) {
|
|
817
|
+
parent.append("subagent/catalog", {
|
|
818
|
+
version: 0,
|
|
819
|
+
childId: child.id,
|
|
820
|
+
childCreatedAt: child.createdAt,
|
|
821
|
+
mode: descriptor.mode,
|
|
822
|
+
label: descriptor.label
|
|
823
|
+
});
|
|
824
|
+
}
|
|
825
|
+
/** Catalog payload version emitted by external execution creation. */
|
|
826
|
+
const EXTERNAL_SUBAGENT_CATALOG_VERSION = 2;
|
|
827
|
+
/**
|
|
828
|
+
* Record an external execution without a local child Session.
|
|
829
|
+
* @param parent - durable direct parent receiving the discovery fact.
|
|
830
|
+
* @param childId - identity returned by the external provider.
|
|
831
|
+
* @param label - display label assigned by the caller.
|
|
832
|
+
*/
|
|
833
|
+
function establishExternalCatalogChild(parent, childId, label) {
|
|
834
|
+
parent.append("subagent/catalog", {
|
|
835
|
+
version: EXTERNAL_SUBAGENT_CATALOG_VERSION,
|
|
836
|
+
childId,
|
|
837
|
+
childCreatedAt: Date.now(),
|
|
838
|
+
mode: "external",
|
|
839
|
+
label
|
|
840
|
+
});
|
|
841
|
+
}
|
|
842
|
+
//#endregion
|
|
843
|
+
//#region lib/types/internal.js
|
|
844
|
+
/**
|
|
845
|
+
* Continuation integration markers and host adapters outside the public
|
|
846
|
+
* Service Definition and model-facing Agent messaging contract.
|
|
847
|
+
* @module @deepseek-ai/dsh-subagent/internal
|
|
848
|
+
*/
|
|
849
|
+
/** Process-stable identity carried only by the standard adjacent-Agent messaging tool. */
|
|
850
|
+
const adjacentAgentSendMessageTool = Symbol.for("dsh.subagent.adjacentAgentSendMessageTool");
|
|
851
|
+
/**
|
|
852
|
+
* Test whether one visible definition is the standard adjacent-Agent messaging tool.
|
|
853
|
+
* @param definition - the scope-resolved `send_message` candidate.
|
|
854
|
+
* @returns whether the definition carries the internal standard-tool identity.
|
|
855
|
+
*/
|
|
856
|
+
function isAdjacentAgentSendMessageTool(definition) {
|
|
857
|
+
return definition !== void 0 && definition[adjacentAgentSendMessageTool] === true;
|
|
858
|
+
}
|
|
859
|
+
/**
|
|
860
|
+
* Process-stable symbol-keyed host delivery shared by the bundled runtime
|
|
861
|
+
* entry and this unbundled internal subpath.
|
|
862
|
+
* @internal
|
|
863
|
+
*/
|
|
864
|
+
const deliverSubagentPrompt = Symbol.for("dsh.subagent.deliverPrompt");
|
|
865
|
+
//#endregion
|
|
866
|
+
//#region lib/types/structured.js
|
|
867
|
+
/**
|
|
868
|
+
* Child-scoped structured-output tool, prompt instruction, terminal guard, and authoritative
|
|
869
|
+
* result capture for in-process subagents. Each child registers its real schema on its own
|
|
870
|
+
* scope, so concurrent runs do not interact and disposal leaves no global residue. The prompt
|
|
871
|
+
* contribution is ordinary reconstructed request state.
|
|
872
|
+
*
|
|
873
|
+
* Capture commits only after the authoritative `tools/result` succeeds; PTC mode capture also
|
|
874
|
+
* waits for the enclosing `run_code` result. The terminal result marker and monotonic tool
|
|
875
|
+
* guard prevent later calls from reopening a completed structured run.
|
|
876
|
+
* @module @deepseek-ai/dsh-subagent/structured
|
|
877
|
+
*/
|
|
878
|
+
/** The model-facing tool name a structured child must call to finish. */
|
|
879
|
+
const STRUCTURED_OUTPUT_TOOL = "structured_output";
|
|
880
|
+
/**
|
|
881
|
+
* The instruction registered as the child's trailing scoped prompt section:
|
|
882
|
+
* the demand travels with the
|
|
883
|
+
* tool, as ordinary prompt state of exactly one agent.
|
|
884
|
+
*/
|
|
885
|
+
const STRUCTURED_OUTPUT_INSTRUCTION = `When you have your final answer, you MUST report it by calling the \`${STRUCTURED_OUTPUT_TOOL}\` tool with arguments matching its parameter schema exactly. Do not finish with a plain text answer: only the tool call counts as your result.`;
|
|
886
|
+
/**
|
|
887
|
+
* Attach the scoped capture tool, instruction, and enforcement to a child during
|
|
888
|
+
* its creation window. Child disposal removes every registration.
|
|
889
|
+
* @param childCtx - the child agent's scope context (`setup`'s argument).
|
|
890
|
+
* @param schema - the trusted, already-asserted schema subset to enforce (see
|
|
891
|
+
* `assertObjectJsonSchema` in dsh-tools).
|
|
892
|
+
* @param canComplete - whether accepted input and owned child work have finished.
|
|
893
|
+
* @returns the attachment handle (read `captured()` after the child settles).
|
|
894
|
+
*/
|
|
895
|
+
function attachStructuredRuntime(childCtx, schema, canComplete) {
|
|
896
|
+
/**
|
|
897
|
+
* Validated values staged by the capture tool body, awaiting THEIR OWN
|
|
898
|
+
* authoritative `tools/result` notification. The execution object's identity
|
|
899
|
+
* uniquely identifies a trip through the pipeline: adapter call ids may
|
|
900
|
+
* repeat across steps, but another execution can never reach this Map
|
|
901
|
+
* entry. This is distinct from the opaque `ToolExecutionToken` used to
|
|
902
|
+
* correlate nested transports. The final notification always deletes its own
|
|
903
|
+
* stage, whether the result succeeded or failed.
|
|
904
|
+
*/
|
|
905
|
+
const staged = /* @__PURE__ */ new Map();
|
|
906
|
+
/** Successful nested capture waiting for its enclosing transport to commit. */
|
|
907
|
+
let pending;
|
|
908
|
+
let captured;
|
|
909
|
+
const schemaEntry = {
|
|
910
|
+
name: STRUCTURED_OUTPUT_TOOL,
|
|
911
|
+
description: "Report your final structured result. Call this exactly once, when your answer is complete; the arguments must match this tool's parameter schema exactly.",
|
|
912
|
+
parameters: { ...schema }
|
|
913
|
+
};
|
|
914
|
+
childCtx.tools.register({
|
|
915
|
+
...schemaEntry,
|
|
916
|
+
output: {
|
|
917
|
+
schema: {
|
|
918
|
+
type: "object",
|
|
919
|
+
properties: { recorded: {
|
|
920
|
+
type: "boolean",
|
|
921
|
+
const: true
|
|
922
|
+
} },
|
|
923
|
+
required: ["recorded"],
|
|
924
|
+
additionalProperties: false
|
|
925
|
+
},
|
|
926
|
+
render: () => [{
|
|
927
|
+
type: "text",
|
|
928
|
+
text: "Structured output recorded."
|
|
929
|
+
}]
|
|
930
|
+
},
|
|
931
|
+
execute(args, exec) {
|
|
932
|
+
const violations = validateJsonSchemaValue(schema, args);
|
|
933
|
+
if (violations.length > 0) throw new ToolArgsError(violations);
|
|
934
|
+
if (!canComplete()) throw new Error("Wait for all delegated child tasks to finish and process pending messages before submitting structured output.");
|
|
935
|
+
staged.set(exec, { value: args });
|
|
936
|
+
exec.concludeTurn();
|
|
937
|
+
return Promise.resolve({ recorded: true });
|
|
938
|
+
}
|
|
939
|
+
});
|
|
940
|
+
childCtx.systemPrompt.section({
|
|
941
|
+
name: `tool:${STRUCTURED_OUTPUT_TOOL}`,
|
|
942
|
+
order: childCtx.systemPrompt.getSectionOrder("STRUCTURED_OUTPUT"),
|
|
943
|
+
text: STRUCTURED_OUTPUT_INSTRUCTION
|
|
944
|
+
});
|
|
945
|
+
childCtx.tools.guard((exec) => captured === void 0 && pending === void 0 ? void 0 : `structured output already recorded: the run is complete, so \`${exec.name}\` is not executed`);
|
|
946
|
+
childCtx.on("tools/result", function(exec, result) {
|
|
947
|
+
if (exec.name === "structured_output") {
|
|
948
|
+
const entry = staged.get(exec);
|
|
949
|
+
if (entry === void 0) return;
|
|
950
|
+
staged.delete(exec);
|
|
951
|
+
if (result.isError) return;
|
|
952
|
+
if (exec.parent === void 0) {
|
|
953
|
+
/* v8 ignore else -- sequential agent-loop dispatch lets the guard block every later supported call */
|
|
954
|
+
if (captured === void 0) captured = { value: entry.value };
|
|
955
|
+
} else if (captured === void 0 && pending === void 0) pending = {
|
|
956
|
+
parent: exec.parent,
|
|
957
|
+
value: entry.value
|
|
958
|
+
};
|
|
959
|
+
return;
|
|
960
|
+
}
|
|
961
|
+
if (pending?.parent !== exec.token) return;
|
|
962
|
+
const entry = pending;
|
|
963
|
+
pending = void 0;
|
|
964
|
+
if (result.isError) return;
|
|
965
|
+
/* v8 ignore else -- PTC mode serializes outer executions, so the guard blocks every later supported call */
|
|
966
|
+
if (captured === void 0) captured = { value: entry.value };
|
|
967
|
+
});
|
|
968
|
+
return {
|
|
969
|
+
captured: () => captured,
|
|
970
|
+
acceptsInput: () => staged.size === 0 && pending === void 0 && captured === void 0
|
|
971
|
+
};
|
|
972
|
+
}
|
|
973
|
+
//#endregion
|
|
974
|
+
//#region lib/types/assistant-output.js
|
|
975
|
+
/**
|
|
976
|
+
* Canonical selection of a child's final assistant output. Backend run results
|
|
977
|
+
* and `subagent/end.lastAssistantMessage` apply the same rule: select the last
|
|
978
|
+
* non-empty assistant message. An empty-content message records usage only
|
|
979
|
+
* when the loop appends it after a max-tokens step with no executable blocks,
|
|
980
|
+
* so it does not replace earlier output. If no non-empty message exists,
|
|
981
|
+
* select the accumulated assistant text. Selection is independent of the
|
|
982
|
+
* run's stop reason.
|
|
983
|
+
*
|
|
984
|
+
* @module @deepseek-ai/dsh-subagent/assistant-output
|
|
985
|
+
*/
|
|
986
|
+
/**
|
|
987
|
+
* Incremental fold of the selection rule, for backends that observe a child's
|
|
988
|
+
* output as it streams: session-event backends {@link push} each event, and
|
|
989
|
+
* transports without session events (ACP content chunks) {@link pushText} raw
|
|
990
|
+
* text into the same streamed fallback.
|
|
991
|
+
*/
|
|
992
|
+
var AssistantOutputFold = class {
|
|
993
|
+
message;
|
|
994
|
+
partial = [];
|
|
995
|
+
/**
|
|
996
|
+
* Fold one session event: a non-empty assistant message becomes the
|
|
997
|
+
* candidate final answer, while its embedded stream and any log-only attempt
|
|
998
|
+
* extend the streamed fallback; every other event contributes nothing.
|
|
999
|
+
* @param event - the next observed session event.
|
|
1000
|
+
*/
|
|
1001
|
+
push(event) {
|
|
1002
|
+
if (event.type === "assistant/message") {
|
|
1003
|
+
const content = event.data.message.content;
|
|
1004
|
+
if (content.length > 0) this.message = content;
|
|
1005
|
+
}
|
|
1006
|
+
if (event.type === "assistant/message" || event.type === "assistant/attempt") this.pushText(joinAssistantStreamText(event.data.stream));
|
|
1007
|
+
}
|
|
1008
|
+
/**
|
|
1009
|
+
* Extend the streamed fallback with text observed outside session events.
|
|
1010
|
+
* @param text - the next streamed text piece (an empty piece is a no-op).
|
|
1011
|
+
*/
|
|
1012
|
+
pushText(text) {
|
|
1013
|
+
if (text.length > 0) this.partial.push(text);
|
|
1014
|
+
}
|
|
1015
|
+
/**
|
|
1016
|
+
* Select the final output folded so far.
|
|
1017
|
+
* @returns the last non-empty assistant message, else the accumulated
|
|
1018
|
+
* streamed text, or `undefined` when the child produced neither.
|
|
1019
|
+
*/
|
|
1020
|
+
collect() {
|
|
1021
|
+
if (this.message !== void 0) return this.message;
|
|
1022
|
+
const text = this.partial.join("");
|
|
1023
|
+
return text.length > 0 ? [{
|
|
1024
|
+
type: "text",
|
|
1025
|
+
text
|
|
1026
|
+
}] : void 0;
|
|
1027
|
+
}
|
|
1028
|
+
};
|
|
1029
|
+
/**
|
|
1030
|
+
* Apply the selection rule to one complete child-owned event suffix.
|
|
1031
|
+
* @param events - the child-owned events (after any seed or epoch boundary).
|
|
1032
|
+
* @returns the selected output, or `undefined` when the child produced none.
|
|
1497
1033
|
*/
|
|
1498
|
-
function
|
|
1499
|
-
const
|
|
1500
|
-
for (const
|
|
1501
|
-
|
|
1502
|
-
createdAt: data.childCreatedAt,
|
|
1503
|
-
mode: data.mode,
|
|
1504
|
-
...data.label === void 0 ? {} : { label: data.label }
|
|
1505
|
-
} : {
|
|
1506
|
-
id: data.childId,
|
|
1507
|
-
createdAt: data.childCreatedAt,
|
|
1508
|
-
mode: data.mode,
|
|
1509
|
-
label: data.label
|
|
1510
|
-
});
|
|
1511
|
-
return entries;
|
|
1034
|
+
function finalAssistantOutput(events) {
|
|
1035
|
+
const fold = new AssistantOutputFold();
|
|
1036
|
+
for (const event of events) fold.push(event);
|
|
1037
|
+
return fold.collect();
|
|
1512
1038
|
}
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1039
|
+
//#endregion
|
|
1040
|
+
//#region lib/types/activation.js
|
|
1041
|
+
/** Execution-period records, capacity slots, child serialization, and local output capture.
|
|
1042
|
+
* @module @deepseek-ai/dsh-subagent/activation
|
|
1043
|
+
*/
|
|
1044
|
+
/** Process-local slots shared through uninterrupted continuable parent links. */
|
|
1045
|
+
var ActivationPool = class {
|
|
1046
|
+
slots = /* @__PURE__ */ new Set();
|
|
1047
|
+
/**
|
|
1048
|
+
* Reserve before reconstruction; release also tolerates unpublished rollback.
|
|
1049
|
+
* @param capacity - the maximum simultaneous activations in this pool.
|
|
1050
|
+
* @returns an idempotent slot release.
|
|
1051
|
+
*/
|
|
1052
|
+
reserve(capacity) {
|
|
1053
|
+
if (this.slots.size >= capacity) throw new SubagentError(`subagent limit reached (active child limit: ${capacity}); wait for an existing child to finish or complete this work with the current agents`, "ACTIVATION_LIMIT_REACHED");
|
|
1054
|
+
const slot = Symbol();
|
|
1055
|
+
this.slots.add(slot);
|
|
1056
|
+
return () => {
|
|
1057
|
+
this.slots.delete(slot);
|
|
1523
1058
|
};
|
|
1524
|
-
},
|
|
1525
|
-
stateVersion: 3,
|
|
1526
|
-
wire: {
|
|
1527
|
-
viewSchema,
|
|
1528
|
-
view: subagentCatalogEntries
|
|
1529
1059
|
}
|
|
1530
1060
|
};
|
|
1531
1061
|
/**
|
|
1532
|
-
*
|
|
1533
|
-
* @param
|
|
1534
|
-
* @
|
|
1535
|
-
* @param descriptor - mode-discriminated creation label frozen with the child.
|
|
1062
|
+
* Read the live Agent retained by local execution.
|
|
1063
|
+
* @param activation - the managed local or external execution.
|
|
1064
|
+
* @returns the local Agent, absent for an external run.
|
|
1536
1065
|
*/
|
|
1537
|
-
function
|
|
1538
|
-
|
|
1539
|
-
version: 0,
|
|
1540
|
-
childId: child.id,
|
|
1541
|
-
childCreatedAt: child.createdAt,
|
|
1542
|
-
mode: descriptor.mode,
|
|
1543
|
-
...descriptor.label === void 0 ? {} : { label: descriptor.label }
|
|
1544
|
-
} : {
|
|
1545
|
-
version: 0,
|
|
1546
|
-
childId: child.id,
|
|
1547
|
-
childCreatedAt: child.createdAt,
|
|
1548
|
-
mode: descriptor.mode,
|
|
1549
|
-
label: descriptor.label
|
|
1550
|
-
});
|
|
1066
|
+
function activationAgent(activation) {
|
|
1067
|
+
return activation.kind === "local" ? activation.handle.agent : void 0;
|
|
1551
1068
|
}
|
|
1552
|
-
//#endregion
|
|
1553
|
-
//#region lib/types/internal.js
|
|
1554
1069
|
/**
|
|
1555
|
-
*
|
|
1556
|
-
*
|
|
1557
|
-
* @
|
|
1070
|
+
* Require local residency before accepting further input.
|
|
1071
|
+
* @param activation - execution targeted by message delivery.
|
|
1072
|
+
* @returns the local activation.
|
|
1073
|
+
* @throws when the external backend cannot accept another message.
|
|
1558
1074
|
*/
|
|
1559
|
-
|
|
1560
|
-
|
|
1075
|
+
function requireLocalActivation(activation) {
|
|
1076
|
+
switch (activation.kind) {
|
|
1077
|
+
case "local": return activation;
|
|
1078
|
+
case "external": throw new SubagentError(`subagent "${activation.childId}" does not accept follow-up input`, "NOT_CONTINUABLE");
|
|
1079
|
+
/* v8 ignore next 2 -- Both execution kinds are handled above. */
|
|
1080
|
+
default: return assertNever(activation);
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1561
1083
|
/**
|
|
1562
|
-
*
|
|
1563
|
-
* @param
|
|
1564
|
-
* @
|
|
1084
|
+
* Retain local failures whose turn ending could not be committed.
|
|
1085
|
+
* @param activation - exact local Agent and epoch receiving the scoped observers.
|
|
1086
|
+
* @param wake - notify its settlement watcher when failure changes readiness.
|
|
1565
1087
|
*/
|
|
1566
|
-
function
|
|
1567
|
-
|
|
1088
|
+
function observeLocalFailure(activation, wake) {
|
|
1089
|
+
const child = activation.handle.agent;
|
|
1090
|
+
child.ctx.on("agent/error", ({ agent }) => {
|
|
1091
|
+
if (agent !== child) return;
|
|
1092
|
+
activation.failureAt = child.session.seq;
|
|
1093
|
+
wake();
|
|
1094
|
+
});
|
|
1095
|
+
child.ctx.on("session/event", (session, event) => {
|
|
1096
|
+
if (session === child.session && event.type === "turn/end" && activation.failureAt !== void 0 && event.seq >= activation.failureAt) activation.failureAt = void 0;
|
|
1097
|
+
});
|
|
1568
1098
|
}
|
|
1569
1099
|
/**
|
|
1570
|
-
*
|
|
1571
|
-
*
|
|
1572
|
-
* @
|
|
1573
|
-
*/
|
|
1574
|
-
|
|
1100
|
+
* Capture local output and retain failures without a later committed turn ending.
|
|
1101
|
+
* @param activation - the settled local execution and any structured result.
|
|
1102
|
+
* @returns final output, stop reason, and committed structured value.
|
|
1103
|
+
*/
|
|
1104
|
+
function captureLocalResult(activation) {
|
|
1105
|
+
const events = activation.handle.agent.session.snapshotEvents(activation.outputStart);
|
|
1106
|
+
const output = finalAssistantOutput(events) ?? [];
|
|
1107
|
+
const stopReason = activation.failureAt === void 0 ? epochStopReason(events) : "error";
|
|
1108
|
+
const structured = activation.structured;
|
|
1109
|
+
if (structured !== void 0) {
|
|
1110
|
+
const captured = structured.captured();
|
|
1111
|
+
if (captured !== void 0) return {
|
|
1112
|
+
output,
|
|
1113
|
+
stopReason,
|
|
1114
|
+
structured: captured.value
|
|
1115
|
+
};
|
|
1116
|
+
if (stopReason === "completed") return {
|
|
1117
|
+
output,
|
|
1118
|
+
stopReason: "error"
|
|
1119
|
+
};
|
|
1120
|
+
}
|
|
1121
|
+
return {
|
|
1122
|
+
output,
|
|
1123
|
+
stopReason
|
|
1124
|
+
};
|
|
1125
|
+
}
|
|
1126
|
+
/** Serialize each durable child's delivery, release, and disposal. */
|
|
1127
|
+
var ChildLock = class {
|
|
1128
|
+
tails = /* @__PURE__ */ new Map();
|
|
1129
|
+
/**
|
|
1130
|
+
* Run `operation` after every previously queued operation for `childId`.
|
|
1131
|
+
* @param childId - the durable child whose operations are linearized.
|
|
1132
|
+
* @param operation - the critical section to run in order.
|
|
1133
|
+
* @returns the operation's own settlement.
|
|
1134
|
+
*/
|
|
1135
|
+
run(childId, operation) {
|
|
1136
|
+
const result = (this.tails.get(childId) ?? Promise.resolve()).then(operation, operation);
|
|
1137
|
+
const tail = result.then(() => void 0, () => void 0);
|
|
1138
|
+
this.tails.set(childId, tail);
|
|
1139
|
+
tail.then(() => {
|
|
1140
|
+
if (this.tails.get(childId) === tail) this.tails.delete(childId);
|
|
1141
|
+
});
|
|
1142
|
+
return result;
|
|
1143
|
+
}
|
|
1144
|
+
};
|
|
1575
1145
|
//#endregion
|
|
1576
|
-
//#region lib/types/
|
|
1146
|
+
//#region lib/types/manager.js
|
|
1577
1147
|
/**
|
|
1578
|
-
*
|
|
1579
|
-
*
|
|
1580
|
-
*
|
|
1581
|
-
* process-local Activation graph and its settlement and disposal lifecycle.
|
|
1148
|
+
* Owns subagent startup, message admission, cold resume, and execution lifetimes.
|
|
1149
|
+
* One child lock orders input acceptance and closure; one activation map owns
|
|
1150
|
+
* capacity, parent relationships, and resource release across all providers.
|
|
1582
1151
|
*
|
|
1583
|
-
*
|
|
1584
|
-
* Activation. The Agent inbox is the only turn queue, so this manager owns
|
|
1585
|
-
* durable orchestration while the Agent loop owns all turn ordering and
|
|
1586
|
-
* execution. No continuable path creates a Task or an intermediate
|
|
1587
|
-
* result-bearing wrapper.
|
|
1588
|
-
*
|
|
1589
|
-
* @module @deepseek-ai/dsh-subagent
|
|
1152
|
+
* @module @deepseek-ai/dsh-subagent/manager
|
|
1590
1153
|
*/
|
|
1591
1154
|
var __addDisposableResource$2 = function(env, value, async) {
|
|
1592
1155
|
if (value !== null && value !== void 0) {
|
|
@@ -1646,20 +1209,183 @@ var __disposeResources$2 = (function(SuppressedError) {
|
|
|
1646
1209
|
var e = new Error(message);
|
|
1647
1210
|
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
1648
1211
|
});
|
|
1649
|
-
/**
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
var SubagentContinuationManager = class {
|
|
1212
|
+
/** Render only the safe outer message from execution or teardown failures. */
|
|
1213
|
+
function failureMessage(error) {
|
|
1214
|
+
return error instanceof Error ? error.message : "unknown teardown failure";
|
|
1215
|
+
}
|
|
1216
|
+
/** Own subagent requests and the resources of every live activation. */
|
|
1217
|
+
var SubagentManager = class {
|
|
1656
1218
|
ctx;
|
|
1657
1219
|
host;
|
|
1658
|
-
|
|
1220
|
+
maxActiveSubagents;
|
|
1221
|
+
/** Child session id → its live Activation. Process-local, never durable. */
|
|
1222
|
+
resident = /* @__PURE__ */ new Map();
|
|
1223
|
+
/** Root identities retain their pool across child settlement without retaining dead roots. */
|
|
1224
|
+
rootPools = /* @__PURE__ */ new WeakMap();
|
|
1225
|
+
/** Materializations admitted before drain, tracked through publication or rollback. */
|
|
1226
|
+
materializations = /* @__PURE__ */ new Set();
|
|
1227
|
+
/** Per-child serializer shared by delivery, release, and disposal. */
|
|
1228
|
+
locks = new ChildLock();
|
|
1229
|
+
/** Structural Cordis owner of every Activation handle. */
|
|
1230
|
+
ownerCtx;
|
|
1231
|
+
/**
|
|
1232
|
+
* Exact roots whose host teardown has begun, with the live lineage members
|
|
1233
|
+
* observed under each root. Entries remain until that exact root leaves the
|
|
1234
|
+
* Agent registry, closing admission throughout its host's teardown without
|
|
1235
|
+
* poisoning a later same-id replacement.
|
|
1236
|
+
*/
|
|
1237
|
+
closingScopes = /* @__PURE__ */ new Map();
|
|
1238
|
+
draining = false;
|
|
1239
|
+
/**
|
|
1240
|
+
* Build one manager inside the service's Agent-injected context.
|
|
1241
|
+
* @param ctx - context providing Agents, Sessions, and teardown ownership.
|
|
1242
|
+
* @param host - provider dispatch and lifecycle observation owned by the service.
|
|
1243
|
+
* @param maxActiveSubagents - the current configured child capacity.
|
|
1244
|
+
*/
|
|
1659
1245
|
constructor(ctx, host, maxActiveSubagents) {
|
|
1660
1246
|
this.ctx = ctx;
|
|
1661
1247
|
this.host = host;
|
|
1662
|
-
this.
|
|
1248
|
+
this.maxActiveSubagents = maxActiveSubagents;
|
|
1249
|
+
const scope = ctx.plugin(function activationOwner() {});
|
|
1250
|
+
this.ownerCtx = scope.ctx;
|
|
1251
|
+
ctx.on("agent/disposed", ({ agent }) => {
|
|
1252
|
+
this.closingScopes.delete(agent);
|
|
1253
|
+
});
|
|
1254
|
+
ctx.effect(function* () {
|
|
1255
|
+
yield scope.dispose;
|
|
1256
|
+
yield () => this.drain();
|
|
1257
|
+
}.bind(this), "subagents.manager()");
|
|
1258
|
+
}
|
|
1259
|
+
/**
|
|
1260
|
+
* Interrupt one live continuable child's current turn under the supplied authority.
|
|
1261
|
+
* @param targetSessionId - the durable child session id to interrupt.
|
|
1262
|
+
* @param authority - the human parent address or exact live ancestor Agent.
|
|
1263
|
+
*/
|
|
1264
|
+
interrupt(targetSessionId, authority) {
|
|
1265
|
+
if (authority.kind === "ancestor") {
|
|
1266
|
+
const caller = authority.agent;
|
|
1267
|
+
if (this.ctx.agents.get(caller.id) !== caller) throw new SubagentError(`interrupting "${targetSessionId}" requires the exact live ancestor agent`, "UNAUTHORIZED");
|
|
1268
|
+
if (caller.id === targetSessionId) throw new SubagentError(`agent "${caller.id}" cannot interrupt itself`, "UNAUTHORIZED");
|
|
1269
|
+
}
|
|
1270
|
+
const activation = this.resident.get(targetSessionId);
|
|
1271
|
+
if (activation === void 0) return;
|
|
1272
|
+
if (authority.kind === "user") {
|
|
1273
|
+
if (activation.parent.id !== authority.parentSessionId) throw new SubagentError(`subagent "${targetSessionId}" belongs to another parent session`, "UNAUTHORIZED");
|
|
1274
|
+
} else if (!activation.ancestry.has(authority.agent)) throw new SubagentError(`subagent "${targetSessionId}" is not a live descendant of agent "${authority.agent.id}"`, "UNAUTHORIZED");
|
|
1275
|
+
if (activation.closing !== void 0) return;
|
|
1276
|
+
const kind = authority.kind === "user" ? "user" : "parent";
|
|
1277
|
+
if (activation.kind === "local") activation.handle.agent.cancel({ kind }, { keepInbox: true });
|
|
1278
|
+
else {
|
|
1279
|
+
activation.controller.abort({ kind });
|
|
1280
|
+
this.dispose(activation).catch(() => void 0);
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
/**
|
|
1284
|
+
* Close admission, await every already-admitted materialization through
|
|
1285
|
+
* publication or rollback, then dispose the stable live Activation graph
|
|
1286
|
+
* child-first.
|
|
1287
|
+
*/
|
|
1288
|
+
async drain() {
|
|
1289
|
+
this.draining = true;
|
|
1290
|
+
for (const pending of this.materializations) pending.controller.abort();
|
|
1291
|
+
await Promise.all([...this.materializations].map((materialization) => materialization.settled));
|
|
1292
|
+
const owned = /* @__PURE__ */ new Set();
|
|
1293
|
+
for (const activation of this.resident.values()) for (const child of activation.ownedChildren) owned.add(child);
|
|
1294
|
+
const roots = [...this.resident.values()].filter((activation) => !owned.has(activation.childId));
|
|
1295
|
+
await this.disposeRoots(roots, "activation(s)");
|
|
1296
|
+
}
|
|
1297
|
+
/**
|
|
1298
|
+
* Join progressing child work, leaving idle parked inboxes resident.
|
|
1299
|
+
* @param parent - exact live parent whose descendants are observed.
|
|
1300
|
+
* @returns whether work was joined and the host must recheck parent completion.
|
|
1301
|
+
*/
|
|
1302
|
+
async waitForChildren(parent) {
|
|
1303
|
+
let observed = false;
|
|
1304
|
+
while (true) {
|
|
1305
|
+
const pending = [...this.materializations].filter((item) => item.lineage.includes(parent));
|
|
1306
|
+
const children = [...this.resident.values()].filter((item) => activationAgent(item) !== parent && item.ancestry.has(parent));
|
|
1307
|
+
observed ||= children.some((item) => item.kind === "local" && item.handle.agent.status !== "idle");
|
|
1308
|
+
await Promise.all(children.flatMap((item) => item.kind === "local" ? [item.handle.agent.whenIdle()] : []));
|
|
1309
|
+
const waits = pending.map((item) => item.settled);
|
|
1310
|
+
for (const item of children) {
|
|
1311
|
+
if (item.kind === "external" || item.closing !== void 0) {
|
|
1312
|
+
waits.push(item.result.promise.catch(() => void 0));
|
|
1313
|
+
continue;
|
|
1314
|
+
}
|
|
1315
|
+
if (item.handle.agent.status !== "idle") {
|
|
1316
|
+
waits.push(item.handle.agent.whenIdle());
|
|
1317
|
+
continue;
|
|
1318
|
+
}
|
|
1319
|
+
if (item.ownedChildren.size > 0) {
|
|
1320
|
+
if ([...item.ownedChildren].some((id) => !this.resident.has(id))) waits.push(item.poke.promise);
|
|
1321
|
+
continue;
|
|
1322
|
+
}
|
|
1323
|
+
const inbox = item.handle.agent.inbox;
|
|
1324
|
+
if (item.failureAt === void 0 && (inbox.nextTurn.length > 0 || inbox.nextStep.length > 0)) continue;
|
|
1325
|
+
waits.push(Promise.race([item.result.promise.catch(() => void 0), item.poke.promise]));
|
|
1326
|
+
}
|
|
1327
|
+
if (waits.length > 0) {
|
|
1328
|
+
observed = true;
|
|
1329
|
+
await Promise.all(waits);
|
|
1330
|
+
continue;
|
|
1331
|
+
}
|
|
1332
|
+
if ([...this.materializations].some((item) => item.lineage.includes(parent) && !pending.includes(item)) || [...this.resident.values()].some((item) => activationAgent(item) !== parent && item.ancestry.has(parent) && !children.includes(item))) continue;
|
|
1333
|
+
return observed;
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
/**
|
|
1337
|
+
* Stop the managed descendants of exact live host-owned parents.
|
|
1338
|
+
* @param parents - exact live roots whose managed descendants must stop.
|
|
1339
|
+
*/
|
|
1340
|
+
async drainDescendants(parents) {
|
|
1341
|
+
const roots = new Set(parents.filter((parent) => this.ctx.agents.get(parent.id) === parent));
|
|
1342
|
+
if (roots.size === 0) return;
|
|
1343
|
+
for (const root of roots) this.closingMembers(root).add(root);
|
|
1344
|
+
const targets = [];
|
|
1345
|
+
for (const activation of this.resident.values()) {
|
|
1346
|
+
const child = activationAgent(activation);
|
|
1347
|
+
const lineage = this.liveLineage(child ?? activation.parent);
|
|
1348
|
+
const owners = [...roots].filter((root) => child !== root && activation.ancestry.has(root));
|
|
1349
|
+
if (owners.length === 0) continue;
|
|
1350
|
+
targets.push(activation);
|
|
1351
|
+
for (const owner of owners) {
|
|
1352
|
+
const members = this.closingMembers(owner);
|
|
1353
|
+
if (child !== void 0) members.add(child);
|
|
1354
|
+
for (const agent of lineage) members.add(agent);
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
const materializations = [...this.materializations].filter((materialization) => {
|
|
1358
|
+
const owners = [...roots].filter((root) => materialization.lineage.includes(root));
|
|
1359
|
+
for (const owner of owners) {
|
|
1360
|
+
const members = this.closingMembers(owner);
|
|
1361
|
+
for (const agent of materialization.lineage) members.add(agent);
|
|
1362
|
+
}
|
|
1363
|
+
if (owners.length > 0) materialization.controller.abort();
|
|
1364
|
+
return owners.length > 0;
|
|
1365
|
+
});
|
|
1366
|
+
const ownedTargets = /* @__PURE__ */ new Set();
|
|
1367
|
+
for (const activation of targets) for (const child of activation.ownedChildren) ownedTargets.add(child);
|
|
1368
|
+
const targetRoots = targets.filter((activation) => !ownedTargets.has(activation.childId));
|
|
1369
|
+
for (const activation of targets) this.dispose(activation).catch(() => void 0);
|
|
1370
|
+
await Promise.all(materializations.map((materialization) => materialization.settled));
|
|
1371
|
+
await this.disposeRoots(targetRoots, "scoped activation(s)");
|
|
1372
|
+
}
|
|
1373
|
+
/**
|
|
1374
|
+
* Release selected resident direct children of one exact live parent.
|
|
1375
|
+
* @param parent - exact live direct parent authorizing the selected release.
|
|
1376
|
+
* @param childIds - durable direct-child ids to release when resident.
|
|
1377
|
+
*/
|
|
1378
|
+
async drainChildren(parent, childIds) {
|
|
1379
|
+
if (this.ctx.agents.get(parent.id) !== parent) throw new SubagentError("selected child teardown requires the exact live parent agent", "UNAUTHORIZED");
|
|
1380
|
+
const targets = [];
|
|
1381
|
+
for (const childId of new Set(childIds)) {
|
|
1382
|
+
const activation = this.resident.get(childId);
|
|
1383
|
+
if (activation === void 0) continue;
|
|
1384
|
+
if (activation.parent.id !== parent.id || !activation.ancestry.has(parent)) throw new SubagentError(`subagent "${childId}" is not a direct child of agent "${parent.id}"`, "UNAUTHORIZED");
|
|
1385
|
+
targets.push(activation);
|
|
1386
|
+
}
|
|
1387
|
+
for (const activation of targets) this.dispose(activation).catch(() => void 0);
|
|
1388
|
+
await this.disposeRoots(targets, "selected activation(s)");
|
|
1663
1389
|
}
|
|
1664
1390
|
/**
|
|
1665
1391
|
* Start one continuable background child and resolve at initial inbox acceptance.
|
|
@@ -1668,14 +1394,14 @@ var SubagentContinuationManager = class {
|
|
|
1668
1394
|
* @param spec - provider, delegation request, and caller cancellation.
|
|
1669
1395
|
* @returns the durable child id and accepted initial prompt message id.
|
|
1670
1396
|
*/
|
|
1671
|
-
async
|
|
1397
|
+
async startLocal(spec) {
|
|
1672
1398
|
const request = spec.request;
|
|
1673
1399
|
const parent = request.parent;
|
|
1674
|
-
this.
|
|
1675
|
-
const persistence = this.requirePersistence();
|
|
1400
|
+
this.assertAdmitting(parent);
|
|
1401
|
+
const persistence = spec.delivery === "caller" ? this.ctx.get("sessionPersistence") : this.requirePersistence();
|
|
1676
1402
|
assertSubagentMaxDepth(request.maxDepth);
|
|
1677
1403
|
const childId = spec.childId ?? brandString(randomUUID());
|
|
1678
|
-
this.
|
|
1404
|
+
this.assertChildIdAvailable(childId);
|
|
1679
1405
|
const childDepth = resolveChildDepth(parent, request.maxDepth);
|
|
1680
1406
|
const agentOptions = resolveChildAgentOptions(parent, request.agentOptions, childDepth);
|
|
1681
1407
|
const agentProvider = agentOptions.provider;
|
|
@@ -1692,123 +1418,677 @@ var SubagentContinuationManager = class {
|
|
|
1692
1418
|
...request.toolFilter !== void 0 ? { toolFilter: request.toolFilter } : {}
|
|
1693
1419
|
});
|
|
1694
1420
|
const delegatedPolicies = captureDelegatedPolicyOverrides(parent);
|
|
1695
|
-
const releaseHold = this.
|
|
1421
|
+
const releaseHold = this.holdOwnership(parent, childId);
|
|
1696
1422
|
try {
|
|
1423
|
+
const cwd = await this.resolveDirectory(request, spec.signal);
|
|
1697
1424
|
const prepared = await this.host.prepareContinuable(spec.provider, {
|
|
1698
1425
|
sessionId: childId,
|
|
1426
|
+
cwd,
|
|
1699
1427
|
parent,
|
|
1700
1428
|
signal: spec.signal
|
|
1701
1429
|
});
|
|
1702
1430
|
spec.signal.throwIfAborted();
|
|
1703
|
-
this.
|
|
1431
|
+
this.assertAdmitting(parent);
|
|
1704
1432
|
const inheritedEventCount = SessionLogOffset(prepared.seed?.length ?? 0);
|
|
1705
1433
|
const seed = prepared.seed;
|
|
1434
|
+
let established;
|
|
1435
|
+
const messageId = await this.locks.run(childId, async () => {
|
|
1436
|
+
spec.signal.throwIfAborted();
|
|
1437
|
+
this.assertAdmitting(parent);
|
|
1438
|
+
this.assertChildIdAvailable(childId);
|
|
1439
|
+
if (spec.childId !== void 0) {
|
|
1440
|
+
const persisted = await persistence?.stat(childId, { signal: spec.signal });
|
|
1441
|
+
spec.signal.throwIfAborted();
|
|
1442
|
+
this.assertAdmitting(parent);
|
|
1443
|
+
this.assertChildIdAvailable(childId);
|
|
1444
|
+
if (persisted !== void 0) throw new SubagentError(`subagent "${childId}" already exists`, "DUPLICATE_CHILD");
|
|
1445
|
+
}
|
|
1446
|
+
const activation = await this.materialize({
|
|
1447
|
+
kind: "local",
|
|
1448
|
+
childId,
|
|
1449
|
+
provider: spec.provider,
|
|
1450
|
+
parent,
|
|
1451
|
+
create: {
|
|
1452
|
+
cwd,
|
|
1453
|
+
seed,
|
|
1454
|
+
meta: childSessionMeta(parent, childDepth, prepared.seed !== void 0),
|
|
1455
|
+
inheritedEventCount,
|
|
1456
|
+
delegatedPolicies,
|
|
1457
|
+
descriptor
|
|
1458
|
+
},
|
|
1459
|
+
agentOptions,
|
|
1460
|
+
composition: {
|
|
1461
|
+
persona: request.persona,
|
|
1462
|
+
toolFilter: request.toolFilter
|
|
1463
|
+
},
|
|
1464
|
+
signal: spec.signal,
|
|
1465
|
+
delivery: spec.delivery,
|
|
1466
|
+
outputSchema: spec.request.outputSchema
|
|
1467
|
+
});
|
|
1468
|
+
established = activation;
|
|
1469
|
+
const child = requireLocalActivation(activation).handle.agent;
|
|
1470
|
+
const childHeader = child.session.header;
|
|
1471
|
+
return await this.submitMaterialized(activation, spec.delivery !== "caller" && isAdjacentAgentSendMessageTool(this.ctx.get("tools")?.get("send_message", child)) ? withContinuableReturnGuidance(parent.id, request.prompt) : request.prompt, {
|
|
1472
|
+
source: { kind: "user" },
|
|
1473
|
+
signal: spec.signal,
|
|
1474
|
+
delivery: "queue"
|
|
1475
|
+
}, parent, () => {
|
|
1476
|
+
establishCatalogChild(parent.session, childHeader, descriptor);
|
|
1477
|
+
});
|
|
1478
|
+
});
|
|
1706
1479
|
return {
|
|
1480
|
+
...this.receipt(established),
|
|
1707
1481
|
childId,
|
|
1708
|
-
messageId
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
signal: spec.signal,
|
|
1741
|
-
delivery: "queue"
|
|
1742
|
-
}, parent, () => {
|
|
1743
|
-
establishCatalogChild(parent.session, childHeader, descriptor);
|
|
1744
|
-
});
|
|
1482
|
+
messageId
|
|
1483
|
+
};
|
|
1484
|
+
} catch (error) {
|
|
1485
|
+
releaseHold();
|
|
1486
|
+
throw error;
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1489
|
+
/**
|
|
1490
|
+
* Start an external backend under activation ownership.
|
|
1491
|
+
* @param spec - task, provider, cancellation, and delivery policy.
|
|
1492
|
+
* @returns the accepted activation and its result/disposal capabilities.
|
|
1493
|
+
*/
|
|
1494
|
+
async startExternal(spec) {
|
|
1495
|
+
const parent = spec.request.parent;
|
|
1496
|
+
this.assertAdmitting(parent);
|
|
1497
|
+
const pendingId = brandString(randomUUID());
|
|
1498
|
+
const releaseHold = this.holdOwnership(parent, pendingId);
|
|
1499
|
+
let activation;
|
|
1500
|
+
try {
|
|
1501
|
+
const cwd = await this.resolveDirectory(spec.request, spec.signal);
|
|
1502
|
+
activation = await this.materialize({
|
|
1503
|
+
kind: "external",
|
|
1504
|
+
childId: pendingId,
|
|
1505
|
+
provider: spec.provider,
|
|
1506
|
+
parent,
|
|
1507
|
+
signal: spec.signal,
|
|
1508
|
+
delivery: spec.delivery,
|
|
1509
|
+
start: (signal) => this.host.startExternal(spec.provider, {
|
|
1510
|
+
...spec.request,
|
|
1511
|
+
cwd,
|
|
1512
|
+
label: spec.label,
|
|
1513
|
+
signal
|
|
1745
1514
|
})
|
|
1515
|
+
});
|
|
1516
|
+
this.assertAdmitting(parent);
|
|
1517
|
+
this.assertLiveParent(parent, activation.childId);
|
|
1518
|
+
spec.signal.throwIfAborted();
|
|
1519
|
+
if (spec.delivery === "parent") establishExternalCatalogChild(parent.session, activation.childId, spec.label);
|
|
1520
|
+
this.announce(activation);
|
|
1521
|
+
return this.receipt(activation);
|
|
1522
|
+
} catch (error) {
|
|
1523
|
+
if (activation !== void 0) try {
|
|
1524
|
+
await this.dispose(activation);
|
|
1525
|
+
} catch (cleanupError) {
|
|
1526
|
+
this.ctx.logger.warn(`subagent "${activation.childId}" admission rollback failed: ${failureMessage(cleanupError)}`);
|
|
1527
|
+
}
|
|
1528
|
+
throw error;
|
|
1529
|
+
} finally {
|
|
1530
|
+
releaseHold();
|
|
1531
|
+
}
|
|
1532
|
+
}
|
|
1533
|
+
/** Capture the explicit or inherited directory while the parent still owns startup. */
|
|
1534
|
+
async resolveDirectory(request, signal) {
|
|
1535
|
+
const cwd = request.cwd !== void 0 && isAbsolute(request.cwd) ? request.cwd : resolve(await this.ctx.workingDirectory.ensure(request.parent, signal), request.cwd ?? ".");
|
|
1536
|
+
signal.throwIfAborted();
|
|
1537
|
+
return cwd;
|
|
1538
|
+
}
|
|
1539
|
+
/**
|
|
1540
|
+
* Deliver one model-authored message to a direct continuable child or to the
|
|
1541
|
+
* sender's direct parent. A missing direct child cold-resumes through the
|
|
1542
|
+
* ordinary continuation lifecycle.
|
|
1543
|
+
* @param sender - exact live Agent authorizing and originating the message.
|
|
1544
|
+
* @param targetId - durable direct-parent or direct-child session id.
|
|
1545
|
+
* @param content - model-authored content to deliver.
|
|
1546
|
+
* @param options - caller cancellation before acceptance.
|
|
1547
|
+
* @returns the accepted message's inbox id.
|
|
1548
|
+
*/
|
|
1549
|
+
async sendMessage(sender, targetId, content, options) {
|
|
1550
|
+
if (this.ctx.agents.get(sender.id) !== sender) throw new SubagentError("message delivery requires the exact live sender agent", "UNAUTHORIZED");
|
|
1551
|
+
this.assertAdmitting(sender);
|
|
1552
|
+
const senderActivation = this.resident.get(sender.id);
|
|
1553
|
+
if (senderActivation !== void 0 && senderActivation.kind === "local" && senderActivation.handle.agent === sender && senderActivation.parent.id === targetId) {
|
|
1554
|
+
options.signal.throwIfAborted();
|
|
1555
|
+
return this.sendToParent(senderActivation, sender, content);
|
|
1556
|
+
}
|
|
1557
|
+
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");
|
|
1558
|
+
return this.deliverToChild(sender, targetId, content, {
|
|
1559
|
+
signal: options.signal,
|
|
1560
|
+
delivery: "steer"
|
|
1561
|
+
});
|
|
1562
|
+
}
|
|
1563
|
+
/**
|
|
1564
|
+
* Queue one human-authored prompt as a distinct direct-child turn.
|
|
1565
|
+
* @param parent - exact live direct parent authorizing delivery.
|
|
1566
|
+
* @param childId - durable direct-child session id.
|
|
1567
|
+
* @param content - model-visible prompt blocks.
|
|
1568
|
+
* @param source - durable attribution for the human prompt.
|
|
1569
|
+
* @param signal - caller cancellation before inbox acceptance.
|
|
1570
|
+
* @returns the accepted durable message id.
|
|
1571
|
+
*/
|
|
1572
|
+
async queuePrompt(parent, childId, content, source, signal) {
|
|
1573
|
+
return this.deliverToChild(parent, childId, content, {
|
|
1574
|
+
source,
|
|
1575
|
+
signal,
|
|
1576
|
+
delivery: "queue"
|
|
1577
|
+
});
|
|
1578
|
+
}
|
|
1579
|
+
/**
|
|
1580
|
+
* Steer one host-authored prompt to a direct continuable child.
|
|
1581
|
+
* @param parent - exact live direct parent authorizing delivery.
|
|
1582
|
+
* @param childId - durable direct-child session id.
|
|
1583
|
+
* @param content - model-visible prompt blocks.
|
|
1584
|
+
* @param source - durable attribution for the host prompt.
|
|
1585
|
+
* @param signal - caller cancellation before inbox acceptance.
|
|
1586
|
+
* @returns the accepted durable message id.
|
|
1587
|
+
*/
|
|
1588
|
+
async steerPrompt(parent, childId, content, source, signal) {
|
|
1589
|
+
return this.deliverToChild(parent, childId, content, {
|
|
1590
|
+
source,
|
|
1591
|
+
signal,
|
|
1592
|
+
delivery: "steer"
|
|
1593
|
+
});
|
|
1594
|
+
}
|
|
1595
|
+
/** Admit local input synchronously before any teardown can begin. */
|
|
1596
|
+
deliver(activation, message, delivery) {
|
|
1597
|
+
if (activation.closing !== void 0) throw new SubagentError("subagent activation is being disposed; the message was not accepted", "ACTIVATION_CLOSING");
|
|
1598
|
+
const local = requireLocalActivation(activation);
|
|
1599
|
+
if (local.structured?.acceptsInput() === false) throw new SubagentError("subagent is submitting or has submitted its structured result; the message was not accepted", "INPUT_CLOSED");
|
|
1600
|
+
if (delivery === "steer") local.handle.agent.steer(message);
|
|
1601
|
+
else local.handle.agent.followup(message);
|
|
1602
|
+
}
|
|
1603
|
+
/**
|
|
1604
|
+
* Reject one child identity already owned by a live Agent or Session.
|
|
1605
|
+
* @param childId - proposed durable child session id.
|
|
1606
|
+
*/
|
|
1607
|
+
assertChildIdAvailable(childId) {
|
|
1608
|
+
if (this.resident.has(childId) || this.ctx.agents.get(childId) !== void 0 || this.ctx.get("sessions")?.get(childId) !== void 0) throw new SubagentError(`subagent "${childId}" already exists`, "DUPLICATE_CHILD");
|
|
1609
|
+
}
|
|
1610
|
+
/**
|
|
1611
|
+
* Pre-register `childId` in a continuation-managed parent's owned set so the
|
|
1612
|
+
* parent cannot settle while a caller is still establishing or resuming that
|
|
1613
|
+
* child. Returns a releaser for the failure path; it removes only a hold
|
|
1614
|
+
* this call added, and leaves ownership in place once a live Activation for
|
|
1615
|
+
* the child exists.
|
|
1616
|
+
* @param parent - the live direct parent the operation is admitted under.
|
|
1617
|
+
* @param childId - the durable child the operation addresses.
|
|
1618
|
+
* @returns the failure-path releaser; a no-op when nothing was added.
|
|
1619
|
+
*/
|
|
1620
|
+
holdOwnership(parent, childId) {
|
|
1621
|
+
const parentActivation = this.resident.get(parent.id);
|
|
1622
|
+
if (parentActivation === void 0 || activationAgent(parentActivation) !== parent) return () => {};
|
|
1623
|
+
if (parentActivation.closing !== void 0) throw new SubagentError(`subagent parent "${parent.id}" is being disposed; the child was not established`, "ACTIVATION_CLOSING");
|
|
1624
|
+
if (parentActivation.ownedChildren.has(childId)) return () => {};
|
|
1625
|
+
parentActivation.ownedChildren.add(childId);
|
|
1626
|
+
return () => {
|
|
1627
|
+
const live = this.resident.get(childId);
|
|
1628
|
+
/* v8 ignore next 4 -- reaching this arm needs another delivery to establish the child
|
|
1629
|
+
* between this operation's failure and its releaser running, which no test can schedule
|
|
1630
|
+
* deterministically: the ownership edge then belongs to that live Activation, so the
|
|
1631
|
+
* conservative keep leaves it for finishDisposal's releaseOwnership. */
|
|
1632
|
+
if (live !== void 0 && live.closing === void 0) return;
|
|
1633
|
+
if (parentActivation.ownedChildren.delete(childId)) this.wake(parentActivation);
|
|
1634
|
+
};
|
|
1635
|
+
}
|
|
1636
|
+
/**
|
|
1637
|
+
* Apply activation admission to a resident parent before delivering input.
|
|
1638
|
+
* @param parent - exact live Agent receiving the message.
|
|
1639
|
+
* @param message - durable user message to deliver.
|
|
1640
|
+
* @param delivery - receiving inbox destination.
|
|
1641
|
+
*/
|
|
1642
|
+
sendWaking(parent, message, delivery) {
|
|
1643
|
+
const parentActivation = this.resident.get(parent.id);
|
|
1644
|
+
if (parentActivation !== void 0 && activationAgent(parentActivation) === parent) {
|
|
1645
|
+
try {
|
|
1646
|
+
this.deliver(parentActivation, message, delivery);
|
|
1647
|
+
} finally {
|
|
1648
|
+
this.wake(parentActivation);
|
|
1649
|
+
}
|
|
1650
|
+
return;
|
|
1651
|
+
}
|
|
1652
|
+
if (delivery === "steer") parent.steer(message);
|
|
1653
|
+
else parent.followup(message);
|
|
1654
|
+
}
|
|
1655
|
+
/**
|
|
1656
|
+
* Reject new admission once the manager or this exact parent tree began draining.
|
|
1657
|
+
* @param agent - exact live Agent whose lineage determines admission.
|
|
1658
|
+
*/
|
|
1659
|
+
assertAdmitting(agent) {
|
|
1660
|
+
const closing = this.closingTeardownFor(agent);
|
|
1661
|
+
if (closing === void 0) return;
|
|
1662
|
+
throw new SubagentError(closing === "manager" ? "subagents are draining; the operation was not admitted" : `subagents below parent "${closing.id}" are draining; the operation was not admitted`, "DRAINING");
|
|
1663
|
+
}
|
|
1664
|
+
/**
|
|
1665
|
+
* Authorize one operation against the durable direct-parent lineage.
|
|
1666
|
+
* @param parent - exact live Agent claiming direct-parent authority.
|
|
1667
|
+
* @param childId - durable child session id addressed by the operation.
|
|
1668
|
+
* @param parentSession - durable direct-parent id recorded by the child.
|
|
1669
|
+
*/
|
|
1670
|
+
authorizeLineage(parent, childId, parentSession) {
|
|
1671
|
+
this.assertLiveParent(parent, childId);
|
|
1672
|
+
if (parentSession !== parent.id) throw new SubagentError(`subagent "${childId}" belongs to another parent session`, "UNAUTHORIZED");
|
|
1673
|
+
}
|
|
1674
|
+
/** Reject parent authority after the live registry entry changes. */
|
|
1675
|
+
assertLiveParent(parent, childId) {
|
|
1676
|
+
if (this.ctx.agents.get(parent.id) !== parent) throw new SubagentError(`subagent "${childId}" delivery requires the exact live parent agent`, "UNAUTHORIZED");
|
|
1677
|
+
}
|
|
1678
|
+
/**
|
|
1679
|
+
* Create or resume one child Agent and publish its Activation.
|
|
1680
|
+
* @param inputs - reconstruction and admission inputs for the residency epoch.
|
|
1681
|
+
* @returns the published process-local Activation.
|
|
1682
|
+
*/
|
|
1683
|
+
materialize(inputs) {
|
|
1684
|
+
this.assertAdmitting(inputs.parent);
|
|
1685
|
+
inputs.signal.throwIfAborted();
|
|
1686
|
+
const lineage = this.liveLineage(inputs.parent);
|
|
1687
|
+
const pool = this.resident.get(inputs.parent.id)?.pool ?? this.rootPool(inputs.parent);
|
|
1688
|
+
const releaseSlot = pool.reserve(this.maxActiveSubagents());
|
|
1689
|
+
const settled = Promise.withResolvers();
|
|
1690
|
+
const controller = new AbortController();
|
|
1691
|
+
const materialization = {
|
|
1692
|
+
lineage,
|
|
1693
|
+
settled: settled.promise,
|
|
1694
|
+
controller
|
|
1695
|
+
};
|
|
1696
|
+
this.materializations.add(materialization);
|
|
1697
|
+
return this.materializeTracked({
|
|
1698
|
+
...inputs,
|
|
1699
|
+
signal: AbortSignal.any([inputs.signal, controller.signal])
|
|
1700
|
+
}, lineage, pool, releaseSlot).catch((error) => {
|
|
1701
|
+
releaseSlot();
|
|
1702
|
+
if (error instanceof Error && error.name === "AbortError") this.assertAdmitting(inputs.parent);
|
|
1703
|
+
throw error;
|
|
1704
|
+
}).finally(() => {
|
|
1705
|
+
this.materializations.delete(materialization);
|
|
1706
|
+
settled.resolve();
|
|
1707
|
+
});
|
|
1708
|
+
}
|
|
1709
|
+
/**
|
|
1710
|
+
* Stop and release one Activation through its memoized close transaction.
|
|
1711
|
+
* @param activation - exact residency epoch to close.
|
|
1712
|
+
* @returns the shared close transaction.
|
|
1713
|
+
*/
|
|
1714
|
+
dispose(activation) {
|
|
1715
|
+
return this.close(activation, () => this.finishDisposal(activation, true));
|
|
1716
|
+
}
|
|
1717
|
+
/** Close admission synchronously and share one release, including startup rollback. */
|
|
1718
|
+
close(activation, release) {
|
|
1719
|
+
if (activation.closing !== void 0) return activation.closing;
|
|
1720
|
+
const completion = Promise.withResolvers();
|
|
1721
|
+
activation.closing = completion.promise;
|
|
1722
|
+
release().then(completion.resolve, completion.reject);
|
|
1723
|
+
return completion.promise;
|
|
1724
|
+
}
|
|
1725
|
+
/** Dispose independent roots and report every branch failure after all settle. */
|
|
1726
|
+
async disposeRoots(roots, failureSubject) {
|
|
1727
|
+
const reasons = (await Promise.all(roots.map(async (activation) => {
|
|
1728
|
+
try {
|
|
1729
|
+
await this.dispose(activation);
|
|
1730
|
+
return;
|
|
1731
|
+
} catch (error) {
|
|
1732
|
+
return error;
|
|
1733
|
+
}
|
|
1734
|
+
}))).filter((failure) => failure !== void 0);
|
|
1735
|
+
if (reasons.length > 0) throw new SubagentError(`subagent teardown failed for ${reasons.length} ${failureSubject}: ` + reasons.map((reason) => failureMessage(reason)).join("; "), "ACTIVATION_TEARDOWN_FAILED");
|
|
1736
|
+
}
|
|
1737
|
+
/** Return the retained member set for one exact scoped-teardown root. */
|
|
1738
|
+
closingMembers(root) {
|
|
1739
|
+
const existing = this.closingScopes.get(root);
|
|
1740
|
+
if (existing !== void 0) return existing;
|
|
1741
|
+
const members = /* @__PURE__ */ new Set();
|
|
1742
|
+
this.closingScopes.set(root, members);
|
|
1743
|
+
return members;
|
|
1744
|
+
}
|
|
1745
|
+
/** Return the exact currently resolvable ancestry from `agent` upward. */
|
|
1746
|
+
liveLineage(agent) {
|
|
1747
|
+
const lineage = [agent];
|
|
1748
|
+
const seen = new Set([agent.id]);
|
|
1749
|
+
let parentSession = agent.session.header.parentSession;
|
|
1750
|
+
while (parentSession !== void 0) {
|
|
1751
|
+
const parent = this.ctx.agents.get(parentSession);
|
|
1752
|
+
if (parent === void 0 || seen.has(parent.id)) break;
|
|
1753
|
+
lineage.push(parent);
|
|
1754
|
+
seen.add(parent.id);
|
|
1755
|
+
parentSession = parent.session.header.parentSession;
|
|
1756
|
+
}
|
|
1757
|
+
return lineage;
|
|
1758
|
+
}
|
|
1759
|
+
/** Return the teardown that closed continuable admission for this agent's lineage. */
|
|
1760
|
+
closingTeardownFor(agent) {
|
|
1761
|
+
if (this.draining) return "manager";
|
|
1762
|
+
const lineage = this.liveLineage(agent);
|
|
1763
|
+
for (const [root, members] of this.closingScopes) if (members.has(agent) || lineage.includes(root)) return root;
|
|
1764
|
+
}
|
|
1765
|
+
/** Resolve a root's pool once; descendants inherit their resident parent's pool directly. */
|
|
1766
|
+
rootPool(parent) {
|
|
1767
|
+
let pool = this.rootPools.get(parent);
|
|
1768
|
+
if (pool === void 0) {
|
|
1769
|
+
pool = new ActivationPool();
|
|
1770
|
+
this.rootPools.set(parent, pool);
|
|
1771
|
+
}
|
|
1772
|
+
return pool;
|
|
1773
|
+
}
|
|
1774
|
+
/** Perform one tracked materialization through publication or rollback. */
|
|
1775
|
+
async materializeTracked(inputs, parentLineage, pool, releaseSlot) {
|
|
1776
|
+
const { provider, parent } = inputs;
|
|
1777
|
+
let childId = inputs.childId;
|
|
1778
|
+
inputs.signal.throwIfAborted();
|
|
1779
|
+
let resource;
|
|
1780
|
+
if (inputs.kind === "external") {
|
|
1781
|
+
const controller = new AbortController();
|
|
1782
|
+
const cancelStartup = () => {
|
|
1783
|
+
controller.abort(inputs.signal.reason);
|
|
1784
|
+
};
|
|
1785
|
+
inputs.signal.addEventListener("abort", cancelStartup, { once: true });
|
|
1786
|
+
try {
|
|
1787
|
+
const run = await inputs.start(controller.signal);
|
|
1788
|
+
resource = {
|
|
1789
|
+
kind: "external",
|
|
1790
|
+
run,
|
|
1791
|
+
controller
|
|
1792
|
+
};
|
|
1793
|
+
run.result.catch(() => void 0);
|
|
1794
|
+
childId = run.id;
|
|
1795
|
+
try {
|
|
1796
|
+
this.assertChildIdAvailable(childId);
|
|
1797
|
+
} catch (error) {
|
|
1798
|
+
await run.dispose();
|
|
1799
|
+
throw error;
|
|
1800
|
+
}
|
|
1801
|
+
} finally {
|
|
1802
|
+
inputs.signal.removeEventListener("abort", cancelStartup);
|
|
1803
|
+
}
|
|
1804
|
+
} else {
|
|
1805
|
+
const { create } = inputs;
|
|
1806
|
+
let structured;
|
|
1807
|
+
const setup = async (childCtx, child) => {
|
|
1808
|
+
if (create !== void 0) {
|
|
1809
|
+
child.session.append("subagent/descriptor", create.descriptor);
|
|
1810
|
+
appendDelegatedPolicyOverrides(child.session, create.delegatedPolicies);
|
|
1811
|
+
const workingDirectory = childCtx.get("workingDirectory");
|
|
1812
|
+
if (workingDirectory === void 0) throw new Error("local subagents require the working-directory service");
|
|
1813
|
+
await workingDirectory.set(child, create.cwd, inputs.signal);
|
|
1814
|
+
}
|
|
1815
|
+
applyChildComposition(childCtx, parent, inputs.composition);
|
|
1816
|
+
if (inputs.outputSchema !== void 0) structured = attachStructuredRuntime(childCtx, inputs.outputSchema, () => this.resident.get(childId)?.ownedChildren.size === 0 && child.inbox.nextStep.length === 0 && child.inbox.nextTurn.length === 0);
|
|
1817
|
+
};
|
|
1818
|
+
const handle = create === void 0 ? await this.ownerCtx.agents.resume({
|
|
1819
|
+
resumeSessionId: childId,
|
|
1820
|
+
parentAgent: parent,
|
|
1821
|
+
agentOptions: inputs.agentOptions,
|
|
1822
|
+
signal: inputs.signal,
|
|
1823
|
+
setup
|
|
1824
|
+
}) : await this.ownerCtx.agents.create({
|
|
1825
|
+
sessionId: childId,
|
|
1826
|
+
parentAgent: parent,
|
|
1827
|
+
meta: create.meta,
|
|
1828
|
+
...create.seed === void 0 ? {} : { seed: create.seed },
|
|
1829
|
+
inheritedEventCount: create.inheritedEventCount,
|
|
1830
|
+
agentOptions: inputs.agentOptions,
|
|
1831
|
+
signal: inputs.signal,
|
|
1832
|
+
setup
|
|
1833
|
+
});
|
|
1834
|
+
resource = {
|
|
1835
|
+
kind: "local",
|
|
1836
|
+
handle,
|
|
1837
|
+
outputStart: handle.agent.session.seq,
|
|
1838
|
+
structured,
|
|
1839
|
+
failureAt: void 0,
|
|
1840
|
+
poke: Promise.withResolvers()
|
|
1841
|
+
};
|
|
1842
|
+
}
|
|
1843
|
+
const observer = this.host.observeActivation(provider, childId, parent);
|
|
1844
|
+
const activation = {
|
|
1845
|
+
pool,
|
|
1846
|
+
releaseSlot,
|
|
1847
|
+
childId,
|
|
1848
|
+
...resource,
|
|
1849
|
+
parent,
|
|
1850
|
+
delivery: inputs.delivery ?? "parent",
|
|
1851
|
+
result: Promise.withResolvers(),
|
|
1852
|
+
closing: void 0,
|
|
1853
|
+
ancestry: new WeakSet(resource.kind === "external" ? parentLineage : [resource.handle.agent, ...parentLineage]),
|
|
1854
|
+
ownedChildren: /* @__PURE__ */ new Set(),
|
|
1855
|
+
observer,
|
|
1856
|
+
announced: false
|
|
1857
|
+
};
|
|
1858
|
+
activation.result.promise.catch(() => void 0);
|
|
1859
|
+
this.resident.set(childId, activation);
|
|
1860
|
+
try {
|
|
1861
|
+
this.assertAdmitting(parent);
|
|
1862
|
+
inputs.signal.throwIfAborted();
|
|
1863
|
+
if (this.ctx.agents.get(parent.id) !== parent) throw new SubagentError("subagent parent is no longer live", "UNAUTHORIZED");
|
|
1864
|
+
this.acquireOwnership(parent, childId);
|
|
1865
|
+
const wakeSettlement = () => {
|
|
1866
|
+
this.wake(activation);
|
|
1746
1867
|
};
|
|
1868
|
+
if (activation.kind === "local") {
|
|
1869
|
+
observeLocalFailure(activation, wakeSettlement);
|
|
1870
|
+
activation.handle.agent.ctx.on("agent/inbox/inserted", wakeSettlement);
|
|
1871
|
+
activation.handle.agent.ctx.on("agent/inbox/claimed", wakeSettlement);
|
|
1872
|
+
activation.handle.agent.ctx.on("agent/inbox/discarded", wakeSettlement);
|
|
1873
|
+
}
|
|
1874
|
+
observer.start(activationAgent(activation));
|
|
1747
1875
|
} catch (error) {
|
|
1748
|
-
|
|
1876
|
+
/* v8 ignore next -- rollback failure must not mask the admission failure
|
|
1877
|
+
* that prevented this operation from returning an accepted message id. */
|
|
1878
|
+
await this.rollbackUnpublished(activation, error).catch(() => void 0);
|
|
1749
1879
|
throw error;
|
|
1750
1880
|
}
|
|
1881
|
+
return activation;
|
|
1751
1882
|
}
|
|
1752
1883
|
/**
|
|
1753
|
-
*
|
|
1754
|
-
*
|
|
1755
|
-
* ordinary continuation lifecycle.
|
|
1756
|
-
* @param sender - exact live Agent authorizing and originating the message.
|
|
1757
|
-
* @param targetId - durable direct-parent or direct-child session id.
|
|
1758
|
-
* @param content - model-authored content to deliver.
|
|
1759
|
-
* @param options - caller cancellation before acceptance.
|
|
1760
|
-
* @returns the accepted message's inbox id.
|
|
1884
|
+
* Begin settlement observation after initial acceptance is recorded.
|
|
1885
|
+
* @param activation - accepted activation to observe.
|
|
1761
1886
|
*/
|
|
1762
|
-
|
|
1763
|
-
if (
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1887
|
+
announce(activation) {
|
|
1888
|
+
if (activation.announced) return;
|
|
1889
|
+
activation.announced = true;
|
|
1890
|
+
if (activation.kind === "local") this.watchLocalSettlement(activation);
|
|
1891
|
+
else {
|
|
1892
|
+
const finish = () => this.close(activation, () => this.finishDisposal(activation, false));
|
|
1893
|
+
activation.run.result.then(finish, finish).catch((error) => {
|
|
1894
|
+
this.reportTeardownFailure(activation, error);
|
|
1895
|
+
});
|
|
1769
1896
|
}
|
|
1770
|
-
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");
|
|
1771
|
-
return this.deliverToChild(sender, targetId, content, {
|
|
1772
|
-
signal: options.signal,
|
|
1773
|
-
delivery: "steer"
|
|
1774
|
-
});
|
|
1775
1897
|
}
|
|
1776
|
-
/**
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
source,
|
|
1788
|
-
signal,
|
|
1789
|
-
delivery: "queue"
|
|
1898
|
+
/** Release an Activation whose start edge was not published. */
|
|
1899
|
+
rollbackUnpublished(activation, error) {
|
|
1900
|
+
return this.close(activation, async () => {
|
|
1901
|
+
try {
|
|
1902
|
+
await (activation.kind === "local" ? activation.handle.dispose() : activation.run.dispose());
|
|
1903
|
+
} finally {
|
|
1904
|
+
this.resident.delete(activation.childId);
|
|
1905
|
+
activation.releaseSlot();
|
|
1906
|
+
this.releaseOwnership(activation.childId);
|
|
1907
|
+
activation.result.reject(error);
|
|
1908
|
+
}
|
|
1790
1909
|
});
|
|
1791
1910
|
}
|
|
1792
|
-
/**
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
*/
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1911
|
+
/** Register the child in a continuation-managed parent's owned set. */
|
|
1912
|
+
acquireOwnership(parent, childId) {
|
|
1913
|
+
const parentActivation = this.resident.get(parent.id);
|
|
1914
|
+
if (parentActivation === void 0) return;
|
|
1915
|
+
if (parentActivation.closing !== void 0) throw new SubagentError(`subagent parent "${parent.id}" is being disposed; the child was not established`, "ACTIVATION_CLOSING");
|
|
1916
|
+
parentActivation.ownedChildren.add(childId);
|
|
1917
|
+
this.wake(parentActivation);
|
|
1918
|
+
}
|
|
1919
|
+
/** Remove one child from its live owner's set and let that owner re-check settlement. */
|
|
1920
|
+
releaseOwnership(childId) {
|
|
1921
|
+
for (const candidate of this.resident.values()) if (candidate.ownedChildren.delete(childId)) this.wake(candidate);
|
|
1922
|
+
}
|
|
1923
|
+
/** Let a settlement watcher re-check residency after relevant state changes. */
|
|
1924
|
+
wake(activation) {
|
|
1925
|
+
if (activation.kind === "external") return;
|
|
1926
|
+
activation.poke.resolve();
|
|
1927
|
+
activation.poke = Promise.withResolvers();
|
|
1928
|
+
}
|
|
1929
|
+
/** Follow one Activation to natural settlement. */
|
|
1930
|
+
watchLocalSettlement(activation) {
|
|
1931
|
+
(async () => {
|
|
1932
|
+
while (true) {
|
|
1933
|
+
const idleObservation = activation.poke;
|
|
1934
|
+
await activation.handle.agent.whenIdle();
|
|
1935
|
+
if (activation.closing !== void 0) return;
|
|
1936
|
+
const readiness = await this.locks.run(activation.childId, () => Promise.resolve(this.settlementState(activation, idleObservation)));
|
|
1937
|
+
if (readiness === "closed") return;
|
|
1938
|
+
if (readiness === "retry") continue;
|
|
1939
|
+
if (readiness === "wait") {
|
|
1940
|
+
await idleObservation.promise;
|
|
1941
|
+
continue;
|
|
1942
|
+
}
|
|
1943
|
+
const finalSeq = activation.handle.agent.session.seq;
|
|
1944
|
+
await this.flushFinalState(activation);
|
|
1945
|
+
const attempt = await this.locks.run(activation.childId, () => {
|
|
1946
|
+
const state = this.settlementState(activation, idleObservation);
|
|
1947
|
+
if (state !== "ready") return Promise.resolve(state);
|
|
1948
|
+
if (activation.handle.agent.session.seq !== finalSeq) return Promise.resolve("retry");
|
|
1949
|
+
let done;
|
|
1950
|
+
try {
|
|
1951
|
+
activation.handle.agent.runMaintenance(() => {
|
|
1952
|
+
done = this.close(activation, () => this.finishDisposal(activation, false));
|
|
1953
|
+
return Promise.resolve();
|
|
1954
|
+
});
|
|
1955
|
+
} catch (_error) {
|
|
1956
|
+
return Promise.resolve("retry");
|
|
1957
|
+
}
|
|
1958
|
+
return Promise.resolve({ done });
|
|
1959
|
+
});
|
|
1960
|
+
if (typeof attempt === "string") {
|
|
1961
|
+
if (attempt === "closed") return;
|
|
1962
|
+
continue;
|
|
1963
|
+
}
|
|
1964
|
+
try {
|
|
1965
|
+
await attempt.done;
|
|
1966
|
+
} catch (error) {
|
|
1967
|
+
this.reportTeardownFailure(activation, error);
|
|
1968
|
+
}
|
|
1969
|
+
return;
|
|
1970
|
+
}
|
|
1971
|
+
})();
|
|
1972
|
+
}
|
|
1973
|
+
/** Report disposal failure once after the natural settlement observer finishes. */
|
|
1974
|
+
reportTeardownFailure(activation, error) {
|
|
1975
|
+
this.ctx.logger.warn(`subagent "${activation.childId}" activation teardown failed: ${failureMessage(error)}`);
|
|
1976
|
+
}
|
|
1977
|
+
/** Check pending input and owned children before closing admission. */
|
|
1978
|
+
settlementState(activation, observation) {
|
|
1979
|
+
if (activation.closing !== void 0) return "closed";
|
|
1980
|
+
if (activation.poke !== observation) return "retry";
|
|
1981
|
+
const inbox = activation.handle.agent.inbox;
|
|
1982
|
+
if (activation.ownedChildren.size > 0) return "wait";
|
|
1983
|
+
if (activation.failureAt === void 0 && (inbox.nextTurn.length > 0 || inbox.nextStep.length > 0)) return "wait";
|
|
1984
|
+
return "ready";
|
|
1985
|
+
}
|
|
1986
|
+
/** Propagate stop synchronously and await descendant startup rollback before release. */
|
|
1987
|
+
async finishDisposal(activation, stop) {
|
|
1988
|
+
this.wake(activation);
|
|
1989
|
+
const { childId } = activation;
|
|
1990
|
+
const failures = [];
|
|
1991
|
+
let result = {
|
|
1992
|
+
output: [],
|
|
1993
|
+
stopReason: "error"
|
|
1994
|
+
};
|
|
1995
|
+
let resultFailure;
|
|
1996
|
+
let externalDisposal;
|
|
1997
|
+
const child = activationAgent(activation);
|
|
1998
|
+
const detail = (error) => child === void 0 ? failureMessage(error) : errorChain(error);
|
|
1999
|
+
try {
|
|
2000
|
+
if (activation.kind === "external") {
|
|
2001
|
+
if (stop) {
|
|
2002
|
+
activation.controller.abort({ kind: "parent" });
|
|
2003
|
+
externalDisposal = Promise.resolve().then(() => activation.run.dispose());
|
|
2004
|
+
externalDisposal.catch(() => void 0);
|
|
2005
|
+
}
|
|
2006
|
+
result = await activation.run.result;
|
|
2007
|
+
} else {
|
|
2008
|
+
if (stop) {
|
|
2009
|
+
activation.handle.agent.cancel({ kind: "parent" });
|
|
2010
|
+
const idle = activation.handle.agent.whenIdle();
|
|
2011
|
+
const children = [...activation.ownedChildren].map((child) => this.resident.get(child)).filter((child) => child !== void 0);
|
|
2012
|
+
const pending = [...this.materializations].filter((item) => item.lineage.includes(activation.handle.agent));
|
|
2013
|
+
const childDisposals = children.map((child) => this.dispose(child));
|
|
2014
|
+
for (const item of pending) item.controller.abort();
|
|
2015
|
+
childDisposals.push(...pending.map((item) => item.settled));
|
|
2016
|
+
const reasons = (await Promise.all(childDisposals.map(async (disposal) => {
|
|
2017
|
+
try {
|
|
2018
|
+
await disposal;
|
|
2019
|
+
return;
|
|
2020
|
+
} catch (error) {
|
|
2021
|
+
return error;
|
|
2022
|
+
}
|
|
2023
|
+
}))).filter((reason) => reason !== void 0);
|
|
2024
|
+
if (reasons.length > 0) failures.push(new SubagentError(`subagent "${childId}" child teardown failed: ${reasons.map((reason) => failureMessage(reason)).join("; ")}`, "ACTIVATION_TEARDOWN_FAILED"));
|
|
2025
|
+
await idle;
|
|
2026
|
+
await this.flushFinalState(activation);
|
|
2027
|
+
}
|
|
2028
|
+
result = captureLocalResult(activation);
|
|
2029
|
+
}
|
|
2030
|
+
} catch (error) {
|
|
2031
|
+
resultFailure = { error };
|
|
2032
|
+
failures.push(new SubagentError(`subagent "${childId}" activation teardown failed: ${detail(error)}`, "ACTIVATION_TEARDOWN_FAILED", { cause: error }));
|
|
2033
|
+
}
|
|
2034
|
+
try {
|
|
2035
|
+
if (activation.kind === "local") await activation.handle.dispose();
|
|
2036
|
+
else await (externalDisposal ?? activation.run.dispose());
|
|
2037
|
+
} catch (error) {
|
|
2038
|
+
failures.push(new SubagentError(`subagent "${childId}" activation handle disposal failed: ${detail(error)}`, "ACTIVATION_TEARDOWN_FAILED", { cause: error }));
|
|
2039
|
+
}
|
|
2040
|
+
let failure;
|
|
2041
|
+
if (failures.length === 1) failure = failures[0];
|
|
2042
|
+
else if (failures.length > 1) failure = new SubagentError(`subagent "${childId}" activation teardown failed at ${failures.length} boundaries: ` + failures.map((item) => detail(item)).join("; "), "ACTIVATION_TEARDOWN_FAILED", { cause: new AggregateError(failures) });
|
|
2043
|
+
this.resident.delete(childId);
|
|
2044
|
+
activation.releaseSlot();
|
|
2045
|
+
const terminal = failure === void 0 ? result : {
|
|
2046
|
+
output: [],
|
|
2047
|
+
stopReason: "error"
|
|
2048
|
+
};
|
|
2049
|
+
this.notifySettlement(activation, result);
|
|
2050
|
+
this.releaseOwnership(childId);
|
|
2051
|
+
activation.observer.settle(terminal);
|
|
2052
|
+
if (resultFailure === void 0) activation.result.resolve(result);
|
|
2053
|
+
else activation.result.reject(resultFailure.error);
|
|
2054
|
+
if (failure !== void 0) throw failure;
|
|
2055
|
+
}
|
|
2056
|
+
/** Deliver the captured execution result to the durable direct parent after cleanup. */
|
|
2057
|
+
notifySettlement(activation, terminal) {
|
|
2058
|
+
if (!activation.announced || activation.delivery === "caller") return;
|
|
2059
|
+
try {
|
|
2060
|
+
const parent = this.ctx.agents.get(activation.parent.id);
|
|
2061
|
+
if (parent !== activation.parent) return;
|
|
2062
|
+
const message = createSettlementMessage(activation.childId, terminal, activationAgent(activation) !== void 0);
|
|
2063
|
+
if (this.closingTeardownFor(parent) !== void 0) {
|
|
2064
|
+
parent.inject(message);
|
|
2065
|
+
return;
|
|
2066
|
+
}
|
|
2067
|
+
this.sendWaking(parent, message, parent.status === "idle" ? "queue" : "steer");
|
|
2068
|
+
} catch (error) {
|
|
2069
|
+
this.ctx.logger.warn(`subagent "${activation.childId}" settlement notice was not delivered to its parent: ` + errorChain(error));
|
|
2070
|
+
}
|
|
2071
|
+
}
|
|
2072
|
+
/** Request a best-effort final session flush before closing natural-settlement admission. */
|
|
2073
|
+
async flushFinalState(activation) {
|
|
2074
|
+
try {
|
|
2075
|
+
await activation.handle.agent.ctx.sessions.flush(activation.handle.agent.session);
|
|
2076
|
+
} catch (error) {
|
|
2077
|
+
this.ctx.logger.warn(`subagent "${activation.childId}" best-effort final session flush failed; the persisted state may be unavailable or stale on resume: ${errorChain(error)}`);
|
|
2078
|
+
}
|
|
2079
|
+
}
|
|
2080
|
+
/** Return capabilities bound to an exact activation instead of a future same-id instance. */
|
|
2081
|
+
receipt(activation) {
|
|
2082
|
+
return {
|
|
2083
|
+
childId: activation.childId,
|
|
2084
|
+
result: activation.result.promise,
|
|
2085
|
+
dispose: () => this.dispose(activation)
|
|
2086
|
+
};
|
|
1807
2087
|
}
|
|
1808
2088
|
/** Route one parent-originated delivery through residency and cold resume. */
|
|
1809
2089
|
async deliverToChild(parent, childId, content, options) {
|
|
1810
|
-
this.
|
|
1811
|
-
const releaseHold = this.
|
|
2090
|
+
this.assertAdmitting(parent);
|
|
2091
|
+
const releaseHold = this.holdOwnership(parent, childId);
|
|
1812
2092
|
try {
|
|
1813
2093
|
return await this.deliverFollowup(parent, childId, content, options);
|
|
1814
2094
|
} catch (error) {
|
|
@@ -1819,47 +2099,38 @@ var SubagentContinuationManager = class {
|
|
|
1819
2099
|
/** The delivery loop behind {@link deliverToChild}, run under the parent hold. */
|
|
1820
2100
|
async deliverFollowup(parent, childId, content, options) {
|
|
1821
2101
|
while (true) {
|
|
1822
|
-
const live = await this.
|
|
1823
|
-
const activation = this.
|
|
2102
|
+
const live = await this.locks.run(childId, async () => {
|
|
2103
|
+
const activation = this.resident.get(childId);
|
|
1824
2104
|
if (activation === void 0) return this.coldResume(parent, childId, content, options);
|
|
1825
|
-
|
|
2105
|
+
requireLocalActivation(activation);
|
|
2106
|
+
const disposal = activation.closing;
|
|
1826
2107
|
/* v8 ignore next 3 -- the send-versus-dispose cutoff needs a delivery to
|
|
1827
2108
|
* observe the transaction inside the same critical section that opened it. */
|
|
1828
2109
|
if (disposal !== void 0) return disposal.then(() => void 0, () => void 0);
|
|
1829
2110
|
if (contentHasImage(content)) {
|
|
1830
|
-
await this.assertImageCapable(activation.handle.agent, options.signal);
|
|
1831
|
-
if (activation.
|
|
1832
|
-
await Promise.allSettled([activation.
|
|
2111
|
+
await this.assertImageCapable(requireLocalActivation(activation).handle.agent, options.signal);
|
|
2112
|
+
if (activation.closing !== void 0) {
|
|
2113
|
+
await Promise.allSettled([activation.closing]);
|
|
1833
2114
|
return;
|
|
1834
2115
|
}
|
|
1835
2116
|
}
|
|
1836
2117
|
const messageId = this.submitAdmitted(activation, content, options, parent);
|
|
1837
|
-
activation
|
|
2118
|
+
this.announce(activation);
|
|
1838
2119
|
return messageId;
|
|
1839
2120
|
});
|
|
1840
2121
|
/* v8 ignore start -- only a delivery that lost the disposal cutoff retries. */
|
|
1841
2122
|
if (live !== void 0) return live;
|
|
1842
|
-
this.
|
|
2123
|
+
this.assertAdmitting(parent);
|
|
1843
2124
|
options.signal.throwIfAborted();
|
|
1844
2125
|
}
|
|
1845
2126
|
}
|
|
1846
|
-
/**
|
|
1847
|
-
* Interrupt one live continuable child's current turn. Admission is
|
|
1848
|
-
* synchronous and the cancellation effect is asynchronous. An absent or
|
|
1849
|
-
* already-closing target is an accepted no-op after authority checks.
|
|
1850
|
-
* @param targetSessionId - the durable child session id to interrupt.
|
|
1851
|
-
* @param authority - the human parent address or exact live ancestor Agent.
|
|
1852
|
-
*/
|
|
1853
|
-
interrupt(targetSessionId, authority) {
|
|
1854
|
-
this.activations.interrupt(targetSessionId, authority);
|
|
1855
|
-
}
|
|
1856
2127
|
/** Deliver one resident continuable child's message to its live direct parent. */
|
|
1857
2128
|
sendToParent(activation, sender, content) {
|
|
1858
2129
|
/* v8 ignore next 6 -- only synchronous re-entrant teardown can open this
|
|
1859
2130
|
* transaction between exact-agent authorization and this no-await span. */
|
|
1860
|
-
if (activation.
|
|
1861
|
-
const parent = this.ctx.agents.get(activation.
|
|
1862
|
-
if (parent
|
|
2131
|
+
if (activation.closing !== void 0) throw new SubagentError(`subagent "${sender.id}" activation is being disposed; the message was not delivered`, "ACTIVATION_CLOSING");
|
|
2132
|
+
const parent = this.ctx.agents.get(activation.parent.id);
|
|
2133
|
+
if (parent !== activation.parent) throw new SubagentError("direct parent is not live; the message was not delivered", "PARENT_UNAVAILABLE");
|
|
1863
2134
|
const message = createAgentMessage(sender, content);
|
|
1864
2135
|
this.sendAgentMessage(parent, message);
|
|
1865
2136
|
return message.id;
|
|
@@ -1867,30 +2138,11 @@ var SubagentContinuationManager = class {
|
|
|
1867
2138
|
/** Send one Agent message while translating only the target's own rejection. */
|
|
1868
2139
|
sendAgentMessage(parent, message) {
|
|
1869
2140
|
try {
|
|
1870
|
-
this.
|
|
2141
|
+
this.sendWaking(parent, message, "steer");
|
|
1871
2142
|
} catch (error) {
|
|
1872
2143
|
throw new SubagentError("direct parent is not live; the message was not delivered", "PARENT_UNAVAILABLE", { cause: error });
|
|
1873
2144
|
}
|
|
1874
2145
|
}
|
|
1875
|
-
/** Close manager-wide admission and release every live Activation. */
|
|
1876
|
-
async drain() {
|
|
1877
|
-
await this.activations.drain();
|
|
1878
|
-
}
|
|
1879
|
-
/**
|
|
1880
|
-
* Stop only the continuable descendants of exact live host-owned parents.
|
|
1881
|
-
* @param parents - exact live roots whose continuable descendants must stop.
|
|
1882
|
-
*/
|
|
1883
|
-
async drainDescendants(parents) {
|
|
1884
|
-
await this.activations.drainDescendants(parents);
|
|
1885
|
-
}
|
|
1886
|
-
/**
|
|
1887
|
-
* Release selected resident direct children of one exact live parent.
|
|
1888
|
-
* @param parent - exact live direct parent authorizing the selected release.
|
|
1889
|
-
* @param childIds - durable direct-child ids to release when resident.
|
|
1890
|
-
*/
|
|
1891
|
-
async drainChildren(parent, childIds) {
|
|
1892
|
-
await this.activations.drainChildren(parent, childIds);
|
|
1893
|
-
}
|
|
1894
2146
|
/**
|
|
1895
2147
|
* Cold-resume a persisted child and submit the waiting turn. The descriptor
|
|
1896
2148
|
* supplies every reconstruction input; no subagent provider is dispatched.
|
|
@@ -1911,13 +2163,14 @@ var SubagentContinuationManager = class {
|
|
|
1911
2163
|
throw new SubagentError(`subagent "${childId}" is unavailable`, "NOT_RESUMABLE", { cause: error });
|
|
1912
2164
|
}
|
|
1913
2165
|
const source = __addDisposableResource$2(env_1, observation, false);
|
|
1914
|
-
this.
|
|
1915
|
-
this.
|
|
2166
|
+
this.assertAdmitting(parent);
|
|
2167
|
+
this.authorizeLineage(parent, childId, source.header.parentSession);
|
|
1916
2168
|
const descriptor = foldSubagentDescriptor(source.events.slice(source.inheritedEventCount));
|
|
1917
2169
|
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");
|
|
1918
2170
|
let activation;
|
|
1919
2171
|
try {
|
|
1920
|
-
activation = await this.
|
|
2172
|
+
activation = await this.materialize({
|
|
2173
|
+
kind: "local",
|
|
1921
2174
|
childId,
|
|
1922
2175
|
provider: descriptor.provider,
|
|
1923
2176
|
parent,
|
|
@@ -1949,16 +2202,16 @@ var SubagentContinuationManager = class {
|
|
|
1949
2202
|
async submitMaterialized(activation, content, options, parent, commit) {
|
|
1950
2203
|
try {
|
|
1951
2204
|
if (contentHasImage(content)) {
|
|
1952
|
-
await this.assertImageCapable(activation.handle.agent, options.signal);
|
|
1953
|
-
if (activation.
|
|
2205
|
+
await this.assertImageCapable(requireLocalActivation(activation).handle.agent, options.signal);
|
|
2206
|
+
if (activation.closing !== void 0) throw new SubagentError(`subagent "${activation.childId}" is closing`, "ACTIVATION_CLOSING");
|
|
1954
2207
|
}
|
|
1955
2208
|
const messageId = this.submitAdmitted(activation, content, options, parent);
|
|
1956
2209
|
commit?.();
|
|
1957
|
-
activation
|
|
2210
|
+
this.announce(activation);
|
|
1958
2211
|
return messageId;
|
|
1959
2212
|
} catch (error) {
|
|
1960
2213
|
try {
|
|
1961
|
-
await this.
|
|
2214
|
+
await this.dispose(activation);
|
|
1962
2215
|
} catch (cleanupError) {
|
|
1963
2216
|
this.ctx.logger.warn(`subagent continuation: disposal after admission or catalog append failure also failed: ${String(cleanupError)}`);
|
|
1964
2217
|
}
|
|
@@ -1971,7 +2224,16 @@ var SubagentContinuationManager = class {
|
|
|
1971
2224
|
content,
|
|
1972
2225
|
source: options.source
|
|
1973
2226
|
});
|
|
1974
|
-
|
|
2227
|
+
options.signal.throwIfAborted();
|
|
2228
|
+
this.assertAdmitting(parent);
|
|
2229
|
+
this.authorizeLineage(parent, activation.childId, activation.parent.id);
|
|
2230
|
+
this.acquireOwnership(parent, activation.childId);
|
|
2231
|
+
try {
|
|
2232
|
+
this.deliver(activation, message, options.delivery);
|
|
2233
|
+
} finally {
|
|
2234
|
+
this.wake(activation);
|
|
2235
|
+
}
|
|
2236
|
+
return message.id;
|
|
1975
2237
|
}
|
|
1976
2238
|
/** Refuse image content for a child whose fixed model accepts text only. */
|
|
1977
2239
|
async assertImageCapable(agent, signal) {
|
|
@@ -2124,7 +2386,7 @@ async function listDescendants(ctx, rootSessionId, signal) {
|
|
|
2124
2386
|
visited.add(entry.id);
|
|
2125
2387
|
let children;
|
|
2126
2388
|
try {
|
|
2127
|
-
children = await readChildren(entry.id);
|
|
2389
|
+
children = entry.mode === "external" ? [] : await readChildren(entry.id);
|
|
2128
2390
|
} catch (error) {
|
|
2129
2391
|
if (error instanceof SubagentError) throw error;
|
|
2130
2392
|
const code = error instanceof Error && "code" in error ? error.code : void 0;
|
|
@@ -2482,8 +2744,7 @@ const subagentIdentityProjectionDefinition = {
|
|
|
2482
2744
|
* Provider-side vocabulary for OUT-OF-PROCESS subagent backends — the pieces
|
|
2483
2745
|
* that enforce this seam's own contracts around a child in another process:
|
|
2484
2746
|
* the no-capabilities advertisement, timing-bound validation, child
|
|
2485
|
-
* working-directory
|
|
2486
|
-
* session's workspace), the never-reject result settlement, and the standard
|
|
2747
|
+
* working-directory validation, the never-reject result settlement, and the standard
|
|
2487
2748
|
* run-handle publication. Backends compose these with their own wire drivers;
|
|
2488
2749
|
* the process machinery itself (spawn, env scrub, managed-range teardown)
|
|
2489
2750
|
* belongs to the `dsh-subprocess` seam.
|
|
@@ -2566,39 +2827,6 @@ function assertUsableCwd(prefix, label, cwd) {
|
|
|
2566
2827
|
if (!isEnterableDirectory(cwd)) throw new Error(`${prefix}: ${label} is not an accessible directory: ${cwd}`);
|
|
2567
2828
|
return cwd;
|
|
2568
2829
|
}
|
|
2569
|
-
/**
|
|
2570
|
-
* Validate a configured `cwd` override ONCE, at plugin load: reject the empty
|
|
2571
|
-
* string (`path.resolve('')` is the process cwd — it would silently
|
|
2572
|
-
* reintroduce the launch-directory fallback this resolution removes),
|
|
2573
|
-
* interpret a relative path against the harness launch directory, and require
|
|
2574
|
-
* an enterable directory.
|
|
2575
|
-
* @param prefix - the consuming plugin's diagnostic prefix.
|
|
2576
|
-
* @param cwd - the configured override, or `undefined` when the config omits it.
|
|
2577
|
-
* @returns the validated absolute override, or `undefined` when omitted.
|
|
2578
|
-
*/
|
|
2579
|
-
function validateConfiguredCwd(prefix, cwd) {
|
|
2580
|
-
if (cwd === void 0) return void 0;
|
|
2581
|
-
if (cwd === "") throw new Error(`${prefix}: config cwd must not be empty — omit the key to inherit the parent session cwd`);
|
|
2582
|
-
return assertUsableCwd(prefix, "config cwd", resolve(cwd));
|
|
2583
|
-
}
|
|
2584
|
-
/**
|
|
2585
|
-
* Resolve the child's working directory at start: the deployment override
|
|
2586
|
-
* when configured (already validated at load), else the parent session's
|
|
2587
|
-
* workspace cwd (validated here, its earliest resolvable point). Fails loud
|
|
2588
|
-
* when neither exists — falling back to the harness process cwd would
|
|
2589
|
-
* silently bind the child to the server's launch directory instead of the
|
|
2590
|
-
* delegating session's workspace (one server process serves many sessions,
|
|
2591
|
-
* each with its own cwd).
|
|
2592
|
-
* @param prefix - the consuming plugin's diagnostic prefix.
|
|
2593
|
-
* @param configured - the load-validated override, or `undefined`.
|
|
2594
|
-
* @param parentCwd - the delegating parent session's workspace cwd, if any.
|
|
2595
|
-
* @returns the absolute child working directory.
|
|
2596
|
-
*/
|
|
2597
|
-
function resolveChildCwd(prefix, configured, parentCwd) {
|
|
2598
|
-
if (configured !== void 0) return configured;
|
|
2599
|
-
if (parentCwd === void 0) throw new Error(`${prefix}: no working directory for the child — configure \`cwd\` or delegate from a parent session that has one`);
|
|
2600
|
-
return assertUsableCwd(prefix, "parent session cwd", parentCwd);
|
|
2601
|
-
}
|
|
2602
2830
|
/** Normalize an unknown thrown value to an Error (the catch binding is `unknown`). */
|
|
2603
2831
|
function toError(value) {
|
|
2604
2832
|
/* v8 ignore next */
|
|
@@ -2646,13 +2874,12 @@ async function settleRunResult(parts) {
|
|
|
2646
2874
|
* local cancellation — there is no assumption the child cooperates — and then
|
|
2647
2875
|
* awaits the backend's teardown to actual exit.
|
|
2648
2876
|
* @param parts - the run identity, result, cancellation wiring, and teardown.
|
|
2649
|
-
* @returns the
|
|
2877
|
+
* @returns the external run handle.
|
|
2650
2878
|
*/
|
|
2651
2879
|
function subprocessRunHandle(parts) {
|
|
2652
2880
|
let disposal;
|
|
2653
2881
|
return {
|
|
2654
2882
|
id: parts.id,
|
|
2655
|
-
localAgent: void 0,
|
|
2656
2883
|
result: parts.result,
|
|
2657
2884
|
dispose() {
|
|
2658
2885
|
if (disposal !== void 0) return disposal;
|
|
@@ -2664,80 +2891,6 @@ function subprocessRunHandle(parts) {
|
|
|
2664
2891
|
};
|
|
2665
2892
|
}
|
|
2666
2893
|
//#endregion
|
|
2667
|
-
//#region lib/types/run-settlement.js
|
|
2668
|
-
/**
|
|
2669
|
-
* Settlement of one ONE-SHOT subagent run into a background-Task outcome. Only
|
|
2670
|
-
* the one-shot background path uses Jobs; continuable children have no Task,
|
|
2671
|
-
* no per-message result, and no Task cancellation.
|
|
2672
|
-
*
|
|
2673
|
-
* @module @deepseek-ai/dsh-subagent/run-settlement
|
|
2674
|
-
*/
|
|
2675
|
-
/** Flatten a child's final output blocks to the task's final text. */
|
|
2676
|
-
function finalText(blocks) {
|
|
2677
|
-
return blocks.filter((block) => block.type === "text").map((block) => block.text).join("");
|
|
2678
|
-
}
|
|
2679
|
-
/** Render a failed stop reason with optional provider-authored detail. */
|
|
2680
|
-
function failureDetail(result) {
|
|
2681
|
-
const stopReason = result.stopReason;
|
|
2682
|
-
return result.diagnostic === void 0 ? stopReason : `${stopReason}; diagnostic: ${result.diagnostic}`;
|
|
2683
|
-
}
|
|
2684
|
-
/**
|
|
2685
|
-
* Map a child result to the task outcome: completed carries final text, local
|
|
2686
|
-
* cancellation (`aborted` without a diagnostic) is killed, and provider-
|
|
2687
|
-
* diagnosed remote aborts plus every other reason are failed without partial
|
|
2688
|
-
* output.
|
|
2689
|
-
* @param result - child terminal result.
|
|
2690
|
-
* @returns outcome for the `ctx.jobs` registration.
|
|
2691
|
-
*/
|
|
2692
|
-
function runOutcome(result) {
|
|
2693
|
-
switch (result.stopReason) {
|
|
2694
|
-
case "completed": return {
|
|
2695
|
-
status: "completed",
|
|
2696
|
-
result: finalText(result.output)
|
|
2697
|
-
};
|
|
2698
|
-
case "aborted": return result.diagnostic === void 0 ? { status: "killed" } : {
|
|
2699
|
-
status: "failed",
|
|
2700
|
-
detail: failureDetail(result)
|
|
2701
|
-
};
|
|
2702
|
-
case "error":
|
|
2703
|
-
case "max-tokens":
|
|
2704
|
-
case "refusal": return {
|
|
2705
|
-
status: "failed",
|
|
2706
|
-
detail: failureDetail(result)
|
|
2707
|
-
};
|
|
2708
|
-
default: return {
|
|
2709
|
-
status: "failed",
|
|
2710
|
-
detail: failureDetail(result)
|
|
2711
|
-
};
|
|
2712
|
-
}
|
|
2713
|
-
}
|
|
2714
|
-
/**
|
|
2715
|
-
* Await the child result, dispose the run, then return its task outcome. Result
|
|
2716
|
-
* and disposal failures become `failed`; when both fail, both details survive.
|
|
2717
|
-
* @param run - live run to settle and release.
|
|
2718
|
-
* @returns outcome after child resources are released.
|
|
2719
|
-
*/
|
|
2720
|
-
async function settleRun(run) {
|
|
2721
|
-
let outcome;
|
|
2722
|
-
try {
|
|
2723
|
-
outcome = runOutcome(await run.result);
|
|
2724
|
-
} catch (error) {
|
|
2725
|
-
outcome = {
|
|
2726
|
-
status: "failed",
|
|
2727
|
-
detail: String(error)
|
|
2728
|
-
};
|
|
2729
|
-
}
|
|
2730
|
-
try {
|
|
2731
|
-
await run.dispose();
|
|
2732
|
-
} catch (error) {
|
|
2733
|
-
return {
|
|
2734
|
-
status: "failed",
|
|
2735
|
-
detail: `${outcome.detail === void 0 ? "" : `${outcome.detail}; `}dispose failed: ${String(error)}`
|
|
2736
|
-
};
|
|
2737
|
-
}
|
|
2738
|
-
return outcome;
|
|
2739
|
-
}
|
|
2740
|
-
//#endregion
|
|
2741
2894
|
//#region lib/types/index.js
|
|
2742
2895
|
var __runInitializers = function(thisArg, initializers, value) {
|
|
2743
2896
|
var useValue = arguments.length > 2;
|
|
@@ -2777,7 +2930,7 @@ var __esDecorate = function(ctor, descriptorIn, decorators, contextIn, initializ
|
|
|
2777
2930
|
if (target) Object.defineProperty(target, contextIn.name, descriptor);
|
|
2778
2931
|
done = true;
|
|
2779
2932
|
};
|
|
2780
|
-
/** Named provider registry with
|
|
2933
|
+
/** Named provider registry with managed activations, durable discovery, and local child messaging. */
|
|
2781
2934
|
let SubagentRuntime = (() => {
|
|
2782
2935
|
let _classSuper = TypertRemoteService;
|
|
2783
2936
|
let _instanceExtraInitializers = [];
|
|
@@ -2822,8 +2975,9 @@ let SubagentRuntime = (() => {
|
|
|
2822
2975
|
maxDepth: z.number().step(1).min(0).max(Number.MAX_SAFE_INTEGER).default(1).volatile(),
|
|
2823
2976
|
maxActiveSubagents: z.number().step(1).min(1).max(Number.MAX_SAFE_INTEGER).default(8).volatile()
|
|
2824
2977
|
});
|
|
2978
|
+
static inject = ["workingDirectory"];
|
|
2825
2979
|
providers = /* @__PURE__ */ new Map();
|
|
2826
|
-
|
|
2980
|
+
manager;
|
|
2827
2981
|
/**
|
|
2828
2982
|
* The contained lifecycle-edge publisher. Built here because scoped dispatch
|
|
2829
2983
|
* keys its carrier by this exact service instance, whose own context filter
|
|
@@ -2835,21 +2989,23 @@ let SubagentRuntime = (() => {
|
|
|
2835
2989
|
this.config = config;
|
|
2836
2990
|
this.emitLifecycle = createLifecycleEmitter(this.ctx, (parent) => scopeTarget(this, parent));
|
|
2837
2991
|
ctx.inject(["agents"], (childCtx) => {
|
|
2838
|
-
const manager = new
|
|
2992
|
+
const manager = new SubagentManager(childCtx, {
|
|
2993
|
+
startExternal: (name, request) => {
|
|
2994
|
+
return this.expectProvider(name).start(request);
|
|
2995
|
+
},
|
|
2839
2996
|
prepareContinuable: (name, request) => this.prepareContinuable(name, request),
|
|
2840
2997
|
observeActivation: (provider, childId, parent) => this.observeActivation(provider, childId, parent)
|
|
2841
2998
|
}, () => this.config.maxActiveSubagents.get());
|
|
2842
|
-
this.
|
|
2999
|
+
this.manager = manager;
|
|
2843
3000
|
childCtx.effect(() => () => {
|
|
2844
3001
|
/* v8 ignore else -- one injected binding owns the slot until its fiber disposes. */
|
|
2845
|
-
if (this.
|
|
2846
|
-
}, "subagents.
|
|
3002
|
+
if (this.manager === manager) this.manager = void 0;
|
|
3003
|
+
}, "subagents.managerBinding()");
|
|
2847
3004
|
});
|
|
2848
3005
|
ctx.inject(["sessionProjections"], (projectionCtx) => {
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
projections.register(subagentIdentityProjectionDefinition);
|
|
3006
|
+
projectionCtx.sessionProjections.register(subagentCatalogProjectionDefinition);
|
|
3007
|
+
projectionCtx.sessionProjections.register(subagentTimingProjectionDefinition);
|
|
3008
|
+
projectionCtx.sessionProjections.register(subagentIdentityProjectionDefinition);
|
|
2853
3009
|
});
|
|
2854
3010
|
ctx.inject(["agents"], (agentsCtx) => {
|
|
2855
3011
|
installSubagentArchiveAdmission(agentsCtx);
|
|
@@ -2868,16 +3024,32 @@ let SubagentRuntime = (() => {
|
|
|
2868
3024
|
return depth;
|
|
2869
3025
|
}
|
|
2870
3026
|
/**
|
|
2871
|
-
* Establish
|
|
2872
|
-
*
|
|
2873
|
-
*
|
|
2874
|
-
*
|
|
2875
|
-
* @param spec - provider, delegation request, and caller cancellation.
|
|
2876
|
-
* @returns the durable child id and the accepted prompt's message id.
|
|
2877
|
-
* @throws when continuation services are unavailable or materialization fails.
|
|
3027
|
+
* Establish a managed child with backend-specific execution capabilities.
|
|
3028
|
+
* @param spec - task, backend, and result recipient.
|
|
3029
|
+
* @returns identities, execution result, and exact-activation disposal. A reserved
|
|
3030
|
+
* childId requires a local backend and guarantees an accepted messageId.
|
|
2878
3031
|
*/
|
|
2879
|
-
async
|
|
2880
|
-
|
|
3032
|
+
async startActivation(spec) {
|
|
3033
|
+
const provider = this.expectProvider(spec.provider);
|
|
3034
|
+
if (spec.childId !== void 0 && provider.prepareContinuable === void 0) throw new SubagentError("reserved child ids require a local backend", "UNSUPPORTED_CAPABILITY");
|
|
3035
|
+
assertSubagentMaxDepth(spec.request.maxDepth);
|
|
3036
|
+
this.assertCapabilities(provider, {
|
|
3037
|
+
...spec.request,
|
|
3038
|
+
signal: spec.signal
|
|
3039
|
+
});
|
|
3040
|
+
const manager = this.requireManager();
|
|
3041
|
+
if (this.ctx.get("agents")?.get(spec.request.parent.id) !== spec.request.parent) throw new SubagentError("subagent creation requires the exact live parent agent", "UNAUTHORIZED");
|
|
3042
|
+
if (spec.request.outputSchema !== void 0) assertObjectJsonSchema(spec.request.outputSchema);
|
|
3043
|
+
return provider.prepareContinuable === void 0 ? manager.startExternal(spec) : manager.startLocal(spec);
|
|
3044
|
+
}
|
|
3045
|
+
/**
|
|
3046
|
+
* Join progressing descendants without cancelling them. Idle descendants whose
|
|
3047
|
+
* inboxes require a later wake stay resident and do not delay host completion.
|
|
3048
|
+
* @param parent - the exact parent whose descendant work is observed.
|
|
3049
|
+
* @returns whether work was joined; hosts recheck parent idle after true.
|
|
3050
|
+
*/
|
|
3051
|
+
async waitForChildren(parent) {
|
|
3052
|
+
return this.manager?.waitForChildren(parent) ?? false;
|
|
2881
3053
|
}
|
|
2882
3054
|
/**
|
|
2883
3055
|
* Steer one model-authored message to the sender's direct parent or direct
|
|
@@ -2894,7 +3066,7 @@ let SubagentRuntime = (() => {
|
|
|
2894
3066
|
* or the message was not admitted.
|
|
2895
3067
|
*/
|
|
2896
3068
|
async sendMessage(sender, targetId, content, options) {
|
|
2897
|
-
return this.
|
|
3069
|
+
return this.requireManager().sendMessage(sender, targetId, content, options);
|
|
2898
3070
|
}
|
|
2899
3071
|
/**
|
|
2900
3072
|
* Deliver one host-protocol message to a direct continuable child.
|
|
@@ -2909,16 +3081,16 @@ let SubagentRuntime = (() => {
|
|
|
2909
3081
|
* @returns the accepted message's inbox id.
|
|
2910
3082
|
*/
|
|
2911
3083
|
[deliverSubagentPrompt](parent, childId, content, source, signal, delivery) {
|
|
2912
|
-
return delivery === "steer" ? this.
|
|
3084
|
+
return delivery === "steer" ? this.requireManager().steerPrompt(parent, childId, content, source, signal) : this.requireManager().queuePrompt(parent, childId, content, source, signal);
|
|
2913
3085
|
}
|
|
2914
3086
|
/**
|
|
2915
|
-
* Interrupt one live
|
|
3087
|
+
* Interrupt one live child's current execution under a human parent
|
|
2916
3088
|
* address or an exact live ancestor Agent. Fire-and-return: the cancel
|
|
2917
3089
|
* signal is issued before this returns, but the target may keep running
|
|
2918
3090
|
* until it observes the signal. Unclaimed pending inbox work, the Activation,
|
|
2919
3091
|
* and published descendants are preserved; claimed work is not requeued.
|
|
2920
|
-
* Once the interrupted
|
|
2921
|
-
* queue.
|
|
3092
|
+
* Once the interrupted Agent is idle, a waking send resumes the parked FIFO
|
|
3093
|
+
* queue. External backends stop their single execution. An absent target
|
|
2922
3094
|
* is an accepted no-op, as is a manager-less composition, which cannot own a
|
|
2923
3095
|
* live Activation.
|
|
2924
3096
|
* @param targetSessionId - the durable child session id to interrupt.
|
|
@@ -2927,35 +3099,35 @@ let SubagentRuntime = (() => {
|
|
|
2927
3099
|
* live target.
|
|
2928
3100
|
*/
|
|
2929
3101
|
interrupt(targetSessionId, authority) {
|
|
2930
|
-
this.
|
|
3102
|
+
this.manager?.interrupt(targetSessionId, authority);
|
|
2931
3103
|
}
|
|
2932
3104
|
/**
|
|
2933
|
-
* Close
|
|
3105
|
+
* Close subagent admission below exact live parent Agents, stop only their
|
|
2934
3106
|
* visible descendant Activations synchronously, then await admitted scoped
|
|
2935
3107
|
* materializations and release those forests child-first. The scoped cutoff
|
|
2936
3108
|
* lasts until each exact parent leaves the registry; unrelated parent trees
|
|
2937
3109
|
* remain live.
|
|
2938
3110
|
* @param parents - exact host-owned parent Agents entering teardown.
|
|
2939
|
-
* @returns once every retained descendant
|
|
3111
|
+
* @returns once every retained descendant activation released its execution handle.
|
|
2940
3112
|
* @throws an aggregate error after all branches settle when any failed.
|
|
2941
3113
|
*/
|
|
2942
|
-
async
|
|
2943
|
-
const manager = this.
|
|
3114
|
+
async drainDescendants(parents) {
|
|
3115
|
+
const manager = this.manager;
|
|
2944
3116
|
if (manager === void 0) return;
|
|
2945
3117
|
await manager.drainDescendants(parents);
|
|
2946
3118
|
}
|
|
2947
3119
|
/**
|
|
2948
|
-
* Release selected resident
|
|
3120
|
+
* Release selected resident direct children of one exact live
|
|
2949
3121
|
* parent. Other children of the same parent remain admitted and resident.
|
|
2950
3122
|
* Absent targets and a manager-less composition are accepted no-ops.
|
|
2951
3123
|
* @param parent - exact live direct parent authorizing the selected release.
|
|
2952
3124
|
* @param childIds - durable direct-child ids to release when resident.
|
|
2953
|
-
* @returns once every selected
|
|
3125
|
+
* @returns once every selected activation released its execution handle.
|
|
2954
3126
|
* @throws {SubagentError} `UNAUTHORIZED` when a resident target belongs to a
|
|
2955
3127
|
* different parent or the supplied parent identity is stale.
|
|
2956
3128
|
*/
|
|
2957
|
-
async
|
|
2958
|
-
const manager = this.
|
|
3129
|
+
async drainChildren(parent, childIds) {
|
|
3130
|
+
const manager = this.manager;
|
|
2959
3131
|
if (manager === void 0) return;
|
|
2960
3132
|
await manager.drainChildren(parent, childIds);
|
|
2961
3133
|
}
|
|
@@ -2974,7 +3146,8 @@ let SubagentRuntime = (() => {
|
|
|
2974
3146
|
/**
|
|
2975
3147
|
* Recursively list reachable parent catalogs in stable pre-order, preserving
|
|
2976
3148
|
* each catalog's event order. Each row carries its catalog parent and depth;
|
|
2977
|
-
* one-shot and unknown-mode children remain
|
|
3149
|
+
* external children are leaves; one-shot and unknown-mode children remain
|
|
3150
|
+
* traversal nodes. Unknown modes
|
|
2978
3151
|
* produce unsupported diagnostics. Unreadable child catalogs produce corrupt
|
|
2979
3152
|
* or unavailable diagnostics and stop only that branch. Root read failures,
|
|
2980
3153
|
* missing services or projections, and cancellation reject the whole listing.
|
|
@@ -3069,12 +3242,14 @@ let SubagentRuntime = (() => {
|
|
|
3069
3242
|
/**
|
|
3070
3243
|
* Register a provider under its name. Registration is effect-scoped and HMR
|
|
3071
3244
|
* safe; removing a provider blocks new starts but does not revoke runs that
|
|
3072
|
-
* were already returned to their holders.
|
|
3245
|
+
* were already returned to their holders. Providers without either execution
|
|
3246
|
+
* method are rejected with UNSUPPORTED_CAPABILITY before registration.
|
|
3073
3247
|
* @param provider - the trusted provider implementation.
|
|
3074
3248
|
* @returns the exact Cordis effect disposer.
|
|
3075
3249
|
*/
|
|
3076
3250
|
registerProvider(provider) {
|
|
3077
3251
|
const name = provider.name;
|
|
3252
|
+
if (provider.start === void 0 && provider.prepareContinuable === void 0) throw new SubagentError(`subagent provider "${name}" must implement start or prepareContinuable`, "UNSUPPORTED_CAPABILITY");
|
|
3078
3253
|
return this.ctx.effect(function* () {
|
|
3079
3254
|
if (this.providers.has(name)) throw new SubagentError(`a subagent provider named "${name}" is already registered`, "DUPLICATE_PROVIDER");
|
|
3080
3255
|
this.providers.set(name, provider);
|
|
@@ -3101,55 +3276,12 @@ let SubagentRuntime = (() => {
|
|
|
3101
3276
|
return [...this.providers.keys()];
|
|
3102
3277
|
}
|
|
3103
3278
|
/**
|
|
3104
|
-
* Establish a published child on the named provider. Capability and semantic
|
|
3105
|
-
* checks run before delegation. Provider ownership lasts until its promise
|
|
3106
|
-
* fulfills; a rejection therefore has no run for the caller to dispose and
|
|
3107
|
-
* emits no run lifecycle events. Post-publication turn and infrastructure
|
|
3108
|
-
* failures settle through the returned run.
|
|
3109
|
-
* A catalog append failure disposes the run and handles its result rejection;
|
|
3110
|
-
* the caller receives the catalog error even if disposal also fails.
|
|
3111
|
-
* @param name - the provider to use.
|
|
3112
|
-
* @param request - child label, prompt, parent, signal, and optional capabilities.
|
|
3113
|
-
* @returns the published holder-owned run.
|
|
3114
|
-
*/
|
|
3115
|
-
async start(name, request) {
|
|
3116
|
-
const provider = this.expectProvider(name);
|
|
3117
|
-
this.assertCapabilities(provider, request);
|
|
3118
|
-
assertSubagentMaxDepth(request.maxDepth);
|
|
3119
|
-
if (request.outputSchema !== void 0) assertObjectJsonSchema(request.outputSchema);
|
|
3120
|
-
const descriptor = snapshotSubagentDescriptor({
|
|
3121
|
-
mode: "one-shot",
|
|
3122
|
-
provider: name,
|
|
3123
|
-
...request.label !== void 0 ? { label: request.label } : {}
|
|
3124
|
-
});
|
|
3125
|
-
const resolved = {
|
|
3126
|
-
...request,
|
|
3127
|
-
descriptor
|
|
3128
|
-
};
|
|
3129
|
-
const run = await provider.start(resolved);
|
|
3130
|
-
const child = run.localAgent?.session;
|
|
3131
|
-
if (child !== void 0) try {
|
|
3132
|
-
establishCatalogChild(request.parent.session, child.header, descriptor);
|
|
3133
|
-
} catch (error) {
|
|
3134
|
-
run.result.catch(() => void 0);
|
|
3135
|
-
try {
|
|
3136
|
-
await run.dispose();
|
|
3137
|
-
} catch (cleanupError) {
|
|
3138
|
-
this.ctx.logger.warn(`subagent: disposal after catalog append failure also failed: ${String(cleanupError)}`);
|
|
3139
|
-
}
|
|
3140
|
-
throw error;
|
|
3141
|
-
}
|
|
3142
|
-
return observeRun(this.emitLifecycle, name, request.parent, run);
|
|
3143
|
-
}
|
|
3144
|
-
/**
|
|
3145
3279
|
* Resolve one provider's detached continuable-creation contribution. Method
|
|
3146
3280
|
* presence on the provider IS the capability, so a provider without it is
|
|
3147
3281
|
* rejected before the manager reserves any child resources.
|
|
3148
3282
|
*/
|
|
3149
3283
|
async prepareContinuable(name, request) {
|
|
3150
|
-
|
|
3151
|
-
if (provider.prepareContinuable === void 0) throw new SubagentError(`subagent provider "${provider.name}" does not support continuable children (no prepareContinuable capability)`, "UNSUPPORTED_CAPABILITY");
|
|
3152
|
-
return provider.prepareContinuable(request);
|
|
3284
|
+
return this.expectProvider(name).prepareContinuable(request);
|
|
3153
3285
|
}
|
|
3154
3286
|
/** Look up a provider for dispatch or fail loud. */
|
|
3155
3287
|
expectProvider(name) {
|
|
@@ -3157,10 +3289,10 @@ let SubagentRuntime = (() => {
|
|
|
3157
3289
|
if (provider === void 0) throw new SubagentError(`no subagent provider registered for "${name}"`, "NO_PROVIDER");
|
|
3158
3290
|
return provider;
|
|
3159
3291
|
}
|
|
3160
|
-
/** Resolve the
|
|
3161
|
-
|
|
3162
|
-
if (this.
|
|
3163
|
-
return this.
|
|
3292
|
+
/** Resolve the subagent manager or fail loud. */
|
|
3293
|
+
requireManager() {
|
|
3294
|
+
if (this.manager === void 0) throw new SubagentError("continuable subagents require the agents service", "CONTINUATION_UNAVAILABLE");
|
|
3295
|
+
return this.manager;
|
|
3164
3296
|
}
|
|
3165
3297
|
/**
|
|
3166
3298
|
* Build the lifecycle observer for one continuable Activation's residency
|
|
@@ -3198,4 +3330,4 @@ let SubagentRuntime = (() => {
|
|
|
3198
3330
|
};
|
|
3199
3331
|
})();
|
|
3200
3332
|
//#endregion
|
|
3201
|
-
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,
|
|
3333
|
+
export { AssistantOutputFold, NO_START_CAPABILITIES, STRUCTURED_OUTPUT_TOOL, SUBAGENT_DESCRIPTOR_VERSION, SubagentDepthError, SubagentError, SubagentRunId, SubagentRuntime, SubagentRuntime as default, appendDelegatedPolicyOverrides, applyChildComposition, assertPositiveFinite, assertSubagentMaxDepth, assertUsableCwd, captureDelegatedPolicyOverrides, childSessionMeta, delegationDepthOf, finalAssistantOutput, foldSubagentDescriptor, parentAgentOptionsForDelegation, resolveChildAgentOptions, resolveChildDepth, settleRunResult, snapshotSubagentDescriptor, subprocessRunHandle };
|