* 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>
* feat(mobile): Resume in New CLI from a phone chat tab
A chat tab on the phone had only Close in its long-press menu, so a chat
that would not load left no way to reach its conversation. The phone now
offers Resume in New CLI, like the desktop tab menu: the chat's host
prepares the conversation and the phone opens a copy in a new terminal tab.
The resume and fork startup builders move to src/shared so the desktop and
the phone build the same command.
* test(mobile): type the fake terminal create instead of casting its calls
* fix(mobile): pass the host's fork source as is; match the quoted args in the test
* test(mobile): give the fake host client the untyped mock the other client fakes use
* style: format the shared resume builder and phone fork files
* Fix mobile chat CLI fork creation on owning host
* Fix mobile chat fork lint and split create owner helpers
* Keep chat fork beside phone anchor across tab groups
* Combine mobile terminal runtime type imports
* Repair mobile create CI contracts after chat fork extraction
* Classify chat fork preflight refusals and preserve mobile create recordings
* Keep route-owned create cleanup quiet after unmount
* Preserve typed runtime repair refusal in chat fork preflight
* Preserve checked store through mobile chat fork preflight
* Register mobile chat fork runtime fixture before test setup
* Remove unused mobile create recording value
* style(mobile): format the chat fork create files; move fork reply messages into the fork feedback module
Formatting pushed the create hook past its line limit, so the fork-only reply message, partial-success warning and host capability check now live beside the other fork messages. Same order and copy as before.
* Keep quick command mutation aligned with formatted request
* Keep mobile chat CLI forks in terminal view
* Pass mobile chat resolution its existing controller arguments
* Match the mobile create fixture mock layout
* Keep mobile chat CLI forks open when placement is unavailable
* Brace placement contract fixture checks
* test(mobile): keep the prompt controller mock under its line limit after the main merge
* Expose pane admission in the runtime test fixture type
* Complete the chat fork contract controller fixture
* feat(wsl-mover): the ledgered desktop rewrite stage that finishes a committed move (D1b-7b)
* fix(wsl-mover): a move finishes its rewrite only once every store exists (D1b-7b)
* fix(wsl-mover): chats never move per decision C, the journal enforces registry completeness, stores time out (D1b-7b)
* feat(space): open disk usage in a separate window
* perf(space): move local disk scan off Electron main thread
* fix(space): satisfy full PR quality gates
* fix(ssh): hidden relay-era panes stop sending every output chunk to the window; a reveal re-reads the relay's screen
* fix(ssh): reveal replays at the relay's snapshot boundary and hidden panes still answer terminal queries
* fix(ssh): hidden bridged panes still deliver split queries, OSC marks and bells
* fix(ssh): hidden bridged panes answer cursor queries from a current screen and keep sequences split at visibility changes
* fix(ssh): hidden bridged panes keep kitty, window-size and DCS queries, and reopen an OSC cut at reveal
* fix(ssh): an opted-in desktop serves its browser to a managed SSH host's orca CLI again
* fix(ssh): scope bridged pages through the renderer, render them as desktop pages, and route the e2e
* fix(ssh): bridge refuses file URLs and clipboard reads, revokes on disconnect, closes desktop tabs
* fix(ssh): judge bridged paste chords as the page sees them; accept long workspace ids
* fix(ssh): bridged clipboard denial follows popups; revoked commands never leave the browser queue
* fix(ssh): refuse option-like host values bound for agent-browser argv; report undeliverable results
* fix(ssh): bound bridged error results and fall back when the host refuses one
* fix(ssh): bridge only routed host tabs, and bound bridged page scripts by a deadline and revocation
* fix(ssh): cap waiting bridged commands below the desktop socket's result capacity
* fix(ssh): bridge revokes on routing changes, checks each page's route at run time, and cancels host-driven downloads
* fix(ssh): name the bridge registry's owner type
* fix(ssh): bridge refuses pages showing a local document, and never starts a command past orcad's deadline
* fix(ssh): bridged commands may leave Chromium error pages, and stop starting before orcad's deadline
* fix(ssh): ask for a fresh opt-in to host CLI control saved before it drove the desktop browser
* test(ssh): type the bridge registry deadline test
* fix(terminal): log a failed pane reveal instead of rethrowing it
The bridge already replies false when the reveal throws, so the rethrow only
surfaced as an uncaught renderer error after the caller had its answer.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(terminal): refuse terminal.reveal from paired clients
It moves this host's window. terminal.focus lets a paired client do that only
by asking for host navigation, and the callers this method serves (the CLI and
companion apps on the same machine) never arrive as paired clients.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* feat(terminal): show a paired server's terminal in this desktop on terminal switch
A paired `orca serve` host has no window, so `orca terminal switch --environment <name>`
resolved the handle there, answered `navigated: false`, and the desktop stayed put (#24688).
Add `terminal.reveal`, a desktop runtime method that names a pane by worktree, tab and leaf, as
the owning host knows them, and runs the Agent Dashboard's click-to-focus sequence before raising
the window. When a paired server cannot navigate, `terminal switch` asks the desktop on this
machine to reveal the terminal instead.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* test(e2e): terminal switch on a paired server moves the desktop on this machine
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* test: update terminal manifest count after main and assert the revealed pane renders
Main added a terminal method since this branch was cut, so the characterization
count is 39 with terminal.reveal. The e2e now proves the target pane is visible
in the DOM, not only selected in the store.
Co-authored-by: Velm14 <Velm14@users.noreply.github.com>
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Neil <neil@stably.ai>
Co-authored-by: Velm14 <Velm14@users.noreply.github.com>
* fix(ssh): flag a macOS host whose managed server outlived its launching SSH session
macOS Local Network privacy pins a nohup-launched orcad to the sshd-session
that started it; once that session ends, its terminals lose LAN access (#25934).
A macOS launch now records that session, a connect reports it when it proved
exited, and the host status offers a restart when no terminals are running.
* fix(ssh): retire the idle terminal daemon on a launch-session restart
* fix(ssh): keep the fence on an unsettled restart stop, and report a restart that can't hold its session
* fix(ssh): carry the launch session forward while the terminal daemon survives; never redial for the check
* chore(i18n): add the launch-session warning and restart strings to the English catalog
* fix(terminal): a reveal after hidden output was dropped always restores it
A restore that failed while its pane was hidden zeroed recovery and wrote
its loss banner where nobody could see it, so the reveal skipped the
restore and the gap stayed silent and permanent. Stay armed instead and
spend one fresh snapshot on reveal. Main's drop latch now retires on the
renderer's paint ack rather than on unhide, so a hidden pane that retires
cannot spend the only marker on a handler that is already gone.
Fixes#15504
Co-authored-by: Jinwoo Hong <73622457+Jinwoo-H@users.noreply.github.com>
* fix(terminal): dedupe a disclosed hidden-output loss by drop episode
The unhide marker carries main's emit-time output sequence, which visible
output advances without any new drop, so a seq comparison re-bannered the
same gap. Main now tags gate markers with a drop episode, and a drop after
an unhide starts a new episode that the in-flight restore's ack cannot
retire.
Co-authored-by: Jinwoo Hong <73622457+Jinwoo-H@users.noreply.github.com>
* fix(terminal): a restore ack retires only the drop episode it covered
An older restore still in flight when a newer hide/drop/reveal cycle ran
could ack and erase the newer episode's drop memory. The renderer now
captures the latest reported episode before fetching and names it in the
ack; main retires the latch only on a matching episode.
Co-authored-by: Jinwoo Hong <73622457+Jinwoo-H@users.noreply.github.com>
* test(terminal): pin hidden-restore recovery against a drop-reporting host (#29253)
Co-authored-by: Jinwoo Hong <73622457+Jinwoo-H@users.noreply.github.com>
---------
Co-authored-by: Jinwoo Hong <73622457+Jinwoo-H@users.noreply.github.com>
* fix(ssh): never leave an SSH host unconnectable after a refused move to its managed server
* fix(ssh): keep unsaved drafts out of what Move anyway leaves behind
* test: run the stuck-host profile test in the Node runtime project
* fix(ssh): spend a "Move anyway" consent when the conversion throws
A throw left managedServerLeaveBehind set, so a later connect left saved
state behind without asking again. A resumed fence replays its journal,
never the export, so clearing is safe.
Co-Authored-By: Claude <noreply@anthropic.com>
* Revert "fix(ssh): spend a "Move anyway" consent when the conversion throws"
This reverts commit 4f63d777d9.
* docs(ssh): note why a Move anyway consent survives a conversion throw
* fix(ssh): Move anyway needs every terminal proven exited, and skips foreign tabs fully
- A bridged terminal proof (relay census live or unverifiable) now refuses a
confirmed "Move anyway" before any fence or journal, with a reason that says
to close the terminals; the host stays on its relay and connectable. The
offer itself is withheld while terminals aren't proven exited.
- A foreign tab left behind is also removed from the legacy tabsByWorktree
rows, its layout, active-tab pointer, relay session id, scrollback, and
pane-keyed resume records, so it can't reappear on the managed server.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(ssh): Move anyway drops left-behind files' tabs, and is offered only when it would work
- An editor tab whose host-bound file is left behind is removed with the
file, so the managed server never opens it as a blank pane.
- The offer is previewed against the leave-behind export first: when what
refuses is a desktop-only draft the export can't leave, no "Move anyway"
is shown and the save-or-discard reason stays.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(ssh): drop surface and closed-tab tombstones of foreign tabs Move anyway skips
* fix(ssh): match legacy pane keys when Move anyway drops a foreign tab's records
---------
Co-authored-by: m4air <m4air@Mac.localdomain>
Co-authored-by: Neil <neil@stably.ai>
Co-authored-by: Claude <noreply@anthropic.com>
* feat(worktree): show checkout progress while a worktree is created
A local create spends most of its time in the checkout `git worktree add`
performs, and the card only said "Creating worktree…" for that whole time.
Git already writes its "Updating files: N%" meter into our stderr pipe, so
the add call reads it passively (same command line and environment) and the
create card shows "Checking out files… N%" with a thin bar. All six create
labels are translated so a create never switches language mid-flight.
Rebuilt on main from the version stacked on the create-gating branch. On
main a create can also claim a prepared checkout; that path never runs
`git worktree add`, so it keeps today's "Creating worktree…" label with no
bar, and a missed claim falls back to the plain add, which shows the bar.
* test(worktree): pin that finalizing a prepared checkout reports no checkout progress
Claiming a prepared checkout never runs `git worktree add`, so the create
card keeps its label with no bar; this pins that at the finalize step,
including the retarget `reset --hard`.
* test(worktree): keep only the checkout-progress tests that pin a contract
Drops unit tests that mirror the implementation; the real-git flow, the parser
captures, the cross-version Git contract and the create card test remain.
* test(worktree): pin the checkout meter's path from git to the create card
* test(worktree): merge the narrow-terminal capture's pipe chunks
* Prepare native chat CLI copies from the host record
* Apply staged-file lint and formatting checks
* Cover pairing replacement before queued preparation dispatch
* Reuse CLI failure feedback and complete the pairing test response
* Isolate Codex materialization test user data
* Preserve native chat CLI copy completion and preparation ownership
* test: consolidate native conversation fork contracts
* fix(native-chat): refuse a Codex chat fork whose saved account home has no CODEX_HOME path
* feat(session-history): the scanner reports its progress to windows
The Session History scanner now counts each agent's history files and
databases while it lists them, and how many transcripts it has read.
Main keeps one progress entry per local scan, tags list results with the
scan that produced them, tells the scan's own no-progress deadline apart
from other faults, and pushes updates to every window. The preload API
exposes getScanProgress and onScanProgress. Nothing reads them yet.
* fix(session-history): drop the redundant scan key from list results and progress
The scan ID already names the scan a result or progress entry came from, so
the key copy on the list result (sent to paired clients too) and on each
progress entry was never needed. The key stays only where it is the lookup.
* fix: avoid blank shells beside new workspace chats
* Carry promised agent chat through worktree creation
* Use live agent launch inventory for new workspace surfaces
* Type live agent launch owner from runtime contract
* Assert one recovery shell after failed agent launch
* Keep worktree activation adapters out of creation flow
* Restore ordinary workspace setup handoff after launch correction
* Cover the phone's agent.launch workspace create in the first-surface flow test
* feat(wsl-mover): pause a moving project's terminals through Sleep and prove they exited (D1b-5d)
* fix(wsl-mover): gate again under the fence, swap journal phases atomically, keep sessions frozen through commit (D1b-5d)
* test(wsl-mover): the fence e2e walks the journal one phase at a time, as a move writes it (D1b-5d)
Remember an optional Jira project for each repository so its issue picker and Tasks list start with the intended project and site. Keep temporary project overrides separate from the saved default, and choose a mapped workspace destination while retaining the Jira account that supplied the issue. Older hosts that discard the optional saved field return an actionable message.
Keep account ownership separate from repository execution identity. An explicit matching repository owner disambiguates repeated IDs; automatic defaults require ID uniqueness, and overrides include execution-host identity. Preserve existing preselection behavior, saved/manual query classification and linked-view choices. Reuse existing persistence, identity and query builders; check the final UTF-8 query length for searches and project-filtered presets and show a recoverable error before dispatch.
Validation at reviewed 9b174ae4: 205 affected tests, node/web typechecks, seven changed-source quality checks, and 189 fresh rendered assertion invocations with 39 inspected screenshots and 1,413 stable source hashes pass. Independent source and artifact reviews found no blocker. The rendered evidence uses synthetic transports. Ubuntu 22.04 persistence CI stopped before tests because the native watcher crashed during the build; those inputs are unchanged from the preceding passing head. Full CI is not claimed green.
Load Jira attachment images separately from issue Markdown so descriptions and comments stay small while screenshots load lazily and open in the existing lightbox. Downloads begin when an image mounts, capture its owning host, require negotiated image support, and use bounded authenticated byte streams and caches. Older clients retain their inline-image handlers.
Keep legacy and cancellable downloads in separate in-flight domains so cancelling a new image reader cannot abort a legacy reader; completed bytes share the bounded cache. Preserve the landed assignee, board, transition/comment queue and request lifetimes. Move unchanged project metadata IPC handlers into their domain module and tighten two inherited focus-check ceilings without changing behavior.
Validation:410 tests/54 suites; all changed-source quality stages, lint, format, routing and contract checks; exact final node/web types; independent source review;48/48 fresh hidden Electron checks with226 stable source hashes and nine inspected screenshots. Synthetic transports exercise production clients and UI. Current CI static checks, mobile bundle, wire compatibility, Linux/Windows packaging and Windows serve pass; five test shards were pending at review.
Extends the existing attachment work credited to Frank (@lifefloating) and Brennan Benson in PR #8938; existing contribution credits and media remain in this PR.
Show Jira issues as a board alongside the existing list and add the current-sprint preset. Status moves use the existing owner-scoped mutation queue, collect required workflow fields/comments, retain rejected drafts, and reload the mounted comment timeline once after success. Source/session fences keep older detail failures from undoing newer board moves.
Preserve statuses plus sprint in query preview and Edit-as-JQL. Older hosts must advertise sprint support; unsupported peers fail clearly. Existing capability definitions and test fixtures are split without changing public exports, wire strings, or coverage to meet current lint limits.
Validation:351 tests/42 suites,all46 changed sources full-file lint/anti-slop and7quality stages,localization/RPC checks,and independent source review passed. Node types passed on runtime-identical99d and web types passed on final602d. Fresh hidden Electron54/54 checks passed on99d; final two test-only changes were hash and emitted-runtime audited. The inherited IPC301-line failure is byte-identical main and being corrected separately by the image PR.
Adapted Eugenio Valeiras (@EugeValeiras)'s board contribution from PR #13054.
Co-authored-by: Eugenio Valeiras <eugenio.valeiras@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Collect required Jira transition fields through the shared issue save queue, preserve entered fields after rejected saves, and refresh the comment timeline after successful transition comments. Keep captured-host and older-peer compatibility.
Co-authored-by: Wooseong Kim <innocarpe@gmail.com>
Combine remembered Jira status filters with presets and saved JQL on the connection host, protecting obsolete owner actions and keeping incompatible controls hidden in linked mode.
Co-authored-by: Brennan Benson <brennankbenson@gmail.com>
Save and apply named Jira queries on their connection host. Reject obsolete owner actions and exit linked mode only after a saved query is actually applied.
Co-authored-by: no-support <jwan0131@gmail.com>