@zhushanwen/subagent-core 0.8.0 → 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 (167) hide show
  1. package/dist/{chunk-OL5BK4VN.js → chunk-LZJOEDGQ.js} +455 -203
  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 +216 -155
  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 +7686 -5894
  10. package/dist/index.d.cts +1876 -1360
  11. package/dist/index.d.ts +1876 -1360
  12. package/dist/index.js +7048 -5493
  13. package/dist.bundle/index.cjs +7747 -5957
  14. package/package.json +4 -4
  15. package/src/__tests__/manifest-store.test.ts +16 -15
  16. package/src/__tests__/robustness-medium-batch1.test.ts +10 -14
  17. package/src/__tests__/robustness-medium-batch2.test.ts +7 -2
  18. package/src/execution/__tests__/alive-store.test.ts +117 -1
  19. package/src/execution/__tests__/batch-finalized.test.ts +206 -0
  20. package/src/execution/__tests__/chat-engine-routing.test.ts +117 -1
  21. package/src/execution/__tests__/{cold-resurrect.test.ts → cold-lookup.test.ts} +133 -33
  22. package/src/execution/__tests__/collect-coordinator-service.test.ts +7 -5
  23. package/src/execution/__tests__/conversation-continuation.test.ts +1452 -0
  24. package/src/execution/__tests__/delivery-methods.test.ts +90 -176
  25. package/src/execution/__tests__/dialog-queue.test.ts +70 -3
  26. package/src/execution/__tests__/dispose-manifest-recovery.test.ts +475 -0
  27. package/src/execution/__tests__/execute-and-await-worktree.test.ts +3 -3
  28. package/src/execution/__tests__/execute-nesting.test.ts +8 -5
  29. package/src/execution/__tests__/execution-record.test.ts +8 -148
  30. package/src/execution/__tests__/finalize-record.test.ts +295 -129
  31. package/src/execution/__tests__/get-record-for-action-restart.test.ts +21 -29
  32. package/src/execution/__tests__/helpers/fake-engine-port.ts +13 -68
  33. package/src/execution/__tests__/helpers/legacy-sidecar.ts +33 -0
  34. package/src/execution/__tests__/helpers/subagent-service-mocks.ts +9 -5
  35. package/src/execution/__tests__/idle-gc.test.ts +60 -0
  36. package/src/execution/__tests__/lifecycle-manager.test.ts +3 -254
  37. package/src/execution/__tests__/manifest-store-tmp-recovery.test.ts +43 -22
  38. package/src/execution/__tests__/model-config-service.test.ts +111 -0
  39. package/src/execution/__tests__/nested-visibility-env-propagation.test.ts +10 -6
  40. package/src/execution/__tests__/notify-ledger.test.ts +42 -1
  41. package/src/execution/__tests__/rebuild-indexes.test.ts +329 -0
  42. package/src/execution/__tests__/record-binding.test.ts +613 -0
  43. package/src/execution/__tests__/record-origin.test.ts +318 -0
  44. package/src/execution/__tests__/record-store-intent-api.test.ts +600 -0
  45. package/src/execution/__tests__/record-store-orphan-revive.test.ts +71 -3
  46. package/src/execution/__tests__/record-store.test.ts +72 -54
  47. package/src/execution/__tests__/recursive-visibility-baseline.test.ts +8 -5
  48. package/src/execution/__tests__/round-supervisor-workflow-origin.test.ts +205 -0
  49. package/src/execution/__tests__/run-orchestration-write-lease.test.ts +293 -0
  50. package/src/execution/__tests__/run-orchestration.test.ts +199 -0
  51. package/src/execution/__tests__/session-file-gc.test.ts +35 -4
  52. package/src/execution/__tests__/session-reconstructor.test.ts +78 -0
  53. package/src/execution/__tests__/state-marker.test.ts +288 -0
  54. package/src/execution/__tests__/stream-sink-retirement.test.ts +8 -5
  55. package/src/execution/__tests__/subagent-actions-core.test.ts +67 -16
  56. package/src/execution/__tests__/subagent-service-multiproc-guard.test.ts +0 -1
  57. package/src/execution/__tests__/subagent-service-notify-gate.test.ts +27 -11
  58. package/src/execution/__tests__/subagent-service-parent-guard.test.ts +17 -11
  59. package/src/execution/__tests__/subagent-service-recovery-bounds.test.ts +130 -53
  60. package/src/execution/__tests__/subagent-service.test.ts +17 -215
  61. package/src/execution/__tests__/subprocess-agent-runner-no-progress-killall.test.ts +138 -0
  62. package/src/execution/__tests__/subprocess-agent-runner.test.ts +105 -660
  63. package/src/execution/__tests__/sync-collect-recovery.test.ts +23 -16
  64. package/src/execution/__tests__/terminal-write-latency.test.ts +79 -0
  65. package/src/execution/__tests__/workflow-agent-dispatch.test.ts +782 -0
  66. package/src/execution/alive-store.ts +37 -28
  67. package/src/execution/{cold-resurrect.ts → cold-lookup.ts} +48 -45
  68. package/src/execution/config.ts +1 -1
  69. package/src/execution/conversation-continuation.ts +541 -0
  70. package/src/execution/dialog-queue.ts +60 -12
  71. package/src/execution/engine/__tests__/common/capability-gate.test.ts +1 -1
  72. package/src/execution/engine/__tests__/common/journal-wiring.test.ts +16 -1
  73. package/src/execution/engine/__tests__/conformance/__fixtures__/engine-protocol/fake-engine-protocol.mjs +17 -51
  74. package/src/execution/engine/__tests__/conformance/__fixtures__/engine-protocol/smoke-run.fixture.json +0 -5
  75. package/src/execution/engine/__tests__/conformance/engine-conformance.live.test.ts +1 -1
  76. package/src/execution/engine/__tests__/conformance/protocol-blackbox.test.ts +5 -9
  77. package/src/execution/engine/__tests__/engine-discovery-scan.test.ts +0 -3
  78. package/src/execution/engine/__tests__/engine-discovery.test.ts +0 -3
  79. package/src/execution/engine/__tests__/model-prompt.test.ts +0 -3
  80. package/src/execution/engine/__tests__/registry.test.ts +0 -1
  81. package/src/execution/engine/__tests__/routing.test.ts +0 -1
  82. package/src/execution/engine/__tests__/run-failure-worktree-cleanup.test.ts +0 -3
  83. package/src/execution/engine/client/__tests__/__fixtures__/fake-engine.mjs +0 -3
  84. package/src/execution/engine/client/__tests__/engine-client-reap.test.ts +212 -0
  85. package/src/execution/engine/client/__tests__/mirror.test.ts +73 -1
  86. package/src/execution/engine/client/__tests__/protocol-closure.test.ts +2 -9
  87. package/src/execution/engine/client/__tests__/remote-engine.test.ts +46 -12
  88. package/src/execution/engine/client/engine-client.ts +81 -25
  89. package/src/execution/engine/client/mirror.ts +9 -0
  90. package/src/execution/engine/client/remote-engine.ts +52 -34
  91. package/src/execution/engine/client/reverse-router.ts +6 -27
  92. package/src/execution/engine/common/__tests__/run-signals.test.ts +79 -0
  93. package/src/execution/engine/common/capability-gate.ts +27 -0
  94. package/src/execution/engine/common/data-dir.ts +3 -2
  95. package/src/execution/engine/common/errors.ts +1 -1
  96. package/src/execution/engine/common/journal-wiring.ts +18 -9
  97. package/src/execution/engine/common/run-signals.ts +83 -0
  98. package/src/execution/engine/d8-compat.ts +1 -1
  99. package/src/execution/engine/host/__tests__/host-bridge.test.ts +3 -18
  100. package/src/execution/engine/host/host-bridge.ts +19 -41
  101. package/src/execution/engine/host/pi-host-binding.ts +5 -5
  102. package/src/execution/engine/port.ts +34 -54
  103. package/src/execution/engine/types.ts +11 -14
  104. package/src/execution/execution-record.ts +15 -47
  105. package/src/execution/finalize-record.ts +94 -197
  106. package/src/execution/idle-gc.ts +9 -3
  107. package/src/execution/lifecycle-manager.ts +25 -361
  108. package/src/execution/lifecycle-predicates.ts +22 -19
  109. package/src/execution/manifest-store.ts +40 -42
  110. package/src/execution/model-config-service.ts +1 -1
  111. package/src/execution/notifier.ts +39 -1
  112. package/src/execution/notify-host.ts +13 -4
  113. package/src/execution/record-entry.ts +20 -2
  114. package/src/execution/record-store.ts +925 -160
  115. package/src/execution/relay-env.ts +8 -3
  116. package/src/execution/round-supervisor/domain.ts +17 -5
  117. package/src/execution/round-supervisor/service-binding.test.ts +89 -53
  118. package/src/execution/round-supervisor/service-binding.ts +71 -21
  119. package/src/execution/round-supervisor/supervisor.ts +23 -9
  120. package/src/execution/service/record-access.ts +547 -0
  121. package/src/execution/service/record-lifecycle.ts +542 -0
  122. package/src/execution/service/run-orchestration.ts +1647 -0
  123. package/src/execution/service/service-bootstrap.ts +111 -0
  124. package/src/execution/service/service-constants.ts +27 -0
  125. package/src/execution/service/session-baselines.ts +401 -0
  126. package/src/execution/service/sync-collect-domain.ts +442 -0
  127. package/src/execution/service/workflow-dispatch.ts +567 -0
  128. package/src/execution/session-file-gc.ts +11 -8
  129. package/src/execution/session-reconstructor.ts +44 -1
  130. package/src/execution/sessions-index.ts +20 -3
  131. package/src/execution/settled-watchdog.ts +86 -43
  132. package/src/execution/state-marker.ts +516 -0
  133. package/src/execution/stream-sink.ts +1 -1
  134. package/src/execution/subagent-actions-core.ts +53 -31
  135. package/src/execution/subagent-service.ts +643 -2980
  136. package/src/execution/subprocess-agent-runner.ts +46 -323
  137. package/src/execution/types.ts +60 -43
  138. package/src/execution/ui-request-handler-factory.ts +5 -0
  139. package/src/execution/worktree-manager.ts +1 -1
  140. package/src/execution/worktree-registry.ts +1 -1
  141. package/src/index.ts +6 -5
  142. package/src/orchestration/__tests__/agent-call-catch-fallback.test.ts +2 -3
  143. package/src/orchestration/__tests__/agent-call-stream.test.ts +24 -49
  144. package/src/orchestration/__tests__/error-recovery-terminal-hardening.test.ts +4 -4
  145. package/src/orchestration/__tests__/file-run-store.test.ts +5 -7
  146. package/src/orchestration/__tests__/lifecycle-abort-broadcast-signal.test.ts +2 -3
  147. package/src/orchestration/__tests__/run-snapshot.test.ts +27 -43
  148. package/src/orchestration/__tests__/worker-message-pump-workflow-dispatch.test.ts +274 -0
  149. package/src/orchestration/execute-agent-call.ts +1 -1
  150. package/src/orchestration/file-run-store.ts +1 -1
  151. package/src/orchestration/models/ports.ts +20 -13
  152. package/src/orchestration/models/types.ts +11 -13
  153. package/src/orchestration/run-snapshot.ts +18 -22
  154. package/src/orchestration/worker-message-pump.ts +33 -57
  155. package/src/shared/atomic-write.ts +10 -8
  156. package/dist/chunk-T2SYBW3I.js +0 -24
  157. package/dist/engine-discovery-scan-BXLA8y-z.d.cts +0 -1624
  158. package/dist/engine-discovery-scan-BXLA8y-z.d.ts +0 -1624
  159. package/src/execution/__tests__/chat-round-first-round-watchdog.test.ts +0 -255
  160. package/src/execution/__tests__/finalized-marker.test.ts +0 -104
  161. package/src/execution/__tests__/lifecycle-manager-lock.test.ts +0 -211
  162. package/src/execution/__tests__/subprocess-agent-runner-routing.test.ts +0 -488
  163. package/src/execution/__tests__/subprocess-agent-runner-timeout.test.ts +0 -75
  164. package/src/execution/__tests__/tombstone-store.test.ts +0 -73
  165. package/src/execution/engine/__tests__/conformance/chat-round-protocol.test.ts +0 -230
  166. package/src/execution/finalized-marker.ts +0 -70
  167. 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
  }
