Commit Graph
11639 Commits
Author SHA1 Message Date
Brennan Benson 6b9151c9d7 Exercise Pi exit retention with real journal write pressure 2026-10-17 01:57:50 -07:00
Brennan Benson a8b9136ae0 Pin complete saved output and fitted client publication 2026-10-17 01:52:56 -07:00
Brennan Benson 36594557b5 Close test journal databases before removing temporary roots 2026-10-17 01:27:17 -07:00
Brennan Benson b25850606c Verify received chat writes wait for journal persistence 2026-10-17 01:20:26 -07:00
Brennan Benson 130ff376d2 Preserve large received chat content while pacing input 2026-10-17 01:01:34 -07:00
Brennan Benson 779d3f5233 Revert "Prototype source-batch journal ownership for Claude"
This reverts commit e5ed387f0c.
2026-10-17 00:55:26 -07:00
Brennan Benson c796b9acfa Revert "Pin the resolved iterator promise in ownership experiment"
This reverts commit bdb49ab738.
2026-10-17 00:55:26 -07:00
Brennan Benson bdb49ab738 Pin the resolved iterator promise in ownership experiment 2026-10-17 00:31:31 -07:00
Brennan Benson e5ed387f0c Prototype source-batch journal ownership for Claude 2026-10-17 00:30:55 -07:00
Brennan Benson 95337704d3 test(chat): align final tail reservation with captured content 2026-10-16 23:34:43 -07:00
Brennan Benson f89b4ea989 fix(chat): preserve complete writes and keep detail current 2026-10-16 23:31:29 -07:00
Brennan Benson 7c5006c083 fix(native-chat): format, keep the sink and projection under the line limit, update method count and tests 2026-10-16 22:47:28 -07:00
Brennan Benson f76af6b054 test(native-chat): an over-budget history item is shortened and marked, not replaced 2026-10-16 22:32:08 -07:00
Brennan Benson 65fb0709ed fix(native-chat): save every chat reply and tool output whole; bound only the copy sent to clients
Orca cut Codex replies, every agent's reasoning, plans, prompts and all tool
output to a 16 KB head when it saved them, and threw the rest away. The
limit existed so a reconnecting client is not replayed a huge row, but it
was applied at save time, so every transport limit became permanent data
loss.

The journal now keeps what the agent said. One outbound form, applied to
every history page, live batch and catch-up, sends a client the same 16 KB
head of each tool output and diff it always got, and marks the item
`shortened`. A new capability-gated `agentSession.item` RPC returns the
whole item, and the desktop and phone show "Show full output" / "Show full
message" when the copy was cut. Items past half a page are shortened and
marked instead of replaced with "too large to show".

