windows-development-sandbox 1.27.1 → 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 (121) hide show
  1. package/dist/app/context.d.ts +5 -0
  2. package/dist/app/context.js +13 -0
  3. package/dist/app/features/accounts/accounts.def.d.ts +16 -0
  4. package/dist/app/features/accounts/accounts.run.d.ts +4 -0
  5. package/dist/app/features/accounts/services.def.d.ts +15 -1
  6. package/dist/app/features/accounts/services.def.js +67 -31
  7. package/dist/app/features/accounts/services.run.d.ts +7 -2
  8. package/dist/app/features/accounts/services.run.js +25 -6
  9. package/dist/app/features/defs.d.ts +17 -0
  10. package/dist/app/registry.d.ts +1 -1
  11. package/dist/app/registry.js +15 -2
  12. package/dist/bin/agent-sandbox.js +2 -2
  13. package/dist/bin/{chunk-RSIX7C6D.js → chunk-CB44HTTN.js} +444 -175
  14. package/dist/bin/{chunk-4TLSWPG7.js → chunk-UZ5S3EMB.js} +1 -1
  15. package/dist/bin/{chunk-QUHNXHVG.js → chunk-ZMZI3625.js} +46 -41
  16. package/dist/bin/claude-host.js +2 -2
  17. package/dist/bin/claude-sbx.js +2 -2
  18. package/dist/bin/codex-sbx.js +2 -2
  19. package/dist/bin/gemini-sbx.js +2 -2
  20. package/dist/bin/sandbox.js +2 -2
  21. package/dist/bin/sbx-hide.js +2 -2
  22. package/dist/bin/sbx-show.js +2 -2
  23. package/dist/cli/menu/menu.js +6 -1
  24. package/dist/cli/menu/pages/accounts-page.js +9 -5
  25. package/dist/core/schema/sandbox.js +1 -1
  26. package/dist/guest/apps/app-installer.js +7 -5
  27. package/dist/guest/apps/comet-unpack.js +2 -2
  28. package/dist/guest/daemon/heartbeat.js +1 -0
  29. package/dist/guest/governor/core.d.ts +53 -1
  30. package/dist/guest/governor/core.js +95 -1
  31. package/dist/guest/governor/decide.d.ts +4 -0
  32. package/dist/guest/governor/decide.js +2 -0
  33. package/dist/guest/governor/governor.js +23 -9
  34. package/dist/guest/runtime/pools.d.ts +4 -4
  35. package/dist/guest/runtime/pools.js +4 -15
  36. package/dist/guest/runtime/process.d.ts +5 -2
  37. package/dist/guest/runtime/process.js +23 -3
  38. package/dist/guest/toolchains/engines/epic-launcher.d.ts +6 -0
  39. package/dist/guest/toolchains/engines/epic-launcher.js +19 -1
  40. package/dist/guest/toolchains/engines/unreal-engines.js +2 -1
  41. package/dist/guest/toolchains/installers.d.ts +3 -3
  42. package/dist/guest/toolchains/installers.js +5 -5
  43. package/dist/guest/toolchains/native/unreal.js +22 -3
  44. package/dist/guest/toolchains/native/vcruntime.js +2 -2
  45. package/dist/guest/toolchains/native/vs-persist.d.ts +2 -2
  46. package/dist/guest/toolchains/runtimes.js +7 -6
  47. package/dist/guest/toolchains/signature.d.ts +2 -0
  48. package/dist/guest/toolchains/signature.js +9 -2
  49. package/dist/guest/toolchains/step-scheduler.js +1 -1
  50. package/dist/host/accounts/deliver.js +15 -7
  51. package/dist/host/life/crash-report.d.ts +4 -0
  52. package/dist/host/life/crash-report.js +30 -1
  53. package/dist/host/life/last-end.js +1 -0
  54. package/dist/host/pc-installs/discover.js +5 -2
  55. package/dist/host/pc-installs/unreal.d.ts +3 -1
  56. package/dist/host/pc-installs/unreal.js +27 -1
  57. package/dist/host/tools/windows-terminal.d.ts +4 -1
  58. package/dist/host/tools/windows-terminal.js +25 -9
  59. package/dist/runtime/agent.js +39 -23
  60. package/dist/runtime/daemon.js +279 -121
  61. package/dist/runtime/db.js +37 -17
  62. package/dist/runtime/main.js +340 -109
  63. package/dist/runtime/netproxy.js +31 -13
  64. package/dist/runtime/tool.js +37 -16
  65. package/dist/runtime/view.js +150 -38
  66. package/dist/selftest/agent-paths.test.js +3 -1
  67. package/dist/selftest/app.test.js +20 -2
  68. package/dist/selftest/boot-runner.test.js +15 -1
  69. package/dist/selftest/channel.test.js +33 -0
  70. package/dist/selftest/engines.test.js +22 -1
  71. package/dist/selftest/heavy-load.test.js +116 -4
  72. package/dist/selftest/live-terminal.test.js +103 -0
  73. package/dist/selftest/menu-screens.js +52 -1
  74. package/dist/selftest/menu.test.js +29 -3
  75. package/dist/selftest/personalization.test.js +2 -1
  76. package/dist/selftest/service-accounts.test.js +77 -2
  77. package/dist/selftest/support/engine-golden.js +2 -0
  78. package/dist/selftest/ui-text.test.js +4 -0
  79. package/dist/shared/accounts/kinds.js +9 -9
  80. package/dist/shared/apps/catalogue-gamedev.js +1 -0
  81. package/dist/shared/apps/catalogue.d.ts +2 -0
  82. package/dist/shared/channel/schema.d.ts +2 -0
  83. package/dist/shared/channel/schema.js +4 -0
  84. package/dist/shared/concurrency/pools.js +1 -1
  85. package/dist/shared/download/archive.js +4 -2
  86. package/dist/shared/download/index.d.ts +1 -1
  87. package/dist/shared/download/index.js +1 -1
  88. package/dist/shared/download/spawn.d.ts +7 -1
  89. package/dist/shared/download/spawn.js +16 -1
  90. package/dist/shared/memory/index.d.ts +1 -0
  91. package/dist/shared/memory/index.js +1 -0
  92. package/dist/shared/memory/policy.d.ts +12 -0
  93. package/dist/shared/memory/policy.js +4 -0
  94. package/dist/shared/memory/unreal-build.d.ts +2 -0
  95. package/dist/shared/memory/unreal-build.js +21 -0
  96. package/dist/shared/paths/pc-installs.d.ts +8 -0
  97. package/dist/shared/pty/clipboard-watch.d.ts +4 -0
  98. package/dist/shared/pty/clipboard-watch.js +40 -0
  99. package/dist/shared/pty/ctrl-c.d.ts +13 -7
  100. package/dist/shared/pty/ctrl-c.js +35 -19
  101. package/dist/shared/pty/index.d.ts +1 -0
  102. package/dist/shared/pty/index.js +1 -0
  103. package/dist/shared/pty/terminal.d.ts +1 -1
  104. package/dist/shared/pty/terminal.js +16 -13
  105. package/dist/shared/unreal/launcher-files.d.ts +7 -0
  106. package/dist/shared/unreal/launcher-files.js +32 -1
  107. package/dist/shared/web/ctrl-c.d.ts +26 -0
  108. package/dist/shared/web/ctrl-c.js +64 -0
  109. package/dist/shared/web/index.d.ts +1 -0
  110. package/dist/shared/web/index.js +1 -0
  111. package/dist/web/index.html +1 -1
  112. package/dist/web/{main-VPMYTSUL.js → main-7NY4X6YU.js} +67 -67
  113. package/native/csharp/HcsHost/AssemblyInfo.cs +3 -3
  114. package/native/csharp/HostTools/AssemblyInfo.cs +3 -3
  115. package/native/csharp/StorageHelper/AssemblyInfo.cs +3 -3
  116. package/package.json +1 -1
  117. package/sandbox/csharp/ClipboardWatch.cs +59 -0
  118. package/sandbox/csharp/MemGov.cs +752 -7
  119. package/sandbox/scripts/watch-clipboard.ps1 +8 -0
  120. package/schemas/config.schema.json +10 -10
  121. package/schemas/sandbox-config.schema.json +9 -9
