@deepseek-ai/dsh-subagent 0.1.6-alpha.2 → 0.1.7-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.
@@ -1,33 +1,3 @@
1
- /**
2
- * Service Definition for the subagent capability seam (`ctx.subagents`): a named-provider registry plus a
3
- * capability-validating asynchronous start API. Providers establish a
4
- * child before returning its run, so fulfillment is the single publication and
5
- * ownership-transfer boundary.
6
- *
7
- * Multiple providers coexist: each registers under a unique name and callers
8
- * select one by name.
9
- *
10
- * This package owns the Service Definition role of the capability seam. Service Providers
11
- * (`@deepseek-ai/dsh-subagent-spawn-in-process`, `-fork`, `-acp`) and the model-facing
12
- * consumer (`@deepseek-ai/dsh-tool-subagent`) are separate packages.
13
- *
14
- * Public operations express caller intent: `start` returns one published owned
15
- * one-shot run, `startContinuable` establishes a durable continuable child, and
16
- * `sendMessage` steers between adjacent Agents without exposing whether a child
17
- * is resident. Continuable children never become a {@link SubagentRun}: the
18
- * continuation manager holds their `AgentHandle` directly and orders every turn
19
- * through the child's own inbox, so providers contribute only the detached
20
- * creation spec and see no handle, turn, or teardown. Child and descendant
21
- * discovery read the live session store and optional session persistence
22
- * directly and do not require that continuation runtime.
23
- *
24
- * Same-process providers are trusted typed collaborators. Requests, provider
25
- * descriptors, results, and lifecycle payloads are borrowed immutable values;
26
- * serialization and hostile-input validation belong at real process, worker,
27
- * persistence, and model boundaries.
28
- *
29
- * @module @deepseek-ai/dsh-subagent
30
- */
31
1
  var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
32
2
  var useValue = arguments.length > 2;
