Commit Graph
468 Commits
Author SHA1 Message Date
Neil 519f6f6c9b fix(billing): spell out price formats per DS-18
Line items: '{{seats}} x {{price}} per seat, billed yearly' / 'per seat per month'
(plural keys); totals '{{total}} per year' / 'per month'; Worker plan labels
'$50 per seat per month' and '$480 per seat, billed yearly'. Drops /mo, /yr,
/year and /seat/month strings.
2026-11-10 02:27:14 -08:00
Neil ebec244447 Merge pull request #203 from nwparker/cloud/agents-gaps2
feat(cloud): swap COPILOT_GITHUB_TOKEN and GOOGLE_API_KEY at egress; routines run all six image agents; AWS keys marked stored on workspace
2026-11-10 02:16:56 -08:00
Neil df8927b7f5 fix(cloud): security review: Copilot token limited to Copilot's GitHub endpoints; routine summaries are final messages with session secrets redacted; untrusted context capped
- COPILOT_GITHUB_TOKEN on api.github.com: GET /user and GET /copilot_internal/* only (normalized
  paths; dot or encoded segments refused); the Copilot API hosts are unchanged. It resolves like
  an env variable (personal beats team): it is one person's GitHub identity.
- Routine summaries for opencode (JSON events), cursor (JSON result) and copilot (--silent) are the
  agent's final message, and every summary has the session's secret values replaced before it can
  be posted as a comment or PR body.
- Webhook/GitHub context in a routine prompt is capped at 48 KiB (compact, then cut), so a prompt
  passed as one argv entry stays under Linux's 128 KiB limit.
- A test fails if an agent credential is neither swapped nor marked 'Stored on workspace'.
2026-11-10 02:13:11 -08:00
Neil e35ff0844a feat(cloud): COPILOT_GITHUB_TOKEN and GOOGLE_API_KEY swapped at egress; routines run all six image agents; AWS keys marked stored on workspace
- COPILOT_GITHUB_TOKEN: placeholder in the VM, swapped on api.github.com (Bearer or token form, next to
  the workspace token's own route) and on the Copilot API hosts (left open for Copilot's own session
  token). GOOGLE_API_KEY: swapped on generativelanguage.googleapis.com and aiplatform.googleapis.com
  (Vertex express). A credential may now route to several hosts.
- Routines accept claude, codex, opencode, gemini, cursor and copilot; the in-VM runner has a
  headless command for each (checked against a local stand-in model: prompts starting with '-' stay
  prompts). The editor picks the agent from a list.
- AWS_* keys (SigV4, signed in the VM) and Bedrock API keys can't be swapped: the secrets UI marks
  them 'Stored on workspace' with the reason, in the list, the .env preview, the effective view and
  the editor.
- Gemini's auth method is seeded for GOOGLE_API_KEY too (vertex-ai with GOOGLE_GENAI_USE_VERTEXAI);
  headless Gemini refuses to start without one.
2026-11-10 02:08:00 -08:00
Neil 209d658b03 Merge pull request #202 from nwparker/cloud/status-rate
feat(cloud): one overage constant ($1/h); workspaces show sandbox hours, not dollars
2026-11-10 01:57:47 -08:00
Neil d4653c15d3 feat(cloud): one overage constant (ORCA_CLOUD_OVERAGE_CENTS_PER_HOUR, $1) for billing and the control plane; workspaces show sandbox hours, not per-workspace dollars (desktop list, phone status) 2026-11-10 01:52:05 -08:00
Neil 8550787232 docs(cloud): region/size AFTER benchmarks — wake p95 5.85 s in pdx1 2026-11-10 01:45:21 -08:00
Neil 2920f1c173 feat(cloud): acceptance image.agents, agent.keys-swap (no model), sub.size at 8/16 (U18); more-agents owner-reported 2026-11-10 01:45:21 -08:00
Neil 1095b509fc fix(cloud): Copilot CLI sign-in works in cloud workspaces (GitHub placeholder no longer overrides /login)
Copilot prefers GH_TOKEN/GITHUB_TOKEN over a stored /login. In Orca Cloud those hold the workspace
GitHub App placeholder, so every Copilot start said 'Resource not accessible by integration' and a
user who signed in could never use it. A wrapper drops the two placeholders for Copilot only, unless
BYOK is configured (no GitHub sign-in needed; gh in Copilot's shell keeps working).
2026-11-10 01:33:29 -08:00
Neil dc0bf820ea feat(billing): overage $1.00 per sandbox hour (interim); plans.ts is the single source (setup script, e2e and spend-cap math read it); new Stripe TEST price 2026-11-10 01:26:39 -08:00
Neil f1aa875edc docs(cloud): checkpoints verified independently by W9 (host path + real git tests) 2026-11-10 01:21:08 -08:00
Neil c9c0a0c65e feat(cloud): acceptance records owner-reported results (import-external); checkpoints capped until independent run 2026-11-10 01:20:19 -08:00
Neil f6b6c5141c feat(cloud): acceptance create-bench (host CPU/size/region), wake-split region + CPU per wake, bench-summary 2026-11-10 01:20:19 -08:00
Neil f9cc0f0c89 docs(cloud): agent caps lifted only by the user's own e2e session (no gateway runs) 2026-11-10 01:20:19 -08:00
Neil 63b6999dee feat(cloud): pdx1 default region, iad1 fallback, golden per region; Standard = 8 vCPU / 16 GB (U17, U18) (#194)
* feat(cloud): pdx1 default region with iad1 fallback and one golden per region; Standard is 8 vCPU / 16 GB (U17, U18)

- golden_images and environment_builds record their region (0040, backfilled iad1); rotation,
  git-source builds, smoke and activation run per region; lifecycle picks the region's golden.
- New creates default to pdx1; a create with no pinned region falls back to iad1 automatically on a
  capacity/provider error (VERCEL_FALLBACK_REGIONS). Until pdx1 has an image, creates use iad1.
- Environment builds boot and smoke in their own region; workspaces from a build boot there.
- One size: Standard = 8 vCPU / 16 GB (ORCA_CLOUD_SANDBOX_SPEC). Legacy 'large' requests map to
  standard; 0040 rewrites existing large rows.

* merge cloud/integration (ORCA_CLOUD_SANDBOX_SPEC landed in #196)
2026-11-10 01:07:56 -08:00
Neil b22512a759 Merge pull request #196 from nwparker/cloud/standard-8vcpu-copy
feat(cloud): one sandbox size (8 vCPU · 16 GB, 1×): billing and copy
2026-11-10 01:00:13 -08:00
Neil c997f9cf3c fix(cloud): orcad and its terminals trust the egress proxy's CA (Gemini CLI hung in every cloud terminal)
Hosts with credential rules (model APIs, api.github.com) are answered by Vercel's egress proxy with a
certificate from its CA. Vercel's command env trusts it (NODE_USE_SYSTEM_CA, NODE_EXTRA_CA_CERTS,
SSL_CERT_FILE) but orcad starts under env -i, so Node clients in terminals used Node's bundled roots:
Gemini CLI retried forever ('This is taking a bit longer') while the same call from a command
returned at once. Found on the live proof of the new agents; NODE_USE_SYSTEM_CA=1 in the terminal
fixed it there (1 s to the provider's answer).
2026-11-10 00:57:32 -08:00
Neil ff12245a82 feat(cloud): one sandbox size (8 vCPU · 16 GB, 1×) — billing counts every size 1×, plans/usage/run-on/env/CLI/phone copy from ORCA_CLOUD_SANDBOX_SPEC, size pickers removed 2026-11-10 00:54:28 -08:00
Neil 0ca83ef860 feat(sandbox-control): dev control plane also trusts staging auth sandbox tokens
Staging sign-in now works end to end in dev without a dev operator key on the user's machine.
Same account ids as billing dev (which trusts staging already); never honored when SANDBOX_ENV=prod;
a staging key cannot pass as the prod issuer.
2026-11-10 00:46:50 -08:00
Neil 64fbff83f1 Merge pull request #190 from nwparker/cloud/agents-in-image
feat(cloud): OpenCode, Gemini CLI, Cursor agent and Copilot CLI in the image; their keys swapped at egress
2026-11-10 00:33:06 -08:00
Neil 4286eb9cfc fix(cloud): no swappable key reaches the VM unrouted; Cursor's exchange reply passes only the access token
Security review: a Copilot BYOK key without a usable base URL (or with one from another scope) had
no route, so it went to the VM as itself. Every swappable name now becomes a placeholder, routed or
not, and the Copilot pair resolves like the other agent keys (shared beats personal, as a pair).
The Cursor exchange reply is parsed whatever its content type and cut down to accessToken; headers
(set-cookie) are not passed. opencode.ai is not allowlisted (anonymous share uploads).
2026-11-10 00:24:34 -08:00
Neil a1d4aad723 build(cloud): source-build smoke prints used disk and /opt/orca size 2026-11-10 00:20:05 -08:00
Neil 095c4ed960 build(cloud): fail the golden build when a new agent CLI does not start; smoke checks them 2026-11-10 00:19:31 -08:00
Neil 2086f40305 feat(cloud): OpenCode, Gemini CLI, Cursor agent and Copilot CLI in the golden image, keys swapped at egress
Image: pinned opencode-ai 1.19.5, @google/gemini-cli 0.67.0, @github/copilot 1.0.108 (npm) and
cursor-agent 2026.11.05-86047df (checksummed tarball). opencode's unused linux build variant is
dropped (~205 MB). First-run screens are answered at every boot/secret refresh (folder trust for
Gemini, Cursor and Copilot; Gemini's auth picker when a Gemini key is set; OpenCode's gateway model
from ANTHROPIC_MODEL/OPENAI_MODEL); self-update is off for all four.

Egress: GEMINI_API_KEY / GOOGLE_GENERATIVE_AI_API_KEY (generativelanguage.googleapis.com,
x-goog-api-key), CURSOR_API_KEY (api2.cursor.sh, only POST /auth/exchange_user_api_key; the
refresh token is stripped so nothing long-lived lands on the VM) and COPILOT_PROVIDER_API_KEY /
BEARER_TOKEN (host of COPILOT_PROVIDER_BASE_URL, same scope only) are placeholders in the VM and
swapped by the proxy. Allowlist adds the agents' API and sign-in hosts. API/MCP accept the image's
agents; routines stay on claude/codex.
2026-11-10 00:19:04 -08:00
Neil 4b3e0b62d3 docs(cloud): lift download/remote-desktop audit caps after real-UI run on m4air 2026-11-09 23:50:14 -08:00
Neil f8058b55a5 feat(cloud): desktop UI checks for acc.download, acc.remote-desktop and local.core-workflow (W9D_ONLY=ui-audit)
- local.core-workflow: composer local worktree (Blank Terminal), edit in its terminal, diff in
  Source Control and the diff tab, Stage All, commit
- acc.download: Explorer Download… (20 MiB file) and Download as ZIP… (folder) with progress and
  success toasts and sha256 checks; only the native Save panel's answer is stubbed in main via the
  inspector (W9D_MAIN_INSPECT)
- acc.remote-desktop: workspace menu Open remote desktop -> orcaDesktop connected, noVNC canvas
  sampled, a red xterm launched on DISPLAY=:1 shows up; guest screenshots over CDP
- local rows without a host badge count as local
2026-11-09 23:50:14 -08:00
Neil 1e2dc4f00f fix(cloud): acceptance report applies independent-audit caps (partial) and adds local core workflow, run actions, read-while-asleep and checkpoints rows 2026-11-09 23:31:50 -08:00
Neil d9b9e23ca3 docs(cloud): acceptance wake delivery on snap_6kSW (#173/#175) 2026-11-09 23:31:50 -08:00
Neil 20338e08c5 fix(sandbox-control): routine comments mint an issues:write token so plain issues get comments (falls back when the installation lacks it) 2026-11-09 23:05:42 -08:00
Neil 543cff405e perf(cloud): seal the ready offer into the start line; poll ready every 10 ms (#175)
The wake phase "orcad ready" (W9) runs from orcad's spawn to the control plane
having the pairing offer. After orca-sandbox-start exits, the control plane
made a second Vercel round trip (files API, ~120 ms from here) to read the
offer, because command stdout is kept as logs.

startOrcad now passes a fresh 32-byte key per call in env (env is not kept).
The start script adds "sealedOffer": "<iv>.<base64 AES-256-CTR(offer line)>"
to the pointer line it already prints, so the logs only hold ciphertext and
the control plane opens the offer locally. The key is unset first thing in the
script and in the prelude, so nothing the script starts inherits it. If the
offer is missing or does not open (older image, no openssl, wrong key), the
control plane reads the file as before. Older Workers send no key, and
current images then print the plain pointer.

The ready poll in the start script drops from 50 ms to 10 ms (~20 ms mean),
keeping the 60 s minimum timeout.

Verified on a VM from snap_aQmW, driving the start script like the control
plane: all four starts, including an idempotent second start, opened without
the files API, and the stdout never held the pairing code.
2026-11-09 22:01:32 -08:00
Neil 213ce633e5 perf(cloud): start orcad from a full V8 code cache (~0.3 s off ready) (#173)
NODE_COMPILE_CACHE only stores what V8 compiles before a script runs: 5.4 MB of
orcad-server.js's 15 MB. Every function startup runs still compiled lazily on
every boot. Measured on a VM from golden snap_aQmW, orcad ready went from
0.95-1.06 s to 0.70-0.78 s (5 pairs, alternated), and from ~1.25 s to ~0.95 s
on a slower host.

image/vm/orcad-boot runs orcad-server.js through a vm.Script whose code cache
is taken as the image's smoke run exits, so it covers what startup compiled.
orcad sees the same argv, __filename, require and import() resolution as under
plain node. The cache file starts with a header (Node version, arch, bundle
length and crc32): V8 itself only checks the source length, so a cache from
other bundle bytes would otherwise run stale code. Without a matching cache
orcad loads through require exactly as before.

Verified in that VM: the cache is accepted, the experimental-loader warning
stays out of orcad.log, and a terminal created through the CLI runs (node-pty
resolves via import() from the bundle directory).
2026-11-09 21:52:21 -08:00
Neil 4ec131ade1 docs(cloud): acceptance findings — asleep delete 1.5 s after #171 2026-11-09 21:40:54 -08:00
Neil 515bc10542 feat(cloud): desktop acceptance stamps app log lines with wall-clock time and records Delete click/done times 2026-11-09 21:40:54 -08:00
Neil dd0cd5b001 feat(cloud): acceptance report p99 column; wake delivery on the #151/#162 golden (n=22) 2026-11-09 21:27:41 -08:00
Neil 6732b345b2 feat(cloud): acceptance env.setup-script verifies orca.yaml setup in the VM exactly once (core #168) 2026-11-09 21:20:26 -08:00
Neil 5a7484cf20 Merge pull request #169 from nwparker/cloud/w9-eval
feat(cloud): acceptance — billing gate via billing-test accounts, CLI --on cloud, asleep move-delete, staging teams
2026-11-09 21:17:28 -08:00
Neil 78c874ee1f feat(cloud): run orca.yaml setup once for API/CLI/phone/routine workspaces (#168)
Desktop-created cloud workspaces already run setup through orcad's worktree
create after the setup prompt. Workspaces made through createWorkspace use the
VM's primary checkout, which no worktree create touches, so their setup never
ran. Start it detached on create (once per workspace, orcad's env, skipped on
environment builds) and have routine runners wait for it.
2026-11-09 21:16:28 -08:00
Neil 22f8d5abb4 docs(cloud): acceptance findings — CLI create and asleep move-delete closed 2026-11-09 21:15:16 -08:00
Neil d9cd1bac93 feat(cloud): acceptance api.cli runs orca worktree create --on cloud headlessly (W5 #167) 2026-11-09 21:14:53 -08:00
Neil 3b8867d3de feat(cloud): acceptance control-plane billing gate end to end via dev:billing-test-* (#166) 2026-11-09 21:14:53 -08:00
Neil e506a7bca7 Merge pull request #167 from nwparker/cloud/w5-cli-worktree-on
feat(cli): orca worktree create --on cloud (private build) with --env, JSON output and clear plan/at-cap/sign-in errors
2026-11-09 21:13:22 -08:00
Neil 70e9b2a6c4 feat(cli): orca worktree create --on cloud[:<project>] [--env] creates an Orca Cloud workspace (private build); clear plan/at-cap/payment/sign-in errors; API accepts a team environment 2026-11-09 21:08:55 -08:00
Neil f2386f9a37 feat(sandbox-control): dev-only SANDBOX_DEV_BILLING_ENFORCE_PREFIX sends one dev account prefix through the real billing gate (#166)
dev:* accounts skip billing on dev, so W9 could not test the gate (no plan refused, active allowed,
grace expiry suspends, at-cap refused) with dev tokens. Dev accounts under the configured prefix now go
to the billing Worker. Honored only when SANDBOX_ENV is exactly 'dev' and only for a specific
sub-prefix (dev:<word>- or dev:<word>:), never all dev accounts; tests cover prod/staging/test being
ignored and over-broad prefixes being rejected. Dev sets dev:billing-test-.
2026-11-09 21:07:28 -08:00
Neil f04525d7db feat(cloud): acceptance closes idle sleep, agent sign-in persistence, wake-on-message, scheduled routines, file search, usage metering, overage cap and full dunning; web client post-MVP 2026-11-09 21:03:10 -08:00
Neil c77ab2b16c feat(cloud): desktop keystroke n=W9D_KEY_SAMPLES with p99/outlier timeline; life.auto-resume desktop check
- W9D_KEY_SAMPLES (default 10): stats gain p99; each sample records time since the workspace's
  first terminal output and wall clock, plus a histogram and the outliers
- life.auto-resume (W9D_AUTO_RESUME=1, W9D_ONLY=auto-resume): personal AI Gateway secrets for the
  dev user (kimi-k2, never logged, deleted after), Claude runs a 40 s loop, status panel Restart,
  Orca must send continue once and the turn must finish; mapped to catalog life.auto-resume
2026-11-09 21:03:10 -08:00
Neil 1772627e6e fix(cloud): CLI/API workspace sleep (POST /v1/workspaces/:id/sleep) records the deliberate sleep too 2026-11-09 20:47:22 -08:00
Neil a878062983 Merge pull request #163 from nwparker/cloud/w2-predict-gaps
fix(cloud): predictive wake gaps: drag, cross-client sleep cooldown, seat cap
2026-11-09 20:43:41 -08:00
Neil 66170d3213 fix(cloud): a failed user-sleep hint write never fails the sleep (worker deployed before migration 0038) 2026-11-09 20:41:55 -08:00
Neil 0a7785d4b5 fix(cloud): predictive wake gaps: a drag never wakes (press waits for release, drag cancels pending), the sleep cooldown reads the server's last chosen sleep from any client, and a wake is skipped with the owner's seat or team at or one below its running cap 2026-11-09 20:41:25 -08:00
Neil b98feda7f8 feat(cloud): targeted desktop acceptance for #157 (picker sign-in twice, Move then Delete)
- W9D_MODE=targeted: picker sign-in on a fresh profile and again after a sign-out, create +
  keystroke, Move to this computer with Archive afterwards then Delete within seconds (destroyed,
  no suspend/archive error toasts), normal delete
- asleep Delete through the dialog without a Force delete offer is intended: graded pass
- cleanup removes local copies a run made; catalog row + import mapping for move-then-delete
2026-11-09 20:34:07 -08:00