Commit Graph
20689 Commits
Author SHA1 Message Date
Kelvin Amoabaandagustin2606 83ec1c09dd fix(browser): keep Orca shortcuts working while an HTML document preview has focus (#41066)
* fix(browser): forward Orca shortcuts from focused HTML document previews

Workspace-document preview guests never installed the guest shortcut
forwarder that browser pages and plugin frames use, so once a preview
took focus Ctrl+Tab and the other window shortcuts stopped reaching
Orca. Install it in attachWorkspaceDocGuestPolicies, bound to the
preview's own host, with browserPage: false so browser-page chords stay
with the document, and dispose it with the rest of the doc policy.

* test(browser): pin Ctrl+Tab forwarding from a focused HTML document preview

* fix(browser): activate a document preview's split when it is clicked

A press inside the preview never reaches the host DOM, so forwarded
shortcuts such as Close acted on whichever split was active before.

* refactor(browser): read a document preview's page id from its guest policy

---------

Co-authored-by: agustin2606 <agustinvarela2606@gmail.com>
2026-11-11 07:40:31 +00:00
Kelvin Amoaba e33a851d8e fix(claude-accounts): restart sessions after a phone or CLI account switch instead of asking on the desktop (#41064)
A switch made from Orca Mobile or the CLI opened the 'restart running sessions?' dialog on the desktop, where the person switching can't see it. Under the 'Ask' setting these switches now restart the moved sessions the way 'Always restart' does: each waits for its turn to end and resumes its conversation on the new account.
2026-11-11 07:29:01 +00:00
Neil 280bcbc48a refactor: organize usage and CSV modules by concrete owners (#41059) 2026-11-10 23:26:07 -08:00
Kelvin Amoabaandzhangxaochen 8cac33b751 fix(mobile): stop dropping display-mode resizes after a terminal opens (#41061)
The phone's stale-resize filter took the binary scrollback frame's seq as
its layout high-water mark, but that seq counts output bytes, not layout
changes. A Desktop/Phone toggle on a terminal with little output then
looked older than the mark and was dropped, so the phone never resized
and kept showing the old mode. Seed the mark from the subscribed frame's
layout seq and leave scrollback out of it.

Co-authored-by: zhangxaochen <3416677+zhangxaochen@users.noreply.github.com>
2026-11-11 07:26:01 +00:00
Kelvin Amoaba c96271dc5b fix(mobile): say Update mobile on desktop chats an old phone app can't show (#41063)
An old mobile app lists a desktop-created structured chat it cannot render
with the title 'Update to view', which doesn't say whether the phone or the
desktop needs the update. Name the phone app, and document the symptom and
recovery steps in the mobile troubleshooting docs.
2026-11-11 07:25:05 +00:00
OrcaWinandOrcaWin fce98d7d21 test: remove 167 low-value cases and 93 suites (#41057)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-11-10 23:17:08 -08:00
Kelvin Amoaba e183ba8bac fix(daemon): stop the macOS logout watch adding a login record every two minutes (#41056)
* fix(daemon): stop the macOS logout watch writing a login record every two minutes

The watch ran /usr/bin/login as its session-health probe, and each run adds an
entry to the macOS login history. Check DNS health first and only run the login
probe when the resolver already looks dead, which retirement requires anyway.

* fix(daemon): keep probing login after a rejection until PAM accepts again

Gate on the last login verdict instead of the rejection streak, so a sleep reset
of the streak cannot leave a rejected verdict cached while DNS looks healthy.

* test(daemon): keep coverage for not retiring once DNS recovers

* test(daemon): drop the duplicate retirement replay from the resolver gate test
2026-11-11 07:07:45 +00:00
Neil d47ed1c468 fix(agent-status): skipping a Codex async question clears it on SSH panes too (#41031)
* fix(agent-status): skipping a Codex async question clears it on SSH panes too

An SSH pane's Codex hooks are normalized by orcad on the host, so the
desktop's own hook server had no row to apply the skip keystroke to. The
renderer now sends a remote pane's skip to the owning runtime through a new
terminal.inferCodexQuestionSkip RPC, which applies it to the host's tracker.

* test(e2e): skipping a Codex async question clears a managed SSH terminal's row
2026-11-10 23:07:24 -08:00
Neil 7803c9b91c fix(source-control): name the base by its short ref in the empty state (#41033)
The Source control empty state named the base by its fully qualified ref ('…no changes ahead of refs/remotes/origin/master'). Show it the way people know it ('origin/master') with a small shortRefLabel helper.
2026-11-10 23:01:35 -08:00
Kelvin Amoaba b0c431c1a3 fix(orchestration): archive a released chat worker's journal up to the byte bound, not one 200-row page (#41049) 2026-11-11 06:36:42 +00:00
Neil 3217e60e84 refactor: give pane conversations a concrete owner and remove redundant wrappers (#41038) 2026-11-10 22:36:36 -08:00
Kelvin Amoaba 37ae7de470 fix(cursor): list your account's Cursor models in the agent launch picker (#41046)
* fix(cursor): read the default marker in cursor-agent model rows

* feat(agent-launch): list a terminal-only agent's own models in the launch picker

* fix(agent-launch): cache Cursor listings and keep the seed for fallback lists

* fix(agent-launch): keep the host catalog for agents that have structured chats

* refactor(agent-launch): resolve the picker's discovery runtime once per render
2026-11-11 06:33:00 +00:00
Neil d5022c6e46 style(new-workspace): icon-only Create From tabs and shorter Create button (#41036)
* style(new-workspace): icon-only Create From tabs and shorter Create button

* style(new-workspace): let Tabs primitives own source-tab spacing

* chore(i18n): add Create button label to English catalog
2026-11-10 22:31:35 -08:00
buf0-bot[bot] 12a6326d1c Update README downloads badge (#41050)
Co-authored-by: buf0-bot[bot] <252831055+buf0-bot[bot]@users.noreply.github.com>
2026-11-11 06:29:15 +00:00
BoyceandKelvin Amoaba 42f55bfa37 fix(session-history): index Codex rollouts archived as .jsonl.zst (#40593)
* fix(session-history): index Codex rollouts archived as .jsonl.zst

Codex compresses rollouts idle for 7+ days to rollout-*.jsonl.zst. The
session scanner only discovered .jsonl files, so every Codex chat older
than about a week disappeared from Agent Session History. Discover .zst
rollouts, decode them through a shared zstd stream wrapper above
openTranscriptReadStream (native, WSL UNC, and SSH alike), never resume
a compressed archive from an offset, and drop a .jsonl.zst candidate
when its plain .jsonl twin is listed, matching Codex's own rule.

* fix(session-history): admit only rollout-named zst archives and close raw zstd streams early

Review follow-up on #40593: a filePredicate limits compressed discovery
to rollout-*.jsonl.zst so unrelated .zst files are never decompressed,
and zstdTranscriptStream links the raw and decoded streams with pipeline
so an early close of the decoded stream releases the raw stream's fd.

* fix(session-history): cover archived Codex rollouts by compressed size and collapse .zst twins in every listing

Archived rollouts keep the bounded resumable reader but never resume, and report their compressed stat size as the read cursor, so search coverage checks match. The .zst suffix is dropped from Codex alias keys with the plain file preferred, which also dedupes scoped walks and rollouts archived in only one home.

* fix(session-history): prefer the live rollout over an archive in another Codex home

---------

Co-authored-by: Kelvin Amoaba <97001695+AmoabaKelvin@users.noreply.github.com>
2026-11-11 06:20:22 +00:00
Kelvin AmoabaandPr1p 4c8feb5621 fix(native-chat): open or reveal folder links, labeled "Open folder", instead of doing nothing (#41040)
* fix(native-chat): open and reveal folder links instead of doing nothing (#40675)

* fix(native-chat): reveal folders in their owning worktree and localize the no-route toast (#40675 review)

- Activate a folder's owning worktree before revealing it: the explorer only
  reveals inside the worktree it shows, so a paired host folder in another
  worktree stayed hidden.
- Reject a paired folder outside the worktree root (e.g. a chat scratch
  folder): the explorer drops those reveals, so report the unavailable route
  instead of returning silently.
- Add an `unavailable` FileOpenFailure verdict with its own localized toast,
  replacing the English error string that other locales would show.

* fix(native-chat): report a folder the OS could not open (#40675 review)

`openFilePath` returning false or rejecting left a local folder click with no
feedback. The directory route now reports the unavailable path in both cases,
with a test covering a refused local folder open.

* fix(native-chat): reveal SSH folders in the explorer and report a failed Shift-open of a folder

Folders below an SSH workspace root still ended in a 'can't open' toast;
the workspace explorer reads through any host, so it shows them. A
Shift-open of a local folder that the OS refused returned silently.
The trailing-separator hint now also hides the download row.

* fix(native-chat): match the owner's host and explorer spelling when revealing a folder

A same-ID workspace on another host counted as already selected, a
superseded folder click could still toast after a newer one, and an
SSH reveal used the chat's separators instead of the explorer's.

* fix(file-explorer): reveal a target spelled with other separators, and skip duplicate folder rows and toasts

The explorer built a reveal's ancestors in its root's spelling but
looked the target up as given, so a forward-slash path under a Windows
root selected nothing. A local folder link no longer repeats its open
as 'Open with default app', and a blocked folder workspace no longer
toasts twice.

---------

Co-authored-by: Pr1p <zwq19980411@gmail.com>
2026-11-11 06:13:32 +00:00
Kelvin Amoaba 681dab3ff9 fix(speech): keep the old speech model when an update can't be moved into place (#41037)
* fix(speech): keep the old model when the new one can't be moved in

Deleting the old folder before the rename lost both copies when the rename
failed, e.g. a Windows antivirus lock.

* fix(speech): keep an interrupted swap's backup and let delete wait for it

Retry renames through Windows file locks, and keep .previous when it is the
only old copy left.

* fix(speech): remove the migration source's model backup on delete

* test(speech): assert the kept model, not the backup folder name

* refactor(speech): remove each model folder's leftovers in one loop on delete

* fix(speech): check model folders with async stat during the swap
2026-11-11 06:09:57 +00:00
Hotragn PettuganiandKelvin Amoaba 05edb8fd89 fix(azure-devops): recognize vs-ssh.visualstudio.com SSH remotes (#40369)
* fix(azure-devops): recognize vs-ssh.visualstudio.com SSH remotes

Organizations on the older visualstudio.com URLs clone over SSH with
<org>@vs-ssh.visualstudio.com:v3/<org>/<project>/<repo>. The repository
parser only accepted ssh.dev.azure.com, so these remotes parsed to null
and the repo got no Azure DevOps pull request or status features.

Accept vs-ssh.visualstudio.com in both the SCP and ssh:// forms, and
resolve it to the same reference the org's https://<org>.visualstudio.com
remote already gets.

Fixes #ISSUE

* fix(azure-devops): validate vs-ssh org names and fix renderer PR links

A vs-ssh remote's org becomes the API hostname that receives the Azure
DevOps PAT, so reject anything but a plain org name. The renderer's
manual PR link parser also missed vs-ssh remotes and built a link on
vs-ssh.visualstudio.com; resolve it to the org's visualstudio.com repo.

---------

Co-authored-by: Kelvin Amoaba <97001695+AmoabaKelvin@users.noreply.github.com>
2026-11-11 06:00:53 +00:00
Jinwoo Hong 5e342d7e1a fix(orchestration): a worker report during an Orca restart is not rejected as a changed identity (#41014)
* fix(orchestration): a worker report during an Orca restart is not rejected as a changed identity

* refactor(orchestration): share the startup bounded wait; skip long-poll checks

* chore(orchestration): drop casts in the restart identity wait

* fix(orchestration): only long-poll checks skip the restart identity wait

* fix(orchestration): ordinary mail never waits for the restart identity restore
2026-11-11 00:59:18 -05:00
OrcaWinandOrcaWin d3ef8b4255 test: remove 216 low-value cases and 42 whole suites (#41030)
* test: remove 216 low-value cases and 42 whole suites

* test: retire unprotected snapshot adoption gate

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-11-10 21:57:58 -08:00
Neil ba3f9918f1 relay-edge: reconnect_gap metric (time from a host's last control close to its next activation) (#41034)
Diagnostics only. Separates sleeping hosts (long gap) from awake hosts whose
connection was dropped (short gap) after silence closes. Stores one small
per-host key, cleared on the next activation and by erase.
2026-11-10 21:57:40 -08:00
Kelvin Amoaba ada663ef62 fix(bitbucket): connect access tokens and API tokens without account access (#41029)
* fix(bitbucket): connect access tokens and API tokens without account access

Bitbucket answers GET /2.0/user with 403 for a credential it accepts but
that has no account access: every repository, project or workspace access
token, and API tokens without read:user:bitbucket. Orca treated that 403 as
a rejected credential, so those tokens could never connect. Only 401 now
means rejected; 403 connects without an account name.

* test(bitbucket): type the 403 fetch stub without a cast

* fix(bitbucket): accept a /user 403 only when Bitbucket sent it
2026-11-11 05:56:15 +00:00
Neil 80026db12a refactor(renderer): place remaining library features with their owners (#41024) 2026-11-10 21:48:26 -08:00
d30ea72469 fix(session-history): discover OMP profile session dirs (#40264)
* fix(session-history): discover OMP profile session dirs

OMP stores per-profile transcripts under <config home>/profiles/<name>/agent/sessions
(XDG_DATA_HOME/omp/profiles/<name>/sessions on POSIX with XDG set). The app process
usually lacks OMP_PROFILE/PI_PROFILE, so profile-run sessions were missing from
Agent Session History, orca search, and native chat.

Enumerate existing profile session dirs in ompProfileSessionsDirs() (same env +
safety rules as resolveOmpSessionsDir; PI_CONFIG_DIR-aware; suppressed under the
legacy OMP_CODING_AGENT_DIR override) and include them in ompSessionsRootDirs()
(discovery + path allowlist) and the omp branch of the native chat resolver.
An explicit ompSessionsDir override still pins one location.

main-sync-fs baseline for omp-session-root.ts 1 -> 3: one readdir of the small
fixed profiles dir plus one existsSync candidate check per profile (single call
site), the same small-dir probe style the file already uses - not a per-file
sweep. Flagged for reviewer sign-off per the ratchet's rule.

* fix(session-history): enumerate XDG-only OMP profile dirs

ompProfileSessionsDirs only listed profile names from the local config home, so a profile that exists solely under $XDG_DATA_HOME/omp/profiles (e.g. OMP run with a different PI_CONFIG_DIR) was never discovered. Enumerate both roots; per-profile candidate checks and output dedup are unchanged.

* docs(session-history): document profile dir helpers for coverage

* fix(session-history): use non-blocking profile enumeration in the native chat ID lookup

* refactor(session-history): share one OMP root list for discovery and chat lookup

Drop the duplicated async profile scan: native chat ID lookup now reuses
ompSessionsRootDirs, which already runs the small sync profile probe on the
main process for row deletion. Reuse normalizedProfile for directory names
and trim tests to the behavior that fails on revert.

* fix(session-history): prefer a profile's XDG sessions dir in OMP ID lookup

---------

Co-authored-by: ducts <ducts@fpt.com>
Co-authored-by: Kelvin Amoaba <97001695+AmoabaKelvin@users.noreply.github.com>
2026-11-11 05:31:23 +00:00
JinjingandClaude b7e0fa8e22 feat(automations): redesign the automation detail overview (#40507)
* feat(automations): redesign the automation detail overview

Group the overview into a run-health band (last run, next run, spend), Schedule and Execution property panels, and a Prompt panel with copy and a fading show-more. Run Now is now the primary action next to Edit; Pause and Delete move into an overflow menu with Delete separated as destructive.

* test(e2e): align prompt disclosure spec with the 8-line clamp

The redesign raised the collapsed prompt clamp from 4 to 8 lines and caps the
prompt at 80ch, so the resize prompt no longer overflowed at 820px. Lengthen it
so it wraps to ~11 lines at 820px and ~6 at 1400px, and assert line-clamp 8.

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-11-10 21:31:18 -08:00
OrcaWinandOrcaWin 5401c07619 test: remove 436 low-value cases and 79 whole suites (#41026)
* test: remove 436 low-value cases across 79 whole suites

* test: prune obsolete popup and stdin timing allowances

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-11-10 21:22:10 -08:00
Wooseong KimandKelvin Amoaba c26e259d0f fix(native-chat): show session skill argument hints in the picker (#40788)
* fix(native-chat): show session skill argument hints in the picker

Claude Code reports argument-hint on skill commands. The native chat skill row kept only the name and description, while built-in commands in the same picker still showed their hint.

* fix(native-chat): keep a skill's hint before Claude classifies its catalog

Before the first stream init, Claude lists skills as unclassified commands.
A scanned skill of the same name replaces that row and dropped its hint.
Fold the three hint tests into one shared-layer test and show a skill hint
in the picker fixture.

---------

Co-authored-by: Kelvin Amoaba <97001695+AmoabaKelvin@users.noreply.github.com>
2026-11-11 05:14:12 +00:00
+26 2a98a4365f style(menus): roomier rows, lighter icons, solid surface (#40000)
* style(menus): roomier 26px rows, lighter icons, solid surface

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(orchestration): record orchestration as the origin of nested workers on connected servers; lead the guide with new-child (#40091)

worker-start --on <server> --worktree new-child now tells the server that
orchestration picked the parent, so the server records the child's lineage as
orchestration, as a local child is, instead of cli. Older coordinators omit the
field and keep cli. The guide now shows new-child first and new-top-level as
the no-parent choice.

* fix(claude): name Orca as the client of its Claude chats (#40057)

* fix(claude): name Orca as the client of its Claude chats

Orca's Claude chats reported the SDK's default entrypoint (sdk-ts), or the
value of a Claude session Orca was launched from. Set CLAUDE_CODE_ENTRYPOINT
to orca for every structured Claude launch; a value in Orca's launch
environment still wins.

* fix(claude): keep a differently cased Windows entrypoint override

Windows env names are case-insensitive, so a launch value spelled
claude_code_entrypoint lost to the uppercase Orca default when Node
deduplicated the keys.

* fix(claude): let a launch value replace a differently cased inherited one on Windows

* fix(claude): store a Windows entrypoint override under the name the SDK reads

* fix(orchestration): refuse a paired-server worker start onto a busy terminal before any wait (#40102)

One terminal claim owner now covers local Dispatches and attachments a paired
coordinator starts here, so either kind refuses the other with terminal_busy
before readiness waits or briefs.

* Show usage reset dates in account switcher (#40210)

* feat(cli): add `orca vm env doctor` to check that an environment can run agents (#40120)

* feat(cli): add orca vm env doctor to check a workspace environment

Checks required CLIs (git plus what the repo manifests imply), agent CLIs
and their sign-in, Git fetch and a dry-run push, the orca.yaml setup script
(optionally re-running it with Orca's runner), and free disk. Pass/warn/fail
with --json; checks live in src/main/environment-doctor for runtime reuse.

* fix(env-doctor): ship the doctor modules in the packaged CLI and share the check formatter

* fix(env-doctor): run Git probes in the C locale so folder detection is locale-independent

* fix(orchestration): keep a paired server's worker list when one Dispatch is from an earlier pairing (#40170)

* fix(orchestration): keep a paired server's worker list when one Dispatch is from an earlier pairing

The server's fleet snapshot refused the whole batch when one requested
Dispatch was not this home's (for example, one started before the home
re-paired), so the home showed every worker on that server as
host_unavailable. Now only that Dispatch reads as unverifiable.

* test(orchestration): cover a live Dispatch held for another home in the fleet snapshot

* test(orchestration): assert the other home's fleet item without a cast

* fix(ci): reuse installed pinned Bun in test workflows (#40234)

* fix(vm): resume a paired-server VM whose host went away on its own (#40199)

* fix(vm): resume a paired-server VM whose host went away on its own

* fix(vm): count only continuous failures as a lost paired server

* fix(browser): restrict paired-host browser tunnel destinations (#40203)

* feat(mobile): cancel dictation in place and show which device is transcribing (#40185)

* feat(mobile): cancel dictation in place and show which device is transcribing

* fix(mobile): give each composer its own dictation cancel target

* fix(mobile): keep the dictating composer row on one line

* refactor(mobile): share the dictation mic face and measure the cancel target per platform

* fix: request native ABI process report only on Linux (#40233)

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>

* feat(vm-recipes): add "Set up with an agent" to Cloud VMs settings (#40131)

* feat(vm-recipes): add Set up with an agent to the Cloud VMs settings

Starts the default agent in the project's workspace with a prompt that loads
the per-workspace environment skill, loops on the recipe and environment
doctors, and ends with a commit and PR. The launcher also takes failure
context (redacted log tail) for callers such as a failed VM create.

* refactor: split recipe log redaction into a browser-safe module

* fix(vm-recipes): bound and redact failure text in the setup-agent prompt

* fix(worktrees): offer nested-worktree delete on remote runtime hosts (#40235)

* fix(worktrees): offer nested-worktree delete for remote runtime hosts

A remote host's 'contains another registered worktree' refusal showed only
the raw error. Add worktree.previewNestedRemoval and an approved-plan
worktree.rm path on the runtime, and route the toast's nested-delete dialog
to the workspace's own host.

* fix(worktrees): re-check checkouts between remote nested deletes; scale reply timeout

* chore: compose nested worktree ids; record improved ratchets

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>

* fix(speech): send cloud dictation through the proxy set in Settings (#40236)

* fix(speech): send cloud dictation through the proxy set in Settings

* fix(speech): keep OpenAI cookies out of the desktop session

* refactor(speech): send transcription and the key check through one request helper

* refactor: move browser contracts and publication to defining owners (#40209)

* refactor: move atomic publication to its filesystem owner

* refactor: group shared browser definitions by feature

* refactor: bind proxy process and session contracts to defining owners

* refactor: consolidate direct session contract type imports

* refactor: retain atomic publication test timing under moved owner

* fix(benchmark): resolve historical tunnel framing source before loading

* Plugins: add workspace.rename and project names in workspace.list (#40197)

* feat(plugins): add workspace.rename and project names in workspace.list

Plugins that name workspaces had to read Orca's private runtime token and call
worktree.set over the runtime socket, bypassing capability consent. workspace.rename
(workspace:write, worker only, host API 1.5) does the same update as a sidebar
rename. workspace.list rows gain the project name so plugins can prefix names
without reading the runtime's repo list.

* fix(plugins): harden workspace.rename against twin folders and hidden characters

Folder renames require a unique folder id, so twins on two hosts are not both renamed,
and fail when no folder has the id. Names refuse C1, bidi and zero-width characters,
which could make a label read as another workspace's name.

* fix(plugins): workspace.rename matches a sidebar rename for folders and reports kept names

A folder rename also cancels a pending first-agent-message rename, so the automatic
title cannot replace the plugin's name. A rename Orca does not apply (during a WSL
move) is a conflict instead of a silent success. workspace.list's project is optional,
so mocks written for host API 1.4 still validate. More invisible characters are refused.

* feat(dictation): dictate into the focused field of a page in the browser pane (#40191)

* feat(dictation): dictate into the focused field of a page in the browser pane

* test(dictation): cover dictation into a focused browser page

* fix(dictation): type page dictation through the frame that owns the field

* fix(dictation): space page rich-text dictation within its block and offer editors the beforeinput

* test(dictation): simplify the page insertion test mock

* fix(dictation): copy page dictation to the clipboard when an editor blocks the input

* feat(dictation): Enter or Send while dictating sends once the words land (#40226)

* feat(dictation): Enter or Send while dictating sends once the words land

* refactor(dictation): composer queued send as a hook

* fix(dictation): queue only Enter typed into the terminal; keep the pill until the send runs

* fix(dictation): a cancel during the terminal's Enter delay drops the Enter

* fix(dictation): keep the queued label until the terminal Enter is pressed

* fix(dictation): keep the queued send compilable

* feat(vm-recipes): build checklist and failure card for VM creates (#40152)

After a recipe provisions a VM, run the environment doctor on the VM's own
server (new environment.doctor RPC) before creating the workspace. The VM
create sheet shows Provision VM, Check environment and Create workspace; a
failed step gets Fix with agent, Retry, Edit recipe, and (for checks) Open
anyway, which keeps the VM. Older VM servers skip the checks; dismissing
aborts a running check and destroys the VM as before.

* Polish agent picker search and rows (#40242)

* wip

* Polish agent picker interactions

* test: reconcile VM mobile and native test removals with current main (#40154)

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>

* test: remove low-value shared and native suites (#40172)

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>

* Fix Simplified Chinese translation for floating button action (#40252)

* fix(vm-recipes): destroy a recipe VM when a phone deletes its workspace (#40205)

Deleting a recipe workspace from the phone removed only its checkout and
left the VM this desktop created for it running. Only the desktop window
ran the destroy.

The relaying desktop now tags the rows of workspaces whose VMs it created
with the environment's name, and says whether deleting will destroy it.
The phone's delete sheet names the environment, and its delete asks for
the destroy (`destroyHost: true`). The desktop then owns that removal:
- it runs as a workspace lifecycle delete
- the environment's Orca removes the checkout and must answer positively.
  An older Orca that answers on acceptance is polled until the workspace
  is gone.
- the VM is destroyed through the shared cleanup, behind any running wake
  or suspend, and only when no other workspace remains on it
- the result reports host.state truthfully: destroyed, kept, or
  left-running (no destroy step)

The environment's own error codes reach the phone unchanged, and a failed
removal shows its message on the phone instead of only a haptic.

Removals that don't ask for the destroy (older phones, the CLI, other
clients) keep today's behavior. No CLI API changes.

Based on #39235 by jutaz, a port of #28435.

Co-authored-by: Justas Brazauskas <brazauskasjustas@gmail.com>

* fix(dictation): stop phone and browser cloud dictation at the 10-minute limit with a notice (#40240)

* fix(dictation): stop a phone's cloud dictation at the 10-minute limit with a notice

* test(dictation): pin the cap flag on every phone chunk reply

* perf(dictation): skip decoding phone audio the host will drop past the cloud cap

* ci(release): mirror published downloads to Cloudflare R2 (#40254)

onorca.dev offers an opt-in 'mirror link' (https://downloads.onorca.dev)
next to the GitHub download for when GitHub release downloads 5xx.
Upload the onorca.dev download assets there right after each stable
desktop release and each Android release, instead of polling.

- config/scripts/release/publication/mirror-release-to-r2.sh: retrying,
  resumable download from GitHub, size + sha256 check, S3 multipart
  upload to R2 (no 300 MiB wrangler cap)
- release-mirror-r2.yml: reusable + manual backfill
- release-cut: run after publish-release for stable vX.Y.Z tags only
- mobile-android-release: run before bump-apk-download so the site's
  mirror link never points at a missing APK (success not required)

* Keep hosted reviews, conversations, and optional GitLab jobs accurate (#40080)

* Fix hosted review selection, conversations and optional job failures

Co-authored-by: Mahedi Hasan Emon <mahedi.emon62@gmail.com>

Co-authored-by: Victor GALISSON <victor.galisson@purecontrol.com>

* Retain complete review history within provider bounds

* Declare optional membership retention policy

* Narrow hydrated revision metadata without a cast

* Preserve completed check presentation through the shared classifier

* Read malformed revision metadata without retaining it

* Exercise legacy alternate discovery after local absence

* Preserve observable remote review routing coverage

* Keep optional GitLab job warnings consistent in details

* Reuse the optional job presentation color

---------

Co-authored-by: Victor GALISSON <victor.galisson@purecontrol.com>

* Continue every usage-limited chat at once, and stop after five automatic refails (#40055)

* fix(native-chat): continue every usage-limited chat at once and stop after five automatic refails

The automatic continue sent one chat per 10 s sweep, so chats stopped by the same limit resumed one by one. Its retry budget belonged to the cut, so a continuation that hit the limit again started a fresh budget and could repeat indefinitely.

Send every due chat in one pass, one at a time. Count consecutive automatic continues that ended in another usage limit from the journal: each continue's message id names the cut it answered, and the next cut's turn is joined to that submission through its key or provider item. After five, the chat shows that Orca won't continue it automatically.

* fix(native-chat): read the usage-limit continue chain from the tail, not the whole conversation

The chain walk built a map over every journal row and rescanned history for each link. Walk back from the end instead, so the work is the chain's own rows; add an N/10N contract. Replace the fan-out test's casts with typed fixtures.

* test(native-chat): count a Claude echo-opened continue in the usage-limit chain

* fix(native-chat): derive the usage-limit continue chain once per cut

A status rebuild for the same cut reuses the chain it already derived, since only history before the cut decides it; the turn's limit is read without copying its rows.

* fix(native-chat): skip slash-command turns when walking a usage-limit continue chain

A /compact run after a usage limit sits between the cut and its automatic continue; the latest-turn read skips it, so the chain walk must too. Count visited rows in the perf contracts with typed getters.

* test(native-chat): type-guard the recorded Claude capture events

* fix(native-chat): recompute the usage-limit chain when a late receipt attributes its opener

Reuse across rebuilds is keyed on the cut, its turn row's revision and the newest send, so a late attribution that completes a join is counted instead of keeping a stale zero.

* test(native-chat): guard usage-limit chain reuse at the status projection

Drives StructuredAgentSessionJournalProjections.read through real journal invalidations at 100 and 1000 turn rows: one chain derivation for an unchanged cut, again for a new cut or epoch, and a recount when a late receipt attributes the continue's opener.

* fix(native-chat): reuse a usage-limit chain only while everything it read is unchanged

The chain walk now reports the turn rows it looked up and the oldest submission it examined; the status projection keys reuse on their revisions and join evidence, so a late receipt for any link, not just the newest send, recounts the chain.

* fix(native-chat): derive the usage-limit chain fresh on each status rebuild

Reuse across rebuilds kept missing inputs the walk reads (late receipts, revised failure rows) and its key re-serialized settled history. The status projection's own cache still skips unchanged reads; each rebuild now walks only the chain's tail. Contract tests at the projection count total reads: independent of earlier history, linear in the chain's rows, one scan of the sends when no opener matches.

* fix(native-chat): find an echo-opened turn's opener without reading the turn before it

A Claude echo-opened turn names the provider's key while its opener row keeps the submission's, so the slash-command check scanned the whole previous turn and missed echo-opened commands. It now stops at the nearest root user row.

* fix(dictation): save Auto mode so older builds keep a working shortcut, and show it on the phone (#40231)

* fix(dictation): store Auto as Toggle plus hold-to-record so older builds keep a working shortcut

* test(mobile): type the Auto mode settings fake instead of casting

* fix(dictation): migrate a stored Hold without hold-to-record

* fix(dictation): keep phone mode segments typed and clear hold-to-record when a phone picks Hold

* refactor(dictation): reuse the shared mode settings in the migration and phone writes

* fix(agent-picker): list agents named by the search before other agents' model setups (#39806)

* fix(agent-picker): list agents named by the search before other agents' model setups

Typing "codex" in the agent picker showed OpenCode/Cursor model setups
(e.g. "OpenCode · GPT-5 Codex") above the Codex agent itself, and Enter
picked the first setup. Direct agent matches now render first and are the
Enter target.

* test(agent-picker): assert search highlight and Enter target for named agent

* test(agent-picker): import fireEvent after rebase

* test: remove low-value WSL and renderer suites (#40237)

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>

* fix(native-chat): pad the sender name chip in orchestration worker cards (#40258)

The compact sender name had no horizontal padding, so its hover fill hugged
the text. Add an inlineChip button size (inline height, px-1.5).

* test: remove low-value renderer and budget suites (#40222)

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>

* docs: put Chat or Terminal first in README features (#40262)

* fix(dictation): a paired phone or browser waits as long as the computer for long cloud dictations (#40238)

* fix(dictation): wait as long as the host's cloud deadline when a paired phone or browser stops

* fix(dictation): compute the host's cloud deadline from its exact sample count

* fix(orchestration): explain the --terminal and --agent refusal; name chat addresses in terminal commands (#40092)

* docs(dictation): say why Hold can't see a key release inside a browser page (#40269)

* test: remove low-value mobile renderer and rate-limit suites (#40188)

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>

* fix(terminal): end a dead preserved terminal host's PTYs as host loss (#40095)

* fix(terminal): end a dead preserved terminal host's PTYs as host loss

After an update Orca keeps the previous version's terminal host so agents
survive. That host has no respawn, so when it later died nothing ended its
PTYs: panes stayed connected on a frozen frame and input went nowhere.

Once every reconnect attempt finds its endpoint refused or missing, each of
its PTYs now ends as host loss with history kept, and the pane's restore is
no longer refused because the dead host cannot answer.

Fixes #38432

* fix(terminal): require process-exit proof on POSIX and keep checking until proven

Review follow-ups: an unproven attempt restarts the count instead of ending
the check; on POSIX the old host's pid must also be gone; a proven-gone host
answers 'not live' for every id; one throwing exit listener cannot strand
the remaining sessions.

* feat(i18n): translate the dictation pill, error pill and queued-send copy (#40266)

* fix(dictation): don't throw when the paired web client has no speech catalog

* feat(i18n): translate the dictation pill, error pill and queued-send copy

* fix(i18n): say Chinese queued sends happen automatically

* test: remove low-value provider and renderer assertions (#40279)

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>

* test: remove low-value copy, inventory, and runner assertions (#40282)

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>

* feat(release): publish a server-only Linux tarball for orca serve (#40280)

* feat(release): publish a server-only Linux tarball for orca serve

Each release now also uploads orca-server-linux-x64.tar.gz and
orca-server-linux-arm64.tar.gz (orcad slot from the qualified template, the
pinned Node, and an orca-ide launcher) plus orca-server-sha256sums.txt, so a
Linux host can run orca serve without the desktop package.

The tarballs are packed and smoke-tested (serve --recipe-json, then pair a
second profile) on both architectures, including bare ubuntu:24.04 and
debian:12-slim containers. Upload runs only after the release is published
and nothing depends on it, so it cannot hold the desktop release.

* fix(release): keep the server tarball's launcher env out of its terminals; skip refs that predate it

- The launcher runs the CLI with ELECTRON_RUN_AS_NODE=1, as the packaged app does, and the CLI strips it
  before starting orcad; ORCA_APP_EXECUTABLE and ORCA_SERVE_RUNTIME are cleared.
- Ship Node's LICENSE beside the runtime.
- Skip packing on release refs that predate the scripts, and upload only when both tarballs passed.
- Run the PR smoke when the CLI's serve-runtime selection changes; document service install.

* refactor: group renderer workspace ownership and navigation (#40230)

* refactor: group renderer workspace ownership and navigation

* refactor: use the defining WSL rewrite store identifier

* refactor: route new upstream consumers to workspace owners

* refactor: consolidate connection owner test imports

* refactor: preserve upstream review-owner test retirement

* test: remove low-value wrappers, inventories, and timing checks (#40284)

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>

* test(vm): pin file saves in a recipe-created workspace, across server restart (#11762) (#40275)

* test(vm): pin file saves in a recipe-created Orca server workspace (#11762)

* test(vm): wait for the save round trip, let the app reconnect itself, edit a plain-text file

* test(vm): say why the recipe save spec needs a longer timeout

* fix(tabs): preserve lastFocusedAt on native chat tabs across host snapshots (#40288)

Host session-tab snapshots rebuild native chat tabs without the
client-owned focus visit, so Cmd+J recents ranked chats last and showed
their creation time.

Fixes #39841

* test: remove low-value renderer, chain, and native checks (#40287)

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>

* test: remove low-value work budgets and relocation selftest (#40294)

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>

* fix(ports): keep sticky External Ports header opaque on hover (#40073)

The sticky External Ports header used hover:bg-accent/50, a translucent
background that let scrolled port rows show through on hover. Blend the
accent into the popover color so the hover state stays opaque.

Fixes #40056

* fix(tour): allow chat split for agent sessions without a TUI agent tag (#39976)

The native chat split action required the source agent-session tab to carry
a TUI agent tag, so sessions without one (e.g. untagged or non-TUI agents)
could not be split and the tour's split step stalled. Any agent-session tab
in a known group is now a valid split source.

Co-authored-by: Neil <neil@stably.ai>

* fix(native-chat): move the cursor to the composer after Add to chat (#39845)

The composer refuses focus while an overlay is open, and the selection
popover was still open when the quote was appended. Close it first.

* test(pty): await readiness signals instead of polling with waitFor (#40082)

The plain-shell readiness test polled with vi.waitFor (1s default budget)
for reconcileConfig and the spawn env. Under CPU load the spawn takes over
a second and the test failed with 'expected undefined to be defined'.

Await an explicit reconcile-started signal and the spawn itself, and move
cleanup to onTestFinished so the gate is released and spies/env restored
even when a regressed spawn blocks until the test timeout.

Fixes #40078

Co-authored-by: Jerry Wu <haoyuwu@andrew.cmu.edu>

* fix(terminal): don't restart every dead saved tab when a worktree opens; log how the daemon exited (#40289)

* fix(terminal): don't restart every dead saved tab when a worktree opens; log how the daemon exited

Opening a worktree mounted every saved terminal tab that had no PTY, and each
hidden mount started a fresh shell or agent. With many finished agent tabs this
started dozens of agents at once and could exhaust a host's process limit.

Saved tabs with no PTY anywhere and nothing queued to start now wait until
they are shown; the existing capped idle warm-up still admits up to four.
The main process now records the daemon's exit code or signal and names it in
the respawn log line.

Fixes #39166

* fix(daemon): forget a predecessor's exit when a replacement daemon launches

* fix(diff): persist inline / side-by-side diff view via the Default Diff View setting (#40256)

* fix(diff): persist inline / side-by-side diff view via the Default Diff View setting

The file diff and combined diff toolbars toggled inline vs side-by-side only
in local component state (plus a session-only module preference for the
combined diff), so switching worktrees or agent threads reopened the diff in
the saved inline default.

The toggle now writes the existing diffDefaultView setting, and both viewers
derive sideBySide directly from it. The per-view cache and module memory no
longer carry a separate sideBySide value.

Fixes #40241

Co-authored-by: Wooseong Kim <2222333+innocarpe@users.noreply.github.com>

* test(diff): drop the removed sideBySide field from the cached combined-diff view

---------

Co-authored-by: Neil <neil@stably.ai>

* feat(dashboard): show last messages in agent dialog when no live terminal (#40147)

When a dashboard card has no live PTY (e.g. chat-session tabs), the agent
dialog previously showed only the "preview unavailable" notice. It now also
renders the card's stored last user/agent messages, unclamped and scrollable,
below the notice. The rendering is extracted into AgentCardLastMessages and
shared with the kanban card, which keeps its line clamps.

Fixes #40144

Co-authored-by: Neil <neil@stably.ai>

* fix(claude): verify model switches when terminal echoes omit vendor prefix (#40064)

Claude echoes "Set model to Opus 5.5" without the "Claude" vendor prefix,
and never echoes the picker-only " alias" suffix or a raw "claude-" id
prefix. The confirmation observer required every picker-label token, so a
successful switch resolved to unknown and showed "Could not verify the
model change". Normalize the expected label before matching; context
tokens such as 1M are still required.

Fixes #40058

Co-authored-by: Neil <neil@stably.ai>

* fix(markdown): lower link underlines below descenders (#40218)

Link underlines in Native Chat, the markdown preview and the rich markdown
editor sat 2px below the baseline, so descenders (g, y, p, j) crossed them and
skip-ink broke the line into dashes. Use a 0.25em offset that scales with font
size and zoom, and override the shared renderer's underline-offset-2 in Native
Chat.

Fixes #40217

Co-authored-by: Neil <neil@stably.ai>

* test: remove low-value copy suites and manual status benchmark (#40305)

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>

* fix(native-chat): group deferred context rows separately in the usage popover (#39917)

Claude reports deferred categories (MCP tools, system tools loaded on
demand) that sit outside the used total, but summarizeContextUsage
dropped the flag, so the popover listed them as window percentages next
to real usage and the rows summed to well over 100%.

Keep the deferred flag on each summary row and render deferred rows in
their own group under a divider, with token counts instead of a share of
the window and a note that they are not counted in the used total.

Fixes #39878

Co-authored-by: Neil <neil@stably.ai>

* refactor: group editor browser document and simulator owners (#40273)

* refactor: group editor browser document and simulator owners

* refactor: update active simulator reliability command links

* refactor: preserve upstream editor and emulator test retirements

* fix(codex): close TOML multiline strings on runs of four or five quotes (#40089)

The Codex config line scanner only treated exactly three quotes as the
multiline closer, so a value like '''printf 'ok'''' left the scanner
inside the string and later tables (e.g. [projects."..."]) were missed
when writing project trust. Match toml_parser: a run of 3-5 quotes
closes the string, with up to two quotes belonging to the content.

Co-authored-by: Neil <neil@stably.ai>

* fix(linear): include project in Linear issue list and search results (#40194)

Request the project on the shared issue node fields and map it in
mapRawIssueForWorkspace, so list/search rows carry the project the same
way issue detail already did. Detail now reuses the shared mapping.

Co-authored-by: Neil <neil@stably.ai>

* fix(build): bundle browser-extension-preload as a standalone sandboxed script (#39884)

The browser-extension preload was a main-build Rollup input, so it could be
split into shared CJS chunks that a sandboxed preload cannot require. Build it
with the other guest preloads as a self-contained bundle, and share the
one-byte unminified output options between the preload build and sandboxed
bundles so the extension preload stays one-byte in V8.

Co-authored-by: Neil <neil@stably.ai>

* test: remove obsolete manual tools and duplicate launch coverage (#40317)

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>

* fix(terminal): ignore the inverse caret glyph in an empty Claude composer (#40225)

Claude paints the caret over the first glyph of a dim composer suggestion
as an undimmed inverse cell. The typed-text read kept that glyph, so an
empty composer reported a one-character draft and the stock Try "..."
placeholder was never recognized. Treat a lone inverse caret glyph ahead
of a longer dim suggestion as untyped, while keeping a real typed last
glyph that sits before a dim argument hint (e.g. /commit [message]).

Fixes #40195

Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>

* test(e2e): give the provisioned-root VM a reachable git remote (#40318)

* test(e2e): give the provisioned-root VM a reachable git remote

VM creates now run the environment checks before creating the workspace
(#40152). The fixture VM's origin pointed at a GitHub URL it cannot push
to, so the git push check failed and the create paused on the build
sheet; the spec then read the VM's primary checkout before it was
adopted. Point the VM checkout at a local bare repo so the checks pass.

* test(e2e): wait for the provisioned root's adoption, not its listing

The VM's primary checkout appears under its branch name once its project
is set up, before the environment checks finish and adoption writes
ephemeralVmCheckoutMode. Poll for the adopted row instead.

* refactor(environment-providers): add EnvironmentProviderDriver, contract test kit and ShellRecipeDriver (#40315)

One lifecycle seam (create/resume/suspend/destroy/status + capability flags and a
catalogued error type) that every environment provider implements, a shared
vitest contract any driver runs from its own test, and ShellRecipeDriver, which
wraps today's orca.yaml / plugin VM recipe runner and passes the contract.
No production callers yet.

* refactor: group agent launch, status, and messaging owners (#40313)

* refactor: group agent launch, status, and messaging owners

* refactor: consolidate direct owner imports

* refactor: import pane ownership directly in status listeners

* Update README downloads badge (#40336)

Co-authored-by: buf0-bot[bot] <252831055+buf0-bot[bot]@users.noreply.github.com>

* refactor(environment-providers): run shell recipes through ShellRecipeDriver (#40332)

The runtime service and failed-start cleanup now reach orca.yaml environment
recipes and plugin VM recipes through ShellRecipeDriver's recipe-shaped steps.
Same arguments, same runner, same results: records, failure copy and
diagnostics are unchanged.

* test: remove low-value hook and mirror tests and search benchmarks (#40335)

* test: remove private hook checks and manual search benchmarks

* test: remove masked mirror hydration case and exclusive scaffolding

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>

* fix(native-chat): show approved Codex automatic reviews as neutral notices (#40161)

Codex reports both approved and denied automatic approval reviews on the
guardianWarning channel. Orca rendered every one as a yellow "safety review
warning", so routine approvals looked like problems. Approved reviews now
render as a neutral "approved this action" notice on desktop and mobile;
denials and timeouts keep the warning tone.

Co-authored-by: Sean876-alt <224890246+Sean876-alt@users.noreply.github.com>
Co-authored-by: Neil <neil@stably.ai>

* test: remove low-value composer activation provider and layout coverage (#40342)

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>

* test: remove low-value draft and editor save coverage (#40350)

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>

* test: remove low-value bridge speech and lineage checks (#40356)

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>

* fix(vm-recipes): keep a VM's checkout out of the sidebar until its create finishes (#40355)

A recipe VM create registers the VM's project before its environment
checks run, so the VM's primary checkout showed as a normal workspace
next to the pending row while the checks ran or were paused on a
failure. Hide that checkout in both sidebar list models until the
create adopts it (or makes its worktree). A cancelled or failed create
keeps it hidden while its VM is cleaned up; a VM that could not be
cleaned up is shown again so it can be removed.

* test: remove low-value messaging simulator and editor suites (#40363)

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>

* feat(runtime): tell phones when relay hosts change (#40122)

* feat(runtime): tell phones when relay hosts change

* fix(runtime): keep the relay host backstop live and watch every list input

* fix(mobile): hold the relay host feed only for desktops that announce changes

* perf(runtime): watch relay host inputs only while phones read the list

* perf(github): use the desktop's PR fallback rule on the phone and forward refreshes (#40128)

* perf(github): use the desktop's PR fallback rule on the phone and forward refreshes

* fix(github): keep a merged fallback PR while the phone has no head

* feat(mobile): keep Tasks and new-workspace drafts across kills (#40127)

* feat(mobile): keep Tasks and new-workspace drafts across kills

Task comments (per item and project row), the New issue title and body,
and the New workspace note now live in device-store draft rows. They come
back after the form closes or the app is killed, and only into an empty
field. A comment's setter stays bound to its item, so a send that settles
after the user moved on clears the right row.

* fix(mobile): rebuild comment sends when the draft's item changes

* fix(mobile): key the workspace note by host and keep cleared fields cleared

* fix(mobile): clear only the note a finished create sent

* fix(mobile): count clearing an empty draft field as the user's choice

* perf(mobile): cap field drafts, read sheet drafts on open, retry a failed read once

* fix(mobile): retry a failed draft read after a pause and keep New issue text typed during a create

* fix(mobile): save draft text again once a failed store read can be retried

* fix(mobile): remove a stored draft when an unread field is cleared

* fix(mobile): floor event-driven reads after failures and catch up reads that span a stream reopen (#40142)

* fix(mobile): floor event-driven reads after failures and catch up reads that span a stream reopen

* test(mobile): pin the event router's read floor after a refused read in a recording

* perf(mobile): stop rebooting the Home spare page on every hop and after iOS memory warnings (#40270)

* perf(mobile): back off spare page refills after an unclaimed drop or memory warning

* fix(mobile): skip a spare whose store read outlived an unclaimed drop

* fix(mobile): arm the spare refill timer on whole milliseconds

* feat(vm-recipes): show VM lifecycle states in the sidebar, with Suspend/Resume VM (#40046)

* feat(vm-recipes): show VM lifecycle states in the sidebar, with Suspend/Resume VM

* fix(vm-recipes): Suspend VM suspends an idle parent with active children; dedupe repeat suspends

- With active children, Suspend VM sleeps this workspace too, so its VM
  suspends once their sessions stop.
- A repeat Suspend VM while one is pending is dropped in the coordinator
  instead of trusting a possibly stale suspended record.
- After a direct suspend, re-probe the host so its header reads Suspended.

* perf(mobile): keep three visited terminals mounted (#40111)

* perf(mobile): keep three visited terminals mounted and release hidden WebGL

* fix(mobile): scope the mounted-terminal LRU to the terminal frame

* fix(mobile): keep chat's lease-only stream when a terminal pane unmounts

* fix(mobile): report a hidden terminal's cell box only once it is shown

* perf(mobile): keep hidden terminals' WebGL contexts instead of rebuilding them on every switch

* feat(mobile): open chats on their last messages after a relaunch (#40166)

* feat(mobile): open chats on their last messages after a relaunch

The phone's store keeps each chat's newest rows (up to 512 KiB, 500 rows)
and its resume point, written when a turn settles, on hidden and when the
chat closes. A relaunched session opens on its stored chat tab, paints the
saved rows, and resumes from the stored cursor; the live read restates what
the agent is doing. Offline, the composer line says when the copy is from.

* test(mobile): keep the relaunch test typechecking

* i18n(mobile): translate the offline as-of line

* perf(mobile): encode chat copies in slices off the settle frame

* feat(mobile): keep commit, PR and review drafts across app kills (#40114)

* feat(mobile): keep commit, PR and review drafts across kills

* refactor(mobile): build commit, PR and review drafts on the shared draft fields, and clear only the text that was sent

* fix(mobile): keep a toggle made during Write with AI, the edit note while its sheet closes, and a newer PR edit after an earlier save

* test(mobile): wait for the saved review note row instead of sleeping

* perf(mobile): keep a hash of the PR text an edit was typed against, not a second copy

* test: remove low-value VM creation assertions (#40367)

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>

* perf(mobile): open Home with last-known workspaces and counts (#40129)

* perf(mobile): open Home with last-known workspaces and counts

* fix(mobile): keep disconnected desktops on Home and judge agents by the client's connect time

* fix(mobile): store the seeded Home counts before removing the old snapshot

* fix(mobile): floor router reads after failures, catch up reads that span a stream reopen, seed the wide host list from Home

* fix(mobile): keep the old Home snapshot until the store can take it, and never re-date stored totals

* perf(mobile): key Home's counts in the host list module so the page boot never loads Home's code

* test(mobile): type the Home catalog adapter's modules with type imports

* perf(mobile): read Home's stored rows in one batch beside the host list, and keep mark-read a delta read

* perf(mobile): paint Home's desktop rows without waiting for the stored counts

* perf(mobile): restore Home's stored rows after the desktop list paints, from its host ids

* perf(mobile): read Home's counts on the connected edge itself, not a render later

* test: remove browser startup and CLI prerequisite mock suites (#40371)

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>

* test: remove low-value VM, mobile, work-item and terminal suites (#40376)

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>

* test: remove sidebar VM-wake and template hover suites (#40379)

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>

* fix(vm-recipes): hide an in-progress VM checkout in every window, the phone and the jump palette (#40373)

* fix(vm-recipes): hide an in-progress VM checkout in every window, the phone and the jump palette

A VM create's checkout was hidden only in the sidebar of the window that
started the create. Windows now publish their pending VM checkouts to the
main process, which shares every window's set with all windows and
filters the paired phone's host workspace list and workspace context.
The jump palette and the sidebar read the shared set, and a grouped VM
project no longer draws an empty-project placeholder while its create
is pending. A window's set is dropped when it closes, crashes or reloads.

* test(project-groups): reset leaked runtime environment revisions between files

* test: remove thin agent-hook and mobile migration suites (#40382)

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>

* fix(vm-recipes): tell paired phones to re-read workspaces when a VM create's pending checkout changes (#40384)

The phone filters in-progress VM checkouts only when it reads its host
workspace list, so a read made before a window's hide reached the main
process kept showing the checkout until the next unrelated change. Emit
worktreesChanged for each repo whose pending checkout appears or goes
away; phones already re-read their host workspace list on that event.

* test: remove agent-hook projections and URL inventories (#40388)

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>

* test: remove low-value agent-hook and row-projection coverage (#40395)

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>

* fix(mobile): stop Review's branch compare re-reading back to back during git bursts (#40278)

* fix(mobile): stop Review's branch compare re-reading back to back during git bursts

fetchHostQuery re-read a compare as soon as it landed marked by a git event, so a burst of gitStatusChanged events with reads slower than the event spacing kept the desktop running compares back to back, and kept going after Review closed. A re-read now goes at once only after a quiet spell (a write's own event), otherwise floorMs after the last read landed, and stops once Review's build is no longer current. A superseded build no longer evicts the entry a newer build is reading.

* fix(mobile): show each compare that lands during a git burst while Review reads it again

* fix(mobile): keep note edits when a later compare lands on a shown Review build

* test: remove low-value browser, store and adapter suites (#40399)

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>

* test: prune low-value worktree, SSH and activation coverage (#40406)

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>

* test: prune low-value terminal and workspace coverage (#40413)

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>

* feat(speech): run GGUF models on transcribe-cpp with a CPU fallback (#39326)

* feat(speech): run GGUF models on transcribe-cpp, falling back to the CPU

The speech host child can now load a second engine. A model whose files
are GGUF runs on transcribe-cpp (Metal on Apple silicon, Vulkan on
Windows and Linux, CPU otherwise); everything else stays on sherpa-onnx,
which remains the only engine on Intel Macs.

A GPU that fails to load, errors, stalls for 25 s or crashes the process
moves the dictation to a CPU-only process (TRANSCRIBE_BACKENDS=cpu) that
replays the audio the GPU had not decoded yet, so no words are lost or
repeated. A GPU that crashed or would not load is remembered per
transcribe-cpp version and OS release, and voice.speechGpu = 'off' keeps
it on the CPU.

Packaging copies transcribe-cpp, koffi and the target's native package
into resources/node_modules outside asar, fails when a target's package
is missing, and ships the MSVC runtime beside transcribe.dll on Windows.

No catalog model is GGUF yet, so nothing runs on transcribe-cpp until
the catalog change that follows.

* fix(speech): keep the speech queue running after a failed task, and drop audio frames after a discard

* fix(speech): keep the engine's file reads off the main thread's hot paths

The engine lookup and the GPU failure marker are read once per process
instead of on every dictation start and mobile catalog request, the marker
is written asynchronously, and the child reads the hotwords file with
fs/promises. The baseline moves the existing engine presence checks from
stt-worker-paths to the shared package table and adds the once-per-process
marker reads.

* test: prune low-value browser WSL and telemetry coverage (#40418)

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>

* feat(mobile): keep a failed dictation's recording and offer Retry (#40276)

* feat(speech): dictate through any OpenAI-compatible transcription service (#40160)

Add an OpenAI-compatible model whose base URL, model and optional key
are set up in a Voice settings dialog and sealed together in ~/.orca,
so a key is never sent to a host it wasn't saved for. It reaches
OpenAI, Groq, Mistral and local servers, and replaces the unused
userModels setting.

* test: remove low-value browser, hook and SSH cases (#40423)

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>

* feat(dictation): show committed and in-flight words in the dictation pill (#40428)

The pill showed only the current partial and went back to Listening after
each final, so pause-committed phrases from transcribe-cpp never appeared.
The host's transcript fold moves to src/shared and the desktop pill uses it
too: committed text in the foreground, tentative text muted.

* test: remove redundant browser matrices and SSH inventories (#40429)

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>

* feat(settings): add a Use GPU switch for on-device dictation (#39466)

* feat(settings): add a Use GPU switch for on-device dictation

On by default (Auto: the GPU when it works, the CPU when it fails).
Turning it off keeps the transcribe-cpp speech process on the CPU, for
machines whose GPU driver freezes or restarts them. The row shows when
the selected model runs on transcribe-cpp.

* fix(speech): reload the warm speech model when Use GPU changes

A loaded recognizer kept the GPU choice it started with, so turning Use GPU
off left the next dictations on the GPU until the model unloaded.

* fix(speech): reload the speech model after a dictation when Use GPU changed during it

A recognizer still in use when Use GPU changed stayed loaded afterwards with
the old choice. It now unloads as soon as its last dictation ends, and the
warm-up loads it again with the new choice.

* test: remove low-value terminal, browser-session and SSH cases (#40433)

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>

* perf(mobile): show accounts from the last snapshot (#40425)

* perf(mobile): show accounts from the last snapshot

* fix(mobile): never count a stored accounts snapshot as live

* test(mobile): pin when accounts usage counts as live

* fix(mobile): date accounts reads by when they were sent and block switches until live

* fix(mobile): show accounts usage only from this screen's own read, drop replies from before a reconnect

* feat(mobile): save the chat when the app leaves the foreground (#40417)

* feat(mobile): save the chat when the app leaves the foreground

A swipe-kill from the app switcher can end the app before the page is
hidden: iOS fires visibilitychange only at background, and Android Recents
leaves the activity resumed. The shell now sends an appInactive frame on
iOS inactive and Android window blur, to pages that declare they read it,
and the page saves any chat rows the store doesn't hold yet.

* fix(mobile): keep the leave callback out of render

* fix(mobile): notice a changed middle row before skipping a chat save

* test: remove low-value locale, automation and settings cases (#40437)

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>

* feat(mobile): keep dictating after leaving the session, with a floating pill (#40421)

* feat(mobile): keep dictating after leaving the session, with a floating pill

* feat(mobile): translate the dictation pill and its toasts

* fix(mobile): type away words without claiming the terminal, and keep the pill above screens

* fix(mobile): float the pill above bottom bars, reopen the owner's own screen, and tell a refused start wherever you are

* fix(mobile): float the dictation pill above the source control bar

* fix(mobile): keep pill state out of the host context and reopen the starting screen first

* refactor(mobile): subscribe the pill only while it shows and trim the session dictation hook

* fix(mobile): key bar clearances by symbol

* feat(speech): move the speech models to transcribe-cpp and offer an update for old downloads (#39468)

* feat(speech): move the speech catalog to transcribe-cpp models

Parakeet v2/v3, Whisper tiny and SenseVoice now download their GGUF
builds, which run on transcribe-cpp everywhere, Intel Macs included (on
the CPU). A model still holding its old sherpa-onnx download shows
"Update to the faster version (<size>)"; updating replaces the old
files, and dictating before then says the model needs that update.

The five models with no GGUF build are retired and their users moved
over: the Japanese Parakeet, the Chinese-English Zipformer and the
Paraformer to SenseVoice, and the Korean and Chinese streaming
Zipformers to the new Nemotron 3.5 (28 languages). Nemotron's locale
codes (ko-KR) are matched from the ISO language setting.

* feat(speech): offer the speech model update once after the engine change

A card in the notification corner tells people whose selected on-device
model needs the new download, or whose retired model was replaced, and
starts the same download as Settings > Voice. After a replacement it
offers to delete the retired model's files.

* feat(speech): move retired Korean and Chinese models to SenseVoice and offer Whisper Large v3 Turbo

On transcribe-cpp 0.4.1 with Metal, SenseVoice beat Nemotron 3.5 on Korean
and Chinese FLEURS clips, so the retired Korean and Chinese streaming
Zipformers now move to SenseVoice. Whisper Large v3 Turbo (Q8_0, 886 MB)
joins the catalog as the most accurate choice for Japanese, Korean and
Chinese. Nemotron 3.5 stays, described by the languages it does best.

* Update README downloads badge (#40440)

Co-authored-by: buf0-bot[bot] <252831055+buf0-bot[bot]@users.noreply.github.com>

* perf(github): answer phone PR lookups from the desktop's last result (#40430)

* perf(github): answer phone PR lookups from the desktop's last result

* fix(github): order last PR outcomes by lookup start sequence

* fix(github): keep reused phone PR outcomes out of the refresh observers

* fix(github): reuse a fallback-found PR only for the same fallback number

* test: remove low-value CLI, settings and shortcut suites (#40441)

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>

* refactor(speech): remove sherpa-onnx (#39473)

transcribe-cpp is now the only on-device engine on every platform, so
the sherpa-onnx packages, their packaging (extraResources, the darwin
libonnxruntime prune, the native-variant assert and the Linux libstdc++
exemption), the sherpa recognizer in the speech process and the
per-model engine choice all go. Hotwords are written as plain terms.

* test: remove low-value CLI and renderer inventories (#40445)

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>

* feat(runtime): tell phones when the desktop refreshes a PR (#40438)

* feat(runtime): tell phones when the desktop refreshes a PR

* fix(runtime): name every checkout a coalesced PR read stood for

* fix(runtime): don't count a read that couldn't load checks as a checks change

* feat(speech): spell dictated code terms with a personal and per-repo dictionary (#40450)

* feat(speech): spell dictionary terms in dictation, with repo terms and Whisper prompt

* test(speech): pin host repo terms for phone dictation

* fix(speech): keep symbol terms whole, accept null setup params, resolve SSH repos

* refactor(speech): key transcript words once and type phone terms at the reply schema

* refactor(speech): merge speech-types imports in the speech IPC

* test: remove low-value suites and redundant examples (#40457)

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>

* fix(speech): keep words spoken while the model loads (#40454)

Stopping a CPU dictation closed the renderer's audio port while frames still
waited behind a decode in the speech worker; the close ended the stream at once
and the queued frames were dropped. A close after 'end' now leaves them to
decode. A model slower than 30 s to load also lost the start of the dictation,
since the renderer kept only 30 s of startup audio; it now keeps as much as the
host waits for the load.

* fix(dictation): match the pill's Chinese and Japanese phrases to the inserted text, and space Korean ones (#40455)

* fix(dictation): join Chinese and Japanese phrases without a space, and space Korean ones

The shared transcript fold joined every phrase with a space, so the pill and
phone replies showed Chinese and Japanese text with spaces the inserted text
doesn't have. It now joins with the same rule as insertion. That rule treated
Hangul like Chinese and Japanese, so Korean phrases were inserted with no space
between sentences; Korean spaces words, so Hangul now spaces like Latin text.

* fix(dictation): don't space after Chinese and Japanese full-width punctuation

* fix(dictation): join retried phrases with the same spacing rule

* fix(dictation): escape the spacing rule's non-ASCII marks for the orcad bundle

The shared fold now pulls the spacing rule into the orcad server bundle, which
must stay ASCII. A rich-draft test pinned Hangul joining without a space; it
now expects the Korean spacing.

* Improve project group ordering across hosts (#39816)

* Improve project group ordering across hosts

* fix(project-groups): restore each group order after a failed drop

A deleted group made the aggregate rollback stop early, leaving other groups at
the ranks from the failed drop. Restore each group independently and log the
swallowed single-update failures again.

* refactor(main): give project group RPC methods their own module

Keeps repo.ts under the 300-line cap as the project group surface grows.

* test: remove low-value browser, CLI and budget checks (#40459)

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>

* fix(i18n): preserve web runtime error details (#40267)

* fix(web): preserve runtime failure details

* fix(i18n): update chat behavior settings hint

* feat(speech): commit dictation text at each pause with Silero VAD (#39378)

Offline models write out the speech before each pause while recording goes on.
Short noises and slow window-padded decodes are held off the pause cuts, a VAD
error falls back to bounded chunks, and slow phrase decodes are judged per
dictation against the time spoken. The VAD download carries legacy files and
fetches after any GGUF model.

* test: remove low-value CLI, browser and E2E checks (#40466)

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>

* Show the setup-script choice instead of greying out Create (#40341)

* Show the setup-script choice instead of greying out Create

With a project's setup script set to "Ask every time", Create stayed
disabled until the user picked Run or Skip, but that choice only renders
inside the collapsed Advanced section, so nothing explained the greyed-out
button. Keep the choice out of the disabled gate (as the gate's own comment
already intends) and have Create open Advanced to show it.

* Annotate the partial submit fixture cast

* Bring the setup choice into view when Create needs it

Create now scrolls the Run/Skip choice into the dialog's view and focuses
it on every attempt, including when Advanced is already open, and the
late-discovered setup path uses the same reveal. A submit that only jumps
to an existing pending creation no longer asks for a setup choice.

* Reveal the setup choice once per Create attempt

The choice clears the reveal request after scrolling and focusing, so a
later remount (for example after switching projects) cannot pull focus
without a new Create.

* Drop a pending setup reveal once the user has chosen

Choosing Skip in the moment before the reveal fired could still move focus
onto Run, so a later Enter switched the choice.

* Save a pasted workspace directory as the path it names (#40347)

* Save a pasted workspace directory as the path it names

A workspace directory with a leading space (" /Users/me/dev") was saved as
typed. Readers treat it as relative, so Orca resolved it inside every
registered repository and created an empty " /Users/me/dev" tree in each.
Normalize the saved value once where settings are written and loaded, as
the leading ~ fix did: leading whitespace before an absolute or home path is
dropped, while relative paths and trailing characters stay exact. A value
repaired on load keeps its old spelling in history so checkouts under the
old root stay visible. The settings field shows the saved value.

Co-authored-by: Eng. Mahmoud Bek ElNemer <ElNeMeR70@users.noreply.github.com>

* Expand ~\ only where it is a home path; drop type casts

* Classify the padded path with the host's own path rules

On macOS and Linux a leading backslash is part of a relative name, so only
Windows treats \work as absolute.

---------

Co-authored-by: Eng. Mahmoud Bek ElNemer <ElNeMeR70@users.noreply.github.com>

* fix(i18n): translate browser password prompts (#40446)

* fix(i18n): translate browser password prompts

* fix: wrap localized password save actions

* OpenCode subagents show in Chat UI and keep a chat open while they run (#40141)

* fix(native-chat): show OpenCode subagents and keep a chat open while they run

OpenCode reports each subagent as a child session when the client opts in.
Orca now opts in, files each child's tool calls and replies under its own
subagent, and keeps the subagent open until the child reports that it
completed, failed or was interrupted, including after the parent turn ends.

Co-authored-by: SeaBee <85791528+seabee33@users.noreply.github.com>

* fix(native-chat): keep a subagent's tool context when its rows are read back from the journal

Co-authored-by: SeaBee <85791528+seabee33@users.noreply.github.com>

* fix(native-chat): a Stop also ends running OpenCode subagents, and each subagent can be stopped from the strip

Co-authored-by: SeaBee <85791528+seabee33@users.noreply.github.com>

* fix(native-chat): only a chat Stop closes OpenCode subagents, an early subagent Stop stays available, and the close request has a deadline

Co-authored-by: SeaBee <85791528+seabee33@users.noreply.github.com>

* fix(native-chat): a subagent Stop that the subagent confirms while the request is out reads stopped

Co-authored-by: SeaBee <85791528+seabee33@users.noreply.github.com>

* refactor(acp): close child sessions from the agent connection

Keeps the session runtime under the line limit after the main merge.

* test(acp): read a child command through a type guard

* fix(native-chat): a subagent that reports its ending reads stopped even when the close request fails

A child Stop's session/close now asks the agent to drop the call on timeout instead of withdrawing the write.

* test(native-chat): OpenCode subagent calls group with other agents' subagent calls

---------

Co-authored-by: SeaBee <85791528+seabee33@users.noreply.github.com>

* fix(native-chat): honor Windows environment overrides consistently (#40301)

* fix(native-chat): apply Windows environment overrides consistently

Use the same case-aware composition for native chat launches, preflight,
model discovery and naming. Canonicalize configured Windows names before
account readers and preserve account pins and POSIX case distinctions.

Tracked in #39075.

Co-authored-by: 김태겸 <oharapass@gmail.com>
Co-authored-by: Vitalii Kulish <mancunianez@gmail.com>

* test(native-chat): cover effective Windows catalog changes

* test(native-chat): retain child environment test import after sync

* test(native-chat): verify Windows child environment precedence

---------

Co-authored-by: 김태겸 <oharapass@gmail.com>
Co-authored-by: Vitalii Kulish <mancunianez@gmail.com>

* Source Control's Codex model menu lists your account's models even when Codex's catalog is large (#40117)

* Source Control lists Codex models from app-server model/list instead of debug models

The Codex text-action picker read 'codex debug models', a dump of Codex's whole
catalog with prompt templates (40-90 KB per model). Once it passed the 4 MiB
output cap discovery failed and the picker kept Orca's built-in list, and the
dump's hidden entries were selectable. List models with app-server model/list
(hidden rows excluded), over Source Control's own launch, lock and teardown,
with the same 4 MiB input budget and 60 s deadline.

* Bound Codex model discovery input in bytes, key its cache case-insensitively on Windows, and stop the app-server only when it ignores stdin EOF

- Count raw bytes from both streams against the 4 MiB budget.
- Read CODEX_HOME (and other account dirs) case-insensitively on Windows for the host cache key.
- Skip the post-listing kill when the app-server already exited; fail waiting listing requests once discovery settles.
- Report an incomplete model list as such.
- Tests: fake-timer grace and successor admission after a successful listing, method-not-found and unreadable-page fallbacks.

* Key the Codex discovery cache on the env spelling Windows spawn uses

Windows env names ignore case and Node's spawn takes the lexicographically first spelling, so
CODEX_HOME wins over codex_home; the cache key now picks the same one.

* Narrow Codex app-server records with the provider record guard instead of type assertions

* Read Source Control action model picks under the repo's host key, as the picker saves them

Local repos resolved generation under the 'unknown' host key because their connection id is undefined, so the saved pick was skipped and the agent default ran.

* Key Source Control model picks by connection until the repo record loads, and test with a real local repo

* Key a native Codex listing's cache on process.env when no agent env was resolved

* feat(speech): record each model's languages, licence and measured error rates in the catalog (#40431)

Error rates come from Orca's speech bench (transcribe-cpp 0.4.1, Metal, pinned GGUFs):
LibriSpeech for English, 5 FLEURS dev clips for de, es, fr, ja, ko, pt, ru, tr and zh.
speechModelQuality turns them into the Voice settings word scale.

* fix(speech): let a slow on-device finish run while the engine still answers (#40460)

* fix(speech): let a slow on-device finish run while it still returns text

The stop timeout counted 60 s from Finish however much audio the engine still had
to decode, so a CPU model on a busy machine failed a dictation it was still
transcribing. It now fails only after 60 s without text for that dictation.

* fix(speech): count any engine answer as progress while a dictation finishes

* fix(mobile): close the details card when you touch the terminal (#38823)

* fix(mobile): close the details card when a touch lands outside it

Co-authored-by: zhangxaochen <3416677+zhangxaochen@users.noreply.github.com>

* fix(mobile): explain the pointerdown listener and drop a focus check that can't fail

---------

Co-authored-by: zhangxaochen <3416677+zhangxaochen@users.noreply.github.com>

* feat(dictation): pulse three dots in the pill while spoken words are still on their way (#40443)

* feat(dictation): show pending dots while the engine holds speech it hasn't committed

The VAD pause segmenter already knows when a phrase is open. The recognizer
now posts speech-pending when a phrase opens and clears it once that phrase's
text is posted, and the desktop pill shows three dots after the text while it
is on. Only pause-cut on-device streams send it, so cloud, the paired web
client and phones keep today's pill.

* fix(speech): keep a lone click from turning the pending dots on

* perf(native-chat): addressed tab-map transitions for chat membership (#40098)

* perf(native-chat): project structured chat tabs per workspace bucket

The shared chat-tab list was keyed on the whole unifiedTabsByWorktree
identity, so every tab write anywhere (terminal title batches included)
rescanned all tabs and handed every consumer a new list. Rebuild only the
buckets whose identity changed and return the previous list while no chat
tab changed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* perf(native-chat): reconcile chat status membership per changed tab

The status bridge rebuilt every member and host group and captured every
chat on any tab-map or owner-input change, and recaptured every chat on any
launch notification. Membership now updates only the tabs whose identity
changed; owner-input changes re-resolve only unstamped tabs, the only ones
whose host depends on them; launch notifications name their session so only
its tabs are recaptured (unaddressed ones still recapture all). The sleep
and launch-directory companions of one projection land in one store write.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* chore(ratchets): guard whole tab-map triggers and hot-map listeners

Add a count ratchet for identity comparisons of the whole
unifiedTabsByWorktree/tabsByWorktree map in renderer code (counts may only
fall), wired into pnpm lint, run-ratchets discovery and changed-check
selection. Extend the advisory no-whole-hot-map-selector rule to inline
store listeners that compare a whole hot map and to effect bodies that use
one whole from getState().

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* revert(lint): drop the hot-map listener/effect extension

The guard stays scoped to the repaired seams; no repository-wide rule change.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(native-chat): capture chat status on semantic tab changes; address tombstone retirement

Focus, preview and order-only tab writes no longer recapture a chat's
status: membership and the projection fences compare the fields the
projection reads. Absent-tombstone retirement now names the sessions it
retired, so no launch producer sends an unaddressed notification.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* feat(store): keyed-record transition seam for tab maps and companion records

Writers build next tab buckets and keyed companion records through
replaceTabBuckets / replaceRecordEntries (or name the keys of a record they
already built), which record exactly which keys and tab IDs changed against
the produced record. Readers ask readTabMapChanges / readRecordChanges for
an addressed delta; any unannotated link reads as a full resync. Adds
subscribeTabTransitions and a store-observer installer registry. The
chat-tab list and the status bridge now consume addressed deltas and keep
the whole-list walk only as the cold path.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* feat(store): annotate every shared tab-map writer through the seam

Shared tabsByWorktree/unifiedTabsByWorktree writers (tab, editor, browser,
worktree, ssh, terminal create/close/navigation/title-batch/reconnect/
hydration, web-session mirroring and layout frames) now build or annotate
their next tab buckets and companion records with the exact workspace and
tab IDs they changed; full hydration, full frame builds and reconnect
inventories are named full resyncs. Writers that copied every bucket for
one tab (preview promotion, dirty-file preview clear, worktree removal
filters, sleeping-record identity rename) now keep unchanged buckets.

Replace the renderer-wide tab-map comparison ratchet with a narrow seam
guard: the repaired consumers may walk a whole tab map only in named cold
paths, and the pinned writer modules may not build raw seam records.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* chore(store): name seam fields once; type seam records; record moved respawn writes

Writers stop repeating tracked field names (centralized in
seam-record-fields.ts), the seam takes a named KeyedSnapshot record type
instead of casts, and the layout-writer baseline records the respawn
helpers moved unchanged out of worktree-layout-lifecycle.ts.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* chore(store): drop seam field cast; record the seam field list in the layout-writer baseline

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(native-chat): keep chat-tab list in source workspace order (C3/F1)

Every source workspace keeps a projection slot, even without chats, and the
seam reports when a write added or deleted a workspace key or moved a
surviving bucket, so the incremental list never reorders first-match
consumers; those transitions take the cold rebuild.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(native-chat): keep projection and retirement debt awake (C2, F3)

A member is owed from attach or semantic change, members without a host
feed keep their own debt, and every reconciliation drains existing owed or
retired work even when no new member changed, including a closed last chat
whose feed is gone.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(store): stop inferring host-mirror companion IDs from visible tabs (C1)

A host retraction can delete PTY or layout records for tabs neither
inventory lists, so the mirror funnel marks its tab-keyed records full
resync instead of advertising an incomplete addressed set.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* chore(ratchets): close indirect paths in the tab-map seam guard (C4)

The guard now rejects ordinary callers of the bridge cold path, value
imports the repaired consumers have not reviewed, and raw seam records built
into a local binding without a seam helper annotating it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* test(native-chat): drive real tab actions through the status bridge (F2)

Close, session rebind with a late old-session tick, and a legacy owner
change, through createUnifiedTab/setTabLabel/closeUnifiedTab, so the
addressed reconciliation path is exercised.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* chore: exempt seam field-name constants from the layout-writer ratchet; drop dead setters (F4)

The seam's SEAM_*_FIELDS lists name fields without writing them, so the
ratchet skips them and the raised baseline row is gone (renderer total 569,
main 571). setStructuredChatSleptAt and the two launch-directory setters
lost their last callers to the batched companion write.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* feat(store): Map provenance and source-ordered change keys in the seam (PR A addenda)

replaceMapEntries gives Map-typed fields (runtimeStatusByEnvironmentId) the
same weak provenance and readRecordChanges protocol. Change keys compose
forward through a chain, oldest write first, so added keys come out in
source order (index keys first for records, as Object.keys orders them). A
surviving key deleted and re-added, in one write or across a chain, and a
record builder that moves a surviving key, read as a 'reordered' full
resync.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(store): writers declare key order; seam reports ordered additions (N1c, N1a)

The seam no longer scans both records to rediscover order on every
annotation. replace* helpers know insertion order by construction; annotate*
takes a declared order ('appended' by default, 'reordered' for a builder
that moves keys, read as a full resync), verified only under test.
Addressed changes now report keys, source-ordered added keys and deleted
keys, so a reader holding its own key ranks places changes in O(changed).
Worktree removal declares its editor-transfer records reordered.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* perf(native-chat): scope the chat-tab projection to chat-bearing workspaces (N2c)

The projection keeps chat subsets only for workspaces that have chats, plus
per-workspace source ranks that change only when a workspace is added or
deleted. A focus, label or title write costs O(that workspace); the
ordered list is materialized lazily, only for full-list readers, and keeps
its identity while no chat changed. Workspace adds and deletes are
addressed through the ranks instead of a cold rebuild.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* chore(ratchets): resolve aliases and require annotation before publication (C4)

Any reference to a cold helper or Object enumerator outside its named
callers counts, including aliases and callbacks; writer values are followed
through local aliases and must be annotated before the write that
publishes them.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* test(native-chat): scoped hydration keeps the warm chat list equal to a cold read (D1)

Drives the real scoped hydrateTabsSession on early/later chats of one
session: source order, list order and first-match precedence match a cold
rebuild, including when the selection order differs from hydrated order.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* perf(native-chat): cache the chat-workspace order across chat edits (D1 item 5)

The lineage keeps chat-bearing workspaces in rank order and only inserts or
removes one when it gains its first or loses its last chat; a chat object
change re-flattens the list without re-sorting. Full resyncs rebuild it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* chore(ratchets): the seam guard recognizes only construction helpers and cold marks (D1)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* refactor(store): addressed provenance only from constructing helpers (D1)

Remove annotateRecordReplacement/annotateTabBucketReplacement and their
caller-declared order: only replace*/recordDraft/tabBucketDraft, which
perform the construction, derive changed keys, additions and deletions from
their operations (delete-then-reinsert reads as reordered); opaque
reconstructions use markFullResync. Every former annotate caller is
migrated: ordinary local writes build their records once through helpers
or drafts with today's operations and order; scoped tab and terminal
hydration, the reconnect inventory, host-mirror application and layout
flush are full resyncs. Change readers move to keyed-record-change-reads.ts.
Renderer layout-writer total 567 (main 571).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* test(native-chat): type the hydration fixture without a cast

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(store): seam record writes define own entries, even __proto__ (N5c)

replaceRecordEntries and drafts assigned through the inherited __proto__
setter, so a tab id '__proto__' lost its own PTY, layout and startup
entries. Both now define an own data property, as the computed-key literals
they replaced did; contract test included.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* perf(terminal): build each createTab record once (N4c)

createTab took ensureGroup's and insertSplitGroup's whole records and the
orphan cleanup's seam records, then rebuilt them through the seam. It now
takes the per-worktree group and split results and gives the orphan
cleanup only the bucket it reads, so every seam record is constructed once
with its ordered operations.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* chore(ratchets): record createTab's orphan-cleanup input view (renderer total 570, main 571)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* refactor(terminal): split orphan cleanup so createTab cleans only companion caches (N6c)

buildOrphanTerminalCompanionCleanupPatch holds the per-tab cache, tab-bar
order and selection cleanup; buildOrphanTerminalCleanupPatch adds the seam
record deletes on top, unchanged for reconciliation. createTab calls only
the companion part with real state and deletes the seam entries through its
single construction, so the stub input view and its layout-writer baseline
rise are gone (terminal-tab-creation.ts back to 10).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* perf(terminal): filter the orphan cleanup's kept bucket once (N7c)

The full cleanup filters the workspace's tabs once and hands the kept
bucket to the companion part, which takes its replacement active tab from
it; createTab's companion-only call still makes its single find pass.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(tabs): keep removed worktrees' group entries deleted on removal

The second emptied-group collapse in removeWorktreeLayout also visited
removed worktrees and wrote their deleted groupsByWorktree entry back as
an empty list, a delete-then-set the seam read as a reordered full resync.
It now visits only surviving workspaces, so removal stays addressed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* chore(tabs): closeEmptyGroup collapses through the shared builder again

collapseGroupLayout takes the record builder, and the renderer passes the
seam's replaceRecordEntries, so the per-worktree collapse publishes with
provenance without new counted layout writes (tabs-group-actions back to 12).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* chore(tabs): reconciliation names its draft fields once (2 + 4 <= 9)

The written-field sets come from SEAM_ field-name constants instead of
string literals, so the reconciliation split no longer raises the combined
layout-writer count (tabs-reconciliation 2, tabs-reconciliation-batch 4,
against the original 9).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* chore(tabs): publish reconciliation drafts with explicit field writes (N9c)

Revert the SEAM_ field-name constants: the drafts are held under their own
names and finishReconciliationDrafts publishes each written draft as an
explicit unifiedTabsByWorktree/groupsByWorktree/activeGroupIdByWorktree/
layoutByWorktree property, so the counter sees every real write and the
split totals 2 + 4 against the original 9, with no exemption.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(i18n): avoid gendered review labels in Spanish (#40442)

* test: remove low-value speech, sidebar and composer checks (#40470)

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>

* feat(tab-group): make the split pane landing rows outline buttons (#40469)

* Show AskUserQuestion option previews on desktop and phone (#40109)

* Show AskUserQuestion option previews on desktop and phone

Claude can attach a preview (a mockup or snippet) to each option of a
single-select question. Orca dropped it, so users chose between bare labels.
Options now carry the preview through the journal, terminal asks and the
phone, and the question card shows the selected option's preview as plain
monospace text. Questions with previews confirm explicitly instead of
auto-advancing, so the preview stays readable.

* Keep phone previews bounded and digit focus on the named option

Phone previews get a capped height with their own vertical scroll, and
opening one never scrolls past its top. A digit always focuses its row
before the chosen-row check, so Enter confirms the option the user named.
Tighter preview line height, a stable phone row root, and a persistence
test whose expected previews come from the fixed input.

* Narrow the reopened question with a type guard instead of a cast

* Keep Claude background tasks attached to their subagent after restart (#39958)

* Keep Claude background tasks linked to their subagent across restarts

* Keep task ownership scoped to each Claude invocation

* Keep partial task events scoped to their invocation

* Distinguish pending task facts from admitted invocations

* Wait for saved task ownership before interpreting Claude events

* Verify journal binding order and saved task liveness

* Keep owned task updates from reopening Claude root turns

* Exercise owned historical notifications with provider identity

* Show friendly advice for Codex turn failures (#39959)

* Show friendly Codex turn failure advice

* Match terminal failure callback to status row type

* Keep terminal diagnostics in Details and clarify unknown failure advice

* Preserve usage-limit diagnostic metadata

* Validate custom host advice in phone renderer fixture

* Check categorized compaction failures and retained diagnostics

* Complete localized model-unavailable notices

* test(mobile): keep the failure notice helpers main's test cleanup removed

* Use Claude CLI titles for default native chat naming (#40069)

* Reuse Claude CLI titles for default chat naming

* Preserve independent naming while fencing Claude title reuse

* Keep optional title account lookup from blocking chat naming

* Clarify deferred preparation in naming tests

* Bind first chat naming to its ready handover

* Exercise title cleanup through a real queued command

* Use the timestamped operation fixture for compaction

* test: exercise naming fallback through Send now

* test: exercise terminal naming fallback after final starts

* test: remove low-value mobile and attachment cases (#40475)

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>

* fix(chat): always show copy and fork under a finished agent turn (#40474)

* fix(agent-status): show Codex's mid-turn async question as waiting for the user (#39087)

* fix(agent-status): show Codex's mid-turn async question as waiting for the user

* fix(agent-status): keep Codex's async question waiting on paired hosts under its working title

The phone's PTY-title renewal turned the waiting row back into working, because Codex keeps its working title while the question waits.

* fix(agent-status): clear Codex's open async questions when the user sends a plain message

Orca's chat card answers an async question as a quoted message, not Codex's reply wrapper, so the pane stayed waiting until the turn ended.

* refactor(agent-status): move the legacy status test seed out of listener-state

* feat(settings): redesign Voice settings around a setup card and a rated model list (#40452)

* fix(speech): keep a slow GPU model load on the GPU while it still makes progress (#40472)

The GPU stall watch gave the whole model load one 25 s budget, so a cold disk
or busy machine restarted a healthy load on the CPU and could miss the 60 s
start limit. The speech process now reports each load step, and the watch
restarts on it; a step that hangs for 25 s still moves to the CPU.

* Fork Codex replies after rewinding a chat (#40140)

* Fork Codex replies from the current history after rewind

* Strengthen Codex fork preparation failure coverage

* Preserve Codex fork compatibility and original chat guidance

* Explain when Codex cannot start for a fork

* refactor(claude): move the kill-delivered receipt check out of the session adapter (#40481)

The adapter crossed the 300-line cap after #40069 and main's lint fails. The same receipt check ran twice in it; one shared predicate brings the file back under the cap with no behaviour change.

* fix(speech): reload the warm recognizer once the VAD download lands (#40477)

A recognizer that loaded before the Silero VAD file existed kept cutting text
only at stop until its next restart. When the VAD lands, the speech service now
unloads that recognizer once no dictation uses it and warms it again, so pause
cuts start on first use. A running dictation is never interrupted.

* feat(settings): manage dictation vocabulary in Voice settings, per user and per repo (#40471)

* feat(settings): add a Vocabulary group to Voice settings for personal and repo terms

* feat(speech): use the repo name as a dictation hint without recasing one-word names

* fix(settings): use Input and Button styling as the design system defines it

* fix(settings): chain quick repo-term edits, report a host that drops them, keep the hint off phone replies

* fix(settings): key a pending repo-term write by host too

* refactor(settings): catch a dropped repo-term write in updateRepo and keep the name hint out of stored terms

* fix(settings): one vocabulary chip per term with its aliases, and no repo suffix on the scope tab

* fix(settings): skip a bare vocabulary term that already has an entry

* feat(mobile): add Shift, Ctrl and Alt latches and opt-in agent keys to the terminal key row (#38776)

* feat(mobile): smart rail keys for the terminal agent's state, with Shift/Ctrl/Alt latches

* fix(mobile): cancel key presses only when the key row really moves

* fix(mobile): keep Smart rail on through Reset Defaults and finish two-write keys

* fix(mobile): only a finger scrolling the key row cancels its presses

* fix(mobile): hold the smart group until the tap's press lands, and drop clicks on keys that moved

* refactor(mobile): simplify the smart rail touch hook and layout read

* chore(mobile): allowlist the Shift, Ctrl and Alt key names

* feat(mobile): Codex async question keys on the smart rail

* fix(mobile): drop the Codex Always key, whose key differs by approval

Codex binds "don't ask again" to p for a command-prefix rule and a for session and file approvals (0.136 through 0.166). The permission hook doesn't say which one is on screen.

* test: remove 21 low-value tests and two suites (#40479)

* test: remove 21 low-value tests and two suites

* test: remove Claude test-only background roster getter

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>

* test: remove 16 low-value chat tests (#40488)

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>

* test: remove 12 low-value paste and split tests (#40491)

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>

* fix(agent-picker): apply a model or effort picked in a form's setup panel (#40494)

* fix(agent-picker): apply a model or effort picked in a form's setup panel

* fix(agent-picker): keep the open setup panel when the form takes its pick

* fix(agent-picker): keep a form's accepted pick across scope resets and panel close

* fix(agent-picker): stage Arguments chip edits and keep picks compiler-safe

* perf(agents): release completed helper payloads and avoid history scans (#40171)

* perf(agents): release published child payloads and index live work

* perf(codex): preserve live task order on membership changes

* fix(codex): preserve parent-first helper interruption

* test(codex): assert public helper task identities

* fix(codex): keep helper roots in original stop order

* fix(codex): retain complete live helper ancestry for Stop

* Preserve sibling order when reasserting helper ownership

* perf(native-chat): keep streaming chats from re-projecting their whole transcript (#40251)

* perf(native-chat): keep streaming chats from re-projecting their whole transcript

Each stage of the streaming transcript pipeline (reducer merge, question
pass and receipts, message projection, desktop list fold) keeps its
previous input and output and re-derives only revised or appended rows,
falling back to the existing full projection whenever another row could
change. Desktop folds through the phone's fold units, now shared, and live
trims batch by a bounded slack so appends keep row positions.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* perf(native-chat): reuse appended subagent rows and batched revisions

Appended rows in chats with subagent rows extend their turn chunk when
earlier turn inputs are unchanged; batch merges and multi-row revisions
do one pass each; rows without journal positions keep in-place reuse.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* perf(native-chat): look up subagent chunks once per appended batch

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* test(native-chat): pin the live trim slack

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* feat(orchestration): mail an agent on a paired server with send --on; its reply comes back by itself (#40106)

* feat(orchestration): mail agents on a paired server with send --on, replies come back by themselves

* fix(orchestration): gate --on on the carrying Orca, ack retried deliveries, refuse payloads and federated workers

* chore(orchestration): annotate peer-mail row casts and drop a wall-clock wait in its test

* chore(orchestration): name mail-between-servers codes server_mail_unsupported and mail_requires_server_pairing

* docs(cli): list --on in the orchestration reply usage

* fix(orchestration): name --environment, the flag on main, in agent mail refusals and the guide

* chore(orchestration): mark the two deliberate agent-kind branches mail between servers needs

* docs(cli): leave --on out of the reply usage; it is accepted only to refuse it

* fix(orchestration): keep peer conversations to peer ids, survive resets and re-pairs, keep pulling on a registry error

- A server takes only peerconv_ conversation ids, so a paired computer cannot name another Orca's dispatch in the relay.
- A replayed reply after a mail reset is acknowledged, not retried forever; reset tasks keeps replies waiting to be pulled.
- A re-paired server starts a new conversation instead of refusing every later send and reply.
- A failed registry read keeps the pull loop retrying.
- Drop the two ast-grep-ignore lines that only #40193's rule needed.

* fix(orchestration): worker-start --on runs on the caller's own Orca, like send --on

An inherited ORCA_ENVIRONMENT sent worker-start --on to that server, so the
--on command the agent refusal suggests did not work. One rule now keeps
send, reply and worker-start with --on on the caller's own Orca.

* fix(orchestration): refuse reply --on on both sides of a conversation between servers

The side that opened the conversation still took --on when it named the right
server; the reply usage says --on is accepted only to refuse it.

* feat(native-chat): bring back the New divider behind a setting (#40490)

Restores the line above messages that arrived while you were away, the scroll to it on return and the screen-reader announcement, all removed in #38044. A new Chat > Behavior switch, "Mark new messages when returning to a chat" (on by default), turns it off; off tracks nothing.

* fix(acp): reset reply digest when a subagent restarts (#40496)

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>

* test: remove low-value chat surface suites and cases (#40495)

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>

* fix(speech): skip a canceled dictation's queued decodes, also during Finish (#40483)

* fix(speech): drop a canceled dictation's queued decodes, also during Finish

A cancel during "Finishing…" never reached the speech process, and a cancel
that did waited in line behind every decode queued before it, so the next
dictation sat behind audio nobody wanted. The host now sends the discard during
Finish too, and the speech process answers it at once and skips the stream's
queued work.

* fix(speech): close an audio port whose stream was canceled before it attached

* fix(speech): restart the speech process when a canceled decode outlasts a short grace

A cancel now answers at once, so a decode already running for the canceled
stream (hung in native code, or just long) could hold the queue for the next
dictation. The host waits up to 2 s for the speech process to reach the
discard; past that, with no other dictation on it, the process restarts, and a
dictation starting meanwhile waits for that instead of reusing it.

* fix(speech): skip a canceled stream's remaining chunks inside one feed or finish

* fix(speech): keep a failed restart after a cancel from failing the next dictation

* test: remove 54 low-value chat and terminal tests (#40500)

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>

* test: remove 36 low-value terminal lifecycle cases (#40503)

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>

* fix(automations): reopen a reused automation chat tab in the background (#40271)

* fix(automations): reopen a reused automation chat tab in the background

An automation that reuses its last chat queued the prompt into that
session even after the user closed its tab, but never brought the tab
back. After a reused send is accepted (including a lost or refused reply
recovered from the run receipt), reveal the session and publish its tab
with activate:false so it reappears without taking focus. A publish
failure is logged and never undoes the accepted send.

Extract the reveal + background publish pair into
publishChatTabInBackground and share it with reopenWorkerChat.

Fixes #40260

Co-authored-by: Wooseong Kim <innocarpe@gmail.com>

* test(automations): use the string workspace id in the chat-reuse harness

---------

Co-authored-by: Neil <neil@stably.ai>

* feat(mobile): read dictation progress to VoiceOver and TalkBack (#40504)

* feat(mobile): announce dictation to VoiceOver through the shell

* feat(mobile): speak dictation from the host, so leaving the session and the pill's toasts are announced too

* fix(mobile): say dictation outcomes where they are known, and re-read a repeated live message

* fix(mobile): leave a setup refusal to its setup sheet instead of reading the raw code

* fix(mobile): say cancelled when a kept recording is discarded

* fix(mobile): keep the selected GitHub project when switching projects (#39964)

* fix(mobile): keep the selected GitHub project when switching projects

Picking another GitHub Project on the phone snapped back to the first one.
A remembered desktop Board view made the pick throw before it committed,
and a committed pick wrote picker rows (title, summary) into settings,
which the host's strict schema refused, so the next hydration restored
the old project.

Commit only identity fields, ask for a view when the remembered one is
not a table, keep the candidate project's views out of the active list
until it commits, drop late view replies from an earlier pick, and key
Browse rows by project identity.

Co-authored-by: Eng. Mahmoud Bek ElNemer <tt21913@gmail.com>

* fix(mobile): keep github.com implicit in project refs and error on projects with no table view

---------

Co-authored-by: Eng. Mahmoud Bek ElNemer <tt21913@gmail.com>

* fix(mobile): keep unchanged lines in diffs of large files (#40511)

Files past the LCS cell budget fell back to prefix/suffix diffing, so two
edits at the ends of a 450-line file showed every line as rewritten. Use
jsdiff's Myers diff bounded by edit count for those files, keeping the
prefix/suffix fallback for mass rewrites.

* feat(browser): simplify the toolbar to one Annotate button and one menu (#40501)

Grab, Annotate and Draw become one Annotate split button. DevTools, open
in default browser, hard reload, find and extensions move into the menu,
with the profile list, new profile and cookie import under Profile. Zoom,
viewport size, password offers and file actions show as chips inside a
flat address field, and the Import hint moves to the new tab page. The row
drops from 50 to 40 px and the fold from nine stages to two.

* fix(claude): isolate new launches from parent sessions (#40281)

* fix(claude): isolate independent launches from parent session environments

* test(claude): use supported account environment fixture

* docs: slim CONTRIBUTING.md to the essentials (#40272)

* docs: slim CONTRIBUTING.md to the essentials for getting a PR merged

Move dev-environment reference into docs/development.md and the maintainer
release process into docs/releasing.md.

* docs: match CONTRIBUTING visual-proof guidance to the PR template

* docs: cut dev-environment internals and drop the stale release doc

Trim docs/development.md to the setup steps people actually hit, drop
docs/releasing.md in favor of the self-documenting Cut Release workflow,
and move the RC Homebrew cask into the README.

* Document Node compatibility and dev profile cleanup

* Offer only the speeds the model lists (#40257)

A speed the chat reports that no model lists, in practice Flex from the
user's own Codex config, is no longer added to the Speed choices. It
still runs and is still sent. A chat with no model of its own keeps its
saved speed when some model lists it, so Standard can still turn it off.

* fix(mobile): keep a finished agent tab's icon when its name mentions another agent (#40513)

* fix(mobile): stop reading a tab's name as its agent's terminal title

The host publishes a terminal tab's name (custom name, quick-command
label or agent session name) in place of its live title. Mobile fed
that name to the shared tab-agent resolver as title evidence, so a
finished Claude tab named "Agy status bar item" was read as a reused
Antigravity pane. Desktop resolves from the raw title and shows Claude.

Fixes #40084

Co-authored-by: Wenhao <4804785+Manho@users.noreply.github.com>

* fix(mobile): re-render a terminal tab when only its name changes

---------

Co-authored-by: Wenhao <4804785+Manho@users.noreply.github.com>

* test: remove low-value terminal coordination and startup cases (#40512)

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>

* fix(i18n): translate sparse preset validation failures (#40277)

* fix(i18n): translate sparse preset validation failures

* fix: classify sparse directory validation by type

* test: remove low-value cold restore and reattach variants (#40522)

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>

* test(browser): reset proxy policy in WebAuthn profile fixture (#40468)

* fix(skills): load Orca browser guidance for ordinary web tasks (#39916)

* docs: load Orca browser guidance for ordinary web tasks

Extend the Orca CLI skill description to include local and external page opening, reading, testing, and captures while working in Orca. Preserve explicitly chosen browser tools and the Computer Use boundary. Regenerate discovery/install bytes and append the new digest.

Fixes #39880

* fix: route existing Edge windows to desktop browser control

* docs: preserve configured desktop-control tool precedence

* docs(skills): scope orca-cli browser trigger to page work and keep the Computer Use rule

Drop "reading" so doc lookups stay on fetch tools, say plain HTTP/API calls don't need the skill, and restore the original Computer Use sentence and guide body, which the issue didn't ask to change.

* docs(skills): list Edge among existing browser windows in the orca-cli guide

The wider page trigger makes "screenshot my open Edge tab" load orca-cli, and the guide named only Chrome and Safari as windows that go to desktop control.

---------

Co-authored-by: Kelvin Amoaba <97001695+AmoabaKelvin@users.noreply.github.com>

* fix(usage): price Claude Fable 5.2 and Mythos 5.2 (#40506)

* fix(agent-hooks): unwrap outer sh -c wrapper when capturing Antigravity hook process (#40153)

* fix(agent-hooks): unwrap outer sh -c wrapper when capturing Antigravity hook process

* docs(agent-hooks): add docstrings and verify PreToolUse/PostToolUse/PostInvocation shell sequence

* test(antigravity): keep outer shell wrapper alive in shell-mode hook test

* fix(agent-hooks): require shell wrapper on Darwin and add OSTYPE=darwin unwrap tests

* fix(agent-hooks): match the macOS hook wrapper on argv[0] only

An agent whose prompt quotes the wrapper (/bin/sh ... ORCA_HOOK_AGENT_PID=) would
otherwise report its parent, the pane shell, as the agent. Drops the OSTYPE test
seam and keeps the unwrap test at the capture layer only.

---------

Co-authored-by: Ivan Ha <info.ivanha@gmail.com>
Co-authored-by: Kelvin Amoaba <97001695+AmoabaKelvin@users.noreply.github.com>

* style(menus): roomier 26px rows, lighter icons, solid surface

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Jinwoo Hong <73622457+Jinwoo-H@users.noreply.github.com>
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
Co-authored-by: Kelvin Amoaba <97001695+AmoabaKelvin@users.noreply.github.com>
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
Co-authored-by: Justas Brazauskas <brazauskasjustas@gmail.com>
Co-authored-by: Victor GALISSON <victor.galisson@purecontrol.com>
Co-authored-by: jp-liu <79979500+jp-liu@users.noreply.github.com>
Co-authored-by: Dhruv Singal <dhruvsingalabc@gmail.com>
Co-authored-by: Neil <neil@stably.ai>
Co-authored-by: Manuel Meurer <manuel@meurer.io>
Co-authored-by: Haoyu(Jerry) Wu <wuhaoyujerry@users.noreply.github.com>
Co-authored-by: Jerry Wu <haoyuwu@andrew.cmu.edu>
Co-authored-by: Wooseong Kim <2222333+innocarpe@users.noreply.github.com>
Co-authored-by: Franz Felberer <franz@felberer.at>
Co-authored-by: Calvin <35207870+zzz163519@users.noreply.github.com>
Co-authored-by: Wenhao <manhodl@gmail.com>
Co-authored-by: Pr1p <zwq19980411@gmail.com>
Co-authored-by: Yeji Han <yeji.hvn@gmail.com>
Co-authored-by: buf0-bot[bot] <252831055+buf0-bot[bot]@users.noreply.github.com>
Co-authored-by: Sean876-alt <sean@livingstoncaj.com>
Co-authored-by: Sean876-alt <224890246+Sean876-alt@users.noreply.github.com>
Co-authored-by: Eng. Mahmoud Bek ElNemer <ElNeMeR70@users.noreply.github.com>
Co-authored-by: SeaBee <85791528+seabee33@users.noreply.github.com>
Co-authored-by: 김태겸 <oharapass@gmail.com>
Co-authored-by: Vitalii Kulish <mancunianez@gmail.com>
Co-authored-by: zhangxaochen <3416677+zhangxaochen@users.noreply.github.com>
Co-authored-by: Eng. Mahmoud Bek ElNemer <tt21913@gmail.com>
Co-authored-by: Wenhao <4804785+Manho@users.noreply.github.com>
Co-authored-by: Vyacheslav Scherbinin <gentslava@mail.ru>
Co-authored-by: briannguyen9489032-beep <briannguyen9489032@gmail.com>
Co-authored-by: Ivan Ha <info.ivanha@gmail.com>
2026-11-10 21:10:01 -08:00
Dongho LeeandKelvin Amoaba 828e5bd0c9 fix(kiro): keep the Kiro session when kiro-cli reports status over OSC 7501 (#40568)
* fix(kiro): keep the Kiro session when kiro-cli reports status over OSC 7501

kiro-cli's TUI reports its state over OSC 7501 as `app=kiro-cli`, while
Orca's Kiro hooks, resume records and cold restore all use the agent id
`kiro`. The projector passed the app id through unmapped, so one Kiro pane
was tracked as two agents:

- a `kiro-cli` report that followed a finished `kiro` hook row took the row
  over as a different program and dropped its provider session;
- while a `kiro-cli` row was live, `kiro` hooks were treated as a nested CLI
  and their session id was not recorded.

`kiro-cli` is not a resumable agent id, so no resume record was kept and a
restarted Orca reopened Kiro tabs as plain shells instead of running
`kiro-cli --resume-id <id>`.

Map the app id to `kiro`, as `claude-code` and `grok-build` already are.

* docs(agent-hooks): list Kiro as an OSC 7501 sender reporting as kiro-cli

---------

Co-authored-by: Kelvin Amoaba <97001695+AmoabaKelvin@users.noreply.github.com>
2026-11-11 05:09:17 +00:00
Kelvin Amoaba 11eaa914fa fix(native-chat): let the first chat in a new worktree send and pick models while it starts (#40912)
* fix(native-chat): let the first chat in a new worktree send and pick models while it starts

A message sent while a chat's agent was still starting was refused with a
greyed Send and no reason. It now shows as sent at once and goes out with
the session create, or right after the chat exists, queued behind an
opening turn.

New chats now open their model picker from another workspace's list
without naming its default instead of a dead "Model" pill. The pending
worktree chat re-asks while the host is still listing, falls back to the
built-in list on failure, shares its answer with the worktree's chat, and
seeds the saved selection so the permission picker shows.

The worktree setup card now runs "Start <agent>" once the worktree is
ready instead of leaving it pending forever.

* fix(native-chat): keep a pending chat's host model list when a later re-read fails
2026-11-11 04:51:44 +00:00
Brennan BensonandClaude Opus 5.5 6b7da572e5 perf(persistence): window moves and UI edits no longer re-save the whole profile (#40627)
* perf(persistence): saves name the profile domains they changed

UI changes, window moves, SSH terminal exits, phone tab switches and
repo/folder/SSH/lineage edits no longer re-serialize and hash the whole
profile on the main thread. Each save names its domains (or 'whole' where
an operation can't know them), and one per-domain saved form serves both
selective and whole saves, writing dependent domains together.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(persistence): keep folder re-tiering and plugin context on named saves

Folder workspace unread writes re-tier sort stamps again, and plugins
hear an active folder's rename, now that those saves name their domain
instead of the whole profile. The folder-note test drains the load's
whole save so it checks the named save.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* test(persistence): typed folder workspace fixture for the re-tier case

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* test(persistence): build the serializer from typed state in the parity test

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(persistence): read unprojected domains without a type assertion

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* test(persistence): stall and fail the selective writer UI saves now use

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* perf(persistence): every mutation saves only the domains it changed

Removing a project, renaming a worktree's path, re-adopting an SSH
target, importing a catalog, deleting a group or folder workspace,
retrying a failed terminal close and remapping session panes now name
their domains instead of rewriting the whole profile. Only startup load
and maintenance resume rewrite every domain, through a separately named
entry point a lint rule keeps out of mutation code. A named
automationRuns save refreshes a pending run delta so it can't write an
older run list.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(persistence): a named automation-run save supersedes older run lists

A catalog import replaces the run list while an earlier run write can
still be in flight; if that write fails, its restored pending list must
not win over the import. Domain lists name the local session through
the shared constant so the layout-writer ratchet stays exact.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(persistence): keep the whole-row fallback for replaced run lists

A run list replaced outside the run-delta path (catalog import, SSH
re-adoption) saves as a whole row again, so histories that can't use
the per-run storage still save. A failed in-flight run write restores
its pending list only while that list is still current; otherwise it
marks the run domain for a whole-row save.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(persistence): save run lists per-run rows can't hold as one row

A run delta whose list can't be stored as per-run rows (a legacy
history with duplicate run ids) now saves as the ordinary whole-row
replacement instead of failing the transaction, so a catalog import
or run edit on such a profile still commits.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-11-10 20:49:00 -08:00
OrcaWinandOrcaWin 05cea5e99d test: remove 78 low-value tests across 39 suites (#41012)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-11-10 20:46:53 -08:00
Neil 1fbc0a5455 refactor(renderer): import launch and scroll definitions from owners (#41016) 2026-11-10 20:38:58 -08:00
Kelvin Amoaba ef26fd8ea3 feat(editor): open a file Orca can't display with the default app from its tab (#40971) 2026-11-11 04:32:22 +00:00
Kelvin Amoaba 8afd012d69 feat(browser): move extensions out of Experimental into Settings › Browser, on by default (#40939)
* feat(browser): move extensions out of Experimental into Settings › Browser, on by default

The old experimentalBrowserExtensions key is retired on load so profiles that saved the old false default start with extensions on.

* test(startup): mock extension startup in the ready-phase identity test

Extensions now default on, so ready-phase startup installs extension support, which this browser-identity fixture's Electron and store fakes don't cover.
2026-11-11 04:25:48 +00:00
Kelvin Amoaba 01efbf8cd1 feat(mobile): show merged, closed and draft PR icons by state (#40921)
* feat(mobile): pick the PR glyph from PR state

Workspace rows always drew the open-PR glyph and only changed its color,
and the PR tab's state badge had no glyph. Both now show merged, closed and
draft glyphs, from one prStateIcon mapping that the details sheet and task
rows also use.

* feat(mobile): show the PR state on the session header's source-control button

The button always drew a branch glyph. When the worktree's catalog row has a
linked PR it now draws that PR's state glyph in its state colour, read from
the host list's worktree.ps query in its own component.

* fix(mobile): read the header's PR state from the catalog cache only

Opening a session must not request worktree.ps before its first paint, so the
source-control button reads what the host list last loaded instead of
fetching it.
2026-11-11 04:25:41 +00:00
Neil ee3db6fe8c fix(agent-status): a skipped Codex async question no longer stays pending (#41013)
Codex's skip key (Ctrl+]) drops an async question only in its TUI: no hook,
no transcript line. Orca kept the question open and the row waiting until
the turn ended. The renderer now forwards the skip keystroke as a codexSkip
question inference; main drops one question from the tracker and restates
the row, returning it to working once none remain.
2026-11-10 20:25:29 -08:00
Kelvin Amoaba dc7e540698 fix(agent-status): stop Cursor's tab and sidebar title flickering while it works (#40996)
* fix(agent-status): stop Orca's spinner title fighting Cursor's own title

cursor-agent now paints its own '<chat> - ⏳ Working' title, animated every
200 ms. Orca still reasserted '⠋ Cursor Agent' on every Cursor hook and every
80 ms, so the tab and sidebar flipped between the two. Leave the working title
to Cursor, as for Codex; Orca still writes the ready and action-required titles.

* fix(agent-status): read cursor-agent's own status titles

With Orca no longer writing working titles over cursor-agent, every title
reader saw Cursor's '<chat> - ⏳ Working' titles as no status, so a Cursor
worker stopped taking orchestration mail mid-turn. Parse cursor-agent's exact
emoji + label pairs in the shared title classifier; review, loading, cloud and
reconnect screens are not a running turn and stay unclassified.

* fix(agent-status): keep the Cursor title parser ASCII for orcad bundles

esbuild leaves regex literals as written, and orcad rejects a non-ASCII
server bundle; escape the emoji instead.
2026-11-11 04:23:25 +00:00
Kelvin Amoabaandlucas-wu-wakakak 3e0b7f791a feat(pi): offer a thinking level for Pi terminal launches (#41006)
The launcher's Pi panel now shows Pi's thinking levels on the terminal
surface, narrowed to what the picked model supports, and a pick starts
Pi with `--thinking <level>`. Untouched, Pi keeps its own default.

Co-authored-by: lucas-wu-wakakak <8309814+lucas-wu-wakakak@users.noreply.github.com>
2026-11-11 04:19:46 +00:00
Kelvin Amoaba dbd3454ee1 feat(mobile): GitHub-style diff rows with a pinned gutter and sideways scroll (#40961) 2026-11-11 04:18:55 +00:00
Kelvin Amoaba aba6ec4c4c feat(mobile): GitHub syntax colours and the system code font for code, files and diffs (#40942) 2026-11-11 04:17:12 +00:00
Neil dedec14074 refactor(renderer): group pane layout and terminal input owners (#40989) 2026-11-10 20:09:37 -08:00
Kelvin Amoabaandlucas-wu-wakakak bb1d4789c4 fix(pi): start Pi terminal launches on the model picked in the launcher (#41005)
Pi had no launch catalog, so a picked model compiled to no arguments.
A launch-only catalog maps it to `pi --model <provider/id>`. A --model
in Pi's Arguments stays literal: Pi's CLI fuzzy-matches it, while Pi
chats need an exact provider/id.

Co-authored-by: lucas-wu-wakakak <8309814+lucas-wu-wakakak@users.noreply.github.com>
2026-11-11 03:55:39 +00:00
OrcaWinandOrcaWin 59fd2dac7c Remove 181 low-value tests and 28 entire suites (#41007)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-11-10 19:55:28 -08:00
Kelvin AmoabaandEng. Mahmoud Bek ElNemer ba930a07c1 fix(agent-status): show each OpenCode tab's own session, not every busy session in its folder (#41002)
* fix(agent-status): prevent shared-service OpenCode OSC 7501 from marking idle tabs working (#39619)

* fix(agent-status): scope OpenCode shared-folder OSC 7501 to the pane's hook

OpenCode 2 keys its OSC 7501 records by root sessions and never by
child sessions, so drop the parent/child id exception. Keep OpenCode's
all-idle report able to settle a hook-silent pane, and move the
precedence test to the server layer.

* fix(agent-status): keep another OpenCode session's status off a hooked pane

OpenCode's OSC 7501 root summarizes every session sharing the folder's
tabs, so its block or title on an OpenCode hook row belongs to another
pane. The pane's own prompts already come from Orca's plugin.

* refactor(agent-status): decide OpenCode peer records from the root app

---------

Co-authored-by: Eng. Mahmoud Bek ElNemer <tt21913@gmail.com>
2026-11-11 03:33:26 +00:00
Jinwoo Hong 313edfe375 fix(orchestration): a worker whose terminal died with Orca can be reopened again (#40963)
* fix(orchestration): a worker whose terminal died with Orca can be reopened again

When Orca dies while a terminal worker's follow-up is reopening, restart
recovery proves the worker's terminal exited and retires it, but left the
pane's restored hook row naming the conversation. Agents that report no
process to probe (Grok) then refused every later follow-up as held
elsewhere. Recovery now ends that pane's agent status, as the PTY's own
exit would have.

* fix(orchestration): leave a hook row a newer terminal in the pane owns
2026-11-10 22:32:05 -05:00
Jinwoo Hong 15b4b1eb02 fix(orchestration): a chat worker that died with Orca can be stopped and followed up after restart (#40967)
Restoring orchestration at startup reconciled terminal workers but not chat
workers' sessions. On a headless server (and orcad) nothing else did, so a
chat worker killed with Orca kept its old lease and read as unverifiable:
its open Dispatch could not be stopped, and mail to it was only filed.
Restoring orchestration now reconciles chat restart leases first, as the
desktop window already does at startup, so the worker reads as exited and
behaves as one whose process died while Orca ran.
2026-11-10 22:32:00 -05:00
Jinwoo Hong b89ee4db71 fix(orchestration): say the pairing is gone when a worker's server was removed (#40949)
* fix(orchestration): say the pairing is gone when a worker's server was removed

worker-show, stop and release on a Dispatch from an earlier pairing failed
with the environment store's raw "Unknown environment: <id>". Resolving the
server a worker is pinned to now answers peer_changed with a message that
the saved environment was removed or re-paired, as it already does when the
same environment names a different server.

* fix(orchestration): map only a missing environment to peer_changed
2026-11-10 22:31:56 -05:00
OrcaWinandOrcaWin 8ef629becd test: remove 129 low-value definitions and four whole suites (#41000)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-11-10 19:29:10 -08:00
OrcaWinandOrcaWin 81d3091515 test: remove 130 low-value definitions and eight whole suites (#40997)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-11-10 19:11:20 -08:00