@fishwithcat/dsh-subagent 0.2.2-alpha.2 → 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.
@@ -41,13 +41,11 @@ import { Remote, RemoteError, TypertRemoteService, principalOf } from '@deepseek
41
41
  import { rejectPrompt, validateControlRequest, } from "./control.js";
42
42
  import { SubagentError } from "./error.js";
43
43
  import { assertSubagentMaxDepth } from "./depth.js";
44
- import { createActivationObserver, createLifecycleEmitter } from "./lifecycle.js";
44
+ import { createLifecycleEmitter } from "./lifecycle.js";
45
45
  import SubagentManager from "./manager.js";
46
- import { listChildren as listSubagentChildren, listDescendants as listSubagentDescendants } from "./list-children.js";
47
- import { installSubagentArchiveAdmission } from "./archive-admission.js";
46
+ import { listDescendants as listSubagentDescendants } from "./list-children.js";
48
47
  import { subagentIdentityProjectionDefinition, subagentTimingProjectionDefinition } from "./projection.js";
49
48
  import { subagentCatalogProjectionDefinition } from "./catalog.js";
50
- import { deliverSubagentPrompt } from "./internal.js";
51
49
  export * from "./out-of-process.js";
52
50
  export { AssistantOutputFold, finalAssistantOutput } from "./assistant-output.js";
53
51
  export { SubagentRunId } from "./types.js";
