You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit bf6ca05
Browse filesBrowse the repository at this point in the historyBrowse files
feat(desktop): manage extra sandbox folder mounts from settings
Each sandbox's overflow menu (Settings -> Servers) gains a Folders...
item opening a manage-mounts dialog: the original project folder stays
fixed, extra host folders can be added (multi-select native picker) and
removed, read-write. Windows applies changes hot; macOS and Linux warn
that saving restarts the sandbox (disk state survives, the live session
drops), and macOS also hints that an old-image sandbox needs Reset
before new folders appear.
Wiring: a sandbox-update-mounts IPC channel validates shape, structure
(shared validateMountPaths: duplicates, nesting, UNC), and existence
main-side before calling the controller; normalizePersistedSandbox
tolerates old records and drops malformed mount entries rather than the
sandbox; the renderer connection carries the resolved runtime mounts.
The app mirrors the mount types (it never imports the node package),
validates the draft inline via mounts-model.ts for i18n-able messages,
and seedSandboxProjects now opens one project per mounted folder,
naming each after its host basename. The "cannot be changed later"
copy in the add dialog is reworded in en and es.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: DIVERGENCES.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -191,7 +191,7 @@ from the squash message first, or use a plain merge.
191
191
|`packages/desktop/src/main/updater.ts`|`autoUpdater.allowDowngrade``true` → `false`. | Upstream's `true` is safe against immutable GitHub release tags. The fork's feed is a mutable bucket path, where re-uploading or failing to replace a stale `latest.yml` would actively downgrade every client on the next 10-minute poll. | Keep `false`; take upstream's other updater settings. `channel = "latest"` and `allowPrerelease = false` stay as upstream has them — the first must match `publish.channel` in `electron-builder.config.ts`, the second is inert for the `generic` provider (it only steers GitHub tag resolution). |
192
192
| 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. |
193
193
| 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. |
194
-
| 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`; `server.tsx` adds the third `ServerConnection.Sidecar` variant `{ variant: "sandbox"; name; projectDir? }` with key `sandbox:<name>` (`projectDir` is the host folder mounted into the guest — the guest only ever sees its own mount point, so this is the only way the app can name a sandbox's project after the folder 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, and `sandbox.add.projectDirHint`, the standing explanation above the folder field); 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:<name>` 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. |
194
+
| 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`; `server.tsx` adds the third `ServerConnection.Sidecar` variant `{ variant: "sandbox"; name; projectDir?; mounts? }` with key `sandbox:<name>` (`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; and the `sandbox.mounts.*` family for the manage-folders dialog that adds/removes extra mounts on an existing sandbox); 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:<name>` 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. |
195
195
196
196
| 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. |
Copy file name to clipboardExpand all lines: packages/app/src/i18n/en.ts
+17-1Lines changed: 17 additions & 1 deletion
Original file line number
Diff line number
Diff line change
@@ -383,6 +383,7 @@ export const dict = {
383
383
"sandbox.server.menu.label": "Sandbox",
384
384
"sandbox.server.start": "Start",
385
385
"sandbox.server.reset": "Reset",
386
+
"sandbox.server.mounts": "Folders...",
386
387
"sandbox.status.starting": "Starting...",
387
388
"sandbox.status.ready": "Ready",
388
389
"sandbox.status.stopped": "Stopped",
@@ -394,14 +395,29 @@ export const dict = {
394
395
"sandbox.add.nameInvalid": "Use letters, numbers, dots, dashes, or underscores.",
395
396
"sandbox.add.projectDir": "Project folder",
396
397
"sandbox.add.projectDirHint":
397
-
"Choose the folder you want to work on. It is the only folder on this computer the sandbox can reach, it cannot be changed later, and the agent edits files in it without asking.",
398
+
"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.",
398
399
"sandbox.add.choose": "Choose...",
399
400
"sandbox.add.create": "Create",
400
401
"sandbox.add.cpus": "CPUs (optional)",
401
402
"sandbox.add.memory": "Memory GB (optional)",
402
403
"sandbox.add.resourcesInvalid": "CPUs must be 1-32 and memory 1-64 GB.",
403
404
"sandbox.add.downloadHint": "The first sandbox downloads its image; on a slow connection this can take a while.",
Copy file name to clipboardExpand all lines: packages/app/src/i18n/es.ts
+17-1Lines changed: 17 additions & 1 deletion
Original file line number
Diff line number
Diff line change
@@ -374,6 +374,7 @@ export const dict = {
374
374
"sandbox.server.menu.label": "Sandbox",
375
375
"sandbox.server.start": "Iniciar",
376
376
"sandbox.server.reset": "Restablecer",
377
+
"sandbox.server.mounts": "Carpetas...",
377
378
"sandbox.status.starting": "Iniciando...",
378
379
"sandbox.status.ready": "Listo",
379
380
"sandbox.status.stopped": "Detenido",
@@ -385,14 +386,29 @@ export const dict = {
385
386
"sandbox.add.nameInvalid": "Usa letras, números, puntos, guiones o guiones bajos.",
386
387
"sandbox.add.projectDir": "Carpeta del proyecto",
387
388
"sandbox.add.projectDirHint":
388
-
"Elige la carpeta en la que quieres trabajar. Es la única carpeta de este equipo a la que el sandbox puede acceder, no se puede cambiar después y el agente edita archivos en ella sin preguntar.",
389
+
"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.",
389
390
"sandbox.add.choose": "Elegir...",
390
391
"sandbox.add.create": "Crear",
391
392
"sandbox.add.cpus": "CPU (opcional)",
392
393
"sandbox.add.memory": "Memoria GB (opcional)",
393
394
"sandbox.add.resourcesInvalid": "Las CPU deben ser 1-32 y la memoria 1-64 GB.",
394
395
"sandbox.add.downloadHint": "El primer sandbox descarga su imagen; con una conexión lenta puede tardar un poco.",
"Las carpetas montadas aquí son las únicas de este equipo a las que el sandbox puede acceder, y el agente edita archivos en ellas sin preguntar.",
400
+
"sandbox.mounts.primary": "Carpeta del proyecto",
401
+
"sandbox.mounts.empty": "No hay carpetas adicionales montadas.",
402
+
"sandbox.mounts.add": "Añadir carpeta...",
403
+
"sandbox.mounts.remove": "Quitar carpeta",
404
+
"sandbox.mounts.save": "Guardar",
405
+
"sandbox.mounts.restartWarning":
406
+
"Al guardar se reinicia el sandbox. Los archivos en disco se conservan; las terminales abiertas y el trabajo en curso dentro del sandbox se detienen.",
407
+
"sandbox.mounts.imageHint":
408
+
"Si una carpeta no aparece dentro del sandbox después de guardar, restablece el sandbox para actualizarlo a la imagen actual.",
409
+
"sandbox.mounts.invalid.duplicate": "Esta carpeta ya está montada: {{path}}",
410
+
"sandbox.mounts.invalid.nested": "{{path}} se superpone con una carpeta ya montada ({{other}}).",
411
+
"sandbox.mounts.invalid.unc": "Las rutas de red no se pueden montar: {{path}}",
0 commit comments