windows-development-sandbox 1.0.5 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +25 -8
- package/config.example.json +55 -5
- package/dist/api.js +2 -1
- package/dist/cli/args.d.ts +23 -0
- package/dist/cli/args.js +38 -4
- package/dist/cli/colors.d.ts +24 -0
- package/dist/cli/colors.js +42 -0
- package/dist/cli/commands/accounts.js +2 -2
- package/dist/cli/commands/agent-commands.d.ts +6 -0
- package/dist/cli/commands/agent-commands.js +61 -0
- package/dist/cli/commands/context.d.ts +2 -0
- package/dist/cli/commands/context.js +5 -1
- package/dist/cli/commands/data/clean.d.ts +15 -0
- package/dist/cli/commands/data/clean.js +106 -0
- package/dist/cli/commands/data/collect.d.ts +11 -0
- package/dist/cli/commands/data/collect.js +54 -0
- package/dist/cli/commands/data/index.d.ts +4 -0
- package/dist/cli/commands/data/index.js +45 -0
- package/dist/cli/commands/data/remove.d.ts +2 -0
- package/dist/cli/commands/data/remove.js +30 -0
- package/dist/cli/commands/index.d.ts +6 -4
- package/dist/cli/commands/index.js +6 -4
- package/dist/cli/commands/launch.d.ts +26 -2
- package/dist/cli/commands/launch.js +256 -42
- package/dist/cli/commands/maintenance.d.ts +2 -0
- package/dist/cli/commands/maintenance.js +67 -5
- package/dist/cli/commands/project.d.ts +29 -0
- package/dist/cli/commands/project.js +63 -0
- package/dist/cli/commands/projects.d.ts +14 -0
- package/dist/cli/commands/projects.js +146 -0
- package/dist/cli/commands/repair/checks.d.ts +2 -0
- package/dist/cli/commands/{repair.js → repair/checks.js} +19 -181
- package/dist/cli/commands/{repair.d.ts → repair/finders.d.ts} +0 -29
- package/dist/cli/commands/repair/finders.js +108 -0
- package/dist/cli/commands/repair/index.d.ts +12 -0
- package/dist/cli/commands/repair/index.js +61 -0
- package/dist/cli/commands/repair/types.d.ts +26 -0
- package/dist/cli/commands/repair/types.js +3 -0
- package/dist/cli/commands/restart.d.ts +6 -0
- package/dist/cli/commands/restart.js +85 -0
- package/dist/cli/commands/sessions.d.ts +2 -1
- package/dist/cli/commands/sessions.js +189 -45
- package/dist/cli/commands/setup/checks.d.ts +13 -0
- package/dist/cli/commands/setup/checks.js +71 -0
- package/dist/cli/commands/{setup.d.ts → setup/facts.d.ts} +1 -15
- package/dist/cli/commands/setup/facts.js +60 -0
- package/dist/cli/commands/setup/index.d.ts +2 -0
- package/dist/cli/commands/setup/index.js +43 -0
- package/dist/cli/commands/user-config.d.ts +8 -0
- package/dist/cli/commands/user-config.js +41 -0
- package/dist/cli/history.d.ts +11 -0
- package/dist/cli/history.js +21 -0
- package/dist/cli/index.d.ts +7 -1
- package/dist/cli/index.js +42 -15
- package/dist/cli/manifest.d.ts +8 -0
- package/dist/cli/manifest.js +40 -15
- package/dist/cli/menu/apps.d.ts +21 -0
- package/dist/cli/menu/apps.js +122 -0
- package/dist/cli/menu/command-form.d.ts +28 -0
- package/dist/cli/menu/command-form.js +219 -0
- package/dist/cli/menu/index.d.ts +26 -0
- package/dist/cli/menu/index.js +180 -0
- package/dist/cli/menu/settings/categories.d.ts +9 -0
- package/dist/cli/menu/settings/categories.js +28 -0
- package/dist/cli/menu/settings/config-file.d.ts +40 -0
- package/dist/cli/menu/settings/config-file.js +71 -0
- package/dist/cli/menu/settings/editors.d.ts +30 -0
- package/dist/cli/menu/settings/editors.js +305 -0
- package/dist/cli/menu/settings/index.d.ts +32 -0
- package/dist/cli/menu/settings/index.js +136 -0
- package/dist/cli/menu/settings/schema-node.d.ts +62 -0
- package/dist/cli/menu/settings/schema-node.js +104 -0
- package/dist/cli/menu/several.d.ts +20 -0
- package/dist/cli/menu/several.js +102 -0
- package/dist/cli/progress.d.ts +9 -0
- package/dist/cli/progress.js +49 -0
- package/dist/cli/tui/app.d.ts +39 -0
- package/dist/cli/tui/app.js +52 -0
- package/dist/cli/tui/dialogs.d.ts +57 -0
- package/dist/cli/tui/dialogs.js +162 -0
- package/dist/cli/tui/frame.d.ts +18 -0
- package/dist/cli/tui/frame.js +27 -0
- package/dist/cli/tui/index.d.ts +7 -0
- package/dist/cli/tui/index.js +8 -0
- package/dist/cli/tui/list-screen.d.ts +101 -0
- package/dist/cli/tui/list-screen.js +299 -0
- package/dist/cli/tui/terminal.d.ts +37 -0
- package/dist/cli/tui/terminal.js +89 -0
- package/dist/cli/tui/text.d.ts +20 -0
- package/dist/cli/tui/text.js +105 -0
- package/dist/cli/tui/theme.d.ts +45 -0
- package/dist/cli/tui/theme.js +30 -0
- package/dist/cli/validate.d.ts +17 -0
- package/dist/cli/validate.js +89 -0
- package/dist/core/config.js +12 -6
- package/dist/core/disk.d.ts +16 -0
- package/dist/core/disk.js +55 -0
- package/dist/core/local-config.d.ts +23 -0
- package/dist/core/local-config.js +79 -7
- package/dist/core/project-policy.d.ts +15 -0
- package/dist/core/project-policy.js +35 -0
- package/dist/core/prompt.d.ts +4 -0
- package/dist/core/prompt.js +15 -0
- package/dist/core/safe-paths.d.ts +4 -0
- package/dist/core/safe-paths.js +37 -0
- package/dist/core/schema/apps.d.ts +40 -0
- package/dist/core/schema/apps.js +27 -0
- package/dist/core/schema/common.d.ts +17 -0
- package/dist/core/schema/common.js +15 -0
- package/dist/core/schema/config.d.ts +659 -0
- package/dist/core/schema/config.js +75 -0
- package/dist/core/schema/databases.d.ts +33 -0
- package/dist/core/schema/databases.js +35 -0
- package/dist/core/schema/harness.d.ts +12 -0
- package/dist/core/schema/harness.js +17 -0
- package/dist/core/schema/json-schema.d.ts +5 -0
- package/dist/core/schema/json-schema.js +25 -0
- package/dist/core/schema/profile.d.ts +32 -0
- package/dist/core/schema/profile.js +31 -0
- package/dist/core/schema/projects.d.ts +9 -0
- package/dist/core/schema/projects.js +11 -0
- package/dist/core/schema/sandbox.d.ts +78 -0
- package/dist/core/schema/sandbox.js +64 -0
- package/dist/core/schema/toolchains.d.ts +96 -0
- package/dist/core/schema/toolchains.js +88 -0
- package/dist/core/schema.d.ts +6 -604
- package/dist/core/schema.js +8 -264
- package/dist/core/types.d.ts +35 -1
- package/dist/core/util.d.ts +9 -0
- package/dist/core/util.js +14 -3
- package/dist/core/workspace.d.ts +0 -5
- package/dist/core/workspace.js +33 -50
- package/dist/guest/apps/index.d.ts +10 -0
- package/dist/guest/apps/index.js +206 -0
- package/dist/guest/apps/resolvers.d.ts +8 -0
- package/dist/guest/apps/resolvers.js +90 -0
- package/dist/guest/audit-parse.d.ts +7 -0
- package/dist/guest/audit-parse.js +24 -0
- package/dist/guest/audit.d.ts +4 -0
- package/dist/guest/audit.js +47 -0
- package/dist/guest/boot-env.d.ts +4 -0
- package/dist/guest/boot-env.js +25 -0
- package/dist/guest/cmdline.d.ts +12 -0
- package/dist/guest/cmdline.js +23 -0
- package/dist/guest/daemon/exec.d.ts +5 -0
- package/dist/guest/daemon/exec.js +32 -0
- package/dist/guest/daemon/project.d.ts +14 -0
- package/dist/guest/daemon/project.js +132 -0
- package/dist/guest/daemon/relays.d.ts +1 -0
- package/dist/guest/daemon/relays.js +18 -0
- package/dist/guest/daemon/session.d.ts +13 -0
- package/dist/guest/daemon/session.js +139 -0
- package/dist/guest/daemon/shutdown.d.ts +1 -0
- package/dist/guest/daemon/shutdown.js +21 -0
- package/dist/guest/daemon/state.d.ts +17 -0
- package/dist/guest/daemon/state.js +31 -0
- package/dist/guest/daemon.js +12 -141
- package/dist/guest/db.js +18 -3
- package/dist/guest/env.d.ts +3 -0
- package/dist/guest/env.js +7 -3
- package/dist/guest/guide.d.ts +6 -2
- package/dist/guest/guide.js +5 -4
- package/dist/guest/helpers.d.ts +3 -0
- package/dist/guest/helpers.js +26 -0
- package/dist/guest/main.js +31 -8
- package/dist/guest/netproxy.d.ts +1 -0
- package/dist/guest/netproxy.js +100 -0
- package/dist/guest/ports.d.ts +6 -0
- package/dist/guest/ports.js +22 -0
- package/dist/guest/project-context.d.ts +7 -0
- package/dist/guest/project-context.js +10 -0
- package/dist/guest/project-env.d.ts +20 -0
- package/dist/guest/project-env.js +20 -0
- package/dist/guest/project-status.d.ts +5 -0
- package/dist/guest/project-status.js +17 -0
- package/dist/guest/ptyhost.d.ts +0 -18
- package/dist/guest/ptyhost.js +17 -65
- package/dist/guest/setup/agents.d.ts +5 -2
- package/dist/guest/setup/agents.js +22 -8
- package/dist/guest/setup/claude.d.ts +0 -4
- package/dist/guest/setup/claude.js +59 -5
- package/dist/guest/setup/environment.d.ts +13 -3
- package/dist/guest/setup/environment.js +14 -10
- package/dist/guest/setup/hooks.d.ts +9 -1
- package/dist/guest/setup/hooks.js +28 -14
- package/dist/guest/setup/network.d.ts +9 -0
- package/dist/guest/setup/network.js +97 -4
- package/dist/guest/setup/user.js +4 -0
- package/dist/guest/toolchains/catalogue.d.ts +4 -0
- package/dist/guest/toolchains/catalogue.js +2 -2
- package/dist/guest/toolchains/cli.js +25 -17
- package/dist/guest/toolchains/common.d.ts +18 -1
- package/dist/guest/toolchains/common.js +34 -11
- package/dist/guest/toolchains/databases/dynamodb.d.ts +8 -0
- package/dist/guest/toolchains/databases/dynamodb.js +52 -0
- package/dist/guest/toolchains/{databases.d.ts → databases/index.d.ts} +2 -3
- package/dist/guest/toolchains/databases/index.js +62 -0
- package/dist/guest/toolchains/databases/mongodb.d.ts +3 -0
- package/dist/guest/toolchains/databases/mongodb.js +31 -0
- package/dist/guest/toolchains/databases/mysql.d.ts +3 -0
- package/dist/guest/toolchains/databases/mysql.js +38 -0
- package/dist/guest/toolchains/databases/postgres.d.ts +5 -0
- package/dist/guest/toolchains/databases/postgres.js +38 -0
- package/dist/guest/toolchains/databases/redis.d.ts +3 -0
- package/dist/guest/toolchains/databases/redis.js +16 -0
- package/dist/guest/toolchains/databases/server.d.ts +14 -0
- package/dist/guest/toolchains/databases/server.js +52 -0
- package/dist/guest/toolchains/git.js +25 -16
- package/dist/guest/toolchains/go.js +1 -1
- package/dist/guest/toolchains/index.d.ts +13 -9
- package/dist/guest/toolchains/index.js +40 -82
- package/dist/guest/toolchains/java.js +5 -5
- package/dist/guest/toolchains/leftovers.d.ts +2 -0
- package/dist/guest/toolchains/leftovers.js +26 -0
- package/dist/guest/toolchains/managers.js +12 -10
- package/dist/guest/toolchains/{binstall.js → native/binstall.js} +6 -9
- package/dist/guest/toolchains/native/cpp.d.ts +4 -0
- package/dist/guest/toolchains/native/cpp.js +38 -0
- package/dist/guest/toolchains/native/dotnet.d.ts +4 -0
- package/dist/guest/toolchains/native/dotnet.js +103 -0
- package/dist/guest/toolchains/native/mesa.d.ts +7 -0
- package/dist/guest/toolchains/native/mesa.js +56 -0
- package/dist/guest/toolchains/native/msvc/environment.d.ts +16 -0
- package/dist/guest/toolchains/native/msvc/environment.js +57 -0
- package/dist/guest/toolchains/native/msvc/index.d.ts +6 -0
- package/dist/guest/toolchains/native/msvc/index.js +48 -0
- package/dist/guest/toolchains/native/msvc/packages.d.ts +6 -0
- package/dist/guest/toolchains/native/msvc/packages.js +156 -0
- package/dist/guest/toolchains/native/msvc/tree.d.ts +15 -0
- package/dist/guest/toolchains/native/msvc/tree.js +21 -0
- package/dist/guest/toolchains/native/rust.d.ts +3 -0
- package/dist/guest/toolchains/native/rust.js +45 -0
- package/dist/guest/toolchains/node.js +26 -6
- package/dist/guest/toolchains/plan.d.ts +24 -0
- package/dist/guest/toolchains/plan.js +140 -0
- package/dist/guest/toolchains/pwsh.js +1 -1
- package/dist/guest/toolchains/python.js +43 -12
- package/dist/guest/toolchains/runtimes.d.ts +2 -0
- package/dist/guest/toolchains/runtimes.js +6 -8
- package/dist/guest/toolchains/scripting/php.d.ts +3 -0
- package/dist/guest/toolchains/scripting/php.js +71 -0
- package/dist/guest/toolchains/scripting/ruby.d.ts +3 -0
- package/dist/guest/toolchains/scripting/ruby.js +47 -0
- package/dist/guest/toolchains/scripting/vcruntime.d.ts +7 -0
- package/dist/guest/toolchains/scripting/vcruntime.js +45 -0
- package/dist/guest/toolchains/vscode-profile.d.ts +30 -0
- package/dist/guest/toolchains/vscode-profile.js +69 -0
- package/dist/guest/toolchains/vscode.d.ts +17 -0
- package/dist/guest/toolchains/vscode.js +87 -1
- package/dist/guest/view.js +21 -1
- package/dist/host/accounts.js +14 -5
- package/dist/host/agent-commands.d.ts +25 -0
- package/dist/host/agent-commands.js +145 -0
- package/dist/host/attach.d.ts +17 -0
- package/dist/host/attach.js +70 -0
- package/dist/host/audit/collect.d.ts +34 -0
- package/dist/host/audit/collect.js +126 -0
- package/dist/host/audit/log.d.ts +46 -0
- package/dist/host/audit/log.js +143 -0
- package/dist/host/channel.d.ts +23 -22
- package/dist/host/channel.js +66 -84
- package/dist/host/claude-profile.js +7 -3
- package/dist/host/cleanup.js +2 -11
- package/dist/host/database-plan.d.ts +12 -0
- package/dist/host/database-plan.js +52 -0
- package/dist/host/generate.d.ts +10 -12
- package/dist/host/generate.js +58 -135
- package/dist/host/join.d.ts +28 -0
- package/dist/host/join.js +76 -0
- package/dist/host/launch/mappings.d.ts +3 -0
- package/dist/host/launch/mappings.js +54 -0
- package/dist/host/launch/projects.d.ts +15 -0
- package/dist/host/launch/projects.js +56 -0
- package/dist/host/launch/stage.d.ts +11 -0
- package/dist/host/launch/stage.js +66 -0
- package/dist/host/launch/wsb.d.ts +10 -0
- package/dist/host/launch/wsb.js +34 -0
- package/dist/host/motw.d.ts +17 -0
- package/dist/host/motw.js +74 -0
- package/dist/host/pc-scan.d.ts +6 -0
- package/dist/host/pc-scan.js +37 -0
- package/dist/host/platform.d.ts +2 -0
- package/dist/host/platform.js +16 -1
- package/dist/host/port-forward.d.ts +8 -0
- package/dist/host/port-forward.js +37 -0
- package/dist/host/project-scan.d.ts +1 -17
- package/dist/host/project-scan.js +2 -86
- package/dist/host/projects.d.ts +100 -0
- package/dist/host/projects.js +147 -0
- package/dist/host/repo-guard.d.ts +25 -5
- package/dist/host/repo-guard.js +242 -23
- package/dist/host/stops.d.ts +67 -0
- package/dist/host/stops.js +126 -0
- package/dist/host/terminal.d.ts +8 -0
- package/dist/host/terminal.js +48 -1
- package/dist/host/test-guard.d.ts +6 -0
- package/dist/host/test-guard.js +15 -0
- package/dist/host/window.js +7 -3
- package/dist/host/workspace-view/index.d.ts +20 -0
- package/dist/host/workspace-view/index.js +53 -0
- package/dist/host/workspace-view/rules.d.ts +26 -0
- package/dist/host/workspace-view/rules.js +93 -0
- package/dist/host/workspace-view/view.d.ts +50 -0
- package/dist/host/workspace-view/view.js +281 -0
- package/dist/host/wsb.d.ts +37 -0
- package/dist/host/wsb.js +101 -0
- package/dist/runtime/daemon.js +11791 -2821
- package/dist/runtime/db.js +6585 -466
- package/dist/runtime/main.js +10238 -1724
- package/dist/runtime/netproxy.js +25936 -0
- package/dist/runtime/view.js +6183 -59
- package/dist/selftest/accounts.test.d.ts +2 -0
- package/dist/selftest/accounts.test.js +44 -0
- package/dist/selftest/agent-commands.test.d.ts +2 -0
- package/dist/selftest/agent-commands.test.js +47 -0
- package/dist/selftest/audit.test.d.ts +2 -0
- package/dist/selftest/audit.test.js +57 -0
- package/dist/selftest/channel.test.d.ts +2 -0
- package/dist/selftest/channel.test.js +44 -0
- package/dist/selftest/cli.test.d.ts +2 -2
- package/dist/selftest/cli.test.js +145 -27
- package/dist/selftest/cmdline.test.d.ts +2 -0
- package/dist/selftest/cmdline.test.js +45 -0
- package/dist/selftest/config.test.d.ts +2 -3
- package/dist/selftest/config.test.js +36 -72
- package/dist/selftest/data.test.d.ts +2 -2
- package/dist/selftest/data.test.js +77 -70
- package/dist/selftest/detect.test.d.ts +2 -2
- package/dist/selftest/detect.test.js +54 -38
- package/dist/selftest/fixtures.d.ts +89 -4
- package/dist/selftest/fixtures.js +43 -24
- package/dist/selftest/generate.test.d.ts +2 -0
- package/dist/selftest/generate.test.js +68 -0
- package/dist/selftest/git-access.test.d.ts +2 -0
- package/dist/selftest/git-access.test.js +26 -0
- package/dist/selftest/guest-boot.test.d.ts +2 -0
- package/dist/selftest/guest-boot.test.js +76 -0
- package/dist/selftest/harness.d.ts +44 -7
- package/dist/selftest/harness.js +62 -21
- package/dist/selftest/host-sim.test.d.ts +2 -0
- package/dist/selftest/host-sim.test.js +93 -0
- package/dist/selftest/hostile-input.test.d.ts +2 -0
- package/dist/selftest/hostile-input.test.js +54 -0
- package/dist/selftest/index.d.ts +8 -1
- package/dist/selftest/index.js +137 -35
- package/dist/selftest/links.test.d.ts +2 -0
- package/dist/selftest/links.test.js +50 -0
- package/dist/selftest/live-daemon.test.d.ts +2 -0
- package/dist/selftest/live-daemon.test.js +91 -0
- package/dist/selftest/live-git.test.d.ts +2 -0
- package/dist/selftest/live-git.test.js +60 -0
- package/dist/selftest/live-network.test.d.ts +2 -0
- package/dist/selftest/live-network.test.js +63 -0
- package/dist/selftest/live-projects.test.d.ts +2 -0
- package/dist/selftest/live-projects.test.js +127 -0
- package/dist/selftest/live.d.ts +65 -0
- package/dist/selftest/live.js +109 -0
- package/dist/selftest/local-config.test.d.ts +2 -0
- package/dist/selftest/local-config.test.js +120 -0
- package/dist/selftest/mirror.test.d.ts +2 -3
- package/dist/selftest/mirror.test.js +92 -65
- package/dist/selftest/network.test.d.ts +2 -0
- package/dist/selftest/network.test.js +40 -0
- package/dist/selftest/personalization.test.d.ts +2 -0
- package/dist/selftest/personalization.test.js +53 -0
- package/dist/selftest/projects.test.d.ts +2 -0
- package/dist/selftest/projects.test.js +254 -0
- package/dist/selftest/repair.test.d.ts +2 -0
- package/dist/selftest/repair.test.js +42 -0
- package/dist/selftest/repo-guard.test.d.ts +2 -0
- package/dist/selftest/repo-guard.test.js +121 -0
- package/dist/selftest/secrets.test.d.ts +2 -0
- package/dist/selftest/secrets.test.js +71 -0
- package/dist/selftest/setup.test.d.ts +2 -2
- package/dist/selftest/setup.test.js +45 -22
- package/dist/selftest/suite.d.ts +13 -0
- package/dist/selftest/suite.js +1 -0
- package/dist/selftest/sync.test.d.ts +2 -3
- package/dist/selftest/sync.test.js +130 -78
- package/dist/selftest/test-guard.test.d.ts +2 -0
- package/dist/selftest/test-guard.test.js +49 -0
- package/dist/selftest/toolchains.test.d.ts +2 -0
- package/dist/selftest/toolchains.test.js +133 -0
- package/dist/selftest/workspace-view.test.d.ts +2 -0
- package/dist/selftest/workspace-view.test.js +89 -0
- package/dist/selftest/workspace.test.d.ts +2 -0
- package/dist/selftest/workspace.test.js +53 -0
- package/dist/shared/apps.d.ts +49 -0
- package/dist/shared/apps.js +114 -0
- package/dist/shared/download.d.ts +3 -1
- package/dist/shared/download.js +19 -4
- package/dist/shared/netpolicy.d.ts +27 -0
- package/dist/shared/netpolicy.js +51 -0
- package/dist/shared/paths.d.ts +5 -0
- package/dist/shared/paths.js +6 -0
- package/dist/shared/projects.d.ts +53 -0
- package/dist/shared/projects.js +67 -0
- package/dist/shared/pty-client.d.ts +9 -4
- package/dist/shared/pty-client.js +139 -17
- package/dist/shared/rundir.d.ts +12 -1
- package/dist/shared/rundir.js +14 -4
- package/dist/shared/safe-remove.js +11 -0
- package/dist/shared/schema.d.ts +453 -0
- package/dist/shared/schema.js +43 -2
- package/dist/shared/system.d.ts +1 -0
- package/dist/shared/system.js +1 -0
- package/dist/shared/test-guard.d.ts +4 -0
- package/dist/shared/test-guard.js +37 -0
- package/dist/sync/claude-mem.js +6 -3
- package/dist/sync/history.d.ts +29 -12
- package/dist/sync/history.js +72 -25
- package/dist/sync/watcher.d.ts +8 -0
- package/dist/sync/watcher.js +99 -8
- package/package.json +7 -6
- package/sandbox/bootstrap.ps1 +258 -258
- package/sandbox/cargoshim.cs +56 -0
- package/sandbox/cpucap.cs +33 -0
- package/sandbox/cpucap.ps1 +20 -0
- package/sandbox/logon.cmd +24 -16
- package/sandbox/memlog.ps1 +34 -0
- package/sandbox/openssl.cs +42 -0
- package/sandbox/pagefile.cs +31 -0
- package/sandbox/pagefile.ps1 +21 -0
- package/sandbox/ptyhost.cs +120 -11
- package/sandbox/trust.cmd +35 -35
- package/schemas/config.schema.json +403 -3
- package/schemas/sandbox-config.schema.json +143 -13
- package/dist/cli/commands/data.d.ts +0 -46
- package/dist/cli/commands/data.js +0 -249
- package/dist/cli/commands/setup.js +0 -152
- package/dist/cli/menu.d.ts +0 -22
- package/dist/cli/menu.js +0 -187
- package/dist/cli/settings.d.ts +0 -11
- package/dist/cli/settings.js +0 -156
- package/dist/guest/toolchains/databases.js +0 -226
- package/dist/guest/toolchains/msvc.d.ts +0 -10
- package/dist/guest/toolchains/msvc.js +0 -216
- package/dist/guest/toolchains/native.d.ts +0 -12
- package/dist/guest/toolchains/native.js +0 -220
- package/dist/guest/toolchains/scripting.d.ts +0 -10
- package/dist/guest/toolchains/scripting.js +0 -137
- package/dist/selftest/guest.test.d.ts +0 -3
- package/dist/selftest/guest.test.js +0 -61
- package/dist/selftest/helpers.test.d.ts +0 -2
- package/dist/selftest/helpers.test.js +0 -125
- package/dist/selftest/host.test.d.ts +0 -2
- package/dist/selftest/host.test.js +0 -71
- package/dist/selftest/live.test.d.ts +0 -7
- package/dist/selftest/live.test.js +0 -127
- package/dist/selftest/security.test.d.ts +0 -3
- package/dist/selftest/security.test.js +0 -143
- /package/dist/guest/toolchains/{binstall.d.ts → native/binstall.d.ts} +0 -0
package/dist/host/accounts.js
CHANGED
|
@@ -4,8 +4,14 @@
|
|
|
4
4
|
// restarted in place with --continue so the conversation carries on.
|
|
5
5
|
import fs from 'node:fs';
|
|
6
6
|
import path from 'node:path';
|
|
7
|
+
import { z } from 'zod';
|
|
8
|
+
import { isSafeFileName } from '../shared/paths.js';
|
|
9
|
+
import { assertTestWritable } from './test-guard.js';
|
|
10
|
+
import { readValid, writeJsonAtomic } from '../shared/rundir.js';
|
|
7
11
|
import { assertNoLinks, fail, fileExists, isLink, removeLink, tryReadJson, writeJson } from '../core/util.js';
|
|
8
12
|
const NAME = /^[A-Za-z0-9_-]{1,40}$/;
|
|
13
|
+
const AccountSchema = z.object({ name: z.string().regex(NAME), savedAt: z.string().max(64), email: z.string().max(200).nullable(), credentials: z.looseObject({}), oauthAccount: z.unknown() });
|
|
14
|
+
const IndexSchema = z.object({ default: z.string().regex(NAME).optional(), active: z.record(z.string().max(200), z.string().regex(NAME)).optional() });
|
|
9
15
|
const ACCOUNT_CACHES = ['remote-settings.json', 'policy-limits.json', 'policy-limits.json.stamp.json'];
|
|
10
16
|
const MAX_LOGIN_BYTES = 1024 * 1024;
|
|
11
17
|
/** .claude.json holds per-project state and can be large; it is still capped so a hostile file cannot exhaust memory. */
|
|
@@ -16,15 +22,17 @@ const storageOf = (dataPath) => path.dirname(path.dirname(dataPath));
|
|
|
16
22
|
const dataName = (dataPath) => path.basename(dataPath);
|
|
17
23
|
const accountFile = (storageRoot, name) => path.join(accountsDir(storageRoot), name + '.json');
|
|
18
24
|
const indexFile = (storageRoot) => path.join(accountsDir(storageRoot), 'index.json');
|
|
19
|
-
const readIndex = (storageRoot) =>
|
|
20
|
-
const updateIndex = (storageRoot, change) =>
|
|
21
|
-
|
|
25
|
+
const readIndex = (storageRoot) => readValid(IndexSchema, indexFile(storageRoot)) ?? {};
|
|
26
|
+
const updateIndex = (storageRoot, change) => writeJsonAtomic(indexFile(storageRoot), change(readIndex(storageRoot)));
|
|
27
|
+
/** A file name of its own: not "index" (the index file), not a Windows device name such as con or nul. */
|
|
28
|
+
const validName = (name) => NAME.test(name) && name.toLowerCase() !== 'index' && isSafeFileName(name + '.json');
|
|
29
|
+
const assertName = (name) => { if (!validName(name))
|
|
22
30
|
fail('Invalid account name "' + name + '".', 'Use letters, numbers, - and _ only (max 40), e.g. work or personal.'); };
|
|
23
31
|
const emailOf = (oauthAccount) => {
|
|
24
32
|
const email = oauthAccount?.emailAddress;
|
|
25
33
|
return typeof email === 'string' ? email.slice(0, 200) : null;
|
|
26
34
|
};
|
|
27
|
-
export const readAccount = (storageRoot, name) => (
|
|
35
|
+
export const readAccount = (storageRoot, name) => (validName(name) ? readValid(AccountSchema, accountFile(storageRoot, name)) : null);
|
|
28
36
|
export const defaultAccount = (storageRoot) => readIndex(storageRoot).default ?? null;
|
|
29
37
|
export const activeAccount = (dataPath) => readIndex(storageOf(dataPath)).active?.[dataName(dataPath)] ?? null;
|
|
30
38
|
const setActive = (dataPath, name) => updateIndex(storageOf(dataPath), (i) => ({ ...i, active: { ...i.active, [dataName(dataPath)]: name } }));
|
|
@@ -51,7 +59,7 @@ const readLogin = (claudeDir, globalStateFile) => {
|
|
|
51
59
|
};
|
|
52
60
|
const store = (storageRoot, name, login) => {
|
|
53
61
|
const account = { name, savedAt: new Date().toISOString(), email: emailOf(login.oauthAccount), ...login };
|
|
54
|
-
|
|
62
|
+
writeJsonAtomic(accountFile(storageRoot, name), account);
|
|
55
63
|
return account;
|
|
56
64
|
};
|
|
57
65
|
const sandboxLogin = (dataPath) => {
|
|
@@ -71,6 +79,7 @@ export const saveAccount = (storageRoot, dataPath, name, fromHost) => {
|
|
|
71
79
|
};
|
|
72
80
|
/** Replaces a file that may be a sandbox-planted link instead of writing through it. */
|
|
73
81
|
const writeSandboxJson = (file, value) => {
|
|
82
|
+
assertTestWritable(file);
|
|
74
83
|
if (isLink(file))
|
|
75
84
|
removeLink(file);
|
|
76
85
|
writeJson(file, value);
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Config } from '../core/types.js';
|
|
2
|
+
export declare const commandsDir: (storageRoot: string) => string;
|
|
3
|
+
export interface AgentCommand {
|
|
4
|
+
harness: string;
|
|
5
|
+
command: string;
|
|
6
|
+
real: string;
|
|
7
|
+
}
|
|
8
|
+
/** The agent's own executable on this PC, skipping this tool's forwarding files. */
|
|
9
|
+
export declare const realAgent: (command: string, skipDir: string, pathValue?: string) => string | null;
|
|
10
|
+
/** The agents that get a forwarding command: installed on this PC, and listed (or "auto" = all of them). */
|
|
11
|
+
export declare const agentCommands: (config: Config, storageRoot: string, pathValue?: string) => AgentCommand[];
|
|
12
|
+
/** The .cmd file for one agent (cmd, PowerShell and Windows Terminal all run it). */
|
|
13
|
+
export declare const forwardingCmd: (a: AgentCommand, node: string, entry: string) => string;
|
|
14
|
+
/** The same for Git Bash, which looks for a file without an extension. */
|
|
15
|
+
export declare const forwardingSh: (a: AgentCommand, node: string, entry: string) => string;
|
|
16
|
+
export interface Reconciled {
|
|
17
|
+
on: boolean;
|
|
18
|
+
commands: AgentCommand[];
|
|
19
|
+
changed: boolean;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Makes this PC match agentCommands: on = a forwarding file per installed agent and the folder first on your PATH;
|
|
23
|
+
* off = no files, no PATH entry. Only touches files it wrote and its own PATH entry; does nothing when already right.
|
|
24
|
+
*/
|
|
25
|
+
export declare const reconcileAgentCommands: (config: Config, storageRoot: string, node: string, entry: string) => Reconciled;
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
// (Beta) Agent commands: with agentCommands.enabled, typing an agent's own command on this PC (claude, codex, ...) starts
|
|
2
|
+
// it in a hidden sandbox for the folder you are in and attaches that terminal. Small forwarding files in
|
|
3
|
+
// <storage>\agent-commands go first on YOUR PATH (so they win over npm's and the installers' copies); turning it off
|
|
4
|
+
// removes the files and that one PATH entry. SANDBOX_AGENT_DIRECT=1 runs the real agent on this PC instead.
|
|
5
|
+
import fs from 'node:fs';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import { spawnSync } from 'node:child_process';
|
|
8
|
+
import { ensureDir, fileExists } from '../core/util.js';
|
|
9
|
+
import { writeFileAtomic } from '../shared/rundir.js';
|
|
10
|
+
import { safeRemove } from '../shared/safe-remove.js';
|
|
11
|
+
import { SYSTEM } from '../shared/system.js';
|
|
12
|
+
import { refuseInSelfTest } from './test-guard.js';
|
|
13
|
+
export const commandsDir = (storageRoot) => path.join(storageRoot, 'agent-commands');
|
|
14
|
+
/** Marks files this tool wrote, so only those are ever replaced or removed. */
|
|
15
|
+
const MARKER = 'windows-development-sandbox agent command';
|
|
16
|
+
const EXTENSIONS = ['.exe', '.cmd', '.bat'];
|
|
17
|
+
/** cmd.exe expands % and ^ even inside quotes: such paths cannot go into a forwarding file safely. */
|
|
18
|
+
const cmdSafe = (p) => !/[%"^\r\n]/.test(p);
|
|
19
|
+
/** The agent's own executable on this PC, skipping this tool's forwarding files. */
|
|
20
|
+
export const realAgent = (command, skipDir, pathValue = process.env.PATH ?? process.env.Path ?? '') => {
|
|
21
|
+
const skip = path.resolve(skipDir).toLowerCase();
|
|
22
|
+
const dirs = pathValue.split(path.delimiter).filter((d) => d && path.resolve(d).toLowerCase() !== skip);
|
|
23
|
+
for (const dir of dirs)
|
|
24
|
+
for (const ext of EXTENSIONS) {
|
|
25
|
+
const candidate = path.join(dir, command + ext);
|
|
26
|
+
if (fileExists(candidate))
|
|
27
|
+
return candidate;
|
|
28
|
+
}
|
|
29
|
+
return null;
|
|
30
|
+
};
|
|
31
|
+
/** The agents that get a forwarding command: installed on this PC, and listed (or "auto" = all of them). */
|
|
32
|
+
export const agentCommands = (config, storageRoot, pathValue) => {
|
|
33
|
+
const wanted = config.agentCommands.agents;
|
|
34
|
+
return Object.entries(config.harnesses).flatMap(([harness, h]) => {
|
|
35
|
+
if (wanted !== 'auto' && !wanted.includes(harness))
|
|
36
|
+
return [];
|
|
37
|
+
const command = h.npmBin ?? (h.hostExecutable ? path.parse(h.hostExecutable).name : harness);
|
|
38
|
+
const real = /^[A-Za-z0-9._-]+$/.test(command) ? realAgent(command, commandsDir(storageRoot), pathValue) : null;
|
|
39
|
+
return real && cmdSafe(real) ? [{ harness, command, real }] : [];
|
|
40
|
+
});
|
|
41
|
+
};
|
|
42
|
+
/** The .cmd file for one agent (cmd, PowerShell and Windows Terminal all run it). */
|
|
43
|
+
export const forwardingCmd = (a, node, entry) => [
|
|
44
|
+
'@echo off',
|
|
45
|
+
'rem ' + MARKER + ' (Beta). Turn off: sandbox agent-commands off. Run the real one: set SANDBOX_AGENT_DIRECT=1',
|
|
46
|
+
'if "%SANDBOX_AGENT_DIRECT%"=="1" goto direct',
|
|
47
|
+
'"' + node + '" "' + entry + '" launch --harness ' + a.harness + ' --headless --terminal -- %*',
|
|
48
|
+
'exit /b %errorlevel%',
|
|
49
|
+
':direct',
|
|
50
|
+
'call "' + a.real + '" %*',
|
|
51
|
+
'',
|
|
52
|
+
].join('\r\n');
|
|
53
|
+
/** The same for Git Bash, which looks for a file without an extension. */
|
|
54
|
+
export const forwardingSh = (a, node, entry) => {
|
|
55
|
+
const posix = (p) => p.replace(/^([A-Za-z]):/, (_m, d) => '/' + d.toLowerCase()).replace(/\\/g, '/');
|
|
56
|
+
const q = (s) => "'" + s.replace(/'/g, "'\\''") + "'";
|
|
57
|
+
return [
|
|
58
|
+
'#!/bin/sh',
|
|
59
|
+
'# ' + MARKER + ' (Beta). Turn off: sandbox agent-commands off. Run the real one: SANDBOX_AGENT_DIRECT=1',
|
|
60
|
+
'if [ "$SANDBOX_AGENT_DIRECT" = "1" ]; then exec ' + q(posix(a.real)) + ' "$@"; fi',
|
|
61
|
+
'exec ' + q(posix(node)) + ' ' + q(posix(entry)) + ' launch --harness ' + a.harness + ' --headless --terminal -- "$@"',
|
|
62
|
+
'',
|
|
63
|
+
].join('\n');
|
|
64
|
+
};
|
|
65
|
+
/** Runs a PowerShell script that edits YOUR PATH (HKCU\Environment) unexpanded, then tells Windows it changed. */
|
|
66
|
+
const editUserPath = (dir, add) => {
|
|
67
|
+
refuseInSelfTest('change your PATH');
|
|
68
|
+
const quoted = "'" + dir.replace(/'/g, "''") + "'";
|
|
69
|
+
const script = [
|
|
70
|
+
'$dir = ' + quoted,
|
|
71
|
+
"$key = [Microsoft.Win32.Registry]::CurrentUser.CreateSubKey('Environment')",
|
|
72
|
+
'try {',
|
|
73
|
+
" $raw = [string]$key.GetValue('Path', '', [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames)",
|
|
74
|
+
" $rest = @($raw -split ';' | Where-Object { $_ -and $_.TrimEnd('\\') -ine $dir.TrimEnd('\\') })",
|
|
75
|
+
// First, so these commands win over the agents' own folders (also on YOUR PATH).
|
|
76
|
+
add ? ' $new = (@($dir) + $rest) -join \';\'' : " $new = $rest -join ';'",
|
|
77
|
+
' if ($new -eq $raw.TrimEnd(\';\')) { exit 0 }',
|
|
78
|
+
" if ($new.Length -gt 32000) { Write-Error 'PATH too long'; exit 3 }",
|
|
79
|
+
" $kind = if ($key.GetValueNames() -contains 'Path') { $key.GetValueKind('Path') } else { [Microsoft.Win32.RegistryValueKind]::ExpandString }",
|
|
80
|
+
" $key.SetValue('Path', $new, $kind)",
|
|
81
|
+
'} finally { $key.Close() }',
|
|
82
|
+
"Add-Type -Namespace WdsAgents -Name Native -MemberDefinition '[System.Runtime.InteropServices.DllImport(\"user32.dll\", CharSet = System.Runtime.InteropServices.CharSet.Auto)] public static extern System.IntPtr SendMessageTimeout(System.IntPtr hWnd, uint msg, System.UIntPtr wParam, string lParam, uint flags, uint timeout, out System.UIntPtr result);'",
|
|
83
|
+
'$ignored = [UIntPtr]::Zero',
|
|
84
|
+
"[void][WdsAgents.Native]::SendMessageTimeout([IntPtr]0xffff, 0x1A, [UIntPtr]::Zero, 'Environment', 2, 5000, [ref]$ignored)",
|
|
85
|
+
].join('\n');
|
|
86
|
+
const result = spawnSync(SYSTEM.powershell(), ['-NoProfile', '-NonInteractive', '-OutputFormat', 'Text', '-Command', script], { encoding: 'utf8', windowsHide: true });
|
|
87
|
+
if (result.status === 3)
|
|
88
|
+
throw new Error('Your PATH is too long to add ' + dir + '.');
|
|
89
|
+
if (result.status !== 0)
|
|
90
|
+
throw new Error('Could not ' + (add ? 'add ' + dir + ' to' : 'remove ' + dir + ' from') + ' your PATH: ' + (result.stderr || result.error?.message || 'exit ' + result.status).trim());
|
|
91
|
+
};
|
|
92
|
+
const ownFiles = (dir) => fs.existsSync(dir)
|
|
93
|
+
? fs.readdirSync(dir).map((f) => path.join(dir, f)).filter((f) => { try {
|
|
94
|
+
return fs.lstatSync(f).isFile() && fs.readFileSync(f, 'utf8').includes(MARKER);
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
return false;
|
|
98
|
+
} })
|
|
99
|
+
: [];
|
|
100
|
+
/**
|
|
101
|
+
* Makes this PC match agentCommands: on = a forwarding file per installed agent and the folder first on your PATH;
|
|
102
|
+
* off = no files, no PATH entry. Only touches files it wrote and its own PATH entry; does nothing when already right.
|
|
103
|
+
*/
|
|
104
|
+
export const reconcileAgentCommands = (config, storageRoot, node, entry) => {
|
|
105
|
+
const dir = commandsDir(storageRoot);
|
|
106
|
+
if (!config.agentCommands.enabled) {
|
|
107
|
+
if (!fs.existsSync(dir))
|
|
108
|
+
return { on: false, commands: [], changed: false };
|
|
109
|
+
for (const f of ownFiles(dir))
|
|
110
|
+
fs.rmSync(f, { force: true });
|
|
111
|
+
editUserPath(dir, false);
|
|
112
|
+
if (fs.readdirSync(dir).length === 0)
|
|
113
|
+
safeRemove(dir, storageRoot);
|
|
114
|
+
return { on: false, commands: [], changed: true };
|
|
115
|
+
}
|
|
116
|
+
if (!cmdSafe(node) || !cmdSafe(entry))
|
|
117
|
+
throw new Error('This tool is installed in a folder whose path has % " or ^, which cannot go into a command file safely.');
|
|
118
|
+
const commands = agentCommands(config, storageRoot);
|
|
119
|
+
ensureDir(dir);
|
|
120
|
+
const wanted = new Map();
|
|
121
|
+
for (const a of commands) {
|
|
122
|
+
wanted.set(path.join(dir, a.command + '.cmd'), forwardingCmd(a, node, entry));
|
|
123
|
+
wanted.set(path.join(dir, a.command), forwardingSh(a, node, entry));
|
|
124
|
+
}
|
|
125
|
+
let changed = false;
|
|
126
|
+
for (const f of ownFiles(dir))
|
|
127
|
+
if (!wanted.has(f)) {
|
|
128
|
+
fs.rmSync(f, { force: true });
|
|
129
|
+
changed = true;
|
|
130
|
+
}
|
|
131
|
+
for (const [file, text] of wanted) {
|
|
132
|
+
const current = fileExists(file) ? fs.readFileSync(file, 'utf8') : null;
|
|
133
|
+
// A file of the same name that this tool did not write is never replaced.
|
|
134
|
+
if (current !== null && !current.includes(MARKER))
|
|
135
|
+
continue;
|
|
136
|
+
if (current !== text) {
|
|
137
|
+
writeFileAtomic(file, text);
|
|
138
|
+
changed = true;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
const onPath = (process.env.PATH ?? '').split(path.delimiter).some((d) => path.resolve(d).toLowerCase() === path.resolve(dir).toLowerCase());
|
|
142
|
+
if (changed || !onPath)
|
|
143
|
+
editUserPath(dir, true);
|
|
144
|
+
return { on: true, commands, changed: changed || !onPath };
|
|
145
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import os from 'node:os';
|
|
2
|
+
import type { SessionStatus } from '../core/types.js';
|
|
3
|
+
import { type Viewers } from '../shared/pty-client.js';
|
|
4
|
+
/**
|
|
5
|
+
* True only for IPv4 addresses inside the subnet of one of this PC's Hyper-V adapters ("vEthernet ..."), which is
|
|
6
|
+
* where Windows Sandbox lives. Stops a compromised sandbox from pointing the host (and your keystrokes) anywhere else.
|
|
7
|
+
*/
|
|
8
|
+
export declare const isSandboxAddress: (ip: string, interfaces?: NodeJS.Dict<os.NetworkInterfaceInfo[]>) => boolean;
|
|
9
|
+
/** Who is watching a session right now (closed terminals do not count); null when it cannot be asked. */
|
|
10
|
+
export declare const sessionViewers: (session: SessionStatus) => Promise<Viewers | null>;
|
|
11
|
+
/**
|
|
12
|
+
* The newest running session of this agent in exactly this folder that no terminal on this PC is attached to, so
|
|
13
|
+
* sandbox claude -t picks it back up instead of starting another. A session that cannot answer is never reused.
|
|
14
|
+
*/
|
|
15
|
+
export declare const findUnwatchedSession: (dataPath: string, harness: string, cwd: string) => Promise<SessionStatus | null>;
|
|
16
|
+
/** Bridges this terminal to a session. Returns the agent's exit code, or 0 when the user detaches. */
|
|
17
|
+
export declare const attachTerminal: (session: SessionStatus) => Promise<number>;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// Attaching this terminal to an agent session inside the sandbox. Only addresses on this PC's own Hyper-V network are
|
|
2
|
+
// trusted, and everything the sandbox reports is printed through safeText.
|
|
3
|
+
import net from 'node:net';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import { fail, safeText, sleep } from '../core/util.js';
|
|
6
|
+
import { connect, DETACH_KEY, queryViewers, runTerminalClient } from '../shared/pty-client.js';
|
|
7
|
+
import { currentSessions } from './channel.js';
|
|
8
|
+
import { insideSandbox } from './platform.js';
|
|
9
|
+
const ipToInt = (ip) => ip.split('.').reduce((n, part) => n * 256 + Number(part), 0);
|
|
10
|
+
/**
|
|
11
|
+
* True only for IPv4 addresses inside the subnet of one of this PC's Hyper-V adapters ("vEthernet ..."), which is
|
|
12
|
+
* where Windows Sandbox lives. Stops a compromised sandbox from pointing the host (and your keystrokes) anywhere else.
|
|
13
|
+
*/
|
|
14
|
+
export const isSandboxAddress = (ip, interfaces = os.networkInterfaces()) => {
|
|
15
|
+
if (!net.isIPv4(ip))
|
|
16
|
+
return false;
|
|
17
|
+
// WSL's adapter is a Hyper-V adapter too, but no sandbox lives there: keystrokes must never go to a WSL VM.
|
|
18
|
+
return Object.entries(interfaces).some(([name, list]) => /^vEthernet/i.test(name) && !/WSL/i.test(name) && (list ?? []).some((i) => {
|
|
19
|
+
if (i.family !== 'IPv4' || i.internal)
|
|
20
|
+
return false;
|
|
21
|
+
const mask = ipToInt(i.netmask);
|
|
22
|
+
return mask !== 0 && (ipToInt(i.address) & mask) === (ipToInt(ip) & mask) && i.address !== ip;
|
|
23
|
+
}));
|
|
24
|
+
};
|
|
25
|
+
/** Where this PC may connect for a session: only addresses on its own Hyper-V network (null if there are none). */
|
|
26
|
+
const hostEndpoint = (session) => {
|
|
27
|
+
// Inside the sandbox every session is on loopback; on the PC only addresses on its Hyper-V network are trusted.
|
|
28
|
+
const addresses = insideSandbox() ? ['127.0.0.1'] : session.attach?.addresses.filter((a) => isSandboxAddress(a)) ?? [];
|
|
29
|
+
return session.attach && addresses.length ? { ...session.attach, addresses } : null;
|
|
30
|
+
};
|
|
31
|
+
/** Who is watching a session right now (closed terminals do not count); null when it cannot be asked. */
|
|
32
|
+
export const sessionViewers = (session) => {
|
|
33
|
+
const endpoint = hostEndpoint(session);
|
|
34
|
+
return endpoint ? queryViewers(endpoint) : Promise.resolve(null);
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* The newest running session of this agent in exactly this folder that no terminal on this PC is attached to, so
|
|
38
|
+
* sandbox claude -t picks it back up instead of starting another. A session that cannot answer is never reused.
|
|
39
|
+
*/
|
|
40
|
+
export const findUnwatchedSession = async (dataPath, harness, cwd) => {
|
|
41
|
+
const candidates = currentSessions(dataPath).filter((s) => s.harness === harness && s.cwd.toLowerCase() === cwd.toLowerCase()).reverse().slice(0, 8);
|
|
42
|
+
for (const session of candidates)
|
|
43
|
+
if ((await sessionViewers(session))?.remote === 0)
|
|
44
|
+
return session;
|
|
45
|
+
return null;
|
|
46
|
+
};
|
|
47
|
+
/** Bridges this terminal to a session. Returns the agent's exit code, or 0 when the user detaches. */
|
|
48
|
+
export const attachTerminal = async (session) => {
|
|
49
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY)
|
|
50
|
+
fail('--terminal / attach needs an interactive terminal.', 'Run it directly in Windows Terminal or PowerShell, not through a pipe or script.');
|
|
51
|
+
if (!session.attach)
|
|
52
|
+
fail('This session cannot be attached to: networking or attach is disabled for this sandbox.', 'Set sandbox.networking and attach.enabled to true, then start a new sandbox.');
|
|
53
|
+
const endpoint = hostEndpoint(session) ?? fail('Refusing to connect: the sandbox reported no address on this PC\'s Hyper-V (vEthernet) network (' + session.attach.addresses.join(', ') + ').', 'Use the sandbox window instead (sbx-show); if this keeps happening, run sandbox doctor.');
|
|
54
|
+
const socket = await connect(endpoint);
|
|
55
|
+
console.log('Attaching to ' + safeText(session.harness) + ' session ' + session.id + ' (' + safeText(session.cwd) + '). Detach any time with Ctrl+]');
|
|
56
|
+
await sleep(400);
|
|
57
|
+
const result = await runTerminalClient(socket, session.attach.token, { detachKey: DETACH_KEY });
|
|
58
|
+
switch (result.reason) {
|
|
59
|
+
case 'detach':
|
|
60
|
+
console.log('Detached. The session keeps running; reattach with: sandbox attach ' + session.id);
|
|
61
|
+
return 0;
|
|
62
|
+
case 'exit':
|
|
63
|
+
console.log(safeText(session.harness) + ' exited with code ' + result.exitCode + '.');
|
|
64
|
+
return result.exitCode;
|
|
65
|
+
case 'error': fail('The sandbox refused the connection: ' + result.message, 'Try sandbox attach again; the session may have restarted.');
|
|
66
|
+
default:
|
|
67
|
+
console.log('Connection to the sandbox closed.');
|
|
68
|
+
return 1;
|
|
69
|
+
}
|
|
70
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
declare const AuditSettings: z.ZodObject<{
|
|
3
|
+
enabled: z.ZodBoolean;
|
|
4
|
+
days: z.ZodNumber;
|
|
5
|
+
maxMb: z.ZodNumber;
|
|
6
|
+
processes: z.ZodBoolean;
|
|
7
|
+
files: z.ZodBoolean;
|
|
8
|
+
projects: z.ZodArray<z.ZodString>;
|
|
9
|
+
}, z.core.$strip>;
|
|
10
|
+
export type AuditSettings = z.output<typeof AuditSettings>;
|
|
11
|
+
/** Host-only: the audit settings of the running sandbox (written at launch, read by the watcher). */
|
|
12
|
+
export declare const saveAuditSettings: (storageRoot: string, dataPath: string, settings: AuditSettings) => void;
|
|
13
|
+
export declare const readAuditSettings: (storageRoot: string, dataPath: string) => AuditSettings | null;
|
|
14
|
+
export declare class AuditCollector {
|
|
15
|
+
private readonly dataPath;
|
|
16
|
+
private readonly settings;
|
|
17
|
+
private readonly log;
|
|
18
|
+
private pending;
|
|
19
|
+
private sessions;
|
|
20
|
+
private watchers;
|
|
21
|
+
constructor(dataPath: string, storageRoot: string, settings: AuditSettings);
|
|
22
|
+
private add;
|
|
23
|
+
/** Starts watching the projects' files (when audit.files). */
|
|
24
|
+
start(): void;
|
|
25
|
+
/** Programs the sandbox reported: taken in (checked) and deleted. */
|
|
26
|
+
private takeGuestReports;
|
|
27
|
+
private trackSessions;
|
|
28
|
+
guard(detail: string): void;
|
|
29
|
+
/** Writes what came in since the last call (repeats folded); call every few seconds. */
|
|
30
|
+
flush(): void;
|
|
31
|
+
stop(): void;
|
|
32
|
+
maintain(): void;
|
|
33
|
+
}
|
|
34
|
+
export {};
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
// What goes into the audit log, from the background watcher while the sandbox runs:
|
|
2
|
+
// - programs started in the sandbox: the .jsonl files it leaves in run\audit\ (untrusted: links refused, each file
|
|
3
|
+
// and line size-capped, every line checked and forced to kind "proc"), taken in and deleted;
|
|
4
|
+
// - files changed in the projects: watched from this PC (the sandbox cannot hide or fake these);
|
|
5
|
+
// - sessions starting and ending, and what the repo guard caught.
|
|
6
|
+
import fs from 'node:fs';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import { z } from 'zod';
|
|
9
|
+
import { assertNoLinks } from '../../core/util.js';
|
|
10
|
+
import { listSessions, readValid, runPaths, writeJsonAtomic } from '../../shared/rundir.js';
|
|
11
|
+
import { AuditEvent, AuditLog, fold } from './log.js';
|
|
12
|
+
const MAX_FILES_PER_PASS = 200;
|
|
13
|
+
const MAX_FILE_BYTES = 8 * 1024 * 1024;
|
|
14
|
+
/** Paths in a project not worth a line each (git's own bookkeeping, dependency folders). */
|
|
15
|
+
const QUIET = /(^|[\\/])(\.git[\\/](objects|logs|refs|index\.lock|FETCH_HEAD|ORIG_HEAD)|node_modules[\\/]|target[\\/]|\.venv[\\/]|__pycache__[\\/])/i;
|
|
16
|
+
const AuditSettings = z.object({
|
|
17
|
+
enabled: z.boolean(), days: z.number().int().min(1).max(365), maxMb: z.number().int().min(5).max(5000), processes: z.boolean(), files: z.boolean(),
|
|
18
|
+
projects: z.array(z.string().max(1024)).max(64),
|
|
19
|
+
});
|
|
20
|
+
const settingsFile = (storageRoot, dataPath) => path.join(storageRoot, 'sync', path.basename(dataPath) + '-audit.json');
|
|
21
|
+
/** Host-only: the audit settings of the running sandbox (written at launch, read by the watcher). */
|
|
22
|
+
export const saveAuditSettings = (storageRoot, dataPath, settings) => writeJsonAtomic(settingsFile(storageRoot, dataPath), settings);
|
|
23
|
+
export const readAuditSettings = (storageRoot, dataPath) => readValid(AuditSettings, settingsFile(storageRoot, dataPath));
|
|
24
|
+
export class AuditCollector {
|
|
25
|
+
dataPath;
|
|
26
|
+
settings;
|
|
27
|
+
log;
|
|
28
|
+
pending = [];
|
|
29
|
+
sessions = new Map();
|
|
30
|
+
watchers = [];
|
|
31
|
+
constructor(dataPath, storageRoot, settings) {
|
|
32
|
+
this.dataPath = dataPath;
|
|
33
|
+
this.settings = settings;
|
|
34
|
+
this.log = new AuditLog(AuditLog.dirFor(storageRoot, dataPath), settings);
|
|
35
|
+
}
|
|
36
|
+
add(e) { this.pending.push({ t: new Date().toISOString(), ...e }); }
|
|
37
|
+
/** Starts watching the projects' files (when audit.files). */
|
|
38
|
+
start() {
|
|
39
|
+
this.add({ k: 'note', p: 'sandbox started; audit: ' + [this.settings.processes && 'programs', this.settings.files && 'files', 'sessions'].filter(Boolean).join(', ') });
|
|
40
|
+
if (!this.settings.files)
|
|
41
|
+
return;
|
|
42
|
+
for (const project of this.settings.projects) {
|
|
43
|
+
try {
|
|
44
|
+
this.watchers.push(fs.watch(project, { recursive: true }, (event, file) => {
|
|
45
|
+
const rel = file ? String(file) : '';
|
|
46
|
+
if (!rel || QUIET.test(rel))
|
|
47
|
+
return;
|
|
48
|
+
this.add({ k: 'file', a: event === 'rename' ? 'rename' : 'change', p: rel.replace(/\\/g, '/').slice(0, 1024), w: project });
|
|
49
|
+
}));
|
|
50
|
+
}
|
|
51
|
+
catch { /* a project that is gone */ }
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/** Programs the sandbox reported: taken in (checked) and deleted. */
|
|
55
|
+
takeGuestReports() {
|
|
56
|
+
if (!this.settings.processes)
|
|
57
|
+
return;
|
|
58
|
+
const dir = runPaths(this.dataPath).audit;
|
|
59
|
+
assertNoLinks(this.dataPath, dir);
|
|
60
|
+
let names = [];
|
|
61
|
+
try {
|
|
62
|
+
names = fs.readdirSync(dir).filter((n) => /^[\w-]+\.jsonl$/.test(n)).sort().slice(0, MAX_FILES_PER_PASS);
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
for (const name of names) {
|
|
68
|
+
const file = path.join(dir, name);
|
|
69
|
+
try {
|
|
70
|
+
const stat = fs.lstatSync(file);
|
|
71
|
+
if (stat.isFile() && stat.size <= MAX_FILE_BYTES) {
|
|
72
|
+
for (const line of fs.readFileSync(file, 'utf8').split('\n')) {
|
|
73
|
+
if (!line.trim() || line.length > 8192)
|
|
74
|
+
continue;
|
|
75
|
+
let raw;
|
|
76
|
+
try {
|
|
77
|
+
raw = JSON.parse(line);
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
const e = AuditEvent.safeParse(raw);
|
|
83
|
+
if (e.success)
|
|
84
|
+
this.pending.push({ ...e.data, k: 'proc', w: undefined, a: undefined });
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
finally {
|
|
89
|
+
fs.rmSync(file, { force: true });
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
trackSessions() {
|
|
94
|
+
const now = new Map(listSessions(this.dataPath).map((s) => [s.id, s.phase]));
|
|
95
|
+
for (const s of listSessions(this.dataPath)) {
|
|
96
|
+
const before = this.sessions.get(s.id);
|
|
97
|
+
if (!before && s.phase === 'running')
|
|
98
|
+
this.add({ k: 'session', a: 'start', p: s.harness, w: s.cwd, c: s.id });
|
|
99
|
+
if (before === 'running' && (s.phase === 'exited' || s.phase === 'failed'))
|
|
100
|
+
this.add({ k: 'session', a: 'end', p: s.harness, w: s.cwd, c: s.id + (s.exitCode !== undefined ? ' exit ' + s.exitCode : '') });
|
|
101
|
+
}
|
|
102
|
+
this.sessions = now;
|
|
103
|
+
}
|
|
104
|
+
guard(detail) { this.add({ k: 'guard', p: detail.slice(0, 1024) }); }
|
|
105
|
+
/** Writes what came in since the last call (repeats folded); call every few seconds. */
|
|
106
|
+
flush() {
|
|
107
|
+
try {
|
|
108
|
+
this.takeGuestReports();
|
|
109
|
+
}
|
|
110
|
+
catch { /* next pass */ }
|
|
111
|
+
try {
|
|
112
|
+
this.trackSessions();
|
|
113
|
+
}
|
|
114
|
+
catch { /* next pass */ }
|
|
115
|
+
const events = this.pending.sort((a, b) => a.t.localeCompare(b.t));
|
|
116
|
+
this.pending = [];
|
|
117
|
+
this.log.append(fold(events));
|
|
118
|
+
}
|
|
119
|
+
stop() {
|
|
120
|
+
this.watchers.forEach((w) => w.close());
|
|
121
|
+
this.add({ k: 'note', p: 'sandbox closed' });
|
|
122
|
+
this.flush();
|
|
123
|
+
this.log.maintain();
|
|
124
|
+
}
|
|
125
|
+
maintain() { this.log.maintain(); }
|
|
126
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/** One event. k: proc (a program started), file (changed in a project), session, guard (repo guard), note. */
|
|
3
|
+
export declare const AuditEvent: z.ZodObject<{
|
|
4
|
+
t: z.ZodString;
|
|
5
|
+
k: z.ZodEnum<{
|
|
6
|
+
file: "file";
|
|
7
|
+
session: "session";
|
|
8
|
+
proc: "proc";
|
|
9
|
+
guard: "guard";
|
|
10
|
+
note: "note";
|
|
11
|
+
}>;
|
|
12
|
+
p: z.ZodOptional<z.ZodString>;
|
|
13
|
+
c: z.ZodOptional<z.ZodString>;
|
|
14
|
+
pp: z.ZodOptional<z.ZodString>;
|
|
15
|
+
a: z.ZodOptional<z.ZodString>;
|
|
16
|
+
w: z.ZodOptional<z.ZodString>;
|
|
17
|
+
n: z.ZodOptional<z.ZodNumber>;
|
|
18
|
+
}, z.core.$strip>;
|
|
19
|
+
export type AuditEvent = z.output<typeof AuditEvent>;
|
|
20
|
+
export declare class AuditLog {
|
|
21
|
+
readonly dir: string;
|
|
22
|
+
private readonly limits;
|
|
23
|
+
constructor(dir: string, limits: {
|
|
24
|
+
days: number;
|
|
25
|
+
maxMb: number;
|
|
26
|
+
});
|
|
27
|
+
/** <storage>\audit\<data set name>. */
|
|
28
|
+
static dirFor(storageRoot: string, dataPath: string): string;
|
|
29
|
+
private dayFile;
|
|
30
|
+
append(events: AuditEvent[]): void;
|
|
31
|
+
/** Compresses finished days, then deletes days past the limits. Cheap when there is nothing to do. */
|
|
32
|
+
maintain(today?: string): void;
|
|
33
|
+
/** Events of the last `days` days (newest last), optionally of some kinds and containing a text. */
|
|
34
|
+
read(o?: {
|
|
35
|
+
days?: number;
|
|
36
|
+
kinds?: AuditEvent['k'][];
|
|
37
|
+
grep?: string;
|
|
38
|
+
}): AuditEvent[];
|
|
39
|
+
/** Total size on disk. */
|
|
40
|
+
bytes(): number;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Folds repeats: the same event (kind, program/path, command line, action, project) within one minute becomes one line
|
|
44
|
+
* with n = how many times; after `cap` distinct lines a minute, the rest of that minute is one "note" line.
|
|
45
|
+
*/
|
|
46
|
+
export declare const fold: (events: AuditEvent[], cap?: number) => AuditEvent[];
|