@xneog/dsh-subagent 0.1.0 → 0.1.2-rc.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 (39) 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 +1252 -696
  5. package/lib/typert.host.d.ts +3 -0
  6. package/lib/typert.host.js +911 -0
  7. package/lib/typert.remote-client.d.ts +27 -0
  8. package/lib/typert.remote-client.js +159 -0
  9. package/lib/types/child-agent.d.ts +16 -5
  10. package/lib/types/child-agent.js +51 -13
  11. package/lib/types/client.d.ts +2 -1
  12. package/lib/types/client.js +1 -1
  13. package/lib/types/continuation.d.ts +100 -72
  14. package/lib/types/continuation.js +427 -163
  15. package/lib/types/control-types.d.ts +144 -0
  16. package/lib/types/control-types.js +9 -0
  17. package/lib/types/control.d.ts +67 -0
  18. package/lib/types/control.js +115 -0
  19. package/lib/types/descriptor-seed.d.ts +1 -1
  20. package/lib/types/descriptor-seed.js +1 -1
  21. package/lib/types/descriptor.d.ts +6 -1
  22. package/lib/types/descriptor.js +6 -2
  23. package/lib/types/index.d.ts +103 -68
  24. package/lib/types/index.js +437 -286
  25. package/lib/types/internal.d.ts +59 -0
  26. package/lib/types/internal.js +58 -0
  27. package/lib/types/lifecycle.js +4 -3
  28. package/lib/types/list-children.d.ts +12 -59
  29. package/lib/types/list-children.js +166 -101
  30. package/lib/types/out-of-process.d.ts +5 -2
  31. package/lib/types/out-of-process.js +42 -4
  32. package/lib/types/projection-types.d.ts +4 -3
  33. package/lib/types/projection.d.ts +55 -8
  34. package/lib/types/projection.js +33 -17
  35. package/lib/types/run-settlement.js +17 -6
  36. package/lib/types/types.d.ts +25 -0
  37. package/package.json +67 -37
  38. package/lib/types/activation-setup-registry.d.ts +0 -57
  39. package/lib/types/activation-setup-registry.js +0 -148
package/lib/index.js CHANGED
@@ -1,11 +1,15 @@
1
- import { Service } from "@xneog/cordis";
1
+ import { AttachmentError, admitPromptContent } from "@xneog/dsh-attachment";
2
2
  import { scopeTarget } from "@xneog/dsh-scope";
3
3
  import { assertObjectJsonSchema } from "@xneog/dsh-tools";
