feat(mobile): cloud workspaces — list, open, terminal, provisioning, actions
Merged
SA
3mo ago+1622+1622+0123456789012345678901234567890123456789-198-198-012345678901234567890123456789
Links
- Decision log:
apps/mobile/plans/20260817-cloud-workspaces.md - Follow-up ticket: SUPER-1915 (media/artifacts on mobile)
Summary
- Cloud workspaces (Blaxel sandboxes) on iOS with parity to the desktop's cloud surface: a Cloud section on Home, open a sandbox and drive its terminal, provisioning/failed states, rename/delete via the cloud router, gated by the
cloud-workspacesflag + the API's internal-only check. - Two fixes found on the way that affect desktop too:
deleteSandboxnever recognised the SDK's not-found (removing a failed workspace 500'd), and the builtin Claude agent refuses to launch as root in a sandbox (IS_SANDBOX=1+ baked Bypass Permissions acceptance; needs an image rebuild to land).
Why / Context
Desktop shipped cloud workspaces (#6505, #6555, #6566). Mobile reached host-service only through the relay by routing key; a sandbox has no host row, no presence, no relay path — it is reached directly at a brokered preview URL with a short-lived token. This PR gives mobile that second address source without cloud-specific code in every hook.
How It Works
- A sandbox is a pseudo-host keyed by the cloud workspace id (as on desktop).
hostServiceUrl(org, machineId)replacesbuildRelayHostUrland resolves sandbox addresses fromlib/sandbox-access; the HTTP client attachesX-Blaxel-Preview-Tokenper URL. Every hook that addresses hosts by id — terminals, git status, projects, agents — reaches the sandbox unchanged. useCloudWorkspaces(1s poll while provisioning) +useSandboxAccessmint for every ready row, re-mint at 80% TTL, onAppStateactive, and at dial time if stale (iOS freezes timers).useCloudWorkspaceItemsrestates each sandbox's served row under the cloud id;useWorkspaceHostresolves cloud ids first and lets the cloud row own the name.- The terminal WebView signs sandbox dials with
bl_preview_tokenon every attempt. The desktop's terminal socket works only because the edge sets an HttpOnlybl_preview_tokencookie that Electron replays; WKWebView has its own cookie store, so mobile can't inherit that (recorded indocs/cloud-sandbox-mismatches.md). - Home: Cloud section pinned above projects, visible regardless of the host filter and in the host-offline state; rows reuse
WorkspaceRow(spinner while provisioning, red dot when failed,+disabled until ready);useHostsTerminalsfans out over sandbox hosts; PR badges usecloudWorkspace.repoForProject. - Workspace screen:
CloudWorkspaceProvisioningState(steps, elapsed, stuck hint; failed + Remove) until the sandbox serves the row. Row menu / actions sheet / header actions route rename and delete tocloudWorkspace.rename/delete; the actions sheet drops the Host row for cloud.
Manual QA Checklist (done on the iPhone 16e simulator against two live Blaxel sandboxes through this worktree's local API)
- Cloud section lists live sandboxes; session marks and diff pills resolve over sandbox hosts
- Open → terminal socket connects (query-param token) → composer
terminal.send→ output streams back - Row
+→ composer →agents.runin the sandbox → new tab opens - New-session sheet lists the sandbox's agents; sessions sheet close →
terminal.killSession - Provisioning row + screen; failed row + screen; Remove from the failed screen deletes
- Row long-press menu: Rename/Delete/Copy ID/Share on ready rows, Delete only on failed rows, nothing destructive while provisioning
- Rename (row menu and actions sheet) writes the cloud row; list and title follow
- Actions sheet: project from the sandbox, no Host row, Delete offered
- Changes / files-changed diff / commits answer from the sandbox
- Not exercised: an overnight background token-expiry cycle (re-mint paths are in place); PR-icon tap (needs an open PR on the branch)
Testing
bun run typecheck(mobile, host-service, trpc)bun run lint- Manual, as above; screenshots in the internal report shared with @satya
Design Decisions
- Pseudo-host + registry instead of threading URLs through every hook: the same shape desktop uses (
hostId = cloud id, credentials keyed by URL); 16 hooks kept their signatures. - Explicit
bl_preview_tokenon every terminal dial: correct regardless of the edge cookie; the desktop should do the same rather than rely on/eventshaving dialled first. - Cloud row owns the name: rename never reaches the sandbox's copy, so the served row's name is overridden with the cloud row's.
Known Limitations
- Create from mobile is PR B (interim
cloudWorkspace.listProjects+ App-tokenlistBranches, sectioned project sheet). Externally created rows appear on the next 30s poll or pull-to-refresh. - The host-service/image change for Claude only reaches new sandboxes after an image rebuild.
cloud_workspaces.project_idstill points at retiredv2_projects; the environments direction is written intodocs/cloud-sandbox-considerations.md.
Follow-ups
- PR B: create from the composer.
- Sandbox launches the agent from the prompt itself; sandbox attachments to blob storage (both in the docs).
- SUPER-1915: media/artifacts on mobile.
Summary by cubic
Cloud workspaces on iOS: list, open, and run terminals by reaching each sandbox directly via a brokered preview URL with a short‑lived token. Previously mobile only reached relay hosts; now host resolution prefers a sandbox when the id is a cloud workspace, and terminals authenticate per dial.
- Host routing and auth:
hostServiceUrl(org, id)resolves a relay URL or a sandbox URL; the HTTP client attachesX-Blaxel-Preview-Tokenby URL.TerminalWebViewnow takes a host object and signs WebSocket dials withbl_preview_tokenwhen the host is a sandbox. - Cloud data and access:
useCloudWorkspaceslists all rows (provisioning/ready/failed), polls at 1s while provisioning, and prunes stale sandbox grants.useSandboxAccessmints/refreshes addresses for ready rows (80% TTL, on foreground, and on dial if stale) and fully retires credentials on scope loss or URL changes (epoch guards stop mid‑flight mints from resurrecting retired grants). Delete failures on failed rows are surfaced. - Home: adds a Cloud section above projects, visible even if the selected host is offline (offline takeover waits for the cloud list). Rows reuse
WorkspaceRowwith provisioning/failed treatment. Session marks and attention fan out over machine + sandbox hosts viauseHostsTerminals(30s for sandboxes on the list; an open screen observing the same key at 5s wins). PR badges for cloud rows usecloudWorkspace.repoForProject. - Workspace: shows a provisioning/failed screen until the sandbox serves; hides tabs until live. Rename/delete route to the cloud router; the Host row is omitted for cloud.
- Fixes:
packages/trpcdeletion now recognizes provider not‑found.packages/host-servicesetsIS_SANDBOX=1for sandbox PTYs; the sandbox image bakes Bypass Permissions acceptance for Claude. Docs record the edge‑cookie dependency, Claude findings, and the environments direction. - Gated by the
cloud-workspacesPostHog flag and an API internal‑account check.
Migration
- Replace
buildRelayHostUrlwithhostServiceUrlwherever host-service URLs or terminal dials are built. - Rebuild the sandbox image and create new sandboxes to pick up the Claude fix.
- Ensure your org is entitled and the flag is enabled.
Summary by CodeRabbit
-
New Features
- Added mobile cloud workspace support, including listing, search, sorting, refresh, terminal access, and workspace lookup.
- Added provisioning and failure screens with progress details, recovery, and removal options.
- Added cloud workspace rename and delete actions with confirmation prompts.
- Added secure sandbox access with automatic credential refresh.
- Added cloud-specific status indicators and disabled actions during provisioning.
-
Bug Fixes
- Improved host routing and handling of missing sandbox workspaces.
- Improved terminal environment setup and sandbox authentication.
- Prevented cleared sandbox credentials from being restored by in-flight requests.