* perf(rpc): stop the dispatcher importing every RPC method by default
Callers pass the method list. A test that builds a dispatcher no longer loads
the full method registry, cutting the dispatcher's import graph from ~4k to
~550 modules.
* chore(lint): keep the RPC dispatcher and plugin RPC off the method registry
---------
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
Five refused provider opens can outlast a 2s real-time budget on a loaded host,
so the preamble resolves 'unobserved' instead of throwing the refusal under test.
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
* spike(relay-edge): local Durable Object relay prototype and de-risk harness
Stateless Worker routes control/phone/data sockets to one object per host.
Harness drives the shipped desktop control client and data transport against
wrangler dev. Not wired into builds or deploys.
* spike(relay-edge): one Durable Object per splice isolates slow phones
The host object keeps control, session and connection bookkeeping. Each
phone connection and its host data socket live in their own object and talk
to the host over DO RPC. Harness adds flood isolation, splice reset, budget
reclaim and RPC latency checks; the silent-bytes guard is measured and left
disabled.
* spike(relay-edge): Cloudflare de-risk kit (spike env, load, deploy, canary, rtt tools, runbook)
* spike(relay-edge): rebind after an object reset keeps the generation
* spike(relay-edge): pool splice objects per host to avoid object creation per phone connect
* spike(relay-edge): slow-phone flood test for Cloudflare
* relay-edge: MVP backend (credential store over Hyperdrive, JWKS, epoch check, host commands)
* relay-edge: MVP checks on local Postgres (credentials, commands, epoch, DB-down)
* relay-edge: refusal and database-latency metrics; Analytics Engine on the spike
* relay-edge: alerts cron Worker over Analytics Engine; edge.relay.orca.dev on the spike
* relay-edge: slow assignment check closes 4429, never 4401; staging env over Hyperdrive; db latency debug route
* relay-edge alerts: silence only after traffic
* relay-edge: Durable Object SQLite adapter; relay credential-store tests pass on it (P1)
* relay-edge phase 2: credential store on HostRelay SQLite, signed director push/export/erase; 17/17 in workerd
* relay-edge phase 2: Cloudflare env, push-assignments load prep, remote mode for phase-2 checks
* relay-edge: export freezes the host and is refused while connected; move-on replaces host data; stale/conflict carry the edge epoch; push location hint; admin content-length check
* relay-edge: daily store sweep on the object alarm (armed on every wake and admin call), wipe of objects whose host never arrived or left 30 days ago, erase guard ignores expired bases, admin and sweep metrics
* relay-edge: admin key renamed ADMIN_SIGNING_KEY; optional erase-only signer for the erasure job; a move onto the edge also voids audit rows from an earlier stay
* relay-edge: phase2 test env names the erasure issuer
* relay-edge: director ⇄ cell ⇄ edge round trip with the shipped desktop and phone clients
* relay-edge: round trip resolves the live phone first (a failed resolve rate-limits the host)
* relay-edge: per-source pre-auth limits; phones for unpushed hosts answered without storage
Phone and host data upgrades are counted per source (CF-Connecting-IP, IPv6 by /64)
through a Rate Limiting binding, failing open. A phone for a host the director never
pushed (or one since wiped) gets relay-hello 4409 from the Worker, so neither the host
nor a splice object writes storage. Local 25/25, phase2 21/21.
* relay-edge: production env on edge.relay.orca.dev; object schema version; frame caps; place on metrics
- production env (orca-relay-edge): custom domain edge.relay.orca.dev moved off the
spike env; object backend, production auth JWKS, no debug/reset routes.
- Object storage carries a schema version; code older than an object's schema refuses
it; a test pins the version to a hash of the tables so changes need a migration.
- Frames over the cells' limits close 1009 (control 1 MiB, splices 8 MiB).
- Metrics carry the client's ingress '<continent>/<colo>'.
Local 26/26, phase2 22/22, unit 24/24.
* relay-edge: production alerts env (reads the production dataset)
* relay-edge: keep only local dev config in this repo
Deployed environments (Worker names, hostname, account id, dataset names,
rate limits, Hyperdrive id) move to the private deploy config. The local
config and tests are unchanged.
* cloud: keep relay-edge out of the pnpm workspace (own npm lockfile)
* Revert "cloud: keep relay-edge out of the pnpm workspace (own npm lockfile)"
This reverts commit a55955d3c023964ccd2dfb644e2d09ca67322722.
* cloud: relay-edge as a pnpm workspace member (lockfile; workerd binary-only)
* relay-edge: follow main's relay module moves; alias workspace packages by directory so subpath imports resolve
* relay-edge: splice byte counters on the socket attachment (survive hibernation)
* relay-edge: keep deployed-target load tools out of the public tree; abort routes only on the spike backend
The local harness (spike/run-all.ts, lib.ts, keys.ts) stays. The tools that drive a deployed
Worker and the Cloudflare runbook live with the private deploy config.
* relay-edge: satisfy the changed-lines lint gate
Braces on every if, type aliases over interfaces, template strings, no explicit any,
!== -1 index checks, and a justified max-lines header on the per-host object, the splice
object and the end-to-end harnesses. No behaviour change: unit 19/19, object MVP 26/26.
* relay-edge: split the host object and harnesses under the 300-line limit
HostRelay is layered base → handshake → control → phones → HostRelay (one object, one state;
method bodies moved verbatim, private → protected). SpliceRelay's state types move to
splice-state.ts. The harnesses' helpers move to sibling modules. No max-lines disables.
Unit 19/19, object MVP 26/26, local spike harness 19/19.
* relay-edge: run its vitest suite from cloud's pnpm -r test and register the runner
* perf(terminal): send idle remote keystrokes without the 8ms debounce
Remote terminal input batching was a pure trailing debounce, so every
isolated keystroke waited 8ms before leaving the renderer. Send input at
once when the batcher is idle and coalesce only what arrives inside the
following window. Recovery-hold replay keeps the trailing path so a
synchronous flush cannot re-enter the still-buffering hold.
* perf(ssh): disable Nagle on loopback sockets carrying orcad RPC
The SSH forward listener, the stdio bridge listener, and the remote
bridge's connect to orcad relay WebSocket RPC (keystrokes and echo).
ws sets TCP_NODELAY on its own sockets, but these relay sockets did not,
so small writes could wait behind an unacknowledged segment.
* perf(terminal): decode terminal frame payloads as views, not copies
Every message buffer is fresh and consumers decode the payload
synchronously or retain it read-only; the only in-place rewrite touches
header bytes.
* fix(terminal): send idle remote keys at once only to input-acking hosts
On hosts without input acks, an immediately sent key travels as a stream
frame while a following accepted write (Ctrl+C, paste) goes as a separate
terminal.send RPC on another socket, so the host could apply them out of
order. Keep the trailing batch there so both still leave as one RPC.
TerminalPane's default-shell search entry and the terminal link action
keywords move to their own search modules, like their sibling sections.
The search catalogs bind `t` once through searchKeywordTranslator, so
threading it keeps them within budget; the bound function keeps the
translateSearchKeyword name so catalog extraction still reads its keys.
TerminalSettingsPreview reads useTranslate; its lifecycle test's hook
mock now covers useContext. ShortcutsPane and the catalog app themes
pass `t` into the helpers their memos call.
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
* feat(relay): director edge override for moving hosts to the relay edge (dark by default)
Directors with ORCA_RELAY_EDGE_CELL_URL seed an existing-only 'edge' cell (registered
with the admission selector so deploy workflows carry it) and answer a host cohort
(hash bucket, allow/deny lists, region scope) on the edge before the reserve and
database lanes. The epoch rises only on a cell change; a host still holding units on
its old cell is told to retry. Phone resolve and the invite move answer edge rows
without moving anything; old cells answer WRONG_CELL to phones of moved hosts.
Unset URL keeps today's behaviour.
* feat(relay): director pushes host data to the relay edge on a move and takes it back before any move away
With ORCA_RELAY_EDGE_ADMIN_SIGNING_KEY set (its own key, never the assignment lease key), a move
onto the edge pushes the host's relay_devices and a location hint under the row lock and commits
only after the edge accepts; an edge holding a newer epoch is re-minted above once. Before any
lane may re-place an edge row, the director exports (refused while the desktop is connected; the
edge freezes the host), replaces the host's Postgres devices, places it as today, then fences the
edge with the new cell and epoch. Idle regional rehome skips edge rows. Unset key: unchanged.
* fix(relay): a deferred edge move keeps the host on today's path instead of a 503 loop
A host still holding old-cell units (a pairing invite holds one for its 10 min TTL) or whose push
the edge did not take now stays where it is and moves on a later ask. 503 stays for a held row
lock and for a desktop still connected to the edge on the way out.
* relay: director refuses an edge URL without the edge admin key
The deployed edge keeps host data in its objects; without the key the director could
not push it and hosts would land on an edge holding none of their devices.
* relay: edge cohort by account (ORCA_RELAY_EDGE_ALLOW_USERS)
Every host of a listed account (relay token sub) joins the edge cohort, in any region;
a denied host still stays off. For the team's own accounts before any share rollout.
* relay: edge modules follow main's relay module moves
* relay: satisfy the changed-lines lint gate in the edge cohort code
Braces on every if, T[] arrays, type aliases, one ternary, and a justified max-lines header on
edge-relay.ts. No behaviour change: relay unit 845 passed.
* relay: move edge cohort config and row helpers to edge-relay-cohort.ts
Keeps edge-relay.ts under the 300-line limit without a max-lines disable. Code moved verbatim;
edge-relay.ts re-exports what other modules import. Relay unit 845 passed.
Section headers and the setup prompt's candidate source read their copy
through helpers that called translate(). The worktree viewport and the
setup prompt already read `t`; pass it through instead. Two files
would go over budget, so the header row context type and the setup
inspection effect move to their own modules first.
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
The run sentence and background task rows read their words through
module translate() tables, so a memoized render keeps the old language.
Key both on `t` with recomputeOn. The language-switch tests now mount
TranslationScopeProvider, as the app does, so `t` changes there too.
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
* Report image copies the clipboard refused instead of showing "Copied"
The desktop clipboard image write returned without an error when it refused an
image (too large, not a PNG, undecodable), so "Copy as PNG" and browser screenshot
copies showed success while the clipboard was unchanged.
- Main and web image writes now reject on every refusal.
- The image viewer copies a raster image's own bytes (natural size), reusing the
paste/copy PNG conversion; only SVGs still render at 2x for sharp text.
- Too-large failures say so, through one shared description used by native chat
and the image viewer.
- Browser screenshot copies show success only after the write resolves.
Refs STA-14205
* Keep grab toasts on their own screenshot and show a successful retry as success
Refs STA-14205
* Drop a grab screenshot result once a newer toast, copy or cancel replaced it
Refs STA-14205
* Move the grab screenshot copy owner into its own hook
Refs STA-14205
* Test that native chat explains a too-large image copy behind the IPC error prefix
readFullOutput listed failedAt as a dependency without reading it, so a
compiled build would keep one reader across failures and the dialog
would keep the first run's output. recomputeOn makes failedAt an input.
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
Five tests passed only because each re-render re-read mutated inputs.
Set host capabilities through the store, pass new controller and
runtime snapshot objects, and read popover state from context like
Radix does. Drop AgentIcon render-count assertions.
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
* fix(ssh): keep the managed tunnel's TCP forward when a slow ProxyCommand delays its login
`ssh -L` binds its local listener only after its own login. The managed
tunnel gave that login 750ms plus a 5s channel check, then judged the
forward "never listened", killed it and switched the whole connection
generation to the per-socket stdio bridge. Through a slow ProxyCommand
(Coder `ssh --stdio` on Windows, without ControlMaster) the login often
takes longer, so the working forward was discarded and every socket paid
a fresh proxied SSH login, leaving the server Unreachable (#38636).
Give the listener the same 30s budget a system SSH connect gets, keep
the short wait for the channel answer once it listens, and stop waiting
as soon as the forward process exits so a refused login still falls back
at once with ssh's own exit reason.
Refs #38636
* test: give the slow-login forward test its timeout reason and real-socket wait exception
Adds a branch picker to the Source Control header for switching or creating a branch in place. Switching is disabled while an agent is working in the workspace, and results show as toasts.
Applies pnpm i18n:reactive-translate --helpers. Output is unchanged: t delegates
to translate() and helpers default the threaded parameter.
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
* refactor(agent-session): split subscribe params and read failure text into their own modules
Both host files sit at the 300-line budget, so the append negotiation that
follows needs room. Pure moves: SubscribeParams keeps its exact schema (now
z.object + sessionId instead of OptionsParams.extend, which is equivalent) and
is re-exported from the params module; stream failure text moves beside the
other read-refusal classification.
* perf(agent-session): stream running tool output heads as appends
A running tool call re-sends its whole output head (up to 16KB) on every
revision, and keeps re-sending the saturated head after it stops growing.
Streams that also ask for toolOutputAppend (host advertises
agent-session.tool-output-append.v1) now get only the head's new text under the
same shared sender/reader rules and self-healing refusal as reply appends.
- The live read negotiates the exact subscribe capabilities object instead of
a boolean; mobile asks for tool output appends when advertised.
- Hosts with only text-append.v1 drop the unknown key, so clients ask only
when the new capability is advertised and both ends track the same bases.
- Text-only streams keep the 8-base limit already-shipped readers evict at.
- A host that refuses the capabilities param (an older build after a
downgrade) makes the next open go without appends instead of failing forever.
* perf(native-chat): ask remote hosts for streamed text appends on desktop
Desktop chats on a paired or SSH-managed orcad received every streamed reply
checkpoint whole: about 9x the reply's bytes at the Claude/Codex checkpoint
cadence, and O(N^2) for ACP/JSONL providers that rewrite each 60ms window.
The desktop read now asks environment hosts for reply and tool-output appends
when they advertise them, reusing the shared reader that rebuilds whole items
before the reducer. Local IPC reads are unchanged and still subscribe in the
same turn. The support read runs once per chat run, overlapping hydration.
* refactor(remote-runtime): share compressed frame inflate across client platforms
Move the length checks and decode of paired E2EE compressed frames into
the shared module behind a small gzip/UTF-8 codec, so the desktop can
read them with node zlib while mobile keeps its native/fflate path.
* perf(remote-runtime): read compressed frames from paired hosts on desktop and CLI
Desktop and CLI sockets to orcad/orca serve now advertise
transport.compressed-frames.v1, so a v2 host gzips large text replies and
terminal output before sealing them, as it already does for phones.
- Every Node client transport (one-shot requests, request connection,
shared control, subscriptions) opens frames through one sealed-frame
reader that inflates compressed frames on v2 channels, synchronously so
frame order and v2 counters stay in arrival order.
- A compressed binary frame may claim up to the 8 MB binary outbound cap
(terminal frames can exceed the 4 MB JSON cap), for phones too.
- ORCA_DISABLE_MOBILE_COMPRESSED_FRAMES=1 now also stops a desktop or CLI
from asking for compressed frames.
- Relayed phones and the browser client are unchanged.
* refactor(remote-runtime): simplify the mobile inflate codec and clarify capability comment
* fix(remote-runtime): name why a sealed frame could not be read
Text-only sockets report an authentic binary payload as an unexpected
binary frame again, and tell a decrypt failure apart from a corrupt
compressed frame, instead of calling every failure undecryptable.
* fix(terminal): use Regular weight by default on Windows
Cascadia Mono, the Windows default, has no Medium face, so a 500 request
rasterizes as SemiBold. New Windows profiles default to 400; Windows profiles
still on 500 move to 400 once and see one persistent toast with a live
preview and a switch to keep 400 or go back to 500. Skipped when the
terminal font renders both weights identically.
* chore: prune stale renderer layout-writer baseline entry
* fix(i18n): translate the Windows weight toast label reactively
Adds a Custom… option to the App Icon switcher. The picked PNG/JPEG is
header-checked before decoding (square, 64–2048 px, ≤10 MB), re-encoded as
PNG into userData, and applied to the macOS Dock and every window icon. It
persists across restarts and can be removed, which resets to Classic.
Closes#13316
The managed server liveness probe matched kill -0's ESRCH text
case-sensitively. zsh and ksh print it lowercase, so on hosts whose login
shell is zsh a recorded PID that no longer existed read as UNKNOWN. The
wake then never restarted the stopped server, the update deferred on an
uncounted census, and the host stayed unreachable. Match either case;
permission errors still stay UNKNOWN.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Claude transcripts only sit directly in a project bucket; the History walk now shares the bucket-depth rule the session-id lookup already uses instead of only pruning subagents/.
Applies pnpm i18n:reactive-translate --helpers. Output is unchanged: t delegates
to translate() and helpers default the threaded parameter.
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
* fix(terminal): never type a colour reply into the shell after the program that asked has exited
The PTY owner holds a colour-query reply made inside a shell command for up
to 20 ms and drops it if the command's OSC 133;D arrives, or (POSIX) another
process group takes the terminal; otherwise it answers. Command markers ride
the colour scan every emission already pays, so the hot path does no new
work until a query is pending. No daemon protocol change.
Closes#16633Closes#14631
* fix(terminal): track command markers in the existing colour scan, drop the live-group shortcut, and hold through claimed echoes
* fix(terminal): one verdict for every query held behind the first, contiguous ranges through a held echo, drop held replies at the final snapshot
* refactor(terminal): move the torn query/echo holds out of the ingress
* fix(terminal): a query completed after a verdict is judged afresh; the final snapshot judges held askers instead of dropping them
* perf(renderer): add unfocused slowdown to shared visibility poll primitives
installWindowVisibilityInterval gains slowWhileUnfocused (focus read at tick
time, overdue catch-up on window focus) and the git status refresh scheduler
gains cadenceScale + retime(). Opt-in; focused cadence is unchanged.
* refactor(source-control): extract git status scheduler window-presence hook
* perf(ssh): slow remote port, git status and compare polls while unfocused
Visible-but-unfocused windows poll workspace/host ports, branch compare,
stale conflict probes and active git status 4x slower, so agent edits no
longer drive remote git status every 3s behind another app. Hidden windows
still pause; window focus runs overdue work immediately.
* perf(source-control): keep activity debounce when refocus re-times git status
* test: drop cadence-count tests for unfocused poll slowdown
Applies pnpm i18n:reactive-translate --helpers. Output is unchanged: t delegates
to translate() and helpers default the threaded parameter.
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
Live reasoning and reduced-motion replies passed streaming but never growing,
so each update re-parsed the whole message (~260ms avg per update on a 20KB
reply in happy-dom, vs ~3ms block by block).
Applies pnpm i18n:reactive-translate --helpers. Output is unchanged: t delegates
to translate() and helpers default the threaded parameter.
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
The accepted option support and the selection controller's terminal revision were read from mutable owners during render. Pass the reader snapshot and the selection revision each hook already subscribes to as recomputeOn inputs, so a compiled memo recomputes exactly when those values can change. Without the compiler the reads are unchanged.
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
Applies pnpm i18n:reactive-translate --helpers. Output is unchanged: t delegates
to translate() and helpers default the threaded parameter.
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
* wip
* Open HTML previews in the editor's tab group
Keep previews in the invoking group for local and remote files, and
avoid changing the selection if the user navigates away while opening.
* test: cover HTML preview from editor
* fix perf issue
* Prevent HTML previews from opening on the wrong host
* Guard HTML previews against workspace ownership changes
Applies pnpm i18n:reactive-translate --helpers. Output is unchanged: t delegates
to translate() and helpers default the threaded parameter.
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
Two new React Compiler compat hazards:
- discarded-subscription: a useSyncExternalStore call whose result is thrown
away, so the reads after it get cached on unrelated inputs.
- store-read-in-memo-block: a render-level getState() that the compiler places
in a cache-guarded block. Reads inside the source's own useMemo or callbacks
keep their written deps and are skipped.
The compiled output is parsed once for hook and store reads. Current sites are
grandfathered in the hazard baseline.
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
Applies pnpm i18n:reactive-translate --helpers. Output is unchanged: t delegates
to translate() and helpers default the threaded parameter.
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
Host chat-agent readers called useHostStructuredAgentsVersion, CrewRowTime
called useNow, and both discarded the value before reading module state or wall
time. The titlebar portal target was a bare document lookup. React Compiler
would cache all three on unrelated inputs. The reads now go through recomputeOn
with the returned version, and the titlebar lookup moves into a tiny hook whose
result the compiler never caches. With the compiler off the code runs the same.
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
* fix(renderer): key external-store reads on the subscription version they already take
Browser driver, remote viewer, client-hosted row and automation visibility hooks
subscribed with useSyncExternalStore, discarded the version, then read module
state. useHighlightedSyntax did the same with a reducer tick. React Compiler
memoizes those reads on their arguments only, so they would stop updating.
Each read now goes through recomputeOn with the version it already has; with
the compiler off the same code runs on the same renders.
* fix(renderer): read the client-hosted row selection through its subscription version
---------
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
Shows CI result lines in the chat that opened or pushed to a pull request. They're hidden in the terminal overlay and on the phone until the phone app gets the setting.
The browser page mount admission hook now returns its useSyncExternalStore
snapshot instead of recomputing it after a discarded subscription, and the
sleeping sweep keys its wall-clock read on the grace tick with recomputeOn.
Both run the same code with the compiler off; the hazard baseline is now empty.
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
* perf(mobile): show the workspace list from memory and refresh it on desktop changes
* fix(mobile): import the host list queries type without an import() annotation
* fix(mobile): keep host labels on a not-connected read and update the phone feed e2e helper
* fix(mobile): keep the event router listening when its stream ends while opening
* fix(mobile): reuse the shared read-view base and remount for the cache-return recording
* fix(agent-picker): search fallback models and show cached models while the list refreshes
The new-tab agent picker's search skipped any agent whose model catalog was
unknown, while that agent's model submenu still listed the built-in models,
so typing "gpt" found nothing even though Codex showed GPT models. Search now
covers the same models the submenu offers.
The picker also waited for a fresh model probe on every read, and Claude and
Codex catalogs go stale after two minutes, so most opens showed the built-in
list for 0.5-1.5s. Like the chat composer, it now reads the saved listing
first and shows it at once, then follows with the waiting read, which joins
the same host refresh (no extra probe). An unknown catalog still takes the
waiting read, as before.
* Fix model catalog refresh when reopening the agent picker
* fix(agent-picker): wait only for active catalog refreshes
* fix(runtime): keep a terminal's last output on exit and stop charging dropped screencast frames
* fix(browser): clear an oversize frame held for credit instead of retrying it
* fix(mobile): keep held model lists through late preloads and running chats
* fix(mobile): let progress answers fill empty lists and decide recording per read
* fix(mobile): record a pre-send listing on rejoin and leave unheld retirements alone
* test(desktop): use a valid listed catalog in the preload race test
* test(mobile): type the catalog cache test's renderer