* test: remove redundant cases and repair behavioral assertions
* docs: reconcile audit records with current main
* fix(deps): apply security updates from #32101
Apply the six-file DOMPurify 3.4.17 and Next.js 16.4.1 patch from PR #32101 to resolve the dependency security gate for this test audit. Original patch by Brennan Benson.
* test: satisfy changed-line quality gates
Add required braces and replace filter indexing with find. Rerun both CI quality gates and the 27 affected relay tests; refresh audit hashes and record the corrections.
---------
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
* Track Agent Dashboard adoption and daily visible use
- Record enablement, board opens, and per-surface daily visibility
- Persist daily limits across restarts and respect consent and window visibility
* Track dashboard telemetry at surface roots
* Add Orca's own bundled-skill planner and installer (not wired in yet)
One planner decides what installing Orca's bundled skills does to each
target folder; execute re-plans under per-path locks and runs only a plan
whose digest still matches; a shared summary derives badge, wording and
exit code. A copy counts as Orca's untouched one only when its SKILL.md
bytes match a version some build shipped (resources/skills/released-digests.json,
generated from git history and checked add-only).
Nothing calls this yet; the npx path is unchanged.
* Report left-alone folders as warnings and keep backups in the home
A leave is no longer a failure: the outcome line says what happened plus
"N left alone: <reason>", and the exit code is 1 only for a failed or
changed op. Backups for every scope go to <home>/.agents/.orca-skill-backups
on the executing host, so a project Replace never writes into the repo;
moving there falls back to copy, verify, then remove across filesystems.
* Update Orca's skills with Orca's own installer instead of npx skills
The app's Update skills dialog, badge, nudge and status pills now read the
bundled-skill plan and summary, and Update executes the reviewed plan's
digest. `orca skills install|update` runs the same planner in-process with
--dry-run, --force, --json and per-summary exit codes. Removes both npx
spawn sites, the freshness inventory modules and the npx round-trip CI.
* Update only where the skill already is; one provider-root rule for app and CLI
installedOnly now drops every location that holds nothing (keeping the shared slot
only when an existing agent link points at it), so Update never adds a skill for
another agent. The app and CLI resolve the Claude skills folder through the same
rule in localBundledSkillInput; a test asserts both plan identically.
* Compact Update skills dialog: one row per skill, locations named by agent
summarizeBundledSkillPlan now returns each skill's locations, labelled by
agent (or "Shared folder") with a ~-collapsed path; the dialog shows one row
per skill that needs something, collapses up-to-date skills into one line, and
lists every location behind "Show all locations". The result reads "Updated N
skills · M left alone". Under installedOnly a Replace with no shared copy
replaces in place instead of adding one.
* fix(skills): keep location paths readable by putting the state under the path
* fix(skills): hold the last plan in state, not a ref mutated during render
Split the update dialog's rows, headline and footer into components, move the Update label
next to the other plan copy, and use z.strictObject for the request schemas.
* Escape non-ASCII characters in bundled-skill regex and wording
The orcad bundle rejects a literal non-ASCII character in a regex.
* fix(cli): escape non-ASCII apostrophes in the skills install help text
* Plan and update Orca's skills on paired servers, SSH hosts and WSL
* Show the skills pill for paired hosts; real-WSL installer test
* test(orcad): check the bundled CLI plans a skill install without npx
Drop the deleted roundtrip workflow's group from the closed-PR cancellation fixture.
* Let the WSL installer create the backups folder itself
* Review fixes: follow the active workspace's host, keep hosts' plans and runs apart, never boot WSL for a background check
* test: execute requests carry the plan request; CLI pill follows the local target
* Write large WSL skill files in chunks that stay under the command-line limit
* Make bundled-skill test fixtures link folders the way the product does on Windows
Directory links in fixtures are junctions on Windows, which need no
Developer Mode; the one file-link fixture is skipped there.
* Let a client ask a host to install named Orca skills, not only update installed ones
* Plan an SSH workspace on the managed server that serves its host; ask to connect otherwise
* Translate the remote skill states into every locale
* Resolve legacy skill agent keys canonically
* Reject stale skill replacements when auxiliary content changes
* Remove the Windows-hosted WSL bundled skill installer
* Wait for active workspace ownership before planning bundled skills
* Keep ordinary bundled skill classification shallow
* Leave unreadable skill replacements alone without aborting plans
* Pass force-all through shallow planning and describe unreadable copies
* Clarify skill update statuses and folder details
* Keep selected skill replacements in place with one status
* Report completed skill replacements with their backup confirmation
* Clarify available skill updates and skipped copies
* Keep skill update results in preview order
* Show each skill skip reason once
* Count skipped skills instead of skipped folders
* Make installer input and link tests work on Windows
* Bound bundled skill classification reads and parse names linearly
* Keep workspace skill roots inside the resolved workspace
* Compare workspace skill roots with native path semantics
* Restrict bundled skill backups to their owner on POSIX
* Keep bundled skill updates on the proven local workspace host
* Keep recovered skills needing attention until they are updated
* Count completed skill updates in the status tooltip
* Sync the runtime localization catalog
* Add Orca's own bundled-skill planner and installer (not wired in yet)
One planner decides what installing Orca's bundled skills does to each
target folder; execute re-plans under per-path locks and runs only a plan
whose digest still matches; a shared summary derives badge, wording and
exit code. A copy counts as Orca's untouched one only when its SKILL.md
bytes match a version some build shipped (resources/skills/released-digests.json,
generated from git history and checked add-only).
Nothing calls this yet; the npx path is unchanged.
* Report left-alone folders as warnings and keep backups in the home
A leave is no longer a failure: the outcome line says what happened plus
"N left alone: <reason>", and the exit code is 1 only for a failed or
changed op. Backups for every scope go to <home>/.agents/.orca-skill-backups
on the executing host, so a project Replace never writes into the repo;
moving there falls back to copy, verify, then remove across filesystems.
* Update Orca's skills with Orca's own installer instead of npx skills
The app's Update skills dialog, badge, nudge and status pills now read the
bundled-skill plan and summary, and Update executes the reviewed plan's
digest. `orca skills install|update` runs the same planner in-process with
--dry-run, --force, --json and per-summary exit codes. Removes both npx
spawn sites, the freshness inventory modules and the npx round-trip CI.
* Update only where the skill already is; one provider-root rule for app and CLI
installedOnly now drops every location that holds nothing (keeping the shared slot
only when an existing agent link points at it), so Update never adds a skill for
another agent. The app and CLI resolve the Claude skills folder through the same
rule in localBundledSkillInput; a test asserts both plan identically.
* Compact Update skills dialog: one row per skill, locations named by agent
summarizeBundledSkillPlan now returns each skill's locations, labelled by
agent (or "Shared folder") with a ~-collapsed path; the dialog shows one row
per skill that needs something, collapses up-to-date skills into one line, and
lists every location behind "Show all locations". The result reads "Updated N
skills · M left alone". Under installedOnly a Replace with no shared copy
replaces in place instead of adding one.
* fix(skills): keep location paths readable by putting the state under the path
* fix(skills): hold the last plan in state, not a ref mutated during render
Split the update dialog's rows, headline and footer into components, move the Update label
next to the other plan copy, and use z.strictObject for the request schemas.
* Escape non-ASCII characters in bundled-skill regex and wording
The orcad bundle rejects a literal non-ASCII character in a regex.
* fix(cli): escape non-ASCII apostrophes in the skills install help text
* test(orcad): check the bundled CLI plans a skill install without npx
Drop the deleted roundtrip workflow's group from the closed-PR cancellation fixture.
* Make bundled-skill test fixtures link folders the way the product does on Windows
Directory links in fixtures are junctions on Windows, which need no
Developer Mode; the one file-link fixture is skipped there.
* Resolve legacy skill agent keys canonically
* Reject stale skill replacements when auxiliary content changes
* Keep ordinary bundled skill classification shallow
* Leave unreadable skill replacements alone without aborting plans
* Pass force-all through shallow planning and describe unreadable copies
* Clarify skill update statuses and folder details
* Keep selected skill replacements in place with one status
* Report completed skill replacements with their backup confirmation
* Clarify available skill updates and skipped copies
* Keep skill update results in preview order
* Show each skill skip reason once
* Count skipped skills instead of skipped folders
* Make installer input and link tests work on Windows
* Bound bundled skill classification reads and parse names linearly
* Keep workspace skill roots inside the resolved workspace
* Compare workspace skill roots with native path semantics
* Restrict bundled skill backups to their owner on POSIX
* Keep recovered skills needing attention until they are updated
* Count completed skill updates in the status tooltip
* Sync the runtime localization catalog
* fix(sidebar): land flip-a's switch-on fixes on main, still dark (no flip)
The fixes found while the sidebar ran from main's feed, without turning
that on: ORCA_SIDEBAR_INPUTS_IS_MAIN stays false, and none of flip-a's
stop-gaps come along.
- Main stops counting a closed tab's terminal once it was asked to stop
and no pane shows it; a failed or refused stop takes the request back,
and a host out of contact stays unverifiable.
- Main fetches a paired server's rows with the transport's normal bound,
so a server slower than 5 s still lists.
- A deferred unread raise flips the folder row it was decided for, and a
read while the owner check is out cancels it.
- Phone: no worktree.ps flash before the feed; a host's last installed
feed stands in while a fresh read is out; it highlights its own
last-opened workspace and a fold never scrolls; projects file under
their project group with the desktop's indent; the collapse spinner
shows only after 500 ms; "Reopen to update" keeps the rows on screen.
- e2e helpers and journeys for the switch-on suite.
* fix(sidebar): keep an owed relay stop marked, clear a thrown stop, and prune pending unread raises
- A stop-and-wait that throws takes its stop request back, like one that fails.
- A final stop the legacy relay still owes stays marked, since the relay retries it.
- Pending unread raises are tracked only while one waits, so the map no longer grows.
- The paired-server fallback poll test gives its third fetch a reply and asserts live.
* feat(sidebar): main publishes cached snapshots and announces its own stores; detected, view and live-content domains (inert)
* fix(sidebar): keep links confirmed only by a new git run, no rescan on metadata writes, no phone wait on detected checkouts
* fix(sidebar): rescan the workspace rows when the Windows runtime default changes
* feat(sidebar): one lifecycle queue per workspace in main, with in-progress hints (switch-gated)
Create, delete, terminal.closeAll, terminal.sleep and worktree.sleep now run through one
first-in, first-out queue per host-qualified workspace: strict order, no merging, delete
cancels what has not started and waits for what has, an unconfirmed outcome pauses until
the host's own answer. Hints publish in a new inProgress feed domain (no reader yet); the
creations domain is its projection. A finished create or delete keeps its hint until a scan
started after it applies, in the same revision. Adds the paired-server metadata serializer
(saving domain, no caller yet). Replaces the creation registry.
* fix(sidebar): a create never queues behind the previous occupant's delete
* feat(sidebar): main runs sleep; the window follows (P3b)
* fix(sidebar): keep electron out of the headless server's sleep path
The desktop registers how sleep reaches paired servers; a headless serve has none.
Co-Authored-By: Claude <noreply@anthropic.com>
* test(sidebar): type-check the sleep e2e specs; drop the removed VM suspend stub
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(sidebar): resume a paused workspace by re-observing, never replaying
- A command issued while paused waits behind the pause; when the owner answers again main
re-reads terminal liveness and the listing, then carries on. A forced delete runs past it.
- Commands keep their arrival order across a workspace lookup; deletes by name key by the
resolved id, so they wait for that workspace's running sleep.
- A second delete with different options is refused as already in progress.
- Close and delete read lost contact as sleep does.
- A create always frees its own queue entry, even after a retry took over its id.
- Drops the unused opId, and the paired-server write serializer and its feed section,
which land with their first caller.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(sidebar): main is the only writer of a sleep's wake records; sleep classifies only lost contact as unconfirmed
- The window no longer captures wake records for a sleep main runs; it takes main's records
from main's answer, which its wake and tab-open resume then read.
- A workspace reopened during its sleep keeps its VM running (the window and a phone's
activation tell main).
- Only lost contact pauses: a definite chat-stop failure or a server's still-live answer is a
plain failure with today's toast.
- A paired server's chats are asked with the Sleep mark, and again without it when an older
server refuses the field, instead of reading an unprobed status.
- A phone's worktree.sleep is answered on acceptance and its activation no longer waits on a
queued wake, so neither can outlast the phone's request timeout. The web client passes
markChatsAsleep and keeps the receipts.
- One desktop adapter, passed where the desktop builds the runtime, replaces the two module
slots; a headless server gets none.
Co-Authored-By: Claude <noreply@anthropic.com>
* chore(sidebar): share worktree.sleep's params with the RPC catalog; lint fixes
Co-Authored-By: Claude <noreply@anthropic.com>
* test(sidebar): test the paired-server sleep where main runs it; drop the window's copies
The window no longer sleeps a paired server's terminals, so its convergence and order tests for
that path move to main (remote-workspace-terminal-sleep.test.ts) or follow main's answer.
Co-Authored-By: Claude <noreply@anthropic.com>
* test(sidebar): retry the sleep spec's liveness read while terminals stop
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(sleep): main resumes every workspace-sleep wake; the window only attaches
- Main claims and starts each slept pane through its windowless resume path, with or
without a window: the "window present" check is gone.
- A phone's open queues main's wake, which finishes only once the resumes have started,
and always notes the reopen so a running sleep leaves the VM up.
- The window no longer relaunches a workspace sleep's records in fresh tabs; opening a
slept pane asks main to resume it and the pane's spawn attaches to main's terminal.
- A window save cannot overwrite main's workspace-sleep records.
- A VM that would not suspend no longer reads as "kept open": the terminals slept, and
the toast says the machine is still running.
* test(e2e): main resumes a slept pane once, in place, and keeps its record
* chore(i18n): add the VM-still-running sleep message
* fix(sleep): a phone's or the CLI's failed sleep shows the desktop toast again
* fix: restore main's final P2 test fixes a merge reverted
* fix(sleep): window saves never write or clear main's sleep records; headless wake report counts only real sleeps
* test: a same-id twin's grace follows the rows' change, not the removed catalog hook
* fix(sleep): drop the 1 s settle wait after an observed exit, and wake a slept workspace's mounted agents on phone open
A workspace sleep stopped each terminal through main's stop-and-verify, which polled for a
further second after the exit it had already seen. And a phone opening a slept workspace no
longer reached the window's mounted panes of agents that had finished before the sleep, so
they stayed asleep until the window opened their tab.
* test: give the workspace-sleep wake record its prompt field
* refactor: match slept-agent records to the workspace with worktreeIdsEqual
* chore(sleep): one import from sleep-record-pane-claim
* test(sleep): route the disconnected-sleep SSH spec, declare the desktop sleep adapter, and open the workspace after its sleep starts
* test(sleep): delete a workspace the spec creates, not the seeded one later specs share
---------
Co-authored-by: Claude <noreply@anthropic.com>
* feat(resources): end local terminals with checked identity checks
- Add a PTYs view to the Resource Manager that lists local terminals and can end one. Before stopping, the host confirms the terminal's incarnation, agent ownership, and attachments. This prevents ending a same-ID replacement that started after the list was read.
- Bump the daemon protocol to v50 and gate the new fenced kill on it. Older daemons ignore the extra fields, so the client refuses End instead of sending an unchecked kill.
- Admit diagnostic reads (PTY listing and memory snapshot) only while the Resources view is visible, so a hidden window does no background scanning.
- Report incarnation and host on spawn and exit events so the renderer can match them to the terminal it showed.
- Replace the popover's terminal-session list with the new inventory and move memory polling into the Resources view.
* Show unrouted daemon PTYs in the Resource Manager
* Simplify PTY controls and clarify safe termination
* Format local build compatibility protocol lists
* Add PTY resource management localization strings
* Harden PTY resource manager test fixtures
* Use typed IPC event in PTY kill test fixture
* Refine local PTY resource view model typing
* Refine local PTY resource view model typing
* Include local host identity in PTY lifecycle event tests
* Offer Resolve with AI for paused merge, rebase, and cherry-pick
A paused operation can need recovery after every conflict is staged, but the Resolve with AI action only appeared when unmerged paths remained. The operation banner now offers it whenever AI actions are visible and an operation is in progress.
Starting an AI conflict-resolution agent is also blocked while a Git operation is running. The composer, the banner and card buttons, and the agent launch itself check the running-operation state, so a launch that began during agent detection cannot start while Git is still running.
* Block paused-operation recovery only on Orca's own Git work
The previous gate scanned the host's process table and refused "Resolve with AI" whenever it could not prove no Git process was running. That refused recovery on older paired hosts, on Windows whenever any Git process ran anywhere on the machine, and while a paged `git reflog` or `git grep` was open in a terminal.
- The host tracks Orca's own mutating Git commands per worktree (including ones still queued) and reports them through `git.mutationInFlight`. Reads and fetch/push/ls-remote are not tracked.
- Recovery is refused only when the host reports that work. Older hosts without the method keep today's behavior.
- The gate applies only to launches from the paused-operation banner and conflict card, not every resolveConflicts launch, so the Checks panel's PR-conflict flow is unchanged.
- The agent prompt now tells the agent to stop if index.lock exists or another git command is running, which covers Git started outside Orca on every host. With no unmerged files, the prompt asks the agent to find why Git paused instead of listing zero conflicts.
* Preserve Git mutation state until terminated commands exit
- Cancel stale recovery checks and agent launches when their dialog or scope closes
- Guide rebase recovery agents to amend fixes after failed exec steps
* Cover failed rebase exec recovery with real Git
* Recognize global Git options in worktree mutation tracking
* fix(native-chat): attach a long paste as a text file, and say a message is too large before it is sent
A paste of 32 KB or more (or one that would push the message past the 256 KB
send limit) into a structured chat is saved as a file the agent reads, through
the same pending-chip and @path route a dropped file takes: Orca's paste folder
here, or the chat's attachment store on its paired server. Claude gets the paste
folder as an allowed directory. A message still over the limit is handed back
before it is sent, on desktop and the phone, with words naming the limit instead
of the generic 'not sent, send it again'.
* test: Claude launch parity snapshot grants the paste folder
* fix(native-chat): a long paste replaces the selection at once and settles in place
A long paste now puts an inline Pasted text pill where it went, replacing any
selected text immediately, and the saved file (or inline text, or the original
selection on failure) replaces that same pill when the save finishes, in its own
chat's draft. Nothing reads the caret later, so order and position hold. The
paste never joins the image row, so image numbers are unchanged. The size check
measures the real message body with the selection replaced and images included.
A server that keeps no files takes the paste inline when the message still fits.
A pending paste a restart lost shows Paste not saved, and Send says so.
* fix(native-chat): type the pasted-text parser and the composer's missing draft scope
* fix(native-chat): admit paste references before goals and commands
* fix(native-chat): resolve staged paste paths without editing the draft
* fix(native-chat): consolidate paste imports for code quality checks
* fix(native-chat): retain checked string types for saved paste paths
* fix(native-chat): retain completed paste facts through history and delimit paths
* fix(native-chat): keep structured question rendering within file limit
* fix(native-chat): pasted or inserted text that looks like a paste token stays literal
* fix(web): keep the browser's UI bridge under the file length limit
* style: format the files #29982 merged unformatted
* fix(native-chat): retain oversized drafts before send admission
* fix(native-chat): keep the composer handle's merge resolution to main's destructure
* Add onboarding language selection and system locale detection
- Detect and synchronize native/browser language preferences safely
- Load catalogs before rendering onboarding and support recovery
- Let users choose a supported language or request another language
* Add onboarding language experiment cohorts and detection recovery
* Stabilize onboarding language detection tests
* Test persisted onboarding language cohort behavior
* feat(ssh): open a plain SSH terminal to a host whose move to its Orca server is blocked
A refused or unserved host fails the whole connect, so the user had no way to
reach their work there. The blocked-move banner and the SSH host row now offer
an unmanaged, unpersisted ssh login in a non-modal window. Main builds the argv
from the saved target (no -T, BatchMode or Orca multiplexing; the config alias
stays bare so ssh_config applies) and spawns ssh with an argv array through the
local PTY spawn primitive.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(ssh): claim a host before awaiting, wait for ssh on quit, and share the preview terminal's keys
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(ssh): give the plain SSH terminal Orca's Unicode widths, kitty flags and spawn-helper repair
Co-Authored-By: Claude <noreply@anthropic.com>
* refactor(terminal): one standalone terminal for the agent preview and plain SSH
Extracts the preview's xterm setup (Unicode widths, links, IME bridge, kitty
flags, copy/paste and shortcut policy, user-input routing) into
openStandaloneTerminal, so the plain SSH terminal reuses it instead of a second
copy. Plain SSH output is now acked once rendered, and main pauses ssh while too
much is unrendered.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(ssh): plain SSH answers terminal queries, keeps early exits, and ends a paused pty on Windows
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(ssh): plain SSH applies theme changes live and never refits while minimized
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(ssh): a native call on a just-exited plain SSH pty never crashes main
Co-Authored-By: Claude <noreply@anthropic.com>
* test(ssh): give the plain SSH test's ResizeObserver stub unobserve, which Tooltip needs
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* feat(terminal): the window's store shows main's pane start status (layout-owner build)
A status-only frame changes one store entry and no pane tree, so the pane's view stays mounted.
* refactor(terminal): keep the frame sync within its line budget
* feat(terminal): every pane main starts shows 'starting' from before it is written (layout-owner build)
* feat(terminal): a pane main is starting waits and attaches; a failed start shows why (layout-owner build)
Fixes owner-visible row 15: a failed agent or setup start no longer opens a plain shell.
* test(terminal): iterate store listeners directly
* fix(terminal): an exit that reports no code keeps its pane, as lost contact (layout-owner build)
* test(terminal): a pane main started attaches to main's terminal at its own grid
* test(terminal): detach census tests assert main's move and frame with the switch on
* fix(terminal): a restored pane paints history only from its terminal's host, never the window's copy (layout-owner build only)
* fix(terminal): main releases a painted cold restore only for the pane's current terminal (layout-owner build only)
* test(terminal): typed ack args in the painted cold-restore test
* test(terminal): exited-pane tests assert main's close with the switch on
These 8 have failed switch-on since #31130 moved the exit close to main.
* feat(layout): main starts the panes a client's startPane or wake names, through its one restored-pane starter (layout-owner build only)
The model's start and stop effects reach the runtime once the command is written, never the
reply. startPane starts a pane whose binding has a start status, and a slept pane from its record.
A wake keeps a pane's resume record until its start binds; a failed start keeps it and shows why.
With the switch on, main owns a running agent's wake even with a window open.
* fix(layout): a window's wake of a pane it resumed itself is record-only, so main does not start it again (layout-owner build only)
* test(layout): a client's startPane starts the pane before any other trigger
* Revert "fix(terminal): a restored pane paints history only from its terminal's host, never the window's copy (layout-owner build only)"
This reverts commit a646d89b26.
* test(terminal): the cold-restore repaint ack names its pane in the layout-owner build
* fix(layout): a wake keeps the pane's resume record in the pane-owned form, so a host crash resumes the agent again (layout-owner build only)
* test(layout): a failing start in the pane-start runner test throws
* feat(terminal): main writes and starts every split of a pane it holds; the window attaches (layout-owner build)
The window's split, a startup split with its command, and a CLI split all go through main's
terminal.split; main's frame mounts the pane, with or without a window intent. Deletes the
window's split wait and withdraw, the split appear intents, and the queued-split skip and cancel.
The e2e helper uses the real split. A startup split leaves focus on its source pane.
* fix(terminal): the attach-only wait leaves sleeping, resuming and held panes to their restore path
* fix(terminal): a split pane attaches to the terminal main bound it to; a taken pane id is refused
Also restores the proof that a split closed while it starts gets one answer and leaves no
pane or start status behind.
* Reapply "fix(terminal): a restored pane paints history only from its terminal's host, never the window's copy (layout-owner build only)"
This reverts commit 420e605ad0.
* fix(layout): a trigger between a restored pane's start and its bind write joins that start, not a second one (layout-owner build only)
* fix(terminal): a window split main cannot start still shows its pane, failed, with why
* fix(terminal): keep the switch import the D1b-1 merge dropped from the layout restore
* feat(layout): main's starter shows a restored pane starting before any await, and a failed start with its reason; startPane starts a pane whose bound terminal ended (layout-owner build only)
* feat(terminal): a window's attach-only view of a pane attaches to its terminal or starts nothing, keeping the binding for main's start (layout-owner build only)
* feat(terminal): a restored pane with no live terminal asks main to start it and attaches once main binds it; the window's wake, hibernated wake, restart-on-show and gone-terminal fallbacks route through main (layout-owner build only)
* fix(terminal): check a taken pane id first, keep the start's own error, clear a gone pane's status
Three test stores gain the pane-start field the window's store always has.
* test(terminal): the admission stub answers whether its pane is still written; a closed pane's failed start clears
* test(terminal): with the switch on, the window's restores, wakes and restart replacements ask main to start the pane and attach to the terminal main binds, starting and resuming nothing themselves
* refactor(terminal): the rebound workspaces' bindings show through one call in the bindings module
* fix(terminal): a held pane connects as today so main's refusal holds it; main's start follows output; a capacity death after main's resume keeps the resume (layout-owner build only)
* fix(layout): main passes its start options only for commands that start panes; a windowed open resumes the agent under main (layout-owner build only)
* refactor(terminal): break the pane start module cycle; typed test fixtures
* test(terminal): a pane main wrote and the user closed mid-start drops its failed status
* fix(terminal): every waiter on main's start runs, a failed or refused start settles and is asked again on the next view, a hidden gone pane waits for its view, a twin main hands back stays on today's path, and lost contact never starts a pane (layout-owner build only)
* test(terminal): iterate the store listeners directly
* test(terminal): a cold-restore paint applies no window resume with the switch on
* fix(terminal): a split main starts is listed to phones and session.tabs without a window's report
* test(terminal): name the main pane start test helper as a fixture
* fix(terminal): a reconnect shows its pane starting and why it failed; a lost owner race shows no failure; a split no one asked to see keeps the phones' tab
* fix(terminal): a kept create another owner won shows no failure; the window attaches to the winner
* fix(terminal): a pane main starts at relaunch keeps its full history and starts before the window's graph lists its tab (layout-owner build only)
* feat(terminal): main replaces the empty shell a hibernated agent left and resumes the agent when a view asks; a start main could not attach shows main's own terminal_not_started (layout-owner build only)
* test(terminal): main seeds its copy of a restored pane even when a window declared one, with the switch on
* fix(terminal): a pane main started hidden paints once when its view attaches (switch-on builds only)
The view's attach snapshot, taken after main unmarks the hidden gate, already holds the bytes main
dropped, so the reveal no longer asks a second restore from main's buffer.
* refactor(terminal): main's pane start reads the pane from its own layout, not the client projection (switch-on builds only)
* fix(terminal): a pane restart stops its terminal before writing the pane unbound, so a stop that fails keeps the pane (switch-on builds only)
* test(terminal): a start main could not bind shows terminal_not_started through the window's wait, from one copy site (switch-on builds only)
* fix(terminal): main's pane start resumes an agent recorded under the pane's number, as the window did; stale switch-on tests show main's start and the known banner loss (switch-on builds only)
* fix(terminal): review round: a visible view alone retires main's hidden-start drops, by episode; main reads a split pane's numbered record by its number; the window keeps a shell main kept (switch-on builds only)
* fix(terminal): main's start of a pane a window view is attaching to leaves it visible, so the view paints once (switch-on builds only)
* fix(terminal): a window view that attaches while main starts its pane takes main's start, with no second provider attach (switch-on builds only)
* fix(terminal): review round: a view takes main's start only when it carries a payload and still owns its terminal; attaching views are counted, keep main's pre-spawn mark off, and a failed attach hides only what it kept visible (switch-on builds only)
* test(terminal): checked test doubles in the view-join and numbered-record tests; reattach options fit the file budget
* test(e2e): with the switch on, the mount-authority spec keeps the window's bindings, which only main's frames write
Claude-Session: W5b
* test(e2e): the mount-authority spec fits its line budget; the layout-owner build check is one helper
Claude-Session: W5b
* test(layout): an unbound pane holding a resume record starts and keeps the record, since main's start resumes from it
Claude-Session: W5b
* fix(terminal): a pane main resumed holds a running agent's record, so it reads awake, not sleeping (switch-on builds only)
Claude-Session: W5b
* fix(terminal): in a split where no pane owns a numbered record, main resumes the tab's one conversation in pane 1 only (switch-on builds only)
Claude-Session: W5b
* fix(terminal): main marks only the record a resume started from live, never a sleep captured after it (switch-on builds only)
Claude-Session: W5b
* test(terminal): with the switch on, a windowless resume leaves the pane's record a running agent's
Claude-Session: W5b
* test(terminal): the resumed-record test double typechecks
Claude-Session: W5b
* refactor(terminal): with the switch off, main's pane start reads no resume record for the live mark
Claude-Session: W5b
* refactor(terminal): the merged files fit their line budget: the folder-fallback notice and a hidden pane's wait for its first view are helpers, and the answering-transport lookup moves to its one test caller
Claude-Session: W5b
* refactor(runtime): the window's layout apply fits the runtime's line budget after the main merge
Claude-Session: W5b
* fix(wsl-host): apply the SSH update rules to a WSL connect
A WSL connect deployed this app's orcad whenever the active build differed,
so a distro another, newer Orca had activated ended in an unconnectable
deferral, and a build the host had rolled back from was re-activated on
every connect. It now runs the same planManagedOrcadAutoUpdate decision SSH
uses and serves the active build when that plan skips.
* fix(wsl-host): serve the skipped-update incumbent only if it is still active
* fix(orcad): one update decision for SSH and WSL, applied again under the fence
The connect's update plan moves to its own module, and the fenced admission
every first deploy and WSL connect uses now applies it, so a transient
readiness failure or a race cannot reactivate a rolled-back build.
* refactor(wsl-host): adopt the serving build through the SSH first-deploy adoption
* fix(wsl-host): do not retry a candidate rejected earlier this launch
A rejected candidate is restored without recording rolledBackFrom, so WSL
retried it on every connect. SSH's launch-scoped failure memory moves to a
shared helper keyed per host, and WSL feeds it to the shared update decision.
A null adoption now re-reads the record before the deploy, as SSH does.
* fix(wsl-host): keep Force offered when a connect skips this app's build
The WSL connect's update admission moves to its own module, which returns a
forceable hint when it serves the incumbent over a rolled-back or failed build.
* fix(orcad): connect, Update and Force are the three update intents on SSH and WSL
Only a connect applies the skips; an explicit Update retries a skipped or failed
build (clearing the failure memory) behind the terminal census; only Force passes
the census. WSL's Update button now sends the update intent instead of a plain
connect. A connect that records a rejection serves the restored incumbent at once.
* fix(orcad): an explicit Update keeps the newer-host skip on SSH and WSL
Update lifts only a host's own skips (rolled back, failed before); Force lifts
all. SSH's explicit update now applies the same fenced admission, so stable
cannot replace a host a newer Orca activated without Force. A rejection retries
adoption only once the incumbent is back and no journal awaits recovery.
* fix(wsl-host): keep Update and Force offered on a deferred cold connect
A connect the host deferred with nothing serving now publishes its deferral, so
a host stopped after a rollback can still be retried from the UI. SSH's explicit
Update forgets a remembered failure only once it lands, and a restore that could
not be read keeps the rejection as reported.
* fix(wsl-host): offer only Force over a host a newer Orca runs, and keep failure memory until an Update lands
A host-newer refusal gets its own deferral code so the WSL row hides Update,
which can never pass it. An explicit Update no longer clears the launch failure
memory up front; only a landed deploy does, as on SSH.
* refactor(wsl-host): merge the intent type into the existing shared imports
* fix(terminal): start every terminal in the workspace root when its saved folder is gone, and say so
A terminal whose start folder was deleted opened at the workspace root only
when the window started it; the CLI, phone restore, orchestration and other
host starts failed with the raw "Working directory ... does not exist" error,
because the renderer had to opt in with a cwdFallback flag.
The execution host now makes that decision for every start that may create a
process (anything but a known live pane), with no caller flag. The fallback is
reported only when a process was proven started at the requested folder, so a
reattach or a crash restore in its recovered folder never claims "workspace
root", and only then does the saved start folder heal. A recovered folder that
was itself deleted yields to the request's resolved folder.
The notice reaches each caller through surfaces it already has: the window's
terminal line (now also written when a host-created tab attaches, and on a
restored reattach), the agent.launch warning (also for callers that show the
tab themselves), and a toast when a phone tap restores the tab.
* fix: recover terminal startup folders without healing unreadable paths
* fix: preserve saved start folder when revealing fallback terminals
* Reuse saved-tab lookup in cwd persistence test
* Import mobile tab activation result from its contract
* Check startup fallback response without a test cast
* Update expectation to catch up with accepted saved-folder metadata
* Preserve inconclusive WSL directory evidence when saving startup folders
* fix: project restored terminal ids for handle consumers
* test: return void from launch parity cleanup
* test: retain saved folder in shared spawn expectation
* fix(runtime): a phone reads and saves Markdown tabs on a headless server
With no window to relay through, markdown.readTab and markdown.saveTab answered
renderer_unavailable. Main now reads the tab's file (or its saved draft) from disk and
saves with a version compare plus the disk write precondition, in the shapes the
window's editor relay returns. With a window attached the relay is unchanged.
* Headless Markdown save: keep the draft's disk baseline, honour read-only tabs, re-find the record to clear
* Use the disk baseline only for a draft
* ci: run the phone Markdown spec's orcad case in the orca serve mode-switch job
* Headless Markdown: answer from disk only; a window's saved draft stays its view state
Clearing a saved draft wrote workspace layout from main, which no layout command carries.
Main now never shows or edits a draft; the desktop's restore check reports a disk change.
* Phone Markdown: one path through main and the disk, window or not
A paired server's workspace is read and saved on that server with files.read /
files.writeIfUnchanged, so it answers from its own disk and keeps the precondition.
The window relay (ui:mobileMarkdownRequest and the renderer bridge) is removed.
* e2e: a phone saves a Markdown tab the desktop holds unsaved edits in
The phone reads the disk with the tab marked dirty; its save lands on disk, and the
desktop keeps its draft and shows the changed-on-disk banner.
* Group the Markdown disk host's imports
* Drop an unneeded store cast from the dirty-readers test
* Phone Markdown: refuse a save on a server too old to check the disk; cover the untitled tab
A server before #30605 has no files.writeIfUnchanged, so the save is refused with the
existing update-required message rather than written unchecked.
* Phone Markdown: refuse a file an SSH host holds outside the workspace; pin the untitled answer
* Phone Markdown: untitled tabs stay read-only; older servers save like the desktop editor; a closed-source preview still reads
- The desktop lists an untitled document's tab with isUntitled (optional, additive); main answers
it read-only with unsupported_untitled and refuses its save before touching the disk.
- A server without the files.write.disk-precondition capability gets the desktop editor's guarded
save: compare from here, then a plain files.write, or a conflict. Every call of a save is pinned
to the pairing it probed.
- A preview whose source tab was closed has no saved record; it reads its own file, read-only.
* e2e: read the untitled tab's answer without a cast
* Phone Markdown: pin a save's pairing before its first read
A save captures the server's pairing before it reads, and every call of the save, the read
included, is pinned to it, so a re-pair between the read and the write is refused.
* Test: with no window, a phone saves an existing untitled.md and never creates a deleted one
---------
Co-authored-by: E2E Test <e2e@test.local>
* fix(native-chat): name a chat from its first message at once; the generated name replaces it
A native chat's one stored name was written only by a best-effort, one-shot
generation, so any failure left every surface on the 'Codex Chat' placeholder.
The first prompt now names the chat when its first turn starts, and the
generated name replaces it only while the field still holds that seed.
* fix(native-chat): the chat names setting says a chat shows its first message until it is named
* fix(native-chat): retry a failed first chat name twice before keeping the first message
A failed name generation was final for the chat's lifetime. The first name now
gets two retries, after 2 and 4 seconds, and stops early once the
first-message name was replaced.
* fix(native-chat): show a failed chat name on the tab hover card, with Regenerate name
A chat whose name generation failed after every retry gave no sign of it, and
nothing could run it again. The host now keeps the last failure in memory and
publishes it with the chat's status while the chat still has the name it failed
to replace; the tab hover card shows it with the error dialog, and a new
agentSession.regenerateName (behind a capability) makes one more try.
* fix: derive chat naming progress from host jobs
* fix(native-chat): merge duplicate import CI caught
* fix(native-chat): declare agentSession.regenerateName in the cross-version surface manifest
* fix(native-chat): stub naming-failure hook in SortableTab rename shortcut test
Behind ORCA_LAYOUT_OWNER_IS_MAIN, a CLI or phone file open is applied by main as an
openEditorTab layout command, so a host with no window can open files. The window
reports each workspace's active group as an optional view fact on syncWindowGraph,
and shows main's tab once the frame that draws it arrives. Editor language
detection moves to shared so main can name a file's language.
Co-authored-by: E2E Test <e2e@test.local>
* feat(accounts): save MiniMax and GLM Coding Plan keys on the Settings host
The usage poller on an SSH, `orca serve` or paired server reads these keys from
that machine's ~/.orca, but Settings could only write them on this computer, so
those hosts never showed the meters. New accounts.* RPC methods (advertised as
accounts.usage-credentials.v1) save, clear and report the keys on the host and
refresh its usage. Settings routes the MiniMax and GLM sections, and their
endpoint/site settings, to the host picked in the page; an older host is asked
to update instead of the key being saved locally.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(accounts): a host whose desktop owns its accounts refuses usage keys instead of storing them unread
A WSL host's server has no usage poller, so a key saved there was reported
saved yet never read. Every usage-credential method now throws the same
desktop-owns-these-accounts message as the other account methods, and the
host stops advertising accounts.usage-credentials.v1.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(accounts): a paired client never overwrites a usage key the other Orca app on that machine sealed
The desktop app (safeStorage) and orcad (its own host key) share ~/.orca but
cannot open each other's sealed files. A remote save or forget of a MiniMax or
GLM key that this host cannot read is now refused, and the status reports it so
Settings locks that key and says to change it in the other app. Blank keys now
fail parameter validation.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(accounts): only blame the other Orca app for a key it really sealed, and always allow forgetting
Any read failure was treated as "saved by another Orca app", so a corrupt file,
a permission error or a host that cannot decrypt at all locked the key with a
false message and refused Forget, the only recovery. The host now reads the file
itself: only a well-formed encrypted envelope that a working store cannot open
counts as sealed elsewhere and blocks Replace; anything else is reported as
unreadable and stays editable. Forget is always allowed, and the check logs
nothing per status poll.
Co-Authored-By: Claude <noreply@anthropic.com>
* test(e2e): fail clearly when the orcad slot is missing instead of passing null as the program
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(settings): name the host in remote key descriptions, give the MiniMax API key its own failure toast, and treat malformed sealed files as unreadable
---------
Co-authored-by: Jinwoo-H <e2e@test.local>
Co-authored-by: Claude <noreply@anthropic.com>
* fix(worktrees): a sort order and a minted push remote land on the copy that named them, not skipped for a twin
The renderer already knows each id's host when it splits a sort order; it now sends it
(an optional parallel hostIds; older peers ignore it and keep the skip-twins rule). An IPC
git handler knows the host it required, so a minted push remote records on that copy.
* fix(worktrees): a twinned repo re-lists instead of an id-keyed sort patch; client hosts must name a registered repo
* fix(worktrees): a sort order naming an unregistered host skips that id instead of falling back
* fix(worktrees): a moving project's sort order is fenced before any host-qualified read
* fix(worktrees): a client's local sort-order host matches this server's own runtime stamp
* fix(worktrees): an exact host match wins before the own-runtime alias
* fix(worktrees): terminal-spawn materialization serves only a local row and records on the local copy
* test: drop casts from the spawn twin tests
* fix(worktrees): adopt evidence and the spawn hook stay on their host; an unmatched local keeps the id-only rule
* fix(worktrees): adopt evidence reads the host's own metadata rows
* fix(worktrees): reusing a same-URL remote reads ownership from this host only
* fix(worktrees): a server's own runtime-stamped row still materializes its push remote
* fix(native-chat): a chat's visuals folder lives as long as the chat
A chat's scratch folder was deleted once the chat had gone 30 days
unused, so an old chat's inline visuals turned into "Visualization
unavailable" while the chat and its history stayed. The common pattern
keeps a chat's files for as long as the chat and never deletes them on
an "unused for N days" timer.
The sweep now removes only folders no chat record maps to, plus
leftover .expired folders older builds parked. Everything that existed
only to make the age rule safe goes with it: the per-folder save lock
and its expiry candidates, the session-lane idle check, the journal
last-activity query, and the migration's fresh-window timestamp. The
visuals skill no longer tells agents the folder is temporary.
* fix(native-chat): an image sent in a local chat lives as long as the chat
A local chat's pasted images lived in one paste folder that deleted
every file older than 30 days, sent or not, so an old chat's images
turned into label chips. Paired-server chats already keep pastes in the
host's attachment store, where the send that names a file claims it for
its chat and only unclaimed files are ever swept: the common pattern.
A local composer paste now goes into this machine's own attachment
store (starting the local chat host first), so its send claims it the
same way. The old paste folder is no longer written or swept: messages
sent before this change may name its files. Restored drafts still read
it.
An unclaimed upload now waits 30 days instead of one before the sweep
takes it. A draft names its image by this path, so the window is how
long an unsent draft keeps its image; 30 days is what local drafts had,
and it outlasts the host's send-id window so a resent draft still finds
its image.
A chat folder an older build set aside as unused (<hash>.expired) goes
back to its chat instead of being deleted.
* fix(native-chat): a terminal-backed chat's pastes are never swept
A terminal-backed chat has no journal, so no send claims its pastes and
nothing can tell a sent one from an abandoned one. Its pastes therefore
stay in the paste folder, which is no longer swept, rather than in the
attachment store, whose sweep would take them after 30 days unclaimed.
Only a local structured chat's paste (forStructuredChat) goes to the
store, where its send claims it.
* fix(native-chat): keep the attachment owner's literal kind for structured local chats
* test(ipc): expect the local chat store option on the clipboard registrar
* fix(native-chat): preserve attachment ownership and restore held files safely
* fix(native-chat): settle restored image checks independently
* test(native-chat): assert restored image outcomes without batching
* Re-check draft images when the attachment sweep removes their upload
A draft checks its pasted images once, when its composer comes back. Every
chat in an open workspace mounts its composer at launch, so that check ran
before the store sweep (60 s after host start, then every 6 h) deleted an
expired unsent upload, and nothing checked again: the chip lost its picture
with no Not kept feedback and Send stayed enabled.
The sweep now reports each removal attempt; the main process forwards it to
its window, and the draft store starts a fresh finite check for every loaded
local draft image in that folder, mounted or not. A notice is a reason to
check, never proof of absence.
* Let the app effect remove the draft re-check listener it adds
startNativeChatDraftLoad dropped the unsubscribe for the upload-removal
listener, so each recovery through the root error boundary's Retry remounted
App and added another listener. Return it so the effect's cleanup removes it.
* fix(persistence): session, automation-label writes take the workspace's host; exempt minted-unique folder and group ids (R4-2c)
* fix(persistence): keep the session removal's spill rule; the twin guard was unreachable (review)
* fix(persistence): repo-row store writes take the row, not the id; ratchet the rest (R4-2a)
* fix(persistence): host-filter the runtime nested import; widen the id-only write ratchet (review)
* fix(runtime): nested import keeps matching runtime-stamped native rows on a paired host
* fix(persistence): class-scope the ratchet param exemptions; update a new main test to the target shape
* fix(ipc): fence a project-group move by the resolved twin host, so an SSH twin is not WSL-fenced
* test(ci): pin the id-only store write ratchet step to the static phase
* feat(files): guarded file writes that refuse to overwrite a changed file
Adds an optional disk precondition to file writes: the writer compares the file
on disk with an expected signature inside the write, serialized per file (path
then inode), and refuses with a typed changed/missing result instead of writing.
Remote hosts serve it as files.writeIfUnchanged behind a capability; older
hosts get a client-side compare that fails closed, pinned to the probed runtime
and connection. Undo of a delete and the MCP starter config use it to write only
into a missing or identical file. Groundwork for guarding editor saves.
* fix(files): pin guarded writes on the sending socket; keep refused undo; narrow not-found and pinning
* fix(files): pin a guarded save to one pairing; keep only refused undo steps
* fix(files): ENOENT by code only; resolve symlink chains fully; pin lost-reply recovery to the save's pairing
* Main keeps an agent's resume record from the status its hook server accepts (dark, layout-owner build only)
In a layout-owner build, the window's recorder and quit capture kept only
records of tabs the window holds, so an agent in a workspace the window
never opened lost its resume record at quit. Main now applies the window's
rule (moved into src/shared) to every hook status for a pane it holds, and
captures its own panes' agents at quit; the window's quit capture is gone.
Co-Authored-By: Claude <noreply@anthropic.com>
* A snapshot replay looks up a pane's tab only when its status can leave a resume record
Restores the lookup order the window had before the record builder moved to shared code, and
updates the two handler tests for the quit-records option.
---------
Co-authored-by: Claude <noreply@anthropic.com>
* 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>
* 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 #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>
* 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
* fix(drops): name extensionless dropped images by their bytes
A file dropped onto a remote workspace with no extension (some drag sources
hand images over as `noname`) now gets the extension its PNG/JPEG/GIF/WebP
signature declares, so the agent sees `noname.png`. Repo icon detection
reuses the same signature check.
Fixes#30378
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(drops): sniff extensionless images only for .orca/drops and chat attachments
Explorer imports into a user-picked remote folder keep the name, matching
local imports. Chat attachments on a paired server now use the staged name
so they get the extension too.
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Neil <neil@stably.ai>
* fix(layout): an SSH pane's terminal binds in its own session partition (dark, layout-owner build only)
Main keeps its own layout over a window save, so a binding only a save carries is lost. A window
or runtime spawn of an SSH pane bound in the local partition; it now binds where the pane's layout
lives.
(cherry picked from commit beb2275b45)
* test(layout): the SSH binding test uses the shared spawn commit dependencies
(cherry picked from commit 672b641897)
* fix(layout): main moves a tab between a local and an SSH workspace itself (dark, layout-owner build only)
moveTabToWorkspace across two partitions was refused as other_partition and left to the window's
save. Main now reads both partitions, moves the tabs as within one, and writes both in one durable
mutation.
(cherry picked from commit ee27a0bb07)
* chore(layout): one binding write for the partition choice; shipped builds keep their exact replaced-binding read
* fix(layout): window saves keep main's layout and give it only the window's view and facts (dark, layout-owner build only)
windowViewSaveForMain/windowViewPatchForMain replace windowSaveForMain: main's own layout joined
with the window's view, facts and carried fields, written only into the records that change. A
paired server's partition passes through. A visited workspace main holds keeps its terminal row
(D1). The P10 pending case is on.
(cherry picked from commit 0c2fc15974)
* fix(layout): the save divergence detector becomes a report-only drift check behind its log (dark, layout-owner build only)
It no longer wraps the write: it compares the window's raw save with main's layout model, and
only when ORCA_LAYOUT_SAVE_DIVERGENCE_LOG is set.
(cherry picked from commit 6bc1e5556f)
* fix(layout): tab reconcile and terminal reconnect stop writing tab structure (dark, layout-owner build only)
Main's frames draw the tab bar and rows: reconcile still returns its counts but writes nothing,
and reconnect still advertises restored terminals but leaves the rows to main.
(cherry picked from commit ef8eb625ca)
* fix(layout): a layout command main cannot apply is a logged refusal, not the window's own change (dark, layout-owner build only)
other_partition, a thrown call, and host_not_owned for a workspace that is not a paired server's
are refused and logged: a window save no longer carries the change, so drawing it would last only
until the next frame. A twin main may route to a paired server keeps today's path, as does the
first terminal's host_not_owned fallback.
(cherry picked from commit 2c513b2d10)
* fix(layout): the window's refusal code type keeps main's host refusals
(cherry picked from commit 36d46bdcc2)
* fix(layout): the quit checkpoint records only the quit capture's resume records in main (dark, layout-owner build only)
Main keeps its own resume records over a window save, so the quit capture's changes, which reach
main as async commands, could miss the quit. The capture now sends only what it changed with the
synchronous stage, and main writes them through the record-only sleep command before staging; a
wake main recorded that the window had not seen stays.
(cherry picked from commit 2a0f433c34)
* test(layout): the quit capture test builds a typed resume record
(cherry picked from commit d92d1246ee)
* chore(layout): one layout write per save handler
* chore(layout): shipped builds keep their exact quit capture
* fix(layout): the drift check compares structure only, not records main writes itself (dark, layout-owner build only)
Pane bindings and resume records, default-tab marks, closed-tab records and terminal row owners
are main's own writes; a save that has not caught up with them asks main for nothing, so they are
no longer reported.
(cherry picked from commit 93e8b2627c)
* test(layout): relaunch first-terminal cases, and the side-by-side resume case moves its tab the way the window does
(cherry picked from commit ab381332b1)
* test(layout): the relaunch case also checks a reopen that cleared read-only
(cherry picked from commit bbf3d040c8)
* docs(layout): the drift check names the record kinds it ignores
* revert(layout): drop the SSH binding partition change; SSH terminal starts are refused before any binding is saved
getProvider refuses every non-local host, so neither spawn path reaches persistPtyBinding for an
SSH pane, with the switch on or off.
* test: run A2's two real-SQLite session tests in the Node runtime project
* fix(runtime): show Re-pair required for revoked servers and re-pair in place
A server that revokes this computer's pairing token was labeled "Update
required" because auth refusals and protocol mismatches shared one blocked
state. Carry the blocked reason locally, label revoked hosts "Re-pair
required", and add a Re-pair action (sidebar host menu + Settings server
row) that verifies a new pairing link reaches the same server before
replacing the saved pairing.
* fix(sidebar): stop host header menu clicks from toggling or dragging the row
* fix(sidebar): vertically center host header label and menu button
Importers call worktreeIdsEqual from shared/worktree/id directly (the alias was a renamed
re-export). isRemoteAgentHooksEnabled becomes isWslAgentHookRelayEnabled; the env var and its
parsing are unchanged. ws-transport inlines its one-line HTTP server factory, and a duplicated
acquisition-id doc comment goes.
The legacy relay bridge counted viewers per PTY id and released them by PTY id. A pane whose
attach answered after it left (or after a reconnect replaced the bridge) handed its viewer back
by PTY id, so the release landed on the current bridge and dropped a successor pane's viewer:
that pane stopped receiving output. Each attach now returns an acquisition id (in-app IPC only;
the relay wire is unchanged) and detach releases exactly that acquisition.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
A reattach applies the relay's screen from main's output stream instead of the invoke reply, so its clear never erases output printed after the snapshot. An endpoint refresh re-reads leases after asking its relays, so a pane that exited meanwhile is not republished live. An attach answered after its pane left hands its viewer back to main.
Co-authored-by: Claude <noreply@anthropic.com>