windows-development-sandbox 1.27.3 → 1.28.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 (68) hide show
  1. package/dist/bin/agent-sandbox.js +2 -2
  2. package/dist/bin/{chunk-RU7XRN62.js → chunk-CB44HTTN.js} +196 -75
  3. package/dist/bin/{chunk-FA5VWLP4.js → chunk-UZ5S3EMB.js} +1 -1
  4. package/dist/bin/{chunk-4M5SZZ5P.js → chunk-ZMZI3625.js} +1 -1
  5. package/dist/bin/claude-host.js +2 -2
  6. package/dist/bin/claude-sbx.js +2 -2
  7. package/dist/bin/codex-sbx.js +2 -2
  8. package/dist/bin/gemini-sbx.js +2 -2
  9. package/dist/bin/sandbox.js +2 -2
  10. package/dist/bin/sbx-hide.js +2 -2
  11. package/dist/bin/sbx-show.js +2 -2
  12. package/dist/core/schema/sandbox.js +1 -1
  13. package/dist/guest/daemon/heartbeat.js +1 -0
  14. package/dist/guest/governor/core.d.ts +53 -1
  15. package/dist/guest/governor/core.js +95 -1
  16. package/dist/guest/governor/decide.d.ts +4 -0
  17. package/dist/guest/governor/decide.js +2 -0
  18. package/dist/guest/governor/governor.js +23 -9
  19. package/dist/guest/toolchains/native/unreal.js +22 -3
  20. package/dist/guest/toolchains/runtimes.js +7 -6
  21. package/dist/host/life/crash-report.d.ts +4 -0
  22. package/dist/host/life/crash-report.js +30 -1
  23. package/dist/host/life/last-end.js +1 -0
  24. package/dist/host/tools/windows-terminal.d.ts +4 -1
  25. package/dist/host/tools/windows-terminal.js +25 -9
  26. package/dist/runtime/agent.js +5 -1
  27. package/dist/runtime/daemon.js +183 -70
  28. package/dist/runtime/db.js +5 -1
  29. package/dist/runtime/main.js +242 -56
  30. package/dist/runtime/netproxy.js +5 -1
  31. package/dist/runtime/tool.js +5 -1
  32. package/dist/runtime/view.js +125 -27
  33. package/dist/selftest/agent-paths.test.js +3 -1
  34. package/dist/selftest/channel.test.js +33 -0
  35. package/dist/selftest/heavy-load.test.js +116 -4
  36. package/dist/selftest/live-terminal.test.js +103 -0
  37. package/dist/selftest/personalization.test.js +2 -1
  38. package/dist/selftest/support/engine-golden.js +2 -0
  39. package/dist/shared/channel/schema.d.ts +2 -0
  40. package/dist/shared/channel/schema.js +4 -0
  41. package/dist/shared/memory/index.d.ts +1 -0
  42. package/dist/shared/memory/index.js +1 -0
  43. package/dist/shared/memory/policy.d.ts +12 -0
  44. package/dist/shared/memory/policy.js +4 -0
  45. package/dist/shared/memory/unreal-build.d.ts +2 -0
  46. package/dist/shared/memory/unreal-build.js +21 -0
  47. package/dist/shared/pty/clipboard-watch.d.ts +4 -0
  48. package/dist/shared/pty/clipboard-watch.js +40 -0
  49. package/dist/shared/pty/ctrl-c.d.ts +13 -7
  50. package/dist/shared/pty/ctrl-c.js +35 -19
  51. package/dist/shared/pty/index.d.ts +1 -0
  52. package/dist/shared/pty/index.js +1 -0
  53. package/dist/shared/pty/terminal.d.ts +1 -1
  54. package/dist/shared/pty/terminal.js +16 -13
  55. package/dist/shared/web/ctrl-c.d.ts +26 -0
  56. package/dist/shared/web/ctrl-c.js +64 -0
  57. package/dist/shared/web/index.d.ts +1 -0
  58. package/dist/shared/web/index.js +1 -0
  59. package/dist/web/index.html +1 -1
  60. package/dist/web/{main-NKM7ECSB.js → main-7NY4X6YU.js} +67 -67
  61. package/native/csharp/HcsHost/AssemblyInfo.cs +3 -3
  62. package/native/csharp/HostTools/AssemblyInfo.cs +3 -3
  63. package/native/csharp/StorageHelper/AssemblyInfo.cs +3 -3
  64. package/package.json +1 -1
  65. package/sandbox/csharp/ClipboardWatch.cs +59 -0
  66. package/sandbox/csharp/MemGov.cs +752 -7
  67. package/sandbox/scripts/watch-clipboard.ps1 +8 -0
  68. package/schemas/config.schema.json +1 -1
@@ -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-4M5SZZ5P.js";
6
- import "./chunk-RU7XRN62.js";
5
+ } from "./chunk-ZMZI3625.js";
6
+ import "./chunk-CB44HTTN.js";
7
7
 
8
8
  // src/bin/agent-sandbox.ts
9
9
  run(process.argv.slice(2));
