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
|
@@ -1,12 +1,19 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/** A git folder's own state: config lines, hooks, redirect files. */
|
|
2
|
+
export interface GitState {
|
|
3
3
|
config: string[];
|
|
4
4
|
hooks: Record<string, string>;
|
|
5
5
|
redirects: string[];
|
|
6
|
+
}
|
|
7
|
+
export interface RepoSnapshot extends GitState {
|
|
8
|
+
gitDir: boolean;
|
|
6
9
|
commands: Record<string, string>;
|
|
10
|
+
/** Fingerprints of AGENT_FILES (missing in snapshots of older versions: then they are not checked). */
|
|
11
|
+
agent?: Record<string, string>;
|
|
12
|
+
/** Other git folders: submodules (.git/modules/...) and repositories nested in the project, by relative path. */
|
|
13
|
+
repos?: Record<string, GitState>;
|
|
7
14
|
}
|
|
8
15
|
export interface RepoFinding {
|
|
9
|
-
kind: 'hook' | 'config' | 'git' | 'command';
|
|
16
|
+
kind: 'hook' | 'config' | 'git' | 'command' | 'agent';
|
|
10
17
|
detail: string;
|
|
11
18
|
}
|
|
12
19
|
export declare const snapshotRepo: (workspace: string) => RepoSnapshot;
|
|
@@ -15,8 +22,21 @@ export declare const snapshotRepo: (workspace: string) => RepoSnapshot;
|
|
|
15
22
|
* Returns what was found (and fixed). Never touches what was already there before the sandbox ran.
|
|
16
23
|
*/
|
|
17
24
|
export declare const guardRepo: (workspace: string, before: RepoSnapshot) => RepoFinding[];
|
|
18
|
-
|
|
19
|
-
|
|
25
|
+
/**
|
|
26
|
+
* At launch: every folder the sandbox will be able to write. The launch folder, projects mapped with it and every
|
|
27
|
+
* project already inside a project root are snapshotted as they are now, before the sandbox can touch them.
|
|
28
|
+
*/
|
|
29
|
+
export declare const saveRepoSnapshot: (dataPath: string, workspace: string, more?: string[], roots?: string[]) => void;
|
|
30
|
+
/**
|
|
31
|
+
* A project that joins the running sandbox: snapshotted before its first session, then guarded like the launch
|
|
32
|
+
* folder. A project already guarded keeps its first snapshot (what was there before the sandbox could touch it).
|
|
33
|
+
*/
|
|
34
|
+
export declare const addRepoSnapshot: (dataPath: string, workspace: string, o?: {
|
|
35
|
+
empty?: boolean;
|
|
36
|
+
}) => void;
|
|
37
|
+
/** The folders being guarded (for status). */
|
|
38
|
+
export declare const guardedProjects: (dataPath: string) => string[];
|
|
39
|
+
/** Re-checks every project of the current/last launch; findings are kept in a host-only file for `status`. Returns the new ones. */
|
|
20
40
|
export declare const checkRepo: (dataPath: string) => RepoFinding[];
|
|
21
41
|
export declare const repoFindings: (dataPath: string) => (RepoFinding & {
|
|
22
42
|
at?: string;
|
package/dist/host/repo-guard.js
CHANGED
|
@@ -6,14 +6,22 @@
|
|
|
6
6
|
// - git: new or changed hooks, and config that makes git run programs (core.fsmonitor, core.sshCommand, filters,
|
|
7
7
|
// include.path, ...), plus files that redirect where git reads config and hooks (commondir, config.worktree);
|
|
8
8
|
// - commands: executables dropped in the project root under the name of something you type there (node.exe,
|
|
9
|
-
// git.cmd, npm.cmd, ...), because Windows runs a program from the current folder before searching PATH
|
|
9
|
+
// git.cmd, npm.cmd, ...), because Windows runs a program from the current folder before searching PATH;
|
|
10
|
+
// - agent and editor files: hooks, helper commands and MCP servers in .claude/ and .mcp.json, VS Code folder-open
|
|
11
|
+
// tasks and program paths, .envrc. Submodules and nested repositories get the git checks too.
|
|
10
12
|
import crypto from 'node:crypto';
|
|
11
13
|
import fs from 'node:fs';
|
|
12
14
|
import path from 'node:path';
|
|
13
15
|
import _ from 'lodash';
|
|
14
|
-
import {
|
|
16
|
+
import { z } from 'zod';
|
|
17
|
+
import { parse as parseJsonc } from 'jsonc-parser';
|
|
18
|
+
import { readValid, writeFileAtomic, writeJsonAtomic } from '../shared/rundir.js';
|
|
19
|
+
import { assertTestWritable } from './test-guard.js';
|
|
20
|
+
import { dirExists, fileExists, isLink } from '../core/util.js';
|
|
15
21
|
/** Config keys that make git run a program, or load more config. */
|
|
16
|
-
const DANGEROUS_KEY = /^(fsmonitor|hookspath|sshcommand|pager|editor|askpass|program|gpgprogram|textconv|command|cmd|clean|smudge|process|driver|helper|uploadpack|receivepack|path|attributesfile|excludesfile|worktree|worktreeconfig|gitproxy|proxycommand|sendemailvalidate|tool)\s*=/i;
|
|
22
|
+
const DANGEROUS_KEY = /^(fsmonitor|hookspath|sshcommand|pager|editor|askpass|program|gpgprogram|textconv|command|cmd|clean|smudge|process|driver|helper|uploadpack|receivepack|path|attributesfile|excludesfile|worktree|worktreeconfig|gitproxy|proxycommand|sendemailvalidate|tool|external|difffilter|defaultkeycommand|alternaterefscommand)\s*=/i;
|
|
23
|
+
/** Sections where any key runs a program ([pager] log = x), or where a "!" value does ([alias] st = !x). */
|
|
24
|
+
const dangerousInSection = (section, keyPart) => (section === 'pager' && keyPart.includes('=')) || (section === 'alias' && /^[^=]+=\s*"?\s*!/.test(keyPart));
|
|
17
25
|
const SECTION = /^\s*\[([^\]]*)\]\s*/;
|
|
18
26
|
const MARK = '# [agent-sandbox] disabled, added inside the sandbox: ';
|
|
19
27
|
/** Files in .git that make git read config/hooks from somewhere else. */
|
|
@@ -30,7 +38,65 @@ const COMMANDS = new RegExp('^(' + [
|
|
|
30
38
|
'make', 'cmake', 'ninja', 'gcc', 'g\\+\\+', 'clang', 'psql', 'mysql', 'mariadb', 'mongosh', 'redis-cli', 'docker', 'kubectl', 'terraform', 'az', 'aws',
|
|
31
39
|
'powershell', 'pwsh', 'cmd', 'where', 'tasklist', 'tar', 'curl', 'ssh', 'scp',
|
|
32
40
|
].join('|') + ')\\.(exe|com|bat|cmd|vbs|vbe|js|jse|wsf|wsh|msc|ps1)$', 'i');
|
|
33
|
-
|
|
41
|
+
/**
|
|
42
|
+
* Project files that tools on this PC act on by themselves: Claude Code runs a project's hooks, helpers and MCP servers,
|
|
43
|
+
* VS Code runs folder-open tasks and the programs workspace settings point at, direnv runs .envrc. For each, only the
|
|
44
|
+
* part that can run something is compared (`risky`), so the agent may still edit the rest (permissions, formatting).
|
|
45
|
+
*/
|
|
46
|
+
const AGENT_FILES = {
|
|
47
|
+
'.claude/settings.json': (d) => claudeRisk(d),
|
|
48
|
+
'.claude/settings.local.json': (d) => claudeRisk(d),
|
|
49
|
+
'.mcp.json': (_d, text) => text,
|
|
50
|
+
'.vscode/tasks.json': (d) => JSON.stringify(d).match(/"runOn"\s*:\s*"folderOpen"/) ? d : null,
|
|
51
|
+
'.vscode/settings.json': (d) => _.pickBy(_.isPlainObject(d) ? d : {}, (_v, k) => /path|executable|command|interpreter|shell|profile|args|env|exec|program|server|launch|runtime|binary/i.test(k)),
|
|
52
|
+
'.envrc': (_d, text) => text,
|
|
53
|
+
};
|
|
54
|
+
const claudeRisk = (d) => {
|
|
55
|
+
const s = (_.isPlainObject(d) ? d : {});
|
|
56
|
+
const permissions = (_.isPlainObject(s.permissions) ? s.permissions : {});
|
|
57
|
+
return { ..._.pick(s, ['hooks', 'apiKeyHelper', 'awsAuthRefresh', 'awsCredentialExport', 'otelHeadersHelper', 'statusLine', 'env', 'enableAllProjectMcpServers', 'enabledMcpjsonServers', 'disableAllHooks']), permissions: _.pick(permissions, ['defaultMode', 'additionalDirectories']) };
|
|
58
|
+
};
|
|
59
|
+
const MAX_AGENT_FILE_BYTES = 1024 * 1024;
|
|
60
|
+
/** What can run in one agent file, as a short fingerprint (null = the file is not there). */
|
|
61
|
+
const agentRisk = (workspace, rel) => {
|
|
62
|
+
const file = path.join(workspace, ...rel.split('/'));
|
|
63
|
+
if (!fs.existsSync(file))
|
|
64
|
+
return null;
|
|
65
|
+
if (isLink(file) || isLink(path.dirname(file)) || !fileExists(file))
|
|
66
|
+
return 'link-or-not-a-file';
|
|
67
|
+
if (fs.statSync(file).size > MAX_AGENT_FILE_BYTES)
|
|
68
|
+
return 'too-large-' + hash(file);
|
|
69
|
+
const text = fs.readFileSync(file, 'utf8');
|
|
70
|
+
const errors = [];
|
|
71
|
+
const data = parseJsonc(text, errors, { allowTrailingComma: true });
|
|
72
|
+
const risk = AGENT_FILES[rel](rel === '.envrc' ? null : data, text);
|
|
73
|
+
return crypto.createHash('sha256').update(JSON.stringify(risk ?? null) + (errors.length && rel !== '.envrc' && rel !== '.mcp.json' ? hash(file) : '')).digest('hex');
|
|
74
|
+
};
|
|
75
|
+
const agentSnapshot = (workspace) => Object.fromEntries(Object.keys(AGENT_FILES).flatMap((rel) => { const r = agentRisk(workspace, rel); return r ? [[rel, r]] : []; }));
|
|
76
|
+
/** SHA-256 in small chunks: a sandbox can plant a multi-gigabyte "node.exe" to exhaust this PC's memory. */
|
|
77
|
+
const hash = (file) => {
|
|
78
|
+
if (isLink(file))
|
|
79
|
+
return 'link';
|
|
80
|
+
const sum = crypto.createHash('sha256');
|
|
81
|
+
const buffer = Buffer.alloc(1 << 20);
|
|
82
|
+
let fd;
|
|
83
|
+
// A file that cannot be read (locked by a program in the sandbox) never matches the snapshot, so it is dealt with.
|
|
84
|
+
try {
|
|
85
|
+
fd = fs.openSync(file, 'r');
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return 'unreadable-' + crypto.randomBytes(4).toString('hex');
|
|
89
|
+
}
|
|
90
|
+
try {
|
|
91
|
+
for (let n = fs.readSync(fd, buffer, 0, buffer.length, null); n > 0; n = fs.readSync(fd, buffer, 0, buffer.length, null))
|
|
92
|
+
sum.update(buffer.subarray(0, n));
|
|
93
|
+
}
|
|
94
|
+
finally {
|
|
95
|
+
fs.closeSync(fd);
|
|
96
|
+
}
|
|
97
|
+
return sum.digest('hex');
|
|
98
|
+
};
|
|
99
|
+
const MAX_CONFIG_BYTES = 4 * 1024 * 1024;
|
|
34
100
|
const plainGitDir = (workspace) => {
|
|
35
101
|
const dir = path.join(workspace, '.git');
|
|
36
102
|
return dirExists(dir) && !isLink(dir) ? dir : null;
|
|
@@ -40,16 +106,68 @@ const filesIn = (dir, accept) => dirExists(dir) && !isLink(dir)
|
|
|
40
106
|
: {};
|
|
41
107
|
const configLines = (dir) => {
|
|
42
108
|
const file = path.join(dir, 'config');
|
|
43
|
-
return fileExists(file) && !isLink(file) ? fs.readFileSync(file, 'utf8').split(/\r?\n/) : [];
|
|
109
|
+
return fileExists(file) && !isLink(file) && fs.statSync(file).size <= MAX_CONFIG_BYTES ? fs.readFileSync(file, 'utf8').split(/\r?\n/) : [];
|
|
44
110
|
};
|
|
111
|
+
const gitState = (dir) => ({
|
|
112
|
+
config: dir ? configLines(dir) : [],
|
|
113
|
+
hooks: dir ? filesIn(path.join(dir, 'hooks'), (n) => !n.endsWith('.sample')) : {},
|
|
114
|
+
redirects: dir ? REDIRECTS.filter((n) => fs.existsSync(path.join(dir, n))) : [],
|
|
115
|
+
});
|
|
116
|
+
const EMPTY_GIT = { config: [], hooks: {}, redirects: [] };
|
|
117
|
+
const MAX_OTHER_REPOS = 64;
|
|
118
|
+
const SKIP_DIRS = new Set(['node_modules', '.git', 'agent-sandbox-quarantine', 'target', 'dist', 'build', 'bin', 'obj', '.venv', 'venv']);
|
|
119
|
+
const plainDirs = (dir, limit) => {
|
|
120
|
+
try {
|
|
121
|
+
return fs.readdirSync(dir, { withFileTypes: true }).filter((e) => e.isDirectory() && !e.isSymbolicLink()).slice(0, limit).map((e) => path.join(dir, e.name));
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
return [];
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
/**
|
|
128
|
+
* Git folders other than the project's own: submodules (.git/modules/**) and repositories nested up to two levels deep.
|
|
129
|
+
* git runs their hooks and config too (git status in a subfolder, VS Code's repository scan). Bounded: links are never
|
|
130
|
+
* followed and at most MAX_OTHER_REPOS are looked at.
|
|
131
|
+
*/
|
|
132
|
+
const otherGitDirs = (workspace) => {
|
|
133
|
+
const found = [];
|
|
134
|
+
const main = plainGitDir(workspace);
|
|
135
|
+
// Submodules: a folder with a config and a HEAD is a git folder, and may have modules of its own.
|
|
136
|
+
const walk = (dir, depth) => {
|
|
137
|
+
for (const d of plainDirs(dir, 200)) {
|
|
138
|
+
if (found.length >= MAX_OTHER_REPOS)
|
|
139
|
+
return;
|
|
140
|
+
if (fileExists(path.join(d, 'config')) && fileExists(path.join(d, 'HEAD'))) {
|
|
141
|
+
found.push(d);
|
|
142
|
+
if (depth < 3)
|
|
143
|
+
walk(path.join(d, 'modules'), depth + 1);
|
|
144
|
+
}
|
|
145
|
+
else if (depth < 3)
|
|
146
|
+
walk(d, depth + 1);
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
if (main)
|
|
150
|
+
walk(path.join(main, 'modules'), 0);
|
|
151
|
+
for (const sub of plainDirs(workspace, 500).filter((d) => !SKIP_DIRS.has(path.basename(d).toLowerCase()))) {
|
|
152
|
+
for (const candidate of [sub, ...plainDirs(sub, 200).filter((d) => !SKIP_DIRS.has(path.basename(d).toLowerCase()))]) {
|
|
153
|
+
if (found.length >= MAX_OTHER_REPOS)
|
|
154
|
+
return found;
|
|
155
|
+
const git = plainGitDir(candidate);
|
|
156
|
+
if (git)
|
|
157
|
+
found.push(git);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return found;
|
|
161
|
+
};
|
|
162
|
+
const relative = (workspace, dir) => path.relative(workspace, dir).split(path.sep).join('/');
|
|
45
163
|
export const snapshotRepo = (workspace) => {
|
|
46
164
|
const dir = plainGitDir(workspace);
|
|
47
165
|
return {
|
|
48
166
|
gitDir: dir !== null,
|
|
49
|
-
|
|
50
|
-
hooks: dir ? filesIn(path.join(dir, 'hooks'), (n) => !n.endsWith('.sample')) : {},
|
|
51
|
-
redirects: dir ? REDIRECTS.filter((n) => fs.existsSync(path.join(dir, n))) : [],
|
|
167
|
+
...gitState(dir),
|
|
52
168
|
commands: filesIn(workspace, (n) => COMMANDS.test(n)),
|
|
169
|
+
agent: agentSnapshot(workspace),
|
|
170
|
+
repos: Object.fromEntries(otherGitDirs(workspace).map((d) => [relative(workspace, d), gitState(d)])),
|
|
53
171
|
};
|
|
54
172
|
};
|
|
55
173
|
/** Moves files into <dir>\agent-sandbox-quarantine\<time>\ and reports them. */
|
|
@@ -59,7 +177,12 @@ const quarantine = (from, names, into, kind) => {
|
|
|
59
177
|
const target = path.join(into, 'agent-sandbox-quarantine', String(Date.now()));
|
|
60
178
|
fs.mkdirSync(target, { recursive: true });
|
|
61
179
|
return names.map((name) => {
|
|
62
|
-
|
|
180
|
+
try {
|
|
181
|
+
fs.renameSync(path.join(from, name), path.join(target, name));
|
|
182
|
+
}
|
|
183
|
+
catch (e) {
|
|
184
|
+
return { kind, detail: name + ' (could not be moved: ' + e.code + '; delete it yourself before running anything here)' };
|
|
185
|
+
}
|
|
63
186
|
return { kind, detail: name + ' (moved to ' + target + ')' };
|
|
64
187
|
});
|
|
65
188
|
};
|
|
@@ -70,22 +193,30 @@ const disableConfig = (dir, before) => {
|
|
|
70
193
|
const seen = {};
|
|
71
194
|
const findings = [];
|
|
72
195
|
let inInclude = false;
|
|
196
|
+
let section = '';
|
|
73
197
|
const fixed = lines.map((line) => {
|
|
74
198
|
const header = SECTION.exec(line);
|
|
75
|
-
if (header)
|
|
199
|
+
if (header) {
|
|
200
|
+
section = header[1].trim().toLowerCase().split(/\s/)[0];
|
|
76
201
|
inInclude = /^include(if\s+"[^"]*")?$/i.test(header[1].trim());
|
|
202
|
+
}
|
|
77
203
|
seen[line] = (seen[line] ?? 0) + 1;
|
|
78
204
|
if (line.startsWith(MARK) || seen[line] <= (known[line] ?? 0))
|
|
79
205
|
return line;
|
|
80
206
|
// git also accepts "[core] fsmonitor = x" on one line, so look past a leading section header.
|
|
81
207
|
const keyPart = line.replace(SECTION, '').trim();
|
|
82
|
-
if (!(DANGEROUS_KEY.test(keyPart) || (inInclude && keyPart.includes('='))))
|
|
208
|
+
if (!(DANGEROUS_KEY.test(keyPart) || (inInclude && keyPart.includes('=')) || dangerousInSection(section, keyPart)))
|
|
83
209
|
return line;
|
|
84
210
|
findings.push({ kind: 'config', detail: line.trim() });
|
|
85
211
|
return MARK + line.trim();
|
|
86
212
|
});
|
|
87
213
|
if (findings.length)
|
|
88
|
-
|
|
214
|
+
writeFileAtomic(path.join(dir, 'config'), fixed.join('\n'));
|
|
215
|
+
const file = path.join(dir, 'config');
|
|
216
|
+
// A link or a huge file is not read at all, so say so instead of letting it pass as "nothing found".
|
|
217
|
+
const unchecked = isLink(file) ? '.git/config is a link' : lines.length === 0 && fileExists(file) && fs.statSync(file).size > MAX_CONFIG_BYTES ? '.git/config is over 4 MB' : null;
|
|
218
|
+
if (unchecked)
|
|
219
|
+
findings.push({ kind: 'git', detail: unchecked + ', so it cannot be checked. Do not run git in this folder until you have looked at it.' });
|
|
89
220
|
return findings;
|
|
90
221
|
};
|
|
91
222
|
/**
|
|
@@ -93,9 +224,19 @@ const disableConfig = (dir, before) => {
|
|
|
93
224
|
* Returns what was found (and fixed). Never touches what was already there before the sandbox ran.
|
|
94
225
|
*/
|
|
95
226
|
export const guardRepo = (workspace, before) => {
|
|
227
|
+
assertTestWritable(workspace);
|
|
96
228
|
const findings = [];
|
|
97
229
|
const commandsNow = filesIn(workspace, (n) => COMMANDS.test(n));
|
|
98
230
|
findings.push(...quarantine(workspace, Object.keys(commandsNow).filter((n) => before.commands[n] !== commandsNow[n]), workspace, 'command'));
|
|
231
|
+
// Snapshots from before these checks existed have no baseline: checking against nothing would move your own files.
|
|
232
|
+
if (before.agent)
|
|
233
|
+
findings.push(...guardAgentFiles(workspace, before.agent));
|
|
234
|
+
if (before.repos) {
|
|
235
|
+
for (const other of otherGitDirs(workspace)) {
|
|
236
|
+
const rel = relative(workspace, other);
|
|
237
|
+
findings.push(...guardGitDir(other, before.repos[rel] ?? EMPTY_GIT).map((f) => ({ ...f, detail: rel + ': ' + f.detail })));
|
|
238
|
+
}
|
|
239
|
+
}
|
|
99
240
|
const dir = plainGitDir(workspace);
|
|
100
241
|
if (!dir) {
|
|
101
242
|
const replaced = before.gitDir && fs.existsSync(path.join(workspace, '.git'));
|
|
@@ -103,26 +244,104 @@ export const guardRepo = (workspace, before) => {
|
|
|
103
244
|
findings.push({ kind: 'git', detail: '.git was replaced by a link or a "gitdir:" file inside the sandbox. Do not run git in ' + workspace + ' until you have checked it.' });
|
|
104
245
|
return findings;
|
|
105
246
|
}
|
|
247
|
+
findings.push(...guardGitDir(dir, before));
|
|
248
|
+
return findings;
|
|
249
|
+
};
|
|
250
|
+
const guardGitDir = (dir, before) => {
|
|
106
251
|
const hooksDir = path.join(dir, 'hooks');
|
|
107
252
|
const hooksNow = filesIn(hooksDir, (n) => !n.endsWith('.sample'));
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
253
|
+
return [
|
|
254
|
+
...quarantine(hooksDir, Object.keys(hooksNow).filter((n) => before.hooks[n] !== hooksNow[n]), dir, 'hook'),
|
|
255
|
+
...quarantine(dir, REDIRECTS.filter((n) => fs.existsSync(path.join(dir, n)) && !before.redirects.includes(n)), dir, 'git'),
|
|
256
|
+
...disableConfig(dir, before.config),
|
|
257
|
+
];
|
|
258
|
+
};
|
|
259
|
+
/** Agent files whose runnable part changed are moved to the quarantine folder (the rest of your edits are in there too). */
|
|
260
|
+
const guardAgentFiles = (workspace, before) => {
|
|
261
|
+
const changed = Object.keys(AGENT_FILES).filter((rel) => { const now = agentRisk(workspace, rel); return now !== null && now !== before[rel]; });
|
|
262
|
+
if (!changed.length)
|
|
263
|
+
return [];
|
|
264
|
+
const target = path.join(workspace, 'agent-sandbox-quarantine', String(Date.now()));
|
|
265
|
+
fs.mkdirSync(target, { recursive: true });
|
|
266
|
+
return changed.map((rel) => {
|
|
267
|
+
const what = rel + ' (the sandbox added something it would run on this PC: hooks, MCP servers, helper commands or tasks';
|
|
268
|
+
try {
|
|
269
|
+
fs.renameSync(path.join(workspace, ...rel.split('/')), path.join(target, rel.replace(/\//g, '_')));
|
|
270
|
+
}
|
|
271
|
+
catch (e) {
|
|
272
|
+
return { kind: 'agent', detail: what + '; could not be moved: ' + e.code + '. Check it before you open this folder in Claude Code or VS Code)' };
|
|
273
|
+
}
|
|
274
|
+
return { kind: 'agent', detail: what + '; moved to ' + target + ')' };
|
|
275
|
+
});
|
|
112
276
|
};
|
|
113
277
|
// ---- snapshot storage (host-only, next to the sync state) ---------------------------------------------------------
|
|
114
278
|
const syncDir = (dataPath) => path.join(path.dirname(path.dirname(dataPath)), 'sync');
|
|
115
279
|
const snapshotFile = (dataPath) => path.join(syncDir(dataPath), path.basename(dataPath) + '-repo.json');
|
|
116
280
|
const findingsFile = (dataPath) => path.join(syncDir(dataPath), path.basename(dataPath) + '-repo-findings.json');
|
|
117
|
-
|
|
118
|
-
|
|
281
|
+
const Finding = z.object({ kind: z.enum(['hook', 'config', 'git', 'command', 'agent']), detail: z.string().max(4096), at: z.string().max(64).optional() });
|
|
282
|
+
const GitStateSchema = z.object({ config: z.array(z.string()), hooks: z.record(z.string(), z.string()), redirects: z.array(z.string()) });
|
|
283
|
+
const Saved = z.object({
|
|
284
|
+
workspace: z.string().max(1024).refine((p) => path.isAbsolute(p)),
|
|
285
|
+
snapshot: GitStateSchema.extend({ gitDir: z.boolean(), commands: z.record(z.string(), z.string()), agent: z.record(z.string(), z.string()).optional(), repos: z.record(z.string(), GitStateSchema).optional() }),
|
|
286
|
+
});
|
|
287
|
+
const Store = z.strictObject({ projects: z.array(Saved).max(4096), roots: z.array(z.string().max(1024).refine((p) => path.isAbsolute(p))).max(64) });
|
|
288
|
+
const StoreAny = z.union([Store, Saved.transform((s) => ({ projects: [s], roots: [] }))]);
|
|
289
|
+
const readStore = (dataPath) => readValid(StoreAny, snapshotFile(dataPath)) ?? { projects: [], roots: [] };
|
|
290
|
+
const EMPTY = { gitDir: false, config: [], hooks: {}, redirects: [], commands: {}, agent: {}, repos: {} };
|
|
291
|
+
const MAX_ROOT_PROJECTS = 2000;
|
|
292
|
+
/** The folders directly inside a project root (plain folders only; links are never followed). */
|
|
293
|
+
const rootProjects = (root) => {
|
|
294
|
+
if (!dirExists(root) || isLink(root))
|
|
295
|
+
return [];
|
|
296
|
+
return fs.readdirSync(root, { withFileTypes: true }).filter((e) => e.isDirectory() && !e.isSymbolicLink()).slice(0, MAX_ROOT_PROJECTS).map((e) => path.join(root, e.name));
|
|
297
|
+
};
|
|
298
|
+
const has = (projects, workspace) => projects.some((s) => s.workspace.toLowerCase() === workspace.toLowerCase());
|
|
299
|
+
/**
|
|
300
|
+
* At launch: every folder the sandbox will be able to write. The launch folder, projects mapped with it and every
|
|
301
|
+
* project already inside a project root are snapshotted as they are now, before the sandbox can touch them.
|
|
302
|
+
*/
|
|
303
|
+
export const saveRepoSnapshot = (dataPath, workspace, more = [], roots = []) => {
|
|
304
|
+
const folders = _.uniqBy([workspace, ...more, ...roots.flatMap((r) => [r, ...rootProjects(r)])], (w) => w.toLowerCase());
|
|
305
|
+
writeJsonAtomic(snapshotFile(dataPath), { projects: folders.map((w) => ({ workspace: w, snapshot: snapshotRepo(w) })), roots });
|
|
306
|
+
};
|
|
307
|
+
/**
|
|
308
|
+
* A project that joins the running sandbox: snapshotted before its first session, then guarded like the launch
|
|
309
|
+
* folder. A project already guarded keeps its first snapshot (what was there before the sandbox could touch it).
|
|
310
|
+
*/
|
|
311
|
+
export const addRepoSnapshot = (dataPath, workspace, o = {}) => {
|
|
312
|
+
const store = readStore(dataPath);
|
|
313
|
+
// A folder inside one that is guarded already is covered by it only for its root files; it gets its own entry.
|
|
314
|
+
if (has(store.projects, workspace))
|
|
315
|
+
return;
|
|
316
|
+
writeJsonAtomic(snapshotFile(dataPath), { ...store, projects: [...store.projects, { workspace, snapshot: o.empty ? EMPTY : snapshotRepo(workspace) }] });
|
|
317
|
+
};
|
|
318
|
+
/**
|
|
319
|
+
* Folders that appeared inside a project root after the launch: the sandbox could have written them from the start,
|
|
320
|
+
* so they get an EMPTY baseline: any git hook, git config that runs programs (core.hooksPath, core.fsmonitor, ...)
|
|
321
|
+
* or planted command in them counts as added inside the sandbox and is neutralised.
|
|
322
|
+
*/
|
|
323
|
+
const discoverRootProjects = (dataPath) => {
|
|
324
|
+
const store = readStore(dataPath);
|
|
325
|
+
const found = store.roots.flatMap(rootProjects).filter((w) => !has(store.projects, w)).map((workspace) => ({ workspace, snapshot: EMPTY }));
|
|
326
|
+
if (!found.length)
|
|
327
|
+
return store.projects;
|
|
328
|
+
const projects = [...store.projects, ...found].slice(0, 4096);
|
|
329
|
+
writeJsonAtomic(snapshotFile(dataPath), { ...store, projects });
|
|
330
|
+
return projects;
|
|
331
|
+
};
|
|
332
|
+
/** The folders being guarded (for status). */
|
|
333
|
+
export const guardedProjects = (dataPath) => readStore(dataPath).projects.map((s) => s.workspace);
|
|
334
|
+
/** Re-checks every project of the current/last launch; findings are kept in a host-only file for `status`. Returns the new ones. */
|
|
119
335
|
export const checkRepo = (dataPath) => {
|
|
120
|
-
const saved =
|
|
121
|
-
if (!saved
|
|
336
|
+
const saved = discoverRootProjects(dataPath);
|
|
337
|
+
if (!saved.length)
|
|
122
338
|
return [];
|
|
123
|
-
const
|
|
339
|
+
const old = repoFindings(dataPath);
|
|
340
|
+
// A finding that cannot be fixed (a file locked in the sandbox) is reported once, not every minute.
|
|
341
|
+
const findings = saved.flatMap((s) => guardRepo(s.workspace, s.snapshot).map((f) => (saved.length > 1 ? { ...f, detail: s.workspace + ': ' + f.detail } : f)))
|
|
342
|
+
.filter((f) => !old.some((o) => o.kind === f.kind && o.detail === f.detail));
|
|
124
343
|
if (findings.length)
|
|
125
|
-
|
|
344
|
+
writeJsonAtomic(findingsFile(dataPath), [...old, ...findings.map((f) => ({ ...f, at: new Date().toISOString() }))]);
|
|
126
345
|
return findings;
|
|
127
346
|
};
|
|
128
|
-
export const repoFindings = (dataPath) =>
|
|
347
|
+
export const repoFindings = (dataPath) => readValid(z.array(Finding).max(10000), findingsFile(dataPath)) ?? [];
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { type ChildProcess } from 'node:child_process';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
declare const Life: z.ZodObject<{
|
|
4
|
+
started: z.ZodString;
|
|
5
|
+
lastSeen: z.ZodString;
|
|
6
|
+
keepAwake: z.ZodBoolean;
|
|
7
|
+
ended: z.ZodOptional<z.ZodObject<{
|
|
8
|
+
at: z.ZodString;
|
|
9
|
+
cause: z.ZodNullable<z.ZodObject<{
|
|
10
|
+
kind: z.ZodEnum<{
|
|
11
|
+
restart: "restart";
|
|
12
|
+
updates: "updates";
|
|
13
|
+
crash: "crash";
|
|
14
|
+
sleep: "sleep";
|
|
15
|
+
memory: "memory";
|
|
16
|
+
sandboxCrash: "sandboxCrash";
|
|
17
|
+
}>;
|
|
18
|
+
at: z.ZodString;
|
|
19
|
+
text: z.ZodString;
|
|
20
|
+
}, z.core.$strip>>;
|
|
21
|
+
}, z.core.$strip>>;
|
|
22
|
+
reported: z.ZodOptional<z.ZodBoolean>;
|
|
23
|
+
}, z.core.$strip>;
|
|
24
|
+
type LifeT = z.infer<typeof Life>;
|
|
25
|
+
export declare const readLife: (storageRoot: string, dataPath: string) => LifeT | null;
|
|
26
|
+
/** At launch: a fresh record (the previous one has been reported by then). */
|
|
27
|
+
export declare const startLife: (storageRoot: string, dataPath: string, keepAwake: boolean, now?: Date) => void;
|
|
28
|
+
/** Every minute while the sandbox runs. */
|
|
29
|
+
export declare const beatLife: (storageRoot: string, dataPath: string, now?: Date) => void;
|
|
30
|
+
/** The watcher saw the sandbox close while this PC kept running: closed by you (no cause), or by sleep / low memory. */
|
|
31
|
+
export declare const endLife: (storageRoot: string, dataPath: string, cause: StopCause | null, now?: Date) => void;
|
|
32
|
+
export interface StopEvent {
|
|
33
|
+
at: string;
|
|
34
|
+
id: number;
|
|
35
|
+
provider: string;
|
|
36
|
+
process: string;
|
|
37
|
+
}
|
|
38
|
+
export type StopKind = 'updates' | 'restart' | 'crash' | 'sleep' | 'memory' | 'sandboxCrash';
|
|
39
|
+
export interface StopCause {
|
|
40
|
+
kind: StopKind;
|
|
41
|
+
at: string;
|
|
42
|
+
text: string;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* The first event that ends a running sandbox (pure): Windows inside the sandbox crashing (Hyper-V 18590; `process`
|
|
46
|
+
* holds its message with the stop code), a restart or shutdown, a crash or power loss, sleep, low memory. Switching
|
|
47
|
+
* user does not end it (seen on 13 Oct 2026: the sandbox ran on after a switch).
|
|
48
|
+
*/
|
|
49
|
+
export declare const explainStop: (events: StopEvent[]) => StopCause | null;
|
|
50
|
+
/** System log and Hyper-V events since `since` that can end a sandbox (Windows only; empty when the logs cannot be read). */
|
|
51
|
+
export declare const readStopEvents: (since: Date) => StopEvent[];
|
|
52
|
+
/** Looking back from the last time the sandbox was seen running (a minute between checks, plus margin). */
|
|
53
|
+
export declare const causeSince: (lastSeen: string) => StopCause | null;
|
|
54
|
+
/**
|
|
55
|
+
* At launch: one warning when the last sandbox ended without anyone closing it (the PC restarted, crashed, slept or ran
|
|
56
|
+
* out of memory), then never again for that run. `watcherAlive` = its watcher is still running (then it did not end).
|
|
57
|
+
*/
|
|
58
|
+
export declare const lastStopWarning: (storageRoot: string, dataPath: string, watcherAlive: boolean, find?: (lastSeen: string) => StopCause | null) => string | null;
|
|
59
|
+
/** Windows has installed updates and waits to restart: it may do so on its own, outside your active hours. */
|
|
60
|
+
export declare const updateRestartPending: () => boolean;
|
|
61
|
+
export declare const UPDATE_RESTART_WARNING = "Windows is waiting to restart for updates and may do so on its own (often overnight), which closes the sandbox. Restart first, or pause updates: Settings > Windows Update > Pause updates.";
|
|
62
|
+
/**
|
|
63
|
+
* sandbox.keepAwake: asks Windows not to sleep while the sandbox runs (as a video player does; the screen may still turn
|
|
64
|
+
* off). A small PowerShell holds the request until `ownerPid` exits, so it never outlives the watcher.
|
|
65
|
+
*/
|
|
66
|
+
export declare const keepAwake: (ownerPid: number) => ChildProcess | null;
|
|
67
|
+
export {};
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
// Why the last sandbox stopped, when nobody stopped it. The background watcher writes a host-only "life" record (when
|
|
2
|
+
// the sandbox started, when it was last seen running, how it ended) and keeps this PC awake while it runs
|
|
3
|
+
// (sandbox.keepAwake). When the PC restarts, sleeps or crashes, the watcher stops too and the record has no end: the
|
|
4
|
+
// next launch reads Windows' System log from that moment on and says what happened, and what keeps it from happening.
|
|
5
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import { z } from 'zod';
|
|
8
|
+
import { SYSTEM } from '../shared/system.js';
|
|
9
|
+
import { readValid, writeJsonAtomic } from '../shared/rundir.js';
|
|
10
|
+
import { isWin } from '../core/util.js';
|
|
11
|
+
import { refuseInSelfTest } from './test-guard.js';
|
|
12
|
+
const Iso = z.string().max(40);
|
|
13
|
+
const Life = z.object({
|
|
14
|
+
started: Iso, lastSeen: Iso, keepAwake: z.boolean(),
|
|
15
|
+
ended: z.object({ at: Iso, cause: z.object({ kind: z.enum(['updates', 'restart', 'crash', 'sleep', 'memory', 'sandboxCrash']), at: Iso, text: z.string().max(400) }).nullable() }).optional(),
|
|
16
|
+
reported: z.boolean().optional(),
|
|
17
|
+
});
|
|
18
|
+
const lifeFile = (storageRoot, dataPath) => path.join(storageRoot, 'sync', path.basename(dataPath) + '-life.json');
|
|
19
|
+
export const readLife = (storageRoot, dataPath) => readValid(Life, lifeFile(storageRoot, dataPath));
|
|
20
|
+
const writeLife = (storageRoot, dataPath, life) => writeJsonAtomic(lifeFile(storageRoot, dataPath), life);
|
|
21
|
+
/** At launch: a fresh record (the previous one has been reported by then). */
|
|
22
|
+
export const startLife = (storageRoot, dataPath, keepAwake, now = new Date()) => writeLife(storageRoot, dataPath, { started: now.toISOString(), lastSeen: now.toISOString(), keepAwake });
|
|
23
|
+
/** Every minute while the sandbox runs. */
|
|
24
|
+
export const beatLife = (storageRoot, dataPath, now = new Date()) => {
|
|
25
|
+
const life = readLife(storageRoot, dataPath);
|
|
26
|
+
if (life && !life.ended)
|
|
27
|
+
writeLife(storageRoot, dataPath, { ...life, lastSeen: now.toISOString() });
|
|
28
|
+
};
|
|
29
|
+
/** The watcher saw the sandbox close while this PC kept running: closed by you (no cause), or by sleep / low memory. */
|
|
30
|
+
export const endLife = (storageRoot, dataPath, cause, now = new Date()) => {
|
|
31
|
+
const life = readLife(storageRoot, dataPath);
|
|
32
|
+
if (life && !life.ended)
|
|
33
|
+
writeLife(storageRoot, dataPath, { ...life, ended: { at: now.toISOString(), cause } });
|
|
34
|
+
};
|
|
35
|
+
const StopEvents = z.array(z.object({ at: Iso, id: z.number().int(), provider: z.string().max(200), process: z.string().max(400).nullable().transform((p) => p ?? '') })).max(100);
|
|
36
|
+
const UPDATERS = /^(trustedinstaller|mousocoreworker|musnotification(ux)?|usoclient|wuauclt|tiworker|updateorchestrator)\.exe$/i;
|
|
37
|
+
/**
|
|
38
|
+
* The first event that ends a running sandbox (pure): Windows inside the sandbox crashing (Hyper-V 18590; `process`
|
|
39
|
+
* holds its message with the stop code), a restart or shutdown, a crash or power loss, sleep, low memory. Switching
|
|
40
|
+
* user does not end it (seen on 13 Oct 2026: the sandbox ran on after a switch).
|
|
41
|
+
*/
|
|
42
|
+
export const explainStop = (events) => {
|
|
43
|
+
const causes = events.flatMap((e) => {
|
|
44
|
+
const p = e.provider.toLowerCase();
|
|
45
|
+
if (e.id === 1074 && p.includes('user32')) {
|
|
46
|
+
const exe = e.process.split(/[\\/]/).pop()?.replace(/\s*\(.*$/, '') ?? '';
|
|
47
|
+
return UPDATERS.test(exe)
|
|
48
|
+
? [{ kind: 'updates', at: e.at, text: 'Windows restarted this PC to install updates' }]
|
|
49
|
+
: [{ kind: 'restart', at: e.at, text: 'this PC was restarted or shut down' + (exe ? ' (by ' + exe + ')' : '') }];
|
|
50
|
+
}
|
|
51
|
+
if ((e.id === 41 && p.includes('kernel-power')) || (e.id === 6008 && p.includes('eventlog')))
|
|
52
|
+
return [{ kind: 'crash', at: e.at, text: 'Windows stopped unexpectedly (a crash, a power cut, or the power button held down)' }];
|
|
53
|
+
if (e.id === 42 && p.includes('kernel-power'))
|
|
54
|
+
return [{ kind: 'sleep', at: e.at, text: 'this PC went to sleep or hibernated' }];
|
|
55
|
+
if (e.id === 2004 && p.includes('resource-exhaustion'))
|
|
56
|
+
return [{ kind: 'memory', at: e.at, text: 'this PC ran out of memory' }];
|
|
57
|
+
if (e.id === 18590 && p.includes('hyper-v-worker')) {
|
|
58
|
+
const code = /ErrorCode0:\s*(0x[0-9a-f]{1,8})/i.exec(e.process)?.[1];
|
|
59
|
+
const sub = /ErrorCode1:\s*(0x[0-9a-f]{1,16})/i.exec(e.process)?.[1];
|
|
60
|
+
return [{ kind: 'sandboxCrash', at: e.at, text: 'Windows inside the sandbox crashed' + (code ? ' (stop code ' + code + (sub ? ', ' + sub : '') + ')' : '') }];
|
|
61
|
+
}
|
|
62
|
+
return [];
|
|
63
|
+
});
|
|
64
|
+
return causes.sort((a, b) => Date.parse(a.at) - Date.parse(b.at))[0] ?? null;
|
|
65
|
+
};
|
|
66
|
+
const ADVICE = {
|
|
67
|
+
updates: 'Before leaving agents overnight, restart for pending updates first or pause them (Settings > Windows Update > Pause updates).',
|
|
68
|
+
restart: 'If that was not you, check Settings > Windows Update and Event Viewer > Windows Logs > System.',
|
|
69
|
+
crash: 'Event Viewer > Windows Logs > System has the details; a laptop also needs to stay plugged in.',
|
|
70
|
+
sleep: 'sandbox.keepAwake keeps this PC awake while a sandbox runs (closing a laptop lid still sleeps it). Check it is on, and that sleep is not forced by a policy.',
|
|
71
|
+
sandboxCrash: 'C:\\AgentData\\logs\\memory-*.log shows what used memory just before. Fewer builds at once inside the sandbox (e.g. cargo build.jobs) makes it less likely.',
|
|
72
|
+
memory: 'Give the sandbox less RAM (sandbox.maxMemoryMb, sandbox.reserveHostMb) or run fewer builds at once inside it.',
|
|
73
|
+
};
|
|
74
|
+
/** System log and Hyper-V events since `since` that can end a sandbox (Windows only; empty when the logs cannot be read). */
|
|
75
|
+
export const readStopEvents = (since) => {
|
|
76
|
+
if (!isWin)
|
|
77
|
+
return [];
|
|
78
|
+
refuseInSelfTest('read the Windows event log');
|
|
79
|
+
const script = "$ErrorActionPreference='SilentlyContinue'; $t=[datetime]::Parse('" + since.toISOString() + "').ToLocalTime(); $e = Get-WinEvent -FilterHashtable @(@{LogName='System'; Id=1074,6008,41,42,2004; StartTime=$t}, @{LogName='Microsoft-Windows-Hyper-V-Worker-Admin'; Id=18590; StartTime=$t}) -MaxEvents 100 | ForEach-Object { [pscustomobject]@{ at=$_.TimeCreated.ToUniversalTime().ToString('o'); id=$_.Id; provider=$_.ProviderName; process=$(if ($_.Id -eq 1074) { [string]$_.Properties[0].Value } elseif ($_.Id -eq 18590) { ([string]$_.Message).Substring(0, [math]::Min(390, ([string]$_.Message).Length)) } else { '' }) } }; ConvertTo-Json -Compress -InputObject @($e)";
|
|
80
|
+
const result = spawnSync(SYSTEM.powershell(), ['-NoProfile', '-NonInteractive', '-EncodedCommand', Buffer.from(script, 'utf16le').toString('base64')], { encoding: 'utf8', windowsHide: true, timeout: 60000 });
|
|
81
|
+
try {
|
|
82
|
+
return StopEvents.parse(JSON.parse(result.stdout || '[]'));
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
return [];
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
/** Looking back from the last time the sandbox was seen running (a minute between checks, plus margin). */
|
|
89
|
+
export const causeSince = (lastSeen) => explainStop(readStopEvents(new Date(Date.parse(lastSeen) - 2 * 60 * 1000)));
|
|
90
|
+
const when = (iso) => new Date(iso).toLocaleString(undefined, { weekday: 'short', hour: '2-digit', minute: '2-digit' });
|
|
91
|
+
/**
|
|
92
|
+
* At launch: one warning when the last sandbox ended without anyone closing it (the PC restarted, crashed, slept or ran
|
|
93
|
+
* out of memory), then never again for that run. `watcherAlive` = its watcher is still running (then it did not end).
|
|
94
|
+
*/
|
|
95
|
+
export const lastStopWarning = (storageRoot, dataPath, watcherAlive, find = causeSince) => {
|
|
96
|
+
const life = readLife(storageRoot, dataPath);
|
|
97
|
+
if (!life || life.reported || watcherAlive)
|
|
98
|
+
return null;
|
|
99
|
+
writeLife(storageRoot, dataPath, { ...life, reported: true });
|
|
100
|
+
if (life.ended && !life.ended.cause)
|
|
101
|
+
return null;
|
|
102
|
+
const cause = life.ended ? life.ended.cause : find(life.lastSeen);
|
|
103
|
+
const what = cause ? cause.text + ' at ' + when(cause.at) + '. ' + ADVICE[cause.kind] : 'this PC stopped (Windows logged no reason). ' + ADVICE.crash;
|
|
104
|
+
return 'The last sandbox did not close normally: it was last seen running at ' + when(life.lastSeen) + ', and ' + what + ' Agent conversations are kept: /resume in Claude picks one up.';
|
|
105
|
+
};
|
|
106
|
+
// ---- Windows Update and keeping awake ----------------------------------------------------------------------------
|
|
107
|
+
/** Windows has installed updates and waits to restart: it may do so on its own, outside your active hours. */
|
|
108
|
+
export const updateRestartPending = () => isWin && [
|
|
109
|
+
'HKLM\\SOFTWARE\\Microsoft\\WindowsUpdate\\Auto Update\\RebootRequired',
|
|
110
|
+
'HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Component Based Servicing\\RebootPending',
|
|
111
|
+
].some((key) => spawnSync(SYSTEM.reg(), ['query', key], { windowsHide: true, stdio: 'ignore', timeout: 15000 }).status === 0);
|
|
112
|
+
export const UPDATE_RESTART_WARNING = 'Windows is waiting to restart for updates and may do so on its own (often overnight), which closes the sandbox. Restart first, or pause updates: Settings > Windows Update > Pause updates.';
|
|
113
|
+
/**
|
|
114
|
+
* sandbox.keepAwake: asks Windows not to sleep while the sandbox runs (as a video player does; the screen may still turn
|
|
115
|
+
* off). A small PowerShell holds the request until `ownerPid` exits, so it never outlives the watcher.
|
|
116
|
+
*/
|
|
117
|
+
export const keepAwake = (ownerPid) => {
|
|
118
|
+
if (!isWin)
|
|
119
|
+
return null;
|
|
120
|
+
refuseInSelfTest('keep this PC awake');
|
|
121
|
+
const script = "Add-Type -Name Power -Namespace AgentSandbox -MemberDefinition '[DllImport(\"kernel32.dll\")] public static extern uint SetThreadExecutionState(uint f);'; "
|
|
122
|
+
+ '[void][AgentSandbox.Power]::SetThreadExecutionState([uint32]2147483649); Wait-Process -Id ' + Math.trunc(ownerPid) + ' -ErrorAction SilentlyContinue';
|
|
123
|
+
const child = spawn(SYSTEM.powershell(), ['-NoProfile', '-NonInteractive', '-EncodedCommand', Buffer.from(script, 'utf16le').toString('base64')], { stdio: 'ignore', windowsHide: true });
|
|
124
|
+
child.on('error', () => undefined);
|
|
125
|
+
return child;
|
|
126
|
+
};
|
package/dist/host/terminal.d.ts
CHANGED
|
@@ -29,4 +29,12 @@ export declare const ensureTerminal: (dataPath: string, refresh: boolean, option
|
|
|
29
29
|
exe: string | null;
|
|
30
30
|
message: string;
|
|
31
31
|
}>;
|
|
32
|
+
/** Where the sandbox finds conpty.dll + OpenConsole.exe (guest/ptyhost.ts points ptyhost at it). */
|
|
33
|
+
export declare const conptyDir: (dataPath: string) => string;
|
|
34
|
+
/**
|
|
35
|
+
* Microsoft's newer pseudo console (the Microsoft.Windows.Console.ConPTY package: conpty.dll and OpenConsole.exe, the
|
|
36
|
+
* same one Windows Terminal and VS Code use), downloaded once and verified against the SHA-512 NuGet publishes. Agent
|
|
37
|
+
* sessions use it instead of the one built into Windows, which re-renders the screen and garbles it after resizes.
|
|
38
|
+
*/
|
|
39
|
+
export declare const ensureConpty: (dataPath: string, refresh: boolean) => Promise<string>;
|
|
32
40
|
export {};
|