windows-development-sandbox 1.5.0 → 1.7.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/README.md +1 -0
- package/config.example.json +17 -1
- package/dist/cli/args.d.ts +13 -2
- package/dist/cli/args.js +95 -24
- package/dist/cli/commands/accounts.js +45 -17
- package/dist/cli/commands/context.d.ts +2 -0
- package/dist/cli/commands/context.js +9 -2
- package/dist/cli/commands/data/clean.js +32 -9
- package/dist/cli/commands/index.d.ts +1 -0
- package/dist/cli/commands/index.js +1 -0
- package/dist/cli/commands/launch/add-root.d.ts +3 -2
- package/dist/cli/commands/launch/add-root.js +11 -10
- package/dist/cli/commands/launch/launch.js +57 -4
- package/dist/cli/commands/launch/prepare.js +3 -1
- package/dist/cli/commands/maintenance/audit.js +1 -1
- package/dist/cli/commands/maintenance/clean.d.ts +1 -1
- package/dist/cli/commands/maintenance/clean.js +17 -3
- package/dist/cli/commands/maintenance/config.d.ts +1 -1
- package/dist/cli/commands/maintenance/config.js +112 -10
- package/dist/cli/commands/maintenance/doctor.d.ts +1 -1
- package/dist/cli/commands/maintenance/doctor.js +2 -4
- package/dist/cli/commands/maintenance/migrate.d.ts +2 -1
- package/dist/cli/commands/maintenance/migrate.js +19 -4
- package/dist/cli/commands/maintenance/sync.js +2 -0
- package/dist/cli/commands/maintenance/update.d.ts +2 -0
- package/dist/cli/commands/maintenance/update.js +27 -4
- package/dist/cli/commands/projects/here.js +12 -7
- package/dist/cli/commands/projects/projects.js +26 -17
- package/dist/cli/commands/registry.js +7 -0
- package/dist/cli/commands/repair/checks/agents.d.ts +2 -0
- package/dist/cli/commands/repair/checks/agents.js +70 -0
- package/dist/cli/commands/repair/checks/common.d.ts +8 -0
- package/dist/cli/commands/repair/checks/common.js +22 -0
- package/dist/cli/commands/repair/checks/data.d.ts +2 -0
- package/dist/cli/commands/repair/checks/data.js +241 -0
- package/dist/cli/commands/repair/checks/index.d.ts +2 -0
- package/dist/cli/commands/repair/checks/index.js +20 -0
- package/dist/cli/commands/repair/checks/install.d.ts +5 -0
- package/dist/cli/commands/repair/checks/install.js +318 -0
- package/dist/cli/commands/repair/checks/machine.d.ts +2 -0
- package/dist/cli/commands/repair/checks/machine.js +232 -0
- package/dist/cli/commands/repair/checks/network.d.ts +6 -0
- package/dist/cli/commands/repair/checks/network.js +60 -0
- package/dist/cli/commands/repair/checks/pc.d.ts +4 -0
- package/dist/cli/commands/repair/checks/pc.js +55 -0
- package/dist/cli/commands/repair/checks/sandbox.d.ts +2 -0
- package/dist/cli/commands/repair/checks/sandbox.js +267 -0
- package/dist/cli/commands/repair/checks/settings.d.ts +2 -0
- package/dist/cli/commands/repair/checks/settings.js +169 -0
- package/dist/cli/commands/repair/checks/vpn.d.ts +2 -0
- package/dist/cli/commands/repair/checks/vpn.js +53 -0
- package/dist/cli/commands/repair/engine.d.ts +32 -0
- package/dist/cli/commands/repair/engine.js +127 -0
- package/dist/cli/commands/repair/env.d.ts +7 -0
- package/dist/cli/commands/repair/env.js +49 -0
- package/dist/cli/commands/repair/index.d.ts +1 -9
- package/dist/cli/commands/repair/index.js +2 -54
- package/dist/cli/commands/repair/log.d.ts +17 -0
- package/dist/cli/commands/repair/log.js +82 -0
- package/dist/cli/commands/repair/printer.d.ts +32 -0
- package/dist/cli/commands/repair/printer.js +132 -0
- package/dist/cli/commands/repair/run.d.ts +33 -0
- package/dist/cli/commands/repair/run.js +133 -0
- package/dist/cli/commands/repair/types.d.ts +97 -9
- package/dist/cli/commands/repair/types.js +11 -7
- package/dist/cli/commands/resume.js +2 -2
- package/dist/cli/commands/sessions/list.js +5 -2
- package/dist/cli/commands/sessions/status.d.ts +2 -0
- package/dist/cli/commands/sessions/status.js +27 -6
- package/dist/cli/commands/setup/index.d.ts +5 -0
- package/dist/cli/commands/setup/index.js +25 -1
- package/dist/cli/commands/storage/index.d.ts +2 -0
- package/dist/cli/commands/storage/index.js +2 -0
- package/dist/cli/commands/storage/offer.d.ts +3 -0
- package/dist/cli/commands/storage/offer.js +85 -0
- package/dist/cli/commands/storage/storage.d.ts +8 -0
- package/dist/cli/commands/storage/storage.js +171 -0
- package/dist/cli/commands/user-config.js +1 -1
- package/dist/cli/commands/vpn.d.ts +4 -0
- package/dist/cli/commands/vpn.js +75 -0
- package/dist/cli/commands/web.d.ts +4 -0
- package/dist/cli/commands/web.js +95 -0
- package/dist/cli/history.js +1 -1
- package/dist/cli/index.js +46 -3
- package/dist/cli/manifest.d.ts +303 -29
- package/dist/cli/manifest.js +173 -43
- package/dist/cli/menu/add-agent.d.ts +47 -0
- package/dist/cli/menu/add-agent.js +168 -0
- package/dist/cli/menu/apps.d.ts +31 -2
- package/dist/cli/menu/apps.js +44 -5
- package/dist/cli/menu/command-fields.d.ts +18 -0
- package/dist/cli/menu/command-fields.js +61 -0
- package/dist/cli/menu/command-form.d.ts +22 -0
- package/dist/cli/menu/command-form.js +169 -46
- package/dist/cli/menu/data-folder.d.ts +18 -0
- package/dist/cli/menu/data-folder.js +99 -0
- package/dist/cli/menu/menu.d.ts +1 -1
- package/dist/cli/menu/menu.js +11 -8
- package/dist/cli/menu/pages/accounts-page.js +55 -24
- package/dist/cli/menu/pages/agents-page.d.ts +66 -0
- package/dist/cli/menu/pages/agents-page.js +338 -0
- package/dist/cli/menu/pages/apps-page.js +1 -2
- package/dist/cli/menu/pages/list-page.d.ts +16 -3
- package/dist/cli/menu/pages/list-page.js +40 -8
- package/dist/cli/menu/pages/maintenance-page.d.ts +9 -0
- package/dist/cli/menu/pages/maintenance-page.js +161 -72
- package/dist/cli/menu/pages/sandbox-page.d.ts +3 -0
- package/dist/cli/menu/pages/sandbox-page.js +132 -43
- package/dist/cli/menu/pages/sessions-page.d.ts +4 -0
- package/dist/cli/menu/pages/sessions-page.js +41 -25
- package/dist/cli/menu/pages/settings-page.d.ts +8 -4
- package/dist/cli/menu/pages/settings-page.js +36 -13
- package/dist/cli/menu/settings/categories.d.ts +2 -0
- package/dist/cli/menu/settings/categories.js +19 -44
- package/dist/cli/menu/settings/collections/collection-editor.d.ts +7 -1
- package/dist/cli/menu/settings/collections/collection-editor.js +12 -8
- package/dist/cli/menu/settings/collections/map-editor.js +5 -1
- package/dist/cli/menu/settings/collections/object-list-editor.js +2 -2
- package/dist/cli/menu/settings/collections/object-map-editor.js +2 -2
- package/dist/cli/menu/settings/collections/string-list-editor.d.ts +11 -1
- package/dist/cli/menu/settings/collections/string-list-editor.js +23 -0
- package/dist/cli/menu/settings/index.d.ts +1 -1
- package/dist/cli/menu/settings/index.js +1 -1
- package/dist/cli/menu/settings/object-editor.d.ts +5 -1
- package/dist/cli/menu/settings/object-editor.js +15 -4
- package/dist/cli/menu/settings/path-kind.d.ts +20 -0
- package/dist/cli/menu/settings/path-kind.js +44 -0
- package/dist/cli/menu/settings/schema-node.d.ts +9 -0
- package/dist/cli/menu/settings/schema-node.js +26 -0
- package/dist/cli/menu/settings/value-editor.d.ts +5 -0
- package/dist/cli/menu/settings/value-editor.js +51 -18
- package/dist/cli/menu/several.js +7 -5
- package/dist/cli/menu/shell.d.ts +37 -10
- package/dist/cli/menu/shell.js +90 -10
- package/dist/cli/menu/vpn-login.d.ts +16 -0
- package/dist/cli/menu/vpn-login.js +66 -0
- package/dist/cli/output/print.d.ts +2 -0
- package/dist/cli/output/print.js +8 -1
- package/dist/cli/services/accounts-service.d.ts +2 -0
- package/dist/cli/services/accounts-service.js +2 -2
- package/dist/cli/services/health-service.d.ts +0 -6
- package/dist/cli/services/health-service.js +5 -42
- package/dist/cli/services/index.d.ts +3 -1
- package/dist/cli/services/index.js +3 -1
- package/dist/cli/services/migrate-service.js +1 -1
- package/dist/cli/services/update-service.d.ts +2 -0
- package/dist/cli/services/update-service.js +13 -6
- package/dist/cli/services/vpn-service.d.ts +28 -0
- package/dist/cli/services/vpn-service.js +46 -0
- package/dist/cli/services/web-service.d.ts +22 -0
- package/dist/cli/services/web-service.js +111 -0
- package/dist/cli/tui/app.d.ts +6 -3
- package/dist/cli/tui/app.js +12 -5
- package/dist/cli/tui/dialogs/path-listing.d.ts +43 -0
- package/dist/cli/tui/dialogs/path-listing.js +127 -0
- package/dist/cli/tui/dialogs/path-picker.d.ts +67 -0
- package/dist/cli/tui/dialogs/path-picker.js +423 -0
- package/dist/cli/tui/dialogs/text-screen.d.ts +6 -0
- package/dist/cli/tui/dialogs/text-screen.js +10 -3
- package/dist/cli/tui/frame.d.ts +7 -10
- package/dist/cli/tui/frame.js +19 -10
- package/dist/cli/tui/index.d.ts +4 -3
- package/dist/cli/tui/index.js +2 -1
- package/dist/cli/tui/list/list-screen.d.ts +14 -2
- package/dist/cli/tui/list/list-screen.js +68 -32
- package/dist/cli/tui/list/list-types.d.ts +8 -4
- package/dist/cli/tui/nav.d.ts +29 -0
- package/dist/cli/tui/nav.js +67 -0
- package/dist/cli/validate.d.ts +1 -1
- package/dist/cli/validate.js +3 -3
- package/dist/cli/version.d.ts +2 -0
- package/dist/cli/version.js +11 -0
- package/dist/core/config/defaults.js +5 -1
- package/dist/core/config/loader.js +12 -2
- package/dist/core/local-config/overrides.js +1 -0
- package/dist/core/prompt.d.ts +19 -0
- package/dist/core/prompt.js +43 -3
- package/dist/core/schema/common.d.ts +6 -0
- package/dist/core/schema/common.js +8 -0
- package/dist/core/schema/config.d.ts +23 -0
- package/dist/core/schema/config.js +10 -1
- package/dist/core/schema/harness.d.ts +1 -0
- package/dist/core/schema/harness.js +8 -2
- package/dist/core/schema/index.d.ts +3 -1
- package/dist/core/schema/index.js +3 -1
- package/dist/core/schema/profile.d.ts +1 -0
- package/dist/core/schema/profile.js +3 -0
- package/dist/core/schema/redact.d.ts +13 -0
- package/dist/core/schema/redact.js +75 -0
- package/dist/core/schema/sandbox.js +1 -0
- package/dist/core/schema/vpn.d.ts +17 -0
- package/dist/core/schema/vpn.js +22 -0
- package/dist/core/schema/web.d.ts +6 -0
- package/dist/core/schema/web.js +14 -0
- package/dist/core/types.d.ts +26 -0
- package/dist/guest/agents/agent-files.d.ts +49 -0
- package/dist/guest/agents/agent-files.js +63 -0
- package/dist/guest/agents/agent-installer.js +2 -9
- package/dist/guest/agents/local-state.d.ts +12 -0
- package/dist/guest/agents/local-state.js +198 -0
- package/dist/guest/agents/sbx-agent.d.ts +2 -0
- package/dist/guest/agents/sbx-agent.js +35 -0
- package/dist/guest/boot/boot.d.ts +4 -0
- package/dist/guest/boot/boot.js +50 -11
- package/dist/guest/daemon/daemon.js +4 -1
- package/dist/guest/daemon/exec.js +2 -1
- package/dist/guest/daemon/idle-shutdown.js +2 -0
- package/dist/guest/daemon/sessions.js +4 -1
- package/dist/guest/entries/agent.d.ts +1 -0
- package/dist/guest/entries/agent.js +10 -0
- package/dist/guest/entries/daemon.js +2 -0
- package/dist/guest/runtime/csc.d.ts +1 -0
- package/dist/guest/runtime/csc.js +1 -0
- package/dist/guest/runtime/secrets.d.ts +12 -0
- package/dist/guest/runtime/secrets.js +63 -0
- package/dist/guest/setup/claude-profile.d.ts +2 -1
- package/dist/guest/setup/claude-profile.js +12 -1
- package/dist/guest/setup/environment.d.ts +6 -0
- package/dist/guest/setup/environment.js +56 -6
- package/dist/guest/setup/path-plan.d.ts +32 -0
- package/dist/guest/setup/path-plan.js +67 -0
- package/dist/guest/setup/shell-path.d.ts +1 -0
- package/dist/guest/setup/shell-path.js +22 -0
- package/dist/guest/toolchains/languages/python.js +1 -1
- package/dist/guest/toolchains/runtimes.js +2 -2
- package/dist/guest/vpn/index.d.ts +1 -0
- package/dist/guest/vpn/index.js +1 -0
- package/dist/guest/vpn/openvpn.d.ts +9 -0
- package/dist/guest/vpn/openvpn.js +47 -0
- package/dist/guest/vpn/tunnel.d.ts +17 -0
- package/dist/guest/vpn/tunnel.js +78 -0
- package/dist/guest/vpn/vpn.d.ts +6 -0
- package/dist/guest/vpn/vpn.js +126 -0
- package/dist/host/channel/attach.d.ts +3 -1
- package/dist/host/channel/attach.js +1 -1
- package/dist/host/channel/channel.d.ts +7 -0
- package/dist/host/channel/channel.js +26 -2
- package/dist/host/health/acl-read.d.ts +17 -0
- package/dist/host/health/acl-read.js +45 -0
- package/dist/host/health/config-backup.d.ts +11 -0
- package/dist/host/health/config-backup.js +73 -0
- package/dist/host/health/index.d.ts +6 -0
- package/dist/host/health/index.js +7 -0
- package/dist/host/health/install-state.d.ts +33 -0
- package/dist/host/health/install-state.js +110 -0
- package/dist/host/health/path-copies.d.ts +32 -0
- package/dist/host/health/path-copies.js +82 -0
- package/dist/host/health/stale.d.ts +45 -0
- package/dist/host/health/stale.js +120 -0
- package/dist/host/health/system.d.ts +37 -0
- package/dist/host/health/system.js +87 -0
- package/dist/host/install/copies.d.ts +13 -0
- package/dist/host/install/copies.js +63 -0
- package/dist/host/install/index.d.ts +1 -0
- package/dist/host/install/index.js +1 -0
- package/dist/host/launch/generate.js +25 -1
- package/dist/host/launch/session-file.js +4 -1
- package/dist/host/launch/stage.js +1 -1
- package/dist/host/migrate/backup.js +3 -0
- package/dist/host/migrate/migrations/config-file.d.ts +2 -0
- package/dist/host/migrate/migrations/config-file.js +7 -0
- package/dist/host/migrate/migrations/follow-machine-storage.js +9 -1
- package/dist/host/migrate/migrations/pin-storage-root.d.ts +4 -0
- package/dist/host/migrate/migrations/pin-storage-root.js +46 -0
- package/dist/host/migrate/migrations/unpin-config-defaults.js +4 -1
- package/dist/host/migrate/registry.js +2 -1
- package/dist/host/profile/accounts.d.ts +25 -1
- package/dist/host/profile/accounts.js +96 -13
- package/dist/host/profile/agent-accounts.d.ts +5 -1
- package/dist/host/profile/agent-accounts.js +35 -5
- package/dist/host/profile/index.d.ts +2 -1
- package/dist/host/profile/index.js +1 -0
- package/dist/host/profile/plugin-share.d.ts +26 -0
- package/dist/host/profile/plugin-share.js +146 -0
- package/dist/host/projects/join.d.ts +7 -1
- package/dist/host/projects/join.js +21 -4
- package/dist/host/projects/registry.d.ts +16 -10
- package/dist/host/projects/registry.js +74 -6
- package/dist/host/sandbox/platform.d.ts +2 -0
- package/dist/host/sandbox/platform.js +21 -2
- package/dist/host/secrets/index.d.ts +2 -0
- package/dist/host/secrets/index.js +2 -0
- package/dist/host/secrets/launch-secrets.d.ts +17 -0
- package/dist/host/secrets/launch-secrets.js +93 -0
- package/dist/host/secrets/secret-store.d.ts +25 -0
- package/dist/host/secrets/secret-store.js +89 -0
- package/dist/host/sharing/acl.d.ts +16 -2
- package/dist/host/sharing/acl.js +35 -10
- package/dist/host/sharing/current.js +11 -2
- package/dist/host/sharing/doctor.d.ts +2 -0
- package/dist/host/sharing/doctor.js +44 -0
- package/dist/host/sharing/group-access.d.ts +6 -0
- package/dist/host/sharing/group-access.js +24 -0
- package/dist/host/sharing/index.d.ts +2 -1
- package/dist/host/sharing/index.js +2 -1
- package/dist/host/sharing/lifecycle.d.ts +8 -2
- package/dist/host/sharing/lifecycle.js +16 -7
- package/dist/host/storage/cleanup.d.ts +2 -0
- package/dist/host/storage/cleanup.js +2 -1
- package/dist/host/storage/history.d.ts +21 -0
- package/dist/host/storage/history.js +49 -0
- package/dist/host/storage/index.d.ts +4 -0
- package/dist/host/storage/index.js +4 -0
- package/dist/host/storage/move.d.ts +95 -0
- package/dist/host/storage/move.js +384 -0
- package/dist/host/storage/offer.d.ts +11 -0
- package/dist/host/storage/offer.js +36 -0
- package/dist/host/storage/root.d.ts +18 -0
- package/dist/host/storage/root.js +77 -0
- package/dist/host/tools/windows-terminal.d.ts +2 -0
- package/dist/host/tools/windows-terminal.js +23 -5
- package/dist/host/update/index.d.ts +1 -1
- package/dist/host/update/index.js +1 -1
- package/dist/host/update/notice.d.ts +2 -0
- package/dist/host/update/notice.js +13 -1
- package/dist/host/update/update-state.d.ts +6 -0
- package/dist/host/update/update-state.js +2 -0
- package/dist/host/update/updater.d.ts +19 -0
- package/dist/host/update/updater.js +88 -12
- package/dist/host/vpn/index.d.ts +4 -0
- package/dist/host/vpn/index.js +4 -0
- package/dist/host/vpn/login.d.ts +24 -0
- package/dist/host/vpn/login.js +79 -0
- package/dist/host/vpn/nordvpn.d.ts +21 -0
- package/dist/host/vpn/nordvpn.js +77 -0
- package/dist/host/vpn/prepare.d.ts +17 -0
- package/dist/host/vpn/prepare.js +77 -0
- package/dist/host/vpn/status.d.ts +12 -0
- package/dist/host/vpn/status.js +53 -0
- package/dist/host/web/auth.d.ts +38 -0
- package/dist/host/web/auth.js +96 -0
- package/dist/host/web/backend.d.ts +14 -0
- package/dist/host/web/backend.js +1 -0
- package/dist/host/web/bridge.d.ts +27 -0
- package/dist/host/web/bridge.js +144 -0
- package/dist/host/web/browser.d.ts +1 -0
- package/dist/host/web/browser.js +10 -0
- package/dist/host/web/control.d.ts +17 -0
- package/dist/host/web/control.js +67 -0
- package/dist/host/web/index.d.ts +9 -0
- package/dist/host/web/index.js +8 -0
- package/dist/host/web/runner.d.ts +14 -0
- package/dist/host/web/runner.js +45 -0
- package/dist/host/web/server.d.ts +40 -0
- package/dist/host/web/server.js +218 -0
- package/dist/host/web/state.d.ts +20 -0
- package/dist/host/web/state.js +41 -0
- package/dist/host/web/static-files.d.ts +13 -0
- package/dist/host/web/static-files.js +57 -0
- package/dist/runtime/agent.js +27614 -0
- package/dist/runtime/daemon.js +1012 -618
- package/dist/runtime/db.js +236 -106
- package/dist/runtime/main.js +1093 -250
- package/dist/runtime/netproxy.js +102 -3
- package/dist/runtime/view.js +102 -3
- package/dist/selftest/accounts.test.js +40 -0
- package/dist/selftest/agent-paths.test.d.ts +2 -0
- package/dist/selftest/agent-paths.test.js +115 -0
- package/dist/selftest/channel.test.js +5 -0
- package/dist/selftest/cli.test.js +34 -4
- package/dist/selftest/credentials.test.d.ts +2 -0
- package/dist/selftest/credentials.test.js +198 -0
- package/dist/selftest/generate.test.js +1 -1
- package/dist/selftest/health.test.d.ts +2 -0
- package/dist/selftest/health.test.js +470 -0
- package/dist/selftest/host-sim.test.js +2 -0
- package/dist/selftest/index.js +19 -0
- package/dist/selftest/layout.test.js +2 -0
- package/dist/selftest/menu-screens.d.ts +7 -0
- package/dist/selftest/menu-screens.js +65 -16
- package/dist/selftest/menu.test.js +574 -21
- package/dist/selftest/migrate.test.js +1 -1
- package/dist/selftest/personalization.test.js +3 -1
- package/dist/selftest/projects.test.js +39 -1
- package/dist/selftest/shared-use.test.js +64 -2
- package/dist/selftest/sharing.test.js +50 -1
- package/dist/selftest/storage.test.d.ts +2 -0
- package/dist/selftest/storage.test.js +363 -0
- package/dist/selftest/support/fixtures.d.ts +14 -0
- package/dist/selftest/support/releases.js +1 -1
- package/dist/selftest/toolchains.test.js +21 -0
- package/dist/selftest/update-service.test.js +87 -3
- package/dist/selftest/vpn.test.d.ts +2 -0
- package/dist/selftest/vpn.test.js +279 -0
- package/dist/selftest/web.test.d.ts +2 -0
- package/dist/selftest/web.test.js +304 -0
- package/dist/shared/apps/catalogue.js +31 -0
- package/dist/shared/channel/index.d.ts +1 -0
- package/dist/shared/channel/index.js +1 -0
- package/dist/shared/channel/run-dir.d.ts +6 -1
- package/dist/shared/channel/run-dir.js +12 -1
- package/dist/shared/channel/schema.d.ts +19 -0
- package/dist/shared/channel/schema.js +11 -0
- package/dist/shared/channel/secrets.d.ts +28 -0
- package/dist/shared/channel/secrets.js +44 -0
- package/dist/shared/vpn/download.d.ts +13 -0
- package/dist/shared/vpn/download.js +14 -0
- package/dist/shared/vpn/kill-switch.d.ts +18 -0
- package/dist/shared/vpn/kill-switch.js +81 -0
- package/dist/shared/vpn/ovpn.d.ts +23 -0
- package/dist/shared/vpn/ovpn.js +244 -0
- package/dist/shared/web/index.d.ts +1 -0
- package/dist/shared/web/index.js +1 -0
- package/dist/shared/web/protocol.d.ts +61 -0
- package/dist/shared/web/protocol.js +35 -0
- package/dist/sync/sync-cycle.js +5 -1
- package/dist/sync/sync-state.d.ts +1 -0
- package/dist/sync/sync-state.js +1 -0
- package/dist/sync/watcher.js +10 -0
- package/dist/web/index.html +13 -0
- package/dist/web/main-CXESL347.js +45 -0
- package/dist/web/main-VLR7DVQ4.css +1 -0
- package/native/csharp/FolderSecurity.cs +29 -0
- package/native/scripts/protect-data.ps1 +29 -0
- package/native/scripts/read-folder-sddl.ps1 +10 -0
- package/native/scripts/set-folder-access.ps1 +49 -7
- package/node_modules/ws/LICENSE +20 -0
- package/node_modules/ws/README.md +548 -0
- package/node_modules/ws/browser.js +8 -0
- package/node_modules/ws/index.js +22 -0
- package/node_modules/ws/lib/buffer-util.js +131 -0
- package/node_modules/ws/lib/constants.js +19 -0
- package/node_modules/ws/lib/event-target.js +292 -0
- package/node_modules/ws/lib/extension.js +203 -0
- package/node_modules/ws/lib/limiter.js +55 -0
- package/node_modules/ws/lib/permessage-deflate.js +530 -0
- package/node_modules/ws/lib/receiver.js +743 -0
- package/node_modules/ws/lib/sender.js +607 -0
- package/node_modules/ws/lib/stream.js +161 -0
- package/node_modules/ws/lib/subprotocol.js +62 -0
- package/node_modules/ws/lib/validation.js +152 -0
- package/node_modules/ws/lib/websocket-server.js +562 -0
- package/node_modules/ws/lib/websocket.js +1421 -0
- package/node_modules/ws/package.json +74 -0
- package/node_modules/ws/wrapper.mjs +21 -0
- package/package.json +15 -5
- package/sandbox/csharp/EnvBroadcast.cs +33 -0
- package/sandbox/csharp/PathSync.cs +90 -0
- package/sandbox/scripts/install-shell-path.ps1 +73 -0
- package/sandbox/scripts/set-machine-environment.ps1 +37 -4
- package/schemas/config.schema.json +79 -4
- package/schemas/sandbox-config.schema.json +14 -1
- package/dist/cli/commands/repair/checks.d.ts +0 -2
- package/dist/cli/commands/repair/checks.js +0 -246
- package/dist/cli/menu/pages/start-page.d.ts +0 -43
- package/dist/cli/menu/pages/start-page.js +0 -192
- package/dist/cli/tui/tab-bar.d.ts +0 -9
- package/dist/cli/tui/tab-bar.js +0 -33
|
@@ -7,8 +7,18 @@ import { isWithin } from '../../shared/paths/index.js';
|
|
|
7
7
|
import { now, RunDir } from '../../shared/channel/index.js';
|
|
8
8
|
import { safeRemove, writeJsonAtomic } from '../../shared/io/index.js';
|
|
9
9
|
import { isSandboxRunning } from '../sandbox/index.js';
|
|
10
|
+
import { withoutSecrets, writeSecrets } from '../secrets/index.js';
|
|
10
11
|
import { assertTestWritable, refuseInSelfTest } from '../test-guard.js';
|
|
11
12
|
const BOOT_TIMEOUT_MS = 30 * 60 * 1000;
|
|
13
|
+
/**
|
|
14
|
+
* The boot script writes its first status once Windows has booted the sandbox and logged on (a minute or two, longer on
|
|
15
|
+
* a slow PC or the very first start); none after this long means it never got going.
|
|
16
|
+
*/
|
|
17
|
+
export const FIRST_STATUS_MS = 8 * 60 * 1000;
|
|
18
|
+
/** True when a boot is silent: no status of this launch yet, and the sandbox had its time to write one. */
|
|
19
|
+
export function bootSilent(elapsedMs, seenStatus, limitMs = FIRST_STATUS_MS) {
|
|
20
|
+
return !seenStatus && elapsedMs > limitMs;
|
|
21
|
+
}
|
|
12
22
|
/** Most sessions the host will ever contact for one listing: a sandbox can write as many session files as it likes. */
|
|
13
23
|
export const MAX_QUERIED_SESSIONS = 32;
|
|
14
24
|
const printLine = (line) => console.log(line);
|
|
@@ -57,10 +67,20 @@ export class SandboxChannel {
|
|
|
57
67
|
*/
|
|
58
68
|
waitForReady(launchId, progress = (m) => printLine(' [sandbox] ' + m), onStatus) {
|
|
59
69
|
let last = '';
|
|
70
|
+
let seen = false;
|
|
71
|
+
const started = Date.now();
|
|
60
72
|
return this.until(BOOT_TIMEOUT_MS, () => {
|
|
61
73
|
const status = this.run.readSandboxStatus();
|
|
62
|
-
if (!status || (launchId !== null && status.launchId !== launchId))
|
|
74
|
+
if (!status || (launchId !== null && status.launchId !== launchId)) {
|
|
75
|
+
if (bootSilent(Date.now() - started, seen))
|
|
76
|
+
fail('The sandbox has not reported anything for ' + Math.round(FIRST_STATUS_MS / 60000) + ' minutes.', 'It may still be starting, or it cannot reach its folders. Look at its window (sbx-show), the run folder ' +
|
|
77
|
+
this.run.root +
|
|
78
|
+
' and the data folder ' +
|
|
79
|
+
this.dataPath +
|
|
80
|
+
', and check that you can write to both. sandbox logs shows what happened.');
|
|
63
81
|
return null;
|
|
82
|
+
}
|
|
83
|
+
seen = true;
|
|
64
84
|
onStatus?.(status);
|
|
65
85
|
if (status.message !== last) {
|
|
66
86
|
progress(safeText(status.message).slice(0, 300));
|
|
@@ -77,7 +97,11 @@ export class SandboxChannel {
|
|
|
77
97
|
if (request.kind === 'exec' && /shutdown/i.test(request.command[0] ?? ''))
|
|
78
98
|
refuseInSelfTest('shut down Windows Sandbox');
|
|
79
99
|
assertNoLinks(this.dataPath, this.run.requestsDir);
|
|
80
|
-
|
|
100
|
+
// Agent environment values (they may be API keys) never go into run: they travel in a one-shot file next to it.
|
|
101
|
+
const { request: plain, secrets } = withoutSecrets(request);
|
|
102
|
+
if (secrets)
|
|
103
|
+
writeSecrets(this.dataPath, request.id, secrets);
|
|
104
|
+
writeJsonAtomic(this.run.requestFile(request.id), plain);
|
|
81
105
|
}
|
|
82
106
|
waitForResponse(requestId, timeoutMs) {
|
|
83
107
|
return this.until(timeoutMs, () => {
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** The Windows group of the users who may use a shared sandbox. */
|
|
2
|
+
export declare const SHARING_GROUP = "Agent Sandbox Users";
|
|
3
|
+
export interface AclEntry {
|
|
4
|
+
readonly principal: string;
|
|
5
|
+
/** The flags in order, e.g. "(OI)(CI)(F)". */
|
|
6
|
+
readonly flags: string;
|
|
7
|
+
readonly inherited: boolean;
|
|
8
|
+
readonly deny: boolean;
|
|
9
|
+
}
|
|
10
|
+
/** Entries of `icacls` output for `dir` (the first line starts with the folder's path). */
|
|
11
|
+
export declare function parseIcacls(text: string, dir: string): AclEntry[];
|
|
12
|
+
/** The entries that make a folder readable by more than you, SYSTEM and Administrators (empty: it is private). */
|
|
13
|
+
export declare function privacyProblems(entries: readonly AclEntry[], groupName: string): string[];
|
|
14
|
+
/** Entries of the sharing group (the folders it may enter only while a shared sandbox runs). */
|
|
15
|
+
export declare function groupEntries(entries: readonly AclEntry[], groupName: string): AclEntry[];
|
|
16
|
+
/** The folders a sharing plan touches for one user's data set: the storage folder, data folder, data set and run. */
|
|
17
|
+
export declare function sharedFolders(storageUserDir: string, dataPath: string): string[];
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// Reading a folder's permissions from `icacls <folder>` (read only): who has an entry, and is it inherited. Used to
|
|
2
|
+
// tell whether a user's data folder is private and whether the sharing group still has entries on folders it should
|
|
3
|
+
// not.
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
/** The Windows group of the users who may use a shared sandbox. */
|
|
6
|
+
export const SHARING_GROUP = 'Agent Sandbox Users';
|
|
7
|
+
/** Entries of `icacls` output for `dir` (the first line starts with the folder's path). */
|
|
8
|
+
export function parseIcacls(text, dir) {
|
|
9
|
+
const entries = [];
|
|
10
|
+
const lines = text.split(/\r?\n/);
|
|
11
|
+
for (let i = 0; i < lines.length; i++) {
|
|
12
|
+
let line = lines[i];
|
|
13
|
+
if (/^\s*(Successfully processed|Failed processing)/i.test(line))
|
|
14
|
+
break;
|
|
15
|
+
if (i === 0 && line.toLowerCase().startsWith(dir.toLowerCase()))
|
|
16
|
+
line = line.slice(dir.length);
|
|
17
|
+
const m = /^\s*(\S.*?):((?:\([A-Za-z0-9,]+\))+)\s*$/.exec(line);
|
|
18
|
+
if (!m)
|
|
19
|
+
continue;
|
|
20
|
+
entries.push({
|
|
21
|
+
principal: m[1].trim(),
|
|
22
|
+
flags: m[2],
|
|
23
|
+
inherited: m[2].includes('(I)'),
|
|
24
|
+
deny: m[2].includes('(DENY)'),
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
return entries;
|
|
28
|
+
}
|
|
29
|
+
const BROAD = /(^|\\)(Everyone|Users|Authenticated Users)$/i;
|
|
30
|
+
/** The entries that make a folder readable by more than you, SYSTEM and Administrators (empty: it is private). */
|
|
31
|
+
export function privacyProblems(entries, groupName) {
|
|
32
|
+
const group = new RegExp('(^|\\\\)' + groupName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '$', 'i');
|
|
33
|
+
return entries
|
|
34
|
+
.filter((e) => !e.deny && (BROAD.test(e.principal) || group.test(e.principal) || e.inherited))
|
|
35
|
+
.map((e) => e.principal + (e.inherited ? ' (inherited)' : ''));
|
|
36
|
+
}
|
|
37
|
+
/** Entries of the sharing group (the folders it may enter only while a shared sandbox runs). */
|
|
38
|
+
export function groupEntries(entries, groupName) {
|
|
39
|
+
const group = new RegExp('(^|\\\\)' + groupName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '$', 'i');
|
|
40
|
+
return entries.filter((e) => group.test(e.principal));
|
|
41
|
+
}
|
|
42
|
+
/** The folders a sharing plan touches for one user's data set: the storage folder, data folder, data set and run. */
|
|
43
|
+
export function sharedFolders(storageUserDir, dataPath) {
|
|
44
|
+
return [storageUserDir, path.dirname(dataPath), dataPath, path.join(dataPath, 'run')];
|
|
45
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface ConfigBackup {
|
|
2
|
+
readonly folder: string;
|
|
3
|
+
/** The saved copy of the settings file. */
|
|
4
|
+
readonly copy: string;
|
|
5
|
+
/** From the folder name: "1.4.0 to 1.5.0 at 2026-10-14 09:30:12". */
|
|
6
|
+
readonly label: string;
|
|
7
|
+
}
|
|
8
|
+
/** Saved copies of `configFile`, newest first. A backup with no readable manifest or no such copy is skipped. */
|
|
9
|
+
export declare function configBackups(storageRoot: string, configFile: string): ConfigBackup[];
|
|
10
|
+
/** A short line difference: "- line" only in `current`, "+ line" only in `backup`; at most `max` lines. */
|
|
11
|
+
export declare function lineDiff(current: string, backup: string, max?: number): string[];
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// The copies of config.json that an update saved before it changed anything (<storage>\backups\migrate-*\), so a
|
|
2
|
+
// settings file that no longer loads can be put back. Read only; the caller shows the difference first and keeps the
|
|
3
|
+
// broken file.
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { z } from 'zod';
|
|
7
|
+
import { assertNoLinks, dirExists } from '../../core/util/index.js';
|
|
8
|
+
import { readValid } from '../../shared/io/index.js';
|
|
9
|
+
const ManifestSchema = z.object({
|
|
10
|
+
entries: z.array(z.object({ original: z.string(), copy: z.string(), existed: z.boolean() })),
|
|
11
|
+
});
|
|
12
|
+
const MAX_CONFIG_BYTES = 1024 * 1024;
|
|
13
|
+
/** The stamp in migrate-<from>-<to>-<yyyymmddhhmmss>[-n] folder names. */
|
|
14
|
+
function stampOf(name) {
|
|
15
|
+
return /-(\d{14})(?:-\d+)?$/.exec(name)?.[1] ?? '';
|
|
16
|
+
}
|
|
17
|
+
function labelOf(name) {
|
|
18
|
+
const m = /^migrate-(.+?)-(.+?)-(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)(?:-\d+)?$/.exec(name);
|
|
19
|
+
return m
|
|
20
|
+
? m[1] + ' to ' + m[2] + ' at ' + m[3] + '-' + m[4] + '-' + m[5] + ' ' + m[6] + ':' + m[7] + ':' + m[8]
|
|
21
|
+
: name;
|
|
22
|
+
}
|
|
23
|
+
/** Saved copies of `configFile`, newest first. A backup with no readable manifest or no such copy is skipped. */
|
|
24
|
+
export function configBackups(storageRoot, configFile) {
|
|
25
|
+
const dir = path.join(storageRoot, 'backups');
|
|
26
|
+
if (!dirExists(dir))
|
|
27
|
+
return [];
|
|
28
|
+
assertNoLinks(storageRoot, dir);
|
|
29
|
+
const found = [];
|
|
30
|
+
for (const name of fs.readdirSync(dir)) {
|
|
31
|
+
if (!name.startsWith('migrate-'))
|
|
32
|
+
continue;
|
|
33
|
+
const folder = path.join(dir, name);
|
|
34
|
+
const manifest = readValid(ManifestSchema, path.join(folder, 'manifest.json'));
|
|
35
|
+
const entry = manifest?.entries.find((e) => e.existed && path.resolve(e.original).toLowerCase() === path.resolve(configFile).toLowerCase());
|
|
36
|
+
if (!entry)
|
|
37
|
+
continue;
|
|
38
|
+
// The manifest is data on disk: the copy must be a file inside this backup folder, and small.
|
|
39
|
+
const copy = path.resolve(entry.copy);
|
|
40
|
+
if (!copy.toLowerCase().startsWith(path.resolve(folder).toLowerCase() + path.sep))
|
|
41
|
+
continue;
|
|
42
|
+
try {
|
|
43
|
+
assertNoLinks(storageRoot, copy);
|
|
44
|
+
const stat = fs.statSync(copy);
|
|
45
|
+
if (!stat.isFile() || stat.size > MAX_CONFIG_BYTES)
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
found.push({ folder, copy, label: labelOf(name) });
|
|
52
|
+
}
|
|
53
|
+
return found.sort((a, b) => stampOf(path.basename(b.folder)).localeCompare(stampOf(path.basename(a.folder))));
|
|
54
|
+
}
|
|
55
|
+
/** A short line difference: "- line" only in `current`, "+ line" only in `backup`; at most `max` lines. */
|
|
56
|
+
export function lineDiff(current, backup, max = 12) {
|
|
57
|
+
const split = (t) => t
|
|
58
|
+
.replace(/\r\n/g, '\n')
|
|
59
|
+
.split('\n')
|
|
60
|
+
.map((l) => l.trimEnd())
|
|
61
|
+
.filter((l) => l.trim() !== '');
|
|
62
|
+
const a = split(current);
|
|
63
|
+
const b = split(backup);
|
|
64
|
+
const inB = new Set(b);
|
|
65
|
+
const inA = new Set(a);
|
|
66
|
+
const lines = [
|
|
67
|
+
...a.filter((l) => !inB.has(l)).map((l) => '- ' + l.trim()),
|
|
68
|
+
...b.filter((l) => !inA.has(l)).map((l) => '+ ' + l.trim()),
|
|
69
|
+
];
|
|
70
|
+
return lines.length > max
|
|
71
|
+
? [...lines.slice(0, max), '... and ' + (lines.length - max) + ' more changed lines']
|
|
72
|
+
: lines;
|
|
73
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// Looking at the install, the data folders and what a closed sandbox leaves behind: the host side of doctor and repair.
|
|
2
|
+
export * from './system.js';
|
|
3
|
+
export * from './install-state.js';
|
|
4
|
+
export * from './acl-read.js';
|
|
5
|
+
export * from './stale.js';
|
|
6
|
+
export * from './config-backup.js';
|
|
7
|
+
export * from './path-copies.js';
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { type InstallLayout } from '../install/index.js';
|
|
2
|
+
export interface PointerState {
|
|
3
|
+
readonly activeVersion: string | null;
|
|
4
|
+
readonly activeNode: string | null;
|
|
5
|
+
/** Versions that have a package.json, oldest first. */
|
|
6
|
+
readonly versions: readonly string[];
|
|
7
|
+
/** Node folders that have node.exe, oldest first. */
|
|
8
|
+
readonly nodes: readonly string[];
|
|
9
|
+
readonly problems: readonly string[];
|
|
10
|
+
/** Where the pointers should point to be whole again, or null when nothing usable is installed. */
|
|
11
|
+
readonly want: {
|
|
12
|
+
readonly version: string;
|
|
13
|
+
readonly node: string;
|
|
14
|
+
} | null;
|
|
15
|
+
}
|
|
16
|
+
/** Node versions under <root>\node that really hold node.exe, oldest first. */
|
|
17
|
+
export declare function installedNodes(layout: InstallLayout): string[];
|
|
18
|
+
/** Versions that are really there (a package.json in their package folder), oldest first. */
|
|
19
|
+
export declare function completeVersions(layout: InstallLayout): string[];
|
|
20
|
+
/** Looks at active-version and active-node. */
|
|
21
|
+
export declare function inspectPointers(layout: InstallLayout): PointerState;
|
|
22
|
+
/** Moves the pointers to what is installed (the newest version and Node that are whole). Returns what changed. */
|
|
23
|
+
export declare function repairPointers(layout: InstallLayout, state?: PointerState): string;
|
|
24
|
+
/** The commands a package folder declares (name -> program path), or null when its package.json cannot be read. */
|
|
25
|
+
export declare function binsOfPackage(packageDir: string): Record<string, string> | null;
|
|
26
|
+
export interface ShimProblems {
|
|
27
|
+
readonly missing: readonly string[];
|
|
28
|
+
readonly different: readonly string[];
|
|
29
|
+
}
|
|
30
|
+
/** Shim files (both the .cmd and the shell one) that are missing or not what shimText() writes now. */
|
|
31
|
+
export declare function shimProblems(layout: InstallLayout, bins: Record<string, string>): ShimProblems;
|
|
32
|
+
/** Is `dir` one of the entries of a PATH value (variables expanded, case and a trailing slash ignored)? */
|
|
33
|
+
export declare function pathHas(value: string | null, dir: string): boolean;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
// Is a managed install (versions side by side, a private Node, shims in bin) in one piece: do the two pointer files
|
|
2
|
+
// name installed folders, do the shims say what they should, is bin on PATH. These look; the fixes move a pointer or
|
|
3
|
+
// rewrite shims, and never delete a version.
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { expandVariables } from '../../core/util/index.js';
|
|
7
|
+
import { NODE_PATTERN, compareVersions, readBins, shimText } from '../install/index.js';
|
|
8
|
+
/** Node versions under <root>\node that really hold node.exe, oldest first. */
|
|
9
|
+
export function installedNodes(layout) {
|
|
10
|
+
const dir = path.join(layout.root, 'node');
|
|
11
|
+
try {
|
|
12
|
+
return fs
|
|
13
|
+
.readdirSync(dir, { withFileTypes: true })
|
|
14
|
+
.filter((d) => d.isDirectory() && NODE_PATTERN.test(d.name) && fs.existsSync(layout.nodeExe(d.name)))
|
|
15
|
+
.map((d) => d.name)
|
|
16
|
+
.sort((a, b) => compareVersions(a.slice(1), b.slice(1)));
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return [];
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/** Versions that are really there (a package.json in their package folder), oldest first. */
|
|
23
|
+
export function completeVersions(layout) {
|
|
24
|
+
return layout.installedVersions().filter((v) => fs.existsSync(path.join(layout.packageDir(v), 'package.json')));
|
|
25
|
+
}
|
|
26
|
+
function rawPointer(layout, name) {
|
|
27
|
+
try {
|
|
28
|
+
return fs.readFileSync(path.join(layout.root, name), 'utf8').trim();
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/** Looks at active-version and active-node. */
|
|
35
|
+
export function inspectPointers(layout) {
|
|
36
|
+
const versions = completeVersions(layout);
|
|
37
|
+
const nodes = installedNodes(layout);
|
|
38
|
+
const activeVersion = layout.activeVersion();
|
|
39
|
+
const activeNode = layout.activeNode();
|
|
40
|
+
const problems = [];
|
|
41
|
+
const raw = (name) => {
|
|
42
|
+
const text = rawPointer(layout, name);
|
|
43
|
+
return text === null ? 'is missing' : text === '' ? 'is empty' : 'says "' + text.slice(0, 40) + '"';
|
|
44
|
+
};
|
|
45
|
+
if (!activeVersion)
|
|
46
|
+
problems.push('active-version ' + raw('active-version') + ' (it must name a version like 1.5.0)');
|
|
47
|
+
else if (!versions.includes(activeVersion))
|
|
48
|
+
problems.push('active-version points at ' + activeVersion + ', which is not installed');
|
|
49
|
+
if (!activeNode)
|
|
50
|
+
problems.push('active-node ' + raw('active-node') + ' (it must name a Node like v22.11.0)');
|
|
51
|
+
else if (!nodes.includes(activeNode))
|
|
52
|
+
problems.push('active-node points at ' + activeNode + ', which is not installed');
|
|
53
|
+
const version = activeVersion && versions.includes(activeVersion) ? activeVersion : versions[versions.length - 1];
|
|
54
|
+
const node = activeNode && nodes.includes(activeNode) ? activeNode : nodes[nodes.length - 1];
|
|
55
|
+
return { activeVersion, activeNode, versions, nodes, problems, want: version && node ? { version, node } : null };
|
|
56
|
+
}
|
|
57
|
+
/** Moves the pointers to what is installed (the newest version and Node that are whole). Returns what changed. */
|
|
58
|
+
export function repairPointers(layout, state = inspectPointers(layout)) {
|
|
59
|
+
if (!state.want)
|
|
60
|
+
throw new Error('No complete version or Node is installed in ' + layout.root + '.');
|
|
61
|
+
layout.setActive(state.want.version, state.want.node);
|
|
62
|
+
return 'active-version ' + state.want.version + ', active-node ' + state.want.node;
|
|
63
|
+
}
|
|
64
|
+
/** The commands a package folder declares (name -> program path), or null when its package.json cannot be read. */
|
|
65
|
+
export function binsOfPackage(packageDir) {
|
|
66
|
+
try {
|
|
67
|
+
return readBins(JSON.parse(fs.readFileSync(path.join(packageDir, 'package.json'), 'utf8')));
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
const sameText = (a, b) => a.replace(/\r\n/g, '\n') === b.replace(/\r\n/g, '\n');
|
|
74
|
+
/** Shim files (both the .cmd and the shell one) that are missing or not what shimText() writes now. */
|
|
75
|
+
export function shimProblems(layout, bins) {
|
|
76
|
+
const missing = [];
|
|
77
|
+
const different = [];
|
|
78
|
+
for (const [name, target] of Object.entries(bins)) {
|
|
79
|
+
for (const [file, kind] of [
|
|
80
|
+
[name + '.cmd', 'cmd'],
|
|
81
|
+
[name, 'sh'],
|
|
82
|
+
]) {
|
|
83
|
+
let text;
|
|
84
|
+
try {
|
|
85
|
+
text = fs.readFileSync(path.join(layout.binDir, file), 'utf8');
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
missing.push(file);
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
if (!sameText(text, shimText(name, kind, target)))
|
|
92
|
+
different.push(file);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return { missing, different };
|
|
96
|
+
}
|
|
97
|
+
const normal = (p) => path
|
|
98
|
+
.normalize(expandVariables(p.trim().replace(/^"|"$/g, '')))
|
|
99
|
+
.replace(/[\\/]+$/, '')
|
|
100
|
+
.toLowerCase();
|
|
101
|
+
/** Is `dir` one of the entries of a PATH value (variables expanded, case and a trailing slash ignored)? */
|
|
102
|
+
export function pathHas(value, dir) {
|
|
103
|
+
if (!value)
|
|
104
|
+
return false;
|
|
105
|
+
const want = normal(dir);
|
|
106
|
+
return value
|
|
107
|
+
.split(';')
|
|
108
|
+
.filter((p) => p.trim() !== '')
|
|
109
|
+
.some((p) => normal(p) === want);
|
|
110
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export declare const COMMAND_NAMES: readonly ["sandbox", "agent-sandbox", "claude-sbx"];
|
|
2
|
+
export type CopyKind = 'managed-machine' | 'managed-user' | 'npm' | 'old-scripts' | 'other';
|
|
3
|
+
/** One folder on the PATH that holds the commands. */
|
|
4
|
+
export interface CommandCopy {
|
|
5
|
+
readonly dir: string;
|
|
6
|
+
/** The commands found in it, as the file Windows would run for each (sandbox.cmd, ...). */
|
|
7
|
+
readonly files: readonly string[];
|
|
8
|
+
readonly kind: CopyKind;
|
|
9
|
+
/** Position among the PATH folders that hold a copy (0 = the first one, the one that runs). */
|
|
10
|
+
readonly order: number;
|
|
11
|
+
}
|
|
12
|
+
export interface PathCopyEnv {
|
|
13
|
+
/** Machine PATH, then user PATH (raw registry values; null when unreadable). */
|
|
14
|
+
readonly machinePath: string | null;
|
|
15
|
+
readonly userPath: string | null;
|
|
16
|
+
/** PATHEXT, e.g. ".COM;.EXE;.BAT;.CMD". */
|
|
17
|
+
readonly pathext: string;
|
|
18
|
+
/** %ProgramFiles% and %LOCALAPPDATA% (where the managed installs live). */
|
|
19
|
+
readonly programFiles: string | null;
|
|
20
|
+
readonly localAppData: string | null;
|
|
21
|
+
readonly exists: (file: string) => boolean;
|
|
22
|
+
}
|
|
23
|
+
/** The PATH folders in the order Windows searches them (machine, then user), each once, variables expanded. */
|
|
24
|
+
export declare function pathFolders(machinePath: string | null, userPath: string | null): string[];
|
|
25
|
+
/** The folders that hold at least one of the commands, in search order. */
|
|
26
|
+
export declare function findCommandCopies(env: PathCopyEnv): CommandCopy[];
|
|
27
|
+
/** The folder a program runs from as a bin folder: the managed bin, or the npm global folder holding the package. */
|
|
28
|
+
export declare function ownBinDir(packageRoot: string, managedBin: string | null): string | null;
|
|
29
|
+
export declare const sameFolder: (a: string, b: string) => boolean;
|
|
30
|
+
/** PATHEXT from the process, or the Windows default. */
|
|
31
|
+
export declare function currentPathext(): string;
|
|
32
|
+
export declare const fileOnDisk: (file: string) => boolean;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// Every copy of the sandbox commands on the PATH (sandbox, agent-sandbox, claude-sbx), in the order Windows finds them:
|
|
2
|
+
// the machine PATH, then yours, and in each folder PATHEXT order. Read only: nothing is run, and a file is only checked
|
|
3
|
+
// for existing. Used by doctor to say which copy runs and how to remove each of the others.
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { expandVariables } from '../../core/util/index.js';
|
|
7
|
+
export const COMMAND_NAMES = ['sandbox', 'agent-sandbox', 'claude-sbx'];
|
|
8
|
+
const normal = (p) => path
|
|
9
|
+
.normalize(expandVariables(p.trim().replace(/^"|"$/g, '')))
|
|
10
|
+
.replace(/[\\/]+$/, '')
|
|
11
|
+
.toLowerCase();
|
|
12
|
+
/** The PATH folders in the order Windows searches them (machine, then user), each once, variables expanded. */
|
|
13
|
+
export function pathFolders(machinePath, userPath) {
|
|
14
|
+
const seen = new Set();
|
|
15
|
+
const out = [];
|
|
16
|
+
for (const value of [machinePath, userPath])
|
|
17
|
+
for (const raw of (value ?? '').split(';')) {
|
|
18
|
+
if (raw.trim() === '')
|
|
19
|
+
continue;
|
|
20
|
+
const dir = path.normalize(expandVariables(raw.trim().replace(/^"|"$/g, '')));
|
|
21
|
+
const key = normal(raw);
|
|
22
|
+
if (seen.has(key))
|
|
23
|
+
continue;
|
|
24
|
+
seen.add(key);
|
|
25
|
+
out.push(dir);
|
|
26
|
+
}
|
|
27
|
+
return out;
|
|
28
|
+
}
|
|
29
|
+
function classify(dir, env) {
|
|
30
|
+
const here = normal(dir);
|
|
31
|
+
const managed = (base) => base ? normal(path.join(base, 'WindowsDevelopmentSandbox', 'bin')) : null;
|
|
32
|
+
if (here === managed(env.programFiles))
|
|
33
|
+
return 'managed-machine';
|
|
34
|
+
if (here === managed(env.localAppData))
|
|
35
|
+
return 'managed-user';
|
|
36
|
+
for (const pkg of ['windows-development-sandbox', 'agent-sandbox'])
|
|
37
|
+
if (env.exists(path.join(dir, 'node_modules', pkg, 'package.json')))
|
|
38
|
+
return 'npm';
|
|
39
|
+
if (/[\\/]scripts[\\/]agent-sandbox([\\/]bin)?$/i.test(here))
|
|
40
|
+
return 'old-scripts';
|
|
41
|
+
return 'other';
|
|
42
|
+
}
|
|
43
|
+
/** The folders that hold at least one of the commands, in search order. */
|
|
44
|
+
export function findCommandCopies(env) {
|
|
45
|
+
const exts = env.pathext
|
|
46
|
+
.split(';')
|
|
47
|
+
.map((e) => e.trim().toLowerCase())
|
|
48
|
+
.filter((e) => e.startsWith('.'));
|
|
49
|
+
const copies = [];
|
|
50
|
+
for (const dir of pathFolders(env.machinePath, env.userPath)) {
|
|
51
|
+
const files = [];
|
|
52
|
+
for (const name of COMMAND_NAMES) {
|
|
53
|
+
const hit = exts.map((ext) => name + ext).find((file) => env.exists(path.join(dir, file)));
|
|
54
|
+
if (hit)
|
|
55
|
+
files.push(hit);
|
|
56
|
+
}
|
|
57
|
+
if (files.length)
|
|
58
|
+
copies.push({ dir, files, kind: classify(dir, env), order: copies.length });
|
|
59
|
+
}
|
|
60
|
+
return copies;
|
|
61
|
+
}
|
|
62
|
+
/** The folder a program runs from as a bin folder: the managed bin, or the npm global folder holding the package. */
|
|
63
|
+
export function ownBinDir(packageRoot, managedBin) {
|
|
64
|
+
if (managedBin)
|
|
65
|
+
return managedBin;
|
|
66
|
+
const resolved = path.resolve(packageRoot);
|
|
67
|
+
const nodeModules = path.dirname(resolved);
|
|
68
|
+
return path.basename(nodeModules).toLowerCase() === 'node_modules' ? path.dirname(nodeModules) : null;
|
|
69
|
+
}
|
|
70
|
+
export const sameFolder = (a, b) => normal(a) === normal(b);
|
|
71
|
+
/** PATHEXT from the process, or the Windows default. */
|
|
72
|
+
export function currentPathext() {
|
|
73
|
+
return process.env.PATHEXT ?? '.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC';
|
|
74
|
+
}
|
|
75
|
+
export const fileOnDisk = (file) => {
|
|
76
|
+
try {
|
|
77
|
+
return fs.statSync(file).isFile();
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { ActiveSandbox, TaskRow } from '../sharing/index.js';
|
|
2
|
+
/** A watcher this old, with its log quiet and no sandbox, is left over (a finishing one is done within minutes). */
|
|
3
|
+
export declare const WATCHER_MIN_AGE_MS: number;
|
|
4
|
+
export declare const WATCHER_LOG_QUIET_MS: number;
|
|
5
|
+
/** Windows sessions that have a sandbox running (empty: none runs). */
|
|
6
|
+
export declare function sandboxSessions(rows: readonly TaskRow[]): number[];
|
|
7
|
+
/** Sharing records whose Windows session has no sandbox running (nothing else removes them after a crash). */
|
|
8
|
+
export declare function staleActiveRecords(records: readonly ActiveSandbox[], rows: readonly TaskRow[]): ActiveSandbox[];
|
|
9
|
+
export interface RunStateProblems {
|
|
10
|
+
/** Sessions still marked running or starting. */
|
|
11
|
+
readonly sessions: number;
|
|
12
|
+
/** Requests nobody answered (valid or not). */
|
|
13
|
+
readonly requests: number;
|
|
14
|
+
/** The sandbox status file says the boot never finished (booting, provisioning, failed). */
|
|
15
|
+
readonly unfinishedBoot: string | null;
|
|
16
|
+
readonly any: boolean;
|
|
17
|
+
}
|
|
18
|
+
/** What is left in <data>\run by a sandbox that is no longer running. */
|
|
19
|
+
export declare function runStateProblems(dataPath: string): RunStateProblems;
|
|
20
|
+
export interface OldLaunch {
|
|
21
|
+
readonly path: string;
|
|
22
|
+
readonly ageDays: number;
|
|
23
|
+
}
|
|
24
|
+
/** Launch folders under <storage>\launches that nothing has touched for `days` days (each is a finished start). */
|
|
25
|
+
export declare function staleLaunchFolders(storageRoot: string, days: number, now: number, keep?: readonly string[]): OldLaunch[];
|
|
26
|
+
export interface WatcherInfo {
|
|
27
|
+
readonly file: string;
|
|
28
|
+
readonly pid: number | null;
|
|
29
|
+
readonly ageMs: number;
|
|
30
|
+
/** How long ago the watcher's log was written; null when there is none. */
|
|
31
|
+
readonly logAgeMs: number | null;
|
|
32
|
+
}
|
|
33
|
+
/** The history-sync watcher records in <storage>\run (watcher-<data set>.pid) with the age of each and of its log. */
|
|
34
|
+
export declare function watcherRecords(storageRoot: string, now: number): WatcherInfo[];
|
|
35
|
+
/**
|
|
36
|
+
* Watchers that run with no sandbox to watch: the process is alive and is a Node program (a recycled pid is somebody
|
|
37
|
+
* else's), no sandbox runs, and neither its record nor its log has changed for a while (a finishing watcher is spared).
|
|
38
|
+
*/
|
|
39
|
+
export declare function orphanWatchers(infos: readonly WatcherInfo[], o: {
|
|
40
|
+
running: boolean;
|
|
41
|
+
alive: (pid: number) => boolean;
|
|
42
|
+
image: (pid: number) => string | null;
|
|
43
|
+
}): WatcherInfo[];
|
|
44
|
+
/** null when a small file can be created in the folder, else the reason (the file is removed again). */
|
|
45
|
+
export declare function writeProblem(dir: string): string | null;
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
// What a closed or crashed sandbox leaves behind, and how to tell it from what is in use: records of sandboxes that are
|
|
2
|
+
// gone, run files, old launch folders, background watchers with nothing to watch. These only look; the repair checks
|
|
3
|
+
// remove through the safe helpers. Everything takes its folders as arguments, so tests use temp folders.
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { z } from 'zod';
|
|
7
|
+
import { dirExists, isLink } from '../../core/util/index.js';
|
|
8
|
+
import { RunDir } from '../../shared/channel/index.js';
|
|
9
|
+
const DAY = 24 * 60 * 60 * 1000;
|
|
10
|
+
const SANDBOX_IMAGES = ['windowssandboxclient.exe', 'windowssandboxremotesession.exe', 'windowssandbox.exe'];
|
|
11
|
+
/** A watcher this old, with its log quiet and no sandbox, is left over (a finishing one is done within minutes). */
|
|
12
|
+
export const WATCHER_MIN_AGE_MS = 10 * 60 * 1000;
|
|
13
|
+
export const WATCHER_LOG_QUIET_MS = 5 * 60 * 1000;
|
|
14
|
+
/** Windows sessions that have a sandbox running (empty: none runs). */
|
|
15
|
+
export function sandboxSessions(rows) {
|
|
16
|
+
const found = rows.filter((r) => SANDBOX_IMAGES.includes(r.image.toLowerCase()));
|
|
17
|
+
return [...new Set(found.map((r) => r.session))];
|
|
18
|
+
}
|
|
19
|
+
/** Sharing records whose Windows session has no sandbox running (nothing else removes them after a crash). */
|
|
20
|
+
export function staleActiveRecords(records, rows) {
|
|
21
|
+
const sessions = sandboxSessions(rows);
|
|
22
|
+
return records.filter((r) => !sessions.includes(r.windowsSession));
|
|
23
|
+
}
|
|
24
|
+
/** What is left in <data>\run by a sandbox that is no longer running. */
|
|
25
|
+
export function runStateProblems(dataPath) {
|
|
26
|
+
const run = new RunDir(dataPath);
|
|
27
|
+
const sessions = run.listSessions().filter((s) => s.phase === 'running' || s.phase === 'starting').length;
|
|
28
|
+
const found = run.scanRequests();
|
|
29
|
+
const requests = found.valid.length + found.invalid.length;
|
|
30
|
+
const phase = run.readSandboxStatus()?.phase;
|
|
31
|
+
const unfinishedBoot = phase && phase !== 'ready' ? phase : null;
|
|
32
|
+
return { sessions, requests, unfinishedBoot, any: sessions > 0 || requests > 0 || unfinishedBoot !== null };
|
|
33
|
+
}
|
|
34
|
+
/** Launch folders under <storage>\launches that nothing has touched for `days` days (each is a finished start). */
|
|
35
|
+
export function staleLaunchFolders(storageRoot, days, now, keep = []) {
|
|
36
|
+
const dir = path.join(storageRoot, 'launches');
|
|
37
|
+
if (!dirExists(dir) || isLink(dir))
|
|
38
|
+
return [];
|
|
39
|
+
const found = [];
|
|
40
|
+
for (const name of fs.readdirSync(dir)) {
|
|
41
|
+
if (keep.includes(name))
|
|
42
|
+
continue;
|
|
43
|
+
const full = path.join(dir, name);
|
|
44
|
+
try {
|
|
45
|
+
const age = now - fs.lstatSync(full).mtimeMs;
|
|
46
|
+
if (age > days * DAY)
|
|
47
|
+
found.push({ path: full, ageDays: Math.floor(age / DAY) });
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
// gone while we looked
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return found;
|
|
54
|
+
}
|
|
55
|
+
const PidSchema = z.coerce.number().int().positive();
|
|
56
|
+
/** The history-sync watcher records in <storage>\run (watcher-<data set>.pid) with the age of each and of its log. */
|
|
57
|
+
export function watcherRecords(storageRoot, now) {
|
|
58
|
+
const dir = path.join(storageRoot, 'run');
|
|
59
|
+
if (!dirExists(dir) || isLink(dir))
|
|
60
|
+
return [];
|
|
61
|
+
const age = (p) => {
|
|
62
|
+
try {
|
|
63
|
+
return now - fs.statSync(p).mtimeMs;
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
return fs
|
|
70
|
+
.readdirSync(dir)
|
|
71
|
+
.map((name) => ({ name, m: /^watcher-(.+)\.pid$/i.exec(name) }))
|
|
72
|
+
.flatMap(({ name, m }) => {
|
|
73
|
+
if (!m)
|
|
74
|
+
return [];
|
|
75
|
+
const file = path.join(dir, name);
|
|
76
|
+
let pid = null;
|
|
77
|
+
try {
|
|
78
|
+
const parsed = PidSchema.safeParse(fs.readFileSync(file, 'utf8').trim());
|
|
79
|
+
pid = parsed.success ? parsed.data : null;
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return [];
|
|
83
|
+
}
|
|
84
|
+
return [
|
|
85
|
+
{
|
|
86
|
+
file,
|
|
87
|
+
pid,
|
|
88
|
+
ageMs: age(file) ?? 0,
|
|
89
|
+
logAgeMs: age(path.join(storageRoot, 'logs', 'host-sync-' + m[1] + '.log')),
|
|
90
|
+
},
|
|
91
|
+
];
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Watchers that run with no sandbox to watch: the process is alive and is a Node program (a recycled pid is somebody
|
|
96
|
+
* else's), no sandbox runs, and neither its record nor its log has changed for a while (a finishing watcher is spared).
|
|
97
|
+
*/
|
|
98
|
+
export function orphanWatchers(infos, o) {
|
|
99
|
+
if (o.running)
|
|
100
|
+
return [];
|
|
101
|
+
return infos.filter((w) => w.pid !== null &&
|
|
102
|
+
o.alive(w.pid) &&
|
|
103
|
+
o.image(w.pid)?.toLowerCase() === 'node.exe' &&
|
|
104
|
+
w.ageMs > WATCHER_MIN_AGE_MS &&
|
|
105
|
+
(w.logAgeMs === null || w.logAgeMs > WATCHER_LOG_QUIET_MS));
|
|
106
|
+
}
|
|
107
|
+
/** null when a small file can be created in the folder, else the reason (the file is removed again). */
|
|
108
|
+
export function writeProblem(dir) {
|
|
109
|
+
const probe = path.join(dir, '.write-test-' + process.pid);
|
|
110
|
+
try {
|
|
111
|
+
fs.writeFileSync(probe, '');
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
catch (e) {
|
|
115
|
+
return e.code ?? String(e);
|
|
116
|
+
}
|
|
117
|
+
finally {
|
|
118
|
+
fs.rmSync(probe, { force: true });
|
|
119
|
+
}
|
|
120
|
+
}
|