@xneog/dsh-subagent 0.1.0 → 0.1.3-alpha.1

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