@morlay/dsh-subagent 0.0.10 → 0.0.11-alpha.0

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.
@@ -6,132 +6,101 @@
6
6
  * @module @deepseek-ai/dsh-subagent/manager
7
7
  */
8
8
 
9
- import { randomUUID } from "node:crypto";
10
- import { isAbsolute, resolve } from "node:path";
11
- import type {} from "@deepseek-ai/dsh-working-directory";
12
- import type { Context } from "@deepseek-ai/cordis";
13
- import type { Agent, AgentOptions, CreateAgentOptions } from "@deepseek-ai/dsh-agent";
14
- import { brandString } from "@deepseek-ai/dsh-brand";
9
+ import { randomUUID } from 'node:crypto'
10
+ import { isAbsolute, resolve } from 'node:path'
11
+ import type {} from '@deepseek-ai/dsh-working-directory'
12
+ import type { Context } from '@deepseek-ai/cordis'
13
+ import type { Agent, AgentOptions, CreateAgentOptions } from '@deepseek-ai/dsh-agent'
14
+ import { brandString } from '@deepseek-ai/dsh-brand'
15
+ import { ReasoningEffortId, contentHasImage, createUserMessage, errorChain } from '@deepseek-ai/dsh-llm'
16
+ import type { ContentBlock, MessageId, MessageSource } from '@deepseek-ai/dsh-llm'
17
+ import { SessionLogOffset } from '@deepseek-ai/dsh-session'
18
+ import type { SessionId, SessionEvent, UserMessage } from '@deepseek-ai/dsh-session'
19
+ import type { SessionPersistence } from '@deepseek-ai/dsh-session-persistence'
20
+ import type { SessionObservation, SessionQueryEngine } from '@deepseek-ai/dsh-session-query'
21
+ import type { ObjectJsonSchema } from '@deepseek-ai/dsh-tools'
15
22
  import {
16
- ReasoningEffortId,
17
- contentHasImage,
18
- createUserMessage,
19
- errorChain,
20
- } from "@deepseek-ai/dsh-llm";
21
- import type { ContentBlock, MessageId, MessageSource } from "@deepseek-ai/dsh-llm";
22
- import { SessionLogOffset } from "@deepseek-ai/dsh-session";
23
- import type { SessionId, SessionEvent, UserMessage } from "@deepseek-ai/dsh-session";
24
- import type { SessionPersistence } from "@deepseek-ai/dsh-session-persistence";
25
- import type { SessionObservation, SessionQueryEngine } from "@deepseek-ai/dsh-session-query";
26
- import type { ObjectJsonSchema } from "@deepseek-ai/dsh-tools";
27
- import {
28
- appendDelegatedPolicyOverrides,
29
- applyChildComposition,
30
- captureDelegatedPolicyOverrides,
31
- childSessionMeta,
32
- resolveChildAgentOptions,
33
- resolveChildDepth,
34
- } from "./child-agent.ts";
35
- import type { ChildComposition, DelegatedPolicyOverrides } from "./child-agent.ts";
36
- import {
37
- createAgentMessage,
38
- withContinuableReturnGuidance,
39
- createSettlementMessage,
40
- } from "./continuation-messages.ts";
41
- import { assertSubagentMaxDepth } from "./depth.ts";
42
- import { foldSubagentDescriptor, snapshotSubagentDescriptor } from "./descriptor.ts";
43
- import type { SubagentDescriptorData } from "./descriptor.ts";
44
- import { establishCatalogChild, establishExternalCatalogChild } from "./catalog.ts";
45
- import { SubagentError } from "./error.ts";
46
- import { isAdjacentAgentSendMessageTool } from "./internal.ts";
47
- import type { ActivationObserver } from "./lifecycle.ts";
23
+ appendDelegatedPolicyOverrides, applyChildComposition, captureDelegatedPolicyOverrides,
24
+ childSessionMeta, resolveChildAgentOptions, resolveChildDepth,
25
+ } from './child-agent.ts'
26
+ import type { ChildComposition, DelegatedPolicyOverrides } from './child-agent.ts'
27
+ import { createAgentMessage, withContinuableReturnGuidance, createSettlementMessage } from './continuation-messages.ts'
28
+ import { assertSubagentMaxDepth } from './depth.ts'
29
+ import { foldSubagentDescriptor, snapshotSubagentDescriptor } from './descriptor.ts'
30
+ import type { SubagentDescriptorData } from './descriptor.ts'
31
+ import { establishCatalogChild, establishExternalCatalogChild } from './catalog.ts'
32
+ import { SubagentError } from './error.ts'
33
+ import { isAdjacentAgentSendMessageTool } from './internal.ts'
34
+ import type { ActivationObserver } from './lifecycle.ts'
48
35
  import type {
49
- ContinuableCreateRequest,
50
- ContinuableCreateSpec,
51
- SubagentInterruptAuthority,
52
- SubagentActivationSpec,
53
- SubagentActivation,
54
- SubagentStartRequest,
55
- SubagentRun,
56
- SubagentSendMessageOptions,
57
- SubagentResult,
58
- ResolvedSubagentStartRequest,
59
- } from "./types.ts";
60
- import { attachStructuredRuntime } from "./structured.ts";
61
- import type { StructuredAttachment } from "./structured.ts";
62
- import type { SubagentDelivery } from "./control-types.ts";
63
- import {
64
- ActivationPool,
65
- ChildLock,
66
- activationAgent,
67
- captureLocalResult,
68
- observeLocalFailure,
69
- requireLocalActivation,
70
- } from "./activation.ts";
71
- import type { Activation, ActivationResource, LocalActivation } from "./activation.ts";
36
+ ContinuableCreateRequest, ContinuableCreateSpec, SubagentInterruptAuthority,
37
+ SubagentActivationSpec, SubagentActivation, SubagentStartRequest, SubagentRun,
38
+ SubagentSendMessageOptions, SubagentResult, ResolvedSubagentStartRequest,
39
+ } from './types.ts'
40
+ import { attachStructuredRuntime } from './structured.ts'
41
+ import type { StructuredAttachment } from './structured.ts'
42
+ import type { SubagentDelivery } from './control-types.ts'
43
+ import { ActivationPool, ChildLock, activationAgent, captureLocalResult, observeLocalFailure, requireLocalActivation } from './activation.ts'
44
+ import type { Activation, ActivationResource, LocalActivation } from './activation.ts'
72
45
 
73
46
  /** Inputs shared by model steering and human prompt delivery. */
74
47
  type ChildDeliveryOptions =
75
48
  | {
76
- readonly delivery: "steer";
77
- /**
78
- * A provided host source is preserved on the user message; omission attributes
79
- * an adjacent-Agent message to the parent.
80
- */
81
- readonly source?: MessageSource;
82
- readonly signal: AbortSignal;
83
- }
84
- | { readonly delivery: "queue"; readonly source: MessageSource; readonly signal: AbortSignal };
49
+ readonly delivery: 'steer'
50
+ /**
51
+ * A provided host source is preserved on the user message; omission attributes
52
+ * an adjacent-Agent message to the parent.
53
+ */
54
+ readonly source?: MessageSource
55
+ readonly signal: AbortSignal
56
+ }
57
+ | { readonly delivery: 'queue'; readonly source: MessageSource; readonly signal: AbortSignal }
85
58
 
86
59
  /** Package-private hooks supplied by the owning service. */
87
60
  interface SubagentHost {
88
- startExternal(name: string, request: ResolvedSubagentStartRequest): Promise<SubagentRun>;
61
+ startExternal(name: string, request: ResolvedSubagentStartRequest): Promise<SubagentRun>
89
62
  /** Resolve one provider's detached continuable-creation contribution. */
90
- prepareContinuable(
91
- name: string,
92
- request: ContinuableCreateRequest,
93
- ): Promise<ContinuableCreateSpec>;
63
+ prepareContinuable(name: string, request: ContinuableCreateRequest): Promise<ContinuableCreateSpec>
94
64
  /** Build the lifecycle observer for one Activation residency epoch. */
95
- observeActivation(provider: string, childId: SessionId, parent: Agent): ActivationObserver;
65
+ observeActivation(provider: string, childId: SessionId, parent: Agent): ActivationObserver
96
66
  }
97
67
 
98
68
  /** Inputs shared by fresh and resumed Activation materialization. */
99
69
  interface MaterializeBase {
100
- childId: SessionId;
101
- provider: string;
102
- parent: Agent;
103
- signal: AbortSignal;
104
- delivery?: "parent" | "caller" | undefined;
70
+ childId: SessionId
71
+ provider: string
72
+ parent: Agent
73
+ signal: AbortSignal
74
+ delivery?: 'parent' | 'caller' | undefined
105
75
  }
106
76
 
107
77
  /** Creation inputs for a local residency or one external backend execution. */
108
- type MaterializeInputs = MaterializeBase &
109
- (
110
- | {
111
- kind: "local";
112
- /**
113
- * Creation inputs; absent for a cold resume, which loads the persisted
114
- * session — including the delegation policy events a fresh creation seeded,
115
- * so a resume never re-captures the parent's policy.
116
- */
117
- create?: {
118
- /** Absolute initial directory captured before provider preparation. */
119
- cwd: string;
120
- seed: readonly SessionEvent[] | undefined;
121
- meta: NonNullable<CreateAgentOptions["meta"]>;
122
- /** Exact parent-log prefix length inside {@link seed}. */
123
- inheritedEventCount: SessionLogOffset;
124
- /** Policy captured at delegation: the parent's sandbox override plus the approval pin. */
125
- delegatedPolicies: DelegatedPolicyOverrides;
126
- /** Child-owned composition record appended after the inherited marker. */
127
- descriptor: SubagentDescriptorData;
128
- };
129
- agentOptions: AgentOptions;
130
- composition: ChildComposition;
131
- outputSchema?: ObjectJsonSchema | undefined;
132
- }
133
- | { kind: "external"; start: (signal: AbortSignal) => Promise<SubagentRun> }
134
- );
78
+ type MaterializeInputs = MaterializeBase & (
79
+ | {
80
+ kind: 'local'
81
+ /**
82
+ * Creation inputs; absent for a cold resume, which loads the persisted
83
+ * session — including the delegation policy events a fresh creation seeded,
84
+ * so a resume never re-captures the parent's policy.
85
+ */
86
+ create?: {
87
+ /** Absolute initial directory captured before provider preparation. */
88
+ cwd: string
89
+ seed: readonly SessionEvent[] | undefined
90
+ meta: NonNullable<CreateAgentOptions['meta']>
91
+ /** Exact parent-log prefix length inside {@link seed}. */
92
+ inheritedEventCount: SessionLogOffset
93
+ /** Policy captured at delegation: the parent's sandbox override plus the approval pin. */
94
+ delegatedPolicies: DelegatedPolicyOverrides
95
+ /** Child-owned composition record appended after the inherited marker. */
96
+ descriptor: SubagentDescriptorData
97
+ }
98
+ agentOptions: AgentOptions
99
+ composition: ChildComposition
100
+ outputSchema?: ObjectJsonSchema | undefined
101
+ }
102
+ | { kind: 'external'; start: (signal: AbortSignal) => Promise<SubagentRun> }
103
+ )
135
104
 
136
105
  /**
137
106
  * One admitted materialization and the exact live ancestry observed at its
@@ -139,42 +108,44 @@ type MaterializeInputs = MaterializeBase &
139
108
  * keep waiting even if an intermediate Agent leaves the registry meanwhile.
140
109
  */
141
110
  interface Materialization {
142
- readonly lineage: readonly Agent[];
143
- readonly settled: Promise<void>;
144
- readonly controller: AbortController;
111
+ readonly lineage: readonly Agent[]
112
+ readonly settled: Promise<void>
113
+ readonly controller: AbortController
145
114
  }
146
115
 
147
116
  /** Residency state observed by the natural-settlement watcher. */
148
- type SettlementState = "closed" | "retry" | "wait" | "ready";
117
+ type SettlementState = 'closed' | 'retry' | 'wait' | 'ready'
149
118
 
150
119
  /** Result of the final child-lock settlement decision. */
151
- type SettlementAttempt = Exclude<SettlementState, "ready"> | { readonly done: Promise<void> };
120
+ type SettlementAttempt =
121
+ | Exclude<SettlementState, 'ready'>
122
+ | { readonly done: Promise<void> }
152
123
 
153
124
  /** Render only the safe outer message from execution or teardown failures. */
154
125
  function failureMessage(error: unknown): string {
155
- return error instanceof Error ? error.message : "unknown teardown failure";
126
+ return error instanceof Error ? error.message : 'unknown teardown failure'
156
127
  }
157
128
 
158
129
  /** Own subagent requests and the resources of every live activation. */
159
130
  export class SubagentManager {
160
131
  /** Child session id → its live Activation. Process-local, never durable. */
161
- private readonly resident = new Map<SessionId, Activation>();
132
+ private readonly resident = new Map<SessionId, Activation>()
162
133
  /** Root identities retain their pool across child settlement without retaining dead roots. */
163
- private readonly rootPools = new WeakMap<Agent, ActivationPool>();
134
+ private readonly rootPools = new WeakMap<Agent, ActivationPool>()
164
135
  /** Materializations admitted before drain, tracked through publication or rollback. */
165
- private readonly materializations = new Set<Materialization>();
136
+ private readonly materializations = new Set<Materialization>()
166
137
  /** Per-child serializer shared by delivery, release, and disposal. */
167
- private readonly locks = new ChildLock();
138
+ private readonly locks = new ChildLock()
168
139
  /** Structural Cordis owner of every Activation handle. */
169
- private readonly ownerCtx: Context;
140
+ private readonly ownerCtx: Context
170
141
  /**
171
142
  * Exact roots whose host teardown has begun, with the live lineage members
172
143
  * observed under each root. Entries remain until that exact root leaves the
173
144
  * Agent registry, closing admission throughout its host's teardown without
174
145
  * poisoning a later same-id replacement.
175
146
  */
176
- private readonly closingScopes = new Map<Agent, Set<Agent>>();
177
- private draining = false;
147
+ private readonly closingScopes = new Map<Agent, Set<Agent>>()
148
+ private draining = false
178
149
 
179
150
  /**
180
151
  * Build one manager inside the service's Agent-injected context.
@@ -193,18 +164,15 @@ export class SubagentManager {
193
164
  // the drain before releasing the scope; a cleanup effect on the same scope
194
165
  // as the Agent handles would let structural handle disposal bypass
195
166
  // child-first ordering.
196
- const scope = ctx.plugin(function activationOwner() {});
197
- this.ownerCtx = scope.ctx;
198
- ctx.on("agent/disposed", ({ agent }) => {
199
- this.closingScopes.delete(agent);
200
- });
201
- ctx.effect(
202
- function* (this: SubagentManager) {
203
- yield scope.dispose;
204
- yield () => this.drain();
205
- }.bind(this),
206
- "subagents.manager()",
207
- );
167
+ const scope = ctx.plugin(function activationOwner() {})
168
+ this.ownerCtx = scope.ctx
169
+ ctx.on('agent/disposed', ({ agent }) => {
170
+ this.closingScopes.delete(agent)
171
+ })
172
+ ctx.effect(function* (this: SubagentManager) {
173
+ yield scope.dispose
174
+ yield () => this.drain()
175
+ }.bind(this), 'subagents.manager()')
208
176
  }
209
177
 
210
178
  /**
@@ -212,44 +180,50 @@ export class SubagentManager {
212
180
  * @param targetSessionId - the durable child session id to interrupt.
213
181
  * @param authority - the human parent address or exact live ancestor Agent.
214
182
  */
215
- interrupt(targetSessionId: SessionId, authority: SubagentInterruptAuthority): void {
216
- if (authority.kind === "ancestor") {
217
- const caller = authority.agent;
183
+ interrupt(
184
+ targetSessionId: SessionId,
185
+ authority: SubagentInterruptAuthority,
186
+ ): void {
187
+ if (authority.kind === 'ancestor') {
188
+ const caller = authority.agent
218
189
  if (this.ctx.agents.get(caller.id) !== caller) {
219
190
  throw new SubagentError(
220
191
  `interrupting "${targetSessionId}" requires the exact live ancestor agent`,
221
- "UNAUTHORIZED",
222
- );
192
+ 'UNAUTHORIZED',
193
+ )
223
194
  }
224
195
  if (caller.id === targetSessionId) {
225
- throw new SubagentError(`agent "${caller.id}" cannot interrupt itself`, "UNAUTHORIZED");
196
+ throw new SubagentError(
197
+ `agent "${caller.id}" cannot interrupt itself`,
198
+ 'UNAUTHORIZED',
199
+ )
226
200
  }
227
201
  }
228
- const activation = this.resident.get(targetSessionId);
229
- if (activation === undefined) return;
230
- if (authority.kind === "user") {
202
+ const activation = this.resident.get(targetSessionId)
203
+ if (activation === undefined) return
204
+ if (authority.kind === 'user') {
231
205
  if (activation.parent.id !== authority.parentSessionId) {
232
206
  throw new SubagentError(
233
207
  `subagent "${targetSessionId}" belongs to another parent session`,
234
- "UNAUTHORIZED",
235
- );
208
+ 'UNAUTHORIZED',
209
+ )
236
210
  }
237
211
  } else if (!activation.ancestry.has(authority.agent)) {
238
212
  throw new SubagentError(
239
213
  `subagent "${targetSessionId}" is not a live descendant of agent "${authority.agent.id}"`,
240
- "UNAUTHORIZED",
241
- );
214
+ 'UNAUTHORIZED',
215
+ )
242
216
  }
243
217
  // Disposal already stopped the target with a whole-Activation teardown;
244
218
  // a second cancel would be a redundant signal on a closing handle.
245
- if (activation.closing !== undefined) return;
246
- const kind = authority.kind === "user" ? "user" : "parent";
247
- if (activation.kind === "local") {
248
- activation.handle.agent.cancel({ kind }, { keepInbox: true });
219
+ if (activation.closing !== undefined) return
220
+ const kind = authority.kind === 'user' ? 'user' : 'parent'
221
+ if (activation.kind === 'local') {
222
+ activation.handle.agent.cancel({ kind }, { keepInbox: true })
249
223
  } else {
250
- activation.controller.abort({ kind });
224
+ activation.controller.abort({ kind })
251
225
  // The settlement observer reports teardown failures.
252
- void this.dispose(activation).catch(() => undefined);
226
+ void this.dispose(activation).catch(() => undefined)
253
227
  }
254
228
  }
255
229
 
@@ -259,17 +233,15 @@ export class SubagentManager {
259
233
  * child-first.
260
234
  */
261
235
  async drain(): Promise<void> {
262
- this.draining = true;
263
- for (const pending of this.materializations) pending.controller.abort();
264
- await Promise.all([...this.materializations].map((materialization) => materialization.settled));
265
- const owned = new Set<SessionId>();
236
+ this.draining = true
237
+ for (const pending of this.materializations) pending.controller.abort()
238
+ await Promise.all([...this.materializations].map(materialization => materialization.settled))
239
+ const owned = new Set<SessionId>()
266
240
  for (const activation of this.resident.values()) {
267
- for (const child of activation.ownedChildren) owned.add(child);
241
+ for (const child of activation.ownedChildren) owned.add(child)
268
242
  }
269
- const roots = [...this.resident.values()].filter(
270
- (activation) => !owned.has(activation.childId),
271
- );
272
- await this.disposeRoots(roots, "activation(s)");
243
+ const roots = [...this.resident.values()].filter(activation => !owned.has(activation.childId))
244
+ await this.disposeRoots(roots, 'activation(s)')
273
245
  }
274
246
 
275
247
  /**
@@ -278,62 +250,42 @@ export class SubagentManager {
278
250
  * @returns whether work was joined and the host must recheck parent completion.
279
251
  */
280
252
  async waitForChildren(parent: Agent): Promise<boolean> {
281
- let observed = false;
253
+ let observed = false
282
254
  while (true) {
283
- const pending = [...this.materializations].filter((item) => item.lineage.includes(parent));
284
- const children = [...this.resident.values()].filter(
285
- (item) => activationAgent(item) !== parent && item.ancestry.has(parent),
286
- );
287
- observed ||= children.some(
288
- (item) => item.kind === "local" && item.handle.agent.status !== "idle",
289
- );
290
- await Promise.all(
291
- children.flatMap((item) => (item.kind === "local" ? [item.handle.agent.whenIdle()] : [])),
292
- );
293
- const waits: Promise<unknown>[] = pending.map((item) => item.settled);
255
+ const pending = [...this.materializations].filter(item => item.lineage.includes(parent))
256
+ const children = [...this.resident.values()].filter(item => activationAgent(item) !== parent && item.ancestry.has(parent))
257
+ observed ||= children.some(item => item.kind === 'local' && item.handle.agent.status !== 'idle')
258
+ await Promise.all(children.flatMap(item => item.kind === 'local' ? [item.handle.agent.whenIdle()] : []))
259
+ const waits: Promise<unknown>[] = pending.map(item => item.settled)
294
260
  for (const item of children) {
295
- if (item.kind === "external" || item.closing !== undefined) {
296
- waits.push(item.result.promise.catch(() => undefined));
297
- continue;
261
+ if (item.kind === 'external' || item.closing !== undefined) {
262
+ waits.push(item.result.promise.catch(() => undefined))
263
+ continue
298
264
  }
299
- if (item.handle.agent.status !== "idle") {
300
- waits.push(item.handle.agent.whenIdle());
301
- continue;
265
+ if (item.handle.agent.status !== 'idle') {
266
+ waits.push(item.handle.agent.whenIdle())
267
+ continue
302
268
  }
303
269
  if (item.ownedChildren.size > 0) {
304
270
  // Published descendants are observed separately; preparation holds
305
271
  // keep the owner pending until publication or rollback wakes it.
306
- if ([...item.ownedChildren].some((id) => !this.resident.has(id)))
307
- waits.push(item.poke.promise);
308
- continue;
272
+ if ([...item.ownedChildren].some(id => !this.resident.has(id))) waits.push(item.poke.promise)
273
+ continue
309
274
  }
310
- const inbox = item.handle.agent.inbox;
311
- if (
312
- item.failureAt === undefined &&
313
- (inbox.nextTurn.length > 0 || inbox.nextStep.length > 0)
314
- )
315
- continue;
316
- waits.push(Promise.race([item.result.promise.catch(() => undefined), item.poke.promise]));
275
+ const inbox = item.handle.agent.inbox
276
+ if (item.failureAt === undefined && (inbox.nextTurn.length > 0 || inbox.nextStep.length > 0)) continue
277
+ waits.push(Promise.race([item.result.promise.catch(() => undefined), item.poke.promise]))
317
278
  }
318
279
  if (waits.length > 0) {
319
- observed = true;
320
- await Promise.all(waits);
321
- continue;
280
+ observed = true
281
+ await Promise.all(waits)
282
+ continue
322
283
  }
323
284
  // A descendant can be published while whenIdle follows another child.
324
- if (
325
- [...this.materializations].some(
326
- (item) => item.lineage.includes(parent) && !pending.includes(item),
327
- ) ||
328
- [...this.resident.values()].some(
329
- (item) =>
330
- activationAgent(item) !== parent &&
331
- item.ancestry.has(parent) &&
332
- !children.includes(item),
333
- )
334
- )
335
- continue;
336
- return observed;
285
+ if ([...this.materializations].some(item => item.lineage.includes(parent) && !pending.includes(item))
286
+ || [...this.resident.values()].some(item => activationAgent(item) !== parent
287
+ && item.ancestry.has(parent) && !children.includes(item))) continue
288
+ return observed
337
289
  }
338
290
  }
339
291
 
@@ -342,49 +294,50 @@ export class SubagentManager {
342
294
  * @param parents - exact live roots whose managed descendants must stop.
343
295
  */
344
296
  async drainDescendants(parents: readonly Agent[]): Promise<void> {
345
- const roots = new Set(parents.filter((parent) => this.ctx.agents.get(parent.id) === parent));
346
- if (roots.size === 0) return;
297
+ const roots = new Set(parents.filter(parent => this.ctx.agents.get(parent.id) === parent))
298
+ if (roots.size === 0) return
347
299
 
348
300
  for (const root of roots) {
349
- this.closingMembers(root).add(root);
301
+ this.closingMembers(root).add(root)
350
302
  }
351
303
 
352
- const targets: Activation[] = [];
304
+ const targets: Activation[] = []
353
305
  for (const activation of this.resident.values()) {
354
- const child = activationAgent(activation);
355
- const lineage = this.liveLineage(child ?? activation.parent);
356
- const owners = [...roots].filter((root) => child !== root && activation.ancestry.has(root));
357
- if (owners.length === 0) continue;
358
- targets.push(activation);
306
+ const child = activationAgent(activation)
307
+ const lineage = this.liveLineage(child ?? activation.parent)
308
+ const owners = [...roots].filter(root => child !== root
309
+ && activation.ancestry.has(root))
310
+ if (owners.length === 0) continue
311
+ targets.push(activation)
359
312
  for (const owner of owners) {
360
- const members = this.closingMembers(owner);
361
- if (child !== undefined) members.add(child);
362
- for (const agent of lineage) members.add(agent);
313
+ const members = this.closingMembers(owner)
314
+ if (child !== undefined) members.add(child)
315
+ for (const agent of lineage) members.add(agent)
363
316
  }
364
317
  }
365
318
  const materializations = [...this.materializations].filter((materialization) => {
366
- const owners = [...roots].filter((root) => materialization.lineage.includes(root));
319
+ const owners = [...roots].filter(root => materialization.lineage.includes(root))
367
320
  for (const owner of owners) {
368
- const members = this.closingMembers(owner);
369
- for (const agent of materialization.lineage) members.add(agent);
321
+ const members = this.closingMembers(owner)
322
+ for (const agent of materialization.lineage) members.add(agent)
370
323
  }
371
- if (owners.length > 0) materialization.controller.abort();
372
- return owners.length > 0;
373
- });
324
+ if (owners.length > 0) materialization.controller.abort()
325
+ return owners.length > 0
326
+ })
374
327
 
375
- const ownedTargets = new Set<SessionId>();
328
+ const ownedTargets = new Set<SessionId>()
376
329
  for (const activation of targets) {
377
- for (const child of activation.ownedChildren) ownedTargets.add(child);
330
+ for (const child of activation.ownedChildren) ownedTargets.add(child)
378
331
  }
379
- const targetRoots = targets.filter((activation) => !ownedTargets.has(activation.childId));
332
+ const targetRoots = targets.filter(activation => !ownedTargets.has(activation.childId))
380
333
 
381
334
  for (const activation of targets) {
382
- const disposal = this.dispose(activation);
383
- void disposal.catch(() => undefined);
335
+ const disposal = this.dispose(activation)
336
+ void disposal.catch(() => undefined)
384
337
  }
385
338
 
386
- await Promise.all(materializations.map((materialization) => materialization.settled));
387
- await this.disposeRoots(targetRoots, "scoped activation(s)");
339
+ await Promise.all(materializations.map(materialization => materialization.settled))
340
+ await this.disposeRoots(targetRoots, 'scoped activation(s)')
388
341
  }
389
342
 
390
343
  /**
@@ -394,29 +347,26 @@ export class SubagentManager {
394
347
  */
395
348
  async drainChildren(parent: Agent, childIds: readonly SessionId[]): Promise<void> {
396
349
  if (this.ctx.agents.get(parent.id) !== parent) {
397
- throw new SubagentError(
398
- "selected child teardown requires the exact live parent agent",
399
- "UNAUTHORIZED",
400
- );
350
+ throw new SubagentError('selected child teardown requires the exact live parent agent', 'UNAUTHORIZED')
401
351
  }
402
- const targets: Activation[] = [];
352
+ const targets: Activation[] = []
403
353
  for (const childId of new Set(childIds)) {
404
- const activation = this.resident.get(childId);
405
- if (activation === undefined) continue;
354
+ const activation = this.resident.get(childId)
355
+ if (activation === undefined) continue
406
356
  if (activation.parent.id !== parent.id || !activation.ancestry.has(parent)) {
407
357
  throw new SubagentError(
408
358
  `subagent "${childId}" is not a direct child of agent "${parent.id}"`,
409
- "UNAUTHORIZED",
410
- );
359
+ 'UNAUTHORIZED',
360
+ )
411
361
  }
412
- targets.push(activation);
362
+ targets.push(activation)
413
363
  }
414
364
 
415
365
  for (const activation of targets) {
416
- const disposal = this.dispose(activation);
417
- void disposal.catch(() => undefined);
366
+ const disposal = this.dispose(activation)
367
+ void disposal.catch(() => undefined)
418
368
  }
419
- await this.disposeRoots(targets, "selected activation(s)");
369
+ await this.disposeRoots(targets, 'selected activation(s)')
420
370
  }
421
371
 
422
372
  /**
@@ -426,81 +376,73 @@ export class SubagentManager {
426
376
  * @param spec - provider, delegation request, and caller cancellation.
427
377
  * @returns the durable child id and accepted initial prompt message id.
428
378
  */
429
- async startLocal(
430
- spec: SubagentActivationSpec,
431
- ): Promise<SubagentActivation & { messageId: MessageId }> {
432
- const request = spec.request;
433
- const parent = request.parent;
434
- this.assertAdmitting(parent);
435
- const persistence =
436
- spec.delivery === "caller" ? this.ctx.get("sessionPersistence") : this.requirePersistence();
437
- assertSubagentMaxDepth(request.maxDepth);
438
- const childId = spec.childId ?? brandString<SessionId>(randomUUID());
439
- this.assertChildIdAvailable(childId);
440
- const childDepth = resolveChildDepth(parent, request.maxDepth);
379
+ async startLocal(spec: SubagentActivationSpec): Promise<SubagentActivation & { messageId: MessageId }> {
380
+ const request = spec.request
381
+ const parent = request.parent
382
+ this.assertAdmitting(parent)
383
+ const persistence = spec.delivery === 'caller' ? this.ctx.get('sessionPersistence') : this.requirePersistence()
384
+ assertSubagentMaxDepth(request.maxDepth)
385
+ const childId = spec.childId ?? brandString<SessionId>(randomUUID())
386
+ this.assertChildIdAvailable(childId)
387
+ const childDepth = resolveChildDepth(parent, request.maxDepth)
441
388
  // Snapshot before any await: invalid descriptor JSON rejects the call
442
389
  // before a child exists, and the detached value is what reaches the log.
443
- const agentOptions = resolveChildAgentOptions(parent, request.agentOptions, childDepth);
444
- const agentProvider = agentOptions.provider;
445
- const agentModel = agentOptions.model;
446
- const agentReasoningEffort = agentOptions.reasoningEffort;
390
+ const agentOptions = resolveChildAgentOptions(parent, request.agentOptions, childDepth)
391
+ const agentProvider = agentOptions.provider
392
+ const agentModel = agentOptions.model
393
+ const agentReasoningEffort = agentOptions.reasoningEffort
447
394
  const descriptor = snapshotSubagentDescriptor({
448
- mode: "continuable",
395
+ mode: 'continuable',
449
396
  provider: spec.provider,
450
397
  label: spec.label,
451
- ...(agentProvider !== undefined ? { agentProvider } : {}),
452
- ...(agentModel !== undefined ? { agentModel } : {}),
453
- ...(agentReasoningEffort !== undefined ? { agentReasoningEffort } : {}),
454
- });
398
+ ...agentProvider !== undefined ? { agentProvider } : {},
399
+ ...agentModel !== undefined ? { agentModel } : {},
400
+ ...agentReasoningEffort !== undefined ? { agentReasoningEffort } : {},
401
+ })
455
402
  // Capture before the first await: a later parent switch belongs to the
456
403
  // parent's future, not to this child.
457
- const delegatedPolicies = captureDelegatedPolicyOverrides(parent);
404
+ const delegatedPolicies = captureDelegatedPolicyOverrides(parent)
458
405
 
459
406
  // An idle continuation-managed parent must not settle while a caller is
460
407
  // still creating its child. A turn-scoped delegation does not need this,
461
408
  // but the service is also callable outside a turn.
462
- const releaseHold = this.holdOwnership(parent, childId);
409
+ const releaseHold = this.holdOwnership(parent, childId)
463
410
  try {
464
- const cwd = await this.resolveDirectory(request, spec.signal);
411
+ const cwd = await this.resolveDirectory(request, spec.signal)
465
412
  const prepared = await this.host.prepareContinuable(spec.provider, {
466
413
  sessionId: childId,
467
414
  cwd,
468
415
  parent,
469
416
  signal: spec.signal,
470
- });
471
- spec.signal.throwIfAborted();
472
- this.assertAdmitting(parent);
417
+ })
418
+ spec.signal.throwIfAborted()
419
+ this.assertAdmitting(parent)
473
420
 
474
- const inheritedEventCount = SessionLogOffset(prepared.seed?.length ?? 0);
475
- const seed = prepared.seed;
476
- let established!: Activation;
421
+ const inheritedEventCount = SessionLogOffset(prepared.seed?.length ?? 0)
422
+ const seed = prepared.seed
423
+ let established!: Activation
477
424
  const messageId = await this.locks.run(childId, async () => {
478
- spec.signal.throwIfAborted();
479
- this.assertAdmitting(parent);
480
- this.assertChildIdAvailable(childId);
425
+ spec.signal.throwIfAborted()
426
+ this.assertAdmitting(parent)
427
+ this.assertChildIdAvailable(childId)
481
428
  if (spec.childId !== undefined) {
482
- const persisted = await persistence?.stat(childId, { signal: spec.signal });
483
- spec.signal.throwIfAborted();
484
- this.assertAdmitting(parent);
485
- this.assertChildIdAvailable(childId);
429
+ const persisted = await persistence?.stat(childId, { signal: spec.signal })
430
+ spec.signal.throwIfAborted()
431
+ this.assertAdmitting(parent)
432
+ this.assertChildIdAvailable(childId)
486
433
  if (persisted !== undefined) {
487
- throw new SubagentError(`subagent "${childId}" already exists`, "DUPLICATE_CHILD");
434
+ throw new SubagentError(`subagent "${childId}" already exists`, 'DUPLICATE_CHILD')
488
435
  }
489
436
  }
490
437
  const activation = await this.materialize({
491
- kind: "local",
438
+ kind: 'local',
492
439
  childId,
493
440
  provider: spec.provider,
494
441
  parent,
495
442
  create: {
496
443
  cwd,
497
444
  seed,
498
- meta: childSessionMeta(
499
- parent,
500
- childDepth,
501
- prepared.seed !== undefined,
502
- request.agentPreset,
503
- ),
445
+ meta: childSessionMeta(parent, childDepth, prepared.seed !== undefined, request.agentPreset),
504
446
  inheritedEventCount,
505
447
  delegatedPolicies,
506
448
  descriptor,
@@ -510,27 +452,26 @@ export class SubagentManager {
510
452
  signal: spec.signal,
511
453
  delivery: spec.delivery,
512
454
  outputSchema: spec.request.outputSchema,
513
- });
514
- established = activation;
515
- const child = requireLocalActivation(activation).handle.agent;
516
- const childHeader = child.session.header;
455
+ })
456
+ established = activation
457
+ const child = requireLocalActivation(activation).handle.agent
458
+ const childHeader = child.session.header
517
459
  return await this.submitMaterialized(
518
460
  activation,
519
- spec.delivery !== "caller" &&
520
- isAdjacentAgentSendMessageTool(this.ctx.get("tools")?.get("send_message", child))
461
+ spec.delivery !== 'caller' && isAdjacentAgentSendMessageTool(this.ctx.get('tools')?.get('send_message', child))
521
462
  ? withContinuableReturnGuidance(parent.id, request.prompt)
522
463
  : request.prompt,
523
- { source: { kind: "user" }, signal: spec.signal, delivery: "queue" },
464
+ { source: { kind: 'user' }, signal: spec.signal, delivery: 'queue' },
524
465
  parent,
525
466
  () => {
526
- establishCatalogChild(parent.session, childHeader, descriptor);
467
+ establishCatalogChild(parent.session, childHeader, descriptor)
527
468
  },
528
- );
529
- });
530
- return { ...this.receipt(established), childId, messageId };
469
+ )
470
+ })
471
+ return { ...this.receipt(established), childId, messageId }
531
472
  } catch (error: unknown) {
532
- releaseHold();
533
- throw error;
473
+ releaseHold()
474
+ throw error
534
475
  }
