windows-development-sandbox 1.12.0 → 1.13.2
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 +11 -1
- package/dist/app/features/access/access.def.d.ts +190 -0
- package/dist/app/features/access/access.def.js +273 -0
- package/dist/app/features/access/access.run.d.ts +79 -0
- package/dist/app/features/access/access.run.js +129 -0
- package/dist/app/features/access/index.d.ts +2 -0
- package/dist/app/features/access/index.js +3 -0
- package/dist/app/features/accounts/accounts.def.d.ts +1 -0
- package/dist/app/features/accounts/accounts.def.js +12 -3
- package/dist/app/features/accounts/accounts.run.d.ts +1 -0
- package/dist/app/features/accounts/accounts.run.js +20 -11
- package/dist/app/features/agents/agent-entry.js +5 -0
- package/dist/app/features/agents/agents.def.js +1 -0
- package/dist/app/features/apps/apps.def.js +1 -0
- package/dist/app/features/audit/audit.def.d.ts +11 -2
- package/dist/app/features/audit/audit.def.js +7 -4
- package/dist/app/features/audit/audit.run.d.ts +3 -0
- package/dist/app/features/defs.d.ts +156 -3
- package/dist/app/features/defs.js +12 -1
- package/dist/app/features/files/files.def.js +1 -0
- package/dist/app/features/health/health.def.js +1 -0
- package/dist/app/features/sandbox/sandbox.def.js +3 -0
- package/dist/app/features/sessions/sessions.def.d.ts +36 -0
- package/dist/app/features/sessions/sessions.def.js +77 -9
- package/dist/app/features/sessions/sessions.run.d.ts +25 -2
- package/dist/app/features/sessions/sessions.run.js +71 -3
- package/dist/app/features/settings/settings-file.d.ts +7 -0
- package/dist/app/features/settings/settings-file.js +19 -2
- package/dist/app/features/settings/settings-rows.js +9 -1
- package/dist/app/features/settings/settings.def.d.ts +8 -0
- package/dist/app/features/settings/settings.def.js +17 -0
- package/dist/app/features/web/web.def.d.ts +9 -0
- package/dist/app/features/web/web.def.js +26 -1
- package/dist/app/features/web/web.run.d.ts +3 -0
- package/dist/app/features/web/web.run.js +11 -3
- package/dist/app/kernel/access.d.ts +33 -0
- package/dist/app/kernel/access.js +44 -0
- package/dist/app/kernel/define.d.ts +14 -1
- package/dist/app/kernel/index.d.ts +1 -0
- package/dist/app/kernel/index.js +1 -0
- package/dist/app/kernel/rpc.d.ts +5 -2
- package/dist/app/registry.d.ts +4 -1
- package/dist/app/registry.js +24 -4
- package/dist/bin/agent-sandbox.js +7 -1
- package/dist/bin/chunk-IL7BXJF2.js +7 -0
- package/dist/bin/chunk-K4SU3MHJ.js +10432 -0
- package/dist/bin/chunk-VENWSGX2.js +78437 -0
- package/dist/bin/claude-host.js +10 -4
- package/dist/bin/claude-sbx.js +8 -2
- package/dist/bin/codex-sbx.js +8 -2
- package/dist/bin/gemini-sbx.js +8 -2
- package/dist/bin/sandbox.js +7 -2
- package/dist/bin/sbx-hide.js +8 -2
- package/dist/bin/sbx-show.js +8 -2
- package/dist/cli/commands/launch/prepare.js +24 -7
- package/dist/cli/commands/launch/running.js +4 -0
- package/dist/cli/commands/registry.js +2 -0
- package/dist/cli/commands/remote.d.ts +2 -0
- package/dist/cli/commands/remote.js +49 -0
- package/dist/cli/commands/sessions/reopen.js +3 -0
- package/dist/cli/commands/storage/offer.js +1 -1
- package/dist/cli/commands/web.js +66 -0
- package/dist/cli/manifest.d.ts +63 -4
- package/dist/cli/manifest.js +33 -4
- package/dist/cli/menu/io-menu.d.ts +5 -3
- package/dist/cli/menu/io-menu.js +12 -5
- package/dist/cli/menu/live.d.ts +29 -0
- package/dist/cli/menu/live.js +92 -21
- package/dist/cli/menu/menu.d.ts +2 -1
- package/dist/cli/menu/menu.js +16 -2
- package/dist/cli/menu/pages/list-page.d.ts +5 -0
- package/dist/cli/menu/pages/list-page.js +19 -0
- package/dist/cli/menu/remote-menu.d.ts +19 -0
- package/dist/cli/menu/remote-menu.js +108 -0
- package/dist/cli/menu/shell.d.ts +2 -0
- package/dist/cli/menu/shell.js +1 -1
- package/dist/cli/menu/views.js +2 -0
- package/dist/cli/operations/handler.js +15 -1
- package/dist/cli/services/accounts-service.d.ts +7 -0
- package/dist/cli/services/accounts-service.js +20 -0
- package/dist/cli/services/remote-client.d.ts +16 -0
- package/dist/cli/services/remote-client.js +141 -0
- package/dist/cli/services/sessions-service.d.ts +5 -0
- package/dist/cli/services/sessions-service.js +17 -1
- package/dist/cli/tui/app.d.ts +4 -0
- package/dist/cli/tui/app.js +5 -0
- package/dist/cli/tui/terminal.d.ts +6 -0
- package/dist/cli/tui/terminal.js +8 -1
- package/dist/cli/validate.js +8 -4
- package/dist/core/config/defaults.js +11 -1
- package/dist/core/local-config/file.js +16 -2
- package/dist/core/local-config/overrides.js +7 -0
- package/dist/core/package-info.js +1 -1
- package/dist/core/run/versions.js +9 -8
- package/dist/core/schema/config.d.ts +22 -3
- package/dist/core/schema/config.js +39 -2
- package/dist/core/schema/web.d.ts +51 -0
- package/dist/core/schema/web.js +46 -1
- package/dist/core/util/fs.d.ts +5 -0
- package/dist/core/util/fs.js +23 -0
- package/dist/core/util/index.d.ts +1 -1
- package/dist/core/util/index.js +1 -1
- package/dist/guest/daemon/daemon.js +2 -0
- package/dist/guest/daemon/idle-shutdown.js +2 -1
- package/dist/guest/daemon/sessions.d.ts +16 -2
- package/dist/guest/daemon/sessions.js +126 -20
- package/dist/guest/git/decision.js +7 -0
- package/dist/guest/sessions/cmdline.d.ts +14 -0
- package/dist/guest/sessions/cmdline.js +10 -0
- package/dist/guest/toolchains/setup.js +3 -0
- package/dist/host/audit/log.d.ts +3 -2
- package/dist/host/audit/log.js +3 -3
- package/dist/host/channel/channel.d.ts +5 -1
- package/dist/host/channel/channel.js +12 -0
- package/dist/host/guard/agent-files.js +7 -1
- package/dist/host/guard/commands.js +13 -1
- package/dist/host/guard/git-config.js +27 -7
- package/dist/host/guard/git-dirs.js +78 -3
- package/dist/host/guard/mark-of-the-web.js +3 -1
- package/dist/host/guard/nested-repos.js +7 -2
- package/dist/host/guard/store.js +1 -0
- package/dist/host/guard/types.d.ts +2 -0
- package/dist/host/launch/mappings.js +11 -1
- package/dist/host/launch/project-scan.js +2 -21
- package/dist/host/launch/stage.js +4 -0
- package/dist/host/life/session-ledger.js +2 -1
- package/dist/host/remote/access-store.d.ts +156 -0
- package/dist/host/remote/access-store.js +308 -0
- package/dist/host/remote/cert.d.ts +20 -0
- package/dist/host/remote/cert.js +103 -0
- package/dist/host/remote/door-client.d.ts +13 -0
- package/dist/host/remote/door-client.js +54 -0
- package/dist/host/remote/firewall.d.ts +44 -0
- package/dist/host/remote/firewall.js +118 -0
- package/dist/host/remote/gate.d.ts +125 -0
- package/dist/host/remote/gate.js +391 -0
- package/dist/host/remote/index.d.ts +8 -0
- package/dist/host/remote/index.js +10 -0
- package/dist/host/remote/lan.d.ts +24 -0
- package/dist/host/remote/lan.js +88 -0
- package/dist/host/remote/tls-store.d.ts +22 -0
- package/dist/host/remote/tls-store.js +63 -0
- package/dist/host/remote/windows-helper.d.ts +67 -0
- package/dist/host/remote/windows-helper.js +129 -0
- package/dist/host/sandbox/platform.d.ts +2 -0
- package/dist/host/sandbox/platform.js +19 -4
- package/dist/host/storage/contained/state.d.ts +3 -2
- package/dist/host/storage/contained/state.js +5 -2
- package/dist/host/web/bridge.d.ts +5 -1
- package/dist/host/web/bridge.js +6 -2
- package/dist/host/web/browser.js +1 -1
- package/dist/host/web/control.d.ts +4 -1
- package/dist/host/web/control.js +28 -4
- package/dist/host/web/rpc.d.ts +16 -2
- package/dist/host/web/rpc.js +37 -4
- package/dist/host/web/runner.d.ts +4 -0
- package/dist/host/web/runner.js +12 -0
- package/dist/host/web/server.d.ts +18 -2
- package/dist/host/web/server.js +97 -24
- package/dist/host/web/state.d.ts +5 -0
- package/dist/host/web/state.js +8 -0
- package/dist/runtime/agent.js +74 -3
- package/dist/runtime/daemon.js +205 -24
- package/dist/runtime/db.js +74 -3
- package/dist/runtime/gitguard.js +6 -0
- package/dist/runtime/main.js +77 -3
- package/dist/runtime/netproxy.js +74 -3
- package/dist/runtime/view.js +76 -3
- package/dist/selftest/app.test.js +41 -6
- package/dist/selftest/channel.test.js +16 -0
- package/dist/selftest/cmdline.test.js +16 -0
- package/dist/selftest/git-access.test.js +8 -0
- package/dist/selftest/hostile-input.test.js +12 -1
- package/dist/selftest/index.js +2 -0
- package/dist/selftest/layout.test.js +10 -1
- package/dist/selftest/live-daemon.test.js +25 -0
- package/dist/selftest/local-config.test.js +30 -0
- package/dist/selftest/menu-perf.test.js +198 -12
- package/dist/selftest/menu.test.js +4 -0
- package/dist/selftest/remote.test.d.ts +2 -0
- package/dist/selftest/remote.test.js +536 -0
- package/dist/selftest/repo-guard.test.js +56 -2
- package/dist/selftest/resume.test.js +2 -1
- package/dist/selftest/sharing.test.js +9 -2
- package/dist/selftest/support/fixtures.d.ts +13 -0
- package/dist/selftest/support/web-preview.js +63 -0
- package/dist/selftest/web.test.js +2 -0
- package/dist/shared/channel/schema.d.ts +23 -0
- package/dist/shared/channel/schema.js +8 -0
- package/dist/shared/pty/protocol.d.ts +2 -0
- package/dist/shared/pty/protocol.js +2 -0
- package/dist/web/index.html +1 -1
- package/dist/web/main-NBQ3LF4E.js +221 -0
- package/native/csharp/HcsHost/AssemblyInfo.cs +3 -3
- package/native/csharp/HostTools/AssemblyInfo.cs +3 -3
- package/native/csharp/HostTools/Program.cs +17 -1
- package/native/csharp/HostTools/WebHelper.cs +499 -0
- package/native/scripts/get-network-profile.ps1 +19 -0
- package/native/scripts/set-folder-access.ps1 +11 -0
- package/native/scripts/set-web-firewall.ps1 +103 -0
- package/package.json +1 -1
- package/sandbox/csharp/PtyHost.cs +20 -0
- package/schemas/config.schema.json +65 -0
- package/schemas/sandbox-config.schema.json +0 -18
- package/dist/web/main-UD4QCXZR.js +0 -219
package/dist/runtime/main.js
CHANGED
|
@@ -26765,12 +26765,39 @@ var ResumeSchema = external_exports.strictObject({
|
|
|
26765
26765
|
});
|
|
26766
26766
|
|
|
26767
26767
|
// src/core/schema/web.ts
|
|
26768
|
+
var REMOTE_ROLES = ["view", "control"];
|
|
26769
|
+
var WindowsUserNameSchema = external_exports.string().trim().min(1).max(120).regex(
|
|
26770
|
+
/^(?:[A-Za-z0-9][A-Za-z0-9._ -]{0,62}\\)?[^\\/"[\]:;|=,+*?<>@]{1,64}$/,
|
|
26771
|
+
"a Windows user name (name or DOMAIN\\name)"
|
|
26772
|
+
);
|
|
26773
|
+
var RemoteUserSchema = external_exports.strictObject({
|
|
26774
|
+
name: WindowsUserNameSchema.describe("The Windows user (name, or DOMAIN\\name)"),
|
|
26775
|
+
role: external_exports.enum(REMOTE_ROLES).describe("view: sees everything, terminals read-only. control: everything but remote access, logins and tokens.")
|
|
26776
|
+
});
|
|
26777
|
+
var WebLanSchema = external_exports.strictObject({
|
|
26778
|
+
enabled: external_exports.boolean().describe(
|
|
26779
|
+
"(Beta) Devices on your network (a phone): an HTTPS address on this PC for devices you pair with a one-time code (sandbox web pair). Needs a firewall rule (sandbox web lan on asks Windows once). Off: every paired device is signed out."
|
|
26780
|
+
),
|
|
26781
|
+
port: external_exports.number().int().min(1024).max(65535).describe("HTTPS port for paired devices on your network."),
|
|
26782
|
+
address: external_exports.string().trim().max(15).regex(/^$|^\d{1,3}(\.\d{1,3}){3}$/, "an IPv4 address or empty").describe("This PC's private address to listen on. Empty: the one of its network adapter (never a public one).")
|
|
26783
|
+
});
|
|
26784
|
+
var WebRemoteSchema = external_exports.strictObject({
|
|
26785
|
+
windowsSignIn: external_exports.boolean().describe(
|
|
26786
|
+
"(Beta) Other Windows users of this PC named in web.remote.users sign in to your web UI (and sandbox remote) with their own Windows account: no password, no link."
|
|
26787
|
+
),
|
|
26788
|
+
users: external_exports.array(RemoteUserSchema).max(32).describe("Who else may sign in with Windows, and their role (view or control)."),
|
|
26789
|
+
rememberDays: external_exports.number().int().min(1).max(90).describe(
|
|
26790
|
+
"How many days a paired device or a Windows sign-in stays signed in (from when it signed in, not renewed)."
|
|
26791
|
+
),
|
|
26792
|
+
lan: WebLanSchema
|
|
26793
|
+
});
|
|
26768
26794
|
var WebSchema = external_exports.strictObject({
|
|
26769
26795
|
enabled: external_exports.boolean().describe(
|
|
26770
26796
|
"(Beta) The web UI: your sandbox sessions in a browser on this PC (http://127.0.0.1:<port>, this PC only). On: every sandbox start also starts it in the background (it keeps running until sandbox web stop or a restart of the PC). Off: only sandbox web start runs it. Open it signed in with sandbox web open."
|
|
26771
26797
|
),
|
|
26772
26798
|
port: external_exports.number().int().min(1024).max(65535).describe("Port of the web UI on 127.0.0.1 (this PC only). Pick another when it is taken."),
|
|
26773
|
-
openOnLaunch: external_exports.boolean().describe("(Beta) With the web UI on: open it in your browser, already signed in, every time a sandbox starts.")
|
|
26799
|
+
openOnLaunch: external_exports.boolean().describe("(Beta) With the web UI on: open it in your browser, already signed in, every time a sandbox starts."),
|
|
26800
|
+
remote: WebRemoteSchema.describe("(Beta) Access for other Windows users of this PC and for paired devices.")
|
|
26774
26801
|
});
|
|
26775
26802
|
|
|
26776
26803
|
// src/core/schema/vpn.ts
|
|
@@ -26800,6 +26827,36 @@ var GithubSchema = external_exports.strictObject({
|
|
|
26800
26827
|
});
|
|
26801
26828
|
|
|
26802
26829
|
// src/core/schema/config.ts
|
|
26830
|
+
var RESERVED_ENV_PREFIX = /^(AGENT_|GIT_|GH_|GITHUB_|NODE_|NPM_CONFIG_(USERCONFIG|GLOBALCONFIG|PREFIX|SCRIPT_SHELL)|SSL_|CLAUDE|ANTHROPIC_|OPENAI_|CODEX_|GEMINI_|GOOGLE_|COPILOT_|AWS_|AZURE_)/i;
|
|
26831
|
+
var RESERVED_ENV_SUFFIX = /(_PROXY|_BASE_URL|_ENDPOINT|_CA_BUNDLE|_CA_CERTS)$/i;
|
|
26832
|
+
var RESERVED_ENV_NAMES = new Set(
|
|
26833
|
+
[
|
|
26834
|
+
"PATH",
|
|
26835
|
+
"PATHEXT",
|
|
26836
|
+
"BASH_ENV",
|
|
26837
|
+
"ENV",
|
|
26838
|
+
"COMSPEC",
|
|
26839
|
+
"PSMODULEPATH",
|
|
26840
|
+
"PYTHONPATH",
|
|
26841
|
+
"PYTHONSTARTUP",
|
|
26842
|
+
"PERL5OPT",
|
|
26843
|
+
"RUBYOPT",
|
|
26844
|
+
"JAVA_TOOL_OPTIONS",
|
|
26845
|
+
"LD_PRELOAD",
|
|
26846
|
+
"SYSTEMROOT",
|
|
26847
|
+
"WINDIR",
|
|
26848
|
+
"HOME",
|
|
26849
|
+
"USERPROFILE",
|
|
26850
|
+
"APPDATA",
|
|
26851
|
+
"LOCALAPPDATA",
|
|
26852
|
+
"PROGRAMDATA",
|
|
26853
|
+
"TEMP",
|
|
26854
|
+
"TMP"
|
|
26855
|
+
].map((n) => n.toUpperCase())
|
|
26856
|
+
);
|
|
26857
|
+
function reservedEnvName(name) {
|
|
26858
|
+
return RESERVED_ENV_PREFIX.test(name) || RESERVED_ENV_SUFFIX.test(name) || RESERVED_ENV_NAMES.has(name.toUpperCase());
|
|
26859
|
+
}
|
|
26803
26860
|
var ConfigSchema = external_exports.strictObject({
|
|
26804
26861
|
$schema: SchemaRefSchema.optional(),
|
|
26805
26862
|
defaultHarness: NameSchema.describe("Agent started by `sandbox launch` (sandbox claude always runs claude)."),
|
|
@@ -26866,10 +26923,14 @@ var LocalOverridesSchema = external_exports.strictObject({
|
|
|
26866
26923
|
scripts: StringsSchema.describe(
|
|
26867
26924
|
"Scripts (.ps1 / .mjs / .cmd, relative to this file) run inside the sandbox before the agent."
|
|
26868
26925
|
),
|
|
26869
|
-
// This PC's resources are yours to share: CPU
|
|
26926
|
+
// This PC's resources are yours to share: CPU, paging file (this PC's disk) and the RAM kept for this PC are not a
|
|
26927
|
+
// project's to set.
|
|
26870
26928
|
sandbox: SandboxSchema.omit({
|
|
26871
26929
|
cpuLimit: true,
|
|
26872
26930
|
pageFileMb: true,
|
|
26931
|
+
reserveHostMb: true,
|
|
26932
|
+
maxMemoryPercent: true,
|
|
26933
|
+
maxMemoryMb: true,
|
|
26873
26934
|
engine: true,
|
|
26874
26935
|
agentMemoryHeadroomMb: true,
|
|
26875
26936
|
pressureResponse: true
|
|
@@ -26883,7 +26944,10 @@ var LocalOverridesSchema = external_exports.strictObject({
|
|
|
26883
26944
|
// Every toolchain setting, each one optional (a project usually only switches languages on or pins a version).
|
|
26884
26945
|
toolchains: external_exports.strictObject({
|
|
26885
26946
|
...import_lodash.default.mapValues(import_lodash.default.omit(ToolchainsSchema.shape, "environment"), (shape) => shape.partial()),
|
|
26886
|
-
environment: ToolchainsSchema.shape.environment
|
|
26947
|
+
environment: ToolchainsSchema.shape.environment.refine(
|
|
26948
|
+
(env) => !Object.keys(env).some(reservedEnvName),
|
|
26949
|
+
"names the sandbox sets or that carry logins (AGENT_*, GIT_*, PATH, BASH_ENV, NODE_*, proxies, ANTHROPIC_*, CLAUDE_* ...) cannot be set by a project"
|
|
26950
|
+
)
|
|
26887
26951
|
}).partial(),
|
|
26888
26952
|
databases: external_exports.strictObject(import_lodash.default.mapValues(DatabasesSchema.shape, (shape) => shape.partial())).partial(),
|
|
26889
26953
|
attach: AttachSchema.partial(),
|
|
@@ -26953,6 +27017,7 @@ var ProjectSpecSchema = external_exports.strictObject({
|
|
|
26953
27017
|
/** Its hooks were staged in C:\AgentLaunch\projects\<name>\hooks. */
|
|
26954
27018
|
hooks: external_exports.boolean()
|
|
26955
27019
|
});
|
|
27020
|
+
var ShellChoiceSchema = external_exports.enum(["auto", "pwsh", "powershell", "cmd"]);
|
|
26956
27021
|
var GuestRequestSchema = external_exports.discriminatedUnion("kind", [
|
|
26957
27022
|
external_exports.strictObject({
|
|
26958
27023
|
kind: external_exports.literal("session"),
|
|
@@ -26962,8 +27027,12 @@ var GuestRequestSchema = external_exports.discriminatedUnion("kind", [
|
|
|
26962
27027
|
cwd: Text(),
|
|
26963
27028
|
project: ProjectSpecSchema.optional(),
|
|
26964
27029
|
harnessSpec: HarnessSchema.optional(),
|
|
27030
|
+
/** A plain shell instead of an agent (harness is then "shell" and args are ignored). */
|
|
27031
|
+
shell: ShellChoiceSchema.optional(),
|
|
26965
27032
|
createdAt: TimeSchema
|
|
26966
27033
|
}),
|
|
27034
|
+
/** End a running session (an agent or a shell) and everything it started. */
|
|
27035
|
+
external_exports.strictObject({ kind: external_exports.literal("close"), id: IdSchema, session: IdSchema, createdAt: TimeSchema }),
|
|
26967
27036
|
/** Provision a registered project without starting an agent (sandbox start / exec / db from its folder). */
|
|
26968
27037
|
external_exports.strictObject({ kind: external_exports.literal("project"), id: IdSchema, project: ProjectSpecSchema, createdAt: TimeSchema }),
|
|
26969
27038
|
external_exports.strictObject({
|
|
@@ -27029,6 +27098,8 @@ var SessionStatusSchema = external_exports.object({
|
|
|
27029
27098
|
id: IdSchema,
|
|
27030
27099
|
launchId: Text(64),
|
|
27031
27100
|
harness: Text(64),
|
|
27101
|
+
/** A plain shell (harness is then the shell: pwsh, powershell or cmd); absent: an agent. */
|
|
27102
|
+
kind: external_exports.enum(["agent", "shell"]).optional(),
|
|
27032
27103
|
cwd: Text(),
|
|
27033
27104
|
/** The project it runs in: its hub name, or the launch folder's for the first project. */
|
|
27034
27105
|
project: Text(64).optional(),
|
|
@@ -32651,6 +32722,7 @@ async function setupToolchains(config2, versions, options, run3 = {}) {
|
|
|
32651
32722
|
env.summary.push(...part.summary);
|
|
32652
32723
|
}
|
|
32653
32724
|
Object.assign(env.vars, config2.environment);
|
|
32725
|
+
Object.assign(env.vars, envs.get("Git")?.vars);
|
|
32654
32726
|
log("Set up " + steps.length + " toolchains and databases.");
|
|
32655
32727
|
return env;
|
|
32656
32728
|
}
|
|
@@ -34964,6 +35036,8 @@ var FrameType = {
|
|
|
34964
35036
|
Resize: 3,
|
|
34965
35037
|
Restart: 4,
|
|
34966
35038
|
Ping: 5,
|
|
35039
|
+
/** Ends the program and everything it started, and with it the session. */
|
|
35040
|
+
Close: 6,
|
|
34967
35041
|
Output: 16,
|
|
34968
35042
|
Exit: 17,
|
|
34969
35043
|
Error: 18,
|
package/dist/runtime/netproxy.js
CHANGED
|
@@ -26689,12 +26689,39 @@ var ResumeSchema = external_exports.strictObject({
|
|
|
26689
26689
|
});
|
|
26690
26690
|
|
|
26691
26691
|
// src/core/schema/web.ts
|
|
26692
|
+
var REMOTE_ROLES = ["view", "control"];
|
|
26693
|
+
var WindowsUserNameSchema = external_exports.string().trim().min(1).max(120).regex(
|
|
26694
|
+
/^(?:[A-Za-z0-9][A-Za-z0-9._ -]{0,62}\\)?[^\\/"[\]:;|=,+*?<>@]{1,64}$/,
|
|
26695
|
+
"a Windows user name (name or DOMAIN\\name)"
|
|
26696
|
+
);
|
|
26697
|
+
var RemoteUserSchema = external_exports.strictObject({
|
|
26698
|
+
name: WindowsUserNameSchema.describe("The Windows user (name, or DOMAIN\\name)"),
|
|
26699
|
+
role: external_exports.enum(REMOTE_ROLES).describe("view: sees everything, terminals read-only. control: everything but remote access, logins and tokens.")
|
|
26700
|
+
});
|
|
26701
|
+
var WebLanSchema = external_exports.strictObject({
|
|
26702
|
+
enabled: external_exports.boolean().describe(
|
|
26703
|
+
"(Beta) Devices on your network (a phone): an HTTPS address on this PC for devices you pair with a one-time code (sandbox web pair). Needs a firewall rule (sandbox web lan on asks Windows once). Off: every paired device is signed out."
|
|
26704
|
+
),
|
|
26705
|
+
port: external_exports.number().int().min(1024).max(65535).describe("HTTPS port for paired devices on your network."),
|
|
26706
|
+
address: external_exports.string().trim().max(15).regex(/^$|^\d{1,3}(\.\d{1,3}){3}$/, "an IPv4 address or empty").describe("This PC's private address to listen on. Empty: the one of its network adapter (never a public one).")
|
|
26707
|
+
});
|
|
26708
|
+
var WebRemoteSchema = external_exports.strictObject({
|
|
26709
|
+
windowsSignIn: external_exports.boolean().describe(
|
|
26710
|
+
"(Beta) Other Windows users of this PC named in web.remote.users sign in to your web UI (and sandbox remote) with their own Windows account: no password, no link."
|
|
26711
|
+
),
|
|
26712
|
+
users: external_exports.array(RemoteUserSchema).max(32).describe("Who else may sign in with Windows, and their role (view or control)."),
|
|
26713
|
+
rememberDays: external_exports.number().int().min(1).max(90).describe(
|
|
26714
|
+
"How many days a paired device or a Windows sign-in stays signed in (from when it signed in, not renewed)."
|
|
26715
|
+
),
|
|
26716
|
+
lan: WebLanSchema
|
|
26717
|
+
});
|
|
26692
26718
|
var WebSchema = external_exports.strictObject({
|
|
26693
26719
|
enabled: external_exports.boolean().describe(
|
|
26694
26720
|
"(Beta) The web UI: your sandbox sessions in a browser on this PC (http://127.0.0.1:<port>, this PC only). On: every sandbox start also starts it in the background (it keeps running until sandbox web stop or a restart of the PC). Off: only sandbox web start runs it. Open it signed in with sandbox web open."
|
|
26695
26721
|
),
|
|
26696
26722
|
port: external_exports.number().int().min(1024).max(65535).describe("Port of the web UI on 127.0.0.1 (this PC only). Pick another when it is taken."),
|
|
26697
|
-
openOnLaunch: external_exports.boolean().describe("(Beta) With the web UI on: open it in your browser, already signed in, every time a sandbox starts.")
|
|
26723
|
+
openOnLaunch: external_exports.boolean().describe("(Beta) With the web UI on: open it in your browser, already signed in, every time a sandbox starts."),
|
|
26724
|
+
remote: WebRemoteSchema.describe("(Beta) Access for other Windows users of this PC and for paired devices.")
|
|
26698
26725
|
});
|
|
26699
26726
|
|
|
26700
26727
|
// src/core/schema/vpn.ts
|
|
@@ -26724,6 +26751,36 @@ var GithubSchema = external_exports.strictObject({
|
|
|
26724
26751
|
});
|
|
26725
26752
|
|
|
26726
26753
|
// src/core/schema/config.ts
|
|
26754
|
+
var RESERVED_ENV_PREFIX = /^(AGENT_|GIT_|GH_|GITHUB_|NODE_|NPM_CONFIG_(USERCONFIG|GLOBALCONFIG|PREFIX|SCRIPT_SHELL)|SSL_|CLAUDE|ANTHROPIC_|OPENAI_|CODEX_|GEMINI_|GOOGLE_|COPILOT_|AWS_|AZURE_)/i;
|
|
26755
|
+
var RESERVED_ENV_SUFFIX = /(_PROXY|_BASE_URL|_ENDPOINT|_CA_BUNDLE|_CA_CERTS)$/i;
|
|
26756
|
+
var RESERVED_ENV_NAMES = new Set(
|
|
26757
|
+
[
|
|
26758
|
+
"PATH",
|
|
26759
|
+
"PATHEXT",
|
|
26760
|
+
"BASH_ENV",
|
|
26761
|
+
"ENV",
|
|
26762
|
+
"COMSPEC",
|
|
26763
|
+
"PSMODULEPATH",
|
|
26764
|
+
"PYTHONPATH",
|
|
26765
|
+
"PYTHONSTARTUP",
|
|
26766
|
+
"PERL5OPT",
|
|
26767
|
+
"RUBYOPT",
|
|
26768
|
+
"JAVA_TOOL_OPTIONS",
|
|
26769
|
+
"LD_PRELOAD",
|
|
26770
|
+
"SYSTEMROOT",
|
|
26771
|
+
"WINDIR",
|
|
26772
|
+
"HOME",
|
|
26773
|
+
"USERPROFILE",
|
|
26774
|
+
"APPDATA",
|
|
26775
|
+
"LOCALAPPDATA",
|
|
26776
|
+
"PROGRAMDATA",
|
|
26777
|
+
"TEMP",
|
|
26778
|
+
"TMP"
|
|
26779
|
+
].map((n) => n.toUpperCase())
|
|
26780
|
+
);
|
|
26781
|
+
function reservedEnvName(name) {
|
|
26782
|
+
return RESERVED_ENV_PREFIX.test(name) || RESERVED_ENV_SUFFIX.test(name) || RESERVED_ENV_NAMES.has(name.toUpperCase());
|
|
26783
|
+
}
|
|
26727
26784
|
var ConfigSchema = external_exports.strictObject({
|
|
26728
26785
|
$schema: SchemaRefSchema.optional(),
|
|
26729
26786
|
defaultHarness: NameSchema.describe("Agent started by `sandbox launch` (sandbox claude always runs claude)."),
|
|
@@ -26790,10 +26847,14 @@ var LocalOverridesSchema = external_exports.strictObject({
|
|
|
26790
26847
|
scripts: StringsSchema.describe(
|
|
26791
26848
|
"Scripts (.ps1 / .mjs / .cmd, relative to this file) run inside the sandbox before the agent."
|
|
26792
26849
|
),
|
|
26793
|
-
// This PC's resources are yours to share: CPU
|
|
26850
|
+
// This PC's resources are yours to share: CPU, paging file (this PC's disk) and the RAM kept for this PC are not a
|
|
26851
|
+
// project's to set.
|
|
26794
26852
|
sandbox: SandboxSchema.omit({
|
|
26795
26853
|
cpuLimit: true,
|
|
26796
26854
|
pageFileMb: true,
|
|
26855
|
+
reserveHostMb: true,
|
|
26856
|
+
maxMemoryPercent: true,
|
|
26857
|
+
maxMemoryMb: true,
|
|
26797
26858
|
engine: true,
|
|
26798
26859
|
agentMemoryHeadroomMb: true,
|
|
26799
26860
|
pressureResponse: true
|
|
@@ -26807,7 +26868,10 @@ var LocalOverridesSchema = external_exports.strictObject({
|
|
|
26807
26868
|
// Every toolchain setting, each one optional (a project usually only switches languages on or pins a version).
|
|
26808
26869
|
toolchains: external_exports.strictObject({
|
|
26809
26870
|
...import_lodash.default.mapValues(import_lodash.default.omit(ToolchainsSchema.shape, "environment"), (shape) => shape.partial()),
|
|
26810
|
-
environment: ToolchainsSchema.shape.environment
|
|
26871
|
+
environment: ToolchainsSchema.shape.environment.refine(
|
|
26872
|
+
(env) => !Object.keys(env).some(reservedEnvName),
|
|
26873
|
+
"names the sandbox sets or that carry logins (AGENT_*, GIT_*, PATH, BASH_ENV, NODE_*, proxies, ANTHROPIC_*, CLAUDE_* ...) cannot be set by a project"
|
|
26874
|
+
)
|
|
26811
26875
|
}).partial(),
|
|
26812
26876
|
databases: external_exports.strictObject(import_lodash.default.mapValues(DatabasesSchema.shape, (shape) => shape.partial())).partial(),
|
|
26813
26877
|
attach: AttachSchema.partial(),
|
|
@@ -26877,6 +26941,7 @@ var ProjectSpecSchema = external_exports.strictObject({
|
|
|
26877
26941
|
/** Its hooks were staged in C:\AgentLaunch\projects\<name>\hooks. */
|
|
26878
26942
|
hooks: external_exports.boolean()
|
|
26879
26943
|
});
|
|
26944
|
+
var ShellChoiceSchema = external_exports.enum(["auto", "pwsh", "powershell", "cmd"]);
|
|
26880
26945
|
var GuestRequestSchema = external_exports.discriminatedUnion("kind", [
|
|
26881
26946
|
external_exports.strictObject({
|
|
26882
26947
|
kind: external_exports.literal("session"),
|
|
@@ -26886,8 +26951,12 @@ var GuestRequestSchema = external_exports.discriminatedUnion("kind", [
|
|
|
26886
26951
|
cwd: Text(),
|
|
26887
26952
|
project: ProjectSpecSchema.optional(),
|
|
26888
26953
|
harnessSpec: HarnessSchema.optional(),
|
|
26954
|
+
/** A plain shell instead of an agent (harness is then "shell" and args are ignored). */
|
|
26955
|
+
shell: ShellChoiceSchema.optional(),
|
|
26889
26956
|
createdAt: TimeSchema
|
|
26890
26957
|
}),
|
|
26958
|
+
/** End a running session (an agent or a shell) and everything it started. */
|
|
26959
|
+
external_exports.strictObject({ kind: external_exports.literal("close"), id: IdSchema, session: IdSchema, createdAt: TimeSchema }),
|
|
26891
26960
|
/** Provision a registered project without starting an agent (sandbox start / exec / db from its folder). */
|
|
26892
26961
|
external_exports.strictObject({ kind: external_exports.literal("project"), id: IdSchema, project: ProjectSpecSchema, createdAt: TimeSchema }),
|
|
26893
26962
|
external_exports.strictObject({
|
|
@@ -26953,6 +27022,8 @@ var SessionStatusSchema = external_exports.object({
|
|
|
26953
27022
|
id: IdSchema,
|
|
26954
27023
|
launchId: Text(64),
|
|
26955
27024
|
harness: Text(64),
|
|
27025
|
+
/** A plain shell (harness is then the shell: pwsh, powershell or cmd); absent: an agent. */
|
|
27026
|
+
kind: external_exports.enum(["agent", "shell"]).optional(),
|
|
26956
27027
|
cwd: Text(),
|
|
26957
27028
|
/** The project it runs in: its hub name, or the launch folder's for the first project. */
|
|
26958
27029
|
project: Text(64).optional(),
|
package/dist/runtime/view.js
CHANGED
|
@@ -5538,6 +5538,8 @@ var FrameType = {
|
|
|
5538
5538
|
Resize: 3,
|
|
5539
5539
|
Restart: 4,
|
|
5540
5540
|
Ping: 5,
|
|
5541
|
+
/** Ends the program and everything it started, and with it the session. */
|
|
5542
|
+
Close: 6,
|
|
5541
5543
|
Output: 16,
|
|
5542
5544
|
Exit: 17,
|
|
5543
5545
|
Error: 18,
|
|
@@ -27088,12 +27090,39 @@ var ResumeSchema = external_exports.strictObject({
|
|
|
27088
27090
|
});
|
|
27089
27091
|
|
|
27090
27092
|
// src/core/schema/web.ts
|
|
27093
|
+
var REMOTE_ROLES = ["view", "control"];
|
|
27094
|
+
var WindowsUserNameSchema = external_exports.string().trim().min(1).max(120).regex(
|
|
27095
|
+
/^(?:[A-Za-z0-9][A-Za-z0-9._ -]{0,62}\\)?[^\\/"[\]:;|=,+*?<>@]{1,64}$/,
|
|
27096
|
+
"a Windows user name (name or DOMAIN\\name)"
|
|
27097
|
+
);
|
|
27098
|
+
var RemoteUserSchema = external_exports.strictObject({
|
|
27099
|
+
name: WindowsUserNameSchema.describe("The Windows user (name, or DOMAIN\\name)"),
|
|
27100
|
+
role: external_exports.enum(REMOTE_ROLES).describe("view: sees everything, terminals read-only. control: everything but remote access, logins and tokens.")
|
|
27101
|
+
});
|
|
27102
|
+
var WebLanSchema = external_exports.strictObject({
|
|
27103
|
+
enabled: external_exports.boolean().describe(
|
|
27104
|
+
"(Beta) Devices on your network (a phone): an HTTPS address on this PC for devices you pair with a one-time code (sandbox web pair). Needs a firewall rule (sandbox web lan on asks Windows once). Off: every paired device is signed out."
|
|
27105
|
+
),
|
|
27106
|
+
port: external_exports.number().int().min(1024).max(65535).describe("HTTPS port for paired devices on your network."),
|
|
27107
|
+
address: external_exports.string().trim().max(15).regex(/^$|^\d{1,3}(\.\d{1,3}){3}$/, "an IPv4 address or empty").describe("This PC's private address to listen on. Empty: the one of its network adapter (never a public one).")
|
|
27108
|
+
});
|
|
27109
|
+
var WebRemoteSchema = external_exports.strictObject({
|
|
27110
|
+
windowsSignIn: external_exports.boolean().describe(
|
|
27111
|
+
"(Beta) Other Windows users of this PC named in web.remote.users sign in to your web UI (and sandbox remote) with their own Windows account: no password, no link."
|
|
27112
|
+
),
|
|
27113
|
+
users: external_exports.array(RemoteUserSchema).max(32).describe("Who else may sign in with Windows, and their role (view or control)."),
|
|
27114
|
+
rememberDays: external_exports.number().int().min(1).max(90).describe(
|
|
27115
|
+
"How many days a paired device or a Windows sign-in stays signed in (from when it signed in, not renewed)."
|
|
27116
|
+
),
|
|
27117
|
+
lan: WebLanSchema
|
|
27118
|
+
});
|
|
27091
27119
|
var WebSchema = external_exports.strictObject({
|
|
27092
27120
|
enabled: external_exports.boolean().describe(
|
|
27093
27121
|
"(Beta) The web UI: your sandbox sessions in a browser on this PC (http://127.0.0.1:<port>, this PC only). On: every sandbox start also starts it in the background (it keeps running until sandbox web stop or a restart of the PC). Off: only sandbox web start runs it. Open it signed in with sandbox web open."
|
|
27094
27122
|
),
|
|
27095
27123
|
port: external_exports.number().int().min(1024).max(65535).describe("Port of the web UI on 127.0.0.1 (this PC only). Pick another when it is taken."),
|
|
27096
|
-
openOnLaunch: external_exports.boolean().describe("(Beta) With the web UI on: open it in your browser, already signed in, every time a sandbox starts.")
|
|
27124
|
+
openOnLaunch: external_exports.boolean().describe("(Beta) With the web UI on: open it in your browser, already signed in, every time a sandbox starts."),
|
|
27125
|
+
remote: WebRemoteSchema.describe("(Beta) Access for other Windows users of this PC and for paired devices.")
|
|
27097
27126
|
});
|
|
27098
27127
|
|
|
27099
27128
|
// src/core/schema/vpn.ts
|
|
@@ -27123,6 +27152,36 @@ var GithubSchema = external_exports.strictObject({
|
|
|
27123
27152
|
});
|
|
27124
27153
|
|
|
27125
27154
|
// src/core/schema/config.ts
|
|
27155
|
+
var RESERVED_ENV_PREFIX = /^(AGENT_|GIT_|GH_|GITHUB_|NODE_|NPM_CONFIG_(USERCONFIG|GLOBALCONFIG|PREFIX|SCRIPT_SHELL)|SSL_|CLAUDE|ANTHROPIC_|OPENAI_|CODEX_|GEMINI_|GOOGLE_|COPILOT_|AWS_|AZURE_)/i;
|
|
27156
|
+
var RESERVED_ENV_SUFFIX = /(_PROXY|_BASE_URL|_ENDPOINT|_CA_BUNDLE|_CA_CERTS)$/i;
|
|
27157
|
+
var RESERVED_ENV_NAMES = new Set(
|
|
27158
|
+
[
|
|
27159
|
+
"PATH",
|
|
27160
|
+
"PATHEXT",
|
|
27161
|
+
"BASH_ENV",
|
|
27162
|
+
"ENV",
|
|
27163
|
+
"COMSPEC",
|
|
27164
|
+
"PSMODULEPATH",
|
|
27165
|
+
"PYTHONPATH",
|
|
27166
|
+
"PYTHONSTARTUP",
|
|
27167
|
+
"PERL5OPT",
|
|
27168
|
+
"RUBYOPT",
|
|
27169
|
+
"JAVA_TOOL_OPTIONS",
|
|
27170
|
+
"LD_PRELOAD",
|
|
27171
|
+
"SYSTEMROOT",
|
|
27172
|
+
"WINDIR",
|
|
27173
|
+
"HOME",
|
|
27174
|
+
"USERPROFILE",
|
|
27175
|
+
"APPDATA",
|
|
27176
|
+
"LOCALAPPDATA",
|
|
27177
|
+
"PROGRAMDATA",
|
|
27178
|
+
"TEMP",
|
|
27179
|
+
"TMP"
|
|
27180
|
+
].map((n) => n.toUpperCase())
|
|
27181
|
+
);
|
|
27182
|
+
function reservedEnvName(name) {
|
|
27183
|
+
return RESERVED_ENV_PREFIX.test(name) || RESERVED_ENV_SUFFIX.test(name) || RESERVED_ENV_NAMES.has(name.toUpperCase());
|
|
27184
|
+
}
|
|
27126
27185
|
var ConfigSchema = external_exports.strictObject({
|
|
27127
27186
|
$schema: SchemaRefSchema.optional(),
|
|
27128
27187
|
defaultHarness: NameSchema.describe("Agent started by `sandbox launch` (sandbox claude always runs claude)."),
|
|
@@ -27189,10 +27248,14 @@ var LocalOverridesSchema = external_exports.strictObject({
|
|
|
27189
27248
|
scripts: StringsSchema.describe(
|
|
27190
27249
|
"Scripts (.ps1 / .mjs / .cmd, relative to this file) run inside the sandbox before the agent."
|
|
27191
27250
|
),
|
|
27192
|
-
// This PC's resources are yours to share: CPU
|
|
27251
|
+
// This PC's resources are yours to share: CPU, paging file (this PC's disk) and the RAM kept for this PC are not a
|
|
27252
|
+
// project's to set.
|
|
27193
27253
|
sandbox: SandboxSchema.omit({
|
|
27194
27254
|
cpuLimit: true,
|
|
27195
27255
|
pageFileMb: true,
|
|
27256
|
+
reserveHostMb: true,
|
|
27257
|
+
maxMemoryPercent: true,
|
|
27258
|
+
maxMemoryMb: true,
|
|
27196
27259
|
engine: true,
|
|
27197
27260
|
agentMemoryHeadroomMb: true,
|
|
27198
27261
|
pressureResponse: true
|
|
@@ -27206,7 +27269,10 @@ var LocalOverridesSchema = external_exports.strictObject({
|
|
|
27206
27269
|
// Every toolchain setting, each one optional (a project usually only switches languages on or pins a version).
|
|
27207
27270
|
toolchains: external_exports.strictObject({
|
|
27208
27271
|
...import_lodash.default.mapValues(import_lodash.default.omit(ToolchainsSchema.shape, "environment"), (shape) => shape.partial()),
|
|
27209
|
-
environment: ToolchainsSchema.shape.environment
|
|
27272
|
+
environment: ToolchainsSchema.shape.environment.refine(
|
|
27273
|
+
(env) => !Object.keys(env).some(reservedEnvName),
|
|
27274
|
+
"names the sandbox sets or that carry logins (AGENT_*, GIT_*, PATH, BASH_ENV, NODE_*, proxies, ANTHROPIC_*, CLAUDE_* ...) cannot be set by a project"
|
|
27275
|
+
)
|
|
27210
27276
|
}).partial(),
|
|
27211
27277
|
databases: external_exports.strictObject(import_lodash.default.mapValues(DatabasesSchema.shape, (shape) => shape.partial())).partial(),
|
|
27212
27278
|
attach: AttachSchema.partial(),
|
|
@@ -27276,6 +27342,7 @@ var ProjectSpecSchema = external_exports.strictObject({
|
|
|
27276
27342
|
/** Its hooks were staged in C:\AgentLaunch\projects\<name>\hooks. */
|
|
27277
27343
|
hooks: external_exports.boolean()
|
|
27278
27344
|
});
|
|
27345
|
+
var ShellChoiceSchema = external_exports.enum(["auto", "pwsh", "powershell", "cmd"]);
|
|
27279
27346
|
var GuestRequestSchema = external_exports.discriminatedUnion("kind", [
|
|
27280
27347
|
external_exports.strictObject({
|
|
27281
27348
|
kind: external_exports.literal("session"),
|
|
@@ -27285,8 +27352,12 @@ var GuestRequestSchema = external_exports.discriminatedUnion("kind", [
|
|
|
27285
27352
|
cwd: Text(),
|
|
27286
27353
|
project: ProjectSpecSchema.optional(),
|
|
27287
27354
|
harnessSpec: HarnessSchema.optional(),
|
|
27355
|
+
/** A plain shell instead of an agent (harness is then "shell" and args are ignored). */
|
|
27356
|
+
shell: ShellChoiceSchema.optional(),
|
|
27288
27357
|
createdAt: TimeSchema
|
|
27289
27358
|
}),
|
|
27359
|
+
/** End a running session (an agent or a shell) and everything it started. */
|
|
27360
|
+
external_exports.strictObject({ kind: external_exports.literal("close"), id: IdSchema, session: IdSchema, createdAt: TimeSchema }),
|
|
27290
27361
|
/** Provision a registered project without starting an agent (sandbox start / exec / db from its folder). */
|
|
27291
27362
|
external_exports.strictObject({ kind: external_exports.literal("project"), id: IdSchema, project: ProjectSpecSchema, createdAt: TimeSchema }),
|
|
27292
27363
|
external_exports.strictObject({
|
|
@@ -27352,6 +27423,8 @@ var SessionStatusSchema = external_exports.object({
|
|
|
27352
27423
|
id: IdSchema,
|
|
27353
27424
|
launchId: Text(64),
|
|
27354
27425
|
harness: Text(64),
|
|
27426
|
+
/** A plain shell (harness is then the shell: pwsh, powershell or cmd); absent: an agent. */
|
|
27427
|
+
kind: external_exports.enum(["agent", "shell"]).optional(),
|
|
27355
27428
|
cwd: Text(),
|
|
27356
27429
|
/** The project it runs in: its hub name, or the launch folder's for the first project. */
|
|
27357
27430
|
project: Text(64).optional(),
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import fs from 'node:fs';
|
|
6
6
|
import path from 'node:path';
|
|
7
7
|
import { z } from 'zod';
|
|
8
|
-
import { agentsPage, agentsView } from '../app/features/agents/index.js';
|
|
8
|
+
import { agentNameProblem, agentsPage, agentsView } from '../app/features/agents/index.js';
|
|
9
9
|
import { DEFINITIONS, NAV, navSections, navViews, QUERIES, VIEWS, viewById } from '../app/features/defs.js';
|
|
10
10
|
import { accountsPage, agentPage, listAccounts } from '../app/features/accounts/index.js';
|
|
11
11
|
import { addProject, projectRoots, projectsPage } from '../app/features/projects/index.js';
|
|
@@ -15,13 +15,13 @@ import { insideSandbox, isSandboxRunning } from '../host/sandbox/index.js';
|
|
|
15
15
|
import { CRASH, listLogs, logsPage, tailLog } from '../app/features/logs/index.js';
|
|
16
16
|
import { DEFAULTS } from '../core/config/index.js';
|
|
17
17
|
import { sandboxPage } from '../app/features/sandbox/index.js';
|
|
18
|
-
import { sessionsPage } from '../app/features/sessions/index.js';
|
|
18
|
+
import { closeSession, sessionsPage, startShell, } from '../app/features/sessions/index.js';
|
|
19
19
|
import { APP_GROUPS, appsPage, listApps } from '../app/features/apps/index.js';
|
|
20
20
|
import { keepShownSecrets, settingsPage } from '../app/features/settings/index.js';
|
|
21
21
|
import { checkEngine, checkText, enginePage } from '../app/features/engine/index.js';
|
|
22
22
|
import { runUpdate, updatesPage } from '../app/features/updates/index.js';
|
|
23
23
|
import { MenuOperations } from '../cli/menu/io-menu.js';
|
|
24
|
-
import { ago, bytesText, Cancelled, ClientMessageSchema, defineOperation, exemption, fakeIO, fieldOf, fieldProblem, FieldSchema, fieldsOf, fromWireError, humanize, implementer, invoke, pageActions, pageHasContent, ServerMessageSchema, SURFACE_NAMES, toWireError, valueText, } from '../app/kernel/index.js';
|
|
24
|
+
import { accessNeeded, ago, allows, bytesText, Cancelled, ClientMessageSchema, defineOperation, exemption, fakeIO, fieldOf, fieldProblem, FieldSchema, fieldsOf, fromWireError, humanize, implementer, invoke, pageActions, pageHasContent, ServerMessageSchema, SURFACE_NAMES, toWireError, valueText, OWNER, } from '../app/kernel/index.js';
|
|
25
25
|
import { createRpcHost, IMPLEMENTATIONS, readQuery, runDefinition } from '../app/registry.js';
|
|
26
26
|
import { webPage } from '../app/features/web/index.js';
|
|
27
27
|
import { parseCli } from '../cli/args.js';
|
|
@@ -326,11 +326,43 @@ export const appSuite = {
|
|
|
326
326
|
const menu = menuView(sessions);
|
|
327
327
|
const chosen = menu.resolve({ kind: 'pick', value: 'new' });
|
|
328
328
|
t.check(menu.items.length === 2 &&
|
|
329
|
-
menu.actions.map((a) => a.key).join() === 'n,r,o,l' &&
|
|
329
|
+
menu.actions.map((a) => a.key).join() === 'n,s,r,o,x,l' &&
|
|
330
330
|
menu.enterLabel(undefined, 'new') === 'attach' &&
|
|
331
331
|
chosen?.kind === 'action' &&
|
|
332
332
|
chosen.action.kind === 'link' &&
|
|
333
|
-
menu.resolve({ kind: 'action', key: 'l', value: undefined })?.kind === 'refresh', 'sessions in the menu: rows, the n/r/o/l keys, Enter attaches');
|
|
333
|
+
menu.resolve({ kind: 'action', key: 'l', value: undefined })?.kind === 'refresh', 'sessions in the menu: rows, the n/s/r/o/x/l keys, Enter attaches');
|
|
334
|
+
t.section('plain shell sessions');
|
|
335
|
+
const shellRow = { ...running('sh1', true), harness: 'pwsh', kind: 'shell' };
|
|
336
|
+
const shellPage = sessionsPage({ ...list, sessions: [shellRow] });
|
|
337
|
+
const shellItem = shellPage.sections[0].items?.[0];
|
|
338
|
+
const closeAction = shellItem?.actions?.[0];
|
|
339
|
+
t.check(shellItem?.label === 'pwsh (shell) api' &&
|
|
340
|
+
closeAction?.kind === 'operation' &&
|
|
341
|
+
closeAction.op === 'sessions.close' &&
|
|
342
|
+
closeAction.key === 'x' &&
|
|
343
|
+
closeAction.danger === true &&
|
|
344
|
+
JSON.stringify(closeAction.input) === '{"session":"sh1"}', 'a shell is listed by its name, marked as a shell, and can be closed (x)', JSON.stringify(shellItem));
|
|
345
|
+
const newShell = shellPage.actions?.find((a) => a.kind === 'operation' && a.op === 'sessions.shell');
|
|
346
|
+
t.check(newShell?.kind === 'operation' && newShell.key === 's' && JSON.stringify(newShell.input) === '{"shell":"auto"}', 'the sessions page offers New shell (s) next to New session');
|
|
347
|
+
t.check(!sessionsPage({ ...list, sandboxRunning: false, sessions: [] }).actions?.some((a) => a.kind === 'operation' && a.op === 'sessions.shell'), 'no New shell without a running sandbox');
|
|
348
|
+
t.equal(startShell.opens?.({ session: 'sh1', shell: 'pwsh', message: '' }), { kind: 'terminal', session: 'sh1' }, 'a new shell opens its terminal on every surface');
|
|
349
|
+
const controlCaller = { kind: 'windows', user: 'bob', role: 'control' };
|
|
350
|
+
const viewCaller = { kind: 'device', id: 'd', name: 'phone', role: 'view' };
|
|
351
|
+
t.check(accessNeeded(startShell, {}) === 'owner' &&
|
|
352
|
+
!allows(controlCaller, accessNeeded(startShell, {})) &&
|
|
353
|
+
allows(OWNER, accessNeeded(startShell, {})) &&
|
|
354
|
+
accessNeeded(closeSession, { session: 'x' }) === 'control' &&
|
|
355
|
+
allows(controlCaller, accessNeeded(closeSession, { session: 'x' })) &&
|
|
356
|
+
!allows(viewCaller, accessNeeded(closeSession, { session: 'x' })), 'only the owner opens a shell; control may close a session, view may not');
|
|
357
|
+
t.equal([
|
|
358
|
+
cliInput(definitionFor('shell', []), parseCli(['shell', 'api', '--shell', 'cmd'])),
|
|
359
|
+
cliInput(definitionFor('close', []), parseCli(['close', 'sh1'])),
|
|
360
|
+
], [{ project: 'api', shell: 'cmd' }, { session: 'sh1' }], 'sandbox shell [project] --shell and sandbox close <id> become the input');
|
|
361
|
+
t.check(agentNameProblem('pwsh', []) !== true && agentNameProblem('Shell', []) !== true, 'an agent cannot be named after a shell or the shell command');
|
|
362
|
+
if (insideSandbox())
|
|
363
|
+
await t.throws(() => runDefinition('sessions.shell', {}, {}, fakeIO([]).io), 'has nothing to control', 'sandbox shell refuses inside the sandbox');
|
|
364
|
+
else
|
|
365
|
+
t.skip('sandbox shell refuses inside the sandbox (this PC is not a sandbox)');
|
|
334
366
|
const menuStatus = menuView(statusPage, { category: 'status', prefix: 'status:' });
|
|
335
367
|
t.check(menuStatus.items.every((i) => i.category === 'status' && i.value.startsWith('status:')) &&
|
|
336
368
|
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');
|
|
@@ -963,6 +995,9 @@ export const appSuite = {
|
|
|
963
995
|
t.check(definitionFor('web', ['open'])?.id === 'web.open' &&
|
|
964
996
|
definitionFor('web', ['status'])?.id === 'web.status' &&
|
|
965
997
|
definitionFor('web', []) === undefined &&
|
|
966
|
-
commandSpec('web')?.args[0]?.choices?.join() ===
|
|
998
|
+
commandSpec('web')?.args[0]?.choices?.join() ===
|
|
999
|
+
'start,stop,status,open,allow-device,revoke,revoke-all,lan-on,lan-off,devices' &&
|
|
1000
|
+
definitionFor('web', ['allow-device'])?.id === 'access.allowDevice' &&
|
|
1001
|
+
definitionFor('web', ['revoke', '0123456789abcdef'])?.id === 'access.revoke', 'the web actions (and the remote access ones) are actions of the web command; sandbox web alone is not one');
|
|
967
1002
|
},
|
|
968
1003
|
};
|
|
@@ -35,6 +35,22 @@ export const channelSuite = {
|
|
|
35
35
|
t.check(validSession(goodSession) &&
|
|
36
36
|
!validSession({ ...goodSession, attach: { ...goodSession.attach, port: 70000 } }) &&
|
|
37
37
|
!validSession({ ...goodSession, phase: 'pwned' }), 'session status: a well-formed one passes; an impossible port or unknown phase is rejected');
|
|
38
|
+
const shellRequest = {
|
|
39
|
+
kind: 'session',
|
|
40
|
+
id: 'r1',
|
|
41
|
+
harness: 'shell',
|
|
42
|
+
args: [],
|
|
43
|
+
cwd: 'C:\\Workspace',
|
|
44
|
+
shell: 'pwsh',
|
|
45
|
+
createdAt: 'x',
|
|
46
|
+
};
|
|
47
|
+
t.check(validRequest(shellRequest) &&
|
|
48
|
+
validRequest({ ...shellRequest, shell: 'auto' }) &&
|
|
49
|
+
!validRequest({ ...shellRequest, shell: 'bash -c evil' }) &&
|
|
50
|
+
validRequest({ kind: 'close', id: 'c1', session: 's1', createdAt: 'x' }) &&
|
|
51
|
+
!validRequest({ kind: 'close', id: 'c1', session: '..\\s1', createdAt: 'x' }) &&
|
|
52
|
+
validSession({ ...goodSession, harness: 'pwsh', kind: 'shell' }) &&
|
|
53
|
+
!validSession({ ...goodSession, kind: 'daemon' }), 'shells: a session request names one of the known shells; close names a safe session id; a status says shell');
|
|
38
54
|
t.section('boot progress');
|
|
39
55
|
const goodStatus = {
|
|
40
56
|
launchId: 'l1',
|
|
@@ -4,6 +4,7 @@ import { spawnSync } from 'node:child_process';
|
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { pathToFileURL } from 'node:url';
|
|
6
6
|
import { isWin } from '../core/util/index.js';
|
|
7
|
+
import { commandLine, shellLaunch } from '../guest/sessions/cmdline.js';
|
|
7
8
|
import { runNode } from './support/fixtures.js';
|
|
8
9
|
// Odd quote counts flip cmd's quote state for every later argument: those come after them on purpose.
|
|
9
10
|
const TRICKY_ARGUMENTS = [
|
|
@@ -71,5 +72,20 @@ export const cmdlineSuite = {
|
|
|
71
72
|
}
|
|
72
73
|
else
|
|
73
74
|
t.skip('end-to-end cmd.exe quoting run (not Windows, or the temp path has spaces)');
|
|
75
|
+
t.section('plain shell sessions');
|
|
76
|
+
const paths = { pwsh: 'C:/T/pwsh/pwsh.exe', powershell: 'C:/W/powershell.exe', cmd: 'C:/W/cmd.exe' };
|
|
77
|
+
const noPwsh = { ...paths, pwsh: null };
|
|
78
|
+
t.equal([
|
|
79
|
+
shellLaunch('auto', paths),
|
|
80
|
+
shellLaunch('auto', noPwsh),
|
|
81
|
+
shellLaunch('powershell', paths),
|
|
82
|
+
shellLaunch('cmd', paths),
|
|
83
|
+
].map((l) => l.name + ' ' + commandLine(l.file, l.args)), [
|
|
84
|
+
'pwsh C:/T/pwsh/pwsh.exe -NoLogo',
|
|
85
|
+
'powershell C:/W/powershell.exe -NoLogo',
|
|
86
|
+
'powershell C:/W/powershell.exe -NoLogo',
|
|
87
|
+
'cmd C:/W/cmd.exe',
|
|
88
|
+
], 'a shell runs by its absolute path: auto is pwsh when present, else Windows PowerShell; cmd takes no arguments');
|
|
89
|
+
await t.throws(() => shellLaunch('pwsh', noPwsh), 'PowerShell 7 is not installed', 'asking for pwsh where it is missing says so');
|
|
74
90
|
},
|
|
75
91
|
};
|
|
@@ -48,6 +48,14 @@ export const gitAccessSuite = {
|
|
|
48
48
|
'--git-dir=x status',
|
|
49
49
|
'clean -fd',
|
|
50
50
|
'apply p.diff',
|
|
51
|
+
// reading commands with options that run a program or write a file
|
|
52
|
+
'grep -Ocalc.exe x',
|
|
53
|
+
'grep --open-files-in-pager=calc.exe x',
|
|
54
|
+
'ls-remote --upload-pack=calc.exe origin',
|
|
55
|
+
'ls-remote -ucalc.exe origin',
|
|
56
|
+
'diff --output=C:/x.txt',
|
|
57
|
+
'log --output C:/x.txt',
|
|
58
|
+
'diff --ext-diff',
|
|
51
59
|
];
|
|
52
60
|
t.equal(writing.filter((command) => allowed(command.split(' '))), [], 'readonly: anything that writes (or overrides config) is refused');
|
|
53
61
|
t.section('none and full');
|
|
@@ -12,7 +12,7 @@ import { writeJsonAtomic } from '../shared/io/index.js';
|
|
|
12
12
|
export const hostileInputSuite = {
|
|
13
13
|
name: 'hostile-input',
|
|
14
14
|
title: 'file names, deletes, printed text, mappings and state files from an untrusted sandbox',
|
|
15
|
-
run: async (t) => {
|
|
15
|
+
run: async (t, f) => {
|
|
16
16
|
t.section('file names');
|
|
17
17
|
const unsafe = ['CON.jsonl', 'nul', 'aux.json', 'COM1.txt', 'a:b', 'x.jsonl.', 'x ', '..', 'a\\b', 'LPT3'];
|
|
18
18
|
t.equal(unsafe.filter(isSafeFileName), [], 'device names, streams and trailing dots are not valid names to copy into this PC');
|
|
@@ -52,5 +52,16 @@ export const hostileInputSuite = {
|
|
|
52
52
|
t.equal(fs.readdirSync(path.dirname(atomicFile)).length, 1, '... with no temp files left behind');
|
|
53
53
|
await t.throws(() => assertCmdSafePath('C:\\a\\%TEMP%\\agent.wsb'), 'cannot be started safely', 'a launch path with %VAR% (which cmd.exe would expand) is refused');
|
|
54
54
|
await t.throws(() => assertCmdSafePath('C:\\a"&calc&"\\agent.wsb'), 'cannot be started safely', 'a launch path with quotes and & is refused');
|
|
55
|
+
t.section('repo files read on this PC (security review 2026-10)');
|
|
56
|
+
const repoDir = t.dir('hostile-versions');
|
|
57
|
+
fs.writeFileSync(path.join(repoDir, '.nvmrc'), '\u001b]52;c;ZXZpbA==\u0007\u001b[2J20\n');
|
|
58
|
+
fs.writeFileSync(path.join(repoDir, '.python-version'), '3.12.1\n');
|
|
59
|
+
const versionsRun = await f.run({ cwd: repoDir });
|
|
60
|
+
t.check(!versionsRun.versions.node.spec.includes('\u001b') &&
|
|
61
|
+
versionsRun.versions.node.source !== '.nvmrc' &&
|
|
62
|
+
versionsRun.versions.python.spec === '3.12.1', 'a version file holding escape codes is ignored (it would reach the launch table); a plain one is used', JSON.stringify(versionsRun.versions));
|
|
63
|
+
fs.writeFileSync(path.join(repoDir, '.sandbox-config.json'), '{"readOnly": true}' + ' '.repeat(1100 * 1024));
|
|
64
|
+
await t.throws(() => f.run({ cwd: repoDir, trustConfig: true }), 'over 1 MB', 'a .sandbox-config.json over 1 MB is refused before it is parsed');
|
|
65
|
+
fs.rmSync(path.join(repoDir, '.sandbox-config.json'));
|
|
55
66
|
},
|
|
56
67
|
};
|
package/dist/selftest/index.js
CHANGED
|
@@ -57,6 +57,7 @@ import { syncSuite } from './sync.test.js';
|
|
|
57
57
|
import { testGuardSuite } from './test-guard.test.js';
|
|
58
58
|
import { toolchainsSuite } from './toolchains.test.js';
|
|
59
59
|
import { webSuite } from './web.test.js';
|
|
60
|
+
import { remoteSuite } from './remote.test.js';
|
|
60
61
|
import { workspaceSuite } from './workspace.test.js';
|
|
61
62
|
import { workspaceViewSuite } from './workspace-view.test.js';
|
|
62
63
|
import { liveNetworkSuite } from './live-network.test.js';
|
|
@@ -107,6 +108,7 @@ export const SUITES = [
|
|
|
107
108
|
secretsSuite,
|
|
108
109
|
hostileInputSuite,
|
|
109
110
|
webSuite,
|
|
111
|
+
remoteSuite,
|
|
110
112
|
testGuardSuite,
|
|
111
113
|
// pure helpers and the command line
|
|
112
114
|
layoutSuite,
|