diff --git a/CHANGELOG.md b/CHANGELOG.md index dc4dcd425e..0195b7a7d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,14 @@ file is the human-readable summary of what changed and why. ### Desktop / Web UI +- **The CPU and memory fields explain themselves** — the Add sandbox dialog + offered two unlabelled optional boxes with no hint of what a sandbox gets if + you leave them empty, and the memory box ran past the edge of the dialog. + They now sit side by side within the dialog, show their defaults (4 CPUs and + 4 GB), and say what they control and how high you can go. The dialog also + drops two lines that were not earning their space. The fields appear + only on macOS, where each sandbox is its own virtual machine and the limits + actually apply. - **Deleting a sandbox no longer breaks the app** — deleting a sandbox while one of its sessions was open could take the whole window down to the "Something went wrong" screen, and because the app reopens whatever you were diff --git a/DIVERGENCES.md b/DIVERGENCES.md index 41ed592ec9..7a370ab5ba 100644 --- a/DIVERGENCES.md +++ b/DIVERGENCES.md @@ -198,7 +198,7 @@ from the squash message first, or use a plain merge. | Dual-path WSL install: `packages/desktop/src/main/wsl/{runtime,servers}.ts` | `runtime.ts`: three internals gain `export` (`runPowerShell`, `runInteractiveCommand`, `resolveSystem32Command`) — no behavior change. `servers.ts`: the `install-wsl` job calls `installWslRuntimeDualPath` and the `install-distro` job calls `installWslDistroDualPath` (both from the fork-only `install-bundle.ts`, which holds all new logic) instead of `installWslRuntimeElevated` / `installWslDistro` directly; both return the same `WslCommandResult` shape, so the surrounding job/error handling is untouched. | The customer fleet sits behind whitelist-only proxies that cannot reach Microsoft's servers. The dual path downloads the pinned bundle (`wsl.zip`: runtime MSI + Ubuntu-24.04 image) from the codev storage host, enables VirtualMachinePlatform via offline dism + installs the MSI behind one UAC prompt, and falls back to upstream's `wsl --install` / `--web-download` paths only when the bundle cannot be *obtained* — a completed-but-failed elevated install is returned as-is, never retried behind a second UAC prompt. | Keep the two call swaps and the three `export` keywords; take upstream's surrounding changes. If upstream reworks `installWslRuntimeElevated` / `installWslDistro`, keep them exported from `runtime.ts` — `install-bundle.ts` calls them as the fallback path. | | Async settings stores: `packages/desktop/src/main/{store,index}.ts`, `packages/desktop/package.json` | `store.ts` no longer uses electron-store: `getStore` now returns the fork-only `store-file.ts` store — in-memory state, flushed write-behind through `store-writer.ts`'s serialized latest-wins async atomic writer (temp → fsync → rename via `fs.promises`, so the fsync runs on the libuv threadpool). Adds `flushStoreWrites(timeoutMs)`; `removeStoreFile`/`removeStoreFileIfEmpty` discard/flush the writer around the delete so a late flush cannot resurrect the file. `index.ts`: `stopSidecars` awaits `flushStoreWrites(1500)`. `package.json`: the `electron-store` dependency is removed. On-disk format is unchanged (flat keys, tab-indented JSON, no extension) — existing files load as-is; a corrupt file now starts empty with a logged error instead of throwing from every accessor. | conf v15's `store.set` is a synchronous fsync on the Electron main thread, and one of those parked the whole app in uninterruptible disk sleep (kernel journal stall + lost ext4 fast-commit wakeup, observed live during a sandbox mount-apply restart): event loop dead, IPC dead, quit impossible. conf also rereads the file on every access, which makes write-behind unsound with the stock class (a set on one key reads a stale file and drops a still-queued update), so the storage layer had to be replaced rather than patched. | Keep the fork store; take upstream's surrounding changes. If upstream adds electron-store options (schema, migrations, dot-notation) or new store call sites, extend `store-file.ts` to match — its API is the superset the desktop actually uses (`get/set/has/delete/clear/store/path`). Do not reintroduce a synchronous write path on the main thread. | | Desktop sandbox wiring: `packages/desktop/src/main/{index,store-keys}.ts`, `packages/desktop/package.json` | Hooks the fork-only `src/main/sandbox/{bundle,controller,ipc}.ts` modules into startup: `index.ts` constructs the sandbox controller beside the WSL servers controller, adds `sandboxes.stopAll()` to `stopSidecars`, registers the `sandbox-*` IPC channels next to the WSL ones, and calls `sandboxes.initialize()` (all platforms) once the local server is ready; `store-keys.ts` gains `SANDBOXES_KEY = "sandboxes"`; `package.json` gains the `@codev/sandbox` and `@codev/gateway` workspace devDependencies (bundled into the main process by electron-vite, so not runtime dependencies; gateway supplies the sandbox egress broker's credentials and config fragment). | The desktop app is where sandboxes are created and run; `createSandboxController` generalizes the proven WSL-servers controller pattern to the per-platform `@codev/sandbox` providers (macOS vz / Windows wsl / Linux bwrap). | Keep the wiring lines and take upstream's surrounding changes; they are additive and order-insensitive apart from `initialize()` needing the local server ready first. Do not rename the `sandbox-*` channels or the `sandboxes` store key — preload/renderer and persisted sandbox configs depend on them. | -| Desktop sandbox renderer slice: `packages/app/package.json`, `packages/app/src/{index.ts,app.tsx}`, `packages/app/src/context/{platform,server}.tsx`, `packages/app/src/components/{settings-v2/servers.tsx,dialog-select-server.tsx}`, `packages/app/src/wsl/settings.tsx`, `packages/app/src/i18n/{en,es}.ts`, `packages/desktop/src/preload/{index,types}.ts`, `packages/desktop/src/renderer/index.tsx` | Surfaces the fork-only sandbox modules (`packages/app/src/sandbox/*`, `packages/desktop/src/renderer/sandbox/*`) in the UI: app `package.json` exports `./sandbox/types` (mirror of `./wsl/types`); `index.ts` re-exports `useSandboxes` + the sandbox types; `app.tsx` mounts `SandboxesProvider` inside `WslServersProvider`; `platform.tsx` gains optional `sandboxes?: SandboxesPlatform` and, on the desktop variant, `openFilePathDialog` (a path-only file picker — the existing attachment picker reads the file's bytes, which is unusable for a multi-gigabyte guest image), implemented in `renderer/index.tsx` over the existing `open-file-picker` channel and releasing the read token immediately; `server.tsx` adds the third `ServerConnection.Sidecar` variant `{ variant: "sandbox"; name; projectDir?; mounts? }` with key `sandbox:` (`projectDir` is the host folder mounted into the guest and `mounts` the extra host folders with their resolved guest paths — the guest only ever sees its own mount points, so this is the only way the app can name a sandbox's projects after the folders it works on); `settings-v2/servers.tsx` filters sandbox rows out of the generic list and renders `SandboxServerSettings` beside the WSL section; `dialog-select-server.tsx` `handleRemove` routes `sandbox:` keys to `platform.sandboxes.remove` (like `wsl:` keys) and moves the `tabs.removeServer(key)` + `server.remove(key)` pair *above* both awaited platform removals; `wsl/settings.tsx` `AddServerMenu` shows the menu when either wslServers or sandboxes exist and gains an "Add sandbox" item; i18n gains the `sandbox.*` key family (including the optional per-sandbox CPU/memory fields, macOS-only since only the vz backend applies them; `sandbox.add.projectDirHint`, the standing explanation above the folder field; the `sandbox.mounts.*` family for the manage-folders dialog that adds/removes extra mounts on an existing sandbox; `sandbox.job.queued`, shown while a sandbox's create/reset/destroy waits its turn behind another sandbox's job; `sandbox.server.stop` for the menu entry that shuts a sandbox down until it is explicitly started again; and the `sandbox.add.image*` / `sandbox.add.name*` / `sandbox.arch.*` families for the optional custom-image field and the sandbox-name checks); preload exposes `api.sandboxes` over the `sandbox-*` channels and `renderer/index.tsx` wires the platform api, loading gate, ready-connection list, and the `startupServer` fallback (fork-only wrapper that validates `sandbox:` default keys the way `availableStartupServer` validates `wsl:` ones). | Ready sandboxes must appear as first-class servers and be manageable from settings; the slice mirrors the WSL vertical exactly so upstream conflicts resolve the same way in both. Removing a server destroys a disk and takes seconds, and the connection leaves the list the moment it finishes, so the tab cleanup has to happen before the await rather than after it. | Keep every listed edit; take upstream's surrounding changes. If upstream adds a `Sidecar` variant or reworks `AddServerMenu`, re-apply the sandbox variant/menu item on top. The `sandbox:` key format is persisted as the default-server key and as `config.id` in the main-process store — never change one side alone. Keep the tab cleanup ahead of the platform removals in `handleRemove`; if upstream reorders it back, re-apply. | +| Desktop sandbox renderer slice: `packages/app/package.json`, `packages/app/src/{index.ts,app.tsx}`, `packages/app/src/context/{platform,server}.tsx`, `packages/app/src/components/{settings-v2/servers.tsx,dialog-select-server.tsx}`, `packages/app/src/wsl/settings.tsx`, `packages/app/src/i18n/{en,es}.ts`, `packages/desktop/src/preload/{index,types}.ts`, `packages/desktop/src/renderer/index.tsx` | Surfaces the fork-only sandbox modules (`packages/app/src/sandbox/*`, `packages/desktop/src/renderer/sandbox/*`) in the UI: app `package.json` exports `./sandbox/types` (mirror of `./wsl/types`); `index.ts` re-exports `useSandboxes` + the sandbox types; `app.tsx` mounts `SandboxesProvider` inside `WslServersProvider`; `platform.tsx` gains optional `sandboxes?: SandboxesPlatform` and, on the desktop variant, `openFilePathDialog` (a path-only file picker — the existing attachment picker reads the file's bytes, which is unusable for a multi-gigabyte guest image), implemented in `renderer/index.tsx` over the existing `open-file-picker` channel and releasing the read token immediately; `server.tsx` adds the third `ServerConnection.Sidecar` variant `{ variant: "sandbox"; name; projectDir?; mounts? }` with key `sandbox:` (`projectDir` is the host folder mounted into the guest and `mounts` the extra host folders with their resolved guest paths — the guest only ever sees its own mount points, so this is the only way the app can name a sandbox's projects after the folders it works on); `settings-v2/servers.tsx` filters sandbox rows out of the generic list and renders `SandboxServerSettings` beside the WSL section; `dialog-select-server.tsx` `handleRemove` routes `sandbox:` keys to `platform.sandboxes.remove` (like `wsl:` keys) and moves the `tabs.removeServer(key)` + `server.remove(key)` pair *above* both awaited platform removals; `wsl/settings.tsx` `AddServerMenu` shows the menu when either wslServers or sandboxes exist and gains an "Add sandbox" item; i18n gains the `sandbox.*` key family (including the optional per-sandbox CPU/memory fields, macOS-only since only the vz backend applies them; `sandbox.add.projectDirHint`, the standing explanation above the folder field; the `sandbox.mounts.*` family for the manage-folders dialog that adds/removes extra mounts on an existing sandbox; `sandbox.job.queued`, shown while a sandbox's create/reset/destroy waits its turn behind another sandbox's job; `sandbox.server.stop` for the menu entry that shuts a sandbox down until it is explicitly started again; the `sandbox.add.image*` / `sandbox.add.name*` / `sandbox.arch.*` families for the optional custom-image field and the sandbox-name checks; and `sandbox.add.resourcesHint` plus interpolated `sandbox.add.{cpus,memory,resourcesInvalid}`, which quote the vz defaults and ranges from the constants rather than restating them in prose); preload exposes `api.sandboxes` over the `sandbox-*` channels and `renderer/index.tsx` wires the platform api, loading gate, ready-connection list, and the `startupServer` fallback (fork-only wrapper that validates `sandbox:` default keys the way `availableStartupServer` validates `wsl:` ones). | Ready sandboxes must appear as first-class servers and be manageable from settings; the slice mirrors the WSL vertical exactly so upstream conflicts resolve the same way in both. Removing a server destroys a disk and takes seconds, and the connection leaves the list the moment it finishes, so the tab cleanup has to happen before the await rather than after it. | Keep every listed edit; take upstream's surrounding changes. If upstream adds a `Sidecar` variant or reworks `AddServerMenu`, re-apply the sandbox variant/menu item on top. The `sandbox:` key format is persisted as the default-server key and as `config.id` in the main-process store — never change one side alone. Keep the tab cleanup ahead of the platform removals in `handleRemove`; if upstream reorders it back, re-apply. | | Desktop identity: `packages/desktop/src/main/{index,store}.ts`, `packages/desktop/electron-builder.config{,.test}.ts`, `packages/desktop/scripts/copy-metainfo.ts`, `packages/desktop/resources/linux/opencode-desktop.desktop` (deleted), `packages/app/src/pages/layout/{deep-links.ts,helpers.test.ts}`, `packages/codev-brand/test/brand.test.ts` | appId `ai.opencode.desktop{,.beta,.dev}` → `ai.codev.desktop{,.beta,.dev}` (both copies: `APP_IDS` in main and in the builder config, plus the unpackaged fallback); deep-link scheme `opencode://` → `codev://` in all three places that must agree — `protocols.schemes`, `app.setAsDefaultProtocolClient`, and the renderer's `parseUrl` — plus the `argv.startsWith` filter the second-instance handler uses; `rpm.packageName` `opencode`/`opencode-beta`/`opencode-dev` → `codev`/`codev-beta`/`codev-dev`; the hidden `opencode-desktop.desktop` legacy launcher and its `deb`/`rpm` fpm wiring deleted. `src/main/migrate.ts` keeps the old ids — they name the *Tauri* app's directories, not ours. | The appId is the whole desktop identity: `app.setPath("userData", join(appData, appId))`, so it also names the data directory, and it drives the macOS bundle id, the Windows AppUserModelId and NSIS keys, the Linux `.desktop` filename, `executableName` and `StartupWMClass`, and the AppStream metainfo id. Leaving it as opencode meant the fork's app identified itself as upstream to the OS. Renamed as a clean cut with no data migration and no back-compat shim, on the explicit basis that there is no installed base: existing profiles under `appData/ai.opencode.desktop*` are simply orphaned (settings, session history, sandboxes and the downloaded guest image), and a packaged update across the rename would install beside the old app rather than replace it. The legacy launcher went with it — it existed to rescue GNOME/KDE pins from a superseded packaging id, and its `Exec=/opt/OpenCode/...` no longer matched any install layout. | Keep our ids and scheme; take upstream's other config. Never rename the `TAURI_APP_IDS` in `migrate.ts` to match — a blind find-and-replace there points the Tauri migration at a directory that never existed. All three deep-link sites have to move together or links open nothing; `electron-builder.config.test.ts` covers the ids, the package names and the scheme per channel. | diff --git a/packages/app/src/i18n/en.ts b/packages/app/src/i18n/en.ts index f3546cb3d9..49ea12586b 100644 --- a/packages/app/src/i18n/en.ts +++ b/packages/app/src/i18n/en.ts @@ -402,8 +402,7 @@ export const dict = { "sandbox.arch.arm64": "ARM64 (Apple silicon)", "sandbox.add.image": "Sandbox image", "sandbox.add.imagePlaceholder": "Sandbox image file (optional)", - "sandbox.add.imageHint": - "Leave this empty and the right image is downloaded for you. Choose a file only if you have one already \u2014 an image built for this computer, or one someone shared with you.", + "sandbox.add.imageHint": "Leave this empty and the right image is downloaded for you.", "sandbox.add.imageClear": "Clear", "sandbox.add.imageChecking": "Checking the image\u2026", "sandbox.add.imageOk": "This image can be used on this computer.", @@ -415,13 +414,14 @@ export const dict = { "sandbox.add.imageWrongArch": "That image is built for {{found}} machines, which this computer is not.", "sandbox.add.projectDir": "Project folder", "sandbox.add.projectDirHint": - "Choose the folder you want to work on. The sandbox can only reach folders you mount from this computer — you can mount more later — and the agent edits files in them without asking.", + "Choose the folder you want to work on. The sandbox can only reach folders you mount from this computer, and you can mount more later.", "sandbox.add.choose": "Choose...", "sandbox.add.create": "Create", - "sandbox.add.cpus": "CPUs (optional)", - "sandbox.add.memory": "Memory GB (optional)", - "sandbox.add.resourcesInvalid": "CPUs must be 1-32 and memory 1-64 GB.", - "sandbox.add.downloadHint": "The first sandbox downloads its image; on a slow connection this can take a while.", + "sandbox.add.cpus": "CPUs (default {{cpus}})", + "sandbox.add.memory": "Memory (default {{memory}} GB)", + "sandbox.add.resourcesHint": + "How much of this Mac the sandbox may use. Each sandbox is a virtual machine, so its memory is held for as long as it runs.", + "sandbox.add.resourcesInvalid": "CPUs must be {{minCpus}}\u2013{{maxCpus}} and memory {{minMemory}}\u2013{{maxMemory}} GB.", "sandbox.add.downloadProgress": "Downloading image… {{done}}/{{total}} MB", "sandbox.add.creating": "Creating the sandbox\u2026 this takes a few minutes.", "sandbox.mounts.title": "Sandbox folders", diff --git a/packages/app/src/i18n/es.ts b/packages/app/src/i18n/es.ts index 1afb02c99b..d064e3f931 100644 --- a/packages/app/src/i18n/es.ts +++ b/packages/app/src/i18n/es.ts @@ -393,8 +393,7 @@ export const dict = { "sandbox.arch.arm64": "ARM64 (Apple silicon)", "sandbox.add.image": "Imagen del sandbox", "sandbox.add.imagePlaceholder": "Archivo de imagen del sandbox (opcional)", - "sandbox.add.imageHint": - "D\u00e9jalo vac\u00edo y se descargar\u00e1 la imagen adecuada. Elige un archivo solo si ya tienes uno: una imagen creada para este equipo o una que te hayan compartido.", + "sandbox.add.imageHint": "D\u00e9jalo vac\u00edo y se descargar\u00e1 la imagen adecuada.", "sandbox.add.imageClear": "Quitar", "sandbox.add.imageChecking": "Comprobando la imagen\u2026", "sandbox.add.imageOk": "Esta imagen se puede usar en este equipo.", @@ -406,13 +405,14 @@ export const dict = { "sandbox.add.imageWrongArch": "Esa imagen est\u00e1 hecha para equipos {{found}}, y este no lo es.", "sandbox.add.projectDir": "Carpeta del proyecto", "sandbox.add.projectDirHint": - "Elige la carpeta en la que quieres trabajar. El sandbox solo puede acceder a las carpetas de este equipo que montes — puedes montar más después — y el agente edita archivos en ellas sin preguntar.", + "Elige la carpeta en la que quieres trabajar. El sandbox solo puede acceder a las carpetas de este equipo que montes, y puedes montar más después.", "sandbox.add.choose": "Elegir...", "sandbox.add.create": "Crear", - "sandbox.add.cpus": "CPU (opcional)", - "sandbox.add.memory": "Memoria GB (opcional)", - "sandbox.add.resourcesInvalid": "Las CPU deben ser 1-32 y la memoria 1-64 GB.", - "sandbox.add.downloadHint": "El primer sandbox descarga su imagen; con una conexión lenta puede tardar un poco.", + "sandbox.add.cpus": "CPU (por defecto {{cpus}})", + "sandbox.add.memory": "Memoria (por defecto {{memory}} GB)", + "sandbox.add.resourcesHint": + "Cu\u00e1nto de este Mac puede usar el sandbox. Cada sandbox es una m\u00e1quina virtual, as\u00ed que su memoria queda reservada mientras est\u00e9 en marcha.", + "sandbox.add.resourcesInvalid": "Las CPU deben ser {{minCpus}}\u2013{{maxCpus}} y la memoria {{minMemory}}\u2013{{maxMemory}} GB.", "sandbox.add.downloadProgress": "Descargando imagen… {{done}}/{{total}} MB", "sandbox.add.creating": "Creando el sandbox\u2026 esto tarda unos minutos.", "sandbox.mounts.title": "Carpetas del sandbox", diff --git a/packages/app/src/sandbox/dialog-add.tsx b/packages/app/src/sandbox/dialog-add.tsx index d1f9e31013..80ea641738 100644 --- a/packages/app/src/sandbox/dialog-add.tsx +++ b/packages/app/src/sandbox/dialog-add.tsx @@ -13,7 +13,13 @@ import type { SandboxConfig, SandboxImageProblem } from "./types" import { useSandboxes } from "./context" import { sandboxCreatePhase } from "./create-phase" import { sandboxNameUsable, validateSandboxName } from "./name-model" -import { sandboxResourcesValid } from "./resources" +import { + SANDBOX_CPUS_RANGE, + SANDBOX_DEFAULT_CPUS, + SANDBOX_DEFAULT_MEMORY_GIB, + SANDBOX_MEMORY_MIB_RANGE, + sandboxResourcesValid, +} from "./resources" // onCreated fires with the new sandbox before the dialog closes. Callers that // need to react to a creation must use it rather than watching the sandbox @@ -140,14 +146,13 @@ export function DialogAddSandbox(props: { onCreated?: (config: SandboxConfig) => const createJob = () => sandboxes.state.data?.jobs.find((entry) => entry.kind === "create") const createPhase = () => sandboxCreatePhase({ adding: store.adding, job: createJob() }) const downloadProgress = () => (createPhase() === "downloading" ? createJob()?.progress : undefined) - // Null while the bar is up: the byte counter under it already says what is - // happening, and a custom image needs no download warning at all. + // Only speaks while something is happening: the byte counter under the bar + // already says what a download is doing, and an idle form needs no commentary. const statusMessage = () => { const phase = createPhase() if (phase === "queued") return language.t("sandbox.job.queued") if (phase === "creating") return language.t("sandbox.add.creating") - if (phase === "downloading") return null - return store.imagePath ? null : language.t("sandbox.add.downloadHint") + return null } const create = async () => { @@ -261,26 +266,41 @@ export function DialogAddSandbox(props: { onCreated?: (config: SandboxConfig) =>

