Skip to content

feat(acp-nats): complete ACP 0.10.2 agent method coverage#48

Merged
yordis merged 1 commit intomainfrom
feat/acp-complete-agent-coverage
Mar 24, 2026
Merged

feat(acp-nats): complete ACP 0.10.2 agent method coverage#48
yordis merged 1 commit intomainfrom
feat/acp-complete-agent-coverage

Conversation

@yordis
Copy link
Copy Markdown
Member

@yordis yordis commented Mar 24, 2026

Summary

  • Enable all 9 ACP unstable feature flags (added 5 missing)
  • Add 6 missing agent method handlers for full protocol coverage
  • Add client-side ext_method and ext_notification handlers
  • 75 new tests (492 total in acp-nats)

Feature flags enabled

Flag Status Purpose
unstable_session_model was enabled Set session model
unstable_session_fork was enabled Fork session
unstable_session_resume was enabled Resume session
unstable_session_usage was enabled Session usage reporting
unstable_session_close new Close session gracefully
unstable_message_id new Message ID in notifications
unstable_auth_methods new Auth method negotiation
unstable_cancel_request new RequestCancelled error code
unstable_boolean_config new Boolean config option type

New agent handlers

Handler Pattern Session-scoped Side effect
list_sessions initialize No None
set_session_config_option set_session_mode Yes None
set_session_model set_session_mode Yes None
fork_session new_session Yes Publishes session.ready (new session ID)
resume_session load_session Yes Publishes session.ready (request session ID)
close_session set_session_mode Yes None

New client handlers

Handler Pattern Purpose
ext_method fs_read_text_file (request/response) Forward _-prefixed ext RPC calls to client
ext_notification session_update (fire-and-forget) Forward _-prefixed ext notifications to client

ACP uses _ prefix on JSON-RPC method names for extensions (e.g., _my_method). These map to NATS subjects like {prefix}.{sid}.client._my_method. Previously these were silently dropped; now they're routed to the Client trait's ext_method/ext_notification based on whether a reply subject is present.

NATS subject mapping (agent)

Method Subject
session/list {prefix}.agent.session.list
session/setConfigOption {prefix}.{sid}.agent.session.set_config_option
session/setModel {prefix}.{sid}.agent.session.set_model
session/fork {prefix}.{sid}.agent.session.fork
session/resume {prefix}.{sid}.agent.session.resume
session/close {prefix}.{sid}.agent.session.close

Test plan

  • cargo check --workspace
  • cargo clippy --workspace --all-targets
  • cargo test --workspace — 492 tests pass (75 new)
  • cargo fmt --all -- --check

@yordis yordis added the rust:coverage-baseline-reset Relax Rust coverage gate to establish a new baseline label Mar 24, 2026
@cursor
Copy link
Copy Markdown

cursor bot commented Mar 24, 2026

PR Summary

Medium Risk
Adds multiple new RPC surfaces and NATS subject routes (including session lifecycle operations and generic client extensions), which could impact interoperability if subject naming/validation is incorrect. Changes are well-tested but touch core request routing paths.

Overview
Completes ACP 0.10.2 coverage by enabling additional agent-client-protocol unstable features and adding new agent handlers for list_sessions, set_session_config_option, set_session_model, fork_session, resume_session, and close_session (including session ID validation, request metrics, and session.ready publishing on fork/resume).

Adds generic client extension routing: parses client.ext.{name} subjects into ClientMethod::Ext, dispatches to a new client::ext handler that forwards to the Client trait’s ext_method/ext_notification based on presence of a reply subject, and tightens ext-name validation to avoid ambiguous subjects.

Extends NATS subject helpers to cover the new agent methods and adds extensive tests around routing, error handling, metrics, and session.ready side effects.

Written by Cursor Bugbot for commit 580eaf0. This will update automatically on new commits. Configure here.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 24, 2026

Caution

Review failed

The head commit changed during the review from c6409a0 to 580eaf0.

Walkthrough

This PR expands the acp-nats crate with six new session-related agent request handlers (list, fork, resume, close, set model, set config option), introduces client extension method support via JSON-RPC-like handling over NATS, adds corresponding NATS subject builders, and extends NATS parsing to recognize and route extension methods while enabling additional Cargo feature flags for the underlying protocol library.

Changes

Cohort / File(s) Summary
Session Handlers
agent/close_session.rs, agent/fork_session.rs, agent/list_sessions.rs, agent/resume_session.rs, agent/set_session_config_option.rs, agent/set_session_model.rs
Six new async request handlers for ACP session lifecycle operations. Each validates the session ID, constructs a NATS subject, performs request/response with timeout, records telemetry (metrics and tracing spans), and maps NATS errors to protocol errors. Handlers that fork/resume sessions additionally schedule session-ready publication. Comprehensive test suites cover success/failure paths, error handling, and metric emission.
Agent Trait Extension
agent/mod.rs
Added six new Agent trait methods (list_sessions, fork_session, resume_session, close_session, set_session_model, set_session_config_option) implemented by Bridge<N, C>, each delegating to corresponding handler module functions.
NATS Subject Builders
nats/subjects.rs
Added six public subject builder functions under the agent module namespace: session_list(prefix) and five session-scoped variants (session_fork, session_resume, session_close, session_set_model, session_set_config_option) that format NATS subject paths with configured ACP prefix and optional session ID. Includes verification tests for exact subject formatting.
Client Extension Method Support
client/mod.rs, client/ext.rs, nats/parsing.rs
Added JSON-RPC-like extension method handling for NATS clients. New ClientMethod::Ext(String) variant in parsing recognizes client._<methodName> suffixes; client/ext.rs routes parsed extension requests/notifications to the client, handles serialization, and publishes responses; client/mod.rs wires ext dispatch into the main method routing.
NATS Parsing Extensions
nats/parsing.rs
Extended ClientMethod::from_subject_suffix to parse extension methods matching client._<name> pattern using ExtMethodName validation; parse_client_subject rejects ambiguous subjects containing multiple .client. occurrences when ext methods are present.
Cargo Dependency
Cargo.toml
Enabled additional unstable_* feature flags (unstable_auth_methods, unstable_boolean_config, unstable_cancel_request, unstable_message_id, unstable_session_close) on agent-client-protocol alongside the existing four unstable session features.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Bridge
    participant Handler as Session Handler
    participant NATS
    participant Metrics

    Client->>Bridge: list_sessions(args)
    Bridge->>Handler: handle(bridge, args)
    Handler->>Handler: Start elapsed timer
    Handler->>NATS: request_with_timeout(subject)
    alt NATS Success
        NATS-->>Handler: ListSessionsResponse
        Handler->>Handler: Parse response
        Handler->>Metrics: record_request("list_sessions", elapsed, true)
        Handler-->>Bridge: Ok(response)
    else NATS Failure
        NATS-->>Handler: Error
        Handler->>Handler: map_nats_error()
        Handler->>Metrics: record_request("list_sessions", elapsed, false)
        Handler-->>Bridge: Err(protocol_error)
    end
    Bridge-->>Client: Result
Loading
sequenceDiagram
    participant Client
    participant Bridge as Bridge<N,C>
    participant Handler as Session Handler<br/>(fork/resume)
    participant NATS
    participant Scheduler
    participant Metrics

    Client->>Bridge: fork_session(args) or resume_session(args)
    Bridge->>Handler: handle(bridge, args)
    Handler->>Handler: Validate session_id
    Handler->>NATS: request_with_timeout(subject)
    alt Session ID Invalid
        Handler->>Metrics: record_error(session_validate)
        Handler-->>Bridge: Err(InvalidParams)
    else NATS Success
        NATS-->>Handler: ForkSessionResponse/<br/>ResumeSessionResponse
        Handler->>Scheduler: schedule_session_ready(new_session_id)
        Scheduler->>NATS: publish(ready_subject)
        Handler->>Metrics: record_request(method, elapsed, true)
        Handler-->>Bridge: Ok(response)
    else NATS Failure
        NATS-->>Handler: Error
        Handler->>Metrics: record_request(method, elapsed, false)
        Handler-->>Bridge: Err(protocol_error)
    end
    Bridge-->>Client: Result
Loading
sequenceDiagram
    participant Client as NATS Client
    participant Dispatcher as client/mod
    participant ExtHandler as client/ext
    participant AppClient as Application<br/>Client
    participant NATS as NATS<br/>Broker

    Client->>Dispatcher: dispatch(ClientMethod::Ext(name), payload, reply)
    Dispatcher->>ExtHandler: handle(payload, app_client, reply, ...)
    alt Has Reply (Request-Response)
        ExtHandler->>ExtHandler: Parse ExtRequest (with request_id)
        ExtHandler->>AppClient: ext_method(name, params)
        alt Client Success
            AppClient-->>ExtHandler: Response
            ExtHandler->>ExtHandler: Serialize result
            ExtHandler->>NATS: publish(reply, success_response)
        else Client Error
            AppClient-->>ExtHandler: Err
            ExtHandler->>NATS: publish(reply, error_response)
        end
    else No Reply (Notification)
        ExtHandler->>ExtHandler: Parse ExtNotification
        ExtHandler->>AppClient: ext_notification(name, params)
        AppClient-->>ExtHandler: (fire-and-forget)
    end
    ExtHandler->>NATS: flush()
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐰 Six new sessions bloom on NATS threads so bright,
Fork and resume, list them all in sight!
Extension methods hop with JSON delight,
Metrics and traces guide each request's flight!
🌟

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 57.47% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: enabling full ACP 0.10.2 agent method coverage by adding missing handlers and feature flags.
Description check ✅ Passed The description is comprehensive and directly related to the changeset, detailing feature flags enabled, new agent handlers, new client handlers, NATS subject mappings, and test results.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/acp-complete-agent-coverage

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@yordis yordis removed the rust:coverage-baseline-reset Relax Rust coverage gate to establish a new baseline label Mar 24, 2026
@github-actions
Copy link
Copy Markdown

github-actions bot commented Mar 24, 2026

badge

Code Coverage Summary

Details
Filename                                                     Stmts    Miss  Cover    Missing
---------------------------------------------------------  -------  ------  -------  ---------------------------------------------------------------------------------------------
crates/acp-nats/src/telemetry/metrics.rs                        65       0  100.00%
crates/acp-nats/src/config.rs                                  200       0  100.00%
crates/acp-nats/src/error.rs                                    84       0  100.00%
crates/acp-nats/src/session_id.rs                               88       0  100.00%
crates/acp-nats/src/lib.rs                                      73       0  100.00%
crates/acp-nats/src/in_flight_slot_guard.rs                     32       0  100.00%
crates/acp-nats/src/pending_prompt_waiters.rs                  112       0  100.00%
crates/acp-nats/src/jsonrpc.rs                                   6       0  100.00%
crates/acp-nats/src/acp_prefix.rs                               63       0  100.00%
crates/acp-nats/src/ext_method_name.rs                          85       0  100.00%
crates/acp-nats/src/agent/load_session.rs                      250       0  100.00%
crates/acp-nats/src/agent/ext_method.rs                        232       0  100.00%
crates/acp-nats/src/agent/new_session.rs                       234       0  100.00%
crates/acp-nats/src/agent/fork_session.rs                      245       0  100.00%
crates/acp-nats/src/agent/mod.rs                               147       0  100.00%
crates/acp-nats/src/agent/resume_session.rs                    245       0  100.00%
crates/acp-nats/src/agent/initialize.rs                        178       0  100.00%
crates/acp-nats/src/agent/set_session_mode.rs                  165       0  100.00%
crates/acp-nats/src/agent/prompt.rs                            218       0  100.00%
crates/acp-nats/src/agent/set_session_config_option.rs         162       0  100.00%
crates/acp-nats/src/agent/ext_notification.rs                  224       0  100.00%
crates/acp-nats/src/agent/cancel.rs                            236       0  100.00%
crates/acp-nats/src/agent/authenticate.rs                      148       0  100.00%
crates/acp-nats/src/agent/list_sessions.rs                     143       0  100.00%
crates/acp-nats/src/agent/set_session_model.rs                 162       0  100.00%
crates/acp-nats/src/agent/close_session.rs                     158       0  100.00%
crates/acp-nats/src/nats/extensions.rs                           3       0  100.00%
crates/acp-nats/src/nats/subjects.rs                           152       0  100.00%
crates/acp-nats/src/nats/parsing.rs                            237       1  99.58%   68
crates/acp-nats/src/nats/token.rs                                8       0  100.00%
crates/acp-nats-stdio/src/main.rs                              113      11  90.27%   58, 106-113, 119-121, 138
crates/acp-nats-stdio/src/config.rs                             72       0  100.00%
crates/trogon-nats/src/auth.rs                                 114       3  97.37%   49-51
crates/trogon-nats/src/connect.rs                               96      16  83.33%   21-23, 36, 50, 69-152
crates/trogon-nats/src/client.rs                                25      25  0.00%    50-89
crates/trogon-nats/src/messaging.rs                            528       4  99.24%   135-140, 150-151
crates/trogon-nats/src/mocks.rs                                304       0  100.00%
crates/acp-nats-ws/src/connection.rs                           162      35  78.40%   71-78, 83-94, 110, 112-113, 118, 129-131, 138, 142, 146, 149-157, 168, 172, 175, 178-182, 216
crates/acp-nats-ws/src/main.rs                                 157       2  98.73%   83, 246
crates/acp-nats-ws/src/config.rs                                83       0  100.00%
crates/acp-nats-ws/src/upgrade.rs                               57       2  96.49%   59, 90
crates/acp-nats/src/client/terminal_release.rs                 357       0  100.00%
crates/acp-nats/src/client/request_permission.rs               338       0  100.00%
crates/acp-nats/src/client/terminal_wait_for_exit.rs           396       0  100.00%
crates/acp-nats/src/client/session_update.rs                    55       0  100.00%
crates/acp-nats/src/client/ext_session_prompt_response.rs      149       0  100.00%
crates/acp-nats/src/client/fs_write_text_file.rs               451       0  100.00%
crates/acp-nats/src/client/ext.rs                              365       8  97.81%   193-204, 229-240
crates/acp-nats/src/client/mod.rs                             2978       0  100.00%
crates/acp-nats/src/client/terminal_create.rs                  294       0  100.00%
crates/acp-nats/src/client/terminal_kill.rs                    309       0  100.00%
crates/acp-nats/src/client/terminal_output.rs                  223       0  100.00%
crates/acp-nats/src/client/rpc_reply.rs                         71       0  100.00%
crates/acp-nats/src/client/fs_read_text_file.rs                384       0  100.00%
crates/trogon-std/src/dirs/system.rs                            98      11  88.78%   57, 65, 67, 75, 77, 85, 87, 96, 98, 109, 154
crates/trogon-std/src/dirs/fixed.rs                             84       0  100.00%
crates/acp-telemetry/src/signal.rs                               3       3  0.00%    4-43
crates/acp-telemetry/src/trace.rs                               32       4  87.50%   23-24, 31-32
crates/acp-telemetry/src/metric.rs                              35       4  88.57%   33-34, 41-42
crates/acp-telemetry/src/log.rs                                 70       2  97.14%   39-40
crates/acp-telemetry/src/lib.rs                                153      22  85.62%   38-45, 80, 85, 90, 104-119
crates/acp-telemetry/src/service_name.rs                        16       0  100.00%
crates/trogon-std/src/json.rs                                   30       0  100.00%
crates/trogon-std/src/args.rs                                   10       0  100.00%
crates/trogon-std/src/fs/mem.rs                                220      10  95.45%   61-63, 77-79, 133-135, 158
crates/trogon-std/src/fs/system.rs                              29      12  58.62%   17-19, 31-45
crates/trogon-std/src/time/mock.rs                             123       0  100.00%
crates/trogon-std/src/time/system.rs                            24       0  100.00%
crates/trogon-std/src/env/system.rs                             17       0  100.00%
crates/trogon-std/src/env/in_memory.rs                          81       0  100.00%
TOTAL                                                        13461     175  98.70%

Diff against main

Filename                                                  Stmts    Miss  Cover
------------------------------------------------------  -------  ------  --------
crates/acp-nats/src/agent/fork_session.rs                  +245       0  +100.00%
crates/acp-nats/src/agent/mod.rs                            +12       0  +100.00%
crates/acp-nats/src/agent/resume_session.rs                +245       0  +100.00%
crates/acp-nats/src/agent/set_session_config_option.rs     +162       0  +100.00%
crates/acp-nats/src/agent/list_sessions.rs                 +143       0  +100.00%
crates/acp-nats/src/agent/set_session_model.rs             +162       0  +100.00%
crates/acp-nats/src/agent/close_session.rs                 +158       0  +100.00%
crates/acp-nats/src/nats/subjects.rs                        +46       0  +100.00%
crates/acp-nats/src/nats/parsing.rs                         +51      +1  -0.42%
crates/acp-nats/src/client/ext.rs                          +365      +8  +97.81%
TOTAL                                                     +1589      +9  +0.10%

Results for commit: 580eaf0

Minimum allowed coverage is 95%

♻️ This comment has been updated with latest results

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (3)
rsworkspace/crates/acp-nats/src/agent/resume_session.rs (1)

232-319: Replace the fixed sleeps with deterministic task draining.

The waits on Lines 244, 266, and 308 couple these tests to SESSION_READY_DELAY and publish-retry timing, which makes them slower and eventually flaky. Bridge::drain_background_tasks() is already the right barrier for the publish/error-path cases, and the request-metric assertion on Lines 254-274 does not need to wait at all because record_request runs before resume_session returns.

⏱️ Suggested cleanup
-        tokio::time::sleep(Duration::from_millis(300)).await;
+        bridge.drain_background_tasks().await;
         let published = mock.published_messages();
@@
-        tokio::time::sleep(Duration::from_millis(150)).await;
         provider.force_flush().unwrap();
@@
-        tokio::time::sleep(Duration::from_millis(600)).await;
+        bridge.drain_background_tasks().await;
         provider.force_flush().unwrap();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rsworkspace/crates/acp-nats/src/agent/resume_session.rs` around lines 232 -
319, Replace the fixed tokio::time::sleep waits with deterministic task
draining: in resume_session_publishes_session_ready_to_correct_subject and
resume_session_records_error_when_session_ready_publish_fails call
bridge.drain_background_tasks() instead of sleeping to wait for publish/retry
completion; in resume_session_records_metrics_on_success remove the sleep
entirely (the request metric is recorded synchronously before resume_session
returns) and proceed to provider.force_flush() and assertions; keep
provider.force_flush()/shutdown where present and ensure you call
bridge.drain_background_tasks() only where publish background work must
complete.
rsworkspace/crates/acp-nats/src/nats/subjects.rs (1)

53-75: Stop widening the raw &str subject API.

These new builders take primitive prefix/session_id values even though the crate already has validated domain values like AcpSessionId. Accepting the value objects here would make invalid subject parts unrepresentable and remove the need for every caller to remember pre-validation.

As per coding guidelines, "Prefer domain-specific value objects over primitives (e.g., AcpPrefix not String), with each type's factory guaranteeing correctness at construction—invalid instances should be unrepresentable."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rsworkspace/crates/acp-nats/src/nats/subjects.rs` around lines 53 - 75, The
subject builder functions (session_list, session_set_config_option,
session_set_model, session_fork, session_resume, session_close) currently accept
raw &str for prefix and session_id; change their signatures to accept the domain
value objects (e.g., AcpPrefix for prefix and AcpSessionId for session_id) so
invalid parts are unrepresentable, and build the subject string from those types
(use their accessor method, e.g., as_str() or to_string()) when calling format!;
update all usages to pass the domain types instead of raw &str.
rsworkspace/crates/acp-nats/src/agent/fork_session.rs (1)

248-252: Prefer drain_background_tasks() over fixed sleeps in these tests.

These waits are coupled to the current session.ready delay and publish-retry timing. Since Bridge already tracks the spawned background work, awaiting bridge.drain_background_tasks().await here will make the assertions deterministic and cut CI flakiness.

♻️ Suggested change
-        tokio::time::sleep(Duration::from_millis(300)).await;
+        bridge.drain_background_tasks().await;
         let published = mock.published_messages();
@@
-        tokio::time::sleep(Duration::from_millis(150)).await;
+        bridge.drain_background_tasks().await;
         provider.force_flush().unwrap();
@@
-        tokio::time::sleep(Duration::from_millis(600)).await;
+        bridge.drain_background_tasks().await;
         provider.force_flush().unwrap();

Also applies to: 270-275, 312-317

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rsworkspace/crates/acp-nats/src/agent/fork_session.rs` around lines 248 -
252, Replace the fixed tokio::time::sleep(Duration::from_millis(...)).await
waits after calling bridge.fork_session(ForkSessionRequest::new(...)).await with
an await on bridge.drain_background_tasks().await so the test waits for
Bridge-tracked background work instead of timing assumptions; update the
occurrences around the fork_session calls (including the similar blocks at the
other noted locations) to call bridge.drain_background_tasks().await immediately
after the fork_session await to make assertions deterministic.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@rsworkspace/crates/acp-nats/src/agent/close_session.rs`:
- Around line 22-47: The session ID validation path in the
AcpSessionId::try_from call returns early on error and only records a
"session_validate" metric, so malformed IDs never get counted by
bridge.metrics.record_request for "close_session"; update the error handling in
the AcpSessionId::try_from map_err closure (the block around
AcpSessionId::try_from(&args.session_id)) to also call
bridge.metrics.record_request("close_session",
bridge.clock.elapsed(start).as_secs_f64(), false) before returning the Error so
that every request path (including validation failures) logs a failed
close_session request.

In `@rsworkspace/crates/acp-nats/src/agent/fork_session.rs`:
- Around line 43-47: The success branch currently treats response.session_id as
valid without converting it into the domain type; change the flow in the
Ok(result) branch to attempt constructing/validating an AcpSessionId (using its
try_from/parse/factory) from response.session_id first, and only upon successful
creation record the id via Span::current().record, call info!(new_session_id =
%session_id, ...) and call bridge.schedule_session_ready with the validated
AcpSessionId; if validation fails, emit an error log (including the raw
response.session_id and the validation error) and do not call
schedule_session_ready so we never publish against an invalid subject. Ensure
you update uses of response.session_id in this block to the validated variable
and keep scheduling limited to the typed AcpSessionId.

---

Nitpick comments:
In `@rsworkspace/crates/acp-nats/src/agent/fork_session.rs`:
- Around line 248-252: Replace the fixed
tokio::time::sleep(Duration::from_millis(...)).await waits after calling
bridge.fork_session(ForkSessionRequest::new(...)).await with an await on
bridge.drain_background_tasks().await so the test waits for Bridge-tracked
background work instead of timing assumptions; update the occurrences around the
fork_session calls (including the similar blocks at the other noted locations)
to call bridge.drain_background_tasks().await immediately after the fork_session
await to make assertions deterministic.

In `@rsworkspace/crates/acp-nats/src/agent/resume_session.rs`:
- Around line 232-319: Replace the fixed tokio::time::sleep waits with
deterministic task draining: in
resume_session_publishes_session_ready_to_correct_subject and
resume_session_records_error_when_session_ready_publish_fails call
bridge.drain_background_tasks() instead of sleeping to wait for publish/retry
completion; in resume_session_records_metrics_on_success remove the sleep
entirely (the request metric is recorded synchronously before resume_session
returns) and proceed to provider.force_flush() and assertions; keep
provider.force_flush()/shutdown where present and ensure you call
bridge.drain_background_tasks() only where publish background work must
complete.

In `@rsworkspace/crates/acp-nats/src/nats/subjects.rs`:
- Around line 53-75: The subject builder functions (session_list,
session_set_config_option, session_set_model, session_fork, session_resume,
session_close) currently accept raw &str for prefix and session_id; change their
signatures to accept the domain value objects (e.g., AcpPrefix for prefix and
AcpSessionId for session_id) so invalid parts are unrepresentable, and build the
subject string from those types (use their accessor method, e.g., as_str() or
to_string()) when calling format!; update all usages to pass the domain types
instead of raw &str.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7495593b-446e-465a-ad0b-46b552627512

📥 Commits

Reviewing files that changed from the base of the PR and between 64b028f and 28bb5b8.

📒 Files selected for processing (9)
  • rsworkspace/crates/acp-nats/Cargo.toml
  • rsworkspace/crates/acp-nats/src/agent/close_session.rs
  • rsworkspace/crates/acp-nats/src/agent/fork_session.rs
  • rsworkspace/crates/acp-nats/src/agent/list_sessions.rs
  • rsworkspace/crates/acp-nats/src/agent/mod.rs
  • rsworkspace/crates/acp-nats/src/agent/resume_session.rs
  • rsworkspace/crates/acp-nats/src/agent/set_session_config_option.rs
  • rsworkspace/crates/acp-nats/src/agent/set_session_model.rs
  • rsworkspace/crates/acp-nats/src/nats/subjects.rs

@yordis yordis force-pushed the feat/acp-complete-agent-coverage branch from 28bb5b8 to 1486981 Compare March 24, 2026 06:02
@yordis yordis force-pushed the feat/acp-complete-agent-coverage branch from 1486981 to e125286 Compare March 24, 2026 13:20
@yordis yordis force-pushed the feat/acp-complete-agent-coverage branch from e125286 to cfd8df4 Compare March 24, 2026 13:30
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (2)
rsworkspace/crates/acp-nats/src/agent/fork_session.rs (1)

43-47: ⚠️ Potential issue | 🟠 Major

Validate the forked session ID before treating this as success.

response.session_id never crosses AcpSessionId, so this branch records new_session_id and schedules session.ready for whatever the agent returned. Because the publish is fire-and-forget, a malformed ID becomes a silent readiness miss unless you validate it here or make schedule_session_ready accept a validated type.

As per coding guidelines, "Prefer domain-specific value objects over primitives (e.g., AcpPrefix not String), with each type's factory guaranteeing correctness at construction—invalid instances should be unrepresentable".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rsworkspace/crates/acp-nats/src/agent/fork_session.rs` around lines 43 - 47,
The code treats response.session_id as valid without converting it into the
domain type, so a malformed ID can be silently accepted; update the branch
handling Ok(response) to validate/construct an AcpSessionId (e.g., via
AcpSessionId::try_from or a parse/try_new factory) from response.session_id
before recording it with Span::current().record, logging it with info!, and
calling bridge.schedule_session_ready; on validation failure, log an error (with
the invalid raw value) and do not call bridge.schedule_session_ready so only
valid AcpSessionId instances are scheduled.
rsworkspace/crates/acp-nats/src/agent/close_session.rs (1)

22-30: ⚠️ Potential issue | 🟠 Major

Count invalid session IDs as failed requests.

Line 30 returns before Lines 43-47, so malformed IDs only emit session_validate and never increment failed close_session requests. That skews acp.requests, and the same copied pattern is still present in set_session_config_option.rs, set_session_model.rs, fork_session.rs, and resume_session.rs.

Also applies to: 43-47

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rsworkspace/crates/acp-nats/src/agent/close_session.rs` around lines 22 - 30,
The invalid-session-id branch in close_session (where
AcpSessionId::try_from(&args.session_id) maps Err to Error::new with
ErrorCode::InvalidParams) currently only calls
bridge.metrics.record_error("session_validate", "invalid_session_id") and
returns early, so it never increments the failed "close_session" request metric;
update that error handler to also increment the close_session failure metric
(e.g., call bridge.metrics.record_error("close_session", "invalid_session_id")
or the appropriate failed-request counter) before returning the Error, and apply
the same change to the analogous handlers in set_session_config_option.rs,
set_session_model.rs, fork_session.rs, and resume_session.rs to ensure malformed
IDs count as failed requests.
🧹 Nitpick comments (2)
rsworkspace/crates/acp-nats/src/nats/parsing.rs (1)

36-40: Consider storing ExtMethodName instead of raw String.

The code validates via ExtMethodName::new() but then discards the value object and stores only the raw string. Per coding guidelines, domain-specific value objects should be preferred over primitives. Storing ExtMethodName directly would preserve the validation guarantee downstream.

That said, this is a minor point since validation does occur, and the string is immediately used for forwarding.

♻️ Optional refactor to preserve the value object
 #[derive(Debug, Clone, PartialEq, Eq)]
 pub enum ClientMethod {
     // ... other variants
-    Ext(String),
+    Ext(ExtMethodName),
 }

 impl ClientMethod {
     pub fn from_subject_suffix(suffix: &str) -> Option<Self> {
         match suffix {
             // ... other cases
             other => {
                 let ext_name = other.strip_prefix(EXT_METHOD_SUBJECT_PREFIX)?;
-                ExtMethodName::new(ext_name).ok()?;
-                Some(Self::Ext(ext_name.to_string()))
+                let validated = ExtMethodName::new(ext_name).ok()?;
+                Some(Self::Ext(validated))
             }
         }
     }
 }

