@morlay/dsh-subagent 0.0.9 → 0.0.10

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