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
@@ -1,11 +1,14 @@
1
1
  // A file's Authenticode signature (sandbox\scripts\get-signature.ps1) checked against the signer a download names.
2
2
  import { z } from 'zod';
3
3
  import { signerMatches } from '../../shared/signer.js';
4
- import { scriptOutput } from '../runtime/process.js';
4
+ import { scriptOutput, scriptOutputAsync } from '../runtime/process.js';
5
5
  const SignatureSchema = z.object({ subject: z.string().max(4096), issuer: z.string().max(4096) });
6
6
  /** The names on a file's valid signature, or null when it is unsigned, tampered with, or cannot be read. */
7
7
  export function signatureOf(file) {
8
- const text = scriptOutput('get-signature.ps1', ['-Path', file]).trim();
8
+ return parseSignature(scriptOutput('get-signature.ps1', ['-Path', file]));
9
+ }
10
+ function parseSignature(output) {
11
+ const text = output.trim();
9
12
  if (!text)
10
13
  return null;
11
14
  try {
@@ -20,3 +23,7 @@ export function signatureOf(file) {
20
23
  export function signedBy(file, signer) {
21
24
  return signerMatches(signatureOf(file), signer);
22
25
  }
26
+ /** signedBy without blocking the event loop: a boot check of a big download must not stall the other tasks. */
27
+ export async function signedByAsync(file, signer) {
28
+ return signerMatches(parseSignature(await scriptOutputAsync('get-signature.ps1', ['-Path', file])), signer);
29
+ }
@@ -5,7 +5,7 @@ import { downloader } from '../../shared/download/index.js';
5
5
  import { errMsg } from '../../core/util/errors.js';
6
6
  import { emptyEnv } from './tool-env.js';
7
7
  /** How many installers run at once: enough to overlap downloads with installs, few enough not to thrash the disk. */
8
- const PARALLEL = 4;
8
+ const PARALLEL = 3;
9
9
  /** Runs one installer; a failure is reported and skipped so it never blocks the other toolchains. */
10
10
  async function runReported(sink, name, env, work) {
11
11
  try {
@@ -22,19 +22,27 @@ function howToSave(spec) {
22
22
  ? 'save one: sandbox account login ' + spec.kind
23
23
  : 'save one: sandbox account import ' + spec.kind + ' --from <file>';
24
24
  }
25
- /** The first time a shared kind has nothing saved: this PC's own sign-in, saved like `sandbox account import`. */
26
- function takeFromPc(kind, pc, store) {
25
+ /**
26
+ * This PC's own sign-in, saved like `sandbox account import`: when nothing is saved, and when the saved one was taken
27
+ * from this PC and the PC's has changed since (Epic replaces its token whenever the launcher here renews it, so the
28
+ * old copy no longer signs in). One a sandbox handed back is newer than the PC's and is kept.
29
+ */
30
+ function takeFromPc(kind, pc, store, saved) {
27
31
  const source = HOST_SOURCES[kind];
28
32
  if (!pc || !source.fromPc)
29
- return null;
33
+ return saved;
34
+ if (saved && store.info(kind)?.source !== 'pc')
35
+ return saved;
30
36
  let payload = null;
31
37
  try {
32
38
  payload = source.fromPc(pc);
33
39
  }
34
40
  catch {
35
- return null; // This PC's program has no usable sign-in: the note says how to save one.
41
+ return saved; // This PC's program has no usable sign-in: the saved one (or the note) stands.
36
42
  }
37
- if (payload)
43
+ if (!payload)
44
+ return saved;
45
+ if (!saved || fingerprint(payload) !== fingerprint(saved))
38
46
  store.save(kind, payload, 'pc', source.summary(payload));
39
47
  return payload;
40
48
  }
@@ -70,8 +78,8 @@ export function prepareServiceAccounts(i) {
70
78
  catch {
71
79
  unreadable = true;
72
80
  }
73
- if (!payload && !unreadable)
74
- payload = takeFromPc(kind, i.pc, store);
81
+ if (!unreadable)
82
+ payload = takeFromPc(kind, i.pc, store, payload);
75
83
  if (spec.singleHolder) {
76
84
  const claim = leases.claim(kind, dataSet, i.launchId, payload ? fingerprint(payload) : null, i.isRunning);
77
85
  if (!claim.ok) {
@@ -22,6 +22,10 @@ export declare function findingLines(events: readonly StopEvent[]): string[];
22
22
  export declare function findDumps(dirs: readonly string[], since: Date): DumpFile[];
23
23
  /** Whether the newest launch started at or before `lastSeen` had vGPU on (host-only .wsb in <storage>\launches). */
24
24
  export declare function launchHadVgpu(storageRoot: string, lastSeen: string): boolean;
25
+ /**
26
+ * "This PC still had ..." from the last line of its memory log, when RAM and commit were both 4 GB or more free (pure).
27
+ */
28
+ export declare function memoryWasFineLine(hostMemoryLog: string): string | null;
25
29
  /**
26
30
  * The report text (pure): what happened (the sandbox's or this PC's stop code, where the dump is) and the memory log's
27
31
  * last lines. `memoryLog` is the sandbox's own file, so it is shown through safeText.
@@ -54,7 +54,10 @@ export function findingLines(events) {
54
54
  lines.push('WHEA-Logger: Windows reported a hardware error (CPU, memory, a bus or a disk): heat or failing hardware.');
55
55
  if (has((e) => lower(e).startsWith('hyper-v')))
56
56
  lines.push("Hyper-V events: the sandbox's virtual machine (or the service that runs it) reported a problem.");
57
- if (has((e) => /^(application error|windows error reporting)$/i.test(e.provider)))
57
+ const isAppCrash = (e) => /^(application error|windows error reporting)$/i.test(e.provider);
58
+ if (has((e) => isAppCrash(e) && /vmwp\.exe/i.test(e.process)))
59
+ lines.push("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.");
60
+ else if (has(isAppCrash))
58
61
  lines.push("A crash report names vmwp.exe or vmmem, the programs that hold the sandbox's VM.");
59
62
  return lines;
60
63
  }
@@ -122,6 +125,30 @@ function hostMemoryTail(storageRoot, dataPath) {
122
125
  return '';
123
126
  }
124
127
  }
128
+ /**
129
+ * "This PC still had ..." from the last line of its memory log, when RAM and commit were both 4 GB or more free (pure).
130
+ */
131
+ export function memoryWasFineLine(hostMemoryLog) {
132
+ const last = hostMemoryLog
133
+ .split(/\r?\n/)
134
+ .filter((l) => l.trim() !== '')
135
+ .pop();
136
+ if (!last)
137
+ return null;
138
+ const ram = /RAM free\/total MB (\d{1,9})\/(\d{1,9})/.exec(last);
139
+ const commit = /commit free\/limit MB (\d{1,9})\/(\d{1,9})/.exec(last);
140
+ if (!ram || !commit)
141
+ return null;
142
+ const freeRam = Number(ram[1]);
143
+ const freeCommit = Number(commit[1]);
144
+ if (freeRam < 4096 || freeCommit < 4096)
145
+ return null;
146
+ return ('This PC still had ' +
147
+ Math.floor(freeRam / 1024) +
148
+ ' GB of RAM and ' +
149
+ Math.floor(freeCommit / 1024) +
150
+ ' GB of memory (RAM plus paging file) free just before, so it did not run out of memory.');
151
+ }
125
152
  /**
126
153
  * The report text (pure): what happened (the sandbox's or this PC's stop code, where the dump is) and the memory log's
127
154
  * last lines. `memoryLog` is the sandbox's own file, so it is shown through safeText.
@@ -152,6 +179,7 @@ export function crashReportText(cause, events, memoryLog, lastSeen, extra = {})
152
179
  .map((l) => ' ' + safeText(l));
153
180
  const freeze = freezeLine(cause, lastSeen);
154
181
  const meaning = findingLines(unique);
182
+ const fine = memoryWasFineLine(extra.hostMemoryLog ?? '');
155
183
  const dumps = extra.dumps ?? [];
156
184
  const vgpu = extra.vgpu ? ['vGPU was on for this sandbox. ' + vgpuWarning('its launch settings'), ''] : [];
157
185
  return [
@@ -165,6 +193,7 @@ export function crashReportText(cause, events, memoryLog, lastSeen, extra = {})
165
193
  ...vgpu,
166
194
  'What that usually means:',
167
195
  ...(meaning.length ? meaning.map((l) => ' ' + l) : [' (no memory, blue screen or hardware event was logged)']),
196
+ ...(fine ? [' ' + fine] : []),
168
197
  '',
169
198
  'Crash dumps Windows left (C:\\Windows\\Minidump, MEMORY.DMP):',
170
199
  ...(dumps.length ? dumps.map((d) => ' ' + d.name + ' ' + d.at) : [' (none from that time)']),
@@ -39,6 +39,7 @@ export function heartbeatLine(hb) {
39
39
  hb.step +
40
40
  (hb.hostStep > 0 ? ' (this PC asked for step ' + hb.hostStep + ')' : '') +
41
41
  (hb.diskQueueReported !== undefined ? ', this PC disk queue ' + hb.diskQueueReported : '') +
42
+ (hb.held ? ', ' + hb.held + ' builds held by the memory gate' : '') +
42
43
  (top ? '; biggest: ' + top : ''));
43
44
  }
44
45
  /** The likeliest reason, from the last heartbeat (pure). */
@@ -1,6 +1,7 @@
1
1
  import { assertSafeMapping } from '../../core/safe-paths.js';
2
2
  import { dirExists, errMsg } from '../../core/util/index.js';
3
3
  import { pcInstallGuest } from '../../shared/paths/index.js';
4
+ import { relocateItem } from '../../shared/unreal/index.js';
4
5
  import { pcEnv } from '../accounts/index.js';
5
6
  import { epicInstalls } from './epic.js';
6
7
  import { steamInstalls } from './steam.js';
@@ -74,8 +75,10 @@ export function discoverPcInstalls(s, o, pc = pcEnv()) {
74
75
  readOnly: !writable,
75
76
  label: writable ? 'this PC, writable' : 'this PC, read-only',
76
77
  });
77
- const { host: _host, ...shown } = install;
78
- items.push({ ...shown, guest });
78
+ const { host: _host, launcherItem, ...shown } = install;
79
+ // The manifest names the sandbox's folder, or it is left out: no path of this PC goes to the sandbox.
80
+ const moved = launcherItem ? relocateItem(launcherItem.text, install.host, guest) : null;
81
+ items.push({ ...shown, guest, ...(moved ? { launcherItem: { name: launcherItem.name, text: moved } } : {}) });
79
82
  }
80
83
  catch (e) {
81
84
  notes.push('pcInstalls: ' + install.name + ' is not mapped: ' + errMsg(e));
@@ -13,6 +13,8 @@ export interface UnrealPcFiles {
13
13
  readonly defaultRoot: string;
14
14
  /** More folders that may hold UE_x.y folders: the same places on every drive (D:\Program Files\Epic Games ...). */
15
15
  readonly otherRoots?: readonly string[];
16
+ /** The launcher's Data\Manifests folder (its .item files), or none. */
17
+ readonly manifests?: string;
16
18
  readonly list: (dir: string) => string[];
17
19
  readonly read: (file: string, max: number) => Buffer | null;
18
20
  }
@@ -33,7 +35,7 @@ export interface PcSourceBuild {
33
35
  export declare function pcSourceBuilds(files: Pick<UnrealPcFiles, 'read' | 'builds'>): PcSourceBuild[];
34
36
  /** The engine folders you registered (pcInstalls.unrealEngineFolders), mapped writable. A folder that is a source
35
37
  * build of this PC keeps the PC's build id. */
36
- export declare function findEngineFolders(files: Pick<UnrealPcFiles, 'read' | 'builds'>, folders: readonly (string | number)[]): PcInstall[];
38
+ export declare function findEngineFolders(files: Pick<UnrealPcFiles, 'read' | 'builds'> & Partial<Pick<UnrealPcFiles, 'launcherInstalled' | 'manifests' | 'list'>>, folders: readonly (string | number)[]): PcInstall[];
37
39
  /** This PC's files (reg.exe through system32, bounded reads). */
38
40
  export declare function unrealPcFiles(pc: PcEnv): UnrealPcFiles;
39
41
  export declare const unrealInstalls: PcInstallSource;
@@ -10,7 +10,7 @@ import fs from 'node:fs';
10
10
  import path from 'node:path';
11
11
  import { readBoundedFile } from '../../shared/io/index.js';
12
12
  import { SYSTEM } from '../../shared/system.js';
13
- import { BUILDS_KEY, launcherInstallations, launcherInstalledFile, MAX_LAUNCHER_FILE_BYTES, parseBuildVersion, versionText, } from '../../shared/unreal/index.js';
13
+ import { BUILDS_KEY, itemInstallLocation, ITEM_NAME, launcherInstallations, launcherInstalledFile, launcherManifestsDir, MAX_LAUNCHER_FILE_BYTES, parseBuildVersion, versionText, } from '../../shared/unreal/index.js';
14
14
  const win = path.win32;
15
15
  /** At most this many engines of this PC are mapped. */
16
16
  export const MAX_PC_ENGINES = 8;
@@ -29,6 +29,27 @@ const versionOf = (dir, files) => {
29
29
  const bytes = files.read(win.join(dir, 'Engine', 'Build', 'Build.version'), 64 * 1024);
30
30
  return bytes ? parseBuildVersion(bytes.toString('utf8')) : null;
31
31
  };
32
+ /** At most this much of a manifest goes into session.json (an engine's is a few KB). */
33
+ const MAX_ITEM_BYTES = 64 * 1024;
34
+ /** The launcher's manifest of the app in `dir` (with this PC's paths), or nothing. */
35
+ function launcherItemOf(dir, files) {
36
+ if (!files.manifests)
37
+ return {};
38
+ const want = win.resolve(dir).toLowerCase();
39
+ for (const name of files.list(files.manifests).filter((n) => ITEM_NAME.test(n))) {
40
+ const bytes = files.read(win.join(files.manifests, name), MAX_ITEM_BYTES);
41
+ const at = bytes ? itemInstallLocation(bytes.toString('utf8')) : null;
42
+ if (bytes && at && win.resolve(at).toLowerCase() === want)
43
+ return { launcherItem: { name, text: bytes.toString('utf8') } };
44
+ }
45
+ return {};
46
+ }
47
+ /** The launcher's ids for the app in `dir` (from LauncherInstalled.dat), or nothing. */
48
+ function launcherIdsOf(dir, files) {
49
+ const want = win.resolve(dir).toLowerCase();
50
+ const entry = (files.launcherInstalled ? launcherInstallations(files.launcherInstalled) : []).find((i) => APP_NAME.test(i.appName) && i.ids && win.resolve(i.installLocation).toLowerCase() === want);
51
+ return entry?.ids ? { launcher: entry.ids } : {};
52
+ }
32
53
  const isWanted = (wanted, v) => wanted === true || wanted.map((w) => String(w).replace(/^UE_/i, '')).includes(v.major + '.' + v.minor);
33
54
  /** The engines of this PC that `wanted` maps, each folder once, at most MAX_PC_ENGINES. */
34
55
  export function findUnrealInstalls(files, wanted) {
@@ -53,6 +74,7 @@ export function findUnrealInstalls(files, wanted) {
53
74
  host: win.normalize(dir),
54
75
  guest: '',
55
76
  ...(ids ? { launcher: ids } : {}),
77
+ ...launcherItemOf(dir, files),
56
78
  });
57
79
  };
58
80
  const listed = files.launcherInstalled ? launcherInstallations(files.launcherInstalled) : [];
@@ -117,6 +139,9 @@ export function findEngineFolders(files, folders) {
117
139
  guest: '',
118
140
  writable: true,
119
141
  ...(source ? { buildId: source.buildId } : {}),
142
+ // An engine the launcher installed keeps its ids and manifest, so the sandbox's launcher lists it too.
143
+ ...launcherIdsOf(dir, { launcherInstalled: files.launcherInstalled ?? null }),
144
+ ...(files.list ? launcherItemOf(dir, { manifests: files.manifests, list: files.list, read: files.read }) : {}),
120
145
  });
121
146
  }
122
147
  return found;
@@ -146,6 +171,7 @@ export function unrealPcFiles(pc) {
146
171
  win.join(d, 'Program Files', 'Epic Games'),
147
172
  win.join(d, 'Epic Games'),
148
173
  ]),
174
+ manifests: launcherManifestsDir(pc.programData),
149
175
  list: listDir,
150
176
  read: readBoundedFile,
151
177
  };
@@ -19,10 +19,13 @@ declare const TerminalSettingsSchema: z.ZodObject<{
19
19
  export declare function withPwshProfile(settings: z.output<typeof TerminalSettingsSchema>, visible: boolean): z.output<typeof TerminalSettingsSchema> | null;
20
20
  /** settings.json that makes new tabs read the current environment; null when it already does. Pure. */
21
21
  export declare function withReloadEnvironment(settings: z.output<typeof TerminalSettingsSchema>): z.output<typeof TerminalSettingsSchema> | null;
22
+ /** settings.json that copies on select; null when the user already chose (true or false). Pure. */
23
+ export declare function withCopyOnSelect(settings: z.output<typeof TerminalSettingsSchema>): z.output<typeof TerminalSettingsSchema> | null;
22
24
  /**
23
25
  * Shows the PowerShell 7 profile once pwsh is installed in this data folder (and `allowed`, i.e. toolchains.pwsh is
24
26
  * not off), hides it otherwise. settings.json is written by the sandbox too: read with a size cap, through no links,
25
- * shape-checked, only rewritten when the profile changes; a file that does not parse is left alone.
27
+ * shape-checked, only rewritten when the profile changes or a default switch is missing (reload the environment,
28
+ * copy on select; a value the user set stays); a file that does not parse is left alone.
26
29
  */
27
30
  export declare function syncPwshProfile(dataPath: string, allowed: boolean): void;
28
31
  export declare function ensureTerminal(dataPath: string, refresh: boolean, options?: {
@@ -24,6 +24,9 @@ const SETTINGS = {
24
24
  theme: 'dark',
25
25
  firstWindowPreference: 'defaultProfile',
26
26
  confirmCloseAllTabs: false,
27
+ // Copied the moment the mouse is released, so an agent's redraw cannot clear the selection first.
28
+ copyOnSelect: true,
29
+ copyFormatting: 'none',
27
30
  // New tabs pick up the PATH and variables the runner writes machine-wide after the window opened.
28
31
  compatibility: { reloadEnvironmentVariables: true },
29
32
  warning: { confirmCloseAllTabs: false, largePaste: false, multiLinePaste: false },
@@ -75,10 +78,17 @@ export function withReloadEnvironment(settings) {
75
78
  return null;
76
79
  return { ...settings, compatibility: { ...compatibility, reloadEnvironmentVariables: true } };
77
80
  }
81
+ /** settings.json that copies on select; null when the user already chose (true or false). Pure. */
82
+ export function withCopyOnSelect(settings) {
83
+ if ('copyOnSelect' in settings)
84
+ return null;
85
+ return { ...settings, copyOnSelect: true };
86
+ }
78
87
  /**
79
88
  * Shows the PowerShell 7 profile once pwsh is installed in this data folder (and `allowed`, i.e. toolchains.pwsh is
80
89
  * not off), hides it otherwise. settings.json is written by the sandbox too: read with a size cap, through no links,
81
- * shape-checked, only rewritten when the profile changes; a file that does not parse is left alone.
90
+ * shape-checked, only rewritten when the profile changes or a default switch is missing (reload the environment,
91
+ * copy on select; a value the user set stays); a file that does not parse is left alone.
82
92
  */
83
93
  export function syncPwshProfile(dataPath, allowed) {
84
94
  const file = path.join(terminalDir(dataPath), 'settings', 'settings.json');
@@ -100,16 +110,22 @@ export function syncPwshProfile(dataPath, allowed) {
100
110
  }
101
111
  const withProfile = withPwshProfile(parsed, visible);
102
112
  if (withProfile) {
103
- writeJson(file, withReloadEnvironment(withProfile) ?? withProfile);
113
+ const reloaded = withReloadEnvironment(withProfile) ?? withProfile;
114
+ writeJson(file, withCopyOnSelect(reloaded) ?? reloaded);
104
115
  return;
105
116
  }
106
- // Only the reload switch is missing: set that one value in the text, so comments and the layout stay.
107
- if (withReloadEnvironment(parsed)) {
108
- const edits = modify(text, ['compatibility', 'reloadEnvironmentVariables'], true, {
109
- formattingOptions: { insertSpaces: true, tabSize: 4 },
110
- });
111
- writeFileAtomic(file, applyEdits(text, edits));
112
- }
117
+ // Only default switches are missing: set those values in the text, so comments and the layout stay.
118
+ const missing = [];
119
+ if (withReloadEnvironment(parsed))
120
+ missing.push([['compatibility', 'reloadEnvironmentVariables'], true]);
121
+ if (withCopyOnSelect(parsed))
122
+ missing.push([['copyOnSelect'], true]);
123
+ if (missing.length === 0)
124
+ return;
125
+ let edited = text;
126
+ for (const [at, value] of missing)
127
+ edited = applyEdits(edited, modify(edited, at, value, { formattingOptions: { insertSpaces: true, tabSize: 4 } }));
128
+ writeFileAtomic(file, edited);
113
129
  }
114
130
  export async function ensureTerminal(dataPath, refresh, options = {}) {
115
131
  const result = await installTerminal(dataPath, refresh);
@@ -25502,7 +25502,7 @@ var SandboxSchema = external_exports.strictObject({
25502
25502
  '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.'
25503
25503
  ),
25504
25504
  pressureResponse: external_exports.boolean().describe(
25505
- "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."
25505
+ "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."
25506
25506
  ),
25507
25507
  vGpu: external_exports.boolean().describe(
25508
25508
  "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)."
@@ -26093,6 +26093,7 @@ var GAMEDEV_APPS = [
26093
26093
  size: "100 MB",
26094
26094
  source: { github: "f3d-app/f3d", asset: "^F3D-[\\d.]+-Windows-x86_64\\.zip$" },
26095
26095
  exe: "f3d.exe",
26096
+ onPath: true,
26096
26097
  needs: ["mesa"]
26097
26098
  },
26098
26099
  {
@@ -27192,7 +27193,7 @@ var SERVICE_SPECS = {
27192
27193
  },
27193
27194
  steam: {
27194
27195
  kind: "steam",
27195
- label: "Steam (SteamCMD)",
27196
+ label: "Steam",
27196
27197
  usedBy: ["steam"],
27197
27198
  sources: ["file", "sandbox"],
27198
27199
  singleHolder: true,
@@ -27228,7 +27229,7 @@ var SERVICE_SPECS = {
27228
27229
  },
27229
27230
  cfx: {
27230
27231
  kind: "cfx",
27231
- label: "Cfx.re (FiveM / RedM server key)",
27232
+ label: "FiveM key",
27232
27233
  usedBy: ["fivem"],
27233
27234
  sources: ["typed", "file"],
27234
27235
  singleHolder: false,
@@ -27257,7 +27258,7 @@ var SERVICE_SPECS = {
27257
27258
  },
27258
27259
  keystore: {
27259
27260
  kind: "keystore",
27260
- label: "Android upload keystore",
27261
+ label: "Android keystore",
27261
27262
  usedBy: ["android"],
27262
27263
  sources: ["file"],
27263
27264
  singleHolder: false,
@@ -27272,7 +27273,7 @@ var SERVICE_SPECS = {
27272
27273
  },
27273
27274
  play: {
27274
27275
  kind: "play",
27275
- label: "Google Play (service account)",
27276
+ label: "Google Play",
27276
27277
  usedBy: ["android"],
27277
27278
  sources: ["file"],
27278
27279
  singleHolder: false,
@@ -27283,7 +27284,7 @@ var SERVICE_SPECS = {
27283
27284
  },
27284
27285
  expo: {
27285
27286
  kind: "expo",
27286
- label: "Expo (EAS)",
27287
+ label: "Expo",
27287
27288
  usedBy: ["reactNative"],
27288
27289
  sources: ["typed"],
27289
27290
  singleHolder: false,
@@ -27294,7 +27295,7 @@ var SERVICE_SPECS = {
27294
27295
  },
27295
27296
  appstore: {
27296
27297
  kind: "appstore",
27297
- label: "App Store Connect API key",
27298
+ label: "App Store Connect",
27298
27299
  usedBy: ["reactNative", "flutter"],
27299
27300
  sources: ["file", "typed"],
27300
27301
  singleHolder: false,
@@ -27308,7 +27309,7 @@ var SERVICE_SPECS = {
27308
27309
  },
27309
27310
  perplexity: {
27310
27311
  kind: "perplexity",
27311
- label: "Perplexity API key",
27312
+ label: "Perplexity API",
27312
27313
  usedBy: ["perplexity"],
27313
27314
  sources: ["pc", "file", "typed"],
27314
27315
  singleHolder: false,
@@ -27332,7 +27333,7 @@ var SERVICE_SPECS = {
27332
27333
  },
27333
27334
  comet: {
27334
27335
  kind: "comet",
27335
- label: "Comet (Perplexity browser)",
27336
+ label: "Comet",
27336
27337
  usedBy: ["comet"],
27337
27338
  sources: ["sandbox"],
27338
27339
  singleHolder: false,
@@ -27348,7 +27349,7 @@ var SERVICE_SPECS = {
27348
27349
  },
27349
27350
  perplexityapp: {
27350
27351
  kind: "perplexityapp",
27351
- label: "Perplexity desktop app",
27352
+ label: "Perplexity app",
27352
27353
  usedBy: ["perplexityapp"],
27353
27354
  sources: ["sandbox"],
27354
27355
  singleHolder: false,
@@ -27699,6 +27700,8 @@ var PressureStatusSchema = external_exports.object({
27699
27700
  lowered: external_exports.number().int().min(0).max(1024),
27700
27701
  /** Compilers waiting their turn right now (name, process id, seconds until they may go on). */
27701
27702
  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),
27703
+ /** Build programs the memory gate holds right now (let go one at a time). */
27704
+ held: external_exports.number().int().min(0).max(1024).optional(),
27702
27705
  /** The CPU share the compilers get together (percent; 100 = no cap). */
27703
27706
  cpuPercent: external_exports.number().int().min(0).max(100).optional(),
27704
27707
  /** The PC itself asked for slowing down (its memory or CPU is low). */
@@ -27719,6 +27722,8 @@ var HeartbeatSchema = external_exports.object({
27719
27722
  topProcesses: external_exports.array(external_exports.object({ name: Text(64), privateMb: external_exports.number().int().min(0).max(1e7) })).max(5),
27720
27723
  /** The disk queue this PC last reported while asking the sandbox to slow down. */
27721
27724
  diskQueueReported: external_exports.number().min(0).max(1e5).optional(),
27725
+ /** Build programs the memory gate holds right now (let go one at a time). */
27726
+ held: external_exports.number().int().min(0).max(1024).optional(),
27722
27727
  /** "ending": the sandbox is shutting down in order (idle shutdown, sandbox stop, Windows shutting down). */
27723
27728
  phase: external_exports.enum(["ready", "working", "ending"])
27724
27729
  });
@@ -28074,7 +28079,7 @@ import path12 from "node:path";
28074
28079
 
28075
28080
  // src/guest/runtime/process.ts
28076
28081
  import fs10 from "node:fs";
28077
- import os3 from "node:os";
28082
+ import os4 from "node:os";
28078
28083
  import path11 from "node:path";
28079
28084
  import { spawn as spawn2, spawnSync } from "node:child_process";
28080
28085
 
@@ -28134,10 +28139,20 @@ function scriptArgs(name, args = []) {
28134
28139
  }
28135
28140
 
28136
28141
  // src/shared/download/spawn.ts
28142
+ import os3 from "node:os";
28137
28143
  import { spawn } from "node:child_process";
28144
+ function lowerPriority(pid) {
28145
+ if (pid === void 0) return;
28146
+ try {
28147
+ os3.setPriority(pid, os3.constants.priority.PRIORITY_BELOW_NORMAL);
28148
+ } catch {
28149
+ }
28150
+ }
28138
28151
  function spawnAsync(file2, args, options = {}) {
28152
+ const { lowPriority, ...spawnOptions } = options;
28139
28153
  return new Promise((resolve, reject) => {
28140
- const child = spawn(file2, args, { stdio: "ignore", windowsHide: true, ...options });
28154
+ const child = spawn(file2, args, { stdio: "ignore", windowsHide: true, ...spawnOptions });
28155
+ if (lowPriority) lowerPriority(child.pid);
28141
28156
  child.once("error", reject);
28142
28157
  child.once("close", (code) => resolve(code ?? -1));
28143
28158
  });
@@ -28158,13 +28173,16 @@ function extractArchive(archive, destination, members2 = []) {
28158
28173
  }
28159
28174
  async function extract(archive, destination, members2) {
28160
28175
  fs8.mkdirSync(destination, { recursive: true });
28161
- let status = await spawnAsync(SYSTEM.tar(), ["-xf", archive, "-C", destination, ...members2]);
28176
+ let status = await spawnAsync(SYSTEM.tar(), ["-xf", archive, "-C", destination, ...members2], {
28177
+ lowPriority: true
28178
+ });
28162
28179
  if (status !== 0 && /\.(zip|nupkg)$/i.test(archive)) {
28163
28180
  const zip = archive.endsWith(".zip") ? archive : archive + ".zip";
28164
28181
  if (zip !== archive) fs8.copyFileSync(archive, zip);
28165
28182
  status = await spawnAsync(
28166
28183
  SYSTEM.powershell(),
28167
- scriptArgs("expand-archive.ps1", ["-Archive", zip, "-Destination", destination])
28184
+ scriptArgs("expand-archive.ps1", ["-Archive", zip, "-Destination", destination]),
28185
+ { lowPriority: true }
28168
28186
  );
28169
28187
  }
28170
28188
  if (status !== 0) throw new Error("Could not extract " + archive);
@@ -28381,13 +28399,15 @@ function logLines(stream) {
28381
28399
  stream.on("end", () => logOutput(partial2));
28382
28400
  }
28383
28401
  async function execAsync(file2, args, options = {}) {
28402
+ const { lowPriority, ...spawnOptions } = options;
28384
28403
  const code = captureOutput() ? await new Promise((resolve, reject) => {
28385
- const child = spawn2(file2, args, { stdio: ["inherit", "pipe", "pipe"], windowsHide: true, ...options });
28404
+ const child = spawn2(file2, args, { stdio: ["inherit", "pipe", "pipe"], windowsHide: true, ...spawnOptions });
28405
+ if (lowPriority) lowerPriority(child.pid);
28386
28406
  logLines(child.stdout);
28387
28407
  logLines(child.stderr);
28388
28408
  child.once("error", reject);
28389
28409
  child.once("close", (c) => resolve(c ?? -1));
28390
- }) : await spawnAsync(file2, args, { stdio: "inherit", ...options });
28410
+ }) : await spawnAsync(file2, args, { stdio: "inherit", ...spawnOptions, ...lowPriority ? { lowPriority } : {} });
28391
28411
  if (code !== 0)
28392
28412
  throw new Error(path11.basename(file2) + " " + args.slice(0, 3).join(" ") + " failed with exit code " + code);
28393
28413
  }
@@ -28416,7 +28436,7 @@ function runScript(name, args = []) {
28416
28436
  throw new Error(name + " failed with exit code " + result.status + (errors ? ": " + errors.slice(0, 400) : ""));
28417
28437
  }
28418
28438
  function runScriptWithJson(name, param, data, args = []) {
28419
- const dir = fs10.mkdtempSync(path11.join(os3.tmpdir(), "sbx-"));
28439
+ const dir = fs10.mkdtempSync(path11.join(os4.tmpdir(), "sbx-"));
28420
28440
  const file2 = path11.join(dir, "input.json");
28421
28441
  try {
28422
28442
  fs10.writeFileSync(file2, JSON.stringify(data), "utf8");
@@ -28492,9 +28512,6 @@ async function setupUv(env) {
28492
28512
  env.prepend.push(mkdirp(tool("uv-bin")));
28493
28513
  }
28494
28514
 
28495
- // src/guest/runtime/pools.ts
28496
- import os4 from "node:os";
28497
-
28498
28515
  // src/shared/concurrency/limiter.ts
28499
28516
  import { AsyncLocalStorage as AsyncLocalStorage2 } from "node:async_hooks";
28500
28517
  var held = new AsyncLocalStorage2();
@@ -28554,14 +28571,13 @@ var Limiter = class {
28554
28571
  };
28555
28572
 
28556
28573
  // src/shared/concurrency/pools.ts
28557
- var net = new Limiter(4, "net");
28574
+ var net = new Limiter(3, "net");
28558
28575
  var heavy = new Limiter(2, "heavy");
28559
28576
  var installer = new Limiter(1, "installer");
28560
28577
 
28561
28578
  // src/guest/runtime/pools.ts
28562
- var BELOW_NORMAL = os4.constants.priority.PRIORITY_BELOW_NORMAL;
28563
28579
  function execHeavy(file2, args, options = {}) {
28564
- return heavy.run(() => execAsync(file2, args, options));
28580
+ return heavy.run(() => execAsync(file2, args, { ...options, lowPriority: true }));
28565
28581
  }
28566
28582
 
28567
28583
  // src/guest/runtime/session.ts