Commit Graph
10 Commits
Author SHA1 Message Date
2c5c629db8 fix(usage): read Cursor token from modern state.vscdb key via node:sqlite (#2704)
* fix(usage): read Cursor token from modern state.vscdb key via node:sqlite

Settings → Usage showed Cursor as unavailable for a normal Cursor desktop
login. The quota fetcher read the access token from the legacy `cursorAuthStatus`
key, but current Cursor builds store a plain JWT under `cursorAuth/accessToken`
with no such legacy row, so the lookup returned null.

Read the token with built-in node:sqlite instead of shelling out to a `sqlite3`
CLI (absent by default on Windows) — a second, independent cause of the same
"token not found → unavailable" failure. Prefer the modern key, fall back to the
legacy `cursorAuthStatus` blob for older builds, and log read failures at debug
so an unavailable card stays diagnosable.

Fixes #2586

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test(usage): cover Cursor sqlite text and blob auth

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
2026-08-12 12:25:04 +02:00
1a117652fd Fix Kimi usage refresh, 5 hour session limit, and reasoning levels (#2743)
* Refresh expired Kimi usage tokens

* Test Kimi usage token refresh

* Harden Kimi usage token refresh

Follow-up review of the refresh path added in f6ce001a.

Credential reads no longer depend on the shape of fields this provider
only writes back: declaring expires_at and friends as required numbers
meant a null or stringified value threw away the access token sitting
next to them, turning a working Kimi quota bar into "unavailable" with
no way back. Those fields are nullish and coerced now.

Refreshed tokens are merged into a re-read of the credential file rather
than the copy the request started with, so a write by Kimi Code during
the refresh round-trip survives, and a file deleted in that window stays
deleted instead of being resurrected with fresh credentials. A failed
write logs at warn: Kimi rotates the refresh token, so losing that write
silently logs the user out of Kimi Code.

Only 401 triggers a refresh. A 403 means authenticated but not entitled,
which no refresh fixes, and refreshing anyway rotated the user's refresh
token on every poll.

Also: floor expires_at to whole seconds, indent the file the way Kimi
Code does, derive the temp file name from the real basename, and split
out fetchUsageResponse, which is what puts fetchUsage back under the
complexity limit — the original passed neither lint nor the formatter.

* Keep CLI-rotated Kimi credentials during refresh

The Kimi CLI shares the credential file the quota fetcher writes. A refresh
read the file, merged its new tokens, and renamed over it, so a rotation that
landed while the refresh was in flight was replaced with stale tokens and the
CLI's session broke until credentials were recreated.

Thread the refresh token that was actually spent into the persistence step and
skip the write when the file no longer carries it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(kimi): normalize additional usage windows

* fix(kimi): preserve custom auth while parsing limit windows

* test(kimi): cover additional usage windows

* test(kimi): move usage-window tests into service.test.ts

Every other quota provider is tested in service.test.ts; kimi.test.ts
was the only provider with a standalone collocated test file.

* fix(kimi): resolve reasoning-effort options per model, not per catalog probe

deriveModelDefinitionsFromACP derived thinkingOptions once from whichever
model happened to be active in the one-off catalog probe session, then
copied that same array onto every model. Kimi's ACP adapter only reports
thinking levels for the currently selected model, so K2.7/K2.7-coding-fast
(boolean on/off) and K3 (off/low/medium/high) ended up with identical,
often-wrong options depending on which model the probe defaulted to.

fetchCatalog now reuses the single probe session to switch through each
candidate model and read back its real thought_level options, falling
back to the default options if an individual model's probe fails. Skips
entirely when there's one model or no thinking picker, so providers
without per-model effort levels pay no extra round trips.

* refactor(kimi): merge kimi-usage.ts back into kimi.ts

kimi.ts was the only quota provider split across two files; every
other provider (including claude.ts at 503 lines, larger than the
merged Kimi file) stays in one. Also fixes a nested-ternary lint
error surfaced by the merge.

* fix(kimi): re-validate credentials immediately before the rename

Greptile flagged that saveRefreshedCredentials read the credential file,
checked its refresh token against a stale snapshot, then did async
writeFile work before an unconditional rename — leaving a window where
the Kimi CLI could rotate the file after the check and have its newer
tokens clobbered by our stale merge. The refresh-token-only comparison
also missed a CLI access-token rotation that kept the same refresh token.

Re-read the file immediately before the rename and compare both tokens
against the snapshot the merge was built from, discarding the merge if
anything changed. Narrows the check-then-write gap to the smallest
window possible without cross-process file locking.

* refactor(acp): make per-model catalog probing an injectable resolver

Catalog discovery in ACPAgentClient.fetchCatalog ran sequential
setSessionConfigOption model switches for every multi-model ACP with a
thought_level picker, so a slow or nonconforming agent could stall any
provider's catalog probe under the shared 60s timeout.

Replace the built-in resolvePerModelThinkingOptions with an optional
ACPCatalogModelResolver injected through ACPAgentClientOptions (threaded
through GenericACPAgentClient like the existing transformer/parser deps).
The base client ships no resolver, so catalog discovery derives models
from the initial session response and never switches models. Export the
config-option helpers a resolver needs.

The base regression test pins that a plain ACPAgentClient with multiple
models and a thinking picker never calls setSessionConfigOption.

* feat(kimi): resolve per-model thinking options via KimiACPAgentClient

Kimi exposes different thinking-effort levels per model but only reports
the currently selected model's levels, so the catalog probe must switch
through each model to read them back. Move that logic off the shared ACP
adapter and onto a Kimi-specific client.

Add resolveKimiCatalogModels (the sequential setSessionConfigOption probe,
fallback, and warning) and KimiACPAgentClient, which injects it via the
new catalogModelResolver dependency. Select it for providerId "kimi" in
the registry alongside cursor/kiro/traecli, so only Kimi pays the extra
round trips; every other ACP provider skips model switching entirely.

Tests cover the four probe behaviors on KimiACPAgentClient and the
registry dispatch to it.

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 21:09:26 +02:00
Jason@HNDandGitHub f0d7eeb98c fix(quota): restore Grok Settings usage for current CLI auth/billing (#2353)
* fix(quota): restore Grok Settings usage for current CLI auth/billing

Grok CLI no longer stores a top-level access_token or usage.creditUsage.
Read nested auth key tokens and config.used.val so Settings → Usage
shows monthly credits again. Keep legacy shapes and env tokens working.

Fixes #2352

* fix(quota): make Grok auth-file tests work on Windows

Inject homeDir into GrokQuotaProvider like Kimi so nested
~/.grok/auth.json tests do not depend on os.homedir() which
ignores $HOME on Windows (USERPROFILE).
2026-07-28 18:49:21 +08:00
99440201bd fix(server): show Claude model-scoped weekly usage limits (#2303)
The Claude usage card showed only Session and Weekly. Anthropic moved
model-scoped weekly limits out of the top-level `seven_day_<model>` keys
into a `limits[]` array, and `seven_day_opus` / `seven_day_omelette` now
return null, so the scoped bar silently disappeared. Nothing errored, so
the card looked healthy while under-reporting.

Scoped limits are now read from `limits[]`, restoring a `Weekly · Fable`
bar. Session and all-models weekly keep coming from the top-level keys,
which is also where the Claude CLI reads them from.

A response mid-migration can describe one limit twice, as a legacy key
and as a `limits[]` entry, so both shapes normalize into a single
`ScopedLimit` and one predicate decides whether two are the same limit:
same dimension, same id when both carry one, else same normalized name.
That is the only comparison in the file. Comparing display labels would
conflate a surface with a model of the same name, and collapse ids that
differ only by punctuation.

The `limits[]` entry supplies identity, since that is the shape the API
is migrating towards. Its `percent` and `resets_at` are nullable, so
each field falls back to the legacy twin rather than discarding a value
the response did carry.

Legacy scoped windows adopt the same id scheme, so `weekly_opus` becomes
`weekly_model_opus`. Window ids are internal React keys, not
user-facing, and unifying them is what lets a limit keep one id
whichever shape of the response carried it.

Two supporting changes:

- `limits[]` entries are validated one at a time. The response goes
  through a single parse, so one malformed entry would otherwise throw
  and take the windows that already parsed with it.
- The provider stores the logger it was already handed and warns when a
  response parses but yields no windows, when a scope resolves to no
  name, and when an entry is unparseable. This failure was silent at
  every level, which is why it went unnoticed. Warn rather than debug,
  because file logging defaults to info.

Scoped windows render even at 0% and inactive, so a bar does not appear
and disappear between refreshes.

Closes #2302

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-24 21:16:05 +08:00
de69b2a2af fix(server): tone usage bars by how full they are (#2322)
A usage bar in Settings -> Usage stayed green all the way to 100%, so
the one moment the meter matters was the moment it said nothing.

The client already computes this. `window-bar.tsx` reads
`window.tone ?? deriveTone(usedPct)`, and `deriveTone` escalates past
70% and 90%. But `usage.ts` only assigns `window.tone` when a provider
sends one, so any provider that sends a tone opts out of the thresholds
entirely. Claude and Kimi hardcoded `tone: "ok"`, and Codex escalated to
`warning` past 70% but could never reach `danger`.

Providers now derive tone from the percentage they already have, through
one shared `toneFromUsedPct` whose thresholds match the client's.
Claude, Codex and Kimi windows all escalate correctly.

Balances had the same gap by a different route: `balance-bar.tsx` has no
`deriveTone` fallback, and `balanceToneFromRemaining` only escalates
once a balance is completely spent, so a credits bar at 99% was green.
Cursor and Grok know their limit, so they now tone by used percentage.
Codex credits report only a remaining balance with no limit, so they
keep the remaining-based tone, documented as the no-limit case.

MiniMax is unchanged: it maps a status its own API supplies rather than
hardcoding a tone.

Verified against the live Anthropic API with a session window at 83%,
which now reports `warning` where it previously reported `ok`.

Closes #2320

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 03:30:44 +08:00
Mohamed BoudraandGitHub 2263469342 fix(server): show Claude usage when a quota window has no scheduled reset (#1855)
The Anthropic usage API returns resets_at: null when a window has no
active session. The schema only allowed string/undefined, so the parse
threw and the whole Claude provider surfaced as an error in the quota
panel. The consuming code already handled null; only the schema was
stricter than the API.
2026-07-02 08:30:36 +02:00
Matteo Pietro DazziandGitHub 862154541a feat: add MiniMax quota fetcher and brand icon (#1662)
* feat(server): add MiniMax quota fetcher

Mirror the live multi-provider quota panel (#1278) for MiniMax. Resolves the
bearer token from $MINIMAX_API_KEY or the MiniMax CLI config at
~/.mmx/config.json (api_key, oauth.access_token) and credentials at
~/.mmx/credentials.json, then queries the /v1/token_plan/remains endpoint on
the configured region (global by default, cn when region='cn'). The response
is normalized into ProviderUsage windows per model_remains entry, surfacing
the interval and weekly limits with their reset times and a danger tone when
the server reports status=2.

* feat(app): add MiniMax brand icon for usage page

The quota panel resolves the icon via resolveProviderIconName, which falls
back to a generic Bot for any provider not in BUILTIN_PROVIDER_ICON_NAMES.
Register 'minimax' as a built-in icon and vendor the official 24×24 brand
mark (single Path, currentColor) as MiniMaxIcon so the usage card stops
showing the generic robot.

* refactor(server): flatten nested ternaries in MiniMax quota fetcher

Greptile review on #1662 called out two nested ternaries in toIntervalWindow
and toWeeklyWindow. Extract a toneForStatus helper that uses an explicit
if/else chain so the same status-to-tone mapping is shared between the
interval and weekly windows, and add the missing trailing newline.

* fix: format
2026-06-26 14:14:29 +08:00
Mohamed BoudraandGitHub c2d7796b78 Isolate Kimi usage home lookup in tests (#1636) 2026-06-20 22:54:41 +08:00
Mohamed Boudra 6a3856b639 Fix Kimi usage credential lookup 2026-06-20 19:10:40 +07:00
acea7f3d24 feat: live multi-provider quota panel (#1278)
* feat: live provider quota panel (Claude + Codex)

Adds Claude and Codex plan usage to the context window percentage circle tooltip, querying their respective APIs directly using existing CLI auth tokens.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: address review feedback - restore quota trigger, guard NaN date

- Re-add triggerFetch() on agent idle/error in agent status subscription
- Guard formatResetsAtLabel against NaN from malformed API date strings

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: resolve composer conflict and stub subscribe in websocket tests

* feat(quota): add cursor, copilot, zai, grok, and kimi quota UI and improve type safety

* feat(quota): make Claude and Codex pluggable, and map additional quota/credits metrics

* security(quota): fix shell injection vulnerability by migrating exec to execFile

* revert: restore original scripts/dev.ps1 and packages/desktop/scripts/dev.ps1

* Fix i18n resource test expectation

* Fix quota tooltip empty-provider state

* Preserve zero values in Grok quota

* Fix empty quota fetch state

* Ignore quota frames in relay reconnect tests

* Persist refreshed Codex tokens to source auth file

* fix(quota): read Claude OAuth credentials from the macOS login Keychain

On macOS, Claude Code stores its OAuth credential in the login Keychain
(generic-password item, service "Claude Code-credentials"), not in
~/.claude/.credentials.json — that file usually does not exist there, so the
Claude provider's existsSync check failed and macOS users silently got no
Claude quota. Read it via the macOS `security` CLI (its ACL only trusts
/usr/bin/security to decrypt; a native Keychain read would prompt), and stay
read-only there since Claude Code owns the refresh/persist. Linux and Windows
keep using ~/.claude/.credentials.json unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Fix quota keychain timeout and local host reconciliation

* Fix terminal notification test quota stub

* Ignore quota frames in terminal notification tests

* fix(quota): bypass Claude token refresh on macOS Keychain-backed logins

* fix(dev): update local dev daemon port to 6768 on Windows scripts/dev.ps1

* fix(dev): bypass Unix dev-daemon.sh on Windows in dev.ps1

* fix(client): handle and dispatch provider_quota event in DaemonClient

* fix(desktop): fix PowerShell quote stripping in desktop dev.ps1 config seeding

* fix(desktop): execute config update via temp file to avoid PowerShell quoting bugs

* fix(desktop): fix concurrently command invocation on Windows in dev.ps1

* fix(desktop): resolve path escaping and environment setting syntax errors in dev.ps1 for Windows

* Add on-demand provider usage views

* Fix Cursor usage billing dates

* Move provider usage renderer coverage to e2e

* Use provider manifest for quota fetchers

* Add provider usage fetch timeouts

* Reshape provider usage fetchers

---------

Co-authored-by: ABorakati <ABorakati@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
Co-authored-by: lumingjun <lumingjun@bytedance.com>
2026-06-20 12:19:50 +08:00