windows-development-sandbox 1.25.0 → 1.25.1

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 (158) hide show
  1. package/config.example.json +5 -2
  2. package/dist/app/features/data/data.def.d.ts +4 -4
  3. package/dist/app/features/data/data.run.d.ts +1 -1
  4. package/dist/app/features/defs.d.ts +161 -6
  5. package/dist/app/features/defs.js +7 -0
  6. package/dist/app/features/projects/projects.def.d.ts +1 -1
  7. package/dist/app/features/projects/projects.run.d.ts +1 -1
  8. package/dist/app/features/unreal/index.d.ts +2 -0
  9. package/dist/app/features/unreal/index.js +3 -0
  10. package/dist/app/features/unreal/unreal.def.d.ts +236 -0
  11. package/dist/app/features/unreal/unreal.def.js +379 -0
  12. package/dist/app/features/unreal/unreal.run.d.ts +96 -0
  13. package/dist/app/features/unreal/unreal.run.js +217 -0
  14. package/dist/app/registry.js +5 -0
  15. package/dist/bin/agent-sandbox.js +2 -2
  16. package/dist/bin/{chunk-SUYYVIDW.js → chunk-3GAXLATK.js} +58 -5
  17. package/dist/bin/{chunk-IZRGTSQ5.js → chunk-F4PVBN23.js} +2465 -1392
  18. package/dist/bin/{chunk-KH4MI2U5.js → chunk-G3TG4ZT7.js} +1 -1
  19. package/dist/bin/claude-host.js +2 -2
  20. package/dist/bin/claude-sbx.js +2 -2
  21. package/dist/bin/codex-sbx.js +2 -2
  22. package/dist/bin/gemini-sbx.js +2 -2
  23. package/dist/bin/sandbox.js +2 -2
  24. package/dist/bin/sbx-hide.js +2 -2
  25. package/dist/bin/sbx-show.js +2 -2
  26. package/dist/cli/commands/launch/launch.js +5 -2
  27. package/dist/cli/commands/registry.js +3 -0
  28. package/dist/cli/manifest.d.ts +18 -0
  29. package/dist/cli/manifest.js +26 -0
  30. package/dist/cli/menu/coverage-exempt.js +1 -0
  31. package/dist/cli/menu/pages/sandbox-page.d.ts +3 -0
  32. package/dist/cli/menu/pages/sandbox-page.js +34 -4
  33. package/dist/cli/menu/views.js +2 -0
  34. package/dist/core/config/defaults.js +18 -4
  35. package/dist/core/schema/config.d.ts +9 -7
  36. package/dist/core/schema/pc-installs.d.ts +3 -1
  37. package/dist/core/schema/pc-installs.js +10 -1
  38. package/dist/core/schema/toolchains.js +1 -1
  39. package/dist/guest/accounts/kinds/epic.js +18 -13
  40. package/dist/guest/apps/after-install.d.ts +1 -1
  41. package/dist/guest/apps/after-install.js +6 -1
  42. package/dist/guest/apps/app-installer.d.ts +1 -0
  43. package/dist/guest/apps/app-installer.js +13 -3
  44. package/dist/guest/apps/comet-unpack.js +3 -2
  45. package/dist/guest/apps/resolvers-gamedev.js +55 -1
  46. package/dist/guest/boot/boot.js +14 -1
  47. package/dist/guest/boot/guide.js +24 -0
  48. package/dist/guest/daemon/daemon.js +3 -0
  49. package/dist/guest/setup/addon-mcp.d.ts +7 -0
  50. package/dist/guest/setup/addon-mcp.js +19 -4
  51. package/dist/guest/setup/browser-gpu.js +14 -11
  52. package/dist/guest/setup/headroom.js +4 -0
  53. package/dist/guest/toolchains/engines/epic-launcher.d.ts +26 -0
  54. package/dist/guest/toolchains/engines/epic-launcher.js +127 -0
  55. package/dist/guest/toolchains/engines/unreal-engines.d.ts +21 -1
  56. package/dist/guest/toolchains/engines/unreal-engines.js +163 -3
  57. package/dist/guest/toolchains/engines/unreal-status.d.ts +13 -0
  58. package/dist/guest/toolchains/engines/unreal-status.js +41 -0
  59. package/dist/guest/toolchains/native/debuggers.d.ts +6 -0
  60. package/dist/guest/toolchains/native/debuggers.js +57 -0
  61. package/dist/guest/toolchains/native/mesa.js +15 -8
  62. package/dist/guest/toolchains/native/rust-extras.d.ts +7 -0
  63. package/dist/guest/toolchains/native/rust-extras.js +23 -1
  64. package/dist/guest/toolchains/native/unreal.d.ts +2 -2
  65. package/dist/guest/toolchains/native/unreal.js +96 -18
  66. package/dist/guest/toolchains/plan.js +12 -1
  67. package/dist/guest/toolchains/tools/vscode-profile.js +2 -0
  68. package/dist/host/accounts/kinds/epic.js +11 -5
  69. package/dist/host/accounts/source.d.ts +2 -0
  70. package/dist/host/accounts/source.js +1 -0
  71. package/dist/host/pc-installs/discover.d.ts +2 -0
  72. package/dist/host/pc-installs/discover.js +25 -5
  73. package/dist/host/pc-installs/epic.d.ts +3 -0
  74. package/dist/host/pc-installs/epic.js +33 -2
  75. package/dist/host/pc-installs/source.d.ts +1 -1
  76. package/dist/host/pc-installs/unreal-vault.d.ts +11 -0
  77. package/dist/host/pc-installs/unreal-vault.js +78 -0
  78. package/dist/host/pc-installs/unreal.d.ts +36 -0
  79. package/dist/host/pc-installs/unreal.js +145 -2
  80. package/dist/host/profile/claude-profile.d.ts +8 -0
  81. package/dist/host/profile/claude-profile.js +43 -4
  82. package/dist/host/profile/index.d.ts +1 -1
  83. package/dist/host/profile/index.js +1 -1
  84. package/dist/runtime/agent.js +81 -7
  85. package/dist/runtime/daemon.js +2645 -1287
  86. package/dist/runtime/db.js +81 -7
  87. package/dist/runtime/main.js +2901 -1722
  88. package/dist/runtime/netproxy.js +81 -7
  89. package/dist/runtime/tool.js +81 -7
  90. package/dist/runtime/view.js +81 -7
  91. package/dist/selftest/agent-paths.test.js +1 -0
  92. package/dist/selftest/agents-web.test.js +12 -0
  93. package/dist/selftest/engines.test.js +602 -3
  94. package/dist/selftest/heavy-load.test.js +3 -1
  95. package/dist/selftest/hostile-input.test.js +11 -1
  96. package/dist/selftest/index.js +2 -0
  97. package/dist/selftest/menu-screens.js +3 -1
  98. package/dist/selftest/mixamo.test.d.ts +2 -0
  99. package/dist/selftest/mixamo.test.js +60 -0
  100. package/dist/selftest/secrets.test.js +36 -1
  101. package/dist/selftest/support/engine-golden.js +3 -0
  102. package/dist/selftest/support/fixtures.d.ts +3 -1
  103. package/dist/selftest/support/unreal-sample.d.ts +2 -0
  104. package/dist/selftest/support/unreal-sample.js +83 -0
  105. package/dist/selftest/toolchains.test.js +32 -2
  106. package/dist/shared/accounts/index.d.ts +1 -1
  107. package/dist/shared/accounts/index.js +1 -1
  108. package/dist/shared/accounts/kinds/epic.d.ts +6 -1
  109. package/dist/shared/accounts/kinds/epic.js +11 -1
  110. package/dist/shared/apps/associations.d.ts +7 -0
  111. package/dist/shared/apps/associations.js +26 -0
  112. package/dist/shared/apps/catalogue-agents-web.d.ts +4 -1
  113. package/dist/shared/apps/catalogue-agents-web.js +5 -2
  114. package/dist/shared/apps/catalogue-gamedev.js +71 -1
  115. package/dist/shared/apps/index.d.ts +2 -0
  116. package/dist/shared/apps/index.js +2 -0
  117. package/dist/shared/apps/preview.d.ts +2 -0
  118. package/dist/shared/apps/preview.js +10 -0
  119. package/dist/shared/browser-policy.d.ts +14 -0
  120. package/dist/shared/browser-policy.js +22 -0
  121. package/dist/shared/io/index.d.ts +1 -0
  122. package/dist/shared/io/index.js +1 -0
  123. package/dist/shared/io/rotate-log.d.ts +2 -0
  124. package/dist/shared/io/rotate-log.js +16 -0
  125. package/dist/shared/memory/policy.d.ts +5 -0
  126. package/dist/shared/memory/policy.js +7 -0
  127. package/dist/shared/paths/pc-installs.d.ts +13 -2
  128. package/dist/shared/paths/pc-installs.js +1 -0
  129. package/dist/shared/readiness.d.ts +0 -1
  130. package/dist/shared/readiness.js +0 -1
  131. package/dist/shared/signer.d.ts +4 -0
  132. package/dist/shared/signer.js +2 -0
  133. package/dist/shared/unreal/engines.d.ts +161 -0
  134. package/dist/shared/unreal/engines.js +308 -0
  135. package/dist/shared/unreal/guide.d.ts +19 -0
  136. package/dist/shared/unreal/guide.js +105 -0
  137. package/dist/shared/unreal/index.d.ts +7 -0
  138. package/dist/shared/unreal/index.js +8 -0
  139. package/dist/shared/unreal/launcher-files.d.ts +88 -0
  140. package/dist/shared/unreal/launcher-files.js +292 -0
  141. package/dist/shared/unreal/mixamo.d.ts +5 -0
  142. package/dist/shared/unreal/mixamo.js +16 -0
  143. package/dist/shared/unreal/room.d.ts +17 -0
  144. package/dist/shared/unreal/room.js +34 -0
  145. package/dist/shared/unreal/status.d.ts +54 -0
  146. package/dist/shared/unreal/status.js +57 -0
  147. package/dist/shared/unreal/vault.d.ts +21 -0
  148. package/dist/shared/unreal/vault.js +67 -0
  149. package/dist/shared/unreal/vs-check.d.ts +39 -0
  150. package/dist/shared/unreal/vs-check.js +79 -0
  151. package/dist/web/index.html +1 -1
  152. package/dist/web/{main-CCILLX2M.js → main-FNE6XEHA.js} +84 -84
  153. package/package.json +1 -1
  154. package/sandbox/csharp/VsSetup.cs +210 -0
  155. package/sandbox/scripts/get-vs-setup.ps1 +93 -0
  156. package/sandbox/scripts/set-registry-values.ps1 +35 -0
  157. package/schemas/config.schema.json +35 -2
  158. package/schemas/sandbox-config.schema.json +8 -1
