Skip to content

feat(cursor): wire /models /agents /cancel to cursor-agent configOptions#492

Open
brettchien wants to merge 3 commits intoopenabdev:mainfrom
brettchien:feat/cursor-config-options-discord
Open

feat(cursor): wire /models /agents /cancel to cursor-agent configOptions#492
brettchien wants to merge 3 commits intoopenabdev:mainfrom
brettchien:feat/cursor-config-options-discord

Conversation

@brettchien
Copy link
Copy Markdown
Contributor

@brettchien brettchien commented Apr 20, 2026

Closes #489.

Discussion: https://discord.com/channels/1491295327620169908/1491365150664560881/1495774750226645133

Upstream cursor-agent ACP compliance gaps tracked separately in #493.

Summary

  • Discord slash commands /models, /agents, /cancel now work end-to-end against cursor-agent acp.
  • Each handler: defer_ephemeralpool.get_or_create(thread_key) → build select menu from the session's cached configOptionsedit_response. No more "start a conversation first" dead-end, and the 3s Discord interaction deadline is honoured even on cold-start sessions.
  • StringSelectMenu capped at Discord's 25-option limit with a plain take(25) — no vendor-specific pinning. cursor-agent returns 26 models on a Pro account; the last one is dropped.
  • agent / mode category treated as aliases so the same /agents handler works against both kiro-cli (category=agent) and cursor-agent (category=mode). Per ACP spec the reserved value is "mode".
  • Trust-the-protocol: OpenAB does not probe or auto-rewrite after set_config_option. If cursor-agent accepts an unroutable model with setOk:true and then soft-rejects on the next prompt, that soft-reject is passed through to the channel. Upstream bug tracked in tracking: cursor-agent ACP compliance issues — upstream bugs affecting /models #493.