535
476
  }
536
477
 
@@ -540,66 +481,53 @@ export class SubagentManager {
540
481
  * @returns the accepted activation and its result/disposal capabilities.
541
482
  */
542
483
  async startExternal(spec: SubagentActivationSpec): Promise<SubagentActivation> {
543
- const parent = spec.request.parent;
544
- this.assertAdmitting(parent);
545
- const pendingId = brandString<SessionId>(randomUUID());
546
- const releaseHold = this.holdOwnership(parent, pendingId);
547
- let activation: Activation | undefined;
484
+ const parent = spec.request.parent
485
+ this.assertAdmitting(parent)
486
+ const pendingId = brandString<SessionId>(randomUUID())
487
+ const releaseHold = this.holdOwnership(parent, pendingId)
488
+ let activation: Activation | undefined
548
489
  try {
549
- const cwd = await this.resolveDirectory(spec.request, spec.signal);
490
+ const cwd = await this.resolveDirectory(spec.request, spec.signal)
550
491
  activation = await this.materialize({
551
- kind: "external",
492
+ kind: 'external',
552
493
  childId: pendingId,
553
494
  provider: spec.provider,
554
495
  parent,
555
496
  signal: spec.signal,
556
497
  delivery: spec.delivery,
557
- start: (signal) =>
558
- this.host.startExternal(spec.provider, {
559
- ...spec.request,
560
- cwd,
561
- label: spec.label,
562
- signal,
563
- }),
564
- });
565
- this.assertAdmitting(parent);
566
- this.assertLiveParent(parent, activation.childId);
567
- spec.signal.throwIfAborted();
568
- if (spec.delivery === "parent") {
569
- establishExternalCatalogChild(parent.session, activation.childId, spec.label);
498
+ start: signal => this.host.startExternal(spec.provider, {
499
+ ...spec.request, cwd, label: spec.label, signal,
500
+ }),
501
+ })
502
+ this.assertAdmitting(parent)
503
+ this.assertLiveParent(parent, activation.childId)
504
+ spec.signal.throwIfAborted()
505
+ if (spec.delivery === 'parent') {
506
+ establishExternalCatalogChild(parent.session, activation.childId, spec.label)
570
507
  }
571
- this.announce(activation);
572
- return this.receipt(activation);
508
+ this.announce(activation)
509
+ return this.receipt(activation)
573
510
  } catch (error: unknown) {
574
511
  if (activation !== undefined) {
575
512
  try {
576
- await this.dispose(activation);
513
+ await this.dispose(activation)
577
514
  } catch (cleanupError: unknown) {
578
- this.ctx.logger.warn(
579
- `subagent "${activation.childId}" admission rollback failed: ${failureMessage(cleanupError)}`,
580
- );
515
+ this.ctx.logger.warn(`subagent "${activation.childId}" admission rollback failed: ${failureMessage(cleanupError)}`)
581
516
  }
582
517
  }
583
- throw error;
518
+ throw error
584
519
  } finally {
585
- releaseHold();
520
+ releaseHold()
586
521
  }
587
522
  }
588
523
 
589
524
  /** Capture the explicit or inherited directory while the parent still owns startup. */