@@ -1,7 +1,7 @@
1
1
  import { createRequire as __createRequire } from 'node:module'; const require = __createRequire(import.meta.url);
2
2
  import {
3
3
  attach
4
- } from "./chunk-IZRGTSQ5.js";
4
+ } from "./chunk-F4PVBN23.js";
5
5
  export {
6
6
  attach
7
7
  };
@@ -2,8 +2,8 @@
2
2
  import { createRequire as __createRequire } from 'node:module'; const require = __createRequire(import.meta.url);
3
3
  import {
4
4
  run
5
- } from "./chunk-SUYYVIDW.js";
6
- import "./chunk-IZRGTSQ5.js";
5
+ } from "./chunk-3GAXLATK.js";
6
+ import "./chunk-F4PVBN23.js";
7
7
 
8
8
  // src/bin/claude-host.ts
9
9
  var args = process.argv.slice(2);
@@ -2,8 +2,8 @@
2
2
  import { createRequire as __createRequire } from 'node:module'; const require = __createRequire(import.meta.url);
3
3
  import {
4
4
  run
5
- } from "./chunk-SUYYVIDW.js";
6
- import "./chunk-IZRGTSQ5.js";
5
+ } from "./chunk-3GAXLATK.js";
6
+ import "./chunk-F4PVBN23.js";
7
7
 