@@ -22806,10 +22806,10 @@ var esm_default2 = createPrompt((config3, done) => {
22806
22806
  const answer = theme.style.answer(theme.style.renderSelectedChoices(selection, items));
22807
22807
  return [prefix, message, answer].filter(Boolean).join(" ");
22808
22808
  }
22809
- let helpLine;
22809
+ let helpLine2;
22810
22810
  if (theme.helpMode !== "never" && instructions !== false) {
22811
22811
  if (typeof instructions === "string") {
22812
- helpLine = instructions;
22812
+ helpLine2 = instructions;
22813
22813
  } else {
22814
22814
  const keys = [
22815
22815
  ["\u2191\u2193", "navigate"],
@@ -22820,7 +22820,7 @@ var esm_default2 = createPrompt((config3, done) => {
22820
22820
  if (shortcuts.invert)
22821
22821
  keys.push([shortcuts.invert, "invert"]);
22822
22822
  keys.push(["\u23CE", "submit"]);
22823
- helpLine = theme.style.keysHelpTip(keys);
22823
+ helpLine2 = theme.style.keysHelpTip(keys);
22824
22824
  }
22825
22825
  }
22826
22826
  const lines = [
@@ -22829,7 +22829,7 @@ var esm_default2 = createPrompt((config3, done) => {
22829
22829
  " ",
22830
22830
  description ? theme.style.description(description) : "",
22831
22831
  errorMsg ? theme.style.error(errorMsg) : "",
22832
- helpLine
22832
+ helpLine2
22833
22833
  ].filter(Boolean).join("\n").trimEnd();
22834
22834
  return `${lines}${cursorHide}`;
22835
22835
  });
@@ -23687,13 +23687,13 @@ var esm_default10 = createPrompt((config3, done) => {
23687
23687
  }
23688
23688
  });
23689
23689
  const message = theme.style.message(config3.message, status);
23690
- let helpLine;
23690
+ let helpLine2;
23691
23691
  if (theme.helpMode !== "never") {
23692
23692
  if (config3.instructions) {
23693
23693
  const { pager, navigation } = config3.instructions;
23694
- helpLine = theme.style.help(searchResults.length > pageSize ? pager : navigation);
23694
+ helpLine2 = theme.style.help(searchResults.length > pageSize ? pager : navigation);
23695
23695
  } else {
23696
- helpLine = theme.style.keysHelpTip([
23696
+ helpLine2 = theme.style.keysHelpTip([
23697
23697
  ["\u2191\u2193", "navigate"],
23698
23698
  ["\u23CE", "select"]
23699
23699
  ]);
@@ -23735,7 +23735,7 @@ var esm_default10 = createPrompt((config3, done) => {
23735
23735
  error62 ?? page,
23736
23736
  " ",
23737
23737
  description ? theme.style.description(description) : "",
23738
- helpLine
23738
+ helpLine2
23739
23739
  ].filter(Boolean).join("\n").trimEnd();
23740
23740
  return [header, body];
23741
23741
  });
@@ -23857,13 +23857,13 @@ var esm_default11 = createPrompt((config3, done) => {
23857
23857
  clearTimeout(searchTimeoutRef.current);
23858
23858
  }, []);
23859
23859
  const message = theme.style.message(config3.message, status);
23860
- let helpLine;
23860
+ let helpLine2;
23861
23861
  if (theme.helpMode !== "never") {
23862
23862
  if (config3.instructions) {
23863
23863
  const { pager, navigation } = config3.instructions;
23864
- helpLine = theme.style.help(items.length > pageSize ? pager : navigation);
23864
+ helpLine2 = theme.style.help(items.length > pageSize ? pager : navigation);
23865
23865
  } else {
23866
- helpLine = theme.style.keysHelpTip([
23866
+ helpLine2 = theme.style.keysHelpTip([
23867
23867
  ["\u2191\u2193", "navigate"],
23868
23868
  ["\u23CE", "select"]
23869
23869
  ]);
@@ -23899,7 +23899,7 @@ var esm_default11 = createPrompt((config3, done) => {
23899
23899
  page,
23900
23900
  " ",
23901
23901
  description ? theme.style.description(description) : "",
23902
- helpLine
23902
+ helpLine2
23903
23903
  ].filter(Boolean).join("\n").trimEnd();
23904
23904
  return `${lines}${cursorHide}`;
23905
23905
  });
@@ -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)."
@@ -46814,6 +46814,7 @@ var GAMEDEV_APPS = [
46814
46814
  size: "100 MB",
46815
46815
  source: { github: "f3d-app/f3d", asset: "^F3D-[\\d.]+-Windows-x86_64\\.zip$" },
46816
46816
  exe: "f3d.exe",
46817
+ onPath: true,
46817
46818
  needs: ["mesa"]
46818
46819
  },
46819
46820
  {
@@ -47961,7 +47962,7 @@ var SERVICE_SPECS = {
47961
47962
  },
47962
47963
  steam: {
47963
47964
  kind: "steam",
47964
- label: "Steam (SteamCMD)",
47965
+ label: "Steam",
47965
47966
  usedBy: ["steam"],
47966
47967
  sources: ["file", "sandbox"],
47967
47968
  singleHolder: true,
@@ -47997,7 +47998,7 @@ var SERVICE_SPECS = {
47997
47998
  },
47998
47999
  cfx: {
47999
48000
  kind: "cfx",
48000
- label: "Cfx.re (FiveM / RedM server key)",
48001
+ label: "FiveM key",
48001
48002
  usedBy: ["fivem"],
48002
48003
  sources: ["typed", "file"],
48003
48004
  singleHolder: false,
@@ -48026,7 +48027,7 @@ var SERVICE_SPECS = {
48026
48027
  },
48027
48028
  keystore: {
48028
48029
  kind: "keystore",
48029
- label: "Android upload keystore",
48030
+ label: "Android keystore",
48030
48031
  usedBy: ["android"],
48031
48032
  sources: ["file"],
48032
48033
  singleHolder: false,
@@ -48041,7 +48042,7 @@ var SERVICE_SPECS = {
48041
48042
  },
48042
48043
  play: {
48043
48044
  kind: "play",
48044
- label: "Google Play (service account)",
48045
+ label: "Google Play",
48045
48046
  usedBy: ["android"],
48046
48047
  sources: ["file"],
48047
48048
  singleHolder: false,
@@ -48052,7 +48053,7 @@ var SERVICE_SPECS = {
48052
48053
  },
48053
48054
  expo: {
48054
48055
  kind: "expo",
48055
- label: "Expo (EAS)",
48056
+ label: "Expo",
48056
48057
  usedBy: ["reactNative"],
48057
48058
  sources: ["typed"],
48058
48059
  singleHolder: false,
@@ -48063,7 +48064,7 @@ var SERVICE_SPECS = {
48063
48064
  },
48064
48065
  appstore: {
48065
48066
  kind: "appstore",
48066
- label: "App Store Connect API key",
48067
+ label: "App Store Connect",
48067
48068
  usedBy: ["reactNative", "flutter"],
48068
48069
  sources: ["file", "typed"],
48069
48070
  singleHolder: false,
@@ -48077,7 +48078,7 @@ var SERVICE_SPECS = {
48077
48078
  },
48078
48079
  perplexity: {
48079
48080
  kind: "perplexity",
48080
- label: "Perplexity API key",
48081
+ label: "Perplexity API",
48081
48082
  usedBy: ["perplexity"],
48082
48083
  sources: ["pc", "file", "typed"],
48083
48084
  singleHolder: false,
@@ -48101,7 +48102,7 @@ var SERVICE_SPECS = {
48101
48102
  },
48102
48103
  comet: {
48103
48104
  kind: "comet",
48104
- label: "Comet (Perplexity browser)",
48105
+ label: "Comet",
48105
48106
  usedBy: ["comet"],
48106
48107
  sources: ["sandbox"],
48107
48108
  singleHolder: false,
@@ -48117,7 +48118,7 @@ var SERVICE_SPECS = {
48117
48118
  },
48118
48119
  perplexityapp: {
48119
48120
  kind: "perplexityapp",
48120
- label: "Perplexity desktop app",
48121
+ label: "Perplexity app",
48121
48122
  usedBy: ["perplexityapp"],
48122
48123
  sources: ["sandbox"],
48123
48124
  singleHolder: false,
@@ -48940,6 +48941,8 @@ var PressureStatusSchema = external_exports.object({
48940
48941
  lowered: external_exports.number().int().min(0).max(1024),
48941
48942
  /** Compilers waiting their turn right now (name, process id, seconds until they may go on). */
48942
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(),
48943
48946
  /** The CPU share the compilers get together (percent; 100 = no cap). */
48944
48947
  cpuPercent: external_exports.number().int().min(0).max(100).optional(),
48945
48948
  /** The PC itself asked for slowing down (its memory or CPU is low). */
@@ -48960,6 +48963,8 @@ var HeartbeatSchema = external_exports.object({
48960
48963
  topProcesses: external_exports.array(external_exports.object({ name: Text(64), privateMb: external_exports.number().int().min(0).max(1e7) })).max(5),
48961
48964
  /** The disk queue this PC last reported while asking the sandbox to slow down. */
48962
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(),
48963
48968
  /** "ending": the sandbox is shutting down in order (idle shutdown, sandbox stop, Windows shutting down). */
48964
48969
  phase: external_exports.enum(["ready", "working", "ending"])
48965
48970
  });
@@ -53742,7 +53747,7 @@ var clock = (iso) => {
53742
53747
  };
53743
53748
  function heartbeatLine(hb) {
53744
53749
  const top = hb.topProcesses.map((p) => safeText(p.name) + " " + p.privateMb + " MB").join(", ");
53745
- 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 : "");
53746
53751
  }
53747
53752
  function guessReason(hb) {
53748
53753
  if (hb && hb.diskQueueReported !== void 0 && hb.hostStep > 0)
@@ -53816,7 +53821,12 @@ function findingLines(events) {
53816
53821
  );
53817
53822
  if (has2((e) => lower(e).startsWith("hyper-v")))
53818
53823
  lines.push("Hyper-V events: the sandbox's virtual machine (or the service that runs it) reported a problem.");
53819
- 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))
53820
53830
  lines.push("A crash report names vmwp.exe or vmmem, the programs that hold the sandbox's VM.");
53821
53831
  return lines;
53822
53832
  }
@@ -53866,6 +53876,17 @@ function hostMemoryTail(storageRoot, dataPath) {
53866
53876
  return "";
53867
53877
  }
53868
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
+ }
53869
53890
  function crashReportText(cause, events, memoryLog, lastSeen, extra = {}) {
53870
53891
  const seen = /* @__PURE__ */ new Set();
53871
53892
  const unique = events.filter((e) => {
@@ -53881,6 +53902,7 @@ function crashReportText(cause, events, memoryLog, lastSeen, extra = {}) {
53881
53902
  const hostLines = (extra.hostMemoryLog ?? "").split(/\r?\n/).filter(Boolean).slice(-20).map((l) => " " + safeText(l));
53882
53903
  const freeze = freezeLine(cause, lastSeen);
53883
53904
  const meaning = findingLines(unique);
53905
+ const fine = memoryWasFineLine(extra.hostMemoryLog ?? "");
53884
53906
  const dumps = extra.dumps ?? [];
53885
53907
  const vgpu = extra.vgpu ? ["vGPU was on for this sandbox. " + vgpuWarning("its launch settings"), ""] : [];
53886
53908
  return [
@@ -53894,6 +53916,7 @@ function crashReportText(cause, events, memoryLog, lastSeen, extra = {}) {
53894
53916
  ...vgpu,
53895
53917
  "What that usually means:",
53896
53918
  ...meaning.length ? meaning.map((l) => " " + l) : [" (no memory, blue screen or hardware event was logged)"],
53919
+ ...fine ? [" " + fine] : [],
53897
53920
  "",
53898
53921
  "Crash dumps Windows left (C:\\Windows\\Minidump, MEMORY.DMP):",
53899
53922
  ...dumps.length ? dumps.map((d) => " " + d.name + " " + d.at) : [" (none from that time)"],
@@ -56560,7 +56583,7 @@ function clearLiveRecord(storageRoot, dataPath) {
56560
56583
  import fs71 from "node:fs";
56561
56584
  import path94 from "node:path";
56562
56585
  import crypto14 from "node:crypto";
56563
- import os16 from "node:os";
56586
+ import os17 from "node:os";
56564
56587
 
56565
56588
  // src/shared/accounts/fingerprint.ts
56566
56589
  import crypto8 from "node:crypto";
@@ -58861,10 +58884,20 @@ function scriptArgs(name2, args = []) {
58861
58884
  }
58862
58885
 
58863
58886
  // src/shared/download/spawn.ts
58887
+ import os14 from "node:os";
58864
58888
  import { spawn as spawn9 } from "node:child_process";
58889
+ function lowerPriority(pid) {
58890
+ if (pid === void 0) return;
58891
+ try {
58892
+ os14.setPriority(pid, os14.constants.priority.PRIORITY_BELOW_NORMAL);
58893
+ } catch {
58894
+ }
58895
+ }
58865
58896
  function spawnAsync(file2, args, options = {}) {
58897
+ const { lowPriority, ...spawnOptions } = options;
58866
58898
  return new Promise((resolve, reject) => {
58867
- const child = spawn9(file2, args, { stdio: "ignore", windowsHide: true, ...options });
58899
+ const child = spawn9(file2, args, { stdio: "ignore", windowsHide: true, ...spawnOptions });
58900
+ if (lowPriority) lowerPriority(child.pid);
58868
58901
  child.once("error", reject);
58869
58902
  child.once("close", (code) => resolve(code ?? -1));
58870
58903
  });
@@ -58885,13 +58918,16 @@ function extractArchive(archive, destination, members2 = []) {
58885
58918
  }
58886
58919
  async function extract(archive, destination, members2) {
58887
58920
  fs57.mkdirSync(destination, { recursive: true });
58888
- let status = await spawnAsync(SYSTEM.tar(), ["-xf", archive, "-C", destination, ...members2]);
58921
+ let status = await spawnAsync(SYSTEM.tar(), ["-xf", archive, "-C", destination, ...members2], {
58922
+ lowPriority: true
58923
+ });
58889
58924
  if (status !== 0 && /\.(zip|nupkg)$/i.test(archive)) {
58890
58925
  const zip = archive.endsWith(".zip") ? archive : archive + ".zip";
58891
58926
  if (zip !== archive) fs57.copyFileSync(archive, zip);
58892
58927
  status = await spawnAsync(
58893
58928
  SYSTEM.powershell(),
58894
- scriptArgs("expand-archive.ps1", ["-Archive", zip, "-Destination", destination])
58929
+ scriptArgs("expand-archive.ps1", ["-Archive", zip, "-Destination", destination]),
58930
+ { lowPriority: true }
58895
58931
  );
58896
58932
  }
58897
58933
  if (status !== 0) throw new Error("Could not extract " + archive);
@@ -59789,10 +59825,10 @@ function pcHasPwsh(env = process.env, exists2 = fs60.existsSync) {
59789
59825
 
59790
59826
  // src/host/launch/pc-scan.ts
59791
59827
  import fs61 from "node:fs";
59792
- import os14 from "node:os";
59828
+ import os15 from "node:os";
59793
59829
  import path84 from "node:path";
59794
59830
  function msvcRust() {
59795
- const settings = path84.join(process.env.RUSTUP_HOME ?? path84.join(os14.homedir(), ".rustup"), "settings.toml");
59831
+ const settings = path84.join(process.env.RUSTUP_HOME ?? path84.join(os15.homedir(), ".rustup"), "settings.toml");
59796
59832
  try {
59797
59833
  return /default_host_triple\s*=\s*"[^"]*-msvc"/.test(fs61.readFileSync(settings, "utf8").slice(0, 64 * 1024));
59798
59834
  } catch {
@@ -59959,7 +59995,7 @@ function databasePlanner(config3, compose2) {
59959
59995
  import path89 from "node:path";
59960
59996
 
59961
59997
  // src/host/launch/wsb-file.ts
59962
- import os15 from "node:os";
59998
+ import os16 from "node:os";
59963
59999
  var GUEST = {
59964
60000
  launch: "C:\\AgentLaunch",
59965
60001
  data: "C:\\AgentData",
@@ -60002,7 +60038,7 @@ function memoryNote(decision) {
60002
60038
  if (decision.limitedBy !== "commit" || decision.withoutCommitMb - decision.mb < 2048) return null;
60003
60039
  return "The sandbox gets " + decision.mb / 1024 + " GB of memory instead of " + decision.withoutCommitMb / 1024 + " GB: this PC has only " + Math.floor((decision.commitAvailableMb ?? 0) / 1024) + " GB of memory (RAM plus paging file) left to hand out, and if it runs out Windows closes the sandbox. A bigger Windows paging file, or closing other programs, gives it more.";
60004
60040
  }
60005
- function resolveMemoryMb(sandbox, freeMemoryBytes = os15.freemem(), totalMemoryBytes = os15.totalmem(), commitAvailableMb = null) {
60041
+ function resolveMemoryMb(sandbox, freeMemoryBytes = os16.freemem(), totalMemoryBytes = os16.totalmem(), commitAvailableMb = null) {
60006
60042
  if (sandbox.memoryMb !== "auto") return sandbox.memoryMb;
60007
60043
  const decision = decideMemory(
60008
60044
  sandbox,
@@ -60915,7 +60951,7 @@ function generateSandbox(given, opened) {
60915
60951
  resolveMemoryMb(config3.sandbox, o.freeMemoryBytes, void 0, commitAvailableMb),
60916
60952
  ...others.map((p) => typeof p.config.sandbox.memoryMb === "number" ? p.config.sandbox.memoryMb : 0)
60917
60953
  );
60918
- const memoryMb = cappedMemoryMb(askedMb, config3.sandbox, Math.floor(os16.totalmem() / 1048576));
60954
+ const memoryMb = cappedMemoryMb(askedMb, config3.sandbox, Math.floor(os17.totalmem() / 1048576));
60919
60955
  const launchId = o.launchId ?? newLaunchId();
60920
60956
  const launchPath = path94.join(storageRoot, "launches", launchId);
60921
60957
  ensureDir(dataPath);
@@ -60988,8 +61024,8 @@ function generateSandbox(given, opened) {
60988
61024
  memoryNote: residentNote(askedMb, memoryMb) ?? (config3.sandbox.memoryMb === "auto" ? memoryNote(
60989
61025
  decideMemory(
60990
61026
  config3.sandbox,
60991
- Math.floor((o.freeMemoryBytes ?? os16.freemem()) / 1048576),
60992
- Math.floor(os16.totalmem() / 1048576),
61027
+ Math.floor((o.freeMemoryBytes ?? os17.freemem()) / 1048576),
61028
+ Math.floor(os17.totalmem() / 1048576),
60993
61029
  commitAvailableMb
60994
61030
  )
60995
61031
  ) : null),
@@ -62927,16 +62963,19 @@ function howToSave(spec) {
62927
62963
  if (spec.returns) return "sign in once in this sandbox, or: sandbox account import " + spec.kind;
62928
62964
  return spec.sources.includes("typed") ? "save one: sandbox account login " + spec.kind : "save one: sandbox account import " + spec.kind + " --from <file>";
62929
62965
  }
62930
- function takeFromPc(kind, pc, store) {
62966
+ function takeFromPc(kind, pc, store, saved) {
62931
62967
  const source = HOST_SOURCES[kind];
62932
- if (!pc || !source.fromPc) return null;
62968
+ if (!pc || !source.fromPc) return saved;
62969
+ if (saved && store.info(kind)?.source !== "pc") return saved;
62933
62970
  let payload = null;
62934
62971
  try {
62935
62972
  payload = source.fromPc(pc);
62936
62973
  } catch {
62937
- return null;
62974
+ return saved;
62938
62975
  }
62939
- if (payload) store.save(kind, payload, "pc", source.summary(payload));
62976
+ if (!payload) return saved;
62977
+ if (!saved || fingerprint(payload) !== fingerprint(saved))
62978
+ store.save(kind, payload, "pc", source.summary(payload));
62940
62979
  return payload;
62941
62980
  }
62942
62981
  function prepareServiceAccounts(i) {
@@ -62968,7 +63007,7 @@ function prepareServiceAccounts(i) {
62968
63007
  } catch {
62969
63008
  unreadable = true;
62970
63009
  }
62971
- if (!payload && !unreadable) payload = takeFromPc(kind, i.pc, store);
63010
+ if (!unreadable) payload = takeFromPc(kind, i.pc, store, payload);
62972
63011
  if (spec.singleHolder) {
62973
63012
  const claim2 = leases.claim(kind, dataSet, i.launchId, payload ? fingerprint(payload) : null, i.isRunning);
62974
63013
  if (!claim2.ok) {
@@ -63178,9 +63217,6 @@ async function prepareStorePackages(apps, o) {
63178
63217
  return { notes };
63179
63218
  }
63180
63219
 
63181
- // src/host/pc-installs/epic.ts
63182
- import path117 from "node:path";
63183
-
63184
63220
  // src/shared/unreal/engines.ts
63185
63221
  import path113 from "node:path";
63186
63222
  var win = path113.win32;
@@ -63226,6 +63262,9 @@ var stripBom2 = (text) => text.replace(/^\uFEFF/, "");
63226
63262
  function launcherInstalledFile(programData) {
63227
63263
  return win2.join(programData, "Epic", "UnrealEngineLauncher", "LauncherInstalled.dat");
63228
63264
  }
63265
+ function launcherManifestsDir(programData) {
63266
+ return win2.join(programData, "Epic", "EpicGamesLauncher", "Data", "Manifests");
63267
+ }
63229
63268
  var EntrySchema = external_exports.looseObject({
63230
63269
  InstallLocation: external_exports.string().max(1024),
63231
63270
  AppName: external_exports.string().max(200),
@@ -63261,6 +63300,41 @@ var ItemSchema = external_exports.looseObject({
63261
63300
  InstallSize: external_exports.number().int().min(0).optional(),
63262
63301
  bIsIncompleteInstall: external_exports.boolean().optional()
63263
63302
  });
63303
+ function itemInstallLocation(text) {
63304
+ return itemInfo(text)?.installLocation ?? null;
63305
+ }
63306
+ function itemInfo(text) {
63307
+ try {
63308
+ const item = ItemSchema.parse(JSON.parse(stripBom2(text)));
63309
+ const at = item.InstallLocation;
63310
+ return at && win2.isAbsolute(at) ? { installLocation: at, sizeBytes: item.InstallSize ?? null } : null;
63311
+ } catch {
63312
+ return null;
63313
+ }
63314
+ }
63315
+ var ITEM_NAME = /^[A-Za-z0-9_-]{1,80}\.item$/;
63316
+ function relocateItem(text, from, to) {
63317
+ let item;
63318
+ try {
63319
+ item = JSON.parse(stripBom2(text));
63320
+ } catch {
63321
+ return null;
63322
+ }
63323
+ if (!item || typeof item !== "object" || Array.isArray(item)) return null;
63324
+ const base = win2.resolve(from).toLowerCase();
63325
+ const out = {};
63326
+ for (const [key3, value] of Object.entries(item)) {
63327
+ if (typeof value !== "string" || !win2.isAbsolute(value)) {
63328
+ out[key3] = value;
63329
+ continue;
63330
+ }
63331
+ const full = win2.resolve(value);
63332
+ const low = full.toLowerCase();
63333
+ if (low === base || low.startsWith(base + "\\")) out[key3] = to + full.slice(base.length);
63334
+ }
63335
+ const at = typeof out.InstallLocation === "string" ? out.InstallLocation : "";
63336
+ return at.toLowerCase() === to.toLowerCase() ? JSON.stringify(out, null, " ") : null;
63337
+ }
63264
63338
 
63265
63339
  // src/shared/unreal/status.ts
63266
63340
  var UNREAL_STATUS_FILE = "status.json";
@@ -63404,6 +63478,7 @@ function vaultPacks(vault, fs141) {
63404
63478
  }
63405
63479
 
63406
63480
  // src/host/pc-installs/epic.ts
63481
+ import path117 from "node:path";
63407
63482
  var APP = /^[A-Za-z0-9._-]{1,100}$/;
63408
63483
  function findEpicGames(text, wanted) {
63409
63484
  const names = wanted === true ? null : new Set(wanted.map((w) => String(w).toLowerCase()));
@@ -63925,6 +64000,25 @@ var versionOf = (dir, files) => {
63925
64000
  const bytes = files.read(win9.join(dir, "Engine", "Build", "Build.version"), 64 * 1024);
63926
64001
  return bytes ? parseBuildVersion(bytes.toString("utf8")) : null;
63927
64002
  };
64003
+ var MAX_ITEM_BYTES2 = 64 * 1024;
64004
+ function launcherItemOf(dir, files) {
64005
+ if (!files.manifests) return {};
64006
+ const want = win9.resolve(dir).toLowerCase();
64007
+ for (const name2 of files.list(files.manifests).filter((n) => ITEM_NAME.test(n))) {
64008
+ const bytes = files.read(win9.join(files.manifests, name2), MAX_ITEM_BYTES2);
64009
+ const at = bytes ? itemInstallLocation(bytes.toString("utf8")) : null;
64010
+ if (bytes && at && win9.resolve(at).toLowerCase() === want)
64011
+ return { launcherItem: { name: name2, text: bytes.toString("utf8") } };
64012
+ }
64013
+ return {};
64014
+ }
64015
+ function launcherIdsOf(dir, files) {
64016
+ const want = win9.resolve(dir).toLowerCase();
64017
+ const entry = (files.launcherInstalled ? launcherInstallations(files.launcherInstalled) : []).find(
64018
+ (i) => APP_NAME.test(i.appName) && i.ids && win9.resolve(i.installLocation).toLowerCase() === want
64019
+ );
64020
+ return entry?.ids ? { launcher: entry.ids } : {};
64021
+ }
63928
64022
  var isWanted = (wanted, v) => wanted === true || wanted.map((w) => String(w).replace(/^UE_/i, "")).includes(v.major + "." + v.minor);
63929
64023
  function findUnrealInstalls(files, wanted) {
63930
64024
  const found = [];
@@ -63944,7 +64038,8 @@ function findUnrealInstalls(files, wanted) {
63944
64038
  version: versionText(v),
63945
64039
  host: win9.normalize(dir),
63946
64040
  guest: "",
63947
- ...ids ? { launcher: ids } : {}
64041
+ ...ids ? { launcher: ids } : {},
64042
+ ...launcherItemOf(dir, files)
63948
64043
  });
63949
64044
  };
63950
64045
  const listed = files.launcherInstalled ? launcherInstallations(files.launcherInstalled) : [];
@@ -63989,7 +64084,10 @@ function findEngineFolders(files, folders) {
63989
64084
  host: dir,
63990
64085
  guest: "",
63991
64086
  writable: true,
63992
- ...source ? { buildId: source.buildId } : {}
64087
+ ...source ? { buildId: source.buildId } : {},
64088
+ // An engine the launcher installed keeps its ids and manifest, so the sandbox's launcher lists it too.
64089
+ ...launcherIdsOf(dir, { launcherInstalled: files.launcherInstalled ?? null }),
64090
+ ...files.list ? launcherItemOf(dir, { manifests: files.manifests, list: files.list, read: files.read }) : {}
63993
64091
  });
63994
64092
  }
63995
64093
  return found;
@@ -64017,6 +64115,7 @@ function unrealPcFiles(pc) {
64017
64115
  win9.join(d, "Program Files", "Epic Games"),
64018
64116
  win9.join(d, "Epic Games")
64019
64117
  ]),
64118
+ manifests: launcherManifestsDir(pc.programData),
64020
64119
  list: listDir2,
64021
64120
  read: readBoundedFile
64022
64121
  };
@@ -64155,8 +64254,9 @@ function discoverPcInstalls(s3, o, pc = pcEnv()) {
64155
64254
  readOnly: !writable,
64156
64255
  label: writable ? "this PC, writable" : "this PC, read-only"
64157
64256
  });
64158
- const { host: _host, ...shown } = install;
64159
- items.push({ ...shown, guest });
64257
+ const { host: _host, launcherItem, ...shown } = install;
64258
+ const moved = launcherItem ? relocateItem(launcherItem.text, install.host, guest) : null;
64259
+ items.push({ ...shown, guest, ...moved ? { launcherItem: { name: launcherItem.name, text: moved } } : {} });
64160
64260
  } catch (e) {
64161
64261
  notes.push("pcInstalls: " + install.name + " is not mapped: " + errMsg(e));
64162
64262
  }
@@ -64866,7 +64966,7 @@ var SandboxChannel = class {
64866
64966
 
64867
64967
  // src/host/channel/attach.ts
64868
64968
  import net4 from "node:net";
64869
- import os17 from "node:os";
64969
+ import os18 from "node:os";
64870
64970
 
64871
64971
  // src/shared/pty/protocol.ts
64872
64972
  var FrameType = {
@@ -65114,8 +65214,37 @@ var OutputFilter = class {
65114
65214
  }
65115
65215
  };
65116
65216
 
65117
- // src/shared/pty/console-input.ts
65217
+ // src/shared/pty/clipboard-watch.ts
65118
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";
65119
65248
  function enableVtInput() {
65120
65249
  if (process.platform !== "win32" || !process.stdin.isTTY)
65121
65250
  return { ready: Promise.resolve(false), stop: () => Promise.resolve() };
@@ -65123,7 +65252,7 @@ function enableVtInput() {
65123
65252
  let exited = Promise.resolve();
65124
65253
  const ready = new Promise((resolve) => {
65125
65254
  try {
65126
- 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)]), {
65127
65256
  stdio: "ignore",
65128
65257
  windowsHide: true
65129
65258
  });
@@ -65182,30 +65311,118 @@ function unfinishedSequence(data) {
65182
65311
  return data.length;
65183
65312
  }
65184
65313
 
65185
- // 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
65186
65342
  var CTRL_C_CONFIRM_MS = 1500;
65187
65343
  var CTRL_C_HINT = "Ctrl+C again to send it to the program (Esc stops an agent)";
65188
- var OSC_TITLE = /\x1b\][02];([^\x07\x1b]{0,256})(?:\x07|\x1b\\)/g;
65189
- 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;
65190
65350
  armedAt = Number.NEGATIVE_INFINITY;
65191
- lastTitle = "";
65192
- /** What to send for one read from the keyboard: a lone Ctrl+C only when another came just before. */
65193
- push(chunk, nowMs) {
65194
- 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";
65195
65365
  if (nowMs - this.armedAt <= CTRL_C_CONFIRM_MS) {
65196
65366
  this.armedAt = Number.NEGATIVE_INFINITY;
65197
- return { send: chunk, held: false };
65367
+ return "send";
65198
65368
  }
65199
65369
  this.armedAt = nowMs;
65200
- 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();
65201
65418
  }
65202
65419
  /** Remembers the title the program last set (from its output), to put it back after the hint. */
65203
65420
  watchOutput(text) {
65204
65421
  for (const m of text.matchAll(OSC_TITLE)) this.lastTitle = m[1];
65205
65422
  }
65206
65423
  /** The escape sequence that shows the hint, and the one that puts the program's title back. */
65207
- hintTitle() {
65208
- 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";
65209
65426
  }
65210
65427
  restoreTitle() {
65211
65428
  return "\x1B]0;" + this.lastTitle.replace(/[\x00-\x1f\x7f]/g, "") + "\x07";
@@ -65409,6 +65626,7 @@ function runTerminalClient(socket, token, options = {}) {
65409
65626
  clearTimeout(joinTimer);
65410
65627
  clearTimeout(hintTimer);
65411
65628
  clearTimeout(vtTimer);
65629
+ clipboard.stop();
65412
65630
  socket.destroy();
65413
65631
  const tracked = mouse.tracking();
65414
65632
  const reset = "\x1B[0m\x1B[?25h" + MOUSE_OFF + (mouse.has(2004) ? "\x1B[?2004l" : "") + "\r\n";
@@ -65426,23 +65644,22 @@ function runTerminalClient(socket, token, options = {}) {
65426
65644
  if (data.length && !done) socket.write(encodeFrame(FrameType.Input, data));
65427
65645
  };
65428
65646
  const sendInput = (data) => write(keys.push(passthrough ? mouse.filter(data) : data));
65429
- const guard2 = options.confirmCtrlC ? new CtrlCGuard() : null;
65647
+ const guard2 = new CtrlCGuard({ confirm: options.confirmCtrlC !== false });
65430
65648
  let hintTimer;
65649
+ const clipboard = watchClipboard(() => guard2.clipboardChanged());
65431
65650
  const onInput = (chunk) => {
65432
65651
  if (options.detachKey !== void 0 && chunk.includes(options.detachKey)) {
65433
65652
  finish({ reason: "detach" });
65434
65653
  return;
65435
65654
  }
65436
- if (guard2) {
65437
- const decided = guard2.push(chunk, Date.now());
65438
- clearTimeout(hintTimer);
65439
- if (decided.held) {
65440
- stdout.write(guard2.hintTitle());
65441
- hintTimer = setTimeout(() => stdout.write(guard2.restoreTitle()), CTRL_C_CONFIRM_MS);
65442
- return;
65443
- }
65444
- 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;
65445
65661
  }
65662
+ if (decided.send.length === 1 && decided.send[0] === 3) stdout.write(guard2.restoreTitle());
65446
65663
  clearTimeout(joinTimer);
65447
65664
  sendInput(joiner.push(chunk));
65448
65665
  if (joiner.holding()) joinTimer = setTimeout(() => sendInput(joiner.flush()), JOIN_MS);
@@ -65475,9 +65692,10 @@ function runTerminalClient(socket, token, options = {}) {
65475
65692
  }
65476
65693
  for (const frame of frames) {
65477
65694
  if (frame.type === FrameType.Output) {
65695
+ guard2.watchClipboard(frame.payload.toString("latin1"));
65478
65696
  const out = filter.push(frame.payload);
65479
65697
  mouse.watch(out);
65480
- guard2?.watchOutput(out.toString("latin1"));
65698
+ guard2.watchOutput(out.toString("latin1"));
65481
65699
  stdout.write(out);
65482
65700
  } else if (frame.type === FrameType.Console) passthrough = frame.payload.toString("latin1") === "conpty.dll";
65483
65701
  else if (frame.type === FrameType.Restarting) {
@@ -65512,7 +65730,7 @@ function runTerminalClient(socket, token, options = {}) {
65512
65730
 
65513
65731
  // src/host/channel/attach.ts
65514
65732
  var ipToInt = (ip) => ip.split(".").reduce((n, part) => n * 256 + Number(part), 0);
65515
- function isSandboxAddress(ip, interfaces = os17.networkInterfaces()) {
65733
+ function isSandboxAddress(ip, interfaces = os18.networkInterfaces()) {
65516
65734
  if (!net4.isIPv4(ip)) return false;
65517
65735
  return Object.entries(interfaces).some(
65518
65736
  ([name2, list2]) => /^vEthernet/i.test(name2) && !/WSL/i.test(name2) && (list2 ?? []).some((i) => {
@@ -66353,7 +66571,7 @@ var AuditCollector = class {
66353
66571
  };
66354
66572
 
66355
66573
  // src/sync/watcher.ts
66356
- import os18 from "node:os";
66574
+ import os19 from "node:os";
66357
66575
  var WATCH_INTERVAL_MS = 60 * 1e3;
66358
66576
  var FIRST_SYNC_MS = 3e4;
66359
66577
  var AUDIT_FLUSH_MS = 1e4;
@@ -66412,7 +66630,7 @@ var Watcher = class {
66412
66630
  memoryStream = null;
66413
66631
  watchdog = WATCHDOG_START;
66414
66632
  watchdogBusy = false;
66415
- lastCpu = os18.cpus();
66633
+ lastCpu = os19.cpus();
66416
66634
  lastHostLog = 0;
66417
66635
  /** Runs until the sandbox has closed and the final sync is done (or the end deadline passed). */
66418
66636
  async run() {
@@ -66532,7 +66750,7 @@ var Watcher = class {
66532
66750
  try {
66533
66751
  const settings = readLoadSettings(this.storageRoot, this.dataPath);
66534
66752
  const memory = this.memoryStream?.latest() ?? await readHostMemoryAsync(this.storageRoot);
66535
- const cpus = os18.cpus();
66753
+ const cpus = os19.cpus();
66536
66754
  const cpuPercent = cpuPercentBetween(this.lastCpu, cpus);
66537
66755
  this.lastCpu = cpus;
66538
66756
  const nowMs = Date.now();
@@ -69257,7 +69475,7 @@ function continuesAfterFailedMigration(command2) {
69257
69475
 
69258
69476
  // src/cli/services/update-service.ts
69259
69477
  import fs112 from "node:fs";
69260
- import os19 from "node:os";
69478
+ import os20 from "node:os";
69261
69479
  import path156 from "node:path";
69262
69480
 
69263
69481
  // src/host/update/release-source.ts
@@ -69692,7 +69910,7 @@ var Updater = class {
69692
69910
  var NPM_UPDATE_COMMAND = "npm install -g windows-development-sandbox";
69693
69911
  var AUTO_CHECK_HOURS = 24;
69694
69912
  var SeenSchema = external_exports.object({ seen: external_exports.string() });
69695
- function runsAsSystem(env = process.env, username = os19.userInfo().username) {
69913
+ function runsAsSystem(env = process.env, username = os20.userInfo().username) {
69696
69914
  return username.toUpperCase() === "SYSTEM" || (env.USERNAME ?? "").endsWith("$");
69697
69915
  }
69698
69916
  var UpdateService = class _UpdateService {
@@ -70315,7 +70533,7 @@ import { spawnSync as spawnSync29 } from "node:child_process";
70315
70533
 
70316
70534
  // src/cli/commands/setup/facts.ts
70317
70535
  var import_lodash27 = __toESM(require_lodash(), 1);
70318
- import os20 from "node:os";
70536
+ import os21 from "node:os";
70319
70537
  var FEATURE = "Containers-DisposableClientVM";
70320
70538
  var ENABLE_COMMAND = "Enable-WindowsOptionalFeature -Online -FeatureName " + FEATURE + " -All -NoRestart";
70321
70539
  function parseRegQuery(text) {
@@ -70363,7 +70581,7 @@ function gatherFacts(storageRoot) {
70363
70581
  sandboxExe: fileExists(system32("WindowsSandbox.exe")),
70364
70582
  virtualizationFirmware: probe.virtualizationFirmware ?? null,
70365
70583
  hypervisorPresent: probe.hypervisorPresent ?? null,
70366
- totalRamBytes: os20.totalmem(),
70584
+ totalRamBytes: os21.totalmem(),
70367
70585
  freeDiskBytes: freeBytesAt(storageRoot),
70368
70586
  storageRoot,
70369
70587
  nodeVersion: process.version
@@ -71381,6 +71599,12 @@ import path162 from "node:path";
71381
71599
 
71382
71600
  // src/app/context.ts
71383
71601
  var implement = implementer();
71602
+ function reloadSettings(ctx) {
71603
+ try {
71604
+ ctx.loaded = loadConfig(ctx.loaded.configPath ?? ctx.options.config);
71605
+ } catch {
71606
+ }
71607
+ }
71384
71608
  function typedConfirm(io) {
71385
71609
  return (prompt, expected) => io.confirm({
71386
71610
  title: prompt,
@@ -72949,7 +73173,7 @@ var AccessStore = class _AccessStore {
72949
73173
  };
72950
73174
 
72951
73175
  // src/host/remote/door-client.ts
72952
- import { spawn as spawn12 } from "node:child_process";
73176
+ import { spawn as spawn13 } from "node:child_process";
72953
73177
  var NOT_OWNER = 3;
72954
73178
  var NO_PIPE = 4;
72955
73179
  var NO_USER = 5;
@@ -72976,7 +73200,7 @@ async function knockOnDoor(storageRoot, owner) {
72976
73200
  const exe = hostToolsExe(storageRoot);
72977
73201
  if (!exe) fail("Windows sign-in needs HostTools, which could not be built on this PC.", "Run sandbox doctor.");
72978
73202
  return new Promise((resolve) => {
72979
- const child = spawn12(exe, ["door-client", "--owner", owner], { windowsHide: true });
73203
+ const child = spawn13(exe, ["door-client", "--owner", owner], { windowsHide: true });
72980
73204
  let out = "";
72981
73205
  child.stdout.on("data", (d) => {
72982
73206
  if (out.length < 1024) out += d.toString("utf8");
@@ -73088,13 +73312,13 @@ function fingerprintOf(der) {
73088
73312
  // src/host/remote/firewall.ts
73089
73313
  import crypto22 from "node:crypto";
73090
73314
  import fs119 from "node:fs";
73091
- import os22 from "node:os";
73315
+ import os23 from "node:os";
73092
73316
  import path168 from "node:path";
73093
- import { spawn as spawn13 } from "node:child_process";
73317
+ import { spawn as spawn14 } from "node:child_process";
73094
73318
 
73095
73319
  // src/host/remote/lan.ts
73096
73320
  import net7 from "node:net";
73097
- import os21 from "node:os";
73321
+ import os22 from "node:os";
73098
73322
  var toInt = (ip) => ip.split(".").reduce((n, part) => n * 256 + Number(part), 0) >>> 0;
73099
73323
  function isPrivateIPv4(ip) {
73100
73324
  if (!net7.isIPv4(ip)) return false;
@@ -73111,7 +73335,7 @@ function plainIPv4(ip) {
73111
73335
  function virtualAdapter(name2) {
73112
73336
  return /^vEthernet/i.test(name2) || /Hyper-V|WSL/i.test(name2);
73113
73337
  }
73114
- function privateAddresses(interfaces = os21.networkInterfaces()) {
73338
+ function privateAddresses(interfaces = os22.networkInterfaces()) {
73115
73339
  const out = [];
73116
73340
  for (const [adapter, list2] of Object.entries(interfaces)) {
73117
73341
  if (virtualAdapter(adapter)) continue;
@@ -73121,7 +73345,7 @@ function privateAddresses(interfaces = os21.networkInterfaces()) {
73121
73345
  }
73122
73346
  return out;
73123
73347
  }
73124
- function chooseLanAddress(configured, interfaces = os21.networkInterfaces()) {
73348
+ function chooseLanAddress(configured, interfaces = os22.networkInterfaces()) {
73125
73349
  const own2 = privateAddresses(interfaces);
73126
73350
  if (configured) {
73127
73351
  if (!isPrivateIPv4(configured))
@@ -73149,7 +73373,7 @@ function sameSubnet(a, b, netmask) {
73149
73373
  const mask = toInt(netmask);
73150
73374
  return mask !== 0 && (toInt(a) & mask) === (toInt(b) & mask);
73151
73375
  }
73152
- function lanPeerProblem(peer, local, bound, interfaces = os21.networkInterfaces()) {
73376
+ function lanPeerProblem(peer, local, bound, interfaces = os22.networkInterfaces()) {
73153
73377
  const ip = plainIPv4(peer);
73154
73378
  if (plainIPv4(local) !== bound.address) return "reached another address of this PC";
73155
73379
  if (!isPrivateIPv4(ip)) return "not a private address";
@@ -73182,7 +73406,7 @@ function removeRuleRequest() {
73182
73406
  return { action: "remove", ruleName: FIREWALL_RULE_NAME };
73183
73407
  }
73184
73408
  var spawnRunner3 = (exe, args, timeoutMs) => new Promise((resolve) => {
73185
- const child = spawn13(exe, [...args], { windowsHide: true });
73409
+ const child = spawn14(exe, [...args], { windowsHide: true });
73186
73410
  let stdout = "";
73187
73411
  let stderr = "";
73188
73412
  const timer = setTimeout(() => child.kill(), timeoutMs);
@@ -73200,7 +73424,7 @@ var OutcomeSchema3 = external_exports.object({
73200
73424
  commands: external_exports.array(external_exports.string().max(1e3)).max(4).optional()
73201
73425
  });
73202
73426
  async function runScript2(request2, dryRun, run3) {
73203
- const dir = fs119.mkdtempSync(path168.join(os22.tmpdir(), "agent-sandbox-firewall-"));
73427
+ const dir = fs119.mkdtempSync(path168.join(os23.tmpdir(), "agent-sandbox-firewall-"));
73204
73428
  try {
73205
73429
  const file2 = path168.join(dir, "request.json");
73206
73430
  fs119.writeFileSync(file2, JSON.stringify(request2, null, 2), "utf8");
@@ -73259,35 +73483,6 @@ import https from "node:https";
73259
73483
 
73260
73484
  // src/host/web/auth.ts
73261
73485
  import crypto23 from "node:crypto";
73262
-
73263
- // src/shared/web/protocol.ts
73264
- var CSRF_HEADER = "x-sandbox-web";
73265
- var CSRF_VALUE = "1";
73266
- var COOKIE_NAME = "sbxweb";
73267
- var MIN_COLS = 10;
73268
- var MAX_COLS = 500;
73269
- var MIN_ROWS = 2;
73270
- var MAX_ROWS = 300;
73271
- var MAX_CLIENT_MESSAGE_BYTES = 256 * 1024;
73272
- function parseClientMessage(text) {
73273
- let value;
73274
- try {
73275
- value = JSON.parse(text);
73276
- } catch {
73277
- return null;
73278
- }
73279
- if (typeof value !== "object" || value === null) return null;
73280
- const m = value;
73281
- if (m.t !== "resize") return null;
73282
- const { cols, rows } = m;
73283
- if (!Number.isInteger(cols) || !Number.isInteger(rows)) return null;
73284
- const c2 = cols;
73285
- const r = rows;
73286
- if (c2 < MIN_COLS || c2 > MAX_COLS || r < MIN_ROWS || r > MAX_ROWS) return null;
73287
- return { t: "resize", cols: c2, rows: r };
73288
- }
73289
-
73290
- // src/host/web/auth.ts
73291
73486
  var COOKIE_LIFETIME_MS = 12 * 60 * 60 * 1e3;
73292
73487
  var MAX_COOKIES = 50;
73293
73488
  var MAX_FAILURES = 8;
@@ -73367,17 +73562,17 @@ function cookieHeader(value) {
73367
73562
  }
73368
73563
 
73369
73564
  // src/host/web/browser.ts
73370
- import { spawn as spawn14 } from "node:child_process";
73565
+ import { spawn as spawn15 } from "node:child_process";
73371
73566
  function openBrowser(url2) {
73372
73567
  refuseInSelfTest("open a browser");
73373
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.");
73374
- 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();
73375
73570
  }
73376
73571
  var ISSUE_URL = /^https:\/\/github\.com\/[\w.-]+\/[\w.-]+\/issues\/new\?[\w%.~&=+-]*$/;
73377
73572
  function openIssuePage(url2) {
73378
73573
  refuseInSelfTest("open a browser");
73379
73574
  if (!ISSUE_URL.test(url2) || url2.length > 2e3) throw new Error("Not a GitHub new-issue address.");
73380
- spawn14(system32("rundll32.exe"), ["url.dll,FileProtocolHandler", url2], {
73575
+ spawn15(system32("rundll32.exe"), ["url.dll,FileProtocolHandler", url2], {
73381
73576
  detached: true,
73382
73577
  stdio: "ignore",
73383
73578
  windowsHide: true
@@ -74357,7 +74552,7 @@ var WebRunner = class {
74357
74552
  };
74358
74553
 
74359
74554
  // src/host/remote/windows-helper.ts
74360
- import { spawn as spawn15 } from "node:child_process";
74555
+ import { spawn as spawn16 } from "node:child_process";
74361
74556
  import readline3 from "node:readline";
74362
74557
  var SID2 = /^S-1-[0-9-]{1,180}$/;
74363
74558
  var MEDIUM_INTEGRITY = 8192;
@@ -74396,7 +74591,7 @@ var HostToolsHelper = class _HostToolsHelper {
74396
74591
  answer = async () => "refused Windows sign-in is off.";
74397
74592
  problem = () => void 0;
74398
74593
  constructor(exe, pipe2) {
74399
- 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)], {
74400
74595
  windowsHide: true,
74401
74596
  stdio: ["pipe", "pipe", "pipe"]
74402
74597
  });
@@ -74793,7 +74988,7 @@ var RemoteGate = class {
74793
74988
  };
74794
74989
 
74795
74990
  // src/host/remote/tls-store.ts
74796
- import os23 from "node:os";
74991
+ import os24 from "node:os";
74797
74992
  import path171 from "node:path";
74798
74993
  var TlsFileSchema = external_exports.strictObject({
74799
74994
  cert: external_exports.string().max(8e3),
@@ -74835,7 +75030,7 @@ var TlsStore = class {
74835
75030
  }
74836
75031
  /** A new certificate (the old one is gone). */
74837
75032
  renew(addresses) {
74838
- const made = makeCertificate({ addresses, dnsNames: [os23.hostname()], now: this.now() });
75033
+ const made = makeCertificate({ addresses, dnsNames: [os24.hostname()], now: this.now() });
74839
75034
  this.file.write({
74840
75035
  cert: made.cert,
74841
75036
  key: this.secrets.seal(made.key),
@@ -76331,12 +76526,16 @@ var ServiceRowSchema = external_exports.object({
76331
76526
  canImport: external_exports.boolean().describe("It can be taken from this PC or a file (sandbox account import)"),
76332
76527
  canLogin: external_exports.boolean().describe("It can be typed (sandbox account login)"),
76333
76528
  needsFile: external_exports.boolean().describe("Importing it needs --from <file>"),
76529
+ fromPc: external_exports.boolean().describe("This PC's own program can give it (the next launch takes it when sharing is on)"),
76530
+ hasFileSource: external_exports.boolean().describe("It can be read from a file you name"),
76531
+ onPc: external_exports.boolean().nullable().describe("This PC has one to take (null: it cannot be taken from this PC). Only the presence, never the sign-in"),
76532
+ sandboxOnly: external_exports.boolean().describe("Only a sandbox can give it: sign in once inside one and it is kept"),
76334
76533
  ready: external_exports.boolean().describe("Its sign-in is written; false: the menu and the web leave it out (the CLI says coming)")
76335
76534
  });
76336
76535
  var ServicesListSchema = external_exports.object({ services: external_exports.array(ServiceRowSchema) });
76337
76536
  var STATE_TEXT = {
76338
76537
  none: "not saved",
76339
- saved: "saved (encrypted)",
76538
+ saved: "saved",
76340
76539
  unreadable: "cannot be read"
76341
76540
  };
76342
76541
  var STATE_TONE = {
@@ -76345,10 +76544,10 @@ var STATE_TONE = {
76345
76544
  unreadable: "danger"
76346
76545
  };
76347
76546
  var SOURCE_TEXT = {
76348
- pc: "taken from this PC",
76547
+ pc: "from this PC",
76349
76548
  file: "from a file",
76350
- typed: "typed in",
76351
- sandbox: "signed in (or renewed) in a sandbox"
76549
+ typed: "by typing it",
76550
+ sandbox: "from a sandbox"
76352
76551
  };
76353
76552
  var day = (iso) => iso ? iso.slice(0, 10) : "";
76354
76553
  function servicesText(o) {
@@ -76381,7 +76580,8 @@ var serviceImport = defineOperation({
76381
76580
  summary: "save a service sign-in from this PC's program or a file, for sandboxes",
76382
76581
  input: external_exports.object({
76383
76582
  name: KindSchema,
76384
- from: external_exports.string().max(1e3).optional().describe("(import) the file or folder to take the sign-in from")
76583
+ from: external_exports.string().max(1e3).optional().describe("(import) the file or folder to take the sign-in from"),
76584
+ file: external_exports.boolean().optional().describe("(import) ask for a file even when this PC has one to take")
76385
76585
  }),
76386
76586
  output: MessageSchema2,
76387
76587
  text: (o) => o.message,
@@ -76410,6 +76610,12 @@ var SERVICE_OPERATIONS = [serviceImport, serviceLogin, serviceLogout];
76410
76610
  function serviceCategory(kind) {
76411
76611
  return "service:" + kind;
76412
76612
  }
76613
+ var PC_HELP = {
76614
+ epic: "Sign in to the Epic launcher here, with Remember me on.",
76615
+ steam: "Log in once with SteamCMD in C:\\steamcmd.",
76616
+ unity: "Sign in to the Unity Hub here.",
76617
+ perplexity: "Set PERPLEXITY_API_KEY for your Windows user."
76618
+ };
76413
76619
  function serviceActions(s3) {
76414
76620
  return [
76415
76621
  action(
@@ -76422,51 +76628,75 @@ function serviceActions(s3) {
76422
76628
  importance: 1
76423
76629
  }
76424
76630
  ),
76425
- ...s3.canImport ? [action(serviceImport, { name: s3.kind }, { key: "i", verb: "import", importance: 1 })] : [],
76426
- ...s3.canLogin ? [action(serviceLogin, { name: s3.kind }, { key: "n", verb: "sign in", importance: 1 })] : [],
76631
+ ...s3.fromPc ? [
76632
+ action(
76633
+ serviceImport,
76634
+ { name: s3.kind },
76635
+ { key: "i", verb: "take from this PC", label: "Take this PC's sign-in", importance: 1 }
76636
+ )
76637
+ ] : [],
76638
+ ...s3.hasFileSource ? [
76639
+ action(
76640
+ serviceImport,
76641
+ { name: s3.kind, file: true },
76642
+ {
76643
+ key: s3.fromPc ? "f" : "i",
76644
+ verb: "import a file",
76645
+ label: "Import from a file\u2026",
76646
+ importance: s3.fromPc ? 2 : 1
76647
+ }
76648
+ )
76649
+ ] : [],
76650
+ ...s3.canLogin ? [
76651
+ action(
76652
+ serviceLogin,
76653
+ { name: s3.kind },
76654
+ { key: "n", verb: "type it in", label: "Type the sign-in\u2026", importance: 1 }
76655
+ )
76656
+ ] : [],
76427
76657
  ...s3.state !== "none" ? [action(serviceLogout, { name: s3.kind }, { key: "x", verb: "forget", importance: 2 })] : []
76428
76658
  ];
76429
76659
  }
76430
- function howTo(s3) {
76431
- const ways = [...s3.canImport ? ["import it"] : [], ...s3.canLogin ? ["type it in"] : []];
76432
- return ways.length ? ways.join(" or ") : "sign in once inside a sandbox (it can't be copied from this PC: Windows locks it to your PC account)";
76660
+ function helpLine(s3) {
76661
+ if (s3.state !== "none") return void 0;
76662
+ if (s3.sandboxOnly) return "Sign in once inside a sandbox; it is kept for the next ones.";
76663
+ if (s3.fromPc && s3.onPc)
76664
+ return s3.share ? "The next sandbox takes this PC's." : "Share it and the next sandbox takes this PC's.";
76665
+ if (s3.fromPc) return PC_HELP[s3.kind] ?? "Not found on this PC.";
76666
+ return s3.canLogin ? "Type it in, or import it from a file." : "Import it from a file.";
76433
76667
  }
76434
76668
  function serviceSection(s3) {
76435
76669
  const facts2 = [
76436
76670
  {
76437
76671
  id: s3.kind + ":share",
76438
- label: "Shared with sandboxes",
76672
+ label: "Shared",
76439
76673
  value: s3.share ? "on" : "off",
76440
76674
  tone: s3.share ? "ok" : "muted",
76441
- description: "For sandboxes that use " + s3.usedFor + ". " + s3.warnings.join(" ")
76675
+ description: "The agent can use it."
76442
76676
  },
76443
76677
  {
76444
76678
  id: s3.kind + ":state",
76445
76679
  label: "Sign-in",
76446
76680
  value: STATE_TEXT[s3.state] + (s3.savedAt ? " " + day(s3.savedAt) : ""),
76447
76681
  tone: STATE_TONE[s3.state],
76448
- description: s3.state === "none" ? "Save one: " + howTo(s3) + "." : void 0
76682
+ description: s3.state === "unreadable" ? "Only the Windows user that saved it can read it: save it again." : helpLine(s3) ?? (s3.source ? "Saved " + (SOURCE_TEXT[s3.source] ?? s3.source) + "." : void 0)
76449
76683
  },
76450
- ...s3.source ? [{ id: s3.kind + ":source", label: "From", value: SOURCE_TEXT[s3.source] ?? s3.source }] : [],
76684
+ ...s3.onPc === null ? [] : [
76685
+ {
76686
+ id: s3.kind + ":pc",
76687
+ label: "On this PC",
76688
+ value: s3.onPc ? "found" : "not found",
76689
+ tone: s3.onPc ? "ok" : "muted"
76690
+ }
76691
+ ],
76451
76692
  ...s3.summary ? [{ id: s3.kind + ":summary", label: "Account", value: s3.summary }] : [],
76452
76693
  ...s3.heldBy ? [{ id: s3.kind + ":held", label: "In use by", value: "sandbox " + s3.heldBy, tone: "accent" }] : []
76453
76694
  ];
76454
- const warnings = s3.warnings.map((w, i) => ({
76455
- id: s3.kind + ":warning:" + i,
76456
- label: w,
76457
- tone: "warn",
76458
- group: "Before you share it"
76459
- }));
76460
- const notes = [
76461
- ...s3.state === "unreadable" ? [{ text: "Only the Windows user that saved it can read it: save it again.", tone: "muted" }] : []
76462
- ];
76463
76695
  return {
76464
76696
  id: serviceCategory(s3.kind),
76465
76697
  title: s3.label,
76466
76698
  summary: (s3.share ? "shared" : "not shared") + " \xB7 " + STATE_TEXT[s3.state],
76467
76699
  facts: facts2,
76468
- items: warnings,
76469
- notes,
76470
76700
  actions: serviceActions(s3)
76471
76701
  };
76472
76702
  }
@@ -76781,7 +77011,17 @@ var accountsView = defineView({
76781
77011
  // src/app/features/accounts/services.run.ts
76782
77012
  import fs124 from "node:fs";
76783
77013
  import path174 from "node:path";
76784
- function serviceRow(kind, config3, store, leases) {
77014
+ function presentOnPc(kind, pc) {
77015
+ const source = HOST_SOURCES[kind];
77016
+ if (!source.fromPc) return null;
77017
+ if (!pc) return false;
77018
+ try {
77019
+ return source.fromPc(pc) !== null;
77020
+ } catch {
77021
+ return false;
77022
+ }
77023
+ }
77024
+ function serviceRow(kind, config3, store, leases, pc) {
76785
77025
  const spec = SERVICE_SPECS[kind];
76786
77026
  const source = HOST_SOURCES[kind];
76787
77027
  const state = store.state(kind);
@@ -76802,6 +77042,10 @@ function serviceRow(kind, config3, store, leases) {
76802
77042
  canImport: Boolean(source.fromPc || source.fromFile),
76803
77043
  canLogin: Boolean(source.fromAnswers),
76804
77044
  needsFile: Boolean(source.fromFile && !source.fromPc),
77045
+ fromPc: Boolean(source.fromPc),
77046
+ hasFileSource: Boolean(source.fromFile),
77047
+ onPc: presentOnPc(kind, pc),
77048
+ sandboxOnly: !source.fromPc && !source.fromFile && !source.fromAnswers,
76805
77049
  ready: isKindReady(kind)
76806
77050
  };
76807
77051
  }
@@ -76849,7 +77093,7 @@ function refuseSandboxOnly(spec) {
76849
77093
  function savedMessage(kind, share2) {
76850
77094
  return "Saved your " + SERVICE_SPECS[kind].label + " sign-in (encrypted for your Windows user)." + (share2 ? "" : " Sandboxes get it once you turn on Share with sandboxes.");
76851
77095
  }
76852
- async function importServiceFlow(io, kind, from, deps) {
77096
+ async function importServiceFlow(io, kind, from, deps, wantFile = false) {
76853
77097
  const spec = SERVICE_SPECS[kind];
76854
77098
  const source = HOST_SOURCES[kind];
76855
77099
  if (!source.fromPc && !source.fromFile) refuseSandboxOnly(spec);
@@ -76857,7 +77101,7 @@ async function importServiceFlow(io, kind, from, deps) {
76857
77101
  failWith(spec.label + " cannot be imported.", "Type it instead.", {
76858
77102
  op: "accounts.service.login",
76859
77103
  input: { name: kind },
76860
- label: "Sign in\u2026",
77104
+ label: "Type the sign-in\u2026",
76861
77105
  cli: "sandbox account login " + kind
76862
77106
  });
76863
77107
  let file2 = from === void 0 ? void 0 : path174.resolve(from);
@@ -76868,7 +77112,7 @@ async function importServiceFlow(io, kind, from, deps) {
76868
77112
  return false;
76869
77113
  }
76870
77114
  };
76871
- const useFile = Boolean(source.fromFile && (file2 ? isFile(file2) : !source.fromPc));
77115
+ const useFile = Boolean(source.fromFile && (file2 ? isFile(file2) : wantFile || !source.fromPc));
76872
77116
  if (useFile && !file2) {
76873
77117
  const asked = await io.path({ title: "The " + spec.label + " file:", pathKind: "file" });
76874
77118
  if (asked === void 0) cancelled();
@@ -76945,7 +77189,8 @@ var listServicesRun = implement(listServices, async (ctx, input2) => {
76945
77189
  if (name2 !== void 0 && name2 !== "" && !isServiceKind(name2))
76946
77190
  fail('There is no service called "' + safeText(input2.name ?? "") + '".', "Use one of: " + SERVICE_KINDS.join(", "));
76947
77191
  const kinds = name2 ? [name2] : [...SERVICE_KINDS];
76948
- return { services: kinds.map((k) => serviceRow(k, ctx.loaded.config, store, leases)) };
77192
+ const pc = pcEnv();
77193
+ return { services: kinds.map((k) => serviceRow(k, ctx.loaded.config, store, leases, pc)) };
76949
77194
  });
76950
77195
  var serviceImportRun = implement(serviceImport, async (ctx, input2, io) => {
76951
77196
  requireOnPc("account import");
@@ -76955,11 +77200,17 @@ var serviceImportRun = implement(serviceImport, async (ctx, input2, io) => {
76955
77200
  "import",
76956
77201
  (s3) => Boolean(HOST_SOURCES[s3.kind].fromPc || HOST_SOURCES[s3.kind].fromFile)
76957
77202
  );
76958
- return importServiceFlow(io, kind, input2.from, {
76959
- store: new ServiceAccountStore(ctx.storageRoot),
76960
- pc: pcEnv(),
76961
- share: ctx.loaded.config.accounts[kind].share
76962
- });
77203
+ return importServiceFlow(
77204
+ io,
77205
+ kind,
77206
+ input2.from,
77207
+ {
77208
+ store: new ServiceAccountStore(ctx.storageRoot),
77209
+ pc: pcEnv(),
77210
+ share: ctx.loaded.config.accounts[kind].share
77211
+ },
77212
+ input2.file ?? false
77213
+ );
76963
77214
  });
76964
77215
  var serviceLoginRun = implement(serviceLogin, async (ctx, input2, io) => {
76965
77216
  requireOnPc("account login");
@@ -80011,6 +80262,9 @@ var SETTINGS = {
80011
80262
  theme: "dark",
80012
80263
  firstWindowPreference: "defaultProfile",
80013
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",
80014
80268
  // New tabs pick up the PATH and variables the runner writes machine-wide after the window opened.
80015
80269
  compatibility: { reloadEnvironmentVariables: true },
80016
80270
  warning: { confirmCloseAllTabs: false, largePaste: false, multiLinePaste: false },
@@ -80049,6 +80303,10 @@ function withReloadEnvironment(settings) {
80049
80303
  if (compatibility?.reloadEnvironmentVariables === true) return null;
80050
80304
  return { ...settings, compatibility: { ...compatibility, reloadEnvironmentVariables: true } };
80051
80305
  }
80306
+ function withCopyOnSelect(settings) {
80307
+ if ("copyOnSelect" in settings) return null;
80308
+ return { ...settings, copyOnSelect: true };
80309
+ }
80052
80310
  function syncPwshProfile(dataPath, allowed) {
80053
80311
  const file2 = path185.join(terminalDir(dataPath), "settings", "settings.json");
80054
80312
  assertTestWritable2(file2);
@@ -80066,15 +80324,18 @@ function syncPwshProfile(dataPath, allowed) {
80066
80324
  }
80067
80325
  const withProfile = withPwshProfile(parsed, visible);
80068
80326
  if (withProfile) {
80069
- writeJson(file2, withReloadEnvironment(withProfile) ?? withProfile);
80327
+ const reloaded = withReloadEnvironment(withProfile) ?? withProfile;
80328
+ writeJson(file2, withCopyOnSelect(reloaded) ?? reloaded);
80070
80329
  return;
80071
80330
  }
80072
- if (withReloadEnvironment(parsed)) {
80073
- const edits = modify(text, ["compatibility", "reloadEnvironmentVariables"], true, {
80074
- formattingOptions: { insertSpaces: true, tabSize: 4 }
80075
- });
80076
- writeFileAtomic(file2, applyEdits(text, edits));
80077
- }
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);
80078
80339
  }
80079
80340
  async function ensureTerminal(dataPath, refresh, options = {}) {
80080
80341
  const result = await installTerminal(dataPath, refresh);
@@ -83319,7 +83580,7 @@ var vpnLogoutRun = implement(vpnLogout, async (ctx, _input, io) => {
83319
83580
  });
83320
83581
 
83321
83582
  // src/app/features/github/github.run.ts
83322
- import os24 from "node:os";
83583
+ import os25 from "node:os";
83323
83584
  async function saveGithubTokenFlow(io, store, verify = verifyGithubToken) {
83324
83585
  io.log("Make a GitHub token only for sandboxes:");
83325
83586
  GITHUB_STEPS.forEach((s3, i) => io.log(" " + (i + 1) + ". " + s3));
@@ -83380,7 +83641,7 @@ var githubStatusRun = implement(githubStatus, async (ctx) => {
83380
83641
  expiresAt: token === "none" ? null : store.check()?.expiresAt ?? null,
83381
83642
  repoCount: token === "none" ? null : store.check()?.repoCount ?? null,
83382
83643
  checkedAt: token === "none" ? null : store.check()?.checkedAt ?? null,
83383
- tokenUrl: tokenUrlFor(os24.hostname())
83644
+ tokenUrl: tokenUrlFor(os25.hostname())
83384
83645
  };
83385
83646
  });
83386
83647
  var githubLoginRun = implement(githubLogin, async (ctx, _input, io) => {
@@ -84736,7 +84997,14 @@ function implementationOf(id) {
84736
84997
  function runDefinition(id, ctx, input2, io) {
84737
84998
  const impl = implementationOf(id);
84738
84999
  if (!impl) throw new Error("No implementation for " + id);
84739
- return invoke(impl, ctx, input2, io);
85000
+ return invokeAndRefresh(impl, ctx, input2, io);
85001
+ }
85002
+ async function invokeAndRefresh(impl, ctx, input2, io) {
85003
+ try {
85004
+ return await invoke(impl, ctx, input2, io);
85005
+ } finally {
85006
+ if (impl.def.kind === "operation") reloadSettings(ctx);
85007
+ }
84740
85008
  }
84741
85009
  async function readQuery(def, ctx, input2 = {}) {
84742
85010
  return await runDefinition(def.id, ctx, input2, NO_QUESTIONS);
@@ -84761,7 +85029,7 @@ function createRpcHost(ctx) {
84761
85029
  if (!impl) throw new Error("Not allowed on the web: " + id);
84762
85030
  const need = accessNeeded(impl.def, input2);
84763
85031
  if (!allows(caller, need)) fail(refusalText(caller, need));
84764
- return invoke(impl, ctx, input2, io);
85032
+ return invokeAndRefresh(impl, ctx, input2, io);
84765
85033
  }
84766
85034
  };
84767
85035
  }
@@ -85188,7 +85456,7 @@ async function attachOpened(ctx, session) {
85188
85456
  nextStep("sandbox attach " + session);
85189
85457
  return 0;
85190
85458
  }
85191
- const { attach: attach2 } = await import("./chunk-4TLSWPG7.js");
85459
+ const { attach: attach2 } = await import("./chunk-UZ5S3EMB.js");
85192
85460
  return attach2({ ...ctx, options: { ...ctx.options, positionals: [session] } });
85193
85461
  }
85194
85462
  function definitionHandler(def) {
@@ -86148,6 +86416,9 @@ export {
86148
86416
  NAV,
86149
86417
  withOperationCommands,
86150
86418
  SandboxChannel,
86419
+ CSRF_HEADER,
86420
+ CSRF_VALUE,
86421
+ reloadSettings,
86151
86422
  esm_default4 as esm_default,
86152
86423
  esm_default11 as esm_default2,
86153
86424
  askChoice,
@@ -86206,8 +86477,6 @@ export {
86206
86477
  settingRow,
86207
86478
  ProjectsService,
86208
86479
  StorageService,
86209
- CSRF_HEADER,
86210
- CSRF_VALUE,
86211
86480
  openBrowser,
86212
86481
  import_websocket,
86213
86482
  INSTALL,