{language.t("sandbox.add.imageOk")}

-
+ {/* Says what the numbers buy. The defaults live in the + placeholders and the ceilings in the out-of-range message, so + neither needs restating here. */} +

{language.t("sandbox.add.resourcesHint")}

+ {/* Grid, not flex: the input sets its own `width: 280px` and + `flex: none`, so two of them side by side overflowed the dialog + rather than sharing the row. Grid tracks size them, and + `!w-full` is how the rest of the app overrides that width. */} +
setStore("cpus", event.currentTarget.value)} /> setStore("memoryGiB", event.currentTarget.value)} />
-

{language.t("sandbox.add.resourcesInvalid")}

+

+ {language.t("sandbox.add.resourcesInvalid", { + minCpus: SANDBOX_CPUS_RANGE.min, + maxCpus: SANDBOX_CPUS_RANGE.max, + minMemory: SANDBOX_MEMORY_MIB_RANGE.min / 1024, + maxMemory: SANDBOX_MEMORY_MIB_RANGE.max / 1024, + })} +

diff --git a/packages/app/src/sandbox/resources.ts b/packages/app/src/sandbox/resources.ts index 55125845a6..2aea456431 100644 --- a/packages/app/src/sandbox/resources.ts +++ b/packages/app/src/sandbox/resources.ts @@ -3,6 +3,8 @@ import type { SandboxResources } from "./types" // Mirrors SANDBOX_*_RANGE in @codev/sandbox: the app package must not import // the node-flavored package, and the main process re-validates on the way in, // so this copy only decides when the dialog's Create button is enabled. +export const SANDBOX_DEFAULT_CPUS = 4 +export const SANDBOX_DEFAULT_MEMORY_GIB = 4 export const SANDBOX_CPUS_RANGE = { min: 1, max: 32 } as const export const SANDBOX_MEMORY_MIB_RANGE = { min: 1024, max: 65536 } as const diff --git a/packages/codev-sandbox/src/provider.ts b/packages/codev-sandbox/src/provider.ts index 993567eb2a..fe7681aa50 100644 --- a/packages/codev-sandbox/src/provider.ts +++ b/packages/codev-sandbox/src/provider.ts @@ -40,6 +40,12 @@ export type ResolvedSandboxMount = { guestPath: string } +// What a sandbox gets when it sets no limits of its own. Named here rather +// than left as literals in the vz backend because the Add sandbox dialog +// quotes these numbers to the user; they must not drift apart. +export const SANDBOX_DEFAULT_CPUS = 4 +export const SANDBOX_DEFAULT_MEMORY_MIB = 4096 + // Ceilings keep an unusable VM (0 cpus, 64MiB) or an oversubscribed host out // of the persisted config; the guest needs real headroom for the office // toolchain. Both ends inclusive. diff --git a/packages/codev-sandbox/src/vz/provider.ts b/packages/codev-sandbox/src/vz/provider.ts index 33f34c8a4f..1d3f73f587 100644 --- a/packages/codev-sandbox/src/vz/provider.ts +++ b/packages/codev-sandbox/src/vz/provider.ts @@ -10,6 +10,7 @@ import type { SandboxAsset } from "../manifest" import { resolveGuestMounts } from "../mounts" import { macosSandboxAvailability } from "../policy" import { createSandboxSocketDir } from "../socket" +import { SANDBOX_DEFAULT_CPUS, SANDBOX_DEFAULT_MEMORY_MIB } from "../provider" import type { SandboxAvailability, SandboxConfig, SandboxJobOptions, SandboxProvider } from "../provider" import { createVzHelperEventReader, type VzHelperEvent, type VzVmConfig } from "./protocol" @@ -182,8 +183,8 @@ export function createVzProvider(options: VzProviderOptions): SandboxProvider { disk: dirs.disk, cmdline: VZ_CMDLINE, // Per-sandbox limits win over the provider-wide defaults. - cpus: config.cpus ?? options.cpus ?? 4, - memoryMiB: config.memoryMiB ?? options.memoryMiB ?? 4096, + cpus: config.cpus ?? options.cpus ?? SANDBOX_DEFAULT_CPUS, + memoryMiB: config.memoryMiB ?? options.memoryMiB ?? SANDBOX_DEFAULT_MEMORY_MIB, shares: [ { tag: "workspace", path: config.projectDir, readOnly: false }, { tag: "bundle", path: options.bundleDir, readOnly: true },