@deepseek-ai/dsh-subagent 0.2.1-alpha.1 → 0.2.1-alpha.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/README.i18n.yaml +40 -34
  2. package/README.md +43 -26
  3. package/README.zh.md +43 -26
  4. package/lib/index.js +1480 -1349
  5. package/lib/typert.host.js +230 -76
  6. package/lib/typert.remote-client.d.ts +6 -2
  7. package/lib/typert.remote-client.js +82 -28
  8. package/lib/types/activation.d.ts +114 -0
  9. package/lib/types/activation.js +112 -0
  10. package/lib/types/catalog.d.ts +14 -6
  11. package/lib/types/catalog.js +34 -30
  12. package/lib/types/child-agent.d.ts +16 -32
  13. package/lib/types/child-agent.js +25 -30
  14. package/lib/types/continuation-messages.d.ts +4 -3
  15. package/lib/types/continuation-messages.js +13 -5
  16. package/lib/types/control-types.d.ts +7 -0
  17. package/lib/types/descriptor.d.ts +7 -46
  18. package/lib/types/descriptor.js +20 -21
  19. package/lib/types/index.d.ts +52 -51
  20. package/lib/types/index.js +81 -98
  21. package/lib/types/internal.d.ts +1 -1
  22. package/lib/types/lifecycle.d.ts +28 -58
  23. package/lib/types/lifecycle.js +9 -74
  24. package/lib/types/list-children.js +1 -1
  25. package/lib/types/manager.d.ts +247 -0
  26. package/lib/types/manager.js +1316 -0
  27. package/lib/types/out-of-process.d.ts +3 -29
  28. package/lib/types/out-of-process.js +5 -47
  29. package/lib/types/projection-types.d.ts +3 -0
  30. package/lib/types/structured.d.ts +49 -0
  31. package/lib/types/structured.js +130 -0
  32. package/lib/types/types.d.ts +66 -100
  33. package/lib/types/types.js +1 -1
  34. package/locale/en.json +5 -0
  35. package/locale/zh.json +6 -0
  36. package/package.json +56 -53
  37. package/lib/types/continuation-activation.d.ts +0 -267
  38. package/lib/types/continuation-activation.js +0 -700
  39. package/lib/types/continuation.d.ts +0 -124
  40. package/lib/types/continuation.js +0 -455
  41. package/lib/types/inbox.d.ts +0 -43
  42. package/lib/types/inbox.js +0 -61
  43. package/lib/types/run-settlement.d.ts +0 -17
  44. package/lib/types/run-settlement.js +0 -70
package/lib/index.js CHANGED
@@ -1,19 +1,19 @@
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
- import { Remote, RemoteError, TypertRemoteService } from "@deepseek-ai/dsh-typert-protocol";
5
+ import { Remote, RemoteError, TypertRemoteService, principalOf } from "@deepseek-ai/dsh-typert-protocol";
6
6
  import { AttachmentError } from "@deepseek-ai/dsh-attachment";
7
7
  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 { SessionLogOffset, SessionSeq } from "@deepseek-ai/dsh-session";
11
+ import { isAbsolute, resolve } from "node:path";
12
12
  import { brandString } from "@deepseek-ai/dsh-brand";
13
- import { snapshotJsonValue } from "@deepseek-ai/dsh-util-values";
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
- * continuation host in `./continuation.ts` — so this module stays the published
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 both subagent shapes: the contained emitter,
213
- * the one-shot run observer, and the continuable Activation observer.
146
+ * Lifecycle-edge publication for managed subagent activations.
214
147
  *
215
- * The public payload contracts ({@link SubagentRunInfo},
216
- * {@link SubagentRunEndInfo}) live in `./types.ts` with the rest of the seam's
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
- * Emit the start/end lifecycle pair for one accepted one-shot run.
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: true
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
- boundary = child.session.seq;
196
+ identity.local = child !== void 0;
306
197
  emit("subagent/start", identity, parent);
307
198
  },
