@xneog/dsh-subagent 0.1.0 → 0.1.3-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.i18n.yaml +2 -2
  2. package/README.md +108 -76
  3. package/README.zh.md +112 -80
  4. package/lib/index.js +1258 -718
  5. package/lib/typert.host.d.ts +3 -0
  6. package/lib/typert.host.js +923 -0
  7. package/lib/typert.remote-client.d.ts +27 -0
  8. package/lib/typert.remote-client.js +159 -0
  9. package/lib/types/assistant-output.d.ts +3 -3
  10. package/lib/types/assistant-output.js +8 -4
  11. package/lib/types/child-agent.d.ts +16 -5
  12. package/lib/types/child-agent.js +51 -13
  13. package/lib/types/client.d.ts +2 -1
  14. package/lib/types/client.js +1 -1
  15. package/lib/types/continuation.d.ts +100 -72
  16. package/lib/types/continuation.js +439 -169
  17. package/lib/types/control-types.d.ts +144 -0
  18. package/lib/types/control-types.js +9 -0
  19. package/lib/types/control.d.ts +67 -0
  20. package/lib/types/control.js +115 -0
  21. package/lib/types/descriptor-seed.d.ts +1 -1
  22. package/lib/types/descriptor-seed.js +1 -1
  23. package/lib/types/descriptor.d.ts +6 -1
  24. package/lib/types/descriptor.js +6 -2
  25. package/lib/types/index.d.ts +103 -69
  26. package/lib/types/index.js +436 -287
  27. package/lib/types/internal.d.ts +59 -0
  28. package/lib/types/internal.js +58 -0
  29. package/lib/types/lifecycle.js +4 -3
  30. package/lib/types/list-children.d.ts +12 -59
  31. package/lib/types/list-children.js +166 -101
  32. package/lib/types/out-of-process.d.ts +5 -2
  33. package/lib/types/out-of-process.js +42 -4
  34. package/lib/types/projection-types.d.ts +4 -3
  35. package/lib/types/projection.d.ts +55 -8
  36. package/lib/types/projection.js +33 -17
  37. package/lib/types/run-settlement.js +17 -6
  38. package/lib/types/types.d.ts +25 -0
  39. package/package.json +67 -37
  40. package/lib/types/activation-setup-registry.d.ts +0 -57
  41. package/lib/types/activation-setup-registry.js +0 -148
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Continuation integration markers and host adapters outside the public
3
+ * Service Definition and model-facing Agent messaging contract.
4
+ * @module @xneog/dsh-subagent/internal
5
+ */
6
+ import type { Agent } from '@xneog/dsh-agent';
7
+ import type { ContentBlock, MessageId, MessageSource } from '@xneog/dsh-llm';
8
+ import type { SessionId } from '@xneog/dsh-session';
9
+ import type { ToolDefinition } from '@xneog/dsh-tools';
10
+ import type SubagentRuntime from './index.ts';
11
+ /** Process-stable identity carried only by the standard adjacent-Agent messaging tool. */
12
+ export declare const adjacentAgentSendMessageTool: unique symbol;
13
+ /**
14
+ * Mark the standard adjacent-Agent messaging tool without changing its model-visible schema.
15
+ * @param definition - the standard `send_message` definition.
16
+ * @returns the same definition with its internal identity installed.
17
+ */
18
+ export declare function markAdjacentAgentSendMessageTool(definition: ToolDefinition): ToolDefinition;
19
+ /**
20
+ * Test whether one visible definition is the standard adjacent-Agent messaging tool.
21
+ * @param definition - the scope-resolved `send_message` candidate.
22
+ * @returns whether the definition carries the internal standard-tool identity.
23
+ */
24
+ export declare function isAdjacentAgentSendMessageTool(definition: ToolDefinition | undefined): boolean;
25
+ /**
26
+ * Process-stable symbol-keyed host delivery shared by the bundled runtime
27
+ * entry and this unbundled internal subpath.
28
+ * @internal
29
+ */
30
+ export declare const deliverSubagentPrompt: unique symbol;
31
+ /** Scheduling mode for one host-only direct-child prompt. */
32
+ export type HostPromptDeliveryMode = 'queue' | 'steer';
33
+ /** Runtime face required by the host-only prompt adapters. */
34
+ export interface HostPromptDeliverer {
35
+ [deliverSubagentPrompt](parent: Agent, childId: SessionId, content: ContentBlock[], source: MessageSource, signal: AbortSignal, delivery: HostPromptDeliveryMode): Promise<MessageId>;
36
+ }
37
+ /**
38
+ * Queue one host-protocol message without exposing another Service operation.
39
+ * @param runtime - subagent runtime owning continuation residency.
40
+ * @param parent - exact live direct parent authorizing delivery.
41
+ * @param childId - durable direct-child session id.
42
+ * @param content - host-authored content to deliver.
43
+ * @param source - durable host-protocol provenance.
44
+ * @param signal - caller cancellation before inbox acceptance.
45
+ * @returns the accepted message's inbox id.
46
+ */
47
+ export declare function queueHostSubagentPrompt(runtime: SubagentRuntime, parent: Agent, childId: SessionId, content: ContentBlock[], source: MessageSource, signal: AbortSignal): Promise<MessageId>;
48
+ /**
49
+ * Steer one host-protocol message without exposing another Service operation.
50
+ * @param runtime - subagent runtime owning continuation residency.
51
+ * @param parent - exact live direct parent authorizing delivery.
52
+ * @param childId - durable direct-child session id.
53
+ * @param content - host-authored content to deliver.
54
+ * @param source - durable host-protocol provenance.
55
+ * @param signal - caller cancellation before inbox acceptance.
56
+ * @returns the accepted message's inbox id.
57
+ */
58
+ export declare function steerHostSubagentPrompt(runtime: SubagentRuntime, parent: Agent, childId: SessionId, content: ContentBlock[], source: MessageSource, signal: AbortSignal): Promise<MessageId>;
59
+ //# sourceMappingURL=internal.d.ts.map
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Continuation integration markers and host adapters outside the public
3
+ * Service Definition and model-facing Agent messaging contract.
4
+ * @module @xneog/dsh-subagent/internal
5
+ */
6
+ /** Process-stable identity carried only by the standard adjacent-Agent messaging tool. */
7
+ export const adjacentAgentSendMessageTool = Symbol.for('dsh.subagent.adjacentAgentSendMessageTool');
8
+ /**
9
+ * Mark the standard adjacent-Agent messaging tool without changing its model-visible schema.
10
+ * @param definition - the standard `send_message` definition.
11
+ * @returns the same definition with its internal identity installed.
12
+ */
13
+ export function markAdjacentAgentSendMessageTool(definition) {
14
+ Object.defineProperty(definition, adjacentAgentSendMessageTool, { value: true });
15
+ return definition;
16
+ }
17
+ /**
18
+ * Test whether one visible definition is the standard adjacent-Agent messaging tool.
19
+ * @param definition - the scope-resolved `send_message` candidate.
20
+ * @returns whether the definition carries the internal standard-tool identity.
21
+ */
22
+ export function isAdjacentAgentSendMessageTool(definition) {
23
+ return definition !== undefined
24
+ && definition[adjacentAgentSendMessageTool] === true;
25
+ }
26
+ /**
27
+ * Process-stable symbol-keyed host delivery shared by the bundled runtime
28
+ * entry and this unbundled internal subpath.
29
+ * @internal
30
+ */
31
+ export const deliverSubagentPrompt = Symbol.for('dsh.subagent.deliverPrompt');
32
+ /**
33
+ * Queue one host-protocol message without exposing another Service operation.
34
+ * @param runtime - subagent runtime owning continuation residency.
35
+ * @param parent - exact live direct parent authorizing delivery.
36
+ * @param childId - durable direct-child session id.
37
+ * @param content - host-authored content to deliver.
38
+ * @param source - durable host-protocol provenance.
39
+ * @param signal - caller cancellation before inbox acceptance.
40
+ * @returns the accepted message's inbox id.
41
+ */
42
+ export function queueHostSubagentPrompt(runtime, parent, childId, content, source, signal) {
43
+ return runtime[deliverSubagentPrompt](parent, childId, content, source, signal, 'queue');
44
+ }
45
+ /**
46
+ * Steer one host-protocol message without exposing another Service operation.
47
+ * @param runtime - subagent runtime owning continuation residency.
48
+ * @param parent - exact live direct parent authorizing delivery.
49
+ * @param childId - durable direct-child session id.
50
+ * @param content - host-authored content to deliver.
51
+ * @param source - durable host-protocol provenance.
52
+ * @param signal - caller cancellation before inbox acceptance.
53
+ * @returns the accepted message's inbox id.
54
+ */
55
+ export function steerHostSubagentPrompt(runtime, parent, childId, content, source, signal) {
56
+ return runtime[deliverSubagentPrompt](parent, childId, content, source, signal, 'steer');
57
+ }
58
+ //# sourceMappingURL=internal.js.map
@@ -15,6 +15,7 @@
15
15
  */