Two bounds stay, both named: a 4 MiB ceiling per text (head and tail),
applied where every provider's rows enter the write queue so one write can
never stall a chat, and a preview on Grok/OpenCode/OMP/Pi calls while they
run, whose closing row is built from the whole value the translator held.
2026-10-16 22:27:17 -07:00
Brennan Benson 1ed735b5e6 feat(acp): one install row shows and drives a managed runtime install, for Settings and chat (#29049)
* feat(acp): one install row shows and drives a managed runtime install, for Settings and chat

The row reads the shared host state: Install, byte progress with Cancel, extract and verify lines,
Retry after a failure or cancel, and in Settings Reinstall or Update once installed. A failed status
read says it could not check, never that nothing is installed. Strings in all six locales.

* fix(settings): the install row says when a copy is damaged, when a PATH copy is used, and when the host needs unzip

- A damaged copy reads "This copy of {agent} is damaged." with Install, which repairs it.
- A copy found on this host outside Orca's reads "Uses the copy of {agent} found on this host."
  with no Install, since a launch would never run the download.
- A host without the program to unpack the archive says so, with no Install.

* fix(settings): say why a host running its own copy is offered no download

* fix(settings): the install row says Cancelling only where Cancel was clicked, offers Orca's copy beside one found on the host, and keeps its status line after a failed install

The host no longer publishes a cancelling phase or a missing unzip, so the row drops both: the
row whose Cancel is still out says Cancelling and holds its button until the host's cancel
settles. A copy found on PATH or in an install folder still gets Install of Orca's copy in
Settings (the chat keeps its composer). An older installed release reads Installed. with Update.
A failed first install keeps Not installed. beside the failure and Retry. The status line is a
polite live region.

* fix(settings): the install row says it is retrying a failed status check, and names the step after the download

* fix(settings): a Cancel that fails for no stated reason says the cancel failed, not the install
2026-10-16 21:32:46 -07:00
Brennan Benson 7b6f02fdbf fix(native-chat): idle cleanup and commands read the helpers the agent reported, not the capped status store (#29905)
* fix(native-chat): decide owed helper work from what the provider reported, not the capped store

Idle cleanup, replacing an outgrown agent, /clear, /compact, rewind,
command handover and the teardown resume marker decided whether a chat
still had helpers running by reading the agent-status store. That store
is a bounded display projection: it refuses records when it is full or
for per-record reasons, and a refused helper is still running.

The host now keeps an uncapped per-session set of the child work its
provider reported running and has not reported over, fed from every
child-work edge before the store sees it. Every owed-work decision reads
that set. An entry leaves on the child's own end (a late end of an
earlier run leaves the current run open, as the store's run verdict
does), on a Stop the store settled, or on the session's end.

The strip's "incomplete" note and Stop all's unlisted route are now
derived: open work that no live current-run record answers to. This
replaces the refused-for-room side list, so every refusal reason is
covered and nothing needs separate bookkeeping.

* test(native-chat): the real-store test sink reads recorded helper handles, as production's does

* fix(native-chat): keep helper run identity through delayed events and Stop

* fix(native-chat): name the host helper lifetime token

* test(native-chat): explain the Stop adapter fixture boundary

* fix(native-chat): derive helper coverage from running store records

* docs(native-chat): clarify running helper coverage

* test(native-chat): use an explicit helper end event

* Scope background Stop to captured child work

* Exercise refused helper Stop race through status store
2026-10-16 21:26:34 -07:00
Jinwoo Hong 442491be11 test(sidebar): golden for accented, mixed-case and numbered sidebar names (#29966)
Records the post-#29946 text order for workspace rows (Name, Recent and
project-name sort), project group names and folder workspace names, ungrouped
and grouped by project, so a future ordering change shows up as a golden diff.
2026-10-16 21:25:08 -07:00
Brennan Benson a4532faa30 fix(workspace-cleanup): delete the active or a running-agent workspace like a sidebar delete (#23593)
* fix(workspace-cleanup): let select-all include the workspace you are in

Deleting the current workspace is allowed like any other, so select-all now
includes it; a live agent stays excluded. Cleanup removal now hands focus to a
surviving sibling after the batch, as the sidebar's batch delete does.

Linear: STA-4686

* test(workspace-cleanup): cover focus handoff after the dialog closes and after Delete anyway

* fix(workspace-cleanup): move focus off a deleted active workspace as soon as its row is gone

* fix(workspace-cleanup): don't reopen a workspace the user left while a cleanup batch runs

The per-row focus handoff fired whenever no workspace was selected, so
closing the successor's last tab mid-batch re-activated it (and spawned a
new terminal) on the next row. Move focus only when the workspace the
batch left the user on is actually gone and nothing else took focus.

* refactor(workspace-cleanup): move focus off the active workspace when a cleanup delete starts

Switch to the successor once, right after the batch rows are marked deleting, instead of
repairing an empty selection after each row settles. The active row is no longer active when
it is removed, so the store never clears the selection and no later handoff is needed.

* fix(workspace-cleanup): never hand focus to a workspace in the same cleanup batch

The scan qualifies local rows as host `local`, so their delete state lives
under a host-qualified key, while a sidebar row with no stored host only
matches an unqualified key. The successor picker therefore saw batch rows as
not deleting and could focus one about to be removed, leaving an empty screen
once it went. Exclude the batch's own ids directly.

* test(workspace-cleanup): use scan-shaped hosts in the active-workspace focus tests

Candidates now carry the `local` host the scan publishes, which is what
exposed the picker gap. The other-host case also stamps the viewed row with
its host so it resolves; before, the lookup missed and the test passed with
the host check removed.

* feat(workspace-cleanup): confirm before a cleanup delete stops a running agent

A cleanup delete (bulk, single row, or "Delete anyway") that includes a
workspace with a running agent now shows a step naming those workspaces
before anything is queued. The check reads live agent state at click time,
attributed like the sidebar so structured chats count too, and re-asks if
another agent starts while the step is open.

The approved candidate records which agents the user agreed to stop; the
per-row preflight that runs right before each delete refuses a row whose
agent started after confirmation. With the step in place, live-agent rows
join select-all like the active workspace.

* refactor(workspace-cleanup): name the agent-stop same-action check

* fix(workspace-cleanup): drop an open agent-stop step when the dialog reopens mid-batch

* style(workspace-cleanup): match the agent-stop step's heading to the delete step

* test(workspace-cleanup): type the cache-hydration mocks in the reopen test

* style(workspace-cleanup): let the agent-stop step's heading use the dialog's own typography

The changed-lines design-system gate rejects restyling DialogTitle and
DialogDescription; the delete step's overrides predate that gate.

* fix(worktree-delete): key delete state by the identity the sidebar card reads

A cleanup delete names local rows with the 'local' host, so their queued and
deleting state landed under 'local|<id>' while a card stored without a host
reads the bare id and never showed it. Every delete-state write now derives
its key from the store's own worktree record.

* refactor(workspace-cleanup): reuse the sidebar delete's focus hand-off

Drops the move-focus-before-delete path. Each confirmed row prepares the same
prepareActiveWorktreeFocusAfterDelete committer the sidebar delete uses and
runs it when that row's own removal succeeds, so the user stays on the
workspace while it is queued, lands on the most recently visited surviving
sibling once it is gone, and stays put if its delete fails.

* refactor(workspace-cleanup): reuse the close-terminal stop-agent wording

The stop step's single-agent title is the existing "Stop this agent?" string,
and its description follows the close-terminal prompt's wording.

* test(worktree-delete): cover a local-qualified removal failure on an unhosted card

* docs(workspace-cleanup): describe the after-delete focus hand-off in the bulk-select note

* fix(workspace-cleanup): name one agent in the stop step's button and keep one agent term per locale

* fix(terminal): keep starting terminals in a workspace that is only queued for deletion

A cleanup batch now marks its local rows with the same delete state the sidebar card reads,
so the terminal spawn guard sees them. The guard exists because removal just killed the
workspace's terminals; a queued row still has them and can fail its own delete and stay, so
a new terminal opened there (often the current workspace) stayed blank.

* refactor(terminal): name the teardown-started check the spawn guards share

* chore(workspace-cleanup): keep one agent term in ko/zh and refresh a spawn-guard comment

* fix(workspace-cleanup): refresh consent and focus per removal

* fix(workspace-cleanup): invalidate detected host evidence

* fix(workspace-cleanup): admit removal at transport send

* test(workspace-cleanup): expect the send-time admission callback and a published chat tab
2026-10-16 21:25:00 -07:00
Brennan Benson 002320c17d fix(native-chat): verify the actual CLI conversation during resume (#29124)
* fix(native-chat): identify the owner when resume create is refused

* test(native-chat): consolidate transcript refusal cases

* test(native-chat): keep ownership coverage at the launch boundary

* test(native-chat): update adoption fixture for early ownership admission

* test(native-chat): align resume admission assertions with host ownership

* test(native-chat): keep RPC coverage focused on ownership proof

* test(native-chat): assert blank request presence before optional source

* fix(native-chat): admit the actual CLI source during chat creation

* fix(native-chat): satisfy source admission lint checks

* test(native-chat): provide the waiting hook prompt

* test(native-chat): isolate source admission fixtures from host homes

* test(native-chat): canonicalize adoption fixture directories

* test(native-chat): preserve early ownership admission in source stack

* fix(native-chat): preserve resume refusal behavior and honest transcript feedback

* fix(native-chat): retain import read evidence in typed result

* fix(native-chat): preserve admitted source and prepared execution home

* fix(chat): retain source evidence for dispatched adoption

* fix(chat): keep source capture within module limits

* test(chat): allow host evidence in create routing

* fix(native-chat): admit an unsaved transcript under a linked account folder

A missing transcript stayed unresolved while its account root resolved through
symlinks, so containment failed and a not-yet-saved conversation showed a load
error instead of the unsaved notice.

* refactor(native-chat): move the newest-item import window into its own module

Keeps journal-legacy-import.ts under the line limit after the source
identity observer landed on top of the streaming import.

* fix(native-chat): restore the refusal error import lost in the main merge

* fix(native-chat): admit Pi transcripts against the root Session History walks

A PI_CODING_AGENT_DIR that already ends in sessions, or any custom folder,
made admission expect <dir>/sessions while the lookup searched <dir>, so a
found conversation was refused.

* refactor(native-chat): admit and look up CLI transcripts in one store per account

Each agent's import described where an account keeps its conversations twice,
once for the path check and once for the lookup, and the two could disagree
(the linked-folder and Pi custom-root refusals). The import now names one
conversation store per account; the create path resolves it once and both the
hook-path check and the fallback lookup judge that same store. OpenCode's
store is its exact database.
2026-10-16 21:14:03 -07:00
Jinwoo HongandClaude 550cc5cdcc fix(sidebar): order sidebar names the same on desktop, phone and server (#29946)
* fix(sidebar): order sidebar text the same on every device

Sidebar names, project labels, project-group names and folder workspace
names were ordered with localeCompare, whose result depends on the ICU data
bundled with Electron, the phone WebView or Node. Order them with one
comparator in src/shared/sidebar-list/text-order.ts instead: case-folded and
NFC-normalized, digit runs by value, the rest by code point, and equal text
broken by host-qualified id.

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

* perf(sidebar): fold each sort label once per sort; reuse the phone row identity helper

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

* fix(sidebar): ignore accents when ordering sidebar names

Accents and case now only break exact ties, so "Éclair" sorts beside "eclair".

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-16 21:07:05 -07:00
Brennan Benson 07d3e33675 fix(tab-bar): stop guessing a launch failed while the new terminal is still starting (#29911)
The + agent menu waited 5 s for the new tab's terminal and otherwise showed
"Couldn't launch X — the terminal did not start." Any slow spawn (Windows,
a cold or busy terminal host, a queued spawn) passed through that state and
got the false toast before the agent came up. The pane's own spawn already
reports a real failure in the pane, so the guess is removed.
2026-10-16 20:55:19 -07:00
Neil 357aa41cb4 fix(terminal): keep Vim scrolling inside shrunken panes (#29960) 2026-10-16 20:45:13 -07:00
Kelvin Amoaba 432c6d0735 fix(mobile): show message times, keep the long-press sheet open, and say when a conversation isn't found (#29105) 2026-10-17 03:37:12 +00:00
Neil 6863096c06 fix(orcad): a client that reconnects during idle shutdown can still open terminals (#29958)
* fix(orcad): replace a retired terminal daemon when a client returns during idle shutdown

* fix(daemon): drop idle-retired legacy generations when replacing the daemon

* fix(orcad): treat a lost or late daemon retirement reply as committed

* fix(orcad): settle a lost retirement reply with the daemon itself before replacing it

* fix(orcad): bind the replacement to the retired daemon and honor the router fence

* test(daemon): type the pending census

* fix(daemon): skip the owner check only for a proven-dead endpoint
2026-10-16 20:25:47 -07:00
Neil 9cc8b80817 fix(worktrees): a preparation another machine owns is never reclaimed as stale (#29957)
The preparation lock reason named only the owner's pid, so a host sharing the repo's disk
(an NFS home) read another machine's in-progress preparation as abandoned and discarded it.
The reason now carries the writer's machine identity (optional, appended after the fields
older builds read); a different or unknown machine is unverifiable, and a reason without
one is judged by pid only on a local disk.
2026-10-16 20:24:04 -07:00
NeilandClaude 15b1217b38 fix(projects): removing one host's copy of a project keeps its same-path twin's shells, tabs and edits (#29954)
* fix(projects): removing one host's project keeps its same-path twin's shells, tabs and edits

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

* fix(projects): close the removed host's browsers, chats and late tabs under a twin id

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

* fix(projects): attribute a twin's terminals by every PTY id it keeps, SSH included

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

* fix(projects): kill a removed local/SSH twin's shells directly and keep its files out of reopen history

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

* fix(projects): prune the removed twin's closed editors from the reopen stack

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

* fix(projects): recheck twin survivors after the host call and keep reopen order in step

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

* fix(projects): close a removed twin's simulators and prune its closed browsers

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

* fix(projects): attribute an unstamped twin browser by its pages' host

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

* fix(projects): plan a twin's terminal teardown so PTYs other tabs bind survive

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

* fix(projects): treat a null browser host as local and close stale stamped editor tabs

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

* fix(projects): a host stamp yields to a PTY naming another host; sweep tabless sleeping records

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

* fix(projects): cancel a twin's pending launches, clear its PTY records, prune its local editors

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

* fix(projects): an ownerless remote pane blocks a stamp; keep the focused surface on background twin removal

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

* chore(projects): leave closeFile's active-surface choice to closeFile

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

* fix(projects): keep the selection fields consistent after twin teardown

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

* fix(editor): closing a background workspace's file keeps the focused surface

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

* fix(projects): a file's own host outranks its tab stamp in twin attribution

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

* fix(projects): kill only a twin tab's live PTYs and honor a browser's own host stamp

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

* fix(projects): skip twin files the removal refresh already closed

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

* fix(projects): kill a batch's live PTYs whichever tab the planner chose; background file close keeps focus

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

* fix(projects): resolve a removed SSH copy's paired-runtime alias in twin attribution

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

* fix(projects): stop the removed host's pane in a tab another host's pane keeps

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

* fix(projects): judge a still-present twin surface on its latest evidence

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

* fix(projects): apply the removed copy's runtime alias to launches and history; layout-only hints prove nothing

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

* fix(projects): repair a selection naming the removed copy's runtime alias

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

* fix(projects): leave single-host selection to the bulk purge; test background file close

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-16 20:22:28 -07:00
Neil 7d077ee9c9 fix(ssh): keep existing hosts' orca CLI control off after the on-by-default flip (#29949)
* fix(ssh): keep existing hosts' orca CLI control switch off after the on-by-default flip

Builds before the flip saved an explicit off as an absent value, so absent=on
silently turned on hosts the user had turned off. On the first load after this
upgrade, pin every saved host's absent switch to an explicit false and record a
one-shot marker in settings so hosts added later keep the new default.

* test(ssh): check the saved profile without a type assertion

* fix(ssh): say the CLI control switch is on by default for new hosts at the call site too
2026-10-16 20:07:35 -07:00
Neil 86315533da fix(orcad): keep an artifact slot whose serve or daemon record names another machine or boot (#29952)
On a shared home, a peer lock's PID is evidence only on the machine that issued it.
Cache retention judged the instance lock and daemon PID records by PID alone, so a
second machine evicted the slot another machine's live serve or daemon runs from.
2026-10-16 20:06:20 -07:00
Neil cffb603b03 fix(remote): opening a slept running agent on a host with no window resumes it, never a fresh launch (#29950)
A host that could open a window but had none left a workspace-slept running
agent's record for a window wake that never came, so opening its tab from a
paired client or phone launched a fresh agent with settings defaults (for
example --dangerously-skip-permissions instead of the saved --permission-mode
plan). The pane now resumes it with only the recorded flags. Before the spawn
it durably claims the record by rewriting it into the host's own pane-owned
capture form, which a window wake never relaunches (no second writer) and
which still resumes the pane on a later open if the PTY is lost or the spawn
fails. With a window present the window still owns the wake, and the pane
opens a plain shell instead of a fresh agent.
2026-10-16 20:05:25 -07:00
Jinwoo Hong 7bf3e150ed refactor(sidebar): move the sidebar's time rules into shared pure reducers (#29943)
* refactor(sidebar): move sort settling, the Smart latch and sweep retention into shared pure reducers

* refactor(sidebar): let the settle reducer own the Manual and mode-switch rule
2026-10-16 19:59:14 -07:00
Jinwoo Hong f7a8e633af Send a terminal tab's own changes to main first, from every caller (dark, layout-owner build only) (#29904)
* feat(layout): route a terminal tab's own changes to main first (dark)

Rename, color, agent and vault titles, view mode and an ended launch
agent now reach main as layout commands from every caller, in
layout-owner builds only. Main's create also takes the window's
createdAt, and frames keep the window's pane mount generation.

* fix(layout): toggle from the mode sent to main, give each agent title its own answer (dark)

A second view toggle before main answers the first now flips from the
mode already sent, so the tab ends where a window-owned build would.
An agent-title batch applies each tab's title when main answers that
tab, and a paired server's tab at once. Drops the frame merge's
generation field: the window's own save already carries it. Reverts
unrelated formatting.

* fix(layout): clear a pending view mode only on its own send's answer (dark)

* chore: restore three files the commit hook reformatted outside this change

* fix(layout): send one agent title per tab while main has not answered (dark)

The window keeps a tab's first generated title, so a second prompt's
title sent before main answered the first would leave main on the
second and the window on the first.
2026-10-16 19:56:25 -07:00
Jinwoo Hong 9cc5d6b427 Keep an editor tab Orca moved to its file's workspace across restart; record editor renames in main (dark) (#29930)
* fix(editor): keep an editor tab Orca moved to its file's workspace across restart

The move saved the tab under the file's owned id and left the tab's
workspace pointing at the old one. Restore rebuilt a local file under
its bare path, so the tab named no open file and was dropped. Restore
now maps a saved owned id to the id it rebuilds, and the move gives
the tab its new workspace.

* refactor(editor): delete reorderFiles, which nothing calls

* chore: prune the renderer layout-writer baseline

* fix(layout-owner): keep diff, preview and conflict records when a frame redraws editor tabs

Switch-on builds only. A frame rebuilds the window's tabs through the
restore builders, which drop every diff, conflict and check tab (restore
has no record for them), and the store patch dropped every open file
the frame did not carry. Frames carry only edit-mode files, so the next
frame after opening a diff closed it in the window while main kept it,
and a markdown preview kept its tab but lost its record.

The rebuild now keeps a transient tab whose record the window holds,
and a held record goes only with the last tab showing it; a record no
tab shows (a conflict review's selected file) stays.

* feat(layout-owner): record renamed open files and closed conflict placeholders in main

Switch-on builds only; switch-off behaviour is unchanged.

A rename or move in the explorer rekeys open editor, preview and diff
tabs in the window and main learned it only from the window's save.
The window now records the rekey as a `renameEditorFiles` command,
under the ids it chose (bare path, owner-qualified id, diff id), one
per workspace, with each edit-mode file's new record. It records
rather than asks first: the rekey is atomic with drafts and the
autosave echo gate, and its answer gates the move coordinator. Main's
path-keyed `editorFilesRenamed` transition, which nothing produced and
which could not match owner-qualified or diff ids, becomes this command.

A git status poll that sees a deleted-side conflict resolved drops its
placeholder record but leaves the tab to the tab reconcile; frames then
brought main's edit record back. Main now closes that tab.

* test: type the rename command as a window layout command

* fix(editor): map a moved tab's owned id on restart only; main records a renamed file only for a tab it shows

* fix(editor): restore a moved tab's file under the owned id the tab names

Restart and frames now agree: neither rekeys the tab, and a frame that
redraws one workspace never points its tab at a same-path file in
another. A rename that changes nothing leaves no empty file list.

* test: a moved tab's owned id stays apart from a sibling's same-path file in any frame
2026-10-16 19:54:52 -07:00
Žan Mlakar 3f651a63ab Fix GitLab label, member and assignee edits through glab (#28776) 2026-10-16 19:54:11 -07:00
Neil 60b8e97451 fix(ssh): a relay on another node of a shared network home is unverifiable, not exited (#29928)
* fix(ssh): a refused relay socket on a network home is unverifiable, not exited

On a shared NFS home behind a round-robin SSH alias, the next connect can land on a
different node than the relay's. That node's kernel refuses the socket and its lsof
sees no holder, so the probe called a live relay exited and its terminals were retired.
A refused socket now proves exit only on a filesystem local to the probing machine.

* fix(ssh): read the socket filesystem on macOS hosts whose Node lacks statfsSync
2026-10-16 19:47:41 -07:00
Neil 0a5d27805f fix(terminals): sleep a twin-published workspace on the host each terminal names, or refuse (#29929)
* fix(terminals): route sleep and hibernation by each PTY's own host when two hosts publish a worktree

* fix(terminals): settle every runtime stop before rolling back a multi-host sleep

* fix(terminals): refuse a sleep whose PTYs span two runtimes instead of splitting it

* test: type-safe selector read in twin hibernation test

* refactor: keep hibernation owner mapping pure and settle the focus-read baseline
2026-10-16 19:47:26 -07:00
Neil 633df3ac7a fix(cli): automations create/edit for an SSH project work against an older paired server (#29924)
Capturing the destination host called ssh.listTargetSummaries directly, so a
paired server predating it failed the whole write with a raw method_not_found.
Reuse the listTargets fallback host-selector-alternatives already applies,
factored so the destination check still surfaces every other error instead
of treating it as "no targets".
2026-10-16 19:38:32 -07:00
Neil aa05edb8d6 fix(wsl-host): a failed removal no longer leaves the host stuck as "being removed" (#29921)
remove() marked the host as being removed, then awaited disconnect and
releaseHolder with no cleanup on failure. A throw (EBUSY/EPERM on Windows)
left every later connect and runtime call refused. Clear the mark in a
finally, keep the record and its visible work (dropped only on success), and rethrow
so the user sees the error and can retry.
2026-10-16 19:35:11 -07:00
Neil 49450b7144 fix(projects): stop offering a project on a paired server's SSH host, and say where to add it (#29919)
Since #27609 a server serves no SSH target and refuses repo.addRemote, yet the add-project dialog
still offered "Project on this server's SSH host" and the refusal read "SSH connection not found
or not connected". The row is now disabled with a message pointing to adding the SSH host on this
computer, and the server's refusal says the same for older clients that still offer it.
2026-10-16 19:33:47 -07:00
Neil 03be5bb9b8 fix(ssh): a Disconnect or Remove during a background connect sticks (#29918)
A background connect (managed tunnel, census, runtime context) that was
waiting for the old transport to close resumed after the user's
Disconnect or Remove host and registered a fresh connection. Re-check the
attempt token after that await so the cancelled attempt never registers.
2026-10-16 19:32:51 -07:00
Jinwoo Hong 776c791554 feat(orcad): ship the mobile web page so phones paired to orcad get it (#29882)
* feat(orcad): ship the mobile web page so phones paired to orcad get it

orcad never carried out/mobile-web, so it did not advertise
mobileWeb.bundle.v1 and phones paired directly to an orcad server fell
back to the app's built-in native screens.

The built page now ships in every orcad package at out/mobile-web, the
path getBundledMobileWebBundleRoot already probes under the install
root. Its manifest is a fixed artifact; its content-addressed assets
join the hashed artifact list from that manifest, as one shared copy in
the template's common files. build-orcad fails when the page is missing
or stale, and CI lanes that build orcad now build the page first.

* ci(orcad): check out mobile/ for the Windows host template build

* refactor(orcad): copy the page tree whole and build it once per CI run

The guard already refuses unlisted files, so staging copies the
bundle directory as native-chat-visuals is copied. The shipped template
takes the page the qualification lanes tested, the e2e build artifact
carries it, and the PR smoke builds it through install-mobile-dependencies
behind its own scope step. Drops tests that duplicated the guard's own.

* fix(orcad): refuse a template page asset name that escapes the slot
2026-10-16 19:29:41 -07:00
Jinwoo Hong 0013011409 refactor(sidebar): move the pure workspace-list builder into src/shared/sidebar-list (no visible change) (#29906)
* test(sidebar): capture golden fixtures of today's sidebar row model

Golden output of the unmodified window pipeline (filter, sort, grouping,
pins, lineage, host sections, Cmd+1-9 targets) across every groupBy x sortBy
x projectOrderBy x pinned policy, plus host-filter, repo-filter, hide-sleeping
and hide-default-branch scenarios, each expanded and collapsed. Kept as the
correctness reference for moving the row builder.

* refactor(sidebar): move the pure row builder into src/shared/sidebar-list

The sidebar's list rules (visible filter, grouping, pinned rows, project
group sections, placeholders, host sections, collapse keys) move to
src/shared/sidebar-list as pure functions. Headers carry an icon key instead
of a React component; the window resolves glyph and tone when it paints, and
passes localized header labels in. The window keeps calling through thin
adapters (build-rows, compute-visible-worktrees); sort latch, settled epoch,
sleeping retention and pending creations stay where they are.

github-cache-key moves to src/shared/github because PR-status grouping needs
it; the renderer path stays as a re-export so its callers are untouched.
Other edits outside the sidebar are import paths only. The golden fixtures
from the previous commit pass unchanged.

* refactor(sidebar): resolve header labels at render, add an inbox golden

Header labels are read on every re-emit again, as before the move. Settings
and labels types move next to the other row types; header visuals no longer
export what nobody imports. Adds a golden for the new-external-worktrees inbox
row, captured on the pre-move pipeline.

* refactor(sidebar): shrink the shared builder's inputs to plain readers

- The builder takes a PR-lane reader instead of the PR cache and settings;
  getPRGroupKey stays in the window, so github-cache-key no longer moves.
- Pinned, All and PR-lane headers carry only an icon key; the window names
  and draws them at paint (status headers keep their data label).
- The status icon key carries the status id; the window looks up its look.
- Window callers use planSidebarRows directly; the positional helper is a
  test fixture.
- Folder path-status routing and rendered-row keys stay in the window.

Goldens pass byte-identical.

* refactor(sidebar): point stale builder comments at planSidebarRows
2026-10-16 19:28:58 -07:00
Neil 6bbc3cbb5b fix(gitlab): load MR reviewer choices from the paired server that owns the repo (#29917)
The runtime GitLab API answered listAssignableUsers with an empty list, so the reviewer picker
was always empty for a paired server's repo. Add an optional gitlab.listAssignableUsers RPC on the
host; a host that predates it answers method_not_found and the picker stays empty as before.
2026-10-16 19:23:49 -07:00
NeilandClaude ed147d5d68 fix(ssh): re-check a macOS host's ended launch-session warning so another desktop's restart can clear it (#29915)
* fix(ssh): re-check a macOS host's ended launch-session warning so another desktop's restart can clear it

The ended verdict was cached for the whole SSH session, so when another desktop restarted the server under a live session, this desktop kept the warning. It now expires like every other verdict: a live re-probe clears the note, and an unanswered one keeps the proven verdict.

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

* fix(ssh): only a live probe clears a proven launch-session end, and a failed recheck republishes it

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

* fix(ssh): a failed launch-session recheck restores the warning only for the same live connection

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-16 19:18:09 -07: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 0e599dddfe feat(acp): the app follows a host's managed runtime install state, shared by every surface (#29048)
* feat(acp): the renderer follows a host's managed runtime install state, shared by every surface

One entry per host runtime instance and agent holds the host's answer. While any shown surface
observes it, one managedRuntime.status long-poll follows the host, idle included, so another
client's install shows up. A failed first read is its own state, never "not installed"; a reply
older than the one shown is dropped. Observed only for agents the host marks as Orca-provided, on
hosts that advertise managed-runtime.install.v1.

* fix(settings): keep one install status wait per host, retry it when the host is back, and wait for the host's agent list

- Hiding and showing a chat no longer leaves a status wait behind on the host for each cycle: the
  wait is never aborted, and a surface shown again reuses the one in flight.
- The host answering again after an outage retries a failed status read at once.
- While the host's capabilities or agent list are still loading, the install state is pending
  rather than "not provided", so a chat doesn't flash its composer; a failed list read reads as
  a failed status read, so Settings says so instead of hiding Install.

* fix(chat): only a reachable host's outstanding agent list holds the Install slot; an offline host or a list without the managed mark keeps the composer

* fix(acp): the renderer follows install status over one host subscription instead of a long-poll

Each host and agent shown on screen holds one managedRuntime.subscribe stream, ended when the last surface hides it. A lost stream resubscribes with backoff, or at once when the host is back; every subscription is a new generation, so a restarted host's snapshot replaces the old state even though its revision count restarted, and a late frame or install reply from a superseded subscription is dropped. Cancel's reply is no longer applied: the stream shows its outcome.
2026-10-16 19:02:54 -07:00
Neil 0bf5209ac8 fix(cli): infer the repo for worktree create when the parent is host-scoped by identity (#29909)
When two hosts share a worktree id, the caller's cwd resolves to an identity: selector, which
carries no repo id, so worktree create reported "Missing repo selector". Keep the selected
worktree id alongside the selector and infer the repo from it.
2026-10-16 19:02:47 -07:00
Neil bdecb92932 fix(ports): show the Ports panel for folder workspaces on a managed SSH server (#29908)
A folder workspace has no repo row, only a synthetic repoId, so the panel's repo lookup came back
empty and it said "No workspace selected", hiding the SSH forwards and the server's listeners.
2026-10-16 18:54:56 -07:00
Brennan Benson b61b9da778 fix(mobile): a streaming reply reveals word by word, like the desktop (#29148)
* fix(mobile): a streaming reply reveals word by word, like the desktop

The phone drew each streamed chunk as it landed, so a reply arrived in a few
jumps. Move the desktop's paced reveal into src/shared and use it on both
surfaces: the phone paces the growing block of the newest reply, remembers
progress per chat list so a recycled row resumes, and draws everything at once
with reduced motion. The terminal-backed chat's live row now counts as growing.

* fix(mobile): hold the reply reveals in the chat overlay

The list's turn resolver must not scan terminal-backed transcripts, and the
overlay outlives the list across chat and terminal toggles, so it owns the
reveal memory and the reduced-motion read. Also fix the moved hook's import
in the onboarding route, and mock the OS read where tests render the overlay.

* test(mobile): mock the message row's other children in the reveal test

Their real modules reach React Native's Flow sources, which Bun cannot load;
the other message row tests mock them the same way.

* style: format the desktop paced-text hook

* Keep phone reply reveal progress owned by its message

* Add braces around phone reveal test assertion

* Start phone prose streaming only at a text block

* Find the mocked phone tool row with a node predicate

* refactor(mobile): move chat prose rendering into its own file

Keeps MobileNativeChatMessage.tsx under the 400-line limit after the main merge.
2026-10-16 18:51:18 -07:00
Brennan Benson 634ca45fbb feat(acp): hosts install a managed runtime over RPC, and mark agents whose program Orca provides (#29024)
* feat(acp): hosts install a managed runtime over RPC, and mark agents whose program Orca provides

managedRuntime.status / managedRuntime.install / managedRuntime.cancel, negotiated by
managed-runtime.install.v1, act on the host that receives them, the one that runs the agent's
chats, with no workspace needed. A status read naming the revision it shows is a long-poll on its
own call-queue lane, so every client sees another's start, progress and outcome.
agentSession.agents marks an agent whose program Orca provides on that host.

* fix(runtime): install status says a copy outside Orca's runs only when a start would really run it

* fix(runtime): install status is pushed to subscribers instead of long-polled, and Cancel answers with the outcome

managedRuntime.subscribe {agent} sends a full snapshot on subscribe, then one
snapshot per install state change, and closes with the transport. It replaces
the managedRuntime.status long-poll and its slot registrations.
2026-10-16 18:47:12 -07:00
Brennan Benson 07558ba8ce fix(native-chat): a dot between the queue header's label and its count (#29268)
* fix(native-chat): a dot between the queue header's label and its count

* style(mobile): wrap the queue header dot's props

* refactor(mobile): move the queued card's text into its own file

The queue file sat at the 400-line cap, so the header dot had no room.
2026-10-16 18:39:08 -07:00