@@ -46223,7 +46223,7 @@ var SandboxSchema = external_exports.strictObject({
46223
46223
  'The last resort that keeps the sandbox up, in MB below its commit limit (RAM plus paging file): builds are slowed down long before (sandbox.pressureResponse); only if everything the agents start together would take the sandbox to the very edge of its memory, where Windows inside it would start failing, is a build refused memory instead of Windows. "auto" = the larger of 1536 MB and 3 % of the commit limit; "none" = no backstop. Yours only: a project file cannot change it.'
46224
46224
  ),
46225
46225
  pressureResponse: external_exports.boolean().describe(
46226
- "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."
46226
+ "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. A memory gate also holds new compilers and linkers for a moment when the memory of the build programs grows faster than 4 GB in 3 seconds (3 GB with vGPU), and lets them go one at a time. Yours only: a project file cannot change it."
46227
46227
  ),
46228
46228
  vGpu: external_exports.boolean().describe(
46229
46229
  "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)."
@@ -48941,6 +48941,8 @@ var PressureStatusSchema = external_exports.object({
48941
48941
  lowered: external_exports.number().int().min(0).max(1024),
48942
48942
  /** Compilers waiting their turn right now (name, process id, seconds until they may go on). */
48943
48943
  paused: external_exports.array(external_exports.object({ name: Text(64), pid: external_exports.number().int(), seconds: external_exports.number().int().min(0).max(60) })).max(8),
48944
+ /** Build programs the memory gate holds right now (let go one at a time). */
48945
+ held: external_exports.number().int().min(0).max(1024).optional(),
48944
48946
  /** The CPU share the compilers get together (percent; 100 = no cap). */
48945
48947
  cpuPercent: external_exports.number().int().min(0).max(100).optional(),
48946
48948
  /** The PC itself asked for slowing down (its memory or CPU is low). */
@@ -48961,6 +48963,8 @@ var HeartbeatSchema = external_exports.object({
48961
48963
  topProcesses: external_exports.array(external_exports.object({ name: Text(64), privateMb: external_exports.number().int().min(0).max(1e7) })).max(5),
48962
48964
  /** The disk queue this PC last reported while asking the sandbox to slow down. */
48963
48965
  diskQueueReported: external_exports.number().min(0).max(1e5).optional(),
48966
+ /** Build programs the memory gate holds right now (let go one at a time). */
48967
+ held: external_exports.number().int().min(0).max(1024).optional(),
48964
48968
  /** "ending": the sandbox is shutting down in order (idle shutdown, sandbox stop, Windows shutting down). */
48965
48969
  phase: external_exports.enum(["ready", "working", "ending"])
48966
48970
  });
@@ -53743,7 +53747,7 @@ var clock = (iso) => {
53743
53747
  };
53744
53748
  function heartbeatLine(hb) {
53745
53749
  const top = hb.topProcesses.map((p) => safeText(p.name) + " " + p.privateMb + " MB").join(", ");
53746
- return "last seen inside at " + clock(hb.at) + ": up " + Math.round(hb.uptimeS / 60) + " min, " + hb.ramFreePct + "% RAM free, " + hb.commitPct + "% commit used, builds at step " + hb.step + (hb.hostStep > 0 ? " (this PC asked for step " + hb.hostStep + ")" : "") + (hb.diskQueueReported !== void 0 ? ", this PC disk queue " + hb.diskQueueReported : "") + (top ? "; biggest: " + top : "");
53750
+ return "last seen inside at " + clock(hb.at) + ": up " + Math.round(hb.uptimeS / 60) + " min, " + hb.ramFreePct + "% RAM free, " + hb.commitPct + "% commit used, builds at step " + hb.step + (hb.hostStep > 0 ? " (this PC asked for step " + hb.hostStep + ")" : "") + (hb.diskQueueReported !== void 0 ? ", this PC disk queue " + hb.diskQueueReported : "") + (hb.held ? ", " + hb.held + " builds held by the memory gate" : "") + (top ? "; biggest: " + top : "");
53747
53751
  }
53748
53752
  function guessReason(hb) {
53749
53753
  if (hb && hb.diskQueueReported !== void 0 && hb.hostStep > 0)
@@ -53817,7 +53821,12 @@ function findingLines(events) {
53817
53821
  );
53818
53822
  if (has2((e) => lower(e).startsWith("hyper-v")))
53819
53823
  lines.push("Hyper-V events: the sandbox's virtual machine (or the service that runs it) reported a problem.");
53820
- if (has2((e) => /^(application error|windows error reporting)$/i.test(e.provider)))
53824
+ const isAppCrash = (e) => /^(application error|windows error reporting)$/i.test(e.provider);
53825
+ if (has2((e) => isAppCrash(e) && /vmwp\.exe/i.test(e.process)))
53826
+ lines.push(
53827
+ "vmwp.exe, the Windows program that runs the sandbox's VM, crashed. That is a fault in Windows' Hyper-V code or a driver it uses (with vGPU on, usually the graphics driver), not a program inside the sandbox running out of memory."
53828
+ );
53829
+ else if (has2(isAppCrash))
53821
53830
  lines.push("A crash report names vmwp.exe or vmmem, the programs that hold the sandbox's VM.");
53822
53831
  return lines;
53823
53832
  }
@@ -53867,6 +53876,17 @@ function hostMemoryTail(storageRoot, dataPath) {
53867
53876
  return "";
53868
53877
  }
53869
53878
  }
53879
+ function memoryWasFineLine(hostMemoryLog) {
53880
+ const last = hostMemoryLog.split(/\r?\n/).filter((l) => l.trim() !== "").pop();
53881
+ if (!last) return null;
53882
+ const ram = /RAM free\/total MB (\d{1,9})\/(\d{1,9})/.exec(last);
53883
+ const commit = /commit free\/limit MB (\d{1,9})\/(\d{1,9})/.exec(last);
53884
+ if (!ram || !commit) return null;
53885
+ const freeRam = Number(ram[1]);
53886
+ const freeCommit = Number(commit[1]);
53887
+ if (freeRam < 4096 || freeCommit < 4096) return null;
53888
+ return "This PC still had " + Math.floor(freeRam / 1024) + " GB of RAM and " + Math.floor(freeCommit / 1024) + " GB of memory (RAM plus paging file) free just before, so it did not run out of memory.";
53889
+ }
53870
53890
  function crashReportText(cause, events, memoryLog, lastSeen, extra = {}) {
53871
53891
  const seen = /* @__PURE__ */ new Set();
53872
53892
  const unique = events.filter((e) => {
@@ -53882,6 +53902,7 @@ function crashReportText(cause, events, memoryLog, lastSeen, extra = {}) {
53882
53902
  const hostLines = (extra.hostMemoryLog ?? "").split(/\r?\n/).filter(Boolean).slice(-20).map((l) => " " + safeText(l));
53883
53903
  const freeze = freezeLine(cause, lastSeen);
53884
53904
  const meaning = findingLines(unique);
53905
+ const fine = memoryWasFineLine(extra.hostMemoryLog ?? "");
53885
53906
  const dumps = extra.dumps ?? [];
53886
53907
  const vgpu = extra.vgpu ? ["vGPU was on for this sandbox. " + vgpuWarning("its launch settings"), ""] : [];
53887
53908
  return [
@@ -53895,6 +53916,7 @@ function crashReportText(cause, events, memoryLog, lastSeen, extra = {}) {
53895
53916
  ...vgpu,
53896
53917
  "What that usually means:",
53897
53918
  ...meaning.length ? meaning.map((l) => " " + l) : [" (no memory, blue screen or hardware event was logged)"],
53919
+ ...fine ? [" " + fine] : [],
53898
53920
  "",
53899
53921
  "Crash dumps Windows left (C:\\Windows\\Minidump, MEMORY.DMP):",
53900
53922
  ...dumps.length ? dumps.map((d) => " " + d.name + " " + d.at) : [" (none from that time)"],
@@ -65192,8 +65214,37 @@ var OutputFilter = class {
65192
65214
  }
65193
65215
  };
65194
65216
 
65195
- // src/shared/pty/console-input.ts
65217
+ // src/shared/pty/clipboard-watch.ts
65196
65218
  import { spawn as spawn11 } from "node:child_process";
65219
+ var MAX_PENDING = 1024;
65220
+ function watchClipboard(onChange) {
65221
+ if (process.platform !== "win32") return { stop: () => void 0 };
65222
+ let child = null;
65223
+ try {
65224
+ child = spawn11(SYSTEM.powershell(), scriptArgs("watch-clipboard.ps1", ["-ProcessId", String(process.pid)]), {
65225
+ stdio: ["ignore", "pipe", "ignore"],
65226
+ windowsHide: true
65227
+ });
65228
+ } catch {
65229
+ return { stop: () => void 0 };
65230
+ }
65231
+ let pending = "";
65232
+ child.stdout?.setEncoding("latin1");
65233
+ child.stdout?.on("data", (text) => {
65234
+ const lines = (pending + text).split("\n");
65235
+ pending = (lines.pop() ?? "").slice(-MAX_PENDING);
65236
+ for (const line of lines) if (line.trim() === "changed") onChange();
65237
+ });
65238
+ child.on("error", () => void 0);
65239
+ return {
65240
+ stop: () => {
65241
+ if (child && child.exitCode === null && child.signalCode === null) child.kill();
65242
+ }
65243
+ };
65244
+ }
65245
+
65246
+ // src/shared/pty/console-input.ts
65247
+ import { spawn as spawn12 } from "node:child_process";
65197
65248
  function enableVtInput() {
65198
65249
  if (process.platform !== "win32" || !process.stdin.isTTY)
65199
65250
  return { ready: Promise.resolve(false), stop: () => Promise.resolve() };
@@ -65201,7 +65252,7 @@ function enableVtInput() {
65201
65252
  let exited = Promise.resolve();
65202
65253
  const ready = new Promise((resolve) => {
65203
65254
  try {
65204
- child = spawn11(SYSTEM.powershell(), scriptArgs("enable-vt-input.ps1", ["-ProcessId", String(process.pid)]), {
65255
+ child = spawn12(SYSTEM.powershell(), scriptArgs("enable-vt-input.ps1", ["-ProcessId", String(process.pid)]), {
65205
65256
  stdio: "ignore",
65206
65257
  windowsHide: true
65207
65258
  });
@@ -65260,30 +65311,118 @@ function unfinishedSequence(data) {
65260
65311
  return data.length;
65261
65312
  }
65262
65313
 
65263
- // src/shared/pty/ctrl-c.ts
65314
+ // src/shared/web/protocol.ts
65315
+ var CSRF_HEADER = "x-sandbox-web";
65316
+ var CSRF_VALUE = "1";
65317
+ var COOKIE_NAME = "sbxweb";
65318
+ var MIN_COLS = 10;
65319
+ var MAX_COLS = 500;
65320
+ var MIN_ROWS = 2;
65321
+ var MAX_ROWS = 300;
65322
+ var MAX_CLIENT_MESSAGE_BYTES = 256 * 1024;
65323
+ function parseClientMessage(text) {
65324
+ let value;
65325
+ try {
65326
+ value = JSON.parse(text);
65327
+ } catch {
65328
+ return null;
65329
+ }
65330
+ if (typeof value !== "object" || value === null) return null;
65331
+ const m = value;
65332
+ if (m.t !== "resize") return null;
65333
+ const { cols, rows } = m;
65334
+ if (!Number.isInteger(cols) || !Number.isInteger(rows)) return null;
65335
+ const c2 = cols;
65336
+ const r = rows;
65337
+ if (c2 < MIN_COLS || c2 > MAX_COLS || r < MIN_ROWS || r > MAX_ROWS) return null;
65338
+ return { t: "resize", cols: c2, rows: r };
65339
+ }
65340
+
65341
+ // src/shared/web/ctrl-c.ts
65264
65342
  var CTRL_C_CONFIRM_MS = 1500;
65265
65343
  var CTRL_C_HINT = "Ctrl+C again to send it to the program (Esc stops an agent)";
65266
- var OSC_TITLE = /\x1b\][02];([^\x07\x1b]{0,256})(?:\x07|\x1b\\)/g;
65267
- var CtrlCGuard = class {
65344
+ var CTRL_C_COPY_HINT = "Copied: Ctrl+C after selecting text is not sent to the program (Esc stops an agent)";
65345
+ var CtrlCDecider = class {
65346
+ constructor(confirm = true) {
65347
+ this.confirm = confirm;
65348
+ }
65349
+ confirm;
65268
65350
  armedAt = Number.NEGATIVE_INFINITY;
65269
- lastTitle = "";
65270
- /** What to send for one read from the keyboard: a lone Ctrl+C only when another came just before. */
65271
- push(chunk, nowMs) {
65272
- if (chunk.length !== 1 || chunk[0] !== 3) return { send: chunk, held: false };
65351
+ selected = false;
65352
+ /** Text was selected in the program: Ctrl+C is "copy" from now on. */
65353
+ selection() {
65354
+ this.selected = true;
65355
+ this.armedAt = Number.NEGATIVE_INFINITY;
65356
+ }
65357
+ /** Any other key or a click: Ctrl+C is an interrupt again. */
65358
+ otherInput() {
65359
+ this.selected = false;
65360
+ }
65361
+ /** What to do with a lone Ctrl+C pressed at `nowMs`. */
65362
+ ctrlC(nowMs) {
65363
+ if (this.selected) return "copy";
65364
+ if (!this.confirm) return "send";
65273
65365
  if (nowMs - this.armedAt <= CTRL_C_CONFIRM_MS) {
65274
65366
  this.armedAt = Number.NEGATIVE_INFINITY;
65275
- return { send: chunk, held: false };
65367
+ return "send";
65276
65368
  }
65277
65369
  this.armedAt = nowMs;
65278
- return { send: Buffer.alloc(0), held: true };
65370
+ return "hold";
65371
+ }
65372
+ };
65373
+ var MOUSE_REPORT = /\x1b\[<(\d{1,4});\d{1,5};\d{1,5}([Mm])|\x1b\[M([\s\S])[\s\S]{2}/g;
65374
+ function classifyInput(text) {
65375
+ let drag = false;
65376
+ let click = false;
65377
+ for (const m of text.matchAll(MOUSE_REPORT)) {
65378
+ const sgr = m[1] !== void 0;
65379
+ const b = sgr ? Number(m[1]) : (m[3] ?? " ").charCodeAt(0) - 32;
65380
+ if (b & 64) continue;
65381
+ if (b & 32) {
65382
+ if ((b & 3) !== 3) drag = true;
65383
+ } else if (sgr ? m[2] === "M" : (b & 3) !== 3) click = true;
65384
+ }
65385
+ return { mouse: drag ? "drag" : click ? "click" : null, keys: text.replace(MOUSE_REPORT, "").length > 0 };
65386
+ }
65387
+ function hasClipboardWrite(text) {
65388
+ return /\x1b\]0{0,2}52;/.test(text) || /\u009d0{0,2}52;/.test(text);
65389
+ }
65390
+
65391
+ // src/shared/pty/ctrl-c.ts
65392
+ var OSC_TITLE = /\x1b\][02];([^\x07\x1b]{0,256})(?:\x07|\x1b\\)/g;
65393
+ var CtrlCGuard = class {
65394
+ decider;
65395
+ lastTitle = "";
65396
+ constructor(options = {}) {
65397
+ this.decider = new CtrlCDecider(options.confirm !== false);
65398
+ }
65399
+ /** What to send for one read from the keyboard: a lone Ctrl+C only when it is not held back or taken as "copy". */
65400
+ push(chunk, nowMs) {
65401
+ if (chunk.length === 1 && chunk[0] === 3) {
65402
+ const action2 = this.decider.ctrlC(nowMs);
65403
+ if (action2 === "send") return { send: chunk, held: false, copied: false };
65404
+ return { send: Buffer.alloc(0), held: action2 === "hold", copied: action2 === "copy" };
65405
+ }
65406
+ const c2 = classifyInput(chunk.toString("latin1"));
65407
+ if (c2.keys || c2.mouse === "click") this.decider.otherInput();
65408
+ else if (c2.mouse === "drag") this.decider.selection();
65409
+ return { send: chunk, held: false, copied: false };
65410
+ }
65411
+ /** The clipboard changed since the last key: you just copied. */
65412
+ clipboardChanged() {
65413
+ this.decider.selection();
65414
+ }
65415
+ /** The program wrote to the clipboard: that is a selection too. */
65416
+ watchClipboard(raw) {
65417
+ if (hasClipboardWrite(raw)) this.decider.selection();
65279
65418
  }
65280
65419
  /** Remembers the title the program last set (from its output), to put it back after the hint. */
65281
65420
  watchOutput(text) {
65282
65421
  for (const m of text.matchAll(OSC_TITLE)) this.lastTitle = m[1];
65283
65422
  }
65284
65423
  /** The escape sequence that shows the hint, and the one that puts the program's title back. */
65285
- hintTitle() {
65286
- return "\x1B]0;" + CTRL_C_HINT + "\x07";
65424
+ hintTitle(kind = "hold") {
65425
+ return "\x1B]0;" + (kind === "copy" ? CTRL_C_COPY_HINT : CTRL_C_HINT) + "\x07";
65287
65426
  }
65288
65427
  restoreTitle() {
65289
65428
  return "\x1B]0;" + this.lastTitle.replace(/[\x00-\x1f\x7f]/g, "") + "\x07";
@@ -65487,6 +65626,7 @@ function runTerminalClient(socket, token, options = {}) {
65487
65626
  clearTimeout(joinTimer);
65488
65627
  clearTimeout(hintTimer);
65489
65628
  clearTimeout(vtTimer);
65629
+ clipboard.stop();
65490
65630
  socket.destroy();
65491
65631
  const tracked = mouse.tracking();
65492
65632
  const reset = "\x1B[0m\x1B[?25h" + MOUSE_OFF + (mouse.has(2004) ? "\x1B[?2004l" : "") + "\r\n";
@@ -65504,23 +65644,22 @@ function runTerminalClient(socket, token, options = {}) {
65504
65644
  if (data.length && !done) socket.write(encodeFrame(FrameType.Input, data));
65505
65645
  };
65506
65646
  const sendInput = (data) => write(keys.push(passthrough ? mouse.filter(data) : data));
65507
- const guard2 = options.confirmCtrlC ? new CtrlCGuard() : null;
65647
+ const guard2 = new CtrlCGuard({ confirm: options.confirmCtrlC !== false });
65508
65648
  let hintTimer;
65649
+ const clipboard = watchClipboard(() => guard2.clipboardChanged());
65509
65650
  const onInput = (chunk) => {
65510
65651
  if (options.detachKey !== void 0 && chunk.includes(options.detachKey)) {
65511
65652
  finish({ reason: "detach" });
65512
65653
  return;
65513
65654
  }
65514
- if (guard2) {
65515
- const decided = guard2.push(chunk, Date.now());
65516
- clearTimeout(hintTimer);
65517
- if (decided.held) {
65518
- stdout.write(guard2.hintTitle());
65519
- hintTimer = setTimeout(() => stdout.write(guard2.restoreTitle()), CTRL_C_CONFIRM_MS);
65520
- return;
65521
- }
65522
- if (decided.send.length === 1 && decided.send[0] === 3) stdout.write(guard2.restoreTitle());
65655
+ const decided = guard2.push(chunk, Date.now());
65656
+ clearTimeout(hintTimer);
65657
+ if (decided.held || decided.copied) {
65658
+ stdout.write(guard2.hintTitle(decided.copied ? "copy" : "hold"));
65659
+ hintTimer = setTimeout(() => stdout.write(guard2.restoreTitle()), CTRL_C_CONFIRM_MS);
65660
+ return;
65523
65661
  }
65662
+ if (decided.send.length === 1 && decided.send[0] === 3) stdout.write(guard2.restoreTitle());
65524
65663
  clearTimeout(joinTimer);
65525
65664
  sendInput(joiner.push(chunk));
65526
65665
  if (joiner.holding()) joinTimer = setTimeout(() => sendInput(joiner.flush()), JOIN_MS);
@@ -65553,9 +65692,10 @@ function runTerminalClient(socket, token, options = {}) {
65553
65692
  }
65554
65693
  for (const frame of frames) {
65555
65694
  if (frame.type === FrameType.Output) {
65695
+ guard2.watchClipboard(frame.payload.toString("latin1"));
65556
65696
  const out = filter.push(frame.payload);
65557
65697
  mouse.watch(out);
65558
- guard2?.watchOutput(out.toString("latin1"));
65698
+ guard2.watchOutput(out.toString("latin1"));
65559
65699
  stdout.write(out);
65560
65700
  } else if (frame.type === FrameType.Console) passthrough = frame.payload.toString("latin1") === "conpty.dll";
65561
65701
  else if (frame.type === FrameType.Restarting) {
@@ -73033,7 +73173,7 @@ var AccessStore = class _AccessStore {
73033
73173
  };
73034
73174
 
73035
73175
  // src/host/remote/door-client.ts
73036
- import { spawn as spawn12 } from "node:child_process";
73176
+ import { spawn as spawn13 } from "node:child_process";
73037
73177
  var NOT_OWNER = 3;
73038
73178
  var NO_PIPE = 4;
73039
73179
  var NO_USER = 5;
@@ -73060,7 +73200,7 @@ async function knockOnDoor(storageRoot, owner) {
73060
73200
  const exe = hostToolsExe(storageRoot);
73061
73201
  if (!exe) fail("Windows sign-in needs HostTools, which could not be built on this PC.", "Run sandbox doctor.");
73062
73202
  return new Promise((resolve) => {
73063
- const child = spawn12(exe, ["door-client", "--owner", owner], { windowsHide: true });
73203
+ const child = spawn13(exe, ["door-client", "--owner", owner], { windowsHide: true });
73064
73204
  let out = "";
73065
73205
  child.stdout.on("data", (d) => {
73066
73206
  if (out.length < 1024) out += d.toString("utf8");
@@ -73174,7 +73314,7 @@ import crypto22 from "node:crypto";
73174
73314
  import fs119 from "node:fs";
73175
73315
  import os23 from "node:os";
73176
73316
  import path168 from "node:path";
73177
- import { spawn as spawn13 } from "node:child_process";
73317
+ import { spawn as spawn14 } from "node:child_process";
73178
73318
 
73179
73319
  // src/host/remote/lan.ts
73180
73320
  import net7 from "node:net";
@@ -73266,7 +73406,7 @@ function removeRuleRequest() {
73266
73406
  return { action: "remove", ruleName: FIREWALL_RULE_NAME };
73267
73407
  }
73268
73408
  var spawnRunner3 = (exe, args, timeoutMs) => new Promise((resolve) => {
73269
- const child = spawn13(exe, [...args], { windowsHide: true });
73409
+ const child = spawn14(exe, [...args], { windowsHide: true });
73270
73410
  let stdout = "";
73271
73411
  let stderr = "";
73272
73412
  const timer = setTimeout(() => child.kill(), timeoutMs);
@@ -73343,35 +73483,6 @@ import https from "node:https";
73343
73483
 
73344
73484
  // src/host/web/auth.ts
73345
73485
  import crypto23 from "node:crypto";
73346
-
73347
- // src/shared/web/protocol.ts
73348
- var CSRF_HEADER = "x-sandbox-web";
73349
- var CSRF_VALUE = "1";
73350
- var COOKIE_NAME = "sbxweb";
73351
- var MIN_COLS = 10;
73352
- var MAX_COLS = 500;
73353
- var MIN_ROWS = 2;
73354
- var MAX_ROWS = 300;
73355
- var MAX_CLIENT_MESSAGE_BYTES = 256 * 1024;
73356
- function parseClientMessage(text) {
73357
- let value;
73358
- try {
73359
- value = JSON.parse(text);
73360
- } catch {
73361
- return null;
73362
- }
73363
- if (typeof value !== "object" || value === null) return null;
73364
- const m = value;
73365
- if (m.t !== "resize") return null;
73366
- const { cols, rows } = m;
73367
- if (!Number.isInteger(cols) || !Number.isInteger(rows)) return null;
73368
- const c2 = cols;
73369
- const r = rows;
73370
- if (c2 < MIN_COLS || c2 > MAX_COLS || r < MIN_ROWS || r > MAX_ROWS) return null;
73371
- return { t: "resize", cols: c2, rows: r };
73372
- }
73373
-
73374
- // src/host/web/auth.ts
73375
73486
  var COOKIE_LIFETIME_MS = 12 * 60 * 60 * 1e3;
73376
73487
  var MAX_COOKIES = 50;
73377
73488
  var MAX_FAILURES = 8;
@@ -73451,17 +73562,17 @@ function cookieHeader(value) {
73451
73562
  }
73452
73563
 
73453
73564
  // src/host/web/browser.ts
73454
- import { spawn as spawn14 } from "node:child_process";
73565
+ import { spawn as spawn15 } from "node:child_process";
73455
73566
  function openBrowser(url2) {
73456
73567
  refuseInSelfTest("open a browser");
73457
73568
  if (!/^http:\/\/127\.0\.0\.1:\d{1,5}\/(#[kw]=[0-9a-f]{64})?$/.test(url2)) throw new Error("Not a web UI address.");
73458
- spawn14(SYSTEM.cmd(), ["/c", "start", "", url2], { detached: true, stdio: "ignore", windowsHide: true }).unref();
73569
+ spawn15(SYSTEM.cmd(), ["/c", "start", "", url2], { detached: true, stdio: "ignore", windowsHide: true }).unref();
73459
73570
  }
73460
73571
  var ISSUE_URL = /^https:\/\/github\.com\/[\w.-]+\/[\w.-]+\/issues\/new\?[\w%.~&=+-]*$/;
73461
73572
  function openIssuePage(url2) {
73462
73573
  refuseInSelfTest("open a browser");
73463
73574
  if (!ISSUE_URL.test(url2) || url2.length > 2e3) throw new Error("Not a GitHub new-issue address.");
73464
- spawn14(system32("rundll32.exe"), ["url.dll,FileProtocolHandler", url2], {
73575
+ spawn15(system32("rundll32.exe"), ["url.dll,FileProtocolHandler", url2], {
73465
73576
  detached: true,
73466
73577
  stdio: "ignore",
73467
73578
  windowsHide: true
@@ -74441,7 +74552,7 @@ var WebRunner = class {
74441
74552
  };
74442
74553
 
74443
74554
  // src/host/remote/windows-helper.ts
74444
- import { spawn as spawn15 } from "node:child_process";
74555
+ import { spawn as spawn16 } from "node:child_process";
74445
74556
  import readline3 from "node:readline";
74446
74557
  var SID2 = /^S-1-[0-9-]{1,180}$/;
74447
74558
  var MEDIUM_INTEGRITY = 8192;
@@ -74480,7 +74591,7 @@ var HostToolsHelper = class _HostToolsHelper {
74480
74591
  answer = async () => "refused Windows sign-in is off.";
74481
74592
  problem = () => void 0;
74482
74593
  constructor(exe, pipe2) {
74483
- this.child = spawn15(exe, ["web-helper", "--pipe", pipe2 ?? "", "--server-pid", String(process.pid)], {
74594
+ this.child = spawn16(exe, ["web-helper", "--pipe", pipe2 ?? "", "--server-pid", String(process.pid)], {
74484
74595
  windowsHide: true,
74485
74596
  stdio: ["pipe", "pipe", "pipe"]
74486
74597
  });
@@ -80151,6 +80262,9 @@ var SETTINGS = {
80151
80262
  theme: "dark",
80152
80263
  firstWindowPreference: "defaultProfile",
80153
80264
  confirmCloseAllTabs: false,
80265
+ // Copied the moment the mouse is released, so an agent's redraw cannot clear the selection first.
80266
+ copyOnSelect: true,
80267
+ copyFormatting: "none",
80154
80268
  // New tabs pick up the PATH and variables the runner writes machine-wide after the window opened.
80155
80269
  compatibility: { reloadEnvironmentVariables: true },
80156
80270
  warning: { confirmCloseAllTabs: false, largePaste: false, multiLinePaste: false },
@@ -80189,6 +80303,10 @@ function withReloadEnvironment(settings) {
80189
80303
  if (compatibility?.reloadEnvironmentVariables === true) return null;
80190
80304
  return { ...settings, compatibility: { ...compatibility, reloadEnvironmentVariables: true } };
80191
80305
  }
80306
+ function withCopyOnSelect(settings) {
80307
+ if ("copyOnSelect" in settings) return null;
80308
+ return { ...settings, copyOnSelect: true };
80309
+ }
80192
80310
  function syncPwshProfile(dataPath, allowed) {
80193
80311
  const file2 = path185.join(terminalDir(dataPath), "settings", "settings.json");
80194
80312
  assertTestWritable2(file2);
@@ -80206,15 +80324,18 @@ function syncPwshProfile(dataPath, allowed) {
80206
80324
  }
80207
80325
  const withProfile = withPwshProfile(parsed, visible);
80208
80326
  if (withProfile) {
80209
- writeJson(file2, withReloadEnvironment(withProfile) ?? withProfile);
80327
+ const reloaded = withReloadEnvironment(withProfile) ?? withProfile;
80328
+ writeJson(file2, withCopyOnSelect(reloaded) ?? reloaded);
80210
80329
  return;
80211
80330
  }
80212
- if (withReloadEnvironment(parsed)) {
80213
- const edits = modify(text, ["compatibility", "reloadEnvironmentVariables"], true, {
80214
- formattingOptions: { insertSpaces: true, tabSize: 4 }
80215
- });
80216
- writeFileAtomic(file2, applyEdits(text, edits));
80217
- }
80331
+ const missing = [];
80332
+ if (withReloadEnvironment(parsed)) missing.push([["compatibility", "reloadEnvironmentVariables"], true]);
80333
+ if (withCopyOnSelect(parsed)) missing.push([["copyOnSelect"], true]);
80334
+ if (missing.length === 0) return;
80335
+ let edited = text;
80336
+ for (const [at, value] of missing)
80337
+ edited = applyEdits(edited, modify(edited, at, value, { formattingOptions: { insertSpaces: true, tabSize: 4 } }));
80338
+ writeFileAtomic(file2, edited);
80218
80339
  }
80219
80340
  async function ensureTerminal(dataPath, refresh, options = {}) {
80220
80341
  const result = await installTerminal(dataPath, refresh);
@@ -85335,7 +85456,7 @@ async function attachOpened(ctx, session) {
85335
85456
  nextStep("sandbox attach " + session);
85336
85457
  return 0;
85337
85458
  }
85338
- const { attach: attach2 } = await import("./chunk-FA5VWLP4.js");
85459
+ const { attach: attach2 } = await import("./chunk-UZ5S3EMB.js");
85339
85460
  return attach2({ ...ctx, options: { ...ctx.options, positionals: [session] } });
85340
85461
  }
85341
85462
  function definitionHandler(def) {
@@ -86295,6 +86416,8 @@ export {
86295
86416
  NAV,
86296
86417
  withOperationCommands,
86297
86418
  SandboxChannel,
86419
+ CSRF_HEADER,
86420
+ CSRF_VALUE,
86298
86421
  reloadSettings,
86299
86422
  esm_default4 as esm_default,
86300
86423
  esm_default11 as esm_default2,
@@ -86354,8 +86477,6 @@ export {
86354
86477
  settingRow,
86355
86478
  ProjectsService,
86356
86479
  StorageService,
86357
- CSRF_HEADER,
86358
- CSRF_VALUE,
86359
86480
  openBrowser,
86360
86481
  import_websocket,
86361
86482
  INSTALL,
@@ -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-RU7XRN62.js";
4
+ } from "./chunk-CB44HTTN.js";
5
5
  export {
6
6
  attach
7
7
  };
@@ -258,7 +258,7 @@ import {
258
258
  withOperationCommands,
259
259
  working,
260
260
  writeJson
261
- } from "./chunk-RU7XRN62.js";
261
+ } from "./chunk-CB44HTTN.js";
262
262
 
263
263
  // node_modules/commander/lib/error.js
264
264
  var require_error = __commonJS({
@@ -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-4M5SZZ5P.js";
6
- import "./chunk-RU7XRN62.js";
5
+ } from "./chunk-ZMZI3625.js";
6
+ import "./chunk-CB44HTTN.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-4M5SZZ5P.js";
6
- import "./chunk-RU7XRN62.js";
5
+ } from "./chunk-ZMZI3625.js";
6
+ import "./chunk-CB44HTTN.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-4M5SZZ5P.js";
6
- import "./chunk-RU7XRN62.js";
5
+ } from "./chunk-ZMZI3625.js";
6
+ import "./chunk-CB44HTTN.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-4M5SZZ5P.js";
6
- import "./chunk-RU7XRN62.js";
5
+ } from "./chunk-ZMZI3625.js";
6
+ import "./chunk-CB44HTTN.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-4M5SZZ5P.js";
6
- import "./chunk-RU7XRN62.js";
5
+ } from "./chunk-ZMZI3625.js";
6
+ import "./chunk-CB44HTTN.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-4M5SZZ5P.js";
6
- import "./chunk-RU7XRN62.js";
5
+ } from "./chunk-ZMZI3625.js";
6
+ import "./chunk-CB44HTTN.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-4M5SZZ5P.js";
6
- import "./chunk-RU7XRN62.js";
5
+ } from "./chunk-ZMZI3625.js";
6
+ import "./chunk-CB44HTTN.js";
7
7
 
8
8
  // src/bin/sbx-show.ts
9
9
  run(process.argv.slice(2), { command: "show" });
@@ -48,7 +48,7 @@ export const SandboxSchema = z.strictObject({
48
48
  .describe('The last resort that keeps the sandbox up, in MB below its commit limit (RAM plus paging file): builds are slowed down long before (sandbox.pressureResponse); only if everything the agents start together would take the sandbox to the very edge of its memory, where Windows inside it would start failing, is a build refused memory instead of Windows. "auto" = the larger of 1536 MB and 3 % of the commit limit; "none" = no backstop. Yours only: a project file cannot change it.'),
49
49
  pressureResponse: z
50
50
  .boolean()
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.'),
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. A memory gate also holds new compilers and linkers for a moment when the memory of the build programs grows faster than 4 GB in 3 seconds (3 GB with vGPU), and lets them go one at a time. Yours only: a project file cannot change it.'),
52
52
  vGpu: z
53
53
  .boolean()
54
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)."),
@@ -23,6 +23,7 @@ export function buildHeartbeat(snapshot, sessionCount, uptimeSeconds, freeBytes,
23
23
  ...(snapshot?.diskQueue !== null && snapshot?.diskQueue !== undefined
24
24
  ? { diskQueueReported: snapshot.diskQueue }
25
25
  : {}),
26
+ ...(snapshot && snapshot.held > 0 ? { held: Math.min(1024, snapshot.held) } : {}),
26
27
  phase: ending ? 'ending' : sessionCount > 0 ? 'working' : 'ready',
27
28
  };
28
29
  }