windows-development-sandbox 1.13.2 → 1.14.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 (91) hide show
  1. package/dist/app/features/storage/storage.run.js +1 -1
  2. package/dist/bin/agent-sandbox.js +2 -2
  3. package/dist/bin/{chunk-VENWSGX2.js → chunk-BQ23GBLC.js} +411 -58
  4. package/dist/bin/{chunk-K4SU3MHJ.js → chunk-F7IMME5G.js} +1 -1
  5. package/dist/bin/{chunk-IL7BXJF2.js → chunk-LQHEUN2A.js} +1 -1
  6. package/dist/bin/claude-host.js +2 -2
  7. package/dist/bin/claude-sbx.js +2 -2
  8. package/dist/bin/codex-sbx.js +2 -2
  9. package/dist/bin/gemini-sbx.js +2 -2
  10. package/dist/bin/sandbox.js +2 -2
  11. package/dist/bin/sbx-hide.js +2 -2
  12. package/dist/bin/sbx-show.js +2 -2
  13. package/dist/cli/commands/launch/launch.js +1 -1
  14. package/dist/cli/commands/repair/checks/contained.js +1 -1
  15. package/dist/guest/daemon/sessions.d.ts +7 -0
  16. package/dist/guest/daemon/sessions.js +23 -2
  17. package/dist/guest/entries/daemon.js +1 -0
  18. package/dist/guest/sessions/pty-host.d.ts +10 -0
  19. package/dist/guest/sessions/pty-host.js +48 -3
  20. package/dist/host/channel/channel.js +1 -1
  21. package/dist/host/install/install-record.d.ts +1 -0
  22. package/dist/host/install/install-record.js +2 -0
  23. package/dist/host/sandbox/contained-deps.d.ts +2 -1
  24. package/dist/host/sandbox/contained-deps.js +6 -4
  25. package/dist/host/sharing/lifecycle.js +9 -1
  26. package/dist/host/storage/contained/contained-storage.js +13 -0
  27. package/dist/host/storage/contained/disk-port.d.ts +5 -0
  28. package/dist/host/storage/contained/helper-port.d.ts +72 -0
  29. package/dist/host/storage/contained/helper-port.js +162 -0
  30. package/dist/host/storage/contained/index.d.ts +1 -0
  31. package/dist/host/storage/contained/index.js +1 -0
  32. package/dist/host/web/bridge.d.ts +5 -0
  33. package/dist/host/web/bridge.js +11 -1
  34. package/dist/host/web/server.d.ts +2 -0
  35. package/dist/host/web/server.js +11 -1
  36. package/dist/runtime/agent.js +9 -0
  37. package/dist/runtime/daemon.js +57 -3
  38. package/dist/runtime/db.js +9 -0
  39. package/dist/runtime/main.js +195 -12
  40. package/dist/runtime/netproxy.js +9 -0
  41. package/dist/runtime/view.js +195 -12
  42. package/dist/selftest/channel.test.js +72 -1
  43. package/dist/selftest/contained.test.js +1 -37
  44. package/dist/selftest/hcs.test.js +5 -2
  45. package/dist/selftest/index.js +2 -0
  46. package/dist/selftest/install.test.js +11 -0
  47. package/dist/selftest/live-daemon.test.js +51 -1
  48. package/dist/selftest/live-terminal.test.js +112 -4
  49. package/dist/selftest/storage-helper.test.d.ts +2 -0
  50. package/dist/selftest/storage-helper.test.js +493 -0
  51. package/dist/selftest/support/vhdx.d.ts +14 -0
  52. package/dist/selftest/support/vhdx.js +72 -0
  53. package/dist/selftest/web.test.js +14 -2
  54. package/dist/shared/channel/run-dir.d.ts +6 -0
  55. package/dist/shared/channel/run-dir.js +9 -0
  56. package/dist/shared/pty/index.d.ts +3 -1
  57. package/dist/shared/pty/index.js +3 -1
  58. package/dist/shared/pty/key-gate.d.ts +24 -0
  59. package/dist/shared/pty/key-gate.js +45 -0
  60. package/dist/shared/pty/mouse-modes.d.ts +27 -0
  61. package/dist/shared/pty/mouse-modes.js +155 -0
  62. package/dist/shared/pty/terminal.d.ts +0 -5
  63. package/dist/shared/pty/terminal.js +51 -20
  64. package/dist/sync/watcher.js +1 -1
  65. package/dist/web/index.html +1 -1
  66. package/dist/web/{main-NBQ3LF4E.js → main-6XNBTJLT.js} +33 -33
  67. package/native/csharp/HcsHost/AssemblyInfo.cs +3 -3
  68. package/native/csharp/HostTools/AssemblyInfo.cs +3 -3
  69. package/native/csharp/StorageHelper/AssemblyInfo.cs +15 -0
  70. package/native/csharp/StorageHelper/Client.cs +297 -0
  71. package/native/csharp/StorageHelper/Log.cs +177 -0
  72. package/native/csharp/StorageHelper/MountRecords.cs +160 -0
  73. package/native/csharp/StorageHelper/Operations.cs +596 -0
  74. package/native/csharp/StorageHelper/Pins.cs +270 -0
  75. package/native/csharp/StorageHelper/Privileges.cs +78 -0
  76. package/native/csharp/StorageHelper/Program.cs +349 -0
  77. package/native/csharp/StorageHelper/Protocol.cs +107 -0
  78. package/native/csharp/StorageHelper/RateLimiter.cs +97 -0
  79. package/native/csharp/StorageHelper/Rules.cs +156 -0
  80. package/native/csharp/StorageHelper/Server.cs +656 -0
  81. package/native/csharp/StorageHelper/Settings.cs +297 -0
  82. package/native/csharp/StorageHelper/StorageService.cs +53 -0
  83. package/native/csharp/StorageHelper/UserSettings.cs +206 -0
  84. package/native/csharp/StorageHelper/Vhdx.cs +230 -0
  85. package/native/csharp/StorageHelper/VirtualDisk.cs +241 -0
  86. package/native/csharp/StorageHelper/Volumes.cs +379 -0
  87. package/native/csharp/StorageHelper/Watchdog.cs +128 -0
  88. package/native/csharp/StorageHelper/Win32.cs +250 -0
  89. package/native/scripts/set-storage-helper.ps1 +203 -0
  90. package/package.json +1 -1
  91. package/sandbox/csharp/PtyHost.cs +236 -1