@@ -399,7 +414,7 @@ var SpawnedChildrenMirror = class {
399
414
  try {
400
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
  }
@@ -905,17 +909,15 @@ function hasLiveProcessHandleCore(recordId) {
905
909
  }
906
910
 
907
911
  // src/execution/lifecycle-manager.ts
908
- var logger8 = getLogger("subagents");
909
- var MS_PER_SECOND = 1e3;
910
- var SECONDS_PER_MINUTE = 60;
912
+ var logger9 = getLogger("subagents");
913
+ var MS_PER_SECOND2 = 1e3;
914
+ var SECONDS_PER_MINUTE2 = 60;
911
915
  var IDLE_TIMEOUT_MINUTES = 5;
912
- var DEFAULT_IDLE_TIMEOUT_MS = IDLE_TIMEOUT_MINUTES * SECONDS_PER_MINUTE * MS_PER_SECOND;
916
+ var DEFAULT_IDLE_TIMEOUT_MS = IDLE_TIMEOUT_MINUTES * SECONDS_PER_MINUTE2 * MS_PER_SECOND2;
913
917
  var idleTimers = /* @__PURE__ */ new Map();
914
918
  function hasIdleTimer(recordId) {
915
919
  return idleTimers.has(recordId);
916
920
  }
917
- var ACTIVATE_LOCK_TIMEOUT_SECONDS = 30;
918
- var ACTIVATE_LOCK_TIMEOUT_MS = ACTIVATE_LOCK_TIMEOUT_SECONDS * MS_PER_SECOND;
919
921
 
920
922
  // src/execution/lifecycle-predicates.ts
921
923
  function hasLiveProcessHandle(recordId) {
@@ -944,7 +946,7 @@ function countInFlight() {
944
946
  }
945
947
 
946
948
  // src/execution/engine/client/engine-client.ts
947
- var logger9 = (0, import_subagent_engine_sdk6.getLogger)("subagents");
949
+ var logger10 = (0, import_subagent_engine_sdk6.getLogger)("subagents");
948
950
  function bridgeMirrorEventToCoreMirror(event, mirror) {
949
951
  if (event.recordId === void 0 || event.pid === void 0) return;
950
952
  const entry = mirror.getEntry(event.pid);
@@ -986,9 +988,6 @@ var EngineClient = class {
986
988
  nextRequestId = 1;
987
989
  pending = /* @__PURE__ */ new Map();
988
990
  runRoutes = /* @__PURE__ */ new Map();
989
- /** [W3 v1.x] chat 轮次路由(recordId 键,D1-A——续聊轮无 runId);注册经
990
- * RemoteEngine.registerChatRoundRoute,record 终态注销。 */
991
- recordRoutes = /* @__PURE__ */ new Map();
992
991
  stderrTail = "";
993
992
  unavailableReason;
994
993
  connectInFlight;
@@ -1008,7 +1007,6 @@ var EngineClient = class {
1008
1007
  permissionHandler: opts.permissionHandler,
1009
1008
  log: opts.log,
1010
1009
  runRoutes: this.runRoutes,
1011
- recordRoutes: this.recordRoutes,
1012
1010
  mirror: this.mirror,
1013
1011
  setPartialHandle: (partial) => {
1014
1012
  this.lastPartialHandle = partial;
@@ -1082,19 +1080,19 @@ var EngineClient = class {
1082
1080
  matchesEngineCmdline: this.opts.engineCmdlineMatcher ?? defaultEngineCmdlineMatcher(this.opts.command)
1083
1081
  });
1084
1082
  if (sweep.killed.length > 0) {
1085
- logger9.warn(
1083
+ logger10.warn(
1086
1084
  `[engine-client:${this.engineId}] startup sweep killed stale orphan engine pids: ${sweep.killed.join(",")}`
1087
1085
  );
1088
1086
  }
1089
1087
  for (const removed of sweep.removed) {
1090
- logger9.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}`);
1091
1089
  }
1092
1090
  }
1093
1091
  let attempt = 0;
1094
1092
  while (attempt <= import_subagent_engine_sdk6.CRASH_REBUILD_MAX_ATTEMPTS) {
1095
1093
  if (attempt > 0) {
1096
1094
  const backoff = import_subagent_engine_sdk6.CRASH_REBUILD_BACKOFF_MS[attempt - 1];
1097
- logger9.warn(
1095
+ logger10.warn(
1098
1096
  `[engine-client:${this.engineId}] rebuild attempt ${attempt}/${import_subagent_engine_sdk6.CRASH_REBUILD_MAX_ATTEMPTS} after ${backoff}ms backoff`
1099
1097
  );
1100
1098
  await delay(backoff);
@@ -1127,7 +1125,7 @@ var EngineClient = class {
1127
1125
  markUnavailable(reason) {
1128
1126
  this.state = "unavailable";
1129
1127
  this.unavailableReason = reason;
1130
- logger9.error(`[engine-client:${this.engineId}] marked unavailable: ${reason.message}`);
1128
+ logger10.error(`[engine-client:${this.engineId}] marked unavailable: ${reason.message}`);
1131
1129
  }
1132
1130
  /** spawn 引擎 CLI + initialize 握手 + 版本协商 + 诊断留痕。 */
1133
1131
  async spawnAndInitialize() {
@@ -1157,13 +1155,13 @@ var EngineClient = class {
1157
1155
  });
1158
1156
  const child = this.child;
1159
1157
  child.on("error", (err) => {
1160
- logger9.warn(`[engine-client:${this.engineId}] spawn error: ${err.message}`);
1158
+ logger10.warn(`[engine-client:${this.engineId}] spawn error: ${err.message}`);
1161
1159
  this.appendStderrTail(`spawn error: ${err.message}
1162
1160
  `);
1163
1161
  });
1164
1162
  for (const pipe of ["stdin", "stdout", "stderr"]) {
1165
1163
  child[pipe]?.on("error", (err) => {
1166
- logger9.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}`);
1167
1165
  });
1168
1166
  }
1169
1167
  child.stdout?.setEncoding("utf-8");
@@ -1232,7 +1230,7 @@ var EngineClient = class {
1232
1230
  try {
1233
1231
  frame = JSON.parse(line);
1234
1232
  } catch {
1235
- logger9.warn(
1233
+ logger10.warn(
1236
1234
  `[engine-client:${this.engineId}] dropped non-NDJSON stdout line (protocol contract: stdout is NDJSON-only): ${truncate(line, FRAME_ECHO_MAX_CHARS)}`
1237
1235
  );
1238
1236
  return;
@@ -1249,7 +1247,7 @@ var EngineClient = class {
1249
1247
  this.onReverseRequest(frame);
1250
1248
  return;
1251
1249
  }
1252
- logger9.warn(
1250
+ logger10.warn(
1253
1251
  `[engine-client:${this.engineId}] dropped unrecognized frame: ${truncate(line, FRAME_ECHO_MAX_CHARS)}`
1254
1252
  );
1255
1253
  }
@@ -1330,14 +1328,6 @@ var EngineClient = class {
1330
1328
  if (this.runRoutes.get(runId) === route) this.runRoutes.delete(runId);
1331
1329
  };
1332
1330
  }
1333
- /** [W3 v1.x] 注册 chat 轮次路由(recordId 键);分发见 reverse-router.ts
1334
- * (与 runRoutes 同构,键空间互斥:runId=run 帧分配 / recordId=宿主铸造)。 */
1335
- registerRecordRoute(recordId, route) {
1336
- this.recordRoutes.set(recordId, route);
1337
- return () => {
1338
- if (this.recordRoutes.get(recordId) === route) this.recordRoutes.delete(recordId);
1339
- };
1340
- }
1341
1331
  /** 健康检查(ADR-0047:静默 ≠ 卡死,不据此杀任务)。 */
1342
1332
  async ping() {
1343
1333
  await this.ensureConnected();
@@ -1373,7 +1363,7 @@ var EngineClient = class {
1373
1363
  try {
1374
1364
  await this.request("dispose", {}, { timeoutMs: DISPOSE_GRACE_MS });
1375
1365
  } catch (err) {
1376
- logger9.debug(
1366
+ logger10.debug(
1377
1367
  `[engine-client:${this.engineId}] dispose frame failed, falling back to kill chain: ${err instanceof Error ? err.message : String(err)}`
1378
1368
  );
1379
1369
  }
@@ -1385,18 +1375,20 @@ var EngineClient = class {
1385
1375
  onEngineExit(code, signal) {
1386
1376
  const detail = signal !== null ? `signal ${signal}` : `exit code ${code}`;
1387
1377
  if (this.intentionalKill) {
1388
- logger9.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`);
1389
1379
  } else {
1390
- logger9.warn(`[engine-client:${this.engineId}] engine process exited unexpectedly (${detail})`);
1380
+ logger10.warn(`[engine-client:${this.engineId}] engine process exited unexpectedly (${detail})`);
1391
1381
  }
1392
1382
  this.teardownProcess(detail);
1393
1383
  this.state = "exited";
1394
1384
  }
1395
1385
  /**
1396
- * 进程死亡 / 失败后的统一清理:镜像整体置死(失效语义 2+3:killed=true 广播后
1397
- * 清空)→ pidfile 删 → 在途请求 engine_crashed(附 stderr 尾)→ run 路由清空。
1386
+ * 进程死亡 / 失败后的统一清理(收割/组杀分支各成子函数,此处只做顺序编排):
1387
+ * 孤儿收割(镜像置死前)→ 镜像置死(失效语义 2+3)→ pidfile 删 → 在途请求
1388
+ * engine_crashed(附 stderr 尾)→ run 路由清空 → 活引擎组杀兜底。
1398
1389
  */
1399
1390
  teardownProcess(detail) {
1391
+ this.reapOrphansAfterUnexpectedDeath(detail);
1400
1392
  this.mirror.killAll();
1401
1393
  if (this.pidfileWritten !== void 0) {
1402
1394
  removePidfile(this.pidfileWritten);
@@ -1409,8 +1401,46 @@ var EngineClient = class {
1409
1401
  }
1410
1402
  this.pending.clear();
1411
1403
  this.runRoutes.clear();
1412
- this.recordRoutes.clear();
1413
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) {
1414
1444
  if (this.child !== void 0) {
1415
1445
  const child = this.child;
1416
1446
  this.child = void 0;
@@ -1420,8 +1450,30 @@ var EngineClient = class {
1420
1450
  }
1421
1451
  child.removeAllListeners();
1422
1452
  }
1423
- if (this.state !== "unavailable" && this.state !== "disposed") {
1424
- 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
+ });
1425
1477
  }
1426
1478
  }
1427
1479
  // ── pidfile ──────────────────────────────────────────────────────────────
@@ -1436,7 +1488,7 @@ var EngineClient = class {
1436
1488
  `);
1437
1489
  return true;
1438
1490
  } catch (err) {
1439
- logger9.debug(
1491
+ logger10.debug(
1440
1492
  `[engine-client:${this.engineId}] stdin write failed: ${err instanceof Error ? err.message : String(err)}`
1441
1493
  );
1442
1494
  return false;
@@ -1459,8 +1511,19 @@ function truncate(text, max) {
1459
1511
  }
1460
1512
 
1461
1513
  // src/execution/engine/client/remote-engine.ts
1514
+ var import_node_os2 = require("os");
1515
+ var import_node_path4 = require("path");
1462
1516
  var import_subagent_engine_sdk8 = require("@zhushanwen/subagent-engine-sdk");
1463
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
+
1464
1527
  // src/execution/engine/common/capability-gate.ts
1465
1528
  var import_subagent_engine_sdk7 = require("@zhushanwen/subagent-engine-sdk");
1466
1529
 
@@ -1475,7 +1538,7 @@ var EngineError = class extends Error {
1475
1538
  this.code = code;
1476
1539
  this.recovery = recovery;
1477
1540
  }
1478
- /** 结构化投影(InteractResult.code/message 与 GUI 警告条共用形态)。 */
1541
+ /** 结构化投影(code/message 与 GUI 警告条共用形态)。 */
1479
1542
  toStructured() {
1480
1543
  return { code: this.code, message: this.message, recovery: this.recovery };
1481
1544
  }
@@ -1624,22 +1687,6 @@ var RemoteEngine = class {
1624
1687
  unregister();
1625
1688
  }
1626
1689
  }
1627
- async interact(handle, action) {
1628
- await this.opts.client.ensureConnected();
1629
- const result = await this.opts.client.request("interact", {
1630
- handle: handle.data,
1631
- action
1632
- });
1633
- return result;
1634
- }
1635
- /**
1636
- * [W3 v1.x] chat 轮次反向通道路由注册(recordId 键,EnginePort 可选面实现):
1637
- * interact 续聊轮的 streamDelta / roundLifecycle 分发目标。薄委托 EngineClient
1638
- * 的 recordRoutes(键分发见 reverse-router.ts)。
1639
- */
1640
- registerChatRoundRoute(recordId, route) {
1641
- return this.opts.client.registerRecordRoute(recordId, route);
1642
- }
1643
1690
  /** 协议 read(dataDir 必填——引擎数据根,构造注入)。 */
1644
1691
  async read(handle) {
1645
1692
  await this.opts.client.ensureConnected();
@@ -1671,6 +1718,15 @@ function isTransientRunFailure(err) {
1671
1718
  if (!(err instanceof import_subagent_engine_sdk8.EngineSdkError)) return false;
1672
1719
  return err.code === "engine_crashed" || err.code === "engine_request_timeout" || err.code === "engine_handshake_timeout";
1673
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
+ }
1674
1730
  function buildRunParams(task, ctx, runId) {
1675
1731
  const ctxModelRef = ctx.ctxModel ? `${ctx.ctxModel.provider}/${ctx.ctxModel.id}` : void 0;
1676
1732
  return {
@@ -1683,9 +1739,16 @@ function buildRunParams(task, ctx, runId) {
1683
1739
  schemaEnv: ctx.schemaEnv ?? task.schemaEnv,
1684
1740
  ctxModel: ctxModelRef,
1685
1741
  engineFallback: ctx.engineFallback,
1686
- 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()
1687
1750
  },
1688
- ...ctx.chat !== void 0 ? { chat: ctx.chat } : {}
1751
+ ...ctx.resume !== void 0 ? { resume: ctx.resume } : {}
1689
1752
  };
1690
1753
  }
1691
1754
  function buildRunRouteHandlers(ctx) {
@@ -1693,9 +1756,7 @@ function buildRunRouteHandlers(ctx) {
1693
1756
  onEvent: (event) => ctx.onEvent?.(event),
1694
1757
  onStreamDelta: (delta) => ctx.stream?.onDelta(delta),
1695
1758
  onPoolResolved: (poolKey) => ctx.onPoolResolved?.(poolKey),
1696
- onHandleReady: (partial) => ctx.onHandleReady?.(partial),
1697
- // [W3 v1.x] 首轮(run 会话形态)轮次生命周期帧(runId 键)→ 宿主消费口。
1698
- onRoundLifecycle: (phase) => ctx.onRoundLifecycle?.(phase)
1759
+ onHandleReady: (partial) => ctx.onHandleReady?.(partial)
1699
1760
  };
1700
1761
  }
1701
1762
  function wireAbortSignal(client, runId, ctx) {
@@ -1785,11 +1846,11 @@ function getHostUiRequestEndpoint() {
1785
1846
  }
1786
1847
 
1787
1848
  // src/execution/engine/engine-inspect-package.ts
1788
- var logger10 = getLogger("subagents");
1849
+ var logger11 = getLogger("subagents");
1789
1850
  function readEnginePackageJson(pkgDir) {
1790
1851
  let raw;
1791
1852
  try {
1792
- raw = fs.readFileSync(path2.join(pkgDir, "package.json"), "utf8");
1853
+ raw = fs.readFileSync(path3.join(pkgDir, "package.json"), "utf8");
1793
1854
  } catch (err) {
1794
1855
  return { status: "skip", reason: `package.json unreadable: ${errorMessage(err)}` };
1795
1856
  }
@@ -1864,12 +1925,12 @@ function parseOptionalDisplayFields(m, id) {
1864
1925
  if (typeof rawDisplayName === "string" && rawDisplayName.trim() !== "") {
1865
1926
  displayName = rawDisplayName;
1866
1927
  } else {
1867
- logger10.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`);
1868
1929
  }
1869
1930
  }
1870
1931
  const rawDescription = m["description"];
1871
1932
  if (rawDescription !== void 0 && typeof rawDescription !== "string") {
1872
- logger10.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`);
1873
1934
  }
1874
1935
  return displayName;
1875
1936
  }
@@ -1957,19 +2018,19 @@ function errorMessage(err) {
1957
2018
 
1958
2019
  // src/execution/engine/engine-discovery-roots.ts
1959
2020
  var fs2 = __toESM(require("fs"), 1);
1960
- var path3 = __toESM(require("path"), 1);
1961
- var logger11 = getLogger("subagents");
2021
+ var path4 = __toESM(require("path"), 1);
2022
+ var logger12 = getLogger("subagents");
1962
2023
  var ENGINE_ROOTS_ENV = "XYZ_AGENT_ENGINE_ROOTS";
1963
2024
  function parseEngineRootsEnv(env) {
1964
2025
  const raw = env[ENGINE_ROOTS_ENV];
1965
2026
  if (raw === void 0 || raw.trim() === "") return [];
1966
2027
  const out = [];
1967
2028
  const seen = /* @__PURE__ */ new Set();
1968
- for (const part of raw.split(path3.delimiter)) {
2029
+ for (const part of raw.split(path4.delimiter)) {
1969
2030
  const dir = part.trim();
1970
2031
  if (dir === "") continue;
1971
- if (!path3.isAbsolute(dir)) {
1972
- logger11.warn(
2032
+ if (!path4.isAbsolute(dir)) {
2033
+ logger12.warn(
1973
2034
  `[engine-discovery] ${ENGINE_ROOTS_ENV} entry '${dir}' is not an absolute path \u2014 dropping entry`
1974
2035
  );
1975
2036
  continue;
@@ -1983,11 +2044,11 @@ function parseEngineRootsEnv(env) {
1983
2044
  function deriveNodeModuleRoots(argvEntry = process.argv[1]) {
1984
2045
  if (!argvEntry || argvEntry.trim() === "") return [];
1985
2046
  const roots = [];
1986
- let dir = path3.dirname(path3.resolve(argvEntry));
2047
+ let dir = path4.dirname(path4.resolve(argvEntry));
1987
2048
  while (true) {
1988
- const nm = path3.join(dir, "node_modules");
2049
+ const nm = path4.join(dir, "node_modules");
1989
2050
  if (isDirectory(nm)) roots.push(nm);
1990
- const parent = path3.dirname(dir);
2051
+ const parent = path4.dirname(dir);
1991
2052
  if (parent === dir) break;
1992
2053
  dir = parent;
1993
2054
  }
@@ -2002,8 +2063,8 @@ function scanEngineRoot(root, visit) {
2002
2063
  }
2003
2064
  for (const item of entries) {
2004
2065
  if (!item.isDirectory()) continue;
2005
- const dir = path3.join(root, item.name);
2006
- if (isFile(path3.join(dir, "package.json"))) {
2066
+ const dir = path4.join(root, item.name);
2067
+ if (isFile(path4.join(dir, "package.json"))) {
2007
2068
  visit(dir);
2008
2069
  continue;
2009
2070
  }
@@ -2015,8 +2076,8 @@ function scanEngineRoot(root, visit) {
2015
2076
  }
2016
2077
  for (const child of sub) {
2017
2078
  if (!child.isDirectory()) continue;
2018
- const subDir = path3.join(dir, child.name);
2019
- 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);
2020
2081
  }
2021
2082
  }
2022
2083
  }
@@ -2037,10 +2098,10 @@ function isFile(p) {
2037
2098
 
2038
2099
  // src/execution/engine/config.ts
2039
2100
  var fs3 = __toESM(require("fs"), 1);
2040
- var path4 = __toESM(require("path"), 1);
2041
- var logger12 = getLogger("subagents");
2101
+ var path5 = __toESM(require("path"), 1);
2102
+ var logger13 = getLogger("subagents");
2042
2103
  function readExplicitEngines(agentDir) {
2043
- const configPath = path4.join(agentDir, "subagents", "config.json");
2104
+ const configPath = path5.join(agentDir, "subagents", "config.json");
2044
2105
  let parsed;
2045
2106
  try {
2046
2107
  parsed = JSON.parse(fs3.readFileSync(configPath, "utf8"));
@@ -2051,7 +2112,7 @@ function readExplicitEngines(agentDir) {
2051
2112
  const engines = parsed["engines"];
2052
2113
  if (typeof engines !== "object" || engines === null || Array.isArray(engines)) {
2053
2114
  if (engines !== void 0) {
2054
- logger12.warn(
2115
+ logger13.warn(
2055
2116
  `[engine-discovery] config.json engines section must be an object keyed by engine id, got ${jsonKindOf(engines)} \u2014 ignoring L3 explicit engines`
2056
2117
  );
2057
2118
  }
@@ -2079,7 +2140,7 @@ function sanitizeExplicitEntry(id, raw) {
2079
2140
  }
2080
2141
  function requireEntryObject(id, raw) {
2081
2142
  if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
2082
- logger12.warn(
2143
+ logger13.warn(
2083
2144
  `[engine-discovery] config.json engines.${id} must be an object, got ${jsonKindOf(raw)} \u2014 skipping entry`
2084
2145
  );
2085
2146
  return void 0;
@@ -2088,7 +2149,7 @@ function requireEntryObject(id, raw) {
2088
2149
  }
2089
2150
  function requireCommand(id, command) {
2090
2151
  if (typeof command !== "string" || command.trim() === "") {
2091
- logger12.warn(
2152
+ logger13.warn(
2092
2153
  `[engine-discovery] config.json engines.${id}.command is required (non-empty string) \u2014 skipping entry`
2093
2154
  );
2094
2155
  return void 0;
@@ -2097,7 +2158,7 @@ function requireCommand(id, command) {
2097
2158
  }
2098
2159
  function normalizeArgs(id, args) {
2099
2160
  if (args !== void 0 && !isStringArray(args)) {
2100
- logger12.warn(
2161
+ logger13.warn(
2101
2162
  `[engine-discovery] config.json engines.${id}.args must be a string array \u2014 ignoring args`
2102
2163
  );
2103
2164
  }
@@ -2105,7 +2166,7 @@ function normalizeArgs(id, args) {
2105
2166
  }
2106
2167
  function normalizeConfig(id, config) {
2107
2168
  if (config !== void 0 && !isStringRecord(config)) {
2108
- logger12.warn(
2169
+ logger13.warn(
2109
2170
  `[engine-discovery] config.json engines.${id}.config must be a Record<string, string> \u2014 ignoring config`
2110
2171
  );
2111
2172
  }
@@ -2113,7 +2174,7 @@ function normalizeConfig(id, config) {
2113
2174
  }
2114
2175
  function normalizeCwd(id, cwd) {
2115
2176
  if (cwd !== void 0 && typeof cwd !== "string") {
2116
- logger12.warn(
2177
+ logger13.warn(
2117
2178
  `[engine-discovery] config.json engines.${id}.cwd must be a string \u2014 ignoring cwd`
2118
2179
  );
2119
2180
  }
@@ -2133,7 +2194,7 @@ function jsonKindOf(value) {
2133
2194
  }
2134
2195
 
2135
2196
  // src/execution/engine/registry.ts
2136
- var logger13 = getLogger("subagents");
2197
+ var logger14 = getLogger("subagents");
2137
2198
  var ENGINE_REGISTRY_SLOT_KEY = /* @__PURE__ */ Symbol.for("@zhushanwen/pi-subagent-workflow.engineRegistry");
2138
2199
  function getRegistrySlot() {
2139
2200
  let slot = Reflect.get(globalThis, ENGINE_REGISTRY_SLOT_KEY);
@@ -2147,12 +2208,12 @@ function triggerEngineDispose(engine, source) {
2147
2208
  if (typeof engine.dispose !== "function") return;
2148
2209
  try {
2149
2210
  engine.dispose().then(void 0, (err) => {
2150
- logger13.warn(
2211
+ logger14.warn(
2151
2212
  `[engine-registry] engine '${engine.id}' dispose rejected (${source}, best-effort continue): ${err instanceof Error ? err.message : String(err)}`
2152
2213
  );
2153
2214
  });
2154
2215
  } catch (err) {
2155
- logger13.warn(
2216
+ logger14.warn(
2156
2217
  `[engine-registry] engine '${engine.id}' dispose threw synchronously (${source}, best-effort continue): ${err instanceof Error ? err.message : String(err)}`
2157
2218
  );
2158
2219
  }
@@ -2169,14 +2230,14 @@ function hasEngine(id) {
2169
2230
  }
2170
2231
 
2171
2232
  // src/execution/engine/engine-discovery-scan.ts
2172
- var logger14 = getLogger("subagents");
2233
+ var logger15 = getLogger("subagents");
2173
2234
  function scanEngines(opts) {
2174
2235
  const env = opts.env ?? process.env;
2175
2236
  const result = { discovered: [], skipped: [], unusable: [] };
2176
2237
  const present = (entry) => {
2177
2238
  const idx = result.discovered.findIndex((d) => d.id === entry.id);
2178
2239
  if (idx >= 0) {
2179
- logger14.debug(
2240
+ logger15.debug(
2180
2241
  `[engine-discovery] engine id '${entry.id}' from '${entry.source}' overrides earlier discovery from '${result.discovered[idx].source}' \u2014 same-id override`
2181
2242
  );
2182
2243
  result.discovered[idx] = entry;
@@ -2188,13 +2249,13 @@ function scanEngines(opts) {
2188
2249
  const inspection = inspectEnginePackage(pkgDir, source, opts.hostKind, env);
2189
2250
  if (inspection.status === "skip") {
2190
2251
  if (!inspection.reason.includes("not an engine package")) {
2191
- logger14.warn(`[engine-discovery] skipping ${pkgDir} (${source}): ${inspection.reason}`);
2252
+ logger15.warn(`[engine-discovery] skipping ${pkgDir} (${source}): ${inspection.reason}`);
2192
2253
  result.skipped.push({ pkgDir, reason: inspection.reason });
2193
2254
  }
2194
2255
  return;
2195
2256
  }
2196
2257
  if (inspection.status === "unusable") {
2197
- logger14.warn(`[engine-discovery] engine unavailable: ${inspection.reason}`);
2258
+ logger15.warn(`[engine-discovery] engine unavailable: ${inspection.reason}`);
2198
2259
  result.unusable.push({ pkgDir, id: inspection.id, reason: inspection.reason });
2199
2260
  return;
2200
2261
  }
@@ -2219,7 +2280,7 @@ function scanEngines(opts) {
2219
2280
  const command = resolveExplicitCommand(entry.command, env);
2220
2281
  if (command === void 0) {
2221
2282
  const reason = `engine '${id}' (config.json engines.${id}): command '${entry.command}' not found or not executable`;
2222
- logger14.warn(`[engine-discovery] ${reason} \u2014 not registering`);
2283
+ logger15.warn(`[engine-discovery] ${reason} \u2014 not registering`);
2223
2284
  result.unusable.push({ pkgDir: `config.json engines.${id}`, id, reason });
2224
2285
  continue;
2225
2286
  }
@@ -2236,7 +2297,7 @@ function discoverAndRegisterEngines(opts) {
2236
2297
  registerEngineDescriptor(entry.id, entry.descriptor);
2237
2298
  loadedDiscoveryIdSet.add(entry.id);
2238
2299
  if (existed) {
2239
- logger14.debug(
2300
+ logger15.debug(
2240
2301
  `[engine-discovery] engine '${entry.id}' descriptor overwritten in registry (source '${entry.source}') \u2014 same-id override`
2241
2302
  );
2242
2303
  }
@@ -2253,20 +2314,20 @@ function ensureEngineDiscovered(id, opts) {
2253
2314
  }
2254
2315
  function resolveExplicitCommand(command, env) {
2255
2316
  if (command.includes("/") || command.includes("\\")) {
2256
- const p = path5.isAbsolute(command) ? command : path5.resolve(command);
2317
+ const p = path6.isAbsolute(command) ? command : path6.resolve(command);
2257
2318
  return canExecute(p) ? p : void 0;
2258
2319
  }
2259
2320
  const pathVar = env["PATH"] ?? env["Path"] ?? "";
2260
- for (const dir of pathVar.split(path5.delimiter)) {
2321
+ for (const dir of pathVar.split(path6.delimiter)) {
2261
2322
  if (dir === "") continue;
2262
- const p = path5.join(dir, command);
2323
+ const p = path6.join(dir, command);
2263
2324
  if (canExecute(p)) return p;
2264
2325
  }
2265
2326
  return void 0;
2266
2327
  }
2267
2328
  function buildExplicitDescriptor(id, command, entry, opts) {
2268
2329
  const env = opts.env ?? process.env;
2269
- logger14.debug(
2330
+ logger15.debug(
2270
2331
  `[engine-discovery] engine '${id}' registered from config.json engines section with conservative capabilities (no manifest)`
2271
2332
  );
2272
2333
  const caps = { ...CONSERVATIVE_CAPABILITIES };