590
- private async resolveDirectory(
591
- request: Pick<SubagentStartRequest, "parent" | "cwd">,
592
- signal: AbortSignal,
593
- ): Promise<string> {
594
- const cwd =
595
- request.cwd !== undefined && isAbsolute(request.cwd)
596
- ? request.cwd
597
- : resolve(
598
- await this.ctx.workingDirectory.ensure(request.parent, signal),
599
- request.cwd ?? ".",
600
- );
601
- signal.throwIfAborted();
602
- return cwd;
525
+ private async resolveDirectory(request: Pick<SubagentStartRequest, 'parent' | 'cwd'>, signal: AbortSignal): Promise<string> {
526
+ const cwd = request.cwd !== undefined && isAbsolute(request.cwd)
527
+ ? request.cwd
528
+ : resolve(await this.ctx.workingDirectory.ensure(request.parent, signal), request.cwd ?? '.')
529
+ signal.throwIfAborted()
530
+ return cwd
603
531
  }
604
532
 
605
533
  /**
@@ -620,31 +548,29 @@ export class SubagentManager {
620
548
  ): Promise<MessageId> {
621
549
  if (this.ctx.agents.get(sender.id) !== sender) {
622
550
  throw new SubagentError(
623
- "message delivery requires the exact live sender agent",
624
- "UNAUTHORIZED",
625
- );
551
+ 'message delivery requires the exact live sender agent',
552
+ 'UNAUTHORIZED',
553
+ )
626
554
  }
627
- this.assertAdmitting(sender);
628
- const senderActivation = this.resident.get(sender.id);
629
- if (
630
- senderActivation !== undefined &&
631
- senderActivation.kind === "local" &&
632
- senderActivation.handle.agent === sender &&
633
- senderActivation.parent.id === targetId
634
- ) {
635
- options.signal.throwIfAborted();
636
- return this.sendToParent(senderActivation, sender, content);
555
+ this.assertAdmitting(sender)
556
+ const senderActivation = this.resident.get(sender.id)
557
+ if (senderActivation !== undefined
558
+ && senderActivation.kind === 'local'
559
+ && senderActivation.handle.agent === sender
560
+ && senderActivation.parent.id === targetId) {
561
+ options.signal.throwIfAborted()
562
+ return this.sendToParent(senderActivation, sender, content)
637
563
  }
638
564
  if (sender.session.header.parentSession === targetId) {
639
565
  throw new SubagentError(
640
566
  `agent "${sender.id}" is not a resident continuable child and cannot send to parent "${targetId}"`,
641
- "UNAUTHORIZED",
642
- );
567
+ 'UNAUTHORIZED',
568
+ )
643
569
  }
644
570
  return this.deliverToChild(sender, targetId, content, {
645
571
  signal: options.signal,
646
- delivery: "steer",
647
- });
572
+ delivery: 'steer',
573
+ })
648
574
  }
649
575
 
650
576
  /**
@@ -663,7 +589,7 @@ export class SubagentManager {
663
589
  source: MessageSource,
664
590
  signal: AbortSignal,
665
591
  ): Promise<MessageId> {
666
- return this.deliverToChild(parent, childId, content, { source, signal, delivery: "queue" });
592
+ return this.deliverToChild(parent, childId, content, { source, signal, delivery: 'queue' })
667
593
  }
668
594
 
669
595
  /**
@@ -682,26 +608,23 @@ export class SubagentManager {
682
608
  source: MessageSource,
683
609
  signal: AbortSignal,
684
610
  ): Promise<MessageId> {
685
- return this.deliverToChild(parent, childId, content, { source, signal, delivery: "steer" });
611
+ return this.deliverToChild(parent, childId, content, { source, signal, delivery: 'steer' })
686
612
  }
687
613
 
688
614
  /** Admit local input synchronously before any teardown can begin. */
689
615
  private deliver(activation: Activation, message: UserMessage, delivery: SubagentDelivery): void {
690
616
  if (activation.closing !== undefined) {
691
617
  throw new SubagentError(
692
- "subagent activation is being disposed; the message was not accepted",
693
- "ACTIVATION_CLOSING",
694
- );
618
+ 'subagent activation is being disposed; the message was not accepted',
619
+ 'ACTIVATION_CLOSING',
620
+ )
695
621
  }
696
- const local = requireLocalActivation(activation);
622
+ const local = requireLocalActivation(activation)
697
623
  if (local.structured?.acceptsInput() === false) {
698
- throw new SubagentError(
699
- "subagent is submitting or has submitted its structured result; the message was not accepted",
700
- "INPUT_CLOSED",
701
- );
624
+ throw new SubagentError('subagent is submitting or has submitted its structured result; the message was not accepted', 'INPUT_CLOSED')
702
625
  }
703
- if (delivery === "steer") local.handle.agent.steer(message);
704
- else local.handle.agent.followup(message);
626
+ if (delivery === 'steer') local.handle.agent.steer(message)
627
+ else local.handle.agent.followup(message)
705
628
  }
706
629
 
707
630
  /**
@@ -709,12 +632,8 @@ export class SubagentManager {
709
632
  * @param childId - proposed durable child session id.
710
633
  */
711
634
  private assertChildIdAvailable(childId: SessionId): void {
712
- if (
713
- this.resident.has(childId) ||
714
- this.ctx.agents.get(childId) !== undefined ||
715
- this.ctx.get("sessions")?.get(childId) !== undefined
716
- ) {
717
- throw new SubagentError(`subagent "${childId}" already exists`, "DUPLICATE_CHILD");
635
+ if (this.resident.has(childId) || this.ctx.agents.get(childId) !== undefined || this.ctx.get('sessions')?.get(childId) !== undefined) {
636
+ throw new SubagentError(`subagent "${childId}" already exists`, 'DUPLICATE_CHILD')
718
637
  }
719
638
  }
720
639
 
@@ -729,26 +648,25 @@ export class SubagentManager {
729
648
  * @returns the failure-path releaser; a no-op when nothing was added.
730
649
  */
731
650
  private holdOwnership(parent: Agent, childId: SessionId): () => void {
732
- const parentActivation = this.resident.get(parent.id);
733
- if (parentActivation === undefined || activationAgent(parentActivation) !== parent)
734
- return () => {};
651
+ const parentActivation = this.resident.get(parent.id)
652
+ if (parentActivation === undefined || activationAgent(parentActivation) !== parent) return () => {}
735
653
  if (parentActivation.closing !== undefined) {
736
654
  throw new SubagentError(
737
655
  `subagent parent "${parent.id}" is being disposed; the child was not established`,
738
- "ACTIVATION_CLOSING",
739
- );
656
+ 'ACTIVATION_CLOSING',
657
+ )
740
658
  }
741
- if (parentActivation.ownedChildren.has(childId)) return () => {};
742
- parentActivation.ownedChildren.add(childId);
659
+ if (parentActivation.ownedChildren.has(childId)) return () => {}
660
+ parentActivation.ownedChildren.add(childId)
743
661
  return () => {
744
- const live = this.resident.get(childId);
662
+ const live = this.resident.get(childId)
745
663
  /* v8 ignore next 4 -- reaching this arm needs another delivery to establish the child
746
664
  * between this operation's failure and its releaser running, which no test can schedule
747
665
  * deterministically: the ownership edge then belongs to that live Activation, so the
748
666
  * conservative keep leaves it for finishDisposal's releaseOwnership. */
749
- if (live !== undefined && live.closing === undefined) return;
750
- if (parentActivation.ownedChildren.delete(childId)) this.wake(parentActivation);
751
- };
667
+ if (live !== undefined && live.closing === undefined) return
668
+ if (parentActivation.ownedChildren.delete(childId)) this.wake(parentActivation)
669
+ }
752
670
  }
753
671
 
754
672
  /**
@@ -758,17 +676,17 @@ export class SubagentManager {
758
676
  * @param delivery - receiving inbox destination.
759
677
  */
760
678
  private sendWaking(parent: Agent, message: UserMessage, delivery: SubagentDelivery): void {
761
- const parentActivation = this.resident.get(parent.id);
679
+ const parentActivation = this.resident.get(parent.id)
762
680
  if (parentActivation !== undefined && activationAgent(parentActivation) === parent) {
763
681
  try {
764
- this.deliver(parentActivation, message, delivery);
682
+ this.deliver(parentActivation, message, delivery)
765
683
  } finally {
766
- this.wake(parentActivation);
684
+ this.wake(parentActivation)
767
685
  }
768
- return;
686
+ return
769
687
  }
770
- if (delivery === "steer") parent.steer(message);
771
- else parent.followup(message);
688
+ if (delivery === 'steer') parent.steer(message)
689
+ else parent.followup(message)
772
690
  }
773
691
 
774
692
  /**
@@ -776,14 +694,14 @@ export class SubagentManager {
776
694
  * @param agent - exact live Agent whose lineage determines admission.
777
695
  */
778
696
  private assertAdmitting(agent: Agent): void {
779
- const closing = this.closingTeardownFor(agent);
780
- if (closing === undefined) return;
697
+ const closing = this.closingTeardownFor(agent)
698
+ if (closing === undefined) return
781
699
  throw new SubagentError(
782
- closing === "manager"
783
- ? "subagents are draining; the operation was not admitted"
700
+ closing === 'manager'
701
+ ? 'subagents are draining; the operation was not admitted'
784
702
  : `subagents below parent "${closing.id}" are draining; the operation was not admitted`,
785
- "DRAINING",
786
- );
703
+ 'DRAINING',
704
+ )
787
705
  }
788
706
 
789
707
  /**
@@ -797,12 +715,9 @@ export class SubagentManager {
797
715
  childId: SessionId,
798
716
  parentSession: SessionId | undefined,
799
717
  ): void {
800
- this.assertLiveParent(parent, childId);
718
+ this.assertLiveParent(parent, childId)
801
719
  if (parentSession !== parent.id) {
802
- throw new SubagentError(
803
- `subagent "${childId}" belongs to another parent session`,
804
- "UNAUTHORIZED",
805
- );
720
+ throw new SubagentError(`subagent "${childId}" belongs to another parent session`, 'UNAUTHORIZED')
806
721
  }
807
722
  }
808
723
 
@@ -811,8 +726,8 @@ export class SubagentManager {
811
726
  if (this.ctx.agents.get(parent.id) !== parent) {
812
727
  throw new SubagentError(
813
728
  `subagent "${childId}" delivery requires the exact live parent agent`,
814
- "UNAUTHORIZED",
815
- );
729
+ 'UNAUTHORIZED',
730
+ )
816
731
  }
817
732
  }
818
733
 
@@ -822,35 +737,29 @@ export class SubagentManager {
822
737
  * @returns the published process-local Activation.
823
738
  */
824
739
  private materialize(inputs: MaterializeInputs): Promise<Activation> {
825
- this.assertAdmitting(inputs.parent);
826
- inputs.signal.throwIfAborted();
827
- const lineage = this.liveLineage(inputs.parent);
828
- const pool = this.resident.get(inputs.parent.id)?.pool ?? this.rootPool(inputs.parent);
829
- const releaseSlot = pool.reserve(this.maxActiveSubagents());
830
- const settled = Promise.withResolvers<void>();
831
- const controller = new AbortController();
740
+ this.assertAdmitting(inputs.parent)
741
+ inputs.signal.throwIfAborted()
742
+ const lineage = this.liveLineage(inputs.parent)
743
+ const pool = this.resident.get(inputs.parent.id)?.pool ?? this.rootPool(inputs.parent)
744
+ const releaseSlot = pool.reserve(this.maxActiveSubagents())
745
+ const settled = Promise.withResolvers<void>()
746
+ const controller = new AbortController()
832
747
  const materialization: Materialization = {
833
748
  lineage,
834
749
  settled: settled.promise,
835
750
  controller,
836
- };
837
- this.materializations.add(materialization);
751
+ }
752
+ this.materializations.add(materialization)
838
753
  return this.materializeTracked(
839
- { ...inputs, signal: AbortSignal.any([inputs.signal, controller.signal]) },
840
- lineage,
841
- pool,
842
- releaseSlot,
843
- )
844
- .catch((error: unknown) => {
845
- releaseSlot();
846
- if (error instanceof Error && error.name === "AbortError")
847
- this.assertAdmitting(inputs.parent);
848
- throw error;
849
- })
850
- .finally(() => {
851
- this.materializations.delete(materialization);
852
- settled.resolve();
853
- });
754
+ { ...inputs, signal: AbortSignal.any([inputs.signal, controller.signal]) }, lineage, pool, releaseSlot,
755
+ ).catch((error: unknown) => {
756
+ releaseSlot()
757
+ if (error instanceof Error && error.name === 'AbortError') this.assertAdmitting(inputs.parent)
758
+ throw error
759
+ }).finally(() => {
760
+ this.materializations.delete(materialization)
761
+ settled.resolve()
762
+ })
854
763
  }
855
764
 
856
765
  /**
@@ -859,85 +768,83 @@ export class SubagentManager {
859
768
  * @returns the shared close transaction.
860
769
  */
861
770
  private dispose(activation: Activation): Promise<void> {
862
- return this.close(activation, () => this.finishDisposal(activation, true));
771
+ return this.close(activation, () => this.finishDisposal(activation, true))
863
772
  }
864
773
 
865
774
  /** Close admission synchronously and share one release, including startup rollback. */
