feat(cli): cache Cursor per-model config options from capability probes - #344
feat(cli): cache Cursor per-model config options from capability probes#344Tigatron wants to merge 3 commits into
Conversation
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
An ACP probe snapshot only describes the model that was current when the probe ran, while an agent such as cursor-agent publishes a distinct option set for every model. AcpCapabilityCacheEntry gains configOptionsByModel, the per-model catalog an explicit probe may store, where a model mapped to an empty list has no model-dependent options and a missing key means the catalog does not know that model. resolveAcpConfigOptionsForModel is the one composition rule for a model's options: the snapshot options no catalog entry owns plus the selected model's entry, falling back to the snapshot for an unknown model. model and mode options always come from the snapshot, so a catalog can neither shrink the model picker nor replace the permission modes. resolveAcpTargetModelId names the model a run config targets. Fast mode now also recognises a select whose values are exactly true and false, the shape cursor-agent uses for boolean parameters, and writes back the advertised representation instead of on/off. Implemented with cursor-grok-4.6-xhigh-fast subagents. Model: claude-fable-5.1 Co-authored-by: Cursor <cursoragent@cursor.com>
Registry Cursor now declares clientCapabilities._meta.parameterizedModelPicker at initialize, so probes and sessions see clean model ids plus per-model thinking, effort, context, and fast options instead of exploded variant strings whose in-session model switch silently fails. The gate is registry identity, never a same-named custom or builtin config. A session/new snapshot only describes the model current at probe time, so an explicit machine/acp-capabilities-refresh additionally calls the agent's cursor/list_available_models once and stores every model's options as AcpCapabilityCacheEntry.configOptionsByModel. That method is the only extension cursor-agent serves and performs no writes, unlike enumerating models through session/set_config_option, which rewrites the user's global Cursor config. Real sessions never fetch the catalog; their snapshot write keeps the stored catalog for the same sourceVersion, and the unchanged-entry comparison includes it so a refreshed catalog is committed. JSON-RPC -32601 means no catalog; a response that fails validation or lists a model twice, a timeout, or any other failure fails the probe with [ACP_CAPABILITIES_INCOMPLETE] so the settings test button can retry. Implemented with cursor-grok-4.6-xhigh-fast subagents. Model: claude-fable-5.1 Co-authored-by: Cursor <cursoragent@cursor.com>
Registry and custom agents carry the selected model in the model config option rather than modelId, and the applier switched it inside the option loop at its key position. Cursor validates thinking, effort, and fast against the model that is current when each option arrives, so options ordered before the model key were checked against the previous model and rejected. Apply the config-option model right after the explicit modelId path and skip its loop entry; the unstable_setSessionModel channel and its fallback are unchanged. Implemented with cursor-grok-4.6-xhigh-fast subagents. Model: claude-fable-5.1 Co-authored-by: Cursor <cursoragent@cursor.com>
2675f4f to
3f62760
Compare
|
Rebased onto The only textual conflict was in Re-verified on the rebased head: workspace |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3f627604d4
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| ? { _meta: { parameterizedModelPicker: true } } | ||
| : {}), |
There was a problem hiding this comment.
Invalidate pre-parameterized Cursor capability caches
When upgrading an installation with an existing registry Cursor capability row, this opt-in changes Cursor from exploded variant IDs to clean parameterized model IDs, but ACP_CAPABILITY_CACHE_VERSION remains 6 and the source version remains cursor@2026.08.31. The existing row therefore stays authoritative, so selectors and dispatch can send a cached legacy model ID that the newly initialized agent no longer advertises, causing the first post-upgrade turn to reject the requested model or run with the default until the user manually refreshes capabilities. Invalidate or migrate those prior Cursor cache entries when enabling this capability.
AGENTS.md reference: apps/cli/src/agent/AGENTS.md:L243-L249
Useful? React with 👍 / 👎.
| * with no model-dependent options maps to `[]`, while a missing key means the | ||
| * catalog does not know that model. Absent when the agent exposes no catalog. | ||
| */ | ||
| configOptionsByModel?: Record<string, AcpConfigOptionSummary[]>; |
There was a problem hiding this comment.
Accept the catalog in the refresh response schema
For a remote-machine Cursor refresh that returns this new field, executeAcpRefresh includes the complete capability in MachineAcpCapabilitiesRefreshResponse, but the nested AcpCapabilityCacheEntrySchema in packages/shared/src/message-schemas.ts is strict and does not declare configOptionsByModel. Consequently parseRpcSuccessResult rejects the otherwise successful response and returns null, which the desktop reports as a capability-refresh timeout; the local IPC path bypasses that parser, explaining why local end-to-end testing does not expose the failure. Add the field to the transport schema or omit it from the RPC response.
Useful? React with 👍 / 👎.
Related issue
Refs #343
Problem / pressure
Lody's registry Cursor agent runs in cursor-agent's legacy "variants" mode because the ACP client never declares
clientCapabilities._meta.parameterizedModelPicker: model ids arrive exploded, no per-model options are published, and an in-session model switch throughsession/set_config_optionsilently fails to match. Once the picker is enabled the second problem surfaces: asession/newsnapshot describes only the model current at probe time, while Cursor rebuilds thinking/effort/context/fast per model and rejects values the new model lacks, so nothing outside a live session can know another model's options. The run-config applier also sent per-model options before the model when the model lived inconfigOptionValues, which Cursor validates against the previous model.Summary
cliType: 'registry'andagentType: 'cursor') declaresparameterizedModelPickerat initialize;AgentClientgains a genericrequestExtMethodwith abort support.machine/acp-capabilities-refreshprobe calls Cursor's side-effect-freecursor/list_available_modelsonce aftersession/newand stores every model's non-model/mode options asAcpCapabilityCacheEntry.configOptionsByModel([]= a known model with no options; missing key = unknown model). JSON-RPC-32601means no catalog; a response that fails Zod validation or lists a model twice, a timeout, an abort, or any other failure fails the probe with[ACP_CAPABILITIES_INCOMPLETE]so the Settings Test action can retry. Real sessions never fetch it; their snapshot write preserves the stored catalog for the samesourceVersion, and the unchanged-entry comparison includes the field.@lody/sharedgains the one composition ruleresolveAcpConfigOptionsForModel(snapshot options no catalog entry owns + the selected model's entry;model/modealways from the snapshot; unknown model → snapshot),resolveAcpTargetModelId, and true/false toggle predicates; Fast mode now recognises a select whose values are exactlytrue/false.unstable_setSessionModelchannel and its fallback are unchanged.ACP_CAPABILITY_CACHE_VERSIONchange; consumers of the catalog (composer, MCP mapping) follow in separate PRs.Before / after
parameterizedModelPicker: exploded model ids, no per-model options, silent model-switch failures.session/set_config_optionmodel switches take effect.configOptionsByModelfrom an explicit probe, preserved across same-version session writes.Test plan
cd packages/shared && corepack pnpm exec tsgo --noEmit && corepack pnpm exec vitest run tests/acp-run-config.test.ts— 29 passed (composition rule, target-model resolution, toggle predicates).cd apps/cli && corepack pnpm exec tsgo --noEmit && corepack pnpm exec vitest run src/agent/cursor-acp.test.ts src/agent/acp-capabilities.test.ts src/agent/agent-client-initialize.test.ts src/lib/loro/machine-document-capabilities.test.ts src/session/acp-session-config-applier.test.ts tests/message-handler-fable-fast-mode.test.ts tests/session-execution-service.test.ts— all passed (catalog parsing,-32601, INCOMPLETE paths with fake timers, identity-gated initialize meta, same-version catalog preservation and change detection, applier ordering).corepack pnpm typecheck,corepack pnpm lint(0 errors),corepack pnpm format:check,corepack pnpm lint:i18n,check:code-collab-imports,check:platform-boundaries,check:public-boundary— passed.corepack pnpm test:ci— all packages pass except two pre-existingapps/cliworktree tests (session-manager.test.ts"rebuilds a prepared worktree whose directory disappeared before adoption",worktree-manager.create.test.ts"should rebuild a missing registered worktree") that fail identically on a cleanmainwith git 2.51.0; unrelated to this change.cursor-agent 2026.08.31protocol probe (no prompt sent):cursor/list_available_modelsreturned 37 unique models whose option shapes match the parser (allselect,thinking/fastvaluesfalse/true,contextcategorymodel_config, some models with no options); an unknown ext method returned-32601. The captured payload parses throughfetchCursorModelCataloginto 37 entries.LODY_DATA_DIR: add registry Cursor, refresh → Ready; a turn with Thinking on and effort high completes without a rejected-selection notice; the same session switched togpt-5.4runs a second turn cleanly. Skipped: Windows/Linux runs; a real-32601agent (covered by unit tests only).Context handoff
Instructions for reviewing agents
apps/cli/src/agent/cursor-acp.ts(validation and error mapping of the catalog response),MachineDocument.updateAcpCapabilitiesinapps/cli/src/lib/loro/doc.ts(same-sourceVersionpreservation and the change detector including the new field), andresolveAcpConfigOptionsForModelinpackages/shared/src/acp-run-config.ts(union rule and themodel/modeexclusion).ACP_CAPABILITY_CACHE_VERSIONunchanged because the field is additive.agent-client.ts↔cursor-acp.tsimport cycle is function-scoped only; the applier still logs (not warns) when a config-option model switch is rejected, unchanged from before.Authoring context
modelReasoningEffortsfor Grok) is sibling work this composes with.parameterizedModelPickerchanges Cursor's advertised model ids for new probes and sessions; the catalog is stored only from explicit probes and preserved across same-version session writes; enumeration throughsession/set_config_optionis deliberately forbidden because it rewrites the user's global Cursor config.