* fix(terminal): stop middle-click primary paste inserting twice on X11
When "Middle-click Paste from Selection" is enabled, the integrated
terminal reads the X11 PRIMARY selection on mousedown and writes it to
the PTY itself. It calls preventDefault on the mousedown, but Chromium's
native X11 middle-click primary paste fires on mouse release regardless,
landing in xterm's helper textarea, which xterm then forwards to the PTY
a second time. The result is the selection pasted twice.
The global primary-selection hook already suppresses the follow-up native
paste, but only for editable DOM targets it owns via a pending-target
handle; it deliberately excludes xterm, so the terminal path had no
suppression at all.
Arm a short shared suppression window when the terminal handles a
middle-click, and have the global hook's capture-phase beforeinput/paste
suppressor honor it. This swallows the single native paste event so the
selection reaches the PTY exactly once. Ctrl+Shift+V and right-click
paste are unaffected: they use the CLIPBOARD and never emit a native
primary-paste event.
Closes#8860
* docs(terminal): clarify native middle-click paste suppression
* fix(terminal): scope primary-selection native-paste suppression to Linux xterm
Gate the armed native-paste suppression window to Linux (X11 primary-selection
path) and scope it to xterm's helper textarea so unrelated document pastes and
non-Linux platforms are never affected. Re-arm on auxclick for slow releases.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
Co-authored-by: Orca <help@stably.ai>
When the macOS Computer Use helper app is missing (e.g. a dev build that
never ran `pnpm build:computer-macos`), getComputerUsePermissionStatus
reports helperUnavailableReason alongside all permissions set to
not-granted. The onboarding runner only checked the permissions, so it
called openSetup, which throws a RuntimeClientError in the
computerUsePermissions:openSetup IPC handler and logs an alarming error
to the main process console.
Guard on helperUnavailableReason first: surface the reason as a warning
and skip openSetup, so onboarding degrades gracefully instead of
triggering an IPC handler error.
The "Add a setup script" card cached its inspection result and only re-ran
on activeRepo/settings/sidebarOpen/dismiss/retry changes. A shared orca.yaml
setup hook that became effective on disk — edited externally, or run during
worktree creation — left the stale prompt visible until a full sidebar reopen.
Extract the revalidation into useSetupScriptPromptRevalidation, which
re-inspects on window focus (external edits / terminal hook runs) and when a
worktree of the repo activates while the prompt still shows no effective setup.
Claude-Session: https://claude.ai/code/session_01C8RPZ1mhUCMWcojgD6jLaN
Co-authored-by: kaynan <kaynan.camargo@terceiro-sky.com.br>
* fix(agent-status): replay remote Codex startup snapshots
* fix(agent-status): move unattributed-track comment next to its call
The '// Why: ... track the adoption/routing failure separately' comment
was left above the new replay branch after the track() call moved into
the non-replay branch. Move it next to track('agent_hook_unattributed')
so the rationale sits with the code it explains.
---------
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
* fix(tab-bar): open absolute local paths from worktree tab create entry
Local worktrees can paste absolute file paths into tab create; remote and
SSH workspaces stay blocked. Harden classifier ordering, ownership gating,
and render-time fail-closed behavior from review.
* fix(tab-bar): restore typecheck after worktree path refactor
Add the missing editor file-operation import, finish the global file
drop relative-path helper migration, and align the runtime env test mock.
* fix(tab-bar): fail closed for absolute local paths
---------
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
* fix: route folder workspaces in worktree operations (#10251)
Folder workspaces are not Git worktrees and never appear in the repo/worktree
catalogs, so they were falling through to unresolved cross-host routing and
failing closed on all owner-dependent operations. Extract folder workspace
ownership logic to a dedicated module and add dedicated routing for folder
workspace identifiers before checking Git worktree catalogs.
* persist folder workspace metadata on the FolderWorkspace record
Folder workspaces lack worktreeMeta rows; metadata updates (activity bumps, unread status, terminal focus) must call updateFolderWorkspace. Fixes routing so local folder workspaces resolve to 'local' even when unrelated runtimes exist (#10251).
* Fix folder workspace mutations routing through owners
Folder workspace updates and deletions were routing through the currently
focused runtime instead of the owning runtime. Add coordinators for
concurrent-update race prevention and activity-persistence coalescing.
Handle runtime-owned folders in editor file operations and terminal activity
tracking.
* fix(terminal): stop the reveal fit from reflow-garbling inline TUIs
grok (and other inline-viewport TUIs like Codex) render garbled after the
floating terminal is minimized and brought back up. On reveal the resume path
fit xterm synchronously right after re-attaching WebGL, whose cell metrics
differ from the DOM renderer's — so it could propose a one-column-off grid,
reflow xterm, then snap back a frame later (a net-zero resize "wiggle"). xterm's
main-buffer wrap→unwrap is not a perfect inverse, and an inline TUI that
diff-paints its pinned region redraws over the corrupted buffer.
Replace the unconditional synchronous reveal fit (fitAllPanes) with a gated fit
(PaneManager.fitAllPanesStable → fitRevealedPane):
- fit synchronously only when the fit element's pixels actually changed while
hidden (a real resize the app must reflow for anyway, kept ahead of the async
PTY size reassert so it can't forward a stale grid);
- if the pixels are unchanged but the grid diverged while hidden (snapshot /
SSH-reattach direct terminal.resize, or an appearance/DPI change), repair it on
a steady grid (requestStablePaneFit) so a sustained mismatch refits while a
transient cell-metric wobble does not reflow;
- otherwise leave the pane alone.
The common minimize→restore is now a hard no-op with zero reflow. Also applied to
the window-wake reveal path.
* refactor(terminal): tighten reveal-fit comments + rename to fitAllRevealedPanes
Quality pass: make fitRevealedPane the single canonical explanation of the
reveal wobble and reduce the duplicated comments at the call sites to short
pointers; rename PaneManager.fitAllPanesStable -> fitAllRevealedPanes ("stable"
only described one of its three branches); symmetric early-returns in
fitRevealedPane. No behavior change.
* fix(workspace-board): sync Linear on context-menu Move to Status
The board's right-click "Move to Status" only wrote the local
workspaceStatus and silently dropped the Linear sync that drag-and-drop
performs. Thread an onAssignWorkspaceStatus callback from the drawer
through the kanban card chain into WorktreeContextMenu so the menu
funnels through the same local-first + Linear-sync path
(moveWorktreesToStatus) as drag-and-drop. Outside the board (sidebar
list) the menu keeps its local-only behavior.
* test(workspace-board): guard context-menu Move to Status routing
Extract the context-menu status-assign routing into a pure
planWorkspaceStatusAssignment helper (behavior-preserving) and unit-test
it, so the board Linear-sync vs sidebar local-only branch — the exact
path #10175 regressed on — cannot silently flip back unnoticed. Covers
board-sync-forwards-all-ids, local-only-writes-only-changed, and the
same-status no-op case.
Addresses code-review finding: the added drawer tests exercised the
sync wiring via a mocked LaneGrid but never the menu's routing branch.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: ElNelyo <ElNelyo@users.noreply.github.com>
Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
Co-authored-by: Orca <help@stably.ai>
Preserve OMP session identity and exact resume paths across cold restoration, AI Vault, mobile, WSL/SSH, and host-authority routes. Add mixed-version fallback and completed-session recovery coverage.
* fix(terminal): hide SSH error toast under the reconnect banner
The z-50 TerminalErrorToast was stacking over the non-blocking SSH reconnect
banner with the raw ssh:connect failure. Suppress the toast while the banner
owns recovery and clear matching toast text so it cannot flash after reconnect.
* fix(terminal): strip only SSH-owned lines from aggregated terminal error
onPtyError newline-joins multiple PTY errors into one string, so the prior
startsWith() classification misfired on aggregated errors: an unrelated error
before the SSH failure left the stale ssh:connect text to flash after reconnect,
and an SSH-first error discarded any unrelated error. Classify per line and drop
only reconnect-owned lines, keeping the rest. Closes CodeRabbit's aggregation edge.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Orca <help@stably.ai>
* fix(worktree): stop terminal removal fence error flashing on delete
Deleting a worktree kills its PTYs for the filesystem teardown, then runs
git worktree remove (~1s). During that window a doomed pane races a fresh
respawn that main correctly fences with TerminalRemovalInProgressError, but
the renderer surfaced that internal fence verbatim as a pane error banner
until the worktree unmounted.
- startFreshSpawn: skip the respawn when the pane's own worktree is being
deleted (isDeleting) — no shell should spawn into a directory being removed
and the pane is about to unmount.
- reportError: swallow the removal fence at the single pane-error sink so it
never reaches the banner, covering the parent-removal-fences-child case the
own-worktree skip cannot see.
- Share the fence messages between main (thrown) and renderer (recognized) via
worktree-removal-fence-error.ts so the thrown text and predicate can't drift.
* test(worktree): assert onError callback captured before invoking
Optional invocation let the fence-suppression test false-pass if the
transport onError wiring broke; require the callback so a broken wire
fails loudly. Addresses CodeRabbit review on #10240.
* fix(app): bound the wake/quit paths implicated in the phone-session-ended freeze (#9447)
- relay-transport: waitForClose now times out (5s) so a half-open post-sleep
socket can't wedge runtimeRpc.stop()
- will-quit: race teardown against a 20s deadline so app.quit() always runs
(Force Quit was the only escape when any teardown member never settled)
- terminal-fit-restore: local restoreTerminalFit invoke gets the same 15s
bound as the remote path so the held-fit modal buttons can't pin disabled
* fix(app): close wake recovery timeout gaps
* fix(relay): drop late frames after forced teardown
* fix(relay): fence detached socket callbacks
* fix(app): close timeout resource gaps
* fix(relay): detach retired mobile transports
* fix(types): exclude absent stat overloads
* fix(runtime): expire wedged terminal restore dedupe
* fix(runtime): keep restore retries on one reclaim
* chore(skills): refresh bundled skill manifests
* fix(window): fence quit acknowledgements by request
* fix(relay): bound revoked device socket cleanup
Portal the non-blocking reconnect banner into each pane container so WebGL
compositing cannot paint terminal text through it, raise z-index above
pane-local chrome, and use a fully opaque card background.
* feat(agent-rows): opt-in conversation-name labels for agent rows
Sidebar worktree-card and dashboard agent rows always show the last
message sent to the agent, so rows relabel on every turn and a 'continue'
prompt becomes the row's name. Add an opt-in Agents setting that labels
rows with the conversation name instead, resolved with the tab bar's
precedence: manual rename, quick-command label, OpenCode session title,
generated title (behind its existing setting), then the agent-set live
title. Live titles count only when they carry a real name - status
decoration is stripped, and pure status, identity-echo, spinner+cwd, and
placeholder titles fall back to the last-message label. Subagent child
rows keep their own descriptions.
Locale note: sync:localization-catalog also restored parity for keys
already missing on main (add-host, sleep-worktree copy).
* fix(agent-rows): read the live tab so late renames and titles surface
Row data patches live entries in place and keeps the tab snapshot from
row creation, so a rename or agent-set title landing after the row was
built never reached the conversation-name resolver. Select the current
tab from the store in the hook; retained rows without a live tab keep
the snapshot fallback.
* fix(agent-rows): reject status labels and bound lookup work
Reject native provider/status titles and Windows/UNC cwd frames so conversation-name mode falls back to the user's last message instead of relabeling rows with identity or path text. Keep default-off and subagent rows off the tab map, and share a WeakMap-backed tab index across mounted rows to avoid repeated linear scans on store writes.
* feat(agent-rows): make conversation names the default
Always prefer a usable conversation name for sidebar and dashboard agent rows, falling back to the last message when the resolver rejects a title. Remove the preference, settings UI, search copy, and catalog entries. Keep subagent labels unchanged and reject generic Terminal N placeholders so partial tab snapshots cannot hide meaningful prompts.
* chore(i18n): sync catalog after main merge
* fix(agent-rows): preserve same-tab child labels
* fix(agent-status): resume finished agents after macOS logout instead of a bare shell (#9454)
After the daemon-retirement fix (#9826) recovers terminals across a macOS
logout, a running Claude Code (or other resumable TUI agent) that had
finished its turn came back as a BARE SHELL — the user had to open a new
tab and `claude --resume` in every worktree to get logged back in.
Root cause: setAgentStatus writes a persisted 'live recovery' sleeping
record (the cold-restore --resume anchor) for a live resumable agent, but
drops it once the turn is 'done' — except for Pi, which was special-cased
(retainsPiRecoveryIdentity). A finished Claude/Codex turn leaves the TUI
alive and resumable at its prompt just like Pi, so an idle agent lost its
anchor; a cold restore after an abrupt app death (logout) then found
nothing to resume and dropped to a bare shell.
Fix: generalize the Pi-only recovery-identity retention to every resumable
TUI agent (isResumableTuiAgent), gated on a valid resume argv, so a finished
agent keeps its persisted providerSession anchor and the already-shipped
cold-restore --resume path brings it back logged in. Extends an existing
pattern; no new subsystem.
Reproduced + validated live in Electron via the login-session-death seam:
idle Claude -> daemon retire -> app relaunch previously bare-shelled; now
spawns 'claude --resume <session>' and restores the conversation logged in.
* test(agent-status): update sleep-hygiene assertions for retained live recovery anchor (#9454)
* fix(editor): preserve PDF zoom when file content reloads
PdfViewer rebuilt the pdf.js document on every content change and always
reset to page-width, so external PDF updates wiped the user's zoom. Keep
a fit-width vs absolute preference across reloads for the same file path.
Closes#10165
* refactor(editor): dedupe PDF zoom handlers into shared callbacks
Collapse six copies of the step-scale-and-record-preference logic (three
directions duplicated across the keyboard handler and toolbar callbacks)
into a single stepZoom callback; the keydown handler now reuses zoomIn/
zoomOut/zoomReset so keyboard and toolbar can't drift apart.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
Co-authored-by: Orca <help@stably.ai>
* perf(startup): defer full worktree scan until after session restore
* fix(startup): normalize worktree recovery session keys
* fix(startup): preserve persisted workspace hydration
* fix(startup): keep SSH repos in pre-hydration worktree fetch
Round 1 review fixes for the selective pre-hydration fetch:
- The prefetch filter gated on LOCAL_EXECUTION_HOST_ID, which excluded
SSH repos. Old full-scan code populated worktreesByRepo for disconnected
SSH repos via the local metadata fallback (no network), so hydrateTabsSession
/ editor / browser preserved their chrome. Excluding SSH dropped that chrome
at hydration and setHydrationSucceeded made the loss durable. Include SSH by
excluding only runtime-owned repos (which hydrate via placeholders instead).
- collectWorktreeHydrationRepoIdsFromSession never collected activeRepoId, so a
repo referenced only by active-repo state (no active worktree, no tabs) was
not prefetched, breaking hydrateWorkspaceSession's main-worktree fallback.
* fix(startup): re-prune visit timestamps after deferred full worktree scan
Round 2 review fix. The selective pre-hydration fetch only populates
session-referenced repos, so the one-shot pruneLastVisitedTimestamps at
startup no longer sees non-session repos and keeps their deleted-worktree
entries (the prune defers unloaded repos to preserve SSH focus recency).
The old full pre-hydration scan had populated all local repos before the
prune. Re-run the prune after the deferred fetchAllWorktrees so stale
lastVisitedAtByWorktreeId entries are dropped once every repo is
authoritative; disconnected SSH stays non-authoritative and is kept.
* fix(startup): exclude unbounded history maps from hydration repo enumeration
Round 3 review fix. collectWorktreeHydrationRepoIdsFromSession collected
repo ids from lastVisitedAtByWorktreeId and defaultTerminalTabsAppliedByWorktreeId,
which accumulate one entry per worktree ever focused / given default tabs.
On the long-lived large profile this fix targets (#9441), those maps
reference nearly every repo, so the 'selective' pre-hydration fetch
degenerated back toward the full all-repos scan the fix removed.
Split the keyed-field list: repo enumeration now scans only chrome-bearing
fields (open tabs/editors/browser), while folder-key detection keeps
scanning all fields. The history maps hydrate unfiltered regardless, and
the deferred re-prune reaps their stale entries, so no data or recency is
lost. Also pin the round-2 re-prune ordering in the routing test.
* fix(startup): prevent competing full worktree scans
* fix(startup): keep selective hydration scans bounded
* fix(startup): ignore stale tab selection markers
* fix(startup): keep deferred worktree recovery selective
* test(windows): dismiss startup overlays before terminal setup
---------
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
* fix(status-bar): seed Resource Manager closed badge from daemon inventory
The closed chip counted tab/layout PTY wake hints (inflating terminal
count) and never fetched memory until the popover opened (showing "—").
Cache listSessions for the badge, seed memory on session ready, and drop
the wake-hint closed selector.
* fix(status-bar): update session inventory ref in an effect
CodeRabbit/React Doctor flagged mutating sessionInventoryRef during
render; keep the write in useEffect after commit.
* fix(status-bar): follow daemon session lifecycle events
* perf(status-bar): skip inventory scans for known PTYs
* perf(status-bar): bound resource inventory refreshes
---------
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
* feat(source-control): show current branch without evicting Create PR
#9787 added the current-branch identity to the Source Control header but
did it by replacing the Create PR button's toolbar slot, so #10032 reverted
the whole thing. Create PR is the primary entry point into the
stage→commit→push→generate-PR flow, so it can't be traded away.
Restore the branch identity as its own row above the toolbar so it coexists
with the Create PR button (Option 2 layout). Detached HEAD renders in the
same identity row via DetachedHeadBadge (re-adds its tabIndex/aria-label),
replacing the separate below-toolbar badge row.
* style(source-control): match branch identity text to the 'vs main' base ref
Same font-mono / 10.5px / foreground-90 / underline treatment so the current
branch name reads visually consistent with the base ref in the context row.
* style(source-control): drop branch identity underline
Keeps the 'vs main' font/size/color match but no underline — the label isn't
clickable, so the underline read as a false affordance.
* test(source-control): harden identity-row detached + no-identity contracts
Add a stable data-testid to the identity row so the no-identity case proves no
row renders, and assert the detached badge's accessible label + focusability.
Addresses CodeRabbit review on #10215.
Improves clarity and consistency throughout mobile pairing, settings,
and permission descriptions. Makes the distinction from Tailscale
more explicit where relevant. Updates all translated locales.
* feat(diagnostics): name what grew in renderer OOM crash reports
Renderer OOMs are the dominant crash class (heap pinned at the ~3.5GB V8
ceiling in crash-channel reports) but renderer_memory breadcrumbs carry only
heap totals, so reports say "it grew" without saying what.
Add a one-shot renderer_memory_highwater breadcrumb at 60%/80% of the heap
limit carrying leak-diagnosis counts: DOM node census, terminal element
count, and per-subsystem counts from a new contributor registry. The store
registers the first contributor, reporting its 20 largest top-level
collections. Counts only, capped per contributor; zero work on the 60s
sample path while below threshold.
* fix(diagnostics): retain renderer heap profiles
* test(agent-status): remove subagent row order race
* fix(diagnostics): bound aggregate heap profile work
* fix(diagnostics): bound heap profile contributor calls
* fix(types): avoid overloaded stat return inference
* fix(diagnostics): fully bound heap profile registry
* chore(skills): refresh release snapshot manifest
* fix(diagnostics): retain browser counts in heap profiles
* fix(checks): stop the transient "PR already exists" card before the PR loads
The Checks panel briefly showed the terminal "Pull request already exists"
card (Open Review only) on a cold mount, and only rendered the real PR panel
after a manual refresh.
Root cause (regression from #9428): the eligibility probe resolves
`existing_review` from cache faster than the renderable PR hydrates, and the
new exhaustive selector ranks `existing_review` as a terminal safety blocker
above every loading state. So during the fetch window the panel settled on the
terminal card instead of a self-updating loading state. The auto-fetch also
used a lazy SWR priority, so the PR landed slowly unless the user forced a
foreground refresh.
Fix:
- Selector: while a PR fetch is queued/in-flight and we have positive-but-not-
yet-renderable review evidence (existing_review or positive_unresolved), show
the self-updating "Checking status" state instead of the terminal card. It
flips to the review once details land. A concurrent branch blocker still owns
the copy.
- Fetch: when a review is known to exist but no renderable PR is cached, escalate
the auto-fetch to a foreground `active`/80 request (matching manual refresh) so
it resolves promptly.
Reuses existing i18n keys; adds unit coverage for both parts.
* fix(checks): bound unresolved review foreground refreshes
* fix(types): exclude absent stat overloads
* fix(checks): preserve refresh precedence and budget
* fix(checks): avoid redundant foreground PR refreshes
Fold unresolved-review promotion into the existing refresh effect so cached evidence produces one dispatch instead of an SWR plus active pair. Keep non-GitHub evidence on the budgeted path to avoid forced GitHub work for GitLab and other providers.
* fix(checks): scope foreground refreshes by provider
Inline setup/onboarding terminals (skill installs, feature tips) and the floating terminal use host-agnostic synthetic worktree ids with no worktree/repo row. Since #9994, connectPanePty gated their transport on resolveWorktreeOperationRouteResult, which fails closed ('missing') for unknown ids — so the Orca CLI skill Update button, every other inline setup terminal, and the floating terminal showed "Workspace identity is ambiguous across hosts" instead of running.
Route them through the shared resolveTerminalWorktreeRoute (which already exempts the floating terminal and folder workspaces), and add the missing ephemeral-setup exemption so setup terminals follow the single active runtime (remote skill installs land there) or run locally when none is focused. Genuinely unknown/stale repo-backed worktrees still fail closed, preserving #9994.
Adds terminal-worktree-route unit tests and connectPanePty regression tests (proven to fail without the fix).
Co-authored-by: Orca <help@stably.ai>
* fix(terminal): restore a dark-background contrast floor
Fully disabling xterm minimumContrastRatio on dark backgrounds (#9599)
left near-background body text unreadable — Antigravity paints #262b30
on #1e242a (~1.1:1). Keep light backgrounds at WCAG-AA 4.5 and use a
milder dark floor (3) so dark-on-dark body text is lifted without the
full light-bg correction strength.
Fixes#10104
* fix(terminal): extend dark-bg contrast floor to preview + mobile terminals
The dark-background minimumContrastRatio floor (#10104) is applied per
`new Terminal()` construction site. Beyond the live pane, agent output also
renders in the dashboard popout preview and the mobile WebView, which were
still at the floor-1 default, so Antigravity output stayed unreadable there.
- AgentTerminalPreview: gate via resolveTerminalMinimumContrastRatio
- mobile WebView: port the gate as resolveTerminalContrastFloor (Chrome-74 JS)
- tests: builtin-catalog guard + mobile vm-harness coverage
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
Co-authored-by: Orca <help@stably.ai>
* fix(quick-open): guide rg install after fallback budget errors
* fix(quick-open): show local host wording for local install guidance
The install-rg guidance component hardcoded 'on the remote', so the new
local fallback path told local users to install ripgrep 'on the remote'
— wrong for the exact case #9627 targets. Parse the location out of the
message and render the matching wording; add the local locale string and
a render test that guards against the 'on the remote' regression. Also
harden the reason capture against a stray ')' in the error text.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
Co-authored-by: Orca <help@stably.ai>
- Tighten combobox row padding (py-2 -> py-1.5) in the Project and Run-on
lists so items aren't so vertically loose.
- Use a Monitor glyph (not a server) for the local machine in Run-on.
- Pin 'Add host' as a compact single-row footer with one clean divider,
matching the Project picker's 'Add a new project' footer (removes the
taller two-line in-list row and its extra separator).
- Give the smart name field a solid bg-background so it matches the other
inputs on light mode instead of reading translucent over the dialog.
Co-authored-by: Orca <help@stably.ai>