The terminalPreview:* handlers gated every channel to the pop-out
renderer, so the in-window overlay's terminal dialog (running in the
main renderer) got { snapshot: null } from connect and falsely showed
"No live terminal — this agent's pane has closed." for live agents.
Accept the trusted UI renderer too — it already has full PTY access
through the regular terminal channels, so this adds no reach.
The experimental Agent Dashboard opened only as a separate pop-out
window. Add an "Open as" mode under the experimental toggle so it can
open as an in-window screen popover (new default) or a pop-out window
(prior behavior). The mode row appears only when the feature is on.
- New setting `experimentalAgentDashboardMode: 'in-window' | 'popout'`
(default in-window); sidebar entry branches on it.
- In-window: AgentDashboardOverlay renders the shared AgentKanbanBoard
in a near-fullscreen dialog, snapshot built locally via
useLiveDashboardSnapshot (the pop-out relays over IPC; in-window has
no relay). Ack/reveal act on the local store — the pop-out IPC
handlers are gated to the pop-out renderer.
- AgentKanbanBoard gains containerClassName/onAckAgent/onRevealAgent/
onClose props; defaults preserve the pop-out behavior.
- Extracted AgentDashboardExperimentalSetting to keep ExperimentalPane
under the max-lines cap.
* 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>
Switching, adding, re-authing, or removing a Codex account awaited
refreshForCodexAccountChange before resolving the IPC call. Since the
per-account CODEX_HOME rollout (#9501) that probe runs against a cold
home (10s RPC + 15s PTY fallback, 25s WSL) and can queue behind an
in-flight global usage fetch, so the switcher sat unresponsive for tens
of seconds and a fresh login looked stuck on the loading screen.
Worse, in addAccount the awaited refresh sat inside the login cleanup
try/catch: a refresh rejection after the account was durably committed
deleted the just-created managed home, leaving a registered account
with no home ("account never connects").
Run the refresh as best-effort background work instead. Its synchronous
prefix still flips usage to "fetching" before the first await, so the
switcher updates instantly and usage fills in via the normal
rate-limit state pushes; a probe failure is logged and can never
trigger managed-home cleanup.
Fixes#10141
* 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>
Replace the api.onorca.dev fallback pattern with same-host retries on
www.onorca.dev, the endpoint that owns crash delivery. This ensures
consistent behavior and clearer error reporting when failures occur.
- 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>
- orca-runtime: 'stop cannot be proven' now requires the fresh PTY inventory
to still show the pty live (empty inventory proves exit post-#10106).
- git-common-watch: swap worktrees dir->file synchronously so no poll tick
observes the transient ENOENT window and fabricates a delete.
(cherry picked from commit e11736d7e170982eeebe09256eb549446a4f4f7b)
Co-authored-by: Orca <help@stably.ai>
Credits remote terminal output only after parse or intentional discard, with bounded adaptive windows, fair draining, recovery cleanup, and RTT/parser benchmarks.
* fix(terminal): resume output after recovery resync
* fix(terminal): harden recovery resync boundaries
* fix(terminal): prevent double rendering on frame-drop resync
After an untagged resync snapshot, the client resets to the snapshot's output
high-water. Buffered output generated while serializing is already included in
the snapshot, so replaying the entire buffer afterward renders those bytes
twice. Trim to output after the snapshot seq.
Tagged snapshots feed side consumers and need all buffered output unchanged.
Add exponential backoff for truncated recovery retries to avoid stampeding
flooded servers.
* feat(dashboard-popout): size the terminal dialog's PTY to the dialog grid
The popout agent-terminal dialog rendered the pane's serialized frame at its
original cols/rows and CSS-scaled it down to fit. The dialog now claims the
PTY grid for its own box through the remote-desktop viewer registry: the PTY
reflows to the dialog's dimensions (crisp, unscaled), the main-window pane
parks at the held grid like any remote viewer hold, and closing the dialog
releases the claim so the pane reclaims its geometry. A phone-driven PTY
keeps the floor; the dialog then falls back to the scale-to-fit rendering.
Any grid change under a live preview stream (fit landing, host reclaim,
phone takeover) now pushes a resync so the dialog repaints from a snapshot
at the new grid — this also fixes garbled dialogs when the pane resized
while a dialog was open.
* fix(dashboard-popout): harden terminal grid claims
* perf(dashboard-popout): bound terminal preview resize work
* Revert "Enable accessibility tree (`ax`) command on iOS emulator sessions (#10007)"
This reverts commit 43ae014a64.
* fix(emulator): expose iOS accessibility tree
* fix(emulator): support device-only iOS AX
* fix(emulator): normalize iOS ax to 0..1 and heal missing axUrl
serve-sim's helper /ax reports element frames in absolute pixels, but
tap/gesture take normalized 0..1 coords. Normalize the raw AX node tree
into a compact nested shape whose frames are 0..1 over the device screen
(first root's frame), mirroring serve-sim's own normalizeAxTree, so agents
can feed ax output straight back into input commands.
Also heal sessions that were registered without an axUrl: #9924 only
derived /ax at parse time, so already-active sessions had no endpoint.
The bridge now derives it from the session's mjpeg stream URL, guarded to
the /stream.mjpeg suffix so a non-mjpeg URL never fabricates a bogus /ax.
* docs(emulator): mark ax working on iOS with correct raw-AX-tree shape
Both skill guides and the CLI summary described iOS ax as unsupported (or,
via the reverted #10007, as a normalized "screen + elements" shape that
never matched the endpoint). ax works on both backends: Android via
uiautomator, iOS via the serve-sim helper. Document the real iOS output —
a raw AX node tree (labels, roles, nested children) with frames normalized
to 0..1 — and regenerate the bundled skill guides.
* chore(skills): regenerate skill bundle manifests
CI verify failed because generated skill artifacts were stale after version/skill revision bumps.
* fix(emulator): read ax from explicit device without active session
Fall back to udid-keyed session lookup when a worktree has no active emulator,
allowing `--device` targeting to work the same way for ax as it does for tap/type.
Also clarify in docs that AX frames are normalized 0..1 with top-left origin,
and show how to tap an element at its frame center (x+width/2, y+height/2).
* fix(emulator): cap iOS AX tree at 500 nodes
Unbounded accessibility trees can flood agent output. Enforce a 500-node limit (matching serve-sim's snapshot cap) and mark truncated parents so consumers know the tree was cut.
---------
Co-authored-by: 5Hyeons <ohs2251@naver.com>
* fix(agents): scope Settings agent list and quick-launch menu to the remote-server host
With a paired Remote Server as the Active Server, Settings → Agents and the
tab-bar + quick-launch items always ran agent detection on the local client's
PATH, so a Windows client showed its own agents while worktree-create
correctly listed the server's.
- Extract TabBar's ssh/runtime/local owner resolution into a shared
useAgentDetectionTargetForWorktree hook and use it in QuickLaunch, which
previously resolved only SSH connections and fell back to local for
paired-runtime worktrees.
- Scope AgentsPane detection (and its Refresh button) to the Active Server,
with an "on <server>" badge showing which host the list came from.
Enable/disable/default toggles remain client-side settings.
- Split runtime detection into store/slices/runtime-detected-agents.ts and add
refreshRuntimeDetectedAgents: preflight.refreshAgents over the relay
(login-shell PATH re-read), falling back to preflight.detectAgents on
servers that predate the refresh RPC, keeping the last known list when the
runtime is unreachable.
* fix(agents): avoid redundant runtime refresh fallback
* fix(agents): dedupe SSH agent refreshes
* fix(agents): preserve remote host boundaries
* fix(agents): prevent remote detection refresh races
* fix(agents): harden remote detection failures
* fix(agents): keep unresolved detection off local host
* fix(agents): keep cold remote ownership unresolved
* perf(startup): overlap catalog/session disk reads with the worktree scan
App.tsx hydrated repos, project-groups, folder-workspaces, worktrees, then
session-get strictly serially, even though only worktrees depends on repos.
Once repos is loaded, run fetch-worktrees, session-get, and the (internally
ordered) local project-group/folder-workspace catalog chain concurrently so
the two disk reads hide behind the O(repos) worktree git scan (the startup
long pole). list-runtime-session-hosts now overlaps the repo scan too.
Ordering preserved: repos before worktrees/session; project-groups before
folder-workspaces; hydrate-session-stores still runs only after all settle.
fetchAllWorktrees({hydrationPurge:'defer'}) returns before its folderWorkspaces
read, so it needs no catalog ordering at startup.
Co-authored-by: Orca <help@stably.ai>
* perf(startup): don't serialize the worktree scan behind host discovery
Address CodeRabbit review: awaiting runtimeHostsPromise before the Promise.all
made fetch-worktrees + the catalog chain wait on list-runtime-session-hosts, even
though only session-get needs the host ids. Chain session-get off the host promise
inside the Promise.all instead, so the worktree scan and catalog reads start
immediately and the host-list IPC only gates session-get.
Co-authored-by: Orca <help@stably.ai>
* test(startup): assert concurrent hydration graph in source-order guard
The #18 startup change runs worktrees/session-get/catalog concurrently in a
single Promise.all, so the guard's old serial folders<worktrees assertion (and
its session-get slice terminator) no longer describe the code. Assert the real
invariants: UI hydrates before any local read, the catalog chain stays ordered,
worktrees+session start after repos, and all three are joined in one Promise.all.
Co-authored-by: Orca <help@stably.ai>
* fix(startup): join concurrent hydration with allSettled so recovery can't race in-flight tasks
The concurrent worktrees/session/catalog join used fail-fast Promise.all, so a fast
rejection from one branch dropped into the catch/recovery path (which reconnects
terminals and flips readiness) while a sibling hydration task was still in flight and
mutating catalog/worktree state — a race the old serial flow could not hit. Use
Promise.allSettled and surface the first rejection only after all three settle, so
recovery still triggers but nothing is left writing to the store. Guard test updated.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Orca <help@stably.ai>
* perf(main): gate idle git-common polling
Co-authored-by: Orca <help@stably.ai>
* fix(main): enumerate git-common worktrees every tick to catch coarse-FS add/remove
The worktrees-dir readdir was gated on the dir's mtime:ctime:ino:size signature, so on
a coarse-mtime/FAT filesystem a same-granule add+remove (all four fields collide) went
undetected until the ~30s index backstop. A single readdir of a small dir is negligible
next to the per-entry structural stats that already run every tick, so always enumerate —
the listing is the authoritative add/remove signal. The expensive per-entry index read
stays gated on each entry's own dir signature; onFullScan now reflects the ungated
index-metadata backstop fan-out (the real periodic cost) rather than the readdir.
Co-authored-by: Orca <help@stably.ai>
* fix(main): don't fabricate worktree deletions on a transient git-common readdir failure
Follow-up to always-enumerating the worktrees dir: the readdir catch-all treated ANY
error as an authoritative empty listing, so a transient failure (EIO/ESTALE/EMFILE,
network/SSH hiccup) emitted a false delete for every linked worktree (and a false create
next tick) — and enumerating every tick widened that exposure. Only ENOENT (dir truly
absent) now yields an empty listing; other errors retain the known entries so per-entry
stats still run and a real removal surfaces as that entry's own stat miss. Adds a
regression test (readdir → ENOTDIR) asserting no false delete.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Orca <help@stably.ai>
* perf(main): park worktree pollers while hidden
The per-repo worktree metadata pollers ran 24/7 with no window-visibility gate
(~50 fs.stat/sec at 20 repos on macOS while the window is hidden). Park the poll
timers while the window is hidden and resume losslessly (immediate fresh-vs-retained
snapshot diff) on reveal, via an injected WorktreePollerWindowVisibility mirroring
the ssh-port-scanner pattern.
Only the poll timers are gated; the darwin native worktrees/ fsevents watch and its
teardown/re-arm lifecycle stay always-on (push-based, ~0 idle cost; #8732 race).
A window is parked only once it has actually been shown and is now hidden: a live
never-shown window (ORCA_E2E_HEADLESS keeps one) and null/destroyed windows stay
always-visible, so a windowless/headless host never permanently parks the poller.
Co-authored-by: Orca <help@stably.ai>
* fix(main): keep start-to-start poller cadence after the interval→timeout change
Parking the pollers moved them from setInterval to a one-shot setTimeout chain, but
scheduling the next tick a full pollIntervalMs AFTER each scan completed turned the
cadence into gap-after-completion — every visible refresh landed ~one scan-duration
late per tick. Measure from tick start and schedule max(0, interval - elapsed) so the
cadence is start-to-start like the original interval, while keeping the one-shot chain
that park/resume needs.
Co-authored-by: Orca <help@stably.ai>
* fix(main): clamp poller reschedule delay to [0, pollIntervalMs]
Date.now() is not monotonic: a backward wall-clock jump (NTP correction) during a scan
makes (now - startedAt) negative, so the start-to-start delay pollIntervalMs - elapsed
would exceed one interval by the adjustment — suppressing visible metadata refreshes for
minutes/hours, unlike the former setInterval. Cap the computed delay at pollIntervalMs
(upper) as well as 0 (lower) in all three pollers.
Co-authored-by: Orca <help@stably.ai>
* refactor(main): drop redundant notifyTimer guards to stay under max-lines
The visibility-parking change pushed worktree-base-directory-watcher.ts to 303 code
lines (limit 300). clearTimeout tolerates null/undefined, so the two truthy guards
around it are redundant — remove them (coalescing null→undefined for the type). No
behavior change; back under the limit without an eslint-disable.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Orca <help@stably.ai>
Adds a keyboard command that opens the "Send notes to an agent" picker for
the active worktree's AI diff-review notes, enabling a fully keyboard-driven
review flow. Unbound by default; users assign it in Settings → Keyboard
Shortcuts.
- New `sourceControl.sendReviewNotes` command (scope global, unbound). Set
`conflictGroup: 'editor'` so Settings warns on collisions with editor chords
(e.g. Add Review Note), not just global ones.
- Dispatched from App.tsx's existing global capture handler so it respects the
terminal-shortcut policy, the shortcut-recorder guard, and defaultPrevented.
- Store thunk `openDiffNotesSendMenuForActiveWorktree` reveals Source Control
and requests the notes send menu open; no-op when there are no unsent notes.
- Menu opens via a nonce-based store request consumed on mount, TTL-bounded so
a request the menu never consumed can't reopen it on a later remount.
Co-authored-by: Orca <help@stably.ai>
The type-aware switch-exhaustiveness lint rule requires explicit cases
for 'current', 'duplicate', and undefined; they fall through to the
existing generic skipped message, so behavior is unchanged.
Co-authored-by: Yuris Auzins <zuz666@users.noreply.github.com>
Grok transcripts carry no timestamps. Previous logic excluded these rows
from matching, leaving pending sends and launch prompts unmatched and
causing the seeded bubble to appear rank-pinned at the list tail — which
reads as conversation reordering.
Now pending sends, launch prompts, and their pruning rules treat null
timestamps as matching-eligible, allowing echo suppression and proper
cleanup of delivered messages.
* fix(mobile): keep native chat from resizing the covered terminal PTY
Native chat reads the agent transcript stream and never renders the
terminal grid, but two paths still pushed phone dimensions into the
covered PTY, reflowing the desktop terminal for no benefit:
- The covered lease-only subscribe carried the cached viewport, and
handleMobileSubscribe phone-fits the PTY whenever a viewport is
present. The lease now omits the viewport so the host keeps the
desktop baseline and late-binds on return to the terminal tab.
- useTerminalViewportRefit measured the still-mounted WebView under
the chat overlay and sent terminal.updateViewport on rotation,
keyboard, text-scale, reconnect, and iOS-resume triggers. Refits
are now suppressed while native chat covers the active terminal;
the triggers already mark the viewport stale, and the
return-to-terminal resubscribe re-measures.
* fix(mobile): harden native-chat resize suppression
Update desktop experimental settings, mobile settings/onboarding, i18n
(en/zh/ja/ko/es), and user-visible error strings. Keep internal APIs and
identifiers as nativeChat.
Coalesce identical and sub-pixel fallback overlay measurements so ResizeObserver and xterm fit cannot sustain a render feedback loop, while preserving precise committed geometry.
Adds regression coverage for stable measurements, sub-pixel jitter across integer boundaries, and genuine resizes.