windows-development-sandbox 1.23.2 → 1.25.1
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 +5 -3
- package/dist/app/features/data/data.def.d.ts +4 -4
- package/dist/app/features/data/data.run.d.ts +1 -1
- package/dist/app/features/defs.d.ts +161 -6
- package/dist/app/features/defs.js +7 -0
- package/dist/app/features/projects/projects.def.d.ts +1 -1
- package/dist/app/features/projects/projects.run.d.ts +1 -1
- package/dist/app/features/unreal/index.d.ts +2 -0
- package/dist/app/features/unreal/index.js +3 -0
- package/dist/app/features/unreal/unreal.def.d.ts +236 -0
- package/dist/app/features/unreal/unreal.def.js +379 -0
- package/dist/app/features/unreal/unreal.run.d.ts +96 -0
- package/dist/app/features/unreal/unreal.run.js +217 -0
- package/dist/app/registry.js +5 -0
- package/dist/bin/agent-sandbox.js +2 -2
- package/dist/bin/{chunk-7YPEDAJD.js → chunk-3GAXLATK.js} +58 -5
- package/dist/bin/{chunk-USTRSHIE.js → chunk-F4PVBN23.js} +2615 -1515
- package/dist/bin/{chunk-QFSJX2KW.js → chunk-G3TG4ZT7.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 +5 -2
- package/dist/cli/commands/registry.js +3 -0
- package/dist/cli/manifest.d.ts +18 -0
- package/dist/cli/manifest.js +26 -0
- package/dist/cli/menu/coverage-exempt.js +1 -0
- package/dist/cli/menu/pages/sandbox-page.d.ts +3 -0
- package/dist/cli/menu/pages/sandbox-page.js +34 -4
- package/dist/cli/menu/views.js +2 -0
- package/dist/core/config/defaults.js +18 -5
- package/dist/core/detect/toolchains.js +0 -6
- package/dist/core/schema/config.d.ts +10 -9
- package/dist/core/schema/pc-installs.d.ts +3 -1
- package/dist/core/schema/pc-installs.js +10 -1
- package/dist/core/schema/toolchains.d.ts +1 -2
- package/dist/core/schema/toolchains.js +4 -7
- package/dist/guest/accounts/kinds/epic.js +18 -13
- package/dist/guest/agents/agent-files.d.ts +11 -7
- package/dist/guest/agents/agent-files.js +37 -11
- package/dist/guest/agents/local-state.d.ts +2 -1
- package/dist/guest/agents/local-state.js +2 -1
- package/dist/guest/apps/after-install.d.ts +1 -1
- package/dist/guest/apps/after-install.js +6 -1
- package/dist/guest/apps/app-installer.d.ts +1 -0
- package/dist/guest/apps/app-installer.js +13 -3
- package/dist/guest/apps/comet-unpack.js +3 -2
- package/dist/guest/apps/resolvers-gamedev.js +55 -1
- package/dist/guest/boot/boot-console.d.ts +28 -0
- package/dist/guest/boot/boot-console.js +98 -0
- package/dist/guest/boot/boot.d.ts +8 -1
- package/dist/guest/boot/boot.js +80 -43
- package/dist/guest/boot/graph.d.ts +9 -5
- package/dist/guest/boot/graph.js +8 -2
- package/dist/guest/boot/guide.js +24 -0
- package/dist/guest/boot/progress-view.d.ts +116 -0
- package/dist/guest/boot/progress-view.js +456 -0
- package/dist/guest/boot/runner.d.ts +20 -10
- package/dist/guest/boot/runner.js +39 -47
- package/dist/guest/daemon/daemon.js +3 -0
- package/dist/guest/runtime/log-line.d.ts +7 -0
- package/dist/guest/runtime/log-line.js +18 -0
- package/dist/guest/runtime/log.d.ts +17 -4
- package/dist/guest/runtime/log.js +40 -17
- package/dist/guest/runtime/process.d.ts +9 -0
- package/dist/guest/runtime/process.js +69 -8
- package/dist/guest/setup/addon-mcp.d.ts +7 -0
- package/dist/guest/setup/addon-mcp.js +19 -4
- package/dist/guest/setup/browser-gpu.js +14 -11
- package/dist/guest/setup/environment.js +9 -4
- package/dist/guest/setup/headroom.js +4 -0
- package/dist/guest/setup/hooks.d.ts +2 -0
- package/dist/guest/setup/hooks.js +4 -0
- package/dist/guest/setup/path-plan.d.ts +12 -0
- package/dist/guest/setup/path-plan.js +18 -0
- package/dist/guest/toolchains/engines/epic-launcher.d.ts +26 -0
- package/dist/guest/toolchains/engines/epic-launcher.js +127 -0
- package/dist/guest/toolchains/engines/unreal-engines.d.ts +21 -1
- package/dist/guest/toolchains/engines/unreal-engines.js +163 -3
- package/dist/guest/toolchains/engines/unreal-status.d.ts +13 -0
- package/dist/guest/toolchains/engines/unreal-status.js +41 -0
- package/dist/guest/toolchains/native/debuggers.d.ts +6 -0
- package/dist/guest/toolchains/native/debuggers.js +57 -0
- package/dist/guest/toolchains/native/mesa.js +15 -8
- package/dist/guest/toolchains/native/rust-extras.d.ts +7 -0
- package/dist/guest/toolchains/native/rust-extras.js +23 -1
- package/dist/guest/toolchains/native/unreal.d.ts +2 -2
- package/dist/guest/toolchains/native/unreal.js +96 -20
- package/dist/guest/toolchains/native/visual-studio.d.ts +14 -14
- package/dist/guest/toolchains/native/visual-studio.js +145 -174
- package/dist/guest/toolchains/native/vs-persist.d.ts +189 -0
- package/dist/guest/toolchains/native/vs-persist.js +256 -0
- package/dist/guest/toolchains/native/vs-request.d.ts +6 -26
- package/dist/guest/toolchains/native/vs-request.js +16 -50
- package/dist/guest/toolchains/plan.js +23 -15
- package/dist/guest/toolchains/tools/vscode-profile.js +2 -0
- package/dist/host/accounts/kinds/epic.js +11 -5
- package/dist/host/accounts/source.d.ts +2 -0
- package/dist/host/accounts/source.js +1 -0
- package/dist/host/migrate/migrations/drop-vs-install-files.d.ts +5 -0
- package/dist/host/migrate/migrations/drop-vs-install-files.js +34 -0
- package/dist/host/migrate/registry.js +7 -1
- package/dist/host/pc-installs/discover.d.ts +2 -0
- package/dist/host/pc-installs/discover.js +25 -5
- package/dist/host/pc-installs/epic.d.ts +3 -0
- package/dist/host/pc-installs/epic.js +33 -2
- package/dist/host/pc-installs/source.d.ts +1 -1
- package/dist/host/pc-installs/unreal-vault.d.ts +11 -0
- package/dist/host/pc-installs/unreal-vault.js +78 -0
- package/dist/host/pc-installs/unreal.d.ts +36 -0
- package/dist/host/pc-installs/unreal.js +145 -2
- package/dist/host/profile/claude-profile.d.ts +8 -0
- package/dist/host/profile/claude-profile.js +43 -4
- package/dist/host/profile/index.d.ts +1 -1
- package/dist/host/profile/index.js +1 -1
- package/dist/runtime/agent.js +148 -33
- package/dist/runtime/daemon.js +3146 -1546
- package/dist/runtime/db.js +148 -33
- package/dist/runtime/main.js +3982 -2049
- package/dist/runtime/netproxy.js +84 -13
- package/dist/runtime/tool.js +125 -31
- package/dist/runtime/view.js +85 -14
- package/dist/selftest/agent-paths.test.js +18 -1
- package/dist/selftest/agent-settings.test.js +36 -5
- package/dist/selftest/agents-web.test.js +12 -0
- package/dist/selftest/boot-console.test.d.ts +2 -0
- package/dist/selftest/boot-console.test.js +291 -0
- package/dist/selftest/boot-runner.test.js +10 -12
- package/dist/selftest/engines.test.js +602 -3
- package/dist/selftest/guest-boot.test.js +7 -1
- package/dist/selftest/heavy-load.test.js +3 -1
- package/dist/selftest/hostile-input.test.js +11 -1
- package/dist/selftest/index.js +4 -0
- package/dist/selftest/layout.test.js +4 -0
- package/dist/selftest/menu-screens.js +3 -1
- package/dist/selftest/migrate.test.js +21 -0
- package/dist/selftest/mixamo.test.d.ts +2 -0
- package/dist/selftest/mixamo.test.js +60 -0
- package/dist/selftest/persisted-installs.test.js +169 -33
- package/dist/selftest/secrets.test.js +36 -1
- package/dist/selftest/support/engine-golden.js +5 -0
- package/dist/selftest/support/fixtures.d.ts +4 -3
- package/dist/selftest/support/unreal-sample.d.ts +2 -0
- package/dist/selftest/support/unreal-sample.js +83 -0
- package/dist/selftest/toolchains.test.js +32 -2
- package/dist/shared/accounts/index.d.ts +1 -1
- package/dist/shared/accounts/index.js +1 -1
- package/dist/shared/accounts/kinds/epic.d.ts +6 -1
- package/dist/shared/accounts/kinds/epic.js +11 -1
- package/dist/shared/apps/associations.d.ts +7 -0
- package/dist/shared/apps/associations.js +26 -0
- package/dist/shared/apps/catalogue-agents-web.d.ts +4 -1
- package/dist/shared/apps/catalogue-agents-web.js +5 -2
- package/dist/shared/apps/catalogue-gamedev.js +71 -1
- package/dist/shared/apps/index.d.ts +2 -0
- package/dist/shared/apps/index.js +2 -0
- package/dist/shared/apps/preview.d.ts +2 -0
- package/dist/shared/apps/preview.js +10 -0
- package/dist/shared/browser-policy.d.ts +14 -0
- package/dist/shared/browser-policy.js +22 -0
- package/dist/shared/channel/schema.d.ts +3 -6
- package/dist/shared/io/index.d.ts +1 -0
- package/dist/shared/io/index.js +1 -0
- package/dist/shared/io/rotate-log.d.ts +2 -0
- package/dist/shared/io/rotate-log.js +16 -0
- package/dist/shared/memory/policy.d.ts +5 -0
- package/dist/shared/memory/policy.js +7 -0
- package/dist/shared/paths/pc-installs.d.ts +13 -2
- package/dist/shared/paths/pc-installs.js +1 -0
- package/dist/shared/pty/terminal.js +2 -1
- package/dist/shared/readiness.d.ts +0 -1
- package/dist/shared/readiness.js +0 -1
- package/dist/shared/signer.d.ts +4 -0
- package/dist/shared/signer.js +2 -0
- package/dist/shared/unreal/engines.d.ts +161 -0
- package/dist/shared/unreal/engines.js +308 -0
- package/dist/shared/unreal/guide.d.ts +19 -0
- package/dist/shared/unreal/guide.js +105 -0
- package/dist/shared/unreal/index.d.ts +7 -0
- package/dist/shared/unreal/index.js +8 -0
- package/dist/shared/unreal/launcher-files.d.ts +88 -0
- package/dist/shared/unreal/launcher-files.js +292 -0
- package/dist/shared/unreal/mixamo.d.ts +5 -0
- package/dist/shared/unreal/mixamo.js +16 -0
- package/dist/shared/unreal/room.d.ts +17 -0
- package/dist/shared/unreal/room.js +34 -0
- package/dist/shared/unreal/status.d.ts +54 -0
- package/dist/shared/unreal/status.js +57 -0
- package/dist/shared/unreal/vault.d.ts +21 -0
- package/dist/shared/unreal/vault.js +67 -0
- package/dist/shared/unreal/vs-check.d.ts +39 -0
- package/dist/shared/unreal/vs-check.js +79 -0
- package/dist/sync/watcher.js +5 -1
- package/dist/web/index.html +1 -1
- package/dist/web/{main-CCILLX2M.js → main-FNE6XEHA.js} +84 -84
- package/native/csharp/HcsHost/AssemblyInfo.cs +3 -3
- package/native/csharp/HostTools/AssemblyInfo.cs +3 -3
- package/native/csharp/StorageHelper/AssemblyInfo.cs +3 -3
- package/package.json +1 -1
- package/sandbox/csharp/VsSetup.cs +210 -0
- package/sandbox/scripts/get-vs-setup.ps1 +93 -0
- package/sandbox/scripts/restore-visual-studio.ps1 +78 -0
- package/sandbox/scripts/save-visual-studio.ps1 +106 -0
- package/sandbox/scripts/set-registry-values.ps1 +35 -0
- package/schemas/config.schema.json +38 -9
- package/schemas/sandbox-config.schema.json +11 -8
- package/dist/guest/toolchains/native/autosdk.d.ts +0 -3
- package/dist/guest/toolchains/native/autosdk.js +0 -50
|
@@ -622,6 +622,10 @@ export const agentsWebSuite = {
|
|
|
622
622
|
issuer: 'CN=GlobalSign GCC R45 EV CodeSigning CA 2020, O=GlobalSign nv-sa, C=BE',
|
|
623
623
|
};
|
|
624
624
|
t.check(signerMatches(signed, COMET_SIGNER), 'Perplexity AI, Inc. with its GlobalSign EV certificate passes');
|
|
625
|
+
t.check(signerMatches({
|
|
626
|
+
subject: 'E=dmitryp@perplexity.ai, CN="PERPLEXITY AI, INC.", O="PERPLEXITY AI, INC.", L=San Francisco, S=California, C=US, SERIALNUMBER=6909743, OID.2.5.4.15=Private Organization',
|
|
627
|
+
issuer: signed.issuer,
|
|
628
|
+
}, COMET_SIGNER), 'the subject as Windows reports it (E= contact address first) passes: it was refused before');
|
|
625
629
|
t.check(!signerMatches(null, COMET_SIGNER) &&
|
|
626
630
|
!signerMatches({ ...signed, subject: signed.subject.replace('O="PERPLEXITY AI, INC."', 'O=Someone Else') }, COMET_SIGNER) &&
|
|
627
631
|
!signerMatches({ ...signed, subject: 'CN="PERPLEXITY AI, INC. EVIL", O="PERPLEXITY AI, INC. EVIL"' }, COMET_SIGNER) &&
|
|
@@ -639,6 +643,14 @@ export const agentsWebSuite = {
|
|
|
639
643
|
t.check(chromiumBin(stage, 'comet.exe') === null, 'a Chrome-bin without the browser does not count');
|
|
640
644
|
t.write(path.join(stage, 'd', 'Chrome-bin', 'comet.exe'), 'x');
|
|
641
645
|
t.equal(chromiumBin(stage, 'comet.exe'), path.join(stage, 'd', 'Chrome-bin'), 'the browser folder is found');
|
|
646
|
+
const real = t.dir('web-unpack-real');
|
|
647
|
+
const nested = path.join(real, 'bin', 'Offline', '{a8c89918}', '{42e10078}');
|
|
648
|
+
t.write(path.join(nested, 'mini_installer.exe'), 'x');
|
|
649
|
+
t.write(path.join(real, 'updater.7z'), 'x');
|
|
650
|
+
t.check(path.basename(nextInner(real) ?? '') === 'mini_installer.exe', "Comet's current installer (updater.7z, mini_installer.exe five folders down) is opened stage by stage");
|
|
651
|
+
const only = t.dir('web-unpack-first');
|
|
652
|
+
t.write(path.join(only, 'updater.7z'), 'x');
|
|
653
|
+
t.check(path.basename(nextInner(only) ?? '') === 'updater.7z', 'updater.7z is a stage too');
|
|
642
654
|
const appDir = t.dir('web-kept');
|
|
643
655
|
t.check(keptInstaller(appDir) === null, 'no installer kept when it was unpacked');
|
|
644
656
|
t.write(path.join(appDir, 'installer', 'comet_installer.exe'), 'x');
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
// The boot's console (docs/design/parallel-boot.md, "Console output"): a fixture of runner events played into the
|
|
2
|
+
// progress view, read back as the exact plain output (logs, CI, dry runs) and as live frames (a terminal), colours
|
|
3
|
+
// stripped. Also the log file's line format. Pure: a fake clock and a fake screen, no terminal.
|
|
4
|
+
import { plainText, fileLines } from '../guest/runtime/log-line.js';
|
|
5
|
+
import { CONSOLE_GLYPHS, ProgressView, readyLines, RICH_GLYPHS, stepsDetail, wrap, } from '../guest/boot/progress-view.js';
|
|
6
|
+
const STAGES = [
|
|
7
|
+
{ stage: 1, name: 'Network and session host' },
|
|
8
|
+
{ stage: 2, name: 'Pre-provision hooks and desktop' },
|
|
9
|
+
{ stage: 3, name: 'Developer tools' },
|
|
10
|
+
{ stage: 4, name: 'Agent: claude' },
|
|
11
|
+
];
|
|
12
|
+
const TASKS = [
|
|
13
|
+
{ id: 'ptyhost', stage: 1, title: 'Session host' },
|
|
14
|
+
{ id: 'network', stage: 1, title: 'Private-network blocking' },
|
|
15
|
+
{ id: 'desktop', stage: 2, title: 'Personalisation' },
|
|
16
|
+
{ id: 'toolchains', stage: 3, title: 'Developer tools', critical: true },
|
|
17
|
+
{ id: 'dbCommands', stage: 3, title: 'Tool commands' },
|
|
18
|
+
{ id: 'agent', stage: 4, title: 'Agent install', critical: true },
|
|
19
|
+
];
|
|
20
|
+
const task = (id) => TASKS.find((t) => t.id === id);
|
|
21
|
+
const done = (id, ms) => ({ id, title: task(id).title, state: 'done', ms });
|
|
22
|
+
const play = (live, columns = 80) => {
|
|
23
|
+
let now = 0;
|
|
24
|
+
let written = '';
|
|
25
|
+
let taken = 0;
|
|
26
|
+
const view = new ProgressView(STAGES, TASKS, {
|
|
27
|
+
live,
|
|
28
|
+
glyphs: live ? RICH_GLYPHS : CONSOLE_GLYPHS,
|
|
29
|
+
now: () => now,
|
|
30
|
+
screen: { write: (text) => void (written += text), columns: () => columns },
|
|
31
|
+
});
|
|
32
|
+
return {
|
|
33
|
+
view,
|
|
34
|
+
out: () => written,
|
|
35
|
+
take: () => {
|
|
36
|
+
const part = written.slice(taken);
|
|
37
|
+
taken = written.length;
|
|
38
|
+
return part;
|
|
39
|
+
},
|
|
40
|
+
at: (ms) => {
|
|
41
|
+
now = ms;
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
/** The happy boot: overlapping tasks, lines from several stages, a warning, a soft failure, the summary. */
|
|
46
|
+
const happyBoot = (p) => {
|
|
47
|
+
const v = p.view;
|
|
48
|
+
v.start('Agent sandbox · demo · claude · C:\\Work\\demo');
|
|
49
|
+
v.line('Service sign-ins: none', undefined);
|
|
50
|
+
v.started(task('ptyhost'));
|
|
51
|
+
v.started(task('desktop'));
|
|
52
|
+
v.started(task('dbCommands'));
|
|
53
|
+
v.line('Desktop personalisation applied from this PC.', 2);
|
|
54
|
+
p.at(300);
|
|
55
|
+
v.finished(task('ptyhost'), done('ptyhost', 300));
|
|
56
|
+
v.started(task('network'));
|
|
57
|
+
p.at(1200);
|
|
58
|
+
v.finished(task('desktop'), done('desktop', 1200));
|
|
59
|
+
v.line('LAN blocked in the guest firewall.', 1);
|
|
60
|
+
v.warning('gh: the GitHub login was kept in Credential Manager. Log in again with: gh auth login --web');
|
|
61
|
+
p.at(1300);
|
|
62
|
+
v.finished(task('dbCommands'), { ...done('dbCommands', 1300), state: 'failed', error: 'disk full' });
|
|
63
|
+
v.warning('Tool commands failed (continuing): disk full');
|
|
64
|
+
p.at(4600);
|
|
65
|
+
v.finished(task('network'), done('network', 4300));
|
|
66
|
+
v.started(task('toolchains'));
|
|
67
|
+
v.detail('toolchains', stepsDetail(['Node', 'Python', 'Rust', 'Go', 'Java'], 5, 13));
|
|
68
|
+
v.line('Installing the Visual C++ runtime ...', 3);
|
|
69
|
+
p.at(13600);
|
|
70
|
+
v.finished(task('toolchains'), done('toolchains', 9000));
|
|
71
|
+
v.started(task('agent'));
|
|
72
|
+
v.line('claude already cached in persistent storage.', 4);
|
|
73
|
+
p.at(15400);
|
|
74
|
+
v.finished(task('agent'), done('agent', 1800));
|
|
75
|
+
v.summary(['git: 2.43.0.windows.1', 'node: 24.22.0 (from this PC, switch with nvm use)', 'vc++ runtime: installed']);
|
|
76
|
+
v.notice('If Claude asks you to sign in, do it once; the login is saved in persistent storage.');
|
|
77
|
+
v.release({
|
|
78
|
+
ms: 15400,
|
|
79
|
+
outcomes: [done('ptyhost', 300), done('network', 4300), done('toolchains', 9000), done('agent', 1800)],
|
|
80
|
+
background: ['Visual Studio', 'Unreal'],
|
|
81
|
+
logPath: 'C:\\AgentData\\logs\\guest.log',
|
|
82
|
+
});
|
|
83
|
+
};
|
|
84
|
+
const PLAIN_HAPPY = [
|
|
85
|
+
'Agent sandbox · demo · claude · C:\\Work\\demo',
|
|
86
|
+
'',
|
|
87
|
+
'Service sign-ins: none',
|
|
88
|
+
'[1/4] Network and session host 4.6s',
|
|
89
|
+
' LAN blocked in the guest firewall.',
|
|
90
|
+
'[2/4] Pre-provision hooks and desktop 1.2s',
|
|
91
|
+
' Desktop personalisation applied from this PC.',
|
|
92
|
+
'[3/4] Developer tools 13.6s',
|
|
93
|
+
' Installing the Visual C++ runtime ...',
|
|
94
|
+
'[4/4] Agent: claude 1.8s',
|
|
95
|
+
' claude already cached in persistent storage.',
|
|
96
|
+
'',
|
|
97
|
+
'Warnings',
|
|
98
|
+
' ! gh: the GitHub login was kept in Credential Manager. Log in again with: gh auth login --web',
|
|
99
|
+
' ! Tool commands failed (continuing): disk full',
|
|
100
|
+
'',
|
|
101
|
+
'Tools',
|
|
102
|
+
' git 2.43.0.windows.1',
|
|
103
|
+
' node 24.22.0 (from this PC, switch with nvm use)',
|
|
104
|
+
' vc++ runtime installed',
|
|
105
|
+
'',
|
|
106
|
+
'If Claude asks you to sign in, do it once; the login is saved in persistent storage.',
|
|
107
|
+
'',
|
|
108
|
+
'Ready in 15.4s (slowest: Developer tools 9.0s, Private-network blocking 4.3s, Agent install 1.8s)',
|
|
109
|
+
'Still installing in the background: Visual Studio, Unreal (a few minutes; it does not hold you up)',
|
|
110
|
+
'Log: C:\\AgentData\\logs\\guest.log',
|
|
111
|
+
'',
|
|
112
|
+
].join('\n');
|
|
113
|
+
export const bootConsoleSuite = {
|
|
114
|
+
name: 'boot-console',
|
|
115
|
+
title: "the boot's console: plain ordered output, live stage rows, warnings, tool summary, log file lines",
|
|
116
|
+
run: (t) => {
|
|
117
|
+
t.section('plain output (logs, CI, dry runs)');
|
|
118
|
+
{
|
|
119
|
+
const p = play(false);
|
|
120
|
+
happyBoot(p);
|
|
121
|
+
t.equal(p.out().split('\n'), PLAIN_HAPPY.split('\n'), 'a boot prints its stages in order, each once finished');
|
|
122
|
+
t.check(!p.out().includes('\x1b'), 'plain output has no colour or cursor codes');
|
|
123
|
+
}
|
|
124
|
+
{
|
|
125
|
+
// Stage 1 finishes last: stages 2 and 3 wait for it, then all three come out in order.
|
|
126
|
+
const p = play(false);
|
|
127
|
+
const v = p.view;
|
|
128
|
+
v.start('header');
|
|
129
|
+
p.take();
|
|
130
|
+
v.started(task('ptyhost'));
|
|
131
|
+
v.started(task('desktop'));
|
|
132
|
+
v.finished(task('desktop'), done('desktop', 0));
|
|
133
|
+
const early = p.take();
|
|
134
|
+
v.finished(task('ptyhost'), done('ptyhost', 0));
|
|
135
|
+
v.started(task('network'));
|
|
136
|
+
v.finished(task('network'), done('network', 0));
|
|
137
|
+
t.check(early === '' && /^\[1\/4\][^\n]*\n\[2\/4\]/.test(p.take()), 'a finished stage waits until the stages before it are printed', early);
|
|
138
|
+
}
|
|
139
|
+
{
|
|
140
|
+
const p = play(false);
|
|
141
|
+
const v = p.view;
|
|
142
|
+
v.start('header');
|
|
143
|
+
v.started(task('ptyhost'));
|
|
144
|
+
v.finished(task('ptyhost'), done('ptyhost', 100));
|
|
145
|
+
v.started(task('network'));
|
|
146
|
+
v.finished(task('network'), done('network', 100));
|
|
147
|
+
v.started(task('desktop'));
|
|
148
|
+
v.finished(task('desktop'), done('desktop', 100));
|
|
149
|
+
v.started(task('dbCommands'));
|
|
150
|
+
v.started(task('toolchains'));
|
|
151
|
+
p.at(2000);
|
|
152
|
+
v.finished(task('toolchains'), { ...done('toolchains', 2000), state: 'failed', error: 'no network' });
|
|
153
|
+
v.finished(task('dbCommands'), done('dbCommands', 2000));
|
|
154
|
+
v.finished(task('agent'), { ...done('agent', 0), state: 'skipped', error: 'Developer tools failed' });
|
|
155
|
+
p.take();
|
|
156
|
+
v.release(null);
|
|
157
|
+
t.equal(p.out().split('\n').slice(4, 8), [
|
|
158
|
+
'[3/4] Developer tools 2.0s FAILED',
|
|
159
|
+
' x Developer tools failed: no network',
|
|
160
|
+
'[4/4] Agent: claude skipped',
|
|
161
|
+
' - Agent install skipped (Developer tools failed)',
|
|
162
|
+
], 'a critical failure marks its stage FAILED and says why; what comes after it is skipped');
|
|
163
|
+
}
|
|
164
|
+
{
|
|
165
|
+
const p = play(false);
|
|
166
|
+
p.view.start('header');
|
|
167
|
+
p.view.started(task('ptyhost'));
|
|
168
|
+
p.view.line('half way', 1);
|
|
169
|
+
p.take();
|
|
170
|
+
p.view.release(null);
|
|
171
|
+
t.equal(p.take().split('\n'), ['[1/4] Network and session host 0.0s not finished', ' half way', ''], 'when the boot fails, a stage that had started is printed with what it logged');
|
|
172
|
+
}
|
|
173
|
+
t.section('live frames (a terminal)');
|
|
174
|
+
{
|
|
175
|
+
const p = play(true);
|
|
176
|
+
const v = p.view;
|
|
177
|
+
v.start('Agent sandbox · demo · claude · C:\\Work\\demo');
|
|
178
|
+
t.equal(v.frame(false), [
|
|
179
|
+
' · 1 Network and session host waiting',
|
|
180
|
+
' · 2 Pre-provision hooks and desktop waiting',
|
|
181
|
+
' · 3 Developer tools waiting',
|
|
182
|
+
' · 4 Agent: claude waiting',
|
|
183
|
+
], 'before any task: every stage waits');
|
|
184
|
+
v.started(task('ptyhost'));
|
|
185
|
+
v.started(task('desktop'));
|
|
186
|
+
v.started(task('dbCommands'));
|
|
187
|
+
p.at(300);
|
|
188
|
+
v.finished(task('ptyhost'), done('ptyhost', 300));
|
|
189
|
+
v.started(task('network'));
|
|
190
|
+
p.at(1200);
|
|
191
|
+
v.finished(task('desktop'), done('desktop', 1200));
|
|
192
|
+
v.finished(task('dbCommands'), done('dbCommands', 1200));
|
|
193
|
+
v.started(task('toolchains'));
|
|
194
|
+
v.detail('toolchains', stepsDetail(['Node', 'Python', 'Rust', 'Go', 'Java'], 5, 13));
|
|
195
|
+
v.warning('gh: the GitHub login was kept in Credential Manager. Log in again with: gh auth login --web');
|
|
196
|
+
p.at(12340);
|
|
197
|
+
t.equal(v.frame(false), [
|
|
198
|
+
// 12340 ms: the spinner's fifth frame.
|
|
199
|
+
' ⠼ 1 Network and session host 12.3s Private-network blocking',
|
|
200
|
+
' ✓ 2 Pre-provision hooks and desktop 1.2s',
|
|
201
|
+
' ⠼ 3 Developer tools 12.3s Node, Python, Rust +2 (5/13)',
|
|
202
|
+
' · 4 Agent: claude waiting',
|
|
203
|
+
'',
|
|
204
|
+
' ! gh: the GitHub login was kept in Credential Manager. Log in again with: gh…',
|
|
205
|
+
], 'while tasks run: their stage spins and names them (the toolchain steps), done stages show their time');
|
|
206
|
+
t.check(v.frame(false).every((l) => l.length <= 79) && plainText(v.frame().join('\n')) === v.frame(false).join('\n'), 'every row fits in the terminal (one column spare); the colours are only colours');
|
|
207
|
+
const narrow = play(true, 53);
|
|
208
|
+
narrow.view.started(task('toolchains'));
|
|
209
|
+
narrow.view.detail('toolchains', stepsDetail(['Node', 'Python'], 1, 9));
|
|
210
|
+
t.equal(narrow.view.frame(false)[2], ' ⠋ 3 Developer tools 0.0s Nod…', 'a narrow terminal cuts the row, not wraps it');
|
|
211
|
+
p.take();
|
|
212
|
+
v.tick();
|
|
213
|
+
const redraw = p.take();
|
|
214
|
+
// The first frame (at start) had the 4 rows; this one has the rows, a blank line and the warning.
|
|
215
|
+
t.check(redraw.startsWith('\r\x1b[4A') && redraw.endsWith('\x1b[J') && redraw.split('\n').length === 7, 'a redraw moves up over the block it drew last and draws the new one in place', JSON.stringify(redraw.slice(0, 40)));
|
|
216
|
+
v.tick();
|
|
217
|
+
t.check(p.take() === '', 'nothing is drawn when nothing changed');
|
|
218
|
+
const before = v.capturing;
|
|
219
|
+
let inside = true;
|
|
220
|
+
const printed = v.aside(() => {
|
|
221
|
+
inside = v.capturing;
|
|
222
|
+
v.line('Hook [pre-agent]: 90-games.cmd', 2);
|
|
223
|
+
return 7;
|
|
224
|
+
});
|
|
225
|
+
const asideText = p.take();
|
|
226
|
+
t.check(printed === 7 &&
|
|
227
|
+
asideText.startsWith('\r\x1b[6A\x1b[J') &&
|
|
228
|
+
asideText.includes('Hook [pre-agent]: 90-games.cmd\n') &&
|
|
229
|
+
asideText.endsWith('\x1b[J'), 'a hook gets the terminal: the block steps aside, the hook prints, the block comes back below it', JSON.stringify(asideText.slice(0, 60)));
|
|
230
|
+
t.check(before && !inside && v.capturing, "programs' output goes to the log while the block owns the terminal, not while a hook has it");
|
|
231
|
+
v.finished(task('network'), done('network', 12040));
|
|
232
|
+
v.finished(task('toolchains'), done('toolchains', 11140));
|
|
233
|
+
v.started(task('agent'));
|
|
234
|
+
p.at(14000);
|
|
235
|
+
v.finished(task('agent'), done('agent', 1660));
|
|
236
|
+
v.summary(['git: 2.43.0.windows.1', 'PROTECTION MISSING: the LAN firewall is off']);
|
|
237
|
+
p.take();
|
|
238
|
+
v.release({
|
|
239
|
+
ms: 14000,
|
|
240
|
+
outcomes: [done('network', 12040), done('toolchains', 11140), done('agent', 1660)],
|
|
241
|
+
background: [],
|
|
242
|
+
logPath: 'C:\\AgentData\\logs\\guest.log',
|
|
243
|
+
});
|
|
244
|
+
const final = p.take();
|
|
245
|
+
t.equal(plainText(final).replace(/\r/g, '').split('\n'), [
|
|
246
|
+
' ✓ 1 Network and session host 12.3s',
|
|
247
|
+
' ✓ 2 Pre-provision hooks and desktop 1.2s',
|
|
248
|
+
' ✓ 3 Developer tools 12.3s',
|
|
249
|
+
' ✓ 4 Agent: claude 1.7s',
|
|
250
|
+
'',
|
|
251
|
+
'Warnings',
|
|
252
|
+
' ! gh: the GitHub login was kept in Credential Manager. Log in again with: gh',
|
|
253
|
+
' auth login --web',
|
|
254
|
+
'',
|
|
255
|
+
'Tools',
|
|
256
|
+
' git 2.43.0.windows.1',
|
|
257
|
+
' PROTECTION MISSING the LAN firewall is off',
|
|
258
|
+
'',
|
|
259
|
+
'Ready in 14.0s (slowest: Private-network blocking 12.0s, Developer tools 11.1s, Agent install 1.7s)',
|
|
260
|
+
'Log: C:\\AgentData\\logs\\guest.log',
|
|
261
|
+
'',
|
|
262
|
+
], 'when ready: the block stays as a still list, then the warnings in full, the tools and the ready line');
|
|
263
|
+
t.check(final.startsWith('\r\x1b[') && final.endsWith('\x1b[?25h') && !v.capturing, 'release erases the live block, shows the cursor again and gives the console back');
|
|
264
|
+
v.line('late line', 3);
|
|
265
|
+
v.warning('late warning');
|
|
266
|
+
v.tick();
|
|
267
|
+
t.check(p.take() === '', 'nothing prints after the release (the first session owns the console)');
|
|
268
|
+
}
|
|
269
|
+
{
|
|
270
|
+
const p = play(true);
|
|
271
|
+
p.view.start('x');
|
|
272
|
+
p.view.started(task('ptyhost'));
|
|
273
|
+
p.view.line('an info line', 1);
|
|
274
|
+
t.check(!plainText(p.out()).includes('an info line') && p.view.capturing, 'live: info lines go to the log file only, and programs hand their output to the log');
|
|
275
|
+
}
|
|
276
|
+
t.section('pieces');
|
|
277
|
+
t.equal(stepsDetail([], 0, 0), 'Developer tools', 'no step running yet: the stage says what it does');
|
|
278
|
+
t.equal(readyLines({ ms: 900, outcomes: [done('ptyhost', 900)], background: ['A', 'B', 'C', 'D', 'E'], logPath: 'l' }), [
|
|
279
|
+
'Ready in 0.9s',
|
|
280
|
+
'Still installing in the background: A, B, C and 2 more (a few minutes; it does not hold you up)',
|
|
281
|
+
], 'the ready line names no task under a second; a long background list is shortened');
|
|
282
|
+
t.equal(wrap('aa bb cc dd', 5), ['aa bb', 'cc dd'], 'wrap breaks between words');
|
|
283
|
+
t.equal(wrap('abcdefghij k', 4), ['abcd', 'efgh', 'ij k'], 'wrap cuts a word longer than the width');
|
|
284
|
+
t.section('log file lines');
|
|
285
|
+
const at = new Date(Date.UTC(2026, 10, 4, 2, 7, 5, 9));
|
|
286
|
+
t.equal(fileLines('hello', at), ['2026-11-04T02:07:05.009Z hello'], 'a file line starts with a zero-padded UTC time');
|
|
287
|
+
t.equal(fileLines('\n\x1b[36m[1/9] Network\x1b[0m\r\nsecond ', at, 'Developer tools'), ['2026-11-04T02:07:05.009Z [Developer tools] [1/9] Network', '2026-11-04T02:07:05.009Z [Developer tools] second'], 'colours are stripped, each line gets its own time and task, blank lines are dropped');
|
|
288
|
+
t.equal(fileLines('\n \n', at), [], 'a blank message writes nothing');
|
|
289
|
+
t.equal(plainText('\x1b[2K\x1b[?25l\x1b[1;33mx\x1b[0m'), 'x', 'cursor and colour codes are removed');
|
|
290
|
+
},
|
|
291
|
+
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// The parallel boot (docs/design/parallel-boot.md): the task runner, the pools, the boot graph and the toolchain
|
|
2
2
|
// scheduler's download phase and background split. Fake tasks and deferred promises only: no machine state.
|
|
3
|
-
import { BootRunner,
|
|
3
|
+
import { BootRunner, checkGraph, duration } from '../guest/boot/runner.js';
|
|
4
4
|
import { BOOT_TASKS, STAGES, stageTitle } from '../guest/boot/graph.js';
|
|
5
5
|
import { backgroundNames, mergeParts, runSplit } from '../guest/toolchains/split.js';
|
|
6
6
|
import { StepScheduler } from '../guest/toolchains/step-scheduler.js';
|
|
@@ -31,13 +31,19 @@ const def = (id, after = [], more = {}) => ({
|
|
|
31
31
|
const fixture = () => {
|
|
32
32
|
const lines = [];
|
|
33
33
|
const warnings = [];
|
|
34
|
+
const events = [];
|
|
34
35
|
const contexts = [];
|
|
35
36
|
return {
|
|
36
37
|
lines,
|
|
37
38
|
warnings,
|
|
39
|
+
events,
|
|
38
40
|
contexts,
|
|
39
41
|
options: (extra = {}) => ({
|
|
40
|
-
|
|
42
|
+
trace: (line) => void lines.push(line),
|
|
43
|
+
observer: {
|
|
44
|
+
started: (task) => void events.push('started ' + task.id),
|
|
45
|
+
finished: (task, outcome) => void events.push('finished ' + task.id + ' ' + outcome.state),
|
|
46
|
+
},
|
|
41
47
|
warn: (line) => void warnings.push(line),
|
|
42
48
|
now: () => Date.now(),
|
|
43
49
|
dryRun: false,
|
|
@@ -243,7 +249,7 @@ export const bootRunnerSuite = {
|
|
|
243
249
|
const quietBefore = f.contexts.map((c) => c.quiet).join(',');
|
|
244
250
|
await runner.run('background');
|
|
245
251
|
t.check(before === 'a' && quietBefore === 'false' && ran.join(',') === 'a,bg' && f.contexts[1]?.quiet === true, 'a background task starts only on run("background"), in a quiet (file-only) log context', before + ' / ' + quietBefore);
|
|
246
|
-
t.check(!f.
|
|
252
|
+
t.check(!f.events.some((e) => e.includes(' bg')) && f.events.join(',') === 'started a,finished a done', 'the progress view hears only the foreground tasks (the background ones are file-only)', f.events.join(','));
|
|
247
253
|
}
|
|
248
254
|
t.section('runner: graph checks');
|
|
249
255
|
t.check(throws(() => checkGraph([def('a', ['b']), def('b', ['a'])])).includes('circle'), 'a cycle is refused');
|
|
@@ -347,16 +353,8 @@ export const bootRunnerSuite = {
|
|
|
347
353
|
[1, 2, 3, 4, 5, 6, 7, 8, 9].every((n) => stageTitle(n).length > 0) &&
|
|
348
354
|
stageTitle(4, { vpnServer: 'x' }).includes('x') &&
|
|
349
355
|
stageTitle(4) === 'VPN (off)', 'there are nine stage titles, the VPN one names the server or says off');
|
|
350
|
-
const summary = bootSummary(31800, [
|
|
351
|
-
{ id: 'a', title: 'Headroom', state: 'done', ms: 28100 },
|
|
352
|
-
{ id: 'b', title: 'Developer tools', state: 'done', ms: 7000 },
|
|
353
|
-
{ id: 'c', title: 'Skipped one', state: 'skipped', ms: 0 },
|
|
354
|
-
], ['Visual Studio'], 'guest.log');
|
|
355
|
-
t.check(summary.startsWith('Boot ready in 31.8s.') &&
|
|
356
|
-
summary.includes('Headroom 28.1s') &&
|
|
357
|
-
!summary.includes('Skipped one') &&
|
|
358
|
-
summary.includes('In the background: Visual Studio'), 'the boot summary names the time, the slowest tasks and the background work', summary);
|
|
359
356
|
}
|
|
357
|
+
t.equal([duration(0), duration(4630), duration(59949), duration(60000), duration(422400)], ['0.0s', '4.6s', '59.9s', '1m00s', '7m02s'], 'durations: tenths of a second under a minute, then minutes and seconds');
|
|
360
358
|
t.section('toolchain scheduler');
|
|
361
359
|
{
|
|
362
360
|
const events = [];
|