@deepseek-ai/dsh-subagent 0.2.1-alpha.1 → 0.2.1-alpha.2

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 (41) hide show
  1. package/README.i18n.yaml +34 -34
  2. package/README.md +32 -26
  3. package/README.zh.md +32 -26
  4. package/lib/index.js +1436 -1304
  5. package/lib/typert.host.js +59 -48
  6. package/lib/typert.remote-client.js +2 -2
  7. package/lib/types/activation.d.ts +114 -0
  8. package/lib/types/activation.js +112 -0
  9. package/lib/types/catalog.d.ts +14 -6
  10. package/lib/types/catalog.js +34 -30
  11. package/lib/types/child-agent.d.ts +1 -3
  12. package/lib/types/child-agent.js +1 -3
  13. package/lib/types/continuation-messages.d.ts +4 -3
  14. package/lib/types/continuation-messages.js +13 -5
  15. package/lib/types/control-types.d.ts +7 -0
  16. package/lib/types/descriptor.d.ts +7 -46
  17. package/lib/types/descriptor.js +20 -21
  18. package/lib/types/index.d.ts +52 -51
  19. package/lib/types/index.js +75 -95
  20. package/lib/types/internal.d.ts +1 -1
  21. package/lib/types/lifecycle.d.ts +28 -58
  22. package/lib/types/lifecycle.js +9 -74
  23. package/lib/types/list-children.js +1 -1
  24. package/lib/types/manager.d.ts +247 -0
  25. package/lib/types/manager.js +1294 -0
  26. package/lib/types/out-of-process.d.ts +2 -28
  27. package/lib/types/out-of-process.js +3 -44
  28. package/lib/types/projection-types.d.ts +3 -0
  29. package/lib/types/structured.d.ts +49 -0
  30. package/lib/types/structured.js +130 -0
  31. package/lib/types/types.d.ts +57 -81
  32. package/lib/types/types.js +1 -1
  33. package/package.json +50 -49
  34. package/lib/types/continuation-activation.d.ts +0 -267
  35. package/lib/types/continuation-activation.js +0 -700
  36. package/lib/types/continuation.d.ts +0 -124
  37. package/lib/types/continuation.js +0 -455
  38. package/lib/types/inbox.d.ts +0 -43
  39. package/lib/types/inbox.js +0 -61
  40. package/lib/types/run-settlement.d.ts +0 -17
  41. package/lib/types/run-settlement.js +0 -70
@@ -40,23 +40,22 @@ import { Remote, RemoteError, TypertRemoteService } from '@deepseek-ai/dsh-typer
40
40
  import { rejectPrompt, validateControlRequest, } from "./control.js";
41
41
  import { SubagentError } from "./error.js";
42
42
  import { assertSubagentMaxDepth } from "./depth.js";
43
- import { createActivationObserver, createLifecycleEmitter, observeRun } from "./lifecycle.js";
44
- import SubagentContinuationManager from "./continuation.js";
43
+ import { createActivationObserver, createLifecycleEmitter } from "./lifecycle.js";
44
+ import SubagentManager from "./manager.js";
45
45
  import { listChildren as listSubagentChildren, listDescendants as listSubagentDescendants } from "./list-children.js";
46
46
  import { installSubagentArchiveAdmission } from "./archive-admission.js";
47
- import { snapshotSubagentDescriptor } from "./descriptor.js";
48
47
  import { subagentIdentityProjectionDefinition, subagentTimingProjectionDefinition } from "./projection.js";
49
- import { establishCatalogChild, subagentCatalogProjectionDefinition } from "./catalog.js";
48
+ import { subagentCatalogProjectionDefinition } from "./catalog.js";
50
49
  import { deliverSubagentPrompt } from "./internal.js";
51
50
  export * from "./out-of-process.js";
52
51
  export { AssistantOutputFold, finalAssistantOutput } from "./assistant-output.js";
53
52
  export { SubagentRunId } from "./types.js";
54
53
  export { foldSubagentDescriptor, snapshotSubagentDescriptor, SUBAGENT_DESCRIPTOR_VERSION, } from "./descriptor.js";
54
+ export { STRUCTURED_OUTPUT_TOOL } from "./structured.js";
55
55
  export { SubagentError } from "./error.js";
