Commit Graph
4169 Commits
Author SHA1 Message Date
Brennan Benson baf25785e0 fix(tasks): keep repos with a pending remote-identity probe in the picker (#10527)
* fix(tasks): keep repos with a pending remote-identity probe in the picker

Task-repo eligibility filtered on `hasProjectRemoteIdentity`, which is
populated by a background `git remote -v` probe. When the probe could not
reach git — an SSH-hosted repo whose connection is not up yet, a cold
launch — the repo silently vanished from the Tasks picker and stayed
hidden for the full 5-minute negative-cache TTL, even after the host came
back. GitHub repos were largely shielded because a persisted `upstream`
satisfies the identity projection through a different route; GitLab and
other providers depend on the probe.

Distinguish unknown from settled instead of hiding both:

- `probeGitRemoteIdentity` reports `resolved` / `no-remote` (git answered,
  no usable remote) / `unavailable` (never reached git).
- Enrichment persists `gitRemoteIdentity: null` only on `no-remote`,
  mirroring the existing `upstream: null` "not a fork" marker. An
  unreachable host leaves the identity undefined.
- Persistence keeps the explicit `null` instead of dropping it.
- `getTaskEligibleRepos` keeps a repo whose identity is still pending;
  folders and settled remote-less repos stay filtered out.

* test(tasks): cover the SSH probe exec paths for remote-identity status

Addresses CodeRabbit review: the unavailable-on-error case only exercised
the local git runner. Adds a connected-provider whose exec rejects, and an
SSH repo git answered for with no remotes.

* test(tasks): pin that a settled no-remote repo still resolves once it gains a remote

Three independent reviewers flagged that the candidate filter's `!repo.gitRemoteIdentity`
looks like an oversight next to the new null marker. Tightening it to `=== undefined`
would silently stop detecting a remote added after the marker landed. Document that the
re-probe is deliberate and pin the behavior with a test.
2026-07-25 04:04:19 -07:00
Brennan Benson 5faf3b2ea0 fix(i18n): keep the macOS "Local Network" wording searchable in every locale (#10536)
Settings search indexed the macOS privacy-toggle name as English-only
aliases on the LAN keyword, so a Chinese user typing the term macOS
System Settings actually shows them (本地网络) got no hit — zh's catalog
value had been changed to 局域网 (LAN).

Split the two wordings onto their own catalog keys so each locale carries
both: 87620e6416 = LAN, fa3239cd42 = Local Network (its true content
hash). Localized values come from the repo's own LAN title translations
and from macOS 26's SecurityPrivacyExtension Localizable.loctable
(LOCAL_NETWORK), so nothing is invented. ja/ko/es already matched macOS
and are unchanged apart from gaining the LAN key.
2026-07-25 03:35:13 -07:00
Brennan Benson 9bc640addb fix(terminal): make primary-selection paste suppression single-shot (#10526)
* fix(terminal): make primary-selection paste suppression single-shot

Middle-clicking in the terminal armed a 750ms window that swallowed every
native paste event, not just Chromium's one follow-up — so a real Ctrl+V
inside that window was silently dropped on Linux.

Consume the deadline on first use (`shouldSuppress*` -> `consume*`) so the
arm owes exactly one event; 750ms stays as that event's expiry bound.

* test(terminal): cover the paste event xterm actually forwards to the PTY

Review follow-ups on the single-shot suppression change; no source change.

The new real-module file only synthesized `beforeinput`. An Electron
probe (Chromium 150) showed `paste` fires first, is cancelable, and
cancelling it at document capture suppresses `beforeinput` entirely — and
xterm registers `handlePasteEvent` for `paste` only, never `beforeinput`.
So `paste` is the sole event that can double-write the PTY, and it was
the one event the end-to-end file did not exercise. Add it; it fails with
the fix reverted.

Consuming mutates, so `isTerminalNativePasteTarget(...) && consume()` is
now load-bearing: swapped operands would burn the arm on an unrelated
paste and let the real follow-up double-paste. The guarding test asserted
only `defaultPrevented`, which survives the swap — assert `consume` is
never reached instead.

Rename the mocked-file case that claimed to prove single-shot. With the
module mocked its sequence is dictated by the mock; it pins that the hook
re-asks per event rather than caching, so name it that.

* test(terminal): name the beforeinput dispatcher for the event it dispatches

Round-2 review nit on my own round-1 change: once `dispatchClipboardPaste`
existed alongside it, a helper named `dispatchPaste` that dispatches
`beforeinput` inverted the reader's expectation. Match the sibling file's
`dispatchNativePasteBeforeInput` convention.
2026-07-25 03:33:53 -07:00
Brennan Benson ba434a4a93 fix(sidebar): stop a worktree drag preview from closing the Agent Dashboard (#10531)
The companion-board mutual-exclusion Effect keyed on
`workspaceBoardRenderedOpen`, which is `workspaceBoardOpen ||
workspaceBoardDragPreviewOpen`. WorktreeList sets the drag preview at the
start of every card drag — including a pure reorder within a group that
never opens the board — so dragging any card closed the Agent Dashboard
drawer, and cancelling the drag never restored it.

Key the Effect on `workspaceBoardOpen` so only the user actually opening
the board evicts the dashboard. The reciprocal Effect is unchanged.
2026-07-25 03:05:33 -07:00
NeilandOrca 879aad7dd6 oom(foundation): bound shared readers/limits + add BoundedMap primitive (#10299)
* oom(01): A1-shared-readers — reintroduce #10179 subset

Files: 18 applied, 0 deleted (from 6eb70d8370)

Co-authored-by: Orca <help@stably.ai>

* oom(02): A2-shared-image-media — reintroduce #10179 subset

Files: 7 applied, 0 deleted (from 6eb70d8370)

Co-authored-by: Orca <help@stably.ai>

* oom(03): A3-shared-fs-listing — reintroduce #10179 subset

Files: 21 applied, 0 deleted (from 6eb70d8370)

Co-authored-by: Orca <help@stably.ai>

* oom(04): A4-shared-remote-relay — reintroduce #10179 subset

Files: 8 applied, 0 deleted (from 6eb70d8370)

Co-authored-by: Orca <help@stably.ai>

* oom(05): A5-shared-misc — reintroduce #10179 subset

Files: 28 applied, 0 deleted (from 6eb70d8370)

Co-authored-by: Orca <help@stably.ai>

* oom(06): B-shared-wiring — reintroduce #10179 subset

Files: 81 applied, 0 deleted (from 6eb70d8370)

Co-authored-by: Orca <help@stably.ai>

---------

Co-authored-by: Orca <help@stably.ai>
2026-07-24 21:36:57 -07:00
NeilandOrca cafa958e70 fix(ui): stop the reveal reflow from dismissing popovers (#10487)
* fix(ui): stop the reveal reflow from dismissing popovers

The main process reflows the renderer on every reveal, resume, and restore.
On macOS 26 that nudges the emulated device scale factor, and Chromium fires
a real window resize for it even though innerWidth/innerHeight are identical.
Anything bound directly to resize treated that as a user resize.

The selection copy menu and the markdown link bubble both dismiss on resize
unconditionally, so they closed on their own whenever the window was revealed
or restored. Gate both on an actual dimension change.

Only fixes the macOS 26 path: the pre-Tahoe repaint jiggles the native frame
by a real pixel, so the renderer sees a genuine dimension change and no
change-detection guard can — or should — filter it.

Co-authored-by: Orca <help@stably.ai>

* test(ui): cover the reveal-reflow guard at the component level

Pins both halves of the wiring: a same-size resize must not dismiss, a real
one still must. Mutation-checked — reverting to a bare resize listener, or
dropping the listener entirely, each fails the suite.

Co-authored-by: Orca <help@stably.ai>

---------

Co-authored-by: Orca <help@stably.ai>
2026-07-24 21:11:48 -07:00
OrcaWin b4718dd05c fix(terminal): resume hibernated agents that reattach with no payload (#9648)
* fix(terminal): resume hibernated agents that reattach with no payload

When agent hibernation is on, a stopped (done) agent's PTY is killed and a
passive sleeping record is kept; returning to the worktree relies on the pane
reattaching on remount. On the daemon path (Windows/local worktrees) the daemon
can reattach the hibernation-killed session as already-live (isReattach,
isNew:false) and return no snapshot/replay/coldRestore — and, being a reattach
rather than a fresh spawn, it silently drops the --resume command passed on
connect. The renderer adopted that empty session, leaving a blank terminal with
nothing running and the sidebar history still pointing at the dead tab (the
sleeping record never cleared).

A reopened pane that owns a resumable slept session must always be re-driven
with its resume command, never left as a bare empty attach. handleReattachResult
now discards a contentless isReattach for a pane with a hibernation record and
re-drives the prepared resume. This excludes the healthy cases: a fresh session
the daemon created (isReattach falsy — it already ran the command) and a live
reattach (carries a snapshot/replay). Forward the isReattach signal the
transport was dropping so the two cases are distinguishable.

Adds a deterministic regression test that fails without the guard and passes
with it.

* fix(terminal): preserve provider ownership on resume

* chore(skills): refresh generated skill bundle manifests

Regenerate the skill bundle artifacts against the full release-tag set so
the freshness verify check passes. Append-only additions for the newer
release tags; no released snapshot history is rewritten.
2026-07-24 18:40:05 -07:00
Brennan Benson c3526cc19d feat(codex): surface a stalled config sync instead of failing silently (#10449)
* feat(codex): surface a stalled config sync instead of failing silently

Why: the mirror keeps serving the last synced settings when ~/.codex/config.toml
is missing, blank, or unreadable. That is the right call for data safety, but it
is invisible — a downed WSL distro or an unhydrated cloud-synced home leaves
"Orca ignores my config edits" with no log line and no UI to diagnose.

Status is derived on demand from the same predicates the mirror uses, so the two
cannot disagree. The stall is logged once per episode rather than on every launch
and quota poll, and the Codex account section names the file and what to do.

* fix(codex): latch an unreadable source and stop over-claiming recovery

An unreadable source throws out of the mirror, so reporting only on the success
path left that stall latch-less: it logged the raw failure on every launch and
quota poll while its reason never reached the surfaced status. Report from the
catch path too.

The clear message also claimed the source was "readable again", which is false
when the stall ended because the runtime config was removed rather than because
the source came back.

Restoring console.warn now happens in afterEach — an inline mockRestore is
skipped by a failing assertion, and the leaked spy made every later case in the
block fail spuriously.

* fix(codex): latch the stall promotion hits first, and scope it to the host

Review round 1 findings:

- The unreadable-source latch still never fired in the steady state. Once a
  baseline exists, promotion reads the source before the mirror does, so it
  throws first and `!promotionPlan` returned before any reporting — logging a
  reasonless failure every launch and quota poll, which is exactly what the
  previous commit claimed to fix. Report from that branch too. The test only
  passed because its fixture had no baseline; it now seeds one first and fails
  without the fix.
- The banner named the host's ~/.codex while a WSL or per-account runtime was
  selected, whose real source is a different file entirely. Gate it to the host
  scope, matching how the sign-in warning is already gated.
- Three new translate keys were missing from the locale catalogs, failing the
  localization gate in `pnpm lint`.
- The registrar mock was never asserted, so deleting the registration left the
  suite green.
- `codexConfigSyncStatus` hung off the `agentHooks` namespace despite having
  nothing to do with agent hooks; moved to its own `codexConfigSync.status`
  while it is still a four-file change.

* fix(codex): report sync health for the home the selection actually mirrors

Review round 2:

- The status resolved the shared runtime home, but the system default now runs
  Codex directly against ~/.codex and managed accounts get their own home. So a
  stalled per-account mirror showed no banner at all, while a stale shared home
  could warn about a config the active lane never reads. Resolve the mirrored
  home from the current selection, and report synced when the lane has no mirror
  to fall behind.
- The round-1 report on the promotion failure path could clear the latch on a
  pass where no mirror ran, claiming a recovery that never happened and
  silencing every later pass. Only ever latch a stall there; leave clearing to
  the path that actually mirrored.

* fix(codex): refetch sync status when the active Codex account changes

Review round 3:

- Resolving the status per selection made the fetch account-dependent, but the
  effect was not keyed on the active account. Switching accounts left the banner
  describing the previous one — and switching INTO a stalled account showed
  nothing at all, which is the silence this change exists to remove.
- Pin the home resolution itself: it had no direct test, and its shared-home
  path was a hand-copied literal that could drift from the real helper and
  silence the banner with every other test still green.
- Narrow the handler's dependency to the one method it calls, which also drops
  an `as unknown as` cast from its test.
- Skip the chmod-based test on Windows, where a read-only directory does not
  block writes so the scenario cannot be constructed; matches the convention
  already used in config-settings-promotion.test.ts.

* chore(codex): restore the handler docstring and isolate the resolver suite

Round 4 returned clean; these are its two non-blocking nits.

Narrowing the handler param left its JSDoc stranded above the new type, so the
function had no hover doc. The resolver suite also read the developer's real
CODEX_HOME and shell rc, so anyone exporting one would see it fail locally.
2026-07-24 18:30:54 -07:00
Brennan Benson b9a7c00df5 fix(editor): read out-of-worktree SSH paths without a stamped target id (#9743) (#10455)
* fix(editor): read out-of-worktree SSH paths without a stamped target id (#9743)

Route external absolute-path tabs by their resolved SSH connection instead of
the externalSshTargetId stamp, which only the terminal-link open path sets. The
stamp keeps its fail-closed role when present.

* fix(editor): keep client-local live-tail log tabs off the worktree SSH host

AI Vault "View Log" tabs are opened client-local by construction (readOnly +
liveTail, no runtime env), so inferring the external SSH owner from the
worktree connection made them read the remote host instead of the granted
client path. Restrict that inference to non-live-tail tabs; an explicit
externalSshTargetId stamp still routes remotely.
2026-07-24 17:38:03 -07:00
moseohandBrennan Benson 1aaf049a4d fix(rate-limits): keep Codex PTY reset text for weekly-only plans (#8643)
* fix(rate-limits): keep Codex PTY reset text for weekly-only plans

The PTY /status fallback parses '5h limit' and 'Weekly limit' lines by
label, but the extracted reset text was only ever attached to the
session window. Codex plans without a 5h session bucket (e.g. current
Pro) produce a weekly-only parse, so the reset time the CLI printed was
silently dropped. Fall back to the weekly window when no session window
exists.

* review: parse Codex PTY reset text per window into resetsAt

* review: make Codex PTY status fallback work on codex >=0.145

* review: harden PTY status parse against model-scoped rows and styled output

* fix(rate-limits): strip private PTY control sequences

---------

Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
2026-07-24 16:26:55 -07:00
Brennan Benson e133d93b7b fix(skills): stop promising a skill update the command cannot deliver (#10436)
* fix(skills): stop promising a skill update the command cannot deliver

An "Update available" badge could never clear: pressing Update ran
`npx skills update <name> --global`, which reported "All global skills are
up to date" and wrote nothing, while the badge stayed on.

Freshness marked a name updatable whenever ANY placement was outdated,
including a standalone duplicate in an agent home. The global command only
converges the canonical copy and its symlink aliases, so a stale duplicate
kept the badge lit with no command that could clear it.

- Count only reliably-convergent placements toward the update promise, so a
  stale duplicate no longer advertises an update that cannot land.
- Give the duplicate chip a real skipped-reason instead of falling through to
  the generic sentence, naming the copy and how to resolve it.
- Surface the existing freshness review dialog from the setup rails via a
  Details link, so a blocked or duplicate copy is explainable where the user
  actually sees the badge. Covers orchestration, Computer Use, Ephemeral VMs,
  Linear, the CLI section, the floating orchestration modal, the Browser Use
  card, and the Mobile Emulator row.

* fix(skills): say when a skill copy needs attention instead of reading as all-clear

A skill with an out-of-date copy the update command cannot reach rendered as a
green "Installed" pill. That is honest about the main copy but reads as
all-clear, so real drift in an agent home stayed invisible — the user had no
reason to suspect there was anything to click.

- Add a needs-attention display status (amber) for a placement that is not
  current but has no eligible update: stale duplicates, edited copies,
  read-only, inaccessible, and broken or external links. Presence-only stays
  green, and an unscanned inventory stays quiet so nothing flashes amber on
  launch.
- State the reason inline on the setup rails, so the cause is readable without
  opening the review dialog; Details still opens the full per-location list.
- Extract the skipped-reason sentence into its own module so the rails and the
  dialog share one source and can never drift apart.
- Carry the state through the settings sidebar badge so the nav and the card
  cannot disagree.

* fix(skills): give the inline skill warning something to point at

The shared reason sentences are deictic — "this copy", "the copy here" — because
they were written for the review dialog, where the location rows they describe sit
directly beneath them. On the setup rails there are no rows, so "this" referred to
nothing and the sentence read as if it were about the skill itself.

Name the offending paths above the sentence on the rails, so the referent is
present before the wording that depends on it. Every copy sharing the blocking
reason is listed, not just the first, so resolving one does not leave the badge
unexplained. The dialog keeps its existing wording and rows unchanged.

* fix(skills): stop withholding the update over copies it cannot reach

Eligibility is now decided purely over the placements the global command actually
converges — the canonical copy and its symlink aliases.

A project skill, plugin cache, standalone duplicate, or unreadable copy in another
agent's home used to withhold the update from the whole name. `skills update
--global` provably never writes any of them, so that refused work the command could
have done over a copy that was never at stake. A blocked *convergent* copy still
withholds it: that is the placement the command writes to, and overwriting it is the
real data-loss case.

Also drop the inline reason from the setup rails. The reasons are written to sit
beside the location rows they describe, so on a card they had nothing to point at and
could only ever name one cause. The rails now mark Details with a warning icon when a
copy needs the user's own hands, and the dialog does the explaining with every
location and cause it knows about.

* fix(skills): make the skill Details affordance read as a control

The review link rendered as bare text, so nothing but hover said it could be
clicked — the badge told the user something was wrong and then gave them no visible
way in.

Use the ghost variant so it carries a hover/focus background and a real hit target
instead of a zero-padding text run, and add a chevron so it reads as a control at
rest. The chevron points right, not down: this opens the review dialog, while a down
chevron already means the in-place expander inside that dialog.
2026-07-24 15:53:22 -07:00
Brennan Benson 20ceeeb0f4 fix(runtime): stop the headless hydration repo gate from dropping every tab (#10437)
* fix(runtime): stop the headless hydration repo gate from dropping every tab

#9343 gated headless mobile-session hydration on the live repo list, but read
it as `this.store?.getRepos?.() ?? []`. A store that cannot report repos then
yields an empty set, which reads as "every repo is gone" and skips every
parseable session key — so no tabs hydrate at all. It also called getRepos on
every hydrate, including the hot floating-tab poll path that is contractually
free of repo/provider inventory work.

Resolve the inventory lazily and only for keys that parse to a repoId, and keep
`null` (unavailable) distinct from an empty list (all repos really gone). An
absent list now fails open; a known list still prunes as #9343 intended.

Fixes 3 tests that have been failing on main since #9343 landed:
orca-runtime-terminal-retirement (2) and orca-runtime (1).

* refactor(editor): extract the pending-focus effect to clear the max-lines cap

#8083 pushed RichMarkdownEditor.tsx to 404 counted lines against the 400-line
.tsx cap, failing lint for every PR that merges current main. The Explorer
find-focus request is self-contained, so it moves to its own hook.
2026-07-24 13:25:15 -07:00
JinjingandOrca 772081577e Fix fork PR/MR worktree creation race via durable review-head refs (#10429)
* Fix fork PR/MR worktree creation race via durable review-head refs

When creating a fork PR/MR worktree, concurrent `git fetch origin` operations
clobber the shared FETCH_HEAD, causing the wrong commit to be checked out.
Fetch PR/MR heads into dedicated per-review refs (`refs/orca/pull/<N>`,
`refs/orca/merge-requests/<N>`) that persist and isolate each head from other
fetches. Gracefully keep the compare-base when the fetch fails but the local
ref already exists, avoiding silent fallback to the wrong branch on transient
network errors.

* Bound PR/MR head fetches with 60s timeout

Prevent PR/MR creation from hanging when a remote is stalled or
unreachable. Both GitHub and GitLab head fetches now enforce a
60-second timeout, matching the bound used in the create-path
fetch. Durable refs (refs/orca/pull/*, refs/orca/merge-requests/*)
decouple the ref from FETCH_HEAD, preserving legacy client semantics.

* test: align CI expectations with main PowerShell/sparse regressions

PR checks merge into main, which recently changed PowerShell launch args
(cwd restore after profiles) and sparse-checkout detection (require
core.sparseCheckout). Derive PowerShell spawn args from the production
resolver, mock the sparse config flag, reset shared worktree list scan
cache between tests, and stop requiring floating polls to avoid getRepos
hydration.

* Address review follow-ups on durable review-head refs

- Unify PR review-head remote selection: local and SSH GitHub paths share
  resolveGitHubReviewHeadRemote, which prefers the remote mapping to the
  hosting GitHub project (upstream before origin, matching work-item/API
  candidate order) so contributor clones fetch refs/pull from the repo
  that actually hosts the PR.
- Soft-keep durable review heads: when the PR/MR head fetch fails but
  refs/orca/pull/<N> / refs/orca/merge-requests/<iid> still resolves,
  keep the pinned SHA (warn) instead of failing resolve, mirroring the
  compare-base fallback. Extracted shared compare-base soft-keep into
  compare-base-ref-fetch.ts.
- Extract fetchGitLabMergeRequestHeadRef (local + SSH) parallel to the
  GitHub helper; bound its local fetch with the shared 60s timeout.
- Share relay-style fetch validation (positive safe-integer id, remote
  not starting with "-") between relay and local helpers via
  review-head-tracking-ref.ts; move REVIEW_HEAD_FETCH_TIMEOUT_MS there.
- Drop the githubPullRequestHeadLocalRef re-export; resolve head SHAs via
  rev-parse --verify <ref>^{commit}.
- Add GitLab anti-FETCH_HEAD regression test plus durable-head soft-keep
  and remote-selection unit tests.

Co-authored-by: Orca <help@stably.ai>

* test: supply live getRepos for terminal-retirement hydrates

Main's headless tab hydrate (#9343) skips worktree keys whose repo is not
in getRepos. Retirement tests that rebuild mobile tabs from a persisted
session now advertise the fixture repo as live so PR Checks merge stays green.

* fix(editor): extract RichMarkdownEditor props to stay under max-lines

Main's SSH external-image wiring (#10323) pushed RichMarkdownEditor.tsx over
the 400-line tsx budget, failing PR Checks lint on every merge into main.
Move the props type into a sibling module so the component stays under the
limit without disabling max-lines.

* Make durable review-head refs remote-identity scoped

Embed remote name + URL hash into refs/orca/pull|merge-requests refs to prevent soft-keep from serving wrong project's PR/MR when FETCH_HEAD is clobbered by concurrent fetch. Fetch functions now return the written ref path (writer-authoritative) so callers rev-parse exactly what was fetched, not re-derive identity. Soft-keep only applies to transient errors (timeout, network); fails hard on missing refs, auth failures, and stale relay. Relay returns localRef so client avoids re-hashing (URL normalization can disagree).

---------

Co-authored-by: Orca <help@stably.ai>
2026-07-24 12:53:18 -07:00
Brennan Benson d56e2fbbe4 feat(agent-dashboard): choose in-window board or pop-out window (#10243)
* feat(agent-dashboard): choose in-window screen popover or pop-out window

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(agent-dashboard): admit main renderer to terminal-preview IPC for in-window dialog

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.

* fix(agent-dashboard): sync locale catalogs for new mode/close keys

verify:localization-catalog (part of lint CI) fails when en.json keys are
missing from the other locale catalogs; run sync:localization-catalog so
the six new agent-dashboard keys exist everywhere (English fallback text;
translated copy remains the documented follow-up).

* feat(agent-dashboard): present in-window mode as a companion board sheet

The in-window dashboard now uses the same non-modal left sheet as the
workspace kanban board — anchored to the sidebar edge, chrome/status-bar
bounds, sidebar stays interactive — instead of a near-fullscreen modal
dialog. Both companion boards are mutually exclusive; the sidebar entry
toggles the drawer. Removes the modal focus-restore timing coupling on
reveal.

* fix(agent-dashboard): ignore Radix dismiss requests like the workspace board

Non-modal Radix layers also request dismissal for interactions the drawer's
outside guards cannot classify — focus moving outside carries no pointer
coordinates, and clicks in the status bar / top chrome fall outside the
right-side dismiss band. Forward only open requests from the Sheet, matching
WorkspaceKanbanDrawer, so only the drawer's own escape/outside/close paths
close it.

* fix(agent-dashboard): guard reveal relay and refresh stale mode copy

CodeRabbit review: revealAgent lacked the ?. HMR-skew guard its sibling
ackAgent has (both channels shipped together, so a stale dev preload
lacks both). The es/ja/ko/zh catalogs also still described the dashboard
as pop-out-only in stale English, contradicting the new in-window
default; refreshed to the current English source.

* feat(agent-dashboard): add board settings menu to the in-window header

Mirrors the workspace board's settings gear: an Open as segmented control
in the board header so the mode is changeable without opening Settings.
Switching to pop-out hands the surface over (closes the drawer, opens the
window) instead of leaving a board the setting says should be a window.
In-window only via an optional headerActions slot - the pop-out renderer
has no store to drive it.

* fix(agent-dashboard): reset the settings-menu flag when the drawer closes

The pop-out hand-off closes the sheet while the menu is still open, so the
menu unmounts without Radix reporting onOpenChange(false). The stale
menuOpen=true then blocked outside-dismiss permanently on the next
in-window open. Mirror closeWorkspaceBoard by resetting the flag in close.

* fix(agent-dashboard): reset the menu flag on store-driven drawer closes

Cmd+B sidebar collapse and the workspace-board exclusivity effect close
the drawer via setAgentDashboardDrawerOpen directly, bypassing close();
a settings menu open at that moment unmounted without Radix reporting
onOpenChange(false), leaving menuOpen stuck true and outside-dismiss
disabled on the next open. Sync the flag to the open state so every
close path resets it.
2026-07-24 12:48:18 -07:00
Jinjing b71f71904f Stack head identity above base ref in compare row (#10430)
Stacked layout (head / → base) lets long branch names fit narrow sidebars
without truncating either line. Also show head-only identity when compare base
isn't configured, and improve upstream vs compare target distinction in stats.
2026-07-24 12:26:14 -07:00
Jinwoo HongandOrcaWin 01929fde40 fix(ssh): open external host images from terminal links (#10323)
* fix(ssh): open external host images from terminal links

* fix(ssh): keep external file ownership host-scoped

* fix(ssh): reject blank external file owners

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-07-24 12:05:38 -07:00
Mark Xian 1bd36ce04b fix(github): align PR file diff order (#10081) 2026-07-24 02:10:30 -07:00
Neil 1367094bbc fix(terminal): flush URL hover tooltip to pane bottom-left corner (#10351)
Move link-tooltip chrome into terminal.css so offsets cannot drift via
inline styles, and square the bottom-left corner so the hover preview
sits flush against the pane edge (Ghostty-style).
2026-07-24 01:50:55 -07:00
Kaynan Sampaio de Camargoandkaynan d50ea090cf fix(terminal): reveal Markdown links at target lines (#9518)
* fix(terminal): reveal Markdown links at target lines

* fix(terminal): scope line reveals to opened tabs

---------

Co-authored-by: kaynan <kaynan.camargo@terceiro-sky.com.br>
2026-07-24 00:39:37 -07:00
YoelandCursor d1ccfcff40 fix(settings): normalize and validate branch prefixes (#7772)
* Normalize branch prefixes and flag invalid ones in settings

A custom branch prefix ending in a slash (e.g. "team/") produced a
double-slashed branch name like "team//feature" that git rejects, and
the raw check-ref-format error gave no hint that the prefix caused it.

- Normalize the configured prefix (trim whitespace, strip leading/
  trailing and duplicate slashes) in the shared branch-name builder so
  the common trailing-slash case just works, for local and SSH worktrees.
- Validate the prefix on the worktree-create path (computeValidatedBranchName)
  so a genuinely invalid prefix fails fast with a clear
  "update it in Settings -> Git" message instead of an opaque git error.
- Add a live BranchPrefixFeedback under the Branch Prefix setting: previews
  the resulting branch name, warns on invalid characters, and notes when a
  prefix collapses to none.
- Keep the background first-work rename on the non-throwing builder since the
  prefix is already validated at create time.

* Keep caret in place when editing the branch prefix

The custom branch prefix input was directly controlled by settings, but
updateSettings persists through an async IPC round-trip, so the value
updated a tick late and React re-assigned it, snapping the caret to the
end on mid-string edits. Drive the input from a local draft and only
adopt genuine external settings changes so the caret stays put (and fast
typing survives slow SSH round-trips).

Co-authored-by: Cursor <cursoragent@cursor.com>

* Return ReactNode from BranchPrefixFeedback

JSX.Element needlessly excludes null/string/number returns; ReactNode
keeps the component's return type from over-constraining future changes.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-24 00:39:32 -07:00
NeilandOrca eea1577ddd fix(dashboard): close closed-agent retention race (#9008) (#10334)
Co-authored-by: Orca <help@stably.ai>
2026-07-24 00:39:06 -07:00
Brennan Benson ec04827b37 test(session): guard against workspace-reopen tab fork-bomb (STA-1111) (#10057)
* test(session): guard against workspace-reopen tab fork-bomb (STA-1111)

Relates to STA-1111 (already fixed on main by #6945).

The runaway-tab-on-reopen bug was fixed by #6945 (activeOrQueuedResumeClaimsProviderSession dedup guard) hours before the ticket was filed. Verified: disabling that guard makes tab count climb 1->2->3->4 across reopens; restoring it holds at 1. This adds a revert-sensitive regression test covering the created-agent and sleeping-resume reopen paths so it cannot regress.

Test plan: 2 new tests pass; fail if the #6945 guard is removed.

* test(session): isolate automatic resume replay coverage

* test(session): assert resumed tab identity stays stable
2026-07-24 00:36:50 -07:00
Brennan Benson 1b5db4bc2a fix(window): reflow on macOS occlusion-reveal so the bottom bar is not clipped (#10056)
* fix(window): reflow on macOS occlusion-reveal so the bottom bar is not clipped

Fixes STA-2383.

On macOS the window is background-throttled while hidden; on occlusion-uncover only 'focus' fires and its handler runs webContents.invalidate() (the setSize jiggle is skipped to avoid SIGWINCH-ing terminals). invalidate() repaints but does not reflow, so the app-shell h-dvh root keeps a stale dynamic-viewport height and the StatusBar is clipped below the viewport ('no bottom bar'); a manual resize restores it.

Fix: renderer relays a genuine hidden->visible reveal (visibilitychange) to main via new ui.notifyWindowRevealed IPC; main runs the same proven forceRepaint. Occlusion-gated (no per-Cmd+Tab SIGWINCH regression), darwin-scoped, sender-guarded, cleaned up on close.

Test plan: vitest createMainWindow + web-preload-api green. Recommend on-device macOS occlusion QA before merge.

* fix(window): preserve user resizes during reveal repaint
2026-07-24 00:36:18 -07:00
scokeepaandECO2G Migration 6997bc40ab fix(usage): guard web client against undefined usage scan state (#10073)
The claude/codex/openCode usage store slices read `scanState.enabled`
directly off `window.api.<provider>Usage.getScanState()`. In the web
client that usage IPC is not bridged, so the preload fallback proxy
resolves those calls to `undefined`, and enabling usage tracking from
Settings -> Stats & Usage throws
`TypeError: Cannot read properties of undefined (reading 'enabled')`
(reproduced live against `orca serve` v1.4.150; present on main too).

Guard the getScanState()/setEnabled() seams in all three slices so an
absent scan state degrades to a graceful no-op instead of crashing.
Desktop behavior is unchanged (a real ScanState is always truthy).

Adds a regression test that stubs the web-client fallback (every call
-> undefined) and asserts fetch*/enable* no-op without throwing for all
three providers.

Co-authored-by: ECO2G Migration <cmeia.ai02@cmeia.co.kr>
2026-07-24 00:28:52 -07:00
Wooseong Kim 1f29a33b2a fix(sidebar): do not sticky-pin Project header without mounted geometry (#10090)
During scrollToIndex/reveal, rangeStartIndex can advance before TanStack
mounts the candidate Project row. Returning that unmounted index made a
Project sticky paint over the Host card in multi-host views. Prefer a
previous mounted sticky (or none for the group tier) until geometry exists.

Closes #10088
2026-07-24 00:26:44 -07:00
JeongUk Park fc181a8496 fix(i18n): restore count separators in terminal theme picker for CJK locales (#9935)
The theme picker count row concatenates "Showing {count}" directly with
the " of {{value0}}" fragment. The ko/ja/zh translations dropped the
fragment's leading separator, so the shown and total counts fused
(e.g. Korean rendered "표시 중 3030 중" instead of "표시 중 30/30").

Restore a slash separator for the total-count fragment and the leading
space for the search-match fragment in ko/ja/zh, in both the runtime
catalogs and the key-override sources so catalog regeneration keeps the
repaired values. Add a regression test covering both fragments.

🤖 Generated with Claude Code
2026-07-24 00:26:39 -07:00
a4f42ad427 fix(terminal): make Ctrl+V / Cmd+V paste work in the HTTP web client (#9900)
* fix(terminal): make Ctrl+V paste work in the HTTP web client

navigator.clipboard only exists in secure contexts, so the web client
served over plain HTTP (e.g. a LAN address) read an empty clipboard and
Ctrl+V silently did nothing: the keydown handler preventDefault-ed the
chord and suppressed the native paste event whose clipboardData is the
only clipboard access available there.

When the async clipboard reader is unavailable in the web client, let
the chord's native paste event fire and feed its clipboardData text
through the existing paste pipeline (size limits, bracketed-paste
handling and error toasts included).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RTjQWLg4wy1CgmcZKmGAuN

* fix: address review finding (bug-bash takeover)

Co-authored-by: Orca <help@stably.ai>

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
Co-authored-by: Orca <help@stably.ai>
2026-07-24 00:26:30 -07:00
Kaynan Sampaio de Camargoandkaynan b5ae776c31 fix(terminal): clear stale search highlights (#9519)
Co-authored-by: kaynan <kaynan.camargo@terceiro-sky.com.br>
2026-07-24 00:26:21 -07:00
developerKYHandClaude Opus 4.8 a90ec540f2 fix(settings): persist the last IME syllable when leaving the display-name field mid-composition (#9517)
RepoSettingsDraftInput holds unconfirmed IME text in local draft state and only
persists on compositionend (so the async store echo cannot cancel the composition).
If the user types a Hangul syllable and immediately navigates back out of project
settings while it is still composing, no compositionend fires — the last syllable
stays in the draft and never reaches the store, so the project's display name loses
its final character. Users have to click back into the field or press Enter (both
force compositionend) to make it stick.

Flush the visible draft on blur and on unmount, guarded by lastPersistedRef so it is
a no-op when a keystroke or compositionend already persisted that value. The flush
closure is published from an effect (post-commit) so a discarded concurrent render
cannot leave the cleanup pointing at uncommitted draft state.

Adds blur / unmount / no-double-persist / no-op regression tests to the component's
existing suite.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-24 00:26:16 -07:00
David Whatley 5a1ca24262 fix(status-bar): tolerate missing provider snapshots (#9408) 2026-07-24 00:26:02 -07:00
kazu-42 53222cc9c1 fix(terminal): wait for shell readiness on Codex account restart (#9365) 2026-07-24 00:25:58 -07:00
143d2232bb fix: reconcile a stale activeWorktreeId against live worktrees on hydration (#9344)
The web client persists its workspace session (activeWorktreeId,
lastVisitedAtByWorktreeId, ...) in localStorage and, unlike the main-process
Store, gets no load-time orphan GC. `pruneLastVisitedTimestamps` already drops
stale focus-recency entries on hydration, but the persisted `activeWorktreeId`
pointer is not reconciled — so a pointer to a worktree the server no longer
reports lingers (the main-process path clears it via removeWorkspaceSessionOwner
when a repo is removed; the web has no equivalent), and can surface a phantom /
duplicate workspace that survives reloads.

Extend the hydration reconcile to also clear `activeWorktreeId` once its repo is
hydrated and the worktree is confirmed gone, mirroring the existing per-repo
defer rule (a not-yet-hydrated repo, e.g. SSH pre-connect, keeps its pointer).

Co-authored-by: fanyunqian.1 <fanyunqian.1@bytedance.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-24 00:25:49 -07:00
eefded2a04 fix(terminal): stop native middle-click paste inserting twice on Linux (#8993)
* 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>
2026-07-24 00:25:21 -07:00
morluto 9ced27eca8 fix(onboarding): skip Computer Use setup when macOS helper app is unavailable (#8951)
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.
2026-07-24 00:25:17 -07:00
Kaynan Sampaio de Camargoandkaynan deb2b50e71 fix(sidebar): revalidate setup-script prompt when the hook becomes effective (#8752) (#8893)
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>
2026-07-24 00:25:12 -07:00
Rod Boev 4e670d3e4c fix(terminal): stop wheel replay when mouse reporting is disabled (#8616) 2026-07-24 00:25:07 -07:00
Rod BoevandBrennan Benson 739fce5287 fix(agent-status): replay remote Codex startup snapshots (#7873)
* 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>
2026-07-24 00:25:03 -07:00
Wooseong Kim 4274dbc48a fix(linear): union filter options across every selected team (#10042) 2026-07-23 23:51:54 -07:00
余辉 fa09d6fd8e fix(tasks): hide repos without remote identity (#9898) 2026-07-23 23:46:01 -07:00
Shahar Mor 877bbdebf8 fix(agents): stop forking a duplicate Pi tab for a live background session (#9729) 2026-07-23 23:42:35 -07:00
92696558c3 fix(settings): gate project path setup on host connection (#9410)
Co-authored-by: linshengtao <linshengtao@bytedance.com>
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
2026-07-23 23:38:43 -07:00
microtaroandClaude Fable 5 001a5c6846 fix: pane divider drag-to-resize never moves under WSLg (mouse down, pen motion) (#9153)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 23:31:35 -07:00
Trevin ChowandOrcaWin bd45d705bf fix(tab-bar): open absolute local paths from worktree tab create entry (#10222)
* 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>
2026-07-23 23:26:39 -07:00
katspaughandClaude Sonnet 5 462020d372 fix: link to repo root instead of 404ing /stargazers page (#8756)
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-23 23:21:29 -07:00
Stanislav Markin 91884b6d5f fix(sidebar): let project and group headers drag by their icon (#8576) 2026-07-23 23:19:43 -07:00
Rod Boev c63ab965d8 fix(terminal): open WSL file links on Windows (#8215) 2026-07-23 23:18:31 -07:00
gatsby74andBrennan Benson 65f245f074 fix(editor): focus Explorer-opened Markdown for find (#8083)
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
2026-07-23 23:18:03 -07:00
SeoYeonKim 933cee633e fix(editor): open files in the focused pane when it is a browser (#6891) (#8014) 2026-07-23 23:17:10 -07:00
Mark XianandClaude Opus 4.8 afa549f1d2 fix(gitlab): open the New-MR page on the fork project for fork-pushed branches (#7654)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 23:10:54 -07:00
Jinjing c88cf8413f docs: finish Android APK 0.0.32 link rollup
Point localized READMEs and in-app Android download CTAs at
mobile-android-v0.0.32 (English README and orca-site were already updated).
2026-07-23 22:20:43 -07:00