@zhushanwen/subagent-core 0.7.1 → 0.9.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 (176) hide show
  1. package/dist/{chunk-VURUAGMM.js → chunk-LZJOEDGQ.js} +603 -154
  2. package/dist/engine-discovery-scan-Da4Dkzsi.d.cts +818 -0
  3. package/dist/engine-discovery-scan-Da4Dkzsi.d.ts +818 -0
  4. package/dist/execution/engine/engine-discovery-scan.cjs +325 -154
  5. package/dist/execution/engine/engine-discovery-scan.d.cts +1 -2
  6. package/dist/execution/engine/engine-discovery-scan.d.ts +1 -2
  7. package/dist/execution/engine/engine-discovery-scan.js +1 -1
  8. package/dist/execution/relay-env.js +5 -4
  9. package/dist/index.cjs +9799 -7954
  10. package/dist/index.d.cts +1896 -1360
  11. package/dist/index.d.ts +1896 -1360
  12. package/dist/index.js +8952 -7513
  13. package/dist.bundle/index.cjs +7897 -6054
  14. package/package.json +5 -5
  15. package/src/__tests__/manifest-store.test.ts +16 -15
  16. package/src/__tests__/record-store-last-line.test.ts +12 -0
  17. package/src/__tests__/robustness-medium-batch1.test.ts +10 -14
  18. package/src/__tests__/robustness-medium-batch2.test.ts +7 -2
  19. package/src/core/logger.ts +1 -1
  20. package/src/execution/__tests__/alive-store.test.ts +117 -1
  21. package/src/execution/__tests__/batch-finalized.test.ts +206 -0
  22. package/src/execution/__tests__/chat-engine-routing.test.ts +117 -1
  23. package/src/execution/__tests__/{cold-resurrect.test.ts → cold-lookup.test.ts} +133 -33
  24. package/src/execution/__tests__/collect-coordinator-service.test.ts +7 -5
  25. package/src/execution/__tests__/conversation-continuation.test.ts +1452 -0
  26. package/src/execution/__tests__/delivery-methods.test.ts +90 -176
  27. package/src/execution/__tests__/dialog-queue.test.ts +70 -3
  28. package/src/execution/__tests__/dispose-manifest-recovery.test.ts +475 -0
  29. package/src/execution/__tests__/execute-and-await-worktree.test.ts +3 -3
  30. package/src/execution/__tests__/execute-nesting.test.ts +8 -5
  31. package/src/execution/__tests__/execution-record.test.ts +8 -148
  32. package/src/execution/__tests__/finalize-record.test.ts +295 -129
  33. package/src/execution/__tests__/get-record-for-action-restart.test.ts +21 -29
  34. package/src/execution/__tests__/helpers/fake-engine-port.ts +13 -68
  35. package/src/execution/__tests__/helpers/legacy-sidecar.ts +33 -0
  36. package/src/execution/__tests__/helpers/subagent-service-mocks.ts +9 -5
  37. package/src/execution/__tests__/idle-gc.test.ts +60 -0
  38. package/src/execution/__tests__/inflight-production-wiring.test.ts +223 -0
  39. package/src/execution/__tests__/lifecycle-manager.test.ts +3 -254
  40. package/src/execution/__tests__/manifest-store-tmp-recovery.test.ts +43 -22
  41. package/src/execution/__tests__/model-config-service.test.ts +111 -0
  42. package/src/execution/__tests__/nested-visibility-env-propagation.test.ts +10 -6
  43. package/src/execution/__tests__/nested-visibility.test.ts +13 -1
  44. package/src/execution/__tests__/notify-ledger.test.ts +42 -1
  45. package/src/execution/__tests__/rebuild-indexes.test.ts +329 -0
  46. package/src/execution/__tests__/record-binding.test.ts +613 -0
  47. package/src/execution/__tests__/record-origin.test.ts +318 -0
  48. package/src/execution/__tests__/record-store-intent-api.test.ts +600 -0
  49. package/src/execution/__tests__/record-store-orphan-revive.test.ts +71 -3
  50. package/src/execution/__tests__/record-store.test.ts +72 -54
  51. package/src/execution/__tests__/recursive-visibility-baseline.test.ts +8 -5
  52. package/src/execution/__tests__/round-supervisor-workflow-origin.test.ts +205 -0
  53. package/src/execution/__tests__/run-orchestration-write-lease.test.ts +293 -0
  54. package/src/execution/__tests__/run-orchestration.test.ts +199 -0
  55. package/src/execution/__tests__/session-file-gc.test.ts +35 -4
  56. package/src/execution/__tests__/session-reconstructor.test.ts +78 -0
  57. package/src/execution/__tests__/state-marker.test.ts +288 -0
  58. package/src/execution/__tests__/stream-sink-retirement.test.ts +8 -5
  59. package/src/execution/__tests__/subagent-actions-core.test.ts +67 -16
  60. package/src/execution/__tests__/subagent-service-multiproc-guard.test.ts +0 -1
  61. package/src/execution/__tests__/subagent-service-notify-gate.test.ts +27 -11
  62. package/src/execution/__tests__/subagent-service-parent-guard.test.ts +17 -11
  63. package/src/execution/__tests__/subagent-service-recovery-bounds.test.ts +130 -53
  64. package/src/execution/__tests__/subagent-service.test.ts +17 -215
  65. package/src/execution/__tests__/subprocess-agent-runner-no-progress-killall.test.ts +138 -0
  66. package/src/execution/__tests__/subprocess-agent-runner.test.ts +105 -660
  67. package/src/execution/__tests__/sync-collect-recovery.test.ts +23 -16
  68. package/src/execution/__tests__/terminal-write-latency.test.ts +79 -0
  69. package/src/execution/__tests__/workflow-agent-dispatch.test.ts +782 -0
  70. package/src/execution/alive-store.ts +37 -28
  71. package/src/execution/{cold-resurrect.ts → cold-lookup.ts} +48 -45
  72. package/src/execution/config.ts +1 -1
  73. package/src/execution/conversation-continuation.ts +541 -0
  74. package/src/execution/dialog-queue.ts +60 -12
  75. package/src/execution/engine/__tests__/common/capability-gate.test.ts +1 -1
  76. package/src/execution/engine/__tests__/common/journal-wiring.test.ts +16 -1
  77. package/src/execution/engine/__tests__/conformance/__fixtures__/engine-protocol/fake-engine-protocol.mjs +17 -51
  78. package/src/execution/engine/__tests__/conformance/__fixtures__/engine-protocol/smoke-run.fixture.json +0 -5
  79. package/src/execution/engine/__tests__/conformance/engine-conformance.live.test.ts +1 -1
  80. package/src/execution/engine/__tests__/conformance/protocol-blackbox.test.ts +5 -9
  81. package/src/execution/engine/__tests__/conformance/registry-fork-filter.test.ts +55 -53
  82. package/src/execution/engine/__tests__/engine-discovery-scan.test.ts +0 -3
  83. package/src/execution/engine/__tests__/engine-discovery.test.ts +0 -3
  84. package/src/execution/engine/__tests__/inflight-snapshot.test.ts +180 -0
  85. package/src/execution/engine/__tests__/model-prompt.test.ts +0 -3
  86. package/src/execution/engine/__tests__/registry.test.ts +0 -1
  87. package/src/execution/engine/__tests__/routing.test.ts +0 -1
  88. package/src/execution/engine/__tests__/run-failure-worktree-cleanup.test.ts +0 -3
  89. package/src/execution/engine/client/__tests__/__fixtures__/fake-engine.mjs +0 -3
  90. package/src/execution/engine/client/__tests__/engine-client-reap.test.ts +212 -0
  91. package/src/execution/engine/client/__tests__/mirror.test.ts +73 -1
  92. package/src/execution/engine/client/__tests__/protocol-closure.test.ts +2 -9
  93. package/src/execution/engine/client/__tests__/remote-engine.test.ts +46 -12
  94. package/src/execution/engine/client/engine-client.ts +111 -26
  95. package/src/execution/engine/client/mirror.ts +9 -0
  96. package/src/execution/engine/client/remote-engine.ts +52 -34
  97. package/src/execution/engine/client/reverse-router.ts +6 -27
  98. package/src/execution/engine/common/__tests__/run-signals.test.ts +79 -0
  99. package/src/execution/engine/common/capability-gate.ts +27 -0
  100. package/src/execution/engine/common/data-dir.ts +3 -2
  101. package/src/execution/engine/common/errors.ts +1 -1
  102. package/src/execution/engine/common/journal-wiring.ts +18 -9
  103. package/src/execution/engine/common/run-signals.ts +83 -0
  104. package/src/execution/engine/d8-compat.ts +1 -1
  105. package/src/execution/engine/host/__tests__/host-bridge.test.ts +3 -18
  106. package/src/execution/engine/host/host-bridge.ts +26 -41
  107. package/src/execution/engine/host/pi-host-binding.ts +5 -5
  108. package/src/execution/engine/host/spawned-children.ts +1 -1
  109. package/src/execution/engine/inflight-snapshot.ts +92 -0
  110. package/src/execution/engine/port.ts +52 -54
  111. package/src/execution/engine/types.ts +11 -14
  112. package/src/execution/execution-record.ts +15 -47
  113. package/src/execution/finalize-record.ts +94 -197
  114. package/src/execution/idle-gc.ts +9 -3
  115. package/src/execution/lifecycle-manager.ts +25 -361
  116. package/src/execution/lifecycle-predicates.ts +22 -19
  117. package/src/execution/manifest-store.ts +40 -42
  118. package/src/execution/model-config-service.ts +1 -1
  119. package/src/execution/notifier.ts +39 -1
  120. package/src/execution/notify-host.ts +13 -4
  121. package/src/execution/path-encoding.ts +6 -0
  122. package/src/execution/record-entry.ts +20 -2
  123. package/src/execution/record-store.ts +925 -160
  124. package/src/execution/relay-env.ts +8 -3
  125. package/src/execution/round-supervisor/domain.ts +17 -5
  126. package/src/execution/round-supervisor/service-binding.test.ts +89 -53
  127. package/src/execution/round-supervisor/service-binding.ts +71 -21
  128. package/src/execution/round-supervisor/supervisor.ts +23 -9
  129. package/src/execution/service/record-access.ts +547 -0
  130. package/src/execution/service/record-lifecycle.ts +542 -0
  131. package/src/execution/service/run-orchestration.ts +1647 -0
  132. package/src/execution/service/service-bootstrap.ts +111 -0
  133. package/src/execution/service/service-constants.ts +27 -0
  134. package/src/execution/service/session-baselines.ts +401 -0
  135. package/src/execution/service/sync-collect-domain.ts +442 -0
  136. package/src/execution/service/workflow-dispatch.ts +567 -0
  137. package/src/execution/session-file-gc.ts +11 -8
  138. package/src/execution/session-reconstructor.ts +44 -1
  139. package/src/execution/sessions-index.ts +20 -3
  140. package/src/execution/settled-watchdog.ts +86 -43
  141. package/src/execution/state-marker.ts +516 -0
  142. package/src/execution/stream-sink.ts +1 -1
  143. package/src/execution/subagent-actions-core.ts +53 -31
  144. package/src/execution/subagent-service.ts +643 -2955
  145. package/src/execution/subprocess-agent-runner.ts +46 -323
  146. package/src/execution/types.ts +60 -43
  147. package/src/execution/ui-request-handler-factory.ts +5 -0
  148. package/src/execution/worktree-manager.ts +1 -1
  149. package/src/execution/worktree-registry.ts +12 -8
  150. package/src/index.ts +16 -5
  151. package/src/orchestration/__tests__/agent-call-catch-fallback.test.ts +2 -3
  152. package/src/orchestration/__tests__/agent-call-stream.test.ts +24 -49
  153. package/src/orchestration/__tests__/error-recovery-terminal-hardening.test.ts +4 -4
  154. package/src/orchestration/__tests__/file-run-store.test.ts +5 -7
  155. package/src/orchestration/__tests__/lifecycle-abort-broadcast-signal.test.ts +2 -3
  156. package/src/orchestration/__tests__/run-snapshot.test.ts +27 -43
  157. package/src/orchestration/__tests__/worker-message-pump-workflow-dispatch.test.ts +274 -0
  158. package/src/orchestration/execute-agent-call.ts +1 -1
  159. package/src/orchestration/file-run-store.ts +1 -1
  160. package/src/orchestration/models/ports.ts +20 -13
  161. package/src/orchestration/models/types.ts +11 -13
  162. package/src/orchestration/run-snapshot.ts +18 -22
  163. package/src/orchestration/worker-message-pump.ts +33 -57
  164. package/src/shared/atomic-write.ts +10 -8
  165. package/dist/chunk-T2SYBW3I.js +0 -24
  166. package/dist/engine-discovery-scan-ocpM8FmI.d.cts +0 -1608
  167. package/dist/engine-discovery-scan-ocpM8FmI.d.ts +0 -1608
  168. package/src/execution/__tests__/chat-round-first-round-watchdog.test.ts +0 -255
  169. package/src/execution/__tests__/finalized-marker.test.ts +0 -104
  170. package/src/execution/__tests__/lifecycle-manager-lock.test.ts +0 -211
  171. package/src/execution/__tests__/subprocess-agent-runner-routing.test.ts +0 -488
  172. package/src/execution/__tests__/subprocess-agent-runner-timeout.test.ts +0 -75
  173. package/src/execution/__tests__/tombstone-store.test.ts +0 -73
  174. package/src/execution/engine/__tests__/conformance/chat-round-protocol.test.ts +0 -230
  175. package/src/execution/finalized-marker.ts +0 -70
  176. package/src/execution/tombstone-store.ts +0 -72