16
16
  import { randomUUID } from 'node:crypto';
17
17
  import { foldConsumedWork } from '@xneog/dsh-agent';
18
+ import { SessionLogOffset } from '@xneog/dsh-session';
18
19
  import { finalAssistantOutput } from "./assistant-output.js";
19
20
  import { SubagentRunId } from "./types.js";
20
21
  /**
@@ -90,7 +91,7 @@ export function createActivationObserver(emit, provider, childId, parent) {
90
91
  // A cold resume replays earlier turns, so this epoch's telemetry must come
91
92
  // from the suffix it actually produced — never the whole session, which
92
93
  // would report a previous epoch's answer when this one opened no turn.
93
- let boundary = 0;
94
+ let boundary = SessionLogOffset(0);
94
95
  // Assigned by `capture()`, which the disposal path always runs before
95
96
  // `settle()`; a resident epoch therefore always has its facts by then.
96
97
  let captured = { stopReason: 'completed' };
@@ -101,11 +102,11 @@ export function createActivationObserver(emit, provider, childId, parent) {
101
102
  : { stopReason: 'error' };
102
103
  return {
103
104
  start: (child) => {
104
- boundary = child.session.events.length;
105
+ boundary = child.session.seq;
105
106
  emit('subagent/start', identity, parent);
106
107
  },
107
108
  capture: (child) => {
108
- const own = child.session.events.slice(boundary);
109
+ const own = child.session.snapshotEvents(boundary);
109
110
  const output = finalAssistantOutput(own);
110
111
  captured = {
111
112
  stopReason: epochStopReason(own),
@@ -1,13 +1,14 @@
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
- * 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
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
11
12
  * itself. Absent persistence, enumeration is live-only: a cold child is
12
13
  * unreachable for resume anyway, so its absence is capability absence, not an
13
14
  * error. The module owns no catalog state and does not consult Activation,
@@ -17,55 +18,8 @@
17
18
  */
