@zhushanwen/subagent-core 0.5.1 → 0.6.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/execution/engine/engines/zcode/reader.d.cts +1 -1
- package/dist/execution/engine/engines/zcode/reader.d.ts +1 -1
- package/dist/index.cjs +1122 -452
- package/dist/index.d.cts +188 -5
- package/dist/index.d.ts +188 -5
- package/dist/index.js +1133 -463
- package/dist/{types-Dv4QhSJ_.d.cts → types-C3fE3R9x.d.cts} +65 -0
- package/dist/{types-Dv4QhSJ_.d.ts → types-C3fE3R9x.d.ts} +65 -0
- package/dist.bundle/index.cjs +34933 -0
- package/package.json +2 -2
- package/src/execution/__tests__/agent-registry.test.ts +1 -1
- package/src/execution/__tests__/alive-store.test.ts +1 -1
- package/src/execution/__tests__/collect-budget.test.ts +291 -0
- package/src/execution/__tests__/collect-coordinator-service.test.ts +327 -0
- package/src/execution/__tests__/collect-coordinator.test.ts +371 -0
- package/src/execution/__tests__/collect-mixed-dispatch.test.ts +262 -0
- package/src/execution/__tests__/config-collect-sync.test.ts +120 -0
- package/src/execution/__tests__/config.test.ts +1 -1
- package/src/execution/__tests__/explicit-agent-ref-guard.test.ts +1 -1
- package/src/execution/__tests__/finalized-marker.test.ts +1 -1
- package/src/execution/__tests__/gc-timer.test.ts +1 -1
- package/src/execution/__tests__/manifest-store-tmp-recovery.test.ts +1 -1
- package/src/execution/__tests__/notify-batch.test.ts +500 -0
- package/src/execution/__tests__/notify-ledger.test.ts +27 -0
- package/src/execution/__tests__/pi-invocation.test.ts +34 -2
- package/src/execution/__tests__/record-entry-collect.test.ts +102 -0
- package/src/execution/__tests__/relay-env.test.ts +12 -1
- package/src/execution/__tests__/session-file-gc.test.ts +1 -1
- package/src/execution/__tests__/spawn-event-adapter.test.ts +2 -2
- package/src/execution/__tests__/start-collect-guard.test.ts +255 -0
- package/src/execution/__tests__/start-sync-model-guard.test.ts +1 -1
- package/src/execution/__tests__/subagent-actions-core.test.ts +6 -0
- package/src/execution/__tests__/subagent-service-multiproc-guard.test.ts +13 -0
- package/src/execution/__tests__/sync-collect-recovery.test.ts +1117 -0
- package/src/execution/__tests__/tombstone-store.test.ts +1 -1
- package/src/execution/__tests__/worktree-git-ops.test.ts +1 -1
- package/src/execution/__tests__/worktree-reconcile-aging.test.ts +2 -2
- package/src/execution/__tests__/worktree-reconcile.integration.test.ts +1 -1
- package/src/execution/__tests__/worktree-registry.test.ts +1 -1
- package/src/execution/collect-coordinator.ts +200 -0
- package/src/execution/config.ts +77 -5
- package/src/execution/engine/__tests__/common/event-journal.test.ts +1 -1
- package/src/execution/engine/__tests__/common/pool-manager.test.ts +2 -2
- package/src/execution/engine/__tests__/conformance/contract.read-degradation.test.ts +1 -1
- package/src/execution/engine/__tests__/conformance/golden-replay.pi.test.ts +1 -1
- package/src/execution/engine/engines/zcode/__tests__/appserver-launcher.test.ts +1 -1
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-degrade.test.ts +1 -1
- package/src/execution/execution-record.ts +7 -0
- package/src/execution/finalize-record.ts +15 -11
- package/src/execution/notifier.ts +234 -25
- package/src/execution/notify-host.ts +21 -1
- package/src/execution/record-entry.ts +16 -0
- package/src/execution/record-store.ts +161 -30
- package/src/execution/subagent-actions-core.ts +62 -6
- package/src/execution/subagent-service.ts +416 -7
- package/src/execution/sync-rebuild.ts +87 -0
- package/src/execution/types.ts +63 -0
- package/src/index.ts +1 -1
- package/src/orchestration/__tests__/config-loader.test.ts +1 -1
- package/src/orchestration/__tests__/file-run-store-prune.test.ts +1 -1
- package/src/orchestration/__tests__/file-run-store-throttle.test.ts +1 -1
- package/src/orchestration/__tests__/review-fix-loop-scriptpath-failfast.test.ts +1 -1
- package/src/orchestration/__tests__/script-generate.test.ts +5 -5
- package/src/orchestration/__tests__/skill-discovery.test.ts +2 -2
- package/src/orchestration/__tests__/workflow-files.test.ts +10 -10
- package/src/orchestration/__tests__/workflow-script-registry-impl.test.ts +1 -1
- package/src/shared/__tests__/atomic-write.test.ts +1 -1
- package/src/shared/__tests__/resource-discovery-host-roots.test.ts +5 -5
package/dist/index.cjs
CHANGED
|
@@ -830,8 +830,202 @@ function bestEffort(err, context, level = "debug") {
|
|
|
830
830
|
}
|
|
831
831
|
}
|
|
832
832
|
|
|
833
|
-
// src/execution/
|
|
833
|
+
// src/execution/collect-coordinator.ts
|
|
834
|
+
var COLLECT_SCAN_LIMIT = 1e3;
|
|
835
|
+
var CollectCoordinator = class {
|
|
836
|
+
constructor(deps) {
|
|
837
|
+
this.deps = deps;
|
|
838
|
+
}
|
|
839
|
+
deps;
|
|
840
|
+
/** 批缓冲(内存):已终态未 flush 的 sync 成员终态快照,跨 route 累积。
|
|
841
|
+
* [U8] 排程合批窗口期内保持可枚举(E9 dispose 转换依赖 pendingMembers 全量)。 */
|
|
842
|
+
buffer = [];
|
|
843
|
+
/** [U8] 挂起的合批排程定时器(undefined = 无排程)。 */
|
|
844
|
+
flushTimer;
|
|
845
|
+
/** notifyComplete 唯一路由入口。返回去向供测试/诊断断言。 */
|
|
846
|
+
route(record) {
|
|
847
|
+
if (record.collectMode !== "sync") {
|
|
848
|
+
this.deps.notifyAsync(record);
|
|
849
|
+
return "async";
|
|
850
|
+
}
|
|
851
|
+
const snapshotRecord = this.deps.toNotifyRecord(record);
|
|
852
|
+
if (!snapshotRecord) return "sync-skipped";
|
|
853
|
+
this.buffer.push(snapshotRecord);
|
|
854
|
+
return this.armFlushIfClosed() ? "sync-flushed" : "sync-buffered";
|
|
855
|
+
}
|
|
856
|
+
/** 诊断/测试:当前缓冲成员数(= 已终态未 flush 的 sync 成员数,含合批窗口内成员)。 */
|
|
857
|
+
get pendingCount() {
|
|
858
|
+
return this.buffer.length;
|
|
859
|
+
}
|
|
860
|
+
/** 诊断/测试 + E9 dispose 转换数据源:缓冲成员快照(副本,非活引用)。 */
|
|
861
|
+
pendingMembers() {
|
|
862
|
+
return [...this.buffer];
|
|
863
|
+
}
|
|
864
|
+
/** [U8·E9 交互] 取消挂起的合批排程(缓冲原样保留——dispose 转 async 通路仍读得到
|
|
865
|
+
* 全部成员)。无排程时 no-op;幂等。 */
|
|
866
|
+
cancelScheduledFlush() {
|
|
867
|
+
if (this.flushTimer !== void 0) {
|
|
868
|
+
clearTimeout(this.flushTimer);
|
|
869
|
+
this.flushTimer = void 0;
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
/** 闭合判定 + 合批排程武装:缓冲非空 && 无非终态 sync 成员 → 排程 flush。
|
|
873
|
+
* 已有排程挂起时幂等(同窗口合批,不重复定时器)。(S6 改名:原名 closeDetected
|
|
874
|
+
* 读作纯检测,实带副作用——武装排程改状态,名实相符优先。) */
|
|
875
|
+
armFlushIfClosed() {
|
|
876
|
+
if (this.buffer.length === 0) return false;
|
|
877
|
+
if (this.hasRunningSync()) return false;
|
|
878
|
+
this.scheduleFlush();
|
|
879
|
+
return true;
|
|
880
|
+
}
|
|
881
|
+
/** [U8] 同宏任务去抖合批排程:setTimeout(0)(Node 1ms clamp)——窗口跨度覆盖一个
|
|
882
|
+
* 完整事件循环轮次(当前 poll 段 I/O 回调 + 微任务链排空),足以收纳 finalize 链
|
|
883
|
+
* 间隙内的背靠背 route,同时无可观察延迟(毫秒级 < 任意 LLM/投递时标)。 */
|
|
884
|
+
scheduleFlush() {
|
|
885
|
+
if (this.flushTimer !== void 0) return;
|
|
886
|
+
this.flushTimer = setTimeout(() => {
|
|
887
|
+
this.flushTimer = void 0;
|
|
888
|
+
this.flushIfClosed();
|
|
889
|
+
}, 0);
|
|
890
|
+
}
|
|
891
|
+
/** 合批窗口到期:重验闭合条件后整体移交 flushBatch 并清空。
|
|
892
|
+
* 窗口内新 sync 成员注册 running(跨轮续累反转)→ 保留缓冲不 flush,等其终态
|
|
893
|
+
* route 重新排程(终态必经 notifyComplete,不丢、不悬挂)。
|
|
894
|
+
* void 不等待 flush 的 async 屏障序列(manifest → 写账 → 落标):缓冲已 splice
|
|
895
|
+
* 先行整体移交,屏障 await 期间窗口内新成员重新入空缓冲开新批,互不干扰。 */
|
|
896
|
+
flushIfClosed() {
|
|
897
|
+
if (this.buffer.length === 0) return;
|
|
898
|
+
if (this.hasRunningSync()) return;
|
|
899
|
+
const members = this.buffer.splice(0, this.buffer.length);
|
|
900
|
+
void this.deps.flushBatch(members);
|
|
901
|
+
}
|
|
902
|
+
/** 是否存在非终态 sync 成员(collectMode=sync && 无 batchFinalized && 非终态)。
|
|
903
|
+
* 非终态口径(⛔3 U1 已核实 + U3 resumable 补丁):status 非 closed 且非 resumable
|
|
904
|
+
* ——池排队/在跑成员在 store.register 时即 status="running",自动计入(闭合等待它);
|
|
905
|
+
* batchFinalized=true 的已离场成员不阻止闭合;running+resumable(SP-5 one-shot
|
|
906
|
+
* 成功回退态,进程已死结果已定格)视为已完成,不阻止闭合。 */
|
|
907
|
+
hasRunningSync() {
|
|
908
|
+
for (const record of this.deps.listRecords(COLLECT_SCAN_LIMIT)) {
|
|
909
|
+
if (record.collectMode === "sync" && record.batchFinalized !== true && record.resumable !== true && record.status !== "closed") {
|
|
910
|
+
return true;
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
return false;
|
|
914
|
+
}
|
|
915
|
+
};
|
|
916
|
+
|
|
917
|
+
// src/execution/config.ts
|
|
918
|
+
var fs3 = __toESM(require("fs"), 1);
|
|
919
|
+
var path3 = __toESM(require("path"), 1);
|
|
834
920
|
var logger4 = getLogger("subagents");
|
|
921
|
+
var DEFAULT_CONFIG = {
|
|
922
|
+
version: 1,
|
|
923
|
+
maxConcurrent: 6
|
|
924
|
+
};
|
|
925
|
+
var DEFAULT_MAX_CONCURRENT = 6;
|
|
926
|
+
var DEFAULT_COLLECT_SYNC = {
|
|
927
|
+
default: "async",
|
|
928
|
+
perItemChars: 4e3,
|
|
929
|
+
totalChars: 24e3
|
|
930
|
+
};
|
|
931
|
+
function getGlobalConfigPath(agentDir) {
|
|
932
|
+
return path3.join(agentDir, "subagents", "config.json");
|
|
933
|
+
}
|
|
934
|
+
function loadGlobalConfig(agentDir) {
|
|
935
|
+
try {
|
|
936
|
+
const raw = fs3.readFileSync(getGlobalConfigPath(agentDir), "utf-8");
|
|
937
|
+
const parsed = JSON.parse(raw);
|
|
938
|
+
return sanitizeParsedConfig(parsed);
|
|
939
|
+
} catch {
|
|
940
|
+
return { ...DEFAULT_CONFIG };
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
function readGlobalConfig(agentDir) {
|
|
944
|
+
const configPath = getGlobalConfigPath(agentDir);
|
|
945
|
+
let raw;
|
|
946
|
+
try {
|
|
947
|
+
raw = fs3.readFileSync(configPath, "utf-8");
|
|
948
|
+
} catch (err) {
|
|
949
|
+
if (errnoCodeOf(err) === "ENOENT") {
|
|
950
|
+
return { status: "absent", config: { ...DEFAULT_CONFIG } };
|
|
951
|
+
}
|
|
952
|
+
return readFailure(configPath, err);
|
|
953
|
+
}
|
|
954
|
+
try {
|
|
955
|
+
const parsed = JSON.parse(raw);
|
|
956
|
+
return { status: "ok", config: sanitizeParsedConfig(parsed) };
|
|
957
|
+
} catch (err) {
|
|
958
|
+
return readFailure(configPath, err);
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
function readFailure(configPath, err) {
|
|
962
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
963
|
+
logger4.warn(`[subagents] global config read failed (read-failure) at ${configPath}: ${reason}`);
|
|
964
|
+
return { status: "failed", reason };
|
|
965
|
+
}
|
|
966
|
+
function errnoCodeOf(err) {
|
|
967
|
+
if (typeof err !== "object" || err === null || !("code" in err)) return void 0;
|
|
968
|
+
const code = Reflect.get(err, "code");
|
|
969
|
+
return typeof code === "string" ? code : void 0;
|
|
970
|
+
}
|
|
971
|
+
function sanitizeParsedConfig(parsed) {
|
|
972
|
+
const defaultEngine = sanitizeDefaultEngine(parsed.defaultEngine);
|
|
973
|
+
const engineRouting = sanitizeEngineRouting(parsed.engineRouting);
|
|
974
|
+
const collectSync = sanitizeCollectSync(parsed.collectSync);
|
|
975
|
+
return {
|
|
976
|
+
version: parsed.version ?? DEFAULT_CONFIG.version,
|
|
977
|
+
maxConcurrent: sanitizeMaxConcurrent(parsed.maxConcurrent),
|
|
978
|
+
...defaultEngine !== void 0 ? { defaultEngine } : {},
|
|
979
|
+
...engineRouting !== void 0 ? { engineRouting } : {},
|
|
980
|
+
...collectSync !== void 0 ? { collectSync } : {}
|
|
981
|
+
};
|
|
982
|
+
}
|
|
983
|
+
function sanitizeMaxConcurrent(value) {
|
|
984
|
+
return sanitizePositiveInt(value, DEFAULT_MAX_CONCURRENT);
|
|
985
|
+
}
|
|
986
|
+
function sanitizeCollectSync(value) {
|
|
987
|
+
if (typeof value !== "object" || value === null) return void 0;
|
|
988
|
+
const v = value;
|
|
989
|
+
const fallback = DEFAULT_COLLECT_SYNC;
|
|
990
|
+
const fmt = (val) => typeof val === "string" ? JSON.stringify(val) : String(val);
|
|
991
|
+
const bad = [];
|
|
992
|
+
if (v.default !== void 0 && v.default !== "sync" && v.default !== "async") {
|
|
993
|
+
bad.push(`default=${fmt(v.default)}`);
|
|
994
|
+
}
|
|
995
|
+
if (v.perItemChars !== void 0 && !isPositiveInt(v.perItemChars)) {
|
|
996
|
+
bad.push(`perItemChars=${fmt(v.perItemChars)}`);
|
|
997
|
+
}
|
|
998
|
+
if (v.totalChars !== void 0 && !isPositiveInt(v.totalChars)) {
|
|
999
|
+
bad.push(`totalChars=${fmt(v.totalChars)}`);
|
|
1000
|
+
}
|
|
1001
|
+
if (bad.length > 0) {
|
|
1002
|
+
logger4.warn(
|
|
1003
|
+
`[subagents] config collectSync invalid field(s) reverted to defaults: ${bad.join(", ")}`
|
|
1004
|
+
);
|
|
1005
|
+
}
|
|
1006
|
+
return {
|
|
1007
|
+
default: v.default === "sync" ? "sync" : v.default === "async" ? "async" : fallback.default,
|
|
1008
|
+
perItemChars: sanitizePositiveInt(v.perItemChars, fallback.perItemChars),
|
|
1009
|
+
totalChars: sanitizePositiveInt(v.totalChars, fallback.totalChars)
|
|
1010
|
+
};
|
|
1011
|
+
}
|
|
1012
|
+
function sanitizePositiveInt(value, fallback) {
|
|
1013
|
+
return isPositiveInt(value) ? value : fallback;
|
|
1014
|
+
}
|
|
1015
|
+
function isPositiveInt(value) {
|
|
1016
|
+
return typeof value === "number" && Number.isInteger(value) && value > 0;
|
|
1017
|
+
}
|
|
1018
|
+
function sanitizeDefaultEngine(value) {
|
|
1019
|
+
return typeof value === "string" && value.trim() !== "" ? value : void 0;
|
|
1020
|
+
}
|
|
1021
|
+
function sanitizeEngineRouting(value) {
|
|
1022
|
+
if (typeof value !== "object" || value === null) return void 0;
|
|
1023
|
+
const strict = value.strict;
|
|
1024
|
+
return typeof strict === "boolean" ? { strict } : void 0;
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
// src/execution/lifecycle-manager.ts
|
|
1028
|
+
var logger5 = getLogger("subagents");
|
|
835
1029
|
var MS_PER_SECOND = 1e3;
|
|
836
1030
|
var SECONDS_PER_MINUTE = 60;
|
|
837
1031
|
var IDLE_TIMEOUT_MINUTES = 5;
|
|
@@ -841,7 +1035,7 @@ function getEnvIdleTimeoutMs() {
|
|
|
841
1035
|
if (!raw) return void 0;
|
|
842
1036
|
const parsed = Number(raw);
|
|
843
1037
|
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
844
|
-
|
|
1038
|
+
logger5.warn(
|
|
845
1039
|
`[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`
|
|
846
1040
|
);
|
|
847
1041
|
return void 0;
|
|
@@ -1001,7 +1195,7 @@ function createConcurrencyPool(options) {
|
|
|
1001
1195
|
}
|
|
1002
1196
|
|
|
1003
1197
|
// src/execution/cold-resurrect.ts
|
|
1004
|
-
var
|
|
1198
|
+
var fs4 = __toESM(require("fs"), 1);
|
|
1005
1199
|
|
|
1006
1200
|
// src/execution/execution-record.ts
|
|
1007
1201
|
var ACTIVITY_LABEL_MAX = 60;
|
|
@@ -1076,6 +1270,7 @@ function createRecord(id, identity) {
|
|
|
1076
1270
|
idleTimeoutMs: identity.idleTimeoutMs,
|
|
1077
1271
|
engine: identity.engine,
|
|
1078
1272
|
engineFallback: identity.engineFallback,
|
|
1273
|
+
collectMode: identity.collectMode,
|
|
1079
1274
|
// 状态(实时更新)
|
|
1080
1275
|
status: "running",
|
|
1081
1276
|
// turns[] 初始化为 [空 turn]——第一个 turn 从创建即存在,
|
|
@@ -1506,7 +1701,7 @@ function resurrectColdRecord(deps, found, id) {
|
|
|
1506
1701
|
if (wasClosed) {
|
|
1507
1702
|
if (record.sessionFile) {
|
|
1508
1703
|
try {
|
|
1509
|
-
|
|
1704
|
+
fs4.rmSync(`${record.sessionFile}.finalized`, { force: true });
|
|
1510
1705
|
writeAliveMarker(record.sessionFile, { pid: process.pid, id, startedAt: Date.now() });
|
|
1511
1706
|
} catch (_e) {
|
|
1512
1707
|
void _e;
|
|
@@ -1537,41 +1732,41 @@ function coldLookupForAction(deps, id, allowReconnect) {
|
|
|
1537
1732
|
}
|
|
1538
1733
|
|
|
1539
1734
|
// src/execution/finalize-record.ts
|
|
1540
|
-
var
|
|
1541
|
-
var
|
|
1735
|
+
var fs8 = __toESM(require("fs"), 1);
|
|
1736
|
+
var path5 = __toESM(require("path"), 1);
|
|
1542
1737
|
|
|
1543
1738
|
// src/execution/finalized-marker.ts
|
|
1544
|
-
var
|
|
1739
|
+
var fs5 = __toESM(require("fs"), 1);
|
|
1545
1740
|
function writeFinalized(sessionFile, reason) {
|
|
1546
1741
|
try {
|
|
1547
|
-
|
|
1548
|
-
|
|
1742
|
+
fs5.rmSync(`${sessionFile}.cancelled`, { force: true });
|
|
1743
|
+
fs5.writeFileSync(`${sessionFile}.finalized`, reason ?? "", "utf-8");
|
|
1549
1744
|
} catch (_e) {
|
|
1550
1745
|
void _e;
|
|
1551
1746
|
}
|
|
1552
1747
|
}
|
|
1553
1748
|
function readFinalizedReason(sessionFile) {
|
|
1554
1749
|
try {
|
|
1555
|
-
return
|
|
1750
|
+
return fs5.readFileSync(`${sessionFile}.finalized`, "utf-8").trim();
|
|
1556
1751
|
} catch {
|
|
1557
1752
|
return void 0;
|
|
1558
1753
|
}
|
|
1559
1754
|
}
|
|
1560
1755
|
|
|
1561
1756
|
// src/execution/path-encoding.ts
|
|
1562
|
-
var
|
|
1757
|
+
var path4 = __toESM(require("path"), 1);
|
|
1563
1758
|
function encodeCwd(cwd) {
|
|
1564
1759
|
return "--" + cwd.replace(/^[/\\]/, "").replace(/[/\\:]/g, "-") + "--";
|
|
1565
1760
|
}
|
|
1566
1761
|
function getSubagentSessionDir(agentDir, mainCwd) {
|
|
1567
|
-
return
|
|
1762
|
+
return path4.join(agentDir, "subagents", encodeCwd(mainCwd), "sessions");
|
|
1568
1763
|
}
|
|
1569
1764
|
function getSubagentRecordsDir(agentDir, mainCwd) {
|
|
1570
|
-
return
|
|
1765
|
+
return path4.join(agentDir, "subagents", encodeCwd(mainCwd), "records");
|
|
1571
1766
|
}
|
|
1572
1767
|
|
|
1573
1768
|
// src/execution/session-reconstructor.ts
|
|
1574
|
-
var
|
|
1769
|
+
var fs6 = __toESM(require("fs"), 1);
|
|
1575
1770
|
var IDENTITY_CUSTOM_TYPE = "subagent-identity";
|
|
1576
1771
|
var TURN_SUMMARY_MAX2 = 80;
|
|
1577
1772
|
function deriveEventLog(turns, lastError, startedAt) {
|
|
@@ -1625,7 +1820,7 @@ function isIdentityData(data) {
|
|
|
1625
1820
|
function readJsonlEntries(sessionFile) {
|
|
1626
1821
|
let raw;
|
|
1627
1822
|
try {
|
|
1628
|
-
raw =
|
|
1823
|
+
raw = fs6.readFileSync(sessionFile, "utf-8");
|
|
1629
1824
|
} catch {
|
|
1630
1825
|
return void 0;
|
|
1631
1826
|
}
|
|
@@ -1795,18 +1990,18 @@ var IDENTITY_HEAD_BYTES = 65536;
|
|
|
1795
1990
|
function readIdentityHeader(sessionFile) {
|
|
1796
1991
|
let text;
|
|
1797
1992
|
try {
|
|
1798
|
-
const fd =
|
|
1993
|
+
const fd = fs6.openSync(sessionFile, "r");
|
|
1799
1994
|
try {
|
|
1800
1995
|
const buf = Buffer.alloc(IDENTITY_HEAD_BYTES);
|
|
1801
1996
|
let total = 0;
|
|
1802
1997
|
while (total < buf.length) {
|
|
1803
|
-
const n =
|
|
1998
|
+
const n = fs6.readSync(fd, buf, total, buf.length - total, total);
|
|
1804
1999
|
if (n <= 0) break;
|
|
1805
2000
|
total += n;
|
|
1806
2001
|
}
|
|
1807
2002
|
text = buf.toString("utf-8", 0, total);
|
|
1808
2003
|
} finally {
|
|
1809
|
-
|
|
2004
|
+
fs6.closeSync(fd);
|
|
1810
2005
|
}
|
|
1811
2006
|
} catch {
|
|
1812
2007
|
return void 0;
|
|
@@ -1816,7 +2011,7 @@ function readIdentityHeader(sessionFile) {
|
|
|
1816
2011
|
function readIdentityAnywhere(sessionFile) {
|
|
1817
2012
|
let text;
|
|
1818
2013
|
try {
|
|
1819
|
-
text =
|
|
2014
|
+
text = fs6.readFileSync(sessionFile, "utf-8");
|
|
1820
2015
|
} catch {
|
|
1821
2016
|
return void 0;
|
|
1822
2017
|
}
|
|
@@ -1832,20 +2027,20 @@ function readIdentityAnywhere(sessionFile) {
|
|
|
1832
2027
|
function readIdentityTail(sessionFile) {
|
|
1833
2028
|
let text;
|
|
1834
2029
|
try {
|
|
1835
|
-
const { size } =
|
|
2030
|
+
const { size } = fs6.statSync(sessionFile);
|
|
1836
2031
|
const start = Math.max(0, size - IDENTITY_HEAD_BYTES);
|
|
1837
|
-
const fd =
|
|
2032
|
+
const fd = fs6.openSync(sessionFile, "r");
|
|
1838
2033
|
try {
|
|
1839
2034
|
const buf = Buffer.alloc(size - start);
|
|
1840
2035
|
let total = 0;
|
|
1841
2036
|
while (total < buf.length) {
|
|
1842
|
-
const n =
|
|
2037
|
+
const n = fs6.readSync(fd, buf, total, buf.length - total, start + total);
|
|
1843
2038
|
if (n <= 0) break;
|
|
1844
2039
|
total += n;
|
|
1845
2040
|
}
|
|
1846
2041
|
text = buf.toString("utf-8", 0, total);
|
|
1847
2042
|
} finally {
|
|
1848
|
-
|
|
2043
|
+
fs6.closeSync(fd);
|
|
1849
2044
|
}
|
|
1850
2045
|
} catch {
|
|
1851
2046
|
return void 0;
|
|
@@ -1920,11 +2115,11 @@ function parseIdentityFromText(text, sessionFile) {
|
|
|
1920
2115
|
}
|
|
1921
2116
|
|
|
1922
2117
|
// src/execution/tombstone-store.ts
|
|
1923
|
-
var
|
|
2118
|
+
var fs7 = __toESM(require("fs"), 1);
|
|
1924
2119
|
function writeCancelledTombstone(sessionFile, meta) {
|
|
1925
2120
|
try {
|
|
1926
2121
|
const tombstonePath = `${sessionFile}.cancelled`;
|
|
1927
|
-
|
|
2122
|
+
fs7.writeFileSync(tombstonePath, `${JSON.stringify(meta)}
|
|
1928
2123
|
`, "utf-8");
|
|
1929
2124
|
} catch (_e) {
|
|
1930
2125
|
void _e;
|
|
@@ -1933,7 +2128,7 @@ function writeCancelledTombstone(sessionFile, meta) {
|
|
|
1933
2128
|
function readCancelledTombstone(sessionFile) {
|
|
1934
2129
|
let raw;
|
|
1935
2130
|
try {
|
|
1936
|
-
raw =
|
|
2131
|
+
raw = fs7.readFileSync(`${sessionFile}.cancelled`, "utf-8");
|
|
1937
2132
|
} catch {
|
|
1938
2133
|
return void 0;
|
|
1939
2134
|
}
|
|
@@ -1949,17 +2144,17 @@ function readCancelledTombstone(sessionFile) {
|
|
|
1949
2144
|
}
|
|
1950
2145
|
|
|
1951
2146
|
// src/execution/finalize-record.ts
|
|
1952
|
-
var
|
|
2147
|
+
var logger6 = getLogger("subagents");
|
|
1953
2148
|
function findSessionFileByRecordIdentity(sessionDir, recordId) {
|
|
1954
2149
|
let names;
|
|
1955
2150
|
try {
|
|
1956
|
-
names =
|
|
2151
|
+
names = fs8.readdirSync(sessionDir);
|
|
1957
2152
|
} catch {
|
|
1958
2153
|
return void 0;
|
|
1959
2154
|
}
|
|
1960
2155
|
for (const name of names) {
|
|
1961
2156
|
if (!name.endsWith(".jsonl")) continue;
|
|
1962
|
-
const full =
|
|
2157
|
+
const full = path5.join(sessionDir, name);
|
|
1963
2158
|
const recon = readIdentityHeader(full) ?? readIdentityTail(full);
|
|
1964
2159
|
if (recon?.id === recordId) return full;
|
|
1965
2160
|
}
|
|
@@ -1970,7 +2165,7 @@ function resolveMissingSessionFile(deps, record) {
|
|
|
1970
2165
|
const resolved = findSessionFileByRecordIdentity(deps.sessionDir, record.id);
|
|
1971
2166
|
if (resolved) {
|
|
1972
2167
|
record.sessionFile = resolved;
|
|
1973
|
-
|
|
2168
|
+
logger6.warn(
|
|
1974
2169
|
`[subagent] finalizeRecord: sessionFile was missing, resolved via sessionDir identity lookup: ${resolved}`
|
|
1975
2170
|
);
|
|
1976
2171
|
}
|
|
@@ -1982,8 +2177,8 @@ async function collectPatchIfWorktree(deps, record) {
|
|
|
1982
2177
|
deps.modelService.getAgentDir(),
|
|
1983
2178
|
record.worktreeHandle.mainCwd
|
|
1984
2179
|
);
|
|
1985
|
-
|
|
1986
|
-
const patchFile =
|
|
2180
|
+
fs8.mkdirSync(sessionsDir, { recursive: true });
|
|
2181
|
+
const patchFile = path5.join(sessionsDir, `${record.worktreeHandle.branch}.patch`);
|
|
1987
2182
|
const patch = await deps.worktreeManager.collectPatch(record.worktreeHandle, patchFile);
|
|
1988
2183
|
if (patch.written) record.patchFile = patchFile;
|
|
1989
2184
|
} catch (pe) {
|
|
@@ -2042,7 +2237,7 @@ async function writeManifestBestEffort(deps, record) {
|
|
|
2042
2237
|
});
|
|
2043
2238
|
} catch (err) {
|
|
2044
2239
|
const msg = err instanceof Error ? err.message : String(err);
|
|
2045
|
-
|
|
2240
|
+
logger6.error(`[subagent] manifest \u5199\u5165\u5931\u8D25 (record=${record.id}): ${msg}`);
|
|
2046
2241
|
deps.pi?.appendEntry?.("subagent:manifest-write-failed", {
|
|
2047
2242
|
id: record.id,
|
|
2048
2243
|
error: msg
|
|
@@ -2174,7 +2369,7 @@ var ExecutionNestingContext = class {
|
|
|
2174
2369
|
};
|
|
2175
2370
|
|
|
2176
2371
|
// src/execution/engine/common/data-dir.ts
|
|
2177
|
-
var
|
|
2372
|
+
var logger7 = getLogger("subagents");
|
|
2178
2373
|
var XYZ_DATA_DIR_ENV = "XYZ_AGENT_DATA_DIR";
|
|
2179
2374
|
var warned = false;
|
|
2180
2375
|
function getEngineDataDir(env = process.env, warn = defaultWarn) {
|
|
@@ -2190,14 +2385,14 @@ function getEngineDataDir(env = process.env, warn = defaultWarn) {
|
|
|
2190
2385
|
return fallback;
|
|
2191
2386
|
}
|
|
2192
2387
|
function defaultWarn(msg) {
|
|
2193
|
-
|
|
2388
|
+
logger7.warn(msg);
|
|
2194
2389
|
}
|
|
2195
2390
|
|
|
2196
2391
|
// src/execution/engine/common/event-journal.ts
|
|
2197
2392
|
var import_promises = require("fs/promises");
|
|
2198
2393
|
var import_node_fs2 = require("fs");
|
|
2199
2394
|
var import_node_path2 = require("path");
|
|
2200
|
-
var
|
|
2395
|
+
var logger8 = getLogger("subagents");
|
|
2201
2396
|
var FLUSH_THRESHOLD_LINES = 64;
|
|
2202
2397
|
var FLUSH_THRESHOLD_BYTES = 32 * 1024;
|
|
2203
2398
|
var defaultFs = {
|
|
@@ -2306,7 +2501,7 @@ var JournalWriter = class {
|
|
|
2306
2501
|
}
|
|
2307
2502
|
};
|
|
2308
2503
|
function defaultWarn2(msg) {
|
|
2309
|
-
|
|
2504
|
+
logger8.warn(msg);
|
|
2310
2505
|
}
|
|
2311
2506
|
function replayJournal(path21) {
|
|
2312
2507
|
let raw;
|
|
@@ -2422,8 +2617,8 @@ function executeOptionsToEngineTaskSpec(opts) {
|
|
|
2422
2617
|
|
|
2423
2618
|
// src/execution/engine/engines/pi/pi-engine.ts
|
|
2424
2619
|
var import_node_child_process2 = require("child_process");
|
|
2425
|
-
var
|
|
2426
|
-
var
|
|
2620
|
+
var fs14 = __toESM(require("fs"), 1);
|
|
2621
|
+
var path9 = __toESM(require("path"), 1);
|
|
2427
2622
|
|
|
2428
2623
|
// src/orchestration/models/types.ts
|
|
2429
2624
|
var VALID_RUN_TRANSITIONS = {
|
|
@@ -2457,8 +2652,8 @@ function stringifySchemaCached(schema, mode) {
|
|
|
2457
2652
|
}
|
|
2458
2653
|
|
|
2459
2654
|
// src/execution/engine/engines/pi/pi-invocation.ts
|
|
2460
|
-
var
|
|
2461
|
-
var
|
|
2655
|
+
var fs9 = __toESM(require("fs"), 1);
|
|
2656
|
+
var path6 = __toESM(require("path"), 1);
|
|
2462
2657
|
|
|
2463
2658
|
// src/execution/relay-env.ts
|
|
2464
2659
|
var RELAY_ENV_SOCKET = "XYZ_SUBAGENT_RELAY_SOCKET";
|
|
@@ -2473,7 +2668,7 @@ function isRelayActive(env) {
|
|
|
2473
2668
|
// src/execution/engine/engines/pi/pi-invocation.ts
|
|
2474
2669
|
var BUN_VIRTUAL_PREFIX = "/$bunfs/root/";
|
|
2475
2670
|
function isGenericRuntime(execPath) {
|
|
2476
|
-
const execName =
|
|
2671
|
+
const execName = path6.basename(execPath).toLowerCase();
|
|
2477
2672
|
return /^(node|bun)(\.exe)?$/.test(execName);
|
|
2478
2673
|
}
|
|
2479
2674
|
var scriptExistsCache;
|
|
@@ -2482,7 +2677,7 @@ function currentScriptExists() {
|
|
|
2482
2677
|
if (scriptExistsCache === void 0 || scriptExistsCache.script !== currentScript) {
|
|
2483
2678
|
scriptExistsCache = {
|
|
2484
2679
|
script: currentScript,
|
|
2485
|
-
exists: currentScript !== void 0 && !currentScript.startsWith(BUN_VIRTUAL_PREFIX) &&
|
|
2680
|
+
exists: currentScript !== void 0 && !currentScript.startsWith(BUN_VIRTUAL_PREFIX) && fs9.existsSync(currentScript)
|
|
2486
2681
|
};
|
|
2487
2682
|
}
|
|
2488
2683
|
return scriptExistsCache.exists;
|
|
@@ -2589,11 +2784,11 @@ async function readPiSessionView(sessionFile) {
|
|
|
2589
2784
|
|
|
2590
2785
|
// src/execution/engine/engines/pi/session-runner.ts
|
|
2591
2786
|
var import_node_child_process = require("child_process");
|
|
2592
|
-
var
|
|
2787
|
+
var fs13 = __toESM(require("fs"), 1);
|
|
2593
2788
|
|
|
2594
2789
|
// src/execution/session-pending.ts
|
|
2595
|
-
var
|
|
2596
|
-
var
|
|
2790
|
+
var fs10 = __toESM(require("fs"), 1);
|
|
2791
|
+
var logger9 = getLogger("subagents");
|
|
2597
2792
|
var RECENT_UNREGISTER_WINDOW_MS = 6e4;
|
|
2598
2793
|
var cursors = /* @__PURE__ */ new Map();
|
|
2599
2794
|
function prunePendingCursor(sessionFile) {
|
|
@@ -2631,7 +2826,7 @@ function pendingFileUnreadableMessage(err) {
|
|
|
2631
2826
|
}
|
|
2632
2827
|
function statPendingFileSize(sessionFile) {
|
|
2633
2828
|
try {
|
|
2634
|
-
return
|
|
2829
|
+
return fs10.statSync(sessionFile).size;
|
|
2635
2830
|
} catch (err) {
|
|
2636
2831
|
cursors.delete(sessionFile);
|
|
2637
2832
|
return { error: pendingFileUnreadableMessage(err) };
|
|
@@ -2640,7 +2835,7 @@ function statPendingFileSize(sessionFile) {
|
|
|
2640
2835
|
function readPendingChunk(sessionFile, cursor, size) {
|
|
2641
2836
|
try {
|
|
2642
2837
|
if (cursor.offset === 0) {
|
|
2643
|
-
return
|
|
2838
|
+
return fs10.readFileSync(sessionFile, "utf-8");
|
|
2644
2839
|
}
|
|
2645
2840
|
return readPendingChunkFrom(sessionFile, cursor.offset, size - cursor.offset);
|
|
2646
2841
|
} catch (err) {
|
|
@@ -2650,17 +2845,17 @@ function readPendingChunk(sessionFile, cursor, size) {
|
|
|
2650
2845
|
}
|
|
2651
2846
|
function readPendingChunkFrom(sessionFile, offset, len) {
|
|
2652
2847
|
const buf = Buffer.alloc(len);
|
|
2653
|
-
const fd =
|
|
2848
|
+
const fd = fs10.openSync(sessionFile, "r");
|
|
2654
2849
|
try {
|
|
2655
2850
|
let total = 0;
|
|
2656
2851
|
while (total < len) {
|
|
2657
|
-
const n =
|
|
2852
|
+
const n = fs10.readSync(fd, buf, total, len - total, offset + total);
|
|
2658
2853
|
if (n <= 0) break;
|
|
2659
2854
|
total += n;
|
|
2660
2855
|
}
|
|
2661
2856
|
return buf.toString("utf-8", 0, total);
|
|
2662
2857
|
} finally {
|
|
2663
|
-
|
|
2858
|
+
fs10.closeSync(fd);
|
|
2664
2859
|
}
|
|
2665
2860
|
}
|
|
2666
2861
|
function consumePendingLines(complete, cursor, sessionFile) {
|
|
@@ -2685,7 +2880,7 @@ function applyPendingLine(line, cursor, sessionFile) {
|
|
|
2685
2880
|
if (Number.isFinite(ts) && ts > cursor.latestUnregisterMs) cursor.latestUnregisterMs = ts;
|
|
2686
2881
|
}
|
|
2687
2882
|
} catch {
|
|
2688
|
-
|
|
2883
|
+
logger9.debug("skipped malformed pending line", { sessionFile });
|
|
2689
2884
|
}
|
|
2690
2885
|
}
|
|
2691
2886
|
function extractPendingEntryId(data) {
|
|
@@ -2726,7 +2921,7 @@ function listActivePendingFromSessionFile(sessionFile) {
|
|
|
2726
2921
|
}
|
|
2727
2922
|
|
|
2728
2923
|
// src/execution/engine/common/kill-chain.ts
|
|
2729
|
-
var
|
|
2924
|
+
var logger10 = getLogger("subagents");
|
|
2730
2925
|
var MS_PER_SECOND3 = 1e3;
|
|
2731
2926
|
var SIGKILL_REAP_TIMEOUT_MS = 1e4;
|
|
2732
2927
|
var HOST_TIMEOUT_ABORT_REASON = "agent-call-timeout";
|
|
@@ -2738,7 +2933,7 @@ async function killChain(child, opts) {
|
|
|
2738
2933
|
if (graceful === "settled") return "terminated";
|
|
2739
2934
|
if (child.exitCode !== null || child.signalCode !== null) return "terminated";
|
|
2740
2935
|
if (opts.escalationNote !== void 0) {
|
|
2741
|
-
|
|
2936
|
+
logger10.warn(
|
|
2742
2937
|
`[kill-chain] ${opts.escalationNote} still alive ${opts.graceMs / MS_PER_SECOND3}s after SIGTERM, escalating to SIGKILL`
|
|
2743
2938
|
);
|
|
2744
2939
|
}
|
|
@@ -2750,7 +2945,7 @@ function safeKill(child, signal) {
|
|
|
2750
2945
|
try {
|
|
2751
2946
|
child.kill(signal);
|
|
2752
2947
|
} catch (err) {
|
|
2753
|
-
|
|
2948
|
+
logger10.debug(
|
|
2754
2949
|
`[kill-chain] ${signal} on exited process: ${toErrorMessage(err)}`
|
|
2755
2950
|
);
|
|
2756
2951
|
}
|
|
@@ -2872,7 +3067,7 @@ function parseMirrorFlagArgs(flagArgs) {
|
|
|
2872
3067
|
|
|
2873
3068
|
// src/execution/engine/engines/pi/stdin-writer.ts
|
|
2874
3069
|
var crypto2 = __toESM(require("crypto"), 1);
|
|
2875
|
-
var
|
|
3070
|
+
var logger11 = getLogger("subagents");
|
|
2876
3071
|
var epipeConsecutiveFailures = /* @__PURE__ */ new Map();
|
|
2877
3072
|
var EPIPE_FAILURE_THRESHOLD = 2;
|
|
2878
3073
|
function recordEpipeFailure(recordId) {
|
|
@@ -2894,7 +3089,7 @@ function respond(child, id, out, signal) {
|
|
|
2894
3089
|
else if ("confirmed" in out) line = JSON.stringify({ type: "extension_ui_response", id, confirmed: out.confirmed });
|
|
2895
3090
|
else if ("cancelled" in out) line = JSON.stringify({ type: "extension_ui_response", id, cancelled: true });
|
|
2896
3091
|
} catch (err) {
|
|
2897
|
-
|
|
3092
|
+
logger11.warn(`[subagents] JSON.stringify failed for ui response ${id}, degrading to cancelled`, {
|
|
2898
3093
|
detail: toErrorMessage(err)
|
|
2899
3094
|
});
|
|
2900
3095
|
line = JSON.stringify({ type: "extension_ui_response", id, cancelled: true });
|
|
@@ -2927,14 +3122,14 @@ function writeStdinLine(child, line, warnTag) {
|
|
|
2927
3122
|
if (!child.stdin || child.stdin.destroyed) return;
|
|
2928
3123
|
try {
|
|
2929
3124
|
const ok = child.stdin.write(line + "\n");
|
|
2930
|
-
if (!ok)
|
|
3125
|
+
if (!ok) logger11.warn(`[subagents] stdin backpressure on ${warnTag}`);
|
|
2931
3126
|
} catch (err) {
|
|
2932
3127
|
if (err !== null && typeof err === "object" && "code" in err && (err.code === "EPIPE" || err.code === "ERR_STREAM_DESTROYED")) {
|
|
2933
3128
|
throw new Error(
|
|
2934
3129
|
`[subagents] EPIPE on stdin write (${warnTag}): pipe broken, child process likely exited. Recovery: treat as dead process and resume via cold path.`
|
|
2935
3130
|
);
|
|
2936
3131
|
}
|
|
2937
|
-
|
|
3132
|
+
logger11.warn(`[subagents] unexpected stdin write error on ${warnTag}`, {
|
|
2938
3133
|
detail: toErrorMessage(err)
|
|
2939
3134
|
});
|
|
2940
3135
|
}
|
|
@@ -3243,7 +3438,7 @@ function schemaEnvByteLength(schemaEnv) {
|
|
|
3243
3438
|
}
|
|
3244
3439
|
|
|
3245
3440
|
// src/execution/settled-watchdog.ts
|
|
3246
|
-
var
|
|
3441
|
+
var logger12 = getLogger("subagents");
|
|
3247
3442
|
var MS_PER_SECOND4 = 1e3;
|
|
3248
3443
|
var SECONDS_PER_MINUTE2 = 60;
|
|
3249
3444
|
var MID_ROUND_MINUTES = 30;
|
|
@@ -3259,13 +3454,13 @@ function resolveSettledWatchdogEnv() {
|
|
|
3259
3454
|
if (raw === void 0 || raw.trim() === "") return envCache;
|
|
3260
3455
|
const parsed = Number(raw);
|
|
3261
3456
|
if (!Number.isFinite(parsed)) {
|
|
3262
|
-
|
|
3457
|
+
logger12.warn(
|
|
3263
3458
|
`[settled-watchdog] ${SETTLED_WATCHDOG_ENV}="${raw}" is invalid (expected a millisecond number) \u2014 falling back to default settled phase limit (${SETTLED_WATCHDOG_TIMEOUT_MS}ms); set a plain ms value (e.g. 60000) to override, or 0 to disable both phases`
|
|
3264
3459
|
);
|
|
3265
3460
|
return envCache;
|
|
3266
3461
|
}
|
|
3267
3462
|
if (parsed <= 0) {
|
|
3268
|
-
|
|
3463
|
+
logger12.warn(
|
|
3269
3464
|
`[settled-watchdog] ${SETTLED_WATCHDOG_ENV}=${parsed} disables BOTH watchdog phases (mid-round no-progress + settled phase limit). Consequence: a wedged chatMode round (no agent_end, or agent_settled never arriving) has NO independent recovery timer \u2014 the process leaks until the host exits (the "three-no-window" shape). Recovery: unset the env or set a positive ms value.`
|
|
3270
3465
|
);
|
|
3271
3466
|
envCache.disabled = true;
|
|
@@ -3292,7 +3487,7 @@ function armMidRoundNoProgress(recordId, handlers) {
|
|
|
3292
3487
|
assertSafeTimerDelay(SETTLED_MID_ROUND_NO_PROGRESS_MS, "settled watchdog (mid-round)");
|
|
3293
3488
|
const timer = setTimeout(() => {
|
|
3294
3489
|
armedEntries.delete(recordId);
|
|
3295
|
-
|
|
3490
|
+
logger12.debug(
|
|
3296
3491
|
`[settled-watchdog] mid-round fired for ${recordId} after ${SETTLED_MID_ROUND_NO_PROGRESS_MS}ms without a valid protocol event`
|
|
3297
3492
|
);
|
|
3298
3493
|
handlers.onMidTimeout({ phase: "mid-round", waitedMs: SETTLED_MID_ROUND_NO_PROGRESS_MS });
|
|
@@ -3312,7 +3507,7 @@ function armSettledWatchdog(recordId, onTimeout) {
|
|
|
3312
3507
|
assertSafeTimerDelay(windowMs, "settled watchdog");
|
|
3313
3508
|
const timer = setTimeout(() => {
|
|
3314
3509
|
armedEntries.delete(recordId);
|
|
3315
|
-
|
|
3510
|
+
logger12.debug(
|
|
3316
3511
|
`[settled-watchdog] settled phase fired for ${recordId} after ${windowMs}ms without agent_settled`
|
|
3317
3512
|
);
|
|
3318
3513
|
onTimeout({ phase: "settled", waitedMs: windowMs });
|
|
@@ -3335,7 +3530,7 @@ function refreshMidRoundNoProgress(recordId) {
|
|
|
3335
3530
|
const onMidTimeout = entry.onMidTimeout;
|
|
3336
3531
|
const timer = setTimeout(() => {
|
|
3337
3532
|
armedEntries.delete(recordId);
|
|
3338
|
-
|
|
3533
|
+
logger12.debug(
|
|
3339
3534
|
`[settled-watchdog] mid-round fired for ${recordId} after ${SETTLED_MID_ROUND_NO_PROGRESS_MS}ms without a valid protocol event`
|
|
3340
3535
|
);
|
|
3341
3536
|
onMidTimeout?.({ phase: "mid-round", waitedMs: SETTLED_MID_ROUND_NO_PROGRESS_MS });
|
|
@@ -3351,8 +3546,8 @@ function disarmSettledWatchdog(recordId) {
|
|
|
3351
3546
|
var MAX_FORK_DEPTH = 10;
|
|
3352
3547
|
|
|
3353
3548
|
// src/execution/engine/engines/pi/spawn-event-adapter.ts
|
|
3354
|
-
var
|
|
3355
|
-
var
|
|
3549
|
+
var fs11 = __toESM(require("fs"), 1);
|
|
3550
|
+
var path7 = __toESM(require("path"), 1);
|
|
3356
3551
|
function isSessionHeader(obj) {
|
|
3357
3552
|
if (typeof obj !== "object" || obj === null) return false;
|
|
3358
3553
|
const r = obj;
|
|
@@ -3500,28 +3695,28 @@ function deriveSessionFilePath(header, sessionDir) {
|
|
|
3500
3695
|
}
|
|
3501
3696
|
function findSessionFileByHeaderId(sessionDir, sessionId) {
|
|
3502
3697
|
try {
|
|
3503
|
-
const files =
|
|
3698
|
+
const files = fs11.readdirSync(sessionDir);
|
|
3504
3699
|
const match = files.find((f) => f.endsWith(`_${sessionId}.jsonl`));
|
|
3505
|
-
return match ?
|
|
3700
|
+
return match ? path7.join(sessionDir, match) : void 0;
|
|
3506
3701
|
} catch {
|
|
3507
3702
|
return void 0;
|
|
3508
3703
|
}
|
|
3509
3704
|
}
|
|
3510
3705
|
|
|
3511
3706
|
// src/execution/engine/engines/pi/temp-prompt.ts
|
|
3512
|
-
var
|
|
3707
|
+
var fs12 = __toESM(require("fs"), 1);
|
|
3513
3708
|
var os = __toESM(require("os"), 1);
|
|
3514
|
-
var
|
|
3709
|
+
var path8 = __toESM(require("path"), 1);
|
|
3515
3710
|
async function writePromptToTempFile(agentName, prompt) {
|
|
3516
|
-
const dir = await
|
|
3711
|
+
const dir = await fs12.promises.mkdtemp(path8.join(os.tmpdir(), "pi-subagent-"));
|
|
3517
3712
|
const safeName = agentName.replace(/[^\w.-]+/g, "_");
|
|
3518
|
-
const filePath =
|
|
3519
|
-
await
|
|
3713
|
+
const filePath = path8.join(dir, `prompt-${safeName}.md`);
|
|
3714
|
+
await fs12.promises.writeFile(filePath, prompt, { encoding: "utf-8", mode: 384 });
|
|
3520
3715
|
return { dir, filePath };
|
|
3521
3716
|
}
|
|
3522
3717
|
async function cleanupTempPrompt(file) {
|
|
3523
3718
|
try {
|
|
3524
|
-
await
|
|
3719
|
+
await fs12.promises.rm(file.dir, { recursive: true, force: true });
|
|
3525
3720
|
} catch (err) {
|
|
3526
3721
|
bestEffort(err, `cleanup temp prompt dir ${file.dir}`);
|
|
3527
3722
|
}
|
|
@@ -3666,7 +3861,7 @@ function createUiChannelRegistry() {
|
|
|
3666
3861
|
}
|
|
3667
3862
|
|
|
3668
3863
|
// src/execution/ui-request-observability.ts
|
|
3669
|
-
var
|
|
3864
|
+
var logger13 = getLogger("subagents");
|
|
3670
3865
|
var GLOBAL_OBSERVABILITY_KEY = /* @__PURE__ */ Symbol.for("pi-subagent-workflow.ui-observability");
|
|
3671
3866
|
function registerGlobalObservability(obs) {
|
|
3672
3867
|
globalThis[GLOBAL_OBSERVABILITY_KEY] = obs;
|
|
@@ -3676,7 +3871,7 @@ function notifyMissingHandlerGlobal(sessionId) {
|
|
|
3676
3871
|
if (obs) {
|
|
3677
3872
|
obs.notifyMissingHandler(sessionId);
|
|
3678
3873
|
} else {
|
|
3679
|
-
|
|
3874
|
+
logger13.warn(
|
|
3680
3875
|
`[subagents] uiRequestHandler missing (session=${sessionId}, global observability not registered)`
|
|
3681
3876
|
);
|
|
3682
3877
|
}
|
|
@@ -3704,12 +3899,12 @@ var UiRequestObservability = class {
|
|
|
3704
3899
|
this.warnedMissingHandlerSessions.clear();
|
|
3705
3900
|
}
|
|
3706
3901
|
this.warnedMissingHandlerSessions.add(sessionId);
|
|
3707
|
-
|
|
3902
|
+
logger13.warn(`[subagents] uiRequestHandler missing (session=${sessionId}, mode=${this.sessionMode})`);
|
|
3708
3903
|
}
|
|
3709
3904
|
};
|
|
3710
3905
|
|
|
3711
3906
|
// src/execution/ui-request-queue.ts
|
|
3712
|
-
var
|
|
3907
|
+
var logger14 = getLogger("subagents");
|
|
3713
3908
|
function createUiRequestQueue(child, ctx) {
|
|
3714
3909
|
const abortController = new AbortController();
|
|
3715
3910
|
const queue = [];
|
|
@@ -3721,7 +3916,7 @@ function createUiRequestQueue(child, ctx) {
|
|
|
3721
3916
|
const { id, request, signal } = queue.shift();
|
|
3722
3917
|
handleUiRequest(child, id, request, ctx, signal).catch((err) => {
|
|
3723
3918
|
const m = toErrorMessage(err);
|
|
3724
|
-
|
|
3919
|
+
logger14.error(`[subagents] ui request ${id} (${request.method}) failed unexpectedly: ${m}`);
|
|
3725
3920
|
}).finally(() => {
|
|
3726
3921
|
processing = false;
|
|
3727
3922
|
processNext();
|
|
@@ -3766,7 +3961,7 @@ async function handleUiRequest(child, id, request, ctx, signal) {
|
|
|
3766
3961
|
respond(child, id, result, signal);
|
|
3767
3962
|
} catch (err) {
|
|
3768
3963
|
if (signal?.aborted) return;
|
|
3769
|
-
|
|
3964
|
+
logger14.error("[subagents] uiRequestHandler threw", {
|
|
3770
3965
|
detail: toErrorMessage(err)
|
|
3771
3966
|
});
|
|
3772
3967
|
respond(child, id, { cancelled: true }, signal);
|
|
@@ -3809,7 +4004,7 @@ function extractMethodFields(req) {
|
|
|
3809
4004
|
}
|
|
3810
4005
|
|
|
3811
4006
|
// src/execution/engine/engines/pi/session-runner.ts
|
|
3812
|
-
var
|
|
4007
|
+
var logger15 = getLogger("subagents");
|
|
3813
4008
|
function isSdkEvent(x) {
|
|
3814
4009
|
if (typeof x !== "object" || x === null) return false;
|
|
3815
4010
|
if (!("type" in x)) return false;
|
|
@@ -3862,7 +4057,7 @@ function getEnvSpawnWatchdogMs() {
|
|
|
3862
4057
|
if (!raw) return void 0;
|
|
3863
4058
|
const parsed = Number(raw);
|
|
3864
4059
|
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
3865
|
-
|
|
4060
|
+
logger15.warn(
|
|
3866
4061
|
`[session-runner] ${SPAWN_WATCHDOG_ENV}="${raw}" is invalid (expected a positive millisecond number) \u2014 spawn watchdog NOT armed, equivalent to disabled; set a plain ms value (e.g. 1800000) to enable`
|
|
3867
4062
|
);
|
|
3868
4063
|
return void 0;
|
|
@@ -3929,7 +4124,7 @@ function killAllSpawnedChildren(signal = "SIGTERM") {
|
|
|
3929
4124
|
child.kill(child.killed ? "SIGKILL" : signal);
|
|
3930
4125
|
n++;
|
|
3931
4126
|
} catch (err) {
|
|
3932
|
-
|
|
4127
|
+
logger15.debug(
|
|
3933
4128
|
`[session-runner] killAllSpawnedChildren: kill failed (best-effort continue): ${toErrorMessage(err)}`
|
|
3934
4129
|
);
|
|
3935
4130
|
}
|
|
@@ -3973,7 +4168,7 @@ function killPidWithEscalation(pid, label) {
|
|
|
3973
4168
|
try {
|
|
3974
4169
|
process.kill(pid, "SIGTERM");
|
|
3975
4170
|
} catch (err) {
|
|
3976
|
-
|
|
4171
|
+
logger15.debug(
|
|
3977
4172
|
`[session-runner] ${label}: SIGTERM to pid ${pid} failed (best-effort continue): ${toErrorMessage(err)}`
|
|
3978
4173
|
);
|
|
3979
4174
|
return;
|
|
@@ -3981,7 +4176,7 @@ function killPidWithEscalation(pid, label) {
|
|
|
3981
4176
|
assertSafeTimerDelay(SIGKILL_ESCALATION_MS, `descendant SIGKILL escalation (${label})`);
|
|
3982
4177
|
const escalation = setTimeout(() => {
|
|
3983
4178
|
if (isProcessAlive(pid)) {
|
|
3984
|
-
|
|
4179
|
+
logger15.warn(
|
|
3985
4180
|
`[session-runner] ${label}: descendant pid ${pid} still alive ${SIGKILL_ESCALATION_MS / MS_PER_SECOND5}s after SIGTERM, escalating to SIGKILL`
|
|
3986
4181
|
);
|
|
3987
4182
|
try {
|
|
@@ -4004,7 +4199,7 @@ function sweepDescendantsOfSession(rootSessionFile, sessionDir, source) {
|
|
|
4004
4199
|
visited.add(sessionFile);
|
|
4005
4200
|
const list = listActivePendingFromSessionFile(sessionFile);
|
|
4006
4201
|
if (list.error) {
|
|
4007
|
-
|
|
4202
|
+
logger15.debug(
|
|
4008
4203
|
`[session-runner] descendant sweep (${source}): pending list unreadable for ${sessionFile}: ${list.error}`
|
|
4009
4204
|
);
|
|
4010
4205
|
continue;
|
|
@@ -4048,7 +4243,7 @@ function sweepDescendantsOfSession(rootSessionFile, sessionDir, source) {
|
|
|
4048
4243
|
});
|
|
4049
4244
|
continue;
|
|
4050
4245
|
}
|
|
4051
|
-
|
|
4246
|
+
logger15.warn(
|
|
4052
4247
|
`[session-runner] descendant sweep (${source}): killing orphan descendant pid=${marker.pid} session=${item.sessionId} (${item.id})`
|
|
4053
4248
|
);
|
|
4054
4249
|
killPidWithEscalation(marker.pid, `descendant sweep (${source})`);
|
|
@@ -4111,7 +4306,7 @@ async function buildEnvBlock(cwd, forkDepth, nestingDepth) {
|
|
|
4111
4306
|
);
|
|
4112
4307
|
});
|
|
4113
4308
|
} catch (err) {
|
|
4114
|
-
|
|
4309
|
+
logger15.debug(
|
|
4115
4310
|
`[session-runner] buildEnvBlock: git branch lookup failed for ${cwd}, fallback to empty: ${toErrorMessage(err)}`
|
|
4116
4311
|
);
|
|
4117
4312
|
branch = "";
|
|
@@ -4162,7 +4357,7 @@ function writeAliveMarkerBestEffort(sessionFile, pid, id) {
|
|
|
4162
4357
|
try {
|
|
4163
4358
|
writeAliveMarker(sessionFile, { pid, id, startedAt: Date.now() });
|
|
4164
4359
|
} catch (err) {
|
|
4165
|
-
|
|
4360
|
+
logger15.debug(
|
|
4166
4361
|
`[session-runner] alive marker write failed (best-effort continue): ${toErrorMessage(err)}`
|
|
4167
4362
|
);
|
|
4168
4363
|
}
|
|
@@ -4198,7 +4393,7 @@ function killRecordChildWithEscalation(recordId, source) {
|
|
|
4198
4393
|
const escalation = setTimeout(
|
|
4199
4394
|
() => {
|
|
4200
4395
|
if (child.exitCode === null && child.signalCode === null) {
|
|
4201
|
-
|
|
4396
|
+
logger15.warn(
|
|
4202
4397
|
`[session-runner] child ${recordId} still alive ${SIGKILL_ESCALATION_MS / MS_PER_SECOND5}s after SIGTERM, escalating to SIGKILL (source: ${source})`
|
|
4203
4398
|
);
|
|
4204
4399
|
child.kill("SIGKILL");
|
|
@@ -4218,7 +4413,7 @@ function killRecordChildWithEscalation(recordId, source) {
|
|
|
4218
4413
|
function hasLiveActiveDescendant(sessionFile, sessionDir) {
|
|
4219
4414
|
const list = listActivePendingFromSessionFile(sessionFile);
|
|
4220
4415
|
if (list.error) {
|
|
4221
|
-
|
|
4416
|
+
logger15.warn(
|
|
4222
4417
|
`[session-runner] keep-alive no-progress re-check failed (treating as no live descendants): ${list.error}`
|
|
4223
4418
|
);
|
|
4224
4419
|
return false;
|
|
@@ -4238,13 +4433,13 @@ function armKeepAliveNoProgressTimer(state, child, sessionDir) {
|
|
|
4238
4433
|
state.keepAliveNoProgressTimer = setTimeout(() => {
|
|
4239
4434
|
if (hasLiveActiveDescendant(state.record.sessionFile, sessionDir)) {
|
|
4240
4435
|
touchAliveMarkerForHeartbeat(state.record.sessionFile, child.pid, state.record.id);
|
|
4241
|
-
|
|
4436
|
+
logger15.debug(
|
|
4242
4437
|
`[session-runner] keep-alive no-progress re-check: live descendant(s) present for ${state.record.id}, re-arm (cadence ${KEEP_ALIVE_NO_PROGRESS_TIMEOUT_MS / MS_PER_SECOND5 / SECONDS_PER_MINUTE3} min)`
|
|
4243
4438
|
);
|
|
4244
4439
|
armKeepAliveNoProgressTimer(state, child, sessionDir);
|
|
4245
4440
|
return;
|
|
4246
4441
|
}
|
|
4247
|
-
|
|
4442
|
+
logger15.warn(
|
|
4248
4443
|
`[session-runner] keep-alive no-progress watchdog fired for ${state.record.id}: no child output for ${KEEP_ALIVE_NO_PROGRESS_TIMEOUT_MS / MS_PER_SECOND5 / SECONDS_PER_MINUTE3} min and no live descendant (bare-default keep-alive without maxTurns/env), terminating`
|
|
4249
4444
|
);
|
|
4250
4445
|
state.sweepDescendantsOnClose = true;
|
|
@@ -4305,7 +4500,7 @@ function createSpawnEventHandlers(state) {
|
|
|
4305
4500
|
bestEffort(err, "armIdleTimer (agent_settled chatMode)", "error");
|
|
4306
4501
|
try {
|
|
4307
4502
|
armIdleTimer(record.id, armIdleTimerOnTimeout, DEFAULT_IDLE_TIMEOUT_MS);
|
|
4308
|
-
|
|
4503
|
+
logger15.warn(
|
|
4309
4504
|
`[session-runner] idleTimeoutMs invalid for ${record.id}, fell back to DEFAULT_IDLE_TIMEOUT_MS (${DEFAULT_IDLE_TIMEOUT_MS}ms) \u2014 idle GC and round notification gate stay active`
|
|
4310
4505
|
);
|
|
4311
4506
|
} catch (fallbackErr) {
|
|
@@ -4467,7 +4662,7 @@ async function backfillSessionFileViaGetState(state, child, registerGetStateList
|
|
|
4467
4662
|
if (child.pid) {
|
|
4468
4663
|
writeAliveMarkerBestEffort(r.sessionFile, child.pid, r.sessionId ?? record.id);
|
|
4469
4664
|
}
|
|
4470
|
-
|
|
4665
|
+
logger15.warn(
|
|
4471
4666
|
`[session-runner] agent_end: sessionFile backfilled via lazy get_state (spawn handshake had failed): ${r.sessionFile}`
|
|
4472
4667
|
);
|
|
4473
4668
|
}
|
|
@@ -4479,11 +4674,11 @@ function keepAliveOnAgentEnd(state, child, sessionDir, pending) {
|
|
|
4479
4674
|
const { record, opts } = state;
|
|
4480
4675
|
touchAliveMarkerForHeartbeat(record.sessionFile, child.pid, record.id);
|
|
4481
4676
|
if (pending.error) {
|
|
4482
|
-
|
|
4677
|
+
logger15.warn(
|
|
4483
4678
|
`[session-runner] agent_end: keep alive (sessionFile unreadable, conservative): ${pending.error}`
|
|
4484
4679
|
);
|
|
4485
4680
|
} else {
|
|
4486
|
-
|
|
4681
|
+
logger15.debug(
|
|
4487
4682
|
`[session-runner] agent_end: keep alive, ${pending.count} active descendant(s) pending`
|
|
4488
4683
|
);
|
|
4489
4684
|
}
|
|
@@ -4510,7 +4705,7 @@ function keepAliveOnAgentEnd(state, child, sessionDir, pending) {
|
|
|
4510
4705
|
function keepAliveForWakeupGrace(state, child) {
|
|
4511
4706
|
const { record } = state;
|
|
4512
4707
|
touchAliveMarkerForHeartbeat(record.sessionFile, child.pid, record.id);
|
|
4513
|
-
|
|
4708
|
+
logger15.debug(
|
|
4514
4709
|
"[session-runner] agent_end: keep alive, recent descendant completion (wake-up in flight)"
|
|
4515
4710
|
);
|
|
4516
4711
|
clearTimeout(state.watchdog);
|
|
@@ -4535,7 +4730,7 @@ function attachStdoutPump(child, state, sessionDir, handleSdkEvent) {
|
|
|
4535
4730
|
invalidLineSamples.push(truncated);
|
|
4536
4731
|
}
|
|
4537
4732
|
if (invalidLineCount <= MAX_INVALID_LINE_SAMPLES) {
|
|
4538
|
-
|
|
4733
|
+
logger15.debug(
|
|
4539
4734
|
`[session-runner] stdout invalid line #${invalidLineCount} dropped (${reason}): ${truncated}`
|
|
4540
4735
|
);
|
|
4541
4736
|
}
|
|
@@ -4645,7 +4840,7 @@ function attachStdoutPump(child, state, sessionDir, handleSdkEvent) {
|
|
|
4645
4840
|
}
|
|
4646
4841
|
}
|
|
4647
4842
|
if (invalidLineCount > 0) {
|
|
4648
|
-
|
|
4843
|
+
logger15.debug(
|
|
4649
4844
|
`[session-runner] stdout had ${invalidLineCount} invalid line(s) dropped in total; sample(s): ${invalidLineSamples.join(" | ")}`
|
|
4650
4845
|
);
|
|
4651
4846
|
}
|
|
@@ -4692,7 +4887,7 @@ function setupFreshChild(state, child, task) {
|
|
|
4692
4887
|
try {
|
|
4693
4888
|
void ctx.onWorktreePid?.(opts.worktree.branch, child.pid);
|
|
4694
4889
|
} catch (err) {
|
|
4695
|
-
|
|
4890
|
+
logger15.warn("[worktree] worktree pid registration failed (defensive)", {
|
|
4696
4891
|
branch: opts.worktree.branch,
|
|
4697
4892
|
pid: child.pid,
|
|
4698
4893
|
err: toErrorMessage(err)
|
|
@@ -4704,7 +4899,7 @@ function setupFreshChild(state, child, task) {
|
|
|
4704
4899
|
child.stdin.on("error", (err) => {
|
|
4705
4900
|
removeChildRegistration(record.id, child);
|
|
4706
4901
|
const count = recordEpipeFailure(record.id);
|
|
4707
|
-
|
|
4902
|
+
logger15.warn(`[subagents] async stdin error for ${record.id}`, {
|
|
4708
4903
|
detail: err.message,
|
|
4709
4904
|
epipeCount: count,
|
|
4710
4905
|
threshold: EPIPE_FAILURE_THRESHOLD,
|
|
@@ -4718,14 +4913,14 @@ function armChatModeSettledWatchdog(state, child) {
|
|
|
4718
4913
|
if (record.chatMode) {
|
|
4719
4914
|
armMidRoundNoProgress(record.id, {
|
|
4720
4915
|
onMidTimeout: (fire) => {
|
|
4721
|
-
|
|
4916
|
+
logger15.warn(
|
|
4722
4917
|
`[session-runner] settled watchdog (mid-round) fired for ${record.id}: no valid protocol event for ${fire.waitedMs / MS_PER_SECOND5 / SECONDS_PER_MINUTE3} min, terminating (LC-1 wedge recovery)`
|
|
4723
4918
|
);
|
|
4724
4919
|
state.settledWatchdogFired = fire;
|
|
4725
4920
|
killChildWithEscalation(state, child, "settled watchdog");
|
|
4726
4921
|
},
|
|
4727
4922
|
onSettleTimeout: (fire) => {
|
|
4728
|
-
|
|
4923
|
+
logger15.warn(
|
|
4729
4924
|
`[session-runner] settled watchdog (settled phase) fired for ${record.id}: no agent_settled within ${fire.waitedMs / MS_PER_SECOND5}s after agent_end, terminating (LC-1 wedge recovery)`
|
|
4730
4925
|
);
|
|
4731
4926
|
state.settledWatchdogFired = fire;
|
|
@@ -4756,14 +4951,14 @@ function startGetStateHandshake(child, pump) {
|
|
|
4756
4951
|
if (pump.isHandshakePending()) pump.finishHandshake(r);
|
|
4757
4952
|
}).catch((err) => {
|
|
4758
4953
|
const m = toErrorMessage(err);
|
|
4759
|
-
|
|
4954
|
+
logger15.error(`[session-runner] get_state handshake failed: ${m}`);
|
|
4760
4955
|
pump.abandonHandshake();
|
|
4761
4956
|
});
|
|
4762
4957
|
}
|
|
4763
4958
|
function backfillSessionFileByLookup(state, sessionDir) {
|
|
4764
4959
|
const { record } = state;
|
|
4765
4960
|
const lookupId = state.sessionHeader?.id ?? state.handshakeResult?.sessionId;
|
|
4766
|
-
const needsLookup = record.sessionFile ? !
|
|
4961
|
+
const needsLookup = record.sessionFile ? !fs13.existsSync(record.sessionFile) : lookupId !== void 0;
|
|
4767
4962
|
if (lookupId && needsLookup) {
|
|
4768
4963
|
const actual = findSessionFileByHeaderId(sessionDir, lookupId);
|
|
4769
4964
|
if (actual && actual !== record.sessionFile) record.sessionFile = actual;
|
|
@@ -4775,7 +4970,7 @@ function sweepDescendantsOnChildClose(state, sessionDir) {
|
|
|
4775
4970
|
try {
|
|
4776
4971
|
const sweep = sweepDescendantsOfSession(record.sessionFile, sessionDir, "keep-alive watchdog");
|
|
4777
4972
|
if (sweep.killed.length > 0 || sweep.skipped.length > 0) {
|
|
4778
|
-
|
|
4973
|
+
logger15.warn(
|
|
4779
4974
|
`[session-runner] descendant sweep (keep-alive watchdog): killed=[${sweep.killed.join(", ")}] skipped=${JSON.stringify(sweep.skipped)}`
|
|
4780
4975
|
);
|
|
4781
4976
|
}
|
|
@@ -4828,7 +5023,7 @@ async function runSpawn(record, task, opts, ctx, resume) {
|
|
|
4828
5023
|
};
|
|
4829
5024
|
const handleSdkEvent = createSpawnEventHandlers(state);
|
|
4830
5025
|
const sessionDir = getSubagentSessionDir(ctx.agentDir, ctx.rootCwd);
|
|
4831
|
-
|
|
5026
|
+
fs13.mkdirSync(sessionDir, { recursive: true });
|
|
4832
5027
|
const spawnCwd = opts.worktree?.path ?? ctx.cwd;
|
|
4833
5028
|
const forkSource = opts.forkSource ?? (opts.fork ? ctx.mainSessionFile : void 0);
|
|
4834
5029
|
const tempPromptFile = await writeAppendSystemPromptFile(record, opts, ctx);
|
|
@@ -4875,7 +5070,7 @@ async function runSpawn(record, task, opts, ctx, resume) {
|
|
|
4875
5070
|
}
|
|
4876
5071
|
|
|
4877
5072
|
// src/execution/engine/engines/pi/pi-engine.ts
|
|
4878
|
-
var
|
|
5073
|
+
var logger16 = getLogger("subagents");
|
|
4879
5074
|
var PI_ENGINE_ID2 = "pi";
|
|
4880
5075
|
var PI_ADAPTER_VERSION = "1.0.0";
|
|
4881
5076
|
var PI_POOL_KEY = "shared";
|
|
@@ -5055,7 +5250,7 @@ var PiEngine = class {
|
|
|
5055
5250
|
sendPromptCommand(child, text, { streamingBehavior: interrupt ? "steer" : "followUp" });
|
|
5056
5251
|
clearEpipeFailure(record.id);
|
|
5057
5252
|
if (child.exitCode !== null || child.signalCode !== null) {
|
|
5058
|
-
|
|
5253
|
+
logger16.warn(
|
|
5059
5254
|
`[subagents] deliverMessage: child ${record.id} died around stdin write, message may be lost`,
|
|
5060
5255
|
{
|
|
5061
5256
|
msgType: interrupt ? "steer" : "followUp",
|
|
@@ -5069,7 +5264,7 @@ var PiEngine = class {
|
|
|
5069
5264
|
service.reportRecordTransition?.(record);
|
|
5070
5265
|
} catch (err) {
|
|
5071
5266
|
if (err instanceof Error && err.message.includes("EPIPE")) {
|
|
5072
|
-
|
|
5267
|
+
logger16.warn(`[subagents] EPIPE on hot path for ${record.id}, falling back to cold path resume`, {
|
|
5073
5268
|
detail: err.message
|
|
5074
5269
|
});
|
|
5075
5270
|
if (spawnedChildren.get(record.id) === child) {
|
|
@@ -5120,7 +5315,7 @@ var PiEngine = class {
|
|
|
5120
5315
|
return;
|
|
5121
5316
|
}
|
|
5122
5317
|
}
|
|
5123
|
-
|
|
5318
|
+
logger16.warn(
|
|
5124
5319
|
`[subagents] deliverPrompt hot path failed for ${record.id}; idle timer re-armed to keep process recovery bounded`,
|
|
5125
5320
|
{ detail }
|
|
5126
5321
|
);
|
|
@@ -5269,7 +5464,7 @@ async function defaultProbeVersion(invocation) {
|
|
|
5269
5464
|
);
|
|
5270
5465
|
});
|
|
5271
5466
|
} catch (err) {
|
|
5272
|
-
|
|
5467
|
+
logger16.debug(
|
|
5273
5468
|
`[pi-engine] probe version check failed (best-effort continue): ${toErrorMessage(err)}`
|
|
5274
5469
|
);
|
|
5275
5470
|
return void 0;
|
|
@@ -5278,12 +5473,12 @@ async function defaultProbeVersion(invocation) {
|
|
|
5278
5473
|
function isInvocationResolvable(invocation) {
|
|
5279
5474
|
if (invocation.command !== "pi") return true;
|
|
5280
5475
|
const pathEnv = process.env.PATH ?? "";
|
|
5281
|
-
for (const dir of pathEnv.split(
|
|
5476
|
+
for (const dir of pathEnv.split(path9.delimiter)) {
|
|
5282
5477
|
if (dir === "") continue;
|
|
5283
5478
|
try {
|
|
5284
|
-
if (
|
|
5479
|
+
if (fs14.existsSync(path9.join(dir, "pi"))) return true;
|
|
5285
5480
|
} catch (err) {
|
|
5286
|
-
|
|
5481
|
+
logger16.debug(
|
|
5287
5482
|
`[pi-engine] PATH dir probe failed (continue scanning): ${dir}: ${toErrorMessage(err)}`
|
|
5288
5483
|
);
|
|
5289
5484
|
}
|
|
@@ -5412,14 +5607,14 @@ function safeEngineDefault(engine) {
|
|
|
5412
5607
|
}
|
|
5413
5608
|
|
|
5414
5609
|
// src/execution/manifest-store.ts
|
|
5415
|
-
var
|
|
5610
|
+
var fs15 = __toESM(require("fs"), 1);
|
|
5416
5611
|
var fsPromises2 = __toESM(require("fs/promises"), 1);
|
|
5417
|
-
var
|
|
5418
|
-
var
|
|
5612
|
+
var path10 = __toESM(require("path"), 1);
|
|
5613
|
+
var logger17 = getLogger("subagents");
|
|
5419
5614
|
var MANIFEST_INDENT_SPACES = 2;
|
|
5420
5615
|
function statStamp(p) {
|
|
5421
5616
|
try {
|
|
5422
|
-
const s =
|
|
5617
|
+
const s = fs15.statSync(p);
|
|
5423
5618
|
return { mtimeMs: s.mtimeMs, size: s.size };
|
|
5424
5619
|
} catch {
|
|
5425
5620
|
return null;
|
|
@@ -5447,8 +5642,8 @@ var ManifestStore = class {
|
|
|
5447
5642
|
cache = /* @__PURE__ */ new Map();
|
|
5448
5643
|
constructor(dir) {
|
|
5449
5644
|
this.dir = dir;
|
|
5450
|
-
if (!
|
|
5451
|
-
|
|
5645
|
+
if (!fs15.existsSync(dir)) {
|
|
5646
|
+
fs15.mkdirSync(dir, { recursive: true });
|
|
5452
5647
|
}
|
|
5453
5648
|
}
|
|
5454
5649
|
/**
|
|
@@ -5460,7 +5655,7 @@ var ManifestStore = class {
|
|
|
5460
5655
|
* 调用方(finalizeRecord)决定降级策略。
|
|
5461
5656
|
*/
|
|
5462
5657
|
async writeManifest(record) {
|
|
5463
|
-
const filePath =
|
|
5658
|
+
const filePath = path10.join(this.dir, `${record.id}.json`);
|
|
5464
5659
|
const content = JSON.stringify(record, null, MANIFEST_INDENT_SPACES);
|
|
5465
5660
|
await writeAtomicFile(filePath, content, { ensureDir: false });
|
|
5466
5661
|
}
|
|
@@ -5469,7 +5664,7 @@ var ManifestStore = class {
|
|
|
5469
5664
|
* 调用方需处理 null。
|
|
5470
5665
|
*/
|
|
5471
5666
|
async readManifest(id) {
|
|
5472
|
-
const filePath =
|
|
5667
|
+
const filePath = path10.join(this.dir, `${id}.json`);
|
|
5473
5668
|
try {
|
|
5474
5669
|
const content = await fsPromises2.readFile(filePath, "utf-8");
|
|
5475
5670
|
const parsed = JSON.parse(content);
|
|
@@ -5489,7 +5684,7 @@ var ManifestStore = class {
|
|
|
5489
5684
|
listAllSync() {
|
|
5490
5685
|
let files;
|
|
5491
5686
|
try {
|
|
5492
|
-
files =
|
|
5687
|
+
files = fs15.readdirSync(this.dir);
|
|
5493
5688
|
} catch {
|
|
5494
5689
|
return [];
|
|
5495
5690
|
}
|
|
@@ -5500,7 +5695,7 @@ var ManifestStore = class {
|
|
|
5500
5695
|
}
|
|
5501
5696
|
const results = [];
|
|
5502
5697
|
for (const file of names) {
|
|
5503
|
-
const filePath =
|
|
5698
|
+
const filePath = path10.join(this.dir, file);
|
|
5504
5699
|
const stamp = statStamp(filePath);
|
|
5505
5700
|
if (!stamp) {
|
|
5506
5701
|
this.cache.delete(file);
|
|
@@ -5512,7 +5707,7 @@ var ManifestStore = class {
|
|
|
5512
5707
|
continue;
|
|
5513
5708
|
}
|
|
5514
5709
|
try {
|
|
5515
|
-
const content =
|
|
5710
|
+
const content = fs15.readFileSync(filePath, "utf-8");
|
|
5516
5711
|
const parsed = JSON.parse(content);
|
|
5517
5712
|
const record = isValidManifest(parsed) ? parsed : null;
|
|
5518
5713
|
this.cache.set(file, { stamp, record });
|
|
@@ -5540,41 +5735,41 @@ var ManifestStore = class {
|
|
|
5540
5735
|
let deleted = 0;
|
|
5541
5736
|
let recovered = 0;
|
|
5542
5737
|
let failed = 0;
|
|
5543
|
-
const files =
|
|
5738
|
+
const files = fs15.readdirSync(this.dir);
|
|
5544
5739
|
const tmpFiles = files.filter((f) => f.includes(".json.tmp."));
|
|
5545
5740
|
for (const tmpFile of tmpFiles) {
|
|
5546
|
-
const tmpPath =
|
|
5741
|
+
const tmpPath = path10.join(this.dir, tmpFile);
|
|
5547
5742
|
const manifestId = tmpFile.split(".json.tmp.")[0];
|
|
5548
|
-
const manifestPath =
|
|
5743
|
+
const manifestPath = path10.join(this.dir, `${manifestId}.json`);
|
|
5549
5744
|
try {
|
|
5550
|
-
if (
|
|
5551
|
-
|
|
5745
|
+
if (fs15.existsSync(manifestPath)) {
|
|
5746
|
+
fs15.unlinkSync(tmpPath);
|
|
5552
5747
|
deleted++;
|
|
5553
5748
|
} else {
|
|
5554
5749
|
try {
|
|
5555
|
-
const content =
|
|
5750
|
+
const content = fs15.readFileSync(tmpPath, "utf-8");
|
|
5556
5751
|
const parsed = JSON.parse(content);
|
|
5557
5752
|
if (isValidManifest(parsed)) {
|
|
5558
|
-
|
|
5753
|
+
fs15.renameSync(tmpPath, manifestPath);
|
|
5559
5754
|
recovered++;
|
|
5560
5755
|
} else {
|
|
5561
|
-
|
|
5756
|
+
fs15.unlinkSync(tmpPath);
|
|
5562
5757
|
deleted++;
|
|
5563
5758
|
}
|
|
5564
5759
|
} catch {
|
|
5565
|
-
|
|
5760
|
+
fs15.unlinkSync(tmpPath);
|
|
5566
5761
|
deleted++;
|
|
5567
5762
|
}
|
|
5568
5763
|
}
|
|
5569
5764
|
} catch (fileErr) {
|
|
5570
5765
|
failed++;
|
|
5571
|
-
|
|
5766
|
+
logger17.warn(`[subagents] recoverTmpFiles: failed to recover ${tmpFile}, skipping (leftovers retry on next startup)`, {
|
|
5572
5767
|
detail: fileErr instanceof Error ? fileErr.message : String(fileErr)
|
|
5573
5768
|
});
|
|
5574
5769
|
}
|
|
5575
5770
|
}
|
|
5576
5771
|
if (failed > 0) {
|
|
5577
|
-
|
|
5772
|
+
logger17.warn(
|
|
5578
5773
|
`[subagents] recoverTmpFiles: ${failed} of ${tmpFiles.length} tmp file(s) could not be recovered`
|
|
5579
5774
|
);
|
|
5580
5775
|
}
|
|
@@ -5647,8 +5842,11 @@ function isResumable(record) {
|
|
|
5647
5842
|
return record.status === "running" && !hasLiveProcessHandle(record.id);
|
|
5648
5843
|
}
|
|
5649
5844
|
|
|
5845
|
+
// src/execution/notifier.ts
|
|
5846
|
+
var import_node_crypto = require("crypto");
|
|
5847
|
+
|
|
5650
5848
|
// src/execution/notify-ledger.ts
|
|
5651
|
-
var
|
|
5849
|
+
var logger18 = getLogger("subagents");
|
|
5652
5850
|
var NOTIFY_LEDGER_CUSTOM_TYPE = "subagent-bg-notify-ledger";
|
|
5653
5851
|
var NOTIFY_ACK_CUSTOM_TYPE = "subagent-bg-notify-ack";
|
|
5654
5852
|
var NOTIFY_ABANDONED_CUSTOM_TYPE = "subagent-bg-notify-abandoned";
|
|
@@ -5874,7 +6072,7 @@ function createNotifyLedger(host, options) {
|
|
|
5874
6072
|
host.appendLedgerEntry(NOTIFY_ABANDONED_CUSTOM_TYPE, { v: 1, notifyId: item.notifyId });
|
|
5875
6073
|
items.delete(item.notifyId);
|
|
5876
6074
|
abandonedIds.add(item.notifyId);
|
|
5877
|
-
|
|
6075
|
+
logger18.warn(
|
|
5878
6076
|
`Subagent "${itemLabel(item)}" notification abandoned - no receipt after ${NOTIFY_REDELIVERY_MAX_ATTEMPTS} delivery attempts; verify manually via subagents action:"list"`,
|
|
5879
6077
|
{ notifyId: item.notifyId, attempts: item.attempts }
|
|
5880
6078
|
);
|
|
@@ -5925,7 +6123,7 @@ function createNotifyLedger(host, options) {
|
|
|
5925
6123
|
api.checkReceipts();
|
|
5926
6124
|
}
|
|
5927
6125
|
function emitBucketLog(bucket, total, extra) {
|
|
5928
|
-
|
|
6126
|
+
logger18.warn(`notify delivery bucket [${bucket}]`, { total, ...extra });
|
|
5929
6127
|
}
|
|
5930
6128
|
if (options?.registerSettledListener !== false) {
|
|
5931
6129
|
host.onAgentSettled(() => {
|
|
@@ -5973,6 +6171,70 @@ function getBoundNotifyLedger() {
|
|
|
5973
6171
|
|
|
5974
6172
|
// src/execution/notifier.ts
|
|
5975
6173
|
var notifyLogger = getLogger("subagents");
|
|
6174
|
+
var BATCH_NOTIFY_ID_PREFIX = "sync-batch:";
|
|
6175
|
+
function buildBatchNotifyId(memberIds) {
|
|
6176
|
+
const digest = (0, import_node_crypto.createHash)("sha1").update([...memberIds].sort().join(",")).digest("hex");
|
|
6177
|
+
return `${BATCH_NOTIFY_ID_PREFIX}${digest}`;
|
|
6178
|
+
}
|
|
6179
|
+
var DEFAULT_BATCH_BUDGET = { perItemChars: 4e3, totalChars: 24e3 };
|
|
6180
|
+
var LIST_ONLY_FLOOR = 200;
|
|
6181
|
+
function computeBatchBudget(bodyLengths, perItemChars, totalChars) {
|
|
6182
|
+
const n = bodyLengths.length;
|
|
6183
|
+
const sumAfterPerItem = bodyLengths.reduce((sum, len) => sum + Math.min(len, perItemChars), 0);
|
|
6184
|
+
if (n === 0 || sumAfterPerItem <= totalChars) {
|
|
6185
|
+
return { tightened: false, effectivePerItem: perItemChars, listOnly: false };
|
|
6186
|
+
}
|
|
6187
|
+
const tight = Math.floor(totalChars / n);
|
|
6188
|
+
if (tight < LIST_ONLY_FLOOR) {
|
|
6189
|
+
return { tightened: true, effectivePerItem: 0, listOnly: true };
|
|
6190
|
+
}
|
|
6191
|
+
return { tightened: true, effectivePerItem: Math.min(tight, perItemChars), listOnly: false };
|
|
6192
|
+
}
|
|
6193
|
+
function formatChars(n) {
|
|
6194
|
+
return n.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
|
6195
|
+
}
|
|
6196
|
+
var SESSION_READ_DEFAULT_LIMIT = 8e3;
|
|
6197
|
+
function buildTruncationPointer(id, kept, total, perItemLimit) {
|
|
6198
|
+
const limitPart = perItemLimit > SESSION_READ_DEFAULT_LIMIT ? `,"limit":${perItemLimit}` : "";
|
|
6199
|
+
return `[truncated ${formatChars(total - kept)} of ${formatChars(total)} chars \u2014 full result: session_read {"action":"result","session":"${id}"${limitPart}}]`;
|
|
6200
|
+
}
|
|
6201
|
+
function withMaterializedOutcome(record) {
|
|
6202
|
+
return record.status === "closed" ? { ...record, outcome: record.outcome ?? deriveOutcome(record.closedReason, record.error) } : record;
|
|
6203
|
+
}
|
|
6204
|
+
function buildBatchLlmContent(records, budget = DEFAULT_BATCH_BUDGET) {
|
|
6205
|
+
const mat = records.map(withMaterializedOutcome);
|
|
6206
|
+
let finished = 0;
|
|
6207
|
+
let failed = 0;
|
|
6208
|
+
let cancelled = 0;
|
|
6209
|
+
const bodies = mat.map((record) => {
|
|
6210
|
+
if (record.status === "closed" && record.outcome !== "completed") return null;
|
|
6211
|
+
return record.result ?? "(empty)";
|
|
6212
|
+
});
|
|
6213
|
+
const plan = computeBatchBudget(
|
|
6214
|
+
bodies.map((body) => body?.length ?? 0),
|
|
6215
|
+
budget.perItemChars,
|
|
6216
|
+
budget.totalChars
|
|
6217
|
+
);
|
|
6218
|
+
const items = mat.map((record, i) => {
|
|
6219
|
+
if (record.status === "closed") {
|
|
6220
|
+
const outcome = record.outcome;
|
|
6221
|
+
if (outcome === "completed") finished += 1;
|
|
6222
|
+
else if (outcome === "failed") failed += 1;
|
|
6223
|
+
else if (outcome === "cancelled") cancelled += 1;
|
|
6224
|
+
}
|
|
6225
|
+
const body = bodies[i];
|
|
6226
|
+
if (body === null || body.length <= plan.effectivePerItem) {
|
|
6227
|
+
return buildLlmContent(record);
|
|
6228
|
+
}
|
|
6229
|
+
const kept = body.slice(0, plan.effectivePerItem);
|
|
6230
|
+
const truncated = buildLlmContent({ ...record, result: plan.listOnly ? "" : `${kept}\u2026` });
|
|
6231
|
+
const stripped = plan.listOnly ? truncated.replace(/\n$/, "") : truncated;
|
|
6232
|
+
return `${stripped}
|
|
6233
|
+
${buildTruncationPointer(record.id, kept.length, body.length, plan.effectivePerItem)}`;
|
|
6234
|
+
});
|
|
6235
|
+
const header = `Subagent batch completed: ${finished} finished, ${failed} failed, ${cancelled} cancelled.`;
|
|
6236
|
+
return [header, ...items].join("\n\n---\n\n");
|
|
6237
|
+
}
|
|
5976
6238
|
function buildLlmContent(record) {
|
|
5977
6239
|
const agent = record.agent;
|
|
5978
6240
|
const id = record.id;
|
|
@@ -6090,31 +6352,45 @@ function createNotifier(host) {
|
|
|
6090
6352
|
});
|
|
6091
6353
|
};
|
|
6092
6354
|
let handle = createHandle();
|
|
6355
|
+
function deliverViaLedgerOrKernel(notifyId, content, details) {
|
|
6356
|
+
const ledger = getBoundNotifyLedger();
|
|
6357
|
+
if (ledger) {
|
|
6358
|
+
if (!ledger.record(notifyId, content, details)) return false;
|
|
6359
|
+
ledger.attemptDeliver();
|
|
6360
|
+
return true;
|
|
6361
|
+
}
|
|
6362
|
+
notifyLogger.warn("notify ledger not bound, falling back to delivery kernel path (at-most-once)", {
|
|
6363
|
+
notifyId
|
|
6364
|
+
});
|
|
6365
|
+
handle.send({
|
|
6366
|
+
payload: {
|
|
6367
|
+
kind: "custom",
|
|
6368
|
+
customType: NOTIFY_CUSTOM_TYPE,
|
|
6369
|
+
content,
|
|
6370
|
+
display: true,
|
|
6371
|
+
details
|
|
6372
|
+
},
|
|
6373
|
+
dedupeKey: notifyId
|
|
6374
|
+
});
|
|
6375
|
+
return true;
|
|
6376
|
+
}
|
|
6093
6377
|
return {
|
|
6094
6378
|
notify(record) {
|
|
6095
6379
|
if (disposed) return;
|
|
6096
6380
|
const notifyId = record.round != null ? `${record.id}:${record.round}` : record.id;
|
|
6097
6381
|
const payload = record.status === "closed" ? { ...record, outcome: record.outcome ?? deriveOutcome(record.closedReason, record.error), notifyId } : { ...record, notifyId };
|
|
6098
6382
|
const content = buildLlmContent(payload);
|
|
6099
|
-
|
|
6100
|
-
|
|
6101
|
-
|
|
6102
|
-
|
|
6103
|
-
|
|
6104
|
-
|
|
6105
|
-
|
|
6106
|
-
|
|
6107
|
-
|
|
6108
|
-
|
|
6109
|
-
|
|
6110
|
-
kind: "custom",
|
|
6111
|
-
customType: NOTIFY_CUSTOM_TYPE,
|
|
6112
|
-
content,
|
|
6113
|
-
display: true,
|
|
6114
|
-
details: payload
|
|
6115
|
-
},
|
|
6116
|
-
dedupeKey: notifyId
|
|
6117
|
-
});
|
|
6383
|
+
deliverViaLedgerOrKernel(notifyId, content, payload);
|
|
6384
|
+
},
|
|
6385
|
+
notifyBatch(records, budget) {
|
|
6386
|
+
if (disposed || records.length === 0) return false;
|
|
6387
|
+
const payloads = records.map(
|
|
6388
|
+
(record) => record.status === "closed" ? { ...record, outcome: record.outcome ?? deriveOutcome(record.closedReason, record.error) } : { ...record }
|
|
6389
|
+
);
|
|
6390
|
+
const batchNotifyId = buildBatchNotifyId(payloads.map((p) => p.id));
|
|
6391
|
+
const content = buildBatchLlmContent(payloads, budget);
|
|
6392
|
+
const details = { batch: true, notifyId: batchNotifyId, items: payloads };
|
|
6393
|
+
return deliverViaLedgerOrKernel(batchNotifyId, content, details);
|
|
6118
6394
|
},
|
|
6119
6395
|
flushPendingNotifications() {
|
|
6120
6396
|
const ledger = getBoundNotifyLedger();
|
|
@@ -6238,6 +6514,15 @@ function createNotifyHost(deps) {
|
|
|
6238
6514
|
emitPendingUnregister(id, reason) {
|
|
6239
6515
|
emitPendingUnregister(deps.getPi(), id, reason);
|
|
6240
6516
|
},
|
|
6517
|
+
toNotifyRecord(record) {
|
|
6518
|
+
return toNotifyRecord(record);
|
|
6519
|
+
},
|
|
6520
|
+
notify(record) {
|
|
6521
|
+
notifier.notify(record);
|
|
6522
|
+
},
|
|
6523
|
+
notifyBatch(records, budget) {
|
|
6524
|
+
return notifier.notifyBatch(records, budget);
|
|
6525
|
+
},
|
|
6241
6526
|
revive() {
|
|
6242
6527
|
notifier.revive();
|
|
6243
6528
|
},
|
|
@@ -6251,7 +6536,7 @@ function createNotifyHost(deps) {
|
|
|
6251
6536
|
}
|
|
6252
6537
|
|
|
6253
6538
|
// src/execution/round-settlement.ts
|
|
6254
|
-
var
|
|
6539
|
+
var logger19 = getLogger("subagents");
|
|
6255
6540
|
function createRoundSettler(deps) {
|
|
6256
6541
|
return (record) => {
|
|
6257
6542
|
record.round = (record.round ?? 0) + 1;
|
|
@@ -6260,7 +6545,7 @@ function createRoundSettler(deps) {
|
|
|
6260
6545
|
deps.notifyComplete(record);
|
|
6261
6546
|
const lastTurn = record.turns[record.turns.length - 1];
|
|
6262
6547
|
if (lastTurn !== void 0 && !lastTurn.closed && lastTurn.text.length > 0) {
|
|
6263
|
-
|
|
6548
|
+
logger19.warn(
|
|
6264
6549
|
`[subagents] round settle with unclosed non-empty turn (record=${record.id}, turnIndex=${record.turns.length - 1}) \u2014 pi turn_end/agent_end ordering may have changed`
|
|
6265
6550
|
);
|
|
6266
6551
|
}
|
|
@@ -6274,8 +6559,8 @@ function createRoundSettler(deps) {
|
|
|
6274
6559
|
}
|
|
6275
6560
|
|
|
6276
6561
|
// src/execution/record-store.ts
|
|
6277
|
-
var
|
|
6278
|
-
var
|
|
6562
|
+
var fs17 = __toESM(require("fs"), 1);
|
|
6563
|
+
var path12 = __toESM(require("path"), 1);
|
|
6279
6564
|
|
|
6280
6565
|
// src/execution/record-entry.ts
|
|
6281
6566
|
var SUBAGENT_RECORD_CUSTOM_TYPE = "subagent-record";
|
|
@@ -6310,14 +6595,18 @@ function toSubagentRecordEntry(record) {
|
|
|
6310
6595
|
resumable: record.resumable,
|
|
6311
6596
|
engine: record.engine,
|
|
6312
6597
|
engineFallback: record.engineFallback,
|
|
6313
|
-
engineHandle: record.engineHandle
|
|
6598
|
+
engineHandle: record.engineHandle,
|
|
6599
|
+
// 同步收集两字段(U1 foundation):undefined 经 JSON.stringify 自然缺省,
|
|
6600
|
+
// 旧记录/旧 entry 序列化产物字节不变(零迁移)。
|
|
6601
|
+
collectMode: record.collectMode,
|
|
6602
|
+
batchFinalized: record.batchFinalized
|
|
6314
6603
|
};
|
|
6315
6604
|
}
|
|
6316
6605
|
|
|
6317
6606
|
// src/execution/sessions-index.ts
|
|
6318
|
-
var
|
|
6319
|
-
var
|
|
6320
|
-
var
|
|
6607
|
+
var fs16 = __toESM(require("fs"), 1);
|
|
6608
|
+
var path11 = __toESM(require("path"), 1);
|
|
6609
|
+
var logger20 = getLogger("subagents");
|
|
6321
6610
|
var INDEX_FILENAME = "sessions-index.json";
|
|
6322
6611
|
var INDEX_VERSION = 1;
|
|
6323
6612
|
var INDEX_WRITE_MIN_INTERVAL_MS = 6e4;
|
|
@@ -6356,11 +6645,11 @@ function errorCodeOf(err) {
|
|
|
6356
6645
|
}
|
|
6357
6646
|
function readIndexFile(indexPath, encDir) {
|
|
6358
6647
|
try {
|
|
6359
|
-
return
|
|
6648
|
+
return fs16.readFileSync(indexPath, "utf-8");
|
|
6360
6649
|
} catch (err) {
|
|
6361
6650
|
const code = errorCodeOf(err);
|
|
6362
6651
|
if (code !== "ENOENT") {
|
|
6363
|
-
|
|
6652
|
+
logger20.debug("[subagents] sessions-index read failed, fallback to empty", {
|
|
6364
6653
|
detail: { dir: encDir, code }
|
|
6365
6654
|
});
|
|
6366
6655
|
}
|
|
@@ -6371,7 +6660,7 @@ function parseIndexJson(raw, indexPath) {
|
|
|
6371
6660
|
try {
|
|
6372
6661
|
return JSON.parse(raw);
|
|
6373
6662
|
} catch (err) {
|
|
6374
|
-
|
|
6663
|
+
logger20.debug("[subagents] sessions-index corrupted JSON, fallback to empty", {
|
|
6375
6664
|
detail: { path: indexPath, error: err instanceof Error ? err.message : String(err) }
|
|
6376
6665
|
});
|
|
6377
6666
|
return null;
|
|
@@ -6382,14 +6671,14 @@ function isIndexTopHeader(v) {
|
|
|
6382
6671
|
}
|
|
6383
6672
|
function readTopLevel(parsed, indexPath) {
|
|
6384
6673
|
if (typeof parsed !== "object" || parsed === null) {
|
|
6385
|
-
|
|
6674
|
+
logger20.debug("[subagents] sessions-index invalid top-level shape, fallback to empty", {
|
|
6386
6675
|
detail: { path: indexPath }
|
|
6387
6676
|
});
|
|
6388
6677
|
return null;
|
|
6389
6678
|
}
|
|
6390
6679
|
const top = parsed;
|
|
6391
6680
|
if (!isIndexTopHeader(top)) {
|
|
6392
|
-
|
|
6681
|
+
logger20.debug("[subagents] sessions-index invalid header fields, fallback to empty", {
|
|
6393
6682
|
detail: { path: indexPath }
|
|
6394
6683
|
});
|
|
6395
6684
|
return null;
|
|
@@ -6406,7 +6695,7 @@ function collectValidEntries(entriesObj) {
|
|
|
6406
6695
|
}
|
|
6407
6696
|
function loadIndex(encDir) {
|
|
6408
6697
|
const empty = { entries: /* @__PURE__ */ new Map(), higherVersion: false };
|
|
6409
|
-
const indexPath =
|
|
6698
|
+
const indexPath = path11.join(encDir, INDEX_FILENAME);
|
|
6410
6699
|
const raw = readIndexFile(indexPath, encDir);
|
|
6411
6700
|
if (raw === null) return empty;
|
|
6412
6701
|
const parsed = parseIndexJson(raw, indexPath);
|
|
@@ -6417,7 +6706,7 @@ function loadIndex(encDir) {
|
|
|
6417
6706
|
return { entries: /* @__PURE__ */ new Map(), higherVersion: true };
|
|
6418
6707
|
}
|
|
6419
6708
|
if (top.version < INDEX_VERSION) {
|
|
6420
|
-
|
|
6709
|
+
logger20.debug("[subagents] sessions-index stale version, discarded", {
|
|
6421
6710
|
detail: { path: indexPath, version: top.version, expected: INDEX_VERSION }
|
|
6422
6711
|
});
|
|
6423
6712
|
return empty;
|
|
@@ -6425,7 +6714,7 @@ function loadIndex(encDir) {
|
|
|
6425
6714
|
return { entries: collectValidEntries(top.entries), higherVersion: false };
|
|
6426
6715
|
}
|
|
6427
6716
|
async function saveIndex(encDir, data) {
|
|
6428
|
-
const filePath =
|
|
6717
|
+
const filePath = path11.join(encDir, INDEX_FILENAME);
|
|
6429
6718
|
const file = {
|
|
6430
6719
|
version: INDEX_VERSION,
|
|
6431
6720
|
pid: process.pid,
|
|
@@ -6435,7 +6724,7 @@ async function saveIndex(encDir, data) {
|
|
|
6435
6724
|
}
|
|
6436
6725
|
|
|
6437
6726
|
// src/execution/record-store.ts
|
|
6438
|
-
var
|
|
6727
|
+
var logger21 = getLogger("subagents");
|
|
6439
6728
|
var STATUS_PRIORITY = {
|
|
6440
6729
|
running: 0,
|
|
6441
6730
|
closed: 3
|
|
@@ -6454,14 +6743,14 @@ var MIN_SEGMENTS_WITH_BOUNDARY = 2;
|
|
|
6454
6743
|
function readLastJsonlLine(sessionFile) {
|
|
6455
6744
|
let fd;
|
|
6456
6745
|
try {
|
|
6457
|
-
fd =
|
|
6458
|
-
const size =
|
|
6746
|
+
fd = fs17.openSync(sessionFile, "r");
|
|
6747
|
+
const size = fs17.fstatSync(fd).size;
|
|
6459
6748
|
let windowBytes = LAST_LINE_WINDOW_BYTES;
|
|
6460
6749
|
let windowStart = Math.max(0, size - windowBytes);
|
|
6461
6750
|
let lines = [];
|
|
6462
6751
|
while (true) {
|
|
6463
6752
|
const buf = Buffer.alloc(size - windowStart);
|
|
6464
|
-
|
|
6753
|
+
fs17.readSync(fd, buf, 0, buf.length, windowStart);
|
|
6465
6754
|
lines = buf.toString("utf-8").split("\n").filter((l) => l.length > 0);
|
|
6466
6755
|
if (windowStart === 0 || lines.length >= MIN_SEGMENTS_WITH_BOUNDARY) break;
|
|
6467
6756
|
windowBytes *= WINDOW_GROWTH_FACTOR;
|
|
@@ -6478,7 +6767,7 @@ function readLastJsonlLine(sessionFile) {
|
|
|
6478
6767
|
} finally {
|
|
6479
6768
|
if (fd !== void 0) {
|
|
6480
6769
|
try {
|
|
6481
|
-
|
|
6770
|
+
fs17.closeSync(fd);
|
|
6482
6771
|
} catch (_e) {
|
|
6483
6772
|
void _e;
|
|
6484
6773
|
}
|
|
@@ -6501,7 +6790,7 @@ function collectLastRecordEntries(content) {
|
|
|
6501
6790
|
try {
|
|
6502
6791
|
entry = asSubagentRecordEntry(JSON.parse(line));
|
|
6503
6792
|
} catch (err) {
|
|
6504
|
-
|
|
6793
|
+
logger21.debug("[subagents] entry-only orphan scan: skip unparsable line", {
|
|
6505
6794
|
reason: err instanceof Error ? err.message : String(err)
|
|
6506
6795
|
});
|
|
6507
6796
|
}
|
|
@@ -6509,37 +6798,64 @@ function collectLastRecordEntries(content) {
|
|
|
6509
6798
|
}
|
|
6510
6799
|
return lastById;
|
|
6511
6800
|
}
|
|
6801
|
+
function entryStr(d, k) {
|
|
6802
|
+
return typeof d[k] === "string" ? d[k] : void 0;
|
|
6803
|
+
}
|
|
6804
|
+
function entryNum(d, k) {
|
|
6805
|
+
return typeof d[k] === "number" ? d[k] : void 0;
|
|
6806
|
+
}
|
|
6807
|
+
function readEntryTerminalFields(d) {
|
|
6808
|
+
const closedReason = entryStr(d, "closedReason");
|
|
6809
|
+
return {
|
|
6810
|
+
status: d.status === "closed" ? "closed" : "running",
|
|
6811
|
+
closedReason: isValidClosedReason(closedReason) ? closedReason : void 0
|
|
6812
|
+
};
|
|
6813
|
+
}
|
|
6814
|
+
function readEntryBatchFields(d) {
|
|
6815
|
+
return {
|
|
6816
|
+
collectMode: d.collectMode === "sync" ? "sync" : void 0,
|
|
6817
|
+
batchFinalized: d.batchFinalized === true ? true : void 0,
|
|
6818
|
+
resumable: d.resumable === true ? true : void 0
|
|
6819
|
+
};
|
|
6820
|
+
}
|
|
6821
|
+
function readEntryEngineFields(d) {
|
|
6822
|
+
return {
|
|
6823
|
+
engine: entryStr(d, "engine"),
|
|
6824
|
+
engineFallback: isEngineFallbackShape(d.engineFallback) ? d.engineFallback : void 0,
|
|
6825
|
+
engineHandle: isEngineHandleShape(d.engineHandle) ? d.engineHandle : void 0
|
|
6826
|
+
};
|
|
6827
|
+
}
|
|
6512
6828
|
function rebuildEntryRecord(id, d) {
|
|
6513
|
-
const
|
|
6514
|
-
const
|
|
6515
|
-
const
|
|
6516
|
-
const task = str("task");
|
|
6517
|
-
const startedAt = num("startedAt");
|
|
6829
|
+
const agent = entryStr(d, "agent");
|
|
6830
|
+
const task = entryStr(d, "task");
|
|
6831
|
+
const startedAt = entryNum(d, "startedAt");
|
|
6518
6832
|
if (agent === void 0 || task === void 0 || startedAt === void 0) return null;
|
|
6519
6833
|
return {
|
|
6520
6834
|
id,
|
|
6521
6835
|
agent,
|
|
6522
6836
|
task,
|
|
6523
|
-
slug:
|
|
6524
|
-
|
|
6837
|
+
slug: entryStr(d, "slug") ?? "",
|
|
6838
|
+
...readEntryTerminalFields(d),
|
|
6525
6839
|
mode: "background",
|
|
6526
6840
|
startedAt,
|
|
6527
|
-
rootSessionId:
|
|
6528
|
-
parentRecordId:
|
|
6529
|
-
depth:
|
|
6530
|
-
endedAt:
|
|
6531
|
-
turns:
|
|
6532
|
-
totalTokens:
|
|
6533
|
-
model:
|
|
6534
|
-
thinkingLevel:
|
|
6841
|
+
rootSessionId: entryStr(d, "rootSessionId"),
|
|
6842
|
+
parentRecordId: entryStr(d, "parentRecordId"),
|
|
6843
|
+
depth: entryNum(d, "depth") ?? 0,
|
|
6844
|
+
endedAt: entryNum(d, "endedAt"),
|
|
6845
|
+
turns: entryNum(d, "turns") ?? 0,
|
|
6846
|
+
totalTokens: entryNum(d, "totalTokens") ?? 0,
|
|
6847
|
+
model: entryStr(d, "model") ?? "",
|
|
6848
|
+
thinkingLevel: entryStr(d, "thinkingLevel"),
|
|
6535
6849
|
eventLog: [],
|
|
6536
6850
|
displayItems: [],
|
|
6537
|
-
|
|
6851
|
+
result: entryStr(d, "result"),
|
|
6852
|
+
error: entryStr(d, "error"),
|
|
6853
|
+
sessionFile: entryStr(d, "sessionFile"),
|
|
6854
|
+
patchFile: entryStr(d, "patchFile"),
|
|
6538
6855
|
chatMode: d.chatMode === true,
|
|
6539
|
-
round:
|
|
6540
|
-
|
|
6541
|
-
|
|
6542
|
-
engineHandle: isEngineHandleShape(d.engineHandle) ? d.engineHandle : void 0
|
|
6856
|
+
round: entryNum(d, "round"),
|
|
6857
|
+
...readEntryEngineFields(d),
|
|
6858
|
+
...readEntryBatchFields(d)
|
|
6543
6859
|
};
|
|
6544
6860
|
}
|
|
6545
6861
|
function isEngineFallbackShape(v) {
|
|
@@ -6562,7 +6878,7 @@ function isEngineHandleShape(v) {
|
|
|
6562
6878
|
}
|
|
6563
6879
|
function statStamp2(p) {
|
|
6564
6880
|
try {
|
|
6565
|
-
const s =
|
|
6881
|
+
const s = fs17.statSync(p);
|
|
6566
6882
|
return { mtimeMs: s.mtimeMs, size: s.size };
|
|
6567
6883
|
} catch {
|
|
6568
6884
|
return null;
|
|
@@ -6738,7 +7054,7 @@ var RecordStore = class _RecordStore {
|
|
|
6738
7054
|
if (rootSessionFilter !== void 0 && manifest.rootSessionId !== rootSessionFilter) continue;
|
|
6739
7055
|
const rec = _RecordStore.manifestToSubagent(manifest);
|
|
6740
7056
|
if (!rec) {
|
|
6741
|
-
|
|
7057
|
+
logger21.warn("[subagents] skip manifest with invalid status", {
|
|
6742
7058
|
detail: { id: manifest.id, status: manifest.status }
|
|
6743
7059
|
});
|
|
6744
7060
|
this.pi?.appendEntry?.("subagent:manifest-invalid-status", {
|
|
@@ -6785,31 +7101,44 @@ var RecordStore = class _RecordStore {
|
|
|
6785
7101
|
* - 文件不可读(IO 错误,可能暂时)→ 不判终态,落 resumable entry(防御性路径,
|
|
6786
7102
|
* IO 恢复后重开可重判)。
|
|
6787
7103
|
*
|
|
7104
|
+
* [v2 D3] mainSessionFile = 覆写 merge 数据源(主 session 每 id 末条 entry,与 E1
|
|
7105
|
+
* 的 scanLastRecordEntries 同款通路):崩溃前的批域标记(collectMode/batchFinalized)
|
|
7106
|
+
* 与轮终 result/model 只活在主文件 entry——重建矩阵(buildRecord)的数据源
|
|
7107
|
+
* (sidecar/子文件 identity)不含它们,覆写前不 merge 就会被抹掉(v2 §2.2 断链 2:
|
|
7108
|
+
* E1 候选集恒空的真根因)。缺省(undefined)时 merge 无源,行为与旧版一致。
|
|
7109
|
+
* 参数为追加式第二参(rootSessionFilter 保持首参):既有调用面只传过滤参。
|
|
7110
|
+
*
|
|
6788
7111
|
* 防重:orphanJudged 实例级缓存(resumable 形态无 sidecar 锚,同进程重复调用跳过;
|
|
6789
7112
|
* 终态形态双重防护 = sidecar + 缓存)。调用方:index.ts session_start 恢复段(一次)。
|
|
6790
7113
|
*/
|
|
6791
|
-
recoverOrphanRecords(rootSessionFilter) {
|
|
7114
|
+
recoverOrphanRecords(rootSessionFilter, mainSessionFile) {
|
|
7115
|
+
const lastById = new Map(this.scanLastRecordEntries(mainSessionFile).map((r) => [r.id, r]));
|
|
6792
7116
|
for (const rec of this.reconstructAll(rootSessionFilter)) {
|
|
6793
7117
|
if (rec.status !== "running" || rec.externalInstance !== void 0) continue;
|
|
6794
7118
|
if (this.orphanJudged.has(rec.id)) continue;
|
|
6795
7119
|
this.orphanJudged.add(rec.id);
|
|
6796
|
-
this.finalizeOrphanRecord(rec);
|
|
7120
|
+
this.finalizeOrphanRecord(rec, lastById.get(rec.id));
|
|
6797
7121
|
}
|
|
6798
7122
|
}
|
|
6799
7123
|
/**
|
|
6800
7124
|
* 单孤儿 record 的终态判定与落 entry(residual-fixes §5.2 三判据 + chat 分流)。
|
|
6801
7125
|
* 防重锚(orphanJudged 标记)已由调用方完成。
|
|
7126
|
+
*
|
|
7127
|
+
* [v2 D3] 覆写前 merge(lastEntry = 主 session 同 id 末条 entry 重建,调用方构建):
|
|
7128
|
+
* 覆写是状态迁移不是信息重建,迁移不应丢末条既有信息。三个落 entry 分支(chatMode
|
|
7129
|
+
* 分流 / IO 保守 / 终态覆写)统一基于 merge 后的 rec,同款口径。
|
|
6802
7130
|
*/
|
|
6803
|
-
finalizeOrphanRecord(rec) {
|
|
6804
|
-
|
|
6805
|
-
|
|
7131
|
+
finalizeOrphanRecord(rec, lastEntry) {
|
|
7132
|
+
const rec0 = lastEntry === void 0 ? rec : _RecordStore.mergeOrphanLastEntry(rec, lastEntry);
|
|
7133
|
+
if (rec0.chatMode === true) {
|
|
7134
|
+
this.reportSubagentRecord({ ...rec0, resumable: true });
|
|
6806
7135
|
return;
|
|
6807
7136
|
}
|
|
6808
|
-
const sessionFile =
|
|
7137
|
+
const sessionFile = rec0.sessionFile;
|
|
6809
7138
|
if (sessionFile === void 0) return;
|
|
6810
7139
|
const lastLine = readLastJsonlLine(sessionFile);
|
|
6811
7140
|
if (!lastLine.ok) {
|
|
6812
|
-
this.reportSubagentRecord({ ...
|
|
7141
|
+
this.reportSubagentRecord({ ...rec0, resumable: true });
|
|
6813
7142
|
return;
|
|
6814
7143
|
}
|
|
6815
7144
|
let parseOk = false;
|
|
@@ -6821,13 +7150,30 @@ var RecordStore = class _RecordStore {
|
|
|
6821
7150
|
}
|
|
6822
7151
|
writeFinalized(sessionFile, "gc");
|
|
6823
7152
|
this.reportSubagentRecord({
|
|
6824
|
-
...
|
|
7153
|
+
...rec0,
|
|
6825
7154
|
status: "closed",
|
|
6826
7155
|
closedReason: "gc",
|
|
6827
7156
|
endedAt: Date.now(),
|
|
6828
7157
|
...parseOk ? {} : { error: "orphan recovery: subagent session ended abnormally (truncated last line)" }
|
|
6829
7158
|
});
|
|
6830
7159
|
}
|
|
7160
|
+
/** [v2 D3] 孤儿覆写 merge 字段集:末条 entry 的批域标记 + 轮终正文/模型,仅补 rec 侧
|
|
7161
|
+
* undefined/空值(model 的空值形态是 ""——light 重建无 model_change entry 时起步
|
|
7162
|
+
* 空串),不覆盖已有值。merge 后写 entry 经 reportSubagentRecord →
|
|
7163
|
+
* toSubagentRecordEntry 序列化,undefined 字段自然缺省(不引入显式 null)。 */
|
|
7164
|
+
static mergeOrphanLastEntry(rec, last) {
|
|
7165
|
+
const pickStr = (cur, src) => cur !== void 0 && cur !== "" ? cur : src;
|
|
7166
|
+
return {
|
|
7167
|
+
...rec,
|
|
7168
|
+
collectMode: rec.collectMode ?? last.collectMode,
|
|
7169
|
+
batchFinalized: rec.batchFinalized ?? last.batchFinalized,
|
|
7170
|
+
result: pickStr(rec.result, last.result),
|
|
7171
|
+
// 类型收尾:两侧实参恒 string(rec.model 类型非可选;last.model 重建投影自带
|
|
7172
|
+
// `?? ""`),pickStr 签名宽返回 string|undefined —— `?? ""` 运行时不可达,
|
|
7173
|
+
// 仅满足 model 非可选类型,空串回退语义不变(cur 空 → src,src 也空 → "")。
|
|
7174
|
+
model: pickStr(rec.model, last.model) ?? ""
|
|
7175
|
+
};
|
|
7176
|
+
}
|
|
6831
7177
|
/**
|
|
6832
7178
|
* [E2E 实测缺口] entry-born 孤儿恢复:register entry 已落主 session、但子 session 文件
|
|
6833
7179
|
* 从未创建(父进程死在 spawn 窗口期——register 写点与子进程首笔写入之间的窗口;外部
|
|
@@ -6844,7 +7190,7 @@ var RecordStore = class _RecordStore {
|
|
|
6844
7190
|
if (mainSessionFile === void 0) return;
|
|
6845
7191
|
let content;
|
|
6846
7192
|
try {
|
|
6847
|
-
content =
|
|
7193
|
+
content = fs17.readFileSync(mainSessionFile, "utf-8");
|
|
6848
7194
|
} catch {
|
|
6849
7195
|
return;
|
|
6850
7196
|
}
|
|
@@ -6883,6 +7229,31 @@ var RecordStore = class _RecordStore {
|
|
|
6883
7229
|
}
|
|
6884
7230
|
});
|
|
6885
7231
|
}
|
|
7232
|
+
/**
|
|
7233
|
+
* [E1/U5] sync 批崩溃恢复扫描:主 session 文件「每 id 末条 subagent-record entry」
|
|
7234
|
+
* (collectLastRecordEntries + rebuildEntryRecord 组合通路,设计 §3.1.3「标记读取
|
|
7235
|
+
* 通路」——batchFinalized 落标 entry 写主 session 文件,本扫描同文件域才可见;禁走
|
|
7236
|
+
* collectRecords light 路径,它只读子文件 identity 头+sidecar,主 session 落标
|
|
7237
|
+
* entry 不可见)。返回每 id 末条重建的完整 record(含 collectMode/batchFinalized /
|
|
7238
|
+
* 终态五字段,损坏 entry 跳过);调用方(service.recoverSyncCollectBatch 的 E1 过滤、
|
|
7239
|
+
* recoverOrphanRecords 的覆写 merge)自行取舍。主文件不可读(含新 session 未 flush
|
|
7240
|
+
* 的 ENOENT)→ 空数组静默。
|
|
7241
|
+
*/
|
|
7242
|
+
scanLastRecordEntries(mainSessionFile) {
|
|
7243
|
+
if (mainSessionFile === void 0) return [];
|
|
7244
|
+
let content;
|
|
7245
|
+
try {
|
|
7246
|
+
content = fs17.readFileSync(mainSessionFile, "utf-8");
|
|
7247
|
+
} catch {
|
|
7248
|
+
return [];
|
|
7249
|
+
}
|
|
7250
|
+
const out = [];
|
|
7251
|
+
for (const [id, d] of collectLastRecordEntries(content)) {
|
|
7252
|
+
const rec = rebuildEntryRecord(id, d);
|
|
7253
|
+
if (rec !== null) out.push(rec);
|
|
7254
|
+
}
|
|
7255
|
+
return out;
|
|
7256
|
+
}
|
|
6886
7257
|
/** 订阅变更。返回取消订阅函数。 */
|
|
6887
7258
|
onChange(listener) {
|
|
6888
7259
|
this.listeners.add(listener);
|
|
@@ -6945,12 +7316,12 @@ var RecordStore = class _RecordStore {
|
|
|
6945
7316
|
reconstructAll(rootSessionFilter) {
|
|
6946
7317
|
let dirMtimeMs;
|
|
6947
7318
|
try {
|
|
6948
|
-
dirMtimeMs =
|
|
7319
|
+
dirMtimeMs = fs17.statSync(this.sessionsDir).mtimeMs;
|
|
6949
7320
|
} catch {
|
|
6950
7321
|
return [];
|
|
6951
7322
|
}
|
|
6952
7323
|
if (this.dirStamp === null) {
|
|
6953
|
-
const loaded = loadIndex(
|
|
7324
|
+
const loaded = loadIndex(path12.dirname(this.sessionsDir));
|
|
6954
7325
|
this.indexEntries = loaded.entries;
|
|
6955
7326
|
this.indexHigherVersion = loaded.higherVersion;
|
|
6956
7327
|
}
|
|
@@ -6966,7 +7337,7 @@ var RecordStore = class _RecordStore {
|
|
|
6966
7337
|
}
|
|
6967
7338
|
let files;
|
|
6968
7339
|
try {
|
|
6969
|
-
files =
|
|
7340
|
+
files = fs17.readdirSync(this.sessionsDir).filter((f) => f.endsWith(".jsonl")).map((f) => path12.join(this.sessionsDir, f));
|
|
6970
7341
|
} catch {
|
|
6971
7342
|
this.indexEntries = null;
|
|
6972
7343
|
return [];
|
|
@@ -7040,7 +7411,7 @@ var RecordStore = class _RecordStore {
|
|
|
7040
7411
|
*/
|
|
7041
7412
|
buildEntryFromIndex(file, stamps, now) {
|
|
7042
7413
|
if (this.indexEntries === null) return void 0;
|
|
7043
|
-
const hit = this.indexEntries.get(
|
|
7414
|
+
const hit = this.indexEntries.get(path12.basename(file));
|
|
7044
7415
|
if (hit === void 0 || hit.mtimeMs !== stamps.jsonl.mtimeMs || hit.size !== stamps.jsonl.size) {
|
|
7045
7416
|
return void 0;
|
|
7046
7417
|
}
|
|
@@ -7072,12 +7443,12 @@ var RecordStore = class _RecordStore {
|
|
|
7072
7443
|
if (Date.now() - this.lastIndexWriteAt < INDEX_WRITE_MIN_INTERVAL_MS) return;
|
|
7073
7444
|
const entries = this.projectIndexEntries();
|
|
7074
7445
|
this.indexDirty = false;
|
|
7075
|
-
const encDir =
|
|
7446
|
+
const encDir = path12.dirname(this.sessionsDir);
|
|
7076
7447
|
saveIndex(encDir, { entries }).then(() => {
|
|
7077
7448
|
this.lastIndexWriteAt = Date.now();
|
|
7078
7449
|
}).catch((err) => {
|
|
7079
7450
|
this.indexDirty = true;
|
|
7080
|
-
|
|
7451
|
+
logger21.warn("[subagents] sessions-index write failed", {
|
|
7081
7452
|
detail: { dir: encDir, error: err instanceof Error ? err.message : String(err) }
|
|
7082
7453
|
});
|
|
7083
7454
|
});
|
|
@@ -7091,7 +7462,7 @@ var RecordStore = class _RecordStore {
|
|
|
7091
7462
|
projectIndexEntries() {
|
|
7092
7463
|
const entries = /* @__PURE__ */ new Map();
|
|
7093
7464
|
for (const [file, cached] of this.fileCache) {
|
|
7094
|
-
const base =
|
|
7465
|
+
const base = path12.basename(file);
|
|
7095
7466
|
if (cached.negative) {
|
|
7096
7467
|
entries.set(base, { negative: true, mtimeMs: cached.jsonl.mtimeMs, size: cached.jsonl.size });
|
|
7097
7468
|
} else {
|
|
@@ -7346,15 +7717,65 @@ var RecordStore = class _RecordStore {
|
|
|
7346
7717
|
engine: r.engine,
|
|
7347
7718
|
engineFallback: r.engineFallback,
|
|
7348
7719
|
// U2:engineHandle 经 entry 持久化(register/archive 双写点均经本投影),无则 undefined 自然省略
|
|
7349
|
-
engineHandle: r.engineHandle
|
|
7720
|
+
engineHandle: r.engineHandle,
|
|
7721
|
+
// [U5 修复 U2 披露的投影缺口] 同步收集两字段随本投影持久化(register entry /
|
|
7722
|
+
// archive entry 双写点)——原缺失时闭合判定 flushBatch 重建、E1 重建扫描等消费方
|
|
7723
|
+
// 读不到原始值。undefined 经 JSON.stringify 自然缺省,旧 entry 零迁移。
|
|
7724
|
+
collectMode: r.collectMode,
|
|
7725
|
+
batchFinalized: r.batchFinalized
|
|
7350
7726
|
};
|
|
7351
7727
|
}
|
|
7352
7728
|
};
|
|
7353
7729
|
|
|
7730
|
+
// src/execution/sync-rebuild.ts
|
|
7731
|
+
function syncRebuildToNotifyMember(rec) {
|
|
7732
|
+
const status = rec.status === "closed" || !rec.chatMode ? "closed" : "running";
|
|
7733
|
+
return {
|
|
7734
|
+
id: rec.id,
|
|
7735
|
+
status,
|
|
7736
|
+
closedReason: rec.closedReason,
|
|
7737
|
+
outcome: status === "closed" ? deriveOutcome(rec.closedReason, rec.error) : void 0,
|
|
7738
|
+
agent: rec.agent,
|
|
7739
|
+
model: rec.model,
|
|
7740
|
+
result: rec.result,
|
|
7741
|
+
error: rec.error,
|
|
7742
|
+
startedAt: rec.startedAt,
|
|
7743
|
+
endedAt: rec.endedAt,
|
|
7744
|
+
patchFile: rec.patchFile
|
|
7745
|
+
};
|
|
7746
|
+
}
|
|
7747
|
+
function bufferedMemberFallbackRecord(m, rootSessionId) {
|
|
7748
|
+
return {
|
|
7749
|
+
id: m.id,
|
|
7750
|
+
agent: m.agent,
|
|
7751
|
+
task: "",
|
|
7752
|
+
slug: "",
|
|
7753
|
+
status: "closed",
|
|
7754
|
+
closedReason: m.closedReason,
|
|
7755
|
+
mode: "background",
|
|
7756
|
+
startedAt: m.startedAt,
|
|
7757
|
+
rootSessionId,
|
|
7758
|
+
parentRecordId: void 0,
|
|
7759
|
+
depth: 0,
|
|
7760
|
+
endedAt: m.endedAt,
|
|
7761
|
+
turns: 0,
|
|
7762
|
+
totalTokens: 0,
|
|
7763
|
+
model: m.model ?? "",
|
|
7764
|
+
thinkingLevel: void 0,
|
|
7765
|
+
eventLog: [],
|
|
7766
|
+
displayItems: [],
|
|
7767
|
+
result: m.result,
|
|
7768
|
+
error: m.error,
|
|
7769
|
+
sessionFile: void 0,
|
|
7770
|
+
chatMode: false,
|
|
7771
|
+
patchFile: m.patchFile
|
|
7772
|
+
};
|
|
7773
|
+
}
|
|
7774
|
+
|
|
7354
7775
|
// src/execution/engine/common/pool-manager.ts
|
|
7355
7776
|
var fsSync = __toESM(require("fs"), 1);
|
|
7356
7777
|
var import_node_path5 = require("path");
|
|
7357
|
-
var
|
|
7778
|
+
var logger22 = getLogger("subagents");
|
|
7358
7779
|
var POOL_CLEANUP_FAILED_MARKER = ".pool-cleanup-failed";
|
|
7359
7780
|
var REFS_JSON_FILENAME = "refs.json";
|
|
7360
7781
|
var REFS_VERSION = 1;
|
|
@@ -7370,7 +7791,7 @@ function readPoolRefs(poolDir, fs28) {
|
|
|
7370
7791
|
try {
|
|
7371
7792
|
parsed = JSON.parse(fs28.readFileSync(refsPath));
|
|
7372
7793
|
} catch (err) {
|
|
7373
|
-
|
|
7794
|
+
logger22.warn(
|
|
7374
7795
|
`[pool-manager] refs.json unparsable for ${poolDir}, starting from empty refs: ${toErrorMessage(err)}`
|
|
7375
7796
|
);
|
|
7376
7797
|
return emptyRefs();
|
|
@@ -7378,7 +7799,7 @@ function readPoolRefs(poolDir, fs28) {
|
|
|
7378
7799
|
if (typeof parsed !== "object" || parsed === null) return emptyRefs();
|
|
7379
7800
|
const obj = parsed;
|
|
7380
7801
|
if (obj.v !== REFS_VERSION || typeof obj.refs !== "object" || obj.refs === null) {
|
|
7381
|
-
|
|
7802
|
+
logger22.warn(`[pool-manager] refs.json unexpected shape for ${poolDir}, starting from empty refs`);
|
|
7382
7803
|
return emptyRefs();
|
|
7383
7804
|
}
|
|
7384
7805
|
const refs = {};
|
|
@@ -7412,7 +7833,7 @@ function releasePoolRef(dataDir, engineId, poolKey, taskId, fs28 = nodeFs) {
|
|
|
7412
7833
|
removeJournalFile(dataDir, engineId, poolKey, taskId, fs28);
|
|
7413
7834
|
const file = readPoolRefs(poolDir, fs28);
|
|
7414
7835
|
if (file.refs[taskId] === void 0) {
|
|
7415
|
-
|
|
7836
|
+
logger22.debug(`[pool-manager] release without live ref, skip pool deletion: ${poolDir} (${taskId})`);
|
|
7416
7837
|
return;
|
|
7417
7838
|
}
|
|
7418
7839
|
delete file.refs[taskId];
|
|
@@ -7505,7 +7926,7 @@ function removeOrphanJournals(poolDir, file, entries, ttlMs, fs28, now) {
|
|
|
7505
7926
|
changed = true;
|
|
7506
7927
|
}
|
|
7507
7928
|
} catch (err) {
|
|
7508
|
-
|
|
7929
|
+
logger22.debug(
|
|
7509
7930
|
`[pool-manager] ttl cleanup stat failed for ${journalPath}: ${toErrorMessage(err)}`
|
|
7510
7931
|
);
|
|
7511
7932
|
}
|
|
@@ -7520,7 +7941,7 @@ function unlinkBestEffort(path21, fs28) {
|
|
|
7520
7941
|
try {
|
|
7521
7942
|
fs28.rmSync(path21, { force: true, recursive: true });
|
|
7522
7943
|
} catch (err) {
|
|
7523
|
-
|
|
7944
|
+
logger22.debug(`[pool-manager] ttl cleanup failed for ${path21}: ${toErrorMessage(err)}`);
|
|
7524
7945
|
}
|
|
7525
7946
|
}
|
|
7526
7947
|
function isJournalFile(name) {
|
|
@@ -7561,14 +7982,14 @@ function deletePoolNativeState(poolDir, fs28) {
|
|
|
7561
7982
|
function markCleanupFailed(poolDir, failures, fs28) {
|
|
7562
7983
|
const marker = (0, import_node_path5.join)(poolDir, POOL_CLEANUP_FAILED_MARKER);
|
|
7563
7984
|
const payload = JSON.stringify({ ts: Date.now(), failures });
|
|
7564
|
-
|
|
7985
|
+
logger22.warn(
|
|
7565
7986
|
`[pool-manager] pool cleanup failed for ${poolDir} (${failures.length} item(s)); marker written to ${marker} \u2014 re-run cleanup after fixing the underlying error`
|
|
7566
7987
|
);
|
|
7567
7988
|
try {
|
|
7568
7989
|
fs28.writeFileSync(marker, `${payload}
|
|
7569
7990
|
`);
|
|
7570
7991
|
} catch (err) {
|
|
7571
|
-
|
|
7992
|
+
logger22.warn(
|
|
7572
7993
|
`[pool-manager] failed to write cleanup-failed marker ${marker}: ${toErrorMessage(err)}`
|
|
7573
7994
|
);
|
|
7574
7995
|
}
|
|
@@ -7586,7 +8007,7 @@ var nodeFs = {
|
|
|
7586
8007
|
};
|
|
7587
8008
|
|
|
7588
8009
|
// src/execution/idle-gc.ts
|
|
7589
|
-
var
|
|
8010
|
+
var logger23 = getLogger("subagents");
|
|
7590
8011
|
var GC_INTERVAL_MS = 60 * 60 * 1e3;
|
|
7591
8012
|
var IDLE_TTL_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
7592
8013
|
var MS_PER_DAY = 24 * 60 * 60 * 1e3;
|
|
@@ -7597,7 +8018,7 @@ function startIdleGc(store) {
|
|
|
7597
8018
|
if (isResumable(record) && record.idleSince) {
|
|
7598
8019
|
const age = now - record.idleSince;
|
|
7599
8020
|
if (age > IDLE_TTL_MS) {
|
|
7600
|
-
|
|
8021
|
+
logger23.warn(`[subagents] GC: archiving idle record ${record.id} (idle for ${Math.round(age / MS_PER_DAY)}d)`);
|
|
7601
8022
|
try {
|
|
7602
8023
|
store.archive(record);
|
|
7603
8024
|
} catch (err) {
|
|
@@ -7680,15 +8101,15 @@ function createBackgroundStream(recordId, sink, mode, env) {
|
|
|
7680
8101
|
|
|
7681
8102
|
// src/execution/worktree-manager.ts
|
|
7682
8103
|
var import_node_child_process3 = require("child_process");
|
|
7683
|
-
var
|
|
8104
|
+
var fs19 = __toESM(require("fs"), 1);
|
|
7684
8105
|
var os2 = __toESM(require("os"), 1);
|
|
7685
|
-
var
|
|
8106
|
+
var path14 = __toESM(require("path"), 1);
|
|
7686
8107
|
|
|
7687
8108
|
// src/execution/worktree-registry.ts
|
|
7688
|
-
var
|
|
7689
|
-
var
|
|
8109
|
+
var fs18 = __toESM(require("fs"), 1);
|
|
8110
|
+
var path13 = __toESM(require("path"), 1);
|
|
7690
8111
|
var import_proper_lockfile = __toESM(require("proper-lockfile"), 1);
|
|
7691
|
-
var
|
|
8112
|
+
var logger24 = getLogger("subagents");
|
|
7692
8113
|
var SPAWN_GRACE_MS = 6e4;
|
|
7693
8114
|
var JSON_INDENT2 = 2;
|
|
7694
8115
|
var LOCK_STALE_MS = 3e4;
|
|
@@ -7699,7 +8120,7 @@ function isRegistryData(value) {
|
|
|
7699
8120
|
var WorktreeRegistry = class {
|
|
7700
8121
|
filePath;
|
|
7701
8122
|
constructor(agentDir) {
|
|
7702
|
-
this.filePath =
|
|
8123
|
+
this.filePath = path13.join(agentDir, "subagents", "worktrees.json");
|
|
7703
8124
|
}
|
|
7704
8125
|
/**
|
|
7705
8126
|
* 新增条目(create 成功后调,pid=0 占位)。
|
|
@@ -7771,7 +8192,7 @@ var WorktreeRegistry = class {
|
|
|
7771
8192
|
run();
|
|
7772
8193
|
});
|
|
7773
8194
|
} catch (lockErr) {
|
|
7774
|
-
|
|
8195
|
+
logger24.warn("[worktree] registry lock unavailable, degraded to lock-free RMW", {
|
|
7775
8196
|
...context ?? {},
|
|
7776
8197
|
err: lockErr instanceof Error ? lockErr.message : String(lockErr)
|
|
7777
8198
|
});
|
|
@@ -7796,8 +8217,8 @@ var WorktreeRegistry = class {
|
|
|
7796
8217
|
* 锁参数值由 LOCK_STALE_MS / LOCK_RETRIES 常量承载(防漂移说明见常量注释)。
|
|
7797
8218
|
*/
|
|
7798
8219
|
async withLock(fn) {
|
|
7799
|
-
const dir =
|
|
7800
|
-
if (!
|
|
8220
|
+
const dir = path13.dirname(this.filePath);
|
|
8221
|
+
if (!fs18.existsSync(dir)) fs18.mkdirSync(dir, { recursive: true });
|
|
7801
8222
|
let compromised;
|
|
7802
8223
|
const release = await import_proper_lockfile.default.lock(this.filePath, {
|
|
7803
8224
|
realpath: false,
|
|
@@ -7820,7 +8241,7 @@ var WorktreeRegistry = class {
|
|
|
7820
8241
|
try {
|
|
7821
8242
|
await release();
|
|
7822
8243
|
} catch (unlockErr) {
|
|
7823
|
-
|
|
8244
|
+
logger24.debug("unlock failed after compromise (ignorable)", {
|
|
7824
8245
|
detail: { err: unlockErr instanceof Error ? unlockErr.message : String(unlockErr) }
|
|
7825
8246
|
});
|
|
7826
8247
|
}
|
|
@@ -7832,7 +8253,7 @@ var WorktreeRegistry = class {
|
|
|
7832
8253
|
*/
|
|
7833
8254
|
load() {
|
|
7834
8255
|
try {
|
|
7835
|
-
const raw =
|
|
8256
|
+
const raw = fs18.readFileSync(this.filePath, "utf-8");
|
|
7836
8257
|
const parsed = JSON.parse(raw);
|
|
7837
8258
|
if (isRegistryData(parsed)) {
|
|
7838
8259
|
return parsed.entries;
|
|
@@ -7855,7 +8276,7 @@ var WorktreeRegistry = class {
|
|
|
7855
8276
|
writeAtomicFileSync(this.filePath, JSON.stringify({ entries }, null, JSON_INDENT2));
|
|
7856
8277
|
} catch (err) {
|
|
7857
8278
|
bestEffort(err, "worktree registry save");
|
|
7858
|
-
|
|
8279
|
+
logger24.warn(
|
|
7859
8280
|
"[worktree] registry save failed; pid may stay 0 and be reaped by orphan reaper",
|
|
7860
8281
|
{ ...context ?? {}, err: err instanceof Error ? err.message : String(err) }
|
|
7861
8282
|
);
|
|
@@ -7864,7 +8285,7 @@ var WorktreeRegistry = class {
|
|
|
7864
8285
|
};
|
|
7865
8286
|
|
|
7866
8287
|
// src/execution/worktree-manager.ts
|
|
7867
|
-
var
|
|
8288
|
+
var logger25 = getLogger("subagents");
|
|
7868
8289
|
var SAFE_ID_RE = /^[\w-]+$/;
|
|
7869
8290
|
var GIT_TIMEOUT_MS = 3e4;
|
|
7870
8291
|
var WORKTREE_TMP_ROOT = "pi-subagents";
|
|
@@ -7889,13 +8310,13 @@ function isWriteCommand(args) {
|
|
|
7889
8310
|
}
|
|
7890
8311
|
function resolveRepoFromCheckout(checkout) {
|
|
7891
8312
|
try {
|
|
7892
|
-
const raw =
|
|
8313
|
+
const raw = fs19.readFileSync(path14.join(checkout, ".git"), "utf-8").trim();
|
|
7893
8314
|
if (!raw.startsWith("gitdir:")) return void 0;
|
|
7894
8315
|
const gitdir = raw.slice("gitdir:".length).trim();
|
|
7895
|
-
const worktreesDir =
|
|
7896
|
-
if (
|
|
7897
|
-
const gitRootDir =
|
|
7898
|
-
return
|
|
8316
|
+
const worktreesDir = path14.dirname(gitdir);
|
|
8317
|
+
if (path14.basename(worktreesDir) !== "worktrees") return void 0;
|
|
8318
|
+
const gitRootDir = path14.dirname(worktreesDir);
|
|
8319
|
+
return path14.dirname(gitRootDir);
|
|
7899
8320
|
} catch {
|
|
7900
8321
|
return void 0;
|
|
7901
8322
|
}
|
|
@@ -7948,10 +8369,10 @@ ${statusText}`
|
|
|
7948
8369
|
if (revR.status === "rejected") throw revR.reason;
|
|
7949
8370
|
const baseCommit = revR.value.trim();
|
|
7950
8371
|
const branch = `pi-sub-${recordId}`;
|
|
7951
|
-
const worktreePath =
|
|
7952
|
-
if (
|
|
8372
|
+
const worktreePath = path14.join(os2.tmpdir(), "pi-subagents", encodeCwd(mainCwd), branch);
|
|
8373
|
+
if (fs19.existsSync(worktreePath)) {
|
|
7953
8374
|
try {
|
|
7954
|
-
|
|
8375
|
+
fs19.rmSync(worktreePath, { recursive: true, force: true });
|
|
7955
8376
|
} catch (cleanErr) {
|
|
7956
8377
|
bestEffort(cleanErr, "pre-create checkout cleanup");
|
|
7957
8378
|
}
|
|
@@ -7967,10 +8388,10 @@ ${statusText}`
|
|
|
7967
8388
|
createdAt: Date.now()
|
|
7968
8389
|
});
|
|
7969
8390
|
try {
|
|
7970
|
-
const mainNodeModules =
|
|
7971
|
-
const worktreeNodeModules =
|
|
7972
|
-
if (
|
|
7973
|
-
|
|
8391
|
+
const mainNodeModules = path14.join(mainCwd, "node_modules");
|
|
8392
|
+
const worktreeNodeModules = path14.join(worktreePath, "node_modules");
|
|
8393
|
+
if (fs19.existsSync(mainNodeModules) && !fs19.existsSync(worktreeNodeModules)) {
|
|
8394
|
+
fs19.symlinkSync(mainNodeModules, worktreeNodeModules);
|
|
7974
8395
|
}
|
|
7975
8396
|
return Object.freeze({
|
|
7976
8397
|
path: worktreePath,
|
|
@@ -8058,7 +8479,7 @@ ${statusText}`
|
|
|
8058
8479
|
return Object.freeze({ patchFile, failed: false, written: false });
|
|
8059
8480
|
}
|
|
8060
8481
|
try {
|
|
8061
|
-
|
|
8482
|
+
fs19.writeFileSync(patchFile, diff, "utf-8");
|
|
8062
8483
|
return Object.freeze({ patchFile, failed: false, written: true });
|
|
8063
8484
|
} catch {
|
|
8064
8485
|
return Object.freeze({ patchFile, failed: true, written: false });
|
|
@@ -8121,9 +8542,9 @@ ${statusText}`
|
|
|
8121
8542
|
const branches = branchesByRepo.get(entry.repo);
|
|
8122
8543
|
if (branches === void 0) continue;
|
|
8123
8544
|
const branchGone = !branches.has(entry.branch);
|
|
8124
|
-
const checkoutGone = !
|
|
8545
|
+
const checkoutGone = !fs19.existsSync(entry.checkout);
|
|
8125
8546
|
if (branchGone && checkoutGone) {
|
|
8126
|
-
|
|
8547
|
+
logger25.warn("[worktree] reconcile: registry entry has no physical worktree/branch, removing entry", {
|
|
8127
8548
|
branch: entry.branch,
|
|
8128
8549
|
repo: entry.repo,
|
|
8129
8550
|
pid: entry.pid
|
|
@@ -8167,14 +8588,14 @@ ${statusText}`
|
|
|
8167
8588
|
}
|
|
8168
8589
|
if (alivePids.length === 1 && list.length === 1) {
|
|
8169
8590
|
const pt = list[0];
|
|
8170
|
-
|
|
8591
|
+
logger25.warn("[worktree] reconcile: unregistered physical worktree with one alive pid, re-registering (self-heal)", {
|
|
8171
8592
|
branch: pt.branch,
|
|
8172
8593
|
checkout: pt.checkout,
|
|
8173
8594
|
repo: pt.repo,
|
|
8174
8595
|
pid: alivePids[0]
|
|
8175
8596
|
});
|
|
8176
8597
|
await this.registry.add({
|
|
8177
|
-
repo: pt.repo ??
|
|
8598
|
+
repo: pt.repo ?? path14.dirname(pt.checkout),
|
|
8178
8599
|
branch: pt.branch,
|
|
8179
8600
|
checkout: pt.checkout,
|
|
8180
8601
|
pid: alivePids[0],
|
|
@@ -8190,7 +8611,7 @@ ${statusText}`
|
|
|
8190
8611
|
if (cycles < RECONCILE_SKIP_ESCALATION_CYCLES) continue;
|
|
8191
8612
|
escalated++;
|
|
8192
8613
|
const repoHint = pt.repo ?? "<main-repo>";
|
|
8193
|
-
|
|
8614
|
+
logger25.warn(
|
|
8194
8615
|
`[worktree] reconcile: unregistered physical worktree skipped for ${cycles} consecutive cycles (alive-pid mapping still ambiguous) \u2014 manual cleanup may be needed. Inspect: git -C ${repoHint} worktree list. If no live process owns it: git -C ${repoHint} worktree remove --force ${pt.checkout} && git -C ${repoHint} branch -D ${pt.branch}. Ownerless checkout (repo unknown, delete the directory directly): rm -rf ${pt.checkout}`,
|
|
8195
8616
|
{
|
|
8196
8617
|
branch: pt.branch,
|
|
@@ -8201,7 +8622,7 @@ ${statusText}`
|
|
|
8201
8622
|
);
|
|
8202
8623
|
}
|
|
8203
8624
|
if (escalated < list.length) {
|
|
8204
|
-
|
|
8625
|
+
logger25.warn("[worktree] reconcile: unregistered physical worktrees present but alive-pid mapping ambiguous, skipping this cycle", {
|
|
8205
8626
|
enc,
|
|
8206
8627
|
orphans: list.length - escalated,
|
|
8207
8628
|
alivePids: alivePids.length
|
|
@@ -8214,7 +8635,7 @@ ${statusText}`
|
|
|
8214
8635
|
for (const pt of list) {
|
|
8215
8636
|
const age = Date.now() - pt.mtimeMs;
|
|
8216
8637
|
if (age <= SPAWN_GRACE_MS) continue;
|
|
8217
|
-
|
|
8638
|
+
logger25.warn("[worktree] reconcile: unregistered physical worktree with no alive pid, cleaning up", {
|
|
8218
8639
|
branch: pt.branch,
|
|
8219
8640
|
checkout: pt.checkout,
|
|
8220
8641
|
repo: pt.repo,
|
|
@@ -8230,10 +8651,10 @@ ${statusText}`
|
|
|
8230
8651
|
* 推导失败(残缺 checkout)repo=undefined,由调用方按无主残留处置。
|
|
8231
8652
|
*/
|
|
8232
8653
|
async discoverPhysicalWorktrees() {
|
|
8233
|
-
const root =
|
|
8654
|
+
const root = path14.join(os2.tmpdir(), WORKTREE_TMP_ROOT);
|
|
8234
8655
|
let encDirs;
|
|
8235
8656
|
try {
|
|
8236
|
-
encDirs =
|
|
8657
|
+
encDirs = fs19.readdirSync(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
8237
8658
|
} catch {
|
|
8238
8659
|
return [];
|
|
8239
8660
|
}
|
|
@@ -8241,14 +8662,14 @@ ${statusText}`
|
|
|
8241
8662
|
for (const enc of encDirs) {
|
|
8242
8663
|
let branchDirs;
|
|
8243
8664
|
try {
|
|
8244
|
-
branchDirs =
|
|
8665
|
+
branchDirs = fs19.readdirSync(path14.join(root, enc), { withFileTypes: true }).filter((d) => d.isDirectory() && d.name.startsWith(BRANCH_PREFIX)).map((d) => d.name);
|
|
8245
8666
|
} catch {
|
|
8246
8667
|
continue;
|
|
8247
8668
|
}
|
|
8248
8669
|
for (const branch of branchDirs) {
|
|
8249
|
-
const checkout =
|
|
8670
|
+
const checkout = path14.join(root, enc, branch);
|
|
8250
8671
|
try {
|
|
8251
|
-
const mtimeMs =
|
|
8672
|
+
const mtimeMs = fs19.statSync(checkout).mtimeMs;
|
|
8252
8673
|
result.push({ enc, branch, checkout, repo: resolveRepoFromCheckout(checkout), mtimeMs });
|
|
8253
8674
|
} catch (err) {
|
|
8254
8675
|
bestEffort(err, "physical worktree stat (reconcile)");
|
|
@@ -8286,17 +8707,17 @@ ${statusText}`
|
|
|
8286
8707
|
* 崩溃残留的 .alive(pid 已死)天然过滤掉——这正是「死活判据」的物理面来源。
|
|
8287
8708
|
*/
|
|
8288
8709
|
collectAlivePids(enc) {
|
|
8289
|
-
const sessionsDir =
|
|
8710
|
+
const sessionsDir = path14.join(this.agentDir, "subagents", enc, "sessions");
|
|
8290
8711
|
let files;
|
|
8291
8712
|
try {
|
|
8292
|
-
files =
|
|
8713
|
+
files = fs19.readdirSync(sessionsDir);
|
|
8293
8714
|
} catch {
|
|
8294
8715
|
return [];
|
|
8295
8716
|
}
|
|
8296
8717
|
const pids = /* @__PURE__ */ new Set();
|
|
8297
8718
|
for (const file of files) {
|
|
8298
8719
|
if (!file.endsWith(".alive")) continue;
|
|
8299
|
-
const marker = readAliveMarker(
|
|
8720
|
+
const marker = readAliveMarker(path14.join(sessionsDir, file.slice(0, -".alive".length)));
|
|
8300
8721
|
if (marker && isProcessAlive(marker.pid)) {
|
|
8301
8722
|
pids.add(marker.pid);
|
|
8302
8723
|
}
|
|
@@ -8328,8 +8749,8 @@ ${statusText}`
|
|
|
8328
8749
|
}
|
|
8329
8750
|
}
|
|
8330
8751
|
try {
|
|
8331
|
-
if (
|
|
8332
|
-
|
|
8752
|
+
if (fs19.existsSync(pt.checkout)) {
|
|
8753
|
+
fs19.rmSync(pt.checkout, { recursive: true, force: true });
|
|
8333
8754
|
}
|
|
8334
8755
|
} catch (err) {
|
|
8335
8756
|
bestEffort(err, "checkout dir rm (reconcile)");
|
|
@@ -8342,7 +8763,7 @@ ${statusText}`
|
|
|
8342
8763
|
if (entry.pid === 0) {
|
|
8343
8764
|
const expired = now - entry.createdAt > SPAWN_GRACE_MS;
|
|
8344
8765
|
if (expired) {
|
|
8345
|
-
|
|
8766
|
+
logger25.warn(
|
|
8346
8767
|
"[worktree] orphan reaper: pid=0 entry exceeded SPAWN_GRACE_MS, treating as orphan",
|
|
8347
8768
|
{ branch: entry.branch, checkout: entry.checkout, createdAt: entry.createdAt, now }
|
|
8348
8769
|
);
|
|
@@ -8418,7 +8839,7 @@ ${statusText}`
|
|
|
8418
8839
|
};
|
|
8419
8840
|
|
|
8420
8841
|
// src/execution/subagent-service.ts
|
|
8421
|
-
var
|
|
8842
|
+
var logger26 = getLogger("subagents");
|
|
8422
8843
|
var disposedUiRequestStub = () => Promise.resolve({ cancelled: true });
|
|
8423
8844
|
var PRIORITY_BACKGROUND = 1e3;
|
|
8424
8845
|
var MS_PER_SECOND6 = 1e3;
|
|
@@ -8433,6 +8854,7 @@ var ENV_ROOT_SESSION_ID = "PI_SUBAGENT_ROOT_SESSION_ID";
|
|
|
8433
8854
|
var ENV_SELF_RECORD_ID = "PI_SUBAGENT_SELF_RECORD_ID";
|
|
8434
8855
|
var ENV_DEPTH = "PI_SUBAGENT_DEPTH";
|
|
8435
8856
|
var ENV_ROOT_CWD = "PI_SUBAGENT_ROOT_CWD";
|
|
8857
|
+
var SETTLED_RESCAN_LIMIT = 8;
|
|
8436
8858
|
var SubagentService = class {
|
|
8437
8859
|
pool;
|
|
8438
8860
|
store;
|
|
@@ -8483,12 +8905,29 @@ var SubagentService = class {
|
|
|
8483
8905
|
/** [D4-①] 通知簇 host 面(notifyComplete/notifyClosed/pending 注册注销 + notifier
|
|
8484
8906
|
* 实例封装,原私有通知簇四方法与模块函数的搬移落点——notify-host.ts)。
|
|
8485
8907
|
* deps 惰性求值(pi/session 级状态运行时可变),行为与原 constructor 内
|
|
8486
|
-
* createNotifier(this.piAdapter()) 逐字节等价。session_start revive,shutdown dispose。
|
|
8908
|
+
* createNotifier(this.piAdapter()) 逐字节等价。session_start revive,shutdown dispose。
|
|
8909
|
+
* [sync-collect 合并] toNotifyRecord/notify/notifyBatch 随批路由需要由 host 导出
|
|
8910
|
+
* (collectCoordinator 闭包与 E9/E1 直发路径消费,见 notify-host.ts 接口注释)。 */
|
|
8487
8911
|
notifyHost = createNotifyHost({
|
|
8488
8912
|
getPi: () => this.pi,
|
|
8489
8913
|
listRunning: () => this.store.listRunning(),
|
|
8490
8914
|
getIsIdle: () => this.isIdleFn
|
|
8491
8915
|
});
|
|
8916
|
+
/** collectCoordinator(subagent-sync-collect U2):sync 批缓冲 + 闭合检测 + flush 分流。 */
|
|
8917
|
+
collectCoordinator;
|
|
8918
|
+
/** [E9] dispose 时已转 async 写账的成员 id(revive 后 flushBatch 防御过滤用)。
|
|
8919
|
+
* 背景:dispose 后同进程 revive(/resume /fork /new)时协调器内部缓冲仍持有已转换
|
|
8920
|
+
* 成员快照(协调器无 drain API,U5 领地不含 collect-coordinator.ts)——若后续新
|
|
8921
|
+
* sync 成员触发闭合,陈旧快照会随批重投(新成员集新 hash,账本跨键不拦)→ 双重
|
|
8922
|
+
* 通知。flushBatch 闭包按本集过滤,陈旧成员零重投。id 唯一 per spawn,无误伤面。 */
|
|
8923
|
+
e9ConvertedIds = /* @__PURE__ */ new Set();
|
|
8924
|
+
/** [v2 D4] E1 等待分支的 settled 有界重扫状态(null = 未注册)。disposed 后保持
|
|
8925
|
+
* 非 null——同 session 内不再重复注册(补发完成/达限后 settled 边沿已无事可做,
|
|
8926
|
+
* 单注册即单重扫);initSession(revive)置 null 允许新 session 重新注册。
|
|
8927
|
+
* pi.on 无 off(见 armSettledRescan):旧 handler 闭包捕获旧 state,未 disposed 时
|
|
8928
|
+
* 遇 settled 边沿仍会执行,扫描 this.mainSessionFile 当前值(handler 不绑定注册时
|
|
8929
|
+
* 的文件域);dispose 的惰化处置见 dispose()。 */
|
|
8930
|
+
settledRescanState = null;
|
|
8492
8931
|
/** [MF#4][MF#2] fork 深度按 async 调用链传递(AsyncLocalStorage),替代共享可变计数器。
|
|
8493
8932
|
* 主 session=0;fork 进入子 session 期间推进为子深度,供嵌套 fork 经 ALS 读到自身深度作为
|
|
8494
8933
|
* parentForkDepth。并发 background fork 各自独立调用链,不再互相压低深度值。
|
|
@@ -8517,7 +8956,7 @@ var SubagentService = class {
|
|
|
8517
8956
|
/** [D4-②] 轮次结算回调(原 buildSessionRunnerContext 内的 onRoundSettled 业务闭包
|
|
8518
8957
|
* 搬移至 round-settlement.ts;deps 回调闭包惰性求值,session-runner agent_settled 时消费)。 */
|
|
8519
8958
|
settleRound = createRoundSettler({
|
|
8520
|
-
notifyComplete: (record) => this.
|
|
8959
|
+
notifyComplete: (record) => this.collectCoordinator.route(record),
|
|
8521
8960
|
reportRecordTransition: (record) => this.store.reportRecordTransition(record),
|
|
8522
8961
|
closeAfterRoundSettled: (record) => this.closeAfterRoundSettled(record)
|
|
8523
8962
|
});
|
|
@@ -8548,6 +8987,11 @@ var SubagentService = class {
|
|
|
8548
8987
|
deliverChatMessage: (record, text, interrupt) => this.deliverChatMessage(record, text, interrupt)
|
|
8549
8988
|
};
|
|
8550
8989
|
manifestStore;
|
|
8990
|
+
/**
|
|
8991
|
+
* [D6 #7a] records 目录(与 manifestStore 同源同一推导)——屏障失败 warn 带 manifest
|
|
8992
|
+
* 文件路径用(ManifestStore.dir 私有,此处不破封装另存同源值;漂移由构造点同语句保证不发生)。
|
|
8993
|
+
*/
|
|
8994
|
+
recordsDir;
|
|
8551
8995
|
/**
|
|
8552
8996
|
* [T1/PS-9] subagent sessionDir(getSubagentSessionDir 推导,与 store 同源同一 rootCwd)。
|
|
8553
8997
|
* 传给 doFinalizeRecord 的 FinalizeDeps.sessionDir——record.sessionFile 缺失时 finalize
|
|
@@ -8566,8 +9010,42 @@ var SubagentService = class {
|
|
|
8566
9010
|
const sessionsDir = getSubagentSessionDir(this.modelService.getAgentDir(), this.rootCwd);
|
|
8567
9011
|
const recordsDir = getSubagentRecordsDir(this.modelService.getAgentDir(), this.rootCwd);
|
|
8568
9012
|
this.sessionsDir = sessionsDir;
|
|
9013
|
+
this.recordsDir = recordsDir;
|
|
8569
9014
|
this.manifestStore = new ManifestStore(recordsDir);
|
|
8570
9015
|
this.store = new RecordStore(sessionsDir, this.manifestStore, this.pi ?? void 0);
|
|
9016
|
+
this.collectCoordinator = new CollectCoordinator({
|
|
9017
|
+
notifyAsync: (record) => {
|
|
9018
|
+
const notify = this.notifyHost.toNotifyRecord(record);
|
|
9019
|
+
if (notify) this.notifyHost.notify(notify);
|
|
9020
|
+
},
|
|
9021
|
+
toNotifyRecord: (record) => this.notifyHost.toNotifyRecord(record),
|
|
9022
|
+
// 闭合判定数据源:listAllActive(原始 ExecutionRecord 内存态,携带 collectMode/
|
|
9023
|
+
// batchFinalized 原始值)。[U3 修正] 原接 collectRecords——其经 recordToSubagent
|
|
9024
|
+
// 投影丢 collectMode(U2 披露的投影缺口)→ 真链上闭合判定恒立即闭合、跨轮续累
|
|
9025
|
+
// 失效(真链 trace 实证)。非终态 sync 成员必在内存(archive 只删终态;磁盘重建
|
|
9026
|
+
// 残留属孤儿恢复域),内存视图语义完整。
|
|
9027
|
+
listRecords: (limit) => this.store.listAllActive().slice(0, limit),
|
|
9028
|
+
flushBatch: async (members) => {
|
|
9029
|
+
const live = members.filter((m) => !this.e9ConvertedIds.has(m.id));
|
|
9030
|
+
if (live.length === 0) return;
|
|
9031
|
+
const fulls = [];
|
|
9032
|
+
const fullMissed = [];
|
|
9033
|
+
for (const m of live) {
|
|
9034
|
+
const full = this.store.getFullRecord(m.id);
|
|
9035
|
+
if (full) fulls.push(full);
|
|
9036
|
+
else fullMissed.push(m);
|
|
9037
|
+
}
|
|
9038
|
+
await this.writeSyncBatchManifestBarrier(fulls);
|
|
9039
|
+
const accepted = this.notifyHost.notifyBatch(live, this.getCollectSyncBudget());
|
|
9040
|
+
if (!accepted) return;
|
|
9041
|
+
for (const full of fulls) {
|
|
9042
|
+
this.appendBatchFinalizedEntry(full);
|
|
9043
|
+
}
|
|
9044
|
+
for (const m of fullMissed) {
|
|
9045
|
+
this.appendBatchFinalizedEntry(bufferedMemberFallbackRecord(m, this.sessionRootId ?? void 0));
|
|
9046
|
+
}
|
|
9047
|
+
}
|
|
9048
|
+
});
|
|
8571
9049
|
registerGlobalObservability(this.uiObservability);
|
|
8572
9050
|
}
|
|
8573
9051
|
// ── 生命周期(index.ts 调)──────────────────────────────
|
|
@@ -8592,6 +9070,7 @@ var SubagentService = class {
|
|
|
8592
9070
|
this.sessionRootId = envRoot ?? init.sessionId;
|
|
8593
9071
|
this.initExecContextBaseline(envRoot, init.sessionId);
|
|
8594
9072
|
this._disposed = false;
|
|
9073
|
+
this.settledRescanState = null;
|
|
8595
9074
|
this.store.revive();
|
|
8596
9075
|
this.notifyHost.revive();
|
|
8597
9076
|
this.recoverOrphansIfRootProcess();
|
|
@@ -8625,7 +9104,7 @@ var SubagentService = class {
|
|
|
8625
9104
|
this.execNesting.setBaseline({ recordId: envSelfRecord, depth: nestingDepth });
|
|
8626
9105
|
this.execNesting.enterWith({ recordId: envSelfRecord, depth: nestingDepth });
|
|
8627
9106
|
if (process.env.XYZ_AGENT_DEBUG) {
|
|
8628
|
-
|
|
9107
|
+
logger26.debug(
|
|
8629
9108
|
`[subagents] execNesting initialized: recordId=${envSelfRecord} depth=${nestingDepth} rootSessionId=${envRoot ?? sessionId}`
|
|
8630
9109
|
);
|
|
8631
9110
|
}
|
|
@@ -8649,26 +9128,28 @@ var SubagentService = class {
|
|
|
8649
9128
|
if (!isChildProcess) {
|
|
8650
9129
|
this.recoverOrphanRecords();
|
|
8651
9130
|
} else if (process.env.XYZ_AGENT_DEBUG) {
|
|
8652
|
-
|
|
9131
|
+
logger26.debug("[subagents] child process detected (PI_SUBAGENT_SELF_RECORD_ID set), skipping orphan recovery scan");
|
|
8653
9132
|
}
|
|
8654
9133
|
}
|
|
8655
9134
|
/** 孤儿终态恢复委托(RecordStore.recoverOrphanRecords 的唯一调用入口,维持 store
|
|
8656
9135
|
* private 封装——与 recoverManifestTmpFiles 同模式;[D4] public 面收窄:唯一调用方
|
|
8657
9136
|
* 是 initSession,转 private)。判定语义见 store 侧注释。
|
|
9137
|
+
* mainSessionFile 随调用透传(v2 D3 覆写 merge 数据源:主文件末条 entry 的批域
|
|
9138
|
+
* 标记与轮终 result/model;initSession 先赋值后恢复,时序就绪)。
|
|
8658
9139
|
* 随后跑 entry-born 孤儿恢复(无子文件锚的 register-only record,spawn 窗口期死亡,
|
|
8659
9140
|
* E2E 实测缺口)——主 session 文件经 getMainSessionFile 注入(构造期可空)。 */
|
|
8660
9141
|
recoverOrphanRecords() {
|
|
8661
9142
|
try {
|
|
8662
|
-
this.store.recoverOrphanRecords(this.sessionRootId ?? void 0);
|
|
9143
|
+
this.store.recoverOrphanRecords(this.sessionRootId ?? void 0, this.mainSessionFile);
|
|
8663
9144
|
} catch (err) {
|
|
8664
|
-
|
|
9145
|
+
logger26.warn("[subagents] orphan recovery failed", {
|
|
8665
9146
|
reason: toErrorMessage(err)
|
|
8666
9147
|
});
|
|
8667
9148
|
}
|
|
8668
9149
|
try {
|
|
8669
9150
|
this.store.recoverEntryOnlyOrphans(this.mainSessionFile, this.sessionRootId ?? void 0);
|
|
8670
9151
|
} catch (err) {
|
|
8671
|
-
|
|
9152
|
+
logger26.warn("[subagents] entry-only orphan recovery failed", {
|
|
8672
9153
|
reason: toErrorMessage(err)
|
|
8673
9154
|
});
|
|
8674
9155
|
}
|
|
@@ -8766,14 +9247,232 @@ var SubagentService = class {
|
|
|
8766
9247
|
* listener 仍然存活。若 pending-notifications 先于本扩展执行 session_shutdown(后注册
|
|
8767
9248
|
* 先执行的语义下会如此),listener 已注销,unregister 事件被静默丢弃。这是可接受的
|
|
8768
9249
|
* 退化——进程退出后两侧状态本就不保证一致,下次 session_start 的 crash recovery 会修正。 */
|
|
9250
|
+
/** [U4 deviation #8 接线] collectSync 预算热读(flush 时读值,与 getCollectSyncDefault
|
|
9251
|
+
* 同款访问链 modelService.getGlobalConfig().collectSync)。节缺失/读失败 → undefined
|
|
9252
|
+
* → notifyBatch 落 buildBatchLlmContent 设计默认值(4000/24000,E5 不炸启动)。
|
|
9253
|
+
* sanitizeCollectSync 保证节存在时两字段必有合法正整数。 */
|
|
9254
|
+
getCollectSyncBudget() {
|
|
9255
|
+
const cs = this.collectSyncSection();
|
|
9256
|
+
return cs !== void 0 ? { perItemChars: cs.perItemChars, totalChars: cs.totalChars } : void 0;
|
|
9257
|
+
}
|
|
9258
|
+
/** [E9 专用] batchFinalized 落标 + manifest fire-and-forget 写(设计 §3.1.5 E9)。
|
|
9259
|
+
* 批通知路径(flush/E1)已改走「manifest 屏障 → 写账 → 纯落标」序列(「通知可达
|
|
9260
|
+
* ⇒ 索引就位」的构造性保证,见 flushBatch 闭包 / runSyncCollectRecoveryScan),
|
|
9261
|
+
* 不再经本 helper;仅 E9 转换的成员保持原形态——其走 async 单条通知(全文注入、
|
|
9262
|
+
* 无指针行消费),manifest 无时序要求,落标后 fire-and-forget 补写(list 后手动
|
|
9263
|
+
* 反查的顺带索引)。
|
|
9264
|
+
* 路径 = getFullRecord 冷路径重建 → appendBatchFinalizedEntry 纯落标 → fire
|
|
9265
|
+
* manifest。getFullRecord 不可达(子 session 文件缺失/已 GC)→ 跳过该成员
|
|
9266
|
+
* (详见 flushBatch 闭包注释)。 */
|
|
9267
|
+
markMembersBatchFinalized(memberIds) {
|
|
9268
|
+
for (const id of memberIds) {
|
|
9269
|
+
const full = this.store.getFullRecord(id);
|
|
9270
|
+
if (!full) continue;
|
|
9271
|
+
this.appendBatchFinalizedEntry(full);
|
|
9272
|
+
void this.writeBatchMemberManifest(full).catch((err) => {
|
|
9273
|
+
logger26.debug(
|
|
9274
|
+
`[subagents] batch-finalized manifest write failed (record=${full.id})`,
|
|
9275
|
+
{ reason: err instanceof Error ? err.message : String(err) }
|
|
9276
|
+
);
|
|
9277
|
+
});
|
|
9278
|
+
}
|
|
9279
|
+
}
|
|
9280
|
+
/** batchFinalized 落标唯一出口(appendEntry 公共末步,纯落标):显式覆写
|
|
9281
|
+
* collectMode/batchFinalized → reportSubagentRecord。覆写动机:recordToSubagent
|
|
9282
|
+
* 投影已含两字段(U5 修复),但 getFullRecord 冷路径含 sidecar/manifest 重建分支
|
|
9283
|
+
* (非 entry 源),显式赋值防非 entry 源重建时丢标记。
|
|
9284
|
+
*
|
|
9285
|
+
* [v2 D1 断链 1] 落标即「离开批 = 通知已/即将送达 = 指针行即将被消费」——成功
|
|
9286
|
+
* 成员走 SP-5 改道 doFinalizeRoundToIdle(不写 manifest),批路径不补写则
|
|
9287
|
+
* records/<sa-id>.json 永不产生、session-reader 反查 0 命中。manifest 写点已从
|
|
9288
|
+
* 本出口的 fire-and-forget 前移至各调用方:批通知路径(flush/E1)在写账前屏障
|
|
9289
|
+
* await 全部落盘(「通知可达 ⇒ 索引就位」的构造性保证);E9 保持落标后
|
|
9290
|
+
* fire-and-forget(async 单条通知无指针行消费,无时序要求)。
|
|
9291
|
+
* rec 两来源(flush 的 getFullRecord 内存全量 / E1 的 rebuildEntryRecord 重建
|
|
9292
|
+
* 快照)必需字段恒齐备(id/agentName←agent/rootSessionId/createdAt←startedAt),
|
|
9293
|
+
* task/slug/parentRecordId 等可选 undefined 自然缺省。 */
|
|
9294
|
+
appendBatchFinalizedEntry(rec) {
|
|
9295
|
+
this.store.reportSubagentRecord({ ...rec, collectMode: "sync", batchFinalized: true });
|
|
9296
|
+
}
|
|
9297
|
+
/** 批成员 manifest(sa- id → sessionFile 反查索引)写的唯一投影点(D2 字段投影 +
|
|
9298
|
+
* status 如实投影:成功成员此刻 record 实态 running+resumable → "running",后续
|
|
9299
|
+
* message upgrade 走完整 finalize 时 Step 4 原子覆盖为 "closed")。
|
|
9300
|
+
* 返回原始 promise 不吞错——失败语义由调用方定:批通知路径经
|
|
9301
|
+
* writeSyncBatchManifestBarrier 的 allSettled(debug 不阻断写账);E9 经
|
|
9302
|
+
* fire-and-forget catch(debug 不阻断落标)。 */
|
|
9303
|
+
writeBatchMemberManifest(rec) {
|
|
9304
|
+
return this.manifestStore.writeManifest({
|
|
9305
|
+
id: rec.id,
|
|
9306
|
+
rootSessionId: rec.rootSessionId ?? "",
|
|
9307
|
+
parentRecordId: rec.parentRecordId,
|
|
9308
|
+
agentName: rec.agent,
|
|
9309
|
+
status: rec.status,
|
|
9310
|
+
createdAt: rec.startedAt,
|
|
9311
|
+
completedAt: rec.endedAt,
|
|
9312
|
+
sessionFile: rec.sessionFile,
|
|
9313
|
+
task: rec.task,
|
|
9314
|
+
slug: rec.slug,
|
|
9315
|
+
model: rec.model
|
|
9316
|
+
});
|
|
9317
|
+
}
|
|
9318
|
+
/** [时序屏障] 批通知路径(flush/E1)专用:成员 manifest 并行写 + await 全部完成
|
|
9319
|
+
* (allSettled)后才允许写账投递——「通知可达 ⇒ 索引就位」的构造性保证。写失败
|
|
9320
|
+
* 不阻断(best-effort 语义与 doFinalizeRecord Step 4 一致:反查索引缺失只影响指针行
|
|
9321
|
+
* 反查,session-reader 错误文案已指引绝对路径兜底,不构成写账失败);warn 留痕
|
|
9322
|
+
* (D6 #7a / SC-1:屏障失败意味着该成员指针行反查索引缺失,debug 级在排障时不可见)。 */
|
|
9323
|
+
async writeSyncBatchManifestBarrier(recs) {
|
|
9324
|
+
const results = await Promise.allSettled(recs.map((rec) => this.writeBatchMemberManifest(rec)));
|
|
9325
|
+
for (let i = 0; i < results.length; i++) {
|
|
9326
|
+
const result = results[i];
|
|
9327
|
+
if (result.status === "rejected") {
|
|
9328
|
+
logger26.warn(
|
|
9329
|
+
`[subagents] batch-finalized manifest write failed (record=${recs[i].id}, manifest=${this.recordsDir}/${recs[i].id}.json)`,
|
|
9330
|
+
{ reason: result.reason instanceof Error ? result.reason.message : String(result.reason) }
|
|
9331
|
+
);
|
|
9332
|
+
}
|
|
9333
|
+
}
|
|
9334
|
+
}
|
|
9335
|
+
/** [E9] dispose 时批未闭合:缓冲中已终态未通知成员逐条转 async 语义写账(放弃攒批)
|
|
9336
|
+
* + 落 batchFinalized 标记(E1 重建扫描据此排除,防双重通知),交由既有 shutdown
|
|
9337
|
+
* flush / resume 重放兑底;仍在跑的成员走现有退出路径(disposeAllRecords 关闭,
|
|
9338
|
+
* 与 async 一致)。写账用 notifier.notify 现有通路(ledger.record + attemptDeliver)。
|
|
9339
|
+
* 源序:写账先于落标——写账后崩溃 → E1 重建收该成员,但 async notifyId 与批 hash
|
|
9340
|
+
* 跨键不拦的重发属设计披露的 E9 残余窗(at-least-once 良性,PS-17 同族,v1 接受)。 */
|
|
9341
|
+
convertPendingSyncBufferToAsync() {
|
|
9342
|
+
this.collectCoordinator.cancelScheduledFlush();
|
|
9343
|
+
const members = this.collectCoordinator.pendingMembers();
|
|
9344
|
+
if (members.length === 0) return;
|
|
9345
|
+
for (const member of members) {
|
|
9346
|
+
this.e9ConvertedIds.add(member.id);
|
|
9347
|
+
this.notifyHost.notify(member);
|
|
9348
|
+
}
|
|
9349
|
+
this.markMembersBatchFinalized(members.map((m) => m.id));
|
|
9350
|
+
logger26.warn(
|
|
9351
|
+
`[subagents] E9 dispose: converted ${members.length} buffered sync member(s) to async notify`,
|
|
9352
|
+
{ ids: members.map((m) => m.id) }
|
|
9353
|
+
);
|
|
9354
|
+
}
|
|
9355
|
+
/**
|
|
9356
|
+
* [E1] sync 批崩溃恢复钩子(设计 §3.1.5 E1,index.ts session_start 恢复编排处调用,
|
|
9357
|
+
* 须晚于 initSession——孤儿终态恢复先行收敛 running 成员,「全员终态」判定才可达):
|
|
9358
|
+
*
|
|
9359
|
+
* - 扫描主 session 文件每 id 末条 subagent-record entry(store.scanLastRecordEntries,
|
|
9360
|
+
* collectLastRecordEntries 同构 + 投影扩展含 collectMode/batchFinalized + 终态五
|
|
9361
|
+
* 字段;禁走 collectRecords light 路径——主 session 落标 entry 对它不可见);
|
|
9362
|
+
* - 只收 collectMode=sync 且无 batchFinalized 的成员(排除已通过批 flush 或 E9
|
|
9363
|
+
* 转换离场的,防双重通知),按 rootSessionId 过滤当前根;
|
|
9364
|
+
* - 全员终态且账本无同成员集批记录 → manifest 屏障(await 落盘,「通知可达 ⇒
|
|
9365
|
+
* 索引就位」构造性保证,与 flushBatch 同款)→ notifyBatch 补发(内容 = 末条
|
|
9366
|
+
* entry 终态快照;账本 record 同 hash 幂等拒绝 = 已投递/已在账,两种结局都算
|
|
9367
|
+
* 「已处理」);
|
|
9368
|
+
* - 仍有 running → 本次不动,注册 settled 有界重扫(D4,见 armSettledRescan)——
|
|
9369
|
+
* 成员延迟终态(主 agent 冷路径 resume → 正常流落 entry)由 settled 边沿驱动
|
|
9370
|
+
* 重扫收敛,不再依赖「下次 session_start」作唯一再驱动(v2 §2.4 断链 4);
|
|
9371
|
+
* running 口径与协调器同构
|
|
9372
|
+
* (resumable 豁免,v2 D3——覆写不可达的防御分支残余不被误判「仍在跑」);
|
|
9373
|
+
* - 补发尝试后统一补 batchFinalized 标记(账本拒绝也算已投递;直接用末条重建快照
|
|
9374
|
+
* 落标不经 getFullRecord——子文件缺失/已 GC 时标记仍可落盘,窗口自愈不依赖二次
|
|
9375
|
+
* 重启;补标自身崩溃重入幂等收敛,末条 entry last-writer-wins)。
|
|
9376
|
+
*
|
|
9377
|
+
* async 化(时序屏障修复):返回 Promise 但**内部自捕获不外抛**——宿主 index.ts
|
|
9378
|
+
* session_start 以同步 try/catch 调用(其 catch 兑现不到 promise 内的异常),
|
|
9379
|
+
* 自捕获维持同款 warn 容错语义,浮动调用零适配、不产生 unhandled rejection。
|
|
9380
|
+
*
|
|
9381
|
+
* [U8 拆批修复] 与协调器合批排程无交集:E1 只在 session_start 编排处运行(此前
|
|
9382
|
+
* dispose 已取消挂起排程),补发直走 notifier.notifyBatch 不经协调器;异常时序
|
|
9383
|
+
* 相撞由账本 sync-batch:<hash> 幂等拒绝兜底。
|
|
9384
|
+
*/
|
|
9385
|
+
async recoverSyncCollectBatch() {
|
|
9386
|
+
try {
|
|
9387
|
+
const { outcome } = await this.runSyncCollectRecoveryScan();
|
|
9388
|
+
if (outcome === "waiting") {
|
|
9389
|
+
this.armSettledRescan();
|
|
9390
|
+
}
|
|
9391
|
+
} catch (err) {
|
|
9392
|
+
logger26.warn("[subagents] sync collect batch recovery failed", {
|
|
9393
|
+
reason: err instanceof Error ? err.message : String(err)
|
|
9394
|
+
});
|
|
9395
|
+
}
|
|
9396
|
+
}
|
|
9397
|
+
/** [E1/D4] 单次「扫描→判定→可达则补发+落标」,E1 首扫与 settled 重扫共用同一实现
|
|
9398
|
+
* (防两处复制粘贴分岔)。三态返回:idle(无 sync 候选——已全部落标/E9 转换/异根,
|
|
9399
|
+
* 无事可等)/ waiting(仍有 running 成员,本次不动)/ dispatched(全员终态,已补发
|
|
9400
|
+
* +统一落标)。async:补发前有 manifest 屏障 await(见函数头 E1 注释)。 */
|
|
9401
|
+
/** 返回 outcome + waitingIds(达限 warn 需滞留成员 id,D6 #7b——仅 waiting 态非空)。 */
|
|
9402
|
+
async runSyncCollectRecoveryScan() {
|
|
9403
|
+
const lastRecords = this.store.scanLastRecordEntries(this.mainSessionFile);
|
|
9404
|
+
if (lastRecords.length === 0) return { outcome: "idle", waitingIds: [] };
|
|
9405
|
+
const rootFilter = this.sessionRootId;
|
|
9406
|
+
const candidates = lastRecords.filter(
|
|
9407
|
+
(r) => r.collectMode === "sync" && r.batchFinalized !== true && (rootFilter === void 0 || r.rootSessionId === rootFilter)
|
|
9408
|
+
);
|
|
9409
|
+
if (candidates.length === 0) return { outcome: "idle", waitingIds: [] };
|
|
9410
|
+
const running = candidates.filter((r) => r.resumable !== true && r.status !== "closed");
|
|
9411
|
+
if (running.length > 0) {
|
|
9412
|
+
logger26.debug(
|
|
9413
|
+
`[subagents] E1 sync batch recovery: ${running.length} member(s) still running, wait for natural completion`,
|
|
9414
|
+
{ ids: running.map((r) => r.id) }
|
|
9415
|
+
);
|
|
9416
|
+
return { outcome: "waiting", waitingIds: running.map((r) => r.id) };
|
|
9417
|
+
}
|
|
9418
|
+
await this.writeSyncBatchManifestBarrier(candidates);
|
|
9419
|
+
const members = candidates.map((r) => syncRebuildToNotifyMember(r));
|
|
9420
|
+
const accepted = this.notifyHost.notifyBatch(members, this.getCollectSyncBudget());
|
|
9421
|
+
for (const rec of candidates) {
|
|
9422
|
+
this.appendBatchFinalizedEntry(rec);
|
|
9423
|
+
}
|
|
9424
|
+
logger26.warn(
|
|
9425
|
+
`[subagents] E1 sync batch recovery: re-notified ${members.length} member(s) (ledger accepted=${accepted})`,
|
|
9426
|
+
{ ids: members.map((m) => m.id) }
|
|
9427
|
+
);
|
|
9428
|
+
return { outcome: "dispatched", waitingIds: [] };
|
|
9429
|
+
}
|
|
9430
|
+
/** [v2 D4] 注册 agent_settled 有界重扫(幂等:settledRescanState 非 null 不叠加注册
|
|
9431
|
+
* ——E1 现仅 session_start 单调用点,守卫是防第二入口引入时的注册叠加断言面)。
|
|
9432
|
+
* 每次 settled 边沿重跑同一 E1 扫描(runSyncCollectRecoveryScan):dispatched
|
|
9433
|
+
* (补发+落标完成,scan 的 warn 已留痕)或 idle(候选已被其他通路落标)→ disposed;
|
|
9434
|
+
* 累计 SETTLED_RESCAN_LIMIT 次仍在等 → disposed + warn 留痕(D6 #7b / SC-2:达限
|
|
9435
|
+
* 放弃重扫意味着滞留成员的批通知要等下次 session_start 才收敛,含滞留 id 的 warn
|
|
9436
|
+
* 是唯一线索,debug 级排障不可见;后续事件零处理,下次 session_start 再收敛)。
|
|
9437
|
+
* pi.on 无 off(0.84.4 实装)——disposed 标志包装兑现退订(scheduler extension
|
|
9438
|
+
* index.ts subscribeSettled 同款先例)。P-settled 定谳(0.84.4 dist 实装证据):
|
|
9439
|
+
* pi.on 为 per-extension 列表分发——loader.js `on()` 把 handler push 进
|
|
9440
|
+
* extension.handlers.get(event) 数组(非覆盖),runner.js `emit()` 对全部
|
|
9441
|
+
* extension 的全部 handler 逐一 await;故本注册与 ledger host 经
|
|
9442
|
+
* piAdapter.onAgentSettled 注册的 settled 分发互不干扰,无需降级并入 host 链。 */
|
|
9443
|
+
armSettledRescan() {
|
|
9444
|
+
if (this.settledRescanState !== null) return;
|
|
9445
|
+
const state = { disposed: false, scans: 0 };
|
|
9446
|
+
this.settledRescanState = state;
|
|
9447
|
+
this.pi?.on?.("agent_settled", async () => {
|
|
9448
|
+
if (state.disposed) return;
|
|
9449
|
+
state.scans += 1;
|
|
9450
|
+
const { outcome, waitingIds } = await this.runSyncCollectRecoveryScan();
|
|
9451
|
+
if (outcome === "waiting" && state.scans < SETTLED_RESCAN_LIMIT) return;
|
|
9452
|
+
state.disposed = true;
|
|
9453
|
+
if (outcome === "waiting") {
|
|
9454
|
+
logger26.warn(
|
|
9455
|
+
`[subagents] E1 settled rescan: reached limit (${SETTLED_RESCAN_LIMIT}) with member(s) still running, disposed until next session_start`,
|
|
9456
|
+
{ ids: waitingIds }
|
|
9457
|
+
);
|
|
9458
|
+
}
|
|
9459
|
+
});
|
|
9460
|
+
}
|
|
9461
|
+
// [E1 语义对齐 toNotifyRecord] 补发成员映射 syncRebuildToNotifyMember 拆至
|
|
9462
|
+
// sync-rebuild.ts(变化轴:恢复批通知语义):one-shot 成功成员末条恒
|
|
9463
|
+
// running+resumable(SP-5),直通 status 会让恢复批批头「0 finished」且丢
|
|
9464
|
+
// patchFile 的 git-apply 指针——对齐后补发记录为 closed + outcome 物化 +
|
|
9465
|
+
// patchFile 透传。调用点:runSyncCollectRecoveryScan。
|
|
8769
9466
|
dispose() {
|
|
8770
9467
|
if (this._disposed) return;
|
|
8771
9468
|
this._disposed = true;
|
|
8772
9469
|
this.stopGcTimer();
|
|
9470
|
+
if (this.settledRescanState !== null) this.settledRescanState.disposed = true;
|
|
8773
9471
|
this.uiRequestHandler = disposedUiRequestStub;
|
|
8774
9472
|
this.uiObservability.resetMissingHandlerWarnings();
|
|
8775
9473
|
this.store.abortRunningControllers();
|
|
8776
9474
|
killAllSpawnedChildren();
|
|
9475
|
+
this.convertPendingSyncBufferToAsync();
|
|
8777
9476
|
this.disposeAllRecords("parent-shutdown");
|
|
8778
9477
|
resetAllEpipeFailures();
|
|
8779
9478
|
this.resumesInFlight.clear();
|
|
@@ -8806,7 +9505,7 @@ var SubagentService = class {
|
|
|
8806
9505
|
record: item.record
|
|
8807
9506
|
});
|
|
8808
9507
|
}
|
|
8809
|
-
|
|
9508
|
+
logger26.warn(
|
|
8810
9509
|
`[subagents] shutdown flush blocked by busy main agent: ${pending.length} pending notification(s) persisted to ledger for replay on next session_start`,
|
|
8811
9510
|
{ count: pending.length }
|
|
8812
9511
|
);
|
|
@@ -9036,7 +9735,7 @@ var SubagentService = class {
|
|
|
9036
9735
|
*/
|
|
9037
9736
|
onHotPathSettledWatchdogTimeout(record, fire) {
|
|
9038
9737
|
const windowDesc = fire.phase === "mid-round" ? `no valid protocol event for ${fire.waitedMs / MS_PER_SECOND6 / SECONDS_PER_MINUTE4} min after prompt (mid-round no-progress)` : `no agent_settled within ${fire.waitedMs / MS_PER_SECOND6}s after agent_end (settled phase)`;
|
|
9039
|
-
|
|
9738
|
+
logger26.warn(
|
|
9040
9739
|
`[subagents] settled watchdog (${fire.phase}) fired for ${record.id}: ${windowDesc}, terminating (LC-1 wedge recovery)`
|
|
9041
9740
|
);
|
|
9042
9741
|
killRecordChildWithEscalation(record.id, "settled watchdog (hot path)");
|
|
@@ -9053,7 +9752,7 @@ var SubagentService = class {
|
|
|
9053
9752
|
return;
|
|
9054
9753
|
}
|
|
9055
9754
|
const finalize = record.chatMode ? this.finalizeRoundToIdle(record, failedResult) : this.finalizeRecord(record, failedResult, "closed", "gc");
|
|
9056
|
-
void finalize.then(() => this.
|
|
9755
|
+
void finalize.then(() => this.collectCoordinator.route(record)).catch((err) => bestEffort(err, "settled watchdog hot-path finalize", "error"));
|
|
9057
9756
|
}
|
|
9058
9757
|
// [T2⑧ / PS-3] 非 EPIPE 热路径写失败后的 idle timer 再武装已随 D2 投递下沉迁移落位:
|
|
9059
9758
|
// 挂载点在 PiEngine.deliverPrompt 的非 EPIPE catch(engine/engines/pi/pi-engine.ts 的
|
|
@@ -9284,6 +9983,21 @@ var SubagentService = class {
|
|
|
9284
9983
|
collectRecords(limit, statusFilter = "all") {
|
|
9285
9984
|
return this.store.collectRecords(limit, statusFilter, this.sessionRootId ?? this.sessionId ?? void 0);
|
|
9286
9985
|
}
|
|
9986
|
+
/**
|
|
9987
|
+
* collectSync.default 当前生效值(subagent-sync-collect U2,偏差#3 接线:
|
|
9988
|
+
* startHandler 缺省 collect 解析用)。
|
|
9989
|
+
* config 未配/读失败 → DEFAULT_COLLECT_SYNC.default 兜底(E5 不炸启动)。
|
|
9990
|
+
* 新 session 生效语义与 engine 配置一致(globalConfig 由 ModelConfigService
|
|
9991
|
+
* reloadGlobalConfig 刷新)。
|
|
9992
|
+
*/
|
|
9993
|
+
/** collectSync 节单读取点(S9,code-simplify):「读节」一处,「投影成 default 或
|
|
9994
|
+
* budget」各自 accessor 负责(getCollectSyncDefault / getCollectSyncBudget)。 */
|
|
9995
|
+
collectSyncSection() {
|
|
9996
|
+
return this.modelService.getGlobalConfig().collectSync;
|
|
9997
|
+
}
|
|
9998
|
+
getCollectSyncDefault() {
|
|
9999
|
+
return this.collectSyncSection()?.default ?? DEFAULT_COLLECT_SYNC.default;
|
|
10000
|
+
}
|
|
9287
10001
|
/** [perf] 单 record 详情懒加载(全量:eventLog/displayItems/result/turns/tokens)。
|
|
9288
10002
|
* 内存 running record 直接投影;磁盘 record 全量重建(per-file 缓存,stat 戳校验)。
|
|
9289
10003
|
* 返回 undefined:id 不存在于内存与磁盘。 */
|
|
@@ -9376,6 +10090,10 @@ var SubagentService = class {
|
|
|
9376
10090
|
// 从 RunContext 回填;缺省 = pi 投影,存量调用方零感知)
|
|
9377
10091
|
engine: opts.engine,
|
|
9378
10092
|
engineFallback: opts.engineFallback,
|
|
10093
|
+
// subagent-sync-collect U2(偏差#4 接线):sync record 落 collectMode——
|
|
10094
|
+
// 协调器路由判据 + startHandler pendingSyncCount 枚举含本条的数据源。
|
|
10095
|
+
// undefined = async(缺省语义,旧记录零迁移)。
|
|
10096
|
+
collectMode: opts.collect === "sync" ? "sync" : void 0,
|
|
9379
10097
|
controller
|
|
9380
10098
|
});
|
|
9381
10099
|
this.store.register(record);
|
|
@@ -9463,7 +10181,7 @@ var SubagentService = class {
|
|
|
9463
10181
|
try {
|
|
9464
10182
|
await this.runEngineTask(record, opts, engine, signal);
|
|
9465
10183
|
if (record.closedReason !== "cancelled") {
|
|
9466
|
-
this.
|
|
10184
|
+
this.collectCoordinator.route(record);
|
|
9467
10185
|
}
|
|
9468
10186
|
} finally {
|
|
9469
10187
|
this.pool.release();
|
|
@@ -9740,12 +10458,12 @@ var SubagentService = class {
|
|
|
9740
10458
|
{ taskId: record.id, poolKey: PI_POOL_KEY, signal, stream }
|
|
9741
10459
|
).then(() => {
|
|
9742
10460
|
if (notifyGateAllowsDelivery(record.closedReason)) {
|
|
9743
|
-
this.
|
|
10461
|
+
this.collectCoordinator.route(record);
|
|
9744
10462
|
}
|
|
9745
10463
|
}).catch((err) => {
|
|
9746
10464
|
this.chatRoundTickets.delete(record.id);
|
|
9747
10465
|
if (err instanceof Error) {
|
|
9748
|
-
|
|
10466
|
+
logger26.debug(`[subagent] background finalize error (record=${record.id}): ${err.message}`);
|
|
9749
10467
|
}
|
|
9750
10468
|
});
|
|
9751
10469
|
}
|
|
@@ -9838,7 +10556,7 @@ var SubagentService = class {
|
|
|
9838
10556
|
}
|
|
9839
10557
|
}
|
|
9840
10558
|
this.notifyHost.emitPendingUnregister(record.id, "closed");
|
|
9841
|
-
this.
|
|
10559
|
+
this.collectCoordinator.route(record);
|
|
9842
10560
|
return true;
|
|
9843
10561
|
}
|
|
9844
10562
|
/**
|
|
@@ -10040,9 +10758,9 @@ var ZCODE_APPSERVER_HARVEST_GRACE_MS = 1e3;
|
|
|
10040
10758
|
|
|
10041
10759
|
// src/execution/engine/engines/zcode/zcode-engine.ts
|
|
10042
10760
|
var import_node_child_process5 = require("child_process");
|
|
10043
|
-
var
|
|
10761
|
+
var fs24 = __toESM(require("fs"), 1);
|
|
10044
10762
|
var os4 = __toESM(require("os"), 1);
|
|
10045
|
-
var
|
|
10763
|
+
var path17 = __toESM(require("path"), 1);
|
|
10046
10764
|
|
|
10047
10765
|
// src/execution/engine/common/schema-emulation.ts
|
|
10048
10766
|
var import_ajv = __toESM(require("ajv"), 1);
|
|
@@ -10182,9 +10900,9 @@ function synthesizeCoarseEvents(response, usage) {
|
|
|
10182
10900
|
}
|
|
10183
10901
|
|
|
10184
10902
|
// src/execution/engine/engines/zcode/preparer.ts
|
|
10185
|
-
var
|
|
10903
|
+
var fs20 = __toESM(require("fs"), 1);
|
|
10186
10904
|
var os3 = __toESM(require("os"), 1);
|
|
10187
|
-
var
|
|
10905
|
+
var path15 = __toESM(require("path"), 1);
|
|
10188
10906
|
var ZcodePrepareError = class extends Error {
|
|
10189
10907
|
code;
|
|
10190
10908
|
constructor(code, message) {
|
|
@@ -10203,7 +10921,7 @@ function readSourceConfig(absPath) {
|
|
|
10203
10921
|
const empty = { providers: /* @__PURE__ */ new Map() };
|
|
10204
10922
|
let raw;
|
|
10205
10923
|
try {
|
|
10206
|
-
raw =
|
|
10924
|
+
raw = fs20.readFileSync(absPath, "utf8");
|
|
10207
10925
|
} catch {
|
|
10208
10926
|
return empty;
|
|
10209
10927
|
}
|
|
@@ -10237,7 +10955,7 @@ function hasApiKey(entry) {
|
|
|
10237
10955
|
return typeof key === "string" && key !== "";
|
|
10238
10956
|
}
|
|
10239
10957
|
function defaultV2ConfigPath() {
|
|
10240
|
-
return
|
|
10958
|
+
return path15.join(os3.homedir(), ...ZCODE_V2_CONFIG_PATH_SUFFIX);
|
|
10241
10959
|
}
|
|
10242
10960
|
var DEFAULT_PROVIDER_ID = "builtin:bigmodel-coding-plan";
|
|
10243
10961
|
function defaultProviderForShortName(merged, withKey) {
|
|
@@ -10300,8 +11018,8 @@ function listZcodeModels(sources) {
|
|
|
10300
11018
|
}
|
|
10301
11019
|
|
|
10302
11020
|
// src/execution/engine/engines/zcode/appserver-launcher.ts
|
|
10303
|
-
var
|
|
10304
|
-
var
|
|
11021
|
+
var fs21 = __toESM(require("fs"), 1);
|
|
11022
|
+
var path16 = __toESM(require("path"), 1);
|
|
10305
11023
|
var ZCODE_APPSERVER_LAUNCHER_NAME = "appserver-launcher.cjs";
|
|
10306
11024
|
var APPSERVER_LAUNCHER_SOURCE = `'use strict';
|
|
10307
11025
|
// zcode app-server fs \u62E6\u622A wrapper\uFF08\u7531 subagent-core appserver-launcher \u843D\u76D8\u751F\u6210\uFF09
|
|
@@ -10407,15 +11125,15 @@ import(CLI_PATH).catch((err) => {
|
|
|
10407
11125
|
});
|
|
10408
11126
|
`;
|
|
10409
11127
|
function ensureAppServerLauncher(engineDataDir) {
|
|
10410
|
-
const dir =
|
|
10411
|
-
const file =
|
|
10412
|
-
const existing =
|
|
11128
|
+
const dir = path16.join(engineDataDir, "engines", "zcode");
|
|
11129
|
+
const file = path16.join(dir, ZCODE_APPSERVER_LAUNCHER_NAME);
|
|
11130
|
+
const existing = fs21.existsSync(file) && fs21.statSync(file).isFile() ? fs21.readFileSync(file, "utf8") : void 0;
|
|
10413
11131
|
if (existing === APPSERVER_LAUNCHER_SOURCE) return file;
|
|
10414
11132
|
try {
|
|
10415
|
-
|
|
11133
|
+
fs21.mkdirSync(dir, { recursive: true });
|
|
10416
11134
|
const tmp = `${file}.tmp-${process.pid}-${Date.now()}`;
|
|
10417
|
-
|
|
10418
|
-
|
|
11135
|
+
fs21.writeFileSync(tmp, APPSERVER_LAUNCHER_SOURCE);
|
|
11136
|
+
fs21.renameSync(tmp, file);
|
|
10419
11137
|
} catch (err) {
|
|
10420
11138
|
const reason = toErrorMessage(err);
|
|
10421
11139
|
throw new Error(
|
|
@@ -10426,7 +11144,7 @@ function ensureAppServerLauncher(engineDataDir) {
|
|
|
10426
11144
|
}
|
|
10427
11145
|
|
|
10428
11146
|
// src/execution/engine/engines/zcode/reader.ts
|
|
10429
|
-
var
|
|
11147
|
+
var fs22 = __toESM(require("fs"), 1);
|
|
10430
11148
|
var ZcodeReaderError = class extends Error {
|
|
10431
11149
|
code;
|
|
10432
11150
|
/** 原始失败细节(缺文件/表漂移/运行时不支持)。 */
|
|
@@ -10625,7 +11343,7 @@ function buildView(db, sessionId) {
|
|
|
10625
11343
|
};
|
|
10626
11344
|
}
|
|
10627
11345
|
async function readZcodeSessionView(dbPath, sessionId) {
|
|
10628
|
-
if (!
|
|
11346
|
+
if (!fs22.existsSync(dbPath)) {
|
|
10629
11347
|
throw new ZcodeReaderError(`db \u6587\u4EF6\u4E0D\u5B58\u5728\uFF1A${dbPath}`);
|
|
10630
11348
|
}
|
|
10631
11349
|
const sqliteModuleId = "node:sqlite";
|
|
@@ -10659,9 +11377,9 @@ async function readZcodeSessionView(dbPath, sessionId) {
|
|
|
10659
11377
|
|
|
10660
11378
|
// src/execution/engine/engines/zcode/connection.ts
|
|
10661
11379
|
var import_node_child_process4 = require("child_process");
|
|
10662
|
-
var
|
|
11380
|
+
var fs23 = __toESM(require("fs"), 1);
|
|
10663
11381
|
var import_node_path6 = require("path");
|
|
10664
|
-
var
|
|
11382
|
+
var logger27 = getLogger("subagents");
|
|
10665
11383
|
var RAW_FRAME_LOG_CHARS = 200;
|
|
10666
11384
|
var RUNTIME_PREFERENCES = Object.freeze({
|
|
10667
11385
|
nativeSearchEnhancementsEnabled: true,
|
|
@@ -10866,7 +11584,7 @@ var AppServerConnection = class {
|
|
|
10866
11584
|
try {
|
|
10867
11585
|
this.onSpawned(child);
|
|
10868
11586
|
} catch (err) {
|
|
10869
|
-
|
|
11587
|
+
logger27.warn(`onSpawned \u56DE\u8C03\u5F02\u5E38\uFF08\u5FFD\u7565\uFF09: ${errMessage(err)}`);
|
|
10870
11588
|
}
|
|
10871
11589
|
}
|
|
10872
11590
|
let finalized = false;
|
|
@@ -10885,7 +11603,7 @@ var AppServerConnection = class {
|
|
|
10885
11603
|
try {
|
|
10886
11604
|
fn(reason);
|
|
10887
11605
|
} catch (err2) {
|
|
10888
|
-
|
|
11606
|
+
logger27.warn(`close handler \u5F02\u5E38: ${errMessage(err2)}`);
|
|
10889
11607
|
}
|
|
10890
11608
|
}
|
|
10891
11609
|
};
|
|
@@ -10925,14 +11643,14 @@ var AppServerConnection = class {
|
|
|
10925
11643
|
try {
|
|
10926
11644
|
frame = JSON.parse(text);
|
|
10927
11645
|
} catch {
|
|
10928
|
-
|
|
11646
|
+
logger27.warn(`\u65E0\u6CD5\u89E3\u6790\u7684\u534F\u8BAE\u884C\uFF08\u5FFD\u7565\uFF09: ${text.slice(0, RAW_FRAME_LOG_CHARS)}`);
|
|
10929
11647
|
return;
|
|
10930
11648
|
}
|
|
10931
11649
|
this.handleFrame(frame, text);
|
|
10932
11650
|
}
|
|
10933
11651
|
handleFrame(frame, rawText) {
|
|
10934
11652
|
if (!isRecord2(frame)) {
|
|
10935
|
-
|
|
11653
|
+
logger27.warn(`\u975E\u5BF9\u8C61\u534F\u8BAE\u5E27\uFF08\u5FFD\u7565\uFF09: ${rawText.slice(0, RAW_FRAME_LOG_CHARS)}`);
|
|
10936
11654
|
return;
|
|
10937
11655
|
}
|
|
10938
11656
|
if (isRecord2(frame.protocol)) this.capturedProtocolInfo = frame.protocol;
|
|
@@ -10947,19 +11665,19 @@ var AppServerConnection = class {
|
|
|
10947
11665
|
}
|
|
10948
11666
|
if (frame.id !== void 0 && frame.id !== null) {
|
|
10949
11667
|
if (typeof frame.id !== "number") {
|
|
10950
|
-
|
|
11668
|
+
logger27.warn(`\u5E94\u7B54 id \u975E\u6570\u5B57\uFF08\u5FFD\u7565\uFF09: ${rawText.slice(0, RAW_FRAME_LOG_CHARS)}`);
|
|
10951
11669
|
return;
|
|
10952
11670
|
}
|
|
10953
11671
|
this.settlePending(frame.id, frame);
|
|
10954
11672
|
return;
|
|
10955
11673
|
}
|
|
10956
11674
|
if (isRecord2(frame.protocol)) return;
|
|
10957
|
-
|
|
11675
|
+
logger27.warn(`\u65E0\u6CD5\u5F52\u7C7B\u7684\u534F\u8BAE\u5E27\uFF08\u5FFD\u7565\uFF09: ${rawText.slice(0, RAW_FRAME_LOG_CHARS)}`);
|
|
10958
11676
|
}
|
|
10959
11677
|
settlePending(id, frame) {
|
|
10960
11678
|
const entry = this.pending.get(id);
|
|
10961
11679
|
if (!entry) {
|
|
10962
|
-
|
|
11680
|
+
logger27.warn(`\u54CD\u5E94\u65E0\u5339\u914D\u8BF7\u6C42 id=${id}\uFF08\u5FFD\u7565\uFF09`);
|
|
10963
11681
|
return;
|
|
10964
11682
|
}
|
|
10965
11683
|
this.pending.delete(id);
|
|
@@ -10985,7 +11703,7 @@ var AppServerConnection = class {
|
|
|
10985
11703
|
try {
|
|
10986
11704
|
fn(params);
|
|
10987
11705
|
} catch (err) {
|
|
10988
|
-
|
|
11706
|
+
logger27.warn(`push handler \u5F02\u5E38\uFF08${method}\uFF09: ${errMessage(err)}`);
|
|
10989
11707
|
}
|
|
10990
11708
|
}
|
|
10991
11709
|
}
|
|
@@ -10995,12 +11713,12 @@ var AppServerConnection = class {
|
|
|
10995
11713
|
answerReverse(id, method, params) {
|
|
10996
11714
|
const handler = this.reverseHandlers[method];
|
|
10997
11715
|
if (!handler) {
|
|
10998
|
-
|
|
11716
|
+
logger27.warn(`\u672A\u77E5\u53CD\u5411\u8BF7\u6C42 ${method}\uFF08id=${id}\uFF09\uFF1A\u56DE\u7A7A result\uFF08\u4E0D\u7B54\u4F1A 15s \u8D85\u65F6\u65AD\u8FDE\uFF0C\u65E7\u5B9E\u6D4B -32022\uFF09`);
|
|
10999
11717
|
this.writeFrame({ id, result: {} });
|
|
11000
11718
|
return;
|
|
11001
11719
|
}
|
|
11002
11720
|
Promise.resolve().then(() => handler(params)).then((result) => this.writeFrame({ id, result: result ?? {} })).catch((err) => {
|
|
11003
|
-
|
|
11721
|
+
logger27.warn(`\u53CD\u5411\u8BF7\u6C42 ${method} handler \u5F02\u5E38: ${errMessage(err)}`);
|
|
11004
11722
|
this.writeFrame({ id, error: { code: -32e3, message: errMessage(err) } });
|
|
11005
11723
|
});
|
|
11006
11724
|
}
|
|
@@ -11016,7 +11734,7 @@ var AppServerConnection = class {
|
|
|
11016
11734
|
`);
|
|
11017
11735
|
return true;
|
|
11018
11736
|
} catch (err) {
|
|
11019
|
-
|
|
11737
|
+
logger27.warn(`\u5199\u5165 app-server \u5931\u8D25: ${errMessage(err)}`);
|
|
11020
11738
|
return false;
|
|
11021
11739
|
}
|
|
11022
11740
|
}
|
|
@@ -11025,8 +11743,8 @@ var AppServerConnection = class {
|
|
|
11025
11743
|
if (this.stderrStreamFailed) return;
|
|
11026
11744
|
if (this.stderrStream === null) {
|
|
11027
11745
|
try {
|
|
11028
|
-
|
|
11029
|
-
this.stderrStream =
|
|
11746
|
+
fs23.mkdirSync((0, import_node_path6.dirname)(this.stderrLogPath), { recursive: true });
|
|
11747
|
+
this.stderrStream = fs23.createWriteStream(this.stderrLogPath, { flags: "a" });
|
|
11030
11748
|
this.stderrStream.on("error", () => {
|
|
11031
11749
|
this.stderrStreamFailed = true;
|
|
11032
11750
|
});
|
|
@@ -11048,8 +11766,8 @@ var AppServerConnection = class {
|
|
|
11048
11766
|
};
|
|
11049
11767
|
|
|
11050
11768
|
// src/execution/engine/engines/zcode/session-channel.ts
|
|
11051
|
-
var
|
|
11052
|
-
var
|
|
11769
|
+
var import_node_crypto2 = require("crypto");
|
|
11770
|
+
var logger28 = getLogger("subagents");
|
|
11053
11771
|
var SUBSCRIBE_DELIVERY_KIND = "desktop-continuous";
|
|
11054
11772
|
var WORKSPACE_KEY_HASH_CHARS = 16;
|
|
11055
11773
|
var CREATE_REPLY_LOG_CHARS = 300;
|
|
@@ -11061,7 +11779,7 @@ function errMessage2(err) {
|
|
|
11061
11779
|
return toErrorMessage(err);
|
|
11062
11780
|
}
|
|
11063
11781
|
function stableWorkspaceKey(workspacePath) {
|
|
11064
|
-
return "ws-" + (0,
|
|
11782
|
+
return "ws-" + (0, import_node_crypto2.createHash)("sha256").update(workspacePath).digest("hex").slice(0, WORKSPACE_KEY_HASH_CHARS);
|
|
11065
11783
|
}
|
|
11066
11784
|
function extractCreatedSessionId(created) {
|
|
11067
11785
|
if (!isRecord3(created)) return void 0;
|
|
@@ -11191,7 +11909,7 @@ function resolveTurnTimerMs(parts) {
|
|
|
11191
11909
|
source = `env ${parts.envName}=${raw}`;
|
|
11192
11910
|
} else {
|
|
11193
11911
|
if (parsed.state === "invalid") {
|
|
11194
|
-
|
|
11912
|
+
logger28.warn(
|
|
11195
11913
|
`[session-channel] ${parts.envName}="${raw}" \u975E\u6CD5\uFF08\u5E94\u4E3A\u6BEB\u79D2\u6570\u5B57\uFF09\u2014\u2014\u56DE\u843D\u9ED8\u8BA4 ${parts.fallbackMs}ms\uFF08${parts.label}\uFF09\u3002\u8BBE\u7F6E\u6B63\u6BEB\u79D2\u503C\u8986\u76D6\uFF0C\u6216 0 \u663E\u5F0F\u5173\u95ED`
|
|
11196
11914
|
);
|
|
11197
11915
|
}
|
|
@@ -11200,7 +11918,7 @@ function resolveTurnTimerMs(parts) {
|
|
|
11200
11918
|
}
|
|
11201
11919
|
}
|
|
11202
11920
|
if (value <= 0) {
|
|
11203
|
-
|
|
11921
|
+
logger28.warn(
|
|
11204
11922
|
`[session-channel] zcode turn ${parts.label}\u5DF2\u5173\u95ED\uFF08${source}\uFF09\u2014\u2014${parts.offConsequence}\u3002\u8BBE\u6B63\u6BEB\u79D2\u503C\uFF08env ${parts.envName} \u6216\u663E\u5F0F\u4F20\u53C2\uFF09\u6062\u590D\u56DE\u6536\u5C42`
|
|
11205
11923
|
);
|
|
11206
11924
|
}
|
|
@@ -11322,7 +12040,7 @@ var SessionChannel = class {
|
|
|
11322
12040
|
{ timeoutMs: ZCODE_APPSERVER_TURN_CLOSE_TIMEOUT_MS }
|
|
11323
12041
|
);
|
|
11324
12042
|
} catch (err) {
|
|
11325
|
-
|
|
12043
|
+
logger28.warn(
|
|
11326
12044
|
`session/close \u5931\u8D25\uFF08\u4F1A\u8BDD ${sessionId}\uFF0Cbest-effort \u5FFD\u7565\uFF09: ${errMessage2(
|
|
11327
12045
|
err
|
|
11328
12046
|
)}`
|
|
@@ -11523,7 +12241,7 @@ var SessionChannel = class {
|
|
|
11523
12241
|
applyStreamDelta(turn, payload) {
|
|
11524
12242
|
if (typeof payload.delta !== "string" || payload.delta === "") return;
|
|
11525
12243
|
if (turn.settled) {
|
|
11526
|
-
|
|
12244
|
+
logger28.warn(
|
|
11527
12245
|
`\u7EC8\u6001\u540E\u8FDF\u5230\u7684 delta \u4E22\u5F03\uFF08\u4F1A\u8BDD ${turn.sessionId}\uFF0C\u4E0D\u53D8\u91CF 2\uFF1Aresolve \u540E\u4E0D\u518D\u53D1\u4E8B\u4EF6\uFF09: ${payload.delta.slice(
|
|
11528
12246
|
0,
|
|
11529
12247
|
DELTA_LOG_CHARS
|
|
@@ -11552,7 +12270,7 @@ var SessionChannel = class {
|
|
|
11552
12270
|
turn.lastTerminalStatus = status;
|
|
11553
12271
|
this.recordTerminalError(turn, params);
|
|
11554
12272
|
if (turn.settled) {
|
|
11555
|
-
|
|
12273
|
+
logger28.warn(
|
|
11556
12274
|
`\u6743\u5A01\u7EC8\u6001\u665A\u4E8E\u843D\u5B9A\u7ED3\u679C\u5230\u8FBE\uFF08\u4F1A\u8BDD ${turn.sessionId}\uFF0C\u5DF2\u843D\u5B9A source=${turn.terminal?.source}\uFF09\uFF1Aturn.terminal status="${status}" \u4EC5\u8BB0\u5F55\u4E0D\u6539\u5199\uFF08P0-1 D5\u2460\uFF09`
|
|
11557
12275
|
);
|
|
11558
12276
|
return;
|
|
@@ -11598,7 +12316,7 @@ var SessionChannel = class {
|
|
|
11598
12316
|
}
|
|
11599
12317
|
);
|
|
11600
12318
|
} catch (err) {
|
|
11601
|
-
|
|
12319
|
+
logger28.warn(
|
|
11602
12320
|
`session/read \u515C\u5E95\u5931\u8D25\uFF08\u4F1A\u8BDD ${sessionId}\uFF0C\u964D\u7EA7\u6536\u5C3E\u5E27/delta \u805A\u5408\uFF09: ${errMessage2(
|
|
11603
12321
|
err
|
|
11604
12322
|
)}`
|
|
@@ -11609,11 +12327,11 @@ var SessionChannel = class {
|
|
|
11609
12327
|
};
|
|
11610
12328
|
|
|
11611
12329
|
// src/execution/engine/engines/zcode/zcode-engine.ts
|
|
11612
|
-
var
|
|
12330
|
+
var logger29 = getLogger("subagents");
|
|
11613
12331
|
var PROBE_VERSION_TIMEOUT_MS2 = 15e3;
|
|
11614
12332
|
var COMMON_THOUGHT_LEVELS = ["low", "high", "max"];
|
|
11615
12333
|
function hostZcodeDbPath() {
|
|
11616
|
-
return
|
|
12334
|
+
return path17.join(os4.homedir(), ...ZCODE_HOST_DB_SUFFIX);
|
|
11617
12335
|
}
|
|
11618
12336
|
var ZcodeEngine = class {
|
|
11619
12337
|
id = ZCODE_ENGINE_ID;
|
|
@@ -11684,7 +12402,7 @@ var ZcodeEngine = class {
|
|
|
11684
12402
|
}
|
|
11685
12403
|
/** check 1:二进制存在性(isFile 才算——同名目录不是可执行入口)。 */
|
|
11686
12404
|
probeBinaryCheck(cliPath) {
|
|
11687
|
-
const binaryOk =
|
|
12405
|
+
const binaryOk = fs24.existsSync(cliPath) && fs24.statSync(cliPath).isFile();
|
|
11688
12406
|
return {
|
|
11689
12407
|
name: "binary",
|
|
11690
12408
|
ok: binaryOk,
|
|
@@ -11778,11 +12496,11 @@ var ZcodeEngine = class {
|
|
|
11778
12496
|
if (final.kind === "run-failed" && final.transient !== void 0 && ctx.signal?.aborted !== true && !this.disposed) {
|
|
11779
12497
|
const budget = resolveTransientRetryBudget(explicitTurnBudgetMs(), Date.now() - attemptStartedAt);
|
|
11780
12498
|
if (budget.state === "depleted") {
|
|
11781
|
-
|
|
12499
|
+
logger29.warn(
|
|
11782
12500
|
`[zcode-engine] \u672B\u6B21 attempt \u77AC\u65F6\u5931\u8D25\uFF08${final.transient}\uFF09\u2014\u2014\u663E\u5F0F\u603B\u4E0A\u754C\u9884\u7B97\u5269\u4F59\u4E0D\u8DB3 ${ZCODE_TURN_RETRY_MIN_BUDGET_MS}ms\uFF0C\u4E0D\u91CD\u8BD5\u76F4\u63A5\u7EC8\u6001\u5316\uFF08\u9884\u7B97\u7EE7\u627F\uFF1A\u91CD\u8BD5\u4E0D\u91CD\u7F6E\u603B\u9884\u7B97\uFF09`
|
|
11783
12501
|
);
|
|
11784
12502
|
} else {
|
|
11785
|
-
|
|
12503
|
+
logger29.warn(
|
|
11786
12504
|
`[zcode-engine] \u672B\u6B21 attempt \u77AC\u65F6\u5931\u8D25\uFF08${final.transient}\uFF09\u2014\u2014\u6B62\u635F\u94FE\u5DF2\u7EC8\u5C40\uFF0C\u65B0\u4F1A\u8BDD\u81EA\u52A8\u91CD\u8BD5\u4E00\u6B21` + (budget.state === "inherit" ? `\uFF08\u9884\u7B97\u7EE7\u627F\uFF1A\u91CD\u8BD5\u8F6E\u603B\u4E0A\u754C=\u5269\u4F59 ${budget.remainingMs}ms\uFF0C\u4E0D\u91CD\u7F6E\u603B\u9884\u7B97\uFF09` : "\uFF08\u65E0\u663E\u5F0F\u603B\u4E0A\u754C\u9884\u7B97\uFF0C\u91CD\u8BD5\u8F6E\u8D70 env/\u9ED8\u8BA4\u4E0A\u754C\uFF09")
|
|
11787
12505
|
);
|
|
11788
12506
|
const retry = await this.attemptAppServerTurn(task, ctx, modelRef, cwd, basePrompt, {
|
|
@@ -11936,7 +12654,7 @@ var ZcodeEngine = class {
|
|
|
11936
12654
|
delayResolved(ZCODE_APPSERVER_ABORT_GRACE_MS, false)
|
|
11937
12655
|
]);
|
|
11938
12656
|
if (settled) return "settled-in-grace";
|
|
11939
|
-
|
|
12657
|
+
logger29.warn(
|
|
11940
12658
|
`[zcode-engine] abort grace \u7A97\u53E3\u5185\u672A\u89C1\u7EC8\u6001\u2014\u2014killChain \u6536\u5272\u5171\u4EAB\u8FDB\u7A0B\uFF08\u63A5\u53D7\u8FDE\u5750\uFF0C\u5728\u9014\u4EFB\u52A1\u8D70\u5D29\u6E83\u8DEF\u5F84\uFF09`
|
|
11941
12659
|
);
|
|
11942
12660
|
await rt.conn.shutdown({ graceMs: ZCODE_KILL_GRACE_MS });
|
|
@@ -11961,19 +12679,19 @@ var ZcodeEngine = class {
|
|
|
11961
12679
|
} catch (err) {
|
|
11962
12680
|
if (entry.escalateOn === "stop-outcome") {
|
|
11963
12681
|
if (isAppServerRpcError(err)) {
|
|
11964
|
-
|
|
12682
|
+
logger29.debug(
|
|
11965
12683
|
`[zcode-engine] session/stop \u62A5\u534F\u8BAE\u6027 error\uFF08${errMessage3(err)}\uFF09\u2014\u2014\u4F1A\u8BDD\u5DF2\u56DE\u6536\uFF0C\u63A7\u5236\u9762\u5B58\u6D3B\uFF0C\u4E0D\u5347\u7EA7\u6740\u94FE`
|
|
11966
12684
|
);
|
|
11967
12685
|
return "stop-rejected";
|
|
11968
12686
|
}
|
|
11969
|
-
|
|
12687
|
+
logger29.warn(
|
|
11970
12688
|
`[zcode-engine] session/stop \u65E0\u5E94\u7B54\uFF08${errMessage3(err)}\uFF09\u2014\u2014\u5347\u7EA7 killChain \u6536\u5272\u5171\u4EAB\u8FDB\u7A0B\uFF08\u8D85\u65F6\u5165\u53E3\uFF0C\u63A5\u53D7\u8FDE\u5750\uFF09`
|
|
11971
12689
|
);
|
|
11972
12690
|
await rt.conn.shutdown({ graceMs: ZCODE_KILL_GRACE_MS });
|
|
11973
12691
|
await this.awaitConnFinalized(rt);
|
|
11974
12692
|
return "stop-unreachable-killed";
|
|
11975
12693
|
}
|
|
11976
|
-
|
|
12694
|
+
logger29.debug(
|
|
11977
12695
|
`[zcode-engine] session/stop \u5931\u8D25\uFF08${errMessage3(err)}\uFF09\u2014\u2014grace \u540E\u8D70 killChain \u515C\u5E95`
|
|
11978
12696
|
);
|
|
11979
12697
|
}
|
|
@@ -12004,7 +12722,7 @@ var ZcodeEngine = class {
|
|
|
12004
12722
|
cwd: engineDataDir,
|
|
12005
12723
|
env,
|
|
12006
12724
|
launcherScript,
|
|
12007
|
-
stderrLogPath:
|
|
12725
|
+
stderrLogPath: path17.join(engineDataDir, "logs", "zcode-appserver-stderr.log")
|
|
12008
12726
|
});
|
|
12009
12727
|
const rt = {
|
|
12010
12728
|
conn,
|
|
@@ -12169,15 +12887,15 @@ var ZcodeEngine = class {
|
|
|
12169
12887
|
const dbPathRaw = handle.data.sessionRef["dbPath"];
|
|
12170
12888
|
if (typeof sessionId === "string" && typeof dbPathRaw === "string") {
|
|
12171
12889
|
let dbPath;
|
|
12172
|
-
if (
|
|
12890
|
+
if (path17.isAbsolute(dbPathRaw)) {
|
|
12173
12891
|
if (dbPathRaw === hostZcodeDbPath()) dbPath = dbPathRaw;
|
|
12174
12892
|
else {
|
|
12175
|
-
|
|
12893
|
+
logger29.warn("[zcode-engine] record dbPath \u975E\u5BBF\u4E3B db \u7EDD\u5BF9\u8DEF\u5F84\uFF0C\u62D2\u7EDD \u2460\u7EA7\u8BFB\u53D6\u964D journal", {
|
|
12176
12894
|
dbPath: dbPathRaw
|
|
12177
12895
|
});
|
|
12178
12896
|
}
|
|
12179
12897
|
} else {
|
|
12180
|
-
dbPath =
|
|
12898
|
+
dbPath = path17.join(
|
|
12181
12899
|
resolvePoolDir(this.deps.engineDataDir(), ZCODE_ENGINE_ID, handle.data.poolKey),
|
|
12182
12900
|
dbPathRaw
|
|
12183
12901
|
);
|
|
@@ -12186,7 +12904,7 @@ var ZcodeEngine = class {
|
|
|
12186
12904
|
try {
|
|
12187
12905
|
return await readZcodeSessionView(dbPath, sessionId);
|
|
12188
12906
|
} catch (err) {
|
|
12189
|
-
|
|
12907
|
+
logger29.warn("[zcode-engine] native session read failed, degrade to journal replay", {
|
|
12190
12908
|
dbPath,
|
|
12191
12909
|
sessionId,
|
|
12192
12910
|
reason: toErrorMessage(err)
|
|
@@ -12212,7 +12930,7 @@ var ZcodeEngine = class {
|
|
|
12212
12930
|
const thoughtLevel = task.thinkingLevel?.trim();
|
|
12213
12931
|
if (thoughtLevel === void 0 || thoughtLevel === "") return;
|
|
12214
12932
|
if (COMMON_THOUGHT_LEVELS.includes(thoughtLevel)) return;
|
|
12215
|
-
|
|
12933
|
+
logger29.warn(
|
|
12216
12934
|
`[zcode-engine] thinkingLevel=${thoughtLevel} \u5DF2\u900F\u4F20\u4E3A thoughtLevel\uFF08\u975E\u5E38\u89C1\u6863\u4F4D\uFF09\uFF1A\u82E5\u76EE\u6807\u6A21\u578B\u4E0D\u652F\u6301\u8BE5\u6863\u4F4D\u5C06\u88AB\u5FFD\u7565/\u56DE\u843D\u5230\u6A21\u578B\u7F3A\u7701\u63A8\u7406\u6863\u4F4D\uFF08\u5E38\u89C1\u6863\u4F4D\uFF1A${COMMON_THOUGHT_LEVELS.join("/")}\uFF09\uFF1B\u6863\u4F4D\u662F\u5426\u751F\u6548\u4EE5\u6A21\u578B\u5B9E\u9645\u884C\u4E3A\u4E3A\u51C6`,
|
|
12217
12935
|
{ taskId: ctx.taskId }
|
|
12218
12936
|
);
|
|
@@ -12230,7 +12948,7 @@ var ZcodeEngine = class {
|
|
|
12230
12948
|
if (ctx.ctxModel === void 0) return;
|
|
12231
12949
|
const requested = task.model?.trim();
|
|
12232
12950
|
if (requested !== void 0 && requested !== "") return;
|
|
12233
|
-
|
|
12951
|
+
logger29.warn(
|
|
12234
12952
|
`[zcode-engine] ctx.ctxModel\uFF08${ctx.ctxModel.id}\uFF09\u88AB\u5FFD\u7565\u2014\u2014ctxModel \u662F pi \u94FE\u8DEF\u515C\u5E95\uFF0Czcode \u4E0D\u6D88\u8D39\uFF1Btask.model \u672A\u663E\u5F0F\u6307\u5B9A\uFF0C\u5B9E\u9645\u4F7F\u7528\u5F15\u64CE\u7F3A\u7701\u6A21\u578B ${modelRef}`,
|
|
12235
12953
|
{ taskId: ctx.taskId }
|
|
12236
12954
|
);
|
|
@@ -12443,7 +13161,7 @@ async function defaultProbeVersion2(cliPath) {
|
|
|
12443
13161
|
);
|
|
12444
13162
|
});
|
|
12445
13163
|
} catch (err) {
|
|
12446
|
-
|
|
13164
|
+
logger29.debug(
|
|
12447
13165
|
`[zcode-engine] probe version check failed (best-effort continue): ${toErrorMessage(err)}`
|
|
12448
13166
|
);
|
|
12449
13167
|
return void 0;
|
|
@@ -12560,10 +13278,10 @@ function parseEngineHandle(raw) {
|
|
|
12560
13278
|
}
|
|
12561
13279
|
|
|
12562
13280
|
// src/execution/engine/common/session-view-service.ts
|
|
12563
|
-
var
|
|
13281
|
+
var import_node_crypto3 = require("crypto");
|
|
12564
13282
|
var import_node_os3 = require("os");
|
|
12565
13283
|
var import_node_path7 = require("path");
|
|
12566
|
-
var
|
|
13284
|
+
var logger30 = getLogger("subagents");
|
|
12567
13285
|
var DEFAULT_ENGINE_ID2 = "pi";
|
|
12568
13286
|
function extractEngineId(record) {
|
|
12569
13287
|
const engine = record.engine;
|
|
@@ -12588,13 +13306,13 @@ async function readZcodeNativeTier(handle, dataDir) {
|
|
|
12588
13306
|
const dbPathRaw = handle.sessionRef["dbPath"];
|
|
12589
13307
|
const sessionId = handle.sessionRef["sessionId"];
|
|
12590
13308
|
if (typeof dbPathRaw !== "string" || typeof sessionId !== "string") {
|
|
12591
|
-
|
|
13309
|
+
logger30.debug("[session-view-service] zcode tier1 skipped: handle missing dbPath/sessionId");
|
|
12592
13310
|
return void 0;
|
|
12593
13311
|
}
|
|
12594
13312
|
let dbPath;
|
|
12595
13313
|
if (dbPathRaw.startsWith("/")) {
|
|
12596
13314
|
if (dbPathRaw !== (0, import_node_path7.resolve)((0, import_node_os3.homedir)(), ...ZCODE_HOST_DB_SUFFIX)) {
|
|
12597
|
-
|
|
13315
|
+
logger30.warn(
|
|
12598
13316
|
`[session-view-service] zcode absolute dbPath not host db, reject tier1: ${dbPathRaw}`
|
|
12599
13317
|
);
|
|
12600
13318
|
return void 0;
|
|
@@ -12604,14 +13322,14 @@ async function readZcodeNativeTier(handle, dataDir) {
|
|
|
12604
13322
|
const poolDir = resolvePoolDir(dataDir, ZCODE_ENGINE_ID, handle.poolKey);
|
|
12605
13323
|
dbPath = (0, import_node_path7.resolve)(poolDir, dbPathRaw);
|
|
12606
13324
|
if (!isStrictlyUnder(poolDir, dbPath)) {
|
|
12607
|
-
|
|
13325
|
+
logger30.warn(`[session-view-service] zcode dbPath escapes pool dir, reject tier1: ${dbPath}`);
|
|
12608
13326
|
return void 0;
|
|
12609
13327
|
}
|
|
12610
13328
|
}
|
|
12611
13329
|
try {
|
|
12612
13330
|
return await readZcodeSessionView(dbPath, sessionId);
|
|
12613
13331
|
} catch (err) {
|
|
12614
|
-
|
|
13332
|
+
logger30.debug(
|
|
12615
13333
|
`[session-view-service] zcode tier1 read failed, degrade to journal tier: ${toErrorMessage(err)}`
|
|
12616
13334
|
);
|
|
12617
13335
|
return void 0;
|
|
@@ -12620,18 +13338,18 @@ async function readZcodeNativeTier(handle, dataDir) {
|
|
|
12620
13338
|
function readJournalTier(record, handle, dataDir) {
|
|
12621
13339
|
const journalPath = handle.journalPath;
|
|
12622
13340
|
if (journalPath === void 0) {
|
|
12623
|
-
|
|
13341
|
+
logger30.debug("[session-view-service] tier2 skipped: no journalPath in handle");
|
|
12624
13342
|
return void 0;
|
|
12625
13343
|
}
|
|
12626
13344
|
if (!isStrictlyUnder(resolveEnginesRoot(dataDir), journalPath)) {
|
|
12627
|
-
|
|
13345
|
+
logger30.warn(
|
|
12628
13346
|
`[session-view-service] journalPath escapes engines root, reject tier2: ${journalPath}`
|
|
12629
13347
|
);
|
|
12630
13348
|
return void 0;
|
|
12631
13349
|
}
|
|
12632
13350
|
const messages = replayEventsToHistory(replayJournal(journalPath), record);
|
|
12633
13351
|
if (messages === void 0) {
|
|
12634
|
-
|
|
13352
|
+
logger30.debug(
|
|
12635
13353
|
`[session-view-service] tier2 journal replay produced no content, degrade to outcome-only (path=${journalPath})`
|
|
12636
13354
|
);
|
|
12637
13355
|
}
|
|
@@ -12724,7 +13442,7 @@ function turnsToMessages(turns, record) {
|
|
|
12724
13442
|
const messages = [];
|
|
12725
13443
|
if (record.task.length > 0) {
|
|
12726
13444
|
messages.push({
|
|
12727
|
-
id: (0,
|
|
13445
|
+
id: (0, import_node_crypto3.randomUUID)(),
|
|
12728
13446
|
role: "user",
|
|
12729
13447
|
content: record.task,
|
|
12730
13448
|
status: "complete",
|
|
@@ -12733,11 +13451,11 @@ function turnsToMessages(turns, record) {
|
|
|
12733
13451
|
}
|
|
12734
13452
|
for (const [i, turn] of turns.entries()) {
|
|
12735
13453
|
messages.push({
|
|
12736
|
-
id: (0,
|
|
13454
|
+
id: (0, import_node_crypto3.randomUUID)(),
|
|
12737
13455
|
role: "assistant",
|
|
12738
13456
|
content: turn.text,
|
|
12739
13457
|
status: "complete",
|
|
12740
|
-
...turn.thinking.length > 0 ? { thinking: [{ id: (0,
|
|
13458
|
+
...turn.thinking.length > 0 ? { thinking: [{ id: (0, import_node_crypto3.randomUUID)(), content: turn.thinking, collapsed: true }] } : {},
|
|
12741
13459
|
...turn.toolCalls.length > 0 ? { toolCalls: turn.toolCalls.map((tc) => toHistoryToolCall(tc, base + i + 1)) } : {},
|
|
12742
13460
|
...turn.usage !== void 0 ? { usage: { inputTokens: turn.usage.input, outputTokens: turn.usage.output } } : {},
|
|
12743
13461
|
timestamp: base + i + 1
|
|
@@ -12749,7 +13467,7 @@ function toHistoryToolCall(tc, ts) {
|
|
|
12749
13467
|
const output = extractTextFromContent(tc.result?.content);
|
|
12750
13468
|
const details = tc.result?.details;
|
|
12751
13469
|
return {
|
|
12752
|
-
id: (0,
|
|
13470
|
+
id: (0, import_node_crypto3.randomUUID)(),
|
|
12753
13471
|
toolName: tc.toolName,
|
|
12754
13472
|
input: tc.args ?? {},
|
|
12755
13473
|
...output !== void 0 ? { output } : {},
|
|
@@ -12777,7 +13495,7 @@ function outcomeOnlyMessages(record) {
|
|
|
12777
13495
|
const messages = [];
|
|
12778
13496
|
if (record.task.length > 0) {
|
|
12779
13497
|
messages.push({
|
|
12780
|
-
id: (0,
|
|
13498
|
+
id: (0, import_node_crypto3.randomUUID)(),
|
|
12781
13499
|
role: "user",
|
|
12782
13500
|
content: record.task,
|
|
12783
13501
|
status: "complete",
|
|
@@ -12786,7 +13504,7 @@ function outcomeOnlyMessages(record) {
|
|
|
12786
13504
|
}
|
|
12787
13505
|
const isErrorOutcome = record.result === void 0 && record.error !== void 0;
|
|
12788
13506
|
messages.push({
|
|
12789
|
-
id: (0,
|
|
13507
|
+
id: (0, import_node_crypto3.randomUUID)(),
|
|
12790
13508
|
role: "assistant",
|
|
12791
13509
|
// result 优先(正常轮终文本);error 次之(失败终态);双缺占位(运行中被杀等)
|
|
12792
13510
|
content: record.result ?? record.error ?? "(no outcome recorded)",
|
|
@@ -12801,14 +13519,14 @@ async function readSubagentHistoryMessages(record, dataDir) {
|
|
|
12801
13519
|
if (engineId === DEFAULT_ENGINE_ID2) return [];
|
|
12802
13520
|
const handle = parseEngineHandle(record.engineHandle);
|
|
12803
13521
|
if (handle === void 0) {
|
|
12804
|
-
|
|
13522
|
+
logger30.debug(
|
|
12805
13523
|
`[session-view-service] engine '${engineId}' record has no engineHandle, degrade to outcome-only (subagentId=${record.subagentId})`
|
|
12806
13524
|
);
|
|
12807
13525
|
return outcomeOnlyMessages(record);
|
|
12808
13526
|
}
|
|
12809
13527
|
const reader = lookupNativeSessionReader(engineId);
|
|
12810
13528
|
if (reader === void 0) {
|
|
12811
|
-
|
|
13529
|
+
logger30.debug(
|
|
12812
13530
|
`[session-view-service] engine '${engineId}' has no native reader tier, degrade to outcome-only (subagentId=${record.subagentId})`
|
|
12813
13531
|
);
|
|
12814
13532
|
return outcomeOnlyMessages(record);
|
|
@@ -12825,14 +13543,14 @@ function isStrictlyUnder(parent, child) {
|
|
|
12825
13543
|
}
|
|
12826
13544
|
|
|
12827
13545
|
// src/execution/agent-registry.ts
|
|
12828
|
-
var
|
|
13546
|
+
var path18 = __toESM(require("path"), 1);
|
|
12829
13547
|
|
|
12830
13548
|
// src/shared/resource-discovery.ts
|
|
12831
13549
|
var fsSync2 = __toESM(require("fs"), 1);
|
|
12832
13550
|
var import_promises2 = require("fs/promises");
|
|
12833
13551
|
var import_node_os4 = require("os");
|
|
12834
13552
|
var import_node_path8 = require("path");
|
|
12835
|
-
var
|
|
13553
|
+
var logger31 = getLogger("subagents");
|
|
12836
13554
|
var shadowWarnDedup = /* @__PURE__ */ new Set();
|
|
12837
13555
|
var MAX_SHADOW_WARN_DEDUP = 1024;
|
|
12838
13556
|
var MACHINE_SOURCES = /* @__PURE__ */ new Set([
|
|
@@ -13155,7 +13873,7 @@ async function discoverResources(config) {
|
|
|
13155
13873
|
const msg = `[resource-discovery] duplicate ${config.kind} "${key}" from ${r.source} shadows ${existing.source}`;
|
|
13156
13874
|
const data = { shadowed: existing.path, kept: r.path };
|
|
13157
13875
|
if (isMachineSource(existing.source) || isMachineSource(r.source)) {
|
|
13158
|
-
|
|
13876
|
+
logger31.debug(msg, data);
|
|
13159
13877
|
} else {
|
|
13160
13878
|
const dedupKey = `${config.kind}|${key}|${existing.path}|${r.path}`;
|
|
13161
13879
|
if (!shadowWarnDedup.has(dedupKey)) {
|
|
@@ -13163,7 +13881,7 @@ async function discoverResources(config) {
|
|
|
13163
13881
|
shadowWarnDedup.clear();
|
|
13164
13882
|
}
|
|
13165
13883
|
shadowWarnDedup.add(dedupKey);
|
|
13166
|
-
|
|
13884
|
+
logger31.warn(msg, data);
|
|
13167
13885
|
}
|
|
13168
13886
|
}
|
|
13169
13887
|
}
|
|
@@ -13804,10 +14522,10 @@ function lintScript(source) {
|
|
|
13804
14522
|
}
|
|
13805
14523
|
|
|
13806
14524
|
// src/execution/agent-registry.ts
|
|
13807
|
-
var
|
|
14525
|
+
var logger32 = getLogger("subagents");
|
|
13808
14526
|
var FM_DELIM = "---";
|
|
13809
14527
|
function parseAgentWithMeta(filePath, content) {
|
|
13810
|
-
const name =
|
|
14528
|
+
const name = path18.basename(filePath, ".md");
|
|
13811
14529
|
const fm = splitAgentFrontmatter(content);
|
|
13812
14530
|
if (fm.kind === "none") {
|
|
13813
14531
|
return { config: { name, systemPrompt: content.trim() }, meta: null };
|
|
@@ -13837,7 +14555,7 @@ function resolveLegacyRoutingFallbacks(agentMeta, yamlBlock, filePath) {
|
|
|
13837
14555
|
const modelFallback = extractYamlField(yamlBlock, "model");
|
|
13838
14556
|
const toolsFallback = parseCommaListFallback(extractYamlField(yamlBlock, "tools"));
|
|
13839
14557
|
if (!agentMeta && /^model:|^tools:/m.test(yamlBlock)) {
|
|
13840
|
-
|
|
14558
|
+
logger32.warn(
|
|
13841
14559
|
`[agent-registry] ${filePath}: agent frontmatter \u7F3A name/description\uFF08IF1 \u5FC5\u586B\uFF09\uFF0Cmodel/tools \u7ECF legacy fallback \u751F\u6548\uFF08\u76F4\u63A5\u8DEF\u5F84\u4E0D\u4E22\u914D\u7F6E\uFF09\uFF0C\u4F46\u7ED3\u6784\u5316\u8DEF\u7531\u4E0D\u53EF\u89C1\u2014\u2014\u8BF7\u8865\u5145 description`
|
|
13842
14560
|
);
|
|
13843
14561
|
}
|
|
@@ -13875,7 +14593,7 @@ function extractYamlField(yaml, key) {
|
|
|
13875
14593
|
return value || void 0;
|
|
13876
14594
|
}
|
|
13877
14595
|
function parseAgentProfile(text, filePath) {
|
|
13878
|
-
const stem3 =
|
|
14596
|
+
const stem3 = path18.basename(filePath, ".md");
|
|
13879
14597
|
const warnings = [];
|
|
13880
14598
|
const fm = splitAgentFrontmatter(text);
|
|
13881
14599
|
if (fm.kind === "none") {
|
|
@@ -14013,84 +14731,13 @@ var AgentRegistry = class {
|
|
|
14013
14731
|
const { config, meta } = parseAgentWithMeta(filePath, file.content);
|
|
14014
14732
|
const lintFindings = meta ? lintAgentMeta(meta) : [];
|
|
14015
14733
|
for (const finding of lintFindings) {
|
|
14016
|
-
|
|
14734
|
+
logger32.warn(`[agent-registry] ${filePath}: ${finding.message}`);
|
|
14017
14735
|
}
|
|
14018
14736
|
this.fileCache.set(filePath, { mtimeMs: file.mtimeMs, config, meta });
|
|
14019
14737
|
return config;
|
|
14020
14738
|
}
|
|
14021
14739
|
};
|
|
14022
14740
|
|
|
14023
|
-
// src/execution/config.ts
|
|
14024
|
-
var fs24 = __toESM(require("fs"), 1);
|
|
14025
|
-
var path18 = __toESM(require("path"), 1);
|
|
14026
|
-
var logger32 = getLogger("subagents");
|
|
14027
|
-
var DEFAULT_CONFIG = {
|
|
14028
|
-
version: 1,
|
|
14029
|
-
maxConcurrent: 6
|
|
14030
|
-
};
|
|
14031
|
-
var DEFAULT_MAX_CONCURRENT = 6;
|
|
14032
|
-
function getGlobalConfigPath(agentDir) {
|
|
14033
|
-
return path18.join(agentDir, "subagents", "config.json");
|
|
14034
|
-
}
|
|
14035
|
-
function loadGlobalConfig(agentDir) {
|
|
14036
|
-
try {
|
|
14037
|
-
const raw = fs24.readFileSync(getGlobalConfigPath(agentDir), "utf-8");
|
|
14038
|
-
const parsed = JSON.parse(raw);
|
|
14039
|
-
return sanitizeParsedConfig(parsed);
|
|
14040
|
-
} catch {
|
|
14041
|
-
return { ...DEFAULT_CONFIG };
|
|
14042
|
-
}
|
|
14043
|
-
}
|
|
14044
|
-
function readGlobalConfig(agentDir) {
|
|
14045
|
-
const configPath = getGlobalConfigPath(agentDir);
|
|
14046
|
-
let raw;
|
|
14047
|
-
try {
|
|
14048
|
-
raw = fs24.readFileSync(configPath, "utf-8");
|
|
14049
|
-
} catch (err) {
|
|
14050
|
-
if (errnoCodeOf(err) === "ENOENT") {
|
|
14051
|
-
return { status: "absent", config: { ...DEFAULT_CONFIG } };
|
|
14052
|
-
}
|
|
14053
|
-
return readFailure(configPath, err);
|
|
14054
|
-
}
|
|
14055
|
-
try {
|
|
14056
|
-
const parsed = JSON.parse(raw);
|
|
14057
|
-
return { status: "ok", config: sanitizeParsedConfig(parsed) };
|
|
14058
|
-
} catch (err) {
|
|
14059
|
-
return readFailure(configPath, err);
|
|
14060
|
-
}
|
|
14061
|
-
}
|
|
14062
|
-
function readFailure(configPath, err) {
|
|
14063
|
-
const reason = err instanceof Error ? err.message : String(err);
|
|
14064
|
-
logger32.warn(`[subagents] global config read failed (read-failure) at ${configPath}: ${reason}`);
|
|
14065
|
-
return { status: "failed", reason };
|
|
14066
|
-
}
|
|
14067
|
-
function errnoCodeOf(err) {
|
|
14068
|
-
if (typeof err !== "object" || err === null || !("code" in err)) return void 0;
|
|
14069
|
-
const code = Reflect.get(err, "code");
|
|
14070
|
-
return typeof code === "string" ? code : void 0;
|
|
14071
|
-
}
|
|
14072
|
-
function sanitizeParsedConfig(parsed) {
|
|
14073
|
-
const defaultEngine = sanitizeDefaultEngine(parsed.defaultEngine);
|
|
14074
|
-
const engineRouting = sanitizeEngineRouting(parsed.engineRouting);
|
|
14075
|
-
return {
|
|
14076
|
-
version: parsed.version ?? DEFAULT_CONFIG.version,
|
|
14077
|
-
maxConcurrent: sanitizeMaxConcurrent(parsed.maxConcurrent),
|
|
14078
|
-
...defaultEngine !== void 0 ? { defaultEngine } : {},
|
|
14079
|
-
...engineRouting !== void 0 ? { engineRouting } : {}
|
|
14080
|
-
};
|
|
14081
|
-
}
|
|
14082
|
-
function sanitizeMaxConcurrent(value) {
|
|
14083
|
-
return typeof value === "number" && Number.isInteger(value) && value > 0 ? value : DEFAULT_MAX_CONCURRENT;
|
|
14084
|
-
}
|
|
14085
|
-
function sanitizeDefaultEngine(value) {
|
|
14086
|
-
return typeof value === "string" && value.trim() !== "" ? value : void 0;
|
|
14087
|
-
}
|
|
14088
|
-
function sanitizeEngineRouting(value) {
|
|
14089
|
-
if (typeof value !== "object" || value === null) return void 0;
|
|
14090
|
-
const strict = value.strict;
|
|
14091
|
-
return typeof strict === "boolean" ? { strict } : void 0;
|
|
14092
|
-
}
|
|
14093
|
-
|
|
14094
14741
|
// src/execution/model-resolver.ts
|
|
14095
14742
|
var MODEL_LIST_LIMIT2 = 20;
|
|
14096
14743
|
function resolveModel(agentConfig, modelRegistry, paramOverride, ctxModel) {
|
|
@@ -15040,6 +15687,12 @@ async function startHandler(service, input, signal, ctxModel) {
|
|
|
15040
15687
|
`slug is required for action:'start' (top-level field, must not be whitespace-only). Correct: {"action":"start","task":"...","slug":"<kebab-case>"}`
|
|
15041
15688
|
);
|
|
15042
15689
|
if (slug.length > SLUG_MAX_LENGTH) throw new Error(`slug must be \u2264${SLUG_MAX_LENGTH} chars (got ${slug.length}). Shorten to a kebab-case label, e.g. "fix-login", "extract-urls".`);
|
|
15690
|
+
const resolvedCollect = input.collect ?? service.getCollectSyncDefault();
|
|
15691
|
+
if (input.conversation === true && resolvedCollect === "sync") {
|
|
15692
|
+
throw new Error(
|
|
15693
|
+
'collect:"sync" only supports one-shot subagents \u2014 it cannot be combined with conversation:true. Remove either conversation or collect (use collect:"async", or omit it, for conversational subagents).'
|
|
15694
|
+
);
|
|
15695
|
+
}
|
|
15043
15696
|
const handle = await service.execute({
|
|
15044
15697
|
task,
|
|
15045
15698
|
slug,
|
|
@@ -15057,10 +15710,25 @@ async function startHandler(service, input, signal, ctxModel) {
|
|
|
15057
15710
|
conversation: input.conversation,
|
|
15058
15711
|
idleTimeoutMs: input.idleTimeoutMs,
|
|
15059
15712
|
engine: input.engine,
|
|
15713
|
+
// B1(code-simplify 审查发现的行为缺口):config collectSync.default=sync 且调用方
|
|
15714
|
+
// 省略 collect 时,record 本体也要落 sync(设计 §3.1.3「缺省 = config 默认」作用于
|
|
15715
|
+
// record,而非仅回显)——createRecordForMode 只认 opts.collect==="sync",原样透传
|
|
15716
|
+
// input.collect 会让 record 走 async 逐条通知而响应声称已入批。仅 sync 落值:
|
|
15717
|
+
// async/缺省路径传 undefined 语义(旧 record 零迁移)字节不变。
|
|
15718
|
+
collect: resolvedCollect === "sync" ? "sync" : input.collect,
|
|
15060
15719
|
ctxModel,
|
|
15061
15720
|
signal
|
|
15062
15721
|
// background detached 运行,完成由 notify 驱动新 turn。
|
|
15063
15722
|
});
|
|
15723
|
+
const response = {
|
|
15724
|
+
status: "running",
|
|
15725
|
+
mode: "background",
|
|
15726
|
+
message: BG_MESSAGE,
|
|
15727
|
+
notifyContract: NOTIFY_CONTRACT
|
|
15728
|
+
};
|
|
15729
|
+
if (resolvedCollect === "sync") {
|
|
15730
|
+
response.collect = { mode: "sync", pendingSyncCount: countPendingSyncRecords(service) };
|
|
15731
|
+
}
|
|
15064
15732
|
return {
|
|
15065
15733
|
kind: "bg",
|
|
15066
15734
|
subagentId: handle.subagentId,
|
|
@@ -15068,14 +15736,16 @@ async function startHandler(service, input, signal, ctxModel) {
|
|
|
15068
15736
|
slug: handle.details.slug,
|
|
15069
15737
|
// registry 全等回显:record.model 由 resolved(裁决放行条目)拼接,原样透出。
|
|
15070
15738
|
model: handle.details.model,
|
|
15071
|
-
response
|
|
15072
|
-
status: "running",
|
|
15073
|
-
mode: "background",
|
|
15074
|
-
message: BG_MESSAGE,
|
|
15075
|
-
notifyContract: NOTIFY_CONTRACT
|
|
15076
|
-
}
|
|
15739
|
+
response
|
|
15077
15740
|
};
|
|
15078
15741
|
}
|
|
15742
|
+
function countPendingSyncRecords(service) {
|
|
15743
|
+
let count = 0;
|
|
15744
|
+
for (const r of service.queries.collectRecords(COLLECT_SCAN_LIMIT, "all")) {
|
|
15745
|
+
if (r.collectMode === "sync" && r.batchFinalized !== true) count += 1;
|
|
15746
|
+
}
|
|
15747
|
+
return count;
|
|
15748
|
+
}
|
|
15079
15749
|
function listHandler(service, input) {
|
|
15080
15750
|
const includeFinished = input?.includeFinished === true;
|
|
15081
15751
|
const rawLimit = input?.limit ?? DEFAULT_LIST_LIMIT;
|
|
@@ -18639,7 +19309,7 @@ function boundedPrettySerialize(value, budget) {
|
|
|
18639
19309
|
}
|
|
18640
19310
|
|
|
18641
19311
|
// src/index.ts
|
|
18642
|
-
var CORE_PACKAGE_VERSION = "0.
|
|
19312
|
+
var CORE_PACKAGE_VERSION = "0.6.0";
|
|
18643
19313
|
// Annotate the CommonJS export names for ESM import in node:
|
|
18644
19314
|
0 && (module.exports = {
|
|
18645
19315
|
AGENT_REF_EXT,
|