33
3
  for (var i = 0; i < initializers.length; i++) {
@@ -67,12 +37,13 @@ import { scopeTarget } from '@deepseek-ai/dsh-scope';
67
37
  import { assertObjectJsonSchema } from '@deepseek-ai/dsh-tools';
68
38
  import { canonicalClientTimeZone } from '@deepseek-ai/dsh-util-time';
69
39
  import { Remote, RemoteError, TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol';
70
- import { catalogView, rejectCatalogRead, rejectPrompt, validateControlRequest, } from "./control.js";
40
+ import { rejectPrompt, validateControlRequest, } from "./control.js";
71
41
  import { SubagentError } from "./error.js";
72
42
  import { assertSubagentMaxDepth } from "./depth.js";
73
43
  import { createActivationObserver, createLifecycleEmitter, observeRun } from "./lifecycle.js";
74
44
  import SubagentContinuationManager from "./continuation.js";
75
45
  import { listChildren as listSubagentChildren, listDescendants as listSubagentDescendants } from "./list-children.js";
46
+ import { installSubagentArchiveAdmission } from "./archive-admission.js";
76
47
  import { snapshotSubagentDescriptor } from "./descriptor.js";
77
48
  import { subagentIdentityProjectionDefinition, subagentTimingProjectionDefinition } from "./projection.js";
78
49
  import { establishCatalogChild, subagentCatalogProjectionDefinition } from "./catalog.js";
@@ -89,25 +60,22 @@ export { appendDelegatedPolicyOverrides, applyChildComposition, captureDelegated
89
60
  let SubagentRuntime = (() => {
90
61
  let _classSuper = TypertRemoteService;
91
62
  let _instanceExtraInitializers = [];
92
- let _remoteExportList_decorators;
93
63
  let _prompt_decorators;
94
64
  let _interruptByParent_decorators;
95
65
  return class SubagentRuntime extends _classSuper {
96
66
  static {
97
67
  const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
98
- _remoteExportList_decorators = [Remote('list')];
99
68
  _prompt_decorators = [Remote('prompt')];
100
69
  _interruptByParent_decorators = [Remote('interruptByParent')];
101
- __esDecorate(this, null, _remoteExportList_decorators, { kind: "method", name: "remoteExportList", static: false, private: false, access: { has: obj => "remoteExportList" in obj, get: obj => obj.remoteExportList }, metadata: _metadata }, null, _instanceExtraInitializers);
102
70
  __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);
103
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);
104
72
  if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
105
73
  }
74
+ config = __runInitializers(this, _instanceExtraInitializers);
106
75
  static Config = z.object({
107
- maxDepth: z.number().step(1).min(0).max(Number.MAX_SAFE_INTEGER).default(1),
108
- maxActiveSubagents: z.number().step(1).min(1).max(Number.MAX_SAFE_INTEGER).default(8),
76
+ maxDepth: z.number().step(1).min(0).max(Number.MAX_SAFE_INTEGER).default(1).volatile(),
77
+ maxActiveSubagents: z.number().step(1).min(1).max(Number.MAX_SAFE_INTEGER).default(8).volatile(),
109
78
  });
110
- settingsSource = __runInitializers(this, _instanceExtraInitializers);
111
79
  providers = new Map();
112
80
  continuations;
113
81
  /**
@@ -118,21 +86,13 @@ let SubagentRuntime = (() => {
118
86
  emitLifecycle;
119
87
  constructor(ctx, config) {
120
88
  super(ctx, 'subagents');
121
- assertSubagentMaxDepth(config.maxDepth);
122
- this.settingsSource = () => config;
123
- ctx.inject(['settings'], (settingsCtx) => {
124
- settingsCtx.settings.installSection(ctx, 'subagent', SubagentRuntime.Config, config, {
125
- validate: (value) => { assertSubagentMaxDepth(value.maxDepth); },
126
- setSource: (source) => { this.settingsSource = source; },
127
- onChange: () => { },
128
- });
129
- });
89
+ this.config = config;
130
90
  this.emitLifecycle = createLifecycleEmitter(this.ctx, parent => scopeTarget(this, parent));
131
91
  ctx.inject(['agents'], (childCtx) => {
132
92
  const manager = new SubagentContinuationManager(childCtx, {
133
93
  prepareContinuable: (name, request) => this.prepareContinuable(name, request),
134
94
  observeActivation: (provider, childId, parent) => this.observeActivation(provider, childId, parent),
135
- }, () => this.settingsSource().maxActiveSubagents);
95
+ }, () => this.config.maxActiveSubagents.get());
136
96
  this.continuations = manager;
137
97
  childCtx.effect(() => () => {
138
98
  /* v8 ignore else -- one injected binding owns the slot until its fiber disposes. */
@@ -141,10 +101,14 @@ let SubagentRuntime = (() => {
141
101
  }, 'subagents.continuationBinding()');
142
102
  });
143
103
  ctx.inject(['sessionProjections'], (projectionCtx) => {
144
- projectionCtx.sessionProjections.register(subagentCatalogProjectionDefinition);
145
- projectionCtx.sessionProjections.register(subagentTimingProjectionDefinition);
146
- projectionCtx.sessionProjections.register(subagentIdentityProjectionDefinition);
104
+ const projections = projectionCtx.sessionProjections;
105
+ projections.register(subagentCatalogProjectionDefinition);
106
+ projections.register(subagentTimingProjectionDefinition);
107
+ projections.register(subagentIdentityProjectionDefinition);
147
108
  });
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); });
148
112
  }
149
113
  /**
150
114
  * Resolve a delegation tool's depth policy against the current user setting.
@@ -154,7 +118,11 @@ let SubagentRuntime = (() => {
154
118
  resolveMaxDepth(configured) {
155
119
  if (configured === 'provider-managed')
156
120
  return undefined;
157
- return configured ?? this.settingsSource().maxDepth;
121
+ if (configured !== undefined)
122
+ return configured;
123
+ const depth = this.config.maxDepth.get();
124
+ assertSubagentMaxDepth(depth);
125
+ return depth;
158
126
  }
159
127
  /**
160
128
  * Establish one durable continuable child and deliver its initial prompt.
@@ -254,21 +222,13 @@ let SubagentRuntime = (() => {
254
222
  await manager.drainChildren(parent, childIds);
255
223
  }
256
224
  /**
257
- * Enumerate the parent's direct session-backed subagents without loading or
258
- * resuming an Agent. The Session query service supplies one live-preferred
259
- * corpus and shared point observations; the projection cache supplies
260
- * immutable descriptor hits without opening cold logs. The registered
261
- * `subagent` projection remains the sole mode/label classifier.
262
- *
263
- * Every query receives `signal`, and the listing rechecks cancellation
264
- * around each await. Read rejections that settle
265
- * after an abort become a stable `SubagentError` with code `CANCELLED`.
266
- * @param parentSessionId - parent session whose direct children are listed.
267
- * @param signal - caller-owned cancellation forwarded to Session queries
268
- * and observed around every read await.
269
- * @returns children and per-child diagnostics ordered by `createdAt`, then id.
270
- * @throws {@link SubagentError} when the projection registry or the session
271
- * store is not mounted, or the caller cancels the listing.
225
+ * Read the parent's durable direct-child catalog without loading or resuming an Agent.
226
+ * The service owns and releases the live-preferred Session observation.
227
+ * @param parentSessionId - parent whose direct children are requested.
228
+ * @param signal - cancellation forwarded to the Session query.
229
+ * @returns catalog children in parent event order.
230
+ * @throws {@link SubagentError} when query or catalog projection is unavailable.
231
+ * @throws SessionQueryError when the parent cannot be read or the query is cancelled.
272
232
  */
273
233
  listChildren(parentSessionId, signal) {
274
234
  return listSubagentChildren(this.ctx, parentSessionId, signal);
@@ -279,40 +239,18 @@ let SubagentRuntime = (() => {
279
239
  * Agent. Ordinary sessions and one-shot children remain traversal nodes so
280
240
  * continuable descendants below them are discovered; each returned entry
281
241
  * adds its durable `parentId` and root-relative `depth`. Identity resolution,
282
- * diagnostics, optional persistence, and cancellation follow the same
283
- * projection-backed contract as {@link listChildren}.
242
+ * diagnostics, optional persistence, and cancellation use the registered
243
+ * child identity projection and complete Session corpus.
284
244
  * @param rootSessionId - session whose complete descendant tree is listed.
285
245
  * @param signal - caller-owned cancellation forwarded to persistence reads
286
246
  * and observed around every read await.
287
247
  * @returns children and per-candidate diagnostics with tree position, in
288
248
  * stable pre-order.
289
- * @throws {@link SubagentError} under the same conditions as {@link listChildren}.
249
+ * @throws {@link SubagentError} when listing dependencies are unavailable or the caller cancels.
290
250
  */
291
251
  listDescendants(rootSessionId, signal) {
292
252
  return listSubagentDescendants(this.ctx, rootSessionId, signal);
293
253
  }
294
- /**
295
- * Remote face of {@link listChildren} for one browser: the durable listing
296
- * plus live Agent activity and the delivery-time parent availability hint.
297
- * Parent availability is a hint; {@link prompt} performs the authoritative
298
- * check. Named apart from the provider-name {@link list}, which owns the
299
- * member.
300
- * @param parentSessionId - parent session whose direct children are listed.
301
- * @param signal - carrier cancellation forwarded to Session queries.
302
- * @returns the catalog view for that parent.
303
- * @throws {RemoteError} `gateway/bad-request` for an empty parent id,
304
- * `gateway/cancelled` for an aborted read, `subagent/projections-unavailable` when
305
- * the deployment has no projection registry, otherwise `gateway/internal`.
306
- */
307
- async remoteExportList(parentSessionId, signal) {
308
- validateControlRequest('subagent.list', { parentSessionId });
309
- try {
310
- return catalogView(this.ctx, parentSessionId, await this.listChildren(parentSessionId, signal));
311
- }
312
- catch (error) {
313
- return rejectCatalogRead(error, signal);
314
- }
315
- }
316
254
  /**
317
255
  * Deliver one browser-authored message to a continuable child through the
318
256
  * exact live direct parent, retaining the caller-minted request identity and
@@ -26,7 +26,7 @@ export interface ActivationTerminal {
26
26
  /** Why this epoch's last ordinary turn ended, or `error` when teardown failed. */
27
27
  readonly stopReason: SubagentResult['stopReason'];
28
28
  /** The epoch's final assistant content, absent when it produced none or failed. */
29
- readonly output?: ContentBlock[];
29
+ readonly output?: readonly ContentBlock[];
30
30
  }
31
31
  /**
32
32
  * Lifecycle observer for one Activation's residency epoch, so continuable
@@ -160,9 +160,10 @@ function epochStopReason(events) {
160
160
  case undefined:
161
161
  case 'completed':
162
162
  return droppedUnrun ? 'aborted' : 'completed';
163
- /* v8 ignore next 3 -- `TurnEndReason` is merge-extensible, so this arm needs a
164
- * backend that adds a variant; treating an unnameable reason as success would
165
- * report failed work as completed. */
163
+ /* v8 ignore next 4 -- `forked` appears only in constructor seed history, while
164
+ * this function reads an epoch-owned suffix. `TurnEndReason` is merge-extensible,
165
+ * so a backend-added variant cannot be listed; treating an unnameable reason as
166
+ * success would report failed work as completed. */
166
167
  default:
167
168
  return 'error';
168
169
  }
@@ -1,24 +1,16 @@
1
1
  /**
2
- * Read-only enumeration of durable subagent children and descendant trees
3
- * through the Session query service. Candidates come from one live-preferred
4
- * corpus; each child's mode/label is the registered `subagent` projection
5
- * unit's value, resolved
6
- * down a three-rung ladder: the registry's watermark cache for a live child,
7
- * an unseeded durable projection-cache row, and one shared Session observation
8
- * otherwise. A seeded header deliberately lacks its exact inherited cut, so
9
- * it takes the body-bearing observation path before classifying an identity.
10
- * The projection fold is the single classification
11
- * authority — this module parses no descriptor
12
- * itself. Absent persistence, enumeration is live-only: a cold child is
13
- * unreachable for resume anyway, so its absence is capability absence, not an
14
- * error. The module owns no catalog state and does not consult Activation,
15
- * Agent-registry, continuation-manager, or provider state.
2
+ * Direct-child discovery from a parent-owned `subagent/catalog` projection,
3
+ * plus complete descendant-tree enumeration from the Session corpus. A direct
4
+ * listing owns one parent observation and reads no child log. Descendant
5
+ * enumeration retains the complete corpus and the child identity projection
6
+ * because ordinary Sessions and one-shot children remain traversal nodes.
16
7
  *
17
8
  * @module @deepseek-ai/dsh-subagent
18
9
  */
19
10
  import type { Context } from '@deepseek-ai/cordis';
20
11
  import type { SessionId } from '@deepseek-ai/dsh-session';
21
12
  import type { SubagentListEntry } from './control-types.ts';
13
+ import type { SubagentCatalogEntry } from './projection-types.ts';
22
14
  export type { SubagentListEntry } from './control-types.ts';
23
15
  /**
24
16
  * One entry of a descendant listing: the interpreted subagent facts plus its
@@ -32,34 +24,25 @@ export type SubagentDescendantListEntry = SubagentListEntry & {
32
24
  readonly depth: number;
33
25
  };
34
26
  /**
35
- * Enumerate one parent's origin-classified direct children from the
36
- * live-preferred merge of `ctx.sessions` and optional session persistence,
37
- * serving each identity from the `subagent` projection unit: the registry's
38
- * watermark snapshot for a live child; for a cold one, a durable
39
- * projection-cache read for an unseeded lifecycle, else one bounded-concurrency
40
- * shared Session observation carrying the exact inherited cut.
41
- * @see SubagentRuntime.listChildren for the public cancellation and failure contract.
42
- * @param ctx - context carrying the session store, the projection registry,
43
- * optional persistence, and the optional projection cache.
44
- * @param parentSessionId - parent session whose direct children are listed.
45
- * @param signal - caller-owned cancellation observed around every persistence read.
46
- * @returns children and per-child diagnostics ordered by `createdAt`, then id.
47
- * @throws {@link SubagentError} when the projection registry or the session
48
- * store is not mounted, or the caller cancels the listing.
27
+ * Read one parent's durable catalog through a live-preferred Session observation.
28
+ * @param ctx - context carrying the Session query service.
29
+ * @param parentSessionId - parent whose direct children are requested.
30
+ * @param signal - cancellation forwarded to the Session observation.
31
+ * @returns direct-child rows in parent catalog event order.
32
+ * @throws {@link SubagentError} when query or catalog projection is unavailable.
49
33
  */
50
- export declare function listChildren(ctx: Context, parentSessionId: SessionId, signal?: AbortSignal): Promise<SubagentListEntry[]>;
34
+ export declare function listChildren(ctx: Context, parentSessionId: SessionId, signal?: AbortSignal): Promise<SubagentCatalogEntry[]>;
51
35
  /**
52
36
  * Enumerate every session-backed subagent below one root in stable pre-order.
53
37
  * Ordinary sessions and one-shot children remain traversal nodes, so a
54
38
  * continuable child below either is still discovered. Classification uses the
55
- * same projection-backed runtime as {@link listChildren}; no Agent is loaded or
56
- * resumed.
39
+ * registered child identity projection; no Agent is loaded or resumed.
57
40
  * @see SubagentRuntime.listDescendants for the public cancellation and failure contract.
58
41
  * @param ctx - context carrying the session store, projection registry, and optional persistence/cache.
59
42
  * @param rootSessionId - session whose complete descendant tree is listed.
60
43
  * @param signal - caller-owned cancellation observed around every persistence read.
61
44
  * @returns interpreted subagents with durable direct-parent and root-relative depth.
62
- * @throws {@link SubagentError} under the same conditions as {@link listChildren}.
45
+ * @throws {@link SubagentError} when listing dependencies are unavailable or the caller cancels.
63
46
  */
64
47
  export declare function listDescendants(ctx: Context, rootSessionId: SessionId, signal?: AbortSignal): Promise<SubagentDescendantListEntry[]>;
65
48
  //# sourceMappingURL=list-children.d.ts.map
@@ -1,18 +1,9 @@
1
1
  /**
2
- * Read-only enumeration of durable subagent children and descendant trees
3
- * through the Session query service. Candidates come from one live-preferred
4
- * corpus; each child's mode/label is the registered `subagent` projection
5
- * unit's value, resolved
6
- * down a three-rung ladder: the registry's watermark cache for a live child,
7
- * an unseeded durable projection-cache row, and one shared Session observation
8
- * otherwise. A seeded header deliberately lacks its exact inherited cut, so
9
- * it takes the body-bearing observation path before classifying an identity.
10
- * The projection fold is the single classification
11
- * authority — this module parses no descriptor
12
- * itself. Absent persistence, enumeration is live-only: a cold child is
13
- * unreachable for resume anyway, so its absence is capability absence, not an
14
- * error. The module owns no catalog state and does not consult Activation,
15
- * Agent-registry, continuation-manager, or provider state.
2
+ * Direct-child discovery from a parent-owned `subagent/catalog` projection,
3
+ * plus complete descendant-tree enumeration from the Session corpus. A direct
4
+ * listing owns one parent observation and reads no child log. Descendant
5
+ * enumeration retains the complete corpus and the child identity projection
6
+ * because ordinary Sessions and one-shot children remain traversal nodes.
16
7
  *
17
8
  * @module @deepseek-ai/dsh-subagent
18
9
  */
@@ -68,7 +59,6 @@ var __disposeResources = (this && this.__disposeResources) || (function (Suppres
68
59
  var e = new Error(message);
69
60
  return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
70
61
  });
71
- import { SessionLogOffset } from '@deepseek-ai/dsh-session';
72
62
  import { SubagentError } from "./error.js";
73
63
  /**
74
64
  * Concurrent cold observations per explicit catalog listing. Current Session
@@ -77,42 +67,48 @@ import { SubagentError } from "./error.js";
77
67
  */
78
68
  const COLD_READ_CONCURRENCY = 4;
79
69
  /**
80
- * Enumerate one parent's origin-classified direct children from the
81
- * live-preferred merge of `ctx.sessions` and optional session persistence,
82
- * serving each identity from the `subagent` projection unit: the registry's
83
- * watermark snapshot for a live child; for a cold one, a durable
84
- * projection-cache read for an unseeded lifecycle, else one bounded-concurrency
85
- * shared Session observation carrying the exact inherited cut.
86
- * @see SubagentRuntime.listChildren for the public cancellation and failure contract.
87
- * @param ctx - context carrying the session store, the projection registry,
88
- * optional persistence, and the optional projection cache.
89
- * @param parentSessionId - parent session whose direct children are listed.
90
- * @param signal - caller-owned cancellation observed around every persistence read.
91
- * @returns children and per-child diagnostics ordered by `createdAt`, then id.
92
- * @throws {@link SubagentError} when the projection registry or the session
93
- * store is not mounted, or the caller cancels the listing.
70
+ * Read one parent's durable catalog through a live-preferred Session observation.
71
+ * @param ctx - context carrying the Session query service.
72
+ * @param parentSessionId - parent whose direct children are requested.
73
+ * @param signal - cancellation forwarded to the Session observation.
74
+ * @returns direct-child rows in parent catalog event order.
75
+ * @throws {@link SubagentError} when query or catalog projection is unavailable.
94
76
  */
95
77
  export async function listChildren(ctx, parentSessionId, signal) {
96
- const listing = await prepareListing(ctx, signal);
97
- const candidates = [...listing.corpus.values()]
98
- .filter(record => record.header.parentSession === parentSessionId
99
- && record.header.origin === 'subagent')
100
- .sort(compareCorpusRecords);
101
- const rows = await resolveCandidateRows(candidates, listing, signal);
102
- return rows.filter((row) => row !== undefined);
78
+ const env_1 = { stack: [], error: void 0, hasError: false };
79
+ try {
80
+ const query = ctx.get('sessionQuery');
81
+ if (query === undefined) {
82
+ throw new SubagentError('listing subagents requires the sessionQuery service (load @deepseek-ai/dsh-session-query)', 'SUBAGENT_CONTROL_QUERY_UNAVAILABLE');
83
+ }
84
+ const parent = __addDisposableResource(env_1, await query.observeSession(parentSessionId, {
85
+ ...signal === undefined ? {} : { signal },
86
+ }), false);
87
+ const entries = parent.projections?.values.subagentCatalog;
88
+ if (entries === undefined) {
89
+ throw new SubagentError('listing subagents requires the registered subagentCatalog projection', 'SUBAGENT_CONTROL_PROJECTIONS_UNAVAILABLE');
90
+ }
91
+ return entries;
92
+ }
93
+ catch (e_1) {
94
+ env_1.error = e_1;
95
+ env_1.hasError = true;
96
+ }
97
+ finally {
98
+ __disposeResources(env_1);
99
+ }
103
100
  }
104
101
  /**
105
102
  * Enumerate every session-backed subagent below one root in stable pre-order.
106
103
  * Ordinary sessions and one-shot children remain traversal nodes, so a
107
104
  * continuable child below either is still discovered. Classification uses the
108
- * same projection-backed runtime as {@link listChildren}; no Agent is loaded or
109
- * resumed.
105
+ * registered child identity projection; no Agent is loaded or resumed.
110
106
  * @see SubagentRuntime.listDescendants for the public cancellation and failure contract.
111
107
  * @param ctx - context carrying the session store, projection registry, and optional persistence/cache.
112
108
  * @param rootSessionId - session whose complete descendant tree is listed.
113
109
  * @param signal - caller-owned cancellation observed around every persistence read.
114
110
  * @returns interpreted subagents with durable direct-parent and root-relative depth.
115
- * @throws {@link SubagentError} under the same conditions as {@link listChildren}.
111
+ * @throws {@link SubagentError} when listing dependencies are unavailable or the caller cancels.
116
112
  */
117
113
  export async function listDescendants(ctx, rootSessionId, signal) {
118
114
  const listing = await prepareListing(ctx, signal);
@@ -271,17 +267,17 @@ function compareCorpusRecords(a, b) {
271
267
  * throw — are final, so they report `corrupt`.
272
268
  */
273
269
  async function resolveColdIdentity(query, cache, header, hasChildren, signal) {
274
- const env_1 = { stack: [], error: void 0, hasError: false };
270
+ const env_2 = { stack: [], error: void 0, hasError: false };
275
271
  try {
276
272
  const childId = header.id;
277
273
  // A header deliberately exposes only whether a fork cut exists, not its
278
- // integer. An unseeded lifecycle has the exact cut 0 and may use the cache;
279
- // a seeded lifecycle must read the body before an identity seq can be
280
- // classified as inherited or owned.
274
+ // integer. An unseeded lifecycle has the exact cut 0, so its cached
275
+ // descriptor is owned at every valid seq; a seeded lifecycle must read the
276
+ // body before an identity seq can be classified as inherited or owned.
281
277
  if (cache !== undefined && !header.isSeeded) {
282
278
  let cached;
283
279
  try {
284
- cached = cache.cachedSnapshot(header, SessionLogOffset(0), ['subagent'])?.values.subagent;
280
+ cached = cache.cachedSnapshot(header, ['subagent'])?.values.subagent;
285
281
  }
286
282
  catch {
287
283
  // Unlike the preparation fold below, a throwing cache read renders no
@@ -317,7 +313,7 @@ async function resolveColdIdentity(query, cache, header, hasChildren, signal) {
317
313
  : 'unavailable',
318
314
  };
319
315
  }
320
- const ownedObservation = __addDisposableResource(env_1, observation, false);
316
+ const ownedObservation = __addDisposableResource(env_2, observation, false);
321
317
  assertListingNotCancelled(signal);
322
318
  // A session id names a slot, not a lifecycle: a child deleted and
323
319
  // re-published under another owner between the enumeration and this read
@@ -332,12 +328,12 @@ async function resolveColdIdentity(query, cache, header, hasChildren, signal) {
332
328
  }
333
329
  return childRow(childId, identity, 'inactive', hasChildren);
334
330
  }
335
- catch (e_1) {
336
- env_1.error = e_1;
337
- env_1.hasError = true;
331
+ catch (e_2) {
332
+ env_2.error = e_2;
333
+ env_2.hasError = true;
338
334
  }
339
335
  finally {
340
- __disposeResources(env_1);
336
+ __disposeResources(env_2);
341
337
  }
342
338
  }
343
339
  /** Materialize one served identity as its child row. */
@@ -68,7 +68,7 @@ export interface RunResultSettlement {
68
68
  /** The turn attempt (typically racing local cancellation); returns the terminal result. */
69
69
  attempt: () => Promise<SubagentResult>;
70
70
  /** Snapshot the provider exposes when cancellation or failure wins settlement. */
71
- collectOutput: () => ContentBlock[];
71
+ collectOutput: () => readonly ContentBlock[];
72
72
  /** Snapshot safe provider-authored detail when a failure wins settlement. */
73
73
  collectDiagnostic?: (() => string | undefined) | undefined;
74
74
  /** Whether local cancellation settled before the attempt's outcome is observed. */
@@ -14,6 +14,9 @@ export type SubagentCatalogEntry = {
14
14
  } | {
15
15
  readonly mode: 'continuable';
16
16
  readonly label: string;
17
+ } | {
18
+ readonly mode: 'unknown';
19
+ readonly label?: string;
17
20
  });
18
21
  /** Durable active-turn timing for one descriptor-backed child session. */
19
22
  export interface SubagentTimingProjection {
@@ -26,6 +29,11 @@ export interface SubagentTimingProjection {
26
29
  /** Latest event time folded into this projection cut. */
27
30
  through: number;
28
31
  };
32
+ /**
33
+ * Whether the latest closed turn after the child's own descriptor completed
34
+ * normally; absent while a turn is open or before one closes.
35
+ */
36
+ lastTurnCompleted?: boolean;
29
37
  }
30
38
  /**
31
39
  * Durable identity of one descriptor-backed subagent session: lifecycle mode
@@ -57,7 +65,7 @@ declare module '@deepseek-ai/dsh-session-projection/types' {
57
65
  interface SessionProjectionMap {
58
66
  /** Direct children in parent catalog event order, excluding fork-inherited facts. */
59
67
  subagentCatalog: SubagentCatalogEntry[];
60
- /** Active-turn duration for a descriptor-backed subagent session. */
68
+ /** Active-turn duration and latest closed-turn completion for a descriptor-backed subagent session. */
61
69
  subagentTiming: SubagentTimingProjection;
62
70
  /**
63
71
  * Identity of a descriptor-backed subagent session. `null` ⟺ no valid
@@ -20,6 +20,8 @@ export interface TimingState {
20
20
  pendingTurnStart?: number | undefined;
21
21
  /** Whether the fold has crossed a descriptor in this logical log. */
22
22
  descriptorSeen: boolean;
23
+ /** Whether the latest closed post-descriptor turn completed normally; absent while a turn is open or before one closes. */
24
+ lastTurnCompleted?: boolean | undefined;
23
25
  }
24
26
  declare module '@deepseek-ai/dsh-session-projection/types' {
25
27
  interface SessionProjectionStateMap {
@@ -52,10 +54,13 @@ export declare const subagentTimingProjectionDefinition: {
52
54
  } | undefined;
53
55
  /** Whether the fold has crossed a descriptor in this logical log. */
54
56
  descriptorSeen: boolean;
57
+ /** Whether the latest closed post-descriptor turn completed normally; absent while a turn is open or before one closes. */
58
+ lastTurnCompleted?: boolean | undefined;
55
59
  };
56
60
  wire: {
57
61
  viewSchema: z.ZodType<SubagentTimingProjection, unknown, z.core.$ZodTypeInternals<SubagentTimingProjection, unknown>>;
58
62
  view: (state: NoInfer<TimingState>) => {
63
+ lastTurnCompleted?: boolean;
59
64
  active?: {
60
65
  since: number;
61
66
  through: number;
@@ -14,15 +14,18 @@ const activeIntervalSchema = z.object({
14
14
  const projectionSchema = z.object({
15
15
  settledMs: z.number().int().nonnegative(),
16
16
  active: activeIntervalSchema.optional(),
17
- }).strict().transform(({ settledMs, active }) => ({
17
+ lastTurnCompleted: z.boolean().optional(),
18
+ }).strict().transform(({ settledMs, active, lastTurnCompleted }) => ({
18
19
  settledMs,
19
20
  ...active === undefined ? {} : { active },
21
+ ...lastTurnCompleted === undefined ? {} : { lastTurnCompleted },
20
22
  }));
21
23
  const timingStateSchema = z.object({
22
24
  settledMs: z.number().int().nonnegative(),
23
25
  active: activeIntervalSchema.optional(),
24
26
  pendingTurnStart: z.number().int().nonnegative().optional(),
25
27
  descriptorSeen: z.boolean(),
28
+ lastTurnCompleted: z.boolean().optional(),
26
29
  }).strict();
27
30
  /**
28
31
  * Fold turn boundaries around the child's own durable descriptor.
@@ -38,9 +41,10 @@ export const subagentTimingProjectionDefinition = {
38
41
  init: () => ({ descriptorSeen: false, settledMs: 0 }),
39
42
  apply: (state, event) => {
40
43
  if (event.type === 'turn/start') {
44
+ const { lastTurnCompleted: _closed, ...openState } = state;
41
45
  return state.descriptorSeen
42
- ? { ...state, active: { since: event.time, through: event.time } }
43
- : { ...state, pendingTurnStart: event.time };
46
+ ? { ...openState, active: { since: event.time, through: event.time } }
47
+ : { ...openState, pendingTurnStart: event.time };
44
48
  }
45
49
  if (event.type === 'subagent/descriptor') {
46
50
  const activeSince = state.active?.since ?? state.pendingTurnStart;
@@ -65,6 +69,7 @@ export const subagentTimingProjectionDefinition = {
65
69
  return {
66
70
  ...rest,
67
71
  settledMs: state.settledMs + Math.max(0, event.time - active.since),
72
+ lastTurnCompleted: event.data.reason.kind === 'completed',
68
73
  };
69
74
  }
70
75
  if (state.active === undefined)
@@ -76,9 +81,10 @@ export const subagentTimingProjectionDefinition = {
76
81
  view: state => ({
77
82
  settledMs: state.settledMs,
78
83
  ...(state.active === undefined ? {} : { active: state.active }),
84
+ ...(state.lastTurnCompleted === undefined ? {} : { lastTurnCompleted: state.lastTurnCompleted }),
79
85
  }),
80
86
  },
81
- stateVersion: 2,
87
+ stateVersion: 3,
82
88
  };
83
89
  // The cast bridges only the optional-label arm: Zod's optional output
84
90
  // includes explicit `undefined`, which exactOptionalPropertyTypes excludes
@@ -30,7 +30,7 @@ function failureDetail(result) {
30
30
  function runOutcome(result) {
31
31
  switch (result.stopReason) {
32
32
  case 'completed':
33
- return { status: 'completed', output: finalText(result.output) };
33
+ return { status: 'completed', result: finalText(result.output) };
34
34
  case 'aborted':
35
35
  return result.diagnostic === undefined
36
36
  ? { status: 'killed' }
@@ -106,7 +106,7 @@ export interface SubagentRunEndInfo {
106
106
  * {@link SubagentResult.output}; absent on infrastructure rejection or when
107
107
  * the child produced none.
108
108
  */
109
- readonly lastAssistantMessage?: ContentBlock[];
109
+ readonly lastAssistantMessage?: readonly ContentBlock[];
110
110
  }
111
111
  /**
112
112
  * Which START-TIME features a provider supports. Checked by the service before delegating to
@@ -260,7 +260,7 @@ export interface SubagentResult {
260
260
  * are skipped. Without a non-empty message, the output is its accumulated
261
261
  * assistant text stream, or `[]` when the child produced neither.
262
262
  */
263
- readonly output: ContentBlock[];
263
+ readonly output: readonly ContentBlock[];
264
264
  /**
265
265
  * The structured result after a requested `outputSchema` was successfully
266
266
  * satisfied. Requesting a schema does not guarantee presence: a provider can