866
775
  private close(activation: Activation, release: () => Promise<void>): Promise<void> {
867
- if (activation.closing !== undefined) return activation.closing;
868
- const completion = Promise.withResolvers<void>();
869
- activation.closing = completion.promise;
870
- void release().then(completion.resolve, completion.reject);
871
- return completion.promise;
776
+ if (activation.closing !== undefined) return activation.closing
777
+ const completion = Promise.withResolvers<void>()
778
+ activation.closing = completion.promise
779
+ void release().then(completion.resolve, completion.reject)
780
+ return completion.promise
872
781
  }
873
782
 
874
783
  /** Dispose independent roots and report every branch failure after all settle. */
875
784
  private async disposeRoots(
876
785
  roots: readonly Activation[],
877
- failureSubject: "activation(s)" | "scoped activation(s)" | "selected activation(s)",
786
+ failureSubject: 'activation(s)' | 'scoped activation(s)' | 'selected activation(s)',
878
787
  ): Promise<void> {
879
- const failures = await Promise.all(
880
- roots.map(async (activation) => {
881
- try {
882
- await this.dispose(activation);
883
- return undefined;
884
- } catch (error: unknown) {
885
- return error;
886
- }
887
- }),
888
- );
889
- const reasons = failures.filter((failure) => failure !== undefined);
788
+ const failures = await Promise.all(roots.map(async (activation) => {
789
+ try {
790
+ await this.dispose(activation)
791
+ return undefined
792
+ } catch (error: unknown) {
793
+ return error
794
+ }
795
+ }))
796
+ const reasons = failures.filter(failure => failure !== undefined)
890
797
  if (reasons.length > 0) {
891
798
  throw new SubagentError(
892
- `subagent teardown failed for ${reasons.length} ${failureSubject}: ` +
893
- reasons.map((reason) => failureMessage(reason)).join("; "),
894
- "ACTIVATION_TEARDOWN_FAILED",
895
- );
799
+ `subagent teardown failed for ${reasons.length} ${failureSubject}: `
800
+ + reasons.map(reason => failureMessage(reason)).join('; '),
801
+ 'ACTIVATION_TEARDOWN_FAILED',
802
+ )
896
803
  }
897
804
  }
898
805
 
899
806
  /** Return the retained member set for one exact scoped-teardown root. */
900
807
  private closingMembers(root: Agent): Set<Agent> {
901
- const existing = this.closingScopes.get(root);
902
- if (existing !== undefined) return existing;
903
- const members = new Set<Agent>();
904
- this.closingScopes.set(root, members);
905
- return members;
808
+ const existing = this.closingScopes.get(root)
809
+ if (existing !== undefined) return existing
810
+ const members = new Set<Agent>()
811
+ this.closingScopes.set(root, members)
812
+ return members
906
813
  }
907
814
 
908
815
  /** Return the exact currently resolvable ancestry from `agent` upward. */
909
816
  private liveLineage(agent: Agent): Agent[] {
910
- const lineage = [agent];
911
- const seen = new Set<SessionId>([agent.id]);
912
- let parentSession = agent.session.header.parentSession;
817
+ const lineage = [agent]
818
+ const seen = new Set<SessionId>([agent.id])
819
+ let parentSession = agent.session.header.parentSession
913
820
  while (parentSession !== undefined) {
914
- const parent = this.ctx.agents.get(parentSession);
915
- if (parent === undefined || seen.has(parent.id)) break;
916
- lineage.push(parent);
917
- seen.add(parent.id);
918
- parentSession = parent.session.header.parentSession;
821
+ const parent = this.ctx.agents.get(parentSession)
822
+ if (parent === undefined || seen.has(parent.id)) break
823
+ lineage.push(parent)
824
+ seen.add(parent.id)
825
+ parentSession = parent.session.header.parentSession
919
826
  }
920
- return lineage;
827
+ return lineage
921
828
  }
922
829
 
923
830
  /** Return the teardown that closed continuable admission for this agent's lineage. */
924
- private closingTeardownFor(agent: Agent): Agent | "manager" | undefined {
925
- if (this.draining) return "manager";
926
- const lineage = this.liveLineage(agent);
831
+ private closingTeardownFor(agent: Agent): Agent | 'manager' | undefined {
832
+ if (this.draining) return 'manager'
833
+ const lineage = this.liveLineage(agent)
927
834
  for (const [root, members] of this.closingScopes) {
928
- if (members.has(agent) || lineage.includes(root)) return root;
835
+ if (members.has(agent) || lineage.includes(root)) return root
929
836
  }
930
- return undefined;
837
+ return undefined
931
838
  }
932
839
 
933
840
  /** Resolve a root's pool once; descendants inherit their resident parent's pool directly. */
934
841
  private rootPool(parent: Agent): ActivationPool {
935
- let pool = this.rootPools.get(parent);
842
+ let pool = this.rootPools.get(parent)
936
843
  if (pool === undefined) {
937
- pool = new ActivationPool();
938
- this.rootPools.set(parent, pool);
844
+ pool = new ActivationPool()
845
+ this.rootPools.set(parent, pool)
939
846
  }
940
- return pool;
847
+ return pool
941
848
  }
942
849
 
943
850
  /** Perform one tracked materialization through publication or rollback. */
@@ -947,95 +854,84 @@ export class SubagentManager {
947
854
  pool: ActivationPool,
948
855
  releaseSlot: () => void,
949
856
  ): Promise<Activation> {
950
- const { provider, parent } = inputs;
951
- let childId = inputs.childId;
952
- inputs.signal.throwIfAborted();
953
- let resource: ActivationResource;
954
- if (inputs.kind === "external") {
955
- const controller = new AbortController();
956
- const cancelStartup = (): void => {
957
- controller.abort(inputs.signal.reason);
958
- };
959
- inputs.signal.addEventListener("abort", cancelStartup, { once: true });
857
+ const { provider, parent } = inputs
858
+ let childId = inputs.childId
859
+ inputs.signal.throwIfAborted()
860
+ let resource: ActivationResource
861
+ if (inputs.kind === 'external') {
862
+ const controller = new AbortController()
863
+ const cancelStartup = (): void => { controller.abort(inputs.signal.reason) }
864
+ inputs.signal.addEventListener('abort', cancelStartup, { once: true })
960
865
  try {
961
- const run = await inputs.start(controller.signal);
962
- resource = { kind: "external", run, controller };
963
- void run.result.catch(() => undefined);
964
- childId = run.id;
965
- try {
966
- this.assertChildIdAvailable(childId);
967
- } catch (error: unknown) {
968
- await run.dispose();
969
- throw error;
866
+ const run = await inputs.start(controller.signal)
867
+ resource = { kind: 'external', run, controller }
868
+ void run.result.catch(() => undefined)
869
+ childId = run.id
870
+ try { this.assertChildIdAvailable(childId) } catch (error: unknown) {
871
+ await run.dispose()
872
+ throw error
970
873
  }
971
874
  } finally {
972
- inputs.signal.removeEventListener("abort", cancelStartup);
875
+ inputs.signal.removeEventListener('abort', cancelStartup)
973
876
  }
974
877
  } else {
975
- const { create } = inputs;
976
- let structured: StructuredAttachment | undefined;
878
+ const { create } = inputs
879
+ let structured: StructuredAttachment | undefined
977
880
  const setup = async (childCtx: Context, child: Agent): Promise<void> => {
978
881
  // Only fresh creation appends the descriptor and delegated policy after
979
882
  // the inherited marker; a cold resume replays those persisted events.
980
883
  if (inputs.composition.agentPreset !== undefined) {
981
- await applyChildComposition(childCtx, parent, inputs.composition);
884
+ await applyChildComposition(childCtx, parent, inputs.composition)
982
885
  }
983
886
  if (create !== undefined) {
984
887
  if (inputs.composition.agentPreset !== undefined) {
985
- child.session.append("agent-preset/selected", {
986
- agentPreset: inputs.composition.agentPreset,
987
- });
888
+ child.session.append('agent-preset/selected', { agentPreset: inputs.composition.agentPreset })
988
889
  }
989
- child.session.append("subagent/descriptor", create.descriptor);
990
- appendDelegatedPolicyOverrides(child.session, create.delegatedPolicies);
991
- const workingDirectory = childCtx.get("workingDirectory");
992
- if (workingDirectory === undefined)
993
- throw new Error("local subagents require the working-directory service");
994
- await workingDirectory.set(child, create.cwd, inputs.signal);
890
+ child.session.append('subagent/descriptor', create.descriptor)
891
+ appendDelegatedPolicyOverrides(child.session, create.delegatedPolicies)
892
+ const workingDirectory = childCtx.get('workingDirectory')
893
+ if (workingDirectory === undefined) throw new Error('local subagents require the working-directory service')
894
+ await workingDirectory.set(child, create.cwd, inputs.signal)
995
895
  }
996
- if (inputs.composition.agentPreset === undefined)
997
- await applyChildComposition(childCtx, parent, inputs.composition);
896
+ if (inputs.composition.agentPreset === undefined) await applyChildComposition(childCtx, parent, inputs.composition)
998
897
  if (inputs.outputSchema !== undefined) {
999
- structured = attachStructuredRuntime(
1000
- childCtx,
1001
- inputs.outputSchema,
1002
- () =>
1003
- this.resident.get(childId)?.ownedChildren.size === 0 &&
1004
- child.inbox.nextStep.length === 0 &&
1005
- child.inbox.nextTurn.length === 0,
1006
- );
898
+ structured = attachStructuredRuntime(childCtx, inputs.outputSchema, () =>
899
+ this.resident.get(childId)?.ownedChildren.size === 0
900
+ && child.inbox.nextStep.length === 0
901
+ && child.inbox.nextTurn.length === 0,
902
+ )
1007
903
  }
1008
- };
1009
- const handle =
1010
- create === undefined
1011
- ? await this.ownerCtx.agents.resume({
1012
- resumeSessionId: childId,
1013
- parentAgent: parent,
1014
- agentOptions: inputs.agentOptions,
1015
- signal: inputs.signal,
1016
- setup,
1017
- })
1018
- : await this.ownerCtx.agents.create({
1019
- sessionId: childId,
1020
- parentAgent: parent,
1021
- meta: create.meta,
1022
- ...(create.seed === undefined ? {} : { seed: create.seed }),
1023
- inheritedEventCount: create.inheritedEventCount,
1024
- agentOptions: inputs.agentOptions,
1025
- signal: inputs.signal,
1026
- setup,
1027
- });
904
+ }
905
+ const handle = create === undefined
906
+ ? await this.ownerCtx.agents.resume({
907
+ resumeSessionId: childId,
908
+ parentAgent: parent,
909
+ agentOptions: inputs.agentOptions,
910
+ signal: inputs.signal,
911
+ setup,
912
+ })
913
+ : await this.ownerCtx.agents.create({
914
+ sessionId: childId,
915
+ parentAgent: parent,
916
+ meta: create.meta,
917
+ ...(create.seed === undefined ? {} : { seed: create.seed }),
918
+ inheritedEventCount: create.inheritedEventCount,
919
+ agentOptions: inputs.agentOptions,
920
+ signal: inputs.signal,
921
+ setup,
922
+ })
1028
923
 
1029
924
  resource = {
1030
- kind: "local",
925
+ kind: 'local',
1031
926
  handle,
1032
927
  outputStart: handle.agent.session.seq,
1033
928
  structured,
1034
929
  failureAt: undefined,
1035
930
  poke: Promise.withResolvers<void>(),
1036
- };
931
+ omitClosingText: false,
932
+ }
1037
933
  }
1038
- const observer = this.host.observeActivation(provider, childId, parent);
934
+ const observer = this.host.observeActivation(provider, childId, parent)
1039
935
 
1040
936
  const activation: Activation = {
1041
937
  pool,
@@ -1043,41 +939,47 @@ export class SubagentManager {
1043
939
  childId,
1044
940
  ...resource,
1045
941
  parent,
1046
- delivery: inputs.delivery ?? "parent",
942
+ delivery: inputs.delivery ?? 'parent',
1047
943
  result: Promise.withResolvers<SubagentResult>(),
1048
944
  closing: undefined,
1049
- ancestry: new WeakSet(
1050
- resource.kind === "external" ? parentLineage : [resource.handle.agent, ...parentLineage],
1051
- ),
945
+ ancestry: new WeakSet(resource.kind === 'external' ? parentLineage : [resource.handle.agent, ...parentLineage]),
1052
946
  ownedChildren: new Set(),
1053
947
  observer,
1054
948
  announced: false,
1055
- };
1056
- void activation.result.promise.catch(() => undefined);
1057
- this.resident.set(childId, activation);
949
+ }
950
+ void activation.result.promise.catch(() => undefined)
951
+ this.resident.set(childId, activation)
1058
952
  try {
1059
- this.assertAdmitting(parent);
1060
- inputs.signal.throwIfAborted();
1061
- if (this.ctx.agents.get(parent.id) !== parent)
1062
- throw new SubagentError("subagent parent is no longer live", "UNAUTHORIZED");
1063
- this.acquireOwnership(parent, childId);
1064
- const wakeSettlement = (): void => {
1065
- this.wake(activation);
1066
- };
1067
- if (activation.kind === "local") {
1068
- observeLocalFailure(activation, wakeSettlement);
1069
- activation.handle.agent.ctx.on("agent/inbox/inserted", wakeSettlement);
1070
- activation.handle.agent.ctx.on("agent/inbox/claimed", wakeSettlement);
1071
- activation.handle.agent.ctx.on("agent/inbox/discarded", wakeSettlement);
953
+ this.assertAdmitting(parent)
954
+ inputs.signal.throwIfAborted()
955
+ if (this.ctx.agents.get(parent.id) !== parent) throw new SubagentError('subagent parent is no longer live', 'UNAUTHORIZED')
956
+ this.acquireOwnership(parent, childId)
957
+ const wakeSettlement = (): void => { this.wake(activation) }
958
+ if (activation.kind === 'local') {
959
+ observeLocalFailure(activation, wakeSettlement)
960
+ activation.handle.agent.ctx.on('agent/inbox/inserted', wakeSettlement)
961
+ activation.handle.agent.ctx.on('agent/inbox/claimed', () => {
962
+ // Claimed input means the closing text may report work the child's
963
+ // last message to its parent did not.
964
+ activation.omitClosingText = false
965
+ wakeSettlement()
966
+ })
967
+ activation.handle.agent.ctx.on('agent/inbox/discarded', wakeSettlement)
968
+ // A tool call logged after the message also voids it; logged calls
969
+ // cannot be skipped by tool policy. `send_message` is not concurrency
970
+ // safe, so a later call in its step is logged after it executes.
971
+ activation.handle.agent.ctx.on('session/event', (_session, event) => {
972
+ if (event.type === 'tool/call') activation.omitClosingText = false
973
+ })
1072
974
  }
1073
- observer.start(activationAgent(activation));
975
+ observer.start(activationAgent(activation))
1074
976
  } catch (error: unknown) {
1075
977
  /* v8 ignore next -- rollback failure must not mask the admission failure
1076
978
  * that prevented this operation from returning an accepted message id. */
1077
- await this.rollbackUnpublished(activation, error).catch(() => undefined);
1078
- throw error;
979
+ await this.rollbackUnpublished(activation, error).catch(() => undefined)
980
+ throw error
1079
981
  }
1080
- return activation;
982
+ return activation
1081
983
  }
1082
984
 
1083
985
  /**
@@ -1085,16 +987,13 @@ export class SubagentManager {
1085
987
  * @param activation - accepted activation to observe.
1086
988
  */
1087
989
  private announce(activation: Activation): void {
1088
- if (activation.announced) return;
1089
- activation.announced = true;
1090
- if (activation.kind === "local") {
1091
- this.watchLocalSettlement(activation);
990
+ if (activation.announced) return
991
+ activation.announced = true
992
+ if (activation.kind === 'local') {
993
+ this.watchLocalSettlement(activation)
1092
994
  } else {
1093
- const finish = (): Promise<void> =>
1094
- this.close(activation, () => this.finishDisposal(activation, false));
1095
- void activation.run.result.then(finish, finish).catch((error: unknown) => {
1096
- this.reportTeardownFailure(activation, error);
1097
- });
995
+ const finish = (): Promise<void> => this.close(activation, () => this.finishDisposal(activation, false))
996
+ void activation.run.result.then(finish, finish).catch((error: unknown) => { this.reportTeardownFailure(activation, error) })
1098
997
  }
1099
998
  }
1100
999
 
@@ -1102,103 +1001,99 @@ export class SubagentManager {
1102
1001
  private rollbackUnpublished(activation: Activation, error: unknown): Promise<void> {
1103
1002
  return this.close(activation, async () => {
1104
1003
  try {
1105
- await (activation.kind === "local"
1106
- ? activation.handle.dispose()
1107
- : activation.run.dispose());
1004
+ await (activation.kind === 'local' ? activation.handle.dispose() : activation.run.dispose())
1108
1005
  } finally {
1109
- this.resident.delete(activation.childId);
1110
- activation.releaseSlot();
1111
- this.releaseOwnership(activation.childId);
1112
- activation.result.reject(error);
1006
+ this.resident.delete(activation.childId)
1007
+ activation.releaseSlot()
1008
+ this.releaseOwnership(activation.childId)
1009
+ activation.result.reject(error)
1113
1010
  }
1114
- });
1011
+ })
1115
1012
  }
1116
1013
 
1117
1014
  /** Register the child in a continuation-managed parent's owned set. */
1118
1015
  private acquireOwnership(parent: Agent, childId: SessionId): void {
1119
- const parentActivation = this.resident.get(parent.id);
1120
- if (parentActivation === undefined) return;
1016
+ const parentActivation = this.resident.get(parent.id)
1017
+ if (parentActivation === undefined) return
1121
1018
  if (parentActivation.closing !== undefined) {
1122
1019
  throw new SubagentError(
1123
1020
  `subagent parent "${parent.id}" is being disposed; the child was not established`,
1124
- "ACTIVATION_CLOSING",
1125
- );
1021
+ 'ACTIVATION_CLOSING',
1022
+ )
1126
1023
  }
1127
- parentActivation.ownedChildren.add(childId);
1128
- this.wake(parentActivation);
1024
+ parentActivation.ownedChildren.add(childId)
1025
+ this.wake(parentActivation)
1129
1026
  }
1130
1027
 
1131
1028
  /** Remove one child from its live owner's set and let that owner re-check settlement. */
1132
1029
  private releaseOwnership(childId: SessionId): void {
1133
1030
  for (const candidate of this.resident.values()) {
1134
- if (candidate.ownedChildren.delete(childId)) this.wake(candidate);
1031
+ if (candidate.ownedChildren.delete(childId)) this.wake(candidate)
1135
1032
  }
1136
1033
  }
1137
1034
 
1138
1035
  /** Let a settlement watcher re-check residency after relevant state changes. */
1139
1036
  private wake(activation: Activation): void {
1140
- if (activation.kind === "external") return;
1141
- activation.poke.resolve();
1142
- activation.poke = Promise.withResolvers<void>();
1037
+ if (activation.kind === 'external') return
1038
+ activation.poke.resolve()
1039
+ activation.poke = Promise.withResolvers<void>()
1143
1040
  }
1144
1041
 
1145
1042
  /** Follow one Activation to natural settlement. */
1146
1043
  private watchLocalSettlement(activation: LocalActivation): void {
1147
1044
  void (async () => {
1148
1045
  while (true) {
1149
- const idleObservation = activation.poke;
1150
- await activation.handle.agent.whenIdle();
1151
- if (activation.closing !== undefined) return;
1152
- const readiness = await this.locks.run(activation.childId, () =>
1153
- Promise.resolve(this.settlementState(activation, idleObservation)),
1154
- );
1155
- if (readiness === "closed") return;
1156
- if (readiness === "retry") continue;
1157
- if (readiness === "wait") {
1158
- await idleObservation.promise;
1159
- continue;
1046
+ const idleObservation = activation.poke
1047
+ await activation.handle.agent.whenIdle()
1048
+ if (activation.closing !== undefined) return
1049
+ const readiness = await this.locks.run(activation.childId, () => Promise.resolve(
1050
+ this.settlementState(activation, idleObservation),
1051
+ ))
1052
+ if (readiness === 'closed') return
1053
+ if (readiness === 'retry') continue
1054
+ if (readiness === 'wait') {
1055
+ await idleObservation.promise
1056
+ continue
1160
1057
  }
1161
1058
 
1162
- const finalSeq = activation.handle.agent.session.seq;
1163
- await this.flushFinalState(activation);
1059
+ const finalSeq = activation.handle.agent.session.seq
1060
+ await this.flushFinalState(activation)
1164
1061
  const attempt = await this.locks.run<SettlementAttempt>(activation.childId, () => {
1165
- const state = this.settlementState(activation, idleObservation);
1166
- if (state !== "ready") return Promise.resolve(state);
1062
+ const state = this.settlementState(activation, idleObservation)
1063
+ if (state !== 'ready') return Promise.resolve(state)
1167
1064
  if (activation.handle.agent.session.seq !== finalSeq) {
1168
- return Promise.resolve("retry");
1065
+ return Promise.resolve('retry')
1169
1066
  }
1170
- let done!: Promise<void>;
1067
+ let done!: Promise<void>
1171
1068
  try {
1172
1069
  void activation.handle.agent.runMaintenance(() => {
1173
- done = this.close(activation, () => this.finishDisposal(activation, false));
1174
- return Promise.resolve();
1175
- });
1070
+ done = this.close(activation, () => this.finishDisposal(activation, false))
1071
+ return Promise.resolve()
1072
+ })
1176
1073
  } catch (_error: unknown) {
1177
1074
  // Another activity already owns the Agent's idle phase.
1178
- return Promise.resolve("retry");
1075
+ return Promise.resolve('retry')
1179
1076
  }
1180
- return Promise.resolve({ done });
1181
- });
1077
+ return Promise.resolve({ done })
1078
+ })
1182
1079
 
1183
- if (typeof attempt === "string") {
1184
- if (attempt === "closed") return;
1185
- continue;
1080
+ if (typeof attempt === 'string') {
1081
+ if (attempt === 'closed') return
1082
+ continue
1186
1083
  }
1187
1084
  try {
1188
- await attempt.done;
1085
+ await attempt.done
1189
1086
  } catch (error: unknown) {
1190
- this.reportTeardownFailure(activation, error);
1087
+ this.reportTeardownFailure(activation, error)
1191
1088
  }
1192
- return;
1089
+ return
1193
1090
  }
1194
- })();
1091
+ })()
1195
1092
  }
1196
1093
 
1197
1094
  /** Report disposal failure once after the natural settlement observer finishes. */
1198
1095
  private reportTeardownFailure(activation: Activation, error: unknown): void {
1199
- this.ctx.logger.warn(
1200
- `subagent "${activation.childId}" activation teardown failed: ${failureMessage(error)}`,
1201
- );
1096
+ this.ctx.logger.warn(`subagent "${activation.childId}" activation teardown failed: ${failureMessage(error)}`)
1202
1097
  }
1203
1098
 
1204
1099
  /** Check pending input and owned children before closing admission. */
@@ -1206,154 +1101,138 @@ export class SubagentManager {
1206
1101
  activation: LocalActivation,
1207
1102
  observation: PromiseWithResolvers<void>,
1208
1103
  ): SettlementState {
1209
- if (activation.closing !== undefined) return "closed";
1210
- if (activation.poke !== observation) return "retry";
1211
- const inbox = activation.handle.agent.inbox;
1212
- if (activation.ownedChildren.size > 0) return "wait";
1213
- if (
1214
- activation.failureAt === undefined &&
1215
- (inbox.nextTurn.length > 0 || inbox.nextStep.length > 0)
1216
- )
1217
- return "wait";
1218
- return "ready";
1104
+ if (activation.closing !== undefined) return 'closed'
1105
+ if (activation.poke !== observation) return 'retry'
1106
+ const inbox = activation.handle.agent.inbox
1107
+ if (activation.ownedChildren.size > 0) return 'wait'
1108
+ if (activation.failureAt === undefined && (inbox.nextTurn.length > 0 || inbox.nextStep.length > 0)) return 'wait'
1109
+ return 'ready'
1219
1110
  }
1220
1111
 
1221
1112
  /** Propagate stop synchronously and await descendant startup rollback before release. */