@@ -20056,7 +20056,7 @@ var require_websocket = __commonJS({
20056
20056
  var EventEmitter = __require("events");
20057
20057
  var https2 = __require("https");
20058
20058
  var http3 = __require("http");
20059
- var net7 = __require("net");
20059
+ var net8 = __require("net");
20060
20060
  var tls = __require("tls");
20061
20061
  var { randomBytes, createHash } = __require("crypto");
20062
20062
  var { Duplex, Readable: Readable2 } = __require("stream");
@@ -20813,12 +20813,12 @@ var require_websocket = __commonJS({
20813
20813
  }
20814
20814
  function netConnect(options) {
20815
20815
  options.path = options.socketPath;
20816
- return net7.connect(options);
20816
+ return net8.connect(options);
20817
20817
  }
20818
20818
  function tlsConnect(options) {
20819
20819
  options.path = void 0;
20820
20820
  if (!options.servername && options.servername !== "") {
20821
- options.servername = net7.isIP(options.host) ? "" : options.host;
20821
+ options.servername = net8.isIP(options.host) ? "" : options.host;
20822
20822
  }
20823
20823
  return tls.connect(options);
20824
20824
  }
@@ -51219,6 +51219,14 @@ var ContainedStorage = class {
51219
51219
  userSid: this.deps.userSid()
51220
51220
  });
51221
51221
  this.requireOk(outcome, "mount");
51222
+ } else {
51223
+ const confirmed = await this.deps.port.confirmMounted?.({
51224
+ paths,
51225
+ maxGb: this.deps.maxGb,
51226
+ create: false,
51227
+ userSid: this.deps.userSid()
51228
+ });
51229
+ if (confirmed) this.requireOk(confirmed, "mount");
51222
51230
  }
51223
51231
  this.assertReallyMounted();
51224
51232
  for (const name2 of CONTAINED_FOLDERS) fs18.mkdirSync(path26.join(paths.mount, name2), { recursive: true });
@@ -51473,6 +51481,138 @@ function safeJson(text) {
51473
51481
  }
51474
51482
  }
51475
51483
 
51484
+ // src/host/storage/contained/helper-port.ts
51485
+ import net from "node:net";
51486
+ var HELPER_PIPE = "\\\\.\\pipe\\AgentSandboxStorage";
51487
+ var HELPER_PROTOCOL = 1;
51488
+ var MOUNT_TIMEOUT_MS2 = 10 * 6e4;
51489
+ var MAX_ANSWER_BYTES = 64 * 1024;
51490
+ var HelperRequestSchema = external_exports.object({ verb: external_exports.enum(["mount", "dismount"]), set: external_exports.string().regex(SET_NAME_RULE) }).strict();
51491
+ var HelperAnswerSchema = external_exports.object({
51492
+ protocol: external_exports.number().int(),
51493
+ ok: external_exports.boolean(),
51494
+ message: external_exports.string().max(4e3),
51495
+ created: external_exports.boolean(),
51496
+ alreadyMounted: external_exports.boolean(),
51497
+ fallback: external_exports.boolean(),
51498
+ image: external_exports.string().max(1e3),
51499
+ mount: external_exports.string().max(1e3),
51500
+ notes: external_exports.array(external_exports.string().max(1e3)).max(50)
51501
+ });
51502
+ var ABSENT_CODES = /* @__PURE__ */ new Set(["ENOENT", "EACCES", "EPERM", "ECONNREFUSED"]);
51503
+ var pipeConnector = (pipe2, line, timeoutMs) => new Promise((resolve) => {
51504
+ let text = "";
51505
+ let connected = false;
51506
+ let settled = false;
51507
+ const done = (result) => {
51508
+ if (settled) return;
51509
+ settled = true;
51510
+ clearTimeout(timer);
51511
+ socket.destroy();
51512
+ resolve(result);
51513
+ };
51514
+ const socket = net.connect(pipe2);
51515
+ const timer = setTimeout(
51516
+ () => done({ kind: "failed", reason: "the storage helper did not answer in time" }),
51517
+ timeoutMs
51518
+ );
51519
+ socket.setEncoding("utf8");
51520
+ socket.on("connect", () => {
51521
+ connected = true;
51522
+ socket.write(line + "\n");
51523
+ });
51524
+ socket.on("data", (chunk) => {
51525
+ text += chunk;
51526
+ const end = text.indexOf("\n");
51527
+ if (end >= 0) done({ kind: "answer", text: text.slice(0, end) });
51528
+ else if (text.length > MAX_ANSWER_BYTES) done({ kind: "failed", reason: "the storage helper answered too much" });
51529
+ });
51530
+ socket.on("error", (e) => {
51531
+ if (!connected && ABSENT_CODES.has(e.code ?? "")) done({ kind: "absent", reason: e.code ?? "no pipe" });
51532
+ else done({ kind: "failed", reason: e.message });
51533
+ });
51534
+ socket.on("close", () => done({ kind: "failed", reason: "the storage helper closed the connection" }));
51535
+ });
51536
+ function helperApplies(env, configOverridden) {
51537
+ if (configOverridden) return false;
51538
+ return !env.AGENT_SANDBOX_CONFIG && !env.AGENT_SANDBOX_HOME;
51539
+ }
51540
+ function samePaths(answer, paths) {
51541
+ const norm3 = (p) => p.replace(/\\+$/, "").toLowerCase();
51542
+ return norm3(answer.image) === norm3(paths.image) && norm3(answer.mount) === norm3(paths.mount);
51543
+ }
51544
+ function readAnswer(result, paths) {
51545
+ if (result.kind === "absent") return { use: "fallback", why: "no storage helper (" + result.reason + ")" };
51546
+ if (result.kind === "failed")
51547
+ return { use: "outcome", outcome: failed("The storage helper did not finish: " + result.reason + ".") };
51548
+ let raw;
51549
+ try {
51550
+ raw = JSON.parse(result.text);
51551
+ } catch {
51552
+ return { use: "fallback", why: "the storage helper gave an answer that is not JSON" };
51553
+ }
51554
+ const parsed = HelperAnswerSchema.safeParse(raw);
51555
+ if (!parsed.success || parsed.data.protocol !== HELPER_PROTOCOL)
51556
+ return { use: "fallback", why: "the storage helper speaks another version" };
51557
+ const answer = parsed.data;
51558
+ if (answer.fallback) return { use: "fallback", why: answer.message };
51559
+ if (answer.ok && !samePaths(answer, paths))
51560
+ return {
51561
+ use: "outcome",
51562
+ outcome: failed(
51563
+ "The storage helper used other folders (" + answer.mount + ") than this command (" + paths.mount + "): your settings differ from what it reads. Run sandbox doctor."
51564
+ )
51565
+ };
51566
+ return {
51567
+ use: "outcome",
51568
+ outcome: {
51569
+ ok: answer.ok,
51570
+ message: answer.message,
51571
+ created: answer.created,
51572
+ alreadyMounted: answer.alreadyMounted,
51573
+ notes: answer.notes
51574
+ }
51575
+ };
51576
+ }
51577
+ function failed(message) {
51578
+ return { ok: false, message, created: false, alreadyMounted: false, notes: [] };
51579
+ }
51580
+ var HelperDiskPort = class {
51581
+ constructor(fallback, applies, connect2 = pipeConnector, pipe2 = HELPER_PIPE) {
51582
+ this.fallback = fallback;
51583
+ this.applies = applies;
51584
+ this.connect = connect2;
51585
+ this.pipe = pipe2;
51586
+ }
51587
+ fallback;
51588
+ applies;
51589
+ connect;
51590
+ pipe;
51591
+ async mount(request2) {
51592
+ const answer = await this.ask({ verb: "mount", set: request2.paths.set }, request2.paths);
51593
+ return answer ?? this.fallback.mount(request2);
51594
+ }
51595
+ /** The helper checks a mount without a prompt (it is "mounted already" only for this image); null without it. */
51596
+ confirmMounted(request2) {
51597
+ return this.ask({ verb: "mount", set: request2.paths.set }, request2.paths);
51598
+ }
51599
+ async dismount(request2) {
51600
+ const answer = await this.ask({ verb: "dismount", set: request2.paths.set }, request2.paths);
51601
+ return answer ?? this.fallback.dismount(request2);
51602
+ }
51603
+ reality(paths) {
51604
+ return this.fallback.reality(paths);
51605
+ }
51606
+ /** The helper's outcome, or null to use the permission prompt. */
51607
+ async ask(request2, paths) {
51608
+ if (!this.applies) return null;
51609
+ if (this.pipe === HELPER_PIPE) refuseInSelfTest("mount or dismount a disk image");
51610
+ const line = JSON.stringify(HelperRequestSchema.parse(request2));
51611
+ const read = readAnswer(await this.connect(this.pipe, line, MOUNT_TIMEOUT_MS2), paths);
51612
+ return read.use === "outcome" ? read.outcome : null;
51613
+ }
51614
+ };
51615
+
51476
51616
  // src/host/storage/contained/resolve.ts
