Commit Graph
11496 Commits
Author SHA1 Message Date
Brennan Benson 9924b07bcc fix(terminal): self-heal panes whose renderer dies while the PTY stays alive (#8630)
* fix(terminal): self-heal panes whose renderer dies while the PTY stays alive

A pane's xterm write pipeline can die while its shell keeps running: a
synchronous throw escaping an unguarded write callback wedges WriteBuffer
(issue #2836), and write() on a disposed terminal silently drops its
completion callback (verified against vendored xterm 6.1.0-beta.287 — it
does NOT throw, falsifying the output scheduler's disposed-race catch).
Every recovery path we have (dead-session reconcile #6514/#7002,
hibernation wake #7145, the allDead activation generation bump) gates on
the PTY being dead, so these panes stayed fossils: last frame painted,
every keystroke and byte of output silently dropped, delivery ack credits
leaking, until the user reloaded the window (issue #8104 class).

Detection is probe-certified, mirroring replay-guard.ts:
- a scheduler write whose completion stalls gets an empty probe write; a
  probe that never parses certifies the pipeline dead (catches both the
  wedged WriteBuffer and the disposed-terminal case) and credits the
  queued deliveries so main's in-flight window no longer leaks
- the replay guard's existing wedged release ("pane likely needs
  recovery") now actually hands the pane to recovery
- user input rejected by an unbound transport (detached during a
  remount/move and never rebound) arms recovery after confirming the PTY
  is alive via pty:hasPty

Recovery reuses the proven remount seam: bump the tab's generation so
TerminalPane unmounts, detach() preserves the live PTY, and the remounted
pane builds a fresh xterm that reattaches and replays the daemon
snapshot — no shell restart, capped per tab to prevent remount storms.

The new e2e spec pins both phenotypes end-to-end (wedge → recover,
dispose-under-live-bindings → recover); both fail on main and pass with
the fix, and the same arc was validated live in a pnpm dev instance.

* fix(terminal): make pane recovery strictly best-effort in timer contexts

Recovery fires from stall-watch timers, replay-guard releases, and onData —
contexts where a throw becomes an unhandled error (CI verify caught this:
pty-connection.test.ts mocks a partial store, other tests advance fake
timers past the stall window, and the certification path hit a missing
remountTerminalTabForRecovery). Guard the store action call and the
ptyIdsByTabId reads so a partial surface yields a false return, never a
throw, and pin it with a regression test.

* fix(terminal): guard pane recovery against in-flight reattach and remote liveness blind spots

Review findings on the self-heal (adversarial pass):

1. HIGH: typing during an in-flight connect/reattach (startup restore,
   app-SSH) hits sendInput while the transport is legitimately unbound; an
   input-undeliverable remount there destroys the unbound transport (no
   ptyId yet, so unmount cannot detach), and pty-transport's destroyed
   check then kills the PTY the resolving reattach returns — the live
   shell recovery exists to preserve. Gate the input detector on a
   transport-connect-in-flight flag (set around all three connect sites)
   and on disposed, so "not deliverable YET" never remounts. The fossil
   case (detached and never rebound) has no pending connect and still
   recovers.

2. pty:hasPty answers null for ids the local registry does not own, which
   made the liveness gate inert for remote panes: a disconnected remote
   runtime would remount-churn on every cooldown window while typing.
   Remote panes (connection-tagged or remote:-prefixed) now require an
   authoritative true; local panes keep the lenient null-proceeds gate.

Flagged for follow-up, not changed here: pty-transport's destroyed check
kills reattached sessions without discriminating isReattach — a
pre-existing hazard that tab-close covers by killing per id anyway.

* fix(terminal): keep certification throw-proof end to end

Guard the two remaining throw paths in the certification chain — the
entry-discard callback and the recovery handler — so nothing can escape a
timer as an unhandled error, and a throwing discard cannot suppress the
recovery notification it exists to precede. Pinned by two new tests.

* fix(terminal): breadcrumb swallowed recovery failures

A store-action throw in recovery returns false without consuming budget,
so the detector retries each cooldown — an invisible loop unless it
leaves a trace. Breadcrumb it (the recorder is self-guarded and cannot
throw where recovery runs). Also invoke transport.isConnected optionally
so partial test transports fail the gate quietly instead of logging a
contained TypeError.

* fix(terminal): end the zombie-pane replay loop at its root

Root-caused the production "wedged release drip" (1,302 breadcrumbs in one
day on one machine, 4-write bursts on a fixed timer phase, idle-required):
once a pane's xterm pipeline dies while its connection lives, the delivery
watchdog's heal (60s cooldown, fires only while idle because a dead xterm
never ACKs its in-flight bytes) re-delivers restore markers, the hidden
output restore replays 3-4 chunks into the dead parser, each write arms a
replay guard destined for another wedged release — and nothing ever
learns. The loop runs forever and re-forms after app restart.

Three fixes so the loop learns:
- replayIntoTerminal/Async short-circuit on a probe-certified dead
  pipeline: no more futile writes, so no more guard drips, and awaited
  restore chains resolve instead of hanging.
- requestHiddenOutputRestoreIfNeeded is gated the same way, so the
  watchdog heal stops refetching snapshots for a pane recovery owns.
- a window-cap recovery decline now schedules one retry for when the
  budget window reopens (deduped per tab, cancelled by any successful
  remount). Without it, the certified-dead latch plus the new write
  silence made a capped pane a permanent zombie: nothing would ever
  re-request recovery. Cooldown declines deliberately do not retry —
  remounts are tab-scoped, so the just-made remount already replaced
  every pane's xterm in the tab.

Still open (tracked separately): the deterministic wedge surface that
creates the dead pipeline on the release build in the first place — an
unguarded, unreported parse-path throw or silent disposed-write; zero
guard breadcrumbs fired all day, so the trigger predates the guards'
coverage.

* feat(terminal): name the silent zombie producer in breadcrumbs

The last unproven link in the zombie-pane chain is HOW a pane's xterm
dies on the release build. Field discriminators eliminated every
reporting channel: zero terminal guard breadcrumbs and zero xterm-stack
renderer_error/unhandled_rejection events across days of logs, while the
drip re-formed after a clean app restart. Every content-triggered wedge
surface would have reported; the only fully silent mechanism left is a
restore write into an already-disposed xterm instance (write() drops its
completion callback without a throw — verified against the vendored
6.1.0-beta.287).

Instrument that exact moment: a version-pinned disposal probe (its test
runs against the real vendored build so an upgrade that moves the private
field fails loudly), a terminal_restore_write_target_disposed breadcrumb
where startup scrollback restore would write into a disposed instance,
and a terminal_restore_write_failed breadcrumb replacing the fully silent
restore catch. The next zombie formation logs its own root cause.
2026-07-13 21:30:10 -07:00
Brennan Benson 6c4bb67415 fix(i18n): localize the terminal tab unread-completion aria-label (#8663)
The unread-activity bell landed with a hardcoded aria-label, and the
localization-coverage gate now fails on main itself (verify does not run
on pushes to main, so the break landed silently and blocks every PR's
CI). Route it through translate() with catalog entries for all five
locales.
2026-07-13 21:25:49 -07:00
Jinjing 9875b5528a Improve native chat style (#8654)
* Redesign native chat interactive UI to match assistant-turn styling

- Restyle approval and question cards as bordered cards docked in the
  composer region instead of top-border strips, and widen the chat
  column (max-w-3xl → max-w-4xl) to match the rest of the app
- Rework the question card into a numbered pick-list with an always-
  present free-text row and explicit Next/Skip/Send action, replacing
  the prior "Other…" toggle flow
- Hide the composer while a question card is active since it supplies
  its own answer input, and hold the card open until the paced answer
  send settles instead of dismissing on click
- Auto-repin the message list to the bottom during in-place streaming
  growth via a ResizeObserver on the content, not just the viewport
- Widen question-answer pacing (800ms→1000ms step, 300ms→500ms buffer)
  for reliability on slower machines / higher-latency SSH sessions
- Restyle tool-run/tool-line disclosure to a right-side hover chevron
  and add a formatted (pretty-printed) detail view for diff-less calls
- Add "Skip" i18n string across all locales

* Extend paste bridge and question-card fixes for native chat interactive

- Route pane-level Paste to the question card's free-text input when
  the composer is unmounted, so Cmd/Ctrl+V keeps working while a
  question prompt is showing.
- Fix a stale-timer bug where a new interactive prompt could inherit
  the previous card's dismiss/submitting state and swallow its first
  answer.
- Only render tool-run detail when there's actually content to show,
  and mark selected question options with aria-pressed for
  accessibility.
2026-07-13 21:24:33 -07:00
Brennan Benson 5a3e4b44f8 fix(lint): cover active/inactive in the terminal tab activity switch (#8660)
The WorktreeStatus union feeding activityDotState widened, and the
switch-exhaustiveness gate now fails on main itself (verify does not run
on pushes to main, so the break landed silently and blocks every PR's
CI). Cover the two glyph-less states explicitly — matching the
function's documented intent — and drop the now-unreachable default.
2026-07-13 21:10:01 -07:00
Jinwoo HongandOrca b379610cbe fix(browser): handle Cmd-click popups without adopted WebContents (#8659)
Co-authored-by: Orca <help@stably.ai>
2026-07-13 20:48:00 -07:00
github-actions[bot] 81cc966ea3 release: v1.4.139-rc.2 v1.4.139-rc.2 2026-07-14 03:12:08 +00:00
JinjingandOrca 1ef0551bc1 Create pr not working for stacked worktree (#8651)
* Fix stacked-worktree PR creation targeting a local-only parent branch

- Resolve the eligibility default base to a remote-tracking ref instead
  of blindly trusting the submitted parent branch, since a stacked
  worktree's base is often a local-only branch the remote can't resolve
- Add a create-time hard block (base_not_on_remote) so a stale or
  unpushed submitted base fails with actionable copy instead of the
  provider's opaque error
- Update the dialog's default-base resolution and blocked-action/
  dropdown copy to match the new remote-validated default

* Split hosted-review-creation.test.ts to fix max-lines lint error

Moved getHostedReviewCreationEligibility tests to a separate file (hosted-review-creation-eligibility.test.ts) to reduce the original file size from 880 to 579 lines, satisfying the max-lines lint constraint.

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

* Fix Create PR intent flow to use remote-validated eligibility default fo

Prefer eligibilityDefaultBaseRef over the raw compare base when resolving
the review base for the one-click Create PR intent flow, since eligibility
is recomputed from the same compare base right before creation and already
corrects a local-only stacked parent to the repo default. Falls back to
the compare base only when eligibility supplies no default.

* Simplify base-ref remote existence check into a single for-each-ref call

Combine the wildcard and exact-tracking-ref lookups into one for-each-ref
invocation with multiple patterns instead of two sequential git calls,
removing the redundant rev-parse fallback path.

---------

Co-authored-by: Orca <help@stably.ai>
2026-07-13 20:10:50 -07:00
NeilandOrca 8662e5a7ab perf(ssh): merge the two identical 5s keepalive timers into one per connection (#8653)
Co-authored-by: Orca <help@stably.ai>
2026-07-13 19:46:08 -07:00
Jinjingandbbingz 1d2aaf1bf5 Fix recipe serve desktop promotion (#8646)
* fix(runtime): preserve terminals during headless desktop activation

* rm design doc

* Fix desktop activation launch ordering and blocked-window status resolut

- Check desktopWindowStatus before spawning the Orca app so a blocked
  runtime no longer launches a doomed second instance.
- Reuse resolveDesktopWindowStatus for remote runtime status so it
  honors the same authoritativeWindowId fallback as local status.
- Re-check the authoritative window at spawn time instead of trusting
  a possibly-stale snapshot, since it can be destroyed mid-await.
- Harden the e2e activation spec against silent spawn failures.

---------

Co-authored-by: bbingz <zzb@gxsmjx.com>
2026-07-13 19:41:24 -07:00
NeilandOrca 6e1aa8503c perf(renderer): pause always-on idle timers while the window is hidden (#8528)
Co-authored-by: Orca <help@stably.ai>
2026-07-13 19:19:17 -07:00
NeilandOrca a68bb39ab1 perf(emulator): stop serve-sim watcher waking the daemon 4×/sec when unused (#8525)
Co-authored-by: Orca <help@stably.ai>
2026-07-13 19:18:37 -07:00
Jinjing 2801c6b03b P2 live log undo memory (#8644)
* fix(editor): avoid undo history for read-only live tails

* Add reliability-gate evidence for read-only live-tail undo-history fix

- Records the passing vitest run that verifies live-tail appends leave
  canUndo false while ordinary external updates stay undoable, backing
  the recent editor undo-history fix.
2026-07-13 19:13:19 -07:00
Jinjing f93e92646c P2 watcher lifecycle bounds (#8640)
* fix watcher lifecycle cancellation bounds

* fix(review): guard remote watcher installs against post-shutdown resurrection

closeAllWatchers aborted the in-flight install *tokens* it could see, but a
same-key joiner awaiting a 'cancelled' resolution (and a fired retry tick)
calls installRemoteWatcher directly and, on the fresh-generation recursion,
builds a brand-new non-aborted AbortController and calls provider.watch()
after teardown — leaking an SSH watcher into the just-cleared remoteWatchers
map. Latch the subsystem shut in closeAllWatchers and refuse installs while
latched; a genuine new fs:watchWorktree clears it. Adds a regression test
(fails without the latch) plus a test for the same-tick handoff-revival guard
that had no coverage.

Also extract the duplicated isolated-quarantine-vs-fuse branch shared by
retireSlot and releaseFailedRoot into quarantineOrFuse (behavior-preserving).

* Add lifecycle generation guard to refuse stale remote-watcher joiners

- A boolean latch alone can't distinguish a pre-shutdown joiner from a
  fresh call once a genuine new watch reopens the subsystem, letting a
  stale joiner recurse and register a post-shutdown provider.watch()
- Each installRemoteWatcher call now captures a generation counter that
  closeAllWatchers bumps, so a waiter that resumes after a later
  shutdown+reopen is refused instead of resurrecting
- Adds a regression test covering the shutdown-then-reopen race
2026-07-13 19:12:30 -07:00
Jinjing 79369d5396 Fail loudly on chmod errors in remote CLI launcher install (#8648)
Silently swallowing chmod failures let installs proceed with a
non-executable launcher, surfacing as a confusing runtime error later
instead of a clear install-time failure.
2026-07-13 19:08:46 -07:00
JinjingandOrca 01d7cd779f P2 mobile firewall scope (#8639)
* fix(mobile): validate Windows firewall remote scope

* fix(review): simplify string-guard ternary to boolean AND

The ternary returned only boolean literals, so cond ? f() : false is
equivalent to cond && f() (addressScopeIsSufficient returns boolean).

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

* fix(mobile): accept dotted-netmask firewall scopes and lock in fail-safe edges

- Parse dotted-netmask CIDR (192.168.0.0/255.255.255.0) via a contiguous-mask
  check, failing closed on holey masks.
- Factor subnetFromParsed so CIDR parsing no longer re-parses the address.
- Document why single-host (/32, /128) subnets and family-specific keywords with
  an unknown interface family fail closed, and add regression tests covering
  those deliberate false-deny edges plus policy-defined keywords (Intranet, DNS).

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

* Add explanatory comment on why firewall scope check isn't unioned

Documents the fail-safe rationale behind checking coverage per rule
instead of merging rule scopes, so future edits don't "fix" this
into a less conservative union check.

---------

Co-authored-by: Orca <help@stably.ai>
2026-07-13 19:02:52 -07:00
Jinjing 302b97029a P2 windows cli hardening (#8638)
* fix(cli): harden Windows launcher transports

* Fix csc.exe compile failures on space-bearing Windows install paths

- Legacy csc.exe mangles absolute paths containing spaces, so the
  compile step now cd's into the bin directory and passes bare
  file names for /out and the source file instead of full paths
2026-07-13 18:58:21 -07:00
c408a3d852 feat(mobile): show usage reset countdown on accounts screen (#7954)
* feat(mobile): show usage reset countdown on accounts screen

Surface the rate-limit reset time ("5h resets in 3h 54m · 7d resets in
6d 7h") under the usage bars on the mobile accounts screen, matching the
desktop status-bar tooltip copy. The resetsAt timestamps already arrive
in the accounts.subscribe snapshot; this only adds the presentation.

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

* docs(mobile): JSDoc for new usage reset selectors

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

* refactor(mobile): per-bar reset countdown instead of combined line

Drop the redundant "5h/7d" prefixes — each countdown now renders under
its own bar ("Resets in 3h 54m"), matching the desktop tooltip copy
exactly.

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

* Extract shared reset-countdown formatter for desktop and mobile

- Move duration/countdown formatting out of tooltip.tsx into
  src/shared/rate-limit-reset-format.ts so mobile's account-usage-state
  can reuse it instead of a duplicated copy (with tests).
- Re-export formatResetCountdown from tooltip.tsx to avoid touching
  existing import paths.
- Resend the pairing deep link once more in start-emulator.mjs since
  the first can arrive before the Expo app's JS router is ready.

---------

Co-authored-by: kaynan <kaynan.camargo@terceiro-sky.com.br>
Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
2026-07-13 18:56:36 -07:00
JinjingandOrca 1c6098c214 fix(terminal-tabs): invalidate activity-status cache on freshness update (#8641)
The store bumps agentStatusEpoch at the 30m stale boundary without
replacing the agentStatusByPaneKey map. Keying the flag cache on map
reference alone kept serving stale flags (old timestamp, abandoned
spinning), while the sidebar — which keys on agentStatusEpoch — correctly
de-spun. Invalidate on either changing.

Co-authored-by: Orca <help@stably.ai>
2026-07-13 18:52:09 -07:00
67fdb1c2b9 Show agent status in terminal tabs (#8142)
* Show agent status in terminal tabs

* Resolve terminal tab activity via the canonical worktree-status engine

The first pass hand-rolled resolveTerminalTabAgentActivityState, a fourth
parallel copy of the pane-iteration/freshness/title-heuristic loop that already
lives in smart-attention.ts and worktree-agent-activity-summary.ts. It diverged
from every existing surface (novel blocked>waiting split, a phantom 'interrupted'
red state the sidebar treats as idle) and re-scanned the global agentStatusByPaneKey
map per tab per store write (O(tabs*agents)).

Replace it with resolveTerminalTabActivityStatus, which reuses resolveWorktreeStatus
(the WorktreeCard resolver: freshness gate, live-PTY liveness, per-leaf title dedup,
permission>working>done priority) over a per-tab flag summary bucketed once per store
snapshot (O(tabs+agents)). Tabs now speak the same WorktreeStatus vocabulary as the
sidebar, so their live states can't disagree with the worktree card.

- Map WorktreeStatus -> AgentStateDot: working=spinner, permission=amber, done=check;
  active/inactive fall through to the agent/shell identity icon.
- Parse legacy numeric pane keys too, matching the sidebar summary, so restored/
  imported sessions light the tab.
- Drop the bespoke resolver + its tests; add focused coverage for the new resolver
  and the leading-icon component.

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

---------

Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
Co-authored-by: Orca <help@stably.ai>
2026-07-13 18:32:08 -07:00
9b092fd129 fix(orchestration): compare stale-dispatch timestamps with julianday (#8636)
getStaleDispatches compared space-format datetime('now') columns
(dispatched_at, last_heartbeat_at) against an ISO-Z threshold using raw
TEXT ordering. The space (0x20) sorts below 'T' (0x54) at index 10, so
every fresh same-UTC-date dispatch/heartbeat was ordered below the
threshold and wrongly flagged stale, producing a false stale warning on
each coordinator tick. Wrap both column comparisons and both bound
threshold params in julianday(), which parses the space and ISO-Z
formats as UTC for a correct numeric comparison.

Fixes #8452


(cherry picked from commit 1d40312bf2)

Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
Co-authored-by: Orca <help@stably.ai>
2026-07-13 18:30:52 -07:00
NeilandOrca 2266c1b03a fix(sidebar): scope project display labels by execution host to prevent cross-host path collision (#8345) (#8511)
* fix(sidebar): scope project display labels by execution host to prevent cross-host path collision (#8345)

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

* review: harden #8345 fix — pin cross-host key separateness for identical path+name

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

---------

Co-authored-by: Orca <help@stably.ai>
2026-07-13 18:16:33 -07:00
NeilandOrca b667b72eb2 Route diffs by explicit worktree owner, not focused runtime (#8484) (#8509)
Co-authored-by: Orca <help@stably.ai>
2026-07-13 18:15:56 -07:00
NeilandOrca f96fa32ad8 fix(codex): debounce auto-resolved 'Approve for me' approval notifications (#8387) (#8519)
* fix(codex): debounce auto-resolved approval notifications (#8387)

Codex fires its PermissionRequest hook at the human-input boundary before
the approval decision, so under 'Approve for me' the review agent approves
and Codex resumes within ~1s. The completion coordinator dispatched the OS
attention notification immediately on that 'waiting'/'blocked' hook, so the
self-resolving pause raised a false 'approval required' notification.

Debounce the Codex OS attention notification behind a 1500ms quiet window
and cancel it when a working/completion hook lands inside the window. Scoped
strictly to agentType==='codex'; every other agent's pause still notifies
immediately. The visual sidebar status is set upstream (store.setAgentStatus)
before the coordinator runs, so it stays immediate.

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

* review: harden #8387 fix — protect debounce evidence + cancel on title resume

Two adversarial-review hardening fixes to the Codex attention debounce, both
preserving the fail-open contract (a genuine 'needs input' pause must always
notify):

- handleProcessInspectionResult: extend the evidence-teardown guard to also
  hold while a pendingCodexAttentionTimer is armed. A transient null/shell
  foreground blip (or a remote/SSH inspection that returns null foreground)
  could otherwise tear down agent evidence mid-window and make the timer's
  hasAgentRunEvidence guard silently drop a genuine pause banner — or fire a
  false process-exit completion racing the pause. Mirrors the pendingHookDone
  guard.
- recordTitleWorking: cancel the debounced attention on a genuine title-driven
  resume (placed after the replay guard, so a stale post-completion title
  replay never drops a still-pending genuine pause banner).

Adds 6 discriminating fake-timer tests (guard, title resume, blocked parity,
done cancels attention, dispose clears timer, second distinct pause re-arms).

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

---------

Co-authored-by: Orca <help@stably.ai>
2026-07-13 18:14:50 -07:00
NeilandOrca 5886ffab63 Fix orchestration skill coverage for provider-home skill roots (#8256) (#8510)
Co-authored-by: Orca <help@stably.ai>
2026-07-13 18:13:36 -07:00
Brennan Benson 2f660a6028 fix(source-control): route GitHub Enterprise Server remotes to the GitHub provider for PR creation (#8312) (#8603)
* fix(source-control): route GHES remotes to the GitHub provider for PR creation

A GitHub Enterprise Server user could not submit a PR — Orca demanded
ORCA_GITEA_TOKEN — while issue sync worked fine (#8312).

Root cause: GitHub owner/repo resolution (parseGitHubOwnerRepo) hard-rejects
any host that is not literally github.com. A GHES remote lives on a custom
host, so GitHub's forge resolveRepository returned null and provider detection
fell through the list to Gitea, whose KNOWN_NON_GITEA_HOSTS denylist cannot
enumerate arbitrary GHES domains. Issue sync was unaffected because gh
issue/pr list run with cwd=repoPath and let gh resolve the GHES host natively.

Fix mirrors GitLab self-hosted detection (getGlabKnownHosts): a new
getEnterpriseGitHubRepoSlug resolves a custom-host origin to owner/repo only
when gh is authenticated to that host — gh only ever manages GitHub/GHES
credentials, so a logged-in host is definitively GitHub. Wired into:
- forge-provider GitHub resolveRepository (fallback after github.com miss),
  so detection claims GHES before Gitea is consulted;
- createGitHubPullRequest owner/repo resolution;
- isGitHubAuthenticated, which now probes the repo's real host instead of a
  hardcoded --hostname github.com.

github.com repos keep the cached getRepoSlug fast path and never spawn the
extra gh auth probe.

* fix(github): host-qualify GHES gh commands and probe auth in the repo runtime

Addresses two correctness issues found in review of the #8312 fix.

1. GHES host was discarded before `gh pr create`. `--repo owner/repo` shorthand
   resolves against gh's default host (usually github.com), so for a user
   authed to both github.com and GHES it could target a same-named github.com
   repo or fail — deterministic for SSH repos, which run gh with no cwd. Now
   `createGitHubPullRequest` and the `findOpenPRByHeadBase` fallback pass a
   host-qualified `HOST/owner/repo` for GHES (github.com keeps the shorthand).
   Also generalize `parseCreatePRPayload`'s URL regex off github.com so a GHES
   PR URL parses directly instead of limping through the list fallback.

2. GHES auth was probed on the wrong gh runtime. `getAuthenticatedGitHubHosts`
   ran a global `gh auth status` with no cwd/WSL/SSH context and cached every
   runtime under one "local" key, so a GHES login present only in the repo's
   WSL distro was missed and the repo fell back to Gitea. Replaced with
   `isGitHubHostAuthenticated`, which runs `gh auth status --hostname <host>`
   with the repository's execution options (cwd/WSL distro, or SSH-local like
   the create path) and caches per runtime+host — mirroring GitLab's
   isGlabConfiguredForRemoteHost. This also honors GH_ENTERPRISE_TOKEN inferred
   from repo context. Spawn failures stay indeterminate (uncached).

Adds createGitHubPullRequest-level tests asserting the actual gh `--repo`
arguments (create + fallback) and the WSL/SSH runtime of the auth probe.

* perf(source-control): drop redundant GHES gh auth probe in eligibility

Review follow-up. Detection only routes a GHES remote to the GitHub provider
after getEnterpriseGitHubRepoSlug has confirmed gh is authenticated to its
host, so isGitHubAuthenticated can trust a non-null slug as authenticated and
skip a second, rate-limited `gh auth status` spawn per eligibility poll.
Reaching the github.com probe now implies the remote is github.com. Tests
assert the enterprise path fires no redundant gh probe.
2026-07-13 18:06:06 -07:00
NeilandOrca 73d83a9fb4 fix(cli): stop ELECTRON_RUN_AS_NODE leaking into orca claude-teams child (#8513)
Co-authored-by: Orca <help@stably.ai>
2026-07-13 17:56:22 -07:00
NeilandOrca 1450db85ba fix(grok): don't tell users to re-run grok login on refreshable token expiry (#8508)
Co-authored-by: Orca <help@stably.ai>
2026-07-13 17:55:47 -07:00
moseoh 6c489a4379 fix(source-control): keep push-signal status refreshes alive while the huge flag is set (#8570)
Once git status hit the 10,000-entry limit, the huge flag disabled every
status-refresh lane — including the push signals (repo metadata watcher,
terminal command-finished) that carry the evidence needed to clear it.
The flag only clears when a fresh non-huge status result arrives, so the
worktree deadlocked into stale Source Control and explorer badges until
an app restart.

Split the gate: evidence-free interval polling stays paused while huge
(preserving the #7983 idle-CPU fix), but push-signal refreshes now ride
the coalesced change-signal lane, so a commit made in the integrated
terminal clears the flag and resumes normal polling. A visibilitychange
listener (active only while huge pauses polling) catches up signals
dropped behind a hidden window, matching the becoming-visible catch-up
the normal lane already has.
2026-07-13 17:52:25 -07:00
github-actions[bot] 9caebc3df9 Update README downloads badge 2026-07-14 00:50:32 +00:00
NeilandOrca 82573d70ae fix(orchestration): compare stale-dispatch timestamps with julianday (#8514)
Co-authored-by: Orca <help@stably.ai>
2026-07-13 17:46:25 -07:00
Jinjing dbe2481d57 Being able to reply to any comment from bot (#8621)
* Allow replying to any comment in a thread, not just the root

Reply state now tracks a comment id instead of a group id, and reply composers/handlers are threaded through each comment row (root and replies alike) so any comment can receive an inline reply rather than only the thread root.

* Fix reply cancellation clearing the wrong comment's reply box

When multiple comment threads had reply boxes open, cancelling one
reply cleared replyingCommentId unconditionally, closing whichever
box was open instead of the one actually cancelled. onCancelReply now
threads through the specific commentId so cancellation only clears
state if it matches the currently open reply.
2026-07-13 17:37:16 -07:00
1724eef2f0 fix(window): extend startup reveal fallback to Linux so first launch never stays hidden (#8425)
* fix(window): extend startup reveal fallback to Linux so first launch never stays hidden (#8421)

On Linux/X11, ready-to-show can never fire (GPU/driver quirks), leaving the
only BrowserWindow hidden until a second launch triggers the second-instance
reveal path. Reuse the existing bounded Windows fallback timer on Linux; the
handledInitialReadyToShow guard and headless E2E check already make the
reveal idempotent and safe.

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

* docs(window): drop win32-only qualifier from tray-fallback comments

The tray-create fallback comments referenced 'createMainWindow's win32 10s
reveal fallback', but #8421 extends that reveal fallback to Linux too. Since
the tray itself is win32-only (createSystemTray no-ops off win32), naming a
platform in these comments is both stale and misleading. Drop the qualifier;
the surrounding Windows-only tray context already scopes it.

---------

Co-authored-by: kaynan <kaynan.camargo@terceiro-sky.com.br>
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
2026-07-13 17:36:26 -07:00
Jinwoo HongandOrca 83588b43a7 fix(terminal): stop phantom pinned-viewport pins from freezing follow-output (#8625)
Co-authored-by: Orca <help@stably.ai>
2026-07-13 16:36:59 -07:00
Neil 469018ec78 fix(sidebar): decouple agent-list expansion from the child-worktrees toggle (#8527)
Agent-list expansion (compact "N agents" summary + per-parent lineage folds) lived in local useState on WorktreeCardAgents, so the WorktreeCard remount triggered by the child-worktrees toggle (a virtual-row key flip between item and lineage-group) and by virtualizer scroll-recycle wiped it — folding one section reset the other. Lift it into a session-scoped, LRU-bounded per-worktree module cache (useWorktreeAgentExpansionState), mirroring pr-comments-list-selection. Adds unit + integration + e2e regression tests. Reviewed for regressions and performance.
2026-07-13 16:15:44 -07:00
NeilandOrca 398fc79c4f Show markdown front-matter by default in the rich editor and preview (#8623)
Flip the default so the front-matter banner is visible unless the user
explicitly hides it. The per-file visibility map now stores only hide
overrides (false) instead of show overrides: read defaults resolve to
true, the setter writes false / deletes on show, session persistence
preserves the real value, and hydration keeps false entries while
dropping legacy true entries back to the visible default.

Co-authored-by: Orca <help@stably.ai>
2026-07-13 16:13:29 -07:00
833724830f Fix folder workspace Git status path (#8326)
* Fix folder workspace Git status path

* fix(git): resolve folder-workspace path for all local filesystem ops

Extend the folder-workspace suffix stripping beyond git status: every
local Git subprocess cwd, the WSL-context probe, the first-work
branch/folder rename hooks, and the renderer session placeholders now
resolve the synthetic `::workspace:<uuid>` instance id to its backing
folder via splitWorktreeIdForFilesystem. Without this they would spawn
Git (or fs ops) against a nonexistent directory (ENOENT).

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

* Preserve folder workspace rename instance ids

* Read folder workspace suffix from worktree id

---------

Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
Co-authored-by: Orca <help@stably.ai>
2026-07-13 15:47:17 -07:00
f90cd6ebc9 fix(cli): preserve WSL cwd through the Windows bridge (#6965) (#7640)
* fix(cli): preserve WSL cwd through the Windows bridge (#6965)

# Conflicts:
#	src/cli/index.test.ts
#	src/cli/index.ts

* fix(cli): preserve bridge exit codes (#6965)

* fix(cli): harden WSL cwd bridge compatibility

* chore(cli): align cwd tests with main

* fix(cli): repair deleted WSL cwd before path conversion

* chore: preserve main formatting after merge

---------

Co-authored-by: Brennan Benson <brennanbenson@Brennans-MacBook-Pro.local>
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
2026-07-13 15:44:32 -07:00
Brennan BensonandBrennan Benson 53a09afbef feat(mobile): match desktop's Smart workspace source picker exactly (#7985)
* feat(mobile): start a workspace from a branch, issue/PR, or Linear ticket

Unify mobile workspace creation with desktop. The "+" Create Workspace
modal now has a primary "Start from" field that opens a tabbed search
drawer (Branch · GitHub · GitLab · Linear), letting a user start a
workspace from an existing/new git branch, a GitHub issue/PR, a GitLab
issue/MR, or a Linear ticket — in addition to the default blank workspace.

No new backend is required: the search RPCs (github.listWorkItems,
gitlab.listWorkItems, linear.searchIssues/listIssues, repo.searchRefs) and
the worktree.create linked-item params were already used by the mobile
Tasks screen. This surfaces them in the create flow, reusing the existing
pure modules (buildTaskWorkspaceCreateParams, shouldResolveHostedReviewStartPoint,
filterAvailableTaskProviders).

Details:
- New pure modules: workspace-source-selection, use-workspace-source-search,
  source-workspace-create, worktree-create-retry, blank-workspace-create
  (the blank/retry path extracted from the modal for reuse + line budget).
- New UI: WorkspaceSourcePickerDrawer (+ row) and SetupHookTrustDrawer
  (extracted from the modal).
- Older paired desktops (missing the mobile.tasks.v1 capability) degrade to
  Branch + Blank only; GitLab/Linear tabs appear only when available.
- GitHub/GitLab sources pin their repo; switching repos resets the source.
  PR/MR sources resolve their base branch at create time; SSH repos gate
  search until connected (Linear search is repo/SSH-independent).

* fix(mobile): hydrate settings/trust before availability probes settle

Review fixes for #7985: setTrustedOrcaHooks/setRuntimeSettings no longer
wait on status.get/preflight.check/linear.status (a first-open
preflight.check can take seconds, widening the spurious setup-trust
re-prompt window). Also adds param-parity tests for createBlankWorkspace
and a GitLab MR base-resolve test.

* feat(mobile): match desktop's Smart source picker exactly

Rework the mobile create-workspace source picker to be a faithful port of
desktop's Smart picker instead of the earlier divergent "Start from" drawer.

The mobile field is now the workspace-name input AND the source search, with the
exact desktop tabs — Smart · GitHub · Linear · GitLab · Branch · Name. "Smart"
fans out across GitHub + GitLab + Linear + branches, prepends a "Use '<name>'"
row, and resolves pasted URLs / #123 / STA-42 to exact items (with a cross-repo
switch prompt). Selecting a source shows a pill and moves the editable name into
Advanced. The invented "Blank workspace" concept is removed — the neutral state
is just a typed/empty name (blank submit still yields a creature name).

DRY: the pure desktop logic (smart-workspace-source-results, -command-value,
github-links, gitlab-links, work-item-link-query-bounds, github-work-item-identity)
moves to src/shared/new-workspace/ with re-export shims at the old renderer paths,
so both renderer and mobile share one implementation. composer-branch-selection
and workspace-name were already shared and are reused directly.

Two read-only lookup RPCs are allowlisted for mobile so pasted GitLab URLs and
cross-repo GitHub URLs resolve to exact items (github.workItemByOwnerRepo,
gitlab.workItemByPath).

New mobile modules are split for max-lines: use-mobile-composer-source (selection
state + desktop-parity handlers, PR/MR base resolve), use-smart-workspace-source
+ smart-source-fan-out/-search-requests/-paste-intent (RPC orchestration),
composer-linked-work-item / work-item-lookup-text / mobile-smart-source-modes
(pure logic), and SmartWorkspaceSourceField/Drawer/Row + SmartWorkspaceAdvancedFields.
Replaces WorkspaceSourcePickerDrawer/Row, workspace-source-selection,
use-workspace-source-search, and MobileWorkspaceNameInput.

Reviewed by three adversarial agents + re-reviewed after fixes: GitHub search now
returns issues AND PRs (not issues-only), Linear defaults to assigned, create-branch
preserves slashy names, cross-repo PR base resolves against the item's own repo,
displayName is suppressed for user-edited names, and the smart-mode GitHub fan-out
respects availability. tsc/oxlint/max-lines-ratchet clean; 1328 mobile tests pass.

* fix(mobile): keep smart source drawer fully visible

* refactor: share workspace creation behavior across clients

* fix: address workspace creation review findings

---------

Co-authored-by: Brennan Benson <brennanbenson@Brennans-MacBook-Pro.local>
2026-07-13 15:38:02 -07:00
Jinjing 280103aa04 Allow replying to any comment in a thread, not just the root (#8562)
Reply state now tracks a comment id instead of a group id, and reply composers/handlers are threaded through each comment row (root and replies alike) so any comment can receive an inline reply rather than only the thread root.
2026-07-13 15:20:43 -07:00
github-actions[bot] 2ebe207bf7 release: v1.4.139-rc.1 v1.4.139-rc.1 2026-07-13 21:18:54 +00:00
Brennan Benson 65c65c37e8 fix(terminal): stop stale PTY resize after worktree reveal (#8502)
* fix(terminal): stop stale PTY resize after worktree reveal

The visibility-resume size readback captures xterm's pre-reveal grid as
its resize target while the applied-size read is in flight. A reveal fit
or snapshot-restore resize can change the grid mid-flight without
queuing a newer request, so the resolved callback "repaired" the PTY
back to the pre-reveal grid — an idle TUI (Claude Code) then redraws
for the wrong grid until a manual resize (refs #7951, #7240).
Instrumented traces show the stale capture on every reveal; correctness
relied on the read winning FIFO against the fit's resize, which busy
daemons and SSH/relay round-trips lose.

Re-measure xterm at resolve time and re-run against the fresh grid
instead of forwarding a stale target. Adds an e2e seam that delays the
readback dispatch to reproduce the losing ordering, unit repros that
fail without the guard, and a hidden-resize/reveal-cycle e2e spec.

* test(terminal): cover single-flight convergence under an oscillating grid

* docs(e2e): note the WINCH bar is illustrative, not a Windows ground truth

The reveal repro asserts on pty:getSize convergence; the bottom-bar TUI
only makes the pane WINCH-reactive. Record that a long-lived process can
miss Node's stdout 'resize' event under Windows ConPTY even when the OS
PTY was resized, so future readers don't add a flaky bar-content check.
2026-07-13 14:17:49 -07:00
github-actions[bot] ebc9a0a358 release: v1.4.139-rc.0 v1.4.139-rc.0 2026-07-13 20:28:57 +00:00
Jinjing 5be44d1553 fix(native-chat): target native chat to the active split leaf (#8569)
* fix(native-chat): target native chat to the active split leaf

* fix(native-chat): harden split leaf reassignment

* fix(native-chat): tolerate missing layout state
2026-07-13 13:07:52 -07:00
Jinjing 39b2ec832e fix(native-chat): scope composer sends to the active turn (#8568)
* fix(native-chat): scope composer sends to the active turn

* fix(native-chat): close pending send lifecycle gaps

* fix(native-chat): deduplicate pending message derivation
2026-07-13 13:07:25 -07:00
Jinjing 476b6f97d0 fix(native-chat): preserve initial chat mode on paired-host launches (#8567)
* fix(native-chat): preserve initial chat mode on paired-host launches

* fix(native-chat): keep paired launch mode authoritative

* fix(native-chat): preserve mode through PTY materialization
2026-07-13 13:06:59 -07:00
Brennan Benson fc458f2b30 Remove finished and killed claude subagents from the sidebar roster (#8522)
* Remove finished and killed claude subagents from the sidebar roster

Finished one-shot subagents stayed in the pane roster as permanent idle
rows (dozens per ultracode/workflow session), and a subagent killed
without its SubagentStop hook stayed 'working' forever, pinning the pane
working. SubagentStop now removes one-shot entries (teammates only idle:
they are alive and resumable), and a lead Stop's present background_tasks
list is treated as authoritative for non-teammates - unlisted entries are
removed. Teammates are identified by their name-embedding agent ids
(a<name>-<hex>, verified against live hook captures).

* Fix Claude subagent roster reconciliation

* Drop named workflow lanes that share the teammate id shape

Workflow/named one-shot agents report name-embedding lifecycle ids
(afinder-C-<hex>, agent_type = the label), indistinguishable by shape
from resumable teammates - so the roster retained them as idle rows
(observed live: a 32-row 12h-old pile). Unlike teammates they ARE
listed id-exact as subagent-typed background tasks, including inside
their own SubagentStop payload. Use that corroboration to remove them
on stop, reclassify task-listed teammate-shaped entries as one-shots,
and reap teammate-shaped leftovers when a complete inventory lists no
teammate-typed task at all (a teams session always lists its teammates,
even idle ones).
2026-07-13 12:47:17 -07:00
Kaynan Sampaio de Camargoandkaynan dc4fb2aa03 fix(native-chat): retry not-yet-flushed transcripts instead of settling into a permanent error (#8418)
* fix(native-chat): retry not-yet-flushed transcripts instead of settling into a permanent error

A freshly-created session's transcript .jsonl lands on disk seconds to
minutes after the process starts. Native chat's one-shot read raced that
first flush: a miss became a permanent "No transcript found" error and
the live-tail subscription silently degraded to a no-op, so the pane
never recovered even after the file appeared.

- transcript-reader/read-cache: mark the miss with notFound so callers
  can tell "not flushed yet" from a real parse/IO error (never cached).
- transcript-watch: poll resolve+install (500ms backoff, 5s cap) for the
  subscription's lifetime instead of returning a dead no-op watcher.
- use-native-chat-live-session: retry a notFound read with backoff for
  up to 60s while staying in the loading state, and let live appends
  render over a stale initial-read error.

Fixes #8401

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

* fix(native-chat): address CodeRabbit review — ENOENT retryable, content over spinner, blank-id guard, unref poll timer

- transcript-reader: an ENOENT after a successful resolve is the same
  first-flush/rotation race as an unresolved path — mark it notFound.
- use-native-chat-live-session: live appends landing mid-retry render
  instead of the loading state (mirrors the stale-error gate).
- transcript-watch: bail out for a blank session id with no explicit
  file (nothing to resolve-poll), and unref the poll timer so headless
  serve shutdown is never held open by an unresolvable session.

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

---------

Co-authored-by: kaynan <kaynan.camargo@terceiro-sky.com.br>
2026-07-13 12:45:46 -07:00
Jinjing 527c692b71 Improve git pull on remote (#8524)
* Fall back to a merge when a divergent pull has no reconciliation strateg

- Git 2.27+ refuses `git pull` on divergent branches unless pull.rebase or
  pull.ff is configured. Retry with `--no-rebase` (Git's historical default)
  so pulls succeed out of the box on fresh hosts.
- Skip the fallback whenever the caller already specified a reconciliation
  strategy (e.g. --ff-only, --rebase) so explicit policies still fail as
  expected on divergence.
- Applied identically in the local git pull path and the relay/SSH git
  handler so both surfaces behave the same way.

* Refactor divergent-pull merge fallback into shared helper

Extracts the retry-as-merge logic (duplicated between local git and
relay SSH pull paths) into `runPullWithDivergenceFallback` in
git-remote-error.ts, so both callers share one implementation and
test coverage.
2026-07-13 12:42:55 -07:00
Brennan Benson 9a2379211e Search worktrees by cached review metadata (#8505)
* feat(renderer): search worktrees by review metadata

* fix(renderer): scope review search by execution host
2026-07-13 11:23:28 -07:00
fsdwen 7b3103f890 fix(i18n): correct 'run' translations from '跑步' to '执行' in zh.json (#8554)
Fixes #8553

'跑步' (jogging) was used in computing/automation contexts where
the correct term is '执行' (execute). 11 entries fixed across:
- TabBarQuickCommandsButton: Run
- Scripts settings: When to run
- quick.commands.search: run (keyword tag)
- ReviewPRViewAnimatedVisual: Running
- AutomationRunHistory: Run (column header)
- AutomationRunPageFrame: Back to runs
- AutomationsPage: Runs, run
- ExternalAutomationManagers: run
- ExternalAutomationRunTable: Runs, run

Note: key e66091daf4 already correctly translated as '运行'.
2026-07-13 06:50:18 -07:00