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.
Files changed (206) hide show
  1. package/README.md +1 -0
  2. package/config.example.json +11 -1
  3. package/dist/app/features/access/access.def.d.ts +190 -0
  4. package/dist/app/features/access/access.def.js +273 -0
  5. package/dist/app/features/access/access.run.d.ts +79 -0
  6. package/dist/app/features/access/access.run.js +129 -0
  7. package/dist/app/features/access/index.d.ts +2 -0
  8. package/dist/app/features/access/index.js +3 -0
  9. package/dist/app/features/accounts/accounts.def.d.ts +1 -0
  10. package/dist/app/features/accounts/accounts.def.js +12 -3
  11. package/dist/app/features/accounts/accounts.run.d.ts +1 -0
  12. package/dist/app/features/accounts/accounts.run.js +20 -11
  13. package/dist/app/features/agents/agent-entry.js +5 -0
  14. package/dist/app/features/agents/agents.def.js +1 -0
  15. package/dist/app/features/apps/apps.def.js +1 -0
  16. package/dist/app/features/audit/audit.def.d.ts +11 -2
  17. package/dist/app/features/audit/audit.def.js +7 -4
  18. package/dist/app/features/audit/audit.run.d.ts +3 -0
  19. package/dist/app/features/defs.d.ts +156 -3
  20. package/dist/app/features/defs.js +12 -1
  21. package/dist/app/features/files/files.def.js +1 -0
  22. package/dist/app/features/health/health.def.js +1 -0
  23. package/dist/app/features/sandbox/sandbox.def.js +3 -0
  24. package/dist/app/features/sessions/sessions.def.d.ts +36 -0
  25. package/dist/app/features/sessions/sessions.def.js +77 -9
  26. package/dist/app/features/sessions/sessions.run.d.ts +25 -2
  27. package/dist/app/features/sessions/sessions.run.js +71 -3
  28. package/dist/app/features/settings/settings-file.d.ts +7 -0
  29. package/dist/app/features/settings/settings-file.js +19 -2
  30. package/dist/app/features/settings/settings-rows.js +9 -1
  31. package/dist/app/features/settings/settings.def.d.ts +8 -0
  32. package/dist/app/features/settings/settings.def.js +17 -0
  33. package/dist/app/features/web/web.def.d.ts +9 -0
  34. package/dist/app/features/web/web.def.js +26 -1
  35. package/dist/app/features/web/web.run.d.ts +3 -0
  36. package/dist/app/features/web/web.run.js +11 -3
  37. package/dist/app/kernel/access.d.ts +33 -0
  38. package/dist/app/kernel/access.js +44 -0
  39. package/dist/app/kernel/define.d.ts +14 -1
  40. package/dist/app/kernel/index.d.ts +1 -0
  41. package/dist/app/kernel/index.js +1 -0
  42. package/dist/app/kernel/rpc.d.ts +5 -2
  43. package/dist/app/registry.d.ts +4 -1
  44. package/dist/app/registry.js +24 -4
  45. package/dist/bin/agent-sandbox.js +7 -1
  46. package/dist/bin/chunk-IL7BXJF2.js +7 -0
  47. package/dist/bin/chunk-K4SU3MHJ.js +10432 -0
  48. package/dist/bin/chunk-VENWSGX2.js +78437 -0
  49. package/dist/bin/claude-host.js +10 -4
  50. package/dist/bin/claude-sbx.js +8 -2
  51. package/dist/bin/codex-sbx.js +8 -2
  52. package/dist/bin/gemini-sbx.js +8 -2
  53. package/dist/bin/sandbox.js +7 -2
  54. package/dist/bin/sbx-hide.js +8 -2
  55. package/dist/bin/sbx-show.js +8 -2
  56. package/dist/cli/commands/launch/prepare.js +24 -7
  57. package/dist/cli/commands/launch/running.js +4 -0
  58. package/dist/cli/commands/registry.js +2 -0
  59. package/dist/cli/commands/remote.d.ts +2 -0
  60. package/dist/cli/commands/remote.js +49 -0
  61. package/dist/cli/commands/sessions/reopen.js +3 -0
  62. package/dist/cli/commands/storage/offer.js +1 -1
  63. package/dist/cli/commands/web.js +66 -0
  64. package/dist/cli/manifest.d.ts +63 -4
  65. package/dist/cli/manifest.js +33 -4
  66. package/dist/cli/menu/io-menu.d.ts +5 -3
  67. package/dist/cli/menu/io-menu.js +12 -5
  68. package/dist/cli/menu/live.d.ts +29 -0
  69. package/dist/cli/menu/live.js +92 -21
  70. package/dist/cli/menu/menu.d.ts +2 -1
  71. package/dist/cli/menu/menu.js +16 -2
  72. package/dist/cli/menu/pages/list-page.d.ts +5 -0
  73. package/dist/cli/menu/pages/list-page.js +19 -0
  74. package/dist/cli/menu/remote-menu.d.ts +19 -0
  75. package/dist/cli/menu/remote-menu.js +108 -0
  76. package/dist/cli/menu/shell.d.ts +2 -0
  77. package/dist/cli/menu/shell.js +1 -1
  78. package/dist/cli/menu/views.js +2 -0
  79. package/dist/cli/operations/handler.js +15 -1
  80. package/dist/cli/services/accounts-service.d.ts +7 -0
  81. package/dist/cli/services/accounts-service.js +20 -0
  82. package/dist/cli/services/remote-client.d.ts +16 -0
  83. package/dist/cli/services/remote-client.js +141 -0
  84. package/dist/cli/services/sessions-service.d.ts +5 -0
  85. package/dist/cli/services/sessions-service.js +17 -1
  86. package/dist/cli/tui/app.d.ts +4 -0
  87. package/dist/cli/tui/app.js +5 -0
  88. package/dist/cli/tui/terminal.d.ts +6 -0
  89. package/dist/cli/tui/terminal.js +8 -1
  90. package/dist/cli/validate.js +8 -4
  91. package/dist/core/config/defaults.js +11 -1
  92. package/dist/core/local-config/file.js +16 -2
  93. package/dist/core/local-config/overrides.js +7 -0
  94. package/dist/core/package-info.js +1 -1
  95. package/dist/core/run/versions.js +9 -8
  96. package/dist/core/schema/config.d.ts +22 -3
  97. package/dist/core/schema/config.js +39 -2
  98. package/dist/core/schema/web.d.ts +51 -0
  99. package/dist/core/schema/web.js +46 -1
  100. package/dist/core/util/fs.d.ts +5 -0
  101. package/dist/core/util/fs.js +23 -0
  102. package/dist/core/util/index.d.ts +1 -1
  103. package/dist/core/util/index.js +1 -1
  104. package/dist/guest/daemon/daemon.js +2 -0
  105. package/dist/guest/daemon/idle-shutdown.js +2 -1
  106. package/dist/guest/daemon/sessions.d.ts +16 -2
  107. package/dist/guest/daemon/sessions.js +126 -20
  108. package/dist/guest/git/decision.js +7 -0
  109. package/dist/guest/sessions/cmdline.d.ts +14 -0
  110. package/dist/guest/sessions/cmdline.js +10 -0
  111. package/dist/guest/toolchains/setup.js +3 -0
  112. package/dist/host/audit/log.d.ts +3 -2
  113. package/dist/host/audit/log.js +3 -3
  114. package/dist/host/channel/channel.d.ts +5 -1
  115. package/dist/host/channel/channel.js +12 -0
  116. package/dist/host/guard/agent-files.js +7 -1
  117. package/dist/host/guard/commands.js +13 -1
  118. package/dist/host/guard/git-config.js +27 -7
  119. package/dist/host/guard/git-dirs.js +78 -3
  120. package/dist/host/guard/mark-of-the-web.js +3 -1
  121. package/dist/host/guard/nested-repos.js +7 -2
  122. package/dist/host/guard/store.js +1 -0
  123. package/dist/host/guard/types.d.ts +2 -0
  124. package/dist/host/launch/mappings.js +11 -1
  125. package/dist/host/launch/project-scan.js +2 -21
  126. package/dist/host/launch/stage.js +4 -0
  127. package/dist/host/life/session-ledger.js +2 -1
  128. package/dist/host/remote/access-store.d.ts +156 -0
  129. package/dist/host/remote/access-store.js +308 -0
  130. package/dist/host/remote/cert.d.ts +20 -0
  131. package/dist/host/remote/cert.js +103 -0
  132. package/dist/host/remote/door-client.d.ts +13 -0
  133. package/dist/host/remote/door-client.js +54 -0
  134. package/dist/host/remote/firewall.d.ts +44 -0
  135. package/dist/host/remote/firewall.js +118 -0
  136. package/dist/host/remote/gate.d.ts +125 -0
  137. package/dist/host/remote/gate.js +391 -0
  138. package/dist/host/remote/index.d.ts +8 -0
  139. package/dist/host/remote/index.js +10 -0
  140. package/dist/host/remote/lan.d.ts +24 -0
  141. package/dist/host/remote/lan.js +88 -0
  142. package/dist/host/remote/tls-store.d.ts +22 -0
  143. package/dist/host/remote/tls-store.js +63 -0
  144. package/dist/host/remote/windows-helper.d.ts +67 -0
  145. package/dist/host/remote/windows-helper.js +129 -0
  146. package/dist/host/sandbox/platform.d.ts +2 -0
  147. package/dist/host/sandbox/platform.js +19 -4
  148. package/dist/host/storage/contained/state.d.ts +3 -2
  149. package/dist/host/storage/contained/state.js +5 -2
  150. package/dist/host/web/bridge.d.ts +5 -1
  151. package/dist/host/web/bridge.js +6 -2
  152. package/dist/host/web/browser.js +1 -1
  153. package/dist/host/web/control.d.ts +4 -1
  154. package/dist/host/web/control.js +28 -4
  155. package/dist/host/web/rpc.d.ts +16 -2
  156. package/dist/host/web/rpc.js +37 -4
  157. package/dist/host/web/runner.d.ts +4 -0
  158. package/dist/host/web/runner.js +12 -0
  159. package/dist/host/web/server.d.ts +18 -2
  160. package/dist/host/web/server.js +97 -24
  161. package/dist/host/web/state.d.ts +5 -0
  162. package/dist/host/web/state.js +8 -0
  163. package/dist/runtime/agent.js +74 -3
  164. package/dist/runtime/daemon.js +205 -24
  165. package/dist/runtime/db.js +74 -3
  166. package/dist/runtime/gitguard.js +6 -0
  167. package/dist/runtime/main.js +77 -3
  168. package/dist/runtime/netproxy.js +74 -3
  169. package/dist/runtime/view.js +76 -3
  170. package/dist/selftest/app.test.js +41 -6
  171. package/dist/selftest/channel.test.js +16 -0
  172. package/dist/selftest/cmdline.test.js +16 -0
  173. package/dist/selftest/git-access.test.js +8 -0
  174. package/dist/selftest/hostile-input.test.js +12 -1
  175. package/dist/selftest/index.js +2 -0
  176. package/dist/selftest/layout.test.js +10 -1
  177. package/dist/selftest/live-daemon.test.js +25 -0
  178. package/dist/selftest/local-config.test.js +30 -0
  179. package/dist/selftest/menu-perf.test.js +198 -12
  180. package/dist/selftest/menu.test.js +4 -0
  181. package/dist/selftest/remote.test.d.ts +2 -0
  182. package/dist/selftest/remote.test.js +536 -0
  183. package/dist/selftest/repo-guard.test.js +56 -2
  184. package/dist/selftest/resume.test.js +2 -1
  185. package/dist/selftest/sharing.test.js +9 -2
  186. package/dist/selftest/support/fixtures.d.ts +13 -0
  187. package/dist/selftest/support/web-preview.js +63 -0
  188. package/dist/selftest/web.test.js +2 -0
  189. package/dist/shared/channel/schema.d.ts +23 -0
  190. package/dist/shared/channel/schema.js +8 -0
  191. package/dist/shared/pty/protocol.d.ts +2 -0
  192. package/dist/shared/pty/protocol.js +2 -0
  193. package/dist/web/index.html +1 -1
  194. package/dist/web/main-NBQ3LF4E.js +221 -0
  195. package/native/csharp/HcsHost/AssemblyInfo.cs +3 -3
  196. package/native/csharp/HostTools/AssemblyInfo.cs +3 -3
  197. package/native/csharp/HostTools/Program.cs +17 -1
  198. package/native/csharp/HostTools/WebHelper.cs +499 -0
  199. package/native/scripts/get-network-profile.ps1 +19 -0
  200. package/native/scripts/set-folder-access.ps1 +11 -0
  201. package/native/scripts/set-web-firewall.ps1 +103 -0
  202. package/package.json +1 -1
  203. package/sandbox/csharp/PtyHost.cs +20 -0
  204. package/schemas/config.schema.json +65 -0
  205. package/schemas/sandbox-config.schema.json +0 -18
  206. package/dist/web/main-UD4QCXZR.js +0 -219
