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.
- package/config.example.json +17 -5
- package/dist/app/features/accounts/services.def.d.ts +1 -1
- package/dist/app/features/accounts/services.def.js +1 -1
- package/dist/app/features/data/data.def.d.ts +4 -4
- package/dist/app/features/data/data.run.d.ts +1 -1
- package/dist/app/features/defs.d.ts +356 -6
- package/dist/app/features/defs.js +15 -0
- package/dist/app/features/projects/projects.def.d.ts +1 -1
- package/dist/app/features/projects/projects.run.d.ts +1 -1
- package/dist/app/features/settings/categories.def.js +1 -0
- package/dist/app/features/unity/index.d.ts +2 -0
- package/dist/app/features/unity/index.js +3 -0
- package/dist/app/features/unity/unity.def.d.ts +294 -0
- package/dist/app/features/unity/unity.def.js +471 -0
- package/dist/app/features/unity/unity.run.d.ts +142 -0
- package/dist/app/features/unity/unity.run.js +363 -0
- package/dist/app/features/unreal/index.d.ts +2 -0
- package/dist/app/features/unreal/index.js +3 -0
- package/dist/app/features/unreal/unreal.def.d.ts +236 -0
- package/dist/app/features/unreal/unreal.def.js +379 -0
- package/dist/app/features/unreal/unreal.run.d.ts +96 -0
- package/dist/app/features/unreal/unreal.run.js +217 -0
- package/dist/app/registry.js +11 -0
- package/dist/bin/agent-sandbox.js +2 -2
- package/dist/bin/{chunk-IZRGTSQ5.js → chunk-6WWMBCT2.js} +4632 -1941
- package/dist/bin/{chunk-SUYYVIDW.js → chunk-JXL6ZOO3.js} +126 -6
- package/dist/bin/{chunk-KH4MI2U5.js → chunk-ZXCAVVS4.js} +1 -1
- package/dist/bin/claude-host.js +2 -2
- package/dist/bin/claude-sbx.js +2 -2
- package/dist/bin/codex-sbx.js +2 -2
- package/dist/bin/gemini-sbx.js +2 -2
- package/dist/bin/sandbox.js +2 -2
- package/dist/bin/sbx-hide.js +2 -2
- package/dist/bin/sbx-show.js +2 -2
- package/dist/cli/commands/launch/launch.js +17 -2
- package/dist/cli/commands/registry.js +6 -0
- package/dist/cli/manifest.d.ts +51 -1
- package/dist/cli/manifest.js +58 -1
- package/dist/cli/menu/coverage-exempt.js +2 -0
- package/dist/cli/menu/pages/sandbox-page.d.ts +6 -0
- package/dist/cli/menu/pages/sandbox-page.js +71 -4
- package/dist/cli/menu/views.js +4 -0
- package/dist/core/config/defaults.js +33 -6
- package/dist/core/schema/accounts.d.ts +3 -0
- package/dist/core/schema/accounts.js +1 -0
- package/dist/core/schema/config.d.ts +32 -14
- package/dist/core/schema/github.d.ts +1 -0
- package/dist/core/schema/github.js +3 -0
- package/dist/core/schema/pc-installs.d.ts +5 -2
- package/dist/core/schema/pc-installs.js +16 -2
- package/dist/core/schema/toolchains.d.ts +8 -0
- package/dist/core/schema/toolchains.js +20 -5
- package/dist/guest/accounts/adapters.js +2 -0
- package/dist/guest/accounts/apply.d.ts +4 -2
- package/dist/guest/accounts/kinds/epic.js +18 -13
- package/dist/guest/accounts/kinds/perplexityapp.d.ts +7 -0
- package/dist/guest/accounts/kinds/perplexityapp.js +53 -0
- package/dist/guest/accounts/kinds/unity.d.ts +27 -0
- package/dist/guest/accounts/kinds/unity.js +164 -7
- package/dist/guest/apps/after-install.d.ts +1 -1
- package/dist/guest/apps/after-install.js +6 -1
- package/dist/guest/apps/app-installer.d.ts +1 -0
- package/dist/guest/apps/app-installer.js +31 -3
- package/dist/guest/apps/comet-unpack.js +3 -2
- package/dist/guest/apps/resolvers-gamedev.js +55 -1
- package/dist/guest/apps/store-app.d.ts +4 -0
- package/dist/guest/apps/store-app.js +19 -0
- package/dist/guest/apps/store-check.d.ts +8 -0
- package/dist/guest/apps/store-check.js +33 -0
- package/dist/guest/boot/boot.js +15 -2
- package/dist/guest/boot/guide.js +42 -0
- package/dist/guest/browser/comet.d.ts +3 -1
- package/dist/guest/browser/comet.js +1 -1
- package/dist/guest/browser/perplexity-app.d.ts +12 -0
- package/dist/guest/browser/perplexity-app.js +60 -0
- package/dist/guest/daemon/daemon.js +5 -0
- package/dist/guest/devtools/devtool.d.ts +1 -1
- package/dist/guest/devtools/registry.js +1 -2
- package/dist/guest/entries/tool.js +1 -1
- package/dist/guest/setup/addon-mcp.d.ts +12 -0
- package/dist/guest/setup/addon-mcp.js +29 -4
- package/dist/guest/setup/browser-gpu.js +14 -11
- package/dist/guest/setup/github-token.js +4 -4
- package/dist/guest/setup/headroom.js +4 -0
- package/dist/guest/setup/unity-agent.d.ts +11 -0
- package/dist/guest/setup/unity-agent.js +36 -0
- package/dist/guest/toolchains/engines/epic-launcher.d.ts +26 -0
- package/dist/guest/toolchains/engines/epic-launcher.js +127 -0
- package/dist/guest/toolchains/engines/unity-cli.d.ts +24 -0
- package/dist/guest/toolchains/engines/unity-cli.js +74 -0
- package/dist/guest/toolchains/engines/unity-hub.d.ts +2 -0
- package/dist/guest/toolchains/engines/unity-hub.js +27 -0
- package/dist/guest/toolchains/engines/unity-release.d.ts +4 -15
- package/dist/guest/toolchains/engines/unity-release.js +17 -5
- package/dist/guest/toolchains/engines/unity-status.d.ts +11 -0
- package/dist/guest/toolchains/engines/unity-status.js +39 -0
- package/dist/guest/toolchains/engines/unity.d.ts +20 -1
- package/dist/guest/toolchains/engines/unity.js +250 -3
- package/dist/guest/toolchains/engines/unreal-engines.d.ts +21 -1
- package/dist/guest/toolchains/engines/unreal-engines.js +163 -3
- package/dist/guest/toolchains/engines/unreal-status.d.ts +13 -0
- package/dist/guest/toolchains/engines/unreal-status.js +41 -0
- package/dist/guest/toolchains/native/debuggers.d.ts +6 -0
- package/dist/guest/toolchains/native/debuggers.js +57 -0
- package/dist/guest/toolchains/native/mesa.js +15 -8
- package/dist/guest/toolchains/native/rust-extras.d.ts +7 -0
- package/dist/guest/toolchains/native/rust-extras.js +23 -1
- package/dist/guest/toolchains/native/unreal.d.ts +2 -2
- package/dist/guest/toolchains/native/unreal.js +96 -18
- package/dist/guest/toolchains/plan.js +17 -2
- package/dist/guest/toolchains/tools/cli.js +2 -2
- package/dist/guest/toolchains/tools/vscode-profile.js +2 -0
- package/dist/host/accounts/deliver.js +4 -0
- package/dist/host/accounts/kinds/epic.js +11 -5
- package/dist/host/accounts/kinds/perplexityapp.d.ts +2 -0
- package/dist/host/accounts/kinds/perplexityapp.js +8 -0
- package/dist/host/accounts/kinds/unity.d.ts +1 -1
- package/dist/host/accounts/kinds/unity.js +60 -8
- package/dist/host/accounts/leases.d.ts +2 -1
- package/dist/host/accounts/source.d.ts +2 -0
- package/dist/host/accounts/source.js +1 -0
- package/dist/host/accounts/sources.js +2 -0
- package/dist/host/apps/index.d.ts +1 -0
- package/dist/host/apps/index.js +2 -0
- package/dist/host/apps/store-package.d.ts +51 -0
- package/dist/host/apps/store-package.js +209 -0
- package/dist/host/github/deliver.d.ts +8 -3
- package/dist/host/github/deliver.js +42 -29
- package/dist/host/github/index.d.ts +1 -0
- package/dist/host/github/index.js +1 -0
- package/dist/host/github/pc-login.d.ts +27 -0
- package/dist/host/github/pc-login.js +129 -0
- package/dist/host/pc-installs/discover.d.ts +6 -0
- package/dist/host/pc-installs/discover.js +37 -5
- package/dist/host/pc-installs/epic.d.ts +3 -0
- package/dist/host/pc-installs/epic.js +33 -2
- package/dist/host/pc-installs/source.d.ts +1 -1
- package/dist/host/pc-installs/unity-assets.d.ts +5 -0
- package/dist/host/pc-installs/unity-assets.js +18 -0
- package/dist/host/pc-installs/unity.d.ts +20 -0
- package/dist/host/pc-installs/unity.js +135 -2
- package/dist/host/pc-installs/unreal-vault.d.ts +11 -0
- package/dist/host/pc-installs/unreal-vault.js +78 -0
- package/dist/host/pc-installs/unreal.d.ts +36 -0
- package/dist/host/pc-installs/unreal.js +145 -2
- package/dist/host/profile/claude-profile.d.ts +8 -0
- package/dist/host/profile/claude-profile.js +43 -4
- package/dist/host/profile/index.d.ts +1 -1
- package/dist/host/profile/index.js +1 -1
- package/dist/runtime/agent.js +197 -18
- package/dist/runtime/daemon.js +3938 -1386
- package/dist/runtime/db.js +197 -18
- package/dist/runtime/main.js +4336 -1978
- package/dist/runtime/netproxy.js +197 -18
- package/dist/runtime/tool.js +214 -38
- package/dist/runtime/view.js +197 -18
- package/dist/selftest/agent-paths.test.js +1 -0
- package/dist/selftest/agents-web.test.js +14 -1
- package/dist/selftest/engines.test.js +602 -3
- package/dist/selftest/github.test.js +64 -1
- package/dist/selftest/heavy-load.test.js +3 -1
- package/dist/selftest/hostile-input.test.js +11 -1
- package/dist/selftest/index.js +12 -0
- package/dist/selftest/menu-screens.js +5 -1
- package/dist/selftest/mixamo.test.d.ts +2 -0
- package/dist/selftest/mixamo.test.js +60 -0
- package/dist/selftest/persisted-installs.test.js +1 -1
- package/dist/selftest/secrets.test.js +36 -1
- package/dist/selftest/service-accounts.test.js +1 -1
- package/dist/selftest/store-app.test.d.ts +2 -0
- package/dist/selftest/store-app.test.js +162 -0
- package/dist/selftest/support/engine-golden.js +10 -0
- package/dist/selftest/support/fixtures.d.ts +13 -2
- package/dist/selftest/support/unity-sample.d.ts +2 -0
- package/dist/selftest/support/unity-sample.js +80 -0
- package/dist/selftest/support/unreal-sample.d.ts +2 -0
- package/dist/selftest/support/unreal-sample.js +83 -0
- package/dist/selftest/toolchains.test.js +32 -2
- package/dist/selftest/unity-accounts.test.d.ts +2 -0
- package/dist/selftest/unity-accounts.test.js +106 -0
- package/dist/selftest/unity-agent.test.d.ts +2 -0
- package/dist/selftest/unity-agent.test.js +47 -0
- package/dist/selftest/unity-page.test.d.ts +2 -0
- package/dist/selftest/unity-page.test.js +177 -0
- package/dist/selftest/unity-setup.test.d.ts +2 -0
- package/dist/selftest/unity-setup.test.js +49 -0
- package/dist/selftest/unity.test.js +238 -3
- package/dist/shared/accounts/index.d.ts +2 -1
- package/dist/shared/accounts/index.js +2 -1
- package/dist/shared/accounts/kinds/epic.d.ts +6 -1
- package/dist/shared/accounts/kinds/epic.js +11 -1
- package/dist/shared/accounts/kinds/unity.d.ts +28 -0
- package/dist/shared/accounts/kinds/unity.js +88 -0
- package/dist/shared/accounts/kinds.d.ts +2 -2
- package/dist/shared/accounts/kinds.js +23 -1
- package/dist/shared/accounts/payloads.d.ts +52 -0
- package/dist/shared/accounts/payloads.js +14 -2
- package/dist/shared/apps/associations.d.ts +7 -0
- package/dist/shared/apps/associations.js +26 -0
- package/dist/shared/apps/catalogue-agents-web.d.ts +11 -1
- package/dist/shared/apps/catalogue-agents-web.js +28 -2
- package/dist/shared/apps/catalogue-gamedev.d.ts +3 -0
- package/dist/shared/apps/catalogue-gamedev.js +102 -1
- package/dist/shared/apps/catalogue.d.ts +8 -0
- package/dist/shared/apps/index.d.ts +5 -2
- package/dist/shared/apps/index.js +5 -2
- package/dist/shared/apps/preview.d.ts +2 -0
- package/dist/shared/apps/preview.js +10 -0
- package/dist/shared/apps/selection.d.ts +6 -0
- package/dist/shared/apps/selection.js +9 -0
- package/dist/shared/apps/store-package.d.ts +31 -0
- package/dist/shared/apps/store-package.js +34 -0
- package/dist/shared/browser-policy.d.ts +14 -0
- package/dist/shared/browser-policy.js +22 -0
- package/dist/shared/channel/schema.d.ts +24 -0
- package/dist/shared/channel/secrets.d.ts +17 -0
- package/dist/shared/io/index.d.ts +1 -0
- package/dist/shared/io/index.js +1 -0
- package/dist/shared/io/rotate-log.d.ts +2 -0
- package/dist/shared/io/rotate-log.js +16 -0
- package/dist/shared/memory/policy.d.ts +5 -0
- package/dist/shared/memory/policy.js +7 -0
- package/dist/shared/paths/pc-installs.d.ts +13 -2
- package/dist/shared/paths/pc-installs.js +2 -0
- package/dist/shared/readiness.d.ts +2 -3
- package/dist/shared/readiness.js +2 -3
- package/dist/shared/signer.d.ts +4 -0
- package/dist/shared/signer.js +2 -0
- package/dist/shared/unity/asset-cache.d.ts +20 -0
- package/dist/shared/unity/asset-cache.js +44 -0
- package/dist/shared/unity/cli.d.ts +40 -0
- package/dist/shared/unity/cli.js +77 -0
- package/dist/shared/unity/editors.d.ts +46 -0
- package/dist/shared/unity/editors.js +89 -0
- package/dist/shared/unity/guide.d.ts +9 -0
- package/dist/shared/unity/guide.js +84 -0
- package/dist/shared/unity/index.d.ts +9 -0
- package/dist/shared/unity/index.js +11 -0
- package/dist/shared/unity/manifest.d.ts +10 -0
- package/dist/shared/unity/manifest.js +53 -0
- package/dist/shared/unity/mcp.d.ts +8 -0
- package/dist/shared/unity/mcp.js +12 -0
- package/dist/shared/unity/release.d.ts +28 -0
- package/dist/shared/unity/release.js +83 -0
- package/dist/shared/unity/status.d.ts +59 -0
- package/dist/shared/unity/status.js +45 -0
- package/dist/shared/unity/version.d.ts +27 -0
- package/dist/shared/unity/version.js +75 -0
- package/dist/shared/unreal/engines.d.ts +161 -0
- package/dist/shared/unreal/engines.js +308 -0
- package/dist/shared/unreal/guide.d.ts +19 -0
- package/dist/shared/unreal/guide.js +105 -0
- package/dist/shared/unreal/index.d.ts +7 -0
- package/dist/shared/unreal/index.js +8 -0
- package/dist/shared/unreal/launcher-files.d.ts +88 -0
- package/dist/shared/unreal/launcher-files.js +292 -0
- package/dist/shared/unreal/mixamo.d.ts +5 -0
- package/dist/shared/unreal/mixamo.js +16 -0
- package/dist/shared/unreal/room.d.ts +17 -0
- package/dist/shared/unreal/room.js +34 -0
- package/dist/shared/unreal/status.d.ts +54 -0
- package/dist/shared/unreal/status.js +57 -0
- package/dist/shared/unreal/vault.d.ts +21 -0
- package/dist/shared/unreal/vault.js +67 -0
- package/dist/shared/unreal/vs-check.d.ts +39 -0
- package/dist/shared/unreal/vs-check.js +79 -0
- package/dist/web/index.html +1 -1
- package/dist/web/{main-CCILLX2M.js → main-VPMYTSUL.js} +87 -87
- package/native/csharp/HcsHost/AssemblyInfo.cs +3 -3
- package/native/csharp/HostTools/AssemblyInfo.cs +3 -3
- package/native/csharp/StorageHelper/AssemblyInfo.cs +3 -3
- package/native/scripts/get-store-package.ps1 +58 -0
- package/package.json +1 -1
- package/sandbox/addons/openupm-cli/package-lock.json +2921 -0
- package/sandbox/addons/openupm-cli/package.json +7 -0
- package/sandbox/addons/unity-mcp/requirements.in +1 -0
- package/sandbox/addons/unity-mcp/requirements.txt +1165 -0
- package/sandbox/csharp/AppActivation.cs +38 -0
- package/sandbox/csharp/VsSetup.cs +210 -0
- package/sandbox/scripts/get-vs-setup.ps1 +93 -0
- package/sandbox/scripts/install-store-package.ps1 +28 -0
- package/sandbox/scripts/set-registry-values.ps1 +35 -0
- package/sandbox/scripts/start-store-app.ps1 +29 -0
- package/schemas/config.schema.json +96 -8
- package/schemas/sandbox-config.schema.json +46 -6
- package/dist/guest/devtools/unity.d.ts +0 -2
- package/dist/guest/devtools/unity.js +0 -7
|
@@ -9,7 +9,7 @@ import { pageLines } from '../cli/output/index.js';
|
|
|
9
9
|
import { DEFAULTS } from '../core/config/index.js';
|
|
10
10
|
import { applyOverrides, normalizeOverrides } from '../core/local-config/index.js';
|
|
11
11
|
import { ConfigSchema, LocalOverridesSchema } from '../core/schema/index.js';
|
|
12
|
-
import { checkGithubToken, GithubToken, githubTokenAllowed, prepareGithubToken, verifyGithubToken, } from '../host/github/index.js';
|
|
12
|
+
import { checkGithubToken, GithubToken, githubTokenAllowed, parseHostsYml, prepareGithubToken, readPcGhLogin, verifyGithubToken, } from '../host/github/index.js';
|
|
13
13
|
import { UserError } from '../core/util/index.js';
|
|
14
14
|
import { report } from '../cli/report.js';
|
|
15
15
|
import { toWireError, fromWireError } from '../app/kernel/index.js';
|
|
@@ -288,6 +288,69 @@ export const githubSuite = {
|
|
|
288
288
|
shareOn.github.shareToken = true;
|
|
289
289
|
t.check(prepareGithubToken(shareOn, shareOn.storage.root, expiring, [], Date.parse('2027-01-30T00:00:00.000Z')).notes.some((n) => n.includes('expires in 5 days')) &&
|
|
290
290
|
!prepareGithubToken(shareOn, shareOn.storage.root, expiring, [], Date.parse('2026-12-01T00:00:00.000Z')).notes.some((n) => n.includes('expires')), 'a launch mentions a token that ends within 7 days, and only then');
|
|
291
|
+
t.section('the gh login on this PC (github.usePcLogin)');
|
|
292
|
+
const PC_TOKEN = 'gho_' + 'a'.repeat(36);
|
|
293
|
+
const KEYRING_TOKEN = 'gho_' + 'b'.repeat(36);
|
|
294
|
+
const hosts = (token) => [
|
|
295
|
+
'github.com:',
|
|
296
|
+
' users:',
|
|
297
|
+
' other:',
|
|
298
|
+
' oauth_token: gho_' + 'z'.repeat(36),
|
|
299
|
+
' josh:',
|
|
300
|
+
...(token ? [' oauth_token: ' + token] : []),
|
|
301
|
+
' git_protocol: https',
|
|
302
|
+
' user: josh',
|
|
303
|
+
'',
|
|
304
|
+
].join('\r\n');
|
|
305
|
+
t.equal(parseHostsYml(hosts(PC_TOKEN)), { signedIn: true, token: PC_TOKEN }, 'hosts.yml: the active user token');
|
|
306
|
+
t.equal(parseHostsYml('github.com:\n oauth_token: ' + PC_TOKEN + '\n user: josh\n'), { signedIn: true, token: PC_TOKEN }, 'hosts.yml: an older file with the token under the host');
|
|
307
|
+
t.equal(parseHostsYml('ghe.corp:\n oauth_token: ' + PC_TOKEN + '\n'), { signedIn: false, token: null }, 'hosts.yml: other hosts are not read');
|
|
308
|
+
t.equal(parseHostsYml(hosts(null)), { signedIn: true, token: null }, 'hosts.yml: a login kept in Credential Manager');
|
|
309
|
+
t.equal(parseHostsYml(''), { signedIn: false, token: null }, 'hosts.yml: empty');
|
|
310
|
+
const pcDir = t.dir('github-pc');
|
|
311
|
+
const pcEnv = { GH_CONFIG_DIR: pcDir, GH_TOKEN: 'x'.repeat(30) };
|
|
312
|
+
t.check(readPcGhLogin({ env: pcEnv, run: () => 'never' }) === null, 'no hosts.yml: no login, quietly');
|
|
313
|
+
fs.writeFileSync(path.join(pcDir, 'hosts.yml'), hosts(PC_TOKEN));
|
|
314
|
+
t.check(readPcGhLogin({ env: pcEnv, run: () => 'never' }) === PC_TOKEN, 'the token is read from hosts.yml');
|
|
315
|
+
fs.writeFileSync(path.join(pcDir, 'hosts.yml'), hosts(null));
|
|
316
|
+
t.check(readPcGhLogin({ env: pcEnv, run: () => KEYRING_TOKEN }) === null, 'a keyring login with no gh.exe found: none');
|
|
317
|
+
const exeDir = t.dir('github-pc-programs');
|
|
318
|
+
fs.mkdirSync(path.join(exeDir, 'GitHub CLI'), { recursive: true });
|
|
319
|
+
fs.writeFileSync(path.join(exeDir, 'GitHub CLI', 'gh.exe'), '');
|
|
320
|
+
const seen = [];
|
|
321
|
+
const keyring = readPcGhLogin({
|
|
322
|
+
env: { ...pcEnv, ProgramFiles: exeDir },
|
|
323
|
+
run: (exe) => (seen.push(exe), KEYRING_TOKEN + '\n'),
|
|
324
|
+
});
|
|
325
|
+
t.check(keyring === KEYRING_TOKEN && seen.length === 1 && path.isAbsolute(seen[0] ?? ''), 'a keyring login is read with the PC gh.exe, by absolute path', JSON.stringify([keyring, seen]));
|
|
326
|
+
t.check(readPcGhLogin({ env: { ...pcEnv, ProgramFiles: exeDir }, run: () => 'not a token' }) === null, 'whatever gh prints must look like a token');
|
|
327
|
+
const pcConfig = f.makeConfig();
|
|
328
|
+
pcConfig.github.usePcLogin = true;
|
|
329
|
+
const pcStore = new GithubToken(pcConfig.storage.root, SecretStore.memory());
|
|
330
|
+
const pcNoLogin = prepareGithubToken(pcConfig, pcConfig.storage.root, pcStore, [], 0, {
|
|
331
|
+
env: { GH_CONFIG_DIR: t.dir('github-pc-empty') },
|
|
332
|
+
});
|
|
333
|
+
t.equal(pcNoLogin, { notes: [] }, 'on, but the PC has no gh login: nothing is delivered, nothing is said');
|
|
334
|
+
fs.writeFileSync(path.join(pcDir, 'hosts.yml'), hosts(PC_TOKEN));
|
|
335
|
+
const pcEnvOnly = { env: { GH_CONFIG_DIR: pcDir } };
|
|
336
|
+
const pcOn = prepareGithubToken(pcConfig, pcConfig.storage.root, pcStore, [], 0, pcEnvOnly);
|
|
337
|
+
t.check(pcOn.token === PC_TOKEN && pcOn.notes.length === 1 && !JSON.stringify(pcOn.notes).includes(PC_TOKEN), 'on with a gh login: delivered, and the notes never hold it');
|
|
338
|
+
pcConfig.github.usePcLogin = false;
|
|
339
|
+
t.equal(prepareGithubToken(pcConfig, pcConfig.storage.root, pcStore, [], 0, pcEnvOnly), { notes: [] }, 'off: the PC login is not even read');
|
|
340
|
+
pcConfig.github.usePcLogin = true;
|
|
341
|
+
pcConfig.github.shareToken = true;
|
|
342
|
+
pcStore.save(FINE);
|
|
343
|
+
t.check(prepareGithubToken(pcConfig, pcConfig.storage.root, pcStore, [], 0, pcEnvOnly).token === FINE, 'a token saved for sandboxes wins over the PC login');
|
|
344
|
+
pcStore.remove();
|
|
345
|
+
t.check(prepareGithubToken(pcConfig, pcConfig.storage.root, pcStore, [], 0, pcEnvOnly).token === PC_TOKEN, 'with none saved, the PC login is used');
|
|
346
|
+
pcConfig.provisioning.git.access = 'readonly';
|
|
347
|
+
const pcStrict = prepareGithubToken(pcConfig, pcConfig.storage.root, pcStore, [], 0, pcEnvOnly);
|
|
348
|
+
t.check(pcStrict.token === undefined &&
|
|
349
|
+
!githubTokenAllowed(pcConfig) &&
|
|
350
|
+
pcStrict.notes.some((n) => n.includes('"readonly"')), 'git access readonly: the PC login is not handed over');
|
|
351
|
+
const pcSecrets = SecretsFileSchema.safeParse({ githubToken: PC_TOKEN });
|
|
352
|
+
t.check(pcSecrets.success, 'an OAuth token from the PC passes the secrets file limits');
|
|
353
|
+
t.check(DEFAULTS.github.usePcLogin === false, 'the PC login is off by default');
|
|
291
354
|
t.section('errors with an action');
|
|
292
355
|
const fixable = new UserError('Broke.', 'Do it.', { op: 'sandbox.stop', label: 'Stop it', cli: 'sandbox stop' });
|
|
293
356
|
const wire = toWireError(fixable);
|
|
@@ -8,7 +8,7 @@ import { DEFAULTS } from '../core/config/index.js';
|
|
|
8
8
|
import { isWin } from '../core/util/index.js';
|
|
9
9
|
import { SYSTEM } from '../shared/system.js';
|
|
10
10
|
import { GuestRequestSchema, PressureStatusSchema } from '../shared/channel/index.js';
|
|
11
|
-
import { compilersAllowed, DIAL_CALM_SAMPLES, DIAL_STEPS, HOST_THRESHOLDS, buildJobs, cargoJobs, linkSlotsFor, usableCpus, effectiveCpuLimit, gentleCpuLimit, maxCompilers, chooseTargetDir, } from '../shared/memory/index.js';
|
|
11
|
+
import { compilersAllowed, DIAL_CALM_SAMPLES, DIAL_STEPS, HOST_THRESHOLDS, buildJobs, cargoJobs, linkSlotsFor, usableCpus, effectiveCpuLimit, gentleCpuLimit, maxCompilers, chooseTargetDir, cargoLinkerVar, } from '../shared/memory/index.js';
|
|
12
12
|
import { HeartbeatSchema } from '../shared/channel/index.js';
|
|
13
13
|
import { heartbeatLine, lastEndOf, vanishedCause, stopWarning } from '../host/life/index.js';
|
|
14
14
|
import { cappedMemoryMb, decideMemory, memoryNote, residentCapMb, residentNote, resolveMemoryMb, } from '../host/launch/index.js';
|
|
@@ -240,6 +240,8 @@ export const heavyLoadSuite = {
|
|
|
240
240
|
chooseTargetDir('auto', 10 * 1024 ** 3).targetDir === 'separate' &&
|
|
241
241
|
Boolean(chooseTargetDir('auto', 10 * 1024 ** 3).warning) &&
|
|
242
242
|
chooseTargetDir('shared', 1).targetDir === 'shared', 'targetDir "auto": local with 25 GB free on C:, else separate with a warning; other values stay');
|
|
243
|
+
t.check(cargoLinkerVar('x86_64-pc-windows-msvc') === 'CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_LINKER' &&
|
|
244
|
+
cargoLinkerVar('aarch64-pc-windows-msvc') === 'CARGO_TARGET_AARCH64_PC_WINDOWS_MSVC_LINKER', 'cargo linker variable names the host triple (the msvc host links with lld, not link.exe)');
|
|
243
245
|
const beat = {
|
|
244
246
|
at: '2026-11-02T21:31:00.000Z',
|
|
245
247
|
uptimeS: 2800,
|
|
@@ -8,7 +8,7 @@ import { assertSafeMapping } from '../core/safe-paths.js';
|
|
|
8
8
|
import { safeText } from '../core/util/index.js';
|
|
9
9
|
import { isSafeFileName } from '../shared/paths/index.js';
|
|
10
10
|
import { removeRefusal, safeRemove } from '../shared/io/index.js';
|
|
11
|
-
import { writeJsonAtomic } from '../shared/io/index.js';
|
|
11
|
+
import { rotateLog, writeJsonAtomic } from '../shared/io/index.js';
|
|
12
12
|
export const hostileInputSuite = {
|
|
13
13
|
name: 'hostile-input',
|
|
14
14
|
title: 'file names, deletes, printed text, mappings and state files from an untrusted sandbox',
|
|
@@ -63,5 +63,15 @@ export const hostileInputSuite = {
|
|
|
63
63
|
fs.writeFileSync(path.join(repoDir, '.sandbox-config.json'), '{"readOnly": true}' + ' '.repeat(1100 * 1024));
|
|
64
64
|
await t.throws(() => f.run({ cwd: repoDir, trustConfig: true }), 'over 1 MB', 'a .sandbox-config.json over 1 MB is refused before it is parsed');
|
|
65
65
|
fs.rmSync(path.join(repoDir, '.sandbox-config.json'));
|
|
66
|
+
t.section('a log that grows for ever (Headroom appends to one on the data disk)');
|
|
67
|
+
const logFile = t.at('rotate', 'proxy.log');
|
|
68
|
+
fs.mkdirSync(path.dirname(logFile), { recursive: true });
|
|
69
|
+
t.check(!rotateLog(logFile, 10), 'no log yet: nothing to rotate');
|
|
70
|
+
fs.writeFileSync(logFile, 'x'.repeat(10));
|
|
71
|
+
t.check(!rotateLog(logFile, 10) && fs.existsSync(logFile), 'a log at the limit stays');
|
|
72
|
+
fs.writeFileSync(logFile + '.1', 'old');
|
|
73
|
+
fs.writeFileSync(logFile, 'y'.repeat(11));
|
|
74
|
+
t.check(rotateLog(logFile, 10), 'a log over the limit is rotated');
|
|
75
|
+
t.check(!fs.existsSync(logFile) && fs.readFileSync(logFile + '.1', 'utf8') === 'y'.repeat(11), 'it replaces the older copy');
|
|
66
76
|
},
|
|
67
77
|
};
|
package/dist/selftest/index.js
CHANGED
|
@@ -16,11 +16,17 @@ import { persistedInstallsSuite } from './persisted-installs.test.js';
|
|
|
16
16
|
import { pcInstallsSuite } from './pc-installs.test.js';
|
|
17
17
|
import { steamSuite } from './steam.test.js';
|
|
18
18
|
import { enginesSuite } from './engines.test.js';
|
|
19
|
+
import { mixamoSuite } from './mixamo.test.js';
|
|
19
20
|
import { unitySuite } from './unity.test.js';
|
|
21
|
+
import { unitySetupSuite } from './unity-setup.test.js';
|
|
22
|
+
import { unityAccountsSuite } from './unity-accounts.test.js';
|
|
23
|
+
import { unityPageSuite } from './unity-page.test.js';
|
|
24
|
+
import { unityAgentSuite } from './unity-agent.test.js';
|
|
20
25
|
import { fivemSuite } from './fivem.test.js';
|
|
21
26
|
import { androidSuite } from './android.test.js';
|
|
22
27
|
import { mobileSuite } from './mobile.test.js';
|
|
23
28
|
import { agentsWebSuite } from './agents-web.test.js';
|
|
29
|
+
import { storeAppSuite } from './store-app.test.js';
|
|
24
30
|
import { credentialsSuite } from './credentials.test.js';
|
|
25
31
|
import { vpnSuite } from './vpn.test.js';
|
|
26
32
|
import { githubSuite } from './github.test.js';
|
|
@@ -139,11 +145,17 @@ export const SUITES = [
|
|
|
139
145
|
persistedInstallsSuite,
|
|
140
146
|
steamSuite,
|
|
141
147
|
enginesSuite,
|
|
148
|
+
mixamoSuite,
|
|
142
149
|
unitySuite,
|
|
150
|
+
unitySetupSuite,
|
|
151
|
+
unityAccountsSuite,
|
|
152
|
+
unityPageSuite,
|
|
153
|
+
unityAgentSuite,
|
|
143
154
|
fivemSuite,
|
|
144
155
|
androidSuite,
|
|
145
156
|
mobileSuite,
|
|
146
157
|
agentsWebSuite,
|
|
158
|
+
storeAppSuite,
|
|
147
159
|
detectSuite,
|
|
148
160
|
networkSuite,
|
|
149
161
|
personalizationSuite,
|
|
@@ -10,6 +10,8 @@ import { appsOptions, stageTicks } from '../cli/menu/pages/apps-page.js';
|
|
|
10
10
|
import { maintenanceOptions } from '../cli/menu/pages/maintenance-page.js';
|
|
11
11
|
import { sandboxOptions } from '../cli/menu/pages/sandbox-page.js';
|
|
12
12
|
import { sessionsOptions } from '../cli/menu/pages/sessions-page.js';
|
|
13
|
+
import { UNREAL_SAMPLE } from './support/unreal-sample.js';
|
|
14
|
+
import { UNITY_SAMPLE } from './support/unity-sample.js';
|
|
13
15
|
import { settingsOf, settingsOptions } from '../cli/menu/pages/settings-page.js';
|
|
14
16
|
import { CATEGORIES } from '../app/features/settings/index.js';
|
|
15
17
|
import { agentsOptions } from '../cli/menu/pages/agents-page.js';
|
|
@@ -199,6 +201,8 @@ export function renderScreens(dir) {
|
|
|
199
201
|
{ name: 'redis', running: false, port: 6379, title: 'Redis' },
|
|
200
202
|
],
|
|
201
203
|
defaultAgent: 'claude',
|
|
204
|
+
unreal: UNREAL_SAMPLE,
|
|
205
|
+
unity: UNITY_SAMPLE,
|
|
202
206
|
};
|
|
203
207
|
const stopped = {
|
|
204
208
|
...sandbox,
|
|
@@ -399,7 +403,7 @@ export function renderScreens(dir) {
|
|
|
399
403
|
out.push(block('1 Agents · Start', draw(0, { ...agentsOptions(agentsModel, API_FOLDER), startCategory: 'start' }, size, status), size), block('1 Agents · Options (navigation focused)', draw(0, { ...agentsOptions(agentsModel, API_FOLDER), startCategory: 'options' }, size, status, 0, true), size), block('1 Agents · On this PC', draw(0, { ...agentsOptions(agentsModel, API_FOLDER), startCategory: 'host' }, size, status), size), block('2 Sessions', draw(1, sessionsOptions({ sandboxRunning: true, sessions: SESSIONS, defaultAgent: 'claude' }), size, status), size), block('2 Sessions (no sandbox)', draw(1, sessionsOptions({ sandboxRunning: false, sessions: [], defaultAgent: 'claude' }), size), size));
|
|
400
404
|
for (const category of ['status', 'databases'])
|
|
401
405
|
out.push(block('3 Sandbox (stopped) · ' + category, draw(2, { ...sandboxOptions(stopped), startCategory: category }, size), size));
|
|
402
|
-
for (const category of ['status', 'projects', 'databases'])
|
|
406
|
+
for (const category of ['status', 'projects', 'databases', 'unreal', 'unity'])
|
|
403
407
|
out.push(block('3 Sandbox · ' + category, draw(2, { ...sandboxOptions(sandbox), startCategory: category }, size, status), size));
|
|
404
408
|
out.push(block('4 Apps (1 unsaved)', draw(3, appsOptions(user, appsData, appsDraft, 'user'), size, status, appsDraft.count()), size));
|
|
405
409
|
for (const category of CATEGORIES)
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// Mixamo: the browser policy table (downloads folder, no GPU) and the guide's Mixamo lines and editor names.
|
|
2
|
+
import { browserPolicies } from '../shared/browser-policy.js';
|
|
3
|
+
import { unrealGuideLines } from '../shared/unreal/guide.js';
|
|
4
|
+
const DOWNLOADS = 'C:\\AgentData\\downloads';
|
|
5
|
+
const PROJECT = { workspace: 'C:\\P\\Game', uproject: 'C:\\P\\Game\\Game.uproject' };
|
|
6
|
+
function statusWith(version) {
|
|
7
|
+
return {
|
|
8
|
+
updatedAt: 'now',
|
|
9
|
+
engines: [
|
|
10
|
+
{
|
|
11
|
+
id: 'UE_' + version,
|
|
12
|
+
dir: 'C:\\E',
|
|
13
|
+
version,
|
|
14
|
+
build: 'installed',
|
|
15
|
+
origin: 'pc',
|
|
16
|
+
writable: true,
|
|
17
|
+
sizeBytes: null,
|
|
18
|
+
},
|
|
19
|
+
],
|
|
20
|
+
projects: [{ workspace: PROJECT.workspace, engine: 'C:\\E', reason: 'test' }],
|
|
21
|
+
launcher: { installed: false, installLocation: null, problem: null, freeBytes: null, needBytes: 1 },
|
|
22
|
+
visualStudio: { version: '17.14', path: 'C:/VS', how: 'kept', check: 'ok', ok: true },
|
|
23
|
+
debugger: null,
|
|
24
|
+
ddc: { mode: 'persisted', dir: null },
|
|
25
|
+
vaults: [],
|
|
26
|
+
mcpUrl: null,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
export const mixamoSuite = {
|
|
30
|
+
name: 'mixamo',
|
|
31
|
+
title: 'Mixamo: browser downloads folder and the guide lines for bringing characters into Unreal',
|
|
32
|
+
run: async (t) => {
|
|
33
|
+
t.section('browser policies');
|
|
34
|
+
const four = browserPolicies({ downloads: DOWNLOADS });
|
|
35
|
+
t.equal(four.length, 4, 'two browsers, two policies each');
|
|
36
|
+
t.equal(four.filter((p) => p.name === 'DefaultDownloadDirectory').map((p) => p.data), [DOWNLOADS, DOWNLOADS], 'both browsers download to the data disk');
|
|
37
|
+
t.equal(browserPolicies({ downloads: null }).length, 2, 'no folder: only the GPU policy');
|
|
38
|
+
await t.throws(() => browserPolicies({ downloads: 'C:\\a"b' }), 'not a folder', 'a quote in the folder is refused');
|
|
39
|
+
await t.throws(() => browserPolicies({ downloads: 'downloads' }), 'not a folder', 'a relative folder is refused');
|
|
40
|
+
t.section('the guide');
|
|
41
|
+
const on = { mixamo: { downloads: DOWNLOADS, script: null } };
|
|
42
|
+
const guide = unrealGuideLines(statusWith('5.4.4'), [PROJECT], on).join('\n');
|
|
43
|
+
t.includes(guide, 'https://www.mixamo.com', 'the Mixamo line is there');
|
|
44
|
+
t.includes(guide, DOWNLOADS, 'it names the downloads folder');
|
|
45
|
+
t.includes(guide, 'Mixamo into Unreal', 'and the import line');
|
|
46
|
+
t.excludes(guide, 'Mixamo import script', 'no script line without a script');
|
|
47
|
+
const withScript = unrealGuideLines(statusWith('5.4.4'), [PROJECT], {
|
|
48
|
+
mixamo: { downloads: DOWNLOADS, script: 'C:\\x\\import_mixamo.py' },
|
|
49
|
+
}).join('\n');
|
|
50
|
+
t.includes(withScript, 'Mixamo import script', 'a script adds its line');
|
|
51
|
+
t.excludes(unrealGuideLines(statusWith('5.4.4'), [PROJECT]).join('\n'), 'Mixamo', 'off by default');
|
|
52
|
+
const noEngine = { ...statusWith('5.4.4'), engines: [] };
|
|
53
|
+
t.excludes(unrealGuideLines(noEngine, [PROJECT], on).join('\n'), 'Mixamo', 'no engine: no Mixamo lines');
|
|
54
|
+
t.section('UE4 names');
|
|
55
|
+
const ue4 = unrealGuideLines(statusWith('4.27.2'), [PROJECT]).join('\n');
|
|
56
|
+
t.includes(ue4, 'UE4Editor-Cmd.exe', 'a UE4 engine uses UE4Editor-Cmd.exe');
|
|
57
|
+
t.excludes(ue4, 'UnrealEditor', 'and not the UE5 names');
|
|
58
|
+
t.includes(unrealGuideLines(statusWith('5.4.4'), [PROJECT]).join('\n'), 'UnrealEditor-Cmd.exe', 'UE5 keeps its names');
|
|
59
|
+
},
|
|
60
|
+
};
|
|
@@ -309,7 +309,7 @@ export const persistedInstallsSuite = {
|
|
|
309
309
|
const found = decideToolchains(everything, ['ProjectVersion.txt', 'project.godot', 'fxmanifest.lua'], ['unity']);
|
|
310
310
|
t.check(NOT_READY.toolchains.every((n) => !found[n].on), '"auto" never turns on a toolchain that is not written yet, whatever the project holds');
|
|
311
311
|
const shown = settingsOf(ConfigFile.user(t.at('settings', 'config.json'))).map((r) => r.key.join('.'));
|
|
312
|
-
t.check(!shown.some((k) => /^toolchains\.(
|
|
312
|
+
t.check(!shown.some((k) => /^toolchains\.(godot|android|flutter|dart|reactNative)\./.test(k)) &&
|
|
313
313
|
shown.includes('toolchains.unreal.visualStudio') &&
|
|
314
314
|
shown.some((k) => k.startsWith('toolchains.fivem.')), 'settings do not list the toolchains that are not written yet');
|
|
315
315
|
},
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
// What reaches the sandbox from this PC's Claude files, and what is stripped first: API keys, cloud credentials, tokens
|
|
2
2
|
// in MCP server definitions. Also the secret-looking files in a workspace that the launch warns about.
|
|
3
3
|
import path from 'node:path';
|
|
4
|
-
import
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import { DEFAULTS } from '../core/config/index.js';
|
|
6
|
+
import { findSecretFiles, pluginFilter, pluginTrimLevel, sandboxGlobalState, stageClaudeProfile, stripMcpServers, stripSettings, } from '../host/profile/index.js';
|
|
5
7
|
const REMOVED = '[removed by agent-sandbox]';
|
|
6
8
|
export const secretsSuite = {
|
|
7
9
|
name: 'secrets',
|
|
@@ -111,5 +113,38 @@ export const secretsSuite = {
|
|
|
111
113
|
])
|
|
112
114
|
t.write(path.join(workspace, file), 'x');
|
|
113
115
|
t.equal(findSecretFiles(workspace).sort(), ['.env', 'api\\credentials.json', 'server.pem'], 'secret-looking workspace files are found for the launch warning (templates and dependencies ignored)');
|
|
116
|
+
t.section('plugins staged from this PC');
|
|
117
|
+
const hostClaude = t.at('plugins-host');
|
|
118
|
+
const plugins = path.join(hostClaude, 'plugins');
|
|
119
|
+
t.write(path.join(plugins, 'installed_plugins.json'), '{"version":2,"plugins":{}}');
|
|
120
|
+
t.write(path.join(plugins, 'known_marketplaces.json'), '{}');
|
|
121
|
+
t.write(path.join(plugins, 'cache', 'm', 'p', '1.0', 'plugin.json'), '{}');
|
|
122
|
+
t.write(path.join(plugins, 'cache', 'm', 'p', '1.0', 'node_modules', 'dep', 'index.js'), 'x');
|
|
123
|
+
t.write(path.join(plugins, 'cache', 'temp_git_1_abc', 'junk.txt'), 'x');
|
|
124
|
+
t.write(path.join(plugins, 'marketplaces', 'm', 'marketplace.json'), '{}');
|
|
125
|
+
t.write(path.join(plugins, 'marketplaces', 'm', '.git', 'HEAD'), 'ref');
|
|
126
|
+
t.write(path.join(plugins, 'marketplaces', 'm.backup', 'old.txt'), 'x');
|
|
127
|
+
t.write(path.join(plugins, 'marketplaces', 'temp_subdir_2.clone', 'junk.txt'), 'x');
|
|
128
|
+
t.write(path.join(hostClaude, 'settings.json'), '{"enabledPlugins":{"p@m":true}}');
|
|
129
|
+
const launch = t.at('plugins-launch');
|
|
130
|
+
const profile = { ...DEFAULTS.claudeProfile, hostDir: hostClaude, globalStateFile: '' };
|
|
131
|
+
const staged = stageClaudeProfile(profile, launch, 'D:/Code/app', DEFAULTS.secrets);
|
|
132
|
+
const out = path.join(launch, 'claude-profile', 'plugins');
|
|
133
|
+
t.check(staged.staged.includes('plugins'), 'plugins are staged');
|
|
134
|
+
t.check(fs.existsSync(path.join(out, 'cache', 'm', 'p', '1.0', 'plugin.json')) &&
|
|
135
|
+
fs.existsSync(path.join(out, 'marketplaces', 'm', 'marketplace.json')) &&
|
|
136
|
+
fs.existsSync(path.join(out, 'marketplaces', 'm', '.git', 'HEAD')) &&
|
|
137
|
+
fs.existsSync(path.join(out, 'cache', 'm', 'p', '1.0', 'node_modules', 'dep', 'index.js')), 'the plugin cache, marketplaces and (when they fit) their git folders and dependencies are copied');
|
|
138
|
+
t.check(!fs.existsSync(path.join(out, 'cache', 'temp_git_1_abc')) &&
|
|
139
|
+
!fs.existsSync(path.join(out, 'marketplaces', 'm.backup')) &&
|
|
140
|
+
!fs.existsSync(path.join(out, 'marketplaces', 'temp_subdir_2.clone')), 'interrupted clones, backups and staging folders of plugins are left out');
|
|
141
|
+
t.check(pluginTrimLevel(plugins) === 0, 'a plugins folder under the cap is not trimmed');
|
|
142
|
+
const git = path.join(plugins, 'marketplaces', 'm', '.git');
|
|
143
|
+
const deps = path.join(plugins, 'cache', 'm', 'p', '1.0', 'node_modules');
|
|
144
|
+
t.check(pluginFilter(plugins, 0)(git) &&
|
|
145
|
+
!pluginFilter(plugins, 1)(git) &&
|
|
146
|
+
pluginFilter(plugins, 1)(deps) &&
|
|
147
|
+
!pluginFilter(plugins, 2)(deps), 'a plugins folder over the cap loses git history first, then node_modules');
|
|
148
|
+
t.check(fs.readFileSync(path.join(launch, 'claude-profile', 'settings.json'), 'utf8').includes('p@m'), 'enabledPlugins reach the sandbox');
|
|
114
149
|
},
|
|
115
150
|
};
|
|
@@ -274,7 +274,7 @@ export const serviceAccountsSuite = {
|
|
|
274
274
|
process.env.LOCALAPPDATA = oldLocal;
|
|
275
275
|
}
|
|
276
276
|
t.section('the account command');
|
|
277
|
-
t.check(!COMMANDS.some((c) => ['epic', 'steam'
|
|
277
|
+
t.check(!COMMANDS.some((c) => ['epic', 'steam'].includes(c.name)), 'no command of its own for a service');
|
|
278
278
|
const account = buildProgram(() => undefined).commands.find((c) => c.name() === 'account');
|
|
279
279
|
const help = account.helpInformation();
|
|
280
280
|
t.check(['status', 'import', 'login', 'logout', '--from', '--yes'].every((w) => help.includes(w)), 'sandbox account --help lists status, import, login, logout, --from and --yes', help);
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
// The Perplexity desktop app, a Microsoft Store package: the catalogue entry, the sign-in kind (sandbox only, like
|
|
2
|
+
// Comet), the package manifest, the PC's fetch and staging with a fake Store tool (no download, nothing run), the
|
|
3
|
+
// sandbox's check of the kept files and the shortcut's command line. Temp folders only.
|
|
4
|
+
import crypto from 'node:crypto';
|
|
5
|
+
import fs from 'node:fs';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import { DEFAULTS } from '../core/config/index.js';
|
|
8
|
+
import { OFF_IS_SAFER } from '../core/local-config/index.js';
|
|
9
|
+
import { HOST_SOURCES } from '../host/accounts/index.js';
|
|
10
|
+
import { prepareStorePackages, stagedPackageDir, StorePackages } from '../host/apps/index.js';
|
|
11
|
+
import { checkedPackage } from '../guest/apps/store-check.js';
|
|
12
|
+
import { peekPendingPerplexityApp, perplexityAppPendingFile } from '../guest/accounts/kinds/perplexityapp.js';
|
|
13
|
+
import { perplexityAppShortcutArgs, restoreAppSignIn } from '../guest/browser/perplexity-app.js';
|
|
14
|
+
import { appsToInstall, appById, isStorePackageFile, PERPLEXITY_DESKTOP_FAMILY, PERPLEXITY_DESKTOP_PACKAGE, PERPLEXITY_DESKTOP_PRODUCT_ID, storeApps, StorePackageManifestSchema, storePackageRole, } from '../shared/apps/index.js';
|
|
15
|
+
import { appUsersOn, PAYLOADS, SERVICE_SPECS, serviceWanted, usedByText, } from '../shared/accounts/index.js';
|
|
16
|
+
const MAIN = 'PerplexityAI.PerplexityApp_1.2.3.0_x64__3jh4kjrg4dzr2.msix';
|
|
17
|
+
const FRAMEWORK = 'Microsoft.VCLibs.140.00_14.0.33519.0_x64__8wekyb3d8bbwe.appx';
|
|
18
|
+
const sha = (text) => crypto.createHash('sha256').update(text).digest('hex');
|
|
19
|
+
/** A fake Store tool: writes the given files into -OutDir and answers like get-store-package.ps1. */
|
|
20
|
+
function fakeStore(files, ok = true) {
|
|
21
|
+
let calls = 0;
|
|
22
|
+
const run = (_exe, args) => {
|
|
23
|
+
calls++;
|
|
24
|
+
if (!ok)
|
|
25
|
+
return Promise.resolve({
|
|
26
|
+
status: 0,
|
|
27
|
+
stdout: JSON.stringify({ ok: false, message: 'no Store access', files: [] }),
|
|
28
|
+
stderr: '',
|
|
29
|
+
});
|
|
30
|
+
const out = args[args.indexOf('-OutDir') + 1];
|
|
31
|
+
fs.mkdirSync(out, { recursive: true });
|
|
32
|
+
for (const [name, text] of Object.entries(files))
|
|
33
|
+
if (path.basename(name) === name)
|
|
34
|
+
fs.writeFileSync(path.join(out, name), text);
|
|
35
|
+
return Promise.resolve({
|
|
36
|
+
status: 0,
|
|
37
|
+
stdout: 'progress\n' + JSON.stringify({ ok: true, message: 'ok', files: Object.keys(files) }),
|
|
38
|
+
stderr: '',
|
|
39
|
+
});
|
|
40
|
+
};
|
|
41
|
+
return { run, calls: () => calls };
|
|
42
|
+
}
|
|
43
|
+
export const storeAppSuite = {
|
|
44
|
+
name: 'store-app',
|
|
45
|
+
title: 'Perplexity desktop app: Store package fetched on the PC, installed in the sandbox, sign-in kept like Comet',
|
|
46
|
+
run: async (t) => {
|
|
47
|
+
t.section('the catalogue entry');
|
|
48
|
+
const entry = appById('perplexity-desktop');
|
|
49
|
+
t.check(entry !== undefined &&
|
|
50
|
+
'store' in entry.source &&
|
|
51
|
+
entry.source.store.productId === PERPLEXITY_DESKTOP_PRODUCT_ID &&
|
|
52
|
+
entry.source.store.family === PERPLEXITY_DESKTOP_FAMILY &&
|
|
53
|
+
PERPLEXITY_DESKTOP_FAMILY.startsWith(PERPLEXITY_DESKTOP_PACKAGE + '_') &&
|
|
54
|
+
entry.userOnly === true, 'a Store source with the product and the package family, yours only');
|
|
55
|
+
t.equal(storeApps(['rtk', 'perplexity-desktop']).map((a) => a.productId), ['9P9XG917PWCJ'], 'only Store apps are fetched');
|
|
56
|
+
t.check(appsToInstall({ ...DEFAULTS.apps, install: ['perplexity-desktop'] }, 'none').entries.length === 1, 'it needs no 7-Zip and no other app');
|
|
57
|
+
t.section('the sign-in kind');
|
|
58
|
+
const spec = SERVICE_SPECS.perplexityapp;
|
|
59
|
+
t.check(spec.sources.join() === 'sandbox' &&
|
|
60
|
+
spec.returns &&
|
|
61
|
+
!spec.singleHolder &&
|
|
62
|
+
DEFAULTS.accounts.perplexityapp.share === false &&
|
|
63
|
+
OFF_IS_SAFER.includes('accounts.perplexityapp.share') &&
|
|
64
|
+
spec.warnings.some((w) => w.includes("can't be copied from this PC")), 'only from a sandbox, handed back, not shared by default, a project file can only turn it off');
|
|
65
|
+
const source = HOST_SOURCES.perplexityapp;
|
|
66
|
+
t.check(!source.fromPc && !source.fromFile && !source.fromAnswers, 'nothing is read from this PC (the cookies are sealed to it)');
|
|
67
|
+
t.check(appUsersOn(['perplexity-desktop']).join() === 'perplexityapp' &&
|
|
68
|
+
serviceWanted('perplexityapp', new Set(['perplexityapp'])) &&
|
|
69
|
+
!serviceWanted('perplexityapp', new Set(['comet'])) &&
|
|
70
|
+
usedByText(['perplexityapp']) === 'the Perplexity desktop app', 'handed over only where the app is on');
|
|
71
|
+
const cookie = { name: 's', value: 'v', domain: '.perplexity.ai', path: '/', secure: true, httpOnly: true };
|
|
72
|
+
t.check(PAYLOADS.perplexityapp.safeParse({ cookies: [cookie] }).success &&
|
|
73
|
+
!PAYLOADS.perplexityapp.safeParse({ cookies: [{ ...cookie, domain: 'example.com' }] }).success, 'the payload is the perplexity.ai cookie allow-list');
|
|
74
|
+
const pendingRoot = t.dir('pending');
|
|
75
|
+
t.write(perplexityAppPendingFile(pendingRoot), JSON.stringify({ cookies: [cookie] }));
|
|
76
|
+
t.check(peekPendingPerplexityApp(pendingRoot)?.cookies.length === 1, 'a waiting sign-in is read and left in place');
|
|
77
|
+
t.write(perplexityAppPendingFile(pendingRoot), '{"cookies":[{"domain":"example.com"}]}');
|
|
78
|
+
t.check(peekPendingPerplexityApp(pendingRoot) === null, 'one that is not the allow-listed shape is not used');
|
|
79
|
+
t.check(!(await restoreAppSignIn({ cookies: [{ ...cookie, expires: 4_000_000_000 }] }, t.dir('no-app-profile'))), 'with the app not running, nothing is written');
|
|
80
|
+
t.section('the package manifest');
|
|
81
|
+
const file = (name, role) => ({ name, role, sha256: sha(name), bytes: 10 });
|
|
82
|
+
const manifest = (files) => ({
|
|
83
|
+
productId: '9P9XG917PWCJ',
|
|
84
|
+
fetchedAt: '2026-01-01T00:00:00Z',
|
|
85
|
+
files,
|
|
86
|
+
});
|
|
87
|
+
t.check(StorePackageManifestSchema.safeParse(manifest([file(MAIN, 'main'), file(FRAMEWORK, 'dependency')])).success &&
|
|
88
|
+
!StorePackageManifestSchema.safeParse(manifest([file(FRAMEWORK, 'dependency')])).success &&
|
|
89
|
+
!StorePackageManifestSchema.safeParse(manifest([file(MAIN, 'main'), file(MAIN.toUpperCase().replace('.MSIX', '.msix'), 'main')])).success &&
|
|
90
|
+
!StorePackageManifestSchema.safeParse(manifest([file('..\\evil.msix', 'main')])).success &&
|
|
91
|
+
!StorePackageManifestSchema.safeParse(manifest([file('x.exe', 'main')])).success, 'exactly one app file, and only .msix/.appx names (no paths, no programs)');
|
|
92
|
+
t.check(storePackageRole(MAIN, PERPLEXITY_DESKTOP_PACKAGE) === 'main' &&
|
|
93
|
+
storePackageRole(FRAMEWORK, PERPLEXITY_DESKTOP_PACKAGE) === 'dependency' &&
|
|
94
|
+
isStorePackageFile(MAIN) &&
|
|
95
|
+
!isStorePackageFile('a/b.msix'), 'the app is told from its frameworks by name');
|
|
96
|
+
t.section('this PC fetches and stages the package');
|
|
97
|
+
const storageRoot = t.dir('pc', 'storage');
|
|
98
|
+
const dataPath = t.dir('pc', 'storage', 'data', 'set');
|
|
99
|
+
const app = {
|
|
100
|
+
id: 'perplexity-desktop',
|
|
101
|
+
productId: PERPLEXITY_DESKTOP_PRODUCT_ID,
|
|
102
|
+
packageName: PERPLEXITY_DESKTOP_PACKAGE,
|
|
103
|
+
};
|
|
104
|
+
const store = fakeStore({
|
|
105
|
+
[MAIN]: 'app-bytes',
|
|
106
|
+
[FRAMEWORK]: 'framework-bytes',
|
|
107
|
+
'..evil.txt': 'x',
|
|
108
|
+
'../escape.msix': 'x',
|
|
109
|
+
});
|
|
110
|
+
let now = Date.parse('2026-06-01T00:00:00Z');
|
|
111
|
+
const packages = new StorePackages({ storageRoot, dataPath, run: store.run, now: () => now });
|
|
112
|
+
const first = await packages.ensure(app);
|
|
113
|
+
t.check(first.note === 'downloaded' &&
|
|
114
|
+
first.manifest.files.length === 2 &&
|
|
115
|
+
first.manifest.files.find((f) => f.role === 'main')?.sha256 === sha('app-bytes') &&
|
|
116
|
+
packages.cached(app.productId) !== null &&
|
|
117
|
+
!fs.existsSync(packages.cacheDir(app.productId) + '.new'), "the files are hashed here, not taken on the script's word; names that are not packages are ignored");
|
|
118
|
+
const second = await packages.ensure(app);
|
|
119
|
+
t.check(store.calls() === 1 && second.note.startsWith('kept'), 'a fresh cache is not fetched again');
|
|
120
|
+
const staged = packages.stage(app, first.manifest);
|
|
121
|
+
t.check(staged === stagedPackageDir(dataPath, app.id) &&
|
|
122
|
+
fs.readFileSync(path.join(staged, MAIN), 'utf8') === 'app-bytes' &&
|
|
123
|
+
fs.existsSync(path.join(staged, 'store-package.json')), 'the package is copied into the data folder with its manifest');
|
|
124
|
+
fs.writeFileSync(path.join(staged, 'stale.msix'), 'old');
|
|
125
|
+
packages.stage(app, first.manifest);
|
|
126
|
+
t.check(!fs.existsSync(path.join(staged, 'stale.msix')), 'a file the manifest does not list is removed');
|
|
127
|
+
t.section('an old cache and a failing Store');
|
|
128
|
+
now += 40 * 24 * 3600 * 1000;
|
|
129
|
+
const failing = new StorePackages({ storageRoot, dataPath, run: fakeStore({}, false).run, now: () => now });
|
|
130
|
+
const kept = await failing.ensure(app);
|
|
131
|
+
t.check(kept.note.includes('could not be fetched') && kept.note.includes('no Store access'), 'the old package stands, and says why');
|
|
132
|
+
const emptyRoot = t.dir('pc2', 'storage');
|
|
133
|
+
const emptyData = t.dir('pc2', 'storage', 'data', 'set');
|
|
134
|
+
const outcome = await prepareStorePackages([app], {
|
|
135
|
+
storageRoot: emptyRoot,
|
|
136
|
+
dataPath: emptyData,
|
|
137
|
+
run: fakeStore({}, false).run,
|
|
138
|
+
});
|
|
139
|
+
t.check(outcome.notes.length === 1 &&
|
|
140
|
+
outcome.notes[0].includes('is not prepared') &&
|
|
141
|
+
outcome.notes[0].includes('winget download --id 9P9XG917PWCJ'), 'with nothing kept the launch goes on, with a note that says what to fix');
|
|
142
|
+
const good = await prepareStorePackages([app], {
|
|
143
|
+
storageRoot: emptyRoot,
|
|
144
|
+
dataPath: emptyData,
|
|
145
|
+
run: fakeStore({ [MAIN]: 'a' }).run,
|
|
146
|
+
});
|
|
147
|
+
t.check(good.notes[0].includes('downloaded') && fs.existsSync(path.join(stagedPackageDir(emptyData, app.id), MAIN)), 'and a working Store stages it');
|
|
148
|
+
t.section('the sandbox checks the kept files');
|
|
149
|
+
const checked = await checkedPackage(staged);
|
|
150
|
+
t.check(path.basename(checked.main) === MAIN && checked.dependencies.map((d) => path.basename(d)).join() === FRAMEWORK, 'the app and its frameworks, in order');
|
|
151
|
+
fs.writeFileSync(path.join(staged, FRAMEWORK), 'tampered');
|
|
152
|
+
t.check((await t.thrownMessage(() => checkedPackage(staged))).includes('missing or changed'), 'a changed file stops the install');
|
|
153
|
+
t.check((await t.thrownMessage(() => checkedPackage(t.dir('nothing')))).includes('not handed over'), 'no package says so');
|
|
154
|
+
t.section('the shortcut');
|
|
155
|
+
const args = perplexityAppShortcutArgs('C:\\AgentLaunch\\sandbox\\scripts\\start-store-app.ps1', 'C:\\Users\\X\\profile');
|
|
156
|
+
t.check(args.includes('-File "C:\\AgentLaunch\\sandbox\\scripts\\start-store-app.ps1"') &&
|
|
157
|
+
args.includes('-PackageName PerplexityAI.PerplexityApp') &&
|
|
158
|
+
args.includes('-UserDataDir "C:\\Users\\X\\profile"') &&
|
|
159
|
+
args.includes('-Port 0') &&
|
|
160
|
+
args.includes('-WindowStyle Hidden'), 'a hidden PowerShell start with the package, its own profile and a free DevTools port');
|
|
161
|
+
},
|
|
162
|
+
};
|
|
@@ -44,10 +44,14 @@ export const LAUNCH_FILES_GOLDEN = [
|
|
|
44
44
|
'sandbox/addons/context7/package.json',
|
|
45
45
|
'sandbox/addons/headroom/requirements.in',
|
|
46
46
|
'sandbox/addons/headroom/requirements.txt',
|
|
47
|
+
'sandbox/addons/openupm-cli/package-lock.json',
|
|
48
|
+
'sandbox/addons/openupm-cli/package.json',
|
|
47
49
|
'sandbox/addons/perplexity-mcp/package-lock.json',
|
|
48
50
|
'sandbox/addons/perplexity-mcp/package.json',
|
|
49
51
|
'sandbox/addons/playwright-mcp/package-lock.json',
|
|
50
52
|
'sandbox/addons/playwright-mcp/package.json',
|
|
53
|
+
'sandbox/addons/unity-mcp/requirements.in',
|
|
54
|
+
'sandbox/addons/unity-mcp/requirements.txt',
|
|
51
55
|
'sandbox/boot/bootstrap.ps1',
|
|
52
56
|
'sandbox/boot/bootstrap/drives.ps1',
|
|
53
57
|
'sandbox/boot/bootstrap/files.ps1',
|
|
@@ -55,6 +59,7 @@ export const LAUNCH_FILES_GOLDEN = [
|
|
|
55
59
|
'sandbox/boot/bootstrap/status.ps1',
|
|
56
60
|
'sandbox/boot/logon.cmd',
|
|
57
61
|
'sandbox/boot/trust.cmd',
|
|
62
|
+
'sandbox/csharp/AppActivation.cs',
|
|
58
63
|
'sandbox/csharp/BindMount.cs',
|
|
59
64
|
'sandbox/csharp/CargoShim.cs',
|
|
60
65
|
'sandbox/csharp/Desktop.cs',
|
|
@@ -69,6 +74,7 @@ export const LAUNCH_FILES_GOLDEN = [
|
|
|
69
74
|
'sandbox/csharp/SandboxTools/MemoryLog.cs',
|
|
70
75
|
'sandbox/csharp/SandboxTools/PageFile.cs',
|
|
71
76
|
'sandbox/csharp/SandboxTools/Program.cs',
|
|
77
|
+
'sandbox/csharp/VsSetup.cs',
|
|
72
78
|
'sandbox/csharp/VsWhere.cs',
|
|
73
79
|
'sandbox/csharp/VtInput.cs',
|
|
74
80
|
'sandbox/scripts/add-firewall-port.ps1',
|
|
@@ -76,7 +82,9 @@ export const LAUNCH_FILES_GOLDEN = [
|
|
|
76
82
|
'sandbox/scripts/expand-archive.ps1',
|
|
77
83
|
'sandbox/scripts/get-gpu-names.ps1',
|
|
78
84
|
'sandbox/scripts/get-signature.ps1',
|
|
85
|
+
'sandbox/scripts/get-vs-setup.ps1',
|
|
79
86
|
'sandbox/scripts/install-shell-path.ps1',
|
|
87
|
+
'sandbox/scripts/install-store-package.ps1',
|
|
80
88
|
'sandbox/scripts/link-profile-dirs.ps1',
|
|
81
89
|
'sandbox/scripts/new-shortcuts.ps1',
|
|
82
90
|
'sandbox/scripts/repair-wmi-services.ps1',
|
|
@@ -85,6 +93,8 @@ export const LAUNCH_FILES_GOLDEN = [
|
|
|
85
93
|
'sandbox/scripts/set-desktop.ps1',
|
|
86
94
|
'sandbox/scripts/set-dns-servers.ps1',
|
|
87
95
|
'sandbox/scripts/set-machine-environment.ps1',
|
|
96
|
+
'sandbox/scripts/set-registry-values.ps1',
|
|
97
|
+
'sandbox/scripts/start-store-app.ps1',
|
|
88
98
|
'sandbox/templates/fivem/README.txt',
|
|
89
99
|
'sandbox/templates/fivem/cs/Client/Client.csproj',
|
|
90
100
|
'sandbox/templates/fivem/cs/Client/ClientMain.cs',
|
|
@@ -106,6 +106,7 @@ export declare function createFixtures(t: Harness): {
|
|
|
106
106
|
};
|
|
107
107
|
github: {
|
|
108
108
|
shareToken: boolean;
|
|
109
|
+
usePcLogin: boolean;
|
|
109
110
|
};
|
|
110
111
|
accounts: {
|
|
111
112
|
epic: {
|
|
@@ -138,12 +139,18 @@ export declare function createFixtures(t: Harness): {
|
|
|
138
139
|
comet: {
|
|
139
140
|
share: boolean;
|
|
140
141
|
};
|
|
142
|
+
perplexityapp: {
|
|
143
|
+
share: boolean;
|
|
144
|
+
};
|
|
141
145
|
};
|
|
142
146
|
pcInstalls: {
|
|
143
147
|
steamGames: boolean | number[];
|
|
144
148
|
epicGames: boolean | string[];
|
|
145
|
-
unrealEngines: boolean | string[];
|
|
146
|
-
|
|
149
|
+
unrealEngines: boolean | string[] | "auto";
|
|
150
|
+
unrealEngineFolders: string[];
|
|
151
|
+
unrealAssets: boolean | "auto";
|
|
152
|
+
unityEditors: boolean | string[] | "auto";
|
|
153
|
+
unityAssets: boolean | "auto";
|
|
147
154
|
};
|
|
148
155
|
provisioning: {
|
|
149
156
|
copyFromHost: boolean;
|
|
@@ -210,6 +217,10 @@ export declare function createFixtures(t: Harness): {
|
|
|
210
217
|
modules: string[];
|
|
211
218
|
hub: boolean;
|
|
212
219
|
keepVersions: number;
|
|
220
|
+
mcp: boolean;
|
|
221
|
+
agentSkills: boolean;
|
|
222
|
+
cliVersion: string;
|
|
223
|
+
assetCache: "pc" | "persisted" | "local";
|
|
213
224
|
};
|
|
214
225
|
godot: {
|
|
215
226
|
enabled: boolean | "auto";
|