56
- export { settleRun } from "./run-settlement.js";
57
56
  export { assertSubagentMaxDepth, delegationDepthOf } from "./depth.js";
58
57
  export { appendDelegatedPolicyOverrides, applyChildComposition, captureDelegatedPolicyOverrides, childSessionMeta, parentAgentOptionsForDelegation, resolveChildAgentOptions, resolveChildDepth, SubagentDepthError, } from "./child-agent.js";
59
- /** Named provider registry with one-shot runs, durable discovery, and continuable-child operations. */
58
+ /** Named provider registry with managed activations, durable discovery, and local child messaging. */
60
59
  let SubagentRuntime = (() => {
61
60
  let _classSuper = TypertRemoteService;
62
61
  let _instanceExtraInitializers = [];
@@ -76,8 +75,9 @@ let SubagentRuntime = (() => {
76
75
  maxDepth: z.number().step(1).min(0).max(Number.MAX_SAFE_INTEGER).default(1).volatile(),
77
76
  maxActiveSubagents: z.number().step(1).min(1).max(Number.MAX_SAFE_INTEGER).default(8).volatile(),
78
77
  });
78
+ static inject = ['workingDirectory'];
79
79
  providers = new Map();
80
- continuations;
80
+ manager;
81
81
  /**
82
82
  * The contained lifecycle-edge publisher. Built here because scoped dispatch
83
83
  * keys its carrier by this exact service instance, whose own context filter
@@ -89,22 +89,25 @@ let SubagentRuntime = (() => {
89
89
  this.config = config;
90
90
  this.emitLifecycle = createLifecycleEmitter(this.ctx, parent => scopeTarget(this, parent));
91
91
  ctx.inject(['agents'], (childCtx) => {
92
- const manager = new SubagentContinuationManager(childCtx, {
92
+ const manager = new SubagentManager(childCtx, {
93
+ startExternal: (name, request) => {
94
+ const provider = this.expectProvider(name);
95
+ return provider.start(request);
96
+ },
93
97
  prepareContinuable: (name, request) => this.prepareContinuable(name, request),
94
98
  observeActivation: (provider, childId, parent) => this.observeActivation(provider, childId, parent),
95
99
  }, () => this.config.maxActiveSubagents.get());
96
- this.continuations = manager;
100
+ this.manager = manager;
97
101
  childCtx.effect(() => () => {
98
102
  /* v8 ignore else -- one injected binding owns the slot until its fiber disposes. */
99
- if (this.continuations === manager)
100
- this.continuations = undefined;
101
- }, 'subagents.continuationBinding()');
103
+ if (this.manager === manager)
104
+ this.manager = undefined;
105
+ }, 'subagents.managerBinding()');
102
106
  });
103
107
  ctx.inject(['sessionProjections'], (projectionCtx) => {
104
- const projections = projectionCtx.sessionProjections;
105
- projections.register(subagentCatalogProjectionDefinition);
106
- projections.register(subagentTimingProjectionDefinition);
107
- projections.register(subagentIdentityProjectionDefinition);
108
+ projectionCtx.sessionProjections.register(subagentCatalogProjectionDefinition);
109
+ projectionCtx.sessionProjections.register(subagentTimingProjectionDefinition);
110
+ projectionCtx.sessionProjections.register(subagentIdentityProjectionDefinition);
108
111
  });
109
112
  // Archive admission: this runtime is the owner that knows which live
110
113
  // children descend from a Session and how a parent stops them.
