@toddzheng024/dscode-bundle 0.4.0 → 0.5.0

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