nolo-cli 0.163.0-alpha.1 → 0.164.0-alpha.2
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/{_executeModel-3FMHTHSS.js → _executeModel-LFPW52TC.js} +12 -12
- package/{agentDeleteCommand-VRHLJZKX.js → agentDeleteCommand-IMM5JJOZ.js} +10 -10
- package/{agentEmailCommands-YGBQXOL3.js → agentEmailCommands-7ILQ4X4X.js} +12 -12
- package/{agentMachineCommands-FBAWZYHI.js → agentMachineCommands-2EOGVRMN.js} +1 -1
- package/{agentRecordCommands-KAK2G56H.js → agentRecordCommands-5ECAWMGW.js} +11 -11
- package/{agentRunCommand-62P3AGJA.js → agentRunCommand-DE4WN2TT.js} +12 -12
- package/{agentRunControl-XBSNHHZE.js → agentRunControl-NQR3GB6H.js} +1 -1
- package/{agentSlice-IZFU3WWH.js → agentSlice-YDRAYF3Z.js} +1 -1
- package/{chunk-2QQ7D3P5.js → chunk-3WTD7IIQ.js} +8 -8
- package/{chunk-7YQQXP42.js → chunk-4BLKVQBS.js} +2 -2
- package/{chunk-HRZW3SQN.js → chunk-6WNJ2XSC.js} +1 -1
- package/{chunk-EDJND3FK.js → chunk-AOVELMYY.js} +3 -3
- package/{chunk-Q5LVXPGT.js → chunk-CAWIORRR.js} +2 -2
- package/{chunk-CI6YDDL2.js → chunk-DRZCUIYC.js} +2 -2
- package/{chunk-K3N2DWLT.js → chunk-FKBWA3NN.js} +2 -2
- package/{chunk-6QIBWOPT.js → chunk-HXFIFEBA.js} +2 -2
- package/{chunk-V67W64OY.js → chunk-JGFVMK3S.js} +7 -7
- package/{chunk-FTHF2P2I.js → chunk-KVDB4IIT.js} +1 -1
- package/{chunk-UUPXTEZA.js → chunk-NOFLIR3Z.js} +1 -1
- package/{chunk-PNACBSRF.js → chunk-ONA4OKNI.js} +194 -31
- package/{chunk-7PDNSQZC.js → chunk-QKBEFLMA.js} +2 -2
- package/{chunk-GXZZOTDM.js → chunk-RYM5SWKD.js} +4546 -3344
- package/{chunk-U5U4WAFS.js → chunk-S53PIA3G.js} +36 -3
- package/{chunk-36IWXF25.js → chunk-VNPUBOKQ.js} +5 -5
- package/{chunk-JIAZK434.js → chunk-VVRYD7UL.js} +2 -2
- package/{chunk-4FDDVVCE.js → chunk-WBRBIRDG.js} +2 -2
- package/{chunk-SNAWGDXT.js → chunk-WJJL7RMW.js} +518 -67
- package/{chunk-QFL42TMV.js → chunk-ZEOGFBR3.js} +90 -15
- package/{commandRegistry-2DDDET7G.js → commandRegistry-TNRRYS6G.js} +26 -26
- package/{contentThunks-5DNEHJ2Z.js → contentThunks-FXRH3TBV.js} +3 -3
- package/{createAgentAutomationAction-4SUS2EFX.js → createAgentAutomationAction-6M64V55C.js} +3 -3
- package/{createDialogAction-JSFXX6JE.js → createDialogAction-LTK2YJ7I.js} +4 -4
- package/{createPageAction-37HHOXZ2.js → createPageAction-IKFGJRKB.js} +3 -3
- package/{dialogCommands-ZJRMCP4S.js → dialogCommands-7FZEZ5DV.js} +2 -2
- package/{dialogSlice-2Z66AWEW.js → dialogSlice-TTJ74TZ2.js} +2 -2
- package/{docStore-XMTUF3GX.js → docStore-GZW6PV6J.js} +1 -1
- package/{handleSendMessageAction-4VFVXTF2.js → handleSendMessageAction-YICSRGEU.js} +7 -7
- package/index.js +3 -3
- package/{localRuntimeAdapter-KKIVRYVN.js → localRuntimeAdapter-MTZVCQW6.js} +10 -10
- package/{machineCommands-UHQVCOD3.js → machineCommands-4P6O4WJD.js} +11 -11
- package/{machineWsRunDispatch-TX4X7TOJ.js → machineWsRunDispatch-2OQB3YA3.js} +11 -11
- package/package.json +1 -1
- package/{readlineWorkspace-BFVB2ON5.js → readlineWorkspace-EB5D4MF6.js} +13 -13
- package/{runAgentClientLoop-HHLHNMBE.js → runAgentClientLoop-DROGF2SZ.js} +10 -10
- package/{streamAgentChatTurn-KNPOLET7.js → streamAgentChatTurn-PJ2L3P25.js} +12 -12
- package/{tools-ULA7XJQT.js → tools-2MFN4Z5Y.js} +7 -7
- package/{updateDialogTitleAction-VUIVBDAS.js → updateDialogTitleAction-VQFUSGRH.js} +3 -3
|
@@ -320,8 +320,8 @@ function createShellApprovalChild(channel, timeoutMs = TTL) {
|
|
|
320
320
|
|
|
321
321
|
// packages/cli/agentRunControl.ts
|
|
322
322
|
import { homedir as nodeHomedir } from "node:os";
|
|
323
|
-
import { dirname, isAbsolute as isAbsolute2, join, resolve } from "node:path";
|
|
324
|
-
import * as
|
|
323
|
+
import { dirname, isAbsolute as isAbsolute2, join as join2, resolve } from "node:path";
|
|
324
|
+
import * as nodeFs2 from "node:fs";
|
|
325
325
|
import { execFileSync as nodeExecFileSync, spawn as nodeSpawn } from "node:child_process";
|
|
326
326
|
|
|
327
327
|
// packages/cli/client/hostMemory.ts
|
|
@@ -551,6 +551,404 @@ function formatHostMemoryLine(mem) {
|
|
|
551
551
|
return isHostMemoryTight(mem) ? `${line} \xB7 ${HOST_MEMORY_PRESSURE_HINT}` : line;
|
|
552
552
|
}
|
|
553
553
|
|
|
554
|
+
// packages/cli/client/hostMemoryReservation.ts
|
|
555
|
+
import { join } from "path";
|
|
556
|
+
import * as nodeFs from "fs";
|
|
557
|
+
|
|
558
|
+
// packages/cli/client/hostMemoryAdmission.ts
|
|
559
|
+
var LOCAL_AGENT_RUN_MIN_AVAILABLE_MB = 512;
|
|
560
|
+
var STARTUP_WINDOW_MS = 1e4;
|
|
561
|
+
var STARTUP_WINDOW_RESERVED_MB_PER_RUN = 384;
|
|
562
|
+
var HostMemoryAdmissionError = class extends Error {
|
|
563
|
+
code = "HOST_MEMORY_ADMISSION_DENIED";
|
|
564
|
+
details;
|
|
565
|
+
constructor(message, details) {
|
|
566
|
+
super(message);
|
|
567
|
+
this.name = "HostMemoryAdmissionError";
|
|
568
|
+
this.details = details;
|
|
569
|
+
}
|
|
570
|
+
};
|
|
571
|
+
function assertHostMemoryAdmission(deps = {}) {
|
|
572
|
+
const readMem = deps.readHostMemory ?? readHostMemory;
|
|
573
|
+
const snapshot = readMem(deps.hostMemoryDeps);
|
|
574
|
+
const minAvailableMb = deps.minAvailableMb ?? LOCAL_AGENT_RUN_MIN_AVAILABLE_MB;
|
|
575
|
+
const startupWindowMs = deps.startupWindowMs ?? STARTUP_WINDOW_MS;
|
|
576
|
+
const reservedMbPerRun = deps.reservedMbPerRun ?? STARTUP_WINDOW_RESERVED_MB_PER_RUN;
|
|
577
|
+
const now = deps.now ? deps.now().getTime() : Date.now();
|
|
578
|
+
for (const [name, value] of Object.entries({ minAvailableMb, startupWindowMs, reservedMbPerRun, now })) {
|
|
579
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) throw new Error(`Invalid ${name}`);
|
|
580
|
+
}
|
|
581
|
+
if (!snapshot) {
|
|
582
|
+
throw new HostMemoryAdmissionError(
|
|
583
|
+
`[host-memory-admission] \u65E0\u6CD5\u6D4B\u5B9A\u672C\u673A\u53EF\u7528\u5185\u5B58\uFF08\u5FEB\u7167\u4E3A null\uFF09\uFF0C\u62D2\u7EDD\u672C\u5730 fork\u3002\u9700\u786E\u4FDD /proc/meminfo \u6216 OS \u5185\u5B58\u6307\u6807\u53EF\u8BFB\u3002`,
|
|
584
|
+
{
|
|
585
|
+
effectiveAvailableMb: null,
|
|
586
|
+
requiredMinMb: minAvailableMb,
|
|
587
|
+
source: "null",
|
|
588
|
+
limitingSource: "unknown",
|
|
589
|
+
activeStartupRuns: 0,
|
|
590
|
+
reservedStartupMb: 0,
|
|
591
|
+
unreservedAvailableMb: null
|
|
592
|
+
}
|
|
593
|
+
);
|
|
594
|
+
}
|
|
595
|
+
if (!["cgroup", "proc-meminfo", "os"].includes(snapshot.source)) throw new Error("Unknown host memory source");
|
|
596
|
+
let effectiveAvailableMb = null;
|
|
597
|
+
let limitingSource = "host";
|
|
598
|
+
if (snapshot.source === "cgroup") {
|
|
599
|
+
const cgroupAvail = snapshot.availableMb;
|
|
600
|
+
const hostAvail = snapshot.hostAvailableMb;
|
|
601
|
+
if (cgroupAvail === null || typeof cgroupAvail !== "number" || Number.isNaN(cgroupAvail) || !Number.isFinite(cgroupAvail) || cgroupAvail < 0) {
|
|
602
|
+
throw new HostMemoryAdmissionError(
|
|
603
|
+
`[host-memory-admission] cgroup \u914D\u989D\u5185\u5B58\u5360\u7528\u672A\u77E5\u6216\u65E0\u6548\uFF0C\u62D2\u7EDD\u65E0\u58F0\u5F53\u5145\u8DB3\u3002`,
|
|
604
|
+
{
|
|
605
|
+
effectiveAvailableMb: null,
|
|
606
|
+
requiredMinMb: minAvailableMb,
|
|
607
|
+
source: snapshot.source,
|
|
608
|
+
limitingSource: "cgroup",
|
|
609
|
+
activeStartupRuns: 0,
|
|
610
|
+
reservedStartupMb: 0,
|
|
611
|
+
unreservedAvailableMb: null
|
|
612
|
+
}
|
|
613
|
+
);
|
|
614
|
+
}
|
|
615
|
+
if (typeof hostAvail !== "number" || !Number.isFinite(hostAvail) || hostAvail < 0) {
|
|
616
|
+
throw new HostMemoryAdmissionError("[host-memory-admission] \u5BBF\u4E3B\u53EF\u7528\u5185\u5B58\u672A\u77E5\u6216\u65E0\u6548\uFF0C\u62D2\u7EDD\u672C\u5730 fork\u3002", {
|
|
617
|
+
effectiveAvailableMb: null,
|
|
618
|
+
requiredMinMb: minAvailableMb,
|
|
619
|
+
source: snapshot.source,
|
|
620
|
+
limitingSource: "host",
|
|
621
|
+
activeStartupRuns: 0,
|
|
622
|
+
reservedStartupMb: 0,
|
|
623
|
+
unreservedAvailableMb: null
|
|
624
|
+
});
|
|
625
|
+
}
|
|
626
|
+
if (Number.isFinite(hostAvail)) {
|
|
627
|
+
if (cgroupAvail <= hostAvail) {
|
|
628
|
+
effectiveAvailableMb = cgroupAvail;
|
|
629
|
+
limitingSource = "cgroup";
|
|
630
|
+
} else {
|
|
631
|
+
effectiveAvailableMb = hostAvail;
|
|
632
|
+
limitingSource = "host";
|
|
633
|
+
}
|
|
634
|
+
} else {
|
|
635
|
+
effectiveAvailableMb = cgroupAvail;
|
|
636
|
+
limitingSource = "cgroup";
|
|
637
|
+
}
|
|
638
|
+
} else {
|
|
639
|
+
if (snapshot.availableMb === null || typeof snapshot.availableMb !== "number" || Number.isNaN(snapshot.availableMb) || !Number.isFinite(snapshot.availableMb) || snapshot.availableMb < 0) {
|
|
640
|
+
throw new HostMemoryAdmissionError(
|
|
641
|
+
`[host-memory-admission] \u5BBF\u4E3B\u53EF\u7528\u5185\u5B58\u672A\u77E5\u6216\u65E0\u6548\uFF0C\u62D2\u7EDD\u65E0\u58F0\u5F53\u5145\u8DB3\u3002`,
|
|
642
|
+
{
|
|
643
|
+
effectiveAvailableMb: null,
|
|
644
|
+
requiredMinMb: minAvailableMb,
|
|
645
|
+
source: snapshot.source,
|
|
646
|
+
limitingSource: "host",
|
|
647
|
+
activeStartupRuns: 0,
|
|
648
|
+
reservedStartupMb: 0,
|
|
649
|
+
unreservedAvailableMb: null
|
|
650
|
+
}
|
|
651
|
+
);
|
|
652
|
+
}
|
|
653
|
+
effectiveAvailableMb = snapshot.availableMb;
|
|
654
|
+
limitingSource = "host";
|
|
655
|
+
}
|
|
656
|
+
let activeStartupRuns = 0;
|
|
657
|
+
if (deps.listRecentRuns) {
|
|
658
|
+
const records = deps.listRecentRuns();
|
|
659
|
+
if (!Array.isArray(records)) {
|
|
660
|
+
throw new HostMemoryAdmissionError(
|
|
661
|
+
`[host-memory-admission] \u83B7\u53D6\u6700\u8FD1\u8FD0\u884C\u8BB0\u5F55\u5217\u8868\u5931\u8D25\uFF0C\u62D2\u7EDD\u672A\u77E5\u5F53\u5F52\u96F6\u3002`,
|
|
662
|
+
{
|
|
663
|
+
effectiveAvailableMb,
|
|
664
|
+
requiredMinMb: minAvailableMb,
|
|
665
|
+
source: snapshot.source,
|
|
666
|
+
limitingSource,
|
|
667
|
+
activeStartupRuns: 0,
|
|
668
|
+
reservedStartupMb: 0,
|
|
669
|
+
unreservedAvailableMb: effectiveAvailableMb
|
|
670
|
+
}
|
|
671
|
+
);
|
|
672
|
+
}
|
|
673
|
+
for (const rec of records) {
|
|
674
|
+
if (!rec || typeof rec !== "object") {
|
|
675
|
+
throw new HostMemoryAdmissionError(
|
|
676
|
+
`[host-memory-admission] \u8FD0\u884C\u8BB0\u5F55\u635F\u574F\uFF0C\u62D2\u7EDD\u672A\u77E5\u5F53\u5F52\u96F6\u3002`,
|
|
677
|
+
{
|
|
678
|
+
effectiveAvailableMb,
|
|
679
|
+
requiredMinMb: minAvailableMb,
|
|
680
|
+
source: snapshot.source,
|
|
681
|
+
limitingSource,
|
|
682
|
+
activeStartupRuns: 0,
|
|
683
|
+
reservedStartupMb: 0,
|
|
684
|
+
unreservedAvailableMb: effectiveAvailableMb
|
|
685
|
+
}
|
|
686
|
+
);
|
|
687
|
+
}
|
|
688
|
+
if (rec.status !== "running") continue;
|
|
689
|
+
const startedTime = new Date(rec.startedAt).getTime();
|
|
690
|
+
if (!Number.isFinite(startedTime) || Number.isNaN(startedTime)) {
|
|
691
|
+
throw new HostMemoryAdmissionError(
|
|
692
|
+
`[host-memory-admission] \u8FD0\u884C\u8BB0\u5F55 startedAt \u65E0\u6548\uFF0C\u62D2\u7EDD\u672A\u77E5\u5F53\u5F52\u96F6\u3002`,
|
|
693
|
+
{
|
|
694
|
+
effectiveAvailableMb,
|
|
695
|
+
requiredMinMb: minAvailableMb,
|
|
696
|
+
source: snapshot.source,
|
|
697
|
+
limitingSource,
|
|
698
|
+
activeStartupRuns: 0,
|
|
699
|
+
reservedStartupMb: 0,
|
|
700
|
+
unreservedAvailableMb: effectiveAvailableMb
|
|
701
|
+
}
|
|
702
|
+
);
|
|
703
|
+
}
|
|
704
|
+
if (now - startedTime >= 0 && now - startedTime < startupWindowMs) {
|
|
705
|
+
activeStartupRuns += 1;
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
const reservedStartupMb = activeStartupRuns * reservedMbPerRun;
|
|
710
|
+
const netAvailableMb = effectiveAvailableMb - reservedStartupMb;
|
|
711
|
+
if (netAvailableMb < minAvailableMb) {
|
|
712
|
+
const detailMsg = `\u5F53\u524D\u6709\u6548\u53EF\u7528 ${formatHostMemoryMb(effectiveAvailableMb)}\uFF08\u53D7\u9650\u6E90: ${limitingSource}\uFF09` + (activeStartupRuns > 0 ? `\uFF0C\u5E76\u53D1\u542F\u52A8\u7A97\u53E3\u5185\u6709 ${activeStartupRuns} \u4E2A\u8FDB\u884C\u4E2D\u4EFB\u52A1\uFF08\u9884\u7559 ${formatHostMemoryMb(reservedStartupMb)}\uFF0C\u6298\u7B97\u5269\u4F59 ${formatHostMemoryMb(netAvailableMb)}\uFF09` : "") + `\uFF0C\u4F4E\u4E8E\u5B89\u5168\u9608\u503C ${formatHostMemoryMb(minAvailableMb)}\u3002`;
|
|
713
|
+
throw new HostMemoryAdmissionError(
|
|
714
|
+
`[host-memory-admission] \u62D2\u7EDD\u672C\u5730\u6D3E\u53D1\uFF1A${detailMsg}\u4E3A\u907F\u514D\u89E6\u53D1\u5BBF\u4E3B/cgroup OOM-killer \u5D29\u6E83\uFF0C\u8BF7\u7B49\u5F85\u5176\u4ED6\u4EFB\u52A1\u5B8C\u6210\u6216\u91CA\u653E\u5185\u5B58\u540E\u518D\u8BD5\u3002`,
|
|
715
|
+
{
|
|
716
|
+
effectiveAvailableMb: netAvailableMb,
|
|
717
|
+
requiredMinMb: minAvailableMb,
|
|
718
|
+
source: snapshot.source,
|
|
719
|
+
limitingSource,
|
|
720
|
+
activeStartupRuns,
|
|
721
|
+
reservedStartupMb,
|
|
722
|
+
unreservedAvailableMb: effectiveAvailableMb
|
|
723
|
+
}
|
|
724
|
+
);
|
|
725
|
+
}
|
|
726
|
+
return {
|
|
727
|
+
allowed: true,
|
|
728
|
+
effectiveAvailableMb: netAvailableMb,
|
|
729
|
+
requiredMinMb: minAvailableMb,
|
|
730
|
+
activeStartupRuns,
|
|
731
|
+
snapshot
|
|
732
|
+
};
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
// packages/cli/client/hostMemoryReservation.ts
|
|
736
|
+
var MEMORY_RESERVATION_LOCK_ID = "__host_memory_reservation__";
|
|
737
|
+
function resolveReservationsDir(runsDir) {
|
|
738
|
+
return join(runsDir, "reservations");
|
|
739
|
+
}
|
|
740
|
+
function defaultIsProcessAlive(pid) {
|
|
741
|
+
if (!Number.isInteger(pid) || pid <= 0) throw new Error("Invalid PID");
|
|
742
|
+
try {
|
|
743
|
+
process.kill(pid, 0);
|
|
744
|
+
return true;
|
|
745
|
+
} catch (err) {
|
|
746
|
+
if (err && err.code === "ESRCH") {
|
|
747
|
+
return false;
|
|
748
|
+
}
|
|
749
|
+
return true;
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
function sleepSync(ms) {
|
|
753
|
+
if (ms <= 0) return;
|
|
754
|
+
try {
|
|
755
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
756
|
+
} catch {
|
|
757
|
+
const end = Date.now() + ms;
|
|
758
|
+
while (Date.now() < end) {
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
function updateReservationFile(filePath, fs, updater) {
|
|
763
|
+
const record = JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
764
|
+
updater(record);
|
|
765
|
+
const payload = JSON.stringify(record, null, 2);
|
|
766
|
+
if (fs.renameSync) {
|
|
767
|
+
const tmpPath = `${filePath}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
768
|
+
fs.writeFileSync(tmpPath, payload);
|
|
769
|
+
fs.renameSync(tmpPath, filePath);
|
|
770
|
+
} else {
|
|
771
|
+
fs.writeFileSync(filePath, payload);
|
|
772
|
+
}
|
|
773
|
+
return record;
|
|
774
|
+
}
|
|
775
|
+
function scanActiveReservations(reservationsDir, deps) {
|
|
776
|
+
const { fs, nowMs, startupWindowMs, isProcessAlive } = deps;
|
|
777
|
+
if (!fs.existsSync(reservationsDir)) {
|
|
778
|
+
return { activeStartupRuns: 0, reservedStartupMb: 0 };
|
|
779
|
+
}
|
|
780
|
+
let entries;
|
|
781
|
+
try {
|
|
782
|
+
entries = fs.readdirSync(reservationsDir);
|
|
783
|
+
} catch (err) {
|
|
784
|
+
throw new Error(
|
|
785
|
+
`[host-memory-reservation] \u8BFB\u53D6\u9884\u7559\u76EE\u5F55\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}`
|
|
786
|
+
);
|
|
787
|
+
}
|
|
788
|
+
let activeStartupRuns = 0;
|
|
789
|
+
let reservedStartupMb = 0;
|
|
790
|
+
for (const entry of entries) {
|
|
791
|
+
if (!entry.endsWith(".json") || !entry.startsWith("reservation-")) continue;
|
|
792
|
+
const filePath = join(reservationsDir, entry);
|
|
793
|
+
let record;
|
|
794
|
+
try {
|
|
795
|
+
const raw = fs.readFileSync(filePath, "utf8");
|
|
796
|
+
record = JSON.parse(raw);
|
|
797
|
+
} catch (err) {
|
|
798
|
+
throw new Error(
|
|
799
|
+
`[host-memory-reservation] \u89E3\u6790\u9884\u7559\u8BB0\u5F55\u6587\u4EF6\u5931\u8D25 (${filePath}): ${err instanceof Error ? err.message : String(err)}\uFF0C\u62D2\u7EDD\u672A\u77E5\u5F53\u5F52\u96F6`
|
|
800
|
+
);
|
|
801
|
+
}
|
|
802
|
+
if (!record || typeof record !== "object") {
|
|
803
|
+
throw new Error(`[host-memory-reservation] \u9884\u7559\u8BB0\u5F55\u683C\u5F0F\u635F\u574F (${filePath})\uFF0C\u62D2\u7EDD\u672A\u77E5\u5F53\u5F52\u96F6`);
|
|
804
|
+
}
|
|
805
|
+
if (typeof record.reservedMb !== "number" || Number.isNaN(record.reservedMb) || !Number.isFinite(record.reservedMb) || record.reservedMb < 0) {
|
|
806
|
+
throw new Error(`[host-memory-reservation] \u9884\u7559\u8BB0\u5F55\u5305\u542B\u65E0\u6548 reservedMb (${filePath})`);
|
|
807
|
+
}
|
|
808
|
+
if (typeof record.createdAtMs !== "number" || Number.isNaN(record.createdAtMs) || !Number.isFinite(record.createdAtMs) || record.createdAtMs < 0) {
|
|
809
|
+
throw new Error(`[host-memory-reservation] \u9884\u7559\u8BB0\u5F55\u5305\u542B\u65E0\u6548 createdAtMs (${filePath})`);
|
|
810
|
+
}
|
|
811
|
+
if (record.status === "released" || record.status === "reclaimed") {
|
|
812
|
+
continue;
|
|
813
|
+
}
|
|
814
|
+
if (record.status !== "reserved" && record.status !== "active") {
|
|
815
|
+
throw new Error(`[host-memory-reservation] invalid status: ${filePath}`);
|
|
816
|
+
}
|
|
817
|
+
const ageMs = nowMs - record.createdAtMs;
|
|
818
|
+
const pid = record.childPid ?? record.holderPid;
|
|
819
|
+
if (!Number.isInteger(pid) || pid <= 0 || ageMs < 0) {
|
|
820
|
+
throw new Error(`[host-memory-reservation] invalid process/time: ${filePath}`);
|
|
821
|
+
}
|
|
822
|
+
const expectedStart = record.childPid ? record.childStartedAt : record.holderStartedAt;
|
|
823
|
+
let alive = true;
|
|
824
|
+
let actualStart;
|
|
825
|
+
try {
|
|
826
|
+
alive = isProcessAlive(pid);
|
|
827
|
+
if (alive && expectedStart) actualStart = deps.getProcessStartTime?.(pid);
|
|
828
|
+
} catch {
|
|
829
|
+
}
|
|
830
|
+
const reused = expectedStart && actualStart && Number.isFinite(actualStart.getTime()) && actualStart.toISOString() !== expectedStart;
|
|
831
|
+
if (!alive || reused || record.status === "active" && ageMs >= startupWindowMs) {
|
|
832
|
+
updateReservationFile(filePath, fs, (r) => {
|
|
833
|
+
r.status = "reclaimed";
|
|
834
|
+
r.reclaimedAt = new Date(nowMs).toISOString();
|
|
835
|
+
r.reason = !alive ? "stale_process_died" : reused ? "pid_reused" : "startup_window_elapsed_rss_accounted";
|
|
836
|
+
});
|
|
837
|
+
continue;
|
|
838
|
+
}
|
|
839
|
+
activeStartupRuns += 1;
|
|
840
|
+
reservedStartupMb += record.reservedMb;
|
|
841
|
+
if (!Number.isFinite(reservedStartupMb)) throw new Error("Invalid reservation total");
|
|
842
|
+
}
|
|
843
|
+
return { activeStartupRuns, reservedStartupMb };
|
|
844
|
+
}
|
|
845
|
+
function reserveHostMemory(deps) {
|
|
846
|
+
const fs = deps.fs ?? nodeFs;
|
|
847
|
+
const runsDir = deps.runsDir;
|
|
848
|
+
const reservationsDir = resolveReservationsDir(runsDir);
|
|
849
|
+
const now = deps.now ? deps.now() : /* @__PURE__ */ new Date();
|
|
850
|
+
let nowMs = now.getTime();
|
|
851
|
+
const minAvailableMb = deps.minAvailableMb ?? LOCAL_AGENT_RUN_MIN_AVAILABLE_MB;
|
|
852
|
+
const startupWindowMs = deps.startupWindowMs ?? STARTUP_WINDOW_MS;
|
|
853
|
+
const requestedMb = deps.requestedMb ?? STARTUP_WINDOW_RESERVED_MB_PER_RUN;
|
|
854
|
+
const isProcessAlive = deps.isProcessAlive ?? defaultIsProcessAlive;
|
|
855
|
+
const maxAttempts = deps.maxLockAttempts ?? 5;
|
|
856
|
+
const delayMs = deps.lockRetryDelayMs ?? 15;
|
|
857
|
+
for (const [name, value] of Object.entries({ requestedMb, minAvailableMb, startupWindowMs, maxAttempts, delayMs, nowMs })) {
|
|
858
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
|
|
859
|
+
throw new Error(`[host-memory-reservation] invalid ${name}: ${value}`);
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
if (!Number.isInteger(maxAttempts) || maxAttempts < 1) throw new Error("Invalid lock attempts");
|
|
863
|
+
if (!deps.withLock) throw new Error("[host-memory-reservation] exclusive lock required");
|
|
864
|
+
fs.mkdirSync(reservationsDir, { recursive: true });
|
|
865
|
+
const locked = (fn) => {
|
|
866
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
867
|
+
const result = deps.withLock(MEMORY_RESERVATION_LOCK_ID, () => ({ value: fn() }), { strict: true });
|
|
868
|
+
if (result !== void 0) return result.value;
|
|
869
|
+
if (attempt + 1 < maxAttempts) sleepSync(delayMs);
|
|
870
|
+
}
|
|
871
|
+
throw new Error("[host-memory-reservation] exclusive lock timeout");
|
|
872
|
+
};
|
|
873
|
+
const executeReservation = () => {
|
|
874
|
+
nowMs = (deps.now ? deps.now() : /* @__PURE__ */ new Date()).getTime();
|
|
875
|
+
if (!Number.isFinite(nowMs)) throw new Error("Invalid reservation time");
|
|
876
|
+
const { activeStartupRuns, reservedStartupMb } = scanActiveReservations(reservationsDir, {
|
|
877
|
+
fs,
|
|
878
|
+
nowMs,
|
|
879
|
+
startupWindowMs,
|
|
880
|
+
isProcessAlive,
|
|
881
|
+
getProcessStartTime: deps.getProcessStartTime
|
|
882
|
+
});
|
|
883
|
+
assertHostMemoryAdmission({
|
|
884
|
+
readHostMemory: deps.readHostMemory,
|
|
885
|
+
hostMemoryDeps: deps.hostMemoryDeps,
|
|
886
|
+
minAvailableMb: minAvailableMb + reservedStartupMb + requestedMb,
|
|
887
|
+
now: () => new Date(nowMs)
|
|
888
|
+
});
|
|
889
|
+
const id = `res-${nowMs.toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
890
|
+
const recordPath = join(reservationsDir, `reservation-${id}.json`);
|
|
891
|
+
const record = {
|
|
892
|
+
id,
|
|
893
|
+
runId: deps.runId,
|
|
894
|
+
status: "reserved",
|
|
895
|
+
reservedMb: requestedMb,
|
|
896
|
+
createdAt: new Date(nowMs).toISOString(),
|
|
897
|
+
createdAtMs: nowMs,
|
|
898
|
+
holderPid: process.pid,
|
|
899
|
+
holderStartedAt: deps.getProcessStartTime?.(process.pid)?.toISOString()
|
|
900
|
+
};
|
|
901
|
+
const payload = JSON.stringify(record, null, 2);
|
|
902
|
+
fs.writeFileSync(recordPath, payload, "utf8");
|
|
903
|
+
return {
|
|
904
|
+
id,
|
|
905
|
+
reservedMb: requestedMb,
|
|
906
|
+
recordPath,
|
|
907
|
+
associate(params) {
|
|
908
|
+
if (!params.runId || !Number.isInteger(params.pid) || params.pid <= 0) throw new Error("Invalid child association");
|
|
909
|
+
const validStart = !!params.processStartedAt && Number.isFinite(Date.parse(params.processStartedAt));
|
|
910
|
+
locked(() => updateReservationFile(recordPath, fs, (r) => {
|
|
911
|
+
if (r.status !== "reserved") throw new Error("Reservation is not reserved");
|
|
912
|
+
if (validStart) {
|
|
913
|
+
r.status = "active";
|
|
914
|
+
r.childStartedAt = params.processStartedAt;
|
|
915
|
+
}
|
|
916
|
+
if (params.runId) r.runId = params.runId;
|
|
917
|
+
if (typeof params.pid === "number") r.childPid = params.pid;
|
|
918
|
+
}));
|
|
919
|
+
if (!validStart) throw new Error("Child starttime unknown; reservation retained");
|
|
920
|
+
},
|
|
921
|
+
release(reason) {
|
|
922
|
+
locked(() => updateReservationFile(recordPath, fs, (r) => {
|
|
923
|
+
if (r.status === "reserved" || r.status === "active") {
|
|
924
|
+
r.status = "released";
|
|
925
|
+
r.releasedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
926
|
+
if (reason) r.reason = reason;
|
|
927
|
+
}
|
|
928
|
+
}));
|
|
929
|
+
}
|
|
930
|
+
};
|
|
931
|
+
};
|
|
932
|
+
if (deps.withLock) {
|
|
933
|
+
const withLock = deps.withLock;
|
|
934
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
935
|
+
const handle = withLock(MEMORY_RESERVATION_LOCK_ID, executeReservation, {
|
|
936
|
+
strict: true
|
|
937
|
+
});
|
|
938
|
+
if (handle !== void 0) {
|
|
939
|
+
return handle;
|
|
940
|
+
}
|
|
941
|
+
if (attempt < maxAttempts - 1) {
|
|
942
|
+
sleepSync(delayMs);
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
throw new Error(
|
|
946
|
+
`[host-memory-reservation] \u83B7\u53D6\u9884\u7559\u4E92\u65A5\u9501\u8D85\u65F6\uFF08\u5C1D\u8BD5 ${maxAttempts} \u6B21\uFF09\uFF0C\u62D2\u7EDD\u5E76\u53D1\u51FB\u7A7F`
|
|
947
|
+
);
|
|
948
|
+
}
|
|
949
|
+
throw new Error("[host-memory-reservation] exclusive lock required");
|
|
950
|
+
}
|
|
951
|
+
|
|
554
952
|
// packages/cli/client/localRuntimeHelpers.ts
|
|
555
953
|
var REMOTE_DIALOG_SYNC_TIMEOUT_MS = 1e4;
|
|
556
954
|
var remoteDialogSyncTimeoutMsForTest;
|
|
@@ -695,19 +1093,19 @@ function isRunTerminalStatus(status) {
|
|
|
695
1093
|
function resolveNoloHome(env, homedir = nodeHomedir) {
|
|
696
1094
|
const fromEnv = env?.NOLO_HOME;
|
|
697
1095
|
if (typeof fromEnv === "string" && fromEnv.length > 0) return fromEnv;
|
|
698
|
-
return
|
|
1096
|
+
return join2(homedir(), ".nolo");
|
|
699
1097
|
}
|
|
700
1098
|
function resolveRunsDir(env, homedir = nodeHomedir) {
|
|
701
|
-
return
|
|
1099
|
+
return join2(resolveNoloHome(env, homedir), "runs");
|
|
702
1100
|
}
|
|
703
1101
|
function resolveRunRecordPath(runId, env, homedir = nodeHomedir) {
|
|
704
|
-
return
|
|
1102
|
+
return join2(resolveRunsDir(env, homedir), `${runId}.json`);
|
|
705
1103
|
}
|
|
706
1104
|
function resolveRunLogPath(runId, env, homedir = nodeHomedir) {
|
|
707
|
-
return
|
|
1105
|
+
return join2(resolveRunsDir(env, homedir), `${runId}.log`);
|
|
708
1106
|
}
|
|
709
1107
|
function resolveRunQueuePath(runId, env, homedir = nodeHomedir) {
|
|
710
|
-
return
|
|
1108
|
+
return join2(resolveRunsDir(env, homedir), `${runId}.queue.jsonl`);
|
|
711
1109
|
}
|
|
712
1110
|
function defaultGenerateQueueEntryId() {
|
|
713
1111
|
const ts = Date.now().toString(36);
|
|
@@ -746,7 +1144,7 @@ function _serializeQueueEntries(entries) {
|
|
|
746
1144
|
return entries.map((e) => JSON.stringify(e)).join("\n") + "\n";
|
|
747
1145
|
}
|
|
748
1146
|
var serializeQueueEntries = _serializeQueueEntries;
|
|
749
|
-
function _countQueueMessagesLocked(queuePath, fs =
|
|
1147
|
+
function _countQueueMessagesLocked(queuePath, fs = nodeFs2) {
|
|
750
1148
|
if (!fs.existsSync(queuePath)) return 0;
|
|
751
1149
|
try {
|
|
752
1150
|
const content = fs.readFileSync(queuePath, "utf8");
|
|
@@ -755,7 +1153,7 @@ function _countQueueMessagesLocked(queuePath, fs = nodeFs) {
|
|
|
755
1153
|
return 0;
|
|
756
1154
|
}
|
|
757
1155
|
}
|
|
758
|
-
function _readQueueMessagesLocked(queuePath, fs =
|
|
1156
|
+
function _readQueueMessagesLocked(queuePath, fs = nodeFs2) {
|
|
759
1157
|
if (!fs.existsSync(queuePath)) return [];
|
|
760
1158
|
try {
|
|
761
1159
|
const content = fs.readFileSync(queuePath, "utf8");
|
|
@@ -764,7 +1162,7 @@ function _readQueueMessagesLocked(queuePath, fs = nodeFs) {
|
|
|
764
1162
|
return [];
|
|
765
1163
|
}
|
|
766
1164
|
}
|
|
767
|
-
function _readQueueEntriesLocked(queuePath, fs =
|
|
1165
|
+
function _readQueueEntriesLocked(queuePath, fs = nodeFs2) {
|
|
768
1166
|
if (!fs.existsSync(queuePath)) return [];
|
|
769
1167
|
try {
|
|
770
1168
|
const content = fs.readFileSync(queuePath, "utf8");
|
|
@@ -831,13 +1229,13 @@ async function withQueueLock(queuePath, fnOrDeps, depsOrFnOrOpts, maybeOpts) {
|
|
|
831
1229
|
fn = depsOrFnOrOpts;
|
|
832
1230
|
}
|
|
833
1231
|
const deps = normalizeQueueDeps(rawDeps);
|
|
834
|
-
const fs = deps.fs ??
|
|
1232
|
+
const fs = deps.fs ?? nodeFs2;
|
|
835
1233
|
const now = deps.now ?? (() => /* @__PURE__ */ new Date());
|
|
836
1234
|
const staleLockMs = opts.staleLockMs ?? QUEUE_LOCK_STALE_MS;
|
|
837
1235
|
const retries = opts.retries ?? QUEUE_LOCK_RETRIES;
|
|
838
1236
|
const retryIntervalMs = opts.retryIntervalMs ?? QUEUE_LOCK_RETRY_INTERVAL_MS;
|
|
839
1237
|
const lockPath = `${queuePath}.lock`;
|
|
840
|
-
const tokenPath =
|
|
1238
|
+
const tokenPath = join2(lockPath, "owner.json");
|
|
841
1239
|
try {
|
|
842
1240
|
fs.mkdirSync(dirname(lockPath), { recursive: true });
|
|
843
1241
|
} catch {
|
|
@@ -930,7 +1328,7 @@ async function withQueueLock(queuePath, fnOrDeps, depsOrFnOrOpts, maybeOpts) {
|
|
|
930
1328
|
}
|
|
931
1329
|
async function appendRunQueue(queuePath, input, depsOrFs = {}) {
|
|
932
1330
|
const deps = normalizeQueueDeps(depsOrFs);
|
|
933
|
-
const fs = deps.fs ??
|
|
1331
|
+
const fs = deps.fs ?? nodeFs2;
|
|
934
1332
|
const now = deps.now ?? (() => /* @__PURE__ */ new Date());
|
|
935
1333
|
const text = typeof input === "string" ? input : input.text;
|
|
936
1334
|
const entryId = typeof input === "object" && typeof input.id === "string" && input.id.trim() ? input.id.trim() : defaultGenerateQueueEntryId();
|
|
@@ -949,7 +1347,7 @@ async function appendRunQueue(queuePath, input, depsOrFs = {}) {
|
|
|
949
1347
|
}
|
|
950
1348
|
async function countQueueMessages(queuePath, depsOrFs = {}) {
|
|
951
1349
|
const deps = normalizeQueueDeps(depsOrFs);
|
|
952
|
-
const fs = deps.fs ??
|
|
1350
|
+
const fs = deps.fs ?? nodeFs2;
|
|
953
1351
|
if (!fs.existsSync(queuePath)) return 0;
|
|
954
1352
|
return await withQueueLock(queuePath, deps, async () => {
|
|
955
1353
|
return _countQueueMessagesLocked(queuePath, fs);
|
|
@@ -957,7 +1355,7 @@ async function countQueueMessages(queuePath, depsOrFs = {}) {
|
|
|
957
1355
|
}
|
|
958
1356
|
async function readQueueMessages(queuePath, depsOrFs = {}) {
|
|
959
1357
|
const deps = normalizeQueueDeps(depsOrFs);
|
|
960
|
-
const fs = deps.fs ??
|
|
1358
|
+
const fs = deps.fs ?? nodeFs2;
|
|
961
1359
|
if (!fs.existsSync(queuePath)) return [];
|
|
962
1360
|
return await withQueueLock(queuePath, deps, async () => {
|
|
963
1361
|
return _readQueueMessagesLocked(queuePath, fs);
|
|
@@ -965,7 +1363,7 @@ async function readQueueMessages(queuePath, depsOrFs = {}) {
|
|
|
965
1363
|
}
|
|
966
1364
|
async function readQueueEntries(queuePath, depsOrFs = {}) {
|
|
967
1365
|
const deps = normalizeQueueDeps(depsOrFs);
|
|
968
|
-
const fs = deps.fs ??
|
|
1366
|
+
const fs = deps.fs ?? nodeFs2;
|
|
969
1367
|
if (!fs.existsSync(queuePath)) return [];
|
|
970
1368
|
return await withQueueLock(queuePath, deps, async () => {
|
|
971
1369
|
return _readQueueEntriesLocked(queuePath, fs);
|
|
@@ -973,7 +1371,7 @@ async function readQueueEntries(queuePath, depsOrFs = {}) {
|
|
|
973
1371
|
}
|
|
974
1372
|
async function popAllQueueEntries(queuePath, depsOrFs = {}) {
|
|
975
1373
|
const deps = normalizeQueueDeps(depsOrFs);
|
|
976
|
-
const fs = deps.fs ??
|
|
1374
|
+
const fs = deps.fs ?? nodeFs2;
|
|
977
1375
|
return await withQueueLock(queuePath, deps, async () => {
|
|
978
1376
|
if (!fs.existsSync(queuePath)) return [];
|
|
979
1377
|
const content = fs.readFileSync(queuePath, "utf8");
|
|
@@ -984,7 +1382,7 @@ async function popAllQueueEntries(queuePath, depsOrFs = {}) {
|
|
|
984
1382
|
}
|
|
985
1383
|
async function popSingleQueueMessage(queuePath, depsOrFs = {}) {
|
|
986
1384
|
const deps = normalizeQueueDeps(depsOrFs);
|
|
987
|
-
const fs = deps.fs ??
|
|
1385
|
+
const fs = deps.fs ?? nodeFs2;
|
|
988
1386
|
return await withQueueLock(queuePath, deps, async () => {
|
|
989
1387
|
if (!fs.existsSync(queuePath)) return null;
|
|
990
1388
|
const content = fs.readFileSync(queuePath, "utf8");
|
|
@@ -1019,7 +1417,7 @@ function defaultGenerateBatchId() {
|
|
|
1019
1417
|
var LOCK_STALE_MS = 5e3;
|
|
1020
1418
|
var TMP_STALE_MS = 6e4;
|
|
1021
1419
|
function withRunRecordLock(runId, deps, fn, opts = {}) {
|
|
1022
|
-
const fs = deps.fs ??
|
|
1420
|
+
const fs = deps.fs ?? nodeFs2;
|
|
1023
1421
|
const now = deps.now ?? (() => /* @__PURE__ */ new Date());
|
|
1024
1422
|
const lockPath = `${resolveRunRecordPath(runId, deps.env, deps.homedir)}.lock`;
|
|
1025
1423
|
let held = false;
|
|
@@ -1051,7 +1449,7 @@ function withRunRecordLock(runId, deps, fn, opts = {}) {
|
|
|
1051
1449
|
}
|
|
1052
1450
|
}
|
|
1053
1451
|
function writeRunRecord(record, deps = {}) {
|
|
1054
|
-
const fs = deps.fs ??
|
|
1452
|
+
const fs = deps.fs ?? nodeFs2;
|
|
1055
1453
|
const path = resolveRunRecordPath(record.runId, deps.env, deps.homedir);
|
|
1056
1454
|
fs.mkdirSync(resolveRunsDir(deps.env, deps.homedir), { recursive: true });
|
|
1057
1455
|
const payload = JSON.stringify(record, null, 2);
|
|
@@ -1073,7 +1471,7 @@ function writeRunRecord(record, deps = {}) {
|
|
|
1073
1471
|
var FILE_EDIT_TOOL_NAMES = /* @__PURE__ */ new Set(["writeFile", "editFile"]);
|
|
1074
1472
|
var DEFAULT_ACTIVITY_WRITE_INTERVAL_MS = 2e3;
|
|
1075
1473
|
function createRunActivityTracker(runId, deps = {}, options = {}) {
|
|
1076
|
-
const fs = deps.fs ??
|
|
1474
|
+
const fs = deps.fs ?? nodeFs2;
|
|
1077
1475
|
const now = deps.now ?? (() => /* @__PURE__ */ new Date());
|
|
1078
1476
|
const minWriteIntervalMs = options.minWriteIntervalMs ?? DEFAULT_ACTIVITY_WRITE_INTERVAL_MS;
|
|
1079
1477
|
const keepAlive = options.keepAlive ?? true;
|
|
@@ -1168,7 +1566,7 @@ function createRunActivityTracker(runId, deps = {}, options = {}) {
|
|
|
1168
1566
|
return { onLoopEvent, getActivity, flush, dispose };
|
|
1169
1567
|
}
|
|
1170
1568
|
function readRunRecord(runId, deps = {}) {
|
|
1171
|
-
const fs = deps.fs ??
|
|
1569
|
+
const fs = deps.fs ?? nodeFs2;
|
|
1172
1570
|
const path = resolveRunRecordPath(runId, deps.env, deps.homedir);
|
|
1173
1571
|
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
1174
1572
|
try {
|
|
@@ -1190,7 +1588,7 @@ function backfillRunRecordParentDialog(runId, parentDialogId, deps = {}) {
|
|
|
1190
1588
|
return updated === true;
|
|
1191
1589
|
}
|
|
1192
1590
|
function listRunRecords(deps = {}) {
|
|
1193
|
-
const fs = deps.fs ??
|
|
1591
|
+
const fs = deps.fs ?? nodeFs2;
|
|
1194
1592
|
const dir = resolveRunsDir(deps.env, deps.homedir);
|
|
1195
1593
|
let entries = [];
|
|
1196
1594
|
try {
|
|
@@ -1261,7 +1659,7 @@ function queryRunRecords(query, deps = {}) {
|
|
|
1261
1659
|
}
|
|
1262
1660
|
var DEFAULT_GC_RETENTION_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
1263
1661
|
async function gcRunRecords(deps = {}, options = {}) {
|
|
1264
|
-
const fs = deps.fs ??
|
|
1662
|
+
const fs = deps.fs ?? nodeFs2;
|
|
1265
1663
|
const now = (deps.now ?? (() => /* @__PURE__ */ new Date()))();
|
|
1266
1664
|
const nowMs = now instanceof Date ? now.getTime() : Date.parse(String(now));
|
|
1267
1665
|
const retentionMs = typeof options.retentionMs === "number" && options.retentionMs >= 0 ? options.retentionMs : DEFAULT_GC_RETENTION_MS;
|
|
@@ -1276,7 +1674,7 @@ async function gcRunRecords(deps = {}, options = {}) {
|
|
|
1276
1674
|
const jsonPath = resolveRunRecordPath(record.runId, deps.env, deps.homedir);
|
|
1277
1675
|
const logPath = resolveRunLogPath(record.runId, deps.env, deps.homedir);
|
|
1278
1676
|
const queuePath = record.queuePath ?? resolveRunQueuePath(record.runId, deps.env, deps.homedir);
|
|
1279
|
-
const msgPath = record.msgFile ??
|
|
1677
|
+
const msgPath = record.msgFile ?? join2(resolveRunsDir(deps.env, deps.homedir), `${record.runId}.msg.md`);
|
|
1280
1678
|
const logOk = tryUnlinkFile(fs, logPath);
|
|
1281
1679
|
const msgOk = tryUnlinkFile(fs, msgPath);
|
|
1282
1680
|
let queueOk = true;
|
|
@@ -1290,9 +1688,9 @@ async function gcRunRecords(deps = {}, options = {}) {
|
|
|
1290
1688
|
}
|
|
1291
1689
|
}
|
|
1292
1690
|
const runsDirForLegacy = resolveRunsDir(deps.env, deps.homedir);
|
|
1293
|
-
tryUnlinkFile(fs,
|
|
1294
|
-
tryUnlinkFile(fs,
|
|
1295
|
-
tryUnlinkFile(fs, record.resultFile ??
|
|
1691
|
+
tryUnlinkFile(fs, join2(runsDirForLegacy, `${record.runId}.report.md`));
|
|
1692
|
+
tryUnlinkFile(fs, join2(runsDirForLegacy, `${record.runId}.report.json`));
|
|
1693
|
+
tryUnlinkFile(fs, record.resultFile ?? join2(runsDirForLegacy, `${record.runId}.result.md`));
|
|
1296
1694
|
if (!logOk || !msgOk || !queueOk) {
|
|
1297
1695
|
failedIds.push(record.runId);
|
|
1298
1696
|
continue;
|
|
@@ -1309,7 +1707,7 @@ async function gcRunRecords(deps = {}, options = {}) {
|
|
|
1309
1707
|
const statSync = fs.statSync;
|
|
1310
1708
|
for (const entry of fs.readdirSync(dir)) {
|
|
1311
1709
|
if (typeof entry !== "string" || !entry.endsWith(".tmp")) continue;
|
|
1312
|
-
const tmpPath =
|
|
1710
|
+
const tmpPath = join2(dir, entry);
|
|
1313
1711
|
try {
|
|
1314
1712
|
const stat = statSync?.(tmpPath);
|
|
1315
1713
|
if (stat && nowMs - stat.mtimeMs <= TMP_STALE_MS) continue;
|
|
@@ -1370,7 +1768,7 @@ function rewriteMsgFileArg(args, messagePath) {
|
|
|
1370
1768
|
}
|
|
1371
1769
|
return result;
|
|
1372
1770
|
}
|
|
1373
|
-
function resolveValidCliEntrypoint(cliEntrypointPath, fs =
|
|
1771
|
+
function resolveValidCliEntrypoint(cliEntrypointPath, fs = nodeFs2) {
|
|
1374
1772
|
if (cliEntrypointPath && fs.existsSync(cliEntrypointPath)) {
|
|
1375
1773
|
return cliEntrypointPath;
|
|
1376
1774
|
}
|
|
@@ -1379,11 +1777,11 @@ function resolveValidCliEntrypoint(cliEntrypointPath, fs = nodeFs) {
|
|
|
1379
1777
|
return fallback;
|
|
1380
1778
|
}
|
|
1381
1779
|
const candidates = [
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1780
|
+
join2(dirname(fallback), "cli", "index.ts"),
|
|
1781
|
+
join2(dirname(fallback), "cli", "index.js"),
|
|
1782
|
+
join2(dirname(fallback), "..", "packages", "cli", "index.ts"),
|
|
1783
|
+
join2(dirname(fallback), "..", "packages", "cli", "index.js"),
|
|
1784
|
+
join2(dirname(fallback), "..", "index.js")
|
|
1387
1785
|
];
|
|
1388
1786
|
for (const cand of candidates) {
|
|
1389
1787
|
if (fs.existsSync(cand)) {
|
|
@@ -1393,7 +1791,7 @@ function resolveValidCliEntrypoint(cliEntrypointPath, fs = nodeFs) {
|
|
|
1393
1791
|
return fallback;
|
|
1394
1792
|
}
|
|
1395
1793
|
function buildAgentRunChildCommand(options, deps = {}) {
|
|
1396
|
-
const fs = deps.fs ??
|
|
1794
|
+
const fs = deps.fs ?? nodeFs2;
|
|
1397
1795
|
const execPath = process.execPath;
|
|
1398
1796
|
const commandParts = options.commandPath ?? [];
|
|
1399
1797
|
let strippedArgs = stripBackgroundFlag(options.rawArgs);
|
|
@@ -1410,13 +1808,6 @@ function buildAgentRunChildCommand(options, deps = {}) {
|
|
|
1410
1808
|
}
|
|
1411
1809
|
return { execPath, childArgs: [entrypoint, ...commandParts, ...strippedArgs] };
|
|
1412
1810
|
}
|
|
1413
|
-
function safeReadHostMemory(reader) {
|
|
1414
|
-
try {
|
|
1415
|
-
return reader();
|
|
1416
|
-
} catch {
|
|
1417
|
-
return null;
|
|
1418
|
-
}
|
|
1419
|
-
}
|
|
1420
1811
|
function emitHostMemoryNotice(mem, output) {
|
|
1421
1812
|
if (!mem || !output) return void 0;
|
|
1422
1813
|
let notice;
|
|
@@ -1436,7 +1827,7 @@ function emitHostMemoryNotice(mem, output) {
|
|
|
1436
1827
|
async function spawnLocalBackgroundRun(input, deps = {}) {
|
|
1437
1828
|
const env = deps.env ?? process.env;
|
|
1438
1829
|
const homedir = deps.homedir ?? nodeHomedir;
|
|
1439
|
-
const fs = deps.fs ??
|
|
1830
|
+
const fs = deps.fs ?? nodeFs2;
|
|
1440
1831
|
const spawn = deps.spawn ?? nodeSpawn;
|
|
1441
1832
|
const generateRunId = deps.generateRunId ?? defaultGenerateRunId;
|
|
1442
1833
|
const now = deps.now ?? (() => /* @__PURE__ */ new Date());
|
|
@@ -1449,7 +1840,7 @@ async function spawnLocalBackgroundRun(input, deps = {}) {
|
|
|
1449
1840
|
fs.mkdirSync(runsDir, { recursive: true });
|
|
1450
1841
|
let messagePath;
|
|
1451
1842
|
if (typeof input.message === "string") {
|
|
1452
|
-
messagePath =
|
|
1843
|
+
messagePath = join2(runsDir, `${runId}.msg.md`);
|
|
1453
1844
|
fs.writeFileSync(messagePath, input.message);
|
|
1454
1845
|
}
|
|
1455
1846
|
const rawDodCommands = [];
|
|
@@ -1516,34 +1907,92 @@ async function spawnLocalBackgroundRun(input, deps = {}) {
|
|
|
1516
1907
|
NOLO_AGENT_RUN_CHILD: "1",
|
|
1517
1908
|
NOLO_AGENT_RUN_ID: runId
|
|
1518
1909
|
};
|
|
1519
|
-
|
|
1910
|
+
let hostMemory = null;
|
|
1911
|
+
let reservation;
|
|
1520
1912
|
const logFd = fs.openSync(logPath, "a");
|
|
1521
|
-
const
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1913
|
+
const reservationFs = {
|
|
1914
|
+
existsSync: (path) => fs.existsSync(path),
|
|
1915
|
+
readFileSync: (path, encoding) => fs.readFileSync(path, encoding),
|
|
1916
|
+
writeFileSync: (path, data, options) => fs.writeFileSync(path, data, options),
|
|
1917
|
+
mkdirSync: (path, options) => fs.mkdirSync(path, options),
|
|
1918
|
+
readdirSync: (path) => fs.readdirSync(path),
|
|
1919
|
+
...fs.renameSync ? { renameSync: (from, to) => fs.renameSync(from, to) } : {},
|
|
1920
|
+
...fs.statSync ? { statSync: (path) => ({ mtimeMs: fs.statSync(path).mtimeMs }) } : {}
|
|
1921
|
+
};
|
|
1922
|
+
let proc;
|
|
1923
|
+
try {
|
|
1924
|
+
reservation = reserveHostMemory({
|
|
1925
|
+
runsDir,
|
|
1530
1926
|
runId,
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1927
|
+
fs: reservationFs,
|
|
1928
|
+
now: deps.now,
|
|
1929
|
+
readHostMemory: () => hostMemory = (deps.readHostMemory ?? readHostMemory)(),
|
|
1930
|
+
getProcessStartTime: deps.getProcessStartTime ?? defaultGetProcessStartTime,
|
|
1931
|
+
withLock: (id, fn, opts) => withRunRecordLock(id, deps, fn, opts)
|
|
1932
|
+
});
|
|
1933
|
+
proc = spawn(execPath, childArgs, {
|
|
1934
|
+
cwd: input.cwd,
|
|
1935
|
+
env: childEnv,
|
|
1936
|
+
detached: true,
|
|
1937
|
+
stdio: deps.shellApprovalHost ? ["ignore", logFd, logFd, "ipc"] : ["ignore", logFd, logFd]
|
|
1938
|
+
});
|
|
1939
|
+
} catch (error) {
|
|
1940
|
+
try {
|
|
1941
|
+
reservation?.release("spawn_sync_failed");
|
|
1942
|
+
} finally {
|
|
1943
|
+
finalizeRunRecord(runId, { status: "failed", exitCode: 1, note: String(error) }, deps);
|
|
1944
|
+
}
|
|
1945
|
+
throw error;
|
|
1946
|
+
} finally {
|
|
1947
|
+
fs.closeSync?.(logFd);
|
|
1535
1948
|
}
|
|
1536
|
-
proc.
|
|
1949
|
+
proc.once?.("error", (error) => {
|
|
1950
|
+
try {
|
|
1951
|
+
reservation.release("child_error");
|
|
1952
|
+
} catch (releaseError) {
|
|
1953
|
+
finalizeRunRecord(runId, {
|
|
1954
|
+
status: "failed",
|
|
1955
|
+
exitCode: 1,
|
|
1956
|
+
note: `${error}; reservation release failed: ${releaseError}`
|
|
1957
|
+
}, deps);
|
|
1958
|
+
return;
|
|
1959
|
+
}
|
|
1960
|
+
finalizeRunRecord(runId, { status: "failed", exitCode: 1, note: String(error) }, deps);
|
|
1961
|
+
});
|
|
1537
1962
|
const hostMemoryNotice = emitHostMemoryNotice(hostMemory, input.output);
|
|
1538
1963
|
if (typeof proc.pid === "number") {
|
|
1539
|
-
|
|
1964
|
+
let processStartedAt;
|
|
1965
|
+
try {
|
|
1966
|
+
processStartedAt = (deps.getProcessStartTime ?? defaultGetProcessStartTime)(proc.pid);
|
|
1967
|
+
if (processStartedAt && !Number.isFinite(processStartedAt.getTime())) processStartedAt = null;
|
|
1968
|
+
} catch {
|
|
1969
|
+
processStartedAt = null;
|
|
1970
|
+
}
|
|
1540
1971
|
withRunRecordLock(runId, deps, () => {
|
|
1541
1972
|
const latest = readRunRecord(runId, deps) ?? record;
|
|
1542
1973
|
latest.pid = proc.pid;
|
|
1543
1974
|
if (processStartedAt) latest.processStartedAt = processStartedAt.toISOString();
|
|
1544
1975
|
writeRunRecord(latest, deps);
|
|
1545
|
-
|
|
1976
|
+
return true;
|
|
1977
|
+
}, { strict: true });
|
|
1978
|
+
try {
|
|
1979
|
+
const persisted = readRunRecord(runId, deps);
|
|
1980
|
+
reservation.associate({ runId, pid: proc.pid, processStartedAt: processStartedAt?.toISOString() });
|
|
1981
|
+
if (persisted?.pid !== proc.pid || !persisted.processStartedAt) throw new Error("Child identity persistence failed");
|
|
1982
|
+
} catch (error) {
|
|
1983
|
+
throw new Error(`[host-memory-reservation] child ${proc.pid}, run ${runId}: association failed: ${error}`);
|
|
1984
|
+
}
|
|
1985
|
+
}
|
|
1986
|
+
if (deps.shellApprovalHost) {
|
|
1987
|
+
attachShellApprovalHost(proc, { ...deps.shellApprovalHost, binding: {
|
|
1988
|
+
...deps.shellApprovalHost.binding,
|
|
1989
|
+
runId,
|
|
1990
|
+
agentKey: input.agentKey,
|
|
1991
|
+
workspaceRoot: nodeFs2.realpathSync(input.cwd ?? process.cwd())
|
|
1992
|
+
} });
|
|
1993
|
+
proc.channel?.unref?.();
|
|
1546
1994
|
}
|
|
1995
|
+
proc.unref();
|
|
1547
1996
|
return {
|
|
1548
1997
|
runId,
|
|
1549
1998
|
pid: proc.pid,
|
|
@@ -1661,8 +2110,8 @@ function transitionRunToTerminal(runId, update, deps = {}, options = {}) {
|
|
|
1661
2110
|
record2.endedAt = now().toISOString();
|
|
1662
2111
|
if (typeof update.lastAssistantText === "string" && update.lastAssistantText) {
|
|
1663
2112
|
try {
|
|
1664
|
-
const fs = deps.fs ??
|
|
1665
|
-
const resultFile =
|
|
2113
|
+
const fs = deps.fs ?? nodeFs2;
|
|
2114
|
+
const resultFile = join2(resolveRunsDir(deps.env, deps.homedir), `${runId}.result.md`);
|
|
1666
2115
|
const startedMs = Date.parse(record2.startedAt);
|
|
1667
2116
|
const endedMs = Date.parse(record2.endedAt);
|
|
1668
2117
|
const content = buildResultFileContent(
|
|
@@ -1829,7 +2278,7 @@ function formatDuration(startedAt, endedAt) {
|
|
|
1829
2278
|
return `${seconds}s`;
|
|
1830
2279
|
}
|
|
1831
2280
|
function readLastLogLines(logPath, count, deps) {
|
|
1832
|
-
const fs = deps.fs ??
|
|
2281
|
+
const fs = deps.fs ?? nodeFs2;
|
|
1833
2282
|
try {
|
|
1834
2283
|
const content = fs.readFileSync(logPath, "utf8");
|
|
1835
2284
|
const lines = content.split("\n");
|
|
@@ -1840,7 +2289,7 @@ function readLastLogLines(logPath, count, deps) {
|
|
|
1840
2289
|
}
|
|
1841
2290
|
}
|
|
1842
2291
|
function readLogContent(logPath, tailCount, deps) {
|
|
1843
|
-
const fs = deps.fs ??
|
|
2292
|
+
const fs = deps.fs ?? nodeFs2;
|
|
1844
2293
|
try {
|
|
1845
2294
|
const content = fs.readFileSync(logPath, "utf8");
|
|
1846
2295
|
if (typeof tailCount === "number" && tailCount > 0) {
|
|
@@ -2127,7 +2576,7 @@ async function runAgentLogsCommand(args, deps) {
|
|
|
2127
2576
|
`);
|
|
2128
2577
|
return 1;
|
|
2129
2578
|
}
|
|
2130
|
-
const fs = deps.fs ??
|
|
2579
|
+
const fs = deps.fs ?? nodeFs2;
|
|
2131
2580
|
if (!fs.existsSync(record.logPath)) {
|
|
2132
2581
|
deps.output.write(`Log not found: ${record.logPath}
|
|
2133
2582
|
`);
|
|
@@ -2230,6 +2679,8 @@ async function runAgentKillCommand(args, deps) {
|
|
|
2230
2679
|
|
|
2231
2680
|
export {
|
|
2232
2681
|
createShellApprovalChild,
|
|
2682
|
+
readHostMemory,
|
|
2683
|
+
assertHostMemoryAdmission,
|
|
2233
2684
|
setRemoteDialogSyncTimeoutForTest,
|
|
2234
2685
|
remoteDialogSyncTimeout,
|
|
2235
2686
|
resolveRuntimeServerUrl,
|