@deepseek-ai/dsh-subagent 0.1.1-rc.2 → 0.1.2-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,13 +1,13 @@
1
1
  /**
2
2
  * Read-only enumeration of durable subagent children and descendant trees
3
- * straight from the live session store and optional session persistence — no
4
- * query service. Candidates come from one live-preferred corpus; each child's
5
- * mode/label is the registered `subagent` projection unit's value, resolved
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
6
  * down a three-rung ladder: the registry's watermark cache for a live child,
7
7
  * a durable projection-cache row when it serves an own-suffix identity (the
8
- * seq gate), and one persistence inspection folded through the registry
9
- * otherwise, validated against the enumerated lifecycle. The projection fold
10
- * is the single classification authority — this module parses no descriptor
8
+ * seq gate), and one shared Session observation otherwise, validated against
9
+ * the enumerated lifecycle. The projection fold is the single classification
10
+ * authority — this module parses no descriptor
11
11
  * itself. Absent persistence, enumeration is live-only: a cold child is
12
12
  * unreachable for resume anyway, so its absence is capability absence, not an
13
13
  * error. The module owns no catalog state and does not consult Activation,
@@ -17,55 +17,8 @@
17
17
  */
18
18
  import type { Context } from '@deepseek-ai/cordis';
19
19
  import type { SessionId } from '@deepseek-ai/dsh-session';
20
- /**
21
- * One entry of a {@link listChildren} result, ordered by header `createdAt`
22
- * with ties broken on id. Only a candidate whose durable header has
23
- * `origin: 'subagent'` is interpreted. A served `subagent` projection value
24
- * produces a `child`; a settled candidate whose fold served no identity
25
- * produces a `diagnostic`; a running candidate without one is omitted — its
26
- * descriptor may not be appended yet (the creation window). Diagnostics
27
- * relay the projection fold's outcome or a failed read, never a per-child
28
- * event scan, and never expose model-hidden descriptor content.
29
- */
30
- export type SubagentListEntry = {
31
- readonly kind: 'child';
32
- /** The durable child session id, stable across Activations. */
33
- readonly id: SessionId;
34
- /**
35
- * Store snapshot activity: `running` means the logical record is live in
36
- * `ctx.sessions`; `inactive` means it exists only in persistence. Neither
37
- * encodes a durable outcome, and a continuable child may still reject
38
- * delivery as an ownership conflict.
39
- */
40
- readonly activity: 'running' | 'inactive';
41
- /** Whether a direct descendant has durable `origin: 'subagent'`. */
42
- readonly hasChildren: boolean;
43
- } & ({
44
- /** A terminal one-shot child. */
45
- readonly mode: 'one-shot';
46
- /** Optional durable creation label from the child's descriptor. */
47
- readonly label?: string;
48
- } | {
49
- /** A resumable conversation. */
50
- readonly mode: 'continuable';
51
- /** Durable creation label from the child's descriptor. */
52
- readonly label: string;
53
- }) | {
54
- readonly kind: 'diagnostic';
55
- /** The candidate's session id. */
56
- readonly id: SessionId;
57
- /**
58
- * Why the candidate has no `child` row: `corrupt` for a settled candidate
59
- * whose projection fold served no identity (a missing, malformed, or
60
- * unrecognized-version descriptor — deliberately undistinguished), and
61
- * for any candidate whose log makes a registered unit's fold or schema
62
- * throw (deterministic data damage, contained per child); `unavailable`
63
- * when the candidate's persistence inspection failed (retried on the
64
- * next listing). `unsupported` is never produced; it remains in the
65
- * union for consumers that route on it.
66
- */
67
- readonly reason: 'corrupt' | 'unsupported' | 'unavailable';
68
- };
20
+ import type { SubagentListEntry } from './control-types.ts';
21
+ export type { SubagentListEntry } from './control-types.ts';
69
22
  /**
70
23
  * One entry of a descendant listing: the interpreted subagent facts plus its
71
24
  * position in the complete session tree. `parentId` is the durable direct
@@ -82,9 +35,8 @@ export type SubagentDescendantListEntry = SubagentListEntry & {
82
35
  * live-preferred merge of `ctx.sessions` and optional session persistence,
83
36
  * serving each identity from the `subagent` projection unit: the registry's
84
37
  * watermark snapshot for a live child; for a cold one, a durable
85
- * projection-cache row when it serves an own-suffix identity (the seq gate),
86
- * else one bounded-concurrency persistence inspection folded through the
87
- * registry.
38
+ * projection-cache read when it serves an own-suffix identity (the seq gate),
39
+ * else one bounded-concurrency shared Session observation.
88
40
  * @see SubagentRuntime.listChildren for the public cancellation and failure contract.
89
41
  * @param ctx - context carrying the session store, the projection registry,
90
42
  * optional persistence, and the optional projection cache.
@@ -1,13 +1,13 @@
1
1
  /**
2
2
  * Read-only enumeration of durable subagent children and descendant trees
3
- * straight from the live session store and optional session persistence — no
4
- * query service. Candidates come from one live-preferred corpus; each child's
5
- * mode/label is the registered `subagent` projection unit's value, resolved
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
6
  * down a three-rung ladder: the registry's watermark cache for a live child,
7
7
  * a durable projection-cache row when it serves an own-suffix identity (the
8
- * seq gate), and one persistence inspection folded through the registry
9
- * otherwise, validated against the enumerated lifecycle. The projection fold
10
- * is the single classification authority — this module parses no descriptor
8
+ * seq gate), and one shared Session observation otherwise, validated against
9
+ * the enumerated lifecycle. The projection fold is the single classification
10
+ * authority — this module parses no descriptor
11
11
  * itself. Absent persistence, enumeration is live-only: a cold child is
12
12
  * unreachable for resume anyway, so its absence is capability absence, not an
13
13
  * error. The module owns no catalog state and does not consult Activation,
@@ -15,11 +15,63 @@
15
15
  *
16
16
  * @module @deepseek-ai/dsh-subagent
17
17
  */
18
+ var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
19
+ if (value !== null && value !== void 0) {
20
+ if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
21
+ var dispose, inner;
22
+ if (async) {
23
+ if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
24
+ dispose = value[Symbol.asyncDispose];
25
+ }
26
+ if (dispose === void 0) {
27
+ if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
28
+ dispose = value[Symbol.dispose];
29
+ if (async) inner = dispose;
30
+ }
31
+ if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
32
+ if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
33
+ env.stack.push({ value: value, dispose: dispose, async: async });
34
+ }
35
+ else if (async) {
36
+ env.stack.push({ async: true });
37
+ }
38
+ return value;
39
+ };
40
+ var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
41
+ return function (env) {
42
+ function fail(e) {
43
+ env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
44
+ env.hasError = true;
45
+ }
46
+ var r, s = 0;
47
+ function next() {
48
+ while (r = env.stack.pop()) {
49
+ try {
50
+ if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
51
+ if (r.dispose) {
52
+ var result = r.dispose.call(r.value);
53
+ if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
54
+ }
55
+ else s |= 1;
56
+ }
57
+ catch (e) {
58
+ fail(e);
59
+ }
60
+ }
61
+ if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
62
+ if (env.hasError) throw env.error;
63
+ }
64
+ return next();
65
+ };
66
+ })(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
67
+ var e = new Error(message);
68
+ return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
69
+ });
18
70
  import { SubagentError } from "./error.js";
19
71
  /**
20
- * Concurrent cold inspections per listing; a constant because it bounds one
21
- * read-only scan of local media, not deployment behavior. Should a networked
22
- * persistence backend appear, promote it to a validated `Config` field.
72
+ * Concurrent cold observations per explicit catalog listing. Current Session
73
+ * persistence providers are local; a networked provider must promote this to
74
+ * a validated deployment setting.
23
75
  */
24
76
  const COLD_READ_CONCURRENCY = 4;
25
77
  /**
@@ -27,9 +79,8 @@ const COLD_READ_CONCURRENCY = 4;
27
79
  * live-preferred merge of `ctx.sessions` and optional session persistence,
28
80
  * serving each identity from the `subagent` projection unit: the registry's
29
81
  * watermark snapshot for a live child; for a cold one, a durable
30
- * projection-cache row when it serves an own-suffix identity (the seq gate),
31
- * else one bounded-concurrency persistence inspection folded through the
32
- * registry.
82
+ * projection-cache read when it serves an own-suffix identity (the seq gate),
83
+ * else one bounded-concurrency shared Session observation.
33
84
  * @see SubagentRuntime.listChildren for the public cancellation and failure contract.
34
85
  * @param ctx - context carrying the session store, the projection registry,
35
86
  * optional persistence, and the optional projection cache.
@@ -91,31 +142,32 @@ async function prepareListing(ctx, signal) {
91
142
  throw new SubagentError('listing subagents requires the session store (load @deepseek-ai/dsh-session)', 'SUBAGENT_CONTROL_SESSION_STORE_UNAVAILABLE');
92
143
  }
93
144
  assertListingNotCancelled(signal);
94
- const persistence = ctx.get('sessionPersistence');
145
+ const query = ctx.get('sessionQuery');
146
+ if (query === undefined) {
147
+ throw new SubagentError('listing subagents requires the sessionQuery service (load @deepseek-ai/dsh-session-query)', 'SUBAGENT_CONTROL_QUERY_UNAVAILABLE');
148
+ }
95
149
  // Optional acceleration only: an absent cache service just means every
96
150
  // cold candidate takes the authoritative preparation rung, so it carries
97
151
  // no error code and no configuration check.
98
152
  const cache = ctx.get('sessionProjectionCache');
99
- let persistedHeaders = [];
100
- if (persistence !== undefined) {
101
- try {
102
- persistedHeaders = await persistence.list(signal);
103
- }
104
- catch (error) {
105
- // The backend may reject with its own abort failure after observing the
106
- // forwarded signal; cancellation stays a stable subagent failure.
107
- assertListingNotCancelled(signal);
108
- throw error;
109
- }
153
+ let records;
154
+ try {
155
+ records = await query.listSessions(signal);
156
+ }
157
+ catch (error) {
110
158
  assertListingNotCancelled(signal);
159
+ throw error;
111
160
  }
161
+ assertListingNotCancelled(signal);
112
162
  // Live-preferred merge without header reconciliation: a live record wins
113
163
  // its id wholesale, exactly as a live-preferred corpus would serve it.
114
164
  const corpus = new Map();
115
- for (const header of persistedHeaders)
116
- corpus.set(header.id, { header, live: undefined });
117
- for (const session of sessions.list()) {
118
- corpus.set(session.header.id, { header: session.header, live: session });
165
+ for (const record of records) {
166
+ const live = sessions.get(record.header.id);
167
+ corpus.set(record.header.id, {
168
+ header: live?.header ?? record.header,
169
+ live,
170
+ });
119
171
  }
120
172
  const subagentParents = new Set();
121
173
  for (const record of corpus.values()) {
@@ -123,11 +175,11 @@ async function prepareListing(ctx, signal) {
123
175
  subagentParents.add(record.header.parentSession);
124
176
  }
125
177
  }
126
- return { projections, persistence, cache, corpus, subagentParents };
178
+ return { projections, query, cache, corpus, subagentParents };
127
179
  }
128
180
  /** Resolve projection-backed rows for aligned candidates with bounded cold reads. */
129
181
  async function resolveCandidateRows(candidates, listing, signal) {
130
- const { projections, persistence, cache, subagentParents } = listing;
182
+ const { projections, query, cache, subagentParents } = listing;
131
183
  const rows = Array.from({ length: candidates.length });
132
184
  const coldReads = [];
133
185
  candidates.forEach((candidate, index) => {
@@ -136,34 +188,31 @@ async function resolveCandidateRows(candidates, listing, signal) {
136
188
  coldReads.push({ index, header: candidate.header });
137
189
  return;
138
190
  }
139
- // The registry's watermark cache serves the live value with zero log
140
- // reads; a live child without an identity yet is the creation window
141
- // before the establishing provider appends its descriptor.
191
+ // Read only the identity unit. A live child without an identity yet is the
192
+ // creation window before the establishing provider appends its descriptor.
142
193
  let identity;
143
194
  try {
144
- identity = projections.snapshot(candidate.live).values.subagent;
195
+ identity = projections.snapshot(candidate.live, ['subagent']).values.subagent;
145
196
  }
146
197
  catch {
147
- // The snapshot folds EVERY registered unit over this child's log, so
148
- // any unit's fold or schema can reject damaged payloads. That is
149
- // deterministic data damage in this one child; it degrades to one
150
- // corrupt diagnostic instead of failing the whole listing.
198
+ // A rejecting identity fold is deterministic data damage in this child;
199
+ // contain it as one diagnostic instead of failing the whole listing.
151
200
  rows[index] = { kind: 'diagnostic', id: childId, reason: 'corrupt' };
152
201
  return;
153
202
  }
154
203
  // The unit's serializable no-value sentinel is `null`; `undefined` can
155
204
  // only mean the key was dropped at a JSON boundary. Both are no value.
156
- if (identity === undefined || identity === null)
205
+ if (identity === undefined || identity === null
206
+ || identity.seq < (candidate.header.seedLength ?? 0))
157
207
  return;
158
208
  rows[index] = childRow(childId, identity, 'running', subagentParents.has(childId));
159
209
  });
160
- // Cold candidates exist only when persistence listed them, so the narrow
161
- // re-check is about types, not reachability.
162
- if (persistence !== undefined && coldReads.length > 0) {
210
+ // Cold candidates came from the query corpus and are resolved concurrently.
211
+ if (coldReads.length > 0) {
163
212
  const queue = [...coldReads];
164
213
  await Promise.all(Array.from({ length: Math.min(COLD_READ_CONCURRENCY, queue.length) }, async () => {
165
214
  for (let job = queue.shift(); job !== undefined; job = queue.shift()) {
166
- rows[job.index] = await resolveColdIdentity(persistence, projections, cache, job.header, subagentParents.has(job.header.id), signal);
215
+ rows[job.index] = await resolveColdIdentity(query, cache, job.header, subagentParents.has(job.header.id), signal);
167
216
  }
168
217
  }));
169
218
  }
@@ -214,70 +263,81 @@ function compareCorpusRecords(a, b) {
214
263
  /**
215
264
  * Resolve one cold candidate down the remaining ladder: a durable
216
265
  * projection-cache row when it serves an own-suffix identity (the seq gate),
217
- * otherwise one persistence inspection folded through the projection
218
- * registry (the same detached recipe the API proxy uses for detached session
219
- * projections). A failed inspection is one transient `unavailable` row
220
- * retried on the next listing; an inspection naming another lifecycle, and a
266
+ * otherwise one shared Session observation. An absent or transiently failed
267
+ * observation is one `unavailable` row retried on the next listing; an observation
268
+ * source naming another lifecycle, and a
221
269
  * settled log the fold cannot identify — or that makes any registered unit
222
270
  * throw — are final, so they report `corrupt`.
223
271
  */
224
- async function resolveColdIdentity(persistence, projections, cache, header, hasChildren, signal) {
225
- const childId = header.id;
226
- if (cache !== undefined) {
227
- let cached;
228
- try {
229
- cached = cache.cachedSnapshot(header)?.values.subagent;
272
+ async function resolveColdIdentity(query, cache, header, hasChildren, signal) {
273
+ const env_1 = { stack: [], error: void 0, hasError: false };
274
+ try {
275
+ const childId = header.id;
276
+ if (cache !== undefined) {
277
+ let cached;
278
+ try {
279
+ cached = cache.cachedSnapshot(header, ['subagent'])?.values.subagent;
280
+ }
281
+ catch {
282
+ // Unlike the preparation fold below, a throwing cache read renders no
283
+ // verdict: the cache is derived data, so its damage (a poisoned stored
284
+ // row of ANY unit) silently falls through to the authoritative re-fold.
285
+ cached = undefined;
286
+ }
287
+ // A child's OWN descriptor is immutable once appended, so a cached
288
+ // identity is final only when the seq gate proves it was folded from the
289
+ // own suffix: a creation-window checkpoint may instead carry a fork
290
+ // seed's replayed ANCESTOR descriptor (seq below `seedLength`), which
291
+ // must not outrank the re-fold. Everything else also falls through to
292
+ // preparation: an absent key (a cut before any descriptor) and the
293
+ // `null` sentinel, whose verdict belongs to the authoritative re-fold,
294
+ // not to a derived row.
295
+ if (cached !== undefined && cached !== null && cached.seq >= (header.seedLength ?? 0)) {
296
+ return childRow(childId, cached, 'inactive', hasChildren);
297
+ }
230
298
  }
231
- catch {
232
- // Unlike the preparation fold below, a throwing cache read renders no
233
- // verdict: the cache is derived data, so its damage (a poisoned stored
234
- // row of ANY unit) silently falls through to the authoritative re-fold.
235
- cached = undefined;
299
+ assertListingNotCancelled(signal);
300
+ let observation;
301
+ try {
302
+ observation = await query.observeSession(childId, {
303
+ ...(signal === undefined ? {} : { signal }),
304
+ });
236
305
  }
237
- // A child's OWN descriptor is immutable once appended, so a cached
238
- // identity is final only when the seq gate proves it was folded from the
239
- // own suffix: a creation-window checkpoint may instead carry a fork
240
- // seed's replayed ANCESTOR descriptor (seq below `seedLength`), which
241
- // must not outrank the re-fold. Everything else also falls through to
242
- // preparation: an absent key (a cut before any descriptor) and the
243
- // `null` sentinel, whose verdict belongs to the authoritative re-fold,
244
- // not to a derived row.
245
- if (cached !== undefined && cached !== null && cached.seq >= (header.seedLength ?? 0)) {
246
- return childRow(childId, cached, 'inactive', hasChildren);
306
+ catch (error) {
307
+ // Per-child isolation: durable corruption is stable; absence and backend
308
+ // failures remain retryable. Either way, the listing itself still succeeds.
309
+ assertListingNotCancelled(signal);
310
+ return {
311
+ kind: 'diagnostic',
312
+ id: childId,
313
+ reason: sessionQueryCode(error) === 'SESSION_QUERY_CORRUPT_SESSION'
314
+ || sessionQueryCode(error) === 'SESSION_QUERY_SOURCE_CONFLICT'
315
+ ? 'corrupt'
316
+ : 'unavailable',
317
+ };
247
318
  }
248
- }
249
- assertListingNotCancelled(signal);
250
- let inspected;
251
- try {
252
- inspected = await persistence.inspect(childId, signal);
253
- }
254
- catch {
255
- // Per-child isolation: the child vanished or its backend read failed —
256
- // one diagnostic row, and the listing itself still succeeds.
319
+ const ownedObservation = __addDisposableResource(env_1, observation, false);
257
320
  assertListingNotCancelled(signal);
258
- return { kind: 'diagnostic', id: childId, reason: 'unavailable' };
259
- }
260
- assertListingNotCancelled(signal);
261
- // A session id names a slot, not a lifecycle: a child deleted and
262
- // re-published under another owner between the enumeration and this read
263
- // must not leak into the old parent's listing.
264
- if (!sameLifecycle(inspected.meta, header)) {
265
- return { kind: 'diagnostic', id: childId, reason: 'corrupt' };
266
- }
267
- let identity;
268
- try {
269
- identity = projections.restore({}, inspected.events, 0).snapshot.values.subagent;
321
+ // A session id names a slot, not a lifecycle: a child deleted and
322
+ // re-published under another owner between the enumeration and this read
323
+ // must not leak into the old parent's listing.
324
+ if (!sameLifecycle(ownedObservation.header, header)) {
325
+ return { kind: 'diagnostic', id: childId, reason: 'corrupt' };
326
+ }
327
+ const identity = ownedObservation.projections?.values.subagent;
328
+ if (identity === undefined || identity === null
329
+ || identity.seq < (header.seedLength ?? 0)) {
330
+ return { kind: 'diagnostic', id: childId, reason: 'corrupt' };
331
+ }
332
+ return childRow(childId, identity, 'inactive', hasChildren);
270
333
  }
271
- catch {
272
- // The restore folds EVERY registered unit over this child's log, so any
273
- // unit's fold or schema can reject damaged payloads — deterministic data
274
- // damage in this one child, contained as its own corrupt diagnostic.
275
- return { kind: 'diagnostic', id: childId, reason: 'corrupt' };
334
+ catch (e_1) {
335
+ env_1.error = e_1;
336
+ env_1.hasError = true;
276
337
  }
277
- if (identity === undefined || identity === null) {
278
- return { kind: 'diagnostic', id: childId, reason: 'corrupt' };
338
+ finally {
339
+ __disposeResources(env_1);
279
340
  }
280
- return childRow(childId, identity, 'inactive', hasChildren);
281
341
  }
282
342
  /** Materialize one served identity as its child row. */
283
343
  function childRow(id, identity, activity, hasChildren) {
@@ -302,6 +362,7 @@ function childRow(id, identity, activity, hasChildren) {
302
362
  /** Immutable header fields that distinguish one session lifecycle from another under the same id. */
303
363
  const LIFECYCLE_WITNESS_KEYS = [
304
364
  'version', 'id', 'createdAt', 'cwd', 'parentSession', 'seedLength', 'delegationDepth',
365
+ 'origin', 'agentPreset',
305
366
  ];
306
367
  /** Whether an inspected log still belongs to the enumerated lifecycle. */
307
368
  function sameLifecycle(meta, expected) {
@@ -313,4 +374,7 @@ function assertListingNotCancelled(signal) {
313
374
  throw new SubagentError('subagent listing was cancelled', 'CANCELLED');
314
375
  }
315
376
  }
377
+ function sessionQueryCode(error) {
378
+ return error instanceof Error && 'code' in error ? error.code : undefined;
379
+ }
316
380
  //# sourceMappingURL=list-children.js.map
@@ -15,7 +15,7 @@ import type { SubagentCapabilities, SubagentResult, SubagentRun, SubagentStopRea
15
15
  /**
16
16
  * The capability advertisement of an out-of-process backend: NONE. A child in
17
17
  * another process cannot honor parent-enforced start features
18
- * (`outputSchema`/`maxDepth`/`toolFilter`/`persona`), so the service rejects a
18
+ * (`agentOptions`/`outputSchema`/`maxDepth`/`toolFilter`/`persona`), so the service rejects a
19
19
  * request needing any of them before `start` runs — never accepted-then-ignored.
20
20
  */
21
21
  export declare const NO_START_CAPABILITIES: SubagentCapabilities;
@@ -85,7 +85,8 @@ export interface RunResultSettlement {
85
85
  * rejects after publication. A normally completed or rejected attempt resolves
86
86
  * as `aborted` when cancellation already settled locally; another rejection is
87
87
  * flattened to `stopReason: 'error'` through the contained diagnostic sink.
88
- * The abort listener is removed on every path.
88
+ * Provider-returned diagnostics use the same byte limit. The abort listener is
89
+ * removed on every path.
89
90
  * @param parts - the attempt, output snapshot, cancellation state, sink, and signal wiring.
90
91
  * @returns the terminal result (never a rejection).
91
92
  */
@@ -34,13 +34,20 @@ function limitSubagentDiagnostic(diagnostic) {
34
34
  return utf8Decoder.decode(bytes.subarray(0, prefixBytes))
35
35
  + DIAGNOSTIC_TRUNCATION_SUFFIX;
36
36
  }
37
+ /** Enforce the byte limit on a provider-returned diagnostic. */
38
+ function normalizeSubagentDiagnostic(result) {
39
+ return result.diagnostic === undefined
40
+ ? result
41
+ : { ...result, diagnostic: limitSubagentDiagnostic(result.diagnostic) };
42
+ }
37
43
  /**
38
44
  * The capability advertisement of an out-of-process backend: NONE. A child in
39
45
  * another process cannot honor parent-enforced start features
40
- * (`outputSchema`/`maxDepth`/`toolFilter`/`persona`), so the service rejects a
46
+ * (`agentOptions`/`outputSchema`/`maxDepth`/`toolFilter`/`persona`), so the service rejects a
41
47
  * request needing any of them before `start` runs — never accepted-then-ignored.
42
48
  */
43
49
  export const NO_START_CAPABILITIES = Object.freeze({
50
+ agentOptions: false,
44
51
  outputSchema: false,
45
52
  depthLimit: false,
46
53
  toolFilter: false,
@@ -148,7 +155,8 @@ function toError(value) {
148
155
  * rejects after publication. A normally completed or rejected attempt resolves
149
156
  * as `aborted` when cancellation already settled locally; another rejection is
150
157
  * flattened to `stopReason: 'error'` through the contained diagnostic sink.
151
- * The abort listener is removed on every path.
158
+ * Provider-returned diagnostics use the same byte limit. The abort listener is
159
+ * removed on every path.
152
160
  * @param parts - the attempt, output snapshot, cancellation state, sink, and signal wiring.
153
161
  * @returns the terminal result (never a rejection).
154
162
  */
@@ -157,7 +165,7 @@ export async function settleRunResult(parts) {
157
165
  const result = await parts.attempt();
158
166
  return parts.cancelled()
159
167
  ? { output: parts.collectOutput(), stopReason: 'aborted' }
160
- : result;
168
+ : normalizeSubagentDiagnostic(result);
161
169
  }
162
170
  catch (error) {
163
171
  // Cover a rejection already queued when cancellation arrives.
@@ -20,8 +20,10 @@ function failureDetail(result) {
20
20
  : `${stopReason}; diagnostic: ${result.diagnostic}`;
21
21
  }
22
22
  /**
23
- * Map a child result to the task outcome: completed carries final text,
24
- * aborted is killed, and every other reason is failed without partial output.
23
+ * Map a child result to the task outcome: completed carries final text, local
24
+ * cancellation (`aborted` without a diagnostic) is killed, and provider-
25
+ * diagnosed remote aborts plus every other reason are failed without partial
26
+ * output.
25
27
  * @param result - child terminal result.
26
28
  * @returns outcome for the `ctx.jobs` registration.
27
29
  */
@@ -30,7 +32,9 @@ function runOutcome(result) {
30
32
  case 'completed':
31
33
  return { status: 'completed', output: finalText(result.output) };
32
34
  case 'aborted':
33
- return { status: 'killed' };
35
+ return result.diagnostic === undefined
36
+ ? { status: 'killed' }
37
+ : { status: 'failed', detail: failureDetail(result) };
34
38
  case 'error':
35
39
  case 'max-tokens':
36
40
  case 'refusal':
@@ -76,6 +76,7 @@ export interface SubagentRunEndInfo {
76
76
  * to `maxDepth`; the other names match.
77
77
  */
78
78
  export interface SubagentCapabilities {
79
+ readonly agentOptions: boolean;
79
80
  readonly outputSchema: boolean;
80
81
  readonly depthLimit: boolean;
81
82
  readonly toolFilter: boolean;
@@ -107,6 +108,13 @@ export interface SubagentStartRequest {
107
108
  * remaining turn work when it fires afterward.
108
109
  */
109
110
  readonly signal: AbortSignal;
111
+ /**
112
+ * Optional host-Agent provider, model, reasoning-effort, and output-token
113
+ * overrides. Requires {@link SubagentCapabilities.agentOptions}; in-process
114
+ * providers merge them over the parent Agent's options when they create the
115
+ * child, while the DSH SDK provider merges them over its instance defaults
116
+ * before initializing the separate child runtime.
117
+ */
110
118
  readonly agentOptions?: AgentOptions;
111
119
  /**
112
120
  * Object-rooted JSON Schema within `assertObjectJsonSchema`'s enforced subset. Start rejects
@@ -283,6 +291,16 @@ export interface SubagentProvider {
283
291
  * It says nothing about tool registration, injected services, or authority inheritance.
284
292
  */
285
293
  readonly inheritsParentContext: boolean;
294
+ /**
295
+ * Optional static provider-owned provider/model route for one-shot Agent
296
+ * options. Consumers merge tool/model overrides over these values before
297
+ * preflight; providers whose route derives from the parent omit it. The value
298
+ * is detached immutable data and requires `agentOptions` support.
299
+ */
300
+ readonly agentRouteDefaults?: Readonly<{
301
+ provider: string;
302
+ model: string;
303
+ }>;
286
304
  /**
287
305
  * Establish a ONE-SHOT child and return its handle after publication.
288
306
  * The service has already validated that every requested start-time