Commit Graph
12279 Commits
Author SHA1 Message Date
b128cbbd6a fix(chat): route OMP question cards to terminal (#31440)
Route terminal-backed OMP question cards to the owning terminal and refuse automated ask answer/cancel writes. Preserve structured semantic answers and recognize OMP multi-select metadata.

Verified against the real OMP selector source; automatic semantic card answering remains tracked in #26692.

Co-authored-by: jairuspace <jairusbpace@gmail.com>
Co-authored-by: Daniel Lopez <dafeloru@gmail.com>
2026-10-21 13:04:38 -07:00
Neil 67886127dc refactor(ssh): delete SSH and orcad helpers nothing calls (#31629)
Removes the relay ladder's answered-failure predicate, the unused test-probe
joins, two dead renderer broadcast helpers, the bound-port reader the tunnel
no longer uses, the WSL terminal-verdict mapper the supervisor never called,
and an unused type.
2026-10-21 13:00:00 -07:00
Jinwoo Hong 1db95e308a fix(terminal): stamp ORCA_WORKSPACE_ID on every terminal main creates, not only folder ones (#31608)
The window stamps it (equal to ORCA_WORKTREE_ID) for every workspace kind and agent
wrappers forward it; terminals started from the phone, the CLI or a server's own create
in a git worktree lacked it.
2026-10-21 15:53:36 -04:00
NeilandClaude 7a06b6579b Remote .tsx files parse as TSX and imports no longer resolve to local files (#31624)
* fix(editor): remote models are keyed by an owner authority, so TSX parses and imports stay on one host

Remote editor models were keyed `file:///path#<owner>`. Monaco's TypeScript service names files by
URI, so the fragment hid the `.tsx` extension and imports resolved across hosts. They are now
`vscode-remote://<owner hash>/<host path>`: the file name stays last, and TypeScript treats
`scheme://authority/` as a root, so `..` can't climb into another owner's tree or into local
`file:` models. Monaco keeps closed-file undo for this scheme, and its default label service shows
the URI path, so peek, references and snippet variables show the host's path with no override.
Local models keep their `file:` URIs. A UNC server moves into the path.

A parity test proves exactly the same tab pairs share a model as before; model URIs stay in memory.

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

* fix(editor): key remote UNC models so the TypeScript worker finds them

A remote UNC file was keyed `vscode-remote://<owner>//server/share/a.ts`. TypeScript collapses the
doubled slash, but the worker looks models up by exact URI, so hover and definitions failed. UNC
paths now mark the owner authority (`<owner>-unc`) and keep `/server/share/a.ts` as the path, which
stays distinct from the same POSIX path. Test-only Monaco module types move out of env.d.ts.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-21 12:43:55 -07:00
Jinjing b3f1607c84 Remove directional tab-close actions and tracking (#31599)
* Remove directional tab-close menu actions

* Remove retired directional-close translations

* Remove retired directional-close PostHog tracking

* Preserve legacy app-open schema compatibility without emitting samples
2026-10-21 12:01:27 -07:00
Neil dafed6b345 fix(orcad): hold terminal admission through a server stop; fence rollback and restart (#31418)
* fix(orcad): hold terminal admission from a stop's start until exit; fence rollback and restart counts

Once orcad begins any stop (signal, managed stop request, idle exit), its terminal
admission stays closed until the process exits, past an update census's 60s fence
or a deferring desktop's reopen. Rollback and restart now take their count through
the same fenced recount the update uses, reopen admission when they leave the
server serving, and stop nothing once that count may have outlived its fence.

* fix(orcad): check the rollback's stop deadline after its transaction write

* fix(orcad): rollback and restart recount under the host lock; a reopen never wakes a server

Only the lock holder closes or reopens a server's terminal admission, so a
rollback or restart that loses the lock can no longer clear another update's
fence. A reopen skips the serving check, so it never starts or recovers a
stopped server, and a stop that begins before the runtime binds still holds.

* fix(orcad): reopen admission when a rollback's stop was withdrawn

* fix(orcad): keep admission closed after a stop that may still be on its way

* refactor(orcad): route update, rollback and restart through one fenced stop

runFencedStop is the single count, close, stop, reopen-on-abort sequence. The
update's activation, the rollback and the restart each supply only their own
refusal, journal step and stop; the reopen and deadline rules live in one place.
2026-10-21 11:57:39 -07:00
Jinwoo Hong 5d713eb41d fix(session-search): Project search works for projects with hundreds of worktrees (#31603)
* fix(session-search): Project search works for projects with hundreds of worktrees

A Project-scope search showed "The search index is not ready yet" on a fully
indexed computer when the project had ~500 or more worktree paths outside a
shared workspace folder. Each scope path added an OR arm to the session filter,
and past ~495 arms SQLite refuses the statement ("Expression tree is too large
(maximum depth 1000)"); the desktop then reported the thrown search as not-ready.

The scope ranges now travel as one JSON parameter read through json_each, so the
statement has the same shape for any number of paths and each path is still a
seek on sessions_cwd_key. A failed search is also logged before it is reported
as not-ready.

* refactor(session-search): one scope subquery with one parameter; keep lone-surrogate scopes matching

One json_each join with the exact-or-range ON plans as a MULTI-INDEX OR seek,
so the UNION ALL and second bound copy go. Scope keys are made well-formed
before JSON, since a bound key stores a lone surrogate as U+FFFD and JSON
would decode it to other bytes. The plan test now also catches a scan of the
aliased inner table.
2026-10-21 14:56:38 -04:00
Jinwoo Hong 2b1da76d70 fix(mobile): a windowless server warns about unwakeable agents only when the workspace really has slept agents (#31607)
* fix(mobile): warn about unwakeable agents on a windowless server only when the workspace really has slept agents

A running ('live') or quit agent's resume record made a windowless server tell the phone
'Open Orca on the host to wake sleeping agents.' Count only real sleep records, using the
shared isPaneSleepRecord predicate.

* refactor: move the headless wake check into a shared hasSleptAgentToWake predicate

* docs: say a live or quit record is resumed by its own pane

* test: build the headless wake report store from the shared partitioned fixture, without casts

* test: make a host-less session read miss the record, so the SSH partition test proves the named-host read
2026-10-21 14:55:06 -04:00
Kelvin Amoaba 3b02945afe fix(native-chat): floating-panel chats started from a phone show on the desktop (#31364) 2026-10-21 18:32:31 +00:00
Kelvin Amoaba 52f6960751 fix(mobile): closing the tab a phone shows returns it to the tab it viewed before (#31365) 2026-10-21 18:28:38 +00:00
Kelvin Amoaba 22154de422 feat(mobile): tab strip shows terminal and chat agent state, reveals the next tab, renames on double-tap (#31372) 2026-10-21 18:27:30 +00:00
Kelvin Amoaba cdb032e83f fix(mobile): New Tab drawer reopens with its agents and refreshes them when desktop does (#31367) 2026-10-21 18:26:42 +00:00
Kelvin Amoaba e3ae41bd08 fix(session-history): Pi and OMP keep their own agent on a transcript they both list (#31377) 2026-10-21 18:23:38 +00:00
Kelvin Amoaba ba8f237ade fix(mobile): phone terminal and chat tab names match the desktop (#31384) 2026-10-21 18:23:12 +00:00
Kelvin Amoaba dde7bf8533 fix(runtime): renaming a terminal no longer hides its running agent (#31428) 2026-10-21 18:22:37 +00:00
Brennan Benson 3d6a945b54 Mobile chat composer uses desktop's placeholder (#31425)
* Mobile chat composer uses desktop's placeholder

The phone composer said "Message Codex" (or "Message, @files, /commands") while desktop says "Ask anything, @ to mention files, / for commands". Both now read the one string from the shared native-chat copy table, so they can't drift again.

* Ship the shared composer placeholder in the runtime English catalog

* Keep the composer placeholder beside, not inside, the turn-status copy

The turn-status table maps one-to-one onto the status.* catalog keys; the placeholder is composer.placeholder, so it gets its own constant and its own catalog check.

* Read the composer catalog without a type assertion

* Read the composer placeholder from the typed catalog directly
2026-10-21 11:16:35 -07:00
Brennan Benson d4a7494c47 A Codex chat shows the turns made outside it (#30883)
* feat(native-chat): a Codex chat shows the turns made outside it

When a Codex chat's agent opens its conversation, Orca reads Codex's own saved
turns and adds the ones the chat doesn't have, after the chat's rows, behind a
quiet "Continued outside this chat." divider. Turns are matched by Codex's own
turn ids, read whole before anything is written, and never re-imported; a chat
imported from a transcript gets only turns started after the import. A failed
or partial read adds nothing and the next open tries again.

* chore: remove accidental package manager lockfile churn

* test(codex): expect restored turns with optional timestamps

* test(codex): run catch-up SQLite fixtures under Node

* Make Codex history catch-up atomic and preserve adoption origin

* Keep imported Codex history separate from send delivery

* Derive Codex history reconciliation from saved journal facts

* Validate saved Codex turn identity before planning its notice

* Align Codex catch-up fixtures with current journal contracts

* Check oversized item echo declarations by their saved identity

* test: retain Codex catch-up flow and concrete regressions
2026-10-21 10:57:20 -07:00
Brennan Benson 0985d6f1a7 fix(sidebar): a worktree card fetches its Linear issue again only when the issue or its source changes (#30904)
The card rebuilt its Linear source context on every workspace write, and both
card Linear effects listed that object as a dependency, so a comment, agent
status or activity update re-ran them and refetched the issue whenever the 60s
cache had expired. Hold the context while it is equal to the rebuilt one.

Tracked in STA-10988. Same approach as community PR #30090.
2026-10-21 10:48:25 -07:00
Brennan Benson 899ed4124f Create worktree, Create folder and continuation pickers offer each agent as a chat or its CLI, like the "+" menu (#31189)
* feat(agents): Create worktree and continuation pickers offer chat or CLI per agent, like the "+" menu

The Create worktree, Create folder and session-continuation agent pickers now
list the same rows as the tab bar "+" menu: chat agents first, each with a
terminal button that picks the agent's CLI instead, then the agents that only
run in a terminal, labelled "(CLI)". A terminal pick shows as "Claude (CLI)"
and starts the agent in a terminal even when chats are the default.
A continuation into a subfolder shows chat as unavailable there.

* fix(agents): picker rows keep the combobox item spacing instead of restyling CommandItem

* chore: drop accidental lockfile churn

* fix(agents): type the surface picker's props and the composer's agent rows

* fix(agents): submit the picker selection captured by each form

* test(agents): include repository identity in continuation fixture

* fix(agents): settle inventory when workspace ownership is unresolved

* test: hydrate workspace owners in quick launch fixtures
2026-10-21 10:44:50 -07:00
Brennan Benson 8174208436 fix(terminal): every start in a deleted folder opens at the workspace root and says so (#30747)
* fix(terminal): start every terminal in the workspace root when its saved folder is gone, and say so

A terminal whose start folder was deleted opened at the workspace root only
when the window started it; the CLI, phone restore, orchestration and other
host starts failed with the raw "Working directory ... does not exist" error,
because the renderer had to opt in with a cwdFallback flag.

The execution host now makes that decision for every start that may create a
process (anything but a known live pane), with no caller flag. The fallback is
reported only when a process was proven started at the requested folder, so a
reattach or a crash restore in its recovered folder never claims "workspace
root", and only then does the saved start folder heal. A recovered folder that
was itself deleted yields to the request's resolved folder.

The notice reaches each caller through surfaces it already has: the window's
terminal line (now also written when a host-created tab attaches, and on a
restored reattach), the agent.launch warning (also for callers that show the
tab themselves), and a toast when a phone tap restores the tab.

* fix: recover terminal startup folders without healing unreadable paths

* fix: preserve saved start folder when revealing fallback terminals

* Reuse saved-tab lookup in cwd persistence test

* Import mobile tab activation result from its contract

* Check startup fallback response without a test cast

* Update expectation to catch up with accepted saved-folder metadata

* Preserve inconclusive WSL directory evidence when saving startup folders

* fix: project restored terminal ids for handle consumers

* test: return void from launch parity cleanup

* test: retain saved folder in shared spawn expectation
2026-10-21 10:21:36 -07:00
Brennan Benson 3086fe238a test(daemon): keep socket backpressure accounting pending through assertion (#31520) 2026-10-21 10:13:58 -07:00
Jinwoo Hong 0eb2403ef8 Main closes, renames, sleeps and starts new tabs' terminals itself, with or without a window (dark, layout-owner build only) (#31571)
* fix(terminal): a CLI or phone close through main keeps a pinned tab, as the window-era close did

* feat(terminal): main applies phone closes, renames, pane titles, sleeps and agent-launch tabs itself, with or without a window (layout-owner build)

* fix(terminal): a tab close through main also ends the tab's dormant SSH relay session; agent-launch tabs main writes keep the window's focus rule, prompt and outcome (layout-owner build)

* feat(terminal): main starts a new tab's first terminal under the window's ids, and the pane attaches to it (layout-owner build)

* test(terminal): tests that counted the window's create calls or publications see main's start of a new tab

* fix(terminal): an account-switch restart binds only into a pane main's layout holds, with no window tree (layout-owner build, L197)

* test(terminal): the new-tab start tests hold no type assertions; the start's ledger row is L252

* fix(terminal): a new tab's pane attaches to any terminal main binds it to, and a failed start keeps the tab's startup

* test(terminal): a Claude account-switch restart's tests draw the pane main holds

* fix(terminal): a launch pane main takes back still tells the window why, which drops its hold and prompt; a rename through main answers the name it stored (layout-owner build)

* fix(terminal): a new tab another owner's terminal won attaches to it, and a failed start's startup never runs twice

* fix(terminal): main's pane close reads its stop result as a type only, so no runtime module loads before its base class

* fix(terminal): a phone's close through main drops the closed rows from what phones list at once, not at the window's next publish (layout-owner build)

* chore(terminal): mark the phone-close row drop interim (ledger L260)

* test(worktree-create): a new local tab's startup counts as run when main starts it (layout-owner build)

* test(worktree-create): record main's starts without a type assertion

* fix(terminal): a tab closed before main declines its start gets no startup back (layout-owner build)

* fix(terminal): the window's new tab starts in main when its own window asks over IPC, which labels itself a client (layout-owner build)

* fix(terminal): a refused close of a pinned tab leaves its running agent launch alone (layout-owner build)
2026-10-21 13:12:19 -04:00
Jinwoo Hong 84a9f995da Sidebar P2: main publishes without waiting on git, and every source announces its changes (inert) (#31513)
* fix(sidebar): land flip-a's switch-on fixes on main, still dark (no flip)

The fixes found while the sidebar ran from main's feed, without turning
that on: ORCA_SIDEBAR_INPUTS_IS_MAIN stays false, and none of flip-a's
stop-gaps come along.

- Main stops counting a closed tab's terminal once it was asked to stop
  and no pane shows it; a failed or refused stop takes the request back,
  and a host out of contact stays unverifiable.
- Main fetches a paired server's rows with the transport's normal bound,
  so a server slower than 5 s still lists.
- A deferred unread raise flips the folder row it was decided for, and a
  read while the owner check is out cancels it.
- Phone: no worktree.ps flash before the feed; a host's last installed
  feed stands in while a fresh read is out; it highlights its own
  last-opened workspace and a fold never scrolls; projects file under
  their project group with the desktop's indent; the collapse spinner
  shows only after 500 ms; "Reopen to update" keeps the rows on screen.
- e2e helpers and journeys for the switch-on suite.

* fix(sidebar): keep an owed relay stop marked, clear a thrown stop, and prune pending unread raises

- A stop-and-wait that throws takes its stop request back, like one that fails.
- A final stop the legacy relay still owes stays marked, since the relay retries it.
- Pending unread raises are tracked only while one waits, so the map no longer grows.
- The paired-server fallback poll test gives its third fetch a reply and asserts live.

* feat(sidebar): main publishes cached snapshots and announces its own stores; detected, view and live-content domains (inert)

* fix(sidebar): keep links confirmed only by a new git run, no rescan on metadata writes, no phone wait on detected checkouts

* fix(sidebar): rescan the workspace rows when the Windows runtime default changes

* test(sidebar): gate the 1000-workspace publish cost on its median

* test(sidebar): name the git-run token type in the identity test
2026-10-21 13:07:39 -04:00
Jinwoo Hong cfd9e9c82c Editor tabs main-first: the window asks main for every editor tab change and draws only main's update (dark, layout-owner build only) (#31438)
* Editor tabs main-first: the window sends each editor action to main and draws editor tabs only from main's frames (dark, layout-owner build only)

With ORCA_LAYOUT_OWNER_IS_MAIN on, editor open (preview replace, open to the side), reopen at
its position, close, close-all, promote, the resolved conflict placeholder, a restored-editor
move and a disk rename are layout commands the window sends before it draws anything. Window
content (open file records, drafts, buffers, the autosave gate) is written at once for an open,
and rekeyed in the same store update as the frame for a move or rename. Main publishes each
non-edit tab's view, keeps views in memory across applies and partition changes, stamps the
tab's time, folds same-file duplicates, and republishes a windowless host's session.tabs after
its own file open. The switch-off path is unchanged.

* test(d3a-2): the buffer view index follows a rename main answers

* test(e2e): the old-phone baseline records a windowless server opening the file, as main does since #31291 (test only)
2026-10-21 12:38:35 -04:00
Jinwoo Hong 92bbb380a6 Sidebar: land the switch-on fixes from the flip branch on main, switch still off (#31466)
* fix(sidebar): land flip-a's switch-on fixes on main, still dark (no flip)

The fixes found while the sidebar ran from main's feed, without turning
that on: ORCA_SIDEBAR_INPUTS_IS_MAIN stays false, and none of flip-a's
stop-gaps come along.

- Main stops counting a closed tab's terminal once it was asked to stop
  and no pane shows it; a failed or refused stop takes the request back,
  and a host out of contact stays unverifiable.
- Main fetches a paired server's rows with the transport's normal bound,
  so a server slower than 5 s still lists.
- A deferred unread raise flips the folder row it was decided for, and a
  read while the owner check is out cancels it.
- Phone: no worktree.ps flash before the feed; a host's last installed
  feed stands in while a fresh read is out; it highlights its own
  last-opened workspace and a fold never scrolls; projects file under
  their project group with the desktop's indent; the collapse spinner
  shows only after 500 ms; "Reopen to update" keeps the rows on screen.
- e2e helpers and journeys for the switch-on suite.

* fix(sidebar): keep an owed relay stop marked, clear a thrown stop, and prune pending unread raises

- A stop-and-wait that throws takes its stop request back, like one that fails.
- A final stop the legacy relay still owes stays marked, since the relay retries it.
- Pending unread raises are tracked only while one waits, so the map no longer grows.
- The paired-server fallback poll test gives its third fetch a reply and asserts live.
2026-10-21 12:24:27 -04:00
Jinwoo Hong de7a2d24f9 Phones get main's tab order, groups and names after every layout change (dark, layout-owner build only) (#31529)
* fix(session-tabs): a windowless layout-owner server lists and pushes main's tab groups and order

With the layout-owner switch on and no window (orca serve, orcad), session.tabs rows came from
the snapshot hydrated from the saved session; main's layout was merged in only when a window
published. A routed reorder or Move to Split reached main's layout but not session.tabs.list or
the phone. Main now merges its layout into the rows it holds on each layout change and before
each list, with the same merge a window publication gets.

* refactor(session-tabs): keep the windowless merge with main's publisher, under the file's line cap

* WIP

* fix(session-tabs): every stored tab row follows main's layout, with or without a window

With the layout-owner switch on, the one writer of the rows phones read now merges main's
layout into every write for a workspace main owns, and main re-merges the held rows after
each layout commit on any host. A CLI rename, create or close reaches phones without a
window publish, and a row main's layout dropped cannot come back from an older snapshot.
A write that main's layout wholly overrides stores nothing, so list calls on settled rows
bump no version and push nothing. A pane's own title now counts as a manual name.

This commit also untracks a local vitest config and a scratch test that the previous
work-in-progress commit picked up by mistake.

* test(session-tabs): a rebuild from the saved session and a CLI rename keep main's groups and names on phones

* fix(session-tabs): the row writer only reorders and regroups; layout commits add main's new rows

With the layout-owner switch on, the one writer of the rows phones read now takes only main's
tab order and groups. It never adds or drops a row and never replaces a terminal id, so a
freshly started terminal keeps its id and rows the exit filters removed stay removed.

After each layout commit, on any host, the held rows take main's new rows, names and order,
keep any row main's layout lacks (closes are the close path's job), and pass any added
terminal through the same exit filters the window path uses. A main binding that runs nowhere
no longer replaces a running terminal's id. Both read main's last projection, so neither
adds a session read per write or per workspace.

A CLI close still leaves its row until the dropped-leaf hook lands; its test is marked as an
expected failure until then.

* test(session-tabs): the row writer's order and a commit merge's keep rules, unit by unit

* fix(session-tabs): a kept row keeps its group, and an unchanged commit pushes nothing

A layout commit's merge placed a row it kept (a tab main closed, until the dropped-leaf hook
removes it) by its row id, but groups list a terminal by its tab, so the row fell into the
active group. Both the writer's reorder and the merge now place rows through one shared
group-placement step keyed by the tab.

A commit now pushes only when the writer stored something. The writer skips a write only
when it keeps the stored occupant, publication and retirement proofs. A terminal row a commit
rebinds also passes the exit filters, a main binding the host saw exit no longer replaces a
running terminal, and a binding main made before its terminal connected reaches the rows when
it connects.

* test(session-tabs): a kept row's place and the no-push commit are separate tests

* fix(session-tabs): a rebuild with a new publication stamp is still skipped; simpler commit merge

The writer's skip compared publication stamps, but a rebuild from the saved session mints a
new stamp from the clock each time, so every rebuild minted a version and pushed. It now
stores only for a different workspace instance or new retirement proofs; an absent one keeps
what is stored.

The commit merge again runs the exit filters only for added terminal rows (they admit any row
the held rows list, so a rebind changed nothing), and a terminal connecting now schedules the
merge itself rather than a separate check, since an unchanged merge stores and pushes nothing.

* refactor(session-tabs): a commit merge pushes after every store again; name the window-path exit test

No reachable case was found where a commit merge's rows differ from the held rows while the
writer keeps the stored ones, so the extra "only when stored" check had nothing to guard and
goes. The exit-rule test now says it covers the window's publication path too.

* fix(session-tabs): a window reload's new publication stamp is stored, not skipped

The writer skipped a write that main's order wholly undid without comparing publication stamps,
so a window reload that republished the same rows under a new stamp kept the old one. The
reconciler then saw its accepted stamp missing and asked the window to republish on every sync,
and a host-built stamp could stay on window rows. The skip now treats stamps as unchanged only
when both are host-built, so repeated rebuilds from the saved session still mint nothing.

The windowed test of a row main's layout lacks also replays the window's older snapshot.
2026-10-21 10:53:40 -04:00
Kelvin Amoaba 6970cad0d3 feat(browser): ask before sites use camera, mic or location (#31158) 2026-10-21 14:45:15 +00:00
Brennan Benson fd8b2dc3a7 fix(splits): dim terminals and chats on the unfocused side like editors (#28952)
* Transfer keyboard focus when a sibling chat pane opens

* fix(native-chat): hand focus to newly opened chat composer

* fix(native-chat): keep tab bar controller within line limit

* fix(chat): use terminal pane opacity for split chat focus

* test(chat): verify split opacity follows the focused group

* fix(chat): an unfocused split chat dims exactly as much as a split terminal

A split chat was dimmed with the terminal's inner-pane opacity (0.9 by
default) while terminals and editors in an unfocused split group get the
group's 0.95, so chats looked dimmer than terminals beside them. The chat
layer now takes the same shared group dim and nothing more.

* fix(chat): terminals and chats in an unfocused split dim like editors

Editors and tab bars in an unfocused split group were dimmed to 0.95, but
terminals and chats are drawn in a layer outside the group, so they stayed
at full opacity. One shared opacity now dims the group panel and the
retained terminal and chat hosts alike, so all three look the same.
2026-10-21 06:25:32 -07:00
Jinwoo Hong ad269f471c A paired desktop shows a server's browser tab without opening a second page (dark, layout-owner build only) (#31509)
* fix(browser): a paired desktop shows a server's browser tab without opening a second page (switch-on builds only)

With the layout-owner switch on, a server browser tab drawn from the server's layout
reaches the desktop without a saved page handle. Showing it made the desktop create a
new page on the server, so the server went from one page to two.

Before creating, the desktop now asks the server for its browser pages and adopts the
one already named by the tab's page or browser tab id. It creates a page only when the
server has none, when the lookup fails, or when the server gives no list. A pane that
moved on during the lookup adopts nothing. Switch-off builds keep creating directly.

* test(browser): explain the fake runtime call's cast in the page session test

* fix(browser): adopt a server's browser page by the id its layout already names, not by listing pages (switch-on only)

This replaces the tab-list lookup. A tab list misses saved pages a restarted server has
not started, wakes the active saved page as a side effect, and can match a page another
client hosts.

The server's layout already records the page id on the browser tab. Desktop hydration
ignored it whenever the page record had not landed yet, and made up a new id on every
frame build. With the switch on, hydration now uses the tab's recorded page id, so the
desktop row is named by the server's page from the first frame and stays named by it.

A frame-drawn row still has no saved page handle: hydration has none for server-hosted
pages, and the server environment is known only to the pane. So, with the switch on, the
pane probes its row id with browser.tabShow. tabShow also starts a saved page the server
has not opened since a restart. On success the pane stores the handle and streams that
page. On not-found, or any other error, it creates a page as before and closes nothing.
A pane that moved on during the probe adopts nothing.

Why not reorder the server to save the page record before opening the tab: it would only
fix the id, not the missing handle, so the probe is needed either way. The page record
write also depends on the tab main writes asynchronously, so the order cannot be made
certain from the server alone.

* test(browser): explain the lifecycle harness's fake runtime call cast

* fix(browser): create a server browser page only when the server lacks it, and prefer the tab's active page id

The switch-on probe created a page after any tabShow failure, so a timeout or a brief
connection error could duplicate a page the server still runs. Now only a page-missing
answer creates one. Any other error is raised, as the stored-handle path already does, and
the pane shows its stopped state with Reconnect.

Hydration now names a tab's page by its recorded active page before its first page id,
which matches the page the server starts for a saved tab.
2026-10-21 08:32:17 -04:00
NeilandClaude 5dd5294294 perf(orcad): keep the server bundle ASCII so V8 stores it at half the size (#31495)
* perf(orcad): keep the server bundle ASCII so V8 stores it one byte per character

129 non-ASCII characters in regex literals (and three in a String.raw plugin
template) made V8 hold the whole ~11 MB orcad-server.js source as a two-byte
string. Write them as \uXXXX escapes and fail the build if any return.

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

* fix(orcad): keep license comments ASCII and name the module the guard trips on

esbuild gathers legal comments at the end of the bundle; escape that comment so
a dependency's license text cannot fail the ASCII guard, while licenses still
ship with the code. The guard now reports the offset and, from an unminified
rebuild, the module the character came from. A dark-switch build is covered.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-21 04:06:49 -07:00
NeilandClaude e208c4712a perf(wsl-host): open the stdio bridge without re-probing the distro's platform (#31508)
A WSL distro is always a POSIX host and its deploy already detected Linux, so
the bridge skips the second uname round trip (100-150 ms per connect). SSH hosts
still probe exactly as before.

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-21 04:03:12 -07:00
Jinwoo Hong 2eae220b2e refactor(renderer): read workspace records through typed selectors (#31462)
* refactor(renderer): read workspace records through typed selectors

* refactor(renderer): keep only the selectors with readers; type the host owner lookup
2026-10-21 06:29:18 -04:00
Neil abce679f88 feat(accounts): add Claude and Codex accounts on a remote server from a paired client (#31461)
* feat(accounts): add Claude and Codex accounts to a remote server from a paired client

The server runs the login (Claude's pasted-code sign-in, Codex's device
code) and keeps the credentials; the client only opens the page and
relays the code. New capability-negotiated accounts.hostSignIn* methods;
older servers degrade to an update message.

Fixes #20009

* fix(accounts): settle host sign-ins by the login's outcome, pin pairing, guard stdin

Review fixes: a cancel the login refused still reports the added account;
the client pins each flow to its pairing revision; Claude's stdin EPIPE no
longer crashes the host; both sides accept only the provider's sign-in
host; pasted codes reject whitespace; a host-side supersede says so; Codex
cancels its own login handle; opt-in real-CLI test.

* fix(accounts): keep an early Cancel, retry polls through the host's window, scan Claude stdout only

A Cancel clicked before the host answers the start is applied once it does;
the client keeps polling through a short outage until the host would have
ended the flow; Claude's link is read from stdout only; a timeout after the
code was sent keeps its own message.

* test(accounts): name the stdin sender and allow its stream write in the persistence ratchet

* fix(accounts): register each Add Account attempt before its first await

One attempt per click owns the pairing pin, the cancel flag and the host's
flow, so a Cancel during the capability check or the start is never lost;
polls retry only lost answers; the stdout forwarder is removed on cleanup.

* refactor(accounts): drive the client sign-in as one explicit state machine per server and provider

checking -> starting -> following -> settled, with Cancel as a flag the run
loop delivers once the host names its flow and re-asks after any answer
until the host takes it. A second Add joins the running attempt; a Cancel
the server could not be told about is surfaced; retry policy tested.

* fix(accounts): retry a lost start answer, and report a cancelled sign-in as cancelled however it ends

* fix(accounts): Cancel reaches the server a sign-in runs on, and a typed code stays with its flow

The host picker can move while a sign-in runs: Cancel now targets the
running server sign-in (or the desktop's own login when none runs), and
the pasted-code form is keyed by server and flow.

* fix(accounts): run at most one server sign-in per provider, so Cancel has one meaning

* fix(accounts): the server sign-in prompt owns its Cancel

Cancel for a server sign-in now lives in the prompt bound to that server,
which also reappears with its Cancel when Settings reopens mid-sign-in; the
header Cancel is only for this desktop's own login.

* fix(accounts): hide the desktop-login Cancel while a server sign-in runs; narrow the prompt's status

* fix(accounts): keep the desktop-login Cancel in local scope even while a server sign-in runs

* fix(accounts): the header Cancel follows the running addition, wherever Settings now points

Each Add Account records where it is signing in. Cancel stops that
sign-in, on a server or this desktop, and is hidden only where that
server's own prompt already offers Cancel.
2026-10-21 03:16:45 -07:00
Neil c149395c20 fix(remote-terminal): repaint an idle full-screen program when a reconnect can't image its pane (#31482)
* fix(remote-terminal): repaint an idle program when a reconnect can't image its pane

After an SSH reconnect the pane is repainted from the host's screen
snapshot. When the host cannot give one (too large for the replay limit,
no buffer, unanswered, overflowed), the pane only printed a loss banner and
an idle full-screen program that writes diffs kept its pre-disconnect frame
until the user resized.

The two places a remote pane declares it has no host image now ask the
program to repaint through one choke point: a one-row viewport claim and
back, which every TUI honours (Node's tty ignores a same-size SIGWINCH).
It respects a phone or another desktop holding the size, skips hidden
panes, and puts the row back even if the pane unmounts mid-nudge.

Fixes #26887

* test(e2e): idle full-screen program frame after a managed SSH outage

* test(e2e): wait for the pane before typing in the idle-frame spec

* fix(remote-terminal): pick the redraw height on the host's clamped grid

* fix(remote-terminal): wait for the host's grid before putting the redraw row back

* test: give transport mocks a connection status for the redraw gate

* test(e2e): keep the orcad idle-frame proxy typed for the e2e ratchet

* fix(remote-terminal): let a new PTY's redraw supersede a swapped-out nudge

* test: keep new transport-mock lines free of type assertions

* ci(e2e): route the managed-SSH idle-frame spec to its sources
2026-10-21 02:48:21 -07:00
Jinwoo Hong bbc8303b47 A running agent's resume note is not a sleep, so switch-on builds report it connected (dark, layout-owner build only) (#31485)
* fix(layout): a running agent's resume record is not a sleep in switch-on builds

The shared layout load put every pane resume record into leaf.sleeping, so main's
terminal index read a live or quit capture as a slept pane and terminal.list
reported running agents as disconnected and unverifiable. Records of slept panes
(isPaneSleepRecord) now load to leaf.sleeping and running agents' records to
leaf.resume; both save to the same sleepingAgentSessionsByPaneKey entry, so the
disk format is unchanged. The disk-field census names both holders, and a model
rule refuses a pane holding both.

* fix(layout): a waking start wakes only slept panes; a recordOnly wake drops either record

* fix(layout): main's shutdown reconnect facts read a running agent's resume record as awake
2026-10-21 05:03:22 -04:00
Neilandsojiro-o 29a1446962 fix(i18n): clarify Japanese phone control and connection wording (#31467)
Adopt the nine Japanese wording corrections from community PR #29248. Clarify smartphone control, manual restoration, and mobile data connections.

Closes #29247.

Co-authored-by: sojiro-o <53679703+sojiro-o@users.noreply.github.com>
2026-10-21 01:41:33 -07:00
Leo(오영택)andJaeyoung22 582efa7989 fix(i18n): correct Korean sleep with descendants wording (#31456)
* fix(i18n): correct Korean sleep with descendants wording

* fix(i18n): keep Korean descendant sleep label compact

---------

Co-authored-by: Jaeyoung22 <89302528+Jaeyoung22@users.noreply.github.com>
2026-10-21 01:38:06 -07:00
Neil a58789d0c1 fix(mobile): keep a Codex chat addressable under Codex's own idle title on headless hosts (#31460)
On a headless host the mobile status builder treated any title without an
agent marker as the shell reclaiming the pane. Codex's idle title is its
project name, and since OSC 7501 program status gives the pane a PTY state,
a Codex turn with no tool call ended with the hook's transcript address
withheld, so the phone showed "Can't find this conversation yet".

Only a shell or management title now retires the hook identity, matching the
renderer projection. Refs #31136.
2026-10-21 01:22:04 -07:00
NeilandWooseong Kim 26bf1286c9 fix(terminal): repaint desktop after phone fit ends (#31451)
Fixes #14321

Supersedes community PR #14787. Original contributor credit is preserved below.

Co-authored-by: Wooseong Kim <innocarpe@gmail.com>
2026-10-21 01:17:23 -07:00
Neil 7f21cc91b8 fix(codex): Codex copy from a remote pane reaches the viewer clipboard (#31453)
* fix(codex): copy from a paired client's or WSL Codex reaches the viewer's clipboard

* test: type the Claude launch fixture
2026-10-21 00:58:03 -07:00
Brennan Benson 920a033928 fix(native-chat): typing fast after focus moves keeps keys in order (#31203)
* fix(native-chat): typing fast after focus moves keeps keys in order

Typing a burst right after focus left the composer (Move Tab to Split, a
paired chat, a click on the transcript) could scramble the first letters:
"Reply exactly" came out as "Rply exactly...e".

The first key was routed into the composer programmatically: focus moved
at once, but the caret was set a frame later from a position computed
before the key. The next keys landed natively, then that late caret write
pulled the caret back. Quote "Add to chat" and picker completion had the
same deferred caret write.

Each programmatic write now puts the text and caret into the editor in one
synchronous step, read from the editor's live value, then focuses it. No
write happens while an IME composition owns the field.

* fix(native-chat): retain saved chips after synchronous composer edits
2026-10-21 00:42:44 -07:00
Neil 9577c531c7 fix(ssh): a managed host's 'no desktop is sharing its browser' error names the browser routing setting too (#31444)
* fix(ssh): the host's 'no desktop is sharing its browser' error names the browser routing setting too

The desktop withholds the browser bridge from a managed SSH host whose pages browse from the
desktop's own network, but the host's CLI was told only to turn on the per-host CLI control
switch, even when it already was on (#31421).

* fix(ssh): name the per-host routing opt-out too, and guard the labels against UI copy drift
2026-10-21 00:28:46 -07:00
Neil c7f722bbdf fix(pairing): offer only addresses the runtime listener answers (#31447)
The runtime WebSocket listener widens to 0.0.0.0, which never accepts
IPv6, yet the Connection address picker and the default pairing address
offered global IPv6 interfaces, minting links nothing answers. Filter the
interface list by the family of the host the listener binds when serving
the network.

Reported-by: @fongyuan-2011
2026-10-21 00:11:21 -07:00
Neil 5fc6cb46b0 fix(plugins): a development plugin reloads an edit saved while its watcher was starting (#31394)
* fix(plugins): a development plugin reloads an edit saved while its watcher was starting

Orca scans a development plugin folder, then starts watching it. The watcher goes live
about a second later and reports only changes made after that, so a save in between was
never picked up and the plugin kept running the old files. Once a development root's
subscription goes live, schedule one catch-up refresh through the existing debounce.

* test(plugins): count a replaced dev root's catch-up refresh regardless of timing
2026-10-21 00:02:39 -07:00
Brennan Benson 92732bb0f9 fix(native-chat): bring back Working-only-while-held (#30134) without shutting down Claude chat workers (#30577)
* fix(native-chat): a Claude chat worker keeps running after Claude acknowledges its task

Claude's adapter dropped an echoed send from the sends it holds before
reporting the send's answer to the host, and emitting the echo's turn
can publish at once. That publish read the still-pending send as held
by nobody, so it read as "couldn't confirm": orchestration worker-start
then closed the new Claude worker, and every other answer wait and
client saw a one-frame doubt.

A frame's settled sends now stay held until their answers are reported,
when the host's handover window takes them over; a Stop's rejected
sends are released and reported one at a time, as Pi and ACP already do.

Tracked in STA-11160.

* test(native-chat): assert the settled send's dispatch state through the send result

* Reapply "fix(native-chat): Claude, Pi and ACP chats are Working only while the agent holds the send (#30134)" (#30572)

This reverts commit 3ef5a46061.

* fix(native-chat): a Claude send stays held until its settlement reaches the host, on the reapplied held-sends read

* fix(native-chat): a Claude send stays Working, not in doubt, while a Stop is still ending its CLI

* test(pi): drop the removed idle-release dependency from the adapter fixture
2026-10-20 23:46:45 -07:00
Jinwoo Hong cfdc347bd8 Layout-owner build: main shows live terminals that lost their tab, at the same moments the window did (dark) (#31430)
* feat(layout): main places a swept workspace's live terminals; the window sweep only reads (switch-on builds only)

The window's activation sweep (first visit, an empty or sleeping activation, a phone wake) now
asks main, through its existing terminal.list call, to place that workspace's unshown live
terminals before listing them. With the switch on the sweep writes nothing to the window store and
counts a pane main names as shown, so it no longer seeds a fresh shell beside a terminal main just
placed. Main's "still binding" check also covers a registration in flight.

* fix(layout): a sweep's placement pass holds back only its own workspace's first surface (switch-on builds only)

* test(layout): switch-on activation-gate and census-call tests read main's listing, not window binds
2026-10-21 02:33:56 -04:00
Neil 2bf77e605b fix(ssh): draw every host list through one bounded, searchable host picker, so a 13k-host ssh config can't crash any of them (#31426)
* fix(ssh): draw every host list through one bounded, searchable host picker

A generated ~/.ssh/config can import 13k+ SSH hosts, and several surfaces drew a
row per host: Settings > General workspace directory 'Apply to', Notifications >
Machines, the sidebar and Agents Hosts submenu, the composer run-target picker,
the Add Project host selector and remote step, the skill-install machine pickers,
Settings > Servers SSH host picker, repository 'Add to another host' and the
automation host field and filter.

They now share components/host-picker: boundHostList (pinned hosts plus a ranked,
capped match list), HostCombobox for single choice (a Command in a Popover that
mounts nothing until opened, replacing Selects that mount every item), and
useBoundedHostRows with HostListSearchField and HostListOverflowNote for
checklists. A host-list-render ratchet stops new JSX from mapping straight over a
host collection.

* fix(ssh): host lists keep room for search results, menu search is keyboard-reachable

- boundHostList gives pinned hosts and other matches separate limits, and narrows
  pinned hosts by the query, so a large selection never hides what the user types.
- In a dropdown, the host search is a menu item: arrow keys reach it, focus moves
  into the input, and arrows leave it again.
- The Add Project host picker hides its setup actions while searching, so Enter
  picks the matching host.

* fix(ssh): re-picking the current host in a host picker is not a change

The Selects these pickers replaced suppressed same-value changes; the skill
install form clears its workspace and target on a machine change.

* chore(ci): let the shared ratchet runner pick up the host-list render ratchet

main now discovers every check-*-ratchet.mjs through run-ratchets.mjs, so the
separate workflow step would run it twice.

* fix(ssh): host lists rank pinned matches and keep input order; label and scroll fixes

- boundHostList ranks pinned and other hosts by the same match order before
  cutting each to its limit, then keeps input order among equal ranks, so a
  muted or checked exact match is always reachable and clicking a row never
  moves it.
- The Servers SSH host picker is named by its form label again.
- The sidebar Hosts submenu scrolls its host rows instead of running off screen.
- The composer's overflow note sits outside the listbox.
2026-10-20 23:33:36 -07:00
Brennan Benson 505d6719c0 Refuse a workspace parent that would close a loop, even when the worktree cache is cold (#29340)
* fix(worktree): refuse a parent loop even when the worktree cache is cold

The parent cycle walk read each workspace's current instance from the
resolved-worktree cache, which the reparent itself invalidates, so it only
knew the two endpoints and accepted A->B->C->A. Read saved worktree
metadata instead.

* fix(worktree): fall back to locator metadata and tolerate loops saved earlier

* fix(worktree): skip another host's locator metadata in the parent loop walk

* fix(worktree): read loop-check identities from a read-only, ownership-scoped view

The per-host metadata accessor migrates a hostless legacy row into the
queried host, so the loop check could adopt another host's worktree and
refuse a valid parent. Read one read-only host snapshot instead, counting
a row only when its project is registered on that host (the resolved
list's ownership rule).

* fix: import host-qualified worktree meta helpers once

* test: match main's host-targeted lineage write contract
2026-10-20 23:23:02 -07:00
Brennan Benson 7dfcfbf735 fix(codex): resume native chats by conversation ID (#31041) 2026-10-20 23:11:28 -07:00
Brennan Benson 786a40ef1f Custom tab shortcuts work right after a keyboard worktree switch (#30941)
* fix(shortcuts): app shortcuts run before the terminal, whatever order they mounted in

App shortcut owners and terminal panes both listened on window capture,
so React's effect order decided who saw a chord first. After a worktree
switch the new pane registered before the tab shortcut listener and
turned a user-bound Cmd+Right into Ctrl+E; clicking a tab flipped the
order back. A keyboard router installed before React renders now runs
every app owner, in registration order, ahead of any pane listener; a
key no owner claims still reaches the terminal's own handling. Held tab
chords are claimed on every repeat but switch once.

Tracked in STA-11205 (same bug as STA-4602).

* test(shortcuts): type the router mock with key event names

* fix(shortcuts): install the keyboard router in the web client before it renders

* test(shortcuts): floating panel specs read the routed key listeners from the window stub
2026-10-20 23:10:59 -07:00