11use super :: Bridge ;
2- use crate :: nats:: { self , FlushClient , PublishClient , RequestClient , agent} ;
2+ use crate :: nats:: { self , FlushClient , PublishClient , agent} ;
33use crate :: session_id:: AcpSessionId ;
4- use agent_client_protocol:: {
5- CancelNotification , Error , ErrorCode , PromptResponse , Result , StopReason ,
6- } ;
4+ use agent_client_protocol:: { CancelNotification , Error , ErrorCode , Result } ;
75use tracing:: { info, instrument, warn} ;
86use trogon_std:: time:: GetElapsed ;
97
@@ -17,7 +15,7 @@ use trogon_std::time::GetElapsed;
1715 skip( bridge, args) ,
1816 fields( session_id = %args. session_id)
1917) ]
20- pub async fn handle < N : RequestClient + PublishClient + FlushClient , C : GetElapsed > (
18+ pub async fn handle < N : PublishClient + FlushClient , C : GetElapsed > (
2119 bridge : & Bridge < N , C > ,
2220 args : CancelNotification ,
2321) -> Result < ( ) > {
@@ -59,15 +57,19 @@ pub async fn handle<N: RequestClient + PublishClient + FlushClient, C: GetElapse
5957 . record_error ( "cancel" , "cancel_publish_failed" ) ;
6058 }
6159
62- bridge
63- . cancelled_sessions
64- . mark_cancelled ( args. session_id . clone ( ) , & bridge. clock ) ;
65- bridge
66- . pending_session_prompt_responses
67- . cancel_waiter_for_session (
68- & args. session_id ,
69- Ok ( PromptResponse :: new ( StopReason :: Cancelled ) ) ,
70- ) ;
60+ let cancelled_subject =
61+ agent:: session_cancelled ( bridge. config . acp_prefix ( ) , & args. session_id . to_string ( ) ) ;
62+ if let Err ( e) = bridge
63+ . nats ( )
64+ . publish_with_headers (
65+ cancelled_subject,
66+ async_nats:: HeaderMap :: new ( ) ,
67+ bytes:: Bytes :: new ( ) ,
68+ )
69+ . await
70+ {
71+ warn ! ( session_id = %args. session_id, error = %e, "Failed to publish session_cancelled broadcast" ) ;
72+ }
7173
7274 bridge. metrics . record_request (
7375 "cancel" ,
@@ -82,7 +84,7 @@ pub async fn handle<N: RequestClient + PublishClient + FlushClient, C: GetElapse
8284mod tests {
8385 use super :: Bridge ;
8486 use crate :: config:: Config ;
85- use agent_client_protocol:: { Agent , CancelNotification , ErrorCode , SessionId , StopReason } ;
87+ use agent_client_protocol:: { Agent , CancelNotification , ErrorCode } ;
8688 use opentelemetry:: Value ;
8789 use opentelemetry:: metrics:: MeterProvider ;
8890 use opentelemetry_sdk:: metrics:: data:: { AggregatedMetrics , MetricData } ;
@@ -103,6 +105,7 @@ mod tests {
103105 trogon_std:: time:: SystemClock ,
104106 & opentelemetry:: global:: meter ( "acp-nats-test" ) ,
105107 Config :: for_test ( "acp" ) ,
108+ tokio:: sync:: mpsc:: channel ( 1 ) . 0 ,
106109 ) ;
107110 ( mock, bridge)
108111 }
@@ -126,6 +129,7 @@ mod tests {
126129 trogon_std:: time:: SystemClock ,
127130 & meter,
128131 Config :: for_test ( "acp" ) ,
132+ tokio:: sync:: mpsc:: channel ( 1 ) . 0 ,
129133 ) ;
130134 ( mock, bridge, exporter, provider)
131135 }
@@ -142,6 +146,7 @@ mod tests {
142146 clock. clone ( ) ,
143147 & opentelemetry:: global:: meter ( "acp-nats-test" ) ,
144148 Config :: for_test ( "acp" ) ,
149+ tokio:: sync:: mpsc:: channel ( 1 ) . 0 ,
145150 ) ;
146151 ( mock, clock, bridge)
147152 }
@@ -228,6 +233,20 @@ mod tests {
228233 ) ;
229234 }
230235
236+ #[ tokio:: test]
237+ async fn cancel_also_publishes_session_cancelled_broadcast ( ) {
238+ let ( mock, bridge) = mock_bridge ( ) ;
239+
240+ let _ = bridge. cancel ( CancelNotification :: new ( "s1" ) ) . await ;
241+
242+ let published = mock. published_messages ( ) ;
243+ assert ! (
244+ published. contains( & "acp.s1.agent.session.cancelled" . to_string( ) ) ,
245+ "expected publish to acp.s1.agent.session.cancelled (prompt broadcast), got: {:?}" ,
246+ published
247+ ) ;
248+ }
249+
231250 #[ tokio:: test]
232251 async fn cancel_validates_session_id ( ) {
233252 let ( _mock, bridge) = mock_bridge ( ) ;
@@ -337,49 +356,6 @@ mod tests {
337356 provider. shutdown ( ) . unwrap ( ) ;
338357 }
339358
340- #[ tokio:: test]
341- async fn cancel_marks_session_as_cancelled ( ) {
342- let ( _mock, _clock, bridge) = mock_bridge_with_clock ( ) ;
343- let session_id = "cancel-session-001" ;
344-
345- assert ! (
346- bridge
347- . cancelled_sessions
348- . take_if_cancelled( & session_id. into( ) , & bridge. clock)
349- . is_none( )
350- ) ;
351-
352- let notification = CancelNotification :: new ( session_id) ;
353- bridge. cancel ( notification) . await . unwrap ( ) ;
354-
355- assert ! (
356- bridge
357- . cancelled_sessions
358- . take_if_cancelled( & session_id. into( ) , & bridge. clock)
359- . is_some( )
360- ) ;
361- }
362-
363- #[ tokio:: test]
364- async fn cancel_resolves_pending_prompt_waiter_with_cancelled ( ) {
365- let ( _mock, _clock, bridge) = mock_bridge_with_clock ( ) ;
366- let session_id: SessionId = "cancel-session-002" . into ( ) ;
367-
368- let ( rx, _guard, _token) = bridge
369- . pending_session_prompt_responses
370- . register_waiter ( session_id. clone ( ) )
371- . unwrap ( ) ;
372-
373- let notification = CancelNotification :: new ( session_id. clone ( ) ) ;
374- bridge. cancel ( notification) . await . unwrap ( ) ;
375-
376- let response = rx
377- . await
378- . expect ( "Should receive cancelled response" )
379- . expect ( "Prompt waiter should receive success response" ) ;
380- assert_eq ! ( response. stop_reason, StopReason :: Cancelled ) ;
381- }
382-
383359 #[ tokio:: test]
384360 async fn cancel_publishes_to_nats ( ) {
385361 let ( mock, _clock, bridge) = mock_bridge_with_clock ( ) ;
@@ -395,43 +371,4 @@ mod tests {
395371 published
396372 ) ;
397373 }
398-
399- #[ tokio:: test]
400- async fn cancel_session_evicts_expired_on_mark ( ) {
401- let ( _mock, clock, bridge) = mock_bridge_with_clock ( ) ;
402-
403- let session_old: SessionId = "old-session" . into ( ) ;
404- let session_new: SessionId = "new-session" . into ( ) ;
405-
406- bridge
407- . cancelled_sessions
408- . mark_cancelled ( session_old. clone ( ) , & bridge. clock ) ;
409-
410- clock. advance ( Duration :: from_secs ( 301 ) ) ;
411-
412- for idx in 0 ..15 {
413- let filler_session: SessionId = format ! ( "filler-{idx}" ) . into ( ) ;
414- bridge
415- . cancelled_sessions
416- . mark_cancelled ( filler_session, & bridge. clock ) ;
417- }
418-
419- bridge
420- . cancelled_sessions
421- . mark_cancelled ( session_new. clone ( ) , & bridge. clock ) ;
422-
423- assert ! (
424- bridge
425- . cancelled_sessions
426- . take_if_cancelled( & session_old, & bridge. clock)
427- . is_none( )
428- ) ;
429-
430- assert ! (
431- bridge
432- . cancelled_sessions
433- . take_if_cancelled( & session_new, & bridge. clock)
434- . is_some( )
435- ) ;
436- }
437374}
0 commit comments