1222
1113
  private async finishDisposal(activation: Activation, stop: boolean): Promise<void> {
1223
- this.wake(activation);
1224
- const { childId } = activation;
1225
- const failures: SubagentError[] = [];
1226
- let result: SubagentResult = { output: [], stopReason: "error" };
1227
- let resultFailure: { error: unknown } | undefined;
1228
- let externalDisposal: Promise<void> | undefined;
1229
- const child = activationAgent(activation);
1230
- const detail = (error: unknown): string =>
1231
- child === undefined ? failureMessage(error) : errorChain(error);
1114
+ this.wake(activation)
1115
+ const { childId } = activation
1116
+ const failures: SubagentError[] = []
1117
+ let result: SubagentResult = { output: [], stopReason: 'error' }
1118
+ let resultFailure: { error: unknown } | undefined
1119
+ let externalDisposal: Promise<void> | undefined
1120
+ const child = activationAgent(activation)
1121
+ const detail = (error: unknown): string => child === undefined
1122
+ ? failureMessage(error)
1123
+ : errorChain(error)
1232
1124
  try {
1233
- if (activation.kind === "external") {
1125
+ if (activation.kind === 'external') {
1234
1126
  if (stop) {
1235
- activation.controller.abort({ kind: "parent" });
1127
+ activation.controller.abort({ kind: 'parent' })
1236
1128
  // Some backends settle their result only when disposal begins.
1237
- externalDisposal = Promise.resolve().then(() => activation.run.dispose());
1238
- void externalDisposal.catch(() => undefined);
1129
+ externalDisposal = Promise.resolve().then(() => activation.run.dispose())
1130
+ void externalDisposal.catch(() => undefined)
1239
1131
  }
1240
- result = await activation.run.result;
1132
+ result = await activation.run.result
1241
1133
  } else {
1242
1134
  if (stop) {
1243
- activation.handle.agent.cancel({ kind: "parent" });
1244
- const idle = activation.handle.agent.whenIdle();
1135
+ activation.handle.agent.cancel({ kind: 'parent' })
1136
+ const idle = activation.handle.agent.whenIdle()
1245
1137
  const children = [...activation.ownedChildren]
1246
- .map((child) => this.resident.get(child))
1247
- .filter((child): child is Activation => child !== undefined);
1248
- const pending = [...this.materializations].filter((item) =>
1249
- item.lineage.includes(activation.handle.agent),
1250
- );
1251
- const childDisposals = children.map((child) => this.dispose(child));
1252
- for (const item of pending) item.controller.abort();
1253
- childDisposals.push(...pending.map((item) => item.settled));
1254
- const childFailures = await Promise.all(
1255
- childDisposals.map(async (disposal) => {
1256
- try {
1257
- await disposal;
1258
- return undefined;
1259
- } catch (error: unknown) {
1260
- return error;
1261
- }
1262
- }),
1263
- );
1264
- const reasons = childFailures.filter((reason) => reason !== undefined);
1138
+ .map(child => this.resident.get(child))
1139
+ .filter((child): child is Activation => child !== undefined)
1140
+ const pending = [...this.materializations].filter(item => item.lineage.includes(activation.handle.agent))
1141
+ const childDisposals = children.map(child => this.dispose(child))
1142
+ for (const item of pending) item.controller.abort()
1143
+ childDisposals.push(...pending.map(item => item.settled))
1144
+ const childFailures = await Promise.all(childDisposals.map(async (disposal) => {
1145
+ try {
1146
+ await disposal
1147
+ return undefined
1148
+ } catch (error: unknown) {
1149
+ return error
1150
+ }
1151
+ }))
1152
+ const reasons = childFailures.filter(reason => reason !== undefined)
1265
1153
  if (reasons.length > 0) {
1266
- failures.push(
1267
- new SubagentError(
1268
- `subagent "${childId}" child teardown failed: ${reasons.map((reason) => failureMessage(reason)).join("; ")}`,
1269
- "ACTIVATION_TEARDOWN_FAILED",
1270
- ),
1271
- );
1154
+ failures.push(new SubagentError(
1155
+ `subagent "${childId}" child teardown failed: ${reasons.map(reason => failureMessage(reason)).join('; ')}`,
1156
+ 'ACTIVATION_TEARDOWN_FAILED',
1157
+ ))
1272
1158
  }
1273
- await idle;
1274
- await this.flushFinalState(activation);
1159
+ await idle
1160
+ await this.flushFinalState(activation)
1275
1161
  }
1276
- result = captureLocalResult(activation);
1162
+ result = captureLocalResult(activation)
1277
1163
  }
1278
1164
  } catch (error: unknown) {
1279
- resultFailure = { error };
1280
- failures.push(
1281
- new SubagentError(
1282
- `subagent "${childId}" activation teardown failed: ${detail(error)}`,
1283
- "ACTIVATION_TEARDOWN_FAILED",
1284
- { cause: error },
1285
- ),
1286
- );
1165
+ resultFailure = { error }
1166
+ failures.push(new SubagentError(
1167
+ `subagent "${childId}" activation teardown failed: ${detail(error)}`,
1168
+ 'ACTIVATION_TEARDOWN_FAILED',
1169
+ { cause: error },
1170
+ ))
1287
1171
  }
1288
1172
  try {
1289
- if (activation.kind === "local") await activation.handle.dispose();
1290
- else await (externalDisposal ?? activation.run.dispose());
1173
+ if (activation.kind === 'local') await activation.handle.dispose()
1174
+ else await (externalDisposal ?? activation.run.dispose())
1291
1175
  } catch (error: unknown) {
1292
- failures.push(
1293
- new SubagentError(
1294
- `subagent "${childId}" activation handle disposal failed: ${detail(error)}`,
1295
- "ACTIVATION_TEARDOWN_FAILED",
1296
- { cause: error },
1297
- ),
1298
- );
1176
+ failures.push(new SubagentError(
1177
+ `subagent "${childId}" activation handle disposal failed: ${detail(error)}`,
1178
+ 'ACTIVATION_TEARDOWN_FAILED',
1179
+ { cause: error },
1180
+ ))
1299
1181
  }
1300
1182
 
1301
- let failure: SubagentError | undefined;
1183
+ let failure: SubagentError | undefined
1302
1184
  if (failures.length === 1) {
1303
- failure = failures[0];
1185
+ failure = failures[0]
1304
1186
  } else if (failures.length > 1) {
1305
1187
  failure = new SubagentError(
1306
- `subagent "${childId}" activation teardown failed at ${failures.length} boundaries: ` +
1307
- failures.map((item) => detail(item)).join("; "),
1308
- "ACTIVATION_TEARDOWN_FAILED",
1188
+ `subagent "${childId}" activation teardown failed at ${failures.length} boundaries: `
1189
+ + failures.map(item => detail(item)).join('; '),
1190
+ 'ACTIVATION_TEARDOWN_FAILED',
1309
1191
  { cause: new AggregateError(failures) },
1310
- );
1192
+ )
1311
1193
  }
1312
- this.resident.delete(childId);
1313
- activation.releaseSlot();
1314
- const terminal: SubagentResult =
1315
- failure === undefined ? result : { output: [], stopReason: "error" };
1316
- this.notifySettlement(activation, result);
1317
- this.releaseOwnership(childId);
1318
- activation.observer.settle(terminal);
1319
- if (resultFailure === undefined) activation.result.resolve(result);
1320
- else activation.result.reject(resultFailure.error);
1321
- if (failure !== undefined) throw failure;
1194
+ this.resident.delete(childId)
1195
+ activation.releaseSlot()
1196
+ const terminal: SubagentResult = failure === undefined ? result : { output: [], stopReason: 'error' }
1197
+ this.notifySettlement(activation, result)
1198
+ this.releaseOwnership(childId)
1199
+ activation.observer.settle(terminal)
1200
+ if (resultFailure === undefined) activation.result.resolve(result)
1201
+ else activation.result.reject(resultFailure.error)
1202
+ if (failure !== undefined) throw failure
1322
1203
  }
1323
1204
 
1324
1205
  /** Deliver the captured execution result to the durable direct parent after cleanup. */
1325
1206
  private notifySettlement(activation: Activation, terminal: SubagentResult): void {
1326
- if (!activation.announced || activation.delivery === "caller") return;
1207
+ if (!activation.announced || activation.delivery === 'caller') return
1327
1208
  try {
1328
- const parent = this.ctx.agents.get(activation.parent.id);
1329
- if (parent !== activation.parent) return;
1209
+ const parent = this.ctx.agents.get(activation.parent.id)
1210
+ if (parent !== activation.parent) return
1330
1211
  const message = createSettlementMessage(
1331
- activation.childId,
1332
- terminal,
1333
- activationAgent(activation) !== undefined,
1334
- );
1212
+ activation.childId, terminal, activation.kind === 'local', activation.kind === 'local' && activation.omitClosingText,
1213
+ )
1335
1214
  if (this.closingTeardownFor(parent) !== undefined) {
1336
- parent.inject(message);
1337
- return;
1215
+ parent.inject(message)
1216
+ return
1338
1217
  }
1339
- this.sendWaking(parent, message, parent.status === "idle" ? "queue" : "steer");
1218
+ this.sendWaking(parent, message, parent.status === 'idle' ? 'queue' : 'steer')
1340
1219
  } catch (error: unknown) {
1341
1220
  this.ctx.logger.warn(
1342
- `subagent "${activation.childId}" settlement notice was not delivered to its parent: ` +
1343
- errorChain(error),
1344
- );
1221
+ `subagent "${activation.childId}" settlement notice was not delivered to its parent: `
1222
+ + errorChain(error),
1223
+ )
1345
1224
  }
1346
1225
  }
1347
1226
 
1348
1227
  /** Request a best-effort final session flush before closing natural-settlement admission. */
1349
1228
  private async flushFinalState(activation: LocalActivation): Promise<void> {
1350
1229
  try {
1351
- await activation.handle.agent.ctx.sessions.flush(activation.handle.agent.session);
1230
+ await activation.handle.agent.ctx.sessions.flush(activation.handle.agent.session)
1352
1231
  } catch (error: unknown) {
1353
1232
  this.ctx.logger.warn(
1354
- `subagent "${activation.childId}" best-effort final session flush failed; ` +
1355
- `the persisted state may be unavailable or stale on resume: ${errorChain(error)}`,
1356
- );
1233
+ `subagent "${activation.childId}" best-effort final session flush failed; `
1234
+ + `the persisted state may be unavailable or stale on resume: ${errorChain(error)}`,
1235
+ )
1357
1236
  }
1358
1237
  }
1359
1238
 
@@ -1363,7 +1242,7 @@ export class SubagentManager {
1363
1242
  childId: activation.childId,
1364
1243
  result: activation.result.promise,
1365
1244
  dispose: () => this.dispose(activation),
1366
- };
1245
+ }
1367
1246
  }
1368
1247
 
1369
1248
  /** Route one parent-originated delivery through residency and cold resume. */
@@ -1373,13 +1252,13 @@ export class SubagentManager {
1373
1252
  content: ContentBlock[],
1374
1253
  options: ChildDeliveryOptions,
1375
1254
  ): Promise<MessageId> {
1376
- this.assertAdmitting(parent);
1377
- const releaseHold = this.holdOwnership(parent, childId);
1255
+ this.assertAdmitting(parent)
1256
+ const releaseHold = this.holdOwnership(parent, childId)
1378
1257
  try {
1379
- return await this.deliverFollowup(parent, childId, content, options);
1258
+ return await this.deliverFollowup(parent, childId, content, options)
1380
1259
  } catch (error: unknown) {
1381
- releaseHold();
1382
- throw error;
1260
+ releaseHold()
1261
+ throw error
1383
1262
  }
1384
1263
  }
1385
1264
 
@@ -1392,72 +1271,74 @@ export class SubagentManager {
1392
1271
  ): Promise<MessageId> {
1393
1272
  while (true) {
1394
1273
  const live = await this.locks.run(childId, async () => {
1395
- const activation = this.resident.get(childId);
1396
- if (activation === undefined) return this.coldResume(parent, childId, content, options);
1397
- requireLocalActivation(activation);
1398
- const disposal = activation.closing;
1274
+ const activation = this.resident.get(childId)
1275
+ if (activation === undefined) return this.coldResume(parent, childId, content, options)
1276
+ requireLocalActivation(activation)
1277
+ const disposal = activation.closing
1399
1278
  /* v8 ignore next 3 -- the send-versus-dispose cutoff needs a delivery to
1400
1279
  * observe the transaction inside the same critical section that opened it. */
1401
1280
  if (disposal !== undefined) {
1402
- return disposal.then(
1403
- () => undefined,
1404
- () => undefined,
1405
- );
1281
+ return disposal.then(() => undefined, () => undefined)
1406
1282
  }
1407
1283
  if (contentHasImage(content)) {
1408
- await this.assertImageCapable(
1409
- requireLocalActivation(activation).handle.agent,
1410
- options.signal,
1411
- );
1284
+ await this.assertImageCapable(requireLocalActivation(activation).handle.agent, options.signal)
1412
1285
  if (activation.closing !== undefined) {
1413
- await Promise.allSettled([activation.closing]);
1414
- return undefined;
1286
+ await Promise.allSettled([activation.closing])
1287
+ return undefined
1415
1288
  }
1416
1289
  }
1417
- const messageId = this.submitAdmitted(activation, content, options, parent);
1418
- this.announce(activation);
1419
- return messageId;
1420
- });
1290
+ const messageId = this.submitAdmitted(activation, content, options, parent)
1291
+ this.announce(activation)
1292
+ return messageId
1293
+ })
1421
1294
  /* v8 ignore start -- only a delivery that lost the disposal cutoff retries. */
1422
- if (live !== undefined) return live;
1423
- this.assertAdmitting(parent);
1424
- options.signal.throwIfAborted();
1295
+ if (live !== undefined) return live
1296
+ this.assertAdmitting(parent)
1297
+ options.signal.throwIfAborted()
1425
1298
  /* v8 ignore stop */
1426
1299
  }
1427
1300
  }
1428
1301
 
1429
1302
  /** Deliver one resident continuable child's message to its live direct parent. */
1430
- private sendToParent(activation: Activation, sender: Agent, content: ContentBlock[]): MessageId {
1303
+ private sendToParent(
1304
+ activation: LocalActivation,
1305
+ sender: Agent,
1306
+ content: ContentBlock[],
1307
+ ): MessageId {
1431
1308
  /* v8 ignore next 6 -- only synchronous re-entrant teardown can open this
1432
1309
  * transaction between exact-agent authorization and this no-await span. */
1433
1310
  if (activation.closing !== undefined) {
1434
1311
  throw new SubagentError(
1435
1312
  `subagent "${sender.id}" activation is being disposed; the message was not delivered`,
1436
- "ACTIVATION_CLOSING",
1437
- );
1313
+ 'ACTIVATION_CLOSING',
1314
+ )
1438
1315
  }
1439
- const parent = this.ctx.agents.get(activation.parent.id);
1316
+ const parent = this.ctx.agents.get(activation.parent.id)
1440
1317
  if (parent !== activation.parent) {
1441
1318
  throw new SubagentError(
1442
- "direct parent is not live; the message was not delivered",
1443
- "PARENT_UNAVAILABLE",
1444
- );
1319
+ 'direct parent is not live; the message was not delivered',
1320
+ 'PARENT_UNAVAILABLE',
1321
+ )
1445
1322
  }
