@fishwithcat/dsh-subagent 0.0.0-stage → 0.2.2-alpha.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.i18n.yaml +121 -0
- package/README.md +258 -2
- package/README.zh.md +259 -0
- package/lib/index.js +3065 -0
- package/lib/typert.host.d.ts +3 -0
- package/lib/typert.host.js +1086 -0
- package/lib/typert.remote-client.d.ts +29 -0
- package/lib/typert.remote-client.js +122 -0
- package/lib/types/activation.d.ts +110 -0
- package/lib/types/activation.js +112 -0
- package/lib/types/archive-admission.d.ts +23 -0
- package/lib/types/archive-admission.js +75 -0
- package/lib/types/assistant-output.d.ts +48 -0
- package/lib/types/assistant-output.js +73 -0
- package/lib/types/catalog.d.ts +87 -0
- package/lib/types/catalog.js +113 -0
- package/lib/types/child-agent.d.ts +145 -0
- package/lib/types/child-agent.js +229 -0
- package/lib/types/client.d.ts +8 -0
- package/lib/types/client.js +7 -0
- package/lib/types/continuation-messages.d.ts +66 -0
- package/lib/types/continuation-messages.js +116 -0
- package/lib/types/control-types.d.ts +121 -0
- package/lib/types/control-types.js +8 -0
- package/lib/types/control.d.ts +41 -0
- package/lib/types/control.js +75 -0
- package/lib/types/depth.d.ts +31 -0
- package/lib/types/depth.js +39 -0
- package/lib/types/descriptor.d.ts +105 -0
- package/lib/types/descriptor.js +192 -0
- package/lib/types/error.d.ts +11 -0
- package/lib/types/error.js +14 -0
- package/lib/types/index.d.ts +282 -0
- package/lib/types/index.js +398 -0
- package/lib/types/lifecycle.d.ts +54 -0
- package/lib/types/lifecycle.js +89 -0
- package/lib/types/list-children.d.ts +36 -0
- package/lib/types/list-children.js +166 -0
- package/lib/types/manager.d.ts +215 -0
- package/lib/types/manager.js +1171 -0
- package/lib/types/out-of-process.d.ts +92 -0
- package/lib/types/out-of-process.js +177 -0
- package/lib/types/projection-types.d.ts +93 -0
- package/lib/types/projection-types.js +7 -0
- package/lib/types/projection.d.ts +94 -0
- package/lib/types/projection.js +157 -0
- package/lib/types/structured.d.ts +49 -0
- package/lib/types/structured.js +130 -0
- package/lib/types/types.d.ts +331 -0
- package/lib/types/types.js +17 -0
- package/locale/de.json +6 -0
- package/locale/en.json +5 -0
- package/locale/es.json +6 -0
- package/locale/fr.json +6 -0
- package/locale/ja.json +6 -0
- package/locale/pt.json +6 -0
- package/locale/ru.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +131 -4
package/lib/index.js
ADDED
|
@@ -0,0 +1,3065 @@
|
|
|
1
|
+
import { Service } from "@deepseek-ai/cordis";
|
|
2
|
+
import z from "@deepseek-ai/schemastery";
|
|
3
|
+
import { scopeTarget } from "@deepseek-ai/dsh-scope";
|
|
4
|
+
import { ToolArgsError, assertObjectJsonSchema, validateJsonSchemaValue } from "@deepseek-ai/dsh-tools";
|
|
5
|
+
import { canonicalClientTimeZone } from "@deepseek-ai/dsh-util-time";
|
|
6
|
+
import { Remote, RemoteError, TypertRemoteService, principalOf } from "@deepseek-ai/dsh-typert-protocol";
|
|
7
|
+
import { AttachmentError } from "@deepseek-ai/dsh-attachment";
|
|
8
|
+
import { z as z$1 } from "zod";
|
|
9
|
+
import { HarnessError, ReasoningEffortId, boundContextSummary, contentHasImage, createUserMessage, errorChain, joinAssistantStreamText } from "@deepseek-ai/dsh-llm";
|
|
10
|
+
import { foldConsumedWork } from "@deepseek-ai/dsh-agent";
|
|
11
|
+
import { randomUUID } from "node:crypto";
|
|
12
|
+
import { isAbsolute, resolve } from "node:path";
|
|
13
|
+
import { brandString } from "@deepseek-ai/dsh-brand";
|
|
14
|
+
import { SessionLogOffset, SessionSeq } from "@deepseek-ai/dsh-session";
|
|
15
|
+
import { assertNever, snapshotJsonValue } from "@deepseek-ai/dsh-util-values";
|
|
16
|
+
import { appendChunkedList, chunkedListSchema, iterateChunkedList } from "@deepseek-ai/dsh-chunked-list";
|
|
17
|
+
import { accessSync, constants, statSync } from "node:fs";
|
|
18
|
+
//#region lib/types/error.js
|
|
19
|
+
/**
|
|
20
|
+
* Typed failures shared by subagent service and provider operations.
|
|
21
|
+
*
|
|
22
|
+
* @module @deepseek-ai/dsh-subagent
|
|
23
|
+
*/
|
|
24
|
+
/** Typed failure for the subagent seam. */
|
|
25
|
+
var SubagentError = class extends HarnessError {
|
|
26
|
+
constructor(message, code, options) {
|
|
27
|
+
super(message, code, options);
|
|
28
|
+
this.name = "SubagentError";
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
//#endregion
|
|
32
|
+
//#region lib/types/control.js
|
|
33
|
+
/**
|
|
34
|
+
* Browser-facing subagent prompt and interrupt request validation plus the
|
|
35
|
+
* stable prompt failure codes returned by the Remote surface.
|
|
36
|
+
*
|
|
37
|
+
* @module @deepseek-ai/dsh-subagent
|
|
38
|
+
*/
|
|
39
|
+
const SESSION_ID_SCHEMA = z$1.string().min(1);
|
|
40
|
+
const CONTROL_ID_SCHEMAS = {
|
|
41
|
+
"subagent.prompt": z$1.object({
|
|
42
|
+
childSessionId: SESSION_ID_SCHEMA,
|
|
43
|
+
delivery: z$1.enum(["queue", "steer"])
|
|
44
|
+
}),
|
|
45
|
+
"subagent.interrupt": z$1.object({ childSessionId: SESSION_ID_SCHEMA })
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Apply the subagent payload checks that are stricter than generated
|
|
49
|
+
* branded-string codecs.
|
|
50
|
+
* @param method - method name carried in the failure message.
|
|
51
|
+
* @param payload - decoded control fields to validate.
|
|
52
|
+
* @throws {RemoteError} `gateway/bad-request` with the original Zod issues.
|
|
53
|
+
*/
|
|
54
|
+
function validateControlRequest(method, payload) {
|
|
55
|
+
const parsed = CONTROL_ID_SCHEMAS[method].safeParse(payload);
|
|
56
|
+
if (!parsed.success) throw new RemoteError("gateway/bad-request", `invalid payload for ${method}`, { issues: parsed.error.issues });
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Refuse one continuation prompt without exposing provider detail: admission
|
|
60
|
+
* failures the caller can act on keep their own code, everything else is
|
|
61
|
+
* internal.
|
|
62
|
+
* @param error - the thrown value.
|
|
63
|
+
* @param childSessionId - the addressed child.
|
|
64
|
+
* @param signal - the caller's cancellation.
|
|
65
|
+
* @returns Never — the refusal is thrown.
|
|
66
|
+
* @throws {RemoteError} always.
|
|
67
|
+
*/
|
|
68
|
+
function rejectPrompt(error, childSessionId, signal) {
|
|
69
|
+
if (isCancellation(error, signal)) throw new RemoteError("gateway/cancelled", "subagent prompt was cancelled", {}, { cause: error });
|
|
70
|
+
if (error instanceof AttachmentError) throw new RemoteError("subagent/attachment-invalid", error.message, { reason: error.code }, { cause: error });
|
|
71
|
+
if (error instanceof SubagentError) switch (error.code) {
|
|
72
|
+
case "MODEL_DOES_NOT_SUPPORT_IMAGES": throw new RemoteError("subagent/attachment-invalid", error.message, { reason: error.code }, { cause: error });
|
|
73
|
+
case "NOT_RESUMABLE": throw new RemoteError("subagent/not-resumable", "subagent cannot be resumed", { childSessionId }, { cause: error });
|
|
74
|
+
case "UNAUTHORIZED": throw new RemoteError("subagent/unauthorized", "subagent membership in the active tree could not be verified", { childSessionId }, { cause: error });
|
|
75
|
+
case "DRAINING":
|
|
76
|
+
case "ACTIVATION_CLOSING":
|
|
77
|
+
case "ACTIVATION_LIMIT_REACHED":
|
|
78
|
+
case "CONTINUATION_UNAVAILABLE":
|
|
79
|
+
case "PERSISTENCE_UNAVAILABLE": throw new RemoteError("subagent/delivery-unavailable", "subagent follow-up is temporarily unavailable", { childSessionId }, { cause: error });
|
|
80
|
+
default: break;
|
|
81
|
+
}
|
|
82
|
+
throw new RemoteError("gateway/internal", "subagent prompt failed", {}, { cause: error });
|
|
83
|
+
}
|
|
84
|
+
function isCancellation(error, signal) {
|
|
85
|
+
return signal.aborted || error instanceof SubagentError && error.code === "CANCELLED";
|
|
86
|
+
}
|
|
87
|
+
//#endregion
|
|
88
|
+
//#region lib/types/depth.js
|
|
89
|
+
/**
|
|
90
|
+
* Delegation-depth accounting: the recursion budget a parent passes to its
|
|
91
|
+
* children. Kept apart from the service so composition helpers can read it
|
|
92
|
+
* without importing the registry.
|
|
93
|
+
*
|
|
94
|
+
* @module @deepseek-ai/dsh-subagent/depth
|
|
95
|
+
*/
|
|
96
|
+
/**
|
|
97
|
+
* Read an agent's delegation depth, treating absence as top-level depth zero.
|
|
98
|
+
* The persisted session header is authoritative and monotone: runtime
|
|
99
|
+
* `AgentOptions.subagentDepth` may DEEPEN the count but can never lower it —
|
|
100
|
+
* a resumed child arrives with fresh options, and counting it from zero would
|
|
101
|
+
* let it delegate as if it were top-level.
|
|
102
|
+
* @param agent - the agent whose header and options carry the depth.
|
|
103
|
+
* @returns its non-negative safe-integer depth.
|
|
104
|
+
* @throws if the runtime `AgentOptions.subagentDepth` is not a non-negative safe integer.
|
|
105
|
+
*/
|
|
106
|
+
function delegationDepthOf(agent) {
|
|
107
|
+
const runtime = agent.options.subagentDepth;
|
|
108
|
+
if (runtime !== void 0 && (!Number.isSafeInteger(runtime) || runtime < 0 || Object.is(runtime, -0))) throw new TypeError("agent subagentDepth must be a non-negative safe integer");
|
|
109
|
+
return Math.max(agent.session.header.delegationDepth ?? 0, runtime ?? 0);
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Reject a recursion cap that cannot represent an exact delegation depth.
|
|
113
|
+
* @param maxDepth - the optional runtime value to validate.
|
|
114
|
+
*/
|
|
115
|
+
function assertSubagentMaxDepth(maxDepth) {
|
|
116
|
+
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");
|
|
117
|
+
}
|
|
118
|
+
//#endregion
|
|
119
|
+
//#region lib/types/lifecycle.js
|
|
120
|
+
/**
|
|
121
|
+
* Contained lifecycle notifications for subagents.
|
|
122
|
+
*
|
|
123
|
+
* The service owns scoped dispatch and listener containment; the manager
|
|
124
|
+
* supplies event payloads from each activation.
|
|
125
|
+
*
|
|
126
|
+
* @module @deepseek-ai/dsh-subagent/lifecycle
|
|
127
|
+
*/
|
|
128
|
+
/**
|
|
129
|
+
* Build the contained lifecycle emitter this seam publishes every edge through.
|
|
130
|
+
* Every listener is independently contained: a synchronous throw or a rejected
|
|
131
|
+
* returned promise is logged without starving peer listeners, changing the run,
|
|
132
|
+
* or — for provider removal, which fires from a disposer — breaking teardown.
|
|
133
|
+
* @param ctx - the service's own context, owning dispatch and the logger.
|
|
134
|
+
* @param carrier - resolve the scoped dispatch carrier for one subagent tree root.
|
|
135
|
+
* @returns the emitter the manager and provider registry publish through.
|
|
136
|
+
*/
|
|
137
|
+
function createLifecycleEmitter(ctx, carrier) {
|
|
138
|
+
return (name, info, parent) => {
|
|
139
|
+
const dispatchArgs = parent === void 0 ? [name, info] : [
|
|
140
|
+
carrier(parent),
|
|
141
|
+
name,
|
|
142
|
+
info
|
|
143
|
+
];
|
|
144
|
+
for (const callback of ctx.events.dispatch("emit", dispatchArgs)) try {
|
|
145
|
+
const returned = callback(info);
|
|
146
|
+
Promise.resolve(returned).catch((error) => {
|
|
147
|
+
ctx.logger.warn(`subagent: ${name} listener rejected: ${renderThrown(error)}`);
|
|
148
|
+
});
|
|
149
|
+
} catch (error) {
|
|
150
|
+
ctx.logger.warn(`subagent: ${name} listener threw: ${renderThrown(error)}`);
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Why this child's epoch ended, for the terminal lifecycle edge and the
|
|
156
|
+
* manager's own parent delivery. The child's own log is authoritative:
|
|
157
|
+
* teardown succeeding says nothing about whether the model errored, hit its
|
|
158
|
+
* token ceiling, or was cancelled, so deriving the reason from disposal would
|
|
159
|
+
* report failed work as completed.
|
|
160
|
+
*
|
|
161
|
+
* {@link foldConsumedWork} supplies both halves the raw turn sequence cannot:
|
|
162
|
+
* which turn accounts for the work this epoch consumed, and whether accepted
|
|
163
|
+
* work was cancelled after it without any turn opening over it. A recorded
|
|
164
|
+
* failure still wins over a cancellation — stopping a child that had already
|
|
165
|
+
* failed does not turn its failure into a cancellation.
|
|
166
|
+
* @param events - this epoch's own event suffix.
|
|
167
|
+
* @returns its terminal stop reason; `completed` only for an epoch that both
|
|
168
|
+
* closed cleanly and had nothing left to run.
|
|
169
|
+
*/
|
|
170
|
+
function epochStopReason(events) {
|
|
171
|
+
const { end, droppedUnrun } = foldConsumedWork(events);
|
|
172
|
+
switch (end?.data.reason.kind) {
|
|
173
|
+
case "max-tokens": return "max-tokens";
|
|
174
|
+
case "aborted":
|
|
175
|
+
case "interrupted": return "aborted";
|
|
176
|
+
case "error": return "error";
|
|
177
|
+
case "blocked": return "refusal";
|
|
178
|
+
case void 0:
|
|
179
|
+
case "completed": return droppedUnrun ? "aborted" : "completed";
|
|
180
|
+
/* v8 ignore next 4 -- `forked` appears only in constructor seed history, while
|
|
181
|
+
* this function reads an epoch-owned suffix. `TurnEndReason` is merge-extensible,
|
|
182
|
+
* so a backend-added variant cannot be listed; treating an unnameable reason as
|
|
183
|
+
* success would report failed work as completed. */
|
|
184
|
+
default: return "error";
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
/** Render any listener-thrown value without letting coercion escape containment. */
|
|
188
|
+
function renderThrown(value) {
|
|
189
|
+
try {
|
|
190
|
+
return value instanceof Error ? `${value.name}: ${value.message}` : String(value);
|
|
191
|
+
} catch {
|
|
192
|
+
return "<unrenderable thrown value>";
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
//#endregion
|
|
196
|
+
//#region lib/types/child-agent.js
|
|
197
|
+
/**
|
|
198
|
+
* Shared in-process child composition: the delegation-depth budget, the
|
|
199
|
+
* durable session metadata, the resolved child `AgentOptions`, the delegated
|
|
200
|
+
* policy seed, and the scoped setup used by local activations.
|
|
201
|
+
*
|
|
202
|
+
* @module @deepseek-ai/dsh-subagent/child-agent
|
|
203
|
+
*/
|
|
204
|
+
/** Thrown when starting a child would exceed the requested depth cap. */
|
|
205
|
+
var SubagentDepthError = class extends Error {
|
|
206
|
+
attemptedDepth;
|
|
207
|
+
maxDepth;
|
|
208
|
+
constructor(attemptedDepth, maxDepth) {
|
|
209
|
+
super(`subagent depth ${attemptedDepth} exceeds maxDepth ${maxDepth}`);
|
|
210
|
+
this.attemptedDepth = attemptedDepth;
|
|
211
|
+
this.maxDepth = maxDepth;
|
|
212
|
+
this.name = "SubagentDepthError";
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
/**
|
|
216
|
+
* Resolve the child's delegation depth from its parent and enforce an optional
|
|
217
|
+
* cap. The persisted parent header is the monotone floor, so a resumed parent
|
|
218
|
+
* cannot delegate as if it were top-level.
|
|
219
|
+
* @param parent - the delegating parent agent.
|
|
220
|
+
* @param maxDepth - optional absolute cap the resolved depth must not exceed.
|
|
221
|
+
* @returns the child's non-negative safe-integer depth.
|
|
222
|
+
* @throws {SubagentDepthError} when the resolved depth exceeds `maxDepth`.
|
|
223
|
+
* @throws {RangeError} when the resolved depth leaves the safe-integer range.
|
|
224
|
+
*/
|
|
225
|
+
function resolveChildDepth(parent, maxDepth) {
|
|
226
|
+
const childDepth = delegationDepthOf(parent) + 1;
|
|
227
|
+
if (!Number.isSafeInteger(childDepth)) throw new RangeError("subagent child depth exceeds the safe-integer range");
|
|
228
|
+
if (maxDepth !== void 0 && childDepth > maxDepth) throw new SubagentDepthError(childDepth, maxDepth);
|
|
229
|
+
return childDepth;
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Resolve the parent values inherited by a child. The latest request header
|
|
233
|
+
* owns provider, model, and reasoning effort after request-time selection;
|
|
234
|
+
* creation options remain the fallback before the first request and retain
|
|
235
|
+
* the configured output-token limit.
|
|
236
|
+
* @param parent - delegating parent Agent.
|
|
237
|
+
* @returns detached Agent options for child-option merging.
|
|
238
|
+
*/
|
|
239
|
+
function parentAgentOptionsForDelegation(parent) {
|
|
240
|
+
const requestConfig = parent.session.requestHeader()?.config;
|
|
241
|
+
if (requestConfig === void 0) return { ...parent.options };
|
|
242
|
+
const { provider: _createdProvider, model: _createdModel, reasoningEffort: _createdReasoningEffort, ...createdOptions } = parent.options;
|
|
243
|
+
return {
|
|
244
|
+
...createdOptions,
|
|
245
|
+
provider: requestConfig.provider,
|
|
246
|
+
model: requestConfig.model,
|
|
247
|
+
...requestConfig.reasoningEffort === void 0 ? {} : { reasoningEffort: requestConfig.reasoningEffort }
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Resolve the child's `AgentOptions`: the parent's provider/model,
|
|
252
|
+
* reasoning-effort, and maxTokens values unless the request overrides them,
|
|
253
|
+
* stamped with the child's own delegation depth. Changing the route without
|
|
254
|
+
* naming an effort clears the parent's route-owned effort so the selected
|
|
255
|
+
* model resolves its own default.
|
|
256
|
+
* @param parent - the delegating parent whose route the child inherits.
|
|
257
|
+
* @param requested - per-child overrides, if any.
|
|
258
|
+
* @param childDepth - the resolved delegation depth to stamp.
|
|
259
|
+
* @returns the resolved options for `ctx.agents.create()`.
|
|
260
|
+
*/
|
|
261
|
+
function resolveChildAgentOptions(parent, requested, childDepth) {
|
|
262
|
+
const parentOptions = parentAgentOptionsForDelegation(parent);
|
|
263
|
+
const parentProvider = parentOptions.provider;
|
|
264
|
+
const parentModel = parentOptions.model;
|
|
265
|
+
const parentReasoningEffort = parentOptions.reasoningEffort;
|
|
266
|
+
const parentMaxTokens = parentOptions.maxTokens;
|
|
267
|
+
const resolved = {
|
|
268
|
+
...parentProvider !== void 0 ? { provider: parentProvider } : {},
|
|
269
|
+
...parentModel !== void 0 ? { model: parentModel } : {},
|
|
270
|
+
...parentReasoningEffort !== void 0 ? { reasoningEffort: parentReasoningEffort } : {},
|
|
271
|
+
...parentMaxTokens !== void 0 ? { maxTokens: parentMaxTokens } : {},
|
|
272
|
+
...requested,
|
|
273
|
+
subagentDepth: childDepth
|
|
274
|
+
};
|
|
275
|
+
if ((resolved.provider !== parentProvider || resolved.model !== parentModel) && requested?.reasoningEffort === void 0) delete resolved.reasoningEffort;
|
|
276
|
+
return resolved;
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Build the child session's durable creation metadata: the parent's workspace,
|
|
280
|
+
* its direct lineage, coarse product origin, the recursion budget that must
|
|
281
|
+
* survive persistence, the seed boundary that separates inherited parent
|
|
282
|
+
* history from child work, and the composition the child runs under.
|
|
283
|
+
*
|
|
284
|
+
* Omitted selection uses the parent's live composition, including any switch
|
|
285
|
+
* made while the parent was blank.
|
|
286
|
+
* @param parent - the delegating parent agent.
|
|
287
|
+
* @param childDepth - the resolved delegation depth to persist.
|
|
288
|
+
* @param isSeeded - whether this child inherits a parent-log prefix, including an explicitly empty one.
|
|
289
|
+
* @param selectedPreset - independent selection, or omission to inherit the parent.
|
|
290
|
+
* @returns the `meta` for `ctx.agents.create()`.
|
|
291
|
+
*/
|
|
292
|
+
function childSessionMeta(parent, childDepth, isSeeded, selectedPreset) {
|
|
293
|
+
const parentHeader = parent.session.header;
|
|
294
|
+
const agentPreset = selectedPreset ?? parent.ctx.get("agentPresets")?.composedPreset(parent.ctx);
|
|
295
|
+
return {
|
|
296
|
+
...parentHeader.cwd !== void 0 ? { cwd: parentHeader.cwd } : {},
|
|
297
|
+
...agentPreset === void 0 ? {} : { agentPreset },
|
|
298
|
+
parentSession: parentHeader.id,
|
|
299
|
+
isSeeded,
|
|
300
|
+
origin: "subagent",
|
|
301
|
+
delegationDepth: childDepth
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Model-facing delegation-scope statement for an in-process child whose approval
|
|
306
|
+
* policy is `never`. A runtime-context contribution rather than a system-prompt
|
|
307
|
+
* section, so the deployment's system prompt stays uniform across parents and
|
|
308
|
+
* children.
|
|
309
|
+
*/
|
|
310
|
+
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.";
|
|
311
|
+
/**
|
|
312
|
+
* Model-facing delegation-scope statement for an in-process child whose approval
|
|
313
|
+
* policy is `ask`: the scope stays fixed, but one operation may request a
|
|
314
|
+
* decision from the user who owns the answerers.
|
|
315
|
+
*/
|
|
316
|
+
const SUBAGENT_DELEGATION_INTERACTIVE_CONTEXT = "You are a delegated subagent: your permission scope was fixed when you were started and cannot be widened from inside this session. An operation that needs access beyond that scope may request approval from the user, who can allow or reject it. Do not retry a rejected or denied operation; state the limitation in your reply so the delegating agent can handle it.";
|
|
317
|
+
/**
|
|
318
|
+
* Compose a child before publication and restore historical descriptor overrides.
|
|
319
|
+
* Explicit selection mounts the named preset. Omission joins a supplied creator's
|
|
320
|
+
* current generation; a restored child without a saved preset uses host composition.
|
|
321
|
+
* Registrations belong to the child's scope.
|
|
322
|
+
* @param childCtx - the child agent's scoped creation context.
|
|
323
|
+
* @param parent - creator for fresh delegation; absent during target-only restoration.
|
|
324
|
+
* @param composition - independent preset and any overrides read from a historical descriptor.
|
|
325
|
+
* @param approvalPolicy - the child's effective approval policy, selecting its delegation statement.
|
|
326
|
+
* @returns A promise when mounting an independent preset; otherwise completes synchronously.
|
|
327
|
+
*/
|
|
328
|
+
function applyChildComposition(childCtx, parent, composition, approvalPolicy) {
|
|
329
|
+
if (composition.agentPreset !== void 0) {
|
|
330
|
+
const presets = childCtx.get("agentPresets");
|
|
331
|
+
if (presets === void 0) throw new SubagentError("selecting a child agent preset requires the agent-presets service", "PRESET_UNAVAILABLE");
|
|
332
|
+
return presets.mount(childCtx, composition.agentPreset).then(() => {
|
|
333
|
+
applyChildOverrides(childCtx, composition, approvalPolicy);
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
if (parent !== void 0) childCtx.get("agentPresets")?.composeFrom(childCtx, parent.ctx);
|
|
337
|
+
applyChildOverrides(childCtx, composition, approvalPolicy);
|
|
338
|
+
}
|
|
339
|
+
function applyChildOverrides(childCtx, composition, approvalPolicy) {
|
|
340
|
+
childCtx.systemPrompt.context({
|
|
341
|
+
name: "subagent:delegation",
|
|
342
|
+
order: childCtx.systemPrompt.getContextOrder("SUBAGENT_DELEGATION"),
|
|
343
|
+
text: approvalPolicy === "ask" ? SUBAGENT_DELEGATION_INTERACTIVE_CONTEXT : SUBAGENT_DELEGATION_CONTEXT
|
|
344
|
+
});
|
|
345
|
+
if (composition.persona !== void 0) childCtx.systemPrompt.section({
|
|
346
|
+
name: "deployment:persona-prefix",
|
|
347
|
+
order: childCtx.systemPrompt.getSectionOrder("DEPLOYMENT_PERSONA_PREFIX"),
|
|
348
|
+
text: composition.persona
|
|
349
|
+
});
|
|
350
|
+
if (composition.toolFilter !== void 0) childCtx.tools.restrict(composition.toolFilter);
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Capture the permission state to seed into one delegation. Call synchronously before
|
|
354
|
+
* the child start's first await: a later parent switch belongs to the
|
|
355
|
+
* parent's future, not to this child. Auto and Full access identities are
|
|
356
|
+
* inherited only through the in-process DSH path so either can replace a stale
|
|
357
|
+
* same-bundle fork value. Only the parent session's explicit sandbox override
|
|
358
|
+
* is captured — never deployment defaults or one-shot grants. Approval is
|
|
359
|
+
* pinned to `'never'` by default; `'inherit'` seeds the parent's current
|
|
360
|
+
* effective policy instead, which only a surface that also answers descendant
|
|
361
|
+
* Agents can turn into a prompt.
|
|
362
|
+
* @param parent - the delegating parent agent.
|
|
363
|
+
* @param delegatedApproval - `'never'` to pin rejection, `'inherit'` to seed the parent's effective policy.
|
|
364
|
+
* @returns the sandbox override (or `undefined` without one) and the approval policy seed.
|
|
365
|
+
*/
|
|
366
|
+
function captureDelegatedPolicyOverrides(parent, delegatedApproval) {
|
|
367
|
+
const preset = parent.ctx.get("permissionPresets")?.current(parent.session);
|
|
368
|
+
const approval = parent.ctx.get("approval");
|
|
369
|
+
return {
|
|
370
|
+
permissionPreset: preset === "auto" || preset === "danger-full-access" ? preset : void 0,
|
|
371
|
+
sandboxMode: parent.ctx.get("sandboxPolicy")?.overrideOf(parent.session),
|
|
372
|
+
approvalPolicy: approval === void 0 ? void 0 : delegatedApproval === "inherit" ? approval.effectivePolicy(parent.session) : "never"
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* Append the captured delegation policy onto the child's own log as
|
|
377
|
+
* `source: 'delegation'` events after Session announcement and before Agent registration, so the
|
|
378
|
+
* child's effective policy is reconstructable from its log alone. Appends land
|
|
379
|
+
* after any fork seed, so fresh policy wins stale seed state; later child
|
|
380
|
+
* switches still win over these events.
|
|
381
|
+
* @param childSession - the child's announced, writable Session.
|
|
382
|
+
* @param overrides - the policy captured at delegation.
|
|
383
|
+
*/
|
|
384
|
+
function appendDelegatedPolicyOverrides(childSession, overrides) {
|
|
385
|
+
if (overrides.sandboxMode !== void 0) childSession.append("sandbox/mode", {
|
|
386
|
+
mode: overrides.sandboxMode,
|
|
387
|
+
source: "delegation"
|
|
388
|
+
});
|
|
389
|
+
if (overrides.approvalPolicy !== void 0) childSession.append("approval/policy", {
|
|
390
|
+
policy: overrides.approvalPolicy,
|
|
391
|
+
source: "delegation"
|
|
392
|
+
});
|
|
393
|
+
if (overrides.permissionPreset !== void 0) childSession.append("permission/preset", { preset: overrides.permissionPreset });
|
|
394
|
+
}
|
|
395
|
+
//#endregion
|
|
396
|
+
//#region lib/types/continuation-messages.js
|
|
397
|
+
/**
|
|
398
|
+
* Model-visible messages owned by continuable-subagent orchestration.
|
|
399
|
+
*
|
|
400
|
+
* @module @deepseek-ai/dsh-subagent/continuation-messages
|
|
401
|
+
*/
|
|
402
|
+
/** Build durable attribution for one Agent message. */
|
|
403
|
+
function agentMessageSource(sender) {
|
|
404
|
+
return {
|
|
405
|
+
kind: "agent-message",
|
|
406
|
+
form: "relay",
|
|
407
|
+
senderSessionId: sender.id
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* Build the model-visible and durable representation of one Agent message.
|
|
412
|
+
* @param sender - exact live Agent that authored the message.
|
|
413
|
+
* @param content - model-visible message blocks supplied by the sender.
|
|
414
|
+
* @returns the durable user-message representation delivered to the recipient.
|
|
415
|
+
*/
|
|
416
|
+
function createAgentMessage(sender, content) {
|
|
417
|
+
return createUserMessage({
|
|
418
|
+
content: [{
|
|
419
|
+
type: "text",
|
|
420
|
+
text: `Agent ${sender.id} sent a message: `
|
|
421
|
+
}, ...content],
|
|
422
|
+
source: agentMessageSource(sender)
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* Append automatic result-delivery guidance to a parent-delivered child's initial task.
|
|
427
|
+
* @param prompt - initial model-visible task blocks.
|
|
428
|
+
* @returns task blocks followed by the result-delivery guidance.
|
|
429
|
+
*/
|
|
430
|
+
function withResultDeliveryGuidance(prompt) {
|
|
431
|
+
return [...prompt, {
|
|
432
|
+
type: "text",
|
|
433
|
+
text: "Your final answer is delivered automatically if your parent is still online. Include the result and relevant evidence in that answer; the parent does not receive your full transcript, tool output, or reasoning. If your last action is a message to your parent, the completion notice omits your final answer; include the result and relevant evidence in that message."
|
|
434
|
+
}];
|
|
435
|
+
}
|
|
436
|
+
/**
|
|
437
|
+
* One line telling a parent that a background child is finished and why, in
|
|
438
|
+
* the parent's own task vocabulary.
|
|
439
|
+
* @param childId - the durable child the parent knows by id.
|
|
440
|
+
* @param stopReason - how the child's last ordinary turn ended.
|
|
441
|
+
* @param continuable - whether the child accepts a later message.
|
|
442
|
+
* @returns the model-facing opening line of the settlement notice.
|
|
443
|
+
*/
|
|
444
|
+
function settlementSummary(childId, stopReason, continuable) {
|
|
445
|
+
const subject = `Background subagent ${childId}`;
|
|
446
|
+
switch (stopReason) {
|
|
447
|
+
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.`;
|
|
448
|
+
case "aborted": return `${subject} was stopped before it finished.`;
|
|
449
|
+
case "max-tokens": return `${subject} ran out of room before it finished.`;
|
|
450
|
+
case "refusal": return `${subject} declined the task.`;
|
|
451
|
+
case "error": return `${subject} failed before it finished.`;
|
|
452
|
+
/* v8 ignore next 4 -- `SubagentResult['stopReason']` is merge-extensible, so this arm
|
|
453
|
+
* needs a backend that adds a variant; an unnameable ending is reported as unfinished
|
|
454
|
+
* rather than silently as success. */
|
|
455
|
+
default: return `${subject} ended abnormally (${String(stopReason)}) before it finished.`;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* Build the runtime-owned settlement notice from the child's nonempty closing text.
|
|
460
|
+
* @param childId - durable child session id named in the notice.
|
|
461
|
+
* @param terminal - recorded terminal state for the settled Activation.
|
|
462
|
+
* @param continuable - whether the child can accept another task.
|
|
463
|
+
* @param omitClosingText - replace the closing text when the child's last action
|
|
464
|
+
* was a message to its direct parent.
|
|
465
|
+
* @returns the durable user-message representation delivered to the parent.
|
|
466
|
+
*/
|
|
467
|
+
function createSettlementMessage(childId, terminal, continuable = true, omitClosingText = false) {
|
|
468
|
+
const summary = settlementSummary(childId, terminal.stopReason, continuable);
|
|
469
|
+
const closingText = terminal.output.flatMap((block) => block.type === "text" && block.text.length > 0 ? [block] : []);
|
|
470
|
+
return createUserMessage({
|
|
471
|
+
content: [
|
|
472
|
+
{
|
|
473
|
+
type: "text",
|
|
474
|
+
text: summary
|
|
475
|
+
},
|
|
476
|
+
...omitClosingText ? [{
|
|
477
|
+
type: "text",
|
|
478
|
+
text: "It sent you a message just before finishing, so its closing message is omitted."
|
|
479
|
+
}] : closingText.length === 0 ? [{
|
|
480
|
+
type: "text",
|
|
481
|
+
text: "It left no closing message."
|
|
482
|
+
}] : [{
|
|
483
|
+
type: "text",
|
|
484
|
+
text: "Its closing message:"
|
|
485
|
+
}, ...closingText],
|
|
486
|
+
...terminal.structured !== void 0 ? [{
|
|
487
|
+
type: "text",
|
|
488
|
+
text: `Structured result: ${JSON.stringify(terminal.structured)}`
|
|
489
|
+
}] : [],
|
|
490
|
+
...terminal.diagnostic !== void 0 ? [{
|
|
491
|
+
type: "text",
|
|
492
|
+
text: terminal.diagnostic
|
|
493
|
+
}] : []
|
|
494
|
+
],
|
|
495
|
+
source: {
|
|
496
|
+
kind: "subagent-settled",
|
|
497
|
+
form: "notice",
|
|
498
|
+
summary: boundContextSummary(summary),
|
|
499
|
+
stopReason: terminal.stopReason,
|
|
500
|
+
senderSessionId: childId
|
|
501
|
+
}
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
//#endregion
|
|
505
|
+
//#region lib/types/archive-admission.js
|
|
506
|
+
/**
|
|
507
|
+
* The `subagent` family of the Workspace registry's archive admission: which
|
|
508
|
+
* subagent descendants of a Session are still inside a turn, and how they
|
|
509
|
+
* stop when the Session is archived with its work. Archived ancestors also
|
|
510
|
+
* block later model steps, including when their Sessions have been released.
|
|
511
|
+
*
|
|
512
|
+
* @module @deepseek-ai/dsh-subagent
|
|
513
|
+
*/
|
|
514
|
+
/**
|
|
515
|
+
* Answer `workspace/session-activity` with the running subagent descendants of
|
|
516
|
+
* the asked Session, and `workspace/session-stop` by cancelling each of them
|
|
517
|
+
* as their parent would. Reject subsequent steps beneath archived ancestors.
|
|
518
|
+
* All listeners live as long as `ctx`'s fiber.
|
|
519
|
+
* @param ctx - context carrying the Agent registry.
|
|
520
|
+
* @param residentAncestors - verified creation ancestors for an exact managed Agent, absent before publication or for unmanaged Agents.
|
|
521
|
+
* @param identityOf - ready service-owned descriptor identity reader; absent or invalid descriptors yield no identity.
|
|
522
|
+
*/
|
|
523
|
+
function installSubagentArchiveAdmission(ctx, residentAncestors, identityOf) {
|
|
524
|
+
const ancestorsOf = (agent) => residentAncestors(agent) ?? headerAncestors(ctx, agent);
|
|
525
|
+
const runningDescendants = (sessionId) => ctx.agents.list().filter((agent) => agent.status === "running" && ancestorsOf(agent).includes(sessionId));
|
|
526
|
+
ctx.on("workspace/session-activity", async ({ sessionId }, next) => {
|
|
527
|
+
const running = runningDescendants(sessionId);
|
|
528
|
+
const rest = await next();
|
|
529
|
+
if (running.length === 0) return rest;
|
|
530
|
+
return [{
|
|
531
|
+
kind: "subagent",
|
|
532
|
+
items: running.map((child) => describe(child, identityOf(child.session)))
|
|
533
|
+
}, ...rest];
|
|
534
|
+
});
|
|
535
|
+
ctx.on("workspace/session-stop", ({ sessionId }) => {
|
|
536
|
+
for (const child of runningDescendants(sessionId)) try {
|
|
537
|
+
child.cancel({ kind: "parent" });
|
|
538
|
+
} catch (error) {
|
|
539
|
+
ctx.logger.warn(`subagent: cancelling "${child.id}" for an archived Session failed: ${String(error)}`);
|
|
540
|
+
}
|
|
541
|
+
});
|
|
542
|
+
ctx.on("agent/pre-step", ({ agent }, next) => {
|
|
543
|
+
const archived = ctx.get("workspaceRegistry")?.archivedSessionIds;
|
|
544
|
+
return archived !== void 0 && ancestorsOf(agent).some((id) => archived.includes(id)) ? Promise.resolve({ kind: "reject" }) : next();
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
/**
|
|
548
|
+
* Header-only ancestry for Agents without a published Activation. Include an
|
|
549
|
+
* offline direct creator; ordinary forks stop the chain and cycles terminate.
|
|
550
|
+
*/
|
|
551
|
+
function headerAncestors(ctx, agent) {
|
|
552
|
+
const ancestors = [];
|
|
553
|
+
const visited = new Set([agent.id]);
|
|
554
|
+
let header = agent.session.header;
|
|
555
|
+
while (header.origin === "subagent" && header.parentSession !== void 0 && !visited.has(header.parentSession)) {
|
|
556
|
+
const parentId = header.parentSession;
|
|
557
|
+
ancestors.push(parentId);
|
|
558
|
+
visited.add(parentId);
|
|
559
|
+
const parent = ctx.agents.get(parentId)?.session;
|
|
560
|
+
if (parent === void 0) break;
|
|
561
|
+
header = parent.header;
|
|
562
|
+
}
|
|
563
|
+
return ancestors;
|
|
564
|
+
}
|
|
565
|
+
/**
|
|
566
|
+
* Report the child's current projected label only when its descriptor belongs
|
|
567
|
+
* to the child's own log; inherited, absent, or invalid identities yield the id alone.
|
|
568
|
+
*/
|
|
569
|
+
function describe(child, identity) {
|
|
570
|
+
const label = identity !== void 0 && child.session.isOwnSeq(identity.seq) ? identity.label : void 0;
|
|
571
|
+
return label === void 0 ? { id: child.id } : {
|
|
572
|
+
id: child.id,
|
|
573
|
+
label
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
//#endregion
|
|
577
|
+
//#region lib/types/descriptor.js
|
|
578
|
+
/**
|
|
579
|
+
* The durable subagent-child descriptor: the versioned, model-hidden
|
|
580
|
+
* `subagent/descriptor` session event that identifies every session-backed
|
|
581
|
+
* subagent, including historical one-shot records. The manager appends each
|
|
582
|
+
* continuable child's descriptor inside its initial turn, preserving the
|
|
583
|
+
* declared composition required for cold resume.
|
|
584
|
+
*
|
|
585
|
+
* The descriptor deliberately snapshots explicit fields rather than the
|
|
586
|
+
* merge-extensible `AgentOptions` object: an unrelated extension value cannot
|
|
587
|
+
* make continuation fail merely because it is not JSON, and later composition
|
|
588
|
+
* inputs require a deliberate {@link SUBAGENT_DESCRIPTOR_VERSION} change. It
|
|
589
|
+
* omits `subagentDepth` — cold resume trusts the persisted header's
|
|
590
|
+
* `delegationDepth` as the monotone floor — and `outputSchema`, which belongs
|
|
591
|
+
* to one activation's result contract rather than durable child composition.
|
|
592
|
+
* Per-activation knobs such as `maxTokens` are omitted for the same reason as
|
|
593
|
+
* `outputSchema`: they budget one activation. Cold resume requires the exact
|
|
594
|
+
* live subagent tree root for authorization but reconstructs child options only from the
|
|
595
|
+
* durable descriptor, so it neither restores the prior budget nor inherits
|
|
596
|
+
* the parent's current one; the resumed route's defaults apply instead.
|
|
597
|
+
*
|
|
598
|
+
* @module @deepseek-ai/dsh-subagent/descriptor
|
|
599
|
+
*/
|
|
600
|
+
/**
|
|
601
|
+
* The current descriptor format version, stamped into every appended
|
|
602
|
+
* `subagent/descriptor` event and required verbatim by {@link foldSubagentDescriptor}.
|
|
603
|
+
* Supporting another composition input is a deliberate version change, never
|
|
604
|
+
* an implicit extra field.
|
|
605
|
+
*/
|
|
606
|
+
const SUBAGENT_DESCRIPTOR_VERSION = 3;
|
|
607
|
+
const DESCRIPTOR_BASE_KEYS = [
|
|
608
|
+
"version",
|
|
609
|
+
"mode",
|
|
610
|
+
"provider",
|
|
611
|
+
"label"
|
|
612
|
+
];
|
|
613
|
+
const ONE_SHOT_DESCRIPTOR_KEYS = new Set(DESCRIPTOR_BASE_KEYS);
|
|
614
|
+
const CONTINUABLE_DESCRIPTOR_KEYS = new Set([
|
|
615
|
+
...DESCRIPTOR_BASE_KEYS,
|
|
616
|
+
"agentProvider",
|
|
617
|
+
"agentModel",
|
|
618
|
+
"agentReasoningEffort",
|
|
619
|
+
"persona",
|
|
620
|
+
"toolFilter"
|
|
621
|
+
]);
|
|
622
|
+
const TOOL_FILTER_KEYS = new Set(["allow", "deny"]);
|
|
623
|
+
/** Whether a persisted JSON value is an object record. */
|
|
624
|
+
function isRecord(value) {
|
|
625
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
626
|
+
}
|
|
627
|
+
/** Reject fields outside one versioned record's declared schema. */
|
|
628
|
+
function assertKnownKeys(value, keys, path) {
|
|
629
|
+
const unknown = Object.keys(value).find((key) => !keys.has(key));
|
|
630
|
+
if (unknown !== void 0) throw new Error(`persisted subagent descriptor ${path} has unknown field "${unknown}"`);
|
|
631
|
+
}
|
|
632
|
+
/** Read one optional string field from a persisted descriptor record. */
|
|
633
|
+
function optionalString(value, key) {
|
|
634
|
+
if (!Object.hasOwn(value, key)) return void 0;
|
|
635
|
+
const field = value[key];
|
|
636
|
+
if (typeof field !== "string") throw new Error(`persisted subagent descriptor ${key} must be a string`);
|
|
637
|
+
return field;
|
|
638
|
+
}
|
|
639
|
+
/** Read one optional string-array field from a persisted tool restriction. */
|
|
640
|
+
function optionalStringArray(value, key) {
|
|
641
|
+
if (!Object.hasOwn(value, key)) return void 0;
|
|
642
|
+
const field = value[key];
|
|
643
|
+
if (!Array.isArray(field)) throw new Error(`persisted subagent descriptor toolFilter.${key} must be an array of strings`);
|
|
644
|
+
const items = field;
|
|
645
|
+
if (items.some((item) => typeof item !== "string")) throw new Error(`persisted subagent descriptor toolFilter.${key} must be an array of strings`);
|
|
646
|
+
return items;
|
|
647
|
+
}
|
|
648
|
+
/** Validate and reconstruct a persisted tool restriction. */
|
|
649
|
+
function parseToolFilter(value) {
|
|
650
|
+
if (!isRecord(value)) throw new Error("persisted subagent descriptor toolFilter must be an object");
|
|
651
|
+
assertKnownKeys(value, TOOL_FILTER_KEYS, "toolFilter");
|
|
652
|
+
const allow = optionalStringArray(value, "allow");
|
|
653
|
+
const deny = optionalStringArray(value, "deny");
|
|
654
|
+
if (allow === void 0 && deny === void 0) throw new Error("persisted subagent descriptor toolFilter must declare allow and/or deny");
|
|
655
|
+
return {
|
|
656
|
+
...allow !== void 0 ? { allow } : {},
|
|
657
|
+
...deny !== void 0 ? { deny } : {}
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
/** Validate one persisted descriptor payload for the current runtime. */
|
|
661
|
+
function parseSubagentDescriptor(value) {
|
|
662
|
+
if (!isRecord(value)) throw new Error("persisted subagent descriptor payload must be an object");
|
|
663
|
+
const version = value["version"];
|
|
664
|
+
if (typeof version !== "number") throw new Error("persisted subagent descriptor version must be a number");
|
|
665
|
+
if (version !== 3) return void 0;
|
|
666
|
+
const mode = value["mode"];
|
|
667
|
+
if (mode !== "one-shot" && mode !== "continuable") throw new Error("persisted subagent descriptor mode must be \"one-shot\" or \"continuable\"");
|
|
668
|
+
assertKnownKeys(value, mode === "one-shot" ? ONE_SHOT_DESCRIPTOR_KEYS : CONTINUABLE_DESCRIPTOR_KEYS, "payload");
|
|
669
|
+
const provider = value["provider"];
|
|
670
|
+
if (typeof provider !== "string") throw new Error("persisted subagent descriptor provider must be a string");
|
|
671
|
+
if (mode === "one-shot") {
|
|
672
|
+
const label = optionalString(value, "label");
|
|
673
|
+
return {
|
|
674
|
+
version: 3,
|
|
675
|
+
mode,
|
|
676
|
+
provider,
|
|
677
|
+
...label !== void 0 ? { label } : {}
|
|
678
|
+
};
|
|
679
|
+
}
|
|
680
|
+
const label = value["label"];
|
|
681
|
+
if (typeof label !== "string") throw new Error("persisted subagent descriptor label must be a string");
|
|
682
|
+
const agentProvider = optionalString(value, "agentProvider");
|
|
683
|
+
const agentModel = optionalString(value, "agentModel");
|
|
684
|
+
const agentReasoningEffort = optionalString(value, "agentReasoningEffort");
|
|
685
|
+
const persona = optionalString(value, "persona");
|
|
686
|
+
const toolFilter = Object.hasOwn(value, "toolFilter") ? parseToolFilter(value["toolFilter"]) : void 0;
|
|
687
|
+
return {
|
|
688
|
+
version: 3,
|
|
689
|
+
mode,
|
|
690
|
+
provider,
|
|
691
|
+
label,
|
|
692
|
+
...agentProvider !== void 0 ? { agentProvider } : {},
|
|
693
|
+
...agentModel !== void 0 ? { agentModel } : {},
|
|
694
|
+
...agentReasoningEffort !== void 0 ? { agentReasoningEffort } : {},
|
|
695
|
+
...persona !== void 0 ? { persona } : {},
|
|
696
|
+
...toolFilter !== void 0 ? { toolFilter } : {}
|
|
697
|
+
};
|
|
698
|
+
}
|
|
699
|
+
/**
|
|
700
|
+
* Validate and detach a continuable descriptor input.
|
|
701
|
+
* @param input - the caller-collected continuable composition fields.
|
|
702
|
+
* @returns the versioned, detached continuable descriptor payload.
|
|
703
|
+
* @throws when a field is not losslessly JSON-serializable.
|
|
704
|
+
*/
|
|
705
|
+
function snapshotSubagentDescriptor(input) {
|
|
706
|
+
const snapshot = snapshotJsonValue({
|
|
707
|
+
version: 3,
|
|
708
|
+
mode: input.mode,
|
|
709
|
+
provider: input.provider,
|
|
710
|
+
label: input.label,
|
|
711
|
+
...input.agentProvider !== void 0 ? { agentProvider: input.agentProvider } : {},
|
|
712
|
+
...input.agentModel !== void 0 ? { agentModel: input.agentModel } : {},
|
|
713
|
+
...input.agentReasoningEffort !== void 0 ? { agentReasoningEffort: input.agentReasoningEffort } : {},
|
|
714
|
+
...input.persona !== void 0 ? { persona: input.persona } : {},
|
|
715
|
+
...input.toolFilter !== void 0 ? { toolFilter: input.toolFilter } : {}
|
|
716
|
+
});
|
|
717
|
+
if (snapshot === void 0) throw new Error("subagent descriptor is not losslessly JSON-serializable");
|
|
718
|
+
return snapshot;
|
|
719
|
+
}
|
|
720
|
+
/**
|
|
721
|
+
* Fold a persisted child log to its supported descriptor. The first
|
|
722
|
+
* `subagent/descriptor` event is authoritative — the establishing provider
|
|
723
|
+
* appends exactly one, so a later same-type event cannot rewrite the declared
|
|
724
|
+
* composition.
|
|
725
|
+
* @param events - the loaded child session events.
|
|
726
|
+
* @returns the descriptor, or `undefined` when the log has none or its
|
|
727
|
+
* version is not {@link SUBAGENT_DESCRIPTOR_VERSION} (the child cannot be
|
|
728
|
+
* classified by this runtime).
|
|
729
|
+
* @throws when a current-version persisted payload does not match its complete
|
|
730
|
+
* declared schema.
|
|
731
|
+
*/
|
|
732
|
+
function foldSubagentDescriptor(events) {
|
|
733
|
+
const event = events.find((candidate) => candidate.type === "subagent/descriptor");
|
|
734
|
+
if (event === void 0) return void 0;
|
|
735
|
+
return parseSubagentDescriptor(event.data);
|
|
736
|
+
}
|
|
737
|
+
const sessionIdSchema = z$1.string();
|
|
738
|
+
const oneShotCatalogSchema = z$1.object({
|
|
739
|
+
version: z$1.union([z$1.literal(0), z$1.literal(1)]),
|
|
740
|
+
childId: sessionIdSchema,
|
|
741
|
+
childCreatedAt: z$1.number().int().nonnegative(),
|
|
742
|
+
mode: z$1.literal("one-shot"),
|
|
743
|
+
label: z$1.string().optional()
|
|
744
|
+
}).strict();
|
|
745
|
+
const continuableCatalogSchema = z$1.object({
|
|
746
|
+
version: z$1.union([z$1.literal(0), z$1.literal(1)]),
|
|
747
|
+
childId: sessionIdSchema,
|
|
748
|
+
childCreatedAt: z$1.number().int().nonnegative(),
|
|
749
|
+
mode: z$1.literal("continuable"),
|
|
750
|
+
label: z$1.string()
|
|
751
|
+
}).strict();
|
|
752
|
+
const externalCatalogSchema = oneShotCatalogSchema.extend({
|
|
753
|
+
version: z$1.literal(2),
|
|
754
|
+
mode: z$1.literal("external")
|
|
755
|
+
});
|
|
756
|
+
const unknownCatalogSchema = oneShotCatalogSchema.extend({
|
|
757
|
+
version: z$1.literal(1),
|
|
758
|
+
mode: z$1.literal("unknown")
|
|
759
|
+
});
|
|
760
|
+
const eventDataSchema = z$1.union([
|
|
761
|
+
oneShotCatalogSchema,
|
|
762
|
+
externalCatalogSchema,
|
|
763
|
+
continuableCatalogSchema,
|
|
764
|
+
unknownCatalogSchema
|
|
765
|
+
]);
|
|
766
|
+
const viewSchema = z$1.array(z$1.union([
|
|
767
|
+
oneShotCatalogSchema.omit({
|
|
768
|
+
version: true,
|
|
769
|
+
childId: true,
|
|
770
|
+
childCreatedAt: true
|
|
771
|
+
}).extend({
|
|
772
|
+
id: sessionIdSchema,
|
|
773
|
+
createdAt: oneShotCatalogSchema.shape.childCreatedAt
|
|
774
|
+
}),
|
|
775
|
+
externalCatalogSchema.omit({
|
|
776
|
+
version: true,
|
|
777
|
+
childId: true,
|
|
778
|
+
childCreatedAt: true
|
|
779
|
+
}).extend({
|
|
780
|
+
id: sessionIdSchema,
|
|
781
|
+
createdAt: externalCatalogSchema.shape.childCreatedAt
|
|
782
|
+
}),
|
|
783
|
+
continuableCatalogSchema.omit({
|
|
784
|
+
version: true,
|
|
785
|
+
childId: true,
|
|
786
|
+
childCreatedAt: true
|
|
787
|
+
}).extend({
|
|
788
|
+
id: sessionIdSchema,
|
|
789
|
+
createdAt: continuableCatalogSchema.shape.childCreatedAt
|
|
790
|
+
}),
|
|
791
|
+
unknownCatalogSchema.omit({
|
|
792
|
+
version: true,
|
|
793
|
+
childId: true,
|
|
794
|
+
childCreatedAt: true
|
|
795
|
+
}).extend({
|
|
796
|
+
id: sessionIdSchema,
|
|
797
|
+
createdAt: unknownCatalogSchema.shape.childCreatedAt
|
|
798
|
+
})
|
|
799
|
+
]));
|
|
800
|
+
const stateSchema = z$1.object({
|
|
801
|
+
inheritedEventCount: z$1.number().int().nonnegative(),
|
|
802
|
+
head: chunkedListSchema(eventDataSchema).optional()
|
|
803
|
+
}).strict();
|
|
804
|
+
/**
|
|
805
|
+
* Materialize complete and unknown-mode child identities from parent catalog events.
|
|
806
|
+
* @param state - parent catalog fold state.
|
|
807
|
+
* @returns current direct-child rows in parent catalog event order.
|
|
808
|
+
*/
|
|
809
|
+
function subagentCatalogEntries(state) {
|
|
810
|
+
const entries = [];
|
|
811
|
+
for (const data of iterateChunkedList(state.head)) {
|
|
812
|
+
const { version: _version, childId, childCreatedAt, ...descriptor } = data;
|
|
813
|
+
entries.push({
|
|
814
|
+
id: childId,
|
|
815
|
+
createdAt: childCreatedAt,
|
|
816
|
+
...descriptor
|
|
817
|
+
});
|
|
818
|
+
}
|
|
819
|
+
return entries;
|
|
820
|
+
}
|
|
821
|
+
/** Parent-owned direct-child catalog projection; invalid own facts reject restoration. */
|
|
822
|
+
const subagentCatalogProjectionDefinition = {
|
|
823
|
+
key: "subagentCatalog",
|
|
824
|
+
stateSchema,
|
|
825
|
+
init: (_header, inheritedEventCount) => ({ inheritedEventCount }),
|
|
826
|
+
apply: (state, event) => {
|
|
827
|
+
if (event.type !== "subagent/catalog" || event.seq < state.inheritedEventCount) return state;
|
|
828
|
+
return {
|
|
829
|
+
...state,
|
|
830
|
+
head: appendChunkedList(state.head, eventDataSchema.parse(event.data))
|
|
831
|
+
};
|
|
832
|
+
},
|
|
833
|
+
stateVersion: 6,
|
|
834
|
+
wire: {
|
|
835
|
+
viewSchema,
|
|
836
|
+
view: subagentCatalogEntries
|
|
837
|
+
}
|
|
838
|
+
};
|
|
839
|
+
/**
|
|
840
|
+
* Append a complete direct-child discovery fact to its parent Session.
|
|
841
|
+
* @param parent - durable direct parent receiving the discovery fact.
|
|
842
|
+
* @param child - established child's immutable Session metadata.
|
|
843
|
+
* @param descriptor - mode-discriminated creation label frozen with the child.
|
|
844
|
+
*/
|
|
845
|
+
function establishCatalogChild(parent, child, descriptor) {
|
|
846
|
+
parent.append("subagent/catalog", {
|
|
847
|
+
version: 0,
|
|
848
|
+
childId: child.id,
|
|
849
|
+
childCreatedAt: child.createdAt,
|
|
850
|
+
mode: descriptor.mode,
|
|
851
|
+
label: descriptor.label
|
|
852
|
+
});
|
|
853
|
+
}
|
|
854
|
+
/** Catalog payload version emitted by external execution creation. */
|
|
855
|
+
const EXTERNAL_SUBAGENT_CATALOG_VERSION = 2;
|
|
856
|
+
/**
|
|
857
|
+
* Record an external execution without a local child Session.
|
|
858
|
+
* @param parent - durable direct parent receiving the discovery fact.
|
|
859
|
+
* @param childId - identity returned by the external provider.
|
|
860
|
+
* @param label - display label assigned by the caller.
|
|
861
|
+
*/
|
|
862
|
+
function establishExternalCatalogChild(parent, childId, label) {
|
|
863
|
+
parent.append("subagent/catalog", {
|
|
864
|
+
version: EXTERNAL_SUBAGENT_CATALOG_VERSION,
|
|
865
|
+
childId,
|
|
866
|
+
childCreatedAt: Date.now(),
|
|
867
|
+
mode: "external",
|
|
868
|
+
label
|
|
869
|
+
});
|
|
870
|
+
}
|
|
871
|
+
//#endregion
|
|
872
|
+
//#region lib/types/types.js
|
|
873
|
+
/**
|
|
874
|
+
* The seam's consumer-facing contracts: request, result, and capability types
|
|
875
|
+
* for {@link SubagentProvider}, plus the `subagent/start` and `subagent/end`
|
|
876
|
+
* payloads that plugins and hosts observe. Internal control interfaces, such
|
|
877
|
+
* as the manager host, belong with their implementation.
|
|
878
|
+
*
|
|
879
|
+
* @module @deepseek-ai/dsh-subagent/types
|
|
880
|
+
*/
|
|
881
|
+
/**
|
|
882
|
+
* Brand a string as a {@link SubagentRunId}.
|
|
883
|
+
* @param id - the raw run id.
|
|
884
|
+
* @returns the same string, branded.
|
|
885
|
+
*/
|
|
886
|
+
function SubagentRunId(id) {
|
|
887
|
+
return id;
|
|
888
|
+
}
|
|
889
|
+
//#endregion
|
|
890
|
+
//#region lib/types/structured.js
|
|
891
|
+
/**
|
|
892
|
+
* Child-scoped structured-output tool, prompt instruction, terminal guard, and authoritative
|
|
893
|
+
* result capture for in-process subagents. Each child registers its real schema on its own
|
|
894
|
+
* scope, so concurrent runs do not interact and disposal leaves no global residue. The prompt
|
|
895
|
+
* contribution is ordinary reconstructed request state.
|
|
896
|
+
*
|
|
897
|
+
* Capture commits only after the authoritative `tools/result` succeeds; PTC mode capture also
|
|
898
|
+
* waits for the enclosing `run_code` result. The terminal result marker and monotonic tool
|
|
899
|
+
* guard prevent later calls from reopening a completed structured run.
|
|
900
|
+
* @module @deepseek-ai/dsh-subagent/structured
|
|
901
|
+
*/
|
|
902
|
+
/** The model-facing tool name a structured child must call to finish. */
|
|
903
|
+
const STRUCTURED_OUTPUT_TOOL = "structured_output";
|
|
904
|
+
/**
|
|
905
|
+
* The instruction registered as the child's trailing scoped prompt section:
|
|
906
|
+
* the demand travels with the
|
|
907
|
+
* tool, as ordinary prompt state of exactly one agent.
|
|
908
|
+
*/
|
|
909
|
+
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.`;
|
|
910
|
+
/**
|
|
911
|
+
* Attach the scoped capture tool, instruction, and enforcement to a child during
|
|
912
|
+
* its creation window. Child disposal removes every registration.
|
|
913
|
+
* @param childCtx - the child agent's scope context (`setup`'s argument).
|
|
914
|
+
* @param schema - the trusted, already-asserted schema subset to enforce (see
|
|
915
|
+
* `assertObjectJsonSchema` in dsh-tools).
|
|
916
|
+
* @param canComplete - whether accepted input has been processed.
|
|
917
|
+
* @returns the attachment handle (read `captured()` after the child settles).
|
|
918
|
+
*/
|
|
919
|
+
function attachStructuredRuntime(childCtx, schema, canComplete) {
|
|
920
|
+
/**
|
|
921
|
+
* Validated values staged by the capture tool body, awaiting THEIR OWN
|
|
922
|
+
* authoritative `tools/result` notification. The execution object's identity
|
|
923
|
+
* uniquely identifies a trip through the pipeline: adapter call ids may
|
|
924
|
+
* repeat across steps, but another execution can never reach this Map
|
|
925
|
+
* entry. This is distinct from the opaque `ToolExecutionToken` used to
|
|
926
|
+
* correlate nested transports. The final notification always deletes its own
|
|
927
|
+
* stage, whether the result succeeded or failed.
|
|
928
|
+
*/
|
|
929
|
+
const staged = /* @__PURE__ */ new Map();
|
|
930
|
+
/** Successful nested capture waiting for its enclosing transport to commit. */
|
|
931
|
+
let pending;
|
|
932
|
+
let captured;
|
|
933
|
+
const schemaEntry = {
|
|
934
|
+
name: STRUCTURED_OUTPUT_TOOL,
|
|
935
|
+
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.",
|
|
936
|
+
parameters: { ...schema }
|
|
937
|
+
};
|
|
938
|
+
childCtx.tools.register({
|
|
939
|
+
...schemaEntry,
|
|
940
|
+
output: {
|
|
941
|
+
schema: {
|
|
942
|
+
type: "object",
|
|
943
|
+
properties: { recorded: {
|
|
944
|
+
type: "boolean",
|
|
945
|
+
const: true
|
|
946
|
+
} },
|
|
947
|
+
required: ["recorded"],
|
|
948
|
+
additionalProperties: false
|
|
949
|
+
},
|
|
950
|
+
render: () => [{
|
|
951
|
+
type: "text",
|
|
952
|
+
text: "Structured output recorded."
|
|
953
|
+
}]
|
|
954
|
+
},
|
|
955
|
+
execute(args, exec) {
|
|
956
|
+
const violations = validateJsonSchemaValue(schema, args);
|
|
957
|
+
if (violations.length > 0) throw new ToolArgsError(violations);
|
|
958
|
+
if (!canComplete()) throw new Error("Process pending messages before submitting structured output.");
|
|
959
|
+
staged.set(exec, { value: args });
|
|
960
|
+
exec.concludeTurn();
|
|
961
|
+
return Promise.resolve({ recorded: true });
|
|
962
|
+
}
|
|
963
|
+
});
|
|
964
|
+
childCtx.systemPrompt.section({
|
|
965
|
+
name: `tool:${STRUCTURED_OUTPUT_TOOL}`,
|
|
966
|
+
order: childCtx.systemPrompt.getSectionOrder("STRUCTURED_OUTPUT"),
|
|
967
|
+
text: STRUCTURED_OUTPUT_INSTRUCTION
|
|
968
|
+
});
|
|
969
|
+
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`);
|
|
970
|
+
childCtx.on("tools/result", function(exec, result) {
|
|
971
|
+
if (exec.name === "structured_output") {
|
|
972
|
+
const entry = staged.get(exec);
|
|
973
|
+
if (entry === void 0) return;
|
|
974
|
+
staged.delete(exec);
|
|
975
|
+
if (result.isError) return;
|
|
976
|
+
if (exec.parent === void 0) {
|
|
977
|
+
/* v8 ignore else -- sequential agent-loop dispatch lets the guard block every later supported call */
|
|
978
|
+
if (captured === void 0) captured = { value: entry.value };
|
|
979
|
+
} else if (captured === void 0 && pending === void 0) pending = {
|
|
980
|
+
parent: exec.parent,
|
|
981
|
+
value: entry.value
|
|
982
|
+
};
|
|
983
|
+
return;
|
|
984
|
+
}
|
|
985
|
+
if (pending?.parent !== exec.token) return;
|
|
986
|
+
const entry = pending;
|
|
987
|
+
pending = void 0;
|
|
988
|
+
if (result.isError) return;
|
|
989
|
+
/* v8 ignore else -- PTC mode serializes outer executions, so the guard blocks every later supported call */
|
|
990
|
+
if (captured === void 0) captured = { value: entry.value };
|
|
991
|
+
});
|
|
992
|
+
return {
|
|
993
|
+
captured: () => captured,
|
|
994
|
+
acceptsInput: () => staged.size === 0 && pending === void 0 && captured === void 0
|
|
995
|
+
};
|
|
996
|
+
}
|
|
997
|
+
//#endregion
|
|
998
|
+
//#region lib/types/assistant-output.js
|
|
999
|
+
/**
|
|
1000
|
+
* Canonical selection of a child's final assistant output. Backend run results
|
|
1001
|
+
* and `subagent/end.lastAssistantMessage` apply the same rule: select the last
|
|
1002
|
+
* non-empty assistant message. An empty-content message records usage only
|
|
1003
|
+
* when the loop appends it after a max-tokens step with no executable blocks,
|
|
1004
|
+
* so it does not replace earlier output. If no non-empty message exists,
|
|
1005
|
+
* select the accumulated assistant text. Selection is independent of the
|
|
1006
|
+
* run's stop reason.
|
|
1007
|
+
*
|
|
1008
|
+
* @module @deepseek-ai/dsh-subagent/assistant-output
|
|
1009
|
+
*/
|
|
1010
|
+
/**
|
|
1011
|
+
* Incremental fold of the selection rule, for backends that observe a child's
|
|
1012
|
+
* output as it streams: session-event backends {@link push} each event, and
|
|
1013
|
+
* transports without session events (ACP content chunks) {@link pushText} raw
|
|
1014
|
+
* text into the same streamed fallback.
|
|
1015
|
+
*/
|
|
1016
|
+
var AssistantOutputFold = class {
|
|
1017
|
+
message;
|
|
1018
|
+
partial = [];
|
|
1019
|
+
/**
|
|
1020
|
+
* Fold one session event: a non-empty assistant message becomes the
|
|
1021
|
+
* candidate final answer, while its embedded stream and any log-only attempt
|
|
1022
|
+
* extend the streamed fallback; every other event contributes nothing.
|
|
1023
|
+
* @param event - the next observed session event.
|
|
1024
|
+
*/
|
|
1025
|
+
push(event) {
|
|
1026
|
+
if (event.type === "assistant/message") {
|
|
1027
|
+
const content = event.data.message.content;
|
|
1028
|
+
if (content.length > 0) this.message = content;
|
|
1029
|
+
}
|
|
1030
|
+
if (event.type === "assistant/message" || event.type === "assistant/attempt") this.pushText(joinAssistantStreamText(event.data.stream));
|
|
1031
|
+
}
|
|
1032
|
+
/**
|
|
1033
|
+
* Extend the streamed fallback with text observed outside session events.
|
|
1034
|
+
* @param text - the next streamed text piece (an empty piece is a no-op).
|
|
1035
|
+
*/
|
|
1036
|
+
pushText(text) {
|
|
1037
|
+
if (text.length > 0) this.partial.push(text);
|
|
1038
|
+
}
|
|
1039
|
+
/**
|
|
1040
|
+
* Select the final output folded so far.
|
|
1041
|
+
* @returns the last non-empty assistant message, else the accumulated
|
|
1042
|
+
* streamed text, or `undefined` when the child produced neither.
|
|
1043
|
+
*/
|
|
1044
|
+
collect() {
|
|
1045
|
+
if (this.message !== void 0) return this.message;
|
|
1046
|
+
const text = this.partial.join("");
|
|
1047
|
+
return text.length > 0 ? [{
|
|
1048
|
+
type: "text",
|
|
1049
|
+
text
|
|
1050
|
+
}] : void 0;
|
|
1051
|
+
}
|
|
1052
|
+
};
|
|
1053
|
+
/**
|
|
1054
|
+
* Apply the selection rule to one complete child-owned event suffix.
|
|
1055
|
+
* @param events - the child-owned events (after any seed or epoch boundary).
|
|
1056
|
+
* @returns the selected output, or `undefined` when the child produced none.
|
|
1057
|
+
*/
|
|
1058
|
+
function finalAssistantOutput(events) {
|
|
1059
|
+
const fold = new AssistantOutputFold();
|
|
1060
|
+
for (const event of events) fold.push(event);
|
|
1061
|
+
return fold.collect();
|
|
1062
|
+
}
|
|
1063
|
+
//#endregion
|
|
1064
|
+
//#region lib/types/activation.js
|
|
1065
|
+
/** Execution-period records, capacity slots, child serialization, and local output capture.
|
|
1066
|
+
* @module @deepseek-ai/dsh-subagent/activation
|
|
1067
|
+
*/
|
|
1068
|
+
/** Process-local capacity shared by every member of a subagent tree. */
|
|
1069
|
+
var ActivationPool = class {
|
|
1070
|
+
slots = /* @__PURE__ */ new Set();
|
|
1071
|
+
/**
|
|
1072
|
+
* Reserve before reconstruction; release also tolerates unpublished rollback.
|
|
1073
|
+
* @param capacity - the maximum simultaneous activations in this pool.
|
|
1074
|
+
* @returns an idempotent slot release.
|
|
1075
|
+
*/
|
|
1076
|
+
reserve(capacity) {
|
|
1077
|
+
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");
|
|
1078
|
+
const slot = Symbol();
|
|
1079
|
+
this.slots.add(slot);
|
|
1080
|
+
return () => {
|
|
1081
|
+
this.slots.delete(slot);
|
|
1082
|
+
};
|
|
1083
|
+
}
|
|
1084
|
+
};
|
|
1085
|
+
/**
|
|
1086
|
+
* Read the live Agent retained by local execution.
|
|
1087
|
+
* @param activation - the managed local or external execution.
|
|
1088
|
+
* @returns the local Agent, absent for an external run.
|
|
1089
|
+
*/
|
|
1090
|
+
function activationAgent(activation) {
|
|
1091
|
+
return activation.kind === "local" ? activation.handle.agent : void 0;
|
|
1092
|
+
}
|
|
1093
|
+
/**
|
|
1094
|
+
* Require local residency before accepting further input.
|
|
1095
|
+
* @param activation - execution targeted by message delivery.
|
|
1096
|
+
* @returns the local activation.
|
|
1097
|
+
* @throws when the external backend cannot accept another message.
|
|
1098
|
+
*/
|
|
1099
|
+
function requireLocalActivation(activation) {
|
|
1100
|
+
switch (activation.kind) {
|
|
1101
|
+
case "local": return activation;
|
|
1102
|
+
case "external": throw new SubagentError(`subagent "${activation.childId}" does not accept follow-up input`, "NOT_CONTINUABLE");
|
|
1103
|
+
/* v8 ignore next 2 -- Both execution kinds are handled above. */
|
|
1104
|
+
default: return assertNever(activation);
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
/**
|
|
1108
|
+
* Retain local failures whose turn ending could not be committed.
|
|
1109
|
+
* @param activation - exact local Agent and epoch receiving the scoped observers.
|
|
1110
|
+
* @param wake - notify its settlement watcher when failure changes readiness.
|
|
1111
|
+
*/
|
|
1112
|
+
function observeLocalFailure(activation, wake) {
|
|
1113
|
+
const child = activation.handle.agent;
|
|
1114
|
+
child.ctx.on("agent/error", ({ agent }) => {
|
|
1115
|
+
if (agent !== child) return;
|
|
1116
|
+
activation.failureAt = child.session.seq;
|
|
1117
|
+
wake();
|
|
1118
|
+
});
|
|
1119
|
+
child.ctx.on("session/event", (session, event) => {
|
|
1120
|
+
if (session === child.session && event.type === "turn/end" && activation.failureAt !== void 0 && event.seq >= activation.failureAt) activation.failureAt = void 0;
|
|
1121
|
+
});
|
|
1122
|
+
}
|
|
1123
|
+
/**
|
|
1124
|
+
* Capture local output and retain failures without a later committed turn ending.
|
|
1125
|
+
* @param activation - the settled local execution and any structured result.
|
|
1126
|
+
* @returns final output, stop reason, and committed structured value.
|
|
1127
|
+
*/
|
|
1128
|
+
function captureLocalResult(activation) {
|
|
1129
|
+
const events = activation.handle.agent.session.snapshotEvents(activation.outputStart);
|
|
1130
|
+
const output = finalAssistantOutput(events) ?? [];
|
|
1131
|
+
const stopReason = activation.failureAt === void 0 ? epochStopReason(events) : "error";
|
|
1132
|
+
const structured = activation.structured;
|
|
1133
|
+
if (structured !== void 0) {
|
|
1134
|
+
const captured = structured.captured();
|
|
1135
|
+
if (captured !== void 0) return {
|
|
1136
|
+
output,
|
|
1137
|
+
stopReason,
|
|
1138
|
+
structured: captured.value
|
|
1139
|
+
};
|
|
1140
|
+
if (stopReason === "completed") return {
|
|
1141
|
+
output,
|
|
1142
|
+
stopReason: "error"
|
|
1143
|
+
};
|
|
1144
|
+
}
|
|
1145
|
+
return {
|
|
1146
|
+
output,
|
|
1147
|
+
stopReason
|
|
1148
|
+
};
|
|
1149
|
+
}
|
|
1150
|
+
/** Serialize each durable child's delivery, release, and disposal. */
|
|
1151
|
+
var ChildLock = class {
|
|
1152
|
+
tails = /* @__PURE__ */ new Map();
|
|
1153
|
+
/**
|
|
1154
|
+
* Run `operation` after every previously queued operation for `childId`.
|
|
1155
|
+
* @param childId - the durable child whose operations are linearized.
|
|
1156
|
+
* @param operation - the critical section to run in order.
|
|
1157
|
+
* @returns the operation's own settlement.
|
|
1158
|
+
*/
|
|
1159
|
+
run(childId, operation) {
|
|
1160
|
+
const result = (this.tails.get(childId) ?? Promise.resolve()).then(operation, operation);
|
|
1161
|
+
const tail = result.then(() => void 0, () => void 0);
|
|
1162
|
+
this.tails.set(childId, tail);
|
|
1163
|
+
tail.then(() => {
|
|
1164
|
+
if (this.tails.get(childId) === tail) this.tails.delete(childId);
|
|
1165
|
+
});
|
|
1166
|
+
return result;
|
|
1167
|
+
}
|
|
1168
|
+
};
|
|
1169
|
+
//#endregion
|
|
1170
|
+
//#region lib/types/manager.js
|
|
1171
|
+
/**
|
|
1172
|
+
* Owns subagent startup, message admission, cold resume, and execution lifetimes.
|
|
1173
|
+
* One child lock orders input acceptance and closure; one activation map owns
|
|
1174
|
+
* capacity, runtime ownership, and resource release across all providers.
|
|
1175
|
+
*
|
|
1176
|
+
* @module @deepseek-ai/dsh-subagent/manager
|
|
1177
|
+
*/
|
|
1178
|
+
var __addDisposableResource$1 = function(env, value, async) {
|
|
1179
|
+
if (value !== null && value !== void 0) {
|
|
1180
|
+
if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
|
|
1181
|
+
var dispose, inner;
|
|
1182
|
+
if (async) {
|
|
1183
|
+
if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
|
|
1184
|
+
dispose = value[Symbol.asyncDispose];
|
|
1185
|
+
}
|
|
1186
|
+
if (dispose === void 0) {
|
|
1187
|
+
if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
|
|
1188
|
+
dispose = value[Symbol.dispose];
|
|
1189
|
+
if (async) inner = dispose;
|
|
1190
|
+
}
|
|
1191
|
+
if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
|
|
1192
|
+
if (inner) dispose = function() {
|
|
1193
|
+
try {
|
|
1194
|
+
inner.call(this);
|
|
1195
|
+
} catch (e) {
|
|
1196
|
+
return Promise.reject(e);
|
|
1197
|
+
}
|
|
1198
|
+
};
|
|
1199
|
+
env.stack.push({
|
|
1200
|
+
value,
|
|
1201
|
+
dispose,
|
|
1202
|
+
async
|
|
1203
|
+
});
|
|
1204
|
+
} else if (async) env.stack.push({ async: true });
|
|
1205
|
+
return value;
|
|
1206
|
+
};
|
|
1207
|
+
var __disposeResources$1 = (function(SuppressedError) {
|
|
1208
|
+
return function(env) {
|
|
1209
|
+
function fail(e) {
|
|
1210
|
+
env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
|
|
1211
|
+
env.hasError = true;
|
|
1212
|
+
}
|
|
1213
|
+
var r, s = 0;
|
|
1214
|
+
function next() {
|
|
1215
|
+
while (r = env.stack.pop()) try {
|
|
1216
|
+
if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
|
|
1217
|
+
if (r.dispose) {
|
|
1218
|
+
var result = r.dispose.call(r.value);
|
|
1219
|
+
if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) {
|
|
1220
|
+
fail(e);
|
|
1221
|
+
return next();
|
|
1222
|
+
});
|
|
1223
|
+
} else s |= 1;
|
|
1224
|
+
} catch (e) {
|
|
1225
|
+
fail(e);
|
|
1226
|
+
}
|
|
1227
|
+
if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
|
|
1228
|
+
if (env.hasError) throw env.error;
|
|
1229
|
+
}
|
|
1230
|
+
return next();
|
|
1231
|
+
};
|
|
1232
|
+
})(typeof SuppressedError === "function" ? SuppressedError : function(error, suppressed, message) {
|
|
1233
|
+
var e = new Error(message);
|
|
1234
|
+
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
1235
|
+
});
|
|
1236
|
+
/** Render only the safe outer message from execution or teardown failures. */
|
|
1237
|
+
function failureMessage(error) {
|
|
1238
|
+
return error instanceof Error ? error.message : "unknown teardown failure";
|
|
1239
|
+
}
|
|
1240
|
+
/** Own subagent requests and the resources of every live activation. */
|
|
1241
|
+
var SubagentManager = class {
|
|
1242
|
+
ctx;
|
|
1243
|
+
host;
|
|
1244
|
+
maxActiveSubagents;
|
|
1245
|
+
delegatedApproval;
|
|
1246
|
+
/** Child session id → its live Activation. Process-local, never durable. */
|
|
1247
|
+
resident = /* @__PURE__ */ new Map();
|
|
1248
|
+
/** Root identities retain their pool across child settlement without retaining dead roots. */
|
|
1249
|
+
rootPools = /* @__PURE__ */ new WeakMap();
|
|
1250
|
+
/** Materializations admitted before drain, tracked through publication or rollback. */
|
|
1251
|
+
materializations = /* @__PURE__ */ new Set();
|
|
1252
|
+
/** Per-child serializer shared by delivery, release, and disposal. */
|
|
1253
|
+
locks = new ChildLock();
|
|
1254
|
+
/** Structural Cordis owner of every Activation handle. */
|
|
1255
|
+
ownerCtx;
|
|
1256
|
+
/** Exact subagent tree roots whose host teardown has closed admission. */
|
|
1257
|
+
closingRoots = /* @__PURE__ */ new Set();
|
|
1258
|
+
draining = false;
|
|
1259
|
+
/**
|
|
1260
|
+
* Build one manager inside the service's Agent-injected context.
|
|
1261
|
+
* @param ctx - context providing Agents, Sessions, and teardown ownership.
|
|
1262
|
+
* @param host - descriptor reads and lifecycle publication owned by the service.
|
|
1263
|
+
* @param maxActiveSubagents - the current configured child capacity.
|
|
1264
|
+
* @param delegatedApproval - the configured approval policy seed for each local child.
|
|
1265
|
+
*/
|
|
1266
|
+
constructor(ctx, host, maxActiveSubagents, delegatedApproval) {
|
|
1267
|
+
this.ctx = ctx;
|
|
1268
|
+
this.host = host;
|
|
1269
|
+
this.maxActiveSubagents = maxActiveSubagents;
|
|
1270
|
+
this.delegatedApproval = delegatedApproval;
|
|
1271
|
+
const scope = ctx.plugin(function activationOwner() {});
|
|
1272
|
+
this.ownerCtx = scope.ctx;
|
|
1273
|
+
installSubagentArchiveAdmission(ctx, (agent) => {
|
|
1274
|
+
const activation = this.resident.get(agent.id);
|
|
1275
|
+
return activation !== void 0 && activationAgent(activation) === agent ? activation.ancestors : void 0;
|
|
1276
|
+
}, (session) => host.identityOf(session));
|
|
1277
|
+
ctx.on("agent/disposed", ({ agent }) => {
|
|
1278
|
+
this.closingRoots.delete(agent);
|
|
1279
|
+
});
|
|
1280
|
+
ctx.effect(function* () {
|
|
1281
|
+
yield scope.dispose;
|
|
1282
|
+
yield () => this.drain();
|
|
1283
|
+
}.bind(this), "subagents.manager()");
|
|
1284
|
+
}
|
|
1285
|
+
/**
|
|
1286
|
+
* Interrupt one live continuable child's current turn under the supplied authority.
|
|
1287
|
+
* @param targetSessionId - the durable child session id to interrupt.
|
|
1288
|
+
* @param authority - a trusted user operation or exact live ancestor Agent.
|
|
1289
|
+
*/
|
|
1290
|
+
interrupt(targetSessionId, authority) {
|
|
1291
|
+
if (authority.kind === "ancestor") {
|
|
1292
|
+
const caller = authority.agent;
|
|
1293
|
+
if (this.ctx.agents.get(caller.id) !== caller) throw new SubagentError(`interrupting "${targetSessionId}" requires the exact live ancestor agent`, "UNAUTHORIZED");
|
|
1294
|
+
if (caller.id === targetSessionId) throw new SubagentError(`agent "${caller.id}" cannot interrupt itself`, "UNAUTHORIZED");
|
|
1295
|
+
}
|
|
1296
|
+
const activation = this.resident.get(targetSessionId);
|
|
1297
|
+
if (activation === void 0) return;
|
|
1298
|
+
if (authority.kind === "ancestor" && (this.rootOf(authority.agent) !== activation.root || !activation.ancestors.includes(authority.agent.id))) throw new SubagentError(`subagent "${targetSessionId}" is not a live descendant of agent "${authority.agent.id}"`, "UNAUTHORIZED");
|
|
1299
|
+
if (activation.closing !== void 0) return;
|
|
1300
|
+
const kind = authority.kind === "user" ? "user" : "parent";
|
|
1301
|
+
if (activation.kind === "local") activation.handle.agent.cancel({ kind }, { keepInbox: true });
|
|
1302
|
+
else {
|
|
1303
|
+
activation.controller.abort({ kind });
|
|
1304
|
+
this.dispose(activation).catch(() => void 0);
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
/**
|
|
1308
|
+
* Close admission, await every already-admitted materialization through
|
|
1309
|
+
* publication or rollback, then dispose every agent in the subagent tree.
|
|
1310
|
+
*/
|
|
1311
|
+
async drain() {
|
|
1312
|
+
this.draining = true;
|
|
1313
|
+
for (const pending of this.materializations) pending.controller.abort();
|
|
1314
|
+
await Promise.all([...this.materializations].map((materialization) => materialization.settled));
|
|
1315
|
+
await this.disposeRoots([...this.resident.values()], "activation(s)");
|
|
1316
|
+
}
|
|
1317
|
+
/**
|
|
1318
|
+
* Join progressing subagent tree work for its root, leaving idle parked inboxes resident.
|
|
1319
|
+
* @param root - exact live subagent tree root; ordinary members return false.
|
|
1320
|
+
* @returns whether work was joined and the host must recheck root completion.
|
|
1321
|
+
*/
|
|
1322
|
+
async waitForTreeWork(root) {
|
|
1323
|
+
if (this.rootOf(root) !== root) return false;
|
|
1324
|
+
let observed = false;
|
|
1325
|
+
while (true) {
|
|
1326
|
+
const pending = [...this.materializations].filter((item) => item.root === root);
|
|
1327
|
+
const children = [...this.resident.values()].filter((item) => item.root === root);
|
|
1328
|
+
observed ||= children.some((item) => item.kind === "local" && item.handle.agent.status !== "idle");
|
|
1329
|
+
await Promise.all(children.flatMap((item) => item.kind === "local" ? [item.handle.agent.whenIdle()] : []));
|
|
1330
|
+
const waits = pending.map((item) => item.settled);
|
|
1331
|
+
for (const item of children) {
|
|
1332
|
+
if (item.kind === "external" || item.closing !== void 0) {
|
|
1333
|
+
waits.push(item.result.promise.catch(() => void 0));
|
|
1334
|
+
continue;
|
|
1335
|
+
}
|
|
1336
|
+
if (item.handle.agent.status !== "idle") {
|
|
1337
|
+
waits.push(item.handle.agent.whenIdle());
|
|
1338
|
+
continue;
|
|
1339
|
+
}
|
|
1340
|
+
const inbox = item.handle.agent.inbox;
|
|
1341
|
+
if (item.failureAt === void 0 && (inbox.nextTurn.length > 0 || inbox.nextStep.length > 0)) continue;
|
|
1342
|
+
waits.push(Promise.race([item.result.promise.catch(() => void 0), item.poke.promise]));
|
|
1343
|
+
}
|
|
1344
|
+
if (waits.length > 0) {
|
|
1345
|
+
observed = true;
|
|
1346
|
+
await Promise.all(waits);
|
|
1347
|
+
continue;
|
|
1348
|
+
}
|
|
1349
|
+
if ([...this.materializations].some((item) => item.root === root && !pending.includes(item)) || [...this.resident.values()].some((item) => item.root === root && !children.includes(item))) continue;
|
|
1350
|
+
return observed;
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
/**
|
|
1354
|
+
* Stop every member of the selected subagent trees; ordinary members do not close a subagent tree.
|
|
1355
|
+
* @param parents - exact live subagent tree roots whose instances must stop.
|
|
1356
|
+
*/
|
|
1357
|
+
async drainDescendants(parents) {
|
|
1358
|
+
const roots = new Set(parents.filter((root) => this.ctx.agents.get(root.id) === root && this.rootOf(root) === root));
|
|
1359
|
+
for (const root of roots) this.closingRoots.add(root);
|
|
1360
|
+
const pending = [...this.materializations].filter((item) => roots.has(item.root));
|
|
1361
|
+
for (const item of pending) item.controller.abort();
|
|
1362
|
+
const targets = [...this.resident.values()].filter((item) => roots.has(item.root));
|
|
1363
|
+
const disposal = this.disposeRoots(targets, "scoped activation(s)");
|
|
1364
|
+
const [disposed] = await Promise.allSettled([disposal, ...pending.map((item) => item.settled)]);
|
|
1365
|
+
if (disposed.status === "rejected") throw disposed.reason;
|
|
1366
|
+
}
|
|
1367
|
+
/**
|
|
1368
|
+
* Start one continuable background child and resolve at initial inbox acceptance.
|
|
1369
|
+
* Every earlier failure disposes any created handle and rolls back Activation
|
|
1370
|
+
* and its capacity slot without returning either id.
|
|
1371
|
+
* @param spec - provider, delegation request, and caller cancellation.
|
|
1372
|
+
* @param prepare - creation contribution bound to the checked provider.
|
|
1373
|
+
* @returns the durable child id and accepted initial prompt message id.
|
|
1374
|
+
*/
|
|
1375
|
+
async startLocal(spec, prepare) {
|
|
1376
|
+
const parent = spec.parent;
|
|
1377
|
+
this.assertAdmitting(parent);
|
|
1378
|
+
const ancestry = this.ancestryForChild(parent);
|
|
1379
|
+
return this.trackStartup(ancestry.root, spec.signal, async (signal) => {
|
|
1380
|
+
const persistence = spec.delivery === "caller" ? this.ctx.get("sessionPersistence") : this.requirePersistence();
|
|
1381
|
+
const childId = spec.childId ?? brandString(randomUUID());
|
|
1382
|
+
this.assertChildIdAvailable(childId);
|
|
1383
|
+
const childDepth = resolveChildDepth(parent, spec.maxDepth);
|
|
1384
|
+
const agentOptions = resolveChildAgentOptions(parent, spec.agentOptions, childDepth);
|
|
1385
|
+
const agentProvider = agentOptions.provider;
|
|
1386
|
+
const agentModel = agentOptions.model;
|
|
1387
|
+
const agentReasoningEffort = agentOptions.reasoningEffort;
|
|
1388
|
+
const descriptor = snapshotSubagentDescriptor({
|
|
1389
|
+
mode: "continuable",
|
|
1390
|
+
provider: spec.provider,
|
|
1391
|
+
label: spec.label,
|
|
1392
|
+
...agentProvider !== void 0 ? { agentProvider } : {},
|
|
1393
|
+
...agentModel !== void 0 ? { agentModel } : {},
|
|
1394
|
+
...agentReasoningEffort !== void 0 ? { agentReasoningEffort } : {}
|
|
1395
|
+
});
|
|
1396
|
+
const delegatedPolicies = captureDelegatedPolicyOverrides(parent, this.delegatedApproval());
|
|
1397
|
+
const cwd = await this.resolveDirectory(spec, signal);
|
|
1398
|
+
const prepared = await prepare({
|
|
1399
|
+
sessionId: childId,
|
|
1400
|
+
cwd,
|
|
1401
|
+
parent,
|
|
1402
|
+
signal
|
|
1403
|
+
});
|
|
1404
|
+
signal.throwIfAborted();
|
|
1405
|
+
this.assertAdmitting(parent);
|
|
1406
|
+
const inheritedEventCount = SessionLogOffset(prepared.seed?.length ?? 0);
|
|
1407
|
+
const seed = prepared.seed;
|
|
1408
|
+
let established;
|
|
1409
|
+
const messageId = await this.locks.run(childId, async () => {
|
|
1410
|
+
signal.throwIfAborted();
|
|
1411
|
+
this.assertAdmitting(parent);
|
|
1412
|
+
this.assertChildIdAvailable(childId);
|
|
1413
|
+
if (spec.childId !== void 0) {
|
|
1414
|
+
const persisted = await persistence?.stat(childId, { signal });
|
|
1415
|
+
signal.throwIfAborted();
|
|
1416
|
+
this.assertAdmitting(parent);
|
|
1417
|
+
this.assertChildIdAvailable(childId);
|
|
1418
|
+
if (persisted !== void 0) throw new SubagentError(`subagent "${childId}" already exists`, "DUPLICATE_CHILD");
|
|
1419
|
+
}
|
|
1420
|
+
const activation = await this.materialize({
|
|
1421
|
+
kind: "local",
|
|
1422
|
+
childId,
|
|
1423
|
+
provider: spec.provider,
|
|
1424
|
+
...ancestry,
|
|
1425
|
+
create: {
|
|
1426
|
+
parent,
|
|
1427
|
+
cwd,
|
|
1428
|
+
seed,
|
|
1429
|
+
meta: childSessionMeta(parent, childDepth, prepared.seed !== void 0, spec.agentPreset),
|
|
1430
|
+
inheritedEventCount,
|
|
1431
|
+
delegatedPolicies,
|
|
1432
|
+
descriptor
|
|
1433
|
+
},
|
|
1434
|
+
agentOptions,
|
|
1435
|
+
composition: { agentPreset: spec.agentPreset },
|
|
1436
|
+
signal,
|
|
1437
|
+
delivery: spec.delivery,
|
|
1438
|
+
outputSchema: spec.outputSchema
|
|
1439
|
+
});
|
|
1440
|
+
established = activation;
|
|
1441
|
+
const childHeader = requireLocalActivation(activation).handle.agent.session.header;
|
|
1442
|
+
return await this.submitMaterialized(activation, spec.delivery !== "caller" ? withResultDeliveryGuidance(spec.prompt) : spec.prompt, {
|
|
1443
|
+
source: { kind: "user" },
|
|
1444
|
+
signal,
|
|
1445
|
+
delivery: "queue",
|
|
1446
|
+
authorize: () => {
|
|
1447
|
+
this.assertAdmitting(parent);
|
|
1448
|
+
}
|
|
1449
|
+
}, ancestry.root, () => {
|
|
1450
|
+
this.assertAdmitting(parent);
|
|
1451
|
+
establishCatalogChild(parent.session, childHeader, descriptor);
|
|
1452
|
+
});
|
|
1453
|
+
});
|
|
1454
|
+
return {
|
|
1455
|
+
...this.receipt(established),
|
|
1456
|
+
childId,
|
|
1457
|
+
messageId
|
|
1458
|
+
};
|
|
1459
|
+
});
|
|
1460
|
+
}
|
|
1461
|
+
/**
|
|
1462
|
+
* Start an external backend under activation ownership.
|
|
1463
|
+
* @param spec - task, provider, cancellation, and delivery policy.
|
|
1464
|
+
* @param start - execution bound to the checked provider.
|
|
1465
|
+
* @returns the accepted activation and its result/disposal capabilities.
|
|
1466
|
+
*/
|
|
1467
|
+
async startExternal(spec, start) {
|
|
1468
|
+
const { provider, delivery, childId: _childId, ...request } = spec;
|
|
1469
|
+
const parent = request.parent;
|
|
1470
|
+
this.assertAdmitting(parent);
|
|
1471
|
+
const ancestry = this.ancestryForChild(parent);
|
|
1472
|
+
return this.trackStartup(ancestry.root, spec.signal, async (signal) => {
|
|
1473
|
+
let activation;
|
|
1474
|
+
try {
|
|
1475
|
+
const cwd = await this.resolveDirectory(spec, signal);
|
|
1476
|
+
activation = await this.materialize({
|
|
1477
|
+
kind: "external",
|
|
1478
|
+
provider,
|
|
1479
|
+
...ancestry,
|
|
1480
|
+
signal,
|
|
1481
|
+
delivery,
|
|
1482
|
+
start: (signal) => start({
|
|
1483
|
+
...request,
|
|
1484
|
+
cwd,
|
|
1485
|
+
signal
|
|
1486
|
+
})
|
|
1487
|
+
});
|
|
1488
|
+
this.assertAdmitting(parent);
|
|
1489
|
+
signal.throwIfAborted();
|
|
1490
|
+
if (delivery === "parent") establishExternalCatalogChild(parent.session, activation.childId, spec.label);
|
|
1491
|
+
this.accept(activation);
|
|
1492
|
+
return this.receipt(activation);
|
|
1493
|
+
} catch (error) {
|
|
1494
|
+
if (activation !== void 0) try {
|
|
1495
|
+
await this.dispose(activation);
|
|
1496
|
+
} catch (cleanupError) {
|
|
1497
|
+
this.ctx.logger.warn(`subagent "${activation.childId}" admission rollback failed: ${failureMessage(cleanupError)}`);
|
|
1498
|
+
}
|
|
1499
|
+
throw error;
|
|
1500
|
+
}
|
|
1501
|
+
});
|
|
1502
|
+
}
|
|
1503
|
+
/** Capture the explicit or inherited directory while the parent still owns startup. */
|
|
1504
|
+
async resolveDirectory(request, signal) {
|
|
1505
|
+
const cwd = request.cwd !== void 0 && isAbsolute(request.cwd) ? request.cwd : resolve(await this.ctx.workingDirectory.ensure(request.parent, signal), request.cwd ?? ".");
|
|
1506
|
+
signal.throwIfAborted();
|
|
1507
|
+
return cwd;
|
|
1508
|
+
}
|
|
1509
|
+
/**
|
|
1510
|
+
* Resolve the live subagent tree root from runtime ownership, independent of creator residency.
|
|
1511
|
+
* @param caller - exact live Agent selecting the subagent tree.
|
|
1512
|
+
* @returns the live subagent tree root.
|
|
1513
|
+
* @throws when the caller has no active runtime owner.
|
|
1514
|
+
*/
|
|
1515
|
+
rootOf(caller) {
|
|
1516
|
+
if (this.ctx.agents.get(caller.id) !== caller) throw new SubagentError("subagent tree access requires the exact live caller", "UNAUTHORIZED");
|
|
1517
|
+
const identity = this.host.identityOf(caller.session);
|
|
1518
|
+
const delegated = caller.session.header.origin === "subagent" || identity != null && caller.session.isOwnSeq(identity.seq);
|
|
1519
|
+
const activation = this.resident.get(caller.id);
|
|
1520
|
+
if (activation !== void 0 && activationAgent(activation) === caller && this.ctx.agents.get(activation.root.id) === activation.root) return activation.root;
|
|
1521
|
+
if (delegated || activation !== void 0) throw new SubagentError("agent has no active runtime owner", "UNAUTHORIZED");
|
|
1522
|
+
return caller;
|
|
1523
|
+
}
|
|
1524
|
+
/** Capture creation ancestry separately from the root that owns the new instance. */
|
|
1525
|
+
ancestryForChild(creator) {
|
|
1526
|
+
return {
|
|
1527
|
+
root: this.rootOf(creator),
|
|
1528
|
+
ancestors: [...this.resident.get(creator.id)?.ancestors ?? [], creator.id]
|
|
1529
|
+
};
|
|
1530
|
+
}
|
|
1531
|
+
/**
|
|
1532
|
+
* Deliver to another agent in the subagent tree, restoring only the target when absent.
|
|
1533
|
+
* @param sender - exact live root or resident continuable Agent.
|
|
1534
|
+
* @param targetId - another member's durable Session id.
|
|
1535
|
+
* @param content - model-authored message blocks.
|
|
1536
|
+
* @param options - cancellation before inbox acceptance.
|
|
1537
|
+
* @returns the accepted inbox message id.
|
|
1538
|
+
*/
|
|
1539
|
+
async sendMessage(sender, targetId, content, options) {
|
|
1540
|
+
options.signal.throwIfAborted();
|
|
1541
|
+
const root = this.rootOf(sender);
|
|
1542
|
+
if (sender.id === targetId) throw new SubagentError("send_message requires another agent in the subagent tree", "UNAUTHORIZED");
|
|
1543
|
+
this.assertAdmitting(sender);
|
|
1544
|
+
const message = createAgentMessage(sender, content);
|
|
1545
|
+
const activation = this.resident.get(sender.id);
|
|
1546
|
+
return this.deliverToMember(root, targetId, [...message.content], {
|
|
1547
|
+
signal: options.signal,
|
|
1548
|
+
delivery: "steer",
|
|
1549
|
+
source: message.source,
|
|
1550
|
+
authorize: () => {
|
|
1551
|
+
this.assertAdmitting(sender);
|
|
1552
|
+
},
|
|
1553
|
+
onAccepted: () => {
|
|
1554
|
+
if (activation?.kind === "local" && activation.ancestors.at(-1) === targetId) activation.omitClosingText = true;
|
|
1555
|
+
}
|
|
1556
|
+
});
|
|
1557
|
+
}
|
|
1558
|
+
/** Resolve the live owner through verified creation edges without activating ancestors. */
|
|
1559
|
+
async resolveCreationAncestry(source, signal, root) {
|
|
1560
|
+
const ancestors = [];
|
|
1561
|
+
const seen = new Set([source.header.id]);
|
|
1562
|
+
let { header, projections, inheritedEventCount } = source;
|
|
1563
|
+
while (true) {
|
|
1564
|
+
const env_1 = {
|
|
1565
|
+
stack: [],
|
|
1566
|
+
error: void 0,
|
|
1567
|
+
hasError: false
|
|
1568
|
+
};
|
|
1569
|
+
try {
|
|
1570
|
+
signal.throwIfAborted();
|
|
1571
|
+
const parentId = header.parentSession;
|
|
1572
|
+
const identity = projections?.values.subagent;
|
|
1573
|
+
if (parentId === void 0 || identity == null || identity.seq < inheritedEventCount) break;
|
|
1574
|
+
if (seen.has(parentId)) throw new SubagentError("cyclic subagent creation ancestry", "UNAUTHORIZED");
|
|
1575
|
+
seen.add(parentId);
|
|
1576
|
+
const parent = __addDisposableResource$1(env_1, await this.observeContinuation(parentId, signal), false);
|
|
1577
|
+
if (!parent.projections?.values.subagentCatalog?.some((entry) => entry.id === header.id)) throw new SubagentError("subagent is absent from its creator catalog", "UNAUTHORIZED");
|
|
1578
|
+
ancestors.unshift(parentId);
|
|
1579
|
+
if (parentId === root?.id) return {
|
|
1580
|
+
root,
|
|
1581
|
+
ancestors
|
|
1582
|
+
};
|
|
1583
|
+
({header, projections, inheritedEventCount} = parent);
|
|
1584
|
+
} catch (e_1) {
|
|
1585
|
+
env_1.error = e_1;
|
|
1586
|
+
env_1.hasError = true;
|
|
1587
|
+
} finally {
|
|
1588
|
+
__disposeResources$1(env_1);
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1591
|
+
if (root === void 0 && ancestors.length > 0 && header.origin !== "subagent") {
|
|
1592
|
+
const owner = this.ctx.agents.get(header.id);
|
|
1593
|
+
if (owner === void 0 || this.rootOf(owner) !== owner) throw new SubagentError("subagent tree root is not live", "CONTINUATION_UNAVAILABLE");
|
|
1594
|
+
return {
|
|
1595
|
+
root: owner,
|
|
1596
|
+
ancestors
|
|
1597
|
+
};
|
|
1598
|
+
}
|
|
1599
|
+
throw new SubagentError(`agent "${source.header.id}" is not a member of this subagent tree`, "UNAUTHORIZED");
|
|
1600
|
+
}
|
|
1601
|
+
/**
|
|
1602
|
+
* Deliver one host-authored prompt through the target's live subagent tree root.
|
|
1603
|
+
* Remote callers must be authorized for the target Session before invoking this operation.
|
|
1604
|
+
* @param childId - durable continuable subagent session id.
|
|
1605
|
+
* @param content - model-visible prompt blocks.
|
|
1606
|
+
* @param options - inbox destination, durable attribution, and cancellation before acceptance.
|
|
1607
|
+
* @returns the accepted durable message id.
|
|
1608
|
+
*/
|
|
1609
|
+
async deliverPrompt(childId, content, options) {
|
|
1610
|
+
options.signal.throwIfAborted();
|
|
1611
|
+
let root = this.resident.get(childId)?.root;
|
|
1612
|
+
if (root === void 0) {
|
|
1613
|
+
const env_2 = {
|
|
1614
|
+
stack: [],
|
|
1615
|
+
error: void 0,
|
|
1616
|
+
hasError: false
|
|
1617
|
+
};
|
|
1618
|
+
try {
|
|
1619
|
+
const source = __addDisposableResource$1(env_2, await this.observeContinuation(childId, options.signal), false);
|
|
1620
|
+
({root} = await this.resolveCreationAncestry(source, options.signal));
|
|
1621
|
+
} catch (e_2) {
|
|
1622
|
+
env_2.error = e_2;
|
|
1623
|
+
env_2.hasError = true;
|
|
1624
|
+
} finally {
|
|
1625
|
+
__disposeResources$1(env_2);
|
|
1626
|
+
}
|
|
1627
|
+
}
|
|
1628
|
+
return this.deliverToMember(root, childId, content, options);
|
|
1629
|
+
}
|
|
1630
|
+
/** Admit local input synchronously before any teardown can begin. */
|
|
1631
|
+
deliver(activation, message, delivery) {
|
|
1632
|
+
if (activation.closing !== void 0) throw new SubagentError("subagent activation is being disposed; the message was not accepted", "ACTIVATION_CLOSING");
|
|
1633
|
+
const local = requireLocalActivation(activation);
|
|
1634
|
+
if (local.structured?.acceptsInput() === false) throw new SubagentError("subagent is submitting or has submitted its structured result; the message was not accepted", "INPUT_CLOSED");
|
|
1635
|
+
if (delivery === "steer") local.handle.agent.steer(message);
|
|
1636
|
+
else local.handle.agent.followup(message);
|
|
1637
|
+
}
|
|
1638
|
+
/**
|
|
1639
|
+
* Reject one child identity already owned by a live Agent or Session.
|
|
1640
|
+
* @param childId - proposed durable child session id.
|
|
1641
|
+
*/
|
|
1642
|
+
assertChildIdAvailable(childId) {
|
|
1643
|
+
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");
|
|
1644
|
+
}
|
|
1645
|
+
/**
|
|
1646
|
+
* Apply activation admission to a resident parent before delivering input.
|
|
1647
|
+
* @param parent - exact live Agent receiving the message.
|
|
1648
|
+
* @param message - durable user message to deliver.
|
|
1649
|
+
* @param delivery - receiving inbox destination.
|
|
1650
|
+
*/
|
|
1651
|
+
sendWaking(parent, message, delivery) {
|
|
1652
|
+
const parentActivation = this.resident.get(parent.id);
|
|
1653
|
+
if (parentActivation !== void 0 && activationAgent(parentActivation) === parent) {
|
|
1654
|
+
try {
|
|
1655
|
+
this.deliver(parentActivation, message, delivery);
|
|
1656
|
+
} finally {
|
|
1657
|
+
this.wake(parentActivation);
|
|
1658
|
+
}
|
|
1659
|
+
return;
|
|
1660
|
+
}
|
|
1661
|
+
if (delivery === "steer") parent.steer(message);
|
|
1662
|
+
else parent.followup(message);
|
|
1663
|
+
}
|
|
1664
|
+
/**
|
|
1665
|
+
* Reject new work from closing members or subagent trees.
|
|
1666
|
+
* @param agent - exact live caller whose instance and subagent tree must admit work.
|
|
1667
|
+
*/
|
|
1668
|
+
assertAdmitting(agent) {
|
|
1669
|
+
const closing = this.closingTeardownFor(agent);
|
|
1670
|
+
if (closing === void 0) {
|
|
1671
|
+
if (this.resident.get(agent.id)?.closing !== void 0) throw new SubagentError(`subagent "${agent.id}" is closing`, "ACTIVATION_CLOSING");
|
|
1672
|
+
return;
|
|
1673
|
+
}
|
|
1674
|
+
throw new SubagentError(closing === "manager" ? "subagents are draining; the operation was not admitted" : `subagent tree "${closing.id}" is draining; the operation was not admitted`, "DRAINING");
|
|
1675
|
+
}
|
|
1676
|
+
/**
|
|
1677
|
+
* Create or resume one child Agent and publish its Activation.
|
|
1678
|
+
* @param inputs - reconstruction and admission inputs for the residency epoch.
|
|
1679
|
+
* @returns the published process-local Activation.
|
|
1680
|
+
*/
|
|
1681
|
+
materialize(inputs) {
|
|
1682
|
+
this.assertAdmitting(inputs.root);
|
|
1683
|
+
inputs.signal.throwIfAborted();
|
|
1684
|
+
const releaseSlot = this.rootPool(inputs.root).reserve(this.maxActiveSubagents());
|
|
1685
|
+
return this.materializeTracked(inputs, releaseSlot).catch((error) => {
|
|
1686
|
+
releaseSlot();
|
|
1687
|
+
throw error;
|
|
1688
|
+
});
|
|
1689
|
+
}
|
|
1690
|
+
/** Keep subagent tree shutdown and host waits joined to preparation, publication, and rollback. */
|
|
1691
|
+
trackStartup(root, signal, start) {
|
|
1692
|
+
this.assertAdmitting(root);
|
|
1693
|
+
signal.throwIfAborted();
|
|
1694
|
+
const settled = Promise.withResolvers();
|
|
1695
|
+
const controller = new AbortController();
|
|
1696
|
+
const materialization = {
|
|
1697
|
+
root,
|
|
1698
|
+
settled: settled.promise,
|
|
1699
|
+
controller
|
|
1700
|
+
};
|
|
1701
|
+
this.materializations.add(materialization);
|
|
1702
|
+
return start(AbortSignal.any([signal, controller.signal])).catch((error) => {
|
|
1703
|
+
if (error instanceof Error && error.name === "AbortError") this.assertAdmitting(root);
|
|
1704
|
+
throw error;
|
|
1705
|
+
}).finally(() => {
|
|
1706
|
+
this.materializations.delete(materialization);
|
|
1707
|
+
settled.resolve();
|
|
1708
|
+
});
|
|
1709
|
+
}
|
|
1710
|
+
/**
|
|
1711
|
+
* Stop and release one Activation through its memoized close transaction.
|
|
1712
|
+
* @param activation - exact residency epoch to close.
|
|
1713
|
+
* @returns the shared close transaction.
|
|
1714
|
+
*/
|
|
1715
|
+
dispose(activation) {
|
|
1716
|
+
return this.close(activation, () => this.finishDisposal(activation, true));
|
|
1717
|
+
}
|
|
1718
|
+
/** Close admission synchronously and share one release, including startup rollback. */
|
|
1719
|
+
close(activation, release) {
|
|
1720
|
+
if (activation.closing !== void 0) return activation.closing;
|
|
1721
|
+
const completion = Promise.withResolvers();
|
|
1722
|
+
activation.closing = completion.promise;
|
|
1723
|
+
release().then(completion.resolve, completion.reject);
|
|
1724
|
+
return completion.promise;
|
|
1725
|
+
}
|
|
1726
|
+
/** Dispose independent roots and report every branch failure after all settle. */
|
|
1727
|
+
async disposeRoots(roots, failureSubject) {
|
|
1728
|
+
const reasons = (await Promise.all(roots.map(async (activation) => {
|
|
1729
|
+
try {
|
|
1730
|
+
await this.dispose(activation);
|
|
1731
|
+
return;
|
|
1732
|
+
} catch (error) {
|
|
1733
|
+
return error;
|
|
1734
|
+
}
|
|
1735
|
+
}))).filter((failure) => failure !== void 0);
|
|
1736
|
+
if (reasons.length > 0) throw new SubagentError(`subagent teardown failed for ${reasons.length} ${failureSubject}: ` + reasons.map((reason) => failureMessage(reason)).join("; "), "ACTIVATION_TEARDOWN_FAILED");
|
|
1737
|
+
}
|
|
1738
|
+
/** Return the subagent tree teardown that closed admission for this exact Agent. */
|
|
1739
|
+
closingTeardownFor(agent) {
|
|
1740
|
+
if (this.draining) return "manager";
|
|
1741
|
+
const root = this.rootOf(agent);
|
|
1742
|
+
return this.closingRoots.has(root) ? root : void 0;
|
|
1743
|
+
}
|
|
1744
|
+
/** Share one capacity pool across every instance in a subagent tree. */
|
|
1745
|
+
rootPool(parent) {
|
|
1746
|
+
let pool = this.rootPools.get(parent);
|
|
1747
|
+
if (pool === void 0) {
|
|
1748
|
+
pool = new ActivationPool();
|
|
1749
|
+
this.rootPools.set(parent, pool);
|
|
1750
|
+
}
|
|
1751
|
+
return pool;
|
|
1752
|
+
}
|
|
1753
|
+
/** Perform one tracked materialization through publication or rollback. */
|
|
1754
|
+
async materializeTracked(inputs, releaseSlot) {
|
|
1755
|
+
const { provider, root } = inputs;
|
|
1756
|
+
inputs.signal.throwIfAborted();
|
|
1757
|
+
let resource;
|
|
1758
|
+
if (inputs.kind === "external") {
|
|
1759
|
+
const controller = new AbortController();
|
|
1760
|
+
const cancelStartup = () => {
|
|
1761
|
+
controller.abort(inputs.signal.reason);
|
|
1762
|
+
};
|
|
1763
|
+
inputs.signal.addEventListener("abort", cancelStartup, { once: true });
|
|
1764
|
+
try {
|
|
1765
|
+
const run = await inputs.start(controller.signal);
|
|
1766
|
+
resource = {
|
|
1767
|
+
kind: "external",
|
|
1768
|
+
run,
|
|
1769
|
+
controller
|
|
1770
|
+
};
|
|
1771
|
+
run.result.catch(() => void 0);
|
|
1772
|
+
try {
|
|
1773
|
+
this.assertChildIdAvailable(run.id);
|
|
1774
|
+
} catch (error) {
|
|
1775
|
+
await run.dispose();
|
|
1776
|
+
throw error;
|
|
1777
|
+
}
|
|
1778
|
+
} finally {
|
|
1779
|
+
inputs.signal.removeEventListener("abort", cancelStartup);
|
|
1780
|
+
}
|
|
1781
|
+
} else {
|
|
1782
|
+
const { create, childId } = inputs;
|
|
1783
|
+
let structured;
|
|
1784
|
+
const setup = async (childCtx, child) => {
|
|
1785
|
+
const approvalPolicy = create?.delegatedPolicies.approvalPolicy ?? childCtx.get("approval")?.effectivePolicy(child.session);
|
|
1786
|
+
if (inputs.composition.agentPreset !== void 0) await applyChildComposition(childCtx, create?.parent, inputs.composition, approvalPolicy);
|
|
1787
|
+
if (create !== void 0) {
|
|
1788
|
+
if (inputs.composition.agentPreset !== void 0) child.session.append("agent-preset/selected", { agentPreset: inputs.composition.agentPreset });
|
|
1789
|
+
child.session.append("subagent/descriptor", create.descriptor);
|
|
1790
|
+
appendDelegatedPolicyOverrides(child.session, create.delegatedPolicies);
|
|
1791
|
+
const workingDirectory = childCtx.get("workingDirectory");
|
|
1792
|
+
if (workingDirectory === void 0) throw new Error("local subagents require the working-directory service");
|
|
1793
|
+
await workingDirectory.set(child, create.cwd, inputs.signal);
|
|
1794
|
+
}
|
|
1795
|
+
if (inputs.composition.agentPreset === void 0) await applyChildComposition(childCtx, create?.parent, inputs.composition, approvalPolicy);
|
|
1796
|
+
if (inputs.outputSchema !== void 0) structured = attachStructuredRuntime(childCtx, inputs.outputSchema, () => child.inbox.nextStep.length === 0 && child.inbox.nextTurn.length === 0);
|
|
1797
|
+
};
|
|
1798
|
+
const handle = create === void 0 ? await this.ownerCtx.agents.resume({
|
|
1799
|
+
resumeSessionId: childId,
|
|
1800
|
+
agentOptions: inputs.agentOptions,
|
|
1801
|
+
signal: inputs.signal,
|
|
1802
|
+
setup
|
|
1803
|
+
}) : await this.ownerCtx.agents.create({
|
|
1804
|
+
sessionId: childId,
|
|
1805
|
+
meta: create.meta,
|
|
1806
|
+
...create.seed === void 0 ? {} : { seed: create.seed },
|
|
1807
|
+
inheritedEventCount: create.inheritedEventCount,
|
|
1808
|
+
agentOptions: inputs.agentOptions,
|
|
1809
|
+
signal: inputs.signal,
|
|
1810
|
+
setup
|
|
1811
|
+
});
|
|
1812
|
+
resource = {
|
|
1813
|
+
kind: "local",
|
|
1814
|
+
handle,
|
|
1815
|
+
outputStart: handle.agent.session.seq,
|
|
1816
|
+
structured,
|
|
1817
|
+
failureAt: void 0,
|
|
1818
|
+
poke: Promise.withResolvers(),
|
|
1819
|
+
omitClosingText: false
|
|
1820
|
+
};
|
|
1821
|
+
}
|
|
1822
|
+
const childId = resource.kind === "local" ? resource.handle.agent.id : resource.run.id;
|
|
1823
|
+
const activation = {
|
|
1824
|
+
releaseSlot,
|
|
1825
|
+
childId,
|
|
1826
|
+
runId: SubagentRunId(randomUUID()),
|
|
1827
|
+
provider,
|
|
1828
|
+
...resource,
|
|
1829
|
+
root,
|
|
1830
|
+
ancestors: inputs.ancestors,
|
|
1831
|
+
delivery: inputs.delivery ?? "parent",
|
|
1832
|
+
result: Promise.withResolvers(),
|
|
1833
|
+
closing: void 0,
|
|
1834
|
+
accepted: false
|
|
1835
|
+
};
|
|
1836
|
+
activation.result.promise.catch(() => void 0);
|
|
1837
|
+
this.resident.set(childId, activation);
|
|
1838
|
+
try {
|
|
1839
|
+
this.assertAdmitting(root);
|
|
1840
|
+
inputs.signal.throwIfAborted();
|
|
1841
|
+
const wakeSettlement = () => {
|
|
1842
|
+
this.wake(activation);
|
|
1843
|
+
};
|
|
1844
|
+
if (activation.kind === "local") {
|
|
1845
|
+
observeLocalFailure(activation, wakeSettlement);
|
|
1846
|
+
activation.handle.agent.ctx.on("agent/inbox/inserted", wakeSettlement);
|
|
1847
|
+
activation.handle.agent.ctx.on("agent/inbox/claimed", () => {
|
|
1848
|
+
activation.omitClosingText = false;
|
|
1849
|
+
wakeSettlement();
|
|
1850
|
+
});
|
|
1851
|
+
activation.handle.agent.ctx.on("agent/inbox/discarded", wakeSettlement);
|
|
1852
|
+
activation.handle.agent.ctx.on("session/event", (_session, event) => {
|
|
1853
|
+
if (event.type === "tool/call") activation.omitClosingText = false;
|
|
1854
|
+
});
|
|
1855
|
+
}
|
|
1856
|
+
this.host.emitLifecycle("subagent/start", this.runInfo(activation), root);
|
|
1857
|
+
} catch (error) {
|
|
1858
|
+
/* v8 ignore next -- rollback failure must not mask the admission failure
|
|
1859
|
+
* that prevented this operation from returning an accepted message id. */
|
|
1860
|
+
await this.rollbackUnpublished(activation, error).catch(() => void 0);
|
|
1861
|
+
throw error;
|
|
1862
|
+
}
|
|
1863
|
+
return activation;
|
|
1864
|
+
}
|
|
1865
|
+
/** Identity shared by one residency epoch's start/end pair, including cold resumes. */
|
|
1866
|
+
runInfo(activation) {
|
|
1867
|
+
return {
|
|
1868
|
+
runId: activation.runId,
|
|
1869
|
+
provider: activation.provider,
|
|
1870
|
+
parentSessionId: activation.ancestors.at(-1),
|
|
1871
|
+
id: activation.childId,
|
|
1872
|
+
local: activation.kind === "local"
|
|
1873
|
+
};
|
|
1874
|
+
}
|
|
1875
|
+
/** Record acceptance and begin settlement observation before returning the receipt. */
|
|
1876
|
+
accept(activation) {
|
|
1877
|
+
activation.accepted = true;
|
|
1878
|
+
if (activation.kind === "local") this.watchLocalSettlement(activation);
|
|
1879
|
+
else {
|
|
1880
|
+
const finish = () => this.close(activation, () => this.finishDisposal(activation, false));
|
|
1881
|
+
activation.run.result.then(finish, finish).catch((error) => {
|
|
1882
|
+
this.reportTeardownFailure(activation, error);
|
|
1883
|
+
});
|
|
1884
|
+
}
|
|
1885
|
+
}
|
|
1886
|
+
/** Release an Activation whose start edge was not published. */
|
|
1887
|
+
rollbackUnpublished(activation, error) {
|
|
1888
|
+
return this.close(activation, async () => {
|
|
1889
|
+
try {
|
|
1890
|
+
await (activation.kind === "local" ? activation.handle.dispose() : activation.run.dispose());
|
|
1891
|
+
} finally {
|
|
1892
|
+
this.resident.delete(activation.childId);
|
|
1893
|
+
activation.releaseSlot();
|
|
1894
|
+
activation.result.reject(error);
|
|
1895
|
+
}
|
|
1896
|
+
});
|
|
1897
|
+
}
|
|
1898
|
+
/** Let a settlement watcher re-check residency after relevant state changes. */
|
|
1899
|
+
wake(activation) {
|
|
1900
|
+
if (activation.kind === "external") return;
|
|
1901
|
+
activation.poke.resolve();
|
|
1902
|
+
activation.poke = Promise.withResolvers();
|
|
1903
|
+
}
|
|
1904
|
+
/** Follow one Activation to natural settlement. */
|
|
1905
|
+
watchLocalSettlement(activation) {
|
|
1906
|
+
(async () => {
|
|
1907
|
+
while (true) {
|
|
1908
|
+
const idleObservation = activation.poke;
|
|
1909
|
+
await activation.handle.agent.whenIdle();
|
|
1910
|
+
if (activation.closing !== void 0) return;
|
|
1911
|
+
const readiness = await this.locks.run(activation.childId, () => Promise.resolve(this.settlementState(activation, idleObservation)));
|
|
1912
|
+
if (readiness === "closed") return;
|
|
1913
|
+
if (readiness === "retry") continue;
|
|
1914
|
+
if (readiness === "wait") {
|
|
1915
|
+
await idleObservation.promise;
|
|
1916
|
+
continue;
|
|
1917
|
+
}
|
|
1918
|
+
const finalSeq = activation.handle.agent.session.seq;
|
|
1919
|
+
await this.flushFinalState(activation);
|
|
1920
|
+
const attempt = await this.locks.run(activation.childId, () => {
|
|
1921
|
+
const state = this.settlementState(activation, idleObservation);
|
|
1922
|
+
if (state !== "ready") return Promise.resolve(state);
|
|
1923
|
+
if (activation.handle.agent.session.seq !== finalSeq) return Promise.resolve("retry");
|
|
1924
|
+
let done;
|
|
1925
|
+
try {
|
|
1926
|
+
activation.handle.agent.runMaintenance(() => {
|
|
1927
|
+
done = this.close(activation, () => this.finishDisposal(activation, false));
|
|
1928
|
+
return Promise.resolve();
|
|
1929
|
+
});
|
|
1930
|
+
} catch (_error) {
|
|
1931
|
+
return Promise.resolve("retry");
|
|
1932
|
+
}
|
|
1933
|
+
return Promise.resolve({ done });
|
|
1934
|
+
});
|
|
1935
|
+
if (typeof attempt === "string") {
|
|
1936
|
+
if (attempt === "closed") return;
|
|
1937
|
+
continue;
|
|
1938
|
+
}
|
|
1939
|
+
try {
|
|
1940
|
+
await attempt.done;
|
|
1941
|
+
} catch (error) {
|
|
1942
|
+
this.reportTeardownFailure(activation, error);
|
|
1943
|
+
}
|
|
1944
|
+
return;
|
|
1945
|
+
}
|
|
1946
|
+
})();
|
|
1947
|
+
}
|
|
1948
|
+
/** Report disposal failure once after the natural settlement observer finishes. */
|
|
1949
|
+
reportTeardownFailure(activation, error) {
|
|
1950
|
+
this.ctx.logger.warn(`subagent "${activation.childId}" activation teardown failed: ${failureMessage(error)}`);
|
|
1951
|
+
}
|
|
1952
|
+
/** Check pending input before closing admission. */
|
|
1953
|
+
settlementState(activation, observation) {
|
|
1954
|
+
if (activation.closing !== void 0) return "closed";
|
|
1955
|
+
if (activation.poke !== observation) return "retry";
|
|
1956
|
+
const inbox = activation.handle.agent.inbox;
|
|
1957
|
+
if (activation.failureAt === void 0 && (inbox.nextTurn.length > 0 || inbox.nextStep.length > 0)) return "wait";
|
|
1958
|
+
return "ready";
|
|
1959
|
+
}
|
|
1960
|
+
/** Stop and release one instance without affecting its creation descendants. */
|
|
1961
|
+
async finishDisposal(activation, stop) {
|
|
1962
|
+
this.wake(activation);
|
|
1963
|
+
const { childId } = activation;
|
|
1964
|
+
const failures = [];
|
|
1965
|
+
let result = {
|
|
1966
|
+
output: [],
|
|
1967
|
+
stopReason: "error"
|
|
1968
|
+
};
|
|
1969
|
+
let resultFailure;
|
|
1970
|
+
let externalDisposal;
|
|
1971
|
+
const child = activationAgent(activation);
|
|
1972
|
+
const detail = (error) => child === void 0 ? failureMessage(error) : errorChain(error);
|
|
1973
|
+
try {
|
|
1974
|
+
if (activation.kind === "external") {
|
|
1975
|
+
if (stop) {
|
|
1976
|
+
activation.controller.abort({ kind: "parent" });
|
|
1977
|
+
externalDisposal = Promise.resolve().then(() => activation.run.dispose());
|
|
1978
|
+
externalDisposal.catch(() => void 0);
|
|
1979
|
+
}
|
|
1980
|
+
result = await activation.run.result;
|
|
1981
|
+
} else {
|
|
1982
|
+
if (stop) {
|
|
1983
|
+
activation.handle.agent.cancel({ kind: "parent" });
|
|
1984
|
+
await activation.handle.agent.whenIdle();
|
|
1985
|
+
await this.flushFinalState(activation);
|
|
1986
|
+
}
|
|
1987
|
+
result = captureLocalResult(activation);
|
|
1988
|
+
}
|
|
1989
|
+
} catch (error) {
|
|
1990
|
+
resultFailure = { error };
|
|
1991
|
+
failures.push(new SubagentError(`subagent "${childId}" activation teardown failed: ${detail(error)}`, "ACTIVATION_TEARDOWN_FAILED", { cause: error }));
|
|
1992
|
+
}
|
|
1993
|
+
try {
|
|
1994
|
+
if (activation.kind === "local") await activation.handle.dispose();
|
|
1995
|
+
else await (externalDisposal ?? activation.run.dispose());
|
|
1996
|
+
} catch (error) {
|
|
1997
|
+
failures.push(new SubagentError(`subagent "${childId}" activation handle disposal failed: ${detail(error)}`, "ACTIVATION_TEARDOWN_FAILED", { cause: error }));
|
|
1998
|
+
}
|
|
1999
|
+
let failure;
|
|
2000
|
+
if (failures.length === 1) failure = failures[0];
|
|
2001
|
+
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) });
|
|
2002
|
+
this.resident.delete(childId);
|
|
2003
|
+
activation.releaseSlot();
|
|
2004
|
+
const terminal = failure === void 0 ? result : {
|
|
2005
|
+
output: [],
|
|
2006
|
+
stopReason: "error"
|
|
2007
|
+
};
|
|
2008
|
+
this.notifySettlement(activation, result);
|
|
2009
|
+
this.host.emitLifecycle("subagent/end", {
|
|
2010
|
+
...this.runInfo(activation),
|
|
2011
|
+
stopReason: terminal.stopReason,
|
|
2012
|
+
...terminal.output.length === 0 ? {} : { lastAssistantMessage: terminal.output }
|
|
2013
|
+
}, activation.root);
|
|
2014
|
+
if (resultFailure === void 0) activation.result.resolve(result);
|
|
2015
|
+
else activation.result.reject(resultFailure.error);
|
|
2016
|
+
if (failure !== void 0) throw failure;
|
|
2017
|
+
}
|
|
2018
|
+
/** Deliver the captured execution result to the durable direct parent after cleanup. */
|
|
2019
|
+
notifySettlement(activation, terminal) {
|
|
2020
|
+
if (!activation.accepted || activation.delivery === "caller") return;
|
|
2021
|
+
try {
|
|
2022
|
+
const parentId = activation.ancestors.at(-1);
|
|
2023
|
+
const parent = this.ctx.agents.get(parentId);
|
|
2024
|
+
if (parent === void 0 || this.rootOf(parent) !== activation.root) return;
|
|
2025
|
+
const message = createSettlementMessage(activation.childId, terminal, activation.kind === "local", activation.kind === "local" && activation.omitClosingText);
|
|
2026
|
+
if (this.closingTeardownFor(parent) !== void 0) {
|
|
2027
|
+
parent.inject(message);
|
|
2028
|
+
return;
|
|
2029
|
+
}
|
|
2030
|
+
this.sendWaking(parent, message, parent.status === "idle" ? "queue" : "steer");
|
|
2031
|
+
} catch (error) {
|
|
2032
|
+
this.ctx.logger.warn(`subagent "${activation.childId}" settlement notice was not delivered to its parent: ` + errorChain(error));
|
|
2033
|
+
}
|
|
2034
|
+
}
|
|
2035
|
+
/** Request a best-effort final session flush before closing natural-settlement admission. */
|
|
2036
|
+
async flushFinalState(activation) {
|
|
2037
|
+
try {
|
|
2038
|
+
await activation.handle.agent.ctx.sessions.flush(activation.handle.agent.session);
|
|
2039
|
+
} catch (error) {
|
|
2040
|
+
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)}`);
|
|
2041
|
+
}
|
|
2042
|
+
}
|
|
2043
|
+
/** Return capabilities bound to an exact activation instead of a future same-id instance. */
|
|
2044
|
+
receipt(activation) {
|
|
2045
|
+
return {
|
|
2046
|
+
childId: activation.childId,
|
|
2047
|
+
result: activation.result.promise,
|
|
2048
|
+
dispose: () => this.dispose(activation)
|
|
2049
|
+
};
|
|
2050
|
+
}
|
|
2051
|
+
/** Serialize target restoration and inbox admission; closure is awaited outside the lock. */
|
|
2052
|
+
async deliverToMember(root, childId, content, options) {
|
|
2053
|
+
while (true) {
|
|
2054
|
+
this.assertAdmitting(root);
|
|
2055
|
+
options.signal.throwIfAborted();
|
|
2056
|
+
const result = await this.locks.run(childId, async () => {
|
|
2057
|
+
if (childId === root.id) {
|
|
2058
|
+
if (contentHasImage(content)) await this.assertImageCapable(root, options.signal);
|
|
2059
|
+
options.signal.throwIfAborted();
|
|
2060
|
+
options.authorize?.();
|
|
2061
|
+
this.assertAdmitting(root);
|
|
2062
|
+
const message = createUserMessage({
|
|
2063
|
+
content,
|
|
2064
|
+
source: options.source
|
|
2065
|
+
});
|
|
2066
|
+
this.sendWaking(root, message, options.delivery);
|
|
2067
|
+
options.onAccepted?.();
|
|
2068
|
+
return { messageId: message.id };
|
|
2069
|
+
}
|
|
2070
|
+
const activation = this.resident.get(childId);
|
|
2071
|
+
if (activation === void 0) {
|
|
2072
|
+
const restored = await this.restoreTarget(root, childId, options.signal);
|
|
2073
|
+
return { messageId: await this.submitMaterialized(restored, content, options, root) };
|
|
2074
|
+
}
|
|
2075
|
+
if (activation.root !== root) throw new SubagentError("target belongs to another subagent tree", "UNAUTHORIZED");
|
|
2076
|
+
requireLocalActivation(activation);
|
|
2077
|
+
if (activation.closing === void 0 && contentHasImage(content)) await this.assertImageCapable(requireLocalActivation(activation).handle.agent, options.signal);
|
|
2078
|
+
if (activation.closing !== void 0) return { closing: activation.closing };
|
|
2079
|
+
return { messageId: this.submitAdmitted(activation, content, options, root) };
|
|
2080
|
+
});
|
|
2081
|
+
if ("messageId" in result) return result.messageId;
|
|
2082
|
+
await Promise.allSettled([result.closing]);
|
|
2083
|
+
}
|
|
2084
|
+
}
|
|
2085
|
+
/** Read continuation history, preserving cancellation and reporting unavailable storage. */
|
|
2086
|
+
async observeContinuation(childId, signal) {
|
|
2087
|
+
const query = this.requireSessionQuery();
|
|
2088
|
+
try {
|
|
2089
|
+
return await query.observeSession(childId, { signal });
|
|
2090
|
+
} catch (error) {
|
|
2091
|
+
signal.throwIfAborted();
|
|
2092
|
+
throw new SubagentError(`subagent "${childId}" is unavailable`, "NOT_RESUMABLE", { cause: error });
|
|
2093
|
+
}
|
|
2094
|
+
}
|
|
2095
|
+
/** Reconstruct only the target under its subagent tree root; the caller owns acceptance or rollback. */
|
|
2096
|
+
async restoreTarget(root, childId, signal) {
|
|
2097
|
+
return this.trackStartup(root, signal, async (signal) => {
|
|
2098
|
+
const env_3 = {
|
|
2099
|
+
stack: [],
|
|
2100
|
+
error: void 0,
|
|
2101
|
+
hasError: false
|
|
2102
|
+
};
|
|
2103
|
+
try {
|
|
2104
|
+
const source = __addDisposableResource$1(env_3, await this.observeContinuation(childId, signal), false);
|
|
2105
|
+
this.assertAdmitting(root);
|
|
2106
|
+
const descriptor = foldSubagentDescriptor(source.events.slice(source.inheritedEventCount));
|
|
2107
|
+
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");
|
|
2108
|
+
const { ancestors } = await this.resolveCreationAncestry(source, signal, root);
|
|
2109
|
+
let activation;
|
|
2110
|
+
try {
|
|
2111
|
+
const projectedPreset = source.projections?.values.agentPreset;
|
|
2112
|
+
if (projectedPreset === void 0 && source.events.some((event) => event.type === "agent-preset/selected")) throw new SubagentError(`subagent "${childId}" has no agent preset projection for restoration`, "NOT_RESUMABLE");
|
|
2113
|
+
const agentPreset = projectedPreset ?? source.header.agentPreset;
|
|
2114
|
+
activation = await this.materialize({
|
|
2115
|
+
kind: "local",
|
|
2116
|
+
childId,
|
|
2117
|
+
provider: descriptor.provider,
|
|
2118
|
+
root,
|
|
2119
|
+
ancestors,
|
|
2120
|
+
agentOptions: {
|
|
2121
|
+
...descriptor.agentProvider !== void 0 ? { provider: descriptor.agentProvider } : {},
|
|
2122
|
+
...descriptor.agentModel !== void 0 ? { model: descriptor.agentModel } : {},
|
|
2123
|
+
...descriptor.agentReasoningEffort !== void 0 ? { reasoningEffort: ReasoningEffortId(descriptor.agentReasoningEffort) } : {}
|
|
2124
|
+
},
|
|
2125
|
+
composition: {
|
|
2126
|
+
agentPreset: agentPreset ?? void 0,
|
|
2127
|
+
persona: descriptor.persona,
|
|
2128
|
+
toolFilter: descriptor.toolFilter
|
|
2129
|
+
},
|
|
2130
|
+
signal
|
|
2131
|
+
});
|
|
2132
|
+
} catch (error) {
|
|
2133
|
+
signal.throwIfAborted();
|
|
2134
|
+
if (error instanceof SubagentError) throw error;
|
|
2135
|
+
throw new SubagentError(`subagent "${childId}" is unavailable`, "NOT_RESUMABLE", { cause: error });
|
|
2136
|
+
}
|
|
2137
|
+
return activation;
|
|
2138
|
+
} catch (e_3) {
|
|
2139
|
+
env_3.error = e_3;
|
|
2140
|
+
env_3.hasError = true;
|
|
2141
|
+
} finally {
|
|
2142
|
+
__disposeResources$1(env_3);
|
|
2143
|
+
}
|
|
2144
|
+
});
|
|
2145
|
+
}
|
|
2146
|
+
/** Admit a materialized child, commit its creation fact, and release it on failure. */
|
|
2147
|
+
async submitMaterialized(activation, content, options, parent, commit) {
|
|
2148
|
+
try {
|
|
2149
|
+
if (contentHasImage(content)) {
|
|
2150
|
+
await this.assertImageCapable(requireLocalActivation(activation).handle.agent, options.signal);
|
|
2151
|
+
if (activation.closing !== void 0) throw new SubagentError(`subagent "${activation.childId}" is closing`, "ACTIVATION_CLOSING");
|
|
2152
|
+
}
|
|
2153
|
+
const messageId = this.submitAdmitted(activation, content, options, parent);
|
|
2154
|
+
commit?.();
|
|
2155
|
+
this.accept(activation);
|
|
2156
|
+
return messageId;
|
|
2157
|
+
} catch (error) {
|
|
2158
|
+
try {
|
|
2159
|
+
await this.dispose(activation);
|
|
2160
|
+
} catch (cleanupError) {
|
|
2161
|
+
this.ctx.logger.warn(`subagent continuation: disposal after admission or catalog append failure also failed: ${String(cleanupError)}`);
|
|
2162
|
+
}
|
|
2163
|
+
throw error;
|
|
2164
|
+
}
|
|
2165
|
+
}
|
|
2166
|
+
/** Build and submit one message across the final synchronous admission cutoff. */
|
|
2167
|
+
submitAdmitted(activation, content, options, parent) {
|
|
2168
|
+
const message = createUserMessage({
|
|
2169
|
+
content,
|
|
2170
|
+
source: options.source
|
|
2171
|
+
});
|
|
2172
|
+
options.signal.throwIfAborted();
|
|
2173
|
+
options.authorize?.();
|
|
2174
|
+
this.assertAdmitting(parent);
|
|
2175
|
+
try {
|
|
2176
|
+
this.deliver(activation, message, options.delivery);
|
|
2177
|
+
} finally {
|
|
2178
|
+
this.wake(activation);
|
|
2179
|
+
}
|
|
2180
|
+
options.onAccepted?.();
|
|
2181
|
+
return message.id;
|
|
2182
|
+
}
|
|
2183
|
+
/** Refuse image content for a child whose fixed model accepts text only. */
|
|
2184
|
+
async assertImageCapable(agent, signal) {
|
|
2185
|
+
const { provider, model } = agent.options;
|
|
2186
|
+
if (provider === void 0 || model === void 0) return;
|
|
2187
|
+
const llm = this.ctx.get("llm");
|
|
2188
|
+
/* v8 ignore next -- without an LLM registry, delivery defers to projection. */
|
|
2189
|
+
if (llm === void 0) return;
|
|
2190
|
+
const info = await llm.resolveModelInfo(provider, model, signal);
|
|
2191
|
+
if (info.inputModalities !== void 0 && !info.inputModalities.includes("image")) throw new SubagentError(`Model "${model}" does not support image input.`, "MODEL_DOES_NOT_SUPPORT_IMAGES");
|
|
2192
|
+
}
|
|
2193
|
+
/** Resolve the persistence service continuable children require, or fail loud. */
|
|
2194
|
+
requirePersistence() {
|
|
2195
|
+
const persistence = this.ctx.get("sessionPersistence");
|
|
2196
|
+
if (persistence === void 0) throw new SubagentError("continuable subagents require session persistence (load a dsh-session-persistence backend)", "PERSISTENCE_UNAVAILABLE");
|
|
2197
|
+
return persistence;
|
|
2198
|
+
}
|
|
2199
|
+
/** Resolve the Session query service used for cold child observations. */
|
|
2200
|
+
requireSessionQuery() {
|
|
2201
|
+
const query = this.ctx.get("sessionQuery");
|
|
2202
|
+
if (query === void 0) throw new SubagentError("continuable subagents require session query (load @deepseek-ai/dsh-session-query)", "CONTINUATION_UNAVAILABLE");
|
|
2203
|
+
return query;
|
|
2204
|
+
}
|
|
2205
|
+
};
|
|
2206
|
+
//#endregion
|
|
2207
|
+
//#region lib/types/list-children.js
|
|
2208
|
+
/**
|
|
2209
|
+
* Direct-child and recursive descendant discovery from parent-owned catalogs.
|
|
2210
|
+
* Each catalog read releases its Session observation before the next branch.
|
|
2211
|
+
* @module @deepseek-ai/dsh-subagent
|
|
2212
|
+
*/
|
|
2213
|
+
var __addDisposableResource = function(env, value, async) {
|
|
2214
|
+
if (value !== null && value !== void 0) {
|
|
2215
|
+
if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
|
|
2216
|
+
var dispose, inner;
|
|
2217
|
+
if (async) {
|
|
2218
|
+
if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
|
|
2219
|
+
dispose = value[Symbol.asyncDispose];
|
|
2220
|
+
}
|
|
2221
|
+
if (dispose === void 0) {
|
|
2222
|
+
if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
|
|
2223
|
+
dispose = value[Symbol.dispose];
|
|
2224
|
+
if (async) inner = dispose;
|
|
2225
|
+
}
|
|
2226
|
+
if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
|
|
2227
|
+
if (inner) dispose = function() {
|
|
2228
|
+
try {
|
|
2229
|
+
inner.call(this);
|
|
2230
|
+
} catch (e) {
|
|
2231
|
+
return Promise.reject(e);
|
|
2232
|
+
}
|
|
2233
|
+
};
|
|
2234
|
+
env.stack.push({
|
|
2235
|
+
value,
|
|
2236
|
+
dispose,
|
|
2237
|
+
async
|
|
2238
|
+
});
|
|
2239
|
+
} else if (async) env.stack.push({ async: true });
|
|
2240
|
+
return value;
|
|
2241
|
+
};
|
|
2242
|
+
var __disposeResources = (function(SuppressedError) {
|
|
2243
|
+
return function(env) {
|
|
2244
|
+
function fail(e) {
|
|
2245
|
+
env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
|
|
2246
|
+
env.hasError = true;
|
|
2247
|
+
}
|
|
2248
|
+
var r, s = 0;
|
|
2249
|
+
function next() {
|
|
2250
|
+
while (r = env.stack.pop()) try {
|
|
2251
|
+
if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
|
|
2252
|
+
if (r.dispose) {
|
|
2253
|
+
var result = r.dispose.call(r.value);
|
|
2254
|
+
if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) {
|
|
2255
|
+
fail(e);
|
|
2256
|
+
return next();
|
|
2257
|
+
});
|
|
2258
|
+
} else s |= 1;
|
|
2259
|
+
} catch (e) {
|
|
2260
|
+
fail(e);
|
|
2261
|
+
}
|
|
2262
|
+
if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
|
|
2263
|
+
if (env.hasError) throw env.error;
|
|
2264
|
+
}
|
|
2265
|
+
return next();
|
|
2266
|
+
};
|
|
2267
|
+
})(typeof SuppressedError === "function" ? SuppressedError : function(error, suppressed, message) {
|
|
2268
|
+
var e = new Error(message);
|
|
2269
|
+
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
2270
|
+
});
|
|
2271
|
+
/**
|
|
2272
|
+
* Read one parent's durable catalog through a live-preferred Session observation.
|
|
2273
|
+
* @param ctx - context carrying the Session query service.
|
|
2274
|
+
* @param parentSessionId - parent whose direct children are requested.
|
|
2275
|
+
* @param signal - cancellation forwarded to the Session observation.
|
|
2276
|
+
* @returns direct-child rows in parent catalog event order.
|
|
2277
|
+
* @throws {@link SubagentError} when query or catalog projection is unavailable.
|
|
2278
|
+
*/
|
|
2279
|
+
async function listChildren(ctx, parentSessionId, signal) {
|
|
2280
|
+
const env_1 = {
|
|
2281
|
+
stack: [],
|
|
2282
|
+
error: void 0,
|
|
2283
|
+
hasError: false
|
|
2284
|
+
};
|
|
2285
|
+
try {
|
|
2286
|
+
const query = ctx.get("sessionQuery");
|
|
2287
|
+
if (query === void 0) throw new SubagentError("listing subagents requires the sessionQuery service (load @deepseek-ai/dsh-session-query)", "SUBAGENT_CONTROL_QUERY_UNAVAILABLE");
|
|
2288
|
+
const entries = __addDisposableResource(env_1, await query.observeSession(parentSessionId, { ...signal === void 0 ? {} : { signal } }), false).projections?.values.subagentCatalog;
|
|
2289
|
+
if (entries === void 0) throw new SubagentError("listing subagents requires the registered subagentCatalog projection", "SUBAGENT_CONTROL_PROJECTIONS_UNAVAILABLE");
|
|
2290
|
+
return entries;
|
|
2291
|
+
} catch (e_1) {
|
|
2292
|
+
env_1.error = e_1;
|
|
2293
|
+
env_1.hasError = true;
|
|
2294
|
+
} finally {
|
|
2295
|
+
__disposeResources(env_1);
|
|
2296
|
+
}
|
|
2297
|
+
}
|
|
2298
|
+
/**
|
|
2299
|
+
* Walk reachable parent catalogs in stable pre-order without loading Agents.
|
|
2300
|
+
* @see SubagentRuntime.listDescendants for failure and cancellation semantics.
|
|
2301
|
+
* @param ctx - context carrying the Session store and query service.
|
|
2302
|
+
* @param rootSessionId - parent whose catalog starts the traversal.
|
|
2303
|
+
* @param signal - cancellation checked around each catalog read.
|
|
2304
|
+
* @returns children and branch diagnostics with catalog parent and depth.
|
|
2305
|
+
*/
|
|
2306
|
+
async function listDescendants(ctx, rootSessionId, signal) {
|
|
2307
|
+
const sessions = ctx.get("sessions");
|
|
2308
|
+
if (sessions === void 0) throw new SubagentError("listing subagents requires the session store (load @deepseek-ai/dsh-session)", "SUBAGENT_CONTROL_SESSION_STORE_UNAVAILABLE");
|
|
2309
|
+
const readChildren = async (id) => {
|
|
2310
|
+
assertListingNotCancelled(signal);
|
|
2311
|
+
let children;
|
|
2312
|
+
try {
|
|
2313
|
+
children = await listChildren(ctx, id, signal);
|
|
2314
|
+
} catch (error) {
|
|
2315
|
+
assertListingNotCancelled(signal);
|
|
2316
|
+
throw error;
|
|
2317
|
+
}
|
|
2318
|
+
assertListingNotCancelled(signal);
|
|
2319
|
+
return children;
|
|
2320
|
+
};
|
|
2321
|
+
const stack = (await readChildren(rootSessionId)).map((entry) => ({
|
|
2322
|
+
entry,
|
|
2323
|
+
parentId: rootSessionId,
|
|
2324
|
+
depth: 1
|
|
2325
|
+
})).reverse();
|
|
2326
|
+
const visited = new Set([rootSessionId]);
|
|
2327
|
+
const result = [];
|
|
2328
|
+
for (let position = stack.pop(); position !== void 0; position = stack.pop()) {
|
|
2329
|
+
const { entry, parentId, depth } = position;
|
|
2330
|
+
if (visited.has(entry.id)) continue;
|
|
2331
|
+
visited.add(entry.id);
|
|
2332
|
+
let children;
|
|
2333
|
+
try {
|
|
2334
|
+
children = entry.mode === "external" ? [] : await readChildren(entry.id);
|
|
2335
|
+
} catch (error) {
|
|
2336
|
+
if (error instanceof SubagentError) throw error;
|
|
2337
|
+
const code = error instanceof Error && "code" in error ? error.code : void 0;
|
|
2338
|
+
result.push({
|
|
2339
|
+
kind: "diagnostic",
|
|
2340
|
+
id: entry.id,
|
|
2341
|
+
parentId,
|
|
2342
|
+
depth,
|
|
2343
|
+
reason: code === "SESSION_QUERY_CORRUPT_SESSION" || code === "SESSION_QUERY_SOURCE_CONFLICT" ? "corrupt" : "unavailable"
|
|
2344
|
+
});
|
|
2345
|
+
continue;
|
|
2346
|
+
}
|
|
2347
|
+
if (entry.mode === "unknown") result.push({
|
|
2348
|
+
kind: "diagnostic",
|
|
2349
|
+
id: entry.id,
|
|
2350
|
+
parentId,
|
|
2351
|
+
depth,
|
|
2352
|
+
reason: "unsupported"
|
|
2353
|
+
});
|
|
2354
|
+
else {
|
|
2355
|
+
const { createdAt: _createdAt, ...identity } = entry;
|
|
2356
|
+
result.push({
|
|
2357
|
+
...identity,
|
|
2358
|
+
kind: "child",
|
|
2359
|
+
parentId,
|
|
2360
|
+
depth,
|
|
2361
|
+
activity: sessions.get(entry.id) === void 0 ? "inactive" : "running",
|
|
2362
|
+
hasChildren: children.length > 0
|
|
2363
|
+
});
|
|
2364
|
+
}
|
|
2365
|
+
for (const child of [...children].reverse()) stack.push({
|
|
2366
|
+
entry: child,
|
|
2367
|
+
parentId: entry.id,
|
|
2368
|
+
depth: depth + 1
|
|
2369
|
+
});
|
|
2370
|
+
}
|
|
2371
|
+
return result;
|
|
2372
|
+
}
|
|
2373
|
+
/** Stop the complete traversal when its caller cancels. */
|
|
2374
|
+
function assertListingNotCancelled(signal) {
|
|
2375
|
+
if (signal?.aborted) throw new SubagentError("subagent listing was cancelled", "CANCELLED");
|
|
2376
|
+
}
|
|
2377
|
+
//#endregion
|
|
2378
|
+
//#region lib/types/projection.js
|
|
2379
|
+
/**
|
|
2380
|
+
* Pure session projections for subagent identity (mode/label) and active-turn
|
|
2381
|
+
* duration.
|
|
2382
|
+
*
|
|
2383
|
+
* @module @deepseek-ai/dsh-subagent/projection
|
|
2384
|
+
*/
|
|
2385
|
+
const activeIntervalSchema = z$1.object({
|
|
2386
|
+
since: z$1.number().int().nonnegative(),
|
|
2387
|
+
through: z$1.number().int().nonnegative()
|
|
2388
|
+
}).strict();
|
|
2389
|
+
const projectionSchema = z$1.object({
|
|
2390
|
+
settledMs: z$1.number().int().nonnegative(),
|
|
2391
|
+
active: activeIntervalSchema.optional(),
|
|
2392
|
+
lastTurnCompleted: z$1.boolean().optional()
|
|
2393
|
+
}).strict().transform(({ settledMs, active, lastTurnCompleted }) => ({
|
|
2394
|
+
settledMs,
|
|
2395
|
+
...active === void 0 ? {} : { active },
|
|
2396
|
+
...lastTurnCompleted === void 0 ? {} : { lastTurnCompleted }
|
|
2397
|
+
}));
|
|
2398
|
+
/**
|
|
2399
|
+
* Fold turn boundaries around the child's own durable descriptor.
|
|
2400
|
+
*
|
|
2401
|
+
* A fork seed may contain an ancestor descriptor and completed turns. Every
|
|
2402
|
+
* descriptor therefore resets the accumulated state; the healthy catalog
|
|
2403
|
+
* admits only a child with exactly one descriptor in its own suffix, making
|
|
2404
|
+
* the final reset the child's authoritative timing origin.
|
|
2405
|
+
*/
|
|
2406
|
+
const subagentTimingProjectionDefinition = {
|
|
2407
|
+
key: "subagentTiming",
|
|
2408
|
+
stateSchema: z$1.object({
|
|
2409
|
+
settledMs: z$1.number().int().nonnegative(),
|
|
2410
|
+
active: activeIntervalSchema.optional(),
|
|
2411
|
+
pendingTurnStart: z$1.number().int().nonnegative().optional(),
|
|
2412
|
+
descriptorSeen: z$1.boolean(),
|
|
2413
|
+
lastTurnCompleted: z$1.boolean().optional()
|
|
2414
|
+
}).strict(),
|
|
2415
|
+
init: () => ({
|
|
2416
|
+
descriptorSeen: false,
|
|
2417
|
+
settledMs: 0
|
|
2418
|
+
}),
|
|
2419
|
+
apply: (state, event) => {
|
|
2420
|
+
if (event.type === "turn/start") {
|
|
2421
|
+
const { lastTurnCompleted: _closed, ...openState } = state;
|
|
2422
|
+
return state.descriptorSeen ? {
|
|
2423
|
+
...openState,
|
|
2424
|
+
active: {
|
|
2425
|
+
since: event.time,
|
|
2426
|
+
through: event.time
|
|
2427
|
+
}
|
|
2428
|
+
} : {
|
|
2429
|
+
...openState,
|
|
2430
|
+
pendingTurnStart: event.time
|
|
2431
|
+
};
|
|
2432
|
+
}
|
|
2433
|
+
if (event.type === "subagent/descriptor") {
|
|
2434
|
+
const activeSince = state.active?.since ?? state.pendingTurnStart;
|
|
2435
|
+
return {
|
|
2436
|
+
descriptorSeen: true,
|
|
2437
|
+
settledMs: 0,
|
|
2438
|
+
...activeSince === void 0 ? {} : { active: {
|
|
2439
|
+
since: activeSince,
|
|
2440
|
+
through: event.time
|
|
2441
|
+
} }
|
|
2442
|
+
};
|
|
2443
|
+
}
|
|
2444
|
+
if (event.type === "turn/end") {
|
|
2445
|
+
if (!state.descriptorSeen) {
|
|
2446
|
+
if (state.pendingTurnStart === void 0) return state;
|
|
2447
|
+
const { pendingTurnStart: _closed, ...next } = state;
|
|
2448
|
+
return next;
|
|
2449
|
+
}
|
|
2450
|
+
if (state.active === void 0) return state;
|
|
2451
|
+
const { active, ...rest } = state;
|
|
2452
|
+
return {
|
|
2453
|
+
...rest,
|
|
2454
|
+
settledMs: state.settledMs + Math.max(0, event.time - active.since),
|
|
2455
|
+
lastTurnCompleted: event.data.reason.kind === "completed"
|
|
2456
|
+
};
|
|
2457
|
+
}
|
|
2458
|
+
if (state.active === void 0) return state;
|
|
2459
|
+
return {
|
|
2460
|
+
...state,
|
|
2461
|
+
active: {
|
|
2462
|
+
...state.active,
|
|
2463
|
+
through: event.time
|
|
2464
|
+
}
|
|
2465
|
+
};
|
|
2466
|
+
},
|
|
2467
|
+
wire: {
|
|
2468
|
+
viewSchema: projectionSchema,
|
|
2469
|
+
view: (state) => ({
|
|
2470
|
+
settledMs: state.settledMs,
|
|
2471
|
+
...state.active === void 0 ? {} : { active: state.active },
|
|
2472
|
+
...state.lastTurnCompleted === void 0 ? {} : { lastTurnCompleted: state.lastTurnCompleted }
|
|
2473
|
+
})
|
|
2474
|
+
},
|
|
2475
|
+
stateVersion: 3
|
|
2476
|
+
};
|
|
2477
|
+
const identityValueSchema = z$1.discriminatedUnion("mode", [z$1.object({
|
|
2478
|
+
mode: z$1.literal("one-shot"),
|
|
2479
|
+
label: z$1.string().optional(),
|
|
2480
|
+
seq: z$1.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER).transform(SessionSeq)
|
|
2481
|
+
}).strict(), z$1.object({
|
|
2482
|
+
mode: z$1.literal("continuable"),
|
|
2483
|
+
label: z$1.string(),
|
|
2484
|
+
seq: z$1.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER).transform(SessionSeq)
|
|
2485
|
+
}).strict()]);
|
|
2486
|
+
const identitySchema = identityValueSchema.nullable();
|
|
2487
|
+
const identityStateSchema = z$1.object({ identity: identityValueSchema.optional() }).strict();
|
|
2488
|
+
/** Interpret one `subagent/descriptor` event's identity; no value when the payload cannot be trusted. */
|
|
2489
|
+
function descriptorIdentity(event) {
|
|
2490
|
+
let descriptor;
|
|
2491
|
+
try {
|
|
2492
|
+
descriptor = foldSubagentDescriptor([event]);
|
|
2493
|
+
} catch {
|
|
2494
|
+
descriptor = void 0;
|
|
2495
|
+
}
|
|
2496
|
+
if (descriptor === void 0) return void 0;
|
|
2497
|
+
return descriptor.mode === "one-shot" ? {
|
|
2498
|
+
mode: "one-shot",
|
|
2499
|
+
...descriptor.label !== void 0 ? { label: descriptor.label } : {},
|
|
2500
|
+
seq: event.seq
|
|
2501
|
+
} : {
|
|
2502
|
+
mode: "continuable",
|
|
2503
|
+
label: descriptor.label,
|
|
2504
|
+
seq: event.seq
|
|
2505
|
+
};
|
|
2506
|
+
}
|
|
2507
|
+
/**
|
|
2508
|
+
* Fold the durable mode/label identity from `subagent/descriptor` events,
|
|
2509
|
+
* last-wins: a fork seed may replay an ancestor's descriptor, and the child's
|
|
2510
|
+
* own descriptor must override it — the same reset discipline as
|
|
2511
|
+
* {@link subagentTimingProjectionDefinition}. A malformed or unknown-version
|
|
2512
|
+
* payload resets to the `null` sentinel instead of throwing, so a fork of a
|
|
2513
|
+
* healthy ancestor never inherits an identity its own descriptor failed to
|
|
2514
|
+
* establish — and the reset survives every JSON push frame, so a consumer
|
|
2515
|
+
* holding the earlier identity replaces it instead of keeping it stale;
|
|
2516
|
+
* `null` ⟺ no valid descriptor, with the causes deliberately undistinguished.
|
|
2517
|
+
*/
|
|
2518
|
+
const subagentIdentityProjectionDefinition = {
|
|
2519
|
+
key: "subagent",
|
|
2520
|
+
stateSchema: identityStateSchema,
|
|
2521
|
+
init: () => ({}),
|
|
2522
|
+
apply: (state, event) => {
|
|
2523
|
+
if (event.type !== "subagent/descriptor") return state;
|
|
2524
|
+
const identity = descriptorIdentity(event);
|
|
2525
|
+
return identity === void 0 ? {} : { identity };
|
|
2526
|
+
},
|
|
2527
|
+
wire: {
|
|
2528
|
+
viewSchema: identitySchema,
|
|
2529
|
+
view: (state) => state.identity ?? null
|
|
2530
|
+
},
|
|
2531
|
+
stateVersion: 2
|
|
2532
|
+
};
|
|
2533
|
+
//#endregion
|
|
2534
|
+
//#region lib/types/out-of-process.js
|
|
2535
|
+
/**
|
|
2536
|
+
* Provider-side vocabulary for OUT-OF-PROCESS subagent backends — the pieces
|
|
2537
|
+
* that enforce this seam's own contracts around a child in another process:
|
|
2538
|
+
* the no-capabilities advertisement, timing-bound validation, child
|
|
2539
|
+
* working-directory validation, the never-reject result settlement, and the standard
|
|
2540
|
+
* run-handle publication. Backends compose these with their own wire drivers;
|
|
2541
|
+
* the process machinery itself (spawn, env scrub, managed-range teardown)
|
|
2542
|
+
* belongs to the `dsh-subprocess` seam.
|
|
2543
|
+
*
|
|
2544
|
+
* @module @deepseek-ai/dsh-subagent/out-of-process
|
|
2545
|
+
*/
|
|
2546
|
+
/** Maximum UTF-8 size of {@link SubagentResult.diagnostic}. */
|
|
2547
|
+
const MAX_SUBAGENT_DIAGNOSTIC_BYTES = 4096;
|
|
2548
|
+
const DIAGNOSTIC_TRUNCATION_SUFFIX = "\n[diagnostic truncated]";
|
|
2549
|
+
const utf8Encoder = new TextEncoder();
|
|
2550
|
+
const utf8Decoder = new TextDecoder();
|
|
2551
|
+
/**
|
|
2552
|
+
* Limit provider-authored failure detail without splitting a UTF-8 sequence.
|
|
2553
|
+
* @param diagnostic - safe diagnostic text produced by the provider.
|
|
2554
|
+
* @returns the original text, or a visibly truncated value within the limit.
|
|
2555
|
+
*/
|
|
2556
|
+
function limitSubagentDiagnostic(diagnostic) {
|
|
2557
|
+
const bytes = utf8Encoder.encode(diagnostic);
|
|
2558
|
+
if (bytes.byteLength <= MAX_SUBAGENT_DIAGNOSTIC_BYTES) return diagnostic;
|
|
2559
|
+
let prefixBytes = MAX_SUBAGENT_DIAGNOSTIC_BYTES - utf8Encoder.encode(DIAGNOSTIC_TRUNCATION_SUFFIX).byteLength;
|
|
2560
|
+
while ((bytes[prefixBytes] & 192) === 128) prefixBytes -= 1;
|
|
2561
|
+
return utf8Decoder.decode(bytes.subarray(0, prefixBytes)) + DIAGNOSTIC_TRUNCATION_SUFFIX;
|
|
2562
|
+
}
|
|
2563
|
+
/** Enforce the byte limit on a provider-returned diagnostic. */
|
|
2564
|
+
function normalizeSubagentDiagnostic(result) {
|
|
2565
|
+
return result.diagnostic === void 0 ? result : {
|
|
2566
|
+
...result,
|
|
2567
|
+
diagnostic: limitSubagentDiagnostic(result.diagnostic)
|
|
2568
|
+
};
|
|
2569
|
+
}
|
|
2570
|
+
/**
|
|
2571
|
+
* The capability advertisement of an out-of-process backend: NONE. A child in
|
|
2572
|
+
* another process cannot honor parent-enforced start features
|
|
2573
|
+
* (`agentOptions`/`agentPreset`/`outputSchema`/`maxDepth`), so the service rejects a
|
|
2574
|
+
* request needing any of them before `start` runs — never accepted-then-ignored.
|
|
2575
|
+
*/
|
|
2576
|
+
const NO_START_CAPABILITIES = Object.freeze({
|
|
2577
|
+
agentOptions: false,
|
|
2578
|
+
agentPreset: false,
|
|
2579
|
+
outputSchema: false,
|
|
2580
|
+
depthLimit: false
|
|
2581
|
+
});
|
|
2582
|
+
/**
|
|
2583
|
+
* Assert a configured timing bound is a positive finite number (it bounds a
|
|
2584
|
+
* teardown or shutdown wait; zero, negative, or NaN would skip or wedge it).
|
|
2585
|
+
* @param prefix - the consuming plugin's diagnostic prefix (e.g. `subagent-acp`).
|
|
2586
|
+
* @param name - the config field name, for the diagnostic.
|
|
2587
|
+
* @param value - the configured value.
|
|
2588
|
+
*/
|
|
2589
|
+
function assertPositiveFinite(prefix, name, value) {
|
|
2590
|
+
if (!Number.isFinite(value) || value <= 0) throw new Error(`${prefix}: ${name} must be a positive finite number`);
|
|
2591
|
+
}
|
|
2592
|
+
/**
|
|
2593
|
+
* Whether `path` names an existing directory the harness can ENTER. The
|
|
2594
|
+
* search-permission probe matters: `statSync().isDirectory()` is true for a
|
|
2595
|
+
* mode-600 directory, but a subprocess cwd needs `X_OK` or spawn fails EACCES.
|
|
2596
|
+
*/
|
|
2597
|
+
function isEnterableDirectory(path) {
|
|
2598
|
+
try {
|
|
2599
|
+
if (!statSync(path).isDirectory()) return false;
|
|
2600
|
+
accessSync(path, constants.X_OK);
|
|
2601
|
+
return true;
|
|
2602
|
+
} catch {
|
|
2603
|
+
return false;
|
|
2604
|
+
}
|
|
2605
|
+
}
|
|
2606
|
+
/**
|
|
2607
|
+
* Assert `cwd` can actually host the child: absolute (it doubles as the
|
|
2608
|
+
* child's workspace identity, and a relative path would be re-anchored to the
|
|
2609
|
+
* server process's launch directory) and an existing directory (fail here,
|
|
2610
|
+
* before the process boundary, instead of as an ambiguous spawn ENOENT).
|
|
2611
|
+
* @param prefix - the consuming plugin's diagnostic prefix.
|
|
2612
|
+
* @param label - which source supplied the value, for the diagnostic.
|
|
2613
|
+
* @param cwd - the candidate working directory.
|
|
2614
|
+
* @returns `cwd`, validated.
|
|
2615
|
+
*/
|
|
2616
|
+
function assertUsableCwd(prefix, label, cwd) {
|
|
2617
|
+
if (!isAbsolute(cwd)) throw new Error(`${prefix}: ${label} must be an absolute path: ${cwd}`);
|
|
2618
|
+
if (!isEnterableDirectory(cwd)) throw new Error(`${prefix}: ${label} is not an accessible directory: ${cwd}`);
|
|
2619
|
+
return cwd;
|
|
2620
|
+
}
|
|
2621
|
+
/** Normalize an unknown thrown value to an Error (the catch binding is `unknown`). */
|
|
2622
|
+
function toError(value) {
|
|
2623
|
+
/* v8 ignore next */
|
|
2624
|
+
return value instanceof Error ? value : new Error(String(value));
|
|
2625
|
+
}
|
|
2626
|
+
/**
|
|
2627
|
+
* Settle an out-of-process run result under the seam contract: `result` never
|
|
2628
|
+
* rejects after publication. A normally completed or rejected attempt resolves
|
|
2629
|
+
* as `aborted` when cancellation already settled locally; another rejection is
|
|
2630
|
+
* flattened to `stopReason: 'error'` through the contained diagnostic sink.
|
|
2631
|
+
* Provider-returned diagnostics use the same byte limit. The abort listener is
|
|
2632
|
+
* removed on every path.
|
|
2633
|
+
* @param parts - the attempt, output snapshot, cancellation state, sink, and signal wiring.
|
|
2634
|
+
* @returns the terminal result (never a rejection).
|
|
2635
|
+
*/
|
|
2636
|
+
async function settleRunResult(parts) {
|
|
2637
|
+
try {
|
|
2638
|
+
const result = await parts.attempt();
|
|
2639
|
+
return parts.cancelled() ? {
|
|
2640
|
+
output: parts.collectOutput(),
|
|
2641
|
+
stopReason: "aborted"
|
|
2642
|
+
} : normalizeSubagentDiagnostic(result);
|
|
2643
|
+
} catch (error) {
|
|
2644
|
+
if (parts.cancelled()) return {
|
|
2645
|
+
output: parts.collectOutput(),
|
|
2646
|
+
stopReason: "aborted"
|
|
2647
|
+
};
|
|
2648
|
+
try {
|
|
2649
|
+
parts.onError?.(toError(error), "error");
|
|
2650
|
+
} catch {}
|
|
2651
|
+
const collected = parts.collectDiagnostic?.();
|
|
2652
|
+
const diagnostic = collected === void 0 ? void 0 : limitSubagentDiagnostic(collected);
|
|
2653
|
+
return {
|
|
2654
|
+
output: parts.collectOutput(),
|
|
2655
|
+
...diagnostic === void 0 ? {} : { diagnostic },
|
|
2656
|
+
stopReason: "error"
|
|
2657
|
+
};
|
|
2658
|
+
} finally {
|
|
2659
|
+
parts.signal.removeEventListener("abort", parts.onAbort);
|
|
2660
|
+
}
|
|
2661
|
+
}
|
|
2662
|
+
/**
|
|
2663
|
+
* Publish the seam run handle for an out-of-process child. `dispose()` is
|
|
2664
|
+
* idempotent (one memoized teardown): it removes the abort listener, settles
|
|
2665
|
+
* local cancellation — there is no assumption the child cooperates — and then
|
|
2666
|
+
* awaits the backend's teardown to actual exit.
|
|
2667
|
+
* @param parts - the run identity, result, cancellation wiring, and teardown.
|
|
2668
|
+
* @returns the external run handle.
|
|
2669
|
+
*/
|
|
2670
|
+
function subprocessRunHandle(parts) {
|
|
2671
|
+
let disposal;
|
|
2672
|
+
return {
|
|
2673
|
+
id: parts.id,
|
|
2674
|
+
result: parts.result,
|
|
2675
|
+
dispose() {
|
|
2676
|
+
if (disposal !== void 0) return disposal;
|
|
2677
|
+
parts.signal.removeEventListener("abort", parts.onAbort);
|
|
2678
|
+
parts.requestCancel();
|
|
2679
|
+
disposal = parts.teardown();
|
|
2680
|
+
return disposal;
|
|
2681
|
+
}
|
|
2682
|
+
};
|
|
2683
|
+
}
|
|
2684
|
+
//#endregion
|
|
2685
|
+
//#region lib/types/index.js
|
|
2686
|
+
var __runInitializers = function(thisArg, initializers, value) {
|
|
2687
|
+
var useValue = arguments.length > 2;
|
|
2688
|
+
for (var i = 0; i < initializers.length; i++) value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
|
|
2689
|
+
return useValue ? value : void 0;
|
|
2690
|
+
};
|
|
2691
|
+
var __esDecorate = function(ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
|
|
2692
|
+
function accept(f) {
|
|
2693
|
+
if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected");
|
|
2694
|
+
return f;
|
|
2695
|
+
}
|
|
2696
|
+
var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
|
|
2697
|
+
var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
|
|
2698
|
+
var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
|
|
2699
|
+
var _, done = false;
|
|
2700
|
+
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
2701
|
+
var context = {};
|
|
2702
|
+
for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
|
|
2703
|
+
for (var p in contextIn.access) context.access[p] = contextIn.access[p];
|
|
2704
|
+
context.addInitializer = function(f) {
|
|
2705
|
+
if (done) throw new TypeError("Cannot add initializers after decoration has completed");
|
|
2706
|
+
extraInitializers.push(accept(f || null));
|
|
2707
|
+
};
|
|
2708
|
+
var result = (0, decorators[i])(kind === "accessor" ? {
|
|
2709
|
+
get: descriptor.get,
|
|
2710
|
+
set: descriptor.set
|
|
2711
|
+
} : descriptor[key], context);
|
|
2712
|
+
if (kind === "accessor") {
|
|
2713
|
+
if (result === void 0) continue;
|
|
2714
|
+
if (result === null || typeof result !== "object") throw new TypeError("Object expected");
|
|
2715
|
+
if (_ = accept(result.get)) descriptor.get = _;
|
|
2716
|
+
if (_ = accept(result.set)) descriptor.set = _;
|
|
2717
|
+
if (_ = accept(result.init)) initializers.unshift(_);
|
|
2718
|
+
} else if (_ = accept(result)) if (kind === "field") initializers.unshift(_);
|
|
2719
|
+
else descriptor[key] = _;
|
|
2720
|
+
}
|
|
2721
|
+
if (target) Object.defineProperty(target, contextIn.name, descriptor);
|
|
2722
|
+
done = true;
|
|
2723
|
+
};
|
|
2724
|
+
/** Named provider registry with managed activations, durable discovery, and local child messaging. */
|
|
2725
|
+
let SubagentRuntime = (() => {
|
|
2726
|
+
let _classSuper = TypertRemoteService;
|
|
2727
|
+
let _instanceExtraInitializers = [];
|
|
2728
|
+
let _prompt_decorators;
|
|
2729
|
+
let _interruptByUser_decorators;
|
|
2730
|
+
return class SubagentRuntime extends _classSuper {
|
|
2731
|
+
static {
|
|
2732
|
+
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
|
|
2733
|
+
_prompt_decorators = [Remote({
|
|
2734
|
+
name: "prompt",
|
|
2735
|
+
resources: { session: (request) => request.childSessionId }
|
|
2736
|
+
})];
|
|
2737
|
+
_interruptByUser_decorators = [Remote({
|
|
2738
|
+
name: "interruptByUser",
|
|
2739
|
+
resources: { session: (childSessionId) => childSessionId }
|
|
2740
|
+
})];
|
|
2741
|
+
__esDecorate(this, null, _prompt_decorators, {
|
|
2742
|
+
kind: "method",
|
|
2743
|
+
name: "prompt",
|
|
2744
|
+
static: false,
|
|
2745
|
+
private: false,
|
|
2746
|
+
access: {
|
|
2747
|
+
has: (obj) => "prompt" in obj,
|
|
2748
|
+
get: (obj) => obj.prompt
|
|
2749
|
+
},
|
|
2750
|
+
metadata: _metadata
|
|
2751
|
+
}, null, _instanceExtraInitializers);
|
|
2752
|
+
__esDecorate(this, null, _interruptByUser_decorators, {
|
|
2753
|
+
kind: "method",
|
|
2754
|
+
name: "interruptByUser",
|
|
2755
|
+
static: false,
|
|
2756
|
+
private: false,
|
|
2757
|
+
access: {
|
|
2758
|
+
has: (obj) => "interruptByUser" in obj,
|
|
2759
|
+
get: (obj) => obj.interruptByUser
|
|
2760
|
+
},
|
|
2761
|
+
metadata: _metadata
|
|
2762
|
+
}, null, _instanceExtraInitializers);
|
|
2763
|
+
if (_metadata) Object.defineProperty(this, Symbol.metadata, {
|
|
2764
|
+
enumerable: true,
|
|
2765
|
+
configurable: true,
|
|
2766
|
+
writable: true,
|
|
2767
|
+
value: _metadata
|
|
2768
|
+
});
|
|
2769
|
+
}
|
|
2770
|
+
config = __runInitializers(this, _instanceExtraInitializers);
|
|
2771
|
+
static Config = z.object({
|
|
2772
|
+
delegatedApproval: z.union(["never", "inherit"]).default("never").volatile(),
|
|
2773
|
+
maxDepth: z.number().step(1).min(0).max(Number.MAX_SAFE_INTEGER).default(1).volatile(),
|
|
2774
|
+
maxActiveSubagents: z.number().step(1).min(1).max(Number.MAX_SAFE_INTEGER).default(8).volatile()
|
|
2775
|
+
});
|
|
2776
|
+
static inject = ["workingDirectory", "sessionProjections"];
|
|
2777
|
+
providers = /* @__PURE__ */ new Map();
|
|
2778
|
+
manager;
|
|
2779
|
+
/**
|
|
2780
|
+
* The contained lifecycle-edge publisher. Built here because scoped dispatch
|
|
2781
|
+
* keys its carrier by this exact service instance, whose own context filter
|
|
2782
|
+
* composes into the carrier.
|
|
2783
|
+
*/
|
|
2784
|
+
emitLifecycle;
|
|
2785
|
+
constructor(ctx, config) {
|
|
2786
|
+
super(ctx, "subagents");
|
|
2787
|
+
this.config = config;
|
|
2788
|
+
this.emitLifecycle = createLifecycleEmitter(this.ctx, (parent) => scopeTarget(this, parent));
|
|
2789
|
+
}
|
|
2790
|
+
async [Service.init]() {
|
|
2791
|
+
const ctx = this.ctx;
|
|
2792
|
+
await ctx.sessionProjections.register(subagentCatalogProjectionDefinition);
|
|
2793
|
+
await ctx.sessionProjections.register(subagentTimingProjectionDefinition);
|
|
2794
|
+
const identityProjection = await ctx.sessionProjections.register(subagentIdentityProjectionDefinition);
|
|
2795
|
+
if (ctx.fiber.uid === null) return;
|
|
2796
|
+
ctx.inject(["agents"], (childCtx) => {
|
|
2797
|
+
const manager = new SubagentManager(childCtx, {
|
|
2798
|
+
identityOf: (session) => identityProjection.stateOf(session).identity,
|
|
2799
|
+
emitLifecycle: this.emitLifecycle
|
|
2800
|
+
}, () => this.config.maxActiveSubagents.get(), () => this.config.delegatedApproval.get());
|
|
2801
|
+
this.manager = manager;
|
|
2802
|
+
childCtx.effect(() => () => {
|
|
2803
|
+
/* v8 ignore else -- one injected binding owns the slot until its fiber disposes. */
|
|
2804
|
+
if (this.manager === manager) this.manager = void 0;
|
|
2805
|
+
}, "subagents.managerBinding()");
|
|
2806
|
+
});
|
|
2807
|
+
}
|
|
2808
|
+
/**
|
|
2809
|
+
* Resolve a delegation tool's depth policy against the current user setting.
|
|
2810
|
+
* @param configured - Explicit tool limit, or provider-managed for external delegation.
|
|
2811
|
+
* @returns The numeric limit, or undefined when the provider owns depth enforcement.
|
|
2812
|
+
*/
|
|
2813
|
+
resolveMaxDepth(configured) {
|
|
2814
|
+
if (configured === "provider-managed") return void 0;
|
|
2815
|
+
if (configured !== void 0) return configured;
|
|
2816
|
+
const depth = this.config.maxDepth.get();
|
|
2817
|
+
assertSubagentMaxDepth(depth);
|
|
2818
|
+
return depth;
|
|
2819
|
+
}
|
|
2820
|
+
/**
|
|
2821
|
+
* Establish a managed child with backend-specific execution capabilities.
|
|
2822
|
+
* @param spec - task, backend, and result recipient.
|
|
2823
|
+
* @returns identities, execution result, and exact-activation disposal. A reserved
|
|
2824
|
+
* childId requires a local backend and guarantees an accepted messageId.
|
|
2825
|
+
* @throws PROVIDER_CHANGED if the selected provider is removed or replaced before dispatch.
|
|
2826
|
+
*/
|
|
2827
|
+
async startActivation(spec) {
|
|
2828
|
+
const provider = this.expectProvider(spec.provider);
|
|
2829
|
+
if (spec.childId !== void 0 && provider.prepareContinuable === void 0) throw new SubagentError("reserved child ids require a local backend", "UNSUPPORTED_CAPABILITY");
|
|
2830
|
+
assertSubagentMaxDepth(spec.maxDepth);
|
|
2831
|
+
this.assertCapabilities(provider, spec);
|
|
2832
|
+
const manager = this.requireManager();
|
|
2833
|
+
if (this.ctx.get("agents")?.get(spec.parent.id) !== spec.parent) throw new SubagentError("subagent creation requires the exact live parent agent", "UNAUTHORIZED");
|
|
2834
|
+
if (spec.outputSchema !== void 0) assertObjectJsonSchema(spec.outputSchema);
|
|
2835
|
+
const prepare = provider.prepareContinuable?.bind(provider);
|
|
2836
|
+
if (prepare !== void 0) return manager.startLocal(spec, (request) => {
|
|
2837
|
+
this.assertProviderCurrent(provider);
|
|
2838
|
+
return prepare(request);
|
|
2839
|
+
});
|
|
2840
|
+
const start = provider.start.bind(provider);
|
|
2841
|
+
return manager.startExternal(spec, (request) => {
|
|
2842
|
+
this.assertProviderCurrent(provider);
|
|
2843
|
+
return start(request);
|
|
2844
|
+
});
|
|
2845
|
+
}
|
|
2846
|
+
/**
|
|
2847
|
+
* Resolve the root shared by an Agent and its other agents in its subagent tree.
|
|
2848
|
+
* @param caller - exact live Agent whose subagent ancestry selects the tree.
|
|
2849
|
+
* @returns the live root; ordinary Session forks lead independent trees.
|
|
2850
|
+
* @throws when the caller or its runtime owner is unavailable.
|
|
2851
|
+
*/
|
|
2852
|
+
rootOf(caller) {
|
|
2853
|
+
return this.requireManager().rootOf(caller);
|
|
2854
|
+
}
|
|
2855
|
+
/**
|
|
2856
|
+
* Join progressing subagent tree work for its root. Idle members with parked input
|
|
2857
|
+
* stay resident without delaying host completion. Ordinary members return false.
|
|
2858
|
+
* @param root - exact live subagent tree root whose member work is awaited.
|
|
2859
|
+
* @returns whether work was joined; hosts recheck root idle after true.
|
|
2860
|
+
*/
|
|
2861
|
+
async waitForTreeWork(root) {
|
|
2862
|
+
return this.manager?.waitForTreeWork(root) ?? false;
|
|
2863
|
+
}
|
|
2864
|
+
/**
|
|
2865
|
+
* Steer one model-authored message to another member of the same subagent tree.
|
|
2866
|
+
* A running target receives it at its next step; an idle target starts a turn.
|
|
2867
|
+
* A cold target resumes under its runtime subagent tree root without restoring
|
|
2868
|
+
* its creation ancestors. Sender identity is rechecked before inbox acceptance; cancellation
|
|
2869
|
+
* stops only pre-acceptance work and never retracts an accepted message.
|
|
2870
|
+
* @param sender - exact live root or resident continuable Agent originating the message.
|
|
2871
|
+
* @param targetId - another root or continuable member's durable Session id.
|
|
2872
|
+
* @param content - model-authored content to deliver.
|
|
2873
|
+
* @param options - caller cancellation before inbox acceptance.
|
|
2874
|
+
* @returns the accepted message's inbox id.
|
|
2875
|
+
* @throws when the target is outside the tree, cannot resume, or rejects input.
|
|
2876
|
+
*/
|
|
2877
|
+
async sendMessage(sender, targetId, content, options) {
|
|
2878
|
+
return this.requireManager().sendMessage(sender, targetId, content, options);
|
|
2879
|
+
}
|
|
2880
|
+
/**
|
|
2881
|
+
* Interrupt one live child's current execution under a trusted user
|
|
2882
|
+
* operation or an exact live ancestor Agent. Fire-and-return: the cancel
|
|
2883
|
+
* signal is issued before this returns, but the target may keep running
|
|
2884
|
+
* until it observes the signal. Unclaimed pending inbox work, the Activation,
|
|
2885
|
+
* and published descendants are preserved; claimed work is not requeued.
|
|
2886
|
+
* Once the interrupted Agent is idle, a waking send resumes the parked FIFO
|
|
2887
|
+
* queue. External backends stop their single execution. An absent target
|
|
2888
|
+
* is an accepted no-op, as is a manager-less composition, which cannot own a
|
|
2889
|
+
* live Activation.
|
|
2890
|
+
* @param targetSessionId - the durable child session id to interrupt.
|
|
2891
|
+
* @param authority - a trusted user operation or exact live ancestor Agent.
|
|
2892
|
+
* @throws {SubagentError} `UNAUTHORIZED` when the authority does not own the
|
|
2893
|
+
* live target.
|
|
2894
|
+
*/
|
|
2895
|
+
interrupt(targetSessionId, authority) {
|
|
2896
|
+
this.manager?.interrupt(targetSessionId, authority);
|
|
2897
|
+
}
|
|
2898
|
+
/**
|
|
2899
|
+
* Close admission and stop all instances in the selected subagent trees, then await
|
|
2900
|
+
* pending startup and cleanup. Ordinary members are ignored. The cutoff lasts
|
|
2901
|
+
* until each exact root leaves the registry; unrelated subagent trees remain live.
|
|
2902
|
+
* @param parents - exact live subagent tree roots entering teardown.
|
|
2903
|
+
* @returns once all admitted startup and member cleanup have settled.
|
|
2904
|
+
* @throws an aggregate error after all branches settle when any failed.
|
|
2905
|
+
*/
|
|
2906
|
+
async drainDescendants(parents) {
|
|
2907
|
+
const manager = this.manager;
|
|
2908
|
+
if (manager === void 0) return;
|
|
2909
|
+
await manager.drainDescendants(parents);
|
|
2910
|
+
}
|
|
2911
|
+
/**
|
|
2912
|
+
* Recursively list reachable parent catalogs in stable pre-order, preserving
|
|
2913
|
+
* each catalog's event order. Each row carries its catalog parent and depth;
|
|
2914
|
+
* external children are leaves; one-shot and unknown-mode children remain
|
|
2915
|
+
* traversal nodes. Unknown modes
|
|
2916
|
+
* produce unsupported diagnostics. Unreadable child catalogs produce corrupt
|
|
2917
|
+
* or unavailable diagnostics and stop only that branch. Root read failures,
|
|
2918
|
+
* missing services or projections, and cancellation reject the whole listing.
|
|
2919
|
+
* Each catalog is observed once and released before the next read. No Agent
|
|
2920
|
+
* is loaded or resumed; Sessions absent from reachable catalogs are omitted.
|
|
2921
|
+
* @param rootSessionId - session whose catalog starts descendant discovery.
|
|
2922
|
+
* @param signal - cancellation forwarded to and checked around each catalog read.
|
|
2923
|
+
* @returns children and branch diagnostics in parent-catalog pre-order.
|
|
2924
|
+
* @throws {@link SubagentError} when listing dependencies are unavailable or the caller cancels.
|
|
2925
|
+
* @throws SessionQueryError when the root catalog cannot be read.
|
|
2926
|
+
*/
|
|
2927
|
+
listDescendants(rootSessionId, signal) {
|
|
2928
|
+
return listDescendants(this.ctx, rootSessionId, signal);
|
|
2929
|
+
}
|
|
2930
|
+
/**
|
|
2931
|
+
* Deliver one browser-authored message to a continuable child through its
|
|
2932
|
+
* live subagent tree root, retaining the caller-minted request identity and
|
|
2933
|
+
* validated browser zone on the accepted message. Success identifies the
|
|
2934
|
+
* message the child's inbox accepted; later execution is independent of this
|
|
2935
|
+
* call. Queue delivery targets a later turn; steer delivery targets the
|
|
2936
|
+
* nearest step and retains the Agent loop's best-effort fallback semantics.
|
|
2937
|
+
* Image parts are admitted and persisted through the attachment store
|
|
2938
|
+
* before delivery, and the child's model must accept image input.
|
|
2939
|
+
* Cold resume at capacity rejects with `subagent/delivery-unavailable`.
|
|
2940
|
+
* Access is checked on the target Session before attachment admission or restoration.
|
|
2941
|
+
* @param request - target id, delivery, minted identity, content, and optional browser zone.
|
|
2942
|
+
* @param signal - carrier cancellation, owning the call until inbox acceptance.
|
|
2943
|
+
* @returns the accepted message's inbox identity.
|
|
2944
|
+
* @throws {RemoteError} `gateway/bad-request`, `subagent/attachment-invalid`,
|
|
2945
|
+
* `subagent/invalid-time-zone`,
|
|
2946
|
+
* `subagent/not-resumable`, `subagent/unauthorized`,
|
|
2947
|
+
* `subagent/delivery-unavailable`, `gateway/cancelled`, or `gateway/internal`.
|
|
2948
|
+
*/
|
|
2949
|
+
async prompt(request, signal) {
|
|
2950
|
+
const { childSessionId, clientTimeZone, delivery } = request;
|
|
2951
|
+
validateControlRequest("subagent.prompt", request);
|
|
2952
|
+
const canonicalTimeZone = clientTimeZone === void 0 ? void 0 : canonicalClientTimeZone(clientTimeZone);
|
|
2953
|
+
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 });
|
|
2954
|
+
const principal = principalOf(this.ctx.invocation);
|
|
2955
|
+
const source = {
|
|
2956
|
+
kind: "user",
|
|
2957
|
+
rpcId: request.requestId,
|
|
2958
|
+
...canonicalTimeZone === void 0 ? {} : { clientTimeZone: canonicalTimeZone },
|
|
2959
|
+
...principal === void 0 ? {} : { principal }
|
|
2960
|
+
};
|
|
2961
|
+
try {
|
|
2962
|
+
let content;
|
|
2963
|
+
if (request.content.every((part) => part.type === "text")) content = request.content.map((part) => ({
|
|
2964
|
+
type: "text",
|
|
2965
|
+
text: part.text
|
|
2966
|
+
}));
|
|
2967
|
+
else {
|
|
2968
|
+
const attachments = this.ctx.get("attachments");
|
|
2969
|
+
if (attachments === void 0) throw new Error("subagent image prompt requires an attachment store");
|
|
2970
|
+
content = await attachments.admitPromptContent(request.content);
|
|
2971
|
+
}
|
|
2972
|
+
return { messageId: await this.requireManager().deliverPrompt(childSessionId, content, {
|
|
2973
|
+
source,
|
|
2974
|
+
signal,
|
|
2975
|
+
delivery
|
|
2976
|
+
}) };
|
|
2977
|
+
} catch (error) {
|
|
2978
|
+
return rejectPrompt(error, childSessionId, signal);
|
|
2979
|
+
}
|
|
2980
|
+
}
|
|
2981
|
+
/**
|
|
2982
|
+
* Remote face of {@link interrupt}, authorized on the target Session by the Gateway.
|
|
2983
|
+
* No catalog, history, persistence, or parent Agent lookup runs.
|
|
2984
|
+
* Absent, idle, and already-completed targets are accepted no-ops there.
|
|
2985
|
+
* @param childSessionId - durable child session id to interrupt.
|
|
2986
|
+
* @returns acknowledgement that the cancel signal was admitted, not that the target is quiescent.
|
|
2987
|
+
* @throws {RemoteError} `gateway/bad-request` for an empty id,
|
|
2988
|
+
* otherwise `gateway/internal`.
|
|
2989
|
+
*/
|
|
2990
|
+
interruptByUser(childSessionId) {
|
|
2991
|
+
validateControlRequest("subagent.interrupt", { childSessionId });
|
|
2992
|
+
try {
|
|
2993
|
+
this.interrupt(childSessionId, { kind: "user" });
|
|
2994
|
+
} catch (error) {
|
|
2995
|
+
throw new RemoteError("gateway/internal", "subagent interrupt failed", {}, { cause: error });
|
|
2996
|
+
}
|
|
2997
|
+
return { accepted: true };
|
|
2998
|
+
}
|
|
2999
|
+
/**
|
|
3000
|
+
* Register a provider under its name. Registration is effect-scoped and HMR
|
|
3001
|
+
* safe; removing a provider blocks new starts but does not revoke runs that
|
|
3002
|
+
* were already returned to their holders. Providers without either execution
|
|
3003
|
+
* method are rejected with UNSUPPORTED_CAPABILITY before registration.
|
|
3004
|
+
* @param provider - the trusted provider implementation.
|
|
3005
|
+
* @returns the exact Cordis effect disposer.
|
|
3006
|
+
*/
|
|
3007
|
+
registerProvider(provider) {
|
|
3008
|
+
const name = provider.name;
|
|
3009
|
+
if (provider.start === void 0 && provider.prepareContinuable === void 0) throw new SubagentError(`subagent provider "${name}" must implement start or prepareContinuable`, "UNSUPPORTED_CAPABILITY");
|
|
3010
|
+
return this.ctx.effect(function* () {
|
|
3011
|
+
if (this.providers.has(name)) throw new SubagentError(`a subagent provider named "${name}" is already registered`, "DUPLICATE_PROVIDER");
|
|
3012
|
+
this.providers.set(name, provider);
|
|
3013
|
+
yield () => {
|
|
3014
|
+
this.providers.delete(name);
|
|
3015
|
+
this.emitLifecycle("subagent/provider-removed", name);
|
|
3016
|
+
};
|
|
3017
|
+
this.ctx.emit("subagent/provider-added", provider);
|
|
3018
|
+
}.bind(this), "subagents.registerProvider()");
|
|
3019
|
+
}
|
|
3020
|
+
/**
|
|
3021
|
+
* Look up a provider by name.
|
|
3022
|
+
* @param name - the provider name.
|
|
3023
|
+
* @returns the provider, or undefined when absent.
|
|
3024
|
+
*/
|
|
3025
|
+
getProvider(name) {
|
|
3026
|
+
return this.providers.get(name);
|
|
3027
|
+
}
|
|
3028
|
+
/** A pending start must use the exact provider whose capabilities were checked. */
|
|
3029
|
+
assertProviderCurrent(provider) {
|
|
3030
|
+
if (this.providers.get(provider.name) !== provider) throw new SubagentError(`subagent provider "${provider.name}" changed during startup; retry the delegation`, "PROVIDER_CHANGED");
|
|
3031
|
+
}
|
|
3032
|
+
/** Look up a provider for dispatch or fail loud. */
|
|
3033
|
+
expectProvider(name) {
|
|
3034
|
+
const provider = this.providers.get(name);
|
|
3035
|
+
if (provider === void 0) throw new SubagentError(`no subagent provider registered for "${name}"`, "NO_PROVIDER");
|
|
3036
|
+
return provider;
|
|
3037
|
+
}
|
|
3038
|
+
/** Resolve the subagent manager or fail loud. */
|
|
3039
|
+
requireManager() {
|
|
3040
|
+
if (this.manager === void 0) throw new SubagentError("continuable subagents require the agents service", "CONTINUATION_UNAVAILABLE");
|
|
3041
|
+
return this.manager;
|
|
3042
|
+
}
|
|
3043
|
+
/** Reject the first requested capability that the provider lacks. */
|
|
3044
|
+
assertCapabilities(provider, request) {
|
|
3045
|
+
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");
|
|
3046
|
+
const needs = [
|
|
3047
|
+
{
|
|
3048
|
+
when: request.agentOptions !== void 0,
|
|
3049
|
+
cap: "agentOptions"
|
|
3050
|
+
},
|
|
3051
|
+
{
|
|
3052
|
+
when: request.outputSchema !== void 0,
|
|
3053
|
+
cap: "outputSchema"
|
|
3054
|
+
},
|
|
3055
|
+
{
|
|
3056
|
+
when: request.maxDepth !== void 0,
|
|
3057
|
+
cap: "depthLimit"
|
|
3058
|
+
}
|
|
3059
|
+
];
|
|
3060
|
+
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");
|
|
3061
|
+
}
|
|
3062
|
+
};
|
|
3063
|
+
})();
|
|
3064
|
+
//#endregion
|
|
3065
|
+
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 };
|