51477
51617
  import fs21 from "node:fs";
51478
51618
  import path29 from "node:path";
@@ -52959,6 +53099,11 @@ var RunDir = class {
52959
53099
  projectsDir;
52960
53100
  /** audit.processes: programs the sandbox started, in small .jsonl files the host takes in and deletes. */
52961
53101
  auditDir;
53102
+ /**
53103
+ * Each running session's process tree, recorded by its ptyhost (guest only: the daemon ends what a ptyhost that was
53104
+ * itself ended left running).
53105
+ */
53106
+ treesDir;
52962
53107
  /** One-shot secrets the host hands over (see secrets.ts). Next to run, not in it: shared-use users can change run. */
52963
53108
  secretsDir;
52964
53109
  constructor(dataDir) {
@@ -52972,6 +53117,7 @@ var RunDir = class {
52972
53117
  this.sessionsDir = path41.join(this.root, "sessions");
52973
53118
  this.projectsDir = path41.join(this.root, "projects");
52974
53119
  this.auditDir = path41.join(this.root, "audit");
53120
+ this.treesDir = path41.join(this.root, "trees");
52975
53121
  this.secretsDir = path41.join(dataDir, "secrets");
52976
53122
  }
52977
53123
  requestFile(id) {
@@ -52983,6 +53129,9 @@ var RunDir = class {
52983
53129
  sessionFile(id) {
52984
53130
  return path41.join(this.sessionsDir, safeId(id) + ".json");
52985
53131
  }
53132
+ treeFile(id) {
53133
+ return path41.join(this.treesDir, safeId(id) + ".json");
53134
+ }
52986
53135
  projectFile(name2) {
52987
53136
  return path41.join(this.projectsDir, safeId(name2) + ".json");
52988
53137
  }
@@ -53218,6 +53367,8 @@ var InstallRecordSchema = external_exports.object({
53218
53367
  scope: external_exports.enum(["user", "machine"]),
53219
53368
  groupSid: external_exports.string().nullable(),
53220
53369
  autoUpdate: external_exports.boolean(),
53370
+ /** All-users installs: the storage helper service (AgentSandboxStorage) was installed. Older records have none. */
53371
+ storageHelper: external_exports.boolean().optional(),
53221
53372
  installedAt: external_exports.string()
53222
53373
  });
53223
53374
  var InstallRecordFile = class {
@@ -53452,7 +53603,15 @@ function beginSharing(deps, start, now2 = /* @__PURE__ */ new Date()) {
53452
53603
  deps.records.write({ ...start, startedAt: start.startedAt ?? now2.toISOString() });
53453
53604
  if (!opens(deps, start.dataPath)) return;
53454
53605
  const run3 = new RunDir(start.dataPath);
53455
- for (const dir of [run3.root, run3.requestsDir, run3.responsesDir, run3.sessionsDir, run3.projectsDir, run3.auditDir])
53606
+ for (const dir of [
53607
+ run3.root,
53608
+ run3.requestsDir,
53609
+ run3.responsesDir,
53610
+ run3.sessionsDir,
53611
+ run3.projectsDir,
53612
+ run3.auditDir,
53613
+ run3.treesDir
53614
+ ])
53456
53615
  fs32.mkdirSync(dir, { recursive: true });
53457
53616
  const plan = sharingAclPlan({
53458
53617
  storageUserDir: storageOf(start.dataPath),
@@ -53523,13 +53682,13 @@ function machineDoctorRows(packageRoot, dataPath) {
53523
53682
  }
53524
53683
 
53525
53684
  // src/host/sandbox/contained-deps.ts
53526
- function realContainedDeps(storageRoot, maxGb) {
53685
+ function realContainedDeps(storageRoot, maxGb, configOverridden = false) {
53527
53686
  return {
53528
53687
  storageRoot,
53529
53688
  maxGb,
53530
53689
  protect: (dir) => protectPrivateFolder(dir),
53531
53690
  userSid: currentUserSid,
53532
- port: new ScriptDiskPort(),
53691
+ port: new HelperDiskPort(new ScriptDiskPort(), helperApplies(process.env, configOverridden)),
53533
53692
  sandboxRunning: isSandboxRunning
53534
53693
  };
53535
53694
  }
@@ -59196,7 +59355,7 @@ import fs73 from "node:fs";
59196
59355
  import path95 from "node:path";
59197
59356
 
59198
59357
  // src/shared/vpn/wireguard.ts
59199
- import net from "node:net";
59358
+ import net2 from "node:net";
59200
59359
  var MAX_WIREGUARD_BYTES = 16 * 1024;
59201
59360
  var MAX_LINES3 = 300;
59202
59361
  var MAX_PEERS = 16;
@@ -59209,7 +59368,7 @@ var HOST2 = /^(?:\d{1,3}(?:\.\d{1,3}){3}|[A-Za-z0-9](?:[A-Za-z0-9.-]{0,251}[A-Za
59209
59368
  function validCidr(text) {
59210
59369
  const [addr, prefix, ...rest] = text.split("/");
59211
59370
  if (rest.length > 0 || !addr) return false;
59212
- const version2 = net.isIP(addr);
59371
+ const version2 = net2.isIP(addr);
59213
59372
  if (version2 === 0) return false;
59214
59373
  if (prefix === void 0) return true;
59215
59374
  if (!/^\d{1,3}$/.test(prefix)) return false;
@@ -59273,10 +59432,10 @@ function inspectWireguard(text) {
59273
59432
  else if (!KEY.test(privateKey)) refuse2("PrivateKey is not a WireGuard key (44 characters of base64)");
59274
59433
  const addresses = joined(face, "address");
59275
59434
  if (addresses.length === 0 || !addresses.every(validCidr)) refuse2("[Interface] needs valid Address values");
59276
- else if (!addresses.some((a) => net.isIPv4(a.split("/")[0])))
59435
+ else if (!addresses.some((a) => net2.isIPv4(a.split("/")[0])))
59277
59436
  refuse2("Address needs an IPv4 address (the kill switch works on IPv4)");
59278
59437
  const dns = joined(face, "dns");
59279
- if (!dns.every((d) => net.isIP(d) !== 0)) refuse2("DNS may only list IP addresses");
59438
+ if (!dns.every((d) => net2.isIP(d) !== 0)) refuse2("DNS may only list IP addresses");
59280
59439
  const mtuText = one(face, "mtu");
59281
59440
  const mtu = mtuText === void 0 ? void 0 : inRange(mtuText, 576, 1500);
59282
59441
  if (mtuText !== void 0 && mtu === null) refuse2("MTU must be a number from 576 to 1500");
@@ -59296,7 +59455,7 @@ function inspectWireguard(text) {
59296
59455
  const m = endpoint ? /^([^:[\]]+):(\d{1,5})$/.exec(endpoint) : null;
59297
59456
  if (!m || !HOST2.test(m[1]) || inRange(m[2], 1, 65535) === null)
59298
59457
  return refuse2(who + "Endpoint must be host:port with a name or an IPv4 address");
59299
- if (/^\d+\.\d+\.\d+\.\d+$/.test(m[1]) && !net.isIPv4(m[1])) return refuse2(who + "Endpoint is not a valid address");
59458
+ if (/^\d+\.\d+\.\d+\.\d+$/.test(m[1]) && !net2.isIPv4(m[1])) return refuse2(who + "Endpoint is not a valid address");
59300
59459
  const keepText = one(p, "persistentkeepalive");
59301
59460
  let keepalive;
59302
59461
  if (keepText !== void 0 && keepText.toLowerCase() !== "off") {
@@ -59892,7 +60051,7 @@ import fs80 from "node:fs";
59892
60051
  import path105 from "node:path";
59893
60052
 
59894
60053
  // src/host/network/port-forward.ts
59895
- import net4 from "node:net";
60054
+ import net5 from "node:net";
59896
60055
 
59897
60056
  // src/shared/network/policy.ts
59898
60057
  function relayPort(index) {
@@ -60131,14 +60290,14 @@ var SandboxChannel = class {
60131
60290
  removeLink(p.root);
60132
60291
  return;
60133
60292
  }
60134
- for (const target2 of [p.sandboxFile, p.requestsDir, p.responsesDir, p.sessionsDir, p.projectsDir]) {
60293
+ for (const target2 of [p.sandboxFile, p.requestsDir, p.responsesDir, p.sessionsDir, p.projectsDir, p.treesDir]) {
60135
60294
  safeRemove(target2, this.dataPath);
60136
60295
  }
60137
60296
  }
60138
60297
  };
60139
60298
 
60140
60299
  // src/host/channel/attach.ts
60141
- import net3 from "node:net";
60300
+ import net4 from "node:net";
60142
60301
  import os16 from "node:os";
60143
60302
 
60144
60303
  // src/shared/pty/protocol.ts
@@ -60197,10 +60356,10 @@ var FrameReader = class {
60197
60356
  };
60198
60357
 
60199
60358
  // src/shared/pty/client.ts
60200
- import net2 from "node:net";
60359
+ import net3 from "node:net";
60201
60360
  var connectOnce = (host, port, timeoutMs) => {
60202
60361
  return new Promise((resolve, reject) => {
60203
- const socket = net2.connect({ host, port });
60362
+ const socket = net3.connect({ host, port });
60204
60363
  const timer = setTimeout(() => {
60205
60364
  socket.destroy();
60206
60365
  reject(new Error("timed out connecting to " + host + ":" + port));
@@ -60485,12 +60644,175 @@ var CtrlCGuard = class {
60485
60644
  }
60486
60645
  };
60487
60646
 
60488
- // src/shared/pty/terminal.ts
60489
- var MOUSE_OFF = "\x1B[?1000l\x1B[?1002l\x1B[?1003l\x1B[?1005l\x1B[?1006l\x1B[?1015l";
60647
+ // src/shared/pty/key-gate.ts
60490
60648
  function fixWindowsKeys(chunk) {
60491
60649
  if (!chunk.includes(8) && !chunk.includes(127)) return chunk;
60492
60650
  return Buffer.from(chunk.map((b) => b === 8 ? 127 : b === 127 ? 8 : b));
60493
60651
  }
60652
+ var KeyGate = class {
60653
+ pending = [];
60654
+ decided;
60655
+ swap = false;
60656
+ /** windowsConsole: input comes from a Windows console in raw mode (its mode is decided later); else it goes as is. */
60657
+ constructor(windowsConsole) {
60658
+ this.decided = !windowsConsole;
60659
+ }
60660
+ /** The bytes to send now (empty while the mode is not known yet). */
60661
+ push(data) {
60662
+ if (!data.length) return data;
60663
+ if (!this.decided) {
60664
+ this.pending.push(data);
60665
+ return Buffer.alloc(0);
60666
+ }
60667
+ return this.swap ? fixWindowsKeys(data) : data;
60668
+ }
60669
+ /** The console's mode is known: what was held, ready to send. Later calls only change how new input is read. */
60670
+ decide(vtInput) {
60671
+ this.swap = !vtInput;
60672
+ this.decided = true;
60673
+ const held = Buffer.concat(this.pending);
60674
+ this.pending = [];
60675
+ return this.swap ? fixWindowsKeys(held) : held;
60676
+ }
60677
+ };
60678
+
60679
+ // src/shared/pty/mouse-modes.ts
60680
+ var TRACKING = [9, 1e3, 1002, 1003];
60681
+ var MAX_PARAMS = 64;
60682
+ var MOTION = 32;
60683
+ var NO_BUTTON = 3;
60684
+ var MouseModes = class {
60685
+ on = /* @__PURE__ */ new Set();
60686
+ /** 0 text, 1 after ESC, 2 after ESC[, 3 in ESC[? parameters, 4 after ESC[! */
60687
+ state = 0;
60688
+ params = "";
60689
+ /** Reads the program's output (after OutputFilter): the mode changes in it. */
60690
+ watch(output2) {
60691
+ for (const b of output2) {
60692
+ if (b === 27) {
60693
+ this.state = 1;
60694
+ continue;
60695
+ }
60696
+ if (this.state === 1) {
60697
+ if (b === 99) this.on.clear();
60698
+ this.state = b === 91 ? 2 : 0;
60699
+ } else if (this.state === 2) {
60700
+ this.state = b === 63 ? 3 : b === 33 ? 4 : 0;
60701
+ this.params = "";
60702
+ } else if (this.state === 4) {
60703
+ if (b === 112) this.on.clear();
60704
+ this.state = 0;
60705
+ } else if (this.state === 3) {
60706
+ if (b >= 48 && b <= 57 || b === 59) {
60707
+ this.params += String.fromCharCode(b);
60708
+ if (this.params.length > MAX_PARAMS) this.state = 0;
60709
+ } else {
60710
+ if (b === 104 || b === 108) this.apply(this.params, b === 104);
60711
+ this.state = 0;
60712
+ }
60713
+ }
60714
+ }
60715
+ }
60716
+ /** The program is gone (exited or restarting): its modes went with it. */
60717
+ reset() {
60718
+ this.on.clear();
60719
+ this.state = 0;
60720
+ this.params = "";
60721
+ }
60722
+ /** True while the program tracks the mouse in any way. */
60723
+ tracking() {
60724
+ return TRACKING.some((m) => this.on.has(m));
60725
+ }
60726
+ /** True while the program has a mode on (2004: bracketed paste). */
60727
+ has(mode) {
60728
+ return this.on.has(mode);
60729
+ }
60730
+ /** The mode numbers on now (for the report and tests). */
60731
+ modes() {
60732
+ return [...this.on].sort((a, b) => a - b);
60733
+ }
60734
+ /** `input` without the mouse reports the program has not asked for. Sequences must be whole (InputJoiner). */
60735
+ filter(input2) {
60736
+ if (!input2.includes(27)) return input2;
60737
+ const parts = [];
60738
+ let from = 0;
60739
+ let i = input2.indexOf(27);
60740
+ while (i !== -1) {
60741
+ const report = mouseReport(input2, i, this.on.has(1005), this.on.has(1015));
60742
+ if (report && !this.allows(report.button)) {
60743
+ parts.push(input2.subarray(from, i));
60744
+ from = i + report.length;
60745
+ i = input2.indexOf(27, from);
60746
+ } else i = input2.indexOf(27, i + 1);
60747
+ }
60748
+ if (from === 0) return input2;
60749
+ parts.push(input2.subarray(from));
60750
+ return Buffer.concat(parts);
60751
+ }
60752
+ allows(button) {
60753
+ if (!this.tracking()) return false;
60754
+ if ((button & MOTION) === 0) return true;
60755
+ if (this.on.has(1003)) return true;
60756
+ return this.on.has(1002) && (button & NO_BUTTON) !== NO_BUTTON;
60757
+ }
60758
+ apply(params, set3) {
60759
+ for (const p of params.split(";")) {
60760
+ const mode = Number(p);
60761
+ if (!p || !Number.isSafeInteger(mode)) continue;
60762
+ if (set3) this.on.add(mode);
60763
+ else this.on.delete(mode);
60764
+ }
60765
+ }
60766
+ };
60767
+ function mouseReport(data, at, utf8, urxvt) {
60768
+ if (data[at + 1] !== 91) return null;
60769
+ const sgr = data[at + 2] === 60;
60770
+ if (sgr || urxvt && data[at + 2] >= 48 && data[at + 2] <= 57) {
60771
+ let i = at + (sgr ? 3 : 2);
60772
+ let digits = "";
60773
+ let fields = 1;
60774
+ for (; i < data.length && i - at < 32; i++) {
60775
+ const b = data[i];
60776
+ if (b >= 48 && b <= 57) {
60777
+ if (fields === 1) digits += String.fromCharCode(b);
60778
+ } else if (b === 59) fields++;
60779
+ else break;
60780
+ }
60781
+ if (i >= data.length || fields !== 3 || !digits) return null;
60782
+ if (sgr ? data[i] !== 77 && data[i] !== 109 : data[i] !== 77) return null;
60783
+ const button = Number(digits) - (sgr ? 0 : 32);
60784
+ return button >= 0 ? { button, length: i - at + 1 } : null;
60785
+ }
60786
+ if (data[at + 2] === 77 && !utf8 && data.length - at >= 6) {
60787
+ const button = data[at + 3] - 32;
60788
+ return button >= 0 ? { button, length: 6 } : null;
60789
+ }
60790
+ return null;
60791
+ }
60792
+
60793
+ // src/shared/pty/terminal.ts
60794
+ var MOUSE_OFF = "\x1B[?1000l\x1B[?1002l\x1B[?1003l\x1B[?1005l\x1B[?1006l\x1B[?1015l";
60795
+ var SETTLE_QUIET_MS = 50;
60796
+ var SETTLE_MAX_MS = 300;
60797
+ function drainInput(stream) {
60798
+ return new Promise((done) => {
60799
+ let quiet;
60800
+ const end = () => {
60801
+ clearTimeout(quiet);
60802
+ clearTimeout(cap);
60803
+ stream.off("data", onData);
60804
+ done();
60805
+ };
60806
+ const onData = () => {
60807
+ clearTimeout(quiet);
60808
+ quiet = setTimeout(end, SETTLE_QUIET_MS);
60809
+ };
60810
+ const cap = setTimeout(end, SETTLE_MAX_MS);
60811
+ stream.on("data", onData);
60812
+ onData();
60813
+ });
60814
+ }
60815
+ var VT_WAIT_MS = 1e4;
60494
60816
  function runTerminalClient(socket, token, options = {}) {
60495
60817
  const stdin = process.stdin;
60496
60818
  const stdout = process.stdout;
@@ -60498,14 +60820,16 @@ function runTerminalClient(socket, token, options = {}) {
60498
60820
  const [c2, r] = stdout.isTTY ? stdout.getWindowSize() : [0, 0];
60499
60821
  return [c2 || 120, r || 30];
60500
60822
  };
60501
- let swapKeys = process.platform === "win32" && stdin.isTTY;
60823
+ const keys = new KeyGate(process.platform === "win32" && Boolean(stdin.isTTY));
60502
60824
  return new Promise((resolve) => {
60503
60825
  let done = false;
60504
60826
  const reader = new FrameReader();
60505
60827
  const filter = new OutputFilter({ clipboardWrites: options.clipboardWrites });
60506
60828
  let sent = size();
60507
60829
  let resizeTimer;
60508
- let vtInput;
60830
+ let vtInput, vtTimer;
60831
+ const mouse = new MouseModes();
60832
+ let passthrough = false;
60509
60833
  const finish = (result) => {
60510
60834
  if (done) return;
60511
60835
  done = true;
@@ -60516,9 +60840,13 @@ function runTerminalClient(socket, token, options = {}) {
60516
60840
  clearInterval(sizeWatch);
60517
60841
  clearTimeout(joinTimer);
60518
60842
  clearTimeout(hintTimer);
60843
+ clearTimeout(vtTimer);
60519
60844
  socket.destroy();
60520
- stdout.write("\x1B[0m\x1B[?25h" + MOUSE_OFF + "\r\n");
60521
- void (vtInput?.stop() ?? Promise.resolve()).then(() => {
60845
+ const tracked = mouse.tracking();
60846
+ const reset = "\x1B[0m\x1B[?25h" + MOUSE_OFF + (mouse.has(2004) ? "\x1B[?2004l" : "") + "\r\n";
60847
+ const flushed = new Promise((written) => stdout.write(reset, () => written()));
60848
+ const settled = tracked ? flushed.then(() => drainInput(stdin)) : Promise.resolve();
60849
+ void Promise.all([settled, vtInput?.stop()]).then(() => {
60522
60850
  if (stdin.isTTY) stdin.setRawMode(false);
60523
60851
  stdin.pause();
60524
60852
  resolve(result);
@@ -60526,9 +60854,10 @@ function runTerminalClient(socket, token, options = {}) {
60526
60854
  };
60527
60855
  const joiner = new InputJoiner();
60528
60856
  let joinTimer;
60529
- const sendInput = (data) => {
60530
- if (data.length && !done) socket.write(encodeFrame(FrameType.Input, swapKeys ? fixWindowsKeys(data) : data));
60857
+ const write = (data) => {
60858
+ if (data.length && !done) socket.write(encodeFrame(FrameType.Input, data));
60531
60859
  };
60860
+ const sendInput = (data) => write(keys.push(passthrough ? mouse.filter(data) : data));
60532
60861
  const guard2 = options.confirmCtrlC ? new CtrlCGuard() : null;
60533
60862
  let hintTimer;
60534
60863
  const onInput = (chunk) => {
@@ -60550,7 +60879,6 @@ function runTerminalClient(socket, token, options = {}) {
60550
60879
  sendInput(joiner.push(chunk));
60551
60880
  if (joiner.holding()) joinTimer = setTimeout(() => sendInput(joiner.flush()), JOIN_MS);
60552
60881
  };
60553
- let passthrough = false;
60554
60882
  const sendSize = () => {
60555
60883
  const [c3, r2] = size();
60556
60884
  if (c3 === sent[0] && r2 === sent[1]) return;
@@ -60580,11 +60908,14 @@ function runTerminalClient(socket, token, options = {}) {
60580
60908
  for (const frame of frames) {
60581
60909
  if (frame.type === FrameType.Output) {
60582
60910
  const out = filter.push(frame.payload);
60911
+ mouse.watch(out);
60583
60912
  guard2?.watchOutput(out.toString("latin1"));
60584
60913
  stdout.write(out);
60585
60914
  } else if (frame.type === FrameType.Console) passthrough = frame.payload.toString("latin1") === "conpty.dll";
60586
- else if (frame.type === FrameType.Restarting) stdout.write(MOUSE_OFF + "\x1B[2J\x1B[3J\x1B[H");
60587
- else if (frame.type === FrameType.Exit)
60915
+ else if (frame.type === FrameType.Restarting) {
60916
+ mouse.reset();
60917
+ stdout.write(MOUSE_OFF + "\x1B[2J\x1B[3J\x1B[H");
60918
+ } else if (frame.type === FrameType.Exit)
60588
60919
  finish({ reason: "exit", exitCode: frame.payload.length >= 4 ? frame.payload.readInt32LE(0) : 1 });
60589
60920
  else if (frame.type === FrameType.Error)
60590
60921
  finish({
@@ -60600,8 +60931,10 @@ function runTerminalClient(socket, token, options = {}) {
60600
60931
  socket.write(helloFrame(token, c2, r));
60601
60932
  if (stdin.isTTY) stdin.setRawMode(true);
60602
60933
  vtInput = enableVtInput();
60934
+ vtTimer = setTimeout(() => write(keys.decide(false)), VT_WAIT_MS);
60603
60935
  void vtInput.ready.then((on) => {
60604
- if (on) swapKeys = false;
60936
+ clearTimeout(vtTimer);
60937
+ write(keys.decide(on));
60605
60938
  });
60606
60939
  stdin.on("data", onInput);
60607
60940
  stdin.resume();
@@ -60612,7 +60945,7 @@ function runTerminalClient(socket, token, options = {}) {
60612
60945
  // src/host/channel/attach.ts
60613
60946
  var ipToInt = (ip) => ip.split(".").reduce((n, part) => n * 256 + Number(part), 0);
60614
60947
  function isSandboxAddress(ip, interfaces = os16.networkInterfaces()) {
60615
- if (!net3.isIPv4(ip)) return false;
60948
+ if (!net4.isIPv4(ip)) return false;
60616
60949
  return Object.entries(interfaces).some(
60617
60950
  ([name2, list]) => /^vEthernet/i.test(name2) && !/WSL/i.test(name2) && (list ?? []).some((i) => {
60618
60951
  if (i.family !== "IPv4" || i.internal) return false;
@@ -60691,13 +61024,13 @@ function sandboxAddress(dataPath) {
60691
61024
  }
60692
61025
  function startPortForwards(forwards, address, note = () => void 0) {
60693
61026
  const servers = forwards.map((f, i) => {
60694
- const server = net4.createServer((client) => {
61027
+ const server = net5.createServer((client) => {
60695
61028
  const ip = address();
60696
61029
  if (!ip) {
60697
61030
  client.destroy();
60698
61031
  return;
60699
61032
  }
60700
- const target2 = net4.connect(relayPort(i), ip);
61033
+ const target2 = net5.connect(relayPort(i), ip);
60701
61034
  client.pipe(target2).pipe(client);
60702
61035
  target2.on("error", () => client.destroy());
60703
61036
  client.on("error", () => target2.destroy());
@@ -61547,7 +61880,10 @@ var Watcher = class {
61547
61880
  /** Contained storage of this data set (only when a mounted image is on record), else null. */
61548
61881
  contained() {
61549
61882
  try {
61550
- const storage = new ContainedStorage(realContainedDeps(this.storageRoot, 64), setNameOf(this.dataPath));
61883
+ const storage = new ContainedStorage(
61884
+ realContainedDeps(this.storageRoot, 64, Boolean(this.resume.config)),
61885
+ setNameOf(this.dataPath)
61886
+ );
61551
61887
  return storage.isRecorded() ? storage : null;
61552
61888
  } catch {
61553
61889
  return null;
@@ -64698,17 +65034,17 @@ var DataService = class {
64698
65034
  }
64699
65035
  /** Removes the planned folders. A failure on one does not stop the others. */
64700
65036
  clean(plan) {
64701
- const failed = [];
65037
+ const failed2 = [];
64702
65038
  for (const t of plan.present) {
64703
65039
  try {
64704
65040
  removeTree(t.path, t.root);
64705
65041
  } catch (e) {
64706
- failed.push(t.target + " (" + errMsg(e) + ")");
65042
+ failed2.push(t.target + " (" + errMsg(e) + ")");
64707
65043
  }
64708
65044
  }
64709
65045
  return {
64710
65046
  freedBytes: plan.totalBytes - import_lodash22.default.sumBy(plan.present, (t) => sizeOf(t.path)),
64711
- failed,
65047
+ failed: failed2,
64712
65048
  touchedData: plan.present.some((t) => t.root === this.ctx.dataPath || t.target === "all")
64713
65049
  };
64714
65050
  }
@@ -65196,8 +65532,8 @@ function decideUpdate(input2) {
65196
65532
  if (!input2.latest) return skip("no-release");
65197
65533
  const latest = input2.latest;
65198
65534
  if (compareVersions(latest.version, input2.current) <= 0) return skip("up-to-date");
65199
- const failed = input2.state.failed;
65200
- if (failed && compareVersions(latest.version, failed.version) <= 0) return skip("failed-before");
65535
+ const failed2 = input2.state.failed;
65536
+ if (failed2 && compareVersions(latest.version, failed2.version) <= 0) return skip("failed-before");
65201
65537
  return { kind: "install", release: latest };
65202
65538
  }
65203
65539
 
@@ -65237,13 +65573,13 @@ function updateNotice(state, current, seenKey) {
65237
65573
  const off = state.autoOff;
65238
65574
  if (off && compareVersions(off.version, current) > 0)
65239
65575
  candidates.push({ at: off.at, notice: { key: "auto-off@" + off.at, text: autoOffText(off.version, current) } });
65240
- const failed = state.failed;
65241
- if (failed && compareVersions(failed.version, current) > 0 && off?.version !== failed.version)
65576
+ const failed2 = state.failed;
65577
+ if (failed2 && compareVersions(failed2.version, current) > 0 && off?.version !== failed2.version)
65242
65578
  candidates.push({
65243
- at: failed.at,
65579
+ at: failed2.at,
65244
65580
  notice: {
65245
- key: "failed@" + failed.at,
65246
- text: "Update to " + failed.version + " failed; still on " + current + " (sandbox update --rollback, or sandbox update for details)."
65581
+ key: "failed@" + failed2.at,
65582
+ text: "Update to " + failed2.version + " failed; still on " + current + " (sandbox update --rollback, or sandbox update for details)."
65247
65583
  }
65248
65584
  });
65249
65585
  const newest = candidates.sort((a, b) => Date.parse(b.at) - Date.parse(a.at))[0];
@@ -67615,7 +67951,7 @@ async function knockOnDoor(storageRoot, owner) {
67615
67951
 
67616
67952
  // src/host/remote/cert.ts
67617
67953
  import crypto17 from "node:crypto";
67618
- import net5 from "node:net";
67954
+ import net6 from "node:net";
67619
67955
  var CERT_DAYS = 397;
67620
67956
  var DAY_MS4 = 864e5;
67621
67957
  function length(n) {
@@ -67660,13 +67996,13 @@ function extension(id, critical, value) {
67660
67996
  }
67661
67997
  function altNames(addresses, dnsNames) {
67662
67998
  const entries = [
67663
- ...addresses.filter((a) => net5.isIPv4(a)).map((a) => tlv(135, Buffer.from(a.split(".").map(Number)))),
67999
+ ...addresses.filter((a) => net6.isIPv4(a)).map((a) => tlv(135, Buffer.from(a.split(".").map(Number)))),
67664
68000
  ...dnsNames.filter((d) => /^[A-Za-z0-9-]{1,63}$/.test(d)).map((d) => tlv(130, Buffer.from(d, "ascii")))
67665
68001
  ];
67666
68002
  return seq(...entries);
67667
68003
  }
67668
68004
  function makeCertificate(r) {
67669
- if (!r.addresses.length || !r.addresses.every((a) => net5.isIPv4(a)))
68005
+ if (!r.addresses.length || !r.addresses.every((a) => net6.isIPv4(a)))
67670
68006
  throw new Error("A certificate needs at least one IPv4 address.");
67671
68007
  const now2 = r.now ?? Date.now();
67672
68008
  const days = r.days ?? CERT_DAYS;
@@ -67716,20 +68052,20 @@ import path137 from "node:path";
67716
68052
  import { spawn as spawn11 } from "node:child_process";
67717
68053
 
67718
68054
  // src/host/remote/lan.ts
67719
- import net6 from "node:net";
68055
+ import net7 from "node:net";
67720
68056
  import os20 from "node:os";
67721
68057
  var toInt = (ip) => ip.split(".").reduce((n, part) => n * 256 + Number(part), 0) >>> 0;
67722
68058
  function isPrivateIPv4(ip) {
67723
- if (!net6.isIPv4(ip)) return false;
68059
+ if (!net7.isIPv4(ip)) return false;
67724
68060
  const [a, b] = ip.split(".").map(Number);
67725
68061
  return a === 10 || a === 172 && b >= 16 && b <= 31 || a === 192 && b === 168;
67726
68062
  }
67727
68063
  function isLoopbackIPv4(ip) {
67728
- return net6.isIPv4(ip) && ip.startsWith("127.");
68064
+ return net7.isIPv4(ip) && ip.startsWith("127.");
67729
68065
  }
67730
68066
  function plainIPv4(ip) {
67731
68067
  const value = ip ?? "";
67732
- return value.startsWith("::ffff:") && net6.isIPv4(value.slice(7)) ? value.slice(7) : value;
68068
+ return value.startsWith("::ffff:") && net7.isIPv4(value.slice(7)) ? value.slice(7) : value;
67733
68069
  }
67734
68070
  function virtualAdapter(name2) {
67735
68071
  return /^vEthernet/i.test(name2) || /Hyper-V|WSL/i.test(name2);
@@ -68090,9 +68426,11 @@ var TerminalBridge = class {
68090
68426
  break;
68091
68427
  }
68092
68428
  case FrameType.Restarting:
68429
+ this.mouseOff();
68093
68430
  this.tell({ t: "restarting" });
68094
68431
  break;
68095
68432
  case FrameType.Exit:
68433
+ this.mouseOff();
68096
68434
  this.tell({ t: "exit", code: frame.payload.length >= 4 ? frame.payload.readInt32LE(0) : 1 });
68097
68435
  this.finish(1e3, "exit");
68098
68436
  return;
@@ -68106,6 +68444,13 @@ var TerminalBridge = class {
68106
68444
  }
68107
68445
  if (this.ws.bufferedAmount > MAX_BUFFERED_BYTES) this.hold();
68108
68446
  }
68447
+ /**
68448
+ * The program's mouse tracking ends with it: the browser's terminal stops sending clicks and moves, and a drag
68449
+ * selects text again (an ended terminal stays on screen).
68450
+ */
68451
+ mouseOff() {
68452
+ if (this.ws.readyState === this.ws.OPEN) this.ws.send(Buffer.from(MOUSE_OFF, "latin1"), { binary: true });
68453
+ }
68109
68454
  /** Stops reading from the session until the browser has taken what it was sent. */
68110
68455
  hold() {
68111
68456
  if (this.drain || !this.socket) return;
@@ -68577,6 +68922,7 @@ var WebServer = class {
68577
68922
  live;
68578
68923
  actualPort = 0;
68579
68924
  lastUsed = Date.now();
68925
+ lastCheck = Date.now();
68580
68926
  heartbeat;
68581
68927
  idleCheck;
68582
68928
  get port() {
@@ -68591,7 +68937,8 @@ var WebServer = class {
68591
68937
  this.heartbeat = setInterval(() => this.beat(), this.options.heartbeatMs ?? HEARTBEAT_MS);
68592
68938
  this.heartbeat.unref();
68593
68939
  if (this.options.idleExitMs) {
68594
- this.idleCheck = setInterval(() => void this.checkIdle(), Math.min(6e4, this.quietMs()));
68940
+ this.lastCheck = Date.now();
68941
+ this.idleCheck = setInterval(() => void this.checkIdle(), this.checkEveryMs());
68595
68942
  this.idleCheck.unref();
68596
68943
  }
68597
68944
  const gate = this.options.gate;
@@ -68632,6 +68979,9 @@ var WebServer = class {
68632
68979
  /** No browser has used the server for a while: it ends (sooner when no sandbox runs). */
68633
68980
  async checkIdle() {
68634
68981
  const limit = this.options.idleExitMs ?? 0;
68982
+ const now2 = Date.now();
68983
+ if (now2 - this.lastCheck > 3 * this.checkEveryMs()) this.lastUsed = now2;
68984
+ this.lastCheck = now2;
68635
68985
  if (!limit || this.tracked.size > 0) return;
68636
68986
  const idle = Date.now() - this.lastUsed;
68637
68987
  if (idle >= limit) return this.options.onStop?.();
@@ -68643,6 +68993,9 @@ var WebServer = class {
68643
68993
  }
68644
68994
  this.options.onStop?.();
68645
68995
  }
68996
+ checkEveryMs() {
68997
+ return Math.min(6e4, this.quietMs());
68998
+ }
68646
68999
  /** How long unused before it ends when no sandbox runs. */
68647
69000
  quietMs() {
68648
69001
  return Math.min(this.options.idleExitMs ?? 0, this.options.idleNoSandboxMs ?? IDLE_NO_SANDBOX_MS);
@@ -71166,7 +71519,7 @@ function requireNoSandbox() {
71166
71519
  function storageFor(ctx) {
71167
71520
  const { config: config3 } = ctx.loaded;
71168
71521
  return new ContainedStorage(
71169
- realContainedDeps(ctx.storageRoot, config3.storage.containedMaxGb),
71522
+ realContainedDeps(ctx.storageRoot, config3.storage.containedMaxGb, Boolean(ctx.options.config)),
71170
71523
  config3.storage.dataName
71171
71524
  );
71172
71525
  }
@@ -72551,7 +72904,7 @@ import fs111 from "node:fs";
72551
72904
  function storageOf2(env) {
72552
72905
  const { config: config3 } = env.ctx.loaded;
72553
72906
  return new ContainedStorage(
72554
- realContainedDeps(env.storageRoot, config3.storage.containedMaxGb),
72907
+ realContainedDeps(env.storageRoot, config3.storage.containedMaxGb, Boolean(env.ctx.options.config)),
72555
72908
  config3.storage.dataName
72556
72909
  );
72557
72910
  }
@@ -76578,13 +76931,13 @@ var cleanEngineRun = implement(cleanEngine, async (ctx, _input, io) => {
76578
76931
  r.stopped ? "ok" : "danger"
76579
76932
  );
76580
76933
  const stopped = results.filter((r) => r.stopped).length;
76581
- const failed = results.length - stopped;
76582
- if (failed)
76934
+ const failed2 = results.length - stopped;
76935
+ if (failed2)
76583
76936
  fail(
76584
- plural2(failed, "HCS system") + " could not be stopped.",
76937
+ plural2(failed2, "HCS system") + " could not be stopped.",
76585
76938
  "Run it again from an administrator terminal: sandbox engine clean"
76586
76939
  );
76587
- return { available: true, stopped, failed, message: "Stopped " + plural2(stopped, "HCS system") + "." };
76940
+ return { available: true, stopped, failed: failed2, message: "Stopped " + plural2(stopped, "HCS system") + "." };
76588
76941
  });
76589
76942
 
76590
76943
  // src/app/features/vpn/vpn.def.ts
@@ -77449,7 +77802,7 @@ async function attachOpened(ctx, session) {
77449
77802
  nextStep("sandbox attach " + session);
77450
77803
  return 0;
77451
77804
  }
77452
- const { attach: attach2 } = await import("./chunk-IL7BXJF2.js");
77805
+ const { attach: attach2 } = await import("./chunk-LQHEUN2A.js");
77453
77806
  return attach2({ ...ctx, options: { ...ctx.options, positionals: [session] } });
77454
77807
  }
77455
77808
  function definitionHandler(def) {
@@ -77948,7 +78301,7 @@ var Launcher = class {
77948
78301
  const engine = await openEngine(config3);
77949
78302
  const provider = providerFor(
77950
78303
  config3,
77951
- config3.storage.contained === "off" ? {} : { contained: realContainedDeps(ctx.storageRoot, config3.storage.containedMaxGb) }
78304
+ config3.storage.contained === "off" ? {} : { contained: realContainedDeps(ctx.storageRoot, config3.storage.containedMaxGb, Boolean(ctx.options.config)) }
77952
78305
  );
77953
78306
  if (!generateOnly) assertCanLaunch(isSandboxRunning() ? runningOwner() : void 0);
77954
78307
  const dataMappings = await this.prepareStorage(provider, run3);