Commit Graph
632 Commits
Author SHA1 Message Date
Neil dd34314dc0 feat(billing): bill overage by the minute, put sandboxes to sleep at the cap (U22, U24)
- 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."
2026-11-10 04:33:07 -08:00
Neil ae7750b7af fix(sandbox-control): re-bind the build runner Worker (step 2 of 2 after the delete-class migration) (#240) 2026-11-10 04:24:12 -08:00
Neil 47dc83b869 Merge pull request #238 from nwparker/cloud/agents-copilot-ph
fix(cloud): Copilot's token stand-in looks like a GitHub token
2026-11-10 04:19:18 -08:00
Neil 77af3b072a fix(sandbox-control): unblock dev deploy (10061): apply the build-runner delete-class migration without the binding, step 1 of 2 (#237)
* fix(sandbox-control): unblock dev deploy: apply the build-runner delete-class migration without the binding (step 1 of 2)

* test(sandbox-control): build-runner config test allows the binding-less step of the rollout
2026-11-10 04:17:03 -08:00
Neil d323e4a20f Merge pull request #223 from nwparker/cloud/w5-cli-glossary
feat(cli): orca cloud glossary copy, workspace delete, routine list/run (DS-25, DS-36 CLI)
2026-11-10 04:09:58 -08:00
Neil b55b368887 fix(sandbox-control): host EnvironmentBuildRunner in its own Worker so control-plane preview URLs work (#234)
Cloudflare generates no preview URLs for a Worker that implements a Durable Object, so every
--preview-alias upload of orca-sandbox-control-dev 404ed. The runner DO moves to
orca-sandbox-build-runner-dev (wrangler.build-runner.dev.jsonc); the control plane binds it with
script_name and deletes its own class (migration env-build-runner-v2). The runner only keeps the
alarm: each step calls back into the control plane's BuildTicker RPC entrypoint, which runs the same
tickBuild with the control plane's D1, provider and secrets, so nothing is duplicated.
2026-11-10 04:08:45 -08:00
Neil 33202f5bd9 Merge remote-tracking branch 'private/cloud/integration' into cloud/w5-cli-glossary
# Conflicts:
#	src/cli/root-help-text-primary.ts
2026-11-10 04:06:22 -08:00
Neil adc417cae5 feat(cloud): acceptance read-asleep access (operator member removal, private prune) and phone protocol path, by W9 2026-11-10 03:54:00 -08:00
Neil 3f2b8ead4e Merge pull request #228 from nwparker/cloud/w9-eval
feat(cloud): acceptance — run actions + ports (m4air UI), read-while-asleep (live E2EE), gap rows
2026-11-10 03:51:42 -08:00
Neil f88c09c7fa Merge pull request #211 from nwparker/cloud/w2-squad-archive
fix(cloud): archive from the team list really stops the sandbox (DS-01 P0, DS-09)
2026-11-10 03:47:46 -08:00
Neil c14e875adc Merge pull request #225 from nwparker/fix/ds-34-44-billing-polish
fix(billing): DS-34 billing polish + DS-44 nits
2026-11-10 03:42:47 -08:00
Neil b199587d7e feat(cloud): desktop run actions + ports checks (W9D_ONLY=run-actions)
- local.run-actions: autoRunAfterSetup after a good setup and not after a failed one, Run button,
  one trust prompt, Stop -> Stopped, menu Restart replaces the tab, Cmd+Opt+Enter start/restart
- run.nonconcurrent: echo-loop started in B stops A's
- run.ports: ORCA_PORT/ORCA_PORT_COUNT/CONDUCTOR_PORT per terminal, distinct blocks, stable across
  an app restart, served locally; cloud half: the action in the VM reachable here via auto-forward
- run.cloud-resume: action back after Sleep/wake and after Restart sandbox
2026-11-10 03:42:31 -08:00
Neil 29cdd2cb99 feat(cloud): acceptance read-while-asleep (live, E2EE, no wake) plus access/phone rows (owner-reported) 2026-11-10 03:42:31 -08:00
Neil 322ba5eabb feat(cloud): acceptance catalog rows for run actions and per-workspace ports (gap 4) 2026-11-10 03:42:31 -08:00
Neil c5199433a9 Merge pull request #214 from nwparker/cloud/w6-secret-request
feat(cloud): agent secret requests: orcad RPC, orca secret request, orca-env (contract §3)
2026-11-10 03:42:02 -08:00
Neil 6b2ff92825 fix(cloud): Copilot's token stand-in looks like a GitHub token (Copilot ignored orca-ph-…)
Dry routine run on dev: Copilot CLI answered 'No authentication information found' with
COPILOT_GITHUB_TOKEN=orca-ph-…; it only takes GitHub-shaped tokens (github_pat_/gho_/ghu_; classic
ghp_ refused). The stand-in for that one credential is now github_pat_orcaph_<sandbox>, still
swapped by exact match on api.github.com (Copilot endpoints only) and the Copilot API.
2026-11-10 03:40:24 -08:00
Neil 6fa10c836b Merge remote-tracking branch 'private/cloud/integration' into cloud/w2-squad-archive
# Conflicts:
#	src/renderer/src/components/orca-cloud/cloud-workspace-actions.ts
2026-11-10 03:40:01 -08:00
Neil 01d2482a3a fix(billing): one billing store, skeleton, plurals, plans focus and billing nits (DS-34, DS-44)
DS-34:
- One billing store (billing-summary-store) replaces the summary, the hook's local state and the
  Run-on picker cache; it keeps the last good result through a failed read and still drops answers
  from before a team or account switch (DS-17).
- Skeleton while Plan & billing loads.
- '{{count}} sandbox hours left' and '{{count}} sandbox hours over the included amount this month'
  as i18n plurals; no bare 'hours'; the unreachable 100% branch is gone.
- Usage warning driven by usageWarningPercent ('Your team has used {{percent}}% of this month's
  sandbox hours.'), in Settings, the plans page and the account menu.
- capTooHigh states the maximum ('The spend cap can be at most {{max}}.'), from offers.maxSpendCapCents.
- Plans dialog focuses its heading on open, so Tab reaches the interval toggle before the cards.
- Payment notice uses InlineError; the build card's overage buttons land on the overage section.
- Account menu names the team once and adds the hours-left line.

DS-44: warning tone at 90%+, '$' as an input prefix, neutral paid check, 24px dialog close hit
area, no <p> inside the CheckoutPanel <dl>.
2026-11-10 03:39:21 -08:00
Neil e604bd4f39 Merge pull request #226 from nwparker/cloud/u22-cpu-fairuse
feat(sandbox-control): internal per-session CPU + operator fair-use report (U22)
2026-11-10 03:38:55 -08:00
Neil e02edd279f Merge remote-tracking branch 'private/cloud/integration' into cloud/w6-secret-request
# Conflicts:
#	cloud/apps/sandbox-control/image/vm-scripts.generated.ts
2026-11-10 03:34:32 -08:00
Neil d76cb62c57 fix(sandbox-control): keep preview URLs on for the dev Worker (preview aliases 404ed) (#224) 2026-11-10 03:34:07 -08:00
Neil f8e362259a fix(cloud): harden agent secret requests (review of #214)
- 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.
2026-11-10 03:34:04 -08:00
Neil 43aa595ee7 feat(sandbox-control): internal per-session CPU tracking and fair-use report (U22)
Customers keep flat sandbox hours. Each stopped session's activeCpuDurationMs
(from Vercel's stop response or GET session) is stored in a new session_cpu
table (migration 0042), idempotent per session. Recording is best-effort and
never blocks suspend, stop or usage reporting; a 5-minute cron sweep retries
recent sessions still missing CPU. Operator-only GET /v1/internal/fair-use
reports per-account 30-day utilisation sum(active_cpu_ms)/sum(wall_ms*vcpus)
with fairUseFlag above 60%. Nothing reaches usage_outbox, billing or clients.
2026-11-10 03:34:00 -08:00
Neil 0f8ab90314 Merge pull request #222 from nwparker/cloud/w6-agent-sources
W6: agent Source per person (/v1/agent-sources) + per-agent credential names
2026-11-10 03:31:03 -08:00
Neil bc7e012e6e Merge pull request #219 from nwparker/cloud/agents-a6
feat(cloud): this computer's Agent API Keys never reach an Orca Cloud VM; save each as a personal Cloud secret (A6)
2026-11-10 03:27:21 -08:00
Neil 9b56250956 feat(cloud): agent Source per person (GET/PUT /v1/agent-sources, offer answers, owner block) — 'Your sign-in' keeps that agent's credentials out of the sandbox session live; shared credential-names module + Worker mirror (migration 0041) 2026-11-10 03:21:22 -08:00
Neil 8102e770dc fix(cloud): the registry edit can no longer label a workspace archived; tests for asleep archive (no wake) and creating/suspending refusals 2026-11-10 03:18:45 -08:00
Neil 86ccf4eb29 fix(cloud): read snapshot lists chats from orcad's session store (the local socket can't stream subscribeStatus)
Live on dev the first capture sealed zero sessions: agentSession.subscribeStatus needs a streaming
transport and the unix socket refuses it, and worktree.ps lists only agents live right now. The
collector now reads orcad's own session records (agent-session-journal.db, read-only): provider,
name and last turn, open tabs first; live worktree.ps rows override status; history still comes
from agentSession.history, with the stored last turn as fallback.
2026-11-10 03:10:34 -08:00
Neil c392a487a1 Merge remote-tracking branch 'private/cloud/integration' into cloud/w2-squad-archive
# Conflicts:
#	cloud/apps/sandbox-control/src/index.ts
#	src/renderer/src/components/cloud-workspaces/CloudWorkspacesHome.tsx
#	src/renderer/src/i18n/locales/en.json
2026-11-10 03:09:00 -08:00
Neil 7dbd33a1ad Merge remote-tracking branch 'private/cloud/integration' into cloud/w6-secret-request
# Conflicts:
#	cloud/apps/sandbox-control/image/provision-golden.sh
#	cloud/apps/sandbox-control/image/vm-scripts.generated.ts
2026-11-10 03:05:52 -08:00
Neil e3748c9725 Merge remote-tracking branch 'private/cloud/integration' into cloud/w2-squad-archive
# Conflicts:
#	src/renderer/src/i18n/locales/en.json
2026-11-10 02:57:28 -08:00
Neil 81577b1ac7 feat(cloud): agent secret requests (secrets.request RPC, orca secret request, orca-env)
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.
2026-11-10 02:55:15 -08:00
Neil dbe94611c5 fix(cloud): team-list archive runs the lifecycle first (409 while starting or mid-transition, never a label on a running sandbox); confirm before archiving a teammate's running workspace 2026-11-10 02:48:51 -08:00
Neil 49ce098274 Merge remote-tracking branch 'private/cloud/integration' into cloud/sleeping-read-snapshot
# Conflicts:
#	src/renderer/src/components/cloud-workspaces/CloudWorkspacesHome.fixture.tsx
#	src/renderer/src/i18n/locales/en.json
2026-11-10 02:39:02 -08:00
Neil dd4baa98ed fix(cloud): security review of A6: offer and save only keys the proxy swaps; no desktop keys over SSH into a cloud workspace; orcad on a cloud VM drops carried keys
- 'Save as personal Cloud secret' is offered, and accepted by main, only for names the egress proxy
  swaps (a shared list the control-plane test holds equal to the proxy's); anything else would sit
  on the VM in full, so main answers not_swappable.
- SSH launches into an Orca Cloud workspace (orca-* aliases or an 'orca ssh-proxy' ProxyCommand
  surfaced through ~/.ssh/config Includes) get no desktop keys.
- Defense in depth on the VM: orcad with ORCA_IS_CLOUD=1 drops a carried keyEnv.
2026-11-10 02:37:29 -08:00
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 7316a18fc1 feat(cli): orca cloud glossary copy, workspace delete, routine list/run (DS-25, DS-36 CLI)
- 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
2026-11-10 02:26:14 -08:00
Neil 6fb8497a1a Merge remote-tracking branch 'private/cloud/integration' into cloud/sleeping-read-snapshot
# Conflicts:
#	cloud/apps/sandbox-control/image/vm-scripts.generated.ts
#	mobile/src/cloud/cloud-control-plane-client.ts
#	mobile/src/i18n/en-runtime-required.json
#	mobile/src/i18n/locales/en.json
#	src/renderer/src/components/cloud-workspaces/CloudWorkspaceRow.tsx
#	src/renderer/src/components/cloud-workspaces/CloudWorkspacesHome.tsx
2026-11-10 02:21:17 -08:00
Neil 264f30f8a3 fix(cloud): archive from the team list really stops the sandbox (Sleep → archive with Undo, or the control-plane archive); archive never runs after a failed sleep (DS-01, DS-09) 2026-11-10 02:19:03 -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