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>
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>
* 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>
* 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>
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>
* 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>
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>
* 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>
* 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>
* 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>
Reopening a GitHub issue/PR drawer paid full IPC + `gh` startup latency on
every open. Two changes here:
1. Module-level SWR cache in GitHubItemDialog.tsx keyed by
(repoPath, issueSourcePreference, type, number). Reopening within 30s
paints cached data instantly; older entries paint stale-then-refresh.
Concurrent opens dedupe on a shared in-flight promise. Mutation
handlers invalidate by (repo, type, number); a cache-generation
counter prevents in-flight refetches from resurrecting stale data
after a mid-flight invalidation.
2. Collapsed GraphQL query for issue details replaces 3 serial `gh`
subprocesses (REST issue + REST comments + GraphQL participants) with
one round-trip. Falls back to the legacy fan-out on any GraphQL error
so historical contract is preserved.
Cross-window invalidation rides a new `gh:workItemMutated` IPC broadcast
that skips the originating sender (the source already updated its cache
optimistically — re-broadcasting would race the optimistic write).
`addIssueComment` now takes a `type` so the broadcast scopes correctly
when a PR shares its number with an issue.
Co-authored-by: Orca <help@stably.ai>
* fix(status-bar): hide usage bars for agents not on PATH
When a CLI like Gemini isn't installed, the corresponding usage bar (and
its right-click / Settings toggle) was still surfaced — e.g. a fresh
Ubuntu install showing 'Gemini Usage' with no Gemini CLI on PATH.
Gate the Claude/Codex/Gemini bars + toggles on the existing
preflight.detectAgents() result. OpenCode Go is intentionally exempt
(it's a web/cookie-auth provider, not a CLI on PATH). Pre-detection
(null) preserves legacy behavior so nothing flickers on cold start, and
bars/toggles re-appear automatically once the CLI shows up on PATH.
Co-authored-by: Orca <help@stably.ai>
* fix(status-bar): refresh agent detection alongside rate-limit refresh
Self-review follow-up: when a user installs a new CLI (e.g. Gemini) after
Orca is already running, clicking the status-bar refresh button should
make the corresponding usage bar appear without an app restart.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Orca <help@stably.ai>
When the active surface was a browser tab (or focus was inside a
browser guest webview), Cmd/Ctrl+T was creating a new browser tab
instead of a terminal. Make Cmd/Ctrl+T always open a new terminal
regardless of active surface; Cmd/Ctrl+Shift+B remains the dedicated
new-browser-tab shortcut.
Co-authored-by: Orca <help@stably.ai>
* refactor(sidebar): tidy worktree list group header layout
Move the collapse chevron next to the group label, swap repo icon for a neutral muted Folder, and adjust header padding for better alignment.
Co-authored-by: Orca <help@stably.ai>
* style: minor padding tweaks on worktree group header
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Orca <help@stably.ai>
Sharpens the routing boundary so agents reliably pick the right skill:
orchestration owns agent-to-agent messaging (writes that target another
AI agent's terminal), while orca-cli owns worktrees, browser, and
non-agent terminal I/O — including reads and waits on agent terminals.
Validated against adversarial routing tests.
Co-authored-by: Orca <help@stably.ai>
* fix(skills): remove broken doc references in orca-cli SKILL.md
Three files listed under ## References do not exist in the repository:
docs/orca-cli-focused-v1-status.md, docs/orca-cli-v1-spec.md, and
docs/orca-runtime-layer-design.md. Agents following the skill would hit
dead links when trying to resolve ambiguous CLI behavior. Replace the
stale list with a general pointer to the docs/ directory.
Co-Authored-By: Claude Code <noreply@anthropic.com>
* docs: remove stale Orca CLI doc index
---------
Co-authored-by: claude[bot] <claude[bot]@users.noreply.github.com>
Co-authored-by: Claude Code <noreply@anthropic.com>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
The buf0-bot fix-PR #1572 (findings from #1524) re-added a 30s
focus-gated fetchWorktrees poll in useGitStatusPolling that #1524 was
specifically removing. The other two findings in #1572 (worktrees:list
auth-cache poisoning and detached-HEAD encoding) are preserved.
Why: parent PR #1524's intent was to reduce background polling; the
bot's 'restoration' was an unintended behavior re-add framed as a bug
fix.
Co-authored-by: orca-bot <bot@stably.ai>
Fix mixed terminal/editor/browser tab context menu actions by resolving visible ids back to unified tab ids and counting all visible tab kinds for Close Others.\n\nAdds regression coverage for TabBar menu wiring and TabGroupPanel visible-id resolution.