@@ -2,6 +2,8 @@ import { describe, expect, test } from "bun:test";
22
33import { createSubAgentSessionStore } from "./session-store.js" ;
44import { forcedStopReport } from "./stop-policy.js" ;
5+ import { agentLaneIsLive , fleetProgress } from "../tui/agent-progress.js" ;
6+ import { formatAgentsPanel } from "../tui/chrome-state.js" ;
57
68import type { ReactorEmittedEvent } from "@intx/inference" ;
79
@@ -553,8 +555,9 @@ describe("interrupt stamps finishedAt once", () => {
553555 expect ( store . get ( session . id ) ?. outstandingTools ) . toHaveLength ( 1 ) ;
554556 } ) ;
555557
556- test ( "sendInputOne interrupt sets finishedAt once and keeps tools" , ( ) => {
558+ test ( "sendInputOne interrupt starts a live follow-up turn and keeps tools" , async ( ) => {
557559 let t = 1000 ;
560+ let finish : ( reply : string ) => void = ( ) => { } ;
558561 const store = createSubAgentSessionStore ( {
559562 now : ( ) => t ,
560563 createId : ( ) => "s-send" ,
@@ -568,19 +571,82 @@ describe("interrupt stamps finishedAt once", () => {
568571 store . markRunning ( session . id ) ;
569572 store . appendEvent ( session . id , startCall ( 1 , "call-1" , "run_shell" ) ) ;
570573 store . registerInterrupt ( session . id , ( ) => { } ) ;
571- store . registerFollowup ( session . id , async ( ) => "later" ) ;
574+ store . registerFollowup (
575+ session . id ,
576+ ( ) =>
577+ new Promise < string > ( ( resolve ) => {
578+ finish = resolve ;
579+ } ) ,
580+ ) ;
572581
573582 t = 2500 ;
574583 const outcome = store . sendInputOne ( session . id , "stop that" , { interrupt : true } ) ;
575584 expect ( outcome ) . toEqual ( { ok : true , status : "interrupted" } ) ;
576585 const after = store . get ( session . id ) ;
577586 expect ( after ?. status ) . toBe ( "running" ) ;
578- expect ( after ?. lifecycleStatus ) . toBe ( "interrupted " ) ;
579- expect ( after ?. finishedAt ) . toBe ( 2500 ) ;
587+ expect ( after ?. lifecycleStatus ) . toBe ( "running " ) ;
588+ expect ( after ?. finishedAt ) . toBeUndefined ( ) ;
580589 expect ( after ?. outstandingTools ) . toHaveLength ( 1 ) ;
581590
582591 t = 4000 ;
583592 expect ( store . interruptOne ( session . id ) . ok ) . toBe ( true ) ;
584- expect ( store . get ( session . id ) ?. finishedAt ) . toBe ( 2500 ) ;
593+ expect ( store . get ( session . id ) ?. finishedAt ) . toBe ( 4000 ) ;
594+
595+ t = 5000 ;
596+ finish ( "later" ) ;
597+ await new Promise ( ( resolve ) => setTimeout ( resolve , 0 ) ) ;
598+ expect ( store . get ( session . id ) ?. status ) . toBe ( "done" ) ;
599+ expect ( store . get ( session . id ) ?. lifecycleStatus ) . toBe ( "completed" ) ;
600+ expect ( store . get ( session . id ) ?. finishedAt ) . toBe ( 5000 ) ;
601+ } ) ;
602+
603+ test ( "a follow-up turn keeps the lane live past the linger window until it completes" , async ( ) => {
604+ let t = 1000 ;
605+ let finish : ( reply : string ) => void = ( ) => { } ;
606+ const store = createSubAgentSessionStore ( {
607+ now : ( ) => t ,
608+ createId : ( ) => "s-followup" ,
609+ } ) ;
610+ const session = store . start ( {
611+ description : "looping" ,
612+ agentId : "explorer" ,
613+ brief : "b" ,
614+ retained : true ,
615+ } ) ;
616+ store . markRunning ( session . id ) ;
617+ store . registerInterrupt ( session . id , ( ) => { } ) ;
618+ store . registerFollowup (
619+ session . id ,
620+ ( ) =>
621+ new Promise < string > ( ( resolve ) => {
622+ finish = resolve ;
623+ } ) ,
624+ ) ;
625+
626+ t = 2000 ;
627+ expect ( store . interruptOne ( session . id ) . ok ) . toBe ( true ) ;
628+ expect ( store . get ( session . id ) ?. finishedAt ) . toBe ( 2000 ) ;
629+
630+ t = 3000 ;
631+ const pending = store . followupOne ( session . id , "keep going" ) ;
632+
633+ t = 11_000 ;
634+ store . appendEvent ( session . id , startCall ( 1 , "call-1" , "run_shell" ) ) ;
635+ const live = store . list ( ) ;
636+ expect ( live [ 0 ] ?. lifecycleStatus ) . toBe ( "running" ) ;
637+ expect ( live [ 0 ] ?. finishedAt ) . toBeUndefined ( ) ;
638+ expect ( agentLaneIsLive ( live [ 0 ] ! ) ) . toBe ( true ) ;
639+ expect ( formatAgentsPanel ( live , undefined , t ) ?. [ 0 ] ?. status ) . toBe ( "running" ) ;
640+ expect ( fleetProgress ( live , t ) . running ) . toBe ( 1 ) ;
641+
642+ t = 12_000 ;
643+ finish ( "done" ) ;
644+ expect ( await pending ) . toEqual ( { ok : true , reply : "done" } ) ;
645+ const terminal = store . list ( ) ;
646+ expect ( terminal [ 0 ] ?. status ) . toBe ( "done" ) ;
647+ expect ( terminal [ 0 ] ?. lifecycleStatus ) . toBe ( "completed" ) ;
648+ expect ( terminal [ 0 ] ?. finishedAt ) . toBe ( 12_000 ) ;
649+ expect ( agentLaneIsLive ( terminal [ 0 ] ! ) ) . toBe ( false ) ;
650+ expect ( fleetProgress ( terminal , t ) . running ) . toBe ( 0 ) ;
585651 } ) ;
586652} ) ;
0 commit comments