8
8
  // src/bin/claude-sbx.ts
9
9
  run(process.argv.slice(2), { harness: "claude" });
@@ -2,8 +2,8 @@
2
2
  import { createRequire as __createRequire } from 'node:module'; const require = __createRequire(import.meta.url);
3
3
  import {
4
4
  run
5
- } from "./chunk-SUYYVIDW.js";
6
- import "./chunk-IZRGTSQ5.js";
5
+ } from "./chunk-3GAXLATK.js";
6
+ import "./chunk-F4PVBN23.js";
7
7
 
8
8
  // src/bin/codex-sbx.ts
9
9
  run(process.argv.slice(2), { harness: "codex" });
@@ -2,8 +2,8 @@
2
2
  import { createRequire as __createRequire } from 'node:module'; const require = __createRequire(import.meta.url);
3
3
  import {
4
4
  run
5
- } from "./chunk-SUYYVIDW.js";
6
- import "./chunk-IZRGTSQ5.js";
5
+ } from "./chunk-3GAXLATK.js";
6
+ import "./chunk-F4PVBN23.js";
7
7
 
8
8
  // src/bin/gemini-sbx.ts
9
9
  run(process.argv.slice(2), { harness: "gemini" });
@@ -2,8 +2,8 @@
2
2
  import { createRequire as __createRequire } from 'node:module'; const require = __createRequire(import.meta.url);
3
3
  import {
4
4
  run
5
- } from "./chunk-SUYYVIDW.js";
6
- import "./chunk-IZRGTSQ5.js";
5
+ } from "./chunk-3GAXLATK.js";
6
+ import "./chunk-F4PVBN23.js";
7
7
 
8
8
  // src/bin/sandbox.ts
9
9
  run(process.argv.slice(2));
@@ -2,8 +2,8 @@
2
2
  import { createRequire as __createRequire } from 'node:module'; const require = __createRequire(import.meta.url);
3
3
  import {
4
4
  run
5
- } from "./chunk-SUYYVIDW.js";
6
- import "./chunk-IZRGTSQ5.js";
5
+ } from "./chunk-3GAXLATK.js";
6
+ import "./chunk-F4PVBN23.js";
7
7
 
8
8
  // src/bin/sbx-hide.ts
9
9
  run(process.argv.slice(2), { command: "hide" });
@@ -2,8 +2,8 @@
2
2
  import { createRequire as __createRequire } from 'node:module'; const require = __createRequire(import.meta.url);
3
3
  import {
4
4
  run
5
- } from "./chunk-SUYYVIDW.js";
6
- import "./chunk-IZRGTSQ5.js";
5
+ } from "./chunk-3GAXLATK.js";
6
+ import "./chunk-F4PVBN23.js";
7
7
 
8
8
  // src/bin/sbx-show.ts
9
9
  run(process.argv.slice(2), { command: "show" });
@@ -180,22 +180,25 @@ class Launcher {
180
180
  : prepareGithubToken(config, ctx.storageRoot, undefined, bootWants.wants.projects);
181
181
  // accounts.<kind>.share: the saved service sign-ins this boot's toolchains use, for the one-shot file only.
182
182
  const launchId = newLaunchId();
183
+ const toolchainsOn = toolchainsOnForBoot({ run }, bootWants.wants.projects);
183
184
  const accounts = generateOnly
184
185
  ? null
185
186
  : prepareServiceAccounts({
186
187
  config,
187
188
  others: bootWants.wants.projects,
188
- on: toolchainsOnForBoot({ run }, bootWants.wants.projects),
189
+ on: toolchainsOn,
189
190
  storageRoot: ctx.storageRoot,
190
191
  dataPath: run.dataPath,
191
192
  launchId,
192
193
  // A new launch needs no sandbox running, so a lease held elsewhere is from one that stopped.
193
194
  isRunning: () => isSandboxRunning(),
194
195
  });
195
- // pcInstalls: this PC's games, engines and editors, read-only (yours only: never from a project file).
196
+ // pcInstalls: this PC's games, engines and editors, read-only (yours only: never from a project file). The
197
+ // engines ("auto") come along whenever this launch sets the sandbox up for Unreal.
196
198
  const pcInstalls = discoverPcInstalls(ctx.loaded.config.pcInstalls, {
197
199
  packageRoot: ctx.packageRoot,
198
200
  storageRoot: ctx.storageRoot,
201
+ unrealOn: toolchainsOn.has('unreal'),
199
202
  });
200
203
  // This PC's commit (RAM plus paging file) bounds the sandbox's memory as much as free RAM does.
201
204
  const hostMemory = generateOnly ? null : readHostMemory(ctx.storageRoot);
@@ -7,6 +7,7 @@ import { agentCommandsStatus } from '../../app/features/agent-commands/index.js'
7
7
  import { listAccounts } from '../../app/features/accounts/index.js';
8
8
  import { containedStatus, showStorage } from '../../app/features/storage/index.js';
9
9
  import { listProjects } from '../../app/features/projects/index.js';
10
+ import { unrealOverview } from '../../app/features/unreal/index.js';
10
11
  import { definitionHandler } from '../operations/index.js';
11
12
  import { fail } from '../../core/util/index.js';
12
13
  import { claudeHost, config, doctor, hostCommand, logs, sync, watcher } from './maintenance/index.js';
@@ -48,6 +49,8 @@ export const HANDLERS = {
48
49
  return fail('Unknown storage action "' + action + '".', 'Use: sandbox storage, sandbox storage move <folder>, sandbox storage use <folder> or sandbox storage contained on|off|status');
49
50
  },
50
51
  engine: engine,
52
+ // The bare command shows the overview; its actions (add-engine, forget-engine, launcher) are the operations' hints.
53
+ unreal: definitionHandler(unrealOverview),
51
54
  'host-claude': claudeHost,
52
55
  host: hostCommand,
53
56
  _watch: watcher,
@@ -488,6 +488,24 @@ declare const COMMAND_LIST: readonly [{
488
488
  shownAs?: string | undefined;
489
489
  }];
490
490
  readonly json: true;
491
+ }, {
492
+ readonly name: "unreal";
493
+ readonly summary: "Unreal Engine: the sandbox's engines, this PC's engines and assets, register an engine folder, open the launcher";
494
+ readonly group: "Projects & accounts";
495
+ readonly json: true;
496
+ readonly examples: readonly ["sandbox unreal engines, which project uses which, Visual Studio", "sandbox unreal add-engine D:\\UE5 use this engine folder in sandboxes (writable)", "sandbox unreal launcher open the Epic Games Launcher in the running sandbox"];
497
+ readonly args: readonly [{
498
+ readonly name: "action";
499
+ readonly description: "status, add-engine, forget-engine, launcher";
500
+ readonly required: false;
501
+ readonly choices: readonly ["status"];
502
+ }, {
503
+ readonly name: "folder";
504
+ readonly description: "(add-engine, forget-engine) the engine folder";
505
+ readonly required: false;
506
+ readonly when: readonly [];
507
+ }];
508
+ readonly options: readonly [];
491
509
  }, {
492
510
  readonly name: "engine";
493
511
  readonly summary: "(beta) what runs your sandbox (Windows Sandbox or HCS) and what each engine can do on this PC";
@@ -381,6 +381,32 @@ const COMMAND_LIST = [
381
381
  ],
382
382
  json,
383
383
  },