18
19
  import type { Context } from '@xneog/cordis';
19
20
  import type { SessionId } from '@xneog/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
- };
21
+ import type { SubagentListEntry } from './control-types.ts';
22
+ export type { SubagentListEntry } from './control-types.ts';
69
23
  /**
70
24
  * One entry of a descendant listing: the interpreted subagent facts plus its
71
25
  * position in the complete session tree. `parentId` is the durable direct
@@ -82,9 +36,8 @@ export type SubagentDescendantListEntry = SubagentListEntry & {
82
36
  * live-preferred merge of `ctx.sessions` and optional session persistence,
83
37
  * serving each identity from the `subagent` projection unit: the registry's
84
38
  * 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.
39
+ * projection-cache read for an unseeded lifecycle, else one bounded-concurrency
40
+ * shared Session observation carrying the exact inherited cut.
88
41
  * @see SubagentRuntime.listChildren for the public cancellation and failure contract.
89
42
  * @param ctx - context carrying the session store, the projection registry,
90
43
  * optional persistence, and the optional projection cache.
@@ -1,13 +1,14 @@
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
- * 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
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
11
12
  * itself. Absent persistence, enumeration is live-only: a cold child is
12
13
  * unreachable for resume anyway, so its absence is capability absence, not an
13
14
  * error. The module owns no catalog state and does not consult Activation,
@@ -15,11 +16,64 @@
15
16
  *
16
17
  * @module @xneog/dsh-subagent
17
18
  */
