@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
package/dist/index.cjs CHANGED
@@ -125,6 +125,7 @@ __export(src_exports, {
125
125
  getCachedFileContent: () => getCachedFileContent,
126
126
  getCachedParsed: () => getCachedParsed,
127
127
  getHostServices: () => getHostServices,
128
+ getInFlightSnapshot: () => getInFlightSnapshot,
128
129
  getLogger: () => getLogger,
129
130
  getModelConfigService: () => getModelConfigService,
130
131
  getOrCreateChannelRegistry: () => getOrCreateChannelRegistry,
@@ -182,6 +183,7 @@ __export(src_exports, {
182
183
  saveWorkflow: () => saveWorkflow,
183
184
  scheduleTimeBudget: () => scheduleTimeBudget,
184
185
  setEngineDiscoveryRescanOptions: () => setEngineDiscoveryRescanOptions,
186
+ setInFlightListener: () => setInFlightListener,
185
187
  setModelConfigService: () => setModelConfigService,
186
188
  setSubagentService: () => setSubagentService,
187
189
  sortByCodepoint: () => sortByCodepoint,
@@ -676,16 +678,16 @@ var SpawnedChildrenMirror = class {
676
678
  return this.entries.size;
677
679
  }
678
680
  /** 订阅状态广播(W6 notify/谓词接线点)。返回退订函数。 */
679
- onChange(listener) {
680
- this.listeners.add(listener);
681
+ onChange(listener2) {
682
+ this.listeners.add(listener2);
681
683
  return () => {
682
- this.listeners.delete(listener);
684
+ this.listeners.delete(listener2);
683
685
  };
684
686
  }
685
687
  emit(event) {
686
- for (const listener of this.listeners) {
688
+ for (const listener2 of this.listeners) {
687
689
  try {
688
- listener(event);
690
+ listener2(event);
689
691
  } catch (err) {
690
692
  logger4.debug(
691
693
  `[spawned-children-mirror] listener threw for ${event.reason}: ${err instanceof Error ? err.message : String(err)}`
@@ -1134,8 +1136,189 @@ function waitForChildExit(child, timeoutMs) {
1134
1136
  });
1135
1137
  }
1136
1138
 
1139
+ // src/execution/engine/host/spawned-children.ts
1140
+ var CoreSpawnedChildrenMirror = class {
1141
+ entries = /* @__PURE__ */ new Map();
1142
+ /** 落项 / 覆盖(同 recordId 重 spawn = 覆盖旧句柄,与引擎侧 Map 同语义)。 */
1143
+ register(recordId, child) {
1144
+ this.entries.set(recordId, {
1145
+ pid: child.pid,
1146
+ killed: child.killed,
1147
+ updatedAt: Date.now()
1148
+ });
1149
+ }
1150
+ /** 单项置死(杀链入口:killRecordChildWithEscalation 途经)。 */
1151
+ markKilled(recordId) {
1152
+ const entry = this.entries.get(recordId);
1153
+ if (entry !== void 0) {
1154
+ entry.killed = true;
1155
+ entry.updatedAt = Date.now();
1156
+ }
1157
+ }
1158
+ /** 整体置死 + 清空(失效语义 2+3:引擎 exit / 重建 / dispose / killAll)。 */
1159
+ killAll() {
1160
+ this.entries.clear();
1161
+ }
1162
+ getChildByRecord(recordId) {
1163
+ return this.entries.get(recordId);
1164
+ }
1165
+ /** 句柄存活谓词(镜像面;判据与引擎侧 `!child.killed` 同构)。 */
1166
+ hasLiveProcessHandle(recordId) {
1167
+ const entry = this.entries.get(recordId);
1168
+ return entry !== void 0 && !entry.killed;
1169
+ }
1170
+ /** 快照(诊断/测试)。 */
1171
+ snapshot() {
1172
+ return [...this.entries.entries()].map(([recordId, entry]) => ({ recordId, ...entry }));
1173
+ }
1174
+ /** 测试隔离专用(生产禁用——进程级全局状态)。 */
1175
+ clear() {
1176
+ this.entries.clear();
1177
+ }
1178
+ };
1179
+ var MIRROR_SLOT_KEY = /* @__PURE__ */ Symbol.for(
1180
+ "@zhushanwen/pi-subagent-workflow.coreSpawnedChildrenMirror"
1181
+ );
1182
+ function coreMirrorSlot() {
1183
+ let slot = Reflect.get(globalThis, MIRROR_SLOT_KEY);
1184
+ if (!slot) {
1185
+ slot = new CoreSpawnedChildrenMirror();
1186
+ Reflect.set(globalThis, MIRROR_SLOT_KEY, slot);
1187
+ }
1188
+ return slot;
1189
+ }
1190
+ function coreSpawnedChildrenMirror() {
1191
+ return coreMirrorSlot();
1192
+ }
1193
+ function registerSpawnedChildForRecord(recordId, child) {
1194
+ coreMirrorSlot().register(recordId, { pid: child.pid, killed: child.killed });
1195
+ }
1196
+ function killRecordChildWithEscalation(recordId, _source) {
1197
+ coreMirrorSlot().markKilled(recordId);
1198
+ }
1199
+ function killAllSpawnedChildren(_signal = "SIGTERM") {
1200
+ const before = coreMirrorSlot().snapshot().length;
1201
+ coreMirrorSlot().killAll();
1202
+ return before;
1203
+ }
1204
+ function hasLiveProcessHandleCore(recordId) {
1205
+ return coreMirrorSlot().hasLiveProcessHandle(recordId);
1206
+ }
1207
+
1208
+ // src/shared/timer-delay.ts
1209
+ var MAX_TIMER_DELAY_MS = 2147483647;
1210
+ function assertSafeTimerDelay(ms, source) {
1211
+ if (!Number.isFinite(ms)) {
1212
+ throw new Error(
1213
+ `[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.`
1214
+ );
1215
+ }
1216
+ if (ms > MAX_TIMER_DELAY_MS) {
1217
+ throw new Error(
1218
+ `[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.`
1219
+ );
1220
+ }
1221
+ }
1222
+
1223
+ // src/execution/lifecycle-manager.ts
1224
+ var logger9 = getLogger("subagents");
1225
+ var MS_PER_SECOND = 1e3;
1226
+ var SECONDS_PER_MINUTE = 60;
1227
+ var IDLE_TIMEOUT_MINUTES = 5;
1228
+ var DEFAULT_IDLE_TIMEOUT_MS = IDLE_TIMEOUT_MINUTES * SECONDS_PER_MINUTE * MS_PER_SECOND;
1229
+ function getEnvIdleTimeoutMs() {
1230
+ const raw = process.env.XYZ_SUBAGENT_IDLE_TIMEOUT_MS;
1231
+ if (!raw) return void 0;
1232
+ const parsed = Number(raw);
1233
+ if (!Number.isFinite(parsed) || parsed <= 0) {
1234
+ logger9.warn(
1235
+ `[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`
1236
+ );
1237
+ return void 0;
1238
+ }
1239
+ return parsed;
1240
+ }
1241
+ var idleTimers = /* @__PURE__ */ new Map();
1242
+ function armIdleTimer(recordId, onTimeout, timeoutMs) {
1243
+ if (timeoutMs !== void 0 && timeoutMs <= 0) {
1244
+ disarmIdleTimer(recordId);
1245
+ return;
1246
+ }
1247
+ const resolved = timeoutMs ?? getEnvIdleTimeoutMs() ?? DEFAULT_IDLE_TIMEOUT_MS;
1248
+ assertSafeTimerDelay(resolved, "idleTimeoutMs");
1249
+ disarmIdleTimer(recordId);
1250
+ const timer = setTimeout(() => {
1251
+ if (idleTimers.get(recordId)?.timer === timer) {
1252
+ idleTimers.delete(recordId);
1253
+ }
1254
+ onTimeout();
1255
+ }, resolved);
1256
+ timer.unref?.();
1257
+ idleTimers.set(recordId, { timer, timeoutMs: resolved });
1258
+ }
1259
+ function disarmIdleTimer(recordId) {
1260
+ const entry = idleTimers.get(recordId);
1261
+ if (!entry) {
1262
+ return;
1263
+ }
1264
+ clearTimeout(entry.timer);
1265
+ idleTimers.delete(recordId);
1266
+ }
1267
+ function hasIdleTimer(recordId) {
1268
+ return idleTimers.has(recordId);
1269
+ }
1270
+ var ACTIVATE_LOCK_TIMEOUT_SECONDS = 30;
1271
+ var ACTIVATE_LOCK_TIMEOUT_MS = ACTIVATE_LOCK_TIMEOUT_SECONDS * MS_PER_SECOND;
1272
+
1273
+ // src/execution/lifecycle-predicates.ts
1274
+ function hasLiveProcessHandle(recordId) {
1275
+ return hasLiveProcessHandleCore(recordId);
1276
+ }
1277
+ function isIdle(record) {
1278
+ return hasIdleTimer(record.id);
1279
+ }
1280
+ function isResumable(record) {
1281
+ return record.status === "running" && !hasLiveProcessHandle(record.id);
1282
+ }
1283
+
1284
+ // src/execution/engine/inflight-snapshot.ts
1285
+ var listener = null;
1286
+ function setInFlightListener(next) {
1287
+ listener = next;
1288
+ }
1289
+ function getInFlightSnapshot() {
1290
+ return { inFlight: countInFlight() };
1291
+ }
1292
+ function notifyInFlightChanged() {
1293
+ if (listener === null) return;
1294
+ try {
1295
+ listener(getInFlightSnapshot());
1296
+ } catch {
1297
+ }
1298
+ }
1299
+ function countInFlight() {
1300
+ let count = 0;
1301
+ for (const entry of coreSpawnedChildrenMirror().snapshot()) {
1302
+ const recordId = entry.recordId;
1303
+ if (hasLiveProcessHandle(recordId) && !hasIdleTimer(recordId)) count++;
1304
+ }
1305
+ return count;
1306
+ }
1307
+
1137
1308
  // src/execution/engine/client/engine-client.ts
1138
- var logger9 = (0, import_subagent_engine_sdk6.getLogger)("subagents");
1309
+ var logger10 = (0, import_subagent_engine_sdk6.getLogger)("subagents");
1310
+ function bridgeMirrorEventToCoreMirror(event, mirror) {
1311
+ if (event.recordId === void 0 || event.pid === void 0) return;
1312
+ const entry = mirror.getEntry(event.pid);
1313
+ if (entry === void 0) return;
1314
+ const core = coreSpawnedChildrenMirror();
1315
+ if (entry.killed) {
1316
+ core.markKilled(event.recordId);
1317
+ } else {
1318
+ core.register(event.recordId, { pid: entry.pid, killed: false });
1319
+ }
1320
+ notifyInFlightChanged();
1321
+ }
1139
1322
  var DISPOSE_GRACE_MS = 3e3;
1140
1323
  var SIGKILL_REAP_TIMEOUT_MS = 1e4;
1141
1324
  var FRAME_ECHO_MAX_CHARS = 200;
@@ -1198,6 +1381,7 @@ var EngineClient = class {
1198
1381
  };
1199
1382
  this.mirror.onChange((event) => {
1200
1383
  opts.onMirrorChanged?.(event);
1384
+ bridgeMirrorEventToCoreMirror(event, this.mirror);
1201
1385
  });
1202
1386
  }
1203
1387
  get currentState() {
@@ -1260,19 +1444,19 @@ var EngineClient = class {
1260
1444
  matchesEngineCmdline: this.opts.engineCmdlineMatcher ?? defaultEngineCmdlineMatcher(this.opts.command)
1261
1445
  });
1262
1446
  if (sweep.killed.length > 0) {
1263
- logger9.warn(
1447
+ logger10.warn(
1264
1448
  `[engine-client:${this.engineId}] startup sweep killed stale orphan engine pids: ${sweep.killed.join(",")}`
1265
1449
  );
1266
1450
  }
1267
1451
  for (const removed of sweep.removed) {
1268
- logger9.debug(`[engine-client:${this.engineId}] swept stale pidfile ${removed.file}: ${removed.reason}`);
1452
+ logger10.debug(`[engine-client:${this.engineId}] swept stale pidfile ${removed.file}: ${removed.reason}`);
1269
1453
  }
1270
1454
  }
1271
1455
  let attempt = 0;
1272
1456
  while (attempt <= import_subagent_engine_sdk6.CRASH_REBUILD_MAX_ATTEMPTS) {
1273
1457
  if (attempt > 0) {
1274
1458
  const backoff = import_subagent_engine_sdk6.CRASH_REBUILD_BACKOFF_MS[attempt - 1];
1275
- logger9.warn(
1459
+ logger10.warn(
1276
1460
  `[engine-client:${this.engineId}] rebuild attempt ${attempt}/${import_subagent_engine_sdk6.CRASH_REBUILD_MAX_ATTEMPTS} after ${backoff}ms backoff`
1277
1461
  );
1278
1462
  await delay(backoff);
@@ -1305,7 +1489,7 @@ var EngineClient = class {
1305
1489
  markUnavailable(reason) {
1306
1490
  this.state = "unavailable";
1307
1491
  this.unavailableReason = reason;
1308
- logger9.error(`[engine-client:${this.engineId}] marked unavailable: ${reason.message}`);
1492
+ logger10.error(`[engine-client:${this.engineId}] marked unavailable: ${reason.message}`);
1309
1493
  }
1310
1494
  /** spawn 引擎 CLI + initialize 握手 + 版本协商 + 诊断留痕。 */
1311
1495
  async spawnAndInitialize() {
@@ -1335,13 +1519,13 @@ var EngineClient = class {
1335
1519
  });
1336
1520
  const child = this.child;
1337
1521
  child.on("error", (err) => {
1338
- logger9.warn(`[engine-client:${this.engineId}] spawn error: ${err.message}`);
1522
+ logger10.warn(`[engine-client:${this.engineId}] spawn error: ${err.message}`);
1339
1523
  this.appendStderrTail(`spawn error: ${err.message}
1340
1524
  `);
1341
1525
  });
1342
1526
  for (const pipe of ["stdin", "stdout", "stderr"]) {
1343
1527
  child[pipe]?.on("error", (err) => {
1344
- logger9.debug(`[engine-client:${this.engineId}] ${pipe} pipe error (engine died concurrently = expected): ${err.message}`);
1528
+ logger10.debug(`[engine-client:${this.engineId}] ${pipe} pipe error (engine died concurrently = expected): ${err.message}`);
1345
1529
  });
1346
1530
  }
1347
1531
  child.stdout?.setEncoding("utf-8");
@@ -1410,7 +1594,7 @@ var EngineClient = class {
1410
1594
  try {
1411
1595
  frame = JSON.parse(line);
1412
1596
  } catch {
1413
- logger9.warn(
1597
+ logger10.warn(
1414
1598
  `[engine-client:${this.engineId}] dropped non-NDJSON stdout line (protocol contract: stdout is NDJSON-only): ${truncate(line, FRAME_ECHO_MAX_CHARS)}`
1415
1599
  );
1416
1600
  return;
@@ -1427,7 +1611,7 @@ var EngineClient = class {
1427
1611
  this.onReverseRequest(frame);
1428
1612
  return;
1429
1613
  }
1430
- logger9.warn(
1614
+ logger10.warn(
1431
1615
  `[engine-client:${this.engineId}] dropped unrecognized frame: ${truncate(line, FRAME_ECHO_MAX_CHARS)}`
1432
1616
  );
1433
1617
  }
@@ -1551,7 +1735,7 @@ var EngineClient = class {
1551
1735
  try {
1552
1736
  await this.request("dispose", {}, { timeoutMs: DISPOSE_GRACE_MS });
1553
1737
  } catch (err) {
1554
- logger9.debug(
1738
+ logger10.debug(
1555
1739
  `[engine-client:${this.engineId}] dispose frame failed, falling back to kill chain: ${err instanceof Error ? err.message : String(err)}`
1556
1740
  );
1557
1741
  }
@@ -1563,9 +1747,9 @@ var EngineClient = class {
1563
1747
  onEngineExit(code, signal) {
1564
1748
  const detail = signal !== null ? `signal ${signal}` : `exit code ${code}`;
1565
1749
  if (this.intentionalKill) {
1566
- logger9.debug(`[engine-client:${this.engineId}] engine exited after intentional kill (${detail}) \u2014 expected`);
1750
+ logger10.debug(`[engine-client:${this.engineId}] engine exited after intentional kill (${detail}) \u2014 expected`);
1567
1751
  } else {
1568
- logger9.warn(`[engine-client:${this.engineId}] engine process exited unexpectedly (${detail})`);
1752
+ logger10.warn(`[engine-client:${this.engineId}] engine process exited unexpectedly (${detail})`);
1569
1753
  }
1570
1754
  this.teardownProcess(detail);
1571
1755
  this.state = "exited";
@@ -1614,7 +1798,7 @@ var EngineClient = class {
1614
1798
  `);
1615
1799
  return true;
1616
1800
  } catch (err) {
1617
- logger9.debug(
1801
+ logger10.debug(
1618
1802
  `[engine-client:${this.engineId}] stdin write failed: ${err instanceof Error ? err.message : String(err)}`
1619
1803
  );
1620
1804
  return false;
@@ -1976,7 +2160,7 @@ function getHostUiRequestEndpoint() {
1976
2160
  }
1977
2161
 
1978
2162
  // src/execution/engine/engine-inspect-package.ts
1979
- var logger10 = getLogger("subagents");
2163
+ var logger11 = getLogger("subagents");
1980
2164
  function readEnginePackageJson(pkgDir) {
1981
2165
  let raw;
1982
2166
  try {
@@ -2055,12 +2239,12 @@ function parseOptionalDisplayFields(m, id) {
2055
2239
  if (typeof rawDisplayName === "string" && rawDisplayName.trim() !== "") {
2056
2240
  displayName = rawDisplayName;
2057
2241
  } else {
2058
- logger10.warn(`[engine-discovery] engine '${id}': displayName must be a non-empty string \u2014 ignoring`);
2242
+ logger11.warn(`[engine-discovery] engine '${id}': displayName must be a non-empty string \u2014 ignoring`);
2059
2243
  }
2060
2244
  }
2061
2245
  const rawDescription = m["description"];
2062
2246
  if (rawDescription !== void 0 && typeof rawDescription !== "string") {
2063
- logger10.warn(`[engine-discovery] engine '${id}': description must be a string \u2014 ignoring`);
2247
+ logger11.warn(`[engine-discovery] engine '${id}': description must be a string \u2014 ignoring`);
2064
2248
  }
2065
2249
  return displayName;
2066
2250
  }
@@ -2149,7 +2333,7 @@ function errorMessage(err) {
2149
2333
  // src/execution/engine/engine-discovery-roots.ts
2150
2334
  var fs2 = __toESM(require("fs"), 1);
2151
2335
  var path3 = __toESM(require("path"), 1);
2152
- var logger11 = getLogger("subagents");
2336
+ var logger12 = getLogger("subagents");
2153
2337
  var ENGINE_ROOTS_ENV = "XYZ_AGENT_ENGINE_ROOTS";
2154
2338
  function parseEngineRootsEnv(env) {
2155
2339
  const raw = env[ENGINE_ROOTS_ENV];
@@ -2160,7 +2344,7 @@ function parseEngineRootsEnv(env) {
2160
2344
  const dir = part.trim();
2161
2345
  if (dir === "") continue;
2162
2346
  if (!path3.isAbsolute(dir)) {
2163
- logger11.warn(
2347
+ logger12.warn(
2164
2348
  `[engine-discovery] ${ENGINE_ROOTS_ENV} entry '${dir}' is not an absolute path \u2014 dropping entry`
2165
2349
  );
2166
2350
  continue;
@@ -2229,7 +2413,7 @@ function isFile(p) {
2229
2413
  // src/execution/engine/config.ts
2230
2414
  var fs3 = __toESM(require("fs"), 1);
2231
2415
  var path4 = __toESM(require("path"), 1);
2232
- var logger12 = getLogger("subagents");
2416
+ var logger13 = getLogger("subagents");
2233
2417
  function readExplicitEngines(agentDir) {
2234
2418
  const configPath = path4.join(agentDir, "subagents", "config.json");
2235
2419
  let parsed;
@@ -2242,7 +2426,7 @@ function readExplicitEngines(agentDir) {
2242
2426
  const engines = parsed["engines"];
2243
2427
  if (typeof engines !== "object" || engines === null || Array.isArray(engines)) {
2244
2428
  if (engines !== void 0) {
2245
- logger12.warn(
2429
+ logger13.warn(
2246
2430
  `[engine-discovery] config.json engines section must be an object keyed by engine id, got ${jsonKindOf(engines)} \u2014 ignoring L3 explicit engines`
2247
2431
  );
2248
2432
  }
@@ -2270,7 +2454,7 @@ function sanitizeExplicitEntry(id, raw) {
2270
2454
  }
2271
2455
  function requireEntryObject(id, raw) {
2272
2456
  if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
2273
- logger12.warn(
2457
+ logger13.warn(
2274
2458
  `[engine-discovery] config.json engines.${id} must be an object, got ${jsonKindOf(raw)} \u2014 skipping entry`
2275
2459
  );
2276
2460
  return void 0;
@@ -2279,7 +2463,7 @@ function requireEntryObject(id, raw) {
2279
2463
  }
2280
2464
  function requireCommand(id, command) {
2281
2465
  if (typeof command !== "string" || command.trim() === "") {
2282
- logger12.warn(
2466
+ logger13.warn(
2283
2467
  `[engine-discovery] config.json engines.${id}.command is required (non-empty string) \u2014 skipping entry`
2284
2468
  );
2285
2469
  return void 0;
@@ -2288,7 +2472,7 @@ function requireCommand(id, command) {
2288
2472
  }
2289
2473
  function normalizeArgs(id, args) {
2290
2474
  if (args !== void 0 && !isStringArray(args)) {
2291
- logger12.warn(
2475
+ logger13.warn(
2292
2476
  `[engine-discovery] config.json engines.${id}.args must be a string array \u2014 ignoring args`
2293
2477
  );
2294
2478
  }
@@ -2296,7 +2480,7 @@ function normalizeArgs(id, args) {
2296
2480
  }
2297
2481
  function normalizeConfig(id, config) {
2298
2482
  if (config !== void 0 && !isStringRecord(config)) {
2299
- logger12.warn(
2483
+ logger13.warn(
2300
2484
  `[engine-discovery] config.json engines.${id}.config must be a Record<string, string> \u2014 ignoring config`
2301
2485
  );
2302
2486
  }
@@ -2304,7 +2488,7 @@ function normalizeConfig(id, config) {
2304
2488
  }
2305
2489
  function normalizeCwd(id, cwd) {
2306
2490
  if (cwd !== void 0 && typeof cwd !== "string") {
2307
- logger12.warn(
2491
+ logger13.warn(
2308
2492
  `[engine-discovery] config.json engines.${id}.cwd must be a string \u2014 ignoring cwd`
2309
2493
  );
2310
2494
  }
@@ -2324,14 +2508,14 @@ function jsonKindOf(value) {
2324
2508
  }
2325
2509
 
2326
2510
  // src/execution/engine/engine-discovery-scan.ts
2327
- var logger13 = getLogger("subagents");
2511
+ var logger14 = getLogger("subagents");
2328
2512
  function scanEngines(opts) {
2329
2513
  const env = opts.env ?? process.env;
2330
2514
  const result = { discovered: [], skipped: [], unusable: [] };
2331
2515
  const present = (entry) => {
2332
2516
  const idx = result.discovered.findIndex((d) => d.id === entry.id);
2333
2517
  if (idx >= 0) {
2334
- logger13.debug(
2518
+ logger14.debug(
2335
2519
  `[engine-discovery] engine id '${entry.id}' from '${entry.source}' overrides earlier discovery from '${result.discovered[idx].source}' \u2014 same-id override`
2336
2520
  );
2337
2521
  result.discovered[idx] = entry;
@@ -2343,13 +2527,13 @@ function scanEngines(opts) {
2343
2527
  const inspection = inspectEnginePackage(pkgDir, source, opts.hostKind, env);
2344
2528
  if (inspection.status === "skip") {
2345
2529
  if (!inspection.reason.includes("not an engine package")) {
2346
- logger13.warn(`[engine-discovery] skipping ${pkgDir} (${source}): ${inspection.reason}`);
2530
+ logger14.warn(`[engine-discovery] skipping ${pkgDir} (${source}): ${inspection.reason}`);
2347
2531
  result.skipped.push({ pkgDir, reason: inspection.reason });
2348
2532
  }
2349
2533
  return;
2350
2534
  }
2351
2535
  if (inspection.status === "unusable") {
2352
- logger13.warn(`[engine-discovery] engine unavailable: ${inspection.reason}`);
2536
+ logger14.warn(`[engine-discovery] engine unavailable: ${inspection.reason}`);
2353
2537
  result.unusable.push({ pkgDir, id: inspection.id, reason: inspection.reason });
2354
2538
  return;
2355
2539
  }
@@ -2374,7 +2558,7 @@ function scanEngines(opts) {
2374
2558
  const command = resolveExplicitCommand(entry.command, env);
2375
2559
  if (command === void 0) {
2376
2560
  const reason = `engine '${id}' (config.json engines.${id}): command '${entry.command}' not found or not executable`;
2377
- logger13.warn(`[engine-discovery] ${reason} \u2014 not registering`);
2561
+ logger14.warn(`[engine-discovery] ${reason} \u2014 not registering`);
2378
2562
  result.unusable.push({ pkgDir: `config.json engines.${id}`, id, reason });
2379
2563
  continue;
2380
2564
  }
@@ -2391,7 +2575,7 @@ function discoverAndRegisterEngines(opts) {
2391
2575
  registerEngineDescriptor(entry.id, entry.descriptor);
2392
2576
  loadedDiscoveryIdSet.add(entry.id);
2393
2577
  if (existed) {
2394
- logger13.debug(
2578
+ logger14.debug(
2395
2579
  `[engine-discovery] engine '${entry.id}' descriptor overwritten in registry (source '${entry.source}') \u2014 same-id override`
2396
2580
  );
2397
2581
  }
@@ -2421,7 +2605,7 @@ function resolveExplicitCommand(command, env) {
2421
2605
  }
2422
2606
  function buildExplicitDescriptor(id, command, entry, opts) {
2423
2607
  const env = opts.env ?? process.env;
2424
- logger13.debug(
2608
+ logger14.debug(
2425
2609
  `[engine-discovery] engine '${id}' registered from config.json engines section with conservative capabilities (no manifest)`
2426
2610
  );
2427
2611
  const caps = { ...CONSERVATIVE_CAPABILITIES };
@@ -2464,7 +2648,7 @@ function buildExplicitDescriptor(id, command, entry, opts) {
2464
2648
  }
2465
2649
 
2466
2650
  // src/execution/engine/routing.ts
2467
- var logger14 = getLogger("subagents");
2651
+ var logger15 = getLogger("subagents");
2468
2652
  var RESCAN_OPTS_SLOT_KEY = /* @__PURE__ */ Symbol.for("@zhushanwen/pi-subagent-workflow.engineDiscoveryRescanOpts");
2469
2653
  function setEngineDiscoveryRescanOptions(opts) {
2470
2654
  Reflect.set(globalThis, RESCAN_OPTS_SLOT_KEY, { current: opts });
@@ -2480,7 +2664,7 @@ function hasEngineWithRescan(id) {
2480
2664
  try {
2481
2665
  return ensureEngineDiscovered(id, opts);
2482
2666
  } catch (err) {
2483
- logger14.debug(
2667
+ logger15.debug(
2484
2668
  `[engine-routing] rescan for engine '${id}' failed (treated as not discovered): ${err instanceof Error ? err.message : String(err)}`
2485
2669
  );
2486
2670
  return false;
@@ -2504,7 +2688,7 @@ function resolveEngineRouting(input) {
2504
2688
  function resolveDefaultEngineFallback(requestedId, available) {
2505
2689
  if (available.length === 0) return void 0;
2506
2690
  const target = available[0];
2507
- logger14.warn(
2691
+ logger15.warn(
2508
2692
  `[engine-routing] default engine '${requestedId}' is not in the discovered engines [${available.join(", ")}]; falling back to first available engine '${target}' (D4, recorded via engineFallback)`
2509
2693
  );
2510
2694
  return { engineId: target, fallback: { from: requestedId, reason: "engine_not_found" } };
@@ -2656,7 +2840,7 @@ var path6 = __toESM(require("path"), 1);
2656
2840
  var import_subagent_engine_sdk10 = require("@zhushanwen/subagent-engine-sdk");
2657
2841
 
2658
2842
  // src/shared/atomic-write.ts
2659
- var logger15 = getLogger("subagents");
2843
+ var logger16 = getLogger("subagents");
2660
2844
  var TMP_MARKER = ".tmp.";
2661
2845
  var TMP_NAME_PATTERN = /^(.+)\.tmp\.(\d+)\.[0-9A-Za-z-]+$/;
2662
2846
  var tmpSeq = 0;
@@ -2678,7 +2862,7 @@ function removeTmpBestEffortSync(tmpPath) {
2678
2862
  try {
2679
2863
  (0, import_node_fs2.unlinkSync)(tmpPath);
2680
2864
  } catch (cleanupErr) {
2681
- logger15.debug("[subagent-core] atomic-write cleanup tmp failed", {
2865
+ logger16.debug("[subagent-core] atomic-write cleanup tmp failed", {
2682
2866
  detail: (0, import_subagent_engine_sdk10.toErrorMessage)(cleanupErr),
2683
2867
  tmpPath
2684
2868
  });
@@ -2726,7 +2910,7 @@ async function writeAtomicFile(filePath, content, options = {}) {
2726
2910
  await dirFh.close();
2727
2911
  }
2728
2912
  } catch (dirSyncErr) {
2729
- logger15.debug("[subagent-core] atomic-write fsync dir failed", {
2913
+ logger16.debug("[subagent-core] atomic-write fsync dir failed", {
2730
2914
  detail: (0, import_subagent_engine_sdk10.toErrorMessage)(dirSyncErr),
2731
2915
  dirPath
2732
2916
  });
@@ -2737,7 +2921,7 @@ async function writeAtomicFile(filePath, content, options = {}) {
2737
2921
  try {
2738
2922
  await fsPromises.unlink(tmpPath);
2739
2923
  } catch (cleanupErr) {
2740
- logger15.debug("[subagent-core] atomic-write cleanup tmp failed", {
2924
+ logger16.debug("[subagent-core] atomic-write cleanup tmp failed", {
2741
2925
  detail: (0, import_subagent_engine_sdk10.toErrorMessage)(cleanupErr),
2742
2926
  tmpPath
2743
2927
  });
@@ -2781,7 +2965,7 @@ function cleanupStaleTmpFiles(dir, options = {}) {
2781
2965
  result.removed.push(ref.tmpPath);
2782
2966
  continue;
2783
2967
  }
2784
- logger15.debug("[subagent-core] cleanupStaleTmpFiles stat failed", {
2968
+ logger16.debug("[subagent-core] cleanupStaleTmpFiles stat failed", {
2785
2969
  detail: (0, import_subagent_engine_sdk10.toErrorMessage)(statErr),
2786
2970
  tmpPath: ref.tmpPath
2787
2971
  });
@@ -2796,7 +2980,7 @@ function cleanupStaleTmpFiles(dir, options = {}) {
2796
2980
  if (typeof unlinkErr.code === "string" && unlinkErr.code === "ENOENT") {
2797
2981
  result.removed.push(ref.tmpPath);
2798
2982
  } else {
2799
- logger15.debug("[subagent-core] cleanupStaleTmpFiles unlink failed", {
2983
+ logger16.debug("[subagent-core] cleanupStaleTmpFiles unlink failed", {
2800
2984
  detail: (0, import_subagent_engine_sdk10.toErrorMessage)(unlinkErr),
2801
2985
  tmpPath: ref.tmpPath
2802
2986
  });
@@ -2836,72 +3020,6 @@ function syncEnginesFile(agentDir) {
2836
3020
  }
2837
3021
  }
2838
3022
 
2839
- // src/execution/engine/host/spawned-children.ts
2840
- var CoreSpawnedChildrenMirror = class {
2841
- entries = /* @__PURE__ */ new Map();
2842
- /** 落项 / 覆盖(同 recordId 重 spawn = 覆盖旧句柄,与引擎侧 Map 同语义)。 */
2843
- register(recordId, child) {
2844
- this.entries.set(recordId, {
2845
- pid: child.pid,
2846
- killed: child.killed,
2847
- updatedAt: Date.now()
2848
- });
2849
- }
2850
- /** 单项置死(杀链入口:killRecordChildWithEscalation 途经)。 */
2851
- markKilled(recordId) {
2852
- const entry = this.entries.get(recordId);
2853
- if (entry !== void 0) {
2854
- entry.killed = true;
2855
- entry.updatedAt = Date.now();
2856
- }
2857
- }
2858
- /** 整体置死 + 清空(失效语义 2+3:引擎 exit / 重建 / dispose / killAll)。 */
2859
- killAll() {
2860
- this.entries.clear();
2861
- }
2862
- getChildByRecord(recordId) {
2863
- return this.entries.get(recordId);
2864
- }
2865
- /** 句柄存活谓词(镜像面;判据与引擎侧 `!child.killed` 同构)。 */
2866
- hasLiveProcessHandle(recordId) {
2867
- const entry = this.entries.get(recordId);
2868
- return entry !== void 0 && !entry.killed;
2869
- }
2870
- /** 快照(诊断/测试)。 */
2871
- snapshot() {
2872
- return [...this.entries.entries()].map(([recordId, entry]) => ({ recordId, ...entry }));
2873
- }
2874
- /** 测试隔离专用(生产禁用——进程级全局状态)。 */
2875
- clear() {
2876
- this.entries.clear();
2877
- }
2878
- };
2879
- var MIRROR_SLOT_KEY = /* @__PURE__ */ Symbol.for(
2880
- "@zhushanwen/pi-subagent-workflow.coreSpawnedChildrenMirror"
2881
- );
2882
- function coreMirrorSlot() {
2883
- let slot = Reflect.get(globalThis, MIRROR_SLOT_KEY);
2884
- if (!slot) {
2885
- slot = new CoreSpawnedChildrenMirror();
2886
- Reflect.set(globalThis, MIRROR_SLOT_KEY, slot);
2887
- }
2888
- return slot;
2889
- }
2890
- function registerSpawnedChildForRecord(recordId, child) {
2891
- coreMirrorSlot().register(recordId, { pid: child.pid, killed: child.killed });
2892
- }
2893
- function killRecordChildWithEscalation(recordId, _source) {
2894
- coreMirrorSlot().markKilled(recordId);
2895
- }
2896
- function killAllSpawnedChildren(_signal = "SIGTERM") {
2897
- const before = coreMirrorSlot().snapshot().length;
2898
- coreMirrorSlot().killAll();
2899
- return before;
2900
- }
2901
- function hasLiveProcessHandleCore(recordId) {
2902
- return coreMirrorSlot().hasLiveProcessHandle(recordId);
2903
- }
2904
-
2905
3023
  // src/execution/engine/d8-compat.ts
2906
3024
  var fs5 = __toESM(require("fs"), 1);
2907
3025
  var path8 = __toESM(require("path"), 1);
@@ -2912,7 +3030,7 @@ var import_subagent_engine_sdk11 = require("@zhushanwen/subagent-engine-sdk");
2912
3030
 
2913
3031
  // src/execution/engine/d8-compat.ts
2914
3032
  var import_meta = {};
2915
- var logger16 = getLogger("subagents");
3033
+ var logger17 = getLogger("subagents");
2916
3034
  var D8_ZCODE_ENGINE_ID = "zcode";
2917
3035
  var D8_ZSW_HOST_KIND = "zsw";
2918
3036
  var D8_PI_HOST_KIND = "pi";
@@ -2990,7 +3108,7 @@ function registerZcodeEngine(engineDataDir = getEngineDataDir) {
2990
3108
  if (hasEngine(id)) return;
2991
3109
  const located = locateVendoredEnginePkg(id);
2992
3110
  if (located === void 0) {
2993
- logger16.warn(
3111
+ logger17.warn(
2994
3112
  `[d8-compat] engine '${id}' vendored package not located (enginePkg ${enginePkgName(id)}); left unregistered \u2014 dispatch will fail with engine_not_found + recovery guidance`
2995
3113
  );
2996
3114
  return;
@@ -2998,7 +3116,7 @@ function registerZcodeEngine(engineDataDir = getEngineDataDir) {
2998
3116
  const syntheticEnv = { ...process.env, XYZ_AGENT_DATA_DIR: engineDataDir() };
2999
3117
  const inspection = inspectEnginePackage(located.pkgDir, "d8-compat", D8_PI_HOST_KIND, syntheticEnv);
3000
3118
  if (inspection.status !== "ok") {
3001
- logger16.warn(
3119
+ logger17.warn(
3002
3120
  `[d8-compat] engine '${id}' vendored package inspection failed (${inspection.reason}); left unregistered \u2014 dispatch will fail with engine_not_found + recovery guidance`
3003
3121
  );
3004
3122
  return;
@@ -3008,7 +3126,7 @@ function registerZcodeEngine(engineDataDir = getEngineDataDir) {
3008
3126
  function warnSourcesIgnoredOnce(deps) {
3009
3127
  if (deps.sources === void 0 || warnedSourcesIgnored) return;
3010
3128
  warnedSourcesIgnored = true;
3011
- logger16.warn(
3129
+ logger17.warn(
3012
3130
  "[d8-compat] createZcodeEngine deps.sources is ignored (model/credential sources do not cross the process boundary \u2014 the engine resolves its own credentials, protocol invariant 5)"
3013
3131
  );
3014
3132
  }
@@ -3068,12 +3186,12 @@ function createZcodeEngine(deps) {
3068
3186
  var PI_POOL_KEY = "shared";
3069
3187
  var WATCHDOG_FLOOR_MINUTES = 30;
3070
3188
  var WATCHDOG_MINUTES_PER_TURN = 5;
3071
- var MS_PER_SECOND = 1e3;
3072
- var SECONDS_PER_MINUTE = 60;
3189
+ var MS_PER_SECOND2 = 1e3;
3190
+ var SECONDS_PER_MINUTE2 = 60;
3073
3191
  function maxTurnsToWatchdogMs(maxTurns) {
3074
3192
  return Math.max(
3075
- WATCHDOG_FLOOR_MINUTES * SECONDS_PER_MINUTE * MS_PER_SECOND,
3076
- maxTurns * WATCHDOG_MINUTES_PER_TURN * SECONDS_PER_MINUTE * MS_PER_SECOND
3193
+ WATCHDOG_FLOOR_MINUTES * SECONDS_PER_MINUTE2 * MS_PER_SECOND2,
3194
+ maxTurns * WATCHDOG_MINUTES_PER_TURN * SECONDS_PER_MINUTE2 * MS_PER_SECOND2
3077
3195
  );
3078
3196
  }
3079
3197
  function resolveHostPiEnginePort(_getService) {
@@ -3212,7 +3330,7 @@ var import_subagent_engine_sdk14 = require("@zhushanwen/subagent-engine-sdk");
3212
3330
  var ACTIVITY_LABEL_MAX = 60;
3213
3331
  var TURN_SUMMARY_MAX = 80;
3214
3332
  var TOOL_LABEL_MAX = 100;
3215
- var MS_PER_SECOND2 = 1e3;
3333
+ var MS_PER_SECOND3 = 1e3;
3216
3334
  function extractLabelFromArgs(toolName, args) {
3217
3335
  if (typeof args !== "object" || args === null) return toolName;
3218
3336
  const a = args;
@@ -3543,7 +3661,7 @@ function projectOutcome(record) {
3543
3661
  }
3544
3662
  function computeElapsedSeconds(record) {
3545
3663
  const end = record.endedAt ?? Date.now();
3546
- return Math.floor((end - record.startedAt) / MS_PER_SECOND2);
3664
+ return Math.floor((end - record.startedAt) / MS_PER_SECOND3);
3547
3665
  }
3548
3666
  function project(record) {
3549
3667
  return {
@@ -3606,7 +3724,7 @@ var import_subagent_engine_sdk12 = require("@zhushanwen/subagent-engine-sdk");
3606
3724
  var import_promises = require("fs/promises");
3607
3725
  var import_node_path4 = require("path");
3608
3726
  var import_subagent_engine_sdk13 = require("@zhushanwen/subagent-engine-sdk");
3609
- var logger17 = getLogger("subagents");
3727
+ var logger18 = getLogger("subagents");
3610
3728
  var FLUSH_THRESHOLD_LINES = 64;
3611
3729
  var FLUSH_THRESHOLD_BYTES = 32 * 1024;
3612
3730
  var defaultFs = {
@@ -3715,11 +3833,11 @@ var JournalWriter = class {
3715
3833
  }
3716
3834
  };
3717
3835
  function defaultWarn2(msg) {
3718
- logger17.warn(msg);
3836
+ logger18.warn(msg);
3719
3837
  }
3720
3838
 
3721
3839
  // src/execution/engine/common/session-view-service.ts
3722
- var logger18 = getLogger("subagents");
3840
+ var logger19 = getLogger("subagents");
3723
3841
  var DEFAULT_ENGINE_ID2 = "pi";
3724
3842
  var OUTCOME_PLACEHOLDER_TEXT = "(no outcome recorded)";
3725
3843
  function extractEngineId(record) {
@@ -3746,18 +3864,18 @@ function lookupNativeSessionReader(engineId) {
3746
3864
  function readJournalTier(record, handle, dataDir) {
3747
3865
  const journalPath = handle.journalPath;
3748
3866
  if (journalPath === void 0) {
3749
- logger18.debug("[session-view-service] tier2 skipped: no journalPath in handle");
3867
+ logger19.debug("[session-view-service] tier2 skipped: no journalPath in handle");
3750
3868
  return void 0;
3751
3869
  }
3752
3870
  if (!isStrictlyUnder((0, import_subagent_engine_sdk12.resolveEnginesRoot)(dataDir), journalPath)) {
3753
- logger18.warn(
3871
+ logger19.warn(
3754
3872
  `[session-view-service] journalPath escapes engines root, reject tier2: ${journalPath}`
3755
3873
  );
3756
3874
  return void 0;
3757
3875
  }
3758
3876
  const messages = replayEventsToHistory((0, import_subagent_engine_sdk13.replayJournal)(journalPath), record);
3759
3877
  if (messages === void 0) {
3760
- logger18.debug(
3878
+ logger19.debug(
3761
3879
  `[session-view-service] tier2 journal replay produced no content, degrade to outcome-only (path=${journalPath})`
3762
3880
  );
3763
3881
  }
@@ -3927,7 +4045,7 @@ async function readSubagentHistoryMessages(record, dataDir) {
3927
4045
  if (engineId === DEFAULT_ENGINE_ID2) return [];
3928
4046
  const handle = parseEngineHandle(record.engineHandle);
3929
4047
  if (handle === void 0) {
3930
- logger18.debug(
4048
+ logger19.debug(
3931
4049
  `[session-view-service] engine '${engineId}' record has no engineHandle, degrade to outcome-only (subagentId=${record.subagentId})`
3932
4050
  );
3933
4051
  return outcomeOnlyMessages(record);
@@ -3937,12 +4055,12 @@ async function readSubagentHistoryMessages(record, dataDir) {
3937
4055
  const native = await reader(handle, dataDir);
3938
4056
  if (native !== void 0) {
3939
4057
  if (native.turns.some(hasTurnContent)) return sessionViewToMessages(native, record);
3940
- logger18.debug(
4058
+ logger19.debug(
3941
4059
  `[session-view-service] tier1 native view has no substantive content (turns=${native.turns.length}), degrade to journal tier (subagentId=${record.subagentId})`
3942
4060
  );
3943
4061
  }
3944
4062
  } else {
3945
- logger18.debug(
4063
+ logger19.debug(
3946
4064
  `[session-view-service] engine '${engineId}' has no native reader tier, fall through to journal tier (subagentId=${record.subagentId})`
3947
4065
  );
3948
4066
  }
@@ -3987,21 +4105,6 @@ var DirtyWorktreeError = class extends Error {
3987
4105
  // src/execution/subagent-service.ts
3988
4106
  var import_node_async_hooks = require("async_hooks");
3989
4107
 
3990
- // src/shared/timer-delay.ts
3991
- var MAX_TIMER_DELAY_MS = 2147483647;
3992
- function assertSafeTimerDelay(ms, source) {
3993
- if (!Number.isFinite(ms)) {
3994
- throw new Error(
3995
- `[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.`
3996
- );
3997
- }
3998
- if (ms > MAX_TIMER_DELAY_MS) {
3999
- throw new Error(
4000
- `[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.`
4001
- );
4002
- }
4003
- }
4004
-
4005
4108
  // src/execution/agent-result-mapper.ts
4006
4109
  var TOOL_ARGS_JSON_MAX_CHARS = 500;
4007
4110
  function mapToWorkflowAgentResult(r) {
@@ -4099,14 +4202,14 @@ function isErrnoException(err) {
4099
4202
  }
4100
4203
 
4101
4204
  // src/execution/best-effort.ts
4102
- var logger19 = getLogger("subagents");
4205
+ var logger20 = getLogger("subagents");
4103
4206
  function bestEffort(err, context, level = "debug") {
4104
4207
  const detail = err instanceof Error ? err.message : err;
4105
4208
  const msg = `[subagents] best-effort ${context} failed`;
4106
4209
  if (level === "error") {
4107
- logger19.error(msg, { detail });
4210
+ logger20.error(msg, { detail });
4108
4211
  } else {
4109
- logger19.debug(msg, { detail });
4212
+ logger20.debug(msg, { detail });
4110
4213
  }
4111
4214
  }
4112
4215
 
@@ -4197,7 +4300,7 @@ var CollectCoordinator = class {
4197
4300
  // src/execution/config.ts
4198
4301
  var fs7 = __toESM(require("fs"), 1);
4199
4302
  var path9 = __toESM(require("path"), 1);
4200
- var logger20 = getLogger("subagents");
4303
+ var logger21 = getLogger("subagents");
4201
4304
  var DEFAULT_CONFIG = {
4202
4305
  version: 1,
4203
4306
  maxConcurrent: 6
@@ -4240,7 +4343,7 @@ function readGlobalConfig(agentDir) {
4240
4343
  }
4241
4344
  function readFailure(configPath, err) {
4242
4345
  const reason = err instanceof Error ? err.message : String(err);
4243
- logger20.warn(`[subagents] global config read failed (read-failure) at ${configPath}: ${reason}`);
4346
+ logger21.warn(`[subagents] global config read failed (read-failure) at ${configPath}: ${reason}`);
4244
4347
  return { status: "failed", reason };
4245
4348
  }
4246
4349
  function errnoCodeOf(err) {
@@ -4279,7 +4382,7 @@ function sanitizeCollectSync(value) {
4279
4382
  bad.push(`totalChars=${fmt(v.totalChars)}`);
4280
4383
  }
4281
4384
  if (bad.length > 0) {
4282
- logger20.warn(
4385
+ logger21.warn(
4283
4386
  `[subagents] config collectSync invalid field(s) reverted to defaults: ${bad.join(", ")}`
4284
4387
  );
4285
4388
  }
@@ -4304,56 +4407,6 @@ function sanitizeEngineRouting(value) {
4304
4407
  return typeof strict === "boolean" ? { strict } : void 0;
4305
4408
  }
4306
4409
 
4307
- // src/execution/lifecycle-manager.ts
4308
- var logger21 = getLogger("subagents");
4309
- var MS_PER_SECOND3 = 1e3;
4310
- var SECONDS_PER_MINUTE2 = 60;
4311
- var IDLE_TIMEOUT_MINUTES = 5;
4312
- var DEFAULT_IDLE_TIMEOUT_MS = IDLE_TIMEOUT_MINUTES * SECONDS_PER_MINUTE2 * MS_PER_SECOND3;
4313
- function getEnvIdleTimeoutMs() {
4314
- const raw = process.env.XYZ_SUBAGENT_IDLE_TIMEOUT_MS;
4315
- if (!raw) return void 0;
4316
- const parsed = Number(raw);
4317
- if (!Number.isFinite(parsed) || parsed <= 0) {
4318
- logger21.warn(
4319
- `[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`
4320
- );
4321
- return void 0;
4322
- }
4323
- return parsed;
4324
- }
4325
- var idleTimers = /* @__PURE__ */ new Map();
4326
- function armIdleTimer(recordId, onTimeout, timeoutMs) {
4327
- if (timeoutMs !== void 0 && timeoutMs <= 0) {
4328
- disarmIdleTimer(recordId);
4329
- return;
4330
- }
4331
- const resolved = timeoutMs ?? getEnvIdleTimeoutMs() ?? DEFAULT_IDLE_TIMEOUT_MS;
4332
- assertSafeTimerDelay(resolved, "idleTimeoutMs");
4333
- disarmIdleTimer(recordId);
4334
- const timer = setTimeout(() => {
4335
- if (idleTimers.get(recordId)?.timer === timer) {
4336
- idleTimers.delete(recordId);
4337
- }
4338
- onTimeout();
4339
- }, resolved);
4340
- timer.unref?.();
4341
- idleTimers.set(recordId, { timer, timeoutMs: resolved });
4342
- }
4343
- function disarmIdleTimer(recordId) {
4344
- const entry = idleTimers.get(recordId);
4345
- if (!entry) {
4346
- return;
4347
- }
4348
- clearTimeout(entry.timer);
4349
- idleTimers.delete(recordId);
4350
- }
4351
- function hasIdleTimer(recordId) {
4352
- return idleTimers.has(recordId);
4353
- }
4354
- var ACTIVATE_LOCK_TIMEOUT_SECONDS = 30;
4355
- var ACTIVATE_LOCK_TIMEOUT_MS = ACTIVATE_LOCK_TIMEOUT_SECONDS * MS_PER_SECOND3;
4356
-
4357
4410
  // src/execution/concurrency-pool.ts
4358
4411
  var DefaultConcurrencyPool = class {
4359
4412
  _active = 0;
@@ -5571,17 +5624,6 @@ function displayAgentName(ref) {
5571
5624
  return base.endsWith(AGENT_REF_EXT) ? base.slice(0, -AGENT_REF_EXT.length) : base;
5572
5625
  }
5573
5626
 
5574
- // src/execution/lifecycle-predicates.ts
5575
- function hasLiveProcessHandle(recordId) {
5576
- return hasLiveProcessHandleCore(recordId);
5577
- }
5578
- function isIdle(record) {
5579
- return hasIdleTimer(record.id);
5580
- }
5581
- function isResumable(record) {
5582
- return record.status === "running" && !hasLiveProcessHandle(record.id);
5583
- }
5584
-
5585
5627
  // src/execution/notifier.ts
5586
5628
  var import_node_crypto2 = require("crypto");
5587
5629
 
@@ -6977,10 +7019,10 @@ var RecordStore = class _RecordStore {
6977
7019
  return out;
6978
7020
  }
6979
7021
  /** 订阅变更。返回取消订阅函数。 */
6980
- onChange(listener) {
6981
- this.listeners.add(listener);
7022
+ onChange(listener2) {
7023
+ this.listeners.add(listener2);
6982
7024
  return () => {
6983
- this.listeners.delete(listener);
7025
+ this.listeners.delete(listener2);
6984
7026
  };
6985
7027
  }
6986
7028
  /** 触发所有监听器(TUI widget/list requestRender)。dispose 后短路。
@@ -6988,8 +7030,8 @@ var RecordStore = class _RecordStore {
6988
7030
  * 内存事件(register/archive)不改变磁盘文件——旧实现整体失效是全量重扫的根因。 */
6989
7031
  notifyChange() {
6990
7032
  if (this._disposed) return;
6991
- for (const listener of this.listeners) {
6992
- listener();
7033
+ for (const listener2 of this.listeners) {
7034
+ listener2();
6993
7035
  }
6994
7036
  }
6995
7037
  /** session 结束清理。 */
@@ -9421,9 +9463,11 @@ var WorktreeRegistry = class {
9421
9463
  }
9422
9464
  /**
9423
9465
  * proper-lockfile 直用的跨进程锁(取代已删除的共享 file-lock 包装,抽包去依赖)。
9424
- * 锁协议逐项对齐 extensions/shared/file-lock/src/file-lock.ts 的 withFileLock:
9425
- * - lockfile 路径 = <目标文件>.lock(proper-lockfile 默认,与包装/runtime 侧
9426
- * 同一路径才互斥)
9466
+ * 锁协议对齐现存三方同协议实现——runtime 侧 packages/runtime/src/utils/file-lock.ts
9467
+ * withFileLockAsync(范本 pi FileAuthStorageBackend,参数对齐 proper-lockfile
9468
+ * 内部 retry 库)与 extension 侧 @zhushanwen/pi-file-lock:
9469
+ * - lockfile 路径 = <目标文件>.lock(proper-lockfile 默认,与 runtime 侧/
9470
+ * extension 侧同一路径才互斥)
9427
9471
  * - realpath:false —— 目标文件不存在也可锁(realpath 默认 true 时 ENOENT)
9428
9472
  * - stale 30s:持锁进程崩溃后锁可被夺取
9429
9473
  * - async retries 指数退避:10 次 / factor 2 / 100ms~10s / randomize,耗尽抛
@@ -10210,7 +10254,7 @@ var SubagentService = class {
10210
10254
  lookupRecordAnyState: (id) => this.lookupRecordAnyState(id),
10211
10255
  collectRecords: (limit, statusFilter) => this.collectRecords(limit, statusFilter),
10212
10256
  getFullRecord: (id) => this.getFullRecord(id),
10213
- onChange: (listener) => this.onChange(listener)
10257
+ onChange: (listener2) => this.onChange(listener2)
10214
10258
  };
10215
10259
  /** [D4 对话 action 面聚合] chat 域 message/close 消费面(壳 subagent-actions 经此访问;
10216
10260
  * 纯委托同上。PiEngineService 适配器不经此——引擎边界走 piEngineServiceAdapter)。 */
@@ -10463,6 +10507,7 @@ var SubagentService = class {
10463
10507
  this.notifyHost.emitPendingUnregister(record.id, "closed");
10464
10508
  count++;
10465
10509
  }
10510
+ notifyInFlightChanged();
10466
10511
  return count;
10467
10512
  }
10468
10513
  /** SP-4: /fork 新 session 时清理旧 record。
@@ -10890,6 +10935,7 @@ var SubagentService = class {
10890
10935
  );
10891
10936
  }
10892
10937
  disarmIdleTimer(record.id);
10938
+ notifyInFlightChanged();
10893
10939
  const engine = this.resolveChatEnginePort();
10894
10940
  if (!this.chatRoundRoutes.has(record.id)) {
10895
10941
  this.chatRoundRoutes.set(
@@ -11023,6 +11069,7 @@ var SubagentService = class {
11023
11069
  `[subagents] settled watchdog (${fire.phase}) fired for ${record.id}: ${windowDesc}, terminating (LC-1 wedge recovery)`
11024
11070
  );
11025
11071
  killRecordChildWithEscalation(record.id, "settled watchdog (hot path)");
11072
+ notifyInFlightChanged();
11026
11073
  this.terminateChatSession(record, "cancel", "settled watchdog (hot path)");
11027
11074
  const failedResult = {
11028
11075
  text: "",
@@ -11135,6 +11182,7 @@ var SubagentService = class {
11135
11182
  */
11136
11183
  async closeChatIdle(record) {
11137
11184
  disarmIdleTimer(record.id);
11185
+ notifyInFlightChanged();
11138
11186
  disarmSettledWatchdog(record.id);
11139
11187
  disarmRoundFromProtocol(record.id);
11140
11188
  killRecordChildWithEscalation(record.id, "closeChatIdle");
@@ -11183,6 +11231,7 @@ var SubagentService = class {
11183
11231
  */
11184
11232
  async closeAfterRoundSettled(record) {
11185
11233
  disarmIdleTimer(record.id);
11234
+ notifyInFlightChanged();
11186
11235
  disarmSettledWatchdog(record.id);
11187
11236
  disarmRoundFromProtocol(record.id);
11188
11237
  killRecordChildWithEscalation(record.id, "closeAfterRoundSettled");
@@ -11259,8 +11308,8 @@ var SubagentService = class {
11259
11308
  }
11260
11309
  // ── 状态查询(TUI 调)──────────────────────────────────
11261
11310
  /** 订阅 store 变更(widget/list requestRender)。返回取消订阅。 */
11262
- onChange(listener) {
11263
- return this.store.onChange(listener);
11311
+ onChange(listener2) {
11312
+ return this.store.onChange(listener2);
11264
11313
  }
11265
11314
  // [D4] listRunning 已删除:零生产调用方(TUI 计数经 collectRecords / notify-host 的
11266
11315
  // piAdapter 直调 store.listRunning 覆盖),唯一消费是初始空态单测——保留 store 层方法。
@@ -11872,6 +11921,7 @@ var SubagentService = class {
11872
11921
  bestEffort(fallbackErr, "armIdleTimer fallback (chat idle phase)", "error");
11873
11922
  }
11874
11923
  }
11924
+ notifyInFlightChanged();
11875
11925
  }
11876
11926
  /** [W3] idle 帧锚点回填(冷续锚点 = 引擎侧会话滚动/compaction 后的最新定位)。 */
11877
11927
  backfillChatAnchor(record, anchor) {
@@ -12018,6 +12068,7 @@ var SubagentService = class {
12018
12068
  record.controller?.abort();
12019
12069
  killRecordChildWithEscalation(record.id, "cancelBackground");
12020
12070
  disarmIdleTimer(record.id);
12071
+ notifyInFlightChanged();
12021
12072
  disarmSettledWatchdog(record.id);
12022
12073
  disarmRoundFromProtocol(record.id);
12023
12074
  this.unregisterChatRoundRoute(record.id);
@@ -17294,7 +17345,7 @@ function boundedPrettySerialize(value, budget) {
17294
17345
  }
17295
17346
 
17296
17347
  // src/index.ts
17297
- var CORE_PACKAGE_VERSION = "0.7.1";
17348
+ var CORE_PACKAGE_VERSION = "0.8.0";
17298
17349
  // Annotate the CommonJS export names for ESM import in node:
17299
17350
  0 && (module.exports = {
17300
17351
  AGENT_REF_EXT,
@@ -17392,6 +17443,7 @@ var CORE_PACKAGE_VERSION = "0.7.1";
17392
17443
  getCachedFileContent,
17393
17444
  getCachedParsed,
17394
17445
  getHostServices,
17446
+ getInFlightSnapshot,
17395
17447
  getLogger,
17396
17448
  getModelConfigService,
17397
17449
  getOrCreateChannelRegistry,
@@ -17449,6 +17501,7 @@ var CORE_PACKAGE_VERSION = "0.7.1";
17449
17501
  saveWorkflow,
17450
17502
  scheduleTimeBudget,
17451
17503
  setEngineDiscoveryRescanOptions,
17504
+ setInFlightListener,
17452
17505
  setModelConfigService,
17453
17506
  setSubagentService,
17454
17507
  sortByCodepoint,