windows-development-sandbox 1.7.0 → 1.10.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/config.example.json +12 -1
- package/dist/app/context.d.ts +8 -0
- package/dist/app/context.js +12 -0
- package/dist/app/features/defs.d.ts +322 -0
- package/dist/app/features/defs.js +28 -0
- package/dist/app/features/sandbox/index.d.ts +2 -0
- package/dist/app/features/sandbox/index.js +3 -0
- package/dist/app/features/sandbox/sandbox.def.d.ts +193 -0
- package/dist/app/features/sandbox/sandbox.def.js +245 -0
- package/dist/app/features/sandbox/sandbox.run.d.ts +72 -0
- package/dist/app/features/sandbox/sandbox.run.js +98 -0
- package/dist/app/features/sessions/index.d.ts +2 -0
- package/dist/app/features/sessions/index.js +3 -0
- package/dist/app/features/sessions/sessions.def.d.ts +141 -0
- package/dist/app/features/sessions/sessions.def.js +176 -0
- package/dist/app/features/sessions/sessions.run.d.ts +39 -0
- package/dist/app/features/sessions/sessions.run.js +58 -0
- package/dist/{cli/menu/settings/categories.d.ts → app/features/settings/categories.def.d.ts} +2 -0
- package/dist/{cli/menu/settings/categories.js → app/features/settings/categories.def.js} +24 -5
- package/dist/app/features/settings/index.d.ts +5 -0
- package/dist/app/features/settings/index.js +6 -0
- package/dist/{cli/menu/settings/config-file.d.ts → app/features/settings/settings-file.d.ts} +1 -1
- package/dist/{cli/menu/settings/config-file.js → app/features/settings/settings-file.js} +1 -1
- package/dist/app/features/settings/settings-rows.d.ts +35 -0
- package/dist/app/features/settings/settings-rows.js +94 -0
- package/dist/app/features/settings/settings.def.d.ts +157 -0
- package/dist/app/features/settings/settings.def.js +179 -0
- package/dist/app/features/settings/settings.run.d.ts +67 -0
- package/dist/app/features/settings/settings.run.js +84 -0
- package/dist/app/kernel/define.d.ts +143 -0
- package/dist/app/kernel/define.js +42 -0
- package/dist/app/kernel/errors.d.ts +34 -0
- package/dist/app/kernel/errors.js +45 -0
- package/dist/app/kernel/fields.d.ts +70 -0
- package/dist/app/kernel/fields.js +243 -0
- package/dist/app/kernel/index.d.ts +8 -0
- package/dist/app/kernel/index.js +10 -0
- package/dist/app/kernel/io.d.ts +193 -0
- package/dist/app/kernel/io.js +126 -0
- package/dist/app/kernel/rpc.d.ts +156 -0
- package/dist/app/kernel/rpc.js +47 -0
- package/dist/app/kernel/text.d.ts +6 -0
- package/dist/app/kernel/text.js +57 -0
- package/dist/app/kernel/view.d.ts +146 -0
- package/dist/app/kernel/view.js +28 -0
- package/dist/app/registry.d.ts +10 -0
- package/dist/app/registry.js +50 -0
- package/dist/cli/args.d.ts +5 -0
- package/dist/cli/args.js +6 -0
- package/dist/cli/commands/engine.d.ts +2 -0
- package/dist/cli/commands/engine.js +60 -0
- package/dist/cli/commands/index.d.ts +1 -1
- package/dist/cli/commands/index.js +1 -1
- package/dist/cli/commands/launch/launch.js +49 -7
- package/dist/cli/commands/launch/prepare.js +3 -3
- package/dist/cli/commands/launch/running.js +2 -1
- package/dist/cli/commands/launch/summary.d.ts +2 -0
- package/dist/cli/commands/maintenance/config-share.d.ts +12 -0
- package/dist/cli/commands/maintenance/config-share.js +80 -0
- package/dist/cli/commands/maintenance/config.js +8 -82
- package/dist/cli/commands/maintenance/logs.js +14 -0
- package/dist/cli/commands/registry.js +3 -7
- package/dist/cli/commands/repair/checks/contained.d.ts +2 -0
- package/dist/cli/commands/repair/checks/contained.js +139 -0
- package/dist/cli/commands/repair/checks/hcs.d.ts +7 -0
- package/dist/cli/commands/repair/checks/hcs.js +28 -0
- package/dist/cli/commands/repair/checks/index.js +4 -0
- package/dist/cli/commands/repair/checks/pc.js +32 -1
- package/dist/cli/commands/repair/checks/sandbox.js +16 -1
- package/dist/cli/commands/repair/checks/vpn.js +34 -9
- package/dist/cli/commands/repair/env.js +2 -1
- package/dist/cli/commands/sessions/index.d.ts +0 -4
- package/dist/cli/commands/sessions/index.js +3 -7
- package/dist/cli/commands/shared-use.d.ts +5 -2
- package/dist/cli/commands/shared-use.js +11 -8
- package/dist/cli/commands/storage/contained.d.ts +3 -0
- package/dist/cli/commands/storage/contained.js +179 -0
- package/dist/cli/commands/storage/offer.js +7 -1
- package/dist/cli/commands/storage/storage.js +10 -2
- package/dist/cli/commands/user-config.js +1 -1
- package/dist/cli/commands/vpn.js +20 -3
- package/dist/cli/commands/web.d.ts +5 -0
- package/dist/cli/commands/web.js +40 -0
- package/dist/cli/index.js +2 -1
- package/dist/cli/manifest.d.ts +72 -104
- package/dist/cli/manifest.js +64 -39
- package/dist/cli/menu/add-agent.d.ts +1 -1
- package/dist/cli/menu/add-agent.js +1 -1
- package/dist/cli/menu/apps.d.ts +7 -1
- package/dist/cli/menu/apps.js +36 -0
- package/dist/cli/menu/command-fields.d.ts +1 -1
- package/dist/cli/menu/command-form.js +2 -2
- package/dist/cli/menu/io-menu.d.ts +29 -0
- package/dist/cli/menu/io-menu.js +126 -0
- package/dist/cli/menu/live.d.ts +27 -0
- package/dist/cli/menu/live.js +69 -0
- package/dist/cli/menu/memory.d.ts +19 -0
- package/dist/cli/menu/memory.js +53 -0
- package/dist/cli/menu/menu.d.ts +10 -1
- package/dist/cli/menu/menu.js +27 -9
- package/dist/cli/menu/pages/agents-page.d.ts +3 -1
- package/dist/cli/menu/pages/agents-page.js +12 -5
- package/dist/cli/menu/pages/apps-page.d.ts +3 -1
- package/dist/cli/menu/pages/apps-page.js +19 -2
- package/dist/cli/menu/pages/list-page.d.ts +27 -0
- package/dist/cli/menu/pages/list-page.js +109 -7
- package/dist/cli/menu/pages/maintenance-page.d.ts +0 -5
- package/dist/cli/menu/pages/maintenance-page.js +17 -19
- package/dist/cli/menu/pages/sandbox-page.d.ts +4 -6
- package/dist/cli/menu/pages/sandbox-page.js +26 -118
- package/dist/cli/menu/pages/scopes.d.ts +1 -1
- package/dist/cli/menu/pages/sessions-page.d.ts +5 -11
- package/dist/cli/menu/pages/sessions-page.js +19 -88
- package/dist/cli/menu/pages/settings-page.d.ts +2 -10
- package/dist/cli/menu/pages/settings-page.js +64 -94
- package/dist/cli/menu/render-view.d.ts +28 -0
- package/dist/cli/menu/render-view.js +112 -0
- package/dist/cli/menu/settings/{path-kind.d.ts → ask-path.d.ts} +1 -7
- package/dist/cli/menu/settings/ask-path.js +14 -0
- package/dist/cli/menu/settings/collections/collection-editor.d.ts +1 -1
- package/dist/cli/menu/settings/collections/string-list-editor.d.ts +1 -2
- package/dist/cli/menu/settings/collections/string-list-editor.js +1 -1
- package/dist/cli/menu/settings/draft.d.ts +1 -1
- package/dist/cli/menu/settings/format.d.ts +1 -3
- package/dist/cli/menu/settings/format.js +6 -36
- package/dist/cli/menu/settings/index.d.ts +1 -3
- package/dist/cli/menu/settings/index.js +3 -3
- package/dist/cli/menu/settings/object-editor.d.ts +1 -1
- package/dist/cli/menu/settings/share.d.ts +36 -0
- package/dist/cli/menu/settings/share.js +131 -0
- package/dist/cli/menu/settings/value-editor.d.ts +5 -1
- package/dist/cli/menu/settings/value-editor.js +57 -54
- package/dist/cli/menu/several.js +1 -1
- package/dist/cli/menu/share-settings.d.ts +21 -0
- package/dist/cli/menu/share-settings.js +112 -0
- package/dist/cli/menu/shell.d.ts +21 -5
- package/dist/cli/menu/shell.js +31 -6
- package/dist/cli/menu/views.d.ts +7 -0
- package/dist/cli/menu/views.js +11 -0
- package/dist/cli/menu/vpn-login.js +8 -0
- package/dist/cli/operations/handler.d.ts +6 -0
- package/dist/cli/operations/handler.js +61 -0
- package/dist/cli/operations/index.d.ts +4 -0
- package/dist/cli/operations/index.js +5 -0
- package/dist/cli/operations/input.d.ts +6 -0
- package/dist/cli/operations/input.js +37 -0
- package/dist/cli/operations/io-cli.d.ts +7 -0
- package/dist/cli/operations/io-cli.js +62 -0
- package/dist/cli/operations/spec.d.ts +18 -0
- package/dist/cli/operations/spec.js +108 -0
- package/dist/cli/output/colors.d.ts +2 -2
- package/dist/cli/output/colors.js +2 -11
- package/dist/cli/output/index.d.ts +1 -0
- package/dist/cli/output/index.js +1 -0
- package/dist/cli/output/print.d.ts +3 -1
- package/dist/cli/output/print.js +7 -3
- package/dist/cli/output/progress.d.ts +15 -0
- package/dist/cli/output/progress.js +44 -0
- package/dist/cli/output/render-view.d.ts +4 -0
- package/dist/cli/output/render-view.js +63 -0
- package/dist/cli/services/agent-commands-service.d.ts +2 -0
- package/dist/cli/services/agent-commands-service.js +11 -2
- package/dist/cli/services/logs-service.js +4 -3
- package/dist/cli/tui/app.d.ts +6 -1
- package/dist/cli/tui/app.js +26 -4
- package/dist/cli/tui/list/list-screen.d.ts +14 -0
- package/dist/cli/tui/list/list-screen.js +64 -36
- package/dist/cli/tui/list/list-types.d.ts +2 -0
- package/dist/cli/tui/terminal.d.ts +7 -0
- package/dist/cli/tui/terminal.js +30 -10
- package/dist/core/config/defaults.js +9 -1
- package/dist/core/schema/config.d.ts +14 -0
- package/dist/core/schema/config.js +16 -1
- package/dist/core/schema/harness.d.ts +1 -0
- package/dist/core/schema/harness.js +5 -0
- package/dist/core/schema/sandbox.d.ts +6 -0
- package/dist/core/schema/sandbox.js +20 -3
- package/dist/core/schema/vpn.d.ts +2 -1
- package/dist/core/schema/vpn.js +4 -4
- package/dist/core/schema/web.d.ts +1 -0
- package/dist/core/schema/web.js +4 -1
- package/dist/core/types.d.ts +23 -5
- package/dist/core/util/fs.d.ts +2 -0
- package/dist/core/util/fs.js +9 -0
- package/dist/core/util/index.d.ts +1 -1
- package/dist/core/util/index.js +1 -1
- package/dist/guest/agents/agent-files.d.ts +5 -0
- package/dist/guest/agents/agent-files.js +4 -0
- package/dist/guest/agents/local-state.d.ts +2 -0
- package/dist/guest/agents/local-state.js +21 -1
- package/dist/guest/boot/boot.js +5 -2
- package/dist/guest/daemon/daemon.js +4 -0
- package/dist/guest/daemon/sessions.d.ts +5 -0
- package/dist/guest/daemon/sessions.js +17 -3
- package/dist/guest/entries/daemon.js +3 -0
- package/dist/guest/governor/decide.d.ts +71 -0
- package/dist/guest/governor/decide.js +211 -0
- package/dist/guest/governor/governor.d.ts +45 -0
- package/dist/guest/governor/governor.js +230 -0
- package/dist/guest/governor/index.d.ts +2 -0
- package/dist/guest/governor/index.js +2 -0
- package/dist/guest/network/firewall.d.ts +1 -0
- package/dist/guest/network/firewall.js +65 -23
- package/dist/guest/runtime/csc.d.ts +1 -0
- package/dist/guest/runtime/csc.js +1 -0
- package/dist/guest/runtime/process.js +53 -5
- package/dist/guest/sessions/viewer.js +3 -1
- package/dist/guest/setup/claude-mem.js +12 -4
- package/dist/guest/toolchains/languages/node.js +29 -3
- package/dist/guest/toolchains/native/rust-extras.js +4 -2
- package/dist/guest/vpn/vpn.js +69 -21
- package/dist/guest/vpn/wireguard.d.ts +32 -0
- package/dist/guest/vpn/wireguard.js +132 -0
- package/dist/host/agent-commands/commands.d.ts +4 -0
- package/dist/host/agent-commands/commands.js +31 -7
- package/dist/host/audit/log.d.ts +1 -1
- package/dist/host/channel/attach.d.ts +3 -1
- package/dist/host/channel/attach.js +5 -2
- package/dist/host/channel/channel.d.ts +2 -0
- package/dist/host/channel/channel.js +4 -0
- package/dist/host/engines/engine.d.ts +12 -0
- package/dist/host/engines/engine.js +1 -0
- package/dist/host/engines/hcs/check.d.ts +25 -0
- package/dist/host/engines/hcs/check.js +95 -0
- package/dist/host/engines/hcs/documents.d.ts +44 -0
- package/dist/host/engines/hcs/documents.js +155 -0
- package/dist/host/engines/hcs/engine.d.ts +39 -0
- package/dist/host/engines/hcs/engine.js +171 -0
- package/dist/host/engines/hcs/helper.d.ts +43 -0
- package/dist/host/engines/hcs/helper.js +173 -0
- package/dist/host/engines/hcs/images.d.ts +37 -0
- package/dist/host/engines/hcs/images.js +152 -0
- package/dist/host/engines/index.d.ts +7 -0
- package/dist/host/engines/index.js +5 -0
- package/dist/host/engines/registry.d.ts +16 -0
- package/dist/host/engines/registry.js +30 -0
- package/dist/host/engines/types.d.ts +58 -0
- package/dist/host/engines/types.js +1 -0
- package/dist/host/engines/windows-sandbox/engine.d.ts +16 -0
- package/dist/host/engines/windows-sandbox/engine.js +63 -0
- package/dist/host/engines/windows-sandbox/wsb-text.d.ts +2 -0
- package/dist/host/engines/windows-sandbox/wsb-text.js +29 -0
- package/dist/host/launch/generate.d.ts +5 -1
- package/dist/host/launch/generate.js +50 -8
- package/dist/host/launch/index.d.ts +3 -1
- package/dist/host/launch/index.js +3 -1
- package/dist/host/launch/launch-plan.d.ts +13 -0
- package/dist/host/launch/launch-plan.js +26 -0
- package/dist/host/launch/mappings.js +2 -1
- package/dist/host/launch/risk-notes.d.ts +10 -0
- package/dist/host/launch/risk-notes.js +18 -0
- package/dist/host/launch/session-file.d.ts +2 -0
- package/dist/host/launch/session-file.js +7 -1
- package/dist/host/launch/wsb-file.d.ts +28 -4
- package/dist/host/launch/wsb-file.js +61 -42
- package/dist/host/life/crash-report.d.ts +27 -1
- package/dist/host/life/crash-report.js +173 -7
- package/dist/host/life/host-memory.d.ts +41 -0
- package/dist/host/life/host-memory.js +140 -0
- package/dist/host/life/index.d.ts +2 -0
- package/dist/host/life/index.js +2 -0
- package/dist/host/life/stop-events.d.ts +2 -0
- package/dist/host/life/stop-events.js +20 -3
- package/dist/host/life/stop-warning.js +1 -1
- package/dist/host/life/watchdog.d.ts +56 -0
- package/dist/host/life/watchdog.js +124 -0
- package/dist/host/native.d.ts +3 -2
- package/dist/host/native.js +22 -3
- package/dist/host/profile/claude-login.js +3 -2
- package/dist/host/sandbox/contained-deps.d.ts +2 -0
- package/dist/host/sandbox/contained-deps.js +17 -0
- package/dist/host/sandbox/index.d.ts +2 -0
- package/dist/host/sandbox/index.js +2 -0
- package/dist/host/sandbox/platform.d.ts +2 -0
- package/dist/host/sandbox/platform.js +7 -7
- package/dist/host/sandbox/tasklist.d.ts +13 -0
- package/dist/host/sandbox/tasklist.js +47 -0
- package/dist/host/secrets/launch-secrets.js +2 -1
- package/dist/host/sharing/current.js +2 -6
- package/dist/host/storage/contained/check.d.ts +9 -0
- package/dist/host/storage/contained/check.js +37 -0
- package/dist/host/storage/contained/contained-storage.d.ts +66 -0
- package/dist/host/storage/contained/contained-storage.js +168 -0
- package/dist/host/storage/contained/disk-port.d.ts +43 -0
- package/dist/host/storage/contained/disk-port.js +122 -0
- package/dist/host/storage/contained/index.d.ts +9 -0
- package/dist/host/storage/contained/index.js +8 -0
- package/dist/host/storage/contained/layout.d.ts +28 -0
- package/dist/host/storage/contained/layout.js +45 -0
- package/dist/host/storage/contained/resolve.d.ts +4 -0
- package/dist/host/storage/contained/resolve.js +30 -0
- package/dist/host/storage/contained/setting.d.ts +1 -0
- package/dist/host/storage/contained/setting.js +5 -0
- package/dist/host/storage/contained/state.d.ts +89 -0
- package/dist/host/storage/contained/state.js +166 -0
- package/dist/host/storage/contained/transfer.d.ts +24 -0
- package/dist/host/storage/contained/transfer.js +121 -0
- package/dist/host/storage/index.d.ts +2 -0
- package/dist/host/storage/index.js +2 -0
- package/dist/host/storage/providers/contained.d.ts +15 -0
- package/dist/host/storage/providers/contained.js +39 -0
- package/dist/host/storage/providers/folder.d.ts +12 -0
- package/dist/host/storage/providers/folder.js +20 -0
- package/dist/host/storage/providers/index.d.ts +4 -0
- package/dist/host/storage/providers/index.js +3 -0
- package/dist/host/storage/providers/registry.d.ts +9 -0
- package/dist/host/storage/providers/registry.js +14 -0
- package/dist/host/storage/providers/types.d.ts +33 -0
- package/dist/host/storage/providers/types.js +1 -0
- package/dist/host/tools/conpty.js +3 -2
- package/dist/host/tools/windows-terminal.js +8 -7
- package/dist/host/vpn/index.d.ts +1 -1
- package/dist/host/vpn/index.js +1 -1
- package/dist/host/vpn/prepare.d.ts +2 -0
- package/dist/host/vpn/prepare.js +63 -2
- package/dist/host/web/browser.d.ts +2 -0
- package/dist/host/web/browser.js +14 -1
- package/dist/host/web/index.d.ts +3 -2
- package/dist/host/web/index.js +3 -2
- package/dist/host/web/rpc.d.ts +24 -0
- package/dist/host/web/rpc.js +149 -0
- package/dist/host/web/runner.d.ts +2 -0
- package/dist/host/web/runner.js +1 -2
- package/dist/host/web/server.d.ts +7 -0
- package/dist/host/web/server.js +23 -3
- package/dist/runtime/agent.js +83 -18
- package/dist/runtime/daemon.js +1805 -1221
- package/dist/runtime/db.js +83 -18
- package/dist/runtime/main.js +779 -219
- package/dist/runtime/netproxy.js +73 -11
- package/dist/runtime/view.js +91 -16
- package/dist/selftest/agent-paths.test.js +11 -1
- package/dist/selftest/app.test.d.ts +2 -0
- package/dist/selftest/app.test.js +324 -0
- package/dist/selftest/channel.test.js +13 -0
- package/dist/selftest/cli.test.js +2 -1
- package/dist/selftest/config.test.js +59 -0
- package/dist/selftest/contained.test.d.ts +2 -0
- package/dist/selftest/contained.test.js +544 -0
- package/dist/selftest/credentials.test.js +1 -1
- package/dist/selftest/engine.test.d.ts +2 -0
- package/dist/selftest/engine.test.js +127 -0
- package/dist/selftest/guest-boot.test.js +47 -1
- package/dist/selftest/hcs.test.d.ts +2 -0
- package/dist/selftest/hcs.test.js +482 -0
- package/dist/selftest/heavy-load.test.d.ts +2 -0
- package/dist/selftest/heavy-load.test.js +355 -0
- package/dist/selftest/index.js +12 -0
- package/dist/selftest/layout.test.js +12 -0
- package/dist/selftest/menu-perf.test.d.ts +2 -0
- package/dist/selftest/menu-perf.test.js +248 -0
- package/dist/selftest/menu-screens.js +4 -2
- package/dist/selftest/menu.test.js +36 -8
- package/dist/selftest/network.test.js +20 -1
- package/dist/selftest/support/engine-cases.d.ts +8 -0
- package/dist/selftest/support/engine-cases.js +65 -0
- package/dist/selftest/support/engine-golden.d.ts +3 -0
- package/dist/selftest/support/engine-golden.js +66 -0
- package/dist/selftest/support/fixtures.d.ts +8 -1
- package/dist/selftest/support/hcs-probes.d.ts +17 -0
- package/dist/selftest/support/hcs-probes.js +41 -0
- package/dist/selftest/vpn.test.js +136 -3
- package/dist/selftest/web.test.js +101 -3
- package/dist/shared/channel/run-dir.d.ts +4 -1
- package/dist/shared/channel/run-dir.js +7 -1
- package/dist/shared/channel/schema.d.ts +35 -1
- package/dist/shared/channel/schema.js +27 -0
- package/dist/shared/channel/secrets.d.ts +1 -0
- package/dist/shared/channel/secrets.js +6 -0
- package/dist/shared/io/copy-tree.d.ts +7 -0
- package/dist/shared/io/copy-tree.js +45 -0
- package/dist/shared/io/index.d.ts +2 -0
- package/dist/shared/io/index.js +2 -0
- package/dist/shared/io/robocopy-result.d.ts +6 -0
- package/dist/shared/io/robocopy-result.js +23 -0
- package/dist/shared/memory/index.d.ts +1 -0
- package/dist/shared/memory/index.js +1 -0
- package/dist/shared/memory/policy.d.ts +38 -0
- package/dist/shared/memory/policy.js +63 -0
- package/dist/shared/network/firewall-rules.d.ts +8 -0
- package/dist/shared/network/firewall-rules.js +28 -0
- package/dist/shared/pty/output-filter.d.ts +14 -1
- package/dist/shared/pty/output-filter.js +22 -4
- package/dist/shared/pty/terminal.d.ts +4 -1
- package/dist/shared/pty/terminal.js +4 -2
- package/dist/shared/system.d.ts +1 -0
- package/dist/shared/system.js +1 -0
- package/dist/shared/vpn/download.d.ts +16 -0
- package/dist/shared/vpn/download.js +16 -0
- package/dist/shared/vpn/wireguard.d.ts +51 -0
- package/dist/shared/vpn/wireguard.js +227 -0
- package/dist/sync/sync-cycle.js +5 -5
- package/dist/sync/watcher.d.ts +13 -0
- package/dist/sync/watcher.js +92 -3
- package/dist/web/index.html +4 -3
- package/dist/web/main-NRRS5O6Z.js +157 -0
- package/dist/web/main-T5DMCHYV.css +1 -0
- package/native/csharp/HcsHost/AssemblyInfo.cs +14 -0
- package/native/csharp/HcsHost/Guard.cs +100 -0
- package/native/csharp/HcsHost/Hcs.cs +256 -0
- package/native/csharp/HcsHost/Json.cs +78 -0
- package/native/csharp/HcsHost/Program.cs +185 -0
- package/native/csharp/HcsHost/VirtualDisk.cs +95 -0
- package/native/csharp/HostTools/AssemblyInfo.cs +14 -0
- package/native/csharp/HostTools/MemoryStatus.cs +128 -0
- package/native/csharp/HostTools/Program.cs +58 -17
- package/native/scripts/check-contained.ps1 +28 -0
- package/native/scripts/contained-common.ps1 +241 -0
- package/native/scripts/dismount-contained.ps1 +66 -0
- package/native/scripts/memory-status.ps1 +42 -0
- package/native/scripts/mount-contained.ps1 +139 -0
- package/native/scripts/run-elevated.ps1 +17 -4
- package/native/scripts/stop-events.ps1 +23 -8
- package/package.json +9 -2
- package/sandbox/csharp/MemGov.cs +466 -0
- package/sandbox/csharp/PtyHost.cs +111 -5
- package/sandbox/scripts/new-shortcuts.ps1 +3 -1
- package/schemas/config.schema.json +66 -8
- package/schemas/sandbox-config.schema.json +3 -3
- package/dist/cli/commands/sessions/control.d.ts +0 -4
- package/dist/cli/commands/sessions/control.js +0 -66
- package/dist/cli/commands/sessions/list.d.ts +0 -2
- package/dist/cli/commands/sessions/list.js +0 -48
- package/dist/cli/commands/sessions/status.d.ts +0 -4
- package/dist/cli/commands/sessions/status.js +0 -114
- package/dist/cli/commands/sessions/window.d.ts +0 -3
- package/dist/cli/commands/sessions/window.js +0 -18
- package/dist/cli/menu/settings/path-kind.js +0 -44
- package/dist/web/main-CXESL347.js +0 -45
- package/dist/web/main-VLR7DVQ4.css +0 -1
- /package/dist/{cli/menu/settings → app/kernel}/schema-node.d.ts +0 -0
- /package/dist/{cli/menu/settings → app/kernel}/schema-node.js +0 -0
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
// The shared core: the kernel (definitions checked in and out, Fields from zod, questions and their fake, errors across
|
|
2
|
+
// a surface, the RPC messages), parity (every operation and query reachable on the CLI, the menu and the web, or
|
|
3
|
+
// exempt with a reason), the reference views' pages, their CLI and menu renderings, and the settings feature run
|
|
4
|
+
// against a config file of its own.
|
|
5
|
+
import fs from 'node:fs';
|
|
6
|
+
import { z } from 'zod';
|
|
7
|
+
import { DEFINITIONS, NAV, QUERIES, VIEWS, viewById } from '../app/features/defs.js';
|
|
8
|
+
import { sandboxPage } from '../app/features/sandbox/index.js';
|
|
9
|
+
import { sessionsPage } from '../app/features/sessions/index.js';
|
|
10
|
+
import { settingsPage } from '../app/features/settings/index.js';
|
|
11
|
+
import { ago, Cancelled, ClientMessageSchema, defineOperation, exemption, fakeIO, fieldOf, fieldProblem, FieldSchema, fieldsOf, fromWireError, humanize, implementer, invoke, pageActions, ServerMessageSchema, SURFACE_NAMES, toWireError, valueText, } from '../app/kernel/index.js';
|
|
12
|
+
import { IMPLEMENTATIONS, readQuery, runDefinition } from '../app/registry.js';
|
|
13
|
+
import { parseCli } from '../cli/args.js';
|
|
14
|
+
import { COMMANDS, commandSpec } from '../cli/manifest.js';
|
|
15
|
+
import { menuView } from '../cli/menu/render-view.js';
|
|
16
|
+
import { MENU_VIEWS } from '../cli/menu/views.js';
|
|
17
|
+
import { cliInput, commandSpecOf, definitionFor } from '../cli/operations/index.js';
|
|
18
|
+
import { pageLines } from '../cli/output/index.js';
|
|
19
|
+
import { secret } from '../core/schema/index.js';
|
|
20
|
+
import { UserError } from '../core/util/index.js';
|
|
21
|
+
const NOW = Date.now();
|
|
22
|
+
const running = (id, here) => ({
|
|
23
|
+
id,
|
|
24
|
+
launchId: 'l1',
|
|
25
|
+
harness: 'claude',
|
|
26
|
+
cwd: 'C:\\Projects\\api',
|
|
27
|
+
phase: 'running',
|
|
28
|
+
message: 'running',
|
|
29
|
+
updatedAt: new Date(NOW).toISOString(),
|
|
30
|
+
startedAt: new Date(NOW - 5 * 60000).toISOString(),
|
|
31
|
+
projectName: 'api',
|
|
32
|
+
here,
|
|
33
|
+
viewers: { viewers: 1, remote: 1 },
|
|
34
|
+
canOpen: true,
|
|
35
|
+
});
|
|
36
|
+
const STATUS = {
|
|
37
|
+
sandbox: {
|
|
38
|
+
running: true,
|
|
39
|
+
phase: 'ready',
|
|
40
|
+
message: 'Ready',
|
|
41
|
+
headless: true,
|
|
42
|
+
startedAt: NOW - 3 * 3600000,
|
|
43
|
+
daemon: 'pong (2 session(s) running)',
|
|
44
|
+
account: 'work',
|
|
45
|
+
syncWatcher: true,
|
|
46
|
+
},
|
|
47
|
+
here: { folder: 'C:\\Projects\\api', project: 'api', guest: 'C:\\Projects\\api' },
|
|
48
|
+
roots: [{ host: 'C:\\Projects', guest: 'C:\\Projects' }],
|
|
49
|
+
projects: [
|
|
50
|
+
{
|
|
51
|
+
name: 'api',
|
|
52
|
+
host: 'C:\\Projects\\api',
|
|
53
|
+
guest: 'C:\\Projects\\api',
|
|
54
|
+
mechanism: 'launch',
|
|
55
|
+
here: true,
|
|
56
|
+
setup: { phase: 'ready', message: '' },
|
|
57
|
+
databases: [{ database: 'postgres', port: 5432, state: 'running' }],
|
|
58
|
+
sessions: [
|
|
59
|
+
{ id: 's1', harness: 'claude', viewers: '1 here, 0 in sandbox', started: new Date(NOW).toISOString() },
|
|
60
|
+
],
|
|
61
|
+
repoGuard: [],
|
|
62
|
+
},
|
|
63
|
+
],
|
|
64
|
+
otherSessions: [],
|
|
65
|
+
repoGuard: { guarded: 1, findings: ['hook: C:\\Projects\\api: .git/hooks/pre-commit'] },
|
|
66
|
+
};
|
|
67
|
+
/** The operations and queries a list of views can reach. */
|
|
68
|
+
function reached(views) {
|
|
69
|
+
return new Set(views.flatMap((v) => [...v.operations.map((o) => o.id), ...Object.values(v.queries).map((q) => q.id)]));
|
|
70
|
+
}
|
|
71
|
+
/** Why a definition is not reachable on a surface ('' when it is). */
|
|
72
|
+
function gap(def, surface, reach) {
|
|
73
|
+
if (exemption(def, surface)?.trim())
|
|
74
|
+
return '';
|
|
75
|
+
if (surface === 'cli')
|
|
76
|
+
return def.cli ? '' : 'no CLI hint and no exemption';
|
|
77
|
+
return reach[surface].has(def.id) ? '' : 'no ' + surface + ' view offers it and it declares no exemption';
|
|
78
|
+
}
|
|
79
|
+
export const appSuite = {
|
|
80
|
+
name: 'app',
|
|
81
|
+
title: 'shared core: kernel, parity of the three surfaces, reference views and their renderings, settings',
|
|
82
|
+
run: async (t) => {
|
|
83
|
+
t.section('definitions are checked going in and coming out');
|
|
84
|
+
const implement = implementer();
|
|
85
|
+
const add = defineOperation({
|
|
86
|
+
id: 'test.add',
|
|
87
|
+
title: 'Add',
|
|
88
|
+
summary: 'adds',
|
|
89
|
+
input: z.object({ n: z.number().int().describe('how many'), twice: z.boolean().default(false) }),
|
|
90
|
+
output: z.object({ total: z.number() }),
|
|
91
|
+
text: (o) => 'total ' + o.total,
|
|
92
|
+
});
|
|
93
|
+
const addRun = implement(add, (ctx, input) => ({ total: ctx.base + input.n * (input.twice ? 2 : 1) }));
|
|
94
|
+
const io = fakeIO().io;
|
|
95
|
+
t.equal(await invoke(addRun, { base: 1 }, { n: 2 }, io), { total: 3 }, 'defaults are filled in before run');
|
|
96
|
+
await t.throws(() => invoke(addRun, { base: 1 }, { n: 'x' }, io), 'That does not work for add', 'a bad input');
|
|
97
|
+
const broken = implement(add, () => ({ total: 'nope' }));
|
|
98
|
+
await t.throws(() => invoke(broken, { base: 0 }, { n: 1 }, io), 'expected number', 'an output that does not fit');
|
|
99
|
+
t.section('questions, and the fake that answers them');
|
|
100
|
+
const fake = fakeIO([true, 'too short', 'long enough']);
|
|
101
|
+
const asked = await fake.io.confirm({ title: 'Sure?' });
|
|
102
|
+
const typed = await fake.io.text({ title: 'Name', validate: (v) => (v.length > 9 ? true : 'Longer, please.') });
|
|
103
|
+
fake.io.progress('working');
|
|
104
|
+
fake.io.log('a line', 'muted');
|
|
105
|
+
t.check(asked === true &&
|
|
106
|
+
typed === 'long enough' &&
|
|
107
|
+
fake.seen.questions.length === 3 &&
|
|
108
|
+
fake.seen.questions[2].kind === 'text' &&
|
|
109
|
+
'problem' in fake.seen.questions[2] &&
|
|
110
|
+
fake.seen.questions[2].problem === 'Longer, please.' &&
|
|
111
|
+
fake.seen.progress[0] === 'working' &&
|
|
112
|
+
fake.seen.logs[0].tone === 'muted', 'answers come in order, a typed answer is asked again with what was wrong, lines are recorded');
|
|
113
|
+
await t.throws(() => fakeIO().io.confirm({ title: 'Unplanned?' }), 'No answer for: Unplanned?', 'no answer left');
|
|
114
|
+
t.section('errors cross a surface as message and fix');
|
|
115
|
+
const user = toWireError(new UserError('It broke.', 'Do this.'));
|
|
116
|
+
t.equal(user, { kind: 'user', message: 'It broke.', fix: 'Do this.' }, 'a UserError keeps its fix');
|
|
117
|
+
t.equal(toWireError(new Cancelled()).kind, 'cancelled', 'a declined question is "cancelled"');
|
|
118
|
+
t.equal(toWireError(new Error('C:\\secret')), { kind: 'bug', message: 'Something went wrong.' }, 'a bug says little');
|
|
119
|
+
t.equal(toWireError(z.number().safeParse('x').error).kind, 'invalid', 'a value that does not fit is "invalid"');
|
|
120
|
+
const back = fromWireError(user);
|
|
121
|
+
t.check(back instanceof UserError && back.fix === 'Do this.', 'and it becomes a UserError again on the other side');
|
|
122
|
+
t.section('Fields from zod');
|
|
123
|
+
const schema = z.object({
|
|
124
|
+
on: z.boolean().default(true).describe('Switch it'),
|
|
125
|
+
size: z.union([z.literal('auto'), z.number().int().min(1).max(64)]).describe('How big'),
|
|
126
|
+
days: z.number().int().min(1).max(365).default(7),
|
|
127
|
+
name: z.string().optional(),
|
|
128
|
+
apiKey: secret(z.string()).default('hunter2'),
|
|
129
|
+
cacheDir: z.string(),
|
|
130
|
+
tags: z.array(z.string()),
|
|
131
|
+
env: z.record(z.string(), secret(z.string())),
|
|
132
|
+
mapping: z.object({ host: z.string(), readOnly: z.boolean().optional() }),
|
|
133
|
+
});
|
|
134
|
+
const fields = fieldsOf(schema);
|
|
135
|
+
const kinds = Object.fromEntries(fields.map((f) => [f.name, f.kind]));
|
|
136
|
+
t.equal(kinds, {
|
|
137
|
+
on: 'boolean',
|
|
138
|
+
size: 'enum',
|
|
139
|
+
days: 'number',
|
|
140
|
+
name: 'text',
|
|
141
|
+
apiKey: 'secret',
|
|
142
|
+
cacheDir: 'path',
|
|
143
|
+
tags: 'list',
|
|
144
|
+
env: 'map',
|
|
145
|
+
mapping: 'object',
|
|
146
|
+
}, 'every kind of value gets its kind of field');
|
|
147
|
+
const size = fields.find((f) => f.name === 'size');
|
|
148
|
+
const days = fields.find((f) => f.name === 'days');
|
|
149
|
+
const apiKey = fields.find((f) => f.name === 'apiKey');
|
|
150
|
+
t.check(size.help === 'How big' &&
|
|
151
|
+
size.choices?.[0]?.value === 'auto' &&
|
|
152
|
+
size.open?.kind === 'number' &&
|
|
153
|
+
days.range === '1 to 365' &&
|
|
154
|
+
days.default === 7 &&
|
|
155
|
+
days.optional &&
|
|
156
|
+
apiKey.default === '***' &&
|
|
157
|
+
fields.find((f) => f.name === 'cacheDir')?.pathKind === 'dir' &&
|
|
158
|
+
fields.find((f) => f.name === 'mapping')?.fields?.length === 2 &&
|
|
159
|
+
humanize('memoryMb') === 'Memory MB', 'label, help, default, range, choices with a value of your own, folders, objects; a secret default never shows');
|
|
160
|
+
t.check(fields.every((f) => FieldSchema.safeParse(f).success) && FieldSchema.safeParse({ kind: 'x' }).success === false, 'Fields are plain data that fits FieldSchema (what the browser receives)');
|
|
161
|
+
t.check(fieldProblem(days, 400) === 'Use 1 to 365.' &&
|
|
162
|
+
fieldProblem(days, 3) === undefined &&
|
|
163
|
+
fieldProblem(size, 'big') === undefined &&
|
|
164
|
+
fieldProblem(fieldOf(z.enum(['a', 'b'])), 'c') === 'Choose one.' &&
|
|
165
|
+
valueText('hunter2', apiKey).text === '***' &&
|
|
166
|
+
valueText(true).text === 'On', 'a Field checks a value as far as it can tell, and secrets read as ***');
|
|
167
|
+
t.section('the RPC messages');
|
|
168
|
+
const good = [
|
|
169
|
+
{ t: 'call', id: 'c1', op: 'sessions.restart', input: { all: true } },
|
|
170
|
+
{ t: 'answer', id: 'c1', value: true },
|
|
171
|
+
{ t: 'subscribe', id: 's1', query: 'sandbox.status', input: {} },
|
|
172
|
+
{ t: 'unsubscribe', id: 's1' },
|
|
173
|
+
];
|
|
174
|
+
const bad = [
|
|
175
|
+
{ t: 'call', id: 'c1', op: '../etc', input: {} },
|
|
176
|
+
{ t: 'call', id: 'with space', op: 'a.b', input: {} },
|
|
177
|
+
{ t: 'call', id: 'c1', op: 'a.b', input: {}, extra: 1 },
|
|
178
|
+
{ t: 'shell', id: 'c1' },
|
|
179
|
+
'call',
|
|
180
|
+
];
|
|
181
|
+
t.check(good.every((m) => ClientMessageSchema.safeParse(m).success) &&
|
|
182
|
+
bad.every((m) => !ClientMessageSchema.safeParse(m).success) &&
|
|
183
|
+
ServerMessageSchema.safeParse({ t: 'ask', id: 'c1', question: { kind: 'confirm', title: 'Go?' } }).success &&
|
|
184
|
+
!ServerMessageSchema.safeParse({ t: 'ask', id: 'c1', question: { kind: 'shell', title: 'x' } }).success, 'the browser may send only calls, answers and subscriptions of the right shape');
|
|
185
|
+
t.section('parity: every operation and query on every surface');
|
|
186
|
+
const ids = DEFINITIONS.map((d) => d.id);
|
|
187
|
+
const implemented = IMPLEMENTATIONS.map((i) => i.def.id);
|
|
188
|
+
t.check(new Set(ids).size === ids.length &&
|
|
189
|
+
ids.every((id) => implemented.includes(id)) &&
|
|
190
|
+
implemented.every((id) => ids.includes(id)) &&
|
|
191
|
+
IMPLEMENTATIONS.every((i) => DEFINITIONS.includes(i.def)), 'every definition has exactly one implementation and every implementation is registered', ids.filter((id) => !implemented.includes(id)).join(', '));
|
|
192
|
+
const webViews = NAV.flatMap((n) => (n.view ? [viewById(n.view)] : []));
|
|
193
|
+
const reach = {
|
|
194
|
+
cli: new Set(),
|
|
195
|
+
menu: reached(MENU_VIEWS.map((m) => m.view)),
|
|
196
|
+
web: reached(webViews),
|
|
197
|
+
};
|
|
198
|
+
const gaps = DEFINITIONS.flatMap((d) => SURFACE_NAMES.map((s) => [d.id, s, gap(d, s, reach)]).filter(([, , why]) => why)).map(([id, s, why]) => id + ' on ' + s + ': ' + why);
|
|
199
|
+
t.equal(gaps, [], 'the parity list shows no gaps');
|
|
200
|
+
t.check(VIEWS.every((v) => webViews.includes(v) || MENU_VIEWS.some((m) => m.view === v)) &&
|
|
201
|
+
NAV.every((n) => n.view !== undefined || Boolean(n.elsewhere)), 'every view is shown somewhere, and a web section without a view says where the area is');
|
|
202
|
+
const hinted = DEFINITIONS.filter((d) => d.cli);
|
|
203
|
+
t.check(hinted.every((d) => {
|
|
204
|
+
const names = [...(d.cli?.positional ?? []), ...Object.keys(d.cli?.flags ?? {})];
|
|
205
|
+
const shape = d.input.shape ?? {};
|
|
206
|
+
return names.every((n) => n in shape);
|
|
207
|
+
}) && hinted.filter((d) => !d.cli?.action).every((d) => commandSpecOf(d).name === d.cli?.name), 'every CLI hint names fields of its input, and every command of its own has a group');
|
|
208
|
+
t.section('the commands keep their names and flags');
|
|
209
|
+
const spec = (name) => JSON.stringify(commandSpec(name)?.options.map((o) => [o.flag, o.short ?? '', o.shownAs ?? '', o.type, o.description]));
|
|
210
|
+
t.check(['status', 'sessions', 'restart', 'stop', 'show', 'hide'].every((n) => COMMANDS.filter((c) => c.name === n).length === 1), 'status, sessions, restart, stop, show and hide are commands (once each)');
|
|
211
|
+
t.equal([spec('restart'), spec('stop'), spec('sessions')], [
|
|
212
|
+
JSON.stringify([
|
|
213
|
+
['--harness', '', '--agent', 'string', 'only this agent'],
|
|
214
|
+
['--all', '', '', 'boolean', "every project's sessions, not only this folder's"],
|
|
215
|
+
]),
|
|
216
|
+
JSON.stringify([['--yes', '-y', '', 'boolean', "do not ask when other projects' sessions would end"]]),
|
|
217
|
+
JSON.stringify([['--wide', '', '', 'boolean', "also show each session's folder"]]),
|
|
218
|
+
], 'with the flags and help they had');
|
|
219
|
+
const configChoices = commandSpec('config')?.args[0].choices;
|
|
220
|
+
t.check(Array.isArray(configChoices) && ['get', 'set', 'reset', 'list'].every((a) => configChoices.includes(a)), 'config get, set, reset and list are actions of config');
|
|
221
|
+
const restartLine = parseCli(['restart', '--agent', 'claude', '--all']);
|
|
222
|
+
const setLine = parseCli(['config', 'set', 'sandbox.memoryMb', '8192']);
|
|
223
|
+
t.equal([cliInput(definitionFor('restart', []), restartLine), cliInput(definitionFor('config', ['set']), setLine)], [
|
|
224
|
+
{ harness: 'claude', all: true },
|
|
225
|
+
{ key: 'sandbox.memoryMb', value: '8192' },
|
|
226
|
+
], 'a command line becomes the input (--agent and the old --harness, positional arguments after the action)');
|
|
227
|
+
t.check(definitionFor('config', ['show']) === undefined && definitionFor('config', []) === undefined, "config's own actions stay the command's");
|
|
228
|
+
t.section('the reference views');
|
|
229
|
+
const statusPage = sandboxPage(STATUS);
|
|
230
|
+
const stoppedPage = sandboxPage({ ...STATUS, sandbox: { ...STATUS.sandbox, running: false, phase: 'stopped' } });
|
|
231
|
+
const limited = sandboxPage({
|
|
232
|
+
...STATUS,
|
|
233
|
+
shared: { line: 'Running, started by bob (not shared)', limited: true, startedBy: 'bob' },
|
|
234
|
+
});
|
|
235
|
+
const ops = (page) => pageActions(page).flatMap((a) => (a.kind === 'operation' ? [a.op] : []));
|
|
236
|
+
t.check(ops(statusPage).join() === 'sandbox.showWindow,sandbox.hideWindow,sandbox.stop' &&
|
|
237
|
+
pageActions(statusPage).some((a) => a.kind === 'operation' && a.op === 'sandbox.stop' && a.danger) &&
|
|
238
|
+
ops(stoppedPage).length === 0 &&
|
|
239
|
+
limited.sections.length === 0 &&
|
|
240
|
+
limited.notice?.text === 'Running, started by bob (not shared)', "status: the window and stop (marked dangerous) only while it runs; another user's unshared sandbox shows only whose it is");
|
|
241
|
+
const list = {
|
|
242
|
+
sandboxRunning: true,
|
|
243
|
+
defaultAgent: 'codex',
|
|
244
|
+
wide: false,
|
|
245
|
+
sessions: [running('old', false), { ...running('new', true), startedAt: new Date(NOW).toISOString() }],
|
|
246
|
+
};
|
|
247
|
+
const sessions = sessionsPage(list);
|
|
248
|
+
const items = sessions.sections[0].items ?? [];
|
|
249
|
+
t.check(items[0].id === 'new' &&
|
|
250
|
+
items[0].primary?.kind === 'link' &&
|
|
251
|
+
items[0].primary.target.kind === 'terminal' &&
|
|
252
|
+
items[0].group === "This folder's project" &&
|
|
253
|
+
ops(sessions).includes('sessions.restart') &&
|
|
254
|
+
sessionsPage({ ...list, sessions: [] }).empty?.action?.kind === 'link', "sessions: this folder's project first, a running session opens its terminal, empty offers to start one");
|
|
255
|
+
for (const view of VIEWS) {
|
|
256
|
+
const known = new Set(view.operations.map((o) => o.id));
|
|
257
|
+
const sample = view.id === 'sandbox' ? statusPage : view.id === 'sessions' ? sessions : undefined;
|
|
258
|
+
if (sample)
|
|
259
|
+
t.check(ops(sample).every((op) => known.has(op)), view.id + ': the page offers only operations its view declares');
|
|
260
|
+
}
|
|
261
|
+
t.section('the CLI and the menu draw the same page');
|
|
262
|
+
const sessionLines = pageLines(sessionsPage({ ...list, wide: true }));
|
|
263
|
+
t.check(/^\s+ID\s+AGENT\s+PROJECT\s+STATE\s+ATTACHED\s+FOLDER\s+STARTED$/.test(sessionLines[0]) &&
|
|
264
|
+
/^\*\s+new\s/.test(sessionLines[1]) &&
|
|
265
|
+
sessionLines.includes('* = the project of this folder') &&
|
|
266
|
+
!sessionLines.some((l) => l.includes('--wide')), 'sessions on the command line: the table with the folder column (--wide), the marker and its note', sessionLines.join('\n'));
|
|
267
|
+
const statusLines = pageLines(statusPage);
|
|
268
|
+
t.check(statusLines.some((l) => /^Running\s+yes$/.test(l)) &&
|
|
269
|
+
statusLines.some((l) => /^Helper\s+responding, 2 sessions running$/.test(l)) &&
|
|
270
|
+
statusLines.includes('* api C:\\Projects\\api -> C:\\Projects\\api (started from here)') &&
|
|
271
|
+
statusLines.some((l) => l.startsWith('Repo guard 1 folder(s) guarded')), 'status on the command line: facts as rows, projects with their details, the repo guard', statusLines.join('\n'));
|
|
272
|
+
t.equal(pageLines(sessionsPage({ ...list, sessions: [] })).at(-1), 'No sessions. Start one with: sandbox claude', 'an empty page prints its command-line text');
|
|
273
|
+
const menu = menuView(sessions);
|
|
274
|
+
const chosen = menu.resolve({ kind: 'pick', value: 'new' });
|
|
275
|
+
t.check(menu.items.length === 2 &&
|
|
276
|
+
menu.actions.map((a) => a.key).join() === 'r,o,l' &&
|
|
277
|
+
menu.enterLabel(undefined, 'new') === 'attach' &&
|
|
278
|
+
chosen?.kind === 'action' &&
|
|
279
|
+
chosen.action.kind === 'link' &&
|
|
280
|
+
menu.resolve({ kind: 'action', key: 'l', value: undefined })?.kind === 'refresh', 'sessions in the menu: rows, the r/o/l keys, Enter attaches');
|
|
281
|
+
const menuStatus = menuView(statusPage, { category: 'status', prefix: 'status:' });
|
|
282
|
+
t.check(menuStatus.items.every((i) => i.category === 'status' && i.value.startsWith('status:')) &&
|
|
283
|
+
menuStatus.actions.some((a) => a.key === 'x' && a.label === 'stop'), 'a view inside one category of a page: rows in it, prefixed, with the keys of its actions');
|
|
284
|
+
t.section('settings, run against a config file');
|
|
285
|
+
const configFile = t.writeJson(t.at('app-settings', 'config.json'), {
|
|
286
|
+
storage: { root: t.dir('app-settings', 'storage') },
|
|
287
|
+
harnesses: {
|
|
288
|
+
planted: { npmPackage: 'p', npmBin: 'p', arguments: [], environment: { KEY: 'sk-planted-secret' } },
|
|
289
|
+
},
|
|
290
|
+
});
|
|
291
|
+
const ctx = {
|
|
292
|
+
loaded: { configPath: configFile },
|
|
293
|
+
options: { workspace: t.dir('app-settings', 'project') },
|
|
294
|
+
storageRoot: t.at('app-settings', 'storage'),
|
|
295
|
+
};
|
|
296
|
+
const quiet = fakeIO().io;
|
|
297
|
+
const set = (await runDefinition('settings.set', ctx, { key: 'sandbox.memoryMb', value: '8192' }, quiet));
|
|
298
|
+
const got = (await runDefinition('settings.get', ctx, { key: 'sandbox.memoryMb' }, quiet));
|
|
299
|
+
t.check(set.message === 'sandbox.memoryMb is 8192.' &&
|
|
300
|
+
got.value === 8192 &&
|
|
301
|
+
fs.readFileSync(configFile, 'utf8').includes('8192'), 'set reads text like the command line, writes the file, and get reads it back', set.message);
|
|
302
|
+
await t.throws(() => runDefinition('settings.set', ctx, { key: 'sandbox.memoryMb', value: 'lots' }, quiet), 'does not work for sandbox.memoryMb', 'a value that does not fit is refused with what is allowed');
|
|
303
|
+
await t.throws(() => runDefinition('settings.set', ctx, { key: 'sandbox.memoryMbb', value: '1' }, quiet), 'Did you mean sandbox.memoryMb', 'a typo gets a suggestion');
|
|
304
|
+
const reset = (await runDefinition('settings.reset', ctx, { key: 'sandbox.memoryMb' }, quiet));
|
|
305
|
+
t.check(reset.message.startsWith('sandbox.memoryMb is back to its default') &&
|
|
306
|
+
!fs.readFileSync(configFile, 'utf8').includes('8192'), 'reset removes the line');
|
|
307
|
+
const listed = await readQuery(QUERIES.find((q) => q.id === 'settings.list'), ctx, { scope: 'user' });
|
|
308
|
+
const rows = listed.rows;
|
|
309
|
+
const text = JSON.stringify(listed);
|
|
310
|
+
t.check(rows.length > 50 &&
|
|
311
|
+
!text.includes('sk-planted-secret') &&
|
|
312
|
+
text.includes('***') &&
|
|
313
|
+
!rows.some((r) => r.id === 'storage.root') &&
|
|
314
|
+
rows.every((r) => FieldSchema.safeParse(r.field).success), 'the rows: every setting with a Field, secrets as ***, the data folder through its own row');
|
|
315
|
+
const page = settingsPage(listed);
|
|
316
|
+
const edit = page.sections.flatMap((s) => s.items ?? []).find((i) => i.id === 'sandbox.memoryMb')?.primary;
|
|
317
|
+
t.check(page.sections.length >= 5 &&
|
|
318
|
+
edit?.kind === 'operation' &&
|
|
319
|
+
edit.op === 'settings.set' &&
|
|
320
|
+
edit.fields?.[0]?.name === 'value' &&
|
|
321
|
+
page.sections.some((s) => s.items?.some((i) => i.id === '@data-folder')), 'the settings page: a section per category, each setting edited through its Field');
|
|
322
|
+
t.equal(ago(NOW - 90 * 60000, NOW), '2 h ago', 'times read the same on every surface');
|
|
323
|
+
},
|
|
324
|
+
};
|
|
@@ -108,6 +108,19 @@ export const channelSuite = {
|
|
|
108
108
|
through('x\x1b]52;c;' + 'A'.repeat(70000), 'B'.repeat(100), '\x07y'),
|
|
109
109
|
through('x\xc2\x9d52;c;QUFB\xc2\x9cy\x1bP1$qm\x1b\\z'),
|
|
110
110
|
], ['ab\x1b]0;title\x07cd', 'xy', 'xy', 'xy', 'xyz'], 'clipboard writes (OSC 52) from the sandbox are stripped in every disguise (split chunks, leading zero, huge, C1); titles pass');
|
|
111
|
+
const copying = (...chunks) => {
|
|
112
|
+
const f = new OutputFilter({ clipboardWrites: true });
|
|
113
|
+
return Buffer.concat(chunks.map((s) => f.push(Buffer.from(s, 'latin1')))).toString('latin1');
|
|
114
|
+
};
|
|
115
|
+
const big = 'QUFB'.repeat(50000);
|
|
116
|
+
t.equal([
|
|
117
|
+
copying('a\x1b]52;c;ZXZpbA==\x07b'),
|
|
118
|
+
copying('x\x1b]5', '2;c;cGF5\x1b', '\\y'),
|
|
119
|
+
copying('x\x1b]52;c;' + big + '\x07y').length,
|
|
120
|
+
copying('x\x1b]52;c;?\x07y'),
|
|
121
|
+
copying('x\x1b]52;c;' + 'A'.repeat(1100000) + '\x07y'),
|
|
122
|
+
copying('x\x1bP1$qm\x1b\\y\x1b]9;9;macro\x07z'),
|
|
123
|
+
], ['a\x1b]52;c;ZXZpbA==\x07b', 'x\x1b]52;c;cGF5\x1b\\y', big.length + 10, 'xy', 'xy', 'xyz'], "with clipboard writes allowed: the agent's copy passes (split, long), a clipboard read or a huge one never does, other strings stay dropped");
|
|
111
124
|
const plain = '\x1b]8;;https://a.b\x1b\\link\x1b]8;;\x1b\\ \x1b[31mred\x1b[0m é\xc2\xa0';
|
|
112
125
|
t.equal([through(plain), through('\x1b]9;9;macro\x07\x1b]9;4;1;50\x07')], [plain, '\x1b]9;4;1;50\x07'], 'links, colours and UTF-8 pass unchanged; only the progress form of OSC 9 passes (ConEmu macros do not)');
|
|
113
126
|
await t.throws(() => new FrameReader().push(Buffer.concat([Buffer.from([16]), Buffer.from([0, 0, 0, 0x7f])])), 'frame too large', 'a frame announcing too much data is rejected instead of buffered');
|
|
@@ -13,7 +13,8 @@ import { ago, colorEnabled, shorten } from '../cli/output/index.js';
|
|
|
13
13
|
import { versionText } from '../cli/version.js';
|
|
14
14
|
import { hoistConfig, withoutConfig } from '../cli/argv.js';
|
|
15
15
|
import { closest, validNodeSpec, validPythonSpec } from '../cli/validate.js';
|
|
16
|
-
import { CATEGORIES, editConfigText,
|
|
16
|
+
import { CATEGORIES, editConfigText, SETTINGS_ELSEWHERE } from '../app/features/settings/index.js';
|
|
17
|
+
import { SchemaNode } from '../app/kernel/index.js';
|
|
17
18
|
import { Frame, ListScreen, Text, TextScreen } from '../cli/tui/index.js';
|
|
18
19
|
import { ConfigSchema, LocalOverridesSchema } from '../core/schema/index.js';
|
|
19
20
|
export const cliSuite = {
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
// Loading the settings: built-in defaults, a user config file, and the example config that ships with the tool.
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import _ from 'lodash';
|
|
4
|
+
import fs from 'node:fs';
|
|
4
5
|
import { DEFAULTS, loadConfig } from '../core/config/index.js';
|
|
6
|
+
import { diffText, exportSettings, planImport } from '../cli/menu/settings/index.js';
|
|
5
7
|
import { readJsonc } from '../core/util/index.js';
|
|
6
8
|
export const configSuite = {
|
|
7
9
|
name: 'config',
|
|
@@ -33,6 +35,63 @@ export const configSuite = {
|
|
|
33
35
|
const relativeRun = await f.run({}, { ...custom.config, storage: f.baseConfig.storage });
|
|
34
36
|
const mappedHosts = relativeRun.config.mappings.map((m) => m.host);
|
|
35
37
|
t.check(mappedHosts.includes(t.at('rel-folder')), "relative mappings in the user config resolve against the config file's folder", 'mapped hosts: ' + JSON.stringify(mappedHosts));
|
|
38
|
+
t.section('export and import of settings');
|
|
39
|
+
const SECRET = 'sk-test-0123456789';
|
|
40
|
+
const mine = loadConfig(t.writeJson(t.at('share', 'mine.json'), {
|
|
41
|
+
storage: { root: t.dir('share', 'storage') },
|
|
42
|
+
sandbox: { memoryMb: 4096, headless: true },
|
|
43
|
+
toolchains: { rust: { enabled: false } },
|
|
44
|
+
mappings: [{ host: 'C:\\Somebody\\Folder', guest: 'C:\\Folder', readOnly: false }],
|
|
45
|
+
harnesses: {
|
|
46
|
+
planted: { npmPackage: 'planted', npmBin: 'planted', arguments: [], environment: { PLANTED_KEY: SECRET } },
|
|
47
|
+
},
|
|
48
|
+
})).config;
|
|
49
|
+
const exported = exportSettings(mine, false);
|
|
50
|
+
const flat = JSON.stringify(exported.data);
|
|
51
|
+
t.check(!flat.includes(SECRET) && !flat.includes('storage') && !flat.includes('Somebody'), 'an export has no secret, no storage folder and no folder of this PC', flat);
|
|
52
|
+
t.equal([exported.data.sandbox, exported.data.toolchains, exported.data.defaultHarness], [{ memoryMb: 4096, headless: true }, { rust: { enabled: false } }, undefined], 'an export keeps only what differs from the defaults');
|
|
53
|
+
t.equal(exported.skipped.map((x) => x.why).sort(), ['machine', 'path', 'secret'], 'an export says what it left out (the data folder, a folder of this PC, a secret)');
|
|
54
|
+
t.check(JSON.stringify(exportSettings(mine, true).data).includes('Somebody'), '--with-paths keeps folders of this PC (secrets and the data folder stay out)');
|
|
55
|
+
const shared = JSON.stringify({
|
|
56
|
+
$schema: './config.schema.json',
|
|
57
|
+
sandbox: { memoryMb: 2048, headless: false },
|
|
58
|
+
storage: { root: 'D:\\elsewhere' },
|
|
59
|
+
mappings: [{ host: 'D:\\theirs', guest: 'C:\\T', readOnly: false }],
|
|
60
|
+
harnesses: { planted: { environment: { PLANTED_KEY: 'theirs' } } },
|
|
61
|
+
toolchains: { rust: { enabled: false } },
|
|
62
|
+
});
|
|
63
|
+
const plan = planImport(shared, mine);
|
|
64
|
+
t.equal(diffText(plan.lines), ['sandbox.memoryMb: 4096 -> 2048', 'sandbox.headless: true -> false'], 'an import shows old -> new for each changed setting and nothing for the unchanged ones');
|
|
65
|
+
t.equal(plan.skipped.map((x) => x.why).sort(), ['machine', 'path', 'secret'], 'an import skips secrets, the data folder and paths even when the file has them');
|
|
66
|
+
t.check(planImport('{"sandbox": {"noSuchThing": 1}}', mine).problems.length > 0 &&
|
|
67
|
+
planImport('[1]', mine).problems.length > 0 &&
|
|
68
|
+
planImport('{', mine).problems.length > 0, 'an import refuses a file that is not valid settings');
|
|
69
|
+
const home = t.dir('share', 'home');
|
|
70
|
+
const baseEnv = Object.fromEntries(Object.entries(process.env).filter(([k]) => !['AGENT_SANDBOX_CONFIG', 'AGENT_WORKSPACE'].includes(k)));
|
|
71
|
+
const env = { ...baseEnv, AGENT_SANDBOX_HOME: home, NO_COLOR: '1', AGENT_SANDBOX_HOST_ONLY: '1' };
|
|
72
|
+
const yours = t.writeJson(t.at('share', 'yours.json'), {
|
|
73
|
+
storage: { root: t.dir('share', 'storage2') },
|
|
74
|
+
sandbox: { memoryMb: 4096 },
|
|
75
|
+
});
|
|
76
|
+
const sharedFile = t.at('share', 'theirs.json');
|
|
77
|
+
fs.writeFileSync(sharedFile, shared);
|
|
78
|
+
const bin = (args) => f.runBin('agent-sandbox', [...args, '--config', yours], { cwd: f.workA, env });
|
|
79
|
+
const outFile = t.at('share', 'out.json');
|
|
80
|
+
const exportRun = await bin(['config', 'export', outFile]);
|
|
81
|
+
const written = readJsonc(outFile);
|
|
82
|
+
t.check(exportRun.status === 0 && written.sandbox?.memoryMb === 4096 && written.storage === undefined, 'sandbox config export writes the file without the data folder', exportRun.out);
|
|
83
|
+
const again = await bin(['config', 'export', outFile]);
|
|
84
|
+
t.check(again.status !== 0, 'export does not overwrite a file that is there', again.out);
|
|
85
|
+
const before = fs.readFileSync(yours, 'utf8');
|
|
86
|
+
const refused = await bin(['config', 'import', sharedFile]);
|
|
87
|
+
t.check(refused.status !== 0 && fs.readFileSync(yours, 'utf8') === before, 'import without --yes and without a terminal changes nothing', refused.out);
|
|
88
|
+
const imported = await bin(['config', 'import', sharedFile, '--yes']);
|
|
89
|
+
const after = readJsonc(yours);
|
|
90
|
+
t.check(imported.status === 0 &&
|
|
91
|
+
imported.out.includes('sandbox.memoryMb: 4096 -> 2048') &&
|
|
92
|
+
after.sandbox?.memoryMb === 2048 &&
|
|
93
|
+
after.storage?.root !== 'D:\\elsewhere' &&
|
|
94
|
+
after.mappings === undefined, 'sandbox config import --yes applies the settings and never the data folder or paths', imported.out);
|
|
36
95
|
t.section('config.example.json');
|
|
37
96
|
const examplePath = path.join(t.packageRoot, 'config.example.json');
|
|
38
97
|
t.equal(loadConfig(examplePath).config.defaultHarness, 'claude', 'config.example.json is valid');
|