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.
- One row from the Orca account, never the sandbox: 32 px ready line when every agent is ready at
mount, otherwise 48 px; every later state renders inside that height (Playwright: measured layout
shift 0, logs toggle never moves).
- R2 Set up runs S0's setup inline for each agent in turn; device code, browser, paste, failed,
expired, kill switch (per-workspace sign-in, queued until the sandbox answers), A6 desktop key.
- ⋯ menu: Source per agent in this workspace (new per-workspace setWorkspaceSource / workspaceSources
IPC; main resolves the sandbox id from its own runtime records), Manage agent accounts.
- Removes the v3 'Add an agent' build step and the per-sandbox Cloud sign-in row from the sheet.
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%.
- 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.
- 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.
- 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.
Desktop-sealed keys (Settings → Agent API Keys) ride every agent launch as plain env on other
hosts. An Orca Cloud VM would hold them in plaintext, so they are no longer sent there: runtime
calls to Orca Cloud environments skip them (provider record; otherwise any VM-provisioned host or a
Vercel Sandbox route counts, failing closed). The Add-an-agent view and the build sheet's key step
list each every-host key no Cloud secret covers yet, with its own 'Save as personal Cloud secret'
button: main unseals that one key and writes it to the user's personal Cloud secrets; the renderer
never sees the value, and nothing is uploaded without the click. The VM then gets a placeholder
the egress proxy swaps. Readiness no longer counts desktop keys as cloud access.
* feat(cloud): remote desktop single-use tickets, fit, clipboard and keyboard release
- VM: Xvnc on a 0600 socket with no VNC auth plus an X cookie; orca-desktop-proxy (websockify
subclass) admits one WebSocket per unused 60 s ticket from the same localhost origin, refuses
rebinding Hosts, scrubs tickets from logs, serves the page with connect-src 'self'.
- Worker: POST /desktop mints a fresh ticket per call (schemaVersion 2).
- Desktop: no secret in the tab URL; main hands the page tickets on load and reconnect (backoff,
floor, pause with Reconnect), only for the exact URL of a live forward it opened; keyboard stays
in the desktop until Ctrl+Cmd / Ctrl+Alt; a paste key pushes this computer's clipboard; sandbox
copies reach this computer after a copy gesture.
- UI: keyboard hint, asleep/starting neutral tone and mapping, persistent error toasts, Beta badge.
* fix(cloud): remote desktop review fixes
- Security review: noVNC host/port/encrypt pinned to the page's origin, CSP connect-src 'self',
exact-URL + live-forward trust, mint floor and per-minute cap, copy gate on copy gestures only,
X display cookie.
- Design review: archived is its own neutral state, starting covers waking, page messages say
"Wake it in Orca", pause overlay is a status for calm states and focuses Reconnect, hint wraps
and hides from assistive tech when faded.
- FE review: paste follows the keyboard layout, AltGr never releases, page calls settle on
navigation, state notices only on change, hint once per load/release, paste always pushes,
no committed bytecode, session tests.
* fix(cloud): remote desktop archived page message, hint layout, fixtures
* fix(cloud): remote desktop copy gate counts double-clicks (pointer events have no click count)
* chore(cloud): remote desktop lint (exhaustive pause reasons, no test casts or wall clock)
- Account/billing cards use glossary titles (Out of sandbox hours, Spend cap reached, Payment failed,
running-sandbox limits), role-aware next steps, and 'Nothing was created, and you weren't charged.'
- 409 github_not_connected: 'GitHub isn't connected for <owner/repo>' + Connect GitHub (POST
/v1/github/connect, opens the GitHub install page) + Try again.
- A repo without a GitHub remote is refused in main before the control plane is called, with
Start empty / Push to GitHub first.
- GET /v1/config serves createTiming (p50/p95 of recent create-to-ready); the sheet's 'usually ~Ns'
reads it and shows no hint without a measurement.
- Orca Cloud servers display as 'Orca Cloud' in sidebar host labels and the usage segment; legacy
'<repo> VM <id>' names are renamed at startup.
* feat(dictation): show pending dots while the engine holds speech it hasn't committed
The VAD pause segmenter already knows when a phrase is open. The recognizer
now posts speech-pending when a phrase opens and clears it once that phrase's
text is posted, and the desktop pill shows three dots after the text while it
is on. Only pause-cut on-device streams send it, so cloud, the paired web
client and phones keep today's pill.
* fix(speech): keep a lone click from turning the pending dots on
* Improve project group ordering across hosts
* fix(project-groups): restore each group order after a failed drop
A deleted group made the aggregate rollback stop early, leaving other groups at
the ranks from the failed drop. Restore each group independently and log the
swallowed single-update failures again.
* refactor(main): give project group RPC methods their own module
Keeps repo.ts under the 300-line cap as the project group surface grows.
* feat(speech): spell dictionary terms in dictation, with repo terms and Whisper prompt
* test(speech): pin host repo terms for phone dictation
* fix(speech): keep symbol terms whole, accept null setup params, resolve SSH repos
* refactor(speech): key transcript words once and type phone terms at the reply schema
* refactor(speech): merge speech-types imports in the speech IPC
Add an OpenAI-compatible model whose base URL, model and optional key
are set up in a Voice settings dialog and sealed together in ~/.orca,
so a key is never sent to a host it wasn't saved for. It reaches
OpenAI, Groq, Mistral and local servers, and replaces the unused
userModels setting.