Commit Graph
26 Commits
Author SHA1 Message Date
gatsby74andNeil a278d84a4e fix(pi): show input modals as waiting instead of working (#18836)
* fix(pi): show input modals as waiting instead of working

* test(pi): verify real input dialogs through Electron CDP

---------

Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
2026-09-07 19:35:19 -07:00
aeddfa463d perf(renderer): avoid per-second spinner animation events (#19407)
* perf(renderer): avoid per-second spinner animation events

* fix(bench): ensure the Electron runtime before bench:spinners

The script launches Electron via Playwright but skipped ensure:electron-runtime,
which every other Electron-launching bench script runs first.

* docs(renderer): scope spinner pixel-tolerance claim to paused-animation checks

---------

Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local>
Co-authored-by: pullfrog[bot] <226033991+pullfrog[bot]@users.noreply.github.com>
2026-09-07 19:29:53 -07:00
Jinwoo Hong 6729f3a0b0 tools: add a phone-vantage relay connect benchmark (#19251)
* tools: add a phone-vantage relay connect benchmark

Connect-speed work on the phone had no way to attribute latency to a hop.
Timing the mobile app end to end only says "connect is slow", and a synthetic
WebSocket probe does not exercise the credential check, the E2EE handshake, or
the RPCs the phone blocks on before it publishes connected.

This replays the shipped mobile wire sequence from Node against a real desktop
over the production relay, so each phase gets its own number. The handshake is a
plain-JS port of the mobile client session, which is only trustworthy if it stays
byte-identical to what ships; a parity test runs it against the real desktop
responder in the normal unit suite so drift in the transcript encoding, key
schedule, or frame layout fails there rather than producing a bench that measures
a handshake nobody uses.

Adds a foreground mode for the resume-after-background question the phone lanes
need: connect, go silent past the relay's client silence watchdog, then report
whether the retained socket still answers and what the fallback redial costs.

The bench writes a resume-credential bundle at runtime. That file carries a live
device token for a real paired desktop, so the directory ignores it outright.

* tools: make the relay bench name its target and opt in to dialing

The supporting scripts carried production defaults: the director origin was
hardcoded in both, and the hop-latency probe defaulted to a named production
cell. Running either with no arguments sent live traffic at production, and the
region probe did it on import, before any argument was read. A default like that
is the wrong shape for a bench, because the operator never states what they are
measuring against and a stray invocation is indistinguishable from an intended
one.

Every script now refuses to open a socket unless ORCA_RELAY_BENCH_LIVE=1 is set,
and the director comes from --director or ORCA_RELAY_BENCH_DIRECTOR with no
fallback. The cell origin is a required argument. Refusals print one line of
usage and exit 2, so an accidental run is inert rather than live.

The remaining host-id default is an id no desktop owns, which is the point of
that probe: it measures the cell hop without reaching a desktop at all.

* tools(relay-bench): type refuse() as never so origins are strings

* tools(relay-bench): fail closed on hostile input and bounded arguments

Review found the harness trusted whatever it was handed: the DevTools port
and the director-supplied probe origins went straight into a URL, http
origins were accepted, repeat counts came from a bare Number() cast, and the
state file kept its existing mode.

- Validate the DevTools port as a 1-65535 integer, so '80@attacker.example'
  cannot move the fetch off loopback via URL userinfo.
- Require https for every origin, and refuse loopback, link-local, private,
  and multicast destinations. Region probe origins and the cell URL the
  director returns go through the same check, so a compromised director
  cannot aim the harness at the operator's own network.
- Bound --runs, --rounds, runs, --gap, and --hold as whole numbers, so
  'Infinity' exits 2 instead of looping forever against the relay.
- Report a region as UNREACHABLE when every probe fails, rather than letting
  Math.min([]) spread into NaN and read as ok.
- Bound the director /v1/resolve and /v1/regions fetches and report timeouts.
- Return null openMs when the socket never opened, and clear dial, cell, and
  RPC timers on the first terminal event so Node exits promptly.
- Default handle.rpc() to RPC_TIMEOUT_MS, not DIAL_TIMEOUT_MS.
- Write the state file through a helper that creates the parent directory,
  refuses a symlink, and forces 0600 on an existing file; refuse to read one
  that is readable beyond the operator.
- Read the pairing link from stdin or a 0600 file, never argv.
- Reject missing and invalid positionals with usage and exit 2.

Adds unit tests for the pure guards: argument parsing, bounded integers,
port and origin classification, DNS vetting, state-file modes and symlinks,
region verdicts, and pairing-link decoding. None opens a socket, and every
network path stays gated on ORCA_RELAY_BENCH_LIVE=1.

* tools(relay-bench): settle in-flight rpcs and guard an empty region catalog

Follow-up to the review fixes. Clearing a pending rpc timer without a
resolution swapped a 15 s timeout for an await that never returns, so the
teardown paths now settle each waiter with a closed result. A director that
answers /v1/regions with no regions now reports that and exits 1 instead of
printing an empty round.

* tools(relay-bench): attach the origin-vetting doc to the function it describes

* fix(tools): resolve a director-named cell through DNS and fail cdp-eval clearly
2026-09-07 13:28:47 -04:00
Jinwoo Hong 06a607a1d7 feat(orchestration): make multi-agent workflows durable (#16904)
<!-- orca-pr-loc -->
<!-- Programmatic LoC summary. Do not edit by hand; rewritten on every commit. -->

| | Files | Added | Deleted | Net |
| :--- | ---: | ---: | ---: | ---: |
| Test | 225 | $\color{#1a7f37}{\Huge{\mathbf{+}}}$​21666 | $\color{#cf222e}{\Huge{\mathbf{−}}}$​2820 | $\color{#1a7f37}{\Huge{\mathbf{+}}}$​18846 |
| Prod | 348 | $\color{#1a7f37}{\Huge{\mathbf{+}}}$​17107 | $\color{#cf222e}{\Huge{\mathbf{−}}}$​4706 | $\color{#1a7f37}{\Huge{\mathbf{+}}}$​12401 |

<!-- /orca-pr-loc -->

## ELI5

Orca now treats orchestration like a durable control plane instead of inferring success from terminal keystrokes. Agents can tell whether a prompt was accepted or a turn started, replay an ambiguous request without sending twice, and recover coordinator mail after a crash. Completed workers can be inspected, released, or retained, and their panes no longer auto-resume as if the work were still running.

## What changed

- **Run receipts** from `run-create/use/current/show/list` are the row without routing plumbing (`home_database`, `coordinator_pane_key`) and without the duplicate `binding` object.
- **`terminal send` receipts are honest and idempotent.** `input_accepted` and `turn_started` are the only stages; `--wait-submit` observes without resending; `--retry-request <uuid>` replays the exact request against the same process incarnation. A transport timeout keeps the retry ID; only a different runtime answering strips it. Value-less or non-UUID `--retry-request` is rejected on the CLI and the SSH shim.
- **Mailbox delivery is committed before wakeup.** Pointer writes are staged in the DB before any PTY byte, replayed once after restart, and never emit a naked Enter. The watermark that parks concurrent deliveries is released with the DB reservation. Restart rescans pointer-pending and `dispatch:` mailboxes.
- **Lifecycle is a guarded transition graph** (`lifecycle-transition.ts`) with a table-driven test over every caller edge. Task reopen/overturn stays in the public contract. A PTY exit during `worker-stop` is the stop succeeding, not a failure.
- **Worker lifecycle CLI:** `worker-start` (`--spec` creates Task + attempt in one call), `worker-show`, `worker-read` (provider transcript first, bounded terminal fallback with a typed reason, local/WSL/SSH), `worker-stop`, `worker-abandon`, `worker-release`, `worker-retain`, `worker-list` (rowid-fenced pagination, fleet liveness, `attention`, literal `nextAction`).
- **Release is an explicit ownership table** (`decideWorkerTerminalRelease`): only an `owned` resource can be settled, the archive is mandatory where reachable, and an owner whose process is proven exited can always get out of `retained` via `archive_status: unavailable`. User-taken-over, external, and transferred panes stay retained.
- **Settled-worker resume fence** (folds in #17651): a settled dispatch whose pane is still open is fenced at settlement, on stop/abandon/exit, and at startup; lifted on release, retain, takeover, and pane reuse.
- **Liveness is `live` / `unverifiable` / `exited` only**, from execution-host evidence. Fleet projection reads the evidence clock, not the relay delivery clock. A host-certified exit outranks the worker's settled state. `unverifiable` never authorizes stop, abandon, retry, or release, in code or in the guide.
- **Federation:** structured reads negotiate by `method_not_found` so every shipped host keeps transcript-first output; exited remote workers are closed before being reported closed; epoch fencing holds across peer restart, downgrade, and pairing rotation; no per-second forced capability probe.
- **Schema v35:** repairs databases stamped v34 by the pre-fix branch (mailbox_handle default, index predicates), drops the write-only `lifecycle_transition_receipts` ledger and five never-read v31 identity columns.
- **Schema v36:** `dispatch:<id>` mailboxes get a real consumer generation on `dispatch_contexts` and `remote_dispatch_attachments`, bumped and fenced in the same transaction on every re-attach (manual inject, worker-start, federated attach). A stale worker whose Dispatch moved to another process now gets `consumer_fenced` instead of silently acking the new worker's Delivery. Run mailboxes already worked this way.
- **Schema v37:** `dispatch_contexts` records its creator (`creator_handle`, `creator_pane_key`), so a coordinator's context-only self-dispatch is bookkeeping rather than a nesting parent; before this, one self-dispatch made every later `worker-start` from that coordinator fail the depth cap. Pre-v37 rows keep counting (fails closed).
- **Dispatch-mailbox ownership is checked, not inferred.** A `check` from a process whose pane no longer holds the Dispatch, or whose last Attempt was abandoned/failed and moved to another terminal, gets `consumer_fenced` instead of an empty inbox that reads as "no mail yet". `--peek`/`--all` stay readable. A paneless caller still gets `stable_pane_required` with the rebind recovery.
- **Liveness certification is stricter:** a `process_exited` stage whose termination reason is `unknown` (a stop that was issued but never observed) projects `unverifiable`, not `exited`. Federated `worker-show` carries the execution host's verdict and host kind instead of a local guess. A live, ready worker with nothing pending has `nextAction: none` rather than pointing at the `worker-show` that produced it.
- **Wire:** `workerShow` keeps `dispatch.task_id` next to `taskId` for shipped CLIs. `ask --json` uses the standard `{ok, result}` envelope like every sibling verb.
- **Migration start-version detection** treats the two v32 recovery columns as versioned. Before this, every shipped database stamped below 32 resolved to the v6 floor and replayed the whole chain (the v23 backfill synthesized 68 phantom retained workers on a real v30 profile). Verified on a copy of a real 62 MB v30 profile: starts at 30, no row delta, integrity ok, 11 ms.
- **Skill guide** rewritten as a ≤200-line kernel plus seven references, to the outcome-first standard (Result / Done / Safe failure first, conditions not case lists, one done bar, references loaded at the point of use). The canonical loop uses `worker-start --spec`, names `worker-list` for completion accounting, documents `--retry-request` / `request-show` / `--wait-submit`, and requires positive evidence before any stall action. The other seven guides get the same treatment in #18724, split out so this PR stays orchestration-only.
- **`rpc/methods/orchestration-*`** (126 flat files) regrouped into `orchestration/{worker,federation,messaging,runs,gates}/`.

## Why

User reports showed the same boundary failures: false `agent_prompt_stalled` causing duplicate sends (#15180), coordinators unable to trust screen scrapes, cold-parked terminals receiving a pointer without the submit, settled workers accumulating as live tabs and auto-resuming after restart, and no way to tell a stalled worker from a working one.

## Linked issues

Fixes #15180. Fixes #17935 (orchestration skill description is 866 characters; a guard now caps every bundled skill at 1,024). Supersedes #17651 (fence folded in). Advances #16660, #16522, #14907, #13047.

## Review record

This PR was reviewed adversarially after revival: eight independent lenses (lifecycle, mailbox, send, worker, federation, transcript, complexity, live ergonomics), each required to prove findings with a failing test. That produced 16 proven blockers, all fixed with red-then-green regression tests, followed by two re-review rounds and a third fix wave that caught 3 regressions introduced by the fixes and 7 fixes that missed their target; all closed. A final pass (five lenses incl. a live built-runtime smoke, then a re-review of the fix wave) found and fixed seven more, chiefly the stale-worker mailbox steal, the self-dispatch depth wedge, and the unproven-exit certification. Three independent Codex (gpt-6-astra) passes followed: the first found nothing new, the second found and fixed 3 defects (task-status reachability, WSL-local host classification, peer-capability epoch), the third found and fixed 6 (production PTY controller never installed settled writes, ambiguous in-flight pointer failures allowed duplicate replay, SSH/relay deadlines cut off a valid `--wait-submit`, stop-vs-exit race during inspection, and two release-recovery paths for vanished or exited terminals). The full record (findings, proof tests, triage, declines with reasons) is archived outside the repo.

**Rework after the live smoke.** A first live cross-host run on the shipped adhoc build (this Mac, a paired Windows host on the same build, a paired Mac on 1.4.195, and an SSH host) found a P1: a running local worker read `unverifiable`/`missing_status` because the fleet snapshot rows lacked the terminal handle the matcher keyed on. A 59-row failure table over every bug fixed during review showed the same two classes recurring: a fact dropped in transit through optional fields, and two authorities for one fact. Two blind designs (Opus, Codex) converged on the same mechanisms, and the scoped tranches landed here with red-then-green seam tests from the real producer to the real consumer, faults injected only at the transport or hook-ingest boundary:

- **Settlement (data-loss class):** one three-valued `WriteSettlement` (`accepted | refused{reason} | unverifiable{reason, bytesHandedToTransport}`) from the SSH multiplexer through daemon client, providers, controller, to pointer staging. No boolean, no rejection-as-third-state. The two silent degrades that fabricated a handoff are deleted; a provider that cannot settle refuses before any effect. Pointer text and Enter share the contract; a partial flush is `unverifiable`, never `refused`.
- **Evidence identity (false-liveness class):** fleet agent-status evidence is a tagged union (`binding: worker | pane | unresolved{reason}`, `clock: observed | delivery`) minted once at ingest, so a hook row captured on one process incarnation can never bind to a later dispatch on the same pane. The matcher's `!worker.paneKey ||` defaults are gone. One host-scope parser replaces two.
- **Small pre-merge items:** `capability_unsupported` from an old peer is no longer relabelled `host_unavailable`; a producer census test asserts every agent-status consumer path projects a pane-only hook row as `live`.

Two ergonomics defects the second live run surfaced on a real database are fixed here too: a pre-v3 dispatch already marked `completed` projected as `outcome_unknown` / `requiresAction: true` forever (three copies of the outcome ladder disagreed on legacy rows; now one resolver, legacy `completed` reads `succeeded` with nothing to act on, legacy `failed` stays actionable on the failure), and an unscoped `worker-list` enumerated the entire database (now defaults to the Run bound to the calling terminal, `--run` overrides, and the receipt's additive `scope` field says which).

A third live round on the shipped adhoc build of `b082443e1f` (same four hosts) plus an unscripted run in the user's own prompt style (a plain Claude Code shell, `/orchestration`, three workers, zero errors, bound-Run default confirmed) found two more branch defects, fixed with red-then-green tests: a worker freshly started on a paired server projected `unverifiable`/`host_indeterminate` with `requiresAction` for ~3 minutes, including after its own `worker_done`, because the host's federation observation returned `missing_liveness_verdict` for any PTY the liveness register had not yet swept (the host now reads a connected pane it owns locally as `live`; disconnected or SSH-scoped panes stay `unverifiable`); and six pre-v3 completed rows still carried an `input` category because settling through the task-status path or `failDispatch` never closed the Dispatch's pending question threads (both paths close them now, and schema v38 closes threads already pending on settled rows). The guide's `worker-start` examples now show `--model sonnet`, since an omitted model inherits the launcher's default.

A Codex adversarial pass on the tranche diff found one real design hole (identity minted at read time instead of ingest, now closed) and two daemon settlement paths that threw instead of settling (fixed). Two `@ts-nocheck` runtime mixins on these paths were extracted into checked modules; the repo-wide `@ts-nocheck` count is unchanged at 171.

Deletions during review: ~1,900 lines (write-only ledger, unread columns, dead v1 archive path, test harnesses shipped in prod, duplicated liveness and state-machine copies, self-capability checks that were compile-time true).

## Testing

- `pnpm typecheck:tsc:node|cli|web` clean
- `pnpm run check:code-quality:changed` 0 findings; `check:react-doctor:changed` 0
- `pnpm verify:bundled-skill-guides`, `verify:skill-bundle-manifest`
- full `pnpm test` on the integrated head: 72,332 pass / 292 skipped; the only failures were three non-PR files (two zsh live-shell suites hit a node-pty spawn-helper ENOENT while a concurrent native rebuild ran, 44/44 in isolation; `release-checkout.unit.test.ts` is a known 30 s load timeout that passes in isolation on `origin/main` too).
- CI on 70b4811267 (rerun, pre-Codex): the only reds are five SSH e2e specs plus `terminal-send-agent-prompt-submit:198`, each shown failing identically on main (main's E2E workflow is red on its last 40 runs). The terminal-send spec is root-caused and fixed separately in #18707. The Windows hook-service flake (#17721) and the federation load flake did not recur.
- Skills: `pnpm exec vitest run` over the skill gate files plus `src/cli`, `config/scripts`, `src/main/skills` pass; live smoke on the built CLI of `skills get orchestration` and `--full` (7 references).
- live headless runtime (`orca-dev serve`, isolated profile): canonical loop, stop, release, archive read, retry rejection, stale-handle check, SIGKILL-and-replay all verified with receipts
- Live cross-host smoke on the shipped adhoc build of `0d465e7931` (this Mac and a paired Windows host on the build, a paired Mac left on 1.4.195, an SSH host): local, paired-new, paired-old and SSH loops all settle; running workers read `live` on every host and `exited` after release; the old peer reads `capability_unsupported` and refuses release honestly. Injected 10 s relay stall with a send in flight: delivered exactly once after recovery, zero duplicates. Every liveness field across 104 receipts is only `live` / `unverifiable` / `exited`.
- Final live cross-host smoke on the shipped adhoc build of `b082443e1f` (same hosts): every loop settles; 942 of 948 legacy completed rows read settled with `requiresAction: false` before the question-thread fix and all of them after; `worker-list` scope reads `bound` / `flag` / `all` correctly; 122 JSON receipts carry only `live` / `unverifiable` / `exited`. Unscripted prompt-style run: clean.
- Confirmation smoke on the shipped adhoc build of `2da076d4e9` (this Mac and the paired Windows host, both updated): a freshly started Windows worker reads `live` on the first fleet poll and on all 20 that follow, with no `host_indeterminate` at any point, and `exited` after release; all 948 legacy completed rows read `requiresAction: false` with `nextAction: none` after schema v38; every verdict across 60 receipts is `live` / `unverifiable` / `exited`.
- Not physically exercised: WSL hosts, the renderer notification bell (headless has no renderer), same-session fence via a real pane close (renderer-only state), restart mid-delivery on a real app (covered by e2e only).

## Notes

- Remote-wire additions are optional fields or `method_not_found`-negotiated methods; one new Electron-only IPC channel (`agentStatus:legacyWorkerTerminalResumeFence`) never crosses the wire.
- SSH contact loss remains `unverifiable`; the execution host stays authoritative.
- Intentional wire projection change: an SSH host scope with an empty `targetId` now projects host id `ssh` instead of an empty string (remote-wire-compatibility rule 3, old clients decode the same field). A fleet pane key without a terminal handle is now `unidentifiable` rather than matched by pane key alone.
- Found live but pre-existing on main, filed separately: a relay daemon-start collision during transport loss rewrites the endpoint credential and wedges the surviving relay (host needs a manual kill); `terminal create` on a reconnecting SSH host reports an opaque `No PTY provider for connection`; `terminal list` reports `orphaned:false` and `terminal close` reports `ptyKilled:true` for a pane whose relay is gone (orchestration's own projection reads `unverifiable` correctly at the same moment).
- Downgrade after this PR is not a supported path: main opens a v37 database and early-returns (its inserts still work against the v36/v37 defaulted columns), but its one-outstanding-Delivery-per-Run index is a no-op against the branch's mailbox-scoped index of the same name.
- Known follow-ups (not blockers): `worker-list` materializes every dispatch row per call; a positive "agent absent" signal distinct from PTY liveness is a product decision left open (a headless fake agent never reaches `live`, so its `nextAction` stays `inspect`); a context-only self-dispatch still lists as `role: worker` in `worker-list`; `dispatch` task-not-found / task-not-ready / inject-rejected still surface as `runtime_error`; task and inbox receipts still expose raw row columns. Deferred skill product decisions live on #18724.
2026-09-06 14:34:03 -04:00
OrcaWinandOrca Worker fba90e017c fix(windows): copy the daemon host exe verbatim instead of renaming it (MDE T1036) (#17865)
* docs(windows): document the EDR signal surface

Six Microsoft Defender for Endpoint incidents fired against Orca 1.4.192 in
eight days on one enterprise Windows 11 / Intune tenant. All six were
behavioural process-tree scoring, not signature hits; two escalated to
multi-stage incidents mapped to ATT&CK Execution and Collection.

Add a reference doc mapping each attack-technique-shaped behaviour to the code
that produces it and to why it exists: the renamed daemon image (T1036), the
per-process PEB read, encoded policy-bypassed PowerShell (T1049), caret-escaped
cmd.exe lines, and computer-use screen capture plus runtime-compiled MSIL
(T1113). Records that signing is not the gate -- reputation is signer plus
hash-keyed prevalence -- and carries the two evidence gaps the report noted.

Adds an engineer checklist, deployment guidance for admins (AV path exclusions
do not suppress EDR behavioural alerts; an MDE alert suppression rule does), and
an explicit pre-deployment warning about computer use.

* docs(windows): correct the PowerShell flag inventory and admin paths

Review corrections to the EDR posture doc.

The "encoded, policy-bypassing PowerShell" list conflated three different
shapes and was incomplete. Split it into the three tiers an EDR actually scores
differently -- bypass plus encoding, encoding alone, and bypass alone -- and add
the sites it missed, including windows-mobile-firewall.ts, which encodes a
script and launches it elevated through Start-Process -Verb RunAs. system-fonts.ts
(-Command) and desktop-script-provider-bridge.ts (-File) were listed as encoded
and are not. Notes that a raw grep under-reports, because the hook sites reach
-EncodedCommand through wrapWindowsPowerShellEncodedCommand.

Attribute the in-payload Set-ExecutionPolicy move to #16576 rather than to
#16003's measurement, which keyed on -WindowStyle Hidden + -EncodedCommand, and
record that the launcher's own tradeoff is unverified on a real box.

Admin guidance was missing two ways a suppression rule pinned to one full path
misses real activity: the .staging-<hex> sibling that exists mid-update, which
is when the update-cluster incidents fire, and the userData fallback when
LOCALAPPDATA is unset.

Also: state the measurement conditions on the process-table timings, note that
Hermes has surface even though we have no telemetry for it, note that the
uninstaller names are electron-builder-generated and in no repo file, drop a
volatile line count, and mark the per-operation computer-use shape as being
addressed by an unmerged change. Drops the duplicated AGENTS.md section, keeping
the indexed bullet.

* docs(windows): reconcile the EDR posture doc with the shipped remediation

Three claims in this doc became false once the rest of the Windows EDR set
landed, and two told engineers the opposite of what the release does.

The process-table section still described one shared snapshot taken with
`Memory | CommandLine | CreationTime`, argued that splitting the cache per
field set "would restore exactly the fan-out it exists to prevent", and
concluded the shape was unfixable because "the information is only in the
PEB". The split shipped (identity opens no handle at all), `Memory` is
retired, and the command line now comes from the kernel through
`ProcessCommandLineInformation` -- `ReadProcessMemory` is absent from the
compiled addon and a ratchet asserts it against the import table. An engineer
reading the old text would have concluded both fixes were dead ends.

The PowerShell site inventories were stale in three of four lists: the port
scan went native, every `-ExecutionPolicy Bypass` + `-EncodedCommand` pair
was dropped as a measured no-op, and of the unencoded-bypass list only
`wsl-cli-scripts.ts` survives. Regenerated against the merged tree, including
the sites that reach the flag through `wrapWindowsPowerShellEncodedCommand`
and never spell it, which a raw `rg` misses.

Incident-evidence sections are left alone: they record what the tenant observed
on 1.4.192, not what the code does now.

* fix(windows): copy the daemon host exe verbatim instead of renaming it

Microsoft Defender for Endpoint flagged `orca-terminal-daemon.exe` as MITRE
T1036 (Masquerading): Orca copied its own `Orca.exe` into %LOCALAPPDATA% under a
different name, specifically so the NSIS updater's `taskkill /IM Orca.exe` could
not match, then ran it detached. Because that process is what every other flagged
action was attributed to, the name mismatch acted as a reputation multiplier on
unrelated findings.

The rename was never what made the daemon survive. In app-builder-lib 26.15.3 the
installer's FIND_PROCESS/KILL_PROCESS select processes whose image path is under
$INSTDIR; `taskkill /IM` is only the fallback for hosts where PowerShell is
missing or blocked. Survival is a property of the path, and
%LOCALAPPDATA%\Orca\daemon-host is outside $INSTDIR whatever the file is called.

Derive the host exe name from process.execPath so the copy is byte-for-byte,
name included — it keeps its Authenticode signature and carries no renamed-image
signal. On the no-PowerShell fallback the daemon is now killed with the app and
terminals cold-restore, which is the documented pre-relocation outcome the update
harness already asserts, not a regression.

The uninstall macro no longer needs a distinct name to find the daemon; it kills
the app's own image name (plus the legacy name, for hosts left by older builds).

Adds docs/reference/windows-daemon-host-relocation.md with the survival contract,
the rejected alternatives and their measured costs, and the invariants to keep.

* fix(windows): apply daemon-host relocation review corrections

Scope the uninstall taskkill to the current user with `/FI "USERNAME eq
%USERNAME%"` via cmd.exe, matching upstream's per-user KILL_PROCESS — without it
an elevated machine-wide uninstall reaches another logged-on user's session, so
the "no collateral" claim in the comment was overstated.

Comment the rmSync-before-publish: Windows refuses to delete a running image, so
a live daemon already hosted in this version's dir (same-version reinstall, or a
dev channel reusing a version) throws and materialization fails open.

Doc corrections:
- The fallback selector is the full per-user `taskkill /F /IM "<app>.exe" /FI
  "PID ne $pid" /FI "USERNAME eq %USERNAME%"`, not a bare `taskkill /IM`.
- The probe reads `Get-ExecutionPolicy -Scope Process`, not the effective policy,
  and GPO writes MachinePolicy/UserPolicy — so GPO-managed hosts take the primary
  path-scoped branch. Narrow the fallback triggers accordingly.
- Drop the Authenticode sentence: the old name was equally byte-identical and
  equally signed, so a filename has no bearing on signature validity.
- Name the new update-abort path: the daemon now matches FIND_PROCESS, so on the
  fallback branch an unkillable host reaches the retry loop's MessageBox /SD
  IDCANCEL and Quits, aborting a silent update.
- Correct the customCheckAppRunning rejection. It is ~6 lines, not a rewrite; it
  is wrong because forcing the PowerShell branch where PowerShell is absent makes
  FIND/KILL silently no-op and leaves the real app running with files in use.
- Bound the win honestly: OriginalFilename is empty on the shipped binary, so the
  strongest T1036 indicator never fired, and the residual copy-and-run-detached
  shape still maps to T1036.005.

Reconcile docs/reference/windows-edr-posture.md, which documents the rename as a
live finding and would otherwise contradict this change. Content-only edit:
markdown under docs/reference/ is not oxfmt-formatted as a matter of practice and
nothing in CI gates it, so the file is left consistent with its neighbours.

* fix(windows): expand USERNAME in NSIS instead of spawning cmd.exe

The uninstall macro routed both taskkills through `"$SYSDIR\cmd.exe" /C` purely
so `%USERNAME%` would expand — two extra interpreter spawns on the uninstall
path, in a change whose whole point is not adding scored behaviour, and the
exact `cmd.exe /c` shape the new AGENTS.md EDR bullet warns about. NSIS reads
the variable itself with ReadEnvStr, so the spawns buy nothing.

Verified on Windows 11 that the generated command line does what the filter is
there for: a copy of cmd.exe running as orca-nonexistent-probe.exe (pid 34244)
was terminated by `taskkill /F /IM "orca-nonexistent-probe.exe" /FI "USERNAME eq
<user>"` — SUCCESS, exit 0, process gone.

Guarded on an empty USERNAME because the degenerate case is silent: taskkill
rejects an empty filter value outright ("The search filter cannot be
recognized") and kills nothing, which would leave exactly the orphaned daemon
this macro exists to reap. `*` is rejected as a filter value too, so there is no
branchless spelling. With no USERNAME to scope by it kills unfiltered, as the
macro did before the filter was added. Stack stays balanced: three pushes, two
nsExec pops, three restores.

Also strike the last stale row in windows-edr-posture.md's remediation table.
"Copying our own image under a different name" read as outstanding work; it is
done by this change, so the row now points at the relocation doc. Same class of
staleness as the section reconciled in the previous commit, and git would not
have flagged it either.

* fix(windows): port the daemon-host uninstall sweep into the live NSIS include

The uninstall macro this branch rewrote lived in config/nsis/daemon-host-uninstall.nsh,
which main no longer includes: #17906 consolidated every Windows installer hook into
config/nsis/orca-installer-hooks.nsh because electron-builder accepts exactly one
`nsis.include`. Merged as-is, the rewritten macro would have been dead code while the
shipped uninstaller kept running main's stale sweep — `taskkill /F /IM
orca-terminal-daemon.exe`, which matches nothing now that the relocated host is a
verbatim Orca.exe copy. The RMDir that follows then cannot delete the running image, so
a live orphaned daemon and its ~224 MB tree would survive every uninstall.

Ported into the live include: the ${APP_EXECUTABLE_FILENAME} kill, the USERNAME filter
that keeps an elevated machine-wide uninstall out of another logged-on user's session,
and the register save/restore around both. The legacy orca-terminal-daemon.exe kill
stays so hosts left by older builds are still reaped.

The ratchet that was meant to catch exactly this pinned only the legacy image name,
which main's stale macro already satisfied, so it passed both ways. It now asserts the
app-exe kill and the USERNAME filter, against comment-stripped script — the prose above
the macro names both image names, so a toContain over the raw file proves nothing.

---------

Co-authored-by: Orca Worker <orca-worker@localhost>
2026-09-05 21:11:28 -07:00
Neil 59756b8a1c test: deliver real terminal input and preserve setup reports (#18939) 2026-09-05 16:31:11 -07:00
Jinwoo Hong 436ef827dd fix(browser): present Electron's own user agent so Cloudflare Turnstile clears (#18749)
Orca rewrote every browser session's UA to look like plain Chrome by stripping
the Electron and app tokens. That rewrite is what Cloudflare rejects: a Chrome
UA that ships no client hints reads as a spoof and Turnstile returns 600010,
while the same binary on the same IP clears every challenge with its stock UA.
PR #885 added the rewrite to fix 600010 and was treating a symptom it created;
issue #11518 later found the same rewrite is what broke Google sign-in.

- Keep the stock Electron UA on every partition. The webRequest handler now only
  owns the host-scoped Google auth Firefox switch, which stays unchanged.
- Delete the anti-detection script. Measured on Electron 43: plugins are already
  a real PluginArray, window.chrome exists, and navigator.webdriver is false even
  with the debugger attached, so three of its four premises were wrong, and the
  overrides it installed (instance-level webdriver, non-native Permissions.query,
  stubbed chrome.csi/loadTimes) are themselves published bot signatures.
- Stop attaching a CDP debugger to every browsing guest. Only the auth-UA detach
  listener remains, because a detach clears Chromium's standing UA override.
- Stop sending Runtime.enable into cross-origin iframes when the agent bridge
  auto-attaches. The challenge widget is one, nothing reads iframe Runtime
  events, and the Runtime domain's serialization side effect is the documented
  Cloudflare CDP tell.
- Add a real-Electron test proving the wire identity: stock UA to ordinary
  hosts, Firefox with no client hints to accounts.google.com.

Verified in the dev build: dash.cloudflare.com/login no longer shows
"There was a problem with verification" and scrapingcourse.com's managed
challenge clears, both failing deterministically before.

Fixes #13822
2026-09-04 23:47:15 -04:00
Neil b00ec20731 perf(startup): stop an unreachable SSH host from gating local terminal restore (#18164)
* perf(startup): stop an unreachable SSH host from gating local terminal restore

An asleep or unreachable SSH target held the terminal-restoration gate for the
full 15s reconnect timeout, so no terminal restored — local ones included.
Startup now awaits only the target that owns the active workspace's tabs and
lets the rest connect in the background, folded into the existing deferred path
that reattaches their PTYs on tab focus.

Also splits the renderer's git-environment fence out of the first-window PTY
services barrier: worktree hydration needs shell-PATH generation and the managed
WSL CLI registration, not a daemon PTY spawn or a hook-server bind. Terminal
restoration still fences on the first-window services via
app:prepareTerminalStartupRestoration.

Measured with tests/tools/benchmarks/startup-time-bench.mjs (382 restored tabs,
28k-file profile, medians of 3):
  unreachable SSH host: 17.27s -> 1.34s to renderer-startup-hydration-done
  all-local:             1.98s -> 1.33s

* fix(startup): restore the startup-ordering oracle and keep a connected background SSH target undeferred

app-startup-routing.test.ts pinned the old step names, so the two ordering cases
went vacuous-then-red when the barrier split. Repoint them at the steps that now
carry the same fences: 'git-environment-barrier-await' (shell PATH + managed WSL,
the fence host Git needs) before hydration worktrees, and
'prepare-terminal-startup-restoration' (which awaits firstWindowStartupServicesReady
in main) before terminal reconnect. Both still fail against main's hydration source.

Also: the timed-out-eager rewrite of the deferred list re-added background targets
that had already connected, undoing removeDeferredSshReconnectTarget and sending
fresh panes on a reachable host down the cold-restore path.
2026-09-02 13:10:02 -07:00
Neil 3ab9766e38 perf(worktree): prepare checkouts while the composer is open
Squashed merge of PR #17290.
2026-08-30 12:12:04 -07:00
Neil 5ea9daba97 fix(window): keep automated Electron launches out of the foreground (#17347) 2026-08-29 23:55:00 -07:00
Jinjing c4b39295c1 style: format codebase (#16935)
* style: format codebase

* style: format codebase

* refactor: extract skill install dialog footer and content

Extract footer and content sections from SkillInstallDialog and
SkillInstallManagementDialog into separate components for improved
maintainability and clarity of component responsibilities.
2026-08-28 00:59:21 -07:00
Brennan Benson 913509edeb fix(orchestration): prevent slow worker-start stalls (#16300)
* Extend orchestration agent submission timing budgets

* fix(orchestration): preserve mutation recovery identity

* fix(orchestration): preserve recovery executable identity

* fix(orchestration): keep worker starts and recovery commands safe

* test(orchestration): cover federated worker preflight

* fix(orchestration): harden mutation recovery

* fix(orchestration): redact dispatch recovery credentials

* chore: preserve upstream skill dialog formatting

* test(orchestration): stabilize agent prompt submit e2e

* fix(orchestration): validate federated start receipts

* perf(runtime): cache unchanged prompt verification tail

* fix(orchestration): reject worker-start timer overflow

* fix(orchestration): normalize worker-start timeout defaults

* fix(orchestration): normalize worker-start readiness budgets

* fix(orchestration): normalize federated readiness timeout

* test(runtime): tolerate current-main degradation exports

* chore: preserve current-main orcad formatting

* chore: drop unrelated formatting carryover
2026-08-27 15:25:30 -07:00
Jinwoo Hong c618ec7393 test(reliability): protect recent P0 regression invariants (#16163) 2026-08-24 09:38:46 -07:00
Jinwoo Hong 77ef6bb9ee fix(terminal): verify agent prompt submission (#14962) 2026-08-16 23:34:18 -07:00
Jinwoo Hong d2ffe1f362 fix(terminal): settle CLI prompts for Claude and Codex (#14608) 2026-08-15 15:45:17 -07:00
NeilandOrca 5ea7df1a5b fix(terminal): make DECSET 2031 subscriptions silent (#13904)
fish arms `CSI ?2031h` before painting each prompt and withdraws it when it
hands the tty to a child — a ~1ms window. Orca answered that subscribe with
`CSI ?997;Nn` across a 1-3ms renderer hop, so the reply landed after the
withdrawal and was read as stdin by the next child, corrupting `brew`/`npx`
`[y/N]` prompts.

The reply is not stale by Orca's own view when written (measured
staleReplies: 0), so no suppress-the-stale-reply scheme can close this — the
information needed to suppress does not exist yet. Nothing asked for the reply
either. The Contour spec says a terminal "should only send out the DSR when the
palette has been updated"; Ghostty (Termio.zig:729 — force=true reachable only
from the ?996n DSR), iTerm2 (VT100Terminal.m:995 — flag only) and xterm.js
(InputHandler.ts:2035 — flag only) all emit nothing on the DECSET. So stop
entering the race: record the subscription, answer nothing.

Of 17 real programs measured under a pty, only fish, tmux, claude and opencode
subscribe; none block on a reply, and answering produces one redundant palette
re-query and zero rendering difference. tmux is the only one that sends `?996n`,
which Orca still answers.

- Subscribes are record-only at all four emitters (live scan, hidden-gate fact,
  parked byte watcher, parked responder — the last is deleted, it only replied).
- `?996n` answers, the subscription registry, and the theme-flip push are
  unchanged. `paneLastThemeMode` is still seeded at subscribe so the next
  appearance re-apply is not read as a flip.
- Replay grammar carries `?2031l` alongside `?2031h`, so a late-attaching remote
  client no longer registers a subscription the TUI already retired.

Also closes fish-integration gaps found alongside: `unset` (which fish lacks)
becomes `set -e` on paths parsed by the client's login shell, `config.fish` is
parsed for agent-home detection, and bracketed-paste startup delivery is made
consistent across local/daemon/relay.

Regression test drives real fish 4.7.1 under node-pty and asserts on what the
child process reads; it fails against pre-fix code with the exact payload from
the issue. CI installs fish 4 and fails loudly rather than skipping.

Closes #9993

Co-authored-by: Orca <help@stably.ai>
2026-08-11 21:16:36 -07:00
Neil 991a3fe963 chore(lint): update oxlint to 1.77 and enable no-op cleanup rules (#13901)
Enable eleven oxlint rules that simplify code without changing behavior, and fix
every existing violation. Each candidate was gated on measured cost rather than
assumption, so rules that regressed runtime performance or type checking were
dropped instead of suppressed.

typescript/no-redundant-type-constituents is the largest addition: 113 sites, no
autofix. Dead constituents are deleted. Where the redundant literal existed to
document intent (`string | 'all'`), it is preserved as `(string & {})`, which
keeps the autocomplete hint the original code was reaching for instead of
flattening it away. The rule also caught a broken import —
remote-shared-control-retirement-probe.ts pulled RuntimeStatus from
src/shared/types, which does not export it, so the type silently degraded to
`any`; no tsconfig covers that file, so tsc never saw it.

oxlint stays at 1.77.0 rather than 1.78.0 because .npmrc sets
minimum-release-age=4320 and 1.78.0 is younger than that window.

Rules evaluated and rejected, with what disqualified each:
- prefer-string-raw: String.raw is a runtime call, not a literal (184x slower)
- prefer-string-replace-all: 26% slower
- text-encoding-identifier-case: ~5% slower, reproducible
- prefer-spread: [...str] is 110% slower than split('') and differs on surrogates
- no-implicit-coercion: `!!x` narrows types and `Boolean(x)` does not (22 tsc errors)
- prefer-arrow-callback: arrows are not constructible, breaking `new` on mocks
- object-shorthand: rewrites source text asserted by a tracked reliability gate
- switch-case-braces: pushes ten files past max-lines, which cannot be suppressed
- no-useless-switch-case: drops `case undefined:` that switch-exhaustiveness-check needs
- arrow-body-style: 115 violations have no fix, and it breaks max-lines
- newline-after-import: false-positives on the leading-semicolon ASI idiom

electron-vite-output-contract asserted on the literal
Object.prototype.hasOwnProperty.call text; retarget it to Object.hasOwn, which
rejects inherited keys identically.
2026-08-11 18:19:43 -07:00
Brennan Benson f19ff5be68 fix(browser): keep one identity across hosts during Google sign-in (STA-3811) (#13667)
While the auth document is on screen the WebContents UA is Firefox, so its
cross-host subresource/XHR requests (gstatic, play.google.com, the sign-in
challenge endpoints) reached the header layer carrying the Firefox UA yet still
bearing Chromium client hints, which the else-branch rewrote to Chrome. That
paired a Firefox UA with Chrome client hints on every non-auth Google host — a
sharper cross-host identity tell than either signal alone, and a plausible
cause of the password-submit challenge greying out and stalling.

Strip client hints on any request already carrying the Firefox auth UA so the
UA and hint surfaces tell one Firefox story for the whole flow. Gated on the
same googleAuthOverride flag as the auth-host switch, so imported-native
profiles are unaffected and the clean-Chrome default for non-Google sites
(Cloudflare) is untouched.

Extends tests/tools/google-signin-ua-probe.cjs with app-current/app-fixed
modes that mirror the shipped code and log per-request identity; on the real
accounts.google.com load they show 18 firefox-ua-with-chrome-hints cross-host
mismatches before and 0 after.
2026-08-11 16:37:53 -07:00
Brennan Benson a70291a918 perf(orchestration): index mutation receipt capacity (#13647) 2026-08-10 16:17:52 -07:00
Brennan Benson 4c49989c2e refactor(codex): delete the unreachable managed shared-mirror lane (#12614)
PR 9501 shipped real-home routing for the host system default, and the
env override that could turn it back off was never a shipped control. The
managed-account half of the shared runtime mirror has been unreachable
since: every host account routes to its own self-contained CODEX_HOME
before that code runs.

Delete the flag module and its env plumbing plus the managed branch of
syncForCurrentSelection and the six helpers only it called. The three
lanes that still use the shared mirror -- Windows, a custom CODEX_HOME,
and a hook-lane gate that reports unusable -- are untouched, as are every
legacy migration and the WSL read-back helpers.
2026-08-05 12:57:02 -07:00
Brennan Benson 0ce108d935 fix(browser): add native-UA session profiles (#12608)
* fix(browser): add native-UA session profiles

* test(browser): add Google sign-in UA probe

* fix(browser): preserve native profile UA identity
2026-08-04 19:07:23 -07:00
Neil 339045b150 fix(runtime): coalesce concurrent host terminal focus (#11841)
Bound exclusive host navigation to a generation-aware latest-wins
single-flight so bulk open and switch fan-out stay responsive on large
remote fleets. Add freeze repro harnesses and navigated settlement.
2026-08-03 02:18:05 -07:00
Neil 6e2a88c091 perf(worktrees): avoid redundant fetch during deletion (#11918) 2026-08-01 21:59:41 -07:00
JinjingandOrca a07427e970 fix(ssh, relay): keep remote sessions alive through reconnects and backpressure (#11999)
* fix(ssh,relay): stop remote connections from being killed by backoff and frame caps

Three independent connection killers found in the SSH/remote freeze audit.

FINDING A - the reconnect ladder never escalated for post-handshake drops.
scheduleReconnect() used the single published state.reconnectAttempt for both
the delay index and the give-up test, and runReconnectAttempt() zeroed it
before connecting (ssh.ts gates the relay redeploy on 0-at-connected). Every
post-handshake drop therefore re-entered at 1000ms forever, ~3600 relay
redeploys/hour, and 'reconnection-failed' was unreachable for a flapping host.
New SshReconnectLadder splits the delay index (advanced by every retry) from
the failure streak (advanced only by a failed handshake), so flaps back off
while give-up semantics stay byte-identical to shipped.

FINDING B - notify() closed the client whenever a frame exceeded the producer
frame capacity, conflating a permanently un-sendable frame with transient
backpressure. A 5000-event fs.changed is 425KB against a 49KB cap, so the
watcher flood killed the link and re-killed on every reattach+replay. notify()
now drops and logs once per generation; fs.changed is chunked to each sink's
capacity with a control-lane overflow marker as the resync fallback; agent-hook
envelopes shed lastAssistantMessage/interactivePrompt/subagents to fit.

FINDING B2 - sendResponse routed >1MB responses to a lane whose admission
ignores the frame cap and closed the client on rejection, so a large
fs.listFiles dropped the SSH host. It now substitutes a JSON-RPC error so the
request fails instead of the connection.

Also moves fs.streamEnd/fs.streamError to the control lane so a terminal frame
cannot be dropped by the producer-lane check.

Co-authored-by: Orca <help@stably.ai>

* fix(relay): stop the overflow marker from re-killing the link it protects

Round-1 review fixes on the P0 freeze work.

The control-lane overflow marker could reinstate the exact failure this P0
removes: dispatcher-client-writer closes the client when control-lane
admission fails, and admitControl is the only lane that returns an error, so
one marker per failing batch accumulated to the 256-frame/1MB bound and
dropped the link. Markers are now deduped to one outstanding per
(client, root), cleared on settle.

Chunking also defeated the renderer's per-payload directory dedupe -- events
are now stable-grouped by parent directory so one directory lands in one
chunk -- and the halving walk overshot the byte minimum ~1.7x while the fast
path paid three JSON encodes; both are fixed by publishing first and sizing
from a measured bytes-per-event estimate.

Agent-hook shedding now surrenders the blocking interactive prompt LAST
rather than first, so a degraded envelope cannot strand a pane at
state=waiting with no answerable question card.

The dropped-notification log now distinguishes over-capacity from producer
queue backpressure and no longer lets the first dropped method silence every
other producer for the life of the connection.

* fix(relay,ssh): keep status delivery and terminal frames from trading one freeze for another

Round-2 review fixes.

The round-0 change from close-on-rejection to silent drop removed the only
redelivery path for agent.hook envelopes: they are fire-and-forget and the
per-pane cache only replays on handler install, so a saturated link stranded
a pane on a stale Working spinner until reconnect. Closing used to guarantee
delivery by forcing that replay. Envelopes now publish per client and pend
for bounded latest-wins redelivery when the producer queue rejects them.

Shed fields are now named on the wire. The subagent roster is not cosmetic --
the renderer replaces rather than merges it, and hibernation gates on its
length -- so an unmarked shed could sleep a live pane.

fs.streamEnd rode the control lane because it must not be dropped, but that
lane kills rather than drops. The stream's concurrency slot is now held until
the terminal frame settles rather than until the fd closes, capping queued
terminal frames well under the control budget; overflow costs one refused
read instead of the connection.

The watcher chunk walk now stops while producer retention sits past its
reserve and degrades to a resync, so a 5000-event flood cannot fill the queue
that interactive PTY traffic shares and stall every remote terminal.

The reconnect ladder caps its flap-path delay so delay plus handshake timeout
cannot cross the relay grace floor and let the remote daemon kill live PTYs.

Also: the suppression key no longer embeds a NUL byte, which had made the
file binary to git and grep; producerEnvelopeBudget no longer reports
infinite capacity for a departed client; the drop logger no longer encodes a
frame it will not log; and an over-capacity response substitution no longer
settles as if the result had been delivered.

* fix(relay,ssh): restore relay-shed status fields and scope backpressure per client

Round 3 + 4 review fixes.

Watcher chunking is now gated on the *client's* retention reserve rather than
the dispatcher-wide one, so one stalled peer no longer forces a healthy client
into a full file-tree resync. The relay-lost redeploy ladder no longer burns its
6-attempt budget while the SSH transport itself is down: it holds at the 15s step
with a non-terminal status and rearms, so a laptop that slept past the ladder
comes back instead of landing on a terminal "give up" banner.

The shedFields wire marker had no consumer, so an agent-hook envelope whose
subagent roster was dropped to fit the frame read as "roster cleared" on the Orca
side: live child rows blanked and a done pane became hibernation-eligible while
its teammates were still running. ingestRemote now restores shed fields from the
cached payload (interactivePrompt deliberately excluded — a stale answerable
question card is worse than none).

Also: stream terminal-frame slots are counted per client, since the control queue
they protect is per client; the chunking fast path no longer logs a drop for a
batch it goes on to deliver in full; -32010 is now RelayErrorCode.ResponseOverCapacity.

Test debt from the review: pending-pane eviction, per-client stream isolation, and
the reconnect budget are now asserted rather than assumed; four fragile exact-byte
pins dropped in favour of the tier comparisons that carry the requirement.

* fix(relay,ssh): restore relay-shed status fields and scope backpressure

- Oversized relay responses now fail their request instead of closing the connection,
  preventing one frame from killing every pane on the host
- Restore subagent state for correct hibernation; don't resurrect stale prose
  across turns
- Account for relay re-establishment and PTY reattach time in SSH flap delay caps
- Only log drops of final unsendable envelopes, not temporary rejections during
  measurement probes
- Fix watcher overflow marker release race when notification admission rejects
  without settlement; use precise byte counting for event batching

* Restore relay-shed fields with digest validation and scoped backpressure

Validate that shed subagent rosters match their wire digest and turn identity before
restoration, preventing stale roster resurrection. Compact interactive prompts for waiting
states instead of dropping them. Demote control-queue overflow to non-fatal rejection so
clients can retry on capacity recovery, keeping the link alive during transient backpressure.

* fix(relay): correct ResponseOverCapacity error code

ResponseOverCapacity should use -33008 to stay in the -33xxx range
for relay protocol errors, not -32010.

* fix(relay): close client when pty.replay overflows control queue

Replay is never retried, so it uses the control lane where overflow
is fatal — the writer closes the client and reconnect reloads history
rather than stranding a short buffer.

* fix(relay): prevent infinite redeploy on flapping SSH transports

Charge reconnect attempts when connection restores mid-backoff, preventing
infinite loop on transports that flap between states. Refactor control overflow
handling to use entry property instead of WeakSet marker for clarity.

---------

Co-authored-by: Orca <help@stably.ai>
2026-08-01 14:27:16 -07:00
Neil edb5607e28 ci: block new root-level entries (#11903)
* ci: guard repository root additions

* fix: clear existing type-aware lint warnings
2026-08-01 01:48:24 -07:00
Jinjing ad1e58d966 chore: declutter top-level repo layout (#11890)
Remove one-off incident docs and committed test-results noise, move
dev/repro/bench tools under tests/tools, and relocate i18next config
into config/ so the GitHub root scrolls to the description faster.
2026-08-01 00:25:35 -07:00