Skip to content

Sync upstream main (6fcc183ab9, 1308 commits) - #1

Merged
HyperBlaze456 merged 1309 commits into
mainfrom
sync/upstream-20261017-205443
Oct 17, 2026
Merged

HyperBlaze456 merged 1309 commits into
mainfrom
sync/upstream-20261017-205443

Conversation

@HyperBlaze456

Copy link
Copy Markdown
Owner

Merges 1308 upstream commits: stablyai/orca@45e8306...6fcc183

Conflicts

  • src/main/runtime/rpc/methods/index.ts
  • src/main/runtime/terminal/lifecycle/pty-handle-invalidation.ts
  • src/renderer/src/lib/titlebar-worktree-history-controls.test.ts
  • src/shared/protocol-version.ts
  • src/shared/rpc-contract/rpc-params-catalog.generated.ts

Resolution (Claude)

upstream 동기화 충돌 해결 (sync/upstream-20261017-205443)

결과: 충돌 전부 해결. pnpm tc, pnpm run verify:rpc-params-catalog, 포크 테스트 85개 파일(763개 테스트) 모두 통과. 머지는 진행 중 상태로 git add -A까지만 했고 커밋하지 않았습니다.

충돌 파일

  • src/main/runtime/rpc/methods/index.ts — upstream: workspace-layout import 변경, MOBILE_RELAY_HOSTS/SIDEBAR_INPUT/AUTOMATIC_WORK 등 메서드 추가. 포크: TRIVIUM_METHODS 등록. → upstream 버전 위에 TRIVIUM_METHODS import와 목록 끝 항목만 다시 추가.
  • src/shared/protocol-version.ts — upstream: capability 상수를 새 파일 src/shared/runtime-capability-constants.ts로 옮기고 import로 바꿈. 포크: TRIVIUM_WORKSPACE_RUNTIME_CAPABILITY 정의와 RUNTIME_CAPABILITIES 등록. → upstream 버전을 기준으로, 상수 정의(주석 포함)는 runtime-capability-constants.ts로 옮기고, protocol-version.ts에서는 import 목록과 RUNTIME_CAPABILITIES 끝에 추가.
  • src/main/runtime/terminal/lifecycle/pty-handle-invalidation.ts — upstream: 기존 orca-runtime-invalidate-all-handles-for-pty.ts를 이 경로로 옮기고 abstract class로 바꾸고 @ts-nocheck 제거, 부모는 workspaces/known-workspace-file-target. 포크: 상속 체인 중간에 OrcaRuntimeWithTriviumWorkspaceTarget를 끼워 넣음. → upstream 파일 그대로 쓰고 부모 클래스만 Trivium 클래스로 바꿈.
  • src/main/runtime/orca-runtime-trivium-workspace-target.ts → src/main/runtime/workspaces/trivium-workspace-target.ts (포크 파일 이동) — upstream이 부모 클래스 파일을 workspaces/로 옮겨서, 옆으로 이동하고 import 경로를 고치고 abstract class로 변경(부모가 abstract 멤버를 갖게 되었기 때문).
  • src/renderer/src/lib/titlebar-worktree-history-controls.test.ts — upstream: 저가치 테스트 정리(test: remove low-value coverage and shorten expensive fixtures stablyai/orca#29135)로 파일 삭제. 포크: 'trivium' 검사 한 줄 추가. → upstream 삭제를 따르고, 포크 검사만 titlebar-worktree-history-controls-trivium.test.ts라는 별도 파일로 보존.
  • src/shared/rpc-contract/rpc-params-catalog.generated.ts — 생성 파일. upstream 버전을 받은 뒤 pnpm run generate:rpc-params-catalog로 재생성(trivium.* 5개 메서드 항목이 다시 들어감).

충돌은 아니지만 고친 파일 (upstream 변경에 포크 코드를 맞춤)

  • src/renderer/src/components/trivium/use-trivium-workspace-status.ts, use-trivium-projection.ts, trivium-workspace-query.ts — upstream(refactor(renderer): name target converters by meaning; drop re-export shims stablyai/orca#27992)이 runtimeTargetForExecutionHostId를 runtimeRpcTargetForAuthorityKey로 이름을 바꾸고 @/runtime/runtime-client-target의 RuntimeClientTarget 타입 재-export를 없앰. → 새 이름으로 바꾸고 타입은 shared/runtime-client-target에서 직접 import.
  • src/renderer/src/components/trivium/TriviumComposerNewSession.react.test.tsx — upstream이 프롬프트가 있는 새 탭 실행 방식을 바꿈: 이제 렌더러가 agent.launchReplay RPC를 직접 부르지 않고, 새 탭의 터미널 pane이 첫 spawn 때 PTY를 알려줘야(settleAgentLaunchPaneStart) 프롬프트를 붙여넣음. 테스트에는 pane이 마운트되지 않아 "Starting Claude…"에서 멈춰 2개 테스트가 실패했음. → 쓸모없어진 callRuntimeRpc 모의를 지우고, pane 대신 settleAgentLaunchPaneStart(tabId, leafId, 'pty-agent')를 호출하도록 수정. 제품 코드(use-trivium-agent-launch.ts)는 변경 없음.

사람이 확인하면 좋은 것

  • Trivium 컴포저의 "새 세션으로 보내기"는 이제 upstream의 새 경로를 탑니다: 탭의 pane이 실제로 spawn되어야 "Sent to …" 안내가 뜹니다. 실제 앱에서 Trivium 페이지에 머문 채로(터미널 탭이 화면에 안 보이는 상태로) 실행했을 때도 pane이 마운트·spawn되어 안내가 정상으로 바뀌는지 한 번 눈으로 확인해 주세요. 마운트되지 않으면 "Starting …"에 머물 수 있습니다.
  • 기존 titlebar 테스트 파일은 upstream이 지웠으므로, 포크의 Trivium 검사만 별도 파일로 남겼습니다. 필요 없다고 판단되면 지워도 됩니다.

Checks run locally: pnpm tc, verify:rpc-params-catalog, fork test files.

🤖 Generated by ~/.orca-upstream-sync/sync.sh with Claude Code

AmoabaKelvin and others added 30 commits October 16, 2026 12:11
…isting project (stablyai#29651)

* fix(projects): adding a project through a symlink alias reuses the existing project

On a managed host, adding repo-link (-> repo) created a second project for the
same checkout while re-adding repo was deduped. Compare the real path, resolved
on the host that owns the files, so an alias finds the tracked project; distinct
checkouts keep distinct real paths and stay separate. Local folder workspaces
get the same check.

* test: run fixture git through process-host
…ring an SSH disconnect (stablyai#29636)

* fix(notifications): notify for a managed-server task that finished during an SSH disconnect

* test: type the reconnect completion dispatch mock
…ain (dark, build switch off by default) (stablyai#29653)

* Record a restored editor tab's move to its owning workspace through main (dark, build switch off by default)

* Refuse non-editor and duplicate moves; route the move by workspace

* Type the move routing test's command
* test: batch lint fixtures and remove repeated runtime scan

* test(cli): consolidate owner coverage and remove false decoding proof

* test(renderer): retain IPC lifecycle behavior without copied inventories

* test: share floating panel behavior suite startup

* test: retire synthetic runtime constructor probes

* test: share saved terminal title runtime setup

* test: share native chat render cost setup

* test: share terminal close continuity runtime setup

* test(runtime): prove mobile geometry through desktop restores

* test(runtime): skip unused RPC catalog in bind-host coverage

* test: share transcript scroll suite startup

* test: share SSH lease candidacy suite startup

* test: exercise real gate authorization refusals

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
* test(e2e): stage the WSL inline-image payload in a file instead of a 25 KB typed line

* ci: run the real WSL lane when its inline-image spec changes
…oject browser (stablyai#29658)

A symlink to a directory was listed as a file on SSH and managed hosts, so
projects behind a linked path could not be reached. Follow links when
classifying entries; broken links and loops stay files.

Co-authored-by: Gideon <jjin6573@naver.com>
…i#29657)

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
…d input was lost (stablyai#29660)

* fix(terminal): tell panes their daemon died at once and say when typed input was lost

* fix(terminal): use the info toast the terminal suites already stub
…ts first new turn (stablyai#29664)

* fix(agents): a resumed agent's chat is readable on the phone before its first new turn (stablyai#10923)

* fix(agents): skip a dropped Codex resume and keep the seeded identity across restart

* fix(agents): seed resume identity before the spawn's first await and keep it through terminal evidence

* fix(agents): seed after the spawn commits, and name the session on host-built resumes
…y default) (stablyai#29659)

* Refuse a chat create whose close came first, behind the layout-owner switch

With ORCA_LAYOUT_OWNER=main, main remembers a close that reaches it before
the chat's create and refuses that create, or closes the chat as it lands.
The window then tells this machine's host about a cancel instead of
keeping a cancel tombstone; a host it cannot tell still gets the
tombstone. Paired hosts keep the tombstones, whose fence module moves to
lib/launch-cancel-tombstones-compat/.

* Close a chat whose create a cancel beat, on any create outcome

The local cancel now retires the chat on main (close and host-tab close),
so a create that landed first leaves no tab behind; a tab_not_found answer
means the create had not landed. Main closes the chat for any create
answer once a close was noted, including a refusal or a throw past commit,
and publishes no tab for it. The tombstone fallback notifies listeners.

* Keep the switched-off create path byte-identical

* Log, not throw, a failed close of a chat closed before create

* Run the close-before-create test in the Node SQLite project
…9663)

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
…9667)

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
…tablyai#29614)

* ci: build the WSL server template once

* ci: reuse validated Windows native addons in E2E preparation

* ci: avoid the unused standalone server build in SSH E2E shards
stablyai#29670)

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
…umes it, without widening its permissions (stablyai#29672)

* fix(ssh): reopening a tab slept from a paired desktop resumes its agent on the managed host

A paired client's worktree sleep stops the host's PTYs, but the resume
record its renderer writes never reaches the host, and the host is what
answers the tab open. Capture the record on the host from its own hook
store before the stop, applying the shared holder rule (an agent the
hook store proved exited leaves no record).

* fix(ssh): keep the launch recipe and record acknowledged stops when inventory fails

* fix(ssh): only a proven agent exit retires a pane's earlier sleep record

* fix(ssh): stamp the captured record's SSH host and keep a saved launch recipe

* fix(ssh): read the session id after the presence probe that may outlast it

* fix(agents): a resume reuses only its launch's recorded args, never permission-widening defaults

Resuming a session started as a hand-typed `claude` filled the missing
launch recipe from settings defaults, which carry the permission bypass.
Host tab-open resume, window cold restore and worktree wake now share
one rule: the recorded recipe, or none.

* fix(ssh): a WSL relay tag on a captured record is local, not another host

* fix(ssh): a capture retires when the hook store names another agent in the pane
…y default) (stablyai#29655)

* Open into a new split in one layout command (dark, build switch off by default)

Main makes the window's new split group and its first tab (terminal, editor
or browser) in one apply, so main never refuses the open for a group it lacks
and never stores an empty group. The window derives the split from its own
group tree in the three existing switch-point recorders.

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

* Open beside an emptied root split, and into a group main already holds

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

* Describe both openInNewGroup fallbacks in its doc comment

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

* Test: no split is read back beside a sibling that is itself split

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
stablyai#29673)

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
…same grid from any grid (stablyai#29668)

The web client positions a retained pane host from a measured tab-group body,
but the pane mounted (and fit xterm) a render before that measurement, at the
32px placeholder box, then refit when the measurement landed. Mount the pane
only after the first read.

FitAddon also divides by xterm's css cell, which is the rounded canvas width
over the current cols, so the same box proposed 51 cols from a 75-col replay
and 52 from a 52-col pane: a revealed pane resized twice. Fit with the
grid-independent device cell instead.
…ve-parity profile (stablyai#29669)

* test(orcad): wait for an idle-retiring daemon before deleting the serve-parity profile

A terminal daemon that loses its last client deletes its pid record before it
exits, so teardown that found daemons only through pid records skipped it and
raced its open files (EBUSY on Windows ARM). Record the pid before stopping
orcad and require that process's exit.

* test(orcad): record profile daemons before stopping orcad in launcher and stop-request tests

* test(orcad): wait for the daemon's child processes before deleting its profile on Windows

The terminal daemon runs with the profile as its cwd and its children inherit it,
so a child that outlives the daemon keeps the profile directory busy (EBUSY on
rmdir). Record the daemon's process tree before shutdown and require each
recorded child's exit, naming any survivor at the deadline.

* test(orcad): keep the real Windows process table out of the teardown unit tests
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
… stand-in does (stablyai#29682)

The fake Claude wrote .orca-sign-in-browser.link directly, so the link
poller could read it between create and write, see an empty link, and end
the sign-in. Write a temp file and rename it into place, as the real
stand-in script does. Flake introduced with the tests in stablyai#29566.
…ai#29683)

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
…i#29686)

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
nwparker and others added 28 commits October 17, 2026 00:50
…SH host moves again (stablyai#27285) (stablyai#30017)

* fix(ssh): link a folder workspace the server kept from an earlier move instead of conflicting (stablyai#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
* 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
…rder, and SSH/WSL host state, checked against the window in dev and test (stablyai#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
…ly) (stablyai#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.
…tart or turn signed out (stablyai#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
…pting (stablyai#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 (stablyai#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>
… against the window in dev and test (stablyai#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
…tablyai#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
…#30008)

* fix(mobile): the chat's Working line uses desktop's spinner (turning circle, hourglass under reduced motion)

* fix(mobile): keep working animations off interaction queue
…tablyai#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 stablyai#29973

Co-authored-by: Claude <noreply@anthropic.com>
stablyai#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 stablyai#29968

Co-authored-by: Claude <noreply@anthropic.com>
…the window in dev and test (stablyai#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>
…on retap and revisit (stablyai#30033)

* fix(mobile): prevent markdown and file tab reload and screen flicker on retap and revisit

* fix(mobile): preserve newer saves and refresh document metadata

* test(mobile): supply current document state in recorded reads

---------

Co-authored-by: Neil <neil@stably.ai>
…top renames (stablyai#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 stablyai#29975
…ld only) (stablyai#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>
…ts CLAUDE_CONFIG_DIR (stablyai#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>
…e desktop has no page (stablyai#29956)

* 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
…ccount after a switch (stablyai#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>
…uild switch (stablyai#29969)

* 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): 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
…ut command (dark, layout-owner build only) (stablyai#30039)

* Open into the window's emptied root beside a split branch in one layout command (dark, layout-owner build only)

A tab opened into a group the window kept empty reaches main with the
split that places it, now also when the group's neighbour is itself split
(named by the branch's first and last groups) or is another empty group
main lacks. Main makes the group with its tab beside that branch. Switch-off
builds send no such command.

* Comment the refusal of a branch main's tree does not hold (dark)
…es (stablyai#29979)

* 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
…de Code versions list differently (stablyai#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>
…ner build only) (stablyai#30041)

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

Main stores a tab's preview flag only while it is true. A non-preview
editor open created its tab-bar entry with `isPreview: false`, and making
a preview permanent by an edit, by "keep open" or by pinning wrote the
same, so the window's next save changed that tab in main's model (the
save-divergence check's `tab <id> changed: isPreview` after a CLI file
open). New tabs now carry the flag only when true, and those writers drop
it, by the rule E2-4 gave activation, now in one place (tab-preview-flag).

Claude-Session: 10569c9a-a0a1-4fc9-8469-0edfb7aed125
…ld only) (stablyai#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
…tablyai#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
# Conflicts:
#	src/main/runtime/rpc/methods/index.ts
#	src/main/runtime/terminal/lifecycle/pty-handle-invalidation.ts
#	src/renderer/src/lib/titlebar-worktree-history-controls.test.ts
#	src/shared/protocol-version.ts
#	src/shared/rpc-contract/rpc-params-catalog.generated.ts
@HyperBlaze456
HyperBlaze456 merged commit 6c7da8a into main Oct 17, 2026
@HyperBlaze456
HyperBlaze456 deleted the sync/upstream-20261017-205443 branch October 17, 2026 12:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants