mirror of
https://github.com/stablyai/orca.git
synced 2026-10-07 16:02:29 +00:00
stack-foundation
2191
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
8f7692aa12 |
Fix packaged skills CLI runtime ownership (#11627)
* fix(cli): make packaged skills runtime self-contained * fix(cli): address packaged skills review feedback * ci(cli): smoke packaged skills on Windows --------- Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com> |
||
|
|
554892b184 |
fix(github): classify all blocking check conclusions (#11071)
Co-authored-by: Mubashir Rahim <mubashirrahim3431@gmail.com> |
||
|
|
eb35c7fa3e |
[P2] fix(runtime): stop broadcasting terminalSideEffects to clients without consumers (#11619)
* fix(runtime): stop broadcasting terminalSideEffects to clients without consumers Co-authored-by: Orca <help@stably.ai> * fix(runtime): keep mobile subscribers counted for side-effect availability Excluding phones from the consumer-availability count added a new flip edge (last desktop client leaving a phone-attached host), and the flip's tracker rebuild cancels armed stale-working-title timers — stranding a 'working' spinner on the phone. Availability counts all subscribers again; the broadcast fix stays in the per-listener fan-out skip, now applied inside the delivery callback so live-Set unsubscribe semantics and allocation-free iteration are preserved. Co-authored-by: Orca <help@stably.ai> * fix(runtime): separate mobile title tracking from side-effect scans --------- Co-authored-by: Orca <help@stably.ai> |
||
|
|
6442a9f649 |
fix(persistence): backfill the jira-issue workspace-card property for upgraded profiles (#11618)
Co-authored-by: Orca <help@stably.ai> |
||
|
|
90692ba40b | fix(daemon): preserve audit evidence polarity (#11626) | ||
|
|
d4cfee76be |
Add audit-only daemon incarnation evidence (#11606)
* feat(daemon): add audit incarnation evidence * fix(daemon): isolate audit evidence observers |
||
|
|
5165cd1e19 |
fix(browser): scope Cmd/Ctrl+F find to the focused split (#11348) (#11351)
* fix(browser): scope Cmd/Ctrl+F find to the focused split (#11348) The browser pane's renderer-path Find handler is a window-global capture-phase keydown listener, but it armed on `isActive` (the active tab within its own group) rather than on whether its split holds focus. In a terminal+browser split, the browser was therefore `isActive` even while the terminal held keyboard focus, so it swallowed Cmd/Ctrl+F and opened find-in-page in the browser instead of find-in-terminal. Thread a focused-split signal (`isFocused`) from BrowserPaneOverlayLayer — derived from `activeGroupIdByWorktree` — down to the Find handler and gate the listener on it. This mirrors how terminal leaves already gate global shortcuts via `focusedGroupId` in TabGroupSplitLayout. Floating browser panels omit the prop and fall back to `isActive`, preserving their behavior. The IPC path (webview guest focused) is unchanged; it only fires when the guest genuinely has focus. Not platform-specific: the chord resolves through `keybindingMatchesAction` (Mod -> metaKey on macOS, ctrlKey elsewhere), so the same path is fixed on macOS, Linux, and Windows. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(browser): preserve Find before split focus settles * fix(browser): handle stale focused split IDs * fix(browser): route guest Find to source page * test(browser): wait for split address bar * test(browser): focus split before Find routing --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com> |
||
|
|
6f3845baa4 |
fix(checks): rank successful checks above skipped and neutral (#11337)
* fix(checks): rank successful checks above skipped and neutral Checks were ordered with `skipped` (4) and `neutral` (3) ahead of `success` (5), so a PR with a long tail of skipped jobs pushed every passing check below the fold — you scroll past a wall of "Skipped" to find out whether anything actually ran. Rank the no-signal conclusions last (`success` 3, `neutral` 4, `skipped` 5) and pull the order out of its three duplicated copies (checks-panel-content, PullRequestPage, GitHubItemDialog) into `src/shared/pr-check-severity-order.ts`. Unknown conclusions now sink to the bottom instead of silently ranking as `neutral`. * fix(checks): look up check ranks through a Map, not an object literal An object-literal rank table resolves `constructor`, `toString`, and `__proto__` off Object.prototype, so those keys returned a function instead of falling through to UNKNOWN_CHECK_RANK — the comparator then subtracted functions, went NaN, and left the list in arbitrary order. Conclusions come from provider payloads, so keep the lookup on a Map and cover prototype property names in the test. * test(checks): cover provider-neutral ordering states * fix(checks): preserve actionable provider states * fix(checks): preserve unresolved provider rollups * fix(checks): keep unknown GitLab rollups neutral * fix: preserve neutral review check summaries * fix: complete provider-neutral check ordering remediation * fix: use provider-neutral mobile review status input * fix: hydrate GitLab mobile review status * fix: type mobile GitLab review hydration --------- Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com> |
||
|
|
8ea8fe1a6d |
fix(skills): stop a project's own skill copy driving global freshness (#11474)
* fix(skills): stop a project's own skill copy driving global freshness A pristine global install plus a drifted copy inside a work directory showed Settings -> Computer Use as amber "Needs attention", with the copy labelled "may be modified ... Remove it if you want Orca to update this skill", while the same page said "Computer Use is ready" and the update command correctly printed "All global skills are up to date". No user action could clear it. Orca's updater only ever passes --global, so a copy a project owns has no remedy by design. Two defects made it drive the global verdict anyway: - locationChip tested byte status before topology, so an unrecognized repo-scope placement returned early and never reached its 'in-a-repo' case. Because SKIPPED_REASON_PRIORITY ranks unrecognized above in-a-repo, the summary sentence was wrong too. - isSkillCopyNeedingAttention excepted plugin-cache but not repo-scope. Stated by scope rather than by byte status: an outdated or unreadable project copy is as far outside the global updater's reach as an unrecognized one, so pinning only the reported status would leave the same bug reachable through another. Chip precedence is now explicit -- a read failure outranks ownership so that rule cannot hide a real fault, and ownership outranks byte status. Ownership suppresses the group, never a location's visibility: a project copy is still listed whenever another placement earns the row. The badge predicate deliberately omits the shared helper's outdated carve-out, so a non-eligible outdated copy stays amber. Collapsing the two into one predicate would flip that to green while the dialog still shows its reinstall row, so the distinction is preserved and pinned by a regression guard. Eligibility needed no change: it already filters to convergent placements. * fix(skills): keep a project copy from explaining a global skill's skip Review follow-up. The chip and the group no longer treat a project-owned copy as global drift, but three surfaces still read it as one: - skippedReason derived its one sentence from the highest-priority chip among a group's locations, with 'in-a-repo' ranked above 'duplicate', 'external-link', 'broken-link' and above the no-chip case that hands over the reinstall command. A repo-scope copy can no longer earn a group, so whenever it won it explained a skip it had no part in — and swallowed the one runnable remedy. SkillLocationRow now carries whether the update judged it, and only judged rows explain. That also covers the scan-limit sentinel, which is repo-scope and chips 'inaccessible'. - hasSkillCopyNeedingAttention counted project copies as the presence that makes a plugin-cache read failure a skill's problem, while the status function skipped them — the disagreement the two exist to prevent. - The nudge mixed project copies into its dismissal fingerprint, so re-checking out a repo re-raised a nudge the user had already dismissed. plugin-cache is untouched: it stays on the judged side everywhere, because updating the plugin is a remedy a project copy does not have. |
||
|
|
9e0a9ebc7d |
fix(pty): do not create unused Pi/OMP home dirs on bare shells (#10198)
* fix(pty): do not create unused Pi/OMP home dirs on bare shells Bare terminals used to materialize ~/.pi/agent and ~/.omp/agent (and install managed extensions) for possible later shell-launched agents. Users who never use those agents still saw the directories recreated after deletion. Only create the default agent home when launching that agent explicitly, or when the home already exists. Bare-shell OMP status still uses the userData fallback so typed `omp` keeps the shell wrapper extension. Closes #10196 * fix(relay): OMP bare-shell status fallback without ~/.omp CodeRabbit: relay materializePi returned null on bare shells with a missing OMP home, so SSH PTYs never set ORCA_OMP_STATUS_EXTENSION. Local already wrote a userData-managed status extension in that case. Write the status file under ~/.orca-relay/omp-managed-status-extension and return MaterializePiResult so relay.ts can export ORCA_OMP_STATUS_EXTENSION without ORCA_OMP_SOURCE_AGENT_DIR or creating ~/.omp. Also fix the local withOrcaManagedExtensionMarker typo on the bare-shell path. * fix(pty): only materialize Pi home for Pi launches --------- Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> |
||
|
|
650dd48ec9 |
feat(cli): add orca account add / account list for headless hosts (Claude + Codex) (#9177)
* feat(cli): add `orca account add` / `account list` for headless hosts The desktop "Add account" UI is disabled when the renderer drives a remote runtime (isRemoteAccountScope === kind:'environment'), so a headless server reached from a remote desktop/web client has no way to register managed Claude accounts. Add a host-local CLI path that reuses the existing capture logic: - ClaudeAccountService.addAccountFromConfigDir(): register a managed account by capturing credentials from an already-authenticated CLAUDE_CONFIG_DIR instead of spawning the interactive browser login (extracted persist/rollback helpers shared with the existing add flow) - RPC accounts.addClaudeFromConfigDir, bridged via OrcaRuntime; rejected for mobile device tokens (host-local only) - `orca account add` runs `claude login` in the user's own terminal into a temp CLAUDE_CONFIG_DIR, then registers it via the local runtime; `orca account list` lists managed accounts Switching (select) already works from a remote client; only adding was blocked. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(cli): support Codex in `orca account add` / `account list` Mirror the Claude headless-account CLI for Codex: - CodexAccountService.addAccountFromHome(): register a managed Codex account by importing auth.json from an already-authenticated CODEX_HOME, reusing a shared persist helper extracted from doAddAccount (no interactive login spawned here) - RPC accounts.addCodexFromHome + OrcaRuntime.addCodexAccountFromHome bridge, rejected for mobile device tokens (host-local only) - `orca account add --agent claude|codex` (default claude); `orca account list` now renders both Claude and Codex managed-account blocks Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test: cover headless account-add capture paths (Claude + Codex) - ClaudeAccountService.addAccountFromConfigDir: registers a managed account by capturing an authenticated CLAUDE_CONFIG_DIR; rejects and rolls back when the dir has no .credentials.json - CodexAccountService.addAccountFromHome: imports auth.json from an authenticated CODEX_HOME into a managed account; rejects when auth.json is missing Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix: address CodeRabbit review on headless account-add flows - CLI login spawn uses a shell on Windows so `.cmd` agent shims resolve without ENOENT (args are fixed literals, no injection risk) - Claude capture skips the `.credentials.json` precheck on macOS, where creds live in the Keychain and captureAuthFromConfigDir reads them - Claude add rollback is best-effort: a failed rematerialization no longer skips managed-auth cleanup or masks the original add error - Codex persist restores the prior account/selection if a post-write sync or rate-limit refresh fails, so a failure can't leave a dangling managed account - Codex sync passes the account's selection target (correct runtime for WSL) - Add JSDoc to the new public service methods and CLI functions Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(cli): harden headless account capture * fix(cli): correct account command flag surface and interrupt cleanup - `account` commands no longer accept or advertise the browser `--page` flag; `supportsBrowserPageFlag` allow-listed them by omission, so `orca account list --page x` was silently accepted and `--help` rendered a browser-only option - account specs declare GLOBAL_FLAGS, so `--help`/`--json` render in the Options block like every other command - `--agent` on `account add` documents the account provider instead of the terminal TUI-agent meaning inherited from the shared flag table - a SIGINT/SIGTERM during the interactive login now removes the temp login dir (and restores the macOS Keychain item) before exiting 130; Node terminates without unwinding `finally`, which stranded live OAuth credentials on disk * perf(cli): stop `account list` forcing a provider usage refresh `accounts.list` awaited refreshAccountsForMobile(), which runs fetchAll({ force: true }) — bypassing both the poll throttle and the per-provider Retry-After gate — then O(N) serial per-account round trips. `orca account list` renders only emails and the active ids, so all of that work was discarded. The RPC now takes `refreshUsage` (default true, so mobile and web keep the forced lane) and the CLI opts out. Older hosts declare `params: null` and ignore the field, so a newer CLI degrades to the previous behavior rather than failing. Also documents on `account list` that `--environment` does not retarget it, matching the host-local behavior of shouldIgnoreRemoteSelection. * fix(cli): survive repeated and hangup signals during account add withInterruptCleanup latched cleanup behind a boolean, so a second signal got an already-resolved promise and its process.exit fired while the first cleanup was still inside a Keychain call (3s each) — the temp dir's OAuth credentials and the swapped macOS Keychain item both survived. Memoize the cleanup promise so every signal awaits the same run, and register with `on` instead of `once` so a second Ctrl-C cannot fall through to Node's terminate-immediately default mid-cleanup. Handle SIGHUP too. This flow exists for headless/SSH hosts, where the most likely interrupt is the connection dropping, which hangs up the login's terminal and previously ran no cleanup at all. Warn when the interrupt lands after sign-in completed: the runtime finishes the add independently of this process, so exiting 130 silently would tell the user it was cancelled when the account may exist. Reject a valueless `--agent`; the parser turns it into boolean true, which silently ran a full OAuth login for Claude when the user asked for another provider. Also lock two behaviors the refactor changed but left uncovered: a WSL Codex add must sync the WSL runtime lane rather than the default host lane, and rename the account-spec help test to describe the Options block it actually asserts rather than the usage string it never reads. * fix(build): bundle the main modules the account CLI imports electron-vite cleans out/main and emits only its declared entries, and `build:desktop` runs it after `build:cli`, so the tsc-emitted copies of `claude-accounts/keychain`, `codex-cli/command` and `win32-utils` were deleted before packaging. Both `orca account add` and `orca account list` then died at require time with "Cannot find module '../../main/claude-accounts/keychain'" — reproduced against a real `--serve` host. `agent-hooks/managed-agent-hook-controls` already carried an entry for exactly this reason; these three were missing. Adds a parity test so any future CLI import of a `src/main` module fails in CI rather than at a user's shell after packaging. * test: cover the desktop add-path behavior this PR changes Both changes ride in the persist/rollback helpers the existing GUI add flow shares with the new headless path, and neither had coverage: - Claude: rollbackAddAccount now guards forceMaterializeCurrentSelection- ForRollback, so a rejecting rematerialization no longer replaces the real add error nor skips safeRemoveManagedAuth. Asserts the original error surfaces and the throwaway auth dir is gone. - Codex: the desktop add now passes the account's selection target to syncForCurrentSelection, matching reauthenticate and select. Asserts the host target alongside the existing WSL assertion. Both fail when the corresponding change is reverted. * fix(cli): close the remaining account-add interrupt and preflight gaps The round-1 interrupt fix detached the signal handlers before running the finally-path cleanup, so the very window it was meant to protect — the two serial 3s `security` calls plus rmSync on the success/error path — was still covered only by Node's terminate-immediately default. Both review lanes reproduced it independently. Await cleanup first, detach in a nested finally, and stop a cleanup failure from replacing the error that actually explains why the add failed. Do not burn the interactive login when the runtime is unreachable. The RuntimeClient is lazily constructed and the first call was the registration RPC itself, so "Requires the Orca runtime to be running" was discovered only after the user completed a full OAuth round trip. Preflight with the now-cheap `accounts.list { refreshUsage: false }`. Reject `--environment` / `--pairing-code` on `account add`. shouldIgnoreRemoteSelection pins account commands to the local runtime, so `orca account add --environment homelab` silently registered the account on the laptop instead of the headless host it names. Survive a daemon that cannot spawn `claude`. `allowFailure` is honored in onClose but not onError, and unlike the GUI flow nothing has run `claude` in the daemon before this point — so a launchd/systemd daemon with a minimal PATH hard-failed an add the user had already signed in for, even though identity resolves fine from the config dir's oauthAccount. Also align the `--agent` help description with the global flag column. * fix(cli): reject runtime selectors on `account list` too `orca account list --environment homelab` was accepted and silently listed the LOCAL machine's accounts, because shouldIgnoreRemoteSelection pins account commands to the local runtime. Documenting that in --help does not reach someone who already typed the flag, and answering with the wrong host's accounts is the specific wrong answer they would act on. `account add` already errors; this makes the new command group internally consistent. The other groups in shouldIgnoreRemoteSelection keep their existing silent-ignore behavior — changing those is not this PR's job. * test: harden account-add signal tests and cover cleanup failure - Identify the handler under test by set difference instead of `process.listeners(sig).at(-1)`. Vitest installs its own once-wrapped SIGINT teardown, so the positional lookup could grab the wrong listener; the helper also asserts exactly one new listener was added. - Mock rmSync while keeping the real implementation by default, so the temp-dir assertions elsewhere stay honest. - Cover that a cleanup failure in the `finally` does not replace the error explaining why the add failed. Fails when that guard is removed. Completes the review loop's final round; the loop died on an API error before it could commit this, and its `import()` type annotation would have failed oxlint. * fix(cli): harden interactive account add * test(cli): make account cancellation coverage portable * fix(cli): preserve merged skills runtime modules --------- Co-authored-by: Dominik <marketing@gavaplast.sk> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> |
||
|
|
676ef7fab8 |
feat(cli): add orca skills install and orca skills update for headless skill setup (#9201)
Adds `orca skills install` and `orca skills update` so skills can be set up without the GUI — SSH hosts, containers, CI. Previously `orca skills` had only `list` and `get`, so there was no headless path. **Agent targeting is scoped explicitly rather than delegated to detection.** The `skills` CLI decides which agents to install into, and with `-y` and zero detected agents it takes `targetAgents = validAgents` — all ~75. That is not a corner case for a headless CLI: a fresh SSH box or container with no agent installed is the normal starting state. Measured on a bare host, the unscoped command created **52 top-level agent directories and 54 junctions** (one real payload in `~/.agents/skills`, the rest links) on Windows, and 52/53 on macOS. The CLI now passes `--agent` derived from Orca's own detection, mapped to the `skills` key namespace, plus `universal`. Supplying `--agent` makes `runAdd` use it directly and never call `detectInstalledAgents()`, so the fan-out branch is unreachable. On a bare host it now refuses with `No coding agent detected on this host` and exit 1, creating nothing. Same command with scoping: **1 directory, 0 junctions.** `universal` alone would under-install — Claude Code is not in that set, and 19 of 28 mapped keys write agent-private homes `universal` never touches. `--agent '*'` is the bug itself. The mapping is hedged three ways: `null` for any agent whose key could not be confirmed, `satisfies Record<TuiAgent, …>` so a new Orca agent is a compile error, and a test pinning every mapped key against the CLI's own valid list. Fixed during review — two holes that each restored the full fan-out through a different door: - `--agent ','` trimmed to nothing, which skipped the refusal *and* emitted no `--agent`. - `--agent -y` passed an emptiness check, and the vendor CLI silently drops `-`-leading values, re-emptying its list. The real invariant is argument *shape*, not emptiness, and it is now enforced at the choke point in `buildAgentFeatureSkillInstallArgs`, so no caller can emit `-y` without a usable target. `*` remains allowed — asking for every agent explicitly is a choice, not an accident. Verified with 51 hostile inputs through the built binary, each recorded argv replayed through the vendor's own parser. Also fixed: the `ORCA_CLI_CWD` refusal now runs before target resolution (it was quoting the wrong host's agent list), and `--dry-run` is refused in a forwarded shell rather than printing a command naming the wrong machine. Validated on a real Windows host across PowerShell 7, PowerShell 5.1, cmd.exe and Git Bash: `.cmd` shims route through `cmd.exe` and `.exe` shims spawn directly (proved with instrumented shims, not inferred), the ENOENT path produces an actionable error rather than a silent failure, and `skills update` genuinely restores a corrupted skill byte-for-byte. Known, not addressed here — both upstream behaviours this only forwards: a partial install failure exits 0, and "no installed skills found" exits 0. Both are invisible to the headless callers this feature exists for. Co-authored-by: scastanoh21 <scastanoh21@gmail.com> |
||
|
|
e20554bfd7 | fix(terminal): reduce inactive pane dimming (#11591) | ||
|
|
bbb3e7e5ee |
fix(native-chat): mirror multi-line launch drafts into the chat composer (#11253)
* fix(native-chat): mirror multi-line launch drafts into the chat composer
seedNativeChatLaunchDraftForAgentTab rejected any text containing a newline,
so every Linear launch ("Linked Linear issue: X\n<url>") and any GitHub launch
with a typed note was invisible in chat. The rejection existed because the send
path pre-cleared the TUI with a single Ctrl+U, which cannot clear a buffer with
embedded newlines.
Orca injects the draft itself, so when the composer still holds exactly what was
injected the buffer already IS the message: the send becomes the submit key
alone — no clear, no paste, nothing that can concatenate, and multi-line submits
as one turn for free. Only the edited case needs real buffer replacement, and
that now clears every line and verifies against the agent's rendered input line
instead of firing blind.
Measured on real PTYs against Claude Code and codex (both agree exactly):
clearing N logical lines costs 2N-1 Ctrl+U. See src/shared/agent-tui-input-clear.ts
for the law, the sequences that do NOT work, and why an upper bound is safe.
* fix(native-chat): send the mobile clear burst as its own write
Live QA caught the bundled form failing: a multi-line burst prefixed onto the
body in the SAME terminal.send reached the agent as LITERAL Ctrl+U characters,
so the parked draft survived and the message arrived as
draft + 21x \x15 + body. Sending the burst as its own non-submitting write —
the shape the image paste has always used — clears as intended.
The body write's own single-Ctrl+U prefix is dropped once that dedicated clear
ran, for the same reason: a Ctrl+U immediately followed by body text in one
write lands as a literal control character and headed the received message.
Re-verified live end to end: received prompt is exactly the draft, one turn,
zero control characters.
* test(native-chat): invert the multi-line Linear launch-draft mirror expectation
The Linear work-item launch seeds `Linked Linear issue: ENG-42\n<url>\n`.
This test pinned the pre-relaxation rule (multi-line drafts withheld), which
the send path no longer needs now that it submits the TUI buffer in place or
clears every line first — so it asserted the exact behavior the fix removes.
Assert the seeded payload instead of absence, so the test fails if the mirror
regresses to single-line-only.
* fix(native-chat): preserve launch draft send contents
* fix(native-chat): preserve confirmed send queue ordering
* fix(native-chat): preserve send pacing after renderer stalls
* test(native-chat): align activation with multiline draft mirroring
* fix(native-chat): clear launch drafts from any cursor
* fix(native-chat): retire mobile-consumed launch drafts
* test(mobile): stabilize QR capacity boundary fixture
|
||
|
|
ab665a3ce7 |
fix(remote): preserve terminal recovery across control refresh (#11513)
* fix(remote): recover stalled terminal streams * fix(i18n): localize manual disconnect error * fix(remote): park paired terminals with host snapshots * test(remote): mock authoritative resync snapshots * fix(terminal): defer startup mounts until hydration * fix(remote): raise paired terminal stream capacity * fix(remote): harden terminal recovery lifecycle * fix(remote): preserve calls across control refresh * test(remote): harden paired recovery oracle * test(workspace): seed Jira source context * test(remote): assert raw host terminal identities * test(terminal): keep restore sentinels atomic * test(terminal): keep restore sentinel on one row --------- Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com> |
||
|
|
9eede0084d |
fix(relay): refuse silent fallback when pairing invite fails (#11528)
* fix(relay): refuse silent fallback when pairing invite fails When Orca Relay pairing fails, don't silently degrade to a LAN-only QR under the Relay label. Instead, surface structured failure information so the UI can clearly inform the user and offer recovery options. * fix issues |
||
|
|
0fe1278244 |
fix(sidebar): stop background workspace creation from scrolling the sidebar (#11530)
* fix(sidebar): stop background workspace creation from scrolling the sidebar Creating a workspace in the background still spawns its terminals, and the renderer treated "no presentation stated" as "point the user at this terminal" -- revealing (scrolling to) the owning workspace. Split adoption from surfacing with an explicit surfaceOwner flag: background worktree creates and worker dispatch adopt their tabs silently, while `orca terminal create` keeps its discoverability reveal. * fix(sidebar): keep split-mode setup panes silent, tighten surfaceOwner Review catch: with setupScriptLaunchMode split-vertical/horizontal the Setup terminal goes through splitTerminal, whose reveal payload had no surfaceOwner, so a background create still scrolled the sidebar in that configuration. Also narrow surfaceOwner to `false` so "surface it" can only be expressed by omitting the key, and fold the repeated conditional spreads into ownerSurfacing. |
||
|
|
64a1269409 |
perf(orchestration): bound mutation ledger and run pages (#11432)
* perf(orchestration): bound mutation ledger and run pages Co-authored-by: Orca <help@stably.ai> * fix(orchestration): close retention pagination gaps * fix(orchestration): preserve unpaginated run listing Co-authored-by: Orca <help@stably.ai> * fix(orchestration): reject malformed run cursors --------- Co-authored-by: Orca <help@stably.ai> Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com> |
||
|
|
a60aa85592 |
fix: make remote server pairing failures actionable (#11510)
* fix: make remote server pairing failures actionable * refactor: extract daemon router event types * fix: address remote pairing review findings * fix: address final remote pairing review feedback |
||
|
|
561e2d32cd |
fix(floating-workspace): persist Markdown tab renames (#11398)
* fix(floating-workspace): route markdown renames locally * test(floating-workspace): strengthen rename regression * test(floating-workspace): verify rename restart persistence * fix(filesystem): serialize local rename destinations * fix(filesystem): serialize Unicode rename aliases * fix(filesystem): align rename locks with native aliases * fix(filesystem): canonicalize rename parent locks --------- Co-authored-by: Dzmitry Bachko <dbachko@users.noreply.github.com> Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com> |
||
|
|
f8b553b7d5 |
fix(agent-hooks): skip unavailable agent homes (#11442)
* fix(agent-hooks): skip unavailable agent homes * refactor(agent-hooks): separate Pi and OMP home fix * test(agent-hooks): update merged protocol harnesses * fix(agent-hooks): avoid redundant reconciliation * fix(agent-hooks): harden reconciliation and detection * test(agent-hooks): cover settings reconciliation * fix(agent-hooks): hydrate PATH for paired clients |
||
|
|
f0eca5fe32 | fix(sidebar): isolate runtime reconnect refreshes (#11472) | ||
|
|
bf894ef150 | fix(remote): recover and safely park paired terminals (#11416) | ||
|
|
74563b6498 |
feat(jira): link Jira issues from the workspace create dialog (#11296)
* Link Jira issues from workspace create dialog Add Jira issue linking to workspace creation, matching existing GitHub and Linear workflows. Users can paste Jira issue URLs in the smart name field to auto-populate workspace names and link the issue to the created workspace/worktree. Linked Jira issues appear on workspace cards via the new 'jira-issue' card property. Implements cancellable searches and summary reads to prevent stalled requests from blocking the shared Jira pool. Persists paired issue + source context metadata with validation of provider/site identity. Fixes git-username rate-limit handling to reject malformed JSON responses so garbage never becomes branch prefixes. * feat(jira): link issues during workspace creation - Display linked Jira issues on worktree cards - Fetch issue summaries and timestamps via Jira API - Gate Jira linking behind runtime capability check - Preserve user-typed names during async lookups * Enforce git check-ref-format rules in login validation Extend isBranchSafeHostedLogin to reject usernames that git rejects as invalid branch components: trailing dots, consecutive dots, and .lock suffix. Prevents invalid branch names from login usernames. * Enforce filesystem filename cap for branch-safe logins Loose refs store logins as single filenames, so the real constraint is the 255-byte filesystem cap, not git check-ref-format rules. This allows longer provider-agnostic logins while staying platform-safe. |
||
|
|
791577861b |
fix(project-host-setup): carry identity across hosts (#9413)
Allow setup when the selected project exists only on another host by carrying its validated provider identity with the request instead of reverse-parsing project IDs. Preserve host-qualified provider identity and reject mismatched payloads before linking. Make linking atomic for local and runtime imports, including clone setup: roll back only newly registered repos and invalidate the same caches as canonical removal. Cover local, runtime, host-qualified identity, mismatch, clone rollback, and renderer routing paths. Co-authored-by: fanyunqian.1 <fanyunqian.1@bytedance.com> |
||
|
|
64aa726301 | fix(quick-open): support projects past 10k files (#11440) | ||
|
|
d0f341ad69 |
fix(computer-use): make modifier clicks interruption-safe (#11451)
* fix(computer-use): make modifier clicks interruption-safe * fix(computer-use): pace modified Windows multiclicks * fix(computer-use): address modifier safety review |
||
|
|
5517bfcbd2 |
fix(native-chat): make the launch-draft mirror reachable (#11222)
* fix(native-chat): make the launch-draft mirror reachable Seed the chat-composer copy of unsent launch context on every originating draft path, then let those launches open in chat by default. Three paths delivered a draft to the TUI without mirroring it into chat: folder-workspace create, the local argv-prefill branch of launchAgentInNewTab, and the web-host equivalent. The first was invisible; the other two were hidden only because draft launches were forced into terminal view. The view-mode decision now gates on the same predicate as seeding (canMirrorLaunchDraftToNativeChat), so a draft can never open in chat with a composer chat would refuse to fill. * fix(native-chat): gate draft view mode on argv-prefill launches too The draft view-mode gate read `startup.draftPrompt`, which only the post-ready-paste delivery sets. An argv-prefill launch carries its draft inside `launchCommand`, so the gate never saw one and the tab opened in chat unconditionally — a multi-line draft was correctly not seeded yet still opened chat, leaving an empty composer beside a filled TUI input. Adds `launchDraftText` to the activation startup payload as a view-mode-only field, deliberately distinct from `draftPrompt` so it cannot double-deliver the draft through pty-connection's bracketed paste, and sets it at all four originating producers. * fix(native-chat): reconcile backend draft launch tabs |
||
|
|
8d4e975ff7 |
fix(new-workspace): stop UI flashing when typing ahead of search (#11436)
* fix(new-workspace): stop UI flashing when typing ahead of search Hold branch results while queries settle, show the spinner only on initial load, use stable cmdk values, and guard selections against stale rows. This prevents the highlight from jumping around when typing faster than the debounced search settles. * fix(new-workspace): keep dropdown visible while typing within settled qu Hold the last search results while the user extends or trims the query, only hiding them when the query diverges completely. This prevents the dropdown from flashing empty between debounced keystrokes and removes the guard that made provider rows unselectable during typing. * fix(new-workspace): align held provider results with live typing Cap prefix hold by length delta, hide GitHub/GitLab/Linear rows when the field is cleared ahead of debounce, and re-sync the cmdk arm when search settles so the highlight cannot lag the resolved selection. |
||
|
|
5f7807497e |
feat(ssh): bound relay PTY output end to end (#11005)
* docs: design SSH relay PTY backpressure * fix(ssh): bound relay frame decoding * fix(relay): bound PTY output publication * fix(ssh): bound PTY model admission * fix(ssh): settle closed model admissions * feat(ssh): negotiate bounded PTY consumer sessions * fix(ssh): fence exit on renderer settlement * feat(ssh): track PTY source credit end to end * fix(ssh): recover bounded PTY output across reconnect * feat(ssh): complete relay PTY output backpressure * fix(ssh): close final PTY source credit races * docs(ssh): record final backpressure validation * feat(ssh): complete relay PTY source-credit lifecycle * test(ssh): complete provider notification fixture * fix(ssh): preserve terminal source credit across rotation * fix(ssh): fail closed on recovery cancellation * fix(ssh): prioritize mux control writes after drain * fix(ssh): retire canceled relay restore deliveries * fix(ssh): order exit cancellation cleanup * fix(ssh): gate provisional source activation * test(ssh): register mux drain-priority coverage * fix(ssh): type stale owner recovery mismatches * fix(ssh): close projection replacement races * fix(relay): contain streaming edge failures * fix(ssh): secure relay endpoint credentials * docs(ssh): reconcile final backpressure lifecycle * fix(ssh): bound main IPC output lifecycle * fix(ssh): close recovery ownership gaps * docs(ssh): record exact artifact validation * fix(ssh): reject reclaimed snapshot replacements * fix(ssh): fence model admission across reconnect * fix(ssh): contain migration failure per PTY * docs(ssh): record final exact-head validation * test(ssh): align deploy fixtures with credential publication * feat(ssh): add per-target bounded output setting * fix(ssh): close source recovery review gaps * fix(ssh): latch source credit environment override * feat(ssh): make PTY source credit the default * docs(ssh): record always-on relay validation * docs(ssh): bind validation to current main * test(ssh): grant source credit in IPC fixture * test(ssh): grant source credit in fake relay --------- Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com> |
||
|
|
24a2accc3c |
fix(hibernation): reap restored subagent rows with no live agent process (#11219)
* fix(hibernation): reap restored subagent rows with no live agent process A pane whose Claude session had a subagent in flight can be locked out of agent hibernation for good. A PTY that dies while Orca is down never runs the teardown that clears pane state, so hydrate rebuilds a subagent roster that nothing can retire: the existing reap needs the parent to emit a complete `background_tasks` inventory, and a parent that went idle before the restart never emits one. The restored row keeps gating the pane 'working', and hibernation only accepts 'done'. Observed locally: six panes parked at SubagentStop in state 'working' for 17 to 145 hours, each still holding a working child row. Adds a second reap path. Hydrate seeds are marked `restoredFromSnapshot`, cleared by any live lifecycle event or an id-exact running inventory entry. One post-restore sweep drops the rows still unconfirmed when the pane's PTY is absent from the live local inventory, then re-derives the child-gated 'working' to 'done'. The scan is local-only by construction: panes with a relay connection id are skipped and SSH-scoped PTY ids resolve as live, since a remote agent runs on the far host and could never appear in a local listing. An unreadable inventory is not evidence that anything exited, so it is a no-op. Panes that have reported to this runtime are left alone. `stateStartedAt` and `stateHistory` are untouched, so a draft typed while the pane was working still blocks hibernation. * fix(hibernation): prove local ownership before restored reap * fix(hibernation): require authoritative restored PTY absence * fix(hibernation): probe restored PTY liveness authoritatively * fix(hibernation): restart idle window after restored reap * fix(hibernation): type restored reconciliation timing * fix(hibernation): respect worktree host ownership * fix(hibernation): preserve same-id restored PTY rebinds * fix(hibernation): fence batched restored PTY probes |
||
|
|
c74fb3c71b |
feat(browser): let Shift invert link routing instead of always forcing the system browser (#10991)
* feat(browser): let Shift invert link routing instead of always forcing the system browser Shift+Cmd/Ctrl-click has always meant "open in the system browser", which is a no-op when that is already where links go. Users who keep Link Routing off have had no gesture to pull a single link into Orca's built-in browser. Adds "Hold Shift to open in ___", a nested toggle under Link Routing that makes the modifier open a link the opposite way from the setting. It ships off, so the one-way escape hatch is unchanged for every existing user. The title and description name the destination the modifier actually reaches and flip with the parent setting, since "the opposite" is meaningless on its own. - openHttpLink gains modifierHeld; resolveModifierRouting owns the decision so every surface (terminal URLs, OSC 8, xterm web links, markdown preview) routes identically. forceSystemBrowser stays for callers that must bypass settings. - The terminal hover hint names Orca when the modifier would open there, and is re-resolved per hover so toggling applies without recreating panes. - Link Routing's own copy drops "always uses your system browser", which the new toggle can falsify; the nested row states the live destination instead. * fix(browser): route the Checks panel hosted-review link through the shared modifier The "Open on GitHub" button had its own Shift+Cmd/Ctrl escape hatch that passed forceSystemBrowser directly, so it kept the old one-way behavior while every other surface honored the invert setting. Route it through modifierHeld like the terminal and markdown paths. Also wraps the modifier row's description in translate(); the title in the same file was localized but the description returned raw English (caught in review). * fix(browser): make link-routing modifier copy true in every state Review follow-ups on the Shift-inverts-routing change. - The nested row promised "⇧⌘+click opens one in Orca" in the present tense while its own toggle was off, so the out-of-box state described behavior the user did not have. Phrase it as enabled-state copy, matching sibling rows. - The parent Link Routing description gained "opens a link the other way", which is false in the default state and contradicts the child row when inverting is on. No fixed sentence there is true in every state, so the child row — which knows the live destination — now owns the claim. That leaves getBrowserLinkRoutingShortcutLabel unused, so drop it. - The rich markdown editor still forced the system browser while the preview of the same file honored the modifier, so one link routed two ways depending on which view it was clicked in. - A remote runtime pins every link to the system browser, so the hover hint could promise Orca for a click that lands elsewhere. Gate the hint on the same condition openHttpLink uses. - Index both modifier titles: the search entry is built with openLinksInApp false, so the row was unfindable by the title it renders when routing is on. - Drop the ariaLabel that shared no words with the visible label (WCAG 2.5.3) and add the four missing settings-search keyword keys to all five catalogs. * test(editor): pin the rich markdown Shift+click routing hop The editor half of the modifier fix had no coverage — reverting it to forceSystemBrowser left the suite green while the same link routed one way in the markdown preview and the other way in the rich editor. Also pins that a non-local source owner survives the hop, since that is what keeps an SSH file's links out of Orca's browser. * test(editor): cover the Ctrl chord for rich markdown link routing This file is the only test of handleRichMarkdownEditorClick, and it exercised metaKey alone, so the isMac branch of modKey had no coverage off macOS. Also stop claiming the source-owner case proves SSH links stay out of Orca — it proves the owner survives the hop; http-link-routing.test.ts enforces the rest. * style: trim review comments to the one-line house rule Both explained the change adequately in two lines; the extra lines were worked examples, not information. * fix(browser): keep Link Routing copy unchanged until inverting is enabled Removing the "⇧⌘-click always uses your system browser" sentence outright reworded the row for every user on upgrade, including everyone who never turns the opt-in on. Restore it verbatim in the default state and only hand the chord sentence to the nested row once inverting makes "always" untrue. * revert(editor): keep rich markdown Shift+click on the system browser Per Brennan: the editor's Shift path hands the link to the client OS and should not follow the invert setting — the preview opening in Orca is the intended divergence, not a bug. Restores main's call exactly; the test now pins the divergence so a future consistency pass cannot erase it silently. * fix(browser): surface the inverted modifier on the hosted-review link The hosted-review click path now passes modifierHeld, so with inverting on and Link Routing off the chord opens in Orca — but the hint stayed gated on openLinksInApp, hiding a live gesture. Resolve the destination instead of a boolean. Default-off output is unchanged. * test(browser): pin the inert modifier hint when links already open in Orca * refactor(terminal): require the pane link hint so dropping the wiring fails to build The optional option fell back to a duplicated copy of the legacy hint string, so deleting the hook wiring reverted the tooltip silently with every test green. * fix(browser): trim the runtime id before hiding the hosted-review modifier hint openHttpLink and terminalUrlOpenHintOptionsFor both trim, so a blank runtime id hid the hint while the click still reached Orca. |
||
|
|
363e478909 |
fix(orchestration): preserve active workers across updates (#11271)
* fix(orchestration): preserve active workers across updates * test(ssh): model absent legacy adoption * test(orchestration): align compatibility contracts * fix(windows): escape updater PowerShell booleans * fix(windows): restore stock uninstall process check * fix(orchestration): keep recovery off renderer startup barrier * fix(orchestration): harden legacy recovery migration * fix(orchestration): close recovery review gaps * fix(orchestration): complete legacy worker cutover recovery * fix(orchestration): preserve legacy workers across updates --------- Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com> |
||
|
|
3a80fbe162 |
Revert terminal rendering changes from #10692, #10794, #10871, and #10907 (#11338)
* Revert "fix(terminal): avoid flash while restoring parked terminals (#10871)" This reverts commit |
||
|
|
a7c8b8e071 |
fix(terminal): bound SSH & remote hidden-worktree terminal retention (C1) (#10625)
* fix(terminal): park SSH worktrees like local ones (C1 retention, slice A) SSH ptys were blanket-excluded from hidden-view parking, so a hidden SSH worktree retained every pane forever (C1: renderer heap climbs to the V8 ceiling). SSH bytes transit local main — fact-mode watchers already cover them, and main keeps a headless model served over pty:getMainBufferSnapshot that the SSH reattach path never consulted. - isParkRestorableTerminalPty: snapshot-backed OR (SSH + policy); threaded through both park verdicts, both selectors, watcher coverage, and the watcher start guard. Remote-runtime/fail-open/foreign/null unchanged. - Parked-SSH reveal paints from main's headless model (dimension-matched, ~5k rows) and degrades to the relay 100KiB replay unless the snapshot is a non-empty source==='headless' payload — never a blank/stale paint. - Kill switch: settings.terminalSshViewParking (default on). DESIGN.md records the approved plan and the H1 magnitude non-claim. Co-authored-by: Orca <help@stably.ai> * fix(terminal): bound hidden-worktree retention with a force-park budget (C1, slice B) Un-parkable worktrees (remote-runtime ptys, uncoverable tabs, SSH with the slice-A switch off) had unlimited retention: the parking cap/TTL only ever saw eligibility-passing worktrees, so one bad tab pinned a whole worktree's panes forever. Retention is now memory-bounded, not eligibility-bounded. - terminal-hidden-worktree-retention.ts: retention budget (12 hidden / 45min TTL, sized from the measured 2.5-19MB per-pane V8 cost, DESIGN.md §2) over hidden worktrees ordinary parking can never evict; reuses the hot-retain ranking so last-active exemption, deterministic ties, and deadline-driven rechecks hold. Fail-open/foreign-pty tabs are eviction-exempt (a remount would fresh-spawn and orphan the live shell). - Terminal.tsx: force-parked ids join the parked set AFTER the coverage veto (darkness for uncoverable tabs is the accepted cost); buffers captured via the sleep-flow registry before the unmount render; retention TTL added to the recheck deadlines for budget candidates only. - Verdict stays out of its own effect deps; policy test asserts idempotence and time-monotone membership (flip-loop dwell regression). - Kill switch: settings.terminalHiddenWorktreeRetentionBudget (default on). Co-authored-by: Orca <help@stably.ai> * fix(terminal): demote hidden scrollback for eviction-exempt worktrees (C1, slice C) The retention budget (slice B) must exempt worktrees holding fail-open or foreign-worktree ptys — a remount would fresh-spawn and orphan the live shell — which would leave that class unbounded again. Instead, past the same 45min retention TTL their hidden panes drop to the minimum scrollback tier (measured: ~19MB -> ~1.3MB V8 heap per 50k-row pane; trimmed history is gone by design, reveal restores the configured cap for future output). - terminal-hidden-scrollback-demotion.ts: module-state verdict registry (parked-watcher pattern) with content-equality notify damping; applied in the existing scrollback-rows effect in use-terminal-pane-lifecycle. - selectScrollbackDemotedTerminalWorktrees: pure, TTL-gated, time-monotone. - Retention TTL wakeups now also cover exempt worktrees so demotion fires. - Kill switch: settings.terminalHiddenScrollbackDemotion (default on). Co-authored-by: Orca <help@stably.ai> * fix(terminal): paint the SSH model snapshot inline, not via nested coordinator (C1 slice A fix) applyMainBufferSnapshot runs its own structuralReplayCoordinator.run; calling it from applyReattachPayload (already inside the coordinator when a relay replay exists) deadlocks on the coordinator's tail chain. The model paint now mirrors the daemon-snapshot branch inline (folded scrollback + rehydrate + screen, dimension-matched, escape tail last) and arms the restored-snapshot seq baseline so deferred/live chunks the snapshot covers dedupe instead of double-painting. Also falls through (no early return) so reattachPayloadApplied still latches. Adds the folder-workspace id parity unit case. Co-authored-by: Orca <help@stably.ai> * test(terminal): SSH park+reveal e2e round-trip + as-built design notes (C1) Docker-gated (ORCA_E2E_SSH_DOCKER=1) spec: SSH tab parks behind a decoy and reveal restores marker content at multi-viewport scrollback depth. DESIGN.md records the as-built deltas (inline paint, force-park shape, last-active floor) and the residuals so follow-ups aren't lost. Co-authored-by: Orca <help@stably.ai> * fix(terminal): paint SSH reveal from main's model even when the relay replay is empty (C1 review #1) A relay restart empties the replay buffer; the reveal previously painted nothing even when main's headless model held the session. The reattach now prefetches the model snapshot when no structural replay exists (SSH-shaped ptys only) and paints it inside the coordinator; emptiness is judged on the composed payload (scrollbackAnsi + data + pendingEscapeTailAnsi) so an alt-screen snapshot with an empty screen frame still paints. Co-authored-by: Orca <help@stably.ai> * fix(terminal): decouple scrollback demotion (slice C) from the retention-budget switch (C1 review #2) Per the approved contract each slice reverts behind its own switch: slice C now requires only the master terminalHiddenViewParking plus its own terminalHiddenScrollbackDemotion flag. The TTL wakeup timer fires for demotion candidates even with the budget switch off. No DEFAULT_SETTINGS entries exist for sibling flags (defaults are the '!== false' optional pattern), so no explicit defaults are added. Co-authored-by: Orca <help@stably.ai> * fix(terminal): scope eviction exemption to the tab, not the worktree (C1 review #3) One eviction-exempt tab (fail-open/foreign pty) previously vetoed force-park for its whole worktree, pinning co-located remote-runtime tabs forever. The worktree now force-parks while exempt tabs keep their mounted panes via a per-tab exclusion mirroring the Activity-portal pattern (legacy watcher sync, legacy render, and the overlay cold-parking hook). Ordinary parking is untouched — a worktree with an exempt tab still cannot ordinary-park. Slice C now also demotes exempt tabs' panes as soon as their worktree force-parks under the count budget (they are the only panes left mounted). Co-authored-by: Orca <help@stably.ai> * fix(terminal): demote un-parkable worktrees the force-park lever spared (C1 review #4) The last-active exemption means a single hidden un-parkable worktree never force-parks — and slice C previously only targeted exempt-tab worktrees, so its panes held full scrollback forever. Demotion now also covers un-parkable non-exempt worktrees past the retention TTL that are absent from the force-parked set (last-active spared, or slice B switched off). Membership stays time-monotone for fixed inputs; covered by new idempotence/monotone selector tests. Co-authored-by: Orca <help@stably.ai> * fix(terminal): keep the hidden clock running through transient background-measure windows (C1 review #5) Whole-worktree background mounts (browser-automation bootstrap lease, mobile mounts, agent wakes) open a ~3s self-clearing measure window that previously deleted hiddenSince — every remount restarted the 30s hysteresis and the 45min retention TTL, so a periodically re-mounted force-parked worktree never re-parked. The measure window still pauses parking/eviction verdicts (all selectors skip measuring candidates); only the clock survives, so the prior verdict resumes as soon as the window closes. Visible and portal-holding worktrees still reset the clock. Co-authored-by: Orca <help@stably.ai> * test(terminal): make the SSH park+reveal depth assertion prove the model paint (C1 review #6a) Pad the session with ~180KB of output after the numbered markers so the earliest marker falls outside the relay's 100KiB rolling replay buffer while staying inside main's ~5k-row headless model; asserting marker_1 after reveal now proves the headless-model paint rather than passing under the relay fallback. Co-authored-by: Orca <help@stably.ai> * docs(terminal): rewrite DESIGN.md as the single as-built C1 contract (review #7) One contract matching the code: status IMPLEMENTED around force-park (not the unmount proposal), real kill-switch names with coupling + revert matrices, the true retention-floor formula with measured per-pane and demotion numbers, an explicit when-OOM-is-still-possible paragraph naming the H2 pendingSideEffects residual, the applyMainBufferSnapshot deadlock constraint inside the slice-A section, stable-signal phrasing instead of a capability latch, fail-open AND foreign-worktree exemption class, verified cites, and a planned/landed/follow-up test matrix. Co-authored-by: Orca <help@stably.ai> * fix(terminal): resolve the eviction exemption per pane, not per tab (C1 review #8) isEvictionExemptTerminalTab read only tab.ptyId — the FIRST leaf's pty — while the coverage veto that makes a worktree a retention candidate walks every pane. A split tab whose second leaf held an unrestorable pty therefore failed coverage (→ force-park target) yet looked exempt-free, so force-park unmounted it and orphaned the live shell. The exemption now resolves panes through the same resolveParkedTerminalPaneCandidates, keeping tab.ptyId in the union for the no-layout/no-capture case. Also from the same review round: - force-park's capture passes includeLocalBuffers:false like every other shutdownBufferCaptures caller; it was serializing up to 512KB/pane of scrollback into the store inside a fix meant to bound renderer heap. - Terminal.tsx unmount resets the scrollback-demotion registry — module state with no reset path, read by a pane effect that runs before the host effect that would clear it, so a stale verdict trimmed restore replays. - memoize watcher coverage per tab within the parking pass; the retention candidates re-asked it for every mounted worktree, not just the parked few. * docs(terminal): drop DESIGN.md — the as-built C1 contract moves to the PR body Co-authored-by: Orca <help@stably.ai> * fix(terminal): cap the deferred PTY side-effect queue (C1 residual H2) pendingSideEffects grew without bound under background timer throttling (~64 drained/s vs hundreds queued/s overnight). Cap at 512 entries with oldest-first eviction: titles drop (last-wins), a pending bell latches onto the next survivor, agent-status payloads collapse onto the survivor keeping the newest 16 (last-wins store state, KB-scale strings). Co-authored-by: Orca <help@stably.ai> * fix(terminal): carry command-lifecycle facts through parked watchers (C1 follow-up) Parked fact-mode watchers omitted onCommandFinished/onCommandCode*, so OSC 133;D and Command Code scrape signals went dark while parked. New parked-terminal-command-status.ts ports the store-level subset: git-UI nudge on every command finish, same-turn status-row drop for SSH PTYs (exact mounted-path parity — the foreground tracker refuses SSH ids), and the Command Code working seed / 1500ms done settle. Byte mode scans the same shared parsers for authority-off parity. Local-PTY status drops stay with the mounted pane: they need pty-connection's process-confirm ladder to tell a leaked nested-shell 133;D from a real agent exit. Co-authored-by: Orca <help@stably.ai> * test(terminal): retention-budget force-park e2e with a retentionLimit override (C1 6b) ORCA_E2E_TERMINAL_RETENTION_LIMIT flows preload → e2e-config → getTerminalParkingPolicyOverrides (exposeStore-gated, positive-integer only) so a spec can shrink the force-park budget to 1. The Docker-gated spec opens two remote worktrees on one relay target (second pre-seeded remote repo), disables terminalSshViewParking to make both un-parkable, hides both behind the local context, and proves the older one force-parks while the last-active exemption spares the newest; re-activating the evicted worktree restores the marker tail via relay replay. Co-authored-by: Orca <help@stably.ai> * test(terminal): retention-budget e2e via same-repo remote worktrees (passes docker lane) The first draft added a second remote repo mid-session, whose pane pty spawn misroutes to the local daemon with the remote cwd (pre-existing multi-repo issue, reproducible without any retention override — a seeded local repo plus one remote repo shows the same misroute). The spec now budgets across three worktrees of the ONE connected repo, created through the product createWorktree path (an external git-worktree-add only lands as a detected worktree needing adoption) and polled through the relay's transient post-connect reconnect window. Verified green on the local Docker lane in 20.8s. Co-authored-by: Orca <help@stably.ai> * fix(terminal): prevent remount thrashing during post-measure cool-down ( Implements the C1 retention contract: preserve worktree `hiddenSinceMs` through a background-measure window (so TTL/ranking stay honest), but re-park waits for a full `coldParkDelayMs` cool-down after the measure ends. Without the cool-down, every ~3s measure lease on a past-deadline worktree thrashes remount/reattach. Core changes: - Terminal.tsx: add measure clock (measuringTerminalWorktreeIdsRef) and post-measure cool-down tracking (terminalWorktreeParkCooldownUntilRef); gate parking candidates until cool-down expires. - Extract snapshot replay choreography to shared terminal-snapshot-replay-paint.ts (used by SSH reattach + daemon restore paths). - Add SSH model snapshot timeout (750ms) with fallback to relay replay. - Move cold-park recheck deadline logic to terminal-cold-park-recheck-deadlines.ts; add cool-down deadline to scheduling. - useTerminalTabColdParking: implement matching measure-clock contract with per-tab cool-down gate to keep tab deadlines synced with worktree retention clock. - Add resolveTerminalMountScrollbackRows() to demote new xterms under demoted worktrees (pane births during demotion must take the demoted tier at create). - Add kill switches: terminalSshViewParking, terminalHiddenWorktreeRetentionBudget, terminalHiddenScrollbackDemotion. * fix(terminal): detect Command Code completion in parked mid-turn panes Seed the byte watcher with in-flight turn state from agent status: the watcher is recreated per park cycle with no startup command to arm it, and the banner scrolled away before parking. Also memoize eviction-exempt checks and use SSH PTY ID builder in tests. * fix(terminal): flush pending command-code settles on reveal remount When a parked pane reveals mid-Command Code turn, the new detector cannot re-observe the already-passed idle composer. Cancelling the settle leaves the row stranded at 'working', so dispose now flushes the pending settle instead. Extract readInFlightCommandCodeTurn to shared space and seed detectors with in-flight turns so remounts complete mid-flight commands. Also memoize SSH model probes to prevent double timeouts on reattach. * fix(terminal): remove scrollback demotion (C1 slice C) The scrollback demotion feature for eviction-exempt hidden worktrees is no longer needed. Retention budget limits are now sufficient without this additional bound. Remove the terminal-hidden-scrollback-demotion module, the selectScrollbackDemotedTerminalWorktrees function, and related per-pane demotion logic. * test(terminal): assert bounded probe during stalled reveal Add assertion to verify that a stalled reveal operation makes exactly one `getMainBufferSnapshot` call, ensuring retry logic doesn't introduce redundant probes that would extend the timeout window before relay fallback. * fix(terminal): implement C1 retention budget for hidden parked worktrees Addresses OOM regressions in hidden parked terminals by force-evicting worktrees past a retention budget: at most 12 mounted while hidden, none past 45 minutes (absolute, not exempted by last-active). Eviction is least-recently-hidden-first. Exempt tabs (unrestorable local PTYs) keep their panes to avoid orphaning shells; worktrees are force-parked even if they contain exempts, and their buffers released elsewhere. SSH/remote worktrees serialize buffers pre-eviction for reveal; local worktrees keep daemon snapshots. Command Code's done-settle window is transferred across park/reveal boundaries so the row cannot strand at 'working'. Model probe on SSH reattach is scoped to park-reveal only, not ordinary reconnects. Includes new E2E suite proving the budget actually releases memory. * memoize eviction-exempt terminal tabs to avoid redundant store reads Each tab's exemption check re-reads the store and walks the layout tree. Introduce selectEvictionExemptTerminalTabIds() to resolve all exempt tabs for a worktree in a single pass, then memoize the result in Terminal.tsx and useTerminalTabColdParking. This prevents O(n) store reads when checking exemptions across multiple tabs and ensures the set remains stable across unrelated re-renders. * refactor: reformat hidden-worktree retention comments Reflow to 80-character lines and remove internal ticket references (C1, C1 slice C). * fix(lint): split overlay slot and eviction-exempt tabs under max-lines Static analysis failed because TerminalPaneOverlayLayer (401) and terminal-parked-tab-watchers (304) exceeded oxlint max-lines. Extract the slot component and eviction-exempt helpers into dedicated modules. * test(terminal): stabilize retention budget e2e control arm Stage un-parkable remote pty ids only after both worktrees are hidden, and keep re-staging during the control-arm poll so a late updateTabPtyId cannot flip the decoy back to park-restorable and ordinary-park it before budget engages. * test(terminal): pin retention e2e decoy to a mounted pane snapshot Use the active pane-identity snapshot for the decoy tab instead of all worktree tabs, and re-assert un-parkable ids after the control-arm hold so a deferred/empty tab id cannot fail the budget-off mounted-count check. * fix: memoize terminal eviction exemptions on layout leaf PTYs Splits add leaf panes to the layout store without changing the tabs array. A memo keyed only on tabs misses this change, leaving new panes unexempted for unmount. Include layout leaf PTYs in the exemption memo key so it recalculates when splits occur or PTYs are re-minted. --------- Co-authored-by: Orca <help@stably.ai> |
||
|
|
1df8aa5605 |
fix(dashboard): give the agent preview terminal a real pane's keyboard (#11015)
* fix(dashboard): give the agent preview terminal a real pane's keyboard The dashboard's preview terminal is a bare xterm, not a pane, so it never ran `resolveTerminalShortcutAction` — its only custom key handler covered copy/paste and IME. Ctrl+Backspace therefore fell through to xterm's default `\x08`, which readline binds to backward-delete-char: one character instead of a word. Route the preview's keys through the pane's own shortcut policy, so word and line kills, Option chords, modified Enter, and scrollback chords encode identically. Pane-scoped verdicts (splits, search, focus) are swallowed rather than passed to xterm, which would send e.g. Ctrl+Shift+D as a bare Ctrl+D. The policy needs three things the preview could not see: - kitty-protocol flags — mirrored locally from the same PTY output stream - the PTY's execution host — bytes follow the host, not the client OS, so a new `DashboardCard.terminalInput` profile is derived in the main renderer (the only one holding the store) and relayed to the pop-out - host terminal options — the ConPTY backend and the kitty withhold now apply Also brings the emulator itself up to a pane's: Orca's Unicode 11 width shim (replayed CJK/emoji/ZWJ laid out wrong without it), Windows Ctrl+Alt chord repair, user font/cursor/line-height/word-separator/sensitivity settings, ligatures, the TUI wheel multiplier, lazy Arabic shaping, clickable links, and the IME candidate anchor — extracted from pane-lifecycle so both surfaces share one implementation. The in-window drawer built its snapshot from a slice subset, which would have degraded the new profile to client-OS defaults there; it now reads the full store non-reactively. * fix(dashboard): enumerate pane-scoped chords instead of a default case The switch-exhaustiveness gate rejects a `default` over the shortcut-action union — it would let a newly added action be swallowed silently instead of forcing a decision at the preview's boundary. * fix(dashboard): preserve native shortcuts and PTY host routing * chore: keep merge scope limited to dashboard * perf(dashboard): avoid full-store copies for terminal profiles * fix(dashboard): validate terminal input profiles at IPC boundary * fix(dashboard): sync preview terminal refs on commit, not during render react-compiler rejects ref writes in the render pass; every reader is an event handler or a post-await continuation, so a commit-phase sync is equivalent. * test(dashboard): cover the three seams that relay the host-input profile Reverting any of them left every suite green: the dialog's terminalInput prop (the only reader of DashboardCard.terminalInput), the drawer's hand-threaded store slices, and the pop-out's republish triggers. Each new assertion was mutation-checked against its source line. * fix(dashboard): follow the WSL host for a preview terminal's byte routing The card resolver handed resolveTerminalInputHostPlatform a transport with no getLocalSessionMetadata, so a WSL pty on a Windows client resolved to win32 while its own pane resolves linux — Shift+Enter would then encode CSI-u where the pane sends alt-enter. Mirror the pane transport's own gate. * fix(dashboard): republish on every slice the host-input profile resolves from The compare set covered 4 of the ~11 slices that decide a card's execution host, so a change to the rest (folder workspaces, project groups, the runtime catalog, detected worktrees) never triggered a publish. On a quiet board there is no later publish to heal from, and the pop-out — which cannot re-derive the profile — keeps encoding bytes for the host the pty used to run on. * fix(dashboard): sync preview terminal refs on layout, not on a passive effect xterm's keydown is a native listener, so React never flushes a passive effect before it. A just-relayed host profile could therefore miss the next keystroke. * test(dashboard): pin the preview's replay-vs-live kitty scan The existing A/B passed either way: a lone CSI > u sets the same flags through scan and scanReplay. Redeliver the push across a snapshot and its replay so stack semantics would leave the TUI's single pop on a stale frame. * fix(dashboard): rebuild the drawer's snapshot on every host-input slice The in-window drawer read ~12 host slices through getState() while subscribing to none of them, on the premise that agent activity drives the next rebuild. A quiet board has no such rebuild: an SSH handshake completing with every agent idle leaves the preview terminal encoding bytes for the pre-connect host, and agentStatusEpoch only ticks on live status changes so it never heals. Watch the same set useDashboardPopoutBridge republishes on — each writer bails out when nothing changed, so the added deps are far quieter than agentStatusByPaneKey, which already rebuilds this memo on every status ping. The existing coverage mounted a second hook, which always recomputes; the new test re-renders the same hook after changing only sshConnectionStates. * fix(dashboard): key the preview by the user's terminal shortcut policy The preview passed 11 of the 12 inputs the pane's policy takes and let the 12th default to orca-first. Under terminal-first a remapped tab.close chord is meant to yield to the shell — Ctrl+W is a word-kill there — but the preview kept claiming it as a pane close and swallowed the bytes. * test(dashboard): pin the host-input profile to card snapshots only The count path main added in #11042 renders no cards, so it must not pay a per-pty host resolution on every agent-status tick. Both assertions run against a card that does have a live pty, so only the gate keeps the profile off. * refactor(dashboard): extract the board's client-host read The merge of main's label bounding pushed build-dashboard-snapshot.ts to 302 lines. The client's own platform facts are a distinct concept from the pty host each card keys against, so they move out rather than earn a max-lines bypass. |
||
|
|
afbd98d8a4 |
Support Windows drives in the remote host filesystem picker (#7439)
* Support Windows drives in the remote host filesystem picker
The remote picker was locked to the system drive on Windows hosts: the
breadcrumb root resolved to C:\ and typed drive paths (M:\dev) were
treated as filter text, so projects could only ever be created on C:.
- Server: answer host-root browses ('/') on win32 with the mounted
drives instead of resolving to C:\.
- Client: recognize drive-anchored input (M:\, M:/, m:) as path mode,
resolve segments from the normalized drive root, and make
joinPath/parentPath/breadcrumbs drive-aware. Up from a drive root
returns to the host root (the drive list).
Fixes #7438
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Document why joinDrivePath uses a literal backslash
Review feedback suggested path.win32.join, but the renderer bundle
imports no Node builtins anywhere and runs sandboxed, so path.win32 is
not available here. The backslash targets the remote Windows host
regardless of client OS; say so at the call site.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Complete Windows drive browsing over SSH
* fix remote Windows drive browsing
* fix(ui): key remote breadcrumbs by path
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
|
||
|
|
3f53287554 |
fix(mobile): accept WebSocket pairing addresses (#9912)
* fix(mobile): accept websocket pairing addresses * fix(mobile): align manual pairing address validation * docs(mobile): correct custom address grammar comment * fix(mobile): enforce pairing endpoint size limit * fix(mobile): reject canonical IPv6 wildcard addresses * fix(mobile): handle unscannable pairing offers * fix(mobile): reset custom address dialog on close --------- Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com> |
||
|
|
9150ac65cb |
Fix Windows setup sequencing wrapper quoting (#8806)
* fix(setup): correct Windows sequencing wrapper quoting * test(setup): preserve spaced Windows batch paths * refactor(setup): dedupe PowerShell encoder, clarify wrapCmd comment Route the Windows setup-sequencing and Hermes startup planners through the shared renderer-safe encodePowerShellCommand instead of two verbatim btoa copies, and make that shared encoder renderer-safe (Buffer is unavailable in the sandboxed renderer where both planners also run). Reword the wrapCmd comment so it describes the current single-outer-quote behavior instead of the old quote-doubling bug. * test(setup): cover Windows metacharacter paths * fix(setup): keep Windows runner paths out of cmd source * test(setup): preserve Windows setup failures * docs(setup): explain safe cmd path handoff --------- Co-authored-by: OrcaWin <alpha-eng@stably.ai> Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com> |
||
|
|
5c59c84c7a |
fix(plugins): close four trust-boundary holes in the plugin system (#11232)
* fix(plugins): close trust-boundary holes in the plugin system
Move five security decisions to their chokepoints rather than leaving them
enumerated at individual call sites.
- Kill-list revocation reaches content packs: PluginContentPackRegistry now
takes an isKilled predicate and intersects it with any caller-supplied
approval, so a killed plugin's VM recipes can no longer reach
spawn(..., { shell: true }) through either reconcile() call site.
- Bound kill-list generatedAt to a 24h future skew at the parse chokepoint.
A far-future timestamp previously made every genuine later list look
"older" and disabled revocation permanently, persisted across restarts.
- Protect the whole auto.components.settings.Plugin* translation subtree
instead of an enumerated prefix list, so language packs cannot forge the
consent provenance badge or rewrite install-error security copy.
- Resolve manifest panel icons by own-key only; "constructor"/"__proto__"
previously yielded non-component prototype members that crashed the
right sidebar to its error boundary.
- Give panel liveness frames a reserved control budget so a panel that
saturates its action budget can still answer the watchdog.
Co-authored-by: Orca <help@stably.ai>
* fix(plugins): keep the kill-list future bound off the cache read path
The schema-level generatedAt bound re-judged the on-disk cache against the
device clock at every launch, so a client whose clock ran behind the last
genuine publication discarded its whole cached kill list and started with
zero revocations. Move the bound to the two fetch chokepoints instead.
Co-authored-by: Orca <help@stably.ai>
* fix(plugins): remove the reserved-lane starvation window and the revocation TOCTOU
Review follow-ups on the trust-boundary fixes:
- The reserved liveness lane had a per-window count equal to the ping
interval, so a panel's own pong-shaped traffic could spend it and drop
the next genuine reply — reintroducing the starvation the lane exists to
prevent. The lane is now size-bounded only; rate stays bounded because
every pong is also charged to the data budget.
- Only schema-valid pongs take the lane now, so near-miss pong-shaped junk
cannot drain it. readPanelPongId replaces the zod parse on this
guest-controlled path (a rejected safeParse allocates an issue list, ~90x
the accepted-path cost) and is pinned to the schema by a parity test.
- Re-read the kill list inside approveAtomically: approvedKeys is snapshotted
before an awaited verification phase, so a plugin killed during that wait
could still publish VM recipes and language packs.
- Assert the curated icon resolves to FileText; the old equality also passed
when both sides fell back to Plug.
Co-authored-by: Orca <help@stably.ai>
* fix(plugins): match zod's safe-integer bound in the pong reader
readPanelPongId used Number.isInteger, but zod's .int() rejects anything
above 2**53-1, so pingIds like 1e100 took the reserved lane the schema
would have refused. The parity test never probed that boundary.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Orca <help@stably.ai>
|
||
|
|
681c4ba458 |
fix(skills): stop calling the updater's own install a modified copy (#11249)
After a successful headless update the CLI installs source-repo HEAD,
which legitimately runs ahead of any shipped bundle. The scan classified
those bytes 'unrecognized', so the row went amber ('may be modified…
remove it') seconds after our own Update button ran, and the advice
looped: remove + reinstall lands the same newer content.
Scan half: a canonical/alias placement whose observed git tree sha
equals the updater lock's skillFolderHash is the CLI's own install, not
a user edit — reclassify it 'newer-known'. Display half: 'newer-known'
is recognized official content ahead of this build with nothing to fix,
so it no longer marks the copy blocked. Eligibility is deliberately
unchanged: ahead of the bundle means there is nothing this build can
update to, and offering one risks the provably-unperformable update
(#11110) when source HEAD still equals the lock.
Copies whose sha does not match the lock, copies with no lock entry,
same-name copies outside the placements the command writes, and
plugin-cache behavior all stay flagged exactly as before.
|
||
|
|
1d7e7656e3 |
fix(ui): preference sync, picker arming, zoom, chat status, and reverted locales (#11241)
* fix(ui): preference sync, picker arming, zoom, chat status, and reverted locales 7.1 ui.set rejected whole preference payloads on enum drift. The new AssertNoMissingKeys guard is key-only, so it could not see that LegacyWorktreeCardProperty omitted 'cli' (in DEFAULT_WORKTREE_CARD_PROPERTIES) or that rightSidebarTab omitted 'workspaces'/'pr-checks' and every plugin tab. UiUpdate is .strict(), so one bad value failed the entire batch and silently dropped sidebarWidth/groupBy/sortBy/filterRepoIds riding the same debounced write. Both enums now derive from the shared unions, AssertNoMissingValues catches value drift by name, and UiUpdate drops an unknown value instead of rejecting the batch around it. Unknown KEYS still reject. 7.2 The SSH shell-ready fallback moved from first-output to spawn, so a remote shell needing >1.5s to prompt got the bracketed-paste startup command before readline armed it, with no recovery afterward. The short deadline now applies only once output proves the shell is talking; a silent-since-spawn shell gets a longer budget and still delivers eventually. 7.3 The project picker armed in rank order but rendered in section order, so with a folder group present the BOTTOM row was armed on open and Enter created the workspace in the wrong place. Row keys now derive from the same sections that render. The folders bucket also gains the recent-exclusion guard the projects bucket has; that duplicate was unreachable, so this is symmetry, not a live bug fix. 7.4 setBrowserPageZoomLevel now compares before writing, so a pane reasserting a level the host already holds no longer emits a redundant host-wide HostZoomMap write. The user-applied level also moved to a module-level map keyed by page id: the guest webview outlives its React pane, so the pane-local ref re-seeded from the shared Settings default on every remount and let a later default retroactively hijack an already-zoomed tab. See PR notes on the part of this finding that could not be fixed as prescribed. 7.5 A non-null sessionId short-circuited the live-work escape hatch, forcing 'loading' over hook 'working' and rendering an idle pane mid-turn: Send instead of Stop, no typing indicator, no streaming preview. Status stays 'working'; the empty-transcript loading SURFACE moves to selectNativeChatViewState, which keeps 7.6 #10770 merged from a base predating #8549, reverting 182-187 translated strings per locale to English (es 182, ja/ko/zh 187) plus en.json's recipesHelp. Restored by script, only where the English source is unchanged between the two shas, so later legitimate edits are preserved: 0 keys added or removed, every value sourced from |
||
|
|
a721125d06 |
fix(perf): correct three 07-27 perf regressions (#11234)
* fix(perf): correct three 07-27 perf regressions Traversal capacity cap no longer scales with worker concurrency (#11026). retainWorkspaceSpaceScanEntry charged a traversal-wide entry counter, so N workers each holding a listing multiplied the live charge. At concurrency 48 a 48x2,100 tree (100,848 entries) hit the 100,000 cap while 100x1,500 (150,100 entries, 50% more) passed, and scanLocalWorktree treats the capacity error as terminal, reporting an intact worktree as "Unavailable" with sizeBytes 0. The cap is now per directory listing -- the only quantity fixed by directory shape -- restoring the invariant docs/workspace-space-scan-resource-bounds.md already states. Aggregate live retention stays bounded by the unchanged 64 MiB byte cap. Note: releasing each entry's charge at dispatch (the originally suggested fix) was measured and does not help; the peak is set at admission, before any entry is dispatched. Repo image icons are no longer fully base64-decoded on every snapshot publish (#11012). sanitizeRepoIcon reached decodeBase64Prefix, which sized its buffer to the whole payload to read a 24-byte header, running synchronously inside ipcMain.handle at a 250 ms throttle. Validation is now memoized on source+src in a BoundedMap. Measured for 10 icons x 256 KB: 37.34 ms -> 0.67 ms per publish. One over-long card label no longer discards the entire snapshot (#11012). isDashboardSnapshot was all-or-nothing and dashboard-popout returned early with no log while replaying lastSnapshot, so `orca terminal rename --title "<1025+ chars>"` froze the pop-out board on its last good paint with nothing surfaced. Labels are truncated at the producer, the validator drops only the offending card, and both the rejection and the drop are logged. The bound now lives in the shared snapshot contract so producer and validator cannot drift. Co-authored-by: Orca <help@stably.ai> * fix(perf): charge a scan listing's parent path once, not per entry The 4.1 fix made the entry cap per-listing but left the 64 MiB byte cap charging parentPath.length for every entry in a listing. Because a listing's entries all share one parent-path string, that multiplied the path by the directory's width, so the byte cap measured checkout depth rather than live heap. The reported symptom therefore still reproduced at the production default limits: 48 x 2,100 @ concurrency 48 raised a capacity error once the worktree path passed ~58 characters, while the same layout at concurrency 1 succeeded. The shipped regression test could not see this because it passes maxRetainedBytes: Number.MAX_SAFE_INTEGER, disabling the only cap still in play. Measured at a real 65-char worktree root, 3 of the report's 4 documented layouts still failed. The parent path is now charged once per listing, with its first entry, so an empty listing strands no charge. Per-entry overhead is unchanged at 512 B + name, which still dominates the estimate, so the OOM protection the original PR added is preserved. Adds a production-default-limits case covering the report's layouts under a deep root, plus an assertion that a short and a deep root reach the same verdict -- the path independence docs/workspace-space-scan-resource-bounds.md requires and which no existing test enforced. Co-authored-by: Orca <help@stably.ai> * fix(perf): prove the icon cache by decode count, not wall clock The caching test asserted a per-publish millisecond budget, which failed on CI at 5.64 ms against a 5 ms ceiling. Any threshold flakes on a loaded box, so count real sanitizeRepoIcon entries instead: 10 repos x 20 publishes is 200 icon checks against exactly 1 decode. Added cases pin the cache key (payload and source both re-decode; a cached image verdict never answers for an emoji) and that a rejection is cached too. Also drops budget.entries, which the per-listing cap left as a traversal-wide counter no check reads -- exactly the shape a future guard could reintroduce the concurrency bug from. Co-authored-by: Orca <help@stably.ai> * fix(dashboard): bound the project filter label the whole board rides on #11042 added snapshot-level filterOptions whose project labels are repo.displayName -- the same unbounded source this PR already bounds for card.repoName, but one level up where dropping a card cannot recover it. An over-long project name would fail isDashboardFilterOptions and take the entire snapshot with it, which is the exact frozen-board failure the per-card drop was added to end. Workspace-status labels are already capped at 32 by workspace-statuses.ts, so only projects needed this. Co-authored-by: Orca <help@stably.ai> * fix(dashboard): disambiguate the repo icon cache key The memoization key joined `source` and `src` with a space, but the sanitizer's base64 pattern admits whitespace inside a valid `src`. A rejected icon can therefore split the same concatenation differently and inherit an accepted icon's cached verdict, reaching the pop-out's `<img src>` without ever being sanitized. Length-prefix the source. Co-authored-by: Orca <help@stably.ai> --------- Co-authored-by: Orca <help@stably.ai> |
||
|
|
f790d9cbe8 |
fix(skills): stop the skill review dialog contradicting the badge that opens it (#11128)
* fix(skills): stop the skill review dialog contradicting the badge that opens it A skill whose only fault was an edited copy or one Orca could not read turned the setup-rail badge amber and offered Details — and Details opened a dialog headlined "All installed Orca skills are up to date." over an empty list. The badge says something is wrong, the dialog it points at says nothing is. The grouping only returned skills with an out-of-date copy, so those two states produced no row and the summary fell through to the all-clear headline. Include a skill when a copy needs attention as well, using one shared predicate so the badge and the dialog cannot disagree again. A plugin's own copy of a same-named skill stays out: that is the vendor's, not the user's drift. * test(skills): pin that a routine outdated copy raises no attention marker |
||
|
|
747b241145 |
feat(main): record main-thread hangs so we can measure them (#10256)
A deadlocked main thread never crashes, so it leaves no crash report and no artifact — incidence has been unmeasurable (n=1 confirmed, macOS 26.5.1, FB24004458 / electron#52437). This forks a plain-Node watchdog sibling under ELECTRON_RUN_AS_NODE that survives the deadlock, listens for a 2s heartbeat, and after 45s of silence writes a marker to userData. The next launch consumes it, records a durable crash breadcrumb, and emits a main_thread_hang_detected telemetry event carrying unresponsive_ms and self_recovered. Observes only — it never kills or relaunches the parent. A true positive recovers nothing force-quitting wouldn't, while a false positive would SIGKILL a live main thread mid-write. self_recovered counts exactly the stalls such a killer would have gotten wrong, so recovery can be built on evidence if the field numbers justify it. macOS-only, packaged-only (ORCA_HANG_WATCHDOG_FORCE=1 to test), with sleep-gap suppression and idempotent shutdown on will-quit. |
||
|
|
d3681f6306 |
fix(runtime): surface desktop RPC startup failures (#11037)
* fix(runtime): surface desktop RPC startup failures
* fix(runtime): isolate RPC failure telemetry
* fix(runtime): satisfy the changed-code quality gate and kill vacuous dialog tests
The `no-floating-promises` label span covers the whole `app.whenReady().then()`
callback, so adding lines inside it made a long-standing finding overlap changed
code. `void` is the linter's own suppression; no `.catch()` on purpose.
The startup-failure tests were vacuous: mutation runs showed the wait-for-show
deferral, the destroyed-window guard, the `closed` companion event, listener
cleanup, the cause walk, the cycle guard, and the truncation bound could all be
deleted with every test still green. The "not called yet" assertion ran before
any microtask, so it passed either way.
* test(runtime): de-brittle the desktop RPC-failure source assertions
Anchoring the slice on the full destructure and matching the whole dialog
call expression made an innocuous rename break the test with a cryptic
'expected -1'. Match the shape that is actually the contract instead.
* test(runtime): repair the silently-unbounded desktop startup slice
The desktopEnd anchor comment lost a word in
|
||
|
|
13c193a00a |
feat(dashboard): add agent status search board (#11042)
* feat(dashboard): add agent status search board * fix(dashboard): keep idle controls reachable * chore: drop merge-only formatting drift * fix(dashboard): compare sparse subagent snapshots safely * fix(dashboard): satisfy settings handler lint * fix(dashboard): address review feedback * fix(dashboard): complete search and localized status copy * fix(dashboard): pad active filter row * fix(dashboard): keep idle control in board settings * fix(dashboard): source filters from workspace state * fix(dashboard): clarify PR and MR status filter * fix(dashboard): preserve review and board parity |
||
|
|
50f46889d9 |
fix(ai-vault): resume a bridged Codex session under the selected account's home (#11224)
* fix(ai-vault): resume a bridged Codex session under the selected account's home The account session bridge hardlinks every rollout into each per-account CODEX_HOME, and vault dedup keeps the lexicographically-smallest alias, so Resume could pin an inline CODEX_HOME naming a peer account — running the session under that account's auth.json and quota. At resume time the owning host now substitutes the selected account's home when it holds the same rollout at the same sessions-relative path, declining on any uncertainty so resume degrades to today's behavior instead of failing. * fix(ai-vault): repin dropped sessions without a cwd instead of resuming under the wrong account The drag payload only carried sessionCwd when session.cwd was truthy, so a null-cwd codex session dropped onto a pane silently fell back to the prebuilt command - which pins the wrong account's CODEX_HOME, the exact defect this PR eliminates on the other resume surfaces. - Serializer always sends sessionCwd (null when the session has no cwd), so absence now only means an older-serializer payload. - The repin rebuild accepts a null cwd (the builders already omit the cd prefix), matching the sidebar Resume/Copy paths which repin regardless of cwd. - An unrepinnable payload (absent sessionCwd) now fails loudly with guidance instead of silently resuming under the wrong account's home. |
||
|
|
ca5a821600 |
Stop relaunching creation-time agents on workspace activation (#10647)
* fix(activation): stop relaunching the creation-time agent on workspace activation Activating a workspace with zero renderable tabs launched the agent it was created with, unprompted and in approval-bypass mode. Navigation is not consent to start a process: the same fallback fired from post-delete focus handoff, the jump palette, keyboard cycling, CLI/relay activation, and notification clicks. The mechanism was superseded. #1814 added it when relaunching the created agent *was* the resume feature; #4706 later added real provider-session resume six lines above and left the fallback in place. What remained fired whenever a workspace had no renderable tabs -- including when nothing had ever slept -- and reported itself as `request_kind: 'resume'` while resuming nothing, discarding any resumable session a plain tab close had already purged. No caller depends on it. All seven intent-carrying callers pass an explicit `startup` on the branch where they intend a launch, and every no-startup branch either declined an agent, already has one running (host `didSpawnStartup`), or is this same defect arriving over IPC. Drops the now-orphaned imports, retargets the stale comment in launch-work-item-direct that cited reopen-relaunch as the reason to persist `createdWithAgent`, and moves the WSL default-args quoting assertion to launch-agent-in-new-tab, whose launch path still resolves those args. Regression tests are revert-sensitive -- all four fail if the fallback returns. * test(activation): name the relaunch regression tests after what they reach Three tests were named after scenarios they never invoked, which is the failure mode that lets a coverage gap read as closed. - The "host-originated" test's `notifyHostRuntime: false` is inert here: both gates resolve through `isWebRuntimeSessionActive`, false with no runtime environment seeded, so it was byte-identical to the plain reopen test. It no longer claims to cover the host `didSpawnStartup` leg, which lives in main and is unreachable from this layer. - The "post-delete focus handoff" test never deleted anything and never touched `prepareActiveWorktreeFocusAfterDelete`. That caller is asserted directly in active-worktree-focus-after-delete.test.ts, which locks out any opts. - The activate/close loop resets state instead of calling `closeTab`, so it does not exercise the sleeping-record purge its comment claimed. Also folds the primary reopen test onto `seedEmptyActivatableWorktree` — the fixture extracted for exactly that state, which its inline copy had drifted from by hardcoding a POSIX repo path. `preflight` is dropped from the launch-work-item-direct comment: the trust preflight reads the create-time argument (worktree-remote.ts), not the persisted meta. Removal safety and ownership do read the field and remain accurate. Renames the ported quoting test to what it pins. Under vitest's node environment `navigator.userAgent` carries no "Windows", so platform resolution bails before the WSL branch and the WSL preference is inert — the real coverage is single-quote escaping of user-configured agentDefaultArgs. * transfer large terminal history seeds across bounded protocol messages - Oversized cold-restore snapshots (>1MB) now upload via chunked startHistorySeedTransfer/appendHistorySeedTransfer protocol instead of inline, avoiding NDJSON line-size violations - Checkpoints automatically trim oldest rows to fit within configured byte limit (200MB) before commit - Protocol v30 required for chunked transfers; v29 daemons gracefully fall back to renderer-only recovery - NDJSON encodeNdjson() validates line size and rejects oversized payloads; notifications silently swallow encoding errors * fix(daemon): drop held output when teardown checkpoint fails to serializ When a final snapshot checkpoint fails to serialize (returns retryable), the pending output records must not be appended later—doing so would splice them over the seq gap left by the failed snapshot, defeating gap detection. Drop the records and retry the checkpoint instead. * Bump daemon protocol version to 30 * Bump daemon protocol version to 30 |
||
|
|
930ff96152 |
fix(skills): stop the scan issue budget evicting a read failure (#11221)
The per-scan issue budget kept an issue only when it explained a candidate or truncated the walk. Neither set intersects the attention set, so 'io-error' — the sole reason a plugin-cache scan can raise "Needs attention" — was droppable. Once 16 ordinary issues filled the budget (16 'outside-root' vendor symlinks is an install shape the scan itself documents as normal), a later read failure was evicted for a generic 'issue-limit' row that raises neither attention nor truncation, and the dialog headline read "All installed Orca skills are up to date" over a path that could be hiding a stale copy. Attention issues now outrank the budget, capped at a small reserve so an adversarial tree of unreadable folders cannot pin one issue per folder. |