Repository navigation
feat(accounts): switch off any AI provider - #42
Merged
Merged
Conversation
Settings -> AI Provider Accounts now has a master switch at the top of each provider section. Turning a provider off stops Orca both from polling its usage/quota and from reading its sign-in out of software installed on this computer (cursor-agent Keychain, ~/.codex/auth.json, gh CLI, the Grok, Gemini and OpenCode auth files, the macOS Claude Keychain), hides its status-bar meter, and drops any cached snapshot. Providers stay on by default; the switched-off set persists in GlobalSettings.disabledUsageProviders. The rate-limit service takes an isUsageProviderDisabled probe read from settings, so the gate covers the full cycle, the codex/claude/grok-only cycles, the inactive-account probes, the on-disk credential-presence flags and the codex reset-credit redemption. The pane's cursor/grok status endpoints and the startup gh probe are gated the same way, and RuntimeAccountController returns an empty Claude/Codex roster so a disabled provider's system-default identity is never read.
nplez1
force-pushed
the
nplez1/Disable-AI-Providers
branch
from
September 29, 2026 10:24
5370e8b to
c224a5f
Compare
nplez1
added a commit
that referenced
this pull request
Sep 29, 2026
Settings -> AI Provider Accounts now has a master switch at the top of each provider section. Turning a provider off stops Orca both from polling its usage/quota and from reading its sign-in out of software installed on this computer (cursor-agent Keychain, ~/.codex/auth.json, gh CLI, the Grok, Gemini and OpenCode auth files, the macOS Claude Keychain), hides its status-bar meter, and drops any cached snapshot. Providers stay on by default; the switched-off set persists in GlobalSettings.disabledUsageProviders. The rate-limit service takes an isUsageProviderDisabled probe read from settings, so the gate covers the full cycle, the codex/claude/grok-only cycles, the inactive-account probes, the on-disk credential-presence flags and the codex reset-credit redemption. The pane's cursor/grok status endpoints and the startup gh probe are gated the same way, and RuntimeAccountController returns an empty Claude/Codex roster so a disabled provider's system-default identity is never read.
nplez1
added a commit
that referenced
this pull request
Oct 1, 2026
Settings -> AI Provider Accounts now has a master switch at the top of each provider section. Turning a provider off stops Orca both from polling its usage/quota and from reading its sign-in out of software installed on this computer (cursor-agent Keychain, ~/.codex/auth.json, gh CLI, the Grok, Gemini and OpenCode auth files, the macOS Claude Keychain), hides its status-bar meter, and drops any cached snapshot. Providers stay on by default; the switched-off set persists in GlobalSettings.disabledUsageProviders. The rate-limit service takes an isUsageProviderDisabled probe read from settings, so the gate covers the full cycle, the codex/claude/grok-only cycles, the inactive-account probes, the on-disk credential-presence flags and the codex reset-credit redemption. The pane's cursor/grok status endpoints and the startup gh probe are gated the same way, and RuntimeAccountController returns an empty Claude/Codex roster so a disabled provider's system-default identity is never read.
nplez1
added a commit
that referenced
this pull request
Oct 6, 2026
Settings -> AI Provider Accounts now has a master switch at the top of each provider section. Turning a provider off stops Orca both from polling its usage/quota and from reading its sign-in out of software installed on this computer (cursor-agent Keychain, ~/.codex/auth.json, gh CLI, the Grok, Gemini and OpenCode auth files, the macOS Claude Keychain), hides its status-bar meter, and drops any cached snapshot. Providers stay on by default; the switched-off set persists in GlobalSettings.disabledUsageProviders. The rate-limit service takes an isUsageProviderDisabled probe read from settings, so the gate covers the full cycle, the codex/claude/grok-only cycles, the inactive-account probes, the on-disk credential-presence flags and the codex reset-credit redemption. The pane's cursor/grok status endpoints and the startup gh probe are gated the same way, and RuntimeAccountController returns an empty Claude/Codex roster so a disabled provider's system-default identity is never read.
nplez1
pushed a commit
that referenced
this pull request
Oct 19, 2026
…f the deprecated owner resolver; spawn refuses folder twins (stablyai#30497) * refactor(renderer): fold launch, resume, note-send, paste and terminal-create callers off the deprecated owner resolver Agent launch, AI-vault resume and reveal, note target and send, wake and creation seeds, native chat owner, the shell menu and active-workspace terminal creation (#22, #31-#42, #44) read the owner through runtimeEnvironmentIdWithLegacyFallbackUnlessTwin. Paste uploads images to the server its PTY id names before falling back to the workspace owner. The folder and floating branch of the terminal spawn route (#53) reads resolveOwnerWithLegacyFallback: spawn refuses a folder twin instead of following the focused server, and teardown keeps tearing raw ids down locally. Deprecated importers 31 -> 16. * test: give the paste-listener and tab-bar probes the fields the folded callers read * test: note why the folder-twin route fixture is cast * test: type the paste-owner twin fixture
nplez1
added a commit
that referenced
this pull request
Oct 31, 2026
…header (#256) * feat(accounts): give every provider one switch in its own header The per-provider switch added in #42 was bolted on outside each section: a bordered "Let Orca query <name>" box above the provider's own header, wired by a hand-maintained gateUsageProvider(...) call per section. Two consequences: - the switch never read as part of the provider it belongs to; - providers added later never got one. antigravity and zcode had no switch at all (service-configuration.ts said so in a comment), and kimi had no section. AI Provider Accounts is now driven by PROVIDER_ACCOUNTS_SECTIONS: one entry per provider (id, icon, title, description, search entries, header link, body renderer). ProviderAccountsSection renders that header with the switch leading the row, so a provider cannot reach the pane without one, and the shared header is what fixes the inconsistency the user sees. The twelve section renderers lost their duplicated <section>/<h3>/subtitle markup. kimi, antigravity and zcode joined USAGE_PROVIDER_IDS and are gated in main's cycle: Kimi's home resolver is not called while off, antigravity folds into antigravityFetchAllowed, and resolveZcodePlanConfig returns before reading the encrypted plan key. Kimi got a minimal section (switch plus where its usage comes from; it has no credentials to configure). Tests: provider-accounts-sections.test.ts holds the registry against USAGE_PROVIDER_IDS in both directions, and AccountsPane.test.tsx now iterates the registry instead of a copied label list. AccountsPane.fixture.tsx renders the whole pane for a screenshot in both states. * fix(accounts): re-read the provider registry per translation and cover the new gates Review round 1 found two blocking gaps. The registry was a module-level const, so every provider title, description, the switch's accessible name and the off notice were translated once at import — a language change with no reload left them stale, and a non-English catalog that loads after the module could strand English headings. It is now a `createLocalizedCatalog` getter, the pattern `accounts-search.ts` already uses, and the deleted gate had guarded exactly this. The module-scope label comment that went with the old per-render labels is restored in spirit. None of the three new main-process gates had a test: kimi, antigravity and zcode were gated by one `disabled.has(...)` each with nothing to fail if it were dropped. `service-refresh-orchestration.test.ts` gains one case per provider asserting the resolver, the fetcher and the presence flag, not just the state. Also: a compile-time assertion that every `ProviderRateLimits['provider']` is in `USAGE_PROVIDER_IDS`, so a future metered provider cannot ship without a switch; two comments cited the wrong test filename; and the usage-row settings-target comment still said Kimi and Antigravity have no account settings.
nplez1
added a commit
that referenced
this pull request
Nov 1, 2026
… Source Control (#267) * feat(right-sidebar): pick between a workspace's reviews in Checks and Source Control A workspace can hold several review attachments while its compatibility slots name only one active review, and both panes only ever showed that one. The Checks pane's review number and the Source Control header's review link are now split controls: the link keeps opening the review, and a chevron lists every review the workspace holds (`PR #12` / `MR !7`, with the title) and switches which one the pane describes. Switching drives the panes' reads, not just their labels: - Checks re-reads checks, comments and review metadata for the picked review's number, drops the previous review's rows while that read is in flight, and maps the review onto the PR shape the pane already keys its head-sha freshness gate and comment actions on. - Source Control's header link, review actions and compare base follow the pick. The file list, commit area, push target and PR creation keep resolving through the workspace's own review: pointing those at another branch's review would retarget a push. The selection is the panes' view, not the workspace's link. It lives in the fork's session-only renderer slice, keyed by worktree and shared between the two panes so they cannot disagree, and never moves `linkedPR` — that slot is what push-target and PR-creation safety resolve through. It is keyed on provider and number rather than the attachment key, which moves when a background refresh fills in a title or URL. Unlinking now removes the review the switcher shows instead of the workspace's slot, so it cannot unlink the review the user is not looking at. A picked review is read through its own hosted-review cache key, so switching does not overwrite the branch's review entry, and the Checks Refresh button refreshes the picked review rather than the branch's. * fix(right-sidebar): close the two blockers the review found in the review switcher Round 1 (Opus 5.5, high) found two blocking defects and six should-fix items. The first blocker: the Checks pane's Refresh took its number from any picked review, so a picked GitLab MR sent `!7` to GitHub as PR #7 and a picked Bitbucket PR reused the branch PR's head. The picked path now branches on the provider the pane actually renders — GitHub checks and comments for a GitHub review, `fetchGitLabDetails` for an MR, and the branch path for anything this pane cannot render. The second: both panes cleared the shared pick whenever their own default matched it. Their defaults come from different async sources, so a transient null in one pane wiped the other's pick. Nothing writes the selection from a derived condition any more; the selection's flag stays a per-pane render decision, and a stored key that names another pane's default is simply that pane's default. Should-fix items closed: - The Checks switcher lists only reviews this pane can render, and its checked row is the review the header shows, so a listed row is never a review that is not on screen. - Every "is a pick shown" decision reads the same value: the PR-check fetch no longer passes this worktree's branch for a picked review, which had been writing another PR's check status onto this branch's cached PR. - Refresh-after-mutation and the entry refresh follow the pick; the triage strip's worktree-acting AI actions are hidden for a pick, because they start an agent in *this* worktree and would fix another branch's checks by committing here. - Unlinking removes the row that carries the shown review and tombstones a GitHub one, so a slot-only clear cannot leave the review behind, and a failed update now reports. - The selection is memoized, rows clear with a loading state, and the Refresh of a pick keys its results on the refreshed head instead of discarding them. Tests cover the decision everything downstream reads (`shown-review.test.ts`), both provider branches of a picked Refresh, the picked unlink, the header with a pick, and that the hook never writes the shared selection on its own. * fix(right-sidebar): close what the delta re-check found, including one regression of its own The delta review (Opus 5.5, high) confirmed both round-1 blockers resolved and found one blocker and three should-fix defects introduced by those fixes. The blocker: the picked Refresh's staleness guard compared `asyncResultKeyRef.current` with itself, which always passes, so a switch mid-flight wrote the old pick's rows under the new review's header. The key is now captured locally, assigned to the ref, and compared as a local — and the loading flags are cleared only by the request that still owns them. Should-fix items closed: - `findWorkspaceReviewAttachment` matched issues too. A GitLab issue and an MR have separate number spaces, so unlinking `!42` could remove the issue `#42`; the search is filtered to review attachments, with a test that shares a number. - Unlinking a picked GitHub review overwrote the `suppressedGitHubPR` tombstone of a branch PR the user had already unlinked, letting that PR come back. The tombstone is written only for the workspace's own review, which is the only one the branch lookup can resolve. - The row-clear effect raised the loading flags on mount and on every return to the workspace review, where nothing guarantees a matching clear. It now runs on a switch only, and raises the flags only for a pick. Minor items closed: the shared pick is cleared only when it names the review being unlinked; Unlink is disabled again for a review with no row and no slot, whose unlink would do nothing; the switcher never checks a row the header is not showing, in either pane; a picked GitHub read no longer clears this branch PR's refresh error; and a picked MR is re-read on tab entry and after a mutation, like the branch path. Two round-1 gaps in coverage are also closed: the picked Refresh's guard is now exercised with a real comparison and a mid-flight switch, and the polling path is pinned to pass no branch for a pick. * fix(right-sidebar): stop a mid-refresh switch from claiming the next review's result key Round 3 (Opus 5.5, high, on the R2..R3 delta) confirmed eight of the nine round-2 findings resolved and found that N1 was only half-closed. The remaining hole: after `refreshSelectedReview()` resolved, the picked Refresh only checked `isCurrentRequest()`, which a review switch does not invalidate. It then claimed `asyncResultKeyRef.current` with the *old* pick's key, so the old review's rows landed under the new header and the new review's own in-flight read was discarded. The GitLab sub-path had the same shape, passing no currency predicate to `fetchGitLabDetails`. The fix answers "is this still the review on screen" from the stored selection rather than the result key: the result key cannot tell a switch from the refreshed review landing with a new head, which is the refresh working. Both the claim and the loading flags now sit behind that check, while the refresh latch stays on the request alone so it is always released. Also closed: a pick whose row no longer matches no longer falls through to the branch slot path, which would have addressed a review that is not on screen (and could write the pick's number over an earlier branch-PR tombstone); the duplicate dependency in the entry-refresh hook; and the redundant provider guard, now that `findWorkspaceReviewAttachment` returns the narrowed attachment type. Coverage added for the round-2 items that had none: a switch inside `refreshSelectedReview`, an issue and an MR sharing a number with no URLs (asserting the issue survives the removal), no tombstone written for a pick, no slot path for a pick with no row, and no branch-PR error retraction from a picked read. * test(right-sidebar): make the picked-refresh and picked-unlink assertions able to fail Round 4 (Opus 5.5, high, on the R3..R4 delta) came back clean: no blockers, no should-fix, and all seven round-3 findings resolved. Its three NITs were all about tests that could not fail, so they are worth closing even though a NIT never blocks. - The tombstone assertion used `expect.not.objectContaining({ suppressedGitHubPR: expect.anything() })`, which passes for a write of `suppressedGitHubPR: null` — exactly the write that would clear an earlier branch-PR tombstone and let that PR come back. It now asserts the key's absence. - The mid-refresh-switch test now also pins that the result key was never claimed and that the refresh latch was released; a regression gating the latch on the pick check would have left Refresh disabled for good with no test failing. A second case switches the selection after the key was claimed, so the post-read pick check has its own test instead of relying on the result-key half. - The issue/MR number-collision test moved to the GitLab unlink block, where it belongs, and its comment was wrong. Both new guards were checked against their own absence: removing the post-read pick check fails the claimed-key test, and removing the pick-with-no-row guard fails the no-row test. The issue/MR test already discriminated, since the removal is keyed on an attachment identity that folds in the type. One round-4 assumption was checked and left alone: the GitLab details path clears its loading flags on an abandoned request, which looks like the round-3 flags bug but is deliberate — a generation counter keeps it off a newer request, and an upstream test pins that an abandoned relink must release them or the pane keeps a spinner forever. Adding a currency guard there broke that test, so the change was reverted rather than the contract.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
ELI5
Each AI provider in Settings can now be switched off. While it is off, Orca stops asking that provider for your usage and stops reading its sign-in out of the apps already installed on this computer (Cursor, Codex, the GitHub CLI, Grok/Gemini/OpenCode, Claude). Flipping the switch back restores the old behaviour.
What Changed
Before: every provider Orca supports was always queried. If
cursor-agent,codex,gh, or the Grok/Gemini CLI was installed and signed in, Orca read that sign-in and polled usage on its own, with no way to opt one provider out.After: the top of each of the ten provider sections in Settings → AI Provider Accounts has a master switch ("Let Orca query Claude"). Turning it off:
cursor-agentKeychain and Cursor IDE DB,~/.codex/auth.json,gh auth status,~/.grok/auth.json,~/.gemini/oauth_creds.json, OpenCode'sauth.json/SQLite, the macOS Claude Keychain, and the MiniMax/DeepSeek/Fireworks/Copilot stores;nulltoo);Providers stay on by default. The switched-off set persists in
GlobalSettings.disabledUsageProviders(a profile that predates the key reads it as empty, i.e. everything on).Why
Automatic pickup is the convenient default, but it is not always wanted: a machine may be signed in to a work account, Cursor may be installed for an unrelated reason, or a user simply does not want Orca touching a provider. The ask was to be able to switch that off completely, per provider.
The gate lives in the rate-limit service rather than the UI because that is where the reads happen. The service takes an
isUsageProviderDisabledprobe read from settings; that one predicate covers the full cycle, the codex/claude/grok-only cycles, the inactive-account probes, the on-disk credential-presence flags ingetState(), and the codex reset-credit redemption. The pane's cursor/grok status endpoints and the startupghprobe are gated the same way, andRuntimeAccountControllerreturns an empty Claude/Codex roster so a disabled provider's system-default identity is never read. Disabled state slots are forced tonulllast, which also defeats Claude's live-session stale-preservation.Considered and rejected: gating in the renderer only (misses the startup probes and the account snapshot), and a per-provider opt-in (default-on is what makes the other providers work out of the box).
Linked Issue
N/A — fork-only feature with no tracking issue.
Visual Proof
A before/after capture is not attached from this environment. The change is one master switch at the top of each provider section, plus an "off" notice replacing that section's controls.
AccountsPane.test.tsxasserts the switch label for all ten providers and the notice/suppression when one is off. Happy to attach a capture if you want one before merging.Testing
Verification was automated, not a manual app run:
pnpm typecheckclean.pnpm run check:code-quality:changed,pnpm run check:max-lines-ratchet, localization extraction/coverage and the RPC-params catalog all pass.Affected suites: 329 files / 2702 tests — shared normalization, status-bar disabled cases, rate-limit service "does not fetch or read credentials", codex reset-credit refusal, grok disabled status, AccountsPane switch/notice.
Full suite with
ORCA_BACKGROUND_LAUNCH=1: only pre-existing environmental failures remain (missingpgincloud/, missing git tagv1.4.211), both of which pass in isolation.I manually tested these changes locally
Automated tests added/updated, or explained why not below
AI Disclosure
Authored with an AI coding agent (DeepSeek, model
deepseek-flash) in the pi harness. The plan was reviewed before implementation, the diff was self-reviewed, and typecheck plus the changed-code quality gate and affected test suites were run locally.Review
Agent skill upstream boundary
docs/reference/agent-skill-sharing-upstream-boundary.mdand copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.Notes
disabledUsageProvidersis a local desktop setting, like every other provider setting (geminiCliOAuthEnabled,opencodeSessionCookie), and is not in the RPC settings projection. A remote Orca Server that owns the account polling must set it on its own store.Checklist
N/Awith reasonpnpm lint,pnpm typecheck,pnpm test, andpnpm buildpass (or CI will cover; local preferred)