@fishwithcat/dsh-subagent 0.0.0-stage → 0.2.2-alpha.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/README.i18n.yaml +121 -0
  2. package/README.md +258 -2
  3. package/README.zh.md +259 -0
  4. package/lib/index.js +3065 -0
  5. package/lib/typert.host.d.ts +3 -0
  6. package/lib/typert.host.js +1086 -0
  7. package/lib/typert.remote-client.d.ts +29 -0
  8. package/lib/typert.remote-client.js +122 -0
  9. package/lib/types/activation.d.ts +110 -0
  10. package/lib/types/activation.js +112 -0
  11. package/lib/types/archive-admission.d.ts +23 -0
  12. package/lib/types/archive-admission.js +75 -0
  13. package/lib/types/assistant-output.d.ts +48 -0
  14. package/lib/types/assistant-output.js +73 -0
  15. package/lib/types/catalog.d.ts +87 -0
  16. package/lib/types/catalog.js +113 -0
  17. package/lib/types/child-agent.d.ts +145 -0
  18. package/lib/types/child-agent.js +229 -0
  19. package/lib/types/client.d.ts +8 -0
  20. package/lib/types/client.js +7 -0
  21. package/lib/types/continuation-messages.d.ts +66 -0
  22. package/lib/types/continuation-messages.js +116 -0
  23. package/lib/types/control-types.d.ts +121 -0
  24. package/lib/types/control-types.js +8 -0
  25. package/lib/types/control.d.ts +41 -0
  26. package/lib/types/control.js +75 -0
  27. package/lib/types/depth.d.ts +31 -0
  28. package/lib/types/depth.js +39 -0
  29. package/lib/types/descriptor.d.ts +105 -0
  30. package/lib/types/descriptor.js +192 -0
  31. package/lib/types/error.d.ts +11 -0
  32. package/lib/types/error.js +14 -0
  33. package/lib/types/index.d.ts +282 -0
  34. package/lib/types/index.js +398 -0
  35. package/lib/types/lifecycle.d.ts +54 -0
  36. package/lib/types/lifecycle.js +89 -0
  37. package/lib/types/list-children.d.ts +36 -0
  38. package/lib/types/list-children.js +166 -0
  39. package/lib/types/manager.d.ts +215 -0
  40. package/lib/types/manager.js +1171 -0
  41. package/lib/types/out-of-process.d.ts +92 -0
  42. package/lib/types/out-of-process.js +177 -0
  43. package/lib/types/projection-types.d.ts +93 -0
  44. package/lib/types/projection-types.js +7 -0
  45. package/lib/types/projection.d.ts +94 -0
  46. package/lib/types/projection.js +157 -0
  47. package/lib/types/structured.d.ts +49 -0
  48. package/lib/types/structured.js +130 -0
  49. package/lib/types/types.d.ts +331 -0
  50. package/lib/types/types.js +17 -0
  51. package/locale/de.json +6 -0
  52. package/locale/en.json +5 -0
  53. package/locale/es.json +6 -0
  54. package/locale/fr.json +6 -0
  55. package/locale/ja.json +6 -0
  56. package/locale/pt.json +6 -0
  57. package/locale/ru.json +6 -0
  58. package/locale/zh.json +6 -0
  59. package/package.json +131 -4
@@ -0,0 +1,398 @@
1
+ var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
2
+ var useValue = arguments.length > 2;
3
+ for (var i = 0; i < initializers.length; i++) {
4
+ value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
5
+ }
6
+ return useValue ? value : void 0;
7
+ };
8
+ var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
9
+ function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
10
+ var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
11
+ var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
12
+ var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
13
+ var _, done = false;
14
+ for (var i = decorators.length - 1; i >= 0; i--) {
15
+ var context = {};
16
+ for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
17
+ for (var p in contextIn.access) context.access[p] = contextIn.access[p];
18
+ context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
19
+ var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
20
+ if (kind === "accessor") {
21
+ if (result === void 0) continue;
22
+ if (result === null || typeof result !== "object") throw new TypeError("Object expected");
23
+ if (_ = accept(result.get)) descriptor.get = _;
24
+ if (_ = accept(result.set)) descriptor.set = _;
25
+ if (_ = accept(result.init)) initializers.unshift(_);
26
+ }
27
+ else if (_ = accept(result)) {
28
+ if (kind === "field") initializers.unshift(_);
29
+ else descriptor[key] = _;
30
+ }
31
+ }
32
+ if (target) Object.defineProperty(target, contextIn.name, descriptor);
33
+ done = true;
34
+ };
35
+ import { Service } from '@deepseek-ai/cordis';
36
+ import z from '@deepseek-ai/schemastery';
37
+ import { scopeTarget } from '@deepseek-ai/dsh-scope';
38
+ import { assertObjectJsonSchema } from '@deepseek-ai/dsh-tools';
39
+ import { canonicalClientTimeZone } from '@deepseek-ai/dsh-util-time';
40
+ import { Remote, RemoteError, TypertRemoteService, principalOf } from '@deepseek-ai/dsh-typert-protocol';
41
+ import { rejectPrompt, validateControlRequest, } from "./control.js";
42
+ import { SubagentError } from "./error.js";
43
+ import { assertSubagentMaxDepth } from "./depth.js";
44
+ import { createLifecycleEmitter } from "./lifecycle.js";
45
+ import SubagentManager from "./manager.js";
46
+ import { listDescendants as listSubagentDescendants } from "./list-children.js";
47
+ import { subagentIdentityProjectionDefinition, subagentTimingProjectionDefinition } from "./projection.js";
48
+ import { subagentCatalogProjectionDefinition } from "./catalog.js";
49
+ export * from "./out-of-process.js";
50
+ export { AssistantOutputFold, finalAssistantOutput } from "./assistant-output.js";
51
+ export { SubagentRunId } from "./types.js";
52
+ export { foldSubagentDescriptor, snapshotSubagentDescriptor, SUBAGENT_DESCRIPTOR_VERSION, } from "./descriptor.js";
53
+ export { STRUCTURED_OUTPUT_TOOL } from "./structured.js";
54
+ export { SubagentError } from "./error.js";
55
+ export { assertSubagentMaxDepth, delegationDepthOf } from "./depth.js";
56
+ export { appendDelegatedPolicyOverrides, applyChildComposition, captureDelegatedPolicyOverrides, childSessionMeta, parentAgentOptionsForDelegation, resolveChildAgentOptions, resolveChildDepth, SubagentDepthError, } from "./child-agent.js";
57
+ /** Named provider registry with managed activations, durable discovery, and local child messaging. */
58
+ let SubagentRuntime = (() => {
59
+ let _classSuper = TypertRemoteService;
60
+ let _instanceExtraInitializers = [];
61
+ let _prompt_decorators;
62
+ let _interruptByUser_decorators;
63
+ return class SubagentRuntime extends _classSuper {
64
+ static {
65
+ const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
66
+ _prompt_decorators = [Remote({ name: 'prompt', resources: { session: request => request.childSessionId } })];
67
+ _interruptByUser_decorators = [Remote({ name: 'interruptByUser', resources: { session: childSessionId => childSessionId } })];
68
+ __esDecorate(this, null, _prompt_decorators, { kind: "method", name: "prompt", static: false, private: false, access: { has: obj => "prompt" in obj, get: obj => obj.prompt }, metadata: _metadata }, null, _instanceExtraInitializers);
69
+ __esDecorate(this, null, _interruptByUser_decorators, { kind: "method", name: "interruptByUser", static: false, private: false, access: { has: obj => "interruptByUser" in obj, get: obj => obj.interruptByUser }, metadata: _metadata }, null, _instanceExtraInitializers);
70
+ if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
71
+ }
72
+ config = __runInitializers(this, _instanceExtraInitializers);
73
+ static Config = z.object({
74
+ delegatedApproval: z.union(['never', 'inherit']).default('never').volatile(),
75
+ maxDepth: z.number().step(1).min(0).max(Number.MAX_SAFE_INTEGER).default(1).volatile(),
76
+ maxActiveSubagents: z.number().step(1).min(1).max(Number.MAX_SAFE_INTEGER).default(8).volatile(),
77
+ });
78
+ static inject = ['workingDirectory', 'sessionProjections'];
79
+ providers = new Map();
80
+ manager;
81
+ /**
82
+ * The contained lifecycle-edge publisher. Built here because scoped dispatch
83
+ * keys its carrier by this exact service instance, whose own context filter
84
+ * composes into the carrier.
85
+ */
86
+ emitLifecycle;
87
+ constructor(ctx, config) {
88
+ super(ctx, 'subagents');
89
+ this.config = config;
90
+ this.emitLifecycle = createLifecycleEmitter(this.ctx, parent => scopeTarget(this, parent));
91
+ }
92
+ async [Service.init]() {
93
+ const ctx = this.ctx;
94
+ await ctx.sessionProjections.register(subagentCatalogProjectionDefinition);
95
+ await ctx.sessionProjections.register(subagentTimingProjectionDefinition);
96
+ const identityProjection = await ctx.sessionProjections.register(subagentIdentityProjectionDefinition);
97
+ if (ctx.fiber.uid === null)
98
+ return;
99
+ ctx.inject(['agents'], (childCtx) => {
100
+ const manager = new SubagentManager(childCtx, {
101
+ identityOf: session => identityProjection.stateOf(session).identity,
102
+ emitLifecycle: this.emitLifecycle,
103
+ }, () => this.config.maxActiveSubagents.get(), () => this.config.delegatedApproval.get());
104
+ this.manager = manager;
105
+ childCtx.effect(() => () => {
106
+ /* v8 ignore else -- one injected binding owns the slot until its fiber disposes. */
107
+ if (this.manager === manager)
108
+ this.manager = undefined;
109
+ }, 'subagents.managerBinding()');
110
+ });
111
+ }
112
+ /**
113
+ * Resolve a delegation tool's depth policy against the current user setting.
114
+ * @param configured - Explicit tool limit, or provider-managed for external delegation.
115
+ * @returns The numeric limit, or undefined when the provider owns depth enforcement.
116
+ */
117
+ resolveMaxDepth(configured) {
118
+ if (configured === 'provider-managed')
119
+ return undefined;
120
+ if (configured !== undefined)
121
+ return configured;
122
+ const depth = this.config.maxDepth.get();
123
+ assertSubagentMaxDepth(depth);
124
+ return depth;
125
+ }
126
+ /**
127
+ * Establish a managed child with backend-specific execution capabilities.
128
+ * @param spec - task, backend, and result recipient.
129
+ * @returns identities, execution result, and exact-activation disposal. A reserved
130
+ * childId requires a local backend and guarantees an accepted messageId.
131
+ * @throws PROVIDER_CHANGED if the selected provider is removed or replaced before dispatch.
132
+ */
133
+ async startActivation(spec) {
134
+ const provider = this.expectProvider(spec.provider);
135
+ if (spec.childId !== undefined && provider.prepareContinuable === undefined) {
136
+ throw new SubagentError('reserved child ids require a local backend', 'UNSUPPORTED_CAPABILITY');
137
+ }
138
+ assertSubagentMaxDepth(spec.maxDepth);
139
+ this.assertCapabilities(provider, spec);
140
+ const manager = this.requireManager();
141
+ if (this.ctx.get('agents')?.get(spec.parent.id) !== spec.parent) {
142
+ throw new SubagentError('subagent creation requires the exact live parent agent', 'UNAUTHORIZED');
143
+ }
144
+ if (spec.outputSchema !== undefined)
145
+ assertObjectJsonSchema(spec.outputSchema);
146
+ const prepare = provider.prepareContinuable?.bind(provider);
147
+ if (prepare !== undefined) {
148
+ return manager.startLocal(spec, (request) => {
149
+ this.assertProviderCurrent(provider);
150
+ return prepare(request);
151
+ });
152
+ }
153
+ // oxlint-disable-next-line typescript/no-non-null-assertion -- registerProvider requires start when prepareContinuable is absent.
154
+ const start = provider.start.bind(provider);
155
+ return manager.startExternal(spec, (request) => {
156
+ this.assertProviderCurrent(provider);
157
+ return start(request);
158
+ });
159
+ }
160
+ /**
161
+ * Resolve the root shared by an Agent and its other agents in its subagent tree.
162
+ * @param caller - exact live Agent whose subagent ancestry selects the tree.
163
+ * @returns the live root; ordinary Session forks lead independent trees.
164
+ * @throws when the caller or its runtime owner is unavailable.
165
+ */
166
+ rootOf(caller) {
167
+ return this.requireManager().rootOf(caller);
168
+ }
169
+ /**
170
+ * Join progressing subagent tree work for its root. Idle members with parked input
171
+ * stay resident without delaying host completion. Ordinary members return false.
172
+ * @param root - exact live subagent tree root whose member work is awaited.
173
+ * @returns whether work was joined; hosts recheck root idle after true.
174
+ */
175
+ async waitForTreeWork(root) {
176
+ return this.manager?.waitForTreeWork(root) ?? false;
177
+ }
178
+ /**
179
+ * Steer one model-authored message to another member of the same subagent tree.
180
+ * A running target receives it at its next step; an idle target starts a turn.
181
+ * A cold target resumes under its runtime subagent tree root without restoring
182
+ * its creation ancestors. Sender identity is rechecked before inbox acceptance; cancellation
183
+ * stops only pre-acceptance work and never retracts an accepted message.
184
+ * @param sender - exact live root or resident continuable Agent originating the message.
185
+ * @param targetId - another root or continuable member's durable Session id.
186
+ * @param content - model-authored content to deliver.
187
+ * @param options - caller cancellation before inbox acceptance.
188
+ * @returns the accepted message's inbox id.
189
+ * @throws when the target is outside the tree, cannot resume, or rejects input.
190
+ */
191
+ async sendMessage(sender, targetId, content, options) {
192
+ return this.requireManager().sendMessage(sender, targetId, content, options);
193
+ }
194
+ /**
195
+ * Interrupt one live child's current execution under a trusted user
196
+ * operation or an exact live ancestor Agent. Fire-and-return: the cancel
197
+ * signal is issued before this returns, but the target may keep running
198
+ * until it observes the signal. Unclaimed pending inbox work, the Activation,
199
+ * and published descendants are preserved; claimed work is not requeued.
200
+ * Once the interrupted Agent is idle, a waking send resumes the parked FIFO
201
+ * queue. External backends stop their single execution. An absent target
202
+ * is an accepted no-op, as is a manager-less composition, which cannot own a
203
+ * live Activation.
204
+ * @param targetSessionId - the durable child session id to interrupt.
205
+ * @param authority - a trusted user operation or exact live ancestor Agent.
206
+ * @throws {SubagentError} `UNAUTHORIZED` when the authority does not own the
207
+ * live target.
208
+ */
209
+ interrupt(targetSessionId, authority) {
210
+ this.manager?.interrupt(targetSessionId, authority);
211
+ }
212
+ /**
213
+ * Close admission and stop all instances in the selected subagent trees, then await
214
+ * pending startup and cleanup. Ordinary members are ignored. The cutoff lasts
215
+ * until each exact root leaves the registry; unrelated subagent trees remain live.
216
+ * @param parents - exact live subagent tree roots entering teardown.
217
+ * @returns once all admitted startup and member cleanup have settled.
218
+ * @throws an aggregate error after all branches settle when any failed.
219
+ */
220
+ async drainDescendants(parents) {
221
+ const manager = this.manager;
222
+ // An absent activation manager cannot own materialized children.
223
+ if (manager === undefined)
224
+ return;
225
+ await manager.drainDescendants(parents);
226
+ }
227
+ /**
228
+ * Recursively list reachable parent catalogs in stable pre-order, preserving
229
+ * each catalog's event order. Each row carries its catalog parent and depth;
230
+ * external children are leaves; one-shot and unknown-mode children remain
231
+ * traversal nodes. Unknown modes
232
+ * produce unsupported diagnostics. Unreadable child catalogs produce corrupt
233
+ * or unavailable diagnostics and stop only that branch. Root read failures,
234
+ * missing services or projections, and cancellation reject the whole listing.
235
+ * Each catalog is observed once and released before the next read. No Agent
236
+ * is loaded or resumed; Sessions absent from reachable catalogs are omitted.
237
+ * @param rootSessionId - session whose catalog starts descendant discovery.
238
+ * @param signal - cancellation forwarded to and checked around each catalog read.
239
+ * @returns children and branch diagnostics in parent-catalog pre-order.
240
+ * @throws {@link SubagentError} when listing dependencies are unavailable or the caller cancels.
241
+ * @throws SessionQueryError when the root catalog cannot be read.
242
+ */
243
+ listDescendants(rootSessionId, signal) {
244
+ return listSubagentDescendants(this.ctx, rootSessionId, signal);
245
+ }
246
+ /**
247
+ * Deliver one browser-authored message to a continuable child through its
248
+ * live subagent tree root, retaining the caller-minted request identity and
249
+ * validated browser zone on the accepted message. Success identifies the
250
+ * message the child's inbox accepted; later execution is independent of this
251
+ * call. Queue delivery targets a later turn; steer delivery targets the
252
+ * nearest step and retains the Agent loop's best-effort fallback semantics.
253
+ * Image parts are admitted and persisted through the attachment store
254
+ * before delivery, and the child's model must accept image input.
255
+ * Cold resume at capacity rejects with `subagent/delivery-unavailable`.
256
+ * Access is checked on the target Session before attachment admission or restoration.
257
+ * @param request - target id, delivery, minted identity, content, and optional browser zone.
258
+ * @param signal - carrier cancellation, owning the call until inbox acceptance.
259
+ * @returns the accepted message's inbox identity.
260
+ * @throws {RemoteError} `gateway/bad-request`, `subagent/attachment-invalid`,
261
+ * `subagent/invalid-time-zone`,
262
+ * `subagent/not-resumable`, `subagent/unauthorized`,
263
+ * `subagent/delivery-unavailable`, `gateway/cancelled`, or `gateway/internal`.
264
+ */
265
+ async prompt(request, signal) {
266
+ const { childSessionId, clientTimeZone, delivery } = request;
267
+ validateControlRequest('subagent.prompt', request);
268
+ const canonicalTimeZone = clientTimeZone === undefined
269
+ ? undefined
270
+ : canonicalClientTimeZone(clientTimeZone);
271
+ if (clientTimeZone !== undefined && canonicalTimeZone === undefined) {
272
+ throw new RemoteError('subagent/invalid-time-zone', 'clientTimeZone must be UTC or a valid IANA Area/Location name', { value: clientTimeZone });
273
+ }
274
+ const principal = principalOf(this.ctx.invocation);
275
+ const source = {
276
+ kind: 'user',
277
+ rpcId: request.requestId,
278
+ ...(canonicalTimeZone === undefined ? {} : { clientTimeZone: canonicalTimeZone }),
279
+ ...(principal === undefined ? {} : { principal }),
280
+ };
281
+ try {
282
+ // Admission precedes delivery: image parts become durable references
283
+ // here, so the child inbox only ever accepts Host-persisted attachments.
284
+ let content;
285
+ if (request.content.every((part) => part.type === 'text')) {
286
+ content = request.content.map(part => ({ type: 'text', text: part.text }));
287
+ }
288
+ else {
289
+ const attachments = this.ctx.get('attachments');
290
+ if (attachments === undefined)
291
+ throw new Error('subagent image prompt requires an attachment store');
292
+ content = await attachments.admitPromptContent(request.content);
293
+ }
294
+ return {
295
+ messageId: await this.requireManager().deliverPrompt(childSessionId, content, { source, signal, delivery }),
296
+ };
297
+ }
298
+ catch (error) {
299
+ return rejectPrompt(error, childSessionId, signal);
300
+ }
301
+ }
302
+ /**
303
+ * Remote face of {@link interrupt}, authorized on the target Session by the Gateway.
304
+ * No catalog, history, persistence, or parent Agent lookup runs.
305
+ * Absent, idle, and already-completed targets are accepted no-ops there.
306
+ * @param childSessionId - durable child session id to interrupt.
307
+ * @returns acknowledgement that the cancel signal was admitted, not that the target is quiescent.
308
+ * @throws {RemoteError} `gateway/bad-request` for an empty id,
309
+ * otherwise `gateway/internal`.
310
+ */
311
+ interruptByUser(childSessionId) {
312
+ validateControlRequest('subagent.interrupt', { childSessionId });
313
+ try {
314
+ this.interrupt(childSessionId, { kind: 'user' });
315
+ }
316
+ catch (error) {
317
+ throw new RemoteError('gateway/internal', 'subagent interrupt failed', {}, { cause: error });
318
+ }
319
+ return { accepted: true };
320
+ }
321
+ /**
322
+ * Register a provider under its name. Registration is effect-scoped and HMR
323
+ * safe; removing a provider blocks new starts but does not revoke runs that
324
+ * were already returned to their holders. Providers without either execution
325
+ * method are rejected with UNSUPPORTED_CAPABILITY before registration.
326
+ * @param provider - the trusted provider implementation.
327
+ * @returns the exact Cordis effect disposer.
328
+ */
329
+ registerProvider(provider) {
330
+ const name = provider.name;
331
+ if (provider.start === undefined && provider.prepareContinuable === undefined) {
332
+ throw new SubagentError(`subagent provider "${name}" must implement start or prepareContinuable`, 'UNSUPPORTED_CAPABILITY');
333
+ }
334
+ // oxlint-disable-next-line typescript/no-misused-promises -- synchronous disposer
335
+ return this.ctx.effect(function* () {
336
+ if (this.providers.has(name)) {
337
+ throw new SubagentError(`a subagent provider named "${name}" is already registered`, 'DUPLICATE_PROVIDER');
338
+ }
339
+ this.providers.set(name, provider);
340
+ yield () => {
341
+ this.providers.delete(name);
342
+ this.emitLifecycle('subagent/provider-removed', name);
343
+ };
344
+ // A throwing added-listener unwinds the yielded rollback, matching the
345
+ // repository's fail-loud registration semantics.
346
+ this.ctx.emit('subagent/provider-added', provider);
347
+ }.bind(this), 'subagents.registerProvider()');
348
+ }
349
+ /**
350
+ * Look up a provider by name.
351
+ * @param name - the provider name.
352
+ * @returns the provider, or undefined when absent.
353
+ */
354
+ getProvider(name) {
355
+ return this.providers.get(name);
356
+ }
357
+ /** A pending start must use the exact provider whose capabilities were checked. */
358
+ assertProviderCurrent(provider) {
359
+ if (this.providers.get(provider.name) !== provider) {
360
+ throw new SubagentError(`subagent provider "${provider.name}" changed during startup; retry the delegation`, 'PROVIDER_CHANGED');
361
+ }
362
+ }
363
+ /** Look up a provider for dispatch or fail loud. */
364
+ expectProvider(name) {
365
+ const provider = this.providers.get(name);
366
+ if (provider === undefined) {
367
+ throw new SubagentError(`no subagent provider registered for "${name}"`, 'NO_PROVIDER');
368
+ }
369
+ return provider;
370
+ }
371
+ /** Resolve the subagent manager or fail loud. */
372
+ requireManager() {
373
+ if (this.manager === undefined) {
374
+ throw new SubagentError('continuable subagents require the agents service', 'CONTINUATION_UNAVAILABLE');
375
+ }
376
+ return this.manager;
377
+ }
378
+ /** Reject the first requested capability that the provider lacks. */
379
+ assertCapabilities(provider, request) {
380
+ if (request.agentPreset !== undefined && (!provider.capabilities.agentPreset || provider.inheritsParentContext)) {
381
+ throw new SubagentError(`subagent provider "${provider.name}" does not support independent agent preset selection`, 'UNSUPPORTED_CAPABILITY');
382
+ }
383
+ const needs = [
384
+ { when: request.agentOptions !== undefined, cap: 'agentOptions' },
385
+ { when: request.outputSchema !== undefined, cap: 'outputSchema' },
386
+ { when: request.maxDepth !== undefined, cap: 'depthLimit' },
387
+ ];
388
+ for (const { when, cap } of needs) {
389
+ if (when && !provider.capabilities[cap]) {
390
+ throw new SubagentError(`subagent provider "${provider.name}" does not support the "${cap}" capability`, 'UNSUPPORTED_CAPABILITY');
391
+ }
392
+ }
393
+ }
394
+ };
395
+ })();
396
+ export { SubagentRuntime };
397
+ export default SubagentRuntime;
398
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Contained lifecycle notifications for subagents.
3
+ *
4
+ * The service owns scoped dispatch and listener containment; the manager
5
+ * supplies event payloads from each activation.
6
+ *
7
+ * @module @deepseek-ai/dsh-subagent/lifecycle
8
+ */
9
+ import type { Context } from '@deepseek-ai/cordis';
10
+ import type { Agent } from '@deepseek-ai/dsh-agent';
11
+ import type { SessionEvent } from '@deepseek-ai/dsh-session';
12
+ import type { SubagentResult, SubagentRunEndInfo, SubagentRunInfo } from './types.ts';
13
+ /**
14
+ * Publish one notification with per-listener exception containment. Run edges
15
+ * carry the subagent tree root that keys scoped dispatch. Provider removal
16
+ * reaches all scopes.
17
+ *
18
+ * The service owns this closure because scoped dispatch keys its carrier by the
19
+ * exact service instance, whose own context filter composes into the carrier;
20
+ * a narrowed stand-in would silently change scope filtering.
21
+ */
22
+ export type LifecycleEmitter = {
23
+ (name: 'subagent/start', info: SubagentRunInfo, parent: Agent): void;
24
+ (name: 'subagent/end', info: SubagentRunEndInfo, parent: Agent): void;
25
+ (name: 'subagent/provider-removed', info: string): void;
26
+ };
27
+ /**
28
+ * Build the contained lifecycle emitter this seam publishes every edge through.
29
+ * Every listener is independently contained: a synchronous throw or a rejected
30
+ * returned promise is logged without starving peer listeners, changing the run,
31
+ * or — for provider removal, which fires from a disposer — breaking teardown.
32
+ * @param ctx - the service's own context, owning dispatch and the logger.
33
+ * @param carrier - resolve the scoped dispatch carrier for one subagent tree root.
34
+ * @returns the emitter the manager and provider registry publish through.
35
+ */
36
+ export declare function createLifecycleEmitter(ctx: Context, carrier: (parent: Agent) => object): LifecycleEmitter;
37
+ /**
38
+ * Why this child's epoch ended, for the terminal lifecycle edge and the
39
+ * manager's own parent delivery. The child's own log is authoritative:
40
+ * teardown succeeding says nothing about whether the model errored, hit its
41
+ * token ceiling, or was cancelled, so deriving the reason from disposal would
42
+ * report failed work as completed.
43
+ *
44
+ * {@link foldConsumedWork} supplies both halves the raw turn sequence cannot:
45
+ * which turn accounts for the work this epoch consumed, and whether accepted
46
+ * work was cancelled after it without any turn opening over it. A recorded
47
+ * failure still wins over a cancellation — stopping a child that had already
48
+ * failed does not turn its failure into a cancellation.
49
+ * @param events - this epoch's own event suffix.
50
+ * @returns its terminal stop reason; `completed` only for an epoch that both
51
+ * closed cleanly and had nothing left to run.
52
+ */
53
+ export declare function epochStopReason(events: readonly SessionEvent[]): SubagentResult['stopReason'];
54
+ //# sourceMappingURL=lifecycle.d.ts.map
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Contained lifecycle notifications for subagents.
3
+ *
4
+ * The service owns scoped dispatch and listener containment; the manager
5
+ * supplies event payloads from each activation.
6
+ *
7
+ * @module @deepseek-ai/dsh-subagent/lifecycle
8
+ */
9
+ import { foldConsumedWork } from '@deepseek-ai/dsh-agent';
10
+ /**
11
+ * Build the contained lifecycle emitter this seam publishes every edge through.
12
+ * Every listener is independently contained: a synchronous throw or a rejected
13
+ * returned promise is logged without starving peer listeners, changing the run,
14
+ * or — for provider removal, which fires from a disposer — breaking teardown.
15
+ * @param ctx - the service's own context, owning dispatch and the logger.
16
+ * @param carrier - resolve the scoped dispatch carrier for one subagent tree root.
17
+ * @returns the emitter the manager and provider registry publish through.
18
+ */
19
+ export function createLifecycleEmitter(ctx, carrier) {
20
+ return (name, info, parent) => {
21
+ const dispatchArgs = parent === undefined
22
+ ? [name, info]
23
+ : [carrier(parent), name, info];
24
+ for (const callback of ctx.events.dispatch('emit', dispatchArgs)) {
25
+ try {
26
+ const returned = callback(info);
27
+ void Promise.resolve(returned).catch((error) => {
28
+ ctx.logger.warn(`subagent: ${name} listener rejected: ${renderThrown(error)}`);
29
+ });
30
+ }
31
+ catch (error) {
32
+ ctx.logger.warn(`subagent: ${name} listener threw: ${renderThrown(error)}`);
33
+ }
34
+ }
35
+ };
36
+ }
37
+ /**
38
+ * Why this child's epoch ended, for the terminal lifecycle edge and the
39
+ * manager's own parent delivery. The child's own log is authoritative:
40
+ * teardown succeeding says nothing about whether the model errored, hit its
41
+ * token ceiling, or was cancelled, so deriving the reason from disposal would
42
+ * report failed work as completed.
43
+ *
44
+ * {@link foldConsumedWork} supplies both halves the raw turn sequence cannot:
45
+ * which turn accounts for the work this epoch consumed, and whether accepted
46
+ * work was cancelled after it without any turn opening over it. A recorded
47
+ * failure still wins over a cancellation — stopping a child that had already
48
+ * failed does not turn its failure into a cancellation.
49
+ * @param events - this epoch's own event suffix.
50
+ * @returns its terminal stop reason; `completed` only for an epoch that both
51
+ * closed cleanly and had nothing left to run.
52
+ */
53
+ export function epochStopReason(events) {
54
+ const { end, droppedUnrun } = foldConsumedWork(events);
55
+ switch (end?.data.reason.kind) {
56
+ case 'max-tokens':
57
+ return 'max-tokens';
58
+ case 'aborted':
59
+ case 'interrupted':
60
+ return 'aborted';
61
+ case 'error':
62
+ return 'error';
63
+ // A pre-step rejection — a hook deny, a policy plugin — discarded input
64
+ // this epoch had claimed: the work was declined, not done.
65
+ case 'blocked':
66
+ return 'refusal';
67
+ // A clean ending and no accounting turn at all share one rule: the epoch
68
+ // finished what it was given unless a cancelled queue says otherwise.
69
+ case undefined:
70
+ case 'completed':
71
+ return droppedUnrun ? 'aborted' : 'completed';
72
+ /* v8 ignore next 4 -- `forked` appears only in constructor seed history, while
73
+ * this function reads an epoch-owned suffix. `TurnEndReason` is merge-extensible,
74
+ * so a backend-added variant cannot be listed; treating an unnameable reason as
75
+ * success would report failed work as completed. */
76
+ default:
77
+ return 'error';
78
+ }
79
+ }
80
+ /** Render any listener-thrown value without letting coercion escape containment. */
81
+ function renderThrown(value) {
82
+ try {
83
+ return value instanceof Error ? `${value.name}: ${value.message}` : String(value);
84
+ }
85
+ catch {
86
+ return '<unrenderable thrown value>';
87
+ }
88
+ }
89
+ //# sourceMappingURL=lifecycle.js.map
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Direct-child and recursive descendant discovery from parent-owned catalogs.
3
+ * Each catalog read releases its Session observation before the next branch.
4
+ * @module @deepseek-ai/dsh-subagent
5
+ */
6
+ import type { Context } from '@deepseek-ai/cordis';
7
+ import type { SessionId } from '@deepseek-ai/dsh-session';
8
+ import type { SubagentListEntry } from './control-types.ts';
9
+ import type { SubagentCatalogEntry } from './projection-types.ts';
10
+ export type { SubagentListEntry } from './control-types.ts';
11
+ /** One catalog descendant with its direct parent and edge distance from the requested root. */
12
+ export type SubagentDescendantListEntry = SubagentListEntry & {
13
+ /** Parent whose catalog contains this child. */
14
+ readonly parentId: SessionId;
15
+ /** Edge distance from the requested root; direct children are `1`. */
16
+ readonly depth: number;
17
+ };
18
+ /**
19
+ * Read one parent's durable catalog through a live-preferred Session observation.
20
+ * @param ctx - context carrying the Session query service.
21
+ * @param parentSessionId - parent whose direct children are requested.
22
+ * @param signal - cancellation forwarded to the Session observation.
23
+ * @returns direct-child rows in parent catalog event order.
24
+ * @throws {@link SubagentError} when query or catalog projection is unavailable.
25
+ */
26
+ export declare function listChildren(ctx: Context, parentSessionId: SessionId, signal?: AbortSignal): Promise<SubagentCatalogEntry[]>;
27
+ /**
28
+ * Walk reachable parent catalogs in stable pre-order without loading Agents.
29
+ * @see SubagentRuntime.listDescendants for failure and cancellation semantics.
30
+ * @param ctx - context carrying the Session store and query service.
31
+ * @param rootSessionId - parent whose catalog starts the traversal.
32
+ * @param signal - cancellation checked around each catalog read.
33
+ * @returns children and branch diagnostics with catalog parent and depth.
34
+ */
35
+ export declare function listDescendants(ctx: Context, rootSessionId: SessionId, signal?: AbortSignal): Promise<SubagentDescendantListEntry[]>;
36
+ //# sourceMappingURL=list-children.d.ts.map