@morlay/dsh-subagent 0.0.6 → 0.0.8
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.
- package/README.md +6 -5
- package/dist/index.d.mts +334 -1019
- package/dist/index.mjs +2000 -7392
- package/package.json +41 -30
- package/src/continuation-messages.ts +185 -0
- package/src/delegation-context.ts +39 -0
- package/src/index.ts +627 -0
- package/src/manager.ts +1515 -0
- package/src/rows.ts +44 -0
- package/src/vendor/dsh-subagent/activation.ts +200 -0
- package/src/vendor/dsh-subagent/archive-admission.ts +97 -0
- package/src/vendor/dsh-subagent/assistant-output.ts +77 -0
- package/src/vendor/dsh-subagent/catalog.ts +196 -0
- package/src/vendor/dsh-subagent/child-agent.ts +300 -0
- package/src/vendor/dsh-subagent/continuation-messages.ts +177 -0
- package/src/vendor/dsh-subagent/control-types.ts +146 -0
- package/src/vendor/dsh-subagent/control.ts +123 -0
- package/src/vendor/dsh-subagent/depth.ts +55 -0
- package/src/vendor/dsh-subagent/descriptor.ts +275 -0
- package/src/vendor/dsh-subagent/error.ts +15 -0
- package/src/vendor/dsh-subagent/index.ts +718 -0
- package/src/vendor/dsh-subagent/internal.ts +114 -0
- package/src/vendor/dsh-subagent/lifecycle.ts +172 -0
- package/src/vendor/dsh-subagent/list-children.ts +141 -0
- package/src/vendor/dsh-subagent/manager.ts +1649 -0
- package/src/vendor/dsh-subagent/out-of-process.ts +219 -0
- package/src/vendor/dsh-subagent/projection-types.ts +84 -0
- package/src/vendor/dsh-subagent/projection.ts +205 -0
- package/src/vendor/dsh-subagent/structured.ts +164 -0
- package/src/vendor/dsh-subagent/types.ts +356 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Continuation integration markers and host adapters outside the public
|
|
3
|
+
* Service Definition and model-facing Agent messaging contract.
|
|
4
|
+
* @module @deepseek-ai/dsh-subagent/internal
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { Agent } from "@deepseek-ai/dsh-agent";
|
|
8
|
+
import type { ContentBlock, MessageId, MessageSource } from "@deepseek-ai/dsh-llm";
|
|
9
|
+
import type { SessionId } from "@deepseek-ai/dsh-session";
|
|
10
|
+
import type { ToolDefinition } from "@deepseek-ai/dsh-tools";
|
|
11
|
+
import type SubagentRuntime from "./index.ts";
|
|
12
|
+
import type { SubagentDelivery } from "./control-types.ts";
|
|
13
|
+
|
|
14
|
+
/** Process-stable identity carried only by the standard adjacent-Agent messaging tool. */
|
|
15
|
+
export const adjacentAgentSendMessageTool = Symbol.for("dsh.subagent.adjacentAgentSendMessageTool");
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Mark the standard adjacent-Agent messaging tool without changing its model-visible schema.
|
|
19
|
+
* @param definition - the standard `send_message` definition.
|
|
20
|
+
* @returns the same definition with its internal identity installed.
|
|
21
|
+
*/
|
|
22
|
+
export function markAdjacentAgentSendMessageTool(definition: ToolDefinition): ToolDefinition {
|
|
23
|
+
Object.defineProperty(definition, adjacentAgentSendMessageTool, { value: true });
|
|
24
|
+
return definition;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Test whether one visible definition is the standard adjacent-Agent messaging tool.
|
|
29
|
+
* @param definition - the scope-resolved `send_message` candidate.
|
|
30
|
+
* @returns whether the definition carries the internal standard-tool identity.
|
|
31
|
+
*/
|
|
32
|
+
export function isAdjacentAgentSendMessageTool(definition: ToolDefinition | undefined): boolean {
|
|
33
|
+
return (
|
|
34
|
+
definition !== undefined &&
|
|
35
|
+
(definition as ToolDefinition & { [adjacentAgentSendMessageTool]?: true })[
|
|
36
|
+
adjacentAgentSendMessageTool
|
|
37
|
+
] === true
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Process-stable symbol-keyed host delivery shared by the bundled runtime
|
|
43
|
+
* entry and this unbundled internal subpath.
|
|
44
|
+
* @internal
|
|
45
|
+
*/
|
|
46
|
+
export const deliverSubagentPrompt = Symbol.for("dsh.subagent.deliverPrompt");
|
|
47
|
+
|
|
48
|
+
/** Runtime face required by the host-only prompt adapters. */
|
|
49
|
+
export interface HostPromptDeliverer {
|
|
50
|
+
[deliverSubagentPrompt](
|
|
51
|
+
parent: Agent,
|
|
52
|
+
childId: SessionId,
|
|
53
|
+
content: ContentBlock[],
|
|
54
|
+
source: MessageSource,
|
|
55
|
+
signal: AbortSignal,
|
|
56
|
+
delivery: SubagentDelivery,
|
|
57
|
+
): Promise<MessageId>;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Queue one host-protocol message without exposing another Service operation.
|
|
62
|
+
* @param runtime - subagent runtime owning continuation residency.
|
|
63
|
+
* @param parent - exact live direct parent authorizing delivery.
|
|
64
|
+
* @param childId - durable direct-child session id.
|
|
65
|
+
* @param content - host-authored content to deliver.
|
|
66
|
+
* @param source - durable host-protocol source descriptor.
|
|
67
|
+
* @param signal - caller cancellation before inbox acceptance.
|
|
68
|
+
* @returns the accepted message's inbox id.
|
|
69
|
+
*/
|
|
70
|
+
export function queueHostSubagentPrompt(
|
|
71
|
+
runtime: SubagentRuntime,
|
|
72
|
+
parent: Agent,
|
|
73
|
+
childId: SessionId,
|
|
74
|
+
content: ContentBlock[],
|
|
75
|
+
source: MessageSource,
|
|
76
|
+
signal: AbortSignal,
|
|
77
|
+
): Promise<MessageId> {
|
|
78
|
+
return (runtime as unknown as HostPromptDeliverer)[deliverSubagentPrompt](
|
|
79
|
+
parent,
|
|
80
|
+
childId,
|
|
81
|
+
content,
|
|
82
|
+
source,
|
|
83
|
+
signal,
|
|
84
|
+
"queue",
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Steer one host-protocol message without exposing another Service operation.
|
|
90
|
+
* @param runtime - subagent runtime owning continuation residency.
|
|
91
|
+
* @param parent - exact live direct parent authorizing delivery.
|
|
92
|
+
* @param childId - durable direct-child session id.
|
|
93
|
+
* @param content - host-authored content to deliver.
|
|
94
|
+
* @param source - durable host-protocol source descriptor.
|
|
95
|
+
* @param signal - caller cancellation before inbox acceptance.
|
|
96
|
+
* @returns the accepted message's inbox id.
|
|
97
|
+
*/
|
|
98
|
+
export function steerHostSubagentPrompt(
|
|
99
|
+
runtime: SubagentRuntime,
|
|
100
|
+
parent: Agent,
|
|
101
|
+
childId: SessionId,
|
|
102
|
+
content: ContentBlock[],
|
|
103
|
+
source: MessageSource,
|
|
104
|
+
signal: AbortSignal,
|
|
105
|
+
): Promise<MessageId> {
|
|
106
|
+
return (runtime as unknown as HostPromptDeliverer)[deliverSubagentPrompt](
|
|
107
|
+
parent,
|
|
108
|
+
childId,
|
|
109
|
+
content,
|
|
110
|
+
source,
|
|
111
|
+
signal,
|
|
112
|
+
"steer",
|
|
113
|
+
);
|
|
114
|
+
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lifecycle-edge publication for managed subagent activations.
|
|
3
|
+
*
|
|
4
|
+
* Public event payloads live in `./types.ts`; the package-private observer
|
|
5
|
+
* retains only the identity shared by each start/end pair.
|
|
6
|
+
*
|
|
7
|
+
* @module @deepseek-ai/dsh-subagent/lifecycle
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { randomUUID } from "node:crypto";
|
|
11
|
+
import type { Context } from "@deepseek-ai/cordis";
|
|
12
|
+
import type { Agent } from "@deepseek-ai/dsh-agent";
|
|
13
|
+
import { foldConsumedWork } from "@deepseek-ai/dsh-agent";
|
|
14
|
+
import type { SessionEvent, SessionId } from "@deepseek-ai/dsh-session";
|
|
15
|
+
import { SubagentRunId } from "./types.ts";
|
|
16
|
+
import type { SubagentResult, SubagentRunEndInfo, SubagentRunInfo } from "./types.ts";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Lifecycle observer for one activation's residency epoch. Package-private: the
|
|
20
|
+
* continuation manager is the only consumer, and its call ordering is an
|
|
21
|
+
* in-package contract rather than a published extension point.
|
|
22
|
+
*/
|
|
23
|
+
export interface ActivationObserver {
|
|
24
|
+
/**
|
|
25
|
+
* Publish the start edge once the epoch is resident.
|
|
26
|
+
* @param child - the resident local Agent, or undefined for an external execution.
|
|
27
|
+
*/
|
|
28
|
+
start(child?: Agent): void;
|
|
29
|
+
/**
|
|
30
|
+
* Publish the terminal edge exactly once, pairing this epoch's {@link start},
|
|
31
|
+
* after the disposal outcome is known. Called only for a resident epoch: a
|
|
32
|
+
* failure before residency publishes no edge, because inventing one would
|
|
33
|
+
* report a lifecycle the child never had.
|
|
34
|
+
* @param result - the captured result, with teardown failures applied.
|
|
35
|
+
*/
|
|
36
|
+
settle(result: SubagentResult): void;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Publish one lifecycle edge with per-listener exception containment. Run edges
|
|
41
|
+
* carry the delegating parent that keys scoped dispatch; provider removal has no
|
|
42
|
+
* parent carrier and reaches listeners unscoped.
|
|
43
|
+
*
|
|
44
|
+
* The service owns this closure because scoped dispatch keys its carrier by the
|
|
45
|
+
* exact service instance, whose own context filter composes into the carrier;
|
|
46
|
+
* a narrowed stand-in would silently change scope filtering.
|
|
47
|
+
*/
|
|
48
|
+
export type LifecycleEmitter = {
|
|
49
|
+
(name: "subagent/start", info: SubagentRunInfo, parent: Agent): void;
|
|
50
|
+
(name: "subagent/end", info: SubagentRunEndInfo, parent: Agent): void;
|
|
51
|
+
(name: "subagent/provider-removed", info: string): void;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Build the contained lifecycle emitter this seam publishes every edge through.
|
|
56
|
+
* Every listener is independently contained: a synchronous throw or a rejected
|
|
57
|
+
* returned promise is logged without starving peer listeners, changing the run,
|
|
58
|
+
* or — for provider removal, which fires from a disposer — breaking teardown.
|
|
59
|
+
* @param ctx - the service's own context, owning dispatch and the logger.
|
|
60
|
+
* @param carrier - resolve the scoped dispatch carrier for one delegating parent.
|
|
61
|
+
* @returns the emitter both observers and the provider registry publish through.
|
|
62
|
+
*/
|
|
63
|
+
export function createLifecycleEmitter(
|
|
64
|
+
ctx: Context,
|
|
65
|
+
carrier: (parent: Agent) => object,
|
|
66
|
+
): LifecycleEmitter {
|
|
67
|
+
return (
|
|
68
|
+
name: "subagent/start" | "subagent/end" | "subagent/provider-removed",
|
|
69
|
+
info: SubagentRunInfo | SubagentRunEndInfo | string,
|
|
70
|
+
parent?: Agent,
|
|
71
|
+
): void => {
|
|
72
|
+
const dispatchArgs: unknown[] =
|
|
73
|
+
parent === undefined ? [name, info] : [carrier(parent), name, info];
|
|
74
|
+
for (const callback of ctx.events.dispatch("emit", dispatchArgs)) {
|
|
75
|
+
try {
|
|
76
|
+
const returned: unknown = callback(info);
|
|
77
|
+
void Promise.resolve(returned).catch((error: unknown) => {
|
|
78
|
+
ctx.logger.warn(`subagent: ${name} listener rejected: ${renderThrown(error)}`);
|
|
79
|
+
});
|
|
80
|
+
} catch (error: unknown) {
|
|
81
|
+
ctx.logger.warn(`subagent: ${name} listener threw: ${renderThrown(error)}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Build the observer for one local or external activation's residency epoch. Creation failure before residency emits no lifecycle edge.
|
|
89
|
+
* @param emit - the contained lifecycle emitter.
|
|
90
|
+
* @param provider - the provider name recorded in the durable descriptor.
|
|
91
|
+
* @param childId - the durable child session id.
|
|
92
|
+
* @param parent - the exact live direct parent keying scoped dispatch.
|
|
93
|
+
* @returns the observer whose edges this epoch publishes.
|
|
94
|
+
*/
|
|
95
|
+
export function createActivationObserver(
|
|
96
|
+
emit: LifecycleEmitter,
|
|
97
|
+
provider: string,
|
|
98
|
+
childId: SessionId,
|
|
99
|
+
parent: Agent,
|
|
100
|
+
): ActivationObserver {
|
|
101
|
+
const identity = { runId: SubagentRunId(randomUUID()), provider, id: childId, local: false };
|
|
102
|
+
return {
|
|
103
|
+
start: (child?: Agent): void => {
|
|
104
|
+
identity.local = child !== undefined;
|
|
105
|
+
emit("subagent/start", identity, parent);
|
|
106
|
+
},
|
|
107
|
+
settle: ({ stopReason, output }: SubagentResult): void => {
|
|
108
|
+
emit(
|
|
109
|
+
"subagent/end",
|
|
110
|
+
{
|
|
111
|
+
...identity,
|
|
112
|
+
stopReason,
|
|
113
|
+
...(output.length === 0 ? {} : { lastAssistantMessage: output }),
|
|
114
|
+
},
|
|
115
|
+
parent,
|
|
116
|
+
);
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Why this child's epoch ended, for the terminal lifecycle edge and the
|
|
123
|
+
* manager's own parent delivery. The child's own log is authoritative:
|
|
124
|
+
* teardown succeeding says nothing about whether the model errored, hit its
|
|
125
|
+
* token ceiling, or was cancelled, so deriving the reason from disposal would
|
|
126
|
+
* report failed work as completed.
|
|
127
|
+
*
|
|
128
|
+
* {@link foldConsumedWork} supplies both halves the raw turn sequence cannot:
|
|
129
|
+
* which turn accounts for the work this epoch consumed, and whether accepted
|
|
130
|
+
* work was cancelled after it without any turn opening over it. A recorded
|
|
131
|
+
* failure still wins over a cancellation — stopping a child that had already
|
|
132
|
+
* failed does not turn its failure into a cancellation.
|
|
133
|
+
* @param events - this epoch's own event suffix.
|
|
134
|
+
* @returns its terminal stop reason; `completed` only for an epoch that both
|
|
135
|
+
* closed cleanly and had nothing left to run.
|
|
136
|
+
*/
|
|
137
|
+
export function epochStopReason(events: readonly SessionEvent[]): SubagentResult["stopReason"] {
|
|
138
|
+
const { end, droppedUnrun } = foldConsumedWork(events);
|
|
139
|
+
switch (end?.data.reason.kind) {
|
|
140
|
+
case "max-tokens":
|
|
141
|
+
return "max-tokens";
|
|
142
|
+
case "aborted":
|
|
143
|
+
case "interrupted":
|
|
144
|
+
return "aborted";
|
|
145
|
+
case "error":
|
|
146
|
+
return "error";
|
|
147
|
+
// A pre-step rejection — a hook deny, a policy plugin — discarded input
|
|
148
|
+
// this epoch had claimed: the work was declined, not done.
|
|
149
|
+
case "blocked":
|
|
150
|
+
return "refusal";
|
|
151
|
+
// A clean ending and no accounting turn at all share one rule: the epoch
|
|
152
|
+
// finished what it was given unless a cancelled queue says otherwise.
|
|
153
|
+
case undefined:
|
|
154
|
+
case "completed":
|
|
155
|
+
return droppedUnrun ? "aborted" : "completed";
|
|
156
|
+
/* v8 ignore next 4 -- `forked` appears only in constructor seed history, while
|
|
157
|
+
* this function reads an epoch-owned suffix. `TurnEndReason` is merge-extensible,
|
|
158
|
+
* so a backend-added variant cannot be listed; treating an unnameable reason as
|
|
159
|
+
* success would report failed work as completed. */
|
|
160
|
+
default:
|
|
161
|
+
return "error";
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** Render any listener-thrown value without letting coercion escape containment. */
|
|
166
|
+
function renderThrown(value: unknown): string {
|
|
167
|
+
try {
|
|
168
|
+
return value instanceof Error ? `${value.name}: ${value.message}` : String(value);
|
|
169
|
+
} catch {
|
|
170
|
+
return "<unrenderable thrown value>";
|
|
171
|
+
}
|
|
172
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Direct-child and recursive descendant discovery from parent-owned catalogs.
|
|
3
|
+
* Each catalog read releases its Session observation before the next branch.
|
|
4
|
+
* @module @deepseek-ai/dsh-subagent
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { Context } from "@deepseek-ai/cordis";
|
|
8
|
+
import type { SessionId } from "@deepseek-ai/dsh-session";
|
|
9
|
+
import type { SubagentListEntry } from "./control-types.ts";
|
|
10
|
+
import { SubagentError } from "./error.ts";
|
|
11
|
+
import type { SubagentCatalogEntry } from "./projection-types.ts";
|
|
12
|
+
|
|
13
|
+
export type { SubagentListEntry } from "./control-types.ts";
|
|
14
|
+
|
|
15
|
+
/** One catalog descendant with its direct parent and edge distance from the requested root. */
|
|
16
|
+
export type SubagentDescendantListEntry = SubagentListEntry & {
|
|
17
|
+
/** Parent whose catalog contains this child. */
|
|
18
|
+
readonly parentId: SessionId;
|
|
19
|
+
/** Edge distance from the requested root; direct children are `1`. */
|
|
20
|
+
readonly depth: number;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Read one parent's durable catalog through a live-preferred Session observation.
|
|
25
|
+
* @param ctx - context carrying the Session query service.
|
|
26
|
+
* @param parentSessionId - parent whose direct children are requested.
|
|
27
|
+
* @param signal - cancellation forwarded to the Session observation.
|
|
28
|
+
* @returns direct-child rows in parent catalog event order.
|
|
29
|
+
* @throws {@link SubagentError} when query or catalog projection is unavailable.
|
|
30
|
+
*/
|
|
31
|
+
export async function listChildren(
|
|
32
|
+
ctx: Context,
|
|
33
|
+
parentSessionId: SessionId,
|
|
34
|
+
signal?: AbortSignal,
|
|
35
|
+
): Promise<SubagentCatalogEntry[]> {
|
|
36
|
+
const query = ctx.get("sessionQuery");
|
|
37
|
+
if (query === undefined) {
|
|
38
|
+
throw new SubagentError(
|
|
39
|
+
"listing subagents requires the sessionQuery service (load @deepseek-ai/dsh-session-query)",
|
|
40
|
+
"SUBAGENT_CONTROL_QUERY_UNAVAILABLE",
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
using parent = await query.observeSession(parentSessionId, {
|
|
44
|
+
...(signal === undefined ? {} : { signal }),
|
|
45
|
+
});
|
|
46
|
+
const entries = parent.projections?.values.subagentCatalog;
|
|
47
|
+
if (entries === undefined) {
|
|
48
|
+
throw new SubagentError(
|
|
49
|
+
"listing subagents requires the registered subagentCatalog projection",
|
|
50
|
+
"SUBAGENT_CONTROL_PROJECTIONS_UNAVAILABLE",
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
return entries;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Walk reachable parent catalogs in stable pre-order without loading Agents.
|
|
58
|
+
* @see SubagentRuntime.listDescendants for failure and cancellation semantics.
|
|
59
|
+
* @param ctx - context carrying the Session store and query service.
|
|
60
|
+
* @param rootSessionId - parent whose catalog starts the traversal.
|
|
61
|
+
* @param signal - cancellation checked around each catalog read.
|
|
62
|
+
* @returns children and branch diagnostics with catalog parent and depth.
|
|
63
|
+
*/
|
|
64
|
+
export async function listDescendants(
|
|
65
|
+
ctx: Context,
|
|
66
|
+
rootSessionId: SessionId,
|
|
67
|
+
signal?: AbortSignal,
|
|
68
|
+
): Promise<SubagentDescendantListEntry[]> {
|
|
69
|
+
const sessions = ctx.get("sessions");
|
|
70
|
+
if (sessions === undefined) {
|
|
71
|
+
throw new SubagentError(
|
|
72
|
+
"listing subagents requires the session store (load @deepseek-ai/dsh-session)",
|
|
73
|
+
"SUBAGENT_CONTROL_SESSION_STORE_UNAVAILABLE",
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
const readChildren = async (id: SessionId): Promise<SubagentCatalogEntry[]> => {
|
|
77
|
+
assertListingNotCancelled(signal);
|
|
78
|
+
let children: SubagentCatalogEntry[];
|
|
79
|
+
try {
|
|
80
|
+
children = await listChildren(ctx, id, signal);
|
|
81
|
+
} catch (error) {
|
|
82
|
+
assertListingNotCancelled(signal);
|
|
83
|
+
throw error;
|
|
84
|
+
}
|
|
85
|
+
assertListingNotCancelled(signal);
|
|
86
|
+
return children;
|
|
87
|
+
};
|
|
88
|
+
const stack = (await readChildren(rootSessionId))
|
|
89
|
+
.map((entry) => ({ entry, parentId: rootSessionId, depth: 1 }))
|
|
90
|
+
.reverse();
|
|
91
|
+
const visited = new Set<SessionId>([rootSessionId]);
|
|
92
|
+
const result: SubagentDescendantListEntry[] = [];
|
|
93
|
+
for (let position = stack.pop(); position !== undefined; position = stack.pop()) {
|
|
94
|
+
const { entry, parentId, depth } = position;
|
|
95
|
+
if (visited.has(entry.id)) continue;
|
|
96
|
+
visited.add(entry.id);
|
|
97
|
+
let children: SubagentCatalogEntry[];
|
|
98
|
+
try {
|
|
99
|
+
children = entry.mode === "external" ? [] : await readChildren(entry.id);
|
|
100
|
+
} catch (error) {
|
|
101
|
+
if (error instanceof SubagentError) throw error;
|
|
102
|
+
const code = error instanceof Error && "code" in error ? error.code : undefined;
|
|
103
|
+
result.push({
|
|
104
|
+
kind: "diagnostic",
|
|
105
|
+
id: entry.id,
|
|
106
|
+
parentId,
|
|
107
|
+
depth,
|
|
108
|
+
reason:
|
|
109
|
+
code === "SESSION_QUERY_CORRUPT_SESSION" || code === "SESSION_QUERY_SOURCE_CONFLICT"
|
|
110
|
+
? "corrupt"
|
|
111
|
+
: "unavailable",
|
|
112
|
+
});
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
if (entry.mode === "unknown") {
|
|
116
|
+
result.push({ kind: "diagnostic", id: entry.id, parentId, depth, reason: "unsupported" });
|
|
117
|
+
} else {
|
|
118
|
+
const { createdAt: _createdAt, ...identity } = entry;
|
|
119
|
+
result.push({
|
|
120
|
+
...identity,
|
|
121
|
+
kind: "child",
|
|
122
|
+
parentId,
|
|
123
|
+
depth,
|
|
124
|
+
activity: sessions.get(entry.id) === undefined ? "inactive" : "running",
|
|
125
|
+
hasChildren: children.length > 0,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
// Catalog event order defines siblings; the stack visits the first one next.
|
|
129
|
+
for (const child of [...children].reverse()) {
|
|
130
|
+
stack.push({ entry: child, parentId: entry.id, depth: depth + 1 });
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return result;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** Stop the complete traversal when its caller cancels. */
|
|
137
|
+
function assertListingNotCancelled(signal: AbortSignal | undefined): void {
|
|
138
|
+
if (signal?.aborted) {
|
|
139
|
+
throw new SubagentError("subagent listing was cancelled", "CANCELLED");
|
|
140
|
+
}
|
|
141
|
+
}
|