19
+ var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
20
+ if (value !== null && value !== void 0) {
21
+ if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
22
+ var dispose, inner;
23
+ if (async) {
24
+ if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
25
+ dispose = value[Symbol.asyncDispose];
26
+ }
27
+ if (dispose === void 0) {
28
+ if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
29
+ dispose = value[Symbol.dispose];
30
+ if (async) inner = dispose;
31
+ }
32
+ if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
33
+ if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
34
+ env.stack.push({ value: value, dispose: dispose, async: async });
35
+ }
36
+ else if (async) {
37
+ env.stack.push({ async: true });
38
+ }
39
+ return value;
40
+ };
41
+ var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
42
+ return function (env) {
43
+ function fail(e) {
44
+ env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
45
+ env.hasError = true;
46
+ }
47
+ var r, s = 0;
48
+ function next() {
49
+ while (r = env.stack.pop()) {
50
+ try {
51
+ if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
52
+ if (r.dispose) {
53
+ var result = r.dispose.call(r.value);
54
+ if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
55
+ }
56
+ else s |= 1;
57
+ }
58
+ catch (e) {
59
+ fail(e);
60
+ }
61
+ }
62
+ if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
63
+ if (env.hasError) throw env.error;
64
+ }
65
+ return next();
66
+ };
67
+ })(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
68
+ var e = new Error(message);
69
+ return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
70
+ });
71
+ import { SessionLogOffset } from '@xneog/dsh-session';
18
72
  import { SubagentError } from "./error.js";
19
73
  /**
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.
74
+ * Concurrent cold observations per explicit catalog listing. Current Session
75
+ * persistence providers are local; a networked provider must promote this to
76
+ * a validated deployment setting.
23
77
  */
24
78
  const COLD_READ_CONCURRENCY = 4;
25
79
  /**
@@ -27,9 +81,8 @@ const COLD_READ_CONCURRENCY = 4;
27
81
  * live-preferred merge of `ctx.sessions` and optional session persistence,
28
82
  * serving each identity from the `subagent` projection unit: the registry's
29
83
  * 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.
84
+ * projection-cache read for an unseeded lifecycle, else one bounded-concurrency
85
+ * shared Session observation carrying the exact inherited cut.
33
86
  * @see SubagentRuntime.listChildren for the public cancellation and failure contract.
34
87
  * @param ctx - context carrying the session store, the projection registry,
35
88
  * optional persistence, and the optional projection cache.
@@ -91,31 +144,32 @@ async function prepareListing(ctx, signal) {
91
144
  throw new SubagentError('listing subagents requires the session store (load @xneog/dsh-session)', 'SUBAGENT_CONTROL_SESSION_STORE_UNAVAILABLE');
92
145
  }
93
146
  assertListingNotCancelled(signal);
94
- const persistence = ctx.get('sessionPersistence');
147
+ const query = ctx.get('sessionQuery');
148
+ if (query === undefined) {
149
+ throw new SubagentError('listing subagents requires the sessionQuery service (load @xneog/dsh-session-query)', 'SUBAGENT_CONTROL_QUERY_UNAVAILABLE');
150
+ }
95
151
  // Optional acceleration only: an absent cache service just means every
96
152
  // cold candidate takes the authoritative preparation rung, so it carries
97
153
  // no error code and no configuration check.
98
154
  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
- }
155
+ let records;
156
+ try {
157
+ records = await query.listSessions(signal);
158
+ }
159
+ catch (error) {
110
160
  assertListingNotCancelled(signal);
161
+ throw error;
111
162
  }
163
+ assertListingNotCancelled(signal);
112
164
  // Live-preferred merge without header reconciliation: a live record wins
113
165
  // its id wholesale, exactly as a live-preferred corpus would serve it.
114
166
  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 });
167
+ for (const record of records) {
168
+ const live = sessions.get(record.header.id);
169
+ corpus.set(record.header.id, {
170
+ header: live?.header ?? record.header,
171
+ live,
172
+ });
119
173
  }
120
174
  const subagentParents = new Set();
121
175
  for (const record of corpus.values()) {
@@ -123,11 +177,11 @@ async function prepareListing(ctx, signal) {
123
177
  subagentParents.add(record.header.parentSession);
124
178
  }
125
179
  }
126
- return { projections, persistence, cache, corpus, subagentParents };
180
+ return { projections, query, cache, corpus, subagentParents };
127
181
  }
128
182
  /** Resolve projection-backed rows for aligned candidates with bounded cold reads. */
