windows-development-sandbox 1.18.2 → 1.18.3

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.
Files changed (72) hide show
  1. package/config.example.json +1 -1
  2. package/dist/app/features/defs.d.ts +8 -0
  3. package/dist/app/features/sandbox/sandbox.def.d.ts +12 -0
  4. package/dist/app/features/sandbox/sandbox.def.js +17 -2
  5. package/dist/app/features/sandbox/sandbox.run.d.ts +4 -0
  6. package/dist/app/features/sandbox/sandbox.run.js +4 -0
  7. package/dist/bin/agent-sandbox.js +2 -2
  8. package/dist/bin/{chunk-IIHL7W6B.js → chunk-3G53NNZP.js} +1 -1
  9. package/dist/bin/{chunk-MQIA7GNB.js → chunk-FQWYCF6L.js} +981 -866
  10. package/dist/bin/{chunk-M4OM632I.js → chunk-M6XUQK53.js} +1 -1
  11. package/dist/bin/claude-host.js +2 -2
  12. package/dist/bin/claude-sbx.js +2 -2
  13. package/dist/bin/codex-sbx.js +2 -2
  14. package/dist/bin/gemini-sbx.js +2 -2
  15. package/dist/bin/sandbox.js +2 -2
  16. package/dist/bin/sbx-hide.js +2 -2
  17. package/dist/bin/sbx-show.js +2 -2
  18. package/dist/cli/commands/repair/checks/sandbox.d.ts +3 -1
  19. package/dist/cli/commands/repair/checks/sandbox.js +24 -0
  20. package/dist/core/config/defaults.js +1 -1
  21. package/dist/core/schema/sandbox.js +1 -1
  22. package/dist/core/schema/toolchains.js +2 -2
  23. package/dist/guest/daemon/daemon.js +1 -1
  24. package/dist/guest/daemon/heartbeat.d.ts +6 -0
  25. package/dist/guest/daemon/heartbeat.js +40 -0
  26. package/dist/guest/entries/daemon.js +2 -0
  27. package/dist/guest/governor/core.d.ts +19 -1
  28. package/dist/guest/governor/core.js +23 -1
  29. package/dist/guest/governor/decide.d.ts +14 -0
  30. package/dist/guest/governor/decide.js +52 -7
  31. package/dist/guest/governor/governor.d.ts +4 -1
  32. package/dist/guest/governor/governor.js +9 -2
  33. package/dist/guest/toolchains/native/rust-extras.d.ts +7 -0
  34. package/dist/guest/toolchains/native/rust-extras.js +26 -3
  35. package/dist/guest/toolchains/plan.js +6 -4
  36. package/dist/host/channel/channel.d.ts +1 -1
  37. package/dist/host/channel/channel.js +10 -2
  38. package/dist/host/launch/wsb-file.d.ts +6 -1
  39. package/dist/host/launch/wsb-file.js +9 -5
  40. package/dist/host/life/crash-report.d.ts +2 -0
  41. package/dist/host/life/crash-report.js +4 -0
  42. package/dist/host/life/index.d.ts +1 -0
  43. package/dist/host/life/index.js +1 -0
  44. package/dist/host/life/last-end.d.ts +21 -0
  45. package/dist/host/life/last-end.js +70 -0
  46. package/dist/host/life/watchdog.d.ts +2 -0
  47. package/dist/host/life/watchdog.js +8 -2
  48. package/dist/runtime/agent.js +26 -3
  49. package/dist/runtime/daemon.js +367 -234
  50. package/dist/runtime/db.js +26 -3
  51. package/dist/runtime/main.js +57 -11
  52. package/dist/runtime/netproxy.js +26 -3
  53. package/dist/runtime/tool.js +26 -3
  54. package/dist/runtime/view.js +26 -3
  55. package/dist/selftest/heavy-load.test.js +95 -14
  56. package/dist/selftest/live-terminal.test.js +4 -0
  57. package/dist/shared/channel/run-dir.d.ts +4 -1
  58. package/dist/shared/channel/run-dir.js +7 -1
  59. package/dist/shared/channel/schema.d.ts +23 -0
  60. package/dist/shared/channel/schema.js +21 -0
  61. package/dist/shared/memory/policy.d.ts +10 -0
  62. package/dist/shared/memory/policy.js +17 -1
  63. package/dist/sync/watcher.js +1 -1
  64. package/dist/web/index.html +1 -1
  65. package/dist/web/{main-CWIF4VQI.js → main-6XYO5FZC.js} +43 -43
  66. package/native/csharp/HcsHost/AssemblyInfo.cs +3 -3
  67. package/native/csharp/HostTools/AssemblyInfo.cs +3 -3
  68. package/native/csharp/StorageHelper/AssemblyInfo.cs +3 -3
  69. package/package.json +1 -1
  70. package/sandbox/csharp/MemGov.cs +58 -0
  71. package/schemas/config.schema.json +3 -3
  72. package/schemas/sandbox-config.schema.json +3 -3
