windows-development-sandbox 1.18.2 → 1.18.4
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/config.example.json +1 -1
- package/dist/app/features/defs.d.ts +26 -0
- package/dist/app/features/defs.js +2 -1
- package/dist/app/features/sandbox/sandbox.def.d.ts +34 -0
- package/dist/app/features/sandbox/sandbox.def.js +52 -10
- package/dist/app/features/sandbox/sandbox.run.d.ts +24 -0
- package/dist/app/features/sandbox/sandbox.run.js +45 -1
- package/dist/app/registry.js +2 -1
- package/dist/bin/agent-sandbox.js +2 -2
- package/dist/bin/{chunk-MQIA7GNB.js → chunk-FRPLQLSY.js} +3188 -2765
- package/dist/bin/{chunk-M4OM632I.js → chunk-MY7NLQQY.js} +1 -1
- package/dist/bin/{chunk-IIHL7W6B.js → chunk-N4GXYTLV.js} +1 -1
- package/dist/bin/claude-host.js +2 -2
- package/dist/bin/claude-sbx.js +2 -2
- package/dist/bin/codex-sbx.js +2 -2
- package/dist/bin/gemini-sbx.js +2 -2
- package/dist/bin/sandbox.js +2 -2
- package/dist/bin/sbx-hide.js +2 -2
- package/dist/bin/sbx-show.js +2 -2
- package/dist/cli/commands/launch/launch.js +4 -1
- package/dist/cli/commands/launch/leftover-offer.d.ts +3 -0
- package/dist/cli/commands/launch/leftover-offer.js +26 -0
- package/dist/cli/commands/repair/checks/sandbox.d.ts +3 -1
- package/dist/cli/commands/repair/checks/sandbox.js +53 -2
- package/dist/core/config/defaults.js +1 -1
- package/dist/core/schema/sandbox.js +1 -1
- package/dist/core/schema/toolchains.js +2 -2
- package/dist/guest/daemon/daemon.js +1 -1
- package/dist/guest/daemon/heartbeat.d.ts +11 -0
- package/dist/guest/daemon/heartbeat.js +59 -0
- package/dist/guest/daemon/idle-shutdown.js +2 -0
- package/dist/guest/entries/daemon.js +2 -0
- package/dist/guest/governor/core.d.ts +19 -1
- package/dist/guest/governor/core.js +23 -1
- package/dist/guest/governor/decide.d.ts +14 -0
- package/dist/guest/governor/decide.js +52 -7
- package/dist/guest/governor/governor.d.ts +4 -1
- package/dist/guest/governor/governor.js +9 -2
- package/dist/guest/toolchains/native/rust-extras.d.ts +7 -0
- package/dist/guest/toolchains/native/rust-extras.js +26 -3
- package/dist/guest/toolchains/plan.js +6 -4
- package/dist/host/channel/channel.d.ts +1 -1
- package/dist/host/channel/channel.js +10 -2
- package/dist/host/health/index.d.ts +1 -0
- package/dist/host/health/index.js +1 -0
- package/dist/host/health/leftovers.d.ts +3 -0
- package/dist/host/health/leftovers.js +40 -0
- package/dist/host/health/stale.d.ts +48 -0
- package/dist/host/health/stale.js +40 -0
- package/dist/host/launch/wsb-file.d.ts +6 -1
- package/dist/host/launch/wsb-file.js +9 -5
- package/dist/host/life/crash-report.d.ts +3 -1
- package/dist/host/life/crash-report.js +9 -3
- package/dist/host/life/index.d.ts +1 -0
- package/dist/host/life/index.js +1 -0
- package/dist/host/life/last-end.d.ts +28 -0
- package/dist/host/life/last-end.js +91 -0
- package/dist/host/life/life-record.d.ts +3 -0
- package/dist/host/life/life-record.js +2 -2
- package/dist/host/life/stop-events.d.ts +3 -1
- package/dist/host/life/stop-events.js +59 -0
- package/dist/host/life/stop-warning.js +3 -0
- package/dist/host/life/watchdog.d.ts +2 -0
- package/dist/host/life/watchdog.js +8 -2
- package/dist/host/sandbox/cleanup.d.ts +37 -0
- package/dist/host/sandbox/cleanup.js +82 -0
- package/dist/host/sandbox/index.d.ts +1 -0
- package/dist/host/sandbox/index.js +1 -0
- package/dist/host/sandbox/platform.js +1 -1
- package/dist/runtime/agent.js +28 -3
- package/dist/runtime/daemon.js +629 -479
- package/dist/runtime/db.js +28 -3
- package/dist/runtime/main.js +59 -11
- package/dist/runtime/netproxy.js +28 -3
- package/dist/runtime/tool.js +28 -3
- package/dist/runtime/view.js +28 -3
- package/dist/selftest/heavy-load.test.js +212 -14
- package/dist/selftest/live-terminal.test.js +4 -0
- package/dist/shared/channel/run-dir.d.ts +4 -1
- package/dist/shared/channel/run-dir.js +7 -1
- package/dist/shared/channel/schema.d.ts +24 -0
- package/dist/shared/channel/schema.js +22 -0
- package/dist/shared/memory/policy.d.ts +10 -0
- package/dist/shared/memory/policy.js +17 -1
- package/dist/shared/system.d.ts +1 -0
- package/dist/shared/system.js +1 -0
- package/dist/sync/watcher.js +10 -5
- package/dist/web/index.html +1 -1
- package/dist/web/{main-CWIF4VQI.js → main-GVZWWP4I.js} +63 -63
- package/native/csharp/HcsHost/AssemblyInfo.cs +3 -3
- package/native/csharp/HostTools/AssemblyInfo.cs +3 -3
- package/native/csharp/HostTools/Program.cs +5 -1
- package/native/csharp/StorageHelper/AssemblyInfo.cs +3 -3
- package/native/scripts/stop-events.ps1 +7 -3
- package/package.json +1 -1
- package/sandbox/csharp/MemGov.cs +58 -0
- package/schemas/config.schema.json +3 -3
- package/schemas/sandbox-config.schema.json +3 -3
package/dist/runtime/daemon.js
CHANGED
|
@@ -4517,7 +4517,7 @@ var require_lodash = __commonJS({
|
|
|
4517
4517
|
function valuesIn(object2) {
|
|
4518
4518
|
return object2 == null ? [] : baseValues(object2, keysIn(object2));
|
|
4519
4519
|
}
|
|
4520
|
-
function
|
|
4520
|
+
function clamp2(number4, lower, upper) {
|
|
4521
4521
|
if (upper === undefined2) {
|
|
4522
4522
|
upper = lower;
|
|
4523
4523
|
lower = undefined2;
|
|
@@ -5199,7 +5199,7 @@ var require_lodash = __commonJS({
|
|
|
5199
5199
|
lodash.camelCase = camelCase;
|
|
5200
5200
|
lodash.capitalize = capitalize;
|
|
5201
5201
|
lodash.ceil = ceil;
|
|
5202
|
-
lodash.clamp =
|
|
5202
|
+
lodash.clamp = clamp2;
|
|
5203
5203
|
lodash.clone = clone2;
|
|
5204
5204
|
lodash.cloneDeep = cloneDeep;
|
|
5205
5205
|
lodash.cloneDeepWith = cloneDeepWith;
|
|
@@ -25485,7 +25485,7 @@ var SandboxSchema = external_exports.strictObject({
|
|
|
25485
25485
|
'(alpha) What runs the sandbox. "windows-sandbox" = Windows Sandbox (the default: desktop, Windows Terminal, GPU, clipboard, audio, printers). "hcs" = a headless Hyper-V isolated Server Core container for agents (no desktop, GPU or clipboard); it needs Windows features and an image on this PC (sandbox engine check hcs lists what is missing), and starting it is not enabled yet in this alpha. sandbox engine shows what each engine can do. Yours only: a project file (.sandbox-config.json) cannot set it.'
|
|
25486
25486
|
),
|
|
25487
25487
|
memoryMb: external_exports.union([external_exports.literal("auto"), external_exports.number().int().min(2048)]).describe(
|
|
25488
|
-
'Sandbox RAM in MB (>= 2048), or "auto": free RAM minus reserveHostMb, capped at maxMemoryMb. Never more than this PC can keep in RAM for it (its RAM minus the larger of reserveHostMb and 25 %,
|
|
25488
|
+
'Sandbox RAM in MB (>= 2048), or "auto": free RAM minus reserveHostMb, capped at maxMemoryMb. Never more than this PC can keep in RAM for it (its RAM minus the larger of reserveHostMb and 25 %, 4 GB more with vGPU): a bigger value is lowered.'
|
|
25489
25489
|
),
|
|
25490
25490
|
maxMemoryMb: external_exports.number().int().positive().describe('Upper limit for memoryMb "auto".'),
|
|
25491
25491
|
maxMemoryPercent: external_exports.number().int().min(10).max(100).describe(`memoryMb "auto" never takes more than this share of this PC's total RAM (percent).`),
|
|
@@ -25647,13 +25647,13 @@ var ToolchainsSchema = external_exports.strictObject({
|
|
|
25647
25647
|
'["sccache", "cargo-nextest", "cargo-deny"]'
|
|
25648
25648
|
),
|
|
25649
25649
|
targetDir: external_exports.string().min(1).describe(
|
|
25650
|
-
`"shared": cargo's default target\\ (shared with builds on this PC, so builds on either side invalidate the other), "separate": <workspace>\\target\\sandbox (kept, no cross-talk), "local": on the sandbox's own disk (fastest, lost every boot), or a guest path.`
|
|
25650
|
+
`"auto" (default): on the sandbox's own disk when C: has 25 GB free at boot, else "separate" (builds then write through the mapped workspace, which can overload this PC's disk). "shared": cargo's default target\\ (shared with builds on this PC, so builds on either side invalidate the other), "separate": <workspace>\\target\\sandbox (kept, no cross-talk), "local": on the sandbox's own disk (fastest, lost every boot), or a guest path.`
|
|
25651
25651
|
),
|
|
25652
25652
|
linker: external_exports.enum(["lld", "gnu"]).describe(
|
|
25653
25653
|
`GNU host: "lld" links with Rust's own lld through MinGW's gcc (much less memory than GNU ld), "gnu" with GNU ld.`
|
|
25654
25654
|
),
|
|
25655
25655
|
linkSlots: external_exports.union([external_exports.literal("auto"), external_exports.number().int().min(1).max(32)]).describe(
|
|
25656
|
-
'GNU host: how many programs may link at the same time in the whole sandbox (other links wait). "auto" = one per
|
|
25656
|
+
'GNU host: how many programs may link at the same time in the whole sandbox (other links wait). "auto" = one per 6 GB of sandbox memory, 1 to 4 (the memory governor slows links down too when memory runs short).'
|
|
25657
25657
|
)
|
|
25658
25658
|
}),
|
|
25659
25659
|
msvc: external_exports.strictObject({
|
|
@@ -27367,6 +27367,8 @@ var GuestRequestSchema = external_exports.discriminatedUnion("kind", [
|
|
|
27367
27367
|
seconds: external_exports.number().int().min(10).max(300),
|
|
27368
27368
|
/** "severe": the PC is nearly out of memory; builds go to the governor's last step at once. */
|
|
27369
27369
|
level: external_exports.enum(["slow", "severe"]).optional(),
|
|
27370
|
+
/** The disk queue the PC reported (shown in the heartbeat). */
|
|
27371
|
+
diskQueue: external_exports.number().min(0).max(1e5).optional(),
|
|
27370
27372
|
createdAt: TimeSchema
|
|
27371
27373
|
})
|
|
27372
27374
|
]);
|
|
@@ -27389,6 +27391,22 @@ var PressureStatusSchema = external_exports.object({
|
|
|
27389
27391
|
lastAction: Text(300),
|
|
27390
27392
|
updatedAt: TimeSchema
|
|
27391
27393
|
});
|
|
27394
|
+
var HeartbeatSchema = external_exports.object({
|
|
27395
|
+
at: TimeSchema,
|
|
27396
|
+
/** Seconds since the sandbox booted. */
|
|
27397
|
+
uptimeS: external_exports.number().int().min(0).max(1e9),
|
|
27398
|
+
ramFreePct: external_exports.number().min(0).max(100),
|
|
27399
|
+
commitPct: external_exports.number().min(0).max(100),
|
|
27400
|
+
/** The governor's dial now, and the step this PC asked for. */
|
|
27401
|
+
step: external_exports.number().int().min(0).max(4),
|
|
27402
|
+
hostStep: external_exports.number().int().min(0).max(4),
|
|
27403
|
+
/** The biggest programs in the agents' job. */
|
|
27404
|
+
topProcesses: external_exports.array(external_exports.object({ name: Text(64), privateMb: external_exports.number().int().min(0).max(1e7) })).max(5),
|
|
27405
|
+
/** The disk queue this PC last reported while asking the sandbox to slow down. */
|
|
27406
|
+
diskQueueReported: external_exports.number().min(0).max(1e5).optional(),
|
|
27407
|
+
/** "ending": the sandbox is shutting down in order (idle shutdown, sandbox stop, Windows shutting down). */
|
|
27408
|
+
phase: external_exports.enum(["ready", "working", "ending"])
|
|
27409
|
+
});
|
|
27392
27410
|
var GuestResponseSchema = external_exports.object({
|
|
27393
27411
|
id: IdSchema,
|
|
27394
27412
|
ok: external_exports.boolean(),
|
|
@@ -27699,6 +27717,8 @@ var RunDir = class {
|
|
|
27699
27717
|
vpnFile;
|
|
27700
27718
|
/** What the memory governor in the sandbox is doing (pressure.json). */
|
|
27701
27719
|
pressureFile;
|
|
27720
|
+
/** The daemon's heartbeat every 10 seconds (heartbeat.json): what the sandbox did last. */
|
|
27721
|
+
heartbeatFile;
|
|
27702
27722
|
/** What the sandbox's programs may really use (budget.json): processors, memory, build jobs, link slots. */
|
|
27703
27723
|
budgetFile;
|
|
27704
27724
|
requestsDir;
|
|
@@ -27722,6 +27742,7 @@ var RunDir = class {
|
|
|
27722
27742
|
this.sandboxFile = path7.join(this.root, "sandbox.json");
|
|
27723
27743
|
this.vpnFile = path7.join(this.root, "vpn.json");
|
|
27724
27744
|
this.pressureFile = path7.join(this.root, "pressure.json");
|
|
27745
|
+
this.heartbeatFile = path7.join(this.root, "heartbeat.json");
|
|
27725
27746
|
this.budgetFile = path7.join(this.root, "budget.json");
|
|
27726
27747
|
this.requestsDir = path7.join(this.root, "requests");
|
|
27727
27748
|
this.responsesDir = path7.join(this.root, "responses");
|
|
@@ -27766,6 +27787,9 @@ var RunDir = class {
|
|
|
27766
27787
|
readPressure() {
|
|
27767
27788
|
return readValid(PressureStatusSchema, this.pressureFile);
|
|
27768
27789
|
}
|
|
27790
|
+
readHeartbeat() {
|
|
27791
|
+
return readValid(HeartbeatSchema, this.heartbeatFile);
|
|
27792
|
+
}
|
|
27769
27793
|
readSession(id) {
|
|
27770
27794
|
return readValid(SessionStatusSchema, this.sessionFile(id));
|
|
27771
27795
|
}
|
|
@@ -27866,6 +27890,7 @@ var SYSTEM = {
|
|
|
27866
27890
|
cmd: () => system32("cmd.exe"),
|
|
27867
27891
|
powershell: () => system32("WindowsPowerShell\\v1.0\\powershell.exe"),
|
|
27868
27892
|
tasklist: () => system32("tasklist.exe"),
|
|
27893
|
+
taskkill: () => system32("taskkill.exe"),
|
|
27869
27894
|
whoami: () => system32("whoami.exe"),
|
|
27870
27895
|
icacls: () => system32("icacls.exe"),
|
|
27871
27896
|
tar: () => system32("tar.exe"),
|
|
@@ -33038,11 +33063,280 @@ function cargoJobs(cpus, memoryGb, sessions2) {
|
|
|
33038
33063
|
const jobs = Math.min(byCpu, byMemory);
|
|
33039
33064
|
return Math.max(1, Math.min(jobs, Math.floor(buildJobs(cpus, memoryGb) / Math.max(1, sessions2))));
|
|
33040
33065
|
}
|
|
33066
|
+
var LOCAL_TARGET_MIN_BYTES = 25 * 1024 ** 3;
|
|
33067
|
+
function chooseTargetDir(targetDir, freeBytes2) {
|
|
33068
|
+
if (targetDir !== "auto") return { targetDir };
|
|
33069
|
+
if (freeBytes2 >= LOCAL_TARGET_MIN_BYTES) return { targetDir: "local" };
|
|
33070
|
+
return {
|
|
33071
|
+
targetDir: "separate",
|
|
33072
|
+
warning: "Rust builds write through the mapped workspace; this can overload your PC's disk (C: has less than 25 GB free for a local build folder)"
|
|
33073
|
+
};
|
|
33074
|
+
}
|
|
33041
33075
|
function maxCompilers(cpus, profile) {
|
|
33042
|
-
return profile === "gentle" ? Math.max(2, Math.floor(cpus / 4)) : null;
|
|
33076
|
+
return profile === "gentle" ? Math.min(4, Math.max(2, Math.floor(cpus / 4))) : null;
|
|
33077
|
+
}
|
|
33078
|
+
|
|
33079
|
+
// src/guest/governor/decide.ts
|
|
33080
|
+
var COMPILERS = /* @__PURE__ */ new Set([
|
|
33081
|
+
"rustc",
|
|
33082
|
+
"clippy-driver",
|
|
33083
|
+
"rustdoc",
|
|
33084
|
+
"cc1",
|
|
33085
|
+
"cc1plus",
|
|
33086
|
+
"cc1obj",
|
|
33087
|
+
"collect2",
|
|
33088
|
+
"gcc",
|
|
33089
|
+
"g++",
|
|
33090
|
+
"c++",
|
|
33091
|
+
"cc",
|
|
33092
|
+
"clang",
|
|
33093
|
+
"clang++",
|
|
33094
|
+
"clang-cl",
|
|
33095
|
+
"cl",
|
|
33096
|
+
"link",
|
|
33097
|
+
"lld",
|
|
33098
|
+
"lld-link",
|
|
33099
|
+
"ld",
|
|
33100
|
+
"ld.lld",
|
|
33101
|
+
"as",
|
|
33102
|
+
"tsc",
|
|
33103
|
+
"javac"
|
|
33104
|
+
]);
|
|
33105
|
+
var BUILD_TOOLS = /* @__PURE__ */ new Set([
|
|
33106
|
+
"dotnet",
|
|
33107
|
+
"msbuild",
|
|
33108
|
+
"vbcscompiler",
|
|
33109
|
+
"ninja",
|
|
33110
|
+
"make",
|
|
33111
|
+
"mingw32-make",
|
|
33112
|
+
"cmake",
|
|
33113
|
+
"cargo",
|
|
33114
|
+
"go",
|
|
33115
|
+
"java",
|
|
33116
|
+
"gradle",
|
|
33117
|
+
"python"
|
|
33118
|
+
]);
|
|
33119
|
+
var NEVER2 = /* @__PURE__ */ new Set([
|
|
33120
|
+
"claude",
|
|
33121
|
+
"codex",
|
|
33122
|
+
"gemini",
|
|
33123
|
+
"opencode",
|
|
33124
|
+
"aider",
|
|
33125
|
+
"cursor-agent",
|
|
33126
|
+
"cmd",
|
|
33127
|
+
"powershell",
|
|
33128
|
+
"pwsh",
|
|
33129
|
+
"bash",
|
|
33130
|
+
"sh",
|
|
33131
|
+
"zsh",
|
|
33132
|
+
"git",
|
|
33133
|
+
"conhost",
|
|
33134
|
+
"openconsole",
|
|
33135
|
+
"windowsterminal",
|
|
33136
|
+
"wt",
|
|
33137
|
+
"ptyhost",
|
|
33138
|
+
"memgov",
|
|
33139
|
+
"sandboxtools",
|
|
33140
|
+
"sbx-db",
|
|
33141
|
+
"explorer",
|
|
33142
|
+
"csrss",
|
|
33143
|
+
"wininit",
|
|
33144
|
+
"winlogon",
|
|
33145
|
+
"services",
|
|
33146
|
+
"svchost",
|
|
33147
|
+
"lsass",
|
|
33148
|
+
"system",
|
|
33149
|
+
"smss",
|
|
33150
|
+
"dwm"
|
|
33151
|
+
]);
|
|
33152
|
+
var key = (name) => name.toLowerCase().replace(/\.exe$/, "");
|
|
33153
|
+
var isNeverTouched = (name) => NEVER2.has(key(name));
|
|
33154
|
+
var canLower = (name) => !isNeverTouched(name) && (COMPILERS.has(key(name)) || BUILD_TOOLS.has(key(name)));
|
|
33155
|
+
var HEAVY = /* @__PURE__ */ new Set([
|
|
33156
|
+
"rustc",
|
|
33157
|
+
"clippy-driver",
|
|
33158
|
+
"rustdoc",
|
|
33159
|
+
"cc1",
|
|
33160
|
+
"cc1plus",
|
|
33161
|
+
"clang",
|
|
33162
|
+
"clang++",
|
|
33163
|
+
"clang-cl",
|
|
33164
|
+
"cl",
|
|
33165
|
+
"link",
|
|
33166
|
+
"lld",
|
|
33167
|
+
"lld-link",
|
|
33168
|
+
"ld",
|
|
33169
|
+
"ld.lld"
|
|
33170
|
+
]);
|
|
33171
|
+
var LINKERS = /* @__PURE__ */ new Set(["link", "lld", "lld-link", "ld", "ld.lld"]);
|
|
33172
|
+
var isLinker = (name) => !isNeverTouched(name) && LINKERS.has(key(name));
|
|
33173
|
+
function linkerSlots(totalMb, cpus) {
|
|
33174
|
+
return Math.max(1, Math.min(4, Math.max(1, Math.floor(cpus)), Math.floor(totalMb / 6144)));
|
|
33175
|
+
}
|
|
33176
|
+
var isHeavyCompiler = (name) => !isNeverTouched(name) && HEAVY.has(key(name));
|
|
33177
|
+
var canPause = (name) => !isNeverTouched(name) && COMPILERS.has(key(name));
|
|
33178
|
+
var INITIAL_STATE = {
|
|
33179
|
+
step: 0,
|
|
33180
|
+
calm: 0,
|
|
33181
|
+
lowered: {},
|
|
33182
|
+
memLow: [],
|
|
33183
|
+
throttled: [],
|
|
33184
|
+
trimmed: [],
|
|
33185
|
+
ioLowered: {},
|
|
33186
|
+
cpuRate: 100,
|
|
33187
|
+
lastCommitMb: 0,
|
|
33188
|
+
lastAt: 0,
|
|
33189
|
+
growth: 0,
|
|
33190
|
+
hostUntil: 0,
|
|
33191
|
+
hostStep: 0
|
|
33192
|
+
};
|
|
33193
|
+
var GROWTH_WEIGHT = 0.3;
|
|
33194
|
+
var HOLD_MS = 3e3;
|
|
33195
|
+
var ROTATE_MS = 2e4;
|
|
33196
|
+
var SOON_WITHIN_PERCENT = 12;
|
|
33197
|
+
function stepFor(ramFree, commit, secondsToLimit, profile) {
|
|
33198
|
+
let step = 0;
|
|
33199
|
+
DIAL_STEPS[profile].forEach((t, i) => {
|
|
33200
|
+
const near = commit > t.commitPercent - SOON_WITHIN_PERCENT;
|
|
33201
|
+
const soon = near && t.secondsToLimit !== void 0 && secondsToLimit < t.secondsToLimit;
|
|
33202
|
+
if (ramFree < t.availablePercent || commit > t.commitPercent || soon) step = i + 1;
|
|
33203
|
+
});
|
|
33204
|
+
return step;
|
|
33205
|
+
}
|
|
33206
|
+
function clearOf(step, ramFree, commit, profile) {
|
|
33207
|
+
if (step === 0) return true;
|
|
33208
|
+
const t = DIAL_STEPS[profile][step - 1];
|
|
33209
|
+
return ramFree > t.availablePercent + DIAL_EXIT_MARGIN.availablePercent && commit < t.commitPercent - DIAL_EXIT_MARGIN.commitPercent;
|
|
33210
|
+
}
|
|
33211
|
+
function decide(prev, sample, nowMs, profile, maxCompilers2 = null) {
|
|
33212
|
+
const ramFree = sample.ramAvailMb / Math.max(1, sample.ramTotalMb) * 100;
|
|
33213
|
+
const usedMb = sample.commitLimitMb - sample.commitAvailMb;
|
|
33214
|
+
const commit = usedMb / Math.max(1, sample.commitLimitMb) * 100;
|
|
33215
|
+
const seconds = (nowMs - prev.lastAt) / 1e3;
|
|
33216
|
+
const instant = prev.lastAt > 0 && seconds > 0 ? (usedMb - prev.lastCommitMb) / seconds : 0;
|
|
33217
|
+
const growth = prev.lastAt > 0 ? prev.growth * (1 - GROWTH_WEIGHT) + instant * GROWTH_WEIGHT : 0;
|
|
33218
|
+
const secondsToLimit = growth > 0 ? sample.commitAvailMb / growth : Number.POSITIVE_INFINITY;
|
|
33219
|
+
const pcLow = nowMs < prev.hostUntil;
|
|
33220
|
+
const wanted2 = Math.max(stepFor(ramFree, commit, secondsToLimit, profile), pcLow ? prev.hostStep : 0);
|
|
33221
|
+
let step = prev.step;
|
|
33222
|
+
let calm = 0;
|
|
33223
|
+
if (wanted2 > prev.step) step = wanted2;
|
|
33224
|
+
else if (prev.step > 0 && wanted2 < prev.step && clearOf(prev.step, ramFree, commit, profile)) {
|
|
33225
|
+
calm = prev.calm + 1;
|
|
33226
|
+
if (calm >= DIAL_CALM_SAMPLES) {
|
|
33227
|
+
step = prev.step - 1;
|
|
33228
|
+
calm = 0;
|
|
33229
|
+
}
|
|
33230
|
+
}
|
|
33231
|
+
if (profile === "gentle" && step < 2 && sample.procs.some((p) => isHeavyCompiler(p.name))) step = 2;
|
|
33232
|
+
const actions = [];
|
|
33233
|
+
const present2 = new Set(sample.procs.map((p) => p.pid));
|
|
33234
|
+
const keep = (pids) => pids.filter((pid) => present2.has(pid));
|
|
33235
|
+
const lowered = {};
|
|
33236
|
+
for (const [pid, to] of Object.entries(prev.lowered)) if (present2.has(Number(pid))) lowered[Number(pid)] = to;
|
|
33237
|
+
const memLow = new Set(keep(prev.memLow));
|
|
33238
|
+
const throttled = new Set(keep(prev.throttled));
|
|
33239
|
+
const trimmed = new Set(keep(prev.trimmed));
|
|
33240
|
+
for (const p of sample.procs) {
|
|
33241
|
+
if (!canLower(p.name)) continue;
|
|
33242
|
+
const rest = profile === "gentle" && canPause(p.name) ? "below" : "normal";
|
|
33243
|
+
const target = step >= 2 ? "idle" : step === 1 ? "below" : rest;
|
|
33244
|
+
const was = lowered[p.pid];
|
|
33245
|
+
const touched = was !== void 0 || target !== "normal";
|
|
33246
|
+
if (touched && was !== target && p.prio !== target)
|
|
33247
|
+
actions.push({ kind: "priority", pid: p.pid, name: p.name, to: target });
|
|
33248
|
+
if (target === "normal") delete lowered[p.pid];
|
|
33249
|
+
else lowered[p.pid] = target;
|
|
33250
|
+
}
|
|
33251
|
+
const ioLowered = {};
|
|
33252
|
+
for (const [pid, to] of Object.entries(prev.ioLowered)) if (present2.has(Number(pid))) ioLowered[Number(pid)] = to;
|
|
33253
|
+
for (const p of sample.procs) {
|
|
33254
|
+
if (!canPause(p.name)) continue;
|
|
33255
|
+
const cpu = lowered[p.pid] ?? "normal";
|
|
33256
|
+
const target = step >= 3 ? "verylow" : cpu !== "normal" ? "low" : "normal";
|
|
33257
|
+
const was = ioLowered[p.pid] ?? "normal";
|
|
33258
|
+
if (was !== target) actions.push({ kind: "io", pid: p.pid, name: p.name, to: target });
|
|
33259
|
+
if (target === "normal") delete ioLowered[p.pid];
|
|
33260
|
+
else ioLowered[p.pid] = target;
|
|
33261
|
+
}
|
|
33262
|
+
const lowMemory = step >= 1;
|
|
33263
|
+
for (const p of sample.procs) {
|
|
33264
|
+
if (!canLower(p.name) || lowMemory === memLow.has(p.pid)) continue;
|
|
33265
|
+
actions.push({ kind: "memory", pid: p.pid, name: p.name, low: lowMemory });
|
|
33266
|
+
if (lowMemory) memLow.add(p.pid);
|
|
33267
|
+
else memLow.delete(p.pid);
|
|
33268
|
+
}
|
|
33269
|
+
if (step >= 2) {
|
|
33270
|
+
for (const p of sample.procs)
|
|
33271
|
+
if (canPause(p.name) && !throttled.has(p.pid)) {
|
|
33272
|
+
actions.push({ kind: "throttle", pid: p.pid, name: p.name });
|
|
33273
|
+
throttled.add(p.pid);
|
|
33274
|
+
}
|
|
33275
|
+
}
|
|
33276
|
+
const cpuRate = DIAL_CPU_RATE[step] ?? 100;
|
|
33277
|
+
if (cpuRate !== prev.cpuRate) actions.push({ kind: "cpu", percent: cpuRate });
|
|
33278
|
+
const heavy = sample.procs.filter((p) => isHeavyCompiler(p.name)).sort((a, b) => a.pid - b.pid);
|
|
33279
|
+
let allowed = compilersAllowed(step, heavy.length);
|
|
33280
|
+
if (maxCompilers2 !== null && step >= 1) allowed = Math.min(allowed, Math.max(1, maxCompilers2));
|
|
33281
|
+
const turn = heavy.length > 0 ? Math.floor(nowMs / ROTATE_MS) % heavy.length : 0;
|
|
33282
|
+
const order = [...heavy.slice(turn), ...heavy.slice(0, turn)];
|
|
33283
|
+
let waiting = order.slice(Math.min(allowed, order.length));
|
|
33284
|
+
if (step >= 2) {
|
|
33285
|
+
const waitingPids = new Set(waiting.map((p) => p.pid));
|
|
33286
|
+
let linking = 0;
|
|
33287
|
+
for (const p of order)
|
|
33288
|
+
if (isLinker(p.name) && !waitingPids.has(p.pid) && ++linking > 1) {
|
|
33289
|
+
waiting = [...waiting, p];
|
|
33290
|
+
waitingPids.add(p.pid);
|
|
33291
|
+
}
|
|
33292
|
+
}
|
|
33293
|
+
if (waiting.length > 0) {
|
|
33294
|
+
const waitingPids = new Set(waiting.map((p) => p.pid));
|
|
33295
|
+
for (const p of waiting) {
|
|
33296
|
+
actions.push({ kind: "hold", pid: p.pid, name: p.name, ms: HOLD_MS });
|
|
33297
|
+
if (step >= 3 && !trimmed.has(p.pid)) {
|
|
33298
|
+
actions.push({ kind: "trim", pid: p.pid, name: p.name });
|
|
33299
|
+
trimmed.add(p.pid);
|
|
33300
|
+
}
|
|
33301
|
+
}
|
|
33302
|
+
for (const p of order) if (!waitingPids.has(p.pid)) trimmed.delete(p.pid);
|
|
33303
|
+
} else trimmed.clear();
|
|
33304
|
+
const level = step >= 3 ? "suspending" : step >= 1 ? "throttled" : "ok";
|
|
33305
|
+
return {
|
|
33306
|
+
state: {
|
|
33307
|
+
step,
|
|
33308
|
+
calm,
|
|
33309
|
+
lowered,
|
|
33310
|
+
memLow: [...memLow],
|
|
33311
|
+
throttled: [...throttled],
|
|
33312
|
+
trimmed: [...trimmed],
|
|
33313
|
+
ioLowered,
|
|
33314
|
+
cpuRate,
|
|
33315
|
+
lastCommitMb: usedMb,
|
|
33316
|
+
lastAt: nowMs,
|
|
33317
|
+
growth,
|
|
33318
|
+
hostUntil: prev.hostUntil,
|
|
33319
|
+
hostStep: prev.hostStep
|
|
33320
|
+
},
|
|
33321
|
+
actions,
|
|
33322
|
+
level,
|
|
33323
|
+
ramFreePercent: Math.round(ramFree),
|
|
33324
|
+
commitPercent: Math.round(commit)
|
|
33325
|
+
};
|
|
33043
33326
|
}
|
|
33044
33327
|
|
|
33045
33328
|
// src/guest/toolchains/native/rust-extras.ts
|
|
33329
|
+
function resolveTargetDir(rust, free = freeBytes((process.env.SystemDrive ?? "C:") + "\\"), note = warn) {
|
|
33330
|
+
if (rust.targetDir !== "auto") return rust;
|
|
33331
|
+
const chosen = chooseTargetDir(rust.targetDir, free);
|
|
33332
|
+
if (chosen.warning) note(chosen.warning);
|
|
33333
|
+
return { ...rust, targetDir: chosen.targetDir };
|
|
33334
|
+
}
|
|
33335
|
+
function targetSummary(rust) {
|
|
33336
|
+
const target = cargoTarget(rust.targetDir);
|
|
33337
|
+
if (target) return "rust builds: " + target + " (CARGO_TARGET_DIR)";
|
|
33338
|
+
return rust.targetDir === "shared" ? "rust builds: target\\ in each project (shared with this PC)" : "rust builds: target\\sandbox in each project (on the mapped workspace)";
|
|
33339
|
+
}
|
|
33046
33340
|
var cargoTarget = (targetDir) => {
|
|
33047
33341
|
if (targetDir === "shared" || targetDir === "separate") return null;
|
|
33048
33342
|
if (targetDir === "local") return path70.join(localDir, "cargo-target");
|
|
@@ -33061,8 +33355,7 @@ function linkGate(env, rust, host) {
|
|
|
33061
33355
|
if (!host.endsWith("-pc-windows-gnu") || !exists(gcc)) return;
|
|
33062
33356
|
const gate = path70.join(mkdirp(tool("shims")), "linkgate.exe");
|
|
33063
33357
|
fs56.copyFileSync(ensureCompiled("linkgate"), gate);
|
|
33064
|
-
const
|
|
33065
|
-
const slots = rust.linkSlots === "auto" ? linkSlotsFor(memoryGb) : rust.linkSlots;
|
|
33358
|
+
const slots = rust.linkSlots === "auto" ? linkerSlots(os10.totalmem() / 1024 ** 2, os10.cpus().length) : rust.linkSlots;
|
|
33066
33359
|
env.vars.AGENT_LINK_SLOTS = String(slots);
|
|
33067
33360
|
env.vars.AGENT_REAL_LINKER = gcc;
|
|
33068
33361
|
env.vars.CARGO_TARGET_X86_64_PC_WINDOWS_GNU_LINKER = gate;
|
|
@@ -33174,11 +33467,13 @@ function plan(config2, versions, o) {
|
|
|
33174
33467
|
after: ["C/C++", "MSVC", "Visual Studio", "Unreal"],
|
|
33175
33468
|
install: async (env) => {
|
|
33176
33469
|
await setupRust(env, { ...config2.rust, host: rustHost(config2) }, o.refresh);
|
|
33177
|
-
|
|
33470
|
+
const rust = resolveTargetDir(config2.rust);
|
|
33471
|
+
Object.assign(env.vars, rustCaches(rust));
|
|
33472
|
+
env.summary.push(targetSummary(rust));
|
|
33178
33473
|
if (!dryRun) {
|
|
33179
33474
|
cargoDefaults(rustHost(config2));
|
|
33180
|
-
cargoShim(env,
|
|
33181
|
-
linkGate(env,
|
|
33475
|
+
cargoShim(env, rust);
|
|
33476
|
+
linkGate(env, rust, rustHost(config2));
|
|
33182
33477
|
}
|
|
33183
33478
|
}
|
|
33184
33479
|
},
|
|
@@ -33663,11 +33958,11 @@ function execCommand(req) {
|
|
|
33663
33958
|
}
|
|
33664
33959
|
|
|
33665
33960
|
// src/guest/daemon/sessions.ts
|
|
33666
|
-
import
|
|
33667
|
-
import
|
|
33961
|
+
import fs64 from "node:fs";
|
|
33962
|
+
import path81 from "node:path";
|
|
33668
33963
|
import crypto5 from "node:crypto";
|
|
33669
|
-
import
|
|
33670
|
-
import { spawn as
|
|
33964
|
+
import os15 from "node:os";
|
|
33965
|
+
import { spawn as spawn9 } from "node:child_process";
|
|
33671
33966
|
|
|
33672
33967
|
// src/shared/pty/protocol.ts
|
|
33673
33968
|
var FrameType = {
|
|
@@ -34249,21 +34544,318 @@ async function flushAccounts(final = false) {
|
|
|
34249
34544
|
}
|
|
34250
34545
|
}
|
|
34251
34546
|
|
|
34252
|
-
// src/guest/daemon/
|
|
34253
|
-
|
|
34254
|
-
|
|
34255
|
-
|
|
34256
|
-
|
|
34257
|
-
|
|
34547
|
+
// src/guest/daemon/heartbeat.ts
|
|
34548
|
+
import os14 from "node:os";
|
|
34549
|
+
|
|
34550
|
+
// src/guest/governor/core.ts
|
|
34551
|
+
var STEP_TEXT = [
|
|
34552
|
+
"builds back to normal",
|
|
34553
|
+
"build programs at lower priority",
|
|
34554
|
+
"builds get less CPU and take turns",
|
|
34555
|
+
"builds take turns, waiting ones go to the paging file",
|
|
34556
|
+
"one compiler at a time"
|
|
34557
|
+
];
|
|
34558
|
+
var GovernorCore = class {
|
|
34559
|
+
constructor(deps) {
|
|
34560
|
+
this.deps = deps;
|
|
34258
34561
|
}
|
|
34259
|
-
|
|
34260
|
-
|
|
34261
|
-
|
|
34562
|
+
deps;
|
|
34563
|
+
state = INITIAL_STATE;
|
|
34564
|
+
lastAction = "";
|
|
34565
|
+
lastWrite = "";
|
|
34566
|
+
lastWriteAt = 0;
|
|
34567
|
+
/** Compilers waiting their turn now, by pid (name), to log a hold once. */
|
|
34568
|
+
held = /* @__PURE__ */ new Map();
|
|
34569
|
+
latest = null;
|
|
34570
|
+
diskQueue = null;
|
|
34571
|
+
hostPressure(seconds, reason, level = "slow", diskQueue) {
|
|
34572
|
+
if (diskQueue !== void 0) this.diskQueue = diskQueue;
|
|
34573
|
+
const nowMs = this.deps.now();
|
|
34574
|
+
const until = nowMs + seconds * 1e3;
|
|
34575
|
+
const active = this.state.hostUntil > nowMs;
|
|
34576
|
+
const step = Math.max(level === "severe" ? 4 : 2, active ? this.state.hostStep : 0);
|
|
34577
|
+
if (!active || step > this.state.hostStep)
|
|
34578
|
+
this.deps.logLine(
|
|
34579
|
+
"governor: this PC is " + (level === "severe" ? "nearly out of memory" : "low") + " (" + reason + "); builds to step " + step
|
|
34580
|
+
);
|
|
34581
|
+
this.state = { ...this.state, hostUntil: Math.max(until, this.state.hostUntil), hostStep: step };
|
|
34582
|
+
}
|
|
34583
|
+
/** The newest numbers, null before the first sample. */
|
|
34584
|
+
get snapshot() {
|
|
34585
|
+
return this.latest ? { ...this.latest, diskQueue: this.diskQueue } : null;
|
|
34586
|
+
}
|
|
34587
|
+
get current() {
|
|
34588
|
+
return this.state;
|
|
34589
|
+
}
|
|
34590
|
+
onSample(sample) {
|
|
34591
|
+
const nowMs = this.deps.now();
|
|
34592
|
+
const before = this.state.step;
|
|
34593
|
+
const d = decide(this.state, sample, nowMs, this.deps.profile, this.deps.maxCompilers ?? null);
|
|
34594
|
+
this.state = d.state;
|
|
34595
|
+
this.latest = {
|
|
34596
|
+
ramFreePercent: d.ramFreePercent,
|
|
34597
|
+
commitPercent: d.commitPercent,
|
|
34598
|
+
step: d.state.step,
|
|
34599
|
+
hostStep: nowMs < d.state.hostUntil ? d.state.hostStep : 0,
|
|
34600
|
+
diskQueue: null,
|
|
34601
|
+
top: [...sample.procs].sort((a, b) => b.mb - a.mb).slice(0, 5).map((p) => ({ name: p.name.slice(0, 64), mb: Math.round(p.mb) }))
|
|
34602
|
+
};
|
|
34603
|
+
const holding = /* @__PURE__ */ new Set();
|
|
34604
|
+
for (const action of d.actions) {
|
|
34605
|
+
if (action.kind === "hold") holding.add(action.pid);
|
|
34606
|
+
this.apply(action);
|
|
34607
|
+
}
|
|
34608
|
+
for (const pid of [...this.held.keys()]) if (!holding.has(pid)) this.held.delete(pid);
|
|
34609
|
+
if (d.state.step !== before)
|
|
34610
|
+
this.deps.logLine(
|
|
34611
|
+
"governor: step " + d.state.step + " (" + d.ramFreePercent + "% RAM free, commit " + d.commitPercent + "% used): " + STEP_TEXT[d.state.step]
|
|
34612
|
+
);
|
|
34613
|
+
const status = {
|
|
34614
|
+
level: d.level,
|
|
34615
|
+
step: d.state.step,
|
|
34616
|
+
profile: this.deps.profile,
|
|
34617
|
+
ramFreePercent: Math.max(0, Math.min(100, d.ramFreePercent)),
|
|
34618
|
+
commitPercent: Math.max(0, Math.min(100, d.commitPercent)),
|
|
34619
|
+
lowered: Object.keys(d.state.lowered).length,
|
|
34620
|
+
paused: [...this.held].slice(0, 8).map(([pid, name]) => ({ name: name.slice(0, 64), pid, seconds: HOLD_MS / 1e3 })),
|
|
34621
|
+
cpuPercent: d.state.cpuRate,
|
|
34622
|
+
pcLow: nowMs < d.state.hostUntil,
|
|
34623
|
+
lastAction: this.lastAction.slice(0, 300),
|
|
34624
|
+
updatedAt: now()
|
|
34625
|
+
};
|
|
34626
|
+
const shape = JSON.stringify({ ...status, updatedAt: "", paused: status.paused.map((p) => p.pid) });
|
|
34627
|
+
if (shape !== this.lastWrite || nowMs - this.lastWriteAt > 6e4) {
|
|
34628
|
+
this.lastWrite = shape;
|
|
34629
|
+
this.lastWriteAt = nowMs;
|
|
34630
|
+
this.deps.saveStatus(status);
|
|
34631
|
+
}
|
|
34632
|
+
}
|
|
34633
|
+
apply(action) {
|
|
34634
|
+
const port = this.deps.port;
|
|
34635
|
+
try {
|
|
34636
|
+
switch (action.kind) {
|
|
34637
|
+
case "hold":
|
|
34638
|
+
port.hold(action.pid, action.ms);
|
|
34639
|
+
if (!this.held.has(action.pid)) {
|
|
34640
|
+
this.held.set(action.pid, action.name);
|
|
34641
|
+
this.note(action.name + " (" + action.pid + ") waits its turn");
|
|
34642
|
+
}
|
|
34643
|
+
return;
|
|
34644
|
+
case "priority":
|
|
34645
|
+
port.setPriority(action.pid, action.to);
|
|
34646
|
+
this.note(action.name + " (" + action.pid + ") priority " + action.to);
|
|
34647
|
+
return;
|
|
34648
|
+
case "io":
|
|
34649
|
+
port.setIoPriority(action.pid, action.to);
|
|
34650
|
+
return;
|
|
34651
|
+
case "memory":
|
|
34652
|
+
port.setMemoryPriority(action.pid, action.low);
|
|
34653
|
+
return;
|
|
34654
|
+
case "throttle":
|
|
34655
|
+
port.throttle(action.pid);
|
|
34656
|
+
return;
|
|
34657
|
+
case "cpu":
|
|
34658
|
+
port.setCpuRate(action.percent);
|
|
34659
|
+
this.note(action.percent >= 100 ? "compilers: no CPU cap" : "compilers: at most " + action.percent + "% CPU");
|
|
34660
|
+
return;
|
|
34661
|
+
case "trim":
|
|
34662
|
+
port.trim(action.pid);
|
|
34663
|
+
return;
|
|
34664
|
+
}
|
|
34665
|
+
} catch (e) {
|
|
34666
|
+
this.deps.warn("governor: " + errMsg(e));
|
|
34667
|
+
}
|
|
34668
|
+
}
|
|
34669
|
+
note(text) {
|
|
34670
|
+
this.lastAction = text;
|
|
34671
|
+
this.deps.logLine("governor: " + text);
|
|
34672
|
+
}
|
|
34673
|
+
};
|
|
34674
|
+
|
|
34675
|
+
// src/guest/governor/governor.ts
|
|
34676
|
+
import fs63 from "node:fs";
|
|
34677
|
+
import os13 from "node:os";
|
|
34678
|
+
import path80 from "node:path";
|
|
34679
|
+
import { spawn as spawn8 } from "node:child_process";
|
|
34680
|
+
import { createInterface } from "node:readline";
|
|
34681
|
+
var SampleSchema = external_exports.object({
|
|
34682
|
+
ramTotalMb: external_exports.number(),
|
|
34683
|
+
ramAvailMb: external_exports.number(),
|
|
34684
|
+
commitLimitMb: external_exports.number(),
|
|
34685
|
+
commitAvailMb: external_exports.number(),
|
|
34686
|
+
procs: external_exports.array(
|
|
34687
|
+
external_exports.object({
|
|
34688
|
+
pid: external_exports.number().int(),
|
|
34689
|
+
name: external_exports.string().max(128),
|
|
34690
|
+
mb: external_exports.number(),
|
|
34691
|
+
prio: external_exports.enum(["idle", "below", "normal"]).catch("normal")
|
|
34692
|
+
})
|
|
34693
|
+
).max(1024)
|
|
34694
|
+
});
|
|
34695
|
+
var MemGovProcess = class {
|
|
34696
|
+
constructor(onSample) {
|
|
34697
|
+
this.onSample = onSample;
|
|
34698
|
+
}
|
|
34699
|
+
onSample;
|
|
34700
|
+
child = null;
|
|
34701
|
+
start() {
|
|
34702
|
+
try {
|
|
34703
|
+
const child = spawn8(ensureCompiled("memgov"), ["serve", "--interval", "2"], {
|
|
34704
|
+
stdio: ["pipe", "pipe", "ignore"],
|
|
34705
|
+
windowsHide: true
|
|
34706
|
+
});
|
|
34707
|
+
this.child = child;
|
|
34708
|
+
createInterface({ input: child.stdout }).on("line", (line) => {
|
|
34709
|
+
try {
|
|
34710
|
+
this.onSample(SampleSchema.parse(JSON.parse(line)));
|
|
34711
|
+
} catch (e) {
|
|
34712
|
+
warn("governor: bad sample: " + errMsg(e));
|
|
34713
|
+
}
|
|
34714
|
+
});
|
|
34715
|
+
child.stdin.on("error", () => void 0);
|
|
34716
|
+
child.once("exit", () => {
|
|
34717
|
+
this.child = null;
|
|
34718
|
+
setTimeout(() => this.start(), 3e4).unref();
|
|
34719
|
+
});
|
|
34720
|
+
} catch (e) {
|
|
34721
|
+
warn("governor: could not start memgov.exe (no memory governor): " + errMsg(e));
|
|
34722
|
+
}
|
|
34723
|
+
}
|
|
34724
|
+
setPriority(pid, to) {
|
|
34725
|
+
this.send("priority " + pid + " " + to);
|
|
34726
|
+
}
|
|
34727
|
+
hold(pid, ms) {
|
|
34728
|
+
this.send("suspend " + pid + " " + ms);
|
|
34729
|
+
}
|
|
34730
|
+
setMemoryPriority(pid, low) {
|
|
34731
|
+
this.send("memprio " + pid + " " + (low ? "low" : "normal"));
|
|
34732
|
+
}
|
|
34733
|
+
setIoPriority(pid, to) {
|
|
34734
|
+
this.send("iopriority " + pid + " " + to);
|
|
34735
|
+
}
|
|
34736
|
+
throttle(pid) {
|
|
34737
|
+
this.send("throttle " + pid);
|
|
34738
|
+
}
|
|
34739
|
+
setCpuRate(percent) {
|
|
34740
|
+
this.send("cpurate " + Math.round(percent));
|
|
34741
|
+
}
|
|
34742
|
+
trim(pid) {
|
|
34743
|
+
this.send("trim " + pid);
|
|
34744
|
+
}
|
|
34745
|
+
send(line) {
|
|
34746
|
+
this.child?.stdin?.write(line + "\n");
|
|
34747
|
+
}
|
|
34748
|
+
};
|
|
34749
|
+
function appendToMemoryLog(line) {
|
|
34750
|
+
try {
|
|
34751
|
+
const logs = path80.join(dataDir, "logs");
|
|
34752
|
+
const newest = fs63.readdirSync(logs).filter((f) => /^memory-\d{8}-\d{6}\.log$/.test(f)).sort().pop();
|
|
34753
|
+
if (!newest) return;
|
|
34754
|
+
const t = /* @__PURE__ */ new Date();
|
|
34755
|
+
const stamp = [t.getHours(), t.getMinutes(), t.getSeconds()].map((n) => String(n).padStart(2, "0")).join(":");
|
|
34756
|
+
fs63.appendFileSync(path80.join(logs, newest), stamp + " | " + line + "\r\n");
|
|
34757
|
+
} catch {
|
|
34758
|
+
}
|
|
34759
|
+
}
|
|
34760
|
+
var Governor = class {
|
|
34761
|
+
core = null;
|
|
34762
|
+
describe() {
|
|
34763
|
+
const profile = session.loadProfile === "gentle" ? "gentle" : "normal";
|
|
34764
|
+
const first = DIAL_STEPS[profile][0];
|
|
34765
|
+
return session.pressureResponse === false ? "off (sandbox.pressureResponse)" : profile + " profile: slows builds down from " + first.availablePercent + "% free RAM or " + first.commitPercent + "% commit, in steps";
|
|
34766
|
+
}
|
|
34767
|
+
start() {
|
|
34768
|
+
if (session.pressureResponse === false) return;
|
|
34769
|
+
const profile = session.loadProfile === "gentle" ? "gentle" : "normal";
|
|
34770
|
+
const proc = new MemGovProcess((s) => this.core?.onSample(s));
|
|
34771
|
+
this.core = new GovernorCore({
|
|
34772
|
+
port: proc,
|
|
34773
|
+
profile,
|
|
34774
|
+
maxCompilers: maxCompilers(os13.cpus().length, profile),
|
|
34775
|
+
now: () => Date.now(),
|
|
34776
|
+
logLine: appendToMemoryLog,
|
|
34777
|
+
warn,
|
|
34778
|
+
saveStatus: (status) => {
|
|
34779
|
+
try {
|
|
34780
|
+
writeJsonAtomic(run.pressureFile, status);
|
|
34781
|
+
} catch (e) {
|
|
34782
|
+
warn("governor: could not write its status: " + errMsg(e));
|
|
34783
|
+
}
|
|
34784
|
+
}
|
|
34785
|
+
});
|
|
34786
|
+
proc.start();
|
|
34787
|
+
}
|
|
34788
|
+
/** The host's watchdog: this PC is low (or nearly out of memory) for a while. */
|
|
34789
|
+
hostPressure(seconds, reason, level = "slow", diskQueue) {
|
|
34790
|
+
this.core?.hostPressure(seconds, reason, level, diskQueue);
|
|
34791
|
+
}
|
|
34792
|
+
/** The newest numbers, for the heartbeat (null when the governor is off or has not sampled yet). */
|
|
34793
|
+
snapshot() {
|
|
34794
|
+
return this.core?.snapshot ?? null;
|
|
34795
|
+
}
|
|
34796
|
+
};
|
|
34797
|
+
var governor = new Governor();
|
|
34798
|
+
|
|
34799
|
+
// src/guest/daemon/heartbeat.ts
|
|
34800
|
+
var BEAT_MS = 1e4;
|
|
34801
|
+
var clamp = (n, low, high) => Math.max(low, Math.min(high, n));
|
|
34802
|
+
function buildHeartbeat(snapshot2, sessionCount, uptimeSeconds, freeBytes2, totalBytes, at, ending = false) {
|
|
34803
|
+
const ramFree = snapshot2?.ramFreePercent ?? Math.round(freeBytes2 / Math.max(1, totalBytes) * 100);
|
|
34804
|
+
return {
|
|
34805
|
+
at,
|
|
34806
|
+
uptimeS: Math.max(0, Math.round(uptimeSeconds)),
|
|
34807
|
+
ramFreePct: clamp(ramFree, 0, 100),
|
|
34808
|
+
commitPct: clamp(snapshot2?.commitPercent ?? 0, 0, 100),
|
|
34809
|
+
step: clamp(snapshot2?.step ?? 0, 0, 4),
|
|
34810
|
+
hostStep: clamp(snapshot2?.hostStep ?? 0, 0, 4),
|
|
34811
|
+
topProcesses: (snapshot2?.top ?? []).slice(0, 5).map((p) => ({ name: p.name.slice(0, 64), privateMb: p.mb })),
|
|
34812
|
+
...snapshot2?.diskQueue !== null && snapshot2?.diskQueue !== void 0 ? { diskQueueReported: snapshot2.diskQueue } : {},
|
|
34813
|
+
phase: ending ? "ending" : sessionCount > 0 ? "working" : "ready"
|
|
34814
|
+
};
|
|
34815
|
+
}
|
|
34816
|
+
var countSessions = () => 0;
|
|
34817
|
+
function writeBeat(ending) {
|
|
34818
|
+
try {
|
|
34819
|
+
writeJsonAtomic(
|
|
34820
|
+
run.heartbeatFile,
|
|
34821
|
+
buildHeartbeat(governor.snapshot(), countSessions(), os14.uptime(), os14.freemem(), os14.totalmem(), now(), ending)
|
|
34822
|
+
);
|
|
34823
|
+
} catch (e) {
|
|
34824
|
+
warn("heartbeat: " + errMsg(e));
|
|
34825
|
+
}
|
|
34826
|
+
}
|
|
34827
|
+
function markEnding() {
|
|
34828
|
+
writeBeat(true);
|
|
34829
|
+
}
|
|
34830
|
+
function startHeartbeat(sessionCount) {
|
|
34831
|
+
countSessions = sessionCount;
|
|
34832
|
+
writeBeat(false);
|
|
34833
|
+
const timer = setInterval(() => writeBeat(false), BEAT_MS);
|
|
34834
|
+
timer.unref();
|
|
34835
|
+
for (const signal of ["SIGHUP", "SIGBREAK", "SIGTERM"])
|
|
34836
|
+
process.on(signal, () => {
|
|
34837
|
+
clearInterval(timer);
|
|
34838
|
+
markEnding();
|
|
34839
|
+
process.exit(0);
|
|
34840
|
+
});
|
|
34841
|
+
}
|
|
34842
|
+
|
|
34843
|
+
// src/guest/daemon/idle-shutdown.ts
|
|
34844
|
+
var GRACE_MS = 15e3;
|
|
34845
|
+
var IdleShutdown = class {
|
|
34846
|
+
/** `busy`: a session runs or is starting. */
|
|
34847
|
+
constructor(busy) {
|
|
34848
|
+
this.busy = busy;
|
|
34849
|
+
}
|
|
34850
|
+
busy;
|
|
34851
|
+
/** After a session ended or failed: shuts a headless sandbox down if it stays idle for a moment. */
|
|
34852
|
+
check() {
|
|
34262
34853
|
if (!session.headless || !isWin) return;
|
|
34263
34854
|
if (!this.idle()) return;
|
|
34264
34855
|
setTimeout(() => {
|
|
34265
34856
|
if (!this.idle()) return;
|
|
34266
34857
|
log("Headless sandbox has no sessions left; shutting down.");
|
|
34858
|
+
safely("heartbeat", markEnding);
|
|
34267
34859
|
safely("claude-mem save", saveClaudeMem);
|
|
34268
34860
|
safely("agent databases save", saveLocalState);
|
|
34269
34861
|
void flushAccounts(true).finally(() => spawnSync6(SYSTEM.shutdown(), ["/s", "/t", "5"], { windowsHide: true }));
|
|
@@ -34278,11 +34870,11 @@ var IdleShutdown = class {
|
|
|
34278
34870
|
var CLOSE_WAIT_MS = 8e3;
|
|
34279
34871
|
var ROOTS_KEPT_MS = 10 * 60 * 1e3;
|
|
34280
34872
|
function terminalTab(id, title, cwd) {
|
|
34281
|
-
const view = [process.execPath,
|
|
34282
|
-
const wt =
|
|
34873
|
+
const view = [process.execPath, path81.join(path81.dirname(process.argv[1]), "view.js"), id];
|
|
34874
|
+
const wt = path81.join(toolsDir, "terminal", "wt.exe");
|
|
34283
34875
|
if (exists(wt))
|
|
34284
|
-
|
|
34285
|
-
else
|
|
34876
|
+
spawn9(wt, ["-w", "0", "nt", "--title", title, "-d", cwd, ...view], { detached: true, stdio: "ignore" }).unref();
|
|
34877
|
+
else spawn9(SYSTEM.cmd(), ["/d", "/c", "start", title, ...view], { detached: true, stdio: "ignore", cwd }).unref();
|
|
34286
34878
|
}
|
|
34287
34879
|
var harnessFor = (req) => {
|
|
34288
34880
|
if (req.harness === session.harnessName && session.harness) return session.harness;
|
|
@@ -34295,8 +34887,8 @@ var harnessFor = (req) => {
|
|
|
34295
34887
|
function memoryEnv(env, sessions2) {
|
|
34296
34888
|
const out = {};
|
|
34297
34889
|
out.PTYHOST_BACKSTOP_MB = String(session.agentBackstop ?? "auto");
|
|
34298
|
-
const cpus = usableCpus(
|
|
34299
|
-
const memoryGb =
|
|
34890
|
+
const cpus = usableCpus(os15.cpus().length, session.cpuLimitPercent ?? null);
|
|
34891
|
+
const memoryGb = os15.totalmem() / 1024 ** 3;
|
|
34300
34892
|
const jobs = cargoJobs(cpus, memoryGb, sessions2);
|
|
34301
34893
|
out.NUMBER_OF_PROCESSORS = String(cpus);
|
|
34302
34894
|
if (!env.CARGO_BUILD_JOBS) out.CARGO_BUILD_JOBS = String(jobs);
|
|
@@ -34335,10 +34927,10 @@ var agentProgram = async (req, env, say) => {
|
|
|
34335
34927
|
};
|
|
34336
34928
|
function shellPaths() {
|
|
34337
34929
|
const candidates = [
|
|
34338
|
-
|
|
34339
|
-
|
|
34930
|
+
path81.join(pwshDir(), "pwsh.exe"),
|
|
34931
|
+
path81.join(process.env.ProgramFiles ?? "C:\\Program Files", "PowerShell", "7", "pwsh.exe")
|
|
34340
34932
|
];
|
|
34341
|
-
return { pwsh: candidates.find((f) =>
|
|
34933
|
+
return { pwsh: candidates.find((f) => fs64.existsSync(f)) ?? null, powershell: SYSTEM.powershell(), cmd: SYSTEM.cmd() };
|
|
34342
34934
|
}
|
|
34343
34935
|
var shellProgram = (shell) => {
|
|
34344
34936
|
const line = commandLine(shell.file, shell.args);
|
|
@@ -34372,7 +34964,7 @@ var launchPtyHost = async (req, program, remote, env, sessions2, onSpawn) => {
|
|
|
34372
34964
|
} catch (e) {
|
|
34373
34965
|
const tail = ptyHostLogTail(req.id);
|
|
34374
34966
|
throw new Error(
|
|
34375
|
-
errMsg(e) + (tail ? ". From its log:\n" + tail : "") + "\nLog: " +
|
|
34967
|
+
errMsg(e) + (tail ? ". From its log:\n" + tail : "") + "\nLog: " + path81.join(dataDir, "logs", "ptyhost-" + req.id + ".log")
|
|
34376
34968
|
);
|
|
34377
34969
|
}
|
|
34378
34970
|
return { child, port, token };
|
|
@@ -34486,7 +35078,7 @@ var SessionManager = class {
|
|
|
34486
35078
|
};
|
|
34487
35079
|
this.starting++;
|
|
34488
35080
|
if (req.id !== session.launchId && process.env.AGENT_NO_TABS !== "1")
|
|
34489
|
-
safely("terminal tab", () => terminalTab(req.id, name + " " +
|
|
35081
|
+
safely("terminal tab", () => terminalTab(req.id, name + " " + path81.basename(req.cwd), req.cwd));
|
|
34490
35082
|
try {
|
|
34491
35083
|
if (problem) throw new Error(problem);
|
|
34492
35084
|
const place = await placeFor(req, say);
|
|
@@ -34654,449 +35246,6 @@ var SessionManager = class {
|
|
|
34654
35246
|
};
|
|
34655
35247
|
var sessions = new SessionManager();
|
|
34656
35248
|
|
|
34657
|
-
// src/guest/governor/decide.ts
|
|
34658
|
-
var COMPILERS = /* @__PURE__ */ new Set([
|
|
34659
|
-
"rustc",
|
|
34660
|
-
"clippy-driver",
|
|
34661
|
-
"rustdoc",
|
|
34662
|
-
"cc1",
|
|
34663
|
-
"cc1plus",
|
|
34664
|
-
"cc1obj",
|
|
34665
|
-
"collect2",
|
|
34666
|
-
"gcc",
|
|
34667
|
-
"g++",
|
|
34668
|
-
"c++",
|
|
34669
|
-
"cc",
|
|
34670
|
-
"clang",
|
|
34671
|
-
"clang++",
|
|
34672
|
-
"clang-cl",
|
|
34673
|
-
"cl",
|
|
34674
|
-
"link",
|
|
34675
|
-
"lld",
|
|
34676
|
-
"lld-link",
|
|
34677
|
-
"ld",
|
|
34678
|
-
"ld.lld",
|
|
34679
|
-
"as",
|
|
34680
|
-
"tsc",
|
|
34681
|
-
"javac"
|
|
34682
|
-
]);
|
|
34683
|
-
var BUILD_TOOLS = /* @__PURE__ */ new Set([
|
|
34684
|
-
"dotnet",
|
|
34685
|
-
"msbuild",
|
|
34686
|
-
"vbcscompiler",
|
|
34687
|
-
"ninja",
|
|
34688
|
-
"make",
|
|
34689
|
-
"mingw32-make",
|
|
34690
|
-
"cmake",
|
|
34691
|
-
"cargo",
|
|
34692
|
-
"go",
|
|
34693
|
-
"java",
|
|
34694
|
-
"gradle",
|
|
34695
|
-
"python"
|
|
34696
|
-
]);
|
|
34697
|
-
var NEVER2 = /* @__PURE__ */ new Set([
|
|
34698
|
-
"claude",
|
|
34699
|
-
"codex",
|
|
34700
|
-
"gemini",
|
|
34701
|
-
"opencode",
|
|
34702
|
-
"aider",
|
|
34703
|
-
"cursor-agent",
|
|
34704
|
-
"cmd",
|
|
34705
|
-
"powershell",
|
|
34706
|
-
"pwsh",
|
|
34707
|
-
"bash",
|
|
34708
|
-
"sh",
|
|
34709
|
-
"zsh",
|
|
34710
|
-
"git",
|
|
34711
|
-
"conhost",
|
|
34712
|
-
"openconsole",
|
|
34713
|
-
"windowsterminal",
|
|
34714
|
-
"wt",
|
|
34715
|
-
"ptyhost",
|
|
34716
|
-
"memgov",
|
|
34717
|
-
"sandboxtools",
|
|
34718
|
-
"sbx-db",
|
|
34719
|
-
"explorer",
|
|
34720
|
-
"csrss",
|
|
34721
|
-
"wininit",
|
|
34722
|
-
"winlogon",
|
|
34723
|
-
"services",
|
|
34724
|
-
"svchost",
|
|
34725
|
-
"lsass",
|
|
34726
|
-
"system",
|
|
34727
|
-
"smss",
|
|
34728
|
-
"dwm"
|
|
34729
|
-
]);
|
|
34730
|
-
var key = (name) => name.toLowerCase().replace(/\.exe$/, "");
|
|
34731
|
-
var isNeverTouched = (name) => NEVER2.has(key(name));
|
|
34732
|
-
var canLower = (name) => !isNeverTouched(name) && (COMPILERS.has(key(name)) || BUILD_TOOLS.has(key(name)));
|
|
34733
|
-
var HEAVY = /* @__PURE__ */ new Set([
|
|
34734
|
-
"rustc",
|
|
34735
|
-
"clippy-driver",
|
|
34736
|
-
"rustdoc",
|
|
34737
|
-
"cc1",
|
|
34738
|
-
"cc1plus",
|
|
34739
|
-
"clang",
|
|
34740
|
-
"clang++",
|
|
34741
|
-
"clang-cl",
|
|
34742
|
-
"cl",
|
|
34743
|
-
"link",
|
|
34744
|
-
"lld",
|
|
34745
|
-
"lld-link",
|
|
34746
|
-
"ld",
|
|
34747
|
-
"ld.lld"
|
|
34748
|
-
]);
|
|
34749
|
-
var isHeavyCompiler = (name) => !isNeverTouched(name) && HEAVY.has(key(name));
|
|
34750
|
-
var canPause = (name) => !isNeverTouched(name) && COMPILERS.has(key(name));
|
|
34751
|
-
var INITIAL_STATE = {
|
|
34752
|
-
step: 0,
|
|
34753
|
-
calm: 0,
|
|
34754
|
-
lowered: {},
|
|
34755
|
-
memLow: [],
|
|
34756
|
-
throttled: [],
|
|
34757
|
-
trimmed: [],
|
|
34758
|
-
cpuRate: 100,
|
|
34759
|
-
lastCommitMb: 0,
|
|
34760
|
-
lastAt: 0,
|
|
34761
|
-
growth: 0,
|
|
34762
|
-
hostUntil: 0,
|
|
34763
|
-
hostStep: 0
|
|
34764
|
-
};
|
|
34765
|
-
var GROWTH_WEIGHT = 0.3;
|
|
34766
|
-
var HOLD_MS = 3e3;
|
|
34767
|
-
var ROTATE_MS = 2e4;
|
|
34768
|
-
var SOON_WITHIN_PERCENT = 12;
|
|
34769
|
-
function stepFor(ramFree, commit, secondsToLimit, profile) {
|
|
34770
|
-
let step = 0;
|
|
34771
|
-
DIAL_STEPS[profile].forEach((t, i) => {
|
|
34772
|
-
const near = commit > t.commitPercent - SOON_WITHIN_PERCENT;
|
|
34773
|
-
const soon = near && t.secondsToLimit !== void 0 && secondsToLimit < t.secondsToLimit;
|
|
34774
|
-
if (ramFree < t.availablePercent || commit > t.commitPercent || soon) step = i + 1;
|
|
34775
|
-
});
|
|
34776
|
-
return step;
|
|
34777
|
-
}
|
|
34778
|
-
function clearOf(step, ramFree, commit, profile) {
|
|
34779
|
-
if (step === 0) return true;
|
|
34780
|
-
const t = DIAL_STEPS[profile][step - 1];
|
|
34781
|
-
return ramFree > t.availablePercent + DIAL_EXIT_MARGIN.availablePercent && commit < t.commitPercent - DIAL_EXIT_MARGIN.commitPercent;
|
|
34782
|
-
}
|
|
34783
|
-
function decide(prev, sample, nowMs, profile, maxCompilers2 = null) {
|
|
34784
|
-
const ramFree = sample.ramAvailMb / Math.max(1, sample.ramTotalMb) * 100;
|
|
34785
|
-
const usedMb = sample.commitLimitMb - sample.commitAvailMb;
|
|
34786
|
-
const commit = usedMb / Math.max(1, sample.commitLimitMb) * 100;
|
|
34787
|
-
const seconds = (nowMs - prev.lastAt) / 1e3;
|
|
34788
|
-
const instant = prev.lastAt > 0 && seconds > 0 ? (usedMb - prev.lastCommitMb) / seconds : 0;
|
|
34789
|
-
const growth = prev.lastAt > 0 ? prev.growth * (1 - GROWTH_WEIGHT) + instant * GROWTH_WEIGHT : 0;
|
|
34790
|
-
const secondsToLimit = growth > 0 ? sample.commitAvailMb / growth : Number.POSITIVE_INFINITY;
|
|
34791
|
-
const pcLow = nowMs < prev.hostUntil;
|
|
34792
|
-
const wanted2 = Math.max(stepFor(ramFree, commit, secondsToLimit, profile), pcLow ? prev.hostStep : 0);
|
|
34793
|
-
let step = prev.step;
|
|
34794
|
-
let calm = 0;
|
|
34795
|
-
if (wanted2 > prev.step) step = wanted2;
|
|
34796
|
-
else if (prev.step > 0 && wanted2 < prev.step && clearOf(prev.step, ramFree, commit, profile)) {
|
|
34797
|
-
calm = prev.calm + 1;
|
|
34798
|
-
if (calm >= DIAL_CALM_SAMPLES) {
|
|
34799
|
-
step = prev.step - 1;
|
|
34800
|
-
calm = 0;
|
|
34801
|
-
}
|
|
34802
|
-
}
|
|
34803
|
-
const actions = [];
|
|
34804
|
-
const present2 = new Set(sample.procs.map((p) => p.pid));
|
|
34805
|
-
const keep = (pids) => pids.filter((pid) => present2.has(pid));
|
|
34806
|
-
const lowered = {};
|
|
34807
|
-
for (const [pid, to] of Object.entries(prev.lowered)) if (present2.has(Number(pid))) lowered[Number(pid)] = to;
|
|
34808
|
-
const memLow = new Set(keep(prev.memLow));
|
|
34809
|
-
const throttled = new Set(keep(prev.throttled));
|
|
34810
|
-
const trimmed = new Set(keep(prev.trimmed));
|
|
34811
|
-
for (const p of sample.procs) {
|
|
34812
|
-
if (!canLower(p.name)) continue;
|
|
34813
|
-
const rest = profile === "gentle" && canPause(p.name) ? "below" : "normal";
|
|
34814
|
-
const target = step >= 2 ? "idle" : step === 1 ? "below" : rest;
|
|
34815
|
-
const was = lowered[p.pid];
|
|
34816
|
-
const touched = was !== void 0 || target !== "normal";
|
|
34817
|
-
if (touched && was !== target && p.prio !== target)
|
|
34818
|
-
actions.push({ kind: "priority", pid: p.pid, name: p.name, to: target });
|
|
34819
|
-
if (target === "normal") delete lowered[p.pid];
|
|
34820
|
-
else lowered[p.pid] = target;
|
|
34821
|
-
}
|
|
34822
|
-
const lowMemory = step >= 1;
|
|
34823
|
-
for (const p of sample.procs) {
|
|
34824
|
-
if (!canLower(p.name) || lowMemory === memLow.has(p.pid)) continue;
|
|
34825
|
-
actions.push({ kind: "memory", pid: p.pid, name: p.name, low: lowMemory });
|
|
34826
|
-
if (lowMemory) memLow.add(p.pid);
|
|
34827
|
-
else memLow.delete(p.pid);
|
|
34828
|
-
}
|
|
34829
|
-
if (step >= 2) {
|
|
34830
|
-
for (const p of sample.procs)
|
|
34831
|
-
if (canPause(p.name) && !throttled.has(p.pid)) {
|
|
34832
|
-
actions.push({ kind: "throttle", pid: p.pid, name: p.name });
|
|
34833
|
-
throttled.add(p.pid);
|
|
34834
|
-
}
|
|
34835
|
-
}
|
|
34836
|
-
const cpuRate = DIAL_CPU_RATE[step] ?? 100;
|
|
34837
|
-
if (cpuRate !== prev.cpuRate) actions.push({ kind: "cpu", percent: cpuRate });
|
|
34838
|
-
const heavy = sample.procs.filter((p) => isHeavyCompiler(p.name)).sort((a, b) => a.pid - b.pid);
|
|
34839
|
-
let allowed = compilersAllowed(step, heavy.length);
|
|
34840
|
-
if (maxCompilers2 !== null && step >= 2) allowed = Math.min(allowed, Math.max(1, maxCompilers2));
|
|
34841
|
-
if (allowed < heavy.length) {
|
|
34842
|
-
const turn = Math.floor(nowMs / ROTATE_MS) % heavy.length;
|
|
34843
|
-
const order = [...heavy.slice(turn), ...heavy.slice(0, turn)];
|
|
34844
|
-
for (const p of order.slice(allowed)) {
|
|
34845
|
-
actions.push({ kind: "hold", pid: p.pid, name: p.name, ms: HOLD_MS });
|
|
34846
|
-
if (step >= 3 && !trimmed.has(p.pid)) {
|
|
34847
|
-
actions.push({ kind: "trim", pid: p.pid, name: p.name });
|
|
34848
|
-
trimmed.add(p.pid);
|
|
34849
|
-
}
|
|
34850
|
-
}
|
|
34851
|
-
for (const p of order.slice(0, allowed)) trimmed.delete(p.pid);
|
|
34852
|
-
} else trimmed.clear();
|
|
34853
|
-
const level = step >= 3 ? "suspending" : step >= 1 ? "throttled" : "ok";
|
|
34854
|
-
return {
|
|
34855
|
-
state: {
|
|
34856
|
-
step,
|
|
34857
|
-
calm,
|
|
34858
|
-
lowered,
|
|
34859
|
-
memLow: [...memLow],
|
|
34860
|
-
throttled: [...throttled],
|
|
34861
|
-
trimmed: [...trimmed],
|
|
34862
|
-
cpuRate,
|
|
34863
|
-
lastCommitMb: usedMb,
|
|
34864
|
-
lastAt: nowMs,
|
|
34865
|
-
growth,
|
|
34866
|
-
hostUntil: prev.hostUntil,
|
|
34867
|
-
hostStep: prev.hostStep
|
|
34868
|
-
},
|
|
34869
|
-
actions,
|
|
34870
|
-
level,
|
|
34871
|
-
ramFreePercent: Math.round(ramFree),
|
|
34872
|
-
commitPercent: Math.round(commit)
|
|
34873
|
-
};
|
|
34874
|
-
}
|
|
34875
|
-
|
|
34876
|
-
// src/guest/governor/core.ts
|
|
34877
|
-
var STEP_TEXT = [
|
|
34878
|
-
"builds back to normal",
|
|
34879
|
-
"build programs at lower priority",
|
|
34880
|
-
"builds get less CPU and take turns",
|
|
34881
|
-
"builds take turns, waiting ones go to the paging file",
|
|
34882
|
-
"one compiler at a time"
|
|
34883
|
-
];
|
|
34884
|
-
var GovernorCore = class {
|
|
34885
|
-
constructor(deps) {
|
|
34886
|
-
this.deps = deps;
|
|
34887
|
-
}
|
|
34888
|
-
deps;
|
|
34889
|
-
state = INITIAL_STATE;
|
|
34890
|
-
lastAction = "";
|
|
34891
|
-
lastWrite = "";
|
|
34892
|
-
lastWriteAt = 0;
|
|
34893
|
-
/** Compilers waiting their turn now, by pid (name), to log a hold once. */
|
|
34894
|
-
held = /* @__PURE__ */ new Map();
|
|
34895
|
-
hostPressure(seconds, reason, level = "slow") {
|
|
34896
|
-
const nowMs = this.deps.now();
|
|
34897
|
-
const until = nowMs + seconds * 1e3;
|
|
34898
|
-
const active = this.state.hostUntil > nowMs;
|
|
34899
|
-
const step = Math.max(level === "severe" ? 4 : 2, active ? this.state.hostStep : 0);
|
|
34900
|
-
if (!active || step > this.state.hostStep)
|
|
34901
|
-
this.deps.logLine(
|
|
34902
|
-
"governor: this PC is " + (level === "severe" ? "nearly out of memory" : "low") + " (" + reason + "); builds to step " + step
|
|
34903
|
-
);
|
|
34904
|
-
this.state = { ...this.state, hostUntil: Math.max(until, this.state.hostUntil), hostStep: step };
|
|
34905
|
-
}
|
|
34906
|
-
get current() {
|
|
34907
|
-
return this.state;
|
|
34908
|
-
}
|
|
34909
|
-
onSample(sample) {
|
|
34910
|
-
const nowMs = this.deps.now();
|
|
34911
|
-
const before = this.state.step;
|
|
34912
|
-
const d = decide(this.state, sample, nowMs, this.deps.profile, this.deps.maxCompilers ?? null);
|
|
34913
|
-
this.state = d.state;
|
|
34914
|
-
const holding = /* @__PURE__ */ new Set();
|
|
34915
|
-
for (const action of d.actions) {
|
|
34916
|
-
if (action.kind === "hold") holding.add(action.pid);
|
|
34917
|
-
this.apply(action);
|
|
34918
|
-
}
|
|
34919
|
-
for (const pid of [...this.held.keys()]) if (!holding.has(pid)) this.held.delete(pid);
|
|
34920
|
-
if (d.state.step !== before)
|
|
34921
|
-
this.deps.logLine(
|
|
34922
|
-
"governor: step " + d.state.step + " (" + d.ramFreePercent + "% RAM free, commit " + d.commitPercent + "% used): " + STEP_TEXT[d.state.step]
|
|
34923
|
-
);
|
|
34924
|
-
const status = {
|
|
34925
|
-
level: d.level,
|
|
34926
|
-
step: d.state.step,
|
|
34927
|
-
profile: this.deps.profile,
|
|
34928
|
-
ramFreePercent: Math.max(0, Math.min(100, d.ramFreePercent)),
|
|
34929
|
-
commitPercent: Math.max(0, Math.min(100, d.commitPercent)),
|
|
34930
|
-
lowered: Object.keys(d.state.lowered).length,
|
|
34931
|
-
paused: [...this.held].slice(0, 8).map(([pid, name]) => ({ name: name.slice(0, 64), pid, seconds: HOLD_MS / 1e3 })),
|
|
34932
|
-
cpuPercent: d.state.cpuRate,
|
|
34933
|
-
pcLow: nowMs < d.state.hostUntil,
|
|
34934
|
-
lastAction: this.lastAction.slice(0, 300),
|
|
34935
|
-
updatedAt: now()
|
|
34936
|
-
};
|
|
34937
|
-
const shape = JSON.stringify({ ...status, updatedAt: "", paused: status.paused.map((p) => p.pid) });
|
|
34938
|
-
if (shape !== this.lastWrite || nowMs - this.lastWriteAt > 6e4) {
|
|
34939
|
-
this.lastWrite = shape;
|
|
34940
|
-
this.lastWriteAt = nowMs;
|
|
34941
|
-
this.deps.saveStatus(status);
|
|
34942
|
-
}
|
|
34943
|
-
}
|
|
34944
|
-
apply(action) {
|
|
34945
|
-
const port = this.deps.port;
|
|
34946
|
-
try {
|
|
34947
|
-
switch (action.kind) {
|
|
34948
|
-
case "hold":
|
|
34949
|
-
port.hold(action.pid, action.ms);
|
|
34950
|
-
if (!this.held.has(action.pid)) {
|
|
34951
|
-
this.held.set(action.pid, action.name);
|
|
34952
|
-
this.note(action.name + " (" + action.pid + ") waits its turn");
|
|
34953
|
-
}
|
|
34954
|
-
return;
|
|
34955
|
-
case "priority":
|
|
34956
|
-
port.setPriority(action.pid, action.to);
|
|
34957
|
-
this.note(action.name + " (" + action.pid + ") priority " + action.to);
|
|
34958
|
-
return;
|
|
34959
|
-
case "memory":
|
|
34960
|
-
port.setMemoryPriority(action.pid, action.low);
|
|
34961
|
-
return;
|
|
34962
|
-
case "throttle":
|
|
34963
|
-
port.throttle(action.pid);
|
|
34964
|
-
return;
|
|
34965
|
-
case "cpu":
|
|
34966
|
-
port.setCpuRate(action.percent);
|
|
34967
|
-
this.note(action.percent >= 100 ? "compilers: no CPU cap" : "compilers: at most " + action.percent + "% CPU");
|
|
34968
|
-
return;
|
|
34969
|
-
case "trim":
|
|
34970
|
-
port.trim(action.pid);
|
|
34971
|
-
return;
|
|
34972
|
-
}
|
|
34973
|
-
} catch (e) {
|
|
34974
|
-
this.deps.warn("governor: " + errMsg(e));
|
|
34975
|
-
}
|
|
34976
|
-
}
|
|
34977
|
-
note(text) {
|
|
34978
|
-
this.lastAction = text;
|
|
34979
|
-
this.deps.logLine("governor: " + text);
|
|
34980
|
-
}
|
|
34981
|
-
};
|
|
34982
|
-
|
|
34983
|
-
// src/guest/governor/governor.ts
|
|
34984
|
-
import fs64 from "node:fs";
|
|
34985
|
-
import os14 from "node:os";
|
|
34986
|
-
import path81 from "node:path";
|
|
34987
|
-
import { spawn as spawn9 } from "node:child_process";
|
|
34988
|
-
import { createInterface } from "node:readline";
|
|
34989
|
-
var SampleSchema = external_exports.object({
|
|
34990
|
-
ramTotalMb: external_exports.number(),
|
|
34991
|
-
ramAvailMb: external_exports.number(),
|
|
34992
|
-
commitLimitMb: external_exports.number(),
|
|
34993
|
-
commitAvailMb: external_exports.number(),
|
|
34994
|
-
procs: external_exports.array(
|
|
34995
|
-
external_exports.object({
|
|
34996
|
-
pid: external_exports.number().int(),
|
|
34997
|
-
name: external_exports.string().max(128),
|
|
34998
|
-
mb: external_exports.number(),
|
|
34999
|
-
prio: external_exports.enum(["idle", "below", "normal"]).catch("normal")
|
|
35000
|
-
})
|
|
35001
|
-
).max(1024)
|
|
35002
|
-
});
|
|
35003
|
-
var MemGovProcess = class {
|
|
35004
|
-
constructor(onSample) {
|
|
35005
|
-
this.onSample = onSample;
|
|
35006
|
-
}
|
|
35007
|
-
onSample;
|
|
35008
|
-
child = null;
|
|
35009
|
-
start() {
|
|
35010
|
-
try {
|
|
35011
|
-
const child = spawn9(ensureCompiled("memgov"), ["serve", "--interval", "2"], {
|
|
35012
|
-
stdio: ["pipe", "pipe", "ignore"],
|
|
35013
|
-
windowsHide: true
|
|
35014
|
-
});
|
|
35015
|
-
this.child = child;
|
|
35016
|
-
createInterface({ input: child.stdout }).on("line", (line) => {
|
|
35017
|
-
try {
|
|
35018
|
-
this.onSample(SampleSchema.parse(JSON.parse(line)));
|
|
35019
|
-
} catch (e) {
|
|
35020
|
-
warn("governor: bad sample: " + errMsg(e));
|
|
35021
|
-
}
|
|
35022
|
-
});
|
|
35023
|
-
child.stdin.on("error", () => void 0);
|
|
35024
|
-
child.once("exit", () => {
|
|
35025
|
-
this.child = null;
|
|
35026
|
-
setTimeout(() => this.start(), 3e4).unref();
|
|
35027
|
-
});
|
|
35028
|
-
} catch (e) {
|
|
35029
|
-
warn("governor: could not start memgov.exe (no memory governor): " + errMsg(e));
|
|
35030
|
-
}
|
|
35031
|
-
}
|
|
35032
|
-
setPriority(pid, to) {
|
|
35033
|
-
this.send("priority " + pid + " " + to);
|
|
35034
|
-
}
|
|
35035
|
-
hold(pid, ms) {
|
|
35036
|
-
this.send("suspend " + pid + " " + ms);
|
|
35037
|
-
}
|
|
35038
|
-
setMemoryPriority(pid, low) {
|
|
35039
|
-
this.send("memprio " + pid + " " + (low ? "low" : "normal"));
|
|
35040
|
-
}
|
|
35041
|
-
throttle(pid) {
|
|
35042
|
-
this.send("throttle " + pid);
|
|
35043
|
-
}
|
|
35044
|
-
setCpuRate(percent) {
|
|
35045
|
-
this.send("cpurate " + Math.round(percent));
|
|
35046
|
-
}
|
|
35047
|
-
trim(pid) {
|
|
35048
|
-
this.send("trim " + pid);
|
|
35049
|
-
}
|
|
35050
|
-
send(line) {
|
|
35051
|
-
this.child?.stdin?.write(line + "\n");
|
|
35052
|
-
}
|
|
35053
|
-
};
|
|
35054
|
-
function appendToMemoryLog(line) {
|
|
35055
|
-
try {
|
|
35056
|
-
const logs = path81.join(dataDir, "logs");
|
|
35057
|
-
const newest = fs64.readdirSync(logs).filter((f) => /^memory-\d{8}-\d{6}\.log$/.test(f)).sort().pop();
|
|
35058
|
-
if (!newest) return;
|
|
35059
|
-
const t = /* @__PURE__ */ new Date();
|
|
35060
|
-
const stamp = [t.getHours(), t.getMinutes(), t.getSeconds()].map((n) => String(n).padStart(2, "0")).join(":");
|
|
35061
|
-
fs64.appendFileSync(path81.join(logs, newest), stamp + " | " + line + "\r\n");
|
|
35062
|
-
} catch {
|
|
35063
|
-
}
|
|
35064
|
-
}
|
|
35065
|
-
var Governor = class {
|
|
35066
|
-
core = null;
|
|
35067
|
-
describe() {
|
|
35068
|
-
const profile = session.loadProfile === "gentle" ? "gentle" : "normal";
|
|
35069
|
-
const first = DIAL_STEPS[profile][0];
|
|
35070
|
-
return session.pressureResponse === false ? "off (sandbox.pressureResponse)" : profile + " profile: slows builds down from " + first.availablePercent + "% free RAM or " + first.commitPercent + "% commit, in steps";
|
|
35071
|
-
}
|
|
35072
|
-
start() {
|
|
35073
|
-
if (session.pressureResponse === false) return;
|
|
35074
|
-
const profile = session.loadProfile === "gentle" ? "gentle" : "normal";
|
|
35075
|
-
const proc = new MemGovProcess((s) => this.core?.onSample(s));
|
|
35076
|
-
this.core = new GovernorCore({
|
|
35077
|
-
port: proc,
|
|
35078
|
-
profile,
|
|
35079
|
-
maxCompilers: maxCompilers(os14.cpus().length, profile),
|
|
35080
|
-
now: () => Date.now(),
|
|
35081
|
-
logLine: appendToMemoryLog,
|
|
35082
|
-
warn,
|
|
35083
|
-
saveStatus: (status) => {
|
|
35084
|
-
try {
|
|
35085
|
-
writeJsonAtomic(run.pressureFile, status);
|
|
35086
|
-
} catch (e) {
|
|
35087
|
-
warn("governor: could not write its status: " + errMsg(e));
|
|
35088
|
-
}
|
|
35089
|
-
}
|
|
35090
|
-
});
|
|
35091
|
-
proc.start();
|
|
35092
|
-
}
|
|
35093
|
-
/** The host's watchdog: this PC is low (or nearly out of memory) for a while. */
|
|
35094
|
-
hostPressure(seconds, reason, level = "slow") {
|
|
35095
|
-
this.core?.hostPressure(seconds, reason, level);
|
|
35096
|
-
}
|
|
35097
|
-
};
|
|
35098
|
-
var governor = new Governor();
|
|
35099
|
-
|
|
35100
35249
|
// src/guest/daemon/live.ts
|
|
35101
35250
|
import fs66 from "node:fs";
|
|
35102
35251
|
import path83 from "node:path";
|
|
@@ -35453,7 +35602,7 @@ var Daemon = class {
|
|
|
35453
35602
|
case "close":
|
|
35454
35603
|
return sessions.close(req);
|
|
35455
35604
|
case "pressure":
|
|
35456
|
-
return governor.hostPressure(req.seconds, req.reason, req.level ?? "slow");
|
|
35605
|
+
return governor.hostPressure(req.seconds, req.reason, req.level ?? "slow", req.diskQueue);
|
|
35457
35606
|
case "ping":
|
|
35458
35607
|
return respond(req.id, { ok: true, message: "pong (" + sessions.count + " session(s) running)" });
|
|
35459
35608
|
}
|
|
@@ -35472,4 +35621,5 @@ safely("leftover processes", () => sessions.sweepLeftovers());
|
|
|
35472
35621
|
safely("memory governor", () => governor.start());
|
|
35473
35622
|
log("Memory governor: " + governor.describe());
|
|
35474
35623
|
safely("service sign-ins", startAccountWatch);
|
|
35624
|
+
safely("heartbeat", () => startHeartbeat(() => sessions.count));
|
|
35475
35625
|
new Daemon().start();
|