windows-development-sandbox 1.28.0 → 1.29.0
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/README.md +5 -4
- package/dist/app/features/sandbox/sandbox.run.js +1 -1
- package/dist/bin/{chunk-UZ5S3EMB.js → chunk-O4EL32TM.js} +1 -1
- package/dist/bin/{chunk-CB44HTTN.js → chunk-QYRRWGFY.js} +1442 -1280
- package/dist/bin/sandbox.js +10772 -3
- package/dist/cli/commands/launch/launch.js +1 -1
- package/dist/cli/commands/launch/running.js +2 -2
- package/dist/cli/index.js +1 -1
- package/dist/cli/menu/pages/agents-page.js +1 -1
- package/dist/cli/services/sandbox-service.js +1 -1
- package/dist/core/schema/sandbox.js +1 -1
- package/dist/guest/apps/app-installer.js +1 -1
- package/dist/guest/apps/store-check.d.ts +2 -0
- package/dist/guest/apps/store-check.js +3 -1
- package/dist/guest/boot/guide-lines.d.ts +2 -0
- package/dist/guest/boot/guide-lines.js +7 -0
- package/dist/guest/boot/guide.js +2 -1
- package/dist/guest/toolchains/native/unreal.js +3 -22
- package/dist/host/accounts/deliver.js +16 -2
- package/dist/host/apps/store-package.d.ts +2 -0
- package/dist/host/apps/store-package.js +16 -15
- package/dist/host/channel/attach.js +1 -1
- package/dist/host/channel/channel.js +3 -3
- package/dist/host/guard/commands.js +1 -0
- package/dist/host/health/path-copies.d.ts +1 -1
- package/dist/host/health/path-copies.js +2 -2
- package/dist/host/install/index.d.ts +1 -1
- package/dist/host/install/index.js +1 -1
- package/dist/host/install/shims.d.ts +8 -1
- package/dist/host/install/shims.js +39 -1
- package/dist/host/launch/generate.js +4 -0
- package/dist/host/launch/index.d.ts +1 -0
- package/dist/host/launch/index.js +1 -0
- package/dist/host/launch/pc-logs.d.ts +6 -0
- package/dist/host/launch/pc-logs.js +129 -0
- package/dist/host/sandbox/platform.js +1 -1
- package/dist/host/sandbox/window.js +1 -1
- package/dist/runtime/agent.js +1 -1
- package/dist/runtime/daemon.js +72 -90
- package/dist/runtime/db.js +1 -1
- package/dist/runtime/main.js +35 -57
- package/dist/runtime/netproxy.js +1 -1
- package/dist/runtime/tool.js +1 -1
- package/dist/runtime/view.js +1 -1
- package/dist/selftest/cli.test.js +5 -5
- package/dist/selftest/config.test.js +1 -1
- package/dist/selftest/credentials.test.js +1 -1
- package/dist/selftest/engine.test.js +1 -1
- package/dist/selftest/generate.test.js +66 -1
- package/dist/selftest/hcs.test.js +1 -1
- package/dist/selftest/health.test.js +21 -10
- package/dist/selftest/heavy-load.test.js +1 -5
- package/dist/selftest/host-sim.test.js +13 -14
- package/dist/selftest/install.test.js +6 -2
- package/dist/selftest/store-app.test.js +62 -4
- package/dist/selftest/support/web-preview.js +2 -2
- package/dist/shared/accounts/kinds.d.ts +5 -0
- package/dist/shared/accounts/kinds.js +8 -0
- package/dist/shared/apps/index.d.ts +1 -1
- package/dist/shared/apps/index.js +1 -1
- package/dist/shared/apps/selection.d.ts +1 -0
- package/dist/shared/apps/selection.js +4 -3
- package/dist/shared/apps/store-package.d.ts +13 -0
- package/dist/shared/apps/store-package.js +24 -0
- package/dist/shared/memory/index.d.ts +0 -1
- package/dist/shared/memory/index.js +0 -1
- package/native/csharp/HcsHost/AssemblyInfo.cs +3 -3
- package/native/csharp/HostTools/AssemblyInfo.cs +3 -3
- package/native/csharp/HostTools/Window.cs +1 -1
- package/native/csharp/StorageHelper/AssemblyInfo.cs +3 -3
- package/native/scripts/get-store-package.ps1 +24 -13
- package/package.json +4 -14
- package/sandbox/scripts/install-store-package.ps1 +11 -4
- package/schemas/config.schema.json +1 -1
- package/schemas/sandbox-config.schema.json +1 -1
- package/dist/bin/agent-sandbox.d.ts +0 -2
- package/dist/bin/agent-sandbox.js +0 -9
- package/dist/bin/chunk-ZMZI3625.js +0 -10778
- package/dist/bin/claude-host.d.ts +0 -2
- package/dist/bin/claude-host.js +0 -11
- package/dist/bin/claude-sbx.d.ts +0 -2
- package/dist/bin/claude-sbx.js +0 -9
- package/dist/bin/codex-sbx.d.ts +0 -2
- package/dist/bin/codex-sbx.js +0 -9
- package/dist/bin/gemini-sbx.d.ts +0 -2
- package/dist/bin/gemini-sbx.js +0 -9
- package/dist/bin/sbx-hide.d.ts +0 -2
- package/dist/bin/sbx-hide.js +0 -9
- package/dist/bin/sbx-show.d.ts +0 -2
- package/dist/bin/sbx-show.js +0 -9
- package/dist/shared/memory/unreal-build.d.ts +0 -2
- package/dist/shared/memory/unreal-build.js +0 -21
|
@@ -417,7 +417,7 @@ class Launcher {
|
|
|
417
417
|
console.log(headsUp + '\nFollow along: sandbox logs -f');
|
|
418
418
|
if (hidden)
|
|
419
419
|
console.log((await hidden)
|
|
420
|
-
? 'Sandbox running headless. Open the window:
|
|
420
|
+
? 'Sandbox running headless. Open the window: sandbox show Attach here: sandbox claude -t'
|
|
421
421
|
: 'Could not find the sandbox window to hide (it may still be starting); run sandbox hide in a moment.');
|
|
422
422
|
else
|
|
423
423
|
console.log('Sandbox starting. ' +
|
|
@@ -114,7 +114,7 @@ export async function openSession(ctx, ready, joined, o) {
|
|
|
114
114
|
session.id +
|
|
115
115
|
' opened in a new tab inside the sandbox. Attach here with: sandbox attach ' +
|
|
116
116
|
session.id +
|
|
117
|
-
(ready.headless ? ' (headless:
|
|
117
|
+
(ready.headless ? ' (headless: sandbox show opens the window)' : ''));
|
|
118
118
|
return 0;
|
|
119
119
|
}
|
|
120
120
|
/** The sessions a launch asks for: each --session, else one (the agent, in this folder). */
|
|
@@ -169,7 +169,7 @@ async function openSessions(ctx, ready, list) {
|
|
|
169
169
|
if (!first)
|
|
170
170
|
return 1;
|
|
171
171
|
console.log('Each has its own tab inside the sandbox' +
|
|
172
|
-
(ready.headless ? ' (headless:
|
|
172
|
+
(ready.headless ? ' (headless: sandbox show opens the window)' : '') +
|
|
173
173
|
'. Attach to any of them, from any terminal: sandbox attach');
|
|
174
174
|
return results.every((r) => r.status === 'fulfilled') ? 0 : 1;
|
|
175
175
|
}
|
package/dist/cli/index.js
CHANGED
|
@@ -17,7 +17,7 @@ import { versionText } from './version.js';
|
|
|
17
17
|
import { HANDLERS } from './commands/index.js';
|
|
18
18
|
import { definitionHandler, operationHandler } from './operations/index.js';
|
|
19
19
|
import { runUpdate } from '../app/features/updates/index.js';
|
|
20
|
-
const entry = path.join(PACKAGE_ROOT, 'dist', 'bin', '
|
|
20
|
+
const entry = path.join(PACKAGE_ROOT, 'dist', 'bin', 'sandbox.js');
|
|
21
21
|
/** A first word that is no command and no agent, but close to one: "Did you mean: sandbox status?". */
|
|
22
22
|
function failOnTypo(word, agents) {
|
|
23
23
|
const names = [...COMMANDS.filter((c) => !c.hidden).map((c) => c.name), ...agents];
|
|
@@ -152,7 +152,7 @@ export function agentsOptions(m, cwd = process.cwd()) {
|
|
|
152
152
|
category: 'options',
|
|
153
153
|
section: 'This start',
|
|
154
154
|
hint: WINDOW_TEXT[m.window],
|
|
155
|
-
description: 'Hide or show the Windows Sandbox window (
|
|
155
|
+
description: 'Hide or show the Windows Sandbox window (sandbox show brings a hidden one back).',
|
|
156
156
|
},
|
|
157
157
|
{
|
|
158
158
|
value: 'opt:folder',
|
|
@@ -71,7 +71,7 @@ export class SandboxService {
|
|
|
71
71
|
}
|
|
72
72
|
async hideWindow() {
|
|
73
73
|
if (!(await hideSandboxWindow(this.ctx.storageRoot, 5)))
|
|
74
|
-
fail('Could not find a visible Windows Sandbox window.', 'It may already be hidden (
|
|
74
|
+
fail('Could not find a visible Windows Sandbox window.', 'It may already be hidden (sandbox show brings it back).');
|
|
75
75
|
}
|
|
76
76
|
/** What stopping ends. Fails when this sandbox was not started by this tool. */
|
|
77
77
|
stopPlan() {
|
|
@@ -62,7 +62,7 @@ export const SandboxSchema = z.strictObject({
|
|
|
62
62
|
protectedClient: z.boolean().describe('Run the sandbox window in Protected Client mode (recommended).'),
|
|
63
63
|
headless: z
|
|
64
64
|
.boolean()
|
|
65
|
-
.describe('Hide the sandbox window (
|
|
65
|
+
.describe('Hide the sandbox window ("Show the window" brings it back). A headless sandbox shuts down when its last session ends.'),
|
|
66
66
|
keepAwake: z
|
|
67
67
|
.boolean()
|
|
68
68
|
.describe('Keep this PC from sleeping while a sandbox runs, so agents left working overnight keep going (the screen may still turn off; closing a laptop lid still sleeps it).'),
|
|
@@ -140,7 +140,7 @@ export class AppInstaller {
|
|
|
140
140
|
}
|
|
141
141
|
catch (e) {
|
|
142
142
|
warn(entry.name + ' could not be installed and is skipped; the other apps are not affected. Reason: ' + errMsg(e));
|
|
143
|
-
this.env.summary.push(entry.name + ': FAILED');
|
|
143
|
+
this.env.summary.push('store' in entry.source ? entry.name + ': not installed - ' + errMsg(e).slice(0, 300) : entry.name + ': FAILED');
|
|
144
144
|
}
|
|
145
145
|
}
|
|
146
146
|
/** The command line an app's shortcut starts it with (Comet: its own profile, DevTools on 127.0.0.1). */
|
|
@@ -4,5 +4,7 @@ export interface CheckedPackage {
|
|
|
4
4
|
readonly main: string;
|
|
5
5
|
readonly dependencies: string[];
|
|
6
6
|
}
|
|
7
|
+
/** Why a Store app has no package here, and what to do (the PC's launch notes say what went wrong there). */
|
|
8
|
+
export declare const MISSING_PACKAGE = "this PC did not hand over its package (it could not fetch it: see the launch notes). Fix: on this PC run \"winget download --id <product id> --source msstore\", then launch again";
|
|
7
9
|
/** The package in `dir` when its manifest is valid and every file is there as hashed; throws otherwise. */
|
|
8
10
|
export declare function checkedPackage(dir: string): Promise<CheckedPackage>;
|
|
@@ -16,11 +16,13 @@ export function fileSha256(file) {
|
|
|
16
16
|
stream.on('end', () => resolve(hash.digest('hex')));
|
|
17
17
|
});
|
|
18
18
|
}
|
|
19
|
+
/** Why a Store app has no package here, and what to do (the PC's launch notes say what went wrong there). */
|
|
20
|
+
export const MISSING_PACKAGE = 'this PC did not hand over its package (it could not fetch it: see the launch notes). Fix: on this PC run "winget download --id <product id> --source msstore", then launch again';
|
|
19
21
|
/** The package in `dir` when its manifest is valid and every file is there as hashed; throws otherwise. */
|
|
20
22
|
export async function checkedPackage(dir) {
|
|
21
23
|
const manifest = readValid(StorePackageManifestSchema, path.join(dir, STORE_PACKAGE_MANIFEST));
|
|
22
24
|
if (!manifest)
|
|
23
|
-
throw new Error(
|
|
25
|
+
throw new Error(MISSING_PACKAGE);
|
|
24
26
|
for (const f of manifest.files) {
|
|
25
27
|
if ((await fileSha256(path.join(dir, f.name)).catch(() => '')) !== f.sha256)
|
|
26
28
|
throw new Error(f.name + ' is missing or changed');
|
|
@@ -23,3 +23,5 @@ export declare function projectFolderLines(workspace: string, workspaceHost: str
|
|
|
23
23
|
* (a project that has not been set up yet is not listed: its tools are decided from its own files).
|
|
24
24
|
*/
|
|
25
25
|
export declare function toolLines(primaryName: string, toolSummary: readonly string[], projects: readonly GuideProject[]): string[];
|
|
26
|
+
/** The PC logs line, only when the launch folder has them (a launch of the sandbox's own source repo). */
|
|
27
|
+
export declare function pcLogsGuideLine(launchDir: string, exists: (dir: string) => boolean): string[];
|
|
@@ -39,3 +39,10 @@ export function toolLines(primaryName, toolSummary, projects) {
|
|
|
39
39
|
"- Tools by project: each project gets the tools its own files ask for; python and rust already installed here are on every project's PATH.",
|
|
40
40
|
];
|
|
41
41
|
}
|
|
42
|
+
/** The PC logs line, only when the launch folder has them (a launch of the sandbox's own source repo). */
|
|
43
|
+
export function pcLogsGuideLine(launchDir, exists) {
|
|
44
|
+
const dir = path.win32.join(launchDir, 'pc-logs');
|
|
45
|
+
return exists(dir)
|
|
46
|
+
? ['- PC crash reports and logs (this project only, read-only, never commit them): `' + dir + '`']
|
|
47
|
+
: [];
|
|
48
|
+
}
|
package/dist/guest/boot/guide.js
CHANGED
|
@@ -6,7 +6,7 @@ import { claudeDir, session, stateDir, workspace } from '../runtime/session.js';
|
|
|
6
6
|
import { dataDir, launchDir, logsDir, toolsDir } from '../runtime/paths.js';
|
|
7
7
|
import { addonGuideLine, APPS, headroomGuideLine, RTK_GUIDE_LINE } from '../../shared/apps/index.js';
|
|
8
8
|
import { installedAddon, installedRtk } from '../setup/agent-addons.js';
|
|
9
|
-
import { projectFolderLines, toolLines } from './guide-lines.js';
|
|
9
|
+
import { pcLogsGuideLine, projectFolderLines, toolLines } from './guide-lines.js';
|
|
10
10
|
import { sandboxScriptsDir } from '../../shared/scripts.js';
|
|
11
11
|
import { findUproject, unrealGuideLines } from '../../shared/unreal/index.js';
|
|
12
12
|
import { DISK } from '../toolchains/engines/unreal-engines.js';
|
|
@@ -84,6 +84,7 @@ export function sandboxGuide(toolSummary, lan = 'blocked', projects = [], addons
|
|
|
84
84
|
'`',
|
|
85
85
|
'- RO: `' + launchDir + "` (this boot's scripts) · lost on close: everything else, incl. `C:\\nvm` (Node symlink)",
|
|
86
86
|
...mirrorLine(),
|
|
87
|
+
...pcLogsGuideLine(launchDir, fs.existsSync),
|
|
87
88
|
...toolLines(session.project, toolSummary, projects),
|
|
88
89
|
...unrealLines(projects),
|
|
89
90
|
...unityLines(projects),
|
|
@@ -1,23 +1,20 @@
|
|
|
1
1
|
// Unreal Engine development, machine-wide (toolchains.unreal, once per boot): the DirectX End-User Runtime the engine
|
|
2
2
|
// lists and the Epic Games Launcher (real installers: they run every boot), the launcher pointed at the data disk for
|
|
3
|
-
// engines and given back the engines it installed there, the Windows SDK debuggers for crash dumps, a check that
|
|
4
|
-
// UnrealBuildTool finds Visual Studio the way it looks (the Setup Configuration API)
|
|
5
|
-
//
|
|
6
|
-
// by engines/unreal-engines.ts.
|
|
3
|
+
// engines and given back the engines it installed there, the Windows SDK debuggers for crash dumps, and a check that
|
|
4
|
+
// UnrealBuildTool finds Visual Studio the way it looks (the Setup Configuration API). Visual Studio itself is its own
|
|
5
|
+
// step (visual-studio.ts), which the plan runs first; each project's engine is set up by engines/unreal-engines.ts.
|
|
7
6
|
import fs from 'node:fs';
|
|
8
7
|
import os from 'node:os';
|
|
9
8
|
import path from 'node:path';
|
|
10
9
|
import { errMsg } from '../../../core/util/errors.js';
|
|
11
10
|
import { readValid } from '../../../shared/io/json.js';
|
|
12
11
|
import { safeRemove } from '../../../shared/io/safe-remove.js';
|
|
13
|
-
import { buildJobs, ubtBuildConfiguration, usableCpus } from '../../../shared/memory/index.js';
|
|
14
12
|
import { VsSetupReportSchema, vsVerdict } from '../../../shared/unreal/index.js';
|
|
15
13
|
import { runInstaller } from '../../runtime/pools.js';
|
|
16
14
|
import { dryRun } from '../../runtime/paths.js';
|
|
17
15
|
import { exists, mkdirp } from '../../runtime/fs.js';
|
|
18
16
|
import { log, warn } from '../../runtime/log.js';
|
|
19
17
|
import { runScriptAsync } from '../../runtime/process.js';
|
|
20
|
-
import { session } from '../../runtime/session.js';
|
|
21
18
|
import { ensureLauncher, fetchLauncher, placeEngines, restoreLauncherItems } from '../engines/epic-launcher.js';
|
|
22
19
|
import { resetUnrealStatus, updateUnrealStatus } from '../engines/unreal-status.js';
|
|
23
20
|
import { SIGNERS, signedInstaller } from '../installers.js';
|
|
@@ -71,18 +68,6 @@ async function checkVisualStudio() {
|
|
|
71
68
|
fs.rmSync(dir, { recursive: true, force: true });
|
|
72
69
|
}
|
|
73
70
|
}
|
|
74
|
-
/**
|
|
75
|
-
* UnrealBuildTool's per-user BuildConfiguration.xml (%APPDATA%, wiped with the sandbox): compiles at once from the
|
|
76
|
-
* sandbox's build budget, so a build start cannot outrun the memory Windows backs the sandbox with. Returns the cap.
|
|
77
|
-
*/
|
|
78
|
-
function capUnrealBuilds() {
|
|
79
|
-
const cpus = usableCpus(os.cpus().length, session.cpuLimitPercent ?? null);
|
|
80
|
-
const jobs = buildJobs(cpus, os.totalmem() / 1024 ** 3);
|
|
81
|
-
const appData = process.env.APPDATA ?? 'C:\\Users\\WDAGUtilityAccount\\AppData\\Roaming';
|
|
82
|
-
const dir = mkdirp(path.join(appData, 'Unreal Engine', 'UnrealBuildTool'));
|
|
83
|
-
fs.writeFileSync(path.join(dir, 'BuildConfiguration.xml'), ubtBuildConfiguration(jobs), 'utf8');
|
|
84
|
-
return jobs;
|
|
85
|
-
}
|
|
86
71
|
/** Runs one part, warning (not failing the step) when it does not work. */
|
|
87
72
|
async function part(what, work, fallback) {
|
|
88
73
|
try {
|
|
@@ -101,9 +86,6 @@ export async function setupUnreal(env, config, _ctx) {
|
|
|
101
86
|
return;
|
|
102
87
|
}
|
|
103
88
|
resetUnrealStatus();
|
|
104
|
-
const jobs = await part('capping parallel compiles', async () => capUnrealBuilds(), 0);
|
|
105
|
-
if (jobs)
|
|
106
|
-
log('Unreal: UnrealBuildTool runs at most ' + jobs + ' compiles at once (the build budget)');
|
|
107
89
|
await part('the DirectX End-User Runtime', installDirectX, undefined);
|
|
108
90
|
const launcher = await part('the Epic Games Launcher', ensureLauncher, null);
|
|
109
91
|
// Before anyone opens the launcher: engines go to the data disk, and it knows the ones it installed there before.
|
|
@@ -149,7 +131,6 @@ export async function setupUnreal(env, config, _ctx) {
|
|
|
149
131
|
vs.version +
|
|
150
132
|
(check.ok ? ' (UnrealBuildTool finds it)' : ' (UnrealBuildTool would NOT find it)')
|
|
151
133
|
: 'no Visual Studio') +
|
|
152
|
-
(jobs ? ', at most ' + jobs + ' compiles at once' : '') +
|
|
153
134
|
', DirectX End-User Runtime' +
|
|
154
135
|
(launcher ? ', Epic Games Launcher' : '') +
|
|
155
136
|
(debuggers ? ', cdb/windbg' : ''));
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { fingerprint, SERVICE_KINDS, SERVICE_SPECS, serviceWanted, } from '../../shared/accounts/index.js';
|
|
1
|
+
import { appsNeededFor, fingerprint, SERVICE_KINDS, SERVICE_SPECS, serviceWanted, usedByText, } from '../../shared/accounts/index.js';
|
|
2
2
|
import { setNameOf } from '../storage/index.js';
|
|
3
3
|
import { takeHandoffs } from './handoff.js';
|
|
4
4
|
import { AccountLeases } from './leases.js';
|
|
@@ -62,8 +62,22 @@ export function prepareServiceAccounts(i) {
|
|
|
62
62
|
}
|
|
63
63
|
for (const kind of SERVICE_KINDS) {
|
|
64
64
|
const spec = SERVICE_SPECS[kind];
|
|
65
|
-
if (!i.config.accounts[kind].share
|
|
65
|
+
if (!i.config.accounts[kind].share)
|
|
66
66
|
continue;
|
|
67
|
+
if (!serviceWanted(kind, i.on)) {
|
|
68
|
+
const apps = appsNeededFor(kind);
|
|
69
|
+
if (apps.length)
|
|
70
|
+
notes.push('Your ' +
|
|
71
|
+
spec.label +
|
|
72
|
+
' sign-in is on (accounts.' +
|
|
73
|
+
kind +
|
|
74
|
+
'.share) but ' +
|
|
75
|
+
usedByText(spec.usedBy) +
|
|
76
|
+
' is not installed, so there is nothing to hand it to. Fix: turn the app on in Apps (apps.install: ' +
|
|
77
|
+
apps.join(', ') +
|
|
78
|
+
').');
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
67
81
|
const off = i.others.find((o) => !o.config.accounts[kind].share);
|
|
68
82
|
if (off) {
|
|
69
83
|
notes.push('Your ' + spec.label + ' sign-in was not handed over: ' + off.name + ' (also in this sandbox) turns it off.');
|
|
@@ -12,6 +12,8 @@ export declare const spawnStoreRunner: StoreRunner;
|
|
|
12
12
|
/** What an app needs fetched (catalogue source.store). */
|
|
13
13
|
export interface StoreApp {
|
|
14
14
|
readonly id: string;
|
|
15
|
+
/** The name the notes use (the catalogue's); the id when not given. */
|
|
16
|
+
readonly name?: string;
|
|
15
17
|
readonly productId: string;
|
|
16
18
|
readonly packageName: string;
|
|
17
19
|
}
|
|
@@ -10,7 +10,7 @@ import { spawn } from 'node:child_process';
|
|
|
10
10
|
import { z } from 'zod';
|
|
11
11
|
import { assertNoLinks, errMsg } from '../../core/util/index.js';
|
|
12
12
|
import { readValid } from '../../shared/io/index.js';
|
|
13
|
-
import { isStorePackageFile, STORE_PACKAGE_MANIFEST, StorePackageManifestSchema,
|
|
13
|
+
import { isStorePackageFile, STORE_PACKAGE_MANIFEST, StorePackageManifestSchema, assignStoreRoles, storePackageFitsX64, } from '../../shared/apps/index.js';
|
|
14
14
|
import { SYSTEM } from '../../shared/system.js';
|
|
15
15
|
import { nativeScript } from '../native.js';
|
|
16
16
|
import { hostDataFolder } from '../storage/contained/index.js';
|
|
@@ -24,6 +24,8 @@ const ScriptResultSchema = z.object({
|
|
|
24
24
|
ok: z.boolean(),
|
|
25
25
|
message: z.string().max(2000),
|
|
26
26
|
files: z.array(z.string().max(300)).max(64),
|
|
27
|
+
/** The files that came from winget's Dependencies folder (the frameworks); the rest is the app. */
|
|
28
|
+
dependencies: z.array(z.string().max(300)).max(64).default([]),
|
|
27
29
|
});
|
|
28
30
|
export const spawnStoreRunner = (exe, args, timeoutMs) => new Promise((resolve) => {
|
|
29
31
|
const child = spawn(exe, [...args], { windowsHide: true });
|
|
@@ -117,16 +119,15 @@ export class StorePackages {
|
|
|
117
119
|
}
|
|
118
120
|
if (!parsed.ok)
|
|
119
121
|
throw new Error(parsed.message);
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
|
|
122
|
+
const frameworks = new Set(parsed.dependencies.map((n) => n.toLowerCase()));
|
|
123
|
+
// Only the builds that run on this sandbox (64-bit): the 32-bit and ARM frameworks winget may add are left out.
|
|
124
|
+
const names = parsed.files.filter((n) => isStorePackageFile(n) &&
|
|
125
|
+
n === path.basename(n) &&
|
|
126
|
+
fs.existsSync(path.join(work, n)) &&
|
|
127
|
+
!(frameworks.has(n.toLowerCase()) && !storePackageFitsX64(n)));
|
|
128
|
+
const files = assignStoreRoles(names, app.packageName, parsed.dependencies).map(({ name, role }) => {
|
|
123
129
|
const full = path.join(work, name);
|
|
124
|
-
return {
|
|
125
|
-
name,
|
|
126
|
-
role: storePackageRole(name, app.packageName),
|
|
127
|
-
sha256: sha256Of(full),
|
|
128
|
-
bytes: fs.statSync(full).size,
|
|
129
|
-
};
|
|
130
|
+
return { name, role, sha256: sha256Of(full), bytes: fs.statSync(full).size };
|
|
130
131
|
});
|
|
131
132
|
const manifest = StorePackageManifestSchema.parse({
|
|
132
133
|
productId: app.productId,
|
|
@@ -197,12 +198,12 @@ export async function prepareStorePackages(apps, o) {
|
|
|
197
198
|
notes.push(app.id + ': package ' + note + ' (' + manifest.files.length + ' files)');
|
|
198
199
|
}
|
|
199
200
|
catch (e) {
|
|
200
|
-
notes.push(app.id +
|
|
201
|
-
'
|
|
202
|
-
errMsg(e) +
|
|
203
|
-
' Fix: sign in to the Microsoft Store on this PC and check that winget works (winget download --id ' +
|
|
201
|
+
notes.push((app.name ?? app.id) +
|
|
202
|
+
': NOT installed in this sandbox. ' +
|
|
203
|
+
errMsg(e).replace(/\.$/, '') +
|
|
204
|
+
'. Fix: sign in to the Microsoft Store on this PC and check that winget works (winget download --id ' +
|
|
204
205
|
app.productId +
|
|
205
|
-
' --source msstore), then launch again.');
|
|
206
|
+
' --source msstore --architecture x64 --download-directory <an empty folder>), then launch again.');
|
|
206
207
|
}
|
|
207
208
|
}
|
|
208
209
|
return { notes };
|
|
@@ -61,7 +61,7 @@ export async function attachTerminal(session, o = {}) {
|
|
|
61
61
|
const endpoint = hostEndpoint(session) ??
|
|
62
62
|
fail("Refusing to connect: the sandbox reported no address on this PC's Hyper-V (vEthernet) network (" +
|
|
63
63
|
session.attach.addresses.join(', ') +
|
|
64
|
-
').', 'Use the sandbox window instead (
|
|
64
|
+
').', 'Use the sandbox window instead (sandbox show); if this keeps happening, run sandbox doctor.');
|
|
65
65
|
const socket = await connect(endpoint);
|
|
66
66
|
console.log('Attaching to ' +
|
|
67
67
|
safeText(session.harness) +
|
|
@@ -50,7 +50,7 @@ export class SandboxChannel {
|
|
|
50
50
|
fail('Windows Sandbox closed (or is not running).', 'Start it again with sandbox claude; sandbox logs shows what happened.');
|
|
51
51
|
await sleep(500);
|
|
52
52
|
}
|
|
53
|
-
fail(timeoutMessage, 'Check what the sandbox is doing with sandbox logs -f (or
|
|
53
|
+
fail(timeoutMessage, 'Check what the sandbox is doing with sandbox logs -f (or sandbox show to see its window).');
|
|
54
54
|
}
|
|
55
55
|
/** Deletes an answer once read; a planted link means the answer is read and nothing is deleted. */
|
|
56
56
|
dropResponse(requestId) {
|
|
@@ -73,7 +73,7 @@ export class SandboxChannel {
|
|
|
73
73
|
const status = this.run.readSandboxStatus();
|
|
74
74
|
if (!status || (launchId !== null && status.launchId !== launchId)) {
|
|
75
75
|
if (bootSilent(Date.now() - started, seen))
|
|
76
|
-
fail('The sandbox has not reported anything for ' + Math.round(FIRST_STATUS_MS / 60000) + ' minutes.', 'It may still be starting, or it cannot reach its folders. Look at its window (
|
|
76
|
+
fail('The sandbox has not reported anything for ' + Math.round(FIRST_STATUS_MS / 60000) + ' minutes.', 'It may still be starting, or it cannot reach its folders. Look at its window (sandbox show), the run folder ' +
|
|
77
77
|
this.run.root +
|
|
78
78
|
' and the data folder ' +
|
|
79
79
|
this.dataPath +
|
|
@@ -87,7 +87,7 @@ export class SandboxChannel {
|
|
|
87
87
|
last = status.message;
|
|
88
88
|
}
|
|
89
89
|
if (status.phase === 'failed')
|
|
90
|
-
fail('The sandbox failed to start: ' + status.message, 'See sandbox logs for details; the sandbox window shows the error too (
|
|
90
|
+
fail('The sandbox failed to start: ' + status.message, 'See sandbox logs for details; the sandbox window shows the error too (sandbox show if headless).');
|
|
91
91
|
return status.phase === 'ready' ? status : null;
|
|
92
92
|
}, 'Timed out waiting for the sandbox to finish provisioning.');
|
|
93
93
|
}
|
|
@@ -16,6 +16,7 @@ export const COMMANDS = new RegExp('^(' +
|
|
|
16
16
|
// this tool's own commands (package.json "bin")
|
|
17
17
|
'sandbox',
|
|
18
18
|
'windows-development-sandbox',
|
|
19
|
+
// Removed commands, still blocked: a planted claude-sbx.cmd would run on a PC where nothing else has that name.
|
|
19
20
|
'agent-sandbox',
|
|
20
21
|
'claude-sbx',
|
|
21
22
|
'claude-sandbox',
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const COMMAND_NAMES: readonly ["sandbox"
|
|
1
|
+
export declare const COMMAND_NAMES: readonly ["sandbox"];
|
|
2
2
|
export type CopyKind = 'managed-machine' | 'managed-user' | 'npm' | 'old-scripts' | 'other';
|
|
3
3
|
/** One folder on the PATH that holds the commands. */
|
|
4
4
|
export interface CommandCopy {
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
// Every copy of the sandbox commands on the PATH (sandbox
|
|
1
|
+
// Every copy of the sandbox commands on the PATH (sandbox), in the order Windows finds them:
|
|
2
2
|
// the machine PATH, then yours, and in each folder PATHEXT order. Read only: nothing is run, and a file is only checked
|
|
3
3
|
// for existing. Used by doctor to say which copy runs and how to remove each of the others.
|
|
4
4
|
import fs from 'node:fs';
|
|
5
5
|
import path from 'node:path';
|
|
6
6
|
import { expandVariables } from '../../core/util/index.js';
|
|
7
|
-
export const COMMAND_NAMES = ['sandbox'
|
|
7
|
+
export const COMMAND_NAMES = ['sandbox'];
|
|
8
8
|
const normal = (p) => path
|
|
9
9
|
.normalize(expandVariables(p.trim().replace(/^"|"$/g, '')))
|
|
10
10
|
.replace(/[\\/]+$/, '')
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { type InstallScope, InstallLayout, compareVersions, VERSION_PATTERN, NODE_PATTERN } from './layout.js';
|
|
2
|
-
export { shimText, readBins, writeShims } from './shims.js';
|
|
2
|
+
export { shimText, readBins, writeShims, removeStaleShims, REMOVED_COMMANDS } from './shims.js';
|
|
3
3
|
export { InstallRecordFile, InstallRecordSchema, type InstallRecord } from './install-record.js';
|
|
4
4
|
export { UpdateTask } from './scheduled-task.js';
|
|
5
5
|
export { machineInstall, userInstall, managedInstalls, describeCopy, hiddenInstallLine, takeHiddenInstallNotice, } from './copies.js';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { InstallLayout, compareVersions, VERSION_PATTERN, NODE_PATTERN } from './layout.js';
|
|
2
|
-
export { shimText, readBins, writeShims } from './shims.js';
|
|
2
|
+
export { shimText, readBins, writeShims, removeStaleShims, REMOVED_COMMANDS } from './shims.js';
|
|
3
3
|
export { InstallRecordFile, InstallRecordSchema } from './install-record.js';
|
|
4
4
|
export { UpdateTask } from './scheduled-task.js';
|
|
5
5
|
export { machineInstall, userInstall, managedInstalls, describeCopy, hiddenInstallLine, takeHiddenInstallNotice, } from './copies.js';
|
|
@@ -6,5 +6,12 @@ import type { InstallLayout } from './layout.js';
|
|
|
6
6
|
export declare function shimText(name: string, kind: 'cmd' | 'sh', target?: string): string;
|
|
7
7
|
/** The commands a package.json declares (name -> program path inside the package). Invalid names are refused. */
|
|
8
8
|
export declare function readBins(packageJson: unknown): Record<string, string>;
|
|
9
|
-
/**
|
|
9
|
+
/** Commands earlier versions installed (name -> program they ran). Their shims go when nothing declares them. */
|
|
10
|
+
export declare const REMOVED_COMMANDS: Readonly<Record<string, string>>;
|
|
11
|
+
/** Deletes the shim of a removed command, only when the file is exactly the text we wrote for it. */
|
|
12
|
+
export declare function removeStaleShims(layout: InstallLayout, keep?: Readonly<Record<string, string>>): void;
|
|
13
|
+
/**
|
|
14
|
+
* Writes <bin>.cmd and <bin> for every command in `bins` (name -> program path inside the package), and removes the
|
|
15
|
+
* shims of commands that no longer exist.
|
|
16
|
+
*/
|
|
10
17
|
export declare function writeShims(layout: InstallLayout, bins: Record<string, string>): void;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// The commands on PATH. Each one is a tiny script that reads the two pointer files and runs the version they name, so
|
|
2
2
|
// an update never has to rewrite a shim: it only moves the pointers. A .cmd for cmd.exe and PowerShell, and a POSIX
|
|
3
3
|
// script (same name, no extension) for Git Bash.
|
|
4
|
+
import fs from 'node:fs';
|
|
4
5
|
import path from 'node:path';
|
|
5
6
|
import { z } from 'zod';
|
|
6
7
|
import { fail } from '../../core/util/index.js';
|
|
@@ -46,10 +47,47 @@ export function readBins(packageJson) {
|
|
|
46
47
|
fail('package.json declares a command with an invalid name: ' + name);
|
|
47
48
|
return bins;
|
|
48
49
|
}
|
|
49
|
-
/**
|
|
50
|
+
/** Commands earlier versions installed (name -> program they ran). Their shims go when nothing declares them. */
|
|
51
|
+
export const REMOVED_COMMANDS = {
|
|
52
|
+
'agent-sandbox': 'dist/bin/agent-sandbox.js',
|
|
53
|
+
'claude-sbx': 'dist/bin/claude-sbx.js',
|
|
54
|
+
'claude-sandbox': 'dist/bin/claude-sbx.js',
|
|
55
|
+
'codex-sbx': 'dist/bin/codex-sbx.js',
|
|
56
|
+
'codex-sandbox': 'dist/bin/codex-sbx.js',
|
|
57
|
+
'gemini-sbx': 'dist/bin/gemini-sbx.js',
|
|
58
|
+
'gemini-sandbox': 'dist/bin/gemini-sbx.js',
|
|
59
|
+
'claude-host': 'dist/bin/claude-host.js',
|
|
60
|
+
'sbx-show': 'dist/bin/sbx-show.js',
|
|
61
|
+
'sbx-hide': 'dist/bin/sbx-hide.js',
|
|
62
|
+
};
|
|
63
|
+
/** Deletes the shim of a removed command, only when the file is exactly the text we wrote for it. */
|
|
64
|
+
export function removeStaleShims(layout, keep = {}) {
|
|
65
|
+
for (const [name, target] of Object.entries(REMOVED_COMMANDS)) {
|
|
66
|
+
if (name in keep)
|
|
67
|
+
continue;
|
|
68
|
+
for (const [file, kind] of [
|
|
69
|
+
[name + '.cmd', 'cmd'],
|
|
70
|
+
[name, 'sh'],
|
|
71
|
+
]) {
|
|
72
|
+
const full = path.join(layout.binDir, file);
|
|
73
|
+
try {
|
|
74
|
+
if (fs.readFileSync(full, 'utf8') === shimText(name, kind, target))
|
|
75
|
+
fs.rmSync(full, { force: true });
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
// Not there, or not readable: nothing of ours to remove.
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Writes <bin>.cmd and <bin> for every command in `bins` (name -> program path inside the package), and removes the
|
|
85
|
+
* shims of commands that no longer exist.
|
|
86
|
+
*/
|
|
50
87
|
export function writeShims(layout, bins) {
|
|
51
88
|
for (const [name, target] of Object.entries(bins)) {
|
|
52
89
|
writeFileAtomic(path.join(layout.binDir, name + '.cmd'), shimText(name, 'cmd', target));
|
|
53
90
|
writeFileAtomic(path.join(layout.binDir, name), shimText(name, 'sh', target));
|
|
54
91
|
}
|
|
92
|
+
removeStaleShims(layout, bins);
|
|
55
93
|
}
|
|
@@ -15,6 +15,7 @@ import { folderAttachment } from '../storage/providers/index.js';
|
|
|
15
15
|
import { assertTestWritable } from '../test-guard.js';
|
|
16
16
|
import { writeLoadSettings } from '../life/watchdog.js';
|
|
17
17
|
import { loadProfileOf } from './risk-notes.js';
|
|
18
|
+
import { launchHasSandboxSource, stagePcLogs } from './pc-logs.js';
|
|
18
19
|
import { planBootProjects } from './boot-projects.js';
|
|
19
20
|
import { planDatabases } from './database-plan.js';
|
|
20
21
|
import { collectMappings } from './mappings.js';
|
|
@@ -135,6 +136,9 @@ opened) {
|
|
|
135
136
|
fs.writeFileSync(path.join(launchPath, 'env.cmd'), logonEnv(session.workspace), 'utf8');
|
|
136
137
|
copyRuntime(o.packageRoot, launchPath);
|
|
137
138
|
stageHooks(o.packageRoot, launchPath, o.run.scripts);
|
|
139
|
+
// Developing the sandbox inside a sandbox: this PC's recent crash reports and logs (never for other projects).
|
|
140
|
+
if (launchHasSandboxSource([workspaceHost, ...boot.projects.map((p) => p.host)], o.packageRoot))
|
|
141
|
+
stagePcLogs(storageRoot, dataPath, launchPath);
|
|
138
142
|
const enginePlan = engine.plan(buildLaunchPlan({
|
|
139
143
|
config,
|
|
140
144
|
memoryMb,
|
|
@@ -10,3 +10,4 @@ export { msvcHint, projectEntries, readProjectFiles, uvHint } from './project-sc
|
|
|
10
10
|
export { hostDatabases, pcLanguages } from './pc-scan.js';
|
|
11
11
|
export { collectMappings } from './mappings.js';
|
|
12
12
|
export { RUNTIME_FILES, copyRuntime, gitIdentity, stageGitIdentity, stageHooks, stageScripts } from './stage.js';
|
|
13
|
+
export { isSandboxSourceRepo, launchHasSandboxSource, stagePcLogs } from './pc-logs.js';
|
|
@@ -10,3 +10,4 @@ export { msvcHint, projectEntries, readProjectFiles, uvHint } from './project-sc
|
|
|
10
10
|
export { hostDatabases, pcLanguages } from './pc-scan.js';
|
|
11
11
|
export { collectMappings } from './mappings.js';
|
|
12
12
|
export { RUNTIME_FILES, copyRuntime, gitIdentity, stageGitIdentity, stageHooks, stageScripts } from './stage.js';
|
|
13
|
+
export { isSandboxSourceRepo, launchHasSandboxSource, stagePcLogs } from './pc-logs.js';
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Whether a folder is a checkout (or git worktree) of this sandbox's own source repo, judged by the installed copy. */
|
|
2
|
+
export declare function isSandboxSourceRepo(folder: string, packageRoot: string): boolean;
|
|
3
|
+
/** Whether this launch has the sandbox's own source repo among its projects (the launch folder or a mapped project). */
|
|
4
|
+
export declare function launchHasSandboxSource(folders: readonly string[], packageRoot: string): boolean;
|
|
5
|
+
/** Copies the PC's recent crash reports and log tails into <launchPath>\pc-logs. Returns the folder. */
|
|
6
|
+
export declare function stagePcLogs(storageRoot: string, dataPath: string, launchPath: string): string;
|