@@ -1,5 +1,5 @@
1
1
  // src/execution/engine/engine-discovery-scan.ts
2
- import * as path5 from "path";
2
+ import * as path6 from "path";
3
3
 
4
4
  // src/execution/engine/engine-manifest.ts
5
5
  import * as path from "path";
@@ -247,7 +247,7 @@ function describeValue(value) {
247
247
 
248
248
  // src/execution/engine/engine-inspect-package.ts
249
249
  import * as fs from "fs";
250
- import * as path2 from "path";
250
+ import * as path3 from "path";
251
251
  import { isProtocolVersionCompatible as isProtocolVersionCompatible2 } from "@zhushanwen/subagent-engine-sdk";
252
252
 
253
253
  // src/execution/engine/common/data-dir.ts
@@ -293,7 +293,239 @@ import {
293
293
 
294
294
  // src/execution/engine/client/mirror.ts
295
295
  import { getLogger as getLogger2 } from "@zhushanwen/subagent-engine-sdk";
296
- var logger3 = getLogger2("subagents");
296
+
297
+ // src/shared/timer-delay.ts
298
+ var MAX_TIMER_DELAY_MS = 2147483647;
299
+ function assertSafeTimerDelay(ms, source) {
300
+ if (!Number.isFinite(ms)) {
301
+ throw new Error(
302
+ `[subagent-workflow] ${source} = ${ms} is not a finite number (NaN/\xB1Infinity). Non-finite delays collapse to 1ms in Node setTimeout and fire immediately. Recovery: fix the upstream computation that produced this value (e.g. guard division/parse results before passing them in) and retry.`
303
+ );
304
+ }
305
+ if (ms > MAX_TIMER_DELAY_MS) {
306
+ throw new Error(
307
+ `[subagent-workflow] ${source} = ${ms} exceeds the Node setTimeout limit (${MAX_TIMER_DELAY_MS} ms = 2^31-1); larger delays silently collapse to 1ms and fire immediately. Recovery: clamp the value to <= ${MAX_TIMER_DELAY_MS} (e.g. omit the option for "unlimited" semantics, or clamp explicitly) and retry.`
308
+ );
309
+ }
310
+ }
311
+
312
+ // src/execution/ui-interaction-model.ts
313
+ var DIALOG_METHODS = /* @__PURE__ */ new Set([
314
+ "select",
315
+ "confirm",
316
+ "input",
317
+ "editor"
318
+ ]);
319
+ function isDialogMethod(method) {
320
+ return DIALOG_METHODS.has(method);
321
+ }
322
+
323
+ // src/execution/dialog-queue.ts
324
+ var logger3 = getLogger("subagents");
325
+ var SECONDS_PER_MINUTE = 60;
326
+ var MS_PER_SECOND = 1e3;
327
+ var DEFAULT_DIALOG_TIMEOUT_MINUTES = 30;
328
+ var DEFAULT_DIALOG_TIMEOUT_MS = DEFAULT_DIALOG_TIMEOUT_MINUTES * SECONDS_PER_MINUTE * MS_PER_SECOND;
329
+ function resolveDialogTimeoutMs(timeout) {
330
+ const resolved = isValidDialogTimeout(timeout) ? timeout : DEFAULT_DIALOG_TIMEOUT_MS;
331
+ return Math.min(resolved, MAX_TIMER_DELAY_MS);
332
+ }
333
+ function isValidDialogTimeout(timeout) {
334
+ return typeof timeout === "number" && Number.isFinite(timeout) && timeout > 0;
335
+ }
336
+ function dialogTimeoutLogMessage(req, waitedMs) {
337
+ return `[subagents] dialog timed out after ${waitedMs}ms without response (id=${req.id}, method=${req.method}) \u2014 settled as cancelled. Recovery: the requesting agent can continue and re-issue the question (\u91CD\u65B0\u53D1\u8D77\u63D0\u95EE); to wait longer, pass an explicit timeout (ms) on the request.`;
338
+ }
339
+ var DialogGlobalQueue = class {
340
+ /** 等待处理的队列(FIFO)。正在处理的项从 queue shift 出后由 current 持有。 */
341
+ queue = [];
342
+ /** 正在处理的项(handler 已调、未 settle)。用于 rejectChildDialogs 取消占位中的 dialog。 */
343
+ current;
344
+ processing = false;
345
+ /**
346
+ * 入队一个 UI 请求,返回 Promise<UiResponse>。
347
+ *
348
+ * 调用方约定:只对 dialog 类(isDialogMethod===true)调 enqueue。fire-and-forget 由
349
+ * 调用方(ui-request-handler-factory.ts)在 enqueue 前判 isDialogMethod 后直接调 handler,
350
+ * 不经过本队列。enqueue 内仍防御性兼容 fire-and-forget(万一调用方未判):直接调 handler 返回,
351
+ * 不入队串行,但调用方不应依赖此防御行为。
352
+ *
353
+ * dialog 项处理(TC-E4 case 1):进队列 FIFO 串行,等前一个 settle 后才调 handler
354
+ *(争输入焦点,防并发弹窗)。
355
+ *
356
+ * handler 抛错兜底:catch → 回 {cancelled:true}(dialog 路径,队列不卡死)。
357
+ * SR-4:opts.child 用于 rejectChildDialogs 关联(dialog 项会被批量 reject,含 current)。
358
+ *
359
+ * @param req UI 请求(约定只传 dialog 类;fire-and-forget 防御性兼容)
360
+ * @param handler 真正执行请求的 handler(TUI/GUI 模式分流后的 realHandler)
361
+ * @param opts 可选 child 引用(用于 rejectChildDialogs 关联)
362
+ * @returns handler 的响应;dialog 抛错时回 {cancelled:true};child close 时回 {cancelled:true}
363
+ */
364
+ enqueue(req, handler, opts) {
365
+ if (!isDialogMethod(req.method)) {
366
+ return handler(req);
367
+ }
368
+ return new Promise((resolve4) => {
369
+ this.queue.push({
370
+ req,
371
+ handler,
372
+ resolve: resolve4,
373
+ childPid: opts?.child?.pid,
374
+ settled: false
375
+ });
376
+ void this.processNext();
377
+ });
378
+ }
379
+ /**
380
+ * settle 一个 item(幂等)。handler 完成 / rejectChildDialogs / rejectAll / 超时 timer
381
+ * 都通过本方法,settled 标志保证只 settle 一次(防竞争)。
382
+ *
383
+ * #19 单一推进点:本方法 settle Promise + 清状态后,**唯一**调 processNext 推进队列。
384
+ * processNext 尾部不再调 processNext(旧代码双重推进,虽靠 processing 标志幂等,但语义混乱)。
385
+ * 为什么推进必须在 settleItem 而非 processNext 尾部:rejectChildDialogs 取消一个永不 settle
386
+ * 的 current(handler 等用户输入卡死)时,processNext 的 `await item.handler` 永不 resume,
387
+ * 尾部不会执行;只有 settleItem 里的 processNext 才能打破死锁,推进下一个。
388
+ *
389
+ * [A2-2] 超时 timer 清理也统一收口在这里:任何路径抢先 settle(reject/rejectAll/超时
390
+ * 自身)都必须撤下 armed timer——handler 永挂时 processNext 内联的 clearTimeout 不可达,
391
+ * 不在这里清则 timer 到期触发虚假「dialog timed out」warn 且句柄滞留至超时点。
392
+ */
393
+ settleItem(item, resp) {
394
+ if (item.settled) return;
395
+ item.settled = true;
396
+ if (item.timeoutTimer) {
397
+ clearTimeout(item.timeoutTimer);
398
+ item.timeoutTimer = void 0;
399
+ }
400
+ item.resolve(resp);
401
+ if (this.current === item) {
402
+ this.current = void 0;
403
+ this.processing = false;
404
+ void this.processNext();
405
+ }
406
+ }
407
+ /**
408
+ * SR-4:把指定 child 的所有 pending dialog resolve 为 {cancelled:true}。
409
+ *
410
+ * 触发场景:子进程 close(用户取消 / crash / 超时 kill)时,其 pending dialog 的 handler
411
+ * 可能永不 settle(等用户输入),导致 Promise 永挂 + 内存泄漏。本方法批量清理。
412
+ *
413
+ * 处理范围(TC-E4 case 2):
414
+ * - 正在处理中(current)的该 child 项:settle {cancelled:true},解阻塞队列推进下一个
415
+ * (关键:handler 可能永不 settle,必须由这里打破死锁)
416
+ * - 队列中等待处理的该 child 项:settle {cancelled:true} 并移除
417
+ *
418
+ * 不影响其他 child 的 pending dialog(TC-E4 case 2 子测试 2)。
419
+ */
420
+ rejectChildDialogs(child) {
421
+ const matched = [];
422
+ if (this.queue.length > 0) {
423
+ const remaining = [];
424
+ for (const item of this.queue) {
425
+ if (item.childPid === child.pid) {
426
+ matched.push(item);
427
+ } else {
428
+ remaining.push(item);
429
+ }
430
+ }
431
+ this.queue = remaining;
432
+ }
433
+ if (this.current && this.current.childPid === child.pid) {
434
+ this.settleItem(this.current, { cancelled: true });
435
+ }
436
+ for (const item of matched) {
437
+ this.settleItem(item, { cancelled: true });
438
+ }
439
+ }
440
+ /**
441
+ * 处理队列下一项(FIFO)。
442
+ *
443
+ * processing 标志保证串行:handler 运行期间 processing=true,新的 processNext 调用直接返回;
444
+ * handler settle 后由 settleItem 清 processing=false 并推进下一项(#19 单一推进点)。
445
+ *
446
+ * handler 抛错兜底(TC-E4 case 3):catch → settle {cancelled:true} → 继续。
447
+ * 不能让一个失败卡死队列(processing 永远 true)。
448
+ *
449
+ * LC-3/T2⑦ 超时上界:`await item.handler` 原本无上界——host UI promise 挂死或用户
450
+ * 永不回答时 processing 恒 true(全局 dialog 死锁)。现在每项挂队列级 timer:
451
+ * req.timeout(请求方显式传值)优先,未传/非法挂 DEFAULT_DIALOG_TIMEOUT_MS。
452
+ * 到点 settle {cancelled:true}(完整错误消息落父进程日志,含恢复指引与等待时长),
453
+ * settleItem 的 settled 标志保证与 handler 完成 / rejectChildDialogs 三方竞态下
454
+ * 恰 settle 一次。timer 回调闭包捕获 item(非读 this.current):迟到触发时
455
+ * settled 标志已置位,直接 noop,不误伤后继项。
456
+ */
457
+ async processNext() {
458
+ if (this.processing) return;
459
+ if (this.queue.length === 0) return;
460
+ this.processing = true;
461
+ const item = this.queue.shift();
462
+ this.current = item;
463
+ const timeoutMs = resolveDialogTimeoutMs(item.req.timeout);
464
+ const timer = setTimeout(() => {
465
+ if (item.settled) return;
466
+ logger3.warn(dialogTimeoutLogMessage(item.req, timeoutMs));
467
+ this.settleItem(item, { cancelled: true });
468
+ }, timeoutMs);
469
+ item.timeoutTimer = timer;
470
+ try {
471
+ const resp = await item.handler(item.req);
472
+ clearTimeout(timer);
473
+ this.settleItem(item, resp);
474
+ } catch {
475
+ clearTimeout(timer);
476
+ this.settleItem(item, { cancelled: true });
477
+ }
478
+ }
479
+ /**
480
+ * #10:把所有 pending dialog(queue + current)全部 settle 为 {cancelled:true},
481
+ * 并清空 queue/current/processing 状态。session_shutdown 调用,保证不留永挂 Promise。
482
+ *
483
+ * 约定签名:rejectAll(): void(无参,返 void)。Group C 的 index.ts session_shutdown 依赖此契约。
484
+ *
485
+ * 幂等:依赖 settleItem 的 settled 标志——重复调用只会对已 settled 项 noop。
486
+ * 顺序敏感(#19 推进点在 settleItem):必须先清空 queue 数组再 settle current,
487
+ * 否则 settleItem(current) 同步触发的 processNext 会从旧 queue 抢占下一项作为新 current,
488
+ * 避开本方法的 cancel 语义。清空后 processNext 看到空队列直接返回,新 current 不会被抢占。
489
+ *
490
+ * 单 session 假设(M-2):见类注释。本方法清空所有 pending 不分 session——依赖 Pi 单进程
491
+ * 单 session 串行保证。session_shutdown handler(index.ts)调用本方法时,进程内只会有当前
492
+ * session 的 pending dialog。多 session 并发场景的迁移策略(rejectAllForSession)见类注释。
493
+ */
494
+ rejectAll() {
495
+ const items = this.queue;
496
+ this.queue = [];
497
+ if (this.current) {
498
+ this.settleItem(this.current, { cancelled: true });
499
+ }
500
+ for (const item of items) {
501
+ this.settleItem(item, { cancelled: true });
502
+ }
503
+ this.current = void 0;
504
+ this.processing = false;
505
+ }
506
+ /** 清空队列状态(仅在 rejectAll 之后调用)。pending Promise 必须先由 rejectAll settle。
507
+ * 不 settle Promise 的纯状态重置——单独调用会导致 Promise 永挂(footgun),故设为 private。
508
+ * 外部调用方应使用 rejectAll()(它 settle 所有 pending + 重置状态,是原子操作)。 */
509
+ resetState() {
510
+ this.queue = [];
511
+ this.current = void 0;
512
+ this.processing = false;
513
+ }
514
+ /** 当前队列长度(测试/诊断用)。含等待处理项(不含 current)。 */
515
+ get size() {
516
+ return this.queue.length;
517
+ }
518
+ };
519
+ var activeDialogQueue;
520
+ function registerActiveDialogQueue(queue) {
521
+ activeDialogQueue = queue;
522
+ }
523
+ function notifyChildProcessExited(pid) {
524
+ activeDialogQueue?.rejectChildDialogs({ pid });
525
+ }
526
+
527
+ // src/execution/engine/client/mirror.ts
528
+ var logger4 = getLogger2("subagents");
297
529
  var SpawnedChildrenMirror = class {
298
530
  entries = /* @__PURE__ */ new Map();
299
531
  listeners = /* @__PURE__ */ new Set();
@@ -322,6 +554,7 @@ var SpawnedChildrenMirror = class {
322
554
  };
323
555
  this.entries.set(patch.pid, entry);
324
556
  this.emit({ reason: "childStateChanged", pid: patch.pid, recordId: patch.recordId });
557
+ if (patch.state === "exited") notifyChildProcessExited(patch.pid);
325
558
  }
326
559
  /**
327
560
  * 失效语义 2 + 3:全部镜像项整体置死(killed=true + state=exited)→ 逐项广播 →
@@ -337,6 +570,7 @@ var SpawnedChildrenMirror = class {
337
570
  entry.state = "exited";
338
571
  entry.updatedAt = Date.now();
339
572
  this.emit({ reason: "killedAll", pid: entry.pid, recordId: entry.recordId });
573
+ notifyChildProcessExited(entry.pid);
340
574
  }
341
575
  this.entries.clear();
342
576
  }
@@ -367,18 +601,18 @@ var SpawnedChildrenMirror = class {
367
601
  return this.entries.size;
368
602
  }
369
603
  /** 订阅状态广播(W6 notify/谓词接线点)。返回退订函数。 */
370
- onChange(listener) {
371
- this.listeners.add(listener);
604
+ onChange(listener2) {
605
+ this.listeners.add(listener2);
372
606
  return () => {
373
- this.listeners.delete(listener);
607
+ this.listeners.delete(listener2);
374
608
  };
375
609
  }
376
610
  emit(event) {
377
- for (const listener of this.listeners) {
611
+ for (const listener2 of this.listeners) {
378
612
  try {
379
- listener(event);
613
+ listener2(event);
380
614
  } catch (err) {
381
- logger3.debug(
615
+ logger4.debug(
382
616
  `[spawned-children-mirror] listener threw for ${event.reason}: ${err instanceof Error ? err.message : String(err)}`
383
617
  );
384
618
  }
@@ -391,22 +625,22 @@ import { spawnSync } from "child_process";
391
625
  import { readdirSync, readFileSync, renameSync, rmSync, writeFileSync, mkdirSync } from "fs";
392
626
  import { join as join2 } from "path";
393
627
  import { getLogger as getLogger3, resolveEngineDir } from "@zhushanwen/subagent-engine-sdk";
394
- var logger4 = getLogger3("subagents");
628
+ var logger5 = getLogger3("subagents");
395
629
  var CMDLINE_PROBE_TIMEOUT_MS = 3e3;
396
630
  var PIDFILE_JSON_INDENT = 2;
397
631
  function pidfilePath(engineDataDir, engineId, hostKind, hostPid) {
398
632
  return join2(resolveEngineDir(engineDataDir, engineId), `engine.${hostKind}.${hostPid}.pid`);
399
633
  }
400
- function writePidfileAtomic(path6, content) {
401
- mkdirSync(join2(path6, ".."), { recursive: true });
402
- const tmp = `${path6}.${process.pid}.${Date.now()}.tmp`;
634
+ function writePidfileAtomic(path7, content) {
635
+ mkdirSync(join2(path7, ".."), { recursive: true });
636
+ const tmp = `${path7}.${process.pid}.${Date.now()}.tmp`;
403
637
  writeFileSync(tmp, `${JSON.stringify(content, null, PIDFILE_JSON_INDENT)}
404
638
  `, "utf-8");
405
- renameSync(tmp, path6);
639
+ renameSync(tmp, path7);
406
640
  }
407
- function readPidfile(path6) {
641
+ function readPidfile(path7) {
408
642
  try {
409
- const raw = readFileSync(path6, "utf-8");
643
+ const raw = readFileSync(path7, "utf-8");
410
644
  const parsed = JSON.parse(raw);
411
645
  if (typeof parsed.enginePid !== "number" || typeof parsed.hostPid !== "number" || typeof parsed.engineId !== "string" || typeof parsed.hostKind !== "string") {
412
646
  return void 0;
@@ -420,21 +654,21 @@ function readPidfile(path6) {
420
654
  writtenAt: typeof parsed.writtenAt === "string" ? parsed.writtenAt : ""
421
655
  };
422
656
  } catch (err) {
423
- logger4.debug(`[pid-file] readPidfile(${path6}) failed, treating as stale: ${err instanceof Error ? err.message : String(err)}`);
657
+ logger5.debug(`[pid-file] readPidfile(${path7}) failed, treating as stale: ${err instanceof Error ? err.message : String(err)}`);
424
658
  return void 0;
425
659
  }
426
660
  }
427
- function removePidfile(path6) {
661
+ function removePidfile(path7) {
428
662
  try {
429
- rmSync(path6, { force: true });
663
+ rmSync(path7, { force: true });
430
664
  } catch (err) {
431
- logger4.debug(`[pid-file] removePidfile(${path6}) failed (concurrent sweep = expected): ${err instanceof Error ? err.message : String(err)}`);
665
+ logger5.debug(`[pid-file] removePidfile(${path7}) failed (concurrent sweep = expected): ${err instanceof Error ? err.message : String(err)}`);
432
666
  }
433
667
  }
434
668
  function registerEnginePidfile(reg) {
435
- const path6 = pidfilePath(reg.engineDataDir, reg.engineId, reg.hostKind, reg.hostPid);
669
+ const path7 = pidfilePath(reg.engineDataDir, reg.engineId, reg.hostKind, reg.hostPid);
436
670
  try {
437
- writePidfileAtomic(path6, {
671
+ writePidfileAtomic(path7, {
438
672
  enginePid: reg.enginePid,
439
673
  hostPid: reg.hostPid,
440
674
  engineStartTime: readProcessStartTime(reg.enginePid) ?? null,
@@ -442,9 +676,9 @@ function registerEnginePidfile(reg) {
442
676
  hostKind: reg.hostKind,
443
677
  writtenAt: (/* @__PURE__ */ new Date()).toISOString()
444
678
  });
445
- return path6;
679
+ return path7;
446
680
  } catch (err) {
447
- logger4.warn(
681
+ logger5.warn(
448
682
  `[pid-file] pidfile write failed for ${reg.engineId} (sweep coverage degraded): ${err instanceof Error ? err.message : String(err)}`
449
683
  );
450
684
  return void 0;
@@ -454,7 +688,7 @@ function sweepEnginePidfiles(args) {
454
688
  try {
455
689
  return sweepStalePidfiles(args);
456
690
  } catch (err) {
457
- logger4.warn(
691
+ logger5.warn(
458
692
  `[pid-file] pidfile sweep failed for ${args.engineId} (conservative skip): ${err instanceof Error ? err.message : String(err)}`
459
693
  );
460
694
  return { killed: [], removed: [], skipped: [] };
@@ -512,10 +746,10 @@ function sweepStalePidfiles(opts) {
512
746
  }
513
747
  return result;
514
748
  }
515
- function sweepOnePidfile(path6, file, opts, platform, result) {
516
- const content = readPidfile(path6);
749
+ function sweepOnePidfile(path7, file, opts, platform, result) {
750
+ const content = readPidfile(path7);
517
751
  if (content === void 0) {
518
- removePidfile(path6);
752
+ removePidfile(path7);
519
753
  result.removed.push({ file, reason: "unreadable-or-corrupt" });
520
754
  return;
521
755
  }
@@ -532,11 +766,11 @@ function sweepOnePidfile(path6, file, opts, platform, result) {
532
766
  result.skipped.push({ file, reason: "host-pid-alive" });
533
767
  return;
534
768
  }
535
- reapDeadHostEngine(path6, file, content, opts, platform, result);
769
+ reapDeadHostEngine(path7, file, content, opts, platform, result);
536
770
  }
537
- function reapDeadHostEngine(path6, file, content, opts, platform, result) {
771
+ function reapDeadHostEngine(path7, file, content, opts, platform, result) {
538
772
  if (platform === "win32") {
539
- removePidfile(path6);
773
+ removePidfile(path7);
540
774
  result.removed.push({ file, reason: "stale (host dead); win32 never kills" });
541
775
  return;
542
776
  }
@@ -546,7 +780,7 @@ function reapDeadHostEngine(path6, file, content, opts, platform, result) {
546
780
  return;
547
781
  }
548
782
  if (!engineAlive) {
549
- removePidfile(path6);
783
+ removePidfile(path7);
550
784
  result.removed.push({ file, reason: "engine-pid-dead" });
551
785
  return;
552
786
  }
@@ -556,7 +790,7 @@ function reapDeadHostEngine(path6, file, content, opts, platform, result) {
556
790
  return;
557
791
  }
558
792
  if (!opts.matchesEngineCmdline(cmdline)) {
559
- removePidfile(path6);
793
+ removePidfile(path7);
560
794
  result.removed.push({ file, reason: "cmdline-mismatch (pid reused by unrelated process)" });
561
795
  return;
562
796
  }
@@ -566,13 +800,13 @@ function reapDeadHostEngine(path6, file, content, opts, platform, result) {
566
800
  return;
567
801
  }
568
802
  if (content.engineStartTime === null || content.engineStartTime !== startTime) {
569
- removePidfile(path6);
803
+ removePidfile(path7);
570
804
  result.removed.push({ file, reason: "engine-start-time-mismatch (pid reused)" });
571
805
  return;
572
806
  }
573
807
  const killEngine = opts.killEngine ?? defaultKillEngineProcessGroup;
574
808
  killEngine(content.enginePid);
575
- removePidfile(path6);
809
+ removePidfile(path7);
576
810
  result.killed.push(content.enginePid);
577
811
  result.removed.push({ file, reason: "swept after kill (stale orphan)" });
578
812
  }
@@ -581,12 +815,12 @@ function defaultKillEngineProcessGroup(enginePid) {
581
815
  process.kill(-enginePid, "SIGKILL");
582
816
  return;
583
817
  } catch (groupErr) {
584
- logger4.debug(`[pid-file] process-group kill(-${enginePid}) failed, falling back to single kill: ${groupErr instanceof Error ? groupErr.message : String(groupErr)}`);
818
+ logger5.debug(`[pid-file] process-group kill(-${enginePid}) failed, falling back to single kill: ${groupErr instanceof Error ? groupErr.message : String(groupErr)}`);
585
819
  }
586
820
  try {
587
821
  process.kill(enginePid, "SIGKILL");
588
822
  } catch (err) {
589
- logger4.debug(`[pid-file] single kill(${enginePid}) failed (already dead = expected): ${err instanceof Error ? err.message : String(err)}`);
823
+ logger5.debug(`[pid-file] single kill(${enginePid}) failed (already dead = expected): ${err instanceof Error ? err.message : String(err)}`);
590
824
  }
591
825
  }
592
826
 
@@ -596,7 +830,7 @@ import {
596
830
  REVERSE_CHANNEL_TIMEOUT_CLASS,
597
831
  REVERSE_REQUEST_TIMEOUT_MS
598
832
  } from "@zhushanwen/subagent-engine-sdk";
599
- var logger5 = getLogger4("subagents");
833
+ var logger6 = getLogger4("subagents");
600
834
  function routeReverseRequest(deps, frame) {
601
835
  if (frame.method === "host/askUser") {
602
836
  handleInteractionRequest(deps, frame, deps.uiRequestHandler, (params) => params.request);
@@ -631,7 +865,7 @@ function handleInteractionRequest(deps, frame, handler, extract) {
631
865
  const result = await handler(input);
632
866
  if (!deps.isDisposed()) deps.sendResponse(frame.id, result);
633
867
  } catch (err) {
634
- logger5.warn(
868
+ logger6.warn(
635
869
  `[engine-client:${deps.engineId}] ${frame.method} handler failed, answering cancelled: ${err instanceof Error ? err.message : String(err)}`
636
870
  );
637
871
  if (!deps.isDisposed()) deps.sendResponse(frame.id, { cancelled: true });
@@ -642,7 +876,7 @@ function handleDataPlaneRequest(deps, frame) {
642
876
  let settled = false;
643
877
  const guardTimer = setTimeout(() => {
644
878
  if (settled) return;
645
- logger5.error(
879
+ logger6.error(
646
880
  `[engine-client:${deps.engineId}] data-plane reverse request ${frame.method} unanswered for ${REVERSE_REQUEST_TIMEOUT_MS}ms \u2014 treating as engine fault`
647
881
  );
648
882
  void deps.failEngine(
@@ -658,7 +892,7 @@ function handleDataPlaneRequest(deps, frame) {
658
892
  (err) => {
659
893
  settled = true;
660
894
  clearTimeout(guardTimer);
661
- logger5.warn(
895
+ logger6.warn(
662
896
  `[engine-client:${deps.engineId}] ${frame.method} dispatch failed (answering ok, host-side bug): ${err instanceof Error ? err.message : String(err)}`
663
897
  );
664
898
  deps.sendResponse(frame.id, { ok: true });
@@ -668,7 +902,6 @@ function handleDataPlaneRequest(deps, frame) {
668
902
  var DATA_PLANE_HANDLERS = {
669
903
  "host/log": (deps, params) => dispatchLog(deps, params),
670
904
  "host/streamDelta": (deps, params) => dispatchStreamDelta(deps, params),
671
- "host/roundLifecycle": (deps, params) => dispatchRoundLifecycle(deps, params),
672
905
  "host/poolResolved": (deps, params) => dispatchPoolResolved(deps, params),
673
906
  "host/handleReady": (deps, params) => dispatchHandleReady(deps, params),
674
907
  "host/childSpawned": (deps, params) => dispatchChildSpawned(deps, params),
@@ -679,22 +912,12 @@ async function dispatchDataPlane(deps, method, params) {
679
912
  }
680
913
  function dispatchLog(deps, p) {
681
914
  if (deps.log !== void 0) deps.log(p);
682
- else logger5[p.level](`[engine:${p.component}] ${p.message}`, p.data);
915
+ else logger6[p.level](`[engine:${p.component}] ${p.message}`, p.data);
683
916
  }
684
917
  async function dispatchStreamDelta(deps, p) {
685
- if (p.recordId !== void 0) {
686
- await deps.recordRoutes.get(p.recordId)?.onStreamDelta?.(p.delta);
687
- return;
688
- }
918
+ if (p.runId === void 0) return;
689
919
  await deps.runRoutes.get(p.runId)?.onStreamDelta?.(p.delta);
690
920
  }
691
- async function dispatchRoundLifecycle(deps, p) {
692
- if (p.recordId !== void 0) {
693
- await deps.recordRoutes.get(p.recordId)?.onRoundLifecycle?.(p);
694
- return;
695
- }
696
- await deps.runRoutes.get(p.runId)?.onRoundLifecycle?.(p);
697
- }
698
921
  async function dispatchPoolResolved(deps, p) {
699
922
  await deps.runRoutes.get(p.runId)?.onPoolResolved?.(p.poolKey);
700
923
  }
@@ -710,11 +933,11 @@ function dispatchChildSpawned(deps, p) {
710
933
  if (process.platform !== "win32" && typeof p.pid === "number") {
711
934
  try {
712
935
  process.kill(-p.pid, 0);
713
- logger5.warn(
936
+ logger6.warn(
714
937
  `[engine-client:${deps.engineId}] childSpawned pid ${p.pid} leads its own process group (not in the engine harvest group \u2014 one-generation children + in-group descendants only; engine-detached descendants are an accepted cost per design \xA73.9)`
715
938
  );
716
939
  } catch (err) {
717
- logger5.debug(
940
+ logger6.debug(
718
941
  `[engine-client:${deps.engineId}] childSpawned pid ${p.pid} group probe settled (${err instanceof Error ? err.message : String(err)})`
719
942
  );
720
943
  }
@@ -736,7 +959,7 @@ import { basename } from "path";
736
959
  import {
737
960
  getLogger as getLogger5
738
961
  } from "@zhushanwen/subagent-engine-sdk";
739
- var logger6 = getLogger5("subagents");
962
+ var logger7 = getLogger5("subagents");
740
963
  function defaultEngineCmdlineMatcher(command) {
741
964
  const base = basename(command);
742
965
  return (cmdline) => {
@@ -749,7 +972,7 @@ function warnOnManifestDiagnostics(engineId, diag, result) {
749
972
  const answered = JSON.stringify(result.capabilities ?? null);
750
973
  const declared = JSON.stringify(diag.capabilities);
751
974
  if (answered !== declared) {
752
- logger6.warn(
975
+ logger7.warn(
753
976
  `[engine-client:${engineId}] initialize capabilities differ from manifest (diagnostic only): manifest=${declared} answered=${answered}`
754
977
  );
755
978
  }
@@ -758,7 +981,7 @@ function warnOnManifestDiagnostics(engineId, diag, result) {
758
981
  const answered = JSON.stringify(result.models ?? null);
759
982
  const declared = JSON.stringify(diag.models ?? null);
760
983
  if (answered !== declared) {
761
- logger6.warn(
984
+ logger7.warn(
762
985
  `[engine-client:${engineId}] initialize models differ from manifest (diagnostic only): manifest=${declared} answered=${answered}`
763
986
  );
764
987
  }
@@ -768,13 +991,13 @@ function warnOnManifestDiagnostics(engineId, diag, result) {
768
991
  // src/execution/engine/client/reaper.ts
769
992
  import { spawnSync as spawnSync2 } from "child_process";
770
993
  import { getLogger as getLogger6 } from "@zhushanwen/subagent-engine-sdk";
771
- var logger7 = getLogger6("subagents");
994
+ var logger8 = getLogger6("subagents");
772
995
  var KILL_GRACE_MS = 5e3;
773
996
  function killProcessTree(pid, reason, opts) {
774
997
  if (process.platform === "win32") {
775
998
  const r = spawnSync2("taskkill", ["/PID", String(pid), "/T", "/F"], { timeout: 1e4 });
776
999
  if (r.error || r.status !== 0) {
777
- logger7.warn(
1000
+ logger8.warn(
778
1001
  `[engine-client:${opts.engineId}] taskkill failed for pid ${pid} (${reason}): ${r.error?.message ?? `exit ${r.status}`}`
779
1002
  );
780
1003
  }
@@ -785,7 +1008,7 @@ function killProcessTree(pid, reason, opts) {
785
1008
  process.kill(-pid, signal);
786
1009
  return true;
787
1010
  } catch (err) {
788
- logger7.debug(
1011
+ logger8.debug(
789
1012
  `[engine-client:${opts.engineId}] process.kill(-${pid}, ${signal}) failed: ${err instanceof Error ? err.message : String(err)}`
790
1013
  );
791
1014
  return false;
@@ -795,7 +1018,7 @@ function killProcessTree(pid, reason, opts) {
795
1018
  try {
796
1019
  process.kill(pid, "SIGTERM");
797
1020
  } catch (err) {
798
- logger7.debug(
1021
+ logger8.debug(
799
1022
  `[engine-client:${opts.engineId}] single SIGTERM to ${pid} failed (already dead = expected): ${err instanceof Error ? err.message : String(err)}`
800
1023
  );
801
1024
  return;
@@ -807,7 +1030,7 @@ function killProcessTree(pid, reason, opts) {
807
1030
  try {
808
1031
  process.kill(pid, "SIGKILL");
809
1032
  } catch (err) {
810
- logger7.debug(
1033
+ logger8.debug(
811
1034
  `[engine-client:${opts.engineId}] SIGKILL to ${pid} failed (already dead = expected): ${err instanceof Error ? err.message : String(err)}`
812
1035
  );
813
1036
  }
@@ -831,8 +1054,143 @@ function waitForChildExit(child, timeoutMs) {
831
1054
  });
832
1055
  }
833
1056
 
1057
+ // src/execution/engine/host/spawned-children.ts
1058
+ var CoreSpawnedChildrenMirror = class {
1059
+ entries = /* @__PURE__ */ new Map();
1060
+ /** 落项 / 覆盖(同 recordId 重 spawn = 覆盖旧句柄,与引擎侧 Map 同语义)。 */
1061
+ register(recordId, child) {
1062
+ this.entries.set(recordId, {
1063
+ pid: child.pid,
1064
+ killed: child.killed,
1065
+ updatedAt: Date.now()
1066
+ });
1067
+ }
1068
+ /** 单项置死(杀链入口:killRecordChildWithEscalation 途经)。 */
1069
+ markKilled(recordId) {
1070
+ const entry = this.entries.get(recordId);
1071
+ if (entry !== void 0) {
1072
+ entry.killed = true;
1073
+ entry.updatedAt = Date.now();
1074
+ }
1075
+ }
1076
+ /** 整体置死 + 清空(失效语义 2+3:引擎 exit / 重建 / dispose / killAll)。 */
1077
+ killAll() {
1078
+ this.entries.clear();
1079
+ }
1080
+ getChildByRecord(recordId) {
1081
+ return this.entries.get(recordId);
1082
+ }
1083
+ /** 句柄存活谓词(镜像面;判据与引擎侧 `!child.killed` 同构)。 */
1084
+ hasLiveProcessHandle(recordId) {
1085
+ const entry = this.entries.get(recordId);
1086
+ return entry !== void 0 && !entry.killed;
1087
+ }
1088
+ /** 快照(诊断/测试)。 */
1089
+ snapshot() {
1090
+ return [...this.entries.entries()].map(([recordId, entry]) => ({ recordId, ...entry }));
1091
+ }
1092
+ /** 测试隔离专用(生产禁用——进程级全局状态)。 */
1093
+ clear() {
1094
+ this.entries.clear();
1095
+ }
1096
+ };
1097
+ var MIRROR_SLOT_KEY = /* @__PURE__ */ Symbol.for(
1098
+ "@zhushanwen/pi-subagent-workflow.coreSpawnedChildrenMirror"
1099
+ );
1100
+ function coreMirrorSlot() {
1101
+ let slot = Reflect.get(globalThis, MIRROR_SLOT_KEY);
1102
+ if (!slot) {
1103
+ slot = new CoreSpawnedChildrenMirror();
1104
+ Reflect.set(globalThis, MIRROR_SLOT_KEY, slot);
1105
+ }
1106
+ return slot;
1107
+ }
1108
+ function coreSpawnedChildrenMirror() {
1109
+ return coreMirrorSlot();
1110
+ }
1111
+ function registerSpawnedChildForRecord(recordId, child) {
1112
+ coreMirrorSlot().register(recordId, { pid: child.pid, killed: child.killed });
1113
+ }
1114
+ function killRecordChildWithEscalation(recordId, _source) {
1115
+ coreMirrorSlot().markKilled(recordId);
1116
+ }
1117
+ function killAllSpawnedChildren(_signal = "SIGTERM") {
1118
+ const before = coreMirrorSlot().snapshot().length;
1119
+ coreMirrorSlot().killAll();
1120
+ return before;
1121
+ }
1122
+ function hasLiveProcessHandleCore(recordId) {
1123
+ return coreMirrorSlot().hasLiveProcessHandle(recordId);
1124
+ }
1125
+
1126
+ // src/execution/lifecycle-manager.ts
1127
+ var logger9 = getLogger("subagents");
1128
+ var MS_PER_SECOND2 = 1e3;
1129
+ var SECONDS_PER_MINUTE2 = 60;
1130
+ var IDLE_TIMEOUT_MINUTES = 5;
1131
+ var DEFAULT_IDLE_TIMEOUT_MS = IDLE_TIMEOUT_MINUTES * SECONDS_PER_MINUTE2 * MS_PER_SECOND2;
1132
+ var idleTimers = /* @__PURE__ */ new Map();
1133
+ function disarmIdleTimer(recordId) {
1134
+ const entry = idleTimers.get(recordId);
1135
+ if (!entry) {
1136
+ return;
1137
+ }
1138
+ clearTimeout(entry.timer);
1139
+ idleTimers.delete(recordId);
1140
+ }
1141
+ function hasIdleTimer(recordId) {
1142
+ return idleTimers.has(recordId);
1143
+ }
1144
+
1145
+ // src/execution/lifecycle-predicates.ts
1146
+ function hasLiveProcessHandle(recordId) {
1147
+ return hasLiveProcessHandleCore(recordId);
1148
+ }
1149
+ function isIdle(record) {
1150
+ return hasIdleTimer(record.id);
1151
+ }
1152
+ function isResumable(record) {
1153
+ return record.status === "running" && !hasLiveProcessHandle(record.id);
1154
+ }
1155
+
1156
+ // src/execution/engine/inflight-snapshot.ts
1157
+ var listener = null;
1158
+ function setInFlightListener(next) {
1159
+ listener = next;
1160
+ }
1161
+ function getInFlightSnapshot() {
1162
+ return { inFlight: countInFlight() };
1163
+ }
1164
+ function notifyInFlightChanged() {
1165
+ if (listener === null) return;
1166
+ try {
1167
+ listener(getInFlightSnapshot());
1168
+ } catch {
1169
+ }
1170
+ }
1171
+ function countInFlight() {
1172
+ let count = 0;
1173
+ for (const entry of coreSpawnedChildrenMirror().snapshot()) {
1174
+ const recordId = entry.recordId;
1175
+ if (hasLiveProcessHandle(recordId) && !hasIdleTimer(recordId)) count++;
1176
+ }
1177
+ return count;
1178
+ }
1179
+
834
1180
  // src/execution/engine/client/engine-client.ts
835
- var logger8 = getLogger7("subagents");
1181
+ var logger10 = getLogger7("subagents");
1182
+ function bridgeMirrorEventToCoreMirror(event, mirror) {
1183
+ if (event.recordId === void 0 || event.pid === void 0) return;
1184
+ const entry = mirror.getEntry(event.pid);
1185
+ if (entry === void 0) return;
1186
+ const core = coreSpawnedChildrenMirror();
1187
+ if (entry.killed) {
1188
+ core.markKilled(event.recordId);
1189
+ } else {
1190
+ core.register(event.recordId, { pid: entry.pid, killed: false });
1191
+ }
1192
+ notifyInFlightChanged();
1193
+ }
836
1194
  var DISPOSE_GRACE_MS = 3e3;
837
1195
  var SIGKILL_REAP_TIMEOUT_MS = 1e4;
838
1196
  var FRAME_ECHO_MAX_CHARS = 200;
@@ -862,9 +1220,6 @@ var EngineClient = class {
862
1220
  nextRequestId = 1;
863
1221
  pending = /* @__PURE__ */ new Map();
864
1222
  runRoutes = /* @__PURE__ */ new Map();
865
- /** [W3 v1.x] chat 轮次路由(recordId 键,D1-A——续聊轮无 runId);注册经
866
- * RemoteEngine.registerChatRoundRoute,record 终态注销。 */
867
- recordRoutes = /* @__PURE__ */ new Map();
868
1223
  stderrTail = "";
869
1224
  unavailableReason;
870
1225
  connectInFlight;
@@ -884,7 +1239,6 @@ var EngineClient = class {
884
1239
  permissionHandler: opts.permissionHandler,
885
1240
  log: opts.log,
886
1241
  runRoutes: this.runRoutes,
887
- recordRoutes: this.recordRoutes,
888
1242
  mirror: this.mirror,
889
1243
  setPartialHandle: (partial) => {
890
1244
  this.lastPartialHandle = partial;
@@ -895,6 +1249,7 @@ var EngineClient = class {
895
1249
  };
896
1250
  this.mirror.onChange((event) => {
897
1251
  opts.onMirrorChanged?.(event);
1252
+ bridgeMirrorEventToCoreMirror(event, this.mirror);
898
1253
  });
899
1254
  }
900
1255
  get currentState() {
@@ -957,19 +1312,19 @@ var EngineClient = class {
957
1312
  matchesEngineCmdline: this.opts.engineCmdlineMatcher ?? defaultEngineCmdlineMatcher(this.opts.command)
958
1313
  });
959
1314
  if (sweep.killed.length > 0) {
960
- logger8.warn(
1315
+ logger10.warn(
961
1316
  `[engine-client:${this.engineId}] startup sweep killed stale orphan engine pids: ${sweep.killed.join(",")}`
962
1317
  );
963
1318
  }
964
1319
  for (const removed of sweep.removed) {
965
- logger8.debug(`[engine-client:${this.engineId}] swept stale pidfile ${removed.file}: ${removed.reason}`);
1320
+ logger10.debug(`[engine-client:${this.engineId}] swept stale pidfile ${removed.file}: ${removed.reason}`);
966
1321
  }
967
1322
  }
968
1323
  let attempt = 0;
969
1324
  while (attempt <= CRASH_REBUILD_MAX_ATTEMPTS) {
970
1325
  if (attempt > 0) {
971
1326
  const backoff = CRASH_REBUILD_BACKOFF_MS[attempt - 1];
972
- logger8.warn(
1327
+ logger10.warn(
973
1328
  `[engine-client:${this.engineId}] rebuild attempt ${attempt}/${CRASH_REBUILD_MAX_ATTEMPTS} after ${backoff}ms backoff`
974
1329
  );
975
1330
  await delay(backoff);
@@ -1002,7 +1357,7 @@ var EngineClient = class {
1002
1357
  markUnavailable(reason) {
1003
1358
  this.state = "unavailable";
1004
1359
  this.unavailableReason = reason;
1005
- logger8.error(`[engine-client:${this.engineId}] marked unavailable: ${reason.message}`);
1360
+ logger10.error(`[engine-client:${this.engineId}] marked unavailable: ${reason.message}`);
1006
1361
  }
1007
1362
  /** spawn 引擎 CLI + initialize 握手 + 版本协商 + 诊断留痕。 */
1008
1363
  async spawnAndInitialize() {
@@ -1032,13 +1387,13 @@ var EngineClient = class {
1032
1387
  });
1033
1388
  const child = this.child;
1034
1389
  child.on("error", (err) => {
1035
- logger8.warn(`[engine-client:${this.engineId}] spawn error: ${err.message}`);
1390
+ logger10.warn(`[engine-client:${this.engineId}] spawn error: ${err.message}`);
1036
1391
  this.appendStderrTail(`spawn error: ${err.message}
1037
1392
  `);
1038
1393
  });
1039
1394
  for (const pipe of ["stdin", "stdout", "stderr"]) {
1040
1395
  child[pipe]?.on("error", (err) => {
1041
- logger8.debug(`[engine-client:${this.engineId}] ${pipe} pipe error (engine died concurrently = expected): ${err.message}`);
1396
+ logger10.debug(`[engine-client:${this.engineId}] ${pipe} pipe error (engine died concurrently = expected): ${err.message}`);
1042
1397
  });
1043
1398
  }
1044
1399
  child.stdout?.setEncoding("utf-8");
@@ -1107,7 +1462,7 @@ var EngineClient = class {
1107
1462
  try {
1108
1463
  frame = JSON.parse(line);
1109
1464
  } catch {
1110
- logger8.warn(
1465
+ logger10.warn(
1111
1466
  `[engine-client:${this.engineId}] dropped non-NDJSON stdout line (protocol contract: stdout is NDJSON-only): ${truncate(line, FRAME_ECHO_MAX_CHARS)}`
1112
1467
  );
1113
1468
  return;
@@ -1124,7 +1479,7 @@ var EngineClient = class {
1124
1479
  this.onReverseRequest(frame);
1125
1480
  return;
1126
1481
  }
1127
- logger8.warn(
1482
+ logger10.warn(
1128
1483
  `[engine-client:${this.engineId}] dropped unrecognized frame: ${truncate(line, FRAME_ECHO_MAX_CHARS)}`
1129
1484
  );
1130
1485
  }
@@ -1205,14 +1560,6 @@ var EngineClient = class {
1205
1560
  if (this.runRoutes.get(runId) === route) this.runRoutes.delete(runId);
1206
1561
  };
1207
1562
  }
1208
- /** [W3 v1.x] 注册 chat 轮次路由(recordId 键);分发见 reverse-router.ts
1209
- * (与 runRoutes 同构,键空间互斥:runId=run 帧分配 / recordId=宿主铸造)。 */
1210
- registerRecordRoute(recordId, route) {
1211
- this.recordRoutes.set(recordId, route);
1212
- return () => {
1213
- if (this.recordRoutes.get(recordId) === route) this.recordRoutes.delete(recordId);
1214
- };
1215
- }
1216
1563
  /** 健康检查(ADR-0047:静默 ≠ 卡死,不据此杀任务)。 */
1217
1564
  async ping() {
1218
1565
  await this.ensureConnected();
@@ -1248,7 +1595,7 @@ var EngineClient = class {
1248
1595
  try {
1249
1596
  await this.request("dispose", {}, { timeoutMs: DISPOSE_GRACE_MS });
1250
1597
  } catch (err) {
1251
- logger8.debug(
1598
+ logger10.debug(
1252
1599
  `[engine-client:${this.engineId}] dispose frame failed, falling back to kill chain: ${err instanceof Error ? err.message : String(err)}`
1253
1600
  );
1254
1601
  }
@@ -1260,18 +1607,20 @@ var EngineClient = class {
1260
1607
  onEngineExit(code, signal) {
1261
1608
  const detail = signal !== null ? `signal ${signal}` : `exit code ${code}`;
1262
1609
  if (this.intentionalKill) {
1263
- logger8.debug(`[engine-client:${this.engineId}] engine exited after intentional kill (${detail}) \u2014 expected`);
1610
+ logger10.debug(`[engine-client:${this.engineId}] engine exited after intentional kill (${detail}) \u2014 expected`);
1264
1611
  } else {
1265
- logger8.warn(`[engine-client:${this.engineId}] engine process exited unexpectedly (${detail})`);
1612
+ logger10.warn(`[engine-client:${this.engineId}] engine process exited unexpectedly (${detail})`);
1266
1613
  }
1267
1614
  this.teardownProcess(detail);
1268
1615
  this.state = "exited";
1269
1616
  }
1270
1617
  /**
1271
- * 进程死亡 / 失败后的统一清理:镜像整体置死(失效语义 2+3:killed=true 广播后
1272
- * 清空)→ pidfile 删 → 在途请求 engine_crashed(附 stderr 尾)→ run 路由清空。
1618
+ * 进程死亡 / 失败后的统一清理(收割/组杀分支各成子函数,此处只做顺序编排):
1619
+ * 孤儿收割(镜像置死前)→ 镜像置死(失效语义 2+3)→ pidfile 删 → 在途请求
1620
+ * engine_crashed(附 stderr 尾)→ run 路由清空 → 活引擎组杀兜底。
1273
1621
  */
1274
1622
  teardownProcess(detail) {
1623
+ this.reapOrphansAfterUnexpectedDeath(detail);
1275
1624
  this.mirror.killAll();
1276
1625
  if (this.pidfileWritten !== void 0) {
1277
1626
  removePidfile(this.pidfileWritten);
@@ -1284,8 +1633,46 @@ var EngineClient = class {
1284
1633
  }
1285
1634
  this.pending.clear();
1286
1635
  this.runRoutes.clear();
1287
- this.recordRoutes.clear();
1288
1636
  this.lastPartialHandle = void 0;
1637
+ this.killLeakedAliveChild(detail);
1638
+ if (this.state !== "unavailable" && this.state !== "disposed") {
1639
+ this.state = "exited";
1640
+ }
1641
+ }
1642
+ /**
1643
+ * [H1 U2 / 红线①收割链] 非主动死亡且引擎已死的路径,在镜像置死**之前**补发收割
1644
+ * 信号:孤儿真因 = 引擎意外死时宿主未发收割信号(原先 alreadyDead 分支直接跳过
1645
+ * killProcessTree),任务子进程留在无主进程组继续写 session 文件。插入点约束:
1646
+ * - 位置 = mirror.killAll() 置死清空之前——Windows 通道依赖镜像快照过滤活孤儿
1647
+ * (置死后全量项 killed=true 不可过滤);
1648
+ * - 条件 = !intentionalKill && alreadyDead——主动 dispose 链已有两层杀(引擎
1649
+ * killAllActiveChildren SIGTERM+30s + 宿主 killAll 组杀 5s 升级),跳过即不叠加、
1650
+ * 优雅窗零压缩、pi trap-flush 零截断;进程仍活的路径(握手失败重建)由
1651
+ * killLeakedAliveChild 组杀兜底承接。
1652
+ */
1653
+ reapOrphansAfterUnexpectedDeath(detail) {
1654
+ if (!this.intentionalKill && this.child !== void 0) {
1655
+ const dying = this.child;
1656
+ const alreadyDead = dying.exitCode !== null || dying.signalCode !== null;
1657
+ if (alreadyDead && dying.pid !== void 0) {
1658
+ if (process.platform === "win32") {
1659
+ this.reapOrphansViaMirrorSnapshot(detail);
1660
+ } else {
1661
+ killProcessTree(
1662
+ dying.pid,
1663
+ `reap orphaned children after unexpected engine death (${detail})`,
1664
+ { engineId: this.engineId }
1665
+ );
1666
+ }
1667
+ }
1668
+ }
1669
+ }
1670
+ /**
1671
+ * 握手失败路径(版本越界/握手超时/启动僵死)引擎进程仍活着:必须组杀,否则
1672
+ * crash 重建循环每次 spawn 泄漏一个常驻孤儿(测试机实测积累 370+)。主动杀
1673
+ * (intentionalKill=true)与进程已死(exitCode/signalCode 非空)跳过重复发信号。
1674
+ */
1675
+ killLeakedAliveChild(detail) {
1289
1676
  if (this.child !== void 0) {
1290
1677
  const child = this.child;
1291
1678
  this.child = void 0;
@@ -1295,8 +1682,30 @@ var EngineClient = class {
1295
1682
  }
1296
1683
  child.removeAllListeners();
1297
1684
  }
1298
- if (this.state !== "unavailable" && this.state !== "disposed") {
1299
- this.state = "exited";
1685
+ }
1686
+ /**
1687
+ * [H1 U2 / 红线①Windows 通道] 镜像快照收割活孤儿:镜像快照同步抓清单(置死前,
1688
+ * 全量项含引擎一生历史死 pid——镜像对已退出子进程只改 state 不删项、唯一清空点 =
1689
+ * killAll)→ 过滤活孤儿(state === "running" && !killed,活孤儿 ≤ 并发数)→ 逐 pid
1690
+ * **异步 spawn** taskkill /T /F fire-and-forget(树根 = 活着的任务子进程自身,树杀
1691
+ * 有效;快照同步抓、杀动作异步发——与 POSIX fire-and-forget 对称;禁 spawnSync:
1692
+ * 逐个同步 N×10s 上界会冻结 onEngineExit 同步回调、推迟 pending reject(Continuation
1693
+ * 失败通知链源头)与宿主事件循环)。
1694
+ */
1695
+ reapOrphansViaMirrorSnapshot(detail) {
1696
+ const liveOrphans = this.mirror.snapshot().filter((e) => e.state === "running" && !e.killed);
1697
+ if (liveOrphans.length === 0) return;
1698
+ logger10.warn(
1699
+ `[engine-client:${this.engineId}] reaping ${liveOrphans.length} orphaned task child(ren) via mirror snapshot after unexpected engine death (${detail})`
1700
+ );
1701
+ for (const entry of liveOrphans) {
1702
+ const killer = spawn("taskkill", ["/PID", String(entry.pid), "/T", "/F"], { stdio: "ignore" });
1703
+ killer.unref();
1704
+ killer.on("error", (err) => {
1705
+ logger10.debug(
1706
+ `[engine-client:${this.engineId}] taskkill spawn failed for orphan pid ${entry.pid} (${err.message})`
1707
+ );
1708
+ });
1300
1709
  }
1301
1710
  }
1302
1711
  // ── pidfile ──────────────────────────────────────────────────────────────
@@ -1311,7 +1720,7 @@ var EngineClient = class {
1311
1720
  `);
1312
1721
  return true;
1313
1722
  } catch (err) {
1314
- logger8.debug(
1723
+ logger10.debug(
1315
1724
  `[engine-client:${this.engineId}] stdin write failed: ${err instanceof Error ? err.message : String(err)}`
1316
1725
  );
1317
1726
  return false;
@@ -1334,11 +1743,25 @@ function truncate(text, max) {
1334
1743
  }
1335
1744
 
1336
1745
  // src/execution/engine/client/remote-engine.ts
1746
+ import { homedir as homedir2 } from "os";
1747
+ import { join as join4 } from "path";
1337
1748
  import {
1338
1749
  CANCEL_SETTLE_GRACE_MS as CANCEL_SETTLE_GRACE_MS2,
1339
1750
  EngineSdkError as EngineSdkError2
1340
1751
  } from "@zhushanwen/subagent-engine-sdk";
1341
1752
 
1753
+ // src/execution/path-encoding.ts
1754
+ import * as path2 from "path";
1755
+ function encodeCwd(cwd) {
1756
+ return "--" + cwd.replace(/^[/\\]/, "").replace(/[/\\:]/g, "-") + "--";
1757
+ }
1758
+ function getSubagentSessionDir(agentDir, mainCwd) {
1759
+ return path2.join(agentDir, "subagents", encodeCwd(mainCwd), "sessions");
1760
+ }
1761
+ function getSubagentRecordsDir(agentDir, mainCwd) {
1762
+ return path2.join(agentDir, "subagents", encodeCwd(mainCwd), "records");
1763
+ }
1764
+
1342
1765
  // src/execution/engine/common/capability-gate.ts
1343
1766
  import { engineConversationUnsupportedError } from "@zhushanwen/subagent-engine-sdk";
1344
1767
 
@@ -1353,7 +1776,7 @@ var EngineError = class extends Error {
1353
1776
  this.code = code;
1354
1777
  this.recovery = recovery;
1355
1778
  }
1356
- /** 结构化投影(InteractResult.code/message 与 GUI 警告条共用形态)。 */
1779
+ /** 结构化投影(code/message 与 GUI 警告条共用形态)。 */
1357
1780
  toStructured() {
1358
1781
  return { code: this.code, message: this.message, recovery: this.recovery };
1359
1782
  }
@@ -1410,6 +1833,13 @@ function assertGateCapabilitiesMatched(engineId, manifestCaps, answeredCaps) {
1410
1833
  throw gateMismatchError(engineId, "sandbox\uFF08worktree\uFF09", manifestCaps.sandbox, answeredCaps.sandbox);
1411
1834
  }
1412
1835
  }
1836
+ function engineConversationUpgradeUnsupportedError(engineId) {
1837
+ return new EngineError(
1838
+ "engine_capability_unsupported",
1839
+ `subagent completed as one-shot on engine '${engineId}' (capabilities.conversation = 'unsupported') \u2014 it cannot be upgraded to a resumable conversation, so the message is rejected.`,
1840
+ `Recovery: re-dispatch the follow-up as a new subagent (action:'start'), or branch from its session history with action:'fork-from', or pick an engine that declares the conversation capability.`
1841
+ );
1842
+ }
1413
1843
  function gateMismatchError(engineId, cap, declared, answered) {
1414
1844
  return new EngineError(
1415
1845
  "engine_capability_mismatch",
@@ -1531,22 +1961,6 @@ var RemoteEngine = class {
1531
1961
  unregister();
1532
1962
  }
1533
1963
  }
1534
- async interact(handle, action) {
1535
- await this.opts.client.ensureConnected();
1536
- const result = await this.opts.client.request("interact", {
1537
- handle: handle.data,
1538
- action
1539
- });
1540
- return result;
1541
- }
1542
- /**
1543
- * [W3 v1.x] chat 轮次反向通道路由注册(recordId 键,EnginePort 可选面实现):
1544
- * interact 续聊轮的 streamDelta / roundLifecycle 分发目标。薄委托 EngineClient
1545
- * 的 recordRoutes(键分发见 reverse-router.ts)。
1546
- */
1547
- registerChatRoundRoute(recordId, route) {
1548
- return this.opts.client.registerRecordRoute(recordId, route);
1549
- }
1550
1964
  /** 协议 read(dataDir 必填——引擎数据根,构造注入)。 */
1551
1965
  async read(handle) {
1552
1966
  await this.opts.client.ensureConnected();
@@ -1578,6 +1992,15 @@ function isTransientRunFailure(err) {
1578
1992
  if (!(err instanceof EngineSdkError2)) return false;
1579
1993
  return err.code === "engine_crashed" || err.code === "engine_request_timeout" || err.code === "engine_handshake_timeout";
1580
1994
  }
1995
+ var PI_AGENT_DIR_ENV = "PI_CODING_AGENT_DIR";
1996
+ var PI_ROOT_CWD_ENV = "PI_SUBAGENT_ROOT_CWD";
1997
+ function deriveHostSubagentSessionDir() {
1998
+ const agentDir = process.env[PI_AGENT_DIR_ENV];
1999
+ const resolvedAgentDir = agentDir !== void 0 && agentDir !== "" ? agentDir : join4(homedir2(), ".pi", "agent");
2000
+ const rootCwd = process.env[PI_ROOT_CWD_ENV];
2001
+ const resolvedRootCwd = rootCwd !== void 0 && rootCwd !== "" ? rootCwd : process.cwd();
2002
+ return getSubagentSessionDir(resolvedAgentDir, resolvedRootCwd);
2003
+ }
1581
2004
  function buildRunParams(task, ctx, runId) {
1582
2005
  const ctxModelRef = ctx.ctxModel ? `${ctx.ctxModel.provider}/${ctx.ctxModel.id}` : void 0;
1583
2006
  return {
@@ -1590,9 +2013,16 @@ function buildRunParams(task, ctx, runId) {
1590
2013
  schemaEnv: ctx.schemaEnv ?? task.schemaEnv,
1591
2014
  ctxModel: ctxModelRef,
1592
2015
  engineFallback: ctx.engineFallback,
1593
- streamMode: ctx.stream !== void 0 ? "stream" : void 0
2016
+ streamMode: ctx.stream !== void 0 ? "stream" : void 0,
2017
+ // [F6] 根 session id(relay 归属键 SESSION_ID 权威源)——undefined 不上 wire
2018
+ //(additive 语义,与顶层 chat 参数同写法)。
2019
+ ...ctx.sessionRootId !== void 0 ? { sessionRootId: ctx.sessionRootId } : {},
2020
+ // [Option C 协议化] 权威 subagent session 目录(Gate B S6):宿主注入值优先,
2021
+ // 缺省同源 env 推导(deriveHostSubagentSessionDir)——恒有值恒上 wire,引擎
2022
+ // 据此组装 --session-dir 不自推导(引擎本地推导降级 [LEGACY] fallback)。
2023
+ sessionDir: ctx.sessionDir ?? deriveHostSubagentSessionDir()
1594
2024
  },
1595
- ...ctx.chat !== void 0 ? { chat: ctx.chat } : {}
2025
+ ...ctx.resume !== void 0 ? { resume: ctx.resume } : {}
1596
2026
  };
1597
2027
  }
1598
2028
  function buildRunRouteHandlers(ctx) {
@@ -1600,9 +2030,7 @@ function buildRunRouteHandlers(ctx) {
1600
2030
  onEvent: (event) => ctx.onEvent?.(event),
1601
2031
  onStreamDelta: (delta) => ctx.stream?.onDelta(delta),
1602
2032
  onPoolResolved: (poolKey) => ctx.onPoolResolved?.(poolKey),
1603
- onHandleReady: (partial) => ctx.onHandleReady?.(partial),
1604
- // [W3 v1.x] 首轮(run 会话形态)轮次生命周期帧(runId 键)→ 宿主消费口。
1605
- onRoundLifecycle: (phase) => ctx.onRoundLifecycle?.(phase)
2033
+ onHandleReady: (partial) => ctx.onHandleReady?.(partial)
1606
2034
  };
1607
2035
  }
1608
2036
  function wireAbortSignal(client, runId, ctx) {
@@ -1695,11 +2123,11 @@ function getHostUiRequestEndpoint() {
1695
2123
  }
1696
2124
 
1697
2125
  // src/execution/engine/engine-inspect-package.ts
1698
- var logger9 = getLogger("subagents");
2126
+ var logger11 = getLogger("subagents");
1699
2127
  function readEnginePackageJson(pkgDir) {
1700
2128
  let raw;
1701
2129
  try {
1702
- raw = fs.readFileSync(path2.join(pkgDir, "package.json"), "utf8");
2130
+ raw = fs.readFileSync(path3.join(pkgDir, "package.json"), "utf8");
1703
2131
  } catch (err) {
1704
2132
  return { status: "skip", reason: `package.json unreadable: ${errorMessage(err)}` };
1705
2133
  }
@@ -1774,12 +2202,12 @@ function parseOptionalDisplayFields(m, id) {
1774
2202
  if (typeof rawDisplayName === "string" && rawDisplayName.trim() !== "") {
1775
2203
  displayName = rawDisplayName;
1776
2204
  } else {
1777
- logger9.warn(`[engine-discovery] engine '${id}': displayName must be a non-empty string \u2014 ignoring`);
2205
+ logger11.warn(`[engine-discovery] engine '${id}': displayName must be a non-empty string \u2014 ignoring`);
1778
2206
  }
1779
2207
  }
1780
2208
  const rawDescription = m["description"];
1781
2209
  if (rawDescription !== void 0 && typeof rawDescription !== "string") {
1782
- logger9.warn(`[engine-discovery] engine '${id}': description must be a string \u2014 ignoring`);
2210
+ logger11.warn(`[engine-discovery] engine '${id}': description must be a string \u2014 ignoring`);
1783
2211
  }
1784
2212
  return displayName;
1785
2213
  }
@@ -1867,19 +2295,19 @@ function errorMessage(err) {
1867
2295
 
1868
2296
  // src/execution/engine/engine-discovery-roots.ts
1869
2297
  import * as fs2 from "fs";
1870
- import * as path3 from "path";
1871
- var logger10 = getLogger("subagents");
2298
+ import * as path4 from "path";
2299
+ var logger12 = getLogger("subagents");
1872
2300
  var ENGINE_ROOTS_ENV = "XYZ_AGENT_ENGINE_ROOTS";
1873
2301
  function parseEngineRootsEnv(env) {
1874
2302
  const raw = env[ENGINE_ROOTS_ENV];
1875
2303
  if (raw === void 0 || raw.trim() === "") return [];
1876
2304
  const out = [];
1877
2305
  const seen = /* @__PURE__ */ new Set();
1878
- for (const part of raw.split(path3.delimiter)) {
2306
+ for (const part of raw.split(path4.delimiter)) {
1879
2307
  const dir = part.trim();
1880
2308
  if (dir === "") continue;
1881
- if (!path3.isAbsolute(dir)) {
1882
- logger10.warn(
2309
+ if (!path4.isAbsolute(dir)) {
2310
+ logger12.warn(
1883
2311
  `[engine-discovery] ${ENGINE_ROOTS_ENV} entry '${dir}' is not an absolute path \u2014 dropping entry`
1884
2312
  );
1885
2313
  continue;
@@ -1893,11 +2321,11 @@ function parseEngineRootsEnv(env) {
1893
2321
  function deriveNodeModuleRoots(argvEntry = process.argv[1]) {
1894
2322
  if (!argvEntry || argvEntry.trim() === "") return [];
1895
2323
  const roots = [];
1896
- let dir = path3.dirname(path3.resolve(argvEntry));
2324
+ let dir = path4.dirname(path4.resolve(argvEntry));
1897
2325
  while (true) {
1898
- const nm = path3.join(dir, "node_modules");
2326
+ const nm = path4.join(dir, "node_modules");
1899
2327
  if (isDirectory(nm)) roots.push(nm);
1900
- const parent = path3.dirname(dir);
2328
+ const parent = path4.dirname(dir);
1901
2329
  if (parent === dir) break;
1902
2330
  dir = parent;
1903
2331
  }
@@ -1912,8 +2340,8 @@ function scanEngineRoot(root, visit) {
1912
2340
  }
1913
2341
  for (const item of entries) {
1914
2342
  if (!item.isDirectory()) continue;
1915
- const dir = path3.join(root, item.name);
1916
- if (isFile(path3.join(dir, "package.json"))) {
2343
+ const dir = path4.join(root, item.name);
2344
+ if (isFile(path4.join(dir, "package.json"))) {
1917
2345
  visit(dir);
1918
2346
  continue;
1919
2347
  }
@@ -1925,8 +2353,8 @@ function scanEngineRoot(root, visit) {
1925
2353
  }
1926
2354
  for (const child of sub) {
1927
2355
  if (!child.isDirectory()) continue;
1928
- const subDir = path3.join(dir, child.name);
1929
- if (isFile(path3.join(subDir, "package.json"))) visit(subDir);
2356
+ const subDir = path4.join(dir, child.name);
2357
+ if (isFile(path4.join(subDir, "package.json"))) visit(subDir);
1930
2358
  }
1931
2359
  }
1932
2360
  }
@@ -1947,10 +2375,10 @@ function isFile(p) {
1947
2375
 
1948
2376
  // src/execution/engine/config.ts
1949
2377
  import * as fs3 from "fs";
1950
- import * as path4 from "path";
1951
- var logger11 = getLogger("subagents");
2378
+ import * as path5 from "path";
2379
+ var logger13 = getLogger("subagents");
1952
2380
  function readExplicitEngines(agentDir) {
1953
- const configPath = path4.join(agentDir, "subagents", "config.json");
2381
+ const configPath = path5.join(agentDir, "subagents", "config.json");
1954
2382
  let parsed;
1955
2383
  try {
1956
2384
  parsed = JSON.parse(fs3.readFileSync(configPath, "utf8"));
@@ -1961,7 +2389,7 @@ function readExplicitEngines(agentDir) {
1961
2389
  const engines = parsed["engines"];
1962
2390
  if (typeof engines !== "object" || engines === null || Array.isArray(engines)) {
1963
2391
  if (engines !== void 0) {
1964
- logger11.warn(
2392
+ logger13.warn(
1965
2393
  `[engine-discovery] config.json engines section must be an object keyed by engine id, got ${jsonKindOf(engines)} \u2014 ignoring L3 explicit engines`
1966
2394
  );
1967
2395
  }
@@ -1989,7 +2417,7 @@ function sanitizeExplicitEntry(id, raw) {
1989
2417
  }
1990
2418
  function requireEntryObject(id, raw) {
1991
2419
  if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
1992
- logger11.warn(
2420
+ logger13.warn(
1993
2421
  `[engine-discovery] config.json engines.${id} must be an object, got ${jsonKindOf(raw)} \u2014 skipping entry`
1994
2422
  );
1995
2423
  return void 0;
@@ -1998,7 +2426,7 @@ function requireEntryObject(id, raw) {
1998
2426
  }
1999
2427
  function requireCommand(id, command) {
2000
2428
  if (typeof command !== "string" || command.trim() === "") {
2001
- logger11.warn(
2429
+ logger13.warn(
2002
2430
  `[engine-discovery] config.json engines.${id}.command is required (non-empty string) \u2014 skipping entry`
2003
2431
  );
2004
2432
  return void 0;
@@ -2007,7 +2435,7 @@ function requireCommand(id, command) {
2007
2435
  }
2008
2436
  function normalizeArgs(id, args) {
2009
2437
  if (args !== void 0 && !isStringArray(args)) {
2010
- logger11.warn(
2438
+ logger13.warn(
2011
2439
  `[engine-discovery] config.json engines.${id}.args must be a string array \u2014 ignoring args`
2012
2440
  );
2013
2441
  }
@@ -2015,7 +2443,7 @@ function normalizeArgs(id, args) {
2015
2443
  }
2016
2444
  function normalizeConfig(id, config) {
2017
2445
  if (config !== void 0 && !isStringRecord(config)) {
2018
- logger11.warn(
2446
+ logger13.warn(
2019
2447
  `[engine-discovery] config.json engines.${id}.config must be a Record<string, string> \u2014 ignoring config`
2020
2448
  );
2021
2449
  }
@@ -2023,7 +2451,7 @@ function normalizeConfig(id, config) {
2023
2451
  }
2024
2452
  function normalizeCwd(id, cwd) {
2025
2453
  if (cwd !== void 0 && typeof cwd !== "string") {
2026
- logger11.warn(
2454
+ logger13.warn(
2027
2455
  `[engine-discovery] config.json engines.${id}.cwd must be a string \u2014 ignoring cwd`
2028
2456
  );
2029
2457
  }
@@ -2043,7 +2471,7 @@ function jsonKindOf(value) {
2043
2471
  }
2044
2472
 
2045
2473
  // src/execution/engine/registry.ts
2046
- var logger12 = getLogger("subagents");
2474
+ var logger14 = getLogger("subagents");
2047
2475
  var DEFAULT_ENGINE_ID = "pi";
2048
2476
  function normalizeEngineId(engine) {
2049
2477
  return engine?.trim() || DEFAULT_ENGINE_ID;
@@ -2083,12 +2511,12 @@ function triggerEngineDispose(engine, source) {
2083
2511
  if (typeof engine.dispose !== "function") return;
2084
2512
  try {
2085
2513
  engine.dispose().then(void 0, (err) => {
2086
- logger12.warn(
2514
+ logger14.warn(
2087
2515
  `[engine-registry] engine '${engine.id}' dispose rejected (${source}, best-effort continue): ${err instanceof Error ? err.message : String(err)}`
2088
2516
  );
2089
2517
  });
2090
2518
  } catch (err) {
2091
- logger12.warn(
2519
+ logger14.warn(
2092
2520
  `[engine-registry] engine '${engine.id}' dispose threw synchronously (${source}, best-effort continue): ${err instanceof Error ? err.message : String(err)}`
2093
2521
  );
2094
2522
  }
@@ -2131,14 +2559,14 @@ function listEnginesByDisplayName() {
2131
2559
  }
2132
2560
 
2133
2561
  // src/execution/engine/engine-discovery-scan.ts
2134
- var logger13 = getLogger("subagents");
2562
+ var logger15 = getLogger("subagents");
2135
2563
  function scanEngines(opts) {
2136
2564
  const env = opts.env ?? process.env;
2137
2565
  const result = { discovered: [], skipped: [], unusable: [] };
2138
2566
  const present = (entry) => {
2139
2567
  const idx = result.discovered.findIndex((d) => d.id === entry.id);
2140
2568
  if (idx >= 0) {
2141
- logger13.debug(
2569
+ logger15.debug(
2142
2570
  `[engine-discovery] engine id '${entry.id}' from '${entry.source}' overrides earlier discovery from '${result.discovered[idx].source}' \u2014 same-id override`
2143
2571
  );
2144
2572
  result.discovered[idx] = entry;
@@ -2150,13 +2578,13 @@ function scanEngines(opts) {
2150
2578
  const inspection = inspectEnginePackage(pkgDir, source, opts.hostKind, env);
2151
2579
  if (inspection.status === "skip") {
2152
2580
  if (!inspection.reason.includes("not an engine package")) {
2153
- logger13.warn(`[engine-discovery] skipping ${pkgDir} (${source}): ${inspection.reason}`);
2581
+ logger15.warn(`[engine-discovery] skipping ${pkgDir} (${source}): ${inspection.reason}`);
2154
2582
  result.skipped.push({ pkgDir, reason: inspection.reason });
2155
2583
  }
2156
2584
  return;
2157
2585
  }
2158
2586
  if (inspection.status === "unusable") {
2159
- logger13.warn(`[engine-discovery] engine unavailable: ${inspection.reason}`);
2587
+ logger15.warn(`[engine-discovery] engine unavailable: ${inspection.reason}`);
2160
2588
  result.unusable.push({ pkgDir, id: inspection.id, reason: inspection.reason });
2161
2589
  return;
2162
2590
  }
@@ -2181,7 +2609,7 @@ function scanEngines(opts) {
2181
2609
  const command = resolveExplicitCommand(entry.command, env);
2182
2610
  if (command === void 0) {
2183
2611
  const reason = `engine '${id}' (config.json engines.${id}): command '${entry.command}' not found or not executable`;
2184
- logger13.warn(`[engine-discovery] ${reason} \u2014 not registering`);
2612
+ logger15.warn(`[engine-discovery] ${reason} \u2014 not registering`);
2185
2613
  result.unusable.push({ pkgDir: `config.json engines.${id}`, id, reason });
2186
2614
  continue;
2187
2615
  }
@@ -2198,7 +2626,7 @@ function discoverAndRegisterEngines(opts) {
2198
2626
  registerEngineDescriptor(entry.id, entry.descriptor);
2199
2627
  loadedDiscoveryIdSet.add(entry.id);
2200
2628
  if (existed) {
2201
- logger13.debug(
2629
+ logger15.debug(
2202
2630
  `[engine-discovery] engine '${entry.id}' descriptor overwritten in registry (source '${entry.source}') \u2014 same-id override`
2203
2631
  );
2204
2632
  }
@@ -2215,20 +2643,20 @@ function ensureEngineDiscovered(id, opts) {
2215
2643
  }
2216
2644
  function resolveExplicitCommand(command, env) {
2217
2645
  if (command.includes("/") || command.includes("\\")) {
2218
- const p = path5.isAbsolute(command) ? command : path5.resolve(command);
2646
+ const p = path6.isAbsolute(command) ? command : path6.resolve(command);
2219
2647
  return canExecute(p) ? p : void 0;
2220
2648
  }
2221
2649
  const pathVar = env["PATH"] ?? env["Path"] ?? "";
2222
- for (const dir of pathVar.split(path5.delimiter)) {
2650
+ for (const dir of pathVar.split(path6.delimiter)) {
2223
2651
  if (dir === "") continue;
2224
- const p = path5.join(dir, command);
2652
+ const p = path6.join(dir, command);
2225
2653
  if (canExecute(p)) return p;
2226
2654
  }
2227
2655
  return void 0;
2228
2656
  }
2229
2657
  function buildExplicitDescriptor(id, command, entry, opts) {
2230
2658
  const env = opts.env ?? process.env;
2231
- logger13.debug(
2659
+ logger15.debug(
2232
2660
  `[engine-discovery] engine '${id}' registered from config.json engines section with conservative capabilities (no manifest)`
2233
2661
  );
2234
2662
  const caps = { ...CONSERVATIVE_CAPABILITIES };
@@ -2290,8 +2718,29 @@ export {
2290
2718
  parseEnvPrefixes,
2291
2719
  parseModelCatalog,
2292
2720
  getEngineDataDir,
2721
+ MAX_TIMER_DELAY_MS,
2722
+ assertSafeTimerDelay,
2723
+ isDialogMethod,
2724
+ isValidDialogTimeout,
2725
+ DialogGlobalQueue,
2726
+ registerActiveDialogQueue,
2727
+ registerSpawnedChildForRecord,
2728
+ killRecordChildWithEscalation,
2729
+ killAllSpawnedChildren,
2730
+ DEFAULT_IDLE_TIMEOUT_MS,
2731
+ disarmIdleTimer,
2732
+ hasIdleTimer,
2733
+ hasLiveProcessHandle,
2734
+ isIdle,
2735
+ isResumable,
2736
+ setInFlightListener,
2737
+ getInFlightSnapshot,
2293
2738
  EngineClient,
2739
+ encodeCwd,
2740
+ getSubagentSessionDir,
2741
+ getSubagentRecordsDir,
2294
2742
  assertTaskShapeSupported,
2743
+ engineConversationUpgradeUnsupportedError,
2295
2744
  RemoteEngine,
2296
2745
  setHostUiRequestEndpoint,
2297
2746
  inspectEnginePackage,