windows-development-sandbox 1.25.0 → 1.27.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 (287) hide show
  1. package/config.example.json +17 -5
  2. package/dist/app/features/accounts/services.def.d.ts +1 -1
  3. package/dist/app/features/accounts/services.def.js +1 -1
  4. package/dist/app/features/data/data.def.d.ts +4 -4
  5. package/dist/app/features/data/data.run.d.ts +1 -1
  6. package/dist/app/features/defs.d.ts +356 -6
  7. package/dist/app/features/defs.js +15 -0
  8. package/dist/app/features/projects/projects.def.d.ts +1 -1
  9. package/dist/app/features/projects/projects.run.d.ts +1 -1
  10. package/dist/app/features/settings/categories.def.js +1 -0
  11. package/dist/app/features/unity/index.d.ts +2 -0
  12. package/dist/app/features/unity/index.js +3 -0
  13. package/dist/app/features/unity/unity.def.d.ts +294 -0
  14. package/dist/app/features/unity/unity.def.js +471 -0
  15. package/dist/app/features/unity/unity.run.d.ts +142 -0
  16. package/dist/app/features/unity/unity.run.js +363 -0
  17. package/dist/app/features/unreal/index.d.ts +2 -0
  18. package/dist/app/features/unreal/index.js +3 -0
  19. package/dist/app/features/unreal/unreal.def.d.ts +236 -0
  20. package/dist/app/features/unreal/unreal.def.js +379 -0
  21. package/dist/app/features/unreal/unreal.run.d.ts +96 -0
  22. package/dist/app/features/unreal/unreal.run.js +217 -0
  23. package/dist/app/registry.js +11 -0
  24. package/dist/bin/agent-sandbox.js +2 -2
  25. package/dist/bin/{chunk-IZRGTSQ5.js → chunk-6WWMBCT2.js} +4632 -1941
  26. package/dist/bin/{chunk-SUYYVIDW.js → chunk-JXL6ZOO3.js} +126 -6
  27. package/dist/bin/{chunk-KH4MI2U5.js → chunk-ZXCAVVS4.js} +1 -1
  28. package/dist/bin/claude-host.js +2 -2
  29. package/dist/bin/claude-sbx.js +2 -2
  30. package/dist/bin/codex-sbx.js +2 -2
  31. package/dist/bin/gemini-sbx.js +2 -2
  32. package/dist/bin/sandbox.js +2 -2
  33. package/dist/bin/sbx-hide.js +2 -2
  34. package/dist/bin/sbx-show.js +2 -2
  35. package/dist/cli/commands/launch/launch.js +17 -2
  36. package/dist/cli/commands/registry.js +6 -0
  37. package/dist/cli/manifest.d.ts +51 -1
  38. package/dist/cli/manifest.js +58 -1
  39. package/dist/cli/menu/coverage-exempt.js +2 -0
  40. package/dist/cli/menu/pages/sandbox-page.d.ts +6 -0
  41. package/dist/cli/menu/pages/sandbox-page.js +71 -4
  42. package/dist/cli/menu/views.js +4 -0
  43. package/dist/core/config/defaults.js +33 -6
  44. package/dist/core/schema/accounts.d.ts +3 -0
  45. package/dist/core/schema/accounts.js +1 -0
  46. package/dist/core/schema/config.d.ts +32 -14
  47. package/dist/core/schema/github.d.ts +1 -0
  48. package/dist/core/schema/github.js +3 -0
  49. package/dist/core/schema/pc-installs.d.ts +5 -2
  50. package/dist/core/schema/pc-installs.js +16 -2
  51. package/dist/core/schema/toolchains.d.ts +8 -0
  52. package/dist/core/schema/toolchains.js +20 -5
  53. package/dist/guest/accounts/adapters.js +2 -0
  54. package/dist/guest/accounts/apply.d.ts +4 -2
  55. package/dist/guest/accounts/kinds/epic.js +18 -13
  56. package/dist/guest/accounts/kinds/perplexityapp.d.ts +7 -0
  57. package/dist/guest/accounts/kinds/perplexityapp.js +53 -0
  58. package/dist/guest/accounts/kinds/unity.d.ts +27 -0
  59. package/dist/guest/accounts/kinds/unity.js +164 -7
  60. package/dist/guest/apps/after-install.d.ts +1 -1
  61. package/dist/guest/apps/after-install.js +6 -1
  62. package/dist/guest/apps/app-installer.d.ts +1 -0
  63. package/dist/guest/apps/app-installer.js +31 -3
  64. package/dist/guest/apps/comet-unpack.js +3 -2
  65. package/dist/guest/apps/resolvers-gamedev.js +55 -1
  66. package/dist/guest/apps/store-app.d.ts +4 -0
  67. package/dist/guest/apps/store-app.js +19 -0
  68. package/dist/guest/apps/store-check.d.ts +8 -0
  69. package/dist/guest/apps/store-check.js +33 -0
  70. package/dist/guest/boot/boot.js +15 -2
  71. package/dist/guest/boot/guide.js +42 -0
  72. package/dist/guest/browser/comet.d.ts +3 -1
  73. package/dist/guest/browser/comet.js +1 -1
  74. package/dist/guest/browser/perplexity-app.d.ts +12 -0
  75. package/dist/guest/browser/perplexity-app.js +60 -0
  76. package/dist/guest/daemon/daemon.js +5 -0
  77. package/dist/guest/devtools/devtool.d.ts +1 -1
  78. package/dist/guest/devtools/registry.js +1 -2
  79. package/dist/guest/entries/tool.js +1 -1
  80. package/dist/guest/setup/addon-mcp.d.ts +12 -0
  81. package/dist/guest/setup/addon-mcp.js +29 -4
  82. package/dist/guest/setup/browser-gpu.js +14 -11
  83. package/dist/guest/setup/github-token.js +4 -4
  84. package/dist/guest/setup/headroom.js +4 -0
  85. package/dist/guest/setup/unity-agent.d.ts +11 -0
  86. package/dist/guest/setup/unity-agent.js +36 -0
  87. package/dist/guest/toolchains/engines/epic-launcher.d.ts +26 -0
  88. package/dist/guest/toolchains/engines/epic-launcher.js +127 -0
  89. package/dist/guest/toolchains/engines/unity-cli.d.ts +24 -0
  90. package/dist/guest/toolchains/engines/unity-cli.js +74 -0
  91. package/dist/guest/toolchains/engines/unity-hub.d.ts +2 -0
  92. package/dist/guest/toolchains/engines/unity-hub.js +27 -0
  93. package/dist/guest/toolchains/engines/unity-release.d.ts +4 -15
  94. package/dist/guest/toolchains/engines/unity-release.js +17 -5
  95. package/dist/guest/toolchains/engines/unity-status.d.ts +11 -0
  96. package/dist/guest/toolchains/engines/unity-status.js +39 -0
  97. package/dist/guest/toolchains/engines/unity.d.ts +20 -1
  98. package/dist/guest/toolchains/engines/unity.js +250 -3
  99. package/dist/guest/toolchains/engines/unreal-engines.d.ts +21 -1
  100. package/dist/guest/toolchains/engines/unreal-engines.js +163 -3
  101. package/dist/guest/toolchains/engines/unreal-status.d.ts +13 -0
  102. package/dist/guest/toolchains/engines/unreal-status.js +41 -0
  103. package/dist/guest/toolchains/native/debuggers.d.ts +6 -0
  104. package/dist/guest/toolchains/native/debuggers.js +57 -0
  105. package/dist/guest/toolchains/native/mesa.js +15 -8
  106. package/dist/guest/toolchains/native/rust-extras.d.ts +7 -0
  107. package/dist/guest/toolchains/native/rust-extras.js +23 -1
  108. package/dist/guest/toolchains/native/unreal.d.ts +2 -2
  109. package/dist/guest/toolchains/native/unreal.js +96 -18
  110. package/dist/guest/toolchains/plan.js +17 -2
  111. package/dist/guest/toolchains/tools/cli.js +2 -2
  112. package/dist/guest/toolchains/tools/vscode-profile.js +2 -0
  113. package/dist/host/accounts/deliver.js +4 -0
  114. package/dist/host/accounts/kinds/epic.js +11 -5
  115. package/dist/host/accounts/kinds/perplexityapp.d.ts +2 -0
  116. package/dist/host/accounts/kinds/perplexityapp.js +8 -0
  117. package/dist/host/accounts/kinds/unity.d.ts +1 -1
  118. package/dist/host/accounts/kinds/unity.js +60 -8
  119. package/dist/host/accounts/leases.d.ts +2 -1
  120. package/dist/host/accounts/source.d.ts +2 -0
  121. package/dist/host/accounts/source.js +1 -0
  122. package/dist/host/accounts/sources.js +2 -0
  123. package/dist/host/apps/index.d.ts +1 -0
  124. package/dist/host/apps/index.js +2 -0
  125. package/dist/host/apps/store-package.d.ts +51 -0
  126. package/dist/host/apps/store-package.js +209 -0
  127. package/dist/host/github/deliver.d.ts +8 -3
  128. package/dist/host/github/deliver.js +42 -29
  129. package/dist/host/github/index.d.ts +1 -0
  130. package/dist/host/github/index.js +1 -0
  131. package/dist/host/github/pc-login.d.ts +27 -0
  132. package/dist/host/github/pc-login.js +129 -0
  133. package/dist/host/pc-installs/discover.d.ts +6 -0
  134. package/dist/host/pc-installs/discover.js +37 -5
  135. package/dist/host/pc-installs/epic.d.ts +3 -0
  136. package/dist/host/pc-installs/epic.js +33 -2
  137. package/dist/host/pc-installs/source.d.ts +1 -1
  138. package/dist/host/pc-installs/unity-assets.d.ts +5 -0
  139. package/dist/host/pc-installs/unity-assets.js +18 -0
  140. package/dist/host/pc-installs/unity.d.ts +20 -0
  141. package/dist/host/pc-installs/unity.js +135 -2
  142. package/dist/host/pc-installs/unreal-vault.d.ts +11 -0
  143. package/dist/host/pc-installs/unreal-vault.js +78 -0
  144. package/dist/host/pc-installs/unreal.d.ts +36 -0
  145. package/dist/host/pc-installs/unreal.js +145 -2
  146. package/dist/host/profile/claude-profile.d.ts +8 -0
  147. package/dist/host/profile/claude-profile.js +43 -4
  148. package/dist/host/profile/index.d.ts +1 -1
  149. package/dist/host/profile/index.js +1 -1
  150. package/dist/runtime/agent.js +197 -18
  151. package/dist/runtime/daemon.js +3938 -1386
  152. package/dist/runtime/db.js +197 -18
  153. package/dist/runtime/main.js +4336 -1978
  154. package/dist/runtime/netproxy.js +197 -18
  155. package/dist/runtime/tool.js +214 -38
  156. package/dist/runtime/view.js +197 -18
  157. package/dist/selftest/agent-paths.test.js +1 -0
  158. package/dist/selftest/agents-web.test.js +14 -1
  159. package/dist/selftest/engines.test.js +602 -3
  160. package/dist/selftest/github.test.js +64 -1
  161. package/dist/selftest/heavy-load.test.js +3 -1
  162. package/dist/selftest/hostile-input.test.js +11 -1
  163. package/dist/selftest/index.js +12 -0
  164. package/dist/selftest/menu-screens.js +5 -1
  165. package/dist/selftest/mixamo.test.d.ts +2 -0
  166. package/dist/selftest/mixamo.test.js +60 -0
  167. package/dist/selftest/persisted-installs.test.js +1 -1
  168. package/dist/selftest/secrets.test.js +36 -1
  169. package/dist/selftest/service-accounts.test.js +1 -1
  170. package/dist/selftest/store-app.test.d.ts +2 -0
  171. package/dist/selftest/store-app.test.js +162 -0
  172. package/dist/selftest/support/engine-golden.js +10 -0
  173. package/dist/selftest/support/fixtures.d.ts +13 -2
  174. package/dist/selftest/support/unity-sample.d.ts +2 -0
  175. package/dist/selftest/support/unity-sample.js +80 -0
  176. package/dist/selftest/support/unreal-sample.d.ts +2 -0
  177. package/dist/selftest/support/unreal-sample.js +83 -0
  178. package/dist/selftest/toolchains.test.js +32 -2
  179. package/dist/selftest/unity-accounts.test.d.ts +2 -0
  180. package/dist/selftest/unity-accounts.test.js +106 -0
  181. package/dist/selftest/unity-agent.test.d.ts +2 -0
  182. package/dist/selftest/unity-agent.test.js +47 -0
  183. package/dist/selftest/unity-page.test.d.ts +2 -0
  184. package/dist/selftest/unity-page.test.js +177 -0
  185. package/dist/selftest/unity-setup.test.d.ts +2 -0
  186. package/dist/selftest/unity-setup.test.js +49 -0
  187. package/dist/selftest/unity.test.js +238 -3
  188. package/dist/shared/accounts/index.d.ts +2 -1
  189. package/dist/shared/accounts/index.js +2 -1
  190. package/dist/shared/accounts/kinds/epic.d.ts +6 -1
  191. package/dist/shared/accounts/kinds/epic.js +11 -1
  192. package/dist/shared/accounts/kinds/unity.d.ts +28 -0
  193. package/dist/shared/accounts/kinds/unity.js +88 -0
  194. package/dist/shared/accounts/kinds.d.ts +2 -2
  195. package/dist/shared/accounts/kinds.js +23 -1
  196. package/dist/shared/accounts/payloads.d.ts +52 -0
  197. package/dist/shared/accounts/payloads.js +14 -2
  198. package/dist/shared/apps/associations.d.ts +7 -0
  199. package/dist/shared/apps/associations.js +26 -0
  200. package/dist/shared/apps/catalogue-agents-web.d.ts +11 -1
  201. package/dist/shared/apps/catalogue-agents-web.js +28 -2
  202. package/dist/shared/apps/catalogue-gamedev.d.ts +3 -0
  203. package/dist/shared/apps/catalogue-gamedev.js +102 -1
  204. package/dist/shared/apps/catalogue.d.ts +8 -0
  205. package/dist/shared/apps/index.d.ts +5 -2
  206. package/dist/shared/apps/index.js +5 -2
  207. package/dist/shared/apps/preview.d.ts +2 -0
  208. package/dist/shared/apps/preview.js +10 -0
  209. package/dist/shared/apps/selection.d.ts +6 -0
  210. package/dist/shared/apps/selection.js +9 -0
  211. package/dist/shared/apps/store-package.d.ts +31 -0
  212. package/dist/shared/apps/store-package.js +34 -0
  213. package/dist/shared/browser-policy.d.ts +14 -0
  214. package/dist/shared/browser-policy.js +22 -0
  215. package/dist/shared/channel/schema.d.ts +24 -0
  216. package/dist/shared/channel/secrets.d.ts +17 -0
  217. package/dist/shared/io/index.d.ts +1 -0
  218. package/dist/shared/io/index.js +1 -0
  219. package/dist/shared/io/rotate-log.d.ts +2 -0
  220. package/dist/shared/io/rotate-log.js +16 -0
  221. package/dist/shared/memory/policy.d.ts +5 -0
  222. package/dist/shared/memory/policy.js +7 -0
  223. package/dist/shared/paths/pc-installs.d.ts +13 -2
  224. package/dist/shared/paths/pc-installs.js +2 -0
  225. package/dist/shared/readiness.d.ts +2 -3
  226. package/dist/shared/readiness.js +2 -3
  227. package/dist/shared/signer.d.ts +4 -0
  228. package/dist/shared/signer.js +2 -0
  229. package/dist/shared/unity/asset-cache.d.ts +20 -0
  230. package/dist/shared/unity/asset-cache.js +44 -0
  231. package/dist/shared/unity/cli.d.ts +40 -0
  232. package/dist/shared/unity/cli.js +77 -0
  233. package/dist/shared/unity/editors.d.ts +46 -0
  234. package/dist/shared/unity/editors.js +89 -0
  235. package/dist/shared/unity/guide.d.ts +9 -0
  236. package/dist/shared/unity/guide.js +84 -0
  237. package/dist/shared/unity/index.d.ts +9 -0
  238. package/dist/shared/unity/index.js +11 -0
  239. package/dist/shared/unity/manifest.d.ts +10 -0
  240. package/dist/shared/unity/manifest.js +53 -0
  241. package/dist/shared/unity/mcp.d.ts +8 -0
  242. package/dist/shared/unity/mcp.js +12 -0
  243. package/dist/shared/unity/release.d.ts +28 -0
  244. package/dist/shared/unity/release.js +83 -0
  245. package/dist/shared/unity/status.d.ts +59 -0
  246. package/dist/shared/unity/status.js +45 -0
  247. package/dist/shared/unity/version.d.ts +27 -0
  248. package/dist/shared/unity/version.js +75 -0
  249. package/dist/shared/unreal/engines.d.ts +161 -0
  250. package/dist/shared/unreal/engines.js +308 -0
  251. package/dist/shared/unreal/guide.d.ts +19 -0
  252. package/dist/shared/unreal/guide.js +105 -0
  253. package/dist/shared/unreal/index.d.ts +7 -0
  254. package/dist/shared/unreal/index.js +8 -0
  255. package/dist/shared/unreal/launcher-files.d.ts +88 -0
  256. package/dist/shared/unreal/launcher-files.js +292 -0
  257. package/dist/shared/unreal/mixamo.d.ts +5 -0
  258. package/dist/shared/unreal/mixamo.js +16 -0
  259. package/dist/shared/unreal/room.d.ts +17 -0
  260. package/dist/shared/unreal/room.js +34 -0
  261. package/dist/shared/unreal/status.d.ts +54 -0
  262. package/dist/shared/unreal/status.js +57 -0
  263. package/dist/shared/unreal/vault.d.ts +21 -0
  264. package/dist/shared/unreal/vault.js +67 -0
  265. package/dist/shared/unreal/vs-check.d.ts +39 -0
  266. package/dist/shared/unreal/vs-check.js +79 -0
  267. package/dist/web/index.html +1 -1
  268. package/dist/web/{main-CCILLX2M.js → main-VPMYTSUL.js} +87 -87
  269. package/native/csharp/HcsHost/AssemblyInfo.cs +3 -3
  270. package/native/csharp/HostTools/AssemblyInfo.cs +3 -3
  271. package/native/csharp/StorageHelper/AssemblyInfo.cs +3 -3
  272. package/native/scripts/get-store-package.ps1 +58 -0
  273. package/package.json +1 -1
  274. package/sandbox/addons/openupm-cli/package-lock.json +2921 -0
  275. package/sandbox/addons/openupm-cli/package.json +7 -0
  276. package/sandbox/addons/unity-mcp/requirements.in +1 -0
  277. package/sandbox/addons/unity-mcp/requirements.txt +1165 -0
  278. package/sandbox/csharp/AppActivation.cs +38 -0
  279. package/sandbox/csharp/VsSetup.cs +210 -0
  280. package/sandbox/scripts/get-vs-setup.ps1 +93 -0
  281. package/sandbox/scripts/install-store-package.ps1 +28 -0
  282. package/sandbox/scripts/set-registry-values.ps1 +35 -0
  283. package/sandbox/scripts/start-store-app.ps1 +29 -0
  284. package/schemas/config.schema.json +96 -8
  285. package/schemas/sandbox-config.schema.json +46 -6
  286. package/dist/guest/devtools/unity.d.ts +0 -2
  287. package/dist/guest/devtools/unity.js +0 -7
