@fishwithcat/dsh-subagent 0.2.2-alpha.2 → 0.2.2-alpha.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.i18n.yaml +39 -24
- package/README.md +41 -20
- package/README.zh.md +41 -20
- package/lib/index.js +573 -872
- package/lib/typert.host.js +64 -120
- package/lib/typert.remote-client.d.ts +3 -3
- package/lib/typert.remote-client.js +17 -63
- package/lib/types/activation.d.ts +15 -25
- package/lib/types/activation.js +1 -1
- package/lib/types/archive-admission.d.ts +11 -4
- package/lib/types/archive-admission.js +39 -115
- package/lib/types/child-agent.d.ts +7 -17
- package/lib/types/child-agent.js +6 -4
- package/lib/types/continuation-messages.d.ts +8 -7
- package/lib/types/continuation-messages.js +12 -14
- package/lib/types/control-types.d.ts +7 -28
- package/lib/types/control.d.ts +0 -4
- package/lib/types/control.js +1 -5
- package/lib/types/descriptor.d.ts +1 -1
- package/lib/types/descriptor.js +1 -1
- package/lib/types/index.d.ts +46 -95
- package/lib/types/index.js +83 -146
- package/lib/types/lifecycle.d.ts +9 -38
- package/lib/types/lifecycle.js +5 -31
- package/lib/types/manager.d.ts +74 -109
- package/lib/types/manager.js +394 -568
- package/lib/types/projection-types.d.ts +14 -6
- package/lib/types/projection.d.ts +2 -2
- package/lib/types/structured.d.ts +1 -1
- package/lib/types/structured.js +2 -2
- package/lib/types/types.d.ts +16 -28
- package/lib/types/types.js +2 -4
- package/package.json +53 -57
- package/lib/types/internal.d.ts +0 -58
- package/lib/types/internal.js +0 -58
package/lib/types/manager.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Owns subagent startup, message admission, cold resume, and execution lifetimes.
|
|
3
3
|
* One child lock orders input acceptance and closure; one activation map owns
|
|
4
|
-
* capacity,
|
|
4
|
+
* capacity, runtime ownership, and resource release across all providers.
|
|
5
5
|
*
|
|
6
6
|
* @module @deepseek-ai/dsh-subagent/manager
|
|
7
7
|
*/
|
|
@@ -63,12 +63,12 @@ import { brandString } from '@deepseek-ai/dsh-brand';
|
|
|
63
63
|
import { ReasoningEffortId, contentHasImage, createUserMessage, errorChain } from '@deepseek-ai/dsh-llm';
|
|
64
64
|
import { SessionLogOffset } from '@deepseek-ai/dsh-session';
|
|
65
65
|
import { appendDelegatedPolicyOverrides, applyChildComposition, captureDelegatedPolicyOverrides, childSessionMeta, resolveChildAgentOptions, resolveChildDepth, } from "./child-agent.js";
|
|
66
|
-
import { createAgentMessage,
|
|
67
|
-
import {
|
|
66
|
+
import { createAgentMessage, createSettlementMessage, withResultDeliveryGuidance, } from "./continuation-messages.js";
|
|
67
|
+
import { installSubagentArchiveAdmission } from "./archive-admission.js";
|
|
68
68
|
import { foldSubagentDescriptor, snapshotSubagentDescriptor } from "./descriptor.js";
|
|
69
69
|
import { establishCatalogChild, establishExternalCatalogChild } from "./catalog.js";
|
|
70
70
|
import { SubagentError } from "./error.js";
|
|
71
|
-
import {
|
|
71
|
+
import { SubagentRunId } from "./types.js";
|
|
72
72
|
import { attachStructuredRuntime } from "./structured.js";
|
|
73
73
|
import { ActivationPool, ChildLock, activationAgent, captureLocalResult, observeLocalFailure, requireLocalActivation } from "./activation.js";
|
|
74
74
|
/** Render only the safe outer message from execution or teardown failures. */
|
|
@@ -91,18 +91,13 @@ export class SubagentManager {
|
|
|
91
91
|
locks = new ChildLock();
|
|
92
92
|
/** Structural Cordis owner of every Activation handle. */
|
|
93
93
|
ownerCtx;
|
|
94
|
-
/**
|
|
95
|
-
|
|
96
|
-
* observed under each root. Entries remain until that exact root leaves the
|
|
97
|
-
* Agent registry, closing admission throughout its host's teardown without
|
|
98
|
-
* poisoning a later same-id replacement.
|
|
99
|
-
*/
|
|
100
|
-
closingScopes = new Map();
|
|
94
|
+
/** Exact subagent tree roots whose host teardown has closed admission. */
|
|
95
|
+
closingRoots = new Set();
|
|
101
96
|
draining = false;
|
|
102
97
|
/**
|
|
103
98
|
* Build one manager inside the service's Agent-injected context.
|
|
104
99
|
* @param ctx - context providing Agents, Sessions, and teardown ownership.
|
|
105
|
-
* @param host -
|
|
100
|
+
* @param host - descriptor reads and lifecycle publication owned by the service.
|
|
106
101
|
* @param maxActiveSubagents - the current configured child capacity.
|
|
107
102
|
* @param delegatedApproval - the configured approval policy seed for each local child.
|
|
108
103
|
*/
|
|
@@ -111,16 +106,15 @@ export class SubagentManager {
|
|
|
111
106
|
this.host = host;
|
|
112
107
|
this.maxActiveSubagents = maxActiveSubagents;
|
|
113
108
|
this.delegatedApproval = delegatedApproval;
|
|
114
|
-
//
|
|
115
|
-
// cannot express the dynamic child graph. Register the private scope's
|
|
116
|
-
// structural disposer FIRST and the drain SECOND, so reverse unwind invokes
|
|
117
|
-
// the drain before releasing the scope; a cleanup effect on the same scope
|
|
118
|
-
// as the Agent handles would let structural handle disposal bypass
|
|
119
|
-
// child-first ordering.
|
|
109
|
+
// Drain managed instances before Cordis releases their owning scope.
|
|
120
110
|
const scope = ctx.plugin(function activationOwner() { });
|
|
121
111
|
this.ownerCtx = scope.ctx;
|
|
112
|
+
installSubagentArchiveAdmission(ctx, (agent) => {
|
|
113
|
+
const activation = this.resident.get(agent.id);
|
|
114
|
+
return activation !== undefined && activationAgent(activation) === agent ? activation.ancestors : undefined;
|
|
115
|
+
}, session => host.identityOf(session));
|
|
122
116
|
ctx.on('agent/disposed', ({ agent }) => {
|
|
123
|
-
this.
|
|
117
|
+
this.closingRoots.delete(agent);
|
|
124
118
|
});
|
|
125
119
|
ctx.effect(function* () {
|
|
126
120
|
yield scope.dispose;
|
|
@@ -130,7 +124,7 @@ export class SubagentManager {
|
|
|
130
124
|
/**
|
|
131
125
|
* Interrupt one live continuable child's current turn under the supplied authority.
|
|
132
126
|
* @param targetSessionId - the durable child session id to interrupt.
|
|
133
|
-
* @param authority -
|
|
127
|
+
* @param authority - a trusted user operation or exact live ancestor Agent.
|
|
134
128
|
*/
|
|
135
129
|
interrupt(targetSessionId, authority) {
|
|
136
130
|
if (authority.kind === 'ancestor') {
|
|
@@ -145,12 +139,8 @@ export class SubagentManager {
|
|
|
145
139
|
const activation = this.resident.get(targetSessionId);
|
|
146
140
|
if (activation === undefined)
|
|
147
141
|
return;
|
|
148
|
-
if (authority.kind === '
|
|
149
|
-
|
|
150
|
-
throw new SubagentError(`subagent "${targetSessionId}" belongs to another parent session`, 'UNAUTHORIZED');
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
else if (!activation.ancestry.has(authority.agent)) {
|
|
142
|
+
if (authority.kind === 'ancestor'
|
|
143
|
+
&& (this.rootOf(authority.agent) !== activation.root || !activation.ancestors.includes(authority.agent.id))) {
|
|
154
144
|
throw new SubagentError(`subagent "${targetSessionId}" is not a live descendant of agent "${authority.agent.id}"`, 'UNAUTHORIZED');
|
|
155
145
|
}
|
|
156
146
|
// Disposal already stopped the target with a whole-Activation teardown;
|
|
@@ -169,32 +159,27 @@ export class SubagentManager {
|
|
|
169
159
|
}
|
|
170
160
|
/**
|
|
171
161
|
* Close admission, await every already-admitted materialization through
|
|
172
|
-
* publication or rollback, then dispose
|
|
173
|
-
* child-first.
|
|
162
|
+
* publication or rollback, then dispose every agent in the subagent tree.
|
|
174
163
|
*/
|
|
175
164
|
async drain() {
|
|
176
165
|
this.draining = true;
|
|
177
166
|
for (const pending of this.materializations)
|
|
178
167
|
pending.controller.abort();
|
|
179
168
|
await Promise.all([...this.materializations].map(materialization => materialization.settled));
|
|
180
|
-
|
|
181
|
-
for (const activation of this.resident.values()) {
|
|
182
|
-
for (const child of activation.ownedChildren)
|
|
183
|
-
owned.add(child);
|
|
184
|
-
}
|
|
185
|
-
const roots = [...this.resident.values()].filter(activation => !owned.has(activation.childId));
|
|
186
|
-
await this.disposeRoots(roots, 'activation(s)');
|
|
169
|
+
await this.disposeRoots([...this.resident.values()], 'activation(s)');
|
|
187
170
|
}
|
|
188
171
|
/**
|
|
189
|
-
* Join progressing
|
|
190
|
-
* @param
|
|
191
|
-
* @returns whether work was joined and the host must recheck
|
|
172
|
+
* Join progressing subagent tree work for its root, leaving idle parked inboxes resident.
|
|
173
|
+
* @param root - exact live subagent tree root; ordinary members return false.
|
|
174
|
+
* @returns whether work was joined and the host must recheck root completion.
|
|
192
175
|
*/
|
|
193
|
-
async
|
|
176
|
+
async waitForTreeWork(root) {
|
|
177
|
+
if (this.rootOf(root) !== root)
|
|
178
|
+
return false;
|
|
194
179
|
let observed = false;
|
|
195
180
|
while (true) {
|
|
196
|
-
const pending = [...this.materializations].filter(item => item.
|
|
197
|
-
const children = [...this.resident.values()].filter(item =>
|
|
181
|
+
const pending = [...this.materializations].filter(item => item.root === root);
|
|
182
|
+
const children = [...this.resident.values()].filter(item => item.root === root);
|
|
198
183
|
observed ||= children.some(item => item.kind === 'local' && item.handle.agent.status !== 'idle');
|
|
199
184
|
await Promise.all(children.flatMap(item => item.kind === 'local' ? [item.handle.agent.whenIdle()] : []));
|
|
200
185
|
const waits = pending.map(item => item.settled);
|
|
@@ -207,13 +192,6 @@ export class SubagentManager {
|
|
|
207
192
|
waits.push(item.handle.agent.whenIdle());
|
|
208
193
|
continue;
|
|
209
194
|
}
|
|
210
|
-
if (item.ownedChildren.size > 0) {
|
|
211
|
-
// Published descendants are observed separately; preparation holds
|
|
212
|
-
// keep the owner pending until publication or rollback wakes it.
|
|
213
|
-
if ([...item.ownedChildren].some(id => !this.resident.has(id)))
|
|
214
|
-
waits.push(item.poke.promise);
|
|
215
|
-
continue;
|
|
216
|
-
}
|
|
217
195
|
const inbox = item.handle.agent.inbox;
|
|
218
196
|
if (item.failureAt === undefined && (inbox.nextTurn.length > 0 || inbox.nextStep.length > 0))
|
|
219
197
|
continue;
|
|
@@ -225,147 +203,82 @@ export class SubagentManager {
|
|
|
225
203
|
continue;
|
|
226
204
|
}
|
|
227
205
|
// A descendant can be published while whenIdle follows another child.
|
|
228
|
-
if ([...this.materializations].some(item => item.
|
|
229
|
-
|| [...this.resident.values()].some(item =>
|
|
230
|
-
&& item.ancestry.has(parent) && !children.includes(item)))
|
|
206
|
+
if ([...this.materializations].some(item => item.root === root && !pending.includes(item))
|
|
207
|
+
|| [...this.resident.values()].some(item => item.root === root && !children.includes(item)))
|
|
231
208
|
continue;
|
|
232
209
|
return observed;
|
|
233
210
|
}
|
|
234
211
|
}
|
|
235
212
|
/**
|
|
236
|
-
* Stop the
|
|
237
|
-
* @param parents - exact live roots whose
|
|
213
|
+
* Stop every member of the selected subagent trees; ordinary members do not close a subagent tree.
|
|
214
|
+
* @param parents - exact live subagent tree roots whose instances must stop.
|
|
238
215
|
*/
|
|
239
216
|
async drainDescendants(parents) {
|
|
240
|
-
const roots = new Set(parents.filter(
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
const targets = [];
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
&& activation.ancestry.has(root));
|
|
252
|
-
if (owners.length === 0)
|
|
253
|
-
continue;
|
|
254
|
-
targets.push(activation);
|
|
255
|
-
for (const owner of owners) {
|
|
256
|
-
const members = this.closingMembers(owner);
|
|
257
|
-
if (child !== undefined)
|
|
258
|
-
members.add(child);
|
|
259
|
-
for (const agent of lineage)
|
|
260
|
-
members.add(agent);
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
const materializations = [...this.materializations].filter((materialization) => {
|
|
264
|
-
const owners = [...roots].filter(root => materialization.lineage.includes(root));
|
|
265
|
-
for (const owner of owners) {
|
|
266
|
-
const members = this.closingMembers(owner);
|
|
267
|
-
for (const agent of materialization.lineage)
|
|
268
|
-
members.add(agent);
|
|
269
|
-
}
|
|
270
|
-
if (owners.length > 0)
|
|
271
|
-
materialization.controller.abort();
|
|
272
|
-
return owners.length > 0;
|
|
273
|
-
});
|
|
274
|
-
const ownedTargets = new Set();
|
|
275
|
-
for (const activation of targets) {
|
|
276
|
-
for (const child of activation.ownedChildren)
|
|
277
|
-
ownedTargets.add(child);
|
|
278
|
-
}
|
|
279
|
-
const targetRoots = targets.filter(activation => !ownedTargets.has(activation.childId));
|
|
280
|
-
for (const activation of targets) {
|
|
281
|
-
const disposal = this.dispose(activation);
|
|
282
|
-
void disposal.catch(() => undefined);
|
|
283
|
-
}
|
|
284
|
-
await Promise.all(materializations.map(materialization => materialization.settled));
|
|
285
|
-
await this.disposeRoots(targetRoots, 'scoped activation(s)');
|
|
286
|
-
}
|
|
287
|
-
/**
|
|
288
|
-
* Release selected resident direct children of one exact live parent.
|
|
289
|
-
* @param parent - exact live direct parent authorizing the selected release.
|
|
290
|
-
* @param childIds - durable direct-child ids to release when resident.
|
|
291
|
-
*/
|
|
292
|
-
async drainChildren(parent, childIds) {
|
|
293
|
-
if (this.ctx.agents.get(parent.id) !== parent) {
|
|
294
|
-
throw new SubagentError('selected child teardown requires the exact live parent agent', 'UNAUTHORIZED');
|
|
295
|
-
}
|
|
296
|
-
const targets = [];
|
|
297
|
-
for (const childId of new Set(childIds)) {
|
|
298
|
-
const activation = this.resident.get(childId);
|
|
299
|
-
if (activation === undefined)
|
|
300
|
-
continue;
|
|
301
|
-
if (activation.parent.id !== parent.id || !activation.ancestry.has(parent)) {
|
|
302
|
-
throw new SubagentError(`subagent "${childId}" is not a direct child of agent "${parent.id}"`, 'UNAUTHORIZED');
|
|
303
|
-
}
|
|
304
|
-
targets.push(activation);
|
|
305
|
-
}
|
|
306
|
-
for (const activation of targets) {
|
|
307
|
-
const disposal = this.dispose(activation);
|
|
308
|
-
void disposal.catch(() => undefined);
|
|
309
|
-
}
|
|
310
|
-
await this.disposeRoots(targets, 'selected activation(s)');
|
|
217
|
+
const roots = new Set(parents.filter(root => this.ctx.agents.get(root.id) === root && this.rootOf(root) === root));
|
|
218
|
+
for (const root of roots)
|
|
219
|
+
this.closingRoots.add(root);
|
|
220
|
+
const pending = [...this.materializations].filter(item => roots.has(item.root));
|
|
221
|
+
for (const item of pending)
|
|
222
|
+
item.controller.abort();
|
|
223
|
+
const targets = [...this.resident.values()].filter(item => roots.has(item.root));
|
|
224
|
+
const disposal = this.disposeRoots(targets, 'scoped activation(s)');
|
|
225
|
+
const [disposed] = await Promise.allSettled([disposal, ...pending.map(item => item.settled)]);
|
|
226
|
+
if (disposed.status === 'rejected')
|
|
227
|
+
throw disposed.reason;
|
|
311
228
|
}
|
|
312
229
|
/**
|
|
313
230
|
* Start one continuable background child and resolve at initial inbox acceptance.
|
|
314
231
|
* Every earlier failure disposes any created handle and rolls back Activation
|
|
315
|
-
* and
|
|
232
|
+
* and its capacity slot without returning either id.
|
|
316
233
|
* @param spec - provider, delegation request, and caller cancellation.
|
|
234
|
+
* @param prepare - creation contribution bound to the checked provider.
|
|
317
235
|
* @returns the durable child id and accepted initial prompt message id.
|
|
318
236
|
*/
|
|
319
|
-
async startLocal(spec) {
|
|
320
|
-
const
|
|
321
|
-
const parent = request.parent;
|
|
237
|
+
async startLocal(spec, prepare) {
|
|
238
|
+
const parent = spec.parent;
|
|
322
239
|
this.assertAdmitting(parent);
|
|
323
|
-
const
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
const releaseHold = this.holdOwnership(parent, childId);
|
|
349
|
-
try {
|
|
350
|
-
const cwd = await this.resolveDirectory(request, spec.signal);
|
|
351
|
-
const prepared = await this.host.prepareContinuable(spec.provider, {
|
|
240
|
+
const ancestry = this.ancestryForChild(parent);
|
|
241
|
+
return this.trackStartup(ancestry.root, spec.signal, async (signal) => {
|
|
242
|
+
const persistence = spec.delivery === 'caller' ? this.ctx.get('sessionPersistence') : this.requirePersistence();
|
|
243
|
+
const childId = spec.childId ?? brandString(randomUUID());
|
|
244
|
+
this.assertChildIdAvailable(childId);
|
|
245
|
+
const childDepth = resolveChildDepth(parent, spec.maxDepth);
|
|
246
|
+
// Snapshot before any await: invalid descriptor JSON rejects the call
|
|
247
|
+
// before a child exists, and the detached value is what reaches the log.
|
|
248
|
+
const agentOptions = resolveChildAgentOptions(parent, spec.agentOptions, childDepth);
|
|
249
|
+
const agentProvider = agentOptions.provider;
|
|
250
|
+
const agentModel = agentOptions.model;
|
|
251
|
+
const agentReasoningEffort = agentOptions.reasoningEffort;
|
|
252
|
+
const descriptor = snapshotSubagentDescriptor({
|
|
253
|
+
mode: 'continuable',
|
|
254
|
+
provider: spec.provider,
|
|
255
|
+
label: spec.label,
|
|
256
|
+
...agentProvider !== undefined ? { agentProvider } : {},
|
|
257
|
+
...agentModel !== undefined ? { agentModel } : {},
|
|
258
|
+
...agentReasoningEffort !== undefined ? { agentReasoningEffort } : {},
|
|
259
|
+
});
|
|
260
|
+
// Capture before the first await: a later parent switch belongs to the
|
|
261
|
+
// parent's future, not to this child.
|
|
262
|
+
const delegatedPolicies = captureDelegatedPolicyOverrides(parent, this.delegatedApproval());
|
|
263
|
+
const cwd = await this.resolveDirectory(spec, signal);
|
|
264
|
+
const prepared = await prepare({
|
|
352
265
|
sessionId: childId,
|
|
353
266
|
cwd,
|
|
354
267
|
parent,
|
|
355
|
-
signal
|
|
268
|
+
signal,
|
|
356
269
|
});
|
|
357
|
-
|
|
270
|
+
signal.throwIfAborted();
|
|
358
271
|
this.assertAdmitting(parent);
|
|
359
272
|
const inheritedEventCount = SessionLogOffset(prepared.seed?.length ?? 0);
|
|
360
273
|
const seed = prepared.seed;
|
|
361
274
|
let established;
|
|
362
275
|
const messageId = await this.locks.run(childId, async () => {
|
|
363
|
-
|
|
276
|
+
signal.throwIfAborted();
|
|
364
277
|
this.assertAdmitting(parent);
|
|
365
278
|
this.assertChildIdAvailable(childId);
|
|
366
279
|
if (spec.childId !== undefined) {
|
|
367
|
-
const persisted = await persistence?.stat(childId, { signal:
|
|
368
|
-
|
|
280
|
+
const persisted = await persistence?.stat(childId, { signal: signal });
|
|
281
|
+
signal.throwIfAborted();
|
|
369
282
|
this.assertAdmitting(parent);
|
|
370
283
|
this.assertChildIdAvailable(childId);
|
|
371
284
|
if (persisted !== undefined) {
|
|
@@ -376,84 +289,80 @@ export class SubagentManager {
|
|
|
376
289
|
kind: 'local',
|
|
377
290
|
childId,
|
|
378
291
|
provider: spec.provider,
|
|
379
|
-
|
|
292
|
+
...ancestry,
|
|
380
293
|
create: {
|
|
294
|
+
parent,
|
|
381
295
|
cwd,
|
|
382
296
|
seed,
|
|
383
|
-
meta: childSessionMeta(parent, childDepth, prepared.seed !== undefined,
|
|
297
|
+
meta: childSessionMeta(parent, childDepth, prepared.seed !== undefined, spec.agentPreset),
|
|
384
298
|
inheritedEventCount,
|
|
385
299
|
delegatedPolicies,
|
|
386
300
|
descriptor,
|
|
387
301
|
},
|
|
388
302
|
agentOptions,
|
|
389
|
-
composition: { agentPreset:
|
|
390
|
-
signal
|
|
303
|
+
composition: { agentPreset: spec.agentPreset },
|
|
304
|
+
signal,
|
|
391
305
|
delivery: spec.delivery,
|
|
392
|
-
outputSchema: spec.
|
|
306
|
+
outputSchema: spec.outputSchema,
|
|
393
307
|
});
|
|
394
308
|
established = activation;
|
|
395
309
|
const child = requireLocalActivation(activation).handle.agent;
|
|
396
310
|
const childHeader = child.session.header;
|
|
397
|
-
return await this.submitMaterialized(activation, spec.delivery !== 'caller'
|
|
398
|
-
?
|
|
399
|
-
:
|
|
311
|
+
return await this.submitMaterialized(activation, spec.delivery !== 'caller'
|
|
312
|
+
? withResultDeliveryGuidance(spec.prompt)
|
|
313
|
+
: spec.prompt, { source: { kind: 'user' }, signal, delivery: 'queue', authorize: () => { this.assertAdmitting(parent); } }, ancestry.root, () => {
|
|
314
|
+
this.assertAdmitting(parent);
|
|
400
315
|
establishCatalogChild(parent.session, childHeader, descriptor);
|
|
401
316
|
});
|
|
402
317
|
});
|
|
403
318
|
return { ...this.receipt(established), childId, messageId };
|
|
404
|
-
}
|
|
405
|
-
catch (error) {
|
|
406
|
-
releaseHold();
|
|
407
|
-
throw error;
|
|
408
|
-
}
|
|
319
|
+
});
|
|
409
320
|
}
|
|
410
321
|
/**
|
|
411
322
|
* Start an external backend under activation ownership.
|
|
412
323
|
* @param spec - task, provider, cancellation, and delivery policy.
|
|
324
|
+
* @param start - execution bound to the checked provider.
|
|
413
325
|
* @returns the accepted activation and its result/disposal capabilities.
|
|
414
326
|
*/
|
|
415
|
-
async startExternal(spec) {
|
|
416
|
-
const
|
|
327
|
+
async startExternal(spec, start) {
|
|
328
|
+
const { provider, delivery, childId: _childId, ...request } = spec;
|
|
329
|
+
const parent = request.parent;
|
|
417
330
|
this.assertAdmitting(parent);
|
|
418
|
-
const
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
})
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
if (spec.delivery === 'parent') {
|
|
438
|
-
establishExternalCatalogChild(parent.session, activation.childId, spec.label);
|
|
439
|
-
}
|
|
440
|
-
this.announce(activation);
|
|
441
|
-
return this.receipt(activation);
|
|
442
|
-
}
|
|
443
|
-
catch (error) {
|
|
444
|
-
if (activation !== undefined) {
|
|
445
|
-
try {
|
|
446
|
-
await this.dispose(activation);
|
|
331
|
+
const ancestry = this.ancestryForChild(parent);
|
|
332
|
+
return this.trackStartup(ancestry.root, spec.signal, async (signal) => {
|
|
333
|
+
let activation;
|
|
334
|
+
try {
|
|
335
|
+
const cwd = await this.resolveDirectory(spec, signal);
|
|
336
|
+
activation = await this.materialize({
|
|
337
|
+
kind: 'external',
|
|
338
|
+
provider,
|
|
339
|
+
...ancestry,
|
|
340
|
+
signal,
|
|
341
|
+
delivery,
|
|
342
|
+
start: signal => start({
|
|
343
|
+
...request, cwd, signal,
|
|
344
|
+
}),
|
|
345
|
+
});
|
|
346
|
+
this.assertAdmitting(parent);
|
|
347
|
+
signal.throwIfAborted();
|
|
348
|
+
if (delivery === 'parent') {
|
|
349
|
+
establishExternalCatalogChild(parent.session, activation.childId, spec.label);
|
|
447
350
|
}
|
|
448
|
-
|
|
449
|
-
|
|
351
|
+
this.accept(activation);
|
|
352
|
+
return this.receipt(activation);
|
|
353
|
+
}
|
|
354
|
+
catch (error) {
|
|
355
|
+
if (activation !== undefined) {
|
|
356
|
+
try {
|
|
357
|
+
await this.dispose(activation);
|
|
358
|
+
}
|
|
359
|
+
catch (cleanupError) {
|
|
360
|
+
this.ctx.logger.warn(`subagent "${activation.childId}" admission rollback failed: ${failureMessage(cleanupError)}`);
|
|
361
|
+
}
|
|
450
362
|
}
|
|
363
|
+
throw error;
|
|
451
364
|
}
|
|
452
|
-
|
|
453
|
-
}
|
|
454
|
-
finally {
|
|
455
|
-
releaseHold();
|
|
456
|
-
}
|
|
365
|
+
});
|
|
457
366
|
}
|
|
458
367
|
/** Capture the explicit or inherited directory while the parent still owns startup. */
|
|
459
368
|
async resolveDirectory(request, signal) {
|
|
@@ -464,59 +373,126 @@ export class SubagentManager {
|
|
|
464
373
|
return cwd;
|
|
465
374
|
}
|
|
466
375
|
/**
|
|
467
|
-
*
|
|
468
|
-
*
|
|
469
|
-
*
|
|
470
|
-
* @
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
376
|
+
* Resolve the live subagent tree root from runtime ownership, independent of creator residency.
|
|
377
|
+
* @param caller - exact live Agent selecting the subagent tree.
|
|
378
|
+
* @returns the live subagent tree root.
|
|
379
|
+
* @throws when the caller has no active runtime owner.
|
|
380
|
+
*/
|
|
381
|
+
rootOf(caller) {
|
|
382
|
+
if (this.ctx.agents.get(caller.id) !== caller) {
|
|
383
|
+
throw new SubagentError('subagent tree access requires the exact live caller', 'UNAUTHORIZED');
|
|
384
|
+
}
|
|
385
|
+
const identity = this.host.identityOf(caller.session);
|
|
386
|
+
const delegated = caller.session.header.origin === 'subagent' || identity != null && caller.session.isOwnSeq(identity.seq);
|
|
387
|
+
const activation = this.resident.get(caller.id);
|
|
388
|
+
if (activation !== undefined && activationAgent(activation) === caller
|
|
389
|
+
&& this.ctx.agents.get(activation.root.id) === activation.root)
|
|
390
|
+
return activation.root;
|
|
391
|
+
if (delegated || activation !== undefined) {
|
|
392
|
+
throw new SubagentError('agent has no active runtime owner', 'UNAUTHORIZED');
|
|
393
|
+
}
|
|
394
|
+
return caller;
|
|
395
|
+
}
|
|
396
|
+
/** Capture creation ancestry separately from the root that owns the new instance. */
|
|
397
|
+
ancestryForChild(creator) {
|
|
398
|
+
const root = this.rootOf(creator);
|
|
399
|
+
const ancestors = this.resident.get(creator.id)?.ancestors ?? [];
|
|
400
|
+
return { root, ancestors: [...ancestors, creator.id] };
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* Deliver to another agent in the subagent tree, restoring only the target when absent.
|
|
404
|
+
* @param sender - exact live root or resident continuable Agent.
|
|
405
|
+
* @param targetId - another member's durable Session id.
|
|
406
|
+
* @param content - model-authored message blocks.
|
|
407
|
+
* @param options - cancellation before inbox acceptance.
|
|
408
|
+
* @returns the accepted inbox message id.
|
|
475
409
|
*/
|
|
476
410
|
async sendMessage(sender, targetId, content, options) {
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
411
|
+
options.signal.throwIfAborted();
|
|
412
|
+
const root = this.rootOf(sender);
|
|
413
|
+
if (sender.id === targetId)
|
|
414
|
+
throw new SubagentError('send_message requires another agent in the subagent tree', 'UNAUTHORIZED');
|
|
480
415
|
this.assertAdmitting(sender);
|
|
481
|
-
const
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
}
|
|
492
|
-
return this.deliverToChild(sender, targetId, content, {
|
|
493
|
-
signal: options.signal,
|
|
494
|
-
delivery: 'steer',
|
|
416
|
+
const message = createAgentMessage(sender, content);
|
|
417
|
+
const activation = this.resident.get(sender.id);
|
|
418
|
+
return this.deliverToMember(root, targetId, [...message.content], {
|
|
419
|
+
signal: options.signal, delivery: 'steer', source: message.source,
|
|
420
|
+
authorize: () => { this.assertAdmitting(sender); },
|
|
421
|
+
onAccepted: () => {
|
|
422
|
+
if (activation?.kind === 'local' && activation.ancestors.at(-1) === targetId) {
|
|
423
|
+
activation.omitClosingText = true;
|
|
424
|
+
}
|
|
425
|
+
},
|
|
495
426
|
});
|
|
496
427
|
}
|
|
497
|
-
/**
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
428
|
+
/** Resolve the live owner through verified creation edges without activating ancestors. */
|
|
429
|
+
async resolveCreationAncestry(source, signal, root) {
|
|
430
|
+
const ancestors = [];
|
|
431
|
+
const seen = new Set([source.header.id]);
|
|
432
|
+
let { header, projections, inheritedEventCount } = source;
|
|
433
|
+
while (true) {
|
|
434
|
+
const env_1 = { stack: [], error: void 0, hasError: false };
|
|
435
|
+
try {
|
|
436
|
+
signal.throwIfAborted();
|
|
437
|
+
const parentId = header.parentSession;
|
|
438
|
+
const identity = projections?.values.subagent;
|
|
439
|
+
if (parentId === undefined || identity == null || identity.seq < inheritedEventCount)
|
|
440
|
+
break;
|
|
441
|
+
if (seen.has(parentId))
|
|
442
|
+
throw new SubagentError('cyclic subagent creation ancestry', 'UNAUTHORIZED');
|
|
443
|
+
seen.add(parentId);
|
|
444
|
+
const parent = __addDisposableResource(env_1, await this.observeContinuation(parentId, signal), false);
|
|
445
|
+
if (!parent.projections?.values.subagentCatalog?.some(entry => entry.id === header.id)) {
|
|
446
|
+
throw new SubagentError('subagent is absent from its creator catalog', 'UNAUTHORIZED');
|
|
447
|
+
}
|
|
448
|
+
ancestors.unshift(parentId);
|
|
449
|
+
if (parentId === root?.id)
|
|
450
|
+
return { root, ancestors };
|
|
451
|
+
({ header, projections, inheritedEventCount } = parent);
|
|
452
|
+
}
|
|
453
|
+
catch (e_1) {
|
|
454
|
+
env_1.error = e_1;
|
|
455
|
+
env_1.hasError = true;
|
|
456
|
+
}
|
|
457
|
+
finally {
|
|
458
|
+
__disposeResources(env_1);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
if (root === undefined && ancestors.length > 0 && header.origin !== 'subagent') {
|
|
462
|
+
const owner = this.ctx.agents.get(header.id);
|
|
463
|
+
if (owner === undefined || this.rootOf(owner) !== owner) {
|
|
464
|
+
throw new SubagentError('subagent tree root is not live', 'CONTINUATION_UNAVAILABLE');
|
|
465
|
+
}
|
|
466
|
+
return { root: owner, ancestors };
|
|
467
|
+
}
|
|
468
|
+
throw new SubagentError(`agent "${source.header.id}" is not a member of this subagent tree`, 'UNAUTHORIZED');
|
|
508
469
|
}
|
|
509
470
|
/**
|
|
510
|
-
*
|
|
511
|
-
*
|
|
512
|
-
* @param childId - durable
|
|
471
|
+
* Deliver one host-authored prompt through the target's live subagent tree root.
|
|
472
|
+
* Remote callers must be authorized for the target Session before invoking this operation.
|
|
473
|
+
* @param childId - durable continuable subagent session id.
|
|
513
474
|
* @param content - model-visible prompt blocks.
|
|
514
|
-
* @param
|
|
515
|
-
* @param signal - caller cancellation before inbox acceptance.
|
|
475
|
+
* @param options - inbox destination, durable attribution, and cancellation before acceptance.
|
|
516
476
|
* @returns the accepted durable message id.
|
|
517
477
|
*/
|
|
518
|
-
async
|
|
519
|
-
|
|
478
|
+
async deliverPrompt(childId, content, options) {
|
|
479
|
+
options.signal.throwIfAborted();
|
|
480
|
+
let root = this.resident.get(childId)?.root;
|
|
481
|
+
if (root === undefined) {
|
|
482
|
+
const env_2 = { stack: [], error: void 0, hasError: false };
|
|
483
|
+
try {
|
|
484
|
+
const source = __addDisposableResource(env_2, await this.observeContinuation(childId, options.signal), false);
|
|
485
|
+
({ root } = await this.resolveCreationAncestry(source, options.signal));
|
|
486
|
+
}
|
|
487
|
+
catch (e_2) {
|
|
488
|
+
env_2.error = e_2;
|
|
489
|
+
env_2.hasError = true;
|
|
490
|
+
}
|
|
491
|
+
finally {
|
|
492
|
+
__disposeResources(env_2);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
return this.deliverToMember(root, childId, content, options);
|
|
520
496
|
}
|
|
521
497
|
/** Admit local input synchronously before any teardown can begin. */
|
|
522
498
|
deliver(activation, message, delivery) {
|
|
@@ -541,38 +517,6 @@ export class SubagentManager {
|
|
|
541
517
|
throw new SubagentError(`subagent "${childId}" already exists`, 'DUPLICATE_CHILD');
|
|
542
518
|
}
|
|
543
519
|
}
|
|
544
|
-
/**
|
|
545
|
-
* Pre-register `childId` in a continuation-managed parent's owned set so the
|
|
546
|
-
* parent cannot settle while a caller is still establishing or resuming that
|
|
547
|
-
* child. Returns a releaser for the failure path; it removes only a hold
|
|
548
|
-
* this call added, and leaves ownership in place once a live Activation for
|
|
549
|
-
* the child exists.
|
|
550
|
-
* @param parent - the live direct parent the operation is admitted under.
|
|
551
|
-
* @param childId - the durable child the operation addresses.
|
|
552
|
-
* @returns the failure-path releaser; a no-op when nothing was added.
|
|
553
|
-
*/
|
|
554
|
-
holdOwnership(parent, childId) {
|
|
555
|
-
const parentActivation = this.resident.get(parent.id);
|
|
556
|
-
if (parentActivation === undefined || activationAgent(parentActivation) !== parent)
|
|
557
|
-
return () => { };
|
|
558
|
-
if (parentActivation.closing !== undefined) {
|
|
559
|
-
throw new SubagentError(`subagent parent "${parent.id}" is being disposed; the child was not established`, 'ACTIVATION_CLOSING');
|
|
560
|
-
}
|
|
561
|
-
if (parentActivation.ownedChildren.has(childId))
|
|
562
|
-
return () => { };
|
|
563
|
-
parentActivation.ownedChildren.add(childId);
|
|
564
|
-
return () => {
|
|
565
|
-
const live = this.resident.get(childId);
|
|
566
|
-
/* v8 ignore next 4 -- reaching this arm needs another delivery to establish the child
|
|
567
|
-
* between this operation's failure and its releaser running, which no test can schedule
|
|
568
|
-
* deterministically: the ownership edge then belongs to that live Activation, so the
|
|
569
|
-
* conservative keep leaves it for finishDisposal's releaseOwnership. */
|
|
570
|
-
if (live !== undefined && live.closing === undefined)
|
|
571
|
-
return;
|
|
572
|
-
if (parentActivation.ownedChildren.delete(childId))
|
|
573
|
-
this.wake(parentActivation);
|
|
574
|
-
};
|
|
575
|
-
}
|
|
576
520
|
/**
|
|
577
521
|
* Apply activation admission to a resident parent before delivering input.
|
|
578
522
|
* @param parent - exact live Agent receiving the message.
|
|
@@ -596,34 +540,20 @@ export class SubagentManager {
|
|
|
596
540
|
parent.followup(message);
|
|
597
541
|
}
|
|
598
542
|
/**
|
|
599
|
-
* Reject new
|
|
600
|
-
* @param agent - exact live
|
|
543
|
+
* Reject new work from closing members or subagent trees.
|
|
544
|
+
* @param agent - exact live caller whose instance and subagent tree must admit work.
|
|
601
545
|
*/
|
|
602
546
|
assertAdmitting(agent) {
|
|
603
547
|
const closing = this.closingTeardownFor(agent);
|
|
604
|
-
if (closing === undefined)
|
|
548
|
+
if (closing === undefined) {
|
|
549
|
+
if (this.resident.get(agent.id)?.closing !== undefined) {
|
|
550
|
+
throw new SubagentError(`subagent "${agent.id}" is closing`, 'ACTIVATION_CLOSING');
|
|
551
|
+
}
|
|
605
552
|
return;
|
|
553
|
+
}
|
|
606
554
|
throw new SubagentError(closing === 'manager'
|
|
607
555
|
? 'subagents are draining; the operation was not admitted'
|
|
608
|
-
: `
|
|
609
|
-
}
|
|
610
|
-
/**
|
|
611
|
-
* Authorize one operation against the durable direct-parent lineage.
|
|
612
|
-
* @param parent - exact live Agent claiming direct-parent authority.
|
|
613
|
-
* @param childId - durable child session id addressed by the operation.
|
|
614
|
-
* @param parentSession - durable direct-parent id recorded by the child.
|
|
615
|
-
*/
|
|
616
|
-
authorizeLineage(parent, childId, parentSession) {
|
|
617
|
-
this.assertLiveParent(parent, childId);
|
|
618
|
-
if (parentSession !== parent.id) {
|
|
619
|
-
throw new SubagentError(`subagent "${childId}" belongs to another parent session`, 'UNAUTHORIZED');
|
|
620
|
-
}
|
|
621
|
-
}
|
|
622
|
-
/** Reject parent authority after the live registry entry changes. */
|
|
623
|
-
assertLiveParent(parent, childId) {
|
|
624
|
-
if (this.ctx.agents.get(parent.id) !== parent) {
|
|
625
|
-
throw new SubagentError(`subagent "${childId}" delivery requires the exact live parent agent`, 'UNAUTHORIZED');
|
|
626
|
-
}
|
|
556
|
+
: `subagent tree "${closing.id}" is draining; the operation was not admitted`, 'DRAINING');
|
|
627
557
|
}
|
|
628
558
|
/**
|
|
629
559
|
* Create or resume one child Agent and publish its Activation.
|
|
@@ -631,23 +561,26 @@ export class SubagentManager {
|
|
|
631
561
|
* @returns the published process-local Activation.
|
|
632
562
|
*/
|
|
633
563
|
materialize(inputs) {
|
|
634
|
-
this.assertAdmitting(inputs.
|
|
564
|
+
this.assertAdmitting(inputs.root);
|
|
635
565
|
inputs.signal.throwIfAborted();
|
|
636
|
-
const
|
|
637
|
-
const pool = this.resident.get(inputs.parent.id)?.pool ?? this.rootPool(inputs.parent);
|
|
566
|
+
const pool = this.rootPool(inputs.root);
|
|
638
567
|
const releaseSlot = pool.reserve(this.maxActiveSubagents());
|
|
568
|
+
return this.materializeTracked(inputs, releaseSlot).catch((error) => {
|
|
569
|
+
releaseSlot();
|
|
570
|
+
throw error;
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
/** Keep subagent tree shutdown and host waits joined to preparation, publication, and rollback. */
|
|
574
|
+
trackStartup(root, signal, start) {
|
|
575
|
+
this.assertAdmitting(root);
|
|
576
|
+
signal.throwIfAborted();
|
|
639
577
|
const settled = Promise.withResolvers();
|
|
640
578
|
const controller = new AbortController();
|
|
641
|
-
const materialization = {
|
|
642
|
-
lineage,
|
|
643
|
-
settled: settled.promise,
|
|
644
|
-
controller,
|
|
645
|
-
};
|
|
579
|
+
const materialization = { root, settled: settled.promise, controller };
|
|
646
580
|
this.materializations.add(materialization);
|
|
647
|
-
return
|
|
648
|
-
releaseSlot();
|
|
581
|
+
return start(AbortSignal.any([signal, controller.signal])).catch((error) => {
|
|
649
582
|
if (error instanceof Error && error.name === 'AbortError')
|
|
650
|
-
this.assertAdmitting(
|
|
583
|
+
this.assertAdmitting(root);
|
|
651
584
|
throw error;
|
|
652
585
|
}).finally(() => {
|
|
653
586
|
this.materializations.delete(materialization);
|
|
@@ -688,42 +621,14 @@ export class SubagentManager {
|
|
|
688
621
|
+ reasons.map(reason => failureMessage(reason)).join('; '), 'ACTIVATION_TEARDOWN_FAILED');
|
|
689
622
|
}
|
|
690
623
|
}
|
|
691
|
-
/** Return the
|
|
692
|
-
closingMembers(root) {
|
|
693
|
-
const existing = this.closingScopes.get(root);
|
|
694
|
-
if (existing !== undefined)
|
|
695
|
-
return existing;
|
|
696
|
-
const members = new Set();
|
|
697
|
-
this.closingScopes.set(root, members);
|
|
698
|
-
return members;
|
|
699
|
-
}
|
|
700
|
-
/** Return the exact currently resolvable ancestry from `agent` upward. */
|
|
701
|
-
liveLineage(agent) {
|
|
702
|
-
const lineage = [agent];
|
|
703
|
-
const seen = new Set([agent.id]);
|
|
704
|
-
let parentSession = agent.session.header.parentSession;
|
|
705
|
-
while (parentSession !== undefined) {
|
|
706
|
-
const parent = this.ctx.agents.get(parentSession);
|
|
707
|
-
if (parent === undefined || seen.has(parent.id))
|
|
708
|
-
break;
|
|
709
|
-
lineage.push(parent);
|
|
710
|
-
seen.add(parent.id);
|
|
711
|
-
parentSession = parent.session.header.parentSession;
|
|
712
|
-
}
|
|
713
|
-
return lineage;
|
|
714
|
-
}
|
|
715
|
-
/** Return the teardown that closed continuable admission for this agent's lineage. */
|
|
624
|
+
/** Return the subagent tree teardown that closed admission for this exact Agent. */
|
|
716
625
|
closingTeardownFor(agent) {
|
|
717
626
|
if (this.draining)
|
|
718
627
|
return 'manager';
|
|
719
|
-
const
|
|
720
|
-
|
|
721
|
-
if (members.has(agent) || lineage.includes(root))
|
|
722
|
-
return root;
|
|
723
|
-
}
|
|
724
|
-
return undefined;
|
|
628
|
+
const root = this.rootOf(agent);
|
|
629
|
+
return this.closingRoots.has(root) ? root : undefined;
|
|
725
630
|
}
|
|
726
|
-
/**
|
|
631
|
+
/** Share one capacity pool across every instance in a subagent tree. */
|
|
727
632
|
rootPool(parent) {
|
|
728
633
|
let pool = this.rootPools.get(parent);
|
|
729
634
|
if (pool === undefined) {
|
|
@@ -733,9 +638,8 @@ export class SubagentManager {
|
|
|
733
638
|
return pool;
|
|
734
639
|
}
|
|
735
640
|
/** Perform one tracked materialization through publication or rollback. */
|
|
736
|
-
async materializeTracked(inputs,
|
|
737
|
-
const { provider,
|
|
738
|
-
let childId = inputs.childId;
|
|
641
|
+
async materializeTracked(inputs, releaseSlot) {
|
|
642
|
+
const { provider, root } = inputs;
|
|
739
643
|
inputs.signal.throwIfAborted();
|
|
740
644
|
let resource;
|
|
741
645
|
if (inputs.kind === 'external') {
|
|
@@ -746,9 +650,8 @@ export class SubagentManager {
|
|
|
746
650
|
const run = await inputs.start(controller.signal);
|
|
747
651
|
resource = { kind: 'external', run, controller };
|
|
748
652
|
void run.result.catch(() => undefined);
|
|
749
|
-
childId = run.id;
|
|
750
653
|
try {
|
|
751
|
-
this.assertChildIdAvailable(
|
|
654
|
+
this.assertChildIdAvailable(run.id);
|
|
752
655
|
}
|
|
753
656
|
catch (error) {
|
|
754
657
|
await run.dispose();
|
|
@@ -760,7 +663,7 @@ export class SubagentManager {
|
|
|
760
663
|
}
|
|
761
664
|
}
|
|
762
665
|
else {
|
|
763
|
-
const { create } = inputs;
|
|
666
|
+
const { create, childId } = inputs;
|
|
764
667
|
let structured;
|
|
765
668
|
const setup = async (childCtx, child) => {
|
|
766
669
|
// Only fresh creation appends the descriptor and delegated policy after
|
|
@@ -770,7 +673,7 @@ export class SubagentManager {
|
|
|
770
673
|
const approvalPolicy = create?.delegatedPolicies.approvalPolicy
|
|
771
674
|
?? childCtx.get('approval')?.effectivePolicy(child.session);
|
|
772
675
|
if (inputs.composition.agentPreset !== undefined) {
|
|
773
|
-
await applyChildComposition(childCtx, parent, inputs.composition, approvalPolicy);
|
|
676
|
+
await applyChildComposition(childCtx, create?.parent, inputs.composition, approvalPolicy);
|
|
774
677
|
}
|
|
775
678
|
if (create !== undefined) {
|
|
776
679
|
if (inputs.composition.agentPreset !== undefined) {
|
|
@@ -784,25 +687,22 @@ export class SubagentManager {
|
|
|
784
687
|
await workingDirectory.set(child, create.cwd, inputs.signal);
|
|
785
688
|
}
|
|
786
689
|
if (inputs.composition.agentPreset === undefined) {
|
|
787
|
-
await applyChildComposition(childCtx, parent, inputs.composition, approvalPolicy);
|
|
690
|
+
await applyChildComposition(childCtx, create?.parent, inputs.composition, approvalPolicy);
|
|
788
691
|
}
|
|
789
692
|
if (inputs.outputSchema !== undefined) {
|
|
790
|
-
structured = attachStructuredRuntime(childCtx, inputs.outputSchema, () =>
|
|
791
|
-
&& child.inbox.nextStep.length === 0
|
|
693
|
+
structured = attachStructuredRuntime(childCtx, inputs.outputSchema, () => child.inbox.nextStep.length === 0
|
|
792
694
|
&& child.inbox.nextTurn.length === 0);
|
|
793
695
|
}
|
|
794
696
|
};
|
|
795
697
|
const handle = create === undefined
|
|
796
698
|
? await this.ownerCtx.agents.resume({
|
|
797
699
|
resumeSessionId: childId,
|
|
798
|
-
parentAgent: parent,
|
|
799
700
|
agentOptions: inputs.agentOptions,
|
|
800
701
|
signal: inputs.signal,
|
|
801
702
|
setup,
|
|
802
703
|
})
|
|
803
704
|
: await this.ownerCtx.agents.create({
|
|
804
705
|
sessionId: childId,
|
|
805
|
-
parentAgent: parent,
|
|
806
706
|
meta: create.meta,
|
|
807
707
|
...(create.seed === undefined ? {} : { seed: create.seed }),
|
|
808
708
|
inheritedEventCount: create.inheritedEventCount,
|
|
@@ -820,29 +720,25 @@ export class SubagentManager {
|
|
|
820
720
|
omitClosingText: false,
|
|
821
721
|
};
|
|
822
722
|
}
|
|
823
|
-
const
|
|
723
|
+
const childId = resource.kind === 'local' ? resource.handle.agent.id : resource.run.id;
|
|
824
724
|
const activation = {
|
|
825
|
-
pool,
|
|
826
725
|
releaseSlot,
|
|
827
726
|
childId,
|
|
727
|
+
runId: SubagentRunId(randomUUID()),
|
|
728
|
+
provider,
|
|
828
729
|
...resource,
|
|
829
|
-
|
|
730
|
+
root,
|
|
731
|
+
ancestors: inputs.ancestors,
|
|
830
732
|
delivery: inputs.delivery ?? 'parent',
|
|
831
733
|
result: Promise.withResolvers(),
|
|
832
734
|
closing: undefined,
|
|
833
|
-
|
|
834
|
-
ownedChildren: new Set(),
|
|
835
|
-
observer,
|
|
836
|
-
announced: false,
|
|
735
|
+
accepted: false,
|
|
837
736
|
};
|
|
838
737
|
void activation.result.promise.catch(() => undefined);
|
|
839
738
|
this.resident.set(childId, activation);
|
|
840
739
|
try {
|
|
841
|
-
this.assertAdmitting(
|
|
740
|
+
this.assertAdmitting(root);
|
|
842
741
|
inputs.signal.throwIfAborted();
|
|
843
|
-
if (this.ctx.agents.get(parent.id) !== parent)
|
|
844
|
-
throw new SubagentError('subagent parent is no longer live', 'UNAUTHORIZED');
|
|
845
|
-
this.acquireOwnership(parent, childId);
|
|
846
742
|
const wakeSettlement = () => { this.wake(activation); };
|
|
847
743
|
if (activation.kind === 'local') {
|
|
848
744
|
observeLocalFailure(activation, wakeSettlement);
|
|
@@ -862,7 +758,9 @@ export class SubagentManager {
|
|
|
862
758
|
activation.omitClosingText = false;
|
|
863
759
|
});
|
|
864
760
|
}
|
|
865
|
-
|
|
761
|
+
// Listeners may read the resident Agent or drain it immediately. Publish
|
|
762
|
+
// after residency and inbox listeners, as the last rollback-protected step.
|
|
763
|
+
this.host.emitLifecycle('subagent/start', this.runInfo(activation), root);
|
|
866
764
|
}
|
|
867
765
|
catch (error) {
|
|
868
766
|
/* v8 ignore next -- rollback failure must not mask the admission failure
|
|
@@ -872,14 +770,19 @@ export class SubagentManager {
|
|
|
872
770
|
}
|
|
873
771
|
return activation;
|
|
874
772
|
}
|
|
875
|
-
/**
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
773
|
+
/** Identity shared by one residency epoch's start/end pair, including cold resumes. */
|
|
774
|
+
runInfo(activation) {
|
|
775
|
+
return {
|
|
776
|
+
runId: activation.runId,
|
|
777
|
+
provider: activation.provider,
|
|
778
|
+
parentSessionId: activation.ancestors.at(-1),
|
|
779
|
+
id: activation.childId,
|
|
780
|
+
local: activation.kind === 'local',
|
|
781
|
+
};
|
|
782
|
+
}
|
|
783
|
+
/** Record acceptance and begin settlement observation before returning the receipt. */
|
|
784
|
+
accept(activation) {
|
|
785
|
+
activation.accepted = true;
|
|
883
786
|
if (activation.kind === 'local') {
|
|
884
787
|
this.watchLocalSettlement(activation);
|
|
885
788
|
}
|
|
@@ -897,29 +800,10 @@ export class SubagentManager {
|
|
|
897
800
|
finally {
|
|
898
801
|
this.resident.delete(activation.childId);
|
|
899
802
|
activation.releaseSlot();
|
|
900
|
-
this.releaseOwnership(activation.childId);
|
|
901
803
|
activation.result.reject(error);
|
|
902
804
|
}
|
|
903
805
|
});
|
|
904
806
|
}
|
|
905
|
-
/** Register the child in a continuation-managed parent's owned set. */
|
|
906
|
-
acquireOwnership(parent, childId) {
|
|
907
|
-
const parentActivation = this.resident.get(parent.id);
|
|
908
|
-
if (parentActivation === undefined)
|
|
909
|
-
return;
|
|
910
|
-
if (parentActivation.closing !== undefined) {
|
|
911
|
-
throw new SubagentError(`subagent parent "${parent.id}" is being disposed; the child was not established`, 'ACTIVATION_CLOSING');
|
|
912
|
-
}
|
|
913
|
-
parentActivation.ownedChildren.add(childId);
|
|
914
|
-
this.wake(parentActivation);
|
|
915
|
-
}
|
|
916
|
-
/** Remove one child from its live owner's set and let that owner re-check settlement. */
|
|
917
|
-
releaseOwnership(childId) {
|
|
918
|
-
for (const candidate of this.resident.values()) {
|
|
919
|
-
if (candidate.ownedChildren.delete(childId))
|
|
920
|
-
this.wake(candidate);
|
|
921
|
-
}
|
|
922
|
-
}
|
|
923
807
|
/** Let a settlement watcher re-check residency after relevant state changes. */
|
|
924
808
|
wake(activation) {
|
|
925
809
|
if (activation.kind === 'external')
|
|
@@ -985,20 +869,18 @@ export class SubagentManager {
|
|
|
985
869
|
reportTeardownFailure(activation, error) {
|
|
986
870
|
this.ctx.logger.warn(`subagent "${activation.childId}" activation teardown failed: ${failureMessage(error)}`);
|
|
987
871
|
}
|
|
988
|
-
/** Check pending input
|
|
872
|
+
/** Check pending input before closing admission. */
|
|
989
873
|
settlementState(activation, observation) {
|
|
990
874
|
if (activation.closing !== undefined)
|
|
991
875
|
return 'closed';
|
|
992
876
|
if (activation.poke !== observation)
|
|
993
877
|
return 'retry';
|
|
994
878
|
const inbox = activation.handle.agent.inbox;
|
|
995
|
-
if (activation.ownedChildren.size > 0)
|
|
996
|
-
return 'wait';
|
|
997
879
|
if (activation.failureAt === undefined && (inbox.nextTurn.length > 0 || inbox.nextStep.length > 0))
|
|
998
880
|
return 'wait';
|
|
999
881
|
return 'ready';
|
|
1000
882
|
}
|
|
1001
|
-
/**
|
|
883
|
+
/** Stop and release one instance without affecting its creation descendants. */
|
|
1002
884
|
async finishDisposal(activation, stop) {
|
|
1003
885
|
this.wake(activation);
|
|
1004
886
|
const { childId } = activation;
|
|
@@ -1024,27 +906,6 @@ export class SubagentManager {
|
|
|
1024
906
|
if (stop) {
|
|
1025
907
|
activation.handle.agent.cancel({ kind: 'parent' });
|
|
1026
908
|
const idle = activation.handle.agent.whenIdle();
|
|
1027
|
-
const children = [...activation.ownedChildren]
|
|
1028
|
-
.map(child => this.resident.get(child))
|
|
1029
|
-
.filter((child) => child !== undefined);
|
|
1030
|
-
const pending = [...this.materializations].filter(item => item.lineage.includes(activation.handle.agent));
|
|
1031
|
-
const childDisposals = children.map(child => this.dispose(child));
|
|
1032
|
-
for (const item of pending)
|
|
1033
|
-
item.controller.abort();
|
|
1034
|
-
childDisposals.push(...pending.map(item => item.settled));
|
|
1035
|
-
const childFailures = await Promise.all(childDisposals.map(async (disposal) => {
|
|
1036
|
-
try {
|
|
1037
|
-
await disposal;
|
|
1038
|
-
return undefined;
|
|
1039
|
-
}
|
|
1040
|
-
catch (error) {
|
|
1041
|
-
return error;
|
|
1042
|
-
}
|
|
1043
|
-
}));
|
|
1044
|
-
const reasons = childFailures.filter(reason => reason !== undefined);
|
|
1045
|
-
if (reasons.length > 0) {
|
|
1046
|
-
failures.push(new SubagentError(`subagent "${childId}" child teardown failed: ${reasons.map(reason => failureMessage(reason)).join('; ')}`, 'ACTIVATION_TEARDOWN_FAILED'));
|
|
1047
|
-
}
|
|
1048
909
|
await idle;
|
|
1049
910
|
await this.flushFinalState(activation);
|
|
1050
911
|
}
|
|
@@ -1076,8 +937,13 @@ export class SubagentManager {
|
|
|
1076
937
|
activation.releaseSlot();
|
|
1077
938
|
const terminal = failure === undefined ? result : { output: [], stopReason: 'error' };
|
|
1078
939
|
this.notifySettlement(activation, result);
|
|
1079
|
-
|
|
1080
|
-
|
|
940
|
+
// The sole end publication follows resource release and parent notification,
|
|
941
|
+
// before result delivery. Cleanup failure changes the event, not the captured result.
|
|
942
|
+
this.host.emitLifecycle('subagent/end', {
|
|
943
|
+
...this.runInfo(activation),
|
|
944
|
+
stopReason: terminal.stopReason,
|
|
945
|
+
...terminal.output.length === 0 ? {} : { lastAssistantMessage: terminal.output },
|
|
946
|
+
}, activation.root);
|
|
1081
947
|
if (resultFailure === undefined)
|
|
1082
948
|
activation.result.resolve(result);
|
|
1083
949
|
else
|
|
@@ -1087,11 +953,12 @@ export class SubagentManager {
|
|
|
1087
953
|
}
|
|
1088
954
|
/** Deliver the captured execution result to the durable direct parent after cleanup. */
|
|
1089
955
|
notifySettlement(activation, terminal) {
|
|
1090
|
-
if (!activation.
|
|
956
|
+
if (!activation.accepted || activation.delivery === 'caller')
|
|
1091
957
|
return;
|
|
1092
958
|
try {
|
|
1093
|
-
const
|
|
1094
|
-
|
|
959
|
+
const parentId = activation.ancestors.at(-1);
|
|
960
|
+
const parent = this.ctx.agents.get(parentId);
|
|
961
|
+
if (parent === undefined || this.rootOf(parent) !== activation.root)
|
|
1095
962
|
return;
|
|
1096
963
|
const message = createSettlementMessage(activation.childId, terminal, activation.kind === 'local', activation.kind === 'local' && activation.omitClosingText);
|
|
1097
964
|
if (this.closingTeardownFor(parent) !== undefined) {
|
|
@@ -1123,140 +990,112 @@ export class SubagentManager {
|
|
|
1123
990
|
dispose: () => this.dispose(activation),
|
|
1124
991
|
};
|
|
1125
992
|
}
|
|
1126
|
-
/**
|
|
1127
|
-
async
|
|
1128
|
-
this.assertAdmitting(parent);
|
|
1129
|
-
const releaseHold = this.holdOwnership(parent, childId);
|
|
1130
|
-
try {
|
|
1131
|
-
return await this.deliverFollowup(parent, childId, content, options);
|
|
1132
|
-
}
|
|
1133
|
-
catch (error) {
|
|
1134
|
-
releaseHold();
|
|
1135
|
-
throw error;
|
|
1136
|
-
}
|
|
1137
|
-
}
|
|
1138
|
-
/** The delivery loop behind {@link deliverToChild}, run under the parent hold. */
|
|
1139
|
-
async deliverFollowup(parent, childId, content, options) {
|
|
993
|
+
/** Serialize target restoration and inbox admission; closure is awaited outside the lock. */
|
|
994
|
+
async deliverToMember(root, childId, content, options) {
|
|
1140
995
|
while (true) {
|
|
1141
|
-
|
|
996
|
+
this.assertAdmitting(root);
|
|
997
|
+
options.signal.throwIfAborted();
|
|
998
|
+
const result = await this.locks.run(childId, async () => {
|
|
999
|
+
if (childId === root.id) {
|
|
1000
|
+
if (contentHasImage(content))
|
|
1001
|
+
await this.assertImageCapable(root, options.signal);
|
|
1002
|
+
options.signal.throwIfAborted();
|
|
1003
|
+
options.authorize?.();
|
|
1004
|
+
this.assertAdmitting(root);
|
|
1005
|
+
const message = createUserMessage({ content, source: options.source });
|
|
1006
|
+
this.sendWaking(root, message, options.delivery);
|
|
1007
|
+
options.onAccepted?.();
|
|
1008
|
+
return { messageId: message.id };
|
|
1009
|
+
}
|
|
1142
1010
|
const activation = this.resident.get(childId);
|
|
1143
|
-
if (activation === undefined)
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
const disposal = activation.closing;
|
|
1147
|
-
/* v8 ignore next 3 -- the send-versus-dispose cutoff needs a delivery to
|
|
1148
|
-
* observe the transaction inside the same critical section that opened it. */
|
|
1149
|
-
if (disposal !== undefined) {
|
|
1150
|
-
return disposal.then(() => undefined, () => undefined);
|
|
1011
|
+
if (activation === undefined) {
|
|
1012
|
+
const restored = await this.restoreTarget(root, childId, options.signal);
|
|
1013
|
+
return { messageId: await this.submitMaterialized(restored, content, options, root) };
|
|
1151
1014
|
}
|
|
1152
|
-
if (
|
|
1015
|
+
if (activation.root !== root)
|
|
1016
|
+
throw new SubagentError('target belongs to another subagent tree', 'UNAUTHORIZED');
|
|
1017
|
+
requireLocalActivation(activation);
|
|
1018
|
+
if (activation.closing === undefined && contentHasImage(content)) {
|
|
1153
1019
|
await this.assertImageCapable(requireLocalActivation(activation).handle.agent, options.signal);
|
|
1154
|
-
if (activation.closing !== undefined) {
|
|
1155
|
-
await Promise.allSettled([activation.closing]);
|
|
1156
|
-
return undefined;
|
|
1157
|
-
}
|
|
1158
1020
|
}
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1021
|
+
if (activation.closing !== undefined)
|
|
1022
|
+
return { closing: activation.closing };
|
|
1023
|
+
const messageId = this.submitAdmitted(activation, content, options, root);
|
|
1024
|
+
return { messageId };
|
|
1162
1025
|
});
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
this.assertAdmitting(parent);
|
|
1167
|
-
options.signal.throwIfAborted();
|
|
1168
|
-
/* v8 ignore stop */
|
|
1169
|
-
}
|
|
1170
|
-
}
|
|
1171
|
-
/** Deliver one resident continuable child's message to its live direct parent. */
|
|
1172
|
-
sendToParent(activation, sender, content) {
|
|
1173
|
-
/* v8 ignore next 6 -- only synchronous re-entrant teardown can open this
|
|
1174
|
-
* transaction between exact-agent authorization and this no-await span. */
|
|
1175
|
-
if (activation.closing !== undefined) {
|
|
1176
|
-
throw new SubagentError(`subagent "${sender.id}" activation is being disposed; the message was not delivered`, 'ACTIVATION_CLOSING');
|
|
1177
|
-
}
|
|
1178
|
-
const parent = this.ctx.agents.get(activation.parent.id);
|
|
1179
|
-
if (parent !== activation.parent) {
|
|
1180
|
-
throw new SubagentError('direct parent is not live; the message was not delivered', 'PARENT_UNAVAILABLE');
|
|
1026
|
+
if ('messageId' in result)
|
|
1027
|
+
return result.messageId;
|
|
1028
|
+
await Promise.allSettled([result.closing]);
|
|
1181
1029
|
}
|
|
1182
|
-
const message = createAgentMessage(sender, content);
|
|
1183
|
-
this.sendAgentMessage(parent, message);
|
|
1184
|
-
activation.omitClosingText = true;
|
|
1185
|
-
return message.id;
|
|
1186
1030
|
}
|
|
1187
|
-
/**
|
|
1188
|
-
|
|
1031
|
+
/** Read continuation history, preserving cancellation and reporting unavailable storage. */
|
|
1032
|
+
async observeContinuation(childId, signal) {
|
|
1033
|
+
const query = this.requireSessionQuery();
|
|
1189
1034
|
try {
|
|
1190
|
-
|
|
1035
|
+
return await query.observeSession(childId, { signal });
|
|
1191
1036
|
}
|
|
1192
1037
|
catch (error) {
|
|
1193
|
-
|
|
1038
|
+
signal.throwIfAborted();
|
|
1039
|
+
throw new SubagentError(`subagent "${childId}" is unavailable`, 'NOT_RESUMABLE', { cause: error });
|
|
1194
1040
|
}
|
|
1195
1041
|
}
|
|
1196
|
-
/**
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
async coldResume(parent, childId, content, options) {
|
|
1201
|
-
const env_1 = { stack: [], error: void 0, hasError: false };
|
|
1202
|
-
try {
|
|
1203
|
-
const query = this.requireSessionQuery();
|
|
1204
|
-
let observation;
|
|
1042
|
+
/** Reconstruct only the target under its subagent tree root; the caller owns acceptance or rollback. */
|
|
1043
|
+
async restoreTarget(root, childId, signal) {
|
|
1044
|
+
return this.trackStartup(root, signal, async (signal) => {
|
|
1045
|
+
const env_3 = { stack: [], error: void 0, hasError: false };
|
|
1205
1046
|
try {
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
throw new SubagentError(`subagent "${childId}" is unavailable`, 'NOT_RESUMABLE', { cause: error });
|
|
1213
|
-
}
|
|
1214
|
-
const source = __addDisposableResource(env_1, observation, false);
|
|
1215
|
-
this.assertAdmitting(parent);
|
|
1216
|
-
this.authorizeLineage(parent, childId, source.header.parentSession);
|
|
1217
|
-
const ownEvents = source.events.slice(source.inheritedEventCount);
|
|
1218
|
-
const descriptor = foldSubagentDescriptor(ownEvents);
|
|
1219
|
-
if (descriptor === undefined || descriptor.mode !== 'continuable') {
|
|
1220
|
-
throw new SubagentError(`subagent "${childId}" has no supported continuation state and cannot be resumed; choose a different target`, 'NOT_RESUMABLE');
|
|
1221
|
-
}
|
|
1222
|
-
let activation;
|
|
1223
|
-
try {
|
|
1224
|
-
const independentlySelected = hasIndependentPresetSelection(ownEvents);
|
|
1225
|
-
const agentPreset = independentlySelected ? source.projections?.values.agentPreset : undefined;
|
|
1226
|
-
if (independentlySelected && typeof agentPreset !== 'string') {
|
|
1227
|
-
throw new SubagentError(`subagent "${childId}" has no agent preset projection for restoration`, 'NOT_RESUMABLE');
|
|
1047
|
+
const source = __addDisposableResource(env_3, await this.observeContinuation(childId, signal), false);
|
|
1048
|
+
this.assertAdmitting(root);
|
|
1049
|
+
const ownEvents = source.events.slice(source.inheritedEventCount);
|
|
1050
|
+
const descriptor = foldSubagentDescriptor(ownEvents);
|
|
1051
|
+
if (descriptor === undefined || descriptor.mode !== 'continuable') {
|
|
1052
|
+
throw new SubagentError(`subagent "${childId}" has no supported continuation state and cannot be resumed; choose a different target`, 'NOT_RESUMABLE');
|
|
1228
1053
|
}
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1054
|
+
const { ancestors } = await this.resolveCreationAncestry(source, signal, root);
|
|
1055
|
+
let activation;
|
|
1056
|
+
try {
|
|
1057
|
+
const projectedPreset = source.projections?.values.agentPreset;
|
|
1058
|
+
if (projectedPreset === undefined && source.events.some(event => event.type === 'agent-preset/selected')) {
|
|
1059
|
+
throw new SubagentError(`subagent "${childId}" has no agent preset projection for restoration`, 'NOT_RESUMABLE');
|
|
1060
|
+
}
|
|
1061
|
+
const agentPreset = projectedPreset ?? source.header.agentPreset;
|
|
1062
|
+
activation = await this.materialize({
|
|
1063
|
+
kind: 'local',
|
|
1064
|
+
childId,
|
|
1065
|
+
provider: descriptor.provider,
|
|
1066
|
+
root,
|
|
1067
|
+
ancestors,
|
|
1068
|
+
agentOptions: {
|
|
1069
|
+
...descriptor.agentProvider !== undefined ? { provider: descriptor.agentProvider } : {},
|
|
1070
|
+
...descriptor.agentModel !== undefined ? { model: descriptor.agentModel } : {},
|
|
1071
|
+
...descriptor.agentReasoningEffort !== undefined
|
|
1072
|
+
? { reasoningEffort: ReasoningEffortId(descriptor.agentReasoningEffort) }
|
|
1073
|
+
: {},
|
|
1074
|
+
},
|
|
1075
|
+
composition: {
|
|
1076
|
+
agentPreset: agentPreset ?? undefined,
|
|
1077
|
+
persona: descriptor.persona,
|
|
1078
|
+
toolFilter: descriptor.toolFilter,
|
|
1079
|
+
},
|
|
1080
|
+
signal,
|
|
1081
|
+
});
|
|
1082
|
+
}
|
|
1083
|
+
catch (error) {
|
|
1084
|
+
signal.throwIfAborted();
|
|
1085
|
+
if (error instanceof SubagentError)
|
|
1086
|
+
throw error;
|
|
1087
|
+
throw new SubagentError(`subagent "${childId}" is unavailable`, 'NOT_RESUMABLE', { cause: error });
|
|
1088
|
+
}
|
|
1089
|
+
return activation;
|
|
1244
1090
|
}
|
|
1245
|
-
catch (
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
throw error;
|
|
1249
|
-
throw new SubagentError(`subagent "${childId}" is unavailable`, 'NOT_RESUMABLE', { cause: error });
|
|
1091
|
+
catch (e_3) {
|
|
1092
|
+
env_3.error = e_3;
|
|
1093
|
+
env_3.hasError = true;
|
|
1250
1094
|
}
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
env_1.hasError = true;
|
|
1256
|
-
}
|
|
1257
|
-
finally {
|
|
1258
|
-
__disposeResources(env_1);
|
|
1259
|
-
}
|
|
1095
|
+
finally {
|
|
1096
|
+
__disposeResources(env_3);
|
|
1097
|
+
}
|
|
1098
|
+
});
|
|
1260
1099
|
}
|
|
1261
1100
|
/** Admit a materialized child, commit its creation fact, and release it on failure. */
|
|
1262
1101
|
async submitMaterialized(activation, content, options, parent, commit) {
|
|
@@ -1269,7 +1108,7 @@ export class SubagentManager {
|
|
|
1269
1108
|
}
|
|
1270
1109
|
const messageId = this.submitAdmitted(activation, content, options, parent);
|
|
1271
1110
|
commit?.();
|
|
1272
|
-
this.
|
|
1111
|
+
this.accept(activation);
|
|
1273
1112
|
return messageId;
|
|
1274
1113
|
}
|
|
1275
1114
|
catch (error) {
|
|
@@ -1284,19 +1123,17 @@ export class SubagentManager {
|
|
|
1284
1123
|
}
|
|
1285
1124
|
/** Build and submit one message across the final synchronous admission cutoff. */
|
|
1286
1125
|
submitAdmitted(activation, content, options, parent) {
|
|
1287
|
-
const message = options.source
|
|
1288
|
-
? createAgentMessage(parent, content)
|
|
1289
|
-
: createUserMessage({ content, source: options.source });
|
|
1126
|
+
const message = createUserMessage({ content, source: options.source });
|
|
1290
1127
|
options.signal.throwIfAborted();
|
|
1128
|
+
options.authorize?.();
|
|
1291
1129
|
this.assertAdmitting(parent);
|
|
1292
|
-
this.authorizeLineage(parent, activation.childId, activation.parent.id);
|
|
1293
|
-
this.acquireOwnership(parent, activation.childId);
|
|
1294
1130
|
try {
|
|
1295
1131
|
this.deliver(activation, message, options.delivery);
|
|
1296
1132
|
}
|
|
1297
1133
|
finally {
|
|
1298
1134
|
this.wake(activation);
|
|
1299
1135
|
}
|
|
1136
|
+
options.onAccepted?.();
|
|
1300
1137
|
return message.id;
|
|
1301
1138
|
}
|
|
1302
1139
|
/** Refuse image content for a child whose fixed model accepts text only. */
|
|
@@ -1331,15 +1168,4 @@ export class SubagentManager {
|
|
|
1331
1168
|
}
|
|
1332
1169
|
}
|
|
1333
1170
|
export default SubagentManager;
|
|
1334
|
-
/** Creation-time selection precedes the own descriptor; historical forks can select after it. */
|
|
1335
|
-
function hasIndependentPresetSelection(events) {
|
|
1336
|
-
for (const event of events) {
|
|
1337
|
-
if (event.type === 'subagent/descriptor')
|
|
1338
|
-
return false;
|
|
1339
|
-
if (event.type === 'agent-preset/selected')
|
|
1340
|
-
return true;
|
|
1341
|
-
}
|
|
1342
|
-
/* v8 ignore next -- coldResume rejects logs without a supported own descriptor before this lookup. */
|
|
1343
|
-
return false;
|
|
1344
|
-
}
|
|
1345
1171
|
//# sourceMappingURL=manager.js.map
|