@fishwithcat/dsh-subagent 0.2.2-alpha.2 → 0.2.2-alpha.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.js CHANGED
@@ -7,8 +7,8 @@ import { Remote, RemoteError, TypertRemoteService, principalOf } from "@deepseek
7
7
  import { AttachmentError } from "@deepseek-ai/dsh-attachment";
8
8
  import { z as z$1 } from "zod";
9
9
  import { HarnessError, ReasoningEffortId, boundContextSummary, contentHasImage, createUserMessage, errorChain, joinAssistantStreamText } from "@deepseek-ai/dsh-llm";
10
- import { randomUUID } from "node:crypto";
11
10
  import { foldConsumedWork } from "@deepseek-ai/dsh-agent";
11
+ import { randomUUID } from "node:crypto";
12
12
  import { isAbsolute, resolve } from "node:path";
13
13
  import { brandString } from "@deepseek-ai/dsh-brand";
14
14
  import { SessionLogOffset, SessionSeq } from "@deepseek-ai/dsh-session";
@@ -39,16 +39,10 @@ var SubagentError = class extends HarnessError {
39
39
  const SESSION_ID_SCHEMA = z$1.string().min(1);
40
40
  const CONTROL_ID_SCHEMAS = {
41
41
  "subagent.prompt": z$1.object({
42
- parentSessionId: SESSION_ID_SCHEMA,
43
42
  childSessionId: SESSION_ID_SCHEMA,
44
- mode: z$1.literal("continuable"),
45
43
  delivery: z$1.enum(["queue", "steer"])
46
44
  }),
47
- "subagent.interrupt": z$1.object({
48
- parentSessionId: SESSION_ID_SCHEMA,
49
- childSessionId: SESSION_ID_SCHEMA,
50
- mode: z$1.literal("continuable")
51
- })
45
+ "subagent.interrupt": z$1.object({ childSessionId: SESSION_ID_SCHEMA })
52
46
  };
53
47
  /**
54
48
  * Apply the subagent payload checks that are stricter than generated
@@ -77,7 +71,7 @@ function rejectPrompt(error, childSessionId, signal) {
77
71
  if (error instanceof SubagentError) switch (error.code) {
78
72
  case "MODEL_DOES_NOT_SUPPORT_IMAGES": throw new RemoteError("subagent/attachment-invalid", error.message, { reason: error.code }, { cause: error });
79
73
  case "NOT_RESUMABLE": throw new RemoteError("subagent/not-resumable", "subagent cannot be resumed", { childSessionId }, { cause: error });
80
- case "UNAUTHORIZED": throw new RemoteError("subagent/unauthorized", "subagent does not belong to this parent", { childSessionId }, { cause: error });
74
+ case "UNAUTHORIZED": throw new RemoteError("subagent/unauthorized", "subagent membership in the active tree could not be verified", { childSessionId }, { cause: error });
81
75
  case "DRAINING":
82
76
  case "ACTIVATION_CLOSING":
83
77
  case "ACTIVATION_LIMIT_REACHED":
@@ -122,32 +116,12 @@ function assertSubagentMaxDepth(maxDepth) {
122
116
  if (maxDepth !== void 0 && (typeof maxDepth !== "number" || !Number.isSafeInteger(maxDepth) || maxDepth < 0 || Object.is(maxDepth, -0))) throw new TypeError("subagent maxDepth must be a non-negative safe integer");
123
117
  }
124
118
  //#endregion
125
- //#region lib/types/types.js
126
- /**
127
- * The seam's consumer-facing contracts: request, result, and capability types
128
- * for {@link SubagentProvider}, plus the `subagent/start` and `subagent/end`
129
- * payloads that plugins and hosts observe. Internal control interfaces belong
130
- * with their implementation — the lifecycle observer in `./lifecycle.ts`, the
131
- * manager host in `./manager.ts` — so this module stays the published
132
- * surface rather than a bag of everything type-shaped.
133
- *
134
- * @module @deepseek-ai/dsh-subagent/types
135
- */
136
- /**
137
- * Brand a string as a {@link SubagentRunId}.
138
- * @param id - the raw run id.
139
- * @returns the same string, branded.
140
- */
141
- function SubagentRunId(id) {
142
- return id;
143
- }
144
- //#endregion
145
119
  //#region lib/types/lifecycle.js
146
120
  /**
147
- * Lifecycle-edge publication for managed subagent activations.
121
+ * Contained lifecycle notifications for subagents.
148
122
  *
149
- * Public event payloads live in `./types.ts`; the package-private observer
150
- * retains only the identity shared by each start/end pair.
123
+ * The service owns scoped dispatch and listener containment; the manager
124
+ * supplies event payloads from each activation.
151
125
  *
152
126
  * @module @deepseek-ai/dsh-subagent/lifecycle
153
127
  */
@@ -157,8 +131,8 @@ function SubagentRunId(id) {
157
131
  * returned promise is logged without starving peer listeners, changing the run,
158
132
  * or — for provider removal, which fires from a disposer — breaking teardown.
159
133
  * @param ctx - the service's own context, owning dispatch and the logger.
160
- * @param carrier - resolve the scoped dispatch carrier for one delegating parent.
161
- * @returns the emitter both observers and the provider registry publish through.
134
+ * @param carrier - resolve the scoped dispatch carrier for one subagent tree root.
135
+ * @returns the emitter the manager and provider registry publish through.
162
136
  */
163
137
  function createLifecycleEmitter(ctx, carrier) {
164
138
  return (name, info, parent) => {
@@ -178,35 +152,6 @@ function createLifecycleEmitter(ctx, carrier) {
178
152
  };
179
153
  }
180
154
  /**
181
- * Build the observer for one local or external activation's residency epoch. Creation failure before residency emits no lifecycle edge.
182
- * @param emit - the contained lifecycle emitter.
183
- * @param provider - the provider name recorded in the durable descriptor.
184
- * @param childId - the durable child session id.
185
- * @param parent - the exact live direct parent keying scoped dispatch.
186
- * @returns the observer whose edges this epoch publishes.
187
- */
188
- function createActivationObserver(emit, provider, childId, parent) {
189
- const identity = {
190
- runId: SubagentRunId(randomUUID()),
191
- provider,
192
- id: childId,
193
- local: false
194
- };
195
- return {
196
- start: (child) => {
197
- identity.local = child !== void 0;
198
- emit("subagent/start", identity, parent);
199
- },
200
- settle: ({ stopReason, output }) => {
201
- emit("subagent/end", {
202
- ...identity,
203
- stopReason,
204
- ...output.length === 0 ? {} : { lastAssistantMessage: output }
205
- }, parent);
206
- }
207
- };
208
- }
209
- /**
210
155
  * Why this child's epoch ended, for the terminal lifecycle edge and the
211
156
  * manager's own parent delivery. The child's own log is authoritative:
212
157
  * teardown succeeding says nothing about whether the model errored, hit its
@@ -371,10 +316,11 @@ const SUBAGENT_DELEGATION_CONTEXT = "You are a delegated subagent: your permissi
371
316
  const SUBAGENT_DELEGATION_INTERACTIVE_CONTEXT = "You are a delegated subagent: your permission scope was fixed when you were started and cannot be widened from inside this session. An operation that needs access beyond that scope may request approval from the user, who can allow or reject it. Do not retry a rejected or denied operation; state the limitation in your reply so the delegating agent can handle it.";
372
317
  /**
373
318
  * Compose a child before publication and restore historical descriptor overrides.
374
- * Explicit selection mounts the named preset; omission synchronously joins the
375
- * parent's current generation. Registrations belong to the child's scope.
319
+ * Explicit selection mounts the named preset. Omission joins a supplied creator's
320
+ * current generation; a restored child without a saved preset uses host composition.
321
+ * Registrations belong to the child's scope.
376
322
  * @param childCtx - the child agent's scoped creation context.
377
- * @param parent - the delegating parent used when no preset is selected.
323
+ * @param parent - creator for fresh delegation; absent during target-only restoration.
378
324
  * @param composition - independent preset and any overrides read from a historical descriptor.
379
325
  * @param approvalPolicy - the child's effective approval policy, selecting its delegation statement.
380
326
  * @returns A promise when mounting an independent preset; otherwise completes synchronously.
@@ -387,7 +333,7 @@ function applyChildComposition(childCtx, parent, composition, approvalPolicy) {
387
333
  applyChildOverrides(childCtx, composition, approvalPolicy);
388
334
  });
389
335
  }
390
- childCtx.get("agentPresets")?.composeFrom(childCtx, parent.ctx);
336
+ if (parent !== void 0) childCtx.get("agentPresets")?.composeFrom(childCtx, parent.ctx);
391
337
  applyChildOverrides(childCtx, composition, approvalPolicy);
392
338
  }
393
339
  function applyChildOverrides(childCtx, composition, approvalPolicy) {
@@ -453,7 +399,7 @@ function appendDelegatedPolicyOverrides(childSession, overrides) {
453
399
  *
454
400
  * @module @deepseek-ai/dsh-subagent/continuation-messages
455
401
  */
456
- /** Build durable attribution for one adjacent-Agent message. */
402
+ /** Build durable attribution for one Agent message. */
457
403
  function agentMessageSource(sender) {
458
404
  return {
459
405
  kind: "agent-message",
@@ -462,7 +408,7 @@ function agentMessageSource(sender) {
462
408
  };
463
409
  }
464
410
  /**
465
- * Build the model-visible and durable representation of one adjacent-Agent message.
411
+ * Build the model-visible and durable representation of one Agent message.
466
412
  * @param sender - exact live Agent that authored the message.
467
413
  * @param content - model-visible message blocks supplied by the sender.
468
414
  * @returns the durable user-message representation delivered to the recipient.
@@ -477,16 +423,14 @@ function createAgentMessage(sender, content) {
477
423
  });
478
424
  }
479
425
  /**
480
- * Append adjacent-Agent return guidance to a continuable child's initial task.
481
- * @param parentId - durable parent session id named in the guidance.
426
+ * Append automatic result-delivery guidance to a parent-delivered child's initial task.
482
427
  * @param prompt - initial model-visible task blocks.
483
- * @returns task blocks followed by the continuable return guidance.
428
+ * @returns task blocks followed by the result-delivery guidance.
484
429
  */
485
- function withContinuableReturnGuidance(parentId, prompt) {
486
- const encodedParentId = JSON.stringify(parentId);
430
+ function withResultDeliveryGuidance(prompt) {
487
431
  return [...prompt, {
488
432
  type: "text",
489
- 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.`
433
+ text: "Your final answer is delivered automatically if your parent is still online. Include the result and relevant evidence in that answer; the parent does not receive your full transcript, tool output, or reasoning. If your last action is a message to your parent, the completion notice omits your final answer; include the result and relevant evidence in that message."
490
434
  }];
491
435
  }
492
436
  /**
@@ -516,8 +460,8 @@ function settlementSummary(childId, stopReason, continuable) {
516
460
  * @param childId - durable child session id named in the notice.
517
461
  * @param terminal - recorded terminal state for the settled Activation.
518
462
  * @param continuable - whether the child can accept another task.
519
- * @param omitClosingText - replace the closing text with a note that the child's
520
- * last message to the parent already carries it.
463
+ * @param omitClosingText - replace the closing text when the child's last action
464
+ * was a message to its direct parent.
521
465
  * @returns the durable user-message representation delivered to the parent.
522
466
  */
523
467
  function createSettlementMessage(childId, terminal, continuable = true, omitClosingText = false) {
@@ -552,11 +496,84 @@ function createSettlementMessage(childId, terminal, continuable = true, omitClos
552
496
  kind: "subagent-settled",
553
497
  form: "notice",
554
498
  summary: boundContextSummary(summary),
499
+ stopReason: terminal.stopReason,
555
500
  senderSessionId: childId
556
501
  }
557
502
  });
558
503
  }
559
504
  //#endregion
505
+ //#region lib/types/archive-admission.js
506
+ /**
507
+ * The `subagent` family of the Workspace registry's archive admission: which
508
+ * subagent descendants of a Session are still inside a turn, and how they
509
+ * stop when the Session is archived with its work. Archived ancestors also
510
+ * block later model steps, including when their Sessions have been released.
511
+ *
512
+ * @module @deepseek-ai/dsh-subagent
513
+ */
514
+ /**
515
+ * Answer `workspace/session-activity` with the running subagent descendants of
516
+ * the asked Session, and `workspace/session-stop` by cancelling each of them
517
+ * as their parent would. Reject subsequent steps beneath archived ancestors.
518
+ * All listeners live as long as `ctx`'s fiber.
519
+ * @param ctx - context carrying the Agent registry.
520
+ * @param residentAncestors - verified creation ancestors for an exact managed Agent, absent before publication or for unmanaged Agents.
521
+ * @param identityOf - ready service-owned descriptor identity reader; absent or invalid descriptors yield no identity.
522
+ */
523
+ function installSubagentArchiveAdmission(ctx, residentAncestors, identityOf) {
524
+ const ancestorsOf = (agent) => residentAncestors(agent) ?? headerAncestors(ctx, agent);
525
+ const runningDescendants = (sessionId) => ctx.agents.list().filter((agent) => agent.status === "running" && ancestorsOf(agent).includes(sessionId));
526
+ ctx.on("workspace/session-activity", async ({ sessionId }, next) => {
527
+ const running = runningDescendants(sessionId);
528
+ const rest = await next();
529
+ if (running.length === 0) return rest;
530
+ return [{
531
+ kind: "subagent",
532
+ items: running.map((child) => describe(child, identityOf(child.session)))
533
+ }, ...rest];
534
+ });
535
+ ctx.on("workspace/session-stop", ({ sessionId }) => {
536
+ for (const child of runningDescendants(sessionId)) try {
537
+ child.cancel({ kind: "parent" });
538
+ } catch (error) {
539
+ ctx.logger.warn(`subagent: cancelling "${child.id}" for an archived Session failed: ${String(error)}`);
540
+ }
541
+ });
542
+ ctx.on("agent/pre-step", ({ agent }, next) => {
543
+ const archived = ctx.get("workspaceRegistry")?.archivedSessionIds;
544
+ return archived !== void 0 && ancestorsOf(agent).some((id) => archived.includes(id)) ? Promise.resolve({ kind: "reject" }) : next();
545
+ });
546
+ }
547
+ /**
548
+ * Header-only ancestry for Agents without a published Activation. Include an
549
+ * offline direct creator; ordinary forks stop the chain and cycles terminate.
550
+ */
551
+ function headerAncestors(ctx, agent) {
552
+ const ancestors = [];
553
+ const visited = new Set([agent.id]);
554
+ let header = agent.session.header;
555
+ while (header.origin === "subagent" && header.parentSession !== void 0 && !visited.has(header.parentSession)) {
556
+ const parentId = header.parentSession;
557
+ ancestors.push(parentId);
558
+ visited.add(parentId);
559
+ const parent = ctx.agents.get(parentId)?.session;
560
+ if (parent === void 0) break;
561
+ header = parent.header;
562
+ }
563
+ return ancestors;
564
+ }
565
+ /**
566
+ * Report the child's current projected label only when its descriptor belongs
567
+ * to the child's own log; inherited, absent, or invalid identities yield the id alone.
568
+ */
569
+ function describe(child, identity) {
570
+ const label = identity !== void 0 && child.session.isOwnSeq(identity.seq) ? identity.label : void 0;
571
+ return label === void 0 ? { id: child.id } : {
572
+ id: child.id,
573
+ label
574
+ };
575
+ }
576
+ //#endregion
560
577
  //#region lib/types/descriptor.js
561
578
  /**
562
579
  * The durable subagent-child descriptor: the versioned, model-hidden
@@ -574,7 +591,7 @@ function createSettlementMessage(childId, terminal, continuable = true, omitClos
574
591
  * to one activation's result contract rather than durable child composition.
575
592
  * Per-activation knobs such as `maxTokens` are omitted for the same reason as
576
593
  * `outputSchema`: they budget one activation. Cold resume requires the exact
577
- * live parent for authorization but reconstructs child options only from the
594
+ * live subagent tree root for authorization but reconstructs child options only from the
578
595
  * durable descriptor, so it neither restores the prior budget nor inherits
579
596
  * the parent's current one; the resumed route's defaults apply instead.
580
597
  *
@@ -852,28 +869,23 @@ function establishExternalCatalogChild(parent, childId, label) {
852
869
  });
853
870
  }
854
871
  //#endregion
855
- //#region lib/types/internal.js
872
+ //#region lib/types/types.js
856
873
  /**
857
- * Continuation integration markers and host adapters outside the public
858
- * Service Definition and model-facing Agent messaging contract.
859
- * @module @deepseek-ai/dsh-subagent/internal
874
+ * The seam's consumer-facing contracts: request, result, and capability types
875
+ * for {@link SubagentProvider}, plus the `subagent/start` and `subagent/end`
876
+ * payloads that plugins and hosts observe. Internal control interfaces, such
877
+ * as the manager host, belong with their implementation.
878
+ *
879
+ * @module @deepseek-ai/dsh-subagent/types
860
880
  */
861
- /** Process-stable identity carried only by the standard adjacent-Agent messaging tool. */
862
- const adjacentAgentSendMessageTool = Symbol.for("dsh.subagent.adjacentAgentSendMessageTool");
863
881
  /**
864
- * Test whether one visible definition is the standard adjacent-Agent messaging tool.
865
- * @param definition - the scope-resolved `send_message` candidate.
866
- * @returns whether the definition carries the internal standard-tool identity.
882
+ * Brand a string as a {@link SubagentRunId}.
883
+ * @param id - the raw run id.
884
+ * @returns the same string, branded.
867
885
  */
868
- function isAdjacentAgentSendMessageTool(definition) {
869
- return definition !== void 0 && definition[adjacentAgentSendMessageTool] === true;
886
+ function SubagentRunId(id) {
887
+ return id;
870
888
  }
871
- /**
872
- * Process-stable symbol-keyed host delivery shared by the bundled runtime
873
- * entry and this unbundled internal subpath.
874
- * @internal
875
- */
876
- const deliverSubagentPrompt = Symbol.for("dsh.subagent.deliverPrompt");
877
889
  //#endregion
878
890
  //#region lib/types/structured.js
879
891
  /**
@@ -901,7 +913,7 @@ const STRUCTURED_OUTPUT_INSTRUCTION = `When you have your final answer, you MUST
901
913
  * @param childCtx - the child agent's scope context (`setup`'s argument).
902
914
  * @param schema - the trusted, already-asserted schema subset to enforce (see
903
915
  * `assertObjectJsonSchema` in dsh-tools).
904
- * @param canComplete - whether accepted input and owned child work have finished.
916
+ * @param canComplete - whether accepted input has been processed.
905
917
  * @returns the attachment handle (read `captured()` after the child settles).
906
918
  */
907
919
  function attachStructuredRuntime(childCtx, schema, canComplete) {
@@ -943,7 +955,7 @@ function attachStructuredRuntime(childCtx, schema, canComplete) {
943
955
  execute(args, exec) {
944
956
  const violations = validateJsonSchemaValue(schema, args);
945
957
  if (violations.length > 0) throw new ToolArgsError(violations);
946
- if (!canComplete()) throw new Error("Wait for all delegated child tasks to finish and process pending messages before submitting structured output.");
958
+ if (!canComplete()) throw new Error("Process pending messages before submitting structured output.");
947
959
  staged.set(exec, { value: args });
948
960
  exec.concludeTurn();
949
961
  return Promise.resolve({ recorded: true });
@@ -1053,7 +1065,7 @@ function finalAssistantOutput(events) {
1053
1065
  /** Execution-period records, capacity slots, child serialization, and local output capture.
1054
1066
  * @module @deepseek-ai/dsh-subagent/activation
1055
1067
  */
1056
- /** Process-local slots shared through uninterrupted continuable parent links. */
1068
+ /** Process-local capacity shared by every member of a subagent tree. */
1057
1069
  var ActivationPool = class {
1058
1070
  slots = /* @__PURE__ */ new Set();
1059
1071
  /**
@@ -1159,11 +1171,11 @@ var ChildLock = class {
1159
1171
  /**
1160
1172
  * Owns subagent startup, message admission, cold resume, and execution lifetimes.
1161
1173
  * One child lock orders input acceptance and closure; one activation map owns
1162
- * capacity, parent relationships, and resource release across all providers.
1174
+ * capacity, runtime ownership, and resource release across all providers.
1163
1175
  *
1164
1176
  * @module @deepseek-ai/dsh-subagent/manager
1165
1177
  */
1166
- var __addDisposableResource$2 = function(env, value, async) {
1178
+ var __addDisposableResource$1 = function(env, value, async) {
1167
1179
  if (value !== null && value !== void 0) {
1168
1180
  if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
1169
1181
  var dispose, inner;
@@ -1192,7 +1204,7 @@ var __addDisposableResource$2 = function(env, value, async) {
1192
1204
  } else if (async) env.stack.push({ async: true });
1193
1205
  return value;
1194
1206
  };
1195
- var __disposeResources$2 = (function(SuppressedError) {
1207
+ var __disposeResources$1 = (function(SuppressedError) {
1196
1208
  return function(env) {
1197
1209
  function fail(e) {
1198
1210
  env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
@@ -1241,18 +1253,13 @@ var SubagentManager = class {
1241
1253
  locks = new ChildLock();
1242
1254
  /** Structural Cordis owner of every Activation handle. */
1243
1255
  ownerCtx;
1244
- /**
1245
- * Exact roots whose host teardown has begun, with the live lineage members
1246
- * observed under each root. Entries remain until that exact root leaves the
1247
- * Agent registry, closing admission throughout its host's teardown without
1248
- * poisoning a later same-id replacement.
1249
- */
1250
- closingScopes = /* @__PURE__ */ new Map();
1256
+ /** Exact subagent tree roots whose host teardown has closed admission. */
1257
+ closingRoots = /* @__PURE__ */ new Set();
1251
1258
  draining = false;
1252
1259
  /**
1253
1260
  * Build one manager inside the service's Agent-injected context.
1254
1261
  * @param ctx - context providing Agents, Sessions, and teardown ownership.
1255
- * @param host - provider dispatch and lifecycle observation owned by the service.
1262
+ * @param host - descriptor reads and lifecycle publication owned by the service.
1256
1263
  * @param maxActiveSubagents - the current configured child capacity.
1257
1264
  * @param delegatedApproval - the configured approval policy seed for each local child.
1258
1265
  */
@@ -1263,8 +1270,12 @@ var SubagentManager = class {
1263
1270
  this.delegatedApproval = delegatedApproval;
1264
1271
  const scope = ctx.plugin(function activationOwner() {});
1265
1272
  this.ownerCtx = scope.ctx;
1273
+ installSubagentArchiveAdmission(ctx, (agent) => {
1274
+ const activation = this.resident.get(agent.id);
1275
+ return activation !== void 0 && activationAgent(activation) === agent ? activation.ancestors : void 0;
1276
+ }, (session) => host.identityOf(session));
1266
1277
  ctx.on("agent/disposed", ({ agent }) => {
1267
- this.closingScopes.delete(agent);
1278
+ this.closingRoots.delete(agent);
1268
1279
  });
1269
1280
  ctx.effect(function* () {
1270
1281
  yield scope.dispose;
@@ -1274,7 +1285,7 @@ var SubagentManager = class {
1274
1285
  /**
1275
1286
  * Interrupt one live continuable child's current turn under the supplied authority.
1276
1287
  * @param targetSessionId - the durable child session id to interrupt.
1277
- * @param authority - the human parent address or exact live ancestor Agent.
1288
+ * @param authority - a trusted user operation or exact live ancestor Agent.
1278
1289
  */
1279
1290
  interrupt(targetSessionId, authority) {
1280
1291
  if (authority.kind === "ancestor") {
@@ -1284,9 +1295,7 @@ var SubagentManager = class {
1284
1295
  }
1285
1296
  const activation = this.resident.get(targetSessionId);
1286
1297
  if (activation === void 0) return;
1287
- if (authority.kind === "user") {
1288
- if (activation.parent.id !== authority.parentSessionId) throw new SubagentError(`subagent "${targetSessionId}" belongs to another parent session`, "UNAUTHORIZED");
1289
- } else if (!activation.ancestry.has(authority.agent)) throw new SubagentError(`subagent "${targetSessionId}" is not a live descendant of agent "${authority.agent.id}"`, "UNAUTHORIZED");
1298
+ if (authority.kind === "ancestor" && (this.rootOf(authority.agent) !== activation.root || !activation.ancestors.includes(authority.agent.id))) throw new SubagentError(`subagent "${targetSessionId}" is not a live descendant of agent "${authority.agent.id}"`, "UNAUTHORIZED");
1290
1299
  if (activation.closing !== void 0) return;
1291
1300
  const kind = authority.kind === "user" ? "user" : "parent";
1292
1301
  if (activation.kind === "local") activation.handle.agent.cancel({ kind }, { keepInbox: true });
@@ -1297,28 +1306,25 @@ var SubagentManager = class {
1297
1306
  }
1298
1307
  /**
1299
1308
  * Close admission, await every already-admitted materialization through
1300
- * publication or rollback, then dispose the stable live Activation graph
1301
- * child-first.
1309
+ * publication or rollback, then dispose every agent in the subagent tree.
1302
1310
  */
1303
1311
  async drain() {
1304
1312
  this.draining = true;
1305
1313
  for (const pending of this.materializations) pending.controller.abort();
1306
1314
  await Promise.all([...this.materializations].map((materialization) => materialization.settled));
1307
- const owned = /* @__PURE__ */ new Set();
1308
- for (const activation of this.resident.values()) for (const child of activation.ownedChildren) owned.add(child);
1309
- const roots = [...this.resident.values()].filter((activation) => !owned.has(activation.childId));
1310
- await this.disposeRoots(roots, "activation(s)");
1315
+ await this.disposeRoots([...this.resident.values()], "activation(s)");
1311
1316
  }
1312
1317
  /**
1313
- * Join progressing child work, leaving idle parked inboxes resident.
1314
- * @param parent - exact live parent whose descendants are observed.
1315
- * @returns whether work was joined and the host must recheck parent completion.
1318
+ * Join progressing subagent tree work for its root, leaving idle parked inboxes resident.
1319
+ * @param root - exact live subagent tree root; ordinary members return false.
1320
+ * @returns whether work was joined and the host must recheck root completion.
1316
1321
  */
1317
- async waitForChildren(parent) {
1322
+ async waitForTreeWork(root) {
1323
+ if (this.rootOf(root) !== root) return false;
1318
1324
  let observed = false;
1319
1325
  while (true) {
1320
- const pending = [...this.materializations].filter((item) => item.lineage.includes(parent));
1321
- const children = [...this.resident.values()].filter((item) => activationAgent(item) !== parent && item.ancestry.has(parent));
1326
+ const pending = [...this.materializations].filter((item) => item.root === root);
1327
+ const children = [...this.resident.values()].filter((item) => item.root === root);
1322
1328
  observed ||= children.some((item) => item.kind === "local" && item.handle.agent.status !== "idle");
1323
1329
  await Promise.all(children.flatMap((item) => item.kind === "local" ? [item.handle.agent.whenIdle()] : []));
1324
1330
  const waits = pending.map((item) => item.settled);
@@ -1331,10 +1337,6 @@ var SubagentManager = class {
1331
1337
  waits.push(item.handle.agent.whenIdle());
1332
1338
  continue;
1333
1339
  }
1334
- if (item.ownedChildren.size > 0) {
1335
- if ([...item.ownedChildren].some((id) => !this.resident.has(id))) waits.push(item.poke.promise);
1336
- continue;
1337
- }
1338
1340
  const inbox = item.handle.agent.inbox;
1339
1341
  if (item.failureAt === void 0 && (inbox.nextTurn.length > 0 || inbox.nextStep.length > 0)) continue;
1340
1342
  waits.push(Promise.race([item.result.promise.catch(() => void 0), item.poke.promise]));
@@ -1344,114 +1346,73 @@ var SubagentManager = class {
1344
1346
  await Promise.all(waits);
1345
1347
  continue;
1346
1348
  }
1347
- if ([...this.materializations].some((item) => item.lineage.includes(parent) && !pending.includes(item)) || [...this.resident.values()].some((item) => activationAgent(item) !== parent && item.ancestry.has(parent) && !children.includes(item))) continue;
1349
+ if ([...this.materializations].some((item) => item.root === root && !pending.includes(item)) || [...this.resident.values()].some((item) => item.root === root && !children.includes(item))) continue;
1348
1350
  return observed;
1349
1351
  }
1350
1352
  }
1351
1353
  /**
1352
- * Stop the managed descendants of exact live host-owned parents.
1353
- * @param parents - exact live roots whose managed descendants must stop.
1354
+ * Stop every member of the selected subagent trees; ordinary members do not close a subagent tree.
1355
+ * @param parents - exact live subagent tree roots whose instances must stop.
1354
1356
  */
1355
1357
  async drainDescendants(parents) {
1356
- const roots = new Set(parents.filter((parent) => this.ctx.agents.get(parent.id) === parent));
1357
- if (roots.size === 0) return;
1358
- for (const root of roots) this.closingMembers(root).add(root);
1359
- const targets = [];
1360
- for (const activation of this.resident.values()) {
1361
- const child = activationAgent(activation);
1362
- const lineage = this.liveLineage(child ?? activation.parent);
1363
- const owners = [...roots].filter((root) => child !== root && activation.ancestry.has(root));
1364
- if (owners.length === 0) continue;
1365
- targets.push(activation);
1366
- for (const owner of owners) {
1367
- const members = this.closingMembers(owner);
1368
- if (child !== void 0) members.add(child);
1369
- for (const agent of lineage) members.add(agent);
1370
- }
1371
- }
1372
- const materializations = [...this.materializations].filter((materialization) => {
1373
- const owners = [...roots].filter((root) => materialization.lineage.includes(root));
1374
- for (const owner of owners) {
1375
- const members = this.closingMembers(owner);
1376
- for (const agent of materialization.lineage) members.add(agent);
1377
- }
1378
- if (owners.length > 0) materialization.controller.abort();
1379
- return owners.length > 0;
1380
- });
1381
- const ownedTargets = /* @__PURE__ */ new Set();
1382
- for (const activation of targets) for (const child of activation.ownedChildren) ownedTargets.add(child);
1383
- const targetRoots = targets.filter((activation) => !ownedTargets.has(activation.childId));
1384
- for (const activation of targets) this.dispose(activation).catch(() => void 0);
1385
- await Promise.all(materializations.map((materialization) => materialization.settled));
1386
- await this.disposeRoots(targetRoots, "scoped activation(s)");
1387
- }
1388
- /**
1389
- * Release selected resident direct children of one exact live parent.
1390
- * @param parent - exact live direct parent authorizing the selected release.
1391
- * @param childIds - durable direct-child ids to release when resident.
1392
- */
1393
- async drainChildren(parent, childIds) {
1394
- if (this.ctx.agents.get(parent.id) !== parent) throw new SubagentError("selected child teardown requires the exact live parent agent", "UNAUTHORIZED");
1395
- const targets = [];
1396
- for (const childId of new Set(childIds)) {
1397
- const activation = this.resident.get(childId);
1398
- if (activation === void 0) continue;
1399
- if (activation.parent.id !== parent.id || !activation.ancestry.has(parent)) throw new SubagentError(`subagent "${childId}" is not a direct child of agent "${parent.id}"`, "UNAUTHORIZED");
1400
- targets.push(activation);
1401
- }
1402
- for (const activation of targets) this.dispose(activation).catch(() => void 0);
1403
- await this.disposeRoots(targets, "selected activation(s)");
1358
+ const roots = new Set(parents.filter((root) => this.ctx.agents.get(root.id) === root && this.rootOf(root) === root));
1359
+ for (const root of roots) this.closingRoots.add(root);
1360
+ const pending = [...this.materializations].filter((item) => roots.has(item.root));
1361
+ for (const item of pending) item.controller.abort();
1362
+ const targets = [...this.resident.values()].filter((item) => roots.has(item.root));
1363
+ const disposal = this.disposeRoots(targets, "scoped activation(s)");
1364
+ const [disposed] = await Promise.allSettled([disposal, ...pending.map((item) => item.settled)]);
1365
+ if (disposed.status === "rejected") throw disposed.reason;
1404
1366
  }
1405
1367
  /**
1406
1368
  * Start one continuable background child and resolve at initial inbox acceptance.
1407
1369
  * Every earlier failure disposes any created handle and rolls back Activation
1408
- * and parent ownership without returning either id.
1370
+ * and its capacity slot without returning either id.
1409
1371
  * @param spec - provider, delegation request, and caller cancellation.
1372
+ * @param prepare - creation contribution bound to the checked provider.
1410
1373
  * @returns the durable child id and accepted initial prompt message id.
1411
1374
  */
1412
- async startLocal(spec) {
1413
- const request = spec.request;
1414
- const parent = request.parent;
1375
+ async startLocal(spec, prepare) {
1376
+ const parent = spec.parent;
1415
1377
  this.assertAdmitting(parent);
1416
- const persistence = spec.delivery === "caller" ? this.ctx.get("sessionPersistence") : this.requirePersistence();
1417
- assertSubagentMaxDepth(request.maxDepth);
1418
- const childId = spec.childId ?? brandString(randomUUID());
1419
- this.assertChildIdAvailable(childId);
1420
- const childDepth = resolveChildDepth(parent, request.maxDepth);
1421
- const agentOptions = resolveChildAgentOptions(parent, request.agentOptions, childDepth);
1422
- const agentProvider = agentOptions.provider;
1423
- const agentModel = agentOptions.model;
1424
- const agentReasoningEffort = agentOptions.reasoningEffort;
1425
- const descriptor = snapshotSubagentDescriptor({
1426
- mode: "continuable",
1427
- provider: spec.provider,
1428
- label: spec.label,
1429
- ...agentProvider !== void 0 ? { agentProvider } : {},
1430
- ...agentModel !== void 0 ? { agentModel } : {},
1431
- ...agentReasoningEffort !== void 0 ? { agentReasoningEffort } : {}
1432
- });
1433
- const delegatedPolicies = captureDelegatedPolicyOverrides(parent, this.delegatedApproval());
1434
- const releaseHold = this.holdOwnership(parent, childId);
1435
- try {
1436
- const cwd = await this.resolveDirectory(request, spec.signal);
1437
- const prepared = await this.host.prepareContinuable(spec.provider, {
1378
+ const ancestry = this.ancestryForChild(parent);
1379
+ return this.trackStartup(ancestry.root, spec.signal, async (signal) => {
1380
+ const persistence = spec.delivery === "caller" ? this.ctx.get("sessionPersistence") : this.requirePersistence();
1381
+ const childId = spec.childId ?? brandString(randomUUID());
1382
+ this.assertChildIdAvailable(childId);
1383
+ const childDepth = resolveChildDepth(parent, spec.maxDepth);
1384
+ const agentOptions = resolveChildAgentOptions(parent, spec.agentOptions, childDepth);
1385
+ const agentProvider = agentOptions.provider;
1386
+ const agentModel = agentOptions.model;
1387
+ const agentReasoningEffort = agentOptions.reasoningEffort;
1388
+ const descriptor = snapshotSubagentDescriptor({
1389
+ mode: "continuable",
1390
+ provider: spec.provider,
1391
+ label: spec.label,
1392
+ ...agentProvider !== void 0 ? { agentProvider } : {},
1393
+ ...agentModel !== void 0 ? { agentModel } : {},
1394
+ ...agentReasoningEffort !== void 0 ? { agentReasoningEffort } : {}
1395
+ });
1396
+ const delegatedPolicies = captureDelegatedPolicyOverrides(parent, this.delegatedApproval());
1397
+ const cwd = await this.resolveDirectory(spec, signal);
1398
+ const prepared = await prepare({
1438
1399
  sessionId: childId,
1439
1400
  cwd,
1440
1401
  parent,
1441
- signal: spec.signal
1402
+ signal
1442
1403
  });
1443
- spec.signal.throwIfAborted();
1404
+ signal.throwIfAborted();
1444
1405
  this.assertAdmitting(parent);
1445
1406
  const inheritedEventCount = SessionLogOffset(prepared.seed?.length ?? 0);
1446
1407
  const seed = prepared.seed;
1447
1408
  let established;
1448
1409
  const messageId = await this.locks.run(childId, async () => {
1449
- spec.signal.throwIfAborted();
1410
+ signal.throwIfAborted();
1450
1411
  this.assertAdmitting(parent);
1451
1412
  this.assertChildIdAvailable(childId);
1452
1413
  if (spec.childId !== void 0) {
1453
- const persisted = await persistence?.stat(childId, { signal: spec.signal });
1454
- spec.signal.throwIfAborted();
1414
+ const persisted = await persistence?.stat(childId, { signal });
1415
+ signal.throwIfAborted();
1455
1416
  this.assertAdmitting(parent);
1456
1417
  this.assertChildIdAvailable(childId);
1457
1418
  if (persisted !== void 0) throw new SubagentError(`subagent "${childId}" already exists`, "DUPLICATE_CHILD");
@@ -1460,29 +1421,33 @@ var SubagentManager = class {
1460
1421
  kind: "local",
1461
1422
  childId,
1462
1423
  provider: spec.provider,
1463
- parent,
1424
+ ...ancestry,
1464
1425
  create: {
1426
+ parent,
1465
1427
  cwd,
1466
1428
  seed,
1467
- meta: childSessionMeta(parent, childDepth, prepared.seed !== void 0, request.agentPreset),
1429
+ meta: childSessionMeta(parent, childDepth, prepared.seed !== void 0, spec.agentPreset),
1468
1430
  inheritedEventCount,
1469
1431
  delegatedPolicies,
1470
1432
  descriptor
1471
1433
  },
1472
1434
  agentOptions,
1473
- composition: { agentPreset: request.agentPreset },
1474
- signal: spec.signal,
1435
+ composition: { agentPreset: spec.agentPreset },
1436
+ signal,
1475
1437
  delivery: spec.delivery,
1476
- outputSchema: spec.request.outputSchema
1438
+ outputSchema: spec.outputSchema
1477
1439
  });
1478
1440
  established = activation;
1479
- const child = requireLocalActivation(activation).handle.agent;
1480
- const childHeader = child.session.header;
1481
- return await this.submitMaterialized(activation, spec.delivery !== "caller" && isAdjacentAgentSendMessageTool(this.ctx.get("tools")?.get("send_message", child)) ? withContinuableReturnGuidance(parent.id, request.prompt) : request.prompt, {
1441
+ const childHeader = requireLocalActivation(activation).handle.agent.session.header;
1442
+ return await this.submitMaterialized(activation, spec.delivery !== "caller" ? withResultDeliveryGuidance(spec.prompt) : spec.prompt, {
1482
1443
  source: { kind: "user" },
1483
- signal: spec.signal,
1484
- delivery: "queue"
1485
- }, parent, () => {
1444
+ signal,
1445
+ delivery: "queue",
1446
+ authorize: () => {
1447
+ this.assertAdmitting(parent);
1448
+ }
1449
+ }, ancestry.root, () => {
1450
+ this.assertAdmitting(parent);
1486
1451
  establishCatalogChild(parent.session, childHeader, descriptor);
1487
1452
  });
1488
1453
  });
@@ -1491,54 +1456,49 @@ var SubagentManager = class {
1491
1456
  childId,
1492
1457
  messageId
1493
1458
  };
1494
- } catch (error) {
1495
- releaseHold();
1496
- throw error;
1497
- }
1459
+ });
1498
1460
  }
1499
1461
  /**
1500
1462
  * Start an external backend under activation ownership.
1501
1463
  * @param spec - task, provider, cancellation, and delivery policy.
1464
+ * @param start - execution bound to the checked provider.
1502
1465
  * @returns the accepted activation and its result/disposal capabilities.
1503
1466
  */
1504
- async startExternal(spec) {
1505
- const parent = spec.request.parent;
1467
+ async startExternal(spec, start) {
1468
+ const { provider, delivery, childId: _childId, ...request } = spec;
1469
+ const parent = request.parent;
1506
1470
  this.assertAdmitting(parent);
1507
- const pendingId = brandString(randomUUID());
1508
- const releaseHold = this.holdOwnership(parent, pendingId);
1509
- let activation;
1510
- try {
1511
- const cwd = await this.resolveDirectory(spec.request, spec.signal);
1512
- activation = await this.materialize({
1513
- kind: "external",
1514
- childId: pendingId,
1515
- provider: spec.provider,
1516
- parent,
1517
- signal: spec.signal,
1518
- delivery: spec.delivery,
1519
- start: (signal) => this.host.startExternal(spec.provider, {
1520
- ...spec.request,
1521
- cwd,
1522
- label: spec.label,
1523
- signal
1524
- })
1525
- });
1526
- this.assertAdmitting(parent);
1527
- this.assertLiveParent(parent, activation.childId);
1528
- spec.signal.throwIfAborted();
1529
- if (spec.delivery === "parent") establishExternalCatalogChild(parent.session, activation.childId, spec.label);
1530
- this.announce(activation);
1531
- return this.receipt(activation);
1532
- } catch (error) {
1533
- if (activation !== void 0) try {
1534
- await this.dispose(activation);
1535
- } catch (cleanupError) {
1536
- this.ctx.logger.warn(`subagent "${activation.childId}" admission rollback failed: ${failureMessage(cleanupError)}`);
1471
+ const ancestry = this.ancestryForChild(parent);
1472
+ return this.trackStartup(ancestry.root, spec.signal, async (signal) => {
1473
+ let activation;
1474
+ try {
1475
+ const cwd = await this.resolveDirectory(spec, signal);
1476
+ activation = await this.materialize({
1477
+ kind: "external",
1478
+ provider,
1479
+ ...ancestry,
1480
+ signal,
1481
+ delivery,
1482
+ start: (signal) => start({
1483
+ ...request,
1484
+ cwd,
1485
+ signal
1486
+ })
1487
+ });
1488
+ this.assertAdmitting(parent);
1489
+ signal.throwIfAborted();
1490
+ if (delivery === "parent") establishExternalCatalogChild(parent.session, activation.childId, spec.label);
1491
+ this.accept(activation);
1492
+ return this.receipt(activation);
1493
+ } catch (error) {
1494
+ if (activation !== void 0) try {
1495
+ await this.dispose(activation);
1496
+ } catch (cleanupError) {
1497
+ this.ctx.logger.warn(`subagent "${activation.childId}" admission rollback failed: ${failureMessage(cleanupError)}`);
1498
+ }
1499
+ throw error;
1537
1500
  }
1538
- throw error;
1539
- } finally {
1540
- releaseHold();
1541
- }
1501
+ });
1542
1502
  }
1543
1503
  /** Capture the explicit or inherited directory while the parent still owns startup. */
1544
1504
  async resolveDirectory(request, signal) {
@@ -1547,60 +1507,125 @@ var SubagentManager = class {
1547
1507
  return cwd;
1548
1508
  }
1549
1509
  /**
1550
- * Deliver one model-authored message to a direct continuable child or to the
1551
- * sender's direct parent. A missing direct child cold-resumes through the
1552
- * ordinary continuation lifecycle.
1553
- * @param sender - exact live Agent authorizing and originating the message.
1554
- * @param targetId - durable direct-parent or direct-child session id.
1555
- * @param content - model-authored content to deliver.
1556
- * @param options - caller cancellation before acceptance.
1557
- * @returns the accepted message's inbox id.
1510
+ * Resolve the live subagent tree root from runtime ownership, independent of creator residency.
1511
+ * @param caller - exact live Agent selecting the subagent tree.
1512
+ * @returns the live subagent tree root.
1513
+ * @throws when the caller has no active runtime owner.
1514
+ */
1515
+ rootOf(caller) {
1516
+ if (this.ctx.agents.get(caller.id) !== caller) throw new SubagentError("subagent tree access requires the exact live caller", "UNAUTHORIZED");
1517
+ const identity = this.host.identityOf(caller.session);
1518
+ const delegated = caller.session.header.origin === "subagent" || identity != null && caller.session.isOwnSeq(identity.seq);
1519
+ const activation = this.resident.get(caller.id);
1520
+ if (activation !== void 0 && activationAgent(activation) === caller && this.ctx.agents.get(activation.root.id) === activation.root) return activation.root;
1521
+ if (delegated || activation !== void 0) throw new SubagentError("agent has no active runtime owner", "UNAUTHORIZED");
1522
+ return caller;
1523
+ }
1524
+ /** Capture creation ancestry separately from the root that owns the new instance. */
1525
+ ancestryForChild(creator) {
1526
+ return {
1527
+ root: this.rootOf(creator),
1528
+ ancestors: [...this.resident.get(creator.id)?.ancestors ?? [], creator.id]
1529
+ };
1530
+ }
1531
+ /**
1532
+ * Deliver to another agent in the subagent tree, restoring only the target when absent.
1533
+ * @param sender - exact live root or resident continuable Agent.
1534
+ * @param targetId - another member's durable Session id.
1535
+ * @param content - model-authored message blocks.
1536
+ * @param options - cancellation before inbox acceptance.
1537
+ * @returns the accepted inbox message id.
1558
1538
  */
1559
1539
  async sendMessage(sender, targetId, content, options) {
1560
- if (this.ctx.agents.get(sender.id) !== sender) throw new SubagentError("message delivery requires the exact live sender agent", "UNAUTHORIZED");
1540
+ options.signal.throwIfAborted();
1541
+ const root = this.rootOf(sender);
1542
+ if (sender.id === targetId) throw new SubagentError("send_message requires another agent in the subagent tree", "UNAUTHORIZED");
1561
1543
  this.assertAdmitting(sender);
1562
- const senderActivation = this.resident.get(sender.id);
1563
- if (senderActivation !== void 0 && senderActivation.kind === "local" && senderActivation.handle.agent === sender && senderActivation.parent.id === targetId) {
1564
- options.signal.throwIfAborted();
1565
- return this.sendToParent(senderActivation, sender, content);
1566
- }
1567
- 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");
1568
- return this.deliverToChild(sender, targetId, content, {
1544
+ const message = createAgentMessage(sender, content);
1545
+ const activation = this.resident.get(sender.id);
1546
+ return this.deliverToMember(root, targetId, [...message.content], {
1569
1547
  signal: options.signal,
1570
- delivery: "steer"
1548
+ delivery: "steer",
1549
+ source: message.source,
1550
+ authorize: () => {
1551
+ this.assertAdmitting(sender);
1552
+ },
1553
+ onAccepted: () => {
1554
+ if (activation?.kind === "local" && activation.ancestors.at(-1) === targetId) activation.omitClosingText = true;
1555
+ }
1571
1556
  });
1572
1557
  }
1573
- /**
1574
- * Queue one human-authored prompt as a distinct direct-child turn.
1575
- * @param parent - exact live direct parent authorizing delivery.
1576
- * @param childId - durable direct-child session id.
1577
- * @param content - model-visible prompt blocks.
1578
- * @param source - durable attribution for the human prompt.
1579
- * @param signal - caller cancellation before inbox acceptance.
1580
- * @returns the accepted durable message id.
1581
- */
1582
- async queuePrompt(parent, childId, content, source, signal) {
1583
- return this.deliverToChild(parent, childId, content, {
1584
- source,
1585
- signal,
1586
- delivery: "queue"
1587
- });
1558
+ /** Resolve the live owner through verified creation edges without activating ancestors. */
1559
+ async resolveCreationAncestry(source, signal, root) {
1560
+ const ancestors = [];
1561
+ const seen = new Set([source.header.id]);
1562
+ let { header, projections, inheritedEventCount } = source;
1563
+ while (true) {
1564
+ const env_1 = {
1565
+ stack: [],
1566
+ error: void 0,
1567
+ hasError: false
1568
+ };
1569
+ try {
1570
+ signal.throwIfAborted();
1571
+ const parentId = header.parentSession;
1572
+ const identity = projections?.values.subagent;
1573
+ if (parentId === void 0 || identity == null || identity.seq < inheritedEventCount) break;
1574
+ if (seen.has(parentId)) throw new SubagentError("cyclic subagent creation ancestry", "UNAUTHORIZED");
1575
+ seen.add(parentId);
1576
+ const parent = __addDisposableResource$1(env_1, await this.observeContinuation(parentId, signal), false);
1577
+ if (!parent.projections?.values.subagentCatalog?.some((entry) => entry.id === header.id)) throw new SubagentError("subagent is absent from its creator catalog", "UNAUTHORIZED");
1578
+ ancestors.unshift(parentId);
1579
+ if (parentId === root?.id) return {
1580
+ root,
1581
+ ancestors
1582
+ };
1583
+ ({header, projections, inheritedEventCount} = parent);
1584
+ } catch (e_1) {
1585
+ env_1.error = e_1;
1586
+ env_1.hasError = true;
1587
+ } finally {
1588
+ __disposeResources$1(env_1);
1589
+ }
1590
+ }
1591
+ if (root === void 0 && ancestors.length > 0 && header.origin !== "subagent") {
1592
+ const owner = this.ctx.agents.get(header.id);
1593
+ if (owner === void 0 || this.rootOf(owner) !== owner) throw new SubagentError("subagent tree root is not live", "CONTINUATION_UNAVAILABLE");
1594
+ return {
1595
+ root: owner,
1596
+ ancestors
1597
+ };
1598
+ }
1599
+ throw new SubagentError(`agent "${source.header.id}" is not a member of this subagent tree`, "UNAUTHORIZED");
1588
1600
  }
1589
1601
  /**
1590
- * Steer one host-authored prompt to a direct continuable child.
1591
- * @param parent - exact live direct parent authorizing delivery.
1592
- * @param childId - durable direct-child session id.
1602
+ * Deliver one host-authored prompt through the target's live subagent tree root.
1603
+ * Remote callers must be authorized for the target Session before invoking this operation.
1604
+ * @param childId - durable continuable subagent session id.
1593
1605
  * @param content - model-visible prompt blocks.
1594
- * @param source - durable attribution for the host prompt.
1595
- * @param signal - caller cancellation before inbox acceptance.
1606
+ * @param options - inbox destination, durable attribution, and cancellation before acceptance.
1596
1607
  * @returns the accepted durable message id.
1597
1608
  */
1598
- async steerPrompt(parent, childId, content, source, signal) {
1599
- return this.deliverToChild(parent, childId, content, {
1600
- source,
1601
- signal,
1602
- delivery: "steer"
1603
- });
1609
+ async deliverPrompt(childId, content, options) {
1610
+ options.signal.throwIfAborted();
1611
+ let root = this.resident.get(childId)?.root;
1612
+ if (root === void 0) {
1613
+ const env_2 = {
1614
+ stack: [],
1615
+ error: void 0,
1616
+ hasError: false
1617
+ };
1618
+ try {
1619
+ const source = __addDisposableResource$1(env_2, await this.observeContinuation(childId, options.signal), false);
1620
+ ({root} = await this.resolveCreationAncestry(source, options.signal));
1621
+ } catch (e_2) {
1622
+ env_2.error = e_2;
1623
+ env_2.hasError = true;
1624
+ } finally {
1625
+ __disposeResources$1(env_2);
1626
+ }
1627
+ }
1628
+ return this.deliverToMember(root, childId, content, options);
1604
1629
  }
1605
1630
  /** Admit local input synchronously before any teardown can begin. */
1606
1631
  deliver(activation, message, delivery) {
@@ -1618,32 +1643,6 @@ var SubagentManager = class {
1618
1643
  if (this.resident.has(childId) || this.ctx.agents.get(childId) !== void 0 || this.ctx.get("sessions")?.get(childId) !== void 0) throw new SubagentError(`subagent "${childId}" already exists`, "DUPLICATE_CHILD");
1619
1644
  }
1620
1645
  /**
1621
- * Pre-register `childId` in a continuation-managed parent's owned set so the
1622
- * parent cannot settle while a caller is still establishing or resuming that
1623
- * child. Returns a releaser for the failure path; it removes only a hold
1624
- * this call added, and leaves ownership in place once a live Activation for
1625
- * the child exists.
1626
- * @param parent - the live direct parent the operation is admitted under.
1627
- * @param childId - the durable child the operation addresses.
1628
- * @returns the failure-path releaser; a no-op when nothing was added.
1629
- */
1630
- holdOwnership(parent, childId) {
1631
- const parentActivation = this.resident.get(parent.id);
1632
- if (parentActivation === void 0 || activationAgent(parentActivation) !== parent) return () => {};
1633
- if (parentActivation.closing !== void 0) throw new SubagentError(`subagent parent "${parent.id}" is being disposed; the child was not established`, "ACTIVATION_CLOSING");
1634
- if (parentActivation.ownedChildren.has(childId)) return () => {};
1635
- parentActivation.ownedChildren.add(childId);
1636
- return () => {
1637
- const live = this.resident.get(childId);
1638
- /* v8 ignore next 4 -- reaching this arm needs another delivery to establish the child
1639
- * between this operation's failure and its releaser running, which no test can schedule
1640
- * deterministically: the ownership edge then belongs to that live Activation, so the
1641
- * conservative keep leaves it for finishDisposal's releaseOwnership. */
1642
- if (live !== void 0 && live.closing === void 0) return;
1643
- if (parentActivation.ownedChildren.delete(childId)) this.wake(parentActivation);
1644
- };
1645
- }
1646
- /**
1647
1646
  * Apply activation admission to a resident parent before delivering input.
1648
1647
  * @param parent - exact live Agent receiving the message.
1649
1648
  * @param message - durable user message to deliver.
@@ -1663,27 +1662,16 @@ var SubagentManager = class {
1663
1662
  else parent.followup(message);
1664
1663
  }
1665
1664
  /**
1666
- * Reject new admission once the manager or this exact parent tree began draining.
1667
- * @param agent - exact live Agent whose lineage determines admission.
1665
+ * Reject new work from closing members or subagent trees.
1666
+ * @param agent - exact live caller whose instance and subagent tree must admit work.
1668
1667
  */
1669
1668
  assertAdmitting(agent) {
1670
1669
  const closing = this.closingTeardownFor(agent);
1671
- if (closing === void 0) return;
1672
- throw new SubagentError(closing === "manager" ? "subagents are draining; the operation was not admitted" : `subagents below parent "${closing.id}" are draining; the operation was not admitted`, "DRAINING");
1673
- }
1674
- /**
1675
- * Authorize one operation against the durable direct-parent lineage.
1676
- * @param parent - exact live Agent claiming direct-parent authority.
1677
- * @param childId - durable child session id addressed by the operation.
1678
- * @param parentSession - durable direct-parent id recorded by the child.
1679
- */
1680
- authorizeLineage(parent, childId, parentSession) {
1681
- this.assertLiveParent(parent, childId);
1682
- if (parentSession !== parent.id) throw new SubagentError(`subagent "${childId}" belongs to another parent session`, "UNAUTHORIZED");
1683
- }
1684
- /** Reject parent authority after the live registry entry changes. */
1685
- assertLiveParent(parent, childId) {
1686
- if (this.ctx.agents.get(parent.id) !== parent) throw new SubagentError(`subagent "${childId}" delivery requires the exact live parent agent`, "UNAUTHORIZED");
1670
+ if (closing === void 0) {
1671
+ if (this.resident.get(agent.id)?.closing !== void 0) throw new SubagentError(`subagent "${agent.id}" is closing`, "ACTIVATION_CLOSING");
1672
+ return;
1673
+ }
1674
+ throw new SubagentError(closing === "manager" ? "subagents are draining; the operation was not admitted" : `subagent tree "${closing.id}" is draining; the operation was not admitted`, "DRAINING");
1687
1675
  }
1688
1676
  /**
1689
1677
  * Create or resume one child Agent and publish its Activation.
@@ -1691,25 +1679,28 @@ var SubagentManager = class {
1691
1679
  * @returns the published process-local Activation.
1692
1680
  */
1693
1681
  materialize(inputs) {
1694
- this.assertAdmitting(inputs.parent);
1682
+ this.assertAdmitting(inputs.root);
1695
1683
  inputs.signal.throwIfAborted();
1696
- const lineage = this.liveLineage(inputs.parent);
1697
- const pool = this.resident.get(inputs.parent.id)?.pool ?? this.rootPool(inputs.parent);
1698
- const releaseSlot = pool.reserve(this.maxActiveSubagents());
1684
+ const releaseSlot = this.rootPool(inputs.root).reserve(this.maxActiveSubagents());
1685
+ return this.materializeTracked(inputs, releaseSlot).catch((error) => {
1686
+ releaseSlot();
1687
+ throw error;
1688
+ });
1689
+ }
1690
+ /** Keep subagent tree shutdown and host waits joined to preparation, publication, and rollback. */
1691
+ trackStartup(root, signal, start) {
1692
+ this.assertAdmitting(root);
1693
+ signal.throwIfAborted();
1699
1694
  const settled = Promise.withResolvers();
1700
1695
  const controller = new AbortController();
1701
1696
  const materialization = {
1702
- lineage,
1697
+ root,
1703
1698
  settled: settled.promise,
1704
1699
  controller
1705
1700
  };
1706
1701
  this.materializations.add(materialization);
1707
- return this.materializeTracked({
1708
- ...inputs,
1709
- signal: AbortSignal.any([inputs.signal, controller.signal])
1710
- }, lineage, pool, releaseSlot).catch((error) => {
1711
- releaseSlot();
1712
- if (error instanceof Error && error.name === "AbortError") this.assertAdmitting(inputs.parent);
1702
+ return start(AbortSignal.any([signal, controller.signal])).catch((error) => {
1703
+ if (error instanceof Error && error.name === "AbortError") this.assertAdmitting(root);
1713
1704
  throw error;
1714
1705
  }).finally(() => {
1715
1706
  this.materializations.delete(materialization);
@@ -1744,35 +1735,13 @@ var SubagentManager = class {
1744
1735
  }))).filter((failure) => failure !== void 0);
1745
1736
  if (reasons.length > 0) throw new SubagentError(`subagent teardown failed for ${reasons.length} ${failureSubject}: ` + reasons.map((reason) => failureMessage(reason)).join("; "), "ACTIVATION_TEARDOWN_FAILED");
1746
1737
  }
1747
- /** Return the retained member set for one exact scoped-teardown root. */
1748
- closingMembers(root) {
1749
- const existing = this.closingScopes.get(root);
1750
- if (existing !== void 0) return existing;
1751
- const members = /* @__PURE__ */ new Set();
1752
- this.closingScopes.set(root, members);
1753
- return members;
1754
- }
1755
- /** Return the exact currently resolvable ancestry from `agent` upward. */
1756
- liveLineage(agent) {
1757
- const lineage = [agent];
1758
- const seen = new Set([agent.id]);
1759
- let parentSession = agent.session.header.parentSession;
1760
- while (parentSession !== void 0) {
1761
- const parent = this.ctx.agents.get(parentSession);
1762
- if (parent === void 0 || seen.has(parent.id)) break;
1763
- lineage.push(parent);
1764
- seen.add(parent.id);
1765
- parentSession = parent.session.header.parentSession;
1766
- }
1767
- return lineage;
1768
- }
1769
- /** Return the teardown that closed continuable admission for this agent's lineage. */
1738
+ /** Return the subagent tree teardown that closed admission for this exact Agent. */
1770
1739
  closingTeardownFor(agent) {
1771
1740
  if (this.draining) return "manager";
1772
- const lineage = this.liveLineage(agent);
1773
- for (const [root, members] of this.closingScopes) if (members.has(agent) || lineage.includes(root)) return root;
1741
+ const root = this.rootOf(agent);
1742
+ return this.closingRoots.has(root) ? root : void 0;
1774
1743
  }
1775
- /** Resolve a root's pool once; descendants inherit their resident parent's pool directly. */
1744
+ /** Share one capacity pool across every instance in a subagent tree. */
1776
1745
  rootPool(parent) {
1777
1746
  let pool = this.rootPools.get(parent);
1778
1747
  if (pool === void 0) {
@@ -1782,9 +1751,8 @@ var SubagentManager = class {
1782
1751
  return pool;
1783
1752
  }
1784
1753
  /** Perform one tracked materialization through publication or rollback. */
1785
- async materializeTracked(inputs, parentLineage, pool, releaseSlot) {
1786
- const { provider, parent } = inputs;
1787
- let childId = inputs.childId;
1754
+ async materializeTracked(inputs, releaseSlot) {
1755
+ const { provider, root } = inputs;
1788
1756
  inputs.signal.throwIfAborted();
1789
1757
  let resource;
1790
1758
  if (inputs.kind === "external") {
@@ -1801,9 +1769,8 @@ var SubagentManager = class {
1801
1769
  controller
1802
1770
  };
1803
1771
  run.result.catch(() => void 0);
1804
- childId = run.id;
1805
1772
  try {
1806
- this.assertChildIdAvailable(childId);
1773
+ this.assertChildIdAvailable(run.id);
1807
1774
  } catch (error) {
1808
1775
  await run.dispose();
1809
1776
  throw error;
@@ -1812,11 +1779,11 @@ var SubagentManager = class {
1812
1779
  inputs.signal.removeEventListener("abort", cancelStartup);
1813
1780
  }
1814
1781
  } else {
1815
- const { create } = inputs;
1782
+ const { create, childId } = inputs;
1816
1783
  let structured;
1817
1784
  const setup = async (childCtx, child) => {
1818
1785
  const approvalPolicy = create?.delegatedPolicies.approvalPolicy ?? childCtx.get("approval")?.effectivePolicy(child.session);
1819
- if (inputs.composition.agentPreset !== void 0) await applyChildComposition(childCtx, parent, inputs.composition, approvalPolicy);
1786
+ if (inputs.composition.agentPreset !== void 0) await applyChildComposition(childCtx, create?.parent, inputs.composition, approvalPolicy);
1820
1787
  if (create !== void 0) {
1821
1788
  if (inputs.composition.agentPreset !== void 0) child.session.append("agent-preset/selected", { agentPreset: inputs.composition.agentPreset });
1822
1789
  child.session.append("subagent/descriptor", create.descriptor);
@@ -1825,18 +1792,16 @@ var SubagentManager = class {
1825
1792
  if (workingDirectory === void 0) throw new Error("local subagents require the working-directory service");
1826
1793
  await workingDirectory.set(child, create.cwd, inputs.signal);
1827
1794
  }
1828
- if (inputs.composition.agentPreset === void 0) await applyChildComposition(childCtx, parent, inputs.composition, approvalPolicy);
1829
- if (inputs.outputSchema !== void 0) structured = attachStructuredRuntime(childCtx, inputs.outputSchema, () => this.resident.get(childId)?.ownedChildren.size === 0 && child.inbox.nextStep.length === 0 && child.inbox.nextTurn.length === 0);
1795
+ if (inputs.composition.agentPreset === void 0) await applyChildComposition(childCtx, create?.parent, inputs.composition, approvalPolicy);
1796
+ if (inputs.outputSchema !== void 0) structured = attachStructuredRuntime(childCtx, inputs.outputSchema, () => child.inbox.nextStep.length === 0 && child.inbox.nextTurn.length === 0);
1830
1797
  };
1831
1798
  const handle = create === void 0 ? await this.ownerCtx.agents.resume({
1832
1799
  resumeSessionId: childId,
1833
- parentAgent: parent,
1834
1800
  agentOptions: inputs.agentOptions,
1835
1801
  signal: inputs.signal,
1836
1802
  setup
1837
1803
  }) : await this.ownerCtx.agents.create({
1838
1804
  sessionId: childId,
1839
- parentAgent: parent,
1840
1805
  meta: create.meta,
1841
1806
  ...create.seed === void 0 ? {} : { seed: create.seed },
1842
1807
  inheritedEventCount: create.inheritedEventCount,
@@ -1854,28 +1819,25 @@ var SubagentManager = class {
1854
1819
  omitClosingText: false
1855
1820
  };
1856
1821
  }
1857
- const observer = this.host.observeActivation(provider, childId, parent);
1822
+ const childId = resource.kind === "local" ? resource.handle.agent.id : resource.run.id;
1858
1823
  const activation = {
1859
- pool,
1860
1824
  releaseSlot,
1861
1825
  childId,
1826
+ runId: SubagentRunId(randomUUID()),
1827
+ provider,
1862
1828
  ...resource,
1863
- parent,
1829
+ root,
1830
+ ancestors: inputs.ancestors,
1864
1831
  delivery: inputs.delivery ?? "parent",
1865
1832
  result: Promise.withResolvers(),
1866
1833
  closing: void 0,
1867
- ancestry: new WeakSet(resource.kind === "external" ? parentLineage : [resource.handle.agent, ...parentLineage]),
1868
- ownedChildren: /* @__PURE__ */ new Set(),
1869
- observer,
1870
- announced: false
1834
+ accepted: false
1871
1835
  };
1872
1836
  activation.result.promise.catch(() => void 0);
1873
1837
  this.resident.set(childId, activation);
1874
1838
  try {
1875
- this.assertAdmitting(parent);
1839
+ this.assertAdmitting(root);
1876
1840
  inputs.signal.throwIfAborted();
1877
- if (this.ctx.agents.get(parent.id) !== parent) throw new SubagentError("subagent parent is no longer live", "UNAUTHORIZED");
1878
- this.acquireOwnership(parent, childId);
1879
1841
  const wakeSettlement = () => {
1880
1842
  this.wake(activation);
1881
1843
  };
@@ -1891,7 +1853,7 @@ var SubagentManager = class {
1891
1853
  if (event.type === "tool/call") activation.omitClosingText = false;
1892
1854
  });
1893
1855
  }
1894
- observer.start(activationAgent(activation));
1856
+ this.host.emitLifecycle("subagent/start", this.runInfo(activation), root);
1895
1857
  } catch (error) {
1896
1858
  /* v8 ignore next -- rollback failure must not mask the admission failure
1897
1859
  * that prevented this operation from returning an accepted message id. */
@@ -1900,13 +1862,19 @@ var SubagentManager = class {
1900
1862
  }
1901
1863
  return activation;
1902
1864
  }
1903
- /**
1904
- * Begin settlement observation after initial acceptance is recorded.
1905
- * @param activation - accepted activation to observe.
1906
- */
1907
- announce(activation) {
1908
- if (activation.announced) return;
1909
- activation.announced = true;
1865
+ /** Identity shared by one residency epoch's start/end pair, including cold resumes. */
1866
+ runInfo(activation) {
1867
+ return {
1868
+ runId: activation.runId,
1869
+ provider: activation.provider,
1870
+ parentSessionId: activation.ancestors.at(-1),
1871
+ id: activation.childId,
1872
+ local: activation.kind === "local"
1873
+ };
1874
+ }
1875
+ /** Record acceptance and begin settlement observation before returning the receipt. */
1876
+ accept(activation) {
1877
+ activation.accepted = true;
1910
1878
  if (activation.kind === "local") this.watchLocalSettlement(activation);
1911
1879
  else {
1912
1880
  const finish = () => this.close(activation, () => this.finishDisposal(activation, false));
@@ -1923,23 +1891,10 @@ var SubagentManager = class {
1923
1891
  } finally {
1924
1892
  this.resident.delete(activation.childId);
1925
1893
  activation.releaseSlot();
1926
- this.releaseOwnership(activation.childId);
1927
1894
  activation.result.reject(error);
1928
1895
  }
1929
1896
  });
1930
1897
  }
1931
- /** Register the child in a continuation-managed parent's owned set. */
1932
- acquireOwnership(parent, childId) {
1933
- const parentActivation = this.resident.get(parent.id);
1934
- if (parentActivation === void 0) return;
1935
- if (parentActivation.closing !== void 0) throw new SubagentError(`subagent parent "${parent.id}" is being disposed; the child was not established`, "ACTIVATION_CLOSING");
1936
- parentActivation.ownedChildren.add(childId);
1937
- this.wake(parentActivation);
1938
- }
1939
- /** Remove one child from its live owner's set and let that owner re-check settlement. */
1940
- releaseOwnership(childId) {
1941
- for (const candidate of this.resident.values()) if (candidate.ownedChildren.delete(childId)) this.wake(candidate);
1942
- }
1943
1898
  /** Let a settlement watcher re-check residency after relevant state changes. */
1944
1899
  wake(activation) {
1945
1900
  if (activation.kind === "external") return;
@@ -1994,16 +1949,15 @@ var SubagentManager = class {
1994
1949
  reportTeardownFailure(activation, error) {
1995
1950
  this.ctx.logger.warn(`subagent "${activation.childId}" activation teardown failed: ${failureMessage(error)}`);
1996
1951
  }
1997
- /** Check pending input and owned children before closing admission. */
1952
+ /** Check pending input before closing admission. */
1998
1953
  settlementState(activation, observation) {
1999
1954
  if (activation.closing !== void 0) return "closed";
2000
1955
  if (activation.poke !== observation) return "retry";
2001
1956
  const inbox = activation.handle.agent.inbox;
2002
- if (activation.ownedChildren.size > 0) return "wait";
2003
1957
  if (activation.failureAt === void 0 && (inbox.nextTurn.length > 0 || inbox.nextStep.length > 0)) return "wait";
2004
1958
  return "ready";
2005
1959
  }
2006
- /** Propagate stop synchronously and await descendant startup rollback before release. */
1960
+ /** Stop and release one instance without affecting its creation descendants. */
2007
1961
  async finishDisposal(activation, stop) {
2008
1962
  this.wake(activation);
2009
1963
  const { childId } = activation;
@@ -2027,22 +1981,7 @@ var SubagentManager = class {
2027
1981
  } else {
2028
1982
  if (stop) {
2029
1983
  activation.handle.agent.cancel({ kind: "parent" });
2030
- const idle = activation.handle.agent.whenIdle();
2031
- const children = [...activation.ownedChildren].map((child) => this.resident.get(child)).filter((child) => child !== void 0);
2032
- const pending = [...this.materializations].filter((item) => item.lineage.includes(activation.handle.agent));
2033
- const childDisposals = children.map((child) => this.dispose(child));
2034
- for (const item of pending) item.controller.abort();
2035
- childDisposals.push(...pending.map((item) => item.settled));
2036
- const reasons = (await Promise.all(childDisposals.map(async (disposal) => {
2037
- try {
2038
- await disposal;
2039
- return;
2040
- } catch (error) {
2041
- return error;
2042
- }
2043
- }))).filter((reason) => reason !== void 0);
2044
- if (reasons.length > 0) failures.push(new SubagentError(`subagent "${childId}" child teardown failed: ${reasons.map((reason) => failureMessage(reason)).join("; ")}`, "ACTIVATION_TEARDOWN_FAILED"));
2045
- await idle;
1984
+ await activation.handle.agent.whenIdle();
2046
1985
  await this.flushFinalState(activation);
2047
1986
  }
2048
1987
  result = captureLocalResult(activation);
@@ -2067,18 +2006,22 @@ var SubagentManager = class {
2067
2006
  stopReason: "error"
2068
2007
  };
2069
2008
  this.notifySettlement(activation, result);
2070
- this.releaseOwnership(childId);
2071
- activation.observer.settle(terminal);
2009
+ this.host.emitLifecycle("subagent/end", {
2010
+ ...this.runInfo(activation),
2011
+ stopReason: terminal.stopReason,
2012
+ ...terminal.output.length === 0 ? {} : { lastAssistantMessage: terminal.output }
2013
+ }, activation.root);
2072
2014
  if (resultFailure === void 0) activation.result.resolve(result);
2073
2015
  else activation.result.reject(resultFailure.error);
2074
2016
  if (failure !== void 0) throw failure;
2075
2017
  }
2076
2018
  /** Deliver the captured execution result to the durable direct parent after cleanup. */
2077
2019
  notifySettlement(activation, terminal) {
2078
- if (!activation.announced || activation.delivery === "caller") return;
2020
+ if (!activation.accepted || activation.delivery === "caller") return;
2079
2021
  try {
2080
- const parent = this.ctx.agents.get(activation.parent.id);
2081
- if (parent !== activation.parent) return;
2022
+ const parentId = activation.ancestors.at(-1);
2023
+ const parent = this.ctx.agents.get(parentId);
2024
+ if (parent === void 0 || this.rootOf(parent) !== activation.root) return;
2082
2025
  const message = createSettlementMessage(activation.childId, terminal, activation.kind === "local", activation.kind === "local" && activation.omitClosingText);
2083
2026
  if (this.closingTeardownFor(parent) !== void 0) {
2084
2027
  parent.inject(message);
@@ -2105,124 +2048,100 @@ var SubagentManager = class {
2105
2048
  dispose: () => this.dispose(activation)
2106
2049
  };
2107
2050
  }
2108
- /** Route one parent-originated delivery through residency and cold resume. */
2109
- async deliverToChild(parent, childId, content, options) {
2110
- this.assertAdmitting(parent);
2111
- const releaseHold = this.holdOwnership(parent, childId);
2112
- try {
2113
- return await this.deliverFollowup(parent, childId, content, options);
2114
- } catch (error) {
2115
- releaseHold();
2116
- throw error;
2117
- }
2118
- }
2119
- /** The delivery loop behind {@link deliverToChild}, run under the parent hold. */
2120
- async deliverFollowup(parent, childId, content, options) {
2051
+ /** Serialize target restoration and inbox admission; closure is awaited outside the lock. */
2052
+ async deliverToMember(root, childId, content, options) {
2121
2053
  while (true) {
2122
- const live = await this.locks.run(childId, async () => {
2054
+ this.assertAdmitting(root);
2055
+ options.signal.throwIfAborted();
2056
+ const result = await this.locks.run(childId, async () => {
2057
+ if (childId === root.id) {
2058
+ if (contentHasImage(content)) await this.assertImageCapable(root, options.signal);
2059
+ options.signal.throwIfAborted();
2060
+ options.authorize?.();
2061
+ this.assertAdmitting(root);
2062
+ const message = createUserMessage({
2063
+ content,
2064
+ source: options.source
2065
+ });
2066
+ this.sendWaking(root, message, options.delivery);
2067
+ options.onAccepted?.();
2068
+ return { messageId: message.id };
2069
+ }
2123
2070
  const activation = this.resident.get(childId);
2124
- if (activation === void 0) return this.coldResume(parent, childId, content, options);
2125
- requireLocalActivation(activation);
2126
- const disposal = activation.closing;
2127
- /* v8 ignore next 3 -- the send-versus-dispose cutoff needs a delivery to
2128
- * observe the transaction inside the same critical section that opened it. */
2129
- if (disposal !== void 0) return disposal.then(() => void 0, () => void 0);
2130
- if (contentHasImage(content)) {
2131
- await this.assertImageCapable(requireLocalActivation(activation).handle.agent, options.signal);
2132
- if (activation.closing !== void 0) {
2133
- await Promise.allSettled([activation.closing]);
2134
- return;
2135
- }
2071
+ if (activation === void 0) {
2072
+ const restored = await this.restoreTarget(root, childId, options.signal);
2073
+ return { messageId: await this.submitMaterialized(restored, content, options, root) };
2136
2074
  }
2137
- const messageId = this.submitAdmitted(activation, content, options, parent);
2138
- this.announce(activation);
2139
- return messageId;
2075
+ if (activation.root !== root) throw new SubagentError("target belongs to another subagent tree", "UNAUTHORIZED");
2076
+ requireLocalActivation(activation);
2077
+ if (activation.closing === void 0 && contentHasImage(content)) await this.assertImageCapable(requireLocalActivation(activation).handle.agent, options.signal);
2078
+ if (activation.closing !== void 0) return { closing: activation.closing };
2079
+ return { messageId: this.submitAdmitted(activation, content, options, root) };
2140
2080
  });
2141
- /* v8 ignore start -- only a delivery that lost the disposal cutoff retries. */
2142
- if (live !== void 0) return live;
2143
- this.assertAdmitting(parent);
2144
- options.signal.throwIfAborted();
2081
+ if ("messageId" in result) return result.messageId;
2082
+ await Promise.allSettled([result.closing]);
2145
2083
  }
2146
2084
  }
2147
- /** Deliver one resident continuable child's message to its live direct parent. */
2148
- sendToParent(activation, sender, content) {
2149
- /* v8 ignore next 6 -- only synchronous re-entrant teardown can open this
2150
- * transaction between exact-agent authorization and this no-await span. */
2151
- if (activation.closing !== void 0) throw new SubagentError(`subagent "${sender.id}" activation is being disposed; the message was not delivered`, "ACTIVATION_CLOSING");
2152
- const parent = this.ctx.agents.get(activation.parent.id);
2153
- if (parent !== activation.parent) throw new SubagentError("direct parent is not live; the message was not delivered", "PARENT_UNAVAILABLE");
2154
- const message = createAgentMessage(sender, content);
2155
- this.sendAgentMessage(parent, message);
2156
- activation.omitClosingText = true;
2157
- return message.id;
2158
- }
2159
- /** Send one Agent message while translating only the target's own rejection. */
2160
- sendAgentMessage(parent, message) {
2085
+ /** Read continuation history, preserving cancellation and reporting unavailable storage. */
2086
+ async observeContinuation(childId, signal) {
2087
+ const query = this.requireSessionQuery();
2161
2088
  try {
2162
- this.sendWaking(parent, message, "steer");
2089
+ return await query.observeSession(childId, { signal });
2163
2090
  } catch (error) {
2164
- throw new SubagentError("direct parent is not live; the message was not delivered", "PARENT_UNAVAILABLE", { cause: error });
2091
+ signal.throwIfAborted();
2092
+ throw new SubagentError(`subagent "${childId}" is unavailable`, "NOT_RESUMABLE", { cause: error });
2165
2093
  }
2166
2094
  }
2167
- /**
2168
- * Cold-resume a persisted child and submit the waiting turn. The descriptor
2169
- * supplies every reconstruction input; no subagent provider is dispatched.
2170
- */
2171
- async coldResume(parent, childId, content, options) {
2172
- const env_1 = {
2173
- stack: [],
2174
- error: void 0,
2175
- hasError: false
2176
- };
2177
- try {
2178
- const query = this.requireSessionQuery();
2179
- let observation;
2180
- try {
2181
- observation = await query.observeSession(childId, { signal: options.signal });
2182
- } catch (error) {
2183
- options.signal.throwIfAborted();
2184
- throw new SubagentError(`subagent "${childId}" is unavailable`, "NOT_RESUMABLE", { cause: error });
2185
- }
2186
- const source = __addDisposableResource$2(env_1, observation, false);
2187
- this.assertAdmitting(parent);
2188
- this.authorizeLineage(parent, childId, source.header.parentSession);
2189
- const ownEvents = source.events.slice(source.inheritedEventCount);
2190
- const descriptor = foldSubagentDescriptor(ownEvents);
2191
- 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");
2192
- let activation;
2095
+ /** Reconstruct only the target under its subagent tree root; the caller owns acceptance or rollback. */
2096
+ async restoreTarget(root, childId, signal) {
2097
+ return this.trackStartup(root, signal, async (signal) => {
2098
+ const env_3 = {
2099
+ stack: [],
2100
+ error: void 0,
2101
+ hasError: false
2102
+ };
2193
2103
  try {
2194
- const independentlySelected = hasIndependentPresetSelection(ownEvents);
2195
- const agentPreset = independentlySelected ? source.projections?.values.agentPreset : void 0;
2196
- if (independentlySelected && typeof agentPreset !== "string") throw new SubagentError(`subagent "${childId}" has no agent preset projection for restoration`, "NOT_RESUMABLE");
2197
- activation = await this.materialize({
2198
- kind: "local",
2199
- childId,
2200
- provider: descriptor.provider,
2201
- parent,
2202
- agentOptions: {
2203
- ...descriptor.agentProvider !== void 0 ? { provider: descriptor.agentProvider } : {},
2204
- ...descriptor.agentModel !== void 0 ? { model: descriptor.agentModel } : {},
2205
- ...descriptor.agentReasoningEffort !== void 0 ? { reasoningEffort: ReasoningEffortId(descriptor.agentReasoningEffort) } : {}
2206
- },
2207
- composition: {
2208
- agentPreset: agentPreset ?? void 0,
2209
- persona: descriptor.persona,
2210
- toolFilter: descriptor.toolFilter
2211
- },
2212
- signal: options.signal
2213
- });
2214
- } catch (error) {
2215
- options.signal.throwIfAborted();
2216
- if (error instanceof SubagentError) throw error;
2217
- throw new SubagentError(`subagent "${childId}" is unavailable`, "NOT_RESUMABLE", { cause: error });
2104
+ const source = __addDisposableResource$1(env_3, await this.observeContinuation(childId, signal), false);
2105
+ this.assertAdmitting(root);
2106
+ const descriptor = foldSubagentDescriptor(source.events.slice(source.inheritedEventCount));
2107
+ 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");
2108
+ const { ancestors } = await this.resolveCreationAncestry(source, signal, root);
2109
+ let activation;
2110
+ try {
2111
+ const projectedPreset = source.projections?.values.agentPreset;
2112
+ if (projectedPreset === void 0 && source.events.some((event) => event.type === "agent-preset/selected")) throw new SubagentError(`subagent "${childId}" has no agent preset projection for restoration`, "NOT_RESUMABLE");
2113
+ const agentPreset = projectedPreset ?? source.header.agentPreset;
2114
+ activation = await this.materialize({
2115
+ kind: "local",
2116
+ childId,
2117
+ provider: descriptor.provider,
2118
+ root,
2119
+ ancestors,
2120
+ agentOptions: {
2121
+ ...descriptor.agentProvider !== void 0 ? { provider: descriptor.agentProvider } : {},
2122
+ ...descriptor.agentModel !== void 0 ? { model: descriptor.agentModel } : {},
2123
+ ...descriptor.agentReasoningEffort !== void 0 ? { reasoningEffort: ReasoningEffortId(descriptor.agentReasoningEffort) } : {}
2124
+ },
2125
+ composition: {
2126
+ agentPreset: agentPreset ?? void 0,
2127
+ persona: descriptor.persona,
2128
+ toolFilter: descriptor.toolFilter
2129
+ },
2130
+ signal
2131
+ });
2132
+ } catch (error) {
2133
+ signal.throwIfAborted();
2134
+ if (error instanceof SubagentError) throw error;
2135
+ throw new SubagentError(`subagent "${childId}" is unavailable`, "NOT_RESUMABLE", { cause: error });
2136
+ }
2137
+ return activation;
2138
+ } catch (e_3) {
2139
+ env_3.error = e_3;
2140
+ env_3.hasError = true;
2141
+ } finally {
2142
+ __disposeResources$1(env_3);
2218
2143
  }
2219
- return await this.submitMaterialized(activation, content, options, parent);
2220
- } catch (e_1) {
2221
- env_1.error = e_1;
2222
- env_1.hasError = true;
2223
- } finally {
2224
- __disposeResources$2(env_1);
2225
- }
2144
+ });
2226
2145
  }
2227
2146
  /** Admit a materialized child, commit its creation fact, and release it on failure. */
2228
2147
  async submitMaterialized(activation, content, options, parent, commit) {
@@ -2233,7 +2152,7 @@ var SubagentManager = class {
2233
2152
  }
2234
2153
  const messageId = this.submitAdmitted(activation, content, options, parent);
2235
2154
  commit?.();
2236
- this.announce(activation);
2155
+ this.accept(activation);
2237
2156
  return messageId;
2238
2157
  } catch (error) {
2239
2158
  try {
@@ -2246,19 +2165,19 @@ var SubagentManager = class {
2246
2165
  }
2247
2166
  /** Build and submit one message across the final synchronous admission cutoff. */
2248
2167
  submitAdmitted(activation, content, options, parent) {
2249
- const message = options.source === void 0 ? createAgentMessage(parent, content) : createUserMessage({
2168
+ const message = createUserMessage({
2250
2169
  content,
2251
2170
  source: options.source
2252
2171
  });
2253
2172
  options.signal.throwIfAborted();
2173
+ options.authorize?.();
2254
2174
  this.assertAdmitting(parent);
2255
- this.authorizeLineage(parent, activation.childId, activation.parent.id);
2256
- this.acquireOwnership(parent, activation.childId);
2257
2175
  try {
2258
2176
  this.deliver(activation, message, options.delivery);
2259
2177
  } finally {
2260
2178
  this.wake(activation);
2261
2179
  }
2180
+ options.onAccepted?.();
2262
2181
  return message.id;
2263
2182
  }
2264
2183
  /** Refuse image content for a child whose fixed model accepts text only. */
@@ -2284,15 +2203,6 @@ var SubagentManager = class {
2284
2203
  return query;
2285
2204
  }
2286
2205
  };
2287
- /** Creation-time selection precedes the own descriptor; historical forks can select after it. */
2288
- function hasIndependentPresetSelection(events) {
2289
- for (const event of events) {
2290
- if (event.type === "subagent/descriptor") return false;
2291
- if (event.type === "agent-preset/selected") return true;
2292
- }
2293
- /* v8 ignore next -- coldResume rejects logs without a supported own descriptor before this lookup. */
2294
- return false;
2295
- }
2296
2206
  //#endregion
2297
2207
  //#region lib/types/list-children.js
2298
2208
  /**
@@ -2300,7 +2210,7 @@ function hasIndependentPresetSelection(events) {
2300
2210
  * Each catalog read releases its Session observation before the next branch.
2301
2211
  * @module @deepseek-ai/dsh-subagent
2302
2212
  */
2303
- var __addDisposableResource$1 = function(env, value, async) {
2213
+ var __addDisposableResource = function(env, value, async) {
2304
2214
  if (value !== null && value !== void 0) {
2305
2215
  if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
2306
2216
  var dispose, inner;
@@ -2329,7 +2239,7 @@ var __addDisposableResource$1 = function(env, value, async) {
2329
2239
  } else if (async) env.stack.push({ async: true });
2330
2240
  return value;
2331
2241
  };
2332
- var __disposeResources$1 = (function(SuppressedError) {
2242
+ var __disposeResources = (function(SuppressedError) {
2333
2243
  return function(env) {
2334
2244
  function fail(e) {
2335
2245
  env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
@@ -2375,14 +2285,14 @@ async function listChildren(ctx, parentSessionId, signal) {
2375
2285
  try {
2376
2286
  const query = ctx.get("sessionQuery");
2377
2287
  if (query === void 0) throw new SubagentError("listing subagents requires the sessionQuery service (load @deepseek-ai/dsh-session-query)", "SUBAGENT_CONTROL_QUERY_UNAVAILABLE");
2378
- const entries = __addDisposableResource$1(env_1, await query.observeSession(parentSessionId, { ...signal === void 0 ? {} : { signal } }), false).projections?.values.subagentCatalog;
2288
+ const entries = __addDisposableResource(env_1, await query.observeSession(parentSessionId, { ...signal === void 0 ? {} : { signal } }), false).projections?.values.subagentCatalog;
2379
2289
  if (entries === void 0) throw new SubagentError("listing subagents requires the registered subagentCatalog projection", "SUBAGENT_CONTROL_PROJECTIONS_UNAVAILABLE");
2380
2290
  return entries;
2381
2291
  } catch (e_1) {
2382
2292
  env_1.error = e_1;
2383
2293
  env_1.hasError = true;
2384
2294
  } finally {
2385
- __disposeResources$1(env_1);
2295
+ __disposeResources(env_1);
2386
2296
  }
2387
2297
  }
2388
2298
  /**
@@ -2465,159 +2375,6 @@ function assertListingNotCancelled(signal) {
2465
2375
  if (signal?.aborted) throw new SubagentError("subagent listing was cancelled", "CANCELLED");
2466
2376
  }
2467
2377
  //#endregion
2468
- //#region lib/types/archive-admission.js
2469
- /**
2470
- * The `subagent` family of the Workspace registry's archive admission: which
2471
- * subagent descendants of a Session are still inside a turn, and how they
2472
- * stop when the Session is archived with its work.
2473
- *
2474
- * @module @deepseek-ai/dsh-subagent
2475
- */
2476
- var __addDisposableResource = function(env, value, async) {
2477
- if (value !== null && value !== void 0) {
2478
- if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
2479
- var dispose, inner;
2480
- if (async) {
2481
- if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
2482
- dispose = value[Symbol.asyncDispose];
2483
- }
2484
- if (dispose === void 0) {
2485
- if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
2486
- dispose = value[Symbol.dispose];
2487
- if (async) inner = dispose;
2488
- }
2489
- if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
2490
- if (inner) dispose = function() {
2491
- try {
2492
- inner.call(this);
2493
- } catch (e) {
2494
- return Promise.reject(e);
2495
- }
2496
- };
2497
- env.stack.push({
2498
- value,
2499
- dispose,
2500
- async
2501
- });
2502
- } else if (async) env.stack.push({ async: true });
2503
- return value;
2504
- };
2505
- var __disposeResources = (function(SuppressedError) {
2506
- return function(env) {
2507
- function fail(e) {
2508
- env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
2509
- env.hasError = true;
2510
- }
2511
- var r, s = 0;
2512
- function next() {
2513
- while (r = env.stack.pop()) try {
2514
- if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
2515
- if (r.dispose) {
2516
- var result = r.dispose.call(r.value);
2517
- if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) {
2518
- fail(e);
2519
- return next();
2520
- });
2521
- } else s |= 1;
2522
- } catch (e) {
2523
- fail(e);
2524
- }
2525
- if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
2526
- if (env.hasError) throw env.error;
2527
- }
2528
- return next();
2529
- };
2530
- })(typeof SuppressedError === "function" ? SuppressedError : function(error, suppressed, message) {
2531
- var e = new Error(message);
2532
- return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
2533
- });
2534
- /**
2535
- * Answer `workspace/session-activity` with the running subagent descendants of
2536
- * the asked Session, and `workspace/session-stop` by cancelling each of them
2537
- * as their parent would. Both listeners live as long as `ctx`'s fiber.
2538
- * @param ctx - context carrying the Agent registry; the Session query service is optional and only supplies labels.
2539
- */
2540
- function installSubagentArchiveAdmission(ctx) {
2541
- ctx.on("workspace/session-activity", async ({ sessionId }, next) => {
2542
- const running = runningDescendants(ctx, sessionId);
2543
- const rest = await next();
2544
- if (running.length === 0) return rest;
2545
- return [{
2546
- kind: "subagent",
2547
- items: await Promise.all(running.map((child) => describe(ctx, child)))
2548
- }, ...rest];
2549
- });
2550
- ctx.on("workspace/session-stop", ({ sessionId }) => {
2551
- for (const child of runningDescendants(ctx, sessionId)) try {
2552
- child.cancel({ kind: "parent" });
2553
- } catch (error) {
2554
- ctx.logger.warn(`subagent: cancelling "${child.id}" for an archived Session failed: ${String(error)}`);
2555
- }
2556
- });
2557
- }
2558
- /**
2559
- * Live subagent descendants inside a turn, by durable lineage: a child whose
2560
- * header names its parent and carries the subagent origin this package
2561
- * records, at any depth. A fork shares the lineage field without the origin
2562
- * and is an independent conversation, so it never holds its source. Lineage
2563
- * is read as data, so a damaged header chain that loops is visited once.
2564
- */
2565
- function runningDescendants(ctx, rootId) {
2566
- const childrenOf = /* @__PURE__ */ new Map();
2567
- for (const agent of ctx.agents.list()) {
2568
- const { parentSession, origin } = agent.session.header;
2569
- if (parentSession === void 0 || origin !== "subagent") continue;
2570
- const siblings = childrenOf.get(parentSession) ?? [];
2571
- siblings.push(agent);
2572
- childrenOf.set(parentSession, siblings);
2573
- }
2574
- const running = [];
2575
- const pending = [rootId];
2576
- const visited = /* @__PURE__ */ new Set();
2577
- while (pending.length > 0) {
2578
- const parentId = pending.shift();
2579
- if (visited.has(parentId)) continue;
2580
- visited.add(parentId);
2581
- for (const child of childrenOf.get(parentId) ?? []) {
2582
- if (child.status === "running") running.push(child);
2583
- pending.push(child.id);
2584
- }
2585
- }
2586
- return running;
2587
- }
2588
- /**
2589
- * The child's activity item: its id, plus the durable creation label its
2590
- * descriptor carries, read through a live Session observation. Without the
2591
- * Session query service, or with a descriptor that is absent or unreadable,
2592
- * the item names the child by id alone.
2593
- */
2594
- async function describe(ctx, child) {
2595
- const query = ctx.get("sessionQuery");
2596
- if (query === void 0) return { id: child.id };
2597
- try {
2598
- const env_1 = {
2599
- stack: [],
2600
- error: void 0,
2601
- hasError: false
2602
- };
2603
- try {
2604
- const observation = __addDisposableResource(env_1, await query.observeSession(child.id, { projectionMode: "none" }), false);
2605
- const label = foldSubagentDescriptor(observation.events.slice(observation.inheritedEventCount))?.label;
2606
- return label === void 0 ? { id: child.id } : {
2607
- id: child.id,
2608
- label
2609
- };
2610
- } catch (e_1) {
2611
- env_1.error = e_1;
2612
- env_1.hasError = true;
2613
- } finally {
2614
- __disposeResources(env_1);
2615
- }
2616
- } catch {
2617
- return { id: child.id };
2618
- }
2619
- }
2620
- //#endregion
2621
2378
  //#region lib/types/projection.js
2622
2379
  /**
2623
2380
  * Pure session projections for subagent identity (mode/label) and active-turn
@@ -2969,12 +2726,18 @@ let SubagentRuntime = (() => {
2969
2726
  let _classSuper = TypertRemoteService;
2970
2727
  let _instanceExtraInitializers = [];
2971
2728
  let _prompt_decorators;
2972
- let _interruptByParent_decorators;
2729
+ let _interruptByUser_decorators;
2973
2730
  return class SubagentRuntime extends _classSuper {
2974
2731
  static {
2975
2732
  const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
2976
- _prompt_decorators = [Remote("prompt")];
2977
- _interruptByParent_decorators = [Remote("interruptByParent")];
2733
+ _prompt_decorators = [Remote({
2734
+ name: "prompt",
2735
+ resources: { session: (request) => request.childSessionId }
2736
+ })];
2737
+ _interruptByUser_decorators = [Remote({
2738
+ name: "interruptByUser",
2739
+ resources: { session: (childSessionId) => childSessionId }
2740
+ })];
2978
2741
  __esDecorate(this, null, _prompt_decorators, {
2979
2742
  kind: "method",
2980
2743
  name: "prompt",
@@ -2986,14 +2749,14 @@ let SubagentRuntime = (() => {
2986
2749
  },
2987
2750
  metadata: _metadata
2988
2751
  }, null, _instanceExtraInitializers);
2989
- __esDecorate(this, null, _interruptByParent_decorators, {
2752
+ __esDecorate(this, null, _interruptByUser_decorators, {
2990
2753
  kind: "method",
2991
- name: "interruptByParent",
2754
+ name: "interruptByUser",
2992
2755
  static: false,
2993
2756
  private: false,
2994
2757
  access: {
2995
- has: (obj) => "interruptByParent" in obj,
2996
- get: (obj) => obj.interruptByParent
2758
+ has: (obj) => "interruptByUser" in obj,
2759
+ get: (obj) => obj.interruptByUser
2997
2760
  },
2998
2761
  metadata: _metadata
2999
2762
  }, null, _instanceExtraInitializers);
@@ -3023,13 +2786,17 @@ let SubagentRuntime = (() => {
3023
2786
  super(ctx, "subagents");
3024
2787
  this.config = config;
3025
2788
  this.emitLifecycle = createLifecycleEmitter(this.ctx, (parent) => scopeTarget(this, parent));
2789
+ }
2790
+ async [Service.init]() {
2791
+ const ctx = this.ctx;
2792
+ await ctx.sessionProjections.register(subagentCatalogProjectionDefinition);
2793
+ await ctx.sessionProjections.register(subagentTimingProjectionDefinition);
2794
+ const identityProjection = await ctx.sessionProjections.register(subagentIdentityProjectionDefinition);
2795
+ if (ctx.fiber.uid === null) return;
3026
2796
  ctx.inject(["agents"], (childCtx) => {
3027
2797
  const manager = new SubagentManager(childCtx, {
3028
- startExternal: (name, request) => {
3029
- return this.expectProvider(name).start(request);
3030
- },
3031
- prepareContinuable: (name, request) => this.prepareContinuable(name, request),
3032
- observeActivation: (provider, childId, parent) => this.observeActivation(provider, childId, parent)
2798
+ identityOf: (session) => identityProjection.stateOf(session).identity,
2799
+ emitLifecycle: this.emitLifecycle
3033
2800
  }, () => this.config.maxActiveSubagents.get(), () => this.config.delegatedApproval.get());
3034
2801
  this.manager = manager;
3035
2802
  childCtx.effect(() => () => {
@@ -3037,15 +2804,6 @@ let SubagentRuntime = (() => {
3037
2804
  if (this.manager === manager) this.manager = void 0;
3038
2805
  }, "subagents.managerBinding()");
3039
2806
  });
3040
- ctx.inject(["agents"], (agentsCtx) => {
3041
- installSubagentArchiveAdmission(agentsCtx);
3042
- });
3043
- }
3044
- async [Service.init]() {
3045
- const { sessionProjections } = this.ctx;
3046
- await sessionProjections.register(subagentCatalogProjectionDefinition);
3047
- await sessionProjections.register(subagentTimingProjectionDefinition);
3048
- await sessionProjections.register(subagentIdentityProjectionDefinition);
3049
2807
  }
3050
2808
  /**
3051
2809
  * Resolve a delegation tool's depth policy against the current user setting.
@@ -3064,64 +2822,64 @@ let SubagentRuntime = (() => {
3064
2822
  * @param spec - task, backend, and result recipient.
3065
2823
  * @returns identities, execution result, and exact-activation disposal. A reserved
3066
2824
  * childId requires a local backend and guarantees an accepted messageId.
2825
+ * @throws PROVIDER_CHANGED if the selected provider is removed or replaced before dispatch.
3067
2826
  */
3068
2827
  async startActivation(spec) {
3069
2828
  const provider = this.expectProvider(spec.provider);
3070
2829
  if (spec.childId !== void 0 && provider.prepareContinuable === void 0) throw new SubagentError("reserved child ids require a local backend", "UNSUPPORTED_CAPABILITY");
3071
- assertSubagentMaxDepth(spec.request.maxDepth);
3072
- this.assertCapabilities(provider, {
3073
- ...spec.request,
3074
- signal: spec.signal
3075
- });
2830
+ assertSubagentMaxDepth(spec.maxDepth);
2831
+ this.assertCapabilities(provider, spec);
3076
2832
  const manager = this.requireManager();
3077
- if (this.ctx.get("agents")?.get(spec.request.parent.id) !== spec.request.parent) throw new SubagentError("subagent creation requires the exact live parent agent", "UNAUTHORIZED");
3078
- if (spec.request.outputSchema !== void 0) assertObjectJsonSchema(spec.request.outputSchema);
3079
- return provider.prepareContinuable === void 0 ? manager.startExternal(spec) : manager.startLocal(spec);
2833
+ if (this.ctx.get("agents")?.get(spec.parent.id) !== spec.parent) throw new SubagentError("subagent creation requires the exact live parent agent", "UNAUTHORIZED");
2834
+ if (spec.outputSchema !== void 0) assertObjectJsonSchema(spec.outputSchema);
2835
+ const prepare = provider.prepareContinuable?.bind(provider);
2836
+ if (prepare !== void 0) return manager.startLocal(spec, (request) => {
2837
+ this.assertProviderCurrent(provider);
2838
+ return prepare(request);
2839
+ });
2840
+ const start = provider.start.bind(provider);
2841
+ return manager.startExternal(spec, (request) => {
2842
+ this.assertProviderCurrent(provider);
2843
+ return start(request);
2844
+ });
2845
+ }
2846
+ /**
2847
+ * Resolve the root shared by an Agent and its other agents in its subagent tree.
2848
+ * @param caller - exact live Agent whose subagent ancestry selects the tree.
2849
+ * @returns the live root; ordinary Session forks lead independent trees.
2850
+ * @throws when the caller or its runtime owner is unavailable.
2851
+ */
2852
+ rootOf(caller) {
2853
+ return this.requireManager().rootOf(caller);
3080
2854
  }
3081
2855
  /**
3082
- * Join progressing descendants without cancelling them. Idle descendants whose
3083
- * inboxes require a later wake stay resident and do not delay host completion.
3084
- * @param parent - the exact parent whose descendant work is observed.
3085
- * @returns whether work was joined; hosts recheck parent idle after true.
2856
+ * Join progressing subagent tree work for its root. Idle members with parked input
2857
+ * stay resident without delaying host completion. Ordinary members return false.
2858
+ * @param root - exact live subagent tree root whose member work is awaited.
2859
+ * @returns whether work was joined; hosts recheck root idle after true.
3086
2860
  */
3087
- async waitForChildren(parent) {
3088
- return this.manager?.waitForChildren(parent) ?? false;
2861
+ async waitForTreeWork(root) {
2862
+ return this.manager?.waitForTreeWork(root) ?? false;
3089
2863
  }
3090
2864
  /**
3091
- * Steer one model-authored message to the sender's direct parent or direct
3092
- * continuable child. A running target admits it at the nearest step boundary;
3093
- * an idle target starts a turn, and an absent direct child cold-resumes from
3094
- * persistence. The service derives durable sender attribution from the exact
3095
- * live sender. Caller cancellation stops only pre-acceptance work.
3096
- * @param sender - exact live Agent authorizing and originating the message.
3097
- * @param targetId - durable direct-parent or direct-child session id.
2865
+ * Steer one model-authored message to another member of the same subagent tree.
2866
+ * A running target receives it at its next step; an idle target starts a turn.
2867
+ * A cold target resumes under its runtime subagent tree root without restoring
2868
+ * its creation ancestors. Sender identity is rechecked before inbox acceptance; cancellation
2869
+ * stops only pre-acceptance work and never retracts an accepted message.
2870
+ * @param sender - exact live root or resident continuable Agent originating the message.
2871
+ * @param targetId - another root or continuable member's durable Session id.
3098
2872
  * @param content - model-authored content to deliver.
3099
2873
  * @param options - caller cancellation before inbox acceptance.
3100
2874
  * @returns the accepted message's inbox id.
3101
- * @throws when continuation services are unavailable, adjacency is rejected,
3102
- * or the message was not admitted.
2875
+ * @throws when the target is outside the tree, cannot resume, or rejects input.
3103
2876
  */
3104
2877
  async sendMessage(sender, targetId, content, options) {
3105
2878
  return this.requireManager().sendMessage(sender, targetId, content, options);
3106
2879
  }
3107
2880
  /**
3108
- * Deliver one host-protocol message to a direct continuable child.
3109
- * Symbol-keyed so host adapters can preserve their own source descriptors without
3110
- * widening the public Service Definition or impersonating an Agent sender.
3111
- * @param parent - exact live direct parent authorizing delivery.
3112
- * @param childId - durable direct-child session id.
3113
- * @param content - host-authored content to deliver.
3114
- * @param source - durable host-protocol source descriptor.
3115
- * @param signal - caller cancellation before inbox acceptance.
3116
- * @param delivery - Queue as a distinct turn or Steer at the nearest step.
3117
- * @returns the accepted message's inbox id.
3118
- */
3119
- [deliverSubagentPrompt](parent, childId, content, source, signal, delivery) {
3120
- return delivery === "steer" ? this.requireManager().steerPrompt(parent, childId, content, source, signal) : this.requireManager().queuePrompt(parent, childId, content, source, signal);
3121
- }
3122
- /**
3123
- * Interrupt one live child's current execution under a human parent
3124
- * address or an exact live ancestor Agent. Fire-and-return: the cancel
2881
+ * Interrupt one live child's current execution under a trusted user
2882
+ * operation or an exact live ancestor Agent. Fire-and-return: the cancel
3125
2883
  * signal is issued before this returns, but the target may keep running
3126
2884
  * until it observes the signal. Unclaimed pending inbox work, the Activation,
3127
2885
  * and published descendants are preserved; claimed work is not requeued.
@@ -3130,7 +2888,7 @@ let SubagentRuntime = (() => {
3130
2888
  * is an accepted no-op, as is a manager-less composition, which cannot own a
3131
2889
  * live Activation.
3132
2890
  * @param targetSessionId - the durable child session id to interrupt.
3133
- * @param authority - the human parent address or exact live ancestor Agent.
2891
+ * @param authority - a trusted user operation or exact live ancestor Agent.
3134
2892
  * @throws {SubagentError} `UNAUTHORIZED` when the authority does not own the
3135
2893
  * live target.
3136
2894
  */
@@ -3138,13 +2896,11 @@ let SubagentRuntime = (() => {
3138
2896
  this.manager?.interrupt(targetSessionId, authority);
3139
2897
  }
3140
2898
  /**
3141
- * Close subagent admission below exact live parent Agents, stop only their
3142
- * visible descendant Activations synchronously, then await admitted scoped
3143
- * materializations and release those forests child-first. The scoped cutoff
3144
- * lasts until each exact parent leaves the registry; unrelated parent trees
3145
- * remain live.
3146
- * @param parents - exact host-owned parent Agents entering teardown.
3147
- * @returns once every retained descendant activation released its execution handle.
2899
+ * Close admission and stop all instances in the selected subagent trees, then await
2900
+ * pending startup and cleanup. Ordinary members are ignored. The cutoff lasts
2901
+ * until each exact root leaves the registry; unrelated subagent trees remain live.
2902
+ * @param parents - exact live subagent tree roots entering teardown.
2903
+ * @returns once all admitted startup and member cleanup have settled.
3148
2904
  * @throws an aggregate error after all branches settle when any failed.
3149
2905
  */
3150
2906
  async drainDescendants(parents) {
@@ -3153,33 +2909,6 @@ let SubagentRuntime = (() => {
3153
2909
  await manager.drainDescendants(parents);
3154
2910
  }
3155
2911
  /**
3156
- * Release selected resident direct children of one exact live
3157
- * parent. Other children of the same parent remain admitted and resident.
3158
- * Absent targets and a manager-less composition are accepted no-ops.
3159
- * @param parent - exact live direct parent authorizing the selected release.
3160
- * @param childIds - durable direct-child ids to release when resident.
3161
- * @returns once every selected activation released its execution handle.
3162
- * @throws {SubagentError} `UNAUTHORIZED` when a resident target belongs to a
3163
- * different parent or the supplied parent identity is stale.
3164
- */
3165
- async drainChildren(parent, childIds) {
3166
- const manager = this.manager;
3167
- if (manager === void 0) return;
3168
- await manager.drainChildren(parent, childIds);
3169
- }
3170
- /**
3171
- * Read the parent's durable direct-child catalog without loading or resuming an Agent.
3172
- * The service owns and releases the live-preferred Session observation.
3173
- * @param parentSessionId - parent whose direct children are requested.
3174
- * @param signal - cancellation forwarded to the Session query.
3175
- * @returns catalog children in parent event order.
3176
- * @throws {@link SubagentError} when query or catalog projection is unavailable.
3177
- * @throws SessionQueryError when the parent cannot be read or the query is cancelled.
3178
- */
3179
- listChildren(parentSessionId, signal) {
3180
- return listChildren(this.ctx, parentSessionId, signal);
3181
- }
3182
- /**
3183
2912
  * Recursively list reachable parent catalogs in stable pre-order, preserving
3184
2913
  * each catalog's event order. Each row carries its catalog parent and depth;
3185
2914
  * external children are leaves; one-shot and unknown-mode children remain
@@ -3199,8 +2928,8 @@ let SubagentRuntime = (() => {
3199
2928
  return listDescendants(this.ctx, rootSessionId, signal);
3200
2929
  }
3201
2930
  /**
3202
- * Deliver one browser-authored message to a continuable child through the
3203
- * exact live direct parent, retaining the caller-minted request identity and
2931
+ * Deliver one browser-authored message to a continuable child through its
2932
+ * live subagent tree root, retaining the caller-minted request identity and
3204
2933
  * validated browser zone on the accepted message. Success identifies the
3205
2934
  * message the child's inbox accepted; later execution is independent of this
3206
2935
  * call. Queue delivery targets a later turn; steer delivery targets the
@@ -3208,21 +2937,20 @@ let SubagentRuntime = (() => {
3208
2937
  * Image parts are admitted and persisted through the attachment store
3209
2938
  * before delivery, and the child's model must accept image input.
3210
2939
  * Cold resume at capacity rejects with `subagent/delivery-unavailable`.
3211
- * @param request - durable address, delivery, minted identity, content, and optional browser zone.
2940
+ * Access is checked on the target Session before attachment admission or restoration.
2941
+ * @param request - target id, delivery, minted identity, content, and optional browser zone.
3212
2942
  * @param signal - carrier cancellation, owning the call until inbox acceptance.
3213
2943
  * @returns the accepted message's inbox identity.
3214
2944
  * @throws {RemoteError} `gateway/bad-request`, `subagent/attachment-invalid`,
3215
- * `subagent/invalid-time-zone`, `subagent/parent-unavailable`,
2945
+ * `subagent/invalid-time-zone`,
3216
2946
  * `subagent/not-resumable`, `subagent/unauthorized`,
3217
2947
  * `subagent/delivery-unavailable`, `gateway/cancelled`, or `gateway/internal`.
3218
2948
  */
3219
2949
  async prompt(request, signal) {
3220
- const { parentSessionId, childSessionId, clientTimeZone, delivery } = request;
2950
+ const { childSessionId, clientTimeZone, delivery } = request;
3221
2951
  validateControlRequest("subagent.prompt", request);
3222
2952
  const canonicalTimeZone = clientTimeZone === void 0 ? void 0 : canonicalClientTimeZone(clientTimeZone);
3223
2953
  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 });
3224
- const parent = this.ctx.get("agents")?.get(parentSessionId);
3225
- if (parent === void 0) throw new RemoteError("subagent/parent-unavailable", `parent session "${parentSessionId}" is not live`, { parentSessionId });
3226
2954
  const principal = principalOf(this.ctx.invocation);
3227
2955
  const source = {
3228
2956
  kind: "user",
@@ -3241,38 +2969,29 @@ let SubagentRuntime = (() => {
3241
2969
  if (attachments === void 0) throw new Error("subagent image prompt requires an attachment store");
3242
2970
  content = await attachments.admitPromptContent(request.content);
3243
2971
  }
3244
- return { messageId: await this[deliverSubagentPrompt](parent, childSessionId, content, source, signal, delivery) };
2972
+ return { messageId: await this.requireManager().deliverPrompt(childSessionId, content, {
2973
+ source,
2974
+ signal,
2975
+ delivery
2976
+ }) };
3245
2977
  } catch (error) {
3246
2978
  return rejectPrompt(error, childSessionId, signal);
3247
2979
  }
3248
2980
  }
3249
2981
  /**
3250
- * Remote face of {@link interrupt} under one durable parent address. No
3251
- * catalog, history, persistence, or parent Agent lookup runs: the core
3252
- * primitive alone authorizes the address against the live Activation, which
3253
- * is what keeps a live child interruptible while its parent Agent is offline.
2982
+ * Remote face of {@link interrupt}, authorized on the target Session by the Gateway.
2983
+ * No catalog, history, persistence, or parent Agent lookup runs.
3254
2984
  * Absent, idle, and already-completed targets are accepted no-ops there.
3255
2985
  * @param childSessionId - durable child session id to interrupt.
3256
- * @param parentSessionId - durable direct parent whose authority is claimed.
3257
- * @param mode - required continuable-address discriminator.
3258
2986
  * @returns acknowledgement that the cancel signal was admitted, not that the target is quiescent.
3259
2987
  * @throws {RemoteError} `gateway/bad-request` for an empty id,
3260
- * `subagent/unauthorized` when the address does not own the live target,
3261
2988
  * otherwise `gateway/internal`.
3262
2989
  */
3263
- interruptByParent(childSessionId, parentSessionId, mode) {
3264
- validateControlRequest("subagent.interrupt", {
3265
- childSessionId,
3266
- parentSessionId,
3267
- mode
3268
- });
2990
+ interruptByUser(childSessionId) {
2991
+ validateControlRequest("subagent.interrupt", { childSessionId });
3269
2992
  try {
3270
- this.interrupt(childSessionId, {
3271
- kind: "user",
3272
- parentSessionId
3273
- });
2993
+ this.interrupt(childSessionId, { kind: "user" });
3274
2994
  } catch (error) {
3275
- if (error instanceof SubagentError && error.code === "UNAUTHORIZED") throw new RemoteError("subagent/unauthorized", "subagent does not belong to this parent", { childSessionId }, { cause: error });
3276
2995
  throw new RemoteError("gateway/internal", "subagent interrupt failed", {}, { cause: error });
3277
2996
  }
3278
2997
  return { accepted: true };
@@ -3306,20 +3025,9 @@ let SubagentRuntime = (() => {
3306
3025
  getProvider(name) {
3307
3026
  return this.providers.get(name);
3308
3027
  }
3309
- /**
3310
- * List registered provider names in insertion order.
3311
- * @returns the registered names.
3312
- */
3313
- list() {
3314
- return [...this.providers.keys()];
3315
- }
3316
- /**
3317
- * Resolve one provider's detached continuable-creation contribution. Method
3318
- * presence on the provider IS the capability, so a provider without it is
3319
- * rejected before the manager reserves any child resources.
3320
- */
3321
- async prepareContinuable(name, request) {
3322
- return this.expectProvider(name).prepareContinuable(request);
3028
+ /** A pending start must use the exact provider whose capabilities were checked. */
3029
+ assertProviderCurrent(provider) {
3030
+ if (this.providers.get(provider.name) !== provider) throw new SubagentError(`subagent provider "${provider.name}" changed during startup; retry the delegation`, "PROVIDER_CHANGED");
3323
3031
  }
3324
3032
  /** Look up a provider for dispatch or fail loud. */
3325
3033
  expectProvider(name) {
@@ -3332,13 +3040,6 @@ let SubagentRuntime = (() => {
3332
3040
  if (this.manager === void 0) throw new SubagentError("continuable subagents require the agents service", "CONTINUATION_UNAVAILABLE");
3333
3041
  return this.manager;
3334
3042
  }
3335
- /**
3336
- * Build the lifecycle observer for one continuable Activation's residency
3337
- * epoch, so the manager publishes its edges without owning event dispatch.
3338
- */
3339
- observeActivation(provider, childId, parent) {
3340
- return createActivationObserver(this.emitLifecycle, provider, childId, parent);
3341
- }
3342
3043
  /** Reject the first requested capability that the provider lacks. */
3343
3044
  assertCapabilities(provider, request) {
3344
3045
  if (request.agentPreset !== void 0 && (!provider.capabilities.agentPreset || provider.inheritsParentContext)) throw new SubagentError(`subagent provider "${provider.name}" does not support independent agent preset selection`, "UNSUPPORTED_CAPABILITY");