@morlay/dsh-subagent 0.0.8 → 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/dist/index.d.mts +9 -9
- package/package.json +1 -1
- package/src/continuation-messages.ts +68 -59
- package/src/index.ts +262 -189
- package/src/manager.ts +886 -716
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
|
|
10
|
-
import { isAbsolute, resolve } from
|
|
11
|
-
import type {} from
|
|
12
|
-
import type { Context } from
|
|
13
|
-
import type { Agent, AgentOptions, CreateAgentOptions } from
|
|
14
|
-
import { brandString } from
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
|
30
|
-
import { assertSubagentMaxDepth } from
|
|
31
|
-
import {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
import {
|
|
36
|
-
import
|
|
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,
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
| { readonly delivery:
|
|
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(
|
|
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?:
|
|
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
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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 =
|
|
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 :
|
|
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(
|
|
173
|
-
this.closingScopes.delete(agent)
|
|
174
|
-
})
|
|
175
|
-
ctx.effect(
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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
|
-
|
|
188
|
-
|
|
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
|
-
|
|
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 ===
|
|
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
|
-
|
|
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
|
-
|
|
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 ===
|
|
224
|
-
if (activation.kind ===
|
|
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(
|
|
247
|
-
|
|
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(
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
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 ===
|
|
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 !==
|
|
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)))
|
|
276
|
-
|
|
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 (
|
|
280
|
-
|
|
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 (
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
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
|
-
|
|
313
|
-
|
|
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,
|
|
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(
|
|
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
|
-
|
|
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,
|
|
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(
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
const
|
|
387
|
-
|
|
388
|
-
const
|
|
389
|
-
|
|
390
|
-
|
|
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:
|
|
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`,
|
|
504
|
+
throw new SubagentError(`subagent "${childId}" already exists`, "DUPLICATE_CHILD");
|
|
438
505
|
}
|
|
439
506
|
}
|
|
440
507
|
const activation = await this.materialize({
|
|
441
|
-
kind:
|
|
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(
|
|
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 !==
|
|
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:
|
|
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(
|
|
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:
|
|
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 =>
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
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(
|
|
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(
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
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
|
-
|
|
573
|
-
|
|
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 (
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
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
|
-
|
|
589
|
-
)
|
|
676
|
+
"UNAUTHORIZED",
|
|
677
|
+
);
|
|
590
678
|
}
|
|
591
679
|
return this.deliverToChild(sender, targetId, content, {
|
|
592
680
|
signal: options.signal,
|
|
593
|
-
delivery:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
640
|
-
|
|
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(
|
|
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 ===
|
|
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 (
|
|
657
|
-
|
|
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)
|
|
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
|
-
|
|
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 ===
|
|
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 ===
|
|
722
|
-
?
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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]) },
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
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:
|
|
912
|
+
failureSubject: "activation(s)" | "scoped activation(s)" | "selected activation(s)",
|
|
808
913
|
): Promise<void> {
|
|
809
|
-
const failures = await Promise.all(
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
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
|
-
|
|
822
|
-
|
|
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 |
|
|
853
|
-
if (this.draining) return
|
|
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 ===
|
|
883
|
-
const controller = new AbortController()
|
|
884
|
-
const cancelStartup = (): void => {
|
|
885
|
-
|
|
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:
|
|
889
|
-
void run.result.catch(() => undefined)
|
|
890
|
-
childId = run.id
|
|
891
|
-
try {
|
|
892
|
-
|
|
893
|
-
|
|
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(
|
|
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(
|
|
1020
|
+
child.session.append("agent-preset/selected", {
|
|
1021
|
+
agentPreset: inputs.composition.agentPreset,
|
|
1022
|
+
});
|
|
910
1023
|
}
|
|
911
|
-
child.session.append(
|
|
912
|
-
appendDelegatedPolicyOverrides(child.session, create.delegatedPolicies)
|
|
913
|
-
const workingDirectory = childCtx.get(
|
|
914
|
-
if (workingDirectory === undefined)
|
|
915
|
-
|
|
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)
|
|
1031
|
+
if (inputs.composition.agentPreset === undefined)
|
|
1032
|
+
await applyChildComposition(childCtx, parent, inputs.composition);
|
|
918
1033
|
if (inputs.outputSchema !== undefined) {
|
|
919
|
-
structured = attachStructuredRuntime(
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
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 =
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
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 ??
|
|
1081
|
+
delivery: inputs.delivery ?? "parent",
|
|
956
1082
|
result: Promise.withResolvers<SubagentResult>(),
|
|
957
1083
|
closing: undefined,
|
|
958
|
-
ancestry: new WeakSet(
|
|
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)
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
activation
|
|
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 ===
|
|
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> =>
|
|
998
|
-
|
|
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 ===
|
|
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
|
-
|
|
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 ===
|
|
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, () =>
|
|
1052
|
-
this.settlementState(activation, idleObservation),
|
|
1053
|
-
)
|
|
1054
|
-
if (readiness ===
|
|
1055
|
-
if (readiness ===
|
|
1056
|
-
if (readiness ===
|
|
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 !==
|
|
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(
|
|
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(
|
|
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 ===
|
|
1084
|
-
if (attempt ===
|
|
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(
|
|
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
|
|
1108
|
-
if (activation.poke !== observation) return
|
|
1109
|
-
const inbox = activation.handle.agent.inbox
|
|
1110
|
-
if (activation.ownedChildren.size > 0) return
|
|
1111
|
-
if (
|
|
1112
|
-
|
|
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:
|
|
1121
|
-
let resultFailure: { error: unknown } | undefined
|
|
1122
|
-
let externalDisposal: Promise<void> | undefined
|
|
1123
|
-
const child = activationAgent(activation)
|
|
1124
|
-
const detail = (error: unknown): string =>
|
|
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 ===
|
|
1269
|
+
if (activation.kind === "external") {
|
|
1129
1270
|
if (stop) {
|
|
1130
|
-
activation.controller.abort({ kind:
|
|
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:
|
|
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 =>
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
childDisposals.
|
|
1147
|
-
const
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
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(
|
|
1158
|
-
|
|
1159
|
-
|
|
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(
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
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 ===
|
|
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(
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
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
|
-
|
|
1193
|
-
|
|
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 =
|
|
1200
|
-
|
|
1201
|
-
this.
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
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 ===
|
|
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,
|
|
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 ===
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
1438
|
+
return disposal.then(
|
|
1439
|
+
() => undefined,
|
|
1440
|
+
() => undefined,
|
|
1441
|
+
);
|
|
1285
1442
|
}
|
|
1286
1443
|
if (contentHasImage(content)) {
|
|
1287
|
-
await this.assertImageCapable(
|
|
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
|
-
|
|
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
|
-
|
|
1323
|
-
|
|
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,
|
|
1490
|
+
this.sendWaking(parent, message, "steer");
|
|
1338
1491
|
} catch (error: unknown) {
|
|
1339
1492
|
throw new SubagentError(
|
|
1340
|
-
|
|
1341
|
-
|
|
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`,
|
|
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 !==
|
|
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
|
-
|
|
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
|
|
1382
|
-
|
|
1383
|
-
|
|
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:
|
|
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`,
|
|
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(
|
|
1584
|
+
await this.assertImageCapable(
|
|
1585
|
+
requireLocalActivation(activation).handle.agent,
|
|
1586
|
+
options.signal,
|
|
1587
|
+
);
|
|
1419
1588
|
if (activation.closing !== undefined) {
|
|
1420
|
-
throw new SubagentError(
|
|
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 =
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
this.
|
|
1452
|
-
this.
|
|
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
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
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(
|
|
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
|
-
|
|
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(
|
|
1652
|
+
const persistence = this.ctx.get("sessionPersistence");
|
|
1483
1653
|
if (persistence === undefined) {
|
|
1484
1654
|
throw new SubagentError(
|
|
1485
|
-
|
|
1486
|
-
|
|
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(
|
|
1664
|
+
const query = this.ctx.get("sessionQuery");
|
|
1495
1665
|
if (query === undefined) {
|
|
1496
1666
|
throw new SubagentError(
|
|
1497
|
-
|
|
1498
|
-
|
|
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 ===
|
|
1511
|
-
if (event.type ===
|
|
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
|
}
|