@@ -26743,12 +26743,39 @@ var ResumeSchema = external_exports.strictObject({
26743
26743
  });
26744
26744
 
26745
26745
  // src/core/schema/web.ts
26746
+ var REMOTE_ROLES = ["view", "control"];
26747
+ var WindowsUserNameSchema = external_exports.string().trim().min(1).max(120).regex(
26748
+ /^(?:[A-Za-z0-9][A-Za-z0-9._ -]{0,62}\\)?[^\\/"[\]:;|=,+*?<>@]{1,64}$/,
26749
+ "a Windows user name (name or DOMAIN\\name)"
26750
+ );
26751
+ var RemoteUserSchema = external_exports.strictObject({
26752
+ name: WindowsUserNameSchema.describe("The Windows user (name, or DOMAIN\\name)"),
26753
+ role: external_exports.enum(REMOTE_ROLES).describe("view: sees everything, terminals read-only. control: everything but remote access, logins and tokens.")
26754
+ });
26755
+ var WebLanSchema = external_exports.strictObject({
26756
+ enabled: external_exports.boolean().describe(
26757
+ "(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."
26758
+ ),
26759
+ port: external_exports.number().int().min(1024).max(65535).describe("HTTPS port for paired devices on your network."),
26760
+ 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).")
26761
+ });
26762
+ var WebRemoteSchema = external_exports.strictObject({
26763
+ windowsSignIn: external_exports.boolean().describe(
26764
+ "(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."
26765
+ ),
26766
+ users: external_exports.array(RemoteUserSchema).max(32).describe("Who else may sign in with Windows, and their role (view or control)."),
26767
+ rememberDays: external_exports.number().int().min(1).max(90).describe(
26768
+ "How many days a paired device or a Windows sign-in stays signed in (from when it signed in, not renewed)."
26769
+ ),
26770
+ lan: WebLanSchema
26771
+ });
26746
26772
  var WebSchema = external_exports.strictObject({
26747
26773
  enabled: external_exports.boolean().describe(
26748
26774
  "(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."
26749
26775
  ),
26750
26776
  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."),
26751
- openOnLaunch: external_exports.boolean().describe("(Beta) With the web UI on: open it in your browser, already signed in, every time a sandbox starts.")
26777
+ openOnLaunch: external_exports.boolean().describe("(Beta) With the web UI on: open it in your browser, already signed in, every time a sandbox starts."),
26778
+ remote: WebRemoteSchema.describe("(Beta) Access for other Windows users of this PC and for paired devices.")
26752
26779
  });
