* Fix automatic branch rename for non-English git locales
A gettext-enabled git (Homebrew git, most Linux distro gits) under a
non-English locale translates every diagnostic, including the `fatal:`
prefix, so Orca's stderr phrase parsers stop matching. The first-message
branch auto-rename was the headline casualty: isNoUpstreamError missed
the translated no-upstream error, branchHasUpstream failed closed to
"has upstream", and the rename settled silently and permanently.
- Force LC_ALL=C on all Orca-spawned machine-parsed git: the local
prompt-guard env chokepoint, the three relay git spawn sites, and both
local clone spawns (progress + failure-message parsing). User
terminals are untouched.
- Replace the boolean upstream check with a tri-state probe: rename
proceeds only on a proven missing upstream; an unreadable probe now
raises the rename-failed badge and retries instead of settling.
Fixes#7808🤖 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
* Consolidate untranslated-git-locale into runner and relay primitives
Replace the five per-site LC_ALL=C patches with one shared
UNTRANSLATED_GIT_OUTPUT_ENV (LANGUAGE=en LC_ALL=en_US.UTF-8
LANG=en_US.UTF-8) injected inside the git runner primitives
(promptGuardGitEnv, gitSpawn, gitExecFileSync, gitExecFileAsyncBuffer)
and a relay buildRelayGitEnv() helper, so every current and future
machine-parsed git spawn is covered by construction — including the
fs-handler-git-fallback sites the per-site approach missed. The UTF-8
English locale keeps a UTF-8 LC_CTYPE for hooks git spawns; LANGUAGE is
pinned because gettext consults it before LC_ALL.
WSL-routed git gets the same values as a shell assignment prefix built
in resolveCommand, since spawn env cannot cross the wsl.exe boundary —
closing the WSL gap the first pass accepted.
Also scrub credential-bearing remote URLs from the probe-failed message
surfaced on the worktree card.
🤖 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
* Scrub credential-bearing URLs from clone failure messages
---------
Co-authored-by: Brennan Benson <brennanbenson@Brennans-MacBook-Pro.local>
Co-authored-by: Claude <noreply@anthropic.com>
* docs: full design + context for agent status over WSL (STA-1515)
Why hooks don't work on Windows+WSL (loopback transport gap + WSL-side
installation gap), per-client transport map, the OMP-only fixes that
shipped (7642/7641) and why they don't generalize, the recommended
guest-resident relay over wsl.exe stdio mirroring the SSH relay plus
WSL-side hook installers, alternatives considered, validation facts and
gotchas from the 2026-07-08 Windows rig run, and acceptance criteria.
Co-authored-by: Orca <help@stably.ai>
* feat(agent-hooks): agent status over WSL — guest relay + WSL-side hook installers (STA-1515)
Agent hooks have never worked from inside WSL: under default NAT
networking, WSL's 127.0.0.1 is its own loopback, so every hook POST to
the Windows listener dies silently, and hook configs were only ever
written to the Windows home where WSL agents never see them.
Transport: a hooks-only guest relay (src/relay/wsl-agent-hook-relay.ts)
runs inside the distro, binds WSL loopback on the very port the clients
were already given (host-issued token; EADDRINUSE falls back to :0 with
endpoint-file re-coordination, which also covers mirrored networking),
and forwards parsed envelopes over its own wsl.exe stdio into
agentHookServer.ingestRemote — the same shape as the SSH relay. It exits
when stdin closes so a freed Windows port can never be forwarded into a
dead guest listener.
Installation: the unchanged SSH remote hook installers run against an
SFTP-shaped adapter whose primitives are home-scoped fs RPCs served by
the relay, so all 14 managed agents' hooks land in the WSL home over the
already-open channel with zero per-file wsl.exe spawns.
Lifecycle: per-distro manager ensured from buildPtyHostEnv on every WSL
PTY spawn (covers post-restart daemon reattach re-spawns), stale-bundle
reinstall via exit 42, no-node-43 cooldown, bounded retry for wsl.exe
'Catastrophic failure (E_UNEXPECTED)', breadcrumbed failures.
Zero per-client transport changes; listener stays Windows-loopback-only.
Co-authored-by: Orca <help@stably.ai>
* fix(agent-hooks): WSL relay link-death recovery + Codex runtime-home hook install (STA-1515)
Follow-ups from the first Windows-rig validation of PR #7903:
Link death: a mux protocol error or keepalive timeout could kill the
host<->guest link while the guest relay stayed alive returning 204s —
the manager stayed 'running' and every later envelope blackholed
silently (the exact observed signature: Claude hooks POST 204, store
never populates). wsl-hook-relay-link.ts now guarantees exactly-once
death handling from either signal (mux dispose OR child exit); the
manager breadcrumbs it, kills the child, and self-restarts after a
short cooldown since a live agent session produces no new PTY spawns
to re-trigger ensure. ORCA_WSL_HOOK_RELAY_DEBUG=1 traces each received
envelope pre-ingest. A live integration test pins the full host chain:
the real esbuild bundle over real child stdio through the real manager
into a real AgentHookServer.ingestRemote, exact Claude POST shape.
Codex: Orca launches WSL Codex with CODEX_HOME redirected to the
managed runtime home (~/.local/share/orca/codex-runtime-home/home), so
hooks installed to ~/.codex were never read. installRemote now accepts
an explicit codex home (flat layout), threaded from the relay manager;
the config.toml trust write is deferred while the file doesn't exist
(the launch path seeds it only-if-absent — creating it first would
cancel the seed), and the manager re-runs the byte-equality-idempotent
installers on later ensures (30s throttle) to upsert trust once the
seed lands.
Also: WSL test suites now run on Windows dev hosts (fs-backed suites
skip with rig coverage noted; manager suite uses a fixed POSIX home).
Co-authored-by: Orca <help@stably.ai>
* fix(agent-hooks): renderer ownership gate treats wsl:* connection ids as local (STA-1515)
Round-2 rig finding: with the link fixed, WSL hook envelopes reached
ingestRemote and the durable cache, but useIpcEvents.applyAgentStatus
drops any status whose stamped connectionId differs from the owning
repo's — 'wsl:<distro>' !== null for a local repo, so every WSL-relayed
status died before setAgentStatus and notifications.
wsl:* ids are transport provenance, not ownership: the gate now
normalizes them to local via isWslHookRelayConnectionId (shared
contract, also used by the relay link when stamping), while still
rejecting WSL-stamped events against SSH-owned repos. Provenance stays
stamped — it is what made this drop diagnosable.
Co-authored-by: Orca <help@stably.ai>
* fix(agent-hooks): adversarial-review hardening for the WSL hook relay (STA-1515)
Four independent review lenses over the branch; all confirmed findings
fixed before the next rig round:
Endpoint identity (4/4 reviewers): the guest endpoint dir was keyed by
the EPHEMERAL Windows hook port, so a daemon-surviving agent kept
sourcing the dead port-P1 file after an Orca restart — breaking the
restart-resume acceptance criterion and regressing shipped OMP
recovery. Now keyed by a restart-stable instance key (hash of the
Windows endpoint file path, crossed via ORCA_WSL_HOOK_INSTANCE): the
restarted instance's relay rewrites the SAME file, which is exactly
what re-coordinates survivors.
Restart policy: every failure arms the restart timer (one failed
relaunch no longer ends self-recovery), and the timer probes
wsl --list --running first — wsl -d BOOTS a stopped distro, so
recovery must never resurrect a VM the user shut down; stopped-distro
state is dropped instead. Failure counters reset only after 2min of
stable uptime, so connect-then-die loops escalate to the 10-min cap
instead of cycling every 10s. Timer policy extracted to
wsl-hook-relay-recovery.ts with direct tests.
Also: version-namespaced guest install dir (dev+prod instances no
longer reinstall over each other; PID-suffixed tmp files), 30s install
timeout (a wedged wsl.exe could pin the state machine at 'starting'
forever), per-candidate node version probing (apt node 12 on PATH no
longer masks nvm node 20 into a false no-node cooldown), WSL_UTF8=1 +
NUL-stripped stderr (catastrophic-failure matcher survives UTF-16LE),
ordered post-sentinel chunk handoff, port-fallback breadcrumb via the
home handshake, bad home reply now fails the connect, missing-bundle
warn-once, case-normalized distro keys, disposeAll wired to will-quit,
one-shot 60s reinstall timer for single-spawn Codex trust catch-up,
escaped + contract-derived spawn command.
Co-authored-by: Orca <help@stably.ai>
* docs: record round-3 rig validation status for agent status over WSL (STA-1515)
Co-authored-by: Orca <help@stably.ai>
* fix(agent-hooks): round-4 adversarial-review fixes for the WSL hook relay (STA-1515)
- dropState identity race: recovery re-checks state identity after the
distro-running probe await, and the manager's dropState only deletes the
exact state it was armed for — an ensure() landing mid-probe can no longer
have its fresh relay orphaned outside the map.
- Distro-running probe fails CLOSED: a probe error no longer reports
'running', so recovery can never wsl-d-boot a distro the user shut down.
- Relay spawns use --exec: bypasses the distro's default login shell
(fish/nushell chsh) and passes argv verbatim, dropping the $-escape shim;
same form as the Codex WSL login spawn.
- Post-sentinel chunk handoff rides a microtask so an envelope in the
trailing bytes can no longer dispatch before the link's notification
handler is registered.
- Guest relay mirrors the SSH relay's uncaughtException/unhandledRejection
posture.
- Replay cache capped at 256 panes with recency eviction (the WSL relay has
no per-pane teardown signal); meta map kept in lockstep.
- Launch script derives the stale-exit code from the shared contract
constant; one-shot reinstall timer refuses to arm after dispose.
- New oracles: sentinel unit suite, fs-bridge scoping suite, fixed-token
403/204, EADDRINUSE endpoint-file rewrite, cache-cap eviction, and the
recovery/manager race regressions (verified to fail with fixes reverted).
- Doc: round-4 review section + revised curl.exe stance (kept as the
no-node fallback — Codex is a native binary; fresh distros ship no node).
* docs: record round-4 pinned rig validation for agent status over WSL (STA-1515)
---------
Co-authored-by: Orca <help@stably.ai>
Co-authored-by: Brennan Benson <brennanbenson@Brennans-MacBook-Pro.local>
* feat(rate-limits): Grok CLI OAuth weekly credit usage in status bar
Read ~/.grok/auth.json (read-only), fetch billing credits via cli-chat-proxy, and surface Grok in Settings, status bar toggles, and rate-limit polling alongside other usage providers.
* fix(grok): clarify comments and address CodeRabbit review
- Shorten Why comments per AGENTS.md; fix billing period end fallback.
- Share GrokAccountStatus type; hash-based locale keys; reload why in Settings.
* docs(grok): plain-language comments and Settings copy
* feat(stats): subscription usage section with Grok in Stats & Usage
Surface rate-limit weekly credits in Settings > Stats & Usage and link to Accounts for setup.
* feat(stats): Grok tab in Usage Analytics dropdown
* refactor(stats): drop Subscription usage block; align Grok pane with Codex
* fix(grok): align settings copy and visibility tests
* fix(grok): add localization catalog entries
* fix(grok): avoid eager usage refresh fanout
* fix(grok): harden usage refresh visibility
* test(grok): cover account status privacy boundary
* fix(grok): target refreshes and redact auth errors
* fix(grok): hide usage UI for signed-out users and align empty states
- Treat a token-less auth.json (e.g. after grok logout) as signed out
instead of surfacing a permanent status-bar error.
- Map billing responses without credit usage to 'unavailable' so plans
with no weekly credits hide the bar like Claude API-key billing.
- Gate the grok status-bar item and toggle on CLI PATH detection,
matching claude/codex/gemini/kimi.
- Guard an empty GROK_CLI_CHAT_PROXY_BASE_URL from producing a
relative billing URL.
- Drop dead minimax/kimi Stats & Usage search keywords left from the
removed subscription section.
- Add missing grok search keyword catalog entries and translate the
English-stubbed stats keywords in es/ja/ko/zh.
* test(ipc): mock grok account registrar in register-core-handlers test
---------
Co-authored-by: Brennan Benson <brennanbenson@Brennans-MacBook-Pro.local>
* Fix untracked line-stat cache thrash and add source-control scale benchmark (#8013)
The untracked line-stat cache capped at 2,048 entries while a git status
scan can carry up to DEFAULT_GIT_STATUS_LIMIT (10,000) untracked entries.
A sequential scan over more files than the cap FIFO-evicted every entry
before the next poll revisited it (~0% hit rate), so every 3s status poll
re-read every untracked file's full contents. Measured with 64KB files:
warm rescan cost per file was 17x higher just past the cap.
- Size the cache to 2x the status entry limit and make eviction LRU
(delete-before-set on hit and refresh) so a hot worktree's entries
survive another worktree's scan.
- Add tests/e2e/source-control-large-file-count.spec.ts: a 5-scenario
Playwright benchmark (pnpm run test:e2e:source-control-scale) that
reproduces #8013 deterministically — event-loop stall, DOM node count,
JS heap, and OS-level renderer working set at 5k/9.5k/11k changed files,
plus a clean-repo control and a cache-effectiveness gate. Scenarios
asserting bounded row mounting go green with the SourceControl
virtualization fix (#7619).
Co-authored-by: Orca <help@stably.ai>
* Address review: historical cache comment + fixture cleanup on partial setup failure
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Orca <help@stably.ai>
Add attribute filters (status, priority, assignee, labels) to the Linear
issues list, applied in GraphQL before pagination so hasMore matches the
filtered set. Thread filters through IPC/RPC/store cache with invalidation
on issue mutations, and mirror GitHub-style filter chrome on TaskPage.
* Support WSL Codex settings promotion and harden config write-back
- Enable settings promotion for WSL runtimes using per-distro baselines.
- Create parent directories if missing to prevent promotion ENOENTs.
- Keep restrictive permissions (0600) and follow symlinks on promote.
- Respect CRLF line endings when inserting keys into CRLF config files.
- Skip redundant baseline file writes when settings are unchanged.
- Include the release scan report for the 1.4.131-rc2 prep.
* Refactor sleeping agent wake flow and fetch rate limits via backend
- Background-mount only targeted terminal tabs during passive wake to
prevent spawning unnecessary PTYs for unvisited tabs.
- Latch edge-triggered wake requests that arrive mid-hibernation and
track active claims to prevent double-resuming a provider session.
- Query the ChatGPT wham usage backend API directly with fetch for
rate limits, avoiding launching Codex or WSL login shells.
- Asynchronously probe and serialize WSL auth files with timeouts to
prevent synchronous I/O from stalling Electron's main process.
- Fix config promotion edge cases such as missing parent directories,
dangling symlinks, and atomic write permission widening.
* Support WSL dotfile-symlink write-back and lengthen redeem timeout
- Preserve symlinked Codex config on WSL by writing through the
existing file instead of atomic-rename, since \\wsl$ symlink
metadata isn't reliably detected and rename would clobber the link.
- Tighten new ~/.codex directory creation to 0700 (holds auth.json).
- Give explicit reset-credit redemption a 30s backend timeout instead
of the 10s background-poll default, since it's user-triggered.
- Read sleeping-agent session state from the worktree's actual
execution-host partition instead of always the local one, so the
headless-wake check works correctly for SSH-hosted worktrees.
- Isolate serve-sim watcher tests from the real $TMPDIR/serve-sim
state file to avoid leaking unrelated events.
* fix(preflight): resolve WSL/SSH agent paths past shell aliases
LeanCTX and similar tools wrap claude/codex as interactive shell aliases.
command -v then returns alias text, which fails absolute-path detection and
hides installed agents (#7816).
Prefer bash type -P, then zsh type -p, then command -v for dash/sh fallback
in WSL agent discovery, WSL isCommandOnPath, and remote relay probes.
* fix(preflight): harden alias-safe PATH lookup chain
Require non-empty results between type -P, type -p, and command -v so bash
type -p empty success cannot skip later lookups.
* fix(preflight): resolve agent executables directly from PATH
---------
Co-authored-by: Jinwoo Hong <73622457+Jinwoo-H@users.noreply.github.com>
* auth v1
* fable review
* lint
* Account menu with org membership management
Default UX is a compact account menu (sign in, organization selection, sign
out) that renders only when cloud auth is configured; adds an organization
members dialog (invite, role, remove) gated on server-side role checks. The
multi-profile switcher UI is preserved behind ORCA_MULTI_PROFILE_UI=1.
Co-authored-by: Orca <help@stably.ai>
* Gate the optional account sign-in UI to dev builds
The account switcher stays hidden in packaged builds while the feature is
in progress. Dev builds still show it when the client env vars are set, and
a dev-only Settings > Dev Tools > Orca Cloud section mirrors it.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Orca <help@stably.ai>
* feat(onboarding): state-aware macOS notification permission step
The Set up notifications step showed a one-size-fits-all 'Open Mac
Settings' button that simultaneously fired the macOS permission prompt
and opened System Settings — two competing system UIs, with System
Settings unnecessary for the common fresh-install case.
Electron exposes no API to read macOS notification authorization, but
scheduling outcomes do reveal it: a silent probe notification's 'show'
event means permission is granted, 'failed' means delivery is blocked.
A new notifications:probeDelivery IPC runs that probe (cached via
passive delivery evidence and a persisted confirmation flag), and the
onboarding card now renders the real state:
- fresh install: the probe itself pops the native Allow dialog the
moment the step opens; the card flips to 'Notifications are enabled'
automatically when the user clicks Allow (silent 2.5s re-probes)
- blocked: amber card with an Open System Settings deep-link, which
also self-heals once the user flips the toggle
- granted: green confirmation card
The test-notification button now feeds the same card instead of the
ambiguous 'if no banner appeared…' toast during onboarding.
Co-authored-by: Orca <help@stably.ai>
* fix: don't log expected probe rejections while polling for permission
Co-authored-by: Orca <help@stably.ai>
* fix: amber warning styling + single stable dev bundle id for notifications
- Blocked card now uses the app's shipped amber idiom (tinted surface with
amber title/body) instead of white-on-amber-wash, which read muddy in
dark mode; macOS permission card split into its own module to stay under
the max-lines budget.
- Dev instances previously minted a unique macOS bundle id per
branch x Electron version, registering a new Notification Settings entry
every time ('Orca: <branch>' rows piling up forever) and pointing the
settings deep-link at ids System Settings can't resolve. All dev
instances now share com.stablyai.orca.dev: one Notification Center
entry, one permission grant covering every dev build.
Co-authored-by: Orca <help@stably.ai>
* fix: tighten macOS permission card copy
Body copy was one long sentence; now a single short instruction with
'Updates automatically.' as a separate dimmer line. Also repairs locale
catalog parity for keys introduced by commits rebased into this branch.
Co-authored-by: Orca <help@stably.ai>
* fix: drop 'Updates automatically.' line; ad-hoc sign dev app copies
The extra line read as confusing filler — the cards now carry one short
instruction each.
Dev Electron copies had broken code signatures (the Info.plist identity
edits invalidate the ad-hoc seal), which macOS punishes by refusing
Notification Center registration outright: every dev notification failed
with UNErrorDomain error 1, the app never appeared in System Settings >
Notifications, and the settings deep-link had nothing to land on. The dev
runner now ad-hoc re-signs the copied bundle after the plist edits
(bundleLayoutVersion bumped so stale unsigned copies are recreated).
Verified end-to-end: runner-built copy passes codesign --verify --deep,
probe delivery returns delivered, the onboarding card flips green in dev,
and the deep link opens the dev app's own notifications pane.
Co-authored-by: Orca <help@stably.ai>
* fix: drop confusing copy line; session-only permission evidence
Removes the 'Updates automatically.' line from both permission cards.
Also drops the persisted notificationDeliveryConfirmed flag: OS-level
permission changes between sessions, and a stale positive rendered a
false green card. Delivery evidence is now session-scoped only.
Documented detection ceiling (verified empirically on macOS 26): while
the permission dialog is unanswered — and when notifications are toggled
off in System Settings after being authorized — macOS accepts requests
and silently swallows them, with no public API (Notification Center
delivered-history and legacy ncprefs both included) able to distinguish
that from real delivery. 'failed' remains definitive for unsigned builds
and dialog-level denials.
Co-authored-by: Orca <help@stably.ai>
* feat: real macOS notification permission readout via native helper
Electron has no API for UNUserNotificationCenter authorization, and every
observable fallback lies: scheduling succeeds (and getHistory lists the
notification) even while macOS silently swallows display because the
permission dialog is unanswered or notifications were toggled off in
System Settings. The onboarding card therefore showed 'enabled' after the
user disabled notifications.
Adds native/notification-status-macos: a tiny Swift binary that prints
the app's real authorization status. It runs from inside the app bundle
(NSBundle resolves the bundle by walking up from the executable) and
embeds the app's CFBundleIdentifier in a __TEXT,__info_plist section so
every codesign --force pass — electron-builder's signing or the dev
runner's ad-hoc deep sign — derives the identifier macOS keys
notification records to. Spawning it from the app returns authorized /
denied / not-determined exactly matching System Settings.
notifications:probeDelivery now prefers this readout (authoritative,
silent), firing at most one dialog-trigger probe per session while the
decision is pending, and falls back to the previous delivery-probe
heuristics when the helper is unavailable. The card polls the readout
silently in every state, so toggling Allow notifications in System
Settings flips the card within a poll — both directions, verified live.
Test notifications also consult the readout so 'delivered' is no longer
claimed for swallowed notifications.
Packaged builds ship the helper via extraResources and sign it in
afterPack like the computer-use helper; dev copies compile it on demand
(swiftc, non-fatal when missing) with the shared dev bundle id.
Co-authored-by: Orca <help@stably.ai>
* feat: in-app fallback for swallowed notifications + permission card in Settings
- Dispatch now consults the authorization readout before creating a
native notification: when macOS would silently swallow it (denied or
prompt unanswered) it returns reason 'blocked-by-system' instead of
piling invisible notifications into Notification Center. The terminal
notification path surfaces that as a once-per-session in-app toast
with an Open System Settings action. Mobile fan-out is unaffected.
- Settings > Notifications now shows the same live permission card as
onboarding (moved to components/notifications/), polling the readout
so System Settings changes reflect within seconds, and the test
button updates it inline.
- Test sends that are blocked at the OS level now show the
settings-pointing failure toast instead of a generic error.
Co-authored-by: Orca <help@stably.ai>
* fix: hide macOS permission card while Orca notifications are disabled
A green 'Notifications are enabled' card next to a disabled Enable
Notifications toggle read as a contradiction — the card now renders (and
the readout polls) only while Orca's own notifications setting is on.
Also single-flights the authorization helper so simultaneous agent
completions share one readout process.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Orca <help@stably.ai>
Identify Claude sessions with no saved conversation turns but possessing
recoverable signals such as queued prompts or subagent transcripts.
This displays them in the sidebar with a "Not saved" badge instead of
filtering them out as empty, and provides detailed notices to recover
them via logs while disabling standard resume actions.
Additionally, extract Gemini session parsing logic into a separate
module and support counting sibling subagent transcripts across local
and remote SSH session scans.
* fix(mobile): normalize Grok rotating OSC titles at the main observation boundary
Desktop already collapses Grok Build's rotating working frames via the
renderer's normalizeTerminalTitle, but the main process stored raw OSC
titles, so mobile session tabs (fed from pty.lastOscTitle) still saw a
distinct title every spinner frame and re-touched snapshots each time.
Apply normalizeTerminalTitle once where main records an observed OSC
title, before the prevTitle comparison that gates session-tab and
mobile-snapshot touches, and normalize hydration-seeded titles the same
way so the first live frame after a seed compares equal. Agent status
stays detected from the raw title, mirroring the renderer tracker.
Covers remoted/SSH PTYs too since they surface through the same main
observation path.
Co-authored-by: Orca <help@stably.ai>
* Fix Grok and Pi terminal title normalization and status detection
- Require a strict "spinner - phrase - grok" pattern for Grok working
titles to prevent false positives on other agent tasks ending in
"- grok" (such as Claude or Codex).
- Treat collapsed "Pi" and "OMP" synthetic titles as idle by default
to prevent them from reverting to neutral status after normalization.
- Prevent duplicate mobile session tab updates during Grok status
frame rotations by ensuring they normalize to a stable title.
---------
Co-authored-by: Orca <help@stably.ai>
* feat(source-control): add Fix push failure with AI for pre-push hooks
Detect pre-push hook failures separately from auth/transport errors so
push toasts and inline messages no longer suggest checking repo access.
Mirror the commit-failure recovery flow with a fixPushFailure action,
summary panel, details dialog, and agent launch recipe in Settings.
Fixes#6497
* feat(source-control): add push failure AI recovery
Co-authored-by: dalveytech-vincent <vincent@dalveytech.com>
---------
Co-authored-by: dalveytech-vincent <vincent@dalveytech.com>
Grok Build's working OSC titles interpolate a rotating status or tool
phrase between the spinner and its name, causing tab and sidebar
titles to fluctuate rapidly. Collapse these working frames to a stable
"⠋ Grok" label, while leaving idle and session titles untouched.
Claude's AskUserQuestion tool is auto-allowed, so it emits a PreToolUse (not
PermissionRequest) while blocked on a human answer. normalizeClaudeEvent mapped
that to `working`, so the sidebar showed a spinner that decayed to grey while
the question sat. Map the auto-allowed AskUserQuestion PreToolUse to `waiting`
instead, mirroring the Kimi/OpenCode handling, so the row and worktree dots read
amber "Waiting for input".
Separately, a frozen braille-spinner title left by an exited agent (e.g.
"⠐ Review branch for regressions" over a shell prompt) kept classifyTitleActivity
returning `working`, spinning the worktree dot forever with 0 agents — the row
builder rejects the same unattributable title, so no agent row is shown. Gate
tabHasStatus's title-derived working/permission on the same agent attribution the
row builder uses (resolveAgentTypeFromTerminalTitle), so a title only spins the
dot when it would also show a row. Hook-driven status (hasLiveWorking/
hasPermission) is untouched.
After #7750 removed the containment guard, the residual #7239 failure mode
is a persisted/inherited startupCwd whose directory no longer exists: every
spawn dies with the provider's missing-directory error. Fresh local renderer
spawns now opt in (cwdFallback: 'worktree') to recover at the workspace root
with a generic in-terminal notice; reattach, SSH, remote-runtime, runtime/API
and mobile callers keep exact cwd semantics, and existing directories —
including outside the worktree (#7685) — spawn as requested.
Co-authored-by: Orca <help@stably.ai>
* Sweep all persisted carriers of a removed SSH target id on re-adoption
reassignSshTargetId re-pointed repos and worktree metas but left the old
target id embedded in persisted session pty ids (ssh:<id>@@pty-N in tabs,
layouts, remoteSessionIdsByTabId), the startup reconnect list
(activeConnectionIdsAtShutdown, replayed via ssh.connect at boot — the
exact 'SSH target not found' in STA-1468), sleeping-agent resume records,
provisioned project host setups, sidebar host-scope arrays, and relay pty
leases. Any survivor resurfaces later as a failing connect or reattach.
New ssh-target-id-migration module re-points every carrier in one pass,
wired into reassignSshTargetId with per-carrier unit and store-level
round-trip tests.
Co-authored-by: Orca <help@stably.ai>
* Bridge SSH connection state to paired remote clients
The SSH surface was desktop-only: ssh:state-changed went to the host's
own BrowserWindow and the web client's ssh API was a no-op stub, so a
paired client's reconnect overlay never learned the host connected and
its target labels stayed empty (STA-1468 — overlay stuck on 'please
connect' over a live terminal).
- New sshStateChanged runtime client event, emitted from broadcastSshState
through OrcaRuntimeService onto the existing clientEvents stream.
- New ssh.listTargets / ssh.listRemovedTargetLabels RPC methods next to
the previously unused ssh.getState / ssh.connect.
- Web preload now routes listTargets / listRemovedTargetLabels / getState
/ connect to the paired host's runtime RPC instead of stubbing them.
- useIpcEvents applies sshStateChanged on paired web clients through the
same guarded path as desktop ssh.onStateChanged; desktop clients ignore
the event since a foreign runtime's targets would pollute their local
SSH store.
Co-authored-by: Orca <help@stably.ai>
* Harden the SSH reconnect overlay against stale or unknown target state
- Only present the destructive 'SSH host removed' state on positive
evidence (a removal tombstone label, or a hydrated non-empty target
list lacking the id). A client whose SSH state never hydrated has an
empty labels map for every id and must not offer workspace removal.
- After a failed Connect, resync target metadata so a stale overlay
converges to the ghost/re-adopted state instead of offering the same
failing Connect forever (the repeated 'SSH target not found' toast
loop in STA-1468).
Co-authored-by: Orca <help@stably.ai>
* Address CodeRabbit review on #7767
- Re-key workspaceSessionsByHostId partitions stored under a removed SSH
host id during re-adoption (no writer keys partitions by ssh host today,
but the schema tolerates it — re-key instead of stranding; live partition
wins when both keys exist).
- Track SSH target-list hydration explicitly (sshTargetsHydrated) instead
of inferring it from a non-empty label map, so a legitimately empty
target list still counts as removal evidence and a never-hydrated client
still never offers destructive removal.
- Apply the refreshed target list before the best-effort removed-labels
fetch in the overlay resync, so a labels failure can't discard it.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Orca <help@stably.ai>
* Robustify SSH terminal reconnect and session restore recovery
- Release the replay guard after a fallback timeout to prevent permanent
keyboard input lockouts when an unmounted terminal never parses.
- Verify backing process liveness on PTY attach and reap stale entries
so dead shells cleanly trigger a fresh pane spawn.
- Normalize connection IDs during restore to prevent spurious mismatch
errors, and treat true mismatches as expired sessions instead of crashing.
* Route disconnected SSH terminal panes through deferred connection gate
Avoid spawning SSH terminal processes against disconnected targets,
which otherwise throws "No PTY provider" and leaves panes stranded.
- Intercept spawning via a new connect gate that triggers the deferred
connection flow when the SSH target is disconnected.
- Fall back to composite worktree IDs during cold-start hydration to
ensure deferred SSH session IDs are properly stashed.
- Retry spawning and remounting terminal panes upon SSH reconnect if
they are stranded or failed to spawn.
Deleting a config-sourced SSH target had no lasting effect: the Manage-SSH pane
re-imports ~/.ssh/config on open, and the import was a pure upsert with no record
of deletions, so the just-deleted host was re-inserted verbatim from the config
that still exists on disk.
Persist a `deletedSshConfigAliases` tombstone set:
- Deleting a config-managed target (source 'ssh-config', or an adopted legacy
import) records its alias; manual targets are never tombstoned.
- The passive on-open sync skips tombstoned aliases, so a deleted host stays
deleted.
- Re-adding or editing a target reclaims its alias, and the explicit Import
action (`reAdopt`) clears all tombstones to deliberately re-adopt config.
This also fixes the edit-then-reappear case: editing a config host to `manual`
already reserved its current alias, and reclaim covers alias changes.
* feat(ai-vault): add OMP sessions to the AI Vault session browser
Adds OMP ("Oh My Pi") to the AI Vault/Agents catalog so historical
.omp/agent/sessions/**/*.jsonl transcripts are discovered, parsed, and
resumable from the right-sidebar session browser — locally and over SSH.
- Discovery mirrors Pi (OMP_CODING_AGENT_DIR env, WSL home roots, per-agent
limit) in both the local scanner and the remote/SSH scanner.
- Parses OMP's message-graph JSONL via the shared graph parser (new
MessageGraphAgent type), capturing the model from model_change.model (OMP's
key, not Pi's modelId) or the assistant message, and tokens from usage.
- Routes OMP through the incremental parse cache so ~5s rescans resume from
the last byte instead of re-reading whole transcripts.
- Resumes by absolute transcript path (`omp --resume <path>`) so it resolves
regardless of which session-dir root (custom OMP_CODING_AGENT_DIR / WSL
store) the file was discovered under; threaded through both the scanner and
the renderer's local resume/copy rebuild.
- Renderer reuses the existing OmpIcon/catalog/grouping; adds overflow-x-hidden
so long worktree chips never widen the sidebar.
Generalizes normalizePiSessionsDir -> normalizeAgentSessionsDir. Verified
end-to-end against 10 real ~/.omp transcripts and rendered in the app.
Co-authored-by: gatsby74 <166927047+gatsby74@users.noreply.github.com>
* fix: make AI Vault parse-cache agent switch explicit
---------
Co-authored-by: gatsby74 <166927047+gatsby74@users.noreply.github.com>
Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
110 files carried an eslint/oxlint-disable max-lines directive but are
already under the default max-lines budget (300 .ts / 400 .tsx / 600 .mjs
/ 800 test), so the suppression is dead. Removing it restores real
max-lines coverage on these files with zero behavior change.
Each removed directive had max-lines as its only rule; verified via a
full oxlint run (0 max-lines violations, 0 new errors). Diff is pure
deletions (200 lines, 0 additions) — no code touched.
Co-authored-by: Orca <help@stably.ai>
Ensure agent CLI startup and draft launch commands use the correct quoting
format based on the user's configured local Windows shell (e.g., cmd.exe).
This avoids using host settings for remote/SSH targets where local shell
preferences do not apply.
getProcessTableSnapshot deduped the ps fork (#6288/#6667) but cached only the
raw stdout string on POSIX, so every concurrent agent pane re-ran parsePsRows
over the identical output within each 500ms TTL window — O(M*P) redundant
tokenization + row allocation. The Windows reader already caches parsed rows;
this makes the POSIX default reader do the same by parsing inside the deduped
scan and returning ProcessTableRow[]. Collapses the duplicate parsePsRows in
the main and relay foreground resolvers into one shared parseProcessTableRows.
Co-authored-by: Orca <help@stably.ai>
Enable three unicorn rules — one correctness, two performance — and fix every
existing violation repo-wide so the rules pass as errors.
prefer-number-properties (76 sites)
- parseInt/parseFloat/NaN -> Number.* : safe aliases (autofixed).
- isNaN -> Number.isNaN (12 sites, hand-converted): global isNaN coerces its
argument, Number.isNaN does not. Verified every call site already passes a
number (Number.parseInt results, number-typed fields, Date.getTime()), so the
conversion is behavior-preserving today and guards against a future non-numeric
argument silently coercing.
prefer-array-find (26 sites)
- .filter(pred)[0] -> .find(pred); .filter(pred).at(-1) / .pop() -> .findLast(pred).
Drops the intermediate array and short-circuits.
prefer-array-index-of (5 sites)
- .findIndex(x => x === v) -> .indexOf(v).
Verified: typecheck (node/cli/web) clean, 53 affected suites pass (1679 tests),
oxlint clean repo-wide. mobile/ uses findLast safely (already ships ES2023
.toReversed()); config scripts and e2e helpers run on Node 24.
* fix(pty): deliver multiline agent-launch prompts via bracketed paste
Multiline agent-launch prompts (claude/codex/opencode argv injection) were
mangled when Orca typed the startup command into the interactive shell. The
command is single-quoted, but its literal embedded newlines survive quoting;
bash readline / zsh zle read every raw LF as accept-line (Enter), so the first
newline submits an unterminated single-quoted command, drops the shell into PS2
(>) continuation, and the rest executes piecemeal — backticks/$ evaluate, quotes
go unbalanced, and the agent never receives the intact prompt. Short single-line
prompts worked because they have no embedded newline.
Fix: when a startup command contains a newline, wrap the payload in
bracketed-paste markers (ESC[200~ … ESC[201~) before the trailing submit CR/LF
so the line editor inserts the multiline text literally and only the trailing
byte submits it. The single-line fast path is unchanged. Gated on the target
line editor having bracketed-paste mode active (Orca-wrapped bash/zsh) so shells
without it never echo the markers as garbage; Orca's bash rc wrappers now force
`enable-bracketed-paste on` (zsh has it on by default).
Applied consistently across every startup-command delivery path:
- src/main/providers/local-pty-shell-ready.ts (in-process / degraded local)
- src/main/daemon/terminal-host.ts (daemon host — primary local)
- src/relay/pty-handler.ts (SSH relay, remote host)
- src/renderer/src/lib/ssh-background-startup-delivery.ts (hidden SSH tab)
All share src/shared/startup-command-submission.ts. Windows cmd.exe and other
shells keep the current CR submit path (no regression); PSReadLine/POSIX
bash/zsh get the fix.
* Fix multiline detection for CRLF-terminated startup commands
Strip the entire CRLF terminator (or lone CR/LF) from startup commands
before checking if the body contains newline characters.
Previously, slicing off only the last character of a CRLF-terminated
command left a trailing CR in the body. This caused a single-line
command to be incorrectly categorized as multiline and wrapped in
bracketed paste.
Prevent enabling auto-merge when a PR is in an UNSTABLE merge state.
GitHub auto-merge mutations reject UNSTABLE PRs directly instead of
allowing them to wait, so we should suppress the option.