@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
@@ -40,7 +40,7 @@ __export(engine_discovery_scan_exports, {
40
40
  scanEngines: () => scanEngines
41
41
  });
42
42
  module.exports = __toCommonJS(engine_discovery_scan_exports);
43
- var path5 = __toESM(require("path"), 1);
43
+ var path6 = __toESM(require("path"), 1);
44
44
 
45
45
  // src/execution/engine/engine-manifest.ts
46
46
  var path = __toESM(require("path"), 1);
@@ -285,7 +285,7 @@ function describeValue(value) {
285
285
 
286
286
  // src/execution/engine/engine-inspect-package.ts
287
287
  var fs = __toESM(require("fs"), 1);
288
- var path2 = __toESM(require("path"), 1);
288
+ var path3 = __toESM(require("path"), 1);
289
289
  var import_subagent_engine_sdk9 = require("@zhushanwen/subagent-engine-sdk");
290
290
 
291
291
  // src/execution/engine/common/data-dir.ts
@@ -314,7 +314,20 @@ var import_subagent_engine_sdk6 = require("@zhushanwen/subagent-engine-sdk");
314
314
 
315
315
  // src/execution/engine/client/mirror.ts
316
316
  var import_subagent_engine_sdk = require("@zhushanwen/subagent-engine-sdk");
317
- var logger3 = (0, import_subagent_engine_sdk.getLogger)("subagents");
317
+
318
+ // src/execution/dialog-queue.ts
319
+ var logger3 = getLogger("subagents");
320
+ var SECONDS_PER_MINUTE = 60;
321
+ var MS_PER_SECOND = 1e3;
322
+ var DEFAULT_DIALOG_TIMEOUT_MINUTES = 30;
323
+ var DEFAULT_DIALOG_TIMEOUT_MS = DEFAULT_DIALOG_TIMEOUT_MINUTES * SECONDS_PER_MINUTE * MS_PER_SECOND;
324
+ var activeDialogQueue;
325
+ function notifyChildProcessExited(pid) {
326
+ activeDialogQueue?.rejectChildDialogs({ pid });
327
+ }
328
+
329
+ // src/execution/engine/client/mirror.ts
330
+ var logger4 = (0, import_subagent_engine_sdk.getLogger)("subagents");
318
331
  var SpawnedChildrenMirror = class {
319
332
  entries = /* @__PURE__ */ new Map();
320
333
  listeners = /* @__PURE__ */ new Set();
@@ -343,6 +356,7 @@ var SpawnedChildrenMirror = class {
343
356
  };
344
357
  this.entries.set(patch.pid, entry);
345
358
  this.emit({ reason: "childStateChanged", pid: patch.pid, recordId: patch.recordId });
359
+ if (patch.state === "exited") notifyChildProcessExited(patch.pid);
346
360
  }
347
361
  /**
348
362
  * 失效语义 2 + 3:全部镜像项整体置死(killed=true + state=exited)→ 逐项广播 →
@@ -358,6 +372,7 @@ var SpawnedChildrenMirror = class {
358
372
  entry.state = "exited";
359
373
  entry.updatedAt = Date.now();
360
374
  this.emit({ reason: "killedAll", pid: entry.pid, recordId: entry.recordId });
375
+ notifyChildProcessExited(entry.pid);
361
376
  }
362
377
  this.entries.clear();
363
378
  }
@@ -388,18 +403,18 @@ var SpawnedChildrenMirror = class {
388
403
  return this.entries.size;
389
404
  }
390
405
  /** 订阅状态广播(W6 notify/谓词接线点)。返回退订函数。 */
391
- onChange(listener) {
392
- this.listeners.add(listener);
406
+ onChange(listener2) {
407
+ this.listeners.add(listener2);
393
408
  return () => {
394
- this.listeners.delete(listener);
409
+ this.listeners.delete(listener2);
395
410
  };
396
411
  }
397
412
  emit(event) {
398
- for (const listener of this.listeners) {
413
+ for (const listener2 of this.listeners) {
399
414
  try {
400
- listener(event);
415
+ listener2(event);
401
416
  } catch (err) {
402
- logger3.debug(
417
+ logger4.debug(
403
418
  `[spawned-children-mirror] listener threw for ${event.reason}: ${err instanceof Error ? err.message : String(err)}`
404
419
  );
405
420
  }
@@ -412,22 +427,22 @@ var import_node_child_process = require("child_process");
412
427
  var import_node_fs = require("fs");
413
428
  var import_node_path2 = require("path");
414
429
  var import_subagent_engine_sdk2 = require("@zhushanwen/subagent-engine-sdk");
415
- var logger4 = (0, import_subagent_engine_sdk2.getLogger)("subagents");
430
+ var logger5 = (0, import_subagent_engine_sdk2.getLogger)("subagents");
416
431
  var CMDLINE_PROBE_TIMEOUT_MS = 3e3;
417
432
  var PIDFILE_JSON_INDENT = 2;
418
433
  function pidfilePath(engineDataDir, engineId, hostKind, hostPid) {
419
434
  return (0, import_node_path2.join)((0, import_subagent_engine_sdk2.resolveEngineDir)(engineDataDir, engineId), `engine.${hostKind}.${hostPid}.pid`);
420
435
  }
421
- function writePidfileAtomic(path6, content) {
422
- (0, import_node_fs.mkdirSync)((0, import_node_path2.join)(path6, ".."), { recursive: true });
423
- const tmp = `${path6}.${process.pid}.${Date.now()}.tmp`;
436
+ function writePidfileAtomic(path7, content) {
437
+ (0, import_node_fs.mkdirSync)((0, import_node_path2.join)(path7, ".."), { recursive: true });
438
+ const tmp = `${path7}.${process.pid}.${Date.now()}.tmp`;
424
439
  (0, import_node_fs.writeFileSync)(tmp, `${JSON.stringify(content, null, PIDFILE_JSON_INDENT)}
425
440
  `, "utf-8");
426
- (0, import_node_fs.renameSync)(tmp, path6);
441
+ (0, import_node_fs.renameSync)(tmp, path7);
427
442
  }
428
- function readPidfile(path6) {
443
+ function readPidfile(path7) {
429
444
  try {
430
- const raw = (0, import_node_fs.readFileSync)(path6, "utf-8");
445
+ const raw = (0, import_node_fs.readFileSync)(path7, "utf-8");
431
446
  const parsed = JSON.parse(raw);
432
447
  if (typeof parsed.enginePid !== "number" || typeof parsed.hostPid !== "number" || typeof parsed.engineId !== "string" || typeof parsed.hostKind !== "string") {
433
448
  return void 0;
@@ -441,21 +456,21 @@ function readPidfile(path6) {
441
456
  writtenAt: typeof parsed.writtenAt === "string" ? parsed.writtenAt : ""
442
457
  };
443
458
  } catch (err) {
444
- logger4.debug(`[pid-file] readPidfile(${path6}) failed, treating as stale: ${err instanceof Error ? err.message : String(err)}`);
459
+ logger5.debug(`[pid-file] readPidfile(${path7}) failed, treating as stale: ${err instanceof Error ? err.message : String(err)}`);
445
460
  return void 0;
446
461
  }
447
462
  }
448
- function removePidfile(path6) {
463
+ function removePidfile(path7) {
449
464
  try {
450
- (0, import_node_fs.rmSync)(path6, { force: true });
465
+ (0, import_node_fs.rmSync)(path7, { force: true });
451
466
  } catch (err) {
452
- logger4.debug(`[pid-file] removePidfile(${path6}) failed (concurrent sweep = expected): ${err instanceof Error ? err.message : String(err)}`);
467
+ logger5.debug(`[pid-file] removePidfile(${path7}) failed (concurrent sweep = expected): ${err instanceof Error ? err.message : String(err)}`);
453
468
  }
454
469
  }
455
470
  function registerEnginePidfile(reg) {
456
- const path6 = pidfilePath(reg.engineDataDir, reg.engineId, reg.hostKind, reg.hostPid);
471
+ const path7 = pidfilePath(reg.engineDataDir, reg.engineId, reg.hostKind, reg.hostPid);
457
472
  try {
458
- writePidfileAtomic(path6, {
473
+ writePidfileAtomic(path7, {
459
474
  enginePid: reg.enginePid,
460
475
  hostPid: reg.hostPid,
461
476
  engineStartTime: readProcessStartTime(reg.enginePid) ?? null,
@@ -463,9 +478,9 @@ function registerEnginePidfile(reg) {
463
478
  hostKind: reg.hostKind,
464
479
  writtenAt: (/* @__PURE__ */ new Date()).toISOString()
465
480
  });
466
- return path6;
481
+ return path7;
467
482
  } catch (err) {
468
- logger4.warn(
483
+ logger5.warn(
469
484
  `[pid-file] pidfile write failed for ${reg.engineId} (sweep coverage degraded): ${err instanceof Error ? err.message : String(err)}`
470
485
  );
471
486
  return void 0;
@@ -475,7 +490,7 @@ function sweepEnginePidfiles(args) {
475
490
  try {
476
491
  return sweepStalePidfiles(args);
477
492
  } catch (err) {
478
- logger4.warn(
493
+ logger5.warn(
479
494
  `[pid-file] pidfile sweep failed for ${args.engineId} (conservative skip): ${err instanceof Error ? err.message : String(err)}`
480
495
  );
481
496
  return { killed: [], removed: [], skipped: [] };
@@ -533,10 +548,10 @@ function sweepStalePidfiles(opts) {
533
548
  }
534
549
  return result;
535
550
  }
536
- function sweepOnePidfile(path6, file, opts, platform, result) {
537
- const content = readPidfile(path6);
551
+ function sweepOnePidfile(path7, file, opts, platform, result) {
552
+ const content = readPidfile(path7);
538
553
  if (content === void 0) {
539
- removePidfile(path6);
554
+ removePidfile(path7);
540
555
  result.removed.push({ file, reason: "unreadable-or-corrupt" });
541
556
  return;
542
557
  }
@@ -553,11 +568,11 @@ function sweepOnePidfile(path6, file, opts, platform, result) {
553
568
  result.skipped.push({ file, reason: "host-pid-alive" });
554
569
  return;
555
570
  }
556
- reapDeadHostEngine(path6, file, content, opts, platform, result);
571
+ reapDeadHostEngine(path7, file, content, opts, platform, result);
557
572
  }
558
- function reapDeadHostEngine(path6, file, content, opts, platform, result) {
573
+ function reapDeadHostEngine(path7, file, content, opts, platform, result) {
559
574
  if (platform === "win32") {
560
- removePidfile(path6);
575
+ removePidfile(path7);
561
576
  result.removed.push({ file, reason: "stale (host dead); win32 never kills" });
562
577
  return;
563
578
  }
@@ -567,7 +582,7 @@ function reapDeadHostEngine(path6, file, content, opts, platform, result) {
567
582
  return;
568
583
  }
569
584
  if (!engineAlive) {
570
- removePidfile(path6);
585
+ removePidfile(path7);
571
586
  result.removed.push({ file, reason: "engine-pid-dead" });
572
587
  return;
573
588
  }
@@ -577,7 +592,7 @@ function reapDeadHostEngine(path6, file, content, opts, platform, result) {
577
592
  return;
578
593
  }
579
594
  if (!opts.matchesEngineCmdline(cmdline)) {
580
- removePidfile(path6);
595
+ removePidfile(path7);
581
596
  result.removed.push({ file, reason: "cmdline-mismatch (pid reused by unrelated process)" });
582
597
  return;
583
598
  }
@@ -587,13 +602,13 @@ function reapDeadHostEngine(path6, file, content, opts, platform, result) {
587
602
  return;
588
603
  }
589
604
  if (content.engineStartTime === null || content.engineStartTime !== startTime) {
590
- removePidfile(path6);
605
+ removePidfile(path7);
591
606
  result.removed.push({ file, reason: "engine-start-time-mismatch (pid reused)" });
592
607
  return;
593
608
  }
594
609
  const killEngine = opts.killEngine ?? defaultKillEngineProcessGroup;
595
610
  killEngine(content.enginePid);
596
- removePidfile(path6);
611
+ removePidfile(path7);
597
612
  result.killed.push(content.enginePid);
598
613
  result.removed.push({ file, reason: "swept after kill (stale orphan)" });
599
614
  }
@@ -602,18 +617,18 @@ function defaultKillEngineProcessGroup(enginePid) {
602
617
  process.kill(-enginePid, "SIGKILL");
603
618
  return;
604
619
  } catch (groupErr) {
605
- logger4.debug(`[pid-file] process-group kill(-${enginePid}) failed, falling back to single kill: ${groupErr instanceof Error ? groupErr.message : String(groupErr)}`);
620
+ logger5.debug(`[pid-file] process-group kill(-${enginePid}) failed, falling back to single kill: ${groupErr instanceof Error ? groupErr.message : String(groupErr)}`);
606
621
  }
607
622
  try {
608
623
  process.kill(enginePid, "SIGKILL");
609
624
  } catch (err) {
610
- logger4.debug(`[pid-file] single kill(${enginePid}) failed (already dead = expected): ${err instanceof Error ? err.message : String(err)}`);
625
+ logger5.debug(`[pid-file] single kill(${enginePid}) failed (already dead = expected): ${err instanceof Error ? err.message : String(err)}`);
611
626
  }
612
627
  }
613
628
 
614
629
  // src/execution/engine/client/reverse-router.ts
615
630
  var import_subagent_engine_sdk3 = require("@zhushanwen/subagent-engine-sdk");
616
- var logger5 = (0, import_subagent_engine_sdk3.getLogger)("subagents");
631
+ var logger6 = (0, import_subagent_engine_sdk3.getLogger)("subagents");
617
632
  function routeReverseRequest(deps, frame) {
618
633
  if (frame.method === "host/askUser") {
619
634
  handleInteractionRequest(deps, frame, deps.uiRequestHandler, (params) => params.request);
@@ -648,7 +663,7 @@ function handleInteractionRequest(deps, frame, handler, extract) {
648
663
  const result = await handler(input);
649
664
  if (!deps.isDisposed()) deps.sendResponse(frame.id, result);
650
665
  } catch (err) {
651
- logger5.warn(
666
+ logger6.warn(
652
667
  `[engine-client:${deps.engineId}] ${frame.method} handler failed, answering cancelled: ${err instanceof Error ? err.message : String(err)}`
653
668
  );
654
669
  if (!deps.isDisposed()) deps.sendResponse(frame.id, { cancelled: true });
@@ -659,7 +674,7 @@ function handleDataPlaneRequest(deps, frame) {
659
674
  let settled = false;
660
675
  const guardTimer = setTimeout(() => {
661
676
  if (settled) return;
662
- logger5.error(
677
+ logger6.error(
663
678
  `[engine-client:${deps.engineId}] data-plane reverse request ${frame.method} unanswered for ${import_subagent_engine_sdk3.REVERSE_REQUEST_TIMEOUT_MS}ms \u2014 treating as engine fault`
664
679
  );
665
680
  void deps.failEngine(
@@ -675,7 +690,7 @@ function handleDataPlaneRequest(deps, frame) {
675
690
  (err) => {
676
691
  settled = true;
677
692
  clearTimeout(guardTimer);
678
- logger5.warn(
693
+ logger6.warn(
679
694
  `[engine-client:${deps.engineId}] ${frame.method} dispatch failed (answering ok, host-side bug): ${err instanceof Error ? err.message : String(err)}`
680
695
  );
681
696
  deps.sendResponse(frame.id, { ok: true });
@@ -685,7 +700,6 @@ function handleDataPlaneRequest(deps, frame) {
685
700
  var DATA_PLANE_HANDLERS = {
686
701
  "host/log": (deps, params) => dispatchLog(deps, params),
687
702
  "host/streamDelta": (deps, params) => dispatchStreamDelta(deps, params),
688
- "host/roundLifecycle": (deps, params) => dispatchRoundLifecycle(deps, params),
689
703
  "host/poolResolved": (deps, params) => dispatchPoolResolved(deps, params),
690
704
  "host/handleReady": (deps, params) => dispatchHandleReady(deps, params),
691
705
  "host/childSpawned": (deps, params) => dispatchChildSpawned(deps, params),
@@ -696,22 +710,12 @@ async function dispatchDataPlane(deps, method, params) {
696
710
  }
697
711
  function dispatchLog(deps, p) {
698
712
  if (deps.log !== void 0) deps.log(p);
699
- else logger5[p.level](`[engine:${p.component}] ${p.message}`, p.data);
713
+ else logger6[p.level](`[engine:${p.component}] ${p.message}`, p.data);
700
714
  }
701
715
  async function dispatchStreamDelta(deps, p) {
702
- if (p.recordId !== void 0) {
703
- await deps.recordRoutes.get(p.recordId)?.onStreamDelta?.(p.delta);
704
- return;
705
- }
716
+ if (p.runId === void 0) return;
706
717
  await deps.runRoutes.get(p.runId)?.onStreamDelta?.(p.delta);
707
718
  }
708
- async function dispatchRoundLifecycle(deps, p) {
709
- if (p.recordId !== void 0) {
710
- await deps.recordRoutes.get(p.recordId)?.onRoundLifecycle?.(p);
711
- return;
712
- }
713
- await deps.runRoutes.get(p.runId)?.onRoundLifecycle?.(p);
714
- }
715
719
  async function dispatchPoolResolved(deps, p) {
716
720
  await deps.runRoutes.get(p.runId)?.onPoolResolved?.(p.poolKey);
717
721
  }
@@ -727,11 +731,11 @@ function dispatchChildSpawned(deps, p) {
727
731
  if (process.platform !== "win32" && typeof p.pid === "number") {
728
732
  try {
729
733
  process.kill(-p.pid, 0);
730
- logger5.warn(
734
+ logger6.warn(
731
735
  `[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)`
732
736
  );
733
737
  } catch (err) {
734
- logger5.debug(
738
+ logger6.debug(
735
739
  `[engine-client:${deps.engineId}] childSpawned pid ${p.pid} group probe settled (${err instanceof Error ? err.message : String(err)})`
736
740
  );
737
741
  }
@@ -751,7 +755,7 @@ function dispatchChildStateChanged(deps, p) {
751
755
  // src/execution/engine/client/client-options.ts
752
756
  var import_node_path3 = require("path");
753
757
  var import_subagent_engine_sdk4 = require("@zhushanwen/subagent-engine-sdk");
754
- var logger6 = (0, import_subagent_engine_sdk4.getLogger)("subagents");
758
+ var logger7 = (0, import_subagent_engine_sdk4.getLogger)("subagents");
755
759
  function defaultEngineCmdlineMatcher(command) {
756
760
  const base = (0, import_node_path3.basename)(command);
757
761
  return (cmdline) => {
@@ -764,7 +768,7 @@ function warnOnManifestDiagnostics(engineId, diag, result) {
764
768
  const answered = JSON.stringify(result.capabilities ?? null);
765
769
  const declared = JSON.stringify(diag.capabilities);
766
770
  if (answered !== declared) {
767
- logger6.warn(
771
+ logger7.warn(
768
772
  `[engine-client:${engineId}] initialize capabilities differ from manifest (diagnostic only): manifest=${declared} answered=${answered}`
769
773
  );
770
774
  }
@@ -773,7 +777,7 @@ function warnOnManifestDiagnostics(engineId, diag, result) {
773
777
  const answered = JSON.stringify(result.models ?? null);
774
778
  const declared = JSON.stringify(diag.models ?? null);
775
779
  if (answered !== declared) {
776
- logger6.warn(
780
+ logger7.warn(
777
781
  `[engine-client:${engineId}] initialize models differ from manifest (diagnostic only): manifest=${declared} answered=${answered}`
778
782
  );
779
783
  }
@@ -783,13 +787,13 @@ function warnOnManifestDiagnostics(engineId, diag, result) {
783
787
  // src/execution/engine/client/reaper.ts
784
788
  var import_node_child_process2 = require("child_process");
785
789
  var import_subagent_engine_sdk5 = require("@zhushanwen/subagent-engine-sdk");
786
- var logger7 = (0, import_subagent_engine_sdk5.getLogger)("subagents");
790
+ var logger8 = (0, import_subagent_engine_sdk5.getLogger)("subagents");
787
791
  var KILL_GRACE_MS = 5e3;
788
792
  function killProcessTree(pid, reason, opts) {
789
793
  if (process.platform === "win32") {
790
794
  const r = (0, import_node_child_process2.spawnSync)("taskkill", ["/PID", String(pid), "/T", "/F"], { timeout: 1e4 });
791
795
  if (r.error || r.status !== 0) {
792
- logger7.warn(
796
+ logger8.warn(
793
797
  `[engine-client:${opts.engineId}] taskkill failed for pid ${pid} (${reason}): ${r.error?.message ?? `exit ${r.status}`}`
794
798
  );
795
799
  }
@@ -800,7 +804,7 @@ function killProcessTree(pid, reason, opts) {
800
804
  process.kill(-pid, signal);
801
805
  return true;
802
806
  } catch (err) {
803
- logger7.debug(
807
+ logger8.debug(
804
808
  `[engine-client:${opts.engineId}] process.kill(-${pid}, ${signal}) failed: ${err instanceof Error ? err.message : String(err)}`
805
809
  );
806
810
  return false;
@@ -810,7 +814,7 @@ function killProcessTree(pid, reason, opts) {
810
814
  try {
811
815
  process.kill(pid, "SIGTERM");
812
816
  } catch (err) {
813
- logger7.debug(
817
+ logger8.debug(
814
818
  `[engine-client:${opts.engineId}] single SIGTERM to ${pid} failed (already dead = expected): ${err instanceof Error ? err.message : String(err)}`
815
819
  );
816
820
  return;
@@ -822,7 +826,7 @@ function killProcessTree(pid, reason, opts) {
822
826
  try {
823
827
  process.kill(pid, "SIGKILL");
824
828
  } catch (err) {
825
- logger7.debug(
829
+ logger8.debug(
826
830
  `[engine-client:${opts.engineId}] SIGKILL to ${pid} failed (already dead = expected): ${err instanceof Error ? err.message : String(err)}`
827
831
  );
828
832
  }
@@ -846,8 +850,115 @@ function waitForChildExit(child, timeoutMs) {
846
850
  });
847
851
  }
848
852
 
853
+ // src/execution/engine/host/spawned-children.ts
854
+ var CoreSpawnedChildrenMirror = class {
855
+ entries = /* @__PURE__ */ new Map();
856
+ /** 落项 / 覆盖(同 recordId 重 spawn = 覆盖旧句柄,与引擎侧 Map 同语义)。 */
857
+ register(recordId, child) {
858
+ this.entries.set(recordId, {
859
+ pid: child.pid,
860
+ killed: child.killed,
861
+ updatedAt: Date.now()
862
+ });
863
+ }
864
+ /** 单项置死(杀链入口:killRecordChildWithEscalation 途经)。 */
865
+ markKilled(recordId) {
866
+ const entry = this.entries.get(recordId);
867
+ if (entry !== void 0) {
868
+ entry.killed = true;
869
+ entry.updatedAt = Date.now();
870
+ }
871
+ }
872
+ /** 整体置死 + 清空(失效语义 2+3:引擎 exit / 重建 / dispose / killAll)。 */
873
+ killAll() {
874
+ this.entries.clear();
875
+ }
876
+ getChildByRecord(recordId) {
877
+ return this.entries.get(recordId);
878
+ }
879
+ /** 句柄存活谓词(镜像面;判据与引擎侧 `!child.killed` 同构)。 */
880
+ hasLiveProcessHandle(recordId) {
881
+ const entry = this.entries.get(recordId);
882
+ return entry !== void 0 && !entry.killed;
883
+ }
884
+ /** 快照(诊断/测试)。 */
885
+ snapshot() {
886
+ return [...this.entries.entries()].map(([recordId, entry]) => ({ recordId, ...entry }));
887
+ }
888
+ /** 测试隔离专用(生产禁用——进程级全局状态)。 */
889
+ clear() {
890
+ this.entries.clear();
891
+ }
892
+ };
893
+ var MIRROR_SLOT_KEY = /* @__PURE__ */ Symbol.for(
894
+ "@zhushanwen/pi-subagent-workflow.coreSpawnedChildrenMirror"
895
+ );
896
+ function coreMirrorSlot() {
897
+ let slot = Reflect.get(globalThis, MIRROR_SLOT_KEY);
898
+ if (!slot) {
899
+ slot = new CoreSpawnedChildrenMirror();
900
+ Reflect.set(globalThis, MIRROR_SLOT_KEY, slot);
901
+ }
902
+ return slot;
903
+ }
904
+ function coreSpawnedChildrenMirror() {
905
+ return coreMirrorSlot();
906
+ }
907
+ function hasLiveProcessHandleCore(recordId) {
908
+ return coreMirrorSlot().hasLiveProcessHandle(recordId);
909
+ }
910
+
911
+ // src/execution/lifecycle-manager.ts
912
+ var logger9 = getLogger("subagents");
913
+ var MS_PER_SECOND2 = 1e3;
914
+ var SECONDS_PER_MINUTE2 = 60;
915
+ var IDLE_TIMEOUT_MINUTES = 5;
916
+ var DEFAULT_IDLE_TIMEOUT_MS = IDLE_TIMEOUT_MINUTES * SECONDS_PER_MINUTE2 * MS_PER_SECOND2;
917
+ var idleTimers = /* @__PURE__ */ new Map();
918
+ function hasIdleTimer(recordId) {
919
+ return idleTimers.has(recordId);
920
+ }
921
+
922
+ // src/execution/lifecycle-predicates.ts
923
+ function hasLiveProcessHandle(recordId) {
924
+ return hasLiveProcessHandleCore(recordId);
925
+ }
926
+
927
+ // src/execution/engine/inflight-snapshot.ts
928
+ var listener = null;
929
+ function getInFlightSnapshot() {
930
+ return { inFlight: countInFlight() };
931
+ }
932
+ function notifyInFlightChanged() {
933
+ if (listener === null) return;
934
+ try {
935
+ listener(getInFlightSnapshot());
936
+ } catch {
937
+ }
938
+ }
939
+ function countInFlight() {
940
+ let count = 0;
941
+ for (const entry of coreSpawnedChildrenMirror().snapshot()) {
942
+ const recordId = entry.recordId;
943
+ if (hasLiveProcessHandle(recordId) && !hasIdleTimer(recordId)) count++;
944
+ }
945
+ return count;
946
+ }
947
+
849
948
  // src/execution/engine/client/engine-client.ts
850
- var logger8 = (0, import_subagent_engine_sdk6.getLogger)("subagents");
949
+ var logger10 = (0, import_subagent_engine_sdk6.getLogger)("subagents");
950
+ function bridgeMirrorEventToCoreMirror(event, mirror) {
951
+ if (event.recordId === void 0 || event.pid === void 0) return;
952
+ const entry = mirror.getEntry(event.pid);
953
+ if (entry === void 0) return;
954
+ const core = coreSpawnedChildrenMirror();
955
+ if (entry.killed) {
956
+ core.markKilled(event.recordId);
957
+ } else {
958
+ core.register(event.recordId, { pid: entry.pid, killed: false });
959
+ }
960
+ notifyInFlightChanged();
961
+ }
851
962
  var DISPOSE_GRACE_MS = 3e3;
852
963
  var SIGKILL_REAP_TIMEOUT_MS = 1e4;
853
964
  var FRAME_ECHO_MAX_CHARS = 200;
@@ -877,9 +988,6 @@ var EngineClient = class {
877
988
  nextRequestId = 1;
878
989
  pending = /* @__PURE__ */ new Map();
879
990
  runRoutes = /* @__PURE__ */ new Map();
880
- /** [W3 v1.x] chat 轮次路由(recordId 键,D1-A——续聊轮无 runId);注册经
881
- * RemoteEngine.registerChatRoundRoute,record 终态注销。 */
882
- recordRoutes = /* @__PURE__ */ new Map();
883
991
  stderrTail = "";
884
992
  unavailableReason;
885
993
  connectInFlight;
@@ -899,7 +1007,6 @@ var EngineClient = class {
899
1007
  permissionHandler: opts.permissionHandler,
900
1008
  log: opts.log,
901
1009
  runRoutes: this.runRoutes,
902
- recordRoutes: this.recordRoutes,
903
1010
  mirror: this.mirror,
904
1011
  setPartialHandle: (partial) => {
905
1012
  this.lastPartialHandle = partial;
@@ -910,6 +1017,7 @@ var EngineClient = class {
910
1017
  };
911
1018
  this.mirror.onChange((event) => {
912
1019
  opts.onMirrorChanged?.(event);
1020
+ bridgeMirrorEventToCoreMirror(event, this.mirror);
913
1021
  });
914
1022
  }
915
1023
  get currentState() {
@@ -972,19 +1080,19 @@ var EngineClient = class {
972
1080
  matchesEngineCmdline: this.opts.engineCmdlineMatcher ?? defaultEngineCmdlineMatcher(this.opts.command)
973
1081
  });
974
1082
  if (sweep.killed.length > 0) {
975
- logger8.warn(
1083
+ logger10.warn(
976
1084
  `[engine-client:${this.engineId}] startup sweep killed stale orphan engine pids: ${sweep.killed.join(",")}`
977
1085
  );
978
1086
  }
979
1087
  for (const removed of sweep.removed) {
980
- logger8.debug(`[engine-client:${this.engineId}] swept stale pidfile ${removed.file}: ${removed.reason}`);
1088
+ logger10.debug(`[engine-client:${this.engineId}] swept stale pidfile ${removed.file}: ${removed.reason}`);
981
1089
  }
982
1090
  }
983
1091
  let attempt = 0;
984
1092
  while (attempt <= import_subagent_engine_sdk6.CRASH_REBUILD_MAX_ATTEMPTS) {
985
1093
  if (attempt > 0) {
986
1094
  const backoff = import_subagent_engine_sdk6.CRASH_REBUILD_BACKOFF_MS[attempt - 1];
987
- logger8.warn(
1095
+ logger10.warn(
988
1096
  `[engine-client:${this.engineId}] rebuild attempt ${attempt}/${import_subagent_engine_sdk6.CRASH_REBUILD_MAX_ATTEMPTS} after ${backoff}ms backoff`
989
1097
  );
990
1098
  await delay(backoff);
@@ -1017,7 +1125,7 @@ var EngineClient = class {
1017
1125
  markUnavailable(reason) {
1018
1126
  this.state = "unavailable";
1019
1127
  this.unavailableReason = reason;
1020
- logger8.error(`[engine-client:${this.engineId}] marked unavailable: ${reason.message}`);
1128
+ logger10.error(`[engine-client:${this.engineId}] marked unavailable: ${reason.message}`);
1021
1129
  }
1022
1130
  /** spawn 引擎 CLI + initialize 握手 + 版本协商 + 诊断留痕。 */
1023
1131
  async spawnAndInitialize() {
@@ -1047,13 +1155,13 @@ var EngineClient = class {
1047
1155
  });
1048
1156
  const child = this.child;
1049
1157
  child.on("error", (err) => {
1050
- logger8.warn(`[engine-client:${this.engineId}] spawn error: ${err.message}`);
1158
+ logger10.warn(`[engine-client:${this.engineId}] spawn error: ${err.message}`);
1051
1159
  this.appendStderrTail(`spawn error: ${err.message}
1052
1160
  `);
1053
1161
  });
1054
1162
  for (const pipe of ["stdin", "stdout", "stderr"]) {
1055
1163
  child[pipe]?.on("error", (err) => {
1056
- logger8.debug(`[engine-client:${this.engineId}] ${pipe} pipe error (engine died concurrently = expected): ${err.message}`);
1164
+ logger10.debug(`[engine-client:${this.engineId}] ${pipe} pipe error (engine died concurrently = expected): ${err.message}`);
1057
1165
  });
1058
1166
  }
1059
1167
  child.stdout?.setEncoding("utf-8");
@@ -1122,7 +1230,7 @@ var EngineClient = class {
1122
1230
  try {
1123
1231
  frame = JSON.parse(line);
1124
1232
  } catch {
1125
- logger8.warn(
1233
+ logger10.warn(
1126
1234
  `[engine-client:${this.engineId}] dropped non-NDJSON stdout line (protocol contract: stdout is NDJSON-only): ${truncate(line, FRAME_ECHO_MAX_CHARS)}`
1127
1235
  );
1128
1236
  return;
@@ -1139,7 +1247,7 @@ var EngineClient = class {
1139
1247
  this.onReverseRequest(frame);
1140
1248
  return;
1141
1249
  }
1142
- logger8.warn(
1250
+ logger10.warn(
1143
1251
  `[engine-client:${this.engineId}] dropped unrecognized frame: ${truncate(line, FRAME_ECHO_MAX_CHARS)}`
1144
1252
  );
1145
1253
  }
@@ -1220,14 +1328,6 @@ var EngineClient = class {
1220
1328
  if (this.runRoutes.get(runId) === route) this.runRoutes.delete(runId);
1221
1329
  };
1222
1330
  }
1223
- /** [W3 v1.x] 注册 chat 轮次路由(recordId 键);分发见 reverse-router.ts
1224
- * (与 runRoutes 同构,键空间互斥:runId=run 帧分配 / recordId=宿主铸造)。 */
1225
- registerRecordRoute(recordId, route) {
1226
- this.recordRoutes.set(recordId, route);
1227
- return () => {
1228
- if (this.recordRoutes.get(recordId) === route) this.recordRoutes.delete(recordId);
1229
- };
1230
- }
1231
1331
  /** 健康检查(ADR-0047:静默 ≠ 卡死,不据此杀任务)。 */
1232
1332
  async ping() {
1233
1333
  await this.ensureConnected();
@@ -1263,7 +1363,7 @@ var EngineClient = class {
1263
1363
  try {
1264
1364
  await this.request("dispose", {}, { timeoutMs: DISPOSE_GRACE_MS });
1265
1365
  } catch (err) {
1266
- logger8.debug(
1366
+ logger10.debug(
1267
1367
  `[engine-client:${this.engineId}] dispose frame failed, falling back to kill chain: ${err instanceof Error ? err.message : String(err)}`
1268
1368
  );
1269
1369
  }
@@ -1275,18 +1375,20 @@ var EngineClient = class {
1275
1375
  onEngineExit(code, signal) {
1276
1376
  const detail = signal !== null ? `signal ${signal}` : `exit code ${code}`;
1277
1377
  if (this.intentionalKill) {
1278
- logger8.debug(`[engine-client:${this.engineId}] engine exited after intentional kill (${detail}) \u2014 expected`);
1378
+ logger10.debug(`[engine-client:${this.engineId}] engine exited after intentional kill (${detail}) \u2014 expected`);
1279
1379
  } else {
1280
- logger8.warn(`[engine-client:${this.engineId}] engine process exited unexpectedly (${detail})`);
1380
+ logger10.warn(`[engine-client:${this.engineId}] engine process exited unexpectedly (${detail})`);
1281
1381
  }
1282
1382
  this.teardownProcess(detail);
1283
1383
  this.state = "exited";
1284
1384
  }
1285
1385
  /**
1286
- * 进程死亡 / 失败后的统一清理:镜像整体置死(失效语义 2+3:killed=true 广播后
1287
- * 清空)→ pidfile 删 → 在途请求 engine_crashed(附 stderr 尾)→ run 路由清空。
1386
+ * 进程死亡 / 失败后的统一清理(收割/组杀分支各成子函数,此处只做顺序编排):
1387
+ * 孤儿收割(镜像置死前)→ 镜像置死(失效语义 2+3)→ pidfile 删 → 在途请求
1388
+ * engine_crashed(附 stderr 尾)→ run 路由清空 → 活引擎组杀兜底。
1288
1389
  */
1289
1390
  teardownProcess(detail) {
1391
+ this.reapOrphansAfterUnexpectedDeath(detail);
1290
1392
  this.mirror.killAll();
1291
1393
  if (this.pidfileWritten !== void 0) {
1292
1394
  removePidfile(this.pidfileWritten);
@@ -1299,8 +1401,46 @@ var EngineClient = class {
1299
1401
  }
1300
1402
  this.pending.clear();
1301
1403
  this.runRoutes.clear();
1302
- this.recordRoutes.clear();
1303
1404
  this.lastPartialHandle = void 0;
1405
+ this.killLeakedAliveChild(detail);
1406
+ if (this.state !== "unavailable" && this.state !== "disposed") {
1407
+ this.state = "exited";
1408
+ }
1409
+ }
1410
+ /**
1411
+ * [H1 U2 / 红线①收割链] 非主动死亡且引擎已死的路径,在镜像置死**之前**补发收割
1412
+ * 信号:孤儿真因 = 引擎意外死时宿主未发收割信号(原先 alreadyDead 分支直接跳过
1413
+ * killProcessTree),任务子进程留在无主进程组继续写 session 文件。插入点约束:
1414
+ * - 位置 = mirror.killAll() 置死清空之前——Windows 通道依赖镜像快照过滤活孤儿
1415
+ * (置死后全量项 killed=true 不可过滤);
1416
+ * - 条件 = !intentionalKill && alreadyDead——主动 dispose 链已有两层杀(引擎
1417
+ * killAllActiveChildren SIGTERM+30s + 宿主 killAll 组杀 5s 升级),跳过即不叠加、
1418
+ * 优雅窗零压缩、pi trap-flush 零截断;进程仍活的路径(握手失败重建)由
1419
+ * killLeakedAliveChild 组杀兜底承接。
1420
+ */
1421
+ reapOrphansAfterUnexpectedDeath(detail) {
1422
+ if (!this.intentionalKill && this.child !== void 0) {
1423
+ const dying = this.child;
1424
+ const alreadyDead = dying.exitCode !== null || dying.signalCode !== null;
1425
+ if (alreadyDead && dying.pid !== void 0) {
1426
+ if (process.platform === "win32") {
1427
+ this.reapOrphansViaMirrorSnapshot(detail);
1428
+ } else {
1429
+ killProcessTree(
1430
+ dying.pid,
1431
+ `reap orphaned children after unexpected engine death (${detail})`,
1432
+ { engineId: this.engineId }
1433
+ );
1434
+ }
1435
+ }
1436
+ }
1437
+ }
1438
+ /**
1439
+ * 握手失败路径(版本越界/握手超时/启动僵死)引擎进程仍活着:必须组杀,否则
1440
+ * crash 重建循环每次 spawn 泄漏一个常驻孤儿(测试机实测积累 370+)。主动杀
1441
+ * (intentionalKill=true)与进程已死(exitCode/signalCode 非空)跳过重复发信号。
1442
+ */
1443
+ killLeakedAliveChild(detail) {
1304
1444
  if (this.child !== void 0) {
1305
1445
  const child = this.child;
1306
1446
  this.child = void 0;
@@ -1310,8 +1450,30 @@ var EngineClient = class {
1310
1450
  }
1311
1451
  child.removeAllListeners();
1312
1452
  }
1313
- if (this.state !== "unavailable" && this.state !== "disposed") {
1314
- this.state = "exited";
1453
+ }
1454
+ /**
1455
+ * [H1 U2 / 红线①Windows 通道] 镜像快照收割活孤儿:镜像快照同步抓清单(置死前,
1456
+ * 全量项含引擎一生历史死 pid——镜像对已退出子进程只改 state 不删项、唯一清空点 =
1457
+ * killAll)→ 过滤活孤儿(state === "running" && !killed,活孤儿 ≤ 并发数)→ 逐 pid
1458
+ * **异步 spawn** taskkill /T /F fire-and-forget(树根 = 活着的任务子进程自身,树杀
1459
+ * 有效;快照同步抓、杀动作异步发——与 POSIX fire-and-forget 对称;禁 spawnSync:
1460
+ * 逐个同步 N×10s 上界会冻结 onEngineExit 同步回调、推迟 pending reject(Continuation
1461
+ * 失败通知链源头)与宿主事件循环)。
1462
+ */
1463
+ reapOrphansViaMirrorSnapshot(detail) {
1464
+ const liveOrphans = this.mirror.snapshot().filter((e) => e.state === "running" && !e.killed);
1465
+ if (liveOrphans.length === 0) return;
1466
+ logger10.warn(
1467
+ `[engine-client:${this.engineId}] reaping ${liveOrphans.length} orphaned task child(ren) via mirror snapshot after unexpected engine death (${detail})`
1468
+ );
1469
+ for (const entry of liveOrphans) {
1470
+ const killer = (0, import_node_child_process3.spawn)("taskkill", ["/PID", String(entry.pid), "/T", "/F"], { stdio: "ignore" });
1471
+ killer.unref();
1472
+ killer.on("error", (err) => {
1473
+ logger10.debug(
1474
+ `[engine-client:${this.engineId}] taskkill spawn failed for orphan pid ${entry.pid} (${err.message})`
1475
+ );
1476
+ });
1315
1477
  }
1316
1478
  }
1317
1479
  // ── pidfile ──────────────────────────────────────────────────────────────
@@ -1326,7 +1488,7 @@ var EngineClient = class {
1326
1488
  `);
1327
1489
  return true;
1328
1490
  } catch (err) {
1329
- logger8.debug(
1491
+ logger10.debug(
1330
1492
  `[engine-client:${this.engineId}] stdin write failed: ${err instanceof Error ? err.message : String(err)}`
1331
1493
  );
1332
1494
  return false;
@@ -1349,8 +1511,19 @@ function truncate(text, max) {
1349
1511
  }
1350
1512
 
1351
1513
  // src/execution/engine/client/remote-engine.ts
1514
+ var import_node_os2 = require("os");
1515
+ var import_node_path4 = require("path");
1352
1516
  var import_subagent_engine_sdk8 = require("@zhushanwen/subagent-engine-sdk");
1353
1517
 
1518
+ // src/execution/path-encoding.ts
1519
+ var path2 = __toESM(require("path"), 1);
1520
+ function encodeCwd(cwd) {
1521
+ return "--" + cwd.replace(/^[/\\]/, "").replace(/[/\\:]/g, "-") + "--";
1522
+ }
1523
+ function getSubagentSessionDir(agentDir, mainCwd) {
1524
+ return path2.join(agentDir, "subagents", encodeCwd(mainCwd), "sessions");
1525
+ }
1526
+
1354
1527
  // src/execution/engine/common/capability-gate.ts
1355
1528
  var import_subagent_engine_sdk7 = require("@zhushanwen/subagent-engine-sdk");
1356
1529
 
@@ -1365,7 +1538,7 @@ var EngineError = class extends Error {
1365
1538
  this.code = code;
1366
1539
  this.recovery = recovery;
1367
1540
  }
1368
- /** 结构化投影(InteractResult.code/message 与 GUI 警告条共用形态)。 */
1541
+ /** 结构化投影(code/message 与 GUI 警告条共用形态)。 */
1369
1542
  toStructured() {
1370
1543
  return { code: this.code, message: this.message, recovery: this.recovery };
1371
1544
  }
@@ -1514,22 +1687,6 @@ var RemoteEngine = class {
1514
1687
  unregister();
1515
1688
  }
1516
1689
  }
1517
- async interact(handle, action) {
1518
- await this.opts.client.ensureConnected();
1519
- const result = await this.opts.client.request("interact", {
1520
- handle: handle.data,
1521
- action
1522
- });
1523
- return result;
1524
- }
1525
- /**
1526
- * [W3 v1.x] chat 轮次反向通道路由注册(recordId 键,EnginePort 可选面实现):
1527
- * interact 续聊轮的 streamDelta / roundLifecycle 分发目标。薄委托 EngineClient
1528
- * 的 recordRoutes(键分发见 reverse-router.ts)。
1529
- */
1530
- registerChatRoundRoute(recordId, route) {
1531
- return this.opts.client.registerRecordRoute(recordId, route);
1532
- }
1533
1690
  /** 协议 read(dataDir 必填——引擎数据根,构造注入)。 */
1534
1691
  async read(handle) {
1535
1692
  await this.opts.client.ensureConnected();
@@ -1561,6 +1718,15 @@ function isTransientRunFailure(err) {
1561
1718
  if (!(err instanceof import_subagent_engine_sdk8.EngineSdkError)) return false;
1562
1719
  return err.code === "engine_crashed" || err.code === "engine_request_timeout" || err.code === "engine_handshake_timeout";
1563
1720
  }
1721
+ var PI_AGENT_DIR_ENV = "PI_CODING_AGENT_DIR";
1722
+ var PI_ROOT_CWD_ENV = "PI_SUBAGENT_ROOT_CWD";
1723
+ function deriveHostSubagentSessionDir() {
1724
+ const agentDir = process.env[PI_AGENT_DIR_ENV];
1725
+ const resolvedAgentDir = agentDir !== void 0 && agentDir !== "" ? agentDir : (0, import_node_path4.join)((0, import_node_os2.homedir)(), ".pi", "agent");
1726
+ const rootCwd = process.env[PI_ROOT_CWD_ENV];
1727
+ const resolvedRootCwd = rootCwd !== void 0 && rootCwd !== "" ? rootCwd : process.cwd();
1728
+ return getSubagentSessionDir(resolvedAgentDir, resolvedRootCwd);
1729
+ }
1564
1730
  function buildRunParams(task, ctx, runId) {
1565
1731
  const ctxModelRef = ctx.ctxModel ? `${ctx.ctxModel.provider}/${ctx.ctxModel.id}` : void 0;
1566
1732
  return {
@@ -1573,9 +1739,16 @@ function buildRunParams(task, ctx, runId) {
1573
1739
  schemaEnv: ctx.schemaEnv ?? task.schemaEnv,
1574
1740
  ctxModel: ctxModelRef,
1575
1741
  engineFallback: ctx.engineFallback,
1576
- streamMode: ctx.stream !== void 0 ? "stream" : void 0
1742
+ streamMode: ctx.stream !== void 0 ? "stream" : void 0,
1743
+ // [F6] 根 session id(relay 归属键 SESSION_ID 权威源)——undefined 不上 wire
1744
+ //(additive 语义,与顶层 chat 参数同写法)。
1745
+ ...ctx.sessionRootId !== void 0 ? { sessionRootId: ctx.sessionRootId } : {},
1746
+ // [Option C 协议化] 权威 subagent session 目录(Gate B S6):宿主注入值优先,
1747
+ // 缺省同源 env 推导(deriveHostSubagentSessionDir)——恒有值恒上 wire,引擎
1748
+ // 据此组装 --session-dir 不自推导(引擎本地推导降级 [LEGACY] fallback)。
1749
+ sessionDir: ctx.sessionDir ?? deriveHostSubagentSessionDir()
1577
1750
  },
1578
- ...ctx.chat !== void 0 ? { chat: ctx.chat } : {}
1751
+ ...ctx.resume !== void 0 ? { resume: ctx.resume } : {}
1579
1752
  };
1580
1753
  }
1581
1754
  function buildRunRouteHandlers(ctx) {
@@ -1583,9 +1756,7 @@ function buildRunRouteHandlers(ctx) {
1583
1756
  onEvent: (event) => ctx.onEvent?.(event),
1584
1757
  onStreamDelta: (delta) => ctx.stream?.onDelta(delta),
1585
1758
  onPoolResolved: (poolKey) => ctx.onPoolResolved?.(poolKey),
1586
- onHandleReady: (partial) => ctx.onHandleReady?.(partial),
1587
- // [W3 v1.x] 首轮(run 会话形态)轮次生命周期帧(runId 键)→ 宿主消费口。
1588
- onRoundLifecycle: (phase) => ctx.onRoundLifecycle?.(phase)
1759
+ onHandleReady: (partial) => ctx.onHandleReady?.(partial)
1589
1760
  };
1590
1761
  }
1591
1762
  function wireAbortSignal(client, runId, ctx) {
@@ -1675,11 +1846,11 @@ function getHostUiRequestEndpoint() {
1675
1846
  }
1676
1847
 
1677
1848
  // src/execution/engine/engine-inspect-package.ts
1678
- var logger9 = getLogger("subagents");
1849
+ var logger11 = getLogger("subagents");
1679
1850
  function readEnginePackageJson(pkgDir) {
1680
1851
  let raw;
1681
1852
  try {
1682
- raw = fs.readFileSync(path2.join(pkgDir, "package.json"), "utf8");
1853
+ raw = fs.readFileSync(path3.join(pkgDir, "package.json"), "utf8");
1683
1854
  } catch (err) {
1684
1855
  return { status: "skip", reason: `package.json unreadable: ${errorMessage(err)}` };
1685
1856
  }
@@ -1754,12 +1925,12 @@ function parseOptionalDisplayFields(m, id) {
1754
1925
  if (typeof rawDisplayName === "string" && rawDisplayName.trim() !== "") {
1755
1926
  displayName = rawDisplayName;
1756
1927
  } else {
1757
- logger9.warn(`[engine-discovery] engine '${id}': displayName must be a non-empty string \u2014 ignoring`);
1928
+ logger11.warn(`[engine-discovery] engine '${id}': displayName must be a non-empty string \u2014 ignoring`);
1758
1929
  }
1759
1930
  }
1760
1931
  const rawDescription = m["description"];
1761
1932
  if (rawDescription !== void 0 && typeof rawDescription !== "string") {
1762
- logger9.warn(`[engine-discovery] engine '${id}': description must be a string \u2014 ignoring`);
1933
+ logger11.warn(`[engine-discovery] engine '${id}': description must be a string \u2014 ignoring`);
1763
1934
  }
1764
1935
  return displayName;
1765
1936
  }
@@ -1847,19 +2018,19 @@ function errorMessage(err) {
1847
2018
 
1848
2019
  // src/execution/engine/engine-discovery-roots.ts
1849
2020
  var fs2 = __toESM(require("fs"), 1);
1850
- var path3 = __toESM(require("path"), 1);
1851
- var logger10 = getLogger("subagents");
2021
+ var path4 = __toESM(require("path"), 1);
2022
+ var logger12 = getLogger("subagents");
1852
2023
  var ENGINE_ROOTS_ENV = "XYZ_AGENT_ENGINE_ROOTS";
1853
2024
  function parseEngineRootsEnv(env) {
1854
2025
  const raw = env[ENGINE_ROOTS_ENV];
1855
2026
  if (raw === void 0 || raw.trim() === "") return [];
1856
2027
  const out = [];
1857
2028
  const seen = /* @__PURE__ */ new Set();
1858
- for (const part of raw.split(path3.delimiter)) {
2029
+ for (const part of raw.split(path4.delimiter)) {
1859
2030
  const dir = part.trim();
1860
2031
  if (dir === "") continue;
1861
- if (!path3.isAbsolute(dir)) {
1862
- logger10.warn(
2032
+ if (!path4.isAbsolute(dir)) {
2033
+ logger12.warn(
1863
2034
  `[engine-discovery] ${ENGINE_ROOTS_ENV} entry '${dir}' is not an absolute path \u2014 dropping entry`
1864
2035
  );
1865
2036
  continue;
@@ -1873,11 +2044,11 @@ function parseEngineRootsEnv(env) {
1873
2044
  function deriveNodeModuleRoots(argvEntry = process.argv[1]) {
1874
2045
  if (!argvEntry || argvEntry.trim() === "") return [];
1875
2046
  const roots = [];
1876
- let dir = path3.dirname(path3.resolve(argvEntry));
2047
+ let dir = path4.dirname(path4.resolve(argvEntry));
1877
2048
  while (true) {
1878
- const nm = path3.join(dir, "node_modules");
2049
+ const nm = path4.join(dir, "node_modules");
1879
2050
  if (isDirectory(nm)) roots.push(nm);
1880
- const parent = path3.dirname(dir);
2051
+ const parent = path4.dirname(dir);
1881
2052
  if (parent === dir) break;
1882
2053
  dir = parent;
1883
2054
  }
@@ -1892,8 +2063,8 @@ function scanEngineRoot(root, visit) {
1892
2063
  }
1893
2064
  for (const item of entries) {
1894
2065
  if (!item.isDirectory()) continue;
1895
- const dir = path3.join(root, item.name);
1896
- if (isFile(path3.join(dir, "package.json"))) {
2066
+ const dir = path4.join(root, item.name);
2067
+ if (isFile(path4.join(dir, "package.json"))) {
1897
2068
  visit(dir);
1898
2069
  continue;
1899
2070
  }
@@ -1905,8 +2076,8 @@ function scanEngineRoot(root, visit) {
1905
2076
  }
1906
2077
  for (const child of sub) {
1907
2078
  if (!child.isDirectory()) continue;
1908
- const subDir = path3.join(dir, child.name);
1909
- if (isFile(path3.join(subDir, "package.json"))) visit(subDir);
2079
+ const subDir = path4.join(dir, child.name);
2080
+ if (isFile(path4.join(subDir, "package.json"))) visit(subDir);
1910
2081
  }
1911
2082
  }
1912
2083
  }
@@ -1927,10 +2098,10 @@ function isFile(p) {
1927
2098
 
1928
2099
  // src/execution/engine/config.ts
1929
2100
  var fs3 = __toESM(require("fs"), 1);
1930
- var path4 = __toESM(require("path"), 1);
1931
- var logger11 = getLogger("subagents");
2101
+ var path5 = __toESM(require("path"), 1);
2102
+ var logger13 = getLogger("subagents");
1932
2103
  function readExplicitEngines(agentDir) {
1933
- const configPath = path4.join(agentDir, "subagents", "config.json");
2104
+ const configPath = path5.join(agentDir, "subagents", "config.json");
1934
2105
  let parsed;
1935
2106
  try {
1936
2107
  parsed = JSON.parse(fs3.readFileSync(configPath, "utf8"));
@@ -1941,7 +2112,7 @@ function readExplicitEngines(agentDir) {
1941
2112
  const engines = parsed["engines"];
1942
2113
  if (typeof engines !== "object" || engines === null || Array.isArray(engines)) {
1943
2114
  if (engines !== void 0) {
1944
- logger11.warn(
2115
+ logger13.warn(
1945
2116
  `[engine-discovery] config.json engines section must be an object keyed by engine id, got ${jsonKindOf(engines)} \u2014 ignoring L3 explicit engines`
1946
2117
  );
1947
2118
  }
@@ -1969,7 +2140,7 @@ function sanitizeExplicitEntry(id, raw) {
1969
2140
  }
1970
2141
  function requireEntryObject(id, raw) {
1971
2142
  if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
1972
- logger11.warn(
2143
+ logger13.warn(
1973
2144
  `[engine-discovery] config.json engines.${id} must be an object, got ${jsonKindOf(raw)} \u2014 skipping entry`
1974
2145
  );
1975
2146
  return void 0;
@@ -1978,7 +2149,7 @@ function requireEntryObject(id, raw) {
1978
2149
  }
1979
2150
  function requireCommand(id, command) {
1980
2151
  if (typeof command !== "string" || command.trim() === "") {
1981
- logger11.warn(
2152
+ logger13.warn(
1982
2153
  `[engine-discovery] config.json engines.${id}.command is required (non-empty string) \u2014 skipping entry`
1983
2154
  );
1984
2155
  return void 0;
@@ -1987,7 +2158,7 @@ function requireCommand(id, command) {
1987
2158
  }
1988
2159
  function normalizeArgs(id, args) {
1989
2160
  if (args !== void 0 && !isStringArray(args)) {
1990
- logger11.warn(
2161
+ logger13.warn(
1991
2162
  `[engine-discovery] config.json engines.${id}.args must be a string array \u2014 ignoring args`
1992
2163
  );
1993
2164
  }
@@ -1995,7 +2166,7 @@ function normalizeArgs(id, args) {
1995
2166
  }
1996
2167
  function normalizeConfig(id, config) {
1997
2168
  if (config !== void 0 && !isStringRecord(config)) {
1998
- logger11.warn(
2169
+ logger13.warn(
1999
2170
  `[engine-discovery] config.json engines.${id}.config must be a Record<string, string> \u2014 ignoring config`
2000
2171
  );
2001
2172
  }
@@ -2003,7 +2174,7 @@ function normalizeConfig(id, config) {
2003
2174
  }
2004
2175
  function normalizeCwd(id, cwd) {
2005
2176
  if (cwd !== void 0 && typeof cwd !== "string") {
2006
- logger11.warn(
2177
+ logger13.warn(
2007
2178
  `[engine-discovery] config.json engines.${id}.cwd must be a string \u2014 ignoring cwd`
2008
2179
  );
2009
2180
  }
@@ -2023,7 +2194,7 @@ function jsonKindOf(value) {
2023
2194
  }
2024
2195
 
2025
2196
  // src/execution/engine/registry.ts
2026
- var logger12 = getLogger("subagents");
2197
+ var logger14 = getLogger("subagents");
2027
2198
  var ENGINE_REGISTRY_SLOT_KEY = /* @__PURE__ */ Symbol.for("@zhushanwen/pi-subagent-workflow.engineRegistry");
2028
2199
  function getRegistrySlot() {
2029
2200
  let slot = Reflect.get(globalThis, ENGINE_REGISTRY_SLOT_KEY);
@@ -2037,12 +2208,12 @@ function triggerEngineDispose(engine, source) {
2037
2208
  if (typeof engine.dispose !== "function") return;
2038
2209
  try {
2039
2210
  engine.dispose().then(void 0, (err) => {
2040
- logger12.warn(
2211
+ logger14.warn(
2041
2212
  `[engine-registry] engine '${engine.id}' dispose rejected (${source}, best-effort continue): ${err instanceof Error ? err.message : String(err)}`
2042
2213
  );
2043
2214
  });
2044
2215
  } catch (err) {
2045
- logger12.warn(
2216
+ logger14.warn(
2046
2217
  `[engine-registry] engine '${engine.id}' dispose threw synchronously (${source}, best-effort continue): ${err instanceof Error ? err.message : String(err)}`
2047
2218
  );
2048
2219
  }
@@ -2059,14 +2230,14 @@ function hasEngine(id) {
2059
2230
  }
2060
2231
 
2061
2232
  // src/execution/engine/engine-discovery-scan.ts
2062
- var logger13 = getLogger("subagents");
2233
+ var logger15 = getLogger("subagents");
2063
2234
  function scanEngines(opts) {
2064
2235
  const env = opts.env ?? process.env;
2065
2236
  const result = { discovered: [], skipped: [], unusable: [] };
2066
2237
  const present = (entry) => {
2067
2238
  const idx = result.discovered.findIndex((d) => d.id === entry.id);
2068
2239
  if (idx >= 0) {
2069
- logger13.debug(
2240
+ logger15.debug(
2070
2241
  `[engine-discovery] engine id '${entry.id}' from '${entry.source}' overrides earlier discovery from '${result.discovered[idx].source}' \u2014 same-id override`
2071
2242
  );
2072
2243
  result.discovered[idx] = entry;
@@ -2078,13 +2249,13 @@ function scanEngines(opts) {
2078
2249
  const inspection = inspectEnginePackage(pkgDir, source, opts.hostKind, env);
2079
2250
  if (inspection.status === "skip") {
2080
2251
  if (!inspection.reason.includes("not an engine package")) {
2081
- logger13.warn(`[engine-discovery] skipping ${pkgDir} (${source}): ${inspection.reason}`);
2252
+ logger15.warn(`[engine-discovery] skipping ${pkgDir} (${source}): ${inspection.reason}`);
2082
2253
  result.skipped.push({ pkgDir, reason: inspection.reason });
2083
2254
  }
2084
2255
  return;
2085
2256
  }
2086
2257
  if (inspection.status === "unusable") {
2087
- logger13.warn(`[engine-discovery] engine unavailable: ${inspection.reason}`);
2258
+ logger15.warn(`[engine-discovery] engine unavailable: ${inspection.reason}`);
2088
2259
  result.unusable.push({ pkgDir, id: inspection.id, reason: inspection.reason });
2089
2260
  return;
2090
2261
  }
@@ -2109,7 +2280,7 @@ function scanEngines(opts) {
2109
2280
  const command = resolveExplicitCommand(entry.command, env);
2110
2281
  if (command === void 0) {
2111
2282
  const reason = `engine '${id}' (config.json engines.${id}): command '${entry.command}' not found or not executable`;
2112
- logger13.warn(`[engine-discovery] ${reason} \u2014 not registering`);
2283
+ logger15.warn(`[engine-discovery] ${reason} \u2014 not registering`);
2113
2284
  result.unusable.push({ pkgDir: `config.json engines.${id}`, id, reason });
2114
2285
  continue;
2115
2286
  }
@@ -2126,7 +2297,7 @@ function discoverAndRegisterEngines(opts) {
2126
2297
  registerEngineDescriptor(entry.id, entry.descriptor);
2127
2298
  loadedDiscoveryIdSet.add(entry.id);
2128
2299
  if (existed) {
2129
- logger13.debug(
2300
+ logger15.debug(
2130
2301
  `[engine-discovery] engine '${entry.id}' descriptor overwritten in registry (source '${entry.source}') \u2014 same-id override`
2131
2302
  );
2132
2303
  }
@@ -2143,20 +2314,20 @@ function ensureEngineDiscovered(id, opts) {
2143
2314
  }
2144
2315
  function resolveExplicitCommand(command, env) {
2145
2316
  if (command.includes("/") || command.includes("\\")) {
2146
- const p = path5.isAbsolute(command) ? command : path5.resolve(command);
2317
+ const p = path6.isAbsolute(command) ? command : path6.resolve(command);
2147
2318
  return canExecute(p) ? p : void 0;
2148
2319
  }
2149
2320
  const pathVar = env["PATH"] ?? env["Path"] ?? "";
2150
- for (const dir of pathVar.split(path5.delimiter)) {
2321
+ for (const dir of pathVar.split(path6.delimiter)) {
2151
2322
  if (dir === "") continue;
2152
- const p = path5.join(dir, command);
2323
+ const p = path6.join(dir, command);
2153
2324
  if (canExecute(p)) return p;
2154
2325
  }
2155
2326
  return void 0;
2156
2327
  }
2157
2328
  function buildExplicitDescriptor(id, command, entry, opts) {
2158
2329
  const env = opts.env ?? process.env;
2159
- logger13.debug(
2330
+ logger15.debug(
2160
2331
  `[engine-discovery] engine '${id}' registered from config.json engines section with conservative capabilities (no manifest)`
2161
2332
  );
2162
2333
  const caps = { ...CONSERVATIVE_CAPABILITIES };