384
+ {
385
+ name: 'unreal',
386
+ summary: "Unreal Engine: the sandbox's engines, this PC's engines and assets, register an engine folder, open the launcher",
387
+ group: 'Projects & accounts',
388
+ json,
389
+ examples: [
390
+ 'sandbox unreal engines, which project uses which, Visual Studio',
391
+ 'sandbox unreal add-engine D:\\UE5 use this engine folder in sandboxes (writable)',
392
+ 'sandbox unreal launcher open the Epic Games Launcher in the running sandbox',
393
+ ],
394
+ args: [
395
+ {
396
+ name: 'action',
397
+ description: 'status, add-engine, forget-engine, launcher',
398
+ required: false,
399
+ choices: ['status'],
400
+ },
401
+ {
402
+ name: 'folder',
403
+ description: '(add-engine, forget-engine) the engine folder',
404
+ required: false,
405
+ when: [],
406
+ },
407
+ ],
408
+ options: [],
409
+ },
384
410
  {
385
411
  name: 'engine',
386
412
  summary: '(beta) what runs your sandbox (Windows Sandbox or HCS) and what each engine can do on this PC',
@@ -12,6 +12,7 @@ export const COMMAND_PLACES = {
12
12
  data: { place: 'Maintenance > Data' },
13
13
  storage: { place: 'Maintenance > Storage' },
14
14
  engine: { place: 'Maintenance > Engine' },
15
+ unreal: { place: 'Sandbox > Unreal Engine' },
15
16
  sync: { place: 'Maintenance > More commands' },
16
17
  logs: { place: 'Maintenance > Logs (key f follows)' },
17
18
  setup: { place: 'Maintenance > Health' },
@@ -1,5 +1,6 @@
1
1
  import { type ProjectsList } from '../../../app/features/projects/index.js';
2
2
  import { type SandboxStatus } from '../../../app/features/sandbox/index.js';
3
+ import { type UnrealOverview } from '../../../app/features/unreal/index.js';
3
4
  import type { Context } from '../../commands/context.js';
4
5
  import { type DatabaseRow } from '../../services/index.js';
5
6
  import { type App, type ListOptions, type ListResult } from '../../tui/index.js';
@@ -11,6 +12,8 @@ export interface SandboxModel {
11
12
  readonly databases: DatabaseRow[] | null;
12
13
  /** The agent "Start the sandbox" offers while nothing runs. */
13
14
  readonly defaultAgent: string;
15
+ /** The Unreal overview; null when it could not be read. */
16
+ readonly unreal: UnrealOverview | null;
14
17
  }
15
18
  /** The list for the sandbox tab. Pure. */
16
19
  export declare function sandboxOptions(m: SandboxModel): ListOptions<string>;
@@ -1,9 +1,10 @@
1
1
  // Section 3: the sandbox as a whole. Status (the sandbox view: its facts, the window, stopping it), Projects (the
2
- // projects view: roots, the projects and the all-projects setting) and Databases (start and stop), each a category on
3
- // the left.
2
+ // projects view: roots, the projects and the all-projects setting), Databases (start and stop) and Unreal Engine (the
3
+ // unreal view), each a category on the left.
4
4
  import { listProjects, projectsPage } from '../../../app/features/projects/index.js';
5
5
  import { sandboxPage, sandboxStatus } from '../../../app/features/sandbox/index.js';
6
6
  import { readQuery } from '../../../app/registry.js';
7
+ import { unrealOverview, unrealPage } from '../../../app/features/unreal/index.js';
7
8
  import { errMsg, safeText } from '../../../core/util/index.js';
8
9
  import { RunDir } from '../../../shared/channel/index.js';
9
10
  import { DatabasesService } from '../../services/index.js';
@@ -19,7 +20,18 @@ const CATEGORIES = [
19
20
  description: 'Project roots (folders of projects, opened without a restart) and the projects started before.',
20
21
  },
21
22
  { id: 'databases', label: 'Databases', description: 'Databases the sandbox can run: start and stop them.' },
23
+ {
24
+ id: 'unreal',
25
+ label: 'Unreal Engine',
26
+ description: "Engines, which project uses which, this PC's engines and assets, Visual Studio and the debugger.",
27
+ },
22
28
  ];
29
+ /** The Unreal category: the Unreal view's rows and keys (prefixed, so they never clash with other rows). */
30
+ function unrealMenu(m) {
31
+ return m.unreal
32
+ ? menuView(unrealPage(m.unreal), { category: 'unreal', prefix: 'unreal:', refresh: false })
33
+ : undefined;
34
+ }
23
35
  /** The status category: the sandbox view's rows (prefixed, so they never clash with the projects' rows). */
24
36
  function statusView(m) {
25
37
  return menuView(sandboxPage(m.report), { category: 'status', prefix: 'status:', refresh: false });
@@ -32,6 +44,8 @@ function projectsView(m) {
32
44
  function viewFor(m, category) {
33
45
  if (category === 'projects')
34
46
  return projectsView(m);
47
+ if (category === 'unreal')
48
+ return unrealMenu(m);
35
49
  if (category === 'status' || category === undefined)
36
50
  return statusView(m);
37
51
  return undefined;
@@ -64,16 +78,31 @@ export function sandboxOptions(m) {
64
78
  section: 'Databases',
65
79
  description: d.title + '. Enter ' + (d.running ? 'stops' : 'starts') + ' it (the first start downloads it).',
66
80
  }));
81
+ const unreal = unrealMenu(m);
82
+ const noUnreal = unreal
83
+ ? []
84
+ : [
85
+ {
86
+ value: 'info:unreal',
87
+ label: 'Could not read the Unreal set-up',
88
+ category: 'unreal',
89
+ section: 'Unreal Engine',
90
+ description: 'l looks again.',
91
+ disabled: 'l looks again.',
92
+ },
93
+ ];
67
94
  return {
68
95
  path: [],
69
- items: [...statusItems, ...projects.items, ...databases, ...noDatabases],
96
+ items: [...statusItems, ...projects.items, ...databases, ...noDatabases, ...(unreal?.items ?? []), ...noUnreal],
70
97
  categories: CATEGORIES,
71
98
  empty: 'Nothing here yet.',
72
99
  actions: (category) => category === 'projects'
73
100
  ? [...projects.actions, REFRESH]
74
101
  : category === 'databases'
75
102
  ? [REFRESH]
76
- : [...status.actions, REFRESH],
103
+ : category === 'unreal'
104
+ ? [...(unreal?.actions ?? []), REFRESH]
105
+ : [...status.actions, REFRESH],
77
106
  enterLabel: (category, id) => viewFor(m, category)?.enterLabel(category, id) || enterLabel(id ?? '', m),
78
107
  };
79
108
  }
@@ -133,6 +162,7 @@ export class SandboxPage extends ListPage {
133
162
  projects: await readQuery(listProjects, this.ctx),
134
163
  databases,
135
164
  defaultAgent: this.defaultAgent(),
165
+ unreal: await readQuery(unrealOverview, this.ctx).catch(() => null),
136
166
  };
137
167
  }