@@ -1,7 +1,164 @@
1
- import { notAvailableYet } from '../adapter.js';
2
- export const unityAdapter = {
3
- kind: 'unity',
4
- apply(_p) {
5
- return notAvailableYet('unity');
6
- },
7
- };
1
+ // Unity: the licence file (Unity_lic.ulf) goes to %ProgramData%\Unity and the named-user licence to
2
+ // %LOCALAPPDATA%\Unity\licenses, where the editor reads them. The Unity ID (and a Plus or Pro serial) are kept in
3
+ // secretDir('unity')\activation.json, on the sandbox's own disk, for the editor to sign in with. A serial seat is
4
+ // returned when the sandbox stops. Nothing here puts a password, serial or licence text in a log or a summary.
5
+ import { execFile } from 'node:child_process';
6
+ import fs from 'node:fs';
7
+ import path from 'node:path';
8
+ import { z } from 'zod';
9
+ import { MAX_ULF_BYTES, parseLicenceStatus, unityEntitlementFile, unityUlfFile, } from '../../../shared/accounts/index.js';
10
+ import { decodeText, readBoundedFile } from '../../../shared/io/index.js';
11
+ import { licenceStatusArgs, UNITY_CLI_ENV } from '../../../shared/unity/cli.js';
12
+ import { localAppData, secretDir } from '../adapter.js';
13
+ const MAX_JSON_BYTES = 16 * 1024;
14
+ const RETURN_MS = 120_000;
15
+ const ACTIVATE_MS = 180_000;
16
+ const STATUS_MS = 60_000;
17
+ const ActivationSchema = z.object({
18
+ email: z.string().max(256).optional(),
19
+ password: z.string().max(1024).optional(),
20
+ serial: z.string().max(64).optional(),
21
+ });
22
+ const ActivatedSchema = z.object({ editorExe: z.string().min(1).max(520), serial: z.literal(true) });
23
+ export const runUnityProgram = (file, args, o) => new Promise((resolve) => {
24
+ execFile(file, [...args], { windowsHide: true, timeout: o.timeoutMs, maxBuffer: 1024 * 1024, env: { ...process.env, ...o.env } }, (error, stdout) => {
25
+ const code = error ? (typeof error.code === 'number' ? error.code : -1) : 0;
26
+ resolve({ code, stdout: String(stdout ?? '') });
27
+ });
28
+ });
29
+ function readText(file) {
30
+ const bytes = readBoundedFile(file, MAX_ULF_BYTES);
31
+ return bytes ? decodeText(bytes).text : null;
32
+ }
33
+ function readSecretJson(schema, file) {
34
+ const bytes = readBoundedFile(file, MAX_JSON_BYTES);
35
+ if (!bytes)
36
+ return null;
37
+ try {
38
+ const parsed = schema.safeParse(JSON.parse(decodeText(bytes).text));
39
+ return parsed.success ? parsed.data : null;
40
+ }
41
+ catch {
42
+ return null; // not JSON: treated as absent
43
+ }
44
+ }
45
+ function writeText(file, text) {
46
+ fs.mkdirSync(path.dirname(file), { recursive: true });
47
+ fs.writeFileSync(file, text, 'utf8');
48
+ }
49
+ /** The adapter for these folders and this runner (the self-test passes its own). */
50
+ export function unityAdapterFor(dirs, run = runUnityProgram) {
51
+ const activation = path.join(dirs.secrets, 'activation.json');
52
+ const activated = path.join(dirs.secrets, 'activated.json');
53
+ return {
54
+ kind: 'unity',
55
+ apply(p) {
56
+ if (p.ulf)
57
+ writeText(unityUlfFile(dirs.programData), p.ulf);
58
+ if (p.entitlement)
59
+ writeText(unityEntitlementFile(dirs.localAppData), p.entitlement);
60
+ const sign = {};
61
+ if (p.email)
62
+ sign.email = p.email;
63
+ if (p.password)
64
+ sign.password = p.password;
65
+ if (p.serial)
66
+ sign.serial = p.serial;
67
+ if (Object.keys(sign).length)
68
+ writeText(activation, JSON.stringify(sign));
69
+ return {
70
+ summary: p.ulf || p.entitlement
71
+ ? 'Unity: your licence file is in place'
72
+ : 'Unity: your Unity ID is ready to activate the serial',
73
+ };
74
+ },
75
+ current() {
76
+ const ulf = readText(unityUlfFile(dirs.programData));
77
+ const entitlement = readText(unityEntitlementFile(dirs.localAppData));
78
+ const sign = readSecretJson(ActivationSchema, activation) ?? {};
79
+ const out = {
80
+ ...(ulf ? { ulf } : {}),
81
+ ...(entitlement ? { entitlement } : {}),
82
+ ...sign,
83
+ };
84
+ return Object.keys(out).length ? out : null;
85
+ },
86
+ async release() {
87
+ const marker = readSecretJson(ActivatedSchema, activated);
88
+ if (!marker)
89
+ return;
90
+ const sign = readSecretJson(ActivationSchema, activation);
91
+ if (!sign?.email || !sign.password)
92
+ return;
93
+ await run(marker.editorExe, [
94
+ '-batchmode',
95
+ '-nographics',
96
+ '-quit',
97
+ '-returnlicense',
98
+ '-username',
99
+ sign.email,
100
+ '-password',
101
+ sign.password,
102
+ '-logFile',
103
+ path.join(dirs.secrets, 'return.log'),
104
+ ], { timeoutMs: RETURN_MS });
105
+ },
106
+ };
107
+ }
108
+ function defaultDirs() {
109
+ return {
110
+ programData: process.env.ProgramData ?? process.env.PROGRAMDATA ?? 'C:\\ProgramData',
111
+ localAppData: localAppData(),
112
+ secrets: secretDir('unity'),
113
+ };
114
+ }
115
+ export const unityAdapter = unityAdapterFor(defaultDirs());
116
+ /**
117
+ * Activates the serial the sandbox holds once an editor exists, then asks `unity license status` where the licence
118
+ * stands. The detail never holds a secret.
119
+ */
120
+ export async function activateUnityLicence(o, dirs = defaultDirs(), run = runUnityProgram) {
121
+ const sign = readSecretJson(ActivationSchema, path.join(dirs.secrets, 'activation.json'));
122
+ if (sign?.serial && sign.email && sign.password && o.editorExe) {
123
+ await run(o.editorExe, [
124
+ '-batchmode',
125
+ '-nographics',
126
+ '-quit',
127
+ '-serial',
128
+ sign.serial,
129
+ '-username',
130
+ sign.email,
131
+ '-password',
132
+ sign.password,
133
+ '-logFile',
134
+ path.join(dirs.secrets, 'activate.log'),
135
+ ], { timeoutMs: ACTIVATE_MS });
136
+ writeText(path.join(dirs.secrets, 'activated.json'), JSON.stringify({ editorExe: o.editorExe, serial: true }));
137
+ }
138
+ const user = sign?.email ?? null;
139
+ if (!o.unityExe)
140
+ return { state: 'unknown', kind: '', detail: 'not checked', user };
141
+ let parsed = null;
142
+ try {
143
+ const r = await run(o.unityExe, licenceStatusArgs(), { timeoutMs: STATUS_MS, env: UNITY_CLI_ENV });
144
+ parsed = r.code === 0 ? JSON.parse(r.stdout) : null;
145
+ }
146
+ catch {
147
+ parsed = null; // not JSON: reported as not recognised
148
+ }
149
+ const s = parseLicenceStatus(parsed);
150
+ if (s.state === 'active')
151
+ return { state: 'active', kind: s.kind, detail: 'active (' + (s.kind || 'licensed') + ')', user };
152
+ if (s.state === 'none') {
153
+ const hasFile = readText(unityUlfFile(dirs.programData)) !== null || readText(unityEntitlementFile(dirs.localAppData)) !== null;
154
+ return hasFile
155
+ ? {
156
+ state: 'invalid',
157
+ kind: '',
158
+ detail: 'the licence file from ' + (o.source ?? 'your saved sign-in') + ' is not accepted here: sign in again',
159
+ user,
160
+ }
161
+ : { state: 'none', kind: '', detail: 'none: sign in to Unity once in this sandbox', user };
162
+ }
163
+ return { state: 'unknown', kind: '', detail: s.detail, user };
164
+ }
@@ -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';
@@ -13,10 +13,13 @@ import { errMsg } from '../../core/util/errors.js';
13
13
  import { emptyEnv, tool } from '../toolchains/tool-env.js';
