mirror of
https://github.com/stablyai/orca.git
synced 2026-09-22 08:02:28 +00:00
fa769edb8d43a2e2d9eec36d16d42eefe8c9b364
1202
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
fa769edb8d |
Move mobile settings to beta section (#1741)
* Move mobile settings to beta section Co-authored-by: Orca <help@stably.ai> * Add mobile setup guidance Co-authored-by: Orca <help@stably.ai> * Use global App Store URL for mobile Co-authored-by: Orca <help@stably.ai> --------- Co-authored-by: Orca <help@stably.ai> |
||
|
|
954fb9f316 |
Add multi-select worktree actions (#1732)
Co-authored-by: Orca <help@stably.ai> |
||
|
|
13fff0f1fb |
fix(activity): shrink thread-row title, stop branch-name truncation (#1733)
* fix(activity): shrink thread-row title and stop branch-name truncation Move the time + count + unread cluster up onto the title row so the secondary row is full-width for the repo badge and branch name (long branch names were being clipped by the right cluster). Also drop the title from 13px to 11px and the branch metadata to match. Co-authored-by: Orca <help@stably.ai> * fix(activity): swap "New" badge for BellDot icon on unread thread rows The bell-with-dot already represents unread elsewhere (filter toggle, thread-row hover swap), so the text badge was redundant. Co-authored-by: Orca <help@stably.ai> * fix(activity): cluster unread BellDot in the top-right with timestamp Match the position of the existing hover-bell so unread cues live in the row's time/action column instead of inline with the title text. Co-authored-by: Orca <help@stably.ai> * fix(activity): fill the unread bell and merge it into the timestamp slot The static BellDot now lives in the same right-most slot as the timestamp, so on hover the slot's contents (bell + time) fade out and only the hover-toggle button fades in — no more double-bell on hover. Also fill="currentColor" so the unread bell reads as a solid indicator. Co-authored-by: Orca <help@stably.ai> --------- Co-authored-by: Orca <help@stably.ai> |
||
|
|
4608f622bf | fix: open markdown file images in Orca (#1734) | ||
|
|
aa0a1d2ed9 |
feat(ssh): preserve remote PTY sessions across app close (#1706)
Co-authored-by: Orca <help@stably.ai> |
||
|
|
605ee4476a |
perf(runtime): compare mobile session sync key by reference, not JSON.stringify of large maps (#1730)
* perf(runtime): compare mobile session sync key by reference instead of stringifying large maps `getRuntimeMobileSessionSyncKey` used to `JSON.stringify` `terminalLayoutsByTabId` and `runtimePaneTitlesByTabId` whole, which scaled with the lifetime accumulation of tabs in a workspace and pinned the main thread for ~750ms per click in workspaces with hundreds of tabs while an agent was working. The maps reallocate on real changes and stay reference-stable otherwise, so the key now holds them by reference and only pre-serializes the small projected shapes that need value-level comparison. Subscriber gate also early-returns when those two maps are reference-stable, so the common click path (`updateTabTitle` reallocating `tabsByWorktree`) skips the key build entirely. See docs/agent-working-pane-typing-lag.md for the trace and root-cause writeup. Co-authored-by: Orca <help@stably.ai> * docs: correct misleading comment on the relevant-fields gate The earlier comment claimed adding terminalLayoutsByTabId / runtimePaneTitlesByTabId to the gate prevented updateTabTitle from falling through. That's wrong — updateTabTitle reallocates tabsByWorktree, which is a separate field already in the gate and already fails the AND-chain. The two added fields actually catch a different path: layout-only mutations like setTabLayout that don't touch any other gate field. Comment now describes the real intent: the gate is a strict superset of every input to getRuntimeMobileSessionSyncKey, so passing it guarantees the key is unchanged without materializing one. Doc updated to match. Co-authored-by: Orca <help@stably.ai> * chore: drop perf writeup from branch (kept in commit history) Co-authored-by: Orca <help@stably.ai> * test(runtime): pin down by-reference invariant of mobile sync key comparator Reworks the existing reference-stable test to use two distinct AppState instances sharing every comparator-checked map, and adds a negative test that detects a regression to deep equality on terminalLayoutsByTabId. Extracts a makeSharedOverrides() helper so tests can isolate a single field without makeState's fresh-default churn defeating the assertion. Co-authored-by: Orca <help@stably.ai> --------- Co-authored-by: Orca <help@stably.ai> |
||
|
|
7d46eb08dd |
Activity page agent terminal view (#1723)
* fix(activity): align unread dot with row icon Anchor the unread mini-dot to the icon's own bounding box (relative inline-flex with -top-1 -left-1) instead of the 24x24 cell. The previous absolute positioning was relative to the cell, so the dot landed visibly off-center against the centered AgentStateDot/Plus glyph. Adds a ring-2 ring-background halo for contrast against the row's tinted unread background. Applies to both AgentEventRow and WorktreeEventRow. Co-authored-by: Orca <help@stably.ai> * fix(activity): drop top padding so content sits flush with titlebar Outer wrapper used py-3, plus the right-pane <section> had pt-2 and the thread-list header had py-2 — the stacked top whitespace produced a visible gap above the first row against the titlebar. The ActivityTitlebarControls bar already provides the breathing-room band. Drop the wrapper's top padding (py-3 → pb-3), drop the right-pane section's pt-2, and tighten the thread-list header to pt-1.5 pb-2 so the WORKTREES label aligns with the right-pane worktree title row. Co-authored-by: Orca <help@stably.ai> * fix(activity): make event row the click target for navigate + ack The inline "Jump to agent" button only rendered when agentAlive was true, so retained-done rows had no jump affordance, and in compact mode it was hover-only. Users naturally click anywhere on the row expecting it to navigate. Move the navigate-and-ack logic onto the row's onClick (with role, tabIndex, and Enter/Space keyboard support) and drop the inline buttons. Per the design doc (option 1), drop the agentAlive gate entirely: activateAndRevealWorktree is safe unconditionally and activateTabAndFocusPane silently no-ops on a missing tab id, so a stale-tab click is a soft no-op. Mirror the same pattern on WorktreeEventRow so clicking a "Worktree created" row navigates to that worktree. Co-authored-by: Orca <help@stably.ai> * fix(activity): bell glyph for unread + larger row state icon The unread mini-dot stacked on the AgentStateDot competed with the dot itself for the eye and read like a status badge on the agent state. Replace it with a small BellDot glyph at the icon's top-right corner so the unread cue rhymes with the bell button on ThreadRow — one unread vocabulary across both surfaces. While here, bump the row state icon to a new 'lg' AgentStateDot size (18px) so the green check sits center-of-mass in its 32px column instead of floating small and high. Bump the WorktreeEventRow Plus glyph to match. Co-authored-by: Orca <help@stably.ai> * fix(activity): drop left padding so thread list reaches the window edge The page wrapper used px-4, which left a 16px gap to the left of the thread list — visually inconsistent with how sidebars abut the window chrome elsewhere. Switch to pr-4 (keep right padding for the right pane). Inner thread-list and right-pane padding remain unchanged. Co-authored-by: Orca <help@stably.ai> * fix(activity): use left-edge bar for unread instead of an icon overlay Mini-dot and bell-glyph attempts both crowded the AgentStateDot/Plus icon column. Switch to the same left-edge primary bar that ThreadRow already uses for unread — a row-level cue keeps the icon column clean and unifies the unread vocabulary across both panes. Co-authored-by: Orca <help@stably.ai> * fix(activity): page extends to both edges, restore right-pane top padding, rename to "workspace" - Drop horizontal padding on the page wrapper (pr-4 → none) so the thread list reaches the left edge and the right pane reaches the right edge — matches how sidebars abut the chrome elsewhere. - Restore a small top padding (pt-2) on the right-pane title row so the workspace heading isn't pinned to the titlebar. Earlier Fix 2 over-corrected by removing it entirely. - Rename user-facing "Worktree(s)" → "Workspace(s)" (column label, event title "Worktree created", description copy, "Jump to worktree" button, empty state). Internal identifiers stay as worktree* since that's the data-model name. Co-authored-by: Orca <help@stably.ai> * fix(activity): remove redundant "Jump to workspace" header button Clicking any event row already calls markThreadRead + activateAndRevealWorktree (plus a tab focus, so it's a strict superset of what the header button did). The button's only unique behavior was "go to the workspace without picking a tab" — thin justification when the latest event is one row down. cursor-pointer + hover already signal that rows are clickable. Co-authored-by: Orca <help@stably.ai> * fix(activity): wrap agent summary instead of truncating The summary line used 'truncate' with a max-width, hiding the rest of the agent's message behind an ellipsis. The Activity page is meant to let users scan what each agent said without leaving the surface, so truncation defeats its purpose. Switch to break-words + whitespace-pre-wrap so the full message renders and multi-line output keeps its line breaks. Co-authored-by: Orca <help@stably.ai> * fix(activity): clamp agent summary to 3 lines Showing the full message made tall rows for chatty agents. Cap at 3 lines (line-clamp-3) — long enough to convey what happened, short enough that rows stay scannable. Users open the workspace if they want the full transcript. Co-authored-by: Orca <help@stably.ai> * feat(activity): switch left list from workspaces to agent panes Restructure the activity feed so each thread is one *agent pane* (a terminal tab + pane id) rather than one workspace. Internal model goes from WorktreeThread → AgentPaneThread keyed on paneKey. Renderer changes: - Left list: each row shows the pane title (with agent icon + repo badge), then the workspace name as secondary context. Section label changes "Workspaces" → "Agents". - Right pane: header shows the pane title, agent icon, repo badge, and the workspace name beneath. Drops the Today/Yesterday/Earlier day grouping per design — the relative timestamp on each row is enough orientation, and a flat chronological list scans more cleanly. - Drops the workspace-created event kind entirely (and the WorktreeEventRow + ActivityRow union dispatcher), since the surface is now strictly agent activity. Side effects: - Mark-read no longer needs the locallyReadEventIds layer or the worktree-unread store calls (markWorktreeUnread/clearWorktreeUnread). Acknowledge by paneKey is the only persistence path. - Removes Plus and groupForTimestamp (unused after the refactor). Co-authored-by: Orca <help@stably.ai> * feat(activity): label panes like the per-workspace agents dropdown Pane labels now follow the same hierarchy DashboardAgentRow uses inside the WorktreeCardAgents dropdown: customTitle (user rename) > non-default OSC title > last prompt > defaultTitle / "Terminal" The prompt fallback is the important one — agents that haven't been renamed and don't set an OSC title now render with what the user asked the agent to do (e.g. "Fix the unread dot alignment") instead of the generic "Terminal 1" placeholder. Matches the visual + naming pattern on each workspace card so the activity page reads as a flat-feed extension of that surface. Co-authored-by: Orca <help@stably.ai> * fix(activity): smaller titles, allow 2-line prompt clamp Long prompts were getting truncated at one line, hiding most of the ask. Switch the thread row title from truncate to line-clamp-2, with break-words so long words wrap, and tighten size to text-[13px] + leading-snug so two clamped lines don't dominate the row vertically. Anchor the agent icon to the first line via items-start + pt-[3px]. Right-pane heading drops text-base → text-sm to match. Co-authored-by: Orca <help@stably.ai> * fix(activity): right-pane title gets the same 2-line clamp + smaller size Mirror the ThreadRow change on the right-pane heading: line-clamp-2 + break-words + leading-snug so a long prompt title shows two lines instead of a single-line ellipsis. The repo badge moves down to the secondary line (next to the workspace name) so it doesn't shift with the title height. Co-authored-by: Orca <help@stably.ai> * fix(activity): match WorktreeCard's selected/hover/unread cues Three stacked tints (selected + unread row tint + hover) made an unread hovered row look identical to a selected row, and hover even fought selected. Mirror WorktreeCard: - selected → solid black/white tint + faint shadow, hover suppressed (the active class wins so the row stays visually fixed) - non-selected → only then does hover apply (bg-accent/40) - unread → weight + left-edge primary bar carry the cue; row tint removed (matches WorktreeCard's "weight alone carries the unread signal") Co-authored-by: Orca <help@stably.ai> * feat(activity): portal selected agent terminal Co-authored-by: Orca <help@stably.ai> * fix(activity): keep completed entries after terminal input Co-authored-by: Orca <help@stably.ai> * fix(activity): tighten portal target, unread count, and selection edges - activity-terminal-portal: replace document.body MutationObserver with a module-level pub/sub registry; the page publishes the target via a ref callback, Terminal subscribes. Removes body-wide DOM observation. - Terminal: memoize activityTerminalPortal so WorktreeSplitSurface's React.memo bail-out is preserved across unrelated Terminal re-renders. - ActivityPrototypePage: cap events per-pane (5) so a chatty pane can't push quiet panes off the left list; render an empty-state when a retained thread's tab is gone; skip workspace mutation in activateThreadTerminal when the tab is no longer live; keep the selected thread visible in unread-only mode after auto-mark-read. - ActivityTitlebarControls: walk stateHistory in the unread accumulator (mirrors the new feed semantics); drop worktree-created counting since the new feed has no surface to dismiss them. - AgentStateDot: remove the dead 'lg' size variant. - App: hide worktree sidebar on the Activity view and include it in the back/forward shortcut + button cluster. Co-authored-by: Orca <help@stably.ai> * fix(activity): double-buffer portal slots to remove cold-mount flash Switching threads on the Activity page used to expose xterm's empty canvas for ~133ms while a newly-mounted TerminalPane attached. Replace the single portal target with a double-buffered set of slots: keep the previously displayed terminal visible while the next selected terminal mounts in an invisible same-size slot, then flip slots once the staged slot contains the selected terminal DOM with xterm's screen and PTY binding (with one extra frame only after observing an empty mount). Skips publishing a portal target when the selected retained thread no longer has a live tab so the previous tab's target doesn't linger. Co-authored-by: Orca <help@stably.ai> * fix(activity): remove single-option Display style settings dropdown The activity page's gear menu only held a "Compact list" toggle. Drop the dropdown along with the leftSidebarCompact state, the ActivityDensity type, and the unused dropdown-menu / Settings imports. The thread list keeps the compact layout (the previous default). Co-authored-by: Orca <help@stably.ai> * fix(activity): vertically center Jump to workspace button The header row uses items-start so a 2-line prompt clamps without the badge jumping. self-center on the button overrides that just for the action so it sits in the middle of the title block instead of pinned to the top edge. Co-authored-by: Orca <help@stably.ai> * fix(activity): prefer prompt over OSC live title for thread label paneTitleForEvent put the non-default tab.title ahead of the prompt, so agent CLIs that set "Claude Code" / "Codex" via OSC pinned every row and the right-pane heading to the agent name. The dashboard row this function claims to mirror uses the prompt directly. Re-order to: customTitle (explicit rename) → prompt → non-default liveTitle → defaultTitle, so sending "hi" actually shows "hi" in the title. Co-authored-by: Orca <help@stably.ai> * fix(activity): drop extra row height when prompt is one line The thread row's right column stacked time over the count badge with a gap, which forced the row to ~48px even when the left column's prompt was a single line. Collapse the right column into one horizontal cluster (count + time/bell) so single-line rows stay tight, while two-line prompts still drive the row taller as before. Co-authored-by: Orca <help@stably.ai> * fix(activity): add top/bottom padding to thread rows Bump py-1.5 → py-2.5 so single-line rows breathe and don't sit flush against the row dividers. Co-authored-by: Orca <help@stably.ai> * fix(activity): even out optical row padding (top read heavier than bottom) Symmetric py-2.5 looked top-heavy because the title's leading-snug adds internal whitespace above the cap-height that the secondary row doesn't have below it. Trim the top to pt-2 / pb-2.5 so the row reads balanced. Co-authored-by: Orca <help@stably.ai> --------- Co-authored-by: Orca <help@stably.ai> |
||
|
|
15d51dfac2 | fix: open markdown image file links in orca (#1731) | ||
|
|
5762aab573 |
Refine Floating Terminal controls (#1726)
Co-authored-by: Orca <help@stably.ai> |
||
|
|
406304c5e2 |
Add Floating Terminal (#1724)
* Add floating terminal surface Co-authored-by: Orca <help@stably.ai> * Move floating terminal into native window Co-authored-by: Orca <help@stably.ai> * Polish floating terminal native window Co-authored-by: Orca <help@stably.ai> * Revert "Polish floating terminal native window" This reverts commit |
||
|
|
7944293815 |
Improve mobile terminal streaming performance (#1700)
* Improve mobile terminal streaming performance Co-authored-by: Orca <help@stably.ai> * Add mobile clear terminal action Co-authored-by: Orca <help@stably.ai> * Fix terminal connection test mock Co-authored-by: Orca <help@stably.ai> * WIP: mobile markdown tabs before rebase Co-authored-by: Orca <help@stably.ai> * Add mobile markdown editing Co-authored-by: Orca <help@stably.ai> * Harden mobile tab and markdown sync Co-authored-by: Orca <help@stably.ai> * Fix mobile terminal reconnect loading race Co-authored-by: Orca <help@stably.ai> * Polish mobile terminal keyboard behavior Co-authored-by: Orca <help@stably.ai> * Simplify mobile markdown editor chrome Co-authored-by: Orca <help@stably.ai> * Move mobile markdown actions to top Co-authored-by: Orca <help@stably.ai> * Use app modals for markdown discard Co-authored-by: Orca <help@stably.ai> * Dismiss keyboard before markdown confirmations Co-authored-by: Orca <help@stably.ai> * Add mobile file explorer Co-authored-by: Orca <help@stably.ai> * Fix mobile file explorer type narrowing Co-authored-by: Orca <help@stably.ai> * Fix mobile files navigation param Co-authored-by: Orca <help@stably.ai> * Show mobile files connection wait state Co-authored-by: Orca <help@stably.ai> * Preview text files on mobile Co-authored-by: Orca <help@stably.ai> * Simplify mobile file previews Co-authored-by: Orca <help@stably.ai> * Clarify unavailable mobile file types Co-authored-by: Orca <help@stably.ai> * Fix mobile subscription and preview review issues Co-authored-by: Orca <help@stably.ai> * Keep fallback terminals visible on mobile Co-authored-by: Orca <help@stably.ai> * Keep mobile terminal tap active Co-authored-by: Orca <help@stably.ai> * Preserve mobile terminal fallback order Co-authored-by: Orca <help@stably.ai> * Fix mobile session tab authority Co-authored-by: Orca <help@stably.ai> * Run mobile tests in mobile CI lane Co-authored-by: Orca <help@stably.ai> * Bump mobile app version to 0.0.7 Co-authored-by: Orca <help@stably.ai> * Allow main window IPC wiring size Co-authored-by: Orca <help@stably.ai> --------- Co-authored-by: Orca <help@stably.ai> |
||
|
|
bf1e925a1f |
perf(session): gate session-write subscriber on relevant field changes (#1720)
* perf(session): gate session-write subscriber on relevant field changes The App-level Zustand subscriber that debounces buildWorkspaceSessionPayload fires on every store update (agent status, usage refreshes, runtime title ticks, …). Each fire reset the 150ms timer, and when the timer eventually expired the rebuild crossed 70-110ms with many tabs open, tripping setTimeout violation warnings. Add a shallow reference-equality gate over the fields actually consumed by the payload so the timer only resets when those fields change. The field list is co-located with WorkspaceSessionSnapshot and locked to it via a compile-time exhaustiveness check, so adding a future snapshot field will fail to typecheck rather than silently skip the gate. Co-authored-by: Orca <help@stably.ai> * test(session): regression test for session-write debounce gate Extract the subscriber into createSessionWriteSubscriber so a vitest can drive the real Zustand store and assert which mutations cause a session write. The gate against unrelated updates (agent status, cache timers, runtime title ticks) is load-bearing for setTimeout violation budgets and the failure mode is silent — without this test, future store additions could re-introduce the regression unnoticed. Six cases lock in the contract: no write while not ready, exactly one write when ready flips, no write on unrelated mutations, exactly one write on a relevant mutation, coalescing within a debounce window, and cleanup cancels a pending timer. Co-authored-by: Orca <help@stably.ai> * perf(session): rebuild session payload from latest store state in debounce Replace the closed-over `state` snapshot captured at timer-schedule time with `store.getState()` inside the setTimeout callback. Today this is behaviorally equivalent because `buildWorkspaceSessionPayload` reads only SESSION_RELEVANT_FIELDS (the same fields gating the timer reset), but a future refactor that adds a non-relevant field read to the payload builder would silently start emitting stale values without this guard. Also tighten the cleanup test: mutate the store after `cleanup()` and assert no persist, so a regression where the timer is cancelled but the listener is left subscribed would now fail rather than pass. Co-authored-by: Orca <help@stably.ai> --------- Co-authored-by: Orca <help@stably.ai> |
||
|
|
3bfc871f6a |
(Regression) PTY config overlay source dirs (#1694)
* Fix PTY config overlay source dirs * Fix PTY config overlay source dirs * test(pty): cover startup-file config overlays Co-authored-by: Orca <help@stably.ai> * test(pty): make startup-file repro shell explicit Co-authored-by: Orca <help@stably.ai> --------- Co-authored-by: brennanb2025 <brennankbenson@gmail.com> Co-authored-by: Orca <help@stably.ai> |
||
|
|
a54436a2ac |
fix(ssh): treat symlinked directories as folders (#1717)
Co-authored-by: Orca <help@stably.ai> |
||
|
|
a9f0f130ac |
Add reload button to gh auth error help (#1711)
After running `gh auth refresh` in a terminal, users had to manually reload Orca to pick up the new token state. Surface a one-click Reload button in both the block and banner variants of GhAuthErrorHelp. Co-authored-by: Orca <help@stably.ai> |
||
|
|
82090831f6 |
Create Orca CLI terminals without stealing focus (#1707)
Co-authored-by: Orca <help@stably.ai> |
||
|
|
bec16ff11b |
fix: pr-bug-scan validated finding from #1705 (#1708)
Aligned shouldUseMacOSNativeProvider gate with send-time check by using resolveMacOSComputerUseExecutablePath, restoring symmetry so RPCs no longer throw when only the bundle exists. Co-authored-by: orca-bug-scan-bot <orca-bug-scan-bot@stably.ai> |
||
|
|
ff7de93928 |
Fix macOS computer-use helper permission checks (#1705)
Co-authored-by: Orca <help@stably.ai> |
||
|
|
daaea6acaa |
Fix terminal output lag from background panes (#1699)
* Fix terminal output lag from background panes Co-authored-by: Orca <help@stably.ai> * Fix scheduler edge cases: replay ordering, foreground gate, dispose race - Drain queued background bytes before replay/snapshot writes so the scheduler's deferred drain cannot land older bytes on top of the replay. - Gate foreground on isVisibleRef only — visible-but-inactive split panes should not be throttled; only hidden panes (background tabs) should be. - Catch writes to disposed terminals in the drain loop so a late PTY ping after pane.terminal.dispose() drops the dead entry instead of crashing the scheduler for other panes still draining. - Update App.tsx comment to drop stale agentStatusEpoch reference; epoch no longer ticks on every PTY event after the agent-status slice change. - Guard e2e Math.max(...drainWrites) against empty array to avoid a vacuous pass. Co-authored-by: Orca <help@stably.ai> * Fix flaky e2e: put background marker after burst payload The terminal-output-scheduler e2e test asserts that a background marker appears in the terminal buffer after switching to that tab. getTerminalContent returns only the last 4000 chars of the serialized buffer; with the marker prefixed before a 50000-char x-burst, the marker is always evicted and the final assertion always fails. Move the marker to the END of the burst so it survives tail truncation. The burst itself remains the same length, so the chunked-drain invariants the test exercises are unchanged. Co-authored-by: Orca <help@stably.ai> * Add terminal scheduler regression coverage Co-authored-by: Orca <help@stably.ai> --------- Co-authored-by: Orca <help@stably.ai> |
||
|
|
a81016b82d | Add experimental Activity page (#1703) | ||
|
|
c45712cead |
feat(ssh): stream fs.readFile to lift 10MB SSH preview cap (#1095) (#1676)
* feat(ssh): stream fs.readFile to lift 10MB SSH preview cap (#1095)
Replaces the single-shot fs.readFile path on the SSH relay with a
push-style stream protocol modeled on VS Code's readFileStream.
Wire shape:
- fs.readFileStream request returns metadata (streamId, totalSize,
isBinary, mimeType, chunkEncoding, resultEncoding, optional empty)
- Relay pumps fs.streamChunk notifications (256 KB base64 chunks) and
ends with fs.streamEnd or fs.streamError
- Client cancels via fs.cancelStream notification
Invariants:
- Max 16 concurrent streams per FsHandler (TooManyStreams)
- Client clamps totalSize against caps before allocating
- Sequence-number defense against out-of-order/missing chunks
- Subscribe-before-await with frame queueing until streamId is known
- Pump cleans up registry+handle in finally; disposeAll aborts before
release so in-flight reads exit cleanly instead of EBADF
- Empty files short-circuit (no streamId, no handle open)
Compat:
- New client tries fs.readFileStream first, falls back to legacy
fs.readFile on JSON-RPC -32601 (with once-per-session warn log)
- Bumps MAX_PREVIEWABLE_BINARY_SIZE 10 MB to 50 MB to match local
Tests: 91 streaming tests across relay, client, mux, integration.
Co-authored-by: Orca <help@stably.ai>
* test(ssh): wait for streamEnd instead of fixed flush() in stream test
Why: the binary-streaming test relied on 5 setImmediate ticks to drain
the pump, which is racy on slower CI runners (each handle.read is async
I/O). Swap to a deadline-bounded waitFor(streamEnd) so the test is
deterministic regardless of scheduler latency.
Co-authored-by: Orca <help@stably.ai>
* fix(ssh): preserve small binary detection in streamed reads
Co-authored-by: Orca <help@stably.ai>
* fix(ssh): rebind file watcher when connection id hydrates
Co-authored-by: Orca <help@stably.ai>
* fix(ssh): refresh explorer for update-only file creates
Co-authored-by: Orca <help@stably.ai>
* fix(ssh): recompute file watches when repo connection changes
Co-authored-by: Orca <help@stably.ai>
* Revert "fix(ssh): refresh explorer for update-only file creates"
This reverts commit
|
||
|
|
9a39b1345a |
fix(ssh): enable TCP_NODELAY on ssh2 client to eliminate per-keystroke typing lag (#1660) (#1679)
* fix(ssh): enable TCP_NODELAY on ssh2 client to eliminate per-keystroke typing lag (#1660) ssh2 leaves Nagle's algorithm on by default. For single-byte keystrokes through a remote PTY, Nagle interacts with the kernel's delayed-ACK timer and adds up to ~40 ms per keystroke — visible as the typing lag reported in #1660. OpenSSH's `ssh` client sets TCP_NODELAY whenever a PTY is allocated; this change mirrors that on the ssh2 client right after the `ready` event in doSsh2Connect, covering both initial connect and auto-reconnect. Proxy-command / proxy-jump connections (where ssh2's underlying socket is a custom Duplex over a child-process pipe) are a no-op by design, gated by the public Client.setNoDelay()'s own type guard. A discriminating log line records which path each connect took. Tests cover initial connect and a full reconnect cycle to guard against the regression class "Nagle is re-enabled because someone refactored only the initial connect path." Co-authored-by: Orca <help@stably.ai> * fix(ssh): bound relay-lost reconnect with exponential backoff When the relay exec channel keeps dying (e.g. a remote-side bug closes every fresh --connect channel right after handshake, or a stale bridge keeps being replaced), the unguarded _onRelayLost handler reconnects as fast as the network allows — spawning relay deploy attempts in a tight loop until the user force-quits. Each iteration spawns a fresh ssh2 exec channel, hammers sshd's MaxSessions counter, and floods the renderer with state churn. Add per-target exponential backoff (500ms → 15s, capped at 6 attempts) so the loop terminates instead of running forever. After the cap the session goes to 'error' state with a 'Relay channel kept dropping. Please reconnect.' message — visible in the renderer instead of an invisible failure where typing in remote terminals just stops working. Successful 'ready' resets the attempt counter only if the session stabilized for >= 5s; faster flaps preserve the counter so a flaky remote backs off rather than retrying indefinitely on every brief ready→lost cycle. Backoff state is cleared on explicit disconnect, on session replacement during reconnect, and on connect failures, so a real reconnect attempt after backoff exhaustion always starts from zero. Co-authored-by: Orca <help@stably.ai> * fix(ssh): detect stale relay daemons via running-version marker The on-disk relay version check compares local .version against the remote .version file in the relay dir. A daemon launched by an earlier deploy keeps running its in-memory copy of the OLD relay code, so when the client later rewrites relay.js + .version on disk and bridges in via --connect, the new bridge process drives a stale daemon. Protocol or behavior changes between the two versions then tear down the channel in a tight reconnect loop (observed against PR #1672 on a daemon predating that change). The daemon now writes its running version into a .running-version sidecar at startup, anchored to the relay-script directory rather than process.cwd() so test spawns cannot pollute the repo root. Before attaching to an existing socket, the client probes that marker and, on mismatch with the locally-deployed .version, kills the stale daemon (TERM only, never KILL) and falls through to a fresh launch. Conservative defaults: when either marker is unreadable, attach so older builds keep their live PTYs. Co-authored-by: Orca <help@stably.ai> * Revert "fix(ssh): detect stale relay daemons via running-version marker" This reverts commit |
||
|
|
0f54103dda |
Add native computer-use automation (#1683)
Co-authored-by: Orca <help@stably.ai> |
||
|
|
8f3c783767 |
fix(worktree): create branches with --no-track and auto-setup remote (#1563)
* WIP: Changes before auto-review fixes Co-authored-by: Orca <help@stably.ai> * WIP: Changes before auto-review fixes Co-authored-by: Orca <help@stably.ai> * fix(worktree): preserve user push.autoSetupRemote, include path in warn - Probe push.autoSetupRemote with `git config --get` before writing so a deliberate user value at any scope (local/global/system) is preserved. - Include worktree path in the warn log for failed config writes. - Add test pinning the preserve-existing-value behavior. - Remove stray 00-review-context.md committed during review tooling. Co-authored-by: Orca <help@stably.ai> * WIP: Changes before auto-review fixes Co-authored-by: Orca <help@stably.ai> * fix(worktree): narrow config --get error handling, tighten test asserts Treat only exit code 1 from `git config --get push.autoSetupRemote` as "key unset". Other read failures (corrupt config, locked file, parse error) now re-throw to the outer warn handler instead of being silently treated as unset and overwriting whatever value the user actually has. Also: add test for the non-unset read-error path; convert the "preserves existing value" test from `.some()` predicates to a full-array `toEqual` matching sibling-test style; explicitly mock `config --get` (with code: 1) in the sparse-failure rollback test so it exercises the intended branch instead of the helper's empty- stdout fallthrough; document in the design notes that addSparseWorktree's rollback intentionally does not unset push.autoSetupRemote. Co-authored-by: Orca <help@stably.ai> * test(worktree): pin --get-empty-stdout and worktree-add-fail invariants Why: addWorktree's post-create config probe has two ordering invariants worth pinning so a future refactor can't silently regress them: (1) `git config --get` succeeding with empty stdout still counts as "already set" so we don't overwrite an explicit empty value, and (2) the entire config block is skipped when `worktree add` itself rejects. Co-authored-by: Orca <help@stably.ai> * WIP: Changes before auto-review fixes Co-authored-by: Orca <help@stably.ai> * docs(worktree): cross-ref local↔SSH addWorktree, clarify SSH-host git version, add empty-stdout parity test JSDoc on local addWorktree now flags the push.autoSetupRemote side effect; both paths cross-reference each other so the next change keeps them in lockstep. Relay comment clarifies that the git version that matters is the SSH host's, not the client's. Adds the missing empty-stdout-as-already-set parity test on the relay side. Co-authored-by: Orca <help@stably.ai> * chore: remove 00-review-context.md from PR Stray file from local review workflow; should not ship in this PR. Co-authored-by: Orca <help@stably.ai> * chore: remove worktree-ssh-no-track-parity.md from PR Co-authored-by: Orca <help@stably.ai> --------- Co-authored-by: Orca <help@stably.ai> |
||
|
|
9b8324efb8 |
feat(agent-dashboard): persist hook status across Orca restart (#1480)
* feat(agent-dashboard): persist hook status across Orca restart Hydrates the hook server's per-pane lastStatusByPaneKey from userData/agent-hooks/last-status.json before binding the HTTP listener, mirrors mutations to disk via a 250ms trailing debounce, and flushes synchronously on stop(). Renderer dismissals fan out a new agentStatus:drop IPC so the on-disk file evicts the entry and a relaunch cannot resurrect it. Adds a bounded bootstrap queue in useIpcEvents so events replayed by setListener() during window creation are not dropped while App.tsx is still hydrating tabsByWorktree. Gated on settings.experimentalAgentDashboard. Done, blocked, and quiet working rows now all survive across restart. Co-authored-by: Orca <help@stably.ai> * fix(agent-dashboard): harden hook persistence IPC and gate-off deletion Address review findings on the retention-restart branch: - Wrap agentStatus:getSnapshot and agentStatus:drop IPC handlers in try/catch so a throw cannot surface as an unhandled invoke rejection (silent startup-hydration failure) or crash main from a fire-and- forget listener. - runStatusPersist no longer permanently suppresses gate-off deletion retries on transient unlink errors (e.g. EPERM); deletedOnDisable now flips only on success or ENOENT. - Tighten tests: stale-version-hydrate now asserts the warn message content; getSnapshot test uses toEqual; drop-handler test rejects null/{}/[] in addition to the prior bad inputs. Co-authored-by: Orca <help@stably.ai> * fix(agent-dashboard): bound on-disk hydrate growth and reject tabId/paneKey drift - Drop hydrate entries older than 7 days (HYDRATE_MAX_AGE_MS) so stale rows from worktrees archived weeks ago do not pile up forever. PTY- teardown eviction handles closed panes; the TTL covers daemon-restored PTYs that never re-attach and crash-recovery paths. - Reject hydrate entries whose `tabId` field diverges from the paneKey's tab segment. Cheap defensive add against future renamer/shape drift. Doc updated to move TTL out of the follow-ups list (now in scope). Tests: new "drops hydrate entries older than the TTL cutoff" and "drops a hydrate entry whose tabId disagrees with the paneKey prefix"; existing hydrate fixtures now use a `recentTs()` helper instead of fixed 2023 timestamps. Co-authored-by: Orca <help@stably.ai> * fix(agent-dashboard): post-review polish on hook status persistence Apply review-fix corrections on the agent-dashboard restart-persistence work: - Split dropStatusEntry from clearPaneState so renderer-driven dismiss IPC no longer wipes lastPromptByPaneKey/lastToolByPaneKey for a still-alive pane. - Validate paneKey shape at the IPC boundary (isValidPaneKey). - Let getSnapshot errors propagate instead of silently returning [] — matches the renderer's existing .catch and avoids masking a broken persistence path. - Trust main's authoritative timing.stateStartedAt unconditionally on same-state pings; fall back to existing only when timing is absent. - Use strict < on the snapshot/live updatedAt guard so two events in the same millisecond don't drop the second one (a <= guard regressed two existing slice tests). - Don't reset snapshotRequestedForReadyWindow in the catch handler; combined with the per-store-update subscriber it would retry-storm on persistent IPC failure. - scheduleStatusPersist now resets the timer on each call (true trailing-edge debounce) instead of leading-edge throttle. - Fix doc references that named clearPaneState in dismiss/IPC context where the implementation uses dropStatusEntry; add type-level JSDoc on AgentStatusIpcPayload. 109/109 in-scope tests pass. Co-authored-by: Orca <help@stably.ai> * fix(agent-dashboard): clean stale on-disk entries during hydrate - Defensive `lastStatusByPaneKey.clear()` at top of `hydrateLastStatusFromDisk` keeps repeat-start() calls from silently merging prior-session state. - When sanitize drops entries (drift, TTL, schema), log a single `[agent-hooks] last-status hydrate dropped N entries (kept M)` warn and synchronously rewrite the file. Pre-fix, stale entries stayed on disk until a fresh hook event triggered a debounced write — users who hadn't run an agent in 8+ days would re-drop the same entries every cold boot. - Prime `lastWrittenJson` from the raw on-disk bytes (instead of re-serializing) when hydration is lossless — robust against future shape drift in `serializeStatusFile`. - `LAST_STATUS_FILE_VERSION = 2` comment now records why v1 was skipped (in-flight branch shape). - IPC test mock uses `vi.importActual` for `isValidPaneKey` so it stays in sync with the real validator. Co-authored-by: Orca <help@stably.ai> * fix(agent-dashboard): persist acknowledgedAgentsByPaneKey across restart Without this, agent rows the user already visited come back bold every relaunch now that the rows themselves survive restart (per docs/agent-dashboard-retention-restart.md). Hydrate sanitizes input field-by-field (rejects null/non-object/array, prototype-pollution keys, non-finite/non-positive values) and applies a 7-day TTL paralleling HYDRATE_MAX_AGE_MS in agent-hooks/server.ts so hard-quit/crash paths can't grow the persisted map forever. Co-authored-by: Orca <help@stably.ai> * docs(agent-dashboard): drop in-tree retention/restart design doc Doc was a working artifact for this branch; the rationale lives in commit history and the comments next to the persistence/hydrate code. Scrubs the three call-site references that named it. Co-authored-by: Orca <help@stably.ai> --------- Co-authored-by: Orca <help@stably.ai> |
||
|
|
68892b667c |
fix(editor): remove underline beneath markdown h1/h2 headings (#1696)
H2 (and exported H1) headings rendered with a thin border below them; this removes the rule so headings sit flush with following content. Co-authored-by: Orca <help@stably.ai> |
||
|
|
1f0e060c16 |
fix(diff-comments): defer root.unmount() in editor cleanup (#1607)
When the editor is disposed during a parent render, the dispose listener's setState re-runs this effect and triggers a synchronous root.unmount() inside React's commit work loop, producing React 19's "Attempted to synchronously unmount a root while React was already rendering" warning. Snapshot the roots and clear bookkeeping synchronously, then unmount via queueMicrotask — matches the deferred-unmount pattern already used in the diff-pass effect. Co-authored-by: Orca <help@stably.ai> |
||
|
|
1f0caeb40a |
fix(updater): simplify benign check failures (#1691)
* WIP: Changes before auto-review fixes Co-authored-by: Orca <help@stably.ai> * WIP: Changes before auto-review fixes Co-authored-by: Orca <help@stably.ai> * fix(updater): repair retry-state correctness in release-transition fallback Address issues surfaced by automated multi-agent review on the 30s silent-retry + 1h backstop introduced in this branch: - forceLaunchUpdateCheck now OR-merges userInitiatedCheck instead of overwriting it, so a manual click during the 30s wait survives the timer's launch (the click's upgrade was being clobbered). - The .catch path mirrors the same OR-merge by reading the live module flag, so a synchronous throw or pinPrereleaseFeed rejection during the retry doesn't lose the click upgrade either. - checkForUpdatesFromMenu upgrades userInitiatedCheck = true before early-returning during the 30s wait, so the in-flight retry's result reflects the user's click. - Removed cross-cancellation between the 30s retry and 1h backstop callbacks: each callback only nulls its own handle, and both timers stay armed until a terminal event clears them centrally. The backstop is no longer destroyed at T+30s, restoring the app-nap recovery the design intended. - 'error' handler's non-'checking' branch now (a) clears retry state unconditionally so transitionRetryInFlight can never be stranded across a status-race, and (b) suppresses sendErrorStatus when status has already advanced to a good terminal (available/downloading/ downloaded), preventing a late backstop error from overwriting a successful retry result. - performQuitAndInstall now clears the retry timers and flag so the install-quit path (which bypasses the before-quit handler via markMacQuitAndInstallInFlight) doesn't leak a timer firing into the bundle-replacement window. Co-authored-by: Orca <help@stably.ai> * fix(updater): simplify benign check failures Co-authored-by: Orca <help@stably.ai> --------- Co-authored-by: Orca <help@stably.ai> |
||
|
|
333cf6fd6c |
update (#1692)
Co-authored-by: Orca <help@stably.ai> |
||
|
|
648f207281 |
feat(checks-panel): auto-refresh on entering Checks tab (#1688)
Force a freshness check each time the user enters the Checks tab (open sidebar, switch to Checks tab, or switch active worktree/branch) so stale PR metadata, cached-null "no PR" results, stale checks, and stale comments are surfaced immediately rather than waiting for the cache TTL. - Extracts entry-refresh logic into `checks-entry-refresh.ts` with a 30 s grace window to suppress rapid show/hide duplicate fetches. - Adds a `shouldEntryRefresh` effect in `ChecksPanel` keyed by `activeWorktreeId::repo.path::branch`; resets on panel hide so close-and-reopen re-evaluates freshness. - Fixes a stale-closure bug in `handleRefresh`: `fetchPRChecks` is now called directly with the freshly resolved `headSha` after PR refresh instead of reusing the pre-refresh closure's captured sha. - Adds 11 unit tests in `checks-entry-refresh.test.ts`. - Design doc: `docs/refresh-on-checks-tab.md`. Co-authored-by: Orca <help@stably.ai> |
||
|
|
cbf99a1c98 |
feat(sidebar): allow manual drag-and-drop reordering of repos (#1686)
* feat(sidebar): allow manual drag-and-drop reordering of repos Users can now drag repo headers in the sidebar to reorder them. The custom order is persisted to disk and survives restarts. Includes design doc at docs/manual-repo-reorder.md. Co-authored-by: Orca <help@stably.ai> * fix: scope post-drag click swallow to dragged repo header Avoid silently eating unrelated clicks if one races between pointerup and the failsafe teardown. Co-authored-by: Orca <help@stably.ai> --------- Co-authored-by: Orca <help@stably.ai> |
||
|
|
cc9f4bf083 |
feat(settings): list supported audio formats for Custom Sound (#1685)
Updates the Custom Sound search entry description and keywords to include the supported formats (MP3, WAV, OGG, M4A, AAC, FLAC), and adds a small caption under the setting's description in NotificationsPane for clarity. Co-authored-by: Orca <help@stably.ai> |
||
|
|
1041ab4f7c |
feat(agent-hooks): shared listener + relay adapter (PR 1/N for SSH agent status) (#1678)
* feat(agent-hooks): introduce relay wire envelope + connectionId stamping Adds the shared `agent-hook-relay.ts` module with the `agent.hook` JSON-RPC notification envelope, the `agent_hook.requestReplay` / `agent_hook.installPlugins` method names, and the `ORCA_FEATURE_REMOTE_AGENT_HOOKS` flag helper. Promotes `AgentHookSource` to `shared/` so the relay can import it without dragging Electron in. Threads a `connectionId: string | null` field through `AgentHookEventPayload`, the `agentStatus:set` IPC contract, and the renderer-bound preload listener. Local hook posts stamp `null`; the relay-forwarded path will stamp from `mux` identity in a later commit. Renderer uses the stamp for stale-event filtering when an SSH connection tears down with notifications still in flight. See docs/design/agent-status-over-ssh.md §1, §5, §8 (commit #1). Co-authored-by: Orca <help@stably.ai> * refactor(agent-hooks): extract shared listener; add relay-side adapter Extracts the listener internals (request parsing, payload normalization, endpoint-file writing, per-CLI extractors, warn-once Sets, slowloris timer helper, request size cap, paneKey caches) from `src/main/agent-hooks/server.ts` into a new transport-agnostic `src/shared/agent-hook-listener.ts`. The shared module uses only Node builtins (no Electron) so it is safe to import from `src/relay/`. Adds `src/relay/agent-hook-server.ts` — a thin HTTP-loopback adapter that wires the shared listener to a `forward(envelope)` callback so `relay.ts` can re-emit each parsed payload as an `agent.hook` JSON-RPC notification on the existing SshChannelMultiplexer. The adapter owns: - 127.0.0.1:0 socket + bearer-token auth, identical shape to the local server - per-paneKey last-payload cache + replayCachedPayloadsForPanes() for the request-driven replay path used after `--connect` reattach (see §5 Path 3) - clearPaneState(paneKey) for PTY-exit eviction (symmetric with local server) - buildPtyEnv() / endpoint-file writing for relay-spawned PTYs Orca's `AgentHookServer` is now a ~200-LoC adapter over the shared listener that owns the IPC fanout, listener replay, and `ingestRemote(envelope, connId)` entry point that bypasses the HTTP path for relay-forwarded events. See docs/design/agent-status-over-ssh.md §3, §8 (commit #2). Co-authored-by: Orca <help@stably.ai> * fix(preload): expose connectionId on agentStatus.onSet type src/preload/index.ts already passes through `connectionId?: string | null` from main, but the PreloadApi declaration in api-types.ts was missing the field. Align the type with the runtime contract so renderer call sites can read connectionId without an `as` cast. Co-authored-by: Orca <help@stably.ai> * fix(agent-hooks): harden ingestRemote + relay replay; review-driven cleanup - ingestRemote: re-run normalizeAgentStatusPayload at trust boundary; trim+validate connectionId/paneKey/tabId/worktreeId - relay: preserve source/env/version through replay via sidecar map; drop sourceFromAgentType fallback that mis-tagged unknown agents - shared listener: exhaustive switch+never on AgentHookSource dispatch chains; extractPromptText returns trimmed values; export MAX_PANE_KEY_LEN - preload: tighten connectionId from optional to required (always sent) - main IPC: reorder spread so explicit envelope fields win on collision Co-authored-by: Orca <help@stably.ai> * chore(docs): drop agent-status-over-ssh design doc from PR The design RFC was useful for authoring this PR series but doesn't belong in-tree — keeping it here would freeze line-number references and design prose against future churn. Folding it into the PR description instead. Co-authored-by: Orca <help@stably.ai> * chore(agent-hooks): widen ingestRemote type for env/version (PR2 prep) Declares `env?: string` and `version?: string` on the `ingestRemote` envelope parameter so PR2 only needs to add the `warnOnHookEnvOrVersionMismatch` callsite, not also widen the type. The fields are forwarded verbatim from the agent CLI POST body on the remote and let Orca's warn-once cross-build / dev-vs-prod diagnostics fire identically on remote-sourced events. Type-only addition; no runtime consumer in this PR. Co-authored-by: Orca <help@stably.ai> --------- Co-authored-by: Orca <help@stably.ai> |
||
|
|
908bc18234 |
feat(sidebar-filter): replace dropdown with searchable popover for repo filtering (#1684)
Replaces the DropdownMenu-based repo filter with a Command/Popover combo that supports live search, All/None bulk actions, and a Clear all footer. Scales to large repo sets without scroll friction. Design doc added at docs/sidebar-filter-redesign.md. Co-authored-by: Orca <help@stably.ai> |
||
|
|
f446a8b460 |
fix: pr-bug-scan validated finding from #1680 (#1682)
* fix: address pr-bug-scan validated finding from #1680 On cold open, optimistic comments are now surfaced via a loading-shell fallback in the details memo, with a state tick so the memo re-runs after appendOptimisticComment. * fix: address react-hooks lint warnings on #1680 fix-PR - handleSubmit useCallback: add missing itemType dep - details useMemo: keep optimisticTick (rerender signal for cold-open ref reads) with eslint-disable + why-comment --------- Co-authored-by: orca-bug-scan-bot <orca-bug-scan-bot@stably.ai> Co-authored-by: nwparker <neil@stably.ai> |
||
|
|
236087d25d |
fix: pr-bug-scan validated finding from #1671 (#1681)
Add legacy version-first ID branches (3-5-sonnet, 3-5-haiku) in normalizeModelForPricing so legacy logs map to existing pricing entries instead of returning null. Co-authored-by: orca-bug-scan-bot <orca-bug-scan-bot@stably.ai> |
||
|
|
c88287fa9a |
Fix Codex account auth read-back guard (#1629)
* Fix Codex account auth read-back guard Co-authored-by: Orca <help@stably.ai> * Guard Claude auth read-back identity Co-authored-by: Orca <help@stably.ai> * Fix Claude auth read-back test on Linux Co-authored-by: Orca <help@stably.ai> * Require positive Codex auth identity match Co-authored-by: Orca <help@stably.ai> * Address auth read-back review gaps Co-authored-by: Orca <help@stably.ai> * Harden managed auth read-back state machine Co-authored-by: Orca <help@stably.ai> * Harden managed auth token read-back Co-authored-by: Orca <help@stably.ai> * Isolate managed Codex launch homes Co-authored-by: Orca <help@stably.ai> * Keep managed Codex homes in sync Co-authored-by: Orca <help@stably.ai> * Revert "Keep managed Codex homes in sync" This reverts commit |
||
|
|
b501a6faa0 |
fix(github-drawer): eliminate reopen flash via useSyncExternalStore (#1680)
Replace the setState-driven data flow with useSyncExternalStore so the drawer reads cached work-item details synchronously on first render. Warm reopens now paint the cached content immediately with zero blank flash. Adds a pub/sub layer (subscribeWorkItemDetailsCache / notifyWorkItemDetailsCache) to all cache-write paths so React is notified on every touch or invalidation. Includes design doc at docs/gh-work-item-drawer-cache-flash.md. Co-authored-by: Orca <help@stably.ai> |
||
|
|
1f43346c5f |
fix(resource-usage): hydrate pty-registry at boot; render · remote only for SSH repos (#1667)
* WIP: Changes before auto-review fixes Co-authored-by: Orca <help@stably.ai> * fix: address auto-review-fix-multi-agent findings - Replace local ORCA_WORKTREE_ID_SEPARATOR with shared WORKTREE_ID_SEPARATOR - Make hydrateLocalPtyRegistryAtBoot idempotent (one-shot per process, but stays retry-eligible until daemon provider is available) - Strengthen daemon-pty-adapter strict-parser test to actually exercise the new short-circuit (test would have passed under the old loose parser too without the change) - Add eslint-disable max-lines directive to oversized merge test file Co-authored-by: Orca <help@stably.ai> * chore: archive auto-review context to .context/ Co-authored-by: Orca <help@stably.ai> * fix: address auto-review-fix findings Drop the destructive reconcileOnStartup call from boot-time PTY registry hydration: a transient listRepoWorktrees failure (returns [] and only warns) would otherwise let the reconcile pass kill live local sessions. The boot path is now read-only against the daemon — listSessions() only. Also: tighten parsePtySessionId to reject degenerate `::` halves; replace stale pty.ts:1005 references and a misleading local-unknown comment in the hydrate module; narrow Store dependency to Pick<Store, 'getRepos'>; log adapter listSessions failures instead of silently swallowing them; re-anchor design-doc references on stable symbols and align §1b/§1c/§1d with the implementation. Co-authored-by: Orca <help@stably.ai> * docs(resource-usage): update remote badge spec Co-authored-by: Orca <help@stably.ai> * test(resource-usage): cover boot hydration failure modes + warm-reattach e2e Adds the regression coverage flagged in PR #1667's test plan that wasn't already locked down. vitest (`hydrate-local-pty-registry.test.ts`): - daemon offline at first call → no-op, hasHydrated stays false so a later macOS dock re-activation can retry. - listSessions rejection caught and logged, does not throw. - pid-write ordering: a pre-existing registry entry with pid=12345 is not clobbered by a stale `pid: null` from listSessions (§1d). - SSH-gate: a session whose repo has a non-null connectionId stays out of the registry, mirroring the spawn-time gate in pty.ts. - Happy-path: a local session is registered with the daemon's pid. Playwright e2e (`resource-usage-warm-reattach.spec.ts`): Full quit→relaunch cycle against the same userDataDir; asserts that on the second launch the snapshot includes the warm-reattached PTY with a real pid before any pane mount, and that the seeded repo resolves as local (no connectionId). Mirrors the existing terminal-restart-persistence pattern. Co-authored-by: Orca <help@stably.ai> * fix(test): satisfy Pick<Store, 'getRepos'> in hydrator vitest CI typecheck failed because FakeStore's getRepos returned objects missing Repo's required fields (path, displayName, badgeColor, addedAt). Fill with placeholder values; the hydrator only reads id + connectionId, but the type signature still has to line up. Co-authored-by: Orca <help@stably.ai> * chore(resource-usage): drop bug-doc files; strip dead doc refs from comments Remove docs/resource-usage-remote-mislabel.md (new in this PR) and revert docs/resource-usage-merge-spec.md to the PR-base state. Strip the matching `docs/...md §N` pointers from code/test comments, keeping the surrounding "why" explanations intact so readers still get the warm-reattach mislabel context. Co-authored-by: Orca <help@stably.ai> --------- Co-authored-by: Orca <help@stably.ai> |
||
|
|
ad5d5dc841 | fix terminal complex script rendering (#1675) | ||
|
|
01bab271eb |
Fix PTY config overlays being overwritten by shell startup files (#1628)
* Fix OpenCode config overlay env in PTYs * Fix Pi agent dir overlay env in PTYs * Restore overlay env in Windows and fallback shells Co-authored-by: Orca <help@stably.ai> * test(opencode): tighten overlay restore guards Co-authored-by: Orca <help@stably.ai> --------- Co-authored-by: Orca <help@stably.ai> Co-authored-by: brennanb2025 <brennankbenson@gmail.com> |
||
|
|
9290b25307 |
fix: gate worktree status on live PTYs so sleep reports inactive (#1603)
* fix: gate worktree status on live PTYs so sleep reports inactive Sleep preserves tab.ptyId as a wake-hint sessionId, so the previous liveness check (`tab.ptyId != null`) kept the workspace dot green and agent rows as "working" until the 30-min stale TTL decayed them. Switch liveness to ptyIdsByTabId (cleared by every pty.kill / sleep) via a new tabHasLivePty helper, and drop live agentStatusByPaneKey entries on sleep so the inline rows disappear with the dot. Retained "done" rows survive — that signal is dismissed by the user, not the system. Co-authored-by: Orca <help@stably.ai> * fix: drop retained agent rows on worktree sleep Co-authored-by: Orca <help@stably.ai> * WIP: Changes before auto-review fixes Co-authored-by: Orca <help@stably.ai> * fix: preserve slept worktree status liveness Co-authored-by: Orca <help@stably.ai> * fix: treat slept pty hints as inactive Co-authored-by: Orca <help@stably.ai> * chore: remove sleep status planning docs Co-authored-by: Orca <help@stably.ai> --------- Co-authored-by: Orca <help@stably.ai> |
||
|
|
ea9a718e29 |
fix(ssh): remove relay FS path allowlist to support symlinks outside workspace (#1661) (#1672)
When a remote SSH workspace contains a symlink whose target lies outside
the registered repo/worktree roots, file reads failed with 'Path outside
authorized workspace'. This silently broke common workflows: HPC dataset
mounts, multi-checkout repos, dotfile editing, and any cross-mount
symlink.
Drop `RelayContext.authorizedRoots`, `validatePath`, and
`validatePathResolved` along with all ~33 call sites in fs-handler.ts
and git-handler.ts. The relay's threat model becomes 'the relay runs as
the SSH user and trusts the renderer.'
Why this is acceptable: `pty.spawn` and `git.exec` already concede the
same threat. A renderer that wants to reach `/etc/passwd` can spawn a
shell or run `git -C /etc cat-file`; the FS allowlist was friction, not
a security boundary. Intra-worktree path checks in `getDiff` and
`discard` are intentionally preserved.
Back-compat preserved: `session.registerRoot` (notification + request)
remains a valid RPC, retained as no-ops on new relays. Old main + new
relay and new main + old relay both keep working through the upgrade
window. `registerRelayRoots` is also kept for the same reason. A
narrowed error-translation block in `worktree-remote.ts` handles old
relays still surfacing the legacy error string to users.
Tests: removed two negative-allowlist tests; added a positive control
('reads files outside any registered root') and a direct regression
test for #1661 ('reads files via symlinks resolving outside the
workspace'). All 469 relay/SSH/IPC tests pass.
See docs/relay-fs-allowlist-removal.md for the full rationale,
back-compat matrix, alternatives considered, and follow-up cleanup
plan.
Closes #1661
Co-authored-by: Orca <help@stably.ai>
|
||
|
|
98b15aeabc |
feat(telemetry): instrument on_path:false triage on onboarding_agent_picked (#1674)
* feat(telemetry): instrument on_path:false triage on onboarding_agent_picked Adds path_source and path_failure_reason to onboarding_agent_picked so the ~30% on_path:false rate on dashboard 1562016 can be split between shell hydration failures and genuinely-not-on-PATH cases before picking a fix. See docs/agent-on-path-detection.md. Co-authored-by: Orca <help@stably.ai> * fix(telemetry): close PathSource compile-time-sync hole Add `_PathSourceSync` guard mirroring `_PathFailureReasonSync` so adding a new `PathSource` value to the alias without updating the schema (or vice versa) fails the build. Without it, drift would silently drop `onboarding_agent_picked` at the strict validator. Also replace stale line-number references in docs/agent-on-path-detection.md with named function/handler references that survive future edits. Co-authored-by: Orca <help@stably.ai> --------- Co-authored-by: Orca <help@stably.ai> |
||
|
|
24c1254caa |
feat(source-control): add Stage Files primary button (#1670)
When there are unstaged/untracked changes but nothing staged yet, the Source Control primary button now reads "Stage Files" and bulk-stages all unstaged + untracked paths in one shot, so users can immediately hit Commit on the next click without manually staging first. This replaces the previous behavior where Pull/Sync/Push/Publish could appear as the primary on a dirty tree and then fail with "Please commit or stash them". Co-authored-by: Orca <help@stably.ai> |
||
|
|
36ee48fd9c | fix(claude-usage): correct Anthropic model pricing (#1671) | ||
|
|
8977c7e917 |
feat(telemetry): track agent_hook_install_failed per agent (#1668)
* feat(telemetry): track agent_hook_install_failed per agent Replaces the closure-style installer loop in `src/main/index.ts` with a labelled `runManagedHookInstallers` so each catch can attribute the failure to its agent. Adds the `agent_hook_install_failed` event + `hookInstallAgentSchema` enum (claude/codex/gemini/cursor) and a unit test pinning fail-open semantics, label routing, and the 200-char error_message truncation. Co-authored-by: Orca <help@stably.ai> * fix(telemetry): harden agent-hook installer fail-open - describeError always returns a string (JSON.stringify can return literal undefined for throw undefined / Symbol / function, which would crash the catch handler before track fires) - wrap track() in inner try/catch so a telemetry-side throw can't abort the installer loop - dedupe AGENT_HOOK_TARGETS into one tuple in agent-hook-types so the IPC AgentHookTarget type and hookInstallAgentSchema can't drift - regression tests for object/undefined throws and track-throws Co-authored-by: Orca <help@stably.ai> --------- Co-authored-by: Orca <help@stably.ai> |
||
|
|
4ded9e6e9d |
fix(codex-usage): correct Codex model pricing table (#1669)
Update MODEL_PRICING to current Codex rates: add gpt-5.1, gpt-5.4, gpt-5.5; rename gpt-5.2-codex -> gpt-5.2 with corrected rates; align gpt-5.3-codex rates. Stop aliasing gpt-5.4 to gpt-5 in normalizeModelForPricing. Co-authored-by: Orca <help@stably.ai> |
||
|
|
a18e5c97a5 |
feat(diff-viewer): scroll to first change when opening a diff (#1620)
* feat(diff-viewer): scroll to first change when opening a diff On a fresh diff tab open (no cached view state, no pending scroll-to-note), center the first diff change in the viewport. Cached view state and explicit scroll-to-note requests still win. The scroll runs from a dedicated useEffect, not from handleMount, so it sequences after the comment-decorator inserts its view zones — otherwise late zone insertion shifts content downward and the user lands on a note further down the file instead of the first change. Uses getTopForLineNumber(line, /* includeViewZones */ true) so the math accounts for whatever zones the decorator added in this render pass. A one-shot ref guards against re-firing on later effect re-runs. Co-authored-by: Orca <help@stably.ai> * test(window): mock ipcMain.handle/removeHandler in createMainWindow.test The pr-bug-scan from #1583 added an ipcMain.handle('window:isMaximized', …) call to createMainWindow but didn't extend this test's electron mock. Main already added these mocks (PR #1634); add them here so the branch's CI passes. Co-authored-by: Orca <help@stably.ai> --------- Co-authored-by: Orca <help@stably.ai> |
||
|
|
7212118437 |
fix(windows): tab row overlaps window-controls buttons (#1664) (#1665)
* fix(windows): tab row drag region overlaps window-controls overlay The top-right tab group's no-drag spacer was hardcoded to 40px (for the floating sidebar toggle). On Windows the fixed-position window-controls overlay (138px wide) sits on top of the same corner, making the buttons unreachable when the tab row's drag surface extended under them. Widen the spacer to calc(40px + var(--window-controls-width, 0px)) so it punches a hole for both the sidebar toggle and the overlay. The var is 0px on non-Windows so this is a no-op there. Fixes #1664. * fix(windows): remove double-compensation spacers from toggle and sidebar header Two places were using both a CSS-based offset AND an internal spacer div, doubling the reserved width (276px instead of 138px): 1. Floating right-sidebar toggle (workspace view): the container already uses right:var(--window-controls-width) to position itself clear of the overlay. The extra window-controls-titlebar-spacer inside was pushing the button further left AND laying an invisible 138px div over the pane-actions Ellipsis button, blocking clicks. 2. Right sidebar header (both layout modes): the header already has right-sidebar-header-inset = padding-right:var(--window-controls-width). The internal spacer was double-compensating, shifting the close button far left with wrong spacing. Remove the internal spacers from both. The single offset mechanism in each case is sufficient. * fix(windows): side-mode sidebar header has wrong gap before minimize button In side activity-bar mode the 40px icon strip sits to the right of the panel content, so the panel header never reaches the window-controls zone. The right-sidebar-header-inset class (padding-right: 138px) was still applied, pushing the close button 178px from the window edge and producing a 40px visual gap between the close button and the minimize button. Remove the inset class from the side-mode header only. The top-mode header spans to the window edge so it still needs the inset. * fix(windows): side-mode header close button overlaps minimize button The 40px side activity bar absorbs only 40px of the 138px window-controls overlay, leaving 98px of overlap on the panel header. The previous commit removed all inset (making the gap 0), which caused the close button to sit under the minimize button. Add .right-sidebar-header-side-inset with padding-right: max(0px, calc(var(--window-controls-width, 0px) - 40px)) = 98px on Windows, 0px elsewhere — exactly the uncovered remainder. * fix(lint): remove unused isWindows variable in right-sidebar --------- Co-authored-by: Neil Parker <nwparker@anthropic.com> |