You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
* Cursor models survive verification: last-known-good cache + passive warm
A verified Cursor API key produced zero models on every surface because the
dynamic model caches were write-once-wipe-often:
- invalidateProviderReadinessCaches() (which runs on storeApiKey, on every
key verification, and on every forced ai.getStatus) blanked the cursor/
droid model rows the verification probe had just warmed.
- The positive cache only lived 120s, and the passive status path used
"cached-only" discovery, so availableModelIds lost all cursor ids two
minutes after any active probe — desktop gating, iOS, and the TUI then saw
an empty provider.
Fixes:
- cursorModelsDiscovery: serve last-known-good rows for up to 6h with
stale-while-revalidate (background warm past the 120s freshness window).
Auth failures and SDK resolution failures still blank the cache (dead key /
unusable runtime must not advertise phantom models); transient failures no
longer do. Generic readiness invalidation now ages caches via
markCursorModelCachesStale() instead of dropping them; a cursor key change
still does a full clear.
- aiIntegrationService: passive cursor discovery is "cached-or-fallback" so
a cold cache warms in the background instead of never populating.
- droidModelsDiscovery: same stale-while-revalidate + stale-mark treatment.
- listCursorModelsFromCli: detect the CLI's "No models available for this
account" answer (a stale CLI login session shadows CURSOR_API_KEY) and
surface a provider blocker with the cursor-agent logout remediation.
- iOS SyncService: chat.models for cursor/droid passes activateRuntime so a
fresh key surfaces models on first fetch (mirrors the TUI).
Verified on a lane-built isolated runtime: store key -> verify -> 40 models
passively, still present >5min later and across a forced status refresh, and
a composer-2.5 ADE chat round-trip ("pong") through the Cursor SDK worker.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Audit fixes: backoff for background model-cache warms
The stale-while-revalidate change could spawn a discovery probe on every
passive call when there was nothing to serve: an unauthenticated droid got an
SDK session per status read, a broken/missing cursor CLI got re-spawned (4
probes, 12s timeouts) per call, and a timed-out cursor SDK fetch retried on
every passive consumer because the warm gate only counted auth/unavailable
failures.
- warmCursorModelsFromCli / warmDroidModels: at most one background attempt
per freshness window (reset on key change, full clear, or stale-mark so a
forced refresh can retry immediately).
- warmDroidModels also skips when a fresh cached result exists, even an
empty one — droid legitimately caches empty rows for unauthed CLIs.
- hasRecentCursorSdkFailure now defers warms after timeout failures too; it
only gates background warms, active probes always run.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Surface-scoped cursor model discovery (sdk vs cli)
Every cursor refresh used to probe BOTH discovery sources and wait on the
slower one: the SDK list returns in ~300ms but the cursor-agent CLI is a
process spawn with multi-second timeouts. Chat surfaces only run models
through the SDK, and Work-tab CLI lane drafts only through the CLI, so each
was paying for a probe it didn't need — worst on first discovery right after
key entry, when nothing is cached and the picker waits on the CLI before any
cursor rows appear.
- New optional cursorSource ("sdk" | "cli" | "all") on AgentChatModelsArgs
and AgentChatModelCatalogArgs. The cursor branch of loadAvailableModels
probes only the requested source synchronously; the other source serves
last-known-good rows and revalidates via the background warm paths.
- Catalog freshness is flavor-aware on both sides (brain modelCatalog
staleness + renderer runtimeCatalogCache): an SDK-only refresh cannot
satisfy a CLI-surface staleness check, so the work picker still forces its
own CLI probe when cli-capable rows are missing.
- Desktop ModelPicker derives the source from cursorAvailabilityMode
(chat→sdk, cli→cli, all→both); TUI and iOS chat pickers pass "sdk".
Default stays "all", so older clients/brains keep today's behavior in both
mix directions.
- Request dedupe keys (models, catalog, renderer caches) include the source.
Verified on an isolated lane-built runtime: cold sdk-flavored active probe
returns 19 SDK rows without waiting on the CLI; the background CLI warm
fills the merged 40-row view seconds later; legacy no-source calls still
probe both.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* ship: tests, docs, and TUI cursorSource parity for the cursor model fix
automate + finalize pass over the cursor/droid model-discovery lane:
- Tests: stale-while-revalidate + transient-vs-auth cache behavior and the
CLI "no models available" detection (cursorModelsDiscovery); stale-mark +
single-warm backoff (droidModelsDiscovery); new flavor-aware staleness
coverage (runtimeCatalogCache). No suite bloat; touched files were clean.
- TUI parity: app.tsx model-catalog refresh now passes cursorSource:"sdk"
for cursor (both refresh-stale and the force follow-up), matching
getAvailableModels / desktop ModelPicker / iOS so the TUI catalog probe
stays off the host cursor-agent CLI spawn. Updated the stale adeApi test.
- Docs: chat README + agent-routing + remote-commands reflect the cache
resilience and the cursorSource RPC contract.
- Tightened a misleading comment and a test helper type (non-optional
cursorAvailability).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* ship: iteration 1 — address Greptile P2s (droid warm flag, explicit cache fallback)
- clearDroidCliModelsCache now resets warmInFlight, matching
clearCursorCliModelsCache's cliWarmInFlight reset — a clear during an
in-flight warm no longer blocks the next warm until the old promise settles.
- discoverCursorSdkModelDescriptors makes the cached-mode branch explicit
(result == null || failureKind !== "auth"); behavior-preserving, clarifies
that a null result is the cached path, not an auth check.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* ship: iteration 2 — authoritative empty Cursor probe no longer serves stale rows
Codex P2: an active (mode:"probe") SDK fetch that SUCCEEDS with an empty
model list (failureKind == null) was falling back to the 6h last-known-good
cache, so a forced refresh kept advertising models the provider had just
reported as gone. Limit the cache fallback to the cached modes (result ==
null) and genuine transient failures; a successful-but-empty probe and an
auth failure both reflect their result verbatim. Adds a regression test.
Supersedes the cosmetic iter-1 readability tweak on this line.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* ship: iteration 3 — drop stale SDK cache on an authoritative empty probe
Codex P2 follow-up: iter-2 stopped the active probe from serving stale rows
on an authoritative empty result, but left the sdkCached entry in place — so
the next passive cached-or-fallback read (status/mobile/TUI) resurrected the
old models for up to the 6h positive TTL. fetchCursorModelsFromSdk now clears
the matching SDK cache when a fetch returns empty without throwing (both SDK
and official API reported zero models; timeouts/auth/transient throw and
never reach this path). Extends the regression test to assert the passive
read is also empty.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* ship: iteration 4 — scope returned Cursor models to the requested source
Codex P2: cursorSource controlled which source was probed but not which rows
were returned, so a "sdk" surface (TUI/mobile chat) still received CLI-only
cached rows in the merged list — and surfaces that don't filter client-side
could offer a model that throws in assertCursorChatModelCanUseSdk on select
(symmetric for "cli"). getAvailableModels now filters the merged descriptors
to the requested source; the skipped source still annotates availability on
dual-capable rows, only rows exclusive to the skipped source are dropped.
"all" returns the union (unchanged). Extends the CLI-models test to assert an
sdk-scoped request drops CLI-only rows.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* ship: iteration 5 — track cursor catalog freshness per discovery source
Codex P2: catalog freshness was keyed globally per provider, so an SDK-scoped
cursor refresh marked "cursor" fresh for the whole TTL. Because dual-capable
rows keep cursorAvailability.cli=true, a later CLI-scoped request saw both
"catalog contains cli rows" and "globally fresh" and skipped its force CLI
probe, missing CLI-only model changes / CLI auth blockers for up to 30 min
(symmetric for the SDK side after a CLI refresh).
Both the brain (agentChatService model-catalog staleness) and the renderer's
client-side short-circuit (runtimeCatalogCache) now track cursor freshness per
source: a scoped refresh marks only the probed source; an unscoped/"all"
refresh marks both; an "all" staleness check requires both fresh. shouldMark
is source-aware so a source isn't marked fresh without rows it can run.
Updated two tests that encoded the old global-timestamp behavior and added
coverage for the dual-capable-rows + sdk-scoped-refresh case.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
0 commit comments