@ai-agent-forge/plugin-subagent-delegate 0.85.0

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.
Files changed (44) hide show
  1. package/README.md +41 -0
  2. package/agent-forge.json +11 -0
  3. package/dist/delegate.d.ts +275 -0
  4. package/dist/delegate.d.ts.map +1 -0
  5. package/dist/delegate.js +1376 -0
  6. package/dist/delegate.js.map +1 -0
  7. package/dist/entry.d.ts +28 -0
  8. package/dist/entry.d.ts.map +1 -0
  9. package/dist/entry.js +101 -0
  10. package/dist/entry.js.map +1 -0
  11. package/dist/index.d.ts +29 -0
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +22 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/multi-agent-tasks.d.ts +101 -0
  16. package/dist/multi-agent-tasks.d.ts.map +1 -0
  17. package/dist/multi-agent-tasks.js +178 -0
  18. package/dist/multi-agent-tasks.js.map +1 -0
  19. package/dist/remote-adapter.d.ts +11 -0
  20. package/dist/remote-adapter.d.ts.map +1 -0
  21. package/dist/remote-adapter.js +366 -0
  22. package/dist/remote-adapter.js.map +1 -0
  23. package/dist/runner-pool.d.ts +59 -0
  24. package/dist/runner-pool.d.ts.map +1 -0
  25. package/dist/runner-pool.js +330 -0
  26. package/dist/runner-pool.js.map +1 -0
  27. package/dist/runner-spawn.d.ts +23 -0
  28. package/dist/runner-spawn.d.ts.map +1 -0
  29. package/dist/runner-spawn.js +79 -0
  30. package/dist/runner-spawn.js.map +1 -0
  31. package/dist/self-invocation.d.ts +20 -0
  32. package/dist/self-invocation.d.ts.map +1 -0
  33. package/dist/self-invocation.js +13 -0
  34. package/dist/self-invocation.js.map +1 -0
  35. package/dist/shared-adapter.d.ts +29 -0
  36. package/dist/shared-adapter.d.ts.map +1 -0
  37. package/dist/shared-adapter.js +60 -0
  38. package/dist/shared-adapter.js.map +1 -0
  39. package/dist/subagent-runtime.d.ts +162 -0
  40. package/dist/subagent-runtime.d.ts.map +1 -0
  41. package/dist/subagent-runtime.js +849 -0
  42. package/dist/subagent-runtime.js.map +1 -0
  43. package/package.json +55 -0
  44. package/plugin.json +27 -0
@@ -0,0 +1,330 @@
1
+ /**
2
+ * 池化 runner adapter(D-045 / pooled 切片增量 2)。
3
+ *
4
+ * `AgentTaskAdapter` 的 runner 池实现:按 `maxSessionsPerRunner` 选"空闲槽位最多"
5
+ * 的 runner(确定性);无空闲槽位且未达 `maxRunnerProcesses` 时按需 spawn;runner
6
+ * 空闲超时退出(不迁移任务);进程死亡时在途任务以 `runner_lost` 结算(D-044 §4.3
7
+ * 投影 failed + reasonCode subagent_runner_lost)。
8
+ *
9
+ * 分层约束:调度器(SubagentRuntime)继续持有队列与准入;本层只提供"容量 + 进程
10
+ * 生命周期"。宿主接线必须保证 `maxConcurrentSubagents <= maxRunnerProcesses *
11
+ * maxSessionsPerRunner`,否则超出容量的准入会以 `subagent_runner_capacity` 失败。
12
+ * spawn 由 `spawnRunner` 注入:生产侧经 stdio 派生子进程,测试注入进程内 fake。
13
+ *
14
+ * 迁自宿主 `src/plugins/subagent-task/runner-pool.ts`(D-075 S4 第三批);公共契约改自
15
+ * plugin-sdk,wire 协议类型改自 @agent-forge/protocol(宿主侧同名模块已下沉该包)。
16
+ */
17
+ import { randomUUID } from "node:crypto";
18
+ import { SUBAGENT_TASK_PROTOCOL_VERSION, SubagentTaskProtocolError, } from "@agent-forge/protocol/subagent";
19
+ import { createRemoteAgentTaskAdapter } from "./remote-adapter.js";
20
+ export class SubagentRunnerPoolError extends Error {
21
+ code;
22
+ constructor(code, message) {
23
+ super(message);
24
+ this.name = "SubagentRunnerPoolError";
25
+ this.code = code;
26
+ }
27
+ }
28
+ const MAX_RUNNER_PROCESSES = 5;
29
+ const DEFAULT_HANDSHAKE_TIMEOUT_MS = 5000;
30
+ /**
31
+ * 宿主接线 fail-fast 校验(D-044 §8):配置违规在构造期抛出,不静默钳制。
32
+ * `maxRunnerProcesses = 0` 表示不启用 pooled(由宿主决定不构造本 adapter),
33
+ * 因此本校验要求 >= 1。
34
+ */
35
+ export function validateSubagentRunnerPoolOptionsV1(options) {
36
+ const fail = (message) => {
37
+ throw new SubagentRunnerPoolError("subagent_pool_config_invalid", message);
38
+ };
39
+ if (!Number.isInteger(options.maxRunnerProcesses) ||
40
+ options.maxRunnerProcesses < 1 ||
41
+ options.maxRunnerProcesses > MAX_RUNNER_PROCESSES) {
42
+ fail(`maxRunnerProcesses must be an integer between 1 and ${MAX_RUNNER_PROCESSES}, got ${String(options.maxRunnerProcesses)}`);
43
+ }
44
+ if (!Number.isInteger(options.maxSessionsPerRunner) || options.maxSessionsPerRunner < 1) {
45
+ fail(`maxSessionsPerRunner must be a positive integer, got ${String(options.maxSessionsPerRunner)}`);
46
+ }
47
+ if (typeof options.maxConcurrentSubagents === "number") {
48
+ if (!Number.isInteger(options.maxConcurrentSubagents) || options.maxConcurrentSubagents < 1) {
49
+ fail(`maxConcurrentSubagents must be a positive integer, got ${String(options.maxConcurrentSubagents)}`);
50
+ }
51
+ const capacity = options.maxRunnerProcesses * options.maxSessionsPerRunner;
52
+ if (options.maxConcurrentSubagents > capacity) {
53
+ fail(`maxConcurrentSubagents (${options.maxConcurrentSubagents}) exceeds runner capacity (${capacity})`);
54
+ }
55
+ }
56
+ if (!Number.isInteger(options.idleRunnerTimeoutMs) || options.idleRunnerTimeoutMs < 1) {
57
+ fail(`idleRunnerTimeoutMs must be a positive integer, got ${String(options.idleRunnerTimeoutMs)}`);
58
+ }
59
+ if (options.handshakeTimeoutMs !== undefined &&
60
+ (!Number.isInteger(options.handshakeTimeoutMs) || options.handshakeTimeoutMs < 1)) {
61
+ fail(`handshakeTimeoutMs must be a positive integer, got ${String(options.handshakeTimeoutMs)}`);
62
+ }
63
+ }
64
+ /**
65
+ * 就绪握手:在把通道交给 remote adapter 之前做一次 hello 往返,确认 runner 存活
66
+ * 且协议版本一致;超时/版本不符视为启动失败(调用方负责 kill)。
67
+ */
68
+ async function handshakeRunnerV1(channel, timeoutMs) {
69
+ const id = `hello:${randomUUID().slice(0, 8)}`;
70
+ const iterator = channel[Symbol.asyncIterator]();
71
+ let timer;
72
+ const timeout = new Promise((_resolve, reject) => {
73
+ timer = setTimeout(() => reject(new SubagentTaskProtocolError(`runner handshake timed out after ${timeoutMs}ms`, "runner_lost")), timeoutMs);
74
+ });
75
+ try {
76
+ await channel.write({ v: SUBAGENT_TASK_PROTOCOL_VERSION, kind: "request", id, op: "hello" });
77
+ for (;;) {
78
+ const nextPromise = iterator.next();
79
+ void nextPromise.catch(() => { });
80
+ const next = await Promise.race([nextPromise, timeout]);
81
+ if (next.done) {
82
+ throw new SubagentTaskProtocolError("runner closed during handshake", "runner_lost");
83
+ }
84
+ const env = next.value;
85
+ if (env.kind !== "response" || env.id !== id)
86
+ continue; // 握手期不应有其它信封:忽略等待
87
+ if (!env.ok) {
88
+ throw new SubagentTaskProtocolError(`runner rejected handshake: ${env.message}`, env.code);
89
+ }
90
+ if (env.protocolVersion !== SUBAGENT_TASK_PROTOCOL_VERSION) {
91
+ throw new SubagentTaskProtocolError(`subagent-task protocol mismatch: runner=${String(env.protocolVersion)} host=${SUBAGENT_TASK_PROTOCOL_VERSION}`, "invalid_request");
92
+ }
93
+ return;
94
+ }
95
+ }
96
+ finally {
97
+ if (timer !== undefined)
98
+ clearTimeout(timer);
99
+ }
100
+ }
101
+ function slotsUsed(runner) {
102
+ return runner.liveTasks.size + runner.pending;
103
+ }
104
+ export function createPooledAgentTaskAdapterV1(options) {
105
+ // 配置违规在构造期 fail-fast(D-044 §8),不静默钳制。
106
+ validateSubagentRunnerPoolOptionsV1(options);
107
+ const clock = options.clock ?? Date.now;
108
+ const handshakeTimeoutMs = options.handshakeTimeoutMs ?? DEFAULT_HANDSHAKE_TIMEOUT_MS;
109
+ const diagnostic = options.onDiagnostic ?? (() => { });
110
+ const runners = [];
111
+ /** wire taskId(= host.session.getSessionId())-> 承载它的 runner adapter,用于进度转发。 */
112
+ const taskRunnerAdapters = new Map();
113
+ let spawnedTotal = 0;
114
+ let closed = false;
115
+ let sweepTimer;
116
+ let spawnInFlight;
117
+ /** 摘除已死 runner(句柄与状态都不再需要),避免长寿命池按 spawn 次数增长。 */
118
+ const pruneDeadRunners = () => {
119
+ for (let index = runners.length - 1; index >= 0; index -= 1) {
120
+ if (!runners[index]?.alive)
121
+ runners.splice(index, 1);
122
+ }
123
+ };
124
+ const markDead = (runner, error) => {
125
+ if (!runner.alive)
126
+ return;
127
+ runner.alive = false;
128
+ runner.liveTasks.clear();
129
+ diagnostic(`subagent runner lost: ${error?.message ?? "process exited"}`);
130
+ };
131
+ const trackRunner = (handle) => {
132
+ const runner = {
133
+ adapter: createRemoteAgentTaskAdapter(handle.channel),
134
+ handle,
135
+ liveTasks: new Set(),
136
+ pending: 0,
137
+ alive: true,
138
+ idleSince: clock(),
139
+ };
140
+ handle.onExit((error) => markDead(runner, error));
141
+ runners.push(runner);
142
+ spawnedTotal += 1;
143
+ return runner;
144
+ };
145
+ const spawnRunner = async () => {
146
+ // 并发 create 共享同一次 spawn,避免超发进程。
147
+ spawnInFlight ??= (async () => {
148
+ try {
149
+ const handle = await options.spawnRunner();
150
+ try {
151
+ // 就绪握手在把通道交给 remote adapter 之前完成(adapter 启动后独占读取)。
152
+ await handshakeRunnerV1(handle.channel, handshakeTimeoutMs);
153
+ }
154
+ catch (error) {
155
+ // 握手失败:进程不可用,回收并上抛(调用方按启动失败处理)。
156
+ try {
157
+ handle.kill();
158
+ }
159
+ catch {
160
+ // 已退出:忽略
161
+ }
162
+ throw error;
163
+ }
164
+ return trackRunner(handle);
165
+ }
166
+ finally {
167
+ spawnInFlight = undefined;
168
+ }
169
+ })();
170
+ return spawnInFlight;
171
+ };
172
+ const pickRunner = () => {
173
+ let best;
174
+ let bestFree = 0;
175
+ for (const runner of runners) {
176
+ if (!runner.alive)
177
+ continue;
178
+ const free = options.maxSessionsPerRunner - slotsUsed(runner);
179
+ if (free > bestFree) {
180
+ best = runner;
181
+ bestFree = free;
182
+ }
183
+ }
184
+ return best;
185
+ };
186
+ const createOnRunner = async (runner, request) => {
187
+ runner.pending += 1;
188
+ let host;
189
+ try {
190
+ host = await runner.adapter.create(request);
191
+ }
192
+ finally {
193
+ runner.pending -= 1;
194
+ }
195
+ const taskId = host.session.getSessionId();
196
+ runner.liveTasks.add(taskId);
197
+ taskRunnerAdapters.set(taskId, runner.adapter);
198
+ runner.idleSince = 0;
199
+ const remoteSession = host.session;
200
+ // 包装 session:调度器只读 getSessionId/getMessages,其余透传;dispose 时归还槽位。
201
+ // 事实面(getActiveTools/getModel)必须一并透传:档案核验靠它自证,包装层吞掉
202
+ // 就等于把"已核验"变成"无法核验"。
203
+ const session = {
204
+ getSessionId: () => remoteSession.getSessionId(),
205
+ getMessages: () => remoteSession.getMessages(),
206
+ isIdle: () => remoteSession.isIdle(),
207
+ prompt: (text) => remoteSession.prompt(text),
208
+ abort: () => remoteSession.abort(),
209
+ waitForIdle: () => remoteSession.waitForIdle(),
210
+ appendEntry: (customType, data) => remoteSession.appendEntry(customType, data),
211
+ getBranchEntries: () => remoteSession.getBranchEntries(),
212
+ ...(remoteSession.getActiveTools === undefined
213
+ ? {}
214
+ : { getActiveTools: () => remoteSession.getActiveTools?.() ?? [] }),
215
+ ...(remoteSession.getModel === undefined ? {} : { getModel: () => remoteSession.getModel?.() }),
216
+ };
217
+ return {
218
+ session,
219
+ run: (prompt, signal) => host.run(prompt, signal),
220
+ cancel: (reason) => host.cancel(reason),
221
+ dispose: async () => {
222
+ runner.liveTasks.delete(taskId);
223
+ taskRunnerAdapters.delete(taskId);
224
+ if (runner.liveTasks.size === 0)
225
+ runner.idleSince = clock();
226
+ await host.dispose();
227
+ },
228
+ };
229
+ };
230
+ const sweep = () => {
231
+ if (closed)
232
+ return;
233
+ pruneDeadRunners();
234
+ const now = clock();
235
+ for (const runner of runners) {
236
+ // pending > 0 = create 仍在途(真实 runner 的 spawn+握手+会话创建可达
237
+ // 数秒):在途创建是活跃工作,不是空闲——sweep 不得打点也不得回收,
238
+ // 否则激进 idleRunnerTimeoutMs 下 create 中途通道被杀(live 用例实证)。
239
+ if (!runner.alive || runner.pending > 0 || runner.liveTasks.size > 0)
240
+ continue;
241
+ if (runner.idleSince === 0)
242
+ runner.idleSince = now;
243
+ if (now - runner.idleSince < options.idleRunnerTimeoutMs)
244
+ continue;
245
+ // 空闲超时回收:退出前必须无活跃任务(不迁移),dead 标记阻止后续分配。
246
+ runner.alive = false;
247
+ diagnostic("subagent runner idle exit");
248
+ try {
249
+ runner.handle.kill();
250
+ }
251
+ catch {
252
+ // 进程已退出:忽略
253
+ }
254
+ }
255
+ };
256
+ const adapter = {
257
+ /** D-044 §5.6:转发到承载该任务的 runner 远程 adapter。 */
258
+ onTaskProgress(taskId, listener) {
259
+ const runnerAdapter = taskRunnerAdapters.get(taskId);
260
+ return runnerAdapter?.onTaskProgress?.(taskId, listener) ?? (() => { });
261
+ },
262
+ async create(request) {
263
+ if (closed)
264
+ throw new SubagentRunnerPoolError("subagent_pool_closed", "subagent runner pool is closed");
265
+ pruneDeadRunners();
266
+ // 放置循环:并发 create 共享同一次 spawn,因此拿到共享结果后必须**重新**挑选
267
+ // 有空槽的 runner(新 spawn 的那个同样有槽位上限),容量耗尽才报错。
268
+ let runner = pickRunner();
269
+ while (runner === undefined) {
270
+ if (runners.filter((candidate) => candidate.alive).length >= options.maxRunnerProcesses) {
271
+ throw new SubagentRunnerPoolError("subagent_runner_capacity", `no runner capacity: ${options.maxRunnerProcesses} runner(s) at ${options.maxSessionsPerRunner} session(s) each`);
272
+ }
273
+ await spawnRunner();
274
+ runner = pickRunner();
275
+ }
276
+ try {
277
+ return await createOnRunner(runner, request);
278
+ }
279
+ catch (error) {
280
+ if (error instanceof SubagentTaskProtocolError && error.code === "runner_lost") {
281
+ // 分配期失败无副作用:换一个 runner 重试一次;无可用槽位且不能扩则上抛容量错误。
282
+ markDead(runner, error);
283
+ let fallback = pickRunner();
284
+ if (fallback === undefined) {
285
+ if (runners.filter((candidate) => candidate.alive).length >= options.maxRunnerProcesses) {
286
+ throw new SubagentRunnerPoolError("subagent_runner_lost", "runner lost and no spare capacity");
287
+ }
288
+ fallback = await spawnRunner();
289
+ }
290
+ if (fallback !== runner)
291
+ return createOnRunner(fallback, request);
292
+ }
293
+ throw error;
294
+ }
295
+ },
296
+ sweep,
297
+ stats() {
298
+ return {
299
+ runners: runners.length,
300
+ aliveRunners: runners.filter((runner) => runner.alive).length,
301
+ liveTasks: runners.reduce((total, runner) => total + runner.liveTasks.size, 0),
302
+ spawnedTotal,
303
+ };
304
+ },
305
+ async shutdown() {
306
+ if (closed)
307
+ return;
308
+ closed = true;
309
+ if (sweepTimer !== undefined)
310
+ clearInterval(sweepTimer);
311
+ const alive = runners.filter((runner) => runner.alive);
312
+ await Promise.allSettled(alive.map((runner) => runner.adapter.dispose?.()));
313
+ for (const runner of alive) {
314
+ runner.alive = false;
315
+ try {
316
+ runner.handle.kill();
317
+ }
318
+ catch {
319
+ // 已退出:忽略
320
+ }
321
+ }
322
+ },
323
+ };
324
+ if (options.idleCheckIntervalMs !== undefined && options.idleCheckIntervalMs > 0) {
325
+ sweepTimer = setInterval(sweep, options.idleCheckIntervalMs);
326
+ sweepTimer.unref?.();
327
+ }
328
+ return adapter;
329
+ }
330
+ //# sourceMappingURL=runner-pool.js.map
@@ -0,0 +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"]}
@@ -0,0 +1,23 @@
1
+ import type { SubagentRunnerSpawnHandleV1 } from "./runner-pool.ts";
2
+ export interface NodeSubagentRunnerSpawnerOptionsV1 {
3
+ /** runner 入口脚本(宿主提供,例如本包 CLI 入口)。 */
4
+ readonly entryPath: string;
5
+ /** 可执行文件(默认 process.execPath)。 */
6
+ readonly executable?: string;
7
+ /** 运行时前置参数,置于入口脚本之前(如 `--max-old-space-size=…`;TS 入口需要 `--experimental-strip-types`)。 */
8
+ readonly nodeArgs?: readonly string[];
9
+ /** 追加在 entryPath 之后的固定参数(默认加 `--subagent-runner`)。 */
10
+ readonly args?: readonly string[];
11
+ readonly cwd?: string;
12
+ readonly env?: NodeJS.ProcessEnv;
13
+ /** 子进程 stderr 行(诊断;默认丢弃,宿主可接日志)。 */
14
+ onStderrLine?(line: string): void;
15
+ /** 子进程 spawn 失败/异常退出的诊断。 */
16
+ onDiagnostic?(message: string): void;
17
+ /** 每次 spawn 成功的观测点(pid 用于诊断与"确实是独立进程"类断言)。 */
18
+ onSpawn?(info: {
19
+ readonly pid: number | undefined;
20
+ }): void;
21
+ }
22
+ export declare function createNodeSubagentRunnerSpawnerV1(options: NodeSubagentRunnerSpawnerOptionsV1): () => SubagentRunnerSpawnHandleV1;
23
+ //# sourceMappingURL=runner-spawn.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runner-spawn.d.ts","sourceRoot":"","sources":["../src/runner-spawn.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,kBAAkB,CAAC;AAEpE,MAAM,WAAW,kCAAkC;IAClD,yEAAqC;IACrC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,sDAAkC;IAClC,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,6IAAyF;IACzF,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACtC,sFAAsD;IACtD,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACjC,8EAAoC;IACpC,YAAY,CAAC,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,sDAA4B;IAC5B,YAAY,CAAC,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,kGAA8C;IAC9C,OAAO,CAAC,CAAC,IAAI,EAAE;QAAE,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAA;KAAE,GAAG,IAAI,CAAC;CAC3D;AAED,wBAAgB,iCAAiC,CAChD,OAAO,EAAE,kCAAkC,GACzC,MAAM,2BAA2B,CAgEnC","sourcesContent":["/**\n * subagent-task node 进程 spawner(D-045 / pooled 切片增量 2b)。\n *\n * 生产侧 `SubagentRunnerSpawnHandleV1` 实现:用 `node <entryPath> --subagent-runner`\n * 派生 runner 子进程,把 child stdout/stdin 包成命令面通道,`exit`/`error` 映射为\n * onExit,`kill()` 请求退出。entryPath 由宿主提供(通常是本包 CLI 入口),本模块\n * 不对可执行文件布局做假设。\n *\n * 迁自宿主 `src/plugins/subagent-task/runner-spawn.ts`(D-075 S4 第三批);stdio 通道\n * 改自 @agent-forge/protocol(宿主侧同名模块已下沉该包)。\n */\nimport { type ChildProcessWithoutNullStreams, spawn } from \"node:child_process\";\nimport { createSubagentTaskStdioChannelV1 } from \"@agent-forge/protocol/subagent\";\nimport type { SubagentRunnerSpawnHandleV1 } from \"./runner-pool.ts\";\n\nexport interface NodeSubagentRunnerSpawnerOptionsV1 {\n\t/** runner 入口脚本(宿主提供,例如本包 CLI 入口)。 */\n\treadonly entryPath: string;\n\t/** 可执行文件(默认 process.execPath)。 */\n\treadonly executable?: string;\n\t/** 运行时前置参数,置于入口脚本之前(如 `--max-old-space-size=…`;TS 入口需要 `--experimental-strip-types`)。 */\n\treadonly nodeArgs?: readonly string[];\n\t/** 追加在 entryPath 之后的固定参数(默认加 `--subagent-runner`)。 */\n\treadonly args?: readonly string[];\n\treadonly cwd?: string;\n\treadonly env?: NodeJS.ProcessEnv;\n\t/** 子进程 stderr 行(诊断;默认丢弃,宿主可接日志)。 */\n\tonStderrLine?(line: string): void;\n\t/** 子进程 spawn 失败/异常退出的诊断。 */\n\tonDiagnostic?(message: string): void;\n\t/** 每次 spawn 成功的观测点(pid 用于诊断与\"确实是独立进程\"类断言)。 */\n\tonSpawn?(info: { readonly pid: number | undefined }): void;\n}\n\nexport function createNodeSubagentRunnerSpawnerV1(\n\toptions: NodeSubagentRunnerSpawnerOptionsV1,\n): () => SubagentRunnerSpawnHandleV1 {\n\tconst executable = options.executable ?? process.execPath;\n\tconst runnerArgs = options.args ?? [\"--subagent-runner\"];\n\tconst nodeArgs = options.nodeArgs ?? [];\n\treturn () => {\n\t\tconst child: ChildProcessWithoutNullStreams = spawn(executable, [...nodeArgs, options.entryPath, ...runnerArgs], {\n\t\t\t...(options.cwd === undefined ? {} : { cwd: options.cwd }),\n\t\t\t...(options.env === undefined ? {} : { env: options.env }),\n\t\t\tstdio: [\"pipe\", \"pipe\", \"pipe\"],\n\t\t\tshell: false,\n\t\t});\n\t\toptions.onSpawn?.({ pid: child.pid });\n\t\tconst exitHandlers: Array<(error?: Error) => void> = [];\n\t\tlet exited = false;\n\t\tconst notifyExit = (error?: Error): void => {\n\t\t\tif (exited) return;\n\t\t\texited = true;\n\t\t\tfor (const handler of exitHandlers.splice(0)) handler(error);\n\t\t};\n\n\t\tif (options.onStderrLine !== undefined) {\n\t\t\tlet buffer = \"\";\n\t\t\tchild.stderr.setEncoding(\"utf8\");\n\t\t\tchild.stderr.on(\"data\", (chunk: string) => {\n\t\t\t\tbuffer += chunk;\n\t\t\t\tlet index = buffer.indexOf(\"\\n\");\n\t\t\t\twhile (index >= 0) {\n\t\t\t\t\toptions.onStderrLine?.(buffer.slice(0, index));\n\t\t\t\t\tbuffer = buffer.slice(index + 1);\n\t\t\t\t\tindex = buffer.indexOf(\"\\n\");\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\n\t\tconst channel = createSubagentTaskStdioChannelV1({\n\t\t\tinput: child.stdout,\n\t\t\toutput: child.stdin,\n\t\t\tonClosed: (error) => options.onDiagnostic?.(`runner stdio closed: ${error?.message ?? \"eof\"}`),\n\t\t});\n\t\t// 注意:kill() 只标记 killed,不置 exited —— 否则随后的 'exit' 事件会被\n\t\t// notifyExit 的幂等守卫吞掉,onExit 处理器永远收不到通知。\n\t\tlet killed = false;\n\t\tchild.on(\"exit\", (code, signal) => {\n\t\t\tnotifyExit(\n\t\t\t\tkilled || code === 0\n\t\t\t\t\t? undefined\n\t\t\t\t\t: new Error(`subagent runner exited with code ${String(code)} signal ${String(signal)}`),\n\t\t\t);\n\t\t});\n\t\tchild.on(\"error\", (error) => notifyExit(error));\n\n\t\treturn {\n\t\t\tchannel,\n\t\t\tonExit: (handler) => {\n\t\t\t\tif (exited) handler();\n\t\t\t\telse exitHandlers.push(handler);\n\t\t\t},\n\t\t\tkill: () => {\n\t\t\t\tif (exited || killed) return;\n\t\t\t\tkilled = true;\n\t\t\t\tchild.kill();\n\t\t\t},\n\t\t};\n\t};\n}\n"]}
@@ -0,0 +1,79 @@
1
+ /**
2
+ * subagent-task node 进程 spawner(D-045 / pooled 切片增量 2b)。
3
+ *
4
+ * 生产侧 `SubagentRunnerSpawnHandleV1` 实现:用 `node <entryPath> --subagent-runner`
5
+ * 派生 runner 子进程,把 child stdout/stdin 包成命令面通道,`exit`/`error` 映射为
6
+ * onExit,`kill()` 请求退出。entryPath 由宿主提供(通常是本包 CLI 入口),本模块
7
+ * 不对可执行文件布局做假设。
8
+ *
9
+ * 迁自宿主 `src/plugins/subagent-task/runner-spawn.ts`(D-075 S4 第三批);stdio 通道
10
+ * 改自 @agent-forge/protocol(宿主侧同名模块已下沉该包)。
11
+ */
12
+ import { spawn } from "node:child_process";
13
+ import { createSubagentTaskStdioChannelV1 } from "@agent-forge/protocol/subagent";
14
+ export function createNodeSubagentRunnerSpawnerV1(options) {
15
+ const executable = options.executable ?? process.execPath;
16
+ const runnerArgs = options.args ?? ["--subagent-runner"];
17
+ const nodeArgs = options.nodeArgs ?? [];
18
+ return () => {
19
+ const child = spawn(executable, [...nodeArgs, options.entryPath, ...runnerArgs], {
20
+ ...(options.cwd === undefined ? {} : { cwd: options.cwd }),
21
+ ...(options.env === undefined ? {} : { env: options.env }),
22
+ stdio: ["pipe", "pipe", "pipe"],
23
+ shell: false,
24
+ });
25
+ options.onSpawn?.({ pid: child.pid });
26
+ const exitHandlers = [];
27
+ let exited = false;
28
+ const notifyExit = (error) => {
29
+ if (exited)
30
+ return;
31
+ exited = true;
32
+ for (const handler of exitHandlers.splice(0))
33
+ handler(error);
34
+ };
35
+ if (options.onStderrLine !== undefined) {
36
+ let buffer = "";
37
+ child.stderr.setEncoding("utf8");
38
+ child.stderr.on("data", (chunk) => {
39
+ buffer += chunk;
40
+ let index = buffer.indexOf("\n");
41
+ while (index >= 0) {
42
+ options.onStderrLine?.(buffer.slice(0, index));
43
+ buffer = buffer.slice(index + 1);
44
+ index = buffer.indexOf("\n");
45
+ }
46
+ });
47
+ }
48
+ const channel = createSubagentTaskStdioChannelV1({
49
+ input: child.stdout,
50
+ output: child.stdin,
51
+ onClosed: (error) => options.onDiagnostic?.(`runner stdio closed: ${error?.message ?? "eof"}`),
52
+ });
53
+ // 注意:kill() 只标记 killed,不置 exited —— 否则随后的 'exit' 事件会被
54
+ // notifyExit 的幂等守卫吞掉,onExit 处理器永远收不到通知。
55
+ let killed = false;
56
+ child.on("exit", (code, signal) => {
57
+ notifyExit(killed || code === 0
58
+ ? undefined
59
+ : new Error(`subagent runner exited with code ${String(code)} signal ${String(signal)}`));
60
+ });
61
+ child.on("error", (error) => notifyExit(error));
62
+ return {
63
+ channel,
64
+ onExit: (handler) => {
65
+ if (exited)
66
+ handler();
67
+ else
68
+ exitHandlers.push(handler);
69
+ },
70
+ kill: () => {
71
+ if (exited || killed)
72
+ return;
73
+ killed = true;
74
+ child.kill();
75
+ },
76
+ };
77
+ };
78
+ }
79
+ //# sourceMappingURL=runner-spawn.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runner-spawn.js","sourceRoot":"","sources":["../src/runner-spawn.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * subagent-task node 进程 spawner(D-045 / pooled 切片增量 2b)。\n *\n * 生产侧 `SubagentRunnerSpawnHandleV1` 实现:用 `node <entryPath> --subagent-runner`\n * 派生 runner 子进程,把 child stdout/stdin 包成命令面通道,`exit`/`error` 映射为\n * onExit,`kill()` 请求退出。entryPath 由宿主提供(通常是本包 CLI 入口),本模块\n * 不对可执行文件布局做假设。\n *\n * 迁自宿主 `src/plugins/subagent-task/runner-spawn.ts`(D-075 S4 第三批);stdio 通道\n * 改自 @agent-forge/protocol(宿主侧同名模块已下沉该包)。\n */\nimport { type ChildProcessWithoutNullStreams, spawn } from \"node:child_process\";\nimport { createSubagentTaskStdioChannelV1 } from \"@agent-forge/protocol/subagent\";\nimport type { SubagentRunnerSpawnHandleV1 } from \"./runner-pool.ts\";\n\nexport interface NodeSubagentRunnerSpawnerOptionsV1 {\n\t/** runner 入口脚本(宿主提供,例如本包 CLI 入口)。 */\n\treadonly entryPath: string;\n\t/** 可执行文件(默认 process.execPath)。 */\n\treadonly executable?: string;\n\t/** 运行时前置参数,置于入口脚本之前(如 `--max-old-space-size=…`;TS 入口需要 `--experimental-strip-types`)。 */\n\treadonly nodeArgs?: readonly string[];\n\t/** 追加在 entryPath 之后的固定参数(默认加 `--subagent-runner`)。 */\n\treadonly args?: readonly string[];\n\treadonly cwd?: string;\n\treadonly env?: NodeJS.ProcessEnv;\n\t/** 子进程 stderr 行(诊断;默认丢弃,宿主可接日志)。 */\n\tonStderrLine?(line: string): void;\n\t/** 子进程 spawn 失败/异常退出的诊断。 */\n\tonDiagnostic?(message: string): void;\n\t/** 每次 spawn 成功的观测点(pid 用于诊断与\"确实是独立进程\"类断言)。 */\n\tonSpawn?(info: { readonly pid: number | undefined }): void;\n}\n\nexport function createNodeSubagentRunnerSpawnerV1(\n\toptions: NodeSubagentRunnerSpawnerOptionsV1,\n): () => SubagentRunnerSpawnHandleV1 {\n\tconst executable = options.executable ?? process.execPath;\n\tconst runnerArgs = options.args ?? [\"--subagent-runner\"];\n\tconst nodeArgs = options.nodeArgs ?? [];\n\treturn () => {\n\t\tconst child: ChildProcessWithoutNullStreams = spawn(executable, [...nodeArgs, options.entryPath, ...runnerArgs], {\n\t\t\t...(options.cwd === undefined ? {} : { cwd: options.cwd }),\n\t\t\t...(options.env === undefined ? {} : { env: options.env }),\n\t\t\tstdio: [\"pipe\", \"pipe\", \"pipe\"],\n\t\t\tshell: false,\n\t\t});\n\t\toptions.onSpawn?.({ pid: child.pid });\n\t\tconst exitHandlers: Array<(error?: Error) => void> = [];\n\t\tlet exited = false;\n\t\tconst notifyExit = (error?: Error): void => {\n\t\t\tif (exited) return;\n\t\t\texited = true;\n\t\t\tfor (const handler of exitHandlers.splice(0)) handler(error);\n\t\t};\n\n\t\tif (options.onStderrLine !== undefined) {\n\t\t\tlet buffer = \"\";\n\t\t\tchild.stderr.setEncoding(\"utf8\");\n\t\t\tchild.stderr.on(\"data\", (chunk: string) => {\n\t\t\t\tbuffer += chunk;\n\t\t\t\tlet index = buffer.indexOf(\"\\n\");\n\t\t\t\twhile (index >= 0) {\n\t\t\t\t\toptions.onStderrLine?.(buffer.slice(0, index));\n\t\t\t\t\tbuffer = buffer.slice(index + 1);\n\t\t\t\t\tindex = buffer.indexOf(\"\\n\");\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\n\t\tconst channel = createSubagentTaskStdioChannelV1({\n\t\t\tinput: child.stdout,\n\t\t\toutput: child.stdin,\n\t\t\tonClosed: (error) => options.onDiagnostic?.(`runner stdio closed: ${error?.message ?? \"eof\"}`),\n\t\t});\n\t\t// 注意:kill() 只标记 killed,不置 exited —— 否则随后的 'exit' 事件会被\n\t\t// notifyExit 的幂等守卫吞掉,onExit 处理器永远收不到通知。\n\t\tlet killed = false;\n\t\tchild.on(\"exit\", (code, signal) => {\n\t\t\tnotifyExit(\n\t\t\t\tkilled || code === 0\n\t\t\t\t\t? undefined\n\t\t\t\t\t: new Error(`subagent runner exited with code ${String(code)} signal ${String(signal)}`),\n\t\t\t);\n\t\t});\n\t\tchild.on(\"error\", (error) => notifyExit(error));\n\n\t\treturn {\n\t\t\tchannel,\n\t\t\tonExit: (handler) => {\n\t\t\t\tif (exited) handler();\n\t\t\t\telse exitHandlers.push(handler);\n\t\t\t},\n\t\t\tkill: () => {\n\t\t\t\tif (exited || killed) return;\n\t\t\t\tkilled = true;\n\t\t\t\tchild.kill();\n\t\t\t},\n\t\t};\n\t};\n}\n"]}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * 自身调用推导(D-045 pooled 零配置):把当前进程重新拉起来当 runner 需要的信息。
3
+ *
4
+ * 刻意不做启发式猜测:只在显式开启(能力配置 `runnerEntryAuto: true`)时使用,且
5
+ * 仅做"把当前进程的启动方式原样复制"这一件确定的事——`executable` 用 `process.execPath`,
6
+ * 入口用 `process.argv[1]`(CLI 脚本;编译型二进制下为空 → 不可用),node 参数继承
7
+ * `process.execArgv`(tsx loader、`--experimental-strip-types` 等加载器开关因此原样保留)。
8
+ * 若入口是 TS 且没有任何加载器开关,补 `--experimental-strip-types`(本仓要求可擦除语法)。
9
+ * 推导结果是否真能当 runner 由握手裁决:不可用时 runner 启动/握手会快速失败并给结构化错误,
10
+ * 用户可改回显式 `runnerEntryPath`。
11
+ *
12
+ * 迁自宿主 `src/utils/self-invocation.ts`(D-075 S4 第三批),纯迁移(无 import)。
13
+ */
14
+ export interface SelfInvocationV1 {
15
+ readonly executable: string;
16
+ readonly entryPath: string;
17
+ readonly nodeArgs: readonly string[];
18
+ }
19
+ export declare function resolveSelfInvocationV1(): SelfInvocationV1 | undefined;
20
+ //# sourceMappingURL=self-invocation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"self-invocation.d.ts","sourceRoot":"","sources":["../src/self-invocation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,gBAAgB;IAChC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;CACrC;AAKD,wBAAgB,uBAAuB,IAAI,gBAAgB,GAAG,SAAS,CAQtE","sourcesContent":["/**\n * 自身调用推导(D-045 pooled 零配置):把当前进程重新拉起来当 runner 需要的信息。\n *\n * 刻意不做启发式猜测:只在显式开启(能力配置 `runnerEntryAuto: true`)时使用,且\n * 仅做\"把当前进程的启动方式原样复制\"这一件确定的事——`executable` 用 `process.execPath`,\n * 入口用 `process.argv[1]`(CLI 脚本;编译型二进制下为空 → 不可用),node 参数继承\n * `process.execArgv`(tsx loader、`--experimental-strip-types` 等加载器开关因此原样保留)。\n * 若入口是 TS 且没有任何加载器开关,补 `--experimental-strip-types`(本仓要求可擦除语法)。\n * 推导结果是否真能当 runner 由握手裁决:不可用时 runner 启动/握手会快速失败并给结构化错误,\n * 用户可改回显式 `runnerEntryPath`。\n *\n * 迁自宿主 `src/utils/self-invocation.ts`(D-075 S4 第三批),纯迁移(无 import)。\n */\nexport interface SelfInvocationV1 {\n\treadonly executable: string;\n\treadonly entryPath: string;\n\treadonly nodeArgs: readonly string[];\n}\n\nconst TS_ENTRY = /\\.(m|c)?ts$/i;\nconst LOADER_ARG = /(--experimental-strip-types|--import|--require|--loader)/;\n\nexport function resolveSelfInvocationV1(): SelfInvocationV1 | undefined {\n\tconst entryPath = process.argv[1];\n\tif (typeof entryPath !== \"string\" || entryPath.trim() === \"\") return undefined;\n\tconst nodeArgs = [...process.execArgv];\n\tif (TS_ENTRY.test(entryPath) && !nodeArgs.some((arg) => LOADER_ARG.test(arg))) {\n\t\tnodeArgs.push(\"--experimental-strip-types\");\n\t}\n\treturn { executable: process.execPath, entryPath, nodeArgs };\n}\n"]}
@@ -0,0 +1,13 @@
1
+ const TS_ENTRY = /\.(m|c)?ts$/i;
2
+ const LOADER_ARG = /(--experimental-strip-types|--import|--require|--loader)/;
3
+ export function resolveSelfInvocationV1() {
4
+ const entryPath = process.argv[1];
5
+ if (typeof entryPath !== "string" || entryPath.trim() === "")
6
+ return undefined;
7
+ const nodeArgs = [...process.execArgv];
8
+ if (TS_ENTRY.test(entryPath) && !nodeArgs.some((arg) => LOADER_ARG.test(arg))) {
9
+ nodeArgs.push("--experimental-strip-types");
10
+ }
11
+ return { executable: process.execPath, entryPath, nodeArgs };
12
+ }
13
+ //# sourceMappingURL=self-invocation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"self-invocation.js","sourceRoot":"","sources":["../src/self-invocation.ts"],"names":[],"mappings":"AAmBA,MAAM,QAAQ,GAAG,cAAc,CAAC;AAChC,MAAM,UAAU,GAAG,0DAA0D,CAAC;AAE9E,MAAM,UAAU,uBAAuB,GAAiC;IACvE,MAAM,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClC,IAAI,OAAO,SAAS,KAAK,QAAQ,IAAI,SAAS,CAAC,IAAI,EAAE,KAAK,EAAE;QAAE,OAAO,SAAS,CAAC;IAC/E,MAAM,QAAQ,GAAG,CAAC,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;IACvC,IAAI,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;QAC/E,QAAQ,CAAC,IAAI,CAAC,4BAA4B,CAAC,CAAC;IAC7C,CAAC;IACD,OAAO,EAAE,UAAU,EAAE,OAAO,CAAC,QAAQ,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC;AAAA,CAC7D","sourcesContent":["/**\n * 自身调用推导(D-045 pooled 零配置):把当前进程重新拉起来当 runner 需要的信息。\n *\n * 刻意不做启发式猜测:只在显式开启(能力配置 `runnerEntryAuto: true`)时使用,且\n * 仅做\"把当前进程的启动方式原样复制\"这一件确定的事——`executable` 用 `process.execPath`,\n * 入口用 `process.argv[1]`(CLI 脚本;编译型二进制下为空 → 不可用),node 参数继承\n * `process.execArgv`(tsx loader、`--experimental-strip-types` 等加载器开关因此原样保留)。\n * 若入口是 TS 且没有任何加载器开关,补 `--experimental-strip-types`(本仓要求可擦除语法)。\n * 推导结果是否真能当 runner 由握手裁决:不可用时 runner 启动/握手会快速失败并给结构化错误,\n * 用户可改回显式 `runnerEntryPath`。\n *\n * 迁自宿主 `src/utils/self-invocation.ts`(D-075 S4 第三批),纯迁移(无 import)。\n */\nexport interface SelfInvocationV1 {\n\treadonly executable: string;\n\treadonly entryPath: string;\n\treadonly nodeArgs: readonly string[];\n}\n\nconst TS_ENTRY = /\\.(m|c)?ts$/i;\nconst LOADER_ARG = /(--experimental-strip-types|--import|--require|--loader)/;\n\nexport function resolveSelfInvocationV1(): SelfInvocationV1 | undefined {\n\tconst entryPath = process.argv[1];\n\tif (typeof entryPath !== \"string\" || entryPath.trim() === \"\") return undefined;\n\tconst nodeArgs = [...process.execArgv];\n\tif (TS_ENTRY.test(entryPath) && !nodeArgs.some((arg) => LOADER_ARG.test(arg))) {\n\t\tnodeArgs.push(\"--experimental-strip-types\");\n\t}\n\treturn { executable: process.execPath, entryPath, nodeArgs };\n}\n"]}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * subagent-task 共享模式 adapter(D-044 §4.1 消费方接线)。
3
+ *
4
+ * `AgentTaskAdapter` 的进程内实现:把宿主已装配的 `CapabilityAPI.agents`
5
+ * (裸执行 primitive,`AgentTaskAPI`/`AgentTaskHandle` 冻结形状)适配成调度器
6
+ * 要求的 `AgentTaskAdapter`。调度器在 admitted 之后才调用 `create()`,因此本
7
+ * adapter 不承担任何准入/排队语义——它只做创建、运行、取消与释放的形状转换。
8
+ *
9
+ * run 语义与 sdk 本地 adapter / 远程 adapter 逐项对齐:
10
+ * - `handle.run()` resolve = 该次 prompt 完成;`result()?.status === "cancelled"`
11
+ * 时按取消类错误(name="AbortError",与 `core/sdk.ts` 的 abortError 和
12
+ * `remote-adapter.ts` 的 abortLikeError 同形)结算,保证消费方 guards 归到
13
+ * cancelled 而不是 succeeded。
14
+ * - `handle.run()` reject = 执行体失败:原样上抛(`RuntimeAgentTask.run` 在
15
+ * host.run 以非取消类错误失败时 reject),调度器据此落 failed + 原始 message。
16
+ * - signal 中止先发 `cancel(reason)`(reason 规范化与本地一致:非字符串 →
17
+ * "cancelled"),再等待 run 终态;入口即中止同样先 cancel。
18
+ *
19
+ * 迁自宿主 `src/plugins/subagent-task/shared-adapter.ts`(D-075 S4 第三批);公共契约
20
+ * 改自 plugin-sdk,其余逐字保持。
21
+ */
22
+ import type { AgentTaskAdapter, AgentTaskAPI } from "@agent-forge/plugin-sdk";
23
+ /**
24
+ * 共享模式 `AgentTaskAdapter`:`api.agents` 的裸 primitive 投影。
25
+ * create 直接透传宿主 primitive(含 `AgentTaskHandle.session` 的受护会话面),
26
+ * cancel/dispose 幂等转发;本 adapter 不持有额外状态,无 adapter 级 dispose。
27
+ */
28
+ export declare function createAgentTaskApiAdapterV1(agents: AgentTaskAPI): AgentTaskAdapter;
29
+ //# sourceMappingURL=shared-adapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shared-adapter.d.ts","sourceRoot":"","sources":["../src/shared-adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,OAAO,KAAK,EACX,gBAAgB,EAChB,YAAY,EAIZ,MAAM,yBAAyB,CAAC;AAcjC;;;;GAIG;AACH,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,YAAY,GAAG,gBAAgB,CAuClF","sourcesContent":["/**\n * subagent-task 共享模式 adapter(D-044 §4.1 消费方接线)。\n *\n * `AgentTaskAdapter` 的进程内实现:把宿主已装配的 `CapabilityAPI.agents`\n * (裸执行 primitive,`AgentTaskAPI`/`AgentTaskHandle` 冻结形状)适配成调度器\n * 要求的 `AgentTaskAdapter`。调度器在 admitted 之后才调用 `create()`,因此本\n * adapter 不承担任何准入/排队语义——它只做创建、运行、取消与释放的形状转换。\n *\n * run 语义与 sdk 本地 adapter / 远程 adapter 逐项对齐:\n * - `handle.run()` resolve = 该次 prompt 完成;`result()?.status === \"cancelled\"`\n * 时按取消类错误(name=\"AbortError\",与 `core/sdk.ts` 的 abortError 和\n * `remote-adapter.ts` 的 abortLikeError 同形)结算,保证消费方 guards 归到\n * cancelled 而不是 succeeded。\n * - `handle.run()` reject = 执行体失败:原样上抛(`RuntimeAgentTask.run` 在\n * host.run 以非取消类错误失败时 reject),调度器据此落 failed + 原始 message。\n * - signal 中止先发 `cancel(reason)`(reason 规范化与本地一致:非字符串 →\n * \"cancelled\"),再等待 run 终态;入口即中止同样先 cancel。\n *\n * 迁自宿主 `src/plugins/subagent-task/shared-adapter.ts`(D-075 S4 第三批);公共契约\n * 改自 plugin-sdk,其余逐字保持。\n */\nimport type {\n\tAgentTaskAdapter,\n\tAgentTaskAPI,\n\tAgentTaskCreateRequest,\n\tAgentTaskHandle,\n\tAgentTaskHost,\n} from \"@agent-forge/plugin-sdk\";\n\n/** 与本地 SDK adapter 相同的取消原因规范化:非字符串 reason 记为 \"cancelled\"。 */\nfunction abortReasonText(signal: AbortSignal): string {\n\treturn typeof signal.reason === \"string\" ? signal.reason : \"cancelled\";\n}\n\n/** 与 sdk.ts abortError 同形的取消类错误:name=\"AbortError\",由 guards 归到 cancelled。 */\nfunction abortLikeError(message: string): Error {\n\tconst error = new Error(message);\n\terror.name = \"AbortError\";\n\treturn error;\n}\n\n/**\n * 共享模式 `AgentTaskAdapter`:`api.agents` 的裸 primitive 投影。\n * create 直接透传宿主 primitive(含 `AgentTaskHandle.session` 的受护会话面),\n * cancel/dispose 幂等转发;本 adapter 不持有额外状态,无 adapter 级 dispose。\n */\nexport function createAgentTaskApiAdapterV1(agents: AgentTaskAPI): AgentTaskAdapter {\n\tconst create = agents.create;\n\t// v8 ignore next -- 调用方(delegate baseAdapter 链)先检查 create 面存在才转换\n\tif (create === undefined) throw new Error(\"this host's agents capability exposes no task adapter\");\n\treturn {\n\t\tcreate: async (request: AgentTaskCreateRequest): Promise<AgentTaskHost> => {\n\t\t\tconst handle: AgentTaskHandle = await create(request);\n\t\t\treturn {\n\t\t\t\tsession: handle.session,\n\t\t\t\trun: async (_prompt: string, signal: AbortSignal): Promise<void> => {\n\t\t\t\t\t// 取消原因在 abort 时刻读取(与 remote adapter 的 signal 监听一致):\n\t\t\t\t\t// 未中止的信号 reason 不是字符串,提前读取会把真实原因误判为 \"cancelled\"。\n\t\t\t\t\tconst onAbort = (): void => {\n\t\t\t\t\t\tvoid handle.cancel(abortReasonText(signal)).catch(() => {});\n\t\t\t\t\t};\n\t\t\t\t\t// 先启动宿主运行、再挂取消栅栏:真实 handle 在 run() 时进入 running,\n\t\t\t\t\t// 此时 cancel() 是 fence;反过来先 cancel 会让 run() 撞上\n\t\t\t\t\t// \"can only run once\" 而丢失取消语义。\n\t\t\t\t\tconst running = handle.run();\n\t\t\t\t\t// 入口即中止:立即发 cancel(与 sdk/remote adapter 一致),此后同一\n\t\t\t\t\t// 微任务内不会再有其他状态变更,signal.aborted 判定是可靠的。\n\t\t\t\t\tif (signal.aborted) onAbort();\n\t\t\t\t\telse signal.addEventListener(\"abort\", onAbort, { once: true });\n\t\t\t\t\ttry {\n\t\t\t\t\t\tawait running;\n\t\t\t\t\t} finally {\n\t\t\t\t\t\tsignal.removeEventListener(\"abort\", onAbort);\n\t\t\t\t\t}\n\t\t\t\t\t// run 已正常结算但任务终态是 cancelled(例如子任务被其父栅栏或\n\t\t\t\t\t// dispose 取消):按取消类错误结算,不得冒充 succeeded。\n\t\t\t\t\tif (handle.result()?.status === \"cancelled\") {\n\t\t\t\t\t\tthrow abortLikeError(\"agent task run was cancelled\");\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tcancel: (reason?: string): Promise<void> => handle.cancel(reason),\n\t\t\t\tdispose: (): Promise<void> => handle.dispose(),\n\t\t\t};\n\t\t},\n\t};\n}\n"]}