@@ -61,14 +59,14 @@ let SubagentRuntime = (() => {
61
59
  let _classSuper = TypertRemoteService;
62
60
  let _instanceExtraInitializers = [];
63
61
  let _prompt_decorators;
64
- let _interruptByParent_decorators;
62
+ let _interruptByUser_decorators;
65
63
  return class SubagentRuntime extends _classSuper {
66
64
  static {
67
65
  const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
68
- _prompt_decorators = [Remote('prompt')];
69
- _interruptByParent_decorators = [Remote('interruptByParent')];
66
+ _prompt_decorators = [Remote({ name: 'prompt', resources: { session: request => request.childSessionId } })];
67
+ _interruptByUser_decorators = [Remote({ name: 'interruptByUser', resources: { session: childSessionId => childSessionId } })];
70
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);
71
- __esDecorate(this, null, _interruptByParent_decorators, { kind: "method", name: "interruptByParent", static: false, private: false, access: { has: obj => "interruptByParent" in obj, get: obj => obj.interruptByParent }, 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);
72
70
  if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
73
71
  }
74
72
  config = __runInitializers(this, _instanceExtraInitializers);
@@ -90,14 +88,18 @@ let SubagentRuntime = (() => {
90
88
  super(ctx, 'subagents');
91
89
  this.config = config;
92
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;
93
99
  ctx.inject(['agents'], (childCtx) => {
94
100
  const manager = new SubagentManager(childCtx, {
95
- startExternal: (name, request) => {
96
- const provider = this.expectProvider(name);
97
- return provider.start(request);
98
- },
99
- prepareContinuable: (name, request) => this.prepareContinuable(name, request),
100
- observeActivation: (provider, childId, parent) => this.observeActivation(provider, childId, parent),
101
+ identityOf: session => identityProjection.stateOf(session).identity,
102
+ emitLifecycle: this.emitLifecycle,
101
103
  }, () => this.config.maxActiveSubagents.get(), () => this.config.delegatedApproval.get());
102
104
  this.manager = manager;
103
105
  childCtx.effect(() => () => {
@@ -106,15 +108,6 @@ let SubagentRuntime = (() => {
106
108
  this.manager = undefined;
107
109
  }, 'subagents.managerBinding()');
108
110
  });
109
- // Archive admission: this runtime is the owner that knows which live
110
- // children descend from a Session and how a parent stops them.
111
- ctx.inject(['agents'], (agentsCtx) => { installSubagentArchiveAdmission(agentsCtx); });
112
- }
113
- async [Service.init]() {
114
- const { sessionProjections } = this.ctx;
115
- await sessionProjections.register(subagentCatalogProjectionDefinition);
116
- await sessionProjections.register(subagentTimingProjectionDefinition);
117
- await sessionProjections.register(subagentIdentityProjectionDefinition);
118
111
  }
119
112
  /**
120
113
  * Resolve a delegation tool's depth policy against the current user setting.
@@ -135,70 +128,72 @@ let SubagentRuntime = (() => {
135
128
  * @param spec - task, backend, and result recipient.
136
129
  * @returns identities, execution result, and exact-activation disposal. A reserved
137
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.
138
132
  */
139
133
  async startActivation(spec) {
140
134
  const provider = this.expectProvider(spec.provider);
141
135
  if (spec.childId !== undefined && provider.prepareContinuable === undefined) {
142
136
  throw new SubagentError('reserved child ids require a local backend', 'UNSUPPORTED_CAPABILITY');
143
137
  }
144
- assertSubagentMaxDepth(spec.request.maxDepth);
145
- this.assertCapabilities(provider, { ...spec.request, signal: spec.signal });
138
+ assertSubagentMaxDepth(spec.maxDepth);
139
+ this.assertCapabilities(provider, spec);
146
140
  const manager = this.requireManager();
147
- if (this.ctx.get('agents')?.get(spec.request.parent.id) !== spec.request.parent) {
141
+ if (this.ctx.get('agents')?.get(spec.parent.id) !== spec.parent) {
148
142
  throw new SubagentError('subagent creation requires the exact live parent agent', 'UNAUTHORIZED');
149
143
  }
150
- if (spec.request.outputSchema !== undefined)
151
- assertObjectJsonSchema(spec.request.outputSchema);
152
- return provider.prepareContinuable === undefined
153
- ? manager.startExternal(spec)
154
- : manager.startLocal(spec);
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
+ });
155
159
  }
156
160
  /**
157
- * Join progressing descendants without cancelling them. Idle descendants whose
158
- * inboxes require a later wake stay resident and do not delay host completion.
159
- * @param parent - the exact parent whose descendant work is observed.
160
- * @returns whether work was joined; hosts recheck parent idle after true.
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.
161
165
  */
162
- async waitForChildren(parent) {
163
- return this.manager?.waitForChildren(parent) ?? false;
166
+ rootOf(caller) {
167
+ return this.requireManager().rootOf(caller);
164
168
  }
165
169
  /**
166
- * Steer one model-authored message to the sender's direct parent or direct
167
- * continuable child. A running target admits it at the nearest step boundary;
168
- * an idle target starts a turn, and an absent direct child cold-resumes from
169
- * persistence. The service derives durable sender attribution from the exact
170
- * live sender. Caller cancellation stops only pre-acceptance work.
171
- * @param sender - exact live Agent authorizing and originating the message.
172
- * @param targetId - durable direct-parent or direct-child session id.
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.
173
186
  * @param content - model-authored content to deliver.
174
187
  * @param options - caller cancellation before inbox acceptance.
175
188
  * @returns the accepted message's inbox id.
176
- * @throws when continuation services are unavailable, adjacency is rejected,
177
- * or the message was not admitted.
189
+ * @throws when the target is outside the tree, cannot resume, or rejects input.
178
190
  */
179
191
  async sendMessage(sender, targetId, content, options) {
180
192
  return this.requireManager().sendMessage(sender, targetId, content, options);
181
193
  }
182
194
  /**
183
- * Deliver one host-protocol message to a direct continuable child.
184
- * Symbol-keyed so host adapters can preserve their own source descriptors without
185
- * widening the public Service Definition or impersonating an Agent sender.
186
- * @param parent - exact live direct parent authorizing delivery.
187
- * @param childId - durable direct-child session id.
188
- * @param content - host-authored content to deliver.
189
- * @param source - durable host-protocol source descriptor.
190
- * @param signal - caller cancellation before inbox acceptance.
191
- * @param delivery - Queue as a distinct turn or Steer at the nearest step.
192
- * @returns the accepted message's inbox id.
193
- */
194
- [deliverSubagentPrompt](parent, childId, content, source, signal, delivery) {
195
- return delivery === 'steer'
196
- ? this.requireManager().steerPrompt(parent, childId, content, source, signal)
197
- : this.requireManager().queuePrompt(parent, childId, content, source, signal);
198
- }
199
- /**
200
- * Interrupt one live child's current execution under a human parent
201
- * address or an exact live ancestor Agent. Fire-and-return: the cancel
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
202
197
  * signal is issued before this returns, but the target may keep running
203
198
  * until it observes the signal. Unclaimed pending inbox work, the Activation,
204
199
  * and published descendants are preserved; claimed work is not requeued.
@@ -207,7 +202,7 @@ let SubagentRuntime = (() => {
207
202
  * is an accepted no-op, as is a manager-less composition, which cannot own a
208
203
  * live Activation.
209
204
  * @param targetSessionId - the durable child session id to interrupt.
210
- * @param authority - the human parent address or exact live ancestor Agent.
205
+ * @param authority - a trusted user operation or exact live ancestor Agent.
211
206
  * @throws {SubagentError} `UNAUTHORIZED` when the authority does not own the
212
207
  * live target.
213
208
  */
@@ -215,13 +210,11 @@ let SubagentRuntime = (() => {
215
210
  this.manager?.interrupt(targetSessionId, authority);
216
211
  }
217
212
  /**
218
- * Close subagent admission below exact live parent Agents, stop only their
219
- * visible descendant Activations synchronously, then await admitted scoped
220
- * materializations and release those forests child-first. The scoped cutoff
221
- * lasts until each exact parent leaves the registry; unrelated parent trees
222
- * remain live.
223
- * @param parents - exact host-owned parent Agents entering teardown.
224
- * @returns once every retained descendant activation released its execution handle.
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.
225
218
  * @throws an aggregate error after all branches settle when any failed.
226
219
  */
227
220
  async drainDescendants(parents) {
@@ -231,34 +224,6 @@ let SubagentRuntime = (() => {
231
224
  return;
232
225
  await manager.drainDescendants(parents);
233
226
  }
234
- /**
235
- * Release selected resident direct children of one exact live
236
- * parent. Other children of the same parent remain admitted and resident.
237
- * Absent targets and a manager-less composition are accepted no-ops.
238
- * @param parent - exact live direct parent authorizing the selected release.
239
- * @param childIds - durable direct-child ids to release when resident.
240
- * @returns once every selected activation released its execution handle.
241
- * @throws {SubagentError} `UNAUTHORIZED` when a resident target belongs to a
242
- * different parent or the supplied parent identity is stale.
243
- */
244
- async drainChildren(parent, childIds) {
245
- const manager = this.manager;
246
- if (manager === undefined)
247
- return;
248
- await manager.drainChildren(parent, childIds);
249
- }
250
- /**
251
- * Read the parent's durable direct-child catalog without loading or resuming an Agent.
252
- * The service owns and releases the live-preferred Session observation.
253
- * @param parentSessionId - parent whose direct children are requested.
254
- * @param signal - cancellation forwarded to the Session query.
255
- * @returns catalog children in parent event order.
256
- * @throws {@link SubagentError} when query or catalog projection is unavailable.
257
- * @throws SessionQueryError when the parent cannot be read or the query is cancelled.
258
- */
259
- listChildren(parentSessionId, signal) {
260
- return listSubagentChildren(this.ctx, parentSessionId, signal);
261
- }
262
227
  /**
263
228
  * Recursively list reachable parent catalogs in stable pre-order, preserving
264
229
  * each catalog's event order. Each row carries its catalog parent and depth;
@@ -279,8 +244,8 @@ let SubagentRuntime = (() => {
279
244
  return listSubagentDescendants(this.ctx, rootSessionId, signal);
280
245
  }
281
246
  /**
282
- * Deliver one browser-authored message to a continuable child through the
283
- * exact live direct parent, retaining the caller-minted request identity and
247
+ * Deliver one browser-authored message to a continuable child through its
248
+ * live subagent tree root, retaining the caller-minted request identity and
284
249
  * validated browser zone on the accepted message. Success identifies the
285
250
  * message the child's inbox accepted; later execution is independent of this
286
251
  * call. Queue delivery targets a later turn; steer delivery targets the
@@ -288,16 +253,17 @@ let SubagentRuntime = (() => {
288
253
  * Image parts are admitted and persisted through the attachment store
289
254
  * before delivery, and the child's model must accept image input.
290
255
  * Cold resume at capacity rejects with `subagent/delivery-unavailable`.
291
- * @param request - durable address, delivery, minted identity, content, and optional browser zone.
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.
292
258
  * @param signal - carrier cancellation, owning the call until inbox acceptance.
293
259
  * @returns the accepted message's inbox identity.
294
260
  * @throws {RemoteError} `gateway/bad-request`, `subagent/attachment-invalid`,
295
- * `subagent/invalid-time-zone`, `subagent/parent-unavailable`,
261
+ * `subagent/invalid-time-zone`,
296
262
  * `subagent/not-resumable`, `subagent/unauthorized`,
297
263
  * `subagent/delivery-unavailable`, `gateway/cancelled`, or `gateway/internal`.
298
264
  */
299
265
  async prompt(request, signal) {
300
- const { parentSessionId, childSessionId, clientTimeZone, delivery } = request;
266
+ const { childSessionId, clientTimeZone, delivery } = request;
301
267
  validateControlRequest('subagent.prompt', request);
302
268
  const canonicalTimeZone = clientTimeZone === undefined
303
269
  ? undefined
@@ -305,10 +271,6 @@ let SubagentRuntime = (() => {
305
271
  if (clientTimeZone !== undefined && canonicalTimeZone === undefined) {
306
272
  throw new RemoteError('subagent/invalid-time-zone', 'clientTimeZone must be UTC or a valid IANA Area/Location name', { value: clientTimeZone });
307
273
  }
308
- const parent = this.ctx.get('agents')?.get(parentSessionId);
309
- if (parent === undefined) {
310
- throw new RemoteError('subagent/parent-unavailable', `parent session "${parentSessionId}" is not live`, { parentSessionId });
311
- }
312
274
  const principal = principalOf(this.ctx.invocation);
313
275
  const source = {
314
276
  kind: 'user',
@@ -330,7 +292,7 @@ let SubagentRuntime = (() => {
330
292
  content = await attachments.admitPromptContent(request.content);
331
293
  }
332
294
  return {
333
- messageId: await this[deliverSubagentPrompt](parent, childSessionId, content, source, signal, delivery),
295
+ messageId: await this.requireManager().deliverPrompt(childSessionId, content, { source, signal, delivery }),
334
296
  };
335
297
  }
336
298
  catch (error) {
@@ -338,28 +300,20 @@ let SubagentRuntime = (() => {
338
300
  }
339
301
  }
340
302
  /**
341
- * Remote face of {@link interrupt} under one durable parent address. No
342
- * catalog, history, persistence, or parent Agent lookup runs: the core
343
- * primitive alone authorizes the address against the live Activation, which
344
- * is what keeps a live child interruptible while its parent Agent is offline.
303
+ * Remote face of {@link interrupt}, authorized on the target Session by the Gateway.
304
+ * No catalog, history, persistence, or parent Agent lookup runs.
345
305
  * Absent, idle, and already-completed targets are accepted no-ops there.
346
306
  * @param childSessionId - durable child session id to interrupt.
347
- * @param parentSessionId - durable direct parent whose authority is claimed.
348
- * @param mode - required continuable-address discriminator.
349
307
  * @returns acknowledgement that the cancel signal was admitted, not that the target is quiescent.
350
308
  * @throws {RemoteError} `gateway/bad-request` for an empty id,
351
- * `subagent/unauthorized` when the address does not own the live target,
352
309
  * otherwise `gateway/internal`.
353
310
  */
354
- interruptByParent(childSessionId, parentSessionId, mode) {
355
- validateControlRequest('subagent.interrupt', { childSessionId, parentSessionId, mode });
311
+ interruptByUser(childSessionId) {
312
+ validateControlRequest('subagent.interrupt', { childSessionId });
356
313
  try {
357
- this.interrupt(childSessionId, { kind: 'user', parentSessionId });
314
+ this.interrupt(childSessionId, { kind: 'user' });
358
315
  }
359
316
  catch (error) {
360
- if (error instanceof SubagentError && error.code === 'UNAUTHORIZED') {
361
- throw new RemoteError('subagent/unauthorized', 'subagent does not belong to this parent', { childSessionId }, { cause: error });
362
- }
363
317
  throw new RemoteError('gateway/internal', 'subagent interrupt failed', {}, { cause: error });
364
318
  }
365
319
  return { accepted: true };
@@ -400,21 +354,11 @@ let SubagentRuntime = (() => {
400
354
  getProvider(name) {
401
355
  return this.providers.get(name);
402
356
  }
403
- /**
404
- * List registered provider names in insertion order.
405
- * @returns the registered names.
406
- */
407
- list() {
408
- return [...this.providers.keys()];
409
- }
410
- /**
411
- * Resolve one provider's detached continuable-creation contribution. Method
412
- * presence on the provider IS the capability, so a provider without it is
413
- * rejected before the manager reserves any child resources.
414
- */
415
- async prepareContinuable(name, request) {
416
- const provider = this.expectProvider(name);
417
- return provider.prepareContinuable(request);
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
+ }
418
362
  }
419
363
  /** Look up a provider for dispatch or fail loud. */
420
364
  expectProvider(name) {
@@ -431,13 +375,6 @@ let SubagentRuntime = (() => {
431
375
  }
432
376
  return this.manager;
433
377
  }
434
- /**
435
- * Build the lifecycle observer for one continuable Activation's residency
436
- * epoch, so the manager publishes its edges without owning event dispatch.
437
- */
438
- observeActivation(provider, childId, parent) {
439
- return createActivationObserver(this.emitLifecycle, provider, childId, parent);
440
- }
441
378
  /** Reject the first requested capability that the provider lacks. */
442
379
  assertCapabilities(provider, request) {
443
380
  if (request.agentPreset !== undefined && (!provider.capabilities.agentPreset || provider.inheritsParentContext)) {
@@ -1,39 +1,19 @@
1
1
  /**
2
- * Lifecycle-edge publication for managed subagent activations.
2
+ * Contained lifecycle notifications for subagents.
3
3
  *
4
- * Public event payloads live in `./types.ts`; the package-private observer
5
- * retains only the identity shared by each start/end pair.
4
+ * The service owns scoped dispatch and listener containment; the manager
5
+ * supplies event payloads from each activation.
6
6
  *
7
7
  * @module @deepseek-ai/dsh-subagent/lifecycle
8
8
  */
9
9
  import type { Context } from '@deepseek-ai/cordis';
10
10
  import type { Agent } from '@deepseek-ai/dsh-agent';
11
- import type { SessionEvent, SessionId } from '@deepseek-ai/dsh-session';
11
+ import type { SessionEvent } from '@deepseek-ai/dsh-session';
12
12
  import type { SubagentResult, SubagentRunEndInfo, SubagentRunInfo } from './types.ts';
13
13
  /**
14
- * Lifecycle observer for one activation's residency epoch. Package-private: the
15
- * continuation manager is the only consumer, and its call ordering is an
16
- * in-package contract rather than a published extension point.
17
- */
18
- export interface ActivationObserver {
19
- /**
20
- * Publish the start edge once the epoch is resident.
21
- * @param child - the resident local Agent, or undefined for an external execution.
22
- */
23
- start(child?: Agent): void;
24
- /**
25
- * Publish the terminal edge exactly once, pairing this epoch's {@link start},
26
- * after the disposal outcome is known. Called only for a resident epoch: a
27
- * failure before residency publishes no edge, because inventing one would
28
- * report a lifecycle the child never had.
29
- * @param result - the captured result, with teardown failures applied.
30
- */
31
- settle(result: SubagentResult): void;
32
- }
33
- /**
34
- * Publish one lifecycle edge with per-listener exception containment. Run edges
35
- * carry the delegating parent that keys scoped dispatch; provider removal has no
36
- * parent carrier and reaches listeners unscoped.
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.
37
17
  *
38
18
  * The service owns this closure because scoped dispatch keys its carrier by the
39
19
  * exact service instance, whose own context filter composes into the carrier;
@@ -50,19 +30,10 @@ export type LifecycleEmitter = {
50
30
  * returned promise is logged without starving peer listeners, changing the run,
51
31
  * or — for provider removal, which fires from a disposer — breaking teardown.
52
32
  * @param ctx - the service's own context, owning dispatch and the logger.
53
- * @param carrier - resolve the scoped dispatch carrier for one delegating parent.
54
- * @returns the emitter both observers and the provider registry publish through.
33
+ * @param carrier - resolve the scoped dispatch carrier for one subagent tree root.
34
+ * @returns the emitter the manager and provider registry publish through.
55
35
  */
56
36
  export declare function createLifecycleEmitter(ctx: Context, carrier: (parent: Agent) => object): LifecycleEmitter;
57
- /**
58
- * Build the observer for one local or external activation's residency epoch. Creation failure before residency emits no lifecycle edge.
59
- * @param emit - the contained lifecycle emitter.
60
- * @param provider - the provider name recorded in the durable descriptor.
61
- * @param childId - the durable child session id.
62
- * @param parent - the exact live direct parent keying scoped dispatch.
63
- * @returns the observer whose edges this epoch publishes.
64
- */
65
- export declare function createActivationObserver(emit: LifecycleEmitter, provider: string, childId: SessionId, parent: Agent): ActivationObserver;
66
37
  /**
67
38
  * Why this child's epoch ended, for the terminal lifecycle edge and the
68
39
  * manager's own parent delivery. The child's own log is authoritative:
@@ -1,22 +1,20 @@
1
1
  /**
2
- * Lifecycle-edge publication for managed subagent activations.
2
+ * Contained lifecycle notifications for subagents.
3
3
  *
4
- * Public event payloads live in `./types.ts`; the package-private observer
5
- * retains only the identity shared by each start/end pair.
4
+ * The service owns scoped dispatch and listener containment; the manager
5
+ * supplies event payloads from each activation.
6
6
  *
7
7
  * @module @deepseek-ai/dsh-subagent/lifecycle
8
8
  */
9
- import { randomUUID } from 'node:crypto';
10
9
  import { foldConsumedWork } from '@deepseek-ai/dsh-agent';
11
- import { SubagentRunId } from "./types.js";
12
10
  /**
13
11
  * Build the contained lifecycle emitter this seam publishes every edge through.
14
12
  * Every listener is independently contained: a synchronous throw or a rejected
15
13
  * returned promise is logged without starving peer listeners, changing the run,
16
14
  * or — for provider removal, which fires from a disposer — breaking teardown.
17
15
  * @param ctx - the service's own context, owning dispatch and the logger.
18
- * @param carrier - resolve the scoped dispatch carrier for one delegating parent.
19
- * @returns the emitter both observers and the provider registry publish through.
16
+ * @param carrier - resolve the scoped dispatch carrier for one subagent tree root.
17
+ * @returns the emitter the manager and provider registry publish through.
20
18
  */
21
19
  export function createLifecycleEmitter(ctx, carrier) {
22
20
  return (name, info, parent) => {
@@ -36,30 +34,6 @@ export function createLifecycleEmitter(ctx, carrier) {
36
34
  }
37
35
  };
38
36
  }
39
- /**
40
- * Build the observer for one local or external activation's residency epoch. Creation failure before residency emits no lifecycle edge.
41
- * @param emit - the contained lifecycle emitter.
42
- * @param provider - the provider name recorded in the durable descriptor.
43
- * @param childId - the durable child session id.
44
- * @param parent - the exact live direct parent keying scoped dispatch.
45
- * @returns the observer whose edges this epoch publishes.
46
- */
47
- export function createActivationObserver(emit, provider, childId, parent) {
48
- const identity = { runId: SubagentRunId(randomUUID()), provider, id: childId, local: false };
49
- return {
50
- start: (child) => {
51
- identity.local = child !== undefined;
52
- emit('subagent/start', identity, parent);
53
- },
54
- settle: ({ stopReason, output }) => {
55
- emit('subagent/end', {
56
- ...identity,
57
- stopReason,
58
- ...output.length === 0 ? {} : { lastAssistantMessage: output },
59
- }, parent);
60
- },
61
- };
62
- }
63
37
  /**
64
38
  * Why this child's epoch ended, for the terminal lifecycle edge and the
65
39
  * manager's own parent delivery. The child's own log is authoritative: