Skip to content

feat: add Cursor ACP provider with routing and reasoning effort fixes - #1873

Open
alecuba16 wants to merge 15 commits into
1jehuang:masterfrom
alecuba16:feature/cursor-acp
Open

alecuba16 wants to merge 15 commits into
1jehuang:masterfrom
alecuba16:feature/cursor-acp

Conversation

@alecuba16

@alecuba16 alecuba16 commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

Adds a Cursor ACP provider that talks to Cursor's agent client protocol, with model routing and reasoning effort plumbed through. Child processes are reaped on drop and shutdown so no zombies are left behind, and the fake-process tests serialize on shared catalog state to stay stable under parallel runs.

Closes #575.

@greptile-apps

greptile-apps Bot commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerView in GreptileConfidence Score: 0/5

[Medium impact] The PR reaches provider startup, model selection, server restart, and usage-refresh workflows.

This PR is not ready to merge because earlier routing, permission, cancellation, and shared-daemon failures remain.

Findings

  1. P1 Effort changes block prompts ▶
  2. P1 Restored sessions lose their provider ▶
  3. P1 Tools use the wrong project ▶
  4. P1 New clients stop other sessions ▶
  5. P1 Cursor ACP stays hidden ▶
  6. P1 Cursor ACP default is ignored ▶
  7. P1 Picker selects the wrong provider ▶
  8. P1 Security Automatic approval survives config changes ▶
  9. P1 Tests depend on execution order ▶
  10. P1 Provider command misses Cursor ACP ▶
  11. P1 Cancelled prompts can stay stuck ▶
  12. P1 Format check fails ▶
  13. P1 Gemini selection stays on Cursor ▶
  14. P1 Compaction uses the previous provider ▶
  15. P2 Permission tests ignore the answer ▶
  16. P2 Unicode can stop stderr capture ▶
  17. P2 Cursor sessions restart between turns ▶
  18. P2 Failed refresh erases model choices ▶
  19. P2 Updated instructions are ignored ▶
  20. P2 Previous provider stays on screen ▶
  21. P2 New skills go unannounced ▶
  22. P2 Current Cursor model goes unmarked ▶
  23. P2 Failed refresh looks successful ▶
Fix with agent prompt
### Issue 1
crates/jcode-provider-cursor-acp-runtime/src/lib.rs:1393-1401
`set_reasoning_effort` saves a rewritten model ID without checking Cursor's advertised catalog. With only `gpt-5.6-sol[context=272k,reasoning=low]` advertised, changing effort to `high` returns success, but the next prompt fails with a model-not-advertised error before reaching Cursor.

Validate the proposed variant before saving it, preserving the previous usable model when the requested effort is unsupported. This prompt-breaking behavior must be fixed before merging.

### Issue 2
crates/jcode-base/src/provider/selection.rs:undefined-556
This branch restores only the saved model name, discarding its Cursor ACP route. When OpenAI is active, restoring a saved Cursor ACP `gpt-*` model sends the model switch through OpenAI instead; the executed restore returned success while leaving ACP inactive. Without a saved route method, the generated `cursor-acp:` prefix also fails unless ACP is already active.

Preserve the saved provider identity and activate its runtime during restoration. Restoring conversations through the wrong provider must be fixed before merging.

### Issue 3
crates/jcode-provider-cursor-acp-runtime/src/lib.rs:undefined-1420
`with_command` captures the daemon's working directory once, and `fork` copies it without binding the provider to the session's project. Both the child process and `session/new` use that stored directory. In an executed daemon-A/session-B check, the fork launched its child in A, advertised A as the workspace, and read A's relative project file instead of B's.

Because Cursor executes tools itself, sessions in another project can read or modify the wrong workspace. Bind the runtime to the session's working directory before starting its child; this must be fixed before merging.

### Issue 4
src/cli/dispatch.rs:908-915
Opening another terminal with an explicit provider stops the shared daemon without asking. This condition never compares the running provider, so even requesting the same provider calls `stop_running_server`, which sends `SIGTERM` to the daemon's process group. The executed same-provider check terminated an isolated daemon and disconnected both existing session sockets.

Keep the daemon alive unless the user explicitly approves stopping it. Selecting a provider for one client must not silently interrupt other attached sessions or live headless/swarm work; this must be fixed before merging.

### Issue 5
crates/jcode-base/src/provider/startup.rs:206-209
With `--provider auto` and no Cursor ACP preference, startup leaves the ACP runtime empty even when its executable is installed and configured. Catalog refresh and picker routes require an existing runtime, and route selection does not initialize it. In the rendered application, `/model cursor` showed “No matching models for cursor,” and the configured ACP executable was never invoked.

Add discovery or a working lazy-start path so users can select Cursor ACP without restarting with `--provider cursor-acp`. The normal automatic-startup model selection flow must work before merging.

### Issue 6
crates/jcode-base/src/provider/startup.rs:333-340
Setting `default_provider = "cursor-acp"` creates the runtime but does not activate it. Configuration selection uses `parse_provider_hint()`, which does not recognize Cursor ACP, and startup initializes `active_external` to `None`.

With this default and no default model, the executed first completion went to the automatically selected Claude endpoint instead of ACP. Explicitly selecting ACP then successfully used the same runtime. Users therefore send prompts through another provider despite their saved choice, or cannot send them without that provider's credentials. Activate the configured external runtime before choosing a default model; this routing error must be fixed before merging.

### Issue 7
crates/jcode-base/src/provider/catalog_routes.rs:42-49
When Cursor ACP is active, the simplified picker labels every model returned by `available_models_display()` as an available Cursor ACP route. For `MultiProvider`, that list also contains other providers’ models. WSL Windows Terminal enables this simplified picker automatically.

Selecting a valid OpenAI or Anthropic model then calls `cursor_acp.set_model()` instead of switching providers. Executed selections of both kinds failed because Cursor ACP did not advertise those models. Preserve each model’s actual provider route, or limit this branch to Cursor ACP’s own catalog. This provider-switching failure must be fixed before merging.

### Issue 8
src/cli/startup.rs:231-232
Startup copies `[acp] permission_mode` into `JCODE_CURSOR_ACP_PERMISSION` once, but config reloads never update that value. When a user changes `yolo` back to `jcode`, the loaded config updates successfully while Cursor ACP continues approving execute and edit requests using the old `yolo` value. This affects both existing and newly created providers in the running process, so disabling automatic approval does not take effect until restart.

Keep the runtime policy synchronized with config reloads while preserving genuinely caller-supplied environment overrides. This permission-policy failure must be fixed before merging.

**How this was verified:** After the production config loader reloaded `jcode`, actual ACP execute and edit permission replies still selected `allow-always`.

### Issue 9
crates/jcode-provider-cursor-acp-runtime/src/lib.rs:undefined-1822
Fake-process discovery leaves `SHARED_DISCOVERED_MODELS` populated, but this test expects the shared catalog to be empty. Running discovery first makes this assertion fail; reversing the order passes. Both tests acquire the lock, so serialization alone does not make the suite reliable. Reset and restore the shared catalog around tests that read or change it.

### Issue 10
crates/jcode-app-core/src/server/debug_command_exec.rs:undefined-614
`set_provider:cursor-acp` passes the bare model name `default` to `agent.set_model()` without selecting the Cursor ACP runtime. When another provider is active, this either fails or changes that provider instead. With Gemini active, the command returned success but the next completion still went to Gemini using `default`; with Claude active, the command failed model validation. Cursor ACP was available in both checks.

Select the Cursor ACP route explicitly and use its advertised current model so the command actually switches the session before reporting success.

### Issue 11
crates/jcode-provider-cursor-acp-runtime/src/lib.rs:798-799
If the Cursor child stops reading stdin while jcode sends a prompt larger than the pipe buffer, `write_all()` waits indefinitely. The read timeout never starts, and dropping the response stream does not release the session lock. Later prompts and catalog refreshes then hang too.

Bound writes and stop the child when a write times out or the stream closes. This conversation-blocking failure must be fixed before merging.

### Issue 12
crates/jcode-provider-cursor-acp-runtime/src/lib.rs:undefined-1922
The new runtime has formatting differences that fail the required Format check. Here, `use tokio_stream::StreamExt;` is over-indented inside the async block. Several other test wrappers have the same problem. CI runs `cargo fmt --all -- --check`, so format the new file before merging.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

(11 more issues omitted due to length limits.)

Summary

Adds Cursor ACP support, including model routes, reasoning settings, permission choices, and child cleanup.

  • Jcode can route prompts through Cursor’s agent ACP and use the models it advertises.
  • Cursor ACP permission requests follow a configurable Jcode policy.
  • An explicit provider choice restarts the running server with that provider.
  • Provider-run tool results are saved alongside their matching tool calls.
  • Usage refreshes stop waiting forever on unsupported runtimes or stale locks.
  • Slow terminal color replies can arrive in pieces without expiring early.
  • Platform fixes, test adjustments, and cleanup leave other behavior unchanged.

Reviews (26) · Last reviewed commit: "chore(budgets): refresh ratchet baseline..." · Reviewed by Greptile

Comment on lines +1301 to +1309
let new_model = if effort == "none" {
remove_bracket_setting(&current, "reasoning")
} else {
set_bracket_setting(&current, "reasoning", effort)
};
*self
.model
.write()
.map_err(|_| anyhow!("Cursor ACP model state is poisoned"))? = Some(new_model);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Effort changes block prompts

set_reasoning_effort saves a rewritten model ID without checking Cursor's advertised catalog. With only gpt-5.6-sol[context=272k,reasoning=low] advertised, changing effort to high returns success, but the next prompt fails with a model-not-advertised error before reaching Cursor.

Validate the proposed variant before saving it, preserving the previous usable model when the requested effort is unsupported. This prompt-breaking behavior must be fixed before merging.

Artifacts

Evidence from the check

  • Local executed Rust test source, including fake ACP subprocess and assertions. No remote upload reference is present in the session.

Evidence from the check

  • Local executed Python runner that builds independent parent/head source snapshots and captures the targeted test results. No remote upload reference is present in the session.

Command output from the check

  • Local captured parent-revision command, exit code, successful control prompt, setter result, next-prompt error, and ACP wire messages.

Command output from the check

  • Local captured PR-head command, exit code, successful control prompt, setter result, next-prompt error, and ACP wire messages.

Evidence from the check

  • Local executed Rust test source, including fake ACP subprocess and assertions. No remote upload reference is present in the session.

Evidence from the check

  • Local executed Python runner that builds independent parent/head source snapshots and captures the targeted test results. No remote upload reference is present in the session.

Command output from the check

  • Local captured parent-revision command, exit code, successful control prompt, setter result, next-prompt error, and ACP wire messages.

Command output from the check

  • Local captured PR-head command, exit code, successful control prompt, setter result, next-prompt error, and ACP wire messages.

View artifacts

TREX Ran code and verified through TREX

Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/jcode-provider-cursor-acp-runtime/src/lib.rs
Line: 1301-1309

Comment:
**Effort changes block prompts**

`set_reasoning_effort` saves a rewritten model ID without checking Cursor's advertised catalog. With only `gpt-5.6-sol[context=272k,reasoning=low]` advertised, changing effort to `high` returns success, but the next prompt fails with a model-not-advertised error before reaching Cursor.

Validate the proposed variant before saving it, preserving the previous usable model when the requested effort is unsupported. This prompt-breaking behavior must be fixed before merging.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

} => return format!("{profile_id}:{model}"),
ModelRouteApiMethod::Copilot => return format!("copilot:{model}"),
ModelRouteApiMethod::Cursor => return format!("cursor:{model}"),
ModelRouteApiMethod::CursorAcp => return model.to_string(),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Restored sessions lose their provider

This branch restores only the saved model name, discarding its Cursor ACP route. When OpenAI is active, restoring a saved Cursor ACP gpt-* model sends the model switch through OpenAI instead; the executed restore returned success while leaving ACP inactive. Without a saved route method, the generated cursor-acp: prefix also fails unless ACP is already active.

Preserve the saved provider identity and activate its runtime during restoration. Restoring conversations through the wrong provider must be fixed before merging.

Artifacts

Evidence from the check

  • The executed Python harness embeds the Rust regression, copies tracked inputs into a scratch workspace, and runs unchanged and corrected conditions without modifying production files.

Command output from the check

  • Ran the regression on the unchanged PR head and captured complete service responses and state transitions, confirming wrong-provider restoration and failed prefix fallback.

Command output from the check

  • Ran the identical regression after preserving and explicitly dispatching the ACP prefix in the scratch copy, showing all three cases restore ACP successfully.

View artifacts

TREX Ran code and verified through TREX

Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/jcode-base/src/provider/selection.rs
Line: 551

Comment:
**Restored sessions lose their provider**

This branch restores only the saved model name, discarding its Cursor ACP route. When OpenAI is active, restoring a saved Cursor ACP `gpt-*` model sends the model switch through OpenAI instead; the executed restore returned success while leaving ACP inactive. Without a saved route method, the generated `cursor-acp:` prefix also fails unless ACP is already active.

Preserve the saved provider identity and activate its runtime during restoration. Restoring conversations through the wrong provider must be fixed before merging.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

fn fork(&self) -> Arc<dyn Provider> {
Arc::new(Self {
command: self.command.clone(),
cwd: self.cwd.clone(),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Tools use the wrong project

with_command captures the daemon's working directory once, and fork copies it without binding the provider to the session's project. Both the child process and session/new use that stored directory. In an executed daemon-A/session-B check, the fork launched its child in A, advertised A as the workspace, and read A's relative project file instead of B's.

Because Cursor executes tools itself, sessions in another project can read or modify the wrong workspace. Bind the runtime to the session's working directory before starting its child; this must be fixed before merging.

Artifacts

Evidence from the check

  • The harness links the production provider, changes cwd before forking, and drains a real ACP completion stream, exposing whether the captured directory follows the session.

Evidence from the check

  • The fake agent records the received session/new request and reads a relative project marker during session/prompt, showing the actual workspace selected by the provider.

Evidence from the check

  • The runner compiles the harness and executes one matched-directory control and one daemon-A/session-B reproduction, with assertions confirming their different filesystem targets.

Command output from the check

  • The captured rustc command links the harness against the cached production libraries and exits successfully, establishing that production provider code was executable.

Command output from the check

  • The control completes an ACP prompt with child cwd B, session/new.cwd B, and a relative read returning PROJECT_B, demonstrating the expected matched-directory behavior.

Command output from the check

  • The reproduction completes an ACP prompt but reports child cwd A, session/new.cwd A, and PROJECT_A from the relative read, confirming the wrong-project binding.

Command output from the check

  • The captured runner output includes exact compilation and execution commands, both completion streams, and passing pair assertions, confirming the candidate in one validation run.

Command output from the check

  • Executed source-listing commands capture numbered provider, orchestrator, server, and Agent lines plus Git status, showing how session directories remain separate from ACP cwd without tracked edits.

View artifacts

TREX Ran code and verified through TREX

Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/jcode-provider-cursor-acp-runtime/src/lib.rs
Line: 1328

Comment:
**Tools use the wrong project**

`with_command` captures the daemon's working directory once, and `fork` copies it without binding the provider to the session's project. Both the child process and `session/new` use that stored directory. In an executed daemon-A/session-B check, the fork launched its child in A, advertised A as the workspace, and read A's relative project file instead of B's.

Because Cursor executes tools itself, sessions in another project can read or modify the wrong workspace. Bind the runtime to the session's working directory before starting its child; this must be fixed before merging.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Comment thread src/cli/dispatch.rs
Comment on lines +906 to +913
let explicit_non_auto_provider =
args.provider != ProviderChoice::Auto && !args.fresh_spawn && !args.resume.is_some();
if server_running && explicit_non_auto_provider {
output::stderr_info(format!(
"Current server settings control `/model`. Restart server to apply: --provider {}{}",
args.provider.as_arg_value(),
args.model
.as_ref()
.map(|m| format!(" --model {}", m))
.unwrap_or_default()
"Restarting server with --provider {} (was running with a different provider)...",
args.provider.as_arg_value()
));
if let Err(err) = commands::stop_running_server().await {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 New clients stop other sessions

Opening another terminal with an explicit provider stops the shared daemon without asking. This condition never compares the running provider, so even requesting the same provider calls stop_running_server, which sends SIGTERM to the daemon's process group. The executed same-provider check terminated an isolated daemon and disconnected both existing session sockets.

Keep the daemon alive unless the user explicitly approves stopping it. Selecting a provider for one client must not silently interrupt other attached sessions or live headless/swarm work; this must be fixed before merging.

Artifacts

Evidence from the check

  • Extracts and compiles the production Rust branches and stop helper, then runs each once against an owned daemon with two connected sockets, restricting signals to that daemon.

Evidence from the check

  • Contains the pre-change provider branch and controlled dependencies used in the successful baseline run, preserving the existing daemon.

Command output from the check

  • Captures compilation, executed commands, exit codes, and socket observations for the baseline run, showing the daemon and both connections survive the same-provider request.

Evidence from the check

  • Contains the unchanged PR-head branch, stop helper, and process-group signaling function with controlled dependencies, exercising the missing provider comparison.

Command output from the check

  • Captures compilation, executed commands, exit codes, the real SIGTERM, and both socket disconnections, confirming an unnecessary same-provider daemon stop.

View artifacts

TREX Ran code and verified through TREX

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/cli/dispatch.rs
Line: 906-913

Comment:
**New clients stop other sessions**

Opening another terminal with an explicit provider stops the shared daemon without asking. This condition never compares the running provider, so even requesting the same provider calls `stop_running_server`, which sends `SIGTERM` to the daemon's process group. The executed same-provider check terminated an isolated daemon and disconnected both existing session sockets.

Keep the daemon alive unless the user explicitly approves stopping it. Selecting a provider for one client must not silently interrupt other attached sessions or live headless/swarm work; this must be fixed before merging.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Comment on lines +2128 to +2130
printf '%s\n' '{"jsonrpc":"2.0","method":"session/request_permission","id":777,"params":{"toolCall":{"kind":"execute","title":"rm -rf /"},"options":[{"optionId":"allow-once"},{"optionId":"allow-always"},{"optionId":"reject-once"}]}}'
printf '%s\n' '{"jsonrpc":"2.0","method":"session/update","params":{"update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"cancelled!"}}}}'
printf '{"jsonrpc":"2.0","id":%s,"result":{"stopReason":"end_turn"}}\n' "$id"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Permission tests ignore the answer

The fake child emits cancelled! and completes the prompt without inspecting jcode's permission reply. Reversing the response from cancellation to selected/allow-always still leaves jcode_permission_mode_cancels_execute_tools passing.

This is a non-blocking coverage gap: a regression that reverses permission decisions can escape this test. Have the fake child check the matching structured reply before reporting success, and apply that check to the other permission fixtures.

Artifacts

Evidence from the check

  • The executed script creates a scratch copy, captures permission responses, and runs the exact test before and after one policy reversal without editing shared tracked source.

Evidence from the check

  • The generated patch changes only the dangerous-tool response from cancelled to selected/allow-always, leaving the test fixture and assertion unchanged.

Command output from the check

  • The baseline run captures response ID 777 as cancelled and reports one passing test, establishing the original behavior.

Command output from the check

  • The mutated run captures response ID 777 as selected/allow-always while the same test still passes, confirming the coverage gap.

View artifacts

TREX Ran code and verified through TREX

Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/jcode-provider-cursor-acp-runtime/src/lib.rs
Line: 2128-2130

Comment:
**Permission tests ignore the answer**

The fake child emits `cancelled!` and completes the prompt without inspecting jcode's permission reply. Reversing the response from cancellation to `selected/allow-always` still leaves `jcode_permission_mode_cancels_execute_tools` passing.

This is a non-blocking coverage gap: a regression that reverses permission decisions can escape this test. Have the fake child check the matching structured reply before reporting success, and apply that check to the other permission fixtures.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Comment on lines +484 to +487
let len = tail.len();
if len > STDERR_TAIL_BYTES {
let start = len - STDERR_TAIL_BYTES;
tail.drain(..start);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Unicode can stop stderr capture

String::len counts bytes, so len - STDERR_TAIL_BYTES can fall inside a UTF-8 character. An executed 8,193-byte Unicode stderr line made drain panic, terminating the reader and poisoning its tail lock. Diagnostic retrieval then returned an empty string, and a later child write encountered a broken pipe.

This is a non-blocking reliability issue affecting long Unicode diagnostics. Move the trim point to a character boundary or retain the tail as bytes.

TREX Ran code and verified through TREX

Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/jcode-provider-cursor-acp-runtime/src/lib.rs
Line: 484-487

Comment:
**Unicode can stop stderr capture**

`String::len` counts bytes, so `len - STDERR_TAIL_BYTES` can fall inside a UTF-8 character. An executed 8,193-byte Unicode stderr line made `drain` panic, terminating the reader and poisoning its tail lock. Diagnostic retrieval then returned an empty string, and a later child write encountered a broken pipe.

This is a non-blocking reliability issue affecting long Unicode diagnostics. Move the trim point to a character boundary or retain the tail as bytes.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Comment on lines +206 to +209
let cursor_acp_provider = if cursor_acp_is_preferred {
external::instantiate_expected_external_provider(external::CURSOR_ACP_RUNTIME)
} else {
None

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Cursor ACP stays hidden

With --provider auto and no Cursor ACP preference, startup leaves the ACP runtime empty even when its executable is installed and configured. Catalog refresh and picker routes require an existing runtime, and route selection does not initialize it. In the rendered application, /model cursor showed “No matching models for cursor,” and the configured ACP executable was never invoked.

Add discovery or a working lazy-start path so users can select Cursor ACP without restarting with --provider cursor-acp. The normal automatic-startup model selection flow must work before merging.

Artifacts

Candidate0 UI authored sources

  • Uploaded the full fake-agent, TUI harness, and capture script sources in one bundle; all three requested files are preserved.

Candidate0 UI observed output

  • Uploaded the existing auto-picker execution log verbatim in an output bundle; the original observed evidence is preserved.

▶ Candidate0 UI auto-picker recording

  • Uploaded the existing auto-picker WebM recording without rerunning capture; the original visual evidence is preserved.

Candidate0 UI recording poster

  • Uploaded the existing PNG poster for the auto-picker recording; the video has an uploaded preview image.

View artifacts

TREX Ran code and verified through TREX

Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/jcode-base/src/provider/startup.rs
Line: 206-209

Comment:
**Cursor ACP stays hidden**

With `--provider auto` and no Cursor ACP preference, startup leaves the ACP runtime empty even when its executable is installed and configured. Catalog refresh and picker routes require an existing runtime, and route selection does not initialize it. In the rendered application, `/model cursor` showed “No matching models for cursor,” and the configured ACP executable was never invoked.

Add discovery or a working lazy-start path so users can select Cursor ACP without restarting with `--provider cursor-acp`. The normal automatic-startup model selection flow must work before merging.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Comment on lines +333 to +340
cursor_acp: RwLock::new(cursor_acp_provider),
bedrock: RwLock::new(bedrock_provider),
openrouter: RwLock::new(openrouter),
openai_compatible_profiles: RwLock::new(HashMap::new()),
active_openai_compatible_profile: RwLock::new(None),
session_id: RwLock::new(None),
active: RwLock::new(active),
active_external: RwLock::new(None),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Cursor ACP default is ignored

Setting default_provider = "cursor-acp" creates the runtime but does not activate it. Configuration selection uses parse_provider_hint(), which does not recognize Cursor ACP, and startup initializes active_external to None.

With this default and no default model, the executed first completion went to the automatically selected Claude endpoint instead of ACP. Explicitly selecting ACP then successfully used the same runtime. Users therefore send prompts through another provider despite their saved choice, or cannot send them without that provider's credentials. Activate the configured external runtime before choosing a default model; this routing error must be fixed before merging.

Artifacts

Candidate1 routing authored sources

  • Uploaded the full Rust routing harness, Python runner, and fake ACP source in one bundle; the exact reproduction sources are preserved.

Candidate1 HEAD routing observed output

  • Uploaded the existing successful HEAD execution log showing incorrect default routing and a successful explicit ACP control; the confirmed result is preserved.

View artifacts

TREX Ran code and verified through TREX

Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/jcode-base/src/provider/startup.rs
Line: 333-340

Comment:
**Cursor ACP default is ignored**

Setting `default_provider = "cursor-acp"` creates the runtime but does not activate it. Configuration selection uses `parse_provider_hint()`, which does not recognize Cursor ACP, and startup initializes `active_external` to `None`.

With this default and no default model, the executed first completion went to the automatically selected Claude endpoint instead of ACP. Explicitly selecting ACP then successfully used the same runtime. Users therefore send prompts through another provider despite their saved choice, or cannot send them without that provider's credentials. Activate the configured external runtime before choosing a default model; this routing error must be fixed before merging.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

@greptile-apps

This comment has been minimized.

Comment on lines +42 to +49
let routes: Vec<ModelRoute> = display_models
.into_iter()
.filter(|model| is_listable_model_name(model))
.map(|model| ModelRoute {
model,
provider: "Cursor ACP".to_string(),
api_method: super::external::CURSOR_ACP_RUNTIME.to_string(),
available: true,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Picker selects the wrong provider

When Cursor ACP is active, the simplified picker labels every model returned by available_models_display() as an available Cursor ACP route. For MultiProvider, that list also contains other providers’ models. WSL Windows Terminal enables this simplified picker automatically.

Selecting a valid OpenAI or Anthropic model then calls cursor_acp.set_model() instead of switching providers. Executed selections of both kinds failed because Cursor ACP did not advertise those models. Preserve each model’s actual provider route, or limit this branch to Cursor ACP’s own catalog. This provider-switching failure must be fixed before merging.

Artifacts

Executed routing harness for the simplified picker

  • This Rust test executes the production catalog, picker and selection paths against a real ACP runtime with a controlled subprocess catalog, reproducing the foreign-model rejection.

Command script for the isolated before and after comparison

  • This executed script installs the harness in an isolated head worktree, extracts the supplied base picker function, serializes Cargo runs and captures their outputs, making the comparison reproducible.

Selection output with the supplied base picker function

  • The executed before test selects OpenAI and Anthropic models from the mixed catalog and switches successfully to their respective provider runtimes, showing that their route identities are retained.

Selection output with the PR head picker function

  • The executed after test selects the same models after they are relabeled Cursor ACP and receives actual not-advertised errors, confirming the routing defect.

View artifacts

TREX Ran code and verified through TREX

Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/jcode-base/src/provider/catalog_routes.rs
Line: 42-49

Comment:
**Picker selects the wrong provider**

When Cursor ACP is active, the simplified picker labels every model returned by `available_models_display()` as an available Cursor ACP route. For `MultiProvider`, that list also contains other providers’ models. WSL Windows Terminal enables this simplified picker automatically.

Selecting a valid OpenAI or Anthropic model then calls `cursor_acp.set_model()` instead of switching providers. Executed selections of both kinds failed because Cursor ACP did not advertise those models. Preserve each model’s actual provider route, or limit this branch to Cursor ACP’s own catalog. This provider-switching failure must be fixed before merging.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Comment thread src/cli/startup.rs
Comment on lines +228 to +229
if !permission_mode.is_empty() && std::env::var("JCODE_CURSOR_ACP_PERMISSION").is_err() {
crate::env::set_var("JCODE_CURSOR_ACP_PERMISSION", &permission_mode);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 security Automatic approval survives config changes

Startup copies [acp] permission_mode into JCODE_CURSOR_ACP_PERMISSION once, but config reloads never update that value. When a user changes yolo back to jcode, the loaded config updates successfully while Cursor ACP continues approving execute and edit requests using the old yolo value. This affects both existing and newly created providers in the running process, so disabling automatic approval does not take effect until restart.

Keep the runtime policy synchronized with config reloads while preserving genuinely caller-supplied environment overrides. This permission-policy failure must be fixed before merging.

How this was verified: After the production config loader reloaded jcode, actual ACP execute and edit permission replies still selected allow-always.

Artifacts

Command output from the check

  • Captured the reviewed head, relevant PR diff, numbered startup/reload/permission code, and unchanged tracked files, establishing the exact source under test.

Evidence from the check

  • Extracts the startup block verbatim, builds the harness, and captures both comparison runs with their commands and exit codes.

Evidence from the check

  • Exercises real file reload and production ACP permission decisions with assertions for stale policy and explicit override controls.

Evidence from the check

  • Contains the verbatim permission propagation block extracted from the reviewed startup source and compiled into the reproduction.

Evidence from the check

  • Issues execute, edit, and read permission requests and reports the actual handler responses without executing any tools.

Evidence from the check

  • Links the isolated harness to the repository's production config and ACP runtime crates so the changed code executes directly.

Evidence from the check

  • Preserves the repository's dependency versions for the successful offline harness build.

Command output from the check

  • Captured the offline Cargo build command and successful exit code, showing that the production-backed harness compiled.

Command output from the check

  • Ran the control flow without the config-to-environment bridge and observed execute/edit cancellation after reload, isolating the startup export's effect.

Command output from the check

  • Ran the changed startup flow and observed execute/edit approval after config reloaded to jcode, confirming stale permissive policy.

View artifacts

TREX Ran code and verified through TREX

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/cli/startup.rs
Line: 228-229

Comment:
**Automatic approval survives config changes**

Startup copies `[acp] permission_mode` into `JCODE_CURSOR_ACP_PERMISSION` once, but config reloads never update that value. When a user changes `yolo` back to `jcode`, the loaded config updates successfully while Cursor ACP continues approving execute and edit requests using the old `yolo` value. This affects both existing and newly created providers in the running process, so disabling automatic approval does not take effect until restart.

Keep the runtime policy synchronized with config reloads while preserving genuinely caller-supplied environment overrides. This permission-policy failure must be fixed before merging.

**How this was verified:** After the production config loader reloaded `jcode`, actual ACP execute and edit permission replies still selected `allow-always`.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

@alecuba16
alecuba16 force-pushed the feature/cursor-acp branch 3 times, most recently from d2f4376 to 99a3617 Compare October 26, 2026 07:57
let _guard = tests_shared_state_mutex().blocking_lock();
let provider = CursorAcpProvider::with_command(CursorAcpCommand::new("sh", ["-c", "true"]));
assert!(provider.command.configured());
assert!(provider.available_models_display().is_empty());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Tests depend on execution order

Fake-process discovery leaves SHARED_DISCOVERED_MODELS populated, but this test expects the shared catalog to be empty. Running discovery first makes this assertion fail; reversing the order passes. Both tests acquire the lock, so serialization alone does not make the suite reliable. Reset and restore the shared catalog around tests that read or change it.

Artifacts

Evidence from the check

  • The executed wrapper tests call the unchanged original test bodies in opposite orders and print the shared catalog, isolating execution order as the difference.

Evidence from the check

  • The executed runner snapshots the specified HEAD, inserts the Rust harness, runs both focused checks, captures their actual output, and removes the temporary snapshot without tracked edits.

Command output from the check

  • Executed Git and numbered-source commands capture the preserved comparison and original catalog, locking, discovery, and assertion code, tying the reproduction to the requested head.

Command output from the check

  • The focused same-process check runs the original empty-catalog test before fake ACP discovery and exits 0, demonstrating the passing execution order.

Command output from the check

  • The focused same-process check completes fake ACP discovery and streaming before invoking the original empty-catalog test, which fails at line 1822 with exit 101, confirming the finding.

Command output from the check

  • Executed Git status and staged and unstaged diff checks after cleanup show only untracked setup and artifact directories, confirming no tracked edits remain.

View artifacts

TREX Ran code and verified through TREX

Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/jcode-provider-cursor-acp-runtime/src/lib.rs
Line: 1822

Comment:
**Tests depend on execution order**

Fake-process discovery leaves `SHARED_DISCOVERED_MODELS` populated, but this test expects the shared catalog to be empty. Running discovery first makes this assertion fail; reversing the order passes. Both tests acquire the lock, so serialization alone does not make the suite reliable. Reset and restore the shared catalog around tests that read or change it.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

"openai" | "codex" => jcode_provider_core::DEFAULT_OPENAI_MODEL,
"openrouter" => "anthropic/claude-sonnet-4",
"cursor" => "gpt-5",
"cursor-acp" => "default",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Provider command misses Cursor ACP

set_provider:cursor-acp passes the bare model name default to agent.set_model() without selecting the Cursor ACP runtime. When another provider is active, this either fails or changes that provider instead. With Gemini active, the command returned success but the next completion still went to Gemini using default; with Claude active, the command failed model validation. Cursor ACP was available in both checks.

Select the Cursor ACP route explicitly and use its advertised current model so the command actually switches the session before reporting success.

Artifacts

Evidence from the check

  • Uploaded the authored Rust harness that exercised the real handler and router with offline runtimes, preserving the executable reproduction.

Evidence from the check

  • Uploaded the authored Python runner that captured both handler versions and restored the tracked source, preserving the comparison procedure.

Command output from the check

  • Uploaded the existing before-change execution capture, showing explicit command rejection and unchanged provider routing.

Command output from the check

  • Uploaded the existing after-change execution capture, showing that neither attempted provider switch activated ACP.

View artifacts

TREX Ran code and verified through TREX

Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/jcode-app-core/src/server/debug_command_exec.rs
Line: 614

Comment:
**Provider command misses Cursor ACP**

`set_provider:cursor-acp` passes the bare model name `default` to `agent.set_model()` without selecting the Cursor ACP runtime. When another provider is active, this either fails or changes that provider instead. With Gemini active, the command returned success but the next completion still went to Gemini using `default`; with Claude active, the command failed model validation. Cursor ACP was available in both checks.

Select the Cursor ACP route explicitly and use its advertised current model so the command actually switches the session before reporting success.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

self.bind_auto_account_for_request(self.active_provider())?;
self.spawn_anthropic_catalog_refresh_if_needed();
self.spawn_openai_catalog_refresh_if_needed();
self.spawn_cursor_acp_catalog_refresh_if_needed();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Cursor sessions restart between turns

Normal completions trigger another catalog prefetch after the previous refresh finishes. That prefetch shuts down the live Cursor child and creates a fresh session, so the next turn must reconstruct the conversation instead of extending it.

This also loses context when an ACP tool result arrives in tool_call_update.content without rawOutput: jcode records only completed, so replay cannot restore the result that the live Cursor session retained. In a two-turn check, the unchanged code lost an earlier tool value; removing only this completion-triggered refresh preserved it. This is a non-blocking continuity issue in that case, alongside repeated startup work. Keep normal discovery cached or separate it from the live conversation, reserving child replacement for an explicit refresh.

Artifacts

Conversation behavior without the completion-triggered refresh

  • Executed the current harness with only the candidate refresh call removed and captured both fixture modes, provider events, and ACP traffic; both retained the original session and returned the earlier tool value.

Conversation behavior with unchanged head

  • Executed the current harness against unchanged head and captured both fixture modes and ACP traffic; session replacement lost content-only context while faithful rawOutput replay preserved the answer.

Rerun output and source identity verification

  • Captured the driver command, working directory, exit code, four observed outcomes, source hashes before and after execution, and Git status; the uploaded authored sources match the successful run and tracked files remain unchanged.

Executed Rust session-continuity harness

  • The executed harness delegates handles_tools_internally, calls the real MultiProvider and ACP runtime, and reconstructs next-turn history from emitted events; its unchanged hash is recorded in the execution log.

Executed stateful ACP subprocess fixture

  • The executed subprocess retains a tool value in session memory and supports content-only and rawOutput results; its wire captures distinguish missing context from successful replay.

Executed isolated comparison driver

  • The executed driver creates an isolated head archive and runs both code conditions with both fixture modes; it preserves shared production sources and produces the matching before-and-after captures.

View artifacts

TREX Ran code and verified through TREX

Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/jcode-base/src/provider/mod.rs
Line: 488

Comment:
**Cursor sessions restart between turns**

Normal completions trigger another catalog prefetch after the previous refresh finishes. That prefetch shuts down the live Cursor child and creates a fresh session, so the next turn must reconstruct the conversation instead of extending it.

This also loses context when an ACP tool result arrives in `tool_call_update.content` without `rawOutput`: jcode records only `completed`, so replay cannot restore the result that the live Cursor session retained. In a two-turn check, the unchanged code lost an earlier tool value; removing only this completion-triggered refresh preserved it. This is a non-blocking continuity issue in that case, alongside repeated startup work. Keep normal discovery cached or separate it from the live conversation, reserving child replacement for an explicit refresh.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

// it already exist in the session in Cursor's own format.
let recorded = [self.sent_history.clone(), fingerprint_all(tail)].concat();
let payload = match new_turn_start(tail) {
Some(offset) => build_prompt("", &tail[offset..]),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 New skills go unannounced

In a live Cursor ACP conversation, announce_late_skills() adds its reminder before the next user message, but plan_prompt() sends only the tail starting at that user message. Cursor never receives the reminder, while the skill is already marked announced and will not be announced again.

This is non-blocking, but users who install skills mid-conversation lose the announcement. Preserve newly added reminders when skipping Cursor’s earlier responses.

Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/jcode-provider-cursor-acp-runtime/src/lib.rs
Line: 671

Comment:
**New skills go unannounced**

In a live Cursor ACP conversation, `announce_late_skills()` adds its reminder before the next user message, but `plan_prompt()` sends only the tail starting at that user message. Cursor never receives the reminder, while the skill is already marked announced and will not be announced again.

This is non-blocking, but users who install skills mid-conversation lose the announcement. Preserve newly added reminders when skipping Cursor’s earlier responses.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

@alecuba16
alecuba16 force-pushed the feature/cursor-acp branch 2 times, most recently from b598959 to fbf06f6 Compare November 3, 2026 08:42
Comment thread crates/jcode-base/src/auth/muse.rs Resolved
#[tokio::test]
async fn fake_acp_process_discovers_models_and_streams_prompt() {
with_shared_state_lock_async(async {
use tokio_stream::StreamExt;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Format check fails

The new runtime has formatting differences that fail the required Format check. Here, use tokio_stream::StreamExt; is over-indented inside the async block. Several other test wrappers have the same problem. CI runs cargo fmt --all -- --check, so format the new file before merging.

Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/jcode-provider-cursor-acp-runtime/src/lib.rs
Line: 1922

Comment:
**Format check fails**

The new runtime has formatting differences that fail the required Format check. Here, `use tokio_stream::StreamExt;` is over-indented inside the async block. Several other test wrappers have the same problem. CI runs `cargo fmt --all -- --check`, so format the new file before merging.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@alecuba16
alecuba16 force-pushed the feature/cursor-acp branch 4 times, most recently from b4acb8c to a49b15e Compare November 7, 2026 23:48
Comment on lines +2199 to +2208
if explicit_model_provider_prefix(bare_model).is_none()
&& Self::named_provider_profile_model_prefix(bare_model).is_none()
&& !bare_model.contains('@')
{
let Some(cursor_acp) = self.cursor_acp_provider() else {
anyhow::bail!(
"Cursor ACP runtime is not available. Ensure the Cursor CLI is installed and configured."
);
};
cursor_acp.set_model(bare_model)?;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Gemini selection stays on Cursor

When Cursor ACP is active, selecting Gemini in the full /model picker still calls cursor_acp.set_model(). Gemini routes use code-assist-oauth, whose routed_model_spec() returns a bare model name. This branch captures that name before Gemini routing runs. The switch either fails against Cursor’s catalog or succeeds without leaving Cursor ACP.

Apply the selected runtime directly in set_route_selection() before treating bare names as ACP model changes.

Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/jcode-base/src/provider/mod.rs
Line: 2199-2208

Comment:
**Gemini selection stays on Cursor**

When Cursor ACP is active, selecting Gemini in the full `/model` picker still calls `cursor_acp.set_model()`. Gemini routes use `code-assist-oauth`, whose `routed_model_spec()` returns a bare model name. This branch captures that name before Gemini routing runs. The switch either fails against Cursor’s catalog or succeeds without leaving Cursor ACP.

Apply the selected runtime directly in `set_route_selection()` before treating bare names as ACP model changes.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

// Distinguish the direct Cursor HTTPS API transport from the
// Cursor CLI ACP provider, which advertises its own routes as
// "Cursor ACP" via the external runtime registry.
provider: "Cursor (API)".to_string(),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Current Cursor model goes unmarked

Renaming direct Cursor routes to Cursor (API) makes them stop matching the active provider, which still reports Cursor. model_route_provider_labels_match() does not recognize the new label, so /model no longer marks or preselects the current Cursor route.

This is non-blocking, but makes the selected model harder to identify. Add the new label to the shared matching rules when changing these route labels.

Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/jcode-base/src/provider/catalog_routes.rs
Line: 822

Comment:
**Current Cursor model goes unmarked**

Renaming direct Cursor routes to `Cursor (API)` makes them stop matching the active provider, which still reports `Cursor`. `model_route_provider_labels_match()` does not recognize the new label, so `/model` no longer marks or preselects the current Cursor route.

This is non-blocking, but makes the selected model harder to identify. Add the new label to the shared matching rules when changing these route labels.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

("antigravity", antigravity_result),
("gemini", gemini_result),
("cursor", cursor_result),
("cursor-acp", cursor_acp_result),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Failed refresh looks successful

A failed Cursor ACP refresh is treated as optional even when Cursor ACP is active. refresh_catalogs() checks only active_provider, and its matching rules contain no Cursor ACP case. It logs the failure and returns success, so /model reports a successful refresh when the selected provider could not refresh.

This is non-blocking, but hides failed refreshes from users. Check active_external when deciding which refresh errors to return.

Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/jcode-base/src/provider/catalog_refresh.rs
Line: 132

Comment:
**Failed refresh looks successful**

A failed Cursor ACP refresh is treated as optional even when Cursor ACP is active. `refresh_catalogs()` checks only `active_provider`, and its matching rules contain no Cursor ACP case. It logs the failure and returns success, so `/model` reports a successful refresh when the selected provider could not refresh.

This is non-blocking, but hides failed refreshes from users. Check `active_external` when deciding which refresh errors to return.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Master tip 7c69754 fails its own Format and Quality Guardrails
checks: 15 files carry rustfmt drift (app-core server/timing,
jcode-core stdin_detect, ratatui-core span/fast, tui status_tests,
cloud_runtime) and the swallowed-error plus test-size ratchets fell
behind current source. Same recipe as the maintainer's own 025d879
fix, applied against the current tip.
libc 0.2.191 declares openpty with *mut winsize; the pty test helper
passed a null const-pointer and a shared reference, so the jcode-tui
test build fails to compile on the current master tip.
- rendering.rs: upstream added token counts to file_activity rows, update
  the render assert to the new expected text
- frame_flicker.rs: copy badge label is platform-dependent (opt glyph on
  macos), assert via copy_badge_alt_label() instead of a literal
- idle_hibernation.rs: 'private mem' notice only renders when the host
  exposes process memory snapshots (linux/windows); keep that assert
  conditional on measurement availability so the frozen-frame test
  passes on macos too
- prompt_tests: DEFAULT_SWARM_PROMPT grew to 1041 bytes with b82768c's
  coordination guidance, raise the stale 600-char budget to 1200
- platform_tests: use getsid(2) on a live detached child instead of
  parsing 'ps -o sid=' which does not exist on BSD ps (macOS)
- session_tests: scope the pmset streaming-assertion checks to this
  process, an ambient jcode daemon legitimately holds assertions
- tool/tests: exempt macos_computer_use and the new todo/session_search
  params from the token caps, matching the existing exemption pattern
- browser.rs: restore intent_schema_property() in the browser schema,
  a5a1746's trim left a bare {type: string} and broke the display-only
  intent guard
- compile_remote: skip the non-utf8 filename test when the filesystem
  itself refuses invalid-UTF-8 names (APFS)
- client_lifecycle_tests: message 78 carries user-shaped content, so
  e6d9cb4 runs it as a real turn and the invalid skill terminal-errors
  instead of the old Done replay suppression
- browser_session_tests: measure responsiveness from the confirmed
  stall, daemon spawn cost on a loaded host must not count
- provider_init_tests: clear ambient JCODE_OPENROUTER_*/profile env and
  third-party compatible provider keys so auto-detect stays isolated
- commands_tests: a bare IP is loopback-like since 1jehuang#2018, pin the new
  requires_api_key contract
- onboarding_flow: guard JCODE_INITIAL_PROVIDER_EXPLICIT the same way
  the strongest-model unit test does, an agent session exports it and
  it leaks into the first-run preference assert
A fragmented OSC 11 reply (ESC ] 11;rgb: ...) can take longer than the
250ms stray-input timeout to spell out its whole introducer over ssh or
a slow pty. Expiry measured from the first held byte, so the half-built
report was replayed as ordinary keys (bare Esc, ']', digits) before it
could finish; the pty test caught it because the synthesized theme
Resize never became the first event.

Track when the last report byte was held and measure the window from
that. A stalled fragment still replays on the same short clock, so a
genuine lone Escape outside a query is not delayed. The recognized
REPORT_TIMEOUT path is unchanged.
Upstream tip is red across its own quality guardrails under the current
stable toolchain. The workspace lints vendored ratatui-core/-widgets, so
its new clippy findings block everything:

- ratatui-core buffer.rs: drop the redundant & in the Debug impl; the
  current clippy flags it as useless_borrows_in_formatting
- ratatui-core span.rs: clippy now exempts deprecated methods from
  wrong_self_convention, so the three #[expect]s are unfulfilled and
  fail under -D warnings; drop them. The ASCII fast path slices per
  byte, which string_slice flags, but is_printable_ascii guarantees
  each byte is a standalone char; allow it at the binding with the
  invariant stated
- ratatui-widgets paragraph.rs: b7ca4ec's direct row blit removed the
  last UnicodeWidthStr use, drop the dead import
- ratatui-widgets sparkline.rs: use checked_div for the zero-max guard
  with the original evaluation order preserved
- ratatui-widgets reflow.rs: truncate(0) is now manual_clear, use clear
- jcode-voice: allow type_complexity on the focused-session cache
  static, same pattern as the four existing allows in the workspace

Budgets: the tip also drifted past its own ratchets. Refresh
warning_budget.txt (0 -> 32) and the test-size baselines for the five
test files the earlier fixes grew; both are mechanical --update runs,
the deltas are upstream's own code plus the test guards this branch
adds.
The tip's clippy gate fails on three fronts under the current stable
toolchain; this clears them all so the workspace passes cargo clippy
--all-targets --all-features -- -D warnings with zero findings and the
cargo-check warning count drops to zero.

- transcript_search: 25 needless borrows on the search-shard arguments,
  mechanical clippy --fix
- tui_state: redundant closure replaced by the function path
- app-core: drop the child_pid binding c5c6ad8 orphaned, take the
  format_error_chain path directly, and unwrap schedule_queued_retry's
  vestigial loop whose body always returns
- companion: allow too_many_arguments on terminal_command_from_parts,
  same as the three existing allows elsewhere
- binary_watch: allow needless_return on ensure_started, the early
  return is the once-only guard and clippy misreads the cfg block below
  as the function tail
- communicate: mark the three coordination predicates dead-code-exempt
  outside tests; 92ca181 inlined their production use and the unit
  tests keep them pinned
- cloud_git: the probe loop helpers are cfg(not(test)) so nothing
  reaches them in the test build, exempt dead_code there only
- session picker preview path: the live layout rework orphaned several
  helpers while the preview tests kept calling them (draw/draw_widgets/
  draw_preview_input and the per-source preview loaders); exempt
  dead_code outside tests for those, plain allow for the ones the rework
  orphaned outright, and restore the markdown/raw-excerpt re-exports as
  cfg(test) imports the tests actually use
- canvas/filter: move the impl blocks above their test modules so no
  items sit after a cfg(test) module, keeping the cfg attrs intact
- ui_prepare: the too_many_arguments expect sat above the wrong fn
  (a doc comment separated it from shrink_wrapped_lines); reattach it
  to wrap_lines_with_map
- tests: apply the cloned_ref_to_slice_refs and clone_on_copy
  suggestions, drop the fully-specified AccountPool's needless struct
  update
- budgets: re-pin warning_budget to 0 (all 32 warnings were these
  findings) and refresh the size ratchets the new attribute lines grew
CI compiles the workspace for x86_64/aarch64-pc-windows-msvc; four unix
patterns leaked past the cfg surface on the tip and failed those jobs:

- jcode-voice dictation: the niri IPC socket fast path uses
  std::os::unix::net::UnixStream without a cfg, so the windows targets
  failed E0433. Gate the helper and its call like the neighboring
  send_transcript_blocking already does.
- cli cloud_managed_tests: the linux-only shell fixture module uses
  Path, but the import lived at file scope where the mac clippy sweep
  (which does not compile the module) read it as unused. Restore the
  import gated to the module's own cfg.
- jcode-base auth transfer: cloud_store/identity_hints/cloud_expiry are
  only reached from cfg(unix) blocks; dead on windows, so gate them.
- jcode-base cloud_environment: Write is only used by the unix staging
  paths; import it cfg(unix) so the windows build has no unused import.
- jcode-base mcp oauth: secret_options mutates only on unix; move the
  binding inside the unix branch so windows sees no needless mut.
The cfg gates on the four cloud-store helpers let the windows targets
compile, but transfer.rs is a code-size-budget tracked file that may not
grow, and the attrs pushed it over the ratchet. Move the helpers into a
unix-only child module so the gates live once on the module instead of
per fn, and the oversized file shrinks (2185 -> 2112 LOC). Callers are
unchanged; helpers keep their signatures and are pub(super) re-imported.
…gelog updates box

the test asserted no picker metadata (including box borders) in the
preview transcript, but the startup header renders an Updates box
from unseen changelog entries, which on a fresh CI runner is always
non-empty (no last_seen_changelog state file). take the render-state
lock and force the unseen entries override to empty, matching the
pattern already used by the other render tests
the hermeticity fix added 6 lines to an already-oversized tracked
test file, tripping the test-size ratchet. extract nothing, just reuse
the production line_plain_text helper instead of an inline span-join
closure and tighten the comment; net line count back to the baseline
Comment on lines +97 to 102
pub(super) fn set_active_provider_external(&self, key: &str) {
*self
.active_external
.write()
.unwrap_or_else(|poisoned| poisoned.into_inner()) = Some(key.to_string());
self.invalidate_routes_memo();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Compaction uses the previous provider

Selecting Cursor ACP changes active_external but leaves active pointing at the previous provider. The compaction methods still use active. If OpenAI was selected with explicit native compaction enabled, /compact sends the Cursor conversation to OpenAI’s compact endpoint using the old model and credentials.

Delegate compaction capabilities and calls to the selected external runtime too, so choosing Cursor ACP does not send conversation history through the previous provider.

Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/jcode-base/src/provider/selection.rs
Line: 97-102

Comment:
**Compaction uses the previous provider**

Selecting Cursor ACP changes `active_external` but leaves `active` pointing at the previous provider. The compaction methods still use `active`. If OpenAI was selected with explicit native compaction enabled, `/compact` sends the Cursor conversation to OpenAI’s compact endpoint using the old model and credentials.

Delegate compaction capabilities and calls to the selected external runtime too, so choosing Cursor ACP does not send conversation history through the previous provider.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

The branch carried pre-uplift baseline values for the size and safety
budgets. Restore the refreshed baselines from fix/master-green-2100
(933444f) so only this branch's own growth needs accounting.
Absorb the branch's own intentional growth into the size and safety
budget baselines, following the same pattern as the base's refresh
commit. Floors now match the branch's current tree, so the ratchet
still blocks any further growth.

This branch has not been deployed

No deployments
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: Add Cursor CLI ACP provider (cursor-acp)

1 participant