Skip to content

[upstream #30513] fix(mobile): reread the host dictation mode when the session connects - #332

Open
innocarpe wants to merge 8009 commits into
mainfrom
ios-dictation-preference-refresh-20261019
Open

innocarpe wants to merge 8009 commits into
mainfrom
ios-dictation-preference-refresh-20261019

Conversation

@innocarpe

Copy link
Copy Markdown
Owner

Portfolio mirror of my contribution to upstream stablyai/orca.
Exhibition only — the real review/merge target is upstream.

Upstream

Summary

ELI5 The phone remembers whether you hold or tap the mic to dictate, but it only asked the desktop once, sometimes before it was even connected. Now it asks again when the connection comes up or the app comes back. ## What Changed refreshDictationMode in `use-mobile-sessi

Note

  • Do not merge this into innocarpe/orca main until the upstream PR is merged.
  • After upstream merges: sync fork from upstream, then close this mirror PR.
  • This open PR exists so visitors see in-flight work on this fork's Pull requests tab.

brennanb2025 and others added 30 commits October 17, 2026 00:07
…ai#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
…and fit the force-delete button to its notice (stablyai#29878)

* fix(sidebar): let kept-branch toast bodies fill the toast width

* fix(sidebar): close the force-delete menu when its toast is hidden in the stack

* fix(sidebar): keep focus out of a hidden toast after its menu auto-closes

When the toast stack hid a kept-branch or delete-failure toast, the menu
closed but Radix returned focus to the chevron inside the now invisible
toast, so Enter or ArrowDown reopened the menu floating over the app with
the "Always ..." item focused.

- Skip the close focus return while the toast is hidden.
- Refuse to open the menu while its toast is hidden.
- Treat toasts past Sonner's visible-toast limit (data-visible=false) as
  hidden too.

* fix(toasts): stop a folded toast's hidden overflow from re-expanding the stack

* fix(toasts): clip a folded toast to its card instead of disabling its content

The pointer-events rule left the unstyled star-nag card, which Sonner never
fades, visible below the stack but dead to the pointer. Clipping the folded
toast removes the overhang for every toast type, visible or not, so nothing
of a folded toast can sit outside the stack and re-expand it.
…ntity rekey (stablyai#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.
…ver keeps running (stablyai#30006)

A paired runtime (orca serve, managed SSH/WSL orcad) owns its terminals and keeps
them after this client quits, so only SSH relay terminals, which a relay kills
after its grace period, still raise the quit warning. Window close is unchanged.

Fixes stablyai#29916
…stead of guessing this computer (stablyai#30009)

* fix(renderer): resolveOwner keeps folder and project-group twins ambiguous; deprecate the runtime-owner aliases

* test(store): folder and project-group twins in the twin suite; no-host folder delete refuses

* fix review: a folder's own SSH connection names its owner despite twin project groups
…e stand-in (stablyai#30011)

* test(e2e): open and close structured chat tabs through an isolated Claude stub

* test(e2e): satisfy changed-code lint in the structured chat stub
…#30012)

The hostScope annotation on terminal list, worktree list and worktree ps
named --host local / --host ssh:<id> as the follow-up for an omitted host,
but none of those commands accept --host. Those hosts are reached through
the runtime that answered, so no flag selects them: report a null selector
and say so in the human scope line. Paired servers keep --environment.

Fixes stablyai#29901
…tablyai#30014)

On a headless server (orca serve, orcad) files.open and files.openDiff
answer with the bare renderer_unavailable token, which the CLI printed
as-is. Translate it in the CLI into a clear message with next steps,
keeping a stable renderer_unavailable code. The wire answer is
unchanged because the mobile app keys its preview fallback on it.

Fixes stablyai#29822
stablyai#30002)

* chore(ci): ratchet renderer importers of the deprecated runtime-owner resolvers

getRuntimeEnvironmentIdForWorktree and resolveRuntimeOwnerForWorktree read a
worktree two hosts publish as not-a-runtime, which callers treat as local. The
62 renderer files importing them are grandfathered; the list may only shrink.

* ci: register the deprecated runtime-owner ratchet step in the preflight phase map
…9089)

* fix(native-chat): paste a copied file into Chat the way dropping it attaches it

Copying an image file in Finder and pasting into Chat typed the file name
and attached nothing: the paste only attached a bitmap from the clipboard.
Copied files now go through the composer's drop route, on both the
app-menu paste and the paste event.

Fixes STA-10652

* refactor(native-chat): share the paste guards with the copied-files branch

* test(native-chat): late paste results stay dropped when the image waits for the clipboard read

* Keep copied attachments pending through preparation and composition

* Update file drop fixture settlement results

* Keep pending attachment capability current within its draft scope

* fix(native-chat): reserve pending chips before checking dropped files

A local file drop checked each file before adding it, with nothing in the
composer meanwhile: Send during the check went without the file, which then
landed in the next draft; unmounting mid-check lost it; and files queued
behind an input-method composition were lost on unmount.

Drops now reserve a pending chip per file before the check, so Send waits
as it already does for server-upload drops. The prepared attachment travels
through the existing composition queue as one operation, settles into its
draft scope after unmount, releases its chips when refused or disabled, and
reads the scope's current image capability when it applies.

* test(native-chat): server-upload chip calls carry no options without paste

* refactor(native-chat): move the composer's image chip list into its own hook

Main's server-owned paste pushed the attachments hook past the file length limit.

* fix(native-chat): a screenshot picture pastes as a picture on macOS, even beside its saved file

macOS lists a screenshot tool's saved file beside its picture, so the paste read as a file copy and attached nothing once the file was gone. Only a Finder copy also names its files as text.

* fix(native-chat): a macOS right-click paste lets the clipboard tell Finder files from a screenshot

The paste event hides Finder's file names, so a path-backed picture reserves its chip at once and the native clipboard decides: named files go to the drop route, a screenshot's picture is saved from the clipboard. Both paste routes now share the clipboard-picture save.

* test(native-chat): move the paste hook's test probe into its own fixtures file

The paste tests outgrew the test file length limit.

* test(native-chat): hand the paste probe's hook result out after render

* test(native-chat): give the remote image paste probe the copied-files callback

* fix(native-chat): a pending attachment chip shows a spinner and its name, not a stacked icon

A local drop now waits as a pending chip, which drew the kind icon, the file name and a centered spinner on top of each other. Without a thumbnail the chip shows the spinner above the name; with one, the dimmed picture keeps a spinner strip along its bottom.

* test(native-chat): a pending chip shows its name and spinner, no kind icon

* fix(native-chat): a macOS file paste keeps its own chips until the clipboard decides

A macOS paste of Finder files reserved one placeholder chip, asked the native
clipboard whether it held files or a screenshot picture, and then started a
separate attach for the files. Removing the placeholder did not cancel that
attach, and leaving the chat before the answer lost the paste.

The paste now reserves one chip per copied file before asking, and captures
the drop route's intake (owner, destination, chips) at the same moment. Files
continue those exact chips through the existing local check or server upload,
so removal cancels by the original chip and an unmounted composer still
finishes into its draft. A picture keeps the first chip and drops the rest.
Ordinary drops and the picker keep their fresh-admission checks.

* test(native-chat): a macOS file paste survives removal, unmount and disable through the real intake

Drives the real pending cache, composer attachments, external intake and paste
hook with a delayed clipboard answer: removed chips never attach (before or
after the answer, through input-method composition), an unmount finishes both
the local and paired-server routes into the original draft with the original
chip IDs, and disabling the composer meanwhile does not bring the paste back.
…' visit recency (stablyai#30015)

Converting an SSH host drops its relay-era rows, but recency stayed under
ssh:<target>|<id> keys, which the next startup prune discards because no row
matches them any more. Re-key that host's visits to runtime:<env>|<id> in the
same update, keeping the newer visit on a collision.
…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
Jinwoo-H and others added 29 commits October 18, 2026 18:40
…k, layout-owner build only) (stablyai#30439)

* fix(layout): a CLI split whose pane main closes before it mounts answers at once (dark, layout-owner build only)

* test(layout): the closed-before-mount split fails because of the close
…ght seams from their defining modules (stablyai#30440)

The three ipc modules re-exported symbols owned elsewhere. Importers and test mocks now name the
defining module, and the re-export lines are gone (no barrels).
…ting would stamp (stablyai#30445)

A tab restored before its worktree catalog loads gets a placeholder row. SSH placeholders had no
host, and a server's nested-SSH placeholder carried only runtime:E, so an ssh: host selection found
no matching owner and routed the terminal as a direct dial from this desktop. Stamp the SSH target,
and the server proxying it, from the repo row; leave a repo id that lives on several hosts unstamped.
…s the whole page and clicks land under the cursor (stablyai#30437)

* fix(browser): a remote viewer larger than the host's browser pane sees the whole page and clicks land under the cursor

Fixes stablyai#30341

* test: note why the screencast double is cast

* fix(browser): drop guest frames until the fitted size is applied; forget the cached CSS size when the frame size changes

* fix(browser): keep a press's release and markup inside the letterboxed remote frame

* fix(browser): resume live frames once fitted metrics land; remeasure the page after its frame size changes
…its own host's copy (stablyai#30447)

applyCreatedWorktree merged a create into any same-id row, so a local create could inherit a
server twin's stamp (or wipe it). Lineage refresh stamped a server's reply with the focused host and
replaced every same-id row, overwriting the local twin. Both now match id and host, and the lineage
reply is stamped with the transport it came from.
…e-SSH (stablyai#30441)

* fix(open-in): open a managed server's workspace in VS Code over Remote-SSH (stablyai#30332)

* fix(open-in): only open a server workspace over SSH when its files are on that host; validate the SSH link; let process-host quote code.cmd

* test(open-in): expect the workspace ref in the source-control Open in payload

* fix(open-in): pin an SSH-access server's editor host to the settings it was verified on
…nd cancel, on every Orca sign-in button (stablyai#30410)

* fix(cloud-auth): show the browser sign-in wait and offer its link from Relay sign-in

Clicking 'Sign in for Relay' handed the sign-in page to the OS browser and
then showed nothing, so a browser that never came forward looked like a dead
button, and the 5-minute wait ended silently. The Relay sign-in row now shows
the wait with a Copy sign-in link button, and a wait that times out toasts.

STA-10940

* fix(cloud-auth): show the sign-in wait on every Orca sign-in button

The waiting text and Copy sign-in link now come from one shared notice used by
Mobile Relay, Orca Account, Artifacts (settings, page and publish popover),
Share Skills, the unexpected sign-out card and dev tools. The copyable link is
dropped as soon as the browser answers, and a copy with no waiting link
reports a failure instead of doing nothing.

STA-10940

* refactor(cloud-auth): main owns the waiting sign-in link; renderer only mirrors it

The notice now shows exactly while main holds a live link: main announces the
link when the browser wait starts and clears it the moment the browser
answers, so 'Waiting for sign-in in your browser' no longer outlives the wait
during the token exchange. Only the newest attempt may publish its link, so an
older attempt reporting late cannot replace or later wipe it. The renderer's
separate pending flag and its resets are gone.

STA-10940

* fix(cloud-auth): tolerate a sign-in link read that returns no promise

The app-wide IPC tests stub every bridge method as returning nothing, and the
one-time link read called .then on that result.

STA-10940

* fix(cloud-auth): a torn-down link subscription ignores its late snapshot; restore auth bridge tests

A window's one-time sign-in link read could land after its subscription was
disposed and overwrite a newer value from the replacement subscription. The
auth-status refresh tests the earlier rewrite dropped are back, alongside the
link cases.

STA-10940

* feat(cloud-auth): sign-in buttons show Signing in… with copy-link and cancel links

Every Orca sign-in button greys out to 'Signing in…' with a spinner until the
sign-in ends. While the browser has not answered, a line offers 'Copy sign-in
link' and 'Cancel'; Cancel aborts the browser wait so a lost tab never locks
the button. Main owns the in-flight sign-in (waiting on the browser, then
finishing) and every window mirrors it.

STA-10940
…g unpriced models (stablyai#30421)

normalizeModelForPricing was an ordered chain of regexes in which a new point
release fell through to its major: claude-sonnet-5-5 was billed as Sonnet 5
(cache reads at $0.20 instead of $0.10), and any future opus-5-x / fable-5-x
would be too. claude-haiku-5-5 matched nothing and silently added $0 to a
total that showed no caveat.

The resolver now parses family + major + optional minor (family-first and
legacy version-first IDs) and only accepts an exact MODEL_PRICING key, so an
unknown version is unpriced rather than borrowing another model's rates. The
deliberate opus-4.x -> 4.8 and sonnet-4.x -> 4.6 fallbacks go with it.

Adds Sonnet 5.5 and Mythos 5/5.1 rows. Haiku 5.5 stays unpriced: it reprices a
whole request above 100K prompt tokens, which daily totals cannot express.

The Claude summary now carries hasUnpricedModels (as Codex has since stablyai#22073),
and both panes share one unpricedCostLabel helper for the caveat.

Fixes stablyai#30338
…tablyai#30446)

* Check sign-in duplicates against the listed account identity

A managed account folder's current Claude login can differ from the identity saved for it. The account list shows the current login, but the duplicate check compared the saved identity, so a sign-in could be missed or wrongly flagged. Duplicate detection now uses the same listed accounts as the account list, and the duplicate helper accepts account summaries. Tests cover a folder whose login email changed, and one where only the organization changed.

* Tolerate invalid Claude account rows during sign-in
… Haiku 5.5 (stablyai#30427)

* fix(usage): price Claude long-context requests per request, and price Haiku 5.5

Haiku 5.5 bills a whole request at higher rates once its prompt (input plus
cache reads and writes) exceeds 100K tokens. Claude usage was only kept as
daily totals, so that could not be priced and Haiku 5.5 stayed unpriced. The
existing Sonnet 4.5/4 200K tier had the opposite problem: it was applied as a
marginal threshold to each day's totals, so a day of short requests could be
surcharged and a single long request was under-charged (the Claude side of
STA-8117, fixed for Codex in stablyai#22360).

Each turn is now classified when it is aggregated, against its own model's
threshold, and daily and per-location rows carry the long-context subset as
an optional `longContext` field. A streamed turn that grows across the
threshold moves its whole share in the resume correction. estimateCostUsd
takes one token object and bills that subset at the long-context rates and
the rest at base, replacing the per-bucket tiering.

Saved per-file projections carry a projection version; files from an older
version are reparsed on the next scan, so history picks up the split without
touching the signed cache schema.

* fix(usage): keep the projection version out of the integrity hash

Hashing it made older inline reports fail validation and show the
'could not be validated' error; the scanner's version check alone decides reparse.
stablyai#30450)

* feat(sidebar): the owning host raises the workspace unread flag (dark)

* fix(sidebar): phone session open clears and spares the flag; refresh views; blur rescan

* fix(sidebar): throttle failed view retries, resend failed empty views, hold presence one refresh longer

* test(sidebar): switch-on window writes neither the unread flag nor a stamp
stablyai#30456)

* fix(native-chat): clicking Send or Stop keeps focus in the composer

A click focused the Send/Stop button; when it disabled (empty draft after the
turn, or while a Stop is in flight) Chromium dropped focus to <body>. A pointer
press on it no longer moves focus, matching Enter. Adds an e2e regression with
a turn-capable Claude stub. Fixes STA-10897.

* Tighten comments
… PowerShell (stablyai#30424)

* fix(windows): codex and claude get a prompt's double quotes in Windows PowerShell

Windows PowerShell 5.1 (and pwsh before 7.3) does not escape an embedded
`"` when it builds a native program's command line, so every quote in a
launch prompt was dropped, and quotes could split the prompt into extra
arguments. Orca's PowerShell codex and claude wrappers now escape their
arguments at runtime for the PowerShell that is actually running, through
one shared __OrcaNativeArgs function. pwsh 7.3+ already escapes, so it
gets the text untouched.

* fix(windows): escape agent args for .cmd shims under pwsh 7.3+ too

pwsh 7.3+ defaults to the Windows argument mode, which still passes
.cmd/.bat targets the Legacy way, so a codex or claude .cmd shim kept
dropping quotes there. The function now takes the program it escapes
for and names the modes that escape by themselves, and anchors the 5.1
trailing-backslash fix at the true end of the text (`$` also matched
before a final newline). Tests now run the real codex wrapper with a
quoted prompt, cover pwsh's Legacy mode and the newline case, and pin
the claude wrapper's call.

* fix(windows): strip comment lines from the PowerShell bootstrap payload

The shared escaping function pushed the encoded bootstrap to 27,324 of
the 28,000-character budget, leaving almost no room for a startup
command to ride in -EncodedCommand, so short commands fell back to
stdin (the path stablyai#7236 broke). Comment lines document the source but
only spend that budget in the payload; dropping them frees 3,732
encoded characters, more headroom than main had.

* fix(windows): quote agent args with "" where cmd.exe or Windows PowerShell reads them

Escaping embedded quotes as \" let pwsh 7.3+'s .cmd quoting expose a
prompt's `&` to cmd.exe, which then ran the rest as a command. Where
cmd.exe reads the line, or Windows PowerShell counts quotes naively,
the function now quotes the argument itself and writes each quote as
"", as process-host's windows-command-line.ts already does. That also
delivers text 5.1 used to strip, so its special cases go away.

The codex and claude wrappers now come as one fragment with the
function they call, the bootstrap test pins the startup-command budget
instead of restating the strip, and the cwd-restore comment leaves the
payload too.

---------

Co-authored-by: Orca Worker <orca-worker@localhost>
…ds, pinned by an acceptance table (stablyai#30451)

* refactor(renderer): one owner adapter for the deprecated-resolver folds, pinned by an acceptance table

resolveOwnerWithLegacyFallback answers as resolveOwner for a named host and for rows that carry a
host stamp, and keeps the deprecated answer (single focused runtime included) for missing and
unstamped rows. A full-owner acceptance table pins all matrix cases and asserts the only changes
from the deprecated resolver are the designed ones: twins and contested SSH hosts become ambiguous,
and a stale hub stamp routes through the server that names the target.

* refactor(renderer): the fold adapter shares resolveOwner's named-host rule

Clean-code pass: one namedHostForOwnerRef for both resolvers, floating counts as host evidence,
and the acceptance test drops non-null assertions.
…ion control holds in both layout builds (stablyai#30432)

The control record named a pane no window or main holds. A layout-owner build keeps resume
records on main's panes, so a record for no pane only exists when loaded from a profile.
Relaunch instead of reload, seed that record into the stopped profile, and read the CLI
tab's PTY from main's inventory, which both builds report.
…t guest (stablyai#30452)

* fix(browser): switch client-hosted profiles with staged guest replacement

Squashed from stablyai#30351 by @oliver-mee onto the current main.

Fixes stablyai#30350

* fix(browser): fence staged profile guests, release switch grants, carry host profile metadata

- A fence during automation retirement now revokes and stops the staged guest.
- A failed or late-settling switch releases its execution-host grant so the page can be recreated.
- Staging cleanup failures before handover stay visible to executor close.
- createPage/restorePage carry host-owned profile metadata to profile-switch-v1 desktops,
  so recovery and restore can rebuild a switched page.

* fix(browser): roll back a switch whose replacement died, and report a lost old guest

* fix(browser): resolve client-hosted pages before the server-local target path when switching profiles

* fix(browser): a staged profile-switch guest owns downloads it starts while loading

* fix(browser): keep the original placement until a timed-out switch settles

* fix(browser): rebind the original guest before releasing a rolled-back staged guest

* test(browser): type the harness guest release mock

* fix(browser): release the replaced generation's download transfers when a switch commits

* fix(browser): admit file transfers from a staged profile-switch guest while its switch is pending

* fix(browser): release only the replaced generation's download transfers after a switch

* refactor(shared): import BrowserNetworkExecutionHost from its own module, no re-export

* fix(browser): adopt a profile switch the client finished after the caller timed out

---------

Co-authored-by: Oliver Mee <102673257+oliver-mee@users.noreply.github.com>
…owner resolver (stablyai#30458)

Open-in owner, execution-host platform facts, doc-preview grants and the activation PTY inventory
read the owner through resolveOwnerWithLegacyFallback. Open in now refuses a twin instead of
launching it as this desktop's checkout; the others keep reading a twin as not server-owned. A row
whose only repo row is a server proxying its SSH target routes through that server. The
creation-action policy stays until browser creation folds with it. Deprecated importers 57 -> 53.
…efore startup continues (dark, layout-owner build only) (stablyai#30429)

* refactor(orcad): move startOrcad's option and handle types into their own module

Move-only: frees room in orcad-entry.ts for the layout-owner load repair.

* fix(orcad): run main's duplicate-terminal pane repair at orcad startup (layout-owner build only)

orcad builds its own runtime and never called the load-time repair, so an
orcad host kept a terminal drawn in two panes.

* test(orcad): type the hoisted test state without an assertion

* fix(orcad): await the startup pane repair so it lands before reconcile and RPC

* fix(startup): desktop awaits the duplicate-terminal repair before the window and RPC start (layout-owner build only)
…e holds (dark, layout-owner build only) (stablyai#30473)

* fix(layout): editor file records keyed by path and owner, so two hosts' files at one path stay apart (layout-owner build only)

Opening a runtime-environment file at a path a local file already held replaced the local record,
the stored-session patch and unsaved drafts matched by path alone, a move took every owner's record
of the path, and closing a bare-path tab removed both owners' records.

* fix(mobile): a windowless host's editor tab under its owned id shows as active, and retiring it clears the active file (layout-owner build only)

* fix(layout): a layout change's tab and pane holds end 15 s after it was sent when main never answers (layout-owner build only)

* fix(layout): main's view reads and prunes keep an active file under its owned id (layout-owner build only)
…-page readers off the deprecated owner resolver (stablyai#30478)

Thirteen read-routing callers (#4, #5, #10-#20 of the round-3 caller list) read the owner through
runtimeEnvironmentIdWithLegacyFallbackUnlessTwin. A workspace two hosts publish is no longer
searched on either host. The focus-read ratchet now counts the legacy-fallback adapter, which
reads the focused runtime inside an object literal its discovery skipped; the five F1 entries
that blind spot pruned come back. Deprecated importers 53 -> 40.
…h/connection/ and ssh/targets/ (stablyai#30479)

* refactor(ssh): move the ssh-connection and ssh-target cluster into ssh/connection/ and ssh/targets/

Pure git mv plus import and path-literal rewrites. ssh-connection-utils stays put; the next PR splits it.

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

* test(e2e): keep the release-checkout import at the v1.4.218 path

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
12.10.1 repairs a global-virtual-store slot an interrupted install left
without dependency links (pnpm/pnpm#16642); 12.12 adds addMissingPeerTypes.
The CI store-mode and terminal-perf gates follow the pin they were measured
against, and mobile/ and config/audit-tools record the same pnpm.
… a host's own re-dials (stablyai#30487)

* fix(relay): judge the reserve watch's 503 rule on non-drain 503s, not a host's own re-dials

The watch counted every director 503, and on 2026-10-18 ~98% were host-rate-limited re-dials that
grow with traffic, so it flipped production-gce-c7 back twice with no fault. It now uses the roll
ladder's non-drain measure (request_count 503s less drain-return deferrals and host-rate-limited /
host-in-flight re-dials from the director's runtime metrics) for both the background and the live
samples. A judged minute no runtime-metrics line covers is unverifiable, not zero.

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

* fix(relay): state the reserve watch's metrics coverage honestly and share the metrics read limit

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
* feat(cli): set the title of one terminal pane

Adds `orca terminal set-pane-title --terminal <handle> --title <text>`; `--title ""`
clears it. The runtime resolves the live handle to its saved leaf and asks the
desktop window to name that pane through a new optional notifier hook
(`ui:setPaneTitle`). Mounted panes apply it through the same local setter the GUI
uses; parked panes get it in the saved layout their next mount reads. The tab's
own name is never touched.

Rebuilt on current main from stablyai#24755.

Co-authored-by: stevelliu <stevelliu@tencent.com>

* fix(renderer): a main-owned layout takes a CLI pane title before the window shows it

* fix(renderer): a CLI pane title reaches a never-mounted tab with a rootless layout

---------

Co-authored-by: stevelliu <stevelliu@tencent.com>
…stablyai#30436)

* fix(native-chat): a Claude chat keeps a model its CLI stopped listing

Claude Code 2.1.302 stopped listing opus[1m], which still runs, and chats on
it moved to the list's default on the next send, effort remapped. The picker
no longer derives a replacement from the list: the chat's own model shows as
a selected-only entry, its effort read-only, and every send carries both.

A real model_not_found reply still recovers: the options answer shows what
the reset child runs, so the next send records it. A reopened chat's catalog
read lists its own model so older clients that replace one keep it too.

Tracked in STA-10947.

* test(mobile): drop the send-options capability the options hook no longer reads

* test: an unlisted model's saved effort shows read-only at rest, as live, for every agent

* fix(native-chat): round-1 review fixes; a new chat's unlisted remembered model falls back as on main

- The read-only value predicate keys on the effort row, so the phone's legacy
  host Permissions row opens as before.
- The desktop read-only effort row honours a row kept from the previous model.
- A live child with no listing of its own answers with the saved listing's
  ids, so the read-only effort reads the same live and at rest.
- The read-only effort borrows listed rows' effort label and level names.
- The host seeds a new Claude chat from a remembered default the saved listing
  no longer names on that listing's default, effort nearest, as main did; a
  chat's own saved or running model is never replaced.

Tracked in STA-10947.

* test: pin the new-chat seed fallback on its typed helper, with no casts

* fix(native-chat): round-2: a carried effort moves with a fallen-back model; one guard

- An at-rest create carrying an effort but no model, on a remembered model
  the saved listing no longer names, now moves that effort to the nearest
  level the fallback model offers, as main did.
- A create whose own options name no model, and whose merged options carry
  none, reads no listing.
- The seed fallback keeps one guard and no unreachable branch.
- The read-only effort finds its level's name in any listed model's choices.

Tracked in STA-10947.

* fix(native-chat): a live options read with no record borrows no saved ids and never throws
…the build switch (dark) (stablyai#30461)

* feat(sidebar): the desktop window lists main's sidebar inputs, with its own writes shown until main does (dark)

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

* fix(sidebar): a newer write wins in the pending layer, created rows hold the stamped record, writes name their host (dark)

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

* chore(sidebar): move the focus-read allowance with the window list model it moved into (dark)

* perf(sidebar): main's sidebar feed publishes a change after a quiet spell at once, not 50 ms later (dark)

* test(e2e): the desktop reads-main spec skips on a switch-off build and waits for a frame after collapse

---------

Co-authored-by: Claude <noreply@anthropic.com>
@innocarpe innocarpe added the bug Something isn't working label Oct 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.