Commit Graph
6229 Commits
Author SHA1 Message Date
Jinwoo Hong b8264e860b Main drops the layout of workspaces the catalog no longer validates (dark, layout-owner build only) (#30177)
* feat(layout): main drops the layout of workspaces the catalog no longer validates (dark, layout-owner build only)

* rename the paired-host check so the switch-off bundle keeps its names

* a repo id two hosts register skips the listing purge
2026-10-17 22:19:35 -07:00
Neil 904259d122 fix(runtime): keep a restored unfinished agent unverifiable for the hydration window, not 30 minutes (#30186) 2026-10-17 22:14:38 -07:00
Jinwoo Hong aff4ee7ac7 feat(sidebar): main produces this host's workspace rows, checked against the window (dark) (#30184)
* feat(sidebar): main produces the local workspace rows, checked against the window (dark)

* fix(sidebar): workspace rows invalidate the listing only on metadata saves; prove it on the real cache

* fix(sidebar): workspace rows merge the listing fresh instead of invalidating the shared cache

* chore(sidebar): workspace rows comments say what the fresh merge reads
2026-10-17 22:10:09 -07:00
Jinwoo Hong 303c7c91ef fix(sidebar): main's temporal owner reads the catalog the way the window does (dark) (#30166)
* fix(sidebar): main takes its row baseline from the session's catalog and skips unchanged listings

* fix(sidebar): read catalog listings the way the window's merge does

* fix(sidebar): a seeding listing re-samples, and listings compare with the window's equality
2026-10-17 21:32:33 -07:00
Jinwoo Hong 3aad2b17b0 feat(sidebar): dark phone feed of the sidebar inputs (sidebarInputs.read) (#30161)
* feat(sidebar): dark phone feed of the sidebar inputs (sidebarInputs.read)

* fix(sidebar): feed review: fresh read on ready, notices kept per revision, one flush owner, closed connections never subscribe

* fix(sidebar): feed skips values JSON cannot hold; a failed read forgets its notice

* fix(sidebar): name the feed batch key by where the batch starts from
2026-10-17 20:46:10 -07:00
Kelvin Amoaba 5c4c1edc64 feat(terminal): tell users once that the diff panel in a Claude pane is Claude's own (#29818) 2026-10-18 01:49:10 +00:00
Jinwoo Hong ce2d60834c test(pty): the launch race join test no longer waits on real disk I/O (#30132) 2026-10-17 16:54:24 -07:00
Neil ba9c7eadbf Add Reveal in Finder to the file header menu (#30129)
* Add file location actions to the editor header

* Wait for the terminal spawn join before releasing race-test gates
2026-10-17 16:52:27 -07:00
Neil d71e3e9d45 fix(serve): orca serve on orcad serves the browser client and answers plain HTTP (#30120)
* fix(serve): orca serve on orcad serves the browser client and answers plain HTTP

orcad, the default local orca serve host since the compatibility launcher,
was never handed the app's web client directory, so its HTTP server had no
request listener: every GET hung and no Web client URL was printed (#30100).

The CLI now passes the app's unpacked out/web to orcad via --web-client-root,
a server without a web client answers plain HTTP with 404 instead of hanging,
and packaging fails when the unpacked web client or a file it loads is missing.

* test(serve): pin orca serve handing orcad the app's web client root
2026-10-17 16:51:37 -07:00
Jinwoo Hong 45f7429600 Save-divergence detector: skip empty workspaces, see dropped tab-bar entries and kept pane trees (dark, layout-owner build only) (#30125)
* Save-divergence detector: skip empty workspaces, see dropped tab-bar entries and kept pane trees (dark, layout-owner build only)

- A workspace key that names nothing (or only places a stored closed-tab record) is no longer
  reported as "workspace added/removed" or "terminal row owner changed".
- A save that drops a terminal tab's tab-bar entry while keeping its row is reported, read from
  the stored session before the Loader rebuilds the entry.
- A pane tree main keeps over the window's (windowSaveForMain) is reported, so a window-side
  pane repair shows up.

* Detector: keep reporting empty-row flips, cover every overridden pane tree and one-sided closed tabs (dark, layout-owner build only)

* Detector: a save with no pane tree for a tab main keeps one for is reported too (dark, layout-owner build only)

* Detector: a closed tab record moved to another worktree in one save is reported (dark, layout-owner build only)
2026-10-17 16:49:24 -07:00
Neil 27cfee6f7e fix(ssh): a Disconnect stays disconnected, and Settings and the status bar agree (#30130)
A user's Disconnect of an SSH host is now recorded for the session, and background
dials (a managed server's tunnel and status checks, census, host-resume recovery,
pane and automation restores) refuse it until an explicit Connect. Disconnecting a
managed host from Settings also stops its server's status checks, and the status
bar's Disconnect of a managed server also disconnects the SSH host it runs on, so
both surfaces read the same verdict.

Fixes #30086
Fixes #30085
2026-10-17 16:25:42 -07:00
Jinwoo Hong d9a61432c8 Main's layout Loader keeps one pane per terminal on restore (dark, layout-owner build only) (#30110)
* fix(layout): main's Loader repairs a terminal bound in two panes of one tab (dark, layout-owner build only)

* fix(layout): a window save stores main's repaired pane tree, so the duplicate-terminal repair survives the save (dark)

* test(layout): the SSH duplicate-terminal case builds an SSH-hosted tab
2026-10-17 16:03:56 -07:00
Jinwoo Hong a039e9064b feat(sidebar): main keeps the list's time rules on one clock, checked against the window (dark) (#30104)
* refactor(sidebar): move the Smart comparator and attention scoring to src/shared/sidebar-list

Main needs them to own the frozen order. Renderer helpers they depend on move with
them; the window imports the shared copies. No behaviour change.

* refactor(sidebar): one shared ranking for the frozen sort order

The window's sort memo and the palette's Smart sort now call rankWorktreesForSort,
so main can produce the same order from its own records. The pure state-history
step moves to shared for the same reason. No behaviour change.

* feat(sidebar): main keeps the list's time rules on one clock, checked against the window in dev and test

* test(sidebar): fake-clock parity of main's temporal owner against the window

* fix(sidebar): main restarts the settle window only where the window's sortEpoch bumps

* test(sidebar): type the owner harness without an assertion

* fix(sidebar): main's temporal owner stays idle until a dev or test window begins a session

* fix(sidebar): main reads Hide sleeping from the field the window persists

* refactor(sidebar): keep the begin params in their own contract module so the snapshot schema compiles out

* chore: restore files the merge's formatter rewrote to their origin/main content
2026-10-17 15:57:54 -07:00
Neil efcbe1a564 fix(browser): close a client-hosted ghost tab on a host with no browser engine (#30119)
On a headless orcad with no browser provider, every browser RPC went to a
rejecting proxy, including browser.tabClose. Client-hosted page records and
session rows are runtime records, so closing them never needed the engine;
the proxy now retires them, and orcad's provider proxy checks its own page
registry before asking a provider that never held the page.
2026-10-17 15:46:37 -07:00
Jinwoo Hong 9586a3fbb5 feat(sidebar): main serves workspace icons as small data URLs for the phone (dark) (#30046)
* feat(sidebar): main serves workspace icons as small data URLs for the phone, unused until the phone switch

* fix(sidebar): settle review findings in the icons RPC and the manual-redirect port fix

* docs(sidebar): scope the icon fetch-slot bound to a lone read

* fix(sidebar): gate the icons RPC behind the sidebar-inputs switch; prove re-encoding end to end

* fix(sidebar): restore two files the merge reformatted; one pure annotation for the icons params

* fix(sidebar): restore two files the pre-commit formatter rewrote during the merge

* fix(sidebar): icons fetch through their own manual-redirect request; shared desktop HTTP client back to main

* fix(sidebar): settle review findings in the icons manual-redirect request

* fix(sidebar): end a bodyless icon response at once; clearer fetchRepoIcon doc
2026-10-17 15:10:49 -07:00
Brennan Benson 6287ff6f62 One spawn step writes a terminal's pane identity for both ways Orca opens terminals (#29884)
* refactor(pty): one spawn step stamps a terminal's pane identity for both spawn lanes

The window spawn controller and the host runtime's caller each wrote
ORCA_PANE_KEY/ORCA_TAB_ID/ORCA_WORKTREE_ID and jcode's runtime dir, in
different layers, so per-pane env behavior drifted (the jcode daemon
pre-warm exists only on the window lane). stampPtySpawnPaneEnv now writes
them in both controllers from the spawn's own tab/leaf/worktree ids;
buildTerminalWorkspaceEnv no longer stamps them. No behavior change: a
TEMPORARY prewarmsJcodeOnDaemonHost input keeps host daemon spawns from
pre-warming until its own PR.

* test(runtime): read a fake spawn's pane env without type assertions

* test(pty): host serializer-settlement spawns name their pane by tab and leaf, as the runtime does

* fix(pty): preserve caller runtime directory after managed preparation fails
2026-10-17 13:40:44 -07:00
Brennan Benson b9fa92bbd0 feat(acp): pause an agent's chat starts while its shared sign-in changes (#28649)
* feat(acp): an agent's chat starts wait out a change to its shared sign-in

A launch spec can carry a gate that refuses new chat starts while an operation
replaces or removes the sign-in all of that agent's chats share on this host,
and aborts starts already under way when one begins. Antigravity's row sets it.
The refusal reuses the accountSwitchInProgress reason, now worded for any agent.

* Drain ACP starts before stopping chats for account changes

* Forward gated aborts into ACP launch preparation

* fix(acp): the launch gate holds every chat child it admits until it exits, so an operation can't run beside a chat still starting

A start leaves the gate when its child is up, but the host only lists the chat as loaded once its
attach finishes. A sign-in switch (and later Remove) in that window found no start to abort and no
loaded chat to stop. The gate now keeps each admitted child until it exits, and an operation closes
and proves gone any it still holds after the loaded chats stopped; otherwise it fails without
running.

* refactor(native-chat): one helper for failure sentences that only name the agent

Keeps agent-session-failure-words.ts under its line limit now that main grew it; the five sentences
read the same.

* fix(acp): a conversation copy runs the agent too, so it waits out a sign-in change like a chat start
2026-10-17 13:16:51 -07:00
Brennan Benson 21e3d76576 feat(linear): edit and delete Linear comments from the CLI (#30003)
* feat(linear): edit and delete Linear comments from the CLI

Adds orca linear comment edit <commentId> and orca linear comment delete <commentId>
(alias rm). The comment id alone names the comment; Orca finds the connected workspace
that owns it unless --workspace is given. Both refresh a linked worktree's issue and
report unconfirmed writes with a read-back step. The orca-linear guide documents them.

* test(linear): use a checked linked worktree fixture

* fix(linear): bound and cancel comment actions
2026-10-17 13:14:48 -07:00
Brennan Benson 216e3e42a6 fix(native-chat): a Codex chat is Working only while Codex holds the send (#29922)
* fix(native-chat): a Codex chat is Working only while Codex holds the send

Working read 'a send still pending delivery', so any echo or idle signal
that never arrived (a hook-blocked prompt, an answer naming no turn, idle
before a timed-out send was written) kept the chat Working, Stop up, the
queue frozen and /compact refused until the agent exited.

Codex now reports which sends it still holds: unanswered, answered into a
turn that has not ended, or answered into none while a status read after
that answer says the thread runs. Replies and statuses are ordered as they
are read, so a stale status cannot release a newer send. The host reads
that set (plus the short window while it hands a send over) for Working,
publishes a send the child stopped holding as in doubt on every page and
frame so older clients agree, and republishes when the set changes with no
row. The Codex idle latch and its release are removed.

* fix(native-chat): name the handover journal type; reverse without mutating

* test(native-chat): a lost Codex answer while idle is no longer the chat's work; run the held-sends test with the database runtime

* fix(native-chat): bound work updates and own command handovers

* refactor(native-chat): reuse session delivery plumbing

* test(native-chat): allow separate ownership restatements

* test(wire): resolve the release tab predicate after relocation

* test(native-chat): reuse existing promise and empty-batch primitives
2026-10-17 13:14:15 -07:00
Jinwoo Hong b389854b25 feat(sidebar): ui.setCollapsed changes one section's collapse without undoing another device (#30044)
* feat(sidebar): ui.setCollapsed changes one section's collapse, ordered per writer

* fix(sidebar): ui.setCollapsed accepts ui.set's callers; require a fresh writer per counter

* feat(sidebar): register ui.setCollapsed only with ORCA_SIDEBAR_INPUTS=main

* test(sidebar): hidden-app check of ui.setCollapsed in both switch builds

* test(sidebar): off-census covers every gated method; e2e skips before launching

* test(sidebar): type the gated method-name set as strings

* test(sidebar): type the collapse test's dispatch context
2026-10-17 12:56:37 -07:00
Brennan Benson 2117d777be feat(acp): Antigravity in the Chat UI over its ACP server (#28595)
* feat(acp): open Antigravity in the Chat UI over its ACP server

With the experimental chat setting on, Antigravity chats run Google's
Antigravity ACP server, which Orca installs only when the person clicks
Install: a pinned 1.3.0 release whose archive and both executables are
verified by hash and size (macOS arm64/x64, Linux x64/arm64). A start
never downloads it.

Each host keeps one private sign-in folder for these chats under Orca's
data folder, made owner-only before every start; the server keeps its
sign-in there as a file. A chat signs in with the server's Google method
through the existing auth-required retry; with no saved sign-in the
server's sign-in prompt ends the chat signed out. The Agent Permissions
setting maps to the server's own mode, and a question the server asks
shows as a question that takes one of its choices, never auto-answered.
A chat reopens its session with session/resume.

* fix(acp): separate Antigravity chat command and require pinned host

* test(acp): assert Antigravity launch arguments across hosts

* feat(acp): Antigravity chats offer Ask, Accept edits and Full access per chat

Each mode maps to the server's own (default, auto_edit, yolo). No Approve for me:
the server has no automatic reviewer. Accept edits gets its own description,
since auto_edit approves file edit tools only.

* feat(acp): Antigravity installs from Settings and Accounts, and says its download size

The Antigravity chat server installs only when the person clicks Install. Besides
the chat's own Install card, Settings > Chat gains a Providers section and the
Antigravity accounts section gains the same install row for its host (never a WSL
distro), both following the host's install status. A host now reports its pinned
release's download size, so an idle row reads "111.5 MB download." instead of
"Not installed.".

* test(acp): the launch parity install puts the Antigravity harness beside its server

* fix(acp): Antigravity chats also drop the gcloud project variables

* fix(acp): Antigravity command rows, a refused /logout, the risky Allow Always warning, and an install that proves the server runs

- A command's line and output now show in Orca's command row: the dialect maps the server's
  command_line/combinedOutput/exitCode to the shared shape and drops its duplicate output copy.
- The server's own /logout is hidden from the command list, and a message that is only /logout is
  refused before it is sent: it would sign out every Antigravity chat on this host outside Orca's
  sign-in gate. Dialects can now name withheld commands; the refusal has its own plain sentence.
- The server's prompt-injection warning on "Allow Always (risky)" is shown on the approval card.
- Install starts the unpacked server once under a throwaway home, asks initialize only, and keeps
  the copy only if it answers as the pinned release and its process is proven stopped.
- The server runs with PYTHONUNBUFFERED=1; its environment lives beside its runtime.
- The permission mode option is found by its category, so the spec no longer names it.

* fix(antigravity): the install check accepts only the protocol version Orca's connection speaks

* fix(acp): keep the structured session create support within its line limit

Main's file already sat at the limit, so the account-home services pass inline instead of
through a variable.

* test(acp): Antigravity's launch snapshots follow main's per-chat permission launches

Main now starts ACP chats without full access and applies the chat's own mode after
start, as the Grok and OpenCode snapshots already record.
2026-10-17 12:48:22 -07:00
Jinwoo Hong cd29f5f3c2 feat(sidebar): main keeps a registry of workspace creations in progress, checked against the window in dev and test (#30021)
* feat(sidebar): main keeps a registry of workspace creations in progress, checked against the window in dev and test

* fix(sidebar): open the runtime creation entry after the create's own repo resolve

* refactor(sidebar): one creation handle opened after the create's repo lookup; move create startup resolution to its own module

* feat(sidebar): put the creation registry behind the ORCA_SIDEBAR_INPUTS dark build switch

* test(sidebar): cover the creation registry with the switch off and the gated creation id
2026-10-17 12:43:12 -07:00
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