@narumitw/pi-subagents 2.0.0 → 2.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -8
- package/dist/chunks/auto-transport-SY2VHUFH.ts +109 -0
- package/dist/chunks/auto-transport-SY2VHUFH.ts.map +7 -0
- package/dist/chunks/capability-grant-CGEWOEKE.ts +19 -0
- package/dist/chunks/capability-grant-CGEWOEKE.ts.map +7 -0
- package/dist/chunks/chunk-434NII74.ts +282 -0
- package/dist/chunks/chunk-434NII74.ts.map +7 -0
- package/dist/chunks/chunk-47SK2QYC.ts +56 -0
- package/dist/chunks/chunk-47SK2QYC.ts.map +7 -0
- package/dist/chunks/chunk-6KJ34M6S.ts +159 -0
- package/dist/chunks/chunk-6KJ34M6S.ts.map +7 -0
- package/dist/chunks/chunk-7OBINKFM.ts +80 -0
- package/dist/chunks/chunk-7OBINKFM.ts.map +7 -0
- package/dist/chunks/chunk-7QPPBBXZ.ts +335 -0
- package/dist/chunks/chunk-7QPPBBXZ.ts.map +7 -0
- package/dist/chunks/chunk-AEUYS7JC.ts +73 -0
- package/dist/chunks/chunk-AEUYS7JC.ts.map +7 -0
- package/dist/chunks/chunk-BFK2Z4ZF.ts +189 -0
- package/dist/chunks/chunk-BFK2Z4ZF.ts.map +7 -0
- package/dist/chunks/chunk-BOAXY55Y.ts +153 -0
- package/dist/chunks/chunk-BOAXY55Y.ts.map +7 -0
- package/dist/chunks/chunk-C4L6P266.ts +22 -0
- package/dist/chunks/chunk-C4L6P266.ts.map +7 -0
- package/dist/chunks/chunk-DPPVEQAM.ts +29 -0
- package/dist/chunks/chunk-DPPVEQAM.ts.map +7 -0
- package/dist/chunks/chunk-DQMN4OYM.ts +913 -0
- package/dist/chunks/chunk-DQMN4OYM.ts.map +7 -0
- package/dist/chunks/chunk-DSOOH73Y.ts +238 -0
- package/dist/chunks/chunk-DSOOH73Y.ts.map +7 -0
- package/dist/chunks/chunk-EFBPISNT.ts +421 -0
- package/dist/chunks/chunk-EFBPISNT.ts.map +7 -0
- package/dist/chunks/chunk-EWWXFU7N.ts +150 -0
- package/dist/chunks/chunk-EWWXFU7N.ts.map +7 -0
- package/dist/chunks/chunk-F4L2UVSK.ts +380 -0
- package/dist/chunks/chunk-F4L2UVSK.ts.map +7 -0
- package/dist/chunks/chunk-F5YWVHQX.ts +1543 -0
- package/dist/chunks/chunk-F5YWVHQX.ts.map +7 -0
- package/dist/chunks/chunk-G3RSMSXJ.ts +63 -0
- package/dist/chunks/chunk-G3RSMSXJ.ts.map +7 -0
- package/dist/chunks/chunk-G6VZTCFH.ts +70 -0
- package/dist/chunks/chunk-G6VZTCFH.ts.map +7 -0
- package/dist/chunks/chunk-H3FP6DLR.ts +160 -0
- package/dist/chunks/chunk-H3FP6DLR.ts.map +7 -0
- package/dist/chunks/chunk-HA36LPCO.ts +130 -0
- package/dist/chunks/chunk-HA36LPCO.ts.map +7 -0
- package/dist/chunks/chunk-HKC4ES4B.ts +65 -0
- package/dist/chunks/chunk-HKC4ES4B.ts.map +7 -0
- package/dist/chunks/chunk-HOP5FXDT.ts +27 -0
- package/dist/chunks/chunk-HOP5FXDT.ts.map +7 -0
- package/dist/chunks/chunk-I2FAK44T.ts +324 -0
- package/dist/chunks/chunk-I2FAK44T.ts.map +7 -0
- package/dist/chunks/chunk-IGAMVWFN.ts +139 -0
- package/dist/chunks/chunk-IGAMVWFN.ts.map +7 -0
- package/dist/chunks/chunk-KMGKCEO4.ts +38 -0
- package/dist/chunks/chunk-KMGKCEO4.ts.map +7 -0
- package/dist/chunks/chunk-N7BLVXKK.ts +73 -0
- package/dist/chunks/chunk-N7BLVXKK.ts.map +7 -0
- package/dist/chunks/chunk-NIF42QMF.ts +297 -0
- package/dist/chunks/chunk-NIF42QMF.ts.map +7 -0
- package/dist/chunks/chunk-O4VO6JUJ.ts +52 -0
- package/dist/chunks/chunk-O4VO6JUJ.ts.map +7 -0
- package/dist/chunks/chunk-ONDTY4EL.ts +91 -0
- package/dist/chunks/chunk-ONDTY4EL.ts.map +7 -0
- package/dist/chunks/chunk-OVPHFGKO.ts +1769 -0
- package/dist/chunks/chunk-OVPHFGKO.ts.map +7 -0
- package/dist/chunks/chunk-P7OH4XMF.ts +60 -0
- package/dist/chunks/chunk-P7OH4XMF.ts.map +7 -0
- package/dist/chunks/chunk-PSK43Y6A.ts +339 -0
- package/dist/chunks/chunk-PSK43Y6A.ts.map +7 -0
- package/dist/chunks/chunk-QBORTI4W.ts +468 -0
- package/dist/chunks/chunk-QBORTI4W.ts.map +7 -0
- package/dist/chunks/chunk-QDVGH2X7.ts +171 -0
- package/dist/chunks/chunk-QDVGH2X7.ts.map +7 -0
- package/dist/chunks/chunk-RHRQMT7Q.ts +490 -0
- package/dist/chunks/chunk-RHRQMT7Q.ts.map +7 -0
- package/dist/chunks/chunk-RRR66UWR.ts +28 -0
- package/dist/chunks/chunk-RRR66UWR.ts.map +7 -0
- package/dist/chunks/chunk-RSUXZD6S.ts +275 -0
- package/dist/chunks/chunk-RSUXZD6S.ts.map +7 -0
- package/dist/chunks/chunk-RTCVYIZA.ts +221 -0
- package/dist/chunks/chunk-RTCVYIZA.ts.map +7 -0
- package/dist/chunks/chunk-S2IWVK3J.ts +44 -0
- package/dist/chunks/chunk-S2IWVK3J.ts.map +7 -0
- package/dist/chunks/chunk-SCZ33MYW.ts +52 -0
- package/dist/chunks/chunk-SCZ33MYW.ts.map +7 -0
- package/dist/chunks/chunk-SWGQLFSD.ts +60 -0
- package/dist/chunks/chunk-SWGQLFSD.ts.map +7 -0
- package/dist/chunks/chunk-UMBPMVJW.ts +22 -0
- package/dist/chunks/chunk-UMBPMVJW.ts.map +7 -0
- package/dist/chunks/chunk-UVFVHIOP.ts +735 -0
- package/dist/chunks/chunk-UVFVHIOP.ts.map +7 -0
- package/dist/chunks/completion-delivery-YPOWSSV3.ts +16 -0
- package/dist/chunks/completion-delivery-YPOWSSV3.ts.map +7 -0
- package/dist/chunks/config-status-J4GPWP46.ts +34 -0
- package/dist/chunks/config-status-J4GPWP46.ts.map +7 -0
- package/dist/chunks/config-ui-3IBMHTQB.ts +1146 -0
- package/dist/chunks/config-ui-3IBMHTQB.ts.map +7 -0
- package/dist/chunks/consult-NYXVDN5G.ts +518 -0
- package/dist/chunks/consult-NYXVDN5G.ts.map +7 -0
- package/dist/chunks/context-QDKLVQXE.ts +11 -0
- package/dist/chunks/context-QDKLVQXE.ts.map +7 -0
- package/dist/chunks/create-stateful-transport-4WKZHRSI.ts +130 -0
- package/dist/chunks/create-stateful-transport-4WKZHRSI.ts.map +7 -0
- package/dist/chunks/cwd-policy-NB6XZ5GF.ts +17 -0
- package/dist/chunks/cwd-policy-NB6XZ5GF.ts.map +7 -0
- package/dist/chunks/delegation-contract-WJPT4FVS.ts +24 -0
- package/dist/chunks/delegation-contract-WJPT4FVS.ts.map +7 -0
- package/dist/chunks/discovery-K25T3SH4.ts +12 -0
- package/dist/chunks/discovery-K25T3SH4.ts.map +7 -0
- package/dist/chunks/execution-DS2DEIQZ.ts +4034 -0
- package/dist/chunks/execution-DS2DEIQZ.ts.map +7 -0
- package/dist/chunks/in-process-transport-TEYAWSEC.ts +38 -0
- package/dist/chunks/in-process-transport-TEYAWSEC.ts.map +7 -0
- package/dist/chunks/inspect-UOQQ3KNL.ts +619 -0
- package/dist/chunks/inspect-UOQQ3KNL.ts.map +7 -0
- package/dist/chunks/peer-communication-KL36Z6CO.ts +294 -0
- package/dist/chunks/peer-communication-KL36Z6CO.ts.map +7 -0
- package/dist/chunks/persistence-VSGXAW4P.ts +344 -0
- package/dist/chunks/persistence-VSGXAW4P.ts.map +7 -0
- package/dist/chunks/registry-E6XPJB7L.ts +1324 -0
- package/dist/chunks/registry-E6XPJB7L.ts.map +7 -0
- package/dist/chunks/retained-semantic-state-7WXSGRJU.ts +81 -0
- package/dist/chunks/retained-semantic-state-7WXSGRJU.ts.map +7 -0
- package/dist/chunks/rpc-transport-Y6LA5BOP.ts +1154 -0
- package/dist/chunks/rpc-transport-Y6LA5BOP.ts.map +7 -0
- package/dist/chunks/runtime-policy-5YELCOVC.ts +15 -0
- package/dist/chunks/runtime-policy-5YELCOVC.ts.map +7 -0
- package/dist/chunks/semantic-snapshot-WPJ3UAFW.ts +19 -0
- package/dist/chunks/semantic-snapshot-WPJ3UAFW.ts.map +7 -0
- package/dist/chunks/spawn-idempotency-QGHO2WKC.ts +13 -0
- package/dist/chunks/spawn-idempotency-QGHO2WKC.ts.map +7 -0
- package/dist/chunks/stateful-lifecycle-JAR6K55H.ts +15 -0
- package/dist/chunks/stateful-lifecycle-JAR6K55H.ts.map +7 -0
- package/dist/chunks/subprocess-transport-C2XVR25R.ts +158 -0
- package/dist/chunks/subprocess-transport-C2XVR25R.ts.map +7 -0
- package/dist/chunks/workspace-WDKLN72J.ts +11 -0
- package/dist/chunks/workspace-WDKLN72J.ts.map +7 -0
- package/dist/index.ts +1057 -0
- package/dist/index.ts.map +7 -0
- package/package.json +8 -4
- package/src/index.ts +1 -1
- package/src/settings-reader.ts +208 -0
- package/src/settings.ts +22 -187
- package/src/stateful-registration.ts +1230 -0
- package/src/stateful-safety.ts +1 -1
- package/src/stateful.ts +2 -1234
- package/src/subagents-extension.ts +307 -0
- package/src/subagents.ts +1 -308
|
@@ -0,0 +1,1324 @@
|
|
|
1
|
+
// @generated by scripts/build-runtime.mjs; do not edit.
|
|
2
|
+
// @ts-nocheck -- generated JavaScript uses a .ts extension for Pi's Jiti loader.
|
|
3
|
+
import {
|
|
4
|
+
classifyStructuredOutcome
|
|
5
|
+
} from "./chunk-47SK2QYC.js";
|
|
6
|
+
import {
|
|
7
|
+
isCapabilityGrantActive,
|
|
8
|
+
revokeCapabilityGrant
|
|
9
|
+
} from "./chunk-HKC4ES4B.js";
|
|
10
|
+
import {
|
|
11
|
+
copyExecutionPlan,
|
|
12
|
+
rotateExecutionPlanGeneration
|
|
13
|
+
} from "./chunk-DSOOH73Y.js";
|
|
14
|
+
import {
|
|
15
|
+
ROOT_TASK_PATH,
|
|
16
|
+
deriveTaskName,
|
|
17
|
+
joinTaskPath,
|
|
18
|
+
resolveTaskPath,
|
|
19
|
+
validateTaskName,
|
|
20
|
+
validateTaskPath
|
|
21
|
+
} from "./chunk-N7BLVXKK.js";
|
|
22
|
+
import {
|
|
23
|
+
copyTurnTerminationReport
|
|
24
|
+
} from "./chunk-RTCVYIZA.js";
|
|
25
|
+
import {
|
|
26
|
+
validateTurnLimits
|
|
27
|
+
} from "./chunk-ONDTY4EL.js";
|
|
28
|
+
import {
|
|
29
|
+
parseAnyStructuredSubagentResult
|
|
30
|
+
} from "./chunk-RSUXZD6S.js";
|
|
31
|
+
import {
|
|
32
|
+
projectAgentRecords
|
|
33
|
+
} from "./chunk-KMGKCEO4.js";
|
|
34
|
+
import {
|
|
35
|
+
resolveStatefulLimits
|
|
36
|
+
} from "./chunk-7OBINKFM.js";
|
|
37
|
+
import "./chunk-DPPVEQAM.js";
|
|
38
|
+
import {
|
|
39
|
+
safeTerminalLine
|
|
40
|
+
} from "./chunk-SWGQLFSD.js";
|
|
41
|
+
import {
|
|
42
|
+
DEFAULT_MAX_CONTEXT_BYTES,
|
|
43
|
+
DEFAULT_MAX_OUTPUT_BYTES,
|
|
44
|
+
MAX_SUBAGENT_TIMEOUT_MS,
|
|
45
|
+
MAX_TOOL_MESSAGE_BYTES,
|
|
46
|
+
truncateUtf8
|
|
47
|
+
} from "./chunk-H3FP6DLR.js";
|
|
48
|
+
|
|
49
|
+
// src/registry.ts
|
|
50
|
+
import { randomUUID } from "node:crypto";
|
|
51
|
+
|
|
52
|
+
// src/completion-routing.ts
|
|
53
|
+
function resolveCompletionRecipient(agent, getAgent) {
|
|
54
|
+
const visited = /* @__PURE__ */ new Set([agent.id]);
|
|
55
|
+
let parentId = agent.parentId;
|
|
56
|
+
while (parentId && !visited.has(parentId)) {
|
|
57
|
+
visited.add(parentId);
|
|
58
|
+
const parent = getAgent(parentId);
|
|
59
|
+
if (!parent) break;
|
|
60
|
+
if (parent.state !== "closed") {
|
|
61
|
+
return { recipientId: parent.id, recipientPath: parent.taskPath ?? parent.id };
|
|
62
|
+
}
|
|
63
|
+
parentId = parent.parentId;
|
|
64
|
+
}
|
|
65
|
+
return { recipientId: "root", recipientPath: ROOT_TASK_PATH };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// src/transport.ts
|
|
69
|
+
var FunctionTransport = class {
|
|
70
|
+
constructor(runner) {
|
|
71
|
+
this.runner = runner;
|
|
72
|
+
}
|
|
73
|
+
runner;
|
|
74
|
+
kind = "fake";
|
|
75
|
+
runTurn(agent, task, signal, onProgress) {
|
|
76
|
+
return this.runner(agent, task, signal, onProgress);
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
function normalizeTransport(transport) {
|
|
80
|
+
return typeof transport === "function" ? new FunctionTransport(transport) : transport;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// src/registry.ts
|
|
84
|
+
var DEFAULT_STATEFUL_LIMITS = resolveStatefulLimits();
|
|
85
|
+
var MAX_PENDING_COMPLETIONS_PER_AGENT = 20;
|
|
86
|
+
var INITIAL_PERSISTENCE_RETRY_DELAY_MS = 25;
|
|
87
|
+
var MAX_PERSISTENCE_RETRY_DELAY_MS = 1e3;
|
|
88
|
+
function positiveInteger(value, label) {
|
|
89
|
+
if (!Number.isSafeInteger(value) || value < 1) {
|
|
90
|
+
throw new Error(`${label} must be a positive safe integer`);
|
|
91
|
+
}
|
|
92
|
+
return value;
|
|
93
|
+
}
|
|
94
|
+
function nonNegativeInteger(value, label) {
|
|
95
|
+
if (!Number.isSafeInteger(value) || value < 0) {
|
|
96
|
+
throw new Error(`${label} must be a non-negative safe integer`);
|
|
97
|
+
}
|
|
98
|
+
return value;
|
|
99
|
+
}
|
|
100
|
+
function validateTurnTimeout(value) {
|
|
101
|
+
if (!Number.isSafeInteger(value) || value < 1 || value > MAX_SUBAGENT_TIMEOUT_MS) {
|
|
102
|
+
throw new Error(`Subagent timeoutMs must be between 1 and ${MAX_SUBAGENT_TIMEOUT_MS}`);
|
|
103
|
+
}
|
|
104
|
+
return value;
|
|
105
|
+
}
|
|
106
|
+
function clearCurrentTurn(agent) {
|
|
107
|
+
agent.currentTask = void 0;
|
|
108
|
+
agent.currentRunId = void 0;
|
|
109
|
+
agent.currentTurnGeneration = void 0;
|
|
110
|
+
agent.currentTimeoutMs = void 0;
|
|
111
|
+
agent.currentIdleTimeoutMs = void 0;
|
|
112
|
+
agent.currentMaxTurns = void 0;
|
|
113
|
+
agent.currentMaxToolCalls = void 0;
|
|
114
|
+
agent.currentMailboxMessageIds = void 0;
|
|
115
|
+
}
|
|
116
|
+
function waitAbortError() {
|
|
117
|
+
const error = new Error("Subagent wait was aborted");
|
|
118
|
+
error.name = "AbortError";
|
|
119
|
+
return error;
|
|
120
|
+
}
|
|
121
|
+
function waitForPersistenceRetry(milliseconds, signal) {
|
|
122
|
+
if (signal.aborted) return Promise.reject(signal.reason);
|
|
123
|
+
return new Promise((resolve, reject) => {
|
|
124
|
+
const onAbort = () => {
|
|
125
|
+
clearTimeout(timer);
|
|
126
|
+
reject(signal.reason);
|
|
127
|
+
};
|
|
128
|
+
const timer = setTimeout(() => {
|
|
129
|
+
signal.removeEventListener("abort", onAbort);
|
|
130
|
+
resolve();
|
|
131
|
+
}, milliseconds);
|
|
132
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
var AgentRegistry = class {
|
|
136
|
+
constructor(transport, options = {}) {
|
|
137
|
+
this.options = options;
|
|
138
|
+
this.transport = normalizeTransport(transport);
|
|
139
|
+
this.maxAgents = positiveInteger(
|
|
140
|
+
options.maxAgents ?? DEFAULT_STATEFUL_LIMITS.maxAgents,
|
|
141
|
+
"maxAgents"
|
|
142
|
+
);
|
|
143
|
+
this.maxActiveTurns = positiveInteger(
|
|
144
|
+
options.maxActiveTurns ?? DEFAULT_STATEFUL_LIMITS.maxActiveTurns,
|
|
145
|
+
"maxActiveTurns"
|
|
146
|
+
);
|
|
147
|
+
this.maxHistoryTurns = positiveInteger(options.maxHistoryTurns ?? 20, "maxHistoryTurns");
|
|
148
|
+
this.maxDepth = nonNegativeInteger(
|
|
149
|
+
options.maxDepth ?? DEFAULT_STATEFUL_LIMITS.maxDepth,
|
|
150
|
+
"maxDepth"
|
|
151
|
+
);
|
|
152
|
+
this.maxChildrenPerAgent = positiveInteger(
|
|
153
|
+
options.maxChildrenPerAgent ?? DEFAULT_STATEFUL_LIMITS.maxChildrenPerAgent,
|
|
154
|
+
"maxChildrenPerAgent"
|
|
155
|
+
);
|
|
156
|
+
this.maxMailboxMessages = positiveInteger(
|
|
157
|
+
options.maxMailboxMessages ?? 100,
|
|
158
|
+
"maxMailboxMessages"
|
|
159
|
+
);
|
|
160
|
+
this.maxMailboxMessageBytes = positiveInteger(
|
|
161
|
+
options.maxMailboxMessageBytes ?? 16 * 1024,
|
|
162
|
+
"maxMailboxMessageBytes"
|
|
163
|
+
);
|
|
164
|
+
this.maxTaskBytes = positiveInteger(
|
|
165
|
+
options.maxTaskBytes ?? DEFAULT_MAX_CONTEXT_BYTES,
|
|
166
|
+
"maxTaskBytes"
|
|
167
|
+
);
|
|
168
|
+
this.maxTurnOutputBytes = positiveInteger(
|
|
169
|
+
options.maxTurnOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES,
|
|
170
|
+
"maxTurnOutputBytes"
|
|
171
|
+
);
|
|
172
|
+
this.idleTtlMs = positiveInteger(options.idleTtlMs ?? 60 * 60 * 1e3, "idleTtlMs");
|
|
173
|
+
this.now = options.now ?? Date.now;
|
|
174
|
+
}
|
|
175
|
+
options;
|
|
176
|
+
agents = /* @__PURE__ */ new Map();
|
|
177
|
+
controllers = /* @__PURE__ */ new Map();
|
|
178
|
+
running = /* @__PURE__ */ new Map();
|
|
179
|
+
queue = [];
|
|
180
|
+
changeQueue = Promise.resolve();
|
|
181
|
+
shutdownController = new AbortController();
|
|
182
|
+
maxAgents;
|
|
183
|
+
maxActiveTurns;
|
|
184
|
+
maxHistoryTurns;
|
|
185
|
+
maxDepth;
|
|
186
|
+
maxChildrenPerAgent;
|
|
187
|
+
maxMailboxMessages;
|
|
188
|
+
maxMailboxMessageBytes;
|
|
189
|
+
maxTaskBytes;
|
|
190
|
+
maxTurnOutputBytes;
|
|
191
|
+
idleTtlMs;
|
|
192
|
+
transport;
|
|
193
|
+
now;
|
|
194
|
+
lastCompletionAt = 0;
|
|
195
|
+
restore(records) {
|
|
196
|
+
const candidates = new Map(
|
|
197
|
+
projectAgentRecords(
|
|
198
|
+
records.filter((record) => record.id && record.state !== "closed"),
|
|
199
|
+
{ maxAgents: this.maxAgents, maxDepth: this.maxDepth }
|
|
200
|
+
).map((record) => [record.id, record])
|
|
201
|
+
);
|
|
202
|
+
const restorable = [];
|
|
203
|
+
for (const record of candidates.values()) {
|
|
204
|
+
if (record.parentId && !candidates.has(record.parentId)) continue;
|
|
205
|
+
if (record.parentId === record.id) continue;
|
|
206
|
+
const seen = /* @__PURE__ */ new Set([record.id]);
|
|
207
|
+
let parentId = record.parentId;
|
|
208
|
+
let rootId = record.id;
|
|
209
|
+
let cyclic = false;
|
|
210
|
+
while (parentId) {
|
|
211
|
+
if (seen.has(parentId)) {
|
|
212
|
+
cyclic = true;
|
|
213
|
+
break;
|
|
214
|
+
}
|
|
215
|
+
seen.add(parentId);
|
|
216
|
+
rootId = parentId;
|
|
217
|
+
parentId = candidates.get(parentId)?.parentId;
|
|
218
|
+
}
|
|
219
|
+
const depth = seen.size - 1;
|
|
220
|
+
if (!cyclic && depth <= this.maxDepth) restorable.push({ record, rootId, depth });
|
|
221
|
+
}
|
|
222
|
+
restorable.sort(
|
|
223
|
+
(left, right) => left.depth - right.depth || left.record.createdAt - right.record.createdAt || left.record.id.localeCompare(right.record.id)
|
|
224
|
+
);
|
|
225
|
+
const reservedPaths = new Set(
|
|
226
|
+
[...this.agents.values()].flatMap(
|
|
227
|
+
(agent) => agent.state !== "closed" && agent.taskPath ? [agent.taskPath] : []
|
|
228
|
+
)
|
|
229
|
+
);
|
|
230
|
+
for (const { record, rootId, depth } of restorable) {
|
|
231
|
+
const parentPath = record.parentId ? this.agents.get(record.parentId)?.taskPath : ROOT_TASK_PATH;
|
|
232
|
+
if (!parentPath) continue;
|
|
233
|
+
const identity = this.reserveRestoredIdentity(record, parentPath, reservedPaths);
|
|
234
|
+
for (const completion of record.pendingCompletions ?? []) {
|
|
235
|
+
this.lastCompletionAt = Math.max(this.lastCompletionAt, completion.createdAt);
|
|
236
|
+
}
|
|
237
|
+
this.agents.set(record.id, {
|
|
238
|
+
...record,
|
|
239
|
+
...identity,
|
|
240
|
+
state: record.state === "running" || record.state === "starting" ? "interrupted" : record.state,
|
|
241
|
+
rootId,
|
|
242
|
+
depth,
|
|
243
|
+
currentTask: void 0,
|
|
244
|
+
turnGeneration: record.turnGeneration ?? 0,
|
|
245
|
+
currentRunId: void 0,
|
|
246
|
+
currentTurnGeneration: void 0,
|
|
247
|
+
pendingCompletions: (record.pendingCompletions ?? []).map((completion) => ({
|
|
248
|
+
...completion
|
|
249
|
+
})),
|
|
250
|
+
currentTimeoutMs: void 0,
|
|
251
|
+
currentIdleTimeoutMs: void 0,
|
|
252
|
+
currentMaxTurns: void 0,
|
|
253
|
+
currentMaxToolCalls: void 0,
|
|
254
|
+
currentMailboxMessageIds: void 0,
|
|
255
|
+
children: [],
|
|
256
|
+
contextSourceIds: [...record.contextSourceIds ?? []],
|
|
257
|
+
capabilityGrant: record.capabilityGrant?.state === "active" ? revokeCapabilityGrant(record.capabilityGrant, "restore-boundary", this.now()) : record.capabilityGrant,
|
|
258
|
+
mailbox: (record.mailbox ?? []).slice(-this.maxMailboxMessages).map((message) => ({ ...message, recipientId: record.id })),
|
|
259
|
+
history: record.history.slice(-this.maxHistoryTurns).map((turn) => ({ ...turn }))
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
for (const agent of this.agents.values()) {
|
|
263
|
+
if (agent.parentId) {
|
|
264
|
+
const parent = this.agents.get(agent.parentId);
|
|
265
|
+
if (parent && !parent.children.includes(agent.id)) parent.children.push(agent.id);
|
|
266
|
+
}
|
|
267
|
+
for (const completion of agent.pendingCompletions ?? []) {
|
|
268
|
+
if (!completion.recipientId || !completion.recipientPath) {
|
|
269
|
+
Object.assign(completion, this.completionRecipient(agent));
|
|
270
|
+
}
|
|
271
|
+
this.enqueueCompletionMessage(
|
|
272
|
+
agent,
|
|
273
|
+
completion,
|
|
274
|
+
completion.output || completion.error || "(no output)"
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
async spawn(input) {
|
|
280
|
+
if (!input.task.trim()) throw new Error("Subagent tasks cannot be empty");
|
|
281
|
+
if (input.timeoutMs !== void 0) validateTurnTimeout(input.timeoutMs);
|
|
282
|
+
validateTurnLimits(input);
|
|
283
|
+
const existing = this.findBySpawnIdempotencyKey(
|
|
284
|
+
input.spawnIdempotencyKey,
|
|
285
|
+
input.spawnRequestHash
|
|
286
|
+
);
|
|
287
|
+
if (existing) return existing;
|
|
288
|
+
const task = truncateUtf8(input.task, this.maxTaskBytes).text;
|
|
289
|
+
const expired = this.evictExpired();
|
|
290
|
+
let expiryReleaseError;
|
|
291
|
+
try {
|
|
292
|
+
await this.releaseAgents(expired);
|
|
293
|
+
} catch (error) {
|
|
294
|
+
expiryReleaseError = error;
|
|
295
|
+
}
|
|
296
|
+
if (expired.length > 0) await this.changed();
|
|
297
|
+
if (expiryReleaseError) throw expiryReleaseError;
|
|
298
|
+
if (this.retainedCount() >= this.maxAgents) {
|
|
299
|
+
throw new Error(`Subagent capacity reached (${this.maxAgents})`);
|
|
300
|
+
}
|
|
301
|
+
const parent = input.parentId ? this.require(input.parentId) : void 0;
|
|
302
|
+
if (parent?.state === "closed") throw new Error(`Cannot spawn under closed agent ${parent.id}`);
|
|
303
|
+
if (parent && parent.children.length >= this.maxChildrenPerAgent) {
|
|
304
|
+
throw new Error(`Agent ${parent.id} child capacity reached (${this.maxChildrenPerAgent})`);
|
|
305
|
+
}
|
|
306
|
+
const depth = parent ? parent.depth + 1 : 0;
|
|
307
|
+
if (depth > this.maxDepth) throw new Error(`Subagent depth limit reached (${this.maxDepth})`);
|
|
308
|
+
const now = this.now();
|
|
309
|
+
const id = `sa_${randomUUID()}`;
|
|
310
|
+
const taskName = input.taskName ? validateTaskName(input.taskName) : deriveTaskName(id);
|
|
311
|
+
const taskPath = joinTaskPath(parent?.taskPath ?? ROOT_TASK_PATH, taskName);
|
|
312
|
+
if (this.findByTaskPath(taskPath)) {
|
|
313
|
+
throw new Error(`Canonical subagent task path is already retained: ${taskPath}`);
|
|
314
|
+
}
|
|
315
|
+
const record = {
|
|
316
|
+
id,
|
|
317
|
+
taskName,
|
|
318
|
+
taskPath,
|
|
319
|
+
agent: input.agent,
|
|
320
|
+
parentId: parent?.id,
|
|
321
|
+
rootId: parent?.rootId ?? id,
|
|
322
|
+
depth,
|
|
323
|
+
children: [],
|
|
324
|
+
state: "starting",
|
|
325
|
+
createdAt: now,
|
|
326
|
+
updatedAt: now,
|
|
327
|
+
cwd: input.cwd,
|
|
328
|
+
agentScope: input.agentScope,
|
|
329
|
+
thinkingLevel: input.thinkingLevel,
|
|
330
|
+
timeoutMs: input.timeoutMs,
|
|
331
|
+
currentTimeoutMs: input.timeoutMs,
|
|
332
|
+
idleTimeoutMs: input.idleTimeoutMs,
|
|
333
|
+
currentIdleTimeoutMs: input.idleTimeoutMs,
|
|
334
|
+
maxTurns: input.maxTurns,
|
|
335
|
+
currentMaxTurns: input.maxTurns,
|
|
336
|
+
maxToolCalls: input.maxToolCalls,
|
|
337
|
+
currentMaxToolCalls: input.maxToolCalls,
|
|
338
|
+
currentTask: task,
|
|
339
|
+
turnGeneration: 0,
|
|
340
|
+
pendingCompletions: [],
|
|
341
|
+
history: [],
|
|
342
|
+
mailbox: [],
|
|
343
|
+
context: input.context,
|
|
344
|
+
contextSourceIds: input.contextSourceIds,
|
|
345
|
+
contextTruncated: input.contextTruncated,
|
|
346
|
+
contextTurns: input.contextTurns,
|
|
347
|
+
contextBytes: input.contextBytes,
|
|
348
|
+
workspaceMode: input.workspaceMode,
|
|
349
|
+
spawnIdempotencyKey: input.spawnIdempotencyKey,
|
|
350
|
+
spawnRequestHash: input.spawnRequestHash,
|
|
351
|
+
contract: input.contract,
|
|
352
|
+
resultFormat: input.resultFormat,
|
|
353
|
+
executionPlan: input.executionPlan,
|
|
354
|
+
capabilityGrant: input.capabilityGrant,
|
|
355
|
+
semanticSnapshot: input.semanticSnapshot,
|
|
356
|
+
semanticCompatibility: input.semanticCompatibility,
|
|
357
|
+
target: input.target
|
|
358
|
+
};
|
|
359
|
+
this.agents.set(record.id, record);
|
|
360
|
+
if (parent) {
|
|
361
|
+
parent.children.push(record.id);
|
|
362
|
+
parent.updatedAt = now;
|
|
363
|
+
}
|
|
364
|
+
await this.changed();
|
|
365
|
+
this.startTurn(record, task, input);
|
|
366
|
+
return this.copy(record);
|
|
367
|
+
}
|
|
368
|
+
findBySpawnIdempotencyKey(key, requestHash) {
|
|
369
|
+
if (!key) return void 0;
|
|
370
|
+
const existing = [...this.agents.values()].find(
|
|
371
|
+
(agent) => agent.state !== "closed" && agent.spawnIdempotencyKey === key
|
|
372
|
+
);
|
|
373
|
+
if (!existing) return void 0;
|
|
374
|
+
if (!requestHash || existing.spawnRequestHash !== requestHash) {
|
|
375
|
+
throw new Error(
|
|
376
|
+
"The subagent_spawn idempotencyKey was already used with different parameters"
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
return this.copy(existing);
|
|
380
|
+
}
|
|
381
|
+
async followUp(id, task, options = {}) {
|
|
382
|
+
if (!task.trim()) throw new Error("Subagent tasks cannot be empty");
|
|
383
|
+
if (options.timeoutMs !== void 0) validateTurnTimeout(options.timeoutMs);
|
|
384
|
+
validateTurnLimits(options);
|
|
385
|
+
const boundedTask = truncateUtf8(task, this.maxTaskBytes).text;
|
|
386
|
+
const agent = this.require(id);
|
|
387
|
+
if (![
|
|
388
|
+
"idle",
|
|
389
|
+
"completed",
|
|
390
|
+
"blocked",
|
|
391
|
+
"needs-input",
|
|
392
|
+
"abstained",
|
|
393
|
+
"stale",
|
|
394
|
+
"interrupted",
|
|
395
|
+
"failed"
|
|
396
|
+
].includes(agent.state)) {
|
|
397
|
+
throw new Error(`Agent ${id} cannot accept follow-up while ${agent.state}`);
|
|
398
|
+
}
|
|
399
|
+
const unread = agent.mailbox.filter((message) => !message.readAt).slice(-20);
|
|
400
|
+
agent.currentMailboxMessageIds = unread.map((message) => message.id);
|
|
401
|
+
this.startTurn(agent, boundedTask, options);
|
|
402
|
+
return this.copy(agent);
|
|
403
|
+
}
|
|
404
|
+
async updateSemanticState(id, executionPlan, capabilityGrant, snapshot, compatibility) {
|
|
405
|
+
const agent = this.require(id);
|
|
406
|
+
if (agent.state === "running" || agent.state === "starting" || agent.state === "closed") {
|
|
407
|
+
throw new Error(`Agent ${id} cannot update semantic state while ${agent.state}`);
|
|
408
|
+
}
|
|
409
|
+
agent.executionPlan = copyExecutionPlan(executionPlan);
|
|
410
|
+
agent.capabilityGrant = structuredClone(capabilityGrant);
|
|
411
|
+
agent.semanticSnapshot = structuredClone(snapshot);
|
|
412
|
+
agent.semanticCompatibility = structuredClone(compatibility);
|
|
413
|
+
agent.updatedAt = this.now();
|
|
414
|
+
await this.changed();
|
|
415
|
+
return this.copy(agent);
|
|
416
|
+
}
|
|
417
|
+
async sendMessage(recipientId, content, senderId = "root", deduplicationKey) {
|
|
418
|
+
if (!content.trim()) throw new Error("Subagent mailbox messages cannot be empty");
|
|
419
|
+
if (deduplicationKey && deduplicationKey.length > 256) {
|
|
420
|
+
throw new Error("Subagent mailbox deduplication keys cannot exceed 256 characters");
|
|
421
|
+
}
|
|
422
|
+
const sender = senderId === "root" ? void 0 : this.require(senderId);
|
|
423
|
+
if (sender?.state === "closed")
|
|
424
|
+
throw new Error(`Closed agent ${sender.id} cannot send messages`);
|
|
425
|
+
const recipient = this.require(recipientId, sender?.id);
|
|
426
|
+
if (recipient.state === "closed")
|
|
427
|
+
throw new Error(`Cannot message closed agent ${recipient.id}`);
|
|
428
|
+
const message = this.enqueueMessage(recipient, content, sender?.id ?? "root", deduplicationKey);
|
|
429
|
+
await this.changed();
|
|
430
|
+
return { ...message };
|
|
431
|
+
}
|
|
432
|
+
async readMessages(id, acknowledge = true, limit = this.maxMailboxMessages) {
|
|
433
|
+
if (!Number.isSafeInteger(limit) || limit < 1) {
|
|
434
|
+
throw new Error("Subagent mailbox read limit must be a positive safe integer");
|
|
435
|
+
}
|
|
436
|
+
const agent = this.require(id);
|
|
437
|
+
const unread = agent.mailbox.filter((message) => !message.readAt).slice(0, limit);
|
|
438
|
+
if (acknowledge && unread.length > 0) {
|
|
439
|
+
const readAt = this.now();
|
|
440
|
+
for (const message of unread) message.readAt = readAt;
|
|
441
|
+
await this.changed();
|
|
442
|
+
}
|
|
443
|
+
return unread.map((message) => ({ ...message }));
|
|
444
|
+
}
|
|
445
|
+
async acknowledgeVisibleMessages(id, messageIds, completionIds = [], visibleAt = this.now()) {
|
|
446
|
+
const agent = this.require(id);
|
|
447
|
+
const visibleMessageIds = new Set(messageIds);
|
|
448
|
+
const visibleCompletionIds = new Set(completionIds);
|
|
449
|
+
const changedMessages = [];
|
|
450
|
+
const removedCompletions = [];
|
|
451
|
+
for (const message of agent.mailbox) {
|
|
452
|
+
if (message.readAt === void 0 && visibleMessageIds.has(message.id)) {
|
|
453
|
+
message.readAt = visibleAt;
|
|
454
|
+
changedMessages.push(message);
|
|
455
|
+
}
|
|
456
|
+
if (message.completionId && visibleMessageIds.has(message.id)) {
|
|
457
|
+
visibleCompletionIds.add(message.completionId);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
for (const owner of this.agents.values()) {
|
|
461
|
+
for (const completion of owner.pendingCompletions ?? []) {
|
|
462
|
+
if (!visibleCompletionIds.has(completion.completionId)) continue;
|
|
463
|
+
if (completion.recipientId !== agent.id && completion.recipientPath !== agent.taskPath) {
|
|
464
|
+
continue;
|
|
465
|
+
}
|
|
466
|
+
removedCompletions.push({ owner, completion });
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
if (changedMessages.length === 0 && removedCompletions.length === 0) return;
|
|
470
|
+
for (const { owner, completion } of removedCompletions) {
|
|
471
|
+
owner.pendingCompletions = (owner.pendingCompletions ?? []).filter(
|
|
472
|
+
(candidate) => candidate.completionId !== completion.completionId
|
|
473
|
+
);
|
|
474
|
+
}
|
|
475
|
+
agent.updatedAt = Math.max(agent.updatedAt, visibleAt);
|
|
476
|
+
try {
|
|
477
|
+
await this.changed(true);
|
|
478
|
+
} catch (error) {
|
|
479
|
+
for (const message of changedMessages) message.readAt = void 0;
|
|
480
|
+
for (const { owner, completion } of removedCompletions) {
|
|
481
|
+
owner.pendingCompletions = [...owner.pendingCompletions ?? [], completion].sort(
|
|
482
|
+
(left, right) => left.createdAt - right.createdAt || left.generation - right.generation
|
|
483
|
+
);
|
|
484
|
+
}
|
|
485
|
+
throw error;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
async wait(id, timeoutMs = 3e4, signal) {
|
|
489
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs < 1) {
|
|
490
|
+
throw new Error("Subagent wait timeout must be a positive finite number");
|
|
491
|
+
}
|
|
492
|
+
if (signal?.aborted) throw waitAbortError();
|
|
493
|
+
const agent = this.require(id);
|
|
494
|
+
const agentId = agent.id;
|
|
495
|
+
const running = this.running.get(agentId);
|
|
496
|
+
if (!running) return { timedOut: false, agent: this.copy(agent) };
|
|
497
|
+
let timer;
|
|
498
|
+
let onAbort;
|
|
499
|
+
const timeout = new Promise((resolve) => {
|
|
500
|
+
timer = setTimeout(() => resolve("timeout"), Math.max(1, timeoutMs));
|
|
501
|
+
});
|
|
502
|
+
const aborted = new Promise((resolve) => {
|
|
503
|
+
onAbort = () => resolve("aborted");
|
|
504
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
505
|
+
});
|
|
506
|
+
const result = await Promise.race([running, timeout, aborted]);
|
|
507
|
+
if (timer) clearTimeout(timer);
|
|
508
|
+
if (onAbort) signal?.removeEventListener("abort", onAbort);
|
|
509
|
+
if (result === "aborted") throw waitAbortError();
|
|
510
|
+
return result === "timeout" ? { timedOut: true, agent: this.copy(this.require(agentId)) } : { timedOut: false, agent: this.copy(result) };
|
|
511
|
+
}
|
|
512
|
+
async interruptTree(id) {
|
|
513
|
+
const results = [];
|
|
514
|
+
for (const target of this.descendants(id).reverse()) {
|
|
515
|
+
const agent = this.require(target);
|
|
516
|
+
if (agent.state === "running" || agent.state === "starting") {
|
|
517
|
+
results.push(await this.interrupt(target));
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
return results;
|
|
521
|
+
}
|
|
522
|
+
async interrupt(id) {
|
|
523
|
+
const agent = this.require(id);
|
|
524
|
+
const agentId = agent.id;
|
|
525
|
+
if (agent.state !== "running" && agent.state !== "starting")
|
|
526
|
+
throw new Error(`Agent ${id} is not running`);
|
|
527
|
+
if (agent.capabilityGrant?.state === "active") {
|
|
528
|
+
agent.capabilityGrant = revokeCapabilityGrant(
|
|
529
|
+
agent.capabilityGrant,
|
|
530
|
+
"interrupted",
|
|
531
|
+
this.now()
|
|
532
|
+
);
|
|
533
|
+
}
|
|
534
|
+
if (agent.executionPlan) {
|
|
535
|
+
agent.executionPlan = rotateExecutionPlanGeneration(agent.executionPlan);
|
|
536
|
+
}
|
|
537
|
+
if (agent.state === "starting") {
|
|
538
|
+
const index = this.queue.findIndex((entry) => entry.agent.id === agentId);
|
|
539
|
+
if (index >= 0) {
|
|
540
|
+
const [entry] = this.queue.splice(index, 1);
|
|
541
|
+
const recipient = this.completionRecipient(agent);
|
|
542
|
+
const persistedCompletion = {
|
|
543
|
+
completionId: `completion:${agent.id}:${randomUUID()}`,
|
|
544
|
+
...recipient,
|
|
545
|
+
runId: agent.currentRunId ?? `run:${agent.id}:${randomUUID()}`,
|
|
546
|
+
generation: agent.currentTurnGeneration ?? agent.turnGeneration ?? 1,
|
|
547
|
+
task: truncateUtf8(entry.task, 256).text,
|
|
548
|
+
output: "",
|
|
549
|
+
error: "Interrupted before execution",
|
|
550
|
+
createdAt: this.completionCreatedAt()
|
|
551
|
+
};
|
|
552
|
+
agent.state = "interrupted";
|
|
553
|
+
agent.pendingCompletions = [...agent.pendingCompletions ?? [], persistedCompletion];
|
|
554
|
+
this.enqueueCompletionMessage(agent, persistedCompletion, persistedCompletion.error);
|
|
555
|
+
clearCurrentTurn(agent);
|
|
556
|
+
agent.updatedAt = this.now();
|
|
557
|
+
const persisted = await this.persistTerminalState().then(
|
|
558
|
+
() => true,
|
|
559
|
+
() => false
|
|
560
|
+
);
|
|
561
|
+
const completion = {
|
|
562
|
+
...persistedCompletion,
|
|
563
|
+
agent: this.copy(agent)
|
|
564
|
+
};
|
|
565
|
+
entry.resolve(agent);
|
|
566
|
+
this.running.delete(agentId);
|
|
567
|
+
if (persisted) await this.notifyTurnComplete(completion);
|
|
568
|
+
return this.copy(agent);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
this.controllers.get(agentId)?.abort();
|
|
572
|
+
await this.running.get(agentId);
|
|
573
|
+
return this.copy(this.require(agentId));
|
|
574
|
+
}
|
|
575
|
+
async closeTree(id) {
|
|
576
|
+
const results = [];
|
|
577
|
+
const failures = [];
|
|
578
|
+
for (const target of this.descendants(id).reverse()) {
|
|
579
|
+
const agent = this.require(target);
|
|
580
|
+
if (agent.state === "closed") continue;
|
|
581
|
+
try {
|
|
582
|
+
results.push(await this.close(target));
|
|
583
|
+
} catch (error) {
|
|
584
|
+
failures.push(error);
|
|
585
|
+
const closed = this.get(target);
|
|
586
|
+
if (closed?.state === "closed") results.push(closed);
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
if (failures.length > 0) {
|
|
590
|
+
throw new AggregateError(failures, `Failed to release ${failures.length} subagent(s)`);
|
|
591
|
+
}
|
|
592
|
+
return results;
|
|
593
|
+
}
|
|
594
|
+
async close(id) {
|
|
595
|
+
const agent = this.require(id);
|
|
596
|
+
const agentId = agent.id;
|
|
597
|
+
if (agent.state === "closed") throw new Error(`Agent ${id} is already closed`);
|
|
598
|
+
if (agent.children.some((childId) => this.agents.get(childId)?.state !== "closed")) {
|
|
599
|
+
throw new Error(`Agent ${id} has active descendants; close the subtree instead`);
|
|
600
|
+
}
|
|
601
|
+
if (agent.state === "starting" || agent.state === "running") {
|
|
602
|
+
if (agent.capabilityGrant?.state === "active") {
|
|
603
|
+
agent.capabilityGrant = revokeCapabilityGrant(agent.capabilityGrant, "closed", this.now());
|
|
604
|
+
}
|
|
605
|
+
if (agent.executionPlan) {
|
|
606
|
+
agent.executionPlan = rotateExecutionPlanGeneration(agent.executionPlan);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
if (agent.state === "starting") {
|
|
610
|
+
const index = this.queue.findIndex((entry) => entry.agent.id === agentId);
|
|
611
|
+
if (index >= 0) {
|
|
612
|
+
const [entry] = this.queue.splice(index, 1);
|
|
613
|
+
entry.resolve(agent);
|
|
614
|
+
this.running.delete(agentId);
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
this.controllers.get(agentId)?.abort();
|
|
618
|
+
await this.running.get(agentId)?.catch(() => void 0);
|
|
619
|
+
agent.state = "closed";
|
|
620
|
+
agent.updatedAt = this.now();
|
|
621
|
+
if (agent.parentId) {
|
|
622
|
+
const parent = this.agents.get(agent.parentId);
|
|
623
|
+
if (parent) parent.children = parent.children.filter((childId) => childId !== agentId);
|
|
624
|
+
}
|
|
625
|
+
clearCurrentTurn(agent);
|
|
626
|
+
let releaseError;
|
|
627
|
+
try {
|
|
628
|
+
await this.transport.release?.(this.copy(agent));
|
|
629
|
+
} catch (error) {
|
|
630
|
+
releaseError = error;
|
|
631
|
+
}
|
|
632
|
+
this.pruneClosedAgents();
|
|
633
|
+
await this.changed();
|
|
634
|
+
if (releaseError) throw releaseError;
|
|
635
|
+
return this.copy(agent);
|
|
636
|
+
}
|
|
637
|
+
async closeAll() {
|
|
638
|
+
const roots = [...this.agents.values()].filter((agent) => agent.state !== "closed" && !agent.parentId).map((agent) => agent.id);
|
|
639
|
+
const results = await Promise.allSettled(roots.map((id) => this.closeTree(id)));
|
|
640
|
+
const failures = results.flatMap(
|
|
641
|
+
(result) => result.status === "rejected" ? [result.reason] : []
|
|
642
|
+
);
|
|
643
|
+
if (failures.length > 0) {
|
|
644
|
+
throw new AggregateError(failures, `Failed to close ${failures.length} subagent tree(s)`);
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
async shutdown() {
|
|
648
|
+
this.shutdownController.abort(new Error("Subagent registry is shutting down"));
|
|
649
|
+
for (const entry of this.queue.splice(0)) {
|
|
650
|
+
if (entry.agent.capabilityGrant?.state === "active") {
|
|
651
|
+
entry.agent.capabilityGrant = revokeCapabilityGrant(
|
|
652
|
+
entry.agent.capabilityGrant,
|
|
653
|
+
"shutdown",
|
|
654
|
+
this.now()
|
|
655
|
+
);
|
|
656
|
+
}
|
|
657
|
+
if (entry.agent.executionPlan) {
|
|
658
|
+
entry.agent.executionPlan = rotateExecutionPlanGeneration(entry.agent.executionPlan);
|
|
659
|
+
}
|
|
660
|
+
entry.agent.state = "interrupted";
|
|
661
|
+
clearCurrentTurn(entry.agent);
|
|
662
|
+
entry.resolve(entry.agent);
|
|
663
|
+
this.running.delete(entry.agent.id);
|
|
664
|
+
}
|
|
665
|
+
for (const id of this.controllers.keys()) {
|
|
666
|
+
const agent = this.agents.get(id);
|
|
667
|
+
if (agent?.capabilityGrant?.state === "active") {
|
|
668
|
+
agent.capabilityGrant = revokeCapabilityGrant(
|
|
669
|
+
agent.capabilityGrant,
|
|
670
|
+
"shutdown",
|
|
671
|
+
this.now()
|
|
672
|
+
);
|
|
673
|
+
}
|
|
674
|
+
if (agent?.executionPlan) {
|
|
675
|
+
agent.executionPlan = rotateExecutionPlanGeneration(agent.executionPlan);
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
for (const controller of this.controllers.values()) controller.abort();
|
|
679
|
+
await Promise.all([...this.running.values()].map((turn) => turn.catch(() => void 0)));
|
|
680
|
+
for (const agent of this.agents.values()) {
|
|
681
|
+
if (agent.state !== "closed") {
|
|
682
|
+
if (agent.state === "running" || agent.state === "starting") {
|
|
683
|
+
agent.state = "interrupted";
|
|
684
|
+
}
|
|
685
|
+
clearCurrentTurn(agent);
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
let shutdownError;
|
|
689
|
+
try {
|
|
690
|
+
await this.transport.shutdown?.();
|
|
691
|
+
} catch (error) {
|
|
692
|
+
shutdownError = error;
|
|
693
|
+
}
|
|
694
|
+
await this.changed(true);
|
|
695
|
+
if (shutdownError) throw shutdownError;
|
|
696
|
+
}
|
|
697
|
+
inspectionCounts() {
|
|
698
|
+
let activeAgents = 0;
|
|
699
|
+
let retainedAgents = 0;
|
|
700
|
+
for (const agent of this.agents.values()) {
|
|
701
|
+
if (agent.state === "starting" || agent.state === "running") activeAgents++;
|
|
702
|
+
if (agent.state !== "closed") retainedAgents++;
|
|
703
|
+
}
|
|
704
|
+
return { activeAgents, retainedAgents };
|
|
705
|
+
}
|
|
706
|
+
listInspection(includeClosed = false) {
|
|
707
|
+
return [...this.agents.values()].filter((agent) => includeClosed || agent.state !== "closed").sort((left, right) => left.createdAt - right.createdAt).map((agent) => this.inspectSummary(agent));
|
|
708
|
+
}
|
|
709
|
+
getInspection(id) {
|
|
710
|
+
const agent = this.findReference(id);
|
|
711
|
+
if (!agent) return void 0;
|
|
712
|
+
return {
|
|
713
|
+
...this.inspectSummary(agent),
|
|
714
|
+
cwd: agent.cwd,
|
|
715
|
+
thinkingLevel: agent.thinkingLevel,
|
|
716
|
+
timeoutMs: agent.timeoutMs,
|
|
717
|
+
currentTimeoutMs: agent.currentTimeoutMs,
|
|
718
|
+
idleTimeoutMs: agent.idleTimeoutMs,
|
|
719
|
+
currentIdleTimeoutMs: agent.currentIdleTimeoutMs,
|
|
720
|
+
maxTurns: agent.maxTurns,
|
|
721
|
+
currentMaxTurns: agent.currentMaxTurns,
|
|
722
|
+
maxToolCalls: agent.maxToolCalls,
|
|
723
|
+
currentMaxToolCalls: agent.currentMaxToolCalls,
|
|
724
|
+
currentTask: agent.currentTask,
|
|
725
|
+
currentRunId: agent.currentRunId,
|
|
726
|
+
currentTurnGeneration: agent.currentTurnGeneration,
|
|
727
|
+
error: agent.error,
|
|
728
|
+
workspaceMode: agent.workspaceMode,
|
|
729
|
+
contextTurns: agent.contextTurns,
|
|
730
|
+
contextBytes: agent.contextBytes,
|
|
731
|
+
contextSources: agent.contextSourceIds?.length,
|
|
732
|
+
contextTruncated: agent.contextTruncated,
|
|
733
|
+
contract: agent.contract ? structuredClone(agent.contract) : void 0,
|
|
734
|
+
resultFormat: agent.resultFormat,
|
|
735
|
+
target: agent.target ? { ...agent.target, trust: { ...agent.target.trust } } : void 0,
|
|
736
|
+
policy: agent.policy ? {
|
|
737
|
+
inherited: [...agent.policy.inherited],
|
|
738
|
+
overridden: [...agent.policy.overridden],
|
|
739
|
+
unsupported: [...agent.policy.unsupported]
|
|
740
|
+
} : void 0,
|
|
741
|
+
structuredResult: agent.structuredResult ? copyStructuredResult(agent.structuredResult) : void 0,
|
|
742
|
+
termination: agent.termination ? copyTurnTerminationReport(agent.termination) : void 0,
|
|
743
|
+
outcome: agent.outcome ? structuredClone(agent.outcome) : void 0,
|
|
744
|
+
executionPlan: agent.executionPlan ? copyExecutionPlan(agent.executionPlan) : void 0,
|
|
745
|
+
capabilityGrant: agent.capabilityGrant ? structuredClone(agent.capabilityGrant) : void 0,
|
|
746
|
+
semanticSnapshot: agent.semanticSnapshot ? structuredClone(agent.semanticSnapshot) : void 0,
|
|
747
|
+
semanticCompatibility: agent.semanticCompatibility ? structuredClone(agent.semanticCompatibility) : void 0,
|
|
748
|
+
telemetry: agent.telemetry ? copyTelemetry(agent.telemetry) : void 0
|
|
749
|
+
};
|
|
750
|
+
}
|
|
751
|
+
list(includeClosed = false, rootId) {
|
|
752
|
+
return [...this.agents.values()].filter((agent) => !rootId || agent.rootId === rootId).filter((agent) => includeClosed || agent.state !== "closed").sort((a, b) => a.createdAt - b.createdAt).map((agent) => this.copy(agent));
|
|
753
|
+
}
|
|
754
|
+
get(reference) {
|
|
755
|
+
return this.resolveAgent(reference);
|
|
756
|
+
}
|
|
757
|
+
resolveAgent(reference, senderId = "root") {
|
|
758
|
+
const agent = this.findReference(reference, senderId);
|
|
759
|
+
return agent ? this.copy(agent) : void 0;
|
|
760
|
+
}
|
|
761
|
+
listPendingCompletions() {
|
|
762
|
+
return [...this.agents.values()].flatMap(
|
|
763
|
+
(agent) => (agent.pendingCompletions ?? []).map((completion) => ({
|
|
764
|
+
...completion,
|
|
765
|
+
agent: this.copy(agent)
|
|
766
|
+
}))
|
|
767
|
+
).sort(
|
|
768
|
+
(left, right) => left.createdAt - right.createdAt || left.generation - right.generation || left.completionId.localeCompare(right.completionId)
|
|
769
|
+
);
|
|
770
|
+
}
|
|
771
|
+
async markCompletionDelivered(completionId, deliveredAt) {
|
|
772
|
+
const agent = [...this.agents.values()].find(
|
|
773
|
+
(candidate) => candidate.pendingCompletions?.some((completion) => completion.completionId === completionId)
|
|
774
|
+
);
|
|
775
|
+
if (!agent) return;
|
|
776
|
+
const acknowledged = (agent.pendingCompletions ?? []).find(
|
|
777
|
+
(completion) => completion.completionId === completionId
|
|
778
|
+
);
|
|
779
|
+
if (!acknowledged) return;
|
|
780
|
+
agent.pendingCompletions = (agent.pendingCompletions ?? []).filter(
|
|
781
|
+
(completion) => completion.completionId !== completionId
|
|
782
|
+
);
|
|
783
|
+
if (agent.telemetry) {
|
|
784
|
+
agent.telemetry = {
|
|
785
|
+
...agent.telemetry,
|
|
786
|
+
updatedAt: deliveredAt,
|
|
787
|
+
timing: { ...agent.telemetry.timing, completionDeliveredAt: deliveredAt }
|
|
788
|
+
};
|
|
789
|
+
}
|
|
790
|
+
agent.updatedAt = Math.max(agent.updatedAt, deliveredAt);
|
|
791
|
+
try {
|
|
792
|
+
await this.changed(true);
|
|
793
|
+
} catch (error) {
|
|
794
|
+
if (!agent.pendingCompletions?.some(
|
|
795
|
+
(completion) => completion.completionId === acknowledged.completionId
|
|
796
|
+
)) {
|
|
797
|
+
agent.pendingCompletions = [...agent.pendingCompletions ?? [], acknowledged].sort(
|
|
798
|
+
(left, right) => left.createdAt - right.createdAt || left.generation - right.generation
|
|
799
|
+
);
|
|
800
|
+
}
|
|
801
|
+
if (agent.telemetry?.timing.completionDeliveredAt === deliveredAt) {
|
|
802
|
+
const { completionDeliveredAt: _discarded, ...timing } = agent.telemetry.timing;
|
|
803
|
+
agent.telemetry = { ...agent.telemetry, timing };
|
|
804
|
+
}
|
|
805
|
+
throw error;
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
async sweepExpired() {
|
|
809
|
+
const removed = this.evictExpired();
|
|
810
|
+
let releaseError;
|
|
811
|
+
try {
|
|
812
|
+
await this.releaseAgents(removed);
|
|
813
|
+
} catch (error) {
|
|
814
|
+
releaseError = error;
|
|
815
|
+
}
|
|
816
|
+
if (removed.length > 0) await this.changed();
|
|
817
|
+
if (releaseError) throw releaseError;
|
|
818
|
+
return removed.length;
|
|
819
|
+
}
|
|
820
|
+
startTurn(agent, task, limits = {}) {
|
|
821
|
+
if ((agent.pendingCompletions?.length ?? 0) >= MAX_PENDING_COMPLETIONS_PER_AGENT) {
|
|
822
|
+
throw new Error(
|
|
823
|
+
`Agent ${agent.id} has ${MAX_PENDING_COMPLETIONS_PER_AGENT} undelivered completions; wait for delivery before another turn`
|
|
824
|
+
);
|
|
825
|
+
}
|
|
826
|
+
agent.turnGeneration = (agent.turnGeneration ?? 0) + 1;
|
|
827
|
+
agent.currentTurnGeneration = agent.turnGeneration;
|
|
828
|
+
agent.currentRunId = `run:${agent.id}:${randomUUID()}`;
|
|
829
|
+
agent.state = "starting";
|
|
830
|
+
agent.error = void 0;
|
|
831
|
+
agent.currentTask = task;
|
|
832
|
+
agent.currentTimeoutMs = limits.timeoutMs ?? agent.timeoutMs;
|
|
833
|
+
agent.currentIdleTimeoutMs = limits.idleTimeoutMs ?? agent.idleTimeoutMs;
|
|
834
|
+
agent.currentMaxTurns = limits.maxTurns ?? agent.maxTurns;
|
|
835
|
+
agent.currentMaxToolCalls = limits.maxToolCalls ?? agent.maxToolCalls;
|
|
836
|
+
agent.structuredResult = void 0;
|
|
837
|
+
agent.termination = void 0;
|
|
838
|
+
agent.outcome = void 0;
|
|
839
|
+
agent.updatedAt = this.now();
|
|
840
|
+
agent.telemetry = {
|
|
841
|
+
phase: "queued",
|
|
842
|
+
queuePosition: this.queue.length + 1,
|
|
843
|
+
updatedAt: agent.updatedAt,
|
|
844
|
+
timing: { queuedAt: agent.updatedAt }
|
|
845
|
+
};
|
|
846
|
+
let resolveQueued;
|
|
847
|
+
const completion = new Promise((resolve) => {
|
|
848
|
+
resolveQueued = resolve;
|
|
849
|
+
});
|
|
850
|
+
this.running.set(agent.id, completion);
|
|
851
|
+
this.queue.push({ agent, task, resolve: resolveQueued });
|
|
852
|
+
this.updateQueuePositions();
|
|
853
|
+
void this.changed();
|
|
854
|
+
this.pumpQueue();
|
|
855
|
+
}
|
|
856
|
+
pumpQueue() {
|
|
857
|
+
while (this.controllers.size < this.maxActiveTurns && this.queue.length > 0) {
|
|
858
|
+
const next = this.queue.shift();
|
|
859
|
+
if (!next) return;
|
|
860
|
+
this.runQueuedTurn(next.agent, next.task, next.resolve);
|
|
861
|
+
this.updateQueuePositions();
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
runQueuedTurn(agent, task, resolveQueued) {
|
|
865
|
+
if (agent.capabilityGrant && agent.executionPlan && !isCapabilityGrantActive(agent.capabilityGrant, agent.executionPlan, this.now())) {
|
|
866
|
+
agent.state = "failed";
|
|
867
|
+
agent.error = "Capability grant expired or no longer matches the accepted plan";
|
|
868
|
+
agent.outcome = classifyStructuredOutcome("failed", "capability-grant-invalid");
|
|
869
|
+
const recipient = this.completionRecipient(agent);
|
|
870
|
+
const persistedCompletion = {
|
|
871
|
+
completionId: `completion:${agent.id}:${randomUUID()}`,
|
|
872
|
+
...recipient,
|
|
873
|
+
runId: agent.currentRunId ?? `run:${agent.id}:${randomUUID()}`,
|
|
874
|
+
generation: agent.currentTurnGeneration ?? agent.turnGeneration ?? 1,
|
|
875
|
+
task: truncateUtf8(task, 256).text,
|
|
876
|
+
output: "",
|
|
877
|
+
error: truncateUtf8(agent.error, 512).text,
|
|
878
|
+
createdAt: this.completionCreatedAt()
|
|
879
|
+
};
|
|
880
|
+
agent.pendingCompletions = [...agent.pendingCompletions ?? [], persistedCompletion];
|
|
881
|
+
this.enqueueCompletionMessage(agent, persistedCompletion, persistedCompletion.error ?? "");
|
|
882
|
+
clearCurrentTurn(agent);
|
|
883
|
+
agent.updatedAt = this.now();
|
|
884
|
+
void this.persistTerminalState().then(
|
|
885
|
+
() => this.notifyTurnComplete({
|
|
886
|
+
...persistedCompletion,
|
|
887
|
+
agent: this.copy(agent)
|
|
888
|
+
})
|
|
889
|
+
).catch(() => void 0).finally(() => {
|
|
890
|
+
resolveQueued(agent);
|
|
891
|
+
this.running.delete(agent.id);
|
|
892
|
+
});
|
|
893
|
+
return;
|
|
894
|
+
}
|
|
895
|
+
const controller = new AbortController();
|
|
896
|
+
this.controllers.set(agent.id, controller);
|
|
897
|
+
agent.state = "running";
|
|
898
|
+
agent.updatedAt = this.now();
|
|
899
|
+
const startedAt = this.now();
|
|
900
|
+
const runId = agent.currentRunId ?? `run:${agent.id}:${randomUUID()}`;
|
|
901
|
+
const turnGeneration = agent.currentTurnGeneration ?? agent.turnGeneration ?? 1;
|
|
902
|
+
const completionKey = `completion:${agent.id}:${randomUUID()}`;
|
|
903
|
+
const acceptedPlanId = agent.executionPlan?.id;
|
|
904
|
+
let completionContent = "";
|
|
905
|
+
let completionOutput = "";
|
|
906
|
+
let completionError;
|
|
907
|
+
const visibleMailboxMessageIds = [...agent.currentMailboxMessageIds ?? []];
|
|
908
|
+
void this.transport.runTurn(this.copy(agent), task, controller.signal, (progress) => {
|
|
909
|
+
agent.telemetry = {
|
|
910
|
+
...progress,
|
|
911
|
+
queuePosition: void 0,
|
|
912
|
+
timing: {
|
|
913
|
+
queuedAt: agent.telemetry?.timing.queuedAt,
|
|
914
|
+
...progress.timing
|
|
915
|
+
}
|
|
916
|
+
};
|
|
917
|
+
}).then(async (outcome) => {
|
|
918
|
+
if (visibleMailboxMessageIds.length > 0) {
|
|
919
|
+
await this.acknowledgeVisibleMessages(
|
|
920
|
+
agent.id,
|
|
921
|
+
visibleMailboxMessageIds,
|
|
922
|
+
[],
|
|
923
|
+
this.now()
|
|
924
|
+
).catch(() => void 0);
|
|
925
|
+
}
|
|
926
|
+
const output = truncateUtf8(outcome.output, this.maxTurnOutputBytes).text;
|
|
927
|
+
const error = outcome.error ? truncateUtf8(outcome.error, this.maxTurnOutputBytes).text : void 0;
|
|
928
|
+
agent.history.push({
|
|
929
|
+
runId,
|
|
930
|
+
generation: turnGeneration,
|
|
931
|
+
task,
|
|
932
|
+
output,
|
|
933
|
+
startedAt,
|
|
934
|
+
completedAt: this.now(),
|
|
935
|
+
exitCode: outcome.exitCode,
|
|
936
|
+
truncated: outcome.truncated,
|
|
937
|
+
termination: outcome.termination ? copyTurnTerminationReport(outcome.termination) : void 0
|
|
938
|
+
});
|
|
939
|
+
agent.history = agent.history.slice(-this.maxHistoryTurns);
|
|
940
|
+
agent.structuredResult = outcome.structuredResult ?? parseAnyStructuredSubagentResult(output, agent.resultFormat);
|
|
941
|
+
agent.outcome = outcome.outcome ?? (outcome.aborted ? classifyStructuredOutcome("interrupted", "transport-aborted") : agent.structuredResult?.version === "pi-subagents:result:v2" ? classifyStructuredOutcome(
|
|
942
|
+
agent.structuredResult.status,
|
|
943
|
+
agent.structuredResult.reasonCode
|
|
944
|
+
) : agent.resultFormat !== void 0 && agent.resultFormat !== "text" && agent.structuredResult === void 0 ? classifyStructuredOutcome("contract-invalid", "malformed-structured-result") : void 0);
|
|
945
|
+
const staleGeneration = Boolean(
|
|
946
|
+
acceptedPlanId && agent.executionPlan?.id !== acceptedPlanId
|
|
947
|
+
);
|
|
948
|
+
if (staleGeneration) {
|
|
949
|
+
agent.outcome = classifyStructuredOutcome("stale", "cancelled-generation");
|
|
950
|
+
}
|
|
951
|
+
agent.state = staleGeneration ? "stale" : outcome.aborted ? "interrupted" : outcome.exitCode !== 0 ? "failed" : lifecycleStateForOutcome(agent.outcome?.status);
|
|
952
|
+
agent.error = error;
|
|
953
|
+
if (agent.capabilityGrant?.state === "active") {
|
|
954
|
+
agent.capabilityGrant = revokeCapabilityGrant(
|
|
955
|
+
agent.capabilityGrant,
|
|
956
|
+
"turn-settled",
|
|
957
|
+
this.now()
|
|
958
|
+
);
|
|
959
|
+
}
|
|
960
|
+
agent.policy = outcome.policy;
|
|
961
|
+
agent.telemetry = outcome.telemetry ? {
|
|
962
|
+
...outcome.telemetry,
|
|
963
|
+
timing: {
|
|
964
|
+
queuedAt: agent.telemetry?.timing.queuedAt,
|
|
965
|
+
...outcome.telemetry.timing
|
|
966
|
+
}
|
|
967
|
+
} : agent.telemetry;
|
|
968
|
+
agent.termination = outcome.termination ? copyTurnTerminationReport(outcome.termination) : void 0;
|
|
969
|
+
completionOutput = output;
|
|
970
|
+
completionError = error;
|
|
971
|
+
completionContent = output || error || `${agent.id} ${agent.state}`;
|
|
972
|
+
return agent;
|
|
973
|
+
}).catch((error) => {
|
|
974
|
+
const staleGeneration = Boolean(
|
|
975
|
+
acceptedPlanId && agent.executionPlan?.id !== acceptedPlanId
|
|
976
|
+
);
|
|
977
|
+
agent.state = staleGeneration ? "stale" : controller.signal.aborted ? "interrupted" : "failed";
|
|
978
|
+
agent.outcome = classifyStructuredOutcome(
|
|
979
|
+
staleGeneration ? "stale" : controller.signal.aborted ? "interrupted" : "failed",
|
|
980
|
+
staleGeneration ? "cancelled-generation" : controller.signal.aborted ? "transport-aborted" : "transport-error"
|
|
981
|
+
);
|
|
982
|
+
if (agent.capabilityGrant?.state === "active") {
|
|
983
|
+
agent.capabilityGrant = revokeCapabilityGrant(
|
|
984
|
+
agent.capabilityGrant,
|
|
985
|
+
"turn-failed",
|
|
986
|
+
this.now()
|
|
987
|
+
);
|
|
988
|
+
}
|
|
989
|
+
agent.error = truncateUtf8(
|
|
990
|
+
error instanceof Error ? error.message : String(error),
|
|
991
|
+
this.maxTurnOutputBytes
|
|
992
|
+
).text;
|
|
993
|
+
agent.history.push({
|
|
994
|
+
runId,
|
|
995
|
+
generation: turnGeneration,
|
|
996
|
+
task,
|
|
997
|
+
output: "",
|
|
998
|
+
startedAt,
|
|
999
|
+
completedAt: this.now(),
|
|
1000
|
+
exitCode: controller.signal.aborted ? 130 : 1
|
|
1001
|
+
});
|
|
1002
|
+
agent.history = agent.history.slice(-this.maxHistoryTurns);
|
|
1003
|
+
agent.telemetry = {
|
|
1004
|
+
...agent.telemetry ?? {
|
|
1005
|
+
phase: "failed",
|
|
1006
|
+
updatedAt: this.now(),
|
|
1007
|
+
timing: { queuedAt: startedAt }
|
|
1008
|
+
},
|
|
1009
|
+
phase: controller.signal.aborted ? "interrupted" : "failed",
|
|
1010
|
+
failurePhase: agent.telemetry?.phase ?? "running",
|
|
1011
|
+
updatedAt: this.now()
|
|
1012
|
+
};
|
|
1013
|
+
completionError = agent.error;
|
|
1014
|
+
completionContent = agent.error;
|
|
1015
|
+
return agent;
|
|
1016
|
+
}).finally(async () => {
|
|
1017
|
+
const recipient = this.completionRecipient(agent);
|
|
1018
|
+
const persistedCompletion = {
|
|
1019
|
+
completionId: completionKey,
|
|
1020
|
+
...recipient,
|
|
1021
|
+
runId,
|
|
1022
|
+
generation: turnGeneration,
|
|
1023
|
+
task: truncateUtf8(task, 256).text,
|
|
1024
|
+
output: truncateUtf8(completionOutput, MAX_TOOL_MESSAGE_BYTES).text,
|
|
1025
|
+
error: completionError ? truncateUtf8(completionError, 512).text : void 0,
|
|
1026
|
+
createdAt: this.completionCreatedAt()
|
|
1027
|
+
};
|
|
1028
|
+
agent.pendingCompletions = [...agent.pendingCompletions ?? [], persistedCompletion];
|
|
1029
|
+
this.enqueueCompletionMessage(agent, persistedCompletion, completionContent);
|
|
1030
|
+
clearCurrentTurn(agent);
|
|
1031
|
+
agent.updatedAt = this.now();
|
|
1032
|
+
const persisted = await this.persistTerminalState().then(
|
|
1033
|
+
() => true,
|
|
1034
|
+
() => false
|
|
1035
|
+
);
|
|
1036
|
+
this.controllers.delete(agent.id);
|
|
1037
|
+
const turnCompletion = {
|
|
1038
|
+
...persistedCompletion,
|
|
1039
|
+
agent: this.copy(agent)
|
|
1040
|
+
};
|
|
1041
|
+
this.running.delete(agent.id);
|
|
1042
|
+
resolveQueued(agent);
|
|
1043
|
+
this.pumpQueue();
|
|
1044
|
+
if (persisted) await this.notifyTurnComplete(turnCompletion);
|
|
1045
|
+
});
|
|
1046
|
+
}
|
|
1047
|
+
updateQueuePositions() {
|
|
1048
|
+
for (const [index, entry] of this.queue.entries()) {
|
|
1049
|
+
if (!entry.agent.telemetry) continue;
|
|
1050
|
+
entry.agent.telemetry = {
|
|
1051
|
+
...entry.agent.telemetry,
|
|
1052
|
+
queuePosition: index + 1,
|
|
1053
|
+
updatedAt: this.now()
|
|
1054
|
+
};
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
completionRecipient(agent) {
|
|
1058
|
+
return resolveCompletionRecipient(agent, (id) => this.agents.get(id));
|
|
1059
|
+
}
|
|
1060
|
+
enqueueCompletionMessage(agent, completion, content) {
|
|
1061
|
+
if (!completion.recipientId || completion.recipientId === "root") return;
|
|
1062
|
+
const recipient = this.agents.get(completion.recipientId);
|
|
1063
|
+
if (!recipient || recipient.state === "closed") return;
|
|
1064
|
+
this.enqueueMessage(
|
|
1065
|
+
recipient,
|
|
1066
|
+
content || "(no output)",
|
|
1067
|
+
agent.id,
|
|
1068
|
+
completion.completionId,
|
|
1069
|
+
completion.completionId
|
|
1070
|
+
);
|
|
1071
|
+
}
|
|
1072
|
+
enqueueMessage(recipient, content, senderId, deduplicationKey, completionId) {
|
|
1073
|
+
if (deduplicationKey) {
|
|
1074
|
+
const existing = recipient.mailbox.find(
|
|
1075
|
+
(message2) => message2.deduplicationKey === deduplicationKey && message2.senderId === senderId
|
|
1076
|
+
);
|
|
1077
|
+
if (existing) return existing;
|
|
1078
|
+
}
|
|
1079
|
+
const bounded = truncateUtf8(content, this.maxMailboxMessageBytes);
|
|
1080
|
+
const message = {
|
|
1081
|
+
id: `msg_${randomUUID()}`,
|
|
1082
|
+
senderId,
|
|
1083
|
+
recipientId: recipient.id,
|
|
1084
|
+
content: bounded.text,
|
|
1085
|
+
createdAt: this.now(),
|
|
1086
|
+
deduplicationKey,
|
|
1087
|
+
completionId
|
|
1088
|
+
};
|
|
1089
|
+
recipient.mailbox.push(message);
|
|
1090
|
+
recipient.mailbox = recipient.mailbox.slice(-this.maxMailboxMessages);
|
|
1091
|
+
recipient.updatedAt = this.now();
|
|
1092
|
+
return message;
|
|
1093
|
+
}
|
|
1094
|
+
descendants(id) {
|
|
1095
|
+
const root = this.require(id);
|
|
1096
|
+
const result = [];
|
|
1097
|
+
const visit = (agent) => {
|
|
1098
|
+
result.push(agent.id);
|
|
1099
|
+
for (const childId of agent.children) {
|
|
1100
|
+
const child = this.agents.get(childId);
|
|
1101
|
+
if (child) visit(child);
|
|
1102
|
+
}
|
|
1103
|
+
};
|
|
1104
|
+
visit(root);
|
|
1105
|
+
return result;
|
|
1106
|
+
}
|
|
1107
|
+
require(reference, senderId = "root") {
|
|
1108
|
+
const agent = this.findReference(reference, senderId);
|
|
1109
|
+
if (!agent) throw new Error(`Unknown subagent: ${safeTerminalLine(reference, 256)}`);
|
|
1110
|
+
return agent;
|
|
1111
|
+
}
|
|
1112
|
+
findReference(reference, senderId = "root") {
|
|
1113
|
+
const direct = this.agents.get(reference);
|
|
1114
|
+
if (direct) return direct;
|
|
1115
|
+
const sender = senderId === "root" ? void 0 : this.agents.get(senderId) ?? this.findByTaskPath(senderId);
|
|
1116
|
+
if (senderId !== "root" && !sender) return void 0;
|
|
1117
|
+
let taskPath;
|
|
1118
|
+
try {
|
|
1119
|
+
taskPath = resolveTaskPath(sender?.taskPath ?? ROOT_TASK_PATH, reference);
|
|
1120
|
+
} catch {
|
|
1121
|
+
return void 0;
|
|
1122
|
+
}
|
|
1123
|
+
return this.findByTaskPath(taskPath);
|
|
1124
|
+
}
|
|
1125
|
+
findByTaskPath(taskPath) {
|
|
1126
|
+
return [...this.agents.values()].find(
|
|
1127
|
+
(agent) => agent.state !== "closed" && agent.taskPath === taskPath
|
|
1128
|
+
);
|
|
1129
|
+
}
|
|
1130
|
+
reserveRestoredIdentity(record, parentPath, reservedPaths) {
|
|
1131
|
+
let taskName;
|
|
1132
|
+
try {
|
|
1133
|
+
taskName = record.taskName ? validateTaskName(record.taskName) : void 0;
|
|
1134
|
+
} catch {
|
|
1135
|
+
taskName = void 0;
|
|
1136
|
+
}
|
|
1137
|
+
if (!taskName && record.taskPath) {
|
|
1138
|
+
try {
|
|
1139
|
+
const storedPath = validateTaskPath(record.taskPath);
|
|
1140
|
+
const storedName = storedPath.slice(storedPath.lastIndexOf("/") + 1);
|
|
1141
|
+
taskName = validateTaskName(storedName);
|
|
1142
|
+
} catch {
|
|
1143
|
+
taskName = void 0;
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
taskName ??= deriveTaskName(record.id);
|
|
1147
|
+
let taskPath = joinTaskPath(parentPath, taskName);
|
|
1148
|
+
if (reservedPaths.has(taskPath)) {
|
|
1149
|
+
taskName = deriveTaskName(record.id);
|
|
1150
|
+
taskPath = joinTaskPath(parentPath, taskName);
|
|
1151
|
+
}
|
|
1152
|
+
if (reservedPaths.has(taskPath)) {
|
|
1153
|
+
throw new Error(`Cannot restore duplicate canonical subagent task path: ${taskPath}`);
|
|
1154
|
+
}
|
|
1155
|
+
reservedPaths.add(taskPath);
|
|
1156
|
+
return { taskName, taskPath };
|
|
1157
|
+
}
|
|
1158
|
+
retainedCount() {
|
|
1159
|
+
return [...this.agents.values()].filter((agent) => agent.state !== "closed").length;
|
|
1160
|
+
}
|
|
1161
|
+
completionCreatedAt() {
|
|
1162
|
+
this.lastCompletionAt = Math.max(this.now(), this.lastCompletionAt + 1);
|
|
1163
|
+
return this.lastCompletionAt;
|
|
1164
|
+
}
|
|
1165
|
+
evictExpired() {
|
|
1166
|
+
const cutoff = this.now() - this.idleTtlMs;
|
|
1167
|
+
const protectedIds = /* @__PURE__ */ new Set();
|
|
1168
|
+
for (const agent of this.agents.values()) {
|
|
1169
|
+
if (agent.state !== "running" && agent.state !== "starting" && (agent.pendingCompletions?.length ?? 0) === 0) {
|
|
1170
|
+
continue;
|
|
1171
|
+
}
|
|
1172
|
+
let current = agent;
|
|
1173
|
+
while (current) {
|
|
1174
|
+
protectedIds.add(current.id);
|
|
1175
|
+
current = current.parentId ? this.agents.get(current.parentId) : void 0;
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
const removed = [];
|
|
1179
|
+
const candidates = [...this.agents.values()].sort((left, right) => right.depth - left.depth);
|
|
1180
|
+
for (const agent of candidates) {
|
|
1181
|
+
if (protectedIds.has(agent.id) || agent.updatedAt >= cutoff) continue;
|
|
1182
|
+
if (agent.children.some((childId) => this.agents.get(childId)?.state !== "closed")) continue;
|
|
1183
|
+
this.agents.delete(agent.id);
|
|
1184
|
+
if (agent.parentId) {
|
|
1185
|
+
const parent = this.agents.get(agent.parentId);
|
|
1186
|
+
if (parent) parent.children = parent.children.filter((childId) => childId !== agent.id);
|
|
1187
|
+
}
|
|
1188
|
+
removed.push(this.copy(agent));
|
|
1189
|
+
}
|
|
1190
|
+
return removed;
|
|
1191
|
+
}
|
|
1192
|
+
async releaseAgents(agents) {
|
|
1193
|
+
if (!this.transport.release || agents.length === 0) return;
|
|
1194
|
+
const results = await Promise.allSettled(
|
|
1195
|
+
agents.map((agent) => this.transport.release?.(agent))
|
|
1196
|
+
);
|
|
1197
|
+
const failures = results.flatMap(
|
|
1198
|
+
(result) => result.status === "rejected" ? [result.reason] : []
|
|
1199
|
+
);
|
|
1200
|
+
if (failures.length > 0) {
|
|
1201
|
+
throw new AggregateError(
|
|
1202
|
+
failures,
|
|
1203
|
+
`Failed to release ${failures.length} subagent transport session(s)`
|
|
1204
|
+
);
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
pruneClosedAgents() {
|
|
1208
|
+
const closed = [...this.agents.values()].filter((agent) => agent.state === "closed").sort((left, right) => right.updatedAt - left.updatedAt);
|
|
1209
|
+
for (const agent of closed.slice(this.maxAgents)) this.agents.delete(agent.id);
|
|
1210
|
+
}
|
|
1211
|
+
async persistTerminalState() {
|
|
1212
|
+
let failures = 0;
|
|
1213
|
+
for (; ; ) {
|
|
1214
|
+
try {
|
|
1215
|
+
await this.changed(true);
|
|
1216
|
+
return;
|
|
1217
|
+
} catch (error) {
|
|
1218
|
+
if (this.shutdownController.signal.aborted) throw error;
|
|
1219
|
+
failures++;
|
|
1220
|
+
if (failures === 1) continue;
|
|
1221
|
+
const delay = Math.min(
|
|
1222
|
+
INITIAL_PERSISTENCE_RETRY_DELAY_MS * 2 ** (failures - 2),
|
|
1223
|
+
MAX_PERSISTENCE_RETRY_DELAY_MS
|
|
1224
|
+
);
|
|
1225
|
+
await waitForPersistenceRetry(delay, this.shutdownController.signal);
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
async notifyTurnComplete(completion) {
|
|
1230
|
+
try {
|
|
1231
|
+
await this.options.onTurnComplete?.(completion);
|
|
1232
|
+
} catch {
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
changed(propagateError = false) {
|
|
1236
|
+
const snapshot = this.list(true);
|
|
1237
|
+
const next = this.changeQueue.then(async () => {
|
|
1238
|
+
try {
|
|
1239
|
+
await this.options.onChange?.(snapshot);
|
|
1240
|
+
} catch (error) {
|
|
1241
|
+
if (propagateError) throw error;
|
|
1242
|
+
}
|
|
1243
|
+
});
|
|
1244
|
+
this.changeQueue = next.catch(() => void 0);
|
|
1245
|
+
return next;
|
|
1246
|
+
}
|
|
1247
|
+
inspectSummary(agent) {
|
|
1248
|
+
let unreadMessages = 0;
|
|
1249
|
+
for (const message of agent.mailbox) {
|
|
1250
|
+
if (message.readAt === void 0) unreadMessages++;
|
|
1251
|
+
}
|
|
1252
|
+
return {
|
|
1253
|
+
id: agent.id,
|
|
1254
|
+
taskName: agent.taskName,
|
|
1255
|
+
taskPath: agent.taskPath,
|
|
1256
|
+
agent: agent.agent,
|
|
1257
|
+
state: agent.state,
|
|
1258
|
+
createdAt: agent.createdAt,
|
|
1259
|
+
updatedAt: agent.updatedAt,
|
|
1260
|
+
historyCount: agent.history.length,
|
|
1261
|
+
unreadMessages,
|
|
1262
|
+
turnGeneration: agent.turnGeneration ?? 0,
|
|
1263
|
+
pendingCompletionCount: agent.pendingCompletions?.length ?? 0
|
|
1264
|
+
};
|
|
1265
|
+
}
|
|
1266
|
+
copy(agent) {
|
|
1267
|
+
return {
|
|
1268
|
+
...agent,
|
|
1269
|
+
children: [...agent.children],
|
|
1270
|
+
contextSourceIds: [...agent.contextSourceIds ?? []],
|
|
1271
|
+
currentMailboxMessageIds: agent.currentMailboxMessageIds ? [...agent.currentMailboxMessageIds] : void 0,
|
|
1272
|
+
pendingCompletions: (agent.pendingCompletions ?? []).map((completion) => ({
|
|
1273
|
+
...completion
|
|
1274
|
+
})),
|
|
1275
|
+
history: agent.history.map((turn) => ({ ...turn })),
|
|
1276
|
+
mailbox: agent.mailbox.map((message) => ({ ...message })),
|
|
1277
|
+
contract: agent.contract ? structuredClone(agent.contract) : void 0,
|
|
1278
|
+
target: agent.target ? { ...agent.target, trust: { ...agent.target.trust } } : void 0,
|
|
1279
|
+
policy: agent.policy ? {
|
|
1280
|
+
inherited: [...agent.policy.inherited],
|
|
1281
|
+
overridden: [...agent.policy.overridden],
|
|
1282
|
+
unsupported: [...agent.policy.unsupported]
|
|
1283
|
+
} : void 0,
|
|
1284
|
+
structuredResult: agent.structuredResult ? copyStructuredResult(agent.structuredResult) : void 0,
|
|
1285
|
+
termination: agent.termination ? copyTurnTerminationReport(agent.termination) : void 0,
|
|
1286
|
+
outcome: agent.outcome ? structuredClone(agent.outcome) : void 0,
|
|
1287
|
+
executionPlan: agent.executionPlan ? copyExecutionPlan(agent.executionPlan) : void 0,
|
|
1288
|
+
capabilityGrant: agent.capabilityGrant ? structuredClone(agent.capabilityGrant) : void 0,
|
|
1289
|
+
semanticSnapshot: agent.semanticSnapshot ? structuredClone(agent.semanticSnapshot) : void 0,
|
|
1290
|
+
semanticCompatibility: agent.semanticCompatibility ? structuredClone(agent.semanticCompatibility) : void 0,
|
|
1291
|
+
telemetry: agent.telemetry ? copyTelemetry(agent.telemetry) : void 0
|
|
1292
|
+
};
|
|
1293
|
+
}
|
|
1294
|
+
};
|
|
1295
|
+
function lifecycleStateForOutcome(status) {
|
|
1296
|
+
switch (status) {
|
|
1297
|
+
case "blocked":
|
|
1298
|
+
case "needs-input":
|
|
1299
|
+
case "abstained":
|
|
1300
|
+
case "stale":
|
|
1301
|
+
return status;
|
|
1302
|
+
case "failed":
|
|
1303
|
+
case "contract-invalid":
|
|
1304
|
+
return "failed";
|
|
1305
|
+
case "interrupted":
|
|
1306
|
+
return "interrupted";
|
|
1307
|
+
default:
|
|
1308
|
+
return "completed";
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1311
|
+
function copyStructuredResult(value) {
|
|
1312
|
+
return structuredClone(value);
|
|
1313
|
+
}
|
|
1314
|
+
function copyTelemetry(value) {
|
|
1315
|
+
return {
|
|
1316
|
+
...value,
|
|
1317
|
+
timing: { ...value.timing },
|
|
1318
|
+
usage: value.usage ? { ...value.usage } : void 0
|
|
1319
|
+
};
|
|
1320
|
+
}
|
|
1321
|
+
export {
|
|
1322
|
+
AgentRegistry
|
|
1323
|
+
};
|
|
1324
|
+
//# sourceMappingURL=registry-E6XPJB7L.ts.map
|