138
168
  /** The folder `sbx-db` runs in: the sandbox's own workspace. */
@@ -19,6 +19,7 @@ import { webView } from '../../app/features/web/index.js';
19
19
  import { vpnView } from '../../app/features/vpn/index.js';
20
20
  import { githubView } from '../../app/features/github/index.js';
21
21
  import { accessView } from '../../app/features/access/index.js';
22
+ import { unrealView } from '../../app/features/unreal/index.js';
22
23
  export const MENU_VIEWS = [
23
24
  { view: agentsView, section: 'Agents' },
24
25
  { view: agentEnvView, section: 'Agents' },
@@ -28,6 +29,7 @@ export const MENU_VIEWS = [
28
29
  { view: accountsView, section: 'Accounts' },
29
30
  { view: appsView, section: 'Apps' },
30
31
  { view: projectsView, section: 'Sandbox' },
32
+ { view: unrealView, section: 'Sandbox' },
31
33
  { view: healthView, section: 'Maintenance' },
32
34
  { view: dataView, section: 'Maintenance' },
33
35
  { view: storageView, section: 'Maintenance' },
@@ -58,7 +58,14 @@ export const DEFAULTS = {
58
58
  perplexity: { share: false },
59
59
  comet: { share: false },
60
60
  },
61
- pcInstalls: { steamGames: false, epicGames: false, unrealEngines: false, unityEditors: false },
61
+ pcInstalls: {
62
+ steamGames: false,
63
+ epicGames: false,
64
+ unrealEngines: 'auto',
65
+ unrealEngineFolders: [],
66
+ unrealAssets: 'auto',
67
+ unityEditors: false,
68
+ },
62
69
  provisioning: {
63
70
  copyFromHost: true,
64
71
  fallbackToInstall: true,
@@ -196,9 +203,16 @@ export const DEFAULTS = {
196
203
  home: { env: { CODEX_HOME: 'codex' }, loginFiles: ['auth.json', '.credentials.json'], hostDir: '~/.codex' },
197
204
  localState: { CODEX_SQLITE_HOME: 'sqlite' },
198
205
  // Its own command sandbox keeps local users' passwords sealed with DPAPI: unreadable in the next sandbox
199
- // ("CryptUnprotectData" on every command), so it sets that sandbox up again at each boot. tmp\arg0: folders
200
- // Codex cannot remove in a mapped folder (os error 145, a warning at every start); nothing holds them at boot.
201
- bootReset: ['.sandbox-secrets', '.sandbox\\setup_marker.json', '.sandbox\\deny_read_acl_state.json', 'tmp\\arg0'],
206
+ // ("CryptUnprotectData" on every command), so it sets that sandbox up again at each boot. tmp\arg0 and
207
+ // tmp\arg0-windows (newer Codex): folders Codex cannot remove in a mapped folder (os error 145, a warning at
208
+ // every start); nothing holds them at boot.
209
+ bootReset: [
210
+ '.sandbox-secrets',
211
+ '.sandbox\\setup_marker.json',
212
+ '.sandbox\\deny_read_acl_state.json',
213
+ 'tmp\\arg0',
214
+ 'tmp\\arg0-windows',
215
+ ],
202
216
  resumeArguments: ['resume', '--last'],
203
217
  },
204
218
  gemini: {
@@ -131,7 +131,9 @@ export declare const ConfigSchema: z.ZodObject<{
131
131
  pcInstalls: z.ZodObject<{
132
132
  steamGames: z.ZodUnion<readonly [z.ZodBoolean, z.ZodArray<z.ZodNumber>]>;
133
133
  epicGames: z.ZodUnion<readonly [z.ZodBoolean, z.ZodArray<z.ZodString>]>;
134
- unrealEngines: z.ZodUnion<readonly [z.ZodBoolean, z.ZodArray<z.ZodString>]>;
134
+ unrealEngines: z.ZodUnion<readonly [z.ZodLiteral<"auto">, z.ZodBoolean, z.ZodArray<z.ZodString>]>;
135
+ unrealEngineFolders: z.ZodArray<z.ZodString>;
136
+ unrealAssets: z.ZodUnion<readonly [z.ZodLiteral<"auto">, z.ZodBoolean]>;
135
137
  unityEditors: z.ZodUnion<readonly [z.ZodBoolean, z.ZodArray<z.ZodString>]>;
136
138
  }, z.core.$strict>;
137
139
  provisioning: z.ZodObject<{
@@ -689,7 +691,7 @@ export declare const LocalOverridesSchema: z.ZodObject<{
689
691
  zstd: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodLiteral<"auto">]>>;
690
692
  openssl: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodLiteral<"auto">]>>;
691
693
  }, z.core.$strict>>;
692
- unreal: z.ZodOptional<z.ZodObject<{
694
+ node: z.ZodOptional<z.ZodObject<{
693
695
  enabled: z.ZodOptional<z.ZodBoolean>;
694
696
  }, z.core.$strict> | z.ZodObject<{
695
697
  version: z.ZodOptional<z.ZodString>;
@@ -704,7 +706,7 @@ export declare const LocalOverridesSchema: z.ZodObject<{
704
706
  zstd: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodLiteral<"auto">]>>;
705
707
  openssl: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodLiteral<"auto">]>>;
706
708
  }, z.core.$strict>>;
707
- node: z.ZodOptional<z.ZodObject<{
709
+ go: z.ZodOptional<z.ZodObject<{
708
710
  enabled: z.ZodOptional<z.ZodBoolean>;
709
711
  }, z.core.$strict> | z.ZodObject<{
710
712
  version: z.ZodOptional<z.ZodString>;
@@ -719,7 +721,7 @@ export declare const LocalOverridesSchema: z.ZodObject<{
719
721
  zstd: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodLiteral<"auto">]>>;
720
722
  openssl: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodLiteral<"auto">]>>;
721
723
  }, z.core.$strict>>;
722
- go: z.ZodOptional<z.ZodObject<{
724
+ python: z.ZodOptional<z.ZodObject<{
723
725
  enabled: z.ZodOptional<z.ZodBoolean>;
724
726
  }, z.core.$strict> | z.ZodObject<{
725
727
  version: z.ZodOptional<z.ZodString>;
@@ -734,7 +736,7 @@ export declare const LocalOverridesSchema: z.ZodObject<{
734
736
  zstd: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodLiteral<"auto">]>>;
735
737
  openssl: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodLiteral<"auto">]>>;
736
738
  }, z.core.$strict>>;
737
- python: z.ZodOptional<z.ZodObject<{
739
+ rust: z.ZodOptional<z.ZodObject<{
738
740
  enabled: z.ZodOptional<z.ZodBoolean>;
739
741
  }, z.core.$strict> | z.ZodObject<{
740
742
  version: z.ZodOptional<z.ZodString>;
@@ -749,7 +751,7 @@ export declare const LocalOverridesSchema: z.ZodObject<{
749
751
  zstd: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodLiteral<"auto">]>>;
750
752
  openssl: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodLiteral<"auto">]>>;
751
753
  }, z.core.$strict>>;
752
- rust: z.ZodOptional<z.ZodObject<{
754
+ msvc: z.ZodOptional<z.ZodObject<{
753
755
  enabled: z.ZodOptional<z.ZodBoolean>;
754
756
  }, z.core.$strict> | z.ZodObject<{
755
757
  version: z.ZodOptional<z.ZodString>;
@@ -764,7 +766,7 @@ export declare const LocalOverridesSchema: z.ZodObject<{
764
766
  zstd: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodLiteral<"auto">]>>;
765
767
  openssl: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodLiteral<"auto">]>>;
766
768
  }, z.core.$strict>>;
767
- msvc: z.ZodOptional<z.ZodObject<{
769
+ unreal: z.ZodOptional<z.ZodObject<{
768
770
  enabled: z.ZodOptional<z.ZodBoolean>;
769
771
  }, z.core.$strict> | z.ZodObject<{
770
772
  version: z.ZodOptional<z.ZodString>;
@@ -2,6 +2,8 @@ import { z } from 'zod';
2
2
  export declare const PcInstallsSchema: z.ZodObject<{
3
3
  steamGames: z.ZodUnion<readonly [z.ZodBoolean, z.ZodArray<z.ZodNumber>]>;
4
4
  epicGames: z.ZodUnion<readonly [z.ZodBoolean, z.ZodArray<z.ZodString>]>;
5
- unrealEngines: z.ZodUnion<readonly [z.ZodBoolean, z.ZodArray<z.ZodString>]>;
5
+ unrealEngines: z.ZodUnion<readonly [z.ZodLiteral<"auto">, z.ZodBoolean, z.ZodArray<z.ZodString>]>;
6
+ unrealEngineFolders: z.ZodArray<z.ZodString>;
7
+ unrealAssets: z.ZodUnion<readonly [z.ZodLiteral<"auto">, z.ZodBoolean]>;
6
8
  unityEditors: z.ZodUnion<readonly [z.ZodBoolean, z.ZodArray<z.ZodString>]>;
7
9
  }, z.core.$strict>;
@@ -11,7 +11,16 @@ export const PcInstallsSchema = z
11
11
  .strictObject({
12
12
  steamGames: pick(z.number().int().positive(), 'Steam games (true maps each library; a list of app IDs maps only those games)', '[480, 730]'),
13
13
  epicGames: pick(z.string().regex(/^[A-Za-z0-9._-]{1,100}$/), 'Epic Games Launcher games (by their app name)', '["Fortnite"]'),
14
- unrealEngines: pick(z.string().regex(/^(UE_)?\d+\.\d+$/), 'Unreal Engine versions installed with the Epic Games Launcher', '["5.4"]'),
14
+ unrealEngines: z
15
+ .union([z.literal('auto'), z.boolean(), z.array(z.string().regex(/^(UE_)?\d+\.\d+$/))])
16
+ .describe('Unreal Engine versions installed on this PC (with the Epic Games Launcher, or source builds it lists), read-only in the sandbox under C:\\PcInstalls\\unreal and registered there, so projects build with them without a download. "auto" = all of them whenever the sandbox is set up for Unreal, true = always, false = never, or a list (e.g. ["5.4"]).'),
17
+ unrealEngineFolders: z
18
+ .array(z.string().min(3).max(260))
19
+ .max(8)
20
+ .describe('Unreal Engine folders of this PC to use in the sandbox, writable (a source build, or a copy you build plugins into): each must hold Engine\\Build\\Build.version. Mapped under C:\\PcInstalls\\unreal and registered there.'),
21
+ unrealAssets: z
22
+ .union([z.literal('auto'), z.boolean()])
23
+ .describe('The Epic Games Launcher\'s vault cache of this PC (the Fab and Marketplace packs you downloaded), read-only in the sandbox at C:\\PcInstalls\\unreal\\Vault: what you download on your PC is there for the agents at once. "auto" = whenever the sandbox is set up for Unreal, true = always, false = never.'),
15
24
  unityEditors: pick(z.string().regex(/^\d+\.\d+\.\d+[abfp]\d+$/), 'Unity editors installed with Unity Hub', '["6000.0.32f1"]'),
16
25
  })
17
26
  .describe("Games, engines and editors installed on this PC, mapped read-only into sandboxes (no copy, no room on the sandbox's disk).");
@@ -40,7 +40,7 @@ export const ToolchainsSchema = z.strictObject({
40
40
  .describe('"auto" (default): on the sandbox\'s own disk when C: has 25 GB free at boot, else "separate" (builds then write through the mapped workspace, which can overload this PC\'s disk). "shared": cargo\'s default target\\ (shared with builds on this PC, so builds on either side invalidate the other), "separate": <workspace>\\target\\sandbox (kept, no cross-talk), "local": on the sandbox\'s own disk (fastest, lost every boot), or a guest path.'),
41
41
  linker: z
42
42
  .enum(['lld', 'gnu'])
43
- .describe('GNU host: "lld" links with Rust\'s own lld through MinGW\'s gcc (much less memory than GNU ld), "gnu" with GNU ld.'),
43
+ .describe('"lld" (default) links with Rust\'s own lld: through MinGW\'s gcc on the GNU host (much less memory than GNU ld), and instead of link.exe on the MSVC host (link.exe writes executables into mapped folders as all zeros: os error 1392). "gnu" keeps GNU ld / link.exe.'),
44
44
  linkSlots: z
45
45
  .union([z.literal('auto'), z.number().int().min(1).max(32)])
46
46
  .describe('GNU host: how many programs may link at the same time in the whole sandbox (other links wait). "auto" = one per 6 GB of sandbox memory, 1 to 4 (the memory governor slows links down too when memory runs short).'),
@@ -1,26 +1,31 @@
1
1
  // Epic Games: the handed-over sign-in goes into the launcher's GameUserSettings.ini before anyone opens it (its other
2
- // settings are kept, and the file keeps the encoding it had: the launcher often writes UTF-16). The watch reads it
3
- // back to notice when the launcher renews it, or when you sign in here the first time.
2
+ // settings are kept, and the file keeps the encoding it had: the launcher often writes UTF-16). Launcher 17+ reads
3
+ // Config\WindowsEditor, older ones Config\Windows: both get it. The watch reads it back to notice when the launcher
4
+ // renews it, or when you sign in here the first time.
4
5
  import fs from 'node:fs';
5
6
  import path from 'node:path';
6
- import { epicSettingsFile, MAX_EPIC_INI_BYTES, readEpicLogin, withEpicLogin, } from '../../../shared/accounts/index.js';
7
+ import { epicSettingsFiles, MAX_EPIC_INI_BYTES, readEpicLogin, withEpicLogin, } from '../../../shared/accounts/index.js';
7
8
  import { decodeText, encodeText, readBoundedFile } from '../../../shared/io/index.js';
8
9
  import { localAppData } from '../adapter.js';
9
- function settingsFile() {
10
- return epicSettingsFile(localAppData());
11
- }
12
10
  export const epicAdapter = {
13
11
  kind: 'epic',
14
12
  apply(p) {
15
- const file = settingsFile();
16
- const bytes = readBoundedFile(file, MAX_EPIC_INI_BYTES);
17
- const read = bytes ? decodeText(bytes) : { text: '', encoding: 'utf8' };
18
- fs.mkdirSync(path.dirname(file), { recursive: true });
19
- fs.writeFileSync(file, encodeText(withEpicLogin(read.text, p), read.encoding));
13
+ for (const file of epicSettingsFiles(localAppData())) {
14
+ const bytes = readBoundedFile(file, MAX_EPIC_INI_BYTES);
15
+ const read = bytes ? decodeText(bytes) : { text: '', encoding: 'utf8' };
16
+ fs.mkdirSync(path.dirname(file), { recursive: true });
17
+ fs.writeFileSync(file, encodeText(withEpicLogin(read.text, p), read.encoding));
18
+ }
20
19
  return { summary: 'Epic Games Launcher: signed in with your saved sign-in' };
21
20
  },
22
21
  current() {
23
- const bytes = readBoundedFile(settingsFile(), MAX_EPIC_INI_BYTES);
24
- return bytes ? readEpicLogin(decodeText(bytes).text) : null;
22
+ // The newest place first: the one the launcher in this sandbox writes when it renews the sign-in.
23
+ for (const file of epicSettingsFiles(localAppData())) {
24
+ const bytes = readBoundedFile(file, MAX_EPIC_INI_BYTES);
25
+ const login = bytes ? readEpicLogin(decodeText(bytes).text) : null;
26
+ if (login)
27
+ return login;
28
+ }
29
+ return null;
25
30
  },
26
31
  };
@@ -1,3 +1,3 @@
1
- import type { AppEntry } from '../../shared/apps/index.js';
1
+ import { type AppEntry } from '../../shared/apps/index.js';
2
2
  /** Settings an app needs on every boot (the sandbox's registry is new each time), and files next to its program. */
3
3
  export declare function afterInstall(entry: AppEntry, exe: string): Promise<void>;
@@ -1,11 +1,12 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
+ import { associationPlan } from '../../shared/apps/index.js';
3
4
  import { SYSTEM } from '../../shared/system.js';
4
5
  import { errMsg } from '../../core/util/errors.js';
5
6
  import { takePendingComet } from '../accounts/kinds/comet.js';
6
7
  import { restoreCometSignIn } from '../browser/comet.js';
7
8
  import { log, warn } from '../runtime/log.js';
8
- import { execAsync } from '../runtime/process.js';
9
+ import { execAsync, runScriptWithJsonAsync } from '../runtime/process.js';
9
10
  import { exists, mkdirp } from '../runtime/fs.js';
10
11
  import { arch, tool } from '../toolchains/tool-env.js';
11
12
  /** The Sysinternals tools whose EULA is accepted up front (each asks once otherwise). */
@@ -58,6 +59,10 @@ export async function afterInstall(entry, exe) {
58
59
  mkdirp(path.join(dir, 'Data'));
59
60
  if (entry.id === 'comet')
60
61
  await restoreComet(exe);
62
+ const plan = associationPlan(entry.id, exe);
63
+ if (plan.length) {
64
+ await runScriptWithJsonAsync('set-registry-values.ps1', 'PlanFile', { values: plan }).catch((e) => warn(entry.name + ': double-click file types were not set (' + errMsg(e) + ')'));
65
+ }
61
66
  if (entry.id === 'sysinternals') {
62
67
  for (const name of SYSINTERNALS_TOOLS) {
63
68
  await execAsync(SYSTEM.reg(), ['add', 'HKCU\\Software\\Sysinternals\\' + name, '/v', 'EulaAccepted', '/t', 'REG_DWORD', '/d', '1', '/f'], { stdio: 'ignore' }).catch(() => undefined);
@@ -13,6 +13,7 @@ export declare class AppInstaller {
13
13
  private readonly parallel;
14
14
  private readonly gui;
15
15
  private readonly installed;
16
+ private readonly installedIds;
16
17
  constructor(env: ToolEnv, options: AppsOptions, parallel?: number);
17
18
  /**
18
19
  * Downloads the plain downloads (not pinned, not npm, not remote) of the apps that are not installed yet, into
@@ -2,7 +2,7 @@
2
2
  // failing is reported and never stops the others.
3
3
  import fs from 'node:fs';
4
4
  import path from 'node:path';
5
- import { appsToInstall } from '../../shared/apps/index.js';
5
+ import { appsToInstall, previewGuideLine } from '../../shared/apps/index.js';
6
6
  import { downloader, moveIntoPlace, stagingFolder } from '../../shared/download/index.js';
7
7
  import { safeRemove } from '../../shared/io/safe-remove.js';
8
8
  import { dryRun, launchDir } from '../runtime/paths.js';
@@ -33,6 +33,7 @@ export class AppInstaller {
33
33
  parallel;
34
34
  gui = [];
35
35
  installed = [];
36
+ installedIds = [];
36
37
  constructor(env, options, parallel = PARALLEL) {
37
38
  this.env = env;
38
39
  this.options = options;
@@ -82,6 +83,9 @@ export class AppInstaller {
82
83
  }
83
84
  if (this.installed.length)
84
85
  this.env.summary.push('apps: ' + this.installed.join(', ') + ' (shortcuts on the desktop and in Start > Sandbox apps)');
86
+ const preview = previewGuideLine(this.installedIds);
87
+ if (preview)
88
+ this.env.summary.push(preview);
85
89
  }
86
90
  async installOne(entry) {
87
91
  try {
@@ -102,11 +106,17 @@ export class AppInstaller {
102
106
  throw new Error(entry.exe + ' not found in the download');
103
107
  await afterInstall(entry, exe);
104
108
  // A proxy or MCP server (agent add-ons) is started by the sandbox or the agent: no PATH entry, no shortcut.
105
- if (!entry.service && (entry.cli || entry.id === 'sysinternals'))
106
- this.env.prepend.push(path.dirname(exe));
109
+ if (!entry.service && (entry.cli || entry.id === 'sysinternals')) {
110
+ // rustup's rust-analyzer proxy (in cargo's bin folder) only errors until its component is added.
111
+ if (entry.id === 'rust-analyzer')
112
+ this.env.prepend.unshift(path.dirname(exe));
113
+ else
114
+ this.env.prepend.push(path.dirname(exe));
115
+ }
107
116
  if (!entry.cli && !entry.service)
108
117
  this.gui.push({ name: entry.name, exe, ...this.launchArgs(entry) });
109
118
  this.installed.push(entry.name);
119
+ this.installedIds.push(entry.id);
110
120
  }
111
121
  catch (e) {
112
122
  warn(entry.name + ' could not be installed and is skipped; the other apps are not affected. Reason: ' + errMsg(e));
@@ -15,10 +15,11 @@ const INNER = [
15
15
  /^chrome\.7z$/i,
16
16
  /^chrome\.packed\.7z$/i,
17
17
  /^mini_installer\.exe$/i,
18
- /^updater\.packed\.7z$/i,
18
+ /^updater\.(packed\.)?7z$/i,
19
19
  ];
20
20
  const MAX_LEVELS = 6;
21
- const MAX_DEPTH = 4;
21
+ /** Comet's installer nests mini_installer.exe five folders down (bin\Offline\{id}\{id}). */
22
+ const MAX_DEPTH = 7;
22
23
  const MAX_ENTRIES = 20000;
23
24
  /** Where the installer is kept when it could not be unpacked (below the app's folder). */
24
25
  export const KEPT_INSTALLER_DIR = 'installer';