Skip to content

feat(opencode): add killswitch indicators to quota toast#38

Open
iceteaSA wants to merge 4 commits into
cortexkit:mainfrom
iceteaSA:feat/killswitch-toast
Open

feat(opencode): add killswitch indicators to quota toast#38
iceteaSA wants to merge 4 commits into
cortexkit:mainfrom
iceteaSA:feat/killswitch-toast

Conversation

@iceteaSA
Copy link
Copy Markdown
Contributor

@iceteaSA iceteaSA commented May 21, 2026

Dependencies: Requires #35 (killswitch) and #36 (quota-toast) to be merged first.

Adds killswitch awareness to the quota toast (the integration of #35 + #36):

  • Per-account status word in the toast: blocked for killswitch-killed accounts, active / idle for routable ones.
  • Killswitch-aware active-account selection: the first fallback that passes quota policy and is not killswitch-blocked, otherwise main (never a failing fallback).
  • The toast reads main/fallback quota from the shared, token-aware QuotaManager cache.
  • Inherits the killswitch fetch-gate, the fresh/token-aware quota reads, and the Retry-After computation from feat(opencode): killswitch — block requests when quota drops below threshold #35.

Single file change: packages/opencode/src/index.ts.


Summary by cubic

Adds killswitch-enforced routing and an auto-showing, restyled quota toast with active/idle/blocked indicators. Killed accounts never serve; if none are routable, we return a synthetic 429 with a Retry-After based on the earliest reset across all accounts.

  • New Features

    • /claude-killswitch: view/toggle and set per-account thresholds; persisted in config. Uses killswitchPassesPolicy and killswitchRetryAfterSeconds.
    • Unified routing: getRoutableFallbackAccounts filters fallbacks by quota policy and killswitch across fallback‑first, skip‑main, retries, and the gate. Quota toast reads the shared QuotaManager cache, shows 5h/7d bars, marks active/idle/blocked, and auto‑shows after refreshes.
  • Bug Fixes

    • Fail‑closed gate uses token‑aware reads and returns 429 with Retry-After when the quota API is backed off and no cache exists.
    • Sidebar keeps the active fallback after async main quota refreshes and updates without requests (via onFallbackStorageChanged).
    • Fixed a shadowed sessionRequestCount that could skip the every‑N quota refresh.

Written for commit b520a7b. Summary will update on new commits.

Review in cubic

Greptile Summary

This PR integrates the killswitch feature (#35) with the quota toast (#36), adding killswitch-aware routing and per-account status words (blocked/active/idle) to the quota display. It also wires up the onFallbackStorageChanged callback so background quota refreshes update the sidebar without resetting the active account, and centralises all fallback selection through getRoutableFallbackAccounts so a killswitch-killed account can never serve a request.

  • Killswitch-aware routing: getRoutableFallbackAccounts wraps getUsableFallbackAccounts with a killswitch policy filter and is now used by every fallback selection path (fallback-first, soft-quota skip-main, error recovery, killswitch gate itself).
  • Quota toast: a new showQuotaToast/showQuotaToastFromCache pair renders 5h/7d bars with padded percentages, reset times, and status words; the toast fires on fresh quota fetches and after background refreshes complete.
  • Sidebar preservation: lastSidebarRouting remembers the last explicit routing decision so background quota refreshes (refreshSidebarQuota) never clobber the active account back to main.

Confidence Score: 5/5

The killswitch gate, fallback filtering, and sidebar preservation are all correctly wired; no hard-blocking path falls through to a killed account.

All routing paths go through getRoutableFallbackAccounts, the eager quota refresh correctly re-reads mainQuota before the killswitch evaluation, and the onSuccess callback in the killswitch fallback path corrects the sidebar. The remaining findings are cosmetic display issues that do not affect routing correctness or request delivery.

packages/opencode/src/index.ts — the showQuotaToastFromCache function and the toast header reset-time computation.

Important Files Changed

Filename Overview
packages/opencode/src/index.ts Core plugin file with 436-line addition; integrates killswitch gate, quota toast, sidebar preservation, and getRoutableFallbackAccounts. Minor inconsistency in getMain() call without access token inside showQuotaToastFromCache. Toast only shows 5h window reset time, omitting the 7d window.
packages/core/src/accounts.ts Adds KillswitchConfig/KillswitchThresholds types, killswitchPassesPolicy, killswitchRetryAfterSeconds, setKillswitchPersistent, and onFallbackStorageChanged callback to FallbackAccountManager. Logic is correct and well-covered by tests.
packages/core/src/killswitch.ts New file: killswitch command parsing, execution, and status table. Command parsing and set/on/off semantics look correct. Tests cover all branches.
packages/opencode/src/tests/index.test.ts Adds 548 lines of integration tests covering killswitch gate, fallback-first killswitch filtering, fail-closed behaviour, sidebar update, and surviving-fallback error passthrough. Good coverage.
packages/opencode/src/tests/killswitch.test.ts 537-line unit tests for killswitch command parsing, policy evaluation, retry-after computation, and persistent config storage. Comprehensive coverage.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Incoming fetch request] --> B{replayable && quotaRoutingEnabled?}
    B -- yes --> C[getMain with access token]
    C -- no cache --> D[refreshMain sync\nshowQuotaToastFromCache]
    C -- stale / N-boundary --> E[refreshMain background\nshowQuotaToastFromCache in .then]
    C -- fresh --> F[writeSidebarState main]
    D --> F
    E --> F
    B -- no --> G{failClosedOnUnknownQuota?}
    F --> G
    G -- quota backed off & no cache --> H[Return synthetic 429 failClosed]
    G -- ok --> I{isKillswitchEnabled?}
    I -- yes & needsRefresh --> J[await refreshMain + refreshAllFallbacks\nre-read mainQuota]
    I -- no / already fresh --> K{killswitchPassesPolicy main?}
    J --> K
    K -- passes --> L[sendWithAccessToken main]
    K -- killed --> M{canReplayToFallback?}
    M -- yes --> N[getRoutableFallbackAccounts\ntryUsableFallbackAccounts\nonSuccess: writeCurrentSidebarState fallback]
    M -- no --> O[killswitchRetryAfterSeconds\nReturn 429 with Retry-After]
    N -- response --> P[createStrippedStream response]
    N -- null --> O
    L -- success --> Q[Return response]
    L -- fallbackStatus --> R[tryFallbackAccounts\nkillswitch filter inside]
Loading

Comments Outside Diff (1)

  1. packages/opencode/src/index.ts, line 1918-1942 (link)

    P1 Quota routing bypasses killswitch filtering for fallback accounts

    When quotaSnapshotPassesPolicy returns false for main, preselectedFallbackAccounts is fetched from getUsableFallbackAccounts without killswitch filtering and passed directly to tryUsableFallbackAccounts. If a fallback is below its killswitch threshold at this point, the routed request is delivered to that account — the killswitch block below (lines 2076–2109) only runs afterward if main itself is killswitch-killed, not when main merely crossed the softer routing threshold. The same gap exists in the fallback-first path at line 1763. The fix is to filter preselectedFallbackAccounts against killswitchPassesPolicy(getFallbackQuota(a), storage, a.id) before calling tryUsableFallbackAccounts, mirroring what the mainKillswitched path does at line 2085–2090.

Reviews (11): Last reviewed commit: "Merge branch 'fix/sidebar-fallback-quota..." | Re-trigger Greptile

Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 10 files

Tip: cubic can generate docs of your entire codebase and keep them up to date. Try it here.

Fix all with cubic | Re-trigger cubic

Comment thread packages/core/src/quota-manager.ts
@iceteaSA iceteaSA force-pushed the feat/killswitch-toast branch 5 times, most recently from 16c1a8b to b6cc376 Compare May 22, 2026 17:08
@iceteaSA iceteaSA force-pushed the feat/killswitch-toast branch 10 times, most recently from 2064776 to 04b768a Compare June 3, 2026 18:24
Comment thread packages/opencode/src/index.ts Outdated
Comment thread packages/opencode/src/index.ts Outdated
@iceteaSA iceteaSA force-pushed the feat/killswitch-toast branch from 04b768a to a3fca77 Compare June 3, 2026 18:58
Comment thread packages/opencode/src/index.ts Outdated
@iceteaSA iceteaSA force-pushed the feat/killswitch-toast branch from a3fca77 to 4e89c5f Compare June 3, 2026 19:14
Comment thread packages/opencode/src/index.ts
@iceteaSA iceteaSA force-pushed the feat/killswitch-toast branch 4 times, most recently from 3fd2fbf to 930eee6 Compare June 3, 2026 20:20
Comment thread packages/opencode/src/index.ts
@iceteaSA iceteaSA force-pushed the feat/killswitch-toast branch 3 times, most recently from 6081cc0 to c4f8057 Compare June 3, 2026 21:02
@iceteaSA
Copy link
Copy Markdown
Contributor Author

iceteaSA commented Jun 4, 2026

Updated: this branch now carries the sidebar quota fix (#57), merged in to keep the sidebar-touching branches consistent. #57's commits appear in this PR's diff until #57 merges to main, after which the diff reduces to this PR's own changes. Merge order: #57 first.

iceteaSA added 4 commits June 4, 2026 10:02
…reshold

Self-review fixes folded in:
- Token-aware fail-closed read: const mainQuota = quotaManager.getMain(auth.access)
  so a previous main account's cached quota can't satisfy the fail-closed check
  or feed the killswitch eval after a main-account switch.
- Removed a stray inner 'let sessionRequestCount = 0' + unconditional increment
  that shadowed the process-scoped counter, which had left the active-route
  fallback every-N refresh reading a never-incremented counter.
Displays quota usage bar notifications via client.tui.showToast after
quota data is refreshed. Shows main and fallback account usage with
visual bars, percentage, and reset time. Toast variant reflects
severity (info < 70%, warning >= 70%, error >= 90%).
Align the quota refresh toast with the sidebar's visual language:

- Replace the emoji status dots with status words (active/idle)
- Use the shared bar width and a padded percentage via a quotaLine helper
- Rename the seven-day label from 1w to 7d to match the sidebar
- Keep severity-driven variant color (info/warning/error)
Layer killswitch awareness onto the restyled toast (isKilled predicate,
blocked status word, ksIsKilled-aware showQuotaToastFromCache).

Also restores the process-scoped 'let sessionRequestCount' (a prior cascade
had flipped it to const).
@iceteaSA iceteaSA force-pushed the feat/killswitch-toast branch from 98bbba1 to b520a7b Compare June 4, 2026 08:03
Copy link
Copy Markdown

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

iceteaSA has reached the 50-review limit for trial accounts. To continue receiving code reviews, upgrade your plan.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant