@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.
- package/dist/{chunk-VURUAGMM.js → chunk-OL5BK4VN.js} +237 -40
- package/dist/{engine-discovery-scan-ocpM8FmI.d.cts → engine-discovery-scan-BXLA8y-z.d.cts} +17 -1
- package/dist/{engine-discovery-scan-ocpM8FmI.d.ts → engine-discovery-scan-BXLA8y-z.d.ts} +17 -1
- package/dist/execution/engine/engine-discovery-scan.cjs +150 -40
- package/dist/execution/engine/engine-discovery-scan.d.cts +1 -1
- package/dist/execution/engine/engine-discovery-scan.d.ts +1 -1
- package/dist/execution/engine/engine-discovery-scan.js +1 -1
- package/dist/index.cjs +278 -225
- package/dist/index.d.cts +24 -4
- package/dist/index.d.ts +24 -4
- package/dist/index.js +188 -304
- package/dist.bundle/index.cjs +279 -226
- package/package.json +3 -3
- package/src/__tests__/record-store-last-line.test.ts +12 -0
- package/src/core/logger.ts +1 -1
- package/src/execution/__tests__/inflight-production-wiring.test.ts +223 -0
- package/src/execution/__tests__/nested-visibility.test.ts +13 -1
- package/src/execution/engine/__tests__/conformance/registry-fork-filter.test.ts +55 -53
- package/src/execution/engine/__tests__/inflight-snapshot.test.ts +180 -0
- package/src/execution/engine/client/engine-client.ts +30 -1
- package/src/execution/engine/host/host-bridge.ts +7 -0
- package/src/execution/engine/host/spawned-children.ts +1 -1
- package/src/execution/engine/inflight-snapshot.ts +92 -0
- package/src/execution/engine/port.ts +18 -0
- package/src/execution/path-encoding.ts +6 -0
- package/src/execution/subagent-service.ts +25 -0
- package/src/execution/worktree-registry.ts +11 -7
- 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(
|
|
371
|
-
this.listeners.add(
|
|
370
|
+
onChange(listener2) {
|
|
371
|
+
this.listeners.add(listener2);
|
|
372
372
|
return () => {
|
|
373
|
-
this.listeners.delete(
|
|
373
|
+
this.listeners.delete(listener2);
|
|
374
374
|
};
|
|
375
375
|
}
|
|
376
376
|
emit(event) {
|
|
377
|
-
for (const
|
|
377
|
+
for (const listener2 of this.listeners) {
|
|
378
378
|
try {
|
|
379
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1445
|
+
logger9.debug(`[engine-client:${this.engineId}] engine exited after intentional kill (${detail}) \u2014 expected`);
|
|
1264
1446
|
} else {
|
|
1265
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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 源替换,调用面(方法名/参数序)逐文件等价。 */
|