14
14
  import { uvVars } from '../toolchains/runtimes.js';
15
15
  import { COMET_MCP_PORT, cometShortcutArgs } from '../browser/comet.js';
16
+ import { perplexityAppShortcutArgs } from '../browser/perplexity-app.js';
16
17
  import { afterInstall } from './after-install.js';
17
18
  import { installFromKept } from './comet-unpack.js';
18
19
  import { findExe } from './find-exe.js';
20
+ import { installStoreApp } from './store-app.js';
19
21
  import { shortcuts } from './shortcuts.js';
22
+ import { SYSTEM } from '../../shared/system.js';
20
23
  import { signatureCheck } from '../toolchains/installers.js';
21
24
  import { resolve } from './sources.js';
22
25
  import { unpack } from './unpack.js';
@@ -33,6 +36,7 @@ export class AppInstaller {
33
36
  parallel;
34
37
  gui = [];
35
38
  installed = [];
39
+ installedIds = [];
36
40
  constructor(env, options, parallel = PARALLEL) {
37
41
  this.env = env;
38
42
  this.options = options;
@@ -45,6 +49,7 @@ export class AppInstaller {
45
49
  async prefetch(entries) {
46
50
  const todo = entries.filter((e) => !('remote' in e.source) &&
47
51
  !('locked' in e.source) &&
52
+ !('store' in e.source) &&
48
53
  !('npm' in e.source) &&
49
54
  e.id !== '7zip' &&
50
55
  (this.options.refresh || !exists(path.join(tool('apps', ...e.id.split('/')), MARKER))));
@@ -82,6 +87,9 @@ export class AppInstaller {
82
87
  }
83
88
  if (this.installed.length)
84
89
  this.env.summary.push('apps: ' + this.installed.join(', ') + ' (shortcuts on the desktop and in Start > Sandbox apps)');
90
+ const preview = previewGuideLine(this.installedIds);
91
+ if (preview)
92
+ this.env.summary.push(preview);
85
93
  }
86
94
  async installOne(entry) {
87
95
  try {
@@ -93,6 +101,20 @@ export class AppInstaller {
93
101
  this.installed.push(entry.name);
94
102
  return;
95
103
  }
104
+ if ('store' in entry.source) {
105
+ // A Store app: Windows in the sandbox is new on every boot, so it is installed again from the PC's package.
106
+ log('Installing ' + entry.name + ' (Microsoft Store package) ...');
107
+ await installStoreApp(entry.id, entry.source.store.packageName);
108
+ fs.writeFileSync(path.join(mkdirp(dest), MARKER), JSON.stringify({ id: entry.id, at: new Date().toISOString() }));
109
+ this.gui.push({
110
+ name: entry.name,
111
+ exe: SYSTEM.powershell(),
112
+ args: perplexityAppShortcutArgs(path.join(launchDir, 'sandbox', 'scripts', 'start-store-app.ps1')),
113
+ });
114
+ this.installed.push(entry.name);
115
+ this.installedIds.push(entry.id);
116
+ return;
117
+ }
96
118
  if (!exists(path.join(dest, MARKER)) || this.options.refresh)
97
119
  await this.download(entry, dest);
98
120
  // A Chromium installer that could not be unpacked is kept and installed into the sandbox each boot instead.
@@ -102,11 +124,17 @@ export class AppInstaller {
102
124
  throw new Error(entry.exe + ' not found in the download');
103
125
  await afterInstall(entry, exe);
104
126
  // 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));
127
+ if (!entry.service && (entry.cli || entry.id === 'sysinternals')) {
128
+ // rustup's rust-analyzer proxy (in cargo's bin folder) only errors until its component is added.
129
+ if (entry.id === 'rust-analyzer')
130
+ this.env.prepend.unshift(path.dirname(exe));
131
+ else
132
+ this.env.prepend.push(path.dirname(exe));
133
+ }
107
134
  if (!entry.cli && !entry.service)
108
135
  this.gui.push({ name: entry.name, exe, ...this.launchArgs(entry) });
109
136
  this.installed.push(entry.name);
137
+ this.installedIds.push(entry.id);
110
138
  }
111
139
  catch (e) {
112
140
  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';
@@ -1 +1,55 @@
1
- export const GAMEDEV_RESOLVERS = {};
1
+ // Where the latest version of the game-development apps (catalogue-gamedev.ts) is, when it is not a plain GitHub
2
+ // release: Krita (download.kde.org, with the SHA-256 its mirror publishes beside each file), Inkscape (inkscape.org's
3
+ // portable 7z and its .sha256) and RenderDoc (renderdoc.org's portable zip; it publishes no checksum, the download is
4
+ // HTTPS from renderdoc.org).
5
+ import { fetchText } from '../../shared/download/index.js';
6
+ const firstHash = (text) => /\b([0-9a-f]{64})\b/i.exec(text)?.[1].toLowerCase() ?? null;
7
+ const byVersion = (a, b) => {
8
+ const x = a.split('.').map(Number);
9
+ const y = b.split('.').map(Number);
10
+ for (let i = 0; i < Math.max(x.length, y.length); i++)
11
+ if ((x[i] ?? 0) !== (y[i] ?? 0))
12
+ return (x[i] ?? 0) - (y[i] ?? 0);
13
+ return 0;
14
+ };
15
+ /** The newest Krita release folder that has the portable Windows zip (a hotfix folder may have only some files). */
16
+ async function krita() {
17
+ const base = 'https://download.kde.org/stable/krita/';
18
+ const versions = [...(await fetchText(base)).matchAll(/href="(\d+\.\d+\.\d+(?:\.\d+)?)\/"/g)]
19
+ .map((m) => m[1])
20
+ .sort(byVersion)
21
+ .reverse()
22
+ .slice(0, 4);
23
+ for (const v of versions) {
24
+ const file = 'krita-x64-' + v + '.zip';
25
+ if (!(await fetchText(base + v + '/')).includes('href="' + file + '"'))
26
+ continue;
27
+ const url = base + v + '/' + file;
28
+ return { url, fileName: file, sha256: firstHash(await fetchText(url + '.sha256').catch(() => '')) };
29
+ }
30
+ throw new Error('no portable Krita zip on download.kde.org');
31
+ }
32
+ async function inkscape() {
33
+ const release = await fetchText('https://inkscape.org/release/');
34
+ const v = /\/release\/inkscape-(\d+\.\d+(?:\.\d+)?)\//.exec(release)?.[1];
35
+ if (!v)
36
+ throw new Error('could not find the latest Inkscape release');
37
+ const page = await fetchText('https://inkscape.org/release/inkscape-' + v + '/windows/64-bit/compressed-7z/dl/');
38
+ const item = /\/gallery\/item\/\d+\/inkscape-[\w.-]+-x64[\w-]*\.7z/.exec(page);
39
+ const sums = /https:\/\/media\.inkscape\.org\/media\/resources\/sigs\/[\w.-]+\.sha256/.exec(page)?.[0];
40
+ if (!item)
41
+ throw new Error('no portable Inkscape 7z for ' + v);
42
+ return {
43
+ url: 'https://inkscape.org' + item[0],
44
+ fileName: 'inkscape-' + v + '-x64.7z',
45
+ sha256: sums ? firstHash(await fetchText(sums).catch(() => '')) : null,
46
+ };
47
+ }
48
+ async function renderdoc() {
49
+ const page = await fetchText('https://renderdoc.org/builds');
50
+ const hit = /https:\/\/renderdoc\.org\/stable\/([\d.]+)\/RenderDoc_[\d.]+_64\.zip/.exec(page);
51
+ if (!hit)
52
+ throw new Error('could not find the RenderDoc download on renderdoc.org');
53
+ return { url: hit[0], fileName: 'RenderDoc_' + hit[1] + '_64.zip' };
54
+ }
55
+ export const GAMEDEV_RESOLVERS = { krita, inkscape, renderdoc };
@@ -0,0 +1,4 @@
1
+ /** Where the PC left an app's package. */
2
+ export declare function storePackageDir(appId: string): string;
3
+ /** Installs the app from its kept package; resolves when Windows has it. */
4
+ export declare function installStoreApp(appId: string, packageName: string): Promise<void>;
@@ -0,0 +1,19 @@
1
+ // A Microsoft Store app in the sandbox: the PC put its package files in the data folder (toolsapps<id>.package,
2
+ // with store-package.json). Windows in the sandbox is new on every boot, so the app is installed again each boot with
3
+ // Add-AppxPackage (sandbox\scripts\install-store-package.ps1), from the package kept on the data disk. The files are
4
+ // checked against the manifest's SHA-256 first (store-check.ts). Windows checks the publisher's signature itself.
5
+ import { runScriptWithJsonAsync } from '../runtime/process.js';
6
+ import { tool } from '../toolchains/tool-env.js';
7
+ import { checkedPackage } from './store-check.js';
8
+ /** Where the PC left an app's package. */
9
+ export function storePackageDir(appId) {
10
+ return tool('apps', appId + '.package');
11
+ }
12
+ /** Installs the app from its kept package; resolves when Windows has it. */
13
+ export async function installStoreApp(appId, packageName) {
14
+ const { main, dependencies } = await checkedPackage(storePackageDir(appId));
15
+ await runScriptWithJsonAsync('install-store-package.ps1', 'ListFile', { main, dependencies }, [
16
+ '-PackageName',
17
+ packageName,
18
+ ]);
19
+ }
@@ -0,0 +1,8 @@
1
+ /** The SHA-256 of a file, read in pieces (the package is hundreds of MB). */
2
+ export declare function fileSha256(file: string): Promise<string>;
3
+ export interface CheckedPackage {
4
+ readonly main: string;
5
+ readonly dependencies: string[];
6
+ }
7
+ /** The package in `dir` when its manifest is valid and every file is there as hashed; throws otherwise. */
8
+ export declare function checkedPackage(dir: string): Promise<CheckedPackage>;
@@ -0,0 +1,33 @@
1
+ // The package files of a Store app the PC left in the data folder, checked against the SHA-256 in store-package.json.
2
+ // No runtime imports: the self-test loads this file.
3
+ import crypto from 'node:crypto';
4
+ import fs from 'node:fs';
5
+ import path from 'node:path';
6
+ import { STORE_PACKAGE_MANIFEST, StorePackageManifestSchema } from '../../shared/apps/store-package.js';
7
+ import { readValid } from '../../shared/io/json.js';
8
+ const READ_CHUNK = 1024 * 1024;
9
+ /** The SHA-256 of a file, read in pieces (the package is hundreds of MB). */
10
+ export function fileSha256(file) {
11
+ return new Promise((resolve, reject) => {
12
+ const hash = crypto.createHash('sha256');
13
+ const stream = fs.createReadStream(file, { highWaterMark: READ_CHUNK });
14
+ stream.on('data', (chunk) => hash.update(chunk));
15
+ stream.on('error', reject);
16
+ stream.on('end', () => resolve(hash.digest('hex')));
17
+ });
18
+ }
19
+ /** The package in `dir` when its manifest is valid and every file is there as hashed; throws otherwise. */
20
+ export async function checkedPackage(dir) {
21
+ const manifest = readValid(StorePackageManifestSchema, path.join(dir, STORE_PACKAGE_MANIFEST));
22
+ if (!manifest)
23
+ throw new Error('the package was not handed over (this PC could not fetch it: see the launch notes)');
24
+ for (const f of manifest.files) {
25
+ if ((await fileSha256(path.join(dir, f.name)).catch(() => '')) !== f.sha256)
26
+ throw new Error(f.name + ' is missing or changed');
27
+ }
28
+ const full = (name) => path.join(dir, name);
29
+ return {
30
+ main: full(manifest.files.find((f) => f.role === 'main').name),
31
+ dependencies: manifest.files.filter((f) => f.role === 'dependency').map((f) => full(f.name)),
32
+ };
33
+ }
@@ -52,7 +52,20 @@ import { setBackgroundWork, setSandboxStatus } from '../runtime/status.js';
52
52
  import { errMsg } from '../../core/util/errors.js';
53
53
  const PRIMARY_POLLS = 600;
54
54
  const POLL_MS = 500;
55
- const VSCODE_LANGUAGES = ['python', 'java', 'go', 'rust', 'cpp', 'msvc', 'dotnet', 'ruby', 'php', 'deno', 'pwsh'];
55
+ const VSCODE_LANGUAGES = [
56
+ 'python',
57
+ 'java',
58
+ 'go',
59
+ 'rust',
60
+ 'cpp',
61
+ 'msvc',
62
+ 'unreal',
63
+ 'dotnet',
64
+ 'ruby',
65
+ 'php',
66
+ 'deno',
67
+ 'pwsh',
68
+ ];
56
69
  function startDaemon(env, secrets) {
57
70
  spawn(process.execPath, [path.join(path.dirname(process.argv[1]), 'daemon.js')], {
58
71
  detached: true,
@@ -86,7 +99,7 @@ function writeDbCommand() {
86
99
  writeDevToolCommands(dir);
87
100
  return dir;
88
101
  }
89
- /** sbx-steam, sbx-fivem, sbx-unity, sbx-android for the toolchains that are on: runtime\tool.js with that helper. */
102
+ /** sbx-steam, sbx-fivem, sbx-android for the toolchains that are on: runtime\tool.js with that helper. */
90
103
  function writeDevToolCommands(dir) {
91
104
  const script = path.join(path.dirname(process.argv[1]), 'tool.js');
92
105
  for (const tool of devToolsFor(session.toolchains))
@@ -7,6 +7,13 @@ import { dataDir, launchDir, logsDir, toolsDir } from '../runtime/paths.js';
7
7
  import { addonGuideLine, APPS, headroomGuideLine, RTK_GUIDE_LINE } from '../../shared/apps/index.js';
8
8
  import { installedAddon, installedRtk } from '../setup/agent-addons.js';
9
9
  import { projectFolderLines, toolLines } from './guide-lines.js';
10
+ import { sandboxScriptsDir } from '../../shared/scripts.js';
11
+ import { findUproject, unrealGuideLines } from '../../shared/unreal/index.js';
12
+ import { DISK } from '../toolchains/engines/unreal-engines.js';
13
+ import { readValid } from '../../shared/io/json.js';
14
+ import { UNITY_STATUS_FILE, UnityStatusSchema, unityGuideLines } from '../../shared/unity/index.js';
15
+ import { tool } from '../toolchains/tool-env.js';
16
+ import { readUnrealStatus } from '../toolchains/engines/unreal-status.js';
10
17
  const BEGIN = '<!-- agent-sandbox:begin (generated every boot, do not edit) -->';
11
18
  const END = '<!-- agent-sandbox:end -->';
12
19
  /** Drives other than C: are subst drives onto C:\Mirror\<letter> (mapped folders shown at their PC paths). */
@@ -25,6 +32,39 @@ const mirrorLine = () => {
25
32
  };
26
33
  /** Folders mapped for other projects at boot; launch folders from before mappedProjects only list the guest side. */
27
34
  const mappedFolders = () => session.mappedProjects ?? (session.roots ?? []).map((guest) => ({ host: 'a folder on the PC', guest }));
35
+ /** The Mixamo import template, when this version ships one. */
36
+ function mixamoScript() {
37
+ const file = path.join(sandboxScriptsDir(), 'templates', 'unreal', 'import_mixamo.py');
38
+ return fs.existsSync(file) ? file : null;
39
+ }
40
+ /** The Unreal lines (engine per project, build/test/package/debug commands) when the sandbox is set up for Unreal. */
41
+ function unrealLines(projects) {
42
+ const any = session.toolchains.unreal.enabled === true || projects.some((p) => p.tools?.some((t) => /^unreal/.test(t)));
43
+ if (!any)
44
+ return [];
45
+ const status = readUnrealStatus();
46
+ if (!status.updatedAt)
47
+ return [];
48
+ const folders = [workspace, ...projects.map((p) => p.guest)];
49
+ return unrealGuideLines(status, folders
50
+ .filter((f, i) => folders.findIndex((g) => g.toLowerCase() === f.toLowerCase()) === i)
51
+ .filter((f) => status.projects.some((p) => p.workspace.toLowerCase() === f.toLowerCase()))
52
+ .map((f) => ({ workspace: f, uproject: findUproject(f, DISK) })), { mixamo: { downloads: path.join(dataDir, 'downloads'), script: mixamoScript() } });
53
+ }
54
+ /** The Unity lines (editor per project, build/test/import commands) when the sandbox is set up for Unity. */
55
+ function unityLines(projects) {
56
+ const any = session.toolchains.unity.enabled === true || projects.some((p) => p.tools?.some((t) => /^unity/.test(t)));
57
+ if (!any)
58
+ return [];
59
+ const status = readValid(UnityStatusSchema, path.join(tool('unity'), UNITY_STATUS_FILE));
60
+ if (!status?.updatedAt)
61
+ return [];
62
+ const folders = [workspace, ...projects.map((p) => p.guest)];
63
+ return unityGuideLines(status, folders
64
+ .filter((f, i) => folders.findIndex((g) => g.toLowerCase() === f.toLowerCase()) === i)
65
+ .filter((f) => status.projects.some((p) => p.workspace.toLowerCase() === f.toLowerCase()))
66
+ .map((f) => ({ workspace: f })));
67
+ }
28
68
  /** Deliberately tiny: it is loaded into every conversation. */
29
69
  export function sandboxGuide(toolSummary, lan = 'blocked', projects = [], addons = []) {
30
70
  return [
@@ -45,6 +85,8 @@ export function sandboxGuide(toolSummary, lan = 'blocked', projects = [], addons
45
85
  '- RO: `' + launchDir + "` (this boot's scripts) · lost on close: everything else, incl. `C:\\nvm` (Node symlink)",
46
86
  ...mirrorLine(),
47
87
  ...toolLines(session.project, toolSummary, projects),
88
+ ...unrealLines(projects),
89
+ ...unityLines(projects),
48
90
  // Agent add-ons that change how the agent works (rtk, Headroom, MCP servers, ccusage).
49
91
  ...addons,
50
92
  '- Persistent installs: `npm i -g` (per nvm version), `pip`, `cargo install`, `dotnet tool -g`, `uv tool`, `bun add -g`. Real installers must re-run each boot via a hook (ask the user).',
@@ -1,5 +1,5 @@
1
1
  import { type CometPayload } from '../../shared/accounts/kinds/comet.js';
2
- import { type DevToolsEndpoint } from './cdp.js';
2
+ import { CdpConnection, type DevToolsEndpoint } from './cdp.js';
3
3
  /** The port comet-mcp looks for (it connects to a Comet already listening there). */
4
4
  export declare const COMET_MCP_PORT = 9222;
5
5
  /** The addresses whose cookies are read back (their cookies and the parent domain's). */
@@ -16,6 +16,8 @@ export declare function cometLaunchArgs(port: number, profile?: string): string[
16
16
  export declare function cometShortcutArgs(port: number, profile?: string): string;
17
17
  /** Where a Comet running with this profile listens, from its DevToolsActivePort file; null when there is none. */
18
18
  export declare function devToolsEndpoint(profile?: string): DevToolsEndpoint | null;
19
+ /** A connection to the Comet running with this profile, or null when none answers. */
20
+ export declare function connect(profile: string): Promise<CdpConnection | null>;
19
21
  /**
20
22
  * The sign-in the running Comet holds now (perplexity.ai's own cookies, asked of one of its pages), or null when
21
23
  * Comet is not running with this profile, has no page open, or holds no sign-in. Never asks for another site's cookies.
@@ -56,7 +56,7 @@ export function devToolsEndpoint(profile = cometProfileDir()) {
56
56
  return bytes ? parseDevToolsActivePort(bytes.toString('utf8')) : null;
57
57
  }
58
58
  /** A connection to the Comet running with this profile, or null when none answers. */
59
- async function connect(profile) {
59
+ export async function connect(profile) {
60
60
  const endpoint = devToolsEndpoint(profile);
61
61
  if (!endpoint)
62
62
  return null;
@@ -0,0 +1,12 @@
1
+ import { type CometPayload } from '../../shared/accounts/kinds/comet.js';
2
+ /** The app's profile: on the sandbox's own disk, wiped with it. */
3
+ export declare function perplexityAppProfileDir(): string;
4
+ /**
5
+ * The powershell.exe arguments of the app's shortcut: start-store-app.ps1 with the package, the profile and a free
6
+ * DevTools port (0). `script` is the full path of the script in the launch folder.
7
+ */
8
+ export declare function perplexityAppShortcutArgs(script: string, profile?: string): string;
9
+ /** The sign-in the running app holds now, or null (not running with this profile, no page, signed out). */
10
+ export declare function readAppSignIn(profile?: string): Promise<CometPayload | null>;
11
+ /** Writes the sign-in into the running app; false when the app is not running with this profile. */
12
+ export declare function restoreAppSignIn(payload: CometPayload, profile?: string): Promise<boolean>;
@@ -0,0 +1,60 @@
1
+ // The Perplexity desktop app (a Microsoft Store package, Electron inside) in this sandbox: the profile folder it runs
2
+ // with, the command line of its shortcut, and its sign-in over the DevTools port, the way Comet's is kept
3
+ // (browser/comet.ts): only the allow-listed perplexity.ai cookies are read (Network.getCookies) and written back
4
+ // (Storage.setCookies). The profile is on the sandbox's own disk: its cookie key is sealed to this sandbox's Windows
5
+ // user, which is why the sign-in is not copied from the PC. No runtime imports: the sign-in adapter and the self-test
6
+ // load this file.
7
+ import fs from 'node:fs';
8
+ import path from 'node:path';
9
+ import { cookiesToRestore } from '../../shared/accounts/kinds/comet.js';
10
+ import { PERPLEXITY_DESKTOP_PACKAGE } from '../../shared/apps/catalogue-agents-web.js';
11
+ import { localAppData } from '../accounts/adapter.js';
12
+ import { connect, readCometSignIn } from './comet.js';
13
+ /** The app's profile: on the sandbox's own disk, wiped with it. */
14
+ export function perplexityAppProfileDir() {
15
+ return path.join(localAppData(), 'AgentSandbox', 'perplexity-app-profile');
16
+ }
17
+ /**
18
+ * The powershell.exe arguments of the app's shortcut: start-store-app.ps1 with the package, the profile and a free
19
+ * DevTools port (0). `script` is the full path of the script in the launch folder.
20
+ */
21
+ export function perplexityAppShortcutArgs(script, profile = perplexityAppProfileDir()) {
22
+ return [
23
+ '-NoLogo',
24
+ '-NoProfile',
25
+ '-WindowStyle',
26
+ 'Hidden',
27
+ '-ExecutionPolicy',
28
+ 'Bypass',
29
+ '-File',
30
+ '"' + script + '"',
31
+ '-PackageName',
32
+ PERPLEXITY_DESKTOP_PACKAGE,
33
+ '-UserDataDir',
34
+ '"' + profile + '"',
35
+ '-Port',
36
+ '0',
37
+ ].join(' ');
38
+ }
39
+ /** The sign-in the running app holds now, or null (not running with this profile, no page, signed out). */
40
+ export function readAppSignIn(profile = perplexityAppProfileDir()) {
41
+ return readCometSignIn(profile);
42
+ }
43
+ /** Writes the sign-in into the running app; false when the app is not running with this profile. */
44
+ export async function restoreAppSignIn(payload, profile = perplexityAppProfileDir()) {
45
+ const cookies = cookiesToRestore(payload, Math.floor(Date.now() / 1000));
46
+ if (!cookies.length)
47
+ return true;
48
+ if (!fs.existsSync(path.join(profile, 'DevToolsActivePort')))
49
+ return false;
50
+ const cdp = await connect(profile);
51
+ if (!cdp)
52
+ return false;
53
+ try {
54
+ await cdp.call('Storage.setCookies', { cookies });
55
+ return true;
56
+ }
57
+ finally {
58
+ cdp.close();
59
+ }
60
+ }