- orcad: only the owner's desktop may attach/settle; devices paired through a grant are listed
by orca-sandbox-grant and refused (forks keep their offer's origin id)
- desktop: per-workspace limits (3 waiting, 60/hour), always-allow runs one at a time and 30/hour,
one banner per burst, refusal floods logged once a minute
- dialog: commands over 20 lines refused; long ones must be scrolled to the end; agent name muted;
Turn off for this workspace; read delay counts from when a prompt reaches the front
- lookalike spaces and combining marks refused; settled events only close their own workspace's prompt
- executor reports soon after the shell exits even if an escaped process holds the pipes; hard stop
- audit log keeps 3 files, long commands stored as prefix + sha256
- rules expire after 30 days and are cleared when a shared workspace is allowed
- orcad relays (localCommands.request/status/attach/settle); the desktop that owns the workspace decides
- off by default per workspace; owner only; shared workspaces need the owner's explicit OK
- approval dialog: full command and folder, Allow once / Always allow this command / Deny
- minimal env (PATH, HOME, locale), /bin/sh, no stdin, timeout, 128 KB output cap per stream
- audit log on this computer; Settings > Orca Cloud > Commands on this computer
- run_local_command MCP tool (orca local-command mcp) registered for Claude Code and Codex in cloud VMs
- interrupted-turn-continue reads its delivery helpers at call time: the cloud workspace store
imports it, so launch-agent-picker-prompt.test's partial paste mock failed at module load
- plugin-shot-editor test marks its temp cwd as a repo root so the default shots dir (which walks up
to the nearest plugin folder / repo root) can't be redirected by anything above the shared temp dir
AI provider accounts becomes Agent accounts: a page whose first part is This computer (or the
selected host), holding the existing provider accounts, switching and usage. renderAccountsSettingsSection
takes additionalSections so other parts can be added below it with AgentAccountsSubsection. The
section id stays 'accounts', so every deep link keeps working.
The Settings nav, its group labels and the matching section titles move to sentence case
(Orca account, Computer use, SSH hosts, ...). In-app, CLI and docs references to
'AI Provider Accounts' now say 'Agent accounts'.
design-squad, after the W6 live run: an unknown asker reads 'A command asked for NAME' with no repeated 'from' line; the saved detail no longer claims the agent never gets the value (it is a plain env var); the CLI notes say the same. The saved card is announced, then dismisses itself after 6 s (paused while hovered or focused); while asking, scrolling or typing in the pane folds it to the pill.
--wait is a global boolean flag, so '--wait 300' left 300 as a positional and the command failed with 'Unknown command: secret request NAME 300' (found in the W6 live e2e). A spec can now list optionalNumberFlags; secret request does for wait.
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.
- 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."
- Agent and worktree come from the terminal orcad verifies from launch
evidence (verifiedCallerTerminalHandle, shared with turn-sender); the
terminalHandle param is gone and a worktree hint is kept only when the
host knows it.
- secrets.* are accepted, and their events sent, only on cloud orcad
(ORCA_IS_CLOUD=1 + ORCA_SANDBOX_ID); already-set never reads orcad's env.
- touchRequest emits secrets.requestUpdated; natural expiry emits
secrets.requestSettled {status: expired}.
- orca-env skips the control plane's reserved names and prefixes, with a
test that reads them from secrets.ts.
- reason drops bidi controls and turns C0/C1 controls into spaces.
orcad keeps agent-initiated secret requests in memory with the contract's
limits (3 pending, 10/hour, 15 min TTL, touch capped at 60 min, dedupe per
name), derives the agent from the calling terminal, and announces
secrets.requested on runtime.clientEvents (replayed on attach; phones get
status only). The CLI waits on it and prints the D4 reload hint; the VM
ships orca-env and makes zsh read the session env hook.
- workspace destroy -> delete (destroy kept as a hidden alias)
- glossary states (Starting/Running/Asleep/Waking/Couldn't start/Archived/Deleting); list columns
ID NAME STATE REPO BRANCH LAST ACTIVE; raw statuses and codes only under --json
- error copy: resume_failed wake, 409 busy, out of sandbox hours vs spend cap, team owner/admin,
Try again in N seconds; no VM/organization wording; whoami prints team:
- --size dropped from workspace create and worktree create --on cloud; show prints
Sandbox size from ORCA_CLOUD_SANDBOX_SPEC
- orca cloud routine list | run <id-or-name> [--wait]
- control plane: public workspace view adds repoBranch and lastActivityAt
W10 wake-visibility P1s. stderr '- …' lines (NDJSON phase events with
--progress json; --json defaults to none; stdout keeps only the result):
- wake: 'Waking <ws>...', one 'Still waking (Ns)…' line at 10 s,
'<name> is awake (took Ns).', or, when the API answers still waking
after its wait, '<name> is still waking. Check with: …' (exit 0, status
waking kept in the result)
- sleep: 'Putting <ws> to sleep...' / '<name> is asleep (took Ns).'
- worktree create --on cloud: 'Still starting (Ns)…' at 10 s and
'Workspace <id> is running (took Ns).'
Lifecycle handler moved to cloud-lifecycle.ts; shared helper cloud-progress.ts.
* feat(cli,sandbox-control): orca repo add --display-name; cloud sandboxes name the project after its repository
The VM checkout lives at /vercel/sandbox/project, so boot-time registration (#104) named the repo
'project'. repo.add already accepts displayName; the CLI now exposes it as --display-name and
orca-sandbox-start passes the repository basename from ORCA_REPO_URL.
* fix(sandbox-control): golden builder polls detached builds with a bounded wait=true read; image-only merges trigger a rebuild
The provider reports a detached command's exit code only to a wait=true read (W1's finding, #123):
stepBuild's plain GET is what kept finished golden builds 'running' until the deadline. stepBuild and
the operator rotation now use the same bounded wait probe (4 s); the exit-file marker stays as a
fallback. golden-source's VM path list now includes cloud/apps/sandbox-control/image/ (VM scripts,
provision, manifest), so a script-only merge (e.g. orca-desktop-proxy) rebuilds the golden too.
worker-start defaulted --setup to run, so new worker worktrees ran the
setup script even when the project had setup turned off. Without an
explicit --setup, a worker now skips setup when the project skips it by
default; run-by-default and ask keep running it as before. Remote
workers resolve this on the host that owns the project.
Turning in-workspace CLI access off revokes every workspace token, so the
in-VM CLI sees 401, not 403 vm_cli_disabled. Say so and point at the
setting first (verified on a real VM).
* docs: load Orca browser guidance for ordinary web tasks
Extend the Orca CLI skill description to include local and external page opening, reading, testing, and captures while working in Orca. Preserve explicitly chosen browser tools and the Computer Use boundary. Regenerate discovery/install bytes and append the new digest.
Fixes#39880
* fix: route existing Edge windows to desktop browser control
* docs: preserve configured desktop-control tool precedence
* docs(skills): scope orca-cli browser trigger to page work and keep the Computer Use rule
Drop "reading" so doc lookups stay on fetch tools, say plain HTTP/API calls don't need the skill, and restore the original Computer Use sentence and guide body, which the issue didn't ask to change.
* docs(skills): list Edge among existing browser windows in the orca-cli guide
The wider page trigger makes "screenshot my open Edge tab" load orca-cli, and the guide named only Chrome and Safari as windows that go to desktop control.
---------
Co-authored-by: Kelvin Amoaba <97001695+AmoabaKelvin@users.noreply.github.com>
Workspace tokens tell the in-VM orca cloud CLI to call publicOrigin(env), but
that host was only reachable when EGRESS_EXTRA_HOSTS listed it (previews did,
the dev Worker did not), so every in-VM call failed with 'fetch failed'.
extraEgressHostsFor(env) now always adds the public API host. The CLI's
cloud_unreachable hint inside a VM names the egress allowlist.
- drop the duplicate 'cloud' launch root (cloud env already registers it)
- keep cloud env on the runtime: only the cloud API commands ignore --environment
- list cloud messages in root help
- register the 'sender' launch root (parity test was red on integration)
Private build. Talks HTTPS to the Orca Cloud public API, never to the Orca
runtime, so it works inside a VM with no desktop app. Credentials:
ORCA_CLOUD_TOKEN (+ ORCA_CLOUD_API_URL) -> ~/.config/orca-cloud/credentials.json
(written at boot in VMs when vmCliEnabled, or by orca cloud login, 0600).
--json prints { ok, schemaVersion: 1, result }; API failures map to stable
cloud_* codes with next steps (vm_cli_disabled, own_workspace, scopes, 401).
* feat(orchestration): mail agents on a paired server with send --on, replies come back by themselves
* fix(orchestration): gate --on on the carrying Orca, ack retried deliveries, refuse payloads and federated workers
* chore(orchestration): annotate peer-mail row casts and drop a wall-clock wait in its test
* chore(orchestration): name mail-between-servers codes server_mail_unsupported and mail_requires_server_pairing
* docs(cli): list --on in the orchestration reply usage
* fix(orchestration): name --environment, the flag on main, in agent mail refusals and the guide
* chore(orchestration): mark the two deliberate agent-kind branches mail between servers needs
* docs(cli): leave --on out of the reply usage; it is accepted only to refuse it
* fix(orchestration): keep peer conversations to peer ids, survive resets and re-pairs, keep pulling on a registry error
- A server takes only peerconv_ conversation ids, so a paired computer cannot name another Orca's dispatch in the relay.
- A replayed reply after a mail reset is acknowledged, not retried forever; reset tasks keeps replies waiting to be pulled.
- A re-paired server starts a new conversation instead of refusing every later send and reply.
- A failed registry read keeps the pull loop retrying.
- Drop the two ast-grep-ignore lines that only #40193's rule needed.
* fix(orchestration): worker-start --on runs on the caller's own Orca, like send --on
An inherited ORCA_ENVIRONMENT sent worker-start --on to that server, so the
--on command the agent refusal suggests did not work. One rule now keeps
send, reply and worker-start with --on on the caller's own Orca.
* fix(orchestration): refuse reply --on on both sides of a conversation between servers
The side that opened the conversation still took --on when it named the right
server; the reply usage says --on is accepted only to refuse it.
transcribe-cpp is now the only on-device engine on every platform, so
the sherpa-onnx packages, their packaging (extraResources, the darwin
libonnxruntime prune, the native-variant assert and the Linux libstdc++
exemption), the sherpa recognizer in the speech process and the
per-model engine choice all go. Hotwords are written as plain terms.
* feat(speech): run GGUF models on transcribe-cpp, falling back to the CPU
The speech host child can now load a second engine. A model whose files
are GGUF runs on transcribe-cpp (Metal on Apple silicon, Vulkan on
Windows and Linux, CPU otherwise); everything else stays on sherpa-onnx,
which remains the only engine on Intel Macs.
A GPU that fails to load, errors, stalls for 25 s or crashes the process
moves the dictation to a CPU-only process (TRANSCRIBE_BACKENDS=cpu) that
replays the audio the GPU had not decoded yet, so no words are lost or
repeated. A GPU that crashed or would not load is remembered per
transcribe-cpp version and OS release, and voice.speechGpu = 'off' keeps
it on the CPU.
Packaging copies transcribe-cpp, koffi and the target's native package
into resources/node_modules outside asar, fails when a target's package
is missing, and ships the MSVC runtime beside transcribe.dll on Windows.
No catalog model is GGUF yet, so nothing runs on transcribe-cpp until
the catalog change that follows.
* fix(speech): keep the speech queue running after a failed task, and drop audio frames after a discard
* fix(speech): keep the engine's file reads off the main thread's hot paths
The engine lookup and the GPU failure marker are read once per process
instead of on every dictation start and mobile catalog request, the marker
is written asynchronously, and the child reads the hotwords file with
fs/promises. The baseline moves the existing engine presence checks from
stt-worker-paths to the shared package table and adds the once-per-process
marker reads.
## W4: team collaboration on Orca Cloud workspaces
### Control plane (`cloud/apps/sandbox-control`)
- **Registry** (`0024_workspace_registry.sql`, applied on dev D1): owner (reassignable), `created_by`, branch, `visibility` (private default / team), section, last activity, token spend. Reuses 0020's `name`/`archived_at`.
- **Routes under `/v1/team/*`** (W5 owns `/v1/workspaces`): list with Mine/Team/All, By, In, Archived filters; PATCH (rename, share or make private, section, archive, reassign owner); presence heartbeats; wake and lease for teammates; links resolve; org prompts/PR instructions (compare-and-set).
- **Security fix:** `/v1/sandboxes/:id` used to be account-scoped, so any org member could resume a teammate's sandbox and receive its orcad pairing code. It is now owner-only; admins can still suspend and destroy.
- **Per-member, per-device E2EE grants:**
- The VM mints a single-device orcad offer and seals it to the requesting device's X25519 key.
- The control plane stores only the orcad device id.
- Revocation runs `orca pair --revoke`, which closes the device's live sockets, plus an on-disk registry removal.
- When the VM is asleep, the revoke is queued and applied at boot before orcad starts.
- Triggers: explicit revoke, making a workspace private, archive, reassign, member removal (internal route), and phone device-session revoke (W8).
- VMs from older images get the entry points installed on demand.
- **Git as the sender:** a member's own GitHub user-to-server token (App OAuth, envelope-encrypted) is swapped in at egress only for the workspace repo. The VM gets per-member git configs and `orca-git-as`.
- **Cost:** metered compute × billing rate, plus optional token spend.
### orcad (generic)
- New owner-only RPC `pairing.revokeDevice` and `orca pair --revoke <device-id>`: revokes a device and closes its open connections immediately.
### Desktop
- A "Cloud workspaces" section on Home: Team/Mine, By, In and Archived filters; sections; live presence; cost; last activity. Single-click Open on a teammate workspace pairs this device through an E2EE grant. Also a row menu (share/private, move to section, reassign, archive) and copy link.
- Deep links `orca://cloud/w/<id>[/c/<chat>]` (open-url, argv, second instance, sidecar handoff) and the `cloudWorkspace.copyLink` keybinding.
- Messages to agents in shared workspaces carry a sender envelope.
- Uses the W10 shared primitives.
- Two rounds of adversarial design and frontend review.
### Evidence
Real Vercel VMs; see the W4 report. Numbers:
- grant issued in 0.55 s, paired in 1.35 s
- revoke closes the live desktop socket ~30 ms after the PATCH returns
- two members' pushes land on GitHub with their own author identities (shared dev Worker, preview alias w4); a push to another repo gets 403
* feat(cloud): SSH into Orca Cloud workspaces (orca ssh, ssh-proxy, VS Code/Cursor Remote-SSH)
- VM: unprivileged key-only sshd on 127.0.0.1:2222 (orca-sshd), host key and Orca-managed
authorized_keys on the persistent disk; orca-ssh-keys entry point; provision extracts only the
server binaries (dnf or apt, +1.5-2 MB) and unlocks the sandbox user for key logins.
- Control plane: POST/DELETE /v1/sandboxes/:id/ssh-keys runs the fixed entry point and answers
the pinned host key; editor Remote-SSH server hosts on the egress allowlist.
- Desktop: per-workspace ed25519 keys in userData (0600), Orca-owned ssh_config + pinned
known_hosts, ~/.ssh/config Include only after consent; wake -> key -> runtime port forward
(openRuntimePortForward reason 'ssh'); revoke (deferred while asleep); SSH traffic keeps the
workspace awake.
- CLI: orca ssh-proxy <workspace> (ProxyCommand) and orca ssh <workspace>.
- UI: Open in -> VS Code / Cursor over Remote SSH, SSH submenu (Copy SSH Command, Revoke),
include consent dialog, progress/error toasts.
* fix(cloud-ssh): review fixes — no agent forwarding, reinstall per VM boot, wake to revoke
- ForwardAgent/ForwardX11 no in Orca's host blocks; AllowAgentForwarding no in the VM's sshd.
- Reinstall the key (idempotent; restarts sshd, re-pins the host key) once per VM boot, so a
wiped disk, rollover or dead sshd heals on the next connection.
- Revoke wakes the VM to remove its copy; only an unreachable VM falls back to the queue.
- orca-ssh-keys rejects multi-line keys; sshd log truncated per start; audit log entries for
ssh key add/remove; state parsed per entry; % escaped in ssh_config paths; ProxyCommand pinned
to this app's profile; ssh-proxy flushes stdout before exit; --print shell-quotes; editors
open via --folder-uri with a checked path; "Remove from ~/.ssh/config" + one-time backup.