As per coding guidelines: "Prefer domain-specific value objects over primitives (e.g., AcpPrefix not String), with each type's factory guaranteeing correctness at construction."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rsworkspace/crates/acp-nats/src/nats/parsing.rs` around lines 36 - 40, The
branch validates the subject by calling ExtMethodName::new(ext_name) but then
throws away the value object and stores a raw String in
Self::Ext(ext_name.to_string()); change this to construct and store the
ExtMethodName instance instead (e.g., create the ExtMethodName via
ExtMethodName::new(ext_name)? and return Some(Self::Ext(<the ExtMethodName>)))
so downstream code receives the validated domain value object rather than a
plain String; update the Ext variant/constructor to accept ExtMethodName if
necessary and adjust usages that expect a String.
rsworkspace/crates/acp-nats/src/agent/fork_session.rs (1)

252-252: Use drain_background_tasks() instead of fixed sleeps in these tests.

The 150/300/600 ms waits are coupled to SESSION_READY_DELAY and scheduler latency. Bridge::drain_background_tasks() already gives these assertions a deterministic completion point, and the same sleep-based pattern is repeated in resume_session.rs.

Also applies to: 274-274, 316-316

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rsworkspace/crates/acp-nats/src/agent/fork_session.rs` at line 252, Replace
the fixed sleep calls that are waiting for SESSION_READY_DELAY and scheduler
latency with a deterministic drain of background tasks: call
Bridge::drain_background_tasks() (e.g., bridge.drain_background_tasks().await)
instead of tokio::time::sleep(Duration::from_millis(...)).await in the tests in
fork_session.rs; do the same for the other identical sleep occurrences in this
file and replicate the change in resume_session.rs so assertions wait on
drain_background_tasks() rather than timing-based sleeps.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@rsworkspace/crates/acp-nats/src/agent/resume_session.rs`:
- Around line 24-34: The code validates args.session_id into an AcpSessionId but
later publishes using the original args.session_id, which can diverge if
AcpSessionId canonicalizes; update the publish path to use the validated
AcpSessionId (use session_id.as_str() when building the subject via
agent::session_resume and when publishing the "session.ready" message on
bridge.nats()) so both the request subject and follow-up "session.ready" subject
use the canonical ID from AcpSessionId rather than the raw args.session_id.

---

Duplicate comments:
In `@rsworkspace/crates/acp-nats/src/agent/close_session.rs`:
- Around line 22-30: The invalid-session-id branch in close_session (where
AcpSessionId::try_from(&args.session_id) maps Err to Error::new with
ErrorCode::InvalidParams) currently only calls
bridge.metrics.record_error("session_validate", "invalid_session_id") and
returns early, so it never increments the failed "close_session" request metric;
update that error handler to also increment the close_session failure metric
(e.g., call bridge.metrics.record_error("close_session", "invalid_session_id")
or the appropriate failed-request counter) before returning the Error, and apply
the same change to the analogous handlers in set_session_config_option.rs,
set_session_model.rs, fork_session.rs, and resume_session.rs to ensure malformed
IDs count as failed requests.

In `@rsworkspace/crates/acp-nats/src/agent/fork_session.rs`:
- Around line 43-47: The code treats response.session_id as valid without
converting it into the domain type, so a malformed ID can be silently accepted;
update the branch handling Ok(response) to validate/construct an AcpSessionId
(e.g., via AcpSessionId::try_from or a parse/try_new factory) from
response.session_id before recording it with Span::current().record, logging it
with info!, and calling bridge.schedule_session_ready; on validation failure,
log an error (with the invalid raw value) and do not call
bridge.schedule_session_ready so only valid AcpSessionId instances are
scheduled.

---

Nitpick comments:
In `@rsworkspace/crates/acp-nats/src/agent/fork_session.rs`:
- Line 252: Replace the fixed sleep calls that are waiting for
SESSION_READY_DELAY and scheduler latency with a deterministic drain of
background tasks: call Bridge::drain_background_tasks() (e.g.,
bridge.drain_background_tasks().await) instead of
tokio::time::sleep(Duration::from_millis(...)).await in the tests in
fork_session.rs; do the same for the other identical sleep occurrences in this
file and replicate the change in resume_session.rs so assertions wait on
drain_background_tasks() rather than timing-based sleeps.

In `@rsworkspace/crates/acp-nats/src/nats/parsing.rs`:
- Around line 36-40: The branch validates the subject by calling
ExtMethodName::new(ext_name) but then throws away the value object and stores a
raw String in Self::Ext(ext_name.to_string()); change this to construct and
store the ExtMethodName instance instead (e.g., create the ExtMethodName via
ExtMethodName::new(ext_name)? and return Some(Self::Ext(<the ExtMethodName>)))
so downstream code receives the validated domain value object rather than a
plain String; update the Ext variant/constructor to accept ExtMethodName if
necessary and adjust usages that expect a String.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e005c95d-a870-4935-baa7-bc9369268c93

📥 Commits

Reviewing files that changed from the base of the PR and between 28bb5b8 and e125286.

📒 Files selected for processing (13)
  • rsworkspace/crates/acp-nats/Cargo.toml
  • rsworkspace/crates/acp-nats/src/agent/close_session.rs
  • rsworkspace/crates/acp-nats/src/agent/fork_session.rs
  • rsworkspace/crates/acp-nats/src/agent/list_sessions.rs
  • rsworkspace/crates/acp-nats/src/agent/mod.rs
  • rsworkspace/crates/acp-nats/src/agent/resume_session.rs
  • rsworkspace/crates/acp-nats/src/agent/set_session_config_option.rs
  • rsworkspace/crates/acp-nats/src/agent/set_session_model.rs
  • rsworkspace/crates/acp-nats/src/client/ext_method.rs
  • rsworkspace/crates/acp-nats/src/client/ext_notification.rs
  • rsworkspace/crates/acp-nats/src/client/mod.rs
  • rsworkspace/crates/acp-nats/src/nats/parsing.rs
  • rsworkspace/crates/acp-nats/src/nats/subjects.rs
🚧 Files skipped from review as they are similar to previous changes (2)
  • rsworkspace/crates/acp-nats/Cargo.toml
  • rsworkspace/crates/acp-nats/src/nats/subjects.rs

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
rsworkspace/crates/acp-nats/src/agent/fork_session.rs (1)

43-47: ⚠️ Potential issue | 🟠 Major

Still validate the returned session_id before publishing session.ready.

response.session_id comes back from NATS and is used here without crossing an AcpSessionId boundary. If the agent returns an invalid ID, this branch still logs success and schedules session.ready on a bad subject; because that publish is async, the caller only notices later via a missing readiness event. Validate the response ID first, then record/log/schedule from the typed value.

♻️ Minimal fix
-use tracing::{Span, info, instrument};
+use tracing::{Span, info, instrument, warn};
@@
-    if let Ok(ref response) = result {
-        Span::current().record("new_session_id", response.session_id.to_string().as_str());
-        info!(new_session_id = %response.session_id, "Session forked");
-
-        bridge.schedule_session_ready(response.session_id.clone());
-    }
+    if let Ok(ref response) = result {
+        match AcpSessionId::try_from(&response.session_id) {
+            Ok(new_session_id) => {
+                Span::current().record("new_session_id", new_session_id.as_str());
+                info!(new_session_id = new_session_id.as_str(), "Session forked");
+                bridge.schedule_session_ready(agent_client_protocol::SessionId::from(
+                    new_session_id.as_str(),
+                ));
+            }
+            Err(e) => {
+                warn!(
+                    raw_session_id = %response.session_id,
+                    error = %e,
+                    "Ignoring invalid forked session ID from agent"
+                );
+            }
+        }
+    }

As per coding guidelines, "Prefer domain-specific value objects over primitives (e.g., AcpPrefix not String), with each type's factory guaranteeing correctness at construction—invalid instances should be unrepresentable".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rsworkspace/crates/acp-nats/src/agent/fork_session.rs` around lines 43 - 47,
Validate the returned response.session_id by attempting to construct the domain
type (e.g., AcpSessionId::try_from or the crate's constructor) before any side
effects: try to parse/construct an AcpSessionId from
response.session_id.clone(), and if that fails log an error (with the invalid
raw ID) and return/skip scheduling; only after successful construction use the
typed AcpSessionId value for Span::current().record, the info! log, and
bridge.schedule_session_ready so the published session.ready uses a
guaranteed-valid subject.
🧹 Nitpick comments (1)
rsworkspace/crates/acp-nats/src/client/ext.rs (1)

59-65: Keep ext method names typed after parsing.

rsworkspace/crates/acp-nats/src/nats/parsing.rs:23-42 already validates extension names with ExtMethodName::new(...), but this handler drops back to a raw &str. Threading ExtMethodName through ClientMethod::Ext(...) and ext::handle(...) would keep invalid names unrepresentable past the parsing boundary.

As per coding guidelines, "Prefer domain-specific value objects over primitives (e.g., AcpPrefix not String), with each type's factory guaranteeing correctness at construction—invalid instances should be unrepresentable".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rsworkspace/crates/acp-nats/src/client/ext.rs` around lines 59 - 65, The
handler currently accepts ext_method_name as a raw &str—make the extension name
a typed domain value by changing the parameter to accept ExtMethodName (e.g.,
&ExtMethodName or ExtMethodName) in ext::handle and propagate that type through
ClientMethod::Ext so parsed/validated names become unrepresentable when invalid;
update all call sites that construct ClientMethod::Ext and invocations of
ext::handle to pass the validated ExtMethodName (or a reference), and adjust any
pattern matches or uses of the name inside handle to use the ExtMethodName API
instead of string operations.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@rsworkspace/crates/acp-nats/src/agent/fork_session.rs`:
- Around line 43-47: Validate the returned response.session_id by attempting to
construct the domain type (e.g., AcpSessionId::try_from or the crate's
constructor) before any side effects: try to parse/construct an AcpSessionId
from response.session_id.clone(), and if that fails log an error (with the
invalid raw ID) and return/skip scheduling; only after successful construction
use the typed AcpSessionId value for Span::current().record, the info! log, and
bridge.schedule_session_ready so the published session.ready uses a
guaranteed-valid subject.

---

Nitpick comments:
In `@rsworkspace/crates/acp-nats/src/client/ext.rs`:
- Around line 59-65: The handler currently accepts ext_method_name as a raw
&str—make the extension name a typed domain value by changing the parameter to
accept ExtMethodName (e.g., &ExtMethodName or ExtMethodName) in ext::handle and
propagate that type through ClientMethod::Ext so parsed/validated names become
unrepresentable when invalid; update all call sites that construct
ClientMethod::Ext and invocations of ext::handle to pass the validated
ExtMethodName (or a reference), and adjust any pattern matches or uses of the
name inside handle to use the ExtMethodName API instead of string operations.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6cb4c416-f012-4e56-b541-3823843d1341

📥 Commits

Reviewing files that changed from the base of the PR and between e125286 and cfd8df4.

📒 Files selected for processing (12)
  • rsworkspace/crates/acp-nats/Cargo.toml
  • rsworkspace/crates/acp-nats/src/agent/close_session.rs
  • rsworkspace/crates/acp-nats/src/agent/fork_session.rs
  • rsworkspace/crates/acp-nats/src/agent/list_sessions.rs
  • rsworkspace/crates/acp-nats/src/agent/mod.rs
  • rsworkspace/crates/acp-nats/src/agent/resume_session.rs
  • rsworkspace/crates/acp-nats/src/agent/set_session_config_option.rs
  • rsworkspace/crates/acp-nats/src/agent/set_session_model.rs
  • rsworkspace/crates/acp-nats/src/client/ext.rs
  • rsworkspace/crates/acp-nats/src/client/mod.rs
  • rsworkspace/crates/acp-nats/src/nats/parsing.rs
  • rsworkspace/crates/acp-nats/src/nats/subjects.rs
✅ Files skipped from review due to trivial changes (2)
  • rsworkspace/crates/acp-nats/Cargo.toml
  • rsworkspace/crates/acp-nats/src/agent/set_session_model.rs
🚧 Files skipped from review as they are similar to previous changes (3)
  • rsworkspace/crates/acp-nats/src/nats/subjects.rs
  • rsworkspace/crates/acp-nats/src/nats/parsing.rs
  • rsworkspace/crates/acp-nats/src/agent/close_session.rs

@yordis yordis force-pushed the feat/acp-complete-agent-coverage branch from cfd8df4 to c6409a0 Compare March 24, 2026 14:02
Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
@yordis yordis force-pushed the feat/acp-complete-agent-coverage branch from c6409a0 to 580eaf0 Compare March 24, 2026 14:05
Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

@yordis yordis added the rust:coverage-baseline-reset Relax Rust coverage gate to establish a new baseline label Mar 24, 2026
@yordis yordis merged commit 9100b5c into main Mar 24, 2026
7 of 8 checks passed
@yordis yordis deleted the feat/acp-complete-agent-coverage branch March 24, 2026 14:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

rust:coverage-baseline-reset Relax Rust coverage gate to establish a new baseline

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant