@zhushanwen/subagent-core 0.7.1 → 0.8.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 (28) hide show
  1. package/dist/{chunk-VURUAGMM.js → chunk-OL5BK4VN.js} +237 -40
  2. package/dist/{engine-discovery-scan-ocpM8FmI.d.cts → engine-discovery-scan-BXLA8y-z.d.cts} +17 -1
  3. package/dist/{engine-discovery-scan-ocpM8FmI.d.ts → engine-discovery-scan-BXLA8y-z.d.ts} +17 -1
  4. package/dist/execution/engine/engine-discovery-scan.cjs +150 -40
  5. package/dist/execution/engine/engine-discovery-scan.d.cts +1 -1
  6. package/dist/execution/engine/engine-discovery-scan.d.ts +1 -1
  7. package/dist/execution/engine/engine-discovery-scan.js +1 -1
  8. package/dist/index.cjs +278 -225
  9. package/dist/index.d.cts +24 -4
  10. package/dist/index.d.ts +24 -4
  11. package/dist/index.js +188 -304
  12. package/dist.bundle/index.cjs +279 -226
  13. package/package.json +3 -3
  14. package/src/__tests__/record-store-last-line.test.ts +12 -0
  15. package/src/core/logger.ts +1 -1
  16. package/src/execution/__tests__/inflight-production-wiring.test.ts +223 -0
  17. package/src/execution/__tests__/nested-visibility.test.ts +13 -1
  18. package/src/execution/engine/__tests__/conformance/registry-fork-filter.test.ts +55 -53
  19. package/src/execution/engine/__tests__/inflight-snapshot.test.ts +180 -0
  20. package/src/execution/engine/client/engine-client.ts +30 -1
  21. package/src/execution/engine/host/host-bridge.ts +7 -0
  22. package/src/execution/engine/host/spawned-children.ts +1 -1
  23. package/src/execution/engine/inflight-snapshot.ts +92 -0
  24. package/src/execution/engine/port.ts +18 -0
  25. package/src/execution/path-encoding.ts +6 -0
  26. package/src/execution/subagent-service.ts +25 -0
  27. package/src/execution/worktree-registry.ts +11 -7
  28. package/src/index.ts +11 -1
@@ -367,16 +367,16 @@ var SpawnedChildrenMirror = class {
367
367
  return this.entries.size;
368
368
  }
369
369
  /** 订阅状态广播(W6 notify/谓词接线点)。返回退订函数。 */