129
183
  async function resolveCandidateRows(candidates, listing, signal) {
130
- const { projections, persistence, cache, subagentParents } = listing;
184
+ const { projections, query, cache, subagentParents } = listing;
131
185
  const rows = Array.from({ length: candidates.length });
132
186
  const coldReads = [];
133
187
  candidates.forEach((candidate, index) => {
@@ -136,34 +190,31 @@ async function resolveCandidateRows(candidates, listing, signal) {
136
190
  coldReads.push({ index, header: candidate.header });
137
191
  return;
138
192
  }
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.
193
+ // Read only the identity unit. A live child without an identity yet is the
194
+ // creation window before the establishing provider appends its descriptor.
142
195
  let identity;
143
196
  try {
144
- identity = projections.snapshot(candidate.live).values.subagent;
197
+ identity = projections.snapshot(candidate.live, ['subagent']).values.subagent;
145
198
  }
146
199
  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.
200
+ // A rejecting identity fold is deterministic data damage in this child;
201
+ // contain it as one diagnostic instead of failing the whole listing.
151
202
  rows[index] = { kind: 'diagnostic', id: childId, reason: 'corrupt' };
152
203
  return;
153
204
  }
154
205
  // The unit's serializable no-value sentinel is `null`; `undefined` can
155
206
  // only mean the key was dropped at a JSON boundary. Both are no value.
156
- if (identity === undefined || identity === null)
207
+ if (identity === undefined || identity === null
208
+ || !candidate.live.isOwnSeq(identity.seq))
157
209
  return;
158
210
  rows[index] = childRow(childId, identity, 'running', subagentParents.has(childId));
159
211
  });
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) {
212
+ // Cold candidates came from the query corpus and are resolved concurrently.
213
+ if (coldReads.length > 0) {
163
214
  const queue = [...coldReads];
164
215
  await Promise.all(Array.from({ length: Math.min(COLD_READ_CONCURRENCY, queue.length) }, async () => {
165
216
  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);
217
+ rows[job.index] = await resolveColdIdentity(query, cache, job.header, subagentParents.has(job.header.id), signal);
167
218
  }
168
219
  }));
169
220
  }
@@ -212,72 +263,82 @@ function compareCorpusRecords(a, b) {
212
263
  return a.header.createdAt - b.header.createdAt || a.header.id.localeCompare(b.header.id);
213
264
  }
214
265
  /**
215
- * Resolve one cold candidate down the remaining ladder: a durable
216
- * 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
+ * Resolve one cold candidate down the remaining ladder: an unseeded durable
267
+ * projection-cache row, otherwise one shared Session observation. An absent or transiently failed
268
+ * observation is one `unavailable` row retried on the next listing; an observation
269
+ * source naming another lifecycle, and a
221
270
  * settled log the fold cannot identify — or that makes any registered unit
222
271
  * throw — are final, so they report `corrupt`.
223
272
  */
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;
273
+ async function resolveColdIdentity(query, cache, header, hasChildren, signal) {
274
+ const env_1 = { stack: [], error: void 0, hasError: false };
275
+ try {
276
+ const childId = header.id;
277
+ // 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.
281
+ if (cache !== undefined && !header.isSeeded) {
282
+ let cached;
283
+ try {
284
+ cached = cache.cachedSnapshot(header, SessionLogOffset(0), ['subagent'])?.values.subagent;
285
+ }
286
+ catch {
287
+ // Unlike the preparation fold below, a throwing cache read renders no
288
+ // verdict: the cache is derived data, so its damage (a poisoned stored
289
+ // row of ANY unit) silently falls through to the authoritative re-fold.
290
+ cached = undefined;
291
+ }
292
+ // An unseeded child's descriptor is owned at every valid seq. Everything
293
+ // else falls through to preparation: an absent key and the `null`
294
+ // sentinel, whose verdict belongs to the authoritative re-fold, not to a
295
+ // derived row.
296
+ if (cached !== undefined && cached !== null) {
297
+ return childRow(childId, cached, 'inactive', hasChildren);
298
+ }
230
299
  }
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;
300
+ assertListingNotCancelled(signal);
301
+ let observation;
302
+ try {
303
+ observation = await query.observeSession(childId, {
304
+ ...(signal === undefined ? {} : { signal }),
305
+ });
236
306
  }
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);
307
+ catch (error) {
308
+ // Per-child isolation: durable corruption is stable; absence and backend
309
+ // failures remain retryable. Either way, the listing itself still succeeds.
310
+ assertListingNotCancelled(signal);
311
+ return {
312
+ kind: 'diagnostic',
313
+ id: childId,
314
+ reason: sessionQueryCode(error) === 'SESSION_QUERY_CORRUPT_SESSION'
315
+ || sessionQueryCode(error) === 'SESSION_QUERY_SOURCE_CONFLICT'
316
+ ? 'corrupt'
317
+ : 'unavailable',
318
+ };
247
319
  }
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.
320
+ const ownedObservation = __addDisposableResource(env_1, observation, false);
257
321
  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;
322
+ // A session id names a slot, not a lifecycle: a child deleted and
323
+ // re-published under another owner between the enumeration and this read
324
+ // must not leak into the old parent's listing.
325
+ if (!sameLifecycle(ownedObservation.header, header)) {
326
+ return { kind: 'diagnostic', id: childId, reason: 'corrupt' };
327
+ }
328
+ const identity = ownedObservation.projections?.values.subagent;
329
+ if (identity === undefined || identity === null
330
+ || identity.seq < ownedObservation.inheritedEventCount) {
331
+ return { kind: 'diagnostic', id: childId, reason: 'corrupt' };
332
+ }
333
+ return childRow(childId, identity, 'inactive', hasChildren);
270
334
  }
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' };
335
+ catch (e_1) {
336
+ env_1.error = e_1;
337
+ env_1.hasError = true;
276
338
  }
