-
Notifications
You must be signed in to change notification settings - Fork 0
fix(cursor): isolate live roster and Max Mode evidence by account #465
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
06ec553
116c2ac
07b48da
bcdf559
b0900e5
3970601
bba6322
a7eaff1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,6 +3,7 @@ import { | |
| normalizeCursorClaudeId, | ||
| type NormalizedCursorClaudeId, | ||
| } from "./claude-id"; | ||
| import { createHash } from "node:crypto"; | ||
|
|
||
| /** | ||
| * Cursor umbrella catalog — the single source of truth for cursor model | ||
|
|
@@ -588,7 +589,7 @@ export function resolveCursorSelection( | |
| pickedId: string, | ||
| reasoning: string | undefined, | ||
| liveMaxModeIds?: ReadonlySet<string>, | ||
| options: { fast?: boolean } = {}, | ||
| options: { fast?: boolean; liveRosterScope?: string } = {}, | ||
| ): CursorResolvedSelection { | ||
| const parsed = parseCursorVariantId(pickedId); | ||
| if (!parsed.known) { | ||
|
|
@@ -606,7 +607,10 @@ export function resolveCursorSelection( | |
| const requested = parsed.level ?? reasoning; | ||
| const effort = cursorVariantEffort(spec, requested); | ||
| const requestedClaude = normalizeCursorClaudeId(pickedId); | ||
| const claudeIdentity = liveCursorClaudeWireIdentities.get(parsed.baseId) | ||
| const scopedClaudeIdentities = options.liveRosterScope | ||
| ? liveCursorClaudeWireIdentitiesByScope.get(options.liveRosterScope) | ||
| : liveCursorClaudeWireIdentities; | ||
| const claudeIdentity = scopedClaudeIdentities?.get(parsed.baseId) | ||
| ?? (requestedClaude | ||
| ? { sourceBaseId: requestedClaude.sourceBaseId, spelling: requestedClaude.spelling } | ||
| : undefined); | ||
|
|
@@ -615,7 +619,9 @@ export function resolveCursorSelection( | |
| ? `${capability.wirePrefix}${canonicalId}` | ||
| : canonicalId; | ||
| const ultraRequested = parsed.ultra || reasoning?.toLowerCase() === "ultra"; | ||
| const evidence = liveMaxModeIds ?? liveCursorMaxModeBases; | ||
| const evidence = liveMaxModeIds | ||
| ?? (options.liveRosterScope ? liveCursorMaxModeBasesByScope.get(options.liveRosterScope) : undefined) | ||
| ?? liveCursorMaxModeBases; | ||
| const maxModeArmed = capability.maxModeVerified === true || evidence.has(parsed.baseId); | ||
| return { wireId, canonicalId, maxMode: ultraRequested && maxModeArmed, known: true }; | ||
| } | ||
|
|
@@ -628,15 +634,36 @@ export function resolveCursorSelection( | |
| */ | ||
| let liveCursorMaxModeBases: ReadonlySet<string> = new Set(); | ||
| let liveCursorClaudeWireIdentities: ReadonlyMap<string, CursorLiveClaudeWireIdentity> = new Map(); | ||
| const liveCursorMaxModeBasesByScope = new Map<string, ReadonlySet<string>>(); | ||
| const liveCursorClaudeWireIdentitiesByScope = new Map<string, ReadonlyMap<string, CursorLiveClaudeWireIdentity>>(); | ||
| const liveCursorRosterScopesByProvider = new Map<string, Set<string>>(); | ||
|
|
||
| /** Non-secret key binding live roster evidence to one upstream destination and credential. */ | ||
| export function cursorLiveRosterScope(baseUrl: string | undefined, credential: string): string { | ||
| const destination = (baseUrl?.trim().replace(/\/+$/, "") || "https://api2.cursor.sh"); | ||
| return createHash("sha256") | ||
| .update("ocx:cursor:live-roster\0") | ||
| .update(destination) | ||
| .update("\0") | ||
| .update(credential) | ||
| .digest("hex"); | ||
| } | ||
|
|
||
| export function recordLiveCursorClaudeModels(liveIds: readonly string[]): void { | ||
| export function recordLiveCursorClaudeModels(liveIds: readonly string[], scope?: { provider: string; key: string }): void { | ||
| const next = new Map<string, CursorLiveClaudeWireIdentity>(); | ||
| for (const rawId of liveIds) { | ||
| const n = normalizeCursorClaudeId(rawId.startsWith("cursor-") ? rawId.slice(7) : rawId); | ||
| if (!n || !CURSOR_CAPABILITIES[n.canonicalBaseId]) continue; | ||
| if (!next.has(n.canonicalBaseId)) next.set(n.canonicalBaseId, { sourceBaseId: n.sourceBaseId, spelling: n.spelling }); | ||
| } | ||
| liveCursorClaudeWireIdentities = next; | ||
| if (scope) { | ||
| liveCursorClaudeWireIdentitiesByScope.set(scope.key, next); | ||
| const scopes = liveCursorRosterScopesByProvider.get(scope.provider) ?? new Set<string>(); | ||
| scopes.add(scope.key); | ||
| liveCursorRosterScopesByProvider.set(scope.provider, scopes); | ||
| } else { | ||
| liveCursorClaudeWireIdentities = next; | ||
| } | ||
| } | ||
|
|
||
| export function liveCursorClaudeWireIdentitiesForTests(): ReadonlyMap<string, CursorLiveClaudeWireIdentity> { | ||
|
|
@@ -645,15 +672,32 @@ export function liveCursorClaudeWireIdentitiesForTests(): ReadonlyMap<string, Cu | |
|
|
||
| export function resetLiveCursorClaudeWireIdentitiesForTests(): void { | ||
| liveCursorClaudeWireIdentities = new Map(); | ||
| liveCursorClaudeWireIdentitiesByScope.clear(); | ||
| liveCursorRosterScopesByProvider.clear(); | ||
| } | ||
|
|
||
| export function recordLiveCursorMaxModeModels(liveIds: readonly string[]): void { | ||
| export function recordLiveCursorMaxModeModels(liveIds: readonly string[], scope?: { provider: string; key: string }): void { | ||
| const bases = new Set<string>(); | ||
| for (const id of liveIds) { | ||
| const parsed = parseCursorVariantId(id); | ||
| if (parsed.known) bases.add(parsed.baseId); | ||
| } | ||
| liveCursorMaxModeBases = bases; | ||
| if (scope) liveCursorMaxModeBasesByScope.set(scope.key, bases); | ||
| else liveCursorMaxModeBases = bases; | ||
| } | ||
|
|
||
| export function clearLiveCursorRosterState(provider?: string): void { | ||
| if (!provider) { | ||
| liveCursorClaudeWireIdentitiesByScope.clear(); | ||
| liveCursorMaxModeBasesByScope.clear(); | ||
| liveCursorRosterScopesByProvider.clear(); | ||
| return; | ||
| } | ||
| for (const scope of liveCursorRosterScopesByProvider.get(provider) ?? []) { | ||
| liveCursorClaudeWireIdentitiesByScope.delete(scope); | ||
| liveCursorMaxModeBasesByScope.delete(scope); | ||
|
Comment on lines
+697
to
+698
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
If two configured Cursor provider names use the same destination and credential, both register the same scope key in their provider sets. Clearing either provider currently deletes that shared key unconditionally, so the other provider immediately loses its live wire spelling and Max-Mode evidence even though its own model cache and credential are unchanged. Track scope owners/reference counts, or include the provider identity in the stored key, so clearing one provider cannot invalidate another provider's state. AGENTS.md reference: src/AGENTS.md:L20-L20 Useful? React with 👍 / 👎. |
||
| } | ||
| liveCursorRosterScopesByProvider.delete(provider); | ||
| } | ||
|
|
||
| export function liveCursorMaxModeBasesForTests(): ReadonlySet<string> { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When Cursor OAuth refreshes its roughly hourly access token, hashing the raw token creates a new roster scope, but the model cache remains keyed only by provider name. A subsequent catalog gather can therefore return the still-fresh entry at
provider-fetch.ts:1590-1595without executing the new recording calls atprovider-fetch.ts:1625-1629; requests using the refreshed token then find no scoped Claude spelling or Max-Mode evidence and may emit a rejected canonical model ID until another live discovery occurs. Invalidate the provider cache on token-generation changes, republish cached roster evidence into the new scope, or derive the scope from stable credential-lineage identity.AGENTS.md reference: src/AGENTS.md:L20-L20
Useful? React with 👍 / 👎.