nolo-cli 0.157.0-alpha.1 → 0.157.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/{_executeModel-5WS2PA62.js → _executeModel-ZIQHOREX.js} +55 -55
- package/{accountResolver-7RYB4D2P.js → accountResolver-55EDKEIK.js} +1 -1
- package/{action-ZJYUBXFC.js → action-GPPJC5TI.js} +5 -5
- package/{addContentAction-TVHB4526.js → addContentAction-XJQEJ4XW.js} +8 -8
- package/{addDialogAgentAction-FDPBU6K6.js → addDialogAgentAction-5WZX5CAY.js} +7 -7
- package/{agentDeleteCommand-Q6M4F3U6.js → agentDeleteCommand-LQSOJEXY.js} +75 -76
- package/{agentEmailCommands-FNH6FEHS.js → agentEmailCommands-HTPKXONW.js} +78 -79
- package/{agentListCommands-JUNMFS6E.js → agentListCommands-GOCGOQUB.js} +36 -36
- package/{agentMachineCommands-VNLDEKNT.js → agentMachineCommands-T7DAZG77.js} +35 -32
- package/{agentProviderCommands-X2HQE6HY.js → agentProviderCommands-CXBHNONN.js} +5 -5
- package/{agentPullCommand-FA43RBRU.js → agentPullCommand-GXXEDOSA.js} +4 -4
- package/{agentRecordCommands-47XXRVPI.js → agentRecordCommands-HSW3RJ4H.js} +72 -73
- package/{agentRunCommand-QEXFJUXQ.js → agentRunCommand-TIRJ57DK.js} +75 -75
- package/{agentRunControl-2C5GM3BW.js → agentRunControl-QWFFIAQY.js} +9 -7
- package/{agentRuntimeLocal-O5GDAFTN.js → agentRuntimeLocal-J6I63NZC.js} +21 -19
- package/{agentSlice-E3XS5FTQ.js → agentSlice-JNM3HF3V.js} +8 -8
- package/{appDeleteCommands-C2J5RQ3I.js → appDeleteCommands-KKLAK2UR.js} +31 -29
- package/{appDeployCommands-3YRK7PSU.js → appDeployCommands-PXWK4FAX.js} +31 -29
- package/{appDeployStatusCommands-FPAX7MOK.js → appDeployStatusCommands-3NGWDZTX.js} +31 -29
- package/{appGetCommands-XAC2V766.js → appGetCommands-APWYPEO3.js} +31 -29
- package/{appListCommands-ZJL2W5O7.js → appListCommands-OP7IRALO.js} +31 -29
- package/{authCommand-J3XILHWW.js → authCommand-3Z52KJID.js} +22 -302
- package/{authCommands-77CHYBGF.js → authCommands-QOCKVPX3.js} +5 -5
- package/{builtinSkillRegistry-A6VSPJEJ.js → builtinSkillRegistry-LJV7FHNI.js} +4 -4
- package/{chromeCommands-OBBNJCLS.js → chromeCommands-IY4NVSXZ.js} +17 -10
- package/{chunk-SW4S4XYB.js → chunk-2AB2HA4M.js} +84 -34
- package/{chunk-C5VPAHIU.js → chunk-32ZKZET7.js} +1 -1
- package/{chunk-XPWGOTBQ.js → chunk-3L2WKC3W.js} +1 -8
- package/{chunk-3XZKV7QQ.js → chunk-3L5MSPWX.js} +1 -1
- package/{chunk-QLQ573SU.js → chunk-3M52COK5.js} +120 -0
- package/{chunk-VSOWPETY.js → chunk-4226O6F7.js} +10 -10
- package/{chunk-Z6KQDSZW.js → chunk-43OFVLWJ.js} +6 -6
- package/{chunk-TJ2VPCBE.js → chunk-4JCAW7TO.js} +2 -2
- package/{chunk-6TF64IF2.js → chunk-4L4K6YR7.js} +16 -2
- package/{chunk-PEO24UB5.js → chunk-5E5VB7PS.js} +324 -57
- package/{chunk-ZD2BLOY6.js → chunk-5EDQTOUR.js} +7 -7
- package/{chunk-HKT6QXP3.js → chunk-5IKRTRRR.js} +5 -5
- package/{chunk-P5QACQDK.js → chunk-62TBKWTV.js} +1 -1
- package/{chunk-TRW43TSC.js → chunk-6EQD4AQL.js} +41 -1
- package/{chunk-YLMMECCH.js → chunk-6XIE5ZGI.js} +1 -1
- package/{chunk-OH2M7WEU.js → chunk-6Y3ETA2Y.js} +3 -3
- package/{chunk-7A4LJCDR.js → chunk-B3ZIFOFI.js} +5 -5
- package/{chunk-WZ6OU4NX.js → chunk-BAWAKW7X.js} +2 -2
- package/{chunk-UUPR3TD7.js → chunk-BL4HFN6L.js} +2 -4
- package/{chunk-2GGXHQIW.js → chunk-BLVSFYQ3.js} +4 -2
- package/{chunk-HP6O6LTV.js → chunk-CDLP56BS.js} +9 -9
- package/{chunk-UTIMX333.js → chunk-DJ2BOPJY.js} +1 -1
- package/{chunk-36OMEXXR.js → chunk-DLV3H464.js} +8 -8
- package/{chunk-CE7PIHQD.js → chunk-ESMZW5PE.js} +3 -3
- package/{chunk-PSJWPSKS.js → chunk-EWO77ZGF.js} +9 -43
- package/{chunk-ZWG3C7VI.js → chunk-F3SOW2EU.js} +81 -1
- package/{chunk-FY4SDK2E.js → chunk-FDLDMJTM.js} +1 -1
- package/{chunk-KOB75UNU.js → chunk-FHG46N5C.js} +53 -4
- package/{chunk-7KYVUFDG.js → chunk-G3VYRPTV.js} +5 -267
- package/{chunk-DW7MGLMG.js → chunk-GGUTTH6C.js} +35 -207
- package/{chunk-6MJNEXUU.js → chunk-HGDKT2D6.js} +4 -0
- package/{chunk-B2EGWZJG.js → chunk-I7S63QUU.js} +1 -1
- package/{chunk-ROPSATAB.js → chunk-IF7V3CBU.js} +1 -1
- package/chunk-IKOKYBYJ.js +2685 -0
- package/{chunk-T6CBQ6BN.js → chunk-INFHX73M.js} +1 -1
- package/{chunk-YBNHPX4S.js → chunk-IW64YHXL.js} +155 -31
- package/{chunk-K43ZF2C3.js → chunk-JPPQ3GUO.js} +1 -1
- package/{chunk-TZTWMGE3.js → chunk-JWEXJ573.js} +3 -3
- package/{chunk-RUNKPFM5.js → chunk-K6Y5T4GE.js} +12 -8
- package/{chunk-MVJVF4ED.js → chunk-KESKU5BG.js} +18 -17
- package/{chunk-I2ZMGXXF.js → chunk-KFHREHUQ.js} +1 -1
- package/{chunk-3C2QLERX.js → chunk-KMDDG73K.js} +1 -1
- package/{chunk-5MM7NNO6.js → chunk-KSTEXKL6.js} +78 -39
- package/{chunk-EM3YOZJ4.js → chunk-L2JMH4I5.js} +3 -3
- package/{chunk-MM3662RS.js → chunk-LPO6AMQU.js} +780 -64
- package/{chunk-D4OWHT5Y.js → chunk-MAYW6V3E.js} +2 -2
- package/{chunk-IPAW7XLD.js → chunk-MFVOJ6KF.js} +2 -2
- package/{chunk-FDT27L7S.js → chunk-MLEANT7D.js} +19 -17
- package/{chunk-7GC3EKUG.js → chunk-MMSQTZCJ.js} +3 -3
- package/{chunk-XFKZ3MCV.js → chunk-MR3ITQEV.js} +344 -88
- package/{chunk-CBHGZRCE.js → chunk-MYOH447Z.js} +1 -1
- package/{chunk-6MPAEODB.js → chunk-NHEWV5FD.js} +17 -3
- package/{chunk-ZEHTARJA.js → chunk-NLFTBC6S.js} +321 -150
- package/{chunk-SKPPKHD3.js → chunk-OLUKIUX6.js} +2 -2
- package/{chunk-SUKSWFLP.js → chunk-PIG2IUVZ.js} +1 -1
- package/{chunk-SEJ5OS6F.js → chunk-PXYBUFDB.js} +4 -0
- package/{chunk-2MTE2PEH.js → chunk-QA2C5NKI.js} +5 -739
- package/{chunk-L6EQV5OC.js → chunk-QXZH5UDF.js} +3 -3
- package/{chunk-TNIX77YM.js → chunk-R5GO3YZP.js} +34 -1
- package/{chunk-X5SUT4CN.js → chunk-R7GCGOGZ.js} +6 -6
- package/{chunk-4Z245KOA.js → chunk-SH3XD4E2.js} +8 -4
- package/{chunk-JGRPBIZZ.js → chunk-SQX235S2.js} +1 -1
- package/chunk-TT644UJD.js +188 -0
- package/{chunk-IYBIRCQV.js → chunk-U34KT53V.js} +29 -11
- package/{chunk-ANJTRG52.js → chunk-ULQAD7ZY.js} +18 -43
- package/{chunk-QDA4Y5O6.js → chunk-UVYSBQ2M.js} +21 -17
- package/{chunk-I2RMFLLH.js → chunk-V7PCFWX3.js} +1 -1
- package/{chunk-HXHSTRGH.js → chunk-VYNMQ6HW.js} +0 -27
- package/{chunk-G47IPDOK.js → chunk-W7FGZRLK.js} +1 -1
- package/{chunk-7BSQK2UC.js → chunk-WRCZXJ4N.js} +2 -2
- package/{chunk-KX6LPG7W.js → chunk-WYJTNSVL.js} +9 -9
- package/{chunk-H4KACZMG.js → chunk-XZIW3Q5B.js} +271 -138
- package/chunk-Y5GCAMGP.js +9 -0
- package/{chunk-JZX2XLKC.js → chunk-YCRROBDY.js} +1 -1
- package/{chunk-FPWT3RH2.js → chunk-YN5DT4JS.js} +22 -22
- package/{chunk-SCJD2PNK.js → chunk-YPG7TUQD.js} +3 -3
- package/{chunk-EJU23ST5.js → chunk-ZKVS36H3.js} +5069 -3730
- package/{chunk-YANF2RR4.js → chunk-ZPKUJROL.js} +1 -1
- package/{chunk-TYND6RDQ.js → chunk-ZZDVBZOP.js} +74 -3
- package/{commandRegistry-OUBQIF62.js → commandRegistry-DR2RPWK6.js} +88 -96
- package/{contentThunks-B3FRQGP7.js → contentThunks-CSJDJDEJ.js} +21 -21
- package/{createAgentAutomationAction-T2YFJ6MY.js → createAgentAutomationAction-ISEUEQSY.js} +21 -21
- package/{createDialogAction-2NFGFBXW.js → createDialogAction-HUCFOZY3.js} +25 -25
- package/{createPageAction-LDAVZDN7.js → createPageAction-DT5CYXCL.js} +24 -25
- package/{dbSlice-5QGKMQLO.js → dbSlice-6PVPY44C.js} +7 -7
- package/{deleteFile-EW6QNRRT.js → deleteFile-YM6NDPAR.js} +5 -5
- package/{dialogCommands-ZLBEAQHE.js → dialogCommands-3W6AIUXT.js} +35 -32
- package/{dialogSlice-64S3J6CP.js → dialogSlice-WJEEPG4W.js} +20 -20
- package/{dist-NJC2W7I4.js → dist-Q2JSMAKA.js} +7 -361
- package/{docCreateCommands-XOG5BBFQ.js → docCreateCommands-KQHQNLRX.js} +32 -30
- package/{docDeleteCommands-NCW3BBRX.js → docDeleteCommands-O3DWFX2W.js} +33 -31
- package/{docReadCommands-OT7X44Y3.js → docReadCommands-2U2BUONC.js} +32 -30
- package/{docStore-NIP7OO4I.js → docStore-7JPYT25V.js} +8 -8
- package/{docUpdateCommands-JW7OWW2J.js → docUpdateCommands-MF3C52XI.js} +32 -30
- package/{getPricing-TX35D7PI.js → getPricing-XQ3N3RBP.js} +2 -2
- package/{handleSendMessageAction-TOKA7ED7.js → handleSendMessageAction-BV5FMID4.js} +43 -39
- package/index.js +8 -8
- package/{localRuntimeAdapter-EQM6LFYC.js → localRuntimeAdapter-6FBEJJ4D.js} +70 -69
- package/{localRuntimeAuthority-JDAD7CB6.js → localRuntimeAuthority-BPN5OAQG.js} +2 -2
- package/{localRuntimeDb-CVC62PM5.js → localRuntimeDb-QIVAXMUL.js} +1 -1
- package/{machineCommands-WJ7FZ67T.js → machineCommands-WCAFDI5N.js} +80 -79
- package/{machineWsRunDispatch-BQT4DEA2.js → machineWsRunDispatch-ZF6BBQ3X.js} +70 -71
- package/{memberThunks-KRV5YXOB.js → memberThunks-AZINUM4S.js} +10 -10
- package/package.json +6 -2
- package/{profileConfig-ZGOFORWV.js → profileConfig-KN3N2BLF.js} +7 -1
- package/{providerPresetApply-CRZFMQD5.js → providerPresetApply-PKUOHQJT.js} +5 -5
- package/{providerRegistry-7IS43D3X.js → providerRegistry-HNQLIFN2.js} +5 -5
- package/{read-5KAZHHDM.js → read-7JPIWRBQ.js} +5 -5
- package/{readDocTool-5QO34OSX.js → readDocTool-AAQTB2Y3.js} +1 -1
- package/{readlineWorkspace-JHJSU5DS.js → readlineWorkspace-I5MHMEUE.js} +375 -200
- package/{removeDialogAgentAction-6X63HMI3.js → removeDialogAgentAction-RMVRILLK.js} +9 -9
- package/{runAgentClientLoop-5QIGEALK.js → runAgentClientLoop-TSNSRQEP.js} +59 -54
- package/{runtimeDoctorCommands-56NWKNON.js → runtimeDoctorCommands-MMCP4B4U.js} +12 -12
- package/{secretCommands-ZXZRLR3R.js → secretCommands-DNY56NSU.js} +11 -1
- package/{setDialogExtraReferencesAction-4MAYKKYW.js → setDialogExtraReferencesAction-L3V7JTMG.js} +8 -8
- package/{setPrimaryDialogAgentAction-EGBAMREH.js → setPrimaryDialogAgentAction-X52JQGTJ.js} +9 -9
- package/settingThunks-N6XFWPOU.js +923 -0
- package/{spaceCommands-ZWDPTURU.js → spaceCommands-NDPGHDMD.js} +29 -27
- package/{streamAgentChatTurn-456DWXNY.js → streamAgentChatTurn-RJLDV4WZ.js} +154 -95
- package/{switchDialogAgentAction-YBKTD6DO.js → switchDialogAgentAction-ZJ5CGU7J.js} +7 -7
- package/{tableCommands-U3QDY4L6.js → tableCommands-IVC5S7Y3.js} +11 -11
- package/{toolApiClient-7WSJJJBV.js → toolApiClient-HGNO53TC.js} +2 -2
- package/{tools-IWNXKK4Y.js → tools-X6QNIYFJ.js} +43 -44
- package/{tuiSummaryLlmCaller-KLK6K3Q3.js → tuiSummaryLlmCaller-FESHBDTT.js} +7 -7
- package/{updateCommands-NQXAGFM7.js → updateCommands-FJHGWD3Q.js} +5 -3
- package/{updateDialogTitleAction-SX53FZWO.js → updateDialogTitleAction-6LURCTMG.js} +21 -21
- package/{workloadLifecycle-36DXRZKU.js → workloadLifecycle-6O6PMISX.js} +2 -2
- package/chunk-IA7TRNNR.js +0 -11
- package/chunk-UKZX3NHN.js +0 -48
- package/chunk-VAZHH6FO.js +0 -7012
- package/chunk-W4S6OTSC.js +0 -35
- package/clipboardy-GCO3GT3I.js +0 -11793
- package/settingThunks-FTDRZNSJ.js +0 -1371
- package/{agentGrantCommands-HOJ3JBBQ.js → agentGrantCommands-5DPHYEKP.js} +3 -3
- package/{chromeOpsCommands-3Z34RKSE.js → chromeOpsCommands-GZVVK7HU.js} +4 -4
- package/{chunk-XETG4RIX.js → chunk-AETZ7AD6.js} +4 -4
- package/{chunk-2IXDJNZI.js → chunk-GAGGEA7D.js} +3 -3
- package/{memoryCommands-CUNBFRVS.js → memoryCommands-ZS3VQJAL.js} +3 -3
- package/{offlineMarxistsAgentCommand-2QTHJCHV.js → offlineMarxistsAgentCommand-3GLX4QQY.js} +3 -3
|
@@ -2,13 +2,13 @@ import {
|
|
|
2
2
|
isAgentRunTerminalStatus,
|
|
3
3
|
normalizeRunTitle
|
|
4
4
|
} from "./chunk-PJNFNEJE.js";
|
|
5
|
-
import {
|
|
6
|
-
resolvePlatformAuthToken
|
|
7
|
-
} from "./chunk-SUKSWFLP.js";
|
|
8
5
|
import {
|
|
9
6
|
isCompiledBinary,
|
|
10
7
|
resolveCliEntrypointPath
|
|
11
8
|
} from "./chunk-A7GB5UGH.js";
|
|
9
|
+
import {
|
|
10
|
+
resolvePlatformAuthToken
|
|
11
|
+
} from "./chunk-PIG2IUVZ.js";
|
|
12
12
|
|
|
13
13
|
// packages/cli/client/originShellApprovalTransport.ts
|
|
14
14
|
import { realpathSync } from "node:fs";
|
|
@@ -320,10 +320,635 @@ 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
|
+
// packages/cli/client/hostMemory.ts
|
|
328
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
329
|
+
import { freemem as osFreemem, totalmem as osTotalmem } from "node:os";
|
|
330
|
+
var PROC_MEMINFO_PATH = "/proc/meminfo";
|
|
331
|
+
var PROC_MOUNTS_PATH = "/proc/mounts";
|
|
332
|
+
var DEFAULT_CGROUP2_MOUNT = "/sys/fs/cgroup";
|
|
333
|
+
var SANDBOX_MARKER_PATHS = ["/.dockerenv", "/run/.containerenv"];
|
|
334
|
+
var PROC_SELF_CGROUP_PATH = "/proc/self/cgroup";
|
|
335
|
+
var SANDBOX_CGROUP_PATTERN = /(docker|kubepods|instabox|containerd)/;
|
|
336
|
+
var MEMORY_AVAILABLE_RATIO_THRESHOLD = 0.15;
|
|
337
|
+
var SWAP_USED_RATIO_THRESHOLD = 0.7;
|
|
338
|
+
var HOST_MEMORY_PRESSURE_HINT = "\u26A0 \u5185\u5B58\u504F\u7D27\uFF0C\u5EFA\u8BAE\u91CD\u6D3B\u653E\u6C99\u76D2/\u8FDC\u7AEF\u6216\u51CF\u5C11\u5E76\u53D1";
|
|
339
|
+
var BYTES_PER_MB = 1024 * 1024;
|
|
340
|
+
function toMbFromBytes(bytes) {
|
|
341
|
+
return Math.round(bytes / BYTES_PER_MB);
|
|
342
|
+
}
|
|
343
|
+
function kbToMb(kb) {
|
|
344
|
+
return Math.round(kb / 1024);
|
|
345
|
+
}
|
|
346
|
+
function tryReadText(deps, path) {
|
|
347
|
+
try {
|
|
348
|
+
const reader = deps.readFile ?? ((p) => readFileSync(p, "utf8"));
|
|
349
|
+
const text = reader(path);
|
|
350
|
+
return typeof text === "string" ? text : null;
|
|
351
|
+
} catch {
|
|
352
|
+
return null;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
function tryExists(deps, path) {
|
|
356
|
+
try {
|
|
357
|
+
const checker = deps.exists ?? ((p) => existsSync(p));
|
|
358
|
+
return checker(path) === true;
|
|
359
|
+
} catch {
|
|
360
|
+
return false;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
function readKbValue(text, key) {
|
|
364
|
+
const match = new RegExp(`^${key}:\\s*(\\d+)`, "m").exec(text);
|
|
365
|
+
if (!match) return null;
|
|
366
|
+
const value = Number.parseInt(match[1], 10);
|
|
367
|
+
return Number.isFinite(value) ? value : null;
|
|
368
|
+
}
|
|
369
|
+
function parseProcMeminfo(text) {
|
|
370
|
+
if (typeof text !== "string" || text.length === 0) return null;
|
|
371
|
+
const totalKb = readKbValue(text, "MemTotal");
|
|
372
|
+
const availableKb = readKbValue(text, "MemAvailable");
|
|
373
|
+
if (totalKb === null || availableKb === null) return null;
|
|
374
|
+
if (!(totalKb > 0)) return null;
|
|
375
|
+
const swapTotalKb = readKbValue(text, "SwapTotal") ?? 0;
|
|
376
|
+
const swapFreeKb = readKbValue(text, "SwapFree") ?? 0;
|
|
377
|
+
return {
|
|
378
|
+
source: "proc-meminfo",
|
|
379
|
+
totalMb: kbToMb(totalKb),
|
|
380
|
+
availableMb: kbToMb(Math.max(0, availableKb)),
|
|
381
|
+
swapTotalMb: kbToMb(Math.max(0, swapTotalKb)),
|
|
382
|
+
swapUsedMb: kbToMb(Math.max(0, swapTotalKb - swapFreeKb)),
|
|
383
|
+
hostAvailableMb: null
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
function detectSandbox(deps = {}) {
|
|
387
|
+
const platform = deps.platform ?? process.platform;
|
|
388
|
+
if (platform !== "linux") return false;
|
|
389
|
+
for (const marker of SANDBOX_MARKER_PATHS) {
|
|
390
|
+
if (tryExists(deps, marker)) return true;
|
|
391
|
+
}
|
|
392
|
+
const selfCgroup = tryReadText(deps, PROC_SELF_CGROUP_PATH);
|
|
393
|
+
return selfCgroup !== null && SANDBOX_CGROUP_PATTERN.test(selfCgroup);
|
|
394
|
+
}
|
|
395
|
+
function readOsView(deps) {
|
|
396
|
+
const os = deps.os ?? { totalmem: osTotalmem, freemem: osFreemem };
|
|
397
|
+
try {
|
|
398
|
+
const totalMb = toMbFromBytes(os.totalmem());
|
|
399
|
+
const availableMb = toMbFromBytes(os.freemem());
|
|
400
|
+
if (!(totalMb > 0) || !Number.isFinite(availableMb)) return null;
|
|
401
|
+
return {
|
|
402
|
+
source: "os",
|
|
403
|
+
totalMb,
|
|
404
|
+
availableMb: Math.max(0, availableMb),
|
|
405
|
+
// os 没有可移植的 swap 口径:按契约显示 n/a,而不是编一个数字。
|
|
406
|
+
swapTotalMb: null,
|
|
407
|
+
swapUsedMb: null,
|
|
408
|
+
hostAvailableMb: null
|
|
409
|
+
};
|
|
410
|
+
} catch {
|
|
411
|
+
return null;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
function readHostView(deps) {
|
|
415
|
+
const platform = deps.platform ?? process.platform;
|
|
416
|
+
if (platform === "linux") {
|
|
417
|
+
const text = tryReadText(deps, PROC_MEMINFO_PATH);
|
|
418
|
+
if (text !== null) {
|
|
419
|
+
const parsed = parseProcMeminfo(text);
|
|
420
|
+
if (parsed) return parsed;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
return readOsView(deps);
|
|
424
|
+
}
|
|
425
|
+
function parseCgroup2RelativePath(text) {
|
|
426
|
+
for (const line of text.split("\n")) {
|
|
427
|
+
const trimmed = line.trim();
|
|
428
|
+
if (!trimmed) continue;
|
|
429
|
+
const parts = trimmed.split(":");
|
|
430
|
+
if (parts.length < 3 || parts[0] !== "0" || parts[1] !== "") continue;
|
|
431
|
+
const path = parts.slice(2).join(":").trim();
|
|
432
|
+
if (!path) continue;
|
|
433
|
+
return path.startsWith("/") ? path : `/${path}`;
|
|
434
|
+
}
|
|
435
|
+
return null;
|
|
436
|
+
}
|
|
437
|
+
function parseCgroup2MountPoint(mountsText) {
|
|
438
|
+
for (const line of mountsText.split("\n")) {
|
|
439
|
+
const fields = line.trim().split(/\s+/);
|
|
440
|
+
if (fields.length < 3) continue;
|
|
441
|
+
if (fields[2] !== "cgroup2") continue;
|
|
442
|
+
const mountPoint = fields[1].replace(/\\040/g, " ");
|
|
443
|
+
if (mountPoint.startsWith("/")) return mountPoint;
|
|
444
|
+
}
|
|
445
|
+
return null;
|
|
446
|
+
}
|
|
447
|
+
function cgroupAncestorDirs(mountPoint, relativePath) {
|
|
448
|
+
const base = mountPoint.length > 1 && mountPoint.endsWith("/") ? mountPoint.slice(0, -1) : mountPoint;
|
|
449
|
+
const dirs = [];
|
|
450
|
+
if (relativePath && relativePath !== "/") {
|
|
451
|
+
const rel = relativePath.startsWith("/") ? relativePath : `/${relativePath}`;
|
|
452
|
+
const segments = rel.split("/").filter(Boolean);
|
|
453
|
+
for (let depth = segments.length; depth >= 1; depth -= 1) {
|
|
454
|
+
dirs.push(`${base}/${segments.slice(0, depth).join("/")}`);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
dirs.push(base);
|
|
458
|
+
return dirs;
|
|
459
|
+
}
|
|
460
|
+
function readCgroupMaxMb(deps, dir) {
|
|
461
|
+
const maxText = tryReadText(deps, `${dir}/memory.max`);
|
|
462
|
+
if (maxText === null) return null;
|
|
463
|
+
const maxRaw = maxText.trim();
|
|
464
|
+
if (maxRaw === "" || maxRaw === "max") return null;
|
|
465
|
+
const maxBytes = Number.parseInt(maxRaw, 10);
|
|
466
|
+
if (!Number.isFinite(maxBytes) || maxBytes <= 0) return null;
|
|
467
|
+
const totalMb = toMbFromBytes(maxBytes);
|
|
468
|
+
return totalMb > 0 ? totalMb : null;
|
|
469
|
+
}
|
|
470
|
+
function buildCgroupSnapshot(deps, dir, totalMb, hostView) {
|
|
471
|
+
const currentText = tryReadText(deps, `${dir}/memory.current`);
|
|
472
|
+
let usedMb = null;
|
|
473
|
+
if (currentText !== null) {
|
|
474
|
+
const currentBytes = Number.parseInt(currentText.trim(), 10);
|
|
475
|
+
if (Number.isFinite(currentBytes)) {
|
|
476
|
+
usedMb = Math.min(totalMb, Math.max(0, toMbFromBytes(currentBytes)));
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
return {
|
|
480
|
+
source: "cgroup",
|
|
481
|
+
totalMb,
|
|
482
|
+
availableMb: usedMb === null ? null : Math.max(0, totalMb - usedMb),
|
|
483
|
+
// swap 不是 cgroup 配额的一部分,只能借宿主视角(Linux 上就是 /proc/meminfo)。
|
|
484
|
+
swapTotalMb: hostView?.swapTotalMb ?? null,
|
|
485
|
+
swapUsedMb: hostView?.swapUsedMb ?? null,
|
|
486
|
+
hostAvailableMb: hostView?.availableMb ?? null
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
function readCgroupLimitView(deps, hostView) {
|
|
490
|
+
const selfCgroupText = tryReadText(deps, PROC_SELF_CGROUP_PATH) ?? "";
|
|
491
|
+
const relativePath = parseCgroup2RelativePath(selfCgroupText);
|
|
492
|
+
const mountsText = tryReadText(deps, PROC_MOUNTS_PATH) ?? "";
|
|
493
|
+
const mountPoint = parseCgroup2MountPoint(mountsText) ?? DEFAULT_CGROUP2_MOUNT;
|
|
494
|
+
let limitingDir = null;
|
|
495
|
+
let limitingMb = Number.POSITIVE_INFINITY;
|
|
496
|
+
for (const dir of cgroupAncestorDirs(mountPoint, relativePath)) {
|
|
497
|
+
const totalMb = readCgroupMaxMb(deps, dir);
|
|
498
|
+
if (totalMb === null) continue;
|
|
499
|
+
if (totalMb < limitingMb) {
|
|
500
|
+
limitingMb = totalMb;
|
|
501
|
+
limitingDir = dir;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
if (limitingDir === null) return null;
|
|
505
|
+
return buildCgroupSnapshot(deps, limitingDir, limitingMb, hostView);
|
|
506
|
+
}
|
|
507
|
+
function readHostMemory(deps = {}) {
|
|
508
|
+
const platform = deps.platform ?? process.platform;
|
|
509
|
+
if (platform !== "linux") {
|
|
510
|
+
return readHostView(deps);
|
|
511
|
+
}
|
|
512
|
+
const hostView = readHostView(deps);
|
|
513
|
+
const cgroupView = readCgroupLimitView(deps, hostView);
|
|
514
|
+
if (!cgroupView) return hostView;
|
|
515
|
+
return {
|
|
516
|
+
...cgroupView,
|
|
517
|
+
scope: detectSandbox(deps) ? "sandbox" : "cgroup-limit"
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
function isHostMemoryTight(mem) {
|
|
521
|
+
if (!(mem.totalMb > 0)) return false;
|
|
522
|
+
if (mem.availableMb !== null && mem.availableMb / mem.totalMb < MEMORY_AVAILABLE_RATIO_THRESHOLD) {
|
|
523
|
+
return true;
|
|
524
|
+
}
|
|
525
|
+
if (mem.swapTotalMb !== null && mem.swapUsedMb !== null && mem.swapTotalMb > 0 && mem.swapUsedMb / mem.swapTotalMb > SWAP_USED_RATIO_THRESHOLD) {
|
|
526
|
+
return true;
|
|
527
|
+
}
|
|
528
|
+
return false;
|
|
529
|
+
}
|
|
530
|
+
function formatHostMemoryMb(mb) {
|
|
531
|
+
const value = Math.max(0, Math.round(Number.isFinite(mb) ? mb : 0));
|
|
532
|
+
if (value >= 1024) return `${(value / 1024).toFixed(1)}G`;
|
|
533
|
+
return `${value}M`;
|
|
534
|
+
}
|
|
535
|
+
function formatSwapPart(mem) {
|
|
536
|
+
if (mem.swapTotalMb === null || mem.swapUsedMb === null) return "swap n/a";
|
|
537
|
+
return `swap ${formatHostMemoryMb(mem.swapUsedMb)}/${formatHostMemoryMb(mem.swapTotalMb)}`;
|
|
538
|
+
}
|
|
539
|
+
function formatHostMemoryLine(mem) {
|
|
540
|
+
const usedMb = mem.availableMb === null ? null : Math.max(0, mem.totalMb - mem.availableMb);
|
|
541
|
+
let line;
|
|
542
|
+
if (mem.source === "cgroup") {
|
|
543
|
+
const hostAvailable = mem.hostAvailableMb === null ? "n/a" : formatHostMemoryMb(mem.hostAvailableMb);
|
|
544
|
+
const prefix = (mem.scope ?? "sandbox") === "cgroup-limit" ? "[\u9650\u989D] \u672C\u8FDB\u7A0B\u5185\u5B58" : "[sandbox] \u5BB9\u5668\u5185\u5B58";
|
|
545
|
+
const usage = usedMb === null ? "\u5360\u7528\u672A\u77E5" : formatHostMemoryMb(usedMb);
|
|
546
|
+
line = `${prefix} ${usage}/${formatHostMemoryMb(mem.totalMb)} \xB7 \u5BBF\u4E3B\u53EF\u7528 ${hostAvailable}`;
|
|
547
|
+
} else {
|
|
548
|
+
const available = mem.availableMb === null ? "n/a" : formatHostMemoryMb(mem.availableMb);
|
|
549
|
+
line = `\u672C\u5730\u5185\u5B58 ${formatHostMemoryMb(usedMb ?? 0)}/${formatHostMemoryMb(mem.totalMb)}\uFF08\u53EF\u7528 ${available}\uFF09\xB7 ${formatSwapPart(mem)}`;
|
|
550
|
+
}
|
|
551
|
+
return isHostMemoryTight(mem) ? `${line} \xB7 ${HOST_MEMORY_PRESSURE_HINT}` : line;
|
|
552
|
+
}
|
|
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
|
+
|
|
327
952
|
// packages/cli/client/localRuntimeHelpers.ts
|
|
328
953
|
var REMOTE_DIALOG_SYNC_TIMEOUT_MS = 1e4;
|
|
329
954
|
var remoteDialogSyncTimeoutMsForTest;
|
|
@@ -468,19 +1093,19 @@ function isRunTerminalStatus(status) {
|
|
|
468
1093
|
function resolveNoloHome(env, homedir = nodeHomedir) {
|
|
469
1094
|
const fromEnv = env?.NOLO_HOME;
|
|
470
1095
|
if (typeof fromEnv === "string" && fromEnv.length > 0) return fromEnv;
|
|
471
|
-
return
|
|
1096
|
+
return join2(homedir(), ".nolo");
|
|
472
1097
|
}
|
|
473
1098
|
function resolveRunsDir(env, homedir = nodeHomedir) {
|
|
474
|
-
return
|
|
1099
|
+
return join2(resolveNoloHome(env, homedir), "runs");
|
|
475
1100
|
}
|
|
476
1101
|
function resolveRunRecordPath(runId, env, homedir = nodeHomedir) {
|
|
477
|
-
return
|
|
1102
|
+
return join2(resolveRunsDir(env, homedir), `${runId}.json`);
|
|
478
1103
|
}
|
|
479
1104
|
function resolveRunLogPath(runId, env, homedir = nodeHomedir) {
|
|
480
|
-
return
|
|
1105
|
+
return join2(resolveRunsDir(env, homedir), `${runId}.log`);
|
|
481
1106
|
}
|
|
482
1107
|
function resolveRunQueuePath(runId, env, homedir = nodeHomedir) {
|
|
483
|
-
return
|
|
1108
|
+
return join2(resolveRunsDir(env, homedir), `${runId}.queue.jsonl`);
|
|
484
1109
|
}
|
|
485
1110
|
function defaultGenerateQueueEntryId() {
|
|
486
1111
|
const ts = Date.now().toString(36);
|
|
@@ -519,7 +1144,7 @@ function _serializeQueueEntries(entries) {
|
|
|
519
1144
|
return entries.map((e) => JSON.stringify(e)).join("\n") + "\n";
|
|
520
1145
|
}
|
|
521
1146
|
var serializeQueueEntries = _serializeQueueEntries;
|
|
522
|
-
function _countQueueMessagesLocked(queuePath, fs =
|
|
1147
|
+
function _countQueueMessagesLocked(queuePath, fs = nodeFs2) {
|
|
523
1148
|
if (!fs.existsSync(queuePath)) return 0;
|
|
524
1149
|
try {
|
|
525
1150
|
const content = fs.readFileSync(queuePath, "utf8");
|
|
@@ -528,7 +1153,7 @@ function _countQueueMessagesLocked(queuePath, fs = nodeFs) {
|
|
|
528
1153
|
return 0;
|
|
529
1154
|
}
|
|
530
1155
|
}
|
|
531
|
-
function _readQueueMessagesLocked(queuePath, fs =
|
|
1156
|
+
function _readQueueMessagesLocked(queuePath, fs = nodeFs2) {
|
|
532
1157
|
if (!fs.existsSync(queuePath)) return [];
|
|
533
1158
|
try {
|
|
534
1159
|
const content = fs.readFileSync(queuePath, "utf8");
|
|
@@ -537,7 +1162,7 @@ function _readQueueMessagesLocked(queuePath, fs = nodeFs) {
|
|
|
537
1162
|
return [];
|
|
538
1163
|
}
|
|
539
1164
|
}
|
|
540
|
-
function _readQueueEntriesLocked(queuePath, fs =
|
|
1165
|
+
function _readQueueEntriesLocked(queuePath, fs = nodeFs2) {
|
|
541
1166
|
if (!fs.existsSync(queuePath)) return [];
|
|
542
1167
|
try {
|
|
543
1168
|
const content = fs.readFileSync(queuePath, "utf8");
|
|
@@ -604,13 +1229,13 @@ async function withQueueLock(queuePath, fnOrDeps, depsOrFnOrOpts, maybeOpts) {
|
|
|
604
1229
|
fn = depsOrFnOrOpts;
|
|
605
1230
|
}
|
|
606
1231
|
const deps = normalizeQueueDeps(rawDeps);
|
|
607
|
-
const fs = deps.fs ??
|
|
1232
|
+
const fs = deps.fs ?? nodeFs2;
|
|
608
1233
|
const now = deps.now ?? (() => /* @__PURE__ */ new Date());
|
|
609
1234
|
const staleLockMs = opts.staleLockMs ?? QUEUE_LOCK_STALE_MS;
|
|
610
1235
|
const retries = opts.retries ?? QUEUE_LOCK_RETRIES;
|
|
611
1236
|
const retryIntervalMs = opts.retryIntervalMs ?? QUEUE_LOCK_RETRY_INTERVAL_MS;
|
|
612
1237
|
const lockPath = `${queuePath}.lock`;
|
|
613
|
-
const tokenPath =
|
|
1238
|
+
const tokenPath = join2(lockPath, "owner.json");
|
|
614
1239
|
try {
|
|
615
1240
|
fs.mkdirSync(dirname(lockPath), { recursive: true });
|
|
616
1241
|
} catch {
|
|
@@ -703,7 +1328,7 @@ async function withQueueLock(queuePath, fnOrDeps, depsOrFnOrOpts, maybeOpts) {
|
|
|
703
1328
|
}
|
|
704
1329
|
async function appendRunQueue(queuePath, input, depsOrFs = {}) {
|
|
705
1330
|
const deps = normalizeQueueDeps(depsOrFs);
|
|
706
|
-
const fs = deps.fs ??
|
|
1331
|
+
const fs = deps.fs ?? nodeFs2;
|
|
707
1332
|
const now = deps.now ?? (() => /* @__PURE__ */ new Date());
|
|
708
1333
|
const text = typeof input === "string" ? input : input.text;
|
|
709
1334
|
const entryId = typeof input === "object" && typeof input.id === "string" && input.id.trim() ? input.id.trim() : defaultGenerateQueueEntryId();
|
|
@@ -722,7 +1347,7 @@ async function appendRunQueue(queuePath, input, depsOrFs = {}) {
|
|
|
722
1347
|
}
|
|
723
1348
|
async function countQueueMessages(queuePath, depsOrFs = {}) {
|
|
724
1349
|
const deps = normalizeQueueDeps(depsOrFs);
|
|
725
|
-
const fs = deps.fs ??
|
|
1350
|
+
const fs = deps.fs ?? nodeFs2;
|
|
726
1351
|
if (!fs.existsSync(queuePath)) return 0;
|
|
727
1352
|
return await withQueueLock(queuePath, deps, async () => {
|
|
728
1353
|
return _countQueueMessagesLocked(queuePath, fs);
|
|
@@ -730,7 +1355,7 @@ async function countQueueMessages(queuePath, depsOrFs = {}) {
|
|
|
730
1355
|
}
|
|
731
1356
|
async function readQueueMessages(queuePath, depsOrFs = {}) {
|
|
732
1357
|
const deps = normalizeQueueDeps(depsOrFs);
|
|
733
|
-
const fs = deps.fs ??
|
|
1358
|
+
const fs = deps.fs ?? nodeFs2;
|
|
734
1359
|
if (!fs.existsSync(queuePath)) return [];
|
|
735
1360
|
return await withQueueLock(queuePath, deps, async () => {
|
|
736
1361
|
return _readQueueMessagesLocked(queuePath, fs);
|
|
@@ -738,7 +1363,7 @@ async function readQueueMessages(queuePath, depsOrFs = {}) {
|
|
|
738
1363
|
}
|
|
739
1364
|
async function readQueueEntries(queuePath, depsOrFs = {}) {
|
|
740
1365
|
const deps = normalizeQueueDeps(depsOrFs);
|
|
741
|
-
const fs = deps.fs ??
|
|
1366
|
+
const fs = deps.fs ?? nodeFs2;
|
|
742
1367
|
if (!fs.existsSync(queuePath)) return [];
|
|
743
1368
|
return await withQueueLock(queuePath, deps, async () => {
|
|
744
1369
|
return _readQueueEntriesLocked(queuePath, fs);
|
|
@@ -746,7 +1371,7 @@ async function readQueueEntries(queuePath, depsOrFs = {}) {
|
|
|
746
1371
|
}
|
|
747
1372
|
async function popAllQueueEntries(queuePath, depsOrFs = {}) {
|
|
748
1373
|
const deps = normalizeQueueDeps(depsOrFs);
|
|
749
|
-
const fs = deps.fs ??
|
|
1374
|
+
const fs = deps.fs ?? nodeFs2;
|
|
750
1375
|
return await withQueueLock(queuePath, deps, async () => {
|
|
751
1376
|
if (!fs.existsSync(queuePath)) return [];
|
|
752
1377
|
const content = fs.readFileSync(queuePath, "utf8");
|
|
@@ -757,7 +1382,7 @@ async function popAllQueueEntries(queuePath, depsOrFs = {}) {
|
|
|
757
1382
|
}
|
|
758
1383
|
async function popSingleQueueMessage(queuePath, depsOrFs = {}) {
|
|
759
1384
|
const deps = normalizeQueueDeps(depsOrFs);
|
|
760
|
-
const fs = deps.fs ??
|
|
1385
|
+
const fs = deps.fs ?? nodeFs2;
|
|
761
1386
|
return await withQueueLock(queuePath, deps, async () => {
|
|
762
1387
|
if (!fs.existsSync(queuePath)) return null;
|
|
763
1388
|
const content = fs.readFileSync(queuePath, "utf8");
|
|
@@ -792,15 +1417,19 @@ function defaultGenerateBatchId() {
|
|
|
792
1417
|
var LOCK_STALE_MS = 5e3;
|
|
793
1418
|
var TMP_STALE_MS = 6e4;
|
|
794
1419
|
function withRunRecordLock(runId, deps, fn, opts = {}) {
|
|
795
|
-
const fs = deps.fs ??
|
|
1420
|
+
const fs = deps.fs ?? nodeFs2;
|
|
796
1421
|
const now = deps.now ?? (() => /* @__PURE__ */ new Date());
|
|
797
1422
|
const lockPath = `${resolveRunRecordPath(runId, deps.env, deps.homedir)}.lock`;
|
|
798
1423
|
let held = false;
|
|
799
1424
|
try {
|
|
1425
|
+
fs.mkdirSync(dirname(lockPath), { recursive: true });
|
|
800
1426
|
try {
|
|
801
1427
|
fs.writeFileSync(lockPath, String(process.pid), { flag: "wx" });
|
|
802
1428
|
held = true;
|
|
803
|
-
} catch {
|
|
1429
|
+
} catch (err) {
|
|
1430
|
+
if (err?.code !== "EEXIST") {
|
|
1431
|
+
throw err;
|
|
1432
|
+
}
|
|
804
1433
|
try {
|
|
805
1434
|
const stat = fs.statSync?.(lockPath);
|
|
806
1435
|
const ageMs = stat ? now().getTime() - stat.mtimeMs : Number.POSITIVE_INFINITY;
|
|
@@ -824,7 +1453,7 @@ function withRunRecordLock(runId, deps, fn, opts = {}) {
|
|
|
824
1453
|
}
|
|
825
1454
|
}
|
|
826
1455
|
function writeRunRecord(record, deps = {}) {
|
|
827
|
-
const fs = deps.fs ??
|
|
1456
|
+
const fs = deps.fs ?? nodeFs2;
|
|
828
1457
|
const path = resolveRunRecordPath(record.runId, deps.env, deps.homedir);
|
|
829
1458
|
fs.mkdirSync(resolveRunsDir(deps.env, deps.homedir), { recursive: true });
|
|
830
1459
|
const payload = JSON.stringify(record, null, 2);
|
|
@@ -846,7 +1475,7 @@ function writeRunRecord(record, deps = {}) {
|
|
|
846
1475
|
var FILE_EDIT_TOOL_NAMES = /* @__PURE__ */ new Set(["writeFile", "editFile"]);
|
|
847
1476
|
var DEFAULT_ACTIVITY_WRITE_INTERVAL_MS = 2e3;
|
|
848
1477
|
function createRunActivityTracker(runId, deps = {}, options = {}) {
|
|
849
|
-
const fs = deps.fs ??
|
|
1478
|
+
const fs = deps.fs ?? nodeFs2;
|
|
850
1479
|
const now = deps.now ?? (() => /* @__PURE__ */ new Date());
|
|
851
1480
|
const minWriteIntervalMs = options.minWriteIntervalMs ?? DEFAULT_ACTIVITY_WRITE_INTERVAL_MS;
|
|
852
1481
|
const keepAlive = options.keepAlive ?? true;
|
|
@@ -941,7 +1570,7 @@ function createRunActivityTracker(runId, deps = {}, options = {}) {
|
|
|
941
1570
|
return { onLoopEvent, getActivity, flush, dispose };
|
|
942
1571
|
}
|
|
943
1572
|
function readRunRecord(runId, deps = {}) {
|
|
944
|
-
const fs = deps.fs ??
|
|
1573
|
+
const fs = deps.fs ?? nodeFs2;
|
|
945
1574
|
const path = resolveRunRecordPath(runId, deps.env, deps.homedir);
|
|
946
1575
|
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
947
1576
|
try {
|
|
@@ -963,7 +1592,7 @@ function backfillRunRecordParentDialog(runId, parentDialogId, deps = {}) {
|
|
|
963
1592
|
return updated === true;
|
|
964
1593
|
}
|
|
965
1594
|
function listRunRecords(deps = {}) {
|
|
966
|
-
const fs = deps.fs ??
|
|
1595
|
+
const fs = deps.fs ?? nodeFs2;
|
|
967
1596
|
const dir = resolveRunsDir(deps.env, deps.homedir);
|
|
968
1597
|
let entries = [];
|
|
969
1598
|
try {
|
|
@@ -1034,7 +1663,7 @@ function queryRunRecords(query, deps = {}) {
|
|
|
1034
1663
|
}
|
|
1035
1664
|
var DEFAULT_GC_RETENTION_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
1036
1665
|
async function gcRunRecords(deps = {}, options = {}) {
|
|
1037
|
-
const fs = deps.fs ??
|
|
1666
|
+
const fs = deps.fs ?? nodeFs2;
|
|
1038
1667
|
const now = (deps.now ?? (() => /* @__PURE__ */ new Date()))();
|
|
1039
1668
|
const nowMs = now instanceof Date ? now.getTime() : Date.parse(String(now));
|
|
1040
1669
|
const retentionMs = typeof options.retentionMs === "number" && options.retentionMs >= 0 ? options.retentionMs : DEFAULT_GC_RETENTION_MS;
|
|
@@ -1049,7 +1678,7 @@ async function gcRunRecords(deps = {}, options = {}) {
|
|
|
1049
1678
|
const jsonPath = resolveRunRecordPath(record.runId, deps.env, deps.homedir);
|
|
1050
1679
|
const logPath = resolveRunLogPath(record.runId, deps.env, deps.homedir);
|
|
1051
1680
|
const queuePath = record.queuePath ?? resolveRunQueuePath(record.runId, deps.env, deps.homedir);
|
|
1052
|
-
const msgPath = record.msgFile ??
|
|
1681
|
+
const msgPath = record.msgFile ?? join2(resolveRunsDir(deps.env, deps.homedir), `${record.runId}.msg.md`);
|
|
1053
1682
|
const logOk = tryUnlinkFile(fs, logPath);
|
|
1054
1683
|
const msgOk = tryUnlinkFile(fs, msgPath);
|
|
1055
1684
|
let queueOk = true;
|
|
@@ -1063,9 +1692,9 @@ async function gcRunRecords(deps = {}, options = {}) {
|
|
|
1063
1692
|
}
|
|
1064
1693
|
}
|
|
1065
1694
|
const runsDirForLegacy = resolveRunsDir(deps.env, deps.homedir);
|
|
1066
|
-
tryUnlinkFile(fs,
|
|
1067
|
-
tryUnlinkFile(fs,
|
|
1068
|
-
tryUnlinkFile(fs, record.resultFile ??
|
|
1695
|
+
tryUnlinkFile(fs, join2(runsDirForLegacy, `${record.runId}.report.md`));
|
|
1696
|
+
tryUnlinkFile(fs, join2(runsDirForLegacy, `${record.runId}.report.json`));
|
|
1697
|
+
tryUnlinkFile(fs, record.resultFile ?? join2(runsDirForLegacy, `${record.runId}.result.md`));
|
|
1069
1698
|
if (!logOk || !msgOk || !queueOk) {
|
|
1070
1699
|
failedIds.push(record.runId);
|
|
1071
1700
|
continue;
|
|
@@ -1082,7 +1711,7 @@ async function gcRunRecords(deps = {}, options = {}) {
|
|
|
1082
1711
|
const statSync = fs.statSync;
|
|
1083
1712
|
for (const entry of fs.readdirSync(dir)) {
|
|
1084
1713
|
if (typeof entry !== "string" || !entry.endsWith(".tmp")) continue;
|
|
1085
|
-
const tmpPath =
|
|
1714
|
+
const tmpPath = join2(dir, entry);
|
|
1086
1715
|
try {
|
|
1087
1716
|
const stat = statSync?.(tmpPath);
|
|
1088
1717
|
if (stat && nowMs - stat.mtimeMs <= TMP_STALE_MS) continue;
|
|
@@ -1143,7 +1772,7 @@ function rewriteMsgFileArg(args, messagePath) {
|
|
|
1143
1772
|
}
|
|
1144
1773
|
return result;
|
|
1145
1774
|
}
|
|
1146
|
-
function resolveValidCliEntrypoint(cliEntrypointPath, fs =
|
|
1775
|
+
function resolveValidCliEntrypoint(cliEntrypointPath, fs = nodeFs2) {
|
|
1147
1776
|
if (cliEntrypointPath && fs.existsSync(cliEntrypointPath)) {
|
|
1148
1777
|
return cliEntrypointPath;
|
|
1149
1778
|
}
|
|
@@ -1152,11 +1781,11 @@ function resolveValidCliEntrypoint(cliEntrypointPath, fs = nodeFs) {
|
|
|
1152
1781
|
return fallback;
|
|
1153
1782
|
}
|
|
1154
1783
|
const candidates = [
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1784
|
+
join2(dirname(fallback), "cli", "index.ts"),
|
|
1785
|
+
join2(dirname(fallback), "cli", "index.js"),
|
|
1786
|
+
join2(dirname(fallback), "..", "packages", "cli", "index.ts"),
|
|
1787
|
+
join2(dirname(fallback), "..", "packages", "cli", "index.js"),
|
|
1788
|
+
join2(dirname(fallback), "..", "index.js")
|
|
1160
1789
|
];
|
|
1161
1790
|
for (const cand of candidates) {
|
|
1162
1791
|
if (fs.existsSync(cand)) {
|
|
@@ -1166,7 +1795,7 @@ function resolveValidCliEntrypoint(cliEntrypointPath, fs = nodeFs) {
|
|
|
1166
1795
|
return fallback;
|
|
1167
1796
|
}
|
|
1168
1797
|
function buildAgentRunChildCommand(options, deps = {}) {
|
|
1169
|
-
const fs = deps.fs ??
|
|
1798
|
+
const fs = deps.fs ?? nodeFs2;
|
|
1170
1799
|
const execPath = process.execPath;
|
|
1171
1800
|
const commandParts = options.commandPath ?? [];
|
|
1172
1801
|
let strippedArgs = stripBackgroundFlag(options.rawArgs);
|
|
@@ -1183,10 +1812,27 @@ function buildAgentRunChildCommand(options, deps = {}) {
|
|
|
1183
1812
|
}
|
|
1184
1813
|
return { execPath, childArgs: [entrypoint, ...commandParts, ...strippedArgs] };
|
|
1185
1814
|
}
|
|
1815
|
+
function emitHostMemoryNotice(mem, output) {
|
|
1816
|
+
if (!mem || !output) return void 0;
|
|
1817
|
+
let notice;
|
|
1818
|
+
try {
|
|
1819
|
+
notice = formatHostMemoryLine(mem);
|
|
1820
|
+
} catch {
|
|
1821
|
+
return void 0;
|
|
1822
|
+
}
|
|
1823
|
+
try {
|
|
1824
|
+
output.write(`${notice}
|
|
1825
|
+
`);
|
|
1826
|
+
} catch {
|
|
1827
|
+
return notice;
|
|
1828
|
+
}
|
|
1829
|
+
return notice;
|
|
1830
|
+
}
|
|
1186
1831
|
async function spawnLocalBackgroundRun(input, deps = {}) {
|
|
1187
|
-
const env = deps.env ?? process.env;
|
|
1188
1832
|
const homedir = deps.homedir ?? nodeHomedir;
|
|
1189
|
-
const
|
|
1833
|
+
const env = deps.env ?? (deps.homedir ? { ...process.env, NOLO_HOME: join2(homedir(), ".nolo") } : process.env);
|
|
1834
|
+
const resolvedDeps = { ...deps, env, homedir };
|
|
1835
|
+
const fs = deps.fs ?? nodeFs2;
|
|
1190
1836
|
const spawn = deps.spawn ?? nodeSpawn;
|
|
1191
1837
|
const generateRunId = deps.generateRunId ?? defaultGenerateRunId;
|
|
1192
1838
|
const now = deps.now ?? (() => /* @__PURE__ */ new Date());
|
|
@@ -1199,7 +1845,7 @@ async function spawnLocalBackgroundRun(input, deps = {}) {
|
|
|
1199
1845
|
fs.mkdirSync(runsDir, { recursive: true });
|
|
1200
1846
|
let messagePath;
|
|
1201
1847
|
if (typeof input.message === "string") {
|
|
1202
|
-
messagePath =
|
|
1848
|
+
messagePath = join2(runsDir, `${runId}.msg.md`);
|
|
1203
1849
|
fs.writeFileSync(messagePath, input.message);
|
|
1204
1850
|
}
|
|
1205
1851
|
const rawDodCommands = [];
|
|
@@ -1266,33 +1912,100 @@ async function spawnLocalBackgroundRun(input, deps = {}) {
|
|
|
1266
1912
|
NOLO_AGENT_RUN_CHILD: "1",
|
|
1267
1913
|
NOLO_AGENT_RUN_ID: runId
|
|
1268
1914
|
};
|
|
1915
|
+
let hostMemory = null;
|
|
1916
|
+
let reservation;
|
|
1269
1917
|
const logFd = fs.openSync(logPath, "a");
|
|
1270
|
-
const
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1918
|
+
const reservationFs = {
|
|
1919
|
+
existsSync: (path) => fs.existsSync(path),
|
|
1920
|
+
readFileSync: (path, encoding) => fs.readFileSync(path, encoding),
|
|
1921
|
+
writeFileSync: (path, data, options) => fs.writeFileSync(path, data, options),
|
|
1922
|
+
mkdirSync: (path, options) => fs.mkdirSync(path, options),
|
|
1923
|
+
readdirSync: (path) => fs.readdirSync(path),
|
|
1924
|
+
...fs.renameSync ? { renameSync: (from, to) => fs.renameSync(from, to) } : {},
|
|
1925
|
+
...fs.statSync ? { statSync: (path) => ({ mtimeMs: fs.statSync(path).mtimeMs }) } : {}
|
|
1926
|
+
};
|
|
1927
|
+
let proc;
|
|
1928
|
+
try {
|
|
1929
|
+
reservation = reserveHostMemory({
|
|
1930
|
+
runsDir,
|
|
1931
|
+
runId,
|
|
1932
|
+
fs: reservationFs,
|
|
1933
|
+
now: resolvedDeps.now,
|
|
1934
|
+
readHostMemory: () => hostMemory = (resolvedDeps.readHostMemory ?? readHostMemory)(),
|
|
1935
|
+
getProcessStartTime: resolvedDeps.getProcessStartTime ?? defaultGetProcessStartTime,
|
|
1936
|
+
withLock: (id, fn, opts) => withRunRecordLock(id, resolvedDeps, fn, opts)
|
|
1937
|
+
});
|
|
1938
|
+
proc = spawn(execPath, childArgs, {
|
|
1939
|
+
cwd: input.cwd,
|
|
1940
|
+
env: childEnv,
|
|
1941
|
+
detached: true,
|
|
1942
|
+
stdio: deps.shellApprovalHost ? ["ignore", logFd, logFd, "ipc"] : ["ignore", logFd, logFd]
|
|
1943
|
+
});
|
|
1944
|
+
} catch (error) {
|
|
1945
|
+
try {
|
|
1946
|
+
reservation?.release("spawn_sync_failed");
|
|
1947
|
+
} finally {
|
|
1948
|
+
finalizeRunRecord(runId, { status: "failed", exitCode: 1, note: String(error) }, resolvedDeps);
|
|
1949
|
+
}
|
|
1950
|
+
throw error;
|
|
1951
|
+
} finally {
|
|
1952
|
+
fs.closeSync?.(logFd);
|
|
1953
|
+
}
|
|
1954
|
+
proc.once?.("error", (error) => {
|
|
1955
|
+
try {
|
|
1956
|
+
reservation.release("child_error");
|
|
1957
|
+
} catch (releaseError) {
|
|
1958
|
+
finalizeRunRecord(runId, {
|
|
1959
|
+
status: "failed",
|
|
1960
|
+
exitCode: 1,
|
|
1961
|
+
note: `${error}; reservation release failed: ${releaseError}`
|
|
1962
|
+
}, resolvedDeps);
|
|
1963
|
+
return;
|
|
1964
|
+
}
|
|
1965
|
+
finalizeRunRecord(runId, { status: "failed", exitCode: 1, note: String(error) }, resolvedDeps);
|
|
1275
1966
|
});
|
|
1967
|
+
const hostMemoryNotice = emitHostMemoryNotice(hostMemory, input.output);
|
|
1968
|
+
if (typeof proc.pid === "number") {
|
|
1969
|
+
let processStartedAt;
|
|
1970
|
+
try {
|
|
1971
|
+
processStartedAt = (resolvedDeps.getProcessStartTime ?? defaultGetProcessStartTime)(proc.pid);
|
|
1972
|
+
if (processStartedAt && !Number.isFinite(processStartedAt.getTime())) processStartedAt = null;
|
|
1973
|
+
} catch {
|
|
1974
|
+
processStartedAt = null;
|
|
1975
|
+
}
|
|
1976
|
+
withRunRecordLock(runId, resolvedDeps, () => {
|
|
1977
|
+
const latest = readRunRecord(runId, resolvedDeps) ?? record;
|
|
1978
|
+
latest.pid = proc.pid;
|
|
1979
|
+
if (processStartedAt) latest.processStartedAt = processStartedAt.toISOString();
|
|
1980
|
+
writeRunRecord(latest, resolvedDeps);
|
|
1981
|
+
return true;
|
|
1982
|
+
}, { strict: true });
|
|
1983
|
+
try {
|
|
1984
|
+
const persisted = readRunRecord(runId, resolvedDeps);
|
|
1985
|
+
reservation.associate({ runId, pid: proc.pid, processStartedAt: processStartedAt?.toISOString() });
|
|
1986
|
+
if (persisted?.pid !== proc.pid || !persisted.processStartedAt) throw new Error("Child identity persistence failed");
|
|
1987
|
+
} catch (error) {
|
|
1988
|
+
throw new Error(`[host-memory-reservation] child ${proc.pid}, run ${runId}: association failed: ${error}`);
|
|
1989
|
+
}
|
|
1990
|
+
}
|
|
1276
1991
|
if (deps.shellApprovalHost) {
|
|
1277
1992
|
attachShellApprovalHost(proc, { ...deps.shellApprovalHost, binding: {
|
|
1278
1993
|
...deps.shellApprovalHost.binding,
|
|
1279
1994
|
runId,
|
|
1280
1995
|
agentKey: input.agentKey,
|
|
1281
|
-
workspaceRoot:
|
|
1996
|
+
workspaceRoot: nodeFs2.realpathSync(input.cwd ?? process.cwd())
|
|
1282
1997
|
} });
|
|
1283
1998
|
proc.channel?.unref?.();
|
|
1284
1999
|
}
|
|
1285
2000
|
proc.unref();
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
}
|
|
1295
|
-
return { runId, pid: proc.pid, logPath, queuePath, batchId };
|
|
2001
|
+
return {
|
|
2002
|
+
runId,
|
|
2003
|
+
pid: proc.pid,
|
|
2004
|
+
logPath,
|
|
2005
|
+
queuePath,
|
|
2006
|
+
batchId,
|
|
2007
|
+
...hostMemoryNotice ? { hostMemoryNotice } : {}
|
|
2008
|
+
};
|
|
1296
2009
|
}
|
|
1297
2010
|
function ackRunRecord(runId, deps = {}) {
|
|
1298
2011
|
withRunRecordLock(runId, deps, () => {
|
|
@@ -1402,8 +2115,8 @@ function transitionRunToTerminal(runId, update, deps = {}, options = {}) {
|
|
|
1402
2115
|
record2.endedAt = now().toISOString();
|
|
1403
2116
|
if (typeof update.lastAssistantText === "string" && update.lastAssistantText) {
|
|
1404
2117
|
try {
|
|
1405
|
-
const fs = deps.fs ??
|
|
1406
|
-
const resultFile =
|
|
2118
|
+
const fs = deps.fs ?? nodeFs2;
|
|
2119
|
+
const resultFile = join2(resolveRunsDir(deps.env, deps.homedir), `${runId}.result.md`);
|
|
1407
2120
|
const startedMs = Date.parse(record2.startedAt);
|
|
1408
2121
|
const endedMs = Date.parse(record2.endedAt);
|
|
1409
2122
|
const content = buildResultFileContent(
|
|
@@ -1570,7 +2283,7 @@ function formatDuration(startedAt, endedAt) {
|
|
|
1570
2283
|
return `${seconds}s`;
|
|
1571
2284
|
}
|
|
1572
2285
|
function readLastLogLines(logPath, count, deps) {
|
|
1573
|
-
const fs = deps.fs ??
|
|
2286
|
+
const fs = deps.fs ?? nodeFs2;
|
|
1574
2287
|
try {
|
|
1575
2288
|
const content = fs.readFileSync(logPath, "utf8");
|
|
1576
2289
|
const lines = content.split("\n");
|
|
@@ -1581,7 +2294,7 @@ function readLastLogLines(logPath, count, deps) {
|
|
|
1581
2294
|
}
|
|
1582
2295
|
}
|
|
1583
2296
|
function readLogContent(logPath, tailCount, deps) {
|
|
1584
|
-
const fs = deps.fs ??
|
|
2297
|
+
const fs = deps.fs ?? nodeFs2;
|
|
1585
2298
|
try {
|
|
1586
2299
|
const content = fs.readFileSync(logPath, "utf8");
|
|
1587
2300
|
if (typeof tailCount === "number" && tailCount > 0) {
|
|
@@ -1868,7 +2581,7 @@ async function runAgentLogsCommand(args, deps) {
|
|
|
1868
2581
|
`);
|
|
1869
2582
|
return 1;
|
|
1870
2583
|
}
|
|
1871
|
-
const fs = deps.fs ??
|
|
2584
|
+
const fs = deps.fs ?? nodeFs2;
|
|
1872
2585
|
if (!fs.existsSync(record.logPath)) {
|
|
1873
2586
|
deps.output.write(`Log not found: ${record.logPath}
|
|
1874
2587
|
`);
|
|
@@ -1971,6 +2684,8 @@ async function runAgentKillCommand(args, deps) {
|
|
|
1971
2684
|
|
|
1972
2685
|
export {
|
|
1973
2686
|
createShellApprovalChild,
|
|
2687
|
+
readHostMemory,
|
|
2688
|
+
assertHostMemoryAdmission,
|
|
1974
2689
|
setRemoteDialogSyncTimeoutForTest,
|
|
1975
2690
|
remoteDialogSyncTimeout,
|
|
1976
2691
|
resolveRuntimeServerUrl,
|
|
@@ -2008,6 +2723,7 @@ export {
|
|
|
2008
2723
|
popQueueMessages,
|
|
2009
2724
|
defaultGenerateRunId,
|
|
2010
2725
|
defaultGenerateBatchId,
|
|
2726
|
+
withRunRecordLock,
|
|
2011
2727
|
writeRunRecord,
|
|
2012
2728
|
createRunActivityTracker,
|
|
2013
2729
|
readRunRecord,
|