Commit Graph
6207 Commits
Author SHA1 Message Date
Jinwoo Hong 6fcc183ab9 feat(sidebar): put main's sidebar inputs behind the ORCA_SIDEBAR_INPUTS dark build switch (#30050) 2026-10-17 04:06:43 -07:00
Jinwoo Hong 90c1c0ac50 feat(mobile): draw the host Accounts screen from the desktop's page (#30020)
* fix(mobile): wall hosts that cannot serve the page instead of opening native screens

* refactor(mobile): drop the native-route state and the shell fallback prop; unknown routes say unavailable

* test(mobile): read the nullable page route refusal in its policy test

* refactor(mobile): every build is the OTA shell; drop the native/OTA build switch

The owner retired the native-screen fallback with no emergency native build, so
EXPO_PUBLIC_MOBILE_SHELL, mobileShellBuildKind, shellSwitchDecision, the Metro
cache-key suffix and both release workflows' shell input go. Every host route
mounts the shell; a route naming no screen the shell could open gets the
existing refusal screen instead of a native screen.

Claude-Session: https://claude.ai/code/session_6cea92f6

* refactor(mobile): delete native screens the OTA shell no longer reaches

With every build the OTA shell, these native siblings run nowhere: the
native-executed closure (index.js plus every non-web route) does not reach
them, and the page resolves their .web siblings. Each .web sibling takes the
plain name, its web-overrides entry goes, and the native-only rich Markdown
editor and Mermaid WebView generators and their outputs go with them.

Kept: native siblings the node test suite still renders shared screens
through (terminal WebView, browser frame path, clipboard, dictation, media
picker, live input, snapshot budget, input styles).

Claude-Session: https://claude.ai/code/session_6cea92f6

* test(mobile): drop the phone-preview comparison the deleted native sibling held

Claude-Session: https://claude.ai/code/session_6cea92f6

* refactor(mobile): drop the dead web route, flag-era comments and the one-value shell kind

The host route files no longer switch, so their comments say what they do now:
a route naming no screen shows the unavailable screen. app/h/[hostId]/web.tsx
duplicated index.tsx and nothing linked to it, so it and its stack entry go
(the bundle sweep is re-measured: 77 scripts at 15 routes). Diagnostics print
the bridge protocol on its own instead of a shell kind that has one value.

Claude-Session: https://claude.ai/code/session_6cea92f6

* refactor(mobile): one host for the rich editor, plain Text, and drop two unused native deps

MobileSelectableText was react-native's Text, so callers use Text. The rich
Markdown editor's host-facing seams (messages, URL prompt, keyboard inset) are
required now that the page is its only host; the WebView defaults go. Comments
that described deleted native components are rewritten, and
expo-image-manipulator and react-native-uitextview leave package.json and the
lockfile.

Claude-Session: https://claude.ai/code/session_6cea92f6

* feat(mobile): draw the host Accounts screen from the desktop's page

Accounts was the last host screen the shell still drew natively, so its
changes needed a phone build. It is now a page route like Tasks: the
screen moves to src/accounts/MobileAccountsScreen.tsx, accounts.web.tsx
renders it on the page, and accounts.tsx mounts the shell for it.

What React Native Web needed: Alert.alert is a no-op there, so the
reset confirmation, its outcome and a failed account switch are
ConfirmModal drawers (ConfirmModal can now drop Cancel for a notice);
Back goes through the route handoff, which pops the shell's stack; and
the reset idempotency key is built from getRandomBytes, since
randomUUID needs a secure context the iOS custom scheme is not.

edit.tsx edits the phone's own pairing and never renders on the page,
so the route manifest leaves it out and the page's host stack does not
declare it. The script sweep is re-measured for the 14-route tree.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* fix(accounts): answer a same-offer Codex reset retry with its recorded outcome; mobile mirrors the desktop

