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
|
@@ -63,10 +63,17 @@ export function bootResetPaths(agents, stateDir) {
|
|
|
63
63
|
}
|
|
64
64
|
/** The marker the sandbox puts above the lines it writes into config.toml. */
|
|
65
65
|
const CODEX_MARKER = '# Set by the sandbox';
|
|
66
|
+
/** The full marker line: what it is and where to change it. */
|
|
67
|
+
const CODEX_MARKER_LINE = CODEX_MARKER + ' from its codex settings (`sandbox config`); a line of your own without it wins.';
|
|
68
|
+
/** Markers whose lines count as the sandbox's: CODEX_MARKER, and a hand-made fix's (`# agent-sandbox:`). */
|
|
69
|
+
const CODEX_MARKERS = [CODEX_MARKER, '# agent-sandbox:'];
|
|
70
|
+
/** The top-level keys the sandbox writes under its marker. */
|
|
71
|
+
const CODEX_OWNED_KEYS = ['sandbox_mode', 'approval_policy', 'model', 'model_reasoning_effort'];
|
|
66
72
|
/**
|
|
67
|
-
* The flags every Codex start gets
|
|
68
|
-
*
|
|
69
|
-
* setup for
|
|
73
|
+
* The flags every Codex start through the sandbox gets (the codex.cmd shim, the daemon's sessions). Flags beat
|
|
74
|
+
* config.toml and a resumed thread's saved permissions; config.toml carries the same settings for a start without
|
|
75
|
+
* them (codexConfigOwned). Full access with no questions is the one flag that skips Codex's Windows sandbox setup for
|
|
76
|
+
* certain. Pure.
|
|
70
77
|
*/
|
|
71
78
|
export function codexArguments(c) {
|
|
72
79
|
const out = c.sandboxMode === 'danger-full-access' && c.approvalPolicy === 'never'
|
|
@@ -85,10 +92,13 @@ export function agentArguments(name, harness, codex) {
|
|
|
85
92
|
/** The largest config.toml the sandbox edits. */
|
|
86
93
|
export const MAX_CODEX_CONFIG_BYTES = 1024 * 1024;
|
|
87
94
|
/**
|
|
88
|
-
* Codex's config.toml as the sandbox wants it, every other line byte for byte
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
95
|
+
* Codex's config.toml as the sandbox wants it, every other line byte for byte. Above the first table, under the
|
|
96
|
+
* marker comment: sandbox_mode, approval_policy (and model, model_reasoning_effort when set), so ANY start of Codex
|
|
97
|
+
* obeys the codex settings, also one without the shim's flags (a copy installed with npm -g). The marked lines are
|
|
98
|
+
* replaced each time; a key the user set on a line of their own above the first table is left to them.
|
|
99
|
+
* `[windows] sandbox` follows windowsSandbox ("off" removes the line, which Codex adds back after its own setup, so it
|
|
100
|
+
* does not set up its own sandbox users), and mcp_oauth_credentials_store = "file" is added when it is not set.
|
|
101
|
+
* Keeps the file's line endings; running it again changes nothing. Pure.
|
|
92
102
|
*/
|
|
93
103
|
export function codexConfigOwned(toml, c) {
|
|
94
104
|
const eol = toml.includes('\r\n') ? '\r\n' : '\n';
|
|
@@ -96,15 +106,31 @@ export function codexConfigOwned(toml, c) {
|
|
|
96
106
|
let top = firstTable < 0 ? toml : toml.slice(0, firstTable);
|
|
97
107
|
const tables = firstTable < 0 ? '' : toml.slice(firstTable);
|
|
98
108
|
top = top.replace(MARKED_LINES, '');
|
|
99
|
-
if (c.mcpLoginsInFile &&
|
|
109
|
+
if (c.mcpLoginsInFile && !hasKey(top, 'mcp_oauth_credentials_store')) {
|
|
100
110
|
if (top && !top.endsWith('\n'))
|
|
101
111
|
top += eol;
|
|
102
112
|
top += 'mcp_oauth_credentials_store = "file"' + eol + (tables ? eol : '');
|
|
103
113
|
}
|
|
104
|
-
|
|
114
|
+
const values = {
|
|
115
|
+
sandbox_mode: c.sandboxMode,
|
|
116
|
+
approval_policy: c.approvalPolicy,
|
|
117
|
+
model: c.model,
|
|
118
|
+
model_reasoning_effort: c.reasoningEffort === 'default' ? '' : c.reasoningEffort,
|
|
119
|
+
};
|
|
120
|
+
const lines = CODEX_OWNED_KEYS.filter((k) => values[k] && !hasKey(top, k)).map((k) => k + ' = ' + JSON.stringify(values[k]));
|
|
121
|
+
const marked = lines.length ? [CODEX_MARKER_LINE, ...lines].join(eol) + eol + (top || tables ? eol : '') : '';
|
|
122
|
+
return marked + top + codexWindowsSandbox(tables, c.windowsSandbox, eol);
|
|
123
|
+
}
|
|
124
|
+
/** True when the top of config.toml sets the key on a line of its own. */
|
|
125
|
+
function hasKey(top, key) {
|
|
126
|
+
return new RegExp('^[ \t]*' + key + '[ \t]*=', 'm').test(top);
|
|
105
127
|
}
|
|
106
|
-
/** The lines
|
|
107
|
-
const MARKED_LINES = new RegExp('^
|
|
128
|
+
/** The lines the sandbox wrote above the first table: a marker comment, the keys it owns, then one blank line. */
|
|
129
|
+
const MARKED_LINES = new RegExp('^(?:' +
|
|
130
|
+
CODEX_MARKERS.join('|') +
|
|
131
|
+
')[^\r\n]*\r?\n(?:(?:' +
|
|
132
|
+
CODEX_OWNED_KEYS.join('|') +
|
|
133
|
+
')[ \t]*=[^\r\n]*(?:\r?\n|$))*(?:[ \t]*\r?\n)?', 'm');
|
|
108
134
|
/** The tables of config.toml (from the first one on) with `[windows] sandbox` set to the mode, or removed for "off". */
|
|
109
135
|
function codexWindowsSandbox(tables, mode, eol) {
|
|
110
136
|
const setting = 'sandbox = "' + mode + '"';
|
|
@@ -7,7 +7,8 @@ export declare function localStateVars(): Record<string, string>;
|
|
|
7
7
|
export declare function resetMachineBound(): void;
|
|
8
8
|
/**
|
|
9
9
|
* Boot: Codex's config.toml follows the codex settings (codexConfigOwned): no Windows sandbox setup of its own, its MCP
|
|
10
|
-
* logins in a file
|
|
10
|
+
* logins in a file, the sandbox mode and approval policy for a start without the launch flags (codexArguments, which
|
|
11
|
+
* beat this file and still go on every start through the sandbox).
|
|
11
12
|
*/
|
|
12
13
|
export declare function codexBootConfig(): void;
|
|
13
14
|
/** Boot: the saved databases of every configured agent are copied to the sandbox's disk, where the agent runs them. */
|
|
@@ -104,7 +104,8 @@ export function resetMachineBound() {
|
|
|
104
104
|
}
|
|
105
105
|
/**
|
|
106
106
|
* Boot: Codex's config.toml follows the codex settings (codexConfigOwned): no Windows sandbox setup of its own, its MCP
|
|
107
|
-
* logins in a file
|
|
107
|
+
* logins in a file, the sandbox mode and approval policy for a start without the launch flags (codexArguments, which
|
|
108
|
+
* beat this file and still go on every start through the sandbox).
|
|
108
109
|
*/
|
|
109
110
|
export function codexBootConfig() {
|
|
110
111
|
if (dryRun || !session.agents?.codex || !session.codex)
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type AppEntry } from '../../shared/apps/index.js';
|
|
2
2
|
/** Settings an app needs on every boot (the sandbox's registry is new each time), and files next to its program. */
|
|
3
3
|
export declare function afterInstall(entry: AppEntry, exe: string): Promise<void>;
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import { associationPlan } from '../../shared/apps/index.js';
|
|
3
4
|
import { SYSTEM } from '../../shared/system.js';
|
|
4
5
|
import { errMsg } from '../../core/util/errors.js';
|
|
5
6
|
import { takePendingComet } from '../accounts/kinds/comet.js';
|
|
6
7
|
import { restoreCometSignIn } from '../browser/comet.js';
|
|
7
8
|
import { log, warn } from '../runtime/log.js';
|
|
8
|
-
import { execAsync } from '../runtime/process.js';
|
|
9
|
+
import { execAsync, runScriptWithJsonAsync } from '../runtime/process.js';
|
|
9
10
|
import { exists, mkdirp } from '../runtime/fs.js';
|
|
10
11
|
import { arch, tool } from '../toolchains/tool-env.js';
|
|
11
12
|
/** The Sysinternals tools whose EULA is accepted up front (each asks once otherwise). */
|
|
@@ -58,6 +59,10 @@ export async function afterInstall(entry, exe) {
|
|
|
58
59
|
mkdirp(path.join(dir, 'Data'));
|
|
59
60
|
if (entry.id === 'comet')
|
|
60
61
|
await restoreComet(exe);
|
|
62
|
+
const plan = associationPlan(entry.id, exe);
|
|
63
|
+
if (plan.length) {
|
|
64
|
+
await runScriptWithJsonAsync('set-registry-values.ps1', 'PlanFile', { values: plan }).catch((e) => warn(entry.name + ': double-click file types were not set (' + errMsg(e) + ')'));
|
|
65
|
+
}
|
|
61
66
|
if (entry.id === 'sysinternals') {
|
|
62
67
|
for (const name of SYSINTERNALS_TOOLS) {
|
|
63
68
|
await execAsync(SYSTEM.reg(), ['add', 'HKCU\\Software\\Sysinternals\\' + name, '/v', 'EulaAccepted', '/t', 'REG_DWORD', '/d', '1', '/f'], { stdio: 'ignore' }).catch(() => undefined);
|
|
@@ -13,6 +13,7 @@ export declare class AppInstaller {
|
|
|
13
13
|
private readonly parallel;
|
|
14
14
|
private readonly gui;
|
|
15
15
|
private readonly installed;
|
|
16
|
+
private readonly installedIds;
|
|
16
17
|
constructor(env: ToolEnv, options: AppsOptions, parallel?: number);
|
|
17
18
|
/**
|
|
18
19
|
* Downloads the plain downloads (not pinned, not npm, not remote) of the apps that are not installed yet, into
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// failing is reported and never stops the others.
|
|
3
3
|
import fs from 'node:fs';
|
|
4
4
|
import path from 'node:path';
|
|
5
|
-
import { appsToInstall } from '../../shared/apps/index.js';
|
|
5
|
+
import { appsToInstall, previewGuideLine } from '../../shared/apps/index.js';
|
|
6
6
|
import { downloader, moveIntoPlace, stagingFolder } from '../../shared/download/index.js';
|
|
7
7
|
import { safeRemove } from '../../shared/io/safe-remove.js';
|
|
8
8
|
import { dryRun, launchDir } from '../runtime/paths.js';
|
|
@@ -33,6 +33,7 @@ export class AppInstaller {
|
|
|
33
33
|
parallel;
|
|
34
34
|
gui = [];
|
|
35
35
|
installed = [];
|
|
36
|
+
installedIds = [];
|
|
36
37
|
constructor(env, options, parallel = PARALLEL) {
|
|
37
38
|
this.env = env;
|
|
38
39
|
this.options = options;
|
|
@@ -82,6 +83,9 @@ export class AppInstaller {
|
|
|
82
83
|
}
|
|
83
84
|
if (this.installed.length)
|
|
84
85
|
this.env.summary.push('apps: ' + this.installed.join(', ') + ' (shortcuts on the desktop and in Start > Sandbox apps)');
|
|
86
|
+
const preview = previewGuideLine(this.installedIds);
|
|
87
|
+
if (preview)
|
|
88
|
+
this.env.summary.push(preview);
|
|
85
89
|
}
|
|
86
90
|
async installOne(entry) {
|
|
87
91
|
try {
|
|
@@ -102,11 +106,17 @@ export class AppInstaller {
|
|
|
102
106
|
throw new Error(entry.exe + ' not found in the download');
|
|
103
107
|
await afterInstall(entry, exe);
|
|
104
108
|
// A proxy or MCP server (agent add-ons) is started by the sandbox or the agent: no PATH entry, no shortcut.
|
|
105
|
-
if (!entry.service && (entry.cli || entry.id === 'sysinternals'))
|
|
106
|
-
|
|
109
|
+
if (!entry.service && (entry.cli || entry.id === 'sysinternals')) {
|
|
110
|
+
// rustup's rust-analyzer proxy (in cargo's bin folder) only errors until its component is added.
|
|
111
|
+
if (entry.id === 'rust-analyzer')
|
|
112
|
+
this.env.prepend.unshift(path.dirname(exe));
|
|
113
|
+
else
|
|
114
|
+
this.env.prepend.push(path.dirname(exe));
|
|
115
|
+
}
|
|
107
116
|
if (!entry.cli && !entry.service)
|
|
108
117
|
this.gui.push({ name: entry.name, exe, ...this.launchArgs(entry) });
|
|
109
118
|
this.installed.push(entry.name);
|
|
119
|
+
this.installedIds.push(entry.id);
|
|
110
120
|
}
|
|
111
121
|
catch (e) {
|
|
112
122
|
warn(entry.name + ' could not be installed and is skipped; the other apps are not affected. Reason: ' + errMsg(e));
|
|
@@ -15,10 +15,11 @@ const INNER = [
|
|
|
15
15
|
/^chrome\.7z$/i,
|
|
16
16
|
/^chrome\.packed\.7z$/i,
|
|
17
17
|
/^mini_installer\.exe$/i,
|
|
18
|
-
/^updater\.packed\.7z$/i,
|
|
18
|
+
/^updater\.(packed\.)?7z$/i,
|
|
19
19
|
];
|
|
20
20
|
const MAX_LEVELS = 6;
|
|
21
|
-
|
|
21
|
+
/** Comet's installer nests mini_installer.exe five folders down (bin\Offline\{id}\{id}). */
|
|
22
|
+
const MAX_DEPTH = 7;
|
|
22
23
|
const MAX_ENTRIES = 20000;
|
|
23
24
|
/** Where the installer is kept when it could not be unpacked (below the app's folder). */
|
|
24
25
|
export const KEPT_INSTALLER_DIR = 'installer';
|
|
@@ -1 +1,55 @@
|
|
|
1
|
-
|
|
1
|
+
// Where the latest version of the game-development apps (catalogue-gamedev.ts) is, when it is not a plain GitHub
|
|
2
|
+
// release: Krita (download.kde.org, with the SHA-256 its mirror publishes beside each file), Inkscape (inkscape.org's
|
|
3
|
+
// portable 7z and its .sha256) and RenderDoc (renderdoc.org's portable zip; it publishes no checksum, the download is
|
|
4
|
+
// HTTPS from renderdoc.org).
|
|
5
|
+
import { fetchText } from '../../shared/download/index.js';
|
|
6
|
+
const firstHash = (text) => /\b([0-9a-f]{64})\b/i.exec(text)?.[1].toLowerCase() ?? null;
|
|
7
|
+
const byVersion = (a, b) => {
|
|
8
|
+
const x = a.split('.').map(Number);
|
|
9
|
+
const y = b.split('.').map(Number);
|
|
10
|
+
for (let i = 0; i < Math.max(x.length, y.length); i++)
|
|
11
|
+
if ((x[i] ?? 0) !== (y[i] ?? 0))
|
|
12
|
+
return (x[i] ?? 0) - (y[i] ?? 0);
|
|
13
|
+
return 0;
|
|
14
|
+
};
|
|
15
|
+
/** The newest Krita release folder that has the portable Windows zip (a hotfix folder may have only some files). */
|
|
16
|
+
async function krita() {
|
|
17
|
+
const base = 'https://download.kde.org/stable/krita/';
|
|
18
|
+
const versions = [...(await fetchText(base)).matchAll(/href="(\d+\.\d+\.\d+(?:\.\d+)?)\/"/g)]
|
|
19
|
+
.map((m) => m[1])
|
|
20
|
+
.sort(byVersion)
|
|
21
|
+
.reverse()
|
|
22
|
+
.slice(0, 4);
|
|
23
|
+
for (const v of versions) {
|
|
24
|
+
const file = 'krita-x64-' + v + '.zip';
|
|
25
|
+
if (!(await fetchText(base + v + '/')).includes('href="' + file + '"'))
|
|
26
|
+
continue;
|
|
27
|
+
const url = base + v + '/' + file;
|
|
28
|
+
return { url, fileName: file, sha256: firstHash(await fetchText(url + '.sha256').catch(() => '')) };
|
|
29
|
+
}
|
|
30
|
+
throw new Error('no portable Krita zip on download.kde.org');
|
|
31
|
+
}
|
|
32
|
+
async function inkscape() {
|
|
33
|
+
const release = await fetchText('https://inkscape.org/release/');
|
|
34
|
+
const v = /\/release\/inkscape-(\d+\.\d+(?:\.\d+)?)\//.exec(release)?.[1];
|
|
35
|
+
if (!v)
|
|
36
|
+
throw new Error('could not find the latest Inkscape release');
|
|
37
|
+
const page = await fetchText('https://inkscape.org/release/inkscape-' + v + '/windows/64-bit/compressed-7z/dl/');
|
|
38
|
+
const item = /\/gallery\/item\/\d+\/inkscape-[\w.-]+-x64[\w-]*\.7z/.exec(page);
|
|
39
|
+
const sums = /https:\/\/media\.inkscape\.org\/media\/resources\/sigs\/[\w.-]+\.sha256/.exec(page)?.[0];
|
|
40
|
+
if (!item)
|
|
41
|
+
throw new Error('no portable Inkscape 7z for ' + v);
|
|
42
|
+
return {
|
|
43
|
+
url: 'https://inkscape.org' + item[0],
|
|
44
|
+
fileName: 'inkscape-' + v + '-x64.7z',
|
|
45
|
+
sha256: sums ? firstHash(await fetchText(sums).catch(() => '')) : null,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
async function renderdoc() {
|
|
49
|
+
const page = await fetchText('https://renderdoc.org/builds');
|
|
50
|
+
const hit = /https:\/\/renderdoc\.org\/stable\/([\d.]+)\/RenderDoc_[\d.]+_64\.zip/.exec(page);
|
|
51
|
+
if (!hit)
|
|
52
|
+
throw new Error('could not find the RenderDoc download on renderdoc.org');
|
|
53
|
+
return { url: hit[0], fileName: 'RenderDoc_' + hit[1] + '_64.zip' };
|
|
54
|
+
}
|
|
55
|
+
export const GAMEDEV_RESOLVERS = { krita, inkscape, renderdoc };
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type ReadyInfo, type ViewStage, type ViewTask } from './progress-view.js';
|
|
2
|
+
import type { BootTaskDef, RunnerObserver, TaskOutcome } from './runner.js';
|
|
3
|
+
export declare class BootConsole implements RunnerObserver {
|
|
4
|
+
private readonly view;
|
|
5
|
+
private readonly live;
|
|
6
|
+
private timer;
|
|
7
|
+
private open;
|
|
8
|
+
private constructor();
|
|
9
|
+
/** `plain`: ordered lines even on a terminal (a dry run). */
|
|
10
|
+
static create(stages: readonly ViewStage[], tasks: readonly ViewTask[], o: {
|
|
11
|
+
plain: boolean;
|
|
12
|
+
}): BootConsole;
|
|
13
|
+
/** Prints the header and takes the console. */
|
|
14
|
+
start(header: string): void;
|
|
15
|
+
started(task: BootTaskDef): void;
|
|
16
|
+
finished(task: BootTaskDef, outcome: TaskOutcome): void;
|
|
17
|
+
/** What a running task's place on its row says (null: its title). */
|
|
18
|
+
detail(taskId: string, text: string | null): void;
|
|
19
|
+
/** The tool summary: to the file now, to the console once the boot is ready. */
|
|
20
|
+
summary(lines: readonly string[]): void;
|
|
21
|
+
/** Runs `work` that writes to the terminal itself (the hooks). */
|
|
22
|
+
aside<T>(work: () => T): T;
|
|
23
|
+
/**
|
|
24
|
+
* The final summary (`ready`, or null when the boot failed); the console is the log's own again. Programs' output
|
|
25
|
+
* keeps going to the log (the background runs quiet: file only).
|
|
26
|
+
*/
|
|
27
|
+
release(ready: ReadyInfo | null): void;
|
|
28
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
// The boot's console on the sandbox's terminal: the live progress block when it is a terminal, ordered plain lines
|
|
2
|
+
// otherwise (and in a dry run). While it is open it takes the log's console lines and the output of the programs the
|
|
3
|
+
// boot starts; it is released, with the final summary, right before the first session takes the window.
|
|
4
|
+
import { log, record, routeConsole } from '../runtime/log.js';
|
|
5
|
+
import { routeOutput } from '../runtime/process.js';
|
|
6
|
+
import { CONSOLE_GLYPHS, ProgressView, readyLines, RICH_GLYPHS, } from './progress-view.js';
|
|
7
|
+
/** The spinner's pace (the elapsed times change every tenth of a second too). */
|
|
8
|
+
const FRAME_MS = 100;
|
|
9
|
+
const COLUMNS = 80;
|
|
10
|
+
const SHOW_CURSOR = '\x1b[?25h';
|
|
11
|
+
export class BootConsole {
|
|
12
|
+
view;
|
|
13
|
+
live;
|
|
14
|
+
timer = null;
|
|
15
|
+
open = false;
|
|
16
|
+
constructor(view, live) {
|
|
17
|
+
this.view = view;
|
|
18
|
+
this.live = live;
|
|
19
|
+
}
|
|
20
|
+
/** `plain`: ordered lines even on a terminal (a dry run). */
|
|
21
|
+
static create(stages, tasks, o) {
|
|
22
|
+
const live = !o.plain && process.stdout.isTTY === true;
|
|
23
|
+
// Windows Terminal sets WT_SESSION; the classic console's font has no braille or check marks.
|
|
24
|
+
const rich = live && Boolean(process.env.WT_SESSION);
|
|
25
|
+
const view = new ProgressView(stages, tasks, {
|
|
26
|
+
live,
|
|
27
|
+
glyphs: rich ? RICH_GLYPHS : CONSOLE_GLYPHS,
|
|
28
|
+
now: () => Date.now(),
|
|
29
|
+
screen: {
|
|
30
|
+
write: (text) => void process.stdout.write(text),
|
|
31
|
+
columns: () => process.stdout.columns ?? COLUMNS,
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
return new BootConsole(view, live);
|
|
35
|
+
}
|
|
36
|
+
/** Prints the header and takes the console. */
|
|
37
|
+
start(header) {
|
|
38
|
+
record(header);
|
|
39
|
+
this.open = true;
|
|
40
|
+
this.view.start(header);
|
|
41
|
+
routeConsole({
|
|
42
|
+
line: (message, stage) => this.view.line(message, stage),
|
|
43
|
+
warning: (message) => this.view.warning(message),
|
|
44
|
+
notice: (message) => this.view.notice(message),
|
|
45
|
+
});
|
|
46
|
+
// Programs the boot starts would draw over the block (or between the plain stages): their output goes to the log;
|
|
47
|
+
// the hooks keep the terminal. After the release too: the background steps must not write into the session.
|
|
48
|
+
routeOutput({ capturing: () => !this.open || this.view.capturing, line: (text) => log(text) });
|
|
49
|
+
if (!this.live)
|
|
50
|
+
return;
|
|
51
|
+
this.timer = setInterval(() => this.view.tick(), FRAME_MS);
|
|
52
|
+
this.timer.unref();
|
|
53
|
+
// A crash must not leave the terminal without its cursor.
|
|
54
|
+
process.once('exit', () => {
|
|
55
|
+
if (this.open)
|
|
56
|
+
process.stdout.write(SHOW_CURSOR);
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
started(task) {
|
|
60
|
+
this.view.started(task);
|
|
61
|
+
this.view.tick();
|
|
62
|
+
}
|
|
63
|
+
finished(task, outcome) {
|
|
64
|
+
this.view.finished(task, outcome);
|
|
65
|
+
this.view.tick();
|
|
66
|
+
}
|
|
67
|
+
/** What a running task's place on its row says (null: its title). */
|
|
68
|
+
detail(taskId, text) {
|
|
69
|
+
this.view.detail(taskId, text);
|
|
70
|
+
}
|
|
71
|
+
/** The tool summary: to the file now, to the console once the boot is ready. */
|
|
72
|
+
summary(lines) {
|
|
73
|
+
for (const line of lines)
|
|
74
|
+
record(' ' + line);
|
|
75
|
+
this.view.summary(lines);
|
|
76
|
+
}
|
|
77
|
+
/** Runs `work` that writes to the terminal itself (the hooks). */
|
|
78
|
+
aside(work) {
|
|
79
|
+
return this.view.aside(work);
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* The final summary (`ready`, or null when the boot failed); the console is the log's own again. Programs' output
|
|
83
|
+
* keeps going to the log (the background runs quiet: file only).
|
|
84
|
+
*/
|
|
85
|
+
release(ready) {
|
|
86
|
+
if (!this.open)
|
|
87
|
+
return;
|
|
88
|
+
this.open = false;
|
|
89
|
+
if (this.timer)
|
|
90
|
+
clearInterval(this.timer);
|
|
91
|
+
this.timer = null;
|
|
92
|
+
routeConsole(null);
|
|
93
|
+
if (ready)
|
|
94
|
+
for (const line of readyLines(ready))
|
|
95
|
+
record(line);
|
|
96
|
+
this.view.release(ready);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -15,6 +15,9 @@ export declare class Boot {
|
|
|
15
15
|
private secrets;
|
|
16
16
|
private basePath;
|
|
17
17
|
private readonly startedAt;
|
|
18
|
+
private readonly who;
|
|
19
|
+
/** The console while the boot runs: the stage rows, warnings and summary (released before the first session). */
|
|
20
|
+
private readonly progress;
|
|
18
21
|
private openGate;
|
|
19
22
|
private readonly gate;
|
|
20
23
|
/** The boot is ready: the runner no longer writes the status (the session owns it). */
|
|
@@ -24,9 +27,13 @@ export declare class Boot {
|
|
|
24
27
|
private stepsDone;
|
|
25
28
|
private stepsTotal;
|
|
26
29
|
run(): Promise<void>;
|
|
30
|
+
/** The prelude, then every foreground task (the console shows them). */
|
|
31
|
+
private foreground;
|
|
27
32
|
private stageTitle;
|
|
28
|
-
/** The
|
|
33
|
+
/** The toolchain steps that go on after the boot is ready (Visual Studio and what needs it), in plan order. */
|
|
29
34
|
private backgroundNames;
|
|
35
|
+
/** Runs a stage's hooks; they write to the terminal themselves, so the progress block steps aside for them. */
|
|
36
|
+
private hooks;
|
|
30
37
|
/**
|
|
31
38
|
* The host's one-shot file, read once. Your saved service sign-ins (accounts.<kind>.share) go to their tools before
|
|
32
39
|
* anything opens them, and leave memory at once; what they give the agent (EXPO_TOKEN ...) joins its environment
|
package/dist/guest/boot/boot.js
CHANGED
|
@@ -15,7 +15,7 @@ import { linkProfileDirsAsync } from '../agents/profile-links.js';
|
|
|
15
15
|
import { restoreClaudeMem } from '../setup/claude-mem.js';
|
|
16
16
|
import { syncClaudeProfile } from '../setup/claude-profile.js';
|
|
17
17
|
import { agentEnv, persistEnvironment } from '../setup/environment.js';
|
|
18
|
-
import { runHooks } from '../setup/hooks.js';
|
|
18
|
+
import { hasHooks, runHooks } from '../setup/hooks.js';
|
|
19
19
|
import { applyAllowOnly, proxyEnv } from '../network/allow-only.js';
|
|
20
20
|
import { applyNetworkPolicy, networkProblems } from '../network/firewall.js';
|
|
21
21
|
import { writeManagedSettings } from '../setup/managed-policy.js';
|
|
@@ -37,12 +37,14 @@ import { now } from '../../shared/channel/run-dir.js';
|
|
|
37
37
|
import { writeJsonAtomic } from '../../shared/io/json.js';
|
|
38
38
|
import { viewSession } from '../sessions/viewer.js';
|
|
39
39
|
import { saveBootEnv } from './boot-env.js';
|
|
40
|
-
import { BOOT_DEFS, STAGES, stageTitle } from './graph.js';
|
|
41
|
-
import { BootRunner,
|
|
40
|
+
import { BOOT_DEFS, STAGES, stageName, stageTitle } from './graph.js';
|
|
41
|
+
import { BootRunner, duration } from './runner.js';
|
|
42
|
+
import { BootConsole } from './boot-console.js';
|
|
43
|
+
import { stepsDetail } from './progress-view.js';
|
|
42
44
|
import { installPools } from '../runtime/pools.js';
|
|
43
45
|
import { primaryProject, saveProjectStatus } from './project-status.js';
|
|
44
46
|
import { dryRun, isWin, localDir, logsDir, run, toolsDir } from '../runtime/paths.js';
|
|
45
|
-
import { log, logPath, runQuiet, warn, withLogContext } from '../runtime/log.js';
|
|
47
|
+
import { log, logPath, notice, record, runQuiet, warn, withLogContext } from '../runtime/log.js';
|
|
46
48
|
import { mkdirp } from '../runtime/fs.js';
|
|
47
49
|
import { session, workspace } from '../runtime/session.js';
|
|
48
50
|
import { applyBootEnvironment, BOOT_SECRETS_VARIABLE, takeSecrets } from '../runtime/secrets.js';
|
|
@@ -50,7 +52,20 @@ import { setBackgroundWork, setSandboxStatus } from '../runtime/status.js';
|
|
|
50
52
|
import { errMsg } from '../../core/util/errors.js';
|
|
51
53
|
const PRIMARY_POLLS = 600;
|
|
52
54
|
const POLL_MS = 500;
|
|
53
|
-
const VSCODE_LANGUAGES = [
|
|
55
|
+
const VSCODE_LANGUAGES = [
|
|
56
|
+
'python',
|
|
57
|
+
'java',
|
|
58
|
+
'go',
|
|
59
|
+
'rust',
|
|
60
|
+
'cpp',
|
|
61
|
+
'msvc',
|
|
62
|
+
'unreal',
|
|
63
|
+
'dotnet',
|
|
64
|
+
'ruby',
|
|
65
|
+
'php',
|
|
66
|
+
'deno',
|
|
67
|
+
'pwsh',
|
|
68
|
+
];
|
|
54
69
|
function startDaemon(env, secrets) {
|
|
55
70
|
spawn(process.execPath, [path.join(path.dirname(process.argv[1]), 'daemon.js')], {
|
|
56
71
|
detached: true,
|
|
@@ -159,6 +174,12 @@ export class Boot {
|
|
|
159
174
|
secrets = Object.freeze({});
|
|
160
175
|
basePath = '';
|
|
161
176
|
startedAt = Date.now();
|
|
177
|
+
who = {
|
|
178
|
+
...(session.vpn ? { vpnServer: session.vpn.server } : {}),
|
|
179
|
+
agent: session.harnessName,
|
|
180
|
+
};
|
|
181
|
+
/** The console while the boot runs: the stage rows, warnings and summary (released before the first session). */
|
|
182
|
+
progress = BootConsole.create(Array.from({ length: STAGES }, (_, i) => ({ stage: i + 1, name: stageName(i + 1, this.who) })), BOOT_DEFS.filter((d) => !d.background), { plain: dryRun });
|
|
162
183
|
openGate = () => undefined;
|
|
163
184
|
gate = new Promise((resolve) => {
|
|
164
185
|
this.openGate = resolve;
|
|
@@ -170,14 +191,39 @@ export class Boot {
|
|
|
170
191
|
stepsDone = 0;
|
|
171
192
|
stepsTotal = 0;
|
|
172
193
|
async run() {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
194
|
+
const agent = session.harness ? session.harnessName : 'no agent';
|
|
195
|
+
this.progress.start('Agent sandbox · ' + session.project + ' · ' + agent + ' · ' + workspace);
|
|
196
|
+
let runner;
|
|
197
|
+
try {
|
|
198
|
+
runner = await this.foreground();
|
|
199
|
+
}
|
|
200
|
+
catch (e) {
|
|
201
|
+
this.progress.release(null);
|
|
202
|
+
throw e;
|
|
203
|
+
}
|
|
204
|
+
this.progress.release({
|
|
205
|
+
ms: Date.now() - this.startedAt,
|
|
206
|
+
outcomes: runner.results(),
|
|
207
|
+
background: this.backgroundNames(),
|
|
208
|
+
logPath,
|
|
209
|
+
});
|
|
210
|
+
if (dryRun) {
|
|
211
|
+
// Same graph, one task at a time: the background tasks run in place, right after the session.
|
|
212
|
+
this.openGate();
|
|
213
|
+
await runner.run('background');
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
this.openGate();
|
|
217
|
+
const later = runner.run('background');
|
|
218
|
+
// The viewer owns the console now: the background tasks write to the log file only.
|
|
219
|
+
if (session.harness && session.autoSession !== false && process.stdin.isTTY) {
|
|
220
|
+
await viewSession(session.launchId);
|
|
221
|
+
log('\nThis window stays open in ' + workspace + '. Other sessions keep running in their own tabs.');
|
|
222
|
+
}
|
|
223
|
+
await later.catch((e) => warn('Background work failed: ' + errMsg(e)));
|
|
224
|
+
}
|
|
225
|
+
/** The prelude, then every foreground task (the console shows them). */
|
|
226
|
+
async foreground() {
|
|
181
227
|
if (!fs.statSync(workspace, { throwIfNoEntry: false })?.isDirectory())
|
|
182
228
|
throw new Error('Workspace folder is missing in the sandbox: ' + workspace);
|
|
183
229
|
this.basePath = process.env.PATH ?? '';
|
|
@@ -189,7 +235,7 @@ export class Boot {
|
|
|
189
235
|
prepareGithubFolder(this.secrets.githubToken);
|
|
190
236
|
installPools();
|
|
191
237
|
const runner = new BootRunner(BOOT_DEFS, this.tasks(), {
|
|
192
|
-
|
|
238
|
+
trace: (line) => record(line),
|
|
193
239
|
warn: (line, quiet) => (quiet ? runQuiet(() => warn(line)) : warn(line)),
|
|
194
240
|
now: () => Date.now(),
|
|
195
241
|
dryRun,
|
|
@@ -199,36 +245,26 @@ export class Boot {
|
|
|
199
245
|
this.lastStatus = s;
|
|
200
246
|
this.publish();
|
|
201
247
|
},
|
|
202
|
-
|
|
248
|
+
observer: this.progress,
|
|
249
|
+
inContext: (ctx, work) => withLogContext({ stage: ctx.stage, task: ctx.title, quiet: ctx.quiet }, work),
|
|
203
250
|
});
|
|
204
251
|
await runner.run('foreground');
|
|
205
|
-
|
|
206
|
-
log(bootSummary(Date.now() - this.startedAt, runner.results(), background, logPath));
|
|
207
|
-
if (dryRun) {
|
|
208
|
-
// Same graph, one task at a time: the background tasks run in place, right after the session.
|
|
209
|
-
this.openGate();
|
|
210
|
-
await runner.run('background');
|
|
211
|
-
return;
|
|
212
|
-
}
|
|
213
|
-
this.openGate();
|
|
214
|
-
const later = runner.run('background');
|
|
215
|
-
// The viewer owns the console now: the background tasks write to the log file only.
|
|
216
|
-
if (session.harness && session.autoSession !== false && process.stdin.isTTY) {
|
|
217
|
-
await viewSession(session.launchId);
|
|
218
|
-
log('\nThis window stays open in ' + workspace + '. Other sessions keep running in their own tabs.');
|
|
219
|
-
}
|
|
220
|
-
await later.catch((e) => warn('Background work failed: ' + errMsg(e)));
|
|
252
|
+
return runner;
|
|
221
253
|
}
|
|
222
254
|
stageTitle(n) {
|
|
223
|
-
return stageTitle(n,
|
|
255
|
+
return stageTitle(n, this.who);
|
|
224
256
|
}
|
|
225
|
-
/** The
|
|
257
|
+
/** The toolchain steps that go on after the boot is ready (Visual Studio and what needs it), in plan order. */
|
|
226
258
|
backgroundNames() {
|
|
227
259
|
const t = this.tools;
|
|
228
260
|
if (!t?.later)
|
|
229
261
|
return [];
|
|
230
|
-
|
|
231
|
-
|
|
262
|
+
return t.names.filter((n) => !(n in t.steps));
|
|
263
|
+
}
|
|
264
|
+
/** Runs a stage's hooks; they write to the terminal themselves, so the progress block steps aside for them. */
|
|
265
|
+
hooks(stage, env) {
|
|
266
|
+
if (hasHooks(stage))
|
|
267
|
+
this.progress.aside(() => runHooks(stage, env));
|
|
232
268
|
}
|
|
233
269
|
/**
|
|
234
270
|
* The host's one-shot file, read once. Your saved service sign-ins (accounts.<kind>.share) go to their tools before
|
|
@@ -262,7 +298,7 @@ export class Boot {
|
|
|
262
298
|
setSandboxStatus(s.phase, s.message, {
|
|
263
299
|
step: s.step,
|
|
264
300
|
steps: STAGES,
|
|
265
|
-
title:
|
|
301
|
+
title: stageName(s.step, this.who),
|
|
266
302
|
active: [...s.running.map((name) => ({ name })), ...this.activeSteps],
|
|
267
303
|
done: this.stepsDone,
|
|
268
304
|
total: this.stepsTotal,
|
|
@@ -281,7 +317,7 @@ export class Boot {
|
|
|
281
317
|
},
|
|
282
318
|
audit: () => enableProcessAudit(),
|
|
283
319
|
// 2 - pre-provision hooks and desktop
|
|
284
|
-
hooksPreProvision: () =>
|
|
320
|
+
hooksPreProvision: () => this.hooks('pre-provision', process.env),
|
|
285
321
|
desktop: () => applyPersonalization(),
|
|
286
322
|
browserGpu: () => keepBrowsersOffGpu(),
|
|
287
323
|
// 3 - developer tools
|
|
@@ -343,7 +379,7 @@ export class Boot {
|
|
|
343
379
|
await agents.provision(session.harnessName, session.harness, this.env);
|
|
344
380
|
},
|
|
345
381
|
// 8 - pre-agent hooks
|
|
346
|
-
hooksPreAgent: () =>
|
|
382
|
+
hooksPreAgent: () => this.hooks('pre-agent', this.env),
|
|
347
383
|
// 9 - start
|
|
348
384
|
session: () => this.start(),
|
|
349
385
|
// After the first session runs
|
|
@@ -371,6 +407,7 @@ export class Boot {
|
|
|
371
407
|
...(session.pcInstalls ? { pcInstalls: session.pcInstalls } : {}),
|
|
372
408
|
}, {
|
|
373
409
|
progress: (p) => {
|
|
410
|
+
this.progress.detail('toolchains', stepsDetail(p.active.map((a) => a.name + (a.percent === undefined ? '' : ' ' + Math.round(a.percent) + '%')), p.done, p.total));
|
|
374
411
|
this.activeSteps = p.active;
|
|
375
412
|
this.stepsDone = p.done;
|
|
376
413
|
this.stepsTotal = p.total;
|
|
@@ -424,8 +461,7 @@ export class Boot {
|
|
|
424
461
|
lan: this.lan,
|
|
425
462
|
});
|
|
426
463
|
saveProjectStatus(primaryProject(), workspace, 'ready', 'Ready', session.databases ?? [], this.summary);
|
|
427
|
-
|
|
428
|
-
log(' ' + line);
|
|
464
|
+
this.progress.summary(this.summary);
|
|
429
465
|
}
|
|
430
466
|
/** The toolchains' lines, then the missing protections, allowOnly and VS Code (in this order). */
|
|
431
467
|
summaryLines(toolchainLines) {
|
|
@@ -441,7 +477,8 @@ export class Boot {
|
|
|
441
477
|
const tools = this.tools;
|
|
442
478
|
if (!tools?.later)
|
|
443
479
|
return;
|
|
444
|
-
const
|
|
480
|
+
const names = this.backgroundNames();
|
|
481
|
+
const name = names.length ? names.slice(0, 3).join(', ') + (names.length > 3 ? ' ...' : '') : 'Visual Studio';
|
|
445
482
|
const began = Date.now();
|
|
446
483
|
setBackgroundWork([{ name, state: 'running' }]);
|
|
447
484
|
log('Background: ' + name + ' (installing)');
|
|
@@ -468,7 +505,7 @@ export class Boot {
|
|
|
468
505
|
saveProjectStatus(primaryProject(), workspace, 'ready', 'Ready', session.databases ?? [], this.summary);
|
|
469
506
|
await soft('Sandbox guide for the agent', () => writeGuides(this.summary, this.lan));
|
|
470
507
|
setBackgroundWork([{ name, state: 'done' }]);
|
|
471
|
-
log('Background done in ' +
|
|
508
|
+
log('Background done in ' + duration(Date.now() - began) + ': ' + name);
|
|
472
509
|
}
|
|
473
510
|
catch (e) {
|
|
474
511
|
setBackgroundWork([{ name, state: 'failed' }]);
|
|
@@ -512,7 +549,7 @@ export class Boot {
|
|
|
512
549
|
});
|
|
513
550
|
if (!(await waitForPrimary()))
|
|
514
551
|
throw new Error('The agent session did not start. Logs: ' + logsDir);
|
|
515
|
-
|
|
552
|
+
notice('If Claude asks you to sign in, do it once; the login is saved in persistent storage.');
|
|
516
553
|
}
|
|
517
554
|
}
|
|
518
555
|
export function runBoot() {
|