@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.js
CHANGED
|
@@ -674,8 +674,202 @@ function bestEffort(err, context, level = "debug") {
|
|
|
674
674
|
}
|
|
675
675
|
}
|
|
676
676
|
|
|
677
|
-
// src/execution/
|
|
677
|
+
// src/execution/collect-coordinator.ts
|
|
678
|
+
var COLLECT_SCAN_LIMIT = 1e3;
|
|
679
|
+
var CollectCoordinator = class {
|
|
680
|
+
constructor(deps) {
|
|
681
|
+
this.deps = deps;
|
|
682
|
+
}
|
|
683
|
+
deps;
|
|
684
|
+
/** 批缓冲(内存):已终态未 flush 的 sync 成员终态快照,跨 route 累积。
|
|
685
|
+
* [U8] 排程合批窗口期内保持可枚举(E9 dispose 转换依赖 pendingMembers 全量)。 */
|
|
686
|
+
buffer = [];
|
|
687
|
+
/** [U8] 挂起的合批排程定时器(undefined = 无排程)。 */
|
|
688
|
+
flushTimer;
|
|
689
|
+
/** notifyComplete 唯一路由入口。返回去向供测试/诊断断言。 */
|
|
690
|
+
route(record) {
|
|
691
|
+
if (record.collectMode !== "sync") {
|
|
692
|
+
this.deps.notifyAsync(record);
|
|
693
|
+
return "async";
|
|
694
|
+
}
|
|
695
|
+
const snapshotRecord = this.deps.toNotifyRecord(record);
|
|
696
|
+
if (!snapshotRecord) return "sync-skipped";
|
|
697
|
+
this.buffer.push(snapshotRecord);
|
|
698
|
+
return this.armFlushIfClosed() ? "sync-flushed" : "sync-buffered";
|
|
699
|
+
}
|
|
700
|
+
/** 诊断/测试:当前缓冲成员数(= 已终态未 flush 的 sync 成员数,含合批窗口内成员)。 */
|
|
701
|
+
get pendingCount() {
|
|
702
|
+
return this.buffer.length;
|
|
703
|
+
}
|
|
704
|
+
/** 诊断/测试 + E9 dispose 转换数据源:缓冲成员快照(副本,非活引用)。 */
|
|
705
|
+
pendingMembers() {
|
|
706
|
+
return [...this.buffer];
|
|
707
|
+
}
|
|
708
|
+
/** [U8·E9 交互] 取消挂起的合批排程(缓冲原样保留——dispose 转 async 通路仍读得到
|
|
709
|
+
* 全部成员)。无排程时 no-op;幂等。 */
|
|
710
|
+
cancelScheduledFlush() {
|
|
711
|
+
if (this.flushTimer !== void 0) {
|
|
712
|
+
clearTimeout(this.flushTimer);
|
|
713
|
+
this.flushTimer = void 0;
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
/** 闭合判定 + 合批排程武装:缓冲非空 && 无非终态 sync 成员 → 排程 flush。
|
|
717
|
+
* 已有排程挂起时幂等(同窗口合批,不重复定时器)。(S6 改名:原名 closeDetected
|
|
718
|
+
* 读作纯检测,实带副作用——武装排程改状态,名实相符优先。) */
|
|
719
|
+
armFlushIfClosed() {
|
|
720
|
+
if (this.buffer.length === 0) return false;
|
|
721
|
+
if (this.hasRunningSync()) return false;
|
|
722
|
+
this.scheduleFlush();
|
|
723
|
+
return true;
|
|
724
|
+
}
|
|
725
|
+
/** [U8] 同宏任务去抖合批排程:setTimeout(0)(Node 1ms clamp)——窗口跨度覆盖一个
|
|
726
|
+
* 完整事件循环轮次(当前 poll 段 I/O 回调 + 微任务链排空),足以收纳 finalize 链
|
|
727
|
+
* 间隙内的背靠背 route,同时无可观察延迟(毫秒级 < 任意 LLM/投递时标)。 */
|
|
728
|
+
scheduleFlush() {
|
|
729
|
+
if (this.flushTimer !== void 0) return;
|
|
730
|
+
this.flushTimer = setTimeout(() => {
|
|
731
|
+
this.flushTimer = void 0;
|
|
732
|
+
this.flushIfClosed();
|
|
733
|
+
}, 0);
|
|
734
|
+
}
|
|
735
|
+
/** 合批窗口到期:重验闭合条件后整体移交 flushBatch 并清空。
|
|
736
|
+
* 窗口内新 sync 成员注册 running(跨轮续累反转)→ 保留缓冲不 flush,等其终态
|
|
737
|
+
* route 重新排程(终态必经 notifyComplete,不丢、不悬挂)。
|
|
738
|
+
* void 不等待 flush 的 async 屏障序列(manifest → 写账 → 落标):缓冲已 splice
|
|
739
|
+
* 先行整体移交,屏障 await 期间窗口内新成员重新入空缓冲开新批,互不干扰。 */
|
|
740
|
+
flushIfClosed() {
|
|
741
|
+
if (this.buffer.length === 0) return;
|
|
742
|
+
if (this.hasRunningSync()) return;
|
|
743
|
+
const members = this.buffer.splice(0, this.buffer.length);
|
|
744
|
+
void this.deps.flushBatch(members);
|
|
745
|
+
}
|
|
746
|
+
/** 是否存在非终态 sync 成员(collectMode=sync && 无 batchFinalized && 非终态)。
|
|
747
|
+
* 非终态口径(⛔3 U1 已核实 + U3 resumable 补丁):status 非 closed 且非 resumable
|
|
748
|
+
* ——池排队/在跑成员在 store.register 时即 status="running",自动计入(闭合等待它);
|
|
749
|
+
* batchFinalized=true 的已离场成员不阻止闭合;running+resumable(SP-5 one-shot
|
|
750
|
+
* 成功回退态,进程已死结果已定格)视为已完成,不阻止闭合。 */
|
|
751
|
+
hasRunningSync() {
|
|
752
|
+
for (const record of this.deps.listRecords(COLLECT_SCAN_LIMIT)) {
|
|
753
|
+
if (record.collectMode === "sync" && record.batchFinalized !== true && record.resumable !== true && record.status !== "closed") {
|
|
754
|
+
return true;
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
return false;
|
|
758
|
+
}
|
|
759
|
+
};
|
|
760
|
+
|
|
761
|
+
// src/execution/config.ts
|
|
762
|
+
import * as fs3 from "fs";
|
|
763
|
+
import * as path3 from "path";
|
|
678
764
|
var logger4 = getLogger("subagents");
|
|
765
|
+
var DEFAULT_CONFIG = {
|
|
766
|
+
version: 1,
|
|
767
|
+
maxConcurrent: 6
|
|
768
|
+
};
|
|
769
|
+
var DEFAULT_MAX_CONCURRENT = 6;
|
|
770
|
+
var DEFAULT_COLLECT_SYNC = {
|
|
771
|
+
default: "async",
|
|
772
|
+
perItemChars: 4e3,
|
|
773
|
+
totalChars: 24e3
|
|
774
|
+
};
|
|
775
|
+
function getGlobalConfigPath(agentDir) {
|
|
776
|
+
return path3.join(agentDir, "subagents", "config.json");
|
|
777
|
+
}
|
|
778
|
+
function loadGlobalConfig(agentDir) {
|
|
779
|
+
try {
|
|
780
|
+
const raw = fs3.readFileSync(getGlobalConfigPath(agentDir), "utf-8");
|
|
781
|
+
const parsed = JSON.parse(raw);
|
|
782
|
+
return sanitizeParsedConfig(parsed);
|
|
783
|
+
} catch {
|
|
784
|
+
return { ...DEFAULT_CONFIG };
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
function readGlobalConfig(agentDir) {
|
|
788
|
+
const configPath = getGlobalConfigPath(agentDir);
|
|
789
|
+
let raw;
|
|
790
|
+
try {
|
|
791
|
+
raw = fs3.readFileSync(configPath, "utf-8");
|
|
792
|
+
} catch (err) {
|
|
793
|
+
if (errnoCodeOf(err) === "ENOENT") {
|
|
794
|
+
return { status: "absent", config: { ...DEFAULT_CONFIG } };
|
|
795
|
+
}
|
|
796
|
+
return readFailure(configPath, err);
|
|
797
|
+
}
|
|
798
|
+
try {
|
|
799
|
+
const parsed = JSON.parse(raw);
|
|
800
|
+
return { status: "ok", config: sanitizeParsedConfig(parsed) };
|
|
801
|
+
} catch (err) {
|
|
802
|
+
return readFailure(configPath, err);
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
function readFailure(configPath, err) {
|
|
806
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
807
|
+
logger4.warn(`[subagents] global config read failed (read-failure) at ${configPath}: ${reason}`);
|
|
808
|
+
return { status: "failed", reason };
|
|
809
|
+
}
|
|
810
|
+
function errnoCodeOf(err) {
|
|
811
|
+
if (typeof err !== "object" || err === null || !("code" in err)) return void 0;
|
|
812
|
+
const code = Reflect.get(err, "code");
|
|
813
|
+
return typeof code === "string" ? code : void 0;
|
|
814
|
+
}
|
|
815
|
+
function sanitizeParsedConfig(parsed) {
|
|
816
|
+
const defaultEngine = sanitizeDefaultEngine(parsed.defaultEngine);
|
|
817
|
+
const engineRouting = sanitizeEngineRouting(parsed.engineRouting);
|
|
818
|
+
const collectSync = sanitizeCollectSync(parsed.collectSync);
|
|
819
|
+
return {
|
|
820
|
+
version: parsed.version ?? DEFAULT_CONFIG.version,
|
|
821
|
+
maxConcurrent: sanitizeMaxConcurrent(parsed.maxConcurrent),
|
|
822
|
+
...defaultEngine !== void 0 ? { defaultEngine } : {},
|
|
823
|
+
...engineRouting !== void 0 ? { engineRouting } : {},
|
|
824
|
+
...collectSync !== void 0 ? { collectSync } : {}
|
|
825
|
+
};
|
|
826
|
+
}
|
|
827
|
+
function sanitizeMaxConcurrent(value) {
|
|
828
|
+
return sanitizePositiveInt(value, DEFAULT_MAX_CONCURRENT);
|
|
829
|
+
}
|
|
830
|
+
function sanitizeCollectSync(value) {
|
|
831
|
+
if (typeof value !== "object" || value === null) return void 0;
|
|
832
|
+
const v = value;
|
|
833
|
+
const fallback = DEFAULT_COLLECT_SYNC;
|
|
834
|
+
const fmt = (val) => typeof val === "string" ? JSON.stringify(val) : String(val);
|
|
835
|
+
const bad = [];
|
|
836
|
+
if (v.default !== void 0 && v.default !== "sync" && v.default !== "async") {
|
|
837
|
+
bad.push(`default=${fmt(v.default)}`);
|
|
838
|
+
}
|
|
839
|
+
if (v.perItemChars !== void 0 && !isPositiveInt(v.perItemChars)) {
|
|
840
|
+
bad.push(`perItemChars=${fmt(v.perItemChars)}`);
|
|
841
|
+
}
|
|
842
|
+
if (v.totalChars !== void 0 && !isPositiveInt(v.totalChars)) {
|
|
843
|
+
bad.push(`totalChars=${fmt(v.totalChars)}`);
|
|
844
|
+
}
|
|
845
|
+
if (bad.length > 0) {
|
|
846
|
+
logger4.warn(
|
|
847
|
+
`[subagents] config collectSync invalid field(s) reverted to defaults: ${bad.join(", ")}`
|
|
848
|
+
);
|
|
849
|
+
}
|
|
850
|
+
return {
|
|
851
|
+
default: v.default === "sync" ? "sync" : v.default === "async" ? "async" : fallback.default,
|
|
852
|
+
perItemChars: sanitizePositiveInt(v.perItemChars, fallback.perItemChars),
|
|
853
|
+
totalChars: sanitizePositiveInt(v.totalChars, fallback.totalChars)
|
|
854
|
+
};
|
|
855
|
+
}
|
|
856
|
+
function sanitizePositiveInt(value, fallback) {
|
|
857
|
+
return isPositiveInt(value) ? value : fallback;
|
|
858
|
+
}
|
|
859
|
+
function isPositiveInt(value) {
|
|
860
|
+
return typeof value === "number" && Number.isInteger(value) && value > 0;
|
|
861
|
+
}
|
|
862
|
+
function sanitizeDefaultEngine(value) {
|
|
863
|
+
return typeof value === "string" && value.trim() !== "" ? value : void 0;
|
|
864
|
+
}
|
|
865
|
+
function sanitizeEngineRouting(value) {
|
|
866
|
+
if (typeof value !== "object" || value === null) return void 0;
|
|
867
|
+
const strict = value.strict;
|
|
868
|
+
return typeof strict === "boolean" ? { strict } : void 0;
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
// src/execution/lifecycle-manager.ts
|
|
872
|
+
var logger5 = getLogger("subagents");
|
|
679
873
|
var MS_PER_SECOND = 1e3;
|
|
680
874
|
var SECONDS_PER_MINUTE = 60;
|
|
681
875
|
var IDLE_TIMEOUT_MINUTES = 5;
|
|
@@ -685,7 +879,7 @@ function getEnvIdleTimeoutMs() {
|
|
|
685
879
|
if (!raw) return void 0;
|
|
686
880
|
const parsed = Number(raw);
|
|
687
881
|
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
688
|
-
|
|
882
|
+
logger5.warn(
|
|
689
883
|
`[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`
|
|
690
884
|
);
|
|
691
885
|
return void 0;
|
|
@@ -845,7 +1039,7 @@ function createConcurrencyPool(options) {
|
|
|
845
1039
|
}
|
|
846
1040
|
|
|
847
1041
|
// src/execution/cold-resurrect.ts
|
|
848
|
-
import * as
|
|
1042
|
+
import * as fs4 from "fs";
|
|
849
1043
|
|
|
850
1044
|
// src/execution/execution-record.ts
|
|
851
1045
|
var ACTIVITY_LABEL_MAX = 60;
|
|
@@ -920,6 +1114,7 @@ function createRecord(id, identity) {
|
|
|
920
1114
|
idleTimeoutMs: identity.idleTimeoutMs,
|
|
921
1115
|
engine: identity.engine,
|
|
922
1116
|
engineFallback: identity.engineFallback,
|
|
1117
|
+
collectMode: identity.collectMode,
|
|
923
1118
|
// 状态(实时更新)
|
|
924
1119
|
status: "running",
|
|
925
1120
|
// turns[] 初始化为 [空 turn]——第一个 turn 从创建即存在,
|
|
@@ -1350,7 +1545,7 @@ function resurrectColdRecord(deps, found, id) {
|
|
|
1350
1545
|
if (wasClosed) {
|
|
1351
1546
|
if (record.sessionFile) {
|
|
1352
1547
|
try {
|
|
1353
|
-
|
|
1548
|
+
fs4.rmSync(`${record.sessionFile}.finalized`, { force: true });
|
|
1354
1549
|
writeAliveMarker(record.sessionFile, { pid: process.pid, id, startedAt: Date.now() });
|
|
1355
1550
|
} catch (_e) {
|
|
1356
1551
|
void _e;
|
|
@@ -1381,41 +1576,41 @@ function coldLookupForAction(deps, id, allowReconnect) {
|
|
|
1381
1576
|
}
|
|
1382
1577
|
|
|
1383
1578
|
// src/execution/finalize-record.ts
|
|
1384
|
-
import * as
|
|
1385
|
-
import * as
|
|
1579
|
+
import * as fs8 from "fs";
|
|
1580
|
+
import * as path5 from "path";
|
|
1386
1581
|
|
|
1387
1582
|
// src/execution/finalized-marker.ts
|
|
1388
|
-
import * as
|
|
1583
|
+
import * as fs5 from "fs";
|
|
1389
1584
|
function writeFinalized(sessionFile, reason) {
|
|
1390
1585
|
try {
|
|
1391
|
-
|
|
1392
|
-
|
|
1586
|
+
fs5.rmSync(`${sessionFile}.cancelled`, { force: true });
|
|
1587
|
+
fs5.writeFileSync(`${sessionFile}.finalized`, reason ?? "", "utf-8");
|
|
1393
1588
|
} catch (_e) {
|
|
1394
1589
|
void _e;
|
|
1395
1590
|
}
|
|
1396
1591
|
}
|
|
1397
1592
|
function readFinalizedReason(sessionFile) {
|
|
1398
1593
|
try {
|
|
1399
|
-
return
|
|
1594
|
+
return fs5.readFileSync(`${sessionFile}.finalized`, "utf-8").trim();
|
|
1400
1595
|
} catch {
|
|
1401
1596
|
return void 0;
|
|
1402
1597
|
}
|
|
1403
1598
|
}
|
|
1404
1599
|
|
|
1405
1600
|
// src/execution/path-encoding.ts
|
|
1406
|
-
import * as
|
|
1601
|
+
import * as path4 from "path";
|
|
1407
1602
|
function encodeCwd(cwd) {
|
|
1408
1603
|
return "--" + cwd.replace(/^[/\\]/, "").replace(/[/\\:]/g, "-") + "--";
|
|
1409
1604
|
}
|
|
1410
1605
|
function getSubagentSessionDir(agentDir, mainCwd) {
|
|
1411
|
-
return
|
|
1606
|
+
return path4.join(agentDir, "subagents", encodeCwd(mainCwd), "sessions");
|
|
1412
1607
|
}
|
|
1413
1608
|
function getSubagentRecordsDir(agentDir, mainCwd) {
|
|
1414
|
-
return
|
|
1609
|
+
return path4.join(agentDir, "subagents", encodeCwd(mainCwd), "records");
|
|
1415
1610
|
}
|
|
1416
1611
|
|
|
1417
1612
|
// src/execution/session-reconstructor.ts
|
|
1418
|
-
import * as
|
|
1613
|
+
import * as fs6 from "fs";
|
|
1419
1614
|
var IDENTITY_CUSTOM_TYPE = "subagent-identity";
|
|
1420
1615
|
var TURN_SUMMARY_MAX2 = 80;
|
|
1421
1616
|
function deriveEventLog(turns, lastError, startedAt) {
|
|
@@ -1469,7 +1664,7 @@ function isIdentityData(data) {
|
|
|
1469
1664
|
function readJsonlEntries(sessionFile) {
|
|
1470
1665
|
let raw;
|
|
1471
1666
|
try {
|
|
1472
|
-
raw =
|
|
1667
|
+
raw = fs6.readFileSync(sessionFile, "utf-8");
|
|
1473
1668
|
} catch {
|
|
1474
1669
|
return void 0;
|
|
1475
1670
|
}
|
|
@@ -1639,18 +1834,18 @@ var IDENTITY_HEAD_BYTES = 65536;
|
|
|
1639
1834
|
function readIdentityHeader(sessionFile) {
|
|
1640
1835
|
let text;
|
|
1641
1836
|
try {
|
|
1642
|
-
const fd =
|
|
1837
|
+
const fd = fs6.openSync(sessionFile, "r");
|
|
1643
1838
|
try {
|
|
1644
1839
|
const buf = Buffer.alloc(IDENTITY_HEAD_BYTES);
|
|
1645
1840
|
let total = 0;
|
|
1646
1841
|
while (total < buf.length) {
|
|
1647
|
-
const n =
|
|
1842
|
+
const n = fs6.readSync(fd, buf, total, buf.length - total, total);
|
|
1648
1843
|
if (n <= 0) break;
|
|
1649
1844
|
total += n;
|
|
1650
1845
|
}
|
|
1651
1846
|
text = buf.toString("utf-8", 0, total);
|
|
1652
1847
|
} finally {
|
|
1653
|
-
|
|
1848
|
+
fs6.closeSync(fd);
|
|
1654
1849
|
}
|
|
1655
1850
|
} catch {
|
|
1656
1851
|
return void 0;
|
|
@@ -1660,7 +1855,7 @@ function readIdentityHeader(sessionFile) {
|
|
|
1660
1855
|
function readIdentityAnywhere(sessionFile) {
|
|
1661
1856
|
let text;
|
|
1662
1857
|
try {
|
|
1663
|
-
text =
|
|
1858
|
+
text = fs6.readFileSync(sessionFile, "utf-8");
|
|
1664
1859
|
} catch {
|
|
1665
1860
|
return void 0;
|
|
1666
1861
|
}
|
|
@@ -1676,20 +1871,20 @@ function readIdentityAnywhere(sessionFile) {
|
|
|
1676
1871
|
function readIdentityTail(sessionFile) {
|
|
1677
1872
|
let text;
|
|
1678
1873
|
try {
|
|
1679
|
-
const { size } =
|
|
1874
|
+
const { size } = fs6.statSync(sessionFile);
|
|
1680
1875
|
const start = Math.max(0, size - IDENTITY_HEAD_BYTES);
|
|
1681
|
-
const fd =
|
|
1876
|
+
const fd = fs6.openSync(sessionFile, "r");
|
|
1682
1877
|
try {
|
|
1683
1878
|
const buf = Buffer.alloc(size - start);
|
|
1684
1879
|
let total = 0;
|
|
1685
1880
|
while (total < buf.length) {
|
|
1686
|
-
const n =
|
|
1881
|
+
const n = fs6.readSync(fd, buf, total, buf.length - total, start + total);
|
|
1687
1882
|
if (n <= 0) break;
|
|
1688
1883
|
total += n;
|
|
1689
1884
|
}
|
|
1690
1885
|
text = buf.toString("utf-8", 0, total);
|
|
1691
1886
|
} finally {
|
|
1692
|
-
|
|
1887
|
+
fs6.closeSync(fd);
|
|
1693
1888
|
}
|
|
1694
1889
|
} catch {
|
|
1695
1890
|
return void 0;
|
|
@@ -1764,11 +1959,11 @@ function parseIdentityFromText(text, sessionFile) {
|
|
|
1764
1959
|
}
|
|
1765
1960
|
|
|
1766
1961
|
// src/execution/tombstone-store.ts
|
|
1767
|
-
import * as
|
|
1962
|
+
import * as fs7 from "fs";
|
|
1768
1963
|
function writeCancelledTombstone(sessionFile, meta) {
|
|
1769
1964
|
try {
|
|
1770
1965
|
const tombstonePath = `${sessionFile}.cancelled`;
|
|
1771
|
-
|
|
1966
|
+
fs7.writeFileSync(tombstonePath, `${JSON.stringify(meta)}
|
|
1772
1967
|
`, "utf-8");
|
|
1773
1968
|
} catch (_e) {
|
|
1774
1969
|
void _e;
|
|
@@ -1777,7 +1972,7 @@ function writeCancelledTombstone(sessionFile, meta) {
|
|
|
1777
1972
|
function readCancelledTombstone(sessionFile) {
|
|
1778
1973
|
let raw;
|
|
1779
1974
|
try {
|
|
1780
|
-
raw =
|
|
1975
|
+
raw = fs7.readFileSync(`${sessionFile}.cancelled`, "utf-8");
|
|
1781
1976
|
} catch {
|
|
1782
1977
|
return void 0;
|
|
1783
1978
|
}
|
|
@@ -1793,17 +1988,17 @@ function readCancelledTombstone(sessionFile) {
|
|
|
1793
1988
|
}
|
|
1794
1989
|
|
|
1795
1990
|
// src/execution/finalize-record.ts
|
|
1796
|
-
var
|
|
1991
|
+
var logger6 = getLogger("subagents");
|
|
1797
1992
|
function findSessionFileByRecordIdentity(sessionDir, recordId) {
|
|
1798
1993
|
let names;
|
|
1799
1994
|
try {
|
|
1800
|
-
names =
|
|
1995
|
+
names = fs8.readdirSync(sessionDir);
|
|
1801
1996
|
} catch {
|
|
1802
1997
|
return void 0;
|
|
1803
1998
|
}
|
|
1804
1999
|
for (const name of names) {
|
|
1805
2000
|
if (!name.endsWith(".jsonl")) continue;
|
|
1806
|
-
const full =
|
|
2001
|
+
const full = path5.join(sessionDir, name);
|
|
1807
2002
|
const recon = readIdentityHeader(full) ?? readIdentityTail(full);
|
|
1808
2003
|
if (recon?.id === recordId) return full;
|
|
1809
2004
|
}
|
|
@@ -1814,7 +2009,7 @@ function resolveMissingSessionFile(deps, record) {
|
|
|
1814
2009
|
const resolved = findSessionFileByRecordIdentity(deps.sessionDir, record.id);
|
|
1815
2010
|
if (resolved) {
|
|
1816
2011
|
record.sessionFile = resolved;
|
|
1817
|
-
|
|
2012
|
+
logger6.warn(
|
|
1818
2013
|
`[subagent] finalizeRecord: sessionFile was missing, resolved via sessionDir identity lookup: ${resolved}`
|
|
1819
2014
|
);
|
|
1820
2015
|
}
|
|
@@ -1826,8 +2021,8 @@ async function collectPatchIfWorktree(deps, record) {
|
|
|
1826
2021
|
deps.modelService.getAgentDir(),
|
|
1827
2022
|
record.worktreeHandle.mainCwd
|
|
1828
2023
|
);
|
|
1829
|
-
|
|
1830
|
-
const patchFile =
|
|
2024
|
+
fs8.mkdirSync(sessionsDir, { recursive: true });
|
|
2025
|
+
const patchFile = path5.join(sessionsDir, `${record.worktreeHandle.branch}.patch`);
|
|
1831
2026
|
const patch = await deps.worktreeManager.collectPatch(record.worktreeHandle, patchFile);
|
|
1832
2027
|
if (patch.written) record.patchFile = patchFile;
|
|
1833
2028
|
} catch (pe) {
|
|
@@ -1886,7 +2081,7 @@ async function writeManifestBestEffort(deps, record) {
|
|
|
1886
2081
|
});
|
|
1887
2082
|
} catch (err) {
|
|
1888
2083
|
const msg = err instanceof Error ? err.message : String(err);
|
|
1889
|
-
|
|
2084
|
+
logger6.error(`[subagent] manifest \u5199\u5165\u5931\u8D25 (record=${record.id}): ${msg}`);
|
|
1890
2085
|
deps.pi?.appendEntry?.("subagent:manifest-write-failed", {
|
|
1891
2086
|
id: record.id,
|
|
1892
2087
|
error: msg
|
|
@@ -2018,7 +2213,7 @@ var ExecutionNestingContext = class {
|
|
|
2018
2213
|
};
|
|
2019
2214
|
|
|
2020
2215
|
// src/execution/engine/common/data-dir.ts
|
|
2021
|
-
var
|
|
2216
|
+
var logger7 = getLogger("subagents");
|
|
2022
2217
|
var XYZ_DATA_DIR_ENV = "XYZ_AGENT_DATA_DIR";
|
|
2023
2218
|
var warned = false;
|
|
2024
2219
|
function getEngineDataDir(env = process.env, warn = defaultWarn) {
|
|
@@ -2034,14 +2229,14 @@ function getEngineDataDir(env = process.env, warn = defaultWarn) {
|
|
|
2034
2229
|
return fallback;
|
|
2035
2230
|
}
|
|
2036
2231
|
function defaultWarn(msg) {
|
|
2037
|
-
|
|
2232
|
+
logger7.warn(msg);
|
|
2038
2233
|
}
|
|
2039
2234
|
|
|
2040
2235
|
// src/execution/engine/common/event-journal.ts
|
|
2041
2236
|
import { appendFile, mkdir, open as open2 } from "fs/promises";
|
|
2042
|
-
import { readFileSync as
|
|
2237
|
+
import { readFileSync as readFileSync7 } from "fs";
|
|
2043
2238
|
import { dirname as dirname2 } from "path";
|
|
2044
|
-
var
|
|
2239
|
+
var logger8 = getLogger("subagents");
|
|
2045
2240
|
var FLUSH_THRESHOLD_LINES = 64;
|
|
2046
2241
|
var FLUSH_THRESHOLD_BYTES = 32 * 1024;
|
|
2047
2242
|
var defaultFs = {
|
|
@@ -2150,12 +2345,12 @@ var JournalWriter = class {
|
|
|
2150
2345
|
}
|
|
2151
2346
|
};
|
|
2152
2347
|
function defaultWarn2(msg) {
|
|
2153
|
-
|
|
2348
|
+
logger8.warn(msg);
|
|
2154
2349
|
}
|
|
2155
2350
|
function replayJournal(path21) {
|
|
2156
2351
|
let raw;
|
|
2157
2352
|
try {
|
|
2158
|
-
raw =
|
|
2353
|
+
raw = readFileSync7(path21, "utf8");
|
|
2159
2354
|
} catch {
|
|
2160
2355
|
return [];
|
|
2161
2356
|
}
|
|
@@ -2246,8 +2441,8 @@ function executeOptionsToEngineTaskSpec(opts) {
|
|
|
2246
2441
|
|
|
2247
2442
|
// src/execution/engine/engines/pi/pi-engine.ts
|
|
2248
2443
|
import { execFile as execFile2 } from "child_process";
|
|
2249
|
-
import * as
|
|
2250
|
-
import * as
|
|
2444
|
+
import * as fs14 from "fs";
|
|
2445
|
+
import * as path9 from "path";
|
|
2251
2446
|
|
|
2252
2447
|
// src/orchestration/models/types.ts
|
|
2253
2448
|
var VALID_RUN_TRANSITIONS = {
|
|
@@ -2281,11 +2476,11 @@ function stringifySchemaCached(schema, mode) {
|
|
|
2281
2476
|
}
|
|
2282
2477
|
|
|
2283
2478
|
// src/execution/engine/engines/pi/pi-invocation.ts
|
|
2284
|
-
import * as
|
|
2285
|
-
import * as
|
|
2479
|
+
import * as fs9 from "fs";
|
|
2480
|
+
import * as path6 from "path";
|
|
2286
2481
|
var BUN_VIRTUAL_PREFIX = "/$bunfs/root/";
|
|
2287
2482
|
function isGenericRuntime(execPath) {
|
|
2288
|
-
const execName =
|
|
2483
|
+
const execName = path6.basename(execPath).toLowerCase();
|
|
2289
2484
|
return /^(node|bun)(\.exe)?$/.test(execName);
|
|
2290
2485
|
}
|
|
2291
2486
|
var scriptExistsCache;
|
|
@@ -2294,7 +2489,7 @@ function currentScriptExists() {
|
|
|
2294
2489
|
if (scriptExistsCache === void 0 || scriptExistsCache.script !== currentScript) {
|
|
2295
2490
|
scriptExistsCache = {
|
|
2296
2491
|
script: currentScript,
|
|
2297
|
-
exists: currentScript !== void 0 && !currentScript.startsWith(BUN_VIRTUAL_PREFIX) &&
|
|
2492
|
+
exists: currentScript !== void 0 && !currentScript.startsWith(BUN_VIRTUAL_PREFIX) && fs9.existsSync(currentScript)
|
|
2298
2493
|
};
|
|
2299
2494
|
}
|
|
2300
2495
|
return scriptExistsCache.exists;
|
|
@@ -2405,11 +2600,11 @@ import {
|
|
|
2405
2600
|
spawn,
|
|
2406
2601
|
spawnSync
|
|
2407
2602
|
} from "child_process";
|
|
2408
|
-
import * as
|
|
2603
|
+
import * as fs13 from "fs";
|
|
2409
2604
|
|
|
2410
2605
|
// src/execution/session-pending.ts
|
|
2411
|
-
import * as
|
|
2412
|
-
var
|
|
2606
|
+
import * as fs10 from "fs";
|
|
2607
|
+
var logger9 = getLogger("subagents");
|
|
2413
2608
|
var RECENT_UNREGISTER_WINDOW_MS = 6e4;
|
|
2414
2609
|
var cursors = /* @__PURE__ */ new Map();
|
|
2415
2610
|
function prunePendingCursor(sessionFile) {
|
|
@@ -2447,7 +2642,7 @@ function pendingFileUnreadableMessage(err) {
|
|
|
2447
2642
|
}
|
|
2448
2643
|
function statPendingFileSize(sessionFile) {
|
|
2449
2644
|
try {
|
|
2450
|
-
return
|
|
2645
|
+
return fs10.statSync(sessionFile).size;
|
|
2451
2646
|
} catch (err) {
|
|
2452
2647
|
cursors.delete(sessionFile);
|
|
2453
2648
|
return { error: pendingFileUnreadableMessage(err) };
|
|
@@ -2456,7 +2651,7 @@ function statPendingFileSize(sessionFile) {
|
|
|
2456
2651
|
function readPendingChunk(sessionFile, cursor, size) {
|
|
2457
2652
|
try {
|
|
2458
2653
|
if (cursor.offset === 0) {
|
|
2459
|
-
return
|
|
2654
|
+
return fs10.readFileSync(sessionFile, "utf-8");
|
|
2460
2655
|
}
|
|
2461
2656
|
return readPendingChunkFrom(sessionFile, cursor.offset, size - cursor.offset);
|
|
2462
2657
|
} catch (err) {
|
|
@@ -2466,17 +2661,17 @@ function readPendingChunk(sessionFile, cursor, size) {
|
|
|
2466
2661
|
}
|
|
2467
2662
|
function readPendingChunkFrom(sessionFile, offset, len) {
|
|
2468
2663
|
const buf = Buffer.alloc(len);
|
|
2469
|
-
const fd =
|
|
2664
|
+
const fd = fs10.openSync(sessionFile, "r");
|
|
2470
2665
|
try {
|
|
2471
2666
|
let total = 0;
|
|
2472
2667
|
while (total < len) {
|
|
2473
|
-
const n =
|
|
2668
|
+
const n = fs10.readSync(fd, buf, total, len - total, offset + total);
|
|
2474
2669
|
if (n <= 0) break;
|
|
2475
2670
|
total += n;
|
|
2476
2671
|
}
|
|
2477
2672
|
return buf.toString("utf-8", 0, total);
|
|
2478
2673
|
} finally {
|
|
2479
|
-
|
|
2674
|
+
fs10.closeSync(fd);
|
|
2480
2675
|
}
|
|
2481
2676
|
}
|
|
2482
2677
|
function consumePendingLines(complete, cursor, sessionFile) {
|
|
@@ -2501,7 +2696,7 @@ function applyPendingLine(line, cursor, sessionFile) {
|
|
|
2501
2696
|
if (Number.isFinite(ts) && ts > cursor.latestUnregisterMs) cursor.latestUnregisterMs = ts;
|
|
2502
2697
|
}
|
|
2503
2698
|
} catch {
|
|
2504
|
-
|
|
2699
|
+
logger9.debug("skipped malformed pending line", { sessionFile });
|
|
2505
2700
|
}
|
|
2506
2701
|
}
|
|
2507
2702
|
function extractPendingEntryId(data) {
|
|
@@ -2542,7 +2737,7 @@ function listActivePendingFromSessionFile(sessionFile) {
|
|
|
2542
2737
|
}
|
|
2543
2738
|
|
|
2544
2739
|
// src/execution/engine/common/kill-chain.ts
|
|
2545
|
-
var
|
|
2740
|
+
var logger10 = getLogger("subagents");
|
|
2546
2741
|
var MS_PER_SECOND3 = 1e3;
|
|
2547
2742
|
var SIGKILL_REAP_TIMEOUT_MS = 1e4;
|
|
2548
2743
|
var HOST_TIMEOUT_ABORT_REASON = "agent-call-timeout";
|
|
@@ -2554,7 +2749,7 @@ async function killChain(child, opts) {
|
|
|
2554
2749
|
if (graceful === "settled") return "terminated";
|
|
2555
2750
|
if (child.exitCode !== null || child.signalCode !== null) return "terminated";
|
|
2556
2751
|
if (opts.escalationNote !== void 0) {
|
|
2557
|
-
|
|
2752
|
+
logger10.warn(
|
|
2558
2753
|
`[kill-chain] ${opts.escalationNote} still alive ${opts.graceMs / MS_PER_SECOND3}s after SIGTERM, escalating to SIGKILL`
|
|
2559
2754
|
);
|
|
2560
2755
|
}
|
|
@@ -2566,7 +2761,7 @@ function safeKill(child, signal) {
|
|
|
2566
2761
|
try {
|
|
2567
2762
|
child.kill(signal);
|
|
2568
2763
|
} catch (err) {
|
|
2569
|
-
|
|
2764
|
+
logger10.debug(
|
|
2570
2765
|
`[kill-chain] ${signal} on exited process: ${toErrorMessage(err)}`
|
|
2571
2766
|
);
|
|
2572
2767
|
}
|
|
@@ -2688,7 +2883,7 @@ function parseMirrorFlagArgs(flagArgs) {
|
|
|
2688
2883
|
|
|
2689
2884
|
// src/execution/engine/engines/pi/stdin-writer.ts
|
|
2690
2885
|
import * as crypto2 from "crypto";
|
|
2691
|
-
var
|
|
2886
|
+
var logger11 = getLogger("subagents");
|
|
2692
2887
|
var epipeConsecutiveFailures = /* @__PURE__ */ new Map();
|
|
2693
2888
|
var EPIPE_FAILURE_THRESHOLD = 2;
|
|
2694
2889
|
function recordEpipeFailure(recordId) {
|
|
@@ -2710,7 +2905,7 @@ function respond(child, id, out, signal) {
|
|
|
2710
2905
|
else if ("confirmed" in out) line = JSON.stringify({ type: "extension_ui_response", id, confirmed: out.confirmed });
|
|
2711
2906
|
else if ("cancelled" in out) line = JSON.stringify({ type: "extension_ui_response", id, cancelled: true });
|
|
2712
2907
|
} catch (err) {
|
|
2713
|
-
|
|
2908
|
+
logger11.warn(`[subagents] JSON.stringify failed for ui response ${id}, degrading to cancelled`, {
|
|
2714
2909
|
detail: toErrorMessage(err)
|
|
2715
2910
|
});
|
|
2716
2911
|
line = JSON.stringify({ type: "extension_ui_response", id, cancelled: true });
|
|
@@ -2743,14 +2938,14 @@ function writeStdinLine(child, line, warnTag) {
|
|
|
2743
2938
|
if (!child.stdin || child.stdin.destroyed) return;
|
|
2744
2939
|
try {
|
|
2745
2940
|
const ok = child.stdin.write(line + "\n");
|
|
2746
|
-
if (!ok)
|
|
2941
|
+
if (!ok) logger11.warn(`[subagents] stdin backpressure on ${warnTag}`);
|
|
2747
2942
|
} catch (err) {
|
|
2748
2943
|
if (err !== null && typeof err === "object" && "code" in err && (err.code === "EPIPE" || err.code === "ERR_STREAM_DESTROYED")) {
|
|
2749
2944
|
throw new Error(
|
|
2750
2945
|
`[subagents] EPIPE on stdin write (${warnTag}): pipe broken, child process likely exited. Recovery: treat as dead process and resume via cold path.`
|
|
2751
2946
|
);
|
|
2752
2947
|
}
|
|
2753
|
-
|
|
2948
|
+
logger11.warn(`[subagents] unexpected stdin write error on ${warnTag}`, {
|
|
2754
2949
|
detail: toErrorMessage(err)
|
|
2755
2950
|
});
|
|
2756
2951
|
}
|
|
@@ -3059,7 +3254,7 @@ function schemaEnvByteLength(schemaEnv) {
|
|
|
3059
3254
|
}
|
|
3060
3255
|
|
|
3061
3256
|
// src/execution/settled-watchdog.ts
|
|
3062
|
-
var
|
|
3257
|
+
var logger12 = getLogger("subagents");
|
|
3063
3258
|
var MS_PER_SECOND4 = 1e3;
|
|
3064
3259
|
var SECONDS_PER_MINUTE2 = 60;
|
|
3065
3260
|
var MID_ROUND_MINUTES = 30;
|
|
@@ -3075,13 +3270,13 @@ function resolveSettledWatchdogEnv() {
|
|
|
3075
3270
|
if (raw === void 0 || raw.trim() === "") return envCache;
|
|
3076
3271
|
const parsed = Number(raw);
|
|
3077
3272
|
if (!Number.isFinite(parsed)) {
|
|
3078
|
-
|
|
3273
|
+
logger12.warn(
|
|
3079
3274
|
`[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`
|
|
3080
3275
|
);
|
|
3081
3276
|
return envCache;
|
|
3082
3277
|
}
|
|
3083
3278
|
if (parsed <= 0) {
|
|
3084
|
-
|
|
3279
|
+
logger12.warn(
|
|
3085
3280
|
`[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.`
|
|
3086
3281
|
);
|
|
3087
3282
|
envCache.disabled = true;
|
|
@@ -3108,7 +3303,7 @@ function armMidRoundNoProgress(recordId, handlers) {
|
|
|
3108
3303
|
assertSafeTimerDelay(SETTLED_MID_ROUND_NO_PROGRESS_MS, "settled watchdog (mid-round)");
|
|
3109
3304
|
const timer = setTimeout(() => {
|
|
3110
3305
|
armedEntries.delete(recordId);
|
|
3111
|
-
|
|
3306
|
+
logger12.debug(
|
|
3112
3307
|
`[settled-watchdog] mid-round fired for ${recordId} after ${SETTLED_MID_ROUND_NO_PROGRESS_MS}ms without a valid protocol event`
|
|
3113
3308
|
);
|
|
3114
3309
|
handlers.onMidTimeout({ phase: "mid-round", waitedMs: SETTLED_MID_ROUND_NO_PROGRESS_MS });
|
|
@@ -3128,7 +3323,7 @@ function armSettledWatchdog(recordId, onTimeout) {
|
|
|
3128
3323
|
assertSafeTimerDelay(windowMs, "settled watchdog");
|
|
3129
3324
|
const timer = setTimeout(() => {
|
|
3130
3325
|
armedEntries.delete(recordId);
|
|
3131
|
-
|
|
3326
|
+
logger12.debug(
|
|
3132
3327
|
`[settled-watchdog] settled phase fired for ${recordId} after ${windowMs}ms without agent_settled`
|
|
3133
3328
|
);
|
|
3134
3329
|
onTimeout({ phase: "settled", waitedMs: windowMs });
|
|
@@ -3151,7 +3346,7 @@ function refreshMidRoundNoProgress(recordId) {
|
|
|
3151
3346
|
const onMidTimeout = entry.onMidTimeout;
|
|
3152
3347
|
const timer = setTimeout(() => {
|
|
3153
3348
|
armedEntries.delete(recordId);
|
|
3154
|
-
|
|
3349
|
+
logger12.debug(
|
|
3155
3350
|
`[settled-watchdog] mid-round fired for ${recordId} after ${SETTLED_MID_ROUND_NO_PROGRESS_MS}ms without a valid protocol event`
|
|
3156
3351
|
);
|
|
3157
3352
|
onMidTimeout?.({ phase: "mid-round", waitedMs: SETTLED_MID_ROUND_NO_PROGRESS_MS });
|
|
@@ -3167,8 +3362,8 @@ function disarmSettledWatchdog(recordId) {
|
|
|
3167
3362
|
var MAX_FORK_DEPTH = 10;
|
|
3168
3363
|
|
|
3169
3364
|
// src/execution/engine/engines/pi/spawn-event-adapter.ts
|
|
3170
|
-
import * as
|
|
3171
|
-
import * as
|
|
3365
|
+
import * as fs11 from "fs";
|
|
3366
|
+
import * as path7 from "path";
|
|
3172
3367
|
function isSessionHeader(obj) {
|
|
3173
3368
|
if (typeof obj !== "object" || obj === null) return false;
|
|
3174
3369
|
const r = obj;
|
|
@@ -3316,28 +3511,28 @@ function deriveSessionFilePath(header, sessionDir) {
|
|
|
3316
3511
|
}
|
|
3317
3512
|
function findSessionFileByHeaderId(sessionDir, sessionId) {
|
|
3318
3513
|
try {
|
|
3319
|
-
const files =
|
|
3514
|
+
const files = fs11.readdirSync(sessionDir);
|
|
3320
3515
|
const match = files.find((f) => f.endsWith(`_${sessionId}.jsonl`));
|
|
3321
|
-
return match ?
|
|
3516
|
+
return match ? path7.join(sessionDir, match) : void 0;
|
|
3322
3517
|
} catch {
|
|
3323
3518
|
return void 0;
|
|
3324
3519
|
}
|
|
3325
3520
|
}
|
|
3326
3521
|
|
|
3327
3522
|
// src/execution/engine/engines/pi/temp-prompt.ts
|
|
3328
|
-
import * as
|
|
3523
|
+
import * as fs12 from "fs";
|
|
3329
3524
|
import * as os from "os";
|
|
3330
|
-
import * as
|
|
3525
|
+
import * as path8 from "path";
|
|
3331
3526
|
async function writePromptToTempFile(agentName, prompt) {
|
|
3332
|
-
const dir = await
|
|
3527
|
+
const dir = await fs12.promises.mkdtemp(path8.join(os.tmpdir(), "pi-subagent-"));
|
|
3333
3528
|
const safeName = agentName.replace(/[^\w.-]+/g, "_");
|
|
3334
|
-
const filePath =
|
|
3335
|
-
await
|
|
3529
|
+
const filePath = path8.join(dir, `prompt-${safeName}.md`);
|
|
3530
|
+
await fs12.promises.writeFile(filePath, prompt, { encoding: "utf-8", mode: 384 });
|
|
3336
3531
|
return { dir, filePath };
|
|
3337
3532
|
}
|
|
3338
3533
|
async function cleanupTempPrompt(file) {
|
|
3339
3534
|
try {
|
|
3340
|
-
await
|
|
3535
|
+
await fs12.promises.rm(file.dir, { recursive: true, force: true });
|
|
3341
3536
|
} catch (err) {
|
|
3342
3537
|
bestEffort(err, `cleanup temp prompt dir ${file.dir}`);
|
|
3343
3538
|
}
|
|
@@ -3482,7 +3677,7 @@ function createUiChannelRegistry() {
|
|
|
3482
3677
|
}
|
|
3483
3678
|
|
|
3484
3679
|
// src/execution/ui-request-observability.ts
|
|
3485
|
-
var
|
|
3680
|
+
var logger13 = getLogger("subagents");
|
|
3486
3681
|
var GLOBAL_OBSERVABILITY_KEY = /* @__PURE__ */ Symbol.for("pi-subagent-workflow.ui-observability");
|
|
3487
3682
|
function registerGlobalObservability(obs) {
|
|
3488
3683
|
globalThis[GLOBAL_OBSERVABILITY_KEY] = obs;
|
|
@@ -3492,7 +3687,7 @@ function notifyMissingHandlerGlobal(sessionId) {
|
|
|
3492
3687
|
if (obs) {
|
|
3493
3688
|
obs.notifyMissingHandler(sessionId);
|
|
3494
3689
|
} else {
|
|
3495
|
-
|
|
3690
|
+
logger13.warn(
|
|
3496
3691
|
`[subagents] uiRequestHandler missing (session=${sessionId}, global observability not registered)`
|
|
3497
3692
|
);
|
|
3498
3693
|
}
|
|
@@ -3520,12 +3715,12 @@ var UiRequestObservability = class {
|
|
|
3520
3715
|
this.warnedMissingHandlerSessions.clear();
|
|
3521
3716
|
}
|
|
3522
3717
|
this.warnedMissingHandlerSessions.add(sessionId);
|
|
3523
|
-
|
|
3718
|
+
logger13.warn(`[subagents] uiRequestHandler missing (session=${sessionId}, mode=${this.sessionMode})`);
|
|
3524
3719
|
}
|
|
3525
3720
|
};
|
|
3526
3721
|
|
|
3527
3722
|
// src/execution/ui-request-queue.ts
|
|
3528
|
-
var
|
|
3723
|
+
var logger14 = getLogger("subagents");
|
|
3529
3724
|
function createUiRequestQueue(child, ctx) {
|
|
3530
3725
|
const abortController = new AbortController();
|
|
3531
3726
|
const queue = [];
|
|
@@ -3537,7 +3732,7 @@ function createUiRequestQueue(child, ctx) {
|
|
|
3537
3732
|
const { id, request, signal } = queue.shift();
|
|
3538
3733
|
handleUiRequest(child, id, request, ctx, signal).catch((err) => {
|
|
3539
3734
|
const m = toErrorMessage(err);
|
|
3540
|
-
|
|
3735
|
+
logger14.error(`[subagents] ui request ${id} (${request.method}) failed unexpectedly: ${m}`);
|
|
3541
3736
|
}).finally(() => {
|
|
3542
3737
|
processing = false;
|
|
3543
3738
|
processNext();
|
|
@@ -3582,7 +3777,7 @@ async function handleUiRequest(child, id, request, ctx, signal) {
|
|
|
3582
3777
|
respond(child, id, result, signal);
|
|
3583
3778
|
} catch (err) {
|
|
3584
3779
|
if (signal?.aborted) return;
|
|
3585
|
-
|
|
3780
|
+
logger14.error("[subagents] uiRequestHandler threw", {
|
|
3586
3781
|
detail: toErrorMessage(err)
|
|
3587
3782
|
});
|
|
3588
3783
|
respond(child, id, { cancelled: true }, signal);
|
|
@@ -3625,7 +3820,7 @@ function extractMethodFields(req) {
|
|
|
3625
3820
|
}
|
|
3626
3821
|
|
|
3627
3822
|
// src/execution/engine/engines/pi/session-runner.ts
|
|
3628
|
-
var
|
|
3823
|
+
var logger15 = getLogger("subagents");
|
|
3629
3824
|
function isSdkEvent(x) {
|
|
3630
3825
|
if (typeof x !== "object" || x === null) return false;
|
|
3631
3826
|
if (!("type" in x)) return false;
|
|
@@ -3678,7 +3873,7 @@ function getEnvSpawnWatchdogMs() {
|
|
|
3678
3873
|
if (!raw) return void 0;
|
|
3679
3874
|
const parsed = Number(raw);
|
|
3680
3875
|
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
3681
|
-
|
|
3876
|
+
logger15.warn(
|
|
3682
3877
|
`[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`
|
|
3683
3878
|
);
|
|
3684
3879
|
return void 0;
|
|
@@ -3745,7 +3940,7 @@ function killAllSpawnedChildren(signal = "SIGTERM") {
|
|
|
3745
3940
|
child.kill(child.killed ? "SIGKILL" : signal);
|
|
3746
3941
|
n++;
|
|
3747
3942
|
} catch (err) {
|
|
3748
|
-
|
|
3943
|
+
logger15.debug(
|
|
3749
3944
|
`[session-runner] killAllSpawnedChildren: kill failed (best-effort continue): ${toErrorMessage(err)}`
|
|
3750
3945
|
);
|
|
3751
3946
|
}
|
|
@@ -3789,7 +3984,7 @@ function killPidWithEscalation(pid, label) {
|
|
|
3789
3984
|
try {
|
|
3790
3985
|
process.kill(pid, "SIGTERM");
|
|
3791
3986
|
} catch (err) {
|
|
3792
|
-
|
|
3987
|
+
logger15.debug(
|
|
3793
3988
|
`[session-runner] ${label}: SIGTERM to pid ${pid} failed (best-effort continue): ${toErrorMessage(err)}`
|
|
3794
3989
|
);
|
|
3795
3990
|
return;
|
|
@@ -3797,7 +3992,7 @@ function killPidWithEscalation(pid, label) {
|
|
|
3797
3992
|
assertSafeTimerDelay(SIGKILL_ESCALATION_MS, `descendant SIGKILL escalation (${label})`);
|
|
3798
3993
|
const escalation = setTimeout(() => {
|
|
3799
3994
|
if (isProcessAlive(pid)) {
|
|
3800
|
-
|
|
3995
|
+
logger15.warn(
|
|
3801
3996
|
`[session-runner] ${label}: descendant pid ${pid} still alive ${SIGKILL_ESCALATION_MS / MS_PER_SECOND5}s after SIGTERM, escalating to SIGKILL`
|
|
3802
3997
|
);
|
|
3803
3998
|
try {
|
|
@@ -3820,7 +4015,7 @@ function sweepDescendantsOfSession(rootSessionFile, sessionDir, source) {
|
|
|
3820
4015
|
visited.add(sessionFile);
|
|
3821
4016
|
const list = listActivePendingFromSessionFile(sessionFile);
|
|
3822
4017
|
if (list.error) {
|
|
3823
|
-
|
|
4018
|
+
logger15.debug(
|
|
3824
4019
|
`[session-runner] descendant sweep (${source}): pending list unreadable for ${sessionFile}: ${list.error}`
|
|
3825
4020
|
);
|
|
3826
4021
|
continue;
|
|
@@ -3864,7 +4059,7 @@ function sweepDescendantsOfSession(rootSessionFile, sessionDir, source) {
|
|
|
3864
4059
|
});
|
|
3865
4060
|
continue;
|
|
3866
4061
|
}
|
|
3867
|
-
|
|
4062
|
+
logger15.warn(
|
|
3868
4063
|
`[session-runner] descendant sweep (${source}): killing orphan descendant pid=${marker.pid} session=${item.sessionId} (${item.id})`
|
|
3869
4064
|
);
|
|
3870
4065
|
killPidWithEscalation(marker.pid, `descendant sweep (${source})`);
|
|
@@ -3927,7 +4122,7 @@ async function buildEnvBlock(cwd, forkDepth, nestingDepth) {
|
|
|
3927
4122
|
);
|
|
3928
4123
|
});
|
|
3929
4124
|
} catch (err) {
|
|
3930
|
-
|
|
4125
|
+
logger15.debug(
|
|
3931
4126
|
`[session-runner] buildEnvBlock: git branch lookup failed for ${cwd}, fallback to empty: ${toErrorMessage(err)}`
|
|
3932
4127
|
);
|
|
3933
4128
|
branch = "";
|
|
@@ -3978,7 +4173,7 @@ function writeAliveMarkerBestEffort(sessionFile, pid, id) {
|
|
|
3978
4173
|
try {
|
|
3979
4174
|
writeAliveMarker(sessionFile, { pid, id, startedAt: Date.now() });
|
|
3980
4175
|
} catch (err) {
|
|
3981
|
-
|
|
4176
|
+
logger15.debug(
|
|
3982
4177
|
`[session-runner] alive marker write failed (best-effort continue): ${toErrorMessage(err)}`
|
|
3983
4178
|
);
|
|
3984
4179
|
}
|
|
@@ -4014,7 +4209,7 @@ function killRecordChildWithEscalation(recordId, source) {
|
|
|
4014
4209
|
const escalation = setTimeout(
|
|
4015
4210
|
() => {
|
|
4016
4211
|
if (child.exitCode === null && child.signalCode === null) {
|
|
4017
|
-
|
|
4212
|
+
logger15.warn(
|
|
4018
4213
|
`[session-runner] child ${recordId} still alive ${SIGKILL_ESCALATION_MS / MS_PER_SECOND5}s after SIGTERM, escalating to SIGKILL (source: ${source})`
|
|
4019
4214
|
);
|
|
4020
4215
|
child.kill("SIGKILL");
|
|
@@ -4034,7 +4229,7 @@ function killRecordChildWithEscalation(recordId, source) {
|
|
|
4034
4229
|
function hasLiveActiveDescendant(sessionFile, sessionDir) {
|
|
4035
4230
|
const list = listActivePendingFromSessionFile(sessionFile);
|
|
4036
4231
|
if (list.error) {
|
|
4037
|
-
|
|
4232
|
+
logger15.warn(
|
|
4038
4233
|
`[session-runner] keep-alive no-progress re-check failed (treating as no live descendants): ${list.error}`
|
|
4039
4234
|
);
|
|
4040
4235
|
return false;
|
|
@@ -4054,13 +4249,13 @@ function armKeepAliveNoProgressTimer(state, child, sessionDir) {
|
|
|
4054
4249
|
state.keepAliveNoProgressTimer = setTimeout(() => {
|
|
4055
4250
|
if (hasLiveActiveDescendant(state.record.sessionFile, sessionDir)) {
|
|
4056
4251
|
touchAliveMarkerForHeartbeat(state.record.sessionFile, child.pid, state.record.id);
|
|
4057
|
-
|
|
4252
|
+
logger15.debug(
|
|
4058
4253
|
`[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)`
|
|
4059
4254
|
);
|
|
4060
4255
|
armKeepAliveNoProgressTimer(state, child, sessionDir);
|
|
4061
4256
|
return;
|
|
4062
4257
|
}
|
|
4063
|
-
|
|
4258
|
+
logger15.warn(
|
|
4064
4259
|
`[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`
|
|
4065
4260
|
);
|
|
4066
4261
|
state.sweepDescendantsOnClose = true;
|
|
@@ -4121,7 +4316,7 @@ function createSpawnEventHandlers(state) {
|
|
|
4121
4316
|
bestEffort(err, "armIdleTimer (agent_settled chatMode)", "error");
|
|
4122
4317
|
try {
|
|
4123
4318
|
armIdleTimer(record.id, armIdleTimerOnTimeout, DEFAULT_IDLE_TIMEOUT_MS);
|
|
4124
|
-
|
|
4319
|
+
logger15.warn(
|
|
4125
4320
|
`[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`
|
|
4126
4321
|
);
|
|
4127
4322
|
} catch (fallbackErr) {
|
|
@@ -4283,7 +4478,7 @@ async function backfillSessionFileViaGetState(state, child, registerGetStateList
|
|
|
4283
4478
|
if (child.pid) {
|
|
4284
4479
|
writeAliveMarkerBestEffort(r.sessionFile, child.pid, r.sessionId ?? record.id);
|
|
4285
4480
|
}
|
|
4286
|
-
|
|
4481
|
+
logger15.warn(
|
|
4287
4482
|
`[session-runner] agent_end: sessionFile backfilled via lazy get_state (spawn handshake had failed): ${r.sessionFile}`
|
|
4288
4483
|
);
|
|
4289
4484
|
}
|
|
@@ -4295,11 +4490,11 @@ function keepAliveOnAgentEnd(state, child, sessionDir, pending) {
|
|
|
4295
4490
|
const { record, opts } = state;
|
|
4296
4491
|
touchAliveMarkerForHeartbeat(record.sessionFile, child.pid, record.id);
|
|
4297
4492
|
if (pending.error) {
|
|
4298
|
-
|
|
4493
|
+
logger15.warn(
|
|
4299
4494
|
`[session-runner] agent_end: keep alive (sessionFile unreadable, conservative): ${pending.error}`
|
|
4300
4495
|
);
|
|
4301
4496
|
} else {
|
|
4302
|
-
|
|
4497
|
+
logger15.debug(
|
|
4303
4498
|
`[session-runner] agent_end: keep alive, ${pending.count} active descendant(s) pending`
|
|
4304
4499
|
);
|
|
4305
4500
|
}
|
|
@@ -4326,7 +4521,7 @@ function keepAliveOnAgentEnd(state, child, sessionDir, pending) {
|
|
|
4326
4521
|
function keepAliveForWakeupGrace(state, child) {
|
|
4327
4522
|
const { record } = state;
|
|
4328
4523
|
touchAliveMarkerForHeartbeat(record.sessionFile, child.pid, record.id);
|
|
4329
|
-
|
|
4524
|
+
logger15.debug(
|
|
4330
4525
|
"[session-runner] agent_end: keep alive, recent descendant completion (wake-up in flight)"
|
|
4331
4526
|
);
|
|
4332
4527
|
clearTimeout(state.watchdog);
|
|
@@ -4351,7 +4546,7 @@ function attachStdoutPump(child, state, sessionDir, handleSdkEvent) {
|
|
|
4351
4546
|
invalidLineSamples.push(truncated);
|
|
4352
4547
|
}
|
|
4353
4548
|
if (invalidLineCount <= MAX_INVALID_LINE_SAMPLES) {
|
|
4354
|
-
|
|
4549
|
+
logger15.debug(
|
|
4355
4550
|
`[session-runner] stdout invalid line #${invalidLineCount} dropped (${reason}): ${truncated}`
|
|
4356
4551
|
);
|
|
4357
4552
|
}
|
|
@@ -4461,7 +4656,7 @@ function attachStdoutPump(child, state, sessionDir, handleSdkEvent) {
|
|
|
4461
4656
|
}
|
|
4462
4657
|
}
|
|
4463
4658
|
if (invalidLineCount > 0) {
|
|
4464
|
-
|
|
4659
|
+
logger15.debug(
|
|
4465
4660
|
`[session-runner] stdout had ${invalidLineCount} invalid line(s) dropped in total; sample(s): ${invalidLineSamples.join(" | ")}`
|
|
4466
4661
|
);
|
|
4467
4662
|
}
|
|
@@ -4508,7 +4703,7 @@ function setupFreshChild(state, child, task) {
|
|
|
4508
4703
|
try {
|
|
4509
4704
|
void ctx.onWorktreePid?.(opts.worktree.branch, child.pid);
|
|
4510
4705
|
} catch (err) {
|
|
4511
|
-
|
|
4706
|
+
logger15.warn("[worktree] worktree pid registration failed (defensive)", {
|
|
4512
4707
|
branch: opts.worktree.branch,
|
|
4513
4708
|
pid: child.pid,
|
|
4514
4709
|
err: toErrorMessage(err)
|
|
@@ -4520,7 +4715,7 @@ function setupFreshChild(state, child, task) {
|
|
|
4520
4715
|
child.stdin.on("error", (err) => {
|
|
4521
4716
|
removeChildRegistration(record.id, child);
|
|
4522
4717
|
const count = recordEpipeFailure(record.id);
|
|
4523
|
-
|
|
4718
|
+
logger15.warn(`[subagents] async stdin error for ${record.id}`, {
|
|
4524
4719
|
detail: err.message,
|
|
4525
4720
|
epipeCount: count,
|
|
4526
4721
|
threshold: EPIPE_FAILURE_THRESHOLD,
|
|
@@ -4534,14 +4729,14 @@ function armChatModeSettledWatchdog(state, child) {
|
|
|
4534
4729
|
if (record.chatMode) {
|
|
4535
4730
|
armMidRoundNoProgress(record.id, {
|
|
4536
4731
|
onMidTimeout: (fire) => {
|
|
4537
|
-
|
|
4732
|
+
logger15.warn(
|
|
4538
4733
|
`[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)`
|
|
4539
4734
|
);
|
|
4540
4735
|
state.settledWatchdogFired = fire;
|
|
4541
4736
|
killChildWithEscalation(state, child, "settled watchdog");
|
|
4542
4737
|
},
|
|
4543
4738
|
onSettleTimeout: (fire) => {
|
|
4544
|
-
|
|
4739
|
+
logger15.warn(
|
|
4545
4740
|
`[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)`
|
|
4546
4741
|
);
|
|
4547
4742
|
state.settledWatchdogFired = fire;
|
|
@@ -4572,14 +4767,14 @@ function startGetStateHandshake(child, pump) {
|
|
|
4572
4767
|
if (pump.isHandshakePending()) pump.finishHandshake(r);
|
|
4573
4768
|
}).catch((err) => {
|
|
4574
4769
|
const m = toErrorMessage(err);
|
|
4575
|
-
|
|
4770
|
+
logger15.error(`[session-runner] get_state handshake failed: ${m}`);
|
|
4576
4771
|
pump.abandonHandshake();
|
|
4577
4772
|
});
|
|
4578
4773
|
}
|
|
4579
4774
|
function backfillSessionFileByLookup(state, sessionDir) {
|
|
4580
4775
|
const { record } = state;
|
|
4581
4776
|
const lookupId = state.sessionHeader?.id ?? state.handshakeResult?.sessionId;
|
|
4582
|
-
const needsLookup = record.sessionFile ? !
|
|
4777
|
+
const needsLookup = record.sessionFile ? !fs13.existsSync(record.sessionFile) : lookupId !== void 0;
|
|
4583
4778
|
if (lookupId && needsLookup) {
|
|
4584
4779
|
const actual = findSessionFileByHeaderId(sessionDir, lookupId);
|
|
4585
4780
|
if (actual && actual !== record.sessionFile) record.sessionFile = actual;
|
|
@@ -4591,7 +4786,7 @@ function sweepDescendantsOnChildClose(state, sessionDir) {
|
|
|
4591
4786
|
try {
|
|
4592
4787
|
const sweep = sweepDescendantsOfSession(record.sessionFile, sessionDir, "keep-alive watchdog");
|
|
4593
4788
|
if (sweep.killed.length > 0 || sweep.skipped.length > 0) {
|
|
4594
|
-
|
|
4789
|
+
logger15.warn(
|
|
4595
4790
|
`[session-runner] descendant sweep (keep-alive watchdog): killed=[${sweep.killed.join(", ")}] skipped=${JSON.stringify(sweep.skipped)}`
|
|
4596
4791
|
);
|
|
4597
4792
|
}
|
|
@@ -4644,7 +4839,7 @@ async function runSpawn(record, task, opts, ctx, resume) {
|
|
|
4644
4839
|
};
|
|
4645
4840
|
const handleSdkEvent = createSpawnEventHandlers(state);
|
|
4646
4841
|
const sessionDir = getSubagentSessionDir(ctx.agentDir, ctx.rootCwd);
|
|
4647
|
-
|
|
4842
|
+
fs13.mkdirSync(sessionDir, { recursive: true });
|
|
4648
4843
|
const spawnCwd = opts.worktree?.path ?? ctx.cwd;
|
|
4649
4844
|
const forkSource = opts.forkSource ?? (opts.fork ? ctx.mainSessionFile : void 0);
|
|
4650
4845
|
const tempPromptFile = await writeAppendSystemPromptFile(record, opts, ctx);
|
|
@@ -4691,7 +4886,7 @@ async function runSpawn(record, task, opts, ctx, resume) {
|
|
|
4691
4886
|
}
|
|
4692
4887
|
|
|
4693
4888
|
// src/execution/engine/engines/pi/pi-engine.ts
|
|
4694
|
-
var
|
|
4889
|
+
var logger16 = getLogger("subagents");
|
|
4695
4890
|
var PI_ENGINE_ID2 = "pi";
|
|
4696
4891
|
var PI_ADAPTER_VERSION = "1.0.0";
|
|
4697
4892
|
var PI_POOL_KEY = "shared";
|
|
@@ -4871,7 +5066,7 @@ var PiEngine = class {
|
|
|
4871
5066
|
sendPromptCommand(child, text, { streamingBehavior: interrupt ? "steer" : "followUp" });
|
|
4872
5067
|
clearEpipeFailure(record.id);
|
|
4873
5068
|
if (child.exitCode !== null || child.signalCode !== null) {
|
|
4874
|
-
|
|
5069
|
+
logger16.warn(
|
|
4875
5070
|
`[subagents] deliverMessage: child ${record.id} died around stdin write, message may be lost`,
|
|
4876
5071
|
{
|
|
4877
5072
|
msgType: interrupt ? "steer" : "followUp",
|
|
@@ -4885,7 +5080,7 @@ var PiEngine = class {
|
|
|
4885
5080
|
service.reportRecordTransition?.(record);
|
|
4886
5081
|
} catch (err) {
|
|
4887
5082
|
if (err instanceof Error && err.message.includes("EPIPE")) {
|
|
4888
|
-
|
|
5083
|
+
logger16.warn(`[subagents] EPIPE on hot path for ${record.id}, falling back to cold path resume`, {
|
|
4889
5084
|
detail: err.message
|
|
4890
5085
|
});
|
|
4891
5086
|
if (spawnedChildren.get(record.id) === child) {
|
|
@@ -4936,7 +5131,7 @@ var PiEngine = class {
|
|
|
4936
5131
|
return;
|
|
4937
5132
|
}
|
|
4938
5133
|
}
|
|
4939
|
-
|
|
5134
|
+
logger16.warn(
|
|
4940
5135
|
`[subagents] deliverPrompt hot path failed for ${record.id}; idle timer re-armed to keep process recovery bounded`,
|
|
4941
5136
|
{ detail }
|
|
4942
5137
|
);
|
|
@@ -5085,7 +5280,7 @@ async function defaultProbeVersion(invocation) {
|
|
|
5085
5280
|
);
|
|
5086
5281
|
});
|
|
5087
5282
|
} catch (err) {
|
|
5088
|
-
|
|
5283
|
+
logger16.debug(
|
|
5089
5284
|
`[pi-engine] probe version check failed (best-effort continue): ${toErrorMessage(err)}`
|
|
5090
5285
|
);
|
|
5091
5286
|
return void 0;
|
|
@@ -5094,12 +5289,12 @@ async function defaultProbeVersion(invocation) {
|
|
|
5094
5289
|
function isInvocationResolvable(invocation) {
|
|
5095
5290
|
if (invocation.command !== "pi") return true;
|
|
5096
5291
|
const pathEnv = process.env.PATH ?? "";
|
|
5097
|
-
for (const dir of pathEnv.split(
|
|
5292
|
+
for (const dir of pathEnv.split(path9.delimiter)) {
|
|
5098
5293
|
if (dir === "") continue;
|
|
5099
5294
|
try {
|
|
5100
|
-
if (
|
|
5295
|
+
if (fs14.existsSync(path9.join(dir, "pi"))) return true;
|
|
5101
5296
|
} catch (err) {
|
|
5102
|
-
|
|
5297
|
+
logger16.debug(
|
|
5103
5298
|
`[pi-engine] PATH dir probe failed (continue scanning): ${dir}: ${toErrorMessage(err)}`
|
|
5104
5299
|
);
|
|
5105
5300
|
}
|
|
@@ -5228,14 +5423,14 @@ function safeEngineDefault(engine) {
|
|
|
5228
5423
|
}
|
|
5229
5424
|
|
|
5230
5425
|
// src/execution/manifest-store.ts
|
|
5231
|
-
import * as
|
|
5426
|
+
import * as fs15 from "fs";
|
|
5232
5427
|
import * as fsPromises2 from "fs/promises";
|
|
5233
|
-
import * as
|
|
5234
|
-
var
|
|
5428
|
+
import * as path10 from "path";
|
|
5429
|
+
var logger17 = getLogger("subagents");
|
|
5235
5430
|
var MANIFEST_INDENT_SPACES = 2;
|
|
5236
5431
|
function statStamp(p) {
|
|
5237
5432
|
try {
|
|
5238
|
-
const s =
|
|
5433
|
+
const s = fs15.statSync(p);
|
|
5239
5434
|
return { mtimeMs: s.mtimeMs, size: s.size };
|
|
5240
5435
|
} catch {
|
|
5241
5436
|
return null;
|
|
@@ -5263,8 +5458,8 @@ var ManifestStore = class {
|
|
|
5263
5458
|
cache = /* @__PURE__ */ new Map();
|
|
5264
5459
|
constructor(dir) {
|
|
5265
5460
|
this.dir = dir;
|
|
5266
|
-
if (!
|
|
5267
|
-
|
|
5461
|
+
if (!fs15.existsSync(dir)) {
|
|
5462
|
+
fs15.mkdirSync(dir, { recursive: true });
|
|
5268
5463
|
}
|
|
5269
5464
|
}
|
|
5270
5465
|
/**
|
|
@@ -5276,7 +5471,7 @@ var ManifestStore = class {
|
|
|
5276
5471
|
* 调用方(finalizeRecord)决定降级策略。
|
|
5277
5472
|
*/
|
|
5278
5473
|
async writeManifest(record) {
|
|
5279
|
-
const filePath =
|
|
5474
|
+
const filePath = path10.join(this.dir, `${record.id}.json`);
|
|
5280
5475
|
const content = JSON.stringify(record, null, MANIFEST_INDENT_SPACES);
|
|
5281
5476
|
await writeAtomicFile(filePath, content, { ensureDir: false });
|
|
5282
5477
|
}
|
|
@@ -5285,7 +5480,7 @@ var ManifestStore = class {
|
|
|
5285
5480
|
* 调用方需处理 null。
|
|
5286
5481
|
*/
|
|
5287
5482
|
async readManifest(id) {
|
|
5288
|
-
const filePath =
|
|
5483
|
+
const filePath = path10.join(this.dir, `${id}.json`);
|
|
5289
5484
|
try {
|
|
5290
5485
|
const content = await fsPromises2.readFile(filePath, "utf-8");
|
|
5291
5486
|
const parsed = JSON.parse(content);
|
|
@@ -5305,7 +5500,7 @@ var ManifestStore = class {
|
|
|
5305
5500
|
listAllSync() {
|
|
5306
5501
|
let files;
|
|
5307
5502
|
try {
|
|
5308
|
-
files =
|
|
5503
|
+
files = fs15.readdirSync(this.dir);
|
|
5309
5504
|
} catch {
|
|
5310
5505
|
return [];
|
|
5311
5506
|
}
|
|
@@ -5316,7 +5511,7 @@ var ManifestStore = class {
|
|
|
5316
5511
|
}
|
|
5317
5512
|
const results = [];
|
|
5318
5513
|
for (const file of names) {
|
|
5319
|
-
const filePath =
|
|
5514
|
+
const filePath = path10.join(this.dir, file);
|
|
5320
5515
|
const stamp = statStamp(filePath);
|
|
5321
5516
|
if (!stamp) {
|
|
5322
5517
|
this.cache.delete(file);
|
|
@@ -5328,7 +5523,7 @@ var ManifestStore = class {
|
|
|
5328
5523
|
continue;
|
|
5329
5524
|
}
|
|
5330
5525
|
try {
|
|
5331
|
-
const content =
|
|
5526
|
+
const content = fs15.readFileSync(filePath, "utf-8");
|
|
5332
5527
|
const parsed = JSON.parse(content);
|
|
5333
5528
|
const record = isValidManifest(parsed) ? parsed : null;
|
|
5334
5529
|
this.cache.set(file, { stamp, record });
|
|
@@ -5356,41 +5551,41 @@ var ManifestStore = class {
|
|
|
5356
5551
|
let deleted = 0;
|
|
5357
5552
|
let recovered = 0;
|
|
5358
5553
|
let failed = 0;
|
|
5359
|
-
const files =
|
|
5554
|
+
const files = fs15.readdirSync(this.dir);
|
|
5360
5555
|
const tmpFiles = files.filter((f) => f.includes(".json.tmp."));
|
|
5361
5556
|
for (const tmpFile of tmpFiles) {
|
|
5362
|
-
const tmpPath =
|
|
5557
|
+
const tmpPath = path10.join(this.dir, tmpFile);
|
|
5363
5558
|
const manifestId = tmpFile.split(".json.tmp.")[0];
|
|
5364
|
-
const manifestPath =
|
|
5559
|
+
const manifestPath = path10.join(this.dir, `${manifestId}.json`);
|
|
5365
5560
|
try {
|
|
5366
|
-
if (
|
|
5367
|
-
|
|
5561
|
+
if (fs15.existsSync(manifestPath)) {
|
|
5562
|
+
fs15.unlinkSync(tmpPath);
|
|
5368
5563
|
deleted++;
|
|
5369
5564
|
} else {
|
|
5370
5565
|
try {
|
|
5371
|
-
const content =
|
|
5566
|
+
const content = fs15.readFileSync(tmpPath, "utf-8");
|
|
5372
5567
|
const parsed = JSON.parse(content);
|
|
5373
5568
|
if (isValidManifest(parsed)) {
|
|
5374
|
-
|
|
5569
|
+
fs15.renameSync(tmpPath, manifestPath);
|
|
5375
5570
|
recovered++;
|
|
5376
5571
|
} else {
|
|
5377
|
-
|
|
5572
|
+
fs15.unlinkSync(tmpPath);
|
|
5378
5573
|
deleted++;
|
|
5379
5574
|
}
|
|
5380
5575
|
} catch {
|
|
5381
|
-
|
|
5576
|
+
fs15.unlinkSync(tmpPath);
|
|
5382
5577
|
deleted++;
|
|
5383
5578
|
}
|
|
5384
5579
|
}
|
|
5385
5580
|
} catch (fileErr) {
|
|
5386
5581
|
failed++;
|
|
5387
|
-
|
|
5582
|
+
logger17.warn(`[subagents] recoverTmpFiles: failed to recover ${tmpFile}, skipping (leftovers retry on next startup)`, {
|
|
5388
5583
|
detail: fileErr instanceof Error ? fileErr.message : String(fileErr)
|
|
5389
5584
|
});
|
|
5390
5585
|
}
|
|
5391
5586
|
}
|
|
5392
5587
|
if (failed > 0) {
|
|
5393
|
-
|
|
5588
|
+
logger17.warn(
|
|
5394
5589
|
`[subagents] recoverTmpFiles: ${failed} of ${tmpFiles.length} tmp file(s) could not be recovered`
|
|
5395
5590
|
);
|
|
5396
5591
|
}
|
|
@@ -5400,7 +5595,7 @@ var ManifestStore = class {
|
|
|
5400
5595
|
|
|
5401
5596
|
// src/shared/agent-ref.ts
|
|
5402
5597
|
import { homedir as homedir2 } from "os";
|
|
5403
|
-
import { isAbsolute, join as
|
|
5598
|
+
import { isAbsolute, join as join11 } from "path";
|
|
5404
5599
|
var HOME_DIR_PREFIX = "~/";
|
|
5405
5600
|
function hasParentSegment(ref) {
|
|
5406
5601
|
return ref.split(/[\\/]/).includes("..");
|
|
@@ -5409,7 +5604,7 @@ function normalizeRef(ref, ext) {
|
|
|
5409
5604
|
const trimmed = ref.trim();
|
|
5410
5605
|
if (!trimmed) return null;
|
|
5411
5606
|
if (hasParentSegment(trimmed)) return null;
|
|
5412
|
-
const expanded = trimmed.startsWith(HOME_DIR_PREFIX) ?
|
|
5607
|
+
const expanded = trimmed.startsWith(HOME_DIR_PREFIX) ? join11(homedir2(), trimmed.slice(HOME_DIR_PREFIX.length)) : trimmed;
|
|
5413
5608
|
if (!isAbsolute(expanded)) return null;
|
|
5414
5609
|
if (ext !== void 0 && !expanded.endsWith(ext)) return null;
|
|
5415
5610
|
return expanded;
|
|
@@ -5429,7 +5624,7 @@ function normalizeWorkflowRef(ref, opts) {
|
|
|
5429
5624
|
if (looksLikePath) {
|
|
5430
5625
|
const path21 = normalizeRef(trimmed, WORKFLOW_REF_EXT);
|
|
5431
5626
|
if (path21 !== null) return { kind: "path", path: path21 };
|
|
5432
|
-
const expanded = trimmed.startsWith(HOME_DIR_PREFIX) ?
|
|
5627
|
+
const expanded = trimmed.startsWith(HOME_DIR_PREFIX) ? join11(homedir2(), trimmed.slice(HOME_DIR_PREFIX.length)) : trimmed;
|
|
5433
5628
|
const reason = hasParentSegment(trimmed) ? "parent_segment" : !isAbsolute(expanded) ? "not_absolute" : "bad_ext";
|
|
5434
5629
|
return { kind: "invalid", ref, reason };
|
|
5435
5630
|
}
|
|
@@ -5463,8 +5658,11 @@ function isResumable(record) {
|
|
|
5463
5658
|
return record.status === "running" && !hasLiveProcessHandle(record.id);
|
|
5464
5659
|
}
|
|
5465
5660
|
|
|
5661
|
+
// src/execution/notifier.ts
|
|
5662
|
+
import { createHash } from "crypto";
|
|
5663
|
+
|
|
5466
5664
|
// src/execution/notify-ledger.ts
|
|
5467
|
-
var
|
|
5665
|
+
var logger18 = getLogger("subagents");
|
|
5468
5666
|
var NOTIFY_LEDGER_CUSTOM_TYPE = "subagent-bg-notify-ledger";
|
|
5469
5667
|
var NOTIFY_ACK_CUSTOM_TYPE = "subagent-bg-notify-ack";
|
|
5470
5668
|
var NOTIFY_ABANDONED_CUSTOM_TYPE = "subagent-bg-notify-abandoned";
|
|
@@ -5690,7 +5888,7 @@ function createNotifyLedger(host, options) {
|
|
|
5690
5888
|
host.appendLedgerEntry(NOTIFY_ABANDONED_CUSTOM_TYPE, { v: 1, notifyId: item.notifyId });
|
|
5691
5889
|
items.delete(item.notifyId);
|
|
5692
5890
|
abandonedIds.add(item.notifyId);
|
|
5693
|
-
|
|
5891
|
+
logger18.warn(
|
|
5694
5892
|
`Subagent "${itemLabel(item)}" notification abandoned - no receipt after ${NOTIFY_REDELIVERY_MAX_ATTEMPTS} delivery attempts; verify manually via subagents action:"list"`,
|
|
5695
5893
|
{ notifyId: item.notifyId, attempts: item.attempts }
|
|
5696
5894
|
);
|
|
@@ -5741,7 +5939,7 @@ function createNotifyLedger(host, options) {
|
|
|
5741
5939
|
api.checkReceipts();
|
|
5742
5940
|
}
|
|
5743
5941
|
function emitBucketLog(bucket, total, extra) {
|
|
5744
|
-
|
|
5942
|
+
logger18.warn(`notify delivery bucket [${bucket}]`, { total, ...extra });
|
|
5745
5943
|
}
|
|
5746
5944
|
if (options?.registerSettledListener !== false) {
|
|
5747
5945
|
host.onAgentSettled(() => {
|
|
@@ -5789,6 +5987,70 @@ function getBoundNotifyLedger() {
|
|
|
5789
5987
|
|
|
5790
5988
|
// src/execution/notifier.ts
|
|
5791
5989
|
var notifyLogger = getLogger("subagents");
|
|
5990
|
+
var BATCH_NOTIFY_ID_PREFIX = "sync-batch:";
|
|
5991
|
+
function buildBatchNotifyId(memberIds) {
|
|
5992
|
+
const digest = createHash("sha1").update([...memberIds].sort().join(",")).digest("hex");
|
|
5993
|
+
return `${BATCH_NOTIFY_ID_PREFIX}${digest}`;
|
|
5994
|
+
}
|
|
5995
|
+
var DEFAULT_BATCH_BUDGET = { perItemChars: 4e3, totalChars: 24e3 };
|
|
5996
|
+
var LIST_ONLY_FLOOR = 200;
|
|
5997
|
+
function computeBatchBudget(bodyLengths, perItemChars, totalChars) {
|
|
5998
|
+
const n = bodyLengths.length;
|
|
5999
|
+
const sumAfterPerItem = bodyLengths.reduce((sum, len) => sum + Math.min(len, perItemChars), 0);
|
|
6000
|
+
if (n === 0 || sumAfterPerItem <= totalChars) {
|
|
6001
|
+
return { tightened: false, effectivePerItem: perItemChars, listOnly: false };
|
|
6002
|
+
}
|
|
6003
|
+
const tight = Math.floor(totalChars / n);
|
|
6004
|
+
if (tight < LIST_ONLY_FLOOR) {
|
|
6005
|
+
return { tightened: true, effectivePerItem: 0, listOnly: true };
|
|
6006
|
+
}
|
|
6007
|
+
return { tightened: true, effectivePerItem: Math.min(tight, perItemChars), listOnly: false };
|
|
6008
|
+
}
|
|
6009
|
+
function formatChars(n) {
|
|
6010
|
+
return n.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
|
6011
|
+
}
|
|
6012
|
+
var SESSION_READ_DEFAULT_LIMIT = 8e3;
|
|
6013
|
+
function buildTruncationPointer(id, kept, total, perItemLimit) {
|
|
6014
|
+
const limitPart = perItemLimit > SESSION_READ_DEFAULT_LIMIT ? `,"limit":${perItemLimit}` : "";
|
|
6015
|
+
return `[truncated ${formatChars(total - kept)} of ${formatChars(total)} chars \u2014 full result: session_read {"action":"result","session":"${id}"${limitPart}}]`;
|
|
6016
|
+
}
|
|
6017
|
+
function withMaterializedOutcome(record) {
|
|
6018
|
+
return record.status === "closed" ? { ...record, outcome: record.outcome ?? deriveOutcome(record.closedReason, record.error) } : record;
|
|
6019
|
+
}
|
|
6020
|
+
function buildBatchLlmContent(records, budget = DEFAULT_BATCH_BUDGET) {
|
|
6021
|
+
const mat = records.map(withMaterializedOutcome);
|
|
6022
|
+
let finished = 0;
|
|
6023
|
+
let failed = 0;
|
|
6024
|
+
let cancelled = 0;
|
|
6025
|
+
const bodies = mat.map((record) => {
|
|
6026
|
+
if (record.status === "closed" && record.outcome !== "completed") return null;
|
|
6027
|
+
return record.result ?? "(empty)";
|
|
6028
|
+
});
|
|
6029
|
+
const plan = computeBatchBudget(
|
|
6030
|
+
bodies.map((body) => body?.length ?? 0),
|
|
6031
|
+
budget.perItemChars,
|
|
6032
|
+
budget.totalChars
|
|
6033
|
+
);
|
|
6034
|
+
const items = mat.map((record, i) => {
|
|
6035
|
+
if (record.status === "closed") {
|
|
6036
|
+
const outcome = record.outcome;
|
|
6037
|
+
if (outcome === "completed") finished += 1;
|
|
6038
|
+
else if (outcome === "failed") failed += 1;
|
|
6039
|
+
else if (outcome === "cancelled") cancelled += 1;
|
|
6040
|
+
}
|
|
6041
|
+
const body = bodies[i];
|
|
6042
|
+
if (body === null || body.length <= plan.effectivePerItem) {
|
|
6043
|
+
return buildLlmContent(record);
|
|
6044
|
+
}
|
|
6045
|
+
const kept = body.slice(0, plan.effectivePerItem);
|
|
6046
|
+
const truncated = buildLlmContent({ ...record, result: plan.listOnly ? "" : `${kept}\u2026` });
|
|
6047
|
+
const stripped = plan.listOnly ? truncated.replace(/\n$/, "") : truncated;
|
|
6048
|
+
return `${stripped}
|
|
6049
|
+
${buildTruncationPointer(record.id, kept.length, body.length, plan.effectivePerItem)}`;
|
|
6050
|
+
});
|
|
6051
|
+
const header = `Subagent batch completed: ${finished} finished, ${failed} failed, ${cancelled} cancelled.`;
|
|
6052
|
+
return [header, ...items].join("\n\n---\n\n");
|
|
6053
|
+
}
|
|
5792
6054
|
function buildLlmContent(record) {
|
|
5793
6055
|
const agent = record.agent;
|
|
5794
6056
|
const id = record.id;
|
|
@@ -5906,31 +6168,45 @@ function createNotifier(host) {
|
|
|
5906
6168
|
});
|
|
5907
6169
|
};
|
|
5908
6170
|
let handle = createHandle();
|
|
6171
|
+
function deliverViaLedgerOrKernel(notifyId, content, details) {
|
|
6172
|
+
const ledger = getBoundNotifyLedger();
|
|
6173
|
+
if (ledger) {
|
|
6174
|
+
if (!ledger.record(notifyId, content, details)) return false;
|
|
6175
|
+
ledger.attemptDeliver();
|
|
6176
|
+
return true;
|
|
6177
|
+
}
|
|
6178
|
+
notifyLogger.warn("notify ledger not bound, falling back to delivery kernel path (at-most-once)", {
|
|
6179
|
+
notifyId
|
|
6180
|
+
});
|
|
6181
|
+
handle.send({
|
|
6182
|
+
payload: {
|
|
6183
|
+
kind: "custom",
|
|
6184
|
+
customType: NOTIFY_CUSTOM_TYPE,
|
|
6185
|
+
content,
|
|
6186
|
+
display: true,
|
|
6187
|
+
details
|
|
6188
|
+
},
|
|
6189
|
+
dedupeKey: notifyId
|
|
6190
|
+
});
|
|
6191
|
+
return true;
|
|
6192
|
+
}
|
|
5909
6193
|
return {
|
|
5910
6194
|
notify(record) {
|
|
5911
6195
|
if (disposed) return;
|
|
5912
6196
|
const notifyId = record.round != null ? `${record.id}:${record.round}` : record.id;
|
|
5913
6197
|
const payload = record.status === "closed" ? { ...record, outcome: record.outcome ?? deriveOutcome(record.closedReason, record.error), notifyId } : { ...record, notifyId };
|
|
5914
6198
|
const content = buildLlmContent(payload);
|
|
5915
|
-
|
|
5916
|
-
|
|
5917
|
-
|
|
5918
|
-
|
|
5919
|
-
|
|
5920
|
-
|
|
5921
|
-
|
|
5922
|
-
|
|
5923
|
-
|
|
5924
|
-
|
|
5925
|
-
|
|
5926
|
-
kind: "custom",
|
|
5927
|
-
customType: NOTIFY_CUSTOM_TYPE,
|
|
5928
|
-
content,
|
|
5929
|
-
display: true,
|
|
5930
|
-
details: payload
|
|
5931
|
-
},
|
|
5932
|
-
dedupeKey: notifyId
|
|
5933
|
-
});
|
|
6199
|
+
deliverViaLedgerOrKernel(notifyId, content, payload);
|
|
6200
|
+
},
|
|
6201
|
+
notifyBatch(records, budget) {
|
|
6202
|
+
if (disposed || records.length === 0) return false;
|
|
6203
|
+
const payloads = records.map(
|
|
6204
|
+
(record) => record.status === "closed" ? { ...record, outcome: record.outcome ?? deriveOutcome(record.closedReason, record.error) } : { ...record }
|
|
6205
|
+
);
|
|
6206
|
+
const batchNotifyId = buildBatchNotifyId(payloads.map((p) => p.id));
|
|
6207
|
+
const content = buildBatchLlmContent(payloads, budget);
|
|
6208
|
+
const details = { batch: true, notifyId: batchNotifyId, items: payloads };
|
|
6209
|
+
return deliverViaLedgerOrKernel(batchNotifyId, content, details);
|
|
5934
6210
|
},
|
|
5935
6211
|
flushPendingNotifications() {
|
|
5936
6212
|
const ledger = getBoundNotifyLedger();
|
|
@@ -6054,6 +6330,15 @@ function createNotifyHost(deps) {
|
|
|
6054
6330
|
emitPendingUnregister(id, reason) {
|
|
6055
6331
|
emitPendingUnregister(deps.getPi(), id, reason);
|
|
6056
6332
|
},
|
|
6333
|
+
toNotifyRecord(record) {
|
|
6334
|
+
return toNotifyRecord(record);
|
|
6335
|
+
},
|
|
6336
|
+
notify(record) {
|
|
6337
|
+
notifier.notify(record);
|
|
6338
|
+
},
|
|
6339
|
+
notifyBatch(records, budget) {
|
|
6340
|
+
return notifier.notifyBatch(records, budget);
|
|
6341
|
+
},
|
|
6057
6342
|
revive() {
|
|
6058
6343
|
notifier.revive();
|
|
6059
6344
|
},
|
|
@@ -6067,7 +6352,7 @@ function createNotifyHost(deps) {
|
|
|
6067
6352
|
}
|
|
6068
6353
|
|
|
6069
6354
|
// src/execution/round-settlement.ts
|
|
6070
|
-
var
|
|
6355
|
+
var logger19 = getLogger("subagents");
|
|
6071
6356
|
function createRoundSettler(deps) {
|
|
6072
6357
|
return (record) => {
|
|
6073
6358
|
record.round = (record.round ?? 0) + 1;
|
|
@@ -6076,7 +6361,7 @@ function createRoundSettler(deps) {
|
|
|
6076
6361
|
deps.notifyComplete(record);
|
|
6077
6362
|
const lastTurn = record.turns[record.turns.length - 1];
|
|
6078
6363
|
if (lastTurn !== void 0 && !lastTurn.closed && lastTurn.text.length > 0) {
|
|
6079
|
-
|
|
6364
|
+
logger19.warn(
|
|
6080
6365
|
`[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`
|
|
6081
6366
|
);
|
|
6082
6367
|
}
|
|
@@ -6090,8 +6375,8 @@ function createRoundSettler(deps) {
|
|
|
6090
6375
|
}
|
|
6091
6376
|
|
|
6092
6377
|
// src/execution/record-store.ts
|
|
6093
|
-
import * as
|
|
6094
|
-
import * as
|
|
6378
|
+
import * as fs17 from "fs";
|
|
6379
|
+
import * as path12 from "path";
|
|
6095
6380
|
|
|
6096
6381
|
// src/execution/record-entry.ts
|
|
6097
6382
|
var SUBAGENT_RECORD_CUSTOM_TYPE = "subagent-record";
|
|
@@ -6126,14 +6411,18 @@ function toSubagentRecordEntry(record) {
|
|
|
6126
6411
|
resumable: record.resumable,
|
|
6127
6412
|
engine: record.engine,
|
|
6128
6413
|
engineFallback: record.engineFallback,
|
|
6129
|
-
engineHandle: record.engineHandle
|
|
6414
|
+
engineHandle: record.engineHandle,
|
|
6415
|
+
// 同步收集两字段(U1 foundation):undefined 经 JSON.stringify 自然缺省,
|
|
6416
|
+
// 旧记录/旧 entry 序列化产物字节不变(零迁移)。
|
|
6417
|
+
collectMode: record.collectMode,
|
|
6418
|
+
batchFinalized: record.batchFinalized
|
|
6130
6419
|
};
|
|
6131
6420
|
}
|
|
6132
6421
|
|
|
6133
6422
|
// src/execution/sessions-index.ts
|
|
6134
|
-
import * as
|
|
6135
|
-
import * as
|
|
6136
|
-
var
|
|
6423
|
+
import * as fs16 from "fs";
|
|
6424
|
+
import * as path11 from "path";
|
|
6425
|
+
var logger20 = getLogger("subagents");
|
|
6137
6426
|
var INDEX_FILENAME = "sessions-index.json";
|
|
6138
6427
|
var INDEX_VERSION = 1;
|
|
6139
6428
|
var INDEX_WRITE_MIN_INTERVAL_MS = 6e4;
|
|
@@ -6172,11 +6461,11 @@ function errorCodeOf(err) {
|
|
|
6172
6461
|
}
|
|
6173
6462
|
function readIndexFile(indexPath, encDir) {
|
|
6174
6463
|
try {
|
|
6175
|
-
return
|
|
6464
|
+
return fs16.readFileSync(indexPath, "utf-8");
|
|
6176
6465
|
} catch (err) {
|
|
6177
6466
|
const code = errorCodeOf(err);
|
|
6178
6467
|
if (code !== "ENOENT") {
|
|
6179
|
-
|
|
6468
|
+
logger20.debug("[subagents] sessions-index read failed, fallback to empty", {
|
|
6180
6469
|
detail: { dir: encDir, code }
|
|
6181
6470
|
});
|
|
6182
6471
|
}
|
|
@@ -6187,7 +6476,7 @@ function parseIndexJson(raw, indexPath) {
|
|
|
6187
6476
|
try {
|
|
6188
6477
|
return JSON.parse(raw);
|
|
6189
6478
|
} catch (err) {
|
|
6190
|
-
|
|
6479
|
+
logger20.debug("[subagents] sessions-index corrupted JSON, fallback to empty", {
|
|
6191
6480
|
detail: { path: indexPath, error: err instanceof Error ? err.message : String(err) }
|
|
6192
6481
|
});
|
|
6193
6482
|
return null;
|
|
@@ -6198,14 +6487,14 @@ function isIndexTopHeader(v) {
|
|
|
6198
6487
|
}
|
|
6199
6488
|
function readTopLevel(parsed, indexPath) {
|
|
6200
6489
|
if (typeof parsed !== "object" || parsed === null) {
|
|
6201
|
-
|
|
6490
|
+
logger20.debug("[subagents] sessions-index invalid top-level shape, fallback to empty", {
|
|
6202
6491
|
detail: { path: indexPath }
|
|
6203
6492
|
});
|
|
6204
6493
|
return null;
|
|
6205
6494
|
}
|
|
6206
6495
|
const top = parsed;
|
|
6207
6496
|
if (!isIndexTopHeader(top)) {
|
|
6208
|
-
|
|
6497
|
+
logger20.debug("[subagents] sessions-index invalid header fields, fallback to empty", {
|
|
6209
6498
|
detail: { path: indexPath }
|
|
6210
6499
|
});
|
|
6211
6500
|
return null;
|
|
@@ -6222,7 +6511,7 @@ function collectValidEntries(entriesObj) {
|
|
|
6222
6511
|
}
|
|
6223
6512
|
function loadIndex(encDir) {
|
|
6224
6513
|
const empty = { entries: /* @__PURE__ */ new Map(), higherVersion: false };
|
|
6225
|
-
const indexPath =
|
|
6514
|
+
const indexPath = path11.join(encDir, INDEX_FILENAME);
|
|
6226
6515
|
const raw = readIndexFile(indexPath, encDir);
|
|
6227
6516
|
if (raw === null) return empty;
|
|
6228
6517
|
const parsed = parseIndexJson(raw, indexPath);
|
|
@@ -6233,7 +6522,7 @@ function loadIndex(encDir) {
|
|
|
6233
6522
|
return { entries: /* @__PURE__ */ new Map(), higherVersion: true };
|
|
6234
6523
|
}
|
|
6235
6524
|
if (top.version < INDEX_VERSION) {
|
|
6236
|
-
|
|
6525
|
+
logger20.debug("[subagents] sessions-index stale version, discarded", {
|
|
6237
6526
|
detail: { path: indexPath, version: top.version, expected: INDEX_VERSION }
|
|
6238
6527
|
});
|
|
6239
6528
|
return empty;
|
|
@@ -6241,7 +6530,7 @@ function loadIndex(encDir) {
|
|
|
6241
6530
|
return { entries: collectValidEntries(top.entries), higherVersion: false };
|
|
6242
6531
|
}
|
|
6243
6532
|
async function saveIndex(encDir, data) {
|
|
6244
|
-
const filePath =
|
|
6533
|
+
const filePath = path11.join(encDir, INDEX_FILENAME);
|
|
6245
6534
|
const file = {
|
|
6246
6535
|
version: INDEX_VERSION,
|
|
6247
6536
|
pid: process.pid,
|
|
@@ -6251,7 +6540,7 @@ async function saveIndex(encDir, data) {
|
|
|
6251
6540
|
}
|
|
6252
6541
|
|
|
6253
6542
|
// src/execution/record-store.ts
|
|
6254
|
-
var
|
|
6543
|
+
var logger21 = getLogger("subagents");
|
|
6255
6544
|
var STATUS_PRIORITY = {
|
|
6256
6545
|
running: 0,
|
|
6257
6546
|
closed: 3
|
|
@@ -6270,14 +6559,14 @@ var MIN_SEGMENTS_WITH_BOUNDARY = 2;
|
|
|
6270
6559
|
function readLastJsonlLine(sessionFile) {
|
|
6271
6560
|
let fd;
|
|
6272
6561
|
try {
|
|
6273
|
-
fd =
|
|
6274
|
-
const size =
|
|
6562
|
+
fd = fs17.openSync(sessionFile, "r");
|
|
6563
|
+
const size = fs17.fstatSync(fd).size;
|
|
6275
6564
|
let windowBytes = LAST_LINE_WINDOW_BYTES;
|
|
6276
6565
|
let windowStart = Math.max(0, size - windowBytes);
|
|
6277
6566
|
let lines = [];
|
|
6278
6567
|
while (true) {
|
|
6279
6568
|
const buf = Buffer.alloc(size - windowStart);
|
|
6280
|
-
|
|
6569
|
+
fs17.readSync(fd, buf, 0, buf.length, windowStart);
|
|
6281
6570
|
lines = buf.toString("utf-8").split("\n").filter((l) => l.length > 0);
|
|
6282
6571
|
if (windowStart === 0 || lines.length >= MIN_SEGMENTS_WITH_BOUNDARY) break;
|
|
6283
6572
|
windowBytes *= WINDOW_GROWTH_FACTOR;
|
|
@@ -6294,7 +6583,7 @@ function readLastJsonlLine(sessionFile) {
|
|
|
6294
6583
|
} finally {
|
|
6295
6584
|
if (fd !== void 0) {
|
|
6296
6585
|
try {
|
|
6297
|
-
|
|
6586
|
+
fs17.closeSync(fd);
|
|
6298
6587
|
} catch (_e) {
|
|
6299
6588
|
void _e;
|
|
6300
6589
|
}
|
|
@@ -6317,7 +6606,7 @@ function collectLastRecordEntries(content) {
|
|
|
6317
6606
|
try {
|
|
6318
6607
|
entry = asSubagentRecordEntry(JSON.parse(line));
|
|
6319
6608
|
} catch (err) {
|
|
6320
|
-
|
|
6609
|
+
logger21.debug("[subagents] entry-only orphan scan: skip unparsable line", {
|
|
6321
6610
|
reason: err instanceof Error ? err.message : String(err)
|
|
6322
6611
|
});
|
|
6323
6612
|
}
|
|
@@ -6325,37 +6614,64 @@ function collectLastRecordEntries(content) {
|
|
|
6325
6614
|
}
|
|
6326
6615
|
return lastById;
|
|
6327
6616
|
}
|
|
6617
|
+
function entryStr(d, k) {
|
|
6618
|
+
return typeof d[k] === "string" ? d[k] : void 0;
|
|
6619
|
+
}
|
|
6620
|
+
function entryNum(d, k) {
|
|
6621
|
+
return typeof d[k] === "number" ? d[k] : void 0;
|
|
6622
|
+
}
|
|
6623
|
+
function readEntryTerminalFields(d) {
|
|
6624
|
+
const closedReason = entryStr(d, "closedReason");
|
|
6625
|
+
return {
|
|
6626
|
+
status: d.status === "closed" ? "closed" : "running",
|
|
6627
|
+
closedReason: isValidClosedReason(closedReason) ? closedReason : void 0
|
|
6628
|
+
};
|
|
6629
|
+
}
|
|
6630
|
+
function readEntryBatchFields(d) {
|
|
6631
|
+
return {
|
|
6632
|
+
collectMode: d.collectMode === "sync" ? "sync" : void 0,
|
|
6633
|
+
batchFinalized: d.batchFinalized === true ? true : void 0,
|
|
6634
|
+
resumable: d.resumable === true ? true : void 0
|
|
6635
|
+
};
|
|
6636
|
+
}
|
|
6637
|
+
function readEntryEngineFields(d) {
|
|
6638
|
+
return {
|
|
6639
|
+
engine: entryStr(d, "engine"),
|
|
6640
|
+
engineFallback: isEngineFallbackShape(d.engineFallback) ? d.engineFallback : void 0,
|
|
6641
|
+
engineHandle: isEngineHandleShape(d.engineHandle) ? d.engineHandle : void 0
|
|
6642
|
+
};
|
|
6643
|
+
}
|
|
6328
6644
|
function rebuildEntryRecord(id, d) {
|
|
6329
|
-
const
|
|
6330
|
-
const
|
|
6331
|
-
const
|
|
6332
|
-
const task = str("task");
|
|
6333
|
-
const startedAt = num("startedAt");
|
|
6645
|
+
const agent = entryStr(d, "agent");
|
|
6646
|
+
const task = entryStr(d, "task");
|
|
6647
|
+
const startedAt = entryNum(d, "startedAt");
|
|
6334
6648
|
if (agent === void 0 || task === void 0 || startedAt === void 0) return null;
|
|
6335
6649
|
return {
|
|
6336
6650
|
id,
|
|
6337
6651
|
agent,
|
|
6338
6652
|
task,
|
|
6339
|
-
slug:
|
|
6340
|
-
|
|
6653
|
+
slug: entryStr(d, "slug") ?? "",
|
|
6654
|
+
...readEntryTerminalFields(d),
|
|
6341
6655
|
mode: "background",
|
|
6342
6656
|
startedAt,
|
|
6343
|
-
rootSessionId:
|
|
6344
|
-
parentRecordId:
|
|
6345
|
-
depth:
|
|
6346
|
-
endedAt:
|
|
6347
|
-
turns:
|
|
6348
|
-
totalTokens:
|
|
6349
|
-
model:
|
|
6350
|
-
thinkingLevel:
|
|
6657
|
+
rootSessionId: entryStr(d, "rootSessionId"),
|
|
6658
|
+
parentRecordId: entryStr(d, "parentRecordId"),
|
|
6659
|
+
depth: entryNum(d, "depth") ?? 0,
|
|
6660
|
+
endedAt: entryNum(d, "endedAt"),
|
|
6661
|
+
turns: entryNum(d, "turns") ?? 0,
|
|
6662
|
+
totalTokens: entryNum(d, "totalTokens") ?? 0,
|
|
6663
|
+
model: entryStr(d, "model") ?? "",
|
|
6664
|
+
thinkingLevel: entryStr(d, "thinkingLevel"),
|
|
6351
6665
|
eventLog: [],
|
|
6352
6666
|
displayItems: [],
|
|
6353
|
-
|
|
6667
|
+
result: entryStr(d, "result"),
|
|
6668
|
+
error: entryStr(d, "error"),
|
|
6669
|
+
sessionFile: entryStr(d, "sessionFile"),
|
|
6670
|
+
patchFile: entryStr(d, "patchFile"),
|
|
6354
6671
|
chatMode: d.chatMode === true,
|
|
6355
|
-
round:
|
|
6356
|
-
|
|
6357
|
-
|
|
6358
|
-
engineHandle: isEngineHandleShape(d.engineHandle) ? d.engineHandle : void 0
|
|
6672
|
+
round: entryNum(d, "round"),
|
|
6673
|
+
...readEntryEngineFields(d),
|
|
6674
|
+
...readEntryBatchFields(d)
|
|
6359
6675
|
};
|
|
6360
6676
|
}
|
|
6361
6677
|
function isEngineFallbackShape(v) {
|
|
@@ -6378,7 +6694,7 @@ function isEngineHandleShape(v) {
|
|
|
6378
6694
|
}
|
|
6379
6695
|
function statStamp2(p) {
|
|
6380
6696
|
try {
|
|
6381
|
-
const s =
|
|
6697
|
+
const s = fs17.statSync(p);
|
|
6382
6698
|
return { mtimeMs: s.mtimeMs, size: s.size };
|
|
6383
6699
|
} catch {
|
|
6384
6700
|
return null;
|
|
@@ -6554,7 +6870,7 @@ var RecordStore = class _RecordStore {
|
|
|
6554
6870
|
if (rootSessionFilter !== void 0 && manifest.rootSessionId !== rootSessionFilter) continue;
|
|
6555
6871
|
const rec = _RecordStore.manifestToSubagent(manifest);
|
|
6556
6872
|
if (!rec) {
|
|
6557
|
-
|
|
6873
|
+
logger21.warn("[subagents] skip manifest with invalid status", {
|
|
6558
6874
|
detail: { id: manifest.id, status: manifest.status }
|
|
6559
6875
|
});
|
|
6560
6876
|
this.pi?.appendEntry?.("subagent:manifest-invalid-status", {
|
|
@@ -6601,31 +6917,44 @@ var RecordStore = class _RecordStore {
|
|
|
6601
6917
|
* - 文件不可读(IO 错误,可能暂时)→ 不判终态,落 resumable entry(防御性路径,
|
|
6602
6918
|
* IO 恢复后重开可重判)。
|
|
6603
6919
|
*
|
|
6920
|
+
* [v2 D3] mainSessionFile = 覆写 merge 数据源(主 session 每 id 末条 entry,与 E1
|
|
6921
|
+
* 的 scanLastRecordEntries 同款通路):崩溃前的批域标记(collectMode/batchFinalized)
|
|
6922
|
+
* 与轮终 result/model 只活在主文件 entry——重建矩阵(buildRecord)的数据源
|
|
6923
|
+
* (sidecar/子文件 identity)不含它们,覆写前不 merge 就会被抹掉(v2 §2.2 断链 2:
|
|
6924
|
+
* E1 候选集恒空的真根因)。缺省(undefined)时 merge 无源,行为与旧版一致。
|
|
6925
|
+
* 参数为追加式第二参(rootSessionFilter 保持首参):既有调用面只传过滤参。
|
|
6926
|
+
*
|
|
6604
6927
|
* 防重:orphanJudged 实例级缓存(resumable 形态无 sidecar 锚,同进程重复调用跳过;
|
|
6605
6928
|
* 终态形态双重防护 = sidecar + 缓存)。调用方:index.ts session_start 恢复段(一次)。
|
|
6606
6929
|
*/
|
|
6607
|
-
recoverOrphanRecords(rootSessionFilter) {
|
|
6930
|
+
recoverOrphanRecords(rootSessionFilter, mainSessionFile) {
|
|
6931
|
+
const lastById = new Map(this.scanLastRecordEntries(mainSessionFile).map((r) => [r.id, r]));
|
|
6608
6932
|
for (const rec of this.reconstructAll(rootSessionFilter)) {
|
|
6609
6933
|
if (rec.status !== "running" || rec.externalInstance !== void 0) continue;
|
|
6610
6934
|
if (this.orphanJudged.has(rec.id)) continue;
|
|
6611
6935
|
this.orphanJudged.add(rec.id);
|
|
6612
|
-
this.finalizeOrphanRecord(rec);
|
|
6936
|
+
this.finalizeOrphanRecord(rec, lastById.get(rec.id));
|
|
6613
6937
|
}
|
|
6614
6938
|
}
|
|
6615
6939
|
/**
|
|
6616
6940
|
* 单孤儿 record 的终态判定与落 entry(residual-fixes §5.2 三判据 + chat 分流)。
|
|
6617
6941
|
* 防重锚(orphanJudged 标记)已由调用方完成。
|
|
6942
|
+
*
|
|
6943
|
+
* [v2 D3] 覆写前 merge(lastEntry = 主 session 同 id 末条 entry 重建,调用方构建):
|
|
6944
|
+
* 覆写是状态迁移不是信息重建,迁移不应丢末条既有信息。三个落 entry 分支(chatMode
|
|
6945
|
+
* 分流 / IO 保守 / 终态覆写)统一基于 merge 后的 rec,同款口径。
|
|
6618
6946
|
*/
|
|
6619
|
-
finalizeOrphanRecord(rec) {
|
|
6620
|
-
|
|
6621
|
-
|
|
6947
|
+
finalizeOrphanRecord(rec, lastEntry) {
|
|
6948
|
+
const rec0 = lastEntry === void 0 ? rec : _RecordStore.mergeOrphanLastEntry(rec, lastEntry);
|
|
6949
|
+
if (rec0.chatMode === true) {
|
|
6950
|
+
this.reportSubagentRecord({ ...rec0, resumable: true });
|
|
6622
6951
|
return;
|
|
6623
6952
|
}
|
|
6624
|
-
const sessionFile =
|
|
6953
|
+
const sessionFile = rec0.sessionFile;
|
|
6625
6954
|
if (sessionFile === void 0) return;
|
|
6626
6955
|
const lastLine = readLastJsonlLine(sessionFile);
|
|
6627
6956
|
if (!lastLine.ok) {
|
|
6628
|
-
this.reportSubagentRecord({ ...
|
|
6957
|
+
this.reportSubagentRecord({ ...rec0, resumable: true });
|
|
6629
6958
|
return;
|
|
6630
6959
|
}
|
|
6631
6960
|
let parseOk = false;
|
|
@@ -6637,13 +6966,30 @@ var RecordStore = class _RecordStore {
|
|
|
6637
6966
|
}
|
|
6638
6967
|
writeFinalized(sessionFile, "gc");
|
|
6639
6968
|
this.reportSubagentRecord({
|
|
6640
|
-
...
|
|
6969
|
+
...rec0,
|
|
6641
6970
|
status: "closed",
|
|
6642
6971
|
closedReason: "gc",
|
|
6643
6972
|
endedAt: Date.now(),
|
|
6644
6973
|
...parseOk ? {} : { error: "orphan recovery: subagent session ended abnormally (truncated last line)" }
|
|
6645
6974
|
});
|
|
6646
6975
|
}
|
|
6976
|
+
/** [v2 D3] 孤儿覆写 merge 字段集:末条 entry 的批域标记 + 轮终正文/模型,仅补 rec 侧
|
|
6977
|
+
* undefined/空值(model 的空值形态是 ""——light 重建无 model_change entry 时起步
|
|
6978
|
+
* 空串),不覆盖已有值。merge 后写 entry 经 reportSubagentRecord →
|
|
6979
|
+
* toSubagentRecordEntry 序列化,undefined 字段自然缺省(不引入显式 null)。 */
|
|
6980
|
+
static mergeOrphanLastEntry(rec, last) {
|
|
6981
|
+
const pickStr = (cur, src) => cur !== void 0 && cur !== "" ? cur : src;
|
|
6982
|
+
return {
|
|
6983
|
+
...rec,
|
|
6984
|
+
collectMode: rec.collectMode ?? last.collectMode,
|
|
6985
|
+
batchFinalized: rec.batchFinalized ?? last.batchFinalized,
|
|
6986
|
+
result: pickStr(rec.result, last.result),
|
|
6987
|
+
// 类型收尾:两侧实参恒 string(rec.model 类型非可选;last.model 重建投影自带
|
|
6988
|
+
// `?? ""`),pickStr 签名宽返回 string|undefined —— `?? ""` 运行时不可达,
|
|
6989
|
+
// 仅满足 model 非可选类型,空串回退语义不变(cur 空 → src,src 也空 → "")。
|
|
6990
|
+
model: pickStr(rec.model, last.model) ?? ""
|
|
6991
|
+
};
|
|
6992
|
+
}
|
|
6647
6993
|
/**
|
|
6648
6994
|
* [E2E 实测缺口] entry-born 孤儿恢复:register entry 已落主 session、但子 session 文件
|
|
6649
6995
|
* 从未创建(父进程死在 spawn 窗口期——register 写点与子进程首笔写入之间的窗口;外部
|
|
@@ -6660,7 +7006,7 @@ var RecordStore = class _RecordStore {
|
|
|
6660
7006
|
if (mainSessionFile === void 0) return;
|
|
6661
7007
|
let content;
|
|
6662
7008
|
try {
|
|
6663
|
-
content =
|
|
7009
|
+
content = fs17.readFileSync(mainSessionFile, "utf-8");
|
|
6664
7010
|
} catch {
|
|
6665
7011
|
return;
|
|
6666
7012
|
}
|
|
@@ -6699,6 +7045,31 @@ var RecordStore = class _RecordStore {
|
|
|
6699
7045
|
}
|
|
6700
7046
|
});
|
|
6701
7047
|
}
|
|
7048
|
+
/**
|
|
7049
|
+
* [E1/U5] sync 批崩溃恢复扫描:主 session 文件「每 id 末条 subagent-record entry」
|
|
7050
|
+
* (collectLastRecordEntries + rebuildEntryRecord 组合通路,设计 §3.1.3「标记读取
|
|
7051
|
+
* 通路」——batchFinalized 落标 entry 写主 session 文件,本扫描同文件域才可见;禁走
|
|
7052
|
+
* collectRecords light 路径,它只读子文件 identity 头+sidecar,主 session 落标
|
|
7053
|
+
* entry 不可见)。返回每 id 末条重建的完整 record(含 collectMode/batchFinalized /
|
|
7054
|
+
* 终态五字段,损坏 entry 跳过);调用方(service.recoverSyncCollectBatch 的 E1 过滤、
|
|
7055
|
+
* recoverOrphanRecords 的覆写 merge)自行取舍。主文件不可读(含新 session 未 flush
|
|
7056
|
+
* 的 ENOENT)→ 空数组静默。
|
|
7057
|
+
*/
|
|
7058
|
+
scanLastRecordEntries(mainSessionFile) {
|
|
7059
|
+
if (mainSessionFile === void 0) return [];
|
|
7060
|
+
let content;
|
|
7061
|
+
try {
|
|
7062
|
+
content = fs17.readFileSync(mainSessionFile, "utf-8");
|
|
7063
|
+
} catch {
|
|
7064
|
+
return [];
|
|
7065
|
+
}
|
|
7066
|
+
const out = [];
|
|
7067
|
+
for (const [id, d] of collectLastRecordEntries(content)) {
|
|
7068
|
+
const rec = rebuildEntryRecord(id, d);
|
|
7069
|
+
if (rec !== null) out.push(rec);
|
|
7070
|
+
}
|
|
7071
|
+
return out;
|
|
7072
|
+
}
|
|
6702
7073
|
/** 订阅变更。返回取消订阅函数。 */
|
|
6703
7074
|
onChange(listener) {
|
|
6704
7075
|
this.listeners.add(listener);
|
|
@@ -6761,12 +7132,12 @@ var RecordStore = class _RecordStore {
|
|
|
6761
7132
|
reconstructAll(rootSessionFilter) {
|
|
6762
7133
|
let dirMtimeMs;
|
|
6763
7134
|
try {
|
|
6764
|
-
dirMtimeMs =
|
|
7135
|
+
dirMtimeMs = fs17.statSync(this.sessionsDir).mtimeMs;
|
|
6765
7136
|
} catch {
|
|
6766
7137
|
return [];
|
|
6767
7138
|
}
|
|
6768
7139
|
if (this.dirStamp === null) {
|
|
6769
|
-
const loaded = loadIndex(
|
|
7140
|
+
const loaded = loadIndex(path12.dirname(this.sessionsDir));
|
|
6770
7141
|
this.indexEntries = loaded.entries;
|
|
6771
7142
|
this.indexHigherVersion = loaded.higherVersion;
|
|
6772
7143
|
}
|
|
@@ -6782,7 +7153,7 @@ var RecordStore = class _RecordStore {
|
|
|
6782
7153
|
}
|
|
6783
7154
|
let files;
|
|
6784
7155
|
try {
|
|
6785
|
-
files =
|
|
7156
|
+
files = fs17.readdirSync(this.sessionsDir).filter((f) => f.endsWith(".jsonl")).map((f) => path12.join(this.sessionsDir, f));
|
|
6786
7157
|
} catch {
|
|
6787
7158
|
this.indexEntries = null;
|
|
6788
7159
|
return [];
|
|
@@ -6856,7 +7227,7 @@ var RecordStore = class _RecordStore {
|
|
|
6856
7227
|
*/
|
|
6857
7228
|
buildEntryFromIndex(file, stamps, now) {
|
|
6858
7229
|
if (this.indexEntries === null) return void 0;
|
|
6859
|
-
const hit = this.indexEntries.get(
|
|
7230
|
+
const hit = this.indexEntries.get(path12.basename(file));
|
|
6860
7231
|
if (hit === void 0 || hit.mtimeMs !== stamps.jsonl.mtimeMs || hit.size !== stamps.jsonl.size) {
|
|
6861
7232
|
return void 0;
|
|
6862
7233
|
}
|
|
@@ -6888,12 +7259,12 @@ var RecordStore = class _RecordStore {
|
|
|
6888
7259
|
if (Date.now() - this.lastIndexWriteAt < INDEX_WRITE_MIN_INTERVAL_MS) return;
|
|
6889
7260
|
const entries = this.projectIndexEntries();
|
|
6890
7261
|
this.indexDirty = false;
|
|
6891
|
-
const encDir =
|
|
7262
|
+
const encDir = path12.dirname(this.sessionsDir);
|
|
6892
7263
|
saveIndex(encDir, { entries }).then(() => {
|
|
6893
7264
|
this.lastIndexWriteAt = Date.now();
|
|
6894
7265
|
}).catch((err) => {
|
|
6895
7266
|
this.indexDirty = true;
|
|
6896
|
-
|
|
7267
|
+
logger21.warn("[subagents] sessions-index write failed", {
|
|
6897
7268
|
detail: { dir: encDir, error: err instanceof Error ? err.message : String(err) }
|
|
6898
7269
|
});
|
|
6899
7270
|
});
|
|
@@ -6907,7 +7278,7 @@ var RecordStore = class _RecordStore {
|
|
|
6907
7278
|
projectIndexEntries() {
|
|
6908
7279
|
const entries = /* @__PURE__ */ new Map();
|
|
6909
7280
|
for (const [file, cached] of this.fileCache) {
|
|
6910
|
-
const base =
|
|
7281
|
+
const base = path12.basename(file);
|
|
6911
7282
|
if (cached.negative) {
|
|
6912
7283
|
entries.set(base, { negative: true, mtimeMs: cached.jsonl.mtimeMs, size: cached.jsonl.size });
|
|
6913
7284
|
} else {
|
|
@@ -7162,15 +7533,65 @@ var RecordStore = class _RecordStore {
|
|
|
7162
7533
|
engine: r.engine,
|
|
7163
7534
|
engineFallback: r.engineFallback,
|
|
7164
7535
|
// U2:engineHandle 经 entry 持久化(register/archive 双写点均经本投影),无则 undefined 自然省略
|
|
7165
|
-
engineHandle: r.engineHandle
|
|
7536
|
+
engineHandle: r.engineHandle,
|
|
7537
|
+
// [U5 修复 U2 披露的投影缺口] 同步收集两字段随本投影持久化(register entry /
|
|
7538
|
+
// archive entry 双写点)——原缺失时闭合判定 flushBatch 重建、E1 重建扫描等消费方
|
|
7539
|
+
// 读不到原始值。undefined 经 JSON.stringify 自然缺省,旧 entry 零迁移。
|
|
7540
|
+
collectMode: r.collectMode,
|
|
7541
|
+
batchFinalized: r.batchFinalized
|
|
7166
7542
|
};
|
|
7167
7543
|
}
|
|
7168
7544
|
};
|
|
7169
7545
|
|
|
7546
|
+
// src/execution/sync-rebuild.ts
|
|
7547
|
+
function syncRebuildToNotifyMember(rec) {
|
|
7548
|
+
const status = rec.status === "closed" || !rec.chatMode ? "closed" : "running";
|
|
7549
|
+
return {
|
|
7550
|
+
id: rec.id,
|
|
7551
|
+
status,
|
|
7552
|
+
closedReason: rec.closedReason,
|
|
7553
|
+
outcome: status === "closed" ? deriveOutcome(rec.closedReason, rec.error) : void 0,
|
|
7554
|
+
agent: rec.agent,
|
|
7555
|
+
model: rec.model,
|
|
7556
|
+
result: rec.result,
|
|
7557
|
+
error: rec.error,
|
|
7558
|
+
startedAt: rec.startedAt,
|
|
7559
|
+
endedAt: rec.endedAt,
|
|
7560
|
+
patchFile: rec.patchFile
|
|
7561
|
+
};
|
|
7562
|
+
}
|
|
7563
|
+
function bufferedMemberFallbackRecord(m, rootSessionId) {
|
|
7564
|
+
return {
|
|
7565
|
+
id: m.id,
|
|
7566
|
+
agent: m.agent,
|
|
7567
|
+
task: "",
|
|
7568
|
+
slug: "",
|
|
7569
|
+
status: "closed",
|
|
7570
|
+
closedReason: m.closedReason,
|
|
7571
|
+
mode: "background",
|
|
7572
|
+
startedAt: m.startedAt,
|
|
7573
|
+
rootSessionId,
|
|
7574
|
+
parentRecordId: void 0,
|
|
7575
|
+
depth: 0,
|
|
7576
|
+
endedAt: m.endedAt,
|
|
7577
|
+
turns: 0,
|
|
7578
|
+
totalTokens: 0,
|
|
7579
|
+
model: m.model ?? "",
|
|
7580
|
+
thinkingLevel: void 0,
|
|
7581
|
+
eventLog: [],
|
|
7582
|
+
displayItems: [],
|
|
7583
|
+
result: m.result,
|
|
7584
|
+
error: m.error,
|
|
7585
|
+
sessionFile: void 0,
|
|
7586
|
+
chatMode: false,
|
|
7587
|
+
patchFile: m.patchFile
|
|
7588
|
+
};
|
|
7589
|
+
}
|
|
7590
|
+
|
|
7170
7591
|
// src/execution/engine/common/pool-manager.ts
|
|
7171
7592
|
import * as fsSync from "fs";
|
|
7172
|
-
import { join as
|
|
7173
|
-
var
|
|
7593
|
+
import { join as join14 } from "path";
|
|
7594
|
+
var logger22 = getLogger("subagents");
|
|
7174
7595
|
var POOL_CLEANUP_FAILED_MARKER = ".pool-cleanup-failed";
|
|
7175
7596
|
var REFS_JSON_FILENAME = "refs.json";
|
|
7176
7597
|
var REFS_VERSION = 1;
|
|
@@ -7180,13 +7601,13 @@ function emptyRefs() {
|
|
|
7180
7601
|
return { v: REFS_VERSION, refs: {} };
|
|
7181
7602
|
}
|
|
7182
7603
|
function readPoolRefs(poolDir, fs27) {
|
|
7183
|
-
const refsPath =
|
|
7604
|
+
const refsPath = join14(poolDir, REFS_JSON_FILENAME);
|
|
7184
7605
|
if (!fs27.existsSync(refsPath)) return emptyRefs();
|
|
7185
7606
|
let parsed;
|
|
7186
7607
|
try {
|
|
7187
7608
|
parsed = JSON.parse(fs27.readFileSync(refsPath));
|
|
7188
7609
|
} catch (err) {
|
|
7189
|
-
|
|
7610
|
+
logger22.warn(
|
|
7190
7611
|
`[pool-manager] refs.json unparsable for ${poolDir}, starting from empty refs: ${toErrorMessage(err)}`
|
|
7191
7612
|
);
|
|
7192
7613
|
return emptyRefs();
|
|
@@ -7194,7 +7615,7 @@ function readPoolRefs(poolDir, fs27) {
|
|
|
7194
7615
|
if (typeof parsed !== "object" || parsed === null) return emptyRefs();
|
|
7195
7616
|
const obj = parsed;
|
|
7196
7617
|
if (obj.v !== REFS_VERSION || typeof obj.refs !== "object" || obj.refs === null) {
|
|
7197
|
-
|
|
7618
|
+
logger22.warn(`[pool-manager] refs.json unexpected shape for ${poolDir}, starting from empty refs`);
|
|
7198
7619
|
return emptyRefs();
|
|
7199
7620
|
}
|
|
7200
7621
|
const refs = {};
|
|
@@ -7207,7 +7628,7 @@ function readPoolRefs(poolDir, fs27) {
|
|
|
7207
7628
|
return { v: REFS_VERSION, refs };
|
|
7208
7629
|
}
|
|
7209
7630
|
function writePoolRefs(poolDir, file, fs27) {
|
|
7210
|
-
const refsPath =
|
|
7631
|
+
const refsPath = join14(poolDir, REFS_JSON_FILENAME);
|
|
7211
7632
|
const tmpPath = `${refsPath}.tmp.${process.pid}`;
|
|
7212
7633
|
try {
|
|
7213
7634
|
fs27.writeFileSync(tmpPath, `${JSON.stringify(file)}
|
|
@@ -7228,7 +7649,7 @@ function releasePoolRef(dataDir, engineId, poolKey, taskId, fs27 = nodeFs) {
|
|
|
7228
7649
|
removeJournalFile(dataDir, engineId, poolKey, taskId, fs27);
|
|
7229
7650
|
const file = readPoolRefs(poolDir, fs27);
|
|
7230
7651
|
if (file.refs[taskId] === void 0) {
|
|
7231
|
-
|
|
7652
|
+
logger22.debug(`[pool-manager] release without live ref, skip pool deletion: ${poolDir} (${taskId})`);
|
|
7232
7653
|
return;
|
|
7233
7654
|
}
|
|
7234
7655
|
delete file.refs[taskId];
|
|
@@ -7259,7 +7680,7 @@ function cleanupExpiredPoolRefs(dataDir, ttlMs, fs27 = nodeFs, now = Date.now())
|
|
|
7259
7680
|
}
|
|
7260
7681
|
for (const engineEntry of engines) {
|
|
7261
7682
|
if (!engineEntry.isDirectory()) continue;
|
|
7262
|
-
const engineDir =
|
|
7683
|
+
const engineDir = join14(enginesRoot, engineEntry.name);
|
|
7263
7684
|
let pools;
|
|
7264
7685
|
try {
|
|
7265
7686
|
pools = fs27.readdirSync(engineDir);
|
|
@@ -7268,7 +7689,7 @@ function cleanupExpiredPoolRefs(dataDir, ttlMs, fs27 = nodeFs, now = Date.now())
|
|
|
7268
7689
|
}
|
|
7269
7690
|
for (const poolEntry of pools) {
|
|
7270
7691
|
if (!poolEntry.isDirectory()) continue;
|
|
7271
|
-
cleanupPoolByTtl(
|
|
7692
|
+
cleanupPoolByTtl(join14(engineDir, poolEntry.name), ttlMs, fs27, now);
|
|
7272
7693
|
}
|
|
7273
7694
|
}
|
|
7274
7695
|
}
|
|
@@ -7292,7 +7713,7 @@ function cleanupPoolByTtl(poolDir, ttlMs, fs27, now) {
|
|
|
7292
7713
|
function removeExpiredRefEntries(poolDir, file, ttlMs, fs27, now) {
|
|
7293
7714
|
let changed = false;
|
|
7294
7715
|
for (const [taskId, entry] of Object.entries(file.refs)) {
|
|
7295
|
-
const journalPath =
|
|
7716
|
+
const journalPath = join14(poolDir, `${JOURNAL_PREFIX}${sanitizeSeg(taskId)}${JOURNAL_SUFFIX}`);
|
|
7296
7717
|
if (fs27.existsSync(journalPath)) {
|
|
7297
7718
|
const age = now - fs27.statSync(journalPath).mtimeMs;
|
|
7298
7719
|
if (age > ttlMs) {
|
|
@@ -7314,14 +7735,14 @@ function removeOrphanJournals(poolDir, file, entries, ttlMs, fs27, now) {
|
|
|
7314
7735
|
if (!isJournalFile(entry.name)) continue;
|
|
7315
7736
|
const segment = journalTaskSegment(entry.name);
|
|
7316
7737
|
if (segment !== void 0 && ownedSegments.has(segment)) continue;
|
|
7317
|
-
const journalPath =
|
|
7738
|
+
const journalPath = join14(poolDir, entry.name);
|
|
7318
7739
|
try {
|
|
7319
7740
|
if (now - fs27.statSync(journalPath).mtimeMs > ttlMs) {
|
|
7320
7741
|
unlinkBestEffort(journalPath, fs27);
|
|
7321
7742
|
changed = true;
|
|
7322
7743
|
}
|
|
7323
7744
|
} catch (err) {
|
|
7324
|
-
|
|
7745
|
+
logger22.debug(
|
|
7325
7746
|
`[pool-manager] ttl cleanup stat failed for ${journalPath}: ${toErrorMessage(err)}`
|
|
7326
7747
|
);
|
|
7327
7748
|
}
|
|
@@ -7336,7 +7757,7 @@ function unlinkBestEffort(path21, fs27) {
|
|
|
7336
7757
|
try {
|
|
7337
7758
|
fs27.rmSync(path21, { force: true, recursive: true });
|
|
7338
7759
|
} catch (err) {
|
|
7339
|
-
|
|
7760
|
+
logger22.debug(`[pool-manager] ttl cleanup failed for ${path21}: ${toErrorMessage(err)}`);
|
|
7340
7761
|
}
|
|
7341
7762
|
}
|
|
7342
7763
|
function isJournalFile(name) {
|
|
@@ -7353,7 +7774,7 @@ function deletePoolNativeState(poolDir, fs27) {
|
|
|
7353
7774
|
for (const entry of entries) {
|
|
7354
7775
|
if (isJournalFile(entry.name)) continue;
|
|
7355
7776
|
try {
|
|
7356
|
-
fs27.rmSync(
|
|
7777
|
+
fs27.rmSync(join14(poolDir, entry.name), { recursive: true, force: true });
|
|
7357
7778
|
} catch (err) {
|
|
7358
7779
|
failures.push(`${entry.name}: ${toErrorMessage(err)}`);
|
|
7359
7780
|
}
|
|
@@ -7375,16 +7796,16 @@ function deletePoolNativeState(poolDir, fs27) {
|
|
|
7375
7796
|
}
|
|
7376
7797
|
}
|
|
7377
7798
|
function markCleanupFailed(poolDir, failures, fs27) {
|
|
7378
|
-
const marker =
|
|
7799
|
+
const marker = join14(poolDir, POOL_CLEANUP_FAILED_MARKER);
|
|
7379
7800
|
const payload = JSON.stringify({ ts: Date.now(), failures });
|
|
7380
|
-
|
|
7801
|
+
logger22.warn(
|
|
7381
7802
|
`[pool-manager] pool cleanup failed for ${poolDir} (${failures.length} item(s)); marker written to ${marker} \u2014 re-run cleanup after fixing the underlying error`
|
|
7382
7803
|
);
|
|
7383
7804
|
try {
|
|
7384
7805
|
fs27.writeFileSync(marker, `${payload}
|
|
7385
7806
|
`);
|
|
7386
7807
|
} catch (err) {
|
|
7387
|
-
|
|
7808
|
+
logger22.warn(
|
|
7388
7809
|
`[pool-manager] failed to write cleanup-failed marker ${marker}: ${toErrorMessage(err)}`
|
|
7389
7810
|
);
|
|
7390
7811
|
}
|
|
@@ -7402,7 +7823,7 @@ var nodeFs = {
|
|
|
7402
7823
|
};
|
|
7403
7824
|
|
|
7404
7825
|
// src/execution/idle-gc.ts
|
|
7405
|
-
var
|
|
7826
|
+
var logger23 = getLogger("subagents");
|
|
7406
7827
|
var GC_INTERVAL_MS = 60 * 60 * 1e3;
|
|
7407
7828
|
var IDLE_TTL_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
7408
7829
|
var MS_PER_DAY = 24 * 60 * 60 * 1e3;
|
|
@@ -7413,7 +7834,7 @@ function startIdleGc(store) {
|
|
|
7413
7834
|
if (isResumable(record) && record.idleSince) {
|
|
7414
7835
|
const age = now - record.idleSince;
|
|
7415
7836
|
if (age > IDLE_TTL_MS) {
|
|
7416
|
-
|
|
7837
|
+
logger23.warn(`[subagents] GC: archiving idle record ${record.id} (idle for ${Math.round(age / MS_PER_DAY)}d)`);
|
|
7417
7838
|
try {
|
|
7418
7839
|
store.archive(record);
|
|
7419
7840
|
} catch (err) {
|
|
@@ -7496,15 +7917,15 @@ function createBackgroundStream(recordId, sink, mode, env) {
|
|
|
7496
7917
|
|
|
7497
7918
|
// src/execution/worktree-manager.ts
|
|
7498
7919
|
import { execFile as execFile3 } from "child_process";
|
|
7499
|
-
import * as
|
|
7920
|
+
import * as fs19 from "fs";
|
|
7500
7921
|
import * as os2 from "os";
|
|
7501
|
-
import * as
|
|
7922
|
+
import * as path14 from "path";
|
|
7502
7923
|
|
|
7503
7924
|
// src/execution/worktree-registry.ts
|
|
7504
|
-
import * as
|
|
7505
|
-
import * as
|
|
7925
|
+
import * as fs18 from "fs";
|
|
7926
|
+
import * as path13 from "path";
|
|
7506
7927
|
import lockfile from "proper-lockfile";
|
|
7507
|
-
var
|
|
7928
|
+
var logger24 = getLogger("subagents");
|
|
7508
7929
|
var SPAWN_GRACE_MS = 6e4;
|
|
7509
7930
|
var JSON_INDENT2 = 2;
|
|
7510
7931
|
var LOCK_STALE_MS = 3e4;
|
|
@@ -7515,7 +7936,7 @@ function isRegistryData(value) {
|
|
|
7515
7936
|
var WorktreeRegistry = class {
|
|
7516
7937
|
filePath;
|
|
7517
7938
|
constructor(agentDir) {
|
|
7518
|
-
this.filePath =
|
|
7939
|
+
this.filePath = path13.join(agentDir, "subagents", "worktrees.json");
|
|
7519
7940
|
}
|
|
7520
7941
|
/**
|
|
7521
7942
|
* 新增条目(create 成功后调,pid=0 占位)。
|
|
@@ -7587,7 +8008,7 @@ var WorktreeRegistry = class {
|
|
|
7587
8008
|
run();
|
|
7588
8009
|
});
|
|
7589
8010
|
} catch (lockErr) {
|
|
7590
|
-
|
|
8011
|
+
logger24.warn("[worktree] registry lock unavailable, degraded to lock-free RMW", {
|
|
7591
8012
|
...context ?? {},
|
|
7592
8013
|
err: lockErr instanceof Error ? lockErr.message : String(lockErr)
|
|
7593
8014
|
});
|
|
@@ -7612,8 +8033,8 @@ var WorktreeRegistry = class {
|
|
|
7612
8033
|
* 锁参数值由 LOCK_STALE_MS / LOCK_RETRIES 常量承载(防漂移说明见常量注释)。
|
|
7613
8034
|
*/
|
|
7614
8035
|
async withLock(fn) {
|
|
7615
|
-
const dir =
|
|
7616
|
-
if (!
|
|
8036
|
+
const dir = path13.dirname(this.filePath);
|
|
8037
|
+
if (!fs18.existsSync(dir)) fs18.mkdirSync(dir, { recursive: true });
|
|
7617
8038
|
let compromised;
|
|
7618
8039
|
const release = await lockfile.lock(this.filePath, {
|
|
7619
8040
|
realpath: false,
|
|
@@ -7636,7 +8057,7 @@ var WorktreeRegistry = class {
|
|
|
7636
8057
|
try {
|
|
7637
8058
|
await release();
|
|
7638
8059
|
} catch (unlockErr) {
|
|
7639
|
-
|
|
8060
|
+
logger24.debug("unlock failed after compromise (ignorable)", {
|
|
7640
8061
|
detail: { err: unlockErr instanceof Error ? unlockErr.message : String(unlockErr) }
|
|
7641
8062
|
});
|
|
7642
8063
|
}
|
|
@@ -7648,7 +8069,7 @@ var WorktreeRegistry = class {
|
|
|
7648
8069
|
*/
|
|
7649
8070
|
load() {
|
|
7650
8071
|
try {
|
|
7651
|
-
const raw =
|
|
8072
|
+
const raw = fs18.readFileSync(this.filePath, "utf-8");
|
|
7652
8073
|
const parsed = JSON.parse(raw);
|
|
7653
8074
|
if (isRegistryData(parsed)) {
|
|
7654
8075
|
return parsed.entries;
|
|
@@ -7671,7 +8092,7 @@ var WorktreeRegistry = class {
|
|
|
7671
8092
|
writeAtomicFileSync(this.filePath, JSON.stringify({ entries }, null, JSON_INDENT2));
|
|
7672
8093
|
} catch (err) {
|
|
7673
8094
|
bestEffort(err, "worktree registry save");
|
|
7674
|
-
|
|
8095
|
+
logger24.warn(
|
|
7675
8096
|
"[worktree] registry save failed; pid may stay 0 and be reaped by orphan reaper",
|
|
7676
8097
|
{ ...context ?? {}, err: err instanceof Error ? err.message : String(err) }
|
|
7677
8098
|
);
|
|
@@ -7680,7 +8101,7 @@ var WorktreeRegistry = class {
|
|
|
7680
8101
|
};
|
|
7681
8102
|
|
|
7682
8103
|
// src/execution/worktree-manager.ts
|
|
7683
|
-
var
|
|
8104
|
+
var logger25 = getLogger("subagents");
|
|
7684
8105
|
var SAFE_ID_RE = /^[\w-]+$/;
|
|
7685
8106
|
var GIT_TIMEOUT_MS = 3e4;
|
|
7686
8107
|
var WORKTREE_TMP_ROOT = "pi-subagents";
|
|
@@ -7705,13 +8126,13 @@ function isWriteCommand(args) {
|
|
|
7705
8126
|
}
|
|
7706
8127
|
function resolveRepoFromCheckout(checkout) {
|
|
7707
8128
|
try {
|
|
7708
|
-
const raw =
|
|
8129
|
+
const raw = fs19.readFileSync(path14.join(checkout, ".git"), "utf-8").trim();
|
|
7709
8130
|
if (!raw.startsWith("gitdir:")) return void 0;
|
|
7710
8131
|
const gitdir = raw.slice("gitdir:".length).trim();
|
|
7711
|
-
const worktreesDir =
|
|
7712
|
-
if (
|
|
7713
|
-
const gitRootDir =
|
|
7714
|
-
return
|
|
8132
|
+
const worktreesDir = path14.dirname(gitdir);
|
|
8133
|
+
if (path14.basename(worktreesDir) !== "worktrees") return void 0;
|
|
8134
|
+
const gitRootDir = path14.dirname(worktreesDir);
|
|
8135
|
+
return path14.dirname(gitRootDir);
|
|
7715
8136
|
} catch {
|
|
7716
8137
|
return void 0;
|
|
7717
8138
|
}
|
|
@@ -7764,10 +8185,10 @@ ${statusText}`
|
|
|
7764
8185
|
if (revR.status === "rejected") throw revR.reason;
|
|
7765
8186
|
const baseCommit = revR.value.trim();
|
|
7766
8187
|
const branch = `pi-sub-${recordId}`;
|
|
7767
|
-
const worktreePath =
|
|
7768
|
-
if (
|
|
8188
|
+
const worktreePath = path14.join(os2.tmpdir(), "pi-subagents", encodeCwd(mainCwd), branch);
|
|
8189
|
+
if (fs19.existsSync(worktreePath)) {
|
|
7769
8190
|
try {
|
|
7770
|
-
|
|
8191
|
+
fs19.rmSync(worktreePath, { recursive: true, force: true });
|
|
7771
8192
|
} catch (cleanErr) {
|
|
7772
8193
|
bestEffort(cleanErr, "pre-create checkout cleanup");
|
|
7773
8194
|
}
|
|
@@ -7783,10 +8204,10 @@ ${statusText}`
|
|
|
7783
8204
|
createdAt: Date.now()
|
|
7784
8205
|
});
|
|
7785
8206
|
try {
|
|
7786
|
-
const mainNodeModules =
|
|
7787
|
-
const worktreeNodeModules =
|
|
7788
|
-
if (
|
|
7789
|
-
|
|
8207
|
+
const mainNodeModules = path14.join(mainCwd, "node_modules");
|
|
8208
|
+
const worktreeNodeModules = path14.join(worktreePath, "node_modules");
|
|
8209
|
+
if (fs19.existsSync(mainNodeModules) && !fs19.existsSync(worktreeNodeModules)) {
|
|
8210
|
+
fs19.symlinkSync(mainNodeModules, worktreeNodeModules);
|
|
7790
8211
|
}
|
|
7791
8212
|
return Object.freeze({
|
|
7792
8213
|
path: worktreePath,
|
|
@@ -7874,7 +8295,7 @@ ${statusText}`
|
|
|
7874
8295
|
return Object.freeze({ patchFile, failed: false, written: false });
|
|
7875
8296
|
}
|
|
7876
8297
|
try {
|
|
7877
|
-
|
|
8298
|
+
fs19.writeFileSync(patchFile, diff, "utf-8");
|
|
7878
8299
|
return Object.freeze({ patchFile, failed: false, written: true });
|
|
7879
8300
|
} catch {
|
|
7880
8301
|
return Object.freeze({ patchFile, failed: true, written: false });
|
|
@@ -7937,9 +8358,9 @@ ${statusText}`
|
|
|
7937
8358
|
const branches = branchesByRepo.get(entry.repo);
|
|
7938
8359
|
if (branches === void 0) continue;
|
|
7939
8360
|
const branchGone = !branches.has(entry.branch);
|
|
7940
|
-
const checkoutGone = !
|
|
8361
|
+
const checkoutGone = !fs19.existsSync(entry.checkout);
|
|
7941
8362
|
if (branchGone && checkoutGone) {
|
|
7942
|
-
|
|
8363
|
+
logger25.warn("[worktree] reconcile: registry entry has no physical worktree/branch, removing entry", {
|
|
7943
8364
|
branch: entry.branch,
|
|
7944
8365
|
repo: entry.repo,
|
|
7945
8366
|
pid: entry.pid
|
|
@@ -7983,14 +8404,14 @@ ${statusText}`
|
|
|
7983
8404
|
}
|
|
7984
8405
|
if (alivePids.length === 1 && list.length === 1) {
|
|
7985
8406
|
const pt = list[0];
|
|
7986
|
-
|
|
8407
|
+
logger25.warn("[worktree] reconcile: unregistered physical worktree with one alive pid, re-registering (self-heal)", {
|
|
7987
8408
|
branch: pt.branch,
|
|
7988
8409
|
checkout: pt.checkout,
|
|
7989
8410
|
repo: pt.repo,
|
|
7990
8411
|
pid: alivePids[0]
|
|
7991
8412
|
});
|
|
7992
8413
|
await this.registry.add({
|
|
7993
|
-
repo: pt.repo ??
|
|
8414
|
+
repo: pt.repo ?? path14.dirname(pt.checkout),
|
|
7994
8415
|
branch: pt.branch,
|
|
7995
8416
|
checkout: pt.checkout,
|
|
7996
8417
|
pid: alivePids[0],
|
|
@@ -8006,7 +8427,7 @@ ${statusText}`
|
|
|
8006
8427
|
if (cycles < RECONCILE_SKIP_ESCALATION_CYCLES) continue;
|
|
8007
8428
|
escalated++;
|
|
8008
8429
|
const repoHint = pt.repo ?? "<main-repo>";
|
|
8009
|
-
|
|
8430
|
+
logger25.warn(
|
|
8010
8431
|
`[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}`,
|
|
8011
8432
|
{
|
|
8012
8433
|
branch: pt.branch,
|
|
@@ -8017,7 +8438,7 @@ ${statusText}`
|
|
|
8017
8438
|
);
|
|
8018
8439
|
}
|
|
8019
8440
|
if (escalated < list.length) {
|
|
8020
|
-
|
|
8441
|
+
logger25.warn("[worktree] reconcile: unregistered physical worktrees present but alive-pid mapping ambiguous, skipping this cycle", {
|
|
8021
8442
|
enc,
|
|
8022
8443
|
orphans: list.length - escalated,
|
|
8023
8444
|
alivePids: alivePids.length
|
|
@@ -8030,7 +8451,7 @@ ${statusText}`
|
|
|
8030
8451
|
for (const pt of list) {
|
|
8031
8452
|
const age = Date.now() - pt.mtimeMs;
|
|
8032
8453
|
if (age <= SPAWN_GRACE_MS) continue;
|
|
8033
|
-
|
|
8454
|
+
logger25.warn("[worktree] reconcile: unregistered physical worktree with no alive pid, cleaning up", {
|
|
8034
8455
|
branch: pt.branch,
|
|
8035
8456
|
checkout: pt.checkout,
|
|
8036
8457
|
repo: pt.repo,
|
|
@@ -8046,10 +8467,10 @@ ${statusText}`
|
|
|
8046
8467
|
* 推导失败(残缺 checkout)repo=undefined,由调用方按无主残留处置。
|
|
8047
8468
|
*/
|
|
8048
8469
|
async discoverPhysicalWorktrees() {
|
|
8049
|
-
const root =
|
|
8470
|
+
const root = path14.join(os2.tmpdir(), WORKTREE_TMP_ROOT);
|
|
8050
8471
|
let encDirs;
|
|
8051
8472
|
try {
|
|
8052
|
-
encDirs =
|
|
8473
|
+
encDirs = fs19.readdirSync(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
8053
8474
|
} catch {
|
|
8054
8475
|
return [];
|
|
8055
8476
|
}
|
|
@@ -8057,14 +8478,14 @@ ${statusText}`
|
|
|
8057
8478
|
for (const enc of encDirs) {
|
|
8058
8479
|
let branchDirs;
|
|
8059
8480
|
try {
|
|
8060
|
-
branchDirs =
|
|
8481
|
+
branchDirs = fs19.readdirSync(path14.join(root, enc), { withFileTypes: true }).filter((d) => d.isDirectory() && d.name.startsWith(BRANCH_PREFIX)).map((d) => d.name);
|
|
8061
8482
|
} catch {
|
|
8062
8483
|
continue;
|
|
8063
8484
|
}
|
|
8064
8485
|
for (const branch of branchDirs) {
|
|
8065
|
-
const checkout =
|
|
8486
|
+
const checkout = path14.join(root, enc, branch);
|
|
8066
8487
|
try {
|
|
8067
|
-
const mtimeMs =
|
|
8488
|
+
const mtimeMs = fs19.statSync(checkout).mtimeMs;
|
|
8068
8489
|
result.push({ enc, branch, checkout, repo: resolveRepoFromCheckout(checkout), mtimeMs });
|
|
8069
8490
|
} catch (err) {
|
|
8070
8491
|
bestEffort(err, "physical worktree stat (reconcile)");
|
|
@@ -8102,17 +8523,17 @@ ${statusText}`
|
|
|
8102
8523
|
* 崩溃残留的 .alive(pid 已死)天然过滤掉——这正是「死活判据」的物理面来源。
|
|
8103
8524
|
*/
|
|
8104
8525
|
collectAlivePids(enc) {
|
|
8105
|
-
const sessionsDir =
|
|
8526
|
+
const sessionsDir = path14.join(this.agentDir, "subagents", enc, "sessions");
|
|
8106
8527
|
let files;
|
|
8107
8528
|
try {
|
|
8108
|
-
files =
|
|
8529
|
+
files = fs19.readdirSync(sessionsDir);
|
|
8109
8530
|
} catch {
|
|
8110
8531
|
return [];
|
|
8111
8532
|
}
|
|
8112
8533
|
const pids = /* @__PURE__ */ new Set();
|
|
8113
8534
|
for (const file of files) {
|
|
8114
8535
|
if (!file.endsWith(".alive")) continue;
|
|
8115
|
-
const marker = readAliveMarker(
|
|
8536
|
+
const marker = readAliveMarker(path14.join(sessionsDir, file.slice(0, -".alive".length)));
|
|
8116
8537
|
if (marker && isProcessAlive(marker.pid)) {
|
|
8117
8538
|
pids.add(marker.pid);
|
|
8118
8539
|
}
|
|
@@ -8144,8 +8565,8 @@ ${statusText}`
|
|
|
8144
8565
|
}
|
|
8145
8566
|
}
|
|
8146
8567
|
try {
|
|
8147
|
-
if (
|
|
8148
|
-
|
|
8568
|
+
if (fs19.existsSync(pt.checkout)) {
|
|
8569
|
+
fs19.rmSync(pt.checkout, { recursive: true, force: true });
|
|
8149
8570
|
}
|
|
8150
8571
|
} catch (err) {
|
|
8151
8572
|
bestEffort(err, "checkout dir rm (reconcile)");
|
|
@@ -8158,7 +8579,7 @@ ${statusText}`
|
|
|
8158
8579
|
if (entry.pid === 0) {
|
|
8159
8580
|
const expired = now - entry.createdAt > SPAWN_GRACE_MS;
|
|
8160
8581
|
if (expired) {
|
|
8161
|
-
|
|
8582
|
+
logger25.warn(
|
|
8162
8583
|
"[worktree] orphan reaper: pid=0 entry exceeded SPAWN_GRACE_MS, treating as orphan",
|
|
8163
8584
|
{ branch: entry.branch, checkout: entry.checkout, createdAt: entry.createdAt, now }
|
|
8164
8585
|
);
|
|
@@ -8234,7 +8655,7 @@ ${statusText}`
|
|
|
8234
8655
|
};
|
|
8235
8656
|
|
|
8236
8657
|
// src/execution/subagent-service.ts
|
|
8237
|
-
var
|
|
8658
|
+
var logger26 = getLogger("subagents");
|
|
8238
8659
|
var disposedUiRequestStub = () => Promise.resolve({ cancelled: true });
|
|
8239
8660
|
var PRIORITY_BACKGROUND = 1e3;
|
|
8240
8661
|
var MS_PER_SECOND6 = 1e3;
|
|
@@ -8249,6 +8670,7 @@ var ENV_ROOT_SESSION_ID = "PI_SUBAGENT_ROOT_SESSION_ID";
|
|
|
8249
8670
|
var ENV_SELF_RECORD_ID = "PI_SUBAGENT_SELF_RECORD_ID";
|
|
8250
8671
|
var ENV_DEPTH = "PI_SUBAGENT_DEPTH";
|
|
8251
8672
|
var ENV_ROOT_CWD = "PI_SUBAGENT_ROOT_CWD";
|
|
8673
|
+
var SETTLED_RESCAN_LIMIT = 8;
|
|
8252
8674
|
var SubagentService = class {
|
|
8253
8675
|
pool;
|
|
8254
8676
|
store;
|
|
@@ -8299,12 +8721,29 @@ var SubagentService = class {
|
|
|
8299
8721
|
/** [D4-①] 通知簇 host 面(notifyComplete/notifyClosed/pending 注册注销 + notifier
|
|
8300
8722
|
* 实例封装,原私有通知簇四方法与模块函数的搬移落点——notify-host.ts)。
|
|
8301
8723
|
* deps 惰性求值(pi/session 级状态运行时可变),行为与原 constructor 内
|
|
8302
|
-
* createNotifier(this.piAdapter()) 逐字节等价。session_start revive,shutdown dispose。
|
|
8724
|
+
* createNotifier(this.piAdapter()) 逐字节等价。session_start revive,shutdown dispose。
|
|
8725
|
+
* [sync-collect 合并] toNotifyRecord/notify/notifyBatch 随批路由需要由 host 导出
|
|
8726
|
+
* (collectCoordinator 闭包与 E9/E1 直发路径消费,见 notify-host.ts 接口注释)。 */
|
|
8303
8727
|
notifyHost = createNotifyHost({
|
|
8304
8728
|
getPi: () => this.pi,
|
|
8305
8729
|
listRunning: () => this.store.listRunning(),
|
|
8306
8730
|
getIsIdle: () => this.isIdleFn
|
|
8307
8731
|
});
|
|
8732
|
+
/** collectCoordinator(subagent-sync-collect U2):sync 批缓冲 + 闭合检测 + flush 分流。 */
|
|
8733
|
+
collectCoordinator;
|
|
8734
|
+
/** [E9] dispose 时已转 async 写账的成员 id(revive 后 flushBatch 防御过滤用)。
|
|
8735
|
+
* 背景:dispose 后同进程 revive(/resume /fork /new)时协调器内部缓冲仍持有已转换
|
|
8736
|
+
* 成员快照(协调器无 drain API,U5 领地不含 collect-coordinator.ts)——若后续新
|
|
8737
|
+
* sync 成员触发闭合,陈旧快照会随批重投(新成员集新 hash,账本跨键不拦)→ 双重
|
|
8738
|
+
* 通知。flushBatch 闭包按本集过滤,陈旧成员零重投。id 唯一 per spawn,无误伤面。 */
|
|
8739
|
+
e9ConvertedIds = /* @__PURE__ */ new Set();
|
|
8740
|
+
/** [v2 D4] E1 等待分支的 settled 有界重扫状态(null = 未注册)。disposed 后保持
|
|
8741
|
+
* 非 null——同 session 内不再重复注册(补发完成/达限后 settled 边沿已无事可做,
|
|
8742
|
+
* 单注册即单重扫);initSession(revive)置 null 允许新 session 重新注册。
|
|
8743
|
+
* pi.on 无 off(见 armSettledRescan):旧 handler 闭包捕获旧 state,未 disposed 时
|
|
8744
|
+
* 遇 settled 边沿仍会执行,扫描 this.mainSessionFile 当前值(handler 不绑定注册时
|
|
8745
|
+
* 的文件域);dispose 的惰化处置见 dispose()。 */
|
|
8746
|
+
settledRescanState = null;
|
|
8308
8747
|
/** [MF#4][MF#2] fork 深度按 async 调用链传递(AsyncLocalStorage),替代共享可变计数器。
|
|
8309
8748
|
* 主 session=0;fork 进入子 session 期间推进为子深度,供嵌套 fork 经 ALS 读到自身深度作为
|
|
8310
8749
|
* parentForkDepth。并发 background fork 各自独立调用链,不再互相压低深度值。
|
|
@@ -8333,7 +8772,7 @@ var SubagentService = class {
|
|
|
8333
8772
|
/** [D4-②] 轮次结算回调(原 buildSessionRunnerContext 内的 onRoundSettled 业务闭包
|
|
8334
8773
|
* 搬移至 round-settlement.ts;deps 回调闭包惰性求值,session-runner agent_settled 时消费)。 */
|
|
8335
8774
|
settleRound = createRoundSettler({
|
|
8336
|
-
notifyComplete: (record) => this.
|
|
8775
|
+
notifyComplete: (record) => this.collectCoordinator.route(record),
|
|
8337
8776
|
reportRecordTransition: (record) => this.store.reportRecordTransition(record),
|
|
8338
8777
|
closeAfterRoundSettled: (record) => this.closeAfterRoundSettled(record)
|
|
8339
8778
|
});
|
|
@@ -8364,6 +8803,11 @@ var SubagentService = class {
|
|
|
8364
8803
|
deliverChatMessage: (record, text, interrupt) => this.deliverChatMessage(record, text, interrupt)
|
|
8365
8804
|
};
|
|
8366
8805
|
manifestStore;
|
|
8806
|
+
/**
|
|
8807
|
+
* [D6 #7a] records 目录(与 manifestStore 同源同一推导)——屏障失败 warn 带 manifest
|
|
8808
|
+
* 文件路径用(ManifestStore.dir 私有,此处不破封装另存同源值;漂移由构造点同语句保证不发生)。
|
|
8809
|
+
*/
|
|
8810
|
+
recordsDir;
|
|
8367
8811
|
/**
|
|
8368
8812
|
* [T1/PS-9] subagent sessionDir(getSubagentSessionDir 推导,与 store 同源同一 rootCwd)。
|
|
8369
8813
|
* 传给 doFinalizeRecord 的 FinalizeDeps.sessionDir——record.sessionFile 缺失时 finalize
|
|
@@ -8382,8 +8826,42 @@ var SubagentService = class {
|
|
|
8382
8826
|
const sessionsDir = getSubagentSessionDir(this.modelService.getAgentDir(), this.rootCwd);
|
|
8383
8827
|
const recordsDir = getSubagentRecordsDir(this.modelService.getAgentDir(), this.rootCwd);
|
|
8384
8828
|
this.sessionsDir = sessionsDir;
|
|
8829
|
+
this.recordsDir = recordsDir;
|
|
8385
8830
|
this.manifestStore = new ManifestStore(recordsDir);
|
|
8386
8831
|
this.store = new RecordStore(sessionsDir, this.manifestStore, this.pi ?? void 0);
|
|
8832
|
+
this.collectCoordinator = new CollectCoordinator({
|
|
8833
|
+
notifyAsync: (record) => {
|
|
8834
|
+
const notify = this.notifyHost.toNotifyRecord(record);
|
|
8835
|
+
if (notify) this.notifyHost.notify(notify);
|
|
8836
|
+
},
|
|
8837
|
+
toNotifyRecord: (record) => this.notifyHost.toNotifyRecord(record),
|
|
8838
|
+
// 闭合判定数据源:listAllActive(原始 ExecutionRecord 内存态,携带 collectMode/
|
|
8839
|
+
// batchFinalized 原始值)。[U3 修正] 原接 collectRecords——其经 recordToSubagent
|
|
8840
|
+
// 投影丢 collectMode(U2 披露的投影缺口)→ 真链上闭合判定恒立即闭合、跨轮续累
|
|
8841
|
+
// 失效(真链 trace 实证)。非终态 sync 成员必在内存(archive 只删终态;磁盘重建
|
|
8842
|
+
// 残留属孤儿恢复域),内存视图语义完整。
|
|
8843
|
+
listRecords: (limit) => this.store.listAllActive().slice(0, limit),
|
|
8844
|
+
flushBatch: async (members) => {
|
|
8845
|
+
const live = members.filter((m) => !this.e9ConvertedIds.has(m.id));
|
|
8846
|
+
if (live.length === 0) return;
|
|
8847
|
+
const fulls = [];
|
|
8848
|
+
const fullMissed = [];
|
|
8849
|
+
for (const m of live) {
|
|
8850
|
+
const full = this.store.getFullRecord(m.id);
|
|
8851
|
+
if (full) fulls.push(full);
|
|
8852
|
+
else fullMissed.push(m);
|
|
8853
|
+
}
|
|
8854
|
+
await this.writeSyncBatchManifestBarrier(fulls);
|
|
8855
|
+
const accepted = this.notifyHost.notifyBatch(live, this.getCollectSyncBudget());
|
|
8856
|
+
if (!accepted) return;
|
|
8857
|
+
for (const full of fulls) {
|
|
8858
|
+
this.appendBatchFinalizedEntry(full);
|
|
8859
|
+
}
|
|
8860
|
+
for (const m of fullMissed) {
|
|
8861
|
+
this.appendBatchFinalizedEntry(bufferedMemberFallbackRecord(m, this.sessionRootId ?? void 0));
|
|
8862
|
+
}
|
|
8863
|
+
}
|
|
8864
|
+
});
|
|
8387
8865
|
registerGlobalObservability(this.uiObservability);
|
|
8388
8866
|
}
|
|
8389
8867
|
// ── 生命周期(index.ts 调)──────────────────────────────
|
|
@@ -8408,6 +8886,7 @@ var SubagentService = class {
|
|
|
8408
8886
|
this.sessionRootId = envRoot ?? init.sessionId;
|
|
8409
8887
|
this.initExecContextBaseline(envRoot, init.sessionId);
|
|
8410
8888
|
this._disposed = false;
|
|
8889
|
+
this.settledRescanState = null;
|
|
8411
8890
|
this.store.revive();
|
|
8412
8891
|
this.notifyHost.revive();
|
|
8413
8892
|
this.recoverOrphansIfRootProcess();
|
|
@@ -8441,7 +8920,7 @@ var SubagentService = class {
|
|
|
8441
8920
|
this.execNesting.setBaseline({ recordId: envSelfRecord, depth: nestingDepth });
|
|
8442
8921
|
this.execNesting.enterWith({ recordId: envSelfRecord, depth: nestingDepth });
|
|
8443
8922
|
if (process.env.XYZ_AGENT_DEBUG) {
|
|
8444
|
-
|
|
8923
|
+
logger26.debug(
|
|
8445
8924
|
`[subagents] execNesting initialized: recordId=${envSelfRecord} depth=${nestingDepth} rootSessionId=${envRoot ?? sessionId}`
|
|
8446
8925
|
);
|
|
8447
8926
|
}
|
|
@@ -8465,26 +8944,28 @@ var SubagentService = class {
|
|
|
8465
8944
|
if (!isChildProcess) {
|
|
8466
8945
|
this.recoverOrphanRecords();
|
|
8467
8946
|
} else if (process.env.XYZ_AGENT_DEBUG) {
|
|
8468
|
-
|
|
8947
|
+
logger26.debug("[subagents] child process detected (PI_SUBAGENT_SELF_RECORD_ID set), skipping orphan recovery scan");
|
|
8469
8948
|
}
|
|
8470
8949
|
}
|
|
8471
8950
|
/** 孤儿终态恢复委托(RecordStore.recoverOrphanRecords 的唯一调用入口,维持 store
|
|
8472
8951
|
* private 封装——与 recoverManifestTmpFiles 同模式;[D4] public 面收窄:唯一调用方
|
|
8473
8952
|
* 是 initSession,转 private)。判定语义见 store 侧注释。
|
|
8953
|
+
* mainSessionFile 随调用透传(v2 D3 覆写 merge 数据源:主文件末条 entry 的批域
|
|
8954
|
+
* 标记与轮终 result/model;initSession 先赋值后恢复,时序就绪)。
|
|
8474
8955
|
* 随后跑 entry-born 孤儿恢复(无子文件锚的 register-only record,spawn 窗口期死亡,
|
|
8475
8956
|
* E2E 实测缺口)——主 session 文件经 getMainSessionFile 注入(构造期可空)。 */
|
|
8476
8957
|
recoverOrphanRecords() {
|
|
8477
8958
|
try {
|
|
8478
|
-
this.store.recoverOrphanRecords(this.sessionRootId ?? void 0);
|
|
8959
|
+
this.store.recoverOrphanRecords(this.sessionRootId ?? void 0, this.mainSessionFile);
|
|
8479
8960
|
} catch (err) {
|
|
8480
|
-
|
|
8961
|
+
logger26.warn("[subagents] orphan recovery failed", {
|
|
8481
8962
|
reason: toErrorMessage(err)
|
|
8482
8963
|
});
|
|
8483
8964
|
}
|
|
8484
8965
|
try {
|
|
8485
8966
|
this.store.recoverEntryOnlyOrphans(this.mainSessionFile, this.sessionRootId ?? void 0);
|
|
8486
8967
|
} catch (err) {
|
|
8487
|
-
|
|
8968
|
+
logger26.warn("[subagents] entry-only orphan recovery failed", {
|
|
8488
8969
|
reason: toErrorMessage(err)
|
|
8489
8970
|
});
|
|
8490
8971
|
}
|
|
@@ -8582,14 +9063,232 @@ var SubagentService = class {
|
|
|
8582
9063
|
* listener 仍然存活。若 pending-notifications 先于本扩展执行 session_shutdown(后注册
|
|
8583
9064
|
* 先执行的语义下会如此),listener 已注销,unregister 事件被静默丢弃。这是可接受的
|
|
8584
9065
|
* 退化——进程退出后两侧状态本就不保证一致,下次 session_start 的 crash recovery 会修正。 */
|
|
9066
|
+
/** [U4 deviation #8 接线] collectSync 预算热读(flush 时读值,与 getCollectSyncDefault
|
|
9067
|
+
* 同款访问链 modelService.getGlobalConfig().collectSync)。节缺失/读失败 → undefined
|
|
9068
|
+
* → notifyBatch 落 buildBatchLlmContent 设计默认值(4000/24000,E5 不炸启动)。
|
|
9069
|
+
* sanitizeCollectSync 保证节存在时两字段必有合法正整数。 */
|
|
9070
|
+
getCollectSyncBudget() {
|
|
9071
|
+
const cs = this.collectSyncSection();
|
|
9072
|
+
return cs !== void 0 ? { perItemChars: cs.perItemChars, totalChars: cs.totalChars } : void 0;
|
|
9073
|
+
}
|
|
9074
|
+
/** [E9 专用] batchFinalized 落标 + manifest fire-and-forget 写(设计 §3.1.5 E9)。
|
|
9075
|
+
* 批通知路径(flush/E1)已改走「manifest 屏障 → 写账 → 纯落标」序列(「通知可达
|
|
9076
|
+
* ⇒ 索引就位」的构造性保证,见 flushBatch 闭包 / runSyncCollectRecoveryScan),
|
|
9077
|
+
* 不再经本 helper;仅 E9 转换的成员保持原形态——其走 async 单条通知(全文注入、
|
|
9078
|
+
* 无指针行消费),manifest 无时序要求,落标后 fire-and-forget 补写(list 后手动
|
|
9079
|
+
* 反查的顺带索引)。
|
|
9080
|
+
* 路径 = getFullRecord 冷路径重建 → appendBatchFinalizedEntry 纯落标 → fire
|
|
9081
|
+
* manifest。getFullRecord 不可达(子 session 文件缺失/已 GC)→ 跳过该成员
|
|
9082
|
+
* (详见 flushBatch 闭包注释)。 */
|
|
9083
|
+
markMembersBatchFinalized(memberIds) {
|
|
9084
|
+
for (const id of memberIds) {
|
|
9085
|
+
const full = this.store.getFullRecord(id);
|
|
9086
|
+
if (!full) continue;
|
|
9087
|
+
this.appendBatchFinalizedEntry(full);
|
|
9088
|
+
void this.writeBatchMemberManifest(full).catch((err) => {
|
|
9089
|
+
logger26.debug(
|
|
9090
|
+
`[subagents] batch-finalized manifest write failed (record=${full.id})`,
|
|
9091
|
+
{ reason: err instanceof Error ? err.message : String(err) }
|
|
9092
|
+
);
|
|
9093
|
+
});
|
|
9094
|
+
}
|
|
9095
|
+
}
|
|
9096
|
+
/** batchFinalized 落标唯一出口(appendEntry 公共末步,纯落标):显式覆写
|
|
9097
|
+
* collectMode/batchFinalized → reportSubagentRecord。覆写动机:recordToSubagent
|
|
9098
|
+
* 投影已含两字段(U5 修复),但 getFullRecord 冷路径含 sidecar/manifest 重建分支
|
|
9099
|
+
* (非 entry 源),显式赋值防非 entry 源重建时丢标记。
|
|
9100
|
+
*
|
|
9101
|
+
* [v2 D1 断链 1] 落标即「离开批 = 通知已/即将送达 = 指针行即将被消费」——成功
|
|
9102
|
+
* 成员走 SP-5 改道 doFinalizeRoundToIdle(不写 manifest),批路径不补写则
|
|
9103
|
+
* records/<sa-id>.json 永不产生、session-reader 反查 0 命中。manifest 写点已从
|
|
9104
|
+
* 本出口的 fire-and-forget 前移至各调用方:批通知路径(flush/E1)在写账前屏障
|
|
9105
|
+
* await 全部落盘(「通知可达 ⇒ 索引就位」的构造性保证);E9 保持落标后
|
|
9106
|
+
* fire-and-forget(async 单条通知无指针行消费,无时序要求)。
|
|
9107
|
+
* rec 两来源(flush 的 getFullRecord 内存全量 / E1 的 rebuildEntryRecord 重建
|
|
9108
|
+
* 快照)必需字段恒齐备(id/agentName←agent/rootSessionId/createdAt←startedAt),
|
|
9109
|
+
* task/slug/parentRecordId 等可选 undefined 自然缺省。 */
|
|
9110
|
+
appendBatchFinalizedEntry(rec) {
|
|
9111
|
+
this.store.reportSubagentRecord({ ...rec, collectMode: "sync", batchFinalized: true });
|
|
9112
|
+
}
|
|
9113
|
+
/** 批成员 manifest(sa- id → sessionFile 反查索引)写的唯一投影点(D2 字段投影 +
|
|
9114
|
+
* status 如实投影:成功成员此刻 record 实态 running+resumable → "running",后续
|
|
9115
|
+
* message upgrade 走完整 finalize 时 Step 4 原子覆盖为 "closed")。
|
|
9116
|
+
* 返回原始 promise 不吞错——失败语义由调用方定:批通知路径经
|
|
9117
|
+
* writeSyncBatchManifestBarrier 的 allSettled(debug 不阻断写账);E9 经
|
|
9118
|
+
* fire-and-forget catch(debug 不阻断落标)。 */
|
|
9119
|
+
writeBatchMemberManifest(rec) {
|
|
9120
|
+
return this.manifestStore.writeManifest({
|
|
9121
|
+
id: rec.id,
|
|
9122
|
+
rootSessionId: rec.rootSessionId ?? "",
|
|
9123
|
+
parentRecordId: rec.parentRecordId,
|
|
9124
|
+
agentName: rec.agent,
|
|
9125
|
+
status: rec.status,
|
|
9126
|
+
createdAt: rec.startedAt,
|
|
9127
|
+
completedAt: rec.endedAt,
|
|
9128
|
+
sessionFile: rec.sessionFile,
|
|
9129
|
+
task: rec.task,
|
|
9130
|
+
slug: rec.slug,
|
|
9131
|
+
model: rec.model
|
|
9132
|
+
});
|
|
9133
|
+
}
|
|
9134
|
+
/** [时序屏障] 批通知路径(flush/E1)专用:成员 manifest 并行写 + await 全部完成
|
|
9135
|
+
* (allSettled)后才允许写账投递——「通知可达 ⇒ 索引就位」的构造性保证。写失败
|
|
9136
|
+
* 不阻断(best-effort 语义与 doFinalizeRecord Step 4 一致:反查索引缺失只影响指针行
|
|
9137
|
+
* 反查,session-reader 错误文案已指引绝对路径兜底,不构成写账失败);warn 留痕
|
|
9138
|
+
* (D6 #7a / SC-1:屏障失败意味着该成员指针行反查索引缺失,debug 级在排障时不可见)。 */
|
|
9139
|
+
async writeSyncBatchManifestBarrier(recs) {
|
|
9140
|
+
const results = await Promise.allSettled(recs.map((rec) => this.writeBatchMemberManifest(rec)));
|
|
9141
|
+
for (let i = 0; i < results.length; i++) {
|
|
9142
|
+
const result = results[i];
|
|
9143
|
+
if (result.status === "rejected") {
|
|
9144
|
+
logger26.warn(
|
|
9145
|
+
`[subagents] batch-finalized manifest write failed (record=${recs[i].id}, manifest=${this.recordsDir}/${recs[i].id}.json)`,
|
|
9146
|
+
{ reason: result.reason instanceof Error ? result.reason.message : String(result.reason) }
|
|
9147
|
+
);
|
|
9148
|
+
}
|
|
9149
|
+
}
|
|
9150
|
+
}
|
|
9151
|
+
/** [E9] dispose 时批未闭合:缓冲中已终态未通知成员逐条转 async 语义写账(放弃攒批)
|
|
9152
|
+
* + 落 batchFinalized 标记(E1 重建扫描据此排除,防双重通知),交由既有 shutdown
|
|
9153
|
+
* flush / resume 重放兑底;仍在跑的成员走现有退出路径(disposeAllRecords 关闭,
|
|
9154
|
+
* 与 async 一致)。写账用 notifier.notify 现有通路(ledger.record + attemptDeliver)。
|
|
9155
|
+
* 源序:写账先于落标——写账后崩溃 → E1 重建收该成员,但 async notifyId 与批 hash
|
|
9156
|
+
* 跨键不拦的重发属设计披露的 E9 残余窗(at-least-once 良性,PS-17 同族,v1 接受)。 */
|
|
9157
|
+
convertPendingSyncBufferToAsync() {
|
|
9158
|
+
this.collectCoordinator.cancelScheduledFlush();
|
|
9159
|
+
const members = this.collectCoordinator.pendingMembers();
|
|
9160
|
+
if (members.length === 0) return;
|
|
9161
|
+
for (const member of members) {
|
|
9162
|
+
this.e9ConvertedIds.add(member.id);
|
|
9163
|
+
this.notifyHost.notify(member);
|
|
9164
|
+
}
|
|
9165
|
+
this.markMembersBatchFinalized(members.map((m) => m.id));
|
|
9166
|
+
logger26.warn(
|
|
9167
|
+
`[subagents] E9 dispose: converted ${members.length} buffered sync member(s) to async notify`,
|
|
9168
|
+
{ ids: members.map((m) => m.id) }
|
|
9169
|
+
);
|
|
9170
|
+
}
|
|
9171
|
+
/**
|
|
9172
|
+
* [E1] sync 批崩溃恢复钩子(设计 §3.1.5 E1,index.ts session_start 恢复编排处调用,
|
|
9173
|
+
* 须晚于 initSession——孤儿终态恢复先行收敛 running 成员,「全员终态」判定才可达):
|
|
9174
|
+
*
|
|
9175
|
+
* - 扫描主 session 文件每 id 末条 subagent-record entry(store.scanLastRecordEntries,
|
|
9176
|
+
* collectLastRecordEntries 同构 + 投影扩展含 collectMode/batchFinalized + 终态五
|
|
9177
|
+
* 字段;禁走 collectRecords light 路径——主 session 落标 entry 对它不可见);
|
|
9178
|
+
* - 只收 collectMode=sync 且无 batchFinalized 的成员(排除已通过批 flush 或 E9
|
|
9179
|
+
* 转换离场的,防双重通知),按 rootSessionId 过滤当前根;
|
|
9180
|
+
* - 全员终态且账本无同成员集批记录 → manifest 屏障(await 落盘,「通知可达 ⇒
|
|
9181
|
+
* 索引就位」构造性保证,与 flushBatch 同款)→ notifyBatch 补发(内容 = 末条
|
|
9182
|
+
* entry 终态快照;账本 record 同 hash 幂等拒绝 = 已投递/已在账,两种结局都算
|
|
9183
|
+
* 「已处理」);
|
|
9184
|
+
* - 仍有 running → 本次不动,注册 settled 有界重扫(D4,见 armSettledRescan)——
|
|
9185
|
+
* 成员延迟终态(主 agent 冷路径 resume → 正常流落 entry)由 settled 边沿驱动
|
|
9186
|
+
* 重扫收敛,不再依赖「下次 session_start」作唯一再驱动(v2 §2.4 断链 4);
|
|
9187
|
+
* running 口径与协调器同构
|
|
9188
|
+
* (resumable 豁免,v2 D3——覆写不可达的防御分支残余不被误判「仍在跑」);
|
|
9189
|
+
* - 补发尝试后统一补 batchFinalized 标记(账本拒绝也算已投递;直接用末条重建快照
|
|
9190
|
+
* 落标不经 getFullRecord——子文件缺失/已 GC 时标记仍可落盘,窗口自愈不依赖二次
|
|
9191
|
+
* 重启;补标自身崩溃重入幂等收敛,末条 entry last-writer-wins)。
|
|
9192
|
+
*
|
|
9193
|
+
* async 化(时序屏障修复):返回 Promise 但**内部自捕获不外抛**——宿主 index.ts
|
|
9194
|
+
* session_start 以同步 try/catch 调用(其 catch 兑现不到 promise 内的异常),
|
|
9195
|
+
* 自捕获维持同款 warn 容错语义,浮动调用零适配、不产生 unhandled rejection。
|
|
9196
|
+
*
|
|
9197
|
+
* [U8 拆批修复] 与协调器合批排程无交集:E1 只在 session_start 编排处运行(此前
|
|
9198
|
+
* dispose 已取消挂起排程),补发直走 notifier.notifyBatch 不经协调器;异常时序
|
|
9199
|
+
* 相撞由账本 sync-batch:<hash> 幂等拒绝兜底。
|
|
9200
|
+
*/
|
|
9201
|
+
async recoverSyncCollectBatch() {
|
|
9202
|
+
try {
|
|
9203
|
+
const { outcome } = await this.runSyncCollectRecoveryScan();
|
|
9204
|
+
if (outcome === "waiting") {
|
|
9205
|
+
this.armSettledRescan();
|
|
9206
|
+
}
|
|
9207
|
+
} catch (err) {
|
|
9208
|
+
logger26.warn("[subagents] sync collect batch recovery failed", {
|
|
9209
|
+
reason: err instanceof Error ? err.message : String(err)
|
|
9210
|
+
});
|
|
9211
|
+
}
|
|
9212
|
+
}
|
|
9213
|
+
/** [E1/D4] 单次「扫描→判定→可达则补发+落标」,E1 首扫与 settled 重扫共用同一实现
|
|
9214
|
+
* (防两处复制粘贴分岔)。三态返回:idle(无 sync 候选——已全部落标/E9 转换/异根,
|
|
9215
|
+
* 无事可等)/ waiting(仍有 running 成员,本次不动)/ dispatched(全员终态,已补发
|
|
9216
|
+
* +统一落标)。async:补发前有 manifest 屏障 await(见函数头 E1 注释)。 */
|
|
9217
|
+
/** 返回 outcome + waitingIds(达限 warn 需滞留成员 id,D6 #7b——仅 waiting 态非空)。 */
|
|
9218
|
+
async runSyncCollectRecoveryScan() {
|
|
9219
|
+
const lastRecords = this.store.scanLastRecordEntries(this.mainSessionFile);
|
|
9220
|
+
if (lastRecords.length === 0) return { outcome: "idle", waitingIds: [] };
|
|
9221
|
+
const rootFilter = this.sessionRootId;
|
|
9222
|
+
const candidates = lastRecords.filter(
|
|
9223
|
+
(r) => r.collectMode === "sync" && r.batchFinalized !== true && (rootFilter === void 0 || r.rootSessionId === rootFilter)
|
|
9224
|
+
);
|
|
9225
|
+
if (candidates.length === 0) return { outcome: "idle", waitingIds: [] };
|
|
9226
|
+
const running = candidates.filter((r) => r.resumable !== true && r.status !== "closed");
|
|
9227
|
+
if (running.length > 0) {
|
|
9228
|
+
logger26.debug(
|
|
9229
|
+
`[subagents] E1 sync batch recovery: ${running.length} member(s) still running, wait for natural completion`,
|
|
9230
|
+
{ ids: running.map((r) => r.id) }
|
|
9231
|
+
);
|
|
9232
|
+
return { outcome: "waiting", waitingIds: running.map((r) => r.id) };
|
|
9233
|
+
}
|
|
9234
|
+
await this.writeSyncBatchManifestBarrier(candidates);
|
|
9235
|
+
const members = candidates.map((r) => syncRebuildToNotifyMember(r));
|
|
9236
|
+
const accepted = this.notifyHost.notifyBatch(members, this.getCollectSyncBudget());
|
|
9237
|
+
for (const rec of candidates) {
|
|
9238
|
+
this.appendBatchFinalizedEntry(rec);
|
|
9239
|
+
}
|
|
9240
|
+
logger26.warn(
|
|
9241
|
+
`[subagents] E1 sync batch recovery: re-notified ${members.length} member(s) (ledger accepted=${accepted})`,
|
|
9242
|
+
{ ids: members.map((m) => m.id) }
|
|
9243
|
+
);
|
|
9244
|
+
return { outcome: "dispatched", waitingIds: [] };
|
|
9245
|
+
}
|
|
9246
|
+
/** [v2 D4] 注册 agent_settled 有界重扫(幂等:settledRescanState 非 null 不叠加注册
|
|
9247
|
+
* ——E1 现仅 session_start 单调用点,守卫是防第二入口引入时的注册叠加断言面)。
|
|
9248
|
+
* 每次 settled 边沿重跑同一 E1 扫描(runSyncCollectRecoveryScan):dispatched
|
|
9249
|
+
* (补发+落标完成,scan 的 warn 已留痕)或 idle(候选已被其他通路落标)→ disposed;
|
|
9250
|
+
* 累计 SETTLED_RESCAN_LIMIT 次仍在等 → disposed + warn 留痕(D6 #7b / SC-2:达限
|
|
9251
|
+
* 放弃重扫意味着滞留成员的批通知要等下次 session_start 才收敛,含滞留 id 的 warn
|
|
9252
|
+
* 是唯一线索,debug 级排障不可见;后续事件零处理,下次 session_start 再收敛)。
|
|
9253
|
+
* pi.on 无 off(0.84.4 实装)——disposed 标志包装兑现退订(scheduler extension
|
|
9254
|
+
* index.ts subscribeSettled 同款先例)。P-settled 定谳(0.84.4 dist 实装证据):
|
|
9255
|
+
* pi.on 为 per-extension 列表分发——loader.js `on()` 把 handler push 进
|
|
9256
|
+
* extension.handlers.get(event) 数组(非覆盖),runner.js `emit()` 对全部
|
|
9257
|
+
* extension 的全部 handler 逐一 await;故本注册与 ledger host 经
|
|
9258
|
+
* piAdapter.onAgentSettled 注册的 settled 分发互不干扰,无需降级并入 host 链。 */
|
|
9259
|
+
armSettledRescan() {
|
|
9260
|
+
if (this.settledRescanState !== null) return;
|
|
9261
|
+
const state = { disposed: false, scans: 0 };
|
|
9262
|
+
this.settledRescanState = state;
|
|
9263
|
+
this.pi?.on?.("agent_settled", async () => {
|
|
9264
|
+
if (state.disposed) return;
|
|
9265
|
+
state.scans += 1;
|
|
9266
|
+
const { outcome, waitingIds } = await this.runSyncCollectRecoveryScan();
|
|
9267
|
+
if (outcome === "waiting" && state.scans < SETTLED_RESCAN_LIMIT) return;
|
|
9268
|
+
state.disposed = true;
|
|
9269
|
+
if (outcome === "waiting") {
|
|
9270
|
+
logger26.warn(
|
|
9271
|
+
`[subagents] E1 settled rescan: reached limit (${SETTLED_RESCAN_LIMIT}) with member(s) still running, disposed until next session_start`,
|
|
9272
|
+
{ ids: waitingIds }
|
|
9273
|
+
);
|
|
9274
|
+
}
|
|
9275
|
+
});
|
|
9276
|
+
}
|
|
9277
|
+
// [E1 语义对齐 toNotifyRecord] 补发成员映射 syncRebuildToNotifyMember 拆至
|
|
9278
|
+
// sync-rebuild.ts(变化轴:恢复批通知语义):one-shot 成功成员末条恒
|
|
9279
|
+
// running+resumable(SP-5),直通 status 会让恢复批批头「0 finished」且丢
|
|
9280
|
+
// patchFile 的 git-apply 指针——对齐后补发记录为 closed + outcome 物化 +
|
|
9281
|
+
// patchFile 透传。调用点:runSyncCollectRecoveryScan。
|
|
8585
9282
|
dispose() {
|
|
8586
9283
|
if (this._disposed) return;
|
|
8587
9284
|
this._disposed = true;
|
|
8588
9285
|
this.stopGcTimer();
|
|
9286
|
+
if (this.settledRescanState !== null) this.settledRescanState.disposed = true;
|
|
8589
9287
|
this.uiRequestHandler = disposedUiRequestStub;
|
|
8590
9288
|
this.uiObservability.resetMissingHandlerWarnings();
|
|
8591
9289
|
this.store.abortRunningControllers();
|
|
8592
9290
|
killAllSpawnedChildren();
|
|
9291
|
+
this.convertPendingSyncBufferToAsync();
|
|
8593
9292
|
this.disposeAllRecords("parent-shutdown");
|
|
8594
9293
|
resetAllEpipeFailures();
|
|
8595
9294
|
this.resumesInFlight.clear();
|
|
@@ -8622,7 +9321,7 @@ var SubagentService = class {
|
|
|
8622
9321
|
record: item.record
|
|
8623
9322
|
});
|
|
8624
9323
|
}
|
|
8625
|
-
|
|
9324
|
+
logger26.warn(
|
|
8626
9325
|
`[subagents] shutdown flush blocked by busy main agent: ${pending.length} pending notification(s) persisted to ledger for replay on next session_start`,
|
|
8627
9326
|
{ count: pending.length }
|
|
8628
9327
|
);
|
|
@@ -8852,7 +9551,7 @@ var SubagentService = class {
|
|
|
8852
9551
|
*/
|
|
8853
9552
|
onHotPathSettledWatchdogTimeout(record, fire) {
|
|
8854
9553
|
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)`;
|
|
8855
|
-
|
|
9554
|
+
logger26.warn(
|
|
8856
9555
|
`[subagents] settled watchdog (${fire.phase}) fired for ${record.id}: ${windowDesc}, terminating (LC-1 wedge recovery)`
|
|
8857
9556
|
);
|
|
8858
9557
|
killRecordChildWithEscalation(record.id, "settled watchdog (hot path)");
|
|
@@ -8869,7 +9568,7 @@ var SubagentService = class {
|
|
|
8869
9568
|
return;
|
|
8870
9569
|
}
|
|
8871
9570
|
const finalize = record.chatMode ? this.finalizeRoundToIdle(record, failedResult) : this.finalizeRecord(record, failedResult, "closed", "gc");
|
|
8872
|
-
void finalize.then(() => this.
|
|
9571
|
+
void finalize.then(() => this.collectCoordinator.route(record)).catch((err) => bestEffort(err, "settled watchdog hot-path finalize", "error"));
|
|
8873
9572
|
}
|
|
8874
9573
|
// [T2⑧ / PS-3] 非 EPIPE 热路径写失败后的 idle timer 再武装已随 D2 投递下沉迁移落位:
|
|
8875
9574
|
// 挂载点在 PiEngine.deliverPrompt 的非 EPIPE catch(engine/engines/pi/pi-engine.ts 的
|
|
@@ -9100,6 +9799,21 @@ var SubagentService = class {
|
|
|
9100
9799
|
collectRecords(limit, statusFilter = "all") {
|
|
9101
9800
|
return this.store.collectRecords(limit, statusFilter, this.sessionRootId ?? this.sessionId ?? void 0);
|
|
9102
9801
|
}
|
|
9802
|
+
/**
|
|
9803
|
+
* collectSync.default 当前生效值(subagent-sync-collect U2,偏差#3 接线:
|
|
9804
|
+
* startHandler 缺省 collect 解析用)。
|
|
9805
|
+
* config 未配/读失败 → DEFAULT_COLLECT_SYNC.default 兜底(E5 不炸启动)。
|
|
9806
|
+
* 新 session 生效语义与 engine 配置一致(globalConfig 由 ModelConfigService
|
|
9807
|
+
* reloadGlobalConfig 刷新)。
|
|
9808
|
+
*/
|
|
9809
|
+
/** collectSync 节单读取点(S9,code-simplify):「读节」一处,「投影成 default 或
|
|
9810
|
+
* budget」各自 accessor 负责(getCollectSyncDefault / getCollectSyncBudget)。 */
|
|
9811
|
+
collectSyncSection() {
|
|
9812
|
+
return this.modelService.getGlobalConfig().collectSync;
|
|
9813
|
+
}
|
|
9814
|
+
getCollectSyncDefault() {
|
|
9815
|
+
return this.collectSyncSection()?.default ?? DEFAULT_COLLECT_SYNC.default;
|
|
9816
|
+
}
|
|
9103
9817
|
/** [perf] 单 record 详情懒加载(全量:eventLog/displayItems/result/turns/tokens)。
|
|
9104
9818
|
* 内存 running record 直接投影;磁盘 record 全量重建(per-file 缓存,stat 戳校验)。
|
|
9105
9819
|
* 返回 undefined:id 不存在于内存与磁盘。 */
|
|
@@ -9192,6 +9906,10 @@ var SubagentService = class {
|
|
|
9192
9906
|
// 从 RunContext 回填;缺省 = pi 投影,存量调用方零感知)
|
|
9193
9907
|
engine: opts.engine,
|
|
9194
9908
|
engineFallback: opts.engineFallback,
|
|
9909
|
+
// subagent-sync-collect U2(偏差#4 接线):sync record 落 collectMode——
|
|
9910
|
+
// 协调器路由判据 + startHandler pendingSyncCount 枚举含本条的数据源。
|
|
9911
|
+
// undefined = async(缺省语义,旧记录零迁移)。
|
|
9912
|
+
collectMode: opts.collect === "sync" ? "sync" : void 0,
|
|
9195
9913
|
controller
|
|
9196
9914
|
});
|
|
9197
9915
|
this.store.register(record);
|
|
@@ -9279,7 +9997,7 @@ var SubagentService = class {
|
|
|
9279
9997
|
try {
|
|
9280
9998
|
await this.runEngineTask(record, opts, engine, signal);
|
|
9281
9999
|
if (record.closedReason !== "cancelled") {
|
|
9282
|
-
this.
|
|
10000
|
+
this.collectCoordinator.route(record);
|
|
9283
10001
|
}
|
|
9284
10002
|
} finally {
|
|
9285
10003
|
this.pool.release();
|
|
@@ -9556,12 +10274,12 @@ var SubagentService = class {
|
|
|
9556
10274
|
{ taskId: record.id, poolKey: PI_POOL_KEY, signal, stream }
|
|
9557
10275
|
).then(() => {
|
|
9558
10276
|
if (notifyGateAllowsDelivery(record.closedReason)) {
|
|
9559
|
-
this.
|
|
10277
|
+
this.collectCoordinator.route(record);
|
|
9560
10278
|
}
|
|
9561
10279
|
}).catch((err) => {
|
|
9562
10280
|
this.chatRoundTickets.delete(record.id);
|
|
9563
10281
|
if (err instanceof Error) {
|
|
9564
|
-
|
|
10282
|
+
logger26.debug(`[subagent] background finalize error (record=${record.id}): ${err.message}`);
|
|
9565
10283
|
}
|
|
9566
10284
|
});
|
|
9567
10285
|
}
|
|
@@ -9654,7 +10372,7 @@ var SubagentService = class {
|
|
|
9654
10372
|
}
|
|
9655
10373
|
}
|
|
9656
10374
|
this.notifyHost.emitPendingUnregister(record.id, "closed");
|
|
9657
|
-
this.
|
|
10375
|
+
this.collectCoordinator.route(record);
|
|
9658
10376
|
return true;
|
|
9659
10377
|
}
|
|
9660
10378
|
/**
|
|
@@ -9825,9 +10543,9 @@ function registerPiEngine() {
|
|
|
9825
10543
|
|
|
9826
10544
|
// src/execution/engine/engines/zcode/zcode-engine.ts
|
|
9827
10545
|
import { execFile as execFile4 } from "child_process";
|
|
9828
|
-
import * as
|
|
10546
|
+
import * as fs23 from "fs";
|
|
9829
10547
|
import * as os4 from "os";
|
|
9830
|
-
import * as
|
|
10548
|
+
import * as path17 from "path";
|
|
9831
10549
|
|
|
9832
10550
|
// src/execution/engine/common/schema-emulation.ts
|
|
9833
10551
|
import Ajv from "ajv";
|
|
@@ -9967,9 +10685,9 @@ function synthesizeCoarseEvents(response, usage) {
|
|
|
9967
10685
|
}
|
|
9968
10686
|
|
|
9969
10687
|
// src/execution/engine/engines/zcode/preparer.ts
|
|
9970
|
-
import * as
|
|
10688
|
+
import * as fs20 from "fs";
|
|
9971
10689
|
import * as os3 from "os";
|
|
9972
|
-
import * as
|
|
10690
|
+
import * as path15 from "path";
|
|
9973
10691
|
var ZcodePrepareError = class extends Error {
|
|
9974
10692
|
code;
|
|
9975
10693
|
constructor(code, message) {
|
|
@@ -9988,7 +10706,7 @@ function readSourceConfig(absPath) {
|
|
|
9988
10706
|
const empty = { providers: /* @__PURE__ */ new Map() };
|
|
9989
10707
|
let raw;
|
|
9990
10708
|
try {
|
|
9991
|
-
raw =
|
|
10709
|
+
raw = fs20.readFileSync(absPath, "utf8");
|
|
9992
10710
|
} catch {
|
|
9993
10711
|
return empty;
|
|
9994
10712
|
}
|
|
@@ -10022,7 +10740,7 @@ function hasApiKey(entry) {
|
|
|
10022
10740
|
return typeof key === "string" && key !== "";
|
|
10023
10741
|
}
|
|
10024
10742
|
function defaultV2ConfigPath() {
|
|
10025
|
-
return
|
|
10743
|
+
return path15.join(os3.homedir(), ...ZCODE_V2_CONFIG_PATH_SUFFIX);
|
|
10026
10744
|
}
|
|
10027
10745
|
var DEFAULT_PROVIDER_ID = "builtin:bigmodel-coding-plan";
|
|
10028
10746
|
function defaultProviderForShortName(merged, withKey) {
|
|
@@ -10085,8 +10803,8 @@ function listZcodeModels(sources) {
|
|
|
10085
10803
|
}
|
|
10086
10804
|
|
|
10087
10805
|
// src/execution/engine/engines/zcode/appserver-launcher.ts
|
|
10088
|
-
import * as
|
|
10089
|
-
import * as
|
|
10806
|
+
import * as fs21 from "fs";
|
|
10807
|
+
import * as path16 from "path";
|
|
10090
10808
|
var ZCODE_APPSERVER_LAUNCHER_NAME = "appserver-launcher.cjs";
|
|
10091
10809
|
var APPSERVER_LAUNCHER_SOURCE = `'use strict';
|
|
10092
10810
|
// zcode app-server fs \u62E6\u622A wrapper\uFF08\u7531 subagent-core appserver-launcher \u843D\u76D8\u751F\u6210\uFF09
|
|
@@ -10192,15 +10910,15 @@ import(CLI_PATH).catch((err) => {
|
|
|
10192
10910
|
});
|
|
10193
10911
|
`;
|
|
10194
10912
|
function ensureAppServerLauncher(engineDataDir) {
|
|
10195
|
-
const dir =
|
|
10196
|
-
const file =
|
|
10197
|
-
const existing =
|
|
10913
|
+
const dir = path16.join(engineDataDir, "engines", "zcode");
|
|
10914
|
+
const file = path16.join(dir, ZCODE_APPSERVER_LAUNCHER_NAME);
|
|
10915
|
+
const existing = fs21.existsSync(file) && fs21.statSync(file).isFile() ? fs21.readFileSync(file, "utf8") : void 0;
|
|
10198
10916
|
if (existing === APPSERVER_LAUNCHER_SOURCE) return file;
|
|
10199
10917
|
try {
|
|
10200
|
-
|
|
10918
|
+
fs21.mkdirSync(dir, { recursive: true });
|
|
10201
10919
|
const tmp = `${file}.tmp-${process.pid}-${Date.now()}`;
|
|
10202
|
-
|
|
10203
|
-
|
|
10920
|
+
fs21.writeFileSync(tmp, APPSERVER_LAUNCHER_SOURCE);
|
|
10921
|
+
fs21.renameSync(tmp, file);
|
|
10204
10922
|
} catch (err) {
|
|
10205
10923
|
const reason = toErrorMessage(err);
|
|
10206
10924
|
throw new Error(
|
|
@@ -10212,9 +10930,9 @@ function ensureAppServerLauncher(engineDataDir) {
|
|
|
10212
10930
|
|
|
10213
10931
|
// src/execution/engine/engines/zcode/connection.ts
|
|
10214
10932
|
import { spawn as spawn2 } from "child_process";
|
|
10215
|
-
import * as
|
|
10933
|
+
import * as fs22 from "fs";
|
|
10216
10934
|
import { dirname as dirname6 } from "path";
|
|
10217
|
-
var
|
|
10935
|
+
var logger27 = getLogger("subagents");
|
|
10218
10936
|
var RAW_FRAME_LOG_CHARS = 200;
|
|
10219
10937
|
var RUNTIME_PREFERENCES = Object.freeze({
|
|
10220
10938
|
nativeSearchEnhancementsEnabled: true,
|
|
@@ -10419,7 +11137,7 @@ var AppServerConnection = class {
|
|
|
10419
11137
|
try {
|
|
10420
11138
|
this.onSpawned(child);
|
|
10421
11139
|
} catch (err) {
|
|
10422
|
-
|
|
11140
|
+
logger27.warn(`onSpawned \u56DE\u8C03\u5F02\u5E38\uFF08\u5FFD\u7565\uFF09: ${errMessage(err)}`);
|
|
10423
11141
|
}
|
|
10424
11142
|
}
|
|
10425
11143
|
let finalized = false;
|
|
@@ -10438,7 +11156,7 @@ var AppServerConnection = class {
|
|
|
10438
11156
|
try {
|
|
10439
11157
|
fn(reason);
|
|
10440
11158
|
} catch (err2) {
|
|
10441
|
-
|
|
11159
|
+
logger27.warn(`close handler \u5F02\u5E38: ${errMessage(err2)}`);
|
|
10442
11160
|
}
|
|
10443
11161
|
}
|
|
10444
11162
|
};
|
|
@@ -10478,14 +11196,14 @@ var AppServerConnection = class {
|
|
|
10478
11196
|
try {
|
|
10479
11197
|
frame = JSON.parse(text);
|
|
10480
11198
|
} catch {
|
|
10481
|
-
|
|
11199
|
+
logger27.warn(`\u65E0\u6CD5\u89E3\u6790\u7684\u534F\u8BAE\u884C\uFF08\u5FFD\u7565\uFF09: ${text.slice(0, RAW_FRAME_LOG_CHARS)}`);
|
|
10482
11200
|
return;
|
|
10483
11201
|
}
|
|
10484
11202
|
this.handleFrame(frame, text);
|
|
10485
11203
|
}
|
|
10486
11204
|
handleFrame(frame, rawText) {
|
|
10487
11205
|
if (!isRecord2(frame)) {
|
|
10488
|
-
|
|
11206
|
+
logger27.warn(`\u975E\u5BF9\u8C61\u534F\u8BAE\u5E27\uFF08\u5FFD\u7565\uFF09: ${rawText.slice(0, RAW_FRAME_LOG_CHARS)}`);
|
|
10489
11207
|
return;
|
|
10490
11208
|
}
|
|
10491
11209
|
if (isRecord2(frame.protocol)) this.capturedProtocolInfo = frame.protocol;
|
|
@@ -10500,19 +11218,19 @@ var AppServerConnection = class {
|
|
|
10500
11218
|
}
|
|
10501
11219
|
if (frame.id !== void 0 && frame.id !== null) {
|
|
10502
11220
|
if (typeof frame.id !== "number") {
|
|
10503
|
-
|
|
11221
|
+
logger27.warn(`\u5E94\u7B54 id \u975E\u6570\u5B57\uFF08\u5FFD\u7565\uFF09: ${rawText.slice(0, RAW_FRAME_LOG_CHARS)}`);
|
|
10504
11222
|
return;
|
|
10505
11223
|
}
|
|
10506
11224
|
this.settlePending(frame.id, frame);
|
|
10507
11225
|
return;
|
|
10508
11226
|
}
|
|
10509
11227
|
if (isRecord2(frame.protocol)) return;
|
|
10510
|
-
|
|
11228
|
+
logger27.warn(`\u65E0\u6CD5\u5F52\u7C7B\u7684\u534F\u8BAE\u5E27\uFF08\u5FFD\u7565\uFF09: ${rawText.slice(0, RAW_FRAME_LOG_CHARS)}`);
|
|
10511
11229
|
}
|
|
10512
11230
|
settlePending(id, frame) {
|
|
10513
11231
|
const entry = this.pending.get(id);
|
|
10514
11232
|
if (!entry) {
|
|
10515
|
-
|
|
11233
|
+
logger27.warn(`\u54CD\u5E94\u65E0\u5339\u914D\u8BF7\u6C42 id=${id}\uFF08\u5FFD\u7565\uFF09`);
|
|
10516
11234
|
return;
|
|
10517
11235
|
}
|
|
10518
11236
|
this.pending.delete(id);
|
|
@@ -10538,7 +11256,7 @@ var AppServerConnection = class {
|
|
|
10538
11256
|
try {
|
|
10539
11257
|
fn(params);
|
|
10540
11258
|
} catch (err) {
|
|
10541
|
-
|
|
11259
|
+
logger27.warn(`push handler \u5F02\u5E38\uFF08${method}\uFF09: ${errMessage(err)}`);
|
|
10542
11260
|
}
|
|
10543
11261
|
}
|
|
10544
11262
|
}
|
|
@@ -10548,12 +11266,12 @@ var AppServerConnection = class {
|
|
|
10548
11266
|
answerReverse(id, method, params) {
|
|
10549
11267
|
const handler = this.reverseHandlers[method];
|
|
10550
11268
|
if (!handler) {
|
|
10551
|
-
|
|
11269
|
+
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`);
|
|
10552
11270
|
this.writeFrame({ id, result: {} });
|
|
10553
11271
|
return;
|
|
10554
11272
|
}
|
|
10555
11273
|
Promise.resolve().then(() => handler(params)).then((result) => this.writeFrame({ id, result: result ?? {} })).catch((err) => {
|
|
10556
|
-
|
|
11274
|
+
logger27.warn(`\u53CD\u5411\u8BF7\u6C42 ${method} handler \u5F02\u5E38: ${errMessage(err)}`);
|
|
10557
11275
|
this.writeFrame({ id, error: { code: -32e3, message: errMessage(err) } });
|
|
10558
11276
|
});
|
|
10559
11277
|
}
|
|
@@ -10569,7 +11287,7 @@ var AppServerConnection = class {
|
|
|
10569
11287
|
`);
|
|
10570
11288
|
return true;
|
|
10571
11289
|
} catch (err) {
|
|
10572
|
-
|
|
11290
|
+
logger27.warn(`\u5199\u5165 app-server \u5931\u8D25: ${errMessage(err)}`);
|
|
10573
11291
|
return false;
|
|
10574
11292
|
}
|
|
10575
11293
|
}
|
|
@@ -10578,8 +11296,8 @@ var AppServerConnection = class {
|
|
|
10578
11296
|
if (this.stderrStreamFailed) return;
|
|
10579
11297
|
if (this.stderrStream === null) {
|
|
10580
11298
|
try {
|
|
10581
|
-
|
|
10582
|
-
this.stderrStream =
|
|
11299
|
+
fs22.mkdirSync(dirname6(this.stderrLogPath), { recursive: true });
|
|
11300
|
+
this.stderrStream = fs22.createWriteStream(this.stderrLogPath, { flags: "a" });
|
|
10583
11301
|
this.stderrStream.on("error", () => {
|
|
10584
11302
|
this.stderrStreamFailed = true;
|
|
10585
11303
|
});
|
|
@@ -10601,8 +11319,8 @@ var AppServerConnection = class {
|
|
|
10601
11319
|
};
|
|
10602
11320
|
|
|
10603
11321
|
// src/execution/engine/engines/zcode/session-channel.ts
|
|
10604
|
-
import { createHash } from "crypto";
|
|
10605
|
-
var
|
|
11322
|
+
import { createHash as createHash2 } from "crypto";
|
|
11323
|
+
var logger28 = getLogger("subagents");
|
|
10606
11324
|
var SUBSCRIBE_DELIVERY_KIND = "desktop-continuous";
|
|
10607
11325
|
var WORKSPACE_KEY_HASH_CHARS = 16;
|
|
10608
11326
|
var CREATE_REPLY_LOG_CHARS = 300;
|
|
@@ -10614,7 +11332,7 @@ function errMessage2(err) {
|
|
|
10614
11332
|
return toErrorMessage(err);
|
|
10615
11333
|
}
|
|
10616
11334
|
function stableWorkspaceKey(workspacePath) {
|
|
10617
|
-
return "ws-" +
|
|
11335
|
+
return "ws-" + createHash2("sha256").update(workspacePath).digest("hex").slice(0, WORKSPACE_KEY_HASH_CHARS);
|
|
10618
11336
|
}
|
|
10619
11337
|
function extractCreatedSessionId(created) {
|
|
10620
11338
|
if (!isRecord3(created)) return void 0;
|
|
@@ -10744,7 +11462,7 @@ function resolveTurnTimerMs(parts) {
|
|
|
10744
11462
|
source = `env ${parts.envName}=${raw}`;
|
|
10745
11463
|
} else {
|
|
10746
11464
|
if (parsed.state === "invalid") {
|
|
10747
|
-
|
|
11465
|
+
logger28.warn(
|
|
10748
11466
|
`[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`
|
|
10749
11467
|
);
|
|
10750
11468
|
}
|
|
@@ -10753,7 +11471,7 @@ function resolveTurnTimerMs(parts) {
|
|
|
10753
11471
|
}
|
|
10754
11472
|
}
|
|
10755
11473
|
if (value <= 0) {
|
|
10756
|
-
|
|
11474
|
+
logger28.warn(
|
|
10757
11475
|
`[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`
|
|
10758
11476
|
);
|
|
10759
11477
|
}
|
|
@@ -10875,7 +11593,7 @@ var SessionChannel = class {
|
|
|
10875
11593
|
{ timeoutMs: ZCODE_APPSERVER_TURN_CLOSE_TIMEOUT_MS }
|
|
10876
11594
|
);
|
|
10877
11595
|
} catch (err) {
|
|
10878
|
-
|
|
11596
|
+
logger28.warn(
|
|
10879
11597
|
`session/close \u5931\u8D25\uFF08\u4F1A\u8BDD ${sessionId}\uFF0Cbest-effort \u5FFD\u7565\uFF09: ${errMessage2(
|
|
10880
11598
|
err
|
|
10881
11599
|
)}`
|
|
@@ -11076,7 +11794,7 @@ var SessionChannel = class {
|
|
|
11076
11794
|
applyStreamDelta(turn, payload) {
|
|
11077
11795
|
if (typeof payload.delta !== "string" || payload.delta === "") return;
|
|
11078
11796
|
if (turn.settled) {
|
|
11079
|
-
|
|
11797
|
+
logger28.warn(
|
|
11080
11798
|
`\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(
|
|
11081
11799
|
0,
|
|
11082
11800
|
DELTA_LOG_CHARS
|
|
@@ -11105,7 +11823,7 @@ var SessionChannel = class {
|
|
|
11105
11823
|
turn.lastTerminalStatus = status;
|
|
11106
11824
|
this.recordTerminalError(turn, params);
|
|
11107
11825
|
if (turn.settled) {
|
|
11108
|
-
|
|
11826
|
+
logger28.warn(
|
|
11109
11827
|
`\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`
|
|
11110
11828
|
);
|
|
11111
11829
|
return;
|
|
@@ -11151,7 +11869,7 @@ var SessionChannel = class {
|
|
|
11151
11869
|
}
|
|
11152
11870
|
);
|
|
11153
11871
|
} catch (err) {
|
|
11154
|
-
|
|
11872
|
+
logger28.warn(
|
|
11155
11873
|
`session/read \u515C\u5E95\u5931\u8D25\uFF08\u4F1A\u8BDD ${sessionId}\uFF0C\u964D\u7EA7\u6536\u5C3E\u5E27/delta \u805A\u5408\uFF09: ${errMessage2(
|
|
11156
11874
|
err
|
|
11157
11875
|
)}`
|
|
@@ -11162,11 +11880,11 @@ var SessionChannel = class {
|
|
|
11162
11880
|
};
|
|
11163
11881
|
|
|
11164
11882
|
// src/execution/engine/engines/zcode/zcode-engine.ts
|
|
11165
|
-
var
|
|
11883
|
+
var logger29 = getLogger("subagents");
|
|
11166
11884
|
var PROBE_VERSION_TIMEOUT_MS2 = 15e3;
|
|
11167
11885
|
var COMMON_THOUGHT_LEVELS = ["low", "high", "max"];
|
|
11168
11886
|
function hostZcodeDbPath() {
|
|
11169
|
-
return
|
|
11887
|
+
return path17.join(os4.homedir(), ...ZCODE_HOST_DB_SUFFIX);
|
|
11170
11888
|
}
|
|
11171
11889
|
var ZcodeEngine = class {
|
|
11172
11890
|
id = ZCODE_ENGINE_ID;
|
|
@@ -11237,7 +11955,7 @@ var ZcodeEngine = class {
|
|
|
11237
11955
|
}
|
|
11238
11956
|
/** check 1:二进制存在性(isFile 才算——同名目录不是可执行入口)。 */
|
|
11239
11957
|
probeBinaryCheck(cliPath) {
|
|
11240
|
-
const binaryOk =
|
|
11958
|
+
const binaryOk = fs23.existsSync(cliPath) && fs23.statSync(cliPath).isFile();
|
|
11241
11959
|
return {
|
|
11242
11960
|
name: "binary",
|
|
11243
11961
|
ok: binaryOk,
|
|
@@ -11331,11 +12049,11 @@ var ZcodeEngine = class {
|
|
|
11331
12049
|
if (final.kind === "run-failed" && final.transient !== void 0 && ctx.signal?.aborted !== true && !this.disposed) {
|
|
11332
12050
|
const budget = resolveTransientRetryBudget(explicitTurnBudgetMs(), Date.now() - attemptStartedAt);
|
|
11333
12051
|
if (budget.state === "depleted") {
|
|
11334
|
-
|
|
12052
|
+
logger29.warn(
|
|
11335
12053
|
`[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`
|
|
11336
12054
|
);
|
|
11337
12055
|
} else {
|
|
11338
|
-
|
|
12056
|
+
logger29.warn(
|
|
11339
12057
|
`[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")
|
|
11340
12058
|
);
|
|
11341
12059
|
const retry = await this.attemptAppServerTurn(task, ctx, modelRef, cwd, basePrompt, {
|
|
@@ -11489,7 +12207,7 @@ var ZcodeEngine = class {
|
|
|
11489
12207
|
delayResolved(ZCODE_APPSERVER_ABORT_GRACE_MS, false)
|
|
11490
12208
|
]);
|
|
11491
12209
|
if (settled) return "settled-in-grace";
|
|
11492
|
-
|
|
12210
|
+
logger29.warn(
|
|
11493
12211
|
`[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`
|
|
11494
12212
|
);
|
|
11495
12213
|
await rt.conn.shutdown({ graceMs: ZCODE_KILL_GRACE_MS });
|
|
@@ -11514,19 +12232,19 @@ var ZcodeEngine = class {
|
|
|
11514
12232
|
} catch (err) {
|
|
11515
12233
|
if (entry.escalateOn === "stop-outcome") {
|
|
11516
12234
|
if (isAppServerRpcError(err)) {
|
|
11517
|
-
|
|
12235
|
+
logger29.debug(
|
|
11518
12236
|
`[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`
|
|
11519
12237
|
);
|
|
11520
12238
|
return "stop-rejected";
|
|
11521
12239
|
}
|
|
11522
|
-
|
|
12240
|
+
logger29.warn(
|
|
11523
12241
|
`[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`
|
|
11524
12242
|
);
|
|
11525
12243
|
await rt.conn.shutdown({ graceMs: ZCODE_KILL_GRACE_MS });
|
|
11526
12244
|
await this.awaitConnFinalized(rt);
|
|
11527
12245
|
return "stop-unreachable-killed";
|
|
11528
12246
|
}
|
|
11529
|
-
|
|
12247
|
+
logger29.debug(
|
|
11530
12248
|
`[zcode-engine] session/stop \u5931\u8D25\uFF08${errMessage3(err)}\uFF09\u2014\u2014grace \u540E\u8D70 killChain \u515C\u5E95`
|
|
11531
12249
|
);
|
|
11532
12250
|
}
|
|
@@ -11557,7 +12275,7 @@ var ZcodeEngine = class {
|
|
|
11557
12275
|
cwd: engineDataDir,
|
|
11558
12276
|
env,
|
|
11559
12277
|
launcherScript,
|
|
11560
|
-
stderrLogPath:
|
|
12278
|
+
stderrLogPath: path17.join(engineDataDir, "logs", "zcode-appserver-stderr.log")
|
|
11561
12279
|
});
|
|
11562
12280
|
const rt = {
|
|
11563
12281
|
conn,
|
|
@@ -11722,15 +12440,15 @@ var ZcodeEngine = class {
|
|
|
11722
12440
|
const dbPathRaw = handle.data.sessionRef["dbPath"];
|
|
11723
12441
|
if (typeof sessionId === "string" && typeof dbPathRaw === "string") {
|
|
11724
12442
|
let dbPath;
|
|
11725
|
-
if (
|
|
12443
|
+
if (path17.isAbsolute(dbPathRaw)) {
|
|
11726
12444
|
if (dbPathRaw === hostZcodeDbPath()) dbPath = dbPathRaw;
|
|
11727
12445
|
else {
|
|
11728
|
-
|
|
12446
|
+
logger29.warn("[zcode-engine] record dbPath \u975E\u5BBF\u4E3B db \u7EDD\u5BF9\u8DEF\u5F84\uFF0C\u62D2\u7EDD \u2460\u7EA7\u8BFB\u53D6\u964D journal", {
|
|
11729
12447
|
dbPath: dbPathRaw
|
|
11730
12448
|
});
|
|
11731
12449
|
}
|
|
11732
12450
|
} else {
|
|
11733
|
-
dbPath =
|
|
12451
|
+
dbPath = path17.join(
|
|
11734
12452
|
resolvePoolDir(this.deps.engineDataDir(), ZCODE_ENGINE_ID, handle.data.poolKey),
|
|
11735
12453
|
dbPathRaw
|
|
11736
12454
|
);
|
|
@@ -11739,7 +12457,7 @@ var ZcodeEngine = class {
|
|
|
11739
12457
|
try {
|
|
11740
12458
|
return await readZcodeSessionView(dbPath, sessionId);
|
|
11741
12459
|
} catch (err) {
|
|
11742
|
-
|
|
12460
|
+
logger29.warn("[zcode-engine] native session read failed, degrade to journal replay", {
|
|
11743
12461
|
dbPath,
|
|
11744
12462
|
sessionId,
|
|
11745
12463
|
reason: toErrorMessage(err)
|
|
@@ -11765,7 +12483,7 @@ var ZcodeEngine = class {
|
|
|
11765
12483
|
const thoughtLevel = task.thinkingLevel?.trim();
|
|
11766
12484
|
if (thoughtLevel === void 0 || thoughtLevel === "") return;
|
|
11767
12485
|
if (COMMON_THOUGHT_LEVELS.includes(thoughtLevel)) return;
|
|
11768
|
-
|
|
12486
|
+
logger29.warn(
|
|
11769
12487
|
`[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`,
|
|
11770
12488
|
{ taskId: ctx.taskId }
|
|
11771
12489
|
);
|
|
@@ -11783,7 +12501,7 @@ var ZcodeEngine = class {
|
|
|
11783
12501
|
if (ctx.ctxModel === void 0) return;
|
|
11784
12502
|
const requested = task.model?.trim();
|
|
11785
12503
|
if (requested !== void 0 && requested !== "") return;
|
|
11786
|
-
|
|
12504
|
+
logger29.warn(
|
|
11787
12505
|
`[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}`,
|
|
11788
12506
|
{ taskId: ctx.taskId }
|
|
11789
12507
|
);
|
|
@@ -11996,7 +12714,7 @@ async function defaultProbeVersion2(cliPath) {
|
|
|
11996
12714
|
);
|
|
11997
12715
|
});
|
|
11998
12716
|
} catch (err) {
|
|
11999
|
-
|
|
12717
|
+
logger29.debug(
|
|
12000
12718
|
`[zcode-engine] probe version check failed (best-effort continue): ${toErrorMessage(err)}`
|
|
12001
12719
|
);
|
|
12002
12720
|
return void 0;
|
|
@@ -12116,7 +12834,7 @@ function parseEngineHandle(raw) {
|
|
|
12116
12834
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
12117
12835
|
import { homedir as homedir5 } from "os";
|
|
12118
12836
|
import { isAbsolute as isAbsolute3, relative, resolve } from "path";
|
|
12119
|
-
var
|
|
12837
|
+
var logger30 = getLogger("subagents");
|
|
12120
12838
|
var DEFAULT_ENGINE_ID2 = "pi";
|
|
12121
12839
|
function extractEngineId(record) {
|
|
12122
12840
|
const engine = record.engine;
|
|
@@ -12141,13 +12859,13 @@ async function readZcodeNativeTier(handle, dataDir) {
|
|
|
12141
12859
|
const dbPathRaw = handle.sessionRef["dbPath"];
|
|
12142
12860
|
const sessionId = handle.sessionRef["sessionId"];
|
|
12143
12861
|
if (typeof dbPathRaw !== "string" || typeof sessionId !== "string") {
|
|
12144
|
-
|
|
12862
|
+
logger30.debug("[session-view-service] zcode tier1 skipped: handle missing dbPath/sessionId");
|
|
12145
12863
|
return void 0;
|
|
12146
12864
|
}
|
|
12147
12865
|
let dbPath;
|
|
12148
12866
|
if (dbPathRaw.startsWith("/")) {
|
|
12149
12867
|
if (dbPathRaw !== resolve(homedir5(), ...ZCODE_HOST_DB_SUFFIX)) {
|
|
12150
|
-
|
|
12868
|
+
logger30.warn(
|
|
12151
12869
|
`[session-view-service] zcode absolute dbPath not host db, reject tier1: ${dbPathRaw}`
|
|
12152
12870
|
);
|
|
12153
12871
|
return void 0;
|
|
@@ -12157,14 +12875,14 @@ async function readZcodeNativeTier(handle, dataDir) {
|
|
|
12157
12875
|
const poolDir = resolvePoolDir(dataDir, ZCODE_ENGINE_ID, handle.poolKey);
|
|
12158
12876
|
dbPath = resolve(poolDir, dbPathRaw);
|
|
12159
12877
|
if (!isStrictlyUnder(poolDir, dbPath)) {
|
|
12160
|
-
|
|
12878
|
+
logger30.warn(`[session-view-service] zcode dbPath escapes pool dir, reject tier1: ${dbPath}`);
|
|
12161
12879
|
return void 0;
|
|
12162
12880
|
}
|
|
12163
12881
|
}
|
|
12164
12882
|
try {
|
|
12165
12883
|
return await readZcodeSessionView(dbPath, sessionId);
|
|
12166
12884
|
} catch (err) {
|
|
12167
|
-
|
|
12885
|
+
logger30.debug(
|
|
12168
12886
|
`[session-view-service] zcode tier1 read failed, degrade to journal tier: ${toErrorMessage(err)}`
|
|
12169
12887
|
);
|
|
12170
12888
|
return void 0;
|
|
@@ -12173,18 +12891,18 @@ async function readZcodeNativeTier(handle, dataDir) {
|
|
|
12173
12891
|
function readJournalTier(record, handle, dataDir) {
|
|
12174
12892
|
const journalPath = handle.journalPath;
|
|
12175
12893
|
if (journalPath === void 0) {
|
|
12176
|
-
|
|
12894
|
+
logger30.debug("[session-view-service] tier2 skipped: no journalPath in handle");
|
|
12177
12895
|
return void 0;
|
|
12178
12896
|
}
|
|
12179
12897
|
if (!isStrictlyUnder(resolveEnginesRoot(dataDir), journalPath)) {
|
|
12180
|
-
|
|
12898
|
+
logger30.warn(
|
|
12181
12899
|
`[session-view-service] journalPath escapes engines root, reject tier2: ${journalPath}`
|
|
12182
12900
|
);
|
|
12183
12901
|
return void 0;
|
|
12184
12902
|
}
|
|
12185
12903
|
const messages = replayEventsToHistory(replayJournal(journalPath), record);
|
|
12186
12904
|
if (messages === void 0) {
|
|
12187
|
-
|
|
12905
|
+
logger30.debug(
|
|
12188
12906
|
`[session-view-service] tier2 journal replay produced no content, degrade to outcome-only (path=${journalPath})`
|
|
12189
12907
|
);
|
|
12190
12908
|
}
|
|
@@ -12354,14 +13072,14 @@ async function readSubagentHistoryMessages(record, dataDir) {
|
|
|
12354
13072
|
if (engineId === DEFAULT_ENGINE_ID2) return [];
|
|
12355
13073
|
const handle = parseEngineHandle(record.engineHandle);
|
|
12356
13074
|
if (handle === void 0) {
|
|
12357
|
-
|
|
13075
|
+
logger30.debug(
|
|
12358
13076
|
`[session-view-service] engine '${engineId}' record has no engineHandle, degrade to outcome-only (subagentId=${record.subagentId})`
|
|
12359
13077
|
);
|
|
12360
13078
|
return outcomeOnlyMessages(record);
|
|
12361
13079
|
}
|
|
12362
13080
|
const reader = lookupNativeSessionReader(engineId);
|
|
12363
13081
|
if (reader === void 0) {
|
|
12364
|
-
|
|
13082
|
+
logger30.debug(
|
|
12365
13083
|
`[session-view-service] engine '${engineId}' has no native reader tier, degrade to outcome-only (subagentId=${record.subagentId})`
|
|
12366
13084
|
);
|
|
12367
13085
|
return outcomeOnlyMessages(record);
|
|
@@ -12378,14 +13096,14 @@ function isStrictlyUnder(parent, child) {
|
|
|
12378
13096
|
}
|
|
12379
13097
|
|
|
12380
13098
|
// src/execution/agent-registry.ts
|
|
12381
|
-
import * as
|
|
13099
|
+
import * as path18 from "path";
|
|
12382
13100
|
|
|
12383
13101
|
// src/shared/resource-discovery.ts
|
|
12384
13102
|
import * as fsSync2 from "fs";
|
|
12385
13103
|
import { access, readdir, readFile as readFile2, realpath, stat } from "fs/promises";
|
|
12386
13104
|
import { homedir as homedir6 } from "os";
|
|
12387
|
-
import { delimiter as delimiter2, join as
|
|
12388
|
-
var
|
|
13105
|
+
import { delimiter as delimiter2, join as join20, resolve as resolve2 } from "path";
|
|
13106
|
+
var logger31 = getLogger("subagents");
|
|
12389
13107
|
var shadowWarnDedup = /* @__PURE__ */ new Set();
|
|
12390
13108
|
var MAX_SHADOW_WARN_DEDUP = 1024;
|
|
12391
13109
|
var MACHINE_SOURCES = /* @__PURE__ */ new Set([
|
|
@@ -12625,7 +13343,7 @@ async function scanNpmDir(nodeModulesDir, kind) {
|
|
|
12625
13343
|
function readExtensionPaths() {
|
|
12626
13344
|
const raw = process.env.XYZ_EXTENSION_PATHS;
|
|
12627
13345
|
if (!raw) return [];
|
|
12628
|
-
const paths = raw.split(delimiter2).map((p) => p.trim()).filter((p) => p.length > 0).map((p) => p.startsWith("~") ?
|
|
13346
|
+
const paths = raw.split(delimiter2).map((p) => p.trim()).filter((p) => p.length > 0).map((p) => p.startsWith("~") ? join20(homedir6(), p.slice(1)) : p);
|
|
12629
13347
|
return [...new Set(paths)];
|
|
12630
13348
|
}
|
|
12631
13349
|
function buildScanTargets(config) {
|
|
@@ -12641,16 +13359,16 @@ function buildScanTargets(config) {
|
|
|
12641
13359
|
const targets = [];
|
|
12642
13360
|
targets.push(...hostTargets("user-pi"));
|
|
12643
13361
|
targets.push(...hostTargets("user-agents"));
|
|
12644
|
-
targets.push({ dir:
|
|
13362
|
+
targets.push({ dir: join20(home, ".agents", kind), source: "user-agents", enabled: true });
|
|
12645
13363
|
targets.push(...hostTargets("npm"));
|
|
12646
13364
|
targets.push(...hostTargets("npm-dev"));
|
|
12647
13365
|
targets.push(
|
|
12648
13366
|
...readExtensionPaths().map((dir) => ({ dir, source: "user-extension-paths", enabled: true }))
|
|
12649
13367
|
);
|
|
12650
|
-
targets.push({ dir:
|
|
13368
|
+
targets.push({ dir: join20(workspaceRoot, ".pi", kind), source: "project-pi", enabled: true });
|
|
12651
13369
|
if (includeTmp) {
|
|
12652
13370
|
targets.push({
|
|
12653
|
-
dir:
|
|
13371
|
+
dir: join20(workspaceRoot, ".pi", kind, ".tmp"),
|
|
12654
13372
|
source: "project-pi-tmp",
|
|
12655
13373
|
enabled: true
|
|
12656
13374
|
});
|
|
@@ -12658,7 +13376,7 @@ function buildScanTargets(config) {
|
|
|
12658
13376
|
targets.push(...hostTargets("project-host"));
|
|
12659
13377
|
targets.push(...hostTargets("project-agents"));
|
|
12660
13378
|
targets.push({
|
|
12661
|
-
dir:
|
|
13379
|
+
dir: join20(workspaceRoot, ".agents", kind),
|
|
12662
13380
|
source: "project-agents",
|
|
12663
13381
|
enabled: true
|
|
12664
13382
|
});
|
|
@@ -12708,7 +13426,7 @@ async function discoverResources(config) {
|
|
|
12708
13426
|
const msg = `[resource-discovery] duplicate ${config.kind} "${key}" from ${r.source} shadows ${existing.source}`;
|
|
12709
13427
|
const data = { shadowed: existing.path, kept: r.path };
|
|
12710
13428
|
if (isMachineSource(existing.source) || isMachineSource(r.source)) {
|
|
12711
|
-
|
|
13429
|
+
logger31.debug(msg, data);
|
|
12712
13430
|
} else {
|
|
12713
13431
|
const dedupKey = `${config.kind}|${key}|${existing.path}|${r.path}`;
|
|
12714
13432
|
if (!shadowWarnDedup.has(dedupKey)) {
|
|
@@ -12716,7 +13434,7 @@ async function discoverResources(config) {
|
|
|
12716
13434
|
shadowWarnDedup.clear();
|
|
12717
13435
|
}
|
|
12718
13436
|
shadowWarnDedup.add(dedupKey);
|
|
12719
|
-
|
|
13437
|
+
logger31.warn(msg, data);
|
|
12720
13438
|
}
|
|
12721
13439
|
}
|
|
12722
13440
|
}
|
|
@@ -13357,10 +14075,10 @@ function lintScript(source) {
|
|
|
13357
14075
|
}
|
|
13358
14076
|
|
|
13359
14077
|
// src/execution/agent-registry.ts
|
|
13360
|
-
var
|
|
14078
|
+
var logger32 = getLogger("subagents");
|
|
13361
14079
|
var FM_DELIM = "---";
|
|
13362
14080
|
function parseAgentWithMeta(filePath, content) {
|
|
13363
|
-
const name =
|
|
14081
|
+
const name = path18.basename(filePath, ".md");
|
|
13364
14082
|
const fm = splitAgentFrontmatter(content);
|
|
13365
14083
|
if (fm.kind === "none") {
|
|
13366
14084
|
return { config: { name, systemPrompt: content.trim() }, meta: null };
|
|
@@ -13390,7 +14108,7 @@ function resolveLegacyRoutingFallbacks(agentMeta, yamlBlock, filePath) {
|
|
|
13390
14108
|
const modelFallback = extractYamlField(yamlBlock, "model");
|
|
13391
14109
|
const toolsFallback = parseCommaListFallback(extractYamlField(yamlBlock, "tools"));
|
|
13392
14110
|
if (!agentMeta && /^model:|^tools:/m.test(yamlBlock)) {
|
|
13393
|
-
|
|
14111
|
+
logger32.warn(
|
|
13394
14112
|
`[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`
|
|
13395
14113
|
);
|
|
13396
14114
|
}
|
|
@@ -13428,7 +14146,7 @@ function extractYamlField(yaml, key) {
|
|
|
13428
14146
|
return value || void 0;
|
|
13429
14147
|
}
|
|
13430
14148
|
function parseAgentProfile(text, filePath) {
|
|
13431
|
-
const stem3 =
|
|
14149
|
+
const stem3 = path18.basename(filePath, ".md");
|
|
13432
14150
|
const warnings = [];
|
|
13433
14151
|
const fm = splitAgentFrontmatter(text);
|
|
13434
14152
|
if (fm.kind === "none") {
|
|
@@ -13566,84 +14284,13 @@ var AgentRegistry = class {
|
|
|
13566
14284
|
const { config, meta } = parseAgentWithMeta(filePath, file.content);
|
|
13567
14285
|
const lintFindings = meta ? lintAgentMeta(meta) : [];
|
|
13568
14286
|
for (const finding of lintFindings) {
|
|
13569
|
-
|
|
14287
|
+
logger32.warn(`[agent-registry] ${filePath}: ${finding.message}`);
|
|
13570
14288
|
}
|
|
13571
14289
|
this.fileCache.set(filePath, { mtimeMs: file.mtimeMs, config, meta });
|
|
13572
14290
|
return config;
|
|
13573
14291
|
}
|
|
13574
14292
|
};
|
|
13575
14293
|
|
|
13576
|
-
// src/execution/config.ts
|
|
13577
|
-
import * as fs23 from "fs";
|
|
13578
|
-
import * as path18 from "path";
|
|
13579
|
-
var logger32 = getLogger("subagents");
|
|
13580
|
-
var DEFAULT_CONFIG = {
|
|
13581
|
-
version: 1,
|
|
13582
|
-
maxConcurrent: 6
|
|
13583
|
-
};
|
|
13584
|
-
var DEFAULT_MAX_CONCURRENT = 6;
|
|
13585
|
-
function getGlobalConfigPath(agentDir) {
|
|
13586
|
-
return path18.join(agentDir, "subagents", "config.json");
|
|
13587
|
-
}
|
|
13588
|
-
function loadGlobalConfig(agentDir) {
|
|
13589
|
-
try {
|
|
13590
|
-
const raw = fs23.readFileSync(getGlobalConfigPath(agentDir), "utf-8");
|
|
13591
|
-
const parsed = JSON.parse(raw);
|
|
13592
|
-
return sanitizeParsedConfig(parsed);
|
|
13593
|
-
} catch {
|
|
13594
|
-
return { ...DEFAULT_CONFIG };
|
|
13595
|
-
}
|
|
13596
|
-
}
|
|
13597
|
-
function readGlobalConfig(agentDir) {
|
|
13598
|
-
const configPath = getGlobalConfigPath(agentDir);
|
|
13599
|
-
let raw;
|
|
13600
|
-
try {
|
|
13601
|
-
raw = fs23.readFileSync(configPath, "utf-8");
|
|
13602
|
-
} catch (err) {
|
|
13603
|
-
if (errnoCodeOf(err) === "ENOENT") {
|
|
13604
|
-
return { status: "absent", config: { ...DEFAULT_CONFIG } };
|
|
13605
|
-
}
|
|
13606
|
-
return readFailure(configPath, err);
|
|
13607
|
-
}
|
|
13608
|
-
try {
|
|
13609
|
-
const parsed = JSON.parse(raw);
|
|
13610
|
-
return { status: "ok", config: sanitizeParsedConfig(parsed) };
|
|
13611
|
-
} catch (err) {
|
|
13612
|
-
return readFailure(configPath, err);
|
|
13613
|
-
}
|
|
13614
|
-
}
|
|
13615
|
-
function readFailure(configPath, err) {
|
|
13616
|
-
const reason = err instanceof Error ? err.message : String(err);
|
|
13617
|
-
logger32.warn(`[subagents] global config read failed (read-failure) at ${configPath}: ${reason}`);
|
|
13618
|
-
return { status: "failed", reason };
|
|
13619
|
-
}
|
|
13620
|
-
function errnoCodeOf(err) {
|
|
13621
|
-
if (typeof err !== "object" || err === null || !("code" in err)) return void 0;
|
|
13622
|
-
const code = Reflect.get(err, "code");
|
|
13623
|
-
return typeof code === "string" ? code : void 0;
|
|
13624
|
-
}
|
|
13625
|
-
function sanitizeParsedConfig(parsed) {
|
|
13626
|
-
const defaultEngine = sanitizeDefaultEngine(parsed.defaultEngine);
|
|
13627
|
-
const engineRouting = sanitizeEngineRouting(parsed.engineRouting);
|
|
13628
|
-
return {
|
|
13629
|
-
version: parsed.version ?? DEFAULT_CONFIG.version,
|
|
13630
|
-
maxConcurrent: sanitizeMaxConcurrent(parsed.maxConcurrent),
|
|
13631
|
-
...defaultEngine !== void 0 ? { defaultEngine } : {},
|
|
13632
|
-
...engineRouting !== void 0 ? { engineRouting } : {}
|
|
13633
|
-
};
|
|
13634
|
-
}
|
|
13635
|
-
function sanitizeMaxConcurrent(value) {
|
|
13636
|
-
return typeof value === "number" && Number.isInteger(value) && value > 0 ? value : DEFAULT_MAX_CONCURRENT;
|
|
13637
|
-
}
|
|
13638
|
-
function sanitizeDefaultEngine(value) {
|
|
13639
|
-
return typeof value === "string" && value.trim() !== "" ? value : void 0;
|
|
13640
|
-
}
|
|
13641
|
-
function sanitizeEngineRouting(value) {
|
|
13642
|
-
if (typeof value !== "object" || value === null) return void 0;
|
|
13643
|
-
const strict = value.strict;
|
|
13644
|
-
return typeof strict === "boolean" ? { strict } : void 0;
|
|
13645
|
-
}
|
|
13646
|
-
|
|
13647
14294
|
// src/execution/model-resolver.ts
|
|
13648
14295
|
var MODEL_LIST_LIMIT2 = 20;
|
|
13649
14296
|
function resolveModel(agentConfig, modelRegistry, paramOverride, ctxModel) {
|
|
@@ -14593,6 +15240,12 @@ async function startHandler(service, input, signal, ctxModel) {
|
|
|
14593
15240
|
`slug is required for action:'start' (top-level field, must not be whitespace-only). Correct: {"action":"start","task":"...","slug":"<kebab-case>"}`
|
|
14594
15241
|
);
|
|
14595
15242
|
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".`);
|
|
15243
|
+
const resolvedCollect = input.collect ?? service.getCollectSyncDefault();
|
|
15244
|
+
if (input.conversation === true && resolvedCollect === "sync") {
|
|
15245
|
+
throw new Error(
|
|
15246
|
+
'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).'
|
|
15247
|
+
);
|
|
15248
|
+
}
|
|
14596
15249
|
const handle = await service.execute({
|
|
14597
15250
|
task,
|
|
14598
15251
|
slug,
|
|
@@ -14610,10 +15263,25 @@ async function startHandler(service, input, signal, ctxModel) {
|
|
|
14610
15263
|
conversation: input.conversation,
|
|
14611
15264
|
idleTimeoutMs: input.idleTimeoutMs,
|
|
14612
15265
|
engine: input.engine,
|
|
15266
|
+
// B1(code-simplify 审查发现的行为缺口):config collectSync.default=sync 且调用方
|
|
15267
|
+
// 省略 collect 时,record 本体也要落 sync(设计 §3.1.3「缺省 = config 默认」作用于
|
|
15268
|
+
// record,而非仅回显)——createRecordForMode 只认 opts.collect==="sync",原样透传
|
|
15269
|
+
// input.collect 会让 record 走 async 逐条通知而响应声称已入批。仅 sync 落值:
|
|
15270
|
+
// async/缺省路径传 undefined 语义(旧 record 零迁移)字节不变。
|
|
15271
|
+
collect: resolvedCollect === "sync" ? "sync" : input.collect,
|
|
14613
15272
|
ctxModel,
|
|
14614
15273
|
signal
|
|
14615
15274
|
// background detached 运行,完成由 notify 驱动新 turn。
|
|
14616
15275
|
});
|
|
15276
|
+
const response = {
|
|
15277
|
+
status: "running",
|
|
15278
|
+
mode: "background",
|
|
15279
|
+
message: BG_MESSAGE,
|
|
15280
|
+
notifyContract: NOTIFY_CONTRACT
|
|
15281
|
+
};
|
|
15282
|
+
if (resolvedCollect === "sync") {
|
|
15283
|
+
response.collect = { mode: "sync", pendingSyncCount: countPendingSyncRecords(service) };
|
|
15284
|
+
}
|
|
14617
15285
|
return {
|
|
14618
15286
|
kind: "bg",
|
|
14619
15287
|
subagentId: handle.subagentId,
|
|
@@ -14621,14 +15289,16 @@ async function startHandler(service, input, signal, ctxModel) {
|
|
|
14621
15289
|
slug: handle.details.slug,
|
|
14622
15290
|
// registry 全等回显:record.model 由 resolved(裁决放行条目)拼接,原样透出。
|
|
14623
15291
|
model: handle.details.model,
|
|
14624
|
-
response
|
|
14625
|
-
status: "running",
|
|
14626
|
-
mode: "background",
|
|
14627
|
-
message: BG_MESSAGE,
|
|
14628
|
-
notifyContract: NOTIFY_CONTRACT
|
|
14629
|
-
}
|
|
15292
|
+
response
|
|
14630
15293
|
};
|
|
14631
15294
|
}
|
|
15295
|
+
function countPendingSyncRecords(service) {
|
|
15296
|
+
let count = 0;
|
|
15297
|
+
for (const r of service.queries.collectRecords(COLLECT_SCAN_LIMIT, "all")) {
|
|
15298
|
+
if (r.collectMode === "sync" && r.batchFinalized !== true) count += 1;
|
|
15299
|
+
}
|
|
15300
|
+
return count;
|
|
15301
|
+
}
|
|
14632
15302
|
function listHandler(service, input) {
|
|
14633
15303
|
const includeFinished = input?.includeFinished === true;
|
|
14634
15304
|
const rawLimit = input?.limit ?? DEFAULT_LIST_LIMIT;
|
|
@@ -18192,7 +18862,7 @@ function boundedPrettySerialize(value, budget) {
|
|
|
18192
18862
|
}
|
|
18193
18863
|
|
|
18194
18864
|
// src/index.ts
|
|
18195
|
-
var CORE_PACKAGE_VERSION = "0.
|
|
18865
|
+
var CORE_PACKAGE_VERSION = "0.6.0";
|
|
18196
18866
|
export {
|
|
18197
18867
|
AGENT_REF_EXT,
|
|
18198
18868
|
AgentCall,
|