Commit Graph

6723 Commits

Author SHA1 Message Date
BingZ 8461df2139
Fix Ctrl+Alt terminal input on Windows by repairing xterm's AltGr misclassification (#8810)
* fix(terminal): forward Windows Ctrl Alt chords

* fix(terminal): route rescued Windows Ctrl+Alt chords through xterm's own key encoders

Replace the hand-rolled Alt-prefix encoder in the custom key handler with a
narrow repair of xterm's third-level-shift classification. xterm's keyboard
service already computes the correct bytes for every input protocol (legacy
ESC-prefixed, kitty CSI-u, win32-input-mode) before _isThirdLevelShift
discards them on Windows Ctrl+Alt; rescuing only provably-genuine chords
(Chromium's layout-wide AltGraph simulation, crbug 762557) lets those
encoders deliver protocol-correct, layout-aware bytes with no duplicated
encoding knowledge in Orca.

Fixes vs the previous approach: kitty-mode TUIs now receive CSI-u instead of
legacy bytes, digits/punctuation no longer alias to plain Alt chords,
letters follow the logical layout (Dvorak/Colemak), Ctrl+Alt+Shift and
Ctrl+Alt+F-keys gain Linux parity, and handled keys get xterm's stock
preventDefault/stopPropagation. Firefox web clients keep stock behavior; a
real-Terminal contract test fails loudly if an xterm upgrade removes the
seam, degrading at runtime to the historical dropped-chord behavior.

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

* Refactor Windows Ctrl+Alt chord test helpers and clarify AltGraph commen

- Extract a shared getCore() helper in the test file to dedupe repeated
  `_core` casts across third-level-shift and keyboard-service lookups.
- Correct the AltGraph comment: Chromium simulates AltGraph per composing
  keypress, not for the whole Ctrl+Alt press duration.
- Warn via console when xterm no longer exposes `_core._isThirdLevelShift`,
  so a silent classification-repair failure is diagnosable in the wild.

* Clarify comment explaining why Windows Ctrl+Alt chords bypass AltGraph c

The comment previously implied Chromium always sets AltGraph=true for
composable chords; the revised wording states the actual mechanism
(Alt+Ctrl modifiers get replaced by AltGraph) so the inverse case is
unambiguous.

---------

Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
Co-authored-by: Orca <help@stably.ai>
2026-07-16 16:38:20 -07:00
Brennan Benson dd0f4c39c8
fix(pi): stop OMP tab icon flashing to Pi while working (#9077)
OMP runs as a `shell → omp → pi` process tree and Orca recognizes both
`omp` and `pi` as distinct agents, so the foreground-process reader
alternates between reporting the two across command boundaries. In
`resolveTabAgentFromSignals` every identity signal is re-owned onto the
pane's durable owner within its title-identity group (pi and omp share
one) — except the process signal, which was consumed raw. Because it
ranks above launchAgent, each `pi` read repainted an OMP-owned tab's icon
to the Pi glyph and the next read flipped it back.

Re-own the process signal the same way the hook and title signals already
are, so a same-group read collapses onto the owner while a genuine
cross-group process (e.g. Codex) still stands and can reclaim a reused
pane. The tab title text was unaffected — its owner resolution already
excludes the process signal — so this is icon-only.

Adds regression coverage for the oscillation, the mirrored/restored pane
(launchAgent gone, durable hook record), and the cross-group scope guard.
2026-07-16 15:18:58 -07:00
github-actions[bot] de9e9f8f1b release: v1.4.144-rc.2 2026-07-16 22:01:28 +00:00
Brennan Benson 0702e3dd4c
Fix paired-host manual project header ordering (#8919)
* Fix paired-host project header reordering

* fix(sidebar): keep unchanged paired project drops inert
2026-07-16 15:00:09 -07:00
Brennan Benson 09173d02d2
fix(terminal): restore clickable links after returning to a worktree (#9061)
* fix(terminal): restore clickable links after returning to a worktree

Clicking a terminal link (file path, URL, term_* handle, OSC-8) does nothing after switching to another worktree and back, until the terminal is scrolled a little.

xterm's linkifier only re-runs link providers on mousemove when the hovered buffer cell changes vs its cached `_lastBufferCell`. Hiding the terminal surface fires `mouseleave`, which clears the current link but leaves that cache. On reveal the pointer returns to the same cell, so the mousemove handler short-circuits and the link is never re-established; a scroll shifts the buffer position and re-linkifies, which is the "scroll to fix" symptom.

Reset the linkifier hover-cell cache when a terminal surface is revealed (resumeTerminalVisibility) so the next mousemove re-evaluates providers. Covers all link types, including file-path links whose geometry click fallback does not compensate after reveal.

Verified in real Electron via a new e2e that fails without the reset and passes with it, for both a file-path link and a URL.

* test(terminal): prove restored links activate after reveal
2026-07-16 14:48:06 -07:00
Brennan Benson 68fca0b076
Add safe skill freshness detection and update rail (#8637)
* Add safe skill freshness detection

* Accept observed copy-mode rail outcomes

* chore(skills): regenerate snapshot artifacts for the merged guide content

The rebase onto main picked up the reviewed guide fixes (#8624), so the
current manifest hashes and a new appended snapshot generation must
match those bytes; the registry keeps all prior snapshots so existing
installs classify as outdated rather than unrecognized.

* fix(skills): canonicalize snapshot file order and guard released history

Historical snapshots kept git ls-tree byte-order while the working-tree
walk and runtime observation use the sorted depth-first order, so any
future multi-file skill would misclassify older installs as unrecognized
and churn spurious registry revisions; all producers now share one
canonical order (no digest changes for today's single-file packages).
Also rejects executable files from shipped skills (Windows observation
cannot see execute bits, which would misclassify pristine Windows
installs) and adds an explicit append-only invariant for released
snapshots so a generation-logic change cannot rewrite them silently.

* fix(skills): throttle focus rescans and correct self-blocked placement copy

Every window focus re-read and re-hashed all installed packages, and the
nudge and panel each forced their own trailing rescan for one event; a
15s cooldown plus a shared invalidation latch keep one bounded scan per
event while install-change events stay immediate. Bundle artifacts are
now loaded once per run instead of re-parsed on every scan. A read-only
or otherwise unsupported outdated placement now explains that it blocks
itself instead of blaming a phantom sibling placement; the supported
topology set moved to shared so eligibility and copy cannot drift.

* feat(skills): move freshness surfacing to a lingering toast and update modal

The Skills page has been unreachable since its toolbox menu entry was
removed (#4535), so surfacing freshness there buried the feature behind
its own nudge. The nudge now lingers until acted on (ignoring it records
nothing; only the explicit close persists dismissal keys) and opens an
update modal hosting the pre-filled editable terminal, an honest
current/blocked summary, and the per-placement rows in a collapsed
Details section. A compact 'Check for skill updates' row in CLI settings
is the manual re-entry point. Skills page restored to main; design-doc
surfacing section records the venue decision.

* fix(skills): mount update dialog inside the link-routing provider and fold freshness into the setup rails

The dialog hosts a live terminal pane that requires the link-routing
preference context; mounted outside the provider it crashed the renderer
the moment an eligible update existed (caught by live QA — unit tests
mock the terminal). It now mounts inside the provider behind its own
recoverable boundary.

The separate 'Check for skill updates' settings button is gone: the
setup rails' own pill now carries freshness (Update available / Up to
date, falling back to Installed for blocked or unrecognized copies and
for non-local runtimes the local-only scan cannot vouch for), and
Re-check refreshes both installation detection and the freshness
inventory. Wired for the CLI, Orchestration, Computer Use, and
Per-Workspace Environments rails.

* fix(skills): use the sleek scrollbar style in the update dialog

* chore(skills): regenerate manifest for merged main (v1.4.142-rc.1)

Main advanced to 1.4.142-rc.1 with a v1.4.141 release, so the embedded
appVersion and release mapping were stale on the PR's merged tree. Only
appVersion and the new release entry change; no snapshot digests move
(released history preserved).

* fix(skills): bound and batch freshness work

* fix(skills): harden freshness integrity checks

* fix(skills): accept observed copy topology outcomes

* chore(skills): regenerate manifest for current main

* fix(skills): preserve update terminal lifecycle

* chore(skills): regenerate manifest for current main

* fix(skills): fail closed on stale freshness scans

* chore(skills): regenerate manifest for current main

* fix(skills): preserve freshness safety under focus churn

* feat(skills): group the update modal by skill with plain-language status

The Update skills modal now lists only skills that will update or that can't
(with why), grouped by skill with their install locations nested underneath —
no more one row per placement.

- Statuses collapse to "Update available" / "Can't update" at the skill level.
- A location's problem is a chip (Duplicate, Unrecognized, Inaccessible, Read
  only, In a repo, External/Broken link, Plugin cache) with a hover tooltip
  that explains what it means for the user and what to do.
- Up-to-date, unrecognized-only, and unreadable-only skills are hidden; a
  current/unrecognized/etc. location only appears when it explains a shown skill.
- Copy is de-jargoned (drops "copy"/"placement"/"snapshot"/"official copy") and
  names the mechanism as the npx skills update command, not "Orca's update".
- Rename the section to "Update details"; drop the unreachable newer-known state.

Renderer-only: derivation is a pure module (groupSkillFreshness) with unit tests;
no IPC or main-process change. Locales updated for all five languages.

* chore(skills): regenerate manifest for current main (v1.4.143-rc.0)

* feat(skills): don't let a duplicate block the update; clearer skipped copy

- Eligibility: a clean standalone duplicate no longer poisons the whole
  name — the canonical copy still updates and the duplicate is flagged;
  a duplicate-only skill stays unoffered.
- Update modal: "Can't update" -> "Skipped" with a reason-specific
  sentence (edited/read-only/in-a-repo/plugin/link); chips describe only
  the location state; footer "Check now" -> "Re-check".
- Settings sidebar nav pills go amber "Update available" when a skill is
  updatable, matching the setup cards.
- Localized new strings across en/es/ja/ko/zh.

* chore(skills): regenerate manifest for merged main (v1.4.144-rc.1)
2026-07-16 14:47:28 -07:00
Jinwoo Hong f102972cc1
fix(emulator): ignore external serve-sim helpers (#9071)
* fix(emulator): ignore external serve-sim helpers

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

* docs(reliability): align backpressure evidence

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

---------

Co-authored-by: Orca <help@stably.ai>
2026-07-16 14:44:55 -07:00
Maxim Syabro fc6570da67
fix(pi): report completion on agent_settled so intermediate agent_end stays silent (#8035) (#8826)
Pi's agent_end fires per low-level run — auto-retry, auto-compaction and
queued follow-ups all emit it while the agent is still working — so Orca
fired premature completion notifications. Pi >= 0.80.4 emits
agent_settled exactly once when nothing is left to run.

The extension now subscribes to both events: the first agent_settled
proves runtime support and mutes agent_end reporting from then on; on
older Pi/OMP runtimes (where unknown event names register silently and
never fire) agent_end keeps reporting done exactly as before. The
settled handler reports the existing agent_end hook event, so the
agent-hooks server contract is unchanged.
2026-07-16 14:16:30 -07:00
Jinwoo Hong 4e232a030f
fix(usage): show CLI-owned session recovery steps (#9069)
Co-authored-by: Orca <help@stably.ai>
2026-07-16 14:12:47 -07:00
gsxdsm 64181fdd42
feat(native-chat): native chat view across mobile, desktop, and web (#5824)
* feat(native-chat): add native chat view across mobile

* fix(native-chat): address review findings and CodeRabbit threads

Correctness:
- Restore an independent initial readSession seed and surface initial-drain
  errors as snapshot frames so the chat view can never strand on 'loading'
- Pair mobile tool results to calls by ordinal FIFO (parallel calls no longer
  misgraft results); clear a pending ask only when its own call resolves
- Show a new streaming reply immediately (same-turn suppression, not length)
- Delegate mobile noise filtering to the shared harness-injected classifier
- Admit soft-leaving mobile clients in beginMobileInputFloor (parity with
  mobileTookFloor) so grace-window writes aren't dropped
- Self-heal a stale 'working' status once this turn's reply lands
- Catch RPC rejections in mobile file-open helpers; guard sanitizeToolInput
  key collisions; settle web/runtime transports on unrecognized first frames
  and forward snapshot errors

Perf:
- Throttle the mobile streaming bubble (50ms) so per-part status frames stop
  re-parsing the whole accumulated markdown
- Short-circuit markdown path detection on dot-less or oversized runs
  (quadratic backtracking guard)

UX/minor:
- Wire hold-mode dictation through the native chat composer
- Allow scoped-package (@) paths in file-path detection
- Move caret after mid-text autocomplete insertion; index-prefixed ask option
  keys; single scroll-to-end effect; bounded wait + toast when image attach
  races a resubscribe; count-based pending reconciliation; cache-hit search
  cancels stale debounce; chat-tab toggle wins over in-flight preference load
- Share shouldStepNativeChatAskAnswer between desktop and mobile; import
  block guards/source priority from shared instead of local copies
- Defensive non-positive transcript limits; test strengthening (TTL expiry,
  post-unsubscribe stale frame, lease readiness, filtered console.error)

* refactor(native-chat): share desktop/mobile chat logic in src/shared

Extract the parity-mirrored native-chat modules into shared implementations
both surfaces re-export: ask parsing (registry, parseAskFromStatus,
extractPendingAsk, formatAskAnswer), answer stepping offsets/scheduler, diff
detection/parsing, harness-noise filtering, tool fold/pair/split, and tool
summaries. Removes the hand-synced copies and their stale Metro comments.

Divergence reconciliations take the safer side of each: diffs truncate at
120 lines/32KB everywhere (desktop previously unbounded), tool-run summaries
cap at 3 parts with bounded-depth previews, nameless tool calls are skipped,
and basenames split on both separators.

Also: settle and kill every sibling quick-open pass when one reaches
maxResults (main rg/git and relay git; relay rg already did) so a capped
search cannot leave a scan walking a huge tree; fold window-bounding into
the shared merger's applyAppend; localize the web 'Pair a host' snapshot
error.

* fix(native-chat): address CodeRabbit follow-ups on shared modules

- Attachment lease gate re-checks connection/target/tab after the bounded
  wait, so a tab/host switch or disconnect mid-wait can't send into a stale
  terminal; a moved-away target drops silently like the pre-wait guard and
  only an unrecovered lease surfaces the toast. Adds hook tests.
- extractPendingAsk parses transcript tool-calls through the same
  registered-parser + canonical-shape fallback as live status, so a custom
  question tool that rendered live survives reconnect/replay.
- Direct unit tests for the shared ask parser (FIFO ordering, fallback,
  malformed payloads) and tool-summary bounded preview (depth/collection
  caps, circular refs, basename/command branches).

* fix(native-chat): treat initialLimit 0 as a valid empty window

Both engine guards used truthiness, so an explicit zero limit skipped the
bounded tail reader and fell back to an unbounded incremental read. Latent
only (every caller clamps positive), hardened for consistency with the
tail reader's non-positive-limit handling.

* fix(mobile): native-chat composer lock UX + send-failure feedback

- Distinguish input-lock reasons: transport 'disconnected' shows Reconnecting…
  instead of mislabeling a reconnect as locked-by-another-client
- Guard the composer lock behind a 600ms hold so connState blips / lease
  hand-offs don't flicker the placeholder; unlock stays instant
- Surface a rejected send inline above the composer (a bottom toast hides
  behind the keyboard); auto-dismisses after 4s
- waiting-session hint invites the first message instead of implying the
  agent is still starting

* test(mobile): sync answer-send pacing test to the 500ms advance buffer

Missed in merge 8fe3c391c, which carried main's NATIVE_CHAT_ADVANCE_BUFFER_MS
300->500 (#8568) into the shared stepping module that mobile derives from.

* fix(mobile): restore terminal stream after chat cold start

* fix(native-chat): harden retries, optimistic sends, and file scans

* fix(mobile): deliver AskUserQuestion answers by option number (STA-1860)

Port #8840's fix to the mobile native chat: the Ask card now tracks
per-question option INDICES (+ free text) and the answer-send hook drives
Claude's arrow-navigate selector with buildAskAnswerKeys keystroke groups —
option numbers, next-tab arrows, Enter — paced one selector step apart, instead
of pasting label text that the selector ignores (which silently committed the
default option). Non-Claude agents keep the pasted-label path via the
selection-based formatAskAnswer.

Backcompat: keystrokes are built client-side and written through the EXISTING
terminal.send passthrough with enter:false — the same contract the permission
card already uses — so an older desktop runtime (SSH/relay included) replays
them verbatim; no RPC/contract change in either update order. Free text is
newline-sanitized because terminal.send has no paste framing.

Drops the now-unused formatCompleteAskAnswer from the shared module.

* fix native chat send and runtime races

* fix mobile native chat formatting

* fix(native-chat): mobile empty state matches desktop copy

Mobile showed a single generic line ('Send a message to get started') where
desktop shows a titled two-line empty state naming the agent ('Start a chat with
Claude' + 'Ask Claude to inspect code, explain output, or make a change.'). Align
them from one source of truth so they can't drift again:

- Extract the agent-type label map + formatAgentTypeLabel to
  src/shared/agent-type-label.ts (desktop re-exports; mobile imports).
- Add src/shared/native-chat-empty-state.ts with the canonical English copy;
  desktop uses it as its i18n fallbacks (localization unchanged — en/es/ja/ko/zh
  keys still win), mobile substitutes the agent label and renders it directly
  (mobile ships English only).
- Mobile: render title + subtitle for waiting-session AND ready-but-empty (both
  are 'start a chat'), error copy for errors; keep the loading spinner.

Live-verified on the iOS sim against a pn-dev of this branch. typecheck node/web
+ mobile tsc clean; 30 mobile + 428 desktop/shared native-chat tests green.

* style: oxfmt the empty-state parity test (line wrap)

---------

Co-authored-by: Brennan Benson <brennanbenson@Brennans-MacBook-Pro.local>
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
2026-07-16 13:26:15 -07:00
github-actions[bot] 9716dbd45f release: v1.4.144-rc.1 2026-07-16 20:08:45 +00:00
Jinwoo Hong e8a1674a75
fix(terminal): keep divider resize active after capture loss (#9063) 2026-07-16 13:07:44 -07:00
Jinjing 1536171fdb
Add a native macOS menu bar status item with activity indicator (#9042)
* Add native macOS menu bar status item with settings toggle

Extend the Windows-only system tray into a shared status-item module
so macOS gets a template menu bar icon (Open/Settings/Check for
Updates/Quit), a theme-aware attention dot, and a "Show Menu Bar
Icon" appearance setting. Also fixes a startup race where a tray
"Settings…" click could fire before the renderer's ui:openSettings
listener attached, by queuing a one-shot pending-open-settings intent
the renderer consumes on mount.

* Fix Retina blur, race conditions, and menu-label duplication in tray Set

- Rebuild the tray attention icon's @2x representation since toBitmap only
  read 1x pixels, blurring the glyph on Retina displays
- Fix premultiplied-alpha math so light-glyph tinting uses per-pixel alpha
  instead of a flat 0xff, keeping antialiased edges valid
- Always push ui:openSettings and leave a longer-lived pending flag, since
  there was no reliable signal that a renderer's listener was attached
- Preserve tray attention state across macOS menu-bar hide/show toggles
  instead of resetting it on tray destroy
- Route macOS tray creation through syncMacMenuBarIcon so startup and the
  live toggle share one visibility policy
- Reuse app-menu translation keys for tray Settings/Check for Updates and
  drop the now-duplicate tray-scoped locale strings

* Make menu bar icon default on and platform-independent in settings

Previously showMenuBarIcon was hardcoded to darwin-only in both the
default settings and the sanitize/load paths, so a profile written on
macOS lost its opt-out when touched from another OS. Store the raw
boolean everywhere and let darwin-specific consumers decide whether
to act on it, so the value round-trips unchanged across platforms.

* Fix Settings menu click being silently dropped after a slow cold rendere

Replace the 60s pendingOpenSettings TTL with an untimed intent, since a
cold renderer start can outrun any fixed timeout and cause the flag to
expire before the click is consumed. webContents-id scoping plus
consume-on-read still prevent the intent from leaking to an unrelated
renderer. Adds a test covering the queued-before-mount pull path.
2026-07-16 13:06:21 -07:00
Jinwoo Hong b8925657a0
fix(terminal): release reattach fit on an unchanged stable grid (#9056) 2026-07-16 12:38:31 -07:00
Jinwoo Hong 6be4e29394
fix(remote): isolate shared control request timeouts (#9016)
Co-authored-by: Orca <help@stably.ai>
2026-07-16 12:21:07 -07:00
github-actions[bot] 92fc79ee66 release: v1.4.144-rc.0 2026-07-16 17:49:44 +00:00
github-actions[bot] ef23b6760c Update README downloads badge 2026-07-16 12:50:02 +00:00
Tim Suchanek 7e743c41c2
fix(ssh): reconnect persisted PTYs after shutdown races (#8403)
* fix(ssh): reconnect persisted PTYs after shutdown races

* fix(ssh): harden shutdown reconnect-target derivation

- exclude runtime-owned (ephemeral-VM) targets: a renderer-driven startup
  ssh.connect would dispose the runtime layer's live relay session
- exclude explicitly disconnected / auth-failed targets so startup never
  dials a host the user chose to leave offline (sessions still restore
  on tab focus via the deferred flow)
- avoid re-running buildTerminalSessionData via the default parameter
  when the remote-session map is legitimately empty (null sentinel)
- recompute activeConnectionIdsAtShutdown alongside terminal-field
  patches so a crash between patches cannot strand a stale target
- pin all of the above with tests, including the patch-writer path

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

* fix(ssh): filter runtime-owned targets at startup reconnect consumption

Main's windowless-promotion path can persist runtime-owned ids into
activeConnectionIdsAtShutdown; renderer must never dial those.

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

* test(ssh): pin runtime-owned exclusion independently of the status gate

Give the runtime-owned fixture a transient status so the status gate
alone cannot exclude it; verified by mutation (guard removal fails).

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

* refactor(session): extract reconnect-target derivation to its own module

workspace-session.ts crossed the max-lines budget; the derivation is a
self-contained concern (session ids + live SSH state -> startup targets).
Parameter is now required (null = none) since every caller has the
terminal session data or must opt into the scan explicitly.

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

* fix(ssh): exclude runtime-owned targets from the connected-states path too

A pane-level optimistic write can stamp a runtime-owned connection state;
the live 'connected' filter must apply the same exclusion as the
session-id union (CodeRabbit review finding). Pinned in the test on both
paths.

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-16 04:17:21 -07:00
Jinwoo Hong 377082e142
fix(ai-vault): discover Antigravity CLI sessions (#8971)
* fix(ai-vault): discover Antigravity CLI sessions

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

* fix(ai-vault): address remote scanner review

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

---------

Co-authored-by: Orca <help@stably.ai>
2026-07-16 02:57:43 -07:00
Jinjing cf02cc46b1 Add missing WeChat QR image referenced by README
The previous commit updated the README to docs/assets/wechat-qr.jpg and
removed the old dual-QR assets, but never added the new image file, so
GitHub could not load it.
2026-07-16 00:43:11 -07:00
Jinjing 3874734dca Update README WeChat section: single group, new QR image
- Replace outdated dual-QR (primary + backup) layout with one QR code, since the first group filled up and members should join the second group instead
- Applies to both README.md and the zh-CN translation
- Remove the now-unused backup/png QR image assets
2026-07-16 00:41:34 -07:00
Jinjing 8c1d13dc52 cleaning up 2026-07-16 00:32:59 -07:00
Jinjing e84bd666f1 Update wechat QR codes and add spacing between them in README
- Refresh star-history chart and backup WeChat QR image assets
- Add spacing between the primary and backup WeChat QR codes in both README.md and README.zh-CN.md
2026-07-16 00:30:30 -07:00
Neil 4c810b79b2
feat(jira): support self-hosted Jira Server/DC with PAT + username/password (#8976)
Adds self-hosted Jira Server/Data Center support (personal access token or classic username + password) alongside Atlassian Cloud, fully addressing the older-instance ask in #6676. Takeover of #7724 (@wquintal's original PAT implementation), brought current with main and hardened via a multi-agent adversarial review.

Fixes #6676.

Co-authored-by: William Quintal <williamquintal95@gmail.com>
2026-07-16 00:26:51 -07:00
Jinjing 1830348dc4
Persist AI Vault view options (sort, group, hidden agents, hide-empty to (#8961)
- Extracts a per-client localStorage persistence layer with normalization/fallback for malformed or catalog-stale stored values
- Adds a hook wrapping the previous local useState wiring so AiVaultPanel keeps the same setter API while reads/writes go through storage
- Scope is intentionally excluded from persistence since it depends on current workspace/project availability
2026-07-15 23:05:07 -07:00
Jinjing 792160e9ec
fix(packaging): stop tracking pr-evidence screenshots and exclude from app.asar (#8681)
The three pr-evidence/*.png files were accidentally committed with the
sidebar fix (#8527). They are e2e evidence outputs regenerated on demand
by worktree-lineage-agent-expansion.spec.ts only under
ORCA_CAPTURE_EVIDENCE=1, and nothing reads them — contradicting the
.gitignore intent that PR evidence screenshots not be committed.

- Remove the tracked PNGs (unreferenced generated artifacts).
- Ignore pr-evidence/ alongside notes/artifacts/.
- Defensively exclude pr-evidence/ from the electron-builder app.asar
  include surface so a stray local capture never bloats the bundle.
- Assert the new exclusion in electron-builder-config.test.mjs.

Co-authored-by: Orca <help@stably.ai>
2026-07-15 22:42:55 -07:00
Rod Boev 877a74c193
feat(linear): use Linear branch names for worktrees (#8617)
* feat(linear): use Linear branch names for worktrees

* fix(linear): preserve branch overrides across composer resets

Normalize Linear branch metadata at the shared workspace-source boundary, restore it when repo changes preserve the issue, and clear it when another provider replaces or removes the link. Add regression coverage for each lifecycle transition.

---------

Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
2026-07-15 22:29:01 -07:00
Jinwoo Hong 319ae4e9ea
fix(terminal): make whole-tab close durable (#8958)
Co-authored-by: Orca <help@stably.ai>
2026-07-15 22:09:37 -07:00
OrcaWin 9f8b254876
fix(terminal): route Droid Shift+Enter to CSI-u in no-OSC shells (Git Bash) (#7620) (#8948)
Shift+Enter still submitted instead of inserting a newline when Droid ran in
Git Bash on Windows, even after the PowerShell fix (#7620/#8065/#8427).

On Windows, Orca sends Droid the kitty CSI-u byte (ESC [ 13;2u) for Shift+Enter
only when the active pane has fresh, trusted process evidence that Droid is the
foreground app; otherwise it falls back to the Codex-compatible ESC+CR, which
Droid decodes as a plain Enter and submits. That trusted evidence comes from a
confirming foreground read.

PowerShell triggers that read from its OSC 133 command-start event. Git Bash
(and cmd.exe) emit no OSC command boundary, and an Orca-launched agent delivers
its command programmatically (no typed-command inference), so a fresh Droid
spawn in those shells never earned routing trust -- leaving Shift+Enter on the
ESC+CR fallback forever.

Fix: on a fresh spawn into a no-OSC shell, seed the SAME command-start
confirmation the manually-typed launch path already uses
(paneForegroundAgentTracker.onCommandStarted). Its bounded retry ladder spans
Droid boot, and -- crucially -- a miss publishes shellForeground:false, so the
pane stays recoverable by later focus/reveal samples. (A visible-pty sample was
rejected here: on a pathologically slow boot its known-agent shell-confirm would
latch, clear launch identity, and permanently poison Shift+Enter for the rest of
the session.) A real OSC 133;C, if it arrives, simply supersedes this.

Verified on Windows 11 + Git Bash with the real Droid CLI and node-pty ConPTY:
- Droid's TUI treats ESC [ 13;2u as a newline (two-line composer) and ESC+CR as
  a submit -- exactly the reported bug and the fix's mechanism.
- droid.exe is a descendant of bash inside the ConPTY console process list (so
  the main-side scan resolves it) and becomes a live process ~0.8s after launch,
  well within the retry ladder.
Tests: regression test drives the real connectPanePty spawn path and asserts
csi-u; a slow-boot test exercises the shell->droid ladder transition; a
recoverability test proves a total-miss does NOT poison the pane (it fails
against the visible-pty approach and passes with onCommandStarted).
2026-07-15 21:56:25 -07:00
itisbryan 5b5d37f211
fix(pi): suppress subagent agent_end so nested sessions don't fire false notifications (#8545)
* fix(pi): suppress subagent agent_end so nested sessions don't fire false notifications

Pi/OMP run subagents as nested child processes that inherit the lead
pane's env (including ORCA_PANE_KEY), each loading its own copy of the
orca-agent-status.ts extension. A child's agent_end was attributed to the
lead pane, firing a false "agent finished" notification on every subagent
completion.

Gate status reporting to the top-level Pi process per pane via an
ORCA_PI_STATUS_OWNED env marker: the first process claims the pane and
sets the marker; any Pi it spawns inherits it and stays silent. Keyed on
process nesting (not hasUI) so a top-level non-interactive `pi -p` run
still reports its own completion.

* test(pi): assert nested-subagent silence for every guarded event, not just agent_end

* fix(pi): key status owner on pid so extension reload doesn't silence the lead

The subagent-suppression gate marked the pane owner with a boolean
ORCA_PI_STATUS_OWNED='1'. Pi re-invokes an extension's default export on
every in-process reload (/reload, live edit, settings reload), so the lead
process re-ran the factory, read the '1' it had set on first load, and
treated itself as a nested subagent — silencing all its own status
(working, tools, done, notifications) for the rest of the session.

Record the owning process's pid instead. A same-process reload matches its
own pid and keeps reporting; a spawned subagent inherits the lead's pid,
sees it differs from its own, and stays silent. Startup was unaffected
(bindExtensions runs the factory once), which is why the single-run trace
missed it.

Add a reload regression test that re-invokes the factory in the same
process and asserts the lead still reports; it fails against the boolean
marker.

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

* fix(pi): gate nested status hooks at registration

---------

Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
Co-authored-by: Orca <help@stably.ai>
2026-07-15 21:54:15 -07:00
Jinjing f62ad69260
Click a link in orca browser should not open a new tab (#8782)
* Route new-tab link clicks to Orca tabs instead of popup windows

- Adds an isolated-world click/auxclick listener that relabels
  browser-native new-tab intents (target=_blank, cmd/ctrl-click,
  middle-click) with a private frame name, so setWindowOpenHandler can
  distinguish them from opener-dependent window.open() calls without
  breaking OAuth popups.
- Wires matched clicks through to a new browser:open-link-in-orca-tab
  IPC payload carrying foreground/background activation intent, so the
  renderer opens a worktree tab instead of a native window.
- Adds unit and e2e coverage for modifier/middle-click routing,
  cancellation/rewrite handling, and cleanup on guest teardown.

* Route new-tab link clicks to Orca tabs and stop background popups

Plain target=_blank clicks (main frame and iframes) now navigate the
current Orca tab in place instead of opening a new browsing context,
while explicit new-tab gestures (Cmd/Ctrl-click, middle-click,
Shift+modifier) route into Orca tabs via one-use private frame names,
including child frames. Drops the foreground/background frame-name
split and the activate flag now that all routed links always open
active — no more silent background popups from a plain link click.
2026-07-15 21:37:32 -07:00
Jinwoo Hong 69c14fadce
fix(terminal): make viewport ownership transactional across output, replay, and reflow (#8674)
Co-authored-by: Orca <help@stably.ai>
2026-07-15 20:28:39 -07:00
Brennan Benson 1ba71d2cee
fix(mobile): background-mount never-mounted terminals on subscribe so they don't render blank (STA-1840) (#8811)
* fix(mobile): background-mount never-mounted terminals on subscribe so they don't render blank (STA-1840)

A mobile terminal.subscribe to a tab the desktop never mounted this session —
a workspace the desktop isn't currently showing, a cold-activation-deferred tab
(#8597), or a cold-parked tab — has no attached PTY, so the runtime has no
headless emulator and the initial snapshot is empty. The mobile terminal then
renders blank (repro: on the phone, scroll to an old workspace whose terminals
the desktop unmounted; they stay blank).

When a mobile subscribe hits an empty snapshot (or resolves no PTY), the runtime
now asks the renderer to background-mount that tab so the PTY attaches and the
live data stream — already subscribed on that path — delivers its output.

A never-mounted workspace has no renderer-graph leaf, so its terminals are
surfaced to mobile via synthetic pty:<ptyId> handles that carry no real UI
tabId. The runtime passes the ptyId through the mount request and the renderer
resolves the owning tab from the persisted tab model (resolveTerminalTabIdForPtyId),
falling back to a whole-worktree background mount if it cannot. The request is
idempotent for already-mounted tabs and never unmounts a live pane, so the worst
case is the same blank as before — no regression.

Validated on an iOS simulator with a two-workspace desktop: opening the
workspace the desktop was not showing rendered a fully blank terminal without
the fix and the live shell prompt with it.

Tests: runtime mount-request gating (real-tab / pty-form / no-ptyId / unknown),
subscribe-handler empty-snapshot-vs-content, and the ptyId->tabId resolver; all
608 RPC method tests pass.

* test(mobile): mock onRequestTerminalTabMount in useIpcEvents tests (fixes CI for #8811)

* fix(mobile): harden blank terminal recovery mounts

* perf(mobile): avoid redundant terminal recovery mounts

* fix(mobile): ignore stale terminal mount requests

* fix(mobile): skip recovery mounts for closed streams

* fix(mobile): reject ambiguous terminal mount ownership

* fix(mobile): preserve terminal handle through recovery mount

* fix(mobile): recover legacy terminal history safely

---------

Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com>
2026-07-15 19:14:22 -07:00
Jinjing f1d2fe5c65
perf(git-status): duty-cycle, cancel, and cache status polling to cut idle git load (#8922)
* feat(git-status): batch, cancel, and cache git status polling to cut idl

- Add a single duty-cycled refresh scheduler (activity debounce + 60s
  safety timer) replacing multiple overlapping intervals, so status
  polling no longer runs near-continuously on large repos (#7983).
- Let safety refreshes reuse cached numstat line counts instead of
  re-running diff --numstat every cycle, invalidated by head change,
  known mutations, and a bounded TTL.
- Thread AbortSignal/request-token cancellation through IPC, RPC, and
  relay layers so a superseded or backgrounded git:status call is
  killed instead of finishing wastefully.
- Fix automatic upstream/status apply ordering so a slow, older
  refresh can no longer clobber a newer result, and so an earlier
  refresh still applies when a later one fails.

* Fix aborted git status scans being mistaken for completed empty results

- An aborted scan/numstat pass now always rejects instead of silently
  resolving, so a cancelled request can't look like a valid empty status.
- Stop clearing the line-stats cache key on abort, since an aborted pass
  never wrote to it — clearing was evicting a concurrent scan's healthy
  snapshot and forcing a redundant numstat recompute.

* Fix aborted git status scans resolving as completed results

Cancelled scans could still resolve with partial or stale data instead of
rejecting, letting callers treat an aborted refresh as a valid status. Also
stop counting aborted scan duration toward catch-up refresh pacing, which
was stretching the next refresh interval by the full length of a cancelled
(often slow) scan.

* Add cancellable, generation-aware git status polling to cut stale scans

- Route git.status through an abortable subscription per requestToken so
  cancelStatus can actually abort the remote scan instead of being a
  no-op, preventing wasted work and stale responses overwriting fresher
  state.
- Bump the git status polling generation on push-target changes so an
  in-flight refresh against the old remote/branch can't apply stale
  upstream data to the new one.
- Guard the stale-conflict poller against writes after unmount.
- Retire pre-purge line-stat scans in the cache so an older in-flight
  scan can't repopulate a key after a token-scoped purge.
2026-07-15 19:09:14 -07:00
Neil 891a456b69
fix(sidebar): don't scroll to an unfocused worktree when pinning/unpinning it (#8930)
Co-authored-by: Orca <help@stably.ai>
2026-07-15 19:07:49 -07:00
Neil 49e2ac6f59
fix(cmd-j): show cross-project dialog on paste + restore focus on Escape (#8931) 2026-07-15 19:06:37 -07:00
fsdwen 78d2b958bf
fix(issues): replace cursor-based pagination with page-number Search API (#8680)
* fix(issues): replace cursor-based pagination with page-number Search API

Problem
=======
Issue pagination (#8649) had two bugs:
1. Pages 6-16 were unreachable — clicking page 16 highlighted page 5;
   clicking 6/7 did nothing. The old cursor-based approach
   (updated:<CURSOR) broke with Search API's relevance sorting —
   pages after the first few returned no items even though more
   issues existed.
2. Issue numbers appeared out of order on loaded pages (e.g. #1082
   between #1308 and #1499), because client-side sort used
   updatedAt instead of issue number.

Root Cause
==========
The pagination used two separate GitHub API strategies:
- Initial page 0 load: REST endpoints (repos/:owner/:repo/issues,
  repos/:owner/:repo/pulls) sorted by updatedAt
- Subsequent pages: Search API with cursor (updated:<DATE)

These two sources returned items in different orders, causing items
to go missing or appear on wrong pages across page boundaries.

Solution
========
1. Unified on GitHub Search API for all pages — initial load and
   pagination both use search/issues?q=...&page=N, eliminating the
   REST-vs-Search inconsistency.
2. Changed from cursor-based (update:<DATE) to page-number-based
   pagination (page=N), which the Search API supports natively.
3. Switched client-side sort from updatedAt to issue number
   (sortWorkItemsByNumber), matching GitHub's default Issues view.
4. Parallelized page fetches in handleLoadNextPage — clicking page
   16 now fetches all intermediate pages concurrently (~2s) instead
   of sequentially (~30s).
5. Cleaned up dead legacy gh issue list / gh pr list code path,
   extracted quoteForSearch helper, shortened overlong comments.

Files changed: 11 files, +140/-127 lines

Closes #8649

* chore: remove unrelated merge formatting

---------

Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
2026-07-15 18:52:37 -07:00
Jinjing b0def3b130
fix(ssh): keep Windows CLI when the launcher compiler is missing (#8918)
The remote CLI installer deleted the legacy orca.cmd bridge before it
confirmed csc.exe existed, so a minimal Windows host without the .NET
compiler lost its existing CLI command and got nothing back.

Move the legacy-shim removal to run only after the compile and the
launcher-existence guard both pass. Missing-compiler and compile-failure
paths now exit non-zero while leaving the existing orca.cmd untouched; a
successful upgrade still clears the unsafe %* bridge (orca.exe already
shadows orca.cmd via PATHEXT). No %* forwarding is restored.

Co-authored-by: Orca <help@stably.ai>
2026-07-15 18:49:34 -07:00
Jinjing 0da3d392dc
Reorganize heading slash commands and rename toggle heading labels (#8917)
Move toggle heading commands (toggle-h1..h4) into their own "Toggle headings"
group, separate from plain heading commands, so the slash menu distinguishes
static headings from collapsible toggle headings. Also shorten toggle heading
labels from "Toggle Heading N" to "Toggle HN" for consistency with the plain
heading labels.
2026-07-15 18:48:34 -07:00
github-actions[bot] 84ca07d535 Update README downloads badge 2026-07-16 00:51:59 +00:00
BingZ 24dc165b3c
fix(sidebar): map header drags to the nearest boundary slot instead of a dead zone (#8891)
* fix(sidebar): map header drags to the nearest boundary slot instead of a dead zone

Fixes #8879

* fix(sidebar): bound header edge drops to measured content
2026-07-15 17:14:10 -07:00
Brennan Benson d3fedb9ba9
ci(computer-e2e): run native-smoke vitest with the shared config (#8909)
The native-smoke job ran bare `pnpm vitest run <files>` with no --config.
There is no root-level vitest/vite config, so vitest fell back to its
built-in defaults: a 5000ms testTimeout and no Windows worker cap. The
real csc.exe launcher-compile tests (build-windows-cli-launcher and
ssh-remote-cli-launcher) cold-start in 1.4-7.4s on hosted Windows and
tipped over the 5s default, failing the PR check.

Pass --config config/vitest.config.ts like every other vitest invocation
in the repo (pnpm test, pr.yml) so the job gets the shared 30s timeout and
Windows maxWorkers:4 cap (less CPU contention -> lower compile variance).
Positional file args still filter to just the listed files.
2026-07-15 16:53:29 -07:00
BingZ 79551c38f5
feat(mobile): edit saved host endpoints (#8294)
* feat(mobile): edit saved host endpoints

* fix(mobile): reject ambiguous numeric host addresses

* fix(mobile): label edit host inputs

* fix(mobile): make host edit save atomic and remove superseded mutators

Two independent review rounds found the same class of foot-gun: a
superseded mutator (updateHostEndpoint, then renameHost) left in
host-store.ts after the atomic updateHostNameAndEndpoint refactor, with
zero remaining callers. Either could be reintroduced by a future caller
and silently regress the non-atomic name/endpoint race the atomic
function was written to close, so both are removed.

Also covers reconnect-rejection and endpoint-only save paths that were
missing test coverage, and merges origin/main (#8789) so this lands
without reverting the mobile terminal restore fix.

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

* Simplify save-race comment and reword host-removed error message

- Trims the redundant comment explaining the savingRef race guard down
  to one line.
- Changes the "no longer saved" load-error copy to "was removed" for
  clearer phrasing, updating the matching test expectation.

---------

Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
Co-authored-by: Orca <help@stably.ai>
2026-07-15 16:49:21 -07:00
buf0-bot[bot] f7ef467cc9
fix: pr-bug-scan validated finding from #6335 (#8804)
* fix: address pr-bug-scan validated finding from #6335

Replaced unguarded crypto.randomUUID() with the established createBrowserUuid() fallback in both markup shape-id call sites.

* fix(review): cover markup crypto fallback call sites with regression tests

PR #8804 replaced two unguarded crypto.randomUUID() calls with
createBrowserUuid(), matching the convention established in #5471, but
shipped without extending that PR's regression-test pattern to the new
call sites. Add hook-level tests for useMarkupPointerHandlers.onPointerDown
and useMarkupEditor.commitPendingText under a simulated non-secure-context
crypto global.

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

* fix(review): consolidate annotation bridge token onto createBrowserUuid

BrowserPane had its own inline crypto.randomUUID guard with a
Date.now()+Math.random() fallback, duplicating the getRandomValues-based
fallback already centralized in createBrowserUuid() and used at every
other call site in the renderer.

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

---------

Co-authored-by: orca-bug-scan-bot <orca-bug-scan-bot@stably.ai>
Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
Co-authored-by: Orca <help@stably.ai>
2026-07-15 16:47:18 -07:00
haazz 16fbd33c98
docs(readme): fix Korean translation issues (#8883)
* docs(readme): fix Korean translation issues

* docs(readme): align agent list with English README across translations

Replace OpenClaude with Pi in Spanish, Japanese, and Chinese READMEs
to match the English README's agent list (Codex, ClaudeCode, OpenCode, Pi).

Portuguese already had the correct list.

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-15 16:39:34 -07:00
Jiyong Jung 5b329026d5
Update Korean translations for diff word wrapping (#8867)
Fixed misleading Korean translation of "on"/"off" and "word wrap"
2026-07-15 16:39:00 -07:00
Brennan Benson 5739e1844b
Reopen closed tabs with Cmd+Shift+T, including terminals (#7445)
* Reopen closed tabs with Cmd+Shift+T, including terminals

Cmd/Ctrl+Shift+T now reopens the most recently closed tab of any kind
(terminal, browser, or editor) in true recency order, walking back
through close history on repeated presses — Chrome/Ghostty semantics.
Terminal tabs previously had no reopen at all, and the shortcut picked
browser-vs-editor by focused surface instead of recency.

Terminal closes capture a snapshot (startupCwd, shellOverride,
customTitle, color) on explicit user closes only; PTY exits, launch
cleanup, onboarding, and floating-panel closes are excluded. Reopen
spawns a fresh shell through the same createTab path as the "+" button.
A per-worktree cross-type kind list (new recently-closed-tabs slice)
orders the three existing per-type stacks; both new maps are purged on
worktree removal and re-keyed on rename with startupCwd prefix remap.
createNewTerminalTab moved to terminal-tab-create.ts to keep
terminal-tab-actions.ts under the max-lines budget.

Requested via Discord feedback; STA-1287.

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

* Update close-tab assertions for captureRecentlyClosed flag

Floating-panel, launch-failure, and stale-agent-tab closes now thread
{ captureRecentlyClosed: false } so system-initiated closes don't pollute
the reopen stack. Update the affected test expectations to match the new
call shape.

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

* fix(reopen): skip local terminal reopen on remote-runtime worktrees

reopenClosedTerminalTab recreated terminals via a raw local createTab
without the isWebRuntimeSessionActive guard every sibling terminal-create
path uses, so Cmd+Shift+T on a remote-runtime-owned worktree (paired-web
or desktop-attached headless-serve) spawned an unbacked local phantom tab
not tied to the host session. Guard at reopen time via the cycle-free
getRuntimeEnvironmentIdForWorktree — importing web-runtime-session into a
store slice creates a store-index init cycle. Remote terminal reopen stays
deferred; the cross-type dispatcher falls through to browser/editor.

Adds a regression test and corrects an inaccurate removal-path comment.

* fix(test): seed runtime host id for remote-reopen guard test

The prior test set settings.activeRuntimeEnvironmentId by spreading the
store's settings, which widened workspaceDir to string|undefined and broke
`pnpm typecheck` (GlobalSettings requires string). Seed the worktree with a
`runtime:` hostId instead — getRuntimeEnvironmentIdForWorktree reads it
directly, so no settings spread is needed.

* Remove unrelated formatter changes

---------

Co-authored-by: Orca <help@stably.ai>
2026-07-15 16:14:44 -07:00
github-actions[bot] 6a2b90aed1 release: v1.4.143-rc.0 2026-07-15 23:03:55 +00:00
Brennan Benson a1778d93d3
Fix PR checks sticking to a stale linked PR after a terminal branch switch (#8760)
* Fix PR checks sticking to a stale linked PR after a terminal branch switch

A worktree's linked PR is a branch-scoped hint, but two refresh paths race
when a terminal switches branches: the git-status identity path clears
branch-scoped review links, while the worktree-listing path rehydrates the
new branch together with the stale persisted link and clears nothing. When
the listing lands first (the common case — worktree listing is much faster
than git status), the identity path sees no branch change and the stale
link survives. Every subsequent refresh then re-fetches the linked PR by
exact number, which ignores the branch, so Checks stays pinned to the old
branch's PR and the Refresh button cannot recover.

Two-part fix:
- Prevention: listing refreshes now route observed branch switches through
  updateWorktreeGitIdentity before merging, so the existing link clear and
  tombstone machinery runs no matter which refresh path wins. Gated on the
  entry still carrying branch-scoped review context so a stale listing row
  cannot roll back a newer branch identity.
- Recovery: PRInfo now carries headRefName, and a fetch that returns the
  linked OPEN PR whose head branch matches neither the current branch, the
  worktree push target, nor the worktree HEAD clears the durable link and
  re-resolves by branch. Wired into both fetchPRForBranch and the
  background refresh coordinator, mirroring the merged-PR divergence clear.
  This also heals wedged workspaces persisted by earlier builds.

* Harden stale PR recovery across refresh races

* Avoid duplicate PR recovery refresh work

* Index linked PR refresh aliases once
2026-07-15 15:52:41 -07:00
Kaynan Sampaio de Camargo 6e2a4a824d
fix(worktrees): stop surfacing prunable git worktrees as live workspaces (#8409)
* fix(worktrees): stop surfacing prunable git worktrees as live workspaces

A worktree still registered in git but whose directory was deleted
(git's `prunable` state) was enumerated as a normal workspace,
producing repeated pty:spawn DaemonProtocolError / fs:readDir ENOENT
loops and a blank pane.

- Parse the `prunable` porcelain field (Git >= 2.36) in both the main
  and relay worktree-list parsers.
- For Git < 2.36 (no `prunable` field), probe each linked worktree
  path for existence on the fallback line-block path, skipping locked
  registrations to mirror git's own prunable rules.
- Omit prunable worktrees from the detected-workspace enumeration only;
  removal/cleanup flows keep seeing them.
- Extend the real-binary compatibility contract with the 2.36
  `prunable` boundary.

Fixes #8389

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

* fix(worktrees): pin the prunable/locked porcelain annotations to their real Git 2.31 boundary

The prunable and locked annotations landed in Git 2.31, five releases
before `worktree list -z` (2.36); only -z defines the capability
fallback boundary. Correct the compatibility contract so a future
matrix entry in the 2.31-2.35 range passes, and reword the fallback
comments: on 2.31-2.35 the annotations still parse and the existence
probe is a backstop; only Git <2.31 relies on it outright.

* fix(worktrees): omit prunable registrations from the Space scan

A prunable registration has no directory to size or reclaim, so Space
rendered it as a dead "Missing" row whose checkbox stayed disabled with
no prune/remove affordance (reported on macOS after a reboot cleared
/private/tmp under 16 registrations). Skip prunable entries in the scan,
matching the workspace enumeration; removal flows list worktrees
separately and still see them.

---------

Co-authored-by: kaynan <kaynan.camargo@terceiro-sky.com.br>
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
2026-07-15 15:24:15 -07:00