windows-development-sandbox 1.15.0 → 1.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/config.example.json +6 -0
  2. package/dist/app/features/access/access.run.js +1 -1
  3. package/dist/app/features/accounts/accounts.def.js +4 -3
  4. package/dist/app/features/defs.d.ts +3 -1
  5. package/dist/app/features/defs.js +26 -12
  6. package/dist/app/features/sandbox/sandbox.def.js +9 -5
  7. package/dist/app/features/sessions/sessions.def.js +1 -1
  8. package/dist/bin/agent-sandbox.js +2 -2
  9. package/dist/bin/{chunk-HTQT5SBY.js → chunk-QLGGAG3D.js} +98 -79
  10. package/dist/bin/{chunk-D7CM63BS.js → chunk-REE3EMHD.js} +105 -41
  11. package/dist/bin/{chunk-RQ4BWBYX.js → chunk-XYUVIVJT.js} +1 -1
  12. package/dist/bin/claude-host.js +2 -2
  13. package/dist/bin/claude-sbx.js +2 -2
  14. package/dist/bin/codex-sbx.js +2 -2
  15. package/dist/bin/gemini-sbx.js +2 -2
  16. package/dist/bin/sandbox.js +2 -2
  17. package/dist/bin/sbx-hide.js +2 -2
  18. package/dist/bin/sbx-show.js +2 -2
  19. package/dist/cli/commands/web.js +1 -1
  20. package/dist/cli/index.js +4 -1
  21. package/dist/cli/report.d.ts +7 -0
  22. package/dist/cli/report.js +31 -0
  23. package/dist/core/config/defaults.js +1 -0
  24. package/dist/core/detect/toolchains.d.ts +1 -0
  25. package/dist/core/detect/toolchains.js +7 -0
  26. package/dist/core/schema/config.d.ts +21 -0
  27. package/dist/core/schema/sandbox.js +1 -1
  28. package/dist/core/schema/toolchains.d.ts +6 -0
  29. package/dist/core/schema/toolchains.js +10 -0
  30. package/dist/guest/boot/boot.js +2 -0
  31. package/dist/guest/setup/browser-gpu.d.ts +1 -0
  32. package/dist/guest/setup/browser-gpu.js +18 -0
  33. package/dist/guest/toolchains/native/rust-extras.js +3 -1
  34. package/dist/guest/toolchains/native/unreal.d.ts +9 -0
  35. package/dist/guest/toolchains/native/unreal.js +186 -0
  36. package/dist/guest/toolchains/native/vcruntime.d.ts +3 -0
  37. package/dist/guest/toolchains/native/vcruntime.js +2 -2
  38. package/dist/guest/toolchains/plan.js +10 -2
  39. package/dist/host/engines/hcs/live-folders.js +1 -1
  40. package/dist/host/github/token.js +1 -1
  41. package/dist/host/profile/accounts.js +1 -1
  42. package/dist/host/profile/agent-accounts.js +1 -1
  43. package/dist/host/profile/plugin-share.d.ts +5 -0
  44. package/dist/host/profile/plugin-share.js +22 -1
  45. package/dist/host/secrets/secret-store.d.ts +3 -3
  46. package/dist/host/secrets/secret-store.js +37 -10
  47. package/dist/host/vpn/login.js +1 -1
  48. package/dist/runtime/agent.js +16 -1
  49. package/dist/runtime/daemon.js +522 -337
  50. package/dist/runtime/db.js +17 -1
  51. package/dist/runtime/main.js +669 -472
  52. package/dist/runtime/netproxy.js +16 -1
  53. package/dist/runtime/view.js +16 -1
  54. package/dist/selftest/accounts.test.js +29 -0
  55. package/dist/selftest/app.test.js +2 -2
  56. package/dist/selftest/detect.test.js +7 -0
  57. package/dist/selftest/live-network.test.js +5 -0
  58. package/dist/selftest/support/fixtures.d.ts +6 -0
  59. package/dist/shared/channel/schema.d.ts +18 -0
  60. package/dist/web/index.html +2 -2
  61. package/dist/web/main-JTH3FT4O.js +221 -0
  62. package/dist/web/{main-UAQPI5XD.css → main-XT7T6ODM.css} +1 -1
  63. package/native/csharp/HcsHost/AssemblyInfo.cs +3 -3
  64. package/native/csharp/HostTools/AssemblyInfo.cs +3 -3
  65. package/native/csharp/HostTools/Program.cs +6 -1
  66. package/native/csharp/HostTools/Protect.cs +41 -0
  67. package/native/csharp/StorageHelper/AssemblyInfo.cs +3 -3
  68. package/package.json +1 -1
  69. package/schemas/config.schema.json +34 -1
  70. package/schemas/sandbox-config.schema.json +34 -1
  71. package/dist/web/main-7DJZ2M27.js +0 -221
@@ -25938,6 +25938,7 @@ var DEFAULTS = {
25938
25938
  linkSlots: "auto"
25939
25939
  },
25940
25940
  msvc: { enabled: "auto", acceptLicense: false, msvcVersion: "latest", sdkVersion: "latest" },
25941
+ unreal: { enabled: "auto", acceptLicense: false, components: [], keepInstallFiles: true },
25941
25942
  cpp: { enabled: "auto", flexBison: true },