The desktop keeps no client journal for a reset credit: the host's ledger
owns the attempt. A remote client that lost a reply and retried the same
offer under a new key was refused with "already attempted" (or "unknown
outcome" while the provider call was unresolved), which is why the phone
carried its own durable journal to replay the old key.

The coordinator now answers a request for an offer an earlier key already
claimed as that attempt: the settled outcome, the running attempt, or a
resume of the pending one under its original key. A different offer for
the account is still refused while an outcome is unknown, so nothing can
spend twice. The reply shape is the one a same-key replay always had, so
installed clients with their journal read it unchanged.

With that, the Accounts page sends a fresh key per attempt like the
desktop and keeps nothing on the phone: the AsyncStorage journal, its
client singleflight and the retry-record warnings are deleted, and the
journal-resume recording goes with them.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* fix(accounts): resume an account's unresolved Codex reset when the retry names a newer offer

Review follow-ups:
- Host: after an unknown provider outcome the usage refresh moves the
  offer, so a retry names a newer one. The coordinator now answers it as
  the account's unresolved attempt (resumed under its own key and scope),
  as the desktop's consumeCurrent does, instead of refusing forever. One
  ledger lookup finds own key, offer claimant, then unresolved attempt.
- Mobile: a reply is matched on target, account and account revision,
  not the offer, so the resumed attempt's scope is accepted.
- The key is minted inside requestCodexResetCredit from randomV4Uuid, the
  session helper moved to src/platform/random-v4-uuid.ts.
- RefreshControl goes; the header refresh stays. ConfirmModal's
  onConfirm is optional for an OK-only notice.
- Stale journal comments and the mobile docs line are corrected.
- The edit.tsx page exclusion is dropped from this PR; the script sweep
  is re-measured for 15 routes.
- Root oxlint: escape the apostrophe; zod v4 strictObject.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* fix(mobile): secure UUID fallback from expo-crypto; pin reset retry routing

- randomV4Uuid never falls back to Math.random: without crypto.randomUUID
  it builds the v4 UUID from ExpoCrypto.getRandomBytes, as the old reset
  key did. Tests get Node's secure bytes for expo-crypto from the setup
  file, since the native module has no Node build.
- Host tests: a new offer after a settled attempt spends again rather
  than replaying, and a retry naming a newer offer joins a resumed
  attempt in flight (one provider call, original key).

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* chore: restore main's bytes for files the merge's pre-commit format touched

The merge commit's lint-staged run reformatted 27 files this PR does not
change. Committed without the hook so they match origin/main exactly.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* test(mobile): stand in for expo-crypto with a Node alias, not a setup-file mock

Anti-slop forbids module mocking outside test files. The vitest config
now aliases expo-crypto to a faithful Node implementation of the names
shared code reaches; a test file's own mock still wins.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* docs(mobile): match the bridged-parity comments to the measured 795-golden tally

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
2026-10-17 03:11:24 -07:00
Jinwoo Hong 609f3425ad Record simulator tab opens and closes in main (dark, layout-owner build only) (#30034)
* feat(layout): record simulator tab opens and closes in main (dark, layout-owner build only)

* chore(layout): note why sleep/wake params copy OnTab
2026-10-17 02:48:46 -07:00
Brennan Bensonandm4air 3d2e0bd26d fix(native-chat): a running Claude chat keeps its model when two Claude Code versions list differently (#30022)
* fix(native-chat): a running Claude chat keeps its model when two Claude Code versions list differently

A send's stand-in for a model the list lacks took its rows and default from the
chat's own answer but kept only rows the account's saved list names, and every
running chat rewrote that list whatever CLI version it ran. After Claude Code
2.1.280 -> 2.1.302 the filter dropped the default row and sends moved to Sonnet.

- The options answer names the ids its agent listed (listedModelIds); a live
  catalog is verified only by its own listing. An older host's answer may move
  only to the chat's own default row, and only where the host's list names it.
- Only a session's start (and the probe) writes the account's list; later reads
  no longer do, so an older running CLI can't replace a newer start's list.

Tracked in STA-10947.

* fix: keep running chat model selection within its own listing

Preserve learned model menus on later reads and check launch-added model membership before saving.

Tracked in STA-10947.

* test: validate ACP menu regression fixtures

Use the protocol schema for config options and supply the catalog row default flag.

Tracked in STA-10947.

---------

Co-authored-by: m4air <m4air@Mac.localdomain>
2026-10-17 02:38:19 -07:00
Jinwoo HongandClaude 345ef81bfa feat(accounts): offer to restart running Claude sessions on the new account after a switch (#29898)
* refactor(terminal): let account-switch pane restarts name their agent

The queued account-switch restart only knew how to relaunch Codex. The
queue now records which agent each pane restarts as, and the resume
startup builder takes that agent, so the same mounted and detached
restart paths can move Claude sessions too. Codex behaviour is unchanged.

* feat(accounts): offer to move running Claude sessions after an account switch

Switching the selected Claude account only reached Claudes started later,
so users had to /exit and resume every running session by hand. After a
switch from the status bar or Settings, Orca now finds local terminal
panes running a resumable Claude session in the switched lane and shows
one toast offering to move them. Confirming restarts idle sessions with
claude --resume <id> in place; sessions mid-reply move when their turn
ends, and are dropped if the pane closes or the user switches again.

* fix(accounts): wait out permission prompts and keep hand-started Claude args

A Claude session at a permission prompt is mid-turn, so a move now waits
for it like a session that is replying. Resuming a Claude pane with no
recorded launch config no longer adds the user's default agent arguments
or environment, so a hand-started claude is not resumed with a
permission bypass. Codex keeps its existing fallback.

* feat(accounts): track which Claude account each terminal pane runs on

Orca had no record of the account a running Claude used, so an account
switch could only assume every session was on the old one. A renderer
tracker now attributes sessions that start after launch to their lane's
selected account and records the account Orca restarts a session on.
Sessions it cannot attribute stay unknown and keep today's behaviour.

* feat(accounts): ask in a dialog before restarting Claude sessions on a switch

Replaces the move toast. After a Claude account switch, a dialog names the
new account and how many sessions are still on the old one, with Keep as
is and Restart, plus Remember my choice. Closing or Escape keeps sessions.
The remembered choice lives in Settings > Accounts as When you switch
accounts while Claude is running: Ask, Always restart, Never restart.

* refactor(accounts): read Claude session accounts from their transcripts and use one restart queue

Which account a running Claude uses now comes from the transcript path its
hooks already report (<CLAUDE_CONFIG_DIR>/projects/...), so the renderer
tracker and its guesses are gone and unknown sessions are never counted.
Restarts queue straight into the store's pending map through one
queuePaneAccountRestarts action; the pane effect and the detached
scheduler hold a Claude restart while its turn runs and skip it when the
session can no longer be resumed. Codex behaviour is unchanged.

* style(accounts): left-align Keep as is in the Claude account switch dialog

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

* copy(accounts): say busy rather than replying, and drop the menu's switching note

The deferred restart waits on any working or blocked turn, not only a reply. The
switch dialog now covers running sessions, so the menu footer note is redundant.

* copy(accounts): say busy sessions wait for idle before restarting

* test(accounts): type the switch-session store stub without broad object

* fix(accounts): keep a queued Claude restart when Codex restart notices are cleared

Codex notice cleanup shares the pending-restart map with Claude switch restarts;
it now drops only Codex entries.

* fix(accounts): wait out Claude questions and keep accepted restarts across switches

- A Claude permission prompt or question reports 'waiting'; a queued restart now
  waits for 'done' instead of only 'working'/'blocked', so it no longer drops them.
- A later switch keeps accepted restarts still waiting on a turn (they relaunch on
  whatever is selected) and drops only those whose session is already on the new
  account, scoped to the switch's lane. The blanket replaceQueued option is gone.
- A server account switch returns before touching the local dialog or queue.
- Pane restart state reads through one selector to stay inside the subscription
  budget, and the focus-read baseline follows the restart-startup rename.

* test(accounts): feed the account-restart e2e fixture the shared restart queue

* feat(accounts): make the switch dialog's checkbox one-way, like native chat's resume prompt

"Don't ask again (restart automatically)" turns on automatic restarts however the
dialog is left, including Escape; Settings → AI Provider Accounts turns it off.

* feat(accounts): choose which Claude sessions restart after an account switch

With more than one session, the switch dialog lists them by workspace using the
native-chat resume tree rows, all ticked; Restart covers only the ticked ones.

* Merge origin/main into claude-sign-in and rename the host-twin teardown's restart queue

* fix(accounts): localize the session tree's agent label

* fix(accounts): restart a pane on the PTY it has when the dialog is answered

A reattach can rebind a pane while the dialog is open and a closed pane has no
PTY, so the ids it listed may name nothing.

* fix(accounts): read a layout's pane PTYs optionally

* fix(accounts): offer to move sessions of a saved account that runs on System default

A saved account with no login of its own runs on System default, so its sessions'
transcripts read as System default. The switch now compares where launches run
(runsOnSystemDefault on the account summary) and asks nothing when that is unchanged.

* refactor(accounts): give the Claude account switch one boundary and key the whole prompt

The post-switch coordinator now takes the before/after account snapshots and
derives launch folders and labels itself, so the status bar and Settings stop
assembling the same request. The status bar's settings projection reuses the
host's row, which keeps runsOnSystemDefault: a covered account switched from
the status bar now finds its System default sessions. Never restart returns
before any session scan. The outcome collapses to show-confirmation | handled.
The dialog keys one component that owns the whole Dialog with a plain boolean.

* refactor(terminal): subscribe mounted panes to this tab's ready account restarts

Replaces claudeTurnStatus, the whole agent-status map passed through the
controller as a re-run signal, with a shallow selector of the PTY ids whose
queued restart can run now. Other tabs' status updates no longer re-render or
re-run every mounted pane once a Claude restart is queued. The effect re-checks
readiness against the current store before consuming. The detached Claude test
now runs through the scheduler's real subscription instead of manual sweeps.

* docs(accounts): shorten Claude account-move comments to their whys

* test(terminal): give the account-restart e2e a callable store and a queued restart

The mounted pane now reads its ready restarts from the store through useShallow.

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-17 02:11:10 -07:00
Jinwoo HongandClaude 30e3119fc7 fix(accounts): follow an Orca Claude account switch when the shell sets CLAUDE_CONFIG_DIR (#29890)
* fix(accounts): follow an Orca Claude account switch when the shell sets CLAUDE_CONFIG_DIR

A user whose shell config or environment sets CLAUDE_CONFIG_DIR switched Claude accounts in Orca,
but `claude` relaunched in an open terminal kept running the old account: the terminal's claude
function treated any CLAUDE_CONFIG_DIR it did not inject as a deliberate per-terminal override.

Host terminals now always carry the user's own folder (ORCA_CLAUDE_USER_CONFIG_DIR, from the login
shell's env), also on System default and before that env arrives. The claude function (POSIX, fish,
PowerShell) treats a CLAUDE_CONFIG_DIR equal to it, ignoring a trailing separator, as System
default's folder and follows the selected account; any other value set in the terminal still wins.
WSL panes carry no user folder, so they behave as before.

The terminal daemon protocol moves to 49: hourly builds already run v48 daemons whose wrapper has
the old function. v48 stays attachable.

* fix(accounts): match the user's Claude folder on PowerShell whatever its separators

Orca records the user's CLAUDE_CONFIG_DIR resolved (backslashes on Windows), so a
pane value written with forward slashes read as a deliberate override and stopped
following the account switch.

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

* fix(claude): pass the user's own CLAUDE_CONFIG_DIR to terminals exactly as set

Claude keys its login by the literal folder string, so a resolved spelling (a
dropped trailing slash) could switch System default to another saved login, and
Git Bash on Windows compared a backslash spelling against its rc's forward one.

* refactor(claude): one reader decides whose CLAUDE_CONFIG_DIR it is; one base pane env

The resolved reader now derives from the as-set one, every host pane env builds on
one base patch, and the router tests share one shell probe.

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-17 01:54:24 -07:00
Jinwoo HongandClaude c3e3b5241a Record the chat pane the window picks in main (dark, layout-owner build only) (#30036)
In a layout-owner build the window's chat-pane pick (chatLeafId) reached
main only through its session save, so a frame could undo it and the save
changed main's layout. The window now records it with main's existing
setChatPane command. Codex restart and background-agent pane trees are
already written by main; tests pin that.

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-17 01:50:41 -07:00
Neil 6743353116 fix(remote): make the host's tab name the one source for CLI and desktop renames (#30037)
A desktop connected to a remote Orca server kept its own copy of a tab's
manual name, and ranked its saved-session title (a resumed Codex chat)
above the live title the server publishes. So orca terminal rename on the
server never showed on a resumed tab, and a desktop rename never reached
the server.

The server now publishes the tab's manual name as an optional customTitle
on each terminal surface, and the desktop mirror adopts it. Desktop renames
go to the server: terminal.rename by handle, plus session.tabs.setTabProps
customTitle by tab id for panes that are asleep or starting. Older servers
omit customTitle and strip the new param, so the desktop keeps its own
name with them, as before.

Fixes #29975
2026-10-17 01:44:01 -07:00
Jinwoo HongandClaude e6b7c77e1a feat(sidebar): main produces per-workspace PR facts, checked against the window in dev and test (#30026)
* feat(sidebar): main produces per-workspace PR facts, checked against the window in dev and test

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

* refactor(sidebar): share the PR cache key candidates instead of splitting an import

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-17 01:27:27 -07:00
NeilandClaude 0ba5cccf9d fix(mobile): keep a headless host's chat tab through snapshot rebuilds (#30028)
On a host with no window, chat tabs live only in the runtime's session snapshot. A forced rebuild from the saved session (closing a saved editor tab, an expired SSH reattach) dropped them, and every terminal-only group merge removed them from the tab order.

Refs #29968

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-17 01:23:10 -07:00
NeilandClaude 7adcefb078 fix(ai-vault): hide Orca's chat-naming requests from Session History (#30029)
Naming a chat runs a one-shot agent CLI request in the chat's folder, and the CLI saves it beside the user's chats. Session History listed it, and opening it from a phone resumed the naming request instead of the chat.

Fixes #29973

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-17 01:22:15 -07:00
Brennan Benson a5b96a1f8d feat(acp): an agent can run each chat's own permission mode itself (#28455)
* feat(acp): an agent can enforce the permission mode itself through a session config option

An optional launch-spec field names the session config option and its ask/full values. Every new or
reopened session gets the value for the chat's full-access posture before it is exposed, and a refusal
fails the start. With the field present Orca no longer answers permission requests for the person.
No agent sets it yet.

* fix(acp): reject unconfirmed permission mode during acquisition

* feat(acp): an agent enforcing the permission mode itself runs the chat's own mode

The launch spec maps each chat permission mode to its session option value; a start sets the
value for the mode the host launches with (ask when it names none), replacing the global Agent
Permissions full-access read. A mid-chat change takes effect through the host's existing restart
of a child launched with another mode before the next send.

* Keep ACP session acquisition within the line limit

* fix(acp): a chat's permission mode goes to the agent's mode-category option, whatever the agent names it
2026-10-17 01:16:57 -07:00
Jinwoo Hong 03cefcb84c feat(sidebar): main keeps one live set of paired-server rows, checked against the window in dev and test (#30027)
* feat(sidebar): main keeps one live set of paired-server rows, checked against the window in dev and test

* fix(sidebar): settle review findings; main also publishes paired-server host facts
2026-10-17 01:13:34 -07:00
NeilandClaude 98d8257b02 fix(ssh): a password SSH host waits for you at launch instead of prompting (#30025)
* fix(ssh): a host that needs a password waits for the user at launch instead of prompting

Restores the persisted 'last sign-in needed a typed credential' flag that
the relay removal (#27214) stopped writing, and at launch marks those hosts
as awaiting sign-in so background reconnects (startup restore, managed-server
status checks and tunnels) refuse without dialing instead of opening the
password dialog. The user's own Connect lifts it and prompts. The terminal
toast shows the refusal as an SSH state, not a bug to report.

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

* test(ssh): give the raw-connected fixture the cache query; annotate the harness-shaped registration

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

* refactor(terminal): keep the sign-in branch out of the toast component

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-17 01:10:48 -07:00
Brennan Benson 38131048b1 feat(acp): a sign-in prompt on an ACP agent's error output ends the start or turn signed out (#28467)
* feat(acp): a sign-in prompt on an ACP agent's stderr ends the start or turn signed out

An agent that waits for a browser sign-in prints a link on stderr and blocks for
minutes. A launch spec can now name that line (signInPrompt); the connection then
closes with a typed sign-in failure through the existing process owner, so a start
reads notSignedIn and a running turn fails signed out instead of settling unknown.
The matched line, which carries the sign-in URL, never enters the retained stderr
tail, errors or diagnostics.

* Prevent sign-in URLs leaking from long stderr lines

* fix(acp): a stderr line cut at the cap that is not a recognised prompt is dropped, so its URL never reaches the kept tail
2026-10-17 00:59:44 -07:00
Jinwoo Hong fa962dd1cd Record chat tab opens and closes in main (dark, layout-owner build only) (#30019)
* Record chat tab opens, closes and host-listed chats in main (dark, layout-owner build only)

A chat tab the window opens, closes, or adds and removes from a host
listing of this machine's chats now reaches main as openAgentSessionTab,
closeTabs or setTabProps under the window's ids, so the window's save no
longer teaches main a chat tab. openAgentSessionTab takes the window's
place, creation time and new split group, and the same open again
changes nothing. Switch-off builds are unchanged.

* test(layout): a paired server's chat tabs send nothing to main (dark)

* fix(layout): keep a windowless host's chat-tab saves as they were; send synced chat opens in tab order (dark)

Only a window's openAgentSessionTab (main marks it drawn on layout.apply)
takes the new rules: a repeat changes nothing, a taken id is refused, and
a workspace with no group takes the window's. A windowless host's own
saves mint a fresh group and keep a chat whose id another tab holds, as
before. A host listing's new chats are recorded in tab order, so each
open's anchor already reached main.

* refactor(layout): one rule set for a chat tab open; the windowless caller names only a group it holds (dark)

Drops the per-sender drawn flag. The windowless save passes its
remembered active group only when the workspace still holds it, so it
mints a fresh group as before. Refusing a taken chat tab id is gated on
the build switch: shipped builds still save the duplicate id.
2026-10-17 00:57:12 -07:00
Jinwoo Hong 6c7ed18e20 feat(sidebar): main publishes desktop focus, pins, project and host order, and SSH/WSL host state, checked against the window in dev and test (#30018)
* feat(sidebar): main publishes desktop focus, pins, project and host order, and SSH/WSL host state, checked against the window in dev and test

* fix(sidebar): read pins through the host-filtered metadata helper; name pins as workspaces
2026-10-17 00:51:57 -07:00
Brennan Benson 251516de60 feat(native-chat): choose which agents open as a chat (#29071)
* feat(native-chat): choose which agents open as a chat

Settings → Chat gets a Providers list. Each agent the host can open as a chat
has a switch; off opens that agent in a terminal instead. Only choices the user
makes are saved. Unchosen: Claude and Codex on, others on when their CLI is
installed on this computer.

The renderer's route and the host's launch-mode decision both read one rule, so
the tab menu, shortcuts, CLI, orchestration and relayed phone launches agree.

* fix: keep chat provider settings with the other chat settings types

* test: route input carries this computer's detected agents

* fix(chat): resolve provider choices from their owning computer

* test(chat): use checked native inventory fixtures

* Fix launch preparation lifetime and execution host startup

* fix(chat): keep unchosen providers on without discovery

* test(chat): type provider routing fixtures against store contracts

* test(chat): align shared launch fixture with private settings reader

* test(chat): give the structured-session RPC fixture the private settings reader

The host now reads saved chat choices through requireStore().getSettings(); the
shared RPC fixture only had getClientSettings, so the launch-mode reader saw no
settings and create support refused Claude. The fixture now follows each test's
client settings.

* fix: preserve provider choice and cached native inventory
2026-10-17 00:51:43 -07:00
Neil 8f2810ab23 fix(ssh): link a folder workspace the server kept when a re-created SSH host moves again (#27285) (#30017)
* fix(ssh): link a folder workspace the server kept from an earlier move instead of conflicting (#27285)

* fix(ssh): resolve a server folder's host before linking it, and read folder lease owners

* fix(ssh): name a linked folder workspace in the connect's left-behind warning
2026-10-17 00:50:27 -07:00
Jinwoo Hong 5733887967 test(layout-owner): prove main and the window agree on a worktree identity rekey (#30005)
Main's Store.migrateWorktreeIdentity re-keys every stored layout field before it sends the
rename event the window re-keys on, so the window's save only mirrors it. The new oracle checks
main's migrated layout model equals the model of the window's post-rekey save and the model's
identityRenamed transition; the first-work test pins migrate-before-notify.
2026-10-17 00:10:27 -07:00
Brennan Benson 32a53d3ec6 feat(native-chat): image messages wait in the queue like text (#28655)
* feat(native-chat): image messages wait in the queue like text

A message with images now queues behind the running turn and the cards
ahead of it, instead of skipping the queue. Cards show an image icon and
count, an image-only card names its image, and editing a card's text keeps
its images. The host publishes no card holding an image to a client that
has not advertised agent-session.queued-image-cards.v1, since released
clients edit by copying a card's text and deleting the card.

* test(native-chat): an image follow-up queues like text

* fix(native-chat): project queued images before phone frame limits

* fix(native-chat): preserve image sends from older clients

* Clarify direct text recovery at send admission

* fix(native-chat): expose queued image names to keyboard users
2026-10-17 00:07:29 -07:00
Brennan Benson 58f1f7de1e fix(mobile): edit queued messages in place (#28646)
* fix(mobile): edit queued messages in place

* fix(mobile): use the platform input font size for queued edits

* fix(mobile): handle missing host support while probing queued edits

* Keep phone queued edits owned through save settlement

* Keep phone queued edits local to mounted chat

* fix(mobile): retire queued editor when its card disappears

* fix(mobile): retain queued edits while reconnecting

* test(mobile): await reconnect timer inside act callback

* fix(mobile): publish queued edit transport after commit

* test(mobile): await queued Edit inside act callbacks

* fix(mobile): hand a stale queued Save to the composer and reopen on the newer text

* fix(mobile): close an unchanged queued Save locally and keep stale typing in its own chat

* fix(mobile): explain disconnected queued edit refusal

* fix(mobile): combine queued session contract imports
2026-10-17 00:06:07 -07:00
Jinwoo Hong 77c511b316 feat(sidebar): main produces per-workspace activity facts, checked against the window in dev and test (#29980)
* feat(sidebar): main produces per-workspace activity facts in shadow mode

* perf(sidebar): agent pings keep last-read surfaces; shadow counts comparisons

* fix(sidebar): settle review findings in the activity-facts producer and shadow check

* fix(sidebar): refresh on inventory verdict clears; resync agents only when tabs move

* fix(sidebar): scope the shadow comparison by each row's owner, not the focused host

* refactor(sidebar): name the shadow scope after what it keeps

* fix(sidebar): classify sidebarInputs as host-owned in the operation ownership census
2026-10-16 23:40:35 -07:00
Jinwoo Hong a349bfa6cc Agree with main on first-group ids, tab order and the preview flag (dark, layout-owner build only) (#29987)
* Agree with main on a workspace's first group and the tab order, and stop writing a preview flag main never stores (dark, layout-owner build only)

Switch-on builds only; switch-off behaviour is unchanged.

Group ids: a window save made before main's frame with a workspace's first
tab carries none of that workspace's tab-bar records, so the Loader rebuilt
the tab in the group it names for the workspace while main had minted a
random one: every workspace's first save replaced main's group. Main now
names a workspace's first group as the window's root group and the Loader
do (`workspaceRootGroupId`), or takes the sender's when the window drew the
tab in a group main lacks (the window keeps its emptied group); the
window's `ensureGroup` names it the same way. The detector reads a lone
group with no stored tree as that group's leaf, as main's own save writes
it. Empty groups: the window never saves a group it holds empty, so a save
cannot bring back one main closed (locked by a test).

Tab order: a reorder the window makes itself (an anchored CLI or agent
create, a reopened tab's old slot) is recorded in main as `moveTab` steps
at the store boundary; callers that already asked main pass
`layoutOwnerHandled`. The reopened-editor record this replaces is deleted.
A paired client's move (`ui:moveSessionTab`) goes to main first through
the tab-bar drop path.

Preview: activating a tab removed its preview flag by writing
`isPreview: false` on every tab; main stores no flag, so every activation
changed the tab in main's model. The window now drops the flag instead.

* Record a reopened tab's place in main every time, leave a move into a tab's own group alone, and keep a preserved preview untouched (dark, layout-owner build only)

Review fixes, switch-on only:
- A reopened tab's place is recorded as one moveTab whenever its old
  position is known, as the deleted editor-only record did, now for
  every kind; the reorder it may make is not recorded twice.
- A move into the tab's own group (a paired client can send one) goes
  to today's no-op instead of a moveTab that reordered the tab.
- Activation that preserves a preview returns the tab unchanged.
Tests: switch-off first group stays minted; tabOrderMoves refuses a
duplicate order.

* Send a paired client's tab reorder to main as its whole order (dark, layout-owner build only)

A client reorder names the group's full order, but it went to main as
one moveTab of the named tab, so [a,b,c] -> [b,a,c] naming c moved
nothing anywhere. It now takes the store's reorder path, whose record
turns the whole order into main's moves (tabOrderMoves), the one rule
for every window-side reorder. Moves and splits still go main-first.
2026-10-16 23:25:10 -07:00
Jinwoo Hong 5b69634c0a Agree with main on main-made terminal tabs and sleeping-agent records (dark, layout-owner build only) (#29967)
* feat(layout): main-made terminal tabs drawn with main's facts; resume records reach main record-only (dark)

In layout-owner builds only:
- A terminal tab main made itself (drag-out, CLI or host terminal, live-surface
  adoption) is drawn with main's createdAt and creation fields from the frames,
  and the window sends no create for it.
- An editor tab main records takes the time the window drew it.
- Every change of the window's agent resume records (live and quit captures,
  dropped records) reaches main through record-only sleep and wake commands,
  which stop or start nothing.
- A terminal row's remount generation is declared the window's.

* fix(layout): real wake reaches main before the record drop; drag-out keeps a frame-drawn tab (dark)

In layout-owner builds only:
- A resumed pane's wake is sent before the window drops its record, so main's
  wake still finds the pane sleeping.
- A drag-out whose new tab main's frame already drew shows the pane in that
  tab instead of throwing "Terminal tab already exists".
- A main-made tab keeps the window's shell, cwd, host-runtime and launch agent
  where main's copy names none (a runtime create records a shell only when
  its launch named one).
- No record-only wake for a tab the window closed; main dropped its records
  with the tab.
- Tests for the resume-record follower.

* fix(layout): a dragged-out chat pane's new tab opens in chat view in main (dark)

Main's movePaneToNewTab now gives the new tab viewMode 'chat' when the moved
pane is its source tab's chat pane, as the window's own drag-out does. In a
layout-owner build the window adopts the row main's frame drew, so without
this the tab opened in terminal view.

* fix(layout): a bound pane with a resume record starts as a no-op; resume records only for held tabs (dark)

- startPane checks the pane's live terminal before its resume record: a
  running agent's live or quit record does not make its pane asleep. A pane
  with no terminal and a record is still refused.
- The window sends record-only sleeps, like record-only wakes, only for tabs
  it still holds; a capture from a stale agent status no longer names a tab
  main already closed.
2026-10-16 23:23:04 -07:00
Neil 76fc1e5acd fix(web): pair and stay paired from plain-http LAN and Tailscale pages again (#29984)
Partially reverts #27540 and the plain-http part of #27552, which broke web
pairing on upgrade for 1.4.227 users who open Orca at a LAN or Tailscale address:

- The host no longer refuses a first pairing from a plain-http page on another
  machine (insecure_origin, ORCA_ALLOW_INSECURE_WEB_PAIRING).
- Such a page keeps its pairing code in local storage and signs in with it, as
  in 1.4.227, so reloads, new tabs and bookmarks stay paired; no notice.
- Session-only browser keys (pairing.device-key-ephemeral.v1, the idle sweep)
  stay for browsers without Ed25519.
- A web client whose credentials are refused shows the host's reason instead
  of an empty workspace and "Session restore failed".
2026-10-16 22:50:27 -07:00
NeilandClaude a82298ebe6 fix(runtime): the host stops a removed project's shells and agents itself (#29964)
* fix(runtime): the host stops a removed project's terminals itself

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

* fix(runtime): a runtime-stamped project still stops the shells that run here

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

* fix(runtime): refresh the PTY inventory and sweep again after a project's removal

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

* fix(runtime): bound the project inventory refresh and recheck the WSL move fence

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

* fix(runtime): fence a project's removal sweep by its SSH connection and wait out in-flight spawns

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

* fix(runtime): bound the project stop's lease wait by the removal deadline

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

* fix(runtime): refuse spawns into a removed project and skip a stop a same-id sibling shares

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

* fix(runtime): bound each project stop by the removal deadline

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

* fix(runtime): pass the removal deadline to the lease so a timed-out waiter is dequeued

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

* test(runtime): type the project-removal fixtures instead of casting

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-16 21:54:47 -07:00
Neil 0d9beb603e fix(ssh): reconnecting a stopped managed host keeps the server's project groups (#29962)
Relinking projects the server already held still re-imported this desktop's
retained copy of their project groups, so a group renamed on the server while
it was managed failed the reconnect with orcad_migration_project_group_id_conflict.
A relinked move now leaves to the server every group its projects sit in (read
from repo.list's projectGroupId) and those groups' ancestors unless a moving row
needs one, journals those ids, and keeps them out of the exported rows while
still reading them for folder-workspace host ownership. Later delta moves
subtract them as they do moved groups.
2026-10-16 21:54:37 -07:00
Neil dd2bd0b0b7 fix(terminal): recovering one split pane keeps its healthy sibling in the saved layout (#29961) 2026-10-16 21:54:27 -07:00
Brennan Benson 7b6f02fdbf fix(native-chat): idle cleanup and commands read the helpers the agent reported, not the capped status store (#29905)
* fix(native-chat): decide owed helper work from what the provider reported, not the capped store

Idle cleanup, replacing an outgrown agent, /clear, /compact, rewind,
command handover and the teardown resume marker decided whether a chat
still had helpers running by reading the agent-status store. That store
is a bounded display projection: it refuses records when it is full or
for per-record reasons, and a refused helper is still running.

The host now keeps an uncapped per-session set of the child work its
provider reported running and has not reported over, fed from every
child-work edge before the store sees it. Every owed-work decision reads
that set. An entry leaves on the child's own end (a late end of an
earlier run leaves the current run open, as the store's run verdict
does), on a Stop the store settled, or on the session's end.

The strip's "incomplete" note and Stop all's unlisted route are now
derived: open work that no live current-run record answers to. This
replaces the refused-for-room side list, so every refusal reason is
covered and nothing needs separate bookkeeping.

* test(native-chat): the real-store test sink reads recorded helper handles, as production's does

* fix(native-chat): keep helper run identity through delayed events and Stop

* fix(native-chat): name the host helper lifetime token

* test(native-chat): explain the Stop adapter fixture boundary

* fix(native-chat): derive helper coverage from running store records

* docs(native-chat): clarify running helper coverage

* test(native-chat): use an explicit helper end event

* Scope background Stop to captured child work

* Exercise refused helper Stop race through status store
2026-10-16 21:26:34 -07:00
Brennan Benson 002320c17d fix(native-chat): verify the actual CLI conversation during resume (#29124)
* fix(native-chat): identify the owner when resume create is refused

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

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

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

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

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

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

* fix(native-chat): admit the actual CLI source during chat creation

* fix(native-chat): satisfy source admission lint checks

* test(native-chat): provide the waiting hook prompt

* test(native-chat): isolate source admission fixtures from host homes

* test(native-chat): canonicalize adoption fixture directories

* test(native-chat): preserve early ownership admission in source stack

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

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

* fix(native-chat): preserve admitted source and prepared execution home

* fix(chat): retain source evidence for dispatched adoption

* fix(chat): keep source capture within module limits

* test(chat): allow host evidence in create routing

* fix(native-chat): admit an unsaved transcript under a linked account folder

A missing transcript stayed unresolved while its account root resolved through
symlinks, so containment failed and a not-yet-saved conversation showed a load
error instead of the unsaved notice.

* refactor(native-chat): move the newest-item import window into its own module

Keeps journal-legacy-import.ts under the line limit after the source
identity observer landed on top of the streaming import.

* fix(native-chat): restore the refusal error import lost in the main merge

* fix(native-chat): admit Pi transcripts against the root Session History walks

A PI_CODING_AGENT_DIR that already ends in sessions, or any custom folder,
made admission expect <dir>/sessions while the lookup searched <dir>, so a
found conversation was refused.

* refactor(native-chat): admit and look up CLI transcripts in one store per account

Each agent's import described where an account keeps its conversations twice,
once for the path check and once for the lookup, and the two could disagree
(the linked-folder and Pi custom-root refusals). The import now names one
conversation store per account; the create path resolves it once and both the
hook-path check and the fallback lookup judge that same store. OpenCode's
store is its exact database.
2026-10-16 21:14:03 -07:00
Neil 6863096c06 fix(orcad): a client that reconnects during idle shutdown can still open terminals (#29958)
* fix(orcad): replace a retired terminal daemon when a client returns during idle shutdown

* fix(daemon): drop idle-retired legacy generations when replacing the daemon

* fix(orcad): treat a lost or late daemon retirement reply as committed

* fix(orcad): settle a lost retirement reply with the daemon itself before replacing it

* fix(orcad): bind the replacement to the retired daemon and honor the router fence

* test(daemon): type the pending census

* fix(daemon): skip the owner check only for a proven-dead endpoint
2026-10-16 20:25:47 -07:00
Neil 9cc8b80817 fix(worktrees): a preparation another machine owns is never reclaimed as stale (#29957)
The preparation lock reason named only the owner's pid, so a host sharing the repo's disk
(an NFS home) read another machine's in-progress preparation as abandoned and discarded it.
The reason now carries the writer's machine identity (optional, appended after the fields
older builds read); a different or unknown machine is unverifiable, and a reason without
one is judged by pid only on a local disk.
2026-10-16 20:24:04 -07:00
Neil 7d077ee9c9 fix(ssh): keep existing hosts' orca CLI control off after the on-by-default flip (#29949)
* fix(ssh): keep existing hosts' orca CLI control switch off after the on-by-default flip

Builds before the flip saved an explicit off as an absent value, so absent=on
silently turned on hosts the user had turned off. On the first load after this
upgrade, pin every saved host's absent switch to an explicit false and record a
one-shot marker in settings so hosts added later keep the new default.

* test(ssh): check the saved profile without a type assertion

* fix(ssh): say the CLI control switch is on by default for new hosts at the call site too
2026-10-16 20:07:35 -07:00
Neil 86315533da fix(orcad): keep an artifact slot whose serve or daemon record names another machine or boot (#29952)
On a shared home, a peer lock's PID is evidence only on the machine that issued it.
Cache retention judged the instance lock and daemon PID records by PID alone, so a
second machine evicted the slot another machine's live serve or daemon runs from.
2026-10-16 20:06:20 -07:00
Neil cffb603b03 fix(remote): opening a slept running agent on a host with no window resumes it, never a fresh launch (#29950)
A host that could open a window but had none left a workspace-slept running
agent's record for a window wake that never came, so opening its tab from a
paired client or phone launched a fresh agent with settings defaults (for
example --dangerously-skip-permissions instead of the saved --permission-mode
plan). The pane now resumes it with only the recorded flags. Before the spawn
it durably claims the record by rewriting it into the host's own pane-owned
capture form, which a window wake never relaunches (no second writer) and
which still resumes the pane on a later open if the PTY is lost or the spawn
fails. With a window present the window still owns the wake, and the pane
opens a plain shell instead of a fresh agent.
2026-10-16 20:05:25 -07:00
Jinwoo Hong f7a8e633af Send a terminal tab's own changes to main first, from every caller (dark, layout-owner build only) (#29904)
* feat(layout): route a terminal tab's own changes to main first (dark)

Rename, color, agent and vault titles, view mode and an ended launch
agent now reach main as layout commands from every caller, in
layout-owner builds only. Main's create also takes the window's
createdAt, and frames keep the window's pane mount generation.

* fix(layout): toggle from the mode sent to main, give each agent title its own answer (dark)

A second view toggle before main answers the first now flips from the
mode already sent, so the tab ends where a window-owned build would.
An agent-title batch applies each tab's title when main answers that
tab, and a paired server's tab at once. Drops the frame merge's
generation field: the window's own save already carries it. Reverts
unrelated formatting.

* fix(layout): clear a pending view mode only on its own send's answer (dark)

* chore: restore three files the commit hook reformatted outside this change

* fix(layout): send one agent title per tab while main has not answered (dark)

The window keeps a tab's first generated title, so a second prompt's
title sent before main answered the first would leave main on the
second and the window on the first.
2026-10-16 19:56:25 -07:00
Jinwoo Hong 9cc5d6b427 Keep an editor tab Orca moved to its file's workspace across restart; record editor renames in main (dark) (#29930)
* fix(editor): keep an editor tab Orca moved to its file's workspace across restart

The move saved the tab under the file's owned id and left the tab's
workspace pointing at the old one. Restore rebuilt a local file under
its bare path, so the tab named no open file and was dropped. Restore
now maps a saved owned id to the id it rebuilds, and the move gives
the tab its new workspace.

* refactor(editor): delete reorderFiles, which nothing calls

* chore: prune the renderer layout-writer baseline

* fix(layout-owner): keep diff, preview and conflict records when a frame redraws editor tabs

Switch-on builds only. A frame rebuilds the window's tabs through the
restore builders, which drop every diff, conflict and check tab (restore
has no record for them), and the store patch dropped every open file
the frame did not carry. Frames carry only edit-mode files, so the next
frame after opening a diff closed it in the window while main kept it,
and a markdown preview kept its tab but lost its record.

The rebuild now keeps a transient tab whose record the window holds,
and a held record goes only with the last tab showing it; a record no
tab shows (a conflict review's selected file) stays.

* feat(layout-owner): record renamed open files and closed conflict placeholders in main

Switch-on builds only; switch-off behaviour is unchanged.

A rename or move in the explorer rekeys open editor, preview and diff
tabs in the window and main learned it only from the window's save.
The window now records the rekey as a `renameEditorFiles` command,
under the ids it chose (bare path, owner-qualified id, diff id), one
per workspace, with each edit-mode file's new record. It records
rather than asks first: the rekey is atomic with drafts and the
autosave echo gate, and its answer gates the move coordinator. Main's
path-keyed `editorFilesRenamed` transition, which nothing produced and
which could not match owner-qualified or diff ids, becomes this command.

A git status poll that sees a deleted-side conflict resolved drops its
placeholder record but leaves the tab to the tab reconcile; frames then
brought main's edit record back. Main now closes that tab.

* test: type the rename command as a window layout command

* fix(editor): map a moved tab's owned id on restart only; main records a renamed file only for a tab it shows

* fix(editor): restore a moved tab's file under the owned id the tab names

Restart and frames now agree: neither rekeys the tab, and a frame that
redraws one workspace never points its tab at a same-path file in
another. A rename that changes nothing leaves no empty file list.

* test: a moved tab's owned id stays apart from a sibling's same-path file in any frame
2026-10-16 19:54:52 -07:00
Žan Mlakar 3f651a63ab Fix GitLab label, member and assignee edits through glab (#28776) 2026-10-16 19:54:11 -07:00
Neil 60b8e97451 fix(ssh): a relay on another node of a shared network home is unverifiable, not exited (#29928)
* fix(ssh): a refused relay socket on a network home is unverifiable, not exited

On a shared NFS home behind a round-robin SSH alias, the next connect can land on a
different node than the relay's. That node's kernel refuses the socket and its lsof
sees no holder, so the probe called a live relay exited and its terminals were retired.
A refused socket now proves exit only on a filesystem local to the probing machine.

* fix(ssh): read the socket filesystem on macOS hosts whose Node lacks statfsSync
2026-10-16 19:47:41 -07:00
Neil aa05edb8d6 fix(wsl-host): a failed removal no longer leaves the host stuck as "being removed" (#29921)
remove() marked the host as being removed, then awaited disconnect and
releaseHolder with no cleanup on failure. A throw (EBUSY/EPERM on Windows)
left every later connect and runtime call refused. Clear the mark in a
finally, keep the record and its visible work (dropped only on success), and rethrow
so the user sees the error and can retry.
2026-10-16 19:35:11 -07:00
Neil 49450b7144 fix(projects): stop offering a project on a paired server's SSH host, and say where to add it (#29919)
Since #27609 a server serves no SSH target and refuses repo.addRemote, yet the add-project dialog
still offered "Project on this server's SSH host" and the refusal read "SSH connection not found
or not connected". The row is now disabled with a message pointing to adding the SSH host on this
computer, and the server's refusal says the same for older clients that still offer it.
2026-10-16 19:33:47 -07:00
Neil 03be5bb9b8 fix(ssh): a Disconnect or Remove during a background connect sticks (#29918)
A background connect (managed tunnel, census, runtime context) that was
waiting for the old transport to close resumed after the user's
Disconnect or Remove host and registered a fresh connection. Re-check the
attempt token after that await so the cancelled attempt never registers.
2026-10-16 19:32:51 -07:00
Jinwoo Hong 776c791554 feat(orcad): ship the mobile web page so phones paired to orcad get it (#29882)
* feat(orcad): ship the mobile web page so phones paired to orcad get it

orcad never carried out/mobile-web, so it did not advertise
mobileWeb.bundle.v1 and phones paired directly to an orcad server fell
back to the app's built-in native screens.

The built page now ships in every orcad package at out/mobile-web, the
path getBundledMobileWebBundleRoot already probes under the install
root. Its manifest is a fixed artifact; its content-addressed assets
join the hashed artifact list from that manifest, as one shared copy in
the template's common files. build-orcad fails when the page is missing
or stale, and CI lanes that build orcad now build the page first.

* ci(orcad): check out mobile/ for the Windows host template build

* refactor(orcad): copy the page tree whole and build it once per CI run

The guard already refuses unlisted files, so staging copies the
bundle directory as native-chat-visuals is copied. The shipped template
takes the page the qualification lanes tested, the e2e build artifact
carries it, and the PR smoke builds it through install-mobile-dependencies
behind its own scope step. Drops tests that duplicated the guard's own.

* fix(orcad): refuse a template page asset name that escapes the slot
2026-10-16 19:29:41 -07:00
Neil 6bbc3cbb5b fix(gitlab): load MR reviewer choices from the paired server that owns the repo (#29917)
The runtime GitLab API answered listAssignableUsers with an empty list, so the reviewer picker
was always empty for a paired server's repo. Add an optional gitlab.listAssignableUsers RPC on the
host; a host that predates it answers method_not_found and the picker stays empty as before.
2026-10-16 19:23:49 -07:00
NeilandClaude ed147d5d68 fix(ssh): re-check a macOS host's ended launch-session warning so another desktop's restart can clear it (#29915)
* fix(ssh): re-check a macOS host's ended launch-session warning so another desktop's restart can clear it

The ended verdict was cached for the whole SSH session, so when another desktop restarted the server under a live session, this desktop kept the warning. It now expires like every other verdict: a live re-probe clears the note, and an unanswered one keeps the proven verdict.

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

* fix(ssh): only a live probe clears a proven launch-session end, and a failed recheck republishes it

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

* fix(ssh): a failed launch-session recheck restores the warning only for the same live connection

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-16 19:18:09 -07:00
NeilandClaude 710120991f fix(ssh): relay-era pane reattach keeps output printed after its screen, and an exited or abandoned pane never comes back (#29910)
A reattach applies the relay's screen from main's output stream instead of the invoke reply, so its clear never erases output printed after the snapshot. An endpoint refresh re-reads leases after asking its relays, so a pane that exited meanwhile is not republished live. An attach answered after its pane left hands its viewer back to main.

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-16 19:06:20 -07:00
Brennan Benson 634ca45fbb feat(acp): hosts install a managed runtime over RPC, and mark agents whose program Orca provides (#29024)
* feat(acp): hosts install a managed runtime over RPC, and mark agents whose program Orca provides

managedRuntime.status / managedRuntime.install / managedRuntime.cancel, negotiated by
managed-runtime.install.v1, act on the host that receives them, the one that runs the agent's
chats, with no workspace needed. A status read naming the revision it shows is a long-poll on its
own call-queue lane, so every client sees another's start, progress and outcome.
agentSession.agents marks an agent whose program Orca provides on that host.

* fix(runtime): install status says a copy outside Orca's runs only when a start would really run it

* fix(runtime): install status is pushed to subscribers instead of long-polled, and Cancel answers with the outcome

managedRuntime.subscribe {agent} sends a full snapshot on subscribe, then one
snapshot per install state change, and closes with the transport. It replaces
the managedRuntime.status long-poll and its slot registrations.
2026-10-16 18:47:12 -07:00
Neil fd1c6a592c fix(orcad): never claim or reclaim another machine's server, and recover a stale lock without displacing a live one (#29902)
* fix(orcad): never claim or reclaim another machine's orcad, and serialize stale-lock recovery

* fix(orcad): judge data-root locality without a synchronous spawn

* fix(orcad): break a recovery lock only once its owner provably exited

* fix(orcad): publish the recovery lock with its owner in one rename

* fix(orcad): recover a stale lock in generations so no live reclaimer is displaced

* fix(orcad): keep a released recovery generation until the stale lock is replaced

* fix(orcad): fail closed on an unreadable recovery owner, and never let cleanup fail a won lock

* fix(orcad): key recovery locks by the stale record alone
2026-10-16 18:15:32 -07:00
Brennan Benson 64e544a442 feat(mobile): Resume in New CLI from a phone chat tab (#29783)
* feat(mobile): Resume in New CLI from a phone chat tab

A chat tab on the phone had only Close in its long-press menu, so a chat
that would not load left no way to reach its conversation. The phone now
offers Resume in New CLI, like the desktop tab menu: the chat's host
prepares the conversation and the phone opens a copy in a new terminal tab.

The resume and fork startup builders move to src/shared so the desktop and
the phone build the same command.

* test(mobile): type the fake terminal create instead of casting its calls

* fix(mobile): pass the host's fork source as is; match the quoted args in the test

* test(mobile): give the fake host client the untyped mock the other client fakes use

* style: format the shared resume builder and phone fork files

* Fix mobile chat CLI fork creation on owning host

* Fix mobile chat fork lint and split create owner helpers

* Keep chat fork beside phone anchor across tab groups

* Combine mobile terminal runtime type imports

* Repair mobile create CI contracts after chat fork extraction

* Classify chat fork preflight refusals and preserve mobile create recordings

* Keep route-owned create cleanup quiet after unmount

* Preserve typed runtime repair refusal in chat fork preflight

* Preserve checked store through mobile chat fork preflight

* Register mobile chat fork runtime fixture before test setup

* Remove unused mobile create recording value

* style(mobile): format the chat fork create files; move fork reply messages into the fork feedback module

Formatting pushed the create hook past its line limit, so the fork-only reply message, partial-success warning and host capability check now live beside the other fork messages. Same order and copy as before.

* Keep quick command mutation aligned with formatted request

* Keep mobile chat CLI forks in terminal view

* Pass mobile chat resolution its existing controller arguments

* Match the mobile create fixture mock layout

* Keep mobile chat CLI forks open when placement is unavailable

* Brace placement contract fixture checks

* test(mobile): keep the prompt controller mock under its line limit after the main merge

* Expose pane admission in the runtime test fixture type

* Complete the chat fork contract controller fixture
2026-10-16 18:10:24 -07:00