Changes

  • src/discord.rs — new handle_config_command (shared by /models and /agents), handle_config_select, handle_cancel_command; build_config_select with ACP-agnostic 25-option cap; category alias table.
  • config.toml.example — documents the --model auto startup gotcha in the cursor-agent section.
  • docs/cursor.md — Model Selection section with startup --model auto table and passthrough semantics; new Known Limitations subsection listing three upstream cursor-agent ACP compliance gaps (all linked to tracking: cursor-agent ACP compliance issues — upstream bugs affecting /models #493).

No changes in src/acp/connection.rs, src/acp/pool.rs, or src/config.rs — ACP layer untouched.

Test plan

  • cargo check clean
  • cargo test --release — 74/74 pass
  • Manual E2E against openab-cursor pod running cursor-agent 2026.04.15-dccdccd:
    • /models lists 25 models (truncated from 26) with the current value highlighted
    • Switching to a routable model (composer-2) → ✅ Switched to composer-2, next prompt routes there
    • /agents lists Agent / Plan / Ask (regression from v0.7.8 where it returned "No agent options available" against cursor-agent)
    • /cancel against an in-flight prompt → session/cancelstopReason: cancelled (~15ms roundtrip, verified in pod logs)
    • Soft-reject passthrough observed — cursor-agent streamed "\n\nCheck your settings to continue" for an unroutable model and OpenAB forwarded it unchanged (new variant beyond the AI Model Not Found / Model name is not valid family, noted for tracking: cursor-agent ACP compliance issues — upstream bugs affecting /models #493)

Revision history

  • v1: included a proactive per-model probe that sent a silent ping, detected cursor-agent's plain-text soft-reject, and auto-fell-back to default[] on hit.
  • v2 (per maintainer review): probe + fallback removed. That policy belongs upstream, not in OpenAB; the trust-the-protocol approach keeps set_config_option semantics intact and surfaces the upstream bug directly in-channel.
  • v3: truncation pinning (current + "default[]") also removed — kept as a plain in-order take(25) so build_config_select has no vendor literals.

🤖 Generated with Claude Code

Closes openabdev#489.

Key changes:
- discord slash handlers defer_ephemeral first, then get_or_create the
  pooled ACP session, then edit_response with the select menu built from
  the session's cached configOptions.
- /models runs a silent "ping" probe after set_config_option to catch
  cursor-agent's soft-reject (`AI Model Not Found` / `Model name is
  not valid`) and falls back to `default[]` (Auto). Probe timeout is
  configurable via `[agent].probe_timeout_secs` (default 15s); on
  timeout the probe is cancelled and the same fallback runs.
- Cap StringSelectMenu at Discord's 25-option limit, preserving the
  current value and `default[]` when truncating (cursor-agent returns
  26 models on a Pro account).
- Treat `agent` and `mode` categories as aliases so the same `/agents`
  command works against both kiro-cli (category=agent) and cursor-agent
  (category=mode).

Adds `ProbeResult` + `probe_prompt` on AcpConnection and `probe_session`
+ `probe_timeout_secs` on SessionPool. Docs updated in
`docs/cursor.md` with the /models behaviour + probe semantics.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@brettchien brettchien requested a review from thepagent as a code owner April 20, 2026 13:08
@github-actions github-actions bot added closing-soon PR missing Discord Discussion URL — will auto-close in 3 days and removed closing-soon PR missing Discord Discussion URL — will auto-close in 3 days labels Apr 20, 2026
brettchien and others added 2 commits April 20, 2026 23:14
Strip the proactive probe + auto-fallback-to-Auto workaround for
cursor-agent's soft-reject behaviour. Per project policy we trust the
ACP protocol: `setOk:true` means the switch succeeded. The upstream
cursor-agent bug where `set_config_option` accepts unroutable models is
tracked in openabdev#493 and reported via the Cursor forum.

Retained:
- /models, /agents, /cancel wired to ACP `configOptions`
- `defer_ephemeral` + `edit_response` pattern to avoid Discord 3s deadline
- Pre-flight `get_or_create` so the command works before first @mention
- 25-option cap on Discord select menus (cursor-agent returns 26 models
  on Pro accounts); current/default pinned when truncating
- `agent`/`mode` category alias for kiro-cli compatibility

Removed:
- `AcpConnection::probe_prompt` and `ProbeResult` (connection.rs)
- `SessionPool::probe_session` and `probe_timeout_secs` (pool.rs)
- `AgentConfig.probe_timeout_secs` field (config.rs)
- `Handler::fallback_to_auto` and all /models probe logic (discord.rs)
- `probe_timeout_secs` example in config.toml.example
- Proactive-probe section in docs/cursor.md, replaced with
  "Soft rejects from unroutable models" passthrough explanation

docs/cursor.md now includes a Known Limitations subsection listing the
three upstream ACP compliance gaps (setOk for unroutable models,
unfiltered model list, process-local loadSession), all linked to openabdev#493.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Drop the current-value + `default[]` pinning logic inside
`build_config_select`. The pin was motivated by preserving Auto when
truncating from 26 to 25 cursor-agent models, but `"default[]"` is a
cursor-specific literal and the extra complexity buys little — Auto is
already first in cursor-agent's model list, so a straight in-order
`take(25)` keeps it. When `current_value` sits at position >25 the
dropdown won't show it as the default, but the live session retains
the selection on cursor's side; user can switch again by rerunning
`/models`.

This keeps `build_config_select` ACP-agnostic: no vendor literals, no
assumptions about which option is the "fallback".

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@shaun-agent
Copy link
Copy Markdown
Contributor

OpenAB PR Screening

This is a manually uploaded screening report from the OpenAB project-screening flow for context collection and reviewer handoff.

Screening report

Intent

pr #492 is trying to finish the cursor-specific user experience around discord slash commands without turning openab into a cursor workaround layer.

the concrete problem is twofold. first, cursor-backed channels currently miss the intended /models, /agents, and /cancel control path because the handlers assume a session already exists and can run out of time against discord's 3 second interaction deadline. second, cursor-agent exposes broken ACP behavior around model selection, so the pr also tightens the docs to explain what openab will and will not do when the backend lies.

assumption: deploy-time cursor support already exists in-tree through the existing cursor image, helm values, and docs, so this pr is intentionally about discord interaction flow and policy boundary, not first-time packaging.

Feat

this is a feature and interaction-hardening pass centered in src/discord.rs, with supporting docs and config example updates.

in plain terms, it makes cursor sessions behave like a real first-class ACP backend for discord controls:

  • slash commands defer immediately so slow or cold session startup does not blow the interaction deadline
  • the handler creates or restores the pooled ACP session before reading cached configOptions
  • /agents works for both category=agent and category=mode
  • /models respects discord's 25-option select limit with a plain in-order truncation
  • /cancel becomes usable against an active cursor session through the same deferred-response pattern
  • docs now state that --model auto is load-bearing and that openab passes cursor soft rejects through instead of probing or auto-falling back

notably, the ACP transport, pool internals, and config parsing layer stay untouched. that is a good boundary.

Who It Serves

the primary beneficiary is discord users running openab against cursor-agent acp, especially users on a fresh channel session who previously hit the "start a conversation first" dead-end or a silent timeout.

secondarily it serves maintainers and deployers. the docs now make the startup model invariant and upstream cursor-agent defects explicit, which reduces false debugging loops where openab gets blamed for cursor's bad protocol behavior.

Rewritten Prompt

implement cursor-compatible discord config controls without adding cursor-specific heuristics to the ACP layer.

acceptance criteria:

  • /models, /agents, and /cancel must call defer_ephemeral before any potentially slow work
  • config commands must call pool.get_or_create(thread_key) before reading configOptions, so they work on cold-start sessions and after pool eviction
  • config select handling must re-run get_or_create before set_config_option
  • build_config_select must treat agent and mode as aliases and cap string select menus to 25 options in order
  • successful config changes must be acknowledged via edit_response
  • failures to create a session or set an option must surface a direct error in the interaction response
  • docs/cursor.md and config.toml.example must explain that --model auto is required for true auto routing
  • openab must not probe, string-match, or auto-fallback after set_config_option; upstream cursor-agent ACP defects remain documented in #493

validation:

  • manual e2e against a live cursor-agent deployment for /models, /agents, and /cancel
  • verify cold-start command use works before the first normal prompt
  • verify a pool-evicted session can still switch config after the menu interaction

if issue #489 still has deployer ergonomics left over, split them into a follow-up instead of growing this pr sideways.

Merge Pitch

this is worth merging because it closes a real product gap for cursor-backed deployments with relatively tight scope. users get the same slash-command control surface other ACP backends already expose, and the implementation does it in the right layer: discord handler and docs, not protocol-shaping hacks.

risk is moderate but localized. the code changes sit in interaction timing and pooled session bootstrapping, which can regress command UX if wrong, but they do not alter the ACP core. the likely reviewer concern is not the handler logic itself. it is whether this pr is claiming to close more than it actually does, because #489 originally mixed UI wiring, deploy config, and a probe-based workaround that the project has since rejected.

Best-Practice Comparison

OpenClaw: the relevant principle is boundary discipline. route work explicitly, surface failures honestly, and do not smuggle backend-specific recovery rules into the wrong layer. this pr matches that better than the earlier probe-and-fallback direction. durable job persistence and retry/backoff are not directly relevant here because this is an interactive discord control path, not a scheduled execution pipeline.

Hermes Agent: the relevant principle is making state transitions explicit before acting. the defer-first plus get_or_create flow is a small but meaningful version of that: establish session state before assuming cached config exists. file locking, atomic writes, and fresh-session scheduling are not relevant to this path.

net: the useful lesson from both reference systems is not their runtime architecture. it is their refusal to paper over undefined state with wishful logic. #492 is stronger after removing the proactive probe because it keeps openab's contract clean.

Implementation Options

  1. merge this pr as the narrow discord-and-docs slice, and split any remaining cursor deployer ergonomics into a separate follow-up.

  2. broaden this pr slightly to also add or normalize default cursor deployment artifacts if maintainers still think #489 requires a repo-native config entry point beyond the existing helm/docs path.

  3. reintroduce an openab-side probe, entitlement filter, or auto-fallback layer to hide cursor-agent's broken model semantics from users.

Comparison Table

Option Speed to ship Complexity Reliability Maintainability User impact Fit for OpenAB right now
1. merge as a narrow slice high low high high high very high
2. widen scope to include deploy artifacts medium medium medium-high medium high medium
3. add openab-side workaround logic low high medium low medium-high in the short term low

Recommendation

take option 1.

the pr is strongest when treated as a focused interaction-layer improvement plus documentation cleanup. it fixes a real user-facing gap, keeps the ACP boundary honest, and avoids merging policy-confused workaround code just because cursor-agent is sloppy. if maintainers still want a dedicated config/cursor.toml, compose profile, or similar deploy-time artifact, do that as a follow-up and do not use it to block the handler fix.

the one thing to watch in review is scope accounting: if #489 is meant to cover more than this pr actually ships, close only the part that is now done and leave the rest as explicit follow-up work.

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.

feat(cursor): wire up Cursor agent to /models, /agents, /cancel slash commands

2 participants