@ai-agent-forge/plugin-subagent-delegate 0.88.10 → 0.88.12

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.
@@ -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 channel.write({ v: SUBAGENT_TASK_PROTOCOL_VERSION, kind: "request", id, op: "hello" });
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
- const handle = await options.spawnRunner();
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
- // 就绪握手在把通道交给 remote adapter 之前完成(adapter 启动后独占读取)。
152
- await handshakeRunnerV1(handle.channel, handshakeTimeoutMs);
174
+ handle.kill();
153
175
  }
154
- catch (error) {
155
- // 握手失败:进程不可用,回收并上抛(调用方按启动失败处理)。
156
- try {
157
- handle.kill();
158
- }
159
- catch {
160
- // 已退出:忽略
161
- }
162
- throw error;
176
+ catch {
177
+ // 已退出:忽略
163
178
  }
164
- return trackRunner(handle);
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:调度器只读 getSessionId/getMessages,其余透传;dispose 时归还槽位。
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
- ...(remoteSession.getActiveTools === undefined
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
- if (fallback === undefined) {
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
- fallback = await spawnRunner();
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) {
@@ -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
- /** 可选 durable 终态持久化(§4.4 replay 通道):终态以 `subagent-task:<taskId>` 落库。 */
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;