windows-development-sandbox 1.10.0 → 1.11.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +64 -17
- package/config.example.json +5 -1
- package/dist/app/features/accounts/accounts.def.d.ts +122 -0
- package/dist/app/features/accounts/accounts.def.js +243 -0
- package/dist/app/features/accounts/accounts.run.d.ts +51 -0
- package/dist/app/features/accounts/accounts.run.js +125 -0
- package/dist/app/features/accounts/index.d.ts +2 -0
- package/dist/app/features/accounts/index.js +3 -0
- package/dist/app/features/agents/agent-entry.d.ts +17 -0
- package/dist/app/features/agents/agent-entry.js +50 -0
- package/dist/app/features/agents/agents.def.d.ts +65 -0
- package/dist/app/features/agents/agents.def.js +142 -0
- package/dist/app/features/agents/agents.run.d.ts +31 -0
- package/dist/app/features/agents/agents.run.js +189 -0
- package/dist/app/features/agents/index.d.ts +3 -0
- package/dist/app/features/agents/index.js +4 -0
- package/dist/app/features/apps/apps-rows.d.ts +31 -0
- package/dist/app/features/apps/apps-rows.js +73 -0
- package/dist/app/features/apps/apps.def.d.ts +181 -0
- package/dist/app/features/apps/apps.def.js +264 -0
- package/dist/app/features/apps/apps.run.d.ts +58 -0
- package/dist/app/features/apps/apps.run.js +76 -0
- package/dist/app/features/apps/index.d.ts +3 -0
- package/dist/app/features/apps/index.js +4 -0
- package/dist/app/features/data/data.def.d.ts +154 -0
- package/dist/app/features/data/data.def.js +201 -0
- package/dist/app/features/data/data.run.d.ts +46 -0
- package/dist/app/features/data/data.run.js +141 -0
- package/dist/app/features/data/index.d.ts +2 -0
- package/dist/app/features/data/index.js +3 -0
- package/dist/app/features/defs.d.ts +952 -4
- package/dist/app/features/defs.js +147 -10
- package/dist/app/features/engine/engine.def.d.ts +228 -0
- package/dist/app/features/engine/engine.def.js +194 -0
- package/dist/app/features/engine/engine.run.d.ts +72 -0
- package/dist/app/features/engine/engine.run.js +77 -0
- package/dist/app/features/engine/index.d.ts +2 -0
- package/dist/app/features/engine/index.js +4 -0
- package/dist/app/features/files/files.def.d.ts +51 -0
- package/dist/app/features/files/files.def.js +38 -0
- package/dist/app/features/files/files.run.d.ts +20 -0
- package/dist/app/features/files/files.run.js +59 -0
- package/dist/app/features/files/index.d.ts +2 -0
- package/dist/app/features/files/index.js +3 -0
- package/dist/app/features/github/github.def.d.ts +80 -0
- package/dist/app/features/github/github.def.js +142 -0
- package/dist/app/features/github/github.run.d.ts +36 -0
- package/dist/app/features/github/github.run.js +59 -0
- package/dist/app/features/github/index.d.ts +2 -0
- package/dist/app/features/github/index.js +3 -0
- package/dist/app/features/health/health.def.d.ts +275 -0
- package/dist/app/features/health/health.def.js +281 -0
- package/dist/app/features/health/health.run.d.ts +125 -0
- package/dist/app/features/health/health.run.js +204 -0
- package/dist/app/features/health/index.d.ts +2 -0
- package/dist/app/features/health/index.js +3 -0
- package/dist/app/features/logs/index.d.ts +2 -0
- package/dist/app/features/logs/index.js +3 -0
- package/dist/app/features/logs/logs.def.d.ts +78 -0
- package/dist/app/features/logs/logs.def.js +115 -0
- package/dist/app/features/logs/logs.run.d.ts +20 -0
- package/dist/app/features/logs/logs.run.js +42 -0
- package/dist/app/features/projects/index.d.ts +2 -0
- package/dist/app/features/projects/index.js +3 -0
- package/dist/app/features/projects/projects.def.d.ts +101 -0
- package/dist/app/features/projects/projects.def.js +208 -0
- package/dist/app/features/projects/projects.run.d.ts +56 -0
- package/dist/app/features/projects/projects.run.js +155 -0
- package/dist/app/features/sandbox/sandbox.def.js +11 -5
- package/dist/app/features/sessions/sessions.def.js +3 -0
- package/dist/app/features/settings/categories.def.js +3 -2
- package/dist/{cli/menu/settings/share.d.ts → app/features/settings/settings-share.d.ts} +1 -1
- package/dist/{cli/menu/settings/share.js → app/features/settings/settings-share.js} +3 -2
- package/dist/app/features/settings/settings.def.d.ts +22 -5
- package/dist/app/features/settings/settings.def.js +81 -6
- package/dist/app/features/settings/settings.run.d.ts +17 -0
- package/dist/app/features/settings/settings.run.js +89 -4
- package/dist/app/features/storage/index.d.ts +2 -0
- package/dist/app/features/storage/index.js +3 -0
- package/dist/app/features/storage/storage.def.d.ts +133 -0
- package/dist/app/features/storage/storage.def.js +225 -0
- package/dist/app/features/storage/storage.run.d.ts +62 -0
- package/dist/app/features/storage/storage.run.js +370 -0
- package/dist/app/features/updates/index.d.ts +3 -0
- package/dist/app/features/updates/index.js +4 -0
- package/dist/app/features/updates/update-lines.d.ts +13 -0
- package/dist/app/features/updates/update-lines.js +61 -0
- package/dist/app/features/updates/updates.def.d.ts +89 -0
- package/dist/app/features/updates/updates.def.js +225 -0
- package/dist/app/features/updates/updates.run.d.ts +48 -0
- package/dist/app/features/updates/updates.run.js +154 -0
- package/dist/app/features/vpn/index.d.ts +2 -0
- package/dist/app/features/vpn/index.js +3 -0
- package/dist/app/features/vpn/vpn.def.d.ts +112 -0
- package/dist/app/features/vpn/vpn.def.js +174 -0
- package/dist/app/features/vpn/vpn.run.d.ts +37 -0
- package/dist/app/features/vpn/vpn.run.js +54 -0
- package/dist/app/features/web/index.d.ts +2 -0
- package/dist/app/features/web/index.js +3 -0
- package/dist/app/features/web/web.def.d.ts +33 -0
- package/dist/app/features/web/web.def.js +90 -0
- package/dist/app/features/web/web.run.d.ts +24 -0
- package/dist/app/features/web/web.run.js +93 -0
- package/dist/app/kernel/define.d.ts +10 -1
- package/dist/app/kernel/io.d.ts +34 -0
- package/dist/app/kernel/io.js +9 -0
- package/dist/app/kernel/rpc.d.ts +17 -0
- package/dist/app/kernel/text.d.ts +2 -0
- package/dist/app/kernel/text.js +7 -0
- package/dist/app/kernel/view.d.ts +24 -1
- package/dist/app/kernel/view.js +1 -1
- package/dist/app/registry.js +74 -1
- package/dist/cli/args.js +1 -1
- package/dist/cli/commands/context.d.ts +7 -0
- package/dist/cli/commands/context.js +29 -2
- package/dist/cli/commands/engine.js +5 -58
- package/dist/cli/commands/github.d.ts +2 -0
- package/dist/cli/commands/github.js +11 -0
- package/dist/cli/commands/index.d.ts +3 -5
- package/dist/cli/commands/index.js +3 -5
- package/dist/cli/commands/launch/launch.js +21 -4
- package/dist/cli/commands/launch/running.js +4 -1
- package/dist/cli/commands/maintenance/config.js +1 -6
- package/dist/cli/commands/maintenance/index.d.ts +0 -1
- package/dist/cli/commands/maintenance/index.js +1 -2
- package/dist/cli/commands/maintenance/logs.js +27 -32
- package/dist/cli/commands/maintenance/migrate.d.ts +0 -8
- package/dist/cli/commands/maintenance/migrate.js +2 -44
- package/dist/cli/commands/maintenance/update.d.ts +0 -19
- package/dist/cli/commands/maintenance/update.js +1 -99
- package/dist/cli/commands/projects/index.d.ts +0 -1
- package/dist/cli/commands/projects/index.js +0 -1
- package/dist/cli/commands/registry.d.ts +1 -1
- package/dist/cli/commands/registry.js +29 -11
- package/dist/cli/commands/repair/checks/data.js +1 -1
- package/dist/cli/commands/repair/checks/hcs.js +28 -1
- package/dist/cli/commands/repair/checks/sandbox.js +1 -1
- package/dist/cli/commands/repair/health-run.d.ts +30 -0
- package/dist/cli/commands/repair/health-run.js +32 -0
- package/dist/cli/commands/repair/printer.d.ts +2 -2
- package/dist/cli/commands/repair/printer.js +2 -11
- package/dist/cli/commands/repair/run.d.ts +1 -21
- package/dist/cli/commands/repair/run.js +13 -70
- package/dist/cli/commands/storage/index.d.ts +0 -1
- package/dist/cli/commands/storage/index.js +0 -1
- package/dist/cli/commands/storage/offer.js +6 -2
- package/dist/cli/commands/vpn.d.ts +1 -3
- package/dist/cli/commands/vpn.js +8 -89
- package/dist/cli/commands/web.d.ts +2 -1
- package/dist/cli/commands/web.js +14 -78
- package/dist/cli/index.js +3 -2
- package/dist/cli/manifest.d.ts +16 -153
- package/dist/cli/manifest.js +20 -50
- package/dist/cli/menu/command-form.js +1 -2
- package/dist/cli/menu/io-menu.d.ts +2 -2
- package/dist/cli/menu/io-menu.js +6 -4
- package/dist/cli/menu/menu.js +1 -1
- package/dist/cli/menu/pages/accounts-page.d.ts +5 -17
- package/dist/cli/menu/pages/accounts-page.js +28 -170
- package/dist/cli/menu/pages/agents-page.d.ts +17 -18
- package/dist/cli/menu/pages/agents-page.js +67 -124
- package/dist/cli/menu/pages/apps-page.d.ts +15 -8
- package/dist/cli/menu/pages/apps-page.js +121 -29
- package/dist/cli/menu/pages/list-page.js +10 -1
- package/dist/cli/menu/pages/maintenance-page.d.ts +51 -52
- package/dist/cli/menu/pages/maintenance-page.js +246 -427
- package/dist/cli/menu/pages/sandbox-page.d.ts +3 -5
- package/dist/cli/menu/pages/sandbox-page.js +34 -112
- package/dist/cli/menu/pages/settings-page.d.ts +21 -3
- package/dist/cli/menu/pages/settings-page.js +89 -17
- package/dist/cli/menu/render-view.js +2 -1
- package/dist/cli/menu/settings/index.d.ts +0 -1
- package/dist/cli/menu/settings/index.js +3 -3
- package/dist/cli/menu/views.js +28 -0
- package/dist/cli/operations/handler.js +7 -3
- package/dist/cli/operations/input.d.ts +1 -1
- package/dist/cli/operations/input.js +29 -5
- package/dist/cli/operations/io-cli.js +9 -1
- package/dist/cli/operations/spec.js +8 -2
- package/dist/cli/output/render-view.js +2 -0
- package/dist/cli/{commands/data/collect.js → services/data-collect.js} +2 -2
- package/dist/cli/{commands/data/remove.js → services/data-remove.js} +2 -2
- package/dist/cli/services/data-service.d.ts +1 -1
- package/dist/cli/services/data-service.js +2 -2
- package/dist/cli/services/databases-service.d.ts +5 -0
- package/dist/cli/services/databases-service.js +15 -0
- package/dist/cli/services/health-service.d.ts +8 -4
- package/dist/cli/services/health-service.js +10 -8
- package/dist/cli/services/index.d.ts +4 -1
- package/dist/cli/services/index.js +4 -1
- package/dist/cli/services/logs-service.d.ts +7 -0
- package/dist/cli/services/logs-service.js +13 -0
- package/dist/cli/services/storage-service.d.ts +19 -0
- package/dist/cli/services/storage-service.js +71 -0
- package/dist/cli/services/vpn-service.d.ts +6 -4
- package/dist/cli/services/vpn-service.js +5 -11
- package/dist/cli/services/web-service.d.ts +4 -3
- package/dist/cli/services/web-service.js +12 -32
- package/dist/core/config/defaults.js +2 -1
- package/dist/core/schema/config.d.ts +5 -0
- package/dist/core/schema/config.js +2 -0
- package/dist/core/schema/github.d.ts +4 -0
- package/dist/core/schema/github.js +7 -0
- package/dist/core/schema/index.d.ts +1 -0
- package/dist/core/schema/index.js +1 -0
- package/dist/core/schema/profile.d.ts +1 -0
- package/dist/core/schema/profile.js +3 -0
- package/dist/core/schema/sandbox.js +3 -3
- package/dist/core/schema/toolchains.js +2 -2
- package/dist/core/types.d.ts +6 -3
- package/dist/guest/boot/boot.js +8 -0
- package/dist/guest/daemon/daemon.js +1 -1
- package/dist/guest/daemon/sessions.d.ts +3 -2
- package/dist/guest/daemon/sessions.js +38 -7
- package/dist/guest/governor/core.d.ts +44 -0
- package/dist/guest/governor/core.js +124 -0
- package/dist/guest/governor/decide.d.ts +46 -18
- package/dist/guest/governor/decide.js +136 -82
- package/dist/guest/governor/governor.d.ts +2 -38
- package/dist/guest/governor/governor.js +33 -113
- package/dist/guest/governor/index.d.ts +1 -0
- package/dist/guest/governor/index.js +1 -0
- package/dist/guest/network/firewall.d.ts +2 -0
- package/dist/guest/network/firewall.js +3 -3
- package/dist/guest/sessions/viewer.js +5 -1
- package/dist/guest/setup/github-token.d.ts +7 -0
- package/dist/guest/setup/github-token.js +65 -0
- package/dist/guest/toolchains/languages/node.js +11 -1
- package/dist/guest/toolchains/native/rust-extras.js +2 -2
- package/dist/guest/toolchains/tools/cli.d.ts +8 -0
- package/dist/guest/toolchains/tools/cli.js +16 -2
- package/dist/guest/vpn/openvpn.d.ts +5 -0
- package/dist/guest/vpn/openvpn.js +14 -1
- package/dist/guest/vpn/vpn.js +55 -16
- package/dist/guest/vpn/wireguard.js +2 -0
- package/dist/host/channel/attach.d.ts +1 -0
- package/dist/host/channel/attach.js +1 -0
- package/dist/host/channel/channel.d.ts +1 -1
- package/dist/host/channel/channel.js +2 -2
- package/dist/host/engines/hcs/check.d.ts +1 -0
- package/dist/host/engines/hcs/check.js +1 -1
- package/dist/host/engines/hcs/engine.d.ts +1 -1
- package/dist/host/engines/hcs/engine.js +6 -4
- package/dist/host/engines/hcs/helper.d.ts +11 -0
- package/dist/host/engines/hcs/helper.js +31 -2
- package/dist/host/engines/hcs/leftovers.d.ts +42 -0
- package/dist/host/engines/hcs/leftovers.js +101 -0
- package/dist/host/engines/index.d.ts +1 -0
- package/dist/host/engines/index.js +1 -0
- package/dist/host/github/deliver.d.ts +13 -0
- package/dist/host/github/deliver.js +51 -0
- package/dist/host/github/index.d.ts +2 -0
- package/dist/host/github/index.js +3 -0
- package/dist/host/github/token.d.ts +21 -0
- package/dist/host/github/token.js +74 -0
- package/dist/host/launch/generate.js +11 -5
- package/dist/host/launch/index.d.ts +1 -1
- package/dist/host/launch/index.js +1 -1
- package/dist/host/launch/session-file.js +1 -2
- package/dist/host/launch/wsb-file.d.ts +11 -7
- package/dist/host/launch/wsb-file.js +30 -12
- package/dist/host/life/watchdog.d.ts +9 -0
- package/dist/host/life/watchdog.js +17 -1
- package/dist/host/sandbox/tasklist.d.ts +6 -0
- package/dist/host/sandbox/tasklist.js +37 -0
- package/dist/host/secrets/launch-secrets.d.ts +2 -1
- package/dist/host/secrets/launch-secrets.js +4 -2
- package/dist/host/vpn/status.js +13 -3
- package/dist/host/web/backend.d.ts +1 -3
- package/dist/host/web/index.d.ts +2 -1
- package/dist/host/web/index.js +2 -1
- package/dist/host/web/live.d.ts +23 -0
- package/dist/host/web/live.js +107 -0
- package/dist/host/web/rpc.d.ts +6 -1
- package/dist/host/web/rpc.js +23 -31
- package/dist/host/web/runner.d.ts +4 -0
- package/dist/host/web/runner.js +12 -1
- package/dist/host/web/server.d.ts +30 -2
- package/dist/host/web/server.js +87 -29
- package/dist/host/web/state.d.ts +1 -0
- package/dist/host/web/state.js +2 -0
- package/dist/runtime/agent.js +30 -6
- package/dist/runtime/daemon.js +323 -176
- package/dist/runtime/db.js +30 -6
- package/dist/runtime/main.js +375 -76
- package/dist/runtime/netproxy.js +30 -6
- package/dist/runtime/view.js +495 -146
- package/dist/selftest/app.test.js +620 -12
- package/dist/selftest/channel.test.js +18 -1
- package/dist/selftest/cli.test.js +1 -1
- package/dist/selftest/config.test.js +1 -1
- package/dist/selftest/contained.test.js +1 -1
- package/dist/selftest/data.test.js +1 -2
- package/dist/selftest/engine.test.js +3 -1
- package/dist/selftest/github.test.d.ts +2 -0
- package/dist/selftest/github.test.js +243 -0
- package/dist/selftest/hcs.test.js +105 -8
- package/dist/selftest/health.test.js +71 -0
- package/dist/selftest/heavy-load.test.js +132 -72
- package/dist/selftest/index.js +4 -0
- package/dist/selftest/layout.test.js +8 -0
- package/dist/selftest/live-terminal.test.js +74 -0
- package/dist/selftest/menu-screens.js +171 -56
- package/dist/selftest/menu.test.js +54 -37
- package/dist/selftest/migrate.test.js +14 -12
- package/dist/selftest/powershell-variables.test.d.ts +2 -0
- package/dist/selftest/powershell-variables.test.js +61 -0
- package/dist/selftest/support/engine-golden.js +1 -0
- package/dist/selftest/support/fixtures.d.ts +4 -0
- package/dist/selftest/support/web-preview.d.ts +1 -0
- package/dist/selftest/support/web-preview.js +856 -0
- package/dist/selftest/update-service.test.js +15 -8
- package/dist/selftest/vpn.test.js +86 -2
- package/dist/selftest/web.test.js +58 -11
- package/dist/shared/channel/run-dir.d.ts +2 -0
- package/dist/shared/channel/run-dir.js +3 -0
- package/dist/shared/channel/schema.d.ts +7 -0
- package/dist/shared/channel/schema.js +9 -1
- package/dist/shared/channel/secrets.d.ts +1 -0
- package/dist/shared/channel/secrets.js +3 -0
- package/dist/shared/github/index.d.ts +1 -0
- package/dist/shared/github/index.js +2 -0
- package/dist/shared/github/token.d.ts +29 -0
- package/dist/shared/github/token.js +89 -0
- package/dist/shared/memory/policy.d.ts +33 -12
- package/dist/shared/memory/policy.js +50 -19
- package/dist/shared/pty/ctrl-c.d.ts +18 -0
- package/dist/shared/pty/ctrl-c.js +36 -0
- package/dist/shared/pty/index.d.ts +1 -0
- package/dist/shared/pty/index.js +1 -0
- package/dist/shared/pty/terminal.d.ts +2 -0
- package/dist/shared/pty/terminal.js +21 -2
- package/dist/shared/vpn/kill-switch.d.ts +13 -2
- package/dist/shared/vpn/kill-switch.js +88 -8
- package/dist/shared/web/protocol.d.ts +0 -4
- package/dist/sync/watcher.js +1 -1
- package/dist/web/index.html +2 -2
- package/dist/web/{main-T5DMCHYV.css → main-6JF3ASB5.css} +1 -1
- package/dist/web/main-Z645W45E.js +219 -0
- package/native/csharp/HcsHost/AssemblyInfo.cs +3 -3
- package/native/csharp/HostTools/AssemblyInfo.cs +3 -3
- package/package.json +10 -3
- package/sandbox/csharp/MemGov.cs +240 -38
- package/sandbox/csharp/PtyHost.cs +104 -15
- package/sandbox/csharp/SandboxTools/CpuCap.cs +50 -1
- package/sandbox/csharp/SandboxTools/Program.cs +1 -1
- package/sandbox/scripts/repair-wmi-services.ps1 +26 -0
- package/schemas/config.schema.json +19 -5
- package/schemas/sandbox-config.schema.json +7 -3
- package/dist/cli/commands/accounts.d.ts +0 -2
- package/dist/cli/commands/accounts.js +0 -96
- package/dist/cli/commands/data/clean.d.ts +0 -3
- package/dist/cli/commands/data/clean.js +0 -60
- package/dist/cli/commands/data/index.d.ts +0 -4
- package/dist/cli/commands/data/index.js +0 -62
- package/dist/cli/commands/maintenance/clean.d.ts +0 -2
- package/dist/cli/commands/maintenance/clean.js +0 -24
- package/dist/cli/commands/maintenance/config-share.d.ts +0 -12
- package/dist/cli/commands/maintenance/config-share.js +0 -80
- package/dist/cli/commands/projects/projects.d.ts +0 -2
- package/dist/cli/commands/projects/projects.js +0 -162
- package/dist/cli/commands/storage/contained.d.ts +0 -3
- package/dist/cli/commands/storage/contained.js +0 -179
- package/dist/cli/commands/storage/storage.d.ts +0 -8
- package/dist/cli/commands/storage/storage.js +0 -179
- package/dist/cli/menu/add-agent.d.ts +0 -47
- package/dist/cli/menu/add-agent.js +0 -168
- package/dist/cli/menu/apps.d.ts +0 -69
- package/dist/cli/menu/apps.js +0 -214
- package/dist/cli/menu/data-folder.d.ts +0 -18
- package/dist/cli/menu/data-folder.js +0 -99
- package/dist/cli/menu/share-settings.d.ts +0 -21
- package/dist/cli/menu/share-settings.js +0 -112
- package/dist/cli/menu/vpn-login.d.ts +0 -16
- package/dist/cli/menu/vpn-login.js +0 -74
- package/dist/web/main-NRRS5O6Z.js +0 -157
- /package/dist/cli/{commands/data/collect.d.ts → services/data-collect.d.ts} +0 -0
- /package/dist/cli/{commands/data/remove.d.ts → services/data-remove.d.ts} +0 -0
|
@@ -129,6 +129,15 @@ const activate = async (version) => {
|
|
|
129
129
|
fs.rmSync(NVM_SYMLINK, { recursive: true, force: true });
|
|
130
130
|
fs.symlinkSync(path.join(NVM_HOME, 'v' + version), NVM_SYMLINK, 'junction');
|
|
131
131
|
};
|
|
132
|
+
/**
|
|
133
|
+
* Playwright works in every sandbox without setup: the browsers `npx playwright install` downloads go in the tools
|
|
134
|
+
* folder, so they last between sandboxes, and its MCP server (and `channel: 'msedge'` in tests) uses Edge, the one
|
|
135
|
+
* browser Windows Sandbox always has (Chrome is not there).
|
|
136
|
+
*/
|
|
137
|
+
const PLAYWRIGHT_VARS = () => ({
|
|
138
|
+
PLAYWRIGHT_BROWSERS_PATH: tool('ms-playwright'),
|
|
139
|
+
PLAYWRIGHT_MCP_BROWSER: 'msedge',
|
|
140
|
+
});
|
|
132
141
|
export async function setupNode(env, versions, config) {
|
|
133
142
|
if (dryRun) {
|
|
134
143
|
log('(dry run) would provision nvm and Node ' + versions.node.spec + ' (from ' + versions.node.source + ')');
|
|
@@ -162,7 +171,7 @@ export async function setupNode(env, versions, config) {
|
|
|
162
171
|
warn('Node ' + version + ' from this PC: ' + errMsg(e));
|
|
163
172
|
}
|
|
164
173
|
}
|
|
165
|
-
Object.assign(env.vars, { NVM_HOME, NVM_SYMLINK });
|
|
174
|
+
Object.assign(env.vars, { NVM_HOME, NVM_SYMLINK, ...PLAYWRIGHT_VARS() });
|
|
166
175
|
// At boot, nvm's symlink selects the version. A project added later must not switch it under the sessions already
|
|
167
176
|
// running: its own version's folder goes first on its sessions' PATH instead.
|
|
168
177
|
if (duringSession())
|
|
@@ -172,6 +181,7 @@ export async function setupNode(env, versions, config) {
|
|
|
172
181
|
env.prepend.push(NVM_HOME, NVM_SYMLINK);
|
|
173
182
|
}
|
|
174
183
|
env.summary.push('node: ' + main + ' (from ' + versions.node.source + ', switch with nvm use)');
|
|
184
|
+
env.summary.push("playwright: ready (Edge; browsers it downloads are kept; in tests use channel: 'msedge')");
|
|
175
185
|
await packageManagers(env, config, main);
|
|
176
186
|
}
|
|
177
187
|
/** Global npm packages (per Node version, like nvm keeps them) and pnpm/yarn through corepack. */
|
|
@@ -7,7 +7,7 @@ import { execOutput } from '../../runtime/process.js';
|
|
|
7
7
|
import { ensureCompiled } from '../../runtime/csc.js';
|
|
8
8
|
import { arch, tool } from '../tool-env.js';
|
|
9
9
|
import { wanted } from '../detect.js';
|
|
10
|
-
import { cargoJobs } from '../../../shared/memory/index.js';
|
|
10
|
+
import { cargoJobs, linkSlotsFor } from '../../../shared/memory/index.js';
|
|
11
11
|
/**
|
|
12
12
|
* Where cargo builds: shared with the PC's target\, one on the sandbox's disk, or a path. "separate" sets no variable:
|
|
13
13
|
* the cargo shim (sandbox/csharp/CargoShim.cs) gives every checkout its own target\sandbox, so git worktrees of several
|
|
@@ -41,7 +41,7 @@ export function linkGate(env, rust, host) {
|
|
|
41
41
|
const gate = path.join(mkdirp(tool('shims')), 'linkgate.exe');
|
|
42
42
|
fs.copyFileSync(ensureCompiled('linkgate'), gate);
|
|
43
43
|
const memoryGb = os.totalmem() / 1024 ** 3;
|
|
44
|
-
const slots = rust.linkSlots === 'auto' ?
|
|
44
|
+
const slots = rust.linkSlots === 'auto' ? linkSlotsFor(memoryGb) : rust.linkSlots;
|
|
45
45
|
env.vars.AGENT_LINK_SLOTS = String(slots);
|
|
46
46
|
env.vars.AGENT_REAL_LINKER = gcc;
|
|
47
47
|
env.vars.CARGO_TARGET_X86_64_PC_WINDOWS_GNU_LINKER = gate;
|
|
@@ -3,6 +3,14 @@ import { type CliSettings } from '../catalogue.js';
|
|
|
3
3
|
import { type ToolEnv } from '../tool-env.js';
|
|
4
4
|
/** gh keeps `gh auth login` here, under the agent state (persists like Claude's login; never copied from the PC). */
|
|
5
5
|
export declare function ghConfigDir(): string;
|
|
6
|
+
/**
|
|
7
|
+
* gh's folder for the token the host handed over (github.shareToken): on the sandbox's own disk, so it is gone with the
|
|
8
|
+
* sandbox and never reaches the persistent state other sandboxes and projects share. The boot creates it only when a
|
|
9
|
+
* token arrived; while it exists, every gh setup (later projects too) points GH_CONFIG_DIR at it.
|
|
10
|
+
*/
|
|
11
|
+
export declare function deliveredGhConfigDir(): string;
|
|
12
|
+
/** gh.exe where the catalogue installs it. */
|
|
13
|
+
export declare function ghExePath(): string;
|
|
6
14
|
/**
|
|
7
15
|
* Installs the tools toolchains.cli asks for. Settings still "auto" here (normally the host decides) use the same
|
|
8
16
|
* rules on the workspace as the host would. The gh rule is enforced here again, whatever the host sent.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// `openssl` shim onto the copy Git for Windows already ships.
|
|
3
3
|
import fs from 'node:fs';
|
|
4
4
|
import path from 'node:path';
|
|
5
|
-
import { dryRun } from '../../runtime/paths.js';
|
|
5
|
+
import { dryRun, localDir } from '../../runtime/paths.js';
|
|
6
6
|
import { exists, mkdirp } from '../../runtime/fs.js';
|
|
7
7
|
import { log, warn } from '../../runtime/log.js';
|
|
8
8
|
import { stateDir } from '../../runtime/session.js';
|
|
@@ -16,9 +16,23 @@ import { ensureCompiled } from '../../runtime/csc.js';
|
|
|
16
16
|
export function ghConfigDir() {
|
|
17
17
|
return path.join(stateDir, 'gh');
|
|
18
18
|
}
|
|
19
|
+
/**
|
|
20
|
+
* gh's folder for the token the host handed over (github.shareToken): on the sandbox's own disk, so it is gone with the
|
|
21
|
+
* sandbox and never reaches the persistent state other sandboxes and projects share. The boot creates it only when a
|
|
22
|
+
* token arrived; while it exists, every gh setup (later projects too) points GH_CONFIG_DIR at it.
|
|
23
|
+
*/
|
|
24
|
+
export function deliveredGhConfigDir() {
|
|
25
|
+
return path.join(localDir, 'gh-token');
|
|
26
|
+
}
|
|
27
|
+
/** gh.exe where the catalogue installs it. */
|
|
28
|
+
export function ghExePath() {
|
|
29
|
+
const entry = CLI_TOOLS.find((t) => t.name === 'gh');
|
|
30
|
+
const dir = tool('cli', 'gh');
|
|
31
|
+
return path.join(entry?.subdir ? path.join(dir, entry.subdir) : dir, entry?.exe ?? 'gh.exe');
|
|
32
|
+
}
|
|
19
33
|
const toolVars = {
|
|
20
34
|
gh: () => ({
|
|
21
|
-
GH_CONFIG_DIR: mkdirp(ghConfigDir()),
|
|
35
|
+
GH_CONFIG_DIR: exists(deliveredGhConfigDir()) ? deliveredGhConfigDir() : mkdirp(ghConfigDir()),
|
|
22
36
|
GH_NO_UPDATE_NOTIFIER: '1',
|
|
23
37
|
GH_NO_EXTENSION_UPDATE_NOTIFIER: '1',
|
|
24
38
|
}),
|
|
@@ -5,5 +5,10 @@ export declare function openvpnDownload(arch?: string): {
|
|
|
5
5
|
sha256: string;
|
|
6
6
|
url: string;
|
|
7
7
|
};
|
|
8
|
+
/**
|
|
9
|
+
* Windows Sandbox cannot answer WMI questions about services (a class whose provider is missing), and installers that
|
|
10
|
+
* ask (OpenVPN's MSI does) fail with 1603: the dead class is removed first (repair-wmi-services.ps1). Never throws.
|
|
11
|
+
*/
|
|
12
|
+
export declare function repairServiceQueries(): void;
|
|
8
13
|
/** Installs OpenVPN unless it is there; returns the path of openvpn.exe. Throws with the reason when it cannot. */
|
|
9
14
|
export declare function ensureOpenVpn(): Promise<string>;
|
|
@@ -8,7 +8,8 @@ import { downloader, spawnAsync } from '../../shared/download/index.js';
|
|
|
8
8
|
import { SYSTEM } from '../../shared/system.js';
|
|
9
9
|
import { dryRun, isWin, toolsDir } from '../runtime/paths.js';
|
|
10
10
|
import { exists, mkdirp } from '../runtime/fs.js';
|
|
11
|
-
import { log } from '../runtime/log.js';
|
|
11
|
+
import { log, warn } from '../runtime/log.js';
|
|
12
|
+
import { scriptOutput } from '../runtime/process.js';
|
|
12
13
|
import { OPENVPN_MSI } from '../../shared/vpn/download.js';
|
|
13
14
|
/** Where the MSI puts the program. */
|
|
14
15
|
export function openvpnExe(env = process.env) {
|
|
@@ -18,6 +19,17 @@ export function openvpnDownload(arch = process.arch) {
|
|
|
18
19
|
const entry = arch === 'arm64' ? OPENVPN_MSI.arm64 : OPENVPN_MSI.x64;
|
|
19
20
|
return { ...entry, url: OPENVPN_MSI.base + entry.file };
|
|
20
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* Windows Sandbox cannot answer WMI questions about services (a class whose provider is missing), and installers that
|
|
24
|
+
* ask (OpenVPN's MSI does) fail with 1603: the dead class is removed first (repair-wmi-services.ps1). Never throws.
|
|
25
|
+
*/
|
|
26
|
+
export function repairServiceQueries() {
|
|
27
|
+
const did = scriptOutput('repair-wmi-services.ps1');
|
|
28
|
+
if (did === 'removed')
|
|
29
|
+
log('VPN: repaired the Windows service queries installers use (a Windows Sandbox gap).');
|
|
30
|
+
else if (did !== 'absent' && did !== 'kept')
|
|
31
|
+
warn('VPN: could not check the Windows service queries installers use.');
|
|
32
|
+
}
|
|
21
33
|
const hasHash = (file, sha256) => crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex') === sha256;
|
|
22
34
|
/** Installs OpenVPN unless it is there; returns the path of openvpn.exe. Throws with the reason when it cannot. */
|
|
23
35
|
export async function ensureOpenVpn() {
|
|
@@ -33,6 +45,7 @@ export async function ensureOpenVpn() {
|
|
|
33
45
|
log('VPN: downloading OpenVPN ' + OPENVPN_MSI.version + ' (about 6 MB)');
|
|
34
46
|
await downloader.file(dl.url, msi, dl.sha256);
|
|
35
47
|
}
|
|
48
|
+
repairServiceQueries();
|
|
36
49
|
log('VPN: installing OpenVPN');
|
|
37
50
|
const logFile = path.join(toolsDir, 'downloads', 'openvpn-install.log');
|
|
38
51
|
const code = await spawnAsync(SYSTEM.msiexec(), ['/i', msi, '/qn', '/norestart', '/l*v', logFile], {
|
package/dist/guest/vpn/vpn.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
// warns.
|
|
5
5
|
import dns from 'node:dns';
|
|
6
6
|
import fs from 'node:fs';
|
|
7
|
+
import net from 'node:net';
|
|
7
8
|
import os from 'node:os';
|
|
8
9
|
import path from 'node:path';
|
|
9
10
|
import { now } from '../../shared/channel/run-dir.js';
|
|
@@ -13,7 +14,7 @@ import { assertSafeWireguard, renderWireguard, routesEverything, wireguardEndpoi
|
|
|
13
14
|
import { isIPv4 } from '../../shared/network/firewall-rules.js';
|
|
14
15
|
import { killSwitchCommands, tunnelCommands } from '../../shared/vpn/kill-switch.js';
|
|
15
16
|
import { errMsg } from '../../core/util/errors.js';
|
|
16
|
-
import { run as netsh } from '../network/firewall.js';
|
|
17
|
+
import { longPath, run as netsh } from '../network/firewall.js';
|
|
17
18
|
import { dryRun, isWin, launchDir, run } from '../runtime/paths.js';
|
|
18
19
|
import { log, warn } from '../runtime/log.js';
|
|
19
20
|
import { session } from '../runtime/session.js';
|
|
@@ -74,6 +75,27 @@ async function publicAddress() {
|
|
|
74
75
|
}
|
|
75
76
|
}
|
|
76
77
|
const hasAdapter = (ip) => Object.values(os.networkInterfaces()).some((list) => (list ?? []).some((i) => i.address === ip));
|
|
78
|
+
/** Well-known public addresses a direct connection is tried to, to see that the kill switch holds. */
|
|
79
|
+
const PROBE_TARGETS = ['1.1.1.1', '8.8.8.8', '9.9.9.9'];
|
|
80
|
+
const PROBE_TIMEOUT_MS = 3000;
|
|
81
|
+
function connects(ip, port) {
|
|
82
|
+
return new Promise((resolve) => {
|
|
83
|
+
const socket = net.connect({ host: ip, port });
|
|
84
|
+
const done = (ok) => {
|
|
85
|
+
socket.destroy();
|
|
86
|
+
resolve(ok);
|
|
87
|
+
};
|
|
88
|
+
socket.setTimeout(PROBE_TIMEOUT_MS, () => done(false));
|
|
89
|
+
socket.once('connect', () => done(true));
|
|
90
|
+
socket.once('error', () => done(false));
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
/** True when a direct connection out (before the tunnel exists) fails: the kill switch holds. */
|
|
94
|
+
async function directConnectionBlocked(servers) {
|
|
95
|
+
const targets = PROBE_TARGETS.filter((ip) => !servers.some((s) => s.ip === ip));
|
|
96
|
+
const results = await Promise.all(targets.map((ip) => connects(ip, 443)));
|
|
97
|
+
return !results.some(Boolean);
|
|
98
|
+
}
|
|
77
99
|
/** The whole step. Returns true when the tunnel is up. */
|
|
78
100
|
export async function setupVpn(secrets, ptyHostExe) {
|
|
79
101
|
const vpn = session.vpn;
|
|
@@ -81,11 +103,24 @@ export async function setupVpn(secrets, ptyHostExe) {
|
|
|
81
103
|
return true;
|
|
82
104
|
const killSwitch = vpn.killSwitch;
|
|
83
105
|
let blocked = false;
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
106
|
+
/** A direct connection was tried after the kill switch and failed: only then is the sandbox said to be offline. */
|
|
107
|
+
let held = false;
|
|
108
|
+
const block = async (servers) => {
|
|
109
|
+
if (!isWin || dryRun) {
|
|
110
|
+
blocked = held = true;
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
try {
|
|
114
|
+
netsh(killSwitchCommands(servers, ptyHostExe ? longPath(ptyHostExe) : null));
|
|
115
|
+
}
|
|
116
|
+
finally {
|
|
117
|
+
blocked = true;
|
|
118
|
+
held = await directConnectionBlocked(servers);
|
|
119
|
+
}
|
|
120
|
+
if (held)
|
|
121
|
+
log('VPN: kill switch on: nothing leaves the sandbox except through the tunnel (checked).');
|
|
122
|
+
else
|
|
123
|
+
warn('VPN: the kill switch did not hold: a direct connection still got out. If the tunnel drops, traffic leaves through your normal connection.');
|
|
89
124
|
};
|
|
90
125
|
setVpnStatus('starting', 'Starting the VPN');
|
|
91
126
|
try {
|
|
@@ -103,7 +138,7 @@ export async function setupVpn(secrets, ptyHostExe) {
|
|
|
103
138
|
if (killSwitch && !routesEverything(config))
|
|
104
139
|
warn('VPN: AllowedIPs does not include 0.0.0.0/0: with the kill switch on, only the tunnel traffic can leave the sandbox.');
|
|
105
140
|
if (killSwitch)
|
|
106
|
-
block(servers);
|
|
141
|
+
await block(servers);
|
|
107
142
|
if (dryRun || !isWin) {
|
|
108
143
|
log('(dry run) would start the WireGuard tunnel ' + vpn.server);
|
|
109
144
|
setVpnStatus('off', 'dry run');
|
|
@@ -129,7 +164,7 @@ export async function setupVpn(secrets, ptyHostExe) {
|
|
|
129
164
|
if (servers.length === 0)
|
|
130
165
|
throw new Error('the VPN server addresses could not be found');
|
|
131
166
|
if (killSwitch)
|
|
132
|
-
block(servers);
|
|
167
|
+
await block(servers);
|
|
133
168
|
if (dryRun || !isWin) {
|
|
134
169
|
log('(dry run) would start the VPN tunnel to ' + vpn.server);
|
|
135
170
|
setVpnStatus('off', 'dry run');
|
|
@@ -149,7 +184,10 @@ export async function setupVpn(secrets, ptyHostExe) {
|
|
|
149
184
|
throw new Error('the tunnel reported it was up, but no network adapter with its address appeared');
|
|
150
185
|
const publicIp = await publicAddress();
|
|
151
186
|
log('VPN: up' + (publicIp ? ' (the sandbox appears as ' + publicIp + ')' : '') + '.');
|
|
152
|
-
setVpnStatus('up', 'The tunnel is up',
|
|
187
|
+
setVpnStatus('up', 'The tunnel is up', {
|
|
188
|
+
...(publicIp ? { publicIp } : {}),
|
|
189
|
+
...(killSwitch ? { killSwitchHeld: held } : {}),
|
|
190
|
+
});
|
|
153
191
|
return true;
|
|
154
192
|
}
|
|
155
193
|
catch (e) {
|
|
@@ -157,18 +195,19 @@ export async function setupVpn(secrets, ptyHostExe) {
|
|
|
157
195
|
// Without the kill switch the sandbox keeps its normal connection: the rest of the boot goes on, with a warning.
|
|
158
196
|
try {
|
|
159
197
|
if (killSwitch && !blocked)
|
|
160
|
-
block([]);
|
|
198
|
+
await block([]);
|
|
161
199
|
}
|
|
162
200
|
catch (blockError) {
|
|
163
201
|
warn('VPN: could not close the network after the failure: ' + errMsg(blockError));
|
|
164
202
|
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
203
|
+
// "No internet" is said only when a direct connection was tried and failed.
|
|
204
|
+
const after = !killSwitch
|
|
205
|
+
? ' The kill switch is off, so this sandbox is using your normal connection.'
|
|
206
|
+
: held
|
|
168
207
|
? ' The kill switch is on, so this sandbox has NO internet access.'
|
|
169
|
-
: ' The kill switch
|
|
170
|
-
warn(
|
|
171
|
-
setVpnStatus('failed', reason);
|
|
208
|
+
: ' The kill switch could NOT close the network, so this sandbox is ONLINE through your normal connection, without the VPN.';
|
|
209
|
+
warn('VPN failed: ' + reason + after);
|
|
210
|
+
setVpnStatus('failed', reason, killSwitch ? { killSwitchHeld: held } : {});
|
|
172
211
|
return false;
|
|
173
212
|
}
|
|
174
213
|
}
|
|
@@ -14,6 +14,7 @@ import { handshakeDone, serviceRunning } from '../../shared/vpn/wireguard.js';
|
|
|
14
14
|
import { dryRun, isWin, toolsDir } from '../runtime/paths.js';
|
|
15
15
|
import { exists, mkdirp } from '../runtime/fs.js';
|
|
16
16
|
import { log } from '../runtime/log.js';
|
|
17
|
+
import { repairServiceQueries } from './openvpn.js';
|
|
17
18
|
/** The tunnel's name: the config file is sandbox.conf and the Windows service WireGuardTunnel$sandbox. */
|
|
18
19
|
export const WIREGUARD_TUNNEL = 'sandbox';
|
|
19
20
|
export function wireguardDir(env = process.env) {
|
|
@@ -41,6 +42,7 @@ export async function ensureWireguard() {
|
|
|
41
42
|
log('VPN: downloading WireGuard ' + WIREGUARD_MSI.version + ' (about 3 MB)');
|
|
42
43
|
await downloader.file(dl.url, msi, dl.sha256);
|
|
43
44
|
}
|
|
45
|
+
repairServiceQueries();
|
|
44
46
|
log('VPN: installing WireGuard');
|
|
45
47
|
const logFile = path.join(toolsDir, 'downloads', 'wireguard-install.log');
|
|
46
48
|
// DO_NOT_LAUNCH: the installer would otherwise start the manager window.
|
|
@@ -18,4 +18,5 @@ export declare function findUnwatchedSession(dataPath: string, harness: string,
|
|
|
18
18
|
/** Bridges this terminal to a session. Returns the agent's exit code, or 0 when the user detaches. */
|
|
19
19
|
export declare function attachTerminal(session: SessionStatus, o?: {
|
|
20
20
|
clipboardWrites?: boolean;
|
|
21
|
+
confirmCtrlC?: boolean;
|
|
21
22
|
}): Promise<number>;
|
|
@@ -74,6 +74,7 @@ export async function attachTerminal(session, o = {}) {
|
|
|
74
74
|
const result = await runTerminalClient(socket, session.attach.token, {
|
|
75
75
|
detachKey: DETACH_KEY,
|
|
76
76
|
clipboardWrites: o.clipboardWrites,
|
|
77
|
+
confirmCtrlC: o.confirmCtrlC,
|
|
77
78
|
});
|
|
78
79
|
switch (result.reason) {
|
|
79
80
|
case 'detach':
|
|
@@ -47,7 +47,7 @@ export declare class SandboxChannel {
|
|
|
47
47
|
/** Restarts running sessions in place (they resume via the harness's resumeArguments, e.g. claude --continue). */
|
|
48
48
|
restart(harness: string | null, cwd?: string): Promise<GuestResponse>;
|
|
49
49
|
/** The PC is low on memory or CPU: the sandbox slows its builds down for `seconds`. Never waits for an answer. */
|
|
50
|
-
pressure(reason: string, seconds: number): void;
|
|
50
|
+
pressure(reason: string, seconds: number, level?: 'slow' | 'severe'): void;
|
|
51
51
|
ping(): Promise<GuestResponse>;
|
|
52
52
|
runningSessions(): SessionStatus[];
|
|
53
53
|
/** Running sessions of the sandbox that is running now (its launch id), newest last, capped. */
|
|
@@ -180,8 +180,8 @@ export class SandboxChannel {
|
|
|
180
180
|
return this.waitForResponse(requestId, 30000);
|
|
181
181
|
}
|
|
182
182
|
/** The PC is low on memory or CPU: the sandbox slows its builds down for `seconds`. Never waits for an answer. */
|
|
183
|
-
pressure(reason, seconds) {
|
|
184
|
-
this.send({ kind: 'pressure', id: newId(), reason, seconds, createdAt: now() });
|
|
183
|
+
pressure(reason, seconds, level = 'slow') {
|
|
184
|
+
this.send({ kind: 'pressure', id: newId(), reason, seconds, level, createdAt: now() });
|
|
185
185
|
}
|
|
186
186
|
async ping() {
|
|
187
187
|
const requestId = newId();
|
|
@@ -19,6 +19,7 @@ export interface HcsProbes {
|
|
|
19
19
|
imageStores(): readonly ImageStore[];
|
|
20
20
|
}
|
|
21
21
|
export declare function checkHcs(p: HcsProbes): EngineCheck;
|
|
22
|
+
export declare function system32Dir(): string;
|
|
22
23
|
/** The groups of a token from `whoami /groups /fo csv /nh`: name, type, SID, attributes per line. */
|
|
23
24
|
export declare function parseWhoamiGroups(csv: string): TokenGroups;
|
|
24
25
|
/** The real probes: sc.exe query, whoami, file checks. They only read. */
|
|
@@ -45,7 +45,7 @@ export function checkHcs(p) {
|
|
|
45
45
|
need('No Windows Server Core (ltsc2022) image is on this PC. ' + image.reason, 'Pull one with Docker Desktop (Windows containers mode): docker pull mcr.microsoft.com/windows/servercore:ltsc2022 - or put your own in a folder with an image.json and set AGENT_SANDBOX_HCS_IMAGE to it. This tool does not download images in this alpha.');
|
|
46
46
|
return { available: reasons.length === 0, reasons, fixes };
|
|
47
47
|
}
|
|
48
|
-
function system32Dir() {
|
|
48
|
+
export function system32Dir() {
|
|
49
49
|
return path.dirname(system32('x'));
|
|
50
50
|
}
|
|
51
51
|
/** The groups of a token from `whoami /groups /fo csv /nh`: name, type, SID, attributes per line. */
|
|
@@ -32,7 +32,7 @@ export declare class HcsEngine implements SandboxEngine {
|
|
|
32
32
|
private helper;
|
|
33
33
|
start(plan: EnginePlan): Promise<RunningSandbox>;
|
|
34
34
|
private tryTerminate;
|
|
35
|
-
/** Ours: the compute systems the helper lists with our owner name. */
|
|
35
|
+
/** Ours: the compute systems the helper lists with our owner name and this Windows user's id prefix. */
|
|
36
36
|
private ours;
|
|
37
37
|
running(): Promise<boolean>;
|
|
38
38
|
stop(): Promise<void>;
|
|
@@ -9,7 +9,7 @@ import { fail, UserError } from '../../../core/util/index.js';
|
|
|
9
9
|
import { selfTestRunning } from '../../test-guard.js';
|
|
10
10
|
import { checkHcs, realHcsProbes } from './check.js';
|
|
11
11
|
import { buildDocuments, HCS_OWNER } from './documents.js';
|
|
12
|
-
import { HcsHelper, hcsHostExe } from './helper.js';
|
|
12
|
+
import { HcsHelper, hcsHostExe, isMySystemId, userSystemPrefix } from './helper.js';
|
|
13
13
|
import { findImage } from './images.js';
|
|
14
14
|
export const HCS_CAPABILITIES = {
|
|
15
15
|
desktop: false,
|
|
@@ -76,7 +76,7 @@ export class HcsEngine {
|
|
|
76
76
|
const docs = buildDocuments({
|
|
77
77
|
launch,
|
|
78
78
|
image: this.image,
|
|
79
|
-
id:
|
|
79
|
+
id: userSystemPrefix() + slug,
|
|
80
80
|
scratchVhdx,
|
|
81
81
|
hostCpus: (this.deps.hostCpus ?? (() => os.cpus().length))(),
|
|
82
82
|
});
|
|
@@ -147,10 +147,12 @@ export class HcsEngine {
|
|
|
147
147
|
throw e; // a system that is already gone is fine
|
|
148
148
|
}
|
|
149
149
|
}
|
|
150
|
-
/** Ours: the compute systems the helper lists with our owner name. */
|
|
150
|
+
/** Ours: the compute systems the helper lists with our owner name and this Windows user's id prefix. */
|
|
151
151
|
ours(helper) {
|
|
152
152
|
const parsed = SystemListSchema.safeParse(JSON.parse(helper.enumerate().json || '[]'));
|
|
153
|
-
return parsed.success
|
|
153
|
+
return parsed.success
|
|
154
|
+
? parsed.data.filter((s) => s.Owner === HCS_OWNER && isMySystemId(s.Id)).map((s) => s.Id)
|
|
155
|
+
: [];
|
|
154
156
|
}
|
|
155
157
|
running() {
|
|
156
158
|
try {
|
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
/** Whether an id has the shape of the ones this tool gives its compute systems. */
|
|
3
|
+
export declare function isOurSystemId(id: string): boolean;
|
|
4
|
+
/**
|
|
5
|
+
* The start of the ids this Windows user's sandboxes get: the Owner is the same for everyone, so on a PC shared with
|
|
6
|
+
* other users this tag is what keeps one user from listing or stopping another's compute systems.
|
|
7
|
+
*/
|
|
8
|
+
export declare function userSystemPrefix(user?: string, domain?: string): string;
|
|
9
|
+
/** One of this tool's ids, given by this Windows user. */
|
|
10
|
+
export declare function isMySystemId(id: string): boolean;
|
|
2
11
|
export declare const HcsReplySchema: z.ZodObject<{
|
|
3
12
|
ok: z.ZodBoolean;
|
|
4
13
|
hresult: z.ZodString;
|
|
@@ -16,6 +25,8 @@ export type HelperRunner = (exe: string, args: readonly string[], timeoutMs: num
|
|
|
16
25
|
export declare const spawnRunner: HelperRunner;
|
|
17
26
|
/** Compiles the helper's sources to `exe` with the .NET Framework csc (C# 5). False when it cannot be built. */
|
|
18
27
|
export declare function buildHcsHostTo(exe: string): boolean;
|
|
28
|
+
/** HcsHost.exe when it was built already (never builds it): a PC that never used HCS has none. */
|
|
29
|
+
export declare function builtHcsHostExe(storageRoot: string): string | null;
|
|
19
30
|
/**
|
|
20
31
|
* <storage>\bin\hcs\<hash of its sources>\HcsHost.exe, built the first time. Its own folder: HostTools' cleanup of old
|
|
21
32
|
* builds only touches twelve-digit hash folders directly in bin. Null when it cannot be built.
|
|
@@ -15,6 +15,26 @@ const MAX_RESPONSE_BYTES = 2 * 1024 * 1024;
|
|
|
15
15
|
const CALL_TIMEOUT_MS = 240000;
|
|
16
16
|
/** Only ids this tool gives its compute systems (HcsHost.exe refuses any other, and checks the Owner too). */
|
|
17
17
|
const SAFE_ID = /^agent-sandbox-[A-Za-z0-9._-]{1,100}$/;
|
|
18
|
+
/** Whether an id has the shape of the ones this tool gives its compute systems. */
|
|
19
|
+
export function isOurSystemId(id) {
|
|
20
|
+
return SAFE_ID.test(id);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* The start of the ids this Windows user's sandboxes get: the Owner is the same for everyone, so on a PC shared with
|
|
24
|
+
* other users this tag is what keeps one user from listing or stopping another's compute systems.
|
|
25
|
+
*/
|
|
26
|
+
export function userSystemPrefix(user = os.userInfo().username, domain = process.env.USERDOMAIN ?? '') {
|
|
27
|
+
const tag = crypto
|
|
28
|
+
.createHash('sha256')
|
|
29
|
+
.update((domain + '\\' + user).toLowerCase())
|
|
30
|
+
.digest('hex')
|
|
31
|
+
.slice(0, 8);
|
|
32
|
+
return 'agent-sandbox-' + tag + '-';
|
|
33
|
+
}
|
|
34
|
+
/** One of this tool's ids, given by this Windows user. */
|
|
35
|
+
export function isMySystemId(id) {
|
|
36
|
+
return isOurSystemId(id) && id.startsWith(userSystemPrefix());
|
|
37
|
+
}
|
|
18
38
|
export const HcsReplySchema = z.object({
|
|
19
39
|
ok: z.boolean(),
|
|
20
40
|
hresult: z.string().max(16),
|
|
@@ -51,12 +71,22 @@ export function buildHcsHostTo(exe) {
|
|
|
51
71
|
const r = spawnSync(SYSTEM.csc(), ['/nologo', '/optimize', '/target:exe', '/r:System.Web.Extensions.dll', '/out:' + exe, ...sources], { windowsHide: true, timeout: 120000 });
|
|
52
72
|
return r.status === 0 && fs.existsSync(exe);
|
|
53
73
|
}
|
|
74
|
+
/** HcsHost.exe when it was built already (never builds it): a PC that never used HCS has none. */
|
|
75
|
+
export function builtHcsHostExe(storageRoot) {
|
|
76
|
+
const exe = hcsHostPath(storageRoot);
|
|
77
|
+
return exe && fs.existsSync(exe) ? exe : null;
|
|
78
|
+
}
|
|
54
79
|
/**
|
|
55
80
|
* <storage>\bin\hcs\<hash of its sources>\HcsHost.exe, built the first time. Its own folder: HostTools' cleanup of old
|
|
56
81
|
* builds only touches twelve-digit hash folders directly in bin. Null when it cannot be built.
|
|
57
82
|
*/
|
|
58
83
|
export function hcsHostExe(storageRoot) {
|
|
59
84
|
refuseInSelfTest('build HcsHost.exe');
|
|
85
|
+
const exe = hcsHostPath(storageRoot);
|
|
86
|
+
return exe && (fs.existsSync(exe) || buildHcsHostTo(exe)) ? exe : null;
|
|
87
|
+
}
|
|
88
|
+
/** Where HcsHost.exe is (or would be built): named by the hash of its sources. Null without the sources. */
|
|
89
|
+
function hcsHostPath(storageRoot) {
|
|
60
90
|
const dir = nativeFile('csharp', 'HcsHost');
|
|
61
91
|
const hash = crypto.createHash('sha256');
|
|
62
92
|
try {
|
|
@@ -69,8 +99,7 @@ export function hcsHostExe(storageRoot) {
|
|
|
69
99
|
catch {
|
|
70
100
|
return null; // no sources
|
|
71
101
|
}
|
|
72
|
-
|
|
73
|
-
return fs.existsSync(exe) || buildHcsHostTo(exe) ? exe : null;
|
|
102
|
+
return path.join(storageRoot, 'bin', 'hcs', hash.digest('hex').slice(0, 12), 'HcsHost.exe');
|
|
74
103
|
}
|
|
75
104
|
/** A path argument: absolute, not an option. Nothing else reaches the helper's command line. */
|
|
76
105
|
function pathArg(value, what) {
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { type HcsProbes } from './check.js';
|
|
2
|
+
import { HcsHelper } from './helper.js';
|
|
3
|
+
export interface LeftoverSystem {
|
|
4
|
+
readonly id: string;
|
|
5
|
+
/** Container or VirtualMachine (the utility VM). */
|
|
6
|
+
readonly kind: string;
|
|
7
|
+
readonly state: string;
|
|
8
|
+
/** When it was created (ISO text), when HCS says. */
|
|
9
|
+
readonly since: string | null;
|
|
10
|
+
}
|
|
11
|
+
export type LeftoverLook = {
|
|
12
|
+
readonly available: true;
|
|
13
|
+
readonly systems: readonly LeftoverSystem[];
|
|
14
|
+
} | {
|
|
15
|
+
readonly available: false;
|
|
16
|
+
readonly reason: string;
|
|
17
|
+
};
|
|
18
|
+
export interface LeftoverOutcome {
|
|
19
|
+
readonly id: string;
|
|
20
|
+
readonly stopped: boolean;
|
|
21
|
+
/** Why not, in plain words. */
|
|
22
|
+
readonly problem?: string;
|
|
23
|
+
}
|
|
24
|
+
/** What the look runs on: replaceable by tests (probes of a pretend PC, a fake helper). */
|
|
25
|
+
export interface LeftoverDeps {
|
|
26
|
+
readonly probes: HcsProbes;
|
|
27
|
+
/** The helper, or null when HcsHost.exe was never built (then nothing of ours can be running). Never builds it. */
|
|
28
|
+
helper(storageRoot: string): HcsHelper | null;
|
|
29
|
+
}
|
|
30
|
+
export declare const LEFTOVER_DEPS: {
|
|
31
|
+
current: LeftoverDeps | null;
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* The compute systems of ours that exist now. Never builds HcsHost.exe: a PC that never used HCS has none, and
|
|
35
|
+
* nothing of ours can be running there.
|
|
36
|
+
*/
|
|
37
|
+
export declare function lookForLeftovers(storageRoot: string): LeftoverLook;
|
|
38
|
+
/**
|
|
39
|
+
* Stops the named systems, containers before the utility VM that hosts them. Each id is looked up again first: one that
|
|
40
|
+
* is not one of ours (or is gone) never reaches the helper; the helper checks the Owner with HCS itself once more.
|
|
41
|
+
*/
|
|
42
|
+
export declare function stopLeftovers(storageRoot: string, ids: readonly string[]): LeftoverOutcome[];
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
// HCS compute systems this tool left running (a crashed command: they are created to outlive their handle). Only
|
|
2
|
+
// systems with our id prefix AND our Owner are ever listed or stopped; HcsHost.exe checks both again before it
|
|
3
|
+
// terminates one. Nothing here runs unless the PC has the Host Compute Service.
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
import { errMsg, fail } from '../../../core/util/index.js';
|
|
7
|
+
import { realHcsProbes, system32Dir } from './check.js';
|
|
8
|
+
import { HCS_OWNER } from './documents.js';
|
|
9
|
+
import { HcsHelper, builtHcsHostExe, isMySystemId } from './helper.js';
|
|
10
|
+
export const LEFTOVER_DEPS = { current: null };
|
|
11
|
+
function realDeps() {
|
|
12
|
+
return {
|
|
13
|
+
probes: realHcsProbes(),
|
|
14
|
+
helper: (root) => {
|
|
15
|
+
const exe = builtHcsHostExe(root);
|
|
16
|
+
return exe ? new HcsHelper(() => exe) : null;
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
const SystemListSchema = z.array(z
|
|
21
|
+
.object({
|
|
22
|
+
Id: z.string().max(300),
|
|
23
|
+
SystemType: z.string().max(100).optional(),
|
|
24
|
+
Owner: z.string().max(300).optional(),
|
|
25
|
+
State: z.string().max(100).optional(),
|
|
26
|
+
CreateTimestamp: z.string().max(100).optional(),
|
|
27
|
+
})
|
|
28
|
+
.passthrough());
|
|
29
|
+
/** The reason HCS cannot be there at all, or null. Looks only (service list, one file). */
|
|
30
|
+
function noHcsReason(p) {
|
|
31
|
+
if (!p.isWindows())
|
|
32
|
+
return 'HCS only exists on Windows.';
|
|
33
|
+
if (!p.fileExists(path.join(system32Dir(), 'computecore.dll')) || p.serviceInstalled('vmcompute') === false)
|
|
34
|
+
return 'The Host Compute Service is not on this PC.';
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
/** Ours only: the id has our prefix and the system names us as its Owner. Anything else is never shown or touched. */
|
|
38
|
+
function ownedSystems(helper) {
|
|
39
|
+
let list;
|
|
40
|
+
try {
|
|
41
|
+
list = JSON.parse(helper.enumerate().json || '[]');
|
|
42
|
+
}
|
|
43
|
+
catch (e) {
|
|
44
|
+
if (e instanceof SyntaxError)
|
|
45
|
+
return fail('The Host Compute Service answered with something that is not JSON.');
|
|
46
|
+
throw e;
|
|
47
|
+
}
|
|
48
|
+
const parsed = SystemListSchema.safeParse(list);
|
|
49
|
+
if (!parsed.success)
|
|
50
|
+
return [];
|
|
51
|
+
return parsed.data
|
|
52
|
+
.filter((s) => s.Owner === HCS_OWNER && isMySystemId(s.Id))
|
|
53
|
+
.map((s) => ({
|
|
54
|
+
id: s.Id,
|
|
55
|
+
kind: s.SystemType ?? 'unknown',
|
|
56
|
+
state: s.State ?? 'unknown',
|
|
57
|
+
since: s.CreateTimestamp && !Number.isNaN(Date.parse(s.CreateTimestamp)) ? s.CreateTimestamp : null,
|
|
58
|
+
}));
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* The compute systems of ours that exist now. Never builds HcsHost.exe: a PC that never used HCS has none, and
|
|
62
|
+
* nothing of ours can be running there.
|
|
63
|
+
*/
|
|
64
|
+
export function lookForLeftovers(storageRoot) {
|
|
65
|
+
const deps = LEFTOVER_DEPS.current ?? realDeps();
|
|
66
|
+
const none = noHcsReason(deps.probes);
|
|
67
|
+
if (none)
|
|
68
|
+
return { available: false, reason: none };
|
|
69
|
+
try {
|
|
70
|
+
const helper = deps.helper(storageRoot);
|
|
71
|
+
if (!helper)
|
|
72
|
+
return { available: true, systems: [] };
|
|
73
|
+
return { available: true, systems: ownedSystems(helper) };
|
|
74
|
+
}
|
|
75
|
+
catch (e) {
|
|
76
|
+
return { available: false, reason: 'The Host Compute Service could not be asked: ' + errMsg(e) };
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Stops the named systems, containers before the utility VM that hosts them. Each id is looked up again first: one that
|
|
81
|
+
* is not one of ours (or is gone) never reaches the helper; the helper checks the Owner with HCS itself once more.
|
|
82
|
+
*/
|
|
83
|
+
export function stopLeftovers(storageRoot, ids) {
|
|
84
|
+
const deps = LEFTOVER_DEPS.current ?? realDeps();
|
|
85
|
+
const helper = deps.helper(storageRoot);
|
|
86
|
+
if (!helper)
|
|
87
|
+
return ids.map((id) => ({ id, stopped: false, problem: 'HcsHost.exe is not available.' }));
|
|
88
|
+
const now = ownedSystems(helper);
|
|
89
|
+
const wanted = [...ids].sort((a, b) => Number(b.endsWith('-c')) - Number(a.endsWith('-c')));
|
|
90
|
+
return wanted.map((id) => {
|
|
91
|
+
if (!now.some((s) => s.id === id))
|
|
92
|
+
return { id, stopped: false, problem: 'it is gone, or it is not one of ours' };
|
|
93
|
+
try {
|
|
94
|
+
helper.terminate(id);
|
|
95
|
+
return { id, stopped: true };
|
|
96
|
+
}
|
|
97
|
+
catch (e) {
|
|
98
|
+
return { id, stopped: false, problem: errMsg(e) };
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
}
|
|
@@ -5,3 +5,4 @@ export { WindowsSandboxEngine } from './windows-sandbox/engine.js';
|
|
|
5
5
|
export { wsbText } from './windows-sandbox/wsb-text.js';
|
|
6
6
|
export { HcsEngine, HCS_CAPABILITIES, hcsStartEnabled } from './hcs/engine.js';
|
|
7
7
|
export { checkHcs, realHcsProbes, type HcsProbes } from './hcs/check.js';
|
|
8
|
+
export { lookForLeftovers, stopLeftovers, LEFTOVER_DEPS, type LeftoverDeps, type LeftoverLook, type LeftoverOutcome, type LeftoverSystem, } from './hcs/leftovers.js';
|
|
@@ -3,3 +3,4 @@ export { WindowsSandboxEngine } from './windows-sandbox/engine.js';
|
|
|
3
3
|
export { wsbText } from './windows-sandbox/wsb-text.js';
|
|
4
4
|
export { HcsEngine, HCS_CAPABILITIES, hcsStartEnabled } from './hcs/engine.js';
|
|
5
5
|
export { checkHcs, realHcsProbes } from './hcs/check.js';
|
|
6
|
+
export { lookForLeftovers, stopLeftovers, LEFTOVER_DEPS, } from './hcs/leftovers.js';
|