308
- capture: (child) => {
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 === void 0 ? {} : { lastAssistantMessage: 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 a child agent needs. Both the one-shot
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
  */
@@ -456,20 +335,17 @@ function resolveChildAgentOptions(parent, requested, childDepth) {
456
335
  * survive persistence, the seed boundary that separates inherited parent
457
336
  * history from child work, and the composition the child runs under.
458
337
  *
459
- * The preset is read from the parent's LIVE scope chain rather than from its
460
- * header, because a parent that switched preset while blank runs on the newer
461
- * composition and its header still names the older one. Recording it is what
462
- * makes a child's history reconstructable: without it a cold read of the child
463
- * resolves the deployment default and rebuilds turns under a tool set the
464
- * child never had.
338
+ * Omitted selection uses the parent's live composition, including any switch
339
+ * made while the parent was blank.
465
340
  * @param parent - the delegating parent agent.
466
341
  * @param childDepth - the resolved delegation depth to persist.
467
342
  * @param isSeeded - whether this child inherits a parent-log prefix, including an explicitly empty one.
343
+ * @param selectedPreset - independent selection, or omission to inherit the parent.
468
344
  * @returns the `meta` for `ctx.agents.create()`.
469
345
  */
470
- function childSessionMeta(parent, childDepth, isSeeded) {
346
+ function childSessionMeta(parent, childDepth, isSeeded, selectedPreset) {
471
347
  const parentHeader = parent.session.header;
472
- const agentPreset = parent.ctx.get("agentPresets")?.composedPreset(parent.ctx);
348
+ const agentPreset = selectedPreset ?? parent.ctx.get("agentPresets")?.composedPreset(parent.ctx);
473
349
  return {
474
350
  ...parentHeader.cwd !== void 0 ? { cwd: parentHeader.cwd } : {},
475
351
  ...agentPreset === void 0 ? {} : { agentPreset },
@@ -486,29 +362,26 @@ function childSessionMeta(parent, childDepth, isSeeded) {
486
362
  */
487
363
  const SUBAGENT_DELEGATION_CONTEXT = "You are a delegated subagent: your permission scope was fixed when you were started and cannot be widened from inside this session — operations that require approval are rejected automatically. When the task needs access beyond that scope, do not retry the denied operation; state the limitation in your reply so the delegating agent can handle it.";
488
364
  /**
489
- * Compose one child inside its creation window: join its parent's preset,
490
- * register the fixed delegation-scope statement, then apply the child's own
491
- * shadowing persona section and tool restriction, all owned by the child's
492
- * scope and therefore invisible to its parent and siblings. Creation and cold
493
- * resume both pass through here.
494
- *
495
- * The join comes first and the child's own registrations second, which is the
496
- * order the layering already implies — the nearest scope wins a name, and a
497
- * per-child restriction intersects with everything its chain admits — but
498
- * stating it here keeps the two steps from being read as independent.
499
- *
500
- * The join and the per-child registrations live in ONE call because a child
501
- * composed without the join is exactly the defect this function exists to
502
- * prevent: with every model-facing row on the agent plane, a child that joins
503
- * no preset sees an empty tool registry and none of its parent's prompt
504
- * sections. Taking the parent as a parameter is what makes that omission
505
- * unrepresentable at the call sites.
365
+ * Compose a child before publication and restore historical descriptor overrides.
366
+ * Explicit selection mounts the named preset; omission synchronously joins the
367
+ * parent's current generation. Registrations belong to the child's scope.
506
368
  * @param childCtx - the child agent's scoped creation context.
507
- * @param parent - the delegating parent whose composition the child joins.
508
- * @param composition - the per-child persona and tool filter to install.
369
+ * @param parent - the delegating parent used when no preset is selected.
370
+ * @param composition - independent preset and any overrides read from a historical descriptor.
371
+ * @returns A promise when mounting an independent preset; otherwise completes synchronously.
509
372
  */
510
373
  function applyChildComposition(childCtx, parent, composition) {
374
+ if (composition.agentPreset !== void 0) {
375
+ const presets = childCtx.get("agentPresets");
376
+ if (presets === void 0) throw new SubagentError("selecting a child agent preset requires the agent-presets service", "PRESET_UNAVAILABLE");
377
+ return presets.mount(childCtx, composition.agentPreset).then(() => {
378
+ applyChildOverrides(childCtx, composition);
379
+ });
380
+ }
511
381
  childCtx.get("agentPresets")?.composeFrom(childCtx, parent.ctx);
382
+ applyChildOverrides(childCtx, composition);
383
+ }
384
+ function applyChildOverrides(childCtx, composition) {
512
385
  childCtx.systemPrompt.context({
513
386
  name: "subagent:delegation",
514
387
  order: childCtx.systemPrompt.getContextOrder("SUBAGENT_DELEGATION"),
@@ -608,12 +481,13 @@ function withContinuableReturnGuidance(parentId, prompt) {
608
481
  * the parent's own task vocabulary.
609
482
  * @param childId - the durable child the parent knows by id.
610
483
  * @param stopReason - how the child's last ordinary turn ended.
484
+ * @param continuable - whether the child accepts a later message.
611
485
  * @returns the model-facing opening line of the settlement notice.
612
486
  */
613
- function settlementSummary(childId, stopReason) {
487
+ function settlementSummary(childId, stopReason, continuable) {
614
488
  const subject = `Background subagent ${childId}`;
615
489
  switch (stopReason) {
616
- case "completed": return `${subject} finished and will do no further work unless you send it more.`;
490
+ 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
491
  case "aborted": return `${subject} was stopped before it finished.`;
618
492
  case "max-tokens": return `${subject} ran out of room before it finished.`;
619
493
  case "refusal": return `${subject} declined the task.`;
@@ -628,22 +502,34 @@ function settlementSummary(childId, stopReason) {
628
502
  * Build the runtime-owned settlement notice from the child's nonempty closing text.
629
503
  * @param childId - durable child session id named in the notice.
630
504
  * @param terminal - recorded terminal state for the settled Activation.
505
+ * @param continuable - whether the child can accept another task.
631
506
  * @returns the durable user-message representation delivered to the parent.
632
507
  */
633
- function createSettlementMessage(childId, terminal) {
634
- const summary = settlementSummary(childId, terminal.stopReason);
635
- const closingText = (terminal.output ?? []).flatMap((block) => block.type === "text" && block.text.length > 0 ? [block] : []);
508
+ function createSettlementMessage(childId, terminal, continuable = true) {
509
+ const summary = settlementSummary(childId, terminal.stopReason, continuable);
510
+ const closingText = terminal.output.flatMap((block) => block.type === "text" && block.text.length > 0 ? [block] : []);
636
511
  return createUserMessage({
637
- content: [{
638
- type: "text",
639
- text: summary
640
- }, ...closingText.length === 0 ? [{
641
- type: "text",
642
- text: "It left no closing message."
643
- }] : [{
644
- type: "text",
645
- text: "Its closing message:"
646
- }, ...closingText]],
512
+ content: [
513
+ {
514
+ type: "text",
515
+ text: summary
516
+ },
517
+ ...closingText.length === 0 ? [{
518
+ type: "text",
519
+ text: "It left no closing message."
520
+ }] : [{
521
+ type: "text",
522
+ text: "Its closing message:"
523
+ }, ...closingText],
524
+ ...terminal.structured !== void 0 ? [{
525
+ type: "text",
526
+ text: `Structured result: ${JSON.stringify(terminal.structured)}`
527
+ }] : [],
528
+ ...terminal.diagnostic !== void 0 ? [{
529
+ type: "text",
530
+ text: terminal.diagnostic
531
+ }] : []
532
+ ],
647
533
  source: {
648
534
  kind: "subagent-settled",
649
535
  form: "notice",
@@ -653,731 +539,107 @@ function createSettlementMessage(childId, terminal) {
653
539
  });
654
540
  }
655
541
  //#endregion
656
- //#region lib/types/inbox.js
542
+ //#region lib/types/descriptor.js
657
543
  /**
658
- * Activation-local admission around one continuable subagent's Agent inbox.
544
+ * The durable subagent-child descriptor: the versioned, model-hidden
545
+ * `subagent/descriptor` session event that identifies every session-backed
546
+ * subagent, including historical one-shot records. The manager appends each
547
+ * continuable child's descriptor inside its initial turn, preserving the
548
+ * declared composition required for cold resume.
549
+ *
550
+ * The descriptor deliberately snapshots explicit fields rather than the
551
+ * merge-extensible `AgentOptions` object: an unrelated extension value cannot
552
+ * make continuation fail merely because it is not JSON, and later composition
553
+ * inputs require a deliberate {@link SUBAGENT_DESCRIPTOR_VERSION} change. It
554
+ * omits `subagentDepth` — cold resume trusts the persisted header's
555
+ * `delegationDepth` as the monotone floor — and `outputSchema`, which belongs
556
+ * to one activation's result contract rather than durable child composition.
557
+ * Per-activation knobs such as `maxTokens` are omitted for the same reason as
558
+ * `outputSchema`: they budget one activation. Cold resume requires the exact
559
+ * live parent for authorization but reconstructs child options only from the
560
+ * durable descriptor, so it neither restores the prior budget nor inherits
561
+ * the parent's current one; the resumed route's defaults apply instead.
659
562
  *
660
- * @module @deepseek-ai/dsh-subagent/inbox
563
+ * @module @deepseek-ai/dsh-subagent/descriptor
661
564
  */
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
565
  /**
714
- * Process-local Activation ownership for continuable subagents: admission,
715
- * parent-child residency, serialized delivery, settlement, and disposal.
716
- *
717
- * The continuation manager owns durable request orchestration and delegates
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
566
+ * The current descriptor format version, stamped into every appended
567
+ * `subagent/descriptor` event and required verbatim by {@link foldSubagentDescriptor}.
568
+ * Supporting another composition input is a deliberate version change, never
569
+ * an implicit extra field.
722
570
  */
723
- /** Process-local slots shared through uninterrupted continuable parent links. */
724
- var ActivationPool = class {
725
- slots = /* @__PURE__ */ new Set();
726
- /** Reserve before reconstruction; the returned release also tolerates unpublished rollback. */
727
- reserve(capacity) {
728
- 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");
729
- const slot = Symbol();
730
- this.slots.add(slot);
731
- return () => {
732
- this.slots.delete(slot);
733
- };
734
- }
735
- };
736
- /** Serialize each durable child's delivery, release, and disposal. */
737
- var ChildLock = class {
738
- tails = /* @__PURE__ */ new Map();
739
- /**
740
- * Run `operation` after every previously queued operation for `childId`.
741
- * @param childId - the durable child whose operations are linearized.
742
- * @param operation - the critical section to run in order.
743
- * @returns the operation's own settlement.
744
- */
745
- run(childId, operation) {
746
- const result = (this.tails.get(childId) ?? Promise.resolve()).then(operation, operation);
747
- const tail = result.then(() => void 0, () => void 0);
748
- this.tails.set(childId, tail);
749
- tail.then(() => {
750
- if (this.tails.get(childId) === tail) this.tails.delete(childId);
751
- });
752
- return result;
753
- }
754
- };
755
- /** Own the complete process-local lifetime of continuable child Activations. */
756
- var ContinuableActivationRegistry = class {
757
- ctx;
758
- observeActivation;
759
- maxActiveSubagents;
760
- /** Child session id → its live Activation. Process-local, never durable. */
761
- resident = /* @__PURE__ */ new Map();
762
- /** Root identities retain their pool across child settlement without retaining dead roots. */
763
- rootPools = /* @__PURE__ */ new WeakMap();
764
- /** Materializations admitted before drain, tracked through publication or rollback. */
765
- materializations = /* @__PURE__ */ new Set();
766
- /** Per-child serializer shared by delivery, release, and disposal. */
767
- locks = new ChildLock();
768
- /** Structural Cordis owner of every Activation handle. */
769
- ownerCtx;
770
- /**
771
- * Exact roots whose host teardown has begun, with the live lineage members
772
- * observed under each root. Entries remain until that exact root leaves the
773
- * Agent registry, closing admission throughout its host's teardown without
774
- * poisoning a later same-id replacement.
775
- */
776
- closingScopes = /* @__PURE__ */ new Map();
777
- draining = false;
778
- /**
779
- * Build one registry inside the service's Agent-injected context.
780
- * @param ctx - context providing Agents, Sessions, and teardown ownership.
781
- * @param observeActivation - build the lifecycle observer for one residency epoch.
782
- */
783
- constructor(ctx, observeActivation, maxActiveSubagents) {
784
- this.ctx = ctx;
785
- this.observeActivation = observeActivation;
786
- this.maxActiveSubagents = maxActiveSubagents;
787
- const scope = ctx.plugin(function activationOwner() {});
788
- this.ownerCtx = scope.ctx;
789
- ctx.on("agent/disposed", ({ agent }) => {
790
- this.closingScopes.delete(agent);
791
- });
792
- ctx.effect(function* () {
793
- yield scope.dispose;
794
- yield () => this.drain();
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 } : {}
571
+ const SUBAGENT_DESCRIPTOR_VERSION = 3;
572
+ const DESCRIPTOR_BASE_KEYS = [
573
+ "version",
574
+ "mode",
575
+ "provider",
576
+ "label"
577
+ ];
578
+ const ONE_SHOT_DESCRIPTOR_KEYS = new Set(DESCRIPTOR_BASE_KEYS);
579
+ const CONTINUABLE_DESCRIPTOR_KEYS = new Set([
580
+ ...DESCRIPTOR_BASE_KEYS,
581
+ "agentProvider",
582
+ "agentModel",
583
+ "agentReasoningEffort",
584
+ "persona",
585
+ "toolFilter"
586
+ ]);
587
+ const TOOL_FILTER_KEYS = new Set(["allow", "deny"]);
588
+ /** Whether a persisted JSON value is an object record. */
589
+ function isRecord(value) {
590
+ return typeof value === "object" && value !== null && !Array.isArray(value);
591
+ }
592
+ /** Reject fields outside one versioned record's declared schema. */
593
+ function assertKnownKeys(value, keys, path) {
594
+ const unknown = Object.keys(value).find((key) => !keys.has(key));
595
+ if (unknown !== void 0) throw new Error(`persisted subagent descriptor ${path} has unknown field "${unknown}"`);
596
+ }
597
+ /** Read one optional string field from a persisted descriptor record. */
598
+ function optionalString(value, key) {
599
+ if (!Object.hasOwn(value, key)) return void 0;
600
+ const field = value[key];
601
+ if (typeof field !== "string") throw new Error(`persisted subagent descriptor ${key} must be a string`);
602
+ return field;
603
+ }
604
+ /** Read one optional string-array field from a persisted tool restriction. */
605
+ function optionalStringArray(value, key) {
606
+ if (!Object.hasOwn(value, key)) return void 0;
607
+ const field = value[key];
608
+ if (!Array.isArray(field)) throw new Error(`persisted subagent descriptor toolFilter.${key} must be an array of strings`);
609
+ const items = field;
610
+ if (items.some((item) => typeof item !== "string")) throw new Error(`persisted subagent descriptor toolFilter.${key} must be an array of strings`);
611
+ return items;
612
+ }
613
+ /** Validate and reconstruct a persisted tool restriction. */
614
+ function parseToolFilter(value) {
615
+ if (!isRecord(value)) throw new Error("persisted subagent descriptor toolFilter must be an object");
616
+ assertKnownKeys(value, TOOL_FILTER_KEYS, "toolFilter");
617
+ const allow = optionalStringArray(value, "allow");
618
+ const deny = optionalStringArray(value, "deny");
619
+ if (allow === void 0 && deny === void 0) throw new Error("persisted subagent descriptor toolFilter must declare allow and/or deny");
620
+ return {
621
+ ...allow !== void 0 ? { allow } : {},
622
+ ...deny !== void 0 ? { deny } : {}
623
+ };
624
+ }
625
+ /** Validate one persisted descriptor payload for the current runtime. */
626
+ function parseSubagentDescriptor(value) {
627
+ if (!isRecord(value)) throw new Error("persisted subagent descriptor payload must be an object");
628
+ const version = value["version"];
629
+ if (typeof version !== "number") throw new Error("persisted subagent descriptor version must be a number");
630
+ if (version !== 3) return void 0;
631
+ const mode = value["mode"];
632
+ if (mode !== "one-shot" && mode !== "continuable") throw new Error("persisted subagent descriptor mode must be \"one-shot\" or \"continuable\"");
633
+ assertKnownKeys(value, mode === "one-shot" ? ONE_SHOT_DESCRIPTOR_KEYS : CONTINUABLE_DESCRIPTOR_KEYS, "payload");
634
+ const provider = value["provider"];
635
+ if (typeof provider !== "string") throw new Error("persisted subagent descriptor provider must be a string");
636
+ if (mode === "one-shot") {
637
+ const label = optionalString(value, "label");
638
+ return {
639
+ version: 3,
640
+ mode,
641
+ provider,
642
+ ...label !== void 0 ? { label } : {}
1381
643
  };
1382
644
  }
1383
645
  const label = value["label"];
@@ -1399,13 +661,14 @@ function parseSubagentDescriptor(value) {
1399
661
  ...toolFilter !== void 0 ? { toolFilter } : {}
1400
662
  };
1401
663
  }
664
+ /**
665
+ * Validate and detach a continuable descriptor input.
666
+ * @param input - the caller-collected continuable composition fields.
667
+ * @returns the versioned, detached continuable descriptor payload.
668
+ * @throws when a field is not losslessly JSON-serializable.
669
+ */
1402
670
  function snapshotSubagentDescriptor(input) {
1403
- const snapshot = snapshotJsonValue(input.mode === "one-shot" ? {
1404
- version: 3,
1405
- mode: input.mode,
1406
- provider: input.provider,
1407
- ...input.label !== void 0 ? { label: input.label } : {}
1408
- } : {
671
+ const snapshot = snapshotJsonValue({
1409
672
  version: 3,
1410
673
  mode: input.mode,
1411
674
  provider: input.provider,
@@ -1451,12 +714,17 @@ const continuableCatalogSchema = z$1.object({
1451
714
  mode: z$1.literal("continuable"),
1452
715
  label: z$1.string()
1453
716
  }).strict();
717
+ const externalCatalogSchema = oneShotCatalogSchema.extend({
718
+ version: z$1.literal(2),
719
+ mode: z$1.literal("external")
720
+ });
1454
721
  const unknownCatalogSchema = oneShotCatalogSchema.extend({
1455
722
  version: z$1.literal(1),
1456
723
  mode: z$1.literal("unknown")
1457
724
  });
1458
725
  const eventDataSchema = z$1.union([
1459
726
  oneShotCatalogSchema,
727
+ externalCatalogSchema,
1460
728
  continuableCatalogSchema,
1461
729
  unknownCatalogSchema
1462
730
  ]);
@@ -1469,6 +737,14 @@ const viewSchema = z$1.array(z$1.union([
1469
737
  id: sessionIdSchema,
1470
738
  createdAt: oneShotCatalogSchema.shape.childCreatedAt
1471
739
  }),
740
+ externalCatalogSchema.omit({
741
+ version: true,
742
+ childId: true,
743
+ childCreatedAt: true
744
+ }).extend({
745
+ id: sessionIdSchema,
746
+ createdAt: externalCatalogSchema.shape.childCreatedAt
747
+ }),
1472
748
  continuableCatalogSchema.omit({
1473
749
  version: true,
1474
750
  childId: true,
@@ -1491,102 +767,383 @@ const stateSchema = z$1.object({
1491
767
  head: chunkedListSchema(eventDataSchema).optional()
1492
768
  }).strict();
1493
769
  /**
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.
770
+ * Materialize complete and unknown-mode child identities from parent catalog events.
771
+ * @param state - parent catalog fold state.
772
+ * @returns current direct-child rows in parent catalog event order.
773
+ */
774
+ function subagentCatalogEntries(state) {
775
+ const entries = [];
776
+ for (const data of iterateChunkedList(state.head)) {
777
+ const { version: _version, childId, childCreatedAt, ...descriptor } = data;
778
+ entries.push({
779
+ id: childId,
780
+ createdAt: childCreatedAt,
781
+ ...descriptor
782
+ });
783
+ }
784
+ return entries;
785
+ }
786
+ /** Parent-owned direct-child catalog projection; invalid own facts reject restoration. */
787
+ const subagentCatalogProjectionDefinition = {
788
+ key: "subagentCatalog",
789
+ stateSchema,
790
+ init: (_header, inheritedEventCount) => ({ inheritedEventCount }),
791
+ apply: (state, event) => {
792
+ if (event.type !== "subagent/catalog" || event.seq < state.inheritedEventCount) return state;
793
+ return {
794
+ ...state,
795
+ head: appendChunkedList(state.head, eventDataSchema.parse(event.data))
796
+ };
797
+ },
798
+ stateVersion: 6,
799
+ wire: {
800
+ viewSchema,
801
+ view: subagentCatalogEntries
802
+ }
803
+ };
804
+ /**
805
+ * Append a complete direct-child discovery fact to its parent Session.
806
+ * @param parent - durable direct parent receiving the discovery fact.
807
+ * @param child - established child's immutable Session metadata.
808
+ * @param descriptor - mode-discriminated creation label frozen with the child.
809
+ */
810
+ function establishCatalogChild(parent, child, descriptor) {
811
+ parent.append("subagent/catalog", {
812
+ version: 0,
813
+ childId: child.id,
814
+ childCreatedAt: child.createdAt,
815
+ mode: descriptor.mode,
816
+ label: descriptor.label
817
+ });
818
+ }
819
+ /** Catalog payload version emitted by external execution creation. */
820
+ const EXTERNAL_SUBAGENT_CATALOG_VERSION = 2;
821
+ /**
822
+ * Record an external execution without a local child Session.
823
+ * @param parent - durable direct parent receiving the discovery fact.
824
+ * @param childId - identity returned by the external provider.
825
+ * @param label - display label assigned by the caller.
826
+ */
827
+ function establishExternalCatalogChild(parent, childId, label) {
828
+ parent.append("subagent/catalog", {
829
+ version: EXTERNAL_SUBAGENT_CATALOG_VERSION,
830
+ childId,
831
+ childCreatedAt: Date.now(),
832
+ mode: "external",
833
+ label
834
+ });
835
+ }
836
+ //#endregion
837
+ //#region lib/types/internal.js
838
+ /**
839
+ * Continuation integration markers and host adapters outside the public
840
+ * Service Definition and model-facing Agent messaging contract.
841
+ * @module @deepseek-ai/dsh-subagent/internal
842
+ */
843
+ /** Process-stable identity carried only by the standard adjacent-Agent messaging tool. */
844
+ const adjacentAgentSendMessageTool = Symbol.for("dsh.subagent.adjacentAgentSendMessageTool");
845
+ /**
846
+ * Test whether one visible definition is the standard adjacent-Agent messaging tool.
847
+ * @param definition - the scope-resolved `send_message` candidate.
848
+ * @returns whether the definition carries the internal standard-tool identity.
849
+ */
850
+ function isAdjacentAgentSendMessageTool(definition) {
851
+ return definition !== void 0 && definition[adjacentAgentSendMessageTool] === true;
852
+ }
853
+ /**
854
+ * Process-stable symbol-keyed host delivery shared by the bundled runtime
855
+ * entry and this unbundled internal subpath.
856
+ * @internal
857
+ */
858
+ const deliverSubagentPrompt = Symbol.for("dsh.subagent.deliverPrompt");
859
+ //#endregion
860
+ //#region lib/types/structured.js
861
+ /**
862
+ * Child-scoped structured-output tool, prompt instruction, terminal guard, and authoritative
863
+ * result capture for in-process subagents. Each child registers its real schema on its own
864
+ * scope, so concurrent runs do not interact and disposal leaves no global residue. The prompt
865
+ * contribution is ordinary reconstructed request state.
866
+ *
867
+ * Capture commits only after the authoritative `tools/result` succeeds; PTC mode capture also
868
+ * waits for the enclosing `run_code` result. The terminal result marker and monotonic tool
869
+ * guard prevent later calls from reopening a completed structured run.
870
+ * @module @deepseek-ai/dsh-subagent/structured
871
+ */
872
+ /** The model-facing tool name a structured child must call to finish. */
873
+ const STRUCTURED_OUTPUT_TOOL = "structured_output";
874
+ /**
875
+ * The instruction registered as the child's trailing scoped prompt section:
876
+ * the demand travels with the
877
+ * tool, as ordinary prompt state of exactly one agent.
878
+ */
879
+ 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.`;
880
+ /**
881
+ * Attach the scoped capture tool, instruction, and enforcement to a child during
882
+ * its creation window. Child disposal removes every registration.
883
+ * @param childCtx - the child agent's scope context (`setup`'s argument).
884
+ * @param schema - the trusted, already-asserted schema subset to enforce (see
885
+ * `assertObjectJsonSchema` in dsh-tools).
886
+ * @param canComplete - whether accepted input and owned child work have finished.
887
+ * @returns the attachment handle (read `captured()` after the child settles).
888
+ */
889
+ function attachStructuredRuntime(childCtx, schema, canComplete) {
890
+ /**
891
+ * Validated values staged by the capture tool body, awaiting THEIR OWN
892
+ * authoritative `tools/result` notification. The execution object's identity
893
+ * uniquely identifies a trip through the pipeline: adapter call ids may
894
+ * repeat across steps, but another execution can never reach this Map
895
+ * entry. This is distinct from the opaque `ToolExecutionToken` used to
896
+ * correlate nested transports. The final notification always deletes its own
897
+ * stage, whether the result succeeded or failed.
898
+ */
899
+ const staged = /* @__PURE__ */ new Map();
900
+ /** Successful nested capture waiting for its enclosing transport to commit. */
901
+ let pending;
902
+ let captured;
903
+ const schemaEntry = {
904
+ name: STRUCTURED_OUTPUT_TOOL,
905
+ 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.",
906
+ parameters: { ...schema }
907
+ };
908
+ childCtx.tools.register({
909
+ ...schemaEntry,
910
+ output: {
911
+ schema: {
912
+ type: "object",
913
+ properties: { recorded: {
914
+ type: "boolean",
915
+ const: true
916
+ } },
917
+ required: ["recorded"],
918
+ additionalProperties: false
919
+ },
920
+ render: () => [{
921
+ type: "text",
922
+ text: "Structured output recorded."
923
+ }]
924
+ },
925
+ execute(args, exec) {
926
+ const violations = validateJsonSchemaValue(schema, args);
927
+ if (violations.length > 0) throw new ToolArgsError(violations);
928
+ if (!canComplete()) throw new Error("Wait for all delegated child tasks to finish and process pending messages before submitting structured output.");
929
+ staged.set(exec, { value: args });
930
+ exec.concludeTurn();
931
+ return Promise.resolve({ recorded: true });
932
+ }
933
+ });
934
+ childCtx.systemPrompt.section({
935
+ name: `tool:${STRUCTURED_OUTPUT_TOOL}`,
936
+ order: childCtx.systemPrompt.getSectionOrder("STRUCTURED_OUTPUT"),
937
+ text: STRUCTURED_OUTPUT_INSTRUCTION
938
+ });
939
+ 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`);
940
+ childCtx.on("tools/result", function(exec, result) {
941
+ if (exec.name === "structured_output") {
942
+ const entry = staged.get(exec);
943
+ if (entry === void 0) return;
944
+ staged.delete(exec);
945
+ if (result.isError) return;
946
+ if (exec.parent === void 0) {
947
+ /* v8 ignore else -- sequential agent-loop dispatch lets the guard block every later supported call */
948
+ if (captured === void 0) captured = { value: entry.value };
949
+ } else if (captured === void 0 && pending === void 0) pending = {
950
+ parent: exec.parent,
951
+ value: entry.value
952
+ };
953
+ return;
954
+ }
955
+ if (pending?.parent !== exec.token) return;
956
+ const entry = pending;
957
+ pending = void 0;
958
+ if (result.isError) return;
959
+ /* v8 ignore else -- PTC mode serializes outer executions, so the guard blocks every later supported call */
960
+ if (captured === void 0) captured = { value: entry.value };
961
+ });
962
+ return {
963
+ captured: () => captured,
964
+ acceptsInput: () => staged.size === 0 && pending === void 0 && captured === void 0
965
+ };
966
+ }
967
+ //#endregion
968
+ //#region lib/types/assistant-output.js
969
+ /**
970
+ * Canonical selection of a child's final assistant output. Backend run results
971
+ * and `subagent/end.lastAssistantMessage` apply the same rule: select the last
972
+ * non-empty assistant message. An empty-content message records usage only
973
+ * when the loop appends it after a max-tokens step with no executable blocks,
974
+ * so it does not replace earlier output. If no non-empty message exists,
975
+ * select the accumulated assistant text. Selection is independent of the
976
+ * run's stop reason.
977
+ *
978
+ * @module @deepseek-ai/dsh-subagent/assistant-output
979
+ */
980
+ /**
981
+ * Incremental fold of the selection rule, for backends that observe a child's
982
+ * output as it streams: session-event backends {@link push} each event, and
983
+ * transports without session events (ACP content chunks) {@link pushText} raw
984
+ * text into the same streamed fallback.
985
+ */
986
+ var AssistantOutputFold = class {
987
+ message;
988
+ partial = [];
989
+ /**
990
+ * Fold one session event: a non-empty assistant message becomes the
991
+ * candidate final answer, while its embedded stream and any log-only attempt
992
+ * extend the streamed fallback; every other event contributes nothing.
993
+ * @param event - the next observed session event.
994
+ */
995
+ push(event) {
996
+ if (event.type === "assistant/message") {
997
+ const content = event.data.message.content;
998
+ if (content.length > 0) this.message = content;
999
+ }
1000
+ if (event.type === "assistant/message" || event.type === "assistant/attempt") this.pushText(joinAssistantStreamText(event.data.stream));
1001
+ }
1002
+ /**
1003
+ * Extend the streamed fallback with text observed outside session events.
1004
+ * @param text - the next streamed text piece (an empty piece is a no-op).
1005
+ */
1006
+ pushText(text) {
1007
+ if (text.length > 0) this.partial.push(text);
1008
+ }
1009
+ /**
1010
+ * Select the final output folded so far.
1011
+ * @returns the last non-empty assistant message, else the accumulated
1012
+ * streamed text, or `undefined` when the child produced neither.
1013
+ */
1014
+ collect() {
1015
+ if (this.message !== void 0) return this.message;
1016
+ const text = this.partial.join("");
1017
+ return text.length > 0 ? [{
1018
+ type: "text",
1019
+ text
1020
+ }] : void 0;
1021
+ }
1022
+ };
1023
+ /**
1024
+ * Apply the selection rule to one complete child-owned event suffix.
1025
+ * @param events - the child-owned events (after any seed or epoch boundary).
1026
+ * @returns the selected output, or `undefined` when the child produced none.
1497
1027
  */
1498
- function subagentCatalogEntries(state) {
1499
- const entries = [];
1500
- for (const data of iterateChunkedList(state.head)) entries.push(data.mode !== "continuable" ? {
1501
- id: data.childId,
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;
1028
+ function finalAssistantOutput(events) {
1029
+ const fold = new AssistantOutputFold();
1030
+ for (const event of events) fold.push(event);
1031
+ return fold.collect();
1512
1032
  }
1513
- /** Parent-owned direct-child catalog projection; invalid own facts reject restoration. */
1514
- const subagentCatalogProjectionDefinition = {
1515
- key: "subagentCatalog",
1516
- stateSchema,
1517
- init: (_header, inheritedEventCount) => ({ inheritedEventCount }),
1518
- apply: (state, event) => {
1519
- if (event.type !== "subagent/catalog" || event.seq < state.inheritedEventCount) return state;
1520
- return {
1521
- ...state,
1522
- head: appendChunkedList(state.head, eventDataSchema.parse(event.data))
1033
+ //#endregion
1034
+ //#region lib/types/activation.js
1035
+ /** Execution-period records, capacity slots, child serialization, and local output capture.
1036
+ * @module @deepseek-ai/dsh-subagent/activation
1037
+ */
1038
+ /** Process-local slots shared through uninterrupted continuable parent links. */
1039
+ var ActivationPool = class {
1040
+ slots = /* @__PURE__ */ new Set();
1041
+ /**
1042
+ * Reserve before reconstruction; release also tolerates unpublished rollback.
1043
+ * @param capacity - the maximum simultaneous activations in this pool.
1044
+ * @returns an idempotent slot release.
1045
+ */
1046
+ reserve(capacity) {
1047
+ 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");
1048
+ const slot = Symbol();
1049
+ this.slots.add(slot);
1050
+ return () => {
1051
+ this.slots.delete(slot);
1523
1052
  };
1524
- },
1525
- stateVersion: 3,
1526
- wire: {
1527
- viewSchema,
1528
- view: subagentCatalogEntries
1529
1053
  }
1530
1054
  };
1531
1055
  /**
1532
- * Append a complete direct-child discovery fact to its parent Session.
1533
- * @param parent - durable direct parent receiving the discovery fact.
1534
- * @param child - established child's immutable Session metadata.
1535
- * @param descriptor - mode-discriminated creation label frozen with the child.
1056
+ * Read the live Agent retained by local execution.
1057
+ * @param activation - the managed local or external execution.
1058
+ * @returns the local Agent, absent for an external run.
1536
1059
  */
1537
- function establishCatalogChild(parent, child, descriptor) {
1538
- parent.append("subagent/catalog", descriptor.mode === "one-shot" ? {
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
- });
1060
+ function activationAgent(activation) {
1061
+ return activation.kind === "local" ? activation.handle.agent : void 0;
1551
1062
  }
1552
- //#endregion
1553
- //#region lib/types/internal.js
1554
1063
  /**
1555
- * Continuation integration markers and host adapters outside the public
1556
- * Service Definition and model-facing Agent messaging contract.
1557
- * @module @deepseek-ai/dsh-subagent/internal
1064
+ * Require local residency before accepting further input.
1065
+ * @param activation - execution targeted by message delivery.
1066
+ * @returns the local activation.
1067
+ * @throws when the external backend cannot accept another message.
1558
1068
  */
1559
- /** Process-stable identity carried only by the standard adjacent-Agent messaging tool. */
1560
- const adjacentAgentSendMessageTool = Symbol.for("dsh.subagent.adjacentAgentSendMessageTool");
1069
+ function requireLocalActivation(activation) {
1070
+ switch (activation.kind) {
1071
+ case "local": return activation;
1072
+ case "external": throw new SubagentError(`subagent "${activation.childId}" does not accept follow-up input`, "NOT_CONTINUABLE");
1073
+ /* v8 ignore next 2 -- Both execution kinds are handled above. */
1074
+ default: return assertNever(activation);
1075
+ }
1076
+ }
1561
1077
  /**
1562
- * Test whether one visible definition is the standard adjacent-Agent messaging tool.
1563
- * @param definition - the scope-resolved `send_message` candidate.
1564
- * @returns whether the definition carries the internal standard-tool identity.
1078
+ * Retain local failures whose turn ending could not be committed.
1079
+ * @param activation - exact local Agent and epoch receiving the scoped observers.
1080
+ * @param wake - notify its settlement watcher when failure changes readiness.
1565
1081
  */
1566
- function isAdjacentAgentSendMessageTool(definition) {
1567
- return definition !== void 0 && definition[adjacentAgentSendMessageTool] === true;
1082
+ function observeLocalFailure(activation, wake) {
1083
+ const child = activation.handle.agent;
1084
+ child.ctx.on("agent/error", ({ agent }) => {
1085
+ if (agent !== child) return;
1086
+ activation.failureAt = child.session.seq;
1087
+ wake();
1088
+ });
1089
+ child.ctx.on("session/event", (session, event) => {
1090
+ if (session === child.session && event.type === "turn/end" && activation.failureAt !== void 0 && event.seq >= activation.failureAt) activation.failureAt = void 0;
1091
+ });
1568
1092
  }
1569
1093
  /**
1570
- * Process-stable symbol-keyed host delivery shared by the bundled runtime
1571
- * entry and this unbundled internal subpath.
1572
- * @internal
1573
- */
1574
- const deliverSubagentPrompt = Symbol.for("dsh.subagent.deliverPrompt");
1094
+ * Capture local output and retain failures without a later committed turn ending.
1095
+ * @param activation - the settled local execution and any structured result.
1096
+ * @returns final output, stop reason, and committed structured value.
1097
+ */
1098
+ function captureLocalResult(activation) {
1099
+ const events = activation.handle.agent.session.snapshotEvents(activation.outputStart);
1100
+ const output = finalAssistantOutput(events) ?? [];
1101
+ const stopReason = activation.failureAt === void 0 ? epochStopReason(events) : "error";
1102
+ const structured = activation.structured;
1103
+ if (structured !== void 0) {
1104
+ const captured = structured.captured();
1105
+ if (captured !== void 0) return {
1106
+ output,
1107
+ stopReason,
1108
+ structured: captured.value
1109
+ };
1110
+ if (stopReason === "completed") return {
1111
+ output,
1112
+ stopReason: "error"
1113
+ };
1114
+ }
1115
+ return {
1116
+ output,
1117
+ stopReason
1118
+ };
1119
+ }
1120
+ /** Serialize each durable child's delivery, release, and disposal. */
1121
+ var ChildLock = class {
1122
+ tails = /* @__PURE__ */ new Map();
1123
+ /**
1124
+ * Run `operation` after every previously queued operation for `childId`.
1125
+ * @param childId - the durable child whose operations are linearized.
1126
+ * @param operation - the critical section to run in order.
1127
+ * @returns the operation's own settlement.
1128
+ */
1129
+ run(childId, operation) {
1130
+ const result = (this.tails.get(childId) ?? Promise.resolve()).then(operation, operation);
1131
+ const tail = result.then(() => void 0, () => void 0);
1132
+ this.tails.set(childId, tail);
1133
+ tail.then(() => {
1134
+ if (this.tails.get(childId) === tail) this.tails.delete(childId);
1135
+ });
1136
+ return result;
1137
+ }
1138
+ };
1575
1139
  //#endregion
1576
- //#region lib/types/continuation.js
1140
+ //#region lib/types/manager.js
1577
1141
  /**
1578
- * Continuable-subagent orchestration behind `ctx.subagents`: stable child ids,
1579
- * descriptor persistence, provider preparation, cold resume, authorization,
1580
- * and message routing. {@link ContinuableActivationRegistry} owns the mutable
1581
- * process-local Activation graph and its settlement and disposal lifecycle.
1582
- *
1583
- * A continuable child has one durable Session and at most one process-local
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.
1142
+ * Owns subagent startup, message admission, cold resume, and execution lifetimes.
1143
+ * One child lock orders input acceptance and closure; one activation map owns
1144
+ * capacity, parent relationships, and resource release across all providers.
1588
1145
  *
1589
- * @module @deepseek-ai/dsh-subagent
1146
+ * @module @deepseek-ai/dsh-subagent/manager
1590
1147
  */
1591
1148
  var __addDisposableResource$2 = function(env, value, async) {
1592
1149
  if (value !== null && value !== void 0) {
@@ -1646,20 +1203,183 @@ var __disposeResources$2 = (function(SuppressedError) {
1646
1203
  var e = new Error(message);
1647
1204
  return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
1648
1205
  });
1649
- /**
1650
- * The continuable-subagent orchestration service behind `ctx.subagents`. Tool
1651
- * schema and host adapters are consumers of this one contract; foreground
1652
- * one-shot delegation keeps calling `ctx.subagents.start()` and never enters
1653
- * this lifecycle.
1654
- */
1655
- var SubagentContinuationManager = class {
1206
+ /** Render only the safe outer message from execution or teardown failures. */
1207
+ function failureMessage(error) {
1208
+ return error instanceof Error ? error.message : "unknown teardown failure";
1209
+ }
1210
+ /** Own subagent requests and the resources of every live activation. */
1211
+ var SubagentManager = class {
1656
1212
  ctx;
1657
1213
  host;
1658
- activations;
1214
+ maxActiveSubagents;
1215
+ /** Child session id → its live Activation. Process-local, never durable. */
1216
+ resident = /* @__PURE__ */ new Map();
1217
+ /** Root identities retain their pool across child settlement without retaining dead roots. */
1218
+ rootPools = /* @__PURE__ */ new WeakMap();
1219
+ /** Materializations admitted before drain, tracked through publication or rollback. */
1220
+ materializations = /* @__PURE__ */ new Set();
1221
+ /** Per-child serializer shared by delivery, release, and disposal. */
1222
+ locks = new ChildLock();
1223
+ /** Structural Cordis owner of every Activation handle. */
1224
+ ownerCtx;
1225
+ /**
1226
+ * Exact roots whose host teardown has begun, with the live lineage members
1227
+ * observed under each root. Entries remain until that exact root leaves the
1228
+ * Agent registry, closing admission throughout its host's teardown without
1229
+ * poisoning a later same-id replacement.
1230
+ */
1231
+ closingScopes = /* @__PURE__ */ new Map();
1232
+ draining = false;
1233
+ /**
1234
+ * Build one manager inside the service's Agent-injected context.
1235
+ * @param ctx - context providing Agents, Sessions, and teardown ownership.
1236
+ * @param host - provider dispatch and lifecycle observation owned by the service.
1237
+ * @param maxActiveSubagents - the current configured child capacity.
1238
+ */
1659
1239
  constructor(ctx, host, maxActiveSubagents) {
1660
1240
  this.ctx = ctx;
1661
1241
  this.host = host;
1662
- this.activations = new ContinuableActivationRegistry(ctx, (provider, childId, parent) => host.observeActivation(provider, childId, parent), maxActiveSubagents);
1242
+ this.maxActiveSubagents = maxActiveSubagents;
1243
+ const scope = ctx.plugin(function activationOwner() {});
1244
+ this.ownerCtx = scope.ctx;
1245
+ ctx.on("agent/disposed", ({ agent }) => {
1246
+ this.closingScopes.delete(agent);
1247
+ });
1248
+ ctx.effect(function* () {
1249
+ yield scope.dispose;
1250
+ yield () => this.drain();
1251
+ }.bind(this), "subagents.manager()");
1252
+ }
1253
+ /**
1254
+ * Interrupt one live continuable child's current turn under the supplied authority.
1255
+ * @param targetSessionId - the durable child session id to interrupt.
1256
+ * @param authority - the human parent address or exact live ancestor Agent.
1257
+ */
1258
+ interrupt(targetSessionId, authority) {
1259
+ if (authority.kind === "ancestor") {
1260
+ const caller = authority.agent;
1261
+ if (this.ctx.agents.get(caller.id) !== caller) throw new SubagentError(`interrupting "${targetSessionId}" requires the exact live ancestor agent`, "UNAUTHORIZED");
1262
+ if (caller.id === targetSessionId) throw new SubagentError(`agent "${caller.id}" cannot interrupt itself`, "UNAUTHORIZED");
1263
+ }
1264
+ const activation = this.resident.get(targetSessionId);
1265
+ if (activation === void 0) return;
1266
+ if (authority.kind === "user") {
1267
+ if (activation.parent.id !== authority.parentSessionId) throw new SubagentError(`subagent "${targetSessionId}" belongs to another parent session`, "UNAUTHORIZED");
1268
+ } else if (!activation.ancestry.has(authority.agent)) throw new SubagentError(`subagent "${targetSessionId}" is not a live descendant of agent "${authority.agent.id}"`, "UNAUTHORIZED");
1269
+ if (activation.closing !== void 0) return;
1270
+ const kind = authority.kind === "user" ? "user" : "parent";
1271
+ if (activation.kind === "local") activation.handle.agent.cancel({ kind }, { keepInbox: true });
1272
+ else {
1273
+ activation.controller.abort({ kind });
1274
+ this.dispose(activation).catch(() => void 0);
1275
+ }
1276
+ }
1277
+ /**
1278
+ * Close admission, await every already-admitted materialization through
1279
+ * publication or rollback, then dispose the stable live Activation graph
1280
+ * child-first.
1281
+ */
1282
+ async drain() {
1283
+ this.draining = true;
1284
+ for (const pending of this.materializations) pending.controller.abort();
1285
+ await Promise.all([...this.materializations].map((materialization) => materialization.settled));
1286
+ const owned = /* @__PURE__ */ new Set();
1287
+ for (const activation of this.resident.values()) for (const child of activation.ownedChildren) owned.add(child);
1288
+ const roots = [...this.resident.values()].filter((activation) => !owned.has(activation.childId));
1289
+ await this.disposeRoots(roots, "activation(s)");
1290
+ }
1291
+ /**
1292
+ * Join progressing child work, leaving idle parked inboxes resident.
1293
+ * @param parent - exact live parent whose descendants are observed.
1294
+ * @returns whether work was joined and the host must recheck parent completion.
1295
+ */
1296
+ async waitForChildren(parent) {
1297
+ let observed = false;
1298
+ while (true) {
1299
+ const pending = [...this.materializations].filter((item) => item.lineage.includes(parent));
1300
+ const children = [...this.resident.values()].filter((item) => activationAgent(item) !== parent && item.ancestry.has(parent));
1301
+ observed ||= children.some((item) => item.kind === "local" && item.handle.agent.status !== "idle");
1302
+ await Promise.all(children.flatMap((item) => item.kind === "local" ? [item.handle.agent.whenIdle()] : []));
1303
+ const waits = pending.map((item) => item.settled);
1304
+ for (const item of children) {
1305
+ if (item.kind === "external" || item.closing !== void 0) {
1306
+ waits.push(item.result.promise.catch(() => void 0));
1307
+ continue;
1308
+ }
1309
+ if (item.handle.agent.status !== "idle") {
1310
+ waits.push(item.handle.agent.whenIdle());
1311
+ continue;
1312
+ }
1313
+ if (item.ownedChildren.size > 0) {
1314
+ if ([...item.ownedChildren].some((id) => !this.resident.has(id))) waits.push(item.poke.promise);
1315
+ continue;
1316
+ }
1317
+ const inbox = item.handle.agent.inbox;
1318
+ if (item.failureAt === void 0 && (inbox.nextTurn.length > 0 || inbox.nextStep.length > 0)) continue;
1319
+ waits.push(Promise.race([item.result.promise.catch(() => void 0), item.poke.promise]));
1320
+ }
1321
+ if (waits.length > 0) {
1322
+ observed = true;
1323
+ await Promise.all(waits);
1324
+ continue;
1325
+ }
1326
+ 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;
1327
+ return observed;
1328
+ }
1329
+ }
1330
+ /**
1331
+ * Stop the managed descendants of exact live host-owned parents.
1332
+ * @param parents - exact live roots whose managed descendants must stop.
1333
+ */
1334
+ async drainDescendants(parents) {
1335
+ const roots = new Set(parents.filter((parent) => this.ctx.agents.get(parent.id) === parent));
1336
+ if (roots.size === 0) return;
1337
+ for (const root of roots) this.closingMembers(root).add(root);
1338
+ const targets = [];
1339
+ for (const activation of this.resident.values()) {
1340
+ const child = activationAgent(activation);
1341
+ const lineage = this.liveLineage(child ?? activation.parent);
1342
+ const owners = [...roots].filter((root) => child !== root && activation.ancestry.has(root));
1343
+ if (owners.length === 0) continue;
1344
+ targets.push(activation);
1345
+ for (const owner of owners) {
1346
+ const members = this.closingMembers(owner);
1347
+ if (child !== void 0) members.add(child);
1348
+ for (const agent of lineage) members.add(agent);
1349
+ }
1350
+ }
1351
+ const materializations = [...this.materializations].filter((materialization) => {
1352
+ const owners = [...roots].filter((root) => materialization.lineage.includes(root));
1353
+ for (const owner of owners) {
1354
+ const members = this.closingMembers(owner);
1355
+ for (const agent of materialization.lineage) members.add(agent);
1356
+ }
1357
+ if (owners.length > 0) materialization.controller.abort();
1358
+ return owners.length > 0;
1359
+ });
1360
+ const ownedTargets = /* @__PURE__ */ new Set();
1361
+ for (const activation of targets) for (const child of activation.ownedChildren) ownedTargets.add(child);
1362
+ const targetRoots = targets.filter((activation) => !ownedTargets.has(activation.childId));
1363
+ for (const activation of targets) this.dispose(activation).catch(() => void 0);
1364
+ await Promise.all(materializations.map((materialization) => materialization.settled));
1365
+ await this.disposeRoots(targetRoots, "scoped activation(s)");
1366
+ }
1367
+ /**
1368
+ * Release selected resident direct children of one exact live parent.
1369
+ * @param parent - exact live direct parent authorizing the selected release.
1370
+ * @param childIds - durable direct-child ids to release when resident.
1371
+ */
1372
+ async drainChildren(parent, childIds) {
1373
+ if (this.ctx.agents.get(parent.id) !== parent) throw new SubagentError("selected child teardown requires the exact live parent agent", "UNAUTHORIZED");
1374
+ const targets = [];
1375
+ for (const childId of new Set(childIds)) {
1376
+ const activation = this.resident.get(childId);
1377
+ if (activation === void 0) continue;
1378
+ 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");
1379
+ targets.push(activation);
1380
+ }
1381
+ for (const activation of targets) this.dispose(activation).catch(() => void 0);
1382
+ await this.disposeRoots(targets, "selected activation(s)");
1663
1383
  }
1664
1384
  /**
1665
1385
  * Start one continuable background child and resolve at initial inbox acceptance.
@@ -1668,14 +1388,14 @@ var SubagentContinuationManager = class {
1668
1388
  * @param spec - provider, delegation request, and caller cancellation.
1669
1389
  * @returns the durable child id and accepted initial prompt message id.
1670
1390
  */
1671
- async startContinuable(spec) {
1391
+ async startLocal(spec) {
1672
1392
  const request = spec.request;
1673
1393
  const parent = request.parent;
1674
- this.activations.assertAdmitting(parent);
1675
- const persistence = this.requirePersistence();
1394
+ this.assertAdmitting(parent);
1395
+ const persistence = spec.delivery === "caller" ? this.ctx.get("sessionPersistence") : this.requirePersistence();
1676
1396
  assertSubagentMaxDepth(request.maxDepth);
1677
1397
  const childId = spec.childId ?? brandString(randomUUID());
1678
- this.activations.assertChildIdAvailable(childId);
1398
+ this.assertChildIdAvailable(childId);
1679
1399
  const childDepth = resolveChildDepth(parent, request.maxDepth);
1680
1400
  const agentOptions = resolveChildAgentOptions(parent, request.agentOptions, childDepth);
1681
1401
  const agentProvider = agentOptions.provider;
@@ -1687,128 +1407,679 @@ var SubagentContinuationManager = class {
1687
1407
  label: spec.label,
1688
1408
  ...agentProvider !== void 0 ? { agentProvider } : {},
1689
1409
  ...agentModel !== void 0 ? { agentModel } : {},
1690
- ...agentReasoningEffort !== void 0 ? { agentReasoningEffort } : {},
1691
- ...request.persona !== void 0 ? { persona: request.persona } : {},
1692
- ...request.toolFilter !== void 0 ? { toolFilter: request.toolFilter } : {}
1410
+ ...agentReasoningEffort !== void 0 ? { agentReasoningEffort } : {}
1693
1411
  });
1694
1412
  const delegatedPolicies = captureDelegatedPolicyOverrides(parent);
1695
- const releaseHold = this.activations.holdOwnership(parent, childId);
1413
+ const releaseHold = this.holdOwnership(parent, childId);
1696
1414
  try {
1415
+ const cwd = await this.resolveDirectory(request, spec.signal);
1697
1416
  const prepared = await this.host.prepareContinuable(spec.provider, {
1698
1417
  sessionId: childId,
1418
+ cwd,
1699
1419
  parent,
1700
1420
  signal: spec.signal
1701
1421
  });
1702
1422
  spec.signal.throwIfAborted();
1703
- this.activations.assertAdmitting(parent);
1423
+ this.assertAdmitting(parent);
1704
1424
  const inheritedEventCount = SessionLogOffset(prepared.seed?.length ?? 0);
1705
1425
  const seed = prepared.seed;
1426
+ let established;
1427
+ const messageId = await this.locks.run(childId, async () => {
1428
+ spec.signal.throwIfAborted();
1429
+ this.assertAdmitting(parent);
1430
+ this.assertChildIdAvailable(childId);
1431
+ if (spec.childId !== void 0) {
1432
+ const persisted = await persistence?.stat(childId, { signal: spec.signal });
1433
+ spec.signal.throwIfAborted();
1434
+ this.assertAdmitting(parent);
1435
+ this.assertChildIdAvailable(childId);
1436
+ if (persisted !== void 0) throw new SubagentError(`subagent "${childId}" already exists`, "DUPLICATE_CHILD");
1437
+ }
1438
+ const activation = await this.materialize({
1439
+ kind: "local",
1440
+ childId,
1441
+ provider: spec.provider,
1442
+ parent,
1443
+ create: {
1444
+ cwd,
1445
+ seed,
1446
+ meta: childSessionMeta(parent, childDepth, prepared.seed !== void 0, request.agentPreset),
1447
+ inheritedEventCount,
1448
+ delegatedPolicies,
1449
+ descriptor
1450
+ },
1451
+ agentOptions,
1452
+ composition: { agentPreset: request.agentPreset },
1453
+ signal: spec.signal,
1454
+ delivery: spec.delivery,
1455
+ outputSchema: spec.request.outputSchema
1456
+ });
1457
+ established = activation;
1458
+ const child = requireLocalActivation(activation).handle.agent;
1459
+ const childHeader = child.session.header;
1460
+ return await this.submitMaterialized(activation, spec.delivery !== "caller" && isAdjacentAgentSendMessageTool(this.ctx.get("tools")?.get("send_message", child)) ? withContinuableReturnGuidance(parent.id, request.prompt) : request.prompt, {
1461
+ source: { kind: "user" },
1462
+ signal: spec.signal,
1463
+ delivery: "queue"
1464
+ }, parent, () => {
1465
+ establishCatalogChild(parent.session, childHeader, descriptor);
1466
+ });
1467
+ });
1706
1468
  return {
1469
+ ...this.receipt(established),
1707
1470
  childId,
1708
- messageId: await this.activations.locks.run(childId, async () => {
1709
- spec.signal.throwIfAborted();
1710
- this.activations.assertAdmitting(parent);
1711
- this.activations.assertChildIdAvailable(childId);
1712
- if (spec.childId !== void 0) {
1713
- const persisted = await persistence.stat(childId, { signal: spec.signal });
1714
- spec.signal.throwIfAborted();
1715
- this.activations.assertAdmitting(parent);
1716
- this.activations.assertChildIdAvailable(childId);
1717
- if (persisted !== void 0) throw new SubagentError(`subagent "${childId}" already exists`, "DUPLICATE_CHILD");
1718
- }
1719
- const activation = await this.activations.materialize({
1720
- childId,
1721
- provider: spec.provider,
1722
- parent,
1723
- create: {
1724
- seed,
1725
- meta: childSessionMeta(parent, childDepth, prepared.seed !== void 0),
1726
- inheritedEventCount,
1727
- delegatedPolicies,
1728
- descriptor
1729
- },
1730
- agentOptions,
1731
- composition: {
1732
- persona: request.persona,
1733
- toolFilter: request.toolFilter
1734
- },
1735
- signal: spec.signal
1736
- });
1737
- const childHeader = activation.handle.agent.session.header;
1738
- return await this.submitMaterialized(activation, isAdjacentAgentSendMessageTool(this.ctx.get("tools")?.get("send_message", activation.handle.agent)) ? withContinuableReturnGuidance(parent.id, request.prompt) : request.prompt, {
1739
- source: { kind: "user" },
1740
- signal: spec.signal,
1741
- delivery: "queue"
1742
- }, parent, () => {
1743
- establishCatalogChild(parent.session, childHeader, descriptor);
1744
- });
1471
+ messageId
1472
+ };
1473
+ } catch (error) {
1474
+ releaseHold();
1475
+ throw error;
1476
+ }
1477
+ }
1478
+ /**
1479
+ * Start an external backend under activation ownership.
1480
+ * @param spec - task, provider, cancellation, and delivery policy.
1481
+ * @returns the accepted activation and its result/disposal capabilities.
1482
+ */
1483
+ async startExternal(spec) {
1484
+ const parent = spec.request.parent;
1485
+ this.assertAdmitting(parent);
1486
+ const pendingId = brandString(randomUUID());
1487
+ const releaseHold = this.holdOwnership(parent, pendingId);
1488
+ let activation;
1489
+ try {
1490
+ const cwd = await this.resolveDirectory(spec.request, spec.signal);
1491
+ activation = await this.materialize({
1492
+ kind: "external",
1493
+ childId: pendingId,
1494
+ provider: spec.provider,
1495
+ parent,
1496
+ signal: spec.signal,
1497
+ delivery: spec.delivery,
1498
+ start: (signal) => this.host.startExternal(spec.provider, {
1499
+ ...spec.request,
1500
+ cwd,
1501
+ label: spec.label,
1502
+ signal
1745
1503
  })
1504
+ });
1505
+ this.assertAdmitting(parent);
1506
+ this.assertLiveParent(parent, activation.childId);
1507
+ spec.signal.throwIfAborted();
1508
+ if (spec.delivery === "parent") establishExternalCatalogChild(parent.session, activation.childId, spec.label);
1509
+ this.announce(activation);
1510
+ return this.receipt(activation);
1511
+ } catch (error) {
1512
+ if (activation !== void 0) try {
1513
+ await this.dispose(activation);
1514
+ } catch (cleanupError) {
1515
+ this.ctx.logger.warn(`subagent "${activation.childId}" admission rollback failed: ${failureMessage(cleanupError)}`);
1516
+ }
1517
+ throw error;
1518
+ } finally {
1519
+ releaseHold();
1520
+ }
1521
+ }
1522
+ /** Capture the explicit or inherited directory while the parent still owns startup. */
1523
+ async resolveDirectory(request, signal) {
1524
+ const cwd = request.cwd !== void 0 && isAbsolute(request.cwd) ? request.cwd : resolve(await this.ctx.workingDirectory.ensure(request.parent, signal), request.cwd ?? ".");
1525
+ signal.throwIfAborted();
1526
+ return cwd;
1527
+ }
1528
+ /**
1529
+ * Deliver one model-authored message to a direct continuable child or to the
1530
+ * sender's direct parent. A missing direct child cold-resumes through the
1531
+ * ordinary continuation lifecycle.
1532
+ * @param sender - exact live Agent authorizing and originating the message.
1533
+ * @param targetId - durable direct-parent or direct-child session id.
1534
+ * @param content - model-authored content to deliver.
1535
+ * @param options - caller cancellation before acceptance.
1536
+ * @returns the accepted message's inbox id.
1537
+ */
1538
+ async sendMessage(sender, targetId, content, options) {
1539
+ if (this.ctx.agents.get(sender.id) !== sender) throw new SubagentError("message delivery requires the exact live sender agent", "UNAUTHORIZED");
1540
+ this.assertAdmitting(sender);
1541
+ const senderActivation = this.resident.get(sender.id);
1542
+ if (senderActivation !== void 0 && senderActivation.kind === "local" && senderActivation.handle.agent === sender && senderActivation.parent.id === targetId) {
1543
+ options.signal.throwIfAborted();
1544
+ return this.sendToParent(senderActivation, sender, content);
1545
+ }
1546
+ 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");
1547
+ return this.deliverToChild(sender, targetId, content, {
1548
+ signal: options.signal,
1549
+ delivery: "steer"
1550
+ });
1551
+ }
1552
+ /**
1553
+ * Queue one human-authored prompt as a distinct direct-child turn.
1554
+ * @param parent - exact live direct parent authorizing delivery.
1555
+ * @param childId - durable direct-child session id.
1556
+ * @param content - model-visible prompt blocks.
1557
+ * @param source - durable attribution for the human prompt.
1558
+ * @param signal - caller cancellation before inbox acceptance.
1559
+ * @returns the accepted durable message id.
1560
+ */
1561
+ async queuePrompt(parent, childId, content, source, signal) {
1562
+ return this.deliverToChild(parent, childId, content, {
1563
+ source,
1564
+ signal,
1565
+ delivery: "queue"
1566
+ });
1567
+ }
1568
+ /**
1569
+ * Steer one host-authored prompt to a direct continuable child.
1570
+ * @param parent - exact live direct parent authorizing delivery.
1571
+ * @param childId - durable direct-child session id.
1572
+ * @param content - model-visible prompt blocks.
1573
+ * @param source - durable attribution for the host prompt.
1574
+ * @param signal - caller cancellation before inbox acceptance.
1575
+ * @returns the accepted durable message id.
1576
+ */
1577
+ async steerPrompt(parent, childId, content, source, signal) {
1578
+ return this.deliverToChild(parent, childId, content, {
1579
+ source,
1580
+ signal,
1581
+ delivery: "steer"
1582
+ });
1583
+ }
1584
+ /** Admit local input synchronously before any teardown can begin. */
1585
+ deliver(activation, message, delivery) {
1586
+ if (activation.closing !== void 0) throw new SubagentError("subagent activation is being disposed; the message was not accepted", "ACTIVATION_CLOSING");
1587
+ const local = requireLocalActivation(activation);
1588
+ if (local.structured?.acceptsInput() === false) throw new SubagentError("subagent is submitting or has submitted its structured result; the message was not accepted", "INPUT_CLOSED");
1589
+ if (delivery === "steer") local.handle.agent.steer(message);
1590
+ else local.handle.agent.followup(message);
1591
+ }
1592
+ /**
1593
+ * Reject one child identity already owned by a live Agent or Session.
1594
+ * @param childId - proposed durable child session id.
1595
+ */
1596
+ assertChildIdAvailable(childId) {
1597
+ 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");
1598
+ }
1599
+ /**
1600
+ * Pre-register `childId` in a continuation-managed parent's owned set so the
1601
+ * parent cannot settle while a caller is still establishing or resuming that
1602
+ * child. Returns a releaser for the failure path; it removes only a hold
1603
+ * this call added, and leaves ownership in place once a live Activation for
1604
+ * the child exists.
1605
+ * @param parent - the live direct parent the operation is admitted under.
1606
+ * @param childId - the durable child the operation addresses.
1607
+ * @returns the failure-path releaser; a no-op when nothing was added.
1608
+ */
1609
+ holdOwnership(parent, childId) {
1610
+ const parentActivation = this.resident.get(parent.id);
1611
+ if (parentActivation === void 0 || activationAgent(parentActivation) !== parent) return () => {};
1612
+ if (parentActivation.closing !== void 0) throw new SubagentError(`subagent parent "${parent.id}" is being disposed; the child was not established`, "ACTIVATION_CLOSING");
1613
+ if (parentActivation.ownedChildren.has(childId)) return () => {};
1614
+ parentActivation.ownedChildren.add(childId);
1615
+ return () => {
1616
+ const live = this.resident.get(childId);
1617
+ /* v8 ignore next 4 -- reaching this arm needs another delivery to establish the child
1618
+ * between this operation's failure and its releaser running, which no test can schedule
1619
+ * deterministically: the ownership edge then belongs to that live Activation, so the
1620
+ * conservative keep leaves it for finishDisposal's releaseOwnership. */
1621
+ if (live !== void 0 && live.closing === void 0) return;
1622
+ if (parentActivation.ownedChildren.delete(childId)) this.wake(parentActivation);
1623
+ };
1624
+ }
1625
+ /**
1626
+ * Apply activation admission to a resident parent before delivering input.
1627
+ * @param parent - exact live Agent receiving the message.
1628
+ * @param message - durable user message to deliver.
1629
+ * @param delivery - receiving inbox destination.
1630
+ */
1631
+ sendWaking(parent, message, delivery) {
1632
+ const parentActivation = this.resident.get(parent.id);
1633
+ if (parentActivation !== void 0 && activationAgent(parentActivation) === parent) {
1634
+ try {
1635
+ this.deliver(parentActivation, message, delivery);
1636
+ } finally {
1637
+ this.wake(parentActivation);
1638
+ }
1639
+ return;
1640
+ }
1641
+ if (delivery === "steer") parent.steer(message);
1642
+ else parent.followup(message);
1643
+ }
1644
+ /**
1645
+ * Reject new admission once the manager or this exact parent tree began draining.
1646
+ * @param agent - exact live Agent whose lineage determines admission.
1647
+ */
1648
+ assertAdmitting(agent) {
1649
+ const closing = this.closingTeardownFor(agent);
1650
+ if (closing === void 0) return;
1651
+ 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");
1652
+ }
1653
+ /**
1654
+ * Authorize one operation against the durable direct-parent lineage.
1655
+ * @param parent - exact live Agent claiming direct-parent authority.
1656
+ * @param childId - durable child session id addressed by the operation.
1657
+ * @param parentSession - durable direct-parent id recorded by the child.
1658
+ */
1659
+ authorizeLineage(parent, childId, parentSession) {
1660
+ this.assertLiveParent(parent, childId);
1661
+ if (parentSession !== parent.id) throw new SubagentError(`subagent "${childId}" belongs to another parent session`, "UNAUTHORIZED");
1662
+ }
1663
+ /** Reject parent authority after the live registry entry changes. */
1664
+ assertLiveParent(parent, childId) {
1665
+ if (this.ctx.agents.get(parent.id) !== parent) throw new SubagentError(`subagent "${childId}" delivery requires the exact live parent agent`, "UNAUTHORIZED");
1666
+ }
1667
+ /**
1668
+ * Create or resume one child Agent and publish its Activation.
1669
+ * @param inputs - reconstruction and admission inputs for the residency epoch.
1670
+ * @returns the published process-local Activation.
1671
+ */
1672
+ materialize(inputs) {
1673
+ this.assertAdmitting(inputs.parent);
1674
+ inputs.signal.throwIfAborted();
1675
+ const lineage = this.liveLineage(inputs.parent);
1676
+ const pool = this.resident.get(inputs.parent.id)?.pool ?? this.rootPool(inputs.parent);
1677
+ const releaseSlot = pool.reserve(this.maxActiveSubagents());
1678
+ const settled = Promise.withResolvers();
1679
+ const controller = new AbortController();
1680
+ const materialization = {
1681
+ lineage,
1682
+ settled: settled.promise,
1683
+ controller
1684
+ };
1685
+ this.materializations.add(materialization);
1686
+ return this.materializeTracked({
1687
+ ...inputs,
1688
+ signal: AbortSignal.any([inputs.signal, controller.signal])
1689
+ }, lineage, pool, releaseSlot).catch((error) => {
1690
+ releaseSlot();
1691
+ if (error instanceof Error && error.name === "AbortError") this.assertAdmitting(inputs.parent);
1692
+ throw error;
1693
+ }).finally(() => {
1694
+ this.materializations.delete(materialization);
1695
+ settled.resolve();
1696
+ });
1697
+ }
1698
+ /**
1699
+ * Stop and release one Activation through its memoized close transaction.
1700
+ * @param activation - exact residency epoch to close.
1701
+ * @returns the shared close transaction.
1702
+ */
1703
+ dispose(activation) {
1704
+ return this.close(activation, () => this.finishDisposal(activation, true));
1705
+ }
1706
+ /** Close admission synchronously and share one release, including startup rollback. */
1707
+ close(activation, release) {
1708
+ if (activation.closing !== void 0) return activation.closing;
1709
+ const completion = Promise.withResolvers();
1710
+ activation.closing = completion.promise;
1711
+ release().then(completion.resolve, completion.reject);
1712
+ return completion.promise;
1713
+ }
1714
+ /** Dispose independent roots and report every branch failure after all settle. */
1715
+ async disposeRoots(roots, failureSubject) {
1716
+ const reasons = (await Promise.all(roots.map(async (activation) => {
1717
+ try {
1718
+ await this.dispose(activation);
1719
+ return;
1720
+ } catch (error) {
1721
+ return error;
1722
+ }
1723
+ }))).filter((failure) => failure !== void 0);
1724
+ if (reasons.length > 0) throw new SubagentError(`subagent teardown failed for ${reasons.length} ${failureSubject}: ` + reasons.map((reason) => failureMessage(reason)).join("; "), "ACTIVATION_TEARDOWN_FAILED");
1725
+ }
1726
+ /** Return the retained member set for one exact scoped-teardown root. */
1727
+ closingMembers(root) {
1728
+ const existing = this.closingScopes.get(root);
1729
+ if (existing !== void 0) return existing;
1730
+ const members = /* @__PURE__ */ new Set();
1731
+ this.closingScopes.set(root, members);
1732
+ return members;
1733
+ }
1734
+ /** Return the exact currently resolvable ancestry from `agent` upward. */
1735
+ liveLineage(agent) {
1736
+ const lineage = [agent];
1737
+ const seen = new Set([agent.id]);
1738
+ let parentSession = agent.session.header.parentSession;
1739
+ while (parentSession !== void 0) {
1740
+ const parent = this.ctx.agents.get(parentSession);
1741
+ if (parent === void 0 || seen.has(parent.id)) break;
1742
+ lineage.push(parent);
1743
+ seen.add(parent.id);
1744
+ parentSession = parent.session.header.parentSession;
1745
+ }
1746
+ return lineage;
1747
+ }
1748
+ /** Return the teardown that closed continuable admission for this agent's lineage. */
1749
+ closingTeardownFor(agent) {
1750
+ if (this.draining) return "manager";
1751
+ const lineage = this.liveLineage(agent);
1752
+ for (const [root, members] of this.closingScopes) if (members.has(agent) || lineage.includes(root)) return root;
1753
+ }
1754
+ /** Resolve a root's pool once; descendants inherit their resident parent's pool directly. */
1755
+ rootPool(parent) {
1756
+ let pool = this.rootPools.get(parent);
1757
+ if (pool === void 0) {
1758
+ pool = new ActivationPool();
1759
+ this.rootPools.set(parent, pool);
1760
+ }
1761
+ return pool;
1762
+ }
1763
+ /** Perform one tracked materialization through publication or rollback. */
1764
+ async materializeTracked(inputs, parentLineage, pool, releaseSlot) {
1765
+ const { provider, parent } = inputs;
1766
+ let childId = inputs.childId;
1767
+ inputs.signal.throwIfAborted();
1768
+ let resource;
1769
+ if (inputs.kind === "external") {
1770
+ const controller = new AbortController();
1771
+ const cancelStartup = () => {
1772
+ controller.abort(inputs.signal.reason);
1773
+ };
1774
+ inputs.signal.addEventListener("abort", cancelStartup, { once: true });
1775
+ try {
1776
+ const run = await inputs.start(controller.signal);
1777
+ resource = {
1778
+ kind: "external",
1779
+ run,
1780
+ controller
1781
+ };
1782
+ run.result.catch(() => void 0);
1783
+ childId = run.id;
1784
+ try {
1785
+ this.assertChildIdAvailable(childId);
1786
+ } catch (error) {
1787
+ await run.dispose();
1788
+ throw error;
1789
+ }
1790
+ } finally {
1791
+ inputs.signal.removeEventListener("abort", cancelStartup);
1792
+ }
1793
+ } else {
1794
+ const { create } = inputs;
1795
+ let structured;
1796
+ const setup = async (childCtx, child) => {
1797
+ if (inputs.composition.agentPreset !== void 0) await applyChildComposition(childCtx, parent, inputs.composition);
1798
+ if (create !== void 0) {
1799
+ if (inputs.composition.agentPreset !== void 0) child.session.append("agent-preset/selected", { agentPreset: inputs.composition.agentPreset });
1800
+ child.session.append("subagent/descriptor", create.descriptor);
1801
+ appendDelegatedPolicyOverrides(child.session, create.delegatedPolicies);
1802
+ const workingDirectory = childCtx.get("workingDirectory");
1803
+ if (workingDirectory === void 0) throw new Error("local subagents require the working-directory service");
1804
+ await workingDirectory.set(child, create.cwd, inputs.signal);
1805
+ }
1806
+ if (inputs.composition.agentPreset === void 0) await applyChildComposition(childCtx, parent, inputs.composition);
1807
+ 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);
1808
+ };
1809
+ const handle = create === void 0 ? await this.ownerCtx.agents.resume({
1810
+ resumeSessionId: childId,
1811
+ parentAgent: parent,
1812
+ agentOptions: inputs.agentOptions,
1813
+ signal: inputs.signal,
1814
+ setup
1815
+ }) : await this.ownerCtx.agents.create({
1816
+ sessionId: childId,
1817
+ parentAgent: parent,
1818
+ meta: create.meta,
1819
+ ...create.seed === void 0 ? {} : { seed: create.seed },
1820
+ inheritedEventCount: create.inheritedEventCount,
1821
+ agentOptions: inputs.agentOptions,
1822
+ signal: inputs.signal,
1823
+ setup
1824
+ });
1825
+ resource = {
1826
+ kind: "local",
1827
+ handle,
1828
+ outputStart: handle.agent.session.seq,
1829
+ structured,
1830
+ failureAt: void 0,
1831
+ poke: Promise.withResolvers()
1832
+ };
1833
+ }
1834
+ const observer = this.host.observeActivation(provider, childId, parent);
1835
+ const activation = {
1836
+ pool,
1837
+ releaseSlot,
1838
+ childId,
1839
+ ...resource,
1840
+ parent,
1841
+ delivery: inputs.delivery ?? "parent",
1842
+ result: Promise.withResolvers(),
1843
+ closing: void 0,
1844
+ ancestry: new WeakSet(resource.kind === "external" ? parentLineage : [resource.handle.agent, ...parentLineage]),
1845
+ ownedChildren: /* @__PURE__ */ new Set(),
1846
+ observer,
1847
+ announced: false
1848
+ };
1849
+ activation.result.promise.catch(() => void 0);
1850
+ this.resident.set(childId, activation);
1851
+ try {
1852
+ this.assertAdmitting(parent);
1853
+ inputs.signal.throwIfAborted();
1854
+ if (this.ctx.agents.get(parent.id) !== parent) throw new SubagentError("subagent parent is no longer live", "UNAUTHORIZED");
1855
+ this.acquireOwnership(parent, childId);
1856
+ const wakeSettlement = () => {
1857
+ this.wake(activation);
1746
1858
  };
1859
+ if (activation.kind === "local") {
1860
+ observeLocalFailure(activation, wakeSettlement);
1861
+ activation.handle.agent.ctx.on("agent/inbox/inserted", wakeSettlement);
1862
+ activation.handle.agent.ctx.on("agent/inbox/claimed", wakeSettlement);
1863
+ activation.handle.agent.ctx.on("agent/inbox/discarded", wakeSettlement);
1864
+ }
1865
+ observer.start(activationAgent(activation));
1747
1866
  } catch (error) {
1748
- releaseHold();
1867
+ /* v8 ignore next -- rollback failure must not mask the admission failure
1868
+ * that prevented this operation from returning an accepted message id. */
1869
+ await this.rollbackUnpublished(activation, error).catch(() => void 0);
1749
1870
  throw error;
1750
1871
  }
1872
+ return activation;
1751
1873
  }
1752
1874
  /**
1753
- * Deliver one model-authored message to a direct continuable child or to the
1754
- * sender's direct parent. A missing direct child cold-resumes through the
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.
1875
+ * Begin settlement observation after initial acceptance is recorded.
1876
+ * @param activation - accepted activation to observe.
1761
1877
  */
1762
- async sendMessage(sender, targetId, content, options) {
1763
- if (this.ctx.agents.get(sender.id) !== sender) throw new SubagentError("message delivery requires the exact live sender agent", "UNAUTHORIZED");
1764
- this.activations.assertAdmitting(sender);
1765
- const senderActivation = this.activations.get(sender.id);
1766
- if (senderActivation !== void 0 && senderActivation.handle.agent === sender && senderActivation.parentSession === targetId) {
1767
- options.signal.throwIfAborted();
1768
- return this.sendToParent(senderActivation, sender, content);
1878
+ announce(activation) {
1879
+ if (activation.announced) return;
1880
+ activation.announced = true;
1881
+ if (activation.kind === "local") this.watchLocalSettlement(activation);
1882
+ else {
1883
+ const finish = () => this.close(activation, () => this.finishDisposal(activation, false));
1884
+ activation.run.result.then(finish, finish).catch((error) => {
1885
+ this.reportTeardownFailure(activation, error);
1886
+ });
1769
1887
  }
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
1888
  }
1776
- /**
1777
- * Queue one human-authored prompt as a distinct direct-child turn.
1778
- * @param parent - exact live direct parent authorizing delivery.
1779
- * @param childId - durable direct-child session id.
1780
- * @param content - model-visible prompt blocks.
1781
- * @param source - durable attribution for the human prompt.
1782
- * @param signal - caller cancellation before inbox acceptance.
1783
- * @returns the accepted durable message id.
1784
- */
1785
- async queuePrompt(parent, childId, content, source, signal) {
1786
- return this.deliverToChild(parent, childId, content, {
1787
- source,
1788
- signal,
1789
- delivery: "queue"
1889
+ /** Release an Activation whose start edge was not published. */
1890
+ rollbackUnpublished(activation, error) {
1891
+ return this.close(activation, async () => {
1892
+ try {
1893
+ await (activation.kind === "local" ? activation.handle.dispose() : activation.run.dispose());
1894
+ } finally {
1895
+ this.resident.delete(activation.childId);
1896
+ activation.releaseSlot();
1897
+ this.releaseOwnership(activation.childId);
1898
+ activation.result.reject(error);
1899
+ }
1790
1900
  });
1791
1901
  }
1792
- /**
1793
- * Steer one host-authored prompt to a direct continuable child.
1794
- * @param parent - exact live direct parent authorizing delivery.
1795
- * @param childId - durable direct-child session id.
1796
- * @param content - model-visible prompt blocks.
1797
- * @param source - durable attribution for the host prompt.
1798
- * @param signal - caller cancellation before inbox acceptance.
1799
- * @returns the accepted durable message id.
1800
- */
1801
- async steerPrompt(parent, childId, content, source, signal) {
1802
- return this.deliverToChild(parent, childId, content, {
1803
- source,
1804
- signal,
1805
- delivery: "steer"
1806
- });
1902
+ /** Register the child in a continuation-managed parent's owned set. */
1903
+ acquireOwnership(parent, childId) {
1904
+ const parentActivation = this.resident.get(parent.id);
1905
+ if (parentActivation === void 0) return;
1906
+ if (parentActivation.closing !== void 0) throw new SubagentError(`subagent parent "${parent.id}" is being disposed; the child was not established`, "ACTIVATION_CLOSING");
1907
+ parentActivation.ownedChildren.add(childId);
1908
+ this.wake(parentActivation);
1909
+ }
1910
+ /** Remove one child from its live owner's set and let that owner re-check settlement. */
1911
+ releaseOwnership(childId) {
1912
+ for (const candidate of this.resident.values()) if (candidate.ownedChildren.delete(childId)) this.wake(candidate);
1913
+ }
1914
+ /** Let a settlement watcher re-check residency after relevant state changes. */
1915
+ wake(activation) {
1916
+ if (activation.kind === "external") return;
1917
+ activation.poke.resolve();
1918
+ activation.poke = Promise.withResolvers();
1919
+ }
1920
+ /** Follow one Activation to natural settlement. */
1921
+ watchLocalSettlement(activation) {
1922
+ (async () => {
1923
+ while (true) {
1924
+ const idleObservation = activation.poke;
1925
+ await activation.handle.agent.whenIdle();
1926
+ if (activation.closing !== void 0) return;
1927
+ const readiness = await this.locks.run(activation.childId, () => Promise.resolve(this.settlementState(activation, idleObservation)));
1928
+ if (readiness === "closed") return;
1929
+ if (readiness === "retry") continue;
1930
+ if (readiness === "wait") {
1931
+ await idleObservation.promise;
1932
+ continue;
1933
+ }
1934
+ const finalSeq = activation.handle.agent.session.seq;
1935
+ await this.flushFinalState(activation);
1936
+ const attempt = await this.locks.run(activation.childId, () => {
1937
+ const state = this.settlementState(activation, idleObservation);
1938
+ if (state !== "ready") return Promise.resolve(state);
1939
+ if (activation.handle.agent.session.seq !== finalSeq) return Promise.resolve("retry");
1940
+ let done;
1941
+ try {
1942
+ activation.handle.agent.runMaintenance(() => {
1943
+ done = this.close(activation, () => this.finishDisposal(activation, false));
1944
+ return Promise.resolve();
1945
+ });
1946
+ } catch (_error) {
1947
+ return Promise.resolve("retry");
1948
+ }
1949
+ return Promise.resolve({ done });
1950
+ });
1951
+ if (typeof attempt === "string") {
1952
+ if (attempt === "closed") return;
1953
+ continue;
1954
+ }
1955
+ try {
1956
+ await attempt.done;
1957
+ } catch (error) {
1958
+ this.reportTeardownFailure(activation, error);
1959
+ }
1960
+ return;
1961
+ }
1962
+ })();
1963
+ }
1964
+ /** Report disposal failure once after the natural settlement observer finishes. */
1965
+ reportTeardownFailure(activation, error) {
1966
+ this.ctx.logger.warn(`subagent "${activation.childId}" activation teardown failed: ${failureMessage(error)}`);
1967
+ }
1968
+ /** Check pending input and owned children before closing admission. */
1969
+ settlementState(activation, observation) {
1970
+ if (activation.closing !== void 0) return "closed";
1971
+ if (activation.poke !== observation) return "retry";
1972
+ const inbox = activation.handle.agent.inbox;
1973
+ if (activation.ownedChildren.size > 0) return "wait";
1974
+ if (activation.failureAt === void 0 && (inbox.nextTurn.length > 0 || inbox.nextStep.length > 0)) return "wait";
1975
+ return "ready";
1976
+ }
1977
+ /** Propagate stop synchronously and await descendant startup rollback before release. */
1978
+ async finishDisposal(activation, stop) {
1979
+ this.wake(activation);
1980
+ const { childId } = activation;
1981
+ const failures = [];
1982
+ let result = {
1983
+ output: [],
1984
+ stopReason: "error"
1985
+ };
1986
+ let resultFailure;
1987
+ let externalDisposal;
1988
+ const child = activationAgent(activation);
1989
+ const detail = (error) => child === void 0 ? failureMessage(error) : errorChain(error);
1990
+ try {
1991
+ if (activation.kind === "external") {
1992
+ if (stop) {
1993
+ activation.controller.abort({ kind: "parent" });
1994
+ externalDisposal = Promise.resolve().then(() => activation.run.dispose());
1995
+ externalDisposal.catch(() => void 0);
1996
+ }
1997
+ result = await activation.run.result;
1998
+ } else {
1999
+ if (stop) {
2000
+ activation.handle.agent.cancel({ kind: "parent" });
2001
+ const idle = activation.handle.agent.whenIdle();
2002
+ const children = [...activation.ownedChildren].map((child) => this.resident.get(child)).filter((child) => child !== void 0);
2003
+ const pending = [...this.materializations].filter((item) => item.lineage.includes(activation.handle.agent));
2004
+ const childDisposals = children.map((child) => this.dispose(child));
2005
+ for (const item of pending) item.controller.abort();
2006
+ childDisposals.push(...pending.map((item) => item.settled));
2007
+ const reasons = (await Promise.all(childDisposals.map(async (disposal) => {
2008
+ try {
2009
+ await disposal;
2010
+ return;
2011
+ } catch (error) {
2012
+ return error;
2013
+ }
2014
+ }))).filter((reason) => reason !== void 0);
2015
+ if (reasons.length > 0) failures.push(new SubagentError(`subagent "${childId}" child teardown failed: ${reasons.map((reason) => failureMessage(reason)).join("; ")}`, "ACTIVATION_TEARDOWN_FAILED"));
2016
+ await idle;
2017
+ await this.flushFinalState(activation);
2018
+ }
2019
+ result = captureLocalResult(activation);
2020
+ }
2021
+ } catch (error) {
2022
+ resultFailure = { error };
2023
+ failures.push(new SubagentError(`subagent "${childId}" activation teardown failed: ${detail(error)}`, "ACTIVATION_TEARDOWN_FAILED", { cause: error }));
2024
+ }
2025
+ try {
2026
+ if (activation.kind === "local") await activation.handle.dispose();
2027
+ else await (externalDisposal ?? activation.run.dispose());
2028
+ } catch (error) {
2029
+ failures.push(new SubagentError(`subagent "${childId}" activation handle disposal failed: ${detail(error)}`, "ACTIVATION_TEARDOWN_FAILED", { cause: error }));
2030
+ }
2031
+ let failure;
2032
+ if (failures.length === 1) failure = failures[0];
2033
+ 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) });
2034
+ this.resident.delete(childId);
2035
+ activation.releaseSlot();
2036
+ const terminal = failure === void 0 ? result : {
2037
+ output: [],
2038
+ stopReason: "error"
2039
+ };
2040
+ this.notifySettlement(activation, result);
2041
+ this.releaseOwnership(childId);
2042
+ activation.observer.settle(terminal);
2043
+ if (resultFailure === void 0) activation.result.resolve(result);
2044
+ else activation.result.reject(resultFailure.error);
2045
+ if (failure !== void 0) throw failure;
2046
+ }
2047
+ /** Deliver the captured execution result to the durable direct parent after cleanup. */
2048
+ notifySettlement(activation, terminal) {
2049
+ if (!activation.announced || activation.delivery === "caller") return;
2050
+ try {
2051
+ const parent = this.ctx.agents.get(activation.parent.id);
2052
+ if (parent !== activation.parent) return;
2053
+ const message = createSettlementMessage(activation.childId, terminal, activationAgent(activation) !== void 0);
2054
+ if (this.closingTeardownFor(parent) !== void 0) {
2055
+ parent.inject(message);
2056
+ return;
2057
+ }
2058
+ this.sendWaking(parent, message, parent.status === "idle" ? "queue" : "steer");
2059
+ } catch (error) {
2060
+ this.ctx.logger.warn(`subagent "${activation.childId}" settlement notice was not delivered to its parent: ` + errorChain(error));
2061
+ }
2062
+ }
2063
+ /** Request a best-effort final session flush before closing natural-settlement admission. */
2064
+ async flushFinalState(activation) {
2065
+ try {
2066
+ await activation.handle.agent.ctx.sessions.flush(activation.handle.agent.session);
2067
+ } catch (error) {
2068
+ 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)}`);
2069
+ }
2070
+ }
2071
+ /** Return capabilities bound to an exact activation instead of a future same-id instance. */
2072
+ receipt(activation) {
2073
+ return {
2074
+ childId: activation.childId,
2075
+ result: activation.result.promise,
2076
+ dispose: () => this.dispose(activation)
2077
+ };
1807
2078
  }
1808
2079
  /** Route one parent-originated delivery through residency and cold resume. */
1809
2080
  async deliverToChild(parent, childId, content, options) {
1810
- this.activations.assertAdmitting(parent);
1811
- const releaseHold = this.activations.holdOwnership(parent, childId);
2081
+ this.assertAdmitting(parent);
2082
+ const releaseHold = this.holdOwnership(parent, childId);
1812
2083
  try {
1813
2084
  return await this.deliverFollowup(parent, childId, content, options);
1814
2085
  } catch (error) {
@@ -1819,47 +2090,38 @@ var SubagentContinuationManager = class {
1819
2090
  /** The delivery loop behind {@link deliverToChild}, run under the parent hold. */
1820
2091
  async deliverFollowup(parent, childId, content, options) {
1821
2092
  while (true) {
1822
- const live = await this.activations.locks.run(childId, async () => {
1823
- const activation = this.activations.get(childId);
2093
+ const live = await this.locks.run(childId, async () => {
2094
+ const activation = this.resident.get(childId);
1824
2095
  if (activation === void 0) return this.coldResume(parent, childId, content, options);
1825
- const disposal = activation.inbox.closing;
2096
+ requireLocalActivation(activation);
2097
+ const disposal = activation.closing;
1826
2098
  /* v8 ignore next 3 -- the send-versus-dispose cutoff needs a delivery to
1827
2099
  * observe the transaction inside the same critical section that opened it. */
1828
2100
  if (disposal !== void 0) return disposal.then(() => void 0, () => void 0);
1829
2101
  if (contentHasImage(content)) {
1830
- await this.assertImageCapable(activation.handle.agent, options.signal);
1831
- if (activation.inbox.closing !== void 0) {
1832
- await Promise.allSettled([activation.inbox.closing]);
2102
+ await this.assertImageCapable(requireLocalActivation(activation).handle.agent, options.signal);
2103
+ if (activation.closing !== void 0) {
2104
+ await Promise.allSettled([activation.closing]);
1833
2105
  return;
1834
2106
  }
1835
2107
  }
1836
2108
  const messageId = this.submitAdmitted(activation, content, options, parent);
1837
- activation.announced = true;
2109
+ this.announce(activation);
1838
2110
  return messageId;
1839
2111
  });
1840
2112
  /* v8 ignore start -- only a delivery that lost the disposal cutoff retries. */
1841
2113
  if (live !== void 0) return live;
1842
- this.activations.assertAdmitting(parent);
2114
+ this.assertAdmitting(parent);
1843
2115
  options.signal.throwIfAborted();
1844
2116
  }
1845
2117
  }
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
2118
  /** Deliver one resident continuable child's message to its live direct parent. */
1857
2119
  sendToParent(activation, sender, content) {
1858
2120
  /* v8 ignore next 6 -- only synchronous re-entrant teardown can open this
1859
2121
  * transaction between exact-agent authorization and this no-await span. */
1860
- if (activation.inbox.closing !== void 0) throw new SubagentError(`subagent "${sender.id}" activation is being disposed; the message was not delivered`, "ACTIVATION_CLOSING");
1861
- const parent = this.ctx.agents.get(activation.parentSession);
1862
- if (parent === void 0) throw new SubagentError("direct parent is not live; the message was not delivered", "PARENT_UNAVAILABLE");
2122
+ if (activation.closing !== void 0) throw new SubagentError(`subagent "${sender.id}" activation is being disposed; the message was not delivered`, "ACTIVATION_CLOSING");
2123
+ const parent = this.ctx.agents.get(activation.parent.id);
2124
+ if (parent !== activation.parent) throw new SubagentError("direct parent is not live; the message was not delivered", "PARENT_UNAVAILABLE");
1863
2125
  const message = createAgentMessage(sender, content);
1864
2126
  this.sendAgentMessage(parent, message);
1865
2127
  return message.id;
@@ -1867,30 +2129,11 @@ var SubagentContinuationManager = class {
1867
2129
  /** Send one Agent message while translating only the target's own rejection. */
1868
2130
  sendAgentMessage(parent, message) {
1869
2131
  try {
1870
- this.activations.sendWaking(parent, message, "steer");
2132
+ this.sendWaking(parent, message, "steer");
1871
2133
  } catch (error) {
1872
2134
  throw new SubagentError("direct parent is not live; the message was not delivered", "PARENT_UNAVAILABLE", { cause: error });
1873
2135
  }
1874
2136
  }
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
2137
  /**
1895
2138
  * Cold-resume a persisted child and submit the waiting turn. The descriptor
1896
2139
  * supplies every reconstruction input; no subagent provider is dispatched.
@@ -1911,13 +2154,18 @@ var SubagentContinuationManager = class {
1911
2154
  throw new SubagentError(`subagent "${childId}" is unavailable`, "NOT_RESUMABLE", { cause: error });
1912
2155
  }
1913
2156
  const source = __addDisposableResource$2(env_1, observation, false);
1914
- this.activations.assertAdmitting(parent);
1915
- this.activations.authorizeLineage(parent, childId, source.header.parentSession);
1916
- const descriptor = foldSubagentDescriptor(source.events.slice(source.inheritedEventCount));
2157
+ this.assertAdmitting(parent);
2158
+ this.authorizeLineage(parent, childId, source.header.parentSession);
2159
+ const ownEvents = source.events.slice(source.inheritedEventCount);
2160
+ const descriptor = foldSubagentDescriptor(ownEvents);
1917
2161
  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
2162
  let activation;
1919
2163
  try {
1920
- activation = await this.activations.materialize({
2164
+ const independentlySelected = hasIndependentPresetSelection(ownEvents);
2165
+ const agentPreset = independentlySelected ? source.projections?.values.agentPreset : void 0;
2166
+ if (independentlySelected && typeof agentPreset !== "string") throw new SubagentError(`subagent "${childId}" has no agent preset projection for restoration`, "NOT_RESUMABLE");
2167
+ activation = await this.materialize({
2168
+ kind: "local",
1921
2169
  childId,
1922
2170
  provider: descriptor.provider,
1923
2171
  parent,
@@ -1927,6 +2175,7 @@ var SubagentContinuationManager = class {
1927
2175
  ...descriptor.agentReasoningEffort !== void 0 ? { reasoningEffort: ReasoningEffortId(descriptor.agentReasoningEffort) } : {}
1928
2176
  },
1929
2177
  composition: {
2178
+ agentPreset: agentPreset ?? void 0,
1930
2179
  persona: descriptor.persona,
1931
2180
  toolFilter: descriptor.toolFilter
1932
2181
  },
@@ -1949,16 +2198,16 @@ var SubagentContinuationManager = class {
1949
2198
  async submitMaterialized(activation, content, options, parent, commit) {
1950
2199
  try {
1951
2200
  if (contentHasImage(content)) {
1952
- await this.assertImageCapable(activation.handle.agent, options.signal);
1953
- if (activation.inbox.closing !== void 0) throw new SubagentError(`subagent "${activation.childId}" is closing`, "ACTIVATION_CLOSING");
2201
+ await this.assertImageCapable(requireLocalActivation(activation).handle.agent, options.signal);
2202
+ if (activation.closing !== void 0) throw new SubagentError(`subagent "${activation.childId}" is closing`, "ACTIVATION_CLOSING");
1954
2203
  }
1955
2204
  const messageId = this.submitAdmitted(activation, content, options, parent);
1956
2205
  commit?.();
1957
- activation.announced = true;
2206
+ this.announce(activation);
1958
2207
  return messageId;
1959
2208
  } catch (error) {
1960
2209
  try {
1961
- await this.activations.dispose(activation);
2210
+ await this.dispose(activation);
1962
2211
  } catch (cleanupError) {
1963
2212
  this.ctx.logger.warn(`subagent continuation: disposal after admission or catalog append failure also failed: ${String(cleanupError)}`);
1964
2213
  }
@@ -1971,7 +2220,16 @@ var SubagentContinuationManager = class {
1971
2220
  content,
1972
2221
  source: options.source
1973
2222
  });
1974
- return this.activations.submitAdmitted(activation, message, options.delivery, parent, options.signal);
2223
+ options.signal.throwIfAborted();
2224
+ this.assertAdmitting(parent);
2225
+ this.authorizeLineage(parent, activation.childId, activation.parent.id);
2226
+ this.acquireOwnership(parent, activation.childId);
2227
+ try {
2228
+ this.deliver(activation, message, options.delivery);
2229
+ } finally {
2230
+ this.wake(activation);
2231
+ }
2232
+ return message.id;
1975
2233
  }
1976
2234
  /** Refuse image content for a child whose fixed model accepts text only. */
1977
2235
  async assertImageCapable(agent, signal) {
@@ -1996,6 +2254,15 @@ var SubagentContinuationManager = class {
1996
2254
  return query;
1997
2255
  }
1998
2256
  };
2257
+ /** Creation-time selection precedes the own descriptor; historical forks can select after it. */
2258
+ function hasIndependentPresetSelection(events) {
2259
+ for (const event of events) {
2260
+ if (event.type === "subagent/descriptor") return false;
2261
+ if (event.type === "agent-preset/selected") return true;
2262
+ }
2263
+ /* v8 ignore next -- coldResume rejects logs without a supported own descriptor before this lookup. */
2264
+ return false;
2265
+ }
1999
2266
  //#endregion
2000
2267
  //#region lib/types/list-children.js
2001
2268
  /**
@@ -2124,7 +2391,7 @@ async function listDescendants(ctx, rootSessionId, signal) {
2124
2391
  visited.add(entry.id);
2125
2392
  let children;
2126
2393
  try {
2127
- children = await readChildren(entry.id);
2394
+ children = entry.mode === "external" ? [] : await readChildren(entry.id);
2128
2395
  } catch (error) {
2129
2396
  if (error instanceof SubagentError) throw error;
2130
2397
  const code = error instanceof Error && "code" in error ? error.code : void 0;
@@ -2482,8 +2749,7 @@ const subagentIdentityProjectionDefinition = {
2482
2749
  * Provider-side vocabulary for OUT-OF-PROCESS subagent backends — the pieces
2483
2750
  * that enforce this seam's own contracts around a child in another process:
2484
2751
  * the no-capabilities advertisement, timing-bound validation, child
2485
- * working-directory resolution (config override, else the delegating parent
2486
- * session's workspace), the never-reject result settlement, and the standard
2752
+ * working-directory validation, the never-reject result settlement, and the standard
2487
2753
  * run-handle publication. Backends compose these with their own wire drivers;
2488
2754
  * the process machinery itself (spawn, env scrub, managed-range teardown)
2489
2755
  * belongs to the `dsh-subprocess` seam.
@@ -2517,15 +2783,14 @@ function normalizeSubagentDiagnostic(result) {
2517
2783
  /**
2518
2784
  * The capability advertisement of an out-of-process backend: NONE. A child in
2519
2785
  * another process cannot honor parent-enforced start features
2520
- * (`agentOptions`/`outputSchema`/`maxDepth`/`toolFilter`/`persona`), so the service rejects a
2786
+ * (`agentOptions`/`agentPreset`/`outputSchema`/`maxDepth`), so the service rejects a
2521
2787
  * request needing any of them before `start` runs — never accepted-then-ignored.
2522
2788
  */
2523
2789
  const NO_START_CAPABILITIES = Object.freeze({
2524
2790
  agentOptions: false,
2791
+ agentPreset: false,
2525
2792
  outputSchema: false,
2526
- depthLimit: false,
2527
- toolFilter: false,
2528
- persona: false
2793
+ depthLimit: false
2529
2794
  });
2530
2795
  /**
2531
2796
  * Assert a configured timing bound is a positive finite number (it bounds a
@@ -2566,39 +2831,6 @@ function assertUsableCwd(prefix, label, cwd) {
2566
2831
  if (!isEnterableDirectory(cwd)) throw new Error(`${prefix}: ${label} is not an accessible directory: ${cwd}`);
2567
2832
  return cwd;
2568
2833
  }
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
2834
  /** Normalize an unknown thrown value to an Error (the catch binding is `unknown`). */
2603
2835
  function toError(value) {
2604
2836
  /* v8 ignore next */
@@ -2646,13 +2878,12 @@ async function settleRunResult(parts) {
2646
2878
  * local cancellation — there is no assumption the child cooperates — and then
2647
2879
  * awaits the backend's teardown to actual exit.
2648
2880
  * @param parts - the run identity, result, cancellation wiring, and teardown.
2649
- * @returns the seam run handle (`localAgent` is `undefined` for remote runs).
2881
+ * @returns the external run handle.
2650
2882
  */
2651
2883
  function subprocessRunHandle(parts) {
2652
2884
  let disposal;
2653
2885
  return {
2654
2886
  id: parts.id,
2655
- localAgent: void 0,
2656
2887
  result: parts.result,
2657
2888
  dispose() {
2658
2889
  if (disposal !== void 0) return disposal;
@@ -2664,80 +2895,6 @@ function subprocessRunHandle(parts) {
2664
2895
  };
2665
2896
  }
2666
2897
  //#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
2898
  //#region lib/types/index.js
2742
2899
  var __runInitializers = function(thisArg, initializers, value) {
2743
2900
  var useValue = arguments.length > 2;
@@ -2777,7 +2934,7 @@ var __esDecorate = function(ctor, descriptorIn, decorators, contextIn, initializ
2777
2934
  if (target) Object.defineProperty(target, contextIn.name, descriptor);
2778
2935
  done = true;
2779
2936
  };
2780
- /** Named provider registry with one-shot runs, durable discovery, and continuable-child operations. */
2937
+ /** Named provider registry with managed activations, durable discovery, and local child messaging. */
2781
2938
  let SubagentRuntime = (() => {
2782
2939
  let _classSuper = TypertRemoteService;
2783
2940
  let _instanceExtraInitializers = [];
@@ -2822,8 +2979,9 @@ let SubagentRuntime = (() => {
2822
2979
  maxDepth: z.number().step(1).min(0).max(Number.MAX_SAFE_INTEGER).default(1).volatile(),
2823
2980
  maxActiveSubagents: z.number().step(1).min(1).max(Number.MAX_SAFE_INTEGER).default(8).volatile()
2824
2981
  });
2982
+ static inject = ["workingDirectory"];
2825
2983
  providers = /* @__PURE__ */ new Map();
2826
- continuations;
2984
+ manager;
2827
2985
  /**
2828
2986
  * The contained lifecycle-edge publisher. Built here because scoped dispatch
2829
2987
  * keys its carrier by this exact service instance, whose own context filter
@@ -2835,21 +2993,23 @@ let SubagentRuntime = (() => {
2835
2993
  this.config = config;
2836
2994
  this.emitLifecycle = createLifecycleEmitter(this.ctx, (parent) => scopeTarget(this, parent));
2837
2995
  ctx.inject(["agents"], (childCtx) => {
2838
- const manager = new SubagentContinuationManager(childCtx, {
2996
+ const manager = new SubagentManager(childCtx, {
2997
+ startExternal: (name, request) => {
2998
+ return this.expectProvider(name).start(request);
2999
+ },
2839
3000
  prepareContinuable: (name, request) => this.prepareContinuable(name, request),
2840
3001
  observeActivation: (provider, childId, parent) => this.observeActivation(provider, childId, parent)
2841
3002
  }, () => this.config.maxActiveSubagents.get());
2842
- this.continuations = manager;
3003
+ this.manager = manager;
2843
3004
  childCtx.effect(() => () => {
2844
3005
  /* v8 ignore else -- one injected binding owns the slot until its fiber disposes. */
2845
- if (this.continuations === manager) this.continuations = void 0;
2846
- }, "subagents.continuationBinding()");
3006
+ if (this.manager === manager) this.manager = void 0;
3007
+ }, "subagents.managerBinding()");
2847
3008
  });
2848
3009
  ctx.inject(["sessionProjections"], (projectionCtx) => {
2849
- const projections = projectionCtx.sessionProjections;
2850
- projections.register(subagentCatalogProjectionDefinition);
2851
- projections.register(subagentTimingProjectionDefinition);
2852
- projections.register(subagentIdentityProjectionDefinition);
3010
+ projectionCtx.sessionProjections.register(subagentCatalogProjectionDefinition);
3011
+ projectionCtx.sessionProjections.register(subagentTimingProjectionDefinition);
3012
+ projectionCtx.sessionProjections.register(subagentIdentityProjectionDefinition);
2853
3013
  });
2854
3014
  ctx.inject(["agents"], (agentsCtx) => {
2855
3015
  installSubagentArchiveAdmission(agentsCtx);
@@ -2868,16 +3028,32 @@ let SubagentRuntime = (() => {
2868
3028
  return depth;
2869
3029
  }
2870
3030
  /**
2871
- * Establish one durable continuable child and deliver its initial prompt.
2872
- * Resolves when the child's inbox accepts that prompt, without waiting for the
2873
- * turn to start or for the message to reach the Session log; any earlier
2874
- * failure rejects with no ids and rolls back the child entirely.
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.
3031
+ * Establish a managed child with backend-specific execution capabilities.
3032
+ * @param spec - task, backend, and result recipient.
3033
+ * @returns identities, execution result, and exact-activation disposal. A reserved
3034
+ * childId requires a local backend and guarantees an accepted messageId.
3035
+ */
3036
+ async startActivation(spec) {
3037
+ const provider = this.expectProvider(spec.provider);
3038
+ if (spec.childId !== void 0 && provider.prepareContinuable === void 0) throw new SubagentError("reserved child ids require a local backend", "UNSUPPORTED_CAPABILITY");
3039
+ assertSubagentMaxDepth(spec.request.maxDepth);
3040
+ this.assertCapabilities(provider, {
3041
+ ...spec.request,
3042
+ signal: spec.signal
3043
+ });
3044
+ const manager = this.requireManager();
3045
+ 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");
3046
+ if (spec.request.outputSchema !== void 0) assertObjectJsonSchema(spec.request.outputSchema);
3047
+ return provider.prepareContinuable === void 0 ? manager.startExternal(spec) : manager.startLocal(spec);
3048
+ }
3049
+ /**
3050
+ * Join progressing descendants without cancelling them. Idle descendants whose
3051
+ * inboxes require a later wake stay resident and do not delay host completion.
3052
+ * @param parent - the exact parent whose descendant work is observed.
3053
+ * @returns whether work was joined; hosts recheck parent idle after true.
2878
3054
  */
2879
- async startContinuable(spec) {
2880
- return this.requireContinuations().startContinuable(spec);
3055
+ async waitForChildren(parent) {
3056
+ return this.manager?.waitForChildren(parent) ?? false;
2881
3057
  }
2882
3058
  /**
2883
3059
  * Steer one model-authored message to the sender's direct parent or direct
@@ -2894,7 +3070,7 @@ let SubagentRuntime = (() => {
2894
3070
  * or the message was not admitted.
2895
3071
  */
2896
3072
  async sendMessage(sender, targetId, content, options) {
2897
- return this.requireContinuations().sendMessage(sender, targetId, content, options);
3073
+ return this.requireManager().sendMessage(sender, targetId, content, options);
2898
3074
  }
2899
3075
  /**
2900
3076
  * Deliver one host-protocol message to a direct continuable child.
@@ -2909,16 +3085,16 @@ let SubagentRuntime = (() => {
2909
3085
  * @returns the accepted message's inbox id.
2910
3086
  */
2911
3087
  [deliverSubagentPrompt](parent, childId, content, source, signal, delivery) {
2912
- return delivery === "steer" ? this.requireContinuations().steerPrompt(parent, childId, content, source, signal) : this.requireContinuations().queuePrompt(parent, childId, content, source, signal);
3088
+ return delivery === "steer" ? this.requireManager().steerPrompt(parent, childId, content, source, signal) : this.requireManager().queuePrompt(parent, childId, content, source, signal);
2913
3089
  }
2914
3090
  /**
2915
- * Interrupt one live continuable child's current turn under a human parent
3091
+ * Interrupt one live child's current execution under a human parent
2916
3092
  * address or an exact live ancestor Agent. Fire-and-return: the cancel
2917
3093
  * signal is issued before this returns, but the target may keep running
2918
3094
  * until it observes the signal. Unclaimed pending inbox work, the Activation,
2919
3095
  * and published descendants are preserved; claimed work is not requeued.
2920
- * Once the interrupted driver is idle, a waking send resumes the parked FIFO
2921
- * queue. An absent target — including a one-shot or unknown id —
3096
+ * Once the interrupted Agent is idle, a waking send resumes the parked FIFO
3097
+ * queue. External backends stop their single execution. An absent target
2922
3098
  * is an accepted no-op, as is a manager-less composition, which cannot own a
2923
3099
  * live Activation.
2924
3100
  * @param targetSessionId - the durable child session id to interrupt.
@@ -2927,35 +3103,35 @@ let SubagentRuntime = (() => {
2927
3103
  * live target.
2928
3104
  */
2929
3105
  interrupt(targetSessionId, authority) {
2930
- this.continuations?.interrupt(targetSessionId, authority);
3106
+ this.manager?.interrupt(targetSessionId, authority);
2931
3107
  }
2932
3108
  /**
2933
- * Close continuable admission below exact live parent Agents, stop only their
3109
+ * Close subagent admission below exact live parent Agents, stop only their
2934
3110
  * visible descendant Activations synchronously, then await admitted scoped
2935
3111
  * materializations and release those forests child-first. The scoped cutoff
2936
3112
  * lasts until each exact parent leaves the registry; unrelated parent trees
2937
3113
  * remain live.
2938
3114
  * @param parents - exact host-owned parent Agents entering teardown.
2939
- * @returns once every retained descendant Activation released its `AgentHandle`.
3115
+ * @returns once every retained descendant activation released its execution handle.
2940
3116
  * @throws an aggregate error after all branches settle when any failed.
2941
3117
  */
2942
- async drainContinuableDescendants(parents) {
2943
- const manager = this.continuations;
3118
+ async drainDescendants(parents) {
3119
+ const manager = this.manager;
2944
3120
  if (manager === void 0) return;
2945
3121
  await manager.drainDescendants(parents);
2946
3122
  }
2947
3123
  /**
2948
- * Release selected resident continuable direct children of one exact live
3124
+ * Release selected resident direct children of one exact live
2949
3125
  * parent. Other children of the same parent remain admitted and resident.
2950
3126
  * Absent targets and a manager-less composition are accepted no-ops.
2951
3127
  * @param parent - exact live direct parent authorizing the selected release.
2952
3128
  * @param childIds - durable direct-child ids to release when resident.
2953
- * @returns once every selected Activation released its `AgentHandle`.
3129
+ * @returns once every selected activation released its execution handle.
2954
3130
  * @throws {SubagentError} `UNAUTHORIZED` when a resident target belongs to a
2955
3131
  * different parent or the supplied parent identity is stale.
2956
3132
  */
2957
- async drainContinuableChildren(parent, childIds) {
2958
- const manager = this.continuations;
3133
+ async drainChildren(parent, childIds) {
3134
+ const manager = this.manager;
2959
3135
  if (manager === void 0) return;
2960
3136
  await manager.drainChildren(parent, childIds);
2961
3137
  }
@@ -2974,7 +3150,8 @@ let SubagentRuntime = (() => {
2974
3150
  /**
2975
3151
  * Recursively list reachable parent catalogs in stable pre-order, preserving
2976
3152
  * each catalog's event order. Each row carries its catalog parent and depth;
2977
- * one-shot and unknown-mode children remain traversal nodes. Unknown modes
3153
+ * external children are leaves; one-shot and unknown-mode children remain
3154
+ * traversal nodes. Unknown modes
2978
3155
  * produce unsupported diagnostics. Unreadable child catalogs produce corrupt
2979
3156
  * or unavailable diagnostics and stop only that branch. Root read failures,
2980
3157
  * missing services or projections, and cancellation reject the whole listing.
@@ -3014,10 +3191,12 @@ let SubagentRuntime = (() => {
3014
3191
  if (clientTimeZone !== void 0 && canonicalTimeZone === void 0) throw new RemoteError("subagent/invalid-time-zone", "clientTimeZone must be UTC or a valid IANA Area/Location name", { value: clientTimeZone });
3015
3192
  const parent = this.ctx.get("agents")?.get(parentSessionId);
3016
3193
  if (parent === void 0) throw new RemoteError("subagent/parent-unavailable", `parent session "${parentSessionId}" is not live`, { parentSessionId });
3194
+ const principal = principalOf(this.ctx.invocation);
3017
3195
  const source = {
3018
3196
  kind: "user",
3019
3197
  rpcId: request.requestId,
3020
- ...canonicalTimeZone === void 0 ? {} : { clientTimeZone: canonicalTimeZone }
3198
+ ...canonicalTimeZone === void 0 ? {} : { clientTimeZone: canonicalTimeZone },
3199
+ ...principal === void 0 ? {} : { principal }
3021
3200
  };
3022
3201
  try {
3023
3202
  let content;
@@ -3069,12 +3248,14 @@ let SubagentRuntime = (() => {
3069
3248
  /**
3070
3249
  * Register a provider under its name. Registration is effect-scoped and HMR
3071
3250
  * safe; removing a provider blocks new starts but does not revoke runs that
3072
- * were already returned to their holders.
3251
+ * were already returned to their holders. Providers without either execution
3252
+ * method are rejected with UNSUPPORTED_CAPABILITY before registration.
3073
3253
  * @param provider - the trusted provider implementation.
3074
3254
  * @returns the exact Cordis effect disposer.
3075
3255
  */
3076
3256
  registerProvider(provider) {
3077
3257
  const name = provider.name;
3258
+ if (provider.start === void 0 && provider.prepareContinuable === void 0) throw new SubagentError(`subagent provider "${name}" must implement start or prepareContinuable`, "UNSUPPORTED_CAPABILITY");
3078
3259
  return this.ctx.effect(function* () {
3079
3260
  if (this.providers.has(name)) throw new SubagentError(`a subagent provider named "${name}" is already registered`, "DUPLICATE_PROVIDER");
3080
3261
  this.providers.set(name, provider);
@@ -3101,55 +3282,12 @@ let SubagentRuntime = (() => {
3101
3282
  return [...this.providers.keys()];
3102
3283
  }
3103
3284
  /**
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
3285
  * Resolve one provider's detached continuable-creation contribution. Method
3146
3286
  * presence on the provider IS the capability, so a provider without it is
3147
3287
  * rejected before the manager reserves any child resources.
3148
3288
  */
3149
3289
  async prepareContinuable(name, request) {
3150
- const provider = this.expectProvider(name);
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);
3290
+ return this.expectProvider(name).prepareContinuable(request);
3153
3291
  }
3154
3292
  /** Look up a provider for dispatch or fail loud. */
3155
3293
  expectProvider(name) {
@@ -3157,10 +3295,10 @@ let SubagentRuntime = (() => {
3157
3295
  if (provider === void 0) throw new SubagentError(`no subagent provider registered for "${name}"`, "NO_PROVIDER");
3158
3296
  return provider;
3159
3297
  }
3160
- /** Resolve the optional continuable-subagent manager or fail loud. */
3161
- requireContinuations() {
3162
- if (this.continuations === void 0) throw new SubagentError("continuable subagents require the agents service", "CONTINUATION_UNAVAILABLE");
3163
- return this.continuations;
3298
+ /** Resolve the subagent manager or fail loud. */
3299
+ requireManager() {
3300
+ if (this.manager === void 0) throw new SubagentError("continuable subagents require the agents service", "CONTINUATION_UNAVAILABLE");
3301
+ return this.manager;
3164
3302
  }
3165
3303
  /**
3166
3304
  * Build the lifecycle observer for one continuable Activation's residency
@@ -3171,6 +3309,7 @@ let SubagentRuntime = (() => {
3171
3309
  }
3172
3310
  /** Reject the first requested capability that the provider lacks. */
3173
3311
  assertCapabilities(provider, request) {
3312
+ if (request.agentPreset !== void 0 && (!provider.capabilities.agentPreset || provider.inheritsParentContext)) throw new SubagentError(`subagent provider "${provider.name}" does not support independent agent preset selection`, "UNSUPPORTED_CAPABILITY");
3174
3313
  const needs = [
3175
3314
  {
3176
3315
  when: request.agentOptions !== void 0,
@@ -3183,14 +3322,6 @@ let SubagentRuntime = (() => {
3183
3322
  {
3184
3323
  when: request.maxDepth !== void 0,
3185
3324
  cap: "depthLimit"
3186
- },
3187
- {
3188
- when: request.toolFilter !== void 0,
3189
- cap: "toolFilter"
3190
- },
3191
- {
3192
- when: request.persona !== void 0,
3193
- cap: "persona"
3194
3325
  }
3195
3326
  ];
3196
3327
  for (const { when, cap } of needs) if (when && !provider.capabilities[cap]) throw new SubagentError(`subagent provider "${provider.name}" does not support the "${cap}" capability`, "UNSUPPORTED_CAPABILITY");
@@ -3198,4 +3329,4 @@ let SubagentRuntime = (() => {
3198
3329
  };
3199
3330
  })();
3200
3331
  //#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, resolveChildCwd, resolveChildDepth, settleRun, settleRunResult, snapshotSubagentDescriptor, subprocessRunHandle, validateConfiguredCwd };
3332
+ 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 };