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
|
@@ -0,0 +1,470 @@
|
|
|
1
|
+
// doctor and repair: the engine (order, time limit, fixes, summary, JSON shape), the printer and the log, and the
|
|
2
|
+
// decisions of the new checks (install pointers and shims, stale records, run state, orphan watchers, permissions,
|
|
3
|
+
// PATH, settings backups) on temp folders. Nothing here touches this PC.
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { ALL_CHECKS } from '../cli/commands/repair/checks/index.js';
|
|
7
|
+
import { proxyProblem } from '../cli/commands/repair/checks/network.js';
|
|
8
|
+
import { HealthRun, summarize } from '../cli/commands/repair/engine.js';
|
|
9
|
+
import { logText, writeRepairLog } from '../cli/commands/repair/log.js';
|
|
10
|
+
import { HealthPrinter, summaryText } from '../cli/commands/repair/printer.js';
|
|
11
|
+
import { runHealth } from '../cli/commands/repair/run.js';
|
|
12
|
+
import { configBackups, findCommandCopies, fileOnDisk, groupEntries, inspectPointers, lineDiff, orphanWatchers, parseGroupMembers, parseIcacls, parseRegPath, pathHas, privacyProblems, repairPointers, runStateProblems, shimProblems, staleActiveRecords, staleLaunchFolders, watcherRecords, } from '../host/health/index.js';
|
|
13
|
+
import { InstallLayout, writeShims } from '../host/install/index.js';
|
|
14
|
+
import { ActiveRecords } from '../host/sharing/index.js';
|
|
15
|
+
const SID = 'S-1-5-21-1-2-3-1001';
|
|
16
|
+
function fakeSystem(over = {}) {
|
|
17
|
+
return {
|
|
18
|
+
processes: () => [],
|
|
19
|
+
pidAlive: () => false,
|
|
20
|
+
stop: () => undefined,
|
|
21
|
+
registryPath: () => null,
|
|
22
|
+
updateTaskExists: () => null,
|
|
23
|
+
icacls: () => null,
|
|
24
|
+
groupMembers: () => null,
|
|
25
|
+
tokenHasGroup: () => null,
|
|
26
|
+
reachable: () => Promise.resolve(true),
|
|
27
|
+
...over,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
function fakeEnv(t, name, over = {}) {
|
|
31
|
+
const storageRoot = t.dir('health', name, 'storage');
|
|
32
|
+
const dataPath = t.dir('health', name, 'storage', 'data', 'default');
|
|
33
|
+
const userDir = t.dir('health', name, 'user');
|
|
34
|
+
return {
|
|
35
|
+
ctx: {},
|
|
36
|
+
running: false,
|
|
37
|
+
dataPath,
|
|
38
|
+
storageRoot,
|
|
39
|
+
toolsDir: path.join(dataPath, 'tools'),
|
|
40
|
+
configError: null,
|
|
41
|
+
layout: null,
|
|
42
|
+
record: null,
|
|
43
|
+
machineHome: t.dir('health', name, 'machine'),
|
|
44
|
+
userDir,
|
|
45
|
+
configFile: path.join(userDir, 'config.json'),
|
|
46
|
+
migrations: [],
|
|
47
|
+
userInstallRoot: null,
|
|
48
|
+
system: fakeSystem(),
|
|
49
|
+
now: () => Date.now(),
|
|
50
|
+
facts: () => {
|
|
51
|
+
throw new Error('no facts in tests');
|
|
52
|
+
},
|
|
53
|
+
...over,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
const check = (id, group, over = {}) => ({
|
|
57
|
+
id,
|
|
58
|
+
group,
|
|
59
|
+
name: id,
|
|
60
|
+
run: () => ({ status: 'ok', detail: id + ' is fine' }),
|
|
61
|
+
...over,
|
|
62
|
+
});
|
|
63
|
+
const byId = (id) => {
|
|
64
|
+
const found = ALL_CHECKS.find((c) => c.id === id);
|
|
65
|
+
if (!found)
|
|
66
|
+
throw new Error('no check ' + id);
|
|
67
|
+
return found;
|
|
68
|
+
};
|
|
69
|
+
/** Looks with one real check and returns its result (and the run, to fix with). */
|
|
70
|
+
async function lookAt(env, id) {
|
|
71
|
+
const run = new HealthRun([byId(id)], env);
|
|
72
|
+
const [result] = await run.look();
|
|
73
|
+
return { run, result };
|
|
74
|
+
}
|
|
75
|
+
async function engine(t) {
|
|
76
|
+
t.section('engine: order, time limit, fixes, summary');
|
|
77
|
+
const env = fakeEnv(t, 'engine');
|
|
78
|
+
const started = [];
|
|
79
|
+
const ended = [];
|
|
80
|
+
const order = new HealthRun([check('net', 'Network'), check('inst-a', 'Install'), check('pc', 'This PC'), check('inst-b', 'Install')], env, { lookStart: (c) => started.push(c.id), lookEnd: (r) => ended.push(r.id) });
|
|
81
|
+
const looked = await order.look();
|
|
82
|
+
t.equal(looked.map((r) => r.id), ['pc', 'inst-a', 'inst-b', 'net'], 'checks run grouped by heading, in listed order');
|
|
83
|
+
t.equal(started, ended, 'each check is announced before its result');
|
|
84
|
+
t.check(looked.every((r) => r.status === 'ok' && !r.fixable), 'a check that finds nothing is ok and not fixable');
|
|
85
|
+
const slow = new HealthRun([
|
|
86
|
+
check('slow', 'Network', { timeoutMs: 30, run: () => new Promise(() => undefined) }),
|
|
87
|
+
check('after', 'Network'),
|
|
88
|
+
], env);
|
|
89
|
+
const slowResults = await slow.look();
|
|
90
|
+
t.equal([slowResults[0].status, slowResults[0].detail, slowResults[0].timedOut], ['skipped', 'took too long, skipped', true], 'a check that never answers is skipped after its time limit');
|
|
91
|
+
t.equal(slowResults[1].status, 'ok', 'the checks after a stuck one still run');
|
|
92
|
+
const crash = new HealthRun([
|
|
93
|
+
check('crash', 'Data folder', {
|
|
94
|
+
run: () => {
|
|
95
|
+
throw new Error('disk on fire');
|
|
96
|
+
},
|
|
97
|
+
}),
|
|
98
|
+
], env);
|
|
99
|
+
const crashed = (await crash.look())[0];
|
|
100
|
+
t.check(crashed.status === 'needs-you' && crashed.detail.includes('disk on fire'), 'a check that throws is reported, not fatal');
|
|
101
|
+
t.section('engine: fixes');
|
|
102
|
+
const state = { fixed: false, stuck: 0 };
|
|
103
|
+
const bad = (plan, over = {}) => ({
|
|
104
|
+
status: 'needs-you',
|
|
105
|
+
detail: 'broken',
|
|
106
|
+
plan,
|
|
107
|
+
means: 'it hurts',
|
|
108
|
+
...over,
|
|
109
|
+
});
|
|
110
|
+
const checks = [
|
|
111
|
+
check('throws', 'Data folder', {
|
|
112
|
+
run: () => bad('mend it'),
|
|
113
|
+
fix: () => {
|
|
114
|
+
throw new Error('boom');
|
|
115
|
+
},
|
|
116
|
+
}),
|
|
117
|
+
check('works', 'Data folder', {
|
|
118
|
+
run: () => (state.fixed ? { status: 'ok', detail: 'whole' } : bad('mend it for real', { detail: 'cracked' })),
|
|
119
|
+
fix: () => {
|
|
120
|
+
state.fixed = true;
|
|
121
|
+
return { done: 'glued it' };
|
|
122
|
+
},
|
|
123
|
+
}),
|
|
124
|
+
check('still', 'Data folder', { run: () => bad('try'), fix: () => ({ done: 'tried' }) }),
|
|
125
|
+
check('manual', 'Data folder', { run: () => bad(undefined, { fix: 'do it yourself' }) }),
|
|
126
|
+
check('idle', 'Sandbox', { needsIdle: 'fix', run: () => bad('only when closed'), fix: () => ({ done: 'x' }) }),
|
|
127
|
+
];
|
|
128
|
+
const running = { ...env, running: true };
|
|
129
|
+
const run = new HealthRun(checks, running);
|
|
130
|
+
const found = await run.look();
|
|
131
|
+
t.equal(found.map((r) => r.fixable), [true, true, true, false, true], 'fixable means: a plan and a fix function (a check with only advice is not)');
|
|
132
|
+
t.equal(summarize(found).fixable, 4, 'the summary counts what can be fixed');
|
|
133
|
+
const fixed = await run.fix(found);
|
|
134
|
+
const by = Object.fromEntries(fixed.map((r) => [r.id, r]));
|
|
135
|
+
t.equal([by.throws.status, by.throws.error], ['needs-you', 'boom'], 'a fix that throws leaves the problem and says why');
|
|
136
|
+
t.equal([by.works.status, by.works.applied?.done, by.works.applied?.before, by.works.applied?.after], ['fixed', 'glued it', 'cracked', 'whole'], 'a fix that works is checked again and keeps before and after');
|
|
137
|
+
t.check(by.still.status === 'needs-you' && (by.still.error ?? '').includes('still not right'), 'a fix that changes nothing is not called fixed');
|
|
138
|
+
t.equal(by.manual.status, 'needs-you', 'advice-only checks are left alone');
|
|
139
|
+
t.equal(by.idle.status, 'skipped', 'a fix that needs no running sandbox waits while one runs');
|
|
140
|
+
const final = summarize(fixed);
|
|
141
|
+
t.equal([final.fixed, final.needsYou, final.skipped, final.total], [1, 3, 1, 5], 'the summary counts fixed, need you and skipped after the fixes');
|
|
142
|
+
t.section('summary text and JSON shape');
|
|
143
|
+
t.equal(summaryText({ total: 9, ok: 6, fixed: 1, warn: 1, needsYou: 1, skipped: 0, fixable: 0 }), '6 ok, 1 fixed, 1 needs you, 1 warning', 'summary line');
|
|
144
|
+
t.equal(summaryText({ total: 3, ok: 0, fixed: 0, warn: 0, needsYou: 2, skipped: 1, fixable: 0 }), '0 ok, 2 need you, 1 skipped', 'summary line plurals');
|
|
145
|
+
const json = JSON.parse(JSON.stringify(by.works));
|
|
146
|
+
t.check(['id', 'group', 'name', 'status', 'detail', 'fixable', 'ms', 'applied'].every((k) => k in json), 'a result prints as JSON with id, group, name, status, detail, fixable, ms (and applied after a fix)', Object.keys(json).join(', '));
|
|
147
|
+
t.section('doctor and repair decisions');
|
|
148
|
+
const calls = [];
|
|
149
|
+
const fixCheck = () => [
|
|
150
|
+
check('f', 'Data folder', {
|
|
151
|
+
run: () => (calls.includes('fix') ? { status: 'ok', detail: 'ok' } : bad('mend')),
|
|
152
|
+
fix: () => {
|
|
153
|
+
calls.push('fix');
|
|
154
|
+
return { done: 'mended' };
|
|
155
|
+
},
|
|
156
|
+
}),
|
|
157
|
+
];
|
|
158
|
+
const ask = (answer) => () => {
|
|
159
|
+
calls.push('asked');
|
|
160
|
+
return Promise.resolve(answer);
|
|
161
|
+
};
|
|
162
|
+
const plan = { yes: false, ask: false, question: (n) => n + '?' };
|
|
163
|
+
calls.length = 0;
|
|
164
|
+
let report = await runHealth(env, null, plan, fixCheck(), ask(true));
|
|
165
|
+
t.equal([report.changed, calls], [false, []], 'without --yes and without a terminal nothing is changed or asked');
|
|
166
|
+
calls.length = 0;
|
|
167
|
+
report = await runHealth(env, null, { ...plan, ask: true }, fixCheck(), ask(false));
|
|
168
|
+
t.equal([report.changed, calls], [false, ['asked']], 'a "no" changes nothing');
|
|
169
|
+
calls.length = 0;
|
|
170
|
+
report = await runHealth(env, null, { ...plan, ask: true }, fixCheck(), ask(true));
|
|
171
|
+
t.equal([report.changed, report.summary.fixed, calls], [true, 1, ['asked', 'fix']], 'a "yes" fixes, then looks again');
|
|
172
|
+
calls.length = 0;
|
|
173
|
+
report = await runHealth(env, null, { ...plan, yes: true }, fixCheck(), ask(false));
|
|
174
|
+
t.equal([report.changed, calls], [true, ['fix']], '--yes fixes without asking');
|
|
175
|
+
t.section('printer and log');
|
|
176
|
+
const out = [];
|
|
177
|
+
const printer = new HealthPrinter({ write: (s) => void out.push(s) }, false);
|
|
178
|
+
calls.length = 0;
|
|
179
|
+
await runHealth(env, printer, { ...plan, yes: true }, [
|
|
180
|
+
check('p1', 'This PC', { name: 'Windows' }),
|
|
181
|
+
check('p2', 'Data folder', {
|
|
182
|
+
name: 'Cracks',
|
|
183
|
+
run: () => calls.includes('cracks') ? { status: 'ok', detail: 'whole' } : bad('mend the cracks', { fix: undefined }),
|
|
184
|
+
fix: () => {
|
|
185
|
+
calls.push('cracks');
|
|
186
|
+
return { done: 'mended the cracks' };
|
|
187
|
+
},
|
|
188
|
+
}),
|
|
189
|
+
check('p3', 'Network', {
|
|
190
|
+
name: 'GitHub',
|
|
191
|
+
run: () => ({ status: 'warn', detail: 'no answer', fix: 'check your VPN' }),
|
|
192
|
+
}),
|
|
193
|
+
]);
|
|
194
|
+
const text = out.join('');
|
|
195
|
+
t.includes(text, '\nThis PC\n [ok] Windows: p1 is fine', 'headings, then one line per check with a status mark');
|
|
196
|
+
t.includes(text, 'What it means: it hurts', 'a problem says what it means');
|
|
197
|
+
t.includes(text, 'Fix: sandbox repair will mend the cracks', 'a fixable problem says repair will fix it');
|
|
198
|
+
t.includes(text, 'Fix: check your VPN', 'a problem repair cannot mend says what to do');
|
|
199
|
+
t.includes(text, 'Repair will change:', 'the changes are listed before they are made');
|
|
200
|
+
t.includes(text, '[fixed] Cracks: mended the cracks', 'a fix is reported on its own line');
|
|
201
|
+
const storage = fakeEnv(t, 'log').storageRoot;
|
|
202
|
+
const when = new Date(2026, 9, 15, 9, 30, 12);
|
|
203
|
+
const sample = await runHealth(env, null, { ...plan, yes: true }, fixCheck());
|
|
204
|
+
const logFile = writeRepairLog(storage, logText({
|
|
205
|
+
when,
|
|
206
|
+
version: '1.5.0',
|
|
207
|
+
command: 'sandbox repair --yes',
|
|
208
|
+
found: sample.found,
|
|
209
|
+
final: sample.final,
|
|
210
|
+
summary: sample.summary,
|
|
211
|
+
}), when);
|
|
212
|
+
t.equal(logFile && path.basename(logFile), 'repair-20261015-093012.log', 'the repair log is named by the time');
|
|
213
|
+
const logged = logFile ? fs.readFileSync(logFile, 'utf8') : '';
|
|
214
|
+
t.check(logged.includes('changed: mended') && logged.includes('before: broken') && logged.includes('after: ok'), 'the log has what was found, changed, before and after');
|
|
215
|
+
t.equal(writeRepairLog(path.join(storage, 'missing'), 'x', when), null, 'no log is written when the storage folder does not exist');
|
|
216
|
+
}
|
|
217
|
+
function installChecks(t, mode) {
|
|
218
|
+
const root = t.dir('health', 'install-' + mode, 'WindowsDevelopmentSandbox');
|
|
219
|
+
const layout = InstallLayout.at(root, 'user');
|
|
220
|
+
const pkg = (v, bin) => {
|
|
221
|
+
t.writeJson(path.join(layout.packageDir(v), 'package.json'), { name: 'x', version: v, bin });
|
|
222
|
+
};
|
|
223
|
+
pkg('1.4.0', { sandbox: 'dist/bin/sandbox.js' });
|
|
224
|
+
pkg('1.5.0', { sandbox: 'dist/bin/sandbox.js', 'agent-sandbox': 'dist/bin/agent-sandbox.js' });
|
|
225
|
+
t.dir('health', 'install-' + mode, 'WindowsDevelopmentSandbox', 'versions', '1.6.0', 'staging');
|
|
226
|
+
t.write(layout.nodeExe('v22.1.0'), 'x');
|
|
227
|
+
t.write(layout.nodeExe('v22.11.0'), 'x');
|
|
228
|
+
t.dir('health', 'install-' + mode, 'WindowsDevelopmentSandbox', 'node', 'v24.0.0');
|
|
229
|
+
const env = fakeEnv(t, 'install-' + mode, { layout });
|
|
230
|
+
return mode === 'pointers' ? pointers(t, layout, env) : shims(t, layout, env);
|
|
231
|
+
}
|
|
232
|
+
async function pointers(t, layout, env) {
|
|
233
|
+
t.section('install: version and Node pointers');
|
|
234
|
+
t.write(path.join(layout.root, 'active-version'), '1.9.9\n');
|
|
235
|
+
t.write(path.join(layout.root, 'active-node'), 'v22.1.0\n');
|
|
236
|
+
let state = inspectPointers(layout);
|
|
237
|
+
t.equal(state.versions, ['1.4.0', '1.5.0'], 'only versions with a package.json count as installed');
|
|
238
|
+
t.equal(state.nodes, ['v22.1.0', 'v22.11.0'], 'only Node folders with node.exe count as installed');
|
|
239
|
+
t.check(state.problems.length === 1 && state.problems[0].includes('1.9.9'), 'a pointer at a version that is not installed is found');
|
|
240
|
+
t.equal(state.want, { version: '1.5.0', node: 'v22.1.0' }, 'the wanted pointers keep a good Node and take the newest whole version');
|
|
241
|
+
const { run, result } = await lookAt(env, 'install-pointers');
|
|
242
|
+
t.check(result.status === 'needs-you' && result.fixable && (result.plan ?? '').includes('1.5.0'), 'the check offers to repoint');
|
|
243
|
+
const fixed = await run.fix([result]);
|
|
244
|
+
t.equal([fixed[0].status, layout.activeVersion(), layout.activeNode()], ['fixed', '1.5.0', 'v22.1.0'], 'the fix moves only the pointers');
|
|
245
|
+
t.equal(layout.installedVersions(), ['1.4.0', '1.5.0', '1.6.0'], 'no version is deleted');
|
|
246
|
+
fs.rmSync(path.join(layout.root, 'active-node'));
|
|
247
|
+
state = inspectPointers(layout);
|
|
248
|
+
t.check(state.problems[0].includes('active-node is missing'), 'a missing pointer file is found');
|
|
249
|
+
t.equal(repairPointers(layout, state), 'active-version 1.5.0, active-node v22.11.0', 'a missing Node pointer takes the newest Node');
|
|
250
|
+
t.write(path.join(layout.root, 'active-version'), 'banana');
|
|
251
|
+
t.check(inspectPointers(layout).problems[0].includes('says "banana"'), 'a pointer that is not a version number is named');
|
|
252
|
+
}
|
|
253
|
+
async function shims(t, layout, env) {
|
|
254
|
+
t.section('install: shims');
|
|
255
|
+
layout.setActive('1.5.0', 'v22.11.0');
|
|
256
|
+
const bins = { sandbox: 'dist/bin/sandbox.js', 'agent-sandbox': 'dist/bin/agent-sandbox.js' };
|
|
257
|
+
writeShims(layout, bins);
|
|
258
|
+
t.equal(shimProblems(layout, bins), { missing: [], different: [] }, 'freshly written shims are fine');
|
|
259
|
+
fs.rmSync(path.join(layout.binDir, 'sandbox.cmd'));
|
|
260
|
+
fs.writeFileSync(path.join(layout.binDir, 'agent-sandbox'), '#!/bin/sh\necho old\n');
|
|
261
|
+
const problems = shimProblems(layout, bins);
|
|
262
|
+
t.equal([problems.missing, problems.different], [['sandbox.cmd'], ['agent-sandbox']], 'a missing and an out-of-date shim are found');
|
|
263
|
+
const { run, result } = await lookAt(env, 'install-shims');
|
|
264
|
+
t.check(result.status === 'needs-you' && result.fixable, 'the shims check offers to rewrite them');
|
|
265
|
+
const fixed = await run.fix([result]);
|
|
266
|
+
t.equal(fixed[0].status, 'fixed', 'rewriting the shims fixes it');
|
|
267
|
+
t.equal(shimProblems(layout, bins), { missing: [], different: [] }, 'the shims read the active version again');
|
|
268
|
+
fs.writeFileSync(path.join(layout.binDir, 'extra.cmd'), 'echo mine');
|
|
269
|
+
t.check(fs.existsSync(path.join(layout.binDir, 'extra.cmd')), 'a file that is not one of ours is left alone');
|
|
270
|
+
}
|
|
271
|
+
async function copiesOnPath(t) {
|
|
272
|
+
t.section('install: copies of the commands on the PATH');
|
|
273
|
+
const pf = t.dir('copies', 'Program Files');
|
|
274
|
+
const managedBin = path.join(pf, 'WindowsDevelopmentSandbox', 'bin');
|
|
275
|
+
const npmBin = t.dir('copies', 'nodejs');
|
|
276
|
+
const oldBin = path.join(t.dir('copies', 'Scripts'), 'agent-sandbox', 'bin');
|
|
277
|
+
t.write(path.join(npmBin, 'sandbox.cmd'), '@echo off');
|
|
278
|
+
t.write(path.join(npmBin, 'sandbox'), '#!/bin/sh');
|
|
279
|
+
t.write(path.join(npmBin, 'node_modules', 'windows-development-sandbox', 'package.json'), '{}');
|
|
280
|
+
t.write(path.join(managedBin, 'sandbox.cmd'), '@echo off');
|
|
281
|
+
t.write(path.join(managedBin, 'claude-sbx.cmd'), '@echo off');
|
|
282
|
+
t.write(path.join(managedBin, 'claude-sbx.exe'), '');
|
|
283
|
+
t.write(path.join(oldBin, 'claude-sbx.cmd'), '@echo off');
|
|
284
|
+
const machinePath = [npmBin, managedBin].join(';');
|
|
285
|
+
const userPath = [oldBin, npmBin, t.dir('copies', 'empty')].join(';');
|
|
286
|
+
const pathext = '.COM;.EXE;.BAT;.CMD';
|
|
287
|
+
const found = findCommandCopies({
|
|
288
|
+
machinePath,
|
|
289
|
+
userPath,
|
|
290
|
+
pathext,
|
|
291
|
+
programFiles: pf,
|
|
292
|
+
localAppData: null,
|
|
293
|
+
exists: fileOnDisk,
|
|
294
|
+
});
|
|
295
|
+
t.equal(found.map((c) => [c.kind, c.files.join(',')]), [
|
|
296
|
+
['npm', 'sandbox.cmd'],
|
|
297
|
+
['managed-machine', 'sandbox.cmd,claude-sbx.exe'],
|
|
298
|
+
['old-scripts', 'claude-sbx.cmd'],
|
|
299
|
+
], 'copies come in PATH order (machine first, a folder once), each command as its PATHEXT match, extensionless files ignored');
|
|
300
|
+
const asked = [];
|
|
301
|
+
const saved = process.env.ProgramFiles;
|
|
302
|
+
process.env.ProgramFiles = pf;
|
|
303
|
+
try {
|
|
304
|
+
const system = fakeSystem({
|
|
305
|
+
registryPath: (scope) => {
|
|
306
|
+
asked.push(scope);
|
|
307
|
+
return scope === 'machine' ? machinePath : userPath;
|
|
308
|
+
},
|
|
309
|
+
});
|
|
310
|
+
const wrongOne = fakeEnv(t, 'copies-wrong', { system, ctx: { packageRoot: managedBin } });
|
|
311
|
+
const { result } = await lookAt(wrongOne, 'install-copies');
|
|
312
|
+
t.check(result.status === 'needs-you' &&
|
|
313
|
+
!result.fixable &&
|
|
314
|
+
result.detail.includes(npmBin) &&
|
|
315
|
+
(result.lines ?? []).some((l) => l.includes('npm uninstall -g windows-development-sandbox')) &&
|
|
316
|
+
(result.lines ?? []).some((l) => l.includes('Remove-Item -Recurse -Force') && l.includes('agent-sandbox')), 'an npm copy in front of the install is named as the one that runs, with the exact removal of each other copy');
|
|
317
|
+
t.check(!(result.lines ?? []).some((l) => l.startsWith(managedBin)), 'the managed install is the one to keep: it is never offered for removal');
|
|
318
|
+
const alone = fakeEnv(t, 'copies-alone', {
|
|
319
|
+
system: fakeSystem({ registryPath: (scope) => (scope === 'machine' ? managedBin : '') }),
|
|
320
|
+
ctx: { packageRoot: managedBin },
|
|
321
|
+
});
|
|
322
|
+
t.equal((await lookAt(alone, 'install-copies')).result.status, 'ok', 'a single copy is fine');
|
|
323
|
+
}
|
|
324
|
+
finally {
|
|
325
|
+
if (saved === undefined)
|
|
326
|
+
delete process.env.ProgramFiles;
|
|
327
|
+
else
|
|
328
|
+
process.env.ProgramFiles = saved;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
async function staleState(t) {
|
|
332
|
+
t.section('stale records, run state, launch folders, watchers');
|
|
333
|
+
const record = (sid, session) => ({
|
|
334
|
+
user: 'me',
|
|
335
|
+
sid,
|
|
336
|
+
windowsSession: session,
|
|
337
|
+
dataPath: 'C:\\x',
|
|
338
|
+
shared: false,
|
|
339
|
+
startedAt: new Date().toISOString(),
|
|
340
|
+
version: '1.5.0',
|
|
341
|
+
projects: [],
|
|
342
|
+
});
|
|
343
|
+
const rows = [{ image: 'WindowsSandboxClient.exe', pid: 10, session: 1 }];
|
|
344
|
+
t.equal(staleActiveRecords([record(SID, 1), record(SID.replace('1001', '1002'), 2)], rows).map((r) => r.windowsSession), [2], 'a record whose session has no sandbox is stale');
|
|
345
|
+
t.equal(staleActiveRecords([record(SID, 1)], []).length, 1, 'with no sandbox at all every record is stale');
|
|
346
|
+
const machine = fakeEnv(t, 'machine', {
|
|
347
|
+
layout: InstallLayout.at(t.dir('health', 'machine-install'), 'machine'),
|
|
348
|
+
record: { scope: 'machine', groupSid: null, autoUpdate: false, installedAt: 'x' },
|
|
349
|
+
});
|
|
350
|
+
const records = new ActiveRecords(path.join(machine.machineHome, 'active'));
|
|
351
|
+
records.write(record(SID, 3));
|
|
352
|
+
const { run, result } = await lookAt({ ...machine, system: fakeSystem({ processes: () => [] }) }, 'machine-active-records');
|
|
353
|
+
t.check(result.status === 'needs-you' && result.fixable, 'a stale sharing record is found');
|
|
354
|
+
const fixed = await run.fix([result]);
|
|
355
|
+
t.equal([fixed[0].status, records.readAll().length], ['fixed', 0], 'the fix removes the record');
|
|
356
|
+
const env = fakeEnv(t, 'runstate');
|
|
357
|
+
t.equal(runStateProblems(env.dataPath).any, false, 'an empty run folder is clean');
|
|
358
|
+
t.write(path.join(env.dataPath, 'run', 'requests', 'junk.json'), 'not json');
|
|
359
|
+
t.write(path.join(env.dataPath, 'run', 'sandbox.json'), JSON.stringify({
|
|
360
|
+
launchId: 'l1',
|
|
361
|
+
phase: 'booting',
|
|
362
|
+
message: 'x',
|
|
363
|
+
updatedAt: new Date().toISOString(),
|
|
364
|
+
workspaceHost: 'C:\\w',
|
|
365
|
+
guestWorkspace: 'C:\\Workspace\\w',
|
|
366
|
+
headless: false,
|
|
367
|
+
}));
|
|
368
|
+
const problems = runStateProblems(env.dataPath);
|
|
369
|
+
t.equal([problems.requests, problems.unfinishedBoot], [1, 'booting'], 'an unanswered request and an unfinished boot are found');
|
|
370
|
+
const looked = await lookAt(env, 'sandbox-run-state');
|
|
371
|
+
t.check(looked.result.fixable, 'the run state of a closed sandbox can be cleared');
|
|
372
|
+
const cleared = await looked.run.fix([looked.result]);
|
|
373
|
+
t.equal([cleared[0].status, runStateProblems(env.dataPath).any], ['fixed', false], 'clearing it leaves a clean run folder');
|
|
374
|
+
const runningEnv = { ...env, running: true };
|
|
375
|
+
t.equal((await lookAt(runningEnv, 'sandbox-run-state')).result.status, 'ok', 'while a sandbox runs its state is left alone');
|
|
376
|
+
const launches = t.dir('health', 'launches', 'storage');
|
|
377
|
+
const oldLaunch = t.dir('health', 'launches', 'storage', 'launches', 'old');
|
|
378
|
+
t.dir('health', 'launches', 'storage', 'launches', 'new');
|
|
379
|
+
const longAgo = new Date(Date.now() - 20 * 24 * 3600 * 1000);
|
|
380
|
+
fs.utimesSync(oldLaunch, longAgo, longAgo);
|
|
381
|
+
t.equal(staleLaunchFolders(launches, 7, Date.now()).map((o) => path.basename(o.path)), ['old'], 'only launch folders older than 7 days are stale');
|
|
382
|
+
t.equal(staleLaunchFolders(launches, 7, Date.now(), ['old']).length, 0, 'a launch folder in use is kept');
|
|
383
|
+
const wenv = fakeEnv(t, 'watchers');
|
|
384
|
+
const pidFile = path.join(wenv.storageRoot, 'run', 'watcher-default.pid');
|
|
385
|
+
t.write(pidFile, '4242');
|
|
386
|
+
fs.utimesSync(pidFile, longAgo, longAgo);
|
|
387
|
+
const infos = watcherRecords(wenv.storageRoot, Date.now());
|
|
388
|
+
t.equal([infos.length, infos[0].pid], [1, 4242], 'watcher records are read with their pid');
|
|
389
|
+
const alive = (pid) => pid === 4242;
|
|
390
|
+
const node = () => 'node.exe';
|
|
391
|
+
t.equal(orphanWatchers(infos, { running: false, alive, image: node }).length, 1, 'an old watcher with no sandbox is an orphan');
|
|
392
|
+
t.equal(orphanWatchers(infos, { running: true, alive, image: node }).length, 0, 'with a sandbox running no watcher is an orphan');
|
|
393
|
+
t.equal(orphanWatchers(infos, { running: false, alive, image: () => 'chrome.exe' }).length, 0, 'a pid that is another program is never touched');
|
|
394
|
+
t.equal(orphanWatchers(infos, { running: false, alive: () => false, image: node }).length, 0, 'a watcher that is gone is not an orphan');
|
|
395
|
+
t.equal(orphanWatchers([{ ...infos[0], ageMs: 60_000 }], { running: false, alive, image: node }).length, 0, 'a young watcher is spared');
|
|
396
|
+
t.equal(orphanWatchers([{ ...infos[0], logAgeMs: 30_000 }], { running: false, alive, image: node }).length, 0, 'a watcher still writing its log is spared');
|
|
397
|
+
const stopped = [];
|
|
398
|
+
const live = new Set([4242]);
|
|
399
|
+
const system = fakeSystem({
|
|
400
|
+
processes: () => [{ image: 'node.exe', pid: 4242, session: 1 }],
|
|
401
|
+
pidAlive: (pid) => live.has(pid),
|
|
402
|
+
stop: (pid) => {
|
|
403
|
+
stopped.push(pid);
|
|
404
|
+
live.delete(pid);
|
|
405
|
+
},
|
|
406
|
+
});
|
|
407
|
+
const orphan = await lookAt({ ...wenv, system }, 'sandbox-orphan-watchers');
|
|
408
|
+
t.check(orphan.result.status === 'needs-you' && orphan.result.fixable, 'the check finds the orphaned watcher');
|
|
409
|
+
const stoppedResult = await orphan.run.fix([orphan.result]);
|
|
410
|
+
t.equal([stoppedResult[0].status, stopped, fs.existsSync(pidFile)], ['fixed', [4242], false], 'the fix stops it and removes its record');
|
|
411
|
+
}
|
|
412
|
+
function parsing(t) {
|
|
413
|
+
t.section('permissions, PATH, group and settings backups');
|
|
414
|
+
const dir = 'C:\\ProgramData\\AgentSandbox\\users\\me';
|
|
415
|
+
const private_ = [
|
|
416
|
+
dir + ' NT AUTHORITY\\SYSTEM:(OI)(CI)(F)',
|
|
417
|
+
' BUILTIN\\Administrators:(OI)(CI)(F)',
|
|
418
|
+
' PC\\me:(OI)(CI)(F)',
|
|
419
|
+
'',
|
|
420
|
+
'Successfully processed 1 files; Failed processing 0 files',
|
|
421
|
+
].join('\r\n');
|
|
422
|
+
const entries = parseIcacls(private_, dir);
|
|
423
|
+
t.equal(entries.map((e) => e.principal), ['NT AUTHORITY\\SYSTEM', 'BUILTIN\\Administrators', 'PC\\me'], 'icacls entries are read');
|
|
424
|
+
t.equal(privacyProblems(entries, 'Agent Sandbox Users'), [], 'SYSTEM, Administrators and you only is private');
|
|
425
|
+
const open = parseIcacls([dir + ' PC\\Agent Sandbox Users:(RX)', ' BUILTIN\\Users:(I)(OI)(CI)(RX)', ' PC\\me:(I)(F)'].join('\n'), dir);
|
|
426
|
+
t.equal(privacyProblems(open, 'Agent Sandbox Users'), ['PC\\Agent Sandbox Users', 'BUILTIN\\Users (inherited)', 'PC\\me (inherited)'], 'broad and inherited entries break privacy');
|
|
427
|
+
t.equal(groupEntries(open, 'Agent Sandbox Users').length, 1, 'the sharing group is found by name');
|
|
428
|
+
t.check(pathHas('C:\\Windows;"C:\\Tools\\Bin\\";%SystemRoot%\\x', 'c:\\tools\\bin') &&
|
|
429
|
+
pathHas('%SystemRoot%\\x', process.env.SystemRoot + '\\x'), 'PATH entries match ignoring case, quotes, a trailing slash and %VARIABLES%');
|
|
430
|
+
t.check(!pathHas('C:\\Windows', 'C:\\Tools\\bin') && !pathHas(null, 'C:\\x'), 'a folder that is not on PATH is not found');
|
|
431
|
+
t.equal(parseRegPath('\r\nHKEY_CURRENT_USER\\Environment\r\n Path REG_EXPAND_SZ %USERPROFILE%\\bin;C:\\x\r\n'), '%USERPROFILE%\\bin;C:\\x', 'the PATH value is read from reg query');
|
|
432
|
+
t.equal(parseGroupMembers('Alias name Agent Sandbox Users\r\nComment\r\n\r\nMembers\r\n\r\n-------\r\nPC\\me\r\nPC\\you\r\nThe command completed successfully.\r\n'), ['PC\\me', 'PC\\you'], 'the members of the sharing group are read');
|
|
433
|
+
t.check(proxyProblem('http://proxy.example.com:8080') === null && proxyProblem('proxy:3128') === null, 'proxy addresses are accepted');
|
|
434
|
+
t.check(proxyProblem('http://') !== null, 'an empty proxy address is refused');
|
|
435
|
+
}
|
|
436
|
+
function backups(t) {
|
|
437
|
+
const storage = t.dir('health', 'backups', 'storage');
|
|
438
|
+
const config = path.join(t.dir('health', 'backups', 'user'), 'config.json');
|
|
439
|
+
const make = (name, body, original = config) => {
|
|
440
|
+
const folder = path.join(storage, 'backups', name);
|
|
441
|
+
const copy = path.join(folder, 'files', '0-config.json');
|
|
442
|
+
t.write(copy, body);
|
|
443
|
+
t.writeJson(path.join(folder, 'manifest.json'), { entries: [{ original, copy, existed: true }] });
|
|
444
|
+
return copy;
|
|
445
|
+
};
|
|
446
|
+
const older = make('migrate-1.3.0-1.4.0-20260901080000', '{ "a": 1 }');
|
|
447
|
+
const newer = make('migrate-1.4.0-1.5.0-20261014093012', '{ "a": 2 }');
|
|
448
|
+
make('migrate-1.4.0-1.5.0-20261015000000', '{ "b": 1 }', path.join(storage, 'other.json'));
|
|
449
|
+
const outside = t.write(path.join(storage, 'elsewhere.json'), '{}');
|
|
450
|
+
t.writeJson(path.join(storage, 'backups', 'migrate-9.0.0-9.0.1-20270101000000', 'manifest.json'), {
|
|
451
|
+
entries: [{ original: config, copy: outside, existed: true }],
|
|
452
|
+
});
|
|
453
|
+
const found = configBackups(storage, config);
|
|
454
|
+
t.equal(found.map((b) => b.copy), [newer, older], 'saved copies of the settings file come newest first');
|
|
455
|
+
t.equal(found[0].label, '1.4.0 to 1.5.0 at 2026-10-14 09:30:12', 'a backup is labelled with its versions and time');
|
|
456
|
+
t.equal(lineDiff('{\n "a": 1,\n "x": 3\n}', '{\n "a": 2,\n "x": 3\n}'), ['- "a": 1,', '+ "a": 2,'], 'the difference shows the lines only in one file');
|
|
457
|
+
}
|
|
458
|
+
export const healthSuite = {
|
|
459
|
+
name: 'health',
|
|
460
|
+
title: 'doctor and repair (engine, printer, log, the install, stale state, permissions, settings backups)',
|
|
461
|
+
run: async (t) => {
|
|
462
|
+
await engine(t);
|
|
463
|
+
await installChecks(t, 'pointers');
|
|
464
|
+
await installChecks(t, 'shims');
|
|
465
|
+
await copiesOnPath(t);
|
|
466
|
+
await staleState(t);
|
|
467
|
+
parsing(t);
|
|
468
|
+
backups(t);
|
|
469
|
+
},
|
|
470
|
+
};
|
|
@@ -35,6 +35,8 @@ const COMMANDS = [
|
|
|
35
35
|
sandbox('sessions', 'sessions'),
|
|
36
36
|
sandbox('doctor', 'doctor'),
|
|
37
37
|
sandbox('account list', 'account', 'list'),
|
|
38
|
+
sandbox('vpn status', 'vpn', 'status'),
|
|
39
|
+
sandbox('vpn status --json', 'vpn', 'status', '--json'),
|
|
38
40
|
sandbox('account use nobody', 'account', 'use', 'nobody'),
|
|
39
41
|
{ bin: 'agent-sandbox', label: 'config path', args: ['config', 'path'] },
|
|
40
42
|
sandbox('config show', 'config', 'show'),
|
package/dist/selftest/index.js
CHANGED
|
@@ -11,12 +11,16 @@ import { createFixtures } from './support/fixtures.js';
|
|
|
11
11
|
import { createHarness } from './support/harness.js';
|
|
12
12
|
import { liveTestsAvailable } from './support/live.js';
|
|
13
13
|
import { accountsSuite } from './accounts.test.js';
|
|
14
|
+
import { credentialsSuite } from './credentials.test.js';
|
|
15
|
+
import { vpnSuite } from './vpn.test.js';
|
|
14
16
|
import { agentCommandsSuite } from './agent-commands.test.js';
|
|
15
17
|
import { channelSuite } from './channel.test.js';
|
|
16
18
|
import { cliSuite } from './cli.test.js';
|
|
19
|
+
import { agentPathsSuite } from './agent-paths.test.js';
|
|
17
20
|
import { cmdlineSuite } from './cmdline.test.js';
|
|
18
21
|
import { configSuite } from './config.test.js';
|
|
19
22
|
import { migrateSuite } from './migrate.test.js';
|
|
23
|
+
import { storageSuite } from './storage.test.js';
|
|
20
24
|
import { updateSuite } from './update.test.js';
|
|
21
25
|
import { installSuite } from './install.test.js';
|
|
22
26
|
import { sharedUseSuite } from './shared-use.test.js';
|
|
@@ -39,12 +43,14 @@ import { networkSuite } from './network.test.js';
|
|
|
39
43
|
import { personalizationSuite } from './personalization.test.js';
|
|
40
44
|
import { projectsSuite } from './projects.test.js';
|
|
41
45
|
import { repairSuite } from './repair.test.js';
|
|
46
|
+
import { healthSuite } from './health.test.js';
|
|
42
47
|
import { repoGuardSuite } from './repo-guard.test.js';
|
|
43
48
|
import { secretsSuite } from './secrets.test.js';
|
|
44
49
|
import { setupSuite } from './setup.test.js';
|
|
45
50
|
import { syncSuite } from './sync.test.js';
|
|
46
51
|
import { testGuardSuite } from './test-guard.test.js';
|
|
47
52
|
import { toolchainsSuite } from './toolchains.test.js';
|
|
53
|
+
import { webSuite } from './web.test.js';
|
|
48
54
|
import { workspaceSuite } from './workspace.test.js';
|
|
49
55
|
import { workspaceViewSuite } from './workspace-view.test.js';
|
|
50
56
|
import { liveNetworkSuite } from './live-network.test.js';
|
|
@@ -58,6 +64,7 @@ export const SUITES = [
|
|
|
58
64
|
// settings and project files
|
|
59
65
|
configSuite,
|
|
60
66
|
migrateSuite,
|
|
67
|
+
storageSuite,
|
|
61
68
|
updateSuite,
|
|
62
69
|
updateServiceSuite,
|
|
63
70
|
installSuite,
|
|
@@ -74,6 +81,8 @@ export const SUITES = [
|
|
|
74
81
|
// syncing with this PC
|
|
75
82
|
syncSuite,
|
|
76
83
|
accountsSuite,
|
|
84
|
+
credentialsSuite,
|
|
85
|
+
vpnSuite,
|
|
77
86
|
workspaceViewSuite,
|
|
78
87
|
auditSuite,
|
|
79
88
|
// an untrusted sandbox: what the host refuses to do with what it finds
|
|
@@ -83,6 +92,7 @@ export const SUITES = [
|
|
|
83
92
|
repoGuardSuite,
|
|
84
93
|
secretsSuite,
|
|
85
94
|
hostileInputSuite,
|
|
95
|
+
webSuite,
|
|
86
96
|
testGuardSuite,
|
|
87
97
|
// pure helpers and the command line
|
|
88
98
|
layoutSuite,
|
|
@@ -91,9 +101,11 @@ export const SUITES = [
|
|
|
91
101
|
networkSuite,
|
|
92
102
|
personalizationSuite,
|
|
93
103
|
cmdlineSuite,
|
|
104
|
+
agentPathsSuite,
|
|
94
105
|
setupSuite,
|
|
95
106
|
dataSuite,
|
|
96
107
|
repairSuite,
|
|
108
|
+
healthSuite,
|
|
97
109
|
cliSuite,
|
|
98
110
|
menuSuite,
|
|
99
111
|
agentCommandsSuite,
|
|
@@ -208,9 +220,12 @@ export async function selfTest(packageRoot, suiteNames = []) {
|
|
|
208
220
|
home: process.env.AGENT_SANDBOX_HOME,
|
|
209
221
|
machine: process.env.AGENT_SANDBOX_MACHINE_HOME,
|
|
210
222
|
windir: process.env.WINDIR,
|
|
223
|
+
localAppData: process.env.LOCALAPPDATA,
|
|
211
224
|
};
|
|
212
225
|
process.env.AGENT_SANDBOX_HOME = path.join(t.temp, 'user-home');
|
|
213
226
|
process.env.AGENT_SANDBOX_MACHINE_HOME = path.join(t.temp, 'machine-home');
|
|
227
|
+
// The old storage folder (%LOCALAPPDATA%\AgentSandbox) of this PC must not change what the migrations decide.
|
|
228
|
+
process.env.LOCALAPPDATA = t.dir('local-app-data');
|
|
214
229
|
const results = [];
|
|
215
230
|
try {
|
|
216
231
|
const f = createFixtures(t);
|
|
@@ -229,6 +244,10 @@ export async function selfTest(packageRoot, suiteNames = []) {
|
|
|
229
244
|
delete process.env.AGENT_SANDBOX_MACHINE_HOME;
|
|
230
245
|
else
|
|
231
246
|
process.env.AGENT_SANDBOX_MACHINE_HOME = saved.machine;
|
|
247
|
+
if (saved.localAppData === undefined)
|
|
248
|
+
delete process.env.LOCALAPPDATA;
|
|
249
|
+
else
|
|
250
|
+
process.env.LOCALAPPDATA = saved.localAppData;
|
|
232
251
|
if (saved.windir === undefined)
|
|
233
252
|
delete process.env.WINDIR;
|
|
234
253
|
else
|
|
@@ -17,6 +17,8 @@ const PURE_GUEST = [
|
|
|
17
17
|
'guest/boot/project-env.js',
|
|
18
18
|
'guest/audit/parse.js',
|
|
19
19
|
'guest/sessions/cmdline.js',
|
|
20
|
+
'guest/agents/agent-files.js',
|
|
21
|
+
'guest/setup/path-plan.js',
|
|
20
22
|
'guest/toolchains/catalogue.js',
|
|
21
23
|
'guest/toolchains/tools/vscode-profile.js',
|
|
22
24
|
];
|
|
@@ -1,2 +1,9 @@
|
|
|
1
|
+
import { type ListOptions, type NavState } from '../cli/tui/index.js';
|
|
2
|
+
export declare const SIZES: {
|
|
3
|
+
width: number;
|
|
4
|
+
height: number;
|
|
5
|
+
}[];
|
|
6
|
+
/** The navigation the shell would show: every section, the open one with the categories of `options`. */
|
|
7
|
+
export declare function navFor(active: number, options: ListOptions<string>, focused: boolean, unsaved?: number, dimmed?: boolean): NavState;
|
|
1
8
|
/** All screens, at both sizes, as one text. `dir` is a scratch folder for the made-up config files. */
|
|
2
9
|
export declare function renderScreens(dir: string): string;
|