@@ -125,16 +128,36 @@ let SubagentRuntime = (() => {
125
128
  return depth;
126
129
  }
127
130
  /**
128
- * Establish one durable continuable child and deliver its initial prompt.
129
- * Resolves when the child's inbox accepts that prompt, without waiting for the
130
- * turn to start or for the message to reach the Session log; any earlier
131
- * failure rejects with no ids and rolls back the child entirely.
132
- * @param spec - provider, delegation request, and caller cancellation.
133
- * @returns the durable child id and the accepted prompt's message id.
134
- * @throws when continuation services are unavailable or materialization fails.
131
+ * Establish a managed child with backend-specific execution capabilities.
132
+ * @param spec - task, backend, and result recipient.
133
+ * @returns identities, execution result, and exact-activation disposal. A reserved
134
+ * childId requires a local backend and guarantees an accepted messageId.
135
135
  */
136
- async startContinuable(spec) {
137
- return this.requireContinuations().startContinuable(spec);
136
+ async startActivation(spec) {
137
+ const provider = this.expectProvider(spec.provider);
138
+ if (spec.childId !== undefined && provider.prepareContinuable === undefined) {
139
+ throw new SubagentError('reserved child ids require a local backend', 'UNSUPPORTED_CAPABILITY');
140
+ }
141
+ assertSubagentMaxDepth(spec.request.maxDepth);
142
+ this.assertCapabilities(provider, { ...spec.request, signal: spec.signal });
143
+ const manager = this.requireManager();
144
+ if (this.ctx.get('agents')?.get(spec.request.parent.id) !== spec.request.parent) {
145
+ throw new SubagentError('subagent creation requires the exact live parent agent', 'UNAUTHORIZED');
146
+ }
147
+ if (spec.request.outputSchema !== undefined)
148
+ assertObjectJsonSchema(spec.request.outputSchema);
149
+ return provider.prepareContinuable === undefined
150
+ ? manager.startExternal(spec)
151
+ : manager.startLocal(spec);
152
+ }
153
+ /**
154
+ * Join progressing descendants without cancelling them. Idle descendants whose
155
+ * inboxes require a later wake stay resident and do not delay host completion.
156
+ * @param parent - the exact parent whose descendant work is observed.
157
+ * @returns whether work was joined; hosts recheck parent idle after true.
158
+ */
159
+ async waitForChildren(parent) {
160
+ return this.manager?.waitForChildren(parent) ?? false;
138
161
  }
139
162
  /**
140
163
  * Steer one model-authored message to the sender's direct parent or direct
@@ -151,7 +174,7 @@ let SubagentRuntime = (() => {
151
174
  * or the message was not admitted.
152
175
  */
153
176
  async sendMessage(sender, targetId, content, options) {
154
- return this.requireContinuations().sendMessage(sender, targetId, content, options);
177
+ return this.requireManager().sendMessage(sender, targetId, content, options);
155
178
  }
156
179
  /**
157
180
  * Deliver one host-protocol message to a direct continuable child.
@@ -167,17 +190,17 @@ let SubagentRuntime = (() => {
167
190
  */
168
191
  [deliverSubagentPrompt](parent, childId, content, source, signal, delivery) {
169
192
  return delivery === 'steer'
170
- ? this.requireContinuations().steerPrompt(parent, childId, content, source, signal)
171
- : this.requireContinuations().queuePrompt(parent, childId, content, source, signal);
193
+ ? this.requireManager().steerPrompt(parent, childId, content, source, signal)
194
+ : this.requireManager().queuePrompt(parent, childId, content, source, signal);
172
195
  }
173
196
  /**
174
- * Interrupt one live continuable child's current turn under a human parent
197
+ * Interrupt one live child's current execution under a human parent
175
198
  * address or an exact live ancestor Agent. Fire-and-return: the cancel
176
199
  * signal is issued before this returns, but the target may keep running
177
200
  * until it observes the signal. Unclaimed pending inbox work, the Activation,
178
201
  * and published descendants are preserved; claimed work is not requeued.
179
- * Once the interrupted driver is idle, a waking send resumes the parked FIFO
180
- * queue. An absent target — including a one-shot or unknown id —
202
+ * Once the interrupted Agent is idle, a waking send resumes the parked FIFO
203
+ * queue. External backends stop their single execution. An absent target
181
204
  * is an accepted no-op, as is a manager-less composition, which cannot own a
182
205
  * live Activation.
183
206
  * @param targetSessionId - the durable child session id to interrupt.
@@ -186,37 +209,37 @@ let SubagentRuntime = (() => {
186
209
  * live target.
187
210
  */
188
211
  interrupt(targetSessionId, authority) {
189
- this.continuations?.interrupt(targetSessionId, authority);
212
+ this.manager?.interrupt(targetSessionId, authority);
190
213
  }
191
214
  /**
192
- * Close continuable admission below exact live parent Agents, stop only their
215
+ * Close subagent admission below exact live parent Agents, stop only their
193
216
  * visible descendant Activations synchronously, then await admitted scoped
194
217
  * materializations and release those forests child-first. The scoped cutoff
195
218
  * lasts until each exact parent leaves the registry; unrelated parent trees
196
219
  * remain live.
197
220
  * @param parents - exact host-owned parent Agents entering teardown.
198
- * @returns once every retained descendant Activation released its `AgentHandle`.
221
+ * @returns once every retained descendant activation released its execution handle.
199
222
  * @throws an aggregate error after all branches settle when any failed.
200
223
  */
201
- async drainContinuableDescendants(parents) {
202
- const manager = this.continuations;
203
- // Absent continuation services means nothing was ever materialized.
224
+ async drainDescendants(parents) {
225
+ const manager = this.manager;
226
+ // An absent activation manager cannot own materialized children.
204
227
  if (manager === undefined)
205
228
  return;
206
229
  await manager.drainDescendants(parents);
207
230
  }
208
231
  /**
209
- * Release selected resident continuable direct children of one exact live
232
+ * Release selected resident direct children of one exact live
210
233
  * parent. Other children of the same parent remain admitted and resident.
211
234
  * Absent targets and a manager-less composition are accepted no-ops.
212
235
  * @param parent - exact live direct parent authorizing the selected release.
213
236
  * @param childIds - durable direct-child ids to release when resident.
214
- * @returns once every selected Activation released its `AgentHandle`.
237
+ * @returns once every selected activation released its execution handle.
215
238
  * @throws {SubagentError} `UNAUTHORIZED` when a resident target belongs to a
216
239
  * different parent or the supplied parent identity is stale.
217
240
  */
218
- async drainContinuableChildren(parent, childIds) {
219
- const manager = this.continuations;
241
+ async drainChildren(parent, childIds) {
242
+ const manager = this.manager;
220
243
  if (manager === undefined)
221
244
  return;
222
245
  await manager.drainChildren(parent, childIds);
@@ -236,7 +259,8 @@ let SubagentRuntime = (() => {
236
259
  /**
237
260
  * Recursively list reachable parent catalogs in stable pre-order, preserving
238
261
  * each catalog's event order. Each row carries its catalog parent and depth;
239
- * one-shot and unknown-mode children remain traversal nodes. Unknown modes
262
+ * external children are leaves; one-shot and unknown-mode children remain
263
+ * traversal nodes. Unknown modes
240
264
  * produce unsupported diagnostics. Unreadable child catalogs produce corrupt
241
265
  * or unavailable diagnostics and stop only that branch. Root read failures,
242
266
  * missing services or projections, and cancellation reject the whole listing.
@@ -338,12 +362,16 @@ let SubagentRuntime = (() => {
338
362
  /**
339
363
  * Register a provider under its name. Registration is effect-scoped and HMR
340
364
  * safe; removing a provider blocks new starts but does not revoke runs that
341
- * were already returned to their holders.
365
+ * were already returned to their holders. Providers without either execution
366
+ * method are rejected with UNSUPPORTED_CAPABILITY before registration.
342
367
  * @param provider - the trusted provider implementation.
343
368
  * @returns the exact Cordis effect disposer.
344
369
  */
345
370
  registerProvider(provider) {
346
371
  const name = provider.name;
372
+ if (provider.start === undefined && provider.prepareContinuable === undefined) {
373
+ throw new SubagentError(`subagent provider "${name}" must implement start or prepareContinuable`, 'UNSUPPORTED_CAPABILITY');
374
+ }
347
375
  // oxlint-disable-next-line typescript/no-misused-promises -- synchronous disposer
348
376
  return this.ctx.effect(function* () {
349
377
  if (this.providers.has(name)) {
@@ -374,50 +402,6 @@ let SubagentRuntime = (() => {
374
402
  list() {
375
403
  return [...this.providers.keys()];
376
404
  }
377
- /**
378
- * Establish a published child on the named provider. Capability and semantic
379
- * checks run before delegation. Provider ownership lasts until its promise
380
- * fulfills; a rejection therefore has no run for the caller to dispose and
381
- * emits no run lifecycle events. Post-publication turn and infrastructure
382
- * failures settle through the returned run.
383
- * A catalog append failure disposes the run and handles its result rejection;
384
- * the caller receives the catalog error even if disposal also fails.
385
- * @param name - the provider to use.
386
- * @param request - child label, prompt, parent, signal, and optional capabilities.
387
- * @returns the published holder-owned run.
388
- */
389
- async start(name, request) {
390
- const provider = this.expectProvider(name);
391
- this.assertCapabilities(provider, request);
392
- assertSubagentMaxDepth(request.maxDepth);
393
- if (request.outputSchema !== undefined)
394
- assertObjectJsonSchema(request.outputSchema);
395
- const descriptor = snapshotSubagentDescriptor({
396
- mode: 'one-shot',
397
- provider: name,
398
- ...request.label !== undefined ? { label: request.label } : {},
399
- });
400
- const resolved = { ...request, descriptor };
401
- const run = await provider.start(resolved);
402
- const child = run.localAgent?.session;
403
- if (child !== undefined) {
404
- try {
405
- establishCatalogChild(request.parent.session, child.header, descriptor);
406
- }
407
- catch (error) {
408
- // No caller receives this run; the catalog error owns the failed start.
409
- void run.result.catch(() => undefined);
410
- try {
411
- await run.dispose();
412
- }
413
- catch (cleanupError) {
414
- this.ctx.logger.warn(`subagent: disposal after catalog append failure also failed: ${String(cleanupError)}`);
415
- }
416
- throw error;
417
- }
418
- }
419
- return observeRun(this.emitLifecycle, name, request.parent, run);
420
- }
421
405
  /**
422
406
  * Resolve one provider's detached continuable-creation contribution. Method
423
407
  * presence on the provider IS the capability, so a provider without it is
@@ -425,10 +409,6 @@ let SubagentRuntime = (() => {
425
409
  */
426
410
  async prepareContinuable(name, request) {
427
411
  const provider = this.expectProvider(name);
428
- if (provider.prepareContinuable === undefined) {
429
- throw new SubagentError(`subagent provider "${provider.name}" does not support continuable children `
430
- + '(no prepareContinuable capability)', 'UNSUPPORTED_CAPABILITY');
431
- }
432
412
  return provider.prepareContinuable(request);
433
413
  }
434
414
  /** Look up a provider for dispatch or fail loud. */
@@ -439,12 +419,12 @@ let SubagentRuntime = (() => {
439
419
  }
440
420
  return provider;
441
421
  }
442
- /** Resolve the optional continuable-subagent manager or fail loud. */
443
- requireContinuations() {
444
- if (this.continuations === undefined) {
422
+ /** Resolve the subagent manager or fail loud. */
423
+ requireManager() {
424
+ if (this.manager === undefined) {
445
425
  throw new SubagentError('continuable subagents require the agents service', 'CONTINUATION_UNAVAILABLE');
446
426
  }
447
- return this.continuations;
427
+ return this.manager;
448
428
  }
449
429
  /**
450
430
  * Build the lifecycle observer for one continuable Activation's residency
@@ -8,7 +8,7 @@ import type { ContentBlock, MessageId, MessageSource } from '@deepseek-ai/dsh-ll
8
8
  import type { SessionId } from '@deepseek-ai/dsh-session';
9
9
  import type { ToolDefinition } from '@deepseek-ai/dsh-tools';
10
10
  import type SubagentRuntime from './index.ts';
11
- import type { SubagentDelivery } from './inbox.ts';
11
+ import type { SubagentDelivery } from './control-types.ts';
12
12
  /** Process-stable identity carried only by the standard adjacent-Agent messaging tool. */
13
13
  export declare const adjacentAgentSendMessageTool: unique symbol;
14
14
  /**
@@ -1,69 +1,34 @@
1
1
  /**
2
- * Lifecycle-edge publication for both subagent shapes: the contained emitter,
3
- * the one-shot run observer, and the continuable Activation observer.
2
+ * Lifecycle-edge publication for managed subagent activations.
4
3
  *
5
- * The public payload contracts ({@link SubagentRunInfo},
6
- * {@link SubagentRunEndInfo}) live in `./types.ts` with the rest of the seam's
7
- * consumer-facing types; this module owns only the implementation and the
8
- * package-private {@link ActivationObserver} the continuation manager consumes.
9
- * Keeping the internal control interface out of the published surface is
10
- * deliberate: the observer's `start`/`capture`/`settle` ordering is a contract
11
- * between this module and one in-package caller, not something a plugin may
12
- * depend on.
4
+ * Public event payloads live in `./types.ts`; the package-private observer
5
+ * retains only the identity shared by each start/end pair.
13
6
  *
14
7
  * @module @deepseek-ai/dsh-subagent/lifecycle
15
8
  */
16
9
  import type { Context } from '@deepseek-ai/cordis';
17
10
  import type { Agent } from '@deepseek-ai/dsh-agent';
18
- import type { ContentBlock } from '@deepseek-ai/dsh-llm';
19
- import type { SessionId } from '@deepseek-ai/dsh-session';
20
- import type { SubagentResult, SubagentRun, SubagentRunEndInfo, SubagentRunInfo } from './types.ts';
11
+ import type { SessionEvent, SessionId } from '@deepseek-ai/dsh-session';
12
+ import type { SubagentResult, SubagentRunEndInfo, SubagentRunInfo } from './types.ts';
21
13
  /**
22
- * How one Activation's residency epoch ended, as both the terminal lifecycle
23
- * edge and the manager's own parent delivery report it.
24
- */
25
- export interface ActivationTerminal {
26
- /** Why this epoch's last ordinary turn ended, or `error` when teardown failed. */
27
- readonly stopReason: SubagentResult['stopReason'];
28
- /** The epoch's final assistant content, absent when it produced none or failed. */
29
- readonly output?: readonly ContentBlock[];
30
- }
31
- /**
32
- * Lifecycle observer for one Activation's residency epoch, so continuable
33
- * children emit the same start/end pair as one-shot runs. Package-private: the
14
+ * Lifecycle observer for one activation's residency epoch. Package-private: the
34
15
  * continuation manager is the only consumer, and its call ordering is an
35
16
  * in-package contract rather than a published extension point.
36
17
  */
37
18
  export interface ActivationObserver {
38
19
  /**
39
20
  * Publish the start edge once the epoch is resident.
40
- * @param child - the resident child agent, whose log suffix bounds this epoch.
41
- */
42
- start(child: Agent): void;
43
- /**
44
- * Snapshot the child-dependent terminal facts while the child is still
45
- * registered, because handle disposal unregisters it and consumers resolve it
46
- * to read the child's own log and scope.
47
- * @param child - the quiescent child agent about to be released.
48
- */
49
- capture(child: Agent): void;
50
- /**
51
- * Resolve the terminal facts {@link settle} will publish, without publishing
52
- * them. The manager's parent delivery must run before the ownership release
53
- * that lets the parent settle, which is earlier than the terminal edge; both
54
- * therefore read one computation instead of restating the failure rule.
55
- * @param failure - the teardown or durability failure, or `undefined` on success.
56
- * @returns this epoch's stop reason and final assistant content.
21
+ * @param child - the resident local Agent, or undefined for an external execution.
57
22
  */
58
- terminal(failure: unknown): ActivationTerminal;
23
+ start(child?: Agent): void;
59
24
  /**
60
25
  * Publish the terminal edge exactly once, pairing this epoch's {@link start},
61
26
  * after the disposal outcome is known. Called only for a resident epoch: a
62
27
  * failure before residency publishes no edge, because inventing one would
63
28
  * report a lifecycle the child never had.
64
- * @param failure - the teardown or durability failure, or `undefined` on success.
29
+ * @param result - the captured result, with teardown failures applied.
65
30
  */
66
- settle(failure: unknown): void;
31
+ settle(result: SubagentResult): void;
67
32
  }
68
33
  /**
69
34
  * Publish one lifecycle edge with per-listener exception containment. Run edges
@@ -90,19 +55,7 @@ export type LifecycleEmitter = {
90
55
  */
91
56
  export declare function createLifecycleEmitter(ctx: Context, carrier: (parent: Agent) => object): LifecycleEmitter;
92
57
  /**
93
- * Emit the start/end lifecycle pair for one accepted one-shot run.
94
- * @param emit - the contained lifecycle emitter.
95
- * @param provider - the provider that established the run.
96
- * @param parent - the delegating parent keying scoped dispatch.
97
- * @param run - the published run whose settlement closes the pair.
98
- * @returns the same run, unchanged.
99
- */
100
- export declare function observeRun(emit: LifecycleEmitter, provider: string, parent: Agent, run: SubagentRun): SubagentRun;
101
- /**
102
- * Build the observer for one continuable Activation's residency epoch. Observers
103
- * see the same vocabulary as a one-shot run, so a child's start and settlement
104
- * remain observable without exposing whether the manager materialized, woke, or
105
- * cold-resumed it. Creation failure before residency emits no lifecycle edge.
58
+ * Build the observer for one local or external activation's residency epoch. Creation failure before residency emits no lifecycle edge.
106
59
  * @param emit - the contained lifecycle emitter.
107
60
  * @param provider - the provider name recorded in the durable descriptor.
108
61
  * @param childId - the durable child session id.
@@ -110,4 +63,21 @@ export declare function observeRun(emit: LifecycleEmitter, provider: string, par
110
63
  * @returns the observer whose edges this epoch publishes.
111
64
  */
112
65
  export declare function createActivationObserver(emit: LifecycleEmitter, provider: string, childId: SessionId, parent: Agent): ActivationObserver;
66
+ /**
67
+ * Why this child's epoch ended, for the terminal lifecycle edge and the
68
+ * manager's own parent delivery. The child's own log is authoritative:
69
+ * teardown succeeding says nothing about whether the model errored, hit its
70
+ * token ceiling, or was cancelled, so deriving the reason from disposal would
71
+ * report failed work as completed.
72
+ *
73
+ * {@link foldConsumedWork} supplies both halves the raw turn sequence cannot:
74
+ * which turn accounts for the work this epoch consumed, and whether accepted
75
+ * work was cancelled after it without any turn opening over it. A recorded
76
+ * failure still wins over a cancellation — stopping a child that had already
77
+ * failed does not turn its failure into a cancellation.
78
+ * @param events - this epoch's own event suffix.
79
+ * @returns its terminal stop reason; `completed` only for an epoch that both
80
+ * closed cleanly and had nothing left to run.
81
+ */
82
+ export declare function epochStopReason(events: readonly SessionEvent[]): SubagentResult['stopReason'];
113
83
  //# sourceMappingURL=lifecycle.d.ts.map
@@ -1,22 +1,13 @@
1
1
  /**
2
- * Lifecycle-edge publication for both subagent shapes: the contained emitter,
3
- * the one-shot run observer, and the continuable Activation observer.
2
+ * Lifecycle-edge publication for managed subagent activations.
4
3
  *
5
- * The public payload contracts ({@link SubagentRunInfo},
6
- * {@link SubagentRunEndInfo}) live in `./types.ts` with the rest of the seam's
7
- * consumer-facing types; this module owns only the implementation and the
8
- * package-private {@link ActivationObserver} the continuation manager consumes.
9
- * Keeping the internal control interface out of the published surface is
10
- * deliberate: the observer's `start`/`capture`/`settle` ordering is a contract
11
- * between this module and one in-package caller, not something a plugin may
12
- * depend on.
4
+ * Public event payloads live in `./types.ts`; the package-private observer
5
+ * retains only the identity shared by each start/end pair.
13
6
  *
14
7
  * @module @deepseek-ai/dsh-subagent/lifecycle
15
8
  */
16
9
  import { randomUUID } from 'node:crypto';
17
10
  import { foldConsumedWork } from '@deepseek-ai/dsh-agent';
18
- import { SessionLogOffset } from '@deepseek-ai/dsh-session';
19
- import { finalAssistantOutput } from "./assistant-output.js";
20
11
  import { SubagentRunId } from "./types.js";
21
12
  /**
22
13
  * Build the contained lifecycle emitter this seam publishes every edge through.
@@ -46,40 +37,7 @@ export function createLifecycleEmitter(ctx, carrier) {
46
37
  };
47
38
  }
48
39
  /**
49
- * Emit the start/end lifecycle pair for one accepted one-shot run.
50
- * @param emit - the contained lifecycle emitter.
51
- * @param provider - the provider that established the run.
52
- * @param parent - the delegating parent keying scoped dispatch.
53
- * @param run - the published run whose settlement closes the pair.
54
- * @returns the same run, unchanged.
55
- */
56
- export function observeRun(emit, provider, parent, run) {
57
- const identity = {
58
- runId: SubagentRunId(randomUUID()),
59
- provider,
60
- id: run.id,
61
- local: run.localAgent !== undefined,
62
- };
63
- // Attach the terminal observer before dispatching start. Promise reactions
64
- // still run after this synchronous start emission, preserving start → end.
65
- void run.result.then((result) => {
66
- emit('subagent/end', {
67
- ...identity,
68
- stopReason: result.stopReason,
69
- // Omit the field when no output exists, matching continuable epochs.
70
- ...result.output.length === 0 ? {} : { lastAssistantMessage: result.output },
71
- }, parent);
72
- }, () => {
73
- emit('subagent/end', { ...identity, stopReason: 'error' }, parent);
74
- });
75
- emit('subagent/start', identity, parent);
76
- return run;
77
- }
78
- /**
79
- * Build the observer for one continuable Activation's residency epoch. Observers
80
- * see the same vocabulary as a one-shot run, so a child's start and settlement
81
- * remain observable without exposing whether the manager materialized, woke, or
82
- * cold-resumed it. Creation failure before residency emits no lifecycle edge.
40
+ * Build the observer for one local or external activation's residency epoch. Creation failure before residency emits no lifecycle edge.
83
41
  * @param emit - the contained lifecycle emitter.
84
42
  * @param provider - the provider name recorded in the durable descriptor.
85
43
  * @param childId - the durable child session id.
@@ -87,40 +45,17 @@ export function observeRun(emit, provider, parent, run) {
87
45
  * @returns the observer whose edges this epoch publishes.
88
46
  */
89
47
  export function createActivationObserver(emit, provider, childId, parent) {
90
- const identity = { runId: SubagentRunId(randomUUID()), provider, id: childId, local: true };
91
- // A cold resume replays earlier turns, so this epoch's telemetry must come
92
- // from the suffix it actually produced — never the whole session, which
93
- // would report a previous epoch's answer when this one opened no turn.
94
- let boundary = SessionLogOffset(0);
95
- // Assigned by `capture()`, which the disposal path always runs before
96
- // `settle()`; a resident epoch therefore always has its facts by then.
97
- let captured = { stopReason: 'completed' };
98
- // Teardown failure overrides the epoch's own outcome and withholds its
99
- // output: an answer this harness could not durably release is not a result.
100
- const terminal = (failure) => failure === undefined
101
- ? captured
102
- : { stopReason: 'error' };
48
+ const identity = { runId: SubagentRunId(randomUUID()), provider, id: childId, local: false };
103
49
  return {
104
50
  start: (child) => {
105
- boundary = child.session.seq;
51
+ identity.local = child !== undefined;
106
52
  emit('subagent/start', identity, parent);
107
53
  },
108
- capture: (child) => {
109
- // oxlint-disable-next-line typescript/no-deprecated -- Existing Session history read; migration deferred.
110
- const own = child.session.snapshotEvents(boundary);
111
- const output = finalAssistantOutput(own);
112
- captured = {
113
- stopReason: epochStopReason(own),
114
- ...output === undefined ? {} : { output },
115
- };
116
- },
117
- terminal,
118
- settle: (failure) => {
119
- const { stopReason, output } = terminal(failure);
54
+ settle: ({ stopReason, output }) => {
120
55
  emit('subagent/end', {
121
56
  ...identity,
122
57
  stopReason,
123
- ...output === undefined ? {} : { lastAssistantMessage: output },
58
+ ...output.length === 0 ? {} : { lastAssistantMessage: output },
124
59
  }, parent);
125
60
  },
126
61
  };
@@ -141,7 +76,7 @@ export function createActivationObserver(emit, provider, childId, parent) {
141
76
  * @returns its terminal stop reason; `completed` only for an epoch that both
142
77
  * closed cleanly and had nothing left to run.
143
78
  */
144
- function epochStopReason(events) {
79
+ export function epochStopReason(events) {
145
80
  const { end, droppedUnrun } = foldConsumedWork(events);
146
81
  switch (end?.data.reason.kind) {
147
82
  case 'max-tokens':
@@ -126,7 +126,7 @@ export async function listDescendants(ctx, rootSessionId, signal) {
126
126
  visited.add(entry.id);
127
127
  let children;
128
128
  try {
129
- children = await readChildren(entry.id);
129
+ children = entry.mode === 'external' ? [] : await readChildren(entry.id);
130
130
  }
131
131
  catch (error) {
132
132
  if (error instanceof SubagentError)