Bug 6: a team switch left the previous team's cloud workspace in the
sidebar (as its own project header) and kept its lease scheduler, wake
and orcad calls running under the new team's token.
- Orca Cloud runtime records carry the owning control-plane account
(cloudAccountId, sandbox-control's org:<id> / user:<id> form), read
once at the create write-ahead from the active profile.
- Backfill: on the 30 s beat and on every auth change, list the active
team's sandboxes (GET /v1/sandboxes is scoped to the caller's account)
and file the listed records under that team; it re-lists only when the
team or the unfiled set changes, and drops a list that answered for a
team the user has since left.
- The session registry, OOM polling and the window's cloud workspace ids
skip every record outside the active team, so the old team's sandboxes
get no keep-alive or orcad calls after a switch.
- Sidebar rows, the jump palette and project headers hide another team's
sandbox hosts and any whose team is unknown (or when signed out).
Bug 7 (per coordinator): every Orca Cloud sandbox's primary clone stays
hidden; each cloud workspace row appears once.
* feat(cloud): DS-40 Fix with agent for failed Orca Cloud environment builds
A failed environment build (Settings > Orca Cloud > Cloud environments) now offers, to admins on the
latest build, Try again next to Show error, plus Fix with agent when the install script is what failed.
Fix with agent opens the composer on Orca Cloud with the prompt prefilled ('The environment build
failed at {{step}}. Fix the install script so the build succeeds.'), the current install script and the
log lines already visible in the viewer (redacted, 60 lines / 6 KB, fences neutralized). The new cloud
workspace boots from the environment's last good build, or a blank sandbox if none succeeded, as every
new cloud workspace does. orca cloud env only works on the desktop, so the agent hands back the full
fixed script for an admin to save and rebuild.
Install scripts only run in environment builds (never during a workspace create), so this lives on
the environment panel's failure summary rather than the create sheet.
* fix(cloud): DS-40 design follow-ups: two actions, and clone failures
(a) The failed-build summary drops Show error: the failed step already opens on its error, and the
summary title now jumps back to it (log scrolled, collapsed or virtualized). Actions are Try again
(outline) and Fix with agent (ghost). The unused cloudEnvironment.log.showError key is pruned.
(b) Clone failures: from the visible log, repo-content failures (submodules, LFS, paths that can't be
checked out) offer Fix with agent with 'The environment build failed while cloning {{repo}}. Fix
the repository setup so the clone succeeds.' (no install script attached; the agent fixes the repo
on a branch and opens a PR). Access and auth failures offer Give access (GitHub install for that
repo), never an agent. Anything unrecognized, or a clone log that isn't loaded, offers neither.
New fixtures panelCloneFailedContent / panelCloneFailedAccess; classifier and helper unit tests;
fixture spec 3/3.
- A16 panel WSL rows: 'Account: {{name}} (selected in Accounts)', read from this computer's
settings (the selection the desktop pushes to the distro), so a stopped distro is never started.
- Classic SSH probe (private): hasLogin when an API key signs the agent in, from presence-only
checks on the host (Claude: credentials file or macOS Keychain item existence, no -w/-g;
Codex: grep -q for a refresh_token key). Each prints only yes/no; no value is read or returned.
S3 shows 'API key overrides sign-in' (needs attention, Review); the panel row says the same.
Spec v4 §4 annotations 4-5, Q8. Main runs the CLIs' own status commands (claude auth status --json,
codex login status) over the host's SSH exec only while it is connected, 60 s cache per target;
only verdicts reach the renderer. Code sign-in runs Claude's paste-code login and Codex's device
code over SSH exec (no callback, no forward): the pasted code goes to the CLI's stdin, never argv;
codes, tokens and CLI output are never logged. The A16 panel's classic-SSH rows get Sign in (inline
paste / device code), Sign out, the dated last report and the spec's disabled reason while
disconnected; S3 checks classic SSH hosts the same way. No #40228 file edited.
GET /v1/agent-credentials adds providerState {claude, codex: on|off|disabled} next to providerEnabled.
Clients read it through orcaCloudProviderState (older servers: disabled = kill switch; a state that
contradicts providerEnabled is never trusted). Flag off is a new 'unavailable' kind: Settings says
it isn't available in Orca Cloud yet (no 'right now / until it's back'), and the composer hides the
Agent row like an agent without an account path. Kill-switch copy is unchanged.
The desktop parsed the DELETE answer with the full strict sandbox view, so any view field the control
plane adds later (e.g. archive state) turned a successful delete into 'Orca Cloud returned an unexpected
response'. Destroy now parses only what confirms the delete (id + status) and ignores unknown keys; a
body without them is still rejected. Tests cover both.
- setup-token on Windows goes through cmd with the shared quoting as one verbatim
ConPTY line; the child env is buildClaudeChildProcessEnv (no inherited auth) and
runs in the home directory.
- Token capture drops styling escapes, breaks on cursor moves, waits 1 s for a
repaint and keeps the longest complete copy, so a split copy is never stored.
- Broker ids must start with a letter or digit (no '.'/'..' path tricks), checked
at parse time too; server error names are looked up with a Map.
- A setup belongs to the Orca identity it started under: a changed identity drops
the captured token / cancels the broker and resets the rows.
- A token being stored can't be cancelled or raced by a paste or a new start.
- The Codex driver only shows OpenAI sign-in pages and caps the wait at 20 min.
- The paste field clears in a finally; a rejected IPC reads as unknown.
Main:
- agent-credentials client for /v1/agent-credentials, /v1/agent-sources and the
Codex broker routes; replies and errors are codes only. The shared control-plane
request keeps the server's error name (HostedSandboxRefusalError) so newer
routes (broker_in_progress, rate_limited, no_golden_image) map to codes.
- claude setup-token runner (pseudo-terminal in production, fake child in tests):
captures the sk-ant-oat01 token in memory, never logs it, PUTs it from main
with the local Claude account email as label. Capture failure or no claude
offers "Paste the token instead"; a pasted value goes renderer -> main once.
- Codex broker driver: device code, poll until active, cancel, code expiry.
- orcaCloudAgents IPC + preload bridge: states and codes only.
Renderer:
- Settings > Agent accounts > Orca Cloud (S0, spec §5): not set up, setting up
(browser / preparing / device code / paste / saving), state 7 Source choice,
active, expiring, expired/revoked, kill switch, signed out, load and save
errors; the mandatory policy line; Source via SettingsSegmentedControl.
- The composer's Agent row (cloud-start G) replaces ComposerCloudAgentHint:
from your Orca account / team key (collapsed), Set up once -> S0, kill switch
info, signed-out copy; hidden for members blocked by billing; never blocks.
- Telemetry cloud_first_start_agent_coverage (once per install).
- Fixtures: settings/agent-accounts/OrcaCloudAccounts:*, new-workspace/OrcaCloudAgentSetupRow:*.
- Signed in but Orca unreachable: rows keep their last-known state (from the per-account ledger:
names, key ids and timestamps only) under "Can't reach your Orca account right now", with no
account actions that would fail.
- Saves made while signed in that miss the account (offline or a transient failure) are queued in
the ledger and retried at start, on wake and with backoff while waiting; the timer stops when
idle. Only that key at that version: edits, removals and signed-out saves are never uploaded.
- Fixtures: removePrompt, cloudUnreachable.
While signed in to Orca with Orca Cloud, saving an Agent API key also writes it to the
personal Cloud secret of the same variable through /v1/secrets. The sealed vault stays the
cache and the source for non-Cloud launches; host scope stays local. Only every-host keys the
egress proxy swaps are written. A main-side ledger (names only, per account) tracks what this
computer wrote, and only those copies can be removed from the account, after confirmation.
Import from this Mac: main scans OpenCode's auth.json, ~/.gemini/.env and the login shell env;
the renderer sees names, sources and last fours only, and main re-reads values on import.
OAuth sign-ins are listed, never read.
Billing Worker:
- trials + trial_claims (migration 0005): 2 sandbox hours per new team, no card, no Stripe.
- POST /v1/billing/trial grants it (idempotent, any member); grantTrial/trialOffer exported for
the setup session. offers.trial on GET entitlements only when this caller could still claim it.
- Abuse limits: once per team, Orca user, normalized verified email, GitHub user (token claim)
and GitHub owner (POST /v1/internal/trial/github from the binding; a reused identity revokes),
disposable-domain blocklist, 3 grants per IP per day. Keys stored as SHA-256 hashes.
- Entitlement trial {includedSeconds, usedSeconds, remainingSeconds, exhausted}; metered from
usage_events, concurrency 1, never overage; trial_used_up (suspendRunning) / trial_unavailable.
- Any subscription ends the trial; unused trial hours lapse.
- e2e: trial path (grant, no Stripe customer, concurrency, metering to used up, once per user,
plan ends it) with cleanup of the run's abuse keys.
Desktop: trial_used_up / trial_unavailable classify as 'plan'; copy in billing, phone and CLI;
"Trial" badge, TrialPlanSummary in Plan & billing and the plans page strip, account menu row
"Trial · {{count}} sandbox hours left" (Choose plan for owners/admins); billingClaimTrial IPC
and claimOrcaCloudTrial() for the composer.
Usage alerts only fired on a billing read, so a team crossing 75/90/100% mid-session
wasn't alerted until a restart, a team switch or opening a billing view. Main now owns one
app-wide schedule: the hosted recipe's onReady (create and resume success) asks the main
window for a forced billing re-read, and the session registry's sync (already run on every
lifecycle change and on the existing 30 s beat) reports whether any cloud workspace is
running, which drives a 30-minute poll with no timer of its own. Requests go only to the
trusted main window so extra windows never stack reads; the per-level ledger still dedupes.
- Signed out, Set up and start opens one tab. The authorize page shows the
setup account step (A2). Once the code is exchanged, the loopback holds the tab,
creates the billing setup session and 302s the same tab into the setup pages.
- Signed in, the session is created and its start URL is opened.
- IPC: orcaProfiles.cloudSetupStart / cloudSetupGet / cloudSetupLink (Copy setup
link mints a fresh one-time link).
- Renderer: cloud-setup-session.ts polls the session (2 s for 3 min, then 10 s),
keeps a snapshot for the composer and tracker, and switches the profile to the
team setup chose.
- Plans dialog: Checkout joins the open setup session. A personal account
(409 team_required) goes through setup instead, which makes or picks a team
first.
- Billing: POST /v1/cloud-setup/sessions/:id/link.
- Desktop notification (title "Orca Cloud", body reuses the in-app usage sentence) when a
billing read crosses 75, 90 or 100%, once per level per account and billing period. Main
owns the ledger (userData/orca-billing-usage-alerts.json), so reloads, re-renders and
second windows never repeat it; a new period starts over. Members are never notified.
- Click opens Settings > Orca account (Plan & billing) via ui:openSettings with a target.
- The in-app usage notice (account menu, billing panel, plans page) is now owners/admins only.
- Billing panel: muted note under the Overage description that owners and admins are alerted
at 75%, 90% and 100%.
- Fixtures: account menu at 90% (admin and member), billing panel member at 90%.
Security re-review: the VM-side guard matched a fixed list of names, but the desktop key store
accepts any variable name, so a custom-named key (CODEX_API_KEY, GH_TOKEN, ...) could still reach a
hand-paired cloud VM. orcad now advertises host.orca-cloud.v1 in status.get when ORCA_IS_CLOUD=1, and
before any desktop key goes to a runtime host the desktop asks (cached 60 s); a cloud answer, or no
answer, sends none. The VM-side drop stays as defense in depth.
- N9: orca://cloud/setup/return?session=cs_… is routed (open-url + second
instance), validated, focuses Orca and fans out to renderer listeners
(onCloudSetupReturn) so the setup session re-reads at once.
- N10: cloud_setup_step_{started,done,failed}, cloud_setup_first_sandbox and
cloud_setup_plan_chosen, enum-only, derived from the setup rows.
- X1: setup error kinds (timed_out, checkout_canceled, bank_step,
trial_unavailable, team_create_failed, wrong_account) and their actions.
- B2: billing POST /v1/billing/team-plans returns other teams' plan state with
the caller's own token; auth confirms membership from that token (no new
credential). Desktop IPC billingTeamPlans + useSwitchToPlannedTeam. The team
switch logic moves out of SidebarTeamSubmenu so the composer can reuse it.
A shared list of agent and model-provider credential names (AWS, Bedrock, OpenRouter, Groq, xAI,
Mistral, DeepSeek, Together, Fireworks, Perplexity, Cerebras, Moonshot/Kimi, DashScope, Z.ai,
MiniMax, NVIDIA, Cohere, Azure OpenAI, Hugging Face, Amp, Factory, ...) marks every one that has no
egress swap. AWS keeps its own sentence; others read 'Orca can't swap {{name}} at the network edge,
...'. The control-plane test now walks the server's agent keys, this list and the desktop's Agent
API key presets: each is swapped, or flagged.
Verified on a dev sandbox (Claude Code 2.1.319, codex-cli 0.166.0): runCommand runs as ubuntu with
HOME=/vercel (same user as orcad); `claude auth logout` and `codex logout` exist and exit 0; the
script reported {loggedOut:2,left:0}, removed both credential files and kept other ~/.claude.json keys.
* fix(w8): cloud-icon rule on the phone: a paired desktop's Orca Cloud workspaces lead with the Cloud glyph + state dot (worktree.ps carries orcaCloud.status), home cloud rows read 'Cloud workspace, …', muted Cloud before the cloud workspace title
* fix(w8): cloud-icon review: glyph label merges into the row (no own focus stop), no filled ring behind the dot (pressed/active fills), newest VM record speaks for a workspace
* fix(w8): cloud-icon review 2: a cleaned-only workspace stays cloud (as on the desktop) with no dot, a live VM beats a cleaned one, cleanup_failed draws no forever-pulsing dot; store-order test
* test(w8): cloud-icon ps status test without a cast
* fix(w8): cloud-icon dot sits mostly outside the glyph's bottom-right (design-squad), clear of the title
- Panel sections 2–3 on Orca Cloud (standalone, for gap-agents' AgentHostPanel shell):
Cloud-secret key rows with scope chips and shadowed keys listed disabled with the reason;
effective variables; missing names with inline Add / Add mine; asleep and shared (D13) states.
- S2: missing = environment envVarNames − effective secrets for the repo and caller; Paste .env,
per-name scope, batch save, build-time team-only note (Q7), post-open 'secrets missing' notice.
- S4-b: card for orcad secrets.requested/requestSettled; Personal by default, Repository only if
writable, agent scope as hint; N more requests; expired and saved-after-expiry; touchRequest at
most once a minute; Restart {{agent}}; value saved via Cloud secrets PUT, then resolveRequest.
- Main: cloudSecrets.workspaceSharing reads GET /v1/agent-sources (owner, sharedWorkspace).
- Fixtures for every state, dark and light.
- Overage stays $1.00 per sandbox hour (ORCA_CLOUD_OVERAGE_CENTS_PER_HOUR) and is now billed by
the minute: a per-minute metered TEST price (unit_amount_decimal = rate / 60 to 12 decimals,
transform_quantity divide_by 60, round up), created the same way by stripe-test-setup.ts.
capSeconds rounds the cap down to whole minutes and spentCents mirrors the invoice, so the
cap math matches Stripe. The meter still counts seconds.
- Opting in re-prices an overage item still on an older price (e.g. $0.75 / h) instead of adding
a second item on the same meter, and removes any extra stale one.
- Alerts at 75%, 90% and 100% of included hours (warnAtPercent).
- New top-level entitlement.suspendRunning: payment grace expired, or at the limit (out of hours
with overage off, or at the spend cap). The start check and usage answers carry it, so the
control plane puts running sandboxes to sleep (snapshot kept, never deleted). Payment fields and
/v1/internal/suspensions stay payment-only, so no data-retention clock starts at a cap.
- Copy (desktop billing panel, plan card, build sheet, phone, CLI): "billed by the minute" after
the rate; at the limit "Running sandboxes go to sleep and new ones can't start. Nothing is
deleted, and your files are safe."
- Worker: run orca-agent-signout (each agent's logout, then credential files) on a running sandbox
before the provider delete; bounded (20 s) and best effort, never blocks the delete.
- Environment builds: scrub agent auth (--scrub-only) before the snapshot; fail closed.
- New VM script registered in VM_SCRIPTS + provision loop; vm-scripts regenerated.
- Main IPC ephemeralVm:forwardedEnvSummary returns names only, grouped by credential-looking.
- P-4 warning component (+ fixture) shown under Settings > recipes; delete dialog line for awake
Orca Cloud sandboxes.