@deepseek-ai/dsh-subagent 0.2.1-alpha.1 → 0.2.1-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 +40 -34
- package/README.md +43 -26
- package/README.zh.md +43 -26
- package/lib/index.js +1480 -1349
- package/lib/typert.host.js +230 -76
- package/lib/typert.remote-client.d.ts +6 -2
- package/lib/typert.remote-client.js +82 -28
- package/lib/types/activation.d.ts +114 -0
- package/lib/types/activation.js +112 -0
- package/lib/types/catalog.d.ts +14 -6
- package/lib/types/catalog.js +34 -30
- package/lib/types/child-agent.d.ts +16 -32
- package/lib/types/child-agent.js +25 -30
- package/lib/types/continuation-messages.d.ts +4 -3
- package/lib/types/continuation-messages.js +13 -5
- package/lib/types/control-types.d.ts +7 -0
- package/lib/types/descriptor.d.ts +7 -46
- package/lib/types/descriptor.js +20 -21
- package/lib/types/index.d.ts +52 -51
- package/lib/types/index.js +81 -98
- package/lib/types/internal.d.ts +1 -1
- package/lib/types/lifecycle.d.ts +28 -58
- package/lib/types/lifecycle.js +9 -74
- package/lib/types/list-children.js +1 -1
- package/lib/types/manager.d.ts +247 -0
- package/lib/types/manager.js +1316 -0
- package/lib/types/out-of-process.d.ts +3 -29
- package/lib/types/out-of-process.js +5 -47
- package/lib/types/projection-types.d.ts +3 -0
- package/lib/types/structured.d.ts +49 -0
- package/lib/types/structured.js +130 -0
- package/lib/types/types.d.ts +66 -100
- package/lib/types/types.js +1 -1
- package/locale/en.json +5 -0
- package/locale/zh.json +6 -0
- package/package.json +56 -53
- package/lib/types/continuation-activation.d.ts +0 -267
- package/lib/types/continuation-activation.js +0 -700
- package/lib/types/continuation.d.ts +0 -124
- package/lib/types/continuation.js +0 -455
- package/lib/types/inbox.d.ts +0 -43
- package/lib/types/inbox.js +0 -61
- package/lib/types/run-settlement.d.ts +0 -17
- package/lib/types/run-settlement.js +0 -70
package/lib/index.js
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
import z from "@deepseek-ai/schemastery";
|
|
2
2
|
import { scopeTarget } from "@deepseek-ai/dsh-scope";
|
|
3
|
-
import { assertObjectJsonSchema } from "@deepseek-ai/dsh-tools";
|
|
3
|
+
import { ToolArgsError, assertObjectJsonSchema, validateJsonSchemaValue } from "@deepseek-ai/dsh-tools";
|
|
4
4
|
import { canonicalClientTimeZone } from "@deepseek-ai/dsh-util-time";
|
|
5
|
-
import { Remote, RemoteError, TypertRemoteService } from "@deepseek-ai/dsh-typert-protocol";
|
|
5
|
+
import { Remote, RemoteError, TypertRemoteService, principalOf } from "@deepseek-ai/dsh-typert-protocol";
|
|
6
6
|
import { AttachmentError } from "@deepseek-ai/dsh-attachment";
|
|
7
7
|
import { z as z$1 } from "zod";
|
|
8
8
|
import { HarnessError, ReasoningEffortId, boundContextSummary, contentHasImage, createUserMessage, errorChain, joinAssistantStreamText } from "@deepseek-ai/dsh-llm";
|
|
9
9
|
import { randomUUID } from "node:crypto";
|
|
10
10
|
import { foldConsumedWork } from "@deepseek-ai/dsh-agent";
|
|
11
|
-
import {
|
|
11
|
+
import { isAbsolute, resolve } from "node:path";
|
|
12
12
|
import { brandString } from "@deepseek-ai/dsh-brand";
|
|
13
|
-
import {
|
|
13
|
+
import { SessionLogOffset, SessionSeq } from "@deepseek-ai/dsh-session";
|
|
14
|
+
import { assertNever, snapshotJsonValue } from "@deepseek-ai/dsh-util-values";
|
|
14
15
|
import { appendChunkedList, chunkedListSchema, iterateChunkedList } from "@deepseek-ai/dsh-chunked-list";
|
|
15
16
|
import { accessSync, constants, statSync } from "node:fs";
|
|
16
|
-
import { isAbsolute, resolve } from "node:path";
|
|
17
17
|
//#region lib/types/error.js
|
|
18
18
|
/**
|
|
19
19
|
* Typed failures shared by subagent service and provider operations.
|
|
@@ -121,79 +121,13 @@ function assertSubagentMaxDepth(maxDepth) {
|
|
|
121
121
|
if (maxDepth !== void 0 && (typeof maxDepth !== "number" || !Number.isSafeInteger(maxDepth) || maxDepth < 0 || Object.is(maxDepth, -0))) throw new TypeError("subagent maxDepth must be a non-negative safe integer");
|
|
122
122
|
}
|
|
123
123
|
//#endregion
|
|
124
|
-
//#region lib/types/assistant-output.js
|
|
125
|
-
/**
|
|
126
|
-
* Canonical selection of a child's final assistant output. Backend run results
|
|
127
|
-
* and `subagent/end.lastAssistantMessage` apply the same rule: select the last
|
|
128
|
-
* non-empty assistant message. An empty-content message records usage only
|
|
129
|
-
* when the loop appends it after a max-tokens step with no executable blocks,
|
|
130
|
-
* so it does not replace earlier output. If no non-empty message exists,
|
|
131
|
-
* select the accumulated assistant text. Selection is independent of the
|
|
132
|
-
* run's stop reason.
|
|
133
|
-
*
|
|
134
|
-
* @module @deepseek-ai/dsh-subagent/assistant-output
|
|
135
|
-
*/
|
|
136
|
-
/**
|
|
137
|
-
* Incremental fold of the selection rule, for backends that observe a child's
|
|
138
|
-
* output as it streams: session-event backends {@link push} each event, and
|
|
139
|
-
* transports without session events (ACP content chunks) {@link pushText} raw
|
|
140
|
-
* text into the same streamed fallback.
|
|
141
|
-
*/
|
|
142
|
-
var AssistantOutputFold = class {
|
|
143
|
-
message;
|
|
144
|
-
partial = [];
|
|
145
|
-
/**
|
|
146
|
-
* Fold one session event: a non-empty assistant message becomes the
|
|
147
|
-
* candidate final answer, while its embedded stream and any log-only attempt
|
|
148
|
-
* extend the streamed fallback; every other event contributes nothing.
|
|
149
|
-
* @param event - the next observed session event.
|
|
150
|
-
*/
|
|
151
|
-
push(event) {
|
|
152
|
-
if (event.type === "assistant/message") {
|
|
153
|
-
const content = event.data.message.content;
|
|
154
|
-
if (content.length > 0) this.message = content;
|
|
155
|
-
}
|
|
156
|
-
if (event.type === "assistant/message" || event.type === "assistant/attempt") this.pushText(joinAssistantStreamText(event.data.stream));
|
|
157
|
-
}
|
|
158
|
-
/**
|
|
159
|
-
* Extend the streamed fallback with text observed outside session events.
|
|
160
|
-
* @param text - the next streamed text piece (an empty piece is a no-op).
|
|
161
|
-
*/
|
|
162
|
-
pushText(text) {
|
|
163
|
-
if (text.length > 0) this.partial.push(text);
|
|
164
|
-
}
|
|
165
|
-
/**
|
|
166
|
-
* Select the final output folded so far.
|
|
167
|
-
* @returns the last non-empty assistant message, else the accumulated
|
|
168
|
-
* streamed text, or `undefined` when the child produced neither.
|
|
169
|
-
*/
|
|
170
|
-
collect() {
|
|
171
|
-
if (this.message !== void 0) return this.message;
|
|
172
|
-
const text = this.partial.join("");
|
|
173
|
-
return text.length > 0 ? [{
|
|
174
|
-
type: "text",
|
|
175
|
-
text
|
|
176
|
-
}] : void 0;
|
|
177
|
-
}
|
|
178
|
-
};
|
|
179
|
-
/**
|
|
180
|
-
* Apply the selection rule to one complete child-owned event suffix.
|
|
181
|
-
* @param events - the child-owned events (after any seed or epoch boundary).
|
|
182
|
-
* @returns the selected output, or `undefined` when the child produced none.
|
|
183
|
-
*/
|
|
184
|
-
function finalAssistantOutput(events) {
|
|
185
|
-
const fold = new AssistantOutputFold();
|
|
186
|
-
for (const event of events) fold.push(event);
|
|
187
|
-
return fold.collect();
|
|
188
|
-
}
|
|
189
|
-
//#endregion
|
|
190
124
|
//#region lib/types/types.js
|
|
191
125
|
/**
|
|
192
126
|
* The seam's consumer-facing contracts: request, result, and capability types
|
|
193
127
|
* for {@link SubagentProvider}, plus the `subagent/start` and `subagent/end`
|
|
194
128
|
* payloads that plugins and hosts observe. Internal control interfaces belong
|
|
195
129
|
* with their implementation — the lifecycle observer in `./lifecycle.ts`, the
|
|
196
|
-
*
|
|
130
|
+
* manager host in `./manager.ts` — so this module stays the published
|
|
197
131
|
* surface rather than a bag of everything type-shaped.
|
|
198
132
|
*
|
|
199
133
|
* @module @deepseek-ai/dsh-subagent/types
|
|
@@ -209,17 +143,10 @@ function SubagentRunId(id) {
|
|
|
209
143
|
//#endregion
|
|
210
144
|
//#region lib/types/lifecycle.js
|
|
211
145
|
/**
|
|
212
|
-
* Lifecycle-edge publication for
|
|
213
|
-
* the one-shot run observer, and the continuable Activation observer.
|
|
146
|
+
* Lifecycle-edge publication for managed subagent activations.
|
|
214
147
|
*
|
|
215
|
-
*
|
|
216
|
-
*
|
|
217
|
-
* consumer-facing types; this module owns only the implementation and the
|
|
218
|
-
* package-private {@link ActivationObserver} the continuation manager consumes.
|
|
219
|
-
* Keeping the internal control interface out of the published surface is
|
|
220
|
-
* deliberate: the observer's `start`/`capture`/`settle` ordering is a contract
|
|
221
|
-
* between this module and one in-package caller, not something a plugin may
|
|
222
|
-
* depend on.
|
|
148
|
+
* Public event payloads live in `./types.ts`; the package-private observer
|
|
149
|
+
* retains only the identity shared by each start/end pair.
|
|
223
150
|
*
|
|
224
151
|
* @module @deepseek-ai/dsh-subagent/lifecycle
|
|
225
152
|
*/
|
|
@@ -250,40 +177,7 @@ function createLifecycleEmitter(ctx, carrier) {
|
|
|
250
177
|
};
|
|
251
178
|
}
|
|
252
179
|
/**
|
|
253
|
-
*
|
|
254
|
-
* @param emit - the contained lifecycle emitter.
|
|
255
|
-
* @param provider - the provider that established the run.
|
|
256
|
-
* @param parent - the delegating parent keying scoped dispatch.
|
|
257
|
-
* @param run - the published run whose settlement closes the pair.
|
|
258
|
-
* @returns the same run, unchanged.
|
|
259
|
-
*/
|
|
260
|
-
function observeRun(emit, provider, parent, run) {
|
|
261
|
-
const identity = {
|
|
262
|
-
runId: SubagentRunId(randomUUID()),
|
|
263
|
-
provider,
|
|
264
|
-
id: run.id,
|
|
265
|
-
local: run.localAgent !== void 0
|
|
266
|
-
};
|
|
267
|
-
run.result.then((result) => {
|
|
268
|
-
emit("subagent/end", {
|
|
269
|
-
...identity,
|
|
270
|
-
stopReason: result.stopReason,
|
|
271
|
-
...result.output.length === 0 ? {} : { lastAssistantMessage: result.output }
|
|
272
|
-
}, parent);
|
|
273
|
-
}, () => {
|
|
274
|
-
emit("subagent/end", {
|
|
275
|
-
...identity,
|
|
276
|
-
stopReason: "error"
|
|
277
|
-
}, parent);
|
|
278
|
-
});
|
|
279
|
-
emit("subagent/start", identity, parent);
|
|
280
|
-
return run;
|
|
281
|
-
}
|
|
282
|
-
/**
|
|
283
|
-
* Build the observer for one continuable Activation's residency epoch. Observers
|
|
284
|
-
* see the same vocabulary as a one-shot run, so a child's start and settlement
|
|
285
|
-
* remain observable without exposing whether the manager materialized, woke, or
|
|
286
|
-
* cold-resumed it. Creation failure before residency emits no lifecycle edge.
|
|
180
|
+
* Build the observer for one local or external activation's residency epoch. Creation failure before residency emits no lifecycle edge.
|
|
287
181
|
* @param emit - the contained lifecycle emitter.
|
|
288
182
|
* @param provider - the provider name recorded in the durable descriptor.
|
|
289
183
|
* @param childId - the durable child session id.
|
|
@@ -295,31 +189,18 @@ function createActivationObserver(emit, provider, childId, parent) {
|
|
|
295
189
|
runId: SubagentRunId(randomUUID()),
|
|
296
190
|
provider,
|
|
297
191
|
id: childId,
|
|
298
|
-
local:
|
|
192
|
+
local: false
|
|
299
193
|
};
|
|
300
|
-
let boundary = SessionLogOffset(0);
|
|
301
|
-
let captured = { stopReason: "completed" };
|
|
302
|
-
const terminal = (failure) => failure === void 0 ? captured : { stopReason: "error" };
|
|
303
194
|
return {
|
|
304
195
|
start: (child) => {
|
|
305
|
-
|
|
196
|
+
identity.local = child !== void 0;
|
|
306
197
|
emit("subagent/start", identity, parent);
|
|
307
198
|
},
|
|
308
|
-
|
|
309
|
-
const own = child.session.snapshotEvents(boundary);
|
|
310
|
-
const output = finalAssistantOutput(own);
|
|
311
|
-
captured = {
|
|
312
|
-
stopReason: epochStopReason(own),
|
|
313
|
-
...output === void 0 ? {} : { output }
|
|
314
|
-
};
|
|
315
|
-
},
|
|
316
|
-
terminal,
|
|
317
|
-
settle: (failure) => {
|
|
318
|
-
const { stopReason, output } = terminal(failure);
|
|
199
|
+
settle: ({ stopReason, output }) => {
|
|
319
200
|
emit("subagent/end", {
|
|
320
201
|
...identity,
|
|
321
202
|
stopReason,
|
|
322
|
-
...output ===
|
|
203
|
+
...output.length === 0 ? {} : { lastAssistantMessage: output }
|
|
323
204
|
}, parent);
|
|
324
205
|
}
|
|
325
206
|
};
|
|
@@ -370,9 +251,7 @@ function renderThrown(value) {
|
|
|
370
251
|
/**
|
|
371
252
|
* Shared in-process child composition: the delegation-depth budget, the
|
|
372
253
|
* durable session metadata, the resolved child `AgentOptions`, the delegated
|
|
373
|
-
* policy seed, and the scoped setup
|
|
374
|
-
* provider driver and the continuation manager compose children this way, so
|
|
375
|
-
* depth accounting, lineage stamping, and delegation policy have one home.
|
|
254
|
+
* policy seed, and the scoped setup used by local activations.
|
|
376
255
|
*
|
|
377
256
|
* @module @deepseek-ai/dsh-subagent/child-agent
|
|
378
257
|
*/
|
|
@@ -456,20 +335,17 @@ function resolveChildAgentOptions(parent, requested, childDepth) {
|
|
|
456
335
|
* survive persistence, the seed boundary that separates inherited parent
|
|
457
336
|
* history from child work, and the composition the child runs under.
|
|
458
337
|
*
|
|
459
|
-
*
|
|
460
|
-
*
|
|
461
|
-
* composition and its header still names the older one. Recording it is what
|
|
462
|
-
* makes a child's history reconstructable: without it a cold read of the child
|
|
463
|
-
* resolves the deployment default and rebuilds turns under a tool set the
|
|
464
|
-
* child never had.
|
|
338
|
+
* Omitted selection uses the parent's live composition, including any switch
|
|
339
|
+
* made while the parent was blank.
|
|
465
340
|
* @param parent - the delegating parent agent.
|
|
466
341
|
* @param childDepth - the resolved delegation depth to persist.
|
|
467
342
|
* @param isSeeded - whether this child inherits a parent-log prefix, including an explicitly empty one.
|
|
343
|
+
* @param selectedPreset - independent selection, or omission to inherit the parent.
|
|
468
344
|
* @returns the `meta` for `ctx.agents.create()`.
|
|
469
345
|
*/
|
|
470
|
-
function childSessionMeta(parent, childDepth, isSeeded) {
|
|
346
|
+
function childSessionMeta(parent, childDepth, isSeeded, selectedPreset) {
|
|
471
347
|
const parentHeader = parent.session.header;
|
|
472
|
-
const agentPreset = parent.ctx.get("agentPresets")?.composedPreset(parent.ctx);
|
|
348
|
+
const agentPreset = selectedPreset ?? parent.ctx.get("agentPresets")?.composedPreset(parent.ctx);
|
|
473
349
|
return {
|
|
474
350
|
...parentHeader.cwd !== void 0 ? { cwd: parentHeader.cwd } : {},
|
|
475
351
|
...agentPreset === void 0 ? {} : { agentPreset },
|
|
@@ -486,29 +362,26 @@ function childSessionMeta(parent, childDepth, isSeeded) {
|
|
|
486
362
|
*/
|
|
487
363
|
const SUBAGENT_DELEGATION_CONTEXT = "You are a delegated subagent: your permission scope was fixed when you were started and cannot be widened from inside this session — operations that require approval are rejected automatically. When the task needs access beyond that scope, do not retry the denied operation; state the limitation in your reply so the delegating agent can handle it.";
|
|
488
364
|
/**
|
|
489
|
-
* Compose
|
|
490
|
-
*
|
|
491
|
-
*
|
|
492
|
-
* scope and therefore invisible to its parent and siblings. Creation and cold
|
|
493
|
-
* resume both pass through here.
|
|
494
|
-
*
|
|
495
|
-
* The join comes first and the child's own registrations second, which is the
|
|
496
|
-
* order the layering already implies — the nearest scope wins a name, and a
|
|
497
|
-
* per-child restriction intersects with everything its chain admits — but
|
|
498
|
-
* stating it here keeps the two steps from being read as independent.
|
|
499
|
-
*
|
|
500
|
-
* The join and the per-child registrations live in ONE call because a child
|
|
501
|
-
* composed without the join is exactly the defect this function exists to
|
|
502
|
-
* prevent: with every model-facing row on the agent plane, a child that joins
|
|
503
|
-
* no preset sees an empty tool registry and none of its parent's prompt
|
|
504
|
-
* sections. Taking the parent as a parameter is what makes that omission
|
|
505
|
-
* unrepresentable at the call sites.
|
|
365
|
+
* Compose a child before publication and restore historical descriptor overrides.
|
|
366
|
+
* Explicit selection mounts the named preset; omission synchronously joins the
|
|
367
|
+
* parent's current generation. Registrations belong to the child's scope.
|
|
506
368
|
* @param childCtx - the child agent's scoped creation context.
|
|
507
|
-
* @param parent - the delegating parent
|
|
508
|
-
* @param composition -
|
|
369
|
+
* @param parent - the delegating parent used when no preset is selected.
|
|
370
|
+
* @param composition - independent preset and any overrides read from a historical descriptor.
|
|
371
|
+
* @returns A promise when mounting an independent preset; otherwise completes synchronously.
|
|
509
372
|
*/
|
|
510
373
|
function applyChildComposition(childCtx, parent, composition) {
|
|
374
|
+
if (composition.agentPreset !== void 0) {
|
|
375
|
+
const presets = childCtx.get("agentPresets");
|
|
376
|
+
if (presets === void 0) throw new SubagentError("selecting a child agent preset requires the agent-presets service", "PRESET_UNAVAILABLE");
|
|
377
|
+
return presets.mount(childCtx, composition.agentPreset).then(() => {
|
|
378
|
+
applyChildOverrides(childCtx, composition);
|
|
379
|
+
});
|
|
380
|
+
}
|
|
511
381
|
childCtx.get("agentPresets")?.composeFrom(childCtx, parent.ctx);
|
|
382
|
+
applyChildOverrides(childCtx, composition);
|
|
383
|
+
}
|
|
384
|
+
function applyChildOverrides(childCtx, composition) {
|
|
512
385
|
childCtx.systemPrompt.context({
|
|
513
386
|
name: "subagent:delegation",
|
|
514
387
|
order: childCtx.systemPrompt.getContextOrder("SUBAGENT_DELEGATION"),
|
|
@@ -608,12 +481,13 @@ function withContinuableReturnGuidance(parentId, prompt) {
|
|
|
608
481
|
* the parent's own task vocabulary.
|
|
609
482
|
* @param childId - the durable child the parent knows by id.
|
|
610
483
|
* @param stopReason - how the child's last ordinary turn ended.
|
|
484
|
+
* @param continuable - whether the child accepts a later message.
|
|
611
485
|
* @returns the model-facing opening line of the settlement notice.
|
|
612
486
|
*/
|
|
613
|
-
function settlementSummary(childId, stopReason) {
|
|
487
|
+
function settlementSummary(childId, stopReason, continuable) {
|
|
614
488
|
const subject = `Background subagent ${childId}`;
|
|
615
489
|
switch (stopReason) {
|
|
616
|
-
case "completed": return `${subject} finished and will do no further work unless you send it more.`;
|
|
490
|
+
case "completed": return continuable ? `${subject} finished and will do no further work unless you send it more.` : `${subject} finished. It cannot receive follow-up messages.`;
|
|
617
491
|
case "aborted": return `${subject} was stopped before it finished.`;
|
|
618
492
|
case "max-tokens": return `${subject} ran out of room before it finished.`;
|
|
619
493
|
case "refusal": return `${subject} declined the task.`;
|
|
@@ -628,22 +502,34 @@ function settlementSummary(childId, stopReason) {
|
|
|
628
502
|
* Build the runtime-owned settlement notice from the child's nonempty closing text.
|
|
629
503
|
* @param childId - durable child session id named in the notice.
|
|
630
504
|
* @param terminal - recorded terminal state for the settled Activation.
|
|
505
|
+
* @param continuable - whether the child can accept another task.
|
|
631
506
|
* @returns the durable user-message representation delivered to the parent.
|
|
632
507
|
*/
|
|
633
|
-
function createSettlementMessage(childId, terminal) {
|
|
634
|
-
const summary = settlementSummary(childId, terminal.stopReason);
|
|
635
|
-
const closingText =
|
|
508
|
+
function createSettlementMessage(childId, terminal, continuable = true) {
|
|
509
|
+
const summary = settlementSummary(childId, terminal.stopReason, continuable);
|
|
510
|
+
const closingText = terminal.output.flatMap((block) => block.type === "text" && block.text.length > 0 ? [block] : []);
|
|
636
511
|
return createUserMessage({
|
|
637
|
-
content: [
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
512
|
+
content: [
|
|
513
|
+
{
|
|
514
|
+
type: "text",
|
|
515
|
+
text: summary
|
|
516
|
+
},
|
|
517
|
+
...closingText.length === 0 ? [{
|
|
518
|
+
type: "text",
|
|
519
|
+
text: "It left no closing message."
|
|
520
|
+
}] : [{
|
|
521
|
+
type: "text",
|
|
522
|
+
text: "Its closing message:"
|
|
523
|
+
}, ...closingText],
|
|
524
|
+
...terminal.structured !== void 0 ? [{
|
|
525
|
+
type: "text",
|
|
526
|
+
text: `Structured result: ${JSON.stringify(terminal.structured)}`
|
|
527
|
+
}] : [],
|
|
528
|
+
...terminal.diagnostic !== void 0 ? [{
|
|
529
|
+
type: "text",
|
|
530
|
+
text: terminal.diagnostic
|
|
531
|
+
}] : []
|
|
532
|
+
],
|
|
647
533
|
source: {
|
|
648
534
|
kind: "subagent-settled",
|
|
649
535
|
form: "notice",
|
|
@@ -653,731 +539,107 @@ function createSettlementMessage(childId, terminal) {
|
|
|
653
539
|
});
|
|
654
540
|
}
|
|
655
541
|
//#endregion
|
|
656
|
-
//#region lib/types/
|
|
542
|
+
//#region lib/types/descriptor.js
|
|
657
543
|
/**
|
|
658
|
-
*
|
|
544
|
+
* The durable subagent-child descriptor: the versioned, model-hidden
|
|
545
|
+
* `subagent/descriptor` session event that identifies every session-backed
|
|
546
|
+
* subagent, including historical one-shot records. The manager appends each
|
|
547
|
+
* continuable child's descriptor inside its initial turn, preserving the
|
|
548
|
+
* declared composition required for cold resume.
|
|
549
|
+
*
|
|
550
|
+
* The descriptor deliberately snapshots explicit fields rather than the
|
|
551
|
+
* merge-extensible `AgentOptions` object: an unrelated extension value cannot
|
|
552
|
+
* make continuation fail merely because it is not JSON, and later composition
|
|
553
|
+
* inputs require a deliberate {@link SUBAGENT_DESCRIPTOR_VERSION} change. It
|
|
554
|
+
* omits `subagentDepth` — cold resume trusts the persisted header's
|
|
555
|
+
* `delegationDepth` as the monotone floor — and `outputSchema`, which belongs
|
|
556
|
+
* to one activation's result contract rather than durable child composition.
|
|
557
|
+
* Per-activation knobs such as `maxTokens` are omitted for the same reason as
|
|
558
|
+
* `outputSchema`: they budget one activation. Cold resume requires the exact
|
|
559
|
+
* live parent for authorization but reconstructs child options only from the
|
|
560
|
+
* durable descriptor, so it neither restores the prior budget nor inherits
|
|
561
|
+
* the parent's current one; the resumed route's defaults apply instead.
|
|
659
562
|
*
|
|
660
|
-
* @module @deepseek-ai/dsh-subagent/
|
|
563
|
+
* @module @deepseek-ai/dsh-subagent/descriptor
|
|
661
564
|
*/
|
|
662
|
-
/** Delegate Queue and Steer to one live Agent until its Activation starts closing. */
|
|
663
|
-
var SubagentInbox = class {
|
|
664
|
-
agent;
|
|
665
|
-
closingPromise;
|
|
666
|
-
/**
|
|
667
|
-
* Wrap one live continuable Agent.
|
|
668
|
-
* @param agent - the Agent whose inbox receives accepted deliveries.
|
|
669
|
-
*/
|
|
670
|
-
constructor(agent) {
|
|
671
|
-
this.agent = agent;
|
|
672
|
-
}
|
|
673
|
-
/**
|
|
674
|
-
* Read the Activation's close transaction.
|
|
675
|
-
* @returns the memoized transaction, or `undefined` while delivery remains open.
|
|
676
|
-
*/
|
|
677
|
-
get closing() {
|
|
678
|
-
return this.closingPromise;
|
|
679
|
-
}
|
|
680
|
-
/**
|
|
681
|
-
* Read whether the underlying Agent still has accepted work to claim.
|
|
682
|
-
* @returns whether either Agent inbox destination is non-empty.
|
|
683
|
-
*/
|
|
684
|
-
get hasPending() {
|
|
685
|
-
return this.agent.inbox.nextTurn.length > 0 || this.agent.inbox.nextStep.length > 0;
|
|
686
|
-
}
|
|
687
|
-
/**
|
|
688
|
-
* Submit through the Agent only while its Activation remains resident.
|
|
689
|
-
* @param message - the accepted input to submit.
|
|
690
|
-
* @param delivery - whether to queue a distinct turn or steer the nearest step.
|
|
691
|
-
*/
|
|
692
|
-
deliver(message, delivery) {
|
|
693
|
-
if (this.closingPromise !== void 0) throw new SubagentError(`subagent "${this.agent.id}" activation is being disposed; the message was not accepted`, "ACTIVATION_CLOSING");
|
|
694
|
-
if (delivery === "steer") this.agent.steer(message);
|
|
695
|
-
else this.agent.followup(message);
|
|
696
|
-
}
|
|
697
|
-
/**
|
|
698
|
-
* Close delivery synchronously and share one asynchronous release.
|
|
699
|
-
* @param release - the one release operation to start after closing admission.
|
|
700
|
-
* @returns the memoized release transaction.
|
|
701
|
-
*/
|
|
702
|
-
close(release) {
|
|
703
|
-
const existing = this.closingPromise;
|
|
704
|
-
if (existing !== void 0) return existing;
|
|
705
|
-
const completion = Promise.withResolvers();
|
|
706
|
-
this.closingPromise = completion.promise;
|
|
707
|
-
release().then(completion.resolve, completion.reject);
|
|
708
|
-
return completion.promise;
|
|
709
|
-
}
|
|
710
|
-
};
|
|
711
|
-
//#endregion
|
|
712
|
-
//#region lib/types/continuation-activation.js
|
|
713
565
|
/**
|
|
714
|
-
*
|
|
715
|
-
*
|
|
716
|
-
*
|
|
717
|
-
*
|
|
718
|
-
* every mutable residency decision to this registry, so delivery and teardown
|
|
719
|
-
* share one child lock and one Activation map.
|
|
720
|
-
*
|
|
721
|
-
* @module @deepseek-ai/dsh-subagent/continuation-activation
|
|
566
|
+
* The current descriptor format version, stamped into every appended
|
|
567
|
+
* `subagent/descriptor` event and required verbatim by {@link foldSubagentDescriptor}.
|
|
568
|
+
* Supporting another composition input is a deliberate version change, never
|
|
569
|
+
* an implicit extra field.
|
|
722
570
|
*/
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
}
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
}.bind(this), "subagents.continuations()");
|
|
796
|
-
}
|
|
797
|
-
/**
|
|
798
|
-
* Return the live Activation for a durable child id, if resident.
|
|
799
|
-
* @param childId - durable child session id to look up.
|
|
800
|
-
* @returns the process-local Activation, or `undefined` when it is not resident.
|
|
801
|
-
*/
|
|
802
|
-
get(childId) {
|
|
803
|
-
return this.resident.get(childId);
|
|
804
|
-
}
|
|
805
|
-
/**
|
|
806
|
-
* Reject one child identity already owned by a live Agent or Session.
|
|
807
|
-
* @param childId - proposed durable child session id.
|
|
808
|
-
*/
|
|
809
|
-
assertChildIdAvailable(childId) {
|
|
810
|
-
if (this.ctx.agents.get(childId) !== void 0 || this.ctx.get("sessions")?.get(childId) !== void 0) throw new SubagentError(`subagent "${childId}" already exists`, "DUPLICATE_CHILD");
|
|
811
|
-
}
|
|
812
|
-
/**
|
|
813
|
-
* Pre-register `childId` in a continuation-managed parent's owned set so the
|
|
814
|
-
* parent cannot settle while a caller is still establishing or resuming that
|
|
815
|
-
* child. Returns a releaser for the failure path; it removes only a hold
|
|
816
|
-
* this call added, and leaves ownership in place once a live Activation for
|
|
817
|
-
* the child exists.
|
|
818
|
-
* @param parent - the live direct parent the operation is admitted under.
|
|
819
|
-
* @param childId - the durable child the operation addresses.
|
|
820
|
-
* @returns the failure-path releaser; a no-op when nothing was added.
|
|
821
|
-
*/
|
|
822
|
-
holdOwnership(parent, childId) {
|
|
823
|
-
const parentActivation = this.resident.get(parent.id);
|
|
824
|
-
if (parentActivation === void 0 || parentActivation.handle.agent !== parent) return () => {};
|
|
825
|
-
if (parentActivation.inbox.closing !== void 0) throw new SubagentError(`subagent parent "${parent.id}" is being disposed; the child was not established`, "ACTIVATION_CLOSING");
|
|
826
|
-
if (parentActivation.ownedChildren.has(childId)) return () => {};
|
|
827
|
-
parentActivation.ownedChildren.add(childId);
|
|
828
|
-
return () => {
|
|
829
|
-
const live = this.resident.get(childId);
|
|
830
|
-
/* v8 ignore next 4 -- reaching this arm needs another delivery to establish the child
|
|
831
|
-
* between this operation's failure and its releaser running, which no test can schedule
|
|
832
|
-
* deterministically: the ownership edge then belongs to that live Activation, so the
|
|
833
|
-
* conservative keep leaves it for finishDisposal's releaseOwnership. */
|
|
834
|
-
if (live !== void 0 && live.inbox.closing === void 0) return;
|
|
835
|
-
if (parentActivation.ownedChildren.delete(childId)) this.wake(parentActivation);
|
|
836
|
-
};
|
|
837
|
-
}
|
|
838
|
-
/**
|
|
839
|
-
* Interrupt one live continuable child's current turn under the supplied authority.
|
|
840
|
-
* @param targetSessionId - the durable child session id to interrupt.
|
|
841
|
-
* @param authority - the human parent address or exact live ancestor Agent.
|
|
842
|
-
*/
|
|
843
|
-
interrupt(targetSessionId, authority) {
|
|
844
|
-
if (authority.kind === "ancestor") {
|
|
845
|
-
const caller = authority.agent;
|
|
846
|
-
if (this.ctx.agents.get(caller.id) !== caller) throw new SubagentError(`interrupting "${targetSessionId}" requires the exact live ancestor agent`, "UNAUTHORIZED");
|
|
847
|
-
if (caller.id === targetSessionId) throw new SubagentError(`agent "${caller.id}" cannot interrupt itself`, "UNAUTHORIZED");
|
|
848
|
-
}
|
|
849
|
-
const activation = this.resident.get(targetSessionId);
|
|
850
|
-
if (activation === void 0) return;
|
|
851
|
-
if (authority.kind === "user") {
|
|
852
|
-
if (activation.handle.agent.session.header.parentSession !== authority.parentSessionId) throw new SubagentError(`subagent "${targetSessionId}" belongs to another parent session`, "UNAUTHORIZED");
|
|
853
|
-
} else if (!activation.ancestry.has(authority.agent)) throw new SubagentError(`subagent "${targetSessionId}" is not a live descendant of agent "${authority.agent.id}"`, "UNAUTHORIZED");
|
|
854
|
-
if (activation.inbox.closing !== void 0) return;
|
|
855
|
-
activation.handle.agent.cancel(authority.kind === "user" ? { kind: "user" } : { kind: "parent" }, { keepInbox: true });
|
|
856
|
-
}
|
|
857
|
-
/**
|
|
858
|
-
* Send through a receiving parent's Activation inbox when it has one.
|
|
859
|
-
* @param parent - exact live Agent receiving the message.
|
|
860
|
-
* @param message - durable user message to deliver.
|
|
861
|
-
* @param delivery - receiving inbox destination.
|
|
862
|
-
*/
|
|
863
|
-
sendWaking(parent, message, delivery) {
|
|
864
|
-
const parentActivation = this.resident.get(parent.id);
|
|
865
|
-
if (parentActivation !== void 0 && parentActivation.handle.agent === parent) {
|
|
866
|
-
try {
|
|
867
|
-
parentActivation.inbox.deliver(message, delivery);
|
|
868
|
-
} finally {
|
|
869
|
-
this.wake(parentActivation);
|
|
870
|
-
}
|
|
871
|
-
return;
|
|
872
|
-
}
|
|
873
|
-
if (delivery === "steer") parent.steer(message);
|
|
874
|
-
else parent.followup(message);
|
|
875
|
-
}
|
|
876
|
-
/**
|
|
877
|
-
* Close admission, await every already-admitted materialization through
|
|
878
|
-
* publication or rollback, then dispose the stable live Activation graph
|
|
879
|
-
* child-first.
|
|
880
|
-
*/
|
|
881
|
-
async drain() {
|
|
882
|
-
this.draining = true;
|
|
883
|
-
await Promise.all([...this.materializations].map((materialization) => materialization.settled));
|
|
884
|
-
const owned = /* @__PURE__ */ new Set();
|
|
885
|
-
for (const activation of this.resident.values()) for (const child of activation.ownedChildren) owned.add(child);
|
|
886
|
-
const roots = [...this.resident.values()].filter((activation) => !owned.has(activation.childId));
|
|
887
|
-
await this.disposeRoots(roots, "activation(s)");
|
|
888
|
-
}
|
|
889
|
-
/**
|
|
890
|
-
* Stop only the continuable descendants of exact live host-owned parents.
|
|
891
|
-
* @param parents - exact live roots whose continuable descendants must stop.
|
|
892
|
-
*/
|
|
893
|
-
async drainDescendants(parents) {
|
|
894
|
-
const roots = new Set(parents.filter((parent) => this.ctx.agents.get(parent.id) === parent));
|
|
895
|
-
if (roots.size === 0) return;
|
|
896
|
-
for (const root of roots) this.closingMembers(root).add(root);
|
|
897
|
-
const targets = [];
|
|
898
|
-
for (const activation of this.resident.values()) {
|
|
899
|
-
const lineage = this.liveLineage(activation.handle.agent);
|
|
900
|
-
const owners = [...roots].filter((root) => activation.handle.agent !== root && activation.ancestry.has(root));
|
|
901
|
-
if (owners.length === 0) continue;
|
|
902
|
-
targets.push(activation);
|
|
903
|
-
for (const owner of owners) {
|
|
904
|
-
const members = this.closingMembers(owner);
|
|
905
|
-
members.add(activation.handle.agent);
|
|
906
|
-
for (const agent of lineage) members.add(agent);
|
|
907
|
-
}
|
|
908
|
-
}
|
|
909
|
-
const materializations = [...this.materializations].filter((materialization) => {
|
|
910
|
-
const owners = [...roots].filter((root) => materialization.lineage.includes(root));
|
|
911
|
-
for (const owner of owners) {
|
|
912
|
-
const members = this.closingMembers(owner);
|
|
913
|
-
for (const agent of materialization.lineage) members.add(agent);
|
|
914
|
-
}
|
|
915
|
-
return owners.length > 0;
|
|
916
|
-
});
|
|
917
|
-
const ownedTargets = /* @__PURE__ */ new Set();
|
|
918
|
-
for (const activation of targets) for (const child of activation.ownedChildren) ownedTargets.add(child);
|
|
919
|
-
const targetRoots = targets.filter((activation) => !ownedTargets.has(activation.childId));
|
|
920
|
-
for (const activation of targets) this.dispose(activation).catch(() => void 0);
|
|
921
|
-
await Promise.all(materializations.map((materialization) => materialization.settled));
|
|
922
|
-
await this.disposeRoots(targetRoots, "scoped activation(s)");
|
|
923
|
-
}
|
|
924
|
-
/**
|
|
925
|
-
* Release selected resident direct children of one exact live parent.
|
|
926
|
-
* @param parent - exact live direct parent authorizing the selected release.
|
|
927
|
-
* @param childIds - durable direct-child ids to release when resident.
|
|
928
|
-
*/
|
|
929
|
-
async drainChildren(parent, childIds) {
|
|
930
|
-
if (this.ctx.agents.get(parent.id) !== parent) throw new SubagentError("selected child teardown requires the exact live parent agent", "UNAUTHORIZED");
|
|
931
|
-
const targets = [];
|
|
932
|
-
for (const childId of new Set(childIds)) {
|
|
933
|
-
const activation = this.resident.get(childId);
|
|
934
|
-
if (activation === void 0) continue;
|
|
935
|
-
if (activation.parentSession !== parent.id || !activation.ancestry.has(parent)) throw new SubagentError(`subagent "${childId}" is not a direct child of agent "${parent.id}"`, "UNAUTHORIZED");
|
|
936
|
-
targets.push(activation);
|
|
937
|
-
}
|
|
938
|
-
for (const activation of targets) this.dispose(activation).catch(() => void 0);
|
|
939
|
-
await this.disposeRoots(targets, "selected activation(s)");
|
|
940
|
-
}
|
|
941
|
-
/**
|
|
942
|
-
* Reject new admission once the registry or this exact parent tree began draining.
|
|
943
|
-
* @param agent - exact live Agent whose lineage determines admission.
|
|
944
|
-
*/
|
|
945
|
-
assertAdmitting(agent) {
|
|
946
|
-
const closing = this.closingTeardownFor(agent);
|
|
947
|
-
if (closing === void 0) return;
|
|
948
|
-
throw new SubagentError(closing === "manager" ? "continuable subagents are draining; the operation was not admitted" : `continuable subagents below parent "${closing.id}" are draining; the operation was not admitted`, "DRAINING");
|
|
949
|
-
}
|
|
950
|
-
/**
|
|
951
|
-
* Authorize one operation against the durable direct-parent lineage.
|
|
952
|
-
* @param parent - exact live Agent claiming direct-parent authority.
|
|
953
|
-
* @param childId - durable child session id addressed by the operation.
|
|
954
|
-
* @param parentSession - durable direct-parent id recorded by the child.
|
|
955
|
-
*/
|
|
956
|
-
authorizeLineage(parent, childId, parentSession) {
|
|
957
|
-
if (this.ctx.agents.get(parent.id) !== parent) throw new SubagentError(`subagent "${childId}" delivery requires the exact live parent agent`, "UNAUTHORIZED");
|
|
958
|
-
if (parentSession !== parent.id) throw new SubagentError(`subagent "${childId}" belongs to another parent session`, "UNAUTHORIZED");
|
|
959
|
-
}
|
|
960
|
-
/**
|
|
961
|
-
* Create or resume one child Agent and publish its Activation.
|
|
962
|
-
* @param inputs - reconstruction and admission inputs for the residency epoch.
|
|
963
|
-
* @returns the published process-local Activation.
|
|
964
|
-
*/
|
|
965
|
-
materialize(inputs) {
|
|
966
|
-
this.assertAdmitting(inputs.parent);
|
|
967
|
-
inputs.signal.throwIfAborted();
|
|
968
|
-
const lineage = this.liveLineage(inputs.parent);
|
|
969
|
-
const pool = this.resident.get(inputs.parent.id)?.pool ?? this.rootPool(inputs.parent);
|
|
970
|
-
const releaseSlot = pool.reserve(this.maxActiveSubagents());
|
|
971
|
-
const settled = Promise.withResolvers();
|
|
972
|
-
const materialization = {
|
|
973
|
-
lineage,
|
|
974
|
-
settled: settled.promise
|
|
975
|
-
};
|
|
976
|
-
this.materializations.add(materialization);
|
|
977
|
-
return this.materializeTracked(inputs, lineage, pool, releaseSlot).catch((error) => {
|
|
978
|
-
releaseSlot();
|
|
979
|
-
throw error;
|
|
980
|
-
}).finally(() => {
|
|
981
|
-
this.materializations.delete(materialization);
|
|
982
|
-
settled.resolve();
|
|
983
|
-
});
|
|
984
|
-
}
|
|
985
|
-
/**
|
|
986
|
-
* Cross the final admission cutoff and submit without yielding.
|
|
987
|
-
* @param activation - the exact resident child receiving the message.
|
|
988
|
-
* @param message - the already-built durable user message.
|
|
989
|
-
* @param delivery - the Agent inbox destination.
|
|
990
|
-
* @param parent - exact live direct parent authorizing admission.
|
|
991
|
-
* @param signal - caller cancellation before inbox acceptance.
|
|
992
|
-
* @returns the accepted durable message id.
|
|
993
|
-
*/
|
|
994
|
-
submitAdmitted(activation, message, delivery, parent, signal) {
|
|
995
|
-
signal.throwIfAborted();
|
|
996
|
-
this.assertAdmitting(parent);
|
|
997
|
-
this.authorizeLineage(parent, activation.childId, activation.handle.agent.session.header.parentSession);
|
|
998
|
-
this.acquireOwnership(parent, activation.childId);
|
|
999
|
-
try {
|
|
1000
|
-
activation.inbox.deliver(message, delivery);
|
|
1001
|
-
} finally {
|
|
1002
|
-
this.wake(activation);
|
|
1003
|
-
}
|
|
1004
|
-
return message.id;
|
|
1005
|
-
}
|
|
1006
|
-
/**
|
|
1007
|
-
* Stop and release one Activation through its memoized close transaction.
|
|
1008
|
-
* @param activation - exact residency epoch to close.
|
|
1009
|
-
* @param finalStateFlushed - whether natural settlement already flushed final state.
|
|
1010
|
-
* @returns the shared close transaction.
|
|
1011
|
-
*/
|
|
1012
|
-
dispose(activation, finalStateFlushed = false) {
|
|
1013
|
-
return activation.inbox.close(() => this.finishDisposal(activation, finalStateFlushed));
|
|
1014
|
-
}
|
|
1015
|
-
/** Dispose independent roots and report every branch failure after all settle. */
|
|
1016
|
-
async disposeRoots(roots, failureSubject) {
|
|
1017
|
-
const reasons = (await Promise.all(roots.map(async (activation) => {
|
|
1018
|
-
try {
|
|
1019
|
-
await this.dispose(activation);
|
|
1020
|
-
return;
|
|
1021
|
-
} catch (error) {
|
|
1022
|
-
return error;
|
|
1023
|
-
}
|
|
1024
|
-
}))).filter((failure) => failure !== void 0);
|
|
1025
|
-
if (reasons.length > 0) throw new SubagentError(`continuable subagent teardown failed for ${reasons.length} ${failureSubject}: ` + reasons.map((reason) => errorChain(reason)).join("; "), "ACTIVATION_TEARDOWN_FAILED");
|
|
1026
|
-
}
|
|
1027
|
-
/** Return the retained member set for one exact scoped-teardown root. */
|
|
1028
|
-
closingMembers(root) {
|
|
1029
|
-
const existing = this.closingScopes.get(root);
|
|
1030
|
-
if (existing !== void 0) return existing;
|
|
1031
|
-
const members = /* @__PURE__ */ new Set();
|
|
1032
|
-
this.closingScopes.set(root, members);
|
|
1033
|
-
return members;
|
|
1034
|
-
}
|
|
1035
|
-
/** Return the exact currently resolvable ancestry from `agent` upward. */
|
|
1036
|
-
liveLineage(agent) {
|
|
1037
|
-
const lineage = [agent];
|
|
1038
|
-
const seen = new Set([agent.id]);
|
|
1039
|
-
let parentSession = agent.session.header.parentSession;
|
|
1040
|
-
while (parentSession !== void 0) {
|
|
1041
|
-
const parent = this.ctx.agents.get(parentSession);
|
|
1042
|
-
if (parent === void 0 || seen.has(parent.id)) break;
|
|
1043
|
-
lineage.push(parent);
|
|
1044
|
-
seen.add(parent.id);
|
|
1045
|
-
parentSession = parent.session.header.parentSession;
|
|
1046
|
-
}
|
|
1047
|
-
return lineage;
|
|
1048
|
-
}
|
|
1049
|
-
/** Return the teardown that closed continuable admission for this agent's lineage. */
|
|
1050
|
-
closingTeardownFor(agent) {
|
|
1051
|
-
if (this.draining) return "manager";
|
|
1052
|
-
const lineage = this.liveLineage(agent);
|
|
1053
|
-
for (const [root, members] of this.closingScopes) if (members.has(agent) || lineage.includes(root)) return root;
|
|
1054
|
-
}
|
|
1055
|
-
/** Resolve a root's pool once; descendants inherit their resident parent's pool directly. */
|
|
1056
|
-
rootPool(parent) {
|
|
1057
|
-
let pool = this.rootPools.get(parent);
|
|
1058
|
-
if (pool === void 0) {
|
|
1059
|
-
pool = new ActivationPool();
|
|
1060
|
-
this.rootPools.set(parent, pool);
|
|
1061
|
-
}
|
|
1062
|
-
return pool;
|
|
1063
|
-
}
|
|
1064
|
-
/** Perform one tracked materialization through publication or rollback. */
|
|
1065
|
-
async materializeTracked(inputs, parentLineage, pool, releaseSlot) {
|
|
1066
|
-
const { childId, provider, parent, create } = inputs;
|
|
1067
|
-
inputs.signal.throwIfAborted();
|
|
1068
|
-
const setup = (childCtx, child) => {
|
|
1069
|
-
if (create !== void 0) {
|
|
1070
|
-
child.session.append("subagent/descriptor", create.descriptor);
|
|
1071
|
-
appendDelegatedPolicyOverrides(child.session, create.delegatedPolicies);
|
|
1072
|
-
}
|
|
1073
|
-
applyChildComposition(childCtx, parent, inputs.composition);
|
|
1074
|
-
};
|
|
1075
|
-
const observer = this.observeActivation(provider, childId, parent);
|
|
1076
|
-
const handle = create === void 0 ? await this.ownerCtx.agents.resume({
|
|
1077
|
-
resumeSessionId: childId,
|
|
1078
|
-
parentAgent: parent,
|
|
1079
|
-
agentOptions: inputs.agentOptions,
|
|
1080
|
-
signal: inputs.signal,
|
|
1081
|
-
setup
|
|
1082
|
-
}) : await this.ownerCtx.agents.create({
|
|
1083
|
-
sessionId: childId,
|
|
1084
|
-
parentAgent: parent,
|
|
1085
|
-
meta: create.meta,
|
|
1086
|
-
...create.seed === void 0 ? {} : { seed: create.seed },
|
|
1087
|
-
inheritedEventCount: create.inheritedEventCount,
|
|
1088
|
-
agentOptions: inputs.agentOptions,
|
|
1089
|
-
signal: inputs.signal,
|
|
1090
|
-
setup
|
|
1091
|
-
});
|
|
1092
|
-
const activation = {
|
|
1093
|
-
pool,
|
|
1094
|
-
releaseSlot,
|
|
1095
|
-
childId,
|
|
1096
|
-
parentSession: parent.id,
|
|
1097
|
-
provider,
|
|
1098
|
-
handle,
|
|
1099
|
-
inbox: new SubagentInbox(handle.agent),
|
|
1100
|
-
ancestry: new WeakSet([handle.agent, ...parentLineage]),
|
|
1101
|
-
ownedChildren: /* @__PURE__ */ new Set(),
|
|
1102
|
-
observer,
|
|
1103
|
-
announced: false,
|
|
1104
|
-
poke: Promise.withResolvers()
|
|
1105
|
-
};
|
|
1106
|
-
this.resident.set(childId, activation);
|
|
1107
|
-
try {
|
|
1108
|
-
inputs.signal.throwIfAborted();
|
|
1109
|
-
this.assertAdmitting(parent);
|
|
1110
|
-
this.acquireOwnership(parent, childId);
|
|
1111
|
-
const wakeOnInboxRemoval = () => {
|
|
1112
|
-
this.wake(activation);
|
|
1113
|
-
};
|
|
1114
|
-
handle.agent.ctx.on("agent/inbox/claimed", wakeOnInboxRemoval);
|
|
1115
|
-
handle.agent.ctx.on("agent/inbox/discarded", wakeOnInboxRemoval);
|
|
1116
|
-
observer.start(handle.agent);
|
|
1117
|
-
} catch (error) {
|
|
1118
|
-
/* v8 ignore next -- rollback failure must not mask the admission failure
|
|
1119
|
-
* that prevented this operation from returning an accepted message id. */
|
|
1120
|
-
await this.rollbackUnpublished(activation).catch(() => void 0);
|
|
1121
|
-
throw error;
|
|
1122
|
-
}
|
|
1123
|
-
this.watchSettlement(activation);
|
|
1124
|
-
return activation;
|
|
1125
|
-
}
|
|
1126
|
-
/** Release an Activation whose start edge was not published. */
|
|
1127
|
-
rollbackUnpublished(activation) {
|
|
1128
|
-
return activation.inbox.close(async () => {
|
|
1129
|
-
try {
|
|
1130
|
-
await activation.handle.dispose();
|
|
1131
|
-
} finally {
|
|
1132
|
-
this.resident.delete(activation.childId);
|
|
1133
|
-
activation.releaseSlot();
|
|
1134
|
-
this.releaseOwnership(activation.childId);
|
|
1135
|
-
}
|
|
1136
|
-
});
|
|
1137
|
-
}
|
|
1138
|
-
/** Register the child in a continuation-managed parent's owned set. */
|
|
1139
|
-
acquireOwnership(parent, childId) {
|
|
1140
|
-
const parentActivation = this.resident.get(parent.id);
|
|
1141
|
-
if (parentActivation === void 0) return;
|
|
1142
|
-
if (parentActivation.inbox.closing !== void 0) throw new SubagentError(`subagent parent "${parent.id}" is being disposed; the child was not established`, "ACTIVATION_CLOSING");
|
|
1143
|
-
parentActivation.ownedChildren.add(childId);
|
|
1144
|
-
}
|
|
1145
|
-
/** Remove one child from its live owner's set and let that owner re-check settlement. */
|
|
1146
|
-
releaseOwnership(childId) {
|
|
1147
|
-
for (const candidate of this.resident.values()) if (candidate.ownedChildren.delete(childId)) this.wake(candidate);
|
|
1148
|
-
}
|
|
1149
|
-
/** Let a settlement watcher re-check residency after relevant state changes. */
|
|
1150
|
-
wake(activation) {
|
|
1151
|
-
activation.poke.resolve();
|
|
1152
|
-
activation.poke = Promise.withResolvers();
|
|
1153
|
-
}
|
|
1154
|
-
/** Follow one Activation to natural settlement. */
|
|
1155
|
-
watchSettlement(activation) {
|
|
1156
|
-
(async () => {
|
|
1157
|
-
while (true) {
|
|
1158
|
-
const idleObservation = activation.poke;
|
|
1159
|
-
await activation.handle.agent.whenIdle();
|
|
1160
|
-
if (activation.inbox.closing !== void 0) return;
|
|
1161
|
-
const readiness = await this.locks.run(activation.childId, () => Promise.resolve(this.settlementState(activation, idleObservation)));
|
|
1162
|
-
if (readiness === "closed") return;
|
|
1163
|
-
if (readiness === "retry") continue;
|
|
1164
|
-
if (readiness === "wait") {
|
|
1165
|
-
await idleObservation.promise;
|
|
1166
|
-
continue;
|
|
1167
|
-
}
|
|
1168
|
-
const finalSeq = activation.handle.agent.session.seq;
|
|
1169
|
-
await this.flushFinalState(activation);
|
|
1170
|
-
const attempt = await this.locks.run(activation.childId, () => {
|
|
1171
|
-
const state = this.settlementState(activation, idleObservation);
|
|
1172
|
-
if (state !== "ready") return Promise.resolve(state);
|
|
1173
|
-
if (activation.handle.agent.session.seq !== finalSeq) return Promise.resolve("retry");
|
|
1174
|
-
let done;
|
|
1175
|
-
try {
|
|
1176
|
-
activation.handle.agent.runMaintenance(() => {
|
|
1177
|
-
done = this.dispose(activation, true);
|
|
1178
|
-
return Promise.resolve();
|
|
1179
|
-
});
|
|
1180
|
-
} catch {
|
|
1181
|
-
return Promise.resolve("retry");
|
|
1182
|
-
}
|
|
1183
|
-
return Promise.resolve({ done });
|
|
1184
|
-
});
|
|
1185
|
-
if (attempt === "closed") return;
|
|
1186
|
-
if (attempt === "retry") continue;
|
|
1187
|
-
if (attempt === "wait") {
|
|
1188
|
-
await idleObservation.promise;
|
|
1189
|
-
continue;
|
|
1190
|
-
}
|
|
1191
|
-
try {
|
|
1192
|
-
await attempt.done;
|
|
1193
|
-
} catch (error) {
|
|
1194
|
-
this.ctx.logger.warn(`subagent "${activation.childId}" activation teardown failed: ${errorChain(error)}`);
|
|
1195
|
-
}
|
|
1196
|
-
return;
|
|
1197
|
-
}
|
|
1198
|
-
})();
|
|
1199
|
-
}
|
|
1200
|
-
/** Classify one Inbox and owned-child observation without reading Agent execution state. */
|
|
1201
|
-
settlementState(activation, observation) {
|
|
1202
|
-
if (activation.inbox.closing !== void 0) return "closed";
|
|
1203
|
-
if (activation.poke !== observation) return "retry";
|
|
1204
|
-
if (activation.inbox.hasPending || activation.ownedChildren.size > 0) return "wait";
|
|
1205
|
-
return "ready";
|
|
1206
|
-
}
|
|
1207
|
-
/** Propagate stop synchronously, then finish the child-first release. */
|
|
1208
|
-
async finishDisposal(activation, finalStateFlushed) {
|
|
1209
|
-
this.wake(activation);
|
|
1210
|
-
const { childId } = activation;
|
|
1211
|
-
const failures = [];
|
|
1212
|
-
if (finalStateFlushed) try {
|
|
1213
|
-
activation.observer.capture(activation.handle.agent);
|
|
1214
|
-
} catch (error) {
|
|
1215
|
-
failures.push(new SubagentError(`subagent "${childId}" activation teardown failed: ${errorChain(error)}`, "ACTIVATION_TEARDOWN_FAILED", { cause: error }));
|
|
1216
|
-
}
|
|
1217
|
-
else {
|
|
1218
|
-
activation.handle.agent.cancel({ kind: "parent" });
|
|
1219
|
-
const idle = activation.handle.agent.whenIdle();
|
|
1220
|
-
const childDisposals = [...activation.ownedChildren].map((child) => this.resident.get(child)).filter((child) => child !== void 0).map((child) => this.dispose(child));
|
|
1221
|
-
try {
|
|
1222
|
-
const reasons = (await Promise.all(childDisposals.map(async (disposal) => {
|
|
1223
|
-
try {
|
|
1224
|
-
await disposal;
|
|
1225
|
-
return;
|
|
1226
|
-
} catch (error) {
|
|
1227
|
-
return error;
|
|
1228
|
-
}
|
|
1229
|
-
}))).filter((reason) => reason !== void 0);
|
|
1230
|
-
if (reasons.length > 0) failures.push(new SubagentError(`subagent "${childId}" child teardown failed: ${reasons.map((reason) => errorChain(reason)).join("; ")}`, "ACTIVATION_TEARDOWN_FAILED"));
|
|
1231
|
-
await idle;
|
|
1232
|
-
await this.flushFinalState(activation);
|
|
1233
|
-
activation.observer.capture(activation.handle.agent);
|
|
1234
|
-
} catch (error) {
|
|
1235
|
-
failures.push(new SubagentError(`subagent "${childId}" activation teardown failed: ${errorChain(error)}`, "ACTIVATION_TEARDOWN_FAILED", { cause: error }));
|
|
1236
|
-
}
|
|
1237
|
-
}
|
|
1238
|
-
try {
|
|
1239
|
-
await activation.handle.dispose();
|
|
1240
|
-
} catch (error) {
|
|
1241
|
-
failures.push(new SubagentError(`subagent "${childId}" activation handle disposal failed: ${errorChain(error)}`, "ACTIVATION_TEARDOWN_FAILED", { cause: error }));
|
|
1242
|
-
}
|
|
1243
|
-
let failure;
|
|
1244
|
-
if (failures.length === 1) failure = failures[0];
|
|
1245
|
-
else if (failures.length > 1) failure = new SubagentError(`subagent "${childId}" activation teardown failed at ${failures.length} boundaries: ` + failures.map((item) => errorChain(item)).join("; "), "ACTIVATION_TEARDOWN_FAILED", { cause: new AggregateError(failures) });
|
|
1246
|
-
this.resident.delete(childId);
|
|
1247
|
-
activation.releaseSlot();
|
|
1248
|
-
this.notifySettlement(activation, activation.observer.terminal(failure));
|
|
1249
|
-
this.releaseOwnership(childId);
|
|
1250
|
-
activation.observer.settle(failure);
|
|
1251
|
-
if (failure !== void 0) throw failure;
|
|
1252
|
-
}
|
|
1253
|
-
/** Tell the durable direct parent how this Activation ended. */
|
|
1254
|
-
notifySettlement(activation, terminal) {
|
|
1255
|
-
if (!activation.announced) return;
|
|
1256
|
-
try {
|
|
1257
|
-
const parent = this.ctx.agents.get(activation.parentSession);
|
|
1258
|
-
if (parent === void 0) return;
|
|
1259
|
-
const message = createSettlementMessage(activation.childId, terminal);
|
|
1260
|
-
if (this.closingTeardownFor(parent) !== void 0) {
|
|
1261
|
-
parent.inject(message);
|
|
1262
|
-
return;
|
|
1263
|
-
}
|
|
1264
|
-
this.sendWaking(parent, message, parent.status === "idle" ? "queue" : "steer");
|
|
1265
|
-
} catch (error) {
|
|
1266
|
-
this.ctx.logger.warn(`subagent "${activation.childId}" settlement notice was not delivered to its parent: ` + errorChain(error));
|
|
1267
|
-
}
|
|
1268
|
-
}
|
|
1269
|
-
/** Request a best-effort final session flush before closing natural-settlement admission. */
|
|
1270
|
-
async flushFinalState(activation) {
|
|
1271
|
-
const child = activation.handle.agent;
|
|
1272
|
-
try {
|
|
1273
|
-
await child.ctx.sessions.flush(child.session);
|
|
1274
|
-
} catch (error) {
|
|
1275
|
-
this.ctx.logger.warn(`subagent "${activation.childId}" best-effort final session flush failed; the persisted state may be unavailable or stale on resume: ${errorChain(error)}`);
|
|
1276
|
-
}
|
|
1277
|
-
}
|
|
1278
|
-
};
|
|
1279
|
-
//#endregion
|
|
1280
|
-
//#region lib/types/descriptor.js
|
|
1281
|
-
/**
|
|
1282
|
-
* The durable subagent-child descriptor: the versioned, model-hidden
|
|
1283
|
-
* `subagent/descriptor` session event that identifies every session-backed
|
|
1284
|
-
* subagent and records whether it is one-shot or continuable. Continuable
|
|
1285
|
-
* descriptors additionally preserve the declared composition required for
|
|
1286
|
-
* cold resume. Providers append it turn-enclosed in the child's initial turn.
|
|
1287
|
-
*
|
|
1288
|
-
* The descriptor deliberately snapshots explicit fields rather than the
|
|
1289
|
-
* merge-extensible `AgentOptions` object: an unrelated extension value cannot
|
|
1290
|
-
* make continuation fail merely because it is not JSON, and later composition
|
|
1291
|
-
* inputs require a deliberate {@link SUBAGENT_DESCRIPTOR_VERSION} change. It
|
|
1292
|
-
* omits `subagentDepth` — cold resume trusts the persisted header's
|
|
1293
|
-
* `delegationDepth` as the monotone floor — and `outputSchema`, which belongs
|
|
1294
|
-
* to one activation's result contract rather than durable child composition.
|
|
1295
|
-
* Per-activation knobs such as `maxTokens` are omitted for the same reason as
|
|
1296
|
-
* `outputSchema`: they budget one activation. Cold resume requires the exact
|
|
1297
|
-
* live parent for authorization but reconstructs child options only from the
|
|
1298
|
-
* durable descriptor, so it neither restores the prior budget nor inherits
|
|
1299
|
-
* the parent's current one; the resumed route's defaults apply instead.
|
|
1300
|
-
*
|
|
1301
|
-
* @module @deepseek-ai/dsh-subagent/descriptor
|
|
1302
|
-
*/
|
|
1303
|
-
/**
|
|
1304
|
-
* The current descriptor format version, stamped into every appended
|
|
1305
|
-
* `subagent/descriptor` event and required verbatim by {@link foldSubagentDescriptor}.
|
|
1306
|
-
* Supporting another composition input is a deliberate version change, never
|
|
1307
|
-
* an implicit extra field.
|
|
1308
|
-
*/
|
|
1309
|
-
const SUBAGENT_DESCRIPTOR_VERSION = 3;
|
|
1310
|
-
const DESCRIPTOR_BASE_KEYS = [
|
|
1311
|
-
"version",
|
|
1312
|
-
"mode",
|
|
1313
|
-
"provider",
|
|
1314
|
-
"label"
|
|
1315
|
-
];
|
|
1316
|
-
const ONE_SHOT_DESCRIPTOR_KEYS = new Set(DESCRIPTOR_BASE_KEYS);
|
|
1317
|
-
const CONTINUABLE_DESCRIPTOR_KEYS = new Set([
|
|
1318
|
-
...DESCRIPTOR_BASE_KEYS,
|
|
1319
|
-
"agentProvider",
|
|
1320
|
-
"agentModel",
|
|
1321
|
-
"agentReasoningEffort",
|
|
1322
|
-
"persona",
|
|
1323
|
-
"toolFilter"
|
|
1324
|
-
]);
|
|
1325
|
-
const TOOL_FILTER_KEYS = new Set(["allow", "deny"]);
|
|
1326
|
-
/** Whether a persisted JSON value is an object record. */
|
|
1327
|
-
function isRecord(value) {
|
|
1328
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1329
|
-
}
|
|
1330
|
-
/** Reject fields outside one versioned record's declared schema. */
|
|
1331
|
-
function assertKnownKeys(value, keys, path) {
|
|
1332
|
-
const unknown = Object.keys(value).find((key) => !keys.has(key));
|
|
1333
|
-
if (unknown !== void 0) throw new Error(`persisted subagent descriptor ${path} has unknown field "${unknown}"`);
|
|
1334
|
-
}
|
|
1335
|
-
/** Read one optional string field from a persisted descriptor record. */
|
|
1336
|
-
function optionalString(value, key) {
|
|
1337
|
-
if (!Object.hasOwn(value, key)) return void 0;
|
|
1338
|
-
const field = value[key];
|
|
1339
|
-
if (typeof field !== "string") throw new Error(`persisted subagent descriptor ${key} must be a string`);
|
|
1340
|
-
return field;
|
|
1341
|
-
}
|
|
1342
|
-
/** Read one optional string-array field from a persisted tool restriction. */
|
|
1343
|
-
function optionalStringArray(value, key) {
|
|
1344
|
-
if (!Object.hasOwn(value, key)) return void 0;
|
|
1345
|
-
const field = value[key];
|
|
1346
|
-
if (!Array.isArray(field)) throw new Error(`persisted subagent descriptor toolFilter.${key} must be an array of strings`);
|
|
1347
|
-
const items = field;
|
|
1348
|
-
if (items.some((item) => typeof item !== "string")) throw new Error(`persisted subagent descriptor toolFilter.${key} must be an array of strings`);
|
|
1349
|
-
return items;
|
|
1350
|
-
}
|
|
1351
|
-
/** Validate and reconstruct a persisted tool restriction. */
|
|
1352
|
-
function parseToolFilter(value) {
|
|
1353
|
-
if (!isRecord(value)) throw new Error("persisted subagent descriptor toolFilter must be an object");
|
|
1354
|
-
assertKnownKeys(value, TOOL_FILTER_KEYS, "toolFilter");
|
|
1355
|
-
const allow = optionalStringArray(value, "allow");
|
|
1356
|
-
const deny = optionalStringArray(value, "deny");
|
|
1357
|
-
if (allow === void 0 && deny === void 0) throw new Error("persisted subagent descriptor toolFilter must declare allow and/or deny");
|
|
1358
|
-
return {
|
|
1359
|
-
...allow !== void 0 ? { allow } : {},
|
|
1360
|
-
...deny !== void 0 ? { deny } : {}
|
|
1361
|
-
};
|
|
1362
|
-
}
|
|
1363
|
-
/** Validate one persisted descriptor payload for the current runtime. */
|
|
1364
|
-
function parseSubagentDescriptor(value) {
|
|
1365
|
-
if (!isRecord(value)) throw new Error("persisted subagent descriptor payload must be an object");
|
|
1366
|
-
const version = value["version"];
|
|
1367
|
-
if (typeof version !== "number") throw new Error("persisted subagent descriptor version must be a number");
|
|
1368
|
-
if (version !== 3) return void 0;
|
|
1369
|
-
const mode = value["mode"];
|
|
1370
|
-
if (mode !== "one-shot" && mode !== "continuable") throw new Error("persisted subagent descriptor mode must be \"one-shot\" or \"continuable\"");
|
|
1371
|
-
assertKnownKeys(value, mode === "one-shot" ? ONE_SHOT_DESCRIPTOR_KEYS : CONTINUABLE_DESCRIPTOR_KEYS, "payload");
|
|
1372
|
-
const provider = value["provider"];
|
|
1373
|
-
if (typeof provider !== "string") throw new Error("persisted subagent descriptor provider must be a string");
|
|
1374
|
-
if (mode === "one-shot") {
|
|
1375
|
-
const label = optionalString(value, "label");
|
|
1376
|
-
return {
|
|
1377
|
-
version: 3,
|
|
1378
|
-
mode,
|
|
1379
|
-
provider,
|
|
1380
|
-
...label !== void 0 ? { label } : {}
|
|
571
|
+
const SUBAGENT_DESCRIPTOR_VERSION = 3;
|
|
572
|
+
const DESCRIPTOR_BASE_KEYS = [
|
|
573
|
+
"version",
|
|
574
|
+
"mode",
|
|
575
|
+
"provider",
|
|
576
|
+
"label"
|
|
577
|
+
];
|
|
578
|
+
const ONE_SHOT_DESCRIPTOR_KEYS = new Set(DESCRIPTOR_BASE_KEYS);
|
|
579
|
+
const CONTINUABLE_DESCRIPTOR_KEYS = new Set([
|
|
580
|
+
...DESCRIPTOR_BASE_KEYS,
|
|
581
|
+
"agentProvider",
|
|
582
|
+
"agentModel",
|
|
583
|
+
"agentReasoningEffort",
|
|
584
|
+
"persona",
|
|
585
|
+
"toolFilter"
|
|
586
|
+
]);
|
|
587
|
+
const TOOL_FILTER_KEYS = new Set(["allow", "deny"]);
|
|
588
|
+
/** Whether a persisted JSON value is an object record. */
|
|
589
|
+
function isRecord(value) {
|
|
590
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
591
|
+
}
|
|
592
|
+
/** Reject fields outside one versioned record's declared schema. */
|
|
593
|
+
function assertKnownKeys(value, keys, path) {
|
|
594
|
+
const unknown = Object.keys(value).find((key) => !keys.has(key));
|
|
595
|
+
if (unknown !== void 0) throw new Error(`persisted subagent descriptor ${path} has unknown field "${unknown}"`);
|
|
596
|
+
}
|
|
597
|
+
/** Read one optional string field from a persisted descriptor record. */
|
|
598
|
+
function optionalString(value, key) {
|
|
599
|
+
if (!Object.hasOwn(value, key)) return void 0;
|
|
600
|
+
const field = value[key];
|
|
601
|
+
if (typeof field !== "string") throw new Error(`persisted subagent descriptor ${key} must be a string`);
|
|
602
|
+
return field;
|
|
603
|
+
}
|
|
604
|
+
/** Read one optional string-array field from a persisted tool restriction. */
|
|
605
|
+
function optionalStringArray(value, key) {
|
|
606
|
+
if (!Object.hasOwn(value, key)) return void 0;
|
|
607
|
+
const field = value[key];
|
|
608
|
+
if (!Array.isArray(field)) throw new Error(`persisted subagent descriptor toolFilter.${key} must be an array of strings`);
|
|
609
|
+
const items = field;
|
|
610
|
+
if (items.some((item) => typeof item !== "string")) throw new Error(`persisted subagent descriptor toolFilter.${key} must be an array of strings`);
|
|
611
|
+
return items;
|
|
612
|
+
}
|
|
613
|
+
/** Validate and reconstruct a persisted tool restriction. */
|
|
614
|
+
function parseToolFilter(value) {
|
|
615
|
+
if (!isRecord(value)) throw new Error("persisted subagent descriptor toolFilter must be an object");
|
|
616
|
+
assertKnownKeys(value, TOOL_FILTER_KEYS, "toolFilter");
|
|
617
|
+
const allow = optionalStringArray(value, "allow");
|
|
618
|
+
const deny = optionalStringArray(value, "deny");
|
|
619
|
+
if (allow === void 0 && deny === void 0) throw new Error("persisted subagent descriptor toolFilter must declare allow and/or deny");
|
|
620
|
+
return {
|
|
621
|
+
...allow !== void 0 ? { allow } : {},
|
|
622
|
+
...deny !== void 0 ? { deny } : {}
|
|
623
|
+
};
|
|
624
|
+
}
|
|
625
|
+
/** Validate one persisted descriptor payload for the current runtime. */
|
|
626
|
+
function parseSubagentDescriptor(value) {
|
|
627
|
+
if (!isRecord(value)) throw new Error("persisted subagent descriptor payload must be an object");
|
|
628
|
+
const version = value["version"];
|
|
629
|
+
if (typeof version !== "number") throw new Error("persisted subagent descriptor version must be a number");
|
|
630
|
+
if (version !== 3) return void 0;
|
|
631
|
+
const mode = value["mode"];
|
|
632
|
+
if (mode !== "one-shot" && mode !== "continuable") throw new Error("persisted subagent descriptor mode must be \"one-shot\" or \"continuable\"");
|
|
633
|
+
assertKnownKeys(value, mode === "one-shot" ? ONE_SHOT_DESCRIPTOR_KEYS : CONTINUABLE_DESCRIPTOR_KEYS, "payload");
|
|
634
|
+
const provider = value["provider"];
|
|
635
|
+
if (typeof provider !== "string") throw new Error("persisted subagent descriptor provider must be a string");
|
|
636
|
+
if (mode === "one-shot") {
|
|
637
|
+
const label = optionalString(value, "label");
|
|
638
|
+
return {
|
|
639
|
+
version: 3,
|
|
640
|
+
mode,
|
|
641
|
+
provider,
|
|
642
|
+
...label !== void 0 ? { label } : {}
|
|
1381
643
|
};
|
|
1382
644
|
}
|
|
1383
645
|
const label = value["label"];
|
|
@@ -1399,13 +661,14 @@ function parseSubagentDescriptor(value) {
|
|
|
1399
661
|
...toolFilter !== void 0 ? { toolFilter } : {}
|
|
1400
662
|
};
|
|
1401
663
|
}
|
|
664
|
+
/**
|
|
665
|
+
* Validate and detach a continuable descriptor input.
|
|
666
|
+
* @param input - the caller-collected continuable composition fields.
|
|
667
|
+
* @returns the versioned, detached continuable descriptor payload.
|
|
668
|
+
* @throws when a field is not losslessly JSON-serializable.
|
|
669
|
+
*/
|
|
1402
670
|
function snapshotSubagentDescriptor(input) {
|
|
1403
|
-
const snapshot = snapshotJsonValue(
|
|
1404
|
-
version: 3,
|
|
1405
|
-
mode: input.mode,
|
|
1406
|
-
provider: input.provider,
|
|
1407
|
-
...input.label !== void 0 ? { label: input.label } : {}
|
|
1408
|
-
} : {
|
|
671
|
+
const snapshot = snapshotJsonValue({
|
|
1409
672
|
version: 3,
|
|
1410
673
|
mode: input.mode,
|
|
1411
674
|
provider: input.provider,
|
|
@@ -1451,12 +714,17 @@ const continuableCatalogSchema = z$1.object({
|
|
|
1451
714
|
mode: z$1.literal("continuable"),
|
|
1452
715
|
label: z$1.string()
|
|
1453
716
|
}).strict();
|
|
717
|
+
const externalCatalogSchema = oneShotCatalogSchema.extend({
|
|
718
|
+
version: z$1.literal(2),
|
|
719
|
+
mode: z$1.literal("external")
|
|
720
|
+
});
|
|
1454
721
|
const unknownCatalogSchema = oneShotCatalogSchema.extend({
|
|
1455
722
|
version: z$1.literal(1),
|
|
1456
723
|
mode: z$1.literal("unknown")
|
|
1457
724
|
});
|
|
1458
725
|
const eventDataSchema = z$1.union([
|
|
1459
726
|
oneShotCatalogSchema,
|
|
727
|
+
externalCatalogSchema,
|
|
1460
728
|
continuableCatalogSchema,
|
|
1461
729
|
unknownCatalogSchema
|
|
1462
730
|
]);
|
|
@@ -1469,6 +737,14 @@ const viewSchema = z$1.array(z$1.union([
|
|
|
1469
737
|
id: sessionIdSchema,
|
|
1470
738
|
createdAt: oneShotCatalogSchema.shape.childCreatedAt
|
|
1471
739
|
}),
|
|
740
|
+
externalCatalogSchema.omit({
|
|
741
|
+
version: true,
|
|
742
|
+
childId: true,
|
|
743
|
+
childCreatedAt: true
|
|
744
|
+
}).extend({
|
|
745
|
+
id: sessionIdSchema,
|
|
746
|
+
createdAt: externalCatalogSchema.shape.childCreatedAt
|
|
747
|
+
}),
|
|
1472
748
|
continuableCatalogSchema.omit({
|
|
1473
749
|
version: true,
|
|
1474
750
|
childId: true,
|
|
@@ -1491,102 +767,383 @@ const stateSchema = z$1.object({
|
|
|
1491
767
|
head: chunkedListSchema(eventDataSchema).optional()
|
|
1492
768
|
}).strict();
|
|
1493
769
|
/**
|
|
1494
|
-
* Materialize complete and unknown-mode child identities from parent catalog events.
|
|
1495
|
-
* @param state - parent catalog fold state.
|
|
1496
|
-
* @returns current direct-child rows in parent catalog event order.
|
|
770
|
+
* Materialize complete and unknown-mode child identities from parent catalog events.
|
|
771
|
+
* @param state - parent catalog fold state.
|
|
772
|
+
* @returns current direct-child rows in parent catalog event order.
|
|
773
|
+
*/
|
|
774
|
+
function subagentCatalogEntries(state) {
|
|
775
|
+
const entries = [];
|
|
776
|
+
for (const data of iterateChunkedList(state.head)) {
|
|
777
|
+
const { version: _version, childId, childCreatedAt, ...descriptor } = data;
|
|
778
|
+
entries.push({
|
|
779
|
+
id: childId,
|
|
780
|
+
createdAt: childCreatedAt,
|
|
781
|
+
...descriptor
|
|
782
|
+
});
|
|
783
|
+
}
|
|
784
|
+
return entries;
|
|
785
|
+
}
|
|
786
|
+
/** Parent-owned direct-child catalog projection; invalid own facts reject restoration. */
|
|
787
|
+
const subagentCatalogProjectionDefinition = {
|
|
788
|
+
key: "subagentCatalog",
|
|
789
|
+
stateSchema,
|
|
790
|
+
init: (_header, inheritedEventCount) => ({ inheritedEventCount }),
|
|
791
|
+
apply: (state, event) => {
|
|
792
|
+
if (event.type !== "subagent/catalog" || event.seq < state.inheritedEventCount) return state;
|
|
793
|
+
return {
|
|
794
|
+
...state,
|
|
795
|
+
head: appendChunkedList(state.head, eventDataSchema.parse(event.data))
|
|
796
|
+
};
|
|
797
|
+
},
|
|
798
|
+
stateVersion: 6,
|
|
799
|
+
wire: {
|
|
800
|
+
viewSchema,
|
|
801
|
+
view: subagentCatalogEntries
|
|
802
|
+
}
|
|
803
|
+
};
|
|
804
|
+
/**
|
|
805
|
+
* Append a complete direct-child discovery fact to its parent Session.
|
|
806
|
+
* @param parent - durable direct parent receiving the discovery fact.
|
|
807
|
+
* @param child - established child's immutable Session metadata.
|
|
808
|
+
* @param descriptor - mode-discriminated creation label frozen with the child.
|
|
809
|
+
*/
|
|
810
|
+
function establishCatalogChild(parent, child, descriptor) {
|
|
811
|
+
parent.append("subagent/catalog", {
|
|
812
|
+
version: 0,
|
|
813
|
+
childId: child.id,
|
|
814
|
+
childCreatedAt: child.createdAt,
|
|
815
|
+
mode: descriptor.mode,
|
|
816
|
+
label: descriptor.label
|
|
817
|
+
});
|
|
818
|
+
}
|
|
819
|
+
/** Catalog payload version emitted by external execution creation. */
|
|
820
|
+
const EXTERNAL_SUBAGENT_CATALOG_VERSION = 2;
|
|
821
|
+
/**
|
|
822
|
+
* Record an external execution without a local child Session.
|
|
823
|
+
* @param parent - durable direct parent receiving the discovery fact.
|
|
824
|
+
* @param childId - identity returned by the external provider.
|
|
825
|
+
* @param label - display label assigned by the caller.
|
|
826
|
+
*/
|
|
827
|
+
function establishExternalCatalogChild(parent, childId, label) {
|
|
828
|
+
parent.append("subagent/catalog", {
|
|
829
|
+
version: EXTERNAL_SUBAGENT_CATALOG_VERSION,
|
|
830
|
+
childId,
|
|
831
|
+
childCreatedAt: Date.now(),
|
|
832
|
+
mode: "external",
|
|
833
|
+
label
|
|
834
|
+
});
|
|
835
|
+
}
|
|
836
|
+
//#endregion
|
|
837
|
+
//#region lib/types/internal.js
|
|
838
|
+
/**
|
|
839
|
+
* Continuation integration markers and host adapters outside the public
|
|
840
|
+
* Service Definition and model-facing Agent messaging contract.
|
|
841
|
+
* @module @deepseek-ai/dsh-subagent/internal
|
|
842
|
+
*/
|
|
843
|
+
/** Process-stable identity carried only by the standard adjacent-Agent messaging tool. */
|
|
844
|
+
const adjacentAgentSendMessageTool = Symbol.for("dsh.subagent.adjacentAgentSendMessageTool");
|
|
845
|
+
/**
|
|
846
|
+
* Test whether one visible definition is the standard adjacent-Agent messaging tool.
|
|
847
|
+
* @param definition - the scope-resolved `send_message` candidate.
|
|
848
|
+
* @returns whether the definition carries the internal standard-tool identity.
|
|
849
|
+
*/
|
|
850
|
+
function isAdjacentAgentSendMessageTool(definition) {
|
|
851
|
+
return definition !== void 0 && definition[adjacentAgentSendMessageTool] === true;
|
|
852
|
+
}
|
|
853
|
+
/**
|
|
854
|
+
* Process-stable symbol-keyed host delivery shared by the bundled runtime
|
|
855
|
+
* entry and this unbundled internal subpath.
|
|
856
|
+
* @internal
|
|
857
|
+
*/
|
|
858
|
+
const deliverSubagentPrompt = Symbol.for("dsh.subagent.deliverPrompt");
|
|
859
|
+
//#endregion
|
|
860
|
+
//#region lib/types/structured.js
|
|
861
|
+
/**
|
|
862
|
+
* Child-scoped structured-output tool, prompt instruction, terminal guard, and authoritative
|
|
863
|
+
* result capture for in-process subagents. Each child registers its real schema on its own
|
|
864
|
+
* scope, so concurrent runs do not interact and disposal leaves no global residue. The prompt
|
|
865
|
+
* contribution is ordinary reconstructed request state.
|
|
866
|
+
*
|
|
867
|
+
* Capture commits only after the authoritative `tools/result` succeeds; PTC mode capture also
|
|
868
|
+
* waits for the enclosing `run_code` result. The terminal result marker and monotonic tool
|
|
869
|
+
* guard prevent later calls from reopening a completed structured run.
|
|
870
|
+
* @module @deepseek-ai/dsh-subagent/structured
|
|
871
|
+
*/
|
|
872
|
+
/** The model-facing tool name a structured child must call to finish. */
|
|
873
|
+
const STRUCTURED_OUTPUT_TOOL = "structured_output";
|
|
874
|
+
/**
|
|
875
|
+
* The instruction registered as the child's trailing scoped prompt section:
|
|
876
|
+
* the demand travels with the
|
|
877
|
+
* tool, as ordinary prompt state of exactly one agent.
|
|
878
|
+
*/
|
|
879
|
+
const STRUCTURED_OUTPUT_INSTRUCTION = `When you have your final answer, you MUST report it by calling the \`${STRUCTURED_OUTPUT_TOOL}\` tool with arguments matching its parameter schema exactly. Do not finish with a plain text answer: only the tool call counts as your result.`;
|
|
880
|
+
/**
|
|
881
|
+
* Attach the scoped capture tool, instruction, and enforcement to a child during
|
|
882
|
+
* its creation window. Child disposal removes every registration.
|
|
883
|
+
* @param childCtx - the child agent's scope context (`setup`'s argument).
|
|
884
|
+
* @param schema - the trusted, already-asserted schema subset to enforce (see
|
|
885
|
+
* `assertObjectJsonSchema` in dsh-tools).
|
|
886
|
+
* @param canComplete - whether accepted input and owned child work have finished.
|
|
887
|
+
* @returns the attachment handle (read `captured()` after the child settles).
|
|
888
|
+
*/
|
|
889
|
+
function attachStructuredRuntime(childCtx, schema, canComplete) {
|
|
890
|
+
/**
|
|
891
|
+
* Validated values staged by the capture tool body, awaiting THEIR OWN
|
|
892
|
+
* authoritative `tools/result` notification. The execution object's identity
|
|
893
|
+
* uniquely identifies a trip through the pipeline: adapter call ids may
|
|
894
|
+
* repeat across steps, but another execution can never reach this Map
|
|
895
|
+
* entry. This is distinct from the opaque `ToolExecutionToken` used to
|
|
896
|
+
* correlate nested transports. The final notification always deletes its own
|
|
897
|
+
* stage, whether the result succeeded or failed.
|
|
898
|
+
*/
|
|
899
|
+
const staged = /* @__PURE__ */ new Map();
|
|
900
|
+
/** Successful nested capture waiting for its enclosing transport to commit. */
|
|
901
|
+
let pending;
|
|
902
|
+
let captured;
|
|
903
|
+
const schemaEntry = {
|
|
904
|
+
name: STRUCTURED_OUTPUT_TOOL,
|
|
905
|
+
description: "Report your final structured result. Call this exactly once, when your answer is complete; the arguments must match this tool's parameter schema exactly.",
|
|
906
|
+
parameters: { ...schema }
|
|
907
|
+
};
|
|
908
|
+
childCtx.tools.register({
|
|
909
|
+
...schemaEntry,
|
|
910
|
+
output: {
|
|
911
|
+
schema: {
|
|
912
|
+
type: "object",
|
|
913
|
+
properties: { recorded: {
|
|
914
|
+
type: "boolean",
|
|
915
|
+
const: true
|
|
916
|
+
} },
|
|
917
|
+
required: ["recorded"],
|
|
918
|
+
additionalProperties: false
|
|
919
|
+
},
|
|
920
|
+
render: () => [{
|
|
921
|
+
type: "text",
|
|
922
|
+
text: "Structured output recorded."
|
|
923
|
+
}]
|
|
924
|
+
},
|
|
925
|
+
execute(args, exec) {
|
|
926
|
+
const violations = validateJsonSchemaValue(schema, args);
|
|
927
|
+
if (violations.length > 0) throw new ToolArgsError(violations);
|
|
928
|
+
if (!canComplete()) throw new Error("Wait for all delegated child tasks to finish and process pending messages before submitting structured output.");
|
|
929
|
+
staged.set(exec, { value: args });
|
|
930
|
+
exec.concludeTurn();
|
|
931
|
+
return Promise.resolve({ recorded: true });
|
|
932
|
+
}
|
|
933
|
+
});
|
|
934
|
+
childCtx.systemPrompt.section({
|
|
935
|
+
name: `tool:${STRUCTURED_OUTPUT_TOOL}`,
|
|
936
|
+
order: childCtx.systemPrompt.getSectionOrder("STRUCTURED_OUTPUT"),
|
|
937
|
+
text: STRUCTURED_OUTPUT_INSTRUCTION
|
|
938
|
+
});
|
|
939
|
+
childCtx.tools.guard((exec) => captured === void 0 && pending === void 0 ? void 0 : `structured output already recorded: the run is complete, so \`${exec.name}\` is not executed`);
|
|
940
|
+
childCtx.on("tools/result", function(exec, result) {
|
|
941
|
+
if (exec.name === "structured_output") {
|
|
942
|
+
const entry = staged.get(exec);
|
|
943
|
+
if (entry === void 0) return;
|
|
944
|
+
staged.delete(exec);
|
|
945
|
+
if (result.isError) return;
|
|
946
|
+
if (exec.parent === void 0) {
|
|
947
|
+
/* v8 ignore else -- sequential agent-loop dispatch lets the guard block every later supported call */
|
|
948
|
+
if (captured === void 0) captured = { value: entry.value };
|
|
949
|
+
} else if (captured === void 0 && pending === void 0) pending = {
|
|
950
|
+
parent: exec.parent,
|
|
951
|
+
value: entry.value
|
|
952
|
+
};
|
|
953
|
+
return;
|
|
954
|
+
}
|
|
955
|
+
if (pending?.parent !== exec.token) return;
|
|
956
|
+
const entry = pending;
|
|
957
|
+
pending = void 0;
|
|
958
|
+
if (result.isError) return;
|
|
959
|
+
/* v8 ignore else -- PTC mode serializes outer executions, so the guard blocks every later supported call */
|
|
960
|
+
if (captured === void 0) captured = { value: entry.value };
|
|
961
|
+
});
|
|
962
|
+
return {
|
|
963
|
+
captured: () => captured,
|
|
964
|
+
acceptsInput: () => staged.size === 0 && pending === void 0 && captured === void 0
|
|
965
|
+
};
|
|
966
|
+
}
|
|
967
|
+
//#endregion
|
|
968
|
+
//#region lib/types/assistant-output.js
|
|
969
|
+
/**
|
|
970
|
+
* Canonical selection of a child's final assistant output. Backend run results
|
|
971
|
+
* and `subagent/end.lastAssistantMessage` apply the same rule: select the last
|
|
972
|
+
* non-empty assistant message. An empty-content message records usage only
|
|
973
|
+
* when the loop appends it after a max-tokens step with no executable blocks,
|
|
974
|
+
* so it does not replace earlier output. If no non-empty message exists,
|
|
975
|
+
* select the accumulated assistant text. Selection is independent of the
|
|
976
|
+
* run's stop reason.
|
|
977
|
+
*
|
|
978
|
+
* @module @deepseek-ai/dsh-subagent/assistant-output
|
|
979
|
+
*/
|
|
980
|
+
/**
|
|
981
|
+
* Incremental fold of the selection rule, for backends that observe a child's
|
|
982
|
+
* output as it streams: session-event backends {@link push} each event, and
|
|
983
|
+
* transports without session events (ACP content chunks) {@link pushText} raw
|
|
984
|
+
* text into the same streamed fallback.
|
|
985
|
+
*/
|
|
986
|
+
var AssistantOutputFold = class {
|
|
987
|
+
message;
|
|
988
|
+
partial = [];
|
|
989
|
+
/**
|
|
990
|
+
* Fold one session event: a non-empty assistant message becomes the
|
|
991
|
+
* candidate final answer, while its embedded stream and any log-only attempt
|
|
992
|
+
* extend the streamed fallback; every other event contributes nothing.
|
|
993
|
+
* @param event - the next observed session event.
|
|
994
|
+
*/
|
|
995
|
+
push(event) {
|
|
996
|
+
if (event.type === "assistant/message") {
|
|
997
|
+
const content = event.data.message.content;
|
|
998
|
+
if (content.length > 0) this.message = content;
|
|
999
|
+
}
|
|
1000
|
+
if (event.type === "assistant/message" || event.type === "assistant/attempt") this.pushText(joinAssistantStreamText(event.data.stream));
|
|
1001
|
+
}
|
|
1002
|
+
/**
|
|
1003
|
+
* Extend the streamed fallback with text observed outside session events.
|
|
1004
|
+
* @param text - the next streamed text piece (an empty piece is a no-op).
|
|
1005
|
+
*/
|
|
1006
|
+
pushText(text) {
|
|
1007
|
+
if (text.length > 0) this.partial.push(text);
|
|
1008
|
+
}
|
|
1009
|
+
/**
|
|
1010
|
+
* Select the final output folded so far.
|
|
1011
|
+
* @returns the last non-empty assistant message, else the accumulated
|
|
1012
|
+
* streamed text, or `undefined` when the child produced neither.
|
|
1013
|
+
*/
|
|
1014
|
+
collect() {
|
|
1015
|
+
if (this.message !== void 0) return this.message;
|
|
1016
|
+
const text = this.partial.join("");
|
|
1017
|
+
return text.length > 0 ? [{
|
|
1018
|
+
type: "text",
|
|
1019
|
+
text
|
|
1020
|
+
}] : void 0;
|
|
1021
|
+
}
|
|
1022
|
+
};
|
|
1023
|
+
/**
|
|
1024
|
+
* Apply the selection rule to one complete child-owned event suffix.
|
|
1025
|
+
* @param events - the child-owned events (after any seed or epoch boundary).
|
|
1026
|
+
* @returns the selected output, or `undefined` when the child produced none.
|
|
1497
1027
|
*/
|
|
1498
|
-
function
|
|
1499
|
-
const
|
|
1500
|
-
for (const
|
|
1501
|
-
|
|
1502
|
-
createdAt: data.childCreatedAt,
|
|
1503
|
-
mode: data.mode,
|
|
1504
|
-
...data.label === void 0 ? {} : { label: data.label }
|
|
1505
|
-
} : {
|
|
1506
|
-
id: data.childId,
|
|
1507
|
-
createdAt: data.childCreatedAt,
|
|
1508
|
-
mode: data.mode,
|
|
1509
|
-
label: data.label
|
|
1510
|
-
});
|
|
1511
|
-
return entries;
|
|
1028
|
+
function finalAssistantOutput(events) {
|
|
1029
|
+
const fold = new AssistantOutputFold();
|
|
1030
|
+
for (const event of events) fold.push(event);
|
|
1031
|
+
return fold.collect();
|
|
1512
1032
|
}
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1033
|
+
//#endregion
|
|
1034
|
+
//#region lib/types/activation.js
|
|
1035
|
+
/** Execution-period records, capacity slots, child serialization, and local output capture.
|
|
1036
|
+
* @module @deepseek-ai/dsh-subagent/activation
|
|
1037
|
+
*/
|
|
1038
|
+
/** Process-local slots shared through uninterrupted continuable parent links. */
|
|
1039
|
+
var ActivationPool = class {
|
|
1040
|
+
slots = /* @__PURE__ */ new Set();
|
|
1041
|
+
/**
|
|
1042
|
+
* Reserve before reconstruction; release also tolerates unpublished rollback.
|
|
1043
|
+
* @param capacity - the maximum simultaneous activations in this pool.
|
|
1044
|
+
* @returns an idempotent slot release.
|
|
1045
|
+
*/
|
|
1046
|
+
reserve(capacity) {
|
|
1047
|
+
if (this.slots.size >= capacity) throw new SubagentError(`subagent limit reached (active child limit: ${capacity}); wait for an existing child to finish or complete this work with the current agents`, "ACTIVATION_LIMIT_REACHED");
|
|
1048
|
+
const slot = Symbol();
|
|
1049
|
+
this.slots.add(slot);
|
|
1050
|
+
return () => {
|
|
1051
|
+
this.slots.delete(slot);
|
|
1523
1052
|
};
|
|
1524
|
-
},
|
|
1525
|
-
stateVersion: 3,
|
|
1526
|
-
wire: {
|
|
1527
|
-
viewSchema,
|
|
1528
|
-
view: subagentCatalogEntries
|
|
1529
1053
|
}
|
|
1530
1054
|
};
|
|
1531
1055
|
/**
|
|
1532
|
-
*
|
|
1533
|
-
* @param
|
|
1534
|
-
* @
|
|
1535
|
-
* @param descriptor - mode-discriminated creation label frozen with the child.
|
|
1056
|
+
* Read the live Agent retained by local execution.
|
|
1057
|
+
* @param activation - the managed local or external execution.
|
|
1058
|
+
* @returns the local Agent, absent for an external run.
|
|
1536
1059
|
*/
|
|
1537
|
-
function
|
|
1538
|
-
|
|
1539
|
-
version: 0,
|
|
1540
|
-
childId: child.id,
|
|
1541
|
-
childCreatedAt: child.createdAt,
|
|
1542
|
-
mode: descriptor.mode,
|
|
1543
|
-
...descriptor.label === void 0 ? {} : { label: descriptor.label }
|
|
1544
|
-
} : {
|
|
1545
|
-
version: 0,
|
|
1546
|
-
childId: child.id,
|
|
1547
|
-
childCreatedAt: child.createdAt,
|
|
1548
|
-
mode: descriptor.mode,
|
|
1549
|
-
label: descriptor.label
|
|
1550
|
-
});
|
|
1060
|
+
function activationAgent(activation) {
|
|
1061
|
+
return activation.kind === "local" ? activation.handle.agent : void 0;
|
|
1551
1062
|
}
|
|
1552
|
-
//#endregion
|
|
1553
|
-
//#region lib/types/internal.js
|
|
1554
1063
|
/**
|
|
1555
|
-
*
|
|
1556
|
-
*
|
|
1557
|
-
* @
|
|
1064
|
+
* Require local residency before accepting further input.
|
|
1065
|
+
* @param activation - execution targeted by message delivery.
|
|
1066
|
+
* @returns the local activation.
|
|
1067
|
+
* @throws when the external backend cannot accept another message.
|
|
1558
1068
|
*/
|
|
1559
|
-
|
|
1560
|
-
|
|
1069
|
+
function requireLocalActivation(activation) {
|
|
1070
|
+
switch (activation.kind) {
|
|
1071
|
+
case "local": return activation;
|
|
1072
|
+
case "external": throw new SubagentError(`subagent "${activation.childId}" does not accept follow-up input`, "NOT_CONTINUABLE");
|
|
1073
|
+
/* v8 ignore next 2 -- Both execution kinds are handled above. */
|
|
1074
|
+
default: return assertNever(activation);
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1561
1077
|
/**
|
|
1562
|
-
*
|
|
1563
|
-
* @param
|
|
1564
|
-
* @
|
|
1078
|
+
* Retain local failures whose turn ending could not be committed.
|
|
1079
|
+
* @param activation - exact local Agent and epoch receiving the scoped observers.
|
|
1080
|
+
* @param wake - notify its settlement watcher when failure changes readiness.
|
|
1565
1081
|
*/
|
|
1566
|
-
function
|
|
1567
|
-
|
|
1082
|
+
function observeLocalFailure(activation, wake) {
|
|
1083
|
+
const child = activation.handle.agent;
|
|
1084
|
+
child.ctx.on("agent/error", ({ agent }) => {
|
|
1085
|
+
if (agent !== child) return;
|
|
1086
|
+
activation.failureAt = child.session.seq;
|
|
1087
|
+
wake();
|
|
1088
|
+
});
|
|
1089
|
+
child.ctx.on("session/event", (session, event) => {
|
|
1090
|
+
if (session === child.session && event.type === "turn/end" && activation.failureAt !== void 0 && event.seq >= activation.failureAt) activation.failureAt = void 0;
|
|
1091
|
+
});
|
|
1568
1092
|
}
|
|
1569
1093
|
/**
|
|
1570
|
-
*
|
|
1571
|
-
*
|
|
1572
|
-
* @
|
|
1573
|
-
*/
|
|
1574
|
-
|
|
1094
|
+
* Capture local output and retain failures without a later committed turn ending.
|
|
1095
|
+
* @param activation - the settled local execution and any structured result.
|
|
1096
|
+
* @returns final output, stop reason, and committed structured value.
|
|
1097
|
+
*/
|
|
1098
|
+
function captureLocalResult(activation) {
|
|
1099
|
+
const events = activation.handle.agent.session.snapshotEvents(activation.outputStart);
|
|
1100
|
+
const output = finalAssistantOutput(events) ?? [];
|
|
1101
|
+
const stopReason = activation.failureAt === void 0 ? epochStopReason(events) : "error";
|
|
1102
|
+
const structured = activation.structured;
|
|
1103
|
+
if (structured !== void 0) {
|
|
1104
|
+
const captured = structured.captured();
|
|
1105
|
+
if (captured !== void 0) return {
|
|
1106
|
+
output,
|
|
1107
|
+
stopReason,
|
|
1108
|
+
structured: captured.value
|
|
1109
|
+
};
|
|
1110
|
+
if (stopReason === "completed") return {
|
|
1111
|
+
output,
|
|
1112
|
+
stopReason: "error"
|
|
1113
|
+
};
|
|
1114
|
+
}
|
|
1115
|
+
return {
|
|
1116
|
+
output,
|
|
1117
|
+
stopReason
|
|
1118
|
+
};
|
|
1119
|
+
}
|
|
1120
|
+
/** Serialize each durable child's delivery, release, and disposal. */
|
|
1121
|
+
var ChildLock = class {
|
|
1122
|
+
tails = /* @__PURE__ */ new Map();
|
|
1123
|
+
/**
|
|
1124
|
+
* Run `operation` after every previously queued operation for `childId`.
|
|
1125
|
+
* @param childId - the durable child whose operations are linearized.
|
|
1126
|
+
* @param operation - the critical section to run in order.
|
|
1127
|
+
* @returns the operation's own settlement.
|
|
1128
|
+
*/
|
|
1129
|
+
run(childId, operation) {
|
|
1130
|
+
const result = (this.tails.get(childId) ?? Promise.resolve()).then(operation, operation);
|
|
1131
|
+
const tail = result.then(() => void 0, () => void 0);
|
|
1132
|
+
this.tails.set(childId, tail);
|
|
1133
|
+
tail.then(() => {
|
|
1134
|
+
if (this.tails.get(childId) === tail) this.tails.delete(childId);
|
|
1135
|
+
});
|
|
1136
|
+
return result;
|
|
1137
|
+
}
|
|
1138
|
+
};
|
|
1575
1139
|
//#endregion
|
|
1576
|
-
//#region lib/types/
|
|
1140
|
+
//#region lib/types/manager.js
|
|
1577
1141
|
/**
|
|
1578
|
-
*
|
|
1579
|
-
*
|
|
1580
|
-
*
|
|
1581
|
-
* process-local Activation graph and its settlement and disposal lifecycle.
|
|
1582
|
-
*
|
|
1583
|
-
* A continuable child has one durable Session and at most one process-local
|
|
1584
|
-
* Activation. The Agent inbox is the only turn queue, so this manager owns
|
|
1585
|
-
* durable orchestration while the Agent loop owns all turn ordering and
|
|
1586
|
-
* execution. No continuable path creates a Task or an intermediate
|
|
1587
|
-
* result-bearing wrapper.
|
|
1142
|
+
* Owns subagent startup, message admission, cold resume, and execution lifetimes.
|
|
1143
|
+
* One child lock orders input acceptance and closure; one activation map owns
|
|
1144
|
+
* capacity, parent relationships, and resource release across all providers.
|
|
1588
1145
|
*
|
|
1589
|
-
* @module @deepseek-ai/dsh-subagent
|
|
1146
|
+
* @module @deepseek-ai/dsh-subagent/manager
|
|
1590
1147
|
*/
|
|
1591
1148
|
var __addDisposableResource$2 = function(env, value, async) {
|
|
1592
1149
|
if (value !== null && value !== void 0) {
|
|
@@ -1646,20 +1203,183 @@ var __disposeResources$2 = (function(SuppressedError) {
|
|
|
1646
1203
|
var e = new Error(message);
|
|
1647
1204
|
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
1648
1205
|
});
|
|
1649
|
-
/**
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
var SubagentContinuationManager = class {
|
|
1206
|
+
/** Render only the safe outer message from execution or teardown failures. */
|
|
1207
|
+
function failureMessage(error) {
|
|
1208
|
+
return error instanceof Error ? error.message : "unknown teardown failure";
|
|
1209
|
+
}
|
|
1210
|
+
/** Own subagent requests and the resources of every live activation. */
|
|
1211
|
+
var SubagentManager = class {
|
|
1656
1212
|
ctx;
|
|
1657
1213
|
host;
|
|
1658
|
-
|
|
1214
|
+
maxActiveSubagents;
|
|
1215
|
+
/** Child session id → its live Activation. Process-local, never durable. */
|
|
1216
|
+
resident = /* @__PURE__ */ new Map();
|
|
1217
|
+
/** Root identities retain their pool across child settlement without retaining dead roots. */
|
|
1218
|
+
rootPools = /* @__PURE__ */ new WeakMap();
|
|
1219
|
+
/** Materializations admitted before drain, tracked through publication or rollback. */
|
|
1220
|
+
materializations = /* @__PURE__ */ new Set();
|
|
1221
|
+
/** Per-child serializer shared by delivery, release, and disposal. */
|
|
1222
|
+
locks = new ChildLock();
|
|
1223
|
+
/** Structural Cordis owner of every Activation handle. */
|
|
1224
|
+
ownerCtx;
|
|
1225
|
+
/**
|
|
1226
|
+
* Exact roots whose host teardown has begun, with the live lineage members
|
|
1227
|
+
* observed under each root. Entries remain until that exact root leaves the
|
|
1228
|
+
* Agent registry, closing admission throughout its host's teardown without
|
|
1229
|
+
* poisoning a later same-id replacement.
|
|
1230
|
+
*/
|
|
1231
|
+
closingScopes = /* @__PURE__ */ new Map();
|
|
1232
|
+
draining = false;
|
|
1233
|
+
/**
|
|
1234
|
+
* Build one manager inside the service's Agent-injected context.
|
|
1235
|
+
* @param ctx - context providing Agents, Sessions, and teardown ownership.
|
|
1236
|
+
* @param host - provider dispatch and lifecycle observation owned by the service.
|
|
1237
|
+
* @param maxActiveSubagents - the current configured child capacity.
|
|
1238
|
+
*/
|
|
1659
1239
|
constructor(ctx, host, maxActiveSubagents) {
|
|
1660
1240
|
this.ctx = ctx;
|
|
1661
1241
|
this.host = host;
|
|
1662
|
-
this.
|
|
1242
|
+
this.maxActiveSubagents = maxActiveSubagents;
|
|
1243
|
+
const scope = ctx.plugin(function activationOwner() {});
|
|
1244
|
+
this.ownerCtx = scope.ctx;
|
|
1245
|
+
ctx.on("agent/disposed", ({ agent }) => {
|
|
1246
|
+
this.closingScopes.delete(agent);
|
|
1247
|
+
});
|
|
1248
|
+
ctx.effect(function* () {
|
|
1249
|
+
yield scope.dispose;
|
|
1250
|
+
yield () => this.drain();
|
|
1251
|
+
}.bind(this), "subagents.manager()");
|
|
1252
|
+
}
|
|
1253
|
+
/**
|
|
1254
|
+
* Interrupt one live continuable child's current turn under the supplied authority.
|
|
1255
|
+
* @param targetSessionId - the durable child session id to interrupt.
|
|
1256
|
+
* @param authority - the human parent address or exact live ancestor Agent.
|
|
1257
|
+
*/
|
|
1258
|
+
interrupt(targetSessionId, authority) {
|
|
1259
|
+
if (authority.kind === "ancestor") {
|
|
1260
|
+
const caller = authority.agent;
|
|
1261
|
+
if (this.ctx.agents.get(caller.id) !== caller) throw new SubagentError(`interrupting "${targetSessionId}" requires the exact live ancestor agent`, "UNAUTHORIZED");
|
|
1262
|
+
if (caller.id === targetSessionId) throw new SubagentError(`agent "${caller.id}" cannot interrupt itself`, "UNAUTHORIZED");
|
|
1263
|
+
}
|
|
1264
|
+
const activation = this.resident.get(targetSessionId);
|
|
1265
|
+
if (activation === void 0) return;
|
|
1266
|
+
if (authority.kind === "user") {
|
|
1267
|
+
if (activation.parent.id !== authority.parentSessionId) throw new SubagentError(`subagent "${targetSessionId}" belongs to another parent session`, "UNAUTHORIZED");
|
|
1268
|
+
} else if (!activation.ancestry.has(authority.agent)) throw new SubagentError(`subagent "${targetSessionId}" is not a live descendant of agent "${authority.agent.id}"`, "UNAUTHORIZED");
|
|
1269
|
+
if (activation.closing !== void 0) return;
|
|
1270
|
+
const kind = authority.kind === "user" ? "user" : "parent";
|
|
1271
|
+
if (activation.kind === "local") activation.handle.agent.cancel({ kind }, { keepInbox: true });
|
|
1272
|
+
else {
|
|
1273
|
+
activation.controller.abort({ kind });
|
|
1274
|
+
this.dispose(activation).catch(() => void 0);
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
/**
|
|
1278
|
+
* Close admission, await every already-admitted materialization through
|
|
1279
|
+
* publication or rollback, then dispose the stable live Activation graph
|
|
1280
|
+
* child-first.
|
|
1281
|
+
*/
|
|
1282
|
+
async drain() {
|
|
1283
|
+
this.draining = true;
|
|
1284
|
+
for (const pending of this.materializations) pending.controller.abort();
|
|
1285
|
+
await Promise.all([...this.materializations].map((materialization) => materialization.settled));
|
|
1286
|
+
const owned = /* @__PURE__ */ new Set();
|
|
1287
|
+
for (const activation of this.resident.values()) for (const child of activation.ownedChildren) owned.add(child);
|
|
1288
|
+
const roots = [...this.resident.values()].filter((activation) => !owned.has(activation.childId));
|
|
1289
|
+
await this.disposeRoots(roots, "activation(s)");
|
|
1290
|
+
}
|
|
1291
|
+
/**
|
|
1292
|
+
* Join progressing child work, leaving idle parked inboxes resident.
|
|
1293
|
+
* @param parent - exact live parent whose descendants are observed.
|
|
1294
|
+
* @returns whether work was joined and the host must recheck parent completion.
|
|
1295
|
+
*/
|
|
1296
|
+
async waitForChildren(parent) {
|
|
1297
|
+
let observed = false;
|
|
1298
|
+
while (true) {
|
|
1299
|
+
const pending = [...this.materializations].filter((item) => item.lineage.includes(parent));
|
|
1300
|
+
const children = [...this.resident.values()].filter((item) => activationAgent(item) !== parent && item.ancestry.has(parent));
|
|
1301
|
+
observed ||= children.some((item) => item.kind === "local" && item.handle.agent.status !== "idle");
|
|
1302
|
+
await Promise.all(children.flatMap((item) => item.kind === "local" ? [item.handle.agent.whenIdle()] : []));
|
|
1303
|
+
const waits = pending.map((item) => item.settled);
|
|
1304
|
+
for (const item of children) {
|
|
1305
|
+
if (item.kind === "external" || item.closing !== void 0) {
|
|
1306
|
+
waits.push(item.result.promise.catch(() => void 0));
|
|
1307
|
+
continue;
|
|
1308
|
+
}
|
|
1309
|
+
if (item.handle.agent.status !== "idle") {
|
|
1310
|
+
waits.push(item.handle.agent.whenIdle());
|
|
1311
|
+
continue;
|
|
1312
|
+
}
|
|
1313
|
+
if (item.ownedChildren.size > 0) {
|
|
1314
|
+
if ([...item.ownedChildren].some((id) => !this.resident.has(id))) waits.push(item.poke.promise);
|
|
1315
|
+
continue;
|
|
1316
|
+
}
|
|
1317
|
+
const inbox = item.handle.agent.inbox;
|
|
1318
|
+
if (item.failureAt === void 0 && (inbox.nextTurn.length > 0 || inbox.nextStep.length > 0)) continue;
|
|
1319
|
+
waits.push(Promise.race([item.result.promise.catch(() => void 0), item.poke.promise]));
|
|
1320
|
+
}
|
|
1321
|
+
if (waits.length > 0) {
|
|
1322
|
+
observed = true;
|
|
1323
|
+
await Promise.all(waits);
|
|
1324
|
+
continue;
|
|
1325
|
+
}
|
|
1326
|
+
if ([...this.materializations].some((item) => item.lineage.includes(parent) && !pending.includes(item)) || [...this.resident.values()].some((item) => activationAgent(item) !== parent && item.ancestry.has(parent) && !children.includes(item))) continue;
|
|
1327
|
+
return observed;
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
/**
|
|
1331
|
+
* Stop the managed descendants of exact live host-owned parents.
|
|
1332
|
+
* @param parents - exact live roots whose managed descendants must stop.
|
|
1333
|
+
*/
|
|
1334
|
+
async drainDescendants(parents) {
|
|
1335
|
+
const roots = new Set(parents.filter((parent) => this.ctx.agents.get(parent.id) === parent));
|
|
1336
|
+
if (roots.size === 0) return;
|
|
1337
|
+
for (const root of roots) this.closingMembers(root).add(root);
|
|
1338
|
+
const targets = [];
|
|
1339
|
+
for (const activation of this.resident.values()) {
|
|
1340
|
+
const child = activationAgent(activation);
|
|
1341
|
+
const lineage = this.liveLineage(child ?? activation.parent);
|
|
1342
|
+
const owners = [...roots].filter((root) => child !== root && activation.ancestry.has(root));
|
|
1343
|
+
if (owners.length === 0) continue;
|
|
1344
|
+
targets.push(activation);
|
|
1345
|
+
for (const owner of owners) {
|
|
1346
|
+
const members = this.closingMembers(owner);
|
|
1347
|
+
if (child !== void 0) members.add(child);
|
|
1348
|
+
for (const agent of lineage) members.add(agent);
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
const materializations = [...this.materializations].filter((materialization) => {
|
|
1352
|
+
const owners = [...roots].filter((root) => materialization.lineage.includes(root));
|
|
1353
|
+
for (const owner of owners) {
|
|
1354
|
+
const members = this.closingMembers(owner);
|
|
1355
|
+
for (const agent of materialization.lineage) members.add(agent);
|
|
1356
|
+
}
|
|
1357
|
+
if (owners.length > 0) materialization.controller.abort();
|
|
1358
|
+
return owners.length > 0;
|
|
1359
|
+
});
|
|
1360
|
+
const ownedTargets = /* @__PURE__ */ new Set();
|
|
1361
|
+
for (const activation of targets) for (const child of activation.ownedChildren) ownedTargets.add(child);
|
|
1362
|
+
const targetRoots = targets.filter((activation) => !ownedTargets.has(activation.childId));
|
|
1363
|
+
for (const activation of targets) this.dispose(activation).catch(() => void 0);
|
|
1364
|
+
await Promise.all(materializations.map((materialization) => materialization.settled));
|
|
1365
|
+
await this.disposeRoots(targetRoots, "scoped activation(s)");
|
|
1366
|
+
}
|
|
1367
|
+
/**
|
|
1368
|
+
* Release selected resident direct children of one exact live parent.
|
|
1369
|
+
* @param parent - exact live direct parent authorizing the selected release.
|
|
1370
|
+
* @param childIds - durable direct-child ids to release when resident.
|
|
1371
|
+
*/
|
|
1372
|
+
async drainChildren(parent, childIds) {
|
|
1373
|
+
if (this.ctx.agents.get(parent.id) !== parent) throw new SubagentError("selected child teardown requires the exact live parent agent", "UNAUTHORIZED");
|
|
1374
|
+
const targets = [];
|
|
1375
|
+
for (const childId of new Set(childIds)) {
|
|
1376
|
+
const activation = this.resident.get(childId);
|
|
1377
|
+
if (activation === void 0) continue;
|
|
1378
|
+
if (activation.parent.id !== parent.id || !activation.ancestry.has(parent)) throw new SubagentError(`subagent "${childId}" is not a direct child of agent "${parent.id}"`, "UNAUTHORIZED");
|
|
1379
|
+
targets.push(activation);
|
|
1380
|
+
}
|
|
1381
|
+
for (const activation of targets) this.dispose(activation).catch(() => void 0);
|
|
1382
|
+
await this.disposeRoots(targets, "selected activation(s)");
|
|
1663
1383
|
}
|
|
1664
1384
|
/**
|
|
1665
1385
|
* Start one continuable background child and resolve at initial inbox acceptance.
|
|
@@ -1668,14 +1388,14 @@ var SubagentContinuationManager = class {
|
|
|
1668
1388
|
* @param spec - provider, delegation request, and caller cancellation.
|
|
1669
1389
|
* @returns the durable child id and accepted initial prompt message id.
|
|
1670
1390
|
*/
|
|
1671
|
-
async
|
|
1391
|
+
async startLocal(spec) {
|
|
1672
1392
|
const request = spec.request;
|
|
1673
1393
|
const parent = request.parent;
|
|
1674
|
-
this.
|
|
1675
|
-
const persistence = this.requirePersistence();
|
|
1394
|
+
this.assertAdmitting(parent);
|
|
1395
|
+
const persistence = spec.delivery === "caller" ? this.ctx.get("sessionPersistence") : this.requirePersistence();
|
|
1676
1396
|
assertSubagentMaxDepth(request.maxDepth);
|
|
1677
1397
|
const childId = spec.childId ?? brandString(randomUUID());
|
|
1678
|
-
this.
|
|
1398
|
+
this.assertChildIdAvailable(childId);
|
|
1679
1399
|
const childDepth = resolveChildDepth(parent, request.maxDepth);
|
|
1680
1400
|
const agentOptions = resolveChildAgentOptions(parent, request.agentOptions, childDepth);
|
|
1681
1401
|
const agentProvider = agentOptions.provider;
|
|
@@ -1687,128 +1407,679 @@ var SubagentContinuationManager = class {
|
|
|
1687
1407
|
label: spec.label,
|
|
1688
1408
|
...agentProvider !== void 0 ? { agentProvider } : {},
|
|
1689
1409
|
...agentModel !== void 0 ? { agentModel } : {},
|
|
1690
|
-
...agentReasoningEffort !== void 0 ? { agentReasoningEffort } : {}
|
|
1691
|
-
...request.persona !== void 0 ? { persona: request.persona } : {},
|
|
1692
|
-
...request.toolFilter !== void 0 ? { toolFilter: request.toolFilter } : {}
|
|
1410
|
+
...agentReasoningEffort !== void 0 ? { agentReasoningEffort } : {}
|
|
1693
1411
|
});
|
|
1694
1412
|
const delegatedPolicies = captureDelegatedPolicyOverrides(parent);
|
|
1695
|
-
const releaseHold = this.
|
|
1413
|
+
const releaseHold = this.holdOwnership(parent, childId);
|
|
1696
1414
|
try {
|
|
1415
|
+
const cwd = await this.resolveDirectory(request, spec.signal);
|
|
1697
1416
|
const prepared = await this.host.prepareContinuable(spec.provider, {
|
|
1698
1417
|
sessionId: childId,
|
|
1418
|
+
cwd,
|
|
1699
1419
|
parent,
|
|
1700
1420
|
signal: spec.signal
|
|
1701
1421
|
});
|
|
1702
1422
|
spec.signal.throwIfAborted();
|
|
1703
|
-
this.
|
|
1423
|
+
this.assertAdmitting(parent);
|
|
1704
1424
|
const inheritedEventCount = SessionLogOffset(prepared.seed?.length ?? 0);
|
|
1705
1425
|
const seed = prepared.seed;
|
|
1426
|
+
let established;
|
|
1427
|
+
const messageId = await this.locks.run(childId, async () => {
|
|
1428
|
+
spec.signal.throwIfAborted();
|
|
1429
|
+
this.assertAdmitting(parent);
|
|
1430
|
+
this.assertChildIdAvailable(childId);
|
|
1431
|
+
if (spec.childId !== void 0) {
|
|
1432
|
+
const persisted = await persistence?.stat(childId, { signal: spec.signal });
|
|
1433
|
+
spec.signal.throwIfAborted();
|
|
1434
|
+
this.assertAdmitting(parent);
|
|
1435
|
+
this.assertChildIdAvailable(childId);
|
|
1436
|
+
if (persisted !== void 0) throw new SubagentError(`subagent "${childId}" already exists`, "DUPLICATE_CHILD");
|
|
1437
|
+
}
|
|
1438
|
+
const activation = await this.materialize({
|
|
1439
|
+
kind: "local",
|
|
1440
|
+
childId,
|
|
1441
|
+
provider: spec.provider,
|
|
1442
|
+
parent,
|
|
1443
|
+
create: {
|
|
1444
|
+
cwd,
|
|
1445
|
+
seed,
|
|
1446
|
+
meta: childSessionMeta(parent, childDepth, prepared.seed !== void 0, request.agentPreset),
|
|
1447
|
+
inheritedEventCount,
|
|
1448
|
+
delegatedPolicies,
|
|
1449
|
+
descriptor
|
|
1450
|
+
},
|
|
1451
|
+
agentOptions,
|
|
1452
|
+
composition: { agentPreset: request.agentPreset },
|
|
1453
|
+
signal: spec.signal,
|
|
1454
|
+
delivery: spec.delivery,
|
|
1455
|
+
outputSchema: spec.request.outputSchema
|
|
1456
|
+
});
|
|
1457
|
+
established = activation;
|
|
1458
|
+
const child = requireLocalActivation(activation).handle.agent;
|
|
1459
|
+
const childHeader = child.session.header;
|
|
1460
|
+
return await this.submitMaterialized(activation, spec.delivery !== "caller" && isAdjacentAgentSendMessageTool(this.ctx.get("tools")?.get("send_message", child)) ? withContinuableReturnGuidance(parent.id, request.prompt) : request.prompt, {
|
|
1461
|
+
source: { kind: "user" },
|
|
1462
|
+
signal: spec.signal,
|
|
1463
|
+
delivery: "queue"
|
|
1464
|
+
}, parent, () => {
|
|
1465
|
+
establishCatalogChild(parent.session, childHeader, descriptor);
|
|
1466
|
+
});
|
|
1467
|
+
});
|
|
1706
1468
|
return {
|
|
1469
|
+
...this.receipt(established),
|
|
1707
1470
|
childId,
|
|
1708
|
-
messageId
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
signal: spec.signal,
|
|
1741
|
-
delivery: "queue"
|
|
1742
|
-
}, parent, () => {
|
|
1743
|
-
establishCatalogChild(parent.session, childHeader, descriptor);
|
|
1744
|
-
});
|
|
1471
|
+
messageId
|
|
1472
|
+
};
|
|
1473
|
+
} catch (error) {
|
|
1474
|
+
releaseHold();
|
|
1475
|
+
throw error;
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1478
|
+
/**
|
|
1479
|
+
* Start an external backend under activation ownership.
|
|
1480
|
+
* @param spec - task, provider, cancellation, and delivery policy.
|
|
1481
|
+
* @returns the accepted activation and its result/disposal capabilities.
|
|
1482
|
+
*/
|
|
1483
|
+
async startExternal(spec) {
|
|
1484
|
+
const parent = spec.request.parent;
|
|
1485
|
+
this.assertAdmitting(parent);
|
|
1486
|
+
const pendingId = brandString(randomUUID());
|
|
1487
|
+
const releaseHold = this.holdOwnership(parent, pendingId);
|
|
1488
|
+
let activation;
|
|
1489
|
+
try {
|
|
1490
|
+
const cwd = await this.resolveDirectory(spec.request, spec.signal);
|
|
1491
|
+
activation = await this.materialize({
|
|
1492
|
+
kind: "external",
|
|
1493
|
+
childId: pendingId,
|
|
1494
|
+
provider: spec.provider,
|
|
1495
|
+
parent,
|
|
1496
|
+
signal: spec.signal,
|
|
1497
|
+
delivery: spec.delivery,
|
|
1498
|
+
start: (signal) => this.host.startExternal(spec.provider, {
|
|
1499
|
+
...spec.request,
|
|
1500
|
+
cwd,
|
|
1501
|
+
label: spec.label,
|
|
1502
|
+
signal
|
|
1745
1503
|
})
|
|
1504
|
+
});
|
|
1505
|
+
this.assertAdmitting(parent);
|
|
1506
|
+
this.assertLiveParent(parent, activation.childId);
|
|
1507
|
+
spec.signal.throwIfAborted();
|
|
1508
|
+
if (spec.delivery === "parent") establishExternalCatalogChild(parent.session, activation.childId, spec.label);
|
|
1509
|
+
this.announce(activation);
|
|
1510
|
+
return this.receipt(activation);
|
|
1511
|
+
} catch (error) {
|
|
1512
|
+
if (activation !== void 0) try {
|
|
1513
|
+
await this.dispose(activation);
|
|
1514
|
+
} catch (cleanupError) {
|
|
1515
|
+
this.ctx.logger.warn(`subagent "${activation.childId}" admission rollback failed: ${failureMessage(cleanupError)}`);
|
|
1516
|
+
}
|
|
1517
|
+
throw error;
|
|
1518
|
+
} finally {
|
|
1519
|
+
releaseHold();
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
/** Capture the explicit or inherited directory while the parent still owns startup. */
|
|
1523
|
+
async resolveDirectory(request, signal) {
|
|
1524
|
+
const cwd = request.cwd !== void 0 && isAbsolute(request.cwd) ? request.cwd : resolve(await this.ctx.workingDirectory.ensure(request.parent, signal), request.cwd ?? ".");
|
|
1525
|
+
signal.throwIfAborted();
|
|
1526
|
+
return cwd;
|
|
1527
|
+
}
|
|
1528
|
+
/**
|
|
1529
|
+
* Deliver one model-authored message to a direct continuable child or to the
|
|
1530
|
+
* sender's direct parent. A missing direct child cold-resumes through the
|
|
1531
|
+
* ordinary continuation lifecycle.
|
|
1532
|
+
* @param sender - exact live Agent authorizing and originating the message.
|
|
1533
|
+
* @param targetId - durable direct-parent or direct-child session id.
|
|
1534
|
+
* @param content - model-authored content to deliver.
|
|
1535
|
+
* @param options - caller cancellation before acceptance.
|
|
1536
|
+
* @returns the accepted message's inbox id.
|
|
1537
|
+
*/
|
|
1538
|
+
async sendMessage(sender, targetId, content, options) {
|
|
1539
|
+
if (this.ctx.agents.get(sender.id) !== sender) throw new SubagentError("message delivery requires the exact live sender agent", "UNAUTHORIZED");
|
|
1540
|
+
this.assertAdmitting(sender);
|
|
1541
|
+
const senderActivation = this.resident.get(sender.id);
|
|
1542
|
+
if (senderActivation !== void 0 && senderActivation.kind === "local" && senderActivation.handle.agent === sender && senderActivation.parent.id === targetId) {
|
|
1543
|
+
options.signal.throwIfAborted();
|
|
1544
|
+
return this.sendToParent(senderActivation, sender, content);
|
|
1545
|
+
}
|
|
1546
|
+
if (sender.session.header.parentSession === targetId) throw new SubagentError(`agent "${sender.id}" is not a resident continuable child and cannot send to parent "${targetId}"`, "UNAUTHORIZED");
|
|
1547
|
+
return this.deliverToChild(sender, targetId, content, {
|
|
1548
|
+
signal: options.signal,
|
|
1549
|
+
delivery: "steer"
|
|
1550
|
+
});
|
|
1551
|
+
}
|
|
1552
|
+
/**
|
|
1553
|
+
* Queue one human-authored prompt as a distinct direct-child turn.
|
|
1554
|
+
* @param parent - exact live direct parent authorizing delivery.
|
|
1555
|
+
* @param childId - durable direct-child session id.
|
|
1556
|
+
* @param content - model-visible prompt blocks.
|
|
1557
|
+
* @param source - durable attribution for the human prompt.
|
|
1558
|
+
* @param signal - caller cancellation before inbox acceptance.
|
|
1559
|
+
* @returns the accepted durable message id.
|
|
1560
|
+
*/
|
|
1561
|
+
async queuePrompt(parent, childId, content, source, signal) {
|
|
1562
|
+
return this.deliverToChild(parent, childId, content, {
|
|
1563
|
+
source,
|
|
1564
|
+
signal,
|
|
1565
|
+
delivery: "queue"
|
|
1566
|
+
});
|
|
1567
|
+
}
|
|
1568
|
+
/**
|
|
1569
|
+
* Steer one host-authored prompt to a direct continuable child.
|
|
1570
|
+
* @param parent - exact live direct parent authorizing delivery.
|
|
1571
|
+
* @param childId - durable direct-child session id.
|
|
1572
|
+
* @param content - model-visible prompt blocks.
|
|
1573
|
+
* @param source - durable attribution for the host prompt.
|
|
1574
|
+
* @param signal - caller cancellation before inbox acceptance.
|
|
1575
|
+
* @returns the accepted durable message id.
|
|
1576
|
+
*/
|
|
1577
|
+
async steerPrompt(parent, childId, content, source, signal) {
|
|
1578
|
+
return this.deliverToChild(parent, childId, content, {
|
|
1579
|
+
source,
|
|
1580
|
+
signal,
|
|
1581
|
+
delivery: "steer"
|
|
1582
|
+
});
|
|
1583
|
+
}
|
|
1584
|
+
/** Admit local input synchronously before any teardown can begin. */
|
|
1585
|
+
deliver(activation, message, delivery) {
|
|
1586
|
+
if (activation.closing !== void 0) throw new SubagentError("subagent activation is being disposed; the message was not accepted", "ACTIVATION_CLOSING");
|
|
1587
|
+
const local = requireLocalActivation(activation);
|
|
1588
|
+
if (local.structured?.acceptsInput() === false) throw new SubagentError("subagent is submitting or has submitted its structured result; the message was not accepted", "INPUT_CLOSED");
|
|
1589
|
+
if (delivery === "steer") local.handle.agent.steer(message);
|
|
1590
|
+
else local.handle.agent.followup(message);
|
|
1591
|
+
}
|
|
1592
|
+
/**
|
|
1593
|
+
* Reject one child identity already owned by a live Agent or Session.
|
|
1594
|
+
* @param childId - proposed durable child session id.
|
|
1595
|
+
*/
|
|
1596
|
+
assertChildIdAvailable(childId) {
|
|
1597
|
+
if (this.resident.has(childId) || this.ctx.agents.get(childId) !== void 0 || this.ctx.get("sessions")?.get(childId) !== void 0) throw new SubagentError(`subagent "${childId}" already exists`, "DUPLICATE_CHILD");
|
|
1598
|
+
}
|
|
1599
|
+
/**
|
|
1600
|
+
* Pre-register `childId` in a continuation-managed parent's owned set so the
|
|
1601
|
+
* parent cannot settle while a caller is still establishing or resuming that
|
|
1602
|
+
* child. Returns a releaser for the failure path; it removes only a hold
|
|
1603
|
+
* this call added, and leaves ownership in place once a live Activation for
|
|
1604
|
+
* the child exists.
|
|
1605
|
+
* @param parent - the live direct parent the operation is admitted under.
|
|
1606
|
+
* @param childId - the durable child the operation addresses.
|
|
1607
|
+
* @returns the failure-path releaser; a no-op when nothing was added.
|
|
1608
|
+
*/
|
|
1609
|
+
holdOwnership(parent, childId) {
|
|
1610
|
+
const parentActivation = this.resident.get(parent.id);
|
|
1611
|
+
if (parentActivation === void 0 || activationAgent(parentActivation) !== parent) return () => {};
|
|
1612
|
+
if (parentActivation.closing !== void 0) throw new SubagentError(`subagent parent "${parent.id}" is being disposed; the child was not established`, "ACTIVATION_CLOSING");
|
|
1613
|
+
if (parentActivation.ownedChildren.has(childId)) return () => {};
|
|
1614
|
+
parentActivation.ownedChildren.add(childId);
|
|
1615
|
+
return () => {
|
|
1616
|
+
const live = this.resident.get(childId);
|
|
1617
|
+
/* v8 ignore next 4 -- reaching this arm needs another delivery to establish the child
|
|
1618
|
+
* between this operation's failure and its releaser running, which no test can schedule
|
|
1619
|
+
* deterministically: the ownership edge then belongs to that live Activation, so the
|
|
1620
|
+
* conservative keep leaves it for finishDisposal's releaseOwnership. */
|
|
1621
|
+
if (live !== void 0 && live.closing === void 0) return;
|
|
1622
|
+
if (parentActivation.ownedChildren.delete(childId)) this.wake(parentActivation);
|
|
1623
|
+
};
|
|
1624
|
+
}
|
|
1625
|
+
/**
|
|
1626
|
+
* Apply activation admission to a resident parent before delivering input.
|
|
1627
|
+
* @param parent - exact live Agent receiving the message.
|
|
1628
|
+
* @param message - durable user message to deliver.
|
|
1629
|
+
* @param delivery - receiving inbox destination.
|
|
1630
|
+
*/
|
|
1631
|
+
sendWaking(parent, message, delivery) {
|
|
1632
|
+
const parentActivation = this.resident.get(parent.id);
|
|
1633
|
+
if (parentActivation !== void 0 && activationAgent(parentActivation) === parent) {
|
|
1634
|
+
try {
|
|
1635
|
+
this.deliver(parentActivation, message, delivery);
|
|
1636
|
+
} finally {
|
|
1637
|
+
this.wake(parentActivation);
|
|
1638
|
+
}
|
|
1639
|
+
return;
|
|
1640
|
+
}
|
|
1641
|
+
if (delivery === "steer") parent.steer(message);
|
|
1642
|
+
else parent.followup(message);
|
|
1643
|
+
}
|
|
1644
|
+
/**
|
|
1645
|
+
* Reject new admission once the manager or this exact parent tree began draining.
|
|
1646
|
+
* @param agent - exact live Agent whose lineage determines admission.
|
|
1647
|
+
*/
|
|
1648
|
+
assertAdmitting(agent) {
|
|
1649
|
+
const closing = this.closingTeardownFor(agent);
|
|
1650
|
+
if (closing === void 0) return;
|
|
1651
|
+
throw new SubagentError(closing === "manager" ? "subagents are draining; the operation was not admitted" : `subagents below parent "${closing.id}" are draining; the operation was not admitted`, "DRAINING");
|
|
1652
|
+
}
|
|
1653
|
+
/**
|
|
1654
|
+
* Authorize one operation against the durable direct-parent lineage.
|
|
1655
|
+
* @param parent - exact live Agent claiming direct-parent authority.
|
|
1656
|
+
* @param childId - durable child session id addressed by the operation.
|
|
1657
|
+
* @param parentSession - durable direct-parent id recorded by the child.
|
|
1658
|
+
*/
|
|
1659
|
+
authorizeLineage(parent, childId, parentSession) {
|
|
1660
|
+
this.assertLiveParent(parent, childId);
|
|
1661
|
+
if (parentSession !== parent.id) throw new SubagentError(`subagent "${childId}" belongs to another parent session`, "UNAUTHORIZED");
|
|
1662
|
+
}
|
|
1663
|
+
/** Reject parent authority after the live registry entry changes. */
|
|
1664
|
+
assertLiveParent(parent, childId) {
|
|
1665
|
+
if (this.ctx.agents.get(parent.id) !== parent) throw new SubagentError(`subagent "${childId}" delivery requires the exact live parent agent`, "UNAUTHORIZED");
|
|
1666
|
+
}
|
|
1667
|
+
/**
|
|
1668
|
+
* Create or resume one child Agent and publish its Activation.
|
|
1669
|
+
* @param inputs - reconstruction and admission inputs for the residency epoch.
|
|
1670
|
+
* @returns the published process-local Activation.
|
|
1671
|
+
*/
|
|
1672
|
+
materialize(inputs) {
|
|
1673
|
+
this.assertAdmitting(inputs.parent);
|
|
1674
|
+
inputs.signal.throwIfAborted();
|
|
1675
|
+
const lineage = this.liveLineage(inputs.parent);
|
|
1676
|
+
const pool = this.resident.get(inputs.parent.id)?.pool ?? this.rootPool(inputs.parent);
|
|
1677
|
+
const releaseSlot = pool.reserve(this.maxActiveSubagents());
|
|
1678
|
+
const settled = Promise.withResolvers();
|
|
1679
|
+
const controller = new AbortController();
|
|
1680
|
+
const materialization = {
|
|
1681
|
+
lineage,
|
|
1682
|
+
settled: settled.promise,
|
|
1683
|
+
controller
|
|
1684
|
+
};
|
|
1685
|
+
this.materializations.add(materialization);
|
|
1686
|
+
return this.materializeTracked({
|
|
1687
|
+
...inputs,
|
|
1688
|
+
signal: AbortSignal.any([inputs.signal, controller.signal])
|
|
1689
|
+
}, lineage, pool, releaseSlot).catch((error) => {
|
|
1690
|
+
releaseSlot();
|
|
1691
|
+
if (error instanceof Error && error.name === "AbortError") this.assertAdmitting(inputs.parent);
|
|
1692
|
+
throw error;
|
|
1693
|
+
}).finally(() => {
|
|
1694
|
+
this.materializations.delete(materialization);
|
|
1695
|
+
settled.resolve();
|
|
1696
|
+
});
|
|
1697
|
+
}
|
|
1698
|
+
/**
|
|
1699
|
+
* Stop and release one Activation through its memoized close transaction.
|
|
1700
|
+
* @param activation - exact residency epoch to close.
|
|
1701
|
+
* @returns the shared close transaction.
|
|
1702
|
+
*/
|
|
1703
|
+
dispose(activation) {
|
|
1704
|
+
return this.close(activation, () => this.finishDisposal(activation, true));
|
|
1705
|
+
}
|
|
1706
|
+
/** Close admission synchronously and share one release, including startup rollback. */
|
|
1707
|
+
close(activation, release) {
|
|
1708
|
+
if (activation.closing !== void 0) return activation.closing;
|
|
1709
|
+
const completion = Promise.withResolvers();
|
|
1710
|
+
activation.closing = completion.promise;
|
|
1711
|
+
release().then(completion.resolve, completion.reject);
|
|
1712
|
+
return completion.promise;
|
|
1713
|
+
}
|
|
1714
|
+
/** Dispose independent roots and report every branch failure after all settle. */
|
|
1715
|
+
async disposeRoots(roots, failureSubject) {
|
|
1716
|
+
const reasons = (await Promise.all(roots.map(async (activation) => {
|
|
1717
|
+
try {
|
|
1718
|
+
await this.dispose(activation);
|
|
1719
|
+
return;
|
|
1720
|
+
} catch (error) {
|
|
1721
|
+
return error;
|
|
1722
|
+
}
|
|
1723
|
+
}))).filter((failure) => failure !== void 0);
|
|
1724
|
+
if (reasons.length > 0) throw new SubagentError(`subagent teardown failed for ${reasons.length} ${failureSubject}: ` + reasons.map((reason) => failureMessage(reason)).join("; "), "ACTIVATION_TEARDOWN_FAILED");
|
|
1725
|
+
}
|
|
1726
|
+
/** Return the retained member set for one exact scoped-teardown root. */
|
|
1727
|
+
closingMembers(root) {
|
|
1728
|
+
const existing = this.closingScopes.get(root);
|
|
1729
|
+
if (existing !== void 0) return existing;
|
|
1730
|
+
const members = /* @__PURE__ */ new Set();
|
|
1731
|
+
this.closingScopes.set(root, members);
|
|
1732
|
+
return members;
|
|
1733
|
+
}
|
|
1734
|
+
/** Return the exact currently resolvable ancestry from `agent` upward. */
|
|
1735
|
+
liveLineage(agent) {
|
|
1736
|
+
const lineage = [agent];
|
|
1737
|
+
const seen = new Set([agent.id]);
|
|
1738
|
+
let parentSession = agent.session.header.parentSession;
|
|
1739
|
+
while (parentSession !== void 0) {
|
|
1740
|
+
const parent = this.ctx.agents.get(parentSession);
|
|
1741
|
+
if (parent === void 0 || seen.has(parent.id)) break;
|
|
1742
|
+
lineage.push(parent);
|
|
1743
|
+
seen.add(parent.id);
|
|
1744
|
+
parentSession = parent.session.header.parentSession;
|
|
1745
|
+
}
|
|
1746
|
+
return lineage;
|
|
1747
|
+
}
|
|
1748
|
+
/** Return the teardown that closed continuable admission for this agent's lineage. */
|
|
1749
|
+
closingTeardownFor(agent) {
|
|
1750
|
+
if (this.draining) return "manager";
|
|
1751
|
+
const lineage = this.liveLineage(agent);
|
|
1752
|
+
for (const [root, members] of this.closingScopes) if (members.has(agent) || lineage.includes(root)) return root;
|
|
1753
|
+
}
|
|
1754
|
+
/** Resolve a root's pool once; descendants inherit their resident parent's pool directly. */
|
|
1755
|
+
rootPool(parent) {
|
|
1756
|
+
let pool = this.rootPools.get(parent);
|
|
1757
|
+
if (pool === void 0) {
|
|
1758
|
+
pool = new ActivationPool();
|
|
1759
|
+
this.rootPools.set(parent, pool);
|
|
1760
|
+
}
|
|
1761
|
+
return pool;
|
|
1762
|
+
}
|
|
1763
|
+
/** Perform one tracked materialization through publication or rollback. */
|
|
1764
|
+
async materializeTracked(inputs, parentLineage, pool, releaseSlot) {
|
|
1765
|
+
const { provider, parent } = inputs;
|
|
1766
|
+
let childId = inputs.childId;
|
|
1767
|
+
inputs.signal.throwIfAborted();
|
|
1768
|
+
let resource;
|
|
1769
|
+
if (inputs.kind === "external") {
|
|
1770
|
+
const controller = new AbortController();
|
|
1771
|
+
const cancelStartup = () => {
|
|
1772
|
+
controller.abort(inputs.signal.reason);
|
|
1773
|
+
};
|
|
1774
|
+
inputs.signal.addEventListener("abort", cancelStartup, { once: true });
|
|
1775
|
+
try {
|
|
1776
|
+
const run = await inputs.start(controller.signal);
|
|
1777
|
+
resource = {
|
|
1778
|
+
kind: "external",
|
|
1779
|
+
run,
|
|
1780
|
+
controller
|
|
1781
|
+
};
|
|
1782
|
+
run.result.catch(() => void 0);
|
|
1783
|
+
childId = run.id;
|
|
1784
|
+
try {
|
|
1785
|
+
this.assertChildIdAvailable(childId);
|
|
1786
|
+
} catch (error) {
|
|
1787
|
+
await run.dispose();
|
|
1788
|
+
throw error;
|
|
1789
|
+
}
|
|
1790
|
+
} finally {
|
|
1791
|
+
inputs.signal.removeEventListener("abort", cancelStartup);
|
|
1792
|
+
}
|
|
1793
|
+
} else {
|
|
1794
|
+
const { create } = inputs;
|
|
1795
|
+
let structured;
|
|
1796
|
+
const setup = async (childCtx, child) => {
|
|
1797
|
+
if (inputs.composition.agentPreset !== void 0) await applyChildComposition(childCtx, parent, inputs.composition);
|
|
1798
|
+
if (create !== void 0) {
|
|
1799
|
+
if (inputs.composition.agentPreset !== void 0) child.session.append("agent-preset/selected", { agentPreset: inputs.composition.agentPreset });
|
|
1800
|
+
child.session.append("subagent/descriptor", create.descriptor);
|
|
1801
|
+
appendDelegatedPolicyOverrides(child.session, create.delegatedPolicies);
|
|
1802
|
+
const workingDirectory = childCtx.get("workingDirectory");
|
|
1803
|
+
if (workingDirectory === void 0) throw new Error("local subagents require the working-directory service");
|
|
1804
|
+
await workingDirectory.set(child, create.cwd, inputs.signal);
|
|
1805
|
+
}
|
|
1806
|
+
if (inputs.composition.agentPreset === void 0) await applyChildComposition(childCtx, parent, inputs.composition);
|
|
1807
|
+
if (inputs.outputSchema !== void 0) structured = attachStructuredRuntime(childCtx, inputs.outputSchema, () => this.resident.get(childId)?.ownedChildren.size === 0 && child.inbox.nextStep.length === 0 && child.inbox.nextTurn.length === 0);
|
|
1808
|
+
};
|
|
1809
|
+
const handle = create === void 0 ? await this.ownerCtx.agents.resume({
|
|
1810
|
+
resumeSessionId: childId,
|
|
1811
|
+
parentAgent: parent,
|
|
1812
|
+
agentOptions: inputs.agentOptions,
|
|
1813
|
+
signal: inputs.signal,
|
|
1814
|
+
setup
|
|
1815
|
+
}) : await this.ownerCtx.agents.create({
|
|
1816
|
+
sessionId: childId,
|
|
1817
|
+
parentAgent: parent,
|
|
1818
|
+
meta: create.meta,
|
|
1819
|
+
...create.seed === void 0 ? {} : { seed: create.seed },
|
|
1820
|
+
inheritedEventCount: create.inheritedEventCount,
|
|
1821
|
+
agentOptions: inputs.agentOptions,
|
|
1822
|
+
signal: inputs.signal,
|
|
1823
|
+
setup
|
|
1824
|
+
});
|
|
1825
|
+
resource = {
|
|
1826
|
+
kind: "local",
|
|
1827
|
+
handle,
|
|
1828
|
+
outputStart: handle.agent.session.seq,
|
|
1829
|
+
structured,
|
|
1830
|
+
failureAt: void 0,
|
|
1831
|
+
poke: Promise.withResolvers()
|
|
1832
|
+
};
|
|
1833
|
+
}
|
|
1834
|
+
const observer = this.host.observeActivation(provider, childId, parent);
|
|
1835
|
+
const activation = {
|
|
1836
|
+
pool,
|
|
1837
|
+
releaseSlot,
|
|
1838
|
+
childId,
|
|
1839
|
+
...resource,
|
|
1840
|
+
parent,
|
|
1841
|
+
delivery: inputs.delivery ?? "parent",
|
|
1842
|
+
result: Promise.withResolvers(),
|
|
1843
|
+
closing: void 0,
|
|
1844
|
+
ancestry: new WeakSet(resource.kind === "external" ? parentLineage : [resource.handle.agent, ...parentLineage]),
|
|
1845
|
+
ownedChildren: /* @__PURE__ */ new Set(),
|
|
1846
|
+
observer,
|
|
1847
|
+
announced: false
|
|
1848
|
+
};
|
|
1849
|
+
activation.result.promise.catch(() => void 0);
|
|
1850
|
+
this.resident.set(childId, activation);
|
|
1851
|
+
try {
|
|
1852
|
+
this.assertAdmitting(parent);
|
|
1853
|
+
inputs.signal.throwIfAborted();
|
|
1854
|
+
if (this.ctx.agents.get(parent.id) !== parent) throw new SubagentError("subagent parent is no longer live", "UNAUTHORIZED");
|
|
1855
|
+
this.acquireOwnership(parent, childId);
|
|
1856
|
+
const wakeSettlement = () => {
|
|
1857
|
+
this.wake(activation);
|
|
1746
1858
|
};
|
|
1859
|
+
if (activation.kind === "local") {
|
|
1860
|
+
observeLocalFailure(activation, wakeSettlement);
|
|
1861
|
+
activation.handle.agent.ctx.on("agent/inbox/inserted", wakeSettlement);
|
|
1862
|
+
activation.handle.agent.ctx.on("agent/inbox/claimed", wakeSettlement);
|
|
1863
|
+
activation.handle.agent.ctx.on("agent/inbox/discarded", wakeSettlement);
|
|
1864
|
+
}
|
|
1865
|
+
observer.start(activationAgent(activation));
|
|
1747
1866
|
} catch (error) {
|
|
1748
|
-
|
|
1867
|
+
/* v8 ignore next -- rollback failure must not mask the admission failure
|
|
1868
|
+
* that prevented this operation from returning an accepted message id. */
|
|
1869
|
+
await this.rollbackUnpublished(activation, error).catch(() => void 0);
|
|
1749
1870
|
throw error;
|
|
1750
1871
|
}
|
|
1872
|
+
return activation;
|
|
1751
1873
|
}
|
|
1752
1874
|
/**
|
|
1753
|
-
*
|
|
1754
|
-
*
|
|
1755
|
-
* ordinary continuation lifecycle.
|
|
1756
|
-
* @param sender - exact live Agent authorizing and originating the message.
|
|
1757
|
-
* @param targetId - durable direct-parent or direct-child session id.
|
|
1758
|
-
* @param content - model-authored content to deliver.
|
|
1759
|
-
* @param options - caller cancellation before acceptance.
|
|
1760
|
-
* @returns the accepted message's inbox id.
|
|
1875
|
+
* Begin settlement observation after initial acceptance is recorded.
|
|
1876
|
+
* @param activation - accepted activation to observe.
|
|
1761
1877
|
*/
|
|
1762
|
-
|
|
1763
|
-
if (
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1878
|
+
announce(activation) {
|
|
1879
|
+
if (activation.announced) return;
|
|
1880
|
+
activation.announced = true;
|
|
1881
|
+
if (activation.kind === "local") this.watchLocalSettlement(activation);
|
|
1882
|
+
else {
|
|
1883
|
+
const finish = () => this.close(activation, () => this.finishDisposal(activation, false));
|
|
1884
|
+
activation.run.result.then(finish, finish).catch((error) => {
|
|
1885
|
+
this.reportTeardownFailure(activation, error);
|
|
1886
|
+
});
|
|
1769
1887
|
}
|
|
1770
|
-
if (sender.session.header.parentSession === targetId) throw new SubagentError(`agent "${sender.id}" is not a resident continuable child and cannot send to parent "${targetId}"`, "UNAUTHORIZED");
|
|
1771
|
-
return this.deliverToChild(sender, targetId, content, {
|
|
1772
|
-
signal: options.signal,
|
|
1773
|
-
delivery: "steer"
|
|
1774
|
-
});
|
|
1775
1888
|
}
|
|
1776
|
-
/**
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
source,
|
|
1788
|
-
signal,
|
|
1789
|
-
delivery: "queue"
|
|
1889
|
+
/** Release an Activation whose start edge was not published. */
|
|
1890
|
+
rollbackUnpublished(activation, error) {
|
|
1891
|
+
return this.close(activation, async () => {
|
|
1892
|
+
try {
|
|
1893
|
+
await (activation.kind === "local" ? activation.handle.dispose() : activation.run.dispose());
|
|
1894
|
+
} finally {
|
|
1895
|
+
this.resident.delete(activation.childId);
|
|
1896
|
+
activation.releaseSlot();
|
|
1897
|
+
this.releaseOwnership(activation.childId);
|
|
1898
|
+
activation.result.reject(error);
|
|
1899
|
+
}
|
|
1790
1900
|
});
|
|
1791
1901
|
}
|
|
1792
|
-
/**
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
*/
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1902
|
+
/** Register the child in a continuation-managed parent's owned set. */
|
|
1903
|
+
acquireOwnership(parent, childId) {
|
|
1904
|
+
const parentActivation = this.resident.get(parent.id);
|
|
1905
|
+
if (parentActivation === void 0) return;
|
|
1906
|
+
if (parentActivation.closing !== void 0) throw new SubagentError(`subagent parent "${parent.id}" is being disposed; the child was not established`, "ACTIVATION_CLOSING");
|
|
1907
|
+
parentActivation.ownedChildren.add(childId);
|
|
1908
|
+
this.wake(parentActivation);
|
|
1909
|
+
}
|
|
1910
|
+
/** Remove one child from its live owner's set and let that owner re-check settlement. */
|
|
1911
|
+
releaseOwnership(childId) {
|
|
1912
|
+
for (const candidate of this.resident.values()) if (candidate.ownedChildren.delete(childId)) this.wake(candidate);
|
|
1913
|
+
}
|
|
1914
|
+
/** Let a settlement watcher re-check residency after relevant state changes. */
|
|
1915
|
+
wake(activation) {
|
|
1916
|
+
if (activation.kind === "external") return;
|
|
1917
|
+
activation.poke.resolve();
|
|
1918
|
+
activation.poke = Promise.withResolvers();
|
|
1919
|
+
}
|
|
1920
|
+
/** Follow one Activation to natural settlement. */
|
|
1921
|
+
watchLocalSettlement(activation) {
|
|
1922
|
+
(async () => {
|
|
1923
|
+
while (true) {
|
|
1924
|
+
const idleObservation = activation.poke;
|
|
1925
|
+
await activation.handle.agent.whenIdle();
|
|
1926
|
+
if (activation.closing !== void 0) return;
|
|
1927
|
+
const readiness = await this.locks.run(activation.childId, () => Promise.resolve(this.settlementState(activation, idleObservation)));
|
|
1928
|
+
if (readiness === "closed") return;
|
|
1929
|
+
if (readiness === "retry") continue;
|
|
1930
|
+
if (readiness === "wait") {
|
|
1931
|
+
await idleObservation.promise;
|
|
1932
|
+
continue;
|
|
1933
|
+
}
|
|
1934
|
+
const finalSeq = activation.handle.agent.session.seq;
|
|
1935
|
+
await this.flushFinalState(activation);
|
|
1936
|
+
const attempt = await this.locks.run(activation.childId, () => {
|
|
1937
|
+
const state = this.settlementState(activation, idleObservation);
|
|
1938
|
+
if (state !== "ready") return Promise.resolve(state);
|
|
1939
|
+
if (activation.handle.agent.session.seq !== finalSeq) return Promise.resolve("retry");
|
|
1940
|
+
let done;
|
|
1941
|
+
try {
|
|
1942
|
+
activation.handle.agent.runMaintenance(() => {
|
|
1943
|
+
done = this.close(activation, () => this.finishDisposal(activation, false));
|
|
1944
|
+
return Promise.resolve();
|
|
1945
|
+
});
|
|
1946
|
+
} catch (_error) {
|
|
1947
|
+
return Promise.resolve("retry");
|
|
1948
|
+
}
|
|
1949
|
+
return Promise.resolve({ done });
|
|
1950
|
+
});
|
|
1951
|
+
if (typeof attempt === "string") {
|
|
1952
|
+
if (attempt === "closed") return;
|
|
1953
|
+
continue;
|
|
1954
|
+
}
|
|
1955
|
+
try {
|
|
1956
|
+
await attempt.done;
|
|
1957
|
+
} catch (error) {
|
|
1958
|
+
this.reportTeardownFailure(activation, error);
|
|
1959
|
+
}
|
|
1960
|
+
return;
|
|
1961
|
+
}
|
|
1962
|
+
})();
|
|
1963
|
+
}
|
|
1964
|
+
/** Report disposal failure once after the natural settlement observer finishes. */
|
|
1965
|
+
reportTeardownFailure(activation, error) {
|
|
1966
|
+
this.ctx.logger.warn(`subagent "${activation.childId}" activation teardown failed: ${failureMessage(error)}`);
|
|
1967
|
+
}
|
|
1968
|
+
/** Check pending input and owned children before closing admission. */
|
|
1969
|
+
settlementState(activation, observation) {
|
|
1970
|
+
if (activation.closing !== void 0) return "closed";
|
|
1971
|
+
if (activation.poke !== observation) return "retry";
|
|
1972
|
+
const inbox = activation.handle.agent.inbox;
|
|
1973
|
+
if (activation.ownedChildren.size > 0) return "wait";
|
|
1974
|
+
if (activation.failureAt === void 0 && (inbox.nextTurn.length > 0 || inbox.nextStep.length > 0)) return "wait";
|
|
1975
|
+
return "ready";
|
|
1976
|
+
}
|
|
1977
|
+
/** Propagate stop synchronously and await descendant startup rollback before release. */
|
|
1978
|
+
async finishDisposal(activation, stop) {
|
|
1979
|
+
this.wake(activation);
|
|
1980
|
+
const { childId } = activation;
|
|
1981
|
+
const failures = [];
|
|
1982
|
+
let result = {
|
|
1983
|
+
output: [],
|
|
1984
|
+
stopReason: "error"
|
|
1985
|
+
};
|
|
1986
|
+
let resultFailure;
|
|
1987
|
+
let externalDisposal;
|
|
1988
|
+
const child = activationAgent(activation);
|
|
1989
|
+
const detail = (error) => child === void 0 ? failureMessage(error) : errorChain(error);
|
|
1990
|
+
try {
|
|
1991
|
+
if (activation.kind === "external") {
|
|
1992
|
+
if (stop) {
|
|
1993
|
+
activation.controller.abort({ kind: "parent" });
|
|
1994
|
+
externalDisposal = Promise.resolve().then(() => activation.run.dispose());
|
|
1995
|
+
externalDisposal.catch(() => void 0);
|
|
1996
|
+
}
|
|
1997
|
+
result = await activation.run.result;
|
|
1998
|
+
} else {
|
|
1999
|
+
if (stop) {
|
|
2000
|
+
activation.handle.agent.cancel({ kind: "parent" });
|
|
2001
|
+
const idle = activation.handle.agent.whenIdle();
|
|
2002
|
+
const children = [...activation.ownedChildren].map((child) => this.resident.get(child)).filter((child) => child !== void 0);
|
|
2003
|
+
const pending = [...this.materializations].filter((item) => item.lineage.includes(activation.handle.agent));
|
|
2004
|
+
const childDisposals = children.map((child) => this.dispose(child));
|
|
2005
|
+
for (const item of pending) item.controller.abort();
|
|
2006
|
+
childDisposals.push(...pending.map((item) => item.settled));
|
|
2007
|
+
const reasons = (await Promise.all(childDisposals.map(async (disposal) => {
|
|
2008
|
+
try {
|
|
2009
|
+
await disposal;
|
|
2010
|
+
return;
|
|
2011
|
+
} catch (error) {
|
|
2012
|
+
return error;
|
|
2013
|
+
}
|
|
2014
|
+
}))).filter((reason) => reason !== void 0);
|
|
2015
|
+
if (reasons.length > 0) failures.push(new SubagentError(`subagent "${childId}" child teardown failed: ${reasons.map((reason) => failureMessage(reason)).join("; ")}`, "ACTIVATION_TEARDOWN_FAILED"));
|
|
2016
|
+
await idle;
|
|
2017
|
+
await this.flushFinalState(activation);
|
|
2018
|
+
}
|
|
2019
|
+
result = captureLocalResult(activation);
|
|
2020
|
+
}
|
|
2021
|
+
} catch (error) {
|
|
2022
|
+
resultFailure = { error };
|
|
2023
|
+
failures.push(new SubagentError(`subagent "${childId}" activation teardown failed: ${detail(error)}`, "ACTIVATION_TEARDOWN_FAILED", { cause: error }));
|
|
2024
|
+
}
|
|
2025
|
+
try {
|
|
2026
|
+
if (activation.kind === "local") await activation.handle.dispose();
|
|
2027
|
+
else await (externalDisposal ?? activation.run.dispose());
|
|
2028
|
+
} catch (error) {
|
|
2029
|
+
failures.push(new SubagentError(`subagent "${childId}" activation handle disposal failed: ${detail(error)}`, "ACTIVATION_TEARDOWN_FAILED", { cause: error }));
|
|
2030
|
+
}
|
|
2031
|
+
let failure;
|
|
2032
|
+
if (failures.length === 1) failure = failures[0];
|
|
2033
|
+
else if (failures.length > 1) failure = new SubagentError(`subagent "${childId}" activation teardown failed at ${failures.length} boundaries: ` + failures.map((item) => detail(item)).join("; "), "ACTIVATION_TEARDOWN_FAILED", { cause: new AggregateError(failures) });
|
|
2034
|
+
this.resident.delete(childId);
|
|
2035
|
+
activation.releaseSlot();
|
|
2036
|
+
const terminal = failure === void 0 ? result : {
|
|
2037
|
+
output: [],
|
|
2038
|
+
stopReason: "error"
|
|
2039
|
+
};
|
|
2040
|
+
this.notifySettlement(activation, result);
|
|
2041
|
+
this.releaseOwnership(childId);
|
|
2042
|
+
activation.observer.settle(terminal);
|
|
2043
|
+
if (resultFailure === void 0) activation.result.resolve(result);
|
|
2044
|
+
else activation.result.reject(resultFailure.error);
|
|
2045
|
+
if (failure !== void 0) throw failure;
|
|
2046
|
+
}
|
|
2047
|
+
/** Deliver the captured execution result to the durable direct parent after cleanup. */
|
|
2048
|
+
notifySettlement(activation, terminal) {
|
|
2049
|
+
if (!activation.announced || activation.delivery === "caller") return;
|
|
2050
|
+
try {
|
|
2051
|
+
const parent = this.ctx.agents.get(activation.parent.id);
|
|
2052
|
+
if (parent !== activation.parent) return;
|
|
2053
|
+
const message = createSettlementMessage(activation.childId, terminal, activationAgent(activation) !== void 0);
|
|
2054
|
+
if (this.closingTeardownFor(parent) !== void 0) {
|
|
2055
|
+
parent.inject(message);
|
|
2056
|
+
return;
|
|
2057
|
+
}
|
|
2058
|
+
this.sendWaking(parent, message, parent.status === "idle" ? "queue" : "steer");
|
|
2059
|
+
} catch (error) {
|
|
2060
|
+
this.ctx.logger.warn(`subagent "${activation.childId}" settlement notice was not delivered to its parent: ` + errorChain(error));
|
|
2061
|
+
}
|
|
2062
|
+
}
|
|
2063
|
+
/** Request a best-effort final session flush before closing natural-settlement admission. */
|
|
2064
|
+
async flushFinalState(activation) {
|
|
2065
|
+
try {
|
|
2066
|
+
await activation.handle.agent.ctx.sessions.flush(activation.handle.agent.session);
|
|
2067
|
+
} catch (error) {
|
|
2068
|
+
this.ctx.logger.warn(`subagent "${activation.childId}" best-effort final session flush failed; the persisted state may be unavailable or stale on resume: ${errorChain(error)}`);
|
|
2069
|
+
}
|
|
2070
|
+
}
|
|
2071
|
+
/** Return capabilities bound to an exact activation instead of a future same-id instance. */
|
|
2072
|
+
receipt(activation) {
|
|
2073
|
+
return {
|
|
2074
|
+
childId: activation.childId,
|
|
2075
|
+
result: activation.result.promise,
|
|
2076
|
+
dispose: () => this.dispose(activation)
|
|
2077
|
+
};
|
|
1807
2078
|
}
|
|
1808
2079
|
/** Route one parent-originated delivery through residency and cold resume. */
|
|
1809
2080
|
async deliverToChild(parent, childId, content, options) {
|
|
1810
|
-
this.
|
|
1811
|
-
const releaseHold = this.
|
|
2081
|
+
this.assertAdmitting(parent);
|
|
2082
|
+
const releaseHold = this.holdOwnership(parent, childId);
|
|
1812
2083
|
try {
|
|
1813
2084
|
return await this.deliverFollowup(parent, childId, content, options);
|
|
1814
2085
|
} catch (error) {
|
|
@@ -1819,47 +2090,38 @@ var SubagentContinuationManager = class {
|
|
|
1819
2090
|
/** The delivery loop behind {@link deliverToChild}, run under the parent hold. */
|
|
1820
2091
|
async deliverFollowup(parent, childId, content, options) {
|
|
1821
2092
|
while (true) {
|
|
1822
|
-
const live = await this.
|
|
1823
|
-
const activation = this.
|
|
2093
|
+
const live = await this.locks.run(childId, async () => {
|
|
2094
|
+
const activation = this.resident.get(childId);
|
|
1824
2095
|
if (activation === void 0) return this.coldResume(parent, childId, content, options);
|
|
1825
|
-
|
|
2096
|
+
requireLocalActivation(activation);
|
|
2097
|
+
const disposal = activation.closing;
|
|
1826
2098
|
/* v8 ignore next 3 -- the send-versus-dispose cutoff needs a delivery to
|
|
1827
2099
|
* observe the transaction inside the same critical section that opened it. */
|
|
1828
2100
|
if (disposal !== void 0) return disposal.then(() => void 0, () => void 0);
|
|
1829
2101
|
if (contentHasImage(content)) {
|
|
1830
|
-
await this.assertImageCapable(activation.handle.agent, options.signal);
|
|
1831
|
-
if (activation.
|
|
1832
|
-
await Promise.allSettled([activation.
|
|
2102
|
+
await this.assertImageCapable(requireLocalActivation(activation).handle.agent, options.signal);
|
|
2103
|
+
if (activation.closing !== void 0) {
|
|
2104
|
+
await Promise.allSettled([activation.closing]);
|
|
1833
2105
|
return;
|
|
1834
2106
|
}
|
|
1835
2107
|
}
|
|
1836
2108
|
const messageId = this.submitAdmitted(activation, content, options, parent);
|
|
1837
|
-
activation
|
|
2109
|
+
this.announce(activation);
|
|
1838
2110
|
return messageId;
|
|
1839
2111
|
});
|
|
1840
2112
|
/* v8 ignore start -- only a delivery that lost the disposal cutoff retries. */
|
|
1841
2113
|
if (live !== void 0) return live;
|
|
1842
|
-
this.
|
|
2114
|
+
this.assertAdmitting(parent);
|
|
1843
2115
|
options.signal.throwIfAborted();
|
|
1844
2116
|
}
|
|
1845
2117
|
}
|
|
1846
|
-
/**
|
|
1847
|
-
* Interrupt one live continuable child's current turn. Admission is
|
|
1848
|
-
* synchronous and the cancellation effect is asynchronous. An absent or
|
|
1849
|
-
* already-closing target is an accepted no-op after authority checks.
|
|
1850
|
-
* @param targetSessionId - the durable child session id to interrupt.
|
|
1851
|
-
* @param authority - the human parent address or exact live ancestor Agent.
|
|
1852
|
-
*/
|
|
1853
|
-
interrupt(targetSessionId, authority) {
|
|
1854
|
-
this.activations.interrupt(targetSessionId, authority);
|
|
1855
|
-
}
|
|
1856
2118
|
/** Deliver one resident continuable child's message to its live direct parent. */
|
|
1857
2119
|
sendToParent(activation, sender, content) {
|
|
1858
2120
|
/* v8 ignore next 6 -- only synchronous re-entrant teardown can open this
|
|
1859
2121
|
* transaction between exact-agent authorization and this no-await span. */
|
|
1860
|
-
if (activation.
|
|
1861
|
-
const parent = this.ctx.agents.get(activation.
|
|
1862
|
-
if (parent
|
|
2122
|
+
if (activation.closing !== void 0) throw new SubagentError(`subagent "${sender.id}" activation is being disposed; the message was not delivered`, "ACTIVATION_CLOSING");
|
|
2123
|
+
const parent = this.ctx.agents.get(activation.parent.id);
|
|
2124
|
+
if (parent !== activation.parent) throw new SubagentError("direct parent is not live; the message was not delivered", "PARENT_UNAVAILABLE");
|
|
1863
2125
|
const message = createAgentMessage(sender, content);
|
|
1864
2126
|
this.sendAgentMessage(parent, message);
|
|
1865
2127
|
return message.id;
|
|
@@ -1867,30 +2129,11 @@ var SubagentContinuationManager = class {
|
|
|
1867
2129
|
/** Send one Agent message while translating only the target's own rejection. */
|
|
1868
2130
|
sendAgentMessage(parent, message) {
|
|
1869
2131
|
try {
|
|
1870
|
-
this.
|
|
2132
|
+
this.sendWaking(parent, message, "steer");
|
|
1871
2133
|
} catch (error) {
|
|
1872
2134
|
throw new SubagentError("direct parent is not live; the message was not delivered", "PARENT_UNAVAILABLE", { cause: error });
|
|
1873
2135
|
}
|
|
1874
2136
|
}
|
|
1875
|
-
/** Close manager-wide admission and release every live Activation. */
|
|
1876
|
-
async drain() {
|
|
1877
|
-
await this.activations.drain();
|
|
1878
|
-
}
|
|
1879
|
-
/**
|
|
1880
|
-
* Stop only the continuable descendants of exact live host-owned parents.
|
|
1881
|
-
* @param parents - exact live roots whose continuable descendants must stop.
|
|
1882
|
-
*/
|
|
1883
|
-
async drainDescendants(parents) {
|
|
1884
|
-
await this.activations.drainDescendants(parents);
|
|
1885
|
-
}
|
|
1886
|
-
/**
|
|
1887
|
-
* Release selected resident direct children of one exact live parent.
|
|
1888
|
-
* @param parent - exact live direct parent authorizing the selected release.
|
|
1889
|
-
* @param childIds - durable direct-child ids to release when resident.
|
|
1890
|
-
*/
|
|
1891
|
-
async drainChildren(parent, childIds) {
|
|
1892
|
-
await this.activations.drainChildren(parent, childIds);
|
|
1893
|
-
}
|
|
1894
2137
|
/**
|
|
1895
2138
|
* Cold-resume a persisted child and submit the waiting turn. The descriptor
|
|
1896
2139
|
* supplies every reconstruction input; no subagent provider is dispatched.
|
|
@@ -1911,13 +2154,18 @@ var SubagentContinuationManager = class {
|
|
|
1911
2154
|
throw new SubagentError(`subagent "${childId}" is unavailable`, "NOT_RESUMABLE", { cause: error });
|
|
1912
2155
|
}
|
|
1913
2156
|
const source = __addDisposableResource$2(env_1, observation, false);
|
|
1914
|
-
this.
|
|
1915
|
-
this.
|
|
1916
|
-
const
|
|
2157
|
+
this.assertAdmitting(parent);
|
|
2158
|
+
this.authorizeLineage(parent, childId, source.header.parentSession);
|
|
2159
|
+
const ownEvents = source.events.slice(source.inheritedEventCount);
|
|
2160
|
+
const descriptor = foldSubagentDescriptor(ownEvents);
|
|
1917
2161
|
if (descriptor === void 0 || descriptor.mode !== "continuable") throw new SubagentError(`subagent "${childId}" has no supported continuation state and cannot be resumed; choose a different target`, "NOT_RESUMABLE");
|
|
1918
2162
|
let activation;
|
|
1919
2163
|
try {
|
|
1920
|
-
|
|
2164
|
+
const independentlySelected = hasIndependentPresetSelection(ownEvents);
|
|
2165
|
+
const agentPreset = independentlySelected ? source.projections?.values.agentPreset : void 0;
|
|
2166
|
+
if (independentlySelected && typeof agentPreset !== "string") throw new SubagentError(`subagent "${childId}" has no agent preset projection for restoration`, "NOT_RESUMABLE");
|
|
2167
|
+
activation = await this.materialize({
|
|
2168
|
+
kind: "local",
|
|
1921
2169
|
childId,
|
|
1922
2170
|
provider: descriptor.provider,
|
|
1923
2171
|
parent,
|
|
@@ -1927,6 +2175,7 @@ var SubagentContinuationManager = class {
|
|
|
1927
2175
|
...descriptor.agentReasoningEffort !== void 0 ? { reasoningEffort: ReasoningEffortId(descriptor.agentReasoningEffort) } : {}
|
|
1928
2176
|
},
|
|
1929
2177
|
composition: {
|
|
2178
|
+
agentPreset: agentPreset ?? void 0,
|
|
1930
2179
|
persona: descriptor.persona,
|
|
1931
2180
|
toolFilter: descriptor.toolFilter
|
|
1932
2181
|
},
|
|
@@ -1949,16 +2198,16 @@ var SubagentContinuationManager = class {
|
|
|
1949
2198
|
async submitMaterialized(activation, content, options, parent, commit) {
|
|
1950
2199
|
try {
|
|
1951
2200
|
if (contentHasImage(content)) {
|
|
1952
|
-
await this.assertImageCapable(activation.handle.agent, options.signal);
|
|
1953
|
-
if (activation.
|
|
2201
|
+
await this.assertImageCapable(requireLocalActivation(activation).handle.agent, options.signal);
|
|
2202
|
+
if (activation.closing !== void 0) throw new SubagentError(`subagent "${activation.childId}" is closing`, "ACTIVATION_CLOSING");
|
|
1954
2203
|
}
|
|
1955
2204
|
const messageId = this.submitAdmitted(activation, content, options, parent);
|
|
1956
2205
|
commit?.();
|
|
1957
|
-
activation
|
|
2206
|
+
this.announce(activation);
|
|
1958
2207
|
return messageId;
|
|
1959
2208
|
} catch (error) {
|
|
1960
2209
|
try {
|
|
1961
|
-
await this.
|
|
2210
|
+
await this.dispose(activation);
|
|
1962
2211
|
} catch (cleanupError) {
|
|
1963
2212
|
this.ctx.logger.warn(`subagent continuation: disposal after admission or catalog append failure also failed: ${String(cleanupError)}`);
|
|
1964
2213
|
}
|
|
@@ -1971,7 +2220,16 @@ var SubagentContinuationManager = class {
|
|
|
1971
2220
|
content,
|
|
1972
2221
|
source: options.source
|
|
1973
2222
|
});
|
|
1974
|
-
|
|
2223
|
+
options.signal.throwIfAborted();
|
|
2224
|
+
this.assertAdmitting(parent);
|
|
2225
|
+
this.authorizeLineage(parent, activation.childId, activation.parent.id);
|
|
2226
|
+
this.acquireOwnership(parent, activation.childId);
|
|
2227
|
+
try {
|
|
2228
|
+
this.deliver(activation, message, options.delivery);
|
|
2229
|
+
} finally {
|
|
2230
|
+
this.wake(activation);
|
|
2231
|
+
}
|
|
2232
|
+
return message.id;
|
|
1975
2233
|
}
|
|
1976
2234
|
/** Refuse image content for a child whose fixed model accepts text only. */
|
|
1977
2235
|
async assertImageCapable(agent, signal) {
|
|
@@ -1996,6 +2254,15 @@ var SubagentContinuationManager = class {
|
|
|
1996
2254
|
return query;
|
|
1997
2255
|
}
|
|
1998
2256
|
};
|
|
2257
|
+
/** Creation-time selection precedes the own descriptor; historical forks can select after it. */
|
|
2258
|
+
function hasIndependentPresetSelection(events) {
|
|
2259
|
+
for (const event of events) {
|
|
2260
|
+
if (event.type === "subagent/descriptor") return false;
|
|
2261
|
+
if (event.type === "agent-preset/selected") return true;
|
|
2262
|
+
}
|
|
2263
|
+
/* v8 ignore next -- coldResume rejects logs without a supported own descriptor before this lookup. */
|
|
2264
|
+
return false;
|
|
2265
|
+
}
|
|
1999
2266
|
//#endregion
|
|
2000
2267
|
//#region lib/types/list-children.js
|
|
2001
2268
|
/**
|
|
@@ -2124,7 +2391,7 @@ async function listDescendants(ctx, rootSessionId, signal) {
|
|
|
2124
2391
|
visited.add(entry.id);
|
|
2125
2392
|
let children;
|
|
2126
2393
|
try {
|
|
2127
|
-
children = await readChildren(entry.id);
|
|
2394
|
+
children = entry.mode === "external" ? [] : await readChildren(entry.id);
|
|
2128
2395
|
} catch (error) {
|
|
2129
2396
|
if (error instanceof SubagentError) throw error;
|
|
2130
2397
|
const code = error instanceof Error && "code" in error ? error.code : void 0;
|
|
@@ -2482,8 +2749,7 @@ const subagentIdentityProjectionDefinition = {
|
|
|
2482
2749
|
* Provider-side vocabulary for OUT-OF-PROCESS subagent backends — the pieces
|
|
2483
2750
|
* that enforce this seam's own contracts around a child in another process:
|
|
2484
2751
|
* the no-capabilities advertisement, timing-bound validation, child
|
|
2485
|
-
* working-directory
|
|
2486
|
-
* session's workspace), the never-reject result settlement, and the standard
|
|
2752
|
+
* working-directory validation, the never-reject result settlement, and the standard
|
|
2487
2753
|
* run-handle publication. Backends compose these with their own wire drivers;
|
|
2488
2754
|
* the process machinery itself (spawn, env scrub, managed-range teardown)
|
|
2489
2755
|
* belongs to the `dsh-subprocess` seam.
|
|
@@ -2517,15 +2783,14 @@ function normalizeSubagentDiagnostic(result) {
|
|
|
2517
2783
|
/**
|
|
2518
2784
|
* The capability advertisement of an out-of-process backend: NONE. A child in
|
|
2519
2785
|
* another process cannot honor parent-enforced start features
|
|
2520
|
-
* (`agentOptions`/`outputSchema`/`maxDepth
|
|
2786
|
+
* (`agentOptions`/`agentPreset`/`outputSchema`/`maxDepth`), so the service rejects a
|
|
2521
2787
|
* request needing any of them before `start` runs — never accepted-then-ignored.
|
|
2522
2788
|
*/
|
|
2523
2789
|
const NO_START_CAPABILITIES = Object.freeze({
|
|
2524
2790
|
agentOptions: false,
|
|
2791
|
+
agentPreset: false,
|
|
2525
2792
|
outputSchema: false,
|
|
2526
|
-
depthLimit: false
|
|
2527
|
-
toolFilter: false,
|
|
2528
|
-
persona: false
|
|
2793
|
+
depthLimit: false
|
|
2529
2794
|
});
|
|
2530
2795
|
/**
|
|
2531
2796
|
* Assert a configured timing bound is a positive finite number (it bounds a
|
|
@@ -2566,39 +2831,6 @@ function assertUsableCwd(prefix, label, cwd) {
|
|
|
2566
2831
|
if (!isEnterableDirectory(cwd)) throw new Error(`${prefix}: ${label} is not an accessible directory: ${cwd}`);
|
|
2567
2832
|
return cwd;
|
|
2568
2833
|
}
|
|
2569
|
-
/**
|
|
2570
|
-
* Validate a configured `cwd` override ONCE, at plugin load: reject the empty
|
|
2571
|
-
* string (`path.resolve('')` is the process cwd — it would silently
|
|
2572
|
-
* reintroduce the launch-directory fallback this resolution removes),
|
|
2573
|
-
* interpret a relative path against the harness launch directory, and require
|
|
2574
|
-
* an enterable directory.
|
|
2575
|
-
* @param prefix - the consuming plugin's diagnostic prefix.
|
|
2576
|
-
* @param cwd - the configured override, or `undefined` when the config omits it.
|
|
2577
|
-
* @returns the validated absolute override, or `undefined` when omitted.
|
|
2578
|
-
*/
|
|
2579
|
-
function validateConfiguredCwd(prefix, cwd) {
|
|
2580
|
-
if (cwd === void 0) return void 0;
|
|
2581
|
-
if (cwd === "") throw new Error(`${prefix}: config cwd must not be empty — omit the key to inherit the parent session cwd`);
|
|
2582
|
-
return assertUsableCwd(prefix, "config cwd", resolve(cwd));
|
|
2583
|
-
}
|
|
2584
|
-
/**
|
|
2585
|
-
* Resolve the child's working directory at start: the deployment override
|
|
2586
|
-
* when configured (already validated at load), else the parent session's
|
|
2587
|
-
* workspace cwd (validated here, its earliest resolvable point). Fails loud
|
|
2588
|
-
* when neither exists — falling back to the harness process cwd would
|
|
2589
|
-
* silently bind the child to the server's launch directory instead of the
|
|
2590
|
-
* delegating session's workspace (one server process serves many sessions,
|
|
2591
|
-
* each with its own cwd).
|
|
2592
|
-
* @param prefix - the consuming plugin's diagnostic prefix.
|
|
2593
|
-
* @param configured - the load-validated override, or `undefined`.
|
|
2594
|
-
* @param parentCwd - the delegating parent session's workspace cwd, if any.
|
|
2595
|
-
* @returns the absolute child working directory.
|
|
2596
|
-
*/
|
|
2597
|
-
function resolveChildCwd(prefix, configured, parentCwd) {
|
|
2598
|
-
if (configured !== void 0) return configured;
|
|
2599
|
-
if (parentCwd === void 0) throw new Error(`${prefix}: no working directory for the child — configure \`cwd\` or delegate from a parent session that has one`);
|
|
2600
|
-
return assertUsableCwd(prefix, "parent session cwd", parentCwd);
|
|
2601
|
-
}
|
|
2602
2834
|
/** Normalize an unknown thrown value to an Error (the catch binding is `unknown`). */
|
|
2603
2835
|
function toError(value) {
|
|
2604
2836
|
/* v8 ignore next */
|
|
@@ -2646,13 +2878,12 @@ async function settleRunResult(parts) {
|
|
|
2646
2878
|
* local cancellation — there is no assumption the child cooperates — and then
|
|
2647
2879
|
* awaits the backend's teardown to actual exit.
|
|
2648
2880
|
* @param parts - the run identity, result, cancellation wiring, and teardown.
|
|
2649
|
-
* @returns the
|
|
2881
|
+
* @returns the external run handle.
|
|
2650
2882
|
*/
|
|
2651
2883
|
function subprocessRunHandle(parts) {
|
|
2652
2884
|
let disposal;
|
|
2653
2885
|
return {
|
|
2654
2886
|
id: parts.id,
|
|
2655
|
-
localAgent: void 0,
|
|
2656
2887
|
result: parts.result,
|
|
2657
2888
|
dispose() {
|
|
2658
2889
|
if (disposal !== void 0) return disposal;
|
|
@@ -2664,80 +2895,6 @@ function subprocessRunHandle(parts) {
|
|
|
2664
2895
|
};
|
|
2665
2896
|
}
|
|
2666
2897
|
//#endregion
|
|
2667
|
-
//#region lib/types/run-settlement.js
|
|
2668
|
-
/**
|
|
2669
|
-
* Settlement of one ONE-SHOT subagent run into a background-Task outcome. Only
|
|
2670
|
-
* the one-shot background path uses Jobs; continuable children have no Task,
|
|
2671
|
-
* no per-message result, and no Task cancellation.
|
|
2672
|
-
*
|
|
2673
|
-
* @module @deepseek-ai/dsh-subagent/run-settlement
|
|
2674
|
-
*/
|
|
2675
|
-
/** Flatten a child's final output blocks to the task's final text. */
|
|
2676
|
-
function finalText(blocks) {
|
|
2677
|
-
return blocks.filter((block) => block.type === "text").map((block) => block.text).join("");
|
|
2678
|
-
}
|
|
2679
|
-
/** Render a failed stop reason with optional provider-authored detail. */
|
|
2680
|
-
function failureDetail(result) {
|
|
2681
|
-
const stopReason = result.stopReason;
|
|
2682
|
-
return result.diagnostic === void 0 ? stopReason : `${stopReason}; diagnostic: ${result.diagnostic}`;
|
|
2683
|
-
}
|
|
2684
|
-
/**
|
|
2685
|
-
* Map a child result to the task outcome: completed carries final text, local
|
|
2686
|
-
* cancellation (`aborted` without a diagnostic) is killed, and provider-
|
|
2687
|
-
* diagnosed remote aborts plus every other reason are failed without partial
|
|
2688
|
-
* output.
|
|
2689
|
-
* @param result - child terminal result.
|
|
2690
|
-
* @returns outcome for the `ctx.jobs` registration.
|
|
2691
|
-
*/
|
|
2692
|
-
function runOutcome(result) {
|
|
2693
|
-
switch (result.stopReason) {
|
|
2694
|
-
case "completed": return {
|
|
2695
|
-
status: "completed",
|
|
2696
|
-
result: finalText(result.output)
|
|
2697
|
-
};
|
|
2698
|
-
case "aborted": return result.diagnostic === void 0 ? { status: "killed" } : {
|
|
2699
|
-
status: "failed",
|
|
2700
|
-
detail: failureDetail(result)
|
|
2701
|
-
};
|
|
2702
|
-
case "error":
|
|
2703
|
-
case "max-tokens":
|
|
2704
|
-
case "refusal": return {
|
|
2705
|
-
status: "failed",
|
|
2706
|
-
detail: failureDetail(result)
|
|
2707
|
-
};
|
|
2708
|
-
default: return {
|
|
2709
|
-
status: "failed",
|
|
2710
|
-
detail: failureDetail(result)
|
|
2711
|
-
};
|
|
2712
|
-
}
|
|
2713
|
-
}
|
|
2714
|
-
/**
|
|
2715
|
-
* Await the child result, dispose the run, then return its task outcome. Result
|
|
2716
|
-
* and disposal failures become `failed`; when both fail, both details survive.
|
|
2717
|
-
* @param run - live run to settle and release.
|
|
2718
|
-
* @returns outcome after child resources are released.
|
|
2719
|
-
*/
|
|
2720
|
-
async function settleRun(run) {
|
|
2721
|
-
let outcome;
|
|
2722
|
-
try {
|
|
2723
|
-
outcome = runOutcome(await run.result);
|
|
2724
|
-
} catch (error) {
|
|
2725
|
-
outcome = {
|
|
2726
|
-
status: "failed",
|
|
2727
|
-
detail: String(error)
|
|
2728
|
-
};
|
|
2729
|
-
}
|
|
2730
|
-
try {
|
|
2731
|
-
await run.dispose();
|
|
2732
|
-
} catch (error) {
|
|
2733
|
-
return {
|
|
2734
|
-
status: "failed",
|
|
2735
|
-
detail: `${outcome.detail === void 0 ? "" : `${outcome.detail}; `}dispose failed: ${String(error)}`
|
|
2736
|
-
};
|
|
2737
|
-
}
|
|
2738
|
-
return outcome;
|
|
2739
|
-
}
|
|
2740
|
-
//#endregion
|
|
2741
2898
|
//#region lib/types/index.js
|
|
2742
2899
|
var __runInitializers = function(thisArg, initializers, value) {
|
|
2743
2900
|
var useValue = arguments.length > 2;
|
|
@@ -2777,7 +2934,7 @@ var __esDecorate = function(ctor, descriptorIn, decorators, contextIn, initializ
|
|
|
2777
2934
|
if (target) Object.defineProperty(target, contextIn.name, descriptor);
|
|
2778
2935
|
done = true;
|
|
2779
2936
|
};
|
|
2780
|
-
/** Named provider registry with
|
|
2937
|
+
/** Named provider registry with managed activations, durable discovery, and local child messaging. */
|
|
2781
2938
|
let SubagentRuntime = (() => {
|
|
2782
2939
|
let _classSuper = TypertRemoteService;
|
|
2783
2940
|
let _instanceExtraInitializers = [];
|
|
@@ -2822,8 +2979,9 @@ let SubagentRuntime = (() => {
|
|
|
2822
2979
|
maxDepth: z.number().step(1).min(0).max(Number.MAX_SAFE_INTEGER).default(1).volatile(),
|
|
2823
2980
|
maxActiveSubagents: z.number().step(1).min(1).max(Number.MAX_SAFE_INTEGER).default(8).volatile()
|
|
2824
2981
|
});
|
|
2982
|
+
static inject = ["workingDirectory"];
|
|
2825
2983
|
providers = /* @__PURE__ */ new Map();
|
|
2826
|
-
|
|
2984
|
+
manager;
|
|
2827
2985
|
/**
|
|
2828
2986
|
* The contained lifecycle-edge publisher. Built here because scoped dispatch
|
|
2829
2987
|
* keys its carrier by this exact service instance, whose own context filter
|
|
@@ -2835,21 +2993,23 @@ let SubagentRuntime = (() => {
|
|
|
2835
2993
|
this.config = config;
|
|
2836
2994
|
this.emitLifecycle = createLifecycleEmitter(this.ctx, (parent) => scopeTarget(this, parent));
|
|
2837
2995
|
ctx.inject(["agents"], (childCtx) => {
|
|
2838
|
-
const manager = new
|
|
2996
|
+
const manager = new SubagentManager(childCtx, {
|
|
2997
|
+
startExternal: (name, request) => {
|
|
2998
|
+
return this.expectProvider(name).start(request);
|
|
2999
|
+
},
|
|
2839
3000
|
prepareContinuable: (name, request) => this.prepareContinuable(name, request),
|
|
2840
3001
|
observeActivation: (provider, childId, parent) => this.observeActivation(provider, childId, parent)
|
|
2841
3002
|
}, () => this.config.maxActiveSubagents.get());
|
|
2842
|
-
this.
|
|
3003
|
+
this.manager = manager;
|
|
2843
3004
|
childCtx.effect(() => () => {
|
|
2844
3005
|
/* v8 ignore else -- one injected binding owns the slot until its fiber disposes. */
|
|
2845
|
-
if (this.
|
|
2846
|
-
}, "subagents.
|
|
3006
|
+
if (this.manager === manager) this.manager = void 0;
|
|
3007
|
+
}, "subagents.managerBinding()");
|
|
2847
3008
|
});
|
|
2848
3009
|
ctx.inject(["sessionProjections"], (projectionCtx) => {
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
projections.register(subagentIdentityProjectionDefinition);
|
|
3010
|
+
projectionCtx.sessionProjections.register(subagentCatalogProjectionDefinition);
|
|
3011
|
+
projectionCtx.sessionProjections.register(subagentTimingProjectionDefinition);
|
|
3012
|
+
projectionCtx.sessionProjections.register(subagentIdentityProjectionDefinition);
|
|
2853
3013
|
});
|
|
2854
3014
|
ctx.inject(["agents"], (agentsCtx) => {
|
|
2855
3015
|
installSubagentArchiveAdmission(agentsCtx);
|
|
@@ -2868,16 +3028,32 @@ let SubagentRuntime = (() => {
|
|
|
2868
3028
|
return depth;
|
|
2869
3029
|
}
|
|
2870
3030
|
/**
|
|
2871
|
-
* Establish
|
|
2872
|
-
*
|
|
2873
|
-
*
|
|
2874
|
-
*
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
3031
|
+
* Establish a managed child with backend-specific execution capabilities.
|
|
3032
|
+
* @param spec - task, backend, and result recipient.
|
|
3033
|
+
* @returns identities, execution result, and exact-activation disposal. A reserved
|
|
3034
|
+
* childId requires a local backend and guarantees an accepted messageId.
|
|
3035
|
+
*/
|
|
3036
|
+
async startActivation(spec) {
|
|
3037
|
+
const provider = this.expectProvider(spec.provider);
|
|
3038
|
+
if (spec.childId !== void 0 && provider.prepareContinuable === void 0) throw new SubagentError("reserved child ids require a local backend", "UNSUPPORTED_CAPABILITY");
|
|
3039
|
+
assertSubagentMaxDepth(spec.request.maxDepth);
|
|
3040
|
+
this.assertCapabilities(provider, {
|
|
3041
|
+
...spec.request,
|
|
3042
|
+
signal: spec.signal
|
|
3043
|
+
});
|
|
3044
|
+
const manager = this.requireManager();
|
|
3045
|
+
if (this.ctx.get("agents")?.get(spec.request.parent.id) !== spec.request.parent) throw new SubagentError("subagent creation requires the exact live parent agent", "UNAUTHORIZED");
|
|
3046
|
+
if (spec.request.outputSchema !== void 0) assertObjectJsonSchema(spec.request.outputSchema);
|
|
3047
|
+
return provider.prepareContinuable === void 0 ? manager.startExternal(spec) : manager.startLocal(spec);
|
|
3048
|
+
}
|
|
3049
|
+
/**
|
|
3050
|
+
* Join progressing descendants without cancelling them. Idle descendants whose
|
|
3051
|
+
* inboxes require a later wake stay resident and do not delay host completion.
|
|
3052
|
+
* @param parent - the exact parent whose descendant work is observed.
|
|
3053
|
+
* @returns whether work was joined; hosts recheck parent idle after true.
|
|
2878
3054
|
*/
|
|
2879
|
-
async
|
|
2880
|
-
return this.
|
|
3055
|
+
async waitForChildren(parent) {
|
|
3056
|
+
return this.manager?.waitForChildren(parent) ?? false;
|
|
2881
3057
|
}
|
|
2882
3058
|
/**
|
|
2883
3059
|
* Steer one model-authored message to the sender's direct parent or direct
|
|
@@ -2894,7 +3070,7 @@ let SubagentRuntime = (() => {
|
|
|
2894
3070
|
* or the message was not admitted.
|
|
2895
3071
|
*/
|
|
2896
3072
|
async sendMessage(sender, targetId, content, options) {
|
|
2897
|
-
return this.
|
|
3073
|
+
return this.requireManager().sendMessage(sender, targetId, content, options);
|
|
2898
3074
|
}
|
|
2899
3075
|
/**
|
|
2900
3076
|
* Deliver one host-protocol message to a direct continuable child.
|
|
@@ -2909,16 +3085,16 @@ let SubagentRuntime = (() => {
|
|
|
2909
3085
|
* @returns the accepted message's inbox id.
|
|
2910
3086
|
*/
|
|
2911
3087
|
[deliverSubagentPrompt](parent, childId, content, source, signal, delivery) {
|
|
2912
|
-
return delivery === "steer" ? this.
|
|
3088
|
+
return delivery === "steer" ? this.requireManager().steerPrompt(parent, childId, content, source, signal) : this.requireManager().queuePrompt(parent, childId, content, source, signal);
|
|
2913
3089
|
}
|
|
2914
3090
|
/**
|
|
2915
|
-
* Interrupt one live
|
|
3091
|
+
* Interrupt one live child's current execution under a human parent
|
|
2916
3092
|
* address or an exact live ancestor Agent. Fire-and-return: the cancel
|
|
2917
3093
|
* signal is issued before this returns, but the target may keep running
|
|
2918
3094
|
* until it observes the signal. Unclaimed pending inbox work, the Activation,
|
|
2919
3095
|
* and published descendants are preserved; claimed work is not requeued.
|
|
2920
|
-
* Once the interrupted
|
|
2921
|
-
* queue.
|
|
3096
|
+
* Once the interrupted Agent is idle, a waking send resumes the parked FIFO
|
|
3097
|
+
* queue. External backends stop their single execution. An absent target
|
|
2922
3098
|
* is an accepted no-op, as is a manager-less composition, which cannot own a
|
|
2923
3099
|
* live Activation.
|
|
2924
3100
|
* @param targetSessionId - the durable child session id to interrupt.
|
|
@@ -2927,35 +3103,35 @@ let SubagentRuntime = (() => {
|
|
|
2927
3103
|
* live target.
|
|
2928
3104
|
*/
|
|
2929
3105
|
interrupt(targetSessionId, authority) {
|
|
2930
|
-
this.
|
|
3106
|
+
this.manager?.interrupt(targetSessionId, authority);
|
|
2931
3107
|
}
|
|
2932
3108
|
/**
|
|
2933
|
-
* Close
|
|
3109
|
+
* Close subagent admission below exact live parent Agents, stop only their
|
|
2934
3110
|
* visible descendant Activations synchronously, then await admitted scoped
|
|
2935
3111
|
* materializations and release those forests child-first. The scoped cutoff
|
|
2936
3112
|
* lasts until each exact parent leaves the registry; unrelated parent trees
|
|
2937
3113
|
* remain live.
|
|
2938
3114
|
* @param parents - exact host-owned parent Agents entering teardown.
|
|
2939
|
-
* @returns once every retained descendant
|
|
3115
|
+
* @returns once every retained descendant activation released its execution handle.
|
|
2940
3116
|
* @throws an aggregate error after all branches settle when any failed.
|
|
2941
3117
|
*/
|
|
2942
|
-
async
|
|
2943
|
-
const manager = this.
|
|
3118
|
+
async drainDescendants(parents) {
|
|
3119
|
+
const manager = this.manager;
|
|
2944
3120
|
if (manager === void 0) return;
|
|
2945
3121
|
await manager.drainDescendants(parents);
|
|
2946
3122
|
}
|
|
2947
3123
|
/**
|
|
2948
|
-
* Release selected resident
|
|
3124
|
+
* Release selected resident direct children of one exact live
|
|
2949
3125
|
* parent. Other children of the same parent remain admitted and resident.
|
|
2950
3126
|
* Absent targets and a manager-less composition are accepted no-ops.
|
|
2951
3127
|
* @param parent - exact live direct parent authorizing the selected release.
|
|
2952
3128
|
* @param childIds - durable direct-child ids to release when resident.
|
|
2953
|
-
* @returns once every selected
|
|
3129
|
+
* @returns once every selected activation released its execution handle.
|
|
2954
3130
|
* @throws {SubagentError} `UNAUTHORIZED` when a resident target belongs to a
|
|
2955
3131
|
* different parent or the supplied parent identity is stale.
|
|
2956
3132
|
*/
|
|
2957
|
-
async
|
|
2958
|
-
const manager = this.
|
|
3133
|
+
async drainChildren(parent, childIds) {
|
|
3134
|
+
const manager = this.manager;
|
|
2959
3135
|
if (manager === void 0) return;
|
|
2960
3136
|
await manager.drainChildren(parent, childIds);
|
|
2961
3137
|
}
|
|
@@ -2974,7 +3150,8 @@ let SubagentRuntime = (() => {
|
|
|
2974
3150
|
/**
|
|
2975
3151
|
* Recursively list reachable parent catalogs in stable pre-order, preserving
|
|
2976
3152
|
* each catalog's event order. Each row carries its catalog parent and depth;
|
|
2977
|
-
* one-shot and unknown-mode children remain
|
|
3153
|
+
* external children are leaves; one-shot and unknown-mode children remain
|
|
3154
|
+
* traversal nodes. Unknown modes
|
|
2978
3155
|
* produce unsupported diagnostics. Unreadable child catalogs produce corrupt
|
|
2979
3156
|
* or unavailable diagnostics and stop only that branch. Root read failures,
|
|
2980
3157
|
* missing services or projections, and cancellation reject the whole listing.
|
|
@@ -3014,10 +3191,12 @@ let SubagentRuntime = (() => {
|
|
|
3014
3191
|
if (clientTimeZone !== void 0 && canonicalTimeZone === void 0) throw new RemoteError("subagent/invalid-time-zone", "clientTimeZone must be UTC or a valid IANA Area/Location name", { value: clientTimeZone });
|
|
3015
3192
|
const parent = this.ctx.get("agents")?.get(parentSessionId);
|
|
3016
3193
|
if (parent === void 0) throw new RemoteError("subagent/parent-unavailable", `parent session "${parentSessionId}" is not live`, { parentSessionId });
|
|
3194
|
+
const principal = principalOf(this.ctx.invocation);
|
|
3017
3195
|
const source = {
|
|
3018
3196
|
kind: "user",
|
|
3019
3197
|
rpcId: request.requestId,
|
|
3020
|
-
...canonicalTimeZone === void 0 ? {} : { clientTimeZone: canonicalTimeZone }
|
|
3198
|
+
...canonicalTimeZone === void 0 ? {} : { clientTimeZone: canonicalTimeZone },
|
|
3199
|
+
...principal === void 0 ? {} : { principal }
|
|
3021
3200
|
};
|
|
3022
3201
|
try {
|
|
3023
3202
|
let content;
|
|
@@ -3069,12 +3248,14 @@ let SubagentRuntime = (() => {
|
|
|
3069
3248
|
/**
|
|
3070
3249
|
* Register a provider under its name. Registration is effect-scoped and HMR
|
|
3071
3250
|
* safe; removing a provider blocks new starts but does not revoke runs that
|
|
3072
|
-
* were already returned to their holders.
|
|
3251
|
+
* were already returned to their holders. Providers without either execution
|
|
3252
|
+
* method are rejected with UNSUPPORTED_CAPABILITY before registration.
|
|
3073
3253
|
* @param provider - the trusted provider implementation.
|
|
3074
3254
|
* @returns the exact Cordis effect disposer.
|
|
3075
3255
|
*/
|
|
3076
3256
|
registerProvider(provider) {
|
|
3077
3257
|
const name = provider.name;
|
|
3258
|
+
if (provider.start === void 0 && provider.prepareContinuable === void 0) throw new SubagentError(`subagent provider "${name}" must implement start or prepareContinuable`, "UNSUPPORTED_CAPABILITY");
|
|
3078
3259
|
return this.ctx.effect(function* () {
|
|
3079
3260
|
if (this.providers.has(name)) throw new SubagentError(`a subagent provider named "${name}" is already registered`, "DUPLICATE_PROVIDER");
|
|
3080
3261
|
this.providers.set(name, provider);
|
|
@@ -3101,55 +3282,12 @@ let SubagentRuntime = (() => {
|
|
|
3101
3282
|
return [...this.providers.keys()];
|
|
3102
3283
|
}
|
|
3103
3284
|
/**
|
|
3104
|
-
* Establish a published child on the named provider. Capability and semantic
|
|
3105
|
-
* checks run before delegation. Provider ownership lasts until its promise
|
|
3106
|
-
* fulfills; a rejection therefore has no run for the caller to dispose and
|
|
3107
|
-
* emits no run lifecycle events. Post-publication turn and infrastructure
|
|
3108
|
-
* failures settle through the returned run.
|
|
3109
|
-
* A catalog append failure disposes the run and handles its result rejection;
|
|
3110
|
-
* the caller receives the catalog error even if disposal also fails.
|
|
3111
|
-
* @param name - the provider to use.
|
|
3112
|
-
* @param request - child label, prompt, parent, signal, and optional capabilities.
|
|
3113
|
-
* @returns the published holder-owned run.
|
|
3114
|
-
*/
|
|
3115
|
-
async start(name, request) {
|
|
3116
|
-
const provider = this.expectProvider(name);
|
|
3117
|
-
this.assertCapabilities(provider, request);
|
|
3118
|
-
assertSubagentMaxDepth(request.maxDepth);
|
|
3119
|
-
if (request.outputSchema !== void 0) assertObjectJsonSchema(request.outputSchema);
|
|
3120
|
-
const descriptor = snapshotSubagentDescriptor({
|
|
3121
|
-
mode: "one-shot",
|
|
3122
|
-
provider: name,
|
|
3123
|
-
...request.label !== void 0 ? { label: request.label } : {}
|
|
3124
|
-
});
|
|
3125
|
-
const resolved = {
|
|
3126
|
-
...request,
|
|
3127
|
-
descriptor
|
|
3128
|
-
};
|
|
3129
|
-
const run = await provider.start(resolved);
|
|
3130
|
-
const child = run.localAgent?.session;
|
|
3131
|
-
if (child !== void 0) try {
|
|
3132
|
-
establishCatalogChild(request.parent.session, child.header, descriptor);
|
|
3133
|
-
} catch (error) {
|
|
3134
|
-
run.result.catch(() => void 0);
|
|
3135
|
-
try {
|
|
3136
|
-
await run.dispose();
|
|
3137
|
-
} catch (cleanupError) {
|
|
3138
|
-
this.ctx.logger.warn(`subagent: disposal after catalog append failure also failed: ${String(cleanupError)}`);
|
|
3139
|
-
}
|
|
3140
|
-
throw error;
|
|
3141
|
-
}
|
|
3142
|
-
return observeRun(this.emitLifecycle, name, request.parent, run);
|
|
3143
|
-
}
|
|
3144
|
-
/**
|
|
3145
3285
|
* Resolve one provider's detached continuable-creation contribution. Method
|
|
3146
3286
|
* presence on the provider IS the capability, so a provider without it is
|
|
3147
3287
|
* rejected before the manager reserves any child resources.
|
|
3148
3288
|
*/
|
|
3149
3289
|
async prepareContinuable(name, request) {
|
|
3150
|
-
|
|
3151
|
-
if (provider.prepareContinuable === void 0) throw new SubagentError(`subagent provider "${provider.name}" does not support continuable children (no prepareContinuable capability)`, "UNSUPPORTED_CAPABILITY");
|
|
3152
|
-
return provider.prepareContinuable(request);
|
|
3290
|
+
return this.expectProvider(name).prepareContinuable(request);
|
|
3153
3291
|
}
|
|
3154
3292
|
/** Look up a provider for dispatch or fail loud. */
|
|
3155
3293
|
expectProvider(name) {
|
|
@@ -3157,10 +3295,10 @@ let SubagentRuntime = (() => {
|
|
|
3157
3295
|
if (provider === void 0) throw new SubagentError(`no subagent provider registered for "${name}"`, "NO_PROVIDER");
|
|
3158
3296
|
return provider;
|
|
3159
3297
|
}
|
|
3160
|
-
/** Resolve the
|
|
3161
|
-
|
|
3162
|
-
if (this.
|
|
3163
|
-
return this.
|
|
3298
|
+
/** Resolve the subagent manager or fail loud. */
|
|
3299
|
+
requireManager() {
|
|
3300
|
+
if (this.manager === void 0) throw new SubagentError("continuable subagents require the agents service", "CONTINUATION_UNAVAILABLE");
|
|
3301
|
+
return this.manager;
|
|
3164
3302
|
}
|
|
3165
3303
|
/**
|
|
3166
3304
|
* Build the lifecycle observer for one continuable Activation's residency
|
|
@@ -3171,6 +3309,7 @@ let SubagentRuntime = (() => {
|
|
|
3171
3309
|
}
|
|
3172
3310
|
/** Reject the first requested capability that the provider lacks. */
|
|
3173
3311
|
assertCapabilities(provider, request) {
|
|
3312
|
+
if (request.agentPreset !== void 0 && (!provider.capabilities.agentPreset || provider.inheritsParentContext)) throw new SubagentError(`subagent provider "${provider.name}" does not support independent agent preset selection`, "UNSUPPORTED_CAPABILITY");
|
|
3174
3313
|
const needs = [
|
|
3175
3314
|
{
|
|
3176
3315
|
when: request.agentOptions !== void 0,
|
|
@@ -3183,14 +3322,6 @@ let SubagentRuntime = (() => {
|
|
|
3183
3322
|
{
|
|
3184
3323
|
when: request.maxDepth !== void 0,
|
|
3185
3324
|
cap: "depthLimit"
|
|
3186
|
-
},
|
|
3187
|
-
{
|
|
3188
|
-
when: request.toolFilter !== void 0,
|
|
3189
|
-
cap: "toolFilter"
|
|
3190
|
-
},
|
|
3191
|
-
{
|
|
3192
|
-
when: request.persona !== void 0,
|
|
3193
|
-
cap: "persona"
|
|
3194
3325
|
}
|
|
3195
3326
|
];
|
|
3196
3327
|
for (const { when, cap } of needs) if (when && !provider.capabilities[cap]) throw new SubagentError(`subagent provider "${provider.name}" does not support the "${cap}" capability`, "UNSUPPORTED_CAPABILITY");
|
|
@@ -3198,4 +3329,4 @@ let SubagentRuntime = (() => {
|
|
|
3198
3329
|
};
|
|
3199
3330
|
})();
|
|
3200
3331
|
//#endregion
|
|
3201
|
-
export { AssistantOutputFold, NO_START_CAPABILITIES, SUBAGENT_DESCRIPTOR_VERSION, SubagentDepthError, SubagentError, SubagentRunId, SubagentRuntime, SubagentRuntime as default, appendDelegatedPolicyOverrides, applyChildComposition, assertPositiveFinite, assertSubagentMaxDepth, assertUsableCwd, captureDelegatedPolicyOverrides, childSessionMeta, delegationDepthOf, finalAssistantOutput, foldSubagentDescriptor, parentAgentOptionsForDelegation, resolveChildAgentOptions,
|
|
3332
|
+
export { AssistantOutputFold, NO_START_CAPABILITIES, STRUCTURED_OUTPUT_TOOL, SUBAGENT_DESCRIPTOR_VERSION, SubagentDepthError, SubagentError, SubagentRunId, SubagentRuntime, SubagentRuntime as default, appendDelegatedPolicyOverrides, applyChildComposition, assertPositiveFinite, assertSubagentMaxDepth, assertUsableCwd, captureDelegatedPolicyOverrides, childSessionMeta, delegationDepthOf, finalAssistantOutput, foldSubagentDescriptor, parentAgentOptionsForDelegation, resolveChildAgentOptions, resolveChildDepth, settleRunResult, snapshotSubagentDescriptor, subprocessRunHandle };
|