Commit Graph
12248 Commits
Author SHA1 Message Date
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
Brennan Benson fc8439e05f fix(notifications): a pane with hooks gets its finished banner from its hook turn only (#30620)
* fix(notifications): tie a completion banner to the hook turn it announces, not a 10 s window

A title-only working->idle edge (Claude's /btw side question fires no hook) raised a
generic "Task complete" banner because the pane's hook row was older than 10 s. On a
hook-reporting pane the hook row now decides: a row not yet announced supplies the
banner content however old it is; an already-announced row means no turn ended, so
no banner. Hookless agents keep the generic title fallback.

* fix(notifications): a pane with hooks gets its completion banner from its hook turn only

The title lanes (mounted pane and parked watcher) announced a finished task on every
working->idle title edge, even on panes whose agent reports turns through hooks. A title
spin that ends no hook turn (Claude's /btw side question, Codex's /compact) raised an extra
banner that could only be filled with the workspace name and tab title.

On a pane the hook lane is tracking, with a settled Done row from the same agent, the title
lane now defers before it claims the turn, so the hook lane's banner (with the last message)
is the only one. A BEL in the same burst waits on the hook lane's pending Done. Hookless
agents, restored rows, hooks switched off and the post-restart re-arm keep the title banner.
This replaces the previous commit's announcement memory in the dispatcher.

* fix(notifications): hook-owned turn ends settle pane bookkeeping and announce re-entered Stops

- A title idle edge that defers to the hook lane now settles the pane's turn, so quitting the agent after a /btw no longer raises a generic "Task complete" banner.
- A BEL held while the hook lane's Done is pending is superseded by that Done at fire time instead of being held until the next turn start.
- A Stop re-entered after a blocking Stop hook carries its new final message in its identity, so the hook lane announces it instead of deduping it against the first Stop.
- Title lanes ask the hook lane whether it can announce the turn end (live, agent evidence, not re-arming) instead of copying its conditions, and fall back when the agent's own hooks are switched off.
- The hook lane's non-quiet Done forwards its own snapshot, so its banner content no longer depends on the stored row's age.
- The whole-flow test now drives the real pane wiring, the real hook lane and the real dispatcher.

* fix(notifications): the host marks each real turn end, so a repeated status can't raise a second banner

The renderer told a Stop re-entered after a blocking Stop hook from a
restatement by its final message. The message also changes when the
listener's cache resets (a session reset, then an idle reminder), so a
restated finished turn raised a second, empty "finished" banner.

The execution host now classifies each provider's own turn-end event by
name and stamps an optional `turnEndedAt` on the status row. Idle
reminders, session events, replays, child re-emits and result retries
restate it; a turn-end event on an already finished row is a new end only
when the agent says it re-entered (`stop_hook_active`). Local hosts and
SSH hosts (orcad) mint it in `attachStatusTiming`; the WSL relay mints it
at admission and sends it on its envelope. The field is persisted,
forwarded over IPC and the paired-server session projection, and the
renderer's done identity is `turnEndedAt ?? stateStartedAt` (after the
existing `turnCompletedAt` precedence). `stateStartedAt` and every other
clock are unchanged.

Paired servers that predate the stamp advertise no
`agent-status.turn-ended-at.v1` capability; their panes keep the
terminal-title completion fallback exactly as before.

* test(notifications): type the old-server pane setup in the completion flow test

* fix(notifications): a blocking Stop's final banner reaches the user; /compact and quitting an idle agent stay quiet

Real-agent QA on the hook-only completion design found three gaps:

- A Stop re-entered after a blocking Stop hook could be dropped twice
  over: inside 1 s of the first banner by the replay guard (a new host
  turn-end stamp is now a real end), and by main's 5 s per-workspace
  desktop cooldown. A banner with the same notification id and changed
  text now replaces the one already shown instead of being dropped;
  every other cooldown rule is unchanged.
- On a mounted pane, a hook-owned idle edge (Codex /compact) no longer
  claimed a completion, so the burst's BEL went out as a Bell banner. The
  deferred idle edge now absorbs that BEL like a claimed completion did.
- Quitting an idle agent after a title spin raised a generic process-exit
  banner. On a pane whose hook lane owns turn ends and whose last turn is
  done, an exit ends no turn; a crash mid-turn still gets its banner.

* test(runtime): the projection reference keys on the host turn-end stamp too

* test(relay): restore the Copilot retry test while CI isolates a runner error

* test: key the hot-path projection reference on the turn-end stamp; relay retry restates the end

* fix(notifications): host turn identity per agent session; keep later blocking-Stop banners past main's cooldown

- The execution host (local main and the WSL relay, one shared reducer) now tracks which turn an
  agent session is in: Codex by its own turn id, Command Code by its transcript prompt position,
  every other agent by its prompt-opening hook. A turn end mints a new end when its turn differs
  from the last ended one, so a Codex turn woken by agent mail and a text-only Command Code turn
  get their banner; duplicates, replays, tool reopens and result retries restate it. The record
  is saved with the status row (validated on restore) and never published.
- Main's desktop and mobile notification cooldowns are back to main's code exactly.
- A later end in the same done segment (a Stop re-entered after a blocking Stop hook) that lands
  inside main's cooldown is held, latest only, and sent once after that cooldown through main's
  unchanged gates; the renderer learns main's answer to anchor it.
- One pane check gates every changed pane behavior: the pane's bound PTY runs on a host that
  stamps turn ends and its agent's continuation signal is verified (Claude, Codex). Every other
  pane, and an unbound PTY, keeps main's behavior.
- A hook-owned idle edge absorbs its burst's BEL for main's full 1.5 s; exits and replacements
  ask the owner check with the exiting agent; a title spin on a hook-owned pane opens no turn.

* test(notifications): run the whole-pane harness through main's shared desktop cooldown, not main's service module

* fix(notifications): decide hook ownership from how the host identified each turn end

The execution host now labels every classified turn end with one optional
disposition beside its end stamp, by the same rule for every agent:
identified (an unused prompt opening, a new native turn id, or a verified
re-entered Stop) mints a new end; a same-turn repeat is a proven duplicate
only when the agent supplies both a turn id and a verified continuation
flag; anything else is ambiguous and never mints. Replays and retries
restate the decided answer, and local and relay hosts carry an end only
within a done segment.

The renderer replaces the Claude/Codex pane gate with one evidence
decision: identified ends are hook-owned, ambiguous ends keep the title
lane's role, and rows without a disposition (older hosts, unclassified
agents) keep the previous behavior exactly. A title working edge on a
hook-owned pane keeps its turn bookkeeping and only the hook receipt; an
owned idle edge settles after the existing max wait, or announces through
the title path if the end turned out ambiguous. Idle and working title
edges resolve the same owner (the title's agent, else the foreground one).
The held later banner for identified continuations is unchanged.

* test(notifications): give the whole-pane harness's host event its local connection id

* fix(notifications): a settled cancellation ends the open turn, so its late Stop folds as on main

A cancel Orca inferred from Esc or Ctrl+C writes a done row with no turn-end event. The host reducer now completes the current turn key on that settled cancellation, so the same turn's late Stop is a repeat (ambiguous, no new stamp) instead of a second identified end that requested a second banner.

* fix(notifications): a settled cancellation ends the open turn on every lane, so a relay's late cancel end restates none

The desktop restates a WSL relay's end as decided. The relay never learns a
cancel the desktop inferred for a non-Claude agent, so Grok's own late
stop_cancelled reached the desktop as an identified end and gave a second
banner. One predicate now decides both lanes: after a settled cancellation
that carried no end, a restated end keeps the shown one, and a child event
carrying the cancellation also ends the open turn. The relay's own cancel
row is the main agent's, never a child's.

* fix(notifications): a relayed done segment shown without its own end keeps showing none

The restatement fold tested the previous row's cancel verdict, so the row it left behind (a late
plain stop) no longer matched and the relay's next restatement brought its end back. Fold on the
row's turn-end fact instead, which the folded row satisfies again.
2026-10-20 22:59:57 -07:00
Brennan Benson ae9bc8d120 A slept native-chat workspace reads asleep and hides (#28457)
* fix(sidebar): a slept chat workspace reads asleep and hides

* fix(sidebar): a wake during Sleep's teardown keeps the workspace awake

* test(sidebar): give the activation-race mock store the chat tab map Sleep reads

* feat(native-chat): the host marks the chats an explicit Sleep stopped

The fact "the user's Sleep put this chat to rest" now lives on the host
that runs the chat, on its session record, instead of in the window.

- New runtime capability agent-session.workspace-sleep-mark.v1 and an
  optional markAsleep on agentSession.sleepWorkspace (strict params, so
  only hosts advertising the capability are sent it).
- The Sleep close decides each chat's receipt inside the same per-session
  step that stopped it; with markAsleep it writes sleptAt in that step's
  record write, so a chat is marked exactly when it is in the receipt.
- The next send the host hands over clears sleptAt, whichever client sent
  it; the idle sweep and plain stops never mark.
- sleptAt rides the status summary (optional field), its equality, and
  the retained summary of a closed chat, so it survives restart through
  the persisted tab index.

* refactor(sidebar): read a slept chat from its host's mark, not a window record

The window no longer keeps its own saved record of which chats Sleep
stopped (explicitlySleptChatsByWorkspace). That record had to mirror every
host event that should end it (sends, replaced conversations, renames,
profile and WSL moves, removal, wakes during teardown), and each missed
mirror was a bug. The host now owns the fact (previous commit).

- Delete the record, its schema, store slice, workspace-session field
  registrations, moves, renames, purges, migrations and hydration, and the
  renderer clearing hooks. Those files are back to main.
- Opening a workspace no longer wakes its slept chats (product ruling):
  drop the chat Sleep-attempt tokens and their set-active-worktree hook. A
  slept chat wakes only when a send reaches its host, from any client.
- The status bridge mirrors each chat tab's published sleptAt into a
  store map keyed by the chat's pane key, dropped when its projection
  unmounts and never saved; a chat whose host runs it again is not slept.
- The asleep derivation, hide filter, palette, board and context menu read
  that map. Explicit Sleep sends markAsleep only to the local host and to
  hosts last probed with the new capability; a project move never does.

* fix(native-chat): preserve host-owned Sleep across fallback and legacy tabs

* fix(native-chat): clear Sleep when a new send is admitted

* chore: restore incoming documentation after merge hook formatting

* fix(native-chat): let admitted sends supersede pending Sleep receipts

* fix(sidebar): wait for host Sleep state before showing restored chats

* fix(sidebar): retain host Sleep answers when chat projections remount

* fix(native-chat): clear Sleep when queued drafts become sends

* test(sidebar): cover unknown and known chat Sleep state on the board

* test(vault): expect both host status capability checks

* test(native-chat): consolidate Sleep behavior coverage

* fix(native-chat): commit Sleep wake with send admission

* test: pin visibility while chat host is unidentified

* test: consolidate workspace sleep regression flows

* test: keep status feed regression within renderer boundary
2026-10-20 22:42:56 -07:00
Jinjing c9d34bba1a Open new Markdown changes in the rich editor (#31399) 2026-10-20 22:31:24 -07:00
NeilandClaude 546304fa11 Discard pauses autosave of the file in every workspace; read-only Markdown/CSV tabs can't overwrite unsaved edits (phase 1b-3) (#31413)
* Editor: git Discard pauses that file's autosave in every workspace; read-only markdown/CSV never pushes over edits (phase 1b-3)

- A Source Control discard holds autosave (and waits for in-flight saves) for every tab of that
  file in every workspace, keyed by its model save key, until the discard finishes or fails; the
  quiesce now returns the release, called in a finally. Watcher notices don't release it. A 120 s
  backstop only shows the changed-on-disk banner on dirty tabs and never reopens autosave. This
  closes the race where the compare fallback on older hosts reads disk just before git restores it.
- A read-only tab renders raw Monaco source whatever its type, so it counts as a code view: a clean
  read-only .md/.csv tab can no longer push disk text over a writable tab's unsaved edits.

Only removes or delays writes. One autosave timer per model moves to 1b-4.

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

* Editor discard hold review: release on a failed quiesce, cover unstaged diffs, re-show an overdue banner (phase 1b-3)

- A quiesce that throws (e.g. an unresolved CSV cell) releases its hold and rejects, so the discard
  reports the error instead of hanging with autosave off; a bulk discard frees the holds the other
  paths took.
- Holds key on the physical file (host + path), so an unstaged diff tab of the file in another
  workspace is paused and drained too; save-queue keys are unchanged.
- Past the backstop, a dirty held tab whose banner was dismissed gets it back, so the pause is
  never silent. Taking a hold no longer re-syncs; the save-time check already refuses.

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

* Editor discard hold: key the physical file with the 1b-1 path rules, not the client's URI rules (phase 1b-3)

The hold key went through the Monaco model URI, which normalizes paths by the client platform, so
on a Windows client `/repo/a\b.ts` and `/repo/a/b.ts` on a POSIX host shared a key. It now uses
the owner key plus `editorBufferOwnerPathKey` (new, in editor-buffer-identity), which applies the
1b-1 rules: POSIX backslashes kept, separators folded only for proven-Windows paths, UNC server
case, and WSL aliases on the local Windows client. Same on the tab and the discard-path side.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-20 22:17:18 -07:00
Brennan Benson b2370f1c6e fix(terminal): hidden tabs reconnect to sessions that survived a restart (#31254)
* fix(terminal): hidden tabs reconnect to sessions that survived a restart

pty:hasPty answered from the provider's attached-this-run set, so right
after a restart every surviving session read as exited and #29459's
hidden-agent hold kept live hidden tabs disconnected until shown. On an
attached-set miss, ask the host through the provider's existing liveness
probe; providers without one (the in-process provider, its own sole
owner) keep their answer.

* test: remove liveness fixture type assertion
2026-10-20 22:12:27 -07:00
Jinwoo Hong 0a9980b141 fix(terminal): a saved terminal folder on a dead network drive no longer freezes the Orca window (#31381)
* fix(terminal): a folder on a dead network mount fails the terminal start instead of hanging it

Bound the local folder checks a terminal start runs (folder-workspace admission and the
worktree-folder cwd fallback) with a 5s deadline, and make the fallback check async so it
can no longer block the main process. A timeout refuses the start with the existing
folder_workspace_path_unavailable code.

* refactor(terminal): build the folder deadline on the existing working-directory validation helper

Drop src/main/local-folder-stat-deadline.ts. statLocalFolderWithDeadline now lives in
working-directory-validation.ts and reuses its abandon-on-signal waiter and UNC route lane,
so a dead SMB share caps how many folder checks can hold fs threads.

* fix(terminal): keep only the non-blocking saved-folder check; drop the folder-check time limit

Per owner decision, a hung folder waits as before. Folder status and working-directory
validation return to main; the saved-folder fallback check stays async so main never blocks.
2026-10-21 01:04:35 -04:00
Neilandm4air 01562f8bcc fix(ssh): window the SSH Hosts list and search past the Remote Hosts cap, so a 13k-host ssh config no longer crashes the renderer (#31405)
* fix(ssh): the Remote Hosts menu lists a capped set of idle SSH hosts

* fix(ssh): the SSH Hosts settings list renders hosts a page at a time

* fix(ssh): window the SSH Hosts settings list and keep project hosts in the capped menu

Settings > SSH Hosts now windows its rows against the Settings scroller with the
shared VirtualizedList instead of paging 100 at a time, so every host stays
reachable by scrolling or the filter while only the rows near the viewport mount.
The Remote Hosts menu cap rises to 25 idle hosts, fills it with hosts that own
projects first, and the overflow row says to search the rest in Settings.

* fix(ssh): search the Remote Hosts menu's capped idle hosts in place

Web clients reach SSH hosts through this menu but have no SSH Hosts settings
pane, so a link to Settings stranded every host past the cap. The menu now shows
a search box whenever idle hosts are hidden; it narrows them by name, and the
overflow line says how many are left. Also gives the two existing status-bar
store mocks the repos field the menu now reads.

* fix(ssh): typing in the Remote Hosts menu goes to its host search

The search box is not a menu item, so arrow keys can't reach it; letters typed
while the menu has focus now land in the search instead of Radix typeahead.

* fix(ssh): the Remote Hosts search keeps focus and drives its results by keyboard

The host rows are not menu items, so the menu's arrow keys never reach them. The
search box is now a combobox over the connectable results: it keeps focus, arrow
keys move the active result (announced through aria-activedescendant), and Enter
connects it through the same path as the row's Connect button.

* fix(ssh): Remote Hosts search only reaches hosts that match, and keeps the active one in view

Every not-connected row now has to match the search, so Enter can't connect a
failed host the user didn't type. Arrow keys scroll the active result into view.
Also drops restyles on the search Input and fixes the singular overflow default.

* refactor(ssh): find hidden Remote Hosts in a searchable picker, not a search box inside the menu

A search box inside the dropdown kept fighting the menu over focus, Tab, Enter
repeat and IME input. The overflow row is now a menu item that opens a host
picker (Command in a Popover, as the style guide prescribes for single choice
with search). It ranks exact names first so any host is reachable by typing its
name, mounts at most 50 results, and says how many more match. The menu keeps
config order when nothing is capped.

* fix(ssh): closing the host search from the keyboard returns focus to the status button

The picker is anchored, not triggered, so Radix had nowhere to restore focus and
it fell to the page. A keyboard close now focuses the Remote Hosts button; a click
elsewhere keeps focus where the user clicked. Also drops the padding restyle on
PopoverContent.

* fix(ssh): the host search marks the keyboard-selected host with the palette highlight

---------

Co-authored-by: m4air <m4air@Mac.localdomain>
2026-10-20 21:51:27 -07:00
Neilandbbingz e2196403c3 fix(terminal): realign mobile snapshot residual metadata after query stripping (#31348)
Align plain mobile/legacy residual metadata after snapshot-boundary query stripping; preserve genuinely transformed span metadata.

Fixes #9682. Based on the report and suggested correction by @bbingz.

Co-authored-by: bbingz <zzb@gxsmjx.com>
2026-10-20 21:41:02 -07:00
Jinjing d8508da889 fix(ui): clarify continuing conversations between chat and terminal agents (#31402) 2026-10-20 21:36:09 -07:00
Jinwoo Hong 87fe8424eb fix(runtime): a server with no window answers its first terminal create, not 'handle stale' (switch-on builds only) (#31391)
* fix(runtime): a server's new terminal keeps answering under its own handle after main's pane adopts it (switch-on builds only)

* fix(runtime): keep handle branching on the leaf path; only callers with no leaf fallback read the adopted own handle (switch-on builds only)

* fix(runtime): with no window, a terminal's own handle keeps naming it after main's pane adopts it, as before main listed panes (switch-on builds only)
2026-10-21 00:34:08 -04:00
NeilandWooseong Kim 8bec7323ef fix(mobile): preserve Claude chat after renaming its pane (#31358)
fix(mobile): retain Claude chat when its pane is renamed
Distinguish a manual title override from a later shell OSC title, keeping transcript identity and live hook selection while preserving exit retirement.

test: cover restored mobile chat after manual shell-like rename

Co-authored-by: Wooseong Kim <innocarpe@gmail.com>
2026-10-20 21:32:31 -07:00
Brennan Benson b0d873d86a test(claude): isolate real CLI fixtures and cover chat option requests (#31201)
* test(claude): real-claude suite passes on any signed-in machine and current model aliases

Model ids come from the CLI's own model list instead of dated ids, tests that need a SessionStart hook or an effort bring their own project settings, and the supervised-stop test launches and reads its transcript under one Claude home, as acquisition does.

* test(claude): one real-claude flow for the model, effort and permission pickers and the context meter

Drives the live CLI through the production send path (model list, set model, apply effort, read settings), checks the turn's context report, and relaunches into a new permission mode the way Orca changes it. CI only meets these requests through stand-ins.

* test(claude): accept the ran options a send now reports

Since #30561 a Claude send resolves with the options it ran under; the stop test only needs the send admitted, and the options flow checks the ran options are exactly the per-message pick.

* test(claude): an alias pick's dispatch report may omit the model the CLI resolved
2026-10-20 21:25:11 -07:00
Neil d1ed73ba9b fix(orcad): refuse new terminals while an update stops the server (R32) (#29834)
* fix(orcad): refuse new terminals between an update's last census and the stop

The last census before an SSH or WSL update stops the server now closes the
server's terminal admission first. Every spawn lease (acquireWorktreeTerminalSpawn,
which both spawn paths hold across the provider spawn) passes the fence, the
census waits for leases taken before the close, and an update that leaves the
incumbent serving reopens it. A reattach to a session the daemon still runs is
still admitted.

* fix(orcad): fence-exempt reattaches spawn attach-only; reopen admission when no stop was sent

* fix(orcad): stop nothing once the recount's admission fence may have lapsed
2026-10-20 21:23:41 -07:00
Neil b3e6705337 fix(orcad-migration): a sidebar view setting never blocks moving an SSH host to its server (#31374)
* fix(orcad-migration): a sidebar view preference never blocks moving an SSH host to its server

* fix(orcad-migration): keep repo filters on the desktop, keep later acknowledgements; e2e repro
2026-10-20 21:15:56 -07:00
Jinwoo Hong ce71535bbf Layout groundwork (switched off): main starts every split and new pane, the window only attaches (#31258)
* feat(terminal): the window's store shows main's pane start status (layout-owner build)

A status-only frame changes one store entry and no pane tree, so the pane's view stays mounted.

* refactor(terminal): keep the frame sync within its line budget

* feat(terminal): every pane main starts shows 'starting' from before it is written (layout-owner build)

* feat(terminal): a pane main is starting waits and attaches; a failed start shows why (layout-owner build)

Fixes owner-visible row 15: a failed agent or setup start no longer opens a plain shell.

* test(terminal): iterate store listeners directly

* fix(terminal): an exit that reports no code keeps its pane, as lost contact (layout-owner build)

* test(terminal): a pane main started attaches to main's terminal at its own grid

* test(terminal): detach census tests assert main's move and frame with the switch on

* test(terminal): exited-pane tests assert main's close with the switch on

These 8 have failed switch-on since #31130 moved the exit close to main.

* feat(terminal): main writes and starts every split of a pane it holds; the window attaches (layout-owner build)

The window's split, a startup split with its command, and a CLI split all go through main's
terminal.split; main's frame mounts the pane, with or without a window intent. Deletes the
window's split wait and withdraw, the split appear intents, and the queued-split skip and cancel.
The e2e helper uses the real split. A startup split leaves focus on its source pane.

* fix(terminal): the attach-only wait leaves sleeping, resuming and held panes to their restore path

* fix(terminal): a split pane attaches to the terminal main bound it to; a taken pane id is refused

Also restores the proof that a split closed while it starts gets one answer and leaves no
pane or start status behind.

* fix(terminal): a window split main cannot start still shows its pane, failed, with why

* fix(terminal): check a taken pane id first, keep the start's own error, clear a gone pane's status

Three test stores gain the pane-start field the window's store always has.

* test(terminal): the admission stub answers whether its pane is still written; a closed pane's failed start clears

* refactor(terminal): the rebound workspaces' bindings show through one call in the bindings module

* test(terminal): a pane main wrote and the user closed mid-start drops its failed status

* fix(terminal): a split main starts is listed to phones and session.tabs without a window's report

* fix(terminal): a reconnect shows its pane starting and why it failed; a lost owner race shows no failure; a split no one asked to see keeps the phones' tab

* fix(terminal): a kept create another owner won shows no failure; the window attaches to the winner
2026-10-21 00:14:06 -04:00
Jinwoo Hong 046f29b9e5 fix(runtime): a phone's Markdown save keeps the route it started on (#31376)
forSave captures the workspace's route (and its pairing pin) once; every read, stat,
status.get and write of that save uses it, so a workspace that moves between local and
a paired server mid-save never sends the write to the other host unpinned.
2026-10-21 00:14:01 -04:00
Brennan Benson cd007d4106 fix(session-history): scoped history no longer fails on a very large transcript corpus (#30905)
Scoped Session History built its candidate list by spreading each
discovery's files into push(), which throws RangeError once one root holds
more files than V8 accepts as call arguments. Append item by item instead,
and do the same for the bucket-layout candidates (unbounded on unlimited
scans) and the search index's unchecked-paths hand-off.

Tracked in STA-11092.
2026-10-20 21:06:54 -07:00
Jinwoo Hong a763c1d8b7 style: format pty-connection-split-spawn.spec.ts (main fails oxfmt --check after #30442) (#31378)
Claude-Session: 10569c9a-a0a1-4fc9-8469-0edfb7aed125
2026-10-21 00:00:52 -04:00
Brennan Benson f7c07409d6 docs(orca-cli): teach agents cross-project parents and re-parenting (#29900)
* docs(orca-cli): teach agents cross-project parents and re-parenting

* chore(cli): regenerate bundled orca-cli guide

* docs(orca-cli): scope the parent host rule and paired-server selectors
2026-10-20 20:47:50 -07:00
Brennan Benson 8a2ec06a22 Start an agent's terminal at its first real size instead of after a 12-frame wait (#30442)
* fix(terminal): start an agent's terminal at its first real size instead of after a 12-frame wait

The startup grid settle waited for the grid to change and then hold for 2 frames;
a grid that was right from the start always paid the 12-frame maximum (~90 ms at
120 Hz, ~200 ms at 60 Hz, seconds when frames are throttled, since it also cleared
the 250 ms fallback timer). Its only case, the setup split, was retired in #29819.
Command panes now connect like plain panes: on the first frame with a real
measurement, or the 250 ms fallback timer.

* fix(terminal): bound startup by completed fit and deadline

* test(terminal): brace startup contract control flow

* fix(terminal): release pending startup when window hides
2026-10-20 20:45:12 -07:00
Jinwoo HongandE2E Test b99552cabc fix(runtime): a phone reads and saves Markdown tabs on a headless server (#31101)
* fix(runtime): a phone reads and saves Markdown tabs on a headless server

With no window to relay through, markdown.readTab and markdown.saveTab answered
renderer_unavailable. Main now reads the tab's file (or its saved draft) from disk and
saves with a version compare plus the disk write precondition, in the shapes the
window's editor relay returns. With a window attached the relay is unchanged.

* Headless Markdown save: keep the draft's disk baseline, honour read-only tabs, re-find the record to clear

* Use the disk baseline only for a draft

* ci: run the phone Markdown spec's orcad case in the orca serve mode-switch job

* Headless Markdown: answer from disk only; a window's saved draft stays its view state

Clearing a saved draft wrote workspace layout from main, which no layout command carries.
Main now never shows or edits a draft; the desktop's restore check reports a disk change.

* Phone Markdown: one path through main and the disk, window or not

A paired server's workspace is read and saved on that server with files.read /
files.writeIfUnchanged, so it answers from its own disk and keeps the precondition.
The window relay (ui:mobileMarkdownRequest and the renderer bridge) is removed.

* e2e: a phone saves a Markdown tab the desktop holds unsaved edits in

The phone reads the disk with the tab marked dirty; its save lands on disk, and the
desktop keeps its draft and shows the changed-on-disk banner.

* Group the Markdown disk host's imports

* Drop an unneeded store cast from the dirty-readers test

* Phone Markdown: refuse a save on a server too old to check the disk; cover the untitled tab

A server before #30605 has no files.writeIfUnchanged, so the save is refused with the
existing update-required message rather than written unchecked.

* Phone Markdown: refuse a file an SSH host holds outside the workspace; pin the untitled answer

* Phone Markdown: untitled tabs stay read-only; older servers save like the desktop editor; a closed-source preview still reads

- The desktop lists an untitled document's tab with isUntitled (optional, additive); main answers
  it read-only with unsupported_untitled and refuses its save before touching the disk.
- A server without the files.write.disk-precondition capability gets the desktop editor's guarded
  save: compare from here, then a plain files.write, or a conflict. Every call of a save is pinned
  to the pairing it probed.
- A preview whose source tab was closed has no saved record; it reads its own file, read-only.

* e2e: read the untitled tab's answer without a cast

* Phone Markdown: pin a save's pairing before its first read

A save captures the server's pairing before it reads, and every call of the save, the read
included, is pinned to it, so a re-pair between the read and the write is refused.

* Test: with no window, a phone saves an existing untitled.md and never creates a deleted one

---------

Co-authored-by: E2E Test <e2e@test.local>
2026-10-20 23:08:47 -04:00
Jinwoo Hong dda855e837 test(layout): pin the layout-owner save format for downgrade, and keep main's touched-records save with an exhaustive guard (dark, layout-owner build only) (#31341)
* test(layout): downgrade probe for the layout-owner session format, and the touched-records patch's guard

Downgrade probe (ledger L8): a checked-in golden of the session main writes in a layout-owner
build (layout commands through the Store plus a window save), matched byte for byte; a
switch-off Store hands it on unchanged and a switch-off window restores its tabs, panes, groups,
editor file and browser tab. Regenerate with ORCA_UPDATE_LAYOUT_OWNER_GOLDEN=1.

L104: for each writer of the touched-records patch (layout.apply, the cross-partition move, the
window save), the patch equals a full save on every partition the load leaves alone, and differs
only on records the load normalizes otherwise. Two concrete legacy cases are pinned, and a
switch-off window restores each differently after a full save, so the patch stays.

* test(layout): type the guard's partition ids and locate the golden without import.meta

* test(layout): pin every legacy fixture's patch difference, assert restored panes and group selection, keep the golden deterministic
2026-10-20 23:01:17 -04:00
Neil fece03b5ac fix(orcad): voice dictation on headless orca serve hosts (#31217)
* fix(orcad): voice dictation on headless orca serve hosts

Mobile dictation against an orca serve host answered speech_unavailable
since serve moved to orcad: orcad never installed the speech service
factories, and the model manager and download imported Electron.

The model download now takes a host request opener (Chromium net on the
desktop, Node https on orcad, with redirects mapped to the same event),
the STT worker ships in the orcad slot, and orca serve hands orcad the
installed app's on-device recognizer. A host without a recognizer (an
SSH or WSL managed slot) lists only cloud models and sends a phone that
picked an on-device model to the setup sheet. Mobile names the fix when
an older server still has no speech at all.

The parity test now requires both roots to install speech.

Reported-by: delorean2020 (@delorean2020)

* fix(orcad): retry Node download drops, stage the recognizer on Windows, review fixes

- Node https errors (ECONNRESET, ETIMEDOUT, ...) are retryable, so an
  interrupted orcad model download resumes (Codex P2).
- On Windows orca serve hands orcad a per-version copy of the recognizer
  in the profile, so a loaded addon never locks the app's files against
  an update (Opus).
- Drop a recognizer candidate path that could never match; one SAFETY
  helper per test file instead of repeated casts; comments.

* fix(orcad): cloud speech is ready only with a key this host can decrypt; prune superseded recognizer copies

Codex round 2: a desktop-sealed OpenAI key exists on disk but orcad's
secret store cannot open it, so the catalog reported cloud models ready
and dictation failed at the end. orcad's model manager now proves the
key decrypts. The desktop keeps its existence check (no keychain prompt).

* fix(orcad): never prune staged recognizers before the profile lock; copy linked packages as real files

Codex round 3. ref-oss check: versioned copies are cleaned only by the
process that owns them, so the pre-lock prune is dropped rather than
patched.

* fix(orcad): remove a partial recognizer copy when staging fails (Codex round 4)

* fix(speech): never hand out another store's ciphertext as the OpenAI key (Codex round 5)

* refactor(orcad): no recognizer copy on Windows; Windows orcad keeps cloud dictation only

Design check after review round 5: every remaining finding came from the
Windows copy that dodges addon file locks. Dropping it shrinks the
surface to a path lookup; Linux and macOS serve keep on-device models.

* fix(speech): name the fix when an old server refuses a mic tap; always set the recognizer env; retry ENOTFOUND (final Opus review)

* fix(managed-server): probe install completeness relative to the slot dir

CI: shipping stt-worker.js added one more absolute slot path to the
install-complete probe, pushing it further past the exec-size budget.
Testing each artifact relative to the slot (inside a subshell cd) cuts
the probe from ~12.2 KB to ~3.6 KB, so it drops off the over-budget
list instead of raising a ceiling.

* fix(mobile): pin host.platform's read to a platform type, independent of which zod copy resolves

With mobile's own dependencies installed (which CI does for any PR that
touches mobile/), the e2e program resolves two zod copies, and the
salvaged platform arm from src/shared inferred as {}. The schema now
narrows through an explicit platform guard.

* fix(mobile): keep the host.platform read line intact for its RPC mutant; narrow in a second step

Mobile verify: the recorded-RPC mutant anchors on the read line, which
the previous guard rewrote, so the mutant could not apply.

* refactor(cli): app-executable resolution in its own module; drop the platform guard now zod is aligned

launch.ts went one line past max-lines with main; resolveAppRoot,
resolveForegroundOrcaExecutable, getExecutableAppArgs and
stripElectronRunAsNode move to orca-app-executable.ts (re-exported, so
callers are unchanged). With mobile and root on one zod (#31260), the
host.platform schema no longer needs the extra narrowing step.

* refactor(cli): import app-executable helpers from their module; no re-exports from launch.ts
2026-10-20 19:54:55 -07:00
Brennan Benson d98b8eddb4 fix(chat): keep resumed Claude messages deliverable after settings reports (#30982)
* fix(chat): separate applied Claude choices from provider reports

* fix(chat): preserve Max effort without settings readback

* test(chat): retain Max effort confirmation contract

* test(chat): a literal model change no longer pre-flights the catalog

* fix(chat): record observed execution independently of accepted choices

* fix(chat): fence model reports at prompt input before recording

* test(chat): inspect Fast evidence without unsafe outcome access

* test(chat): honor input preparation in turn fixtures

* test(claude): accept dispatch metadata after stopping a turn

* fix: retire deferred chat options at input preparation
2026-10-20 19:50:18 -07:00
Neil 5999642ff1 fix(settings): prioritize sharing in Remote Orca Servers (#31335)
* fix(settings): prioritize sharing in remote server settings

* chore(i18n): remove obsolete remote server settings copy
2026-10-20 19:44:34 -07:00
NeilandClaude 356bda10d1 fix(github): repo cache eviction clears every host's entries, keeping a live twin's (#31312)
* fix(github): repo cache eviction drops every host scope's entries, keeping a twin's own

Eviction and issue-source changes matched only unscoped key prefixes, so
SSH-, runtime- and source-scoped entries survived repo removal. Keys are now
composed by one builder and read back by its inverse, so eviction classifies
each key by host and keeps only a surviving same-id twin's bucket. Hosted
review (GitLab MR) entries are evicted too.

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

* fix(github): read task-source scopes with empty parts back whole

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

* fix(github): an issue-source change evicts only the changed twin's buckets

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-20 19:36:49 -07:00
Jinwoo Hong 379ad0c5cd test(terminal-surfaces): pin why main's pane republish is deferred to a microtask (#31333)
The deferral keeps a terminal's connected flip, noted inside syncWindowGraph's
record update, from re-entering syncWindowGraph and recursing. Layout-owner
(switch-on) builds only.
2026-10-20 22:33:42 -04:00
Neil c6becfaf7a fix(github): main finds a twin project's registered row by id and path, and a refresh by its host (#31274)
validateRegisteredRepo took the first row with the requested id, so for a repo id
registered locally and over SSH it read the other twin and refused the second
twin's fetch as a path mismatch. It now matches id and path together, uses the
GitHub source host when one is named, and lets a PR-refresh candidate's host pick
between twins at one path; a lone row's own fields still win over stale candidate
fields.
2026-10-20 19:31:04 -07:00
Neil 018c6e0c42 fix(mobile): create and edit notes on headless hosts (#31291)
Create, read, and save Markdown notes on headless hosts through authorized file operations and persisted editor tabs.

Addresses the Markdown-note reproduction in #22186, reported by @shota46. Thanks to @Jinwoo-H (#31101), @brennanb2025 (#25436), and @Meapri (#23357) for related community implementations and regression coverage. Those PRs retain broader/distinct scope.

Validation: focused tests, paired-headless E2E, iPhone simulator proof, and all CI checks passed.
2026-10-20 19:30:42 -07:00
Jinwoo Hong 065e6873f6 A paired desktop shows the held-terminal banner and Reopen for a server's held terminals (switch-on builds only) (#31275)
* A paired desktop shows a layout-owner server's held panes on the existing banner; Reopen asks the server (switch-on builds only)

The server's frames carry status 'held' on a pane a newer build's terminal service may still
run. The desktop now lists those panes on its existing newer-build banner, with the same wording
and states. Reopen calls the server's new `layout.reopenHeldPanes` under the server's ids: the
server stops its newer service, as the desktop's own Reopen does, and starts each pane still
held. The desktop starts nothing; the server's next frames clear the status and bind the pane.
A refusal or failure shows the banner's failed state.

Served and advertised (`layout.reopenHeldPanes.v1`) only by a layout-owner build, to paired
desktops (not phones). A server without the capability, or that publishes no status, shows no
banner, as today.

* test(layout): held server pane stays Reopening until the reattach frame, stays failed after a refusal, and shows nothing without the reopen capability

* fix(layout): a held pane's Reopen acts only on its own host and on main's published status (switch-on builds only)

- Reopen needs the clicked pane's key; a pane that already left the banner's store does nothing,
  so a server banner clicked as its frame drops the pane never stops this machine's newer service.
- Local versus server is decided by the pane's host, not by whether it has a release.
- The banner portal for a pane with no terminal handle exists only in a layout-owner build.
- The server reopens only panes its published layout shows held, the status the desktop saw.

* test(layout): Reopen ignores a pane rebound to a new terminal whose held entry is still stored

* test(layout): the dropped-pane Reopen test checks for the reopen method, since the view report shares the RPC client
2026-10-20 22:20:05 -04:00
Neil a1f2094d8c fix(github): work-item cache keys a twin by the row at the request path and its canonical host (#31290)
The fetch picked a twin's row by id first, so the SSH copy's results were cached
under the local copy's key; the task-page reader keyed by the raw executionHostId,
so an SSH row carrying only connectionId read the local bucket; and an unstamped
row was keyed by the focused server while its fetch ran locally. The repo lookup
now prefers the row matching both id and path, and writer and reader both key by
getRepoExecutionHostId.
2026-10-20 19:18:18 -07:00