Commit Graph
14693 Commits
Author SHA1 Message Date
Brennan Benson fcfe3196f7 fix(native-chat): decide provider failures by kind, never by searching the payload (#29092)
* fix(native-chat): decide provider failures by kind, never by searching the payload

A Claude chat opened with a red "claude · message:system:init" card whenever one of the
user's MCP servers failed to connect. The classifier searched every nested field of a
frame for failure-looking values and let that outrank the per-kind catalogue, so the
session-start roster's `status: "failed"` turned the whole frame into an error row. The
same search caused the earlier failed-background-task opcode cards.

A frame is now a failure only because its kind is one, or because a field its kind
declares as its outcome says so (Claude `hook_response.outcome`, Codex `hook/completed`
`run.status`). A Claude turn result is a failure when the result journaling says so,
since that depends on whether the user stopped the turn. With nothing left to override,
the typed-translator coverage list, its option and the task-frame sentence seam go.

Failed MCP servers write no row for Claude or Codex.

* test(claude): an uncatalogued frame keeps its sentence without becoming a failure

* fix(native-chat): keep Claude authentication errors visible
2026-10-16 16:41:14 -07:00
Kelvin Amoaba d963f2a23a fix(mobile): draw your own messages as markdown, fold long ones and open attached pictures full size (#29075) 2026-10-16 23:27:45 +00:00
Brennan Benson 027eb0ec83 Session History: save the parse cache one row per transcript, so restarts re-read nothing (#29401)
* Persist session parse results beyond the memory cache limit

* Expire Session History parse rows by idle time instead of a count cap

The parse cache dropped its oldest row once it held 4,096, so a scan over
more transcripts evicted its own upcoming hits and re-parsed every file on
every scan. Rows now stay until no reader has used them for 24 hours, swept
after each completed History scan and each search-index pass. Only the live
parser state keeps a 4,096 count bound; dropping it costs one whole re-read
of a file that changed, never a re-parse of one that did not.

Removes the overflow directory and the orcad persistence wiring from the
earlier revision of this change.

* Expire only rows History lists, and keep the newest transcripts under bounds

The search pass ran the idle sweep every 20 s but never re-stores History's
unchanged rows, so with search on a day away from History dropped them all
and reopening it re-parsed everything. Only a completed History scan sweeps
now, and it expires only rows History listed (on a monotonic clock). Rows
only the search index or a title lookup created keep main's 4,096 count
bound, so an index build can't grow the scanner process without limit.

Scans store newest transcripts first, so every store-order bound dropped the
newest, most active files: the saved snapshot kept the oldest ~11k rows and
the resume bound stripped the newest files' parser state. The snapshot is now
ordered by transcript mtime, and both count bounds drop the tenth of their
rows with the oldest transcripts.

* Drop the 24 h idle sweep; History rows live for the process

The sweep let whichever listing ran last decide when a row died, and
listings differ in scope: a smaller one (the phone's Agent History, a lower
desktop limit) expired rows only a larger one used, so the next Unlimited
scan re-parsed them. Rows History lists now live as long as the scanner
process; the snapshot's newest-transcripts cap bounds what survives a
reload, and rows other readers made keep the 4,096 bound.

Also clears the resume bookkeeping for a row the unlisted bound drops in the
same store.

* Skip resume bookkeeping for a row the unlisted bound just dropped

Running the resume trim first let a row the unlisted trim then deleted push
the resume set over its bound and cost 409 live resume points.

* perf(ai-vault): store the Session History parse cache one row per transcript

The parse cache was persisted as one JSON file, rewritten whole after any
change and capped at 64 MB / 1M tokens (about 11k rows). Past that cap, a
restart re-read the oldest unchanged transcripts, and an agent writing while
History was open rewrote 10-27 MB every few seconds.

The scanner child now keeps the rows in their own SQLite file. A miss in the
in-memory map reads the row by path; changes are written per row, debounced
and chunked; deletes run immediately. A used_at column, refreshed at most
daily, expires rows nothing has used for 30 days, so rows for transcripts
deleted outside Orca have a way to die. The old JSON file is imported once
and removed. Electron main only records the path for the child.

The rebuild-on-corruption open moves out of the search index into a shared
versioned cache database helper used by both.

* fix(ai-vault): import the old parse cache at open and expire rows only for missing transcripts

Review round 1 on the per-row parse cache store:
- The old JSON was imported only by a History scan or search pass, and only
  into an empty database, but deleted either way. Tab-title sync writes a row
  first, so the import was skipped and the first History open after updating
  re-read everything. The import now runs when the database opens, before any
  lookup, write or delete can reach it, keeping rows already present.
- A 30-day last-use expiry also dropped rows for transcripts that still exist,
  so opening History less than monthly re-read them. Rows unparsed for 30 days
  are now checked on disk and dropped only when the transcript is gone, then
  the freed pages are reclaimed. The daily last-use rewrite goes away.
- 8 KB pages (a ~2.3 KB row filled a 4 KB page), busy_timeout before the WAL
  switch, and lookups served from rows still waiting to be written.
- Revert accidental pnpm-lock.yaml churn.

* chore: revert accidental pnpm-lock.yaml churn

* fix(ai-vault): never stat WSL transcripts from the parse cache recheck

A stat on a \\wsl.localhost path outside the scanner's running-distro
filter and gated WSL lane could boot a stopped distro or hang on a stuck
one. WSL rows now skip the existence check and age out after 180 days
unparsed. SQLite-backed session keys use the shared OpenCode path splitter.

* fix(ai-vault): register the parse cache database with the WSL mover census

Each schema keeps its own meta table again, so the shared open helper
creates none and the search schema's SQL is unchanged. The parse cache's
tables are recorded as not moved: a cache rebuilt from transcripts.

* fix(ai-vault): a forced whole read never reuses a row a failed database delete left behind
2026-10-16 16:24:27 -07:00
Jinwoo Hong 3615d0423b Layout-owner follow-ups: closed waiting splits, paired-server tabs by host, first-tab e2e wait (dark) (#29866)
* fix(layout): E1 follow-ups: closed waiting splits, paired-server tabs by host, first-tab e2e wait

- A pane main closes before its tab mounts drops that tab's waiting split for it.
- Mounted tabs skip main's pane tree for paired-server workspaces by host kind, as frames do.
- ensureTerminalVisible waits for the activation tab before creating one.
- Main-asked splits write their pane through admitRuntimePane; startInAdmittedPane loses its single-caller `shown` option.

* refactor(layout): paired-server check in its own module, out of the frame sync
2026-10-16 16:22:42 -07:00
Brennan BensonandJae 47a5584ccb fix(acp): OMP, OpenCode and Grok chats follow their own permission mode (#29335)
* fix(acp): OMP, OpenCode and Grok chats run their own permission mode

An ACP chat's full access came from its agent's terminal Arguments, so OMP
and OpenCode, which have no bypass flag, asked on every tool call even when
the user chose Yolo. ACP chats now offer Ask for approval and Full access,
seeded from "Permissions for new chats", as Claude and Codex chats do; chats
from before take what they ran with, or the new-chat setting for an agent
that never had a bypass flag. Terminal launches are unchanged.

Co-authored-by: Jae <38941444+JaeCoding@users.noreply.github.com>

* fix(acp): hand the chat's permission mode to the ACP launch

The adapter narrowed the start to its identity and spawn token, so the mode
the host decided never reached the launch. Mobile's picker also showed
Claude's Ask wording for the other chat agents.

* fix(onboarding): Yolo mode also sets new chats' permission mode

Onboarding's Yolo mode says agents run without asking, but it wrote only the
terminal Arguments, so turning it off left new chats on Full access. It now
sets Permissions for new chats too, keeping an Accept edits or Approve for me
choice as it keeps custom Arguments. Pins that every ACP chat agent offers
Full access, and renames the backfill rule for what it decides.

* fix(onboarding): a replay left untouched keeps new chats' permission mode

Replaying onboarding, including from a paired browser whose Yolo switch starts
from its own defaults, rewrote the host's Permissions for new chats. It now
changes only when the person flips Yolo mode in this flow.

* refactor(onboarding): the Yolo switch's state lives in its own hook

Keeps the onboarding flow hook under the file-length limit after it began
passing whether the switch was flipped.

* test(native-chat): pin an OMP chat's pre-mode backfill at the record store

The host-install test's OMP row read no record; the backfill itself is
agent-agnostic, so pin it where the store writes it.

---------

Co-authored-by: Jae <38941444+JaeCoding@users.noreply.github.com>
2026-10-16 16:20:49 -07:00
Brennan Benson 39cf8703b7 fix(native-chat): keep the session host under its line limit (#29869)
#29121 and #27925 each added lines to structured-agent-session-host.ts and passed CI on their own; together they left it at 302 countable lines, so static analysis fails on main. Move the late-dispatch and unanswered-dispatch delegates into the mutation delegates that already wrap the same context.
2026-10-16 16:16:32 -07:00
Neil dd70294f0c fix(ssh): turn a host's orca CLI control on by default and stop resetting saved choices (#29856)
* fix(ssh): allow a host's orca CLI to control Orca by default, and stop resetting saved choices

An absent allowRemoteCliControl now means on; only an explicit false turns the
desktop browser bridge and relay-era orchestration commands off. Removes the
one-time reset of saved true values, the consent stamp writes and the reset
notice, which would have silently turned off hosts upgraded from stable.

* fix(ssh): keep the CLI control choice when adding a host from the sidebar

* fix(ssh): read an older client's omitted CLI control switch as off over RPC

* fix(ssh): check the managed fence before routing settings in browser bridge candidates
2026-10-16 16:08:37 -07:00
Neil c4c03e89bb Compact the Space window layout (#29857)
* Compact Space window layout

* Make Space summary and workspace rows single line

* Expose confirmed single-workspace delete in Space rows
2026-10-16 16:06:15 -07:00
Kelvin Amoaba 17a2bced11 fix(mobile): stop phone reconnects from undoing a desktop take-back (#29465) 2026-10-16 23:04:48 +00:00
Kelvin Amoaba 4d5f2a20ee fix(native-chat): dictate into the composer when it lacks focus (#29855) 2026-10-16 23:02:52 +00:00
Brennan Benson 8c1ade9e44 Keep pending chat decisions available outside loaded history (#29121)
* fix(native-chat): retire acknowledged send echoes after catch-up

* test: follow phone lint rules in reconnect regression

* test: format reconnect message expectation

* Keep current chat requests independent of loaded history

* Read current chat requests in bounded cursor pieces

* Fix current request subscription static checks

* Match generated request schema import ordering

* fix(native-chat): correct current request type boundaries

* Exercise current request reads in wire compatibility inventory

* Align current request fixtures with provider commit and RPC surface

* test(native-chat): include current request browser capability

* Keep the mobile chat session hook within its line budget

* Format mobile current request readers

* fix(native-chat): reopen once for a current-request version whose read keeps failing

A deterministic read failure reopened the subscription, whose first frame re-read the same version and reset the backoff, so the pane resubscribed every 750 ms forever.

* test(native-chat): type the current-request snapshot fixture

* test(native-chat): wait for the hydrated open before driving the subscription
2026-10-16 16:01:58 -07:00
Brennan Benson 6dd7ab5552 fix(native-chat): identify the owner when resume creation is refused (#29119)
* fix(native-chat): identify the owner when resume create is refused

* test(native-chat): consolidate transcript refusal cases

* test(native-chat): keep ownership coverage at the launch boundary

* test(native-chat): update adoption fixture for early ownership admission

* test(native-chat): align resume admission assertions with host ownership

* test(native-chat): keep RPC coverage focused on ownership proof

* test(native-chat): assert blank request presence before optional source

* fix(native-chat): preserve resume refusal behavior and honest transcript feedback

* fix(native-chat): retain import read evidence in typed result

* fix(native-chat): restore the refusal error import lost in the main merge
2026-10-16 15:57:54 -07:00
Brennan Bensonandm4air b7f6e71b7c perf(native-chat): flush send transactions without flushing every streamed revision (#28966)
* perf(native-chat): sync durable actions without flushing streamed revisions

* test(native-chat): provide the pending dispatch turn scope

* refactor(native-chat): reuse the journal transaction for durable writes

* fix(native-chat): save user intent before provider effects

* perf(native-chat): skip the per-commit fsync only for streamed assistant text

Every chat-journal commit ran a full fsync on the main thread. Replace the earlier allowlist of FULL rows (which missed control facts riding generic rows) with the inverse: FULL stays the default, and a transaction commits at NORMAL only when every row is a later revision of an assistant message that changes nothing but its content. An OS crash can lose only that streamed text; the next FULL commit makes it durable.

* fix(native-chat): keep image revisions fully durable

* fix(native-chat): preserve marked assistant revisions at FULL

---------

Co-authored-by: m4air <m4air@Mac.localdomain>
2026-10-16 15:56:21 -07:00
Neil f8089c787b fix(orcad): keep the integration credential key only in the owner-only key file (#29859)
* fix(orcad): keep the integration credential key only in the owner-only key file

Managed servers no longer create, read or wait on the macOS login Keychain or
a Secret Service keyring for their credential master key. A record an hourly
build pointed at the keyring is migrated once: the key is copied into the key
file when it reads back without UI within 5s, otherwise a new key file is
started and old credentials show the usual reconnect error. Removes the
locked-Keychain status fields, retry, the integrationCredentials.moveKeyToFile
RPC and the Settings notice.

* fix(orcad): serialize the keyring-record migration so concurrent starts agree on one key

* docs(orcad): note the migration lock

* fix(orcad): publish the migration lock with its PID and bound every wait

* refactor(orcad): reuse the file transaction lock for the keyring-record migration
2026-10-16 15:53:30 -07:00
Brennan Benson fa923c67e3 fix(projects): a manual folder-to-Git change proves the folder is the Git root first (#29282)
* fix(projects): refuse a folder-to-Git type change that would hide folder workspaces (STA-10638)

A folder project's extra workspaces exist only as metadata rows that the folder listing shows. Changing the project to Git switches it to `git worktree list`, so those workspaces drop out of the next refresh and their saved tabs are purged as if they had been deleted. Only the automatic upgrade checked for this; `orca project setup-update --kind git`, repo updates and clone-into-folder did not.

The check now lives in the one repo write every type change goes through, so any caller gets the same refusal and nothing is written. The automatic upgrade and both clone paths use the same check and keep the project as a folder project.

* test(repos): give the repo handler store mock the metadata read the clone path now uses

* fix(projects): a manual folder-to-Git change proves the folder is the Git root first (STA-10638)

Only the automatic upgrade checked that a folder project's path is the top of a Git repository and recorded Git's spelling of that root. A manual change (orca project setup-update --kind git, repo updates, clone into a folder project) flipped the type without either, so a project inside another repo became a broken Git project, and a project whose path Git spells differently lost its main workspace's id and tabs on the next refresh. All of them now run the same proof and write the proven root with the type change. SSH projects keep the unverified change because their Git runs on a host this process can't probe.

* Revalidate folder-to-Git conversion targets after Git proof

* Explain when a project changes during its Git check

* Fix clone conversion for replaced folder project

* test(runtime): create real Git root for clone upgrade fixture

* test: normalize runtime clone root stamp expectation

* test: check clone registration identity without unknown access

* chore: restore main's pnpm-lock.yaml that the main merge reverted
2026-10-16 15:53:12 -07:00
Jinjing c31712f1a0 fix(tabs): remove close-button tooltip (#29851) 2026-10-16 15:50:34 -07:00
Brennan Benson 69d877e272 fix(pty): window daemon spawns wait for a WSL guest's Pi/OMP relay, like host spawns (#29780)
A Pi or OMP agent started from the window inside WSL, with the terminal
daemon on, could start before the distro's hook relay was up. It then
reported no working/idle status for that session. Host starts and
local-provider starts already waited (#21721); the window's daemon path
was the one left out.

The shared host-env step now always runs the wait on the daemon spawns
it builds, so the lane input that skipped it for window starts is gone.
The wait is a no-op for anything but an explicit Pi/OMP launch in WSL.
2026-10-16 15:50:04 -07:00
Brennan Benson f9acfffa03 fix(unread): bold an agent row only while looking at it can clear it (#28830)
* fix(unread): one rule decides when an agent row is unread; title-only rows never are

The workspace card bolded a row Orca knows only from its terminal title or
process ("Claude Code - Idle") because that row restamps its state time on
every rebuild, so no acknowledgement ever covered it: opening the workspace
left it bold, clicking the row cleared it, and the next rebuild re-bolded it.
The dashboard already skipped such rows.

isAgentRowUnseen is now the single rule for the card, the dashboard snapshot
and counts, and the pop-out patch. It is built on the acknowledgement
module's own turn test, so a row is bold only when viewing it can clear it.

Fixes #28306

* fix(unread): bold an agent row only while auto-acknowledge can clear it

The row unread rule now reads the store's live and retained turn records, the
same records auto-acknowledgement clears, instead of a startedAt === 0 marker
on the row. Rows with no record of their own (title/process-derived, running
subagent children, migration-blocked panes) are never bold, so none can stay
bold with nothing able to clear it. Running subagent rows previously stayed
bold for their whole run.

* chore: drop accidental pnpm-lock.yaml churn

* perf(unread): build the row turn records once per pass, not once per row

* docs(unread): name the rule in the dashboard snapshot comment
2026-10-16 15:25:03 -07:00
Jinwoo HongandClaude afcca77a5e Window pane trees drawn from main's layout frames (dark, build switch off by default) (#29788)
* Window pane trees drawn from main's layout frames (dark, build switch off by default)

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

* Review fixes: paired-server saves keep their trees, last-pane guards count pending closes, draw main's answer when no frame is coming, redraw main's tree after a zoom

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

* Delta review: a closed stream's frozen cache never redraws pane trees; the frame reconciler never closes a tab's last pane

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

* Second delta review: draw the activation tab only when no frame showed it; a stream not yet open counts as coming; a redraw re-queues only its tab's workspace

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

* Independent review: a late record of a split main asked for restores it only if main took it back; a closed stream never redraws pane trees

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

* Delta review: deny a late record only for a split main wrote and did not take back itself; pane_closed only where the tab is; a reply before the stream opens waits for its first frame or end

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

* Re-review: replies wait only once the window opened its stream; a record that lands while main takes its split back waits for the outcome

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

* A record waits only while main is taking its split back

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

* refactor(layout): main keeps the pane of a split it asked for

Drops the timed withdraw and the window's late record of a main-asked split: main writes the pane and keeps it, a refused write fails the split, and a tab's first mount leaves queued main-asked splits to their request so the startup command still runs.

* refactor(layout): drop the start-without-pane option no caller uses

* refactor(layout): drop an always-false check in the queued main-split filter

* test(layout): main-asked splits into an unmounted tab, with the stream down, and with no start

* test(layout): type-safe withdraw stub

* fix(layout): equalize draws itself when no frame is coming

* refactor(layout): name the frame-tree pane moves for their role

* refactor(layout): frames' pane trees reach the store through the frame store patch

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-16 15:24:12 -07:00
Brennan Benson 23b60a5d07 fix(worktree): offer Force Delete with a plain warning when a checkout has submodules (#28878)
* fix(worktree): remove a clean checkout that contains submodules

Git refuses to remove any worktree with an initialized submodule unless --force is set, even when nothing is uncommitted. After that specific refusal, re-check the checkout including files inside the submodule and pass --force once. A dirty submodule still blocks the delete. A second --force is not added, because that would also clear a Git lock.

* fix(worktree): offer Force Delete for a checkout Git refuses over submodules

Drop the automatic --force retry. A forced removal deletes the submodule's
repository under .git/worktrees/<id>/modules, including commits, local
branches and stashes that were never pushed, and the parent's dirty check
cannot see them (the retry #24602 removed for the same reason).

Instead classify Git's "working trees containing submodules cannot be moved
or removed" refusal as a new Force Delete reason, 'submodules', after the
lock check and the force guard. The renderer classifies the error string,
so older SSH and paired hosts get it too with no wire change. The toast,
the delete dialog and the Space manager row show one plain warning naming
what Force Delete would lose instead of Git's text.

Keep --ignore-submodules=none in the pre-teardown dirty check so a user's
ignore setting cannot hide submodule edits from it.

Replace the retry test with one real-Git test: a clean checkout whose
submodule holds a local-only commit is refused without --force, stays
registered, keeps the commit, and its refusal classifies as 'submodules'.

* fix(worktree): clarify the submodule Force Delete warning and hide View for it

The warning now says Git deletes the workspace only with Force Delete, the submodule
refusal no longer offers View (there are no changes to show), the real-Git test pins
that Force Delete then succeeds, and a comment no longer describes the removed retry.

* fix(worktree): name submodules when a changed submodule blocks the delete, and clear a stale delete toast

With a commit inside a submodule that the parent has not recorded, the parent sees `M vendor`, so
Orca's dirty check refused first with the generic "It has changed files". Force Delete then destroyed
the never-pushed submodule commit without warning. On a dirty refusal the check now asks
`git status --porcelain=v2` whether a changed entry is a submodule, and if so reports a refusal the
app shows with the submodule Force Delete warning. The detail keeps the dirty sentence, so older
apps still offer Force Delete.

After the Space manager deleted a workspace (its row Force, or a later delete), the failure toast for
that workspace stayed up and still offered Force Delete. A Space delete now dismisses it.

* fix(worktree): warn about submodules on any refused delete of a checkout that has them

Review found the warning still vanished when a submodule commit was already recorded and some other
file was dirty: the parent's status shows no submodule change, yet Force Delete destroys the commit.
The changed-files check now asks whether the checkout has a populated submodule (a .gitmodules path
holding a .git, as Git's own refusal checks) instead of whether one changed. The refusal keeps the
changed paths after the detail, and View stays available since there are changes to view.

* test(worktree): mock the .gitmodules probe as missing in the preflight unit tests

* refactor(worktree): attach the dirty-check status without a type assertion

* fix(worktree): key the delete failure toast by host and worktree id

A Space delete on one host must not dismiss another host's failure toast for a worktree with the same id.

* fix(worktree): dismiss the failure toast by the store row's host after a Space row Force
2026-10-16 15:23:13 -07:00
Brennan Benson be033c2ab2 Session History: stop re-reading unchanged transcripts past 4,096 files (#28821)
* Persist session parse results beyond the memory cache limit

* Expire Session History parse rows by idle time instead of a count cap

The parse cache dropped its oldest row once it held 4,096, so a scan over
more transcripts evicted its own upcoming hits and re-parsed every file on
every scan. Rows now stay until no reader has used them for 24 hours, swept
after each completed History scan and each search-index pass. Only the live
parser state keeps a 4,096 count bound; dropping it costs one whole re-read
of a file that changed, never a re-parse of one that did not.

Removes the overflow directory and the orcad persistence wiring from the
earlier revision of this change.

* Expire only rows History lists, and keep the newest transcripts under bounds

The search pass ran the idle sweep every 20 s but never re-stores History's
unchanged rows, so with search on a day away from History dropped them all
and reopening it re-parsed everything. Only a completed History scan sweeps
now, and it expires only rows History listed (on a monotonic clock). Rows
only the search index or a title lookup created keep main's 4,096 count
bound, so an index build can't grow the scanner process without limit.

Scans store newest transcripts first, so every store-order bound dropped the
newest, most active files: the saved snapshot kept the oldest ~11k rows and
the resume bound stripped the newest files' parser state. The snapshot is now
ordered by transcript mtime, and both count bounds drop the tenth of their
rows with the oldest transcripts.

* Drop the 24 h idle sweep; History rows live for the process

The sweep let whichever listing ran last decide when a row died, and
listings differ in scope: a smaller one (the phone's Agent History, a lower
desktop limit) expired rows only a larger one used, so the next Unlimited
scan re-parsed them. Rows History lists now live as long as the scanner
process; the snapshot's newest-transcripts cap bounds what survives a
reload, and rows other readers made keep the 4,096 bound.

Also clears the resume bookkeeping for a row the unlisted bound drops in the
same store.

* Skip resume bookkeeping for a row the unlisted bound just dropped

Running the resume trim first let a row the unlisted trim then deleted push
the resume set over its bound and cost 409 live resume points.
2026-10-16 14:58:39 -07:00
Brennan Benson 702ffc7bc4 fix(orchestration): native-chat agents get their mail notice right away, the same way for every agent (#28540)
* fix(orchestration): steer mail notices into a busy native chat's running turn

A native-chat coordinator's mail notice always queued as a card until the whole turn ended, so a
coordinator busy in a long command never saw its workers' reports. Agents that take a message
mid-turn (Claude, Codex, Pi) now get the notice at once; agents that would cancel their turn for
it (ACP) still queue it.

* fix(orchestration): drain mail that arrives while a notice is pending

* Send orchestration mail the same way to every chat agent

A mail notice is a plain send for every agent, as a person's own mid-turn message is.
While a pending approval or question waits on the person it is held, and the turn's
working edge after the answer sends it.

* Hold orchestration mail at live prompt admission
2026-10-16 14:44:59 -07:00
Kelvin Amoaba fe41a9a5e8 fix(clipboard): paste CleanShot JPEG screenshots stored under the macOS PNG type (#29685) 2026-10-16 21:44:15 +00:00
OrcaWinandOrcaWin 74a90ebea7 ci: isolate process probes and palette timing checks (#29843)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-16 14:43:14 -07:00
Neil 3d2f3769f5 test(orcad): wait out the WSL service's hold on a serve-parity profile (#29845)
Since #29209 a full orcad on Windows runs wsl.exe in its profile directory.
On windows-11-arm the WSL service keeps that empty directory busy for ~60s
after every process in the test's tree has exited, past removeTestRoot's
~45s retry budget, so the recipe and D7 cases fail cleanup with EBUSY.
2026-10-16 14:42:44 -07:00
Neil e8ec456707 test(palette): measure matcher scaling within one run instead of absolute milliseconds (#29844)
The warm-match (220 ms) and search+sort (180 ms) ceilings failed on shared
CI runners at 211-245 ms for unchanged code. Compare one full-corpus pass to
four quarter-corpus passes timed in the same run: ~1 for linear work, ~4 for
quadratic, independent of runner speed.
2026-10-16 14:42:39 -07:00
Kelvin Amoaba 5c0ab96d65 fix(checks): show a PR found by Refresh, not "already exists" (#29231) 2026-10-16 21:38:26 +00:00
OrcaWinandOrcaWin f151bd965b perf(dev): avoid pairing web rebuilds after test file changes (#29838)
* perf(dev): keep pairing web bundle after test file changes

* fix(dev): detect web input replacement within one timestamp tick

* test(dev): use real files for web replacement coverage

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-16 14:35:06 -07:00
OrcaWinandOrcaWin ea451aa50a perf: reduce redundant sidebar agent status subscriptions (#29840)
* perf: reduce sidebar agent status subscription work

* test: align sidebar store mocks with subscription API

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-16 14:32:29 -07:00
Jinjing 804cffde0f docs: remove obsolete automated setup tab plans (#29846)
* Run automated workspace setup in a dedicated tab

* Preserve native-chat setup route coverage after placement retirement

* Preserve Setup tab design and independent review evidence

* Classify activation test fixtures consistently for routing checks

* Sync runtime English catalog after removing setup preferences

* Update listener savings model after retiring setup action

* docs: remove automated setup tab planning files [skip ci]
2026-10-16 14:30:32 -07:00
Brennan Bensonandm4air f96c672a9e fix(hibernation): never hibernate floating-workspace terminals (#29790)
* fix(hibernation): never hibernate floating-workspace terminals

* docs(hibernation): clarify floating terminal idle policy

---------

Co-authored-by: m4air <m4air@Mac.localdomain>
2026-10-16 14:17:59 -07:00
NeilandClaude e0e2001701 refactor(legacy-relay): one ask-wait clamp and no needless exports (#29835)
optionalAskTimeout computed exactly what cappedWait does (an undefined timeout stays undefined;
anything else clamps to 0..ORCHESTRATION_ASK_MAX_TIMEOUT_MS), so the ask call uses cappedWait.
Three functions used only inside their own file lose their export, and the per-host switch's
doc names the orchestration commands it has gated since #29571.

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-16 14:11:36 -07:00
Jinjing 005f06bbc7 Run automated workspace setup in a dedicated Setup tab (#29819)
* Run automated workspace setup in a dedicated tab

* Preserve native-chat setup route coverage after placement retirement

* Preserve Setup tab design and independent review evidence

* Classify activation test fixtures consistently for routing checks

* Sync runtime English catalog after removing setup preferences

* Update listener savings model after retiring setup action
2026-10-16 14:05:50 -07:00
Neil a775aeaf65 fix(runtime): do not bind a loopback port another runtime already serves on all interfaces (#29837) 2026-10-16 13:55:24 -07:00
Jinwoo Hong 9528ee23c0 chore(mobile): bump to 0.0.57 and Android versionCode 21 (#29839)
Matches the iOS 0.0.57 OTA TestFlight build. Android skips 0.0.56 (iOS-only release), so the code moves from 20 to 21.
2026-10-16 13:46:41 -07:00
Neil 3fdcbfaa1e fix(terminal): open the picked Windows shell on a managed host instead of typing it into PowerShell (#29836)
* fix(terminal): open the picked Windows shell on a managed host instead of typing it into PowerShell

* fix(terminal): keep the shell pick through materialization and folder runtimes

* fix(terminal): refuse a shell pick on agent launches and update paired-tab expectations

* test(terminal): type the shell-selection runtime fixture instead of casting
2026-10-16 13:45:59 -07:00
NeilandClaude b3299473e6 refactor(resume): build the whole recorded resume recipe in one place (#29830)
resolveResumeLaunchRecipe now also carries the recorded agentCommand and ompResumeFilePath, so
the three resume lanes stop repeating the same conditional spreads. The rule is unchanged: a
resume relaunches with exactly what its launch recorded, or nothing.

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-16 13:33:52 -07:00
NeilandClaude cd42d59a77 refactor: drop the unused sync spool drain and thread-goal wrapper; reuse the shared absence check (#29820)
The synchronous drainAgentHookSpool and currentAgentSessionThreadGoalBySequence lost their
last production callers earlier today; tests now exercise the functions production calls.
git-object-quarantine reuses isDefinitiveAbsence instead of a private ENOENT/ENOTDIR copy.

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-16 13:33:42 -07:00
NeilandClaude dbe85f6da3 test(orcad): pin that the Node runtime download uses the main HTTP client (#29828)
The deploy's pinned-runtime download (SSH, WSL host, Windows host) passes no fetcher, so it goes
through getMainHttpClient(), which the desktop backs with Chromium's default session: system, PAC
and Orca proxy settings and proxy login apply. Guard that a plain global fetch never replaces it.

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-16 13:29:26 -07:00
Neil 95d41afec2 test(e2e): tolerate startup evaluate and daemon pid-file teardown races (#29831) 2026-10-16 13:17:59 -07:00
Brennan Benson 37beafdd6d fix(native-chat): keep Claude's terminal-only commands in the chat / menu (#29784)
A Claude chat's / menu listed /color, /doctor and /reload-plugins until the
first message ran, then dropped them: the CLI names its terminal-only commands
only in the init frame a turn emits, and the catalog treated that list as a
hide-list. Chat runs all three (/color and /reload-plugins answer locally,
/doctor is an ordinary turn), so the catalog now keeps every command Claude
reports and the menu no longer changes on the first message.
2026-10-16 12:56:51 -07:00
Jinjing bb7e74d236 fix(e2e): keep dedicated specs out of general CI shard plan (#28161)
- Specs with their own CI jobs were also being assigned to general E2E shards, so they could run twice or in the wrong job.
- planE2e now drops dedicated specs before balancing and fails if no general specs remain, instead of producing an empty plan.
- The mobile subscribe spec builds a paired runtime connection from the pairing offer before sending requests, matching the updated remote runtime helper signatures.
2026-10-16 12:55:32 -07:00
Neil 33fd7d3991 fix(settings): show a Keychain unlock on its own instead of leaving the locked notice up (#29815)
* fix(settings): show a Keychain unlock on its own instead of leaving the locked notice up

The server already recovers on its next status read, but Settings read Linear
and Jira status once, so the locked notice stayed until the user pressed Test.
While the notice is up it now re-reads its provider's status every 10 seconds.

* fix(settings): wait for each Keychain status read before the next

A newer status read discards an older one, so an interval could keep
discarding every slow read and never show the recovery.

* fix(settings): poll the Keychain status with an in-flight guard
2026-10-16 12:47:05 -07:00
Neil 1e39482f9d fix(wsl-cli): keep the WSL CLI status check from timing out on a cold or slow-profile distro (#29814)
* fix(wsl-cli): keep the WSL CLI status check from timing out on a cold or slow-profile distro (#29497)

* fix(wsl-cli): run install/remove without the login PATH; a PATH-check timeout reports PATH unknown
2026-10-16 12:45:01 -07:00
Brennan Benson b3048a2b9c Wait for agent readiness without blocking native chat (#27925)
* Finish structured agent readiness before delivering held work

* Adapt permission fixtures to startup lifecycle

* Keep model discovery for chats before their first start

* Use one host readiness promise for provider operations

* Await provider opening in the execution host

* Await Claude initialization in host-owned open

* Observe Claude readiness through the host status

* Keep optional readback behind host readiness

* Recheck launch permission after host readiness

* Preserve the awaited child during permission revalidation

* Record failed owned opening before the first message

* Abort a matching provider open when its child exits

* Cover provider exit during an unanswered host open

* Cover explicit failure facts on a host-stopped child

* Keep startup waits cancellable and preserve operation order

* Keep live choices and delivery order while agents start

* Apply startup picks and let parked messages release operations [skip ci]

* Save a waiting startup pick after Stop ends its opening [skip ci]

* Save choices when startup ends or a replacement is still opening

* Check saved choices separately from Claude startup reports

* Align startup fixtures and preserve observed Claude exits

* Order startup picks before later messages and retain flow regressions

* Keep reviewed lifecycle contracts while pruning duplicate tests

* Save picks after a proven exit without requiring a child

* Type the startup reply fixture against the Claude control contract

* Honor the before-input callback in the startup reply fixture

* Keep startup option menus independent and show the saved choice

* Exercise startup picks across real menus and the runtime

* Keep saved effort visible when picking a model during startup

* Include the running-turn scope in repeated Stop fixture

* Use the current attach contract in import fixtures

* Align carried-message fixture with startup event contract

* Keep launch-only permission picks usable while prior writes wait

* ci: rerun on main with the locale fix

* Preserve goal retries and chat launch selections

* Give launch regression its request identity

* test(native-chat): type the provisional-launch hook's session prop
2026-10-16 12:44:35 -07:00
Brennan Benson 6a760682f7 fix(projects): refuse a folder-to-Git type change that would hide folder workspaces (#29257)
* fix(projects): refuse a folder-to-Git type change that would hide folder workspaces (STA-10638)

A folder project's extra workspaces exist only as metadata rows that the folder listing shows. Changing the project to Git switches it to `git worktree list`, so those workspaces drop out of the next refresh and their saved tabs are purged as if they had been deleted. Only the automatic upgrade checked for this; `orca project setup-update --kind git`, repo updates and clone-into-folder did not.

The check now lives in the one repo write every type change goes through, so any caller gets the same refusal and nothing is written. The automatic upgrade and both clone paths use the same check and keep the project as a folder project.

* test(repos): give the repo handler store mock the metadata read the clone path now uses
2026-10-16 12:41:56 -07:00
Jinjing 7f9b51cf5c Fix background native-chat setup terminal seeding (#29810) 2026-10-16 12:38:17 -07:00
Neil f2fb8ff92f refactor(native-chat): move transcript row context into its own hook (#29809) 2026-10-16 12:31:30 -07:00
OrcaWinandOrcaWin 79b9163951 test: route browser paint policy checks through Bun (#29805)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-16 12:16:56 -07:00
Neil 54bb885bbd fix(terminal): repaint the viewport after an inline image is added (#29713) 2026-10-16 12:15:20 -07:00