25942
25943
  dotnet: {
25943
25944
  enabled: "auto",
@@ -46053,7 +46054,7 @@ var SandboxSchema = external_exports.strictObject({
46053
46054
  "When the sandbox (or this PC) runs low on memory, slow builds down in steps, like a PC that gets slower instead of failing: build programs get lower CPU and memory priority, then less CPU, then compilers take turns (the waiting ones briefly suspended and trimmed to the paging file). Nothing is stopped or killed, and the agents themselves are never touched. Yours only: a project file cannot change it."
46054
46055
  ),
46055
46056
  vGpu: external_exports.boolean().describe(
46056
- "Virtual GPU. Off by default: Microsoft notes it widens the sandbox attack surface. The sandbox then uses this PC's graphics card, and with some graphics drivers heavy use can freeze the whole PC: turn it on only when a program in the sandbox needs the GPU (the mesa toolchain gives software OpenGL without it)."
46057
+ "Virtual GPU. Off by default: Microsoft notes it widens the sandbox attack surface. The sandbox then uses this PC's graphics card, and with some graphics drivers heavy use can freeze the whole PC: turn it on only when a program in the sandbox needs the GPU (the mesa toolchain gives software OpenGL without it). Browsers in the sandbox never use it (they draw in software)."
46057
46058
  ),
46058
46059
  networking: external_exports.boolean().describe("Internet access. false is the only hard network isolation Windows Sandbox offers."),
46059
46060
  clipboard: external_exports.boolean().describe(
@@ -46215,6 +46216,21 @@ var ToolchainsSchema = external_exports.strictObject({
46215
46216
  msvcVersion: external_exports.string().min(1).describe('"latest" or an MSVC tools version prefix, e.g. "14.44".'),
46216
46217
  sdkVersion: external_exports.string().min(1).describe('"latest" or a Windows SDK build, e.g. "26100" or "10.0.22621".')
46217
46218
  }),
46219
+ unreal: external_exports.strictObject({
46220
+ enabled: enabled(
46221
+ "Unreal Engine development: Visual Studio Community 2022 with the C++ game workload (MSVC, a Windows 11 SDK, .NET Framework targeting packs, Unreal integration and the Epic Games Launcher) and the DirectX End-User Runtime. A real installer: it runs every boot (~10 GB on the sandbox disk). The engine itself comes from the Epic Games Launcher or its source.",
46222
+ "a .uproject or .uplugin"
46223
+ ),
46224
+ acceptLicense: external_exports.boolean().describe(
46225
+ "Required to install: you accept the Visual Studio Community licence terms (https://visualstudio.microsoft.com/license-terms/) and the DirectX End-User Runtime licence."
46226
+ ),
46227
+ components: StringsSchema.describe(
46228
+ 'More Visual Studio workload or component IDs to add, e.g. ["Microsoft.VisualStudio.Component.VC.Llvm.Clang"].'
46229
+ ),
46230
+ keepInstallFiles: external_exports.boolean().describe(
46231
+ "Keep an offline copy of the Visual Studio install files in tools\\visualstudio (~10 GB, made once), so later boots install without downloading them again. Skipped while that disk has too little room."
46232
+ )
46233
+ }),
46218
46234
  cpp: external_exports.strictObject({
46219
46235
  enabled: enabled(
46220
46236
  "MinGW-w64 GCC with CMake, Ninja, premake5 and mingw32-make (also Rust's linker).",
@@ -54689,7 +54705,23 @@ import { spawn as spawn8 } from "node:child_process";
54689
54705
  // src/host/secrets/secret-store.ts
54690
54706
  import { spawnSync as spawnSync19 } from "node:child_process";
54691
54707
  var TIMEOUT_MS2 = 6e4;
54708
+ var toolsFor = /* @__PURE__ */ new Map();
54709
+ function dpapiTool(storageRoot) {
54710
+ if (!toolsFor.has(storageRoot)) {
54711
+ let exe = null;
54712
+ try {
54713
+ exe = hostToolsExe(storageRoot);
54714
+ } catch {
54715
+ }
54716
+ toolsFor.set(storageRoot, exe);
54717
+ }
54718
+ return toolsFor.get(storageRoot) ?? null;
54719
+ }
54692
54720
  var DpapiBox = class {
54721
+ constructor(storageRoot) {
54722
+ this.storageRoot = storageRoot;
54723
+ }
54724
+ storageRoot;
54693
54725
  kind = "dpapi1";
54694
54726
  seal(plain2) {
54695
54727
  return this.run("protect", plain2.toString("base64"));
@@ -54698,7 +54730,9 @@ var DpapiBox = class {
54698
54730
  return Buffer.from(this.run("unprotect", sealed), "base64");
54699
54731
  }
54700
54732
  run(mode, input2) {
54701
- const result = spawnSync19(SYSTEM.powershell(), nativeScript("protect-data.ps1", ["-Mode", mode]), {
54733
+ const tools = this.storageRoot ? dpapiTool(this.storageRoot) : null;
54734
+ const [exe, args] = tools ? [tools, [mode]] : [SYSTEM.powershell(), nativeScript("protect-data.ps1", ["-Mode", mode])];
54735
+ const result = spawnSync19(exe, args, {
54702
54736
  input: input2,
54703
54737
  encoding: "utf8",
54704
54738
  windowsHide: true,
@@ -54726,17 +54760,17 @@ var SecretStore = class _SecretStore {
54726
54760
  this.box = box;
54727
54761
  }
54728
54762
  box;
54729
- /** Windows DPAPI, current user. */
54730
- static dpapi() {
54731
- return new _SecretStore(new DpapiBox());
54763
+ /** Windows DPAPI, current user; with a storage folder, through its HostTools.exe (much faster than PowerShell). */
54764
+ static dpapi(storageRoot = null) {
54765
+ return new _SecretStore(new DpapiBox(storageRoot));
54732
54766
  }
54733
54767
  /** The test stand-in (not secure). */
54734
54768
  static memory() {
54735
54769
  return new _SecretStore(new MemoryBox());
54736
54770
  }
54737
54771
  /** What the program uses: DPAPI, except during the self-test (it never spawns Windows for this). */
54738
- static standard() {
54739
- return selfTestRunning() ? _SecretStore.memory() : _SecretStore.dpapi();
54772
+ static standard(storageRoot = null) {
54773
+ return selfTestRunning() ? _SecretStore.memory() : _SecretStore.dpapi(storageRoot);
54740
54774
  }
54741
54775
  /** True when `value` looks like something seal() made (any box). */
54742
54776
  static isSealed(value) {
@@ -55134,7 +55168,7 @@ function realLiveFolders(storageRoot, dataPath) {
55134
55168
  return new LiveFolders({
55135
55169
  storageRoot,
55136
55170
  dataPath,
55137
- secrets: SecretStore.standard(),
55171
+ secrets: SecretStore.standard(storageRoot),
55138
55172
  elevated: new ScriptLivePort(),
55139
55173
  userSid: currentUserSid,
55140
55174
  isAlive: isSandboxRunning
@@ -56008,6 +56042,8 @@ var MAX_PLUGIN_BYTES = 1024 * 1024 * 1024;
56008
56042
  var MAX_SETTINGS_BYTES = 4 * 1024 * 1024;
56009
56043
  var MAX_DEPTH = 24;
56010
56044
  var INSTALLED = "installed_plugins.json";
56045
+ var RECORDS = [INSTALLED, "known_marketplaces.json"];
56046
+ var MAX_RECORD_BYTES2 = 4 * 1024 * 1024;
56011
56047
  function isObject2(value) {
56012
56048
  return typeof value === "object" && value !== null && !Array.isArray(value);
56013
56049
  }
@@ -56054,7 +56090,7 @@ var PluginShare = class _PluginShare {
56054
56090
  /** When a sandbox ends: its plugins refresh the shared set. Never throws; returns the problem, if any. */
56055
56091
  saveQuietly(dataPath, claudeDir) {
56056
56092
  try {
56057
- if (_PluginShare.hasPlugins(claudeDir)) this.save(dataPath, claudeDir);
56093
+ if (_PluginShare.hasPlugins(claudeDir) && !this.matches(claudeDir)) this.save(dataPath, claudeDir);
56058
56094
  return null;
56059
56095
  } catch (e) {
56060
56096
  return errMsg(e);
@@ -56108,9 +56144,24 @@ var PluginShare = class _PluginShare {
56108
56144
  if (isObject2(shared[key3])) merged[key3] = { ...shared[key3], ...isObject2(merged[key3]) ? merged[key3] : {} };
56109
56145
  writeSandboxJson(file2, merged);
56110
56146
  }
56147
+ /**
56148
+ * Whether the shared set already holds this Claude folder's plugins: the same plugin records and plugin settings.
56149
+ * Copying a big plugins folder takes seconds, so a switch between accounts with the same plugins skips it.
56150
+ */
56151
+ matches(claudeDir) {
56152
+ if (!this.hasShared()) return false;
56153
+ const same3 = (a, b) => JSON.stringify(a) === JSON.stringify(b);
56154
+ for (const name2 of RECORDS) {
56155
+ const own2 = readUntrusted(path67.join(claudeDir, "plugins", name2), MAX_RECORD_BYTES2);
56156
+ if (!same3(own2, readUntrusted(path67.join(this.pluginsDir, name2), MAX_RECORD_BYTES2))) return false;
56157
+ }
56158
+ const shared = readUntrusted(this.settingsFile, MAX_SETTINGS_BYTES);
56159
+ return same3(pluginSettings(claudeDir), isObject2(shared) ? shared : {});
56160
+ }
56111
56161
  /** One step when an account is switched to. */
56112
56162
  sync(dataPath, claudeDir) {
56113
56163
  if (_PluginShare.hasPlugins(claudeDir)) {
56164
+ if (this.matches(claudeDir)) return "nothing";
56114
56165
  this.save(dataPath, claudeDir);
56115
56166
  return "saved";
56116
56167
  }
@@ -56170,7 +56221,7 @@ function assertName(name2) {
56170
56221
  }
56171
56222
  var AccountStore = class {
56172
56223
  /** @param sharePlugins claudeProfile.sharePluginsAcrossAccounts: carry Claude plugins to the account switched to */
56173
- constructor(storageRoot, sharePlugins = false, secrets = SecretStore.standard()) {
56224
+ constructor(storageRoot, sharePlugins = false, secrets = SecretStore.standard(storageRoot)) {
56174
56225
  this.storageRoot = storageRoot;
56175
56226
  this.sharePlugins = sharePlugins;
56176
56227
  this.secrets = secrets;
@@ -56345,7 +56396,7 @@ var AgentAccountStore = class {
56345
56396
  /**
56346
56397
  * @param loginFiles files of the login, relative to the folder they are read from and written to
56347
56398
  */
56348
- constructor(storageRoot, agent, loginFiles, secrets = SecretStore.standard()) {
56399
+ constructor(storageRoot, agent, loginFiles, secrets = SecretStore.standard(storageRoot)) {
56349
56400
  this.storageRoot = storageRoot;
56350
56401
  this.agent = agent;
56351
56402
  this.loginFiles = loginFiles;
@@ -56508,7 +56559,7 @@ function checkGithubToken(token) {
56508
56559
  return githubTokenKind(token) ?? fail("That is not a GitHub personal access token.", "Run: sandbox github login");
56509
56560
  }
56510
56561
  var GithubToken = class {
56511
- constructor(storageRoot, secrets = SecretStore.standard()) {
56562
+ constructor(storageRoot, secrets = SecretStore.standard(storageRoot)) {
56512
56563
  this.storageRoot = storageRoot;
56513
56564
  this.secrets = secrets;
56514
56565
  }
@@ -57191,6 +57242,7 @@ var MARKERS = {
57191
57242
  ],
57192
57243
  rust: ["Cargo.toml", "rust-toolchain", "rust-toolchain.toml"],
57193
57244
  msvc: ["*.vcxproj", "CMakePresets.json", "CMakeUserPresets.json"],
57245
+ unreal: ["*.uproject", "*.uplugin"],
57194
57246
  cpp: [
57195
57247
  "CMakeLists.txt",
57196
57248
  "Makefile",
@@ -57249,6 +57301,11 @@ function decideToolchains(settings, entries, pcHas, hints = {}) {
57249
57301
  on: false,
57250
57302
  reason: "would be used (" + result.msvc.reason + "); set toolchains.msvc.acceptLicense to install it"
57251
57303
  };
57304
+ if (settings.unreal.enabled === "auto" && result.unreal.on && !settings.unreal.acceptLicense)
57305
+ result.unreal = {
57306
+ on: false,
57307
+ reason: "would be used (" + result.unreal.reason + "); set toolchains.unreal.acceptLicense to install it"
57308
+ };
57252
57309
  if (result.rust.on && !result.msvc.on && !result.cpp.on && settings.cpp.enabled !== false)
57253
57310
  result.cpp = { on: true, reason: "the linker for Rust" };
57254
57311
  if (result.python.on && settings.uv.enabled === "auto" && settings.python.tools.length > 0 && !result.uv.on)
@@ -59144,7 +59201,7 @@ function loginProblem(c2) {
59144
59201
  return null;
59145
59202
  }
59146
59203
  var VpnLogin = class {
59147
- constructor(storageRoot, secrets = SecretStore.standard()) {
59204
+ constructor(storageRoot, secrets = SecretStore.standard(storageRoot)) {
59148
59205
  this.storageRoot = storageRoot;
59149
59206
  this.secrets = secrets;
59150
59207
  }
@@ -70288,7 +70345,7 @@ var TlsStore = class {
70288
70345
 
70289
70346
  // src/app/features/access/access.run.ts
70290
70347
  function accessStoreOf(ctx) {
70291
- return AccessStore.at(ctx.storageRoot, SecretStore.standard(), {
70348
+ return AccessStore.at(ctx.storageRoot, SecretStore.standard(ctx.storageRoot), {
70292
70349
  // The self-test never changes folder permissions; its storage is a temp folder.
70293
70350
  protect: selfTestRunning() ? void 0 : (dir) => protectPrivateFolder(dir)
70294
70351
  });
@@ -71073,9 +71130,10 @@ function loginItem(a, r) {
71073
71130
  ...r.isActive ? [{ text: "in the sandbox", tone: "ok" }] : [],
71074
71131
  ...r.isDefault ? [{ text: "default", tone: "accent" }] : []
71075
71132
  ],
71076
- description: "Choose it to make the sandbox use this login.",
71133
+ description: r.isActive ? "The sandbox uses this login now." : "Choose it to make the sandbox use this login.",
71077
71134
  details: ["Agent " + a.name, "Email " + (r.email ?? "-"), "Saved " + r.savedAt.slice(0, 16).replace("T", " ")],
71078
- primary: action(useAccount, { agent: a.name, name: r.name }, { verb: "use" }),
71135
+ // The login in use already offers nothing to switch to.
71136
+ ...r.isActive ? {} : { primary: action(useAccount, { agent: a.name, name: r.name }, { verb: "use" }) },
71079
71137
  actions: [
71080
71138
  action(defaultAccount, { agent: a.name, name: r.name }, { key: "d", verb: "make default", importance: 1 }),
71081
71139
  action(renameAccount, { agent: a.name, name: r.name }, { key: "r", verb: "rename", importance: 2 }),
@@ -71123,7 +71181,7 @@ function agentPage(a) {
71123
71181
  var accountsView = defineView({
71124
71182
  id: "accounts",
71125
71183
  title: "Accounts",
71126
- summary: "Logins saved on this PC, per agent. Use puts one into the sandbox; new launches start with the default.",
71184
+ summary: 'Logins saved on this PC for each agent. Every launch starts with the default; "Use this login" switches now.',
71127
71185
  queries: { accounts: listAccounts },
71128
71186
  operations: [saveAccount, useAccount, defaultAccount, renameAccount, removeAccount],
71129
71187
  build: ({ accounts }) => accountsPage(accounts)
@@ -75578,11 +75636,6 @@ var SandboxStatusSchema2 = external_exports.object({
75578
75636
  repoGuard: external_exports.object({ guarded: external_exports.number(), findings: external_exports.array(external_exports.string()) })
75579
75637
  });
75580
75638
  var MessageSchema4 = external_exports.object({ message: external_exports.string().describe("What happened") });
75581
- function helperText(answer) {
75582
- const sessions = /^pong \((\d+) session/.exec(answer);
75583
- if (!sessions) return answer;
75584
- return "responding, " + plural2(Number(sessions[1]), "session") + " running";
75585
- }
75586
75639
  function uptime(s) {
75587
75640
  return s.running && s.startedAt ? ago(s.startedAt).replace(" ago", "") : null;
75588
75641
  }
@@ -75694,7 +75747,8 @@ function facts(o) {
75694
75747
  ...s.running ? [
75695
75748
  fact("window", "Window", s.headless ? "hidden (sandbox show opens it)" : "shown"),
75696
75749
  fact("uptime", "Uptime", uptime(s) ?? "-"),
75697
- fact("helper", "Helper", s.daemon === "-" ? "-" : helperText(s.daemon)),
75750
+ // The session count is its own fact: the helper only says whether it answers.
75751
+ fact("helper", "Helper", s.daemon === "-" ? "-" : s.daemon.startsWith("pong") ? "Responding" : s.daemon),
75698
75752
  fact("sessions", "Sessions", String(sessions))
75699
75753
  ] : [],
75700
75754
  fact("account", "Account", s.account ?? "none selected (sandbox account use)"),
@@ -75723,7 +75777,7 @@ function guardSection(o) {
75723
75777
  return {
75724
75778
  id: "guard",
75725
75779
  title: "Repo guard",
75726
- summary: o.repoGuard.guarded + " folder(s) guarded; " + (findings.length ? findings.length + " change(s) neutralised:" : "nothing from the sandbox needed neutralising"),
75780
+ summary: "Watching " + plural2(o.repoGuard.guarded, "folder") + ". " + (findings.length ? plural2(findings.length, "change") + " from the sandbox were blocked:" : "Nothing from the sandbox needed blocking."),
75727
75781
  items: findings.slice(-10).map((f, i) => ({ id: "guard:" + i, label: f, tone: "warn" }))
75728
75782
  };
75729
75783
  }
@@ -75767,7 +75821,7 @@ function sandboxPage(o) {
75767
75821
  var sandboxView = defineView({
75768
75822
  id: "sandbox",
75769
75823
  title: "Sandbox",
75770
- summary: "What the sandbox is doing, its window, and stopping it.",
75824
+ summary: "Status, projects and controls of the running sandbox.",
75771
75825
  queries: { status: sandboxStatus },
75772
75826
  operations: [showWindow, hideWindow, stopSandbox],
75773
75827
  build: ({ status }) => sandboxPage(status)
@@ -76703,7 +76757,7 @@ function sessionsPage(o) {
76703
76757
  var sessionsView = defineView({
76704
76758
  id: "sessions",
76705
76759
  title: "Sessions",
76706
- summary: "The sessions of the running sandbox: open one, restart the agents, open a shell, close one.",
76760
+ summary: "Agent and shell sessions in the running sandbox.",
76707
76761
  queries: { sessions: listSessions },
76708
76762
  operations: [restartSessions, startShell, closeSession],
76709
76763
  build: ({ sessions }) => sessionsPage(sessions)
@@ -78020,27 +78074,37 @@ function navViews(nav) {
78020
78074
  return navSections(nav).flatMap((n) => [...n.view ? [n.view] : [], ...(n.embeds ?? []).map((e) => e.view)]);
78021
78075
  }
78022
78076
  var NAV = [
78023
- { label: "Agents", view: "agents" },
78024
- { label: "Sessions", view: "sessions" },
78025
- { label: "Activity", view: "audit" },
78077
+ {
78078
+ label: "Workspace",
78079
+ children: [
78080
+ { label: "Agents", view: "agents" },
78081
+ { label: "Sessions", view: "sessions" },
78082
+ { label: "Activity", view: "audit" }
78083
+ ]
78084
+ },
78026
78085
  {
78027
78086
  label: "Sandbox",
78028
78087
  children: [
78029
- { label: "Status", view: "sandbox" },
78030
- { label: "Projects", view: "projects" }
78088
+ { label: "Overview", view: "sandbox" },
78089
+ { label: "Projects", view: "projects" },
78090
+ { label: "Apps", view: "apps" }
78031
78091
  ]
78032
78092
  },
78033
- { label: "Apps", view: "apps" },
78034
78093
  {
78035
- label: "Settings",
78036
- view: "settings",
78037
- embeds: [
78038
- { label: "VPN", view: "vpn" },
78039
- { label: "GitHub", view: "github" }
78094
+ label: "Configuration",
78095
+ children: [
78096
+ {
78097
+ label: "Settings",
78098
+ view: "settings",
78099
+ embeds: [
78100
+ { label: "VPN", view: "vpn" },
78101
+ { label: "GitHub", view: "github" }
78102
+ ]
78103
+ },
78104
+ { label: "Accounts", view: "accounts" },
78105
+ { label: "Remote access", view: "access" }
78040
78106
  ]
78041
78107
  },
78042
- { label: "Accounts", view: "accounts" },
78043
- { label: "Remote access", view: "access" },
78044
78108
  {
78045
78109
  label: "Maintenance",
78046
78110
  children: [
@@ -78290,7 +78354,7 @@ async function attachOpened(ctx, session) {
78290
78354
  nextStep("sandbox attach " + session);
78291
78355
  return 0;
78292
78356
  }
78293
- const { attach: attach2 } = await import("./chunk-RQ4BWBYX.js");
78357
+ const { attach: attach2 } = await import("./chunk-XYUVIVJT.js");
78294
78358
  return attach2({ ...ctx, options: { ...ctx.options, positionals: [session] } });
78295
78359
  }
78296
78360
  function definitionHandler(def) {
@@ -78382,7 +78446,7 @@ function remoteAudit(ctx) {
78382
78446
  };
78383
78447
  }
78384
78448
  function remoteGate(ctx) {
78385
- const secrets = SecretStore.standard();
78449
+ const secrets = SecretStore.standard(ctx.storageRoot);
78386
78450
  let ownerSid = "";
78387
78451
  try {
78388
78452
  ownerSid = currentUserSid();
@@ -1,7 +1,7 @@
1
1
  import { createRequire as __createRequire } from 'node:module'; const require = __createRequire(import.meta.url);
2
2
  import {
3
3
  attach
4
- } from "./chunk-D7CM63BS.js";
4
+ } from "./chunk-REE3EMHD.js";
5
5
  export {
6
6
  attach
7
7
  };
@@ -2,8 +2,8 @@
2
2
  import { createRequire as __createRequire } from 'node:module'; const require = __createRequire(import.meta.url);
3
3
  import {
4
4
  run
5
- } from "./chunk-HTQT5SBY.js";
6
- import "./chunk-D7CM63BS.js";
5
+ } from "./chunk-QLGGAG3D.js";
6
+ import "./chunk-REE3EMHD.js";
7
7
 
8
8
  // src/bin/claude-host.ts
9
9
  var args = process.argv.slice(2);
@@ -2,8 +2,8 @@
2
2
  import { createRequire as __createRequire } from 'node:module'; const require = __createRequire(import.meta.url);
3
3
  import {
4
4
  run
5
- } from "./chunk-HTQT5SBY.js";
6
- import "./chunk-D7CM63BS.js";
5
+ } from "./chunk-QLGGAG3D.js";
6
+ import "./chunk-REE3EMHD.js";
7
7
 
8
8
  // src/bin/claude-sbx.ts
9
9
  run(process.argv.slice(2), { harness: "claude" });
@@ -2,8 +2,8 @@
2
2
  import { createRequire as __createRequire } from 'node:module'; const require = __createRequire(import.meta.url);
3
3
  import {
4
4
  run
5
- } from "./chunk-HTQT5SBY.js";
6
- import "./chunk-D7CM63BS.js";
5
+ } from "./chunk-QLGGAG3D.js";
6
+ import "./chunk-REE3EMHD.js";
7
7
 
8
8
  // src/bin/codex-sbx.ts
9
9
  run(process.argv.slice(2), { harness: "codex" });
@@ -2,8 +2,8 @@
2
2
  import { createRequire as __createRequire } from 'node:module'; const require = __createRequire(import.meta.url);
3
3
  import {
4
4
  run
5
- } from "./chunk-HTQT5SBY.js";
6
- import "./chunk-D7CM63BS.js";
5
+ } from "./chunk-QLGGAG3D.js";
6
+ import "./chunk-REE3EMHD.js";
7
7
 
8
8
  // src/bin/gemini-sbx.ts
9
9
  run(process.argv.slice(2), { harness: "gemini" });
@@ -2,8 +2,8 @@
2
2
  import { createRequire as __createRequire } from 'node:module'; const require = __createRequire(import.meta.url);
3
3
  import {
4
4
  run
5
- } from "./chunk-HTQT5SBY.js";
6
- import "./chunk-D7CM63BS.js";
5
+ } from "./chunk-QLGGAG3D.js";
6
+ import "./chunk-REE3EMHD.js";
7
7
 
8
8
  // src/bin/sandbox.ts
9
9
  run(process.argv.slice(2));
@@ -2,8 +2,8 @@
2
2
  import { createRequire as __createRequire } from 'node:module'; const require = __createRequire(import.meta.url);
3
3
  import {
4
4
  run
5
- } from "./chunk-HTQT5SBY.js";
6
- import "./chunk-D7CM63BS.js";
5
+ } from "./chunk-QLGGAG3D.js";
6
+ import "./chunk-REE3EMHD.js";
7
7
 
8
8
  // src/bin/sbx-hide.ts
9
9
  run(process.argv.slice(2), { command: "hide" });
@@ -2,8 +2,8 @@
2
2
  import { createRequire as __createRequire } from 'node:module'; const require = __createRequire(import.meta.url);
3
3
  import {
4
4
  run
5
- } from "./chunk-HTQT5SBY.js";
6
- import "./chunk-D7CM63BS.js";
5
+ } from "./chunk-QLGGAG3D.js";
6
+ import "./chunk-REE3EMHD.js";
7
7
 
8
8
  // src/bin/sbx-show.ts
9
9
  run(process.argv.slice(2), { command: "show" });
@@ -95,7 +95,7 @@ function remoteAudit(ctx) {
95
95
  * served (it refuses everyone while web.remote.windowsSignIn is off).
96
96
  */
97
97
  function remoteGate(ctx) {
98
- const secrets = SecretStore.standard();
98
+ const secrets = SecretStore.standard(ctx.storageRoot);
99
99
  let ownerSid = '';
100
100
  try {
101
101
  ownerSid = currentUserSid();
package/dist/cli/index.js CHANGED
@@ -12,7 +12,7 @@ import { takeHiddenInstallNotice } from '../host/install/index.js';
12
12
  import { c } from './output/index.js';
13
13
  import { dropInheritedModulePath } from '../shared/system.js';
14
14
  import { withConfig, hoistConfig, withoutConfig } from './argv.js';
15
- import { report } from './report.js';
15
+ import { recordUnexpected, report } from './report.js';
16
16
  import { versionText } from './version.js';
17
17
  import { HANDLERS } from './commands/index.js';
18
18
  import { definitionHandler, operationHandler } from './operations/index.js';
@@ -121,6 +121,9 @@ export async function main(rawArgv, preset = {}) {
121
121
  }
122
122
  export function run(argv, preset) {
123
123
  dropInheritedModulePath();
124
+ // Background work that throws must not end the menu or the web server: the error is kept with its stack instead.
125
+ process.on('unhandledRejection', (e) => recordUnexpected(e, 'unhandled rejection'));
126
+ process.on('uncaughtException', (e) => recordUnexpected(e, 'uncaught exception'));
124
127
  main(argv, preset).then((code) => {
125
128
  process.exitCode = code;
126
129
  }, (e) => {
@@ -1,2 +1,9 @@
1
+ /** Where errors nobody caught are written in full (this user's temp folder; kept under 1 MB). */
2
+ export declare const UNEXPECTED_LOG: string;
3
+ /**
4
+ * An error thrown by background work (a timer, a promise nobody awaited): written in full to UNEXPECTED_LOG and named
5
+ * in one line, instead of ending the menu, the web server or a command half-way.
6
+ */
7
+ export declare function recordUnexpected(e: unknown, kind: string): void;
1
8
  /** Prints a problem the way a user can act on it: what happened, how to fix it; stack traces only for real bugs. */
2
9
  export declare function report(e: unknown): number;
@@ -1,6 +1,37 @@
1
+ import fs from 'node:fs';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
1
4
  import { CommanderError } from 'commander';
2
5
  import { c } from './output/index.js';
3
6
  import { UserError, errMsg, plainText } from '../core/util/index.js';
7
+ /** Where errors nobody caught are written in full (this user's temp folder; kept under 1 MB). */
8
+ export const UNEXPECTED_LOG = path.join(os.tmpdir(), 'windows-development-sandbox-errors.log');
9
+ const UNEXPECTED_LOG_MAX = 1024 * 1024;
10
+ /**
11
+ * An error thrown by background work (a timer, a promise nobody awaited): written in full to UNEXPECTED_LOG and named
12
+ * in one line, instead of ending the menu, the web server or a command half-way.
13
+ */
14
+ export function recordUnexpected(e, kind) {
15
+ const text = new Date().toISOString() +
16
+ ' ' +
17
+ kind +
18
+ ' (pid ' +
19
+ process.pid +
20
+ ', ' +
21
+ process.argv.slice(2).join(' ') +
22
+ ')\n' +
23
+ (e instanceof Error && e.stack ? e.stack : errMsg(e)) +
24
+ '\n\n';
25
+ try {
26
+ if (fs.existsSync(UNEXPECTED_LOG) && fs.statSync(UNEXPECTED_LOG).size > UNEXPECTED_LOG_MAX)
27
+ fs.rmSync(UNEXPECTED_LOG, { force: true });
28
+ fs.appendFileSync(UNEXPECTED_LOG, text);
29
+ }
30
+ catch {
31
+ // Nowhere to write it: the line below still says what happened.
32
+ }
33
+ console.error(c.red('Unexpected error (kept running): ') + plainText(errMsg(e)) + c.dim(' Details: ' + UNEXPECTED_LOG));
34
+ }
4
35
  /** Prints a problem the way a user can act on it: what happened, how to fix it; stack traces only for real bugs. */
5
36
  export function report(e) {
6
37
  if (e instanceof CommanderError)
@@ -62,6 +62,7 @@ export const DEFAULTS = {
62
62
  linkSlots: 'auto',
63
63
  },
64
64
  msvc: { enabled: 'auto', acceptLicense: false, msvcVersion: 'latest', sdkVersion: 'latest' },
65
+ unreal: { enabled: 'auto', acceptLicense: false, components: [], keepInstallFiles: true },
65
66
  cpp: { enabled: 'auto', flexBison: true },
66
67
  dotnet: {
67
68
  enabled: 'auto',
@@ -4,6 +4,7 @@ export declare const MARKERS: {
4
4
  readonly python: readonly ["pyproject.toml", "requirements.txt", "setup.py", "setup.cfg", "Pipfile", "uv.lock", "poetry.lock", ".python-version", "*.py", "*.ipynb"];
5
5
  readonly rust: readonly ["Cargo.toml", "rust-toolchain", "rust-toolchain.toml"];
6
6
  readonly msvc: readonly ["*.vcxproj", "CMakePresets.json", "CMakeUserPresets.json"];
7
+ readonly unreal: readonly ["*.uproject", "*.uplugin"];
7
8
  readonly cpp: readonly ["CMakeLists.txt", "Makefile", "meson.build", "premake5.lua", "Cargo.toml", "*.vcxproj", "*.c", "*.cpp", "*.cc", "*.h", "*.hpp"];
8
9
  readonly dotnet: readonly ["*.sln", "*.slnx", "*.csproj", "*.fsproj", "*.vbproj", "global.json", "*.cs"];
9
10
  readonly go: readonly ["go.mod", "go.work", "*.go"];
@@ -15,6 +15,7 @@ export const MARKERS = {
15
15
  ],
16
16
  rust: ['Cargo.toml', 'rust-toolchain', 'rust-toolchain.toml'],
17
17
  msvc: ['*.vcxproj', 'CMakePresets.json', 'CMakeUserPresets.json'],
18
+ unreal: ['*.uproject', '*.uplugin'],
18
19
  cpp: [
19
20
  'CMakeLists.txt',
20
21
  'Makefile',
@@ -86,6 +87,12 @@ export function decideToolchains(settings, entries, pcHas, hints = {}) {
86
87
  on: false,
87
88
  reason: 'would be used (' + result.msvc.reason + '); set toolchains.msvc.acceptLicense to install it',
88
89
  };
90
+ // Unreal is a ~10 GB licensed install every boot: "auto" only installs it once its licence is accepted.
91
+ if (settings.unreal.enabled === 'auto' && result.unreal.on && !settings.unreal.acceptLicense)
92
+ result.unreal = {
93
+ on: false,
94
+ reason: 'would be used (' + result.unreal.reason + '); set toolchains.unreal.acceptLicense to install it',
95
+ };
89
96
  if (result.rust.on && !result.msvc.on && !result.cpp.on && settings.cpp.enabled !== false)
90
97
  result.cpp = { on: true, reason: 'the linker for Rust' };
91
98
  // uv installs the Python tools (`uv tool install`), so Python tools turn an "auto" uv on.
@@ -153,6 +153,12 @@ export declare const ConfigSchema: z.ZodObject<{
153
153
  msvcVersion: z.ZodString;
154
154
  sdkVersion: z.ZodString;
155
155
  }, z.core.$strict>;
156
+ unreal: z.ZodObject<{
157
+ enabled: z.ZodUnion<readonly [z.ZodBoolean, z.ZodLiteral<"auto">]>;
158
+ acceptLicense: z.ZodBoolean;
159
+ components: z.ZodArray<z.ZodString>;
160
+ keepInstallFiles: z.ZodBoolean;
161
+ }, z.core.$strict>;
156
162
  cpp: z.ZodObject<{
157
163
  enabled: z.ZodUnion<readonly [z.ZodBoolean, z.ZodLiteral<"auto">]>;
158
164
  flexBison: z.ZodBoolean;
@@ -506,6 +512,21 @@ export declare const LocalOverridesSchema: z.ZodObject<{
506
512
  zstd: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodLiteral<"auto">]>>;
507
513
  openssl: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodLiteral<"auto">]>>;
508
514
  }, z.core.$strict>>;
515
+ unreal: z.ZodOptional<z.ZodObject<{
516
+ enabled: z.ZodOptional<z.ZodBoolean>;
517
+ }, z.core.$strict> | z.ZodObject<{
518
+ version: z.ZodOptional<z.ZodString>;
519
+ extraVersions: z.ZodOptional<z.ZodArray<z.ZodString>>;
520
+ corepack: z.ZodOptional<z.ZodBoolean>;
521
+ volta: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodLiteral<"auto">]>>;
522
+ globalPackages: z.ZodOptional<z.ZodArray<z.ZodString>>;
523
+ }, z.core.$strict> | z.ZodObject<{
524
+ enabled: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodLiteral<"auto">]>>;
525
+ }, z.core.$strict> | z.ZodObject<{
526
+ gh: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodLiteral<"auto">]>>;
527
+ zstd: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodLiteral<"auto">]>>;
528
+ openssl: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodLiteral<"auto">]>>;
529
+ }, z.core.$strict>>;
509
530
  cpp: z.ZodOptional<z.ZodObject<{
510
531
  enabled: z.ZodOptional<z.ZodBoolean>;
511
532
  }, z.core.$strict> | z.ZodObject<{
@@ -51,7 +51,7 @@ export const SandboxSchema = z.strictObject({
51
51
  .describe('When the sandbox (or this PC) runs low on memory, slow builds down in steps, like a PC that gets slower instead of failing: build programs get lower CPU and memory priority, then less CPU, then compilers take turns (the waiting ones briefly suspended and trimmed to the paging file). Nothing is stopped or killed, and the agents themselves are never touched. Yours only: a project file cannot change it.'),
52
52
  vGpu: z
53
53
  .boolean()
54
- .describe("Virtual GPU. Off by default: Microsoft notes it widens the sandbox attack surface. The sandbox then uses this PC's graphics card, and with some graphics drivers heavy use can freeze the whole PC: turn it on only when a program in the sandbox needs the GPU (the mesa toolchain gives software OpenGL without it)."),
54
+ .describe("Virtual GPU. Off by default: Microsoft notes it widens the sandbox attack surface. The sandbox then uses this PC's graphics card, and with some graphics drivers heavy use can freeze the whole PC: turn it on only when a program in the sandbox needs the GPU (the mesa toolchain gives software OpenGL without it). Browsers in the sandbox never use it (they draw in software)."),
55
55
  networking: z.boolean().describe('Internet access. false is the only hard network isolation Windows Sandbox offers.'),
56
56
  clipboard: z
57
57
  .boolean()
@@ -34,6 +34,12 @@ export declare const ToolchainsSchema: z.ZodObject<{
34
34
  msvcVersion: z.ZodString;
35
35
  sdkVersion: z.ZodString;
36
36
  }, z.core.$strict>;
37
+ unreal: z.ZodObject<{
38
+ enabled: z.ZodUnion<readonly [z.ZodBoolean, z.ZodLiteral<"auto">]>;
39
+ acceptLicense: z.ZodBoolean;
40
+ components: z.ZodArray<z.ZodString>;
41
+ keepInstallFiles: z.ZodBoolean;
42
+ }, z.core.$strict>;
37
43
  cpp: z.ZodObject<{
38
44
  enabled: z.ZodUnion<readonly [z.ZodBoolean, z.ZodLiteral<"auto">]>;
39
45
  flexBison: z.ZodBoolean;
@@ -53,6 +53,16 @@ export const ToolchainsSchema = z.strictObject({
53
53
  msvcVersion: z.string().min(1).describe('"latest" or an MSVC tools version prefix, e.g. "14.44".'),
54
54
  sdkVersion: z.string().min(1).describe('"latest" or a Windows SDK build, e.g. "26100" or "10.0.22621".'),
55
55
  }),
56
+ unreal: z.strictObject({
57
+ enabled: enabled('Unreal Engine development: Visual Studio Community 2022 with the C++ game workload (MSVC, a Windows 11 SDK, .NET Framework targeting packs, Unreal integration and the Epic Games Launcher) and the DirectX End-User Runtime. A real installer: it runs every boot (~10 GB on the sandbox disk). The engine itself comes from the Epic Games Launcher or its source.', 'a .uproject or .uplugin'),
58
+ acceptLicense: z
59
+ .boolean()
60
+ .describe('Required to install: you accept the Visual Studio Community licence terms (https://visualstudio.microsoft.com/license-terms/) and the DirectX End-User Runtime licence.'),
61
+ components: StringsSchema.describe('More Visual Studio workload or component IDs to add, e.g. ["Microsoft.VisualStudio.Component.VC.Llvm.Clang"].'),
62
+ keepInstallFiles: z
63
+ .boolean()
64
+ .describe('Keep an offline copy of the Visual Studio install files in tools\\visualstudio (~10 GB, made once), so later boots install without downloading them again. Skipped while that disk has too little room.'),
65
+ }),
56
66
  cpp: z.strictObject({
57
67
  enabled: enabled("MinGW-w64 GCC with CMake, Ninja, premake5 and mingw32-make (also Rust's linker).", 'CMakeLists.txt, Makefile, meson.build, premake5.lua or Cargo.toml'),
58
68
  flexBison: z