* Revert "Keep same-machine folders with matching Git remotes in separate projects (#32272)"
This reverts commit 29a5f89b8a.
On a machine with several clones of one repo, #32272 moved the oldest clone
into the shared cross-host project. Shared Windows QA machines have several
orca clones, so everyone's sidebar "orca" project suddenly gained that
machine's main checkout and its worktrees. Revert until the intended
grouping is decided.
Conflicts resolved against later main: Settings files moved into
settings/repository (imports adjusted), and two test files later deleted on
main stay deleted.
* Use checked types in restored test fixtures
* Combine restored fixture type imports
* test(native-chat): name the stalled step in the live-history watcher test
A CI run hit the 30 s test timeout with no step named. Each wait now has its
own 8 s deadline that reports the stalled step with what the watcher had
delivered, and the file sets IS_REACT_ACT_ENVIRONMENT so act() flushes.
* test(native-chat): share one 27 s budget across the live-history waits
Per-step 8 s deadlines were stricter than the old 30 s test timeout; a shared
deadline keeps the total unchanged and only improves the failure message.
* test(native-chat): use an AbortSignal budget instead of the wall clock
Date.now trips the flaky-test wall-clock rule; AbortSignal.timeout gives the
same shared 27 s budget without reading the clock.
On CI the first keystrokes typed right after the WSL host terminal attached
were sometimes dropped (the shell received "L HOST TERMINAL ..."), leaving an
open quote, so the marker never printed. The spec now waits for the prompt,
which names the project folder, before typing. The dropped input itself is a
separate follow-up.
* fix(wsl): refuse Windows drive folders on a WSL host and offer to add them on this computer
A /mnt/<drive> project on a WSL host shows false line-ending changes, misses
edits made from Windows, and searches slowly. Add Project refuses typed,
picked and dropped Windows drive folders for a WSL host and shows the refusal
inline with Add on this computer, which switches the open dialog to this
computer. The WSL-host orcad re-checks by /proc/self/mountinfo (after
resolving symlinks) in repo.add, repo.create, repo.clone, explicit
folderWorkspace.create and the nested scan/import, answering with the
existing runtime_error code plus optional data, so older clients still show a
readable message. Everything is behind the WSL hosts gate; SSH is untouched.
* test(wsl): hold the e2e's this-computer add instead of racing its failure
On the Linux CI host the switched local add of a Windows path fails its nested
scan and hands off to the Open as Folder modal, closing Add Project before the
test looked. The refusal itself already came before any scan. The e2e now
records the local add at its scan step and asserts it went to this computer
with the Windows path; a unit test pins that a refused pick never reaches the
host path step.
* test(wsl): type the e2e's held-scan window hooks instead of Reflect
* test(wsl-host): native chat on a WSL host through the fake transport, including a stopped distro
* test(wsl-host): type the chat spec's focus host id
* test(wsl-host): harden the WSL host chat spec and re-run fake-connector specs on change
* test(wsl-host): scope the chat reply check to the transcript and test the fake's route
Find the prompt only in the transcript so the composer can't stand in for it,
re-check the boot-free stop window after unfocusing, right before the focus,
give the fake's distros.json temp file its own name so it can't collide with the
spec's, and test that the fake connector routes to both WSL host specs.
* Add native chat hand-off menu on main
* Fix saved chat hand-off observations and RPC exports
* Reuse indexed chat tabs for hand-off subscriptions
* Match saved-chat clear fixture to current journal API
* fix(ssh): prove a recycled server PID exited on hosts without ps
On slim Linux images that ship no ps, the managed server's liveness probe
could not read the recorded PID's argv, so after a reboot a PID that now
named another process read as LIVE (same user) or UNKNOWN (root). The wake
then refused to start the stopped server, the update deferred on the
terminal census, and the host stayed unreachable. Read argv and the
zombie state from /proc when ps is missing.
Fixes#34149
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(ssh): name argv-less PIDs from /proc stat; annotate e2e timeout
Kernel threads and zombies have an empty cmdline, so a recycled PID owned
by one stayed UNKNOWN on hosts without ps. Quote the stat pattern so the
probe also parses inside command substitution on older bash.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(ssh): fall back to the stat line only after cmdline was read
A failed tr must stay unproven rather than make a live server's PID look
recycled.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(chat): confirm before closing active native chats
* Repair native chat close settings and consent coverage
* Cover native close consent in workspace and floating tests
* Require consent before closing active native chats
* Fix close-warning typed values in deferred actions and tests
* Preserve admission ownership and reconcile relevant send evidence
* Separate chat read lifetime from send settlement and activation effects
* Fix required read activation typing
* test(e2e): repair specs that missed intentional product changes
Align still-stale e2e expectations with shipped product behavior:
- the Cmd+J placeholder names workspaces and tasks
- the worktree meta dialog is titled "Workspace details"
- OS-opened CSV/TSV files are editable and render as an ARIA grid
- the delayed-save notice carries the app toaster's close button
Fixes#30114
Co-authored-by: Neil <neil@stably.ai>
* test(e2e): stub host Linear status in linear-url-workspace-entry spec
Since #33270, Create worktree reads Linear status from the repo's host
(linear:status IPC) instead of the store, so the store-only fixture left
Linear disconnected in the composer and the pasted URL was never looked up.
Stub the linear:status handler with the same connected status.
---------
Co-authored-by: Neil <neil@stably.ai>
Global setup and the worker-scoped fallback now share one seedTestRepository()
that runs its commit and worktree add with per-command
`-c core.hooksPath=/dev/null -c commit.gpgsign=false`, so a contributor's
global hooks or always-on signing no longer abort e2e before any spec runs,
and the test repo's own hook config stays untouched for specs that install hooks.
Fixes#31587
Co-authored-by: Neil <neil@stably.ai>
* refactor(mobile): share the queued-card projection with desktop
Image-only queued drafts showed a blank card on the phone.
* fix(mobile): honor the host's Queue follow-ups setting
With it off, a mid-turn send from the phone still queued while desktop sent it into the running turn.
* fix(mobile): keep a queued-card edit when the card leaves the queue
The phone closed the editor and dropped the typing; it now goes to the chat box as on desktop.
* fix(mobile): hold /clear and /compact for background work and sends
The phone fired the command where desktop holds it, showed start failures twice, and left the agent unnamed in failures.
* fix(mobile): hand a queued edit back to the chat it came from
* fix(mobile): scope a command's send hold to its own chat
* fix(mobile): read a published null queue as empty
* refactor(mobile): share the returned queued-card caption rule
* test(mobile): drop queued-card tests the shared rules already own
* refactor(native-chat): share the needs-attention rule and drop leftover wrappers
* fix(mobile): type the structured sends mock, drop the moved queued-cards path and ship queued edit copy at boot
* fix(native-chat): drop the queued card's unused image-label import after the rebase
* fix(mobile): order the phone's queue reorder preview by position, as the shared cards sort
* test(mobile): keep the queued-messages test within its line budget
* fix(mobile): settle terminal chat working state and turn timing with desktop's rules
Move desktop's live-status merge, Stop suppression, hook-state resolver and
terminal turn timing to src/shared/native-chat. Mobile reads the transcript
lifecycle the host already sends, so a provider turn end settles Working the
same way desktop does. Terminal chats now get the turn bar, timer and folding,
and the streaming preview uses the shared deriveNativeChatStreamingText.
* fix(mobile): send desktop's terminal Stop keys, a second Escape only for OpenCode
Desktop sends one Escape on Stop. It adds a paced second Escape only for
OpenCode, which confirms an interrupt that way. Claude Code and Codex read
Esc-Esc at an idle prompt as rewind. The rule moves to
native-chat-agent-support, and the phone now paces the second Escape at
desktop's answer step instead of 80ms.
* fix(mobile): word Orca-stop and usage-limit notices like desktop
Move desktop's Orca-stop sentences to src/shared with an English table that
desktop translates. The phone names the Orca host it pairs with, and when
that host is unnamed it says "the remote server". Usage-limit rows rebuild
desktop's sentence from the failure fact. The usageLimitLineText fallback,
which only the phone used, is deleted.
* test(mobile): pass the overlay's host name in the prompt controller harness
* test(mobile): re-record Stop goldens at desktop's step, split status-notice tests, pin the turn clock and ship notice copy at boot
* fix(mobile): offer structured Stop while a send is out and read queue-sends-next as working
Move desktop's structured Stop rule, its Stop-joining helpers and the
queue-sends-next rule to src/shared. Both sides now use them. The phone
counts its in-flight sends, so a conversation-stop host offers Stop before
the turn opens. Between queued turns the phone reads as working, and
commands that can't wait in line hold like they do on desktop. Untargeted
Stops are no longer joined on legacy hosts.
* fix(mobile): offer terminal Stop again when its Escape went nowhere
Stop hides Working on the phone like it does on desktop. The phone's Escape
can fail outright, though, while desktop writes to a local PTY. When no Escape
could have landed, the phone now drops that suppression. The streaming preview
also treats a pending echo as the transcript's tail, as desktop does, so an
earlier reply can't hide a new preview.
* fix(mobile): hold structured Stop while a send it covers is in flight
On desktop, Stop's held and Stopping state depends on whether the chat can
stop, and that includes a send still in flight. The phone now does the same.
Once Stop is pressed before the turn opens, the button holds and repeated
presses send no more untargeted cancels.
* fix(mobile): end the terminal Stop hold when a new message is sent
Desktop clears Stop's working suppression on an optimistic send, so a new turn
that keeps the same hook epoch (Claude with working subagents) still offers
Stop. The phone now does the same when a new pending echo appears.
* refactor(mobile): trim chat turn wiring after the desktop rule move
Drop the controller's unread nativeChatAgentWorking field. Re-render only when
a chat starts or stops having a send out. Keep the structured turn indicator
stable across stream frames. Type the Stop test table instead of casting.
* refactor(mobile): track structured sends in flight once for Stop and command holds
* test(mobile): re-record Stop goldens now that Stop settles with its Escape delivery
* fix(mobile): escape terminal image paths like desktop drops
* fix(mobile): keep launch-draft prefill over older user turns
* fix(mobile): rank @ file matches with desktop's fuzzy Quick Open search
* fix(mobile): use desktop's composer triggers for mid-prompt / and $ picks
Picked @ files are now quoted when the path has spaces.
* fix(mobile): send picked skill paths on structured chats
* fix(mobile): list the host's disk-scanned skills in the / and $ menus
The phone names the workspace and the host resolves its folder, refusing SSH
workspaces as desktop does; hosts without skills.discover-workspace.v1 list none.
* fix(mobile): forget a picked skill once its token is deleted
* fix(mobile): drop a picked skill as soon as its token is deleted, and retry failed skill scans
* fix(mobile): treat a picked leading skill as chat and stop failed skill scans from refiring
* test(mobile): drop the menu ranking case the shared picker tests own
* refactor(mobile): keep skill picks on the draft record and reuse shared send rules
A rejected send now keeps its picks and leading-skill origin on both send paths.
* fix(mobile): replace a re-picked skill's file and restore picks with a rejected send
* fix(mobile): read the chat agent for the skill catalog from the active resolution
* style(native-chat): format the composer autocomplete files
* fix(native-chat): import the picker's apply and send-classify rules from the shared composer module
* refactor(mobile): import the composer's draft-store hooks once
* style(mobile): format the notice row's style import
* fix(mobile): resolve terminal chat agents in desktop's order
Mobile now uses the shared detected-then-launch precedence and transcript-readability check; the phone's remembered agent only fills in after them.
* fix(mobile): hide Codex foreground commands from the background-task strip
* refactor(native-chat): share diff-card gutter, basename and task count
* fix(mobile): show clipped tool output sizes in KB/MB like desktop
* refactor(native-chat): share the tool-run header rules with mobile
* fix(native-chat): share tool-line detail rules; skip unsafe-only search hits
* fix(mobile): copy message text with desktop's rule
Copy now leaves out roster and background-task fallback twins and wordless provider frames.
* refactor(native-chat): memoize shared tool-line view and output sizes; share result preview
* fix(native-chat): read a lone result's preview from its narrowed body
* chore(i18n): ship chat tool and stop copy in the runtime English catalog
* refactor(native-chat): move the pending-send echo rule to shared
Mobile reuses it instead of its own matcher.
* fix(mobile): match chat echoes after their send boundary, sink unmatched ones
Uses desktop's rule: rows before the send no longer retire an echo, a send that
never lands no longer lets later glued sends retire, and an unmatched echo sits
below later turns.
* fix(mobile): keep glued photo captions and folded boundaries in echo matching
* fix(mobile): give image preview echoes the send time the occurrence rule reads
* fix(mobile): match echoes on image-normalized rows and cap open sends at eight
Same input and bound as desktop; each open send rescans the transcript.
* test(mobile): drop a pending-order case the tail test already covers
* refactor(mobile): read the send boundary once and skip echo normalization when idle
* test(native-chat): type the occurrence case's pending send
* test(mobile): drop the pending-echo wall-clock entry from the flaky-test baseline
* refactor(native-chat): share session option labels with mobile
Mobile's options pill now shortens effort levels (Med, XHigh, Min) and names
choices and options the way desktop does, from one English table that desktop
translates.
* refactor(native-chat): run mobile terminal chat options on the desktop surface
The terminal-chat option surface, its appliers, the option cache and the
dispatch types move to src/shared, and the phone builds its pickers from them.
Grok and every other agent with a catalog now get model and effort pickers on
the phone, picks are saved as the host's launch defaults, the OMP live switch
gate and discovery mapping come from one copy, and authoritative discovery
retires persisted models it no longer lists. The option stamp moves to shared.
* refactor(mobile): merge the session option type imports
* fix(mobile): keep terminal chat option picks on their own chat
The phone keeps one option surface per chat, so a pick that lands after leaving
and returning shows there, and a picker command only switches the chat it came
from. Discovered models merge with the seed catalog as on desktop, so Grok keeps
its effort picker. A Claude model switch the phone cannot confirm is never
saved as a launch default.
* fix(mobile): start terminal chat options over when a tab changes agent
A tab that runs another agent drops its kept option surfaces, report markers
and cached record, so a fresh session shows its own model. A pick that lands
after the phone moved to another chat is not saved as a launch default.
* fix(mobile): fence retired terminal chat option surfaces
A write that lands after its tab started another agent no longer publishes the
old agent's record, and a changed OMP switch capability rebuilds the chat's
surface from the cache instead of reviving an older one.
* refactor(native-chat): simplify the shared session option wiring
Desktop translation pieces use literal English defaults so the localization gate can read them, the selected-choice lookup is shared once, and the phone keeps one option surface per tab with the agent change detected from the cached record.
* refactor(mobile): return no option surface for an agent without a catalog
* test(native-chat): expect the shared Effort label in the startup picker test
* test(native-chat): keep only the label cases main still checks after the rebase
* refactor(native-chat): import the session option snapshot rules once in the PTY option surface
* refactor(native-chat): share structured option pick bookkeeping with mobile
Desktop and the phone now track picks per option through one shared book: a
repeat of an option on its way waits, other options go together, a lost reply
is not a pick done, and a stale reply only commits its own option. The phone
also re-reads options each turn and after the provider starts, and shows picks
at once while the host is still starting the provider.
* refactor(native-chat): share the option pick blocking rule
* fix(native-chat): read in-flight option picks from the shared book for launch send options
* fix(native-chat): keep the structured options hook within its line budget and pass the phone turn in the e2e harness
Japanese IMEs convert one clause at a time, but the terminal's preedit overlay
painted the whole composition uniformly, so the clause the candidate window
targets was invisible. Read the IME's selection from xterm's textarea on the
insertCompositionText input event (Chromium only updates it there) and paint
that range of the preedit in the theme's selection colors. Cell-grid advance
corrections are unchanged and nothing reaches the PTY; a collapsed caret or a
range outside the preedit highlights nothing.
Fixes#27873
Co-authored-by: Neil <neil@stably.ai>
* feat(quit): add Quit and Stop All Sessions to the app menu and tray
Quit keeps daemon sessions alive for warm reattach. The new item, placed
directly above Quit in the macOS app menu and menu bar icon, the Windows
File menu and tray, and the Linux File menu, asks for confirmation, runs
Settings' Kill all sessions in the renderer, and quits only when every
session stopped. Closing the tabs first means nothing reopens or resumes
on the next launch. Serve hosts do not offer the item.
* fix(quit): keep Orca open when Kill all cannot prove every session stopped
- Kill all no longer reads a daemon generation that failed to list as
"no sessions": a failed poll keeps the last confirmed remaining set,
and a failed initial listing sets listingIncomplete.
- Quit and Stop All Sessions stays open on an incomplete listing or a
terminal tab opened during the sweep, logs why it stayed open, and
reveals the window before the final quit.
- Share the main-window message box helper, stub the request on the web
client, and add Portuguese strings.
- Cover the windowless path, request expiry, the in-flight guard, and
failed listings in tests; bound the E2E exit wait.
* fix(terminal): count Kill all per daemon generation within a time window
- A poll updates only the daemon generations that answered, so one
failing generation no longer hides the kills of the others.
- Poll for 6.5 s of wall-clock time instead of 65 attempts, because a
wedged generation fails each listing only at the request timeout.
- Flag a sweep with no daemon provider as listingIncomplete.
- Log the leftover-tab exit, share the one-shot window intent with
Open Settings, and cover the sender check, the reveal before the final
quit, and retries in tests.
* fix(quit): stay open while a newer build's daemon is held
- Set listingIncomplete while this build holds a newer protocol's
daemon, whose sessions it can neither list nor stop.
- Reuse getDaemonAdapters in Kill all, type the shared one-shot helper
by its flag factory, and move the app-menu quit handlers next to the
tray options so both serve/desktop splits are tested.
- Cover a quit that outlasts the request expiry and a failed request
pull, and fix the docs lead-in for the new quit-time case.
* fix(quit): explain, without stopping anything, while a newer version runs terminals
- Check the newer-version daemon hold before the confirmation and show
an explanation instead, so Kill all no longer closes every tab and
then refuses. listingIncomplete stays as the backstop.
- Translate the explanation, describe listingIncomplete generically,
and assert the single timed-out poll in the Kill all test.
* fix(quit): explain before asking whenever Kill all cannot list every session
- Show the explanation instead of the confirmation while the daemon
provider is missing (still starting or failed) as well as while a
newer version's daemon is held.
- Name only the steps that work (wait for terminals to open, reopen a
terminal opened in a newer version), avoid stating a hold as fact, and
label the dialog's button through the catalog.
* fix(quit): add the relaunch step to the explanation and align the ja wording
* test(pty): keep a confirmed stop when its generation fails a later poll
* test(quit): give the E2E timeout bump its reason
* fix(quit): stay open when the daemon still lists a session after Kill all
Kill all counts only the sessions of its first listing, so a session
started since then, for example from the CLI or an automation before its
tab reaches the renderer, would outlive the quit. Main now lists every
daemon generation again before quitting, and quits only after a listing
in which every generation answers and none lists a session. It polls
within Kill all's 6.5 s window so the tab sweep's PTY shutdowns can
finish; otherwise the renderer shows the existing error and Orca stays
open.
ui:quitAfterStoppingAllSessions becomes an invoke that answers whether
main quits.
* feat(quit): rename to Quit Completely with a macOS hover hint, and explain plain Quit in the confirmation
* fix(quit): show progress while Quit Completely runs, and refuse at once when the daemon can't be fully listed
---------
Co-authored-by: Neil <neil@stably.ai>
* fix(native-chat): keep Claude approval choices across model changes without freezing delivery
A Claude chat saved as "Approve for me" could reopen asking for every approval,
because Orca read the folder's saved model list as launch policy, and a model
change could keep running in a weaker approval mode Claude opened with. The
first fix for this was reverted after it froze the app: restored history left
a turn reading as running, a send was refused against it, and delivery retried
the same send with nothing changed.
- The saved approval choice goes to Claude as-is; the menu offers every mode.
- Orca records the mode Claude actually opened in. A send that picks a new
model reopens Claude first when that mode differs from the one requested,
only when the chat owes no work; Orca mail notices never reopen it.
- A message sent while a Claude turn is running joins that turn on its current
choices; new choices apply at the next new turn. Nothing is refused.
- History catch-up closes every turn it opens, in the thread it opened in.
- At startup, a turn reading as running under the new agent that the agent is
not running is ended as interrupted.
- Delivery goes round again only after a step shows what it changed (a commit,
a different child, a start it joined ending, or a wake); otherwise it waits
for the next change instead of repeating the same step.
Tracked in STA-10953.
* fix(native-chat): preserve execution evidence at dispatch
* Fix delivery iteration ownership and progress contracts
* Correct journal fixture types in delivery regressions
* Separate Claude input choices from error correlation
* feat: drag to reorder queued messages on desktop and phone
* fix: use the queue grip button padding from its primitive
* fix: preserve the queue grip touch width with hit slop
* test: carry queue animation mocks into the chat view harness
* test: complete the shared chat view harness registrations
* test: carry the queue move cross-version wire contract
* Scope queue move feedback to the issuing chat and preserve phone touch width
* Keep queue reorder presentation stable during measurement and pending moves
* Keep measured row handoff tokens constant size
* Avoid rebuilding queue arrival IDs on unchanged renders
* Fix mobile queue regression test fixture boundaries
* Share the queued message leading slot with its reorder grip
* Fix queue regression fixture quality findings
* Check queue icons within the leading slot
* Allow every unsettled queued message to reorder
* Supply stable scope in queued row test mock
* fix(browser): focus newly opened image tabs
Focus the window before the webview so Command+W closes the new
image tab instead of the original tab after a plain image click.
Add tests for focus order, image loading, and close-shortcut forwarding.
Fixes#34114
* test(browser): verify image tab focus with native keyboard input
* refactor(ci): keep browser E2E routes within registry size limit
* fix(ci): enable background launch for native browser regression
* test(ci): include browser routes in isolated detector fixture
---------
Co-authored-by: Neil <neil@stably.ai>
* fix(mobile): ask before running orca.yaml default-tab commands from the phone
The host's repo.hooks reply now carries desktop's setup config, so the phone
gates the Run/Skip choice and the trust prompt on defaultTabs commands too.
* fix(setup): keep the local script in run-both setup config without orca.yaml commands
* test(setup): drop the shared duplicate-host trust test desktop already covers
* refactor(setup): derive host setup trust from the shared setup config
* fix(mobile): count every host's repo rows before honouring repo-wide setup trust
* fix(mobile): show desktop's PR lanes and failed/interrupted status dots on the phone
The host builds worktree.ps linkedPR with the desktop's PR-lane cache reader
and sends the desktop sidebar dot as an optional sidebarStatus field.
* fix(mobile): keep uncached PR badges and let finished turns show through working titles
* fix(mobile): suppress agent panes' permission and parent titles in the host status dot
* test(runtime): expect sidebarStatus on the compact ps summary
* test(runtime): keep only host-specific sidebar status cases
* refactor(mobile): keep host title suppression and the uncached PR lane in one place each
* test(wire): pass the terminal title evidence map to new host builds
* fix(runtime): read each workspace's PR through its own host's repo
* fix(wsl-host): uninstall and startup release the WSL distro holders Orca left running
The detached wsl.exe holder outlives the app, so an uninstall left its distro,
orcad and shells running. The uninstaller now runs Orca with
--orca-uninstall-release-wsl-holders after killing the app and its daemon; it
ends only the holders this profile recorded, each verified by pid, start time
and command line, and keeps any it cannot verify.
At startup the WSL host service now releases holders whose distro is no longer
a host (a failed add or remove), and every holder while WSL hosts are off; a
closed gate releases them too.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(wsl-host): a Remove that can't prove the server stopped keeps the host, pending
A partial Remove dropped the host anyway, leaving a server whose live
terminals block the next add's activation. Remove now drops the host only on a
proven stop; otherwise it records a pending stop on the WSL record, keeps the
host listed and says why (live or unverifiable). Remove again retries without
ever booting a stopped distro (it says to start it); Connect or Add on the same
distro keeps the host; a distro that is no longer registered finishes. Nothing
reconnects a pending host on its own.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(wsl-host): E11 review — keep holders when records are unreadable; finish removals a stopped or renamed distro proves
- Startup keeps every holder when the WSL host records can't all be read
(unverifiable), and no longer re-adopts a pending host's holder.
- Remove matches a renamed distro by GUID (isWslDistroRegistered), and a
stopped distro finishes a pending Remove: the cold probe proves the stop.
- Only the user's Connect or Add withdraws a pending Remove; other connects
are refused, and re-adding respects startIfStopped.
- Settings' Remove carries the kept result, so it warns instead of 'Removed'.
- A holder record without a start time is never killed on its command line
alone, except the one this process just spawned.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(wsl-host): E11 review 2 — a closed gate keeps live hosts' holders; stop a renamed distro by its current name
- With the gate closed, a launch releases only orphan holders (no host record,
or a pending Remove), under the same keep rule as startup; a user with no
holder records costs one folder check, nothing else.
- The keep rule re-reads host records per holder and keeps an Add in flight.
- Remove resolves the distro's current name from its GUID and stops it there.
- The uninstall command arms its exit timer first and exits 1 if setup throws;
NSIS launches it only when a userData holder folder exists.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(wsl-host): never boot a host whose Remove is pending at the next launch
Early wake built its host list from every record, so a host focused when its
Remove ended pending was booted at each launch, undoing the stop's proof. It
now skips pending records, and Remove forgets the host's early wake whatever
the outcome.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(wsl-host): no wake for a pending host; the e2e fake stops an unbridged server like the real one
- mayWake skips a host whose Remove is pending, so a due automation can't
boot it (focus already couldn't).
- The e2e fake connector read a host with no bridge as stopped. Since E9 a
re-enable connects nothing, so Remove after off/on left the host's shells
running. Like the real connector, it now reconnects the always-running fake
distro to stop its server.
- Status bookkeeping moves to WslHostStatusBoard, and the pending-host connect
rule to admitConnectToPendingHost, to keep the service under max-lines.
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* fix(chat): preserve transcript source order across history windows
* test(chat): use template literal for mobile transcript fixture
* test(chat): place composed transcript contracts across project boundaries
* test(chat): move reader-to-phone contract across project boundaries
* test(chat): reuse transcript input setup and caller contracts
* test(chat): align mobile recordings with source-order capability
* test(chat): scope phone transforms and await history snapshot
* fix(session-tabs): keep the server's per-device tab pick on the tab the desktop shows
The server hands its remembered pick back whenever the desktop has no tab groups of its own for a workspace. A desktop that kept its groups never reported what it showed, so that pick went stale and such an open landed on the wrong tab.
* fix(session-tabs): report only a ready visible pane, and retry after an error answer
Name the pane the desktop shows and require it ready, so a report never starts a pending sibling's terminal. A failure envelope no longer marks the pick as reported.
* fix(session-tabs): report a desktop's pane once per change and connection
Two windows sharing one device id could steer the pick back and forth. Report again only when this desktop's own pane changes, scope the record to the pairing and connection, and resolve the visible tab with the existing helper.
* fix(session-tabs): report only the on-screen workspace, compare panes, and remember agreement
Reports stay with the workspace on screen, whose pane attaches anyway, so a report never starts a terminal elsewhere. The pick is compared pane for pane, and an agreeing frame records the pane so a later change of this desktop's pane reports again.
* fix(session-tabs): settle before reporting, report on opening a workspace, and only to its owner
A click records its activation after an async hop, so a report now waits until the pane settles and compares against the latest frame. Opening a workspace reports from its latest frame. A workspace id two servers share is reported only to its owner. The e2e now drives the real flow: a stale pick, then a desktop restart.
* fix(session-tabs): keep one outstanding pick check per workspace, fenced to its connection
A busy workspace's frames no longer postpone the check. A frame from a replaced pairing or connection is dropped, and a frame without the per-device projection clears the cached one.
* fix(session-tabs): bound the pick report caches and clear them on mirror teardown
One record per workspace, with the connection in the value. Environment teardown drops reports, frames and timers. The tests use checked types, and the e2e notes why it waits and why it needs more time.
* fix(session-tabs): check again when the user moved on before a report finished
The host does not order activations, so a report that completes after the user picked another pane may have landed last. Clear its record and recheck from the latest frame.
* fix(session-tabs): type the visible pane's leaf as optional
* fix(session-tabs): send a browser or chat pick again when a late report landed after it
A terminal report that completes after the user picked a browser or chat tab may have had the last word. Re-send that pick the way a click does.
* fix(session-tabs): recheck a late report against the host's answer, and fence it to teardown
The activate answer is the host's per-device frame after the report, so a late landing is checked against it instead of a cached frame. A report answered after mirror teardown or a new connection does nothing.
* test(session-tabs): drop the in-connection suppression check from the restart e2e
Whether the desktop repairs a stale pick within the connection depends on which frames settle first; the guarded outcome is the pick after a restart.
* fix(session-tabs): reconcile the user's newer pick after any report outcome, timeouts included
A report whose answer timed out may still have landed after the user's newer pick. Settle every outcome the same way: retry on failure, then reconcile with what the desktop shows.
* Size tabs to titles and preserve floating panel drag space
* Stabilize agent tab layout during activity changes
* Balance tab widths across titles and tab types
* Reserve terminal tab icon space before agent detection
* Run floating header drag E2E in the isolated headful project
* Prevent native text selection on the floating panel drag handle
* Cap expanded tab width at 24rem
* Let tabs expand to fit titles in available space
* fix(ssh): move enabled SSH automations to the host's Orca server
Connecting an SSH host moves its saved state to the host's managed Orca
server. An enabled SSH automation refused that move on every Connect
("automations (1), automation runs (4)"), leaving the host unconnectable;
Move anyway connected it but stranded the automation on the desktop, where
it no longer ran.
An enabled automation whose runs have all finished now moves with its run
history. The manifest still carries it disabled, which every server version
accepts; once the move commits the desktop disables its retained copy and
turns the server's copy back on through automation.update, re-sending its
start time so the next run is the next future occurrence. A server that
can't is noted in Settings; an unreachable one is retried on the next
Connect. While a move fences the host, the desktop scheduler, Run now and
the launch re-check write nothing for its automations, so a tick can no
longer break the fenced export; releasing the fence resumes them. A run
still in flight keeps blocking and the refusal now says to wait for it.
Fixes#29083
* test(ssh): mock the server automation enabler in the delta-move handler test
* test(e2e): keep the Move anyway specs refusing now that enabled automations move
The inline Move anyway spec seeded an enabled automation to force a
refusal, which this branch now moves instead. It seeds one pinned to an
earlier registration of the host, which still stays behind. The relay
bridge spec's Move anyway lookup also matched the refusal toast's
button since #34041; it now takes the pane's own.
* Source control actions start chats and terminals on their saved model
A "start an agent" action's model lived only in free-text CLI arguments, which a native chat
never reads, so AI Fix opened a chat on the default model. Each action now saves a structured
model and effort that both routes apply: chat create options, or the agent's own flags in a
terminal (winning over the same flag in CLI arguments). Desktop, dialog, fix-checks and mobile
launches carry it; Settings rows get Model/Effort selects and the CLI arguments field says it is
terminal-only.
Tracked in STA-11102.
* Name the model in the repository override note
* Type the dialog's model hand-off; keep Agent in English in Japanese copy
* fix: preserve action chat model choices through launch
* Preserve action selection ownership across chat launches
* Keep terminal host contract fixtures aligned with preflight
* fix: wait for action Settings agent inventory
* fix: consolidate direct agent routing imports
* fix: use standard action option labels
* fix: align action launch contracts with shared types
* fix: sync action option localization metadata
* test: provide action Settings inventory in mounted fixtures
* Keep action options row height stable while loading
* Let each Source Control action choose its chat permission mode
An action's saved chat choice may now carry a permission mode, with or without a model;
"Same as new chats" saves nothing. A model is replaced only when the action names one,
so a permission-only choice keeps the chat's remembered model and options.
* fix(native-chat): leave older hosts their own permission mode and keep action options readable
- An action's saved permission mode is sent only to hosts that advertise
agentSession.create.options-mode.v1. Older hosts get no mode, in create
options or a later setOption, so the chat starts on that host's new-chat
default, as a phone launch to such a host already does. Hosts from
1.4.225 to 1.4.227 refused a mode their tables lacked (OpenCode, Grok,
OMP), leaving the action unable to start.
- Settings action rows give Model and Permissions full rows in the options
column so saved values are readable; the loading placeholder reserves the
same rows.
- Remove two unused locale keys; fix two stale comments.
- Tests: the older-host create carries no permission mode; a permission
mode picked alone keeps the remembered model on an acquired create.
* fix(settings): reserve one options row while loading an action with nothing saved
Terminal and permissions-only rows no longer shrink when loading ends; a chat
agent with a model list may grow by its Model row once known.
* fix(native-chat): keep the create options mode a checked literal type
* fix(settings): share verified action option fields
* fix(settings): keep action selectors on standard typography
* Keep the launch files under their length limits after the main merge
The structured route imports its result types where they are defined, and the two older-host
action-launch tests share one setup helper.
* One action settings section for chat and terminal launches
A Source Control action's Model, model options and Permissions now apply whether it opens a
chat or a terminal; terminals receive them as the agent's own flags, and a repository's own
CLI arguments keep the flags it sets. Extra CLI arguments stay terminal-only.
* fix(source-control-ai): one terminal projection for saved action choices
Ask in a terminal now states its permission instead of leaving it to the
user's own config: Claude and Grok pass --permission-mode default, Codex
passes -a on-request -s read-only -c approvals_reviewer="user" (the
interactive CLI has no untrusted policy, so a read-only sandbox is what
asks before edits). Agents whose CLI has no flag for a mode show the
existing "Applies to chats only" hint.
The strict launch-preferences projection now requires the launch agent at
every caller, composes model ids through the resolver's own step, carries
a mode only where the agent's terminal has a flag for it, and the local
string wire keeps booleans as 'true'/'false', so Cursor Thinking/Fast reach
the terminal and an OpenCode permission-only choice is not refused.
The argument parser knows flag arity, so removing a boolean permission
flag keeps other options' values, following positionals and '--'.
Pre-admission and executor share one terminal admission check on the
options the terminal will receive. Customize with Save launches the recipe
as it will resolve once saved; legacy partial repository overrides are
edited as the complete recipe they resolve to. Empty Extra CLI arguments
start collapsed.
* fix(source-control-ai): an edited repository action saves complete
Every Settings edit of a repository action (agent, model, permission,
prompt or arguments) now starts from the complete recipe the action
resolves to, whether its stored override is owned or a legacy partial.
The resolved agent selection is kept (never an agent only detected to run
a default), an explicit null args clear stays null, and empty args stay
empty instead of being dropped. Clearing the last preference therefore
means the action agent's own defaults rather than re-inheriting Global.
The action text draft is lossless: unset or cleared prompt and args stay
so until the user types, and the inputs show them as empty, so saving a
prompt no longer writes empty args that suppress the agent's default
flags. Untouched sibling actions are not rewritten by the edit.
* fix(settings): style the Extra CLI arguments toggle through Button's own variant and size
CI's changed-code quality check refuses restyle classes on Button and
CollapsibleContent. Use the ghost variant at size xs with a margin, and
put the content spacing on a plain inner wrapper.
* fix: keep saved action and restart launch recipes consistent
* fix: describe inherited arguments and chat-only permissions accurately
* test: check repository action settings fixture type
* test: map saved action options into terminal compiler inputs
* fix: retain saved action controls during agent detection
* fix(i18n): retire obsolete Portuguese action labels
* test: align folder launch fallback mock with outcome export
* fix: carry resolved connector launch intent and mode together
* test: pin shared action launch branches to recorded agent output
* test: preserve saved source control choices through detection and migration
* test: align launch fixtures with integrated result contract
* test: retain recipe and restart contracts after main integration
* Unify Source Control action permission settings
* Keep native Ask copy with its owning change
* Preserve action drafts and old-host permission intent
* Consume rejected Source Control action saves
* Follow the moved Settings persistence fixture
* Keep permission integration contracts in the cross-version test project
* Include the action permission inheritance label in English catalogs
* Align launch contracts with shared permission recipes
* Keep Source Control action controls within their Settings columns
* Use primitive variants for compact action settings controls
* Show action arguments only for terminal defaults and widen recipe rows
* Apply terminal-only action arguments visibility to repository overrides
* Align action permission control with model controls
* Use shared settings label for action permissions
* Hide action model controls until argument migration ships
* perf(renderer): drop full-window overlay blurs and the idle usage-chip pulse
A full-window backdrop-filter is re-blurred on every frame while anything
beneath it animates, which held ~370 MB of extra GPU render-pass textures and
swapchain buffers while a dialog, sheet, palette or onboarding was open. The
2px scrim blur is barely visible under the black scrim, so keep the dim only
(5 points darker to keep the background as recessive) and drop the panel blur
that sits under a 96%-opaque background. The status-bar usage chip also pulsed
forever before its first fetch (unfocused windows never poll); show it static
until a fetch is actually running.
* perf(renderer): preserve modal blur with independent source regions
* fix(renderer): preserve modal blur recovery and Space coverage
* fix(ci): remove stale context-menu import after main merge
---------
Co-authored-by: m4air <m4air@Mac.localdomain>
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
* fix(ssh): show Move anyway on a host card whose move saved state refuses
A host whose move to its Orca server was refused over saved state (an
enabled SSH automation and its runs, stale workspace details, unsupported
saved tabs) stayed in error on every Connect. Move anyway, which connects
it and leaves those items on this computer, was only in the card's
overflow menu, so the host looked stuck. The card now offers Move anyway
next to its refusal, as it already does for Recover server.
Refs #29083.
* fix(ssh): make Move anyway the next step when saved items refuse a host's move
When a host's move to its Orca server is refused only over saved items
that Move anyway can leave on this computer (#29083's enabled
automation and its runs, for example), every surface now leads with it:
- the host card says which items can't move and that Move anyway
connects the host and keeps them here, with Move anyway as its
primary button;
- the refusal toast says the same and offers Move anyway, opening SSH
Hosts where the move is confirmed;
- the terminal pane's Move anyway is primary too;
- the host picker and connect toast read the same line.
Refs #29083.
* fix(ssh): say why SSH failed when Connect never reaches the host
When the SSH session itself could not open (key rejected, port refused,
host not answering, host key not trusted), Connect on a host Orca had not
set up yet said "Orca couldn't start its server on this host" and offered
a plain SSH terminal that could not work either. Errors from opening the
SSH session are now marked where they are dialed, and a setup that fails
on one ends the connect with SSH's own reason. A held move keeps its
status. The connect-failure toast also drops Electron's IPC prefix.
* fix(ssh): lead an SSH connect failure with what to do about it
#33928 shows SSH's own reason when Connect never reaches the host. Raw
reasons like "connect ECONNREFUSED 10.0.0.1:22" or "Timed out while
waiting for SSH authentication" still leave users guessing. A failure
from opening the SSH session is now published with a next step for the
kinds users can act on (host key mismatch, rejected sign-in, refused
port, unknown host name, no route, timeout), and SSH's words follow in
parentheses. The connect-failure toast reads the same published text.
A host whose move to its Orca server was refused over saved state (an
enabled SSH automation and its runs, stale workspace details, unsupported
saved tabs) stayed in error on every Connect. Move anyway, which connects
it and leaves those items on this computer, was only in the card's
overflow menu, so the host looked stuck. The card now offers Move anyway
next to its refusal, as it already does for Recover server.
Refs #29083.
* Apply Codex Ask permissions at each fresh turn
* fix: admit fresh Codex input with current host permissions
* fix(native-chat): finish cancelled Codex input under its owner
* fix(native-chat): preserve mobile type boundary for input finalization
* test(native-chat): bound input finalization work at scale
* Fix cancellation result fences and exercise host proofs
* Fix input finalization type contracts
* Remove obsolete Portuguese permission description
* Preserve proven Stop outcomes during input finalization
* Preserve input handover proofs with session-ending Stop
* Preserve pending input recovery before lifecycle cancellation
* Assert one recovery card across overlapping close requests
* Preserve unwritten inputs across shared-agent Close preparation
* Narrow cancelled dispatch to its rejected outcome
* Import Codex app-server types from their colocated modules
* Use standard Ask policy for fresh Codex chat input
* Deliver applied Codex permission reports to the host
* Cover applied reports with pending settlement waiters at scale
* Fix settlement waiter timer portability on mobile
* Keep pre-send exit failures with their original input result
* fix(ssh): expand %d and other tokens in ssh -G IdentityFile paths
OpenSSH prints IdentityFile values from `ssh -G` with their percent
tokens unexpanded (it expands them only at connect time), so a config
line like `IdentityFile %d/keys/work.pem` reached Orca as a literal
relative path. The key was silently skipped and the connection fell
back to a password prompt even though plain `ssh <alias>` worked.
Expand the tokens OpenSSH supports for IdentityFile that can be derived
locally or from the ssh -G output (%%, %d, %u, %i, %l, %L, %h, %n, %p,
%r). Paths with tokens that need OpenSSH internals (%C, %j, %k) are left
unchanged. A configured, non-default identity file that does not exist
is now named in the auth error instead of being skipped silently.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(ssh): reproduce #33849 end to end and keep tests off the real ~/.ssh
Adds a Docker e2e: a host from ~/.ssh/config whose IdentityFile uses %r
and %p connects with that key. It fails on v1.4.241 and passes with
the token expansion. Drops a unit assertion that read the real
~/.ssh/id_xmss.
* fix(ssh): expand ${NAME} in ssh -G IdentityFile paths too
ssh -G also prints IdentityFile's ${NAME} environment references
unexpanded, so a key at ${KEYS}/work.pem fell back to a password prompt
in the same way as #33849's %d. Expand the braced form first, and keep
the raw path when the variable is unset, as OpenSSH refuses it.
---------
Co-authored-by: william <wanglv93@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
"reconnect restores the managed host name after its conversion catalog
fails" started page.waitForEvent('console') before the reconnect that
converts the host. Its 30 s clock ran through the conversion, so on a
loaded machine or a slow host the test timed out even though the catalog
failure was logged. It failed this way on unmodified main locally. With
the Docker host frozen for 35 s during the reconnect it fails every time
before this change and passes after it. The test now collects console
messages and polls once the host is managed. It also drops a log line
the app no longer prints.
When the SSH session itself could not open (key rejected, port refused,
host not answering, host key not trusted), Connect on a host Orca had not
set up yet said "Orca couldn't start its server on this host" and offered
a plain SSH terminal that could not work either. Errors from opening the
SSH session are now marked where they are dialed, and a setup that fails
on one ends the connect with SSH's own reason. A held move keeps its
status. The connect-failure toast also drops Electron's IPC prefix.
* fix(native-chat): say why Claude refused to start a chat, including Full access as root
Claude Code exits before initialize when Full access (--dangerously-skip-permissions)
meets a root uid, which is common on SSH hosts. Orca reported only that Claude
stopped before it finished starting, and retried four times. Classify that refusal
as fullAccessAsRoot with a next step, and show the CLI's own startup words behind
Details on the failure row and on the unsent message.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(native-chat): keep message Details to start failures; cover the exit path
Route the exit-error module and delivery notices to the managed e2e, explain its
timeout, and drop the exec non-null assertion. Match the zh Full access label.
Co-Authored-By: Claude <noreply@anthropic.com>
* test(native-chat): type the refused fixture so the web typecheck passes
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>