@@ -2110,6 +2110,117 @@ describe("agent event handler", () => {
21102110 expect ( agentRunSeq . has ( "run-terminal-error" ) ) . toBe ( false ) ;
21112111 } ) ;
21122112
2113+ it ( "keeps deferred lifecycle-error cleanup across later non-terminal events" , ( ) => {
2114+ vi . useFakeTimers ( ) ;
2115+ const { broadcast, clearAgentRunContext, agentRunSeq, handler } = createHarness ( {
2116+ resolveSessionKeyForRun : ( ) => "session-terminal-error" ,
2117+ lifecycleErrorRetryGraceMs : 100 ,
2118+ } ) ;
2119+ registerAgentRunContext ( "run-terminal-late-tool" , {
2120+ sessionKey : "session-terminal-error" ,
2121+ } ) ;
2122+
2123+ handler ( {
2124+ runId : "run-terminal-late-tool" ,
2125+ seq : 1 ,
2126+ stream : "lifecycle" ,
2127+ ts : Date . now ( ) ,
2128+ data : { phase : "start" } ,
2129+ } ) ;
2130+ handler ( {
2131+ runId : "run-terminal-late-tool" ,
2132+ seq : 2 ,
2133+ stream : "lifecycle" ,
2134+ ts : Date . now ( ) ,
2135+ data : { phase : "error" , error : "request timed out" } ,
2136+ } ) ;
2137+ handler ( {
2138+ runId : "run-terminal-late-tool" ,
2139+ seq : 3 ,
2140+ stream : "tool" ,
2141+ ts : Date . now ( ) ,
2142+ data : { phase : "result" , name : "exec" } ,
2143+ } ) ;
2144+
2145+ vi . advanceTimersByTime ( 99 ) ;
2146+
2147+ expect ( clearAgentRunContext ) . not . toHaveBeenCalled ( ) ;
2148+ expect ( agentRunSeq . get ( "run-terminal-late-tool" ) ) . toBe ( 3 ) ;
2149+ expect (
2150+ chatBroadcastCalls ( broadcast ) . some (
2151+ ( [ , payload ] ) => ( payload as { state ?: string } ) . state === "error" ,
2152+ ) ,
2153+ ) . toBe ( false ) ;
2154+
2155+ vi . advanceTimersByTime ( 1 ) ;
2156+
2157+ const finalPayload = chatBroadcastCalls ( broadcast ) . at ( - 1 ) ?. [ 1 ] as {
2158+ state ?: string ;
2159+ runId ?: string ;
2160+ errorMessage ?: string ;
2161+ } ;
2162+ expect ( finalPayload . state ) . toBe ( "error" ) ;
2163+ expect ( finalPayload . runId ) . toBe ( "run-terminal-late-tool" ) ;
2164+ expect ( finalPayload . errorMessage ) . toContain ( "request timed out" ) ;
2165+ expect ( clearAgentRunContext ) . toHaveBeenCalledWith ( "run-terminal-late-tool" ) ;
2166+ expect ( agentRunSeq . has ( "run-terminal-late-tool" ) ) . toBe ( false ) ;
2167+ expect (
2168+ persistGatewaySessionLifecycleEventMock . mock . calls . some (
2169+ ( [ params ] ) =>
2170+ ( params as { event ?: { data ?: { phase ?: string } } } ) . event ?. data ?. phase === "error" ,
2171+ ) ,
2172+ ) . toBe ( true ) ;
2173+ } ) ;
2174+
2175+ it ( "cancels deferred lifecycle-error cleanup when the run restarts" , ( ) => {
2176+ vi . useFakeTimers ( ) ;
2177+ const { broadcast, clearAgentRunContext, agentRunSeq, handler } = createHarness ( {
2178+ resolveSessionKeyForRun : ( ) => "session-terminal-retry" ,
2179+ lifecycleErrorRetryGraceMs : 100 ,
2180+ } ) ;
2181+ registerAgentRunContext ( "run-terminal-retry" , {
2182+ sessionKey : "session-terminal-retry" ,
2183+ } ) ;
2184+
2185+ handler ( {
2186+ runId : "run-terminal-retry" ,
2187+ seq : 1 ,
2188+ stream : "lifecycle" ,
2189+ ts : Date . now ( ) ,
2190+ data : { phase : "start" } ,
2191+ } ) ;
2192+ handler ( {
2193+ runId : "run-terminal-retry" ,
2194+ seq : 2 ,
2195+ stream : "lifecycle" ,
2196+ ts : Date . now ( ) ,
2197+ data : { phase : "error" , error : "attempt failed" } ,
2198+ } ) ;
2199+ handler ( {
2200+ runId : "run-terminal-retry" ,
2201+ seq : 3 ,
2202+ stream : "lifecycle" ,
2203+ ts : Date . now ( ) ,
2204+ data : { phase : "start" } ,
2205+ } ) ;
2206+
2207+ vi . advanceTimersByTime ( 100 ) ;
2208+
2209+ expect (
2210+ chatBroadcastCalls ( broadcast ) . some (
2211+ ( [ , payload ] ) => ( payload as { state ?: string } ) . state === "error" ,
2212+ ) ,
2213+ ) . toBe ( false ) ;
2214+ expect ( clearAgentRunContext ) . not . toHaveBeenCalled ( ) ;
2215+ expect ( agentRunSeq . get ( "run-terminal-retry" ) ) . toBe ( 3 ) ;
2216+ expect (
2217+ persistGatewaySessionLifecycleEventMock . mock . calls . filter (
2218+ ( [ params ] ) =>
2219+ ( params as { event ?: { data ?: { phase ?: string } } } ) . event ?. data ?. phase === "error" ,
2220+ ) ,
2221+ ) . toHaveLength ( 0 ) ;
2222+ } ) ;
2223+
21132224 it ( "adds detected errorKind to chat lifecycle error payloads" , ( ) => {
21142225 const { broadcast, nodeSendToSession, handler } = createHarness ( {
21152226 resolveSessionKeyForRun : ( ) => "session-detected-error" ,
0 commit comments