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
|
@@ -12,6 +12,8 @@ import { liveFolders } from './live.js';
|
|
|
12
12
|
import { warn } from '../runtime/log.js';
|
|
13
13
|
import { errMsg } from '../../core/util/errors.js';
|
|
14
14
|
import { flushAccounts } from './accounts.js';
|
|
15
|
+
import { captureLauncherEngines } from '../toolchains/engines/unreal-engines.js';
|
|
16
|
+
import { captureUnityEditors } from '../toolchains/engines/unity.js';
|
|
15
17
|
const POLL_MS = 400;
|
|
16
18
|
const MEM_SAVE_MS = 120000;
|
|
17
19
|
export class Daemon {
|
|
@@ -47,6 +49,9 @@ export class Daemon {
|
|
|
47
49
|
safely('claude-mem save', saveClaudeMem);
|
|
48
50
|
safely('agent databases save', saveLocalState);
|
|
49
51
|
void flushAccounts();
|
|
52
|
+
// Engines the Epic Games Launcher installed on the data disk since: kept for the next sandbox.
|
|
53
|
+
void captureLauncherEngines().catch((e) => warn('Unreal launcher save: ' + errMsg(e)));
|
|
54
|
+
void captureUnityEditors().catch((e) => warn('Unity editors: ' + errMsg(e)));
|
|
50
55
|
}
|
|
51
56
|
}
|
|
52
57
|
handle(req) {
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { androidTool } from './android.js';
|
|
2
2
|
import { fivemTool } from './fivem.js';
|
|
3
3
|
import { steamTool } from './steam.js';
|
|
4
|
-
|
|
5
|
-
export const DEVTOOLS = [steamTool, fivemTool, unityTool, androidTool];
|
|
4
|
+
export const DEVTOOLS = [steamTool, fivemTool, androidTool];
|
|
6
5
|
/** The helpers for the toolchains that are on (resolved by the host; "auto" counts as off here). */
|
|
7
6
|
export function devToolsFor(t) {
|
|
8
7
|
return DEVTOOLS.filter((tool) => t[tool.name].enabled === true);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// The sbx-<name> helpers (sbx-steam, sbx-fivem, sbx-
|
|
1
|
+
// The sbx-<name> helpers (sbx-steam, sbx-fivem, sbx-android): `tool.js <name> <args...>` runs that helper.
|
|
2
2
|
import { errMsg } from '../../core/util/errors.js';
|
|
3
3
|
import { DEVTOOLS, devToolByName } from '../devtools/registry.js';
|
|
4
4
|
const [name, ...args] = process.argv.slice(2);
|
|
@@ -1,2 +1,14 @@
|
|
|
1
|
+
import { type McpServerConfig } from '../../shared/apps/index.js';
|
|
2
|
+
/** The Unreal editor's MCP server (Unreal 5.8's ModelContextProtocol plugin), set by the Unreal engines step. */
|
|
3
|
+
export declare const UNREAL_MCP_NAME = "sbx-unreal-editor";
|
|
4
|
+
/** The engines' own MCP servers: not an Apps add-on, set by the engine's step each boot, and sent to Codex too. */
|
|
5
|
+
export declare const ENGINE_MCP_NAMES: readonly ["sbx-unreal-editor", "sbx-unity"];
|
|
6
|
+
/**
|
|
7
|
+
* Adds an engine's MCP server to the next writeAddonMcpServers, or takes it away (null). A server for an editor only
|
|
8
|
+
* answers while the editor runs; until then Claude Code lists it as not connected.
|
|
9
|
+
*/
|
|
10
|
+
export declare function setEngineMcp(name: (typeof ENGINE_MCP_NAMES)[number], server: McpServerConfig | null): void;
|
|
11
|
+
/** Adds the Unreal editor's MCP server (its address) to the next writeAddonMcpServers, or takes it away (null). */
|
|
12
|
+
export declare function setUnrealMcp(url: string | null): void;
|
|
1
13
|
/** Sets the add-ons' MCP servers in the sandbox's .claude.json (and Codex's config.toml); the names that are on. */
|
|
2
14
|
export declare function writeAddonMcpServers(): string[];
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import fs from 'node:fs';
|
|
7
7
|
import path from 'node:path';
|
|
8
8
|
import { addonMcpServer, APPS, codexWithAddonMcpServers, withAddonMcpServers, } from '../../shared/apps/index.js';
|
|
9
|
+
import { UNITY_MCP_NAME } from '../../shared/unity/index.js';
|
|
9
10
|
import { writeJsonAtomic } from '../../shared/io/json.js';
|
|
10
11
|
import { cometExe } from '../browser/comet.js';
|
|
11
12
|
import { exists, mkdirp } from '../runtime/fs.js';
|
|
@@ -13,8 +14,27 @@ import { dryRun } from '../runtime/paths.js';
|
|
|
13
14
|
import { claudeDir, session, stateDir } from '../runtime/session.js';
|
|
14
15
|
import { log, warn } from '../runtime/log.js';
|
|
15
16
|
import { installedMcpAddons } from './agent-addons.js';
|
|
16
|
-
/** The
|
|
17
|
-
const
|
|
17
|
+
/** The Unreal editor's MCP server (Unreal 5.8's ModelContextProtocol plugin), set by the Unreal engines step. */
|
|
18
|
+
export const UNREAL_MCP_NAME = 'sbx-unreal-editor';
|
|
19
|
+
/** The engines' own MCP servers: not an Apps add-on, set by the engine's step each boot, and sent to Codex too. */
|
|
20
|
+
export const ENGINE_MCP_NAMES = [UNREAL_MCP_NAME, UNITY_MCP_NAME];
|
|
21
|
+
const engineMcp = new Map();
|
|
22
|
+
/** The names of every MCP server the sandbox can add (the ones it may remove). */
|
|
23
|
+
const ADDON_MCP_NAMES = [...APPS.flatMap((a) => (a.mcp ? [a.mcp.name] : [])), ...ENGINE_MCP_NAMES];
|
|
24
|
+
/**
|
|
25
|
+
* Adds an engine's MCP server to the next writeAddonMcpServers, or takes it away (null). A server for an editor only
|
|
26
|
+
* answers while the editor runs; until then Claude Code lists it as not connected.
|
|
27
|
+
*/
|
|
28
|
+
export function setEngineMcp(name, server) {
|
|
29
|
+
if (server)
|
|
30
|
+
engineMcp.set(name, server);
|
|
31
|
+
else
|
|
32
|
+
engineMcp.delete(name);
|
|
33
|
+
}
|
|
34
|
+
/** Adds the Unreal editor's MCP server (its address) to the next writeAddonMcpServers, or takes it away (null). */
|
|
35
|
+
export function setUnrealMcp(url) {
|
|
36
|
+
setEngineMcp(UNREAL_MCP_NAME, url ? { type: 'http', url } : null);
|
|
37
|
+
}
|
|
18
38
|
/** Variables only this sandbox can tell an add-on's server (where Comet is). */
|
|
19
39
|
function placeEnv(id) {
|
|
20
40
|
if (id !== 'comet-mcp')
|
|
@@ -34,7 +54,10 @@ function addonServers() {
|
|
|
34
54
|
export function writeAddonMcpServers() {
|
|
35
55
|
const on = addonServers();
|
|
36
56
|
writeCodexAddonMcpServers(on);
|
|
37
|
-
const servers =
|
|
57
|
+
const servers = {
|
|
58
|
+
...Object.fromEntries(on.map(({ mcp, server }) => [mcp.name, server])),
|
|
59
|
+
...Object.fromEntries(engineMcp),
|
|
60
|
+
};
|
|
38
61
|
const file = path.join(claudeDir, '.claude.json');
|
|
39
62
|
let state = {};
|
|
40
63
|
if (fs.existsSync(file)) {
|
|
@@ -72,10 +95,12 @@ function writeCodexAddonMcpServers(on) {
|
|
|
72
95
|
const servers = Object.fromEntries(on
|
|
73
96
|
.filter(({ mcp }) => mcp.codex)
|
|
74
97
|
.map(({ mcp, server }) => [mcp.name, { ...server, ...(mcp.envVars?.length ? { envVars: mcp.envVars } : {}) }]));
|
|
98
|
+
for (const [name, server] of engineMcp)
|
|
99
|
+
servers[name] = server;
|
|
75
100
|
const before = exists(file) ? fs.readFileSync(file, 'utf8') : '';
|
|
76
101
|
if (!before && !Object.keys(servers).length)
|
|
77
102
|
return;
|
|
78
|
-
const known = APPS.flatMap((a) => (a.mcp?.codex ? [a.mcp.name] : []));
|
|
103
|
+
const known = [...APPS.flatMap((a) => (a.mcp?.codex ? [a.mcp.name] : [])), ...ENGINE_MCP_NAMES];
|
|
79
104
|
const result = codexWithAddonMcpServers(before, servers, known);
|
|
80
105
|
for (const name of result.skipped)
|
|
81
106
|
warn('Codex MCP servers from Apps: ' + name + ' is already in config.toml in another form, left as it is');
|
|
@@ -1,18 +1,21 @@
|
|
|
1
|
-
// Browsers in the sandbox
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
1
|
+
// Browsers in the sandbox (Edge and Chrome machine policy, written every boot) draw without the GPU, and download to
|
|
2
|
+
// C:\AgentData\downloads, which is kept between sandboxes. With sandbox.vGpu the sandbox shares this PC's graphics card
|
|
3
|
+
// through one virtual GPU: a browser's GPU process starting next to a game or other Direct3D program there has taken
|
|
4
|
+
// the whole sandbox down. Pages still render, WebGL included, in software; programs that need the GPU keep it. Without
|
|
5
|
+
// vGPU there is no GPU to use, so only the download folder matters.
|
|
6
|
+
import path from 'node:path';
|
|
5
7
|
import { SYSTEM } from '../../shared/system.js';
|
|
6
|
-
import {
|
|
8
|
+
import { browserPolicies } from '../../shared/browser-policy.js';
|
|
9
|
+
import { dataDir, dryRun, isWin } from '../runtime/paths.js';
|
|
7
10
|
import { exec } from '../runtime/process.js';
|
|
8
11
|
import { log } from '../runtime/log.js';
|
|
9
|
-
|
|
12
|
+
import { mkdirp } from '../runtime/fs.js';
|
|
10
13
|
export function keepBrowsersOffGpu() {
|
|
11
14
|
if (dryRun || !isWin)
|
|
12
15
|
return;
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
});
|
|
17
|
-
log('Browsers draw without the GPU
|
|
16
|
+
const downloads = path.join(dataDir, 'downloads');
|
|
17
|
+
mkdirp(downloads);
|
|
18
|
+
for (const p of browserPolicies({ downloads }))
|
|
19
|
+
exec(SYSTEM.reg(), ['add', p.key, '/v', p.name, '/t', p.type, '/d', p.data, '/f'], { stdio: 'ignore' });
|
|
20
|
+
log('Browsers draw without the GPU and download to ' + downloads + '.');
|
|
18
21
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
// The GitHub token the host handed over (github.shareToken, only with git access "full"): gh logs
|
|
2
|
-
// folder on the sandbox's own disk (gone with the sandbox, never in the persistent state), and https
|
|
3
|
-
// token goes to gh on stdin, never in an argument, a file of ours or a log line.
|
|
1
|
+
// The GitHub token the host handed over (github.shareToken or github.usePcLogin, only with git access "full"): gh logs
|
|
2
|
+
// in with it in a folder on the sandbox's own disk (gone with the sandbox, never in the persistent state), and https
|
|
3
|
+
// git asks gh. The token goes to gh on stdin, never in an argument, a file of ours or a log line.
|
|
4
4
|
import fs from 'node:fs';
|
|
5
5
|
import { spawnSync } from 'node:child_process';
|
|
6
6
|
import { ghLoginCommand, ghOutputText, ghSetupGitCommand } from '../../shared/github/index.js';
|
|
@@ -61,5 +61,5 @@ export function loginGithub(token, env) {
|
|
|
61
61
|
addMachineEnvironment({ GH_CONFIG_DIR: env.GH_CONFIG_DIR }, []);
|
|
62
62
|
throw e;
|
|
63
63
|
}
|
|
64
|
-
log('gh and https git to github.com use the GitHub
|
|
64
|
+
log('gh and https git to github.com use the GitHub login from your PC (github.shareToken or github.usePcLogin); the agent can read it.');
|
|
65
65
|
}
|
|
@@ -5,6 +5,7 @@ import fs from 'node:fs';
|
|
|
5
5
|
import http from 'node:http';
|
|
6
6
|
import path from 'node:path';
|
|
7
7
|
import { spawn } from 'node:child_process';
|
|
8
|
+
import { rotateLog } from '../../shared/io/index.js';
|
|
8
9
|
import { HEADROOM_PORT, headroomArgs, headroomEnv, headroomUrl } from '../../shared/apps/index.js';
|
|
9
10
|
import { logsDir } from '../runtime/paths.js';
|
|
10
11
|
import { portOpen, waitForPort } from '../runtime/ports.js';
|
|
@@ -12,6 +13,8 @@ import { log } from '../runtime/log.js';
|
|
|
12
13
|
import { installedAddon } from './agent-addons.js';
|
|
13
14
|
/** The proxy's log (it redacts keys and tokens in what it logs). */
|
|
14
15
|
export const HEADROOM_LOG = path.join(logsDir, 'headroom.log');
|
|
16
|
+
/** The proxy logs every request and the file lives on the data disk: past this it is kept as headroom.log.1. */
|
|
17
|
+
const HEADROOM_LOG_LIMIT = 20 * 1024 * 1024;
|
|
15
18
|
/** Whether Headroom's own health check answers on the port (not some other program that took it). */
|
|
16
19
|
function answersAsHeadroom(port) {
|
|
17
20
|
return new Promise((resolve) => {
|
|
@@ -53,6 +56,7 @@ async function startProxy(env) {
|
|
|
53
56
|
return false;
|
|
54
57
|
if (await portOpen(HEADROOM_PORT))
|
|
55
58
|
return true;
|
|
59
|
+
rotateLog(HEADROOM_LOG, HEADROOM_LOG_LIMIT);
|
|
56
60
|
const out = fs.openSync(HEADROOM_LOG, 'a');
|
|
57
61
|
try {
|
|
58
62
|
spawn(exe, headroomArgs(HEADROOM_PORT), {
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface UnityAgentInput {
|
|
2
|
+
readonly unityExe: string;
|
|
3
|
+
readonly projectPath: string | null;
|
|
4
|
+
readonly mcp: boolean;
|
|
5
|
+
readonly skills: boolean;
|
|
6
|
+
}
|
|
7
|
+
/** Sets up what is asked; returns the MCP server's name when it was registered and whether the skills were synced. */
|
|
8
|
+
export declare function setupUnityAgentTools(i: UnityAgentInput): Promise<{
|
|
9
|
+
mcp: string | null;
|
|
10
|
+
skills: boolean;
|
|
11
|
+
}>;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// Unity for the agents: the Unity CLI's MCP server (sbx-unity) and its skills in the sandbox's Claude Code and Codex.
|
|
2
|
+
// The project is never changed. A failure is a warning: the editor itself still works without the agent tools.
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { UNITY_CLI_ENV, UNITY_MCP_NAME, unityCliMcpServer } from '../../shared/unity/index.js';
|
|
5
|
+
import { execAsync } from '../runtime/process.js';
|
|
6
|
+
import { log, warn } from '../runtime/log.js';
|
|
7
|
+
import { claudeDir, session, stateDir } from '../runtime/session.js';
|
|
8
|
+
import { setEngineMcp, writeAddonMcpServers } from './addon-mcp.js';
|
|
9
|
+
/** Syncs Unity's skills for one agent, with that agent's home folder in its variable; false on failure. */
|
|
10
|
+
async function syncSkills(unityExe, agent) {
|
|
11
|
+
const home = agent === 'claude' ? { CLAUDE_CONFIG_DIR: claudeDir } : { CODEX_HOME: path.join(stateDir, 'codex') };
|
|
12
|
+
try {
|
|
13
|
+
await execAsync(unityExe, ['skill', 'sync', agent], { env: { ...process.env, ...UNITY_CLI_ENV, ...home } });
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
catch (e) {
|
|
17
|
+
warn('Unity skills for ' + agent + ' were not synced: ' + (e instanceof Error ? e.message : String(e)));
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/** Sets up what is asked; returns the MCP server's name when it was registered and whether the skills were synced. */
|
|
22
|
+
export async function setupUnityAgentTools(i) {
|
|
23
|
+
setEngineMcp(UNITY_MCP_NAME, i.mcp ? unityCliMcpServer(i.unityExe, i.projectPath) : null);
|
|
24
|
+
const names = writeAddonMcpServers();
|
|
25
|
+
const mcp = names.includes(UNITY_MCP_NAME) ? UNITY_MCP_NAME : null;
|
|
26
|
+
let skills = false;
|
|
27
|
+
if (i.skills) {
|
|
28
|
+
const results = [await syncSkills(i.unityExe, 'claude')];
|
|
29
|
+
if (session.agents?.codex)
|
|
30
|
+
results.push(await syncSkills(i.unityExe, 'codex'));
|
|
31
|
+
skills = results.some(Boolean);
|
|
32
|
+
if (skills)
|
|
33
|
+
log('Unity skills synced for the agents');
|
|
34
|
+
}
|
|
35
|
+
return { mcp, skills };
|
|
36
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type LauncherEntry } from '../../../shared/unreal/index.js';
|
|
2
|
+
/** The launcher's program (launcher 17+ is 64-bit; older ones were in Win32). */
|
|
3
|
+
export declare function launcherExe(): string | null;
|
|
4
|
+
/** Downloads the launcher's MSI ahead of the install (once). */
|
|
5
|
+
export declare function fetchLauncher(): Promise<void>;
|
|
6
|
+
/** Installs the launcher when this sandbox does not have it yet (the MSI, quietly). Returns its program. */
|
|
7
|
+
export declare function ensureLauncher(): Promise<string>;
|
|
8
|
+
export interface LauncherPlaceResult {
|
|
9
|
+
readonly installLocation: string | null;
|
|
10
|
+
readonly problem: string | null;
|
|
11
|
+
readonly freeBytes: number | null;
|
|
12
|
+
readonly needBytes: number;
|
|
13
|
+
}
|
|
14
|
+
/** Points the launcher's default install folder at tools\unreal\engines (every settings file it may read). */
|
|
15
|
+
export declare function placeEngines(persist: boolean): LauncherPlaceResult;
|
|
16
|
+
/** The saved LauncherInstalled.dat entries of engines in tools\unreal\engines whose folder is still there. */
|
|
17
|
+
export declare function savedEntries(): LauncherEntry[];
|
|
18
|
+
/** Puts the saved manifests back where the launcher reads them; the engine folders they name. */
|
|
19
|
+
export declare function restoreLauncherItems(): string[];
|
|
20
|
+
/** Merges these entries into the launcher's LauncherInstalled.dat (foreign entries kept). */
|
|
21
|
+
export declare function writeLauncherEntries(entries: readonly LauncherEntry[]): void;
|
|
22
|
+
/**
|
|
23
|
+
* Saves what the launcher knows about engines it installed into tools\unreal\engines (new or changed manifests, its
|
|
24
|
+
* LauncherInstalled.dat entries). Cheap: a few small files. Returns whether something new was saved.
|
|
25
|
+
*/
|
|
26
|
+
export declare function saveLauncherState(): boolean;
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
// The Epic Games Launcher in the sandbox: installed every boot from its MSI (downloaded once into tools\unreal, run
|
|
2
|
+
// only when Epic's signature checks out; a sandbox loses Program Files), pointed at the data disk for engines
|
|
3
|
+
// (GameUserSettings.ini [Launcher] DefaultAppInstallLocation, in the encoding the file has), and given back what it
|
|
4
|
+
// knew about the engines it installed there: their .item manifests and LauncherInstalled.dat entries are saved on the
|
|
5
|
+
// data disk while the sandbox runs (the daemon calls saveLauncherState) and put back at the next boot.
|
|
6
|
+
import fs from 'node:fs';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import { z } from 'zod';
|
|
9
|
+
import { readBoundedFile } from '../../../shared/io/bounded.js';
|
|
10
|
+
import { readValid, writeJsonAtomic } from '../../../shared/io/json.js';
|
|
11
|
+
import { SYSTEM } from '../../../shared/system.js';
|
|
12
|
+
import { enginePlaceProblem, ENGINE_NEED_BYTES, entriesUnder, launcherInstalledFile, launcherManifestsDir, launcherSettingsFiles, MAX_LAUNCHER_FILE_BYTES, mergeLauncherInstalled, restoreItems, saveItems, setLauncherInstallLocation, } from '../../../shared/unreal/index.js';
|
|
13
|
+
import { errMsg } from '../../../core/util/errors.js';
|
|
14
|
+
import { runInstaller } from '../../runtime/pools.js';
|
|
15
|
+
import { exists, mkdirp } from '../../runtime/fs.js';
|
|
16
|
+
import { log } from '../../runtime/log.js';
|
|
17
|
+
import { SIGNERS, signedInstaller } from '../installers.js';
|
|
18
|
+
import { ENGINES_DIR, SAVED_DIR, UNREAL_DIR } from './unreal-status.js';
|
|
19
|
+
const MSI_URL = 'https://launcher-public-service-prod06.ol.epicgames.com/launcher/api/installer/download/EpicGamesLauncherInstaller.msi';
|
|
20
|
+
const MSI_FILE = path.join(UNREAL_DIR, 'EpicGamesLauncherInstaller.msi');
|
|
21
|
+
const SAVED_MANIFESTS = path.join(SAVED_DIR, 'Manifests');
|
|
22
|
+
const SAVED_ENTRIES = path.join(SAVED_DIR, 'LauncherInstalled.json');
|
|
23
|
+
const programData = () => process.env.ProgramData ?? 'C:\\ProgramData';
|
|
24
|
+
const localAppData = () => process.env.LOCALAPPDATA ?? '';
|
|
25
|
+
/** The launcher's program (launcher 17+ is 64-bit; older ones were in Win32). */
|
|
26
|
+
export function launcherExe() {
|
|
27
|
+
const root = path.join(process.env['ProgramFiles(x86)'] ?? 'C:\\Program Files (x86)', 'Epic Games', 'Launcher');
|
|
28
|
+
for (const arch of ['Win64', 'Win32']) {
|
|
29
|
+
const exe = path.join(root, 'Portal', 'Binaries', arch, 'EpicGamesLauncher.exe');
|
|
30
|
+
if (exists(exe))
|
|
31
|
+
return exe;
|
|
32
|
+
}
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
/** Downloads the launcher's MSI ahead of the install (once). */
|
|
36
|
+
export async function fetchLauncher() {
|
|
37
|
+
if (!launcherExe())
|
|
38
|
+
await launcherMsi();
|
|
39
|
+
}
|
|
40
|
+
const launcherMsi = () => signedInstaller(MSI_URL, MSI_FILE, 'the Epic Games Launcher (~250 MB once)', SIGNERS.epic);
|
|
41
|
+
/** Installs the launcher when this sandbox does not have it yet (the MSI, quietly). Returns its program. */
|
|
42
|
+
export async function ensureLauncher() {
|
|
43
|
+
const have = launcherExe();
|
|
44
|
+
if (have)
|
|
45
|
+
return have;
|
|
46
|
+
const msi = await launcherMsi();
|
|
47
|
+
log('Installing the Epic Games Launcher ...');
|
|
48
|
+
const code = await runInstaller(SYSTEM.msiexec(), ['/i', msi, '/qn', '/norestart']);
|
|
49
|
+
const exe = launcherExe();
|
|
50
|
+
if ((code !== 0 && code !== 3010) || !exe)
|
|
51
|
+
throw new Error('the Epic Games Launcher installer failed (msiexec exit ' + code + ')');
|
|
52
|
+
return exe;
|
|
53
|
+
}
|
|
54
|
+
/** Points the launcher's default install folder at tools\unreal\engines (every settings file it may read). */
|
|
55
|
+
export function placeEngines(persist) {
|
|
56
|
+
let error;
|
|
57
|
+
if (persist)
|
|
58
|
+
try {
|
|
59
|
+
mkdirp(ENGINES_DIR);
|
|
60
|
+
for (const file of launcherSettingsFiles(localAppData()))
|
|
61
|
+
setLauncherInstallLocation(file, ENGINES_DIR);
|
|
62
|
+
}
|
|
63
|
+
catch (e) {
|
|
64
|
+
error = "could not write the launcher's settings (" + errMsg(e) + ')';
|
|
65
|
+
}
|
|
66
|
+
else
|
|
67
|
+
error = 'toolchains.unreal.persistEngines is off';
|
|
68
|
+
const set = persist && !error;
|
|
69
|
+
const { problem, freeBytes } = enginePlaceProblem(set ? { set } : { set, error: error }, ENGINES_DIR);
|
|
70
|
+
return { installLocation: set ? ENGINES_DIR : null, problem, freeBytes, needBytes: ENGINE_NEED_BYTES };
|
|
71
|
+
}
|
|
72
|
+
const SavedEntriesSchema = z.object({
|
|
73
|
+
entries: z
|
|
74
|
+
.array(z.object({
|
|
75
|
+
InstallLocation: z.string().max(1024),
|
|
76
|
+
NamespaceId: z.string().max(200),
|
|
77
|
+
ItemId: z.string().max(200),
|
|
78
|
+
ArtifactId: z.string().max(200),
|
|
79
|
+
AppVersion: z.string().max(200),
|
|
80
|
+
AppName: z.string().max(200),
|
|
81
|
+
}))
|
|
82
|
+
.max(64),
|
|
83
|
+
});
|
|
84
|
+
/** The saved LauncherInstalled.dat entries of engines in tools\unreal\engines whose folder is still there. */
|
|
85
|
+
export function savedEntries() {
|
|
86
|
+
return (readValid(SavedEntriesSchema, SAVED_ENTRIES)?.entries ?? []).filter((e) => e.InstallLocation.toLowerCase().startsWith(ENGINES_DIR.toLowerCase() + '\\') && exists(e.InstallLocation));
|
|
87
|
+
}
|
|
88
|
+
/** Puts the saved manifests back where the launcher reads them; the engine folders they name. */
|
|
89
|
+
export function restoreLauncherItems() {
|
|
90
|
+
return restoreItems(SAVED_MANIFESTS, launcherManifestsDir(programData()), ENGINES_DIR, exists);
|
|
91
|
+
}
|
|
92
|
+
/** Merges these entries into the launcher's LauncherInstalled.dat (foreign entries kept). */
|
|
93
|
+
export function writeLauncherEntries(entries) {
|
|
94
|
+
if (!entries.length)
|
|
95
|
+
return;
|
|
96
|
+
const file = launcherInstalledFile(programData());
|
|
97
|
+
const bytes = readBoundedFile(file, MAX_LAUNCHER_FILE_BYTES);
|
|
98
|
+
if (!bytes && fs.existsSync(file))
|
|
99
|
+
throw new Error(file + ' is too big to read');
|
|
100
|
+
const next = mergeLauncherInstalled(bytes ? bytes.toString('utf8') : null, entries);
|
|
101
|
+
mkdirp(path.dirname(file));
|
|
102
|
+
fs.writeFileSync(file, next, 'utf8');
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Saves what the launcher knows about engines it installed into tools\unreal\engines (new or changed manifests, its
|
|
106
|
+
* LauncherInstalled.dat entries). Cheap: a few small files. Returns whether something new was saved.
|
|
107
|
+
*/
|
|
108
|
+
export function saveLauncherState() {
|
|
109
|
+
if (!exists(ENGINES_DIR))
|
|
110
|
+
return false;
|
|
111
|
+
const items = saveItems(launcherManifestsDir(programData()), SAVED_MANIFESTS, ENGINES_DIR);
|
|
112
|
+
const bytes = readBoundedFile(launcherInstalledFile(programData()), MAX_LAUNCHER_FILE_BYTES);
|
|
113
|
+
const live = bytes ? entriesUnder(bytes.toString('utf8'), ENGINES_DIR) : [];
|
|
114
|
+
const old = readValid(SavedEntriesSchema, SAVED_ENTRIES)?.entries ?? [];
|
|
115
|
+
const merged = [
|
|
116
|
+
...old.filter((o) => !live.some((l) => l.InstallLocation.toLowerCase() === o.InstallLocation.toLowerCase())),
|
|
117
|
+
...live,
|
|
118
|
+
].filter((e) => exists(e.InstallLocation));
|
|
119
|
+
const changed = JSON.stringify(merged) !== JSON.stringify(old);
|
|
120
|
+
if (changed) {
|
|
121
|
+
mkdirp(SAVED_DIR);
|
|
122
|
+
writeJsonAtomic(SAVED_ENTRIES, { entries: merged.slice(0, 64) });
|
|
123
|
+
}
|
|
124
|
+
if (items.length || changed)
|
|
125
|
+
log('Unreal: saved what the Epic Games Launcher knows about its engines on the data disk');
|
|
126
|
+
return items.length > 0 || changed;
|
|
127
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export interface UnityCli {
|
|
2
|
+
readonly exe: string;
|
|
3
|
+
readonly version: string;
|
|
4
|
+
}
|
|
5
|
+
export interface UnityRun {
|
|
6
|
+
readonly code: number;
|
|
7
|
+
readonly stdout: string;
|
|
8
|
+
}
|
|
9
|
+
export interface UnityRunOptions {
|
|
10
|
+
/** TEMP and TMP for the run (inside the staging folder of an install). */
|
|
11
|
+
readonly temp?: string;
|
|
12
|
+
readonly cwd?: string;
|
|
13
|
+
readonly timeoutMs?: number;
|
|
14
|
+
/** Extra variables (the asset cache). */
|
|
15
|
+
readonly env?: Readonly<Record<string, string>>;
|
|
16
|
+
}
|
|
17
|
+
/** The pinned Unity CLI on the data disk, downloaded when it is not there yet ("latest" asks the manifest first). */
|
|
18
|
+
export declare function ensureUnityCli(version: string): Promise<UnityCli>;
|
|
19
|
+
/** Downloads the CLI ahead of its step; a failure is ignored (the step downloads again). */
|
|
20
|
+
export declare function prefetchUnityCli(version: string): Promise<void>;
|
|
21
|
+
/** Runs the CLI without blocking; the exit code and the start of its output (at most 1 MB). Never throws on a code. */
|
|
22
|
+
export declare function runUnityCli(exe: string, args: readonly string[], o?: UnityRunOptions): Promise<UnityRun>;
|
|
23
|
+
/** runUnityCli that throws with the end of the output when the exit code is not 0. */
|
|
24
|
+
export declare function runUnityCliChecked(exe: string, args: readonly string[], o?: UnityRunOptions): Promise<string>;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// The Unity CLI (`unity`): its pinned build downloaded once into tools\unity\cli\<version> (checked against the SHA-256
|
|
2
|
+
// Unity publishes, and its signature when it has one), and the way every other Unity step runs it.
|
|
3
|
+
import { spawn } from 'node:child_process';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { fetchBounded, downloader } from '../../../shared/download/index.js';
|
|
6
|
+
import { CliManifestSchema, cliManifestUrl, pickCliBinary, UNITY_CLI_ENV, } from '../../../shared/unity/index.js';
|
|
7
|
+
import { log } from '../../runtime/log.js';
|
|
8
|
+
import { net } from '../../runtime/pools.js';
|
|
9
|
+
import { ensurePersisted } from '../persisted.js';
|
|
10
|
+
import { arch } from '../tool-env.js';
|
|
11
|
+
const MB = 1024 * 1024;
|
|
12
|
+
const MAX_MANIFEST_BYTES = 256 * 1024;
|
|
13
|
+
const MAX_OUTPUT_BYTES = 1024 * 1024;
|
|
14
|
+
/** The binary the manifest of a CLI version names for this CPU. */
|
|
15
|
+
async function resolveCli(version) {
|
|
16
|
+
const text = await net.run(() => fetchBounded(cliManifestUrl(version), MAX_MANIFEST_BYTES));
|
|
17
|
+
const parsed = CliManifestSchema.safeParse(JSON.parse(text));
|
|
18
|
+
const binary = parsed.success ? pickCliBinary(parsed.data, arch) : null;
|
|
19
|
+
if (!binary)
|
|
20
|
+
throw new Error('the Unity CLI manifest for ' + version + ' has no usable Windows build');
|
|
21
|
+
return binary;
|
|
22
|
+
}
|
|
23
|
+
/** The pinned Unity CLI on the data disk, downloaded when it is not there yet ("latest" asks the manifest first). */
|
|
24
|
+
export async function ensureUnityCli(version) {
|
|
25
|
+
const binary = await resolveCli(version);
|
|
26
|
+
const installed = await ensurePersisted({
|
|
27
|
+
name: 'unity/cli',
|
|
28
|
+
version: binary.version,
|
|
29
|
+
needBytes: 200 * MB,
|
|
30
|
+
marker: 'unity.exe',
|
|
31
|
+
what: 'Unity CLI ' + binary.version,
|
|
32
|
+
keepVersions: 2,
|
|
33
|
+
}, async (staging) => {
|
|
34
|
+
log('Downloading the Unity CLI ' + binary.version + ' ...');
|
|
35
|
+
// The CLI is not known to be signed: the SHA-256 from Unity's manifest is the check.
|
|
36
|
+
await downloader.file(binary.url, path.join(staging, 'unity.exe'), { sha256: binary.sha256 });
|
|
37
|
+
});
|
|
38
|
+
return { exe: path.join(installed.dir, 'unity.exe'), version: binary.version };
|
|
39
|
+
}
|
|
40
|
+
/** Downloads the CLI ahead of its step; a failure is ignored (the step downloads again). */
|
|
41
|
+
export async function prefetchUnityCli(version) {
|
|
42
|
+
await ensureUnityCli(version);
|
|
43
|
+
}
|
|
44
|
+
/** Runs the CLI without blocking; the exit code and the start of its output (at most 1 MB). Never throws on a code. */
|
|
45
|
+
export function runUnityCli(exe, args, o = {}) {
|
|
46
|
+
return new Promise((resolve, reject) => {
|
|
47
|
+
const temp = o.temp ? { TEMP: o.temp, TMP: o.temp } : {};
|
|
48
|
+
const child = spawn(exe, [...args], {
|
|
49
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
50
|
+
windowsHide: true,
|
|
51
|
+
...(o.cwd ? { cwd: o.cwd } : {}),
|
|
52
|
+
env: { ...process.env, ...UNITY_CLI_ENV, ...temp, ...(o.env ?? {}) },
|
|
53
|
+
...(o.timeoutMs ? { timeout: o.timeoutMs } : {}),
|
|
54
|
+
});
|
|
55
|
+
let stdout = '';
|
|
56
|
+
const take = (chunk) => {
|
|
57
|
+
if (stdout.length < MAX_OUTPUT_BYTES)
|
|
58
|
+
stdout += chunk.slice(0, MAX_OUTPUT_BYTES - stdout.length);
|
|
59
|
+
};
|
|
60
|
+
child.stdout.setEncoding('utf8');
|
|
61
|
+
child.stderr.setEncoding('utf8');
|
|
62
|
+
child.stdout.on('data', take);
|
|
63
|
+
child.stderr.on('data', take);
|
|
64
|
+
child.once('error', reject);
|
|
65
|
+
child.once('close', (code) => resolve({ code: code ?? -1, stdout }));
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
/** runUnityCli that throws with the end of the output when the exit code is not 0. */
|
|
69
|
+
export async function runUnityCliChecked(exe, args, o = {}) {
|
|
70
|
+
const r = await runUnityCli(exe, args, o);
|
|
71
|
+
if (r.code !== 0)
|
|
72
|
+
throw new Error('unity ' + args.slice(0, 2).join(' ') + ' failed with exit code ' + r.code + ': ' + r.stdout.trim().slice(-400));
|
|
73
|
+
return r.stdout;
|
|
74
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// Unity Hub (toolchains.unity.hub): kept on the data disk (tools\unity\hub\3), installed by its signed installer
|
|
2
|
+
// unpacked there. For signing in to Unity Personal and installing editors by hand.
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { runInstaller } from '../../runtime/pools.js';
|
|
5
|
+
import { shortcuts } from '../../apps/shortcuts.js';
|
|
6
|
+
import { exists } from '../../runtime/fs.js';
|
|
7
|
+
import { log } from '../../runtime/log.js';
|
|
8
|
+
import { SIGNERS, signedInstaller } from '../installers.js';
|
|
9
|
+
import { ensurePersisted } from '../persisted.js';
|
|
10
|
+
import { UNITY_DIR } from './unity-status.js';
|
|
11
|
+
const HUB_URL = 'https://public-cdn.cloud.unity3d.com/hub/prod/UnityHubSetup.exe';
|
|
12
|
+
const HUB_MARKER = 'Unity Hub.exe';
|
|
13
|
+
const GB = 1024 ** 3;
|
|
14
|
+
/** The Hub's program on the data disk, installed there when it is not (only with `hub` on). */
|
|
15
|
+
export async function ensureUnityHub() {
|
|
16
|
+
const setup = await signedInstaller(HUB_URL, path.join(UNITY_DIR, 'UnityHubSetup.exe'), 'Unity Hub (~150 MB once)', SIGNERS.unity);
|
|
17
|
+
const installed = await ensurePersisted({ name: 'unity/hub', version: '3', needBytes: 2 * GB, marker: HUB_MARKER, what: 'Unity Hub', keepVersions: 1 }, async (staging) => {
|
|
18
|
+
log('Installing Unity Hub ...');
|
|
19
|
+
const code = await runInstaller(setup, ['/S', '/D=' + staging]);
|
|
20
|
+
if (code !== 0)
|
|
21
|
+
throw new Error('the Unity Hub installer failed (exit ' + code + ')');
|
|
22
|
+
});
|
|
23
|
+
const exe = path.join(installed.dir, HUB_MARKER);
|
|
24
|
+
if (exists(exe))
|
|
25
|
+
shortcuts([{ name: 'Unity Hub', exe }]);
|
|
26
|
+
return exe;
|
|
27
|
+
}
|
|
@@ -1,15 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
/** The SRI hash (sha384-... or sha256-...). */
|
|
6
|
-
readonly integrity: string | null;
|
|
7
|
-
readonly sizeBytes: number | null;
|
|
8
|
-
}
|
|
9
|
-
export interface UnityRelease {
|
|
10
|
-
readonly version: string;
|
|
11
|
-
readonly changeset: string | null;
|
|
12
|
-
readonly downloads: readonly UnityDownload[];
|
|
13
|
-
}
|
|
14
|
-
/** The release of one editor version (6000.0.32f1), or null when Unity does not list it. */
|
|
15
|
-
export declare function findUnityRelease(_version: string): Promise<UnityRelease | null>;
|
|
1
|
+
import { type UnityRelease } from '../../../shared/unity/index.js';
|
|
2
|
+
export type { UnityRelease };
|
|
3
|
+
/** The release of one editor version (6000.0.58f2), or null when Unity does not list it or answers oddly. */
|
|
4
|
+
export declare function findUnityRelease(version: string): Promise<UnityRelease | null>;
|
|
@@ -1,6 +1,18 @@
|
|
|
1
|
-
// Unity's release API (services.api.unity.com
|
|
2
|
-
//
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
// Unity's release API (services.api.unity.com): the editor installer, its changeset and its modules for one version.
|
|
2
|
+
// The answer is read with a size limit and checked by shared/unity/release.ts.
|
|
3
|
+
import { fetchBounded } from '../../../shared/download/index.js';
|
|
4
|
+
import { pickRelease, releaseUrl } from '../../../shared/unity/index.js';
|
|
5
|
+
const MAX_ANSWER_BYTES = 2 * 1024 * 1024;
|
|
6
|
+
/** The release of one editor version (6000.0.58f2), or null when Unity does not list it or answers oddly. */
|
|
7
|
+
export async function findUnityRelease(version) {
|
|
8
|
+
const text = await fetchBounded(releaseUrl(version), MAX_ANSWER_BYTES);
|
|
9
|
+
let json;
|
|
10
|
+
try {
|
|
11
|
+
json = JSON.parse(text);
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return null; // Not JSON: no release.
|
|
15
|
+
}
|
|
16
|
+
const release = pickRelease(json);
|
|
17
|
+
return release && release.version === version ? release : null;
|
|
6
18
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type UnityStatus } from '../../../shared/unity/index.js';
|
|
2
|
+
export declare const UNITY_DIR: string;
|
|
3
|
+
/** The Unity CLI, one folder per version (unity.exe). */
|
|
4
|
+
export declare const CLI_DIR: string;
|
|
5
|
+
/** The editors on the data disk: one folder per version (Editor\Unity.exe). */
|
|
6
|
+
export declare const EDITORS_DIR: string;
|
|
7
|
+
/** Asset Store downloads made in the sandbox (assetCache "persisted"). */
|
|
8
|
+
export declare const ASSET_DIR: string;
|
|
9
|
+
export declare function readUnityStatus(): UnityStatus;
|
|
10
|
+
/** Changes the status with `change` and writes it (whole, atomically). */
|
|
11
|
+
export declare function updateUnityStatus(change: (s: UnityStatus) => UnityStatus): UnityStatus;
|