windows-development-sandbox 1.11.3 → 1.11.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app/features/accounts/accounts.def.js +0 -1
- package/dist/app/features/data/data.def.d.ts +1 -1
- package/dist/app/features/data/data.def.js +41 -48
- package/dist/app/features/defs.d.ts +7 -0
- package/dist/app/features/defs.js +7 -3
- package/dist/app/features/health/health.def.js +28 -9
- package/dist/app/features/sandbox/sandbox.def.js +4 -4
- package/dist/app/features/storage/storage.def.js +8 -13
- package/dist/app/features/updates/updates.def.js +2 -3
- package/dist/guest/toolchains/tools/cli.js +19 -0
- package/dist/host/life/host-memory.d.ts +18 -0
- package/dist/host/life/host-memory.js +66 -0
- package/dist/host/web/runner.d.ts +1 -1
- package/dist/host/web/server.d.ts +12 -3
- package/dist/host/web/server.js +20 -6
- package/dist/runtime/daemon.js +16 -1
- package/dist/runtime/main.js +16 -1
- package/dist/runtime/view.js +2 -0
- package/dist/selftest/app.test.js +21 -9
- package/dist/selftest/health.test.js +35 -0
- package/dist/selftest/live-terminal.test.js +30 -0
- package/dist/selftest/support/web-preview.js +3 -0
- package/dist/selftest/web.test.js +33 -0
- package/dist/shared/pty/client.d.ts +0 -1
- package/dist/shared/pty/client.js +5 -0
- package/dist/sync/watcher.d.ts +2 -0
- package/dist/sync/watcher.js +7 -2
- package/dist/web/index.html +2 -2
- package/dist/web/{main-Z645W45E.js → main-QL7KHCEE.js} +76 -76
- package/dist/web/{main-6JF3ASB5.css → main-RQFN4WBY.css} +1 -1
- package/native/csharp/HcsHost/AssemblyInfo.cs +3 -3
- package/native/csharp/HostTools/AssemblyInfo.cs +3 -3
- package/native/csharp/HostTools/MemoryStatus.cs +58 -2
- package/native/csharp/HostTools/Program.cs +7 -1
- package/package.json +1 -1
- package/sandbox/csharp/PtyHost.cs +228 -2
package/dist/runtime/main.js
CHANGED
|
@@ -29266,6 +29266,17 @@ function ghExePath() {
|
|
|
29266
29266
|
const dir = tool("cli", "gh");
|
|
29267
29267
|
return path34.join(entry?.subdir ? path34.join(dir, entry.subdir) : dir, entry?.exe ?? "gh.exe");
|
|
29268
29268
|
}
|
|
29269
|
+
var GH_NOTE = " (log in once with: gh auth login --web --insecure-storage; it is kept between sandboxes)";
|
|
29270
|
+
function warnKeyringLogin() {
|
|
29271
|
+
try {
|
|
29272
|
+
const hosts = fs28.readFileSync(path34.join(ghConfigDir(), "hosts.yml"), "utf8");
|
|
29273
|
+
if (/^\s*user:/m.test(hosts) && !/oauth_token:/.test(hosts))
|
|
29274
|
+
warn(
|
|
29275
|
+
"gh: the GitHub login from an earlier sandbox was kept in Windows Credential Manager, which is wiped with each sandbox. Log in again with: gh auth login --web --insecure-storage (then it lasts)."
|
|
29276
|
+
);
|
|
29277
|
+
} catch {
|
|
29278
|
+
}
|
|
29279
|
+
}
|
|
29269
29280
|
var toolVars = {
|
|
29270
29281
|
gh: () => ({
|
|
29271
29282
|
GH_CONFIG_DIR: exists(deliveredGhConfigDir()) ? deliveredGhConfigDir() : mkdirp(ghConfigDir()),
|
|
@@ -29306,9 +29317,11 @@ async function setupCli(env, settings, gitAccess, refresh = false) {
|
|
|
29306
29317
|
await setupSingleExe(env, name, entry.repo, entry.asset(arch), entry.exe, tool("cli", name), {
|
|
29307
29318
|
refresh,
|
|
29308
29319
|
subdir: entry.subdir,
|
|
29309
|
-
size: entry.size
|
|
29320
|
+
size: entry.size,
|
|
29321
|
+
...name === "gh" ? { note: GH_NOTE } : {}
|
|
29310
29322
|
});
|
|
29311
29323
|
Object.assign(env.vars, toolVars[name]?.() ?? {});
|
|
29324
|
+
if (name === "gh") warnKeyringLogin();
|
|
29312
29325
|
} catch (e) {
|
|
29313
29326
|
warn(name + " could not be set up and is skipped; the other tools are not affected. Reason: " + errMsg(e));
|
|
29314
29327
|
env.summary.push(entry.name + ": FAILED");
|
|
@@ -34673,6 +34686,7 @@ var connectOnce = (host, port, timeoutMs) => {
|
|
|
34673
34686
|
socket.once("connect", () => {
|
|
34674
34687
|
clearTimeout(timer);
|
|
34675
34688
|
socket.setNoDelay(true);
|
|
34689
|
+
socket.setKeepAlive(true, KEEPALIVE_IDLE_MS);
|
|
34676
34690
|
resolve2(socket);
|
|
34677
34691
|
});
|
|
34678
34692
|
socket.once("error", (e) => {
|
|
@@ -34681,6 +34695,7 @@ var connectOnce = (host, port, timeoutMs) => {
|
|
|
34681
34695
|
});
|
|
34682
34696
|
});
|
|
34683
34697
|
};
|
|
34698
|
+
var KEEPALIVE_IDLE_MS = 1e4;
|
|
34684
34699
|
async function connect(endpoint, timeoutMs = 3e3) {
|
|
34685
34700
|
const errors = [];
|
|
34686
34701
|
for (const address of endpoint.addresses) {
|
package/dist/runtime/view.js
CHANGED
|
@@ -5594,6 +5594,7 @@ var connectOnce = (host, port, timeoutMs) => {
|
|
|
5594
5594
|
socket.once("connect", () => {
|
|
5595
5595
|
clearTimeout(timer);
|
|
5596
5596
|
socket.setNoDelay(true);
|
|
5597
|
+
socket.setKeepAlive(true, KEEPALIVE_IDLE_MS);
|
|
5597
5598
|
resolve(socket);
|
|
5598
5599
|
});
|
|
5599
5600
|
socket.once("error", (e) => {
|
|
@@ -5602,6 +5603,7 @@ var connectOnce = (host, port, timeoutMs) => {
|
|
|
5602
5603
|
});
|
|
5603
5604
|
});
|
|
5604
5605
|
};
|
|
5606
|
+
var KEEPALIVE_IDLE_MS = 1e4;
|
|
5605
5607
|
async function connect(endpoint, timeoutMs = 3e3) {
|
|
5606
5608
|
const errors = [];
|
|
5607
5609
|
for (const address of endpoint.addresses) {
|
|
@@ -629,12 +629,18 @@ export const appSuite = {
|
|
|
629
629
|
const dataSample = dataPage(stored);
|
|
630
630
|
const dataSectionIds = dataSample.sections.map((s) => s.id);
|
|
631
631
|
const dataOps = pageActions(dataSample);
|
|
632
|
-
const
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
(
|
|
637
|
-
|
|
632
|
+
const keptSection = dataSample.sections.find((s) => s.id === 'kept');
|
|
633
|
+
const keptItems = keptSection?.items ?? [];
|
|
634
|
+
const accountsItem = keptItems.find((i) => i.label === 'accounts');
|
|
635
|
+
t.check(dataSectionIds.join() === 'disks,kept' &&
|
|
636
|
+
keptItems.map((i) => i.label).join() === 'rust,state/claude,accounts' &&
|
|
637
|
+
keptSection?.summary === '6.5 GB in all' &&
|
|
638
|
+
keptItems[0]?.primary?.kind === 'operation' &&
|
|
639
|
+
keptItems[0].primary.op === 'data.clean' &&
|
|
640
|
+
JSON.stringify(keptItems[0].primary.input) === '{"targets":["tools/rust"]}' &&
|
|
641
|
+
accountsItem?.primary === undefined &&
|
|
642
|
+
(keptSection?.actions ?? []).some((a) => a.kind === 'operation' && a.op === 'data.prune') &&
|
|
643
|
+
dataOps.some((a) => a.kind === 'operation' && a.op === 'data.clean'), 'data: disks, then one table of every kept folder (total in the summary); delete only where it may be deleted', dataSectionIds.join());
|
|
638
644
|
const shaped = showData.cli.shapeJson(stored);
|
|
639
645
|
const shapedEntries = shaped.entries;
|
|
640
646
|
const shapedDisks = shaped.disks;
|
|
@@ -644,6 +650,10 @@ export const appSuite = {
|
|
|
644
650
|
const dataLines = pageLines(dataSample);
|
|
645
651
|
t.check(dataLines.some((l) => l.startsWith('Disks')) &&
|
|
646
652
|
dataLines.some((l) => /^DRIVE\s+USED\s+FREE\s+SIZE/.test(l)) &&
|
|
653
|
+
dataLines.some((l) => l.startsWith('What the sandbox keeps 6.5 GB in all')) &&
|
|
654
|
+
dataLines.some((l) => /^KIND\s+NAME\s+SIZE\s+FOLDER/.test(l)) &&
|
|
655
|
+
dataLines.some((l) => /^Toolchains\s+rust\s+4\.6 GB\s+C:\\AgentData\\tools\\rust/.test(l)) &&
|
|
656
|
+
dataLines.some((l) => l.startsWith('Kept on this PC on purpose, not deleted here: accounts.')) &&
|
|
647
657
|
dataLines.some((l) => l.startsWith('Persistent data for the sandbox: ')) &&
|
|
648
658
|
dataLines.some((l) => l.startsWith('Free space with:')), 'data on the command line: the disks table, the data folder line and the free space line', dataLines.join('\n'));
|
|
649
659
|
t.check(definitionFor('data', ['clean'])?.id === 'data.clean' &&
|
|
@@ -837,10 +847,12 @@ export const appSuite = {
|
|
|
837
847
|
mountedNow: null,
|
|
838
848
|
};
|
|
839
849
|
const storagePageNow = storagePage(shownStorage, shownContained);
|
|
840
|
-
const
|
|
850
|
+
const folderSection = storagePageNow.sections[0];
|
|
851
|
+
const folderOps = (folderSection?.actions ?? []).map((a) => (a.kind === 'operation' ? a.op : a.kind));
|
|
841
852
|
const containedActions = storagePageNow.sections[1]?.actions ?? [];
|
|
842
|
-
t.check(
|
|
843
|
-
|
|
853
|
+
t.check(folderOps.join() === 'storage.move,storage.use' &&
|
|
854
|
+
(folderSection?.facts ?? []).map((f) => f.label).join() ===
|
|
855
|
+
'Folder,Size,Default folder,Other folder with data' &&
|
|
844
856
|
containedActions.length === 1 &&
|
|
845
857
|
containedActions[0].kind === 'operation' &&
|
|
846
858
|
containedActions[0].op === 'storage.containedOn', 'the storage page: the data folder row changes it, and contained storage offers to turn it on while it is off');
|
|
@@ -524,6 +524,41 @@ async function sharedCore(t) {
|
|
|
524
524
|
t.equal(keys, ['f', 'l'], 'the health keys are f and l (e only when Windows Sandbox can be turned on)');
|
|
525
525
|
const canEnable = healthPage({ last: { ...saved, setup: { ...saved.setup, canEnable: true } } });
|
|
526
526
|
t.equal(menuView(canEnable, { category: 'health', refresh: false }).actions.map((a) => a.key), ['f', 'l', 'e'], 'e turns Windows Sandbox on when the setup says it can be');
|
|
527
|
+
const problems = healthPage({
|
|
528
|
+
last: {
|
|
529
|
+
...saved,
|
|
530
|
+
mode: 'repair',
|
|
531
|
+
checks: [
|
|
532
|
+
{
|
|
533
|
+
id: 'stale',
|
|
534
|
+
group: 'Data folder',
|
|
535
|
+
name: 'Stale run folders',
|
|
536
|
+
status: 'needs-you',
|
|
537
|
+
detail: 'two folders from a sandbox that did not close',
|
|
538
|
+
means: 'they take space',
|
|
539
|
+
fix: 'stop the sandbox',
|
|
540
|
+
lines: ['run-20261012-0931'],
|
|
541
|
+
fixable: false,
|
|
542
|
+
},
|
|
543
|
+
{
|
|
544
|
+
id: 'net',
|
|
545
|
+
group: 'Network',
|
|
546
|
+
name: 'GitHub',
|
|
547
|
+
status: 'warn',
|
|
548
|
+
detail: 'no answer',
|
|
549
|
+
fix: 'check your VPN',
|
|
550
|
+
fixable: false,
|
|
551
|
+
},
|
|
552
|
+
],
|
|
553
|
+
},
|
|
554
|
+
});
|
|
555
|
+
const repairItems = problems.sections.find((s) => s.id === 'repair')?.items ?? [];
|
|
556
|
+
const stale = repairItems[0];
|
|
557
|
+
t.equal(stale?.details, ['Found: two folders from a sandbox that did not close', 'Fix: stop the sandbox', 'run-20261012-0931'], 'a repair row lists what was found as a Found: line, then the fix and the paths (ids stay plain)');
|
|
558
|
+
t.equal(stale?.description, 'they take space', 'a repair row with a meaning shows it as the description');
|
|
559
|
+
const net = repairItems[1];
|
|
560
|
+
t.equal(net?.details, ['Found: no answer', 'Fix: check your VPN'], 'a repair row without a meaning still says Found:');
|
|
561
|
+
t.equal(net?.description, undefined, 'without a meaning, the found text is not repeated as the description');
|
|
527
562
|
}
|
|
528
563
|
export const healthSuite = {
|
|
529
564
|
name: 'health',
|
|
@@ -90,6 +90,36 @@ export const liveTerminalSuite = {
|
|
|
90
90
|
t.check(!errors, 'ptyhost and the stand-in agent compile', errors);
|
|
91
91
|
if (errors)
|
|
92
92
|
return;
|
|
93
|
+
t.section('what a program leaves running ends with it (no leftover processes)');
|
|
94
|
+
{
|
|
95
|
+
// A copy of PING with a name of its own, started detached and hidden (its own console, so closing the pseudo
|
|
96
|
+
// console does not end it), by a program that then exits.
|
|
97
|
+
const probe = t.at('leftover-probe.exe');
|
|
98
|
+
fs.copyFileSync(path.join(process.env.SystemRoot ?? 'C:\\Windows', 'System32', 'PING.EXE'), probe);
|
|
99
|
+
const starter = '"' +
|
|
100
|
+
SYSTEM.powershell() +
|
|
101
|
+
'" -NoProfile -Command "Start-Process -WindowStyle Hidden -FilePath \'' +
|
|
102
|
+
probe +
|
|
103
|
+
"' -ArgumentList '-n','120','127.0.0.1'; Start-Sleep -Seconds 5\"";
|
|
104
|
+
const host = spawn(ptyhost, [String(await freePort()), 'tok', '80', '24', t.at('leftover.log'), starter], {
|
|
105
|
+
env: { ...process.env, PTYHOST_BIND: 'loopback' },
|
|
106
|
+
stdio: 'ignore',
|
|
107
|
+
windowsHide: true,
|
|
108
|
+
});
|
|
109
|
+
const probeRunning = () => spawnSync(SYSTEM.tasklist(), ['/FI', 'IMAGENAME eq leftover-probe.exe', '/NH'], { encoding: 'utf8' })
|
|
110
|
+
.stdout.toLowerCase()
|
|
111
|
+
.includes('leftover-probe.exe');
|
|
112
|
+
await t.waitFor(probeRunning, WAIT.screen);
|
|
113
|
+
t.check(probeRunning(), 'the program started a background process of its own');
|
|
114
|
+
await t.waitFor(() => host.exitCode !== null, 30000);
|
|
115
|
+
await t.waitFor(() => !probeRunning(), 5000);
|
|
116
|
+
const log = fs.existsSync(t.at('leftover.log')) ? fs.readFileSync(t.at('leftover.log'), 'utf8') : '';
|
|
117
|
+
t.check(!probeRunning() && /ended [1-9]\d* process\(es\) the program left running/.test(log), 'when the program exits, the background process it left running is ended too', log.slice(-400));
|
|
118
|
+
if (probeRunning())
|
|
119
|
+
spawnSync(path.join(process.env.SystemRoot ?? 'C:\\Windows', 'System32', 'taskkill.exe'), ['/F', '/IM', 'leftover-probe.exe'], {
|
|
120
|
+
stdio: 'ignore',
|
|
121
|
+
});
|
|
122
|
+
}
|
|
93
123
|
t.section("the agents' job: a last-resort backstop, set once");
|
|
94
124
|
{
|
|
95
125
|
// A job of the test's own (PTYHOST_JOB_NAME), never the agents' real one.
|
|
@@ -575,6 +575,8 @@ function previewRpc(ctx, state) {
|
|
|
575
575
|
return { message: 'Done. Restart Windows to finish. (preview: nothing was changed)' };
|
|
576
576
|
case 'engine.status':
|
|
577
577
|
return sampleEngines();
|
|
578
|
+
case 'engine.leftovers':
|
|
579
|
+
return { available: true, message: 'No HCS systems of yours are running.', systems: [] };
|
|
578
580
|
case 'logs.list':
|
|
579
581
|
return sampleLogs();
|
|
580
582
|
case 'logs.tail':
|
|
@@ -789,6 +791,7 @@ function previewRpc(ctx, state) {
|
|
|
789
791
|
'logs.list': 'query',
|
|
790
792
|
'logs.tail': 'query',
|
|
791
793
|
'engine.status': 'query',
|
|
794
|
+
'engine.leftovers': 'query',
|
|
792
795
|
};
|
|
793
796
|
return {
|
|
794
797
|
kind: (id) => kinds[id],
|
|
@@ -245,6 +245,39 @@ export const webSuite = {
|
|
|
245
245
|
await new Promise((r) => setTimeout(r, 300));
|
|
246
246
|
t.check(hub.size === 0 && reads === stopped, 'with nobody watching, nothing is read');
|
|
247
247
|
}
|
|
248
|
+
t.section('an unused server ends by itself (no process left behind)');
|
|
249
|
+
{
|
|
250
|
+
const idleServer = async (sandbox, limits) => {
|
|
251
|
+
let ended = false;
|
|
252
|
+
const idle = new WebServer({
|
|
253
|
+
port: 0,
|
|
254
|
+
token: TOKEN,
|
|
255
|
+
backend: {
|
|
256
|
+
info: () => Promise.resolve({
|
|
257
|
+
sandbox,
|
|
258
|
+
owner: null,
|
|
259
|
+
canUse: true,
|
|
260
|
+
harnesses: [],
|
|
261
|
+
defaultHarness: 'claude',
|
|
262
|
+
projects: [],
|
|
263
|
+
}),
|
|
264
|
+
sessions: () => Promise.resolve([]),
|
|
265
|
+
endpoint: () => null,
|
|
266
|
+
},
|
|
267
|
+
staticRoot: t.dir('idle-static-' + sandbox + limits.exit),
|
|
268
|
+
idleExitMs: limits.exit,
|
|
269
|
+
idleNoSandboxMs: limits.quiet,
|
|
270
|
+
onStop: () => (ended = true),
|
|
271
|
+
});
|
|
272
|
+
await idle.start();
|
|
273
|
+
await new Promise((r) => setTimeout(r, 1500));
|
|
274
|
+
await idle.stop();
|
|
275
|
+
return ended;
|
|
276
|
+
};
|
|
277
|
+
t.check(await idleServer('stopped', { exit: 60_000, quiet: 200 }), 'no browser and no sandbox: it ends after the short wait');
|
|
278
|
+
t.check(!(await idleServer('running', { exit: 60_000, quiet: 200 })), 'a sandbox runs: the short wait does not end it');
|
|
279
|
+
t.check(await idleServer('running', { exit: 300, quiet: 200 }), 'no browser for the long wait: it ends even while a sandbox runs');
|
|
280
|
+
}
|
|
248
281
|
t.section('the server');
|
|
249
282
|
const fake = new FakePtyHost();
|
|
250
283
|
await fake.start();
|
|
@@ -4,7 +4,6 @@ export interface Endpoint {
|
|
|
4
4
|
port: number;
|
|
5
5
|
token: string;
|
|
6
6
|
}
|
|
7
|
-
/** Tries every address the sandbox published and returns the first one that answers. */
|
|
8
7
|
export declare function connect(endpoint: Endpoint, timeoutMs?: number): Promise<net.Socket>;
|
|
9
8
|
/** Sends one control frame (e.g. RESTART) without taking over the screen. */
|
|
10
9
|
export declare function sendControl(endpoint: Endpoint, type: number): Promise<void>;
|
|
@@ -10,6 +10,9 @@ const connectOnce = (host, port, timeoutMs) => {
|
|
|
10
10
|
socket.once('connect', () => {
|
|
11
11
|
clearTimeout(timer);
|
|
12
12
|
socket.setNoDelay(true);
|
|
13
|
+
// A sandbox that vanished (closed hard, ended in Task Manager) never closes the connection: keepalive notices
|
|
14
|
+
// within seconds, so an attached terminal or the web's bridge ends instead of hanging on.
|
|
15
|
+
socket.setKeepAlive(true, KEEPALIVE_IDLE_MS);
|
|
13
16
|
resolve(socket);
|
|
14
17
|
});
|
|
15
18
|
socket.once('error', (e) => {
|
|
@@ -19,6 +22,8 @@ const connectOnce = (host, port, timeoutMs) => {
|
|
|
19
22
|
});
|
|
20
23
|
};
|
|
21
24
|
/** Tries every address the sandbox published and returns the first one that answers. */
|
|
25
|
+
/** Idle time before TCP keepalive probes start (the same as ptyhost's own side). */
|
|
26
|
+
const KEEPALIVE_IDLE_MS = 10_000;
|
|
22
27
|
export async function connect(endpoint, timeoutMs = 3000) {
|
|
23
28
|
const errors = [];
|
|
24
29
|
for (const address of endpoint.addresses) {
|
package/dist/sync/watcher.d.ts
CHANGED
|
@@ -23,6 +23,8 @@ export declare class Watcher {
|
|
|
23
23
|
private views;
|
|
24
24
|
private stops;
|
|
25
25
|
private watchdogTimer;
|
|
26
|
+
/** This PC's memory, read by one long-running HostTools instead of a program per sample. */
|
|
27
|
+
private memoryStream;
|
|
26
28
|
private watchdog;
|
|
27
29
|
private watchdogBusy;
|
|
28
30
|
private lastCpu;
|
package/dist/sync/watcher.js
CHANGED
|
@@ -13,7 +13,7 @@ import { readForwards, sandboxAddress, startPortForwards } from '../host/network
|
|
|
13
13
|
import { AuditCollector, readAuditSettings } from '../host/audit/collect.js';
|
|
14
14
|
import { readMarkedProjects, saveDataSize, watchAndMark } from '../host/guard/index.js';
|
|
15
15
|
import { sizeWithin } from '../core/disk.js';
|
|
16
|
-
import { autoRestartAllowed, causeSince, appendHostMemory, cpuPercentBetween, KeepAwake, LifeRecord, readHostMemoryAsync, readLoadSettings, REQUEST_SECONDS, WATCHDOG_START, watchdogStep, saveCrashReport, SessionLedger, } from '../host/life/index.js';
|
|
16
|
+
import { autoRestartAllowed, causeSince, appendHostMemory, cpuPercentBetween, KeepAwake, LifeRecord, HostMemoryStream, readHostMemoryAsync, readLoadSettings, REQUEST_SECONDS, WATCHDOG_START, watchdogStep, saveCrashReport, SessionLedger, } from '../host/life/index.js';
|
|
17
17
|
import { SandboxChannel } from '../host/channel/index.js';
|
|
18
18
|
import { ProjectRegistry } from '../host/projects/registry.js';
|
|
19
19
|
import { ContainedStorage, setNameOf, storageOf } from '../host/storage/index.js';
|
|
@@ -85,6 +85,8 @@ export class Watcher {
|
|
|
85
85
|
views = [];
|
|
86
86
|
stops = [];
|
|
87
87
|
watchdogTimer = null;
|
|
88
|
+
/** This PC's memory, read by one long-running HostTools instead of a program per sample. */
|
|
89
|
+
memoryStream = null;
|
|
88
90
|
watchdog = WATCHDOG_START;
|
|
89
91
|
watchdogBusy = false;
|
|
90
92
|
lastCpu = os.cpus();
|
|
@@ -208,6 +210,8 @@ export class Watcher {
|
|
|
208
210
|
});
|
|
209
211
|
this.stops = [...viewStops, stopForwards, stopMarking];
|
|
210
212
|
// Memory and CPU of this PC: when low, the sandbox is asked to slow its builds down (host/life/watchdog.ts).
|
|
213
|
+
this.memoryStream = new HostMemoryStream(storageRoot, WATCHDOG_MS / 1000);
|
|
214
|
+
this.memoryStream.start();
|
|
211
215
|
this.watchdogTimer = setInterval(() => void this.watchHost(), WATCHDOG_MS);
|
|
212
216
|
this.watchdogTimer.unref();
|
|
213
217
|
// sandbox.keepAwake, and the life record that tells the next launch how this run ended (host/life).
|
|
@@ -220,7 +224,7 @@ export class Watcher {
|
|
|
220
224
|
this.watchdogBusy = true;
|
|
221
225
|
try {
|
|
222
226
|
const settings = readLoadSettings(this.storageRoot, this.dataPath);
|
|
223
|
-
const memory = await readHostMemoryAsync(this.storageRoot);
|
|
227
|
+
const memory = this.memoryStream?.latest() ?? (await readHostMemoryAsync(this.storageRoot));
|
|
224
228
|
const cpus = os.cpus();
|
|
225
229
|
const cpuPercent = cpuPercentBetween(this.lastCpu, cpus);
|
|
226
230
|
this.lastCpu = cpus;
|
|
@@ -324,6 +328,7 @@ export class Watcher {
|
|
|
324
328
|
this.awake?.stop();
|
|
325
329
|
if (this.watchdogTimer)
|
|
326
330
|
clearInterval(this.watchdogTimer);
|
|
331
|
+
this.memoryStream?.stop();
|
|
327
332
|
this.stops.forEach((stop) => stop());
|
|
328
333
|
if (this.auditTimer)
|
|
329
334
|
clearInterval(this.auditTimer);
|
package/dist/web/index.html
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
6
|
<title>Sandbox</title>
|
|
7
7
|
<link rel="icon" href="data:," />
|
|
8
|
-
<link rel="stylesheet" href="/main-
|
|
9
|
-
<script type="module" src="/main-
|
|
8
|
+
<link rel="stylesheet" href="/main-RQFN4WBY.css" />
|
|
9
|
+
<script type="module" src="/main-QL7KHCEE.js"></script>
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
|
12
12
|
<div id="root"></div>
|