windows-development-sandbox 1.0.5 → 1.4.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 +25 -8
- package/config.example.json +55 -5
- package/dist/api.js +2 -1
- package/dist/cli/args.d.ts +23 -0
- package/dist/cli/args.js +38 -4
- package/dist/cli/colors.d.ts +24 -0
- package/dist/cli/colors.js +42 -0
- package/dist/cli/commands/accounts.js +2 -2
- 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 +5 -1
- package/dist/cli/commands/data/clean.d.ts +15 -0
- package/dist/cli/commands/data/clean.js +106 -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 +6 -4
- package/dist/cli/commands/index.js +6 -4
- package/dist/cli/commands/launch.d.ts +26 -2
- package/dist/cli/commands/launch.js +256 -42
- package/dist/cli/commands/maintenance.d.ts +2 -0
- package/dist/cli/commands/maintenance.js +67 -5
- 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 +42 -15
- package/dist/cli/manifest.d.ts +8 -0
- package/dist/cli/manifest.js +40 -15
- package/dist/cli/menu/apps.d.ts +21 -0
- package/dist/cli/menu/apps.js +122 -0
- package/dist/cli/menu/command-form.d.ts +28 -0
- package/dist/cli/menu/command-form.js +219 -0
- package/dist/cli/menu/index.d.ts +26 -0
- package/dist/cli/menu/index.js +180 -0
- package/dist/cli/menu/settings/categories.d.ts +9 -0
- package/dist/cli/menu/settings/categories.js +28 -0
- package/dist/cli/menu/settings/config-file.d.ts +40 -0
- package/dist/cli/menu/settings/config-file.js +71 -0
- package/dist/cli/menu/settings/editors.d.ts +30 -0
- package/dist/cli/menu/settings/editors.js +305 -0
- package/dist/cli/menu/settings/index.d.ts +32 -0
- package/dist/cli/menu/settings/index.js +136 -0
- package/dist/cli/menu/settings/schema-node.d.ts +62 -0
- package/dist/cli/menu/settings/schema-node.js +104 -0
- package/dist/cli/menu/several.d.ts +20 -0
- package/dist/cli/menu/several.js +102 -0
- package/dist/cli/progress.d.ts +9 -0
- package/dist/cli/progress.js +49 -0
- package/dist/cli/tui/app.d.ts +39 -0
- package/dist/cli/tui/app.js +52 -0
- package/dist/cli/tui/dialogs.d.ts +57 -0
- package/dist/cli/tui/dialogs.js +162 -0
- package/dist/cli/tui/frame.d.ts +18 -0
- package/dist/cli/tui/frame.js +27 -0
- package/dist/cli/tui/index.d.ts +7 -0
- package/dist/cli/tui/index.js +8 -0
- package/dist/cli/tui/list-screen.d.ts +101 -0
- package/dist/cli/tui/list-screen.js +299 -0
- package/dist/cli/tui/terminal.d.ts +37 -0
- package/dist/cli/tui/terminal.js +89 -0
- package/dist/cli/tui/text.d.ts +20 -0
- package/dist/cli/tui/text.js +105 -0
- package/dist/cli/tui/theme.d.ts +45 -0
- package/dist/cli/tui/theme.js +30 -0
- package/dist/cli/validate.d.ts +17 -0
- package/dist/cli/validate.js +89 -0
- package/dist/core/config.js +12 -6
- package/dist/core/disk.d.ts +16 -0
- package/dist/core/disk.js +55 -0
- package/dist/core/local-config.d.ts +23 -0
- package/dist/core/local-config.js +79 -7
- 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/apps.d.ts +40 -0
- package/dist/core/schema/apps.js +27 -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 +659 -0
- package/dist/core/schema/config.js +75 -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 +32 -0
- package/dist/core/schema/profile.js +31 -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 +78 -0
- package/dist/core/schema/sandbox.js +64 -0
- package/dist/core/schema/toolchains.d.ts +96 -0
- package/dist/core/schema/toolchains.js +88 -0
- package/dist/core/schema.d.ts +6 -604
- package/dist/core/schema.js +8 -264
- package/dist/core/types.d.ts +35 -1
- package/dist/core/util.d.ts +9 -0
- package/dist/core/util.js +14 -3
- package/dist/core/workspace.d.ts +0 -5
- package/dist/core/workspace.js +33 -50
- package/dist/guest/apps/index.d.ts +10 -0
- package/dist/guest/apps/index.js +206 -0
- package/dist/guest/apps/resolvers.d.ts +8 -0
- package/dist/guest/apps/resolvers.js +90 -0
- package/dist/guest/audit-parse.d.ts +7 -0
- package/dist/guest/audit-parse.js +24 -0
- package/dist/guest/audit.d.ts +4 -0
- package/dist/guest/audit.js +47 -0
- 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/relays.d.ts +1 -0
- package/dist/guest/daemon/relays.js +18 -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 +12 -141
- package/dist/guest/db.js +18 -3
- package/dist/guest/env.d.ts +3 -0
- package/dist/guest/env.js +7 -3
- 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 +31 -8
- package/dist/guest/netproxy.d.ts +1 -0
- package/dist/guest/netproxy.js +100 -0
- 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 +17 -65
- package/dist/guest/setup/agents.d.ts +5 -2
- package/dist/guest/setup/agents.js +22 -8
- package/dist/guest/setup/claude.d.ts +0 -4
- package/dist/guest/setup/claude.js +59 -5
- 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/setup/network.d.ts +9 -0
- package/dist/guest/setup/network.js +97 -4
- package/dist/guest/setup/user.js +4 -0
- 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 +56 -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 +24 -0
- package/dist/guest/toolchains/plan.js +140 -0
- package/dist/guest/toolchains/pwsh.js +1 -1
- package/dist/guest/toolchains/python.js +43 -12
- 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/toolchains/vscode-profile.d.ts +30 -0
- package/dist/guest/toolchains/vscode-profile.js +69 -0
- package/dist/guest/toolchains/vscode.d.ts +17 -0
- package/dist/guest/toolchains/vscode.js +87 -1
- 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 +70 -0
- package/dist/host/audit/collect.d.ts +34 -0
- package/dist/host/audit/collect.js +126 -0
- package/dist/host/audit/log.d.ts +46 -0
- package/dist/host/audit/log.js +143 -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 +10 -12
- package/dist/host/generate.js +58 -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 +54 -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/motw.d.ts +17 -0
- package/dist/host/motw.js +74 -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 +16 -1
- package/dist/host/port-forward.d.ts +8 -0
- package/dist/host/port-forward.js +37 -0
- 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 +25 -5
- package/dist/host/repo-guard.js +242 -23
- package/dist/host/stops.d.ts +67 -0
- package/dist/host/stops.js +126 -0
- package/dist/host/terminal.d.ts +8 -0
- package/dist/host/terminal.js +48 -1
- 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/workspace-view/index.d.ts +20 -0
- package/dist/host/workspace-view/index.js +53 -0
- package/dist/host/workspace-view/rules.d.ts +26 -0
- package/dist/host/workspace-view/rules.js +93 -0
- package/dist/host/workspace-view/view.d.ts +50 -0
- package/dist/host/workspace-view/view.js +281 -0
- package/dist/host/wsb.d.ts +37 -0
- package/dist/host/wsb.js +101 -0
- package/dist/runtime/daemon.js +11791 -2821
- package/dist/runtime/db.js +6585 -466
- package/dist/runtime/main.js +10238 -1724
- package/dist/runtime/netproxy.js +25936 -0
- package/dist/runtime/view.js +6183 -59
- 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/audit.test.d.ts +2 -0
- package/dist/selftest/audit.test.js +57 -0
- package/dist/selftest/channel.test.d.ts +2 -0
- package/dist/selftest/channel.test.js +44 -0
- package/dist/selftest/cli.test.d.ts +2 -2
- package/dist/selftest/cli.test.js +145 -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 +89 -4
- package/dist/selftest/fixtures.js +43 -24
- package/dist/selftest/generate.test.d.ts +2 -0
- package/dist/selftest/generate.test.js +68 -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 +137 -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-network.test.d.ts +2 -0
- package/dist/selftest/live-network.test.js +63 -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 +120 -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 +40 -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 +121 -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 +130 -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 +133 -0
- package/dist/selftest/workspace-view.test.d.ts +2 -0
- package/dist/selftest/workspace-view.test.js +89 -0
- package/dist/selftest/workspace.test.d.ts +2 -0
- package/dist/selftest/workspace.test.js +53 -0
- package/dist/shared/apps.d.ts +49 -0
- package/dist/shared/apps.js +114 -0
- package/dist/shared/download.d.ts +3 -1
- package/dist/shared/download.js +19 -4
- package/dist/shared/netpolicy.d.ts +27 -0
- package/dist/shared/netpolicy.js +51 -0
- 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/pty-client.d.ts +9 -4
- package/dist/shared/pty-client.js +139 -17
- package/dist/shared/rundir.d.ts +12 -1
- package/dist/shared/rundir.js +14 -4
- package/dist/shared/safe-remove.js +11 -0
- package/dist/shared/schema.d.ts +453 -0
- package/dist/shared/schema.js +43 -2
- package/dist/shared/system.d.ts +1 -0
- package/dist/shared/system.js +1 -0
- package/dist/shared/test-guard.d.ts +4 -0
- package/dist/shared/test-guard.js +37 -0
- package/dist/sync/claude-mem.js +6 -3
- package/dist/sync/history.d.ts +29 -12
- package/dist/sync/history.js +72 -25
- package/dist/sync/watcher.d.ts +8 -0
- package/dist/sync/watcher.js +99 -8
- package/package.json +7 -6
- package/sandbox/bootstrap.ps1 +258 -258
- package/sandbox/cargoshim.cs +56 -0
- package/sandbox/cpucap.cs +33 -0
- package/sandbox/cpucap.ps1 +20 -0
- package/sandbox/logon.cmd +24 -16
- package/sandbox/memlog.ps1 +34 -0
- package/sandbox/openssl.cs +42 -0
- package/sandbox/pagefile.cs +31 -0
- package/sandbox/pagefile.ps1 +21 -0
- package/sandbox/ptyhost.cs +120 -11
- package/sandbox/trust.cmd +35 -35
- package/schemas/config.schema.json +403 -3
- package/schemas/sandbox-config.schema.json +143 -13
- 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/menu.d.ts +0 -22
- package/dist/cli/menu.js +0 -187
- 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
package/sandbox/bootstrap.ps1
CHANGED
|
@@ -1,258 +1,258 @@
|
|
|
1
|
-
#Requires -Version 5.1
|
|
2
|
-
# Runs INSIDE Windows Sandbox (Windows PowerShell 5.1) in the sandbox window, started by logon.cmd.
|
|
3
|
-
# Its only job: make sure the runner's Node exists in persistent storage, then hand over to runtime\main.js.
|
|
4
|
-
# (That Node only runs agent-sandbox itself; the agent's `node` comes from nvm.)
|
|
5
|
-
$ErrorActionPreference = 'Stop'
|
|
6
|
-
$ProgressPreference = 'SilentlyContinue' # progress bars make PS 5.1 downloads extremely slow
|
|
7
|
-
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
|
8
|
-
|
|
9
|
-
$launchDir = 'C:\AgentLaunch'
|
|
10
|
-
$dataDir = 'C:\AgentData'
|
|
11
|
-
$hostNodeDir = 'C:\HostTools\node'
|
|
12
|
-
$hostNvm = 'C:\HostTools\nvm'
|
|
13
|
-
$toolsDir = Join-Path $dataDir 'tools'
|
|
14
|
-
# The runner's Node lives with nvm's versions (tools\nvm\v<version>), so nvm reuses it instead of copying it again.
|
|
15
|
-
$nvmDir = Join-Path $toolsDir 'nvm'
|
|
16
|
-
$runnerFile = Join-Path $toolsDir 'runner-node.txt'
|
|
17
|
-
$nodeDir = $null
|
|
18
|
-
$nodeExe = $null
|
|
19
|
-
$logsDir = Join-Path $dataDir 'logs'
|
|
20
|
-
$runDir = Join-Path $dataDir 'run'
|
|
21
|
-
foreach ($dir in @($logsDir, $toolsDir, $nvmDir, $runDir)) { New-Item -ItemType Directory -Path $dir -Force | Out-Null }
|
|
22
|
-
$host.UI.RawUI.WindowTitle = 'Agent Sandbox'
|
|
23
|
-
Write-Host 'Agent sandbox bootstrap started.' -ForegroundColor Cyan
|
|
24
|
-
$transcript = Join-Path $logsDir ('bootstrap-' + (Get-Date -Format 'yyyyMMdd-HHmmss') + '.log')
|
|
25
|
-
Start-Transcript -Path $transcript | Out-Null
|
|
26
|
-
$transcriptStopped = $false
|
|
27
|
-
# -Encoding UTF8: Windows PowerShell 5.1 otherwise reads it in the ANSI code page (non-ASCII paths and names break).
|
|
28
|
-
$session = Get-Content -LiteralPath (Join-Path $launchDir 'session.json') -Raw -Encoding UTF8 | ConvertFrom-Json
|
|
29
|
-
$hostVersion = [string]$session.hostTools.nodeVersion
|
|
30
|
-
|
|
31
|
-
function Set-Status([string]$Phase, [string]$Message) {
|
|
32
|
-
# Same shape as SandboxStatus in src/shared/schema.ts; written atomically so the host never reads half a file.
|
|
33
|
-
$status = [ordered]@{ launchId = $session.launchId; phase = $Phase; message = $Message; updatedAt = (Get-Date).ToUniversalTime().ToString('o'); workspaceHost = $session.workspaceHost; guestWorkspace = $session.workspace; headless = [bool]$session.headless }
|
|
34
|
-
$file = Join-Path $runDir 'sandbox.json'
|
|
35
|
-
[IO.File]::WriteAllText("$file.tmp", ($status | ConvertTo-Json -Compress))
|
|
36
|
-
Move-Item -LiteralPath "$file.tmp" -Destination $file -Force
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function Invoke-Download([string]$Uri, [string]$OutFile) {
|
|
40
|
-
# The sandbox network can take a few seconds to come up, so retry.
|
|
41
|
-
for ($attempt = 1; $attempt -le 5; $attempt++) {
|
|
42
|
-
try {
|
|
43
|
-
if ($OutFile) { Invoke-WebRequest -Uri $Uri -OutFile $OutFile -UseBasicParsing; return $null }
|
|
44
|
-
$response = Invoke-WebRequest -Uri $Uri -UseBasicParsing
|
|
45
|
-
if ($response.Content -is [byte[]]) { return [Text.Encoding]::UTF8.GetString($response.Content) }
|
|
46
|
-
return [string]$response.Content
|
|
47
|
-
}
|
|
48
|
-
catch {
|
|
49
|
-
if ($attempt -eq 5) { throw }
|
|
50
|
-
Write-Warning "Download failed (attempt $attempt of 5): $($_.Exception.Message). Retrying ..."
|
|
51
|
-
Start-Sleep -Seconds 4
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function Set-Runner([string]$Version) {
|
|
57
|
-
$script:nodeDir = Join-Path $nvmDir ('v' + $Version.TrimStart('v'))
|
|
58
|
-
$script:nodeExe = Join-Path $script:nodeDir 'node.exe'
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function Test-Node {
|
|
62
|
-
if (-not (Test-Path -LiteralPath $nodeExe)) { return $false }
|
|
63
|
-
try { $null = & $nodeExe --version; return ($LASTEXITCODE -eq 0) } catch { return $false }
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
function Find-CachedRunner {
|
|
67
|
-
# This PC's version first (nvm will want it anyway), else whichever version the last boot used.
|
|
68
|
-
$last = if (Test-Path -LiteralPath $runnerFile) { (Get-Content -LiteralPath $runnerFile -Raw -Encoding UTF8).Trim() } else { '' }
|
|
69
|
-
foreach ($version in @($hostVersion, $last)) {
|
|
70
|
-
if ($version -notmatch '^\d+\.\d+\.\d+$') { continue }
|
|
71
|
-
Set-Runner $version
|
|
72
|
-
if (Test-Node) { return $true }
|
|
73
|
-
}
|
|
74
|
-
return $false
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function Publish-Directory([string]$Staging, [string]$Destination) {
|
|
78
|
-
if (Test-Path -LiteralPath $Destination) { Remove-Item -LiteralPath $Destination -Recurse -Force }
|
|
79
|
-
Move-Item -LiteralPath $Staging -Destination $Destination
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function Get-Size([string]$Path) {
|
|
83
|
-
if (-not (Test-Path -LiteralPath $Path)) { return 0 }
|
|
84
|
-
$sum = (Get-ChildItem -LiteralPath $Path -Recurse -File -Force -ErrorAction SilentlyContinue | Measure-Object -Property Length -Sum).Sum
|
|
85
|
-
if ($null -eq $sum) { return 0 } else { return [long]$sum }
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function Invoke-Robocopy([string]$Label, [string]$From, [string]$To, [string[]]$Options, [long]$Total) {
|
|
89
|
-
# Runs robocopy in the background and prints how far it has got, so a slow copy never looks frozen.
|
|
90
|
-
# /R:2 /W:1: robocopy's default is 1,000,000 retries 30 s apart, which hangs "forever" on one unreadable file.
|
|
91
|
-
$totalMb = [math]::Round($Total / 1MB, 1)
|
|
92
|
-
Write-Host " $Label ($totalMb MB) ..."
|
|
93
|
-
$argList = @("`"$From`"", "`"$To`"") + $Options + @('/XJ', '/R:2', '/W:1', '/NFL', '/NDL', '/NJH', '/NJS', '/NP')
|
|
94
|
-
$process = Start-Process -FilePath 'robocopy.exe' -ArgumentList $argList -NoNewWindow -PassThru
|
|
95
|
-
$null = $process.Handle # keeps ExitCode readable after the process exits
|
|
96
|
-
$started = Get-Date
|
|
97
|
-
while (-not $process.WaitForExit(2000)) {
|
|
98
|
-
$done = Get-Size $To
|
|
99
|
-
$pct = if ($Total -gt 0) { [math]::Min(100, [math]::Floor($done * 100 / $Total)) } else { 0 }
|
|
100
|
-
$secs = [int]((Get-Date) - $started).TotalSeconds
|
|
101
|
-
Write-Host (" {0}: {1} of {2} MB ({3}%), {4}s" -f $Label, [math]::Round($done / 1MB, 1), $totalMb, $pct, $secs)
|
|
102
|
-
}
|
|
103
|
-
$code = $process.ExitCode
|
|
104
|
-
if ($code -ge 8) { Write-Warning "robocopy failed copying $Label (exit code $code)"; return $false }
|
|
105
|
-
Write-Host (" {0}: done in {1}s" -f $Label, [int]((Get-Date) - $started).TotalSeconds)
|
|
106
|
-
return $true
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
function Copy-NodeFromHost {
|
|
110
|
-
# The host maps one of these: nvm (when this PC's Node is an nvm version) or the plain Node folder.
|
|
111
|
-
if ($hostVersion -notmatch '^\d+\.\d+\.\d+$') { return $false }
|
|
112
|
-
$hostNode = @((Join-Path $hostNvm "v$hostVersion"), $hostNodeDir) | Where-Object { Test-Path -LiteralPath (Join-Path $_ 'node.exe') } | Select-Object -First 1
|
|
113
|
-
if (-not $hostNode) { return $false }
|
|
114
|
-
# Only node.exe, the npm shims and node_modules\npm (same list as copyNode in src/guest/env.ts): nothing else is
|
|
115
|
-
# needed, and the folder can hold every global npm package, i.e. tens of thousands of small files over a slow mapping.
|
|
116
|
-
Write-Host "Copying Node $hostVersion from this PC ($hostNode) ..."
|
|
117
|
-
Set-Status 'booting' 'Copying Node from this PC'
|
|
118
|
-
$staging = Join-Path $toolsDir 'node-staging'
|
|
119
|
-
if (Test-Path -LiteralPath $staging) { Remove-Item -LiteralPath $staging -Recurse -Force }
|
|
120
|
-
$files = @('node.exe', 'npm', 'npm.cmd', 'npx', 'npx.cmd')
|
|
121
|
-
$filesSize = ($files | ForEach-Object { Get-Item -LiteralPath (Join-Path $hostNode $_) -ErrorAction SilentlyContinue } | Measure-Object -Property Length -Sum).Sum
|
|
122
|
-
$npmFrom = Join-Path $hostNode 'node_modules\npm'
|
|
123
|
-
$ok = (Invoke-Robocopy -Label 'node.exe' -From $hostNode -To $staging -Options ($files + '/J') -Total ([long]$filesSize)) -and
|
|
124
|
-
(Invoke-Robocopy -Label 'npm' -From $npmFrom -To (Join-Path $staging 'node_modules\npm') -Options @('/E', '/MT:16') -Total (Get-Size $npmFrom))
|
|
125
|
-
if (-not $ok) { return $false }
|
|
126
|
-
Set-Runner $hostVersion
|
|
127
|
-
Publish-Directory -Staging $staging -Destination $nodeDir
|
|
128
|
-
return (Test-Node)
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
function Mount-MirrorDrives {
|
|
132
|
-
# Windows Sandbox can only map folders onto C:, so a guest path like F:\Games\X is mapped to C:\Mirror\F\Games\X
|
|
133
|
-
# and F: is made here with subst (session.substDrives, written by the host; see src/shared/guest-paths.ts).
|
|
134
|
-
# subst drives belong to the logon session: everything started from here (main.js, the daemon, ptyhost, hooks,
|
|
135
|
-
# Windows Terminal tabs) and Explorer share it, because the sandbox user has one full token (UAC is off).
|
|
136
|
-
# Returns the letters that could NOT be mounted; an existing drive is reported, never replaced.
|
|
137
|
-
$subst = Join-Path $env:SystemRoot 'System32\subst.exe'
|
|
138
|
-
$failed = @()
|
|
139
|
-
foreach ($drive in @($session.substDrives)) {
|
|
140
|
-
if ($null -eq $drive) { continue }
|
|
141
|
-
$letter = ([string]$drive.letter).ToUpperInvariant()
|
|
142
|
-
$target = [string]$drive.target
|
|
143
|
-
if ($letter -notmatch '^[D-Z]$' -or $target -ne "C:\Mirror\$letter") { throw "Invalid subst drive in session.json: '$letter' -> '$target'." }
|
|
144
|
-
$existing = (& $subst) | ForEach-Object { if ($_ -match "^${letter}:\\: => (.+)$") { $Matches[1].Trim() } } | Select-Object -First 1
|
|
145
|
-
if ($existing -and $existing -eq $target) { Write-Host " ${letter}: already points to $target."; continue }
|
|
146
|
-
if ([IO.Directory]::GetLogicalDrives() | Where-Object { $_.Substring(0, 1).ToUpperInvariant() -eq $letter }) {
|
|
147
|
-
Write-Warning "Drive ${letter}: already exists in the sandbox, so it was left alone. Folders mapped to ${letter}:\... are at $target\... instead."
|
|
148
|
-
$failed += $letter
|
|
149
|
-
continue
|
|
150
|
-
}
|
|
151
|
-
if (-not (Test-Path -LiteralPath $target -PathType Container)) { New-Item -ItemType Directory -Path $target -Force | Out-Null }
|
|
152
|
-
# subst.exe exits 0 even when it fails, so check the drive itself.
|
|
153
|
-
& $subst "${letter}:" $target | Out-Host
|
|
154
|
-
if (Test-Path -LiteralPath "${letter}:\") { Write-Host " ${letter}: -> $target" }
|
|
155
|
-
else { Write-Warning "subst ${letter}: $target did not work. Folders mapped to ${letter}:\... are at $target\... instead."; $failed += $letter }
|
|
156
|
-
}
|
|
157
|
-
return ,$failed
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
function Update-Environment {
|
|
161
|
-
# main.js writes PATH and the tool variables machine-wide; reload them so this (-NoExit) shell has nvm, git, etc.
|
|
162
|
-
foreach ($scope in @('Machine', 'User')) {
|
|
163
|
-
$vars = [Environment]::GetEnvironmentVariables($scope)
|
|
164
|
-
foreach ($name in $vars.Keys) { if ($name -ne 'Path') { Set-Item -LiteralPath "env:$name" -Value ([string]$vars[$name]) } }
|
|
165
|
-
}
|
|
166
|
-
$env:PATH = ([Environment]::GetEnvironmentVariable('Path', 'Machine'), [Environment]::GetEnvironmentVariable('Path', 'User') | Where-Object { $_ }) -join ';'
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
function Install-NodeFromWeb {
|
|
170
|
-
$arch = if ($env:PROCESSOR_ARCHITECTURE -eq 'ARM64') { 'arm64' } else { 'x64' }
|
|
171
|
-
Write-Host 'Looking up the Node LTS release ...'
|
|
172
|
-
$index = (Invoke-Download -Uri 'https://nodejs.org/dist/index.json') | ConvertFrom-Json
|
|
173
|
-
$release = $index | Where-Object { $_.lts -and ($_.files -contains "win-$arch-zip") } | Select-Object -First 1
|
|
174
|
-
if ($null -eq $release) { throw "No Node LTS release found ($arch)." }
|
|
175
|
-
$version = [string]$release.version
|
|
176
|
-
if ($version -notmatch '^v\d+\.\d+\.\d+$') { throw "Unexpected Node version string: $version" }
|
|
177
|
-
$zipName = "node-$version-win-$arch.zip"
|
|
178
|
-
$baseUrl = "https://nodejs.org/dist/$version"
|
|
179
|
-
$work = Join-Path $toolsDir ('node-work-' + [guid]::NewGuid().ToString('N'))
|
|
180
|
-
New-Item -ItemType Directory -Path $work -Force | Out-Null
|
|
181
|
-
try {
|
|
182
|
-
$zipPath = Join-Path $work $zipName
|
|
183
|
-
Write-Host "Downloading $zipName ..."
|
|
184
|
-
$null = Invoke-Download -Uri "$baseUrl/$zipName" -OutFile $zipPath
|
|
185
|
-
$sums = Invoke-Download -Uri "$baseUrl/SHASUMS256.txt"
|
|
186
|
-
$line = ($sums -split "`n") | Where-Object { $_.Trim() -like "* $zipName" } | Select-Object -First 1
|
|
187
|
-
if (-not $line) { throw "No checksum entry for $zipName" }
|
|
188
|
-
$expected = ($line.Trim() -split '\s+')[0]
|
|
189
|
-
if ((Get-FileHash -LiteralPath $zipPath -Algorithm SHA256).Hash -ne $expected.ToUpper()) { throw 'Node download failed checksum verification.' }
|
|
190
|
-
$extractDir = Join-Path $work 'extracted'
|
|
191
|
-
New-Item -ItemType Directory -Path $extractDir -Force | Out-Null
|
|
192
|
-
& tar.exe -xf $zipPath -C $extractDir
|
|
193
|
-
if ($LASTEXITCODE -ne 0) { $global:LASTEXITCODE = 0; Expand-Archive -LiteralPath $zipPath -DestinationPath $extractDir -Force }
|
|
194
|
-
$inner = Join-Path $extractDir "node-$version-win-$arch"
|
|
195
|
-
if (-not (Test-Path -LiteralPath (Join-Path $inner 'node.exe'))) { throw 'Unexpected Node archive layout.' }
|
|
196
|
-
Set-Runner $version
|
|
197
|
-
Publish-Directory -Staging $inner -Destination $nodeDir
|
|
198
|
-
}
|
|
199
|
-
finally {
|
|
200
|
-
if (Test-Path -LiteralPath $work) { Remove-Item -LiteralPath $work -Recurse -Force -ErrorAction SilentlyContinue }
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
try {
|
|
205
|
-
# First: hooks, toolchains and the agent all run in (or below) the workspace, which may be on one of these drives.
|
|
206
|
-
$unmounted = Mount-MirrorDrives
|
|
207
|
-
$workspaceDrive = ([string]$session.workspace).Substring(0, 1).ToUpperInvariant()
|
|
208
|
-
if ($unmounted -contains $workspaceDrive) {
|
|
209
|
-
throw "The workspace $($session.workspace) needs drive ${workspaceDrive}:, which could not be created in the sandbox (see the warning above). Set guestWorkspace to ""auto"" (C:\Workspace\<name>) for this project."
|
|
210
|
-
}
|
|
211
|
-
Set-Status 'booting' 'Preparing the runner'
|
|
212
|
-
$provisioning = $session.provisioning
|
|
213
|
-
if (-not $provisioning.refresh -and (Find-CachedRunner)) {
|
|
214
|
-
Write-Host ('Runner Node ' + (& $nodeExe --version) + ' cached in persistent storage.')
|
|
215
|
-
}
|
|
216
|
-
else {
|
|
217
|
-
$ready = $false
|
|
218
|
-
if ($provisioning.copyFromHost) {
|
|
219
|
-
$ready = Copy-NodeFromHost
|
|
220
|
-
if (-not $ready) {
|
|
221
|
-
if (-not $provisioning.fallbackToInstall) { throw 'Could not copy Node from this PC and provisioning.fallbackToInstall is false.' }
|
|
222
|
-
Write-Warning 'Host Node unavailable; downloading Node instead.'
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
if (-not $ready) { Install-NodeFromWeb }
|
|
226
|
-
if (-not (Test-Node)) { throw 'Node is not runnable after provisioning.' }
|
|
227
|
-
Write-Host ('Runner Node ' + (& $nodeExe --version) + ' ready.')
|
|
228
|
-
}
|
|
229
|
-
[IO.File]::WriteAllText($runnerFile, (Split-Path -Leaf $nodeDir).TrimStart('v'))
|
|
230
|
-
# Older versions kept a full copy of the host's Node folder here.
|
|
231
|
-
$legacy = Join-Path $toolsDir 'node'
|
|
232
|
-
if (Test-Path -LiteralPath $legacy) { Remove-Item -LiteralPath $legacy -Recurse -Force -ErrorAction SilentlyContinue }
|
|
233
|
-
|
|
234
|
-
$env:PATH = "$nodeDir;$env:PATH"
|
|
235
|
-
Set-Location -LiteralPath $session.workspace
|
|
236
|
-
Stop-Transcript | Out-Null
|
|
237
|
-
$transcriptStopped = $true
|
|
238
|
-
& $nodeExe (Join-Path $launchDir 'runtime\main.js')
|
|
239
|
-
$code = $LASTEXITCODE
|
|
240
|
-
if ($code -ne 0) {
|
|
241
|
-
# main.js reports its own failures; this catches it dying before it could (e.g. a broken Node).
|
|
242
|
-
$current = Get-Content -LiteralPath (Join-Path $runDir 'sandbox.json') -Raw -Encoding UTF8 -ErrorAction SilentlyContinue | ConvertFrom-Json -ErrorAction SilentlyContinue
|
|
243
|
-
if ($null -eq $current -or $current.phase -ne 'failed') { Set-Status 'failed' ("The sandbox runner stopped (exit code $code). See C:\AgentData\logs.") }
|
|
244
|
-
Write-Host "The sandbox runner stopped with exit code $code. Logs: $logsDir" -ForegroundColor Red
|
|
245
|
-
}
|
|
246
|
-
Update-Environment
|
|
247
|
-
Write-Host "This window stays open in $($session.workspace)."
|
|
248
|
-
}
|
|
249
|
-
catch {
|
|
250
|
-
$report = ($_ | Format-List * -Force | Out-String) + "`r`n" + $_.ScriptStackTrace
|
|
251
|
-
Set-Content -Path (Join-Path $logsDir 'bootstrap-error.txt') -Value $report -Encoding UTF8
|
|
252
|
-
try { Set-Status 'failed' ('Bootstrap failed: ' + $_.Exception.Message) } catch { }
|
|
253
|
-
Write-Host $report -ForegroundColor Red
|
|
254
|
-
Write-Host "Saved to $logsDir\bootstrap-error.txt" -ForegroundColor Yellow
|
|
255
|
-
}
|
|
256
|
-
finally {
|
|
257
|
-
if (-not $transcriptStopped) { try { Stop-Transcript | Out-Null } catch { } }
|
|
258
|
-
}
|
|
1
|
+
#Requires -Version 5.1
|
|
2
|
+
# Runs INSIDE Windows Sandbox (Windows PowerShell 5.1) in the sandbox window, started by logon.cmd.
|
|
3
|
+
# Its only job: make sure the runner's Node exists in persistent storage, then hand over to runtime\main.js.
|
|
4
|
+
# (That Node only runs agent-sandbox itself; the agent's `node` comes from nvm.)
|
|
5
|
+
$ErrorActionPreference = 'Stop'
|
|
6
|
+
$ProgressPreference = 'SilentlyContinue' # progress bars make PS 5.1 downloads extremely slow
|
|
7
|
+
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
|
8
|
+
|
|
9
|
+
$launchDir = 'C:\AgentLaunch'
|
|
10
|
+
$dataDir = 'C:\AgentData'
|
|
11
|
+
$hostNodeDir = 'C:\HostTools\node'
|
|
12
|
+
$hostNvm = 'C:\HostTools\nvm'
|
|
13
|
+
$toolsDir = Join-Path $dataDir 'tools'
|
|
14
|
+
# The runner's Node lives with nvm's versions (tools\nvm\v<version>), so nvm reuses it instead of copying it again.
|
|
15
|
+
$nvmDir = Join-Path $toolsDir 'nvm'
|
|
16
|
+
$runnerFile = Join-Path $toolsDir 'runner-node.txt'
|
|
17
|
+
$nodeDir = $null
|
|
18
|
+
$nodeExe = $null
|
|
19
|
+
$logsDir = Join-Path $dataDir 'logs'
|
|
20
|
+
$runDir = Join-Path $dataDir 'run'
|
|
21
|
+
foreach ($dir in @($logsDir, $toolsDir, $nvmDir, $runDir)) { New-Item -ItemType Directory -Path $dir -Force | Out-Null }
|
|
22
|
+
$host.UI.RawUI.WindowTitle = 'Agent Sandbox'
|
|
23
|
+
Write-Host 'Agent sandbox bootstrap started.' -ForegroundColor Cyan
|
|
24
|
+
$transcript = Join-Path $logsDir ('bootstrap-' + (Get-Date -Format 'yyyyMMdd-HHmmss') + '.log')
|
|
25
|
+
Start-Transcript -Path $transcript | Out-Null
|
|
26
|
+
$transcriptStopped = $false
|
|
27
|
+
# -Encoding UTF8: Windows PowerShell 5.1 otherwise reads it in the ANSI code page (non-ASCII paths and names break).
|
|
28
|
+
$session = Get-Content -LiteralPath (Join-Path $launchDir 'session.json') -Raw -Encoding UTF8 | ConvertFrom-Json
|
|
29
|
+
$hostVersion = [string]$session.hostTools.nodeVersion
|
|
30
|
+
|
|
31
|
+
function Set-Status([string]$Phase, [string]$Message) {
|
|
32
|
+
# Same shape as SandboxStatus in src/shared/schema.ts; written atomically so the host never reads half a file.
|
|
33
|
+
$status = [ordered]@{ launchId = $session.launchId; phase = $Phase; message = $Message; updatedAt = (Get-Date).ToUniversalTime().ToString('o'); workspaceHost = $session.workspaceHost; guestWorkspace = $session.workspace; headless = [bool]$session.headless }
|
|
34
|
+
$file = Join-Path $runDir 'sandbox.json'
|
|
35
|
+
[IO.File]::WriteAllText("$file.tmp", ($status | ConvertTo-Json -Compress))
|
|
36
|
+
Move-Item -LiteralPath "$file.tmp" -Destination $file -Force
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function Invoke-Download([string]$Uri, [string]$OutFile) {
|
|
40
|
+
# The sandbox network can take a few seconds to come up, so retry.
|
|
41
|
+
for ($attempt = 1; $attempt -le 5; $attempt++) {
|
|
42
|
+
try {
|
|
43
|
+
if ($OutFile) { Invoke-WebRequest -Uri $Uri -OutFile $OutFile -UseBasicParsing; return $null }
|
|
44
|
+
$response = Invoke-WebRequest -Uri $Uri -UseBasicParsing
|
|
45
|
+
if ($response.Content -is [byte[]]) { return [Text.Encoding]::UTF8.GetString($response.Content) }
|
|
46
|
+
return [string]$response.Content
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
if ($attempt -eq 5) { throw }
|
|
50
|
+
Write-Warning "Download failed (attempt $attempt of 5): $($_.Exception.Message). Retrying ..."
|
|
51
|
+
Start-Sleep -Seconds 4
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function Set-Runner([string]$Version) {
|
|
57
|
+
$script:nodeDir = Join-Path $nvmDir ('v' + $Version.TrimStart('v'))
|
|
58
|
+
$script:nodeExe = Join-Path $script:nodeDir 'node.exe'
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function Test-Node {
|
|
62
|
+
if (-not (Test-Path -LiteralPath $nodeExe)) { return $false }
|
|
63
|
+
try { $null = & $nodeExe --version; return ($LASTEXITCODE -eq 0) } catch { return $false }
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function Find-CachedRunner {
|
|
67
|
+
# This PC's version first (nvm will want it anyway), else whichever version the last boot used.
|
|
68
|
+
$last = if (Test-Path -LiteralPath $runnerFile) { (Get-Content -LiteralPath $runnerFile -Raw -Encoding UTF8).Trim() } else { '' }
|
|
69
|
+
foreach ($version in @($hostVersion, $last)) {
|
|
70
|
+
if ($version -notmatch '^\d+\.\d+\.\d+$') { continue }
|
|
71
|
+
Set-Runner $version
|
|
72
|
+
if (Test-Node) { return $true }
|
|
73
|
+
}
|
|
74
|
+
return $false
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function Publish-Directory([string]$Staging, [string]$Destination) {
|
|
78
|
+
if (Test-Path -LiteralPath $Destination) { Remove-Item -LiteralPath $Destination -Recurse -Force }
|
|
79
|
+
Move-Item -LiteralPath $Staging -Destination $Destination
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function Get-Size([string]$Path) {
|
|
83
|
+
if (-not (Test-Path -LiteralPath $Path)) { return 0 }
|
|
84
|
+
$sum = (Get-ChildItem -LiteralPath $Path -Recurse -File -Force -ErrorAction SilentlyContinue | Measure-Object -Property Length -Sum).Sum
|
|
85
|
+
if ($null -eq $sum) { return 0 } else { return [long]$sum }
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function Invoke-Robocopy([string]$Label, [string]$From, [string]$To, [string[]]$Options, [long]$Total) {
|
|
89
|
+
# Runs robocopy in the background and prints how far it has got, so a slow copy never looks frozen.
|
|
90
|
+
# /R:2 /W:1: robocopy's default is 1,000,000 retries 30 s apart, which hangs "forever" on one unreadable file.
|
|
91
|
+
$totalMb = [math]::Round($Total / 1MB, 1)
|
|
92
|
+
Write-Host " $Label ($totalMb MB) ..."
|
|
93
|
+
$argList = @("`"$From`"", "`"$To`"") + $Options + @('/XJ', '/R:2', '/W:1', '/NFL', '/NDL', '/NJH', '/NJS', '/NP')
|
|
94
|
+
$process = Start-Process -FilePath 'robocopy.exe' -ArgumentList $argList -NoNewWindow -PassThru
|
|
95
|
+
$null = $process.Handle # keeps ExitCode readable after the process exits
|
|
96
|
+
$started = Get-Date
|
|
97
|
+
while (-not $process.WaitForExit(2000)) {
|
|
98
|
+
$done = Get-Size $To
|
|
99
|
+
$pct = if ($Total -gt 0) { [math]::Min(100, [math]::Floor($done * 100 / $Total)) } else { 0 }
|
|
100
|
+
$secs = [int]((Get-Date) - $started).TotalSeconds
|
|
101
|
+
Write-Host (" {0}: {1} of {2} MB ({3}%), {4}s" -f $Label, [math]::Round($done / 1MB, 1), $totalMb, $pct, $secs)
|
|
102
|
+
}
|
|
103
|
+
$code = $process.ExitCode
|
|
104
|
+
if ($code -ge 8) { Write-Warning "robocopy failed copying $Label (exit code $code)"; return $false }
|
|
105
|
+
Write-Host (" {0}: done in {1}s" -f $Label, [int]((Get-Date) - $started).TotalSeconds)
|
|
106
|
+
return $true
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function Copy-NodeFromHost {
|
|
110
|
+
# The host maps one of these: nvm (when this PC's Node is an nvm version) or the plain Node folder.
|
|
111
|
+
if ($hostVersion -notmatch '^\d+\.\d+\.\d+$') { return $false }
|
|
112
|
+
$hostNode = @((Join-Path $hostNvm "v$hostVersion"), $hostNodeDir) | Where-Object { Test-Path -LiteralPath (Join-Path $_ 'node.exe') } | Select-Object -First 1
|
|
113
|
+
if (-not $hostNode) { return $false }
|
|
114
|
+
# Only node.exe, the npm shims and node_modules\npm (same list as copyNode in src/guest/env.ts): nothing else is
|
|
115
|
+
# needed, and the folder can hold every global npm package, i.e. tens of thousands of small files over a slow mapping.
|
|
116
|
+
Write-Host "Copying Node $hostVersion from this PC ($hostNode) ..."
|
|
117
|
+
Set-Status 'booting' 'Copying Node from this PC'
|
|
118
|
+
$staging = Join-Path $toolsDir 'node-staging'
|
|
119
|
+
if (Test-Path -LiteralPath $staging) { Remove-Item -LiteralPath $staging -Recurse -Force }
|
|
120
|
+
$files = @('node.exe', 'npm', 'npm.cmd', 'npx', 'npx.cmd')
|
|
121
|
+
$filesSize = ($files | ForEach-Object { Get-Item -LiteralPath (Join-Path $hostNode $_) -ErrorAction SilentlyContinue } | Measure-Object -Property Length -Sum).Sum
|
|
122
|
+
$npmFrom = Join-Path $hostNode 'node_modules\npm'
|
|
123
|
+
$ok = (Invoke-Robocopy -Label 'node.exe' -From $hostNode -To $staging -Options ($files + '/J') -Total ([long]$filesSize)) -and
|
|
124
|
+
(Invoke-Robocopy -Label 'npm' -From $npmFrom -To (Join-Path $staging 'node_modules\npm') -Options @('/E', '/MT:16') -Total (Get-Size $npmFrom))
|
|
125
|
+
if (-not $ok) { return $false }
|
|
126
|
+
Set-Runner $hostVersion
|
|
127
|
+
Publish-Directory -Staging $staging -Destination $nodeDir
|
|
128
|
+
return (Test-Node)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function Mount-MirrorDrives {
|
|
132
|
+
# Windows Sandbox can only map folders onto C:, so a guest path like F:\Games\X is mapped to C:\Mirror\F\Games\X
|
|
133
|
+
# and F: is made here with subst (session.substDrives, written by the host; see src/shared/guest-paths.ts).
|
|
134
|
+
# subst drives belong to the logon session: everything started from here (main.js, the daemon, ptyhost, hooks,
|
|
135
|
+
# Windows Terminal tabs) and Explorer share it, because the sandbox user has one full token (UAC is off).
|
|
136
|
+
# Returns the letters that could NOT be mounted; an existing drive is reported, never replaced.
|
|
137
|
+
$subst = Join-Path $env:SystemRoot 'System32\subst.exe'
|
|
138
|
+
$failed = @()
|
|
139
|
+
foreach ($drive in @($session.substDrives)) {
|
|
140
|
+
if ($null -eq $drive) { continue }
|
|
141
|
+
$letter = ([string]$drive.letter).ToUpperInvariant()
|
|
142
|
+
$target = [string]$drive.target
|
|
143
|
+
if ($letter -notmatch '^[D-Z]$' -or $target -ne "C:\Mirror\$letter") { throw "Invalid subst drive in session.json: '$letter' -> '$target'." }
|
|
144
|
+
$existing = (& $subst) | ForEach-Object { if ($_ -match "^${letter}:\\: => (.+)$") { $Matches[1].Trim() } } | Select-Object -First 1
|
|
145
|
+
if ($existing -and $existing -eq $target) { Write-Host " ${letter}: already points to $target."; continue }
|
|
146
|
+
if ([IO.Directory]::GetLogicalDrives() | Where-Object { $_.Substring(0, 1).ToUpperInvariant() -eq $letter }) {
|
|
147
|
+
Write-Warning "Drive ${letter}: already exists in the sandbox, so it was left alone. Folders mapped to ${letter}:\... are at $target\... instead."
|
|
148
|
+
$failed += $letter
|
|
149
|
+
continue
|
|
150
|
+
}
|
|
151
|
+
if (-not (Test-Path -LiteralPath $target -PathType Container)) { New-Item -ItemType Directory -Path $target -Force | Out-Null }
|
|
152
|
+
# subst.exe exits 0 even when it fails, so check the drive itself.
|
|
153
|
+
& $subst "${letter}:" $target | Out-Host
|
|
154
|
+
if (Test-Path -LiteralPath "${letter}:\") { Write-Host " ${letter}: -> $target" }
|
|
155
|
+
else { Write-Warning "subst ${letter}: $target did not work. Folders mapped to ${letter}:\... are at $target\... instead."; $failed += $letter }
|
|
156
|
+
}
|
|
157
|
+
return ,$failed
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function Update-Environment {
|
|
161
|
+
# main.js writes PATH and the tool variables machine-wide; reload them so this (-NoExit) shell has nvm, git, etc.
|
|
162
|
+
foreach ($scope in @('Machine', 'User')) {
|
|
163
|
+
$vars = [Environment]::GetEnvironmentVariables($scope)
|
|
164
|
+
foreach ($name in $vars.Keys) { if ($name -ne 'Path') { Set-Item -LiteralPath "env:$name" -Value ([string]$vars[$name]) } }
|
|
165
|
+
}
|
|
166
|
+
$env:PATH = ([Environment]::GetEnvironmentVariable('Path', 'Machine'), [Environment]::GetEnvironmentVariable('Path', 'User') | Where-Object { $_ }) -join ';'
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function Install-NodeFromWeb {
|
|
170
|
+
$arch = if ($env:PROCESSOR_ARCHITECTURE -eq 'ARM64') { 'arm64' } else { 'x64' }
|
|
171
|
+
Write-Host 'Looking up the Node LTS release ...'
|
|
172
|
+
$index = (Invoke-Download -Uri 'https://nodejs.org/dist/index.json') | ConvertFrom-Json
|
|
173
|
+
$release = $index | Where-Object { $_.lts -and ($_.files -contains "win-$arch-zip") } | Select-Object -First 1
|
|
174
|
+
if ($null -eq $release) { throw "No Node LTS release found ($arch)." }
|
|
175
|
+
$version = [string]$release.version
|
|
176
|
+
if ($version -notmatch '^v\d+\.\d+\.\d+$') { throw "Unexpected Node version string: $version" }
|
|
177
|
+
$zipName = "node-$version-win-$arch.zip"
|
|
178
|
+
$baseUrl = "https://nodejs.org/dist/$version"
|
|
179
|
+
$work = Join-Path $toolsDir ('node-work-' + [guid]::NewGuid().ToString('N'))
|
|
180
|
+
New-Item -ItemType Directory -Path $work -Force | Out-Null
|
|
181
|
+
try {
|
|
182
|
+
$zipPath = Join-Path $work $zipName
|
|
183
|
+
Write-Host "Downloading $zipName ..."
|
|
184
|
+
$null = Invoke-Download -Uri "$baseUrl/$zipName" -OutFile $zipPath
|
|
185
|
+
$sums = Invoke-Download -Uri "$baseUrl/SHASUMS256.txt"
|
|
186
|
+
$line = ($sums -split "`n") | Where-Object { $_.Trim() -like "* $zipName" } | Select-Object -First 1
|
|
187
|
+
if (-not $line) { throw "No checksum entry for $zipName" }
|
|
188
|
+
$expected = ($line.Trim() -split '\s+')[0]
|
|
189
|
+
if ((Get-FileHash -LiteralPath $zipPath -Algorithm SHA256).Hash -ne $expected.ToUpper()) { throw 'Node download failed checksum verification.' }
|
|
190
|
+
$extractDir = Join-Path $work 'extracted'
|
|
191
|
+
New-Item -ItemType Directory -Path $extractDir -Force | Out-Null
|
|
192
|
+
& tar.exe -xf $zipPath -C $extractDir
|
|
193
|
+
if ($LASTEXITCODE -ne 0) { $global:LASTEXITCODE = 0; Expand-Archive -LiteralPath $zipPath -DestinationPath $extractDir -Force }
|
|
194
|
+
$inner = Join-Path $extractDir "node-$version-win-$arch"
|
|
195
|
+
if (-not (Test-Path -LiteralPath (Join-Path $inner 'node.exe'))) { throw 'Unexpected Node archive layout.' }
|
|
196
|
+
Set-Runner $version
|
|
197
|
+
Publish-Directory -Staging $inner -Destination $nodeDir
|
|
198
|
+
}
|
|
199
|
+
finally {
|
|
200
|
+
if (Test-Path -LiteralPath $work) { Remove-Item -LiteralPath $work -Recurse -Force -ErrorAction SilentlyContinue }
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
try {
|
|
205
|
+
# First: hooks, toolchains and the agent all run in (or below) the workspace, which may be on one of these drives.
|
|
206
|
+
$unmounted = Mount-MirrorDrives
|
|
207
|
+
$workspaceDrive = ([string]$session.workspace).Substring(0, 1).ToUpperInvariant()
|
|
208
|
+
if ($unmounted -contains $workspaceDrive) {
|
|
209
|
+
throw "The workspace $($session.workspace) needs drive ${workspaceDrive}:, which could not be created in the sandbox (see the warning above). Set guestWorkspace to ""auto"" (C:\Workspace\<name>) for this project."
|
|
210
|
+
}
|
|
211
|
+
Set-Status 'booting' 'Preparing the runner'
|
|
212
|
+
$provisioning = $session.provisioning
|
|
213
|
+
if (-not $provisioning.refresh -and (Find-CachedRunner)) {
|
|
214
|
+
Write-Host ('Runner Node ' + (& $nodeExe --version) + ' cached in persistent storage.')
|
|
215
|
+
}
|
|
216
|
+
else {
|
|
217
|
+
$ready = $false
|
|
218
|
+
if ($provisioning.copyFromHost) {
|
|
219
|
+
$ready = Copy-NodeFromHost
|
|
220
|
+
if (-not $ready) {
|
|
221
|
+
if (-not $provisioning.fallbackToInstall) { throw 'Could not copy Node from this PC and provisioning.fallbackToInstall is false.' }
|
|
222
|
+
Write-Warning 'Host Node unavailable; downloading Node instead.'
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
if (-not $ready) { Install-NodeFromWeb }
|
|
226
|
+
if (-not (Test-Node)) { throw 'Node is not runnable after provisioning.' }
|
|
227
|
+
Write-Host ('Runner Node ' + (& $nodeExe --version) + ' ready.')
|
|
228
|
+
}
|
|
229
|
+
[IO.File]::WriteAllText($runnerFile, (Split-Path -Leaf $nodeDir).TrimStart('v'))
|
|
230
|
+
# Older versions kept a full copy of the host's Node folder here.
|
|
231
|
+
$legacy = Join-Path $toolsDir 'node'
|
|
232
|
+
if (Test-Path -LiteralPath $legacy) { Remove-Item -LiteralPath $legacy -Recurse -Force -ErrorAction SilentlyContinue }
|
|
233
|
+
|
|
234
|
+
$env:PATH = "$nodeDir;$env:PATH"
|
|
235
|
+
Set-Location -LiteralPath $session.workspace
|
|
236
|
+
Stop-Transcript | Out-Null
|
|
237
|
+
$transcriptStopped = $true
|
|
238
|
+
& $nodeExe (Join-Path $launchDir 'runtime\main.js')
|
|
239
|
+
$code = $LASTEXITCODE
|
|
240
|
+
if ($code -ne 0) {
|
|
241
|
+
# main.js reports its own failures; this catches it dying before it could (e.g. a broken Node).
|
|
242
|
+
$current = Get-Content -LiteralPath (Join-Path $runDir 'sandbox.json') -Raw -Encoding UTF8 -ErrorAction SilentlyContinue | ConvertFrom-Json -ErrorAction SilentlyContinue
|
|
243
|
+
if ($null -eq $current -or $current.phase -ne 'failed') { Set-Status 'failed' ("The sandbox runner stopped (exit code $code). See C:\AgentData\logs.") }
|
|
244
|
+
Write-Host "The sandbox runner stopped with exit code $code. Logs: $logsDir" -ForegroundColor Red
|
|
245
|
+
}
|
|
246
|
+
Update-Environment
|
|
247
|
+
Write-Host "This window stays open in $($session.workspace)."
|
|
248
|
+
}
|
|
249
|
+
catch {
|
|
250
|
+
$report = ($_ | Format-List * -Force | Out-String) + "`r`n" + $_.ScriptStackTrace
|
|
251
|
+
Set-Content -Path (Join-Path $logsDir 'bootstrap-error.txt') -Value $report -Encoding UTF8
|
|
252
|
+
try { Set-Status 'failed' ('Bootstrap failed: ' + $_.Exception.Message) } catch { }
|
|
253
|
+
Write-Host $report -ForegroundColor Red
|
|
254
|
+
Write-Host "Saved to $logsDir\bootstrap-error.txt" -ForegroundColor Yellow
|
|
255
|
+
}
|
|
256
|
+
finally {
|
|
257
|
+
if (-not $transcriptStopped) { try { Stop-Transcript | Out-Null } catch { } }
|
|
258
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// cargo.exe stand-in for toolchains.rust.targetDir = "separate": every checkout builds into ITS OWN target\sandbox.
|
|
2
|
+
// One CARGO_TARGET_DIR for the whole sandbox made agents working in different git worktrees overwrite each other's
|
|
3
|
+
// builds (and the Claude Code Bash tool does not keep a per-call variable). The checkout is the nearest folder with a
|
|
4
|
+
// .git (a folder, or the file a worktree has); without one, the nearest folder with a Cargo.toml. A CARGO_TARGET_DIR
|
|
5
|
+
// you set yourself always wins. Compiled inside the sandbox with the .NET Framework csc.exe (C# 5).
|
|
6
|
+
using System;
|
|
7
|
+
using System.Diagnostics;
|
|
8
|
+
using System.IO;
|
|
9
|
+
|
|
10
|
+
static class CargoShim
|
|
11
|
+
{
|
|
12
|
+
static int Main()
|
|
13
|
+
{
|
|
14
|
+
string real = Environment.GetEnvironmentVariable("AGENT_REAL_CARGO");
|
|
15
|
+
if (string.IsNullOrEmpty(real) || !File.Exists(real))
|
|
16
|
+
{
|
|
17
|
+
Console.Error.WriteLine("error: the real cargo was not found (AGENT_REAL_CARGO).");
|
|
18
|
+
return 101;
|
|
19
|
+
}
|
|
20
|
+
var start = new ProcessStartInfo(real, ArgumentsOf(Environment.CommandLine)) { UseShellExecute = false };
|
|
21
|
+
if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("CARGO_TARGET_DIR")))
|
|
22
|
+
{
|
|
23
|
+
string root = CheckoutOf(Environment.CurrentDirectory);
|
|
24
|
+
if (root != null) start.EnvironmentVariables["CARGO_TARGET_DIR"] = Path.Combine(root, "target", "sandbox");
|
|
25
|
+
}
|
|
26
|
+
// Ctrl+C reaches cargo too (same console); the shim only waits for it to finish.
|
|
27
|
+
Console.CancelKeyPress += delegate(object sender, ConsoleCancelEventArgs e) { e.Cancel = true; };
|
|
28
|
+
using (Process p = Process.Start(start))
|
|
29
|
+
{
|
|
30
|
+
p.WaitForExit();
|
|
31
|
+
return p.ExitCode;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
static string CheckoutOf(string dir)
|
|
36
|
+
{
|
|
37
|
+
string cargoRoot = null;
|
|
38
|
+
for (var d = new DirectoryInfo(dir); d != null; d = d.Parent)
|
|
39
|
+
{
|
|
40
|
+
string git = Path.Combine(d.FullName, ".git");
|
|
41
|
+
if (Directory.Exists(git) || File.Exists(git)) return d.FullName;
|
|
42
|
+
if (cargoRoot == null && File.Exists(Path.Combine(d.FullName, "Cargo.toml"))) cargoRoot = d.FullName;
|
|
43
|
+
}
|
|
44
|
+
return cargoRoot;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Everything after the program name, untouched, so quoting survives exactly as the caller wrote it.
|
|
48
|
+
static string ArgumentsOf(string commandLine)
|
|
49
|
+
{
|
|
50
|
+
string s = commandLine.TrimStart();
|
|
51
|
+
int end;
|
|
52
|
+
if (s.StartsWith("\"")) { end = s.IndexOf('"', 1); end = end < 0 ? s.Length : end + 1; }
|
|
53
|
+
else { end = s.IndexOf(' '); if (end < 0) end = s.Length; }
|
|
54
|
+
return s.Substring(end).TrimStart();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Caps a process and everything it starts at a share of the PC's CPU. Loaded by cpucap.ps1 (Add-Type). C# 5.
|
|
2
|
+
using System;
|
|
3
|
+
using System.ComponentModel;
|
|
4
|
+
using System.Runtime.InteropServices;
|
|
5
|
+
|
|
6
|
+
public static class SandboxCpuCap {
|
|
7
|
+
// JOBOBJECT_CPU_RATE_CONTROL_INFORMATION.
|
|
8
|
+
[StructLayout(LayoutKind.Sequential)]
|
|
9
|
+
private struct CpuRate { public uint ControlFlags; public uint Rate; }
|
|
10
|
+
|
|
11
|
+
[DllImport("kernel32.dll", SetLastError = true)] private static extern IntPtr CreateJobObject(IntPtr attributes, string name);
|
|
12
|
+
[DllImport("kernel32.dll", SetLastError = true)] private static extern bool SetInformationJobObject(IntPtr job, int infoClass, ref CpuRate info, int length);
|
|
13
|
+
[DllImport("kernel32.dll", SetLastError = true)] private static extern IntPtr OpenProcess(uint access, bool inherit, int pid);
|
|
14
|
+
[DllImport("kernel32.dll", SetLastError = true)] private static extern bool AssignProcessToJobObject(IntPtr job, IntPtr process);
|
|
15
|
+
[DllImport("kernel32.dll")] private static extern bool CloseHandle(IntPtr handle);
|
|
16
|
+
|
|
17
|
+
private const int JobObjectCpuRateControlInformation = 15;
|
|
18
|
+
private const uint CpuRateEnable = 0x1, CpuRateHardCap = 0x4;
|
|
19
|
+
private const uint ProcessTerminate = 0x0001, ProcessSetQuota = 0x0100; // what AssignProcessToJobObject needs
|
|
20
|
+
|
|
21
|
+
/// <summary>Puts process pid in a new job with a hard cap of percent % of all processors. Throws on failure.</summary>
|
|
22
|
+
public static void Apply(int pid, int percent) {
|
|
23
|
+
IntPtr job = CreateJobObject(IntPtr.Zero, null);
|
|
24
|
+
if (job == IntPtr.Zero) throw new Win32Exception();
|
|
25
|
+
// The rate is in 1/100 % of all processors.
|
|
26
|
+
CpuRate rate = new CpuRate { ControlFlags = CpuRateEnable | CpuRateHardCap, Rate = (uint)(percent * 100) };
|
|
27
|
+
if (!SetInformationJobObject(job, JobObjectCpuRateControlInformation, ref rate, Marshal.SizeOf(typeof(CpuRate)))) throw new Win32Exception();
|
|
28
|
+
IntPtr process = OpenProcess(ProcessSetQuota | ProcessTerminate, false, pid);
|
|
29
|
+
if (process == IntPtr.Zero) throw new Win32Exception();
|
|
30
|
+
try { if (!AssignProcessToJobObject(job, process)) throw new Win32Exception(); } finally { CloseHandle(process); }
|
|
31
|
+
// The job's handle is left to close with this process: the job lives on while any process in it runs.
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#Requires -Version 5.1
|
|
2
|
+
# Caps how much of the PC's CPU the sandbox may use (sandbox.cpuLimit). Windows Sandbox has no CPU setting and gets
|
|
3
|
+
# every processor, so heavy builds inside can take the whole PC. This puts logon.cmd (the parent of this script),
|
|
4
|
+
# before it starts anything, into a Windows job object with a hard CPU-rate cap; everything it starts inherits it:
|
|
5
|
+
# Windows Terminal and its tabs, the boot, the agents, installs and builds. Programs started from the sandbox's Start
|
|
6
|
+
# menu are not covered. Never stops the boot: it logs one line either way and the sandbox starts uncapped on failure.
|
|
7
|
+
param([int]$ProcessId = 0, [int]$Percent = 0) # 0 = the parent process / cpuLimitPercent from session.json
|
|
8
|
+
$ErrorActionPreference = 'Stop'
|
|
9
|
+
$log = 'C:\AgentData\logs\logon-hit.txt'
|
|
10
|
+
try {
|
|
11
|
+
if ($Percent -eq 0) { $Percent = [int](Get-Content -LiteralPath 'C:\AgentLaunch\session.json' -Raw -Encoding UTF8 | ConvertFrom-Json).cpuLimitPercent }
|
|
12
|
+
if ($Percent -lt 1 -or $Percent -ge 100) { exit 0 }
|
|
13
|
+
if ($ProcessId -eq 0) { $ProcessId = [int](Get-CimInstance Win32_Process -Filter "ProcessId=$PID").ParentProcessId }
|
|
14
|
+
Add-Type -Path (Join-Path $PSScriptRoot 'cpucap.cs')
|
|
15
|
+
[SandboxCpuCap]::Apply($ProcessId, $Percent)
|
|
16
|
+
"CPU capped at $Percent% of the PC (sandbox.cpuLimit)." | Add-Content -LiteralPath $log
|
|
17
|
+
} catch {
|
|
18
|
+
"CPU cap not applied (the sandbox runs uncapped): $($_.Exception.Message)" | Add-Content -LiteralPath $log
|
|
19
|
+
}
|
|
20
|
+
exit 0
|