@@ -192,8 +192,16 @@ export class SandboxChannel {
192
192
  return this.waitForResponse(requestId, 30000);
193
193
  }
194
194
  /** The PC is low on memory or CPU: the sandbox slows its builds down for `seconds`. Never waits for an answer. */
195
- pressure(reason, seconds, level = 'slow') {
196
- this.send({ kind: 'pressure', id: newId(), reason, seconds, level, createdAt: now() });
195
+ pressure(reason, seconds, level = 'slow', diskQueue) {
196
+ this.send({
197
+ kind: 'pressure',
198
+ id: newId(),
199
+ reason,
200
+ seconds,
201
+ level,
202
+ ...(diskQueue === undefined ? {} : { diskQueue }),
203
+ createdAt: now(),
204
+ });
197
205
  }
198
206
  async ping() {
199
207
  const requestId = newId();
@@ -18,7 +18,12 @@ export interface MemoryDecision {
18
18
  readonly commitAvailableMb: number | null;
19
19
  }
20
20
  /**
21
- * The most memory this PC can keep in RAM for the sandbox (pure): its RAM minus max(reserveHostMb, 25 %), and 2 GB
21
+ * GPU-PV (vGPU) needs room of its own on this PC: the GPU's shared memory for the sandbox comes out of this PC's RAM,
22
+ * and a GPU game plus heavy builds with vGPU on has killed the VM when the PC had no RAM left for it.
23
+ */
24
+ export declare const VGPU_RESERVE_MB = 4096;
25
+ /**
26
+ * The most memory this PC can keep in RAM for the sandbox (pure): its RAM minus max(reserveHostMb, 25 %), and 4 GB
22
27
  * more with vGPU. Windows Sandbox's memory is this PC's memory taken as needed; beyond what the PC can keep resident it
23
28
  * is paged to this PC's disk while the sandbox pages to its own, and that is what can freeze the whole PC.
24
29
  */
@@ -11,10 +11,13 @@ export const GUEST = {
11
11
  const MB = 1048576;
12
12
  const MIN_AUTO_MEMORY_MB = 4096;
13
13
  const roundDown = (mb) => Math.floor(mb / 1024) * 1024;
14
- /** GPU-PV (vGPU) needs room of its own on this PC: memory the graphics driver pins for the sandbox. */
15
- const VGPU_RESERVE_MB = 2048;
16
14
  /**
17
- * The most memory this PC can keep in RAM for the sandbox (pure): its RAM minus max(reserveHostMb, 25 %), and 2 GB
15
+ * GPU-PV (vGPU) needs room of its own on this PC: the GPU's shared memory for the sandbox comes out of this PC's RAM,
16
+ * and a GPU game plus heavy builds with vGPU on has killed the VM when the PC had no RAM left for it.
17
+ */
18
+ export const VGPU_RESERVE_MB = 4096;
19
+ /**
20
+ * The most memory this PC can keep in RAM for the sandbox (pure): its RAM minus max(reserveHostMb, 25 %), and 4 GB
18
21
  * more with vGPU. Windows Sandbox's memory is this PC's memory taken as needed; beyond what the PC can keep resident it
19
22
  * is paged to this PC's disk while the sandbox pages to its own, and that is what can freeze the whole PC.
20
23
  */
@@ -46,8 +49,9 @@ export function residentNote(asked, given) {
46
49
  */
47
50
  export function decideMemory(sandbox, freeMb, totalMb, commitAvailableMb) {
48
51
  const shareMb = Math.floor((totalMb * sandbox.maxMemoryPercent) / 100);
49
- const byFree = freeMb - sandbox.reserveHostMb;
50
- const byCommit = commitAvailableMb === null ? Infinity : commitAvailableMb - sandbox.reserveHostMb;
52
+ const reserveMb = sandbox.reserveHostMb + (sandbox.vGpu ? VGPU_RESERVE_MB : 0);
53
+ const byFree = freeMb - reserveMb;
54
+ const byCommit = commitAvailableMb === null ? Infinity : commitAvailableMb - reserveMb;
51
55
  const candidates = [
52
56
  ['max', sandbox.maxMemoryMb],
53
57
  ['share', shareMb],
@@ -11,6 +11,8 @@ export interface CrashExtra {
11
11
  readonly hostMemoryNow?: string;
12
12
  /** The frozen sandbox's launch had vGPU on (read from its .wsb). */
13
13
  readonly vgpu?: boolean;
14
+ /** The sandbox's last heartbeat as a line (see heartbeatLine): what it was doing inside. */
15
+ readonly lastInside?: string;
14
16
  }
15
17
  /** "This PC stopped responding at X and was restarted at Y (n minutes later)", or null when there was no long gap. */
16
18
  export declare function freezeLine(cause: StopCause, lastSeen: string): string | null;
@@ -6,6 +6,7 @@ import { assertNoLinks, safeText } from '../../core/util/index.js';
6
6
  import { vgpuWarning } from '../launch/risk-notes.js';
7
7
  import { hostMemoryLine, readHostMemory } from './host-memory.js';
8
8
  import { hostDataFolder, hostDataRoot } from '../storage/index.js';
9
+ import { heartbeatLine, readHeartbeat } from './last-end.js';
9
10
  import { bugcheckCode, explainStop, readStopEvents } from './stop-events.js';
10
11
  const KEEP_REPORTS = 20;
11
12
  const MEMORY_TAIL_BYTES = 16 * 1024;
@@ -170,6 +171,7 @@ export function crashReportText(cause, events, memoryLog, lastSeen, extra = {})
170
171
  ...(hostLines.length ? hostLines : [' (none: the background sync was not running)']),
171
172
  ...(extra.hostMemoryNow ? [' after the restart: ' + extra.hostMemoryNow] : []),
172
173
  '',
174
+ ...(extra.lastInside ? [safeText(extra.lastInside), ''] : []),
173
175
  "The sandbox's memory log, last lines (C:\\AgentData\\logs\\memory-*.log inside it):",
174
176
  ...memoryLines,
175
177
  '',
@@ -239,9 +241,11 @@ export function saveCrashReport(storageRoot, dataPath, lastSeen) {
239
241
  const since = new Date(Date.parse(lastSeen) - 10 * 60 * 1000);
240
242
  const windir = process.env.SystemRoot ?? 'C:\\Windows';
241
243
  const memoryNow = readHostMemory(storageRoot);
244
+ const heartbeat = readHeartbeat(dataPath);
242
245
  fs.writeFileSync(file, crashReportText(cause, events, memoryLogTail(dataPath), lastSeen, {
243
246
  hostMemoryLog: hostMemoryTail(storageRoot, dataPath),
244
247
  vgpu: launchHadVgpu(storageRoot, lastSeen),
248
+ ...(heartbeat ? { lastInside: heartbeatLine(heartbeat) } : {}),
245
249
  dumps: findDumps([path.join(windir, 'Minidump'), windir], since),
246
250
  ...(memoryNow ? { hostMemoryNow: hostMemoryLine(memoryNow) } : {}),
247
251
  }));
@@ -4,6 +4,7 @@ export * from './stop-warning.js';
4
4
  export * from './keep-awake.js';
5
5
  export * from './windows-update.js';
6
6
  export * from './crash-report.js';
7
+ export * from './last-end.js';
7
8
  export * from './session-ledger.js';
8
9
  export * from './host-memory.js';
9
10
  export * from './watchdog.js';
@@ -4,6 +4,7 @@ export * from './stop-warning.js';
4
4
  export * from './keep-awake.js';
5
5
  export * from './windows-update.js';
6
6
  export * from './crash-report.js';
7
+ export * from './last-end.js';
7
8
  export * from './session-ledger.js';
8
9
  export * from './host-memory.js';
9
10
  export * from './watchdog.js';
@@ -0,0 +1,21 @@
1
+ import { type Heartbeat } from '../../shared/channel/index.js';
2
+ import type { Life } from './life-record.js';
3
+ /** How the last sandbox ended, when nobody closed it. */
4
+ export interface LastEnd {
5
+ /** "ended unexpectedly 21:31: this PC's disks were overloaded (queue 40)". */
6
+ readonly text: string;
7
+ readonly at: string;
8
+ }
9
+ /** The sandbox's last heartbeat, or null (none written, damaged, or reached through a link). */
10
+ export declare function readHeartbeat(dataPath: string): Heartbeat | null;
11
+ /** One line of what the sandbox was doing at its last heartbeat (pure; guest text goes through safeText). */
12
+ export declare function heartbeatLine(hb: Heartbeat): string;
13
+ /** The likeliest reason, from the last heartbeat (pure). */
14
+ export declare function guessReason(hb: Heartbeat | null): string;
15
+ /**
16
+ * How the last sandbox ended (pure): null when it was closed normally, or is still running. `heartbeat` only counts
17
+ * when it was written during that run.
18
+ */
19
+ export declare function lastEndOf(life: Life | null, heartbeat: Heartbeat | null, running: boolean): LastEnd | null;
20
+ /** The status page's "Last sandbox" fact for this data folder. */
21
+ export declare function readLastEnd(lifeRead: () => Life | null, dataPath: string, running: boolean): LastEnd | null;
@@ -0,0 +1,70 @@
1
+ // What the sandbox did in its last seconds and how it ended, for the status page and the crash report. The sandbox's
2
+ // daemon writes run\heartbeat.json every 10 seconds; when the sandbox vanishes, the last one is the best evidence.
3
+ // The guest wrote it, so it is read through the schema, never through a link, and printed through safeText.
4
+ import { assertNoLinks, safeText } from '../../core/util/index.js';
5
+ import { RunDir, HeartbeatSchema } from '../../shared/channel/index.js';
6
+ import { readValid } from '../../shared/io/index.js';
7
+ import { hostDataRoot } from '../storage/index.js';
8
+ /** The sandbox's last heartbeat, or null (none written, damaged, or reached through a link). */
9
+ export function readHeartbeat(dataPath) {
10
+ try {
11
+ const root = hostDataRoot(dataPath);
12
+ const file = new RunDir(root).heartbeatFile;
13
+ assertNoLinks(root, file);
14
+ return readValid(HeartbeatSchema, file);
15
+ }
16
+ catch {
17
+ // A link or an unreadable folder: no evidence, never an error.
18
+ return null;
19
+ }
20
+ }
21
+ const clock = (iso) => {
22
+ const at = new Date(iso);
23
+ return Number.isNaN(at.getTime())
24
+ ? iso
25
+ : at.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit', hour12: false });
26
+ };
27
+ /** One line of what the sandbox was doing at its last heartbeat (pure; guest text goes through safeText). */
28
+ export function heartbeatLine(hb) {
29
+ const top = hb.topProcesses.map((p) => safeText(p.name) + ' ' + p.privateMb + ' MB').join(', ');
30
+ return ('last seen inside at ' +
31
+ clock(hb.at) +
32
+ ': up ' +
33
+ Math.round(hb.uptimeS / 60) +
34
+ ' min, ' +
35
+ hb.ramFreePct +
36
+ '% RAM free, ' +
37
+ hb.commitPct +
38
+ '% commit used, builds at step ' +
39
+ hb.step +
40
+ (hb.hostStep > 0 ? ' (this PC asked for step ' + hb.hostStep + ')' : '') +
41
+ (hb.diskQueueReported !== undefined ? ', this PC disk queue ' + hb.diskQueueReported : '') +
42
+ (top ? '; biggest: ' + top : ''));
43
+ }
44
+ /** The likeliest reason, from the last heartbeat (pure). */
45
+ export function guessReason(hb) {
46
+ if (hb && hb.diskQueueReported !== undefined && hb.hostStep > 0)
47
+ return "this PC's disks were overloaded (queue " + hb.diskQueueReported + ')';
48
+ if (hb && (hb.ramFreePct < 8 || hb.commitPct > 92))
49
+ return 'the sandbox was running out of memory';
50
+ return 'Windows stopped it (no reason was logged)';
51
+ }
52
+ /**
53
+ * How the last sandbox ended (pure): null when it was closed normally, or is still running. `heartbeat` only counts
54
+ * when it was written during that run.
55
+ */
56
+ export function lastEndOf(life, heartbeat, running) {
57
+ if (!life || running)
58
+ return null;
59
+ if (life.ended && !life.ended.cause)
60
+ return null;
61
+ const hb = heartbeat && Date.parse(heartbeat.at) >= Date.parse(life.started) - 60000 ? heartbeat : null;
62
+ const at = life.ended?.at ?? life.lastSeen;
63
+ const why = life.ended?.cause ? safeText(life.ended.cause.text) : guessReason(hb);
64
+ return { at, text: 'ended unexpectedly ' + clock(at) + ': ' + why };
65
+ }
66
+ /** The status page's "Last sandbox" fact for this data folder. */
67
+ export function readLastEnd(lifeRead, dataPath, running) {
68
+ const life = lifeRead();
69
+ return lastEndOf(life, life && !running ? readHeartbeat(dataPath) : null, running);
70
+ }
@@ -10,6 +10,8 @@ export interface WatchdogState {
10
10
  readonly cpuHighSeconds: number;
11
11
  /** Seconds the disk queue has been above the limit. */
12
12
  readonly diskHighSeconds: number;
13
+ /** Seconds the disk queue has been at or above twice the limit (for the severe step). */
14
+ readonly diskSevereSeconds: number;
13
15
  /** Time (ms) the last request went out. */
14
16
  readonly lastSentAt: number;
15
17
  }
@@ -9,6 +9,7 @@ export const WATCHDOG_START = {
9
9
  calm: 0,
10
10
  cpuHighSeconds: 0,
11
11
  diskHighSeconds: 0,
12
+ diskSevereSeconds: 0,
12
13
  lastSentAt: 0,
13
14
  };
14
15
  /** The request is repeated this often while the PC stays low, and each one lasts a bit longer than that. */
@@ -50,8 +51,12 @@ export function watchdogStep(prev, input) {
50
51
  reason = "this PC's CPU has been above " + t.cpuPercent + '% for ' + cpuHigh + ' seconds';
51
52
  const diskQueue = input.memory?.diskQueue;
52
53
  const diskHigh = diskQueue !== undefined && diskQueue >= t.diskQueue ? prev.diskHighSeconds + input.intervalSeconds : 0;
54
+ const diskDouble = diskQueue !== undefined && diskQueue >= 2 * t.diskQueue ? prev.diskSevereSeconds + input.intervalSeconds : 0;
55
+ const diskSevere = diskDouble >= t.diskSeconds || diskHigh >= 3 * t.diskSeconds;
53
56
  if (!reason && diskHigh >= t.diskSeconds)
54
- reason = "this PC's disks have had a queue of " + diskQueue + ' for ' + diskHigh + ' seconds';
57
+ reason = diskSevere
58
+ ? "this PC's disks are overloaded (queue " + diskQueue + ' for ' + diskHigh + ' seconds)'
59
+ : "this PC's disks have had a queue of " + diskQueue + ' for ' + diskHigh + ' seconds';
55
60
  const calmNow = input.memory ? lowReason(input.memory, input.reserveHostMb, t, 1.25) === '' : true;
56
61
  let low = prev.low;
57
62
  let calm = prev.calm;
@@ -66,7 +71,7 @@ export function watchdogStep(prev, input) {
66
71
  calm = 0;
67
72
  }
68
73
  }
69
- const severe = low && input.memory !== null && severeHost(input.memory);
74
+ const severe = low && ((input.memory !== null && severeHost(input.memory)) || diskSevere);
70
75
  // Getting severe is sent at once; otherwise the request is repeated every RESEND_MS while the PC stays low.
71
76
  const send = low && (input.nowMs - prev.lastSentAt >= RESEND_MS || (severe && !prev.severe));
72
77
  return {
@@ -76,6 +81,7 @@ export function watchdogStep(prev, input) {
76
81
  calm,
77
82
  cpuHighSeconds: cpuHigh,
78
83
  diskHighSeconds: diskHigh,
84
+ diskSevereSeconds: diskDouble,
79
85
  lastSentAt: send ? input.nowMs : prev.lastSentAt,
80
86
  },
81
87
  send,
@@ -25466,7 +25466,7 @@ var SandboxSchema = external_exports.strictObject({
25466
25466
  '(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.'
25467
25467
  ),
25468
25468
  memoryMb: external_exports.union([external_exports.literal("auto"), external_exports.number().int().min(2048)]).describe(
25469
- '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 %, 2 GB more with vGPU): a bigger value is lowered.'
25469
+ '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.'
25470
25470
  ),
25471
25471
  maxMemoryMb: external_exports.number().int().positive().describe('Upper limit for memoryMb "auto".'),
25472
25472
  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).`),
@@ -25628,13 +25628,13 @@ var ToolchainsSchema = external_exports.strictObject({
25628
25628
  '["sccache", "cargo-nextest", "cargo-deny"]'
25629
25629
  ),
25630
25630
  targetDir: external_exports.string().min(1).describe(
25631
- `"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.`
25631
+ `"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.`
25632
25632
  ),
25633
25633
  linker: external_exports.enum(["lld", "gnu"]).describe(
25634
25634
  `GNU host: "lld" links with Rust's own lld through MinGW's gcc (much less memory than GNU ld), "gnu" with GNU ld.`
25635
25635
  ),
25636
25636
  linkSlots: external_exports.union([external_exports.literal("auto"), external_exports.number().int().min(1).max(32)]).describe(
25637
- 'GNU host: how many programs may link at the same time in the whole sandbox (other links wait). "auto" = one per 3 GB of sandbox memory after 6 GB, 2 to 8 (the memory governor slows links down too when memory runs short).'
25637
+ '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).'
25638
25638
  )
25639
25639
  }),
25640
25640
  msvc: external_exports.strictObject({
@@ -27282,6 +27282,8 @@ var GuestRequestSchema = external_exports.discriminatedUnion("kind", [
27282
27282
  seconds: external_exports.number().int().min(10).max(300),
27283
27283
  /** "severe": the PC is nearly out of memory; builds go to the governor's last step at once. */
27284
27284
  level: external_exports.enum(["slow", "severe"]).optional(),
27285
+ /** The disk queue the PC reported (shown in the heartbeat). */
27286
+ diskQueue: external_exports.number().min(0).max(1e5).optional(),
27285
27287
  createdAt: TimeSchema
27286
27288
  })
27287
27289
  ]);
@@ -27304,6 +27306,21 @@ var PressureStatusSchema = external_exports.object({
27304
27306
  lastAction: Text(300),
27305
27307
  updatedAt: TimeSchema
27306
27308
  });
27309
+ var HeartbeatSchema = external_exports.object({
27310
+ at: TimeSchema,
27311
+ /** Seconds since the sandbox booted. */
27312
+ uptimeS: external_exports.number().int().min(0).max(1e9),
27313
+ ramFreePct: external_exports.number().min(0).max(100),
27314
+ commitPct: external_exports.number().min(0).max(100),
27315
+ /** The governor's dial now, and the step this PC asked for. */
27316
+ step: external_exports.number().int().min(0).max(4),
27317
+ hostStep: external_exports.number().int().min(0).max(4),
27318
+ /** The biggest programs in the agents' job. */
27319
+ topProcesses: external_exports.array(external_exports.object({ name: Text(64), privateMb: external_exports.number().int().min(0).max(1e7) })).max(5),
27320
+ /** The disk queue this PC last reported while asking the sandbox to slow down. */
27321
+ diskQueueReported: external_exports.number().min(0).max(1e5).optional(),
27322
+ phase: external_exports.enum(["ready", "working"])
27323
+ });
27307
27324
  var GuestResponseSchema = external_exports.object({
27308
27325
  id: IdSchema,
27309
27326
  ok: external_exports.boolean(),
@@ -27489,6 +27506,8 @@ var RunDir = class {
27489
27506
  vpnFile;
27490
27507
  /** What the memory governor in the sandbox is doing (pressure.json). */
27491
27508
  pressureFile;
27509
+ /** The daemon's heartbeat every 10 seconds (heartbeat.json): what the sandbox did last. */
27510
+ heartbeatFile;
27492
27511
  /** What the sandbox's programs may really use (budget.json): processors, memory, build jobs, link slots. */
27493
27512
  budgetFile;
27494
27513
  requestsDir;
@@ -27512,6 +27531,7 @@ var RunDir = class {
27512
27531
  this.sandboxFile = path5.join(this.root, "sandbox.json");
27513
27532
  this.vpnFile = path5.join(this.root, "vpn.json");
27514
27533
  this.pressureFile = path5.join(this.root, "pressure.json");
27534
+ this.heartbeatFile = path5.join(this.root, "heartbeat.json");
27515
27535
  this.budgetFile = path5.join(this.root, "budget.json");
27516
27536
  this.requestsDir = path5.join(this.root, "requests");
27517
27537
  this.responsesDir = path5.join(this.root, "responses");
@@ -27556,6 +27576,9 @@ var RunDir = class {
27556
27576
  readPressure() {
27557
27577
  return readValid(PressureStatusSchema, this.pressureFile);
27558
27578
  }
27579
+ readHeartbeat() {
27580
+ return readValid(HeartbeatSchema, this.heartbeatFile);
27581
+ }
27559
27582
  readSession(id) {
27560
27583
  return readValid(SessionStatusSchema, this.sessionFile(id));
27561
27584
  }