4
- import { HarnessError, boundContextSummary, createUserMessage, errorChain } from "@xneog/dsh-llm";
4
+ import { canonicalClientTimeZone } from "@xneog/dsh-util-time";
5
+ import { Remote, RemoteError, TypertRemoteService } from "@xneog/dsh-typert-protocol";
6
+ import { z } from "zod";
7
+ import { HarnessError, ReasoningEffortId, boundContextSummary, contentHasImage, createUserMessage, errorChain } 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 { Session, 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
@@ -228,16 +327,16 @@ function createActivationObserver(emit, provider, childId, parent) {
228
327
  id: childId,
229
328
  local: true
230
329
  };
231
- let boundary = 0;
330
+ let boundary = SessionLogOffset(0);
232
331
  let captured = { stopReason: "completed" };
233
332
  const terminal = (failure) => failure === void 0 ? captured : { stopReason: "error" };
234
333
  return {
235
334
  start: (child) => {
236
- boundary = child.session.events.length;
335
+ boundary = child.session.seq;
237
336
  emit("subagent/start", identity, parent);
238
337
  },
239
338
  capture: (child) => {
240
- const own = child.session.events.slice(boundary);
339
+ const own = child.session.snapshotEvents(boundary);
241
340
  const output = finalAssistantOutput(own);
242
341
  captured = {
243
342
  stopReason: epochStopReason(own),
@@ -325,7 +424,7 @@ function renderThrown(value) {
325
424
  * Supporting another composition input is a deliberate version change, never
326
425
  * an implicit extra field.
327
426
  */
328
- const SUBAGENT_DESCRIPTOR_VERSION = 2;
427
+ const SUBAGENT_DESCRIPTOR_VERSION = 3;
329
428
  const DESCRIPTOR_BASE_KEYS = [
330
429
  "version",
331
430
  "mode",
@@ -337,6 +436,7 @@ const CONTINUABLE_DESCRIPTOR_KEYS = new Set([
337
436
  ...DESCRIPTOR_BASE_KEYS,
338
437
  "agentProvider",
339
438
  "agentModel",
439
+ "agentReasoningEffort",
340
440
  "persona",
341
441
  "toolFilter"
342
442
  ]);
@@ -383,7 +483,7 @@ function parseSubagentDescriptor(value) {
383
483
  if (!isRecord(value)) throw new Error("persisted subagent descriptor payload must be an object");
384
484
  const version = value["version"];
385
485
  if (typeof version !== "number") throw new Error("persisted subagent descriptor version must be a number");
386
- if (version !== 2) return void 0;
486
+ if (version !== 3) return void 0;
387
487
  const mode = value["mode"];
388
488
  if (mode !== "one-shot" && mode !== "continuable") throw new Error("persisted subagent descriptor mode must be \"one-shot\" or \"continuable\"");
389
489
  assertKnownKeys(value, mode === "one-shot" ? ONE_SHOT_DESCRIPTOR_KEYS : CONTINUABLE_DESCRIPTOR_KEYS, "payload");
@@ -392,7 +492,7 @@ function parseSubagentDescriptor(value) {
392
492
  if (mode === "one-shot") {
393
493
  const label = optionalString(value, "label");
394
494
  return {
395
- version: 2,
495
+ version: 3,
396
496
  mode,
397
497
  provider,
398
498
  ...label !== void 0 ? { label } : {}
@@ -402,32 +502,35 @@ function parseSubagentDescriptor(value) {
402
502
  if (typeof label !== "string") throw new Error("persisted subagent descriptor label must be a string");
403
503
  const agentProvider = optionalString(value, "agentProvider");
404
504
  const agentModel = optionalString(value, "agentModel");
505
+ const agentReasoningEffort = optionalString(value, "agentReasoningEffort");
405
506
  const persona = optionalString(value, "persona");
406
507
  const toolFilter = Object.hasOwn(value, "toolFilter") ? parseToolFilter(value["toolFilter"]) : void 0;
407
508
  return {
408
- version: 2,
509
+ version: 3,
409
510
  mode,
410
511
  provider,
411
512
  label,
412
513
  ...agentProvider !== void 0 ? { agentProvider } : {},
413
514
  ...agentModel !== void 0 ? { agentModel } : {},
515
+ ...agentReasoningEffort !== void 0 ? { agentReasoningEffort } : {},
414
516
  ...persona !== void 0 ? { persona } : {},
415
517
  ...toolFilter !== void 0 ? { toolFilter } : {}
416
518
  };
417
519
  }
418
520
  function snapshotSubagentDescriptor(input) {
419
521
  const snapshot = snapshotJsonValue(input.mode === "one-shot" ? {
420
- version: 2,
522
+ version: 3,
421
523
  mode: input.mode,
422
524
  provider: input.provider,
423
525
  ...input.label !== void 0 ? { label: input.label } : {}
424
526
  } : {
425
- version: 2,
527
+ version: 3,
426
528
  mode: input.mode,
427
529
  provider: input.provider,
428
530
  label: input.label,
429
531
  ...input.agentProvider !== void 0 ? { agentProvider: input.agentProvider } : {},
430
532
  ...input.agentModel !== void 0 ? { agentModel: input.agentModel } : {},
533
+ ...input.agentReasoningEffort !== void 0 ? { agentReasoningEffort: input.agentReasoningEffort } : {},
431
534
  ...input.persona !== void 0 ? { persona: input.persona } : {},
432
535
  ...input.toolFilter !== void 0 ? { toolFilter: input.toolFilter } : {}
433
536
  });
@@ -490,25 +593,51 @@ function resolveChildDepth(parent, maxDepth) {
490
593
  return childDepth;
491
594
  }
492
595
  /**
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.
596
+ * Resolve the parent values inherited by a child. The latest request header
597
+ * owns provider, model, and reasoning effort after request-time selection;
598
+ * creation options remain the fallback before the first request and retain
599
+ * the configured output-token limit.
600
+ * @param parent - delegating parent Agent.
601
+ * @returns detached Agent options for child-option merging.
602
+ */
603
+ function parentAgentOptionsForDelegation(parent) {
604
+ const requestConfig = parent.session.requestHeader()?.config;
605
+ if (requestConfig === void 0) return { ...parent.options };
606
+ const { provider: _createdProvider, model: _createdModel, reasoningEffort: _createdReasoningEffort, ...createdOptions } = parent.options;
607
+ return {
608
+ ...createdOptions,
609
+ provider: requestConfig.provider,
610
+ model: requestConfig.model,
611
+ ...requestConfig.reasoningEffort === void 0 ? {} : { reasoningEffort: requestConfig.reasoningEffort }
612
+ };
613
+ }
614
+ /**
615
+ * Resolve the child's `AgentOptions`: the parent's provider/model,
616
+ * reasoning-effort, and maxTokens values unless the request overrides them,
617
+ * stamped with the child's own delegation depth. Changing the route without
618
+ * naming an effort clears the parent's route-owned effort so the selected
619
+ * model resolves its own default.
496
620
  * @param parent - the delegating parent whose route the child inherits.
497
621
  * @param requested - per-child overrides, if any.
498
622
  * @param childDepth - the resolved delegation depth to stamp.
499
623
  * @returns the resolved options for `ctx.agents.create()`.
500
624
  */
501
625
  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 {
626
+ const parentOptions = parentAgentOptionsForDelegation(parent);
627
+ const parentProvider = parentOptions.provider;
628
+ const parentModel = parentOptions.model;
629
+ const parentReasoningEffort = parentOptions.reasoningEffort;
630
+ const parentMaxTokens = parentOptions.maxTokens;
631
+ const resolved = {
506
632
  ...parentProvider !== void 0 ? { provider: parentProvider } : {},
507
633
  ...parentModel !== void 0 ? { model: parentModel } : {},
634
+ ...parentReasoningEffort !== void 0 ? { reasoningEffort: parentReasoningEffort } : {},
508
635
  ...parentMaxTokens !== void 0 ? { maxTokens: parentMaxTokens } : {},
509
636
  ...requested,
510
637
  subagentDepth: childDepth
511
638
  };
639
+ if ((resolved.provider !== parentProvider || resolved.model !== parentModel) && requested?.reasoningEffort === void 0) delete resolved.reasoningEffort;
640
+ return resolved;
512
641
  }
513
642
  /**
514
643
  * Build the child session's durable creation metadata: the parent's workspace,
@@ -524,19 +653,19 @@ function resolveChildAgentOptions(parent, requested, childDepth) {
524
653
  * child never had.
525
654
  * @param parent - the delegating parent agent.
526
655
  * @param childDepth - the resolved delegation depth to persist.
527
- * @param lineageSeedLength - how many leading events came from the parent's log.
656
+ * @param isSeeded - whether this child inherits a parent-log prefix, including an explicitly empty one.
528
657
  * @returns the `meta` for `ctx.agents.create()`.
529
658
  */
530
- function childSessionMeta(parent, childDepth, lineageSeedLength) {
659
+ function childSessionMeta(parent, childDepth, isSeeded) {
531
660
  const parentHeader = parent.session.header;
532
661
  const agentPreset = parent.ctx.get("agentPresets")?.composedPreset(parent.ctx);
533
662
  return {
534
663
  ...parentHeader.cwd !== void 0 ? { cwd: parentHeader.cwd } : {},
535
664
  ...agentPreset === void 0 ? {} : { agentPreset },
536
665
  parentSession: parentHeader.id,
666
+ isSeeded,
537
667
  origin: "subagent",
538
- delegationDepth: childDepth,
539
- ...lineageSeedLength > 0 ? { seedLength: lineageSeedLength } : {}
668
+ delegationDepth: childDepth
540
669
  };
541
670
  }
542
671
  /**
@@ -571,12 +700,12 @@ function applyChildComposition(childCtx, parent, composition) {
571
700
  childCtx.get("agentPresets")?.composeFrom(childCtx, parent.ctx);
572
701
  childCtx.systemPrompt.context({
573
702
  name: "subagent:delegation",
574
- order: 120,
703
+ order: childCtx.systemPrompt.getContextOrder("SUBAGENT_DELEGATION"),
575
704
  text: SUBAGENT_DELEGATION_CONTEXT
576
705
  });
577
706
  if (composition.persona !== void 0) childCtx.systemPrompt.section({
578
707
  name: "deployment:persona",
579
- order: 0,
708
+ order: childCtx.systemPrompt.getSectionOrder("DEPLOYMENT_PERSONA"),
580
709
  text: composition.persona
581
710
  });
582
711
  if (composition.toolFilter !== void 0) childCtx.tools.restrict(composition.toolFilter);
@@ -638,9 +767,32 @@ function appendDelegatedPolicyOverrides(childSession, overrides) {
638
767
  function seedDescriptorTurn(childId, seed, descriptor) {
639
768
  const staged = Session.create(childId, seed);
640
769
  staged.append("subagent/descriptor", descriptor);
641
- return [...staged.events];
770
+ return staged.snapshotEvents();
642
771
  }
643
772
  //#endregion
773
+ //#region lib/types/internal.js
774
+ /**
775
+ * Continuation integration markers and host adapters outside the public
776
+ * Service Definition and model-facing Agent messaging contract.
777
+ * @module @xneog/dsh-subagent/internal
778
+ */
779
+ /** Process-stable identity carried only by the standard adjacent-Agent messaging tool. */
780
+ const adjacentAgentSendMessageTool = Symbol.for("dsh.subagent.adjacentAgentSendMessageTool");
781
+ /**
782
+ * Test whether one visible definition is the standard adjacent-Agent messaging tool.
783
+ * @param definition - the scope-resolved `send_message` candidate.
784
+ * @returns whether the definition carries the internal standard-tool identity.
785
+ */
786
+ function isAdjacentAgentSendMessageTool(definition) {
787
+ return definition !== void 0 && definition[adjacentAgentSendMessageTool] === true;
788
+ }
789
+ /**
790
+ * Process-stable symbol-keyed host delivery shared by the bundled runtime
791
+ * entry and this unbundled internal subpath.
792
+ * @internal
793
+ */
794
+ const deliverSubagentPrompt = Symbol.for("dsh.subagent.deliverPrompt");
795
+ //#endregion
644
796
  //#region lib/types/continuation.js
645
797
  /**
646
798
  * Internal continuable-subagent manager: stable child ids, descriptor
@@ -664,6 +816,64 @@ function seedDescriptorTurn(childId, seed, descriptor) {
664
816
  *
665
817
  * @module @xneog/dsh-subagent
666
818
  */
819
+ var __addDisposableResource$1 = function(env, value, async) {
820
+ if (value !== null && value !== void 0) {
821
+ if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
822
+ var dispose, inner;
823
+ if (async) {
824
+ if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
825
+ dispose = value[Symbol.asyncDispose];
826
+ }
827
+ if (dispose === void 0) {
828
+ if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
829
+ dispose = value[Symbol.dispose];
830
+ if (async) inner = dispose;
831
+ }
832
+ if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
833
+ if (inner) dispose = function() {
834
+ try {
835
+ inner.call(this);
836
+ } catch (e) {
837
+ return Promise.reject(e);
838
+ }
839
+ };
840
+ env.stack.push({
841
+ value,
842
+ dispose,
843
+ async
844
+ });
845
+ } else if (async) env.stack.push({ async: true });
846
+ return value;
847
+ };
848
+ var __disposeResources$1 = (function(SuppressedError) {
849
+ return function(env) {
850
+ function fail(e) {
851
+ env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
852
+ env.hasError = true;
853
+ }
854
+ var r, s = 0;
855
+ function next() {
856
+ while (r = env.stack.pop()) try {
857
+ if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
858
+ if (r.dispose) {
859
+ var result = r.dispose.call(r.value);
860
+ if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) {
861
+ fail(e);
862
+ return next();
863
+ });
864
+ } else s |= 1;
865
+ } catch (e) {
866
+ fail(e);
867
+ }
868
+ if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
869
+ if (env.hasError) throw env.error;
870
+ }
871
+ return next();
872
+ };
873
+ })(typeof SuppressedError === "function" ? SuppressedError : function(error, suppressed, message) {
874
+ var e = new Error(message);
875
+ return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
876
+ });
667
877
  /**
668
878
  * Read one Activation's current disposal transaction. This indirection exists
669
879
  * because TypeScript would otherwise narrow repeated reads of the mutable field
@@ -674,6 +884,32 @@ function seedDescriptorTurn(childId, seed, descriptor) {
674
884
  function disposalOf(activation) {
675
885
  return activation.disposal;
676
886
  }
887
+ /** Build durable attribution for one adjacent-Agent message. */
888
+ function agentMessageSource(sender) {
889
+ return {
890
+ kind: "agent-message",
891
+ form: "relay",
892
+ senderSessionId: sender.id
893
+ };
894
+ }
895
+ /** Build the model-visible and durable representation of one adjacent-Agent message. */
896
+ function agentMessage(sender, content) {
897
+ return createUserMessage({
898
+ content: [{
899
+ type: "text",
900
+ text: `Agent ${sender.id} sent a message:`
901
+ }, ...content],
902
+ source: agentMessageSource(sender)
903
+ });
904
+ }
905
+ /** Append adjacent-Agent return guidance to a continuable child's initial task. */
906
+ function continuableInitialPrompt(parentId, prompt) {
907
+ const encodedParentId = JSON.stringify(parentId);
908
+ return [...prompt, {
909
+ type: "text",
910
+ 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.`
911
+ }];
912
+ }
677
913
  /**
678
914
  * One line telling a parent that a background child is finished and why, in
679
915
  * the parent's own task vocabulary.
@@ -723,7 +959,6 @@ var ChildLock = class {
723
959
  var SubagentContinuationManager = class {
724
960
  ctx;
725
961
  host;
726
- setupRegistry;
727
962
  /** Child session id → its live Activation. Process-local, never durable. */
728
963
  activations = /* @__PURE__ */ new Map();
729
964
  /** Materializations admitted before drain, tracked through publication or rollback. */
@@ -739,10 +974,9 @@ var SubagentContinuationManager = class {
739
974
  */
740
975
  closingScopes = /* @__PURE__ */ new Map();
741
976
  draining = false;
742
- constructor(ctx, host, setupRegistry) {
977
+ constructor(ctx, host) {
743
978
  this.ctx = ctx;
744
979
  this.host = host;
745
- this.setupRegistry = setupRegistry;
746
980
  const scope = ctx.plugin(function activationOwner() {});
747
981
  this.ownerCtx = scope.ctx;
748
982
  ctx.on("agent/disposed", ({ agent }) => {
@@ -772,83 +1006,202 @@ var SubagentContinuationManager = class {
772
1006
  const request = spec.request;
773
1007
  const parent = request.parent;
774
1008
  this.assertAdmitting(parent);
775
- this.requirePersistence();
1009
+ const persistence = this.requirePersistence();
776
1010
  assertSubagentMaxDepth(request.maxDepth);
777
- const childId = SessionId(randomUUID());
1011
+ const childId = spec.childId ?? brandString(randomUUID());
1012
+ this.assertChildIdAvailable(childId);
778
1013
  const childDepth = resolveChildDepth(parent, request.maxDepth);
779
- const agentProvider = request.agentOptions?.provider ?? parent.options.provider;
780
- const agentModel = request.agentOptions?.model ?? parent.options.model;
1014
+ const agentOptions = resolveChildAgentOptions(parent, request.agentOptions, childDepth);
1015
+ const agentProvider = agentOptions.provider;
1016
+ const agentModel = agentOptions.model;
1017
+ const agentReasoningEffort = agentOptions.reasoningEffort;
781
1018
  const descriptor = snapshotSubagentDescriptor({
782
1019
  mode: "continuable",
783
1020
  provider: spec.provider,
784
1021
  label: spec.label,
785
1022
  ...agentProvider !== void 0 ? { agentProvider } : {},
786
1023
  ...agentModel !== void 0 ? { agentModel } : {},
1024
+ ...agentReasoningEffort !== void 0 ? { agentReasoningEffort } : {},
787
1025
  ...request.persona !== void 0 ? { persona: request.persona } : {},
788
1026
  ...request.toolFilter !== void 0 ? { toolFilter: request.toolFilter } : {}
789
1027
  });
790
1028
  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
- })
1029
+ const releaseHold = this.holdOwnership(parent, childId);
1030
+ try {
1031
+ const prepared = await this.host.prepareContinuable(spec.provider, {
1032
+ sessionId: childId,
1033
+ parent,
1034
+ signal: spec.signal
1035
+ });
1036
+ spec.signal.throwIfAborted();
1037
+ this.assertAdmitting(parent);
1038
+ const inheritedEventCount = SessionLogOffset(prepared.seed?.length ?? 0);
1039
+ const seed = seedDescriptorTurn(childId, prepared.seed, descriptor);
1040
+ return {
1041
+ childId,
1042
+ messageId: await this.locks.run(childId, async () => {
1043
+ spec.signal.throwIfAborted();
1044
+ this.assertAdmitting(parent);
1045
+ this.assertChildIdAvailable(childId);
1046
+ if (spec.childId !== void 0) {
1047
+ const persisted = await persistence.stat(childId, { signal: spec.signal });
1048
+ spec.signal.throwIfAborted();
1049
+ this.assertAdmitting(parent);
1050
+ this.assertChildIdAvailable(childId);
1051
+ if (persisted !== void 0) throw new SubagentError(`subagent "${childId}" already exists`, "DUPLICATE_CHILD");
1052
+ }
1053
+ const activation = await this.materialize({
1054
+ childId,
1055
+ provider: spec.provider,
1056
+ parent,
1057
+ create: {
1058
+ seed,
1059
+ meta: childSessionMeta(parent, childDepth, prepared.seed !== void 0),
1060
+ inheritedEventCount,
1061
+ delegatedPolicies
1062
+ },
1063
+ agentOptions,
1064
+ composition: {
1065
+ persona: request.persona,
1066
+ toolFilter: request.toolFilter
1067
+ },
1068
+ signal: spec.signal
1069
+ });
1070
+ return this.submitMaterialized(activation, isAdjacentAgentSendMessageTool(this.ctx.get("tools")?.get("send_message", activation.handle.agent)) ? continuableInitialPrompt(parent.id, request.prompt) : request.prompt, {
1071
+ source: { kind: "user" },
1072
+ signal: spec.signal,
1073
+ delivery: "queue"
1074
+ }, parent);
1075
+ })
1076
+ };
1077
+ } catch (error) {
1078
+ releaseHold();
1079
+ throw error;
1080
+ }
1081
+ }
1082
+ /**
1083
+ * Pre-register `childId` in a continuation-managed parent's owned set so the
1084
+ * parent cannot settle while a caller is still establishing or resuming that
1085
+ * child. Returns a releaser for the failure path; it removes only a hold
1086
+ * this call added, and leaves ownership in place once a live Activation for
1087
+ * the child exists (an admitted delivery owns it from then on). A parent
1088
+ * without an Activation needs no hold: only this manager settles parents.
1089
+ * @param parent - the live direct parent the operation is admitted under.
1090
+ * @param childId - the durable child the operation addresses.
1091
+ * @returns the failure-path releaser; a no-op when nothing was added.
1092
+ * @throws {SubagentError} `ACTIVATION_CLOSING` when the parent's own
1093
+ * disposal transaction is already open.
1094
+ */
1095
+ holdOwnership(parent, childId) {
1096
+ const parentActivation = this.activations.get(parent.id);
1097
+ if (parentActivation === void 0 || parentActivation.handle.agent !== parent) return () => {};
1098
+ if (parentActivation.disposal !== void 0) throw new SubagentError(`subagent parent "${parent.id}" is being disposed; the child was not established`, "ACTIVATION_CLOSING");
1099
+ if (parentActivation.ownedChildren.has(childId)) return () => {};
1100
+ parentActivation.ownedChildren.add(childId);
1101
+ return () => {
1102
+ const live = this.activations.get(childId);
1103
+ /* v8 ignore next 4 -- reaching this arm needs another delivery to establish the child
1104
+ * between this operation's failure and its releaser running, which no test can schedule
1105
+ * deterministically: the ownership edge then belongs to that live Activation, so the
1106
+ * conservative keep leaves it for finishDisposal's releaseOwnership. */
1107
+ if (live !== void 0 && live.disposal === void 0) return;
1108
+ if (parentActivation.ownedChildren.delete(childId)) this.wake(parentActivation);
821
1109
  };
822
1110
  }
1111
+ /** Reject one child identity already owned by a live Agent or Session. */
1112
+ assertChildIdAvailable(childId) {
1113
+ 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");
1114
+ }
823
1115
  /**
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.
1116
+ * Deliver one model-authored message to a direct continuable child or to the
1117
+ * sender's direct parent. Both directions use Steer: a running target admits
1118
+ * the message at its nearest step boundary, while an idle target starts a
1119
+ * turn. A missing direct child cold-resumes through the ordinary continuation
1120
+ * lifecycle. The caller signal owns the operation only until inbox acceptance.
1121
+ * @param sender - exact live Agent authorizing and originating the message.
1122
+ * @param targetId - durable direct-parent or direct-child session id.
1123
+ * @param content - model-authored content to deliver.
1124
+ * @param options - caller cancellation before acceptance.
837
1125
  * @returns the accepted message's inbox id.
838
- * @throws when parent authority, availability, or admission rejects the delivery.
1126
+ * @throws when adjacency, availability, or admission rejects delivery.
839
1127
  */
840
- async followup(parent, childId, content, options) {
1128
+ async sendMessage(sender, targetId, content, options) {
1129
+ if (this.ctx.agents.get(sender.id) !== sender) throw new SubagentError("message delivery requires the exact live sender agent", "UNAUTHORIZED");
1130
+ this.assertAdmitting(sender);
1131
+ const senderActivation = this.activations.get(sender.id);
1132
+ if (senderActivation !== void 0 && senderActivation.handle.agent === sender && senderActivation.parentSession === targetId) {
1133
+ options.signal.throwIfAborted();
1134
+ return this.sendToParent(senderActivation, sender, content);
1135
+ }
1136
+ 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");
1137
+ return this.deliverToChild(sender, targetId, content, {
1138
+ signal: options.signal,
1139
+ delivery: "steer"
1140
+ });
1141
+ }
1142
+ /**
1143
+ * Queue one human-authored prompt as a distinct direct-child turn.
1144
+ * @param parent - exact live direct parent authorizing delivery.
1145
+ * @param childId - durable direct-child session id.
1146
+ * @param content - human-authored content to deliver.
1147
+ * @param source - durable host-protocol provenance.
1148
+ * @param signal - caller cancellation before inbox acceptance.
1149
+ * @returns the accepted message's inbox id.
1150
+ */
1151
+ async queuePrompt(parent, childId, content, source, signal) {
1152
+ return this.deliverToChild(parent, childId, content, {
1153
+ source,
1154
+ signal,
1155
+ delivery: "queue"
1156
+ });
1157
+ }
1158
+ /**
1159
+ * Steer one host-authored prompt to a direct continuable child.
1160
+ * @param parent - exact live direct parent authorizing delivery.
1161
+ * @param childId - durable direct-child session id.
1162
+ * @param content - host-authored content to deliver.
1163
+ * @param source - durable host-protocol provenance.
1164
+ * @param signal - caller cancellation before inbox acceptance.
1165
+ * @returns the accepted message's inbox id.
1166
+ */
1167
+ async steerPrompt(parent, childId, content, source, signal) {
1168
+ return this.deliverToChild(parent, childId, content, {
1169
+ source,
1170
+ signal,
1171
+ delivery: "steer"
1172
+ });
1173
+ }
1174
+ /** Route one parent-originated delivery through residency and cold resume. */
1175
+ async deliverToChild(parent, childId, content, options) {
841
1176
  this.assertAdmitting(parent);
1177
+ const releaseHold = this.holdOwnership(parent, childId);
1178
+ try {
1179
+ return await this.deliverFollowup(parent, childId, content, options);
1180
+ } catch (error) {
1181
+ releaseHold();
1182
+ throw error;
1183
+ }
1184
+ }
1185
+ /** The delivery loop behind {@link deliverToChild}, run under the parent hold. */
1186
+ async deliverFollowup(parent, childId, content, options) {
842
1187
  while (true) {
843
1188
  const live = await this.locks.run(childId, async () => {
844
1189
  const activation = this.activations.get(childId);
845
1190
  if (activation === void 0) return this.coldResume(parent, childId, content, options);
1191
+ const disposal = activation.disposal;
846
1192
  /* v8 ignore next 3 -- the send-versus-dispose cutoff: reaching this arm needs a
847
1193
  * delivery to observe the transaction inside the same critical section that opened it,
848
1194
  * which no test can schedule deterministically. The behavior is covered end-to-end by
849
1195
  * "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);
1196
+ if (disposal !== void 0) return disposal.then(() => void 0, () => void 0);
1197
+ if (contentHasImage(content)) {
1198
+ await this.assertImageCapable(activation.handle.agent, options.signal);
1199
+ if (activation.disposal !== void 0) {
1200
+ await Promise.allSettled([activation.disposal]);
1201
+ return;
1202
+ }
1203
+ }
1204
+ return this.submitAdmitted(activation, content, options, parent);
852
1205
  });
853
1206
  /* v8 ignore start -- only the lost-cutoff arm above returns undefined, so only that
854
1207
  * race reaches the retry below, which then cold-resumes a new Activation. */
@@ -892,66 +1245,24 @@ var SubagentContinuationManager = class {
892
1245
  if (activation.disposal !== void 0) return;
893
1246
  activation.handle.agent.cancel(authority.kind === "user" ? { kind: "user" } : { kind: "parent" }, { keepInbox: true });
894
1247
  }
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
- }
943
- });
944
- if (delivery === "wakeup") this.sendWaking(parent, message, () => {
945
- this.sendReport(parent, message, delivery);
1248
+ /** Deliver one resident continuable child's message to its live direct parent. */
1249
+ sendToParent(activation, sender, content) {
1250
+ /* v8 ignore next 6 -- only synchronous re-entrant teardown can open this
1251
+ * transaction between exact-agent authorization and this no-await span. */
1252
+ if (activation.disposal !== void 0) throw new SubagentError(`subagent "${sender.id}" activation is being disposed; the message was not delivered`, "ACTIVATION_CLOSING");
1253
+ const parent = this.ctx.agents.get(activation.parentSession);
1254
+ if (parent === void 0) throw new SubagentError("direct parent is not live; the message was not delivered", "PARENT_UNAVAILABLE");
1255
+ const message = agentMessage(sender, content);
1256
+ this.sendWaking(parent, message, () => {
1257
+ this.sendAgentMessage(parent, message);
946
1258
  });
947
- else this.sendReport(parent, message, delivery);
948
1259
  return message.id;
949
1260
  }
950
1261
  /**
951
1262
  * Perform one waking send to a parent, accounted against that parent's own
952
1263
  * Activation when it has one. Registering the id before the send is what
953
1264
  * keeps a continuation-managed parent from being judged quiescent in the
954
- * window between `followup()` and the microtask that admits it.
1265
+ * window between a waking send and the microtask that admits it.
955
1266
  * @param parent - the exact live parent receiving the waking message.
956
1267
  * @param message - the message whose id is accounted.
957
1268
  * @param send - the synchronous waking send to perform.
@@ -961,13 +1272,12 @@ var SubagentContinuationManager = class {
961
1272
  if (parentActivation !== void 0 && parentActivation.handle.agent === parent) this.admitWaking(parentActivation, message.id, send);
962
1273
  else send();
963
1274
  }
964
- /** Send one report while translating only the parent's own rejection. */
965
- sendReport(parent, message, delivery) {
1275
+ /** Send one Agent message while translating only the target's own rejection. */
1276
+ sendAgentMessage(parent, message) {
966
1277
  try {
967
- if (delivery === "wakeup") parent.followup(message);
968
- else parent.inject(message);
1278
+ parent.steer(message);
969
1279
  } catch (error) {
970
- throw new SubagentError("direct parent is not live; report was not delivered", "PARENT_UNAVAILABLE", { cause: error });
1280
+ throw new SubagentError("direct parent is not live; the message was not delivered", "PARENT_UNAVAILABLE", { cause: error });
971
1281
  }
972
1282
  }
973
1283
  /**
@@ -1027,6 +1337,28 @@ var SubagentContinuationManager = class {
1027
1337
  await Promise.all(materializations.map((materialization) => materialization.settled));
1028
1338
  await this.disposeRoots(targetRoots, "scoped activation(s)");
1029
1339
  }
1340
+ /**
1341
+ * Release selected resident direct children of one exact live parent without
1342
+ * closing admission for the parent's other continuable children. Owned
1343
+ * descendants are released recursively through the same lifecycle.
1344
+ * @param parent - exact live direct parent authorizing the selected release.
1345
+ * @param childIds - durable direct-child ids to release when resident.
1346
+ * @returns once every selected Activation released its handle.
1347
+ * @throws {SubagentError} `UNAUTHORIZED` when a resident target is not the
1348
+ * parent's direct continuable child or the parent identity is stale.
1349
+ */
1350
+ async drainChildren(parent, childIds) {
1351
+ if (this.ctx.agents.get(parent.id) !== parent) throw new SubagentError("selected child teardown requires the exact live parent agent", "UNAUTHORIZED");
1352
+ const targets = [];
1353
+ for (const childId of new Set(childIds)) {
1354
+ const activation = this.activations.get(childId);
1355
+ if (activation === void 0) continue;
1356
+ if (activation.parentSession !== parent.id || !activation.ancestry.has(parent)) throw new SubagentError(`subagent "${childId}" is not a direct child of agent "${parent.id}"`, "UNAUTHORIZED");
1357
+ targets.push(activation);
1358
+ }
1359
+ for (const activation of targets) this.dispose(activation).catch(() => void 0);
1360
+ await this.disposeRoots(targets, "selected activation(s)");
1361
+ }
1030
1362
  /** Dispose independent roots and report every branch failure after all settle. */
1031
1363
  async disposeRoots(roots, failureSubject) {
1032
1364
  const reasons = (await Promise.all(roots.map(async (activation) => {
@@ -1098,61 +1430,77 @@ var SubagentContinuationManager = class {
1098
1430
  return "settled";
1099
1431
  }
1100
1432
  /**
1101
- * Cold-resume a persisted child: inspect and authorize its Session, fold the
1433
+ * Cold-resume a persisted child: retain and authorize its prepared Session, fold the
1102
1434
  * generic descriptor, create the Activation through `ctx.agents.resume()`,
1103
1435
  * and submit the waiting turn. This never dispatches through a subagent
1104
1436
  * provider — the persisted Session already holds the initial prefix and the
1105
1437
  * descriptor is the whole reconstruction input.
1106
1438
  */
1107
1439
  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;
1440
+ const env_1 = {
1441
+ stack: [],
1442
+ error: void 0,
1443
+ hasError: false
1444
+ };
1122
1445
  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 });
1446
+ const query = this.requireSessionQuery();
1447
+ let observation;
1448
+ try {
1449
+ observation = await query.observeSession(childId, { signal: options.signal });
1450
+ } catch (error) {
1451
+ options.signal.throwIfAborted();
1452
+ throw new SubagentError(`subagent "${childId}" is unavailable`, "NOT_RESUMABLE", { cause: error });
1453
+ }
1454
+ const source = __addDisposableResource$1(env_1, observation, false);
1455
+ this.assertAdmitting(parent);
1456
+ this.authorizeLineage(parent, childId, source.header.parentSession);
1457
+ const descriptor = foldSubagentDescriptor(source.events.slice(source.inheritedEventCount));
1458
+ 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");
1459
+ let activation;
1460
+ try {
1461
+ activation = await this.materialize({
1462
+ childId,
1463
+ provider: descriptor.provider,
1464
+ parent,
1465
+ agentOptions: {
1466
+ ...descriptor.agentProvider !== void 0 ? { provider: descriptor.agentProvider } : {},
1467
+ ...descriptor.agentModel !== void 0 ? { model: descriptor.agentModel } : {},
1468
+ ...descriptor.agentReasoningEffort !== void 0 ? { reasoningEffort: ReasoningEffortId(descriptor.agentReasoningEffort) } : {}
1469
+ },
1470
+ composition: {
1471
+ persona: descriptor.persona,
1472
+ toolFilter: descriptor.toolFilter
1473
+ },
1474
+ signal: options.signal
1475
+ });
1476
+ } catch (error) {
1477
+ options.signal.throwIfAborted();
1478
+ if (error instanceof SubagentError) throw error;
1479
+ throw new SubagentError(`subagent "${childId}" is unavailable`, "NOT_RESUMABLE", { cause: error });
1480
+ }
1481
+ return await this.submitMaterialized(activation, content, options, parent);
1482
+ } catch (e_1) {
1483
+ env_1.error = e_1;
1484
+ env_1.hasError = true;
1485
+ } finally {
1486
+ __disposeResources$1(env_1);
1141
1487
  }
1142
- return this.submitMaterialized(activation, content, options.source, parent, options.signal);
1143
1488
  }
1144
1489
  /**
1145
1490
  * Submit to a freshly materialized Activation or roll it back completely.
1146
1491
  * @param activation - the just-published Activation to admit or release.
1147
1492
  * @param content - the initial or resumed message content.
1148
- * @param source - durable fields naming who supplied the accepted message.
1493
+ * @param options - durable source, scheduling, and pre-acceptance cancellation.
1149
1494
  * @param parent - the live direct parent authorizing admission.
1150
- * @param signal - caller cancellation owning admission until acceptance.
1151
1495
  * @returns the accepted inbox message id.
1152
1496
  */
1153
- async submitMaterialized(activation, content, source, parent, signal) {
1497
+ async submitMaterialized(activation, content, options, parent) {
1154
1498
  try {
1155
- return this.submitAdmitted(activation, content, source, parent, signal);
1499
+ if (contentHasImage(content)) {
1500
+ await this.assertImageCapable(activation.handle.agent, options.signal);
1501
+ if (activation.disposal !== void 0) throw new SubagentError(`subagent "${activation.childId}" is closing`, "ACTIVATION_CLOSING");
1502
+ }
1503
+ return this.submitAdmitted(activation, content, options, parent);
1156
1504
  } catch (error) {
1157
1505
  /* v8 ignore next -- rollback disposal failures must not mask the
1158
1506
  * pre-acceptance signal, drain, or lifecycle failure. */
@@ -1161,6 +1509,28 @@ var SubagentContinuationManager = class {
1161
1509
  }
1162
1510
  }
1163
1511
  /**
1512
+ * Refuse image content addressed to a child whose model accepts text only.
1513
+ * Callers guard with `contentHasImage`, so text-only delivery never awaits.
1514
+ * The check runs inside the per-child delivery lock, before the message
1515
+ * exists, so a rejection leaves no partial user message. When the child's
1516
+ * route is not fixed by its options (a request-waterfall listener owns it)
1517
+ * or no LLM registry is composed, delivery proceeds and the LLM layer's
1518
+ * text-only projection replaces each image with its stable placeholder.
1519
+ * @param agent - the live or freshly materialized child agent.
1520
+ * @param signal - caller cancellation bounding the model-info read.
1521
+ * @throws {SubagentError} `MODEL_DOES_NOT_SUPPORT_IMAGES` when the child's resolved model declines image input.
1522
+ */
1523
+ async assertImageCapable(agent, signal) {
1524
+ const { provider, model } = agent.options;
1525
+ if (provider === void 0 || model === void 0) return;
1526
+ const llm = this.ctx.get("llm");
1527
+ /* v8 ignore next -- a deployment without the LLM registry serves no model
1528
+ * to refuse against; delivery then defers to the text-only projection. */
1529
+ if (llm === void 0) return;
1530
+ const info = await llm.resolveModelInfo(provider, model, signal);
1531
+ if (info.inputModalities !== void 0 && !info.inputModalities.includes("image")) throw new SubagentError(`Model "${model}" does not support image input.`, "MODEL_DOES_NOT_SUPPORT_IMAGES");
1532
+ }
1533
+ /**
1164
1534
  * Create or resume the child Agent through the private activation-owner
1165
1535
  * scope, install the handle in a fresh Activation, and register ownership on
1166
1536
  * a continuation-managed parent. Rejection leaves no Activation, no handle,
@@ -1191,7 +1561,6 @@ var SubagentContinuationManager = class {
1191
1561
  const setup = (childCtx) => {
1192
1562
  if (create !== void 0) appendDelegatedPolicyOverrides(childCtx.agent.session, create.delegatedPolicies);
1193
1563
  applyChildComposition(childCtx, parent, inputs.composition);
1194
- return this.setupRegistry.apply(childCtx);
1195
1564
  };
1196
1565
  const observer = this.host.observeActivation(provider, childId, parent);
1197
1566
  const handle = create === void 0 ? await this.ownerCtx.agents.resume({
@@ -1203,6 +1572,7 @@ var SubagentContinuationManager = class {
1203
1572
  sessionId: childId,
1204
1573
  meta: create.meta,
1205
1574
  seed: create.seed,
1575
+ inheritedEventCount: create.inheritedEventCount,
1206
1576
  agentOptions: inputs.agentOptions,
1207
1577
  signal: inputs.signal,
1208
1578
  setup
@@ -1284,14 +1654,15 @@ var SubagentContinuationManager = class {
1284
1654
  * inbox id. Acceptance is the operation's success boundary; the manager owns
1285
1655
  * the Activation independently afterwards.
1286
1656
  */
1287
- submit(activation, content, source, parent) {
1657
+ submit(activation, content, options, parent) {
1288
1658
  this.acquireOwnership(parent, activation.childId);
1289
- const message = createUserMessage({
1659
+ const message = options.source === void 0 ? agentMessage(parent, content) : createUserMessage({
1290
1660
  content,
1291
- source
1661
+ source: options.source
1292
1662
  });
1293
1663
  const accepted = this.admitWaking(activation, message.id, () => {
1294
- activation.handle.agent.followup(message);
1664
+ if (options.delivery === "steer") activation.handle.agent.steer(message);
1665
+ else activation.handle.agent.followup(message);
1295
1666
  });
1296
1667
  activation.announced = true;
1297
1668
  return accepted;
@@ -1319,14 +1690,14 @@ var SubagentContinuationManager = class {
1319
1690
  * manager drain, or Activation disposal that wins before this synchronous
1320
1691
  * span rejects without inbox acceptance.
1321
1692
  */
1322
- submitAdmitted(activation, content, source, parent, signal) {
1323
- signal.throwIfAborted();
1693
+ submitAdmitted(activation, content, options, parent) {
1694
+ options.signal.throwIfAborted();
1324
1695
  this.assertAdmitting(parent);
1325
1696
  /* v8 ignore next 6 -- only a synchronous re-entrant disposer can change
1326
1697
  * this field between the caller's live check and this no-await boundary. */
1327
1698
  if (disposalOf(activation) !== void 0) throw new SubagentError(`subagent "${activation.childId}" activation is being disposed; the message was not accepted`, "ACTIVATION_CLOSING");
1328
1699
  this.authorizeLineage(parent, activation.childId, activation.handle.agent.session.header.parentSession);
1329
- return this.submit(activation, content, source, parent);
1700
+ return this.submit(activation, content, options, parent);
1330
1701
  }
1331
1702
  /**
1332
1703
  * Authorize one operation against the durable direct-parent lineage. Other
@@ -1504,147 +1875,26 @@ var SubagentContinuationManager = class {
1504
1875
  if (persistence === void 0) throw new SubagentError("continuable subagents require session persistence (load a dsh-session-persistence backend)", "PERSISTENCE_UNAVAILABLE");
1505
1876
  return persistence;
1506
1877
  }
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();
1878
+ /** Resolve the Session query service used for cold child observations. */
1879
+ requireSessionQuery() {
1880
+ const query = this.ctx.get("sessionQuery");
1881
+ if (query === void 0) throw new SubagentError("continuable subagents require session query (load @xneog/dsh-session-query)", "CONTINUATION_UNAVAILABLE");
1882
+ return query;
1634
1883
  }
1635
1884
  };
1636
1885
  //#endregion
1637
1886
  //#region lib/types/list-children.js
1638
1887
  /**
1639
1888
  * 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
1889
+ * through the Session query service. Candidates come from one live-preferred
1890
+ * corpus; each child's mode/label is the registered `subagent` projection
1891
+ * unit's value, resolved
1643
1892
  * 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
1893
+ * an unseeded durable projection-cache row, and one shared Session observation
1894
+ * otherwise. A seeded header deliberately lacks its exact inherited cut, so
1895
+ * it takes the body-bearing observation path before classifying an identity.
1896
+ * The projection fold is the single classification
1897
+ * authority — this module parses no descriptor
1648
1898
  * itself. Absent persistence, enumeration is live-only: a cold child is
1649
1899
  * unreachable for resume anyway, so its absence is capability absence, not an
1650
1900
  * error. The module owns no catalog state and does not consult Activation,
@@ -1652,10 +1902,68 @@ var SubagentActivationSetupRegistry = class {
1652
1902
  *
1653
1903
  * @module @xneog/dsh-subagent
1654
1904
  */
1905
+ var __addDisposableResource = function(env, value, async) {
1906
+ if (value !== null && value !== void 0) {
1907
+ if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
1908
+ var dispose, inner;
1909
+ if (async) {
1910
+ if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
1911
+ dispose = value[Symbol.asyncDispose];
1912
+ }
1913
+ if (dispose === void 0) {
1914
+ if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
1915
+ dispose = value[Symbol.dispose];
1916
+ if (async) inner = dispose;
1917
+ }
1918
+ if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
1919
+ if (inner) dispose = function() {
1920
+ try {
1921
+ inner.call(this);
1922
+ } catch (e) {
1923
+ return Promise.reject(e);
1924
+ }
1925
+ };
1926
+ env.stack.push({
1927
+ value,
1928
+ dispose,
1929
+ async
1930
+ });
1931
+ } else if (async) env.stack.push({ async: true });
1932
+ return value;
1933
+ };
1934
+ var __disposeResources = (function(SuppressedError) {
1935
+ return function(env) {
1936
+ function fail(e) {
1937
+ env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
1938
+ env.hasError = true;
1939
+ }
1940
+ var r, s = 0;
1941
+ function next() {
1942
+ while (r = env.stack.pop()) try {
1943
+ if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
1944
+ if (r.dispose) {
1945
+ var result = r.dispose.call(r.value);
1946
+ if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) {
1947
+ fail(e);
1948
+ return next();
1949
+ });
1950
+ } else s |= 1;
1951
+ } catch (e) {
1952
+ fail(e);
1953
+ }
1954
+ if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
1955
+ if (env.hasError) throw env.error;
1956
+ }
1957
+ return next();
1958
+ };
1959
+ })(typeof SuppressedError === "function" ? SuppressedError : function(error, suppressed, message) {
1960
+ var e = new Error(message);
1961
+ return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
1962
+ });
1655
1963
  /**
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.
1964
+ * Concurrent cold observations per explicit catalog listing. Current Session
1965
+ * persistence providers are local; a networked provider must promote this to
1966
+ * a validated deployment setting.
1659
1967
  */
1660
1968
  const COLD_READ_CONCURRENCY = 4;
1661
1969
  /**
@@ -1663,9 +1971,8 @@ const COLD_READ_CONCURRENCY = 4;
1663
1971
  * live-preferred merge of `ctx.sessions` and optional session persistence,
1664
1972
  * serving each identity from the `subagent` projection unit: the registry's
1665
1973
  * 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.
1974
+ * projection-cache read for an unseeded lifecycle, else one bounded-concurrency
1975
+ * shared Session observation carrying the exact inherited cut.
1669
1976
  * @see SubagentRuntime.listChildren for the public cancellation and failure contract.
1670
1977
  * @param ctx - context carrying the session store, the projection registry,
1671
1978
  * optional persistence, and the optional projection cache.
@@ -1714,32 +2021,30 @@ async function prepareListing(ctx, signal) {
1714
2021
  const sessions = ctx.get("sessions");
1715
2022
  if (sessions === void 0) throw new SubagentError("listing subagents requires the session store (load @xneog/dsh-session)", "SUBAGENT_CONTROL_SESSION_STORE_UNAVAILABLE");
1716
2023
  assertListingNotCancelled(signal);
1717
- const persistence = ctx.get("sessionPersistence");
2024
+ const query = ctx.get("sessionQuery");
2025
+ if (query === void 0) throw new SubagentError("listing subagents requires the sessionQuery service (load @xneog/dsh-session-query)", "SUBAGENT_CONTROL_QUERY_UNAVAILABLE");
1718
2026
  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
- }
2027
+ let records;
2028
+ try {
2029
+ records = await query.listSessions(signal);
2030
+ } catch (error) {
1727
2031
  assertListingNotCancelled(signal);
2032
+ throw error;
1728
2033
  }
2034
+ assertListingNotCancelled(signal);
1729
2035
  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
- });
2036
+ for (const record of records) {
2037
+ const live = sessions.get(record.header.id);
2038
+ corpus.set(record.header.id, {
2039
+ header: live?.header ?? record.header,
2040
+ live
2041
+ });
2042
+ }
1738
2043
  const subagentParents = /* @__PURE__ */ new Set();
1739
2044
  for (const record of corpus.values()) if (record.header.origin === "subagent" && record.header.parentSession !== void 0) subagentParents.add(record.header.parentSession);
1740
2045
  return {
1741
2046
  projections,
1742
- persistence,
2047
+ query,
1743
2048
  cache,
1744
2049
  corpus,
1745
2050
  subagentParents
@@ -1747,7 +2052,7 @@ async function prepareListing(ctx, signal) {
1747
2052
  }
1748
2053
  /** Resolve projection-backed rows for aligned candidates with bounded cold reads. */
1749
2054
  async function resolveCandidateRows(candidates, listing, signal) {
1750
- const { projections, persistence, cache, subagentParents } = listing;
2055
+ const { projections, query, cache, subagentParents } = listing;
1751
2056
  const rows = Array.from({ length: candidates.length });
1752
2057
  const coldReads = [];
1753
2058
  candidates.forEach((candidate, index) => {
@@ -1761,7 +2066,7 @@ async function resolveCandidateRows(candidates, listing, signal) {
1761
2066
  }
1762
2067
  let identity;
1763
2068
  try {
1764
- identity = projections.snapshot(candidate.live).values.subagent;
2069
+ identity = projections.snapshot(candidate.live, ["subagent"]).values.subagent;
1765
2070
  } catch {
1766
2071
  rows[index] = {
1767
2072
  kind: "diagnostic",
@@ -1770,13 +2075,13 @@ async function resolveCandidateRows(candidates, listing, signal) {
1770
2075
  };
1771
2076
  return;
1772
2077
  }
1773
- if (identity === void 0 || identity === null) return;
2078
+ if (identity === void 0 || identity === null || !candidate.live.isOwnSeq(identity.seq)) return;
1774
2079
  rows[index] = childRow(childId, identity, "running", subagentParents.has(childId));
1775
2080
  });
1776
- if (persistence !== void 0 && coldReads.length > 0) {
2081
+ if (coldReads.length > 0) {
1777
2082
  const queue = [...coldReads];
1778
2083
  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);
2084
+ 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
2085
  }));
1781
2086
  }
1782
2087
  assertListingNotCancelled(signal);
@@ -1820,60 +2125,62 @@ function compareCorpusRecords(a, b) {
1820
2125
  return a.header.createdAt - b.header.createdAt || a.header.id.localeCompare(b.header.id);
1821
2126
  }
1822
2127
  /**
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
2128
+ * Resolve one cold candidate down the remaining ladder: an unseeded durable
2129
+ * projection-cache row, otherwise one shared Session observation. An absent or transiently failed
2130
+ * observation is one `unavailable` row retried on the next listing; an observation
2131
+ * source naming another lifecycle, and a
1829
2132
  * settled log the fold cannot identify — or that makes any registered unit
1830
2133
  * throw — are final, so they report `corrupt`.
1831
2134
  */
1832
- async function resolveColdIdentity(persistence, projections, cache, header, hasChildren, signal) {
1833
- const childId = header.id;
1834
- if (cache !== void 0) {
1835
- let cached;
2135
+ async function resolveColdIdentity(query, cache, header, hasChildren, signal) {
2136
+ const env_1 = {
2137
+ stack: [],
2138
+ error: void 0,
2139
+ hasError: false
2140
+ };
2141
+ try {
2142
+ const childId = header.id;
2143
+ if (cache !== void 0 && !header.isSeeded) {
2144
+ let cached;
2145
+ try {
2146
+ cached = cache.cachedSnapshot(header, SessionLogOffset(0), ["subagent"])?.values.subagent;
2147
+ } catch {
2148
+ cached = void 0;
2149
+ }
2150
+ if (cached !== void 0 && cached !== null) return childRow(childId, cached, "inactive", hasChildren);
2151
+ }
2152
+ assertListingNotCancelled(signal);
2153
+ let observation;
1836
2154
  try {
1837
- cached = cache.cachedSnapshot(header)?.values.subagent;
1838
- } catch {
1839
- cached = void 0;
2155
+ observation = await query.observeSession(childId, { ...signal === void 0 ? {} : { signal } });
2156
+ } catch (error) {
2157
+ assertListingNotCancelled(signal);
2158
+ return {
2159
+ kind: "diagnostic",
2160
+ id: childId,
2161
+ reason: sessionQueryCode(error) === "SESSION_QUERY_CORRUPT_SESSION" || sessionQueryCode(error) === "SESSION_QUERY_SOURCE_CONFLICT" ? "corrupt" : "unavailable"
2162
+ };
1840
2163
  }
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 {
2164
+ const ownedObservation = __addDisposableResource(env_1, observation, false);
1848
2165
  assertListingNotCancelled(signal);
1849
- return {
2166
+ if (!sameLifecycle(ownedObservation.header, header)) return {
1850
2167
  kind: "diagnostic",
1851
2168
  id: childId,
1852
- reason: "unavailable"
2169
+ reason: "corrupt"
1853
2170
  };
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 {
2171
+ const identity = ownedObservation.projections?.values.subagent;
2172
+ if (identity === void 0 || identity === null || identity.seq < ownedObservation.inheritedEventCount) return {
1866
2173
  kind: "diagnostic",
1867
2174
  id: childId,
1868
2175
  reason: "corrupt"
1869
2176
  };
2177
+ return childRow(childId, identity, "inactive", hasChildren);
2178
+ } catch (e_1) {
2179
+ env_1.error = e_1;
2180
+ env_1.hasError = true;
2181
+ } finally {
2182
+ __disposeResources(env_1);
1870
2183
  }
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
2184
  }
1878
2185
  /** Materialize one served identity as its child row. */
1879
2186
  function childRow(id, identity, activity, hasChildren) {
@@ -1900,8 +2207,10 @@ const LIFECYCLE_WITNESS_KEYS = [
1900
2207
  "createdAt",
1901
2208
  "cwd",
1902
2209
  "parentSession",
1903
- "seedLength",
1904
- "delegationDepth"
2210
+ "isSeeded",
2211
+ "delegationDepth",
2212
+ "origin",
2213
+ "agentPreset"
1905
2214
  ];
1906
2215
  /** Whether an inspected log still belongs to the enumerated lifecycle. */
1907
2216
  function sameLifecycle(meta, expected) {
@@ -1911,6 +2220,28 @@ function sameLifecycle(meta, expected) {
1911
2220
  function assertListingNotCancelled(signal) {
1912
2221
  if (signal?.aborted) throw new SubagentError("subagent listing was cancelled", "CANCELLED");
1913
2222
  }
2223
+ function sessionQueryCode(error) {
2224
+ return error instanceof Error && "code" in error ? error.code : void 0;
2225
+ }
2226
+ //#endregion
2227
+ //#region lib/types/projection.js
2228
+ /**
2229
+ * Pure session projections for subagent identity (mode/label) and active-turn
2230
+ * duration.
2231
+ *
2232
+ * @module @xneog/dsh-subagent/projection
2233
+ */
2234
+ const activeIntervalSchema = z.object({
2235
+ since: z.number().int().nonnegative(),
2236
+ through: z.number().int().nonnegative()
2237
+ }).strict();
2238
+ const projectionSchema = z.object({
2239
+ settledMs: z.number().int().nonnegative(),
2240
+ active: activeIntervalSchema.optional()
2241
+ }).strict().transform(({ settledMs, active }) => ({
2242
+ settledMs,
2243
+ ...active === void 0 ? {} : { active }
2244
+ }));
1914
2245
  /**
1915
2246
  * Fold turn boundaries around the child's own durable descriptor.
1916
2247
  *
@@ -1921,12 +2252,11 @@ function assertListingNotCancelled(signal) {
1921
2252
  */
1922
2253
  const subagentTimingProjectionDefinition = {
1923
2254
  key: "subagentTiming",
1924
- schema: z.object({
2255
+ stateSchema: z.object({
1925
2256
  settledMs: z.number().int().nonnegative(),
1926
- active: z.object({
1927
- since: z.number().int().nonnegative(),
1928
- through: z.number().int().nonnegative()
1929
- }).strict().optional()
2257
+ active: activeIntervalSchema.optional(),
2258
+ pendingTurnStart: z.number().int().nonnegative().optional(),
2259
+ descriptorSeen: z.boolean()
1930
2260
  }).strict(),
1931
2261
  init: () => ({
1932
2262
  descriptorSeen: false,
@@ -1976,21 +2306,26 @@ const subagentTimingProjectionDefinition = {
1976
2306
  }
1977
2307
  };
1978
2308
  },
1979
- view: (state) => ({
1980
- settledMs: state.settledMs,
1981
- ...state.active === void 0 ? {} : { active: state.active }
1982
- }),
2309
+ wire: {
2310
+ viewSchema: projectionSchema,
2311
+ view: (state) => ({
2312
+ settledMs: state.settledMs,
2313
+ ...state.active === void 0 ? {} : { active: state.active }
2314
+ })
2315
+ },
1983
2316
  stateVersion: 2
1984
2317
  };
1985
- const identitySchema = z.discriminatedUnion("mode", [z.object({
2318
+ const identityValueSchema = z.discriminatedUnion("mode", [z.object({
1986
2319
  mode: z.literal("one-shot"),
1987
2320
  label: z.string().optional(),
1988
- seq: z.number().int().nonnegative()
2321
+ seq: z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER).transform(SessionSeq)
1989
2322
  }).strict(), z.object({
1990
2323
  mode: z.literal("continuable"),
1991
2324
  label: z.string(),
1992
- seq: z.number().int().nonnegative()
1993
- }).strict()]).nullable();
2325
+ seq: z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER).transform(SessionSeq)
2326
+ }).strict()]);
2327
+ const identitySchema = identityValueSchema.nullable();
2328
+ const identityStateSchema = z.object({ identity: identityValueSchema.optional() }).strict();
1994
2329
  /** Interpret one `subagent/descriptor` event's identity; no value when the payload cannot be trusted. */
1995
2330
  function descriptorIdentity(event) {
1996
2331
  let descriptor;
@@ -2023,14 +2358,17 @@ function descriptorIdentity(event) {
2023
2358
  */
2024
2359
  const subagentIdentityProjectionDefinition = {
2025
2360
  key: "subagent",
2026
- schema: identitySchema,
2361
+ stateSchema: identityStateSchema,
2027
2362
  init: () => ({}),
2028
2363
  apply: (state, event) => {
2029
2364
  if (event.type !== "subagent/descriptor") return state;
2030
2365
  const identity = descriptorIdentity(event);
2031
2366
  return identity === void 0 ? {} : { identity };
2032
2367
  },
2033
- view: (state) => state.identity ?? null,
2368
+ wire: {
2369
+ viewSchema: identitySchema,
2370
+ view: (state) => state.identity ?? null
2371
+ },
2034
2372
  stateVersion: 2
2035
2373
  };
2036
2374
  //#endregion
@@ -2047,13 +2385,38 @@ const subagentIdentityProjectionDefinition = {
2047
2385
  *
2048
2386
  * @module @xneog/dsh-subagent/out-of-process
2049
2387
  */
2388
+ /** Maximum UTF-8 size of {@link SubagentResult.diagnostic}. */
2389
+ const MAX_SUBAGENT_DIAGNOSTIC_BYTES = 4096;
2390
+ const DIAGNOSTIC_TRUNCATION_SUFFIX = "\n[diagnostic truncated]";
2391
+ const utf8Encoder = new TextEncoder();
2392
+ const utf8Decoder = new TextDecoder();
2393
+ /**
2394
+ * Limit provider-authored failure detail without splitting a UTF-8 sequence.
2395
+ * @param diagnostic - safe diagnostic text produced by the provider.
2396
+ * @returns the original text, or a visibly truncated value within the limit.
2397
+ */
2398
+ function limitSubagentDiagnostic(diagnostic) {
2399
+ const bytes = utf8Encoder.encode(diagnostic);
2400
+ if (bytes.byteLength <= MAX_SUBAGENT_DIAGNOSTIC_BYTES) return diagnostic;
2401
+ let prefixBytes = MAX_SUBAGENT_DIAGNOSTIC_BYTES - utf8Encoder.encode(DIAGNOSTIC_TRUNCATION_SUFFIX).byteLength;
2402
+ while ((bytes[prefixBytes] & 192) === 128) prefixBytes -= 1;
2403
+ return utf8Decoder.decode(bytes.subarray(0, prefixBytes)) + DIAGNOSTIC_TRUNCATION_SUFFIX;
2404
+ }
2405
+ /** Enforce the byte limit on a provider-returned diagnostic. */
2406
+ function normalizeSubagentDiagnostic(result) {
2407
+ return result.diagnostic === void 0 ? result : {
2408
+ ...result,
2409
+ diagnostic: limitSubagentDiagnostic(result.diagnostic)
2410
+ };
2411
+ }
2050
2412
  /**
2051
2413
  * The capability advertisement of an out-of-process backend: NONE. A child in
2052
2414
  * another process cannot honor parent-enforced start features
2053
- * (`outputSchema`/`maxDepth`/`toolFilter`/`persona`), so the service rejects a
2415
+ * (`agentOptions`/`outputSchema`/`maxDepth`/`toolFilter`/`persona`), so the service rejects a
2054
2416
  * request needing any of them before `start` runs — never accepted-then-ignored.
2055
2417
  */
2056
2418
  const NO_START_CAPABILITIES = Object.freeze({
2419
+ agentOptions: false,
2057
2420
  outputSchema: false,
2058
2421
  depthLimit: false,
2059
2422
  toolFilter: false,
@@ -2141,7 +2504,8 @@ function toError(value) {
2141
2504
  * rejects after publication. A normally completed or rejected attempt resolves
2142
2505
  * as `aborted` when cancellation already settled locally; another rejection is
2143
2506
  * flattened to `stopReason: 'error'` through the contained diagnostic sink.
2144
- * The abort listener is removed on every path.
2507
+ * Provider-returned diagnostics use the same byte limit. The abort listener is
2508
+ * removed on every path.
2145
2509
  * @param parts - the attempt, output snapshot, cancellation state, sink, and signal wiring.
2146
2510
  * @returns the terminal result (never a rejection).
2147
2511
  */
@@ -2151,7 +2515,7 @@ async function settleRunResult(parts) {
2151
2515
  return parts.cancelled() ? {
2152
2516
  output: parts.collectOutput(),
2153
2517
  stopReason: "aborted"
2154
- } : result;
2518
+ } : normalizeSubagentDiagnostic(result);
2155
2519
  } catch (error) {
2156
2520
  if (parts.cancelled()) return {
2157
2521
  output: parts.collectOutput(),
@@ -2160,8 +2524,11 @@ async function settleRunResult(parts) {
2160
2524
  try {
2161
2525
  parts.onError?.(toError(error), "error");
2162
2526
  } catch {}
2527
+ const collected = parts.collectDiagnostic?.();
2528
+ const diagnostic = collected === void 0 ? void 0 : limitSubagentDiagnostic(collected);
2163
2529
  return {
2164
2530
  output: parts.collectOutput(),
2531
+ ...diagnostic === void 0 ? {} : { diagnostic },
2165
2532
  stopReason: "error"
2166
2533
  };
2167
2534
  } finally {
@@ -2204,9 +2571,16 @@ function subprocessRunHandle(parts) {
2204
2571
  function finalText(blocks) {
2205
2572
  return blocks.filter((block) => block.type === "text").map((block) => block.text).join("");
2206
2573
  }
2574
+ /** Render a failed stop reason with optional provider-authored detail. */
2575
+ function failureDetail(result) {
2576
+ const stopReason = result.stopReason;
2577
+ return result.diagnostic === void 0 ? stopReason : `${stopReason}; diagnostic: ${result.diagnostic}`;
2578
+ }
2207
2579
  /**
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.
2580
+ * Map a child result to the task outcome: completed carries final text, local
2581
+ * cancellation (`aborted` without a diagnostic) is killed, and provider-
2582
+ * diagnosed remote aborts plus every other reason are failed without partial
2583
+ * output.
2210
2584
  * @param result - child terminal result.
2211
2585
  * @returns outcome for the `ctx.jobs` registration.
2212
2586
  */
@@ -2216,16 +2590,19 @@ function runOutcome(result) {
2216
2590
  status: "completed",
2217
2591
  output: finalText(result.output)
2218
2592
  };
2219
- case "aborted": return { status: "killed" };
2593
+ case "aborted": return result.diagnostic === void 0 ? { status: "killed" } : {
2594
+ status: "failed",
2595
+ detail: failureDetail(result)
2596
+ };
2220
2597
  case "error":
2221
2598
  case "max-tokens":
2222
2599
  case "refusal": return {
2223
2600
  status: "failed",
2224
- detail: result.stopReason
2601
+ detail: failureDetail(result)
2225
2602
  };
2226
2603
  default: return {
2227
2604
  status: "failed",
2228
- detail: String(result.stopReason)
2605
+ detail: failureDetail(result)
2229
2606
  };
2230
2607
  }
2231
2608
  }
@@ -2263,10 +2640,8 @@ async function settleRun(run) {
2263
2640
  * child before returning its run, so fulfillment is the single publication and
2264
2641
  * ownership-transfer boundary.
2265
2642
  *
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.
2643
+ * Multiple providers coexist: each registers under a unique name and callers
2644
+ * select one by name.
2270
2645
  *
2271
2646
  * This package owns the Service Definition role of the capability seam. Service Providers
2272
2647
  * (`@xneog/dsh-subagent-spawn-in-process`, `-fork`, `-acp`) and the model-facing
@@ -2274,8 +2649,8 @@ async function settleRun(run) {
2274
2649
  *
2275
2650
  * Public operations express caller intent: `start` returns one published owned
2276
2651
  * 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
2652
+ * `sendMessage` steers between adjacent Agents without exposing whether a child
2653
+ * is resident. Continuable children never become a {@link SubagentRun}: the
2279
2654
  * continuation manager holds their `AgentHandle` directly and orders every turn
2280
2655
  * through the child's own inbox, so providers contribute only the detached
2281
2656
  * creation spec and see no handle, turn, or teardown. Child and descendant
@@ -2289,284 +2664,465 @@ async function settleRun(run) {
2289
2664
  *
2290
2665
  * @module @xneog/dsh-subagent
2291
2666
  */
2667
+ var __runInitializers = function(thisArg, initializers, value) {
2668
+ var useValue = arguments.length > 2;
2669
+ for (var i = 0; i < initializers.length; i++) value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
2670
+ return useValue ? value : void 0;
2671
+ };
2672
+ var __esDecorate = function(ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
2673
+ function accept(f) {
2674
+ if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected");
2675
+ return f;
2676
+ }
2677
+ var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
2678
+ var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
2679
+ var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
2680
+ var _, done = false;
2681
+ for (var i = decorators.length - 1; i >= 0; i--) {
2682
+ var context = {};
2683
+ for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
2684
+ for (var p in contextIn.access) context.access[p] = contextIn.access[p];
2685
+ context.addInitializer = function(f) {
2686
+ if (done) throw new TypeError("Cannot add initializers after decoration has completed");
2687
+ extraInitializers.push(accept(f || null));
2688
+ };
2689
+ var result = (0, decorators[i])(kind === "accessor" ? {
2690
+ get: descriptor.get,
2691
+ set: descriptor.set
2692
+ } : descriptor[key], context);
2693
+ if (kind === "accessor") {
2694
+ if (result === void 0) continue;
2695
+ if (result === null || typeof result !== "object") throw new TypeError("Object expected");
2696
+ if (_ = accept(result.get)) descriptor.get = _;
2697
+ if (_ = accept(result.set)) descriptor.set = _;
2698
+ if (_ = accept(result.init)) initializers.unshift(_);
2699
+ } else if (_ = accept(result)) if (kind === "field") initializers.unshift(_);
2700
+ else descriptor[key] = _;
2701
+ }
2702
+ if (target) Object.defineProperty(target, contextIn.name, descriptor);
2703
+ done = true;
2704
+ };
2292
2705
  /** 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);
2706
+ let SubagentRuntime = (() => {
2707
+ let _classSuper = TypertRemoteService;
2708
+ let _instanceExtraInitializers = [];
2709
+ let _remoteExportList_decorators;
2710
+ let _prompt_decorators;
2711
+ let _interruptByParent_decorators;
2712
+ return class SubagentRuntime extends _classSuper {
2713
+ static {
2714
+ const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
2715
+ _remoteExportList_decorators = [Remote("list")];
2716
+ _prompt_decorators = [Remote("prompt")];
2717
+ _interruptByParent_decorators = [Remote("interruptByParent")];
2718
+ __esDecorate(this, null, _remoteExportList_decorators, {
2719
+ kind: "method",
2720
+ name: "remoteExportList",
2721
+ static: false,
2722
+ private: false,
2723
+ access: {
2724
+ has: (obj) => "remoteExportList" in obj,
2725
+ get: (obj) => obj.remoteExportList
2726
+ },
2727
+ metadata: _metadata
2728
+ }, null, _instanceExtraInitializers);
2729
+ __esDecorate(this, null, _prompt_decorators, {
2730
+ kind: "method",
2731
+ name: "prompt",
2732
+ static: false,
2733
+ private: false,
2734
+ access: {
2735
+ has: (obj) => "prompt" in obj,
2736
+ get: (obj) => obj.prompt
2737
+ },
2738
+ metadata: _metadata
2739
+ }, null, _instanceExtraInitializers);
2740
+ __esDecorate(this, null, _interruptByParent_decorators, {
2741
+ kind: "method",
2742
+ name: "interruptByParent",
2743
+ static: false,
2744
+ private: false,
2745
+ access: {
2746
+ has: (obj) => "interruptByParent" in obj,
2747
+ get: (obj) => obj.interruptByParent
2748
+ },
2749
+ metadata: _metadata
2750
+ }, null, _instanceExtraInitializers);
2751
+ if (_metadata) Object.defineProperty(this, Symbol.metadata, {
2752
+ enumerable: true,
2753
+ configurable: true,
2754
+ writable: true,
2755
+ value: _metadata
2756
+ });
2757
+ }
2758
+ providers = (__runInitializers(this, _instanceExtraInitializers), /* @__PURE__ */ new Map());
2759
+ continuations;
2760
+ /**
2761
+ * The contained lifecycle-edge publisher. Built here because scoped dispatch
2762
+ * keys its carrier by this exact service instance, whose own context filter
2763
+ * composes into the carrier.
2764
+ */
2765
+ emitLifecycle;
2766
+ constructor(ctx) {
2767
+ super(ctx, "subagents");
2768
+ this.emitLifecycle = createLifecycleEmitter(this.ctx, (parent) => scopeTarget(this, parent));
2769
+ ctx.inject(["agents"], (childCtx) => {
2770
+ const manager = new SubagentContinuationManager(childCtx, {
2771
+ prepareContinuable: (name, request) => this.prepareContinuable(name, request),
2772
+ observeActivation: (provider, childId, parent) => this.observeActivation(provider, childId, parent)
2773
+ });
2774
+ this.continuations = manager;
2775
+ childCtx.effect(() => () => {
2776
+ /* v8 ignore else -- one injected binding owns the slot until its fiber disposes. */
2777
+ if (this.continuations === manager) this.continuations = void 0;
2778
+ }, "subagents.continuationBinding()");
2779
+ });
2780
+ ctx.inject(["sessionProjections"], (projectionCtx) => {
2781
+ projectionCtx.sessionProjections.register(subagentTimingProjectionDefinition);
2782
+ projectionCtx.sessionProjections.register(subagentIdentityProjectionDefinition);
2783
+ });
2784
+ }
2785
+ /**
2786
+ * Establish one durable continuable child and deliver its initial prompt.
2787
+ * Resolves when the child's inbox accepts that prompt, without waiting for the
2788
+ * turn to start or for the message to reach the Session log; any earlier
2789
+ * failure rejects with no ids and rolls back the child entirely.
2790
+ * @param spec - provider, delegation request, and caller cancellation.
2791
+ * @returns the durable child id and the accepted prompt's message id.
2792
+ * @throws when continuation services are unavailable or materialization fails.
2793
+ */
2794
+ async startContinuable(spec) {
2795
+ return this.requireContinuations().startContinuable(spec);
2796
+ }
2797
+ /**
2798
+ * Steer one model-authored message to the sender's direct parent or direct
2799
+ * continuable child. A running target admits it at the nearest step boundary;
2800
+ * an idle target starts a turn, and an absent direct child cold-resumes from
2801
+ * persistence. The service derives durable sender attribution from the exact
2802
+ * live sender. Caller cancellation stops only pre-acceptance work.
2803
+ * @param sender - exact live Agent authorizing and originating the message.
2804
+ * @param targetId - durable direct-parent or direct-child session id.
2805
+ * @param content - model-authored content to deliver.
2806
+ * @param options - caller cancellation before inbox acceptance.
2807
+ * @returns the accepted message's inbox id.
2808
+ * @throws when continuation services are unavailable, adjacency is rejected,
2809
+ * or the message was not admitted.
2810
+ */
2811
+ async sendMessage(sender, targetId, content, options) {
2812
+ return this.requireContinuations().sendMessage(sender, targetId, content, options);
2813
+ }
2814
+ /**
2815
+ * Deliver one host-protocol message to a direct continuable child.
2816
+ * Symbol-keyed so host adapters can preserve their own provenance without
2817
+ * widening the public Service Definition or impersonating an Agent sender.
2818
+ * @param parent - exact live direct parent authorizing delivery.
2819
+ * @param childId - durable direct-child session id.
2820
+ * @param content - host-authored content to deliver.
2821
+ * @param source - durable host-protocol provenance.
2822
+ * @param signal - caller cancellation before inbox acceptance.
2823
+ * @param delivery - Queue as a distinct turn or Steer at the nearest step.
2824
+ * @returns the accepted message's inbox id.
2825
+ */
2826
+ [deliverSubagentPrompt](parent, childId, content, source, signal, delivery) {
2827
+ return delivery === "steer" ? this.requireContinuations().steerPrompt(parent, childId, content, source, signal) : this.requireContinuations().queuePrompt(parent, childId, content, source, signal);
2828
+ }
2829
+ /**
2830
+ * Interrupt one live continuable child's current turn under a human parent
2831
+ * address or an exact live ancestor Agent. Fire-and-return: the cancel
2832
+ * signal is issued before this returns, but the target may keep running
2833
+ * until it observes the signal. Unclaimed pending inbox work, the Activation,
2834
+ * and published descendants are preserved; claimed work is not requeued.
2835
+ * Once the interrupted driver is idle, a waking send resumes the parked FIFO
2836
+ * queue. An absent target — including a one-shot or unknown id —
2837
+ * is an accepted no-op, as is a manager-less composition, which cannot own a
2838
+ * live Activation.
2839
+ * @param targetSessionId - the durable child session id to interrupt.
2840
+ * @param authority - the human parent address or exact live ancestor Agent.
2841
+ * @throws {SubagentError} `UNAUTHORIZED` when the authority does not own the
2842
+ * live target.
2843
+ */
2844
+ interrupt(targetSessionId, authority) {
2845
+ this.continuations?.interrupt(targetSessionId, authority);
2846
+ }
2847
+ /**
2848
+ * Close continuable admission below exact live parent Agents, stop only their
2849
+ * visible descendant Activations synchronously, then await admitted scoped
2850
+ * materializations and release those forests child-first. The scoped cutoff
2851
+ * lasts until each exact parent leaves the registry; unrelated parent trees
2852
+ * remain live.
2853
+ * @param parents - exact host-owned parent Agents entering teardown.
2854
+ * @returns once every retained descendant Activation released its `AgentHandle`.
2855
+ * @throws an aggregate error after all branches settle when any failed.
2856
+ */
2857
+ async drainContinuableDescendants(parents) {
2858
+ const manager = this.continuations;
2859
+ if (manager === void 0) return;
2860
+ await manager.drainDescendants(parents);
2861
+ }
2862
+ /**
2863
+ * Release selected resident continuable direct children of one exact live
2864
+ * parent. Other children of the same parent remain admitted and resident.
2865
+ * Absent targets and a manager-less composition are accepted no-ops.
2866
+ * @param parent - exact live direct parent authorizing the selected release.
2867
+ * @param childIds - durable direct-child ids to release when resident.
2868
+ * @returns once every selected Activation released its `AgentHandle`.
2869
+ * @throws {SubagentError} `UNAUTHORIZED` when a resident target belongs to a
2870
+ * different parent or the supplied parent identity is stale.
2871
+ */
2872
+ async drainContinuableChildren(parent, childIds) {
2873
+ const manager = this.continuations;
2874
+ if (manager === void 0) return;
2875
+ await manager.drainChildren(parent, childIds);
2876
+ }
2877
+ /**
2878
+ * Enumerate the parent's direct session-backed subagents without loading or
2879
+ * resuming an Agent. The Session query service supplies one live-preferred
2880
+ * corpus and shared point observations; the projection cache supplies
2881
+ * immutable descriptor hits without opening cold logs. The registered
2882
+ * `subagent` projection remains the sole mode/label classifier.
2883
+ *
2884
+ * Every query receives `signal`, and the listing rechecks cancellation
2885
+ * around each await. Read rejections that settle
2886
+ * after an abort become a stable `SubagentError` with code `CANCELLED`.
2887
+ * @param parentSessionId - parent session whose direct children are listed.
2888
+ * @param signal - caller-owned cancellation forwarded to Session queries
2889
+ * and observed around every read await.
2890
+ * @returns children and per-child diagnostics ordered by `createdAt`, then id.
2891
+ * @throws {@link SubagentError} when the projection registry or the session
2892
+ * store is not mounted, or the caller cancels the listing.
2893
+ */
2894
+ listChildren(parentSessionId, signal) {
2895
+ return listChildren(this.ctx, parentSessionId, signal);
2896
+ }
2897
+ /**
2898
+ * Enumerate the root's complete session-backed subagent tree in stable
2899
+ * pre-order from one live-preferred corpus, without loading or resuming an
2900
+ * Agent. Ordinary sessions and one-shot children remain traversal nodes so
2901
+ * continuable descendants below them are discovered; each returned entry
2902
+ * adds its durable `parentId` and root-relative `depth`. Identity resolution,
2903
+ * diagnostics, optional persistence, and cancellation follow the same
2904
+ * projection-backed contract as {@link listChildren}.
2905
+ * @param rootSessionId - session whose complete descendant tree is listed.
2906
+ * @param signal - caller-owned cancellation forwarded to persistence reads
2907
+ * and observed around every read await.
2908
+ * @returns children and per-candidate diagnostics with tree position, in
2909
+ * stable pre-order.
2910
+ * @throws {@link SubagentError} under the same conditions as {@link listChildren}.
2911
+ */
2912
+ listDescendants(rootSessionId, signal) {
2913
+ return listDescendants(this.ctx, rootSessionId, signal);
2914
+ }
2915
+ /**
2916
+ * Remote face of {@link listChildren} for one browser: the durable listing
2917
+ * plus live Agent activity and the delivery-time parent availability hint.
2918
+ * Parent availability is a hint; {@link prompt} performs the authoritative
2919
+ * check. Named apart from the provider-name {@link list}, which owns the
2920
+ * member.
2921
+ * @param parentSessionId - parent session whose direct children are listed.
2922
+ * @param signal - carrier cancellation forwarded to Session queries.
2923
+ * @returns the catalog view for that parent.
2924
+ * @throws {RemoteError} `gateway/bad-request` for an empty parent id,
2925
+ * `gateway/cancelled` for an aborted read, `subagent/projections-unavailable` when
2926
+ * the deployment has no projection registry, otherwise `gateway/internal`.
2927
+ */
2928
+ async remoteExportList(parentSessionId, signal) {
2929
+ validateControlRequest("subagent.list", { parentSessionId });
2930
+ try {
2931
+ return catalogView(this.ctx, parentSessionId, await this.listChildren(parentSessionId, signal));
2932
+ } catch (error) {
2933
+ return rejectCatalogRead(error, signal);
2934
+ }
2935
+ }
2936
+ /**
2937
+ * Deliver one browser-authored message to a continuable child through the
2938
+ * exact live direct parent, retaining the caller-minted request identity and
2939
+ * validated browser zone on the accepted message. Success identifies the
2940
+ * message the child's FIFO inbox accepted; later execution is independent of
2941
+ * this call.
2942
+ * Image parts are admitted and persisted through the attachment store
2943
+ * before delivery, and the child's model must accept image input.
2944
+ * @param request - durable address, minted identity, content, and optional browser zone.
2945
+ * @param signal - carrier cancellation, owning the call until inbox acceptance.
2946
+ * @returns the accepted message's inbox identity.
2947
+ * @throws {RemoteError} `gateway/bad-request`, `subagent/attachment-invalid`,
2948
+ * `subagent/invalid-time-zone`, `subagent/parent-unavailable`,
2949
+ * `subagent/not-resumable`, `subagent/unauthorized`,
2950
+ * `subagent/delivery-unavailable`, `gateway/cancelled`, or `gateway/internal`.
2951
+ */
2952
+ async prompt(request, signal) {
2953
+ const { parentSessionId, childSessionId, clientTimeZone } = request;
2954
+ validateControlRequest("subagent.prompt", request);
2955
+ const canonicalTimeZone = clientTimeZone === void 0 ? void 0 : canonicalClientTimeZone(clientTimeZone);
2956
+ 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 });
2957
+ const parent = this.ctx.get("agents")?.get(parentSessionId);
2958
+ if (parent === void 0) throw new RemoteError("subagent/parent-unavailable", `parent session "${parentSessionId}" is not live`, { parentSessionId });
2959
+ const source = {
2960
+ kind: "user",
2961
+ rpcId: request.requestId,
2962
+ ...canonicalTimeZone === void 0 ? {} : { clientTimeZone: canonicalTimeZone }
2475
2963
  };
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"
2964
+ try {
2965
+ let content;
2966
+ if (request.content.every((part) => part.type === "text")) content = request.content.map((part) => ({
2967
+ type: "text",
2968
+ text: part.text
2969
+ }));
2970
+ else {
2971
+ const attachments = this.ctx.get("attachments");
2972
+ if (attachments === void 0) throw new Error("subagent image prompt requires an attachment store");
2973
+ content = await admitPromptContent(attachments, request.content);
2974
+ }
2975
+ return { messageId: await this[deliverSubagentPrompt](parent, childSessionId, content, source, signal, "queue") };
2976
+ } catch (error) {
2977
+ return rejectPrompt(error, childSessionId, signal);
2566
2978
  }
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
- };
2979
+ }
2980
+ /**
2981
+ * Remote face of {@link interrupt} under one durable parent address. No
2982
+ * catalog, history, persistence, or parent Agent lookup runs: the core
2983
+ * primitive alone authorizes the address against the live Activation, which
2984
+ * is what keeps a live child interruptible while its parent Agent is offline.
2985
+ * Absent, idle, and already-completed targets are accepted no-ops there.
2986
+ * @param childSessionId - durable child session id to interrupt.
2987
+ * @param parentSessionId - durable direct parent whose authority is claimed.
2988
+ * @param mode - required continuable-address discriminator.
2989
+ * @returns acknowledgement that the cancel signal was admitted, not that the target is quiescent.
2990
+ * @throws {RemoteError} `gateway/bad-request` for an empty id,
2991
+ * `subagent/unauthorized` when the address does not own the live target,
2992
+ * otherwise `gateway/internal`.
2993
+ */
2994
+ interruptByParent(childSessionId, parentSessionId, mode) {
2995
+ validateControlRequest("subagent.interrupt", {
2996
+ childSessionId,
2997
+ parentSessionId,
2998
+ mode
2999
+ });
3000
+ try {
3001
+ this.interrupt(childSessionId, {
3002
+ kind: "user",
3003
+ parentSessionId
3004
+ });
3005
+ } catch (error) {
3006
+ if (error instanceof SubagentError && error.code === "UNAUTHORIZED") throw new RemoteError("subagent/unauthorized", "subagent does not belong to this parent", { childSessionId }, { cause: error });
3007
+ throw new RemoteError("gateway/internal", "subagent interrupt failed", {}, { cause: error });
3008
+ }
3009
+ return { accepted: true };
3010
+ }
3011
+ /**
3012
+ * Register a provider under its name. Registration is effect-scoped and HMR
3013
+ * safe; removing a provider blocks new starts but does not revoke runs that
3014
+ * were already returned to their holders.
3015
+ * @param provider - the trusted provider implementation.
3016
+ * @returns the exact Cordis effect disposer.
3017
+ */
3018
+ registerProvider(provider) {
3019
+ const name = provider.name;
3020
+ return this.ctx.effect(function* () {
3021
+ if (this.providers.has(name)) throw new SubagentError(`a subagent provider named "${name}" is already registered`, "DUPLICATE_PROVIDER");
3022
+ this.providers.set(name, provider);
3023
+ yield () => {
3024
+ this.providers.delete(name);
3025
+ this.emitLifecycle("subagent/provider-removed", name);
3026
+ };
3027
+ this.ctx.emit("subagent/provider-added", provider);
3028
+ }.bind(this), "subagents.registerProvider()");
3029
+ }
3030
+ /**
3031
+ * Look up a provider by name.
3032
+ * @param name - the provider name.
3033
+ * @returns the provider, or undefined when absent.
3034
+ */
3035
+ getProvider(name) {
3036
+ return this.providers.get(name);
3037
+ }
3038
+ /**
3039
+ * List registered provider names in insertion order.
3040
+ * @returns the registered names.
3041
+ */
3042
+ list() {
3043
+ return [...this.providers.keys()];
3044
+ }
3045
+ /**
3046
+ * Establish a published child on the named provider. Capability and semantic
3047
+ * checks run before delegation. Provider ownership lasts until its promise
3048
+ * fulfills; a rejection therefore has no run for the caller to dispose and
3049
+ * emits no run lifecycle events. Post-publication turn and infrastructure
3050
+ * failures settle through the returned run.
3051
+ * @param name - the provider to use.
3052
+ * @param request - child label, prompt, parent, signal, and optional capabilities.
3053
+ * @returns the published holder-owned run.
3054
+ */
3055
+ async start(name, request) {
3056
+ const provider = this.expectProvider(name);
3057
+ this.assertCapabilities(provider, request);
3058
+ assertSubagentMaxDepth(request.maxDepth);
3059
+ if (request.outputSchema !== void 0) assertObjectJsonSchema(request.outputSchema);
3060
+ const descriptor = snapshotSubagentDescriptor({
3061
+ mode: "one-shot",
3062
+ provider: name,
3063
+ ...request.label !== void 0 ? { label: request.label } : {}
3064
+ });
3065
+ const resolved = {
3066
+ ...request,
3067
+ descriptor
3068
+ };
3069
+ return observeRun(this.emitLifecycle, name, request.parent, await provider.start(resolved));
3070
+ }
3071
+ /**
3072
+ * Resolve one provider's detached continuable-creation contribution. Method
3073
+ * presence on the provider IS the capability, so a provider without it is
3074
+ * rejected before the manager reserves any child resources.
3075
+ */
3076
+ async prepareContinuable(name, request) {
3077
+ const provider = this.expectProvider(name);
3078
+ if (provider.prepareContinuable === void 0) throw new SubagentError(`subagent provider "${provider.name}" does not support continuable children (no prepareContinuable capability)`, "UNSUPPORTED_CAPABILITY");
3079
+ return provider.prepareContinuable(request);
3080
+ }
3081
+ /** Look up a provider for dispatch or fail loud. */
3082
+ expectProvider(name) {
3083
+ const provider = this.providers.get(name);
3084
+ if (provider === void 0) throw new SubagentError(`no subagent provider registered for "${name}"`, "NO_PROVIDER");
3085
+ return provider;
3086
+ }
3087
+ /** Resolve the optional continuable-subagent manager or fail loud. */
3088
+ requireContinuations() {
3089
+ if (this.continuations === void 0) throw new SubagentError("continuable subagents require the agents service", "CONTINUATION_UNAVAILABLE");
3090
+ return this.continuations;
3091
+ }
3092
+ /**
3093
+ * Build the lifecycle observer for one continuable Activation's residency
3094
+ * epoch, so the manager publishes its edges without owning event dispatch.
3095
+ */
3096
+ observeActivation(provider, childId, parent) {
3097
+ return createActivationObserver(this.emitLifecycle, provider, childId, parent);
3098
+ }
3099
+ /** Reject the first requested capability that the provider lacks. */
3100
+ assertCapabilities(provider, request) {
3101
+ const needs = [
3102
+ {
3103
+ when: request.agentOptions !== void 0,
3104
+ cap: "agentOptions"
3105
+ },
3106
+ {
3107
+ when: request.outputSchema !== void 0,
3108
+ cap: "outputSchema"
3109
+ },
3110
+ {
3111
+ when: request.maxDepth !== void 0,
3112
+ cap: "depthLimit"
3113
+ },
3114
+ {
3115
+ when: request.toolFilter !== void 0,
3116
+ cap: "toolFilter"
3117
+ },
3118
+ {
3119
+ when: request.persona !== void 0,
3120
+ cap: "persona"
3121
+ }
3122
+ ];
3123
+ 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");
3124
+ }
3125
+ };
3126
+ })();
2571
3127
  //#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 };
3128
+ 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, seedDescriptorTurn, settleRun, settleRunResult, snapshotSubagentDescriptor, subprocessRunHandle, validateConfiguredCwd };