@@ -2300,6 +2300,120 @@ describe("SessionService", () => {
23002300 } ) ;
23012301 } ) ;
23022302
2303+ describe ( "retryUnhealthyCloudSessions" , ( ) => {
2304+ it ( "retries every errored cloud session" , async ( ) => {
2305+ const service = getSessionService ( ) ;
2306+
2307+ const erroredCloudA : AgentSession = {
2308+ ...createMockSession ( {
2309+ taskId : "task-a" ,
2310+ taskRunId : "run-a" ,
2311+ status : "error" ,
2312+ } ) ,
2313+ isCloud : true ,
2314+ } ;
2315+ const erroredCloudB : AgentSession = {
2316+ ...createMockSession ( {
2317+ taskId : "task-b" ,
2318+ taskRunId : "run-b" ,
2319+ status : "error" ,
2320+ } ) ,
2321+ isCloud : true ,
2322+ } ;
2323+
2324+ mockSessionStoreSetters . getSessions . mockReturnValue ( {
2325+ "run-a" : erroredCloudA ,
2326+ "run-b" : erroredCloudB ,
2327+ } ) ;
2328+ mockSessionStoreSetters . getSessionByTaskId . mockImplementation (
2329+ ( taskId : string ) => {
2330+ if ( taskId === "task-a" ) return erroredCloudA ;
2331+ if ( taskId === "task-b" ) return erroredCloudB ;
2332+ return undefined ;
2333+ } ,
2334+ ) ;
2335+
2336+ service . retryUnhealthyCloudSessions ( ) ;
2337+
2338+ await vi . waitFor ( ( ) => {
2339+ expect ( mockTrpcCloudTask . retry . mutate ) . toHaveBeenCalledTimes ( 2 ) ;
2340+ } ) ;
2341+ expect ( mockTrpcCloudTask . retry . mutate ) . toHaveBeenCalledWith ( {
2342+ taskId : "task-a" ,
2343+ runId : "run-a" ,
2344+ } ) ;
2345+ expect ( mockTrpcCloudTask . retry . mutate ) . toHaveBeenCalledWith ( {
2346+ taskId : "task-b" ,
2347+ runId : "run-b" ,
2348+ } ) ;
2349+ } ) ;
2350+
2351+ it . each ( [
2352+ [
2353+ "non-error cloud session (status=connected)" ,
2354+ {
2355+ ...createMockSession ( {
2356+ taskId : "task-skip" ,
2357+ taskRunId : "run-skip" ,
2358+ status : "connected" ,
2359+ } ) ,
2360+ isCloud : true ,
2361+ } as AgentSession ,
2362+ ] ,
2363+ [
2364+ "non-error cloud session (status=disconnected)" ,
2365+ {
2366+ ...createMockSession ( {
2367+ taskId : "task-skip" ,
2368+ taskRunId : "run-skip" ,
2369+ status : "disconnected" ,
2370+ } ) ,
2371+ isCloud : true ,
2372+ } as AgentSession ,
2373+ ] ,
2374+ [
2375+ "errored local session (isCloud=false)" ,
2376+ createMockSession ( {
2377+ taskId : "task-skip" ,
2378+ taskRunId : "run-skip" ,
2379+ status : "error" ,
2380+ } ) ,
2381+ ] ,
2382+ ] ) ( "skips %s" , ( _label , session ) => {
2383+ const service = getSessionService ( ) ;
2384+ mockSessionStoreSetters . getSessions . mockReturnValue ( {
2385+ "run-skip" : session ,
2386+ } ) ;
2387+
2388+ service . retryUnhealthyCloudSessions ( ) ;
2389+
2390+ expect ( mockTrpcCloudTask . retry . mutate ) . not . toHaveBeenCalled ( ) ;
2391+ } ) ;
2392+
2393+ it ( "swallows failures so one bad retry doesn't block the rest" , async ( ) => {
2394+ const service = getSessionService ( ) ;
2395+ const errored : AgentSession = {
2396+ ...createMockSession ( {
2397+ taskId : "task-a" ,
2398+ taskRunId : "run-a" ,
2399+ status : "error" ,
2400+ } ) ,
2401+ isCloud : true ,
2402+ } ;
2403+
2404+ mockSessionStoreSetters . getSessions . mockReturnValue ( { "run-a" : errored } ) ;
2405+ mockSessionStoreSetters . getSessionByTaskId . mockReturnValue ( errored ) ;
2406+ mockTrpcCloudTask . retry . mutate . mockRejectedValueOnce (
2407+ new Error ( "network down" ) ,
2408+ ) ;
2409+
2410+ expect ( ( ) => service . retryUnhealthyCloudSessions ( ) ) . not . toThrow ( ) ;
2411+ await vi . waitFor ( ( ) => {
2412+ expect ( mockTrpcCloudTask . retry . mutate ) . toHaveBeenCalled ( ) ;
2413+ } ) ;
2414+ } ) ;
2415+ } ) ;
2416+
23032417 describe ( "reset" , ( ) => {
23042418 it ( "clears connecting tasks" , ( ) => {
23052419 const service = getSessionService ( ) ;
0 commit comments