mirror of
https://github.com/stablyai/orca.git
synced 2026-10-26 08:02:44 +00:00
dec044ea9eb15982e63e193092dbbb4366f93739
301
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
fa9b20cb41 | feat(skills): reland private bundle sharing safely (#14934) | ||
|
|
763b1febeb |
Revert "feat(skills): add private bundle sharing (#14401)" (#14913)
This reverts commit
|
||
|
|
757fae28d7 |
feat(skills): add private bundle sharing (#14401)
Co-authored-by: E2E Test <e2e@test.local> |
||
|
|
ab9d1a29a9 |
fix(worktree): never reissue a generated workspace name (#14350)
* fix(worktree): never reissue a generated workspace name
Generated workspace names were deduped only against currently-live
worktrees, so deleting a workspace returned its name to the pool. A later
workspace could draw the same name, land on the same directory path, and
inherit the previous occupant's agent conversation history — coding-agent
CLIs key their prompt history and transcripts by cwd.
Names are now retired permanently per repo. The registry is written in
main with the name Git actually used (the create loop can advance past a
requested name on collision), and seeded once per run from workspace
directories and surviving agent transcript buckets so already-spent names
are excluded from the start. Suggestions degrade to -2, -3 variants
instead of recycling, and those variants retire too.
User-typed names are untouched: retirement filters suggestions only.
* fix(mobile): honor retired workspace names, on one shared implementation
Mobile hand-duplicated the desktop name-suggestion algorithm and deduped
only against live workspaces, so a phone could still be offered a name
whose deleted workspace left agent conversation state behind at that path.
Both platforms now call one shared selector in src/shared, so the two can
no longer drift. The host publishes retired names as an optional field on
the existing worktree.list response, and mobile fetches them per selected
repo while the create sheet is open — mirroring the desktop hook.
Mobile never calls worktree.list for its catalog (it uses worktree.ps,
which carries rows only), so this is a targeted request rather than a
change to the catalog or its cache. Hosts predating the field omit it and
mobile falls back to live-only dedupe, which is the pre-change behavior.
* fix(worktree): close retirement consistency gaps
* test(worktree): cover retirement runtime contracts
* fix(worktree): retire generated collision names
* fix(worktree): enforce retired names at creation
* refactor(ai-vault): extract the Claude project-dir encoder
The bucket-name encoder and its scope-boundary check were private to the
session scanner, so a second consumer had to reimplement them — and got the
per-character encoding wrong. Move both to a shared module with direct tests.
* fix(worktree): make the retirement seed scan actually match buckets
The bucket encoder collapsed runs of non-alphanumerics while the real one
emits a dash per character, so every dot-path bucket missed and the Windows
default workspace root (C:\...) matched nothing at all. Reuse the shared
encoder and its boundary check, which also stops a repo absorbing a sibling
whose path merely shares its prefix.
Also:
- Derive the workspace leaf by stripping the known encoded parent instead of
guessing from trailing dash segments, which retired the parent directory's
name whenever a workspace was named numerically.
- Reuse isAutoGeneratedCreatureBranchName so the -10 and -100 tiers retire.
- Drop the .codex/sessions root: Codex keeps the cwd inside the transcript
rather than in a directory name, so the scan could only ever see a year
folder. Reading transcript contents is not a trade this feature justifies,
so the gap is documented instead.
- Honor CLAUDE_CONFIG_DIR, which relocates the bucket root.
- Delete the unused retirableLeafName export.
Tests write buckets with the real per-character encoding against a fake home,
covering POSIX, dot-directory, Windows drive and WSL UNC roots; all three
platform cases fail against the previous encoder.
* fix(worktree): retire only generated names, keyed by cwd namespace
Two problems in the host-side registry.
Retirement fired for every create, including names the user typed. The
creature pool contains ordinary words — orca, runner, sole, molly, oscar — so
typing a retired 'nautilus' silently produced directory and branch
'nautilus-2' and burned the name for good. Creates now carry an explicit
nameWasGenerated flag; both the skip and the retire are gated on it, and it
defaults to false so CLI and automation callers are unaffected.
The registry was keyed by repo id, but both readers already discarded the id
and unioned by the cwd collision key, because the collision this prevents is
on the path. Keying by that namespace directly fixes several things at once:
entries no longer orphan when a repo is removed, remove/re-add no longer loses
every retirement for an unchanged path, the missing removeProject prune is
moot, and the backfill promise no longer merges into only the first repo id it
saw. The feature is unreleased, so no migration is needed.
Also:
- Memoize the collision key. It runs computeWorktreePath, which for a WSL repo
is a blocking execFileSync('wsl.exe') whose failure path is uncached, and
the previous code recomputed it once per repo on every create and every
listRetiredNames call.
- Drop retiredNamesByRepo from the worktree list result. It had no readers and
leaked onto 'orca worktree list --json', and its awaited backfill sat on CLI
selector resolution. The dedicated listRetiredNames RPC keeps its consumers.
- Make the three RuntimeStore methods required. RuntimeStore is file-private
with two constructors, so the 'older embedders' the optionality protected do
not exist, and the optional chain silently returned no retirements.
- Revert the unrelated forceDeleteBranch rewrite, and make room under the
file's line budget by extracting the create-args mapping instead.
* fix(worktree): send name provenance and stop gating Create on the fetch
Desktop and mobile now mark a create as generated-name only when the user
typed nothing and the composer fell back to the suggestion, so the host knows
which names it may retire.
Remove the retired-names loading gate from every create path. The host already
skips retired candidates before doing any git work, so the client gate bought
nothing while it could disable Create for the length of a full mobile
reconnect ladder (the wait had no timeout) and blank the desktop button
between queued creates. The suggestion still waits; the button never does.
Also make the web client call worktree.listRetiredNames instead of hardcoding
an empty list — the method is registered and mobile-allowlisted, so the
comment claiming no wire call existed was wrong — and filter the mobile
response to strings so a malformed row cannot throw during normalization.
* fix(worktree): key retirement by repo id and prune it with the repo
Reverts the collision-key storage key. It was a function of workspaceDir,
nestWorkspaces, worktreeBasePath and repo.path, so toggling any one of those
orphaned every retirement for every affected repo at once — trading a rare
churn (remove/re-add) for a common one. The read path already unions by cwd
namespace at query time, so cross-repo sharing never depended on the storage
key.
Instead, address the growth and orphaning directly:
- Drop the registry in removeProject, and in removeProjectForHost once the last
host's copy of the repo id is gone, alongside the sparse-preset deletes that
already follow this convention.
- Bound each repo's registry. The cap sits far above the 552-name pool because
evicting inside it would reissue a name whose agent state is still on disk;
only -2/-3 tier accumulation can ever reach it.
- Carry retirements through profile transfer, re-keyed to the destination repo
id and dropped from the source, mirroring sparsePresetsByRepo.
Separately, fix the backfill merge: the scan promise is cached per cwd
namespace, but it closed over the first repo id that triggered it, so a second
repo in the same namespace received nothing. The scan stays shared; the merge
moves out of the cached promise and runs for whichever repo asked.
Local repos re-seed on re-add through that backfill. SSH repos do not — the
scan cannot see the execution host — which is now stated in the module.
* docs(worktree): spell out why the retirement bound sits above the pool
Names the trap directly: the neighbouring 50/200 bounds cap histories, so
lowering this one to match them would silently start reissuing names whose
agent state is still on disk. Also states that oldest-first eviction is a
deliberate least-bad choice rather than a neutral one.
* fix(worktree): send name provenance from the web runtime client
This client hand-enumerates worktree.create params, so the new optional field
was silently dropped and typecheck could not see it. On web and paired-desktop
the host therefore never received it: generated names were never retired, and
the host-side skip that backstops a stale suggestion was disabled too. The same
client does fetch retired names for suggestions, so it was filtering against a
registry nothing ever wrote to.
The test asserts both directions, and fails without the fix.
* fix(worktree): retire names that took more than one collision suffix
isAutoGeneratedCreatureBranchName strips exactly one trailing -N, which is
right for auto-rename eligibility but wrong here. Once the pool is spent the
suggester emits nautilus-2, and a collision on that yields nautilus-2-3 —
which a single strip leaves as nautilus-2, not a pool name, so retirement
no-opped at exactly the tier where every base name is already gone. Strip
repeated suffixes locally rather than moving the auto-rename predicate.
* perf(worktree): keep the retirement backfill off the blocking WSL probe
The backfill runs on composer repo-select, not just at create time, and it
derived the probe path synchronously — which for a WSL repo with a mirrored
workspace dir reaches getWslHome and its blocking execFileSync('wsl.exe').
A stopped distro froze the main process for up to 5s on composer open.
Adds an async twin of computeWorktreePath and uses it for the probe. Resolving
the home there also warms the shared cache, so later sync callers are free.
Also stops memoizing the collision key when the WSL home is still unresolved:
only the success path is cached upstream, so caching the fallback namespace
would strand the repo there for the rest of the session.
* fix(worktree): hold retired names across a refresh instead of blanking
refreshKey changes on every workspace-list mutation, so create-multiple
refetches after each create and the hook returned an empty list until the
refetch landed — precisely the window in which resetForNextCreate clears the
name field and a fresh suggestion is drawn. Keep the previous answer while
revalidating and reset only when the repo changes; a failed refresh keeps what
was already loaded rather than un-retiring everything.
Also makes the returned array referentially stable, so the suggestion memo
downstream stops rerunning on every refetch.
* refactor(worktree): put the retired-name cache rules on one implementation
The desktop and mobile hooks that fetch retired names had already drifted
four ways. The transports genuinely differ (IPC vs RPC), but the caching
rules must not, and mobile's copy reset to [] on any error -- which
un-retires every name for the rest of the sheet session, the one outcome
retirement exists to prevent.
Moves the rules into src/shared/worktree/retired-name-cache: response
normalization, the never-leak-across-repos rule, and the hold-previous-on-
failure rule. Pure, no React, because src/shared is on the main process's
import graph. Each platform keeps its own transport and effect.
Mobile moves up to desktop's behavior: it now holds the previous answer
through a failed refresh, and refetches when the workspace list changes
instead of never refetching after mount.
Also drops the unused `loading` return. Neither platform consumed it; its
only consumer was the Create-button gate reviewed out earlier, and removing
it makes that regression unexpressible.
* fix(worktree): import shared types from their real modules
Main dropped the src/shared/types barrel, so the retirement module's import
resolved locally but not against the PR's merge base.
* refactor(worktree): bound the retirement registry by tier compaction, not eviction
Retirement is a correctness guarantee — a spent name's directory may still hold
agent conversation state keyed by that cwd — so the 2000-entry cap was the wrong
shape: reaching it handed a name back. At the owner's measured rate (~6.6 pool
names retired per day in one repo) the cap was ~9 months out.
Names come from a fixed 552-entry pool and the suggester only reaches tier N+1
once every tier-N name is taken, so a completed tier is exactly a set that no
longer needs listing. A row is now a watermark plus the names above it: reads
answer at-or-below the watermark with no lookup, and compaction drops the 552
entries the watermark now covers. Bounded at one pool per repo forever, with no
eviction and nothing un-retired.
Tiers can complete out of order (a create-time collision can spend `nautilus-2`
while tier 1 is open), so compaction loops and higher-tier names simply wait.
The RPC result carries the watermark beside the names as a new field; a client
predating it reads the names only and under-retires the compacted tiers, which
degrades to the pre-retirement behavior rather than breaking.
* fix(worktree): preserve generated name retirement across failures
|
||
|
|
83e2123582 |
Add global worktree visibility source defaults (#14276)
* Add global external worktree visibility defaults * Expand global worktree visibility source defaults * Fix host-scoped visibility settings races * Fix global worktree visibility integration * Enable source visibility defaults on mobile * Polish external worktree settings navigation * Clarify inherited worktree visibility settings * feat(sidebar): replace the inherited-visibility switch with a Show/Hide picker Each source row now shows a two-segment Show / Hide control preselected to the global setting, and explains itself only where the project actually disagrees: an "Overriding global setting: <value>" card names the value being ignored. Picking the segment global already holds drops the override instead of pinning a duplicate, so the same control both overrides and reverts, retiring the separate "Use global" link. The dialog footer now lists every inheritable source with its global value. * fix(sidebar): preserve reset for matching visibility overrides |
||
|
|
77f23b013f |
refactor(shared): drop the shared/types barrel and import from the real modules (#14447)
#14397 split `shared/types.ts` into 46 per-domain modules but kept the path as a re-export barrel so the import sites did not have to change. This removes the barrel: every consumer now imports from the module that actually declares the type, and `src/shared/types.ts` is deleted. Barrels hide where a type lives, make every consumer look like it depends on the whole domain, and let an unrelated edit invalidate a module that ~2,000 files transitively import. 2,323 import declarations across 2,321 files. Rewritten mechanically: each specifier was resolved to an absolute path via the TypeScript AST and recomputed, rather than string-substituted, so alias forms (`@/../../shared/ types`) and per-specifier `type` modifiers survive. Four cases the mechanical pass had to handle, each found by a gate rather than by reading the diff: - Modules inside `src/shared` import the barrel as `./types`, not `shared/types`. A pre-filter on the latter string skipped 176 of them and left imports dangling at a deleted file, which surfaced as confusing `Property 'x' is optional in type 'Repo' but required in Pick<Repo, ...>` errors rather than "module not found". - The barrel RENAMED one type on the way through (`WorkspaceSource as WorkspaceCreateTelemetrySource`), so the original name in the owning module has to be re-aliased at each consumer. - Three test files put `;(globalThis as ...)` on the line after the import. TypeScript parses that `;` as the import statement's terminator, so replacing through `statement.getEnd()` deletes it and breaks ASI. The rewrite now stops at the module specifier. - A file that already imported directly from a module got a SECOND import from it, because the barrel re-exported those same names — which trips `import/no-duplicates` under `--deny-warnings`. A post-pass merges declarations sharing a specifier and type-only-ness; the `import type` plus `import` pair from one module is left alone, since that form is allowed. Splitting one barrel import into several genuinely adds lines, which pushed `terminal-layout-pty-ownership.ts` to 301 counted lines: its 107-character import must wrap, and neither local type collapses onto one line (101 and 116 characters). Rather than contort a type declaration to fit a line budget, `collectLeafIds` and `pruneLeaves` move to `terminal-pane-layout-tree.ts` — they are pure structural operations on the layout tree and independent of PTY ownership. `visible-worktrees.ts` similarly loses its own mini-barrel re-export of `isDefaultBranchWorkspace`, with the four real consumers repointed at the declaring module. No `max-lines` bypass added. Verified: cold `tsc --noEmit` green on node, cli, and web (buildinfo deleted first — these projects are `composite: true` and reuse stale caches); the full `pnpm lint` green, not just bare oxlint — the narrower local check is what let the duplicate imports reach CI; max-lines ratchet OK at 344. |
||
|
|
583ab1601b |
refactor(shared): group worktree, github, and linear modules into folders (#14437)
`src/shared` is a flat directory of ~1,150 entries. The worktree, github, and
linear domains accounted for 71 of them, so finding the module you wanted meant
scanning a wall of same-prefixed filenames.
Move each domain into its own folder and drop the now-redundant prefix:
src/shared/github-pr-types.ts -> src/shared/github/pull-request-types.ts
src/shared/worktree-id.ts -> src/shared/worktree/id.ts
src/shared/linear-links.ts -> src/shared/linear/links.ts
This follows the existing `network/` and `new-workspace/` convention in the
same directory, which also drop the prefix inside the folder.
Whole clusters move, including tests. Foldering only part of a domain would be
worse than flat: a reader would have to check both `github/` and the flat
directory, and `github-auth-types.ts` / `github-project-types.ts` are type
modules that belong with the rest. No files with these prefixes remain flat.
Import specifiers were rewritten by resolving each one to an absolute path and
recomputing it, not by string substitution, so the `@/../../shared/...` alias
forms are handled correctly. 501 specifiers across 298 files.
Two things `tsc` cannot catch, handled explicitly:
- `github-project-types.ts` carries its own `max-lines` bypass, so its baseline
entry is REPOINTED to the new path rather than pruned. Pruning would drop the
bypass and then flag the new path as a fresh violation. Ratchet stays at 345.
- `mobile/` is outside `pnpm typecheck` and cannot be typechecked here
(`mobile/node_modules` is empty). Instead every relative specifier in the repo
was resolved against the filesystem: 174 unresolved before this change and 174
after — identical, so nothing broke in mobile either.
The pinned `tests/e2e/.cross-version-checkouts` fixtures are deliberately NOT
rewritten; they are a snapshot of an older release and still reference the old
paths.
Verified: cold `tsc --noEmit` green on node, cli, and web (buildinfo deleted
first — these projects are `composite: true` and reuse stale caches).
|
||
|
|
953cfab635 |
fix(terminal): make the bold font weight its own setting (#14368)
* fix(terminal): make the bold font weight its own setting
Deriving bold as max(700, regular + 200) silently destroyed bold. A family
exposes only a few real faces: the monospace the default chain resolves to on
macOS has exactly two, splitting at 600. Measured by rasterizing each weight to
a canvas — 100-500 are byte-identical (ink 3023) and 600-900 are byte-identical
(ink 3855), at every weight the same advance. So any base weight at or above 600
put both values in the same face and bold stopped existing, on 4 of the 9
positions the slider offers, with no error and nothing the user could do.
Arithmetic cannot fix it — on a two-face family there is no heavier face to
escape to. So bold is now user-owned: a new terminalFontWeightBold setting with
its own control, defaulting to 700. The default pair (500/700) straddles the
boundary, so existing profiles render exactly as before; a collision is now a
choice the user can see and undo.
The old test asserted 800 -> {800, 900} as 'keeps bold heavier', which is where
this hid: numerically heavier, identically rendered.
* fix(terminal): surface bold face collisions accurately
|
||
|
|
c86418eaad |
rm git shim (#14141)
* rm git shim Drops the terminal git/gh PATH wrapper and its settings toggle. Renames the no-marker shell-ready launch config after what it does. Co-authored-by: Orca <help@stably.ai> * rm git shim: clear stale state from older installs Deletes the orphaned wrapper dir and scrubs inherited env/PATH, so a daemon that outlives the upgrade cannot keep seeding it. Drops a now-unread spawn option. Co-authored-by: Orca <help@stably.ai> * rm git shim: cover the daemon and headless paths Scrub after the PATH prepends (they re-read process.env on the sparse daemon env) and run the cleanup above the serve branch so remote hosts get it too. Retry a locked removal; match PATH case-insensitively. Co-authored-by: Orca <help@stably.ai> * rm git shim: keep the scrub final Refuse to re-prepend a legacy entry during agent-teams PATH promotion, which runs after the scrub. Cover the removal guard. Co-authored-by: Orca <help@stably.ai> --------- Co-authored-by: Orca <help@stably.ai> |
||
|
|
5e3a2d25f7 |
Filter remote workspaces by creating device (#13718)
* Filter remote workspaces by creating device * Fix workspace origin filter reconnect behavior * Shorten workspace origin filter label * Refine remote workspace filter UX --------- Co-authored-by: Jinwoo-H <Jinwoo-H@users.noreply.github.com> |
||
|
|
ce8c3267b7 | feat(terminal): add anchored link action popovers (#13414) | ||
|
|
3ec48a74d5 |
Gate artifact publishing behind off-by-default capability (#13368)
* fix(artifacts): gate agent artifact publishing behind an off-by-default capability Public artifact sharing was reachable by any agent through `orca artifacts share`: the Artifacts settings toggle only controlled sidebar visibility, and nothing in the main process checked a capability before minting a public URL. Add `artifactSharingEnabled` (default off) and enforce it in ArtifactCloudService.share/update — before auth, network, or the share-record write — so the CLI, relay-forwarded remote CLI, and IPC paths are all denied. The denial carries a stable `artifact_sharing_disabled` code plus next steps through the RPC error allowlist, so the CLI prints actionable guidance. list, unshare, and delete stay ungated: turning publishing off must not strand already-published links. The capability is absent from the `settings.update` RPC schema, so an agent cannot grant it to itself — only the desktop UI can. Co-authored-by: Orca <help@stably.ai> * fix(artifacts): gate agent artifact publishing behind an off-by-default Publishing is blocked until enabled in Settings → Artifacts. CLI preflights the capability before reading files to avoid unnecessary uploads. RPC surface rejects capability grants so callers cannot self-grant. UI shows opt-in workflow and recovery path when publishing is off. Web clients mirror the host's setting read-only. --------- Co-authored-by: Orca <help@stably.ai> |
||
|
|
394e4bf1c0 |
Polish Artifacts management UI (#13356)
* refactor(artifacts): polish artifact management UI * fix(artifacts): address UI polish review --------- Co-authored-by: Jinwoo-H <Jinwoo-H@users.noreply.github.com> |
||
|
|
c6ded160b2 |
Revert the Korean Won to backquote mapping (#13312)
* Revert "fix(terminal): detach the Korean input-source probe when its setting goes off (#13283)" This reverts commit |
||
|
|
24003936a5 |
feat(terminal): Korean Won (₩) → backquote key mapping for Korean keyboards (#13104)
* feat(terminal): map Korean Won (₩) key to backquote on macOS Korean keyboard users type markdown code fences and shell backquotes on the key that US layouts reserve for ` — 두벌식 and 세벌식 390 put ₩ there, 세벌식 최종 puts *, so there was no way to type a backquote without switching layouts. Add a Mac-only terminal setting, "Korean Won (₩) to Backquote (`)", that rewrites the plain backquote-position keystroke to backquote while a Korean input source is active. It sits in Terminal → Advanced, right below the existing JIS Yen (¥) to Backslash (\) mapping. The rewrite keys on the keystroke position alone — no character or layout-variant knowledge — and follows the live input source through the existing MacNativeTextInputSourceTracker, which refreshes on focus and keyboard activity (Caps Lock / 한영 input switches never blur the window). Modified chords and IME-composed events pass through untouched. Covered by resolver and input-source tracker unit tests; verified manually in the GUI. * docs(terminal): clarify Korean Won mapping scope and add docstrings State in the setting copy that the backquote rewrite applies only while a Korean input source is active, and add JSDoc to the Korean Won resolver exports (addresses CodeRabbit pre-merge docstring coverage and copy-clarity findings). |
||
|
|
c991bb27d3 | Add account-backed artifact sharing (#13012) | ||
|
|
8ddf575fe6 |
Revert "Remove source control group order preference (#12785)" (#12955)
This reverts commit
|
||
|
|
a30e3b9f61 |
feat(dashboard): add experimental agent map view (#12168)
* feat(dashboard): add experimental agent map view
* fix(dashboard): harden agent map behavior
* fix(dashboard): harden agent map recovery
* fix(dashboard): close map selection on view change
* fix(agent-map): center sparse layouts
* fix(agent-map): align completion and workspace actions
* Polish agent map interactions and repo labels
* feat(agent-map): add worktree lineage and project actions
* fix(agent-map): use marker for unread agents
* fix(agent-map): compact orchestrated families
* fix(dashboard): harden agent map actions and layout
* fix(agent-map): bound layout work and preserve interactions
* fix(agent-map): move unread marker to ring top-right
* fix(agent-map): seat unread marker on the ring's top-left edge
* feat(agent-map): restore the agent launcher and declutter map labels
Three gaps in the experimental Agent Map:
- The "start a new agent" picker was split onto a preserved branch during the
08-02 rebase (
|
||
|
|
ae1ed5e886 |
Remove source control group order preference (#12785)
* Reorder source control to show staged changes first by default Stages are closest to the commit action and most relevant to the commit workflow. Merges untracked files into Changes visually while preserving their Git area. Removes the untracked-first preset and includes migration logic for existing user settings. * Drop source control group order user preference Remove the sourceControlGroupOrder setting and related UI, migrations, and persistence logic. The source control view now always displays sections in the order: staged changes, unstaged changes, untracked files. * Reorder source control to show changes before staged Aligns with the edit-stage-commit workflow by showing unstaged changes (active edits) before staged changes (queued for commit). |
||
|
|
e59a319ffe |
fix(sidebar): keep each project's entry-point workspace visible under "Hide sleeping" (#12257)
"Hide sleeping" swept each project's main workspace out of the sidebar as soon as it had no live PTY, browser tab or agent — even with "Hide default branch" off. For a project whose only row is that workspace (a folder workspace, a fresh clone, a detached-HEAD main), the entire project vanished with no in-place way back. Adds a shared `isSleepingSweepExemptWorkspace` predicate keyed on `isMainWorktree` rather than the branch name, so folder workspaces (no branch), detached-HEAD mains, and SSH rows whose head/branch are blanked while a provider is disconnected all stay put. Wired into `computeVisibleWorktreeIds` (sidebar, Cmd+1-9, workspace board), the jump palette's duplicate inline pass, and mobile's `filterWorktrees`. Ships default-on with an escape hatch: a persisted `alwaysShowDefaultBranchWorkspace` setting surfaced as "Except default branch" under "Hide sleeping". Explicit "Hide default branch" still wins, since it filters before the sleeping sweep. Mobile reads the setting but never writes it back, so a desktop opt-out can't be clobbered by a filter tap before the ui.get roundtrip lands. Combines the two PRs open against #8873. #8966's exempt set is a strict subset of this one, so its production diff was subsumed rather than ported; its jump-palette render harness and e2e spec were carried over, and are the only such coverage here. Fixes #8873 Closes #8966 Co-authored-by: Rod Boev <rod.boev@gmail.com> Co-authored-by: Orca <help@stably.ai> |
||
|
|
93a2ad8fd8 |
fix(browser): bound retained webview guests across worktree switches (#12194)
* fix(browser): bound retained webview guests across worktree switches * fix(browser): merge duplicate imports flagged by import/no-duplicates * fix(browser): evict guests without unmounting the worktree surface Unmounting the surface disposed every parked terminal byte watcher for the evicted worktree (bell/title/agent-completion facts dropped for good after the 15s handoff TTL) and skipped the force-park scrollback capture remote panes rely on. Destroy the guests only: hidden slots mount no BrowserPane, so nothing resurrects a destroyed guest before the next visit, and no slot ever unmount-detaches a live guest (STA-3228). Terminal-state vetoes and the eviction re-render bump are no longer needed; the only veto left is a guest an automation/mobile controller is actively driving. * fix(browser): veto eviction for downloading pages; keep zoom; add kill switch Main cancels a page's active downloads when its guest unregisters (tab-close semantics), so eviction now vetoes worktrees with a page still writing a download, tracked app-wide because download state was pane-local and hidden panes are unmounted. Eviction is not a user close: re-remember explicit zoom past the destroy-path forget so a revisit reasserts the user's zoom instead of resetting same-host siblings through Chromium's partition-wide HostZoomMap. browserGuestWorktreeRetentionBudget mirrors the terminal budget kill switch. |
||
|
|
7c7167028c |
feat(voice): allow selecting a microphone for dictation (#12119)
* feat(voice): allow selecting a microphone for dictation Persist a preferred audioinput device in Voice settings and pass it into getUserMedia, falling back to the system default when the device is gone. * fix(voice): resolve mic preference by label and detect mid-capture loss Drop Chromium's 'default'/'communications' aliases from the picker — pinning one behaved exactly like system default and silently defeated the setting. Resolve a stored preference against the live device list before capturing: a unique label match heals an id that Chromium re-salted, a known-missing device skips the doomed getUserMedia attempt that clipped the first words, and an unreadable list no longer reads as "unplugged". Surface the input ending mid-dictation instead of feeding silent zeros, add a permission affordance so the picker is not empty before mic access, and toast the fallback once per preference rather than once per utterance. Co-authored-by: Orca <help@stably.ai> * add e2e tests * add e2e tests --------- Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com> Co-authored-by: Orca <help@stably.ai> |
||
|
|
ed00ab0f34 | fix(ssh): restore relay ownership after app restart (#11860) | ||
|
|
e698241aab |
fix(mobile): remember custom pairing addresses (#11741)
* fix(mobile): remember custom pairing address * fix(mobile): stabilize custom pairing address sync * fix(mobile): update pairing refresh refs after commit * feat(mobile): manage saved custom pairing addresses * fix(mobile): harden custom address selection |
||
|
|
e20554bfd7 | fix(terminal): reduce inactive pane dimming (#11591) | ||
|
|
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. |
||
|
|
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> |
||
|
|
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 |
||
|
|
1bd80931bd | fix(diff): stop file-tree navigation remounting combined diffs; make tree resizable (#11088) | ||
|
|
97e4776dfe |
feat(plugins): Orca plugin system — kernel, content packs, panels, workers, marketplace v0 (experimental) (#8549)
* feat(plugins): Orca plugin system — kernel, content packs, panels, workers, marketplace v0 (experimental) Adds Orca's experimental plugin system behind a settings flag: a supervised kernel, declarative content packs (VM recipes, commands and keybindings, language packs), sandboxed iframe panels, forked worker hosts, and a Git-backed marketplace v0 with consent, provenance and kill-list enforcement. Theme, icon-theme and terminal-theme contributions are deferred to a follow-up pass. * fix(plugins): make unsupported marketplace listings unreachable by key findPlugin() backs preview/install/previewInstalledUpdate via requireListing(), so filtering only listPlugins() hid the catalog card while leaving the dead install path reachable one click later. * fix(plugins): fan Pi session-only status out to plugin subscribers The providerSessionOnly early-return in applyNormalizedStatus emitted to onAgentStatus (main-window fanout) but skipped enrichedStatusListeners, so plugins subscribed to agent.status.changed silently missed every Pi session_start event. Route both emit sites through one helper so a future early return cannot drop the plugin tap again. Co-authored-by: Orca <help@stably.ai> * plugins: drop dead code and hoist duplicated trust-boundary patterns Cleanup pass over the P1 diff, no behavior change: - Delete `readPluginTreeSnapshot`/`readSnapshotFile` and their types, plus the now-vestigial `directories`/`signal` plumbing in `collectFiles`. - Delete `resolveContainedPluginDirectory` (no callers). - Delete `plugin-content-load-pool.ts`; it reimplemented the existing `mapWithConcurrency`, whose index arg also removes the pairing wrapper in `buildPluginList`. - Hoist `PLUGIN_CONTENT_HASH_PATTERN` and `PLUGIN_COMMIT_PATTERN` into the install-lockfile module; 11 sites hand-rolled these identically. - Point the new reliability gate at the PR instead of gitignored docs paths, matching every other gate's link form. * fix(plugins): retry plugin state renames on Windows AV/EPERM locks Six plugin write paths (lockfile, provenance, current pointer, kill list, marketplace cache, staged install dir) did a plain rename, so an antivirus or indexer holding the target open surfaced as a failed install. The repo already retries this hazard for issue #1507, but only through a sync helper; these paths are all async. Adds one bounded async retry + atomic write used by all six, and trims a consent-provenance header that restated its own JSX. * test(plugins): cover the Windows rename retry path The retry loop shipped untested: both existing cases hit the non-retry path, and the temp-cleanup test passed identically with the `finally` removed. Mock `rename` to queue errno codes so CI can exercise locks it cannot provoke. Co-authored-by: Orca <help@stably.ai> * fix(plugins): pin bundled plugin resources to LF Windows CI checks out with autocrlf, so the byte-hashed launch tree arrived as CRLF and verify-packaged-plugin-resources rejected it — the packaged build could never pass on Windows. Reproduced locally: CRLF yields the exact CI error, LF verifies clean. Files are already LF, so nothing renormalizes. Co-authored-by: Orca <help@stably.ai> * test: guard the bundled-plugin LF pin against a CRLF checkout The byte-hash mismatch only surfaced in Windows packaging CI. Assert the .gitattributes pin and that a CRLF tree is rejected, so a regression fails on any platform instead of waiting for a packaged Windows build. Co-authored-by: Orca <help@stably.ai> * ci: trigger packaged-build check on bundled plugin resource changes The launch tree is byte-hashed during packaging, but no trigger path covered it — so the CRLF fix for that check would not have re-run the check. Add the resources, verifier and .gitattributes paths that can break packaging. Co-authored-by: Orca <help@stably.ai> * perf(plugins): rebuild the panel frame only when its baked theme values change The revision keys the panel iframe, so every bump destroys the sandboxed frame and its in-panel state. It counted root attribute mutations, but --workspace-sidebar-live-width is written every rAF of a sidebar drag, so dragging with a panel open blanked it ~60x/sec. Compare the two values the shell actually bakes in instead. Co-authored-by: Orca <help@stably.ai> * test: stop pinning a plugin name in the CRLF guard The CRLF case rewrites every launch file, so the reported mismatch is whichever plugin sorts first. P2 adds theme plugins that sort ahead of orca-navigation-shortcuts, which broke the assertion there. Co-authored-by: Orca <help@stably.ai> * style: drop stray blank lines left by the rebase resolutions Both sides of the agent-hooks and orca-runtime conflicts contributed a trailing blank, which oxfmt rejects. Whitespace only. Co-authored-by: Orca <help@stably.ai> * test(plugins): stop the startup budget failing on machine load P95 runs 16-34ms idle but exceeds the 50ms bound under full-suite parallelism, so the gate flaked. Widen it to catch an order-of-magnitude regression instead; the no-worker/no-plugin-code assertions are the real guarantee. Verified a 400ms regression still fails. Co-authored-by: Orca <help@stably.ai> --------- Co-authored-by: Orca <help@stably.ai> |
||
|
|
af708d3471 |
feat(sidebar): add a filter to hide detached-HEAD workspaces (#10786)
Adds "Hide detached HEAD" alongside the existing sidebar filters, wired through the same pipeline as Hide CLI-created: sidebar list, Cmd+J empty-query list, workspace board, active-filter badges, Clear/Reset Filters, and persisted UI state. The predicate reuses getWorktreeGitIdentityDisplay so the filter targets exactly what the card renders a Detached HEAD badge for. Requiring a real head (not just an empty branch) keeps folder workspaces and SSH-synthesized rows — which carry both empty — out of the filter. Activating a hidden detached workspace clears the filter, matching the existing reveal escape hatch for automation- and CLI-created workspaces. Splits the filter-state describes out of visible-worktrees.test.ts into sidebar-filter-state.test.ts to stay under the max-lines budget. Co-authored-by: Orca <help@stably.ai> |
||
|
|
28dfc13654 | feat(sidebar): distinguish and filter CLI-created workspaces (#10712) | ||
|
|
9042ef9792 |
fix(terminal): make Zellij/TUI OSC 52 clipboard copy work by default (#10588)
* fix(terminal): make Zellij/TUI OSC 52 clipboard copy work by default Zellij and other multiplexers copy via OSC 52. Empty Pc is a valid XTerm default for clipboard, but we rejected it, and the feature defaulted off so copy silently failed inside Zellij. Accept empty Pc as clipboard, default the setting on (query still blocked; size capped), and surface Zellij in settings. Closes #10567 * fix(review): make the OSC 52 default actually reach existing installs Review fixes for #10588: - Persistence: profiles saved under the old off default persisted `false`, which is indistinguishable from a real opt-out, so the default flip never reached #10567's reporter. Added the repo's one-shot stamp (terminalAllowOsc52ClipboardDefaultedOnForAllUsers) so unmigrated profiles flip once and a later opt-out sticks. - Replay: reattach/cold-restore re-writes recorded PTY bytes through the same parser, so a stale `\e]52;c;...` silently clobbered the clipboard on every restart. Gated behind isPaneReplaying via a new resolveOsc52ClipboardGate. - Blocked toast latches once per renderer session and could be burned by a pre-hydration read; it now fires only for a real opt-out. - An empty Pd decoded to '' and, with the gate default-on, silently blanked the clipboard. Now rejected as invalid. - Localization: en.json is bundled and the catalog beats the code fallback, so all three copy changes were inert. Resynced across five locales. - Corrected the empty-Pc rationale: tmux (not Zellij) emits `\e]52;;<b64>`. * test(terminal): cover the OSC 52 gate wiring and settings copy Extracts createOsc52OscHandler so the replay/hydration gate wiring is covered, not just the pure gate — dropping the isReplaying getter now fails a test instead of passing silently. Adds catalog assertions for the two OSC 52 settings strings. Only the toast key was pinned, so the same inert-copy regression (code fallback edited, bundled en.json not) could still ship for the settings pane. * docs(settings): note that the OSC 52 default only covers new profiles Co-authored-by: Orca <help@stably.ai> * fix(terminal): migrate the web settings store to the OSC 52 default-on flip The default-on flip only reached the Electron store. The web/remote client keeps its own settings in localStorage, so a profile that persisted the old `false` there stayed opted out — the same bug the Electron migration fixed, in the second store. Extract the migration into shared/osc52-clipboard-settings.ts and call it from both stores. Also coalesce OSC 52 writes onto a microtask so a hostile chunk of ~15-byte sequences cannot fan out into a million clipboard writes, and latch the blocked-write toast after it renders rather than before. * feat(terminal): tell users when the OSC 52 flip overrides their opt-out The default-on migration cannot distinguish a deliberate opt-out from a profile that simply never touched the setting — both persisted `false` under the old default. Flipping everyone is the only way to fix #10567 for existing installs, but doing it silently reverses a security choice the user made. Arm a one-shot notice at load when the migration overrides a persisted `false`, on both settings stores, and show it once the renderer hydrates. Profiles that never opted out are never notified. * fix(terminal): clear the OSC 52 notice after it renders, not before Co-authored-by: Orca <help@stably.ai> * fix(terminal): keep the web OSC 52 notice armed against an unmigrated host The host store always projects osc52ClipboardDefaultOnNoticePending, so the plain spread in the web client's runtime UI merge overwrote an arm raised by its own localStorage settings migration — flipping the opt-out in silence. Co-authored-by: Orca <help@stably.ai> * fix(terminal): stop the OSC 52 notice overclaiming, and cover it Round-3 review fixes: - Rename the arming predicate to osc52ClipboardDefaultOnOverridesPersistedOff. Both stores rewrite the whole settings object on every save, so every profile saved under the old off default holds `false` — the deliberate-opt-out cohort is not distinguishable on disk. Name, docs and test names now say so. - Read settings before the UI snapshot in readLocalWebUIState: getStoredSettings() arms the notice, so reading first snapshotted a pre-arm state that callers wrote back, erasing an arm the stamp can never raise again. - Give the notice toast a stable id; StrictMode re-runs the effect against the same closure, so the early return cannot catch the second pass. - Restore guardParserHandler parity in the coalescer microtask. - Drop the unverified Zellij claim justifying all-selections routing; that routing predates this branch and PRIMARY routing stays an open question. - Cover the notice hook (order, single-fire, deep-link), the armed flag reaching disk and surviving a clear, and pin the notice catalog to its code fallbacks. Co-authored-by: Orca <help@stably.ai> * test(terminal): pin OSC 52 setting discovery by product name The migration notice says to turn it off in Terminal settings, so searching Zellij/Grok/tmux has to find it. Also note why the OSC 52 write-back clauses stay despite an unrelated always-true clause in the same condition. Co-authored-by: Orca <help@stably.ai> * fix(terminal): consume the OSC 52 notice on close, and cover the guards it relies on The notice was cleared the moment the toast was enqueued, so a quit inside its 15s window spent the profile's only warning on a launch where nothing was ever seen — and the settings stamp means it can never re-arm. Clear on onAutoClose/onDismiss instead, plus explicitly in the action handler, because sonner's action path deletes the toast without firing onDismiss. Also closes three coverage gaps a review found: - ui.set must accept osc52ClipboardDefaultOnNoticePending. The update schema is strict, so dropping the key rejects the whole call rather than stripping it, and the renderer only logs that failure — every paired client would re-toast forever with nothing red. - the coalescer's try/catch and .catch had no test; the rejection case needs a plain function because vi.fn tracks settled results and hides the leak. - pin that every selection kind (including bare `p`) lands in the system clipboard, so routing PRIMARY separately later is a deliberate break. Co-authored-by: Orca <help@stably.ai> * test(web): pin that ui.get arms the OSC 52 notice when it runs the migration readLocalWebUIState reads settings before the UI blob so the migration's arm is in place before the snapshot every caller writes back. Seeding localStorage after install is what makes ui.get the first settings read, and therefore what makes swapping those two lines fail. Co-authored-by: Orca <help@stably.ai> * test(store): cover the OSC 52 notice clear and its hydration The clear sets local state before persisting so a rejected ui.set cannot leave the toast re-firing for the rest of the session; losing the persist only re-arms the notice next launch. Co-authored-by: Orca <help@stably.ai> * docs(terminal): state the real residual risk of default-on OSC 52 Three comment corrections from review: - the safety note claimed exfil was the risk; queries are blocked, so it isn't. The actual accepted risk is execute-on-paste: decoded text goes to the clipboard verbatim, newlines included. Filtering here would break multi-line TUI copies, which is the feature; bracketed paste is where that is handled, and kitty/Ghostty take the same posture. - the coalescer bounds a flood per parse yield, not overall. - the replay gate reads at parse time while queued live bytes are drained before the guard engages, so a copy racing a reattach is dropped silently. Co-authored-by: Orca <help@stably.ai> * test(terminal): close the four OSC 52 gaps a full revert walked through Mutation testing found four assertions that stayed green against the very change they were written to pin. The notice suite passed 8/9 against a complete revert to clear-at-enqueue: `calls[0][1][callback]?.()` is a silent no-op when the option is absent, and the call count was already satisfied by the enqueue-clear, so nothing separated "cleared by this callback" from "cleared earlier". Assert the option exists and the notice is unspent before invoking it. The stable toast id was deletable with all 9 green despite the adjacent comment calling it load-bearing for StrictMode. Pin it. The blocked toast's latch-after-throw fix was unproven: both orderings pass when `toast.info` succeeds. Only a throwing first call tells them apart. Deleting the hook call in App.tsx silenced the desktop notice with every suite green. Pin it alongside the static Toaster import, since sonner drops a toast enqueued before any Toaster subscribes and never replays it. Also retone the coalescer-latch comment, which claimed the reset ordering was load-bearing on its own; the try/catch reaches the same end, so the test binds the pair. All four verified green->red by mutation, then restored. * test(terminal): cover the OSC 52 notice and its guards Add tests pinning the static Toaster mount required to prevent notice dropout (#10567), the stable toast ID deduping StrictMode double-invokes, that the notice stays unspent on toast throws, and that flush-latch guards prevent silent consumption across error boundaries. --------- Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com> Co-authored-by: Orca <help@stably.ai> |
||
|
|
4a71a0ecb2 |
feat(mobile): add safe Codex rate-limit resets (#9394)
* feat(mobile): add safe Codex rate-limit resets * fix(mobile): address reset credit review feedback * review: purge removed-account reset attempts, shared capability constant, rebase test mocks * review: preserve host compatibility and reset durability * fix(mobile): recover reset capability after cutover * fix(mobile): validate runtime capability payloads * fix(mobile): enforce capability payload contract * fix(mobile): route mock terminals to selected worktree * test(mobile): pin malformed probe retry behavior --------- Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> |
||
|
|
d56e2fbbe4 |
feat(agent-dashboard): choose in-window board or pop-out window (#10243)
* feat(agent-dashboard): choose in-window screen popover or pop-out window
The experimental Agent Dashboard opened only as a separate pop-out
window. Add an "Open as" mode under the experimental toggle so it can
open as an in-window screen popover (new default) or a pop-out window
(prior behavior). The mode row appears only when the feature is on.
- New setting `experimentalAgentDashboardMode: 'in-window' | 'popout'`
(default in-window); sidebar entry branches on it.
- In-window: AgentDashboardOverlay renders the shared AgentKanbanBoard
in a near-fullscreen dialog, snapshot built locally via
useLiveDashboardSnapshot (the pop-out relays over IPC; in-window has
no relay). Ack/reveal act on the local store — the pop-out IPC
handlers are gated to the pop-out renderer.
- AgentKanbanBoard gains containerClassName/onAckAgent/onRevealAgent/
onClose props; defaults preserve the pop-out behavior.
- Extracted AgentDashboardExperimentalSetting to keep ExperimentalPane
under the max-lines cap.
* fix(agent-dashboard): admit main renderer to terminal-preview IPC for in-window dialog
The terminalPreview:* handlers gated every channel to the pop-out
renderer, so the in-window overlay's terminal dialog (running in the
main renderer) got { snapshot: null } from connect and falsely showed
"No live terminal — this agent's pane has closed." for live agents.
Accept the trusted UI renderer too — it already has full PTY access
through the regular terminal channels, so this adds no reach.
* fix(agent-dashboard): sync locale catalogs for new mode/close keys
verify:localization-catalog (part of lint CI) fails when en.json keys are
missing from the other locale catalogs; run sync:localization-catalog so
the six new agent-dashboard keys exist everywhere (English fallback text;
translated copy remains the documented follow-up).
* feat(agent-dashboard): present in-window mode as a companion board sheet
The in-window dashboard now uses the same non-modal left sheet as the
workspace kanban board — anchored to the sidebar edge, chrome/status-bar
bounds, sidebar stays interactive — instead of a near-fullscreen modal
dialog. Both companion boards are mutually exclusive; the sidebar entry
toggles the drawer. Removes the modal focus-restore timing coupling on
reveal.
* fix(agent-dashboard): ignore Radix dismiss requests like the workspace board
Non-modal Radix layers also request dismissal for interactions the drawer's
outside guards cannot classify — focus moving outside carries no pointer
coordinates, and clicks in the status bar / top chrome fall outside the
right-side dismiss band. Forward only open requests from the Sheet, matching
WorkspaceKanbanDrawer, so only the drawer's own escape/outside/close paths
close it.
* fix(agent-dashboard): guard reveal relay and refresh stale mode copy
CodeRabbit review: revealAgent lacked the ?. HMR-skew guard its sibling
ackAgent has (both channels shipped together, so a stale dev preload
lacks both). The es/ja/ko/zh catalogs also still described the dashboard
as pop-out-only in stale English, contradicting the new in-window
default; refreshed to the current English source.
* feat(agent-dashboard): add board settings menu to the in-window header
Mirrors the workspace board's settings gear: an Open as segmented control
in the board header so the mode is changeable without opening Settings.
Switching to pop-out hands the surface over (closes the drawer, opens the
window) instead of leaving a board the setting says should be a window.
In-window only via an optional headerActions slot - the pop-out renderer
has no store to drive it.
* fix(agent-dashboard): reset the settings-menu flag when the drawer closes
The pop-out hand-off closes the sheet while the menu is still open, so the
menu unmounts without Radix reporting onOpenChange(false). The stale
menuOpen=true then blocked outside-dismiss permanently on the next
in-window open. Mirror closeWorkspaceBoard by resetting the flag in close.
* fix(agent-dashboard): reset the menu flag on store-driven drawer closes
Cmd+B sidebar collapse and the workspace-board exclusivity effect close
the drawer via setAgentDashboardDrawerOpen directly, bypassing close();
a settings menu open at that moment unmounted without Radix reporting
onOpenChange(false), leaving menuOpen stuck true and outside-dismiss
disabled on the next open. Sync the flag to the open state so every
close path resets it.
|
||
|
|
91ee29a808 |
feat(editor): add an opt-in editor font family (default: follow terminal font) (#9658)
The code editor was hard-wired to the terminal font, so Nerd Font 'Mono' CJK glyphs overlapped in the editor. Adds an opt-in editorFontFamily setting; default is empty so the resolved value is byte-identical to today for every existing user. Closes #9628 |
||
|
|
6444be3a01 |
fix(accounts): follow the runtime for WSL provider account detection (#9537) (#9611)
* fix(accounts): follow the runtime for WSL provider account detection (#9537) On Windows + WSL, provider-account detection (usage recognition and the status-bar account switcher) was pinned to the Windows host even when the project runs in WSL, so WSL accounts were never recognized and the WSL switcher group never appeared. Root cause: `localAccountRuntime` hard-defaulted to 'host', which short-circuited `getInitialClaude/CodexRateLimitTarget` before the existing "follow the global Windows runtime default" branch could run. That branch was therefore dead for every real user. Fix: add an 'auto' value for `localAccountRuntime`, make it the default, and migrate the untouched legacy 'host' default to 'auto' once (guarded by `localAccountRuntimeDefaultedToAutoForAllUsers`; explicit 'wsl' is preserved). 'auto' resolves via a shared `resolveLocalAccountRuntimeTarget` helper: on a windows-host default it stays host (no behavior change); on a WSL default it follows WSL, so WSL accounts are recognized and the WSL group appears. Wired the shared resolver into the managed-account default target, the status-bar WSL-group gate, and the Accounts settings location toggle. Note: detection follows the global Windows runtime default, not the live active project's runtime (the fetch target is a single global value); the latter is a larger follow-up. * fix(accounts): align auto runtime consumers * fix(accounts): keep runtime polling aligned with settings --------- Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> |
||
|
|
580f8eb492 |
feat(status-bar): consolidate agent usage into a single roster popover (#8761)
* feat(status-bar): consolidate agent usage into a single roster popover The footer usage cluster is now one quiet, borderless control: each agent shows its tightest window as neutral text (letter badges at icon-only width), and clicking anywhere opens a consolidated Usage popover listing every agent worst-first — icon, name, plan, soonest reset, and per-window threshold-colored bars. Claude/Codex rows drill into their existing account switcher, runtime toggle, and Codex reset credits via a submenu variant of ProviderDetailsMenu; all other providers drill into their detail panel. Row actions and the footer links route to Settings. Also: barColor's <60% band goes green -> neutral so color is reserved for approaching limits (threshold color now lives only in the popover, the always-visible bar stays monochrome), Codex plan_type is surfaced as ProviderRateLimits.planType for the "Codex · Plus" label, and the Fable weekly window is labeled "Fable" so it no longer collides with "wk". * fix(status-bar): single-line sign-in row + review dedup Signed-out roster rows now read as one line (name · "not signed in" · right-aligned Sign in) instead of a floating button over an orphaned second line. Review follow-ups: the non-submenu ProviderDetailsMenu branch reuses the extracted panelBody, and the icon-only letter badge is one shared component so its has-data dot condition can't drift. * fix(status-bar): harden usage roster interactions * chore: remove unrelated formatting change * chore(skills): refresh bundle manifest for rc.2 * fix(status-bar): preserve usage roster semantics * fix(status-bar): preserve roster menu interactions * chore: remove unrelated formatting changes * fix(status-bar): keep usage reset countdowns live * feat(status-bar): toggle compact usage summary * fix(status-bar): simplify usage mode toggle * feat(status-bar): replace usage footer toggle with Compact/Detailed segmented control Swap the bottom-of-popover on/off switch for a SettingsSegmentedControl at the top of the Usage popover (view-switcher pattern), so both modes are named and discoverable on first open. Reuses the repo's canonical Compact/Detailed vocabulary from the Workspace card-layout control. |
||
|
|
3468b434d6 |
feat(dashboard): add agent dashboard popout (#9604)
* feat(dashboard): add agent dashboard popout * fix(dashboard): gate and harden agent popout * test(ipc): isolate dashboard handler registration * fix(dashboard): drop diff status from bucket counts * fix(dashboard): address review feedback * perf(dashboard): ignore unrelated store churn |
||
|
|
0989128287 |
refactor(comments): slim verbose comments in shared/cli/relay/preload (#9544)
Collapse multi-line explanatory comment blocks into single-line "why" statements
per AGENTS.md ("Document the Why, Briefly"): drop restatements of the code and
mechanism narration; keep the non-obvious reason, external refs, and directives.
Comments-only — verified no code changed via a Babel/esbuild comment-strip
token-equality gate against origin/main; typecheck and oxlint clean.
Area: shared, cli, relay, preload. 26 files changed, 1039 insertions(+), 3300 deletions(-).
Co-authored-by: Orca <help@stably.ai>
|
||
|
|
c5d40565af |
Clarify Orca Mobile pairing connection paths (#9425)
* Restructure mobile pairing setup into a clear stepped layout * Rework mobile pairing UI: radio-style path selector with sign-in gate fo * fix(mobile): resolve mobile-pairing review findings Applies the code-review findings for the reworked pairing UI: - Clear a displayed Relay QR on sign-out in both MobilePage and MobilePane (the rework dropped the wasSignedInRef watcher, leaving a stale Relay QR next to a "sign in required" prompt). Anywhere stays selected; the QR re-mints as local-only. [#1, #3] - Extract useMobilePairingConnectionMode so both panes resolve the saved preference identically instead of duplicating the state + resync effect. [#6] - Delete ~24 i18n keys the rework left unreferenced; sync locale catalogs to parity. [#4] - Give the connection-path radiogroup roving tabindex + arrow-key nav and document why it diverges from SettingsSegmentedControl. [#7] - Add MobilePane.test.tsx (previously untested safety logic) and a MobilePage saved-local-only restore test. [#2, #5] Verified: 22 targeted tests pass, typecheck + oxlint clean, localization catalog/coverage and max-lines ratchet green. * fix(mobile): resolve adversarial-review findings for pairing UI Address accepted findings from the mobile pairing UI rework: - MobilePane: add a request-generation epoch so a late getPairingQR response can't paint a stale Relay QR after sign-out, a mode switch, or an address change; arm rotation when discarding a pending mint. - MobilePage: on sign-in, upgrade a signed-out local-only fallback QR to Relay (invalidate + rotate-regenerate); handle null->connected too. - Extract useMobilePairingQrInvalidation so both sign-in/out edges and cross-window preference syncs invalidate/re-mint the QR consistently. - MobilePane: clear + rotate the QR when the selected address changes (manual pick or refresh-driven) so it can't encode the old endpoint. - MobilePairingConnectionOptions: guard the Sign in CTA on configured; show an Unavailable panel on unconfigured builds instead of dead CTA. - Drop the duplicate sign-in helper from MobilePairingSetupSection. - Remove dead i18n keys (title, recommended, signInToGenerate) and add relayUnavailable across all locale catalogs. - Add tests: deferred sign-out/mode-switch races, sign-in upgrade, cross-window sync, unconfigured build, arrow-key radiogroup. TODO left for the relay-label-honesty finding: getPairingQR does not expose the actually-encoded mode when an automatic offer degrades to local-only, so the mismatch can't be surfaced without a new return field. * fix(mobile): resolve relay-pairing deep-review regressions - MobilePane: invalidatePairing now clears loading so a superseded mid-flight generate can't wedge Generate disabled forever - MobilePage: stop auto-minting a local-only QR under the Relay label when signed out with Anywhere; gate Step 2 auto-generate and the Generate button on a shared canMintMobilePairingOffer helper, align with Settings, clear QR + loading on sign-out, mint Relay on sign-in - qr-invalidation: clear pairQrDataUrl (and loading) on every invalidation path so a stale QR can't stay scannable during rotation - Strengthen MobilePane/MobilePage tests for the aligned behavior and stuck-loading coverage - Translate relayUnavailable in es/ja/ko/zh Co-authored-by: Orca <help@stably.ai> * fix(mobile): refuse to mint QR when signed-out with Anywhere selected Replace silent degradation of Anywhere mode to local-only QR with explicit refusal. Add canMintMobilePairingOffer guard across all mint paths (address change, network invalidation, connection mode switch). This ensures the UI honestly encodes the selected pairing path. Improve keyboard focus visibility on the path selector by adding a persistent focus ring. * fix(mobile): detect and flag Relay provisioning degradation When Relay provisioning fails during automatic (Anywhere) pairing offer creation, the offer silently degrades to local-only. This confuses users who selected Anywhere expecting cellular capability. Add connectionMode field to the pairing offer result to expose what the offer actually encodes. The UI now flags degradation when the offered mode mismatches the user's selection. Also move credential rotation logic to the main process: rotate when requested mode differs from the pending token's encoded mode. This ensures QR codes displayed under an old policy can't pair under a new one, and windows reminting after preference sync converge on one token. Rename MobileRelayBetaAvailability → MobileRelayBetaNotice. * test(mobile): verify pairing codes don't flash during Relay mint Assert that the pairing QR and URL remain hidden while the Relay mint is pending, preventing confusing intermediate states when signing in unlocks Relay. --------- Co-authored-by: Orca <help@stably.ai> |
||
|
|
c5d2275c35 |
Add preference to show pinned worktrees in original lists (#6216)
* Add setting for pinned worktree group display Co-authored-by: Orca <help@stably.ai> * Fix pinned worktree host metadata Co-authored-by: Orca <help@stably.ai> * Polish pinned worktree setting copy Co-authored-by: Orca <help@stably.ai> * Clarify pinned worktree setting copy Co-authored-by: Orca <help@stably.ai> * Use original lists in pinned setting copy Co-authored-by: Orca <help@stably.ai> * Fix pinned sidebar order and render churn * Fix pinned host and inbox placement * Fix worktree host ownership consistency --------- Co-authored-by: Orca <help@stably.ai> |
||
|
|
5b75a87dd3 |
feat(native-chat): add per-model session option pickers with verified Claude switch (#9085)
* Add per-model session-option pickers (model/effort/fast-mode) to native Introduces a shared agent session-option catalog (Claude/Codex/Gemini/Cursor) with model-scoped options, launch-command composition, mid-session dispatch via slash commands, and per-model persisted defaults. Wires the new NativeChatSessionOptionPickers UI into the composer, threads sessionOptions through every startup-plan builder (worktree creation, onboarding, source control actions, folder workspace, direct work-item launches), and adds localized strings and tests across the affected surfaces. * Add per-model session-option pickers with a verified Claude model switch Native chat's model/effort picker now dispatches option commands through a body-then-verified-Enter write path and, for Claude, arms a PTY observer that classifies the cached-history confirmation prompt as applied/rejected/needs interaction before the picker returns — falling back to the terminal only when Claude genuinely requires manual input, and clearing stale truth otherwise. Also reorders the composer's model/effort pills, disambiguates their tooltip labels, and switches disabled-reason strings to a closed enum so producer and localized copy can't drift. * Add live model/effort detection from Claude's TUI header - Reads the mounted xterm's main-buffer snapshot (falling back to the rendered screen when the alternate screen owns the buffer) to parse Claude's header for the currently active model and effort, so the native chat picker reflects reality instead of only dispatched state - Extracts command-apply recording and reported-value application into dedicated modules, and factors file-link click handling into a hook, to keep native-chat-pty-session-options.ts focused |
||
|
|
1536171fdb |
Add a native macOS menu bar status item with activity indicator (#9042)
* Add native macOS menu bar status item with settings toggle Extend the Windows-only system tray into a shared status-item module so macOS gets a template menu bar icon (Open/Settings/Check for Updates/Quit), a theme-aware attention dot, and a "Show Menu Bar Icon" appearance setting. Also fixes a startup race where a tray "Settings…" click could fire before the renderer's ui:openSettings listener attached, by queuing a one-shot pending-open-settings intent the renderer consumes on mount. * Fix Retina blur, race conditions, and menu-label duplication in tray Set - Rebuild the tray attention icon's @2x representation since toBitmap only read 1x pixels, blurring the glyph on Retina displays - Fix premultiplied-alpha math so light-glyph tinting uses per-pixel alpha instead of a flat 0xff, keeping antialiased edges valid - Always push ui:openSettings and leave a longer-lived pending flag, since there was no reliable signal that a renderer's listener was attached - Preserve tray attention state across macOS menu-bar hide/show toggles instead of resetting it on tray destroy - Route macOS tray creation through syncMacMenuBarIcon so startup and the live toggle share one visibility policy - Reuse app-menu translation keys for tray Settings/Check for Updates and drop the now-duplicate tray-scoped locale strings * Make menu bar icon default on and platform-independent in settings Previously showMenuBarIcon was hardcoded to darwin-only in both the default settings and the sanitize/load paths, so a profile written on macOS lost its opt-out when touched from another OS. Store the raw boolean everywhere and let darwin-specific consumers decide whether to act on it, so the value round-trips unchanged across platforms. * Fix Settings menu click being silently dropped after a slow cold rendere Replace the 60s pendingOpenSettings TTL with an untimed intent, since a cold renderer start can outrun any fixed timeout and cause the flag to expire before the click is consumed. webContents-id scoping plus consume-on-read still prevent the intent from leaking to an unrelated renderer. Adds a test covering the queued-before-mount pull path. |
||
|
|
a2d3451efd |
Fix manual project order across paired runtime hosts (#8894)
* fix(sidebar): persist manual project order across hosts Co-authored-by: Orca <help@stably.ai> * fix(sidebar): scope desktop repo reorder by host Co-authored-by: Orca <help@stably.ai> * fix(web): cover host-scoped repo reorder API Co-authored-by: Orca <help@stably.ai> --------- Co-authored-by: Orca <help@stably.ai> |
||
|
|
9dc1f253ad |
feat(editor): add file editor word wrap preference (#8423)
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com> |
||
|
|
7fe6742b8b |
fix(pr-comments): let users mark comment authors as bots for the Humans/Bots filter (#7598)
* fix(pr-comments): let users mark comment authors as bots for the Humans/Bots filter Some review bots post from regular user accounts that defeat both provider bot metadata and login heuristics, so their comments were misclassified as human. Adds a persisted prBotAuthorOverrides setting with a "Mark author as bot" comment action, applied consistently across desktop and mobile. Fixes #7597 Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * fix(pr-comments): address review feedback on bot-author overrides - Cap sanitized prBotAuthorOverrides at 500 entries so malformed payloads can't bloat GlobalSettings or slow comment classification - Reuse the shared normalizePRCommentAuthorLogin in isBotPRComment on desktop and mobile instead of duplicating the normalization inline - Pass botAuthorOverrides from CommentRow to CommentMoreMenu instead of re-subscribing per menu instance - Re-fetch mobile bot-author overrides alongside each PR refetch so they don't stay a stale one-shot snapshot for the whole session Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * fix(pr-comments): harden bot author override sync * fix(pr-comments): bound and recover override updates * fix(pr-comments): merge overrides from canonical settings * fix(pr-comments): make bot override updates atomic * fix(pr-comments): surface rejected bot overrides * fix(i18n): translate bot override warning * fix(i18n): translate bot author actions --------- Co-authored-by: Dzmitry Bachko <dbachko@users.noreply.github.com> Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: Brennan Benson <brennanbenson@Brennans-MacBook-Pro.local> |
||
|
|
8b8e1bbab2 |
fix: restore the active top-level view across renderer reload (#8265)
Persist and safely restore the active top-level view on startup. Unknown, removed, legacy, or unavailable views fall back to the terminal, while cross-window UI sync cannot navigate the current window.\n\nCloses #8264 |
||
|
|
8a4ec4e856 |
feat(terminal): expose right-click paste on every platform (#8322)
* WIP: Changes before auto-review fixes * fix: preserve terminal paste defaults across platforms |
||
|
|
6be8687c34 |
feat(status-bar): notify upgraded users usage meters show % used (#8319)
* feat(status-bar): notify upgraded users usage meters show % used Show a one-time status-bar callout when upgraded profiles still use the new percent-used default. Brand-new profiles and users who already chose remaining stay quiet; dismissing or changing the setting is permanent. * Add settings deep-link to expand Appearance's Window accordion for one-s - Replaces the searchQuery-based redirect (fragile, flashed filter UI) with a dedicated appearanceAccordionDeepLink store field that force-opens the correct accordion and scrolls to the target row - Rebuilds the status-bar change notice as a plain elevated card instead of a Popover, since PopoverContent's glass/backdrop-filter defaults fought the opaque callout styling and needed heavy overrides - Simplifies the light/dark card CSS tokens accordingly * Reposition status-bar usage-change notice via fixed-position portal Portal the one-shot callout to document.body with fixed positioning anchored via getBoundingClientRect, since the status-bar's overflow-hidden flex ancestors clipped or mispositioned the previous in-tree absolute layout. * Refine status-bar usage notice styling and test coverage - Replace hand-tuned light/dark card colors with existing design tokens (--popover, --border) and the documented floating elevation, so the callout stays in sync with the design system instead of duplicating its own palette - Add tests covering dismiss via X button, "Got it", and Escape - Scope the foreground-process confirm assertion to the pane's ptyId so an unrelated pane's delayed confirm can't cause a false failure |