Commit Graph
8614 Commits
Author SHA1 Message Date
Brennan BensonandNeil 49a8e80526 Temporarily bypass Windows inner signature gate (#7137)
Co-authored-by: Neil <neil@stably.ai>
2026-07-02 12:34:35 -07:00
github-actions[bot] 6bca68ae09 release: v1.4.118-rc.3 v1.4.118-rc.3 2026-07-02 19:13:04 +00:00
Brennan BensonandNeil d2d3e90c96 Fix Windows inner signature PowerShell path (#7136)
Co-authored-by: Neil <neil@stably.ai>
2026-07-02 12:11:58 -07:00
Brennan BensonandOrca 186f6c9e3d Fix stale terminal frames on worktree return: clear alt screen on snapshot restore + WebGL reveal hardening (#7133)
Co-authored-by: Orca <help@stably.ai>
2026-07-02 12:11:27 -07:00
github-actions[bot] 568a869195 Update README downloads badge 2026-07-02 18:48:53 +00:00
github-actions[bot] 33fda893e9 release: v1.4.118-rc.2 v1.4.118-rc.2 2026-07-02 18:43:30 +00:00
Brennan BensonandNeil fc17c57b6b Fix Windows SignPath PSGallery preflight (#7135)
* Verify Windows app executable signing

* Isolate Windows signing verifier tests

* Handle direct Windows installer extraction

* Fix SignPath PowerShell gallery preflight

---------

Co-authored-by: Neil <neil@stably.ai>
2026-07-02 11:42:44 -07:00
Brennan Benson 4c03924618 Show Git-created worktrees in external discovery (#7078) 2026-07-02 11:24:07 -07:00
github-actions[bot] 1ac6de1ccb release: v1.4.118-rc.1 v1.4.118-rc.1 2026-07-02 18:01:05 +00:00
Brennan BensonandNeil 85cec26773 Prevent unsigned Windows app releases (#6806)
* Verify Windows app executable signing

* Isolate Windows signing verifier tests

* Handle direct Windows installer extraction

---------

Co-authored-by: Neil <neil@stably.ai>
2026-07-02 10:54:19 -07:00
github-actions[bot] dbf80864fe release: v1.4.118-rc.0 [rc-slot:2026-07-02-03] v1.4.118-rc.0 2026-07-02 10:58:11 +00:00
NeilandOrca ad13532ab1 Garbage-collect stale worktreeMeta for externally-deleted worktrees (#7107)
* Garbage-collect stale worktreeMeta for externally-deleted worktrees at load

worktreeMeta rows are minted for every worktree Orca ever discovers but
were only deleted by Orca-initiated removal flows. Worktrees deleted
outside Orca (git worktree remove, rm -rf, agent scripts) purge renderer
session state without ever removing the meta, so the map grew
monotonically - 318 of 503 entries (63%, ~110KB) on the reference
machine pointed at long-deleted paths. These entries never reach the
renderer; they only cost disk bytes, parse time, and memory.

GC at load is deliberately narrow: local-host entries only (SSH/runtime
repos, remote meta.hostId, and WSL UNC paths are skipped - a local
existsSync would falsely condemn remote paths), only when the checkout
path no longer exists, only after a 30-day idle grace (preserving
pushTarget cleanup for recently-vanished worktrees and quick
recreations), and never when the entry lacks timestamps to prove
idleness. Lineage rows cascade like removeWorktreeMeta.

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

* Fix GC false positives: folder-workspace instances, WSL paths on Windows

From adversarial review of the GC commit:
- Folder-project workspace instances are keyed repoId::path::workspace:
  <uuid>; splitting on the first :: made the existence probe test a
  path-with-suffix that never exists, deleting idle folder workspaces -
  whose meta IS the workspace record - and cascading into terminal-
  history deletion. Instance keys are now skipped entirely.
- On Windows, WSL linked worktrees carry Linux-style paths from git
  porcelain that a Windows existsSync cannot probe; skip non-Windows
  paths on win32.
- Idle-grace check now runs before existsSync (skips the stat fan-out
  for active entries and shrinks the slow-NFS startup tail), and a
  null worktreeMeta map in the file is normalized instead of throwing
  outside the parse guard.

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

---------

Co-authored-by: Orca <help@stably.ai>
mobile-android-v0.0.21
2026-07-02 03:12:26 -07:00
NeilandOrca 3ba3630150 Move githubCache out of the durable state file into a quit-time sidecar (#7101)
* Move githubCache out of the durable state file into a quit-time sidecar

Every PR/issue poll restamps fetchedAt even when nothing changed, which
defeated the content-hash write guard by construction: ~100KB of
refetchable 5-min-TTL cache rewrote the whole 1.6MB orca-data.json once
per refresh cycle - the last remaining non-user-driven writer.

The cache is now memory-only during the session (setGitHubCache no
longer schedules a save; the durable payload and state hash both omit
it) and is snapshotted best-effort to orca-github-cache.json at flush
(quit/reload), so PR/issue badges still paint instantly on next launch.
A legacy in-file cache is used as the seed and stripped by the next
durable write; downgrades just recreate the key, which the next upgrade
strips again. Sidecar loss costs nothing - the data is refetchable.

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

* Reject null/array pr-issue shapes when reading the github cache sidecar

A corrupt-but-valid-JSON sidecar ({"pr":null}) would seed
state.githubCache.pr = null and crash mobile worktree.ps PR grouping
until the renderer's first cache save repaired it.

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

---------

Co-authored-by: Orca <help@stably.ai>
2026-07-02 02:51:09 -07:00
Jinjing e3f99a7472 Bump mobile app.json to 0.0.20 (#7100) 2026-07-02 02:42:52 -07:00
aa1744e30e Fix cursor agent ime anchor (#7061)
* fix: correct IME cursor after terminal reattach

* fix: stabilize Cursor Agent IME anchor

* Fix Cursor Agent reattach IME detection

* Harden live-agent reattach detection and always restore cursor visibility

- POST_REPLAY_LIVE_AGENT_REATTACH_RESET now includes ?25h so a false-positive
  agent signal (or dead TUI leftovers) can never leave a shell with a
  permanently hidden cursor; only focus reporting (?1004h) is preserved.
- Replace the broad getAgentLabel token match (fires on titles like
  'ssh devin@host') with an exact cursor-agent native-title check.
- Anchor the replay-payload screen signal on the LAST 'Cursor Agent' header so
  scrollback from an earlier finished run does not classify the current screen.
- Make the payload-signal flag replay-shape-aware: incremental
  (clearBeforeReplay:false) frames can only add evidence; full-screen replays
  remain authoritative.

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

* Fix anchor scan direction and drop non-decaying launch metadata from liveness

- Scan the visible screen bottom-up in resolveCursorAgentImeAnchor so a
  transcript line containing an arrow (e.g. a rename diff) cannot hijack the
  IME anchor away from the input box.
- Remove getAuthoritativePaneAgent() from hasLiveAgentReattachSignal:
  tab.launchAgent never decays after the agent exits, so it would preserve
  ?1004h and inject focus-in into the shell left behind by a dead agent.
- Clear a stale replay-payload agent signal on an authoritative empty clearing
  frame.

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

* Veto scrollback-only Cursor Agent signal against the parsed viewport

The replay-byte screen signal also matches a dead run's screen sitting in
scrollback (daemon snapshots serialize full scrollback; the relay buffer is
never cleared). After xterm parses the replay, confirm a payload-only signal
against the visible screen via resolveCursorAgentImeAnchor; when the viewport
shows a plain shell, drop focus reporting (?1004l) and skip the focus-in so a
bare shell never inherits stale focus modes or receives stray focus bytes.

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

* Make the reattach viewport veto shape-based and latest-frame-only

- parsedViewportShowsCursorAgentScreen now checks the screen shape (header +
  input row) via viewportShowsCursorAgentScreen instead of requiring the
  parked-cursor precondition, so a live agent whose cursor sits at the caret
  is not downgraded.
- Add a replay-signal generation counter so a stale post-parse callback stands
  down when a newer replay frame has updated the signal.

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

* Require the parked cursor in the reattach viewport confirmation

A dead cursor-agent screen can still be painted in the viewport with the
shell prompt below it, which passes a shape-only check and would preserve
?1004h and inject a focus-in into the shell. Confirm a payload-only signal
with resolveCursorAgentImeAnchor (shape + parked cursor): a live agent that
needs the focus-in is by definition parked, and an unparked live agent only
falls back to the pre-fix reattach behavior.

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

* Complete window stub in pty-connection tests for command-finished event

Main's onCommandFinished now dispatches a window CustomEvent synchronously,
but the node-env connectPanePty window stub omits dispatchEvent. These tests
only passed when another test file in the same vitest worker happened to leave
a window with dispatchEvent; this branch's added test files shift sharding and
expose the gap. Add dispatchEvent/addEventListener/removeEventListener to the
stub so the command-finished path is covered deterministically.

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-02 02:41:07 -07:00
Neil 443d6efb50 Tune worktree card drag targets (#7095) 2026-07-02 02:34:10 -07:00
WONGILandJinjing 4f0a141951 Fix mobile terminal Korean IME composition on Android (#7011)
* Fix Korean IME composition by deferring live terminal preedit

The mobile terminal capture field previously sent and cleared every TextInput change, which can break Hangul composition on Android keyboards. Introduce a small commit model and extracted live-input hook so composed text is flushed deliberately while ASCII remains immediate.

Constraint: React Native TextInput has no portable composition event for this path; the fix uses a bounded commit delay for likely IME text.

Rejected: Native-module IME integration | unnecessary for the confirmed JS dispatch/clear failure and higher maintenance risk.

Confidence: high

Scope-risk: moderate

Directive: Keep terminal.send payload shape and buffered command input unchanged; do not claim physical Samsung Keyboard QA without device evidence.

Tested: cd mobile && pnpm exec vitest run src/terminal/terminal-live-text-commit.test.ts src/terminal/terminal-live-input.test.ts src/terminal/terminal-text-input-normalization.test.ts src/terminal/terminal-keyboard-type.test.ts --reporter=verbose

Tested: cd mobile && pnpm exec tsc --noEmit

Tested: cd mobile && pnpm exec oxlint src/terminal/terminal-live-text-commit.ts src/terminal/terminal-live-text-commit.test.ts src/terminal/use-terminal-live-input-commit.ts app/h/[hostId]/session/[worktreeId].tsx

Not-tested: Physical Galaxy Fold7/Samsung Keyboard and Android emulator/Gboard QA were unavailable; device probes recorded no attached Android device.

* Preserve pending Korean IME text before mobile accessory controls

Accessory keys share the same pending live-input commit gate as TextInput keypress and submit paths, so control bytes cannot race ahead of composed Hangul.

Constraint: React Native mobile input does not expose portable composition events for Samsung/Gboard IME paths.

Rejected: Let accessory buttons keep sending directly | Direct sends can drop pending Hangul before Tab/Esc/Enter/Backspace reaches the PTY.

Confidence: high

Scope-risk: narrow

Directive: Keep all terminal control-byte paths behind the pending live-input flush/local-edit decision before sending to the PTY.

Tested: pnpm --dir mobile test; pnpm --dir mobile lint; pnpm --dir mobile exec tsc --noEmit; pnpm --dir mobile exec oxfmt --check src/terminal/terminal-live-text-commit.ts src/terminal/terminal-live-text-commit.test.ts src/terminal/use-terminal-live-input-commit.ts src/terminal/use-terminal-live-accessory-input-commit.ts app/h/[hostId]/session/[worktreeId].tsx; git diff --cached --check

Not-tested: Physical Galaxy Fold7 Samsung keyboard manual QA is still external-device only.

* Prevent stale IME timer flushes after mobile terminal teardown

Pending live-input timers now clear on hook unmount, and accessory Delete documents why it stays local without trimming pending IME text.

Constraint: React Native TextInput lacks a portable composition lifecycle, so pending IME text is guarded by a bounded timer that must not survive screen teardown.

Rejected: Use clearPendingLiveInputCommit during unmount | it would also touch React state/native props during teardown when only timer/ref cleanup is required.

Confidence: high

Scope-risk: narrow

Directive: Any delayed terminal input commit must have an owner-lifecycle cleanup path before sending to the PTY.

Tested: pnpm --dir mobile test; pnpm --dir mobile lint; pnpm --dir mobile exec tsc --noEmit; pnpm --dir mobile exec vitest run src/terminal/terminal-live-text-commit.test.ts --reporter=verbose; pnpm --dir mobile exec oxfmt --check src/terminal/terminal-live-text-commit.ts src/terminal/use-terminal-live-input-commit.ts; git diff --check

Not-tested: Physical Galaxy Fold7 Samsung keyboard manual QA remains unavailable in this environment.

* Use semantic accessory edits for mobile IME commits

Accessory Backspace/Delete now carry semantic local-edit intent from built-in keys instead of inferring intent from raw bytes, and submit handling is reconnected to the pure submit-sequence model.

Constraint: Custom terminal accessory keys may produce the same bytes as built-ins but should still flush pending IME text before sending rather than being silently treated as hidden-input edits.

Rejected: Classify local accessory edits by raw bytes | That couples future custom controls to current built-in byte encodings.

Confidence: high

Scope-risk: narrow

Directive: Keep semantic input intent separate from terminal byte payloads when pending IME text is present.

Tested: pnpm --dir mobile test; pnpm --dir mobile lint; pnpm --dir mobile exec tsc --noEmit; pnpm --dir mobile exec oxfmt --check src/terminal/terminal-live-text-commit.ts src/terminal/terminal-live-text-commit.test.ts src/terminal/use-terminal-live-input-commit.ts src/terminal/use-terminal-live-accessory-input-commit.ts app/h/[hostId]/session/[worktreeId].tsx; git diff --check

Not-tested: Physical Galaxy Fold7 Samsung keyboard manual QA remains unavailable in this environment.

* Respect IME flush failures before control input

Propagate terminal.send success from pending Korean IME text before sending Enter, Tab, or accessory bytes, while keeping custom no-pending accessory bytes on the original direct path.

Constraint: PR #7011 review required follow-up control bytes only after the pending composed text send actually succeeds.

Rejected: Treating send invocation as success | It can still reject or no-op when RPC state changed.

Confidence: high

Scope-risk: narrow

Directive: Keep pending IME flush paths async-success-aware before adding new terminal control inputs.

Tested: pnpm --dir mobile test; pnpm --dir mobile exec tsc --noEmit; pnpm --dir mobile lint; pnpm --dir mobile exec oxfmt --check changed files; targeted no-excuse clean for mobile/src/terminal changed files.

Not-tested: Physical Galaxy Fold7 Samsung keyboard; full session file no-excuse audit still reports pre-existing unrelated violations.

* Serialize mobile IME flushes before live controls

Treat terminal.send as successful only when the RPC response is ok and the runtime send result is accepted, then route all live-input control sends through a shared in-flight pending-flush barrier.

Constraint: PR #7011 review found that resolved RPC promises and per-call sequencing were not enough to prove pending Hangul text reached the PTY before follow-up controls.

Rejected: Only awaiting each flush-then-send call | Repeatable accessory keys and no-pending sends can arrive while the first flush is still in flight.

Confidence: high

Scope-risk: moderate

Directive: Keep future mobile terminal control paths behind the pending-flush barrier whenever IME text may be in flight.

Tested: pnpm --dir mobile test; pnpm --dir mobile exec tsc --noEmit; pnpm --dir mobile lint; pnpm --dir mobile exec oxfmt --check changed files; no-excuse clean for terminal changed files.

Not-tested: Physical Galaxy Fold7 Samsung keyboard; full session file no-excuse audit still reports pre-existing unrelated violations.

* Queue current IME snapshots behind active flushes

Drain the pending snapshot captured by a control action after any already-active terminal send, and make accessory commit handling explicit so raw fallback is not encoded as an inverted boolean.

Constraint: Architecture review found the previous single-slot barrier could wait for an older flush while skipping newly pending Hangul text.

Rejected: Reusing the prior in-flight promise as the current flush result | It proves only an older snapshot, not the current pending buffer.

Confidence: high

Scope-risk: narrow

Directive: New mobile terminal control paths must distinguish allow-raw, handled, and suppress-raw outcomes explicitly.

Tested: pnpm --dir mobile test; pnpm --dir mobile exec tsc --noEmit; pnpm --dir mobile lint; pnpm --dir mobile exec oxfmt --check changed files; no-excuse clean for terminal changed files.

Not-tested: Physical Galaxy Fold7 Samsung keyboard; full session file no-excuse audit still reports pre-existing unrelated violations.

* Preserve accessory raw-send terminal targets

Capture the terminal handle at accessory keypress time and suppress raw fallback if the active live terminal changes while waiting for pending IME flushes.

Constraint: Independent review found raw accessory bytes could retarget to a different terminal after an async IME flush barrier.

Rejected: Re-reading activeHandleRef as the send target after await | It can point at a different terminal than the keypress belonged to.

Confidence: high

Scope-risk: narrow

Directive: Raw accessory fallback must use the keypress-time target and revalidate it after any await.

Tested: pnpm --dir mobile test; pnpm --dir mobile exec tsc --noEmit; pnpm --dir mobile lint; pnpm --dir mobile exec oxfmt --check changed files; no-excuse clean for terminal changed files.

Not-tested: Physical Galaxy Fold7 Samsung keyboard; full session file no-excuse audit still reports pre-existing unrelated violations.

* Document accessory flush barrier intent

Make the non-obvious raw accessory wait/suppress behavior explicit so future changes preserve IME-before-control ordering.

Constraint: CodeRabbit requested a why-comment for the send-now accessory branch.

Rejected: Leaving the barrier semantics implicit | The branch can otherwise look like unnecessary async defensive code.

Confidence: high

Scope-risk: narrow

Directive: Keep comments focused on why raw accessory bytes wait behind IME flushes.

Tested: targeted terminal vitest suite; pnpm --dir mobile exec tsc --noEmit; pnpm --dir mobile lint; oxfmt check for changed file.

Not-tested: Physical Galaxy Fold7 Samsung keyboard.

* Preserve buffered accessory raw sends

Keep the stale-handle guard focused on the captured active terminal instead of live-input opt-in state, so buffered mode keeps existing accessory key behavior while async live-input waits still cannot retarget to another terminal.

Constraint: Buffered command input behavior must remain unchanged while fixing mobile Korean IME live input ordering.
Rejected: Requiring live-input enabled handles for raw accessory fallback | suppresses valid buffered-mode accessory sends.
Confidence: high
Scope-risk: narrow
Directive: Do not use live-input opt-in state as terminal liveness for raw accessory sends; validate captured target, active terminal tab, connection, and client instead.
Tested: pnpm --dir mobile test; pnpm --dir mobile exec tsc --noEmit; pnpm --dir mobile lint; oxfmt --check changed mobile terminal/session files; TypeScript no-excuse checker for changed terminal files.
Not-tested: Physical Galaxy Fold7 Samsung Keyboard manual QA and GitHub Actions jobs, blocked by unavailable device and upstream fork workflow approval.

* Keep Hangul IME text pending until explicit flush

Avoid timer-driven PTY writes for Hangul candidates so paused Korean composition cannot leak intermediate jamo, while preserving the bounded settle timer for non-Hangul IME text. Also keep disabled live-input accessory fallback behind any existing pending flush barrier.

Constraint: React Native TextInput does not expose a portable composition lifecycle on this mobile surface.
Rejected: Fixed 150ms auto-flush for Hangul | can emit ㅎ or 하 if the user pauses mid-composition.
Confidence: high
Scope-risk: narrow
Directive: Treat Hangul candidates as pending until submit/control/accessory flush; do not reintroduce idle timer commits for Hangul without device-level composition evidence.
Tested: pnpm --dir mobile test; pnpm --dir mobile exec tsc --noEmit; pnpm --dir mobile lint; oxfmt --check changed mobile terminal/session files; TypeScript no-excuse checker for changed terminal files.
Not-tested: Physical Galaxy Fold7 Samsung Keyboard manual QA and GitHub Actions jobs, blocked by unavailable device and upstream fork workflow approval.

* Gate dictation toast on accepted live send

Honor the async live-input sender contract so the mobile UI reports dictation insertion only after terminal.send is accepted.

Constraint: sendLiveTerminalInput now returns false for stale, disconnected, oversized, or rejected terminal sends.
Rejected: Toasting immediately after dispatch | reports success for sends that never reached the PTY.
Confidence: high
Scope-risk: narrow
Directive: Treat live-input UI success as terminal.send acceptance, not request dispatch.
Tested: pnpm --dir mobile test; pnpm --dir mobile exec tsc --noEmit; pnpm --dir mobile lint; oxfmt --check app/h/[hostId]/session/[worktreeId].tsx.
Not-tested: Physical Galaxy Fold7 Samsung Keyboard manual QA and GitHub Actions jobs, blocked by unavailable device and upstream fork workflow approval.

* Keep accessory edits on Hangul pending path

Make accessory local edits reuse the Hangul-aware defer policy so built-in Backspace/Delete cannot reintroduce timer-driven Hangul PTY writes.

Constraint: Hangul IME candidates must remain pending until explicit submit/control/accessory flush.
Rejected: Reusing the non-Hangul 150ms settle timer for accessory local edits | can leak pending Hangul after Backspace/Delete.
Confidence: high
Scope-risk: narrow
Directive: Any future pending-text reschedule must use getTerminalLiveDeferredTextDelayMs instead of a hardcoded timer.
Tested: pnpm --dir mobile test; pnpm --dir mobile exec tsc --noEmit; pnpm --dir mobile lint; oxfmt --check changed mobile terminal/session files; TypeScript no-excuse checker for changed terminal files.
Not-tested: Physical Galaxy Fold7 Samsung Keyboard manual QA and GitHub Actions jobs, blocked by unavailable device and upstream fork workflow approval.

* Prove Hangul live-input hook ordering

Add a direct hook-level regression so Android Korean IME fixes are covered at the orchestration boundary, not only by lower-level helpers.

Constraint: React Native mobile TextInput lacks portable composition lifecycle events in this path.

Rejected: Relying only on helper tests | misses hook-level pending flush and submit ordering.

Confidence: high

Scope-risk: narrow

Directive: Keep Hangul candidates pending until an explicit terminal action flushes them.

Tested: pnpm --dir mobile test; pnpm --dir mobile exec tsc --noEmit; pnpm --dir mobile lint; oxfmt --check changed mobile files; no-excuse on terminal modules

Not-tested: Physical Galaxy Fold Samsung Keyboard manual QA is not available in this environment.

* Keep accessory raw-send tests precise

Remove a duplicate raw-target assertion whose title implied disabled live-input behavior that is covered at the accessory commit boundary instead.

Constraint: Anti-slop cleanup must preserve existing Hangul/accessory behavior and stay within changed terminal tests.

Rejected: Keeping the duplicate disabled-input wording | it tests the same active-terminal predicate as the preceding case.

Confidence: high

Scope-risk: narrow

Directive: Test disabled live-input buffering in the accessory commit layer, not in the raw-target predicate helper.

Tested: pnpm --dir mobile test; pnpm --dir mobile exec tsc --noEmit; pnpm --dir mobile lint; pnpm --dir mobile exec oxfmt --check changed mobile files; terminal no-excuse checker

Not-tested: Physical Galaxy Fold Samsung Keyboard manual QA is not available in this environment.

* Explain stale mobile terminal send gates

Document why async IME flush paths re-check terminal/client refs before sending raw bytes or reporting live-send success.

Constraint: CodeRabbit review requested short why comments for non-obvious stale-send safety gates.

Rejected: Leaving the gates undocumented | future edits could remove the stale-target suppression contract.

Confidence: high

Scope-risk: narrow

Directive: Keep async terminal sends guarded by current client, active handle, tab type, and connection state.

Tested: pnpm --dir mobile test; pnpm --dir mobile exec tsc --noEmit; pnpm --dir mobile lint; pnpm --dir mobile exec oxfmt --check changed mobile files; terminal no-excuse checker

Not-tested: Physical Galaxy Fold Samsung Keyboard manual QA is not available in this environment.

* Run mobile IME hook tests through effects

Move the Hangul live-input hook regression from server rendering to react-test-renderer so effect cleanup and unmount timer cancellation are exercised.

Constraint: @testing-library/react-native imports React Native's Flow entry under this Vitest setup, so the narrow effect-running renderer is the compatible test surface.

Rejected: Keeping renderToString | it never runs useEffect cleanup and missed the pending timer cleanup path.

Rejected: Adding @testing-library/react-native directly | it failed before tests with React Native Flow syntax under the current Vitest transform.

Confidence: high

Scope-risk: narrow

Directive: Hook-level IME tests must use a renderer that runs effects when asserting pending flush cleanup.

Tested: vitest targeted terminal tests; pnpm --dir mobile test; pnpm --dir mobile exec tsc --noEmit; pnpm --dir mobile lint; oxfmt --check changed mobile files; terminal no-excuse checker

Not-tested: Physical Galaxy Fold Samsung Keyboard manual QA is not available in this environment.

* Keep hook lifecycle tests quiet

Suppress only the react-test-renderer deprecation warning around the effect-running hook harness so real console errors still surface.

Constraint: CodeRabbit flagged React 19 renderer warning noise; @testing-library/react-native remains incompatible with the current Vitest/RN Flow transform path.

Rejected: Global console silencing | it would hide unrelated test failures.

Confidence: high

Scope-risk: narrow

Directive: Keep the renderer warning suppression scoped to this hook harness and pass all other console errors through.

Tested: vitest targeted terminal tests; pnpm --dir mobile test; pnpm --dir mobile exec tsc --noEmit; pnpm --dir mobile lint; oxfmt --check changed mobile files; terminal no-excuse checker

Not-tested: Physical Galaxy Fold Samsung Keyboard manual QA is not available in this environment.

* fix: flush pending mobile IME input before external sends

* fix: guard terminal command finished event dispatch

---------

Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
2026-07-02 02:31:19 -07:00
Neil e59c04122d Fix Activity thread options trigger ref loop (#7096) 2026-07-02 02:17:38 -07:00
NeilandOrca b51dd80252 Skip no-op state writes and bound save postponement in persistence (#7092)
* Skip no-op state writes and bound save postponement in persistence

orca-data.json (1.6MB live) was fully rewritten (pretty-print + tmp +
rename) on every debounced save even when the state content had not
changed - measured 3 rewrites/min (~5MB/min) on an idle production
instance, and a sync-flush storm of identical multi-MB writes at every
warm start via persistPtyBinding re-binds.

- Content-hash guard in both writers: a save whose plaintext state hash
  matches the last successful write skips serialize+write+rename
  entirely. Hashing plaintext (not the payload) because encrypt() uses
  a random IV per call. Safe under flushOrThrow's durability contract:
  a matching hash means the file already holds exactly this state.
- Debounce 300ms -> 1s trailing with a 5s max-wait. The old timer reset
  on every mutation with no bound, so sustained sub-interval mutation
  bursts could postpone the write indefinitely; now staleness is capped
  at 5s while bursts coalesce.

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

* Guard the state-hash against sync-flush interleaving mid-rename

From adversarial review: an async writer that had already passed its
generation check and dispatched the rename could have a sync flush
interleave during the await, write fresher state, and record its hash -
the async continuation then clobbered lastWrittenStateHash with a value
describing content NOT on disk, making later saves (including the quit
flush) silently skip. Re-check writeGeneration before recording.

Twin edge: a hash-matching sync flush could skip its write while a
stale dispatched rename lands afterwards, leaving stale content
unchallenged. flushOrThrow now forces the sync write whenever an async
write chain was in flight at entry, restoring the sync-write-last disk
ordering.

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

---------

Co-authored-by: Orca <help@stably.ai>
2026-07-02 01:56:25 -07:00
JinjingandOrca 6142ec1a06 Refresh AI Vault session list on window refocus (#7075)
* Refresh AI Vault session list when the window regains focus

Sessions started after the panel mounted never appeared until a manual
refresh, since the hook only scanned on mount and scope changes. Listen
for window focus and visibilitychange (to visible) while the panel is
mounted and trigger a non-force re-scan, letting the main process's 15s
scan cache rate-limit rapid focus flips. The manual refresh button keeps
its force (cache-bypassing) behavior.

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

* Keep AI Vault refocus refresh render-free on cache hits

Refocus refreshes now run as background: the loading flag stays down (no
spinner flash on every alt-tab back), and when the main process replays
the cached snapshot (same scope key + scannedAt) the state updates are
skipped entirely so nothing re-renders. Fresh scans and the manual force
refresh apply results exactly as before.

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

* Bypass AI Vault scan cache on panel entry and refocus, throttled

Non-force refreshes were served the 15s-old cached snapshot, so a
session started right before re-entering the panel or refocusing the
window still didn't appear — only the manual force refresh showed it.
Panel entry and refocus now request a force scan, throttled in module
scope to one forced scan per 5s (surviving panel remounts), so rapid
tab/focus flips still resolve from the main-process cache. Manual force
refreshes count against the throttle to avoid back-to-back full scans.

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

* Deliver AI Vault refocus via main-process signal; calm in-app triggers

Renderer DOM focus/visibility events never fire on macOS app activation
(verified live: document.hasFocus() stays true and no focus/blur event
lands when the window loses/gains OS focus), so the refocus refresh was
inert. Main now broadcasts browser-window-focus to the renderer as
aiVault:windowFocused and the hook subscribes to that instead.

Sessions started inside Orca (window never blurs) get their own trigger
from agent hooks: re-scan only when an unseen provider session id
appears in agent status. State transitions and message pings on known
sessions are deliberately ignored — keying on them made the panel churn
on every AI message. Event-driven rescans that land inside the 5s
throttle window defer to one trailing scan instead of being dropped.

Verified end-to-end against a dev instance: a headless claude session
appeared in the panel on OS-level refocus and another on panel
re-entry, without the manual refresh button.

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

---------

Co-authored-by: Orca <help@stably.ai>
2026-07-02 01:45:03 -07:00
Neil 366746ad2b Support Claude weekly Fable usage meter (#7079)
* Support Claude weekly Fable usage meter

* Reference Claude weekly usage research

* Add distinct Claude Fable weekly meter

* Tighten Claude Fable usage parsing
2026-07-02 01:34:44 -07:00
NeilandOrca 9fc61aa7ca Bound hot-terminal full-snapshot checkpoints with a per-session cooldown (#7088)
* Bound hot-terminal full-snapshot checkpoints with a per-session cooldown

A streaming session (build logs, yes) re-triggered a full multi-MB
checkpoint.json tmp+rename on every 5s tick via pending-buffer overflow
or the 5MB log cap - hundreds of MB/min of disk writes from one busy
terminal, and the dominant Orca-generated filesystem event volume.

Cap/overflow-triggered full snapshots now honor a 45s per-session
cooldown: while cooling down the session's checkpoint work is deferred
entirely (no takePendingOutput RPC, no increment appends - appending
past a dropped range would leave a hole in the log, so the on-disk
state stays a consistent, merely stale, prefix) and the session stays
dirty so the timer retries until the cooldown expires. Final/teardown
checkpoints (quit, sleep, clean disconnect) bypass the cooldown, and a
session's first-ever snapshot is never delayed, so cold-restore
fidelity changes only for hot sessions mid-stream: at most 45s of
tail scrollback staleness after a hard crash.

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

* Harden checkpoint cooldown against crash-boundary and reuse edges

From adversarial review of the cooldown commit:
- Warm reattach by an adapter that was not already managing the session
  now forces a full-snapshot re-anchor: the previous adapter may have
  drained daemon records it never persisted (deferred hot-session tick),
  and appending past that unknown drain point would put a seq gap in the
  log, which the restore reader rejects wholesale. This also closes the
  pre-existing (previously ~ms-wide) drain-vs-persist crash window.
- Cold-restore re-anchor clears any cooldown inherited from the
  pre-crash generation (daemon respawn within one adapter), so the
  revived session's first snapshot is never deferred.
- Exit-event routing clears lastFullCheckpointAt (reused ptyIds must not
  inherit a dead session's cooldown); fanoutSyntheticExits now clears
  sessionsNeedingFullCheckpoint for consistency.
- Backward wall-clock jumps count as expired instead of extending the
  deferral.

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

---------

Co-authored-by: Orca <help@stably.ai>
2026-07-02 01:33:51 -07:00
NeilandOrca b07ea4a1a5 Push-refresh git status on repo metadata changes and shell command completion (#7086)
Branch switches and commits made inside terminals now surface through push
signals instead of waiting for the 30s terminal-only fallback poll:

- The git-common watch now also covers the primary checkout's HEAD,
  packed-refs, and index (a few stat calls per tick; no new native watchers).
- worktrees:changed events nudge the active worktree's coalesced git status
  runner (visibility-gated, 3s floor).
- OSC 133;D command completion dispatches a window event that nudges the
  same runner for the active worktree.

Co-authored-by: Orca <help@stably.ai>
2026-07-02 01:32:05 -07:00
Neil 6dafe8c870 Fix worktree sort crash when display name is missing (#6993) 2026-07-02 01:06:10 -07:00
Jinjing 151c567074 Promote stale agent targets when live titles prove they are sendable (#7043)
* Promote stale agent targets when live titles prove they are sendable

- Extract centralized `detectAgentSendTitleStatus` helper to determine if an agent is ready or needs permission based on pane and tab titles.
- Prevent stale hook-backed status rows from disabling active targets when fresh live titles and PTYs prove they are sendable.
- Fallback to the tab's launch agent type when status-backed targets have unknown agent types.
- Add comprehensive test coverage for promoting stale pane targets and preserving permission blocks.

* Prevent split panes from borrowing stale tab titles

Introduce `resolveRuntimePaneTitleLeafResolution` to track whether a
tab has any reported runtime pane titles. Use this to ensure that a split
pane does not fall back to the overall tab title when another pane in the
same tab already has active title evidence, avoiding stale target status.

* Resolve single pane title if tab layout lacks root

When a tab layout does not have a root, we can only attribute a single
reported pane title. This ensures that any pane title still suppresses
the stale tab-title fallback.
2026-07-02 00:52:01 -07:00
github-actions[bot] 279135ed07 Update README downloads badge 2026-07-02 07:27:29 +00:00
github-actions[bot] ab3878ffa0 release: v1.4.117-rc.3 v1.4.117-rc.3 2026-07-02 07:17:22 +00:00
Jinwoo HongandOrca c29bf6f3da Raise WebGL terminal context budget and recover from context loss (#7064)
Co-authored-by: Orca <help@stably.ai>
2026-07-01 23:43:32 -07:00
github-actions[bot] b157c0d5b3 release: v1.4.117-rc.2 v1.4.117-rc.2 2026-07-02 06:36:13 +00:00
Neil 171d32d8f7 Reduce idle git status polling (#7069) 2026-07-01 23:09:23 -07:00
Neil 7c66f37632 Fix AppImage env leakage into Linux terminal shells (#7076) 2026-07-01 23:09:20 -07:00
NeilandOrca 7f1e280436 Cut fseventsd load: fix macOS exclusion cap, drop wide always-on FSEvents streams (#7068)
* Cut fseventsd load: fix macOS exclusion cap, drop wide always-on FSEvents streams

- @parcel/watcher maps plain-path ignores to FSEventStreamSetExclusionPaths,
  but macOS caps exclusions at 8 per stream and fails closed. Orca passed 9,
  silently disabling ALL daemon-side exclusions: fseventsd delivered every
  node_modules/.git event to each per-worktree explorer stream (measured 16x
  client CPU). Cap plain paths at 8 (extras demoted to userspace globs) via a
  shared filesystem-watcher-ignore module, also unifying the runtime
  file-watcher host list so identical roots share one native stream.
- worktree-base-directory-watcher subscribed recursive FSEvents streams over
  every repo's ENTIRE workspace root (all worktrees) and whole common .git
  (objects included) with glob-only ignores (zero daemon-side exclusion),
  just to observe a few shallow paths. Replace with a 2s readdir/stat poller
  (zero fseventsd clients, measured 70x less delivery CPU); SSH targets keep
  provider.watch unchanged.
- serve-sim state watcher held an app-lifetime fs.watch on $TMPDIR (the
  noisiest dir on the system); its existing 250ms existence poll makes the
  parent watch redundant, so drop it.

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

* Eliminate base-poller idle cost: mtime-gated ticks + narrow macOS worktree-metadata stream

- base targets: tick is now one stat of each gate dir (root + nested
  containers); the readdir + marker fan-out runs only when a gate dir
  changed, with a 30s ungated backstop and a bounded pending-marker
  recheck list. Idle tick 600us -> 1.1us at 146 worktrees.
- git-common targets on macOS: replace the 2s entry sweep with one narrow
  native stream on <common>/.git/worktrees (tiny, rare-churn tree): zero
  idle syscalls and external worktree add/remove/HEAD detection back to
  sub-second. Re-arms via existence poll across git worktree prune
  deleting/recreating the dir. Win/Linux keep the listing poll (no
  fseventsd there; a Windows dir handle could block prune).
- split git-common logic into worktree-git-common-watch.ts (max-lines).

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

---------

Co-authored-by: Orca <help@stably.ai>
2026-07-01 23:04:13 -07:00
Brennan Benson 7d3944c8ed Revert "Prevent stale terminal pixels on tab reveal" (#7073) 2026-07-01 22:51:46 -07:00
github-actions[bot] d6578af833 release: v1.4.117-rc.1 v1.4.117-rc.1 2026-07-02 05:14:59 +00:00
Brennan BensonandOrca 347f44485e Prevent stale terminal pixels on tab reveal (#7058)
Co-authored-by: Orca <help@stably.ai>
2026-07-01 22:14:06 -07:00
4dbc9f3817 feat(ssh): add ControlMaster multiplexing for system SSH transport (#6922)
* feat(ssh): add ControlMaster multiplexing for system SSH transport

System SSH transport spawns a new OpenSSH process per exec command
(platform detect, relay install check, node resolution, relay launch,
socket probe). Each process pays the full SSH handshake cost — ~9s on
Uber devpods — making a typical relay connect take 54s+ and reliably
exceeding the 15s startup reconnect budget.

Add SSH ControlMaster multiplexing via a per-target socket in
$TMPDIR/orca-ssh-ctl/<hash>.sock. The first command establishes the
master; subsequent commands reuse it at ~100ms per exec instead of ~9s.
ControlPersist=300 keeps the master alive after commands exit so rapid
reconnects (e.g. on tab focus) also benefit. Windows is excluded since
OpenSSH's ControlMaster support there is limited.

* fix(ssh): address ControlMaster key collision and directory permission risks

- Use target.id in the socket key so distinct SSH targets can never
  collide even when configHost/port/user happen to match
- Switch from SHA1 to SHA256 and extend hash slice from 12 to 16 chars
- Stat the control-socket directory after mkdirSync to reject pre-existing
  dirs that are symlinks, foreign-owned, or have group/other write bits
  (mkdirSync mode is ignored on pre-existing dirs)
- Update two tests that used exact spawn-arg arrays; replace with
  ordering assertions (forward flags before --) that stay correct
  regardless of which extra ControlMaster options are injected

* fix(ssh): bind ControlPath identity to route and reject symlinked ctl dir

Fold proxyCommand/jumpHost/identity fields into the ControlPath hash so a
target whose route is edited no longer reuses a still-alive master built on
the old route. Switch the control-socket dir check from statSync to lstatSync
so a planted symlink fails the directory validation outright.

* test(ssh): drop tautological argv re-assertion in spawn checks

The toHaveBeenCalledWith re-passed the args array extracted from the same
mock call, making that argument position always pass. argv content is
already verified by the index-ordering assertions above; use expect.any(Array)
so the spawn check only claims what it actually verifies (binary path, stdio).

* fix(ssh): harden system ssh connection reuse

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

* test(ssh): isolate control socket runtime dir

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

---------

Co-authored-by: Test <test@example.com>
Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com>
Co-authored-by: Orca <help@stably.ai>
2026-07-01 21:29:48 -07:00
github-actions[bot] de807c5a8c release: v1.4.117-rc.0 v1.4.117-rc.0 2026-07-02 03:58:34 +00:00
Jinwoo HongandOrca f8c1f0fdf8 Fix delayed TUI mouse wheel reports (#7060)
Co-authored-by: Orca <help@stably.ai>
2026-07-01 19:54:53 -07:00
Neil d535cb569a Show collapse affordance on status sidebar headers (#7051) 2026-07-01 19:43:56 -07:00
github-actions[bot] d7322ec1f2 release: v1.4.116 v1.4.116 mobile-android-v0.0.19 2026-07-01 23:11:35 +00:00
Brennan BensonandOrca e0de46ffc8 Prevent hidden terminal TUI overlap (#7054)
Co-authored-by: Orca <help@stably.ai>
2026-07-01 16:10:50 -07:00
github-actions[bot] 126aae7734 release: v1.4.116-rc.0 v1.4.116-rc.0 2026-07-01 22:12:14 +00:00
91e7e1d91c fix: detect valid repos when git rev-parse can't confirm (#6173)
* fix: detect valid repos when git rev-parse can't confirm

isGitRepo() has depended entirely on a successful `git rev-parse`
since 18ed7b27d, with catch blocks that collapse every failure into
"not a git repository". When that subprocess fails for a reason
unrelated to repo-ness — a transient spawn / git-shim hiccup in the
packaged app, main-process resource pressure, or a config-level error —
a real repository is silently downgraded to a plain folder. The folder
scanner already tolerates this via a `.git` marker, so the scan reports
"git_repo" but the subsequent addRepo throws, producing the spurious
"Open as Folder" prompt for a valid repo.

Keep `git rev-parse` as the authoritative positive signal, but on any
non-positive result fall back to a validated `.git` marker instead of
returning false: `.git` dir must contain HEAD, a `.git` file must point
at a gitdir, and bare roots need HEAD + objects/ + refs/. A garbage
`.git` file and an empty `.git/` are still rejected, preserving the
validation 18ed7b27d added. Logs a warning when recovery via the marker
happens so the underlying probe failure stays visible.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: address review feedback on repo detection

- isGitRepo: warn at most once per session when recovering a repo via
  the .git marker, so a broken-git scan can't flood main-process logs.
- repo-detection test: delete PATH instead of assigning "undefined" when
  it was originally unset, avoiding a corrupted PATH for later tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: harden git repo marker fallback

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

---------

Co-authored-by: Omar Shahine <10343873+omarshahine@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com>
Co-authored-by: Orca <help@stably.ai>
2026-07-01 15:10:35 -07:00
Jinwoo HongandOrca dc8161353e Fix Resource Manager session polling lag (#7050)
Co-authored-by: Orca <help@stably.ai>
2026-07-01 15:06:31 -07:00
Jinwoo HongandOrca 8afa84af82 Fix terminal switch input lag from daemon session listing (#7002)
Co-authored-by: Orca <help@stably.ai>
2026-07-01 13:19:50 -07:00
Eddie JaoudeandJinjing e0a7f0eadd fix: reordered default columns in kanban board (#6934)
* fix: reordered default columns in kanban board

* Introduce dedicated flag to repair reversed default workspace statuses

- Add `_workspaceStatusesReorderedDefaultRepaired` to decouple the
  one-shot repair from the initial status order migration.
- Ensure the repair runs for users who saved the reversed default
  payload (with "Done" on the left) during a short-lived broken build.
- Support both "Completed" and "Done" labels when identifying default
  status shapes to migrate or repair.

---------

Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
2026-07-01 13:17:52 -07:00
Jinjing ff44d36c7e Harden divider resize cancellation (#7039) 2026-07-01 12:32:35 -07:00
github-actions[bot] 021cfc4f5d Update README downloads badge 2026-07-01 13:14:56 +00:00
Jinjing 0dfd56af30 Fix terminal pane drag cleanup (#7014)
* Fix terminal pane drag cleanup

* Harden terminal divider cancellation
2026-07-01 03:32:52 -07:00
github-actions[bot] c1fae31a3f release: v1.4.115-rc.4 v1.4.115-rc.4 2026-07-01 10:04:28 +00:00