windows-development-sandbox 1.27.1 → 1.27.3

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 (78) 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-QUHNXHVG.js → chunk-4M5SZZ5P.js} +46 -41
  14. package/dist/bin/{chunk-4TLSWPG7.js → chunk-FA5VWLP4.js} +1 -1
  15. package/dist/bin/{chunk-RSIX7C6D.js → chunk-RU7XRN62.js} +249 -101
  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/guest/apps/app-installer.js +7 -5
  26. package/dist/guest/apps/comet-unpack.js +2 -2
  27. package/dist/guest/runtime/pools.d.ts +4 -4
  28. package/dist/guest/runtime/pools.js +4 -15
  29. package/dist/guest/runtime/process.d.ts +5 -2
  30. package/dist/guest/runtime/process.js +23 -3
  31. package/dist/guest/toolchains/engines/epic-launcher.d.ts +6 -0
  32. package/dist/guest/toolchains/engines/epic-launcher.js +19 -1
  33. package/dist/guest/toolchains/engines/unreal-engines.js +2 -1
  34. package/dist/guest/toolchains/installers.d.ts +3 -3
  35. package/dist/guest/toolchains/installers.js +5 -5
  36. package/dist/guest/toolchains/native/vcruntime.js +2 -2
  37. package/dist/guest/toolchains/native/vs-persist.d.ts +2 -2
  38. package/dist/guest/toolchains/signature.d.ts +2 -0
  39. package/dist/guest/toolchains/signature.js +9 -2
  40. package/dist/guest/toolchains/step-scheduler.js +1 -1
  41. package/dist/host/accounts/deliver.js +15 -7
  42. package/dist/host/pc-installs/discover.js +5 -2
  43. package/dist/host/pc-installs/unreal.d.ts +3 -1
  44. package/dist/host/pc-installs/unreal.js +27 -1
  45. package/dist/runtime/agent.js +34 -22
  46. package/dist/runtime/daemon.js +96 -51
  47. package/dist/runtime/db.js +32 -16
  48. package/dist/runtime/main.js +98 -53
  49. package/dist/runtime/netproxy.js +26 -12
  50. package/dist/runtime/tool.js +32 -15
  51. package/dist/runtime/view.js +26 -12
  52. package/dist/selftest/app.test.js +20 -2
  53. package/dist/selftest/boot-runner.test.js +15 -1
  54. package/dist/selftest/engines.test.js +22 -1
  55. package/dist/selftest/menu-screens.js +52 -1
  56. package/dist/selftest/menu.test.js +29 -3
  57. package/dist/selftest/service-accounts.test.js +77 -2
  58. package/dist/selftest/ui-text.test.js +4 -0
  59. package/dist/shared/accounts/kinds.js +9 -9
  60. package/dist/shared/apps/catalogue-gamedev.js +1 -0
  61. package/dist/shared/apps/catalogue.d.ts +2 -0
  62. package/dist/shared/concurrency/pools.js +1 -1
  63. package/dist/shared/download/archive.js +4 -2
  64. package/dist/shared/download/index.d.ts +1 -1
  65. package/dist/shared/download/index.js +1 -1
  66. package/dist/shared/download/spawn.d.ts +7 -1
  67. package/dist/shared/download/spawn.js +16 -1
  68. package/dist/shared/paths/pc-installs.d.ts +8 -0
  69. package/dist/shared/unreal/launcher-files.d.ts +7 -0
  70. package/dist/shared/unreal/launcher-files.js +32 -1
  71. package/dist/web/index.html +1 -1
  72. package/dist/web/{main-VPMYTSUL.js → main-NKM7ECSB.js} +59 -59
  73. package/native/csharp/HcsHost/AssemblyInfo.cs +3 -3
  74. package/native/csharp/HostTools/AssemblyInfo.cs +3 -3
  75. package/native/csharp/StorageHelper/AssemblyInfo.cs +3 -3
  76. package/package.json +1 -1
  77. package/schemas/config.schema.json +9 -9
  78. package/schemas/sandbox-config.schema.json +9 -9
@@ -26121,6 +26121,7 @@ var GAMEDEV_APPS = [
26121
26121
  size: "100 MB",
26122
26122
  source: { github: "f3d-app/f3d", asset: "^F3D-[\\d.]+-Windows-x86_64\\.zip$" },
26123
26123
  exe: "f3d.exe",
26124
+ onPath: true,
26124
26125
  needs: ["mesa"]
26125
26126
  },
