@@ -165,7 +165,6 @@ impl<I: Copy> PendingSessionPromptResponseWaiters<I> {
165165
166166 /// Cancels any pending waiter for the session with a Cancelled response.
167167 /// Used when a cancel notification arrives; we don't have a prompt_token.
168- #[ allow( dead_code) ]
169168 pub fn cancel_waiter_for_session (
170169 & self ,
171170 session_id : & SessionId ,
@@ -240,7 +239,7 @@ mod tests {
240239 use std:: time:: Duration ;
241240
242241 use agent_client_protocol:: { PromptResponse , SessionId , StopReason } ;
243- use trogon_std:: time:: { MockClock , MockInstant } ;
242+ use trogon_std:: time:: { GetNow , MockClock , MockInstant } ;
244243
245244 use super :: * ;
246245
@@ -255,6 +254,36 @@ mod tests {
255254 assert ! ( !resolved) ;
256255 }
257256
257+ #[ test]
258+ fn should_suppress_missing_waiter_after_timeout ( ) {
259+ let waiters = PendingSessionPromptResponseWaiters :: < MockInstant > :: new ( ) ;
260+ let clock = MockClock :: new ( ) ;
261+ let session_id = SessionId :: from ( "s1" ) ;
262+ let token = PromptToken ( 42 ) ;
263+
264+ assert ! ( !waiters. should_suppress_missing_waiter_warning( & session_id, token, & clock) ) ;
265+ waiters. mark_prompt_waiter_timed_out ( session_id. clone ( ) , token, & clock) ;
266+ assert ! ( waiters. should_suppress_missing_waiter_warning( & session_id, token, & clock) ) ;
267+ }
268+
269+ #[ tokio:: test]
270+ async fn register_waiter_clears_stale_timed_out_entries ( ) {
271+ let waiters = PendingSessionPromptResponseWaiters :: < MockInstant > :: new ( ) ;
272+ let clock = MockClock :: new ( ) ;
273+ let session_id = SessionId :: from ( "s1" ) ;
274+
275+ let ( _rx, _guard, token) = waiters. register_waiter ( session_id. clone ( ) ) . unwrap ( ) ;
276+ waiters. mark_prompt_waiter_timed_out ( session_id. clone ( ) , token, & clock) ;
277+ assert_eq ! ( waiters. timed_out. lock( ) . unwrap( ) . len( ) , 1 ) ;
278+
279+ waiters. remove_waiter_for_test ( & session_id) ;
280+ let ( _rx2, _guard2, _token2) = waiters. register_waiter ( session_id. clone ( ) ) . unwrap ( ) ;
281+ assert ! (
282+ waiters. timed_out. lock( ) . unwrap( ) . is_empty( ) ,
283+ "register_waiter should have cleared timed_out entries for the session"
284+ ) ;
285+ }
286+
258287 #[ test]
259288 fn purge_expired_timed_out_waiters_removes_expired_markers ( ) {
260289 let waiters = PendingSessionPromptResponseWaiters :: < MockInstant > :: new ( ) ;
@@ -268,6 +297,27 @@ mod tests {
268297 assert ! ( waiters. timed_out. lock( ) . unwrap( ) . is_empty( ) ) ;
269298 }
270299
300+ #[ test]
301+ fn purge_keeps_non_expired_markers ( ) {
302+ let waiters = PendingSessionPromptResponseWaiters :: < MockInstant > :: new ( ) ;
303+ let clock = MockClock :: new ( ) ;
304+ let old_instant = clock. now ( ) ;
305+ clock. advance ( PROMPT_TIMEOUT_WARNING_SUPPRESSION_WINDOW + Duration :: from_millis ( 1 ) ) ;
306+ let fresh_instant = clock. now ( ) ;
307+ {
308+ let mut timed_out = waiters. timed_out . lock ( ) . unwrap ( ) ;
309+ timed_out. insert ( ( SessionId :: from ( "old" ) , PromptToken ( 0 ) ) , old_instant) ;
310+ timed_out. insert ( ( SessionId :: from ( "fresh" ) , PromptToken ( 1 ) ) , fresh_instant) ;
311+ }
312+ assert_eq ! ( waiters. timed_out. lock( ) . unwrap( ) . len( ) , 2 ) ;
313+
314+ waiters. purge_expired_timed_out_waiters ( & clock) ;
315+
316+ let timed_out = waiters. timed_out . lock ( ) . unwrap ( ) ;
317+ assert_eq ! ( timed_out. len( ) , 1 ) ;
318+ assert ! ( timed_out. contains_key( & ( SessionId :: from( "fresh" ) , PromptToken ( 1 ) ) ) ) ;
319+ }
320+
271321 #[ tokio:: test]
272322 async fn dropping_old_guard_does_not_remove_new_waiter_for_same_session ( ) {
273323 let waiters = PendingSessionPromptResponseWaiters :: < MockInstant > :: new ( ) ;
0 commit comments