1446
- const message = createAgentMessage(sender, content);
1447
- this.sendAgentMessage(parent, message);
1448
- return message.id;
1323
+ const message = createAgentMessage(sender, content)
1324
+ this.sendAgentMessage(parent, message)
1325
+ activation.omitClosingText = true
1326
+ return message.id
1449
1327
  }
1450
1328
 
1451
1329
  /** Send one Agent message while translating only the target's own rejection. */
1452
- private sendAgentMessage(parent: Agent, message: ReturnType<typeof createUserMessage>): void {
1330
+ private sendAgentMessage(
1331
+ parent: Agent,
1332
+ message: ReturnType<typeof createUserMessage>,
1333
+ ): void {
1453
1334
  try {
1454
- this.sendWaking(parent, message, "steer");
1335
+ this.sendWaking(parent, message, 'steer')
1455
1336
  } catch (error: unknown) {
1456
1337
  throw new SubagentError(
1457
- "direct parent is not live; the message was not delivered",
1458
- "PARENT_UNAVAILABLE",
1338
+ 'direct parent is not live; the message was not delivered',
1339
+ 'PARENT_UNAVAILABLE',
1459
1340
  { cause: error },
1460
- );
1341
+ )
1461
1342
  }
1462
1343
  }
1463
1344
 
@@ -1471,68 +1352,55 @@ export class SubagentManager {
1471
1352
  content: ContentBlock[],
1472
1353
  options: ChildDeliveryOptions,
1473
1354
  ): Promise<MessageId> {
1474
- const query = this.requireSessionQuery();
1475
- let observation: SessionObservation;
1355
+ const query = this.requireSessionQuery()
1356
+ let observation: SessionObservation
1476
1357
  try {
1477
1358
  observation = await query.observeSession(childId, {
1478
1359
  signal: options.signal,
1479
- });
1360
+ })
1480
1361
  } catch (error: unknown) {
1481
- options.signal.throwIfAborted();
1482
- throw new SubagentError(`subagent "${childId}" is unavailable`, "NOT_RESUMABLE", {
1483
- cause: error,
1484
- });
1362
+ options.signal.throwIfAborted()
1363
+ throw new SubagentError(`subagent "${childId}" is unavailable`, 'NOT_RESUMABLE', { cause: error })
1485
1364
  }
1486
- using source = observation;
1487
- this.assertAdmitting(parent);
1488
- this.authorizeLineage(parent, childId, source.header.parentSession);
1489
- const ownEvents = source.events.slice(source.inheritedEventCount);
1490
- const descriptor = foldSubagentDescriptor(ownEvents);
1491
- if (descriptor === undefined || descriptor.mode !== "continuable") {
1365
+ using source = observation
1366
+ this.assertAdmitting(parent)
1367
+ this.authorizeLineage(parent, childId, source.header.parentSession)
1368
+ const ownEvents = source.events.slice(source.inheritedEventCount)
1369
+ const descriptor = foldSubagentDescriptor(ownEvents)
1370
+ if (descriptor === undefined || descriptor.mode !== 'continuable') {
1492
1371
  throw new SubagentError(
1493
1372
  `subagent "${childId}" has no supported continuation state and cannot be resumed; choose a different target`,
1494
- "NOT_RESUMABLE",
1495
- );
1373
+ 'NOT_RESUMABLE',
1374
+ )
1496
1375
  }
1497
- let activation: Activation;
1376
+ let activation: Activation
1498
1377
  try {
1499
- const independentlySelected = hasIndependentPresetSelection(ownEvents);
1500
- const agentPreset = independentlySelected
1501
- ? source.projections?.values.agentPreset
1502
- : undefined;
1503
- if (independentlySelected && typeof agentPreset !== "string") {
1504
- throw new SubagentError(
1505
- `subagent "${childId}" has no agent preset projection for restoration`,
1506
- "NOT_RESUMABLE",
1507
- );
1378
+ const independentlySelected = hasIndependentPresetSelection(ownEvents)
1379
+ const agentPreset = independentlySelected ? source.projections?.values.agentPreset : undefined
1380
+ if (independentlySelected && typeof agentPreset !== 'string') {
1381
+ throw new SubagentError(`subagent "${childId}" has no agent preset projection for restoration`, 'NOT_RESUMABLE')
1508
1382
  }
1509
1383
  activation = await this.materialize({
1510
- kind: "local",
1384
+ kind: 'local',
1511
1385
  childId,
1512
1386
  provider: descriptor.provider,
1513
1387
  parent,
1514
1388
  agentOptions: {
1515
- ...(descriptor.agentProvider !== undefined ? { provider: descriptor.agentProvider } : {}),
1516
- ...(descriptor.agentModel !== undefined ? { model: descriptor.agentModel } : {}),
1517
- ...(descriptor.agentReasoningEffort !== undefined
1389
+ ...descriptor.agentProvider !== undefined ? { provider: descriptor.agentProvider } : {},
1390
+ ...descriptor.agentModel !== undefined ? { model: descriptor.agentModel } : {},
1391
+ ...descriptor.agentReasoningEffort !== undefined
1518
1392
  ? { reasoningEffort: ReasoningEffortId(descriptor.agentReasoningEffort) }
1519
- : {}),
1520
- },
1521
- composition: {
1522
- agentPreset: agentPreset ?? undefined,
1523
- persona: descriptor.persona,
1524
- toolFilter: descriptor.toolFilter,
1393
+ : {},
1525
1394
  },
1395
+ composition: { agentPreset: agentPreset ?? undefined, persona: descriptor.persona, toolFilter: descriptor.toolFilter },
1526
1396
  signal: options.signal,
1527
- });
1397
+ })
1528
1398
  } catch (error: unknown) {
1529
- options.signal.throwIfAborted();
1530
- if (error instanceof SubagentError) throw error;
1531
- throw new SubagentError(`subagent "${childId}" is unavailable`, "NOT_RESUMABLE", {
1532
- cause: error,
1533
- });
1399
+ options.signal.throwIfAborted()
1400
+ if (error instanceof SubagentError) throw error
1401
+ throw new SubagentError(`subagent "${childId}" is unavailable`, 'NOT_RESUMABLE', { cause: error })
1534
1402
  }
1535
- return await this.submitMaterialized(activation, content, options, parent);
1403
+ return await this.submitMaterialized(activation, content, options, parent)
1536
1404
  }
1537
1405
 
1538
1406
  /** Admit a materialized child, commit its creation fact, and release it on failure. */
@@ -1545,30 +1413,24 @@ export class SubagentManager {
1545
1413
  ): Promise<MessageId> {
1546
1414
  try {
1547
1415
  if (contentHasImage(content)) {
1548
- await this.assertImageCapable(
1549
- requireLocalActivation(activation).handle.agent,
1550
- options.signal,
1551
- );
1416
+ await this.assertImageCapable(requireLocalActivation(activation).handle.agent, options.signal)
1552
1417
  if (activation.closing !== undefined) {
1553
- throw new SubagentError(
1554
- `subagent "${activation.childId}" is closing`,
1555
- "ACTIVATION_CLOSING",
1556
- );
1418
+ throw new SubagentError(`subagent "${activation.childId}" is closing`, 'ACTIVATION_CLOSING')
1557
1419
  }
1558
1420
  }
1559
- const messageId = this.submitAdmitted(activation, content, options, parent);
1560
- commit?.();
1561
- this.announce(activation);
1562
- return messageId;
1421
+ const messageId = this.submitAdmitted(activation, content, options, parent)
1422
+ commit?.()
1423
+ this.announce(activation)
1424
+ return messageId
1563
1425
  } catch (error: unknown) {
1564
1426
  try {
1565
- await this.dispose(activation);
1427
+ await this.dispose(activation)
1566
1428
  } catch (cleanupError: unknown) {
1567
1429
  this.ctx.logger.warn(
1568
1430
  `subagent continuation: disposal after admission or catalog append failure also failed: ${String(cleanupError)}`,
1569
- );
1431
+ )
1570
1432
  }
1571
- throw error;
1433
+ throw error
1572
1434
  }
1573
1435
  }
1574
1436
 
@@ -1579,71 +1441,73 @@ export class SubagentManager {
1579
1441
  options: ChildDeliveryOptions,
1580
1442
  parent: Agent,
1581
1443
  ): MessageId {
1582
- const message =
1583
- options.source === undefined
1584
- ? createAgentMessage(parent, content)
1585
- : createUserMessage({ content, source: options.source });
1586
- options.signal.throwIfAborted();
1587
- this.assertAdmitting(parent);
1588
- this.authorizeLineage(parent, activation.childId, activation.parent.id);
1589
- this.acquireOwnership(parent, activation.childId);
1444
+ const message = options.source === undefined
1445
+ ? createAgentMessage(parent, content)
1446
+ : createUserMessage({ content, source: options.source })
1447
+ options.signal.throwIfAborted()
1448
+ this.assertAdmitting(parent)
1449
+ this.authorizeLineage(parent, activation.childId, activation.parent.id)
1450
+ this.acquireOwnership(parent, activation.childId)
1590
1451
  try {
1591
- this.deliver(activation, message, options.delivery);
1452
+ this.deliver(activation, message, options.delivery)
1592
1453
  } finally {
1593
- this.wake(activation);
1454
+ this.wake(activation)
1594
1455
  }
1595
- return message.id;
1456
+ return message.id
1596
1457
  }
1597
1458
 
1598
1459
  /** Refuse image content for a child whose fixed model accepts text only. */
1599
- private async assertImageCapable(agent: Agent, signal: AbortSignal): Promise<void> {
1600
- const { provider, model } = agent.options;
1601
- if (provider === undefined || model === undefined) return;
1602
- const llm = this.ctx.get("llm");
1460
+ private async assertImageCapable(
1461
+ agent: Agent,
1462
+ signal: AbortSignal,
1463
+ ): Promise<void> {
1464
+ const { provider, model } = agent.options
1465
+ if (provider === undefined || model === undefined) return
1466
+ const llm = this.ctx.get('llm')
1603
1467
  /* v8 ignore next -- without an LLM registry, delivery defers to projection. */
1604
- if (llm === undefined) return;
1605
- const info = await llm.resolveModelInfo(provider, model, signal);
1606
- if (info.inputModalities !== undefined && !info.inputModalities.includes("image")) {
1468
+ if (llm === undefined) return
1469
+ const info = await llm.resolveModelInfo(provider, model, signal)
1470
+ if (info.inputModalities !== undefined && !info.inputModalities.includes('image')) {
1607
1471
  throw new SubagentError(
1608
1472
  `Model "${model}" does not support image input.`,
1609
- "MODEL_DOES_NOT_SUPPORT_IMAGES",
1610
- );
1473
+ 'MODEL_DOES_NOT_SUPPORT_IMAGES',
1474
+ )
1611
1475
  }
1612
1476
  }
1613
1477
 
1614
1478
  /** Resolve the persistence service continuable children require, or fail loud. */
1615
1479
  private requirePersistence(): SessionPersistence {
1616
- const persistence = this.ctx.get("sessionPersistence");
1480
+ const persistence = this.ctx.get('sessionPersistence')
1617
1481
  if (persistence === undefined) {
1618
1482
  throw new SubagentError(
1619
- "continuable subagents require session persistence (load a dsh-session-persistence backend)",
1620
- "PERSISTENCE_UNAVAILABLE",
1621
- );
1483
+ 'continuable subagents require session persistence (load a dsh-session-persistence backend)',
1484
+ 'PERSISTENCE_UNAVAILABLE',
1485
+ )
1622
1486
  }
1623
- return persistence;
1487
+ return persistence
1624
1488
  }
1625
1489
 
1626
1490
  /** Resolve the Session query service used for cold child observations. */
1627
1491
  private requireSessionQuery(): SessionQueryEngine {
1628
- const query = this.ctx.get("sessionQuery");
1492
+ const query = this.ctx.get('sessionQuery')
1629
1493
  if (query === undefined) {
1630
1494
  throw new SubagentError(
1631
- "continuable subagents require session query (load @deepseek-ai/dsh-session-query)",
1632
- "CONTINUATION_UNAVAILABLE",
1633
- );
1495
+ 'continuable subagents require session query (load @deepseek-ai/dsh-session-query)',
1496
+ 'CONTINUATION_UNAVAILABLE',
1497
+ )
1634
1498
  }
1635
- return query;
1499
+ return query
1636
1500
  }
1637
1501
  }
1638
1502
 
1639
- export default SubagentManager;
1503
+ export default SubagentManager
1640
1504
 
1641
1505
  /** Creation-time selection precedes the own descriptor; historical forks can select after it. */
1642
1506
  function hasIndependentPresetSelection(events: readonly SessionEvent[]): boolean {
1643
1507
  for (const event of events) {
1644
- if (event.type === "subagent/descriptor") return false;
1645
- if (event.type === "agent-preset/selected") return true;
1508
+ if (event.type === 'subagent/descriptor') return false
1509
+ if (event.type === 'agent-preset/selected') return true
1646
1510
  }
1647
1511
  /* v8 ignore next -- coldResume rejects logs without a supported own descriptor before this lookup. */
1648
- return false;
1512
+ return false
1649
1513
  }