26126
26127
  {
@@ -27220,7 +27221,7 @@ var SERVICE_SPECS = {
27220
27221
  },
27221
27222
  steam: {
27222
27223
  kind: "steam",
27223
- label: "Steam (SteamCMD)",
27224
+ label: "Steam",
27224
27225
  usedBy: ["steam"],
27225
27226
  sources: ["file", "sandbox"],
27226
27227
  singleHolder: true,
@@ -27256,7 +27257,7 @@ var SERVICE_SPECS = {
27256
27257
  },
27257
27258
  cfx: {
27258
27259
  kind: "cfx",
27259
- label: "Cfx.re (FiveM / RedM server key)",
27260
+ label: "FiveM key",
27260
27261
  usedBy: ["fivem"],
27261
27262
  sources: ["typed", "file"],
27262
27263
  singleHolder: false,
@@ -27285,7 +27286,7 @@ var SERVICE_SPECS = {
27285
27286
  },
27286
27287
  keystore: {
27287
27288
  kind: "keystore",
27288
- label: "Android upload keystore",
27289
+ label: "Android keystore",
27289
27290
  usedBy: ["android"],
27290
27291
  sources: ["file"],
27291
27292
  singleHolder: false,
@@ -27300,7 +27301,7 @@ var SERVICE_SPECS = {
27300
27301
  },
27301
27302
  play: {
27302
27303
  kind: "play",
27303
- label: "Google Play (service account)",
27304
+ label: "Google Play",
27304
27305
  usedBy: ["android"],
27305
27306
  sources: ["file"],
27306
27307
  singleHolder: false,
@@ -27311,7 +27312,7 @@ var SERVICE_SPECS = {
27311
27312
  },
27312
27313
  expo: {
27313
27314
  kind: "expo",
27314
- label: "Expo (EAS)",
27315
+ label: "Expo",
27315
27316
  usedBy: ["reactNative"],
27316
27317
  sources: ["typed"],
27317
27318
  singleHolder: false,
@@ -27322,7 +27323,7 @@ var SERVICE_SPECS = {
27322
27323
  },
27323
27324
  appstore: {
27324
27325
  kind: "appstore",
27325
- label: "App Store Connect API key",
27326
+ label: "App Store Connect",
27326
27327
  usedBy: ["reactNative", "flutter"],
27327
27328
  sources: ["file", "typed"],
27328
27329
  singleHolder: false,
@@ -27336,7 +27337,7 @@ var SERVICE_SPECS = {
27336
27337
  },
27337
27338
  perplexity: {
27338
27339
  kind: "perplexity",
27339
- label: "Perplexity API key",
27340
+ label: "Perplexity API",
27340
27341
  usedBy: ["perplexity"],
27341
27342
  sources: ["pc", "file", "typed"],
27342
27343
  singleHolder: false,
@@ -27360,7 +27361,7 @@ var SERVICE_SPECS = {
27360
27361
  },
27361
27362
  comet: {
27362
27363
  kind: "comet",
27363
- label: "Comet (Perplexity browser)",
27364
+ label: "Comet",
27364
27365
  usedBy: ["comet"],
27365
27366
  sources: ["sandbox"],
27366
27367
  singleHolder: false,
@@ -27376,7 +27377,7 @@ var SERVICE_SPECS = {
27376
27377
  },
27377
27378
  perplexityapp: {
27378
27379
  kind: "perplexityapp",
27379
- label: "Perplexity desktop app",
27380
+ label: "Perplexity app",
27380
27381
  usedBy: ["perplexityapp"],
27381
27382
  sources: ["sandbox"],
27382
27383
  singleHolder: false,
@@ -28124,10 +28125,20 @@ function scriptArgs(name, args = []) {
28124
28125
  }
28125
28126
 
28126
28127
  // src/shared/download/spawn.ts
28128
+ import os3 from "node:os";
28127
28129
  import { spawn } from "node:child_process";
28130
+ function lowerPriority(pid) {
28131
+ if (pid === void 0) return;
28132
+ try {
28133
+ os3.setPriority(pid, os3.constants.priority.PRIORITY_BELOW_NORMAL);
28134
+ } catch {
28135
+ }
28136
+ }
28128
28137
  function spawnAsync(file2, args, options = {}) {
28138
+ const { lowPriority, ...spawnOptions } = options;
28129
28139
  return new Promise((resolve, reject) => {
28130
- const child = spawn(file2, args, { stdio: "ignore", windowsHide: true, ...options });
28140
+ const child = spawn(file2, args, { stdio: "ignore", windowsHide: true, ...spawnOptions });
28141
+ if (lowPriority) lowerPriority(child.pid);
28131
28142
  child.once("error", reject);
28132
28143
  child.once("close", (code) => resolve(code ?? -1));
28133
28144
  });
@@ -28148,13 +28159,16 @@ function extractArchive(archive, destination, members2 = []) {
28148
28159
  }
28149
28160
  async function extract(archive, destination, members2) {
28150
28161
  fs6.mkdirSync(destination, { recursive: true });
28151
- let status = await spawnAsync(SYSTEM.tar(), ["-xf", archive, "-C", destination, ...members2]);
28162
+ let status = await spawnAsync(SYSTEM.tar(), ["-xf", archive, "-C", destination, ...members2], {
28163
+ lowPriority: true
28164
+ });
28152
28165
  if (status !== 0 && /\.(zip|nupkg)$/i.test(archive)) {
28153
28166
  const zip = archive.endsWith(".zip") ? archive : archive + ".zip";
28154
28167
  if (zip !== archive) fs6.copyFileSync(archive, zip);
28155
28168
  status = await spawnAsync(
28156
28169
  SYSTEM.powershell(),
28157
- scriptArgs("expand-archive.ps1", ["-Archive", zip, "-Destination", destination])
28170
+ scriptArgs("expand-archive.ps1", ["-Archive", zip, "-Destination", destination]),
28171
+ { lowPriority: true }
28158
28172
  );
28159
28173
  }
28160
28174
  if (status !== 0) throw new Error("Could not extract " + archive);
@@ -5551,7 +5551,7 @@ var androidTool = {
5551
5551
 
5552
5552
  // src/guest/devtools/fivem.ts
5553
5553
  import fs17 from "node:fs";
5554
- import os3 from "node:os";
5554
+ import os4 from "node:os";
5555
5555
  import path20 from "node:path";
5556
5556
  import { spawnSync as spawnSync2 } from "node:child_process";
5557
5557
 
@@ -5831,10 +5831,20 @@ function scriptArgs(name2, args2 = []) {
5831
5831
  }
5832
5832
 
5833
5833
  // src/shared/download/spawn.ts
5834
+ import os3 from "node:os";
5834
5835
  import { spawn } from "node:child_process";
5836
+ function lowerPriority(pid) {
5837
+ if (pid === void 0) return;
5838
+ try {
5839
+ os3.setPriority(pid, os3.constants.priority.PRIORITY_BELOW_NORMAL);
5840
+ } catch {
5841
+ }
5842
+ }
5835
5843
  function spawnAsync(file2, args2, options = {}) {
5844
+ const { lowPriority, ...spawnOptions } = options;
5836
5845
  return new Promise((resolve, reject) => {
5837
- const child = spawn(file2, args2, { stdio: "ignore", windowsHide: true, ...options });
5846
+ const child = spawn(file2, args2, { stdio: "ignore", windowsHide: true, ...spawnOptions });
5847
+ if (lowPriority) lowerPriority(child.pid);
5838
5848
  child.once("error", reject);
5839
5849
  child.once("close", (code) => resolve(code ?? -1));
5840
5850
  });
@@ -5855,13 +5865,16 @@ function extractArchive(archive, destination, members2 = []) {
5855
5865
  }
5856
5866
  async function extract(archive, destination, members2) {
5857
5867
  fs7.mkdirSync(destination, { recursive: true });
5858
- let status = await spawnAsync(SYSTEM.tar(), ["-xf", archive, "-C", destination, ...members2]);
5868
+ let status = await spawnAsync(SYSTEM.tar(), ["-xf", archive, "-C", destination, ...members2], {
5869
+ lowPriority: true
5870
+ });
5859
5871
  if (status !== 0 && /\.(zip|nupkg)$/i.test(archive)) {
5860
5872
  const zip = archive.endsWith(".zip") ? archive : archive + ".zip";
5861
5873
  if (zip !== archive) fs7.copyFileSync(archive, zip);
5862
5874
  status = await spawnAsync(
5863
5875
  SYSTEM.powershell(),
5864
- scriptArgs("expand-archive.ps1", ["-Archive", zip, "-Destination", destination])
5876
+ scriptArgs("expand-archive.ps1", ["-Archive", zip, "-Destination", destination]),
5877
+ { lowPriority: true }
5865
5878
  );
5866
5879
  }
5867
5880
  if (status !== 0) throw new Error("Could not extract " + archive);
@@ -26494,6 +26507,7 @@ var GAMEDEV_APPS = [
26494
26507
  size: "100 MB",
26495
26508
  source: { github: "f3d-app/f3d", asset: "^F3D-[\\d.]+-Windows-x86_64\\.zip$" },
26496
26509
  exe: "f3d.exe",
26510
+ onPath: true,
26497
26511
  needs: ["mesa"]
26498
26512
  },
26499
26513
  {
@@ -27593,7 +27607,7 @@ var SERVICE_SPECS = {
27593
27607
  },
27594
27608
  steam: {
27595
27609
  kind: "steam",
27596
- label: "Steam (SteamCMD)",
27610
+ label: "Steam",
27597
27611
  usedBy: ["steam"],
27598
27612
  sources: ["file", "sandbox"],
27599
27613
  singleHolder: true,
@@ -27629,7 +27643,7 @@ var SERVICE_SPECS = {
27629
27643
  },
27630
27644
  cfx: {
27631
27645
  kind: "cfx",
27632
- label: "Cfx.re (FiveM / RedM server key)",
27646
+ label: "FiveM key",
27633
27647
  usedBy: ["fivem"],
27634
27648
  sources: ["typed", "file"],
27635
27649
  singleHolder: false,
@@ -27658,7 +27672,7 @@ var SERVICE_SPECS = {
27658
27672
  },
27659
27673
  keystore: {
27660
27674
  kind: "keystore",
27661
- label: "Android upload keystore",
27675
+ label: "Android keystore",
27662
27676
  usedBy: ["android"],
27663
27677
  sources: ["file"],
27664
27678
  singleHolder: false,
@@ -27673,7 +27687,7 @@ var SERVICE_SPECS = {
27673
27687
  },
27674
27688
  play: {
27675
27689
  kind: "play",
27676
- label: "Google Play (service account)",
27690
+ label: "Google Play",
27677
27691
  usedBy: ["android"],
27678
27692
  sources: ["file"],
27679
27693
  singleHolder: false,
@@ -27684,7 +27698,7 @@ var SERVICE_SPECS = {
27684
27698
  },
27685
27699
  expo: {
27686
27700
  kind: "expo",
27687
- label: "Expo (EAS)",
27701
+ label: "Expo",
27688
27702
  usedBy: ["reactNative"],
27689
27703
  sources: ["typed"],
27690
27704
  singleHolder: false,
@@ -27695,7 +27709,7 @@ var SERVICE_SPECS = {
27695
27709
  },
27696
27710
  appstore: {
27697
27711
  kind: "appstore",
27698
- label: "App Store Connect API key",
27712
+ label: "App Store Connect",
27699
27713
  usedBy: ["reactNative", "flutter"],
27700
27714
  sources: ["file", "typed"],
27701
27715
  singleHolder: false,
@@ -27709,7 +27723,7 @@ var SERVICE_SPECS = {
27709
27723
  },
27710
27724
  perplexity: {
27711
27725
  kind: "perplexity",
27712
- label: "Perplexity API key",
27726
+ label: "Perplexity API",
27713
27727
  usedBy: ["perplexity"],
27714
27728
  sources: ["pc", "file", "typed"],
27715
27729
  singleHolder: false,
@@ -27733,7 +27747,7 @@ var SERVICE_SPECS = {
27733
27747
  },
27734
27748
  comet: {
27735
27749
  kind: "comet",
27736
- label: "Comet (Perplexity browser)",
27750
+ label: "Comet",
27737
27751
  usedBy: ["comet"],
27738
27752
  sources: ["sandbox"],
27739
27753
  singleHolder: false,
@@ -27749,7 +27763,7 @@ var SERVICE_SPECS = {
27749
27763
  },
27750
27764
  perplexityapp: {
27751
27765
  kind: "perplexityapp",
27752
- label: "Perplexity desktop app",
27766
+ label: "Perplexity app",
27753
27767
  usedBy: ["perplexityapp"],
27754
27768
  sources: ["sandbox"],
27755
27769
  singleHolder: false,
@@ -28484,7 +28498,10 @@ function warn(message) {
28484
28498
  // src/guest/toolchains/signature.ts
28485
28499
  var SignatureSchema = external_exports.object({ subject: external_exports.string().max(4096), issuer: external_exports.string().max(4096) });
28486
28500
  function signatureOf(file2) {
28487
- const text = scriptOutput("get-signature.ps1", ["-Path", file2]).trim();
28501
+ return parseSignature(scriptOutput("get-signature.ps1", ["-Path", file2]));
28502
+ }
28503
+ function parseSignature(output2) {
28504
+ const text = output2.trim();
28488
28505
  if (!text) return null;
28489
28506
  try {
28490
28507
  const parsed = SignatureSchema.safeParse(JSON.parse(text));
@@ -29014,7 +29031,7 @@ function parse3(args2) {
29014
29031
  return { positionals, flags, dir: dir === void 0 ? void 0 : path20.resolve(dir) };
29015
29032
  }
29016
29033
  function addresses() {
29017
- return Object.values(os3.networkInterfaces()).flat().filter((a) => Boolean(a && a.family === "IPv4" && !a.internal)).map((a) => a.address);
29034
+ return Object.values(os4.networkInterfaces()).flat().filter((a) => Boolean(a && a.family === "IPv4" && !a.internal)).map((a) => a.address);
29018
29035
  }
29019
29036
  function mariadbClient() {
29020
29037
  const root = path20.join(toolsDir, "mariadb");
@@ -26697,6 +26697,7 @@ var GAMEDEV_APPS = [
26697
26697
  size: "100 MB",
26698
26698
  source: { github: "f3d-app/f3d", asset: "^F3D-[\\d.]+-Windows-x86_64\\.zip$" },
26699
26699
  exe: "f3d.exe",
26700
+ onPath: true,
26700
26701
  needs: ["mesa"]
26701
26702
  },
26702
26703
  {
@@ -27796,7 +27797,7 @@ var SERVICE_SPECS = {
27796
27797
  },
27797
27798
  steam: {
27798
27799
  kind: "steam",
27799
- label: "Steam (SteamCMD)",
27800
+ label: "Steam",
27800
27801
  usedBy: ["steam"],
27801
27802
  sources: ["file", "sandbox"],
27802
27803
  singleHolder: true,
@@ -27832,7 +27833,7 @@ var SERVICE_SPECS = {
27832
27833
  },
27833
27834
  cfx: {
27834
27835
  kind: "cfx",
27835
- label: "Cfx.re (FiveM / RedM server key)",
27836
+ label: "FiveM key",
27836
27837
  usedBy: ["fivem"],
27837
27838
  sources: ["typed", "file"],
27838
27839
  singleHolder: false,
@@ -27861,7 +27862,7 @@ var SERVICE_SPECS = {
27861
27862
  },
27862
27863
  keystore: {
27863
27864
  kind: "keystore",
27864
- label: "Android upload keystore",
27865
+ label: "Android keystore",
27865
27866
  usedBy: ["android"],
27866
27867
  sources: ["file"],
27867
27868
  singleHolder: false,
@@ -27876,7 +27877,7 @@ var SERVICE_SPECS = {
27876
27877
  },
27877
27878
  play: {
27878
27879
  kind: "play",
27879
- label: "Google Play (service account)",
27880
+ label: "Google Play",
27880
27881
  usedBy: ["android"],
27881
27882
  sources: ["file"],
27882
27883
  singleHolder: false,
@@ -27887,7 +27888,7 @@ var SERVICE_SPECS = {
27887
27888
  },
27888
27889
  expo: {
27889
27890
  kind: "expo",
27890
- label: "Expo (EAS)",
27891
+ label: "Expo",
27891
27892
  usedBy: ["reactNative"],
27892
27893
  sources: ["typed"],
27893
27894
  singleHolder: false,
@@ -27898,7 +27899,7 @@ var SERVICE_SPECS = {
27898
27899
  },
27899
27900
  appstore: {
27900
27901
  kind: "appstore",
27901
- label: "App Store Connect API key",
27902
+ label: "App Store Connect",
27902
27903
  usedBy: ["reactNative", "flutter"],
27903
27904
  sources: ["file", "typed"],
27904
27905
  singleHolder: false,
@@ -27912,7 +27913,7 @@ var SERVICE_SPECS = {
27912
27913
  },
27913
27914
  perplexity: {
27914
27915
  kind: "perplexity",
27915
- label: "Perplexity API key",
27916
+ label: "Perplexity API",
27916
27917
  usedBy: ["perplexity"],
27917
27918
  sources: ["pc", "file", "typed"],
27918
27919
  singleHolder: false,
@@ -27936,7 +27937,7 @@ var SERVICE_SPECS = {
27936
27937
  },
27937
27938
  comet: {
27938
27939
  kind: "comet",
27939
- label: "Comet (Perplexity browser)",
27940
+ label: "Comet",
27940
27941
  usedBy: ["comet"],
27941
27942
  sources: ["sandbox"],
27942
27943
  singleHolder: false,
@@ -27952,7 +27953,7 @@ var SERVICE_SPECS = {
27952
27953
  },
27953
27954
  perplexityapp: {
27954
27955
  kind: "perplexityapp",
27955
- label: "Perplexity desktop app",
27956
+ label: "Perplexity app",
27956
27957
  usedBy: ["perplexityapp"],
27957
27958
  sources: ["sandbox"],
27958
27959
  singleHolder: false,
@@ -28650,10 +28651,20 @@ import path7 from "node:path";
28650
28651
  import crypto from "node:crypto";
28651
28652
 
28652
28653
  // src/shared/download/spawn.ts
28654
+ import os3 from "node:os";
28653
28655
  import { spawn as spawn2 } from "node:child_process";
28656
+ function lowerPriority(pid) {
28657
+ if (pid === void 0) return;
28658
+ try {
28659
+ os3.setPriority(pid, os3.constants.priority.PRIORITY_BELOW_NORMAL);
28660
+ } catch {
28661
+ }
28662
+ }
28654
28663
  function spawnAsync(file2, args, options = {}) {
28664
+ const { lowPriority, ...spawnOptions } = options;
28655
28665
  return new Promise((resolve, reject) => {
28656
- const child = spawn2(file2, args, { stdio: "ignore", windowsHide: true, ...options });
28666
+ const child = spawn2(file2, args, { stdio: "ignore", windowsHide: true, ...spawnOptions });
28667
+ if (lowPriority) lowerPriority(child.pid);
28657
28668
  child.once("error", reject);
28658
28669
  child.once("close", (code) => resolve(code ?? -1));
28659
28670
  });
@@ -28674,13 +28685,16 @@ function extractArchive(archive, destination, members2 = []) {
28674
28685
  }
28675
28686
  async function extract(archive, destination, members2) {
28676
28687
  fs6.mkdirSync(destination, { recursive: true });
28677
- let status = await spawnAsync(SYSTEM.tar(), ["-xf", archive, "-C", destination, ...members2]);
28688
+ let status = await spawnAsync(SYSTEM.tar(), ["-xf", archive, "-C", destination, ...members2], {
28689
+ lowPriority: true
28690
+ });
28678
28691
  if (status !== 0 && /\.(zip|nupkg)$/i.test(archive)) {
28679
28692
  const zip = archive.endsWith(".zip") ? archive : archive + ".zip";
28680
28693
  if (zip !== archive) fs6.copyFileSync(archive, zip);
28681
28694
  status = await spawnAsync(
28682
28695
  SYSTEM.powershell(),
28683
- scriptArgs("expand-archive.ps1", ["-Archive", zip, "-Destination", destination])
28696
+ scriptArgs("expand-archive.ps1", ["-Archive", zip, "-Destination", destination]),
28697
+ { lowPriority: true }
28684
28698
  );
28685
28699
  }
28686
28700
  if (status !== 0) throw new Error("Could not extract " + archive);
@@ -7,13 +7,13 @@ import path from 'node:path';
7
7
  import { z } from 'zod';
8
8
  import { agentNameProblem, agentsPage, agentsView } from '../app/features/agents/index.js';
9
9
  import { DEFINITIONS, NAV, navSections, navViews, QUERIES, VIEWS, viewById } from '../app/features/defs.js';
10
- import { accountsPage, agentPage, listAccounts } from '../app/features/accounts/index.js';
10
+ import { accountsPage, agentPage, listAccounts, listServices, } from '../app/features/accounts/index.js';
11
11
  import { addProject, projectRoots, projectsPage } from '../app/features/projects/index.js';
12
12
  import { cleanData, dataPage, showData } from '../app/features/data/index.js';
13
13
  import { showStorage, storagePage, storageView, } from '../app/features/storage/index.js';
14
14
  import { insideSandbox, isSandboxRunning } from '../host/sandbox/index.js';
15
15
  import { CRASH, listLogs, logsPage, tailLog } from '../app/features/logs/index.js';
16
- import { DEFAULTS } from '../core/config/index.js';
16
+ import { DEFAULTS, loadConfig } from '../core/config/index.js';
17
17
  import { sandboxPage } from '../app/features/sandbox/index.js';
18
18
  import { closeSession, sessionsPage, startShell, } from '../app/features/sessions/index.js';
19
19
  import { APP_GROUPS, appsPage, listApps } from '../app/features/apps/index.js';
@@ -460,6 +460,24 @@ export const appSuite = {
460
460
  storageRoot: t.at('app-apps', 'storage'),
461
461
  dataPath: t.dir('app-apps', 'data'),
462
462
  };
463
+ t.section('a page read after an action shows what the action saved');
464
+ const freshConfig = t.writeJson(t.at('app-fresh', 'config.json'), {
465
+ storage: { root: t.dir('app-fresh', 'storage') },
466
+ });
467
+ const freshCtx = {
468
+ loaded: loadConfig(freshConfig),
469
+ options: {},
470
+ storageRoot: t.at('app-fresh', 'storage'),
471
+ dataPath: t.dir('app-fresh', 'data'),
472
+ };
473
+ const shareOf = async () => (await readQuery(listServices, freshCtx, { name: 'comet' })).services[0]?.share;
474
+ t.check((await shareOf()) === false, 'a service starts not shared');
475
+ await runDefinition('settings.set', freshCtx, { scope: 'user', key: 'accounts.comet.share', value: true }, quiet);
476
+ t.check((await shareOf()) === true, 'after the share toggle runs, the service page reads it as shared');
477
+ await runDefinition('settings.set', freshCtx, { scope: 'user', key: 'accounts.comet.share', value: false }, quiet);
478
+ t.check((await shareOf()) === false, 'and as not shared again after the next toggle');
479
+ await runDefinition('apps.toggle', freshCtx, { id: 'perplexity-desktop', on: true }, quiet);
480
+ t.check(freshCtx.loaded.config.apps.install.includes('perplexity-desktop'), 'an app turned on in Apps is in the settings every page reads next');
463
481
  await runDefinition('apps.toggle', appsCtx, { id: 'lazygit', on: true }, quiet);
464
482
  const onText = fs.readFileSync(appsConfig, 'utf8');
465
483
  await runDefinition('apps.toggle', appsCtx, { id: 'lazygit', on: false }, quiet);
@@ -4,7 +4,9 @@ import { BootRunner, checkGraph, duration } from '../guest/boot/runner.js';
4
4
  import { BOOT_TASKS, STAGES, stageTitle } from '../guest/boot/graph.js';
5
5
  import { backgroundNames, mergeParts, runSplit } from '../guest/toolchains/split.js';
6
6
  import { StepScheduler } from '../guest/toolchains/step-scheduler.js';
7
- import { Limiter, asBackground, installerRetry, INSTALLER_RETRIES } from '../shared/concurrency/index.js';
7
+ import os from 'node:os';
8
+ import { Limiter, asBackground, installerRetry, INSTALLER_RETRIES, net, heavy, installer, } from '../shared/concurrency/index.js';
9
+ import { spawnAsync } from '../shared/download/index.js';
8
10
  const deferred = () => {
9
11
  let resolve = () => undefined;
10
12
  let reject = (_) => undefined;
@@ -288,6 +290,18 @@ export const bootRunnerSuite = {
288
290
  t.check(message.includes('nested pool') && again.includes('nested pool'), 'taking a second pool (or the same one) while holding one throws "nested pool"', message);
289
291
  t.check((await a.run(() => 7)) === 7 && a.running === 0, 'the slot is free again after a nested-pool error');
290
292
  }
293
+ {
294
+ // At most 3 downloads + 2 unpacks + 1 installer run at once, whatever the boot tasks, steps and apps ask for.
295
+ const busy = async (pool) => {
296
+ await Promise.all(Array.from({ length: 12 }, () => pool.run(() => sleep(5))));
297
+ return pool.peak;
298
+ };
299
+ t.check((await busy(net)) <= 3 && (await busy(heavy)) <= 2 && (await busy(installer)) <= 1, 'the shared pools cap at net 3, heavy 2 and installer 1', 'net ' + net.peak + ', heavy ' + heavy.peak + ', installer ' + installer.peak);
300
+ const script = 'setTimeout(() => process.exit(require("os").getPriority() > 0 ? 0 : 3), 400)';
301
+ const low = await spawnAsync(process.execPath, ['-e', script], { lowPriority: true });
302
+ const normal = await spawnAsync(process.execPath, ['-e', script]);
303
+ t.check(low === 0 && normal === 3 && os.getPriority() <= 0, 'lowPriority starts a program below normal priority; the default stays normal', 'low ' + low + ', normal ' + normal);
304
+ }
291
305
  t.check(installerRetry(1618, 1) &&
292
306
  installerRetry(1618, INSTALLER_RETRIES) &&
293
307
  !installerRetry(1618, INSTALLER_RETRIES + 1) &&
@@ -12,7 +12,7 @@ import { isWin } from '../core/util/index.js';
12
12
  import { ConfigSchema } from '../core/schema/index.js';
13
13
  import { decodeText, encodeText, readBoundedFile } from '../shared/io/index.js';
14
14
  import { SYSTEM } from '../shared/system.js';
15
- import { chooseEngine, ddcVars, enginePlaceProblem, ENGINE_NEED_BYTES, engineAt, engineExtraDirs, engineToolDirs, engineVars, entriesUnder, fileAssociationPlan, findEngines, findUproject, hasMcpPlugin, incompleteDirs, isBuildId, installLocationOf, itemSizes, launcherEntriesFor, launcherEntry, launcherInstallations, mergeLauncherInstalled, parseBuildVersion, parseEngineAssociation, preferredInstalled, registryPlan, restoreItems, saveItems, setLauncherInstallLocation, stableBuildId, UNREAL_STATUS_FILE, unrealGuideLines, unrealSummaryLine, uprojectAssociation, vaultDirsFromIni, vaultPacks, vsVerdict, VsSetupReportSchema, withInstallLocation, } from '../shared/unreal/index.js';
15
+ import { chooseEngine, ddcVars, enginePlaceProblem, ENGINE_NEED_BYTES, engineAt, engineExtraDirs, engineToolDirs, engineVars, entriesUnder, fileAssociationPlan, findEngines, findUproject, hasMcpPlugin, incompleteDirs, isBuildId, installLocationOf, itemSizes, launcherEntriesFor, launcherEntry, launcherInstallations, mergeLauncherInstalled, parseBuildVersion, parseEngineAssociation, preferredInstalled, registryPlan, relocateItem, restoreItems, saveItems, setLauncherInstallLocation, stableBuildId, UNREAL_STATUS_FILE, unrealGuideLines, unrealSummaryLine, uprojectAssociation, vaultDirsFromIni, vaultPacks, vsVerdict, VsSetupReportSchema, withInstallLocation, } from '../shared/unreal/index.js';
16
16
  import { discoverPcInstalls } from '../host/pc-installs/index.js';
17
17
  import { findEngineFolders, findUnrealInstalls, pcSourceBuilds, regValues } from '../host/pc-installs/unreal.js';
18
18
  import { findEpicGames } from '../host/pc-installs/epic.js';
@@ -436,6 +436,25 @@ export const enginesSuite = {
436
436
  'relative',
437
437
  ]);
438
438
  t.equal(folders.map((f) => [f.id, f.writable]), [['dir-UnrealEngine', true]], 'a registered folder is mapped writable; one without an engine is left out');
439
+ const pcManifests = t.dir('pc-pcManifests');
440
+ const itemText = JSON.stringify({
441
+ AppName: 'UE_5.4',
442
+ InstallLocation: pcLauncher.replace(/\\/g, '/'),
443
+ ManifestLocation: pcLauncher + '/.egstore',
444
+ StagingLocation: 'D:\\Elsewhere\\bps',
445
+ InstallSize: 123,
446
+ });
447
+ fs.writeFileSync(path.join(pcManifests, 'ABCDEF0123.item'), itemText);
448
+ fs.writeFileSync(path.join(pcManifests, 'notes.txt'), 'not a manifest');
449
+ const pcRegistered = findEngineFolders({ read: DISK.read, builds: '', launcherInstalled: dat, manifests: pcManifests, list: DISK.list }, [pcLauncher])[0];
450
+ t.check(pcRegistered?.launcher?.itemId === 'item54' && pcRegistered.launcherItem?.name === 'ABCDEF0123.item', 'a pcRegistered folder the launcher installed keeps its ids and its manifest');
451
+ const moved = relocateItem(itemText, pcLauncher, 'C:\\PcInstalls\\unreal\\dir-UE_5.4');
452
+ const movedItem = JSON.parse(moved ?? '{}');
453
+ t.check(movedItem.InstallLocation === 'C:\\PcInstalls\\unreal\\dir-UE_5.4' &&
454
+ movedItem.ManifestLocation === 'C:\\PcInstalls\\unreal\\dir-UE_5.4\\.egstore' &&
455
+ !('StagingLocation' in movedItem) &&
456
+ movedItem.InstallSize === 123, "the manifest names the sandbox's folder; a path elsewhere on this PC is dropped");
457
+ t.equal(relocateItem(itemText, path.join(pcRoot, 'other'), 'C:\\x'), null, 'a manifest of another folder: none');
439
458
  t.equal(findEpicGames(dat, true).map((g) => g.id), ['Fortnite'], "the launcher's games are the epic-game installs (engines and the Fab plugin in one are not)");
440
459
  t.equal(findEpicGames(dat, ['nothing']), [], 'a list maps only those games');
441
460
  t.check(engineFolderProblem(pcSource, DISK.read) === null, 'Register an engine folder accepts an engine folder');
@@ -470,6 +489,8 @@ export const enginesSuite = {
470
489
  t.check(new Set(ids).size === ids.length, 'every app id is used once');
471
490
  t.equal(GAMEDEV_APPS.map((a) => a.id), ['krita', 'inkscape', 'audacity', 'renderdoc', 'f3d', 'imagemagick', 'nomacs', 'unity-mcp', 'openupm-cli'], 'Krita, Inkscape, Audacity, RenderDoc, F3D, ImageMagick, nomacs, then the Unity add-ons (Blender was there)');
472
491
  t.check(ids.includes('blender'), 'Blender is in the catalogue');
492
+ const f3d = GAMEDEV_APPS.find((a) => a.id === 'f3d');
493
+ t.check(f3d.onPath === true && !f3d.cli, 'F3D is on PATH (the preview guide runs `f3d`) and keeps its shortcut');
473
494
  t.check(GAMEDEV_APPS.every((a) => (a.category === 'gamedev' || a.id === 'unity-mcp') && a.exe && a.about.length > 20), 'each is a game-development app with its program');
474
495
  const audacity = new RegExp(GAMEDEV_APPS.find((a) => a.id === 'audacity').source.asset);
475
496
  t.check(audacity.test('audacity-win-4.0.2-x86_64.7z') &&
@@ -19,6 +19,7 @@ import { SECTION_LABELS } from '../cli/menu/shell.js';
19
19
  import { Frame, ListScreen, OverlayScreen, Text } from '../cli/tui/index.js';
20
20
  export const SIZES = [
21
21
  { width: 200, height: 60 },
22
+ { width: 120, height: 30 },
22
23
  { width: 100, height: 30 },
23
24
  { width: 80, height: 24 },
24
25
  { width: 60, height: 20 },
@@ -38,6 +39,48 @@ const DATA_FOLDER_ROW = [
38
39
  const ACCOUNTS = {
39
40
  agent: 'claude',
40
41
  services: [
42
+ {
43
+ kind: 'epic',
44
+ label: SERVICE_SPECS.epic.label,
45
+ share: true,
46
+ state: 'none',
47
+ source: null,
48
+ savedAt: null,
49
+ summary: null,
50
+ heldBy: null,
51
+ usedBy: ['unreal'],
52
+ usedFor: 'unreal',
53
+ warnings: [...SERVICE_SPECS.epic.warnings],
54
+ canImport: true,
55
+ canLogin: false,
56
+ needsFile: false,
57
+ fromPc: true,
58
+ hasFileSource: false,
59
+ onPc: true,
60
+ sandboxOnly: false,
61
+ ready: true,
62
+ },
63
+ {
64
+ kind: 'keystore',
65
+ label: SERVICE_SPECS.keystore.label,
66
+ share: false,
67
+ state: 'none',
68
+ source: null,
69
+ savedAt: null,
70
+ summary: null,
71
+ heldBy: null,
72
+ usedBy: ['flutter'],
73
+ usedFor: 'flutter',
74
+ warnings: [...SERVICE_SPECS.keystore.warnings],
75
+ canImport: true,
76
+ canLogin: false,
77
+ needsFile: true,
78
+ fromPc: false,
79
+ hasFileSource: true,
80
+ onPc: null,
81
+ sandboxOnly: false,
82
+ ready: false,
83
+ },
41
84
  {
42
85
  kind: 'perplexity',
43
86
  label: SERVICE_SPECS.perplexity.label,
@@ -53,6 +96,10 @@ const ACCOUNTS = {
53
96
  canImport: true,
54
97
  canLogin: true,
55
98
  needsFile: false,
99
+ fromPc: true,
100
+ hasFileSource: true,
101
+ onPc: true,
102
+ sandboxOnly: false,
56
103
  ready: true,
57
104
  },
58
105
  {
@@ -70,6 +117,10 @@ const ACCOUNTS = {
70
117
  canImport: false,
71
118
  canLogin: false,
72
119
  needsFile: false,
120
+ fromPc: false,
121
+ hasFileSource: false,
122
+ onPc: null,
123
+ sandboxOnly: true,
73
124
  ready: true,
74
125
  },
75
126
  ],
@@ -423,7 +474,7 @@ export function renderScreens(dir) {
423
474
  ],
424
475
  escape: 'cancel',
425
476
  });
426
- out.push(block('5 Settings: Save / Discard / Cancel', Frame.render({ ...dialog.render(bodySize), nav }, size.width, size.height).map((l) => Text.strip(l).replace(/\s+$/, '')), size), block('6 Accounts', draw(5, accountsOptions(ACCOUNTS), size, status), size), block('6 Accounts · codex', draw(5, { ...accountsOptions(ACCOUNTS), startCategory: 'codex' }, size, status), size), block('6 Accounts · opencode (login not kept)', draw(5, { ...accountsOptions(ACCOUNTS), startCategory: 'opencode' }, size, status), size));
477
+ out.push(block('5 Settings: Save / Discard / Cancel', Frame.render({ ...dialog.render(bodySize), nav }, size.width, size.height).map((l) => Text.strip(l).replace(/\s+$/, '')), size), block('6 Accounts', draw(5, accountsOptions(ACCOUNTS), size, status), size), ...['epic', 'perplexity', 'comet'].map((kind) => block('6 Accounts · ' + kind, draw(5, { ...accountsOptions(ACCOUNTS), startCategory: 'service:' + kind }, size, status), size)), block('6 Accounts · codex', draw(5, { ...accountsOptions(ACCOUNTS), startCategory: 'codex' }, size, status), size), block('6 Accounts · opencode (login not kept)', draw(5, { ...accountsOptions(ACCOUNTS), startCategory: 'opencode' }, size, status), size));
427
478
  for (const category of ['updates', 'data', 'health', 'logs', 'more'])
428
479
  out.push(block('7 Maintenance · ' + category, draw(6, { ...maintenanceOptions(maintenance), startCategory: category }, size, status), size));
429
480
  }