26753
26780
 
26754
26781
  // src/core/schema/vpn.ts
@@ -26778,6 +26805,36 @@ var GithubSchema = external_exports.strictObject({
26778
26805
  });
26779
26806
 
26780
26807
  // src/core/schema/config.ts
26808
+ 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;
26809
+ var RESERVED_ENV_SUFFIX = /(_PROXY|_BASE_URL|_ENDPOINT|_CA_BUNDLE|_CA_CERTS)$/i;
26810
+ var RESERVED_ENV_NAMES = new Set(
26811
+ [
26812
+ "PATH",
26813
+ "PATHEXT",
26814
+ "BASH_ENV",
26815
+ "ENV",
26816
+ "COMSPEC",
26817
+ "PSMODULEPATH",
26818
+ "PYTHONPATH",
26819
+ "PYTHONSTARTUP",
26820
+ "PERL5OPT",
26821
+ "RUBYOPT",
26822
+ "JAVA_TOOL_OPTIONS",
26823
+ "LD_PRELOAD",
26824
+ "SYSTEMROOT",
26825
+ "WINDIR",
26826
+ "HOME",
26827
+ "USERPROFILE",
26828
+ "APPDATA",
26829
+ "LOCALAPPDATA",
26830
+ "PROGRAMDATA",
26831
+ "TEMP",
26832
+ "TMP"
26833
+ ].map((n) => n.toUpperCase())
26834
+ );
26835
+ function reservedEnvName(name) {
26836
+ return RESERVED_ENV_PREFIX.test(name) || RESERVED_ENV_SUFFIX.test(name) || RESERVED_ENV_NAMES.has(name.toUpperCase());
26837
+ }
26781
26838
  var ConfigSchema = external_exports.strictObject({
26782
26839
  $schema: SchemaRefSchema.optional(),
26783
26840
  defaultHarness: NameSchema.describe("Agent started by `sandbox launch` (sandbox claude always runs claude)."),
@@ -26844,10 +26901,14 @@ var LocalOverridesSchema = external_exports.strictObject({
26844
26901
  scripts: StringsSchema.describe(
26845
26902
  "Scripts (.ps1 / .mjs / .cmd, relative to this file) run inside the sandbox before the agent."
26846
26903
  ),
26847
- // This PC's resources are yours to share: CPU and paging file (this PC's disk) are not a project's to set.
26904
+ // 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
26905
+ // project's to set.
26848
26906
  sandbox: SandboxSchema.omit({
26849
26907
  cpuLimit: true,
26850
26908
  pageFileMb: true,
26909
+ reserveHostMb: true,
26910
+ maxMemoryPercent: true,
26911
+ maxMemoryMb: true,
26851
26912
  engine: true,
26852
26913
  agentMemoryHeadroomMb: true,
26853
26914
  pressureResponse: true
@@ -26861,7 +26922,10 @@ var LocalOverridesSchema = external_exports.strictObject({
26861
26922
  // Every toolchain setting, each one optional (a project usually only switches languages on or pins a version).
26862
26923
  toolchains: external_exports.strictObject({
26863
26924
  ...import_lodash.default.mapValues(import_lodash.default.omit(ToolchainsSchema.shape, "environment"), (shape) => shape.partial()),
26864
- environment: ToolchainsSchema.shape.environment
26925
+ environment: ToolchainsSchema.shape.environment.refine(
26926
+ (env) => !Object.keys(env).some(reservedEnvName),
26927
+ "names the sandbox sets or that carry logins (AGENT_*, GIT_*, PATH, BASH_ENV, NODE_*, proxies, ANTHROPIC_*, CLAUDE_* ...) cannot be set by a project"
26928
+ )
26865
26929
  }).partial(),
26866
26930
  databases: external_exports.strictObject(import_lodash.default.mapValues(DatabasesSchema.shape, (shape) => shape.partial())).partial(),
26867
26931
  attach: AttachSchema.partial(),
@@ -26931,6 +26995,7 @@ var ProjectSpecSchema = external_exports.strictObject({
26931
26995
  /** Its hooks were staged in C:\AgentLaunch\projects\<name>\hooks. */
26932
26996
  hooks: external_exports.boolean()
26933
26997
  });
26998
+ var ShellChoiceSchema = external_exports.enum(["auto", "pwsh", "powershell", "cmd"]);
26934
26999
  var GuestRequestSchema = external_exports.discriminatedUnion("kind", [
26935
27000
  external_exports.strictObject({
26936
27001
  kind: external_exports.literal("session"),
@@ -26940,8 +27005,12 @@ var GuestRequestSchema = external_exports.discriminatedUnion("kind", [
26940
27005
  cwd: Text(),
26941
27006
  project: ProjectSpecSchema.optional(),
26942
27007
  harnessSpec: HarnessSchema.optional(),
27008
+ /** A plain shell instead of an agent (harness is then "shell" and args are ignored). */
27009
+ shell: ShellChoiceSchema.optional(),
26943
27010
  createdAt: TimeSchema
26944
27011
  }),
27012
+ /** End a running session (an agent or a shell) and everything it started. */
27013
+ external_exports.strictObject({ kind: external_exports.literal("close"), id: IdSchema, session: IdSchema, createdAt: TimeSchema }),
26945
27014
  /** Provision a registered project without starting an agent (sandbox start / exec / db from its folder). */
26946
27015
  external_exports.strictObject({ kind: external_exports.literal("project"), id: IdSchema, project: ProjectSpecSchema, createdAt: TimeSchema }),
26947
27016
  external_exports.strictObject({
@@ -27007,6 +27076,8 @@ var SessionStatusSchema = external_exports.object({
27007
27076
  id: IdSchema,
27008
27077
  launchId: Text(64),
27009
27078
  harness: Text(64),
27079
+ /** A plain shell (harness is then the shell: pwsh, powershell or cmd); absent: an agent. */
27080
+ kind: external_exports.enum(["agent", "shell"]).optional(),
27010
27081
  cwd: Text(),
27011
27082
  /** The project it runs in: its hub name, or the launch folder's for the first project. */
27012
27083
  project: Text(64).optional(),
@@ -27939,7 +28010,7 @@ function withRequestSecrets(req) {
27939
28010
  }
27940
28011
 
27941
28012
  // src/guest/daemon/daemon.ts
27942
- import fs54 from "node:fs";
28013
+ import fs55 from "node:fs";
27943
28014
 
27944
28015
  // src/guest/setup/claude-mem.ts
27945
28016
  import fs15 from "node:fs";
@@ -32046,6 +32117,7 @@ async function setupToolchains(config2, versions, options, run2 = {}) {
32046
32117
  env.summary.push(...part.summary);
32047
32118
  }
32048
32119
  Object.assign(env.vars, config2.environment);
32120
+ Object.assign(env.vars, envs.get("Git")?.vars);
32049
32121
  log("Set up " + steps.length + " toolchains and databases.");
32050
32122
  return env;
32051
32123
  }
@@ -32242,6 +32314,7 @@ function execCommand(req) {
32242
32314
  }
32243
32315
 
32244
32316
  // src/guest/daemon/sessions.ts
32317
+ import fs53 from "node:fs";
32245
32318
  import path68 from "node:path";
32246
32319
  import crypto4 from "node:crypto";
32247
32320
  import os11 from "node:os";
@@ -32254,6 +32327,8 @@ var FrameType = {
32254
32327
  Resize: 3,
32255
32328
  Restart: 4,
32256
32329
  Ping: 5,
32330
+ /** Ends the program and everything it started, and with it the session. */
32331
+ Close: 6,
32257
32332
  Output: 16,
32258
32333
  Exit: 17,
32259
32334
  Error: 18,
@@ -32337,6 +32412,16 @@ function cmdArgs(args) {
32337
32412
  function commandLine(file3, args) {
32338
32413
  return path64.win32.basename(file3).toLowerCase() === "cmd.exe" ? [quoteArg(file3), cmdArgs(args)].filter(Boolean).join(" ") : [file3, ...args].map(quoteArg).join(" ");
32339
32414
  }
32415
+ function shellLaunch(asked, paths) {
32416
+ if (asked === "cmd") return { name: "cmd", file: paths.cmd, args: [] };
32417
+ if (asked === "powershell" || asked === "auto" && !paths.pwsh)
32418
+ return { name: "powershell", file: paths.powershell, args: ["-NoLogo"] };
32419
+ if (!paths.pwsh)
32420
+ throw new Error(
32421
+ "PowerShell 7 is not installed in this sandbox (toolchains.pwsh). Open powershell or cmd instead, or turn pwsh on."
32422
+ );
32423
+ return { name: "pwsh", file: paths.pwsh, args: ["-NoLogo"] };
32424
+ }
32340
32425
 
32341
32426
  // src/guest/sessions/pty-host.ts
32342
32427
  import fs50 from "node:fs";
@@ -32556,6 +32641,7 @@ var IdleShutdown = class {
32556
32641
  };
32557
32642
 
32558
32643
  // src/guest/daemon/sessions.ts
32644
+ var CLOSE_WAIT_MS = 8e3;
32559
32645
  var ROOTS_KEPT_MS = 10 * 60 * 1e3;
32560
32646
  function terminalTab(id, title, cwd) {
32561
32647
  const view = [process.execPath, path68.join(path68.dirname(process.argv[1]), "view.js"), id];
@@ -32601,14 +32687,38 @@ function writeBudget(cpus, memoryGb, jobs, env) {
32601
32687
  warn("budget.json could not be written: " + errMsg(e));
32602
32688
  }
32603
32689
  }
32604
- var launchPtyHost = async (req, remote, env, say, sessions2, onSpawn) => {
32690
+ var agentProgram = async (req, env, say) => {
32605
32691
  const harness = harnessFor(req);
32606
32692
  say("Preparing " + req.harness);
32607
32693
  await agents.provision(req.harness, harness, env, false);
32608
32694
  const launch = agents.resolveLaunch(req.harness, harness);
32609
32695
  const base = [...launch.args, ...harness.arguments];
32696
+ return {
32697
+ command: commandLine(launch.file, [...base, ...req.args]),
32698
+ restartCommand: commandLine(launch.file, [...base, ...harness.resumeArguments ?? []]),
32699
+ environment: harness.environment
32700
+ };
32701
+ };
32702
+ function shellPaths() {
32703
+ const candidates = [
32704
+ path68.join(pwshDir(), "pwsh.exe"),
32705
+ path68.join(process.env.ProgramFiles ?? "C:\\Program Files", "PowerShell", "7", "pwsh.exe")
32706
+ ];
32707
+ return { pwsh: candidates.find((f) => fs53.existsSync(f)) ?? null, powershell: SYSTEM.powershell(), cmd: SYSTEM.cmd() };
32708
+ }
32709
+ var shellProgram = (shell) => {
32710
+ const line = commandLine(shell.file, shell.args);
32711
+ return { command: line, restartCommand: line, environment: {} };
32712
+ };
32713
+ var launchPtyHost = async (req, program, remote, env, sessions2, onSpawn) => {
32610
32714
  const port = await freePort();
32611
32715
  const token = crypto4.randomBytes(24).toString("base64url");
32716
+ const sessionEnv = {
32717
+ ...withRegistryPath(env),
32718
+ ...program.environment,
32719
+ ...memoryEnv(env, sessions2)
32720
+ };
32721
+ if (req.shell) delete sessionEnv.PSModulePath;
32612
32722
  const child = startPtyHost({
32613
32723
  exe: ensurePtyHost(),
32614
32724
  port,
@@ -32617,10 +32727,10 @@ var launchPtyHost = async (req, remote, env, say, sessions2, onSpawn) => {
32617
32727
  rows: 32,
32618
32728
  id: req.id,
32619
32729
  cwd: req.cwd,
32620
- env: { ...withRegistryPath(env), ...harness.environment, ...memoryEnv(env, sessions2) },
32730
+ env: sessionEnv,
32621
32731
  remote,
32622
- command: commandLine(launch.file, [...base, ...req.args]),
32623
- restartCommand: commandLine(launch.file, [...base, ...harness.resumeArguments ?? []])
32732
+ command: program.command,
32733
+ restartCommand: program.restartCommand
32624
32734
  });
32625
32735
  onSpawn(child);
32626
32736
  try {
@@ -32635,6 +32745,17 @@ var launchPtyHost = async (req, remote, env, say, sessions2, onSpawn) => {
32635
32745
  };
32636
32746
  var placeFor = async (req, say) => {
32637
32747
  if (!req.project) {
32748
+ const known = insidePrimary(req.cwd) ? null : provisioner.provisionedFor(req.cwd);
32749
+ if (known && exists(req.cwd))
32750
+ return {
32751
+ env: known.env,
32752
+ hooks: known.spec.hooks ? projectHooks(known.spec.name, known.spec.workspace) : null,
32753
+ project: known.spec.name
32754
+ };
32755
+ if (!insidePrimary(req.cwd) && insideWorkspace(req.cwd))
32756
+ throw new Error(
32757
+ "The project in " + req.cwd + " is not set up in the sandbox yet: run sandbox start in its folder first."
32758
+ );
32638
32759
  if (!insidePrimary(req.cwd) || !exists(req.cwd))
32639
32760
  throw new Error("Folder is not inside the sandbox workspace " + workspace + ": " + req.cwd);
32640
32761
  return { env: process.env, hooks: null, project: primaryProject() };
@@ -32682,16 +32803,29 @@ var SessionManager = class {
32682
32803
  const r = this.ptyHosts.get(id);
32683
32804
  if (r && r.to === void 0) this.ptyHosts.set(id, { ...r, to: Date.now() });
32684
32805
  }
32685
- /** `session` requests: an agent in its own ptyhost, in the launch folder's project or a registered one. */
32806
+ /**
32807
+ * `session` requests: an agent (or a plain shell) in its own ptyhost, in the launch folder's project or a registered
32808
+ * one.
32809
+ */
32686
32810
  async start(req) {
32811
+ let shell = null;
32812
+ let problem = null;
32813
+ if (req.shell)
32814
+ try {
32815
+ shell = shellLaunch(req.shell, shellPaths());
32816
+ } catch (e) {
32817
+ problem = errMsg(e);
32818
+ }
32819
+ const name = shell?.name ?? (req.shell ? "shell" : req.harness);
32687
32820
  let status = {
32688
32821
  id: req.id,
32689
32822
  launchId: session.launchId,
32690
- harness: req.harness,
32823
+ harness: name,
32824
+ ...req.shell ? { kind: "shell" } : {},
32691
32825
  cwd: req.cwd,
32692
32826
  ...req.project ? { project: req.project.name } : {},
32693
32827
  phase: "starting",
32694
- message: "Starting " + req.harness,
32828
+ message: "Starting " + name,
32695
32829
  updatedAt: now(),
32696
32830
  startedAt: now()
32697
32831
  };
@@ -32702,34 +32836,37 @@ var SessionManager = class {
32702
32836
  };
32703
32837
  this.starting++;
32704
32838
  if (req.id !== session.launchId && process.env.AGENT_NO_TABS !== "1")
32705
- safely("terminal tab", () => terminalTab(req.id, req.harness + " " + path68.basename(req.cwd), req.cwd));
32839
+ safely("terminal tab", () => terminalTab(req.id, name + " " + path68.basename(req.cwd), req.cwd));
32706
32840
  try {
32841
+ if (problem) throw new Error(problem);
32707
32842
  const place = await placeFor(req, say);
32843
+ const program = shell ? shellProgram(shell) : await agentProgram(req, place.env, say);
32708
32844
  const remote = session.networking && session.attach.enabled;
32709
32845
  const from = Date.now();
32710
32846
  const { child, port, token } = await launchPtyHost(
32711
32847
  req,
32848
+ program,
32712
32849
  remote,
32713
32850
  place.env,
32714
- say,
32715
32851
  this.live.size + 1,
32716
32852
  (c) => this.rootStarted(req.id, c, from)
32717
32853
  );
32718
- const resumed = req.harness === "claude" ? resumedConversation(req.args) : void 0;
32854
+ const agent = shell === null;
32855
+ const resumed = agent && req.harness === "claude" ? resumedConversation(req.args) : void 0;
32719
32856
  const running = {
32720
32857
  ...status,
32721
32858
  ...resumed ? { conversation: resumed } : {},
32722
32859
  project: place.project,
32723
32860
  phase: "running",
32724
- message: req.harness + " is running",
32861
+ message: name + " is running",
32725
32862
  attach: { addresses: remote ? guestAddresses() : [], port, token }
32726
32863
  };
32727
32864
  this.live.set(req.id, { status: running, child });
32728
32865
  child.once("exit", (code) => this.onExit(running, place, code));
32729
32866
  saveStatus(running);
32730
- if (req.harness === "claude") this.trackConversation(running.id, resumed);
32867
+ if (agent && req.harness === "claude") this.trackConversation(running.id, resumed);
32731
32868
  log(
32732
- "Session " + req.id + " started (" + req.harness + " in " + req.cwd + ", project " + place.project + ", port " + port + ")"
32869
+ "Session " + req.id + " started (" + name + " in " + req.cwd + ", project " + place.project + ", port " + port + ")"
32733
32870
  );
32734
32871
  } catch (e) {
32735
32872
  this.rootEnded(req.id);
@@ -32758,10 +32895,13 @@ var SessionManager = class {
32758
32895
  this.idle.check();
32759
32896
  }
32760
32897
  }
32761
- /** `restart` requests: the matching sessions restart their agent (continuing the conversation where it can). */
32898
+ /**
32899
+ * `restart` requests: the matching sessions restart their agent (continuing the conversation where it can). Shells
32900
+ * are left alone: restarting one would only throw away what it was doing.
32901
+ */
32762
32902
  async restart(req) {
32763
32903
  const targets = [...this.live.values()].filter(
32764
- (s) => s.status.attach && (req.harness === null || s.status.harness === req.harness) && (!req.cwd || isWithin(req.cwd, s.status.cwd))
32904
+ (s) => s.status.attach && s.status.kind !== "shell" && (req.harness === null || s.status.harness === req.harness) && (!req.cwd || isWithin(req.cwd, s.status.cwd))
32765
32905
  );
32766
32906
  for (const s of targets)
32767
32907
  await sendControl(
@@ -32770,6 +32910,41 @@ var SessionManager = class {
32770
32910
  );
32771
32911
  respond(req.id, { ok: true, message: "restarted " + targets.length + " session(s)" });
32772
32912
  }
32913
+ /**
32914
+ * `close` requests: the session's ptyhost ends its program and everything it started (ProcessTree), then exits as
32915
+ * if the program had. One that does not end in time has its ptyhost ended instead (closing the pseudo console ends
32916
+ * what is attached to it).
32917
+ */
32918
+ async close(req) {
32919
+ const entry = this.live.get(req.session);
32920
+ if (!entry?.status.attach) {
32921
+ respond(req.id, { ok: false, message: "Session " + req.session + " is not running." });
32922
+ return;
32923
+ }
32924
+ const { child, status } = entry;
32925
+ const ended = new Promise((resolve2) => {
32926
+ if (child.exitCode !== null || child.signalCode !== null) return resolve2(true);
32927
+ const timer = setTimeout(() => resolve2(false), CLOSE_WAIT_MS);
32928
+ child.once("exit", () => {
32929
+ clearTimeout(timer);
32930
+ resolve2(true);
32931
+ });
32932
+ });
32933
+ try {
32934
+ await sendControl(
32935
+ { addresses: ["127.0.0.1"], port: status.attach.port, token: status.attach.token },
32936
+ FrameType.Close
32937
+ );
32938
+ } catch (e) {
32939
+ warn("Session " + req.session + " did not take the close request: " + errMsg(e));
32940
+ }
32941
+ if (!await ended) {
32942
+ warn("Session " + req.session + " did not end in time; ending its session host.");
32943
+ safely("ending a session host", () => child.kill());
32944
+ }
32945
+ log("Session " + req.session + " closed on request");
32946
+ respond(req.id, { ok: true, message: "Closed " + status.harness + " session " + req.session + "." });
32947
+ }
32773
32948
  /**
32774
32949
  * Finds out which of Claude's conversation files this session writes (it appears with the first message) and puts
32775
32950
  * it in the status, so the host can reopen this very conversation. A reopened session starts with the one it was
@@ -32812,6 +32987,10 @@ var SessionManager = class {
32812
32987
  attach: void 0
32813
32988
  });
32814
32989
  log("Session " + running.id + " ended (exit " + code + ")");
32990
+ if (running.kind === "shell") {
32991
+ this.idle.check();
32992
+ return;
32993
+ }
32815
32994
  if (place.hooks)
32816
32995
  void runHooksAsync("post-agent", place.env, place.hooks).catch(
32817
32996
  (e) => warn("post-agent hooks failed: " + errMsg(e))
@@ -33151,7 +33330,7 @@ var GovernorCore = class {
33151
33330
  };
33152
33331
 
33153
33332
  // src/guest/governor/governor.ts
33154
- import fs53 from "node:fs";
33333
+ import fs54 from "node:fs";
33155
33334
  import os12 from "node:os";
33156
33335
  import path69 from "node:path";
33157
33336
  import { spawn as spawn8 } from "node:child_process";
@@ -33224,11 +33403,11 @@ var MemGovProcess = class {
33224
33403
  function appendToMemoryLog(line) {
33225
33404
  try {
33226
33405
  const logs = path69.join(dataDir, "logs");
33227
- const newest = fs53.readdirSync(logs).filter((f) => /^memory-\d{8}-\d{6}\.log$/.test(f)).sort().pop();
33406
+ const newest = fs54.readdirSync(logs).filter((f) => /^memory-\d{8}-\d{6}\.log$/.test(f)).sort().pop();
33228
33407
  if (!newest) return;
33229
33408
  const t = /* @__PURE__ */ new Date();
33230
33409
  const stamp = [t.getHours(), t.getMinutes(), t.getSeconds()].map((n) => String(n).padStart(2, "0")).join(":");
33231
- fs53.appendFileSync(path69.join(logs, newest), stamp + " | " + line + "\r\n");
33410
+ fs54.appendFileSync(path69.join(logs, newest), stamp + " | " + line + "\r\n");
33232
33411
  } catch {
33233
33412
  }
33234
33413
  }
@@ -33285,9 +33464,9 @@ var Daemon = class {
33285
33464
  tick() {
33286
33465
  const { valid, invalid } = run.scanRequests();
33287
33466
  for (const file3 of invalid)
33288
- safely("removing an invalid request", () => fs54.rmSync(file3, { recursive: true, force: true }));
33467
+ safely("removing an invalid request", () => fs55.rmSync(file3, { recursive: true, force: true }));
33289
33468
  for (const req of valid) {
33290
- safely("claiming a request", () => fs54.rmSync(run.requestFile(req.id), { force: true }));
33469
+ safely("claiming a request", () => fs55.rmSync(run.requestFile(req.id), { force: true }));
33291
33470
  if (run.readSession(req.id)) continue;
33292
33471
  void Promise.resolve(this.handle(withRequestSecrets(req))).catch(
33293
33472
  (e) => respond(req.id, { ok: false, message: errMsg(e) })
@@ -33309,6 +33488,8 @@ var Daemon = class {
33309
33488
  return execCommand(req);
33310
33489
  case "restart":
33311
33490
  return sessions.restart(req);
33491
+ case "close":
33492
+ return sessions.close(req);
33312
33493
  case "pressure":
33313
33494
  return governor.hostPressure(req.seconds, req.reason, req.level ?? "slow");
33314
33495
  case "ping":
@@ -26636,12 +26636,39 @@ var ResumeSchema = external_exports.strictObject({
26636
26636
  });
26637
26637
 
26638
26638
  // src/core/schema/web.ts
26639
+ var REMOTE_ROLES = ["view", "control"];
26640
+ var WindowsUserNameSchema = external_exports.string().trim().min(1).max(120).regex(
26641
+ /^(?:[A-Za-z0-9][A-Za-z0-9._ -]{0,62}\\)?[^\\/"[\]:;|=,+*?<>@]{1,64}$/,
26642
+ "a Windows user name (name or DOMAIN\\name)"
26643
+ );
26644
+ var RemoteUserSchema = external_exports.strictObject({
26645
+ name: WindowsUserNameSchema.describe("The Windows user (name, or DOMAIN\\name)"),
26646
+ role: external_exports.enum(REMOTE_ROLES).describe("view: sees everything, terminals read-only. control: everything but remote access, logins and tokens.")
26647
+ });
26648
+ var WebLanSchema = external_exports.strictObject({
26649
+ enabled: external_exports.boolean().describe(
26650
+ "(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."
26651
+ ),
26652
+ port: external_exports.number().int().min(1024).max(65535).describe("HTTPS port for paired devices on your network."),
26653
+ 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).")
26654
+ });
26655
+ var WebRemoteSchema = external_exports.strictObject({
26656
+ windowsSignIn: external_exports.boolean().describe(
26657
+ "(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."
26658
+ ),
26659
+ users: external_exports.array(RemoteUserSchema).max(32).describe("Who else may sign in with Windows, and their role (view or control)."),
26660
+ rememberDays: external_exports.number().int().min(1).max(90).describe(
26661
+ "How many days a paired device or a Windows sign-in stays signed in (from when it signed in, not renewed)."
26662
+ ),
26663
+ lan: WebLanSchema
26664
+ });
26639
26665
  var WebSchema = external_exports.strictObject({
26640
26666
  enabled: external_exports.boolean().describe(
26641
26667
  "(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."
26642
26668
  ),
26643
26669
  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."),
26644
- openOnLaunch: external_exports.boolean().describe("(Beta) With the web UI on: open it in your browser, already signed in, every time a sandbox starts.")
26670
+ openOnLaunch: external_exports.boolean().describe("(Beta) With the web UI on: open it in your browser, already signed in, every time a sandbox starts."),
26671
+ remote: WebRemoteSchema.describe("(Beta) Access for other Windows users of this PC and for paired devices.")
26645
26672
  });
26646
26673
 
26647
26674
  // src/core/schema/vpn.ts
@@ -26671,6 +26698,36 @@ var GithubSchema = external_exports.strictObject({
26671
26698
  });
26672
26699
 
26673
26700
  // src/core/schema/config.ts
26701
+ 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;
26702
+ var RESERVED_ENV_SUFFIX = /(_PROXY|_BASE_URL|_ENDPOINT|_CA_BUNDLE|_CA_CERTS)$/i;
26703
+ var RESERVED_ENV_NAMES = new Set(
26704
+ [
26705
+ "PATH",
26706
+ "PATHEXT",
26707
+ "BASH_ENV",
26708
+ "ENV",
26709
+ "COMSPEC",
26710
+ "PSMODULEPATH",
26711
+ "PYTHONPATH",
26712
+ "PYTHONSTARTUP",
26713
+ "PERL5OPT",
26714
+ "RUBYOPT",
26715
+ "JAVA_TOOL_OPTIONS",
26716
+ "LD_PRELOAD",
26717
+ "SYSTEMROOT",
26718
+ "WINDIR",
26719
+ "HOME",
26720
+ "USERPROFILE",
26721
+ "APPDATA",
26722
+ "LOCALAPPDATA",
26723
+ "PROGRAMDATA",
26724
+ "TEMP",
26725
+ "TMP"
26726
+ ].map((n) => n.toUpperCase())
26727
+ );
26728
+ function reservedEnvName(name) {
26729
+ return RESERVED_ENV_PREFIX.test(name) || RESERVED_ENV_SUFFIX.test(name) || RESERVED_ENV_NAMES.has(name.toUpperCase());
26730
+ }
26674
26731
  var ConfigSchema = external_exports.strictObject({
26675
26732
  $schema: SchemaRefSchema.optional(),
26676
26733
  defaultHarness: NameSchema.describe("Agent started by `sandbox launch` (sandbox claude always runs claude)."),
@@ -26737,10 +26794,14 @@ var LocalOverridesSchema = external_exports.strictObject({
26737
26794
  scripts: StringsSchema.describe(
26738
26795
  "Scripts (.ps1 / .mjs / .cmd, relative to this file) run inside the sandbox before the agent."
26739
26796
  ),
26740
- // This PC's resources are yours to share: CPU and paging file (this PC's disk) are not a project's to set.
26797
+ // 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
26798
+ // project's to set.
26741
26799
  sandbox: SandboxSchema.omit({
26742
26800
  cpuLimit: true,
26743
26801
  pageFileMb: true,
26802
+ reserveHostMb: true,
26803
+ maxMemoryPercent: true,
26804
+ maxMemoryMb: true,
26744
26805
  engine: true,
26745
26806
  agentMemoryHeadroomMb: true,
26746
26807
  pressureResponse: true
@@ -26754,7 +26815,10 @@ var LocalOverridesSchema = external_exports.strictObject({
26754
26815
  // Every toolchain setting, each one optional (a project usually only switches languages on or pins a version).
26755
26816
  toolchains: external_exports.strictObject({
26756
26817
  ...import_lodash.default.mapValues(import_lodash.default.omit(ToolchainsSchema.shape, "environment"), (shape) => shape.partial()),
26757
- environment: ToolchainsSchema.shape.environment
26818
+ environment: ToolchainsSchema.shape.environment.refine(
26819
+ (env) => !Object.keys(env).some(reservedEnvName),
26820
+ "names the sandbox sets or that carry logins (AGENT_*, GIT_*, PATH, BASH_ENV, NODE_*, proxies, ANTHROPIC_*, CLAUDE_* ...) cannot be set by a project"
26821
+ )
26758
26822
  }).partial(),
26759
26823
  databases: external_exports.strictObject(import_lodash.default.mapValues(DatabasesSchema.shape, (shape) => shape.partial())).partial(),
26760
26824
  attach: AttachSchema.partial(),
@@ -26824,6 +26888,7 @@ var ProjectSpecSchema = external_exports.strictObject({
26824
26888
  /** Its hooks were staged in C:\AgentLaunch\projects\<name>\hooks. */
26825
26889
  hooks: external_exports.boolean()
26826
26890
  });
26891
+ var ShellChoiceSchema = external_exports.enum(["auto", "pwsh", "powershell", "cmd"]);
26827
26892
  var GuestRequestSchema = external_exports.discriminatedUnion("kind", [
26828
26893
  external_exports.strictObject({
26829
26894
  kind: external_exports.literal("session"),
@@ -26833,8 +26898,12 @@ var GuestRequestSchema = external_exports.discriminatedUnion("kind", [
26833
26898
  cwd: Text(),
26834
26899
  project: ProjectSpecSchema.optional(),
26835
26900
  harnessSpec: HarnessSchema.optional(),
26901
+ /** A plain shell instead of an agent (harness is then "shell" and args are ignored). */
26902
+ shell: ShellChoiceSchema.optional(),
26836
26903
  createdAt: TimeSchema
26837
26904
  }),
26905
+ /** End a running session (an agent or a shell) and everything it started. */
26906
+ external_exports.strictObject({ kind: external_exports.literal("close"), id: IdSchema, session: IdSchema, createdAt: TimeSchema }),
26838
26907
  /** Provision a registered project without starting an agent (sandbox start / exec / db from its folder). */
26839
26908
  external_exports.strictObject({ kind: external_exports.literal("project"), id: IdSchema, project: ProjectSpecSchema, createdAt: TimeSchema }),
26840
26909
  external_exports.strictObject({
@@ -26900,6 +26969,8 @@ var SessionStatusSchema = external_exports.object({
26900
26969
  id: IdSchema,
26901
26970
  launchId: Text(64),
26902
26971
  harness: Text(64),
26972
+ /** A plain shell (harness is then the shell: pwsh, powershell or cmd); absent: an agent. */
26973
+ kind: external_exports.enum(["agent", "shell"]).optional(),
26903
26974
  cwd: Text(),
26904
26975
  /** The project it runs in: its hub name, or the launch folder's for the first project. */
26905
26976
  project: Text(64).optional(),
@@ -62,6 +62,7 @@ var LISTING = {
62
62
  };
63
63
  var WITH_VALUE = /* @__PURE__ */ new Set(["-C", "--namespace"]);
64
64
  var FORBIDDEN_GLOBAL = /^(-c|--config-env|--exec-path|--git-dir|--work-tree|--super-prefix)(=|$)/;
65
+ var RUNS_OR_WRITES = /^(--open-files-in-pager|--upload-pack|--output|--ext-diff)(=|$)/;
65
66
  function gitDecision(args, access) {
66
67
  if (access === "full") return { allow: true };
67
68
  if (access !== "readonly")
@@ -76,6 +77,11 @@ function gitDecision(args, access) {
76
77
  i += WITH_VALUE.has(args[i]) ? 2 : 1;
77
78
  }
78
79
  const [command, ...rest] = args.slice(i);
80
+ const runsOrWrites = rest.find(
81
+ (a) => RUNS_OR_WRITES.test(a) || command === "ls-remote" && /^-u(.|$)/.test(a) || command === "grep" && /^-O/.test(a)
82
+ );
83
+ if (runsOrWrites)
84
+ return { allow: false, reason: "git " + runsOrWrites + " is not allowed: git is read-only in this sandbox." };
79
85
  if (command === void 0 || command === "--version" || READ_ONLY.has(command) || (LISTING[command]?.(rest) ?? false))
80
86
  return { allow: true };
81
87
  return {