@ai-agent-forge/plugin-subagent-delegate 0.88.11 → 0.88.13
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 +9 -0
- package/agent-forge.json +1 -1
- package/dist/delegate.d.ts +13 -1
- package/dist/delegate.d.ts.map +1 -1
- package/dist/delegate.js +115 -8
- package/dist/delegate.js.map +1 -1
- package/dist/entry.d.ts +3 -0
- package/dist/entry.d.ts.map +1 -1
- package/dist/entry.js +6 -0
- package/dist/entry.js.map +1 -1
- package/dist/remote-adapter.d.ts.map +1 -1
- package/dist/remote-adapter.js +0 -7
- package/dist/remote-adapter.js.map +1 -1
- package/dist/runner-pool.d.ts.map +1 -1
- package/dist/runner-pool.js +62 -26
- package/dist/runner-pool.js.map +1 -1
- package/dist/subagent-runtime.d.ts +5 -1
- package/dist/subagent-runtime.d.ts.map +1 -1
- package/dist/subagent-runtime.js +180 -22
- package/dist/subagent-runtime.js.map +1 -1
- package/package.json +5 -4
- package/plugin.json +1 -1
package/dist/runner-pool.js
CHANGED
|
@@ -65,19 +65,30 @@ export function validateSubagentRunnerPoolOptionsV1(options) {
|
|
|
65
65
|
* 就绪握手:在把通道交给 remote adapter 之前做一次 hello 往返,确认 runner 存活
|
|
66
66
|
* 且协议版本一致;超时/版本不符视为启动失败(调用方负责 kill)。
|
|
67
67
|
*/
|
|
68
|
-
async function handshakeRunnerV1(channel, timeoutMs) {
|
|
68
|
+
async function handshakeRunnerV1(channel, timeoutMs, signal) {
|
|
69
|
+
signal.throwIfAborted();
|
|
69
70
|
const id = `hello:${randomUUID().slice(0, 8)}`;
|
|
70
71
|
const iterator = channel[Symbol.asyncIterator]();
|
|
71
72
|
let timer;
|
|
72
73
|
const timeout = new Promise((_resolve, reject) => {
|
|
73
74
|
timer = setTimeout(() => reject(new SubagentTaskProtocolError(`runner handshake timed out after ${timeoutMs}ms`, "runner_lost")), timeoutMs);
|
|
74
75
|
});
|
|
76
|
+
let rejectCancelled;
|
|
77
|
+
const cancelled = new Promise((_resolve, reject) => {
|
|
78
|
+
rejectCancelled = reject;
|
|
79
|
+
});
|
|
80
|
+
const onAbort = () => rejectCancelled(signal.reason);
|
|
81
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
75
82
|
try {
|
|
76
|
-
await
|
|
83
|
+
await Promise.race([
|
|
84
|
+
channel.write({ v: SUBAGENT_TASK_PROTOCOL_VERSION, kind: "request", id, op: "hello" }),
|
|
85
|
+
cancelled,
|
|
86
|
+
timeout,
|
|
87
|
+
]);
|
|
77
88
|
for (;;) {
|
|
78
89
|
const nextPromise = iterator.next();
|
|
79
90
|
void nextPromise.catch(() => { });
|
|
80
|
-
const next = await Promise.race([nextPromise, timeout]);
|
|
91
|
+
const next = await Promise.race([nextPromise, timeout, cancelled]);
|
|
81
92
|
if (next.done) {
|
|
82
93
|
throw new SubagentTaskProtocolError("runner closed during handshake", "runner_lost");
|
|
83
94
|
}
|
|
@@ -96,6 +107,7 @@ async function handshakeRunnerV1(channel, timeoutMs) {
|
|
|
96
107
|
finally {
|
|
97
108
|
if (timer !== undefined)
|
|
98
109
|
clearTimeout(timer);
|
|
110
|
+
signal.removeEventListener("abort", onAbort);
|
|
99
111
|
}
|
|
100
112
|
}
|
|
101
113
|
function slotsUsed(runner) {
|
|
@@ -114,6 +126,8 @@ export function createPooledAgentTaskAdapterV1(options) {
|
|
|
114
126
|
let closed = false;
|
|
115
127
|
let sweepTimer;
|
|
116
128
|
let spawnInFlight;
|
|
129
|
+
let pendingSpawnHandle;
|
|
130
|
+
const shutdownController = new AbortController();
|
|
117
131
|
/** 摘除已死 runner(句柄与状态都不再需要),避免长寿命池按 spawn 次数增长。 */
|
|
118
132
|
const pruneDeadRunners = () => {
|
|
119
133
|
for (let index = runners.length - 1; index >= 0; index -= 1) {
|
|
@@ -143,30 +157,33 @@ export function createPooledAgentTaskAdapterV1(options) {
|
|
|
143
157
|
return runner;
|
|
144
158
|
};
|
|
145
159
|
const spawnRunner = async () => {
|
|
160
|
+
shutdownController.signal.throwIfAborted();
|
|
146
161
|
// 并发 create 共享同一次 spawn,避免超发进程。
|
|
147
162
|
spawnInFlight ??= (async () => {
|
|
163
|
+
const handle = await options.spawnRunner();
|
|
164
|
+
pendingSpawnHandle = handle;
|
|
148
165
|
try {
|
|
149
|
-
|
|
166
|
+
shutdownController.signal.throwIfAborted();
|
|
167
|
+
// 就绪握手在把通道交给 remote adapter 之前完成(adapter 启动后独占读取)。
|
|
168
|
+
await handshakeRunnerV1(handle.channel, handshakeTimeoutMs, shutdownController.signal);
|
|
169
|
+
shutdownController.signal.throwIfAborted();
|
|
170
|
+
}
|
|
171
|
+
catch (error) {
|
|
172
|
+
// 握手失败:进程不可用,回收并上抛(调用方按启动失败处理)。
|
|
150
173
|
try {
|
|
151
|
-
|
|
152
|
-
await handshakeRunnerV1(handle.channel, handshakeTimeoutMs);
|
|
174
|
+
handle.kill();
|
|
153
175
|
}
|
|
154
|
-
catch
|
|
155
|
-
//
|
|
156
|
-
try {
|
|
157
|
-
handle.kill();
|
|
158
|
-
}
|
|
159
|
-
catch {
|
|
160
|
-
// 已退出:忽略
|
|
161
|
-
}
|
|
162
|
-
throw error;
|
|
176
|
+
catch {
|
|
177
|
+
// 已退出:忽略
|
|
163
178
|
}
|
|
164
|
-
|
|
165
|
-
}
|
|
166
|
-
finally {
|
|
167
|
-
spawnInFlight = undefined;
|
|
179
|
+
throw error;
|
|
168
180
|
}
|
|
169
|
-
|
|
181
|
+
return trackRunner(handle);
|
|
182
|
+
})().finally(() => {
|
|
183
|
+
// 清理在赋值后的微任务执行;同步 spawn 抛错不能把失败 Promise 留在池中。
|
|
184
|
+
spawnInFlight = undefined;
|
|
185
|
+
pendingSpawnHandle = undefined;
|
|
186
|
+
});
|
|
170
187
|
return spawnInFlight;
|
|
171
188
|
};
|
|
172
189
|
const pickRunner = () => {
|
|
@@ -184,6 +201,8 @@ export function createPooledAgentTaskAdapterV1(options) {
|
|
|
184
201
|
return best;
|
|
185
202
|
};
|
|
186
203
|
const createOnRunner = async (runner, request) => {
|
|
204
|
+
// 首选和恢复 runner 共用准入,异步 spawn 完成不代表池仍可创建任务。
|
|
205
|
+
shutdownController.signal.throwIfAborted();
|
|
187
206
|
runner.pending += 1;
|
|
188
207
|
let host;
|
|
189
208
|
try {
|
|
@@ -192,26 +211,34 @@ export function createPooledAgentTaskAdapterV1(options) {
|
|
|
192
211
|
finally {
|
|
193
212
|
runner.pending -= 1;
|
|
194
213
|
}
|
|
214
|
+
// 回执可能晚于 shutdown;关闭后的任务由 runner 回收,不再发布给调度器。
|
|
215
|
+
shutdownController.signal.throwIfAborted();
|
|
216
|
+
if (!runner.alive) {
|
|
217
|
+
throw new SubagentTaskProtocolError("subagent runner lost before task publication", "runner_lost");
|
|
218
|
+
}
|
|
195
219
|
const taskId = host.session.getSessionId();
|
|
196
220
|
runner.liveTasks.add(taskId);
|
|
197
221
|
taskRunnerAdapters.set(taskId, runner.adapter);
|
|
198
222
|
runner.idleSince = 0;
|
|
199
223
|
const remoteSession = host.session;
|
|
200
|
-
// 包装 session
|
|
224
|
+
// 包装 session:保留远程会话身份与 steer;dispose 时归还槽位。
|
|
201
225
|
// 事实面(getActiveTools/getModel)必须一并透传:档案核验靠它自证,包装层吞掉
|
|
202
226
|
// 就等于把"已核验"变成"无法核验"。
|
|
227
|
+
const remoteGetActiveTools = remoteSession.getActiveTools?.bind(remoteSession);
|
|
203
228
|
const session = {
|
|
204
229
|
getSessionId: () => remoteSession.getSessionId(),
|
|
230
|
+
...(remoteSession.getAgentSessionId === undefined
|
|
231
|
+
? {}
|
|
232
|
+
: { getAgentSessionId: remoteSession.getAgentSessionId.bind(remoteSession) }),
|
|
205
233
|
getMessages: () => remoteSession.getMessages(),
|
|
206
234
|
isIdle: () => remoteSession.isIdle(),
|
|
207
235
|
prompt: (text) => remoteSession.prompt(text),
|
|
236
|
+
...(remoteSession.steer === undefined ? {} : { steer: remoteSession.steer.bind(remoteSession) }),
|
|
208
237
|
abort: () => remoteSession.abort(),
|
|
209
238
|
waitForIdle: () => remoteSession.waitForIdle(),
|
|
210
239
|
appendEntry: (customType, data) => remoteSession.appendEntry(customType, data),
|
|
211
240
|
getBranchEntries: () => remoteSession.getBranchEntries(),
|
|
212
|
-
...(
|
|
213
|
-
? {}
|
|
214
|
-
: { getActiveTools: () => remoteSession.getActiveTools?.() ?? [] }),
|
|
241
|
+
...(remoteGetActiveTools === undefined ? {} : { getActiveTools: () => remoteGetActiveTools() }),
|
|
215
242
|
...(remoteSession.getModel === undefined ? {} : { getModel: () => remoteSession.getModel?.() }),
|
|
216
243
|
};
|
|
217
244
|
return {
|
|
@@ -271,6 +298,7 @@ export function createPooledAgentTaskAdapterV1(options) {
|
|
|
271
298
|
throw new SubagentRunnerPoolError("subagent_runner_capacity", `no runner capacity: ${options.maxRunnerProcesses} runner(s) at ${options.maxSessionsPerRunner} session(s) each`);
|
|
272
299
|
}
|
|
273
300
|
await spawnRunner();
|
|
301
|
+
shutdownController.signal.throwIfAborted();
|
|
274
302
|
runner = pickRunner();
|
|
275
303
|
}
|
|
276
304
|
try {
|
|
@@ -278,14 +306,18 @@ export function createPooledAgentTaskAdapterV1(options) {
|
|
|
278
306
|
}
|
|
279
307
|
catch (error) {
|
|
280
308
|
if (error instanceof SubagentTaskProtocolError && error.code === "runner_lost") {
|
|
309
|
+
shutdownController.signal.throwIfAborted();
|
|
281
310
|
// 分配期失败无副作用:换一个 runner 重试一次;无可用槽位且不能扩则上抛容量错误。
|
|
282
311
|
markDead(runner, error);
|
|
283
312
|
let fallback = pickRunner();
|
|
284
|
-
|
|
313
|
+
while (fallback === undefined) {
|
|
285
314
|
if (runners.filter((candidate) => candidate.alive).length >= options.maxRunnerProcesses) {
|
|
286
315
|
throw new SubagentRunnerPoolError("subagent_runner_lost", "runner lost and no spare capacity");
|
|
287
316
|
}
|
|
288
|
-
|
|
317
|
+
await spawnRunner();
|
|
318
|
+
shutdownController.signal.throwIfAborted();
|
|
319
|
+
// 共享 spawn 的空槽可能已被另一恢复或普通创建预留,必须重新挑选。
|
|
320
|
+
fallback = pickRunner();
|
|
289
321
|
}
|
|
290
322
|
if (fallback !== runner)
|
|
291
323
|
return createOnRunner(fallback, request);
|
|
@@ -306,8 +338,12 @@ export function createPooledAgentTaskAdapterV1(options) {
|
|
|
306
338
|
if (closed)
|
|
307
339
|
return;
|
|
308
340
|
closed = true;
|
|
341
|
+
shutdownController.abort(new SubagentRunnerPoolError("subagent_pool_closed", "subagent runner pool is closed"));
|
|
309
342
|
if (sweepTimer !== undefined)
|
|
310
343
|
clearInterval(sweepTimer);
|
|
344
|
+
// 正在 spawn 的句柄归 spawnRunner 回收;握手可取消,已登记 runner 在下面释放。
|
|
345
|
+
if (pendingSpawnHandle !== undefined && spawnInFlight !== undefined)
|
|
346
|
+
await Promise.allSettled([spawnInFlight]);
|
|
311
347
|
const alive = runners.filter((runner) => runner.alive);
|
|
312
348
|
await Promise.allSettled(alive.map((runner) => runner.adapter.dispose?.()));
|
|
313
349
|
for (const runner of alive) {
|
package/dist/runner-pool.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runner-pool.js","sourceRoot":"","sources":["../src/runner-pool.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * 池化 runner adapter(D-045 / pooled 切片增量 2)。\n *\n * `AgentTaskAdapter` 的 runner 池实现:按 `maxSessionsPerRunner` 选\"空闲槽位最多\"\n * 的 runner(确定性);无空闲槽位且未达 `maxRunnerProcesses` 时按需 spawn;runner\n * 空闲超时退出(不迁移任务);进程死亡时在途任务以 `runner_lost` 结算(D-044 §4.3\n * 投影 failed + reasonCode subagent_runner_lost)。\n *\n * 分层约束:调度器(SubagentRuntime)继续持有队列与准入;本层只提供\"容量 + 进程\n * 生命周期\"。宿主接线必须保证 `maxConcurrentSubagents <= maxRunnerProcesses *\n * maxSessionsPerRunner`,否则超出容量的准入会以 `subagent_runner_capacity` 失败。\n * spawn 由 `spawnRunner` 注入:生产侧经 stdio 派生子进程,测试注入进程内 fake。\n *\n * 迁自宿主 `src/plugins/subagent-task/runner-pool.ts`(D-075 S4 第三批);公共契约改自\n * plugin-sdk,wire 协议类型改自 @agent-forge/protocol(宿主侧同名模块已下沉该包)。\n */\nimport { randomUUID } from \"node:crypto\";\nimport type { AgentTaskAdapter, AgentTaskCreateRequest, AgentTaskHost, SessionAPI } from \"@agent-forge/plugin-sdk\";\nimport {\n\tSUBAGENT_TASK_PROTOCOL_VERSION,\n\ttype SubagentTaskChannelV1,\n\tSubagentTaskProtocolError,\n} from \"@agent-forge/protocol/subagent\";\nimport { createRemoteAgentTaskAdapter } from \"./remote-adapter.ts\";\n\nexport interface SubagentRunnerSpawnHandleV1 {\n\t/** 与 runner 进程对话的通道(父侧)。 */\n\treadonly channel: SubagentTaskChannelV1;\n\t/** 进程退出通知:error 非空表示异常退出。 */\n\tonExit(handler: (error?: Error) => void): void;\n\t/** 请求进程退出(空闲回收与池关闭使用)。 */\n\tkill(): void;\n}\n\nexport interface SubagentRunnerPoolOptionsV1 {\n\tspawnRunner(): Promise<SubagentRunnerSpawnHandleV1> | SubagentRunnerSpawnHandleV1;\n\tmaxRunnerProcesses: number;\n\tmaxSessionsPerRunner: number;\n\tidleRunnerTimeoutMs: number;\n\t/**\n\t * 宿主全局并发(调度器 maxConcurrentSubagents)。提供时参与 fail-fast 校验:\n\t * 必须 <= maxRunnerProcesses * maxSessionsPerRunner,否则超出容量的准入会以容量错误失败。\n\t */\n\tmaxConcurrentSubagents?: number;\n\t/** 就绪握手超时(默认 5000ms):runner 未在期限内应答 hello 视为启动失败。 */\n\thandshakeTimeoutMs?: number;\n\t/** 确定性时钟(默认 Date.now)。 */\n\tclock?: () => number;\n\tidleCheckIntervalMs?: number;\n\tonDiagnostic?(message: string): void;\n}\n\nexport interface SubagentRunnerPoolStatsV1 {\n\treadonly runners: number;\n\treadonly aliveRunners: number;\n\treadonly liveTasks: number;\n\treadonly spawnedTotal: number;\n}\n\nexport interface SubagentRunnerPoolV1 extends AgentTaskAdapter {\n\t/** D-044 §5.6:按 wire taskId(= host.session.getSessionId())订阅运行中进度。 */\n\tonTaskProgress(taskId: string, listener: (message: string | undefined, seq: number) => void): () => void;\n\t/** 空闲回收扫描(宿主心跳调用;也可由内部定时器驱动)。 */\n\tsweep(): void;\n\tstats(): SubagentRunnerPoolStatsV1;\n\tshutdown(): Promise<void>;\n}\n\nexport class SubagentRunnerPoolError extends Error {\n\treadonly code:\n\t\t| \"subagent_runner_capacity\"\n\t\t| \"subagent_runner_lost\"\n\t\t| \"subagent_pool_closed\"\n\t\t| \"subagent_pool_config_invalid\";\n\n\tconstructor(\n\t\tcode:\n\t\t\t| \"subagent_runner_capacity\"\n\t\t\t| \"subagent_runner_lost\"\n\t\t\t| \"subagent_pool_closed\"\n\t\t\t| \"subagent_pool_config_invalid\",\n\t\tmessage: string,\n\t) {\n\t\tsuper(message);\n\t\tthis.name = \"SubagentRunnerPoolError\";\n\t\tthis.code = code;\n\t}\n}\n\nconst MAX_RUNNER_PROCESSES = 5;\nconst DEFAULT_HANDSHAKE_TIMEOUT_MS = 5000;\n\n/**\n * 宿主接线 fail-fast 校验(D-044 §8):配置违规在构造期抛出,不静默钳制。\n * `maxRunnerProcesses = 0` 表示不启用 pooled(由宿主决定不构造本 adapter),\n * 因此本校验要求 >= 1。\n */\nexport function validateSubagentRunnerPoolOptionsV1(options: {\n\tmaxRunnerProcesses: number;\n\tmaxSessionsPerRunner: number;\n\tmaxConcurrentSubagents?: number;\n\tidleRunnerTimeoutMs: number;\n\thandshakeTimeoutMs?: number;\n}): void {\n\tconst fail = (message: string): never => {\n\t\tthrow new SubagentRunnerPoolError(\"subagent_pool_config_invalid\", message);\n\t};\n\tif (\n\t\t!Number.isInteger(options.maxRunnerProcesses) ||\n\t\toptions.maxRunnerProcesses < 1 ||\n\t\toptions.maxRunnerProcesses > MAX_RUNNER_PROCESSES\n\t) {\n\t\tfail(\n\t\t\t`maxRunnerProcesses must be an integer between 1 and ${MAX_RUNNER_PROCESSES}, got ${String(options.maxRunnerProcesses)}`,\n\t\t);\n\t}\n\tif (!Number.isInteger(options.maxSessionsPerRunner) || options.maxSessionsPerRunner < 1) {\n\t\tfail(`maxSessionsPerRunner must be a positive integer, got ${String(options.maxSessionsPerRunner)}`);\n\t}\n\tif (typeof options.maxConcurrentSubagents === \"number\") {\n\t\tif (!Number.isInteger(options.maxConcurrentSubagents) || options.maxConcurrentSubagents < 1) {\n\t\t\tfail(`maxConcurrentSubagents must be a positive integer, got ${String(options.maxConcurrentSubagents)}`);\n\t\t}\n\t\tconst capacity = options.maxRunnerProcesses * options.maxSessionsPerRunner;\n\t\tif (options.maxConcurrentSubagents > capacity) {\n\t\t\tfail(`maxConcurrentSubagents (${options.maxConcurrentSubagents}) exceeds runner capacity (${capacity})`);\n\t\t}\n\t}\n\tif (!Number.isInteger(options.idleRunnerTimeoutMs) || options.idleRunnerTimeoutMs < 1) {\n\t\tfail(`idleRunnerTimeoutMs must be a positive integer, got ${String(options.idleRunnerTimeoutMs)}`);\n\t}\n\tif (\n\t\toptions.handshakeTimeoutMs !== undefined &&\n\t\t(!Number.isInteger(options.handshakeTimeoutMs) || options.handshakeTimeoutMs < 1)\n\t) {\n\t\tfail(`handshakeTimeoutMs must be a positive integer, got ${String(options.handshakeTimeoutMs)}`);\n\t}\n}\n\n/**\n * 就绪握手:在把通道交给 remote adapter 之前做一次 hello 往返,确认 runner 存活\n * 且协议版本一致;超时/版本不符视为启动失败(调用方负责 kill)。\n */\nasync function handshakeRunnerV1(channel: SubagentTaskChannelV1, timeoutMs: number): Promise<void> {\n\tconst id = `hello:${randomUUID().slice(0, 8)}`;\n\tconst iterator = channel[Symbol.asyncIterator]();\n\tlet timer: ReturnType<typeof setTimeout> | undefined;\n\tconst timeout = new Promise<never>((_resolve, reject) => {\n\t\ttimer = setTimeout(\n\t\t\t() => reject(new SubagentTaskProtocolError(`runner handshake timed out after ${timeoutMs}ms`, \"runner_lost\")),\n\t\t\ttimeoutMs,\n\t\t);\n\t});\n\ttry {\n\t\tawait channel.write({ v: SUBAGENT_TASK_PROTOCOL_VERSION, kind: \"request\", id, op: \"hello\" });\n\t\tfor (;;) {\n\t\t\tconst nextPromise = iterator.next();\n\t\t\tvoid nextPromise.catch(() => {});\n\t\t\tconst next = await Promise.race([nextPromise, timeout]);\n\t\t\tif (next.done) {\n\t\t\t\tthrow new SubagentTaskProtocolError(\"runner closed during handshake\", \"runner_lost\");\n\t\t\t}\n\t\t\tconst env = next.value;\n\t\t\tif (env.kind !== \"response\" || env.id !== id) continue; // 握手期不应有其它信封:忽略等待\n\t\t\tif (!env.ok) {\n\t\t\t\tthrow new SubagentTaskProtocolError(`runner rejected handshake: ${env.message}`, env.code);\n\t\t\t}\n\t\t\tif (env.protocolVersion !== SUBAGENT_TASK_PROTOCOL_VERSION) {\n\t\t\t\tthrow new SubagentTaskProtocolError(\n\t\t\t\t\t`subagent-task protocol mismatch: runner=${String(env.protocolVersion)} host=${SUBAGENT_TASK_PROTOCOL_VERSION}`,\n\t\t\t\t\t\"invalid_request\",\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t} finally {\n\t\tif (timer !== undefined) clearTimeout(timer);\n\t}\n}\n\ninterface PoolRunner {\n\treadonly adapter: AgentTaskAdapter;\n\treadonly handle: SubagentRunnerSpawnHandleV1;\n\treadonly liveTasks: Set<string>;\n\t/**\n\t * 分配中(adapter.create 已发起、回执未到)的预留槽位。并发 create 必须在等待\n\t * 回执期间就占位,否则同一 runner 会被超发(压测:40 个并发 create 全落在同一个\n\t * 刚 spawn 的 runner 上,扇出从未发生)。\n\t */\n\tpending: number;\n\talive: boolean;\n\tidleSince: number;\n}\n\nfunction slotsUsed(runner: PoolRunner): number {\n\treturn runner.liveTasks.size + runner.pending;\n}\n\nexport function createPooledAgentTaskAdapterV1(options: SubagentRunnerPoolOptionsV1): SubagentRunnerPoolV1 {\n\t// 配置违规在构造期 fail-fast(D-044 §8),不静默钳制。\n\tvalidateSubagentRunnerPoolOptionsV1(options);\n\tconst clock = options.clock ?? Date.now;\n\tconst handshakeTimeoutMs = options.handshakeTimeoutMs ?? DEFAULT_HANDSHAKE_TIMEOUT_MS;\n\tconst diagnostic = options.onDiagnostic ?? ((): void => {});\n\tconst runners: PoolRunner[] = [];\n\t/** wire taskId(= host.session.getSessionId())-> 承载它的 runner adapter,用于进度转发。 */\n\tconst taskRunnerAdapters = new Map<string, AgentTaskAdapter>();\n\tlet spawnedTotal = 0;\n\tlet closed = false;\n\tlet sweepTimer: ReturnType<typeof setInterval> | undefined;\n\tlet spawnInFlight: Promise<PoolRunner> | undefined;\n\n\t/** 摘除已死 runner(句柄与状态都不再需要),避免长寿命池按 spawn 次数增长。 */\n\tconst pruneDeadRunners = (): void => {\n\t\tfor (let index = runners.length - 1; index >= 0; index -= 1) {\n\t\t\tif (!runners[index]?.alive) runners.splice(index, 1);\n\t\t}\n\t};\n\n\tconst markDead = (runner: PoolRunner, error?: Error): void => {\n\t\tif (!runner.alive) return;\n\t\trunner.alive = false;\n\t\trunner.liveTasks.clear();\n\t\tdiagnostic(`subagent runner lost: ${error?.message ?? \"process exited\"}`);\n\t};\n\n\tconst trackRunner = (handle: SubagentRunnerSpawnHandleV1): PoolRunner => {\n\t\tconst runner: PoolRunner = {\n\t\t\tadapter: createRemoteAgentTaskAdapter(handle.channel),\n\t\t\thandle,\n\t\t\tliveTasks: new Set<string>(),\n\t\t\tpending: 0,\n\t\t\talive: true,\n\t\t\tidleSince: clock(),\n\t\t};\n\t\thandle.onExit((error?: Error) => markDead(runner, error));\n\t\trunners.push(runner);\n\t\tspawnedTotal += 1;\n\t\treturn runner;\n\t};\n\n\tconst spawnRunner = async (): Promise<PoolRunner> => {\n\t\t// 并发 create 共享同一次 spawn,避免超发进程。\n\t\tspawnInFlight ??= (async () => {\n\t\t\ttry {\n\t\t\t\tconst handle = await options.spawnRunner();\n\t\t\t\ttry {\n\t\t\t\t\t// 就绪握手在把通道交给 remote adapter 之前完成(adapter 启动后独占读取)。\n\t\t\t\t\tawait handshakeRunnerV1(handle.channel, handshakeTimeoutMs);\n\t\t\t\t} catch (error) {\n\t\t\t\t\t// 握手失败:进程不可用,回收并上抛(调用方按启动失败处理)。\n\t\t\t\t\ttry {\n\t\t\t\t\t\thandle.kill();\n\t\t\t\t\t} catch {\n\t\t\t\t\t\t// 已退出:忽略\n\t\t\t\t\t}\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t\treturn trackRunner(handle);\n\t\t\t} finally {\n\t\t\t\tspawnInFlight = undefined;\n\t\t\t}\n\t\t})();\n\t\treturn spawnInFlight;\n\t};\n\n\tconst pickRunner = (): PoolRunner | undefined => {\n\t\tlet best: PoolRunner | undefined;\n\t\tlet bestFree = 0;\n\t\tfor (const runner of runners) {\n\t\t\tif (!runner.alive) continue;\n\t\t\tconst free = options.maxSessionsPerRunner - slotsUsed(runner);\n\t\t\tif (free > bestFree) {\n\t\t\t\tbest = runner;\n\t\t\t\tbestFree = free;\n\t\t\t}\n\t\t}\n\t\treturn best;\n\t};\n\n\tconst createOnRunner = async (runner: PoolRunner, request: AgentTaskCreateRequest): Promise<AgentTaskHost> => {\n\t\trunner.pending += 1;\n\t\tlet host: AgentTaskHost;\n\t\ttry {\n\t\t\thost = await runner.adapter.create(request);\n\t\t} finally {\n\t\t\trunner.pending -= 1;\n\t\t}\n\t\tconst taskId = host.session.getSessionId();\n\t\trunner.liveTasks.add(taskId);\n\t\ttaskRunnerAdapters.set(taskId, runner.adapter);\n\t\trunner.idleSince = 0;\n\t\tconst remoteSession = host.session;\n\t\t// 包装 session:调度器只读 getSessionId/getMessages,其余透传;dispose 时归还槽位。\n\t\t// 事实面(getActiveTools/getModel)必须一并透传:档案核验靠它自证,包装层吞掉\n\t\t// 就等于把\"已核验\"变成\"无法核验\"。\n\t\tconst session: SessionAPI = {\n\t\t\tgetSessionId: () => remoteSession.getSessionId(),\n\t\t\tgetMessages: () => remoteSession.getMessages(),\n\t\t\tisIdle: () => remoteSession.isIdle(),\n\t\t\tprompt: (text) => remoteSession.prompt(text),\n\t\t\tabort: () => remoteSession.abort(),\n\t\t\twaitForIdle: () => remoteSession.waitForIdle(),\n\t\t\tappendEntry: (customType, data) => remoteSession.appendEntry(customType, data),\n\t\t\tgetBranchEntries: () => remoteSession.getBranchEntries(),\n\t\t\t...(remoteSession.getActiveTools === undefined\n\t\t\t\t? {}\n\t\t\t\t: { getActiveTools: () => remoteSession.getActiveTools?.() ?? [] }),\n\t\t\t...(remoteSession.getModel === undefined ? {} : { getModel: () => remoteSession.getModel?.() }),\n\t\t};\n\t\treturn {\n\t\t\tsession,\n\t\t\trun: (prompt, signal) => host.run(prompt, signal),\n\t\t\tcancel: (reason) => host.cancel(reason),\n\t\t\tdispose: async () => {\n\t\t\t\trunner.liveTasks.delete(taskId);\n\t\t\t\ttaskRunnerAdapters.delete(taskId);\n\t\t\t\tif (runner.liveTasks.size === 0) runner.idleSince = clock();\n\t\t\t\tawait host.dispose();\n\t\t\t},\n\t\t};\n\t};\n\n\tconst sweep = (): void => {\n\t\tif (closed) return;\n\t\tpruneDeadRunners();\n\t\tconst now = clock();\n\t\tfor (const runner of runners) {\n\t\t\t// pending > 0 = create 仍在途(真实 runner 的 spawn+握手+会话创建可达\n\t\t\t// 数秒):在途创建是活跃工作,不是空闲——sweep 不得打点也不得回收,\n\t\t\t// 否则激进 idleRunnerTimeoutMs 下 create 中途通道被杀(live 用例实证)。\n\t\t\tif (!runner.alive || runner.pending > 0 || runner.liveTasks.size > 0) continue;\n\t\t\tif (runner.idleSince === 0) runner.idleSince = now;\n\t\t\tif (now - runner.idleSince < options.idleRunnerTimeoutMs) continue;\n\t\t\t// 空闲超时回收:退出前必须无活跃任务(不迁移),dead 标记阻止后续分配。\n\t\t\trunner.alive = false;\n\t\t\tdiagnostic(\"subagent runner idle exit\");\n\t\t\ttry {\n\t\t\t\trunner.handle.kill();\n\t\t\t} catch {\n\t\t\t\t// 进程已退出:忽略\n\t\t\t}\n\t\t}\n\t};\n\n\tconst adapter: SubagentRunnerPoolV1 = {\n\t\t/** D-044 §5.6:转发到承载该任务的 runner 远程 adapter。 */\n\t\tonTaskProgress(taskId: string, listener: (message: string | undefined, seq: number) => void): () => void {\n\t\t\tconst runnerAdapter = taskRunnerAdapters.get(taskId) as\n\t\t\t\t| (AgentTaskAdapter & { onTaskProgress?: (id: string, cb: typeof listener) => () => void })\n\t\t\t\t| undefined;\n\t\t\treturn runnerAdapter?.onTaskProgress?.(taskId, listener) ?? (() => {});\n\t\t},\n\t\tasync create(request: AgentTaskCreateRequest): Promise<AgentTaskHost> {\n\t\t\tif (closed) throw new SubagentRunnerPoolError(\"subagent_pool_closed\", \"subagent runner pool is closed\");\n\t\t\tpruneDeadRunners();\n\t\t\t// 放置循环:并发 create 共享同一次 spawn,因此拿到共享结果后必须**重新**挑选\n\t\t\t// 有空槽的 runner(新 spawn 的那个同样有槽位上限),容量耗尽才报错。\n\t\t\tlet runner = pickRunner();\n\t\t\twhile (runner === undefined) {\n\t\t\t\tif (runners.filter((candidate) => candidate.alive).length >= options.maxRunnerProcesses) {\n\t\t\t\t\tthrow new SubagentRunnerPoolError(\n\t\t\t\t\t\t\"subagent_runner_capacity\",\n\t\t\t\t\t\t`no runner capacity: ${options.maxRunnerProcesses} runner(s) at ${options.maxSessionsPerRunner} session(s) each`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tawait spawnRunner();\n\t\t\t\trunner = pickRunner();\n\t\t\t}\n\t\t\ttry {\n\t\t\t\treturn await createOnRunner(runner, request);\n\t\t\t} catch (error) {\n\t\t\t\tif (error instanceof SubagentTaskProtocolError && error.code === \"runner_lost\") {\n\t\t\t\t\t// 分配期失败无副作用:换一个 runner 重试一次;无可用槽位且不能扩则上抛容量错误。\n\t\t\t\t\tmarkDead(runner, error);\n\t\t\t\t\tlet fallback = pickRunner();\n\t\t\t\t\tif (fallback === undefined) {\n\t\t\t\t\t\tif (runners.filter((candidate) => candidate.alive).length >= options.maxRunnerProcesses) {\n\t\t\t\t\t\t\tthrow new SubagentRunnerPoolError(\"subagent_runner_lost\", \"runner lost and no spare capacity\");\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfallback = await spawnRunner();\n\t\t\t\t\t}\n\t\t\t\t\tif (fallback !== runner) return createOnRunner(fallback, request);\n\t\t\t\t}\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t},\n\t\tsweep,\n\t\tstats(): SubagentRunnerPoolStatsV1 {\n\t\t\treturn {\n\t\t\t\trunners: runners.length,\n\t\t\t\taliveRunners: runners.filter((runner) => runner.alive).length,\n\t\t\t\tliveTasks: runners.reduce((total, runner) => total + runner.liveTasks.size, 0),\n\t\t\t\tspawnedTotal,\n\t\t\t};\n\t\t},\n\t\tasync shutdown(): Promise<void> {\n\t\t\tif (closed) return;\n\t\t\tclosed = true;\n\t\t\tif (sweepTimer !== undefined) clearInterval(sweepTimer);\n\t\t\tconst alive = runners.filter((runner) => runner.alive);\n\t\t\tawait Promise.allSettled(alive.map((runner) => runner.adapter.dispose?.()));\n\t\t\tfor (const runner of alive) {\n\t\t\t\trunner.alive = false;\n\t\t\t\ttry {\n\t\t\t\t\trunner.handle.kill();\n\t\t\t\t} catch {\n\t\t\t\t\t// 已退出:忽略\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t};\n\n\tif (options.idleCheckIntervalMs !== undefined && options.idleCheckIntervalMs > 0) {\n\t\tsweepTimer = setInterval(sweep, options.idleCheckIntervalMs);\n\t\tsweepTimer.unref?.();\n\t}\n\n\treturn adapter;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"runner-pool.js","sourceRoot":"","sources":["../src/runner-pool.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * 池化 runner adapter(D-045 / pooled 切片增量 2)。\n *\n * `AgentTaskAdapter` 的 runner 池实现:按 `maxSessionsPerRunner` 选\"空闲槽位最多\"\n * 的 runner(确定性);无空闲槽位且未达 `maxRunnerProcesses` 时按需 spawn;runner\n * 空闲超时退出(不迁移任务);进程死亡时在途任务以 `runner_lost` 结算(D-044 §4.3\n * 投影 failed + reasonCode subagent_runner_lost)。\n *\n * 分层约束:调度器(SubagentRuntime)继续持有队列与准入;本层只提供\"容量 + 进程\n * 生命周期\"。宿主接线必须保证 `maxConcurrentSubagents <= maxRunnerProcesses *\n * maxSessionsPerRunner`,否则超出容量的准入会以 `subagent_runner_capacity` 失败。\n * spawn 由 `spawnRunner` 注入:生产侧经 stdio 派生子进程,测试注入进程内 fake。\n *\n * 迁自宿主 `src/plugins/subagent-task/runner-pool.ts`(D-075 S4 第三批);公共契约改自\n * plugin-sdk,wire 协议类型改自 @agent-forge/protocol(宿主侧同名模块已下沉该包)。\n */\nimport { randomUUID } from \"node:crypto\";\nimport type { AgentTaskAdapter, AgentTaskCreateRequest, AgentTaskHost, SessionAPI } from \"@agent-forge/plugin-sdk\";\nimport {\n\tSUBAGENT_TASK_PROTOCOL_VERSION,\n\ttype SubagentTaskChannelV1,\n\tSubagentTaskProtocolError,\n} from \"@agent-forge/protocol/subagent\";\nimport { createRemoteAgentTaskAdapter } from \"./remote-adapter.ts\";\n\nexport interface SubagentRunnerSpawnHandleV1 {\n\t/** 与 runner 进程对话的通道(父侧)。 */\n\treadonly channel: SubagentTaskChannelV1;\n\t/** 进程退出通知:error 非空表示异常退出。 */\n\tonExit(handler: (error?: Error) => void): void;\n\t/** 请求进程退出(空闲回收与池关闭使用)。 */\n\tkill(): void;\n}\n\nexport interface SubagentRunnerPoolOptionsV1 {\n\tspawnRunner(): Promise<SubagentRunnerSpawnHandleV1> | SubagentRunnerSpawnHandleV1;\n\tmaxRunnerProcesses: number;\n\tmaxSessionsPerRunner: number;\n\tidleRunnerTimeoutMs: number;\n\t/**\n\t * 宿主全局并发(调度器 maxConcurrentSubagents)。提供时参与 fail-fast 校验:\n\t * 必须 <= maxRunnerProcesses * maxSessionsPerRunner,否则超出容量的准入会以容量错误失败。\n\t */\n\tmaxConcurrentSubagents?: number;\n\t/** 就绪握手超时(默认 5000ms):runner 未在期限内应答 hello 视为启动失败。 */\n\thandshakeTimeoutMs?: number;\n\t/** 确定性时钟(默认 Date.now)。 */\n\tclock?: () => number;\n\tidleCheckIntervalMs?: number;\n\tonDiagnostic?(message: string): void;\n}\n\nexport interface SubagentRunnerPoolStatsV1 {\n\treadonly runners: number;\n\treadonly aliveRunners: number;\n\treadonly liveTasks: number;\n\treadonly spawnedTotal: number;\n}\n\nexport interface SubagentRunnerPoolV1 extends AgentTaskAdapter {\n\t/** D-044 §5.6:按 wire taskId(= host.session.getSessionId())订阅运行中进度。 */\n\tonTaskProgress(taskId: string, listener: (message: string | undefined, seq: number) => void): () => void;\n\t/** 空闲回收扫描(宿主心跳调用;也可由内部定时器驱动)。 */\n\tsweep(): void;\n\tstats(): SubagentRunnerPoolStatsV1;\n\tshutdown(): Promise<void>;\n}\n\nexport class SubagentRunnerPoolError extends Error {\n\treadonly code:\n\t\t| \"subagent_runner_capacity\"\n\t\t| \"subagent_runner_lost\"\n\t\t| \"subagent_pool_closed\"\n\t\t| \"subagent_pool_config_invalid\";\n\n\tconstructor(\n\t\tcode:\n\t\t\t| \"subagent_runner_capacity\"\n\t\t\t| \"subagent_runner_lost\"\n\t\t\t| \"subagent_pool_closed\"\n\t\t\t| \"subagent_pool_config_invalid\",\n\t\tmessage: string,\n\t) {\n\t\tsuper(message);\n\t\tthis.name = \"SubagentRunnerPoolError\";\n\t\tthis.code = code;\n\t}\n}\n\nconst MAX_RUNNER_PROCESSES = 5;\nconst DEFAULT_HANDSHAKE_TIMEOUT_MS = 5000;\n\n/**\n * 宿主接线 fail-fast 校验(D-044 §8):配置违规在构造期抛出,不静默钳制。\n * `maxRunnerProcesses = 0` 表示不启用 pooled(由宿主决定不构造本 adapter),\n * 因此本校验要求 >= 1。\n */\nexport function validateSubagentRunnerPoolOptionsV1(options: {\n\tmaxRunnerProcesses: number;\n\tmaxSessionsPerRunner: number;\n\tmaxConcurrentSubagents?: number;\n\tidleRunnerTimeoutMs: number;\n\thandshakeTimeoutMs?: number;\n}): void {\n\tconst fail = (message: string): never => {\n\t\tthrow new SubagentRunnerPoolError(\"subagent_pool_config_invalid\", message);\n\t};\n\tif (\n\t\t!Number.isInteger(options.maxRunnerProcesses) ||\n\t\toptions.maxRunnerProcesses < 1 ||\n\t\toptions.maxRunnerProcesses > MAX_RUNNER_PROCESSES\n\t) {\n\t\tfail(\n\t\t\t`maxRunnerProcesses must be an integer between 1 and ${MAX_RUNNER_PROCESSES}, got ${String(options.maxRunnerProcesses)}`,\n\t\t);\n\t}\n\tif (!Number.isInteger(options.maxSessionsPerRunner) || options.maxSessionsPerRunner < 1) {\n\t\tfail(`maxSessionsPerRunner must be a positive integer, got ${String(options.maxSessionsPerRunner)}`);\n\t}\n\tif (typeof options.maxConcurrentSubagents === \"number\") {\n\t\tif (!Number.isInteger(options.maxConcurrentSubagents) || options.maxConcurrentSubagents < 1) {\n\t\t\tfail(`maxConcurrentSubagents must be a positive integer, got ${String(options.maxConcurrentSubagents)}`);\n\t\t}\n\t\tconst capacity = options.maxRunnerProcesses * options.maxSessionsPerRunner;\n\t\tif (options.maxConcurrentSubagents > capacity) {\n\t\t\tfail(`maxConcurrentSubagents (${options.maxConcurrentSubagents}) exceeds runner capacity (${capacity})`);\n\t\t}\n\t}\n\tif (!Number.isInteger(options.idleRunnerTimeoutMs) || options.idleRunnerTimeoutMs < 1) {\n\t\tfail(`idleRunnerTimeoutMs must be a positive integer, got ${String(options.idleRunnerTimeoutMs)}`);\n\t}\n\tif (\n\t\toptions.handshakeTimeoutMs !== undefined &&\n\t\t(!Number.isInteger(options.handshakeTimeoutMs) || options.handshakeTimeoutMs < 1)\n\t) {\n\t\tfail(`handshakeTimeoutMs must be a positive integer, got ${String(options.handshakeTimeoutMs)}`);\n\t}\n}\n\n/**\n * 就绪握手:在把通道交给 remote adapter 之前做一次 hello 往返,确认 runner 存活\n * 且协议版本一致;超时/版本不符视为启动失败(调用方负责 kill)。\n */\nasync function handshakeRunnerV1(\n\tchannel: SubagentTaskChannelV1,\n\ttimeoutMs: number,\n\tsignal: AbortSignal,\n): Promise<void> {\n\tsignal.throwIfAborted();\n\tconst id = `hello:${randomUUID().slice(0, 8)}`;\n\tconst iterator = channel[Symbol.asyncIterator]();\n\tlet timer: ReturnType<typeof setTimeout> | undefined;\n\tconst timeout = new Promise<never>((_resolve, reject) => {\n\t\ttimer = setTimeout(\n\t\t\t() => reject(new SubagentTaskProtocolError(`runner handshake timed out after ${timeoutMs}ms`, \"runner_lost\")),\n\t\t\ttimeoutMs,\n\t\t);\n\t});\n\tlet rejectCancelled!: (reason: unknown) => void;\n\tconst cancelled = new Promise<never>((_resolve, reject) => {\n\t\trejectCancelled = reject;\n\t});\n\tconst onAbort = (): void => rejectCancelled(signal.reason);\n\tsignal.addEventListener(\"abort\", onAbort, { once: true });\n\ttry {\n\t\tawait Promise.race([\n\t\t\tchannel.write({ v: SUBAGENT_TASK_PROTOCOL_VERSION, kind: \"request\", id, op: \"hello\" }),\n\t\t\tcancelled,\n\t\t\ttimeout,\n\t\t]);\n\t\tfor (;;) {\n\t\t\tconst nextPromise = iterator.next();\n\t\t\tvoid nextPromise.catch(() => {});\n\t\t\tconst next = await Promise.race([nextPromise, timeout, cancelled]);\n\t\t\tif (next.done) {\n\t\t\t\tthrow new SubagentTaskProtocolError(\"runner closed during handshake\", \"runner_lost\");\n\t\t\t}\n\t\t\tconst env = next.value;\n\t\t\tif (env.kind !== \"response\" || env.id !== id) continue; // 握手期不应有其它信封:忽略等待\n\t\t\tif (!env.ok) {\n\t\t\t\tthrow new SubagentTaskProtocolError(`runner rejected handshake: ${env.message}`, env.code);\n\t\t\t}\n\t\t\tif (env.protocolVersion !== SUBAGENT_TASK_PROTOCOL_VERSION) {\n\t\t\t\tthrow new SubagentTaskProtocolError(\n\t\t\t\t\t`subagent-task protocol mismatch: runner=${String(env.protocolVersion)} host=${SUBAGENT_TASK_PROTOCOL_VERSION}`,\n\t\t\t\t\t\"invalid_request\",\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t} finally {\n\t\tif (timer !== undefined) clearTimeout(timer);\n\t\tsignal.removeEventListener(\"abort\", onAbort);\n\t}\n}\n\ninterface PoolRunner {\n\treadonly adapter: AgentTaskAdapter;\n\treadonly handle: SubagentRunnerSpawnHandleV1;\n\treadonly liveTasks: Set<string>;\n\t/**\n\t * 分配中(adapter.create 已发起、回执未到)的预留槽位。并发 create 必须在等待\n\t * 回执期间就占位,否则同一 runner 会被超发(压测:40 个并发 create 全落在同一个\n\t * 刚 spawn 的 runner 上,扇出从未发生)。\n\t */\n\tpending: number;\n\talive: boolean;\n\tidleSince: number;\n}\n\nfunction slotsUsed(runner: PoolRunner): number {\n\treturn runner.liveTasks.size + runner.pending;\n}\n\nexport function createPooledAgentTaskAdapterV1(options: SubagentRunnerPoolOptionsV1): SubagentRunnerPoolV1 {\n\t// 配置违规在构造期 fail-fast(D-044 §8),不静默钳制。\n\tvalidateSubagentRunnerPoolOptionsV1(options);\n\tconst clock = options.clock ?? Date.now;\n\tconst handshakeTimeoutMs = options.handshakeTimeoutMs ?? DEFAULT_HANDSHAKE_TIMEOUT_MS;\n\tconst diagnostic = options.onDiagnostic ?? ((): void => {});\n\tconst runners: PoolRunner[] = [];\n\t/** wire taskId(= host.session.getSessionId())-> 承载它的 runner adapter,用于进度转发。 */\n\tconst taskRunnerAdapters = new Map<string, AgentTaskAdapter>();\n\tlet spawnedTotal = 0;\n\tlet closed = false;\n\tlet sweepTimer: ReturnType<typeof setInterval> | undefined;\n\tlet spawnInFlight: Promise<PoolRunner> | undefined;\n\tlet pendingSpawnHandle: SubagentRunnerSpawnHandleV1 | undefined;\n\tconst shutdownController = new AbortController();\n\n\t/** 摘除已死 runner(句柄与状态都不再需要),避免长寿命池按 spawn 次数增长。 */\n\tconst pruneDeadRunners = (): void => {\n\t\tfor (let index = runners.length - 1; index >= 0; index -= 1) {\n\t\t\tif (!runners[index]?.alive) runners.splice(index, 1);\n\t\t}\n\t};\n\n\tconst markDead = (runner: PoolRunner, error?: Error): void => {\n\t\tif (!runner.alive) return;\n\t\trunner.alive = false;\n\t\trunner.liveTasks.clear();\n\t\tdiagnostic(`subagent runner lost: ${error?.message ?? \"process exited\"}`);\n\t};\n\n\tconst trackRunner = (handle: SubagentRunnerSpawnHandleV1): PoolRunner => {\n\t\tconst runner: PoolRunner = {\n\t\t\tadapter: createRemoteAgentTaskAdapter(handle.channel),\n\t\t\thandle,\n\t\t\tliveTasks: new Set<string>(),\n\t\t\tpending: 0,\n\t\t\talive: true,\n\t\t\tidleSince: clock(),\n\t\t};\n\t\thandle.onExit((error?: Error) => markDead(runner, error));\n\t\trunners.push(runner);\n\t\tspawnedTotal += 1;\n\t\treturn runner;\n\t};\n\n\tconst spawnRunner = async (): Promise<PoolRunner> => {\n\t\tshutdownController.signal.throwIfAborted();\n\t\t// 并发 create 共享同一次 spawn,避免超发进程。\n\t\tspawnInFlight ??= (async () => {\n\t\t\tconst handle = await options.spawnRunner();\n\t\t\tpendingSpawnHandle = handle;\n\t\t\ttry {\n\t\t\t\tshutdownController.signal.throwIfAborted();\n\t\t\t\t// 就绪握手在把通道交给 remote adapter 之前完成(adapter 启动后独占读取)。\n\t\t\t\tawait handshakeRunnerV1(handle.channel, handshakeTimeoutMs, shutdownController.signal);\n\t\t\t\tshutdownController.signal.throwIfAborted();\n\t\t\t} catch (error) {\n\t\t\t\t// 握手失败:进程不可用,回收并上抛(调用方按启动失败处理)。\n\t\t\t\ttry {\n\t\t\t\t\thandle.kill();\n\t\t\t\t} catch {\n\t\t\t\t\t// 已退出:忽略\n\t\t\t\t}\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t\treturn trackRunner(handle);\n\t\t})().finally(() => {\n\t\t\t// 清理在赋值后的微任务执行;同步 spawn 抛错不能把失败 Promise 留在池中。\n\t\t\tspawnInFlight = undefined;\n\t\t\tpendingSpawnHandle = undefined;\n\t\t});\n\t\treturn spawnInFlight;\n\t};\n\n\tconst pickRunner = (): PoolRunner | undefined => {\n\t\tlet best: PoolRunner | undefined;\n\t\tlet bestFree = 0;\n\t\tfor (const runner of runners) {\n\t\t\tif (!runner.alive) continue;\n\t\t\tconst free = options.maxSessionsPerRunner - slotsUsed(runner);\n\t\t\tif (free > bestFree) {\n\t\t\t\tbest = runner;\n\t\t\t\tbestFree = free;\n\t\t\t}\n\t\t}\n\t\treturn best;\n\t};\n\n\tconst createOnRunner = async (runner: PoolRunner, request: AgentTaskCreateRequest): Promise<AgentTaskHost> => {\n\t\t// 首选和恢复 runner 共用准入,异步 spawn 完成不代表池仍可创建任务。\n\t\tshutdownController.signal.throwIfAborted();\n\t\trunner.pending += 1;\n\t\tlet host: AgentTaskHost;\n\t\ttry {\n\t\t\thost = await runner.adapter.create(request);\n\t\t} finally {\n\t\t\trunner.pending -= 1;\n\t\t}\n\t\t// 回执可能晚于 shutdown;关闭后的任务由 runner 回收,不再发布给调度器。\n\t\tshutdownController.signal.throwIfAborted();\n\t\tif (!runner.alive) {\n\t\t\tthrow new SubagentTaskProtocolError(\"subagent runner lost before task publication\", \"runner_lost\");\n\t\t}\n\t\tconst taskId = host.session.getSessionId();\n\t\trunner.liveTasks.add(taskId);\n\t\ttaskRunnerAdapters.set(taskId, runner.adapter);\n\t\trunner.idleSince = 0;\n\t\tconst remoteSession = host.session;\n\t\t// 包装 session:保留远程会话身份与 steer;dispose 时归还槽位。\n\t\t// 事实面(getActiveTools/getModel)必须一并透传:档案核验靠它自证,包装层吞掉\n\t\t// 就等于把\"已核验\"变成\"无法核验\"。\n\t\tconst remoteGetActiveTools = remoteSession.getActiveTools?.bind(remoteSession);\n\t\tconst session: SessionAPI = {\n\t\t\tgetSessionId: () => remoteSession.getSessionId(),\n\t\t\t...(remoteSession.getAgentSessionId === undefined\n\t\t\t\t? {}\n\t\t\t\t: { getAgentSessionId: remoteSession.getAgentSessionId.bind(remoteSession) }),\n\t\t\tgetMessages: () => remoteSession.getMessages(),\n\t\t\tisIdle: () => remoteSession.isIdle(),\n\t\t\tprompt: (text) => remoteSession.prompt(text),\n\t\t\t...(remoteSession.steer === undefined ? {} : { steer: remoteSession.steer.bind(remoteSession) }),\n\t\t\tabort: () => remoteSession.abort(),\n\t\t\twaitForIdle: () => remoteSession.waitForIdle(),\n\t\t\tappendEntry: (customType, data) => remoteSession.appendEntry(customType, data),\n\t\t\tgetBranchEntries: () => remoteSession.getBranchEntries(),\n\t\t\t...(remoteGetActiveTools === undefined ? {} : { getActiveTools: () => remoteGetActiveTools() }),\n\t\t\t...(remoteSession.getModel === undefined ? {} : { getModel: () => remoteSession.getModel?.() }),\n\t\t};\n\t\treturn {\n\t\t\tsession,\n\t\t\trun: (prompt, signal) => host.run(prompt, signal),\n\t\t\tcancel: (reason) => host.cancel(reason),\n\t\t\tdispose: async () => {\n\t\t\t\trunner.liveTasks.delete(taskId);\n\t\t\t\ttaskRunnerAdapters.delete(taskId);\n\t\t\t\tif (runner.liveTasks.size === 0) runner.idleSince = clock();\n\t\t\t\tawait host.dispose();\n\t\t\t},\n\t\t};\n\t};\n\n\tconst sweep = (): void => {\n\t\tif (closed) return;\n\t\tpruneDeadRunners();\n\t\tconst now = clock();\n\t\tfor (const runner of runners) {\n\t\t\t// pending > 0 = create 仍在途(真实 runner 的 spawn+握手+会话创建可达\n\t\t\t// 数秒):在途创建是活跃工作,不是空闲——sweep 不得打点也不得回收,\n\t\t\t// 否则激进 idleRunnerTimeoutMs 下 create 中途通道被杀(live 用例实证)。\n\t\t\tif (!runner.alive || runner.pending > 0 || runner.liveTasks.size > 0) continue;\n\t\t\tif (runner.idleSince === 0) runner.idleSince = now;\n\t\t\tif (now - runner.idleSince < options.idleRunnerTimeoutMs) continue;\n\t\t\t// 空闲超时回收:退出前必须无活跃任务(不迁移),dead 标记阻止后续分配。\n\t\t\trunner.alive = false;\n\t\t\tdiagnostic(\"subagent runner idle exit\");\n\t\t\ttry {\n\t\t\t\trunner.handle.kill();\n\t\t\t} catch {\n\t\t\t\t// 进程已退出:忽略\n\t\t\t}\n\t\t}\n\t};\n\n\tconst adapter: SubagentRunnerPoolV1 = {\n\t\t/** D-044 §5.6:转发到承载该任务的 runner 远程 adapter。 */\n\t\tonTaskProgress(taskId: string, listener: (message: string | undefined, seq: number) => void): () => void {\n\t\t\tconst runnerAdapter = taskRunnerAdapters.get(taskId) as\n\t\t\t\t| (AgentTaskAdapter & { onTaskProgress?: (id: string, cb: typeof listener) => () => void })\n\t\t\t\t| undefined;\n\t\t\treturn runnerAdapter?.onTaskProgress?.(taskId, listener) ?? (() => {});\n\t\t},\n\t\tasync create(request: AgentTaskCreateRequest): Promise<AgentTaskHost> {\n\t\t\tif (closed) throw new SubagentRunnerPoolError(\"subagent_pool_closed\", \"subagent runner pool is closed\");\n\t\t\tpruneDeadRunners();\n\t\t\t// 放置循环:并发 create 共享同一次 spawn,因此拿到共享结果后必须**重新**挑选\n\t\t\t// 有空槽的 runner(新 spawn 的那个同样有槽位上限),容量耗尽才报错。\n\t\t\tlet runner = pickRunner();\n\t\t\twhile (runner === undefined) {\n\t\t\t\tif (runners.filter((candidate) => candidate.alive).length >= options.maxRunnerProcesses) {\n\t\t\t\t\tthrow new SubagentRunnerPoolError(\n\t\t\t\t\t\t\"subagent_runner_capacity\",\n\t\t\t\t\t\t`no runner capacity: ${options.maxRunnerProcesses} runner(s) at ${options.maxSessionsPerRunner} session(s) each`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tawait spawnRunner();\n\t\t\t\tshutdownController.signal.throwIfAborted();\n\t\t\t\trunner = pickRunner();\n\t\t\t}\n\t\t\ttry {\n\t\t\t\treturn await createOnRunner(runner, request);\n\t\t\t} catch (error) {\n\t\t\t\tif (error instanceof SubagentTaskProtocolError && error.code === \"runner_lost\") {\n\t\t\t\t\tshutdownController.signal.throwIfAborted();\n\t\t\t\t\t// 分配期失败无副作用:换一个 runner 重试一次;无可用槽位且不能扩则上抛容量错误。\n\t\t\t\t\tmarkDead(runner, error);\n\t\t\t\t\tlet fallback = pickRunner();\n\t\t\t\t\twhile (fallback === undefined) {\n\t\t\t\t\t\tif (runners.filter((candidate) => candidate.alive).length >= options.maxRunnerProcesses) {\n\t\t\t\t\t\t\tthrow new SubagentRunnerPoolError(\"subagent_runner_lost\", \"runner lost and no spare capacity\");\n\t\t\t\t\t\t}\n\t\t\t\t\t\tawait spawnRunner();\n\t\t\t\t\t\tshutdownController.signal.throwIfAborted();\n\t\t\t\t\t\t// 共享 spawn 的空槽可能已被另一恢复或普通创建预留,必须重新挑选。\n\t\t\t\t\t\tfallback = pickRunner();\n\t\t\t\t\t}\n\t\t\t\t\tif (fallback !== runner) return createOnRunner(fallback, request);\n\t\t\t\t}\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t},\n\t\tsweep,\n\t\tstats(): SubagentRunnerPoolStatsV1 {\n\t\t\treturn {\n\t\t\t\trunners: runners.length,\n\t\t\t\taliveRunners: runners.filter((runner) => runner.alive).length,\n\t\t\t\tliveTasks: runners.reduce((total, runner) => total + runner.liveTasks.size, 0),\n\t\t\t\tspawnedTotal,\n\t\t\t};\n\t\t},\n\t\tasync shutdown(): Promise<void> {\n\t\t\tif (closed) return;\n\t\t\tclosed = true;\n\t\t\tshutdownController.abort(\n\t\t\t\tnew SubagentRunnerPoolError(\"subagent_pool_closed\", \"subagent runner pool is closed\"),\n\t\t\t);\n\t\t\tif (sweepTimer !== undefined) clearInterval(sweepTimer);\n\t\t\t// 正在 spawn 的句柄归 spawnRunner 回收;握手可取消,已登记 runner 在下面释放。\n\t\t\tif (pendingSpawnHandle !== undefined && spawnInFlight !== undefined) await Promise.allSettled([spawnInFlight]);\n\t\t\tconst alive = runners.filter((runner) => runner.alive);\n\t\t\tawait Promise.allSettled(alive.map((runner) => runner.adapter.dispose?.()));\n\t\t\tfor (const runner of alive) {\n\t\t\t\trunner.alive = false;\n\t\t\t\ttry {\n\t\t\t\t\trunner.handle.kill();\n\t\t\t\t} catch {\n\t\t\t\t\t// 已退出:忽略\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t};\n\n\tif (options.idleCheckIntervalMs !== undefined && options.idleCheckIntervalMs > 0) {\n\t\tsweepTimer = setInterval(sweep, options.idleCheckIntervalMs);\n\t\tsweepTimer.unref?.();\n\t}\n\n\treturn adapter;\n}\n"]}
|
|
@@ -81,7 +81,11 @@ export interface SubagentRuntimeOptionsV1 {
|
|
|
81
81
|
registry?: AgentTaskRegistryV1;
|
|
82
82
|
/** 缺省按 maxConcurrentSubagents 自建;多 root 全局并发上限时注入同一实例。 */
|
|
83
83
|
globalAdmission?: SubagentAdmissionGateV1;
|
|
84
|
-
/**
|
|
84
|
+
/**
|
|
85
|
+
* 可选 durable 持久化(§4.4 replay 通道 + §5.8 孤儿自愈):终态以
|
|
86
|
+
* `subagent-task:<taskId>` 落库;任务 running 时同键落非终态 started 痕迹;
|
|
87
|
+
* 装载期扫描 started-无-终态综合为 `runner_lost` 终态。缺席 = 不持久化、无自愈。
|
|
88
|
+
*/
|
|
85
89
|
results?: {
|
|
86
90
|
readonly store: DurableServiceV1;
|
|
87
91
|
readonly writerId: string;
|