windows-development-sandbox 1.0.5 → 1.1.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/LICENSE +21 -0
- package/README.md +22 -6
- package/config.example.json +17 -0
- package/dist/api.js +2 -1
- package/dist/cli/args.d.ts +20 -0
- package/dist/cli/args.js +35 -3
- package/dist/cli/colors.d.ts +24 -0
- package/dist/cli/colors.js +42 -0
- package/dist/cli/commands/agent-commands.d.ts +6 -0
- package/dist/cli/commands/agent-commands.js +61 -0
- package/dist/cli/commands/context.d.ts +2 -0
- package/dist/cli/commands/context.js +3 -0
- package/dist/cli/commands/data/clean.d.ts +15 -0
- package/dist/cli/commands/data/clean.js +101 -0
- package/dist/cli/commands/data/collect.d.ts +11 -0
- package/dist/cli/commands/data/collect.js +54 -0
- package/dist/cli/commands/data/index.d.ts +4 -0
- package/dist/cli/commands/data/index.js +45 -0
- package/dist/cli/commands/data/remove.d.ts +2 -0
- package/dist/cli/commands/data/remove.js +30 -0
- package/dist/cli/commands/index.d.ts +5 -3
- package/dist/cli/commands/index.js +5 -3
- package/dist/cli/commands/launch.d.ts +26 -2
- package/dist/cli/commands/launch.js +220 -39
- package/dist/cli/commands/maintenance.js +33 -4
- package/dist/cli/commands/project.d.ts +29 -0
- package/dist/cli/commands/project.js +63 -0
- package/dist/cli/commands/projects.d.ts +14 -0
- package/dist/cli/commands/projects.js +146 -0
- package/dist/cli/commands/repair/checks.d.ts +2 -0
- package/dist/cli/commands/{repair.js → repair/checks.js} +19 -181
- package/dist/cli/commands/{repair.d.ts → repair/finders.d.ts} +0 -29
- package/dist/cli/commands/repair/finders.js +108 -0
- package/dist/cli/commands/repair/index.d.ts +12 -0
- package/dist/cli/commands/repair/index.js +61 -0
- package/dist/cli/commands/repair/types.d.ts +26 -0
- package/dist/cli/commands/repair/types.js +3 -0
- package/dist/cli/commands/restart.d.ts +6 -0
- package/dist/cli/commands/restart.js +85 -0
- package/dist/cli/commands/sessions.d.ts +2 -1
- package/dist/cli/commands/sessions.js +189 -45
- package/dist/cli/commands/setup/checks.d.ts +13 -0
- package/dist/cli/commands/setup/checks.js +71 -0
- package/dist/cli/commands/{setup.d.ts → setup/facts.d.ts} +1 -15
- package/dist/cli/commands/setup/facts.js +60 -0
- package/dist/cli/commands/setup/index.d.ts +2 -0
- package/dist/cli/commands/setup/index.js +43 -0
- package/dist/cli/commands/user-config.d.ts +8 -0
- package/dist/cli/commands/user-config.js +41 -0
- package/dist/cli/history.d.ts +11 -0
- package/dist/cli/history.js +21 -0
- package/dist/cli/index.d.ts +7 -1
- package/dist/cli/index.js +38 -13
- package/dist/cli/manifest.d.ts +8 -0
- package/dist/cli/manifest.js +35 -15
- package/dist/cli/menu-multi.d.ts +12 -0
- package/dist/cli/menu-multi.js +82 -0
- package/dist/cli/menu.d.ts +5 -13
- package/dist/cli/menu.js +149 -46
- package/dist/cli/progress.d.ts +9 -0
- package/dist/cli/progress.js +49 -0
- package/dist/cli/settings/edit.d.ts +13 -0
- package/dist/cli/settings/edit.js +19 -0
- package/dist/cli/settings/index.d.ts +8 -0
- package/dist/cli/settings/index.js +131 -0
- package/dist/cli/settings/prompts.d.ts +7 -0
- package/dist/cli/settings/prompts.js +48 -0
- package/dist/cli/settings/schema.d.ts +33 -0
- package/dist/cli/settings/schema.js +29 -0
- package/dist/cli/validate.d.ts +17 -0
- package/dist/cli/validate.js +89 -0
- package/dist/core/config.js +4 -1
- package/dist/core/disk.d.ts +16 -0
- package/dist/core/disk.js +55 -0
- package/dist/core/local-config.d.ts +8 -0
- package/dist/core/local-config.js +18 -5
- package/dist/core/project-policy.d.ts +15 -0
- package/dist/core/project-policy.js +35 -0
- package/dist/core/prompt.d.ts +4 -0
- package/dist/core/prompt.js +15 -0
- package/dist/core/safe-paths.d.ts +4 -0
- package/dist/core/safe-paths.js +37 -0
- package/dist/core/schema/common.d.ts +17 -0
- package/dist/core/schema/common.js +15 -0
- package/dist/core/schema/config.d.ts +594 -0
- package/dist/core/schema/config.js +63 -0
- package/dist/core/schema/databases.d.ts +33 -0
- package/dist/core/schema/databases.js +35 -0
- package/dist/core/schema/harness.d.ts +12 -0
- package/dist/core/schema/harness.js +17 -0
- package/dist/core/schema/json-schema.d.ts +5 -0
- package/dist/core/schema/json-schema.js +25 -0
- package/dist/core/schema/profile.d.ts +27 -0
- package/dist/core/schema/profile.js +29 -0
- package/dist/core/schema/projects.d.ts +9 -0
- package/dist/core/schema/projects.js +11 -0
- package/dist/core/schema/sandbox.d.ts +50 -0
- package/dist/core/schema/sandbox.js +36 -0
- package/dist/core/schema/toolchains.d.ts +93 -0
- package/dist/core/schema/toolchains.js +83 -0
- package/dist/core/schema.d.ts +6 -604
- package/dist/core/schema.js +8 -264
- package/dist/core/types.d.ts +29 -1
- package/dist/core/util.d.ts +7 -0
- package/dist/core/util.js +12 -3
- package/dist/core/workspace.d.ts +0 -5
- package/dist/core/workspace.js +12 -46
- package/dist/guest/boot-env.d.ts +4 -0
- package/dist/guest/boot-env.js +25 -0
- package/dist/guest/cmdline.d.ts +12 -0
- package/dist/guest/cmdline.js +23 -0
- package/dist/guest/daemon/exec.d.ts +5 -0
- package/dist/guest/daemon/exec.js +32 -0
- package/dist/guest/daemon/project.d.ts +14 -0
- package/dist/guest/daemon/project.js +132 -0
- package/dist/guest/daemon/session.d.ts +13 -0
- package/dist/guest/daemon/session.js +139 -0
- package/dist/guest/daemon/shutdown.d.ts +1 -0
- package/dist/guest/daemon/shutdown.js +21 -0
- package/dist/guest/daemon/state.d.ts +17 -0
- package/dist/guest/daemon/state.js +31 -0
- package/dist/guest/daemon.js +8 -141
- package/dist/guest/db.js +18 -3
- package/dist/guest/env.d.ts +2 -0
- package/dist/guest/env.js +4 -2
- package/dist/guest/guide.d.ts +6 -2
- package/dist/guest/guide.js +5 -4
- package/dist/guest/helpers.d.ts +3 -0
- package/dist/guest/helpers.js +26 -0
- package/dist/guest/main.js +17 -7
- package/dist/guest/ports.d.ts +6 -0
- package/dist/guest/ports.js +22 -0
- package/dist/guest/project-context.d.ts +7 -0
- package/dist/guest/project-context.js +10 -0
- package/dist/guest/project-env.d.ts +20 -0
- package/dist/guest/project-env.js +20 -0
- package/dist/guest/project-status.d.ts +5 -0
- package/dist/guest/project-status.js +17 -0
- package/dist/guest/ptyhost.d.ts +0 -18
- package/dist/guest/ptyhost.js +5 -63
- package/dist/guest/setup/agents.d.ts +5 -2
- package/dist/guest/setup/agents.js +22 -8
- package/dist/guest/setup/claude.js +11 -3
- package/dist/guest/setup/environment.d.ts +13 -3
- package/dist/guest/setup/environment.js +14 -10
- package/dist/guest/setup/hooks.d.ts +9 -1
- package/dist/guest/setup/hooks.js +28 -14
- package/dist/guest/toolchains/catalogue.d.ts +4 -0
- package/dist/guest/toolchains/catalogue.js +2 -2
- package/dist/guest/toolchains/cli.js +25 -17
- package/dist/guest/toolchains/common.d.ts +18 -1
- package/dist/guest/toolchains/common.js +34 -11
- package/dist/guest/toolchains/databases/dynamodb.d.ts +8 -0
- package/dist/guest/toolchains/databases/dynamodb.js +52 -0
- package/dist/guest/toolchains/{databases.d.ts → databases/index.d.ts} +2 -3
- package/dist/guest/toolchains/databases/index.js +62 -0
- package/dist/guest/toolchains/databases/mongodb.d.ts +3 -0
- package/dist/guest/toolchains/databases/mongodb.js +31 -0
- package/dist/guest/toolchains/databases/mysql.d.ts +3 -0
- package/dist/guest/toolchains/databases/mysql.js +38 -0
- package/dist/guest/toolchains/databases/postgres.d.ts +5 -0
- package/dist/guest/toolchains/databases/postgres.js +38 -0
- package/dist/guest/toolchains/databases/redis.d.ts +3 -0
- package/dist/guest/toolchains/databases/redis.js +16 -0
- package/dist/guest/toolchains/databases/server.d.ts +14 -0
- package/dist/guest/toolchains/databases/server.js +52 -0
- package/dist/guest/toolchains/git.js +25 -16
- package/dist/guest/toolchains/go.js +1 -1
- package/dist/guest/toolchains/index.d.ts +13 -9
- package/dist/guest/toolchains/index.js +40 -82
- package/dist/guest/toolchains/java.js +5 -5
- package/dist/guest/toolchains/leftovers.d.ts +2 -0
- package/dist/guest/toolchains/leftovers.js +26 -0
- package/dist/guest/toolchains/managers.js +12 -10
- package/dist/guest/toolchains/{binstall.js → native/binstall.js} +6 -9
- package/dist/guest/toolchains/native/cpp.d.ts +4 -0
- package/dist/guest/toolchains/native/cpp.js +38 -0
- package/dist/guest/toolchains/native/dotnet.d.ts +4 -0
- package/dist/guest/toolchains/native/dotnet.js +103 -0
- package/dist/guest/toolchains/native/mesa.d.ts +7 -0
- package/dist/guest/toolchains/native/mesa.js +46 -0
- package/dist/guest/toolchains/native/msvc/environment.d.ts +16 -0
- package/dist/guest/toolchains/native/msvc/environment.js +57 -0
- package/dist/guest/toolchains/native/msvc/index.d.ts +6 -0
- package/dist/guest/toolchains/native/msvc/index.js +48 -0
- package/dist/guest/toolchains/native/msvc/packages.d.ts +6 -0
- package/dist/guest/toolchains/native/msvc/packages.js +156 -0
- package/dist/guest/toolchains/native/msvc/tree.d.ts +15 -0
- package/dist/guest/toolchains/native/msvc/tree.js +21 -0
- package/dist/guest/toolchains/native/rust.d.ts +3 -0
- package/dist/guest/toolchains/native/rust.js +45 -0
- package/dist/guest/toolchains/node.js +26 -6
- package/dist/guest/toolchains/plan.d.ts +22 -0
- package/dist/guest/toolchains/plan.js +96 -0
- package/dist/guest/toolchains/pwsh.js +1 -1
- package/dist/guest/toolchains/python.js +17 -10
- package/dist/guest/toolchains/runtimes.d.ts +2 -0
- package/dist/guest/toolchains/runtimes.js +6 -8
- package/dist/guest/toolchains/scripting/php.d.ts +3 -0
- package/dist/guest/toolchains/scripting/php.js +71 -0
- package/dist/guest/toolchains/scripting/ruby.d.ts +3 -0
- package/dist/guest/toolchains/scripting/ruby.js +47 -0
- package/dist/guest/toolchains/scripting/vcruntime.d.ts +7 -0
- package/dist/guest/toolchains/scripting/vcruntime.js +45 -0
- package/dist/guest/view.js +21 -1
- package/dist/host/accounts.js +14 -5
- package/dist/host/agent-commands.d.ts +25 -0
- package/dist/host/agent-commands.js +145 -0
- package/dist/host/attach.d.ts +17 -0
- package/dist/host/attach.js +69 -0
- package/dist/host/channel.d.ts +23 -22
- package/dist/host/channel.js +66 -84
- package/dist/host/claude-profile.js +7 -3
- package/dist/host/cleanup.js +2 -11
- package/dist/host/database-plan.d.ts +12 -0
- package/dist/host/database-plan.js +52 -0
- package/dist/host/generate.d.ts +4 -12
- package/dist/host/generate.js +48 -135
- package/dist/host/join.d.ts +28 -0
- package/dist/host/join.js +76 -0
- package/dist/host/launch/mappings.d.ts +3 -0
- package/dist/host/launch/mappings.js +50 -0
- package/dist/host/launch/projects.d.ts +15 -0
- package/dist/host/launch/projects.js +56 -0
- package/dist/host/launch/stage.d.ts +11 -0
- package/dist/host/launch/stage.js +66 -0
- package/dist/host/launch/wsb.d.ts +10 -0
- package/dist/host/launch/wsb.js +34 -0
- package/dist/host/pc-scan.d.ts +6 -0
- package/dist/host/pc-scan.js +37 -0
- package/dist/host/platform.d.ts +2 -0
- package/dist/host/platform.js +13 -1
- package/dist/host/project-scan.d.ts +1 -17
- package/dist/host/project-scan.js +2 -86
- package/dist/host/projects.d.ts +100 -0
- package/dist/host/projects.js +147 -0
- package/dist/host/repo-guard.d.ts +15 -2
- package/dist/host/repo-guard.js +103 -12
- package/dist/host/terminal.js +3 -0
- package/dist/host/test-guard.d.ts +6 -0
- package/dist/host/test-guard.js +15 -0
- package/dist/host/window.js +7 -3
- package/dist/host/wsb.d.ts +37 -0
- package/dist/host/wsb.js +101 -0
- package/dist/runtime/daemon.js +10997 -2794
- package/dist/runtime/db.js +6428 -464
- package/dist/runtime/main.js +7359 -1135
- package/dist/runtime/view.js +5922 -47
- package/dist/selftest/accounts.test.d.ts +2 -0
- package/dist/selftest/accounts.test.js +44 -0
- package/dist/selftest/agent-commands.test.d.ts +2 -0
- package/dist/selftest/agent-commands.test.js +47 -0
- package/dist/selftest/channel.test.d.ts +2 -0
- package/dist/selftest/channel.test.js +37 -0
- package/dist/selftest/cli.test.d.ts +2 -2
- package/dist/selftest/cli.test.js +115 -27
- package/dist/selftest/cmdline.test.d.ts +2 -0
- package/dist/selftest/cmdline.test.js +45 -0
- package/dist/selftest/config.test.d.ts +2 -3
- package/dist/selftest/config.test.js +36 -72
- package/dist/selftest/data.test.d.ts +2 -2
- package/dist/selftest/data.test.js +77 -70
- package/dist/selftest/detect.test.d.ts +2 -2
- package/dist/selftest/detect.test.js +54 -38
- package/dist/selftest/fixtures.d.ts +44 -4
- package/dist/selftest/fixtures.js +40 -23
- package/dist/selftest/generate.test.d.ts +2 -0
- package/dist/selftest/generate.test.js +63 -0
- package/dist/selftest/git-access.test.d.ts +2 -0
- package/dist/selftest/git-access.test.js +26 -0
- package/dist/selftest/guest-boot.test.d.ts +2 -0
- package/dist/selftest/guest-boot.test.js +76 -0
- package/dist/selftest/harness.d.ts +44 -7
- package/dist/selftest/harness.js +62 -21
- package/dist/selftest/host-sim.test.d.ts +2 -0
- package/dist/selftest/host-sim.test.js +93 -0
- package/dist/selftest/hostile-input.test.d.ts +2 -0
- package/dist/selftest/hostile-input.test.js +54 -0
- package/dist/selftest/index.d.ts +8 -1
- package/dist/selftest/index.js +134 -35
- package/dist/selftest/links.test.d.ts +2 -0
- package/dist/selftest/links.test.js +50 -0
- package/dist/selftest/live-daemon.test.d.ts +2 -0
- package/dist/selftest/live-daemon.test.js +91 -0
- package/dist/selftest/live-git.test.d.ts +2 -0
- package/dist/selftest/live-git.test.js +60 -0
- package/dist/selftest/live-projects.test.d.ts +2 -0
- package/dist/selftest/live-projects.test.js +127 -0
- package/dist/selftest/live.d.ts +65 -0
- package/dist/selftest/live.js +109 -0
- package/dist/selftest/local-config.test.d.ts +2 -0
- package/dist/selftest/local-config.test.js +107 -0
- package/dist/selftest/mirror.test.d.ts +2 -3
- package/dist/selftest/mirror.test.js +92 -65
- package/dist/selftest/network.test.d.ts +2 -0
- package/dist/selftest/network.test.js +28 -0
- package/dist/selftest/personalization.test.d.ts +2 -0
- package/dist/selftest/personalization.test.js +53 -0
- package/dist/selftest/projects.test.d.ts +2 -0
- package/dist/selftest/projects.test.js +254 -0
- package/dist/selftest/repair.test.d.ts +2 -0
- package/dist/selftest/repair.test.js +42 -0
- package/dist/selftest/repo-guard.test.d.ts +2 -0
- package/dist/selftest/repo-guard.test.js +63 -0
- package/dist/selftest/secrets.test.d.ts +2 -0
- package/dist/selftest/secrets.test.js +71 -0
- package/dist/selftest/setup.test.d.ts +2 -2
- package/dist/selftest/setup.test.js +45 -22
- package/dist/selftest/suite.d.ts +13 -0
- package/dist/selftest/suite.js +1 -0
- package/dist/selftest/sync.test.d.ts +2 -3
- package/dist/selftest/sync.test.js +90 -78
- package/dist/selftest/test-guard.test.d.ts +2 -0
- package/dist/selftest/test-guard.test.js +49 -0
- package/dist/selftest/toolchains.test.d.ts +2 -0
- package/dist/selftest/toolchains.test.js +103 -0
- package/dist/selftest/workspace.test.d.ts +2 -0
- package/dist/selftest/workspace.test.js +53 -0
- package/dist/shared/download.d.ts +3 -1
- package/dist/shared/download.js +19 -4
- package/dist/shared/paths.d.ts +5 -0
- package/dist/shared/paths.js +6 -0
- package/dist/shared/projects.d.ts +53 -0
- package/dist/shared/projects.js +67 -0
- package/dist/shared/rundir.d.ts +10 -1
- package/dist/shared/rundir.js +12 -4
- package/dist/shared/safe-remove.js +11 -0
- package/dist/shared/schema.d.ts +443 -0
- package/dist/shared/schema.js +41 -2
- package/dist/shared/test-guard.d.ts +4 -0
- package/dist/shared/test-guard.js +37 -0
- package/dist/sync/claude-mem.js +3 -1
- package/dist/sync/history.d.ts +23 -12
- package/dist/sync/history.js +52 -20
- package/dist/sync/watcher.d.ts +8 -0
- package/dist/sync/watcher.js +23 -4
- package/package.json +6 -6
- package/sandbox/bootstrap.ps1 +258 -258
- package/sandbox/cpucap.ps1 +51 -0
- package/sandbox/logon.cmd +18 -16
- package/sandbox/openssl.cs +42 -0
- package/sandbox/ptyhost.cs +15 -4
- package/sandbox/trust.cmd +35 -35
- package/schemas/config.schema.json +100 -1
- package/schemas/sandbox-config.schema.json +17 -1
- package/dist/cli/commands/data.d.ts +0 -46
- package/dist/cli/commands/data.js +0 -249
- package/dist/cli/commands/setup.js +0 -152
- package/dist/cli/settings.d.ts +0 -11
- package/dist/cli/settings.js +0 -156
- package/dist/guest/toolchains/databases.js +0 -226
- package/dist/guest/toolchains/msvc.d.ts +0 -10
- package/dist/guest/toolchains/msvc.js +0 -216
- package/dist/guest/toolchains/native.d.ts +0 -12
- package/dist/guest/toolchains/native.js +0 -220
- package/dist/guest/toolchains/scripting.d.ts +0 -10
- package/dist/guest/toolchains/scripting.js +0 -137
- package/dist/selftest/guest.test.d.ts +0 -3
- package/dist/selftest/guest.test.js +0 -61
- package/dist/selftest/helpers.test.d.ts +0 -2
- package/dist/selftest/helpers.test.js +0 -125
- package/dist/selftest/host.test.d.ts +0 -2
- package/dist/selftest/host.test.js +0 -71
- package/dist/selftest/live.test.d.ts +0 -7
- package/dist/selftest/live.test.js +0 -127
- package/dist/selftest/security.test.d.ts +0 -3
- package/dist/selftest/security.test.js +0 -143
- /package/dist/guest/toolchains/{binstall.d.ts → native/binstall.d.ts} +0 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { DATABASES, detectDatabases } from '../core/detect-databases.js';
|
|
2
|
+
import { hostDatabases } from './pc-scan.js';
|
|
3
|
+
import { readProjectFiles } from './project-scan.js';
|
|
4
|
+
/** The version a compose file pins (postgres:16-alpine -> 16, mongo:7.0 -> 7.0), if any. */
|
|
5
|
+
const pinnedVersion = (files, image) => {
|
|
6
|
+
for (const file of files) {
|
|
7
|
+
const match = image.exec(file.text);
|
|
8
|
+
if (match?.[1] && /^\d+(\.\d+)?/.test(match[1]))
|
|
9
|
+
return /^\d+(\.\d+)?/.exec(match[1])[0];
|
|
10
|
+
}
|
|
11
|
+
return null;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* What to run in this sandbox: databases set to true, plus "auto" ones the project needs. SQL Server is reported (so
|
|
15
|
+
* the user hears about it) but cannot run inside Windows Sandbox.
|
|
16
|
+
*/
|
|
17
|
+
export const planDatabases = (config, workspace, probe = {}) => {
|
|
18
|
+
const autos = DATABASES.filter((db) => config[db].enabled === 'auto');
|
|
19
|
+
const files = autos.length ? probe.files ?? readProjectFiles(workspace) : [];
|
|
20
|
+
const detected = autos.length ? detectDatabases(files, probe.host ?? hostDatabases) : [];
|
|
21
|
+
const plan = databasePlanner(config, files.filter((f) => /compose/i.test(f.path)));
|
|
22
|
+
return DATABASES.flatMap((database) => {
|
|
23
|
+
const setting = config[database].enabled;
|
|
24
|
+
const found = detected.find((d) => d.database === database);
|
|
25
|
+
if (setting === true)
|
|
26
|
+
return [plan(database, ['enabled in config'])];
|
|
27
|
+
if (setting === 'auto' && found)
|
|
28
|
+
return [plan(database, found.reasons)];
|
|
29
|
+
return [];
|
|
30
|
+
});
|
|
31
|
+
};
|
|
32
|
+
/** Every database the sandbox can run, so `sbx-db start <name>` works for ones that did not start at boot. */
|
|
33
|
+
export const databaseCatalog = (config) => {
|
|
34
|
+
const plan = databasePlanner(config, []);
|
|
35
|
+
return DATABASES.filter((db) => db !== 'sqlserver').map((db) => plan(db, ['started with sbx-db']));
|
|
36
|
+
};
|
|
37
|
+
/** Version (a compose file's image tag wins), port and admin UI for one database. */
|
|
38
|
+
const databasePlanner = (config, compose) => {
|
|
39
|
+
const versions = {
|
|
40
|
+
postgres: pinnedVersion(compose, /image:\s*["']?(?:[\w.-]+\/)*postgres:([\w.-]+)/i) ?? (config.postgres.version === 'latest' ? 'latest' : config.postgres.version),
|
|
41
|
+
mysql: config.mysql.version,
|
|
42
|
+
mongodb: pinnedVersion(compose, /image:\s*["']?(?:[\w.-]+\/)*mongo:([\w.-]+)/i) ?? config.mongodb.version,
|
|
43
|
+
redis: 'latest',
|
|
44
|
+
dynamodb: 'latest',
|
|
45
|
+
sqlserver: '',
|
|
46
|
+
};
|
|
47
|
+
const ports = { postgres: config.postgres.port, mysql: config.mysql.port, mongodb: config.mongodb.port, redis: config.redis.port, dynamodb: config.dynamodb.port, sqlserver: 1433 };
|
|
48
|
+
return (database, reasons) => ({
|
|
49
|
+
database, reasons, version: versions[database], port: ports[database],
|
|
50
|
+
...(database === 'dynamodb' && config.dynamodb.admin ? { adminPort: config.dynamodb.adminPort } : {}),
|
|
51
|
+
});
|
|
52
|
+
};
|
package/dist/host/generate.d.ts
CHANGED
|
@@ -1,15 +1,7 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
readonly hostTools: "C:\\HostTools";
|
|
6
|
-
readonly mirror: "C:\\Mirror";
|
|
7
|
-
};
|
|
8
|
-
export declare const resolveMemoryMb: (sandbox: Config["sandbox"], freeMemoryBytes?: number) => number;
|
|
9
|
-
export declare const buildWsb: (config: Config, memoryMb: number, mappings: ResolvedMapping[]) => string;
|
|
10
|
-
export declare const RUNTIME_FILES: string[];
|
|
11
|
-
/** Only the identity from ~/.gitconfig: the rest can hold credentials (helpers, tokens in insteadOf URLs). */
|
|
12
|
-
export declare const gitIdentity: (gitconfig: string) => string;
|
|
1
|
+
import type { GenerateOptions, Generated } from '../core/types.js';
|
|
2
|
+
import { decideToolchains } from '../core/detect-toolchains.js';
|
|
3
|
+
/** Which toolchains the project needs ("auto" settings follow the project's files and what this PC has). */
|
|
4
|
+
export declare const decideProjectToolchains: (o: Pick<GenerateOptions, "run" | "pcLanguages">) => ReturnType<typeof decideToolchains>;
|
|
13
5
|
export declare const generateSandbox: (o: GenerateOptions) => Generated & {
|
|
14
6
|
personalization: string;
|
|
15
7
|
};
|
package/dist/host/generate.js
CHANGED
|
@@ -1,160 +1,71 @@
|
|
|
1
|
+
// Writes a launch folder for one sandbox: session.json (what the guest needs to know), the bundled runtime, hooks, the
|
|
2
|
+
// staged Claude settings and the .wsb file that starts Windows Sandbox. The pieces live in launch/.
|
|
1
3
|
import fs from 'node:fs';
|
|
2
|
-
import os from 'node:os';
|
|
3
4
|
import path from 'node:path';
|
|
4
5
|
import crypto from 'node:crypto';
|
|
5
|
-
import { assertNoGuestOverlap, userDir } from '../core/config.js';
|
|
6
|
-
import { capturePersonalization } from './personalize.js';
|
|
7
|
-
import { assertSafeMapping } from '../core/workspace.js';
|
|
8
|
-
import { isValidGuestPath, MIRROR_ROOT, sandboxFolder, substDrives } from '../shared/guest-paths.js';
|
|
9
|
-
import { hostClaudeDir, stageClaudeProfile } from './claude-profile.js';
|
|
10
6
|
import { decideToolchains, resolveToolchains } from '../core/detect-toolchains.js';
|
|
11
|
-
import { databaseCatalog, msvcHint, pcLanguages, planDatabases, projectEntries } from './project-scan.js';
|
|
12
7
|
import { readPwshScripts, usesPwsh } from '../core/detect-pwsh.js';
|
|
13
|
-
import { dirExists, ensureDir, fail,
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
+ '<ClipboardRedirection>' + toggle(s.clipboard) + '</ClipboardRedirection>'
|
|
35
|
-
+ '<AudioInput>' + toggle(s.audioInput) + '</AudioInput>'
|
|
36
|
-
+ '<VideoInput>' + toggle(s.videoInput) + '</VideoInput>'
|
|
37
|
-
+ '<PrinterRedirection>' + toggle(s.printerRedirection) + '</PrinterRedirection>'
|
|
38
|
-
+ '<ProtectedClient>' + toggle(s.protectedClient) + '</ProtectedClient>'
|
|
39
|
-
+ '<MappedFolders>' + folders + '</MappedFolders>'
|
|
40
|
-
+ '<LogonCommand><Command>' + xmlEscape(GUEST.launch + '\\sandbox\\logon.cmd') + '</Command></LogonCommand>'
|
|
41
|
-
+ '</Configuration>';
|
|
42
|
-
};
|
|
43
|
-
const collectMappings = (o, launchPath) => {
|
|
44
|
-
const { config, dataPath, workspaceHost, harnessName } = o.run;
|
|
45
|
-
assertGuestPath(config.guestWorkspace, 'guestWorkspace');
|
|
46
|
-
const mappings = [
|
|
47
|
-
{ host: launchPath, guest: GUEST.launch, readOnly: true },
|
|
48
|
-
{ host: dataPath, guest: GUEST.data, readOnly: false },
|
|
49
|
-
{ host: workspaceHost, guest: config.guestWorkspace, readOnly: config.readOnly },
|
|
50
|
-
];
|
|
51
|
-
for (const m of config.mappings) {
|
|
52
|
-
const host = m.host;
|
|
53
|
-
assertGuestPath(m.guest, 'mappings[].guest for ' + host);
|
|
54
|
-
if (!dirExists(host)) {
|
|
55
|
-
if (m.optional) {
|
|
56
|
-
console.warn('Skipping optional mapping (folder not found): ' + host);
|
|
57
|
-
continue;
|
|
58
|
-
}
|
|
59
|
-
fail('A mapped folder does not exist: ' + host, 'Create it, fix the path in your config, or mark the mapping "optional": true.');
|
|
60
|
-
}
|
|
61
|
-
assertSafeMapping(host, o.packageRoot, o.run.storageRoot);
|
|
62
|
-
mappings.push({ host, guest: m.guest, readOnly: m.readOnly });
|
|
63
|
-
}
|
|
64
|
-
const { probe } = o;
|
|
65
|
-
if (probe.nodeDir)
|
|
66
|
-
mappings.push({ host: probe.nodeDir, guest: GUEST.hostTools + '\\node', readOnly: true });
|
|
67
|
-
if (probe.harnessExe && harnessName !== 'none')
|
|
68
|
-
mappings.push({ host: probe.harnessExe.dir, guest: GUEST.hostTools + '\\agents\\' + harnessName, readOnly: true });
|
|
69
|
-
if (probe.gitDir)
|
|
70
|
-
mappings.push({ host: probe.gitDir, guest: GUEST.hostTools + '\\git', readOnly: true });
|
|
71
|
-
if (probe.nvmDir)
|
|
72
|
-
mappings.push({ host: probe.nvmDir, guest: GUEST.hostTools + '\\nvm', readOnly: true });
|
|
73
|
-
assertNoGuestOverlap(mappings);
|
|
74
|
-
// Same check on where the folders really are (F:\x and C:\Mirror\F\x are one place).
|
|
75
|
-
assertNoGuestOverlap(mappings.map((m) => ({ guest: sandboxFolder(m.guest) })));
|
|
76
|
-
return mappings;
|
|
77
|
-
};
|
|
78
|
-
/** The schema checks this already; the host re-checks what goes into the .wsb and the subst list anyway. */
|
|
79
|
-
const assertGuestPath = (guest, what) => {
|
|
80
|
-
if (!isValidGuestPath(guest))
|
|
81
|
-
fail(what + ' is not a usable sandbox folder: ' + guest, 'Use a folder under C:\\ (not Windows, Program Files, Users, AgentData, Mirror, ...) or on drive D: to Z:, e.g. C:\\Assets or F:\\SteamLibrary\\steamapps\\common\\Game.');
|
|
82
|
-
};
|
|
83
|
-
/** Package hooks first, then ~/.agent-sandbox/hooks (same file name = user's wins), then --script / "scripts". */
|
|
84
|
-
const stageHooks = (packageRoot, launchPath, scripts) => {
|
|
85
|
-
for (const stage of HOOK_STAGES) {
|
|
86
|
-
const target = path.join(launchPath, 'hooks', stage);
|
|
87
|
-
ensureDir(target);
|
|
88
|
-
for (const source of [path.join(packageRoot, 'hooks', stage), path.join(userDir(), 'hooks', stage)]) {
|
|
89
|
-
if (dirExists(source))
|
|
90
|
-
fs.cpSync(source, target, { recursive: true, force: true });
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
scripts.forEach((script, index) => {
|
|
94
|
-
if (!fileExists(script))
|
|
95
|
-
fail('Script file not found: ' + script, 'Check the --script path.');
|
|
96
|
-
fs.copyFileSync(script, path.join(launchPath, 'hooks', 'pre-agent', '90-extra-' + index + '-' + path.basename(script)));
|
|
97
|
-
});
|
|
98
|
-
};
|
|
99
|
-
export const RUNTIME_FILES = ['main.js', 'daemon.js', 'view.js', 'gitguard.js', 'db.js'];
|
|
100
|
-
/** The bundled sandbox runtime (dist/runtime: one self-contained file per entry point) and the sandbox/ scripts. */
|
|
101
|
-
const copyRuntime = (packageRoot, launchPath) => {
|
|
102
|
-
const runtime = path.join(packageRoot, 'dist', 'runtime');
|
|
103
|
-
const missing = RUNTIME_FILES.filter((f) => !fileExists(path.join(runtime, f)));
|
|
104
|
-
if (missing.length)
|
|
105
|
-
fail('The sandbox runtime is missing from ' + runtime + ' (' + missing.join(', ') + ').', 'Rebuild it: npm run build (or reinstall the package).');
|
|
106
|
-
ensureDir(path.join(launchPath, 'runtime'));
|
|
107
|
-
for (const file of RUNTIME_FILES)
|
|
108
|
-
fs.copyFileSync(path.join(runtime, file), path.join(launchPath, 'runtime', file));
|
|
109
|
-
fs.writeFileSync(path.join(launchPath, 'package.json'), '{ "type": "module" }\n', 'utf8');
|
|
110
|
-
fs.cpSync(path.join(packageRoot, 'sandbox'), path.join(launchPath, 'sandbox'), { recursive: true });
|
|
111
|
-
};
|
|
112
|
-
/** Only the identity from ~/.gitconfig: the rest can hold credentials (helpers, tokens in insteadOf URLs). */
|
|
113
|
-
export const gitIdentity = (gitconfig) => {
|
|
114
|
-
const values = {};
|
|
115
|
-
let section = '';
|
|
116
|
-
for (const line of gitconfig.split(/\r?\n/)) {
|
|
117
|
-
const header = /^\s*\[([^\]]+)\]/.exec(line);
|
|
118
|
-
if (header) {
|
|
119
|
-
section = header[1].trim().toLowerCase();
|
|
120
|
-
continue;
|
|
121
|
-
}
|
|
122
|
-
const pair = /^\s*(name|email)\s*=\s*(.*?)\s*$/i.exec(line);
|
|
123
|
-
if (section === 'user' && pair && !/[\r\n]/.test(pair[2]))
|
|
124
|
-
values[pair[1].toLowerCase()] = pair[2].replace(/^"(.*)"$/, '$1');
|
|
125
|
-
}
|
|
126
|
-
const lines = Object.entries(values).map(([k, v]) => '\t' + k + ' = ' + v);
|
|
127
|
-
return lines.length ? '[user]\n' + lines.join('\n') + '\n' : '';
|
|
8
|
+
import { dirExists, ensureDir, fail, writeJson } from '../core/util.js';
|
|
9
|
+
import { substDrives } from '../shared/guest-paths.js';
|
|
10
|
+
import { PROJECTS_GUEST, hubName } from '../shared/projects.js';
|
|
11
|
+
import { planBootProjects } from './launch/projects.js';
|
|
12
|
+
import { hostClaudeDir, stageClaudeProfile } from './claude-profile.js';
|
|
13
|
+
import { databaseCatalog, planDatabases } from './database-plan.js';
|
|
14
|
+
import { collectMappings } from './launch/mappings.js';
|
|
15
|
+
import { copyRuntime, stageGitIdentity, stageHooks } from './launch/stage.js';
|
|
16
|
+
import { buildWsb, resolveMemoryMb } from './launch/wsb.js';
|
|
17
|
+
import { capturePersonalization } from './personalize.js';
|
|
18
|
+
import { pcLanguages } from './pc-scan.js';
|
|
19
|
+
import { msvcHint, projectEntries } from './project-scan.js';
|
|
20
|
+
import { assertTestWritable } from './test-guard.js';
|
|
21
|
+
const NO_PROFILE = { hostDir: null, staged: [], skipped: [], credentials: false, secretsRemoved: [] };
|
|
22
|
+
/** Which toolchains the project needs ("auto" settings follow the project's files and what this PC has). */
|
|
23
|
+
export const decideProjectToolchains = (o) => {
|
|
24
|
+
const { config, workspaceHost } = o.run;
|
|
25
|
+
const msvcScript = config.toolchains.msvc.enabled === 'auto' ? msvcHint(workspaceHost) : undefined;
|
|
26
|
+
const pwshScript = config.toolchains.pwsh.enabled === 'auto' ? usesPwsh(readPwshScripts(workspaceHost)) : null;
|
|
27
|
+
const hints = { ...(msvcScript ? { msvc: msvcScript } : {}), ...(pwshScript ? { pwsh: pwshScript } : {}) };
|
|
28
|
+
return decideToolchains(config.toolchains, projectEntries(workspaceHost), o.pcLanguages ?? pcLanguages(), hints);
|
|
128
29
|
};
|
|
30
|
+
/** What was found on this PC to copy instead of download. */
|
|
31
|
+
const hostToolsOf = (probe) => ({
|
|
32
|
+
node: Boolean(probe.nodeDir),
|
|
33
|
+
nodeVersion: probe.nodeDir || probe.nvmNode ? probe.hostNodeVersion.replace(/^v/, '') : null,
|
|
34
|
+
harnessExecutable: probe.harnessExe?.file ?? null,
|
|
35
|
+
git: Boolean(probe.gitDir),
|
|
36
|
+
nvm: Boolean(probe.nvmDir),
|
|
37
|
+
volta: Boolean(probe.volta),
|
|
38
|
+
pyenv: Boolean(probe.pyenv),
|
|
39
|
+
});
|
|
129
40
|
export const generateSandbox = (o) => {
|
|
130
41
|
const { config, dataPath, workspaceHost, harnessName, storageRoot } = o.run;
|
|
131
42
|
const harness = harnessName === 'none' ? null : config.harnesses[harnessName] ?? fail('Unknown agent: ' + harnessName, 'Configured agents: ' + Object.keys(config.harnesses).join(', '));
|
|
132
43
|
if (!dirExists(workspaceHost))
|
|
133
44
|
fail('The workspace folder does not exist: ' + workspaceHost, 'cd into the project first.');
|
|
45
|
+
assertTestWritable(storageRoot);
|
|
46
|
+
assertTestWritable(dataPath);
|
|
134
47
|
// Checked before anything is written, so a refusal leaves nothing behind.
|
|
135
48
|
const memoryMb = resolveMemoryMb(config.sandbox, o.freeMemoryBytes);
|
|
136
49
|
const launchId = Date.now() + '-' + crypto.randomBytes(3).toString('hex');
|
|
137
50
|
const launchPath = path.join(storageRoot, 'launches', launchId);
|
|
138
51
|
ensureDir(launchPath);
|
|
139
52
|
ensureDir(dataPath);
|
|
140
|
-
const
|
|
141
|
-
const
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
if (identity) {
|
|
145
|
-
ensureDir(path.join(launchPath, 'home'));
|
|
146
|
-
fs.writeFileSync(path.join(launchPath, 'home', '.gitconfig'), identity, 'utf8');
|
|
147
|
-
}
|
|
53
|
+
const boot = planBootProjects({ run: o.run, packageRoot: o.packageRoot, extraProjects: o.extraProjects, knownProjects: o.knownProjects });
|
|
54
|
+
const mappings = collectMappings(o, launchPath, boot);
|
|
55
|
+
const profile = harnessName === 'claude' ? stageClaudeProfile(config.claudeProfile, launchPath, workspaceHost, config.secrets) : NO_PROFILE;
|
|
56
|
+
stageGitIdentity(launchPath);
|
|
148
57
|
const personalization = config.personalization.enabled ? capturePersonalization(launchPath) : 'disabled';
|
|
149
58
|
const databases = o.databases ?? planDatabases(config.databases, workspaceHost);
|
|
150
|
-
const
|
|
151
|
-
const pwshScript = config.toolchains.pwsh.enabled === 'auto' ? usesPwsh(readPwshScripts(workspaceHost)) : null;
|
|
152
|
-
const toolchainDecisions = decideToolchains(config.toolchains, projectEntries(workspaceHost), o.pcLanguages ?? pcLanguages(), { ...(msvcScript ? { msvc: msvcScript } : {}), ...(pwshScript ? { pwsh: pwshScript } : {}) });
|
|
59
|
+
const toolchainDecisions = decideProjectToolchains(o);
|
|
153
60
|
const session = {
|
|
154
61
|
launchId,
|
|
155
62
|
project: path.basename(workspaceHost),
|
|
63
|
+
projectId: hubName(workspaceHost, []),
|
|
156
64
|
workspace: config.guestWorkspace,
|
|
157
65
|
workspaceHost,
|
|
66
|
+
projectsDir: PROJECTS_GUEST,
|
|
67
|
+
roots: boot.roots.map((r) => r.guest),
|
|
68
|
+
...(o.autoSession === false ? { autoSession: false } : {}),
|
|
158
69
|
harnessName,
|
|
159
70
|
harness,
|
|
160
71
|
harnessArguments: o.harnessArgs,
|
|
@@ -170,10 +81,12 @@ export const generateSandbox = (o) => {
|
|
|
170
81
|
versions: o.run.versions,
|
|
171
82
|
attach: config.attach,
|
|
172
83
|
hooks: config.hooks,
|
|
173
|
-
|
|
84
|
+
downloads: config.downloads,
|
|
85
|
+
hostTools: hostToolsOf(o.probe),
|
|
174
86
|
claudeProfile: { overwrite: config.claudeProfile.overwrite, items: profile.staged, credentials: profile.credentials, hostConfigDir: profile.hostDir ?? hostClaudeDir(config.claudeProfile), syncClaudeMem: config.claudeProfile.syncClaudeMem },
|
|
175
87
|
personalization: fs.existsSync(path.join(launchPath, 'personalize', 'personalize.reg')),
|
|
176
88
|
substDrives: substDrives(mappings.map((m) => m.guest)),
|
|
89
|
+
cpuLimitPercent: config.sandbox.cpuLimit === 'none' ? null : config.sandbox.cpuLimit,
|
|
177
90
|
};
|
|
178
91
|
const sessionPath = path.join(launchPath, 'session.json');
|
|
179
92
|
writeJson(sessionPath, session);
|
|
@@ -181,5 +94,5 @@ export const generateSandbox = (o) => {
|
|
|
181
94
|
stageHooks(o.packageRoot, launchPath, o.run.scripts);
|
|
182
95
|
const wsbPath = path.join(launchPath, 'agent.wsb');
|
|
183
96
|
fs.writeFileSync(wsbPath, buildWsb(config, memoryMb, mappings), 'utf8');
|
|
184
|
-
return { wsbPath, launchPath, launchId, sessionPath, memoryMb, mappings, profile, personalization, databases, toolchains: toolchainDecisions };
|
|
97
|
+
return { wsbPath, launchPath, launchId, sessionPath, memoryMb, mappings, profile, personalization, databases, toolchains: toolchainDecisions, projects: boot };
|
|
185
98
|
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { ResolvedRun } from '../core/types.js';
|
|
2
|
+
import { type ProjectSpecT } from '../shared/schema.js';
|
|
3
|
+
import { type ProjectMapper, type RegistryEntry } from './projects.js';
|
|
4
|
+
/** The launch folder of the running sandbox (host-only storage; its id comes from the guest, so it is checked). */
|
|
5
|
+
export declare const runningLaunchPath: (storageRoot: string, dataPath: string) => string | null;
|
|
6
|
+
/** What a session or command from this folder needs: where it is inside, and (for another project) its spec. */
|
|
7
|
+
export interface Joined {
|
|
8
|
+
project: RegistryEntry;
|
|
9
|
+
guestDir: string;
|
|
10
|
+
spec: ProjectSpecT | null;
|
|
11
|
+
run: ResolvedRun | null;
|
|
12
|
+
notes: string[];
|
|
13
|
+
}
|
|
14
|
+
/** The project spec the guest provisions from (scans the project folder like a launch does). */
|
|
15
|
+
export declare const projectSpec: (run: ResolvedRun, entry: RegistryEntry, hooks: boolean) => ProjectSpecT;
|
|
16
|
+
/**
|
|
17
|
+
* Makes `hostDir` usable in the running sandbox. The launch folder (or a folder below it) needs nothing; any other
|
|
18
|
+
* folder is resolved with `resolve` (the caller's resolveRun, with the approval prompt), checked, and registered.
|
|
19
|
+
*/
|
|
20
|
+
export declare const joinRunning: (o: {
|
|
21
|
+
storageRoot: string;
|
|
22
|
+
dataPath: string;
|
|
23
|
+
packageRoot: string;
|
|
24
|
+
hostDir: string;
|
|
25
|
+
wsbShare: boolean;
|
|
26
|
+
resolve: (projectRoot: string) => Promise<ResolvedRun>;
|
|
27
|
+
mapper?: ProjectMapper;
|
|
28
|
+
}) => Promise<Joined>;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// Joining the running sandbox from another folder. The folder's run is resolved exactly as a fresh launch would
|
|
2
|
+
// resolve it (its approved .sandbox-config.json, versions, toolchain detection, database plan), checked against the
|
|
3
|
+
// running sandbox's machine-wide protections, made visible inside (host/projects.ts: a project root, a project
|
|
4
|
+
// mapped at boot, or wsb share) and described to the guest, which provisions what is missing before its session.
|
|
5
|
+
// When none of those can make it visible, NeedsRestart reaches the caller, which offers a restart.
|
|
6
|
+
import fs from 'node:fs';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import { checkJoin, protectionsOf } from '../core/project-policy.js';
|
|
9
|
+
import { resolveToolchains } from '../core/detect-toolchains.js';
|
|
10
|
+
import { assertNoLinks, fail } from '../core/util.js';
|
|
11
|
+
import { Id } from '../shared/schema.js';
|
|
12
|
+
import { readSandboxStatus } from '../shared/rundir.js';
|
|
13
|
+
import { syncProjectIn } from '../sync/watcher.js';
|
|
14
|
+
import { planDatabases } from './database-plan.js';
|
|
15
|
+
import { decideProjectToolchains } from './generate.js';
|
|
16
|
+
import { projectHere, projectMapper, readRegistry, rememberProject, rootHere } from './projects.js';
|
|
17
|
+
import { stageScripts } from './launch/stage.js';
|
|
18
|
+
import { addRepoSnapshot } from './repo-guard.js';
|
|
19
|
+
/** The launch folder of the running sandbox (host-only storage; its id comes from the guest, so it is checked). */
|
|
20
|
+
export const runningLaunchPath = (storageRoot, dataPath) => {
|
|
21
|
+
const launchId = readSandboxStatus(dataPath)?.launchId;
|
|
22
|
+
if (!launchId || !/^\d+-[0-9a-f]{6}$/.test(launchId) || readRegistry(storageRoot)?.launchId !== launchId)
|
|
23
|
+
return null;
|
|
24
|
+
const launchPath = path.join(storageRoot, 'launches', launchId);
|
|
25
|
+
assertNoLinks(storageRoot, launchPath);
|
|
26
|
+
return fs.existsSync(path.join(launchPath, 'session.json')) ? launchPath : null;
|
|
27
|
+
};
|
|
28
|
+
/** The project spec the guest provisions from (scans the project folder like a launch does). */
|
|
29
|
+
export const projectSpec = (run, entry, hooks) => {
|
|
30
|
+
const { config } = run;
|
|
31
|
+
const toolchains = resolveToolchains(config.toolchains, decideProjectToolchains({ run }));
|
|
32
|
+
const harness = run.harnessName === 'none' ? null : config.harnesses[run.harnessName];
|
|
33
|
+
return {
|
|
34
|
+
name: Id.parse(entry.name), workspace: entry.guest, workspaceHost: entry.host, toolchains, versions: run.versions,
|
|
35
|
+
databases: planDatabases(config.databases, run.workspaceHost).slice(0, 8),
|
|
36
|
+
harnesses: harness ? { [run.harnessName]: harness } : {},
|
|
37
|
+
hooks,
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Makes `hostDir` usable in the running sandbox. The launch folder (or a folder below it) needs nothing; any other
|
|
42
|
+
* folder is resolved with `resolve` (the caller's resolveRun, with the approval prompt), checked, and registered.
|
|
43
|
+
*/
|
|
44
|
+
export const joinRunning = async (o) => {
|
|
45
|
+
const here = projectHere(o.storageRoot, o.hostDir);
|
|
46
|
+
if (here?.project.primary)
|
|
47
|
+
return { ...here, spec: null, run: null, notes: [] };
|
|
48
|
+
const registry = readRegistry(o.storageRoot) ?? fail('The running sandbox was started by an older version, so it cannot take another folder.', 'Stop it (sandbox stop) and start it again.');
|
|
49
|
+
// A folder below a project already in the sandbox uses that project; anything else becomes a project of its own.
|
|
50
|
+
const root = here?.project.host ?? path.resolve(o.hostDir);
|
|
51
|
+
const run = await o.resolve(root);
|
|
52
|
+
const check = checkJoin(registry.protections, protectionsOf(run.config));
|
|
53
|
+
if (check.stricter.length) {
|
|
54
|
+
fail('This project needs stricter protection than the running sandbox has, and those settings are machine-wide:\n ' + check.stricter.join('\n '), 'Its sessions cannot get them without changing them for the sessions already running. Stop the sandbox (sandbox stop) and start it from ' + root + '.');
|
|
55
|
+
}
|
|
56
|
+
const mapper = o.mapper ?? projectMapper({ storageRoot: o.storageRoot, packageRoot: o.packageRoot, wsbShare: o.wsbShare });
|
|
57
|
+
// Guarded from now on. A folder the sandbox could already write (inside a root, or mapped at boot) and that has no
|
|
58
|
+
// snapshot from the launch gets an empty one: what is there now may have been planted, so it counts as new.
|
|
59
|
+
// Anything else is snapshotted before it becomes visible.
|
|
60
|
+
addRepoSnapshot(o.dataPath, root, { empty: Boolean(here) || Boolean(rootHere(o.storageRoot, root)) });
|
|
61
|
+
const entry = mapper.register(root);
|
|
62
|
+
rememberProject(o.storageRoot, entry.host);
|
|
63
|
+
const launchPath = runningLaunchPath(o.storageRoot, o.dataPath);
|
|
64
|
+
// Your hooks in ~/.agent-sandbox/hooks ran at boot (once per sandbox); the project's own scripts ("scripts" in its
|
|
65
|
+
// .sandbox-config.json, --script) run before its first agent. The launch folder is read-only for the sandbox, but
|
|
66
|
+
// this side can still add them to it.
|
|
67
|
+
const hooks = Boolean(launchPath) && run.scripts.length > 0;
|
|
68
|
+
if (launchPath && hooks)
|
|
69
|
+
stageScripts(path.join(launchPath, 'projects', entry.name, 'hooks', 'pre-agent'), run.scripts);
|
|
70
|
+
try {
|
|
71
|
+
syncProjectIn(o.dataPath, { hostWorkspace: entry.host, guestWorkspace: entry.guest });
|
|
72
|
+
}
|
|
73
|
+
catch { /* history is a convenience; never block the session */ }
|
|
74
|
+
const notes = [...run.notes, ...check.looser.map((l) => 'The running sandbox is stricter: ' + l + '.')];
|
|
75
|
+
return { project: entry, guestDir: here ? here.guestDir : entry.guest, spec: projectSpec(run, entry, hooks), run, notes };
|
|
76
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { assertNoGuestOverlap } from '../../core/config.js';
|
|
2
|
+
import { assertSafeMapping } from '../../core/safe-paths.js';
|
|
3
|
+
import { dirExists, fail } from '../../core/util.js';
|
|
4
|
+
import { isValidGuestPath, sandboxFolder } from '../../shared/guest-paths.js';
|
|
5
|
+
import { GUEST } from './wsb.js';
|
|
6
|
+
export const collectMappings = (o, launchPath, boot = { roots: [], projects: [], notes: [] }) => {
|
|
7
|
+
const { config, dataPath, workspaceHost, harnessName } = o.run;
|
|
8
|
+
assertGuestPath(config.guestWorkspace, 'guestWorkspace');
|
|
9
|
+
const mappings = [
|
|
10
|
+
{ host: launchPath, guest: GUEST.launch, readOnly: true },
|
|
11
|
+
{ host: dataPath, guest: GUEST.data, readOnly: false },
|
|
12
|
+
{ host: workspaceHost, guest: config.guestWorkspace, readOnly: config.readOnly },
|
|
13
|
+
];
|
|
14
|
+
for (const m of config.mappings) {
|
|
15
|
+
const host = m.host;
|
|
16
|
+
assertGuestPath(m.guest, 'mappings[].guest for ' + host);
|
|
17
|
+
if (!dirExists(host)) {
|
|
18
|
+
if (m.optional) {
|
|
19
|
+
console.warn('Skipping optional mapping (folder not found): ' + host);
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
fail('A mapped folder does not exist: ' + host, 'Create it, fix the path in your config, or mark the mapping "optional": true.');
|
|
23
|
+
}
|
|
24
|
+
assertSafeMapping(host, o.packageRoot, o.run.storageRoot);
|
|
25
|
+
mappings.push({ host, guest: m.guest, readOnly: m.readOnly });
|
|
26
|
+
}
|
|
27
|
+
// Project roots and other projects, so they can join the running sandbox without a restart (launch/projects.ts).
|
|
28
|
+
for (const m of [...boot.roots, ...boot.projects]) {
|
|
29
|
+
assertGuestPath(m.guest, 'project folder for ' + m.host);
|
|
30
|
+
mappings.push({ host: m.host, guest: m.guest, readOnly: false });
|
|
31
|
+
}
|
|
32
|
+
const { probe } = o;
|
|
33
|
+
if (probe.nodeDir)
|
|
34
|
+
mappings.push({ host: probe.nodeDir, guest: GUEST.hostTools + '\\node', readOnly: true });
|
|
35
|
+
if (probe.harnessExe && harnessName !== 'none')
|
|
36
|
+
mappings.push({ host: probe.harnessExe.dir, guest: GUEST.hostTools + '\\agents\\' + harnessName, readOnly: true });
|
|
37
|
+
if (probe.gitDir)
|
|
38
|
+
mappings.push({ host: probe.gitDir, guest: GUEST.hostTools + '\\git', readOnly: true });
|
|
39
|
+
if (probe.nvmDir)
|
|
40
|
+
mappings.push({ host: probe.nvmDir, guest: GUEST.hostTools + '\\nvm', readOnly: true });
|
|
41
|
+
assertNoGuestOverlap(mappings);
|
|
42
|
+
// Same check on where the folders really are (F:\x and C:\Mirror\F\x are one place).
|
|
43
|
+
assertNoGuestOverlap(mappings.map((m) => ({ guest: sandboxFolder(m.guest) })));
|
|
44
|
+
return mappings;
|
|
45
|
+
};
|
|
46
|
+
/** The schema checks this already; the host re-checks what goes into the .wsb and the subst list anyway. */
|
|
47
|
+
const assertGuestPath = (guest, what) => {
|
|
48
|
+
if (!isValidGuestPath(guest))
|
|
49
|
+
fail(what + ' is not a usable sandbox folder: ' + guest, 'Use a folder under C:\\ (not Windows, Program Files, Users, AgentData, Mirror, ...) or on drive D: to Z:, e.g. C:\\Assets or F:\\SteamLibrary\\steamapps\\common\\Game.');
|
|
50
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ResolvedRun } from '../../core/types.js';
|
|
2
|
+
import { type RootMapping } from '../../shared/projects.js';
|
|
3
|
+
/** More folders than this would make the .wsb (and every boot) unwieldy; the rest join with a restart later. */
|
|
4
|
+
export declare const MAX_BOOT_PROJECTS = 48;
|
|
5
|
+
export interface BootProjects {
|
|
6
|
+
roots: RootMapping[];
|
|
7
|
+
projects: RootMapping[];
|
|
8
|
+
notes: string[];
|
|
9
|
+
}
|
|
10
|
+
export declare const planBootProjects: (o: {
|
|
11
|
+
run: ResolvedRun;
|
|
12
|
+
packageRoot: string;
|
|
13
|
+
extraProjects?: string[];
|
|
14
|
+
knownProjects?: string[];
|
|
15
|
+
}) => BootProjects;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// Which folders besides the launch folder are mapped so other projects can join without a restart: the project
|
|
2
|
+
// roots (projects.roots), projects carried over by a restart that added one, and with projects.all every project
|
|
3
|
+
// started before (except projects.exclude). Every one passes the same checks as a launch folder.
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { assertSafeMapping, assertSafeWorkspace } from '../../core/safe-paths.js';
|
|
6
|
+
import { dirExists, errMsg, fail } from '../../core/util.js';
|
|
7
|
+
import { isWithin, planProjectMappings } from '../../shared/projects.js';
|
|
8
|
+
import { allProjectsToMap, readKnown } from '../projects.js';
|
|
9
|
+
/** More folders than this would make the .wsb (and every boot) unwieldy; the rest join with a restart later. */
|
|
10
|
+
export const MAX_BOOT_PROJECTS = 48;
|
|
11
|
+
export const planBootProjects = (o) => {
|
|
12
|
+
const { config, workspaceHost, storageRoot } = o.run;
|
|
13
|
+
const notes = [];
|
|
14
|
+
const safe = (folder) => {
|
|
15
|
+
try {
|
|
16
|
+
assertSafeWorkspace(folder, o.packageRoot, storageRoot);
|
|
17
|
+
assertSafeMapping(folder, o.packageRoot, storageRoot);
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
catch (e) {
|
|
21
|
+
return errMsg(e);
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
const roots = [];
|
|
25
|
+
for (const root of config.projects.roots.map((r) => path.resolve(r))) {
|
|
26
|
+
if (!dirExists(root)) {
|
|
27
|
+
notes.push('Project root not found, skipped: ' + root);
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
const refusal = safe(root);
|
|
31
|
+
if (refusal)
|
|
32
|
+
fail('projects.roots: ' + refusal, 'Use a folder that holds your projects, e.g. F:\\Code (not a drive, your profile or a system folder).');
|
|
33
|
+
if (!roots.some((r) => isWithin(r, root)))
|
|
34
|
+
roots.push(root);
|
|
35
|
+
}
|
|
36
|
+
const known = o.knownProjects ?? readKnown(storageRoot).map((k) => k.host);
|
|
37
|
+
const wanted = [...(o.extraProjects ?? []), ...(config.projects.all ? allProjectsToMap(known, { primary: workspaceHost, roots, exclude: config.projects.exclude }) : [])];
|
|
38
|
+
const projects = [];
|
|
39
|
+
for (const folder of wanted.map((p) => path.resolve(p))) {
|
|
40
|
+
if (isWithin(workspaceHost, folder) || roots.some((r) => isWithin(r, folder)) || projects.some((p) => isWithin(p, folder) || isWithin(folder, p)))
|
|
41
|
+
continue;
|
|
42
|
+
if (!dirExists(folder))
|
|
43
|
+
continue;
|
|
44
|
+
const refusal = safe(folder);
|
|
45
|
+
if (refusal) {
|
|
46
|
+
notes.push('Not mapped: ' + refusal);
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
if (projects.length >= MAX_BOOT_PROJECTS) {
|
|
50
|
+
notes.push('More than ' + MAX_BOOT_PROJECTS + ' projects: the rest are not mapped (a project root holds any number).');
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
projects.push(folder);
|
|
54
|
+
}
|
|
55
|
+
return { ...planProjectMappings(roots, projects, Boolean(o.run.mirror)), notes };
|
|
56
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Package hooks first, then ~/.agent-sandbox/hooks (same file name = user's wins), then --script / "scripts". */
|
|
2
|
+
export declare const stageHooks: (packageRoot: string, launchPath: string, scripts: string[]) => void;
|
|
3
|
+
/** --script / "scripts" files, run before the agent in this order. */
|
|
4
|
+
export declare const stageScripts: (target: string, scripts: string[]) => void;
|
|
5
|
+
export declare const RUNTIME_FILES: string[];
|
|
6
|
+
/** The bundled sandbox runtime (dist/runtime: one self-contained file per entry point) and the sandbox/ scripts. */
|
|
7
|
+
export declare const copyRuntime: (packageRoot: string, launchPath: string) => void;
|
|
8
|
+
/** Only the identity from ~/.gitconfig: the rest can hold credentials (helpers, tokens in insteadOf URLs). */
|
|
9
|
+
export declare const gitIdentity: (gitconfig: string) => string;
|
|
10
|
+
/** Writes this PC's git name and email to home\.gitconfig in the launch folder (nothing when there are none). */
|
|
11
|
+
export declare const stageGitIdentity: (launchPath: string) => void;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// What is copied into the launch folder (C:\AgentLaunch inside the sandbox): hooks, the bundled runtime, the git identity.
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { userDir } from '../../core/config.js';
|
|
5
|
+
import { dirExists, ensureDir, fail, fileExists, homeDir } from '../../core/util.js';
|
|
6
|
+
const HOOK_STAGES = ['pre-provision', 'pre-agent', 'post-agent'];
|
|
7
|
+
/** Package hooks first, then ~/.agent-sandbox/hooks (same file name = user's wins), then --script / "scripts". */
|
|
8
|
+
export const stageHooks = (packageRoot, launchPath, scripts) => {
|
|
9
|
+
for (const stage of HOOK_STAGES) {
|
|
10
|
+
const target = path.join(launchPath, 'hooks', stage);
|
|
11
|
+
ensureDir(target);
|
|
12
|
+
for (const source of [path.join(packageRoot, 'hooks', stage), path.join(userDir(), 'hooks', stage)]) {
|
|
13
|
+
if (dirExists(source))
|
|
14
|
+
fs.cpSync(source, target, { recursive: true, force: true });
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
stageScripts(path.join(launchPath, 'hooks', 'pre-agent'), scripts);
|
|
18
|
+
};
|
|
19
|
+
/** --script / "scripts" files, run before the agent in this order. */
|
|
20
|
+
export const stageScripts = (target, scripts) => {
|
|
21
|
+
ensureDir(target);
|
|
22
|
+
scripts.forEach((script, index) => {
|
|
23
|
+
if (!fileExists(script))
|
|
24
|
+
fail('Script file not found: ' + script, 'Check the --script path.');
|
|
25
|
+
fs.copyFileSync(script, path.join(target, '90-extra-' + index + '-' + path.basename(script)));
|
|
26
|
+
});
|
|
27
|
+
};
|
|
28
|
+
export const RUNTIME_FILES = ['main.js', 'daemon.js', 'view.js', 'gitguard.js', 'db.js'];
|
|
29
|
+
/** The bundled sandbox runtime (dist/runtime: one self-contained file per entry point) and the sandbox/ scripts. */
|
|
30
|
+
export const copyRuntime = (packageRoot, launchPath) => {
|
|
31
|
+
const runtime = path.join(packageRoot, 'dist', 'runtime');
|
|
32
|
+
const missing = RUNTIME_FILES.filter((f) => !fileExists(path.join(runtime, f)));
|
|
33
|
+
if (missing.length)
|
|
34
|
+
fail('The sandbox runtime is missing from ' + runtime + ' (' + missing.join(', ') + ').', 'Rebuild it: npm run build (or reinstall the package).');
|
|
35
|
+
ensureDir(path.join(launchPath, 'runtime'));
|
|
36
|
+
for (const file of RUNTIME_FILES)
|
|
37
|
+
fs.copyFileSync(path.join(runtime, file), path.join(launchPath, 'runtime', file));
|
|
38
|
+
fs.writeFileSync(path.join(launchPath, 'package.json'), '{ "type": "module" }\n', 'utf8');
|
|
39
|
+
fs.cpSync(path.join(packageRoot, 'sandbox'), path.join(launchPath, 'sandbox'), { recursive: true });
|
|
40
|
+
};
|
|
41
|
+
/** Only the identity from ~/.gitconfig: the rest can hold credentials (helpers, tokens in insteadOf URLs). */
|
|
42
|
+
export const gitIdentity = (gitconfig) => {
|
|
43
|
+
const values = {};
|
|
44
|
+
let section = '';
|
|
45
|
+
for (const line of gitconfig.split(/\r?\n/)) {
|
|
46
|
+
const header = /^\s*\[([^\]]+)\]/.exec(line);
|
|
47
|
+
if (header) {
|
|
48
|
+
section = header[1].trim().toLowerCase();
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
const pair = /^\s*(name|email)\s*=\s*(.*?)\s*$/i.exec(line);
|
|
52
|
+
if (section === 'user' && pair && !/[\r\n]/.test(pair[2]))
|
|
53
|
+
values[pair[1].toLowerCase()] = pair[2].replace(/^"(.*)"$/, '$1');
|
|
54
|
+
}
|
|
55
|
+
const lines = Object.entries(values).map(([k, v]) => '\t' + k + ' = ' + v);
|
|
56
|
+
return lines.length ? '[user]\n' + lines.join('\n') + '\n' : '';
|
|
57
|
+
};
|
|
58
|
+
/** Writes this PC's git name and email to home\.gitconfig in the launch folder (nothing when there are none). */
|
|
59
|
+
export const stageGitIdentity = (launchPath) => {
|
|
60
|
+
const gitconfig = path.join(homeDir(), '.gitconfig');
|
|
61
|
+
const identity = fileExists(gitconfig) ? gitIdentity(fs.readFileSync(gitconfig, 'utf8')) : '';
|
|
62
|
+
if (!identity)
|
|
63
|
+
return;
|
|
64
|
+
ensureDir(path.join(launchPath, 'home'));
|
|
65
|
+
fs.writeFileSync(path.join(launchPath, 'home', '.gitconfig'), identity, 'utf8');
|
|
66
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Config, ResolvedMapping } from '../../core/types.js';
|
|
2
|
+
export declare const GUEST: {
|
|
3
|
+
readonly launch: "C:\\AgentLaunch";
|
|
4
|
+
readonly data: "C:\\AgentData";
|
|
5
|
+
readonly hostTools: "C:\\HostTools";
|
|
6
|
+
readonly mirror: "C:\\Mirror";
|
|
7
|
+
};
|
|
8
|
+
/** "auto": free RAM minus the reserve, but never more than maxMemoryMb or maxMemoryPercent of the PC's total RAM. */
|
|
9
|
+
export declare const resolveMemoryMb: (sandbox: Config["sandbox"], freeMemoryBytes?: number, totalMemoryBytes?: number) => number;
|
|
10
|
+
export declare const buildWsb: (config: Config, memoryMb: number, mappings: ResolvedMapping[]) => string;
|