370
- onChange(listener) {
371
- this.listeners.add(listener);
370
+ onChange(listener2) {
371
+ this.listeners.add(listener2);
372
372
  return () => {
373
- this.listeners.delete(listener);
373
+ this.listeners.delete(listener2);
374
374
  };
375
375
  }
376
376
  emit(event) {
377
- for (const listener of this.listeners) {
377
+ for (const listener2 of this.listeners) {
378
378
  try {
379
- listener(event);
379
+ listener2(event);
380
380
  } catch (err) {
381
381
  logger3.debug(
382
382
  `[spawned-children-mirror] listener threw for ${event.reason}: ${err instanceof Error ? err.message : String(err)}`
@@ -831,8 +831,189 @@ function waitForChildExit(child, timeoutMs) {
831
831
  });
832
832
  }
833
833
 
834
+ // src/execution/engine/host/spawned-children.ts
835
+ var CoreSpawnedChildrenMirror = class {
836
+ entries = /* @__PURE__ */ new Map();
837
+ /** 落项 / 覆盖(同 recordId 重 spawn = 覆盖旧句柄,与引擎侧 Map 同语义)。 */
838
+ register(recordId, child) {
839
+ this.entries.set(recordId, {
840
+ pid: child.pid,
841
+ killed: child.killed,
842
+ updatedAt: Date.now()
843
+ });
844
+ }
845
+ /** 单项置死(杀链入口:killRecordChildWithEscalation 途经)。 */
846
+ markKilled(recordId) {
847
+ const entry = this.entries.get(recordId);
848
+ if (entry !== void 0) {
849
+ entry.killed = true;
850
+ entry.updatedAt = Date.now();
851
+ }
852
+ }
853
+ /** 整体置死 + 清空(失效语义 2+3:引擎 exit / 重建 / dispose / killAll)。 */
854
+ killAll() {
855
+ this.entries.clear();
856
+ }
857
+ getChildByRecord(recordId) {
858
+ return this.entries.get(recordId);
859
+ }
860
+ /** 句柄存活谓词(镜像面;判据与引擎侧 `!child.killed` 同构)。 */
861
+ hasLiveProcessHandle(recordId) {
862
+ const entry = this.entries.get(recordId);
863
+ return entry !== void 0 && !entry.killed;
864
+ }
865
+ /** 快照(诊断/测试)。 */
866
+ snapshot() {
867
+ return [...this.entries.entries()].map(([recordId, entry]) => ({ recordId, ...entry }));
868
+ }
869
+ /** 测试隔离专用(生产禁用——进程级全局状态)。 */
870
+ clear() {
871
+ this.entries.clear();
872
+ }
873
+ };
874
+ var MIRROR_SLOT_KEY = /* @__PURE__ */ Symbol.for(
875
+ "@zhushanwen/pi-subagent-workflow.coreSpawnedChildrenMirror"
876
+ );
877
+ function coreMirrorSlot() {
878
+ let slot = Reflect.get(globalThis, MIRROR_SLOT_KEY);
879
+ if (!slot) {
880
+ slot = new CoreSpawnedChildrenMirror();
881
+ Reflect.set(globalThis, MIRROR_SLOT_KEY, slot);
882
+ }
883
+ return slot;
884
+ }
885
+ function coreSpawnedChildrenMirror() {
886
+ return coreMirrorSlot();
887
+ }
888
+ function registerSpawnedChildForRecord(recordId, child) {
889
+ coreMirrorSlot().register(recordId, { pid: child.pid, killed: child.killed });
890
+ }
891
+ function killRecordChildWithEscalation(recordId, _source) {
892
+ coreMirrorSlot().markKilled(recordId);
893
+ }
894
+ function killAllSpawnedChildren(_signal = "SIGTERM") {
895
+ const before = coreMirrorSlot().snapshot().length;
896
+ coreMirrorSlot().killAll();
897
+ return before;
898
+ }
899
+ function hasLiveProcessHandleCore(recordId) {
900
+ return coreMirrorSlot().hasLiveProcessHandle(recordId);
901
+ }
902
+
903
+ // src/shared/timer-delay.ts
904
+ var MAX_TIMER_DELAY_MS = 2147483647;
905
+ function assertSafeTimerDelay(ms, source) {
906
+ if (!Number.isFinite(ms)) {
907
+ throw new Error(
908
+ `[subagent-workflow] ${source} = ${ms} is not a finite number (NaN/\xB1Infinity). Non-finite delays collapse to 1ms in Node setTimeout and fire immediately. Recovery: fix the upstream computation that produced this value (e.g. guard division/parse results before passing them in) and retry.`
909
+ );
910
+ }
911
+ if (ms > MAX_TIMER_DELAY_MS) {
912
+ throw new Error(
913
+ `[subagent-workflow] ${source} = ${ms} exceeds the Node setTimeout limit (${MAX_TIMER_DELAY_MS} ms = 2^31-1); larger delays silently collapse to 1ms and fire immediately. Recovery: clamp the value to <= ${MAX_TIMER_DELAY_MS} (e.g. omit the option for "unlimited" semantics, or clamp explicitly) and retry.`
914
+ );
915
+ }
916
+ }
917
+
918
+ // src/execution/lifecycle-manager.ts
919
+ var logger8 = getLogger("subagents");
920
+ var MS_PER_SECOND = 1e3;
921
+ var SECONDS_PER_MINUTE = 60;
922
+ var IDLE_TIMEOUT_MINUTES = 5;
923
+ var DEFAULT_IDLE_TIMEOUT_MS = IDLE_TIMEOUT_MINUTES * SECONDS_PER_MINUTE * MS_PER_SECOND;
924
+ function getEnvIdleTimeoutMs() {
925
+ const raw = process.env.XYZ_SUBAGENT_IDLE_TIMEOUT_MS;
926
+ if (!raw) return void 0;
927
+ const parsed = Number(raw);
928
+ if (!Number.isFinite(parsed) || parsed <= 0) {
929
+ logger8.warn(
930
+ `[lifecycle-manager] XYZ_SUBAGENT_IDLE_TIMEOUT_MS="${raw}" is invalid (expected a positive millisecond number) \u2014 falling back to DEFAULT_IDLE_TIMEOUT_MS (${DEFAULT_IDLE_TIMEOUT_MS}ms); set a plain ms value (e.g. 1800000) to override`
931
+ );
932
+ return void 0;
933
+ }
934
+ return parsed;
935
+ }
936
+ var idleTimers = /* @__PURE__ */ new Map();
937
+ function armIdleTimer(recordId, onTimeout, timeoutMs) {
938
+ if (timeoutMs !== void 0 && timeoutMs <= 0) {
939
+ disarmIdleTimer(recordId);
940
+ return;
941
+ }
942
+ const resolved = timeoutMs ?? getEnvIdleTimeoutMs() ?? DEFAULT_IDLE_TIMEOUT_MS;
943
+ assertSafeTimerDelay(resolved, "idleTimeoutMs");
944
+ disarmIdleTimer(recordId);
945
+ const timer = setTimeout(() => {
946
+ if (idleTimers.get(recordId)?.timer === timer) {
947
+ idleTimers.delete(recordId);
948
+ }
949
+ onTimeout();
950
+ }, resolved);
951
+ timer.unref?.();
952
+ idleTimers.set(recordId, { timer, timeoutMs: resolved });
953
+ }
954
+ function disarmIdleTimer(recordId) {
955
+ const entry = idleTimers.get(recordId);
956
+ if (!entry) {
957
+ return;
958
+ }
959
+ clearTimeout(entry.timer);
960
+ idleTimers.delete(recordId);
961
+ }
962
+ function hasIdleTimer(recordId) {
963
+ return idleTimers.has(recordId);
964
+ }
965
+ var ACTIVATE_LOCK_TIMEOUT_SECONDS = 30;
966
+ var ACTIVATE_LOCK_TIMEOUT_MS = ACTIVATE_LOCK_TIMEOUT_SECONDS * MS_PER_SECOND;
967
+
968
+ // src/execution/lifecycle-predicates.ts
969
+ function hasLiveProcessHandle(recordId) {
970
+ return hasLiveProcessHandleCore(recordId);
971
+ }
972
+ function isIdle(record) {
973
+ return hasIdleTimer(record.id);
974
+ }
975
+ function isResumable(record) {
976
+ return record.status === "running" && !hasLiveProcessHandle(record.id);
977
+ }
978
+
979
+ // src/execution/engine/inflight-snapshot.ts
980
+ var listener = null;
981
+ function setInFlightListener(next) {
982
+ listener = next;
983
+ }
984
+ function getInFlightSnapshot() {
985
+ return { inFlight: countInFlight() };
986
+ }
987
+ function notifyInFlightChanged() {
988
+ if (listener === null) return;
989
+ try {
990
+ listener(getInFlightSnapshot());
991
+ } catch {
992
+ }
993
+ }
994
+ function countInFlight() {
995
+ let count = 0;
996
+ for (const entry of coreSpawnedChildrenMirror().snapshot()) {
997
+ const recordId = entry.recordId;
998
+ if (hasLiveProcessHandle(recordId) && !hasIdleTimer(recordId)) count++;
999
+ }
1000
+ return count;
1001
+ }
1002
+
834
1003
  // src/execution/engine/client/engine-client.ts
835
- var logger8 = getLogger7("subagents");
1004
+ var logger9 = getLogger7("subagents");
1005
+ function bridgeMirrorEventToCoreMirror(event, mirror) {
1006
+ if (event.recordId === void 0 || event.pid === void 0) return;
1007
+ const entry = mirror.getEntry(event.pid);
1008
+ if (entry === void 0) return;
1009
+ const core = coreSpawnedChildrenMirror();
1010
+ if (entry.killed) {
1011
+ core.markKilled(event.recordId);
1012
+ } else {
1013
+ core.register(event.recordId, { pid: entry.pid, killed: false });
1014
+ }
1015
+ notifyInFlightChanged();
1016
+ }
836
1017
  var DISPOSE_GRACE_MS = 3e3;
837
1018
  var SIGKILL_REAP_TIMEOUT_MS = 1e4;
838
1019
  var FRAME_ECHO_MAX_CHARS = 200;
@@ -895,6 +1076,7 @@ var EngineClient = class {
895
1076
  };
896
1077
  this.mirror.onChange((event) => {
897
1078
  opts.onMirrorChanged?.(event);
1079
+ bridgeMirrorEventToCoreMirror(event, this.mirror);
898
1080
  });
899
1081
  }
900
1082
  get currentState() {
@@ -957,19 +1139,19 @@ var EngineClient = class {
957
1139
  matchesEngineCmdline: this.opts.engineCmdlineMatcher ?? defaultEngineCmdlineMatcher(this.opts.command)
958
1140
  });
959
1141
  if (sweep.killed.length > 0) {
960
- logger8.warn(
1142
+ logger9.warn(
961
1143
  `[engine-client:${this.engineId}] startup sweep killed stale orphan engine pids: ${sweep.killed.join(",")}`
962
1144
  );
963
1145
  }
964
1146
  for (const removed of sweep.removed) {
965
- logger8.debug(`[engine-client:${this.engineId}] swept stale pidfile ${removed.file}: ${removed.reason}`);
1147
+ logger9.debug(`[engine-client:${this.engineId}] swept stale pidfile ${removed.file}: ${removed.reason}`);
966
1148
  }
967
1149
  }
968
1150
  let attempt = 0;
969
1151
  while (attempt <= CRASH_REBUILD_MAX_ATTEMPTS) {
970
1152
  if (attempt > 0) {
971
1153
  const backoff = CRASH_REBUILD_BACKOFF_MS[attempt - 1];
972
- logger8.warn(
1154
+ logger9.warn(
973
1155
  `[engine-client:${this.engineId}] rebuild attempt ${attempt}/${CRASH_REBUILD_MAX_ATTEMPTS} after ${backoff}ms backoff`
974
1156
  );
975
1157
  await delay(backoff);
@@ -1002,7 +1184,7 @@ var EngineClient = class {
1002
1184
  markUnavailable(reason) {
1003
1185
  this.state = "unavailable";
1004
1186
  this.unavailableReason = reason;
1005
- logger8.error(`[engine-client:${this.engineId}] marked unavailable: ${reason.message}`);
1187
+ logger9.error(`[engine-client:${this.engineId}] marked unavailable: ${reason.message}`);
1006
1188
  }
1007
1189
  /** spawn 引擎 CLI + initialize 握手 + 版本协商 + 诊断留痕。 */
1008
1190
  async spawnAndInitialize() {
@@ -1032,13 +1214,13 @@ var EngineClient = class {
1032
1214
  });
1033
1215
  const child = this.child;
1034
1216
  child.on("error", (err) => {
1035
- logger8.warn(`[engine-client:${this.engineId}] spawn error: ${err.message}`);
1217
+ logger9.warn(`[engine-client:${this.engineId}] spawn error: ${err.message}`);
1036
1218
  this.appendStderrTail(`spawn error: ${err.message}
1037
1219
  `);
1038
1220
  });
1039
1221
  for (const pipe of ["stdin", "stdout", "stderr"]) {
1040
1222
  child[pipe]?.on("error", (err) => {
1041
- logger8.debug(`[engine-client:${this.engineId}] ${pipe} pipe error (engine died concurrently = expected): ${err.message}`);
1223
+ logger9.debug(`[engine-client:${this.engineId}] ${pipe} pipe error (engine died concurrently = expected): ${err.message}`);
1042
1224
  });
1043
1225
  }
1044
1226
  child.stdout?.setEncoding("utf-8");
@@ -1107,7 +1289,7 @@ var EngineClient = class {
1107
1289
  try {
1108
1290
  frame = JSON.parse(line);
1109
1291
  } catch {
1110
- logger8.warn(
1292
+ logger9.warn(
1111
1293
  `[engine-client:${this.engineId}] dropped non-NDJSON stdout line (protocol contract: stdout is NDJSON-only): ${truncate(line, FRAME_ECHO_MAX_CHARS)}`
1112
1294
  );
1113
1295
  return;
@@ -1124,7 +1306,7 @@ var EngineClient = class {
1124
1306
  this.onReverseRequest(frame);
1125
1307
  return;
1126
1308
  }
1127
- logger8.warn(
1309
+ logger9.warn(
1128
1310
  `[engine-client:${this.engineId}] dropped unrecognized frame: ${truncate(line, FRAME_ECHO_MAX_CHARS)}`
1129
1311
  );
1130
1312
  }
@@ -1248,7 +1430,7 @@ var EngineClient = class {
1248
1430
  try {
1249
1431
  await this.request("dispose", {}, { timeoutMs: DISPOSE_GRACE_MS });
1250
1432
  } catch (err) {
1251
- logger8.debug(
1433
+ logger9.debug(
1252
1434
  `[engine-client:${this.engineId}] dispose frame failed, falling back to kill chain: ${err instanceof Error ? err.message : String(err)}`
1253
1435
  );
1254
1436
  }
@@ -1260,9 +1442,9 @@ var EngineClient = class {
1260
1442
  onEngineExit(code, signal) {
1261
1443
  const detail = signal !== null ? `signal ${signal}` : `exit code ${code}`;
1262
1444
  if (this.intentionalKill) {
1263
- logger8.debug(`[engine-client:${this.engineId}] engine exited after intentional kill (${detail}) \u2014 expected`);
1445
+ logger9.debug(`[engine-client:${this.engineId}] engine exited after intentional kill (${detail}) \u2014 expected`);
1264
1446
  } else {
1265
- logger8.warn(`[engine-client:${this.engineId}] engine process exited unexpectedly (${detail})`);
1447
+ logger9.warn(`[engine-client:${this.engineId}] engine process exited unexpectedly (${detail})`);
1266
1448
  }
1267
1449
  this.teardownProcess(detail);
1268
1450
  this.state = "exited";
@@ -1311,7 +1493,7 @@ var EngineClient = class {
1311
1493
  `);
1312
1494
  return true;
1313
1495
  } catch (err) {
1314
- logger8.debug(
1496
+ logger9.debug(
1315
1497
  `[engine-client:${this.engineId}] stdin write failed: ${err instanceof Error ? err.message : String(err)}`
1316
1498
  );
1317
1499
  return false;
@@ -1695,7 +1877,7 @@ function getHostUiRequestEndpoint() {
1695
1877
  }
1696
1878
 
1697
1879
  // src/execution/engine/engine-inspect-package.ts
1698
- var logger9 = getLogger("subagents");
1880
+ var logger10 = getLogger("subagents");
1699
1881
  function readEnginePackageJson(pkgDir) {
1700
1882
  let raw;
1701
1883
  try {
@@ -1774,12 +1956,12 @@ function parseOptionalDisplayFields(m, id) {
1774
1956
  if (typeof rawDisplayName === "string" && rawDisplayName.trim() !== "") {
1775
1957
  displayName = rawDisplayName;
1776
1958
  } else {
1777
- logger9.warn(`[engine-discovery] engine '${id}': displayName must be a non-empty string \u2014 ignoring`);
1959
+ logger10.warn(`[engine-discovery] engine '${id}': displayName must be a non-empty string \u2014 ignoring`);
1778
1960
  }
1779
1961
  }
1780
1962
  const rawDescription = m["description"];
1781
1963
  if (rawDescription !== void 0 && typeof rawDescription !== "string") {
1782
- logger9.warn(`[engine-discovery] engine '${id}': description must be a string \u2014 ignoring`);
1964
+ logger10.warn(`[engine-discovery] engine '${id}': description must be a string \u2014 ignoring`);
1783
1965
  }
1784
1966
  return displayName;
1785
1967
  }
@@ -1868,7 +2050,7 @@ function errorMessage(err) {
1868
2050
  // src/execution/engine/engine-discovery-roots.ts
1869
2051
  import * as fs2 from "fs";
1870
2052
  import * as path3 from "path";
1871
- var logger10 = getLogger("subagents");
2053
+ var logger11 = getLogger("subagents");
1872
2054
  var ENGINE_ROOTS_ENV = "XYZ_AGENT_ENGINE_ROOTS";
1873
2055
  function parseEngineRootsEnv(env) {
1874
2056
  const raw = env[ENGINE_ROOTS_ENV];
@@ -1879,7 +2061,7 @@ function parseEngineRootsEnv(env) {
1879
2061
  const dir = part.trim();
1880
2062
  if (dir === "") continue;
1881
2063
  if (!path3.isAbsolute(dir)) {
1882
- logger10.warn(
2064
+ logger11.warn(
1883
2065
  `[engine-discovery] ${ENGINE_ROOTS_ENV} entry '${dir}' is not an absolute path \u2014 dropping entry`
1884
2066
  );
1885
2067
  continue;
@@ -1948,7 +2130,7 @@ function isFile(p) {
1948
2130
  // src/execution/engine/config.ts
1949
2131
  import * as fs3 from "fs";
1950
2132
  import * as path4 from "path";
1951
- var logger11 = getLogger("subagents");
2133
+ var logger12 = getLogger("subagents");
1952
2134
  function readExplicitEngines(agentDir) {
1953
2135
  const configPath = path4.join(agentDir, "subagents", "config.json");
1954
2136
  let parsed;
@@ -1961,7 +2143,7 @@ function readExplicitEngines(agentDir) {
1961
2143
  const engines = parsed["engines"];
1962
2144
  if (typeof engines !== "object" || engines === null || Array.isArray(engines)) {
1963
2145
  if (engines !== void 0) {
1964
- logger11.warn(
2146
+ logger12.warn(
1965
2147
  `[engine-discovery] config.json engines section must be an object keyed by engine id, got ${jsonKindOf(engines)} \u2014 ignoring L3 explicit engines`
1966
2148
  );
1967
2149
  }
@@ -1989,7 +2171,7 @@ function sanitizeExplicitEntry(id, raw) {
1989
2171
  }
1990
2172
  function requireEntryObject(id, raw) {
1991
2173
  if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
1992
- logger11.warn(
2174
+ logger12.warn(
1993
2175
  `[engine-discovery] config.json engines.${id} must be an object, got ${jsonKindOf(raw)} \u2014 skipping entry`
1994
2176
  );
1995
2177
  return void 0;
@@ -1998,7 +2180,7 @@ function requireEntryObject(id, raw) {
1998
2180
  }
1999
2181
  function requireCommand(id, command) {
2000
2182
  if (typeof command !== "string" || command.trim() === "") {
2001
- logger11.warn(
2183
+ logger12.warn(
2002
2184
  `[engine-discovery] config.json engines.${id}.command is required (non-empty string) \u2014 skipping entry`
2003
2185
  );
2004
2186
  return void 0;
@@ -2007,7 +2189,7 @@ function requireCommand(id, command) {
2007
2189
  }
2008
2190
  function normalizeArgs(id, args) {
2009
2191
  if (args !== void 0 && !isStringArray(args)) {
2010
- logger11.warn(
2192
+ logger12.warn(
2011
2193
  `[engine-discovery] config.json engines.${id}.args must be a string array \u2014 ignoring args`
2012
2194
  );
2013
2195
  }
@@ -2015,7 +2197,7 @@ function normalizeArgs(id, args) {
2015
2197
  }
2016
2198
  function normalizeConfig(id, config) {
2017
2199
  if (config !== void 0 && !isStringRecord(config)) {
2018
- logger11.warn(
2200
+ logger12.warn(
2019
2201
  `[engine-discovery] config.json engines.${id}.config must be a Record<string, string> \u2014 ignoring config`
2020
2202
  );
2021
2203
  }
@@ -2023,7 +2205,7 @@ function normalizeConfig(id, config) {
2023
2205
  }
2024
2206
  function normalizeCwd(id, cwd) {
2025
2207
  if (cwd !== void 0 && typeof cwd !== "string") {
2026
- logger11.warn(
2208
+ logger12.warn(
2027
2209
  `[engine-discovery] config.json engines.${id}.cwd must be a string \u2014 ignoring cwd`
2028
2210
  );
2029
2211
  }
@@ -2043,7 +2225,7 @@ function jsonKindOf(value) {
2043
2225
  }
2044
2226
 
2045
2227
  // src/execution/engine/registry.ts
2046
- var logger12 = getLogger("subagents");
2228
+ var logger13 = getLogger("subagents");
2047
2229
  var DEFAULT_ENGINE_ID = "pi";
2048
2230
  function normalizeEngineId(engine) {
2049
2231
  return engine?.trim() || DEFAULT_ENGINE_ID;
@@ -2083,12 +2265,12 @@ function triggerEngineDispose(engine, source) {
2083
2265
  if (typeof engine.dispose !== "function") return;
2084
2266
  try {
2085
2267
  engine.dispose().then(void 0, (err) => {
2086
- logger12.warn(
2268
+ logger13.warn(
2087
2269
  `[engine-registry] engine '${engine.id}' dispose rejected (${source}, best-effort continue): ${err instanceof Error ? err.message : String(err)}`
2088
2270
  );
2089
2271
  });
2090
2272
  } catch (err) {
2091
- logger12.warn(
2273
+ logger13.warn(
2092
2274
  `[engine-registry] engine '${engine.id}' dispose threw synchronously (${source}, best-effort continue): ${err instanceof Error ? err.message : String(err)}`
2093
2275
  );
2094
2276
  }
@@ -2131,14 +2313,14 @@ function listEnginesByDisplayName() {
2131
2313
  }
2132
2314
 
2133
2315
  // src/execution/engine/engine-discovery-scan.ts
2134
- var logger13 = getLogger("subagents");
2316
+ var logger14 = getLogger("subagents");
2135
2317
  function scanEngines(opts) {
2136
2318
  const env = opts.env ?? process.env;
2137
2319
  const result = { discovered: [], skipped: [], unusable: [] };
2138
2320
  const present = (entry) => {
2139
2321
  const idx = result.discovered.findIndex((d) => d.id === entry.id);
2140
2322
  if (idx >= 0) {
2141
- logger13.debug(
2323
+ logger14.debug(
2142
2324
  `[engine-discovery] engine id '${entry.id}' from '${entry.source}' overrides earlier discovery from '${result.discovered[idx].source}' \u2014 same-id override`
2143
2325
  );
2144
2326
  result.discovered[idx] = entry;
@@ -2150,13 +2332,13 @@ function scanEngines(opts) {
2150
2332
  const inspection = inspectEnginePackage(pkgDir, source, opts.hostKind, env);
2151
2333
  if (inspection.status === "skip") {
2152
2334
  if (!inspection.reason.includes("not an engine package")) {
2153
- logger13.warn(`[engine-discovery] skipping ${pkgDir} (${source}): ${inspection.reason}`);
2335
+ logger14.warn(`[engine-discovery] skipping ${pkgDir} (${source}): ${inspection.reason}`);
2154
2336
  result.skipped.push({ pkgDir, reason: inspection.reason });
2155
2337
  }
2156
2338
  return;
2157
2339
  }
2158
2340
  if (inspection.status === "unusable") {
2159
- logger13.warn(`[engine-discovery] engine unavailable: ${inspection.reason}`);
2341
+ logger14.warn(`[engine-discovery] engine unavailable: ${inspection.reason}`);
2160
2342
  result.unusable.push({ pkgDir, id: inspection.id, reason: inspection.reason });
2161
2343
  return;
2162
2344
  }
@@ -2181,7 +2363,7 @@ function scanEngines(opts) {
2181
2363
  const command = resolveExplicitCommand(entry.command, env);
2182
2364
  if (command === void 0) {
2183
2365
  const reason = `engine '${id}' (config.json engines.${id}): command '${entry.command}' not found or not executable`;
2184
- logger13.warn(`[engine-discovery] ${reason} \u2014 not registering`);
2366
+ logger14.warn(`[engine-discovery] ${reason} \u2014 not registering`);
2185
2367
  result.unusable.push({ pkgDir: `config.json engines.${id}`, id, reason });
2186
2368
  continue;
2187
2369
  }
@@ -2198,7 +2380,7 @@ function discoverAndRegisterEngines(opts) {
2198
2380
  registerEngineDescriptor(entry.id, entry.descriptor);
2199
2381
  loadedDiscoveryIdSet.add(entry.id);
2200
2382
  if (existed) {
2201
- logger13.debug(
2383
+ logger14.debug(
2202
2384
  `[engine-discovery] engine '${entry.id}' descriptor overwritten in registry (source '${entry.source}') \u2014 same-id override`
2203
2385
  );
2204
2386
  }
@@ -2228,7 +2410,7 @@ function resolveExplicitCommand(command, env) {
2228
2410
  }
2229
2411
  function buildExplicitDescriptor(id, command, entry, opts) {
2230
2412
  const env = opts.env ?? process.env;
2231
- logger13.debug(
2413
+ logger14.debug(
2232
2414
  `[engine-discovery] engine '${id}' registered from config.json engines section with conservative capabilities (no manifest)`
2233
2415
  );
2234
2416
  const caps = { ...CONSERVATIVE_CAPABILITIES };
@@ -2290,6 +2472,21 @@ export {
2290
2472
  parseEnvPrefixes,
2291
2473
  parseModelCatalog,
2292
2474
  getEngineDataDir,
2475
+ registerSpawnedChildForRecord,
2476
+ killRecordChildWithEscalation,
2477
+ killAllSpawnedChildren,
2478
+ MAX_TIMER_DELAY_MS,
2479
+ assertSafeTimerDelay,
2480
+ DEFAULT_IDLE_TIMEOUT_MS,
2481
+ armIdleTimer,
2482
+ disarmIdleTimer,
2483
+ hasIdleTimer,
2484
+ hasLiveProcessHandle,
2485
+ isIdle,
2486
+ isResumable,
2487
+ setInFlightListener,
2488
+ getInFlightSnapshot,
2489
+ notifyInFlightChanged,
2293
2490
  EngineClient,
2294
2491
  assertTaskShapeSupported,
2295
2492
  RemoteEngine,
@@ -1413,6 +1413,22 @@ interface EnginePort {
1413
1413
  * Promise 前完成;grace→SIGKILL 升级序列属异步面(promise 段)。
1414
1414
  */
1415
1415
  dispose?(): Promise<void>;
1416
+ /**
1417
+ * [u7a D5] 可选面:引擎在途任务只读快照(滚动重启推迟谓词的引擎侧输入——
1418
+ * 权威源:docs/design/crash-forensics-and-watchdog.md §3.3 D5「推迟判定源 =
1419
+ * relay ∪ 引擎池在途 ∪ pi 侧 extension 聚合上报」)。同步纯读、无副作用。
1420
+ *
1421
+ * 返回 null = 引擎不提供快照;成员缺席(undefined,pi 引擎不实现)= 无引擎侧
1422
+ * 在途面——pi 形态的在途由 subagent-workflow extension 聚合上报覆盖(EnginePort
1423
+ * 之外的第 4 通道,两通道互不替代)。可选成员保持向后兼容(port.ts 既有扩展
1424
+ * 先例:listModels / validateModel / dispose 全为可选成员)。
1425
+ *
1426
+ * zcode 实现语义(显式裁决):在途 = activeSessions 非空——poolKey 'shared' 的
1427
+ * app-server 空闲常驻进程恒活,**禁止按进程存在判定**(会恒真、推迟常态化)。
1428
+ */
1429
+ inFlightSnapshot?(): {
1430
+ inFlight: number;
1431
+ } | null;
1416
1432
  }
1417
1433
 
1418
1434
  /** 引擎工厂:惰性创建引擎实例(getEngine 首次取用时执行)。 */
@@ -1515,7 +1531,7 @@ declare function parseEngineRootsEnv(env: NodeJS.ProcessEnv): string[];
1515
1531
  */
1516
1532
  declare function deriveNodeModuleRoots(argvEntry?: string | undefined): string[];
1517
1533
 
1518
- /** 日志级别。对齐 @zhushanwen/pi-extension-logger LogLevel(三值,无 info)。 */
1534
+ /** 日志级别。级别集合对齐 pi-extension-logger(其 LogLevel 为包内部类型、含 info 四值;实例 API 仅 debug/warn/error 三方法,core facade 据此收窄为三值)。 */
1519
1535
  type LogLevel = "debug" | "warn" | "error";
1520
1536
  /** core logger 接口。与 pi-extension-logger 的 ExtensionLogger 结构兼容——
1521
1537
  * u0-log 批次替换是纯 import 源替换,调用面(方法名/参数序)逐文件等价。 */
@@ -1413,6 +1413,22 @@ interface EnginePort {
1413
1413
  * Promise 前完成;grace→SIGKILL 升级序列属异步面(promise 段)。
1414
1414
  */
1415
1415
  dispose?(): Promise<void>;
1416
+ /**
1417
+ * [u7a D5] 可选面:引擎在途任务只读快照(滚动重启推迟谓词的引擎侧输入——
1418
+ * 权威源:docs/design/crash-forensics-and-watchdog.md §3.3 D5「推迟判定源 =
1419
+ * relay ∪ 引擎池在途 ∪ pi 侧 extension 聚合上报」)。同步纯读、无副作用。
1420
+ *
1421
+ * 返回 null = 引擎不提供快照;成员缺席(undefined,pi 引擎不实现)= 无引擎侧
1422
+ * 在途面——pi 形态的在途由 subagent-workflow extension 聚合上报覆盖(EnginePort
1423
+ * 之外的第 4 通道,两通道互不替代)。可选成员保持向后兼容(port.ts 既有扩展
1424
+ * 先例:listModels / validateModel / dispose 全为可选成员)。
1425
+ *
1426
+ * zcode 实现语义(显式裁决):在途 = activeSessions 非空——poolKey 'shared' 的
1427
+ * app-server 空闲常驻进程恒活,**禁止按进程存在判定**(会恒真、推迟常态化)。
1428
+ */
1429
+ inFlightSnapshot?(): {
1430
+ inFlight: number;
1431
+ } | null;
1416
1432
  }
1417
1433
 
1418
1434
  /** 引擎工厂:惰性创建引擎实例(getEngine 首次取用时执行)。 */
@@ -1515,7 +1531,7 @@ declare function parseEngineRootsEnv(env: NodeJS.ProcessEnv): string[];
1515
1531
  */
1516
1532
  declare function deriveNodeModuleRoots(argvEntry?: string | undefined): string[];
1517
1533
 
1518
- /** 日志级别。对齐 @zhushanwen/pi-extension-logger LogLevel(三值,无 info)。 */
1534
+ /** 日志级别。级别集合对齐 pi-extension-logger(其 LogLevel 为包内部类型、含 info 四值;实例 API 仅 debug/warn/error 三方法,core facade 据此收窄为三值)。 */
1519
1535
  type LogLevel = "debug" | "warn" | "error";
1520
1536
  /** core logger 接口。与 pi-extension-logger 的 ExtensionLogger 结构兼容——
1521
1537
  * u0-log 批次替换是纯 import 源替换,调用面(方法名/参数序)逐文件等价。 */