277
- if (identity === undefined || identity === null) {
278
- return { kind: 'diagnostic', id: childId, reason: 'corrupt' };
339
+ finally {
340
+ __disposeResources(env_1);
279
341
  }
280
- return childRow(childId, identity, 'inactive', hasChildren);
281
342
  }
282
343
  /** Materialize one served identity as its child row. */
283
344
  function childRow(id, identity, activity, hasChildren) {
@@ -301,7 +362,8 @@ function childRow(id, identity, activity, hasChildren) {
301
362
  }
302
363
  /** Immutable header fields that distinguish one session lifecycle from another under the same id. */
303
364
  const LIFECYCLE_WITNESS_KEYS = [
304
- 'version', 'id', 'createdAt', 'cwd', 'parentSession', 'seedLength', 'delegationDepth',
365
+ 'version', 'id', 'createdAt', 'cwd', 'parentSession', 'isSeeded', 'delegationDepth',
366
+ 'origin', 'agentPreset',
305
367
  ];
306
368
  /** Whether an inspected log still belongs to the enumerated lifecycle. */
307
369
  function sameLifecycle(meta, expected) {
@@ -313,4 +375,7 @@ function assertListingNotCancelled(signal) {
313
375
  throw new SubagentError('subagent listing was cancelled', 'CANCELLED');
314
376
  }
315
377
  }
378
+ function sessionQueryCode(error) {
379
+ return error instanceof Error && 'code' in error ? error.code : undefined;
380
+ }
316
381
  //# 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;
@@ -69,6 +69,8 @@ export interface RunResultSettlement {
69
69
  attempt: () => Promise<SubagentResult>;
70
70
  /** Snapshot the provider exposes when cancellation or failure wins settlement. */
71
71
  collectOutput: () => ContentBlock[];
72
+ /** Snapshot safe provider-authored detail when a failure wins settlement. */
73
+ collectDiagnostic?: (() => string | undefined) | undefined;
72
74
  /** Whether local cancellation settled before the attempt's outcome is observed. */
73
75
  cancelled: () => boolean;
74
76
  /** Diagnostic sink for a failure flattened to a stop reason; a throw from it is contained. */
@@ -83,7 +85,8 @@ export interface RunResultSettlement {
83
85
  * rejects after publication. A normally completed or rejected attempt resolves
84
86
  * as `aborted` when cancellation already settled locally; another rejection is
85
87
  * flattened to `stopReason: 'error'` through the contained diagnostic sink.
86
- * 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.
87
90
  * @param parts - the attempt, output snapshot, cancellation state, sink, and signal wiring.
88
91
  * @returns the terminal result (never a rejection).
89
92
  */