@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.
- package/README.i18n.yaml +2 -2
- package/README.md +108 -76
- package/README.zh.md +112 -80
- package/lib/index.js +1258 -718
- package/lib/typert.host.d.ts +3 -0
- package/lib/typert.host.js +923 -0
- package/lib/typert.remote-client.d.ts +27 -0
- package/lib/typert.remote-client.js +159 -0
- package/lib/types/assistant-output.d.ts +3 -3
- package/lib/types/assistant-output.js +8 -4
- package/lib/types/child-agent.d.ts +16 -5
- package/lib/types/child-agent.js +51 -13
- package/lib/types/client.d.ts +2 -1
- package/lib/types/client.js +1 -1
- package/lib/types/continuation.d.ts +100 -72
- package/lib/types/continuation.js +439 -169
- package/lib/types/control-types.d.ts +144 -0
- package/lib/types/control-types.js +9 -0
- package/lib/types/control.d.ts +67 -0
- package/lib/types/control.js +115 -0
- package/lib/types/descriptor-seed.d.ts +1 -1
- package/lib/types/descriptor-seed.js +1 -1
- package/lib/types/descriptor.d.ts +6 -1
- package/lib/types/descriptor.js +6 -2
- package/lib/types/index.d.ts +103 -69
- package/lib/types/index.js +436 -287
- package/lib/types/internal.d.ts +59 -0
- package/lib/types/internal.js +58 -0
- package/lib/types/lifecycle.js +4 -3
- package/lib/types/list-children.d.ts +12 -59
- package/lib/types/list-children.js +166 -101
- package/lib/types/out-of-process.d.ts +5 -2
- package/lib/types/out-of-process.js +42 -4
- package/lib/types/projection-types.d.ts +4 -3
- package/lib/types/projection.d.ts +55 -8
- package/lib/types/projection.js +33 -17
- package/lib/types/run-settlement.js +17 -6
- package/lib/types/types.d.ts +25 -0
- package/package.json +67 -37
- package/lib/types/activation-setup-registry.d.ts +0 -57
- 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
|
package/lib/types/lifecycle.js
CHANGED
|
@@ -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.
|
|
105
|
+
boundary = child.session.seq;
|
|
105
106
|
emit('subagent/start', identity, parent);
|
|
106
107
|
},
|
|
107
108
|
capture: (child) => {
|
|
108
|
-
const own = child.session.
|
|
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
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
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
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* is the single classification
|
|
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
|
-
|
|
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
|
|
86
|
-
*
|
|
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
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
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
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* is the single classification
|
|
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
|
|
21
|
-
*
|
|
22
|
-
*
|
|
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
|
|
31
|
-
*
|
|
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
|
|
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
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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,
|
|
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,
|
|
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
|
-
//
|
|
140
|
-
//
|
|
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
|
-
//
|
|
148
|
-
//
|
|
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
|
|
161
|
-
|
|
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(
|
|
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:
|
|
216
|
-
* projection-cache row
|
|
217
|
-
*
|
|
218
|
-
*
|
|
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(
|
|
225
|
-
const
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
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
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
300
|
+
assertListingNotCancelled(signal);
|
|
301
|
+
let observation;
|
|
302
|
+
try {
|
|
303
|
+
observation = await query.observeSession(childId, {
|
|
304
|
+
...(signal === undefined ? {} : { signal }),
|
|
305
|
+
});
|
|
236
306
|
}
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
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
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
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
|
-
|
|
273
|
-
|
|
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
|
-
|
|
278
|
-
|
|
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', '
|
|
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
|
|
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
|
*/
|