Commit Graph
836 Commits
Author SHA1 Message Date
Kelvin Amoaba 5c4c1edc64 feat(terminal): tell users once that the diff panel in a Claude pane is Claude's own (#29818) 2026-10-18 01:49:10 +00:00
NeilandClaude 710120991f fix(ssh): relay-era pane reattach keeps output printed after its screen, and an exited or abandoned pane never comes back (#29910)
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>
2026-10-16 19:06:20 -07:00
Brennan Benson 64e544a442 feat(mobile): Resume in New CLI from a phone chat tab (#29783)
* 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
2026-10-16 18:10:24 -07:00
NeilandClaude f1677dbf4e fix(ssh): stop a relay-era shell when its bridged tab closes (#29620)
Co-authored-by: Claude <noreply@anthropic.com>
2026-10-16 04:08:56 -07:00
Neil 8eaaab88bd feat(wsl-mover): the ledgered desktop rewrite stage that finishes a committed move (D1b-7b) (#29597)
* 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)
2026-10-16 04:03:09 -07:00
Neil 7f8faae800 feat(space): open disk usage in a separate window (#29513)
* 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
2026-10-16 01:25:19 -07:00
Neil d9823843bc fix(ssh): hidden relay-era SSH panes stop sending every output chunk to the window (#29537)
* 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
2026-10-16 01:18:50 -07:00
Neil db22421ae7 fix(ssh): agents on a managed SSH host can drive the desktop Orca browser again, behind the per-host switch (#29384)
* 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
2026-10-15 23:40:57 -07:00
0fac887dbc feat(terminal): show a paired server's terminal in this desktop on terminal switch (#28023)
* 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>
2026-10-15 23:26:59 -07:00
Neil 904b6f2b32 fix(ssh): flag a macOS host whose managed server outlived its launching SSH session (#29371)
* 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
2026-10-15 23:14:45 -07:00
NeilandJinwoo Hong d9e4017470 fix(terminal): a reveal after hidden output was dropped always restores it (#29342)
* 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>
2026-10-15 23:13:38 -07:00
699709c0ba fix(ssh): never leave an SSH host unconnectable after a refused move (phone users, #29083, Move anyway) (#29220)
* 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>
2026-10-15 18:26:18 -07:00
Brennan Benson 131986aa16 feat(worktree): show checkout progress while a worktree is created (#24093)
* 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
2026-10-15 18:14:56 -07:00
Brennan Benson 1b1d09cfb5 Open a native chat copy in the CLI without waiting for History (#29126)
* 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
2026-10-15 14:59:26 -07:00
Brennan Benson 6fcd521165 Session History: the scanner reports its progress to windows (no visible change) (#28788)
* 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.
2026-10-15 13:35:47 -07:00
Neil a7edea45b1 Detect stalled terminal daemon I/O and offer restart review (#29087)
* Detect stalled terminal daemon I/O and offer restart review

* test: update terminal notice store mock for shared navigation
2026-10-15 02:36:46 -07:00
Brennan Benson 635d3853f1 fix(agent.launch): avoid blank shells when launching new workspaces (#28451)
* 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
2026-10-15 01:21:24 -07:00
Neil cc690cfe4b feat(jira): add bounded site-specific favourite filter catalogue (#29028) 2026-10-15 00:07:43 -07:00
Neil 8f83f6b076 feat(wsl-mover): pause a moving project's terminals through Sleep and prove they exited (D1b-5d) (#28820)
* 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)
2026-10-14 13:07:50 -07:00
Jinjing 8644ade7fc Remove Hermes automation support (#28519)
* Remove external cron history from the automation UI

* Simplify the automation editor

* Remove Hermes cron discovery and management
2026-10-14 11:36:43 -07:00
Neil ef3406788d feat(jira): remember repository project defaults (#28622)
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.
2026-10-14 06:20:47 -07:00
Neil a0c46b405f feat(jira): load attachment images outside issue Markdown (#28631)
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.
2026-10-14 05:33:30 -07:00
624c4148cc View Jira work on a board and filter the current sprint (#28633)
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>
2026-10-14 05:10:29 -07:00
NeilandWooseong Kim eb1ee8f908 fix(jira): collect required fields before changing status (#28588)
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>
2026-10-14 03:57:00 -07:00
NeilandBrennan Benson 702777e2fe Filter Jira preset views by multiple workflow statuses (#28623)
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>
2026-10-14 03:47:08 -07:00
Neilandno-support d7315af456 Save and reuse named Jira JQL filters on the execution host (#28600)
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>
2026-10-14 03:37:52 -07:00
NeilandKazuki Hanai 51173e45d4 feat(jira): generate issue titles on the connection host (#28625)
Generate Jira issue titles through the selected execution host, protecting cancelled requests and treating Jira content as quoted prompt data.

Co-authored-by: Kazuki Hanai <kazukihanai@sakana.ai>
2026-10-14 03:16:50 -07:00
Neil 9c0420fa9a fix(browser): agent clicks keep the user's keyboard focus (#28454)
Any press on a <webview> guest moves Chromium focus into it, so an agent click
took the user's terminal focus (#20595). Main now asks the hosting pane to guard
focus for each agent press (Orca's click/mouse down and the CDP proxy) and the
pane puts it back inside the webview's focus event, and again when the press
ends. Offscreen pages keep their native focus.
2026-10-14 02:40:07 -07:00
NeilandNikita Maketryuk b428def8f1 feat(cli): open files at a requested line and column (#28240)
Accept positive safe-integer line and column flags through the existing file-open path. Reject invalid or incomplete positions before changing the editor. Bind deferred reveals to the requested workspace and tab, including floating panels, and clamp oversized positions without moving another editor or stealing late focus.

Co-authored-by: Nikita Maketryuk <n.maketryuk@lince.studio>
2026-10-14 00:36:57 -07:00
Brennan Benson a0de43aa1f AI-button and OpenCode-model tabs start their agent in their own terminal (#28210)
* fix(agent-launch): an AI button plans its agent inside its own pane's spawn, with the host's planner and no launch record

The window queues the launch on the tab; the pane's reserved spawn plans it with the planner every
host launch uses and starts it at the pane's size, with its launch token. Every way the spawn ends
finishes the launch, and the window pastes the prompt only once this pane says the agent started.
An ordinary spawn reaches its provider without waiting on a plan. An OpenCode tab with a model
keeps main's host hop untouched (TEMPORARY, until a local producer of model options exists).

* fix(agent-launch): an AI button's prompt waits for its pane to start before the spawn budget runs, so a pane that settles slowly still gets its prompt; stale comments

* test(agent-launch): the late pane-start test's store double is typed without an assertion

* test(agent-launch): the late pane-start test pins the prompt written once and Enter once, and nothing when the pane didn't start

* fix(agent-launch): an AI button's prompt goes only to the PTY its pane started, never a split's shell that bound first; the pane's start answer names that PTY

* fix(agent-launch): an AI button's launch watches each split's readiness as it binds, so the launch pane's own ready output is seen even when a split bound first and it drained at once

* fix(agent-launch): an AI button's pane plans with the host's current planner and runs its runtime fact's shell, so its command is quoted for the shell it is typed into, as a host launch is; the tab's inferred shell is not sent as a request
2026-10-13 22:40:59 -07:00
Jinwoo HongandNeil 29aa2a4f0f Refresh Source Control only for the worktree whose git index changed (#26946)
* Refresh Source Control only for the worktree whose git index changed

Index and HEAD-log writes under <common>/worktrees/<entry>/ now carry the
checkout they belong to (resolved through the head-identity memo), and the
renderer skips the status nudge when that checkout is a known sibling of the
active one. Unattributable changes (shared config, bound upstream ref,
overflow, SSH watches, unknown admin dirs) still refresh as before.

* Pass the stable worktree-path getter as an effect dependency instead of a render-time ref

* Fan out sibling head moves to every checkout's Source Control

A commit to a shared branch (e.g. main) from a sibling worktree can move the
active checkout's local upstream, whose ref leaf is not polled. Its logs/HEAD
append now stays unattributed so ahead/behind refreshes immediately; index-only
churn keeps its per-worktree attribution.

---------

Co-authored-by: Neil <neil@stably.ai>
2026-10-14 04:40:22 +00:00
Neil 03eee256f1 fix(wsl-host): let an app update reach a WSL host orcad (E1) (#28458)
* fix(wsl-host): read the running orcad's census before an app update replaces it

* fix(wsl-host): keep Force, the incumbent watch and its freshness through an update

* fix(orcad): recount terminals just before an update stops the incumbent

* refactor(wsl-host): move watch arming and pairing identity out of oversized modules
2026-10-13 20:38:33 -07:00
Neil 3f81541188 Support restarting Windows Orca as administrator (#28073)
* feat(windows): add administrator PowerShell terminal setting

* fix(settings): localize administrator terminal search keywords

* fix(windows): restrict elevated terminal host to Orca's runtime and requesting account

* fix(windows): harden administrator terminal routing, inventory and restore

* fix(build): build the Windows agent hook when electron-builder packages Windows

* fix(build): build the packaged Windows agent hook for the target architecture

* fix(windows): accept the development terminal runtime and test real elevation in CI

* feat(windows): show administrator status and restart instructions

* fix(settings): localize administrator mode search keywords

* test(settings): isolate the desktop permission section in shell picker tests

* Add Windows administrator restart and simplify Terminal settings

* Keep administrator restart silent and settings actions within narrow panes

* Keep Windows shell choices and permission guides usable at minimum width

* Prevent later restarts from replaying administrator terminal reset

* Fix CI route resolution and transient Windows fixture cleanup
2026-10-13 18:06:05 -07:00
ed18c2c580 fix(ssh): Cancel on an SSH password prompt no longer brings it straight back (#28402)
* fix(ssh): a cancelled sign-in stays cancelled until the user reconnects

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

* test(ssh): drive the credential cancel through the IPC harness

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

* fix(ssh): an explicit server connect lifts the cancelled sign-in of its SSH host

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

* fix(ssh): the cancelled attempt may still offer a password; a successful sign-in lifts the decline

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

* test(ssh): build the managed-server link without the newer kind field

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

---------

Co-authored-by: m4air <m4air@Mac.localdomain>
Co-authored-by: Claude <noreply@anthropic.com>
2026-10-13 16:52:37 -07:00
Neil a65fdc887f feat(wsl): WSL distro hosts, main-process wiring and persistence (WSL-via-orcad C7a, experimental) (#27933)
* feat(wsl): persist WSL host deployments in their own store and type orcad deployment links by kind

* chore(wsl): drop an unused link type export

* feat(wsl): add, persist and connect WSL distro hosts behind an experimental flag (WSL-via-orcad C7a)

Main-process wiring for WSL hosts: list registered WSL 2 distros, add one (C4 deploy and
launch through C6 liveness, then persist the paired environment and its C2 record), keep a
relaunch's new device token, connect a host only for the user or an open project or terminal,
adopt holders at startup, and remove a host (release its holder, drop both records). Refuses a
distro whose orcad this desktop already reaches as another server (R25).

* fix(wsl): read distro registrations through one reader that returns the canonical unbraced GUID

C4's registry reader returned the braced GUID while the C2 record only accepts it unbraced, and
C7 parsed the same output a second time. The one reader now yields lowercase unbraced ids and
each registration's WSL version; the host distro list uses it.

* fix(wsl): refuse to connect a WSL host while the experiment is off

Adding, connecting and background connects all stop while Experimental → WSL distros as hosts
is off; removing still works. The pre-deploy refusals move to their own module.

* fix(wsl): stop a connected WSL host's server when the host is removed

Remove ended the bridge and released the distro's holder but left orcad to exit with the
distro. A connected server is now ended right away, only in a distro already running.

* fix(wsl): import the orcad host errors from their new orcad/ home

* fix(wsl): refuse WSL hosts in a build without the orcad template, and say so up front

A build shipped without the orcad deployment template let the add dialog run and fail after
seconds with a raw internal error. Main now checks the template as SSH hosts do, refuses add
and connect (and skips lazy connects and holder adoption) before any deploy work, and exposes
the capability so the UI can show it.

* test(wsl): let the e2e WSL fake re-read its distro list, to register one mid-run

* fix(wsl): end a removed host's terminals and daemon, and stop WSL hosts when the experiment is off

Remove now ends the host's terminals through orcad's terminal RPC, waits for the terminal census
to prove none remain, closes the bridge, and runs the managed decommission SSH hosts use, which
stops orcad and retires its idle terminal daemon. What it could not end is reported back.

Turning Experimental → WSL distros as hosts off closes every connection and liveness watch,
releases the holders so distros can idle-stop, and keeps liveness from relaunching for visible
work; turning it on resumes only the hosts open work needs.

* fix(wsl): keep WSL hosts out of the servers a phone reaches through this desktop

They are this desktop's own distros, connected only for work open here; a phone listing or
routing to one would wake it behind the user's back.

* fix(wsl): end a removed host's terminals through its own route, and never respawn it

Removal reached orcad with the bridge's raw pairing offer rather than the routed environment and
saved pairing the app already uses for the host; on a real orcad ending terminals failed fast and
left them, the daemon and orcad running. It now calls by environment id through the same
transport as every other call to the host, and the e2e fake runs these real steps against a
headless server.

Removal also stops tracking the host first (open work, connects, runtime calls), so nothing
reconnects it while it stops, and a spawn guard in the connector refuses any holder, deploy or
bridge for a distro whose host is gone (the respawn loop after removal). A WSL host that isn't
connected answers runtime calls with why, instead of failing as an unreachable network server.

* fix(wsl): suggest reconnecting a WSL host, not Tailscale, when it can't be reached

Unreachable-server hints are chosen by the owner host's kind: a WSL host is a distro on this
computer, so it points at its reconnect instead of the Tailscale advice for network servers.

* test(wsl): parse the terminal list removal reads, and log the e2e fake's connects to a file

* fix(wsl): close a removed host's terminals by handle and report stopped only on proof

Removal stopped terminals per workspace, so one orphan whose project was gone threw
selector_not_found and aborted before orcad was stopped. It now closes each terminal by handle,
keeps going past failures and names them, and still asks orcad to stop.

It reports stopped only when the census shows no session left, the daemon retired, and the
orcad the distro records as active is the one this computer was connected to; a re-add that
adopted an orcad the record does not name is reported instead of claimed stopped.

* feat(wsl): close WSL hosts behind an internal-only gate in every build

WSL distros as hosts must not be reachable by users until moving a project
between hosts is lossless and the feature matches today's WSL support.
ORCA_INTERNAL_WSL_HOSTS=1 opens the gate; otherwise main creates no WSL host
service (no holder adoption, wsl.exe spawn or reconnect), refuses add,
connect, remove and every runtime call to a WSL host, keeps persisted WSL
hosts out of the server list, startup status probes and AI vault scans while
leaving them on disk, and the preload exposes no WSL host API.

* fix(wsl): keep SSH and paired servers listed whatever the WSL deployment file holds

An unreadable WSL record no longer hides an SSH-linked server, and with the internal gate closed a corrupt WSL file is warned about instead of failing the whole registry.

* fix(wsl): let an unreadable WSL record hide only a loopback pairing

A WSL host always pairs over a loopback endpoint, so a stale record can no longer remove a remote paired server from the list.

* fix(wsl): ignore unreadable WSL deployment state while the internal gate is closed

A newer or stale record could otherwise hide a re-paired loopback server for users who never open the gate.
2026-10-13 08:18:08 -07:00
Neil 5e9489c0c0 feat(wsl): account env for agent tabs on a WSL orcad host (WSL-via-orcad C9b) (#27940)
* feat(wsl): account env for agent tabs on a WSL orcad host (WSL-via-orcad C9b)

terminal.createAgentSession and an explicit terminal.ensureAgentSession (new
agent tabs, restored and sleeping agents) accept an optional accountEnv, gated
by the new agent-session.account-env.v1 capability because both schemas are
strict. The desktop shim attaches it only for an advertising WSL orcad, reading
its capabilities from status.get; an older host keeps its default account and
the desktop shows a one-time notice per distro. accountEnv stays outside the
create replay fingerprint.

* test(wsl): name the C9b runtime store stub's cast

* test(wsl): expect the WSL account notice listener in the app-lifetime IPC lifecycle
2026-10-13 07:42:38 -07:00
Jinwoo Hong d31f37c2dd chore(session): delete three session paths nothing calls any more (#28152)
* chore(session): delete three session paths nothing calls any more

- session:set-sync IPC (bridge, type member, web adapter stub); quit uses
  app:stage-before-unload-sync
- the session-write persist gate (shouldSchedulePersist /
  subscribeToPersistGateOpen); the one production subscriber passes neither
- reconcileDeadSessions and the binding's reconcileIfSessionDead; the live
  reconcileIfSessionMissing path stays

Tests that drove these are retargeted to the live paths or removed.

* chore(session): name shouldPersistWorkspaceSession in the deferral comment
2026-10-13 02:52:09 -04:00
25f87544d0 feat(terminal): persistent banner to restart a terminal daemon whose files are gone (#28021)
Co-authored-by: m4air <m4air@Mac.localdomain>
Co-authored-by: Claude <noreply@anthropic.com>
2026-10-12 09:09:24 -07:00
OrcaWinandm4air 822bd9f972 fix(ssh): keep 1.4.224's running terminals working after the upgrade (legacy relay bridge) (#27787)
* wip: legacy relay bridge client

* Revert "fix(ssh): end an older Orca's relay terminals from the connect refusal (STA-10166) (#27748)"

This reverts commit d4a55f583e.

* feat(ssh): bridge terminals an older relay still runs into their old tabs

* test(ssh): cover the legacy relay bridge client, target bridge, transport and gate

* test(ssh): type the bridge test fakes without assertions

* test(e2e): a 1.4.224 relay's live terminal survives the upgrade on a Docker SSH host

* ci(e2e): route legacy relay bridge changes to its Docker spec

* fix(ssh): bridge delta moves too, and never read a non-exhaustive relay listing as exit

* fix(ssh): keep retrying a bridge for panes that mount before connect; end panes proven gone

* fix(ssh): bridge 1.4.224's detached leases through export preflight; publish every leased pane before the first look

* test(e2e): build the released relay out of process so Playwright never loads ESM build tooling

* fix(renderer): drop the recovery hook main removed from PtyTransport

* test(e2e): show the converted worktree before reading the bridged tab

* test(e2e): reconnect and wait for the converted host before opening the bridged tab

* fix(renderer): match bridged leases to tabs mirrored from the managed server

* test(e2e): find the mirrored relay-era tab under any worktree key

* test(e2e): open the converted worktree before looking for its mirrored tab

* test(e2e): a retired lease is terminated or pruned

* fix(ssh): back off attaches a listed terminal refuses; one pane per bridged shell

---------

Co-authored-by: m4air <m4air@Mac.localdomain>
2026-10-12 02:24:12 -07:00
Brennan Benson e3ef95dc04 feat(cli): launch agents in existing workspaces (#27794) 2026-10-12 00:38:00 -07:00
OrcaWinandm4air d4a55f583e fix(ssh): end an older Orca's relay terminals from the connect refusal (STA-10166) (#27748)
* fix(ssh): end an older Orca's relay terminals from the refusal, and name the command

* test: type confirmation mocks

* fix(ssh): match old relay daemons by argv position; owner-check the Windows command

* fix(ssh): select Windows relay daemons by argv position in the manual command

* fix(ssh): keep the relay daemon marker local so census mocks stay intact

---------

Co-authored-by: m4air <m4air@Mac.localdomain>
2026-10-11 21:53:57 -07:00
Jinwoo Hong d9a3c84b00 fix(terminal): don't duplicate terminals a newer Orca still runs after a downgrade (#27577)
* refactor(daemon): share the cold-restore payload builder

Lets a reader outside the adapter build the same saved-screen payload from
history without spawning.

* feat(daemon): hold restored terminals a newer build's daemon may still run (STA-9257)

After a downgrade, an older build cannot adopt a newer-protocol daemon, so it
cold-restored that daemon's terminals under the same ids and resumed their
agents beside the originals. Startup now classifies newer-protocol pid records
in this profile's runtime dir as live, unverifiable or exited, and pty:spawn
refuses a restore no adopted daemon owns while one is live or unverifiable.
Reopen stops the newer daemon by verified signal only and reports success only
once its pid is confirmed gone.

* feat(terminal): show held restored panes read-only with a Reopen banner (STA-9257)

A pane refused for a newer daemon shows its saved screen read-only under a
pane-top warning. Reopen terminal stops the newer daemon and releases every held
pane through the existing reattach path, then toasts what actually reopened and
resumed; an unconfirmed stop reopens nothing and offers Try again.

* refactor(daemon): hold newer-daemon restores at the router, one classifier (STA-9257)

The hold moves from the pty:spawn IPC handler into DaemonPtyRouter.spawn so
every spawn source (IPC, runtime controller, mobile refresh, CLI) refuses a
cold restore of an id no adopted daemon owns while a newer daemon is held.
Discovery and stop share one classifier; an unidentifiable owner settles by
whether its endpoint accepts a connection, so a dead leftover never holds a tab
forever. Main announces a confirmed stop to the renderer.

* fix(terminal): per-pane failed state, one release path, no repeated screen (STA-9257)

A pane held after a failed Reopen shows the held state. Held panes also release
on main's stop announcement, with one toast only from the clicking window. A
pane whose saved buffer the layout restore already painted skips the history
screen and is not cleared on release.

* refactor(terminal): drop the release event, await the router hold, one phase per pane (STA-9257)

The release announcement only reached the window that clicked, which already
releases on its reply, so it is removed. The router hold check is always
awaited; three router tests released the adapter spawn before the router had
reached it and now wait for it instead. Each held pane carries its own phase.

* refactor(terminal): trim the newer-daemon hold, keep paired panes out of it (STA-9257)

A paired host's refusal is now an ordinary error: holding it locally looped,
since this window cannot stop that host's daemon. The history-screen read is
gone (the layout restore already paints saved output), discovery folds into the
hold module, the hold store uses zustand, the reattach returns its outcome so a
fresh-session fallback counts as reopened, and the toast uses plural keys.

* feat(terminal): show a held local pane's saved screen read-only (STA-9257)

Local daemon-backed panes keep no layout buffer, so a held pane was blank. Main
reads the session's saved screen from history without spawning and the pane
paints it read-only, unless the layout restore already painted the pane; it is
cleared before the normal reconnect.

* fix(daemon): re-probe a timed-out newer-daemon endpoint before holding (STA-9257)

* feat(terminal): count held panes in the reopen label, add Dismiss (STA-9257)
2026-10-11 22:21:20 -04:00
OrcaWinandm4air cf58a13202 Revert "feat(mobile): verified https pairing links; custom scheme carries no secret (#27475)" (#27693)
This reverts commit 20f8cb02c1.

Co-authored-by: m4air <m4air@Mac.localdomain>
2026-10-11 17:45:02 -07:00
OrcaWinandm4air df2f2a72de refactor(ipc): filesystem and repo handlers are local or runtime only (#27609)
* refactor(ipc): filesystem and repo handlers are local or runtime only

* test(ipc): drop two casts in the rewritten listing and import tests

* fix(providers): keep the default error name so IPC callers see the old text

---------

Co-authored-by: m4air <m4air@Mac.localdomain>
2026-10-11 15:41:56 -07:00
OrcaWinandm4air bd54fc7fa3 refactor(renderer): delete the direct-SSH authority and reconnect coordinator (#27587)
No ssh: git provider is registered in production, so the reconnect
coordinator's catalog and lineage reads always came back non-authoritative
and its worktree refresh never merged. Its only live effect was clearing an
unconverted target's stale PTY bindings on each connected push and on wake;
the state bridge now does that directly.

Deletes the coordinator, preparation, worktree refresh scheduler, host
hydration, rollout flag and their product telemetry event, the scoped
direct-SSH reconnect/hydration options, and the now caller-less
repos:listForExecutionHost and worktrees:listLineageForHost IPC handlers.

Co-authored-by: m4air <m4air@Mac.localdomain>
2026-10-11 14:41:17 -07:00
20f8cb02c1 feat(mobile): verified https pairing links; custom scheme carries no secret (#27475)
* feat(mobile): verified https pairing links; custom scheme carries no secret

The desktop's copy-link now emits https://www.onorca.dev/pair#<code>, with the
code only in the fragment. Orca Mobile claims that link as a verified app link
and refuses any pairing code delivered through orca://, which any app can claim;
orca://pair alone opens the scanner with an explanation. In-app QR scan and
paste still accept the custom scheme, so older desktops and phones keep pairing.

The confirm screen shows the desktop address and host key fingerprint before
pairing. config/verified-links/ holds the apple-app-site-association,
assetlinks.json and fallback page to deploy on www.onorca.dev, with a test that
keeps them, mobile/app.config.js and the link builder in agreement.

Part of #17397.

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

* fix(mobile): refuse pairing codes nested in or normalized out of unverified links

The router unwraps expo-development-client ?url= links and normalizes dot
segments after +native-intent runs, so an orca:// wrapper could still deliver a
code to the confirm screen. Refuse any unverified link carrying code= at any
encoding depth, and match pairing routes on decoded, dot-free segments.

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

* fix(mobile): escape the apostrophe in the refused-link notice

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

* fix(mobile): decode escapes byte by byte before refusing nested pairing codes

An invalid UTF-8 escape made the whole escape run fail to decode, hiding an
adjacent encoded &code= that the router still decodes leniently.

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

* fix(mobile): decode nested pairing links to any depth before refusing codes

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

* fix(mobile): ignore URL-stripped whitespace when refusing nested pairing codes

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

* refactor(mobile): decide pairing-link refusal on the router's own parse

Stop re-implementing URL decoding. The guard now runs expo-router's own
extractExpoPathFromURL and parseUrlUsingCustomBase, the functions its linking
applies after +native-intent, and refuses any link from which the router would
read a `code` param or reach a pairing route with a payload. A link the router
cannot parse is refused. Release builds refuse expo-development-client
wrappers outright. A property-style test walks wrapping depths, encoding
layers, stripped control characters, case and unicode, and asserts that the
guard never passes on a link from which the router would read a code.

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

* fix(mobile): match pairing routes on the top-level segment only

A host link with a segment named pair was sent to the scanner.

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

* test(mobile): pin line-terminator fragment links as refused

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

---------

Co-authored-by: m4air <m4air@Mac.localdomain>
Co-authored-by: Claude <noreply@anthropic.com>
2026-10-11 10:06:05 -07:00
BingZandm4air b4aec8a896 fix(sidebar): stop a removed folder workspace from leaving a blank row (#26376)
* fix(sidebar): stop a removed folder workspace from leaving a blank row

A folder-project workspace exists only as its worktreeMeta row. When it is
deleted while a terminal is still bound, the renderer keeps the row until its
teardown runs, and a PTY reattach in that window stamps lastActivityAt through
worktrees:updateMeta. setWorktreeMeta then minted a default row with an empty
name, which the sidebar listed under the project name and which came back after
every delete. The metadata IPC now refuses to create a row for a folder
workspace instance that no longer exists.

Fixes #22712

Claude-Session: https://claude.ai/code/session_01XZXEdbC4RKp288k2gBzLUW

* fix(sidebar): also refuse runtime writes to a removed folder workspace

Move the existence check into a shared predicate that matches any same-id
folder owner, so an owner at a different path on another host cannot bypass
it, and apply it to the runtime's worktree.set path, where a resolution
snapshot taken before the removal still returns the workspace.

Claude-Session: https://claude.ai/code/session_01XZXEdbC4RKp288k2gBzLUW

* test: type the removed-folder-workspace fixture meta for the anti-slop lint

---------

Co-authored-by: m4air <m4air@Mac.localdomain>
2026-10-11 05:39:31 -07:00
OrcaWinandm4air 10d5108671 feat(runtime): accept a declared spawn intent from paired clients (#27452)
* feat(runtime): accept a declared spawn intent from paired clients

Paired clients can now say whether a terminal restore is the pane the
user is looking at (user-resume) or a hidden one (automatic-restore).
The host forwards the intent to its fork gate, advertises
terminal.spawn-intent.v1 so clients only send it to hosts that accept
it (terminal.ensureAgentSession params are strict), and reports a
deferred fork as terminal_spawn_deferred with its reason.

No client sends the field yet; that lands next with the sleeping-pane
handling.

* fix(daemon): a claimed agent resume cannot declare itself interactive

Interactive forks skip the gate, and paired clients can now declare
intent, so the host keeps inferring from the claim when a request
says interactive.

* fix(runtime): clients may only declare restore intents

A daemon kept running across an update still lets a declared
interactive request skip its gate, even for a claimed resume. The RPC
and local IPC now accept only user-resume and automatic-restore, and
the daemon request boundary turns a claimed interactive into an
automatic restore.

---------

Co-authored-by: m4air <m4air@Mac.localdomain>
2026-10-11 05:34:23 -07:00
OrcaWinandm4air 0024735e82 fix(ssh): remove the relay-only remote workspace sync so reconnect never sticks on pulling (#27409)
* fix(ssh): remove the relay-only remote workspace sync so reconnect never sticks on pulling

* refactor(ssh): drop snapshot apply tokens and terminal finalization only the sync used

* test: drop leftover sync-status mock

---------

Co-authored-by: m4air <m4air@Mac.localdomain>
2026-10-11 00:56:48 -07:00
ff27338299 refactor(ssh): connect fails for hosts orcad can't serve; remove the relay client path (#27214)
* wip: connect fails for hosts the managed server can't serve; remove relay session, deploy and relay UI

* wip: delete relay-only client modules left unreachable, prune their tests and gates

* wip: format

* wip: exhaustive unserved message

* wip: drop relay attestation test for the deleted ownership proof

* fix: prune stale baselines; type the test transport stub

* fix(ssh): census proves leftover relay leases; name the remedy for older terminals; translate every connect-failure surface

* test(ssh): leftover leases defer to the host census

* test(e2e): remove relay-path SSH e2e specs and their CI lanes

* test(e2e): drop the auto-convert case that built its relay era through a live relay

* test(e2e): mark relay-era browser and catalog journeys fixme until they seed a profile

* test(e2e): seed relay-era browser and catalog journeys from a saved profile

The three journeys built their old-version state through a connect without an orcad template, which now refuses the host. They now start from a seeded relay-era profile, so the first connect converts the host as an upgrading user's would.

* chore(ci): drop shard timings for the deleted relay tests

* test(e2e): carry a relay-era login cookie across the browser conversion; own every launch

* test(ci): drop the deleted SSH layout oracle from its route

* test(e2e): open the retained browser after conversion, retrying a pre-connect card

* test(e2e): bind the relay-era browser partition before seeding its cookie

A relay-era build always recorded the partition binding before a page stored
data in it. Seeding the cookie into an unbound partition made the binding store
refuse it (browser_route_partition_binding_store_invalid), so the retained tab
showed the generic routing card and Retry could never clear it.

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

---------

Co-authored-by: m4air <m4air@Mac.localdomain>
Co-authored-by: Claude <noreply@anthropic.com>
2026-10-10 22:10:33 -07:00