@@ -533,6 +533,10 @@ describe("AgentRouter.runEvent — JSONL advancement guarantees", () => {
533533 . fn ( )
534534 . mockReturnValueOnce ( 0 )
535535 . mockReturnValueOnce ( 0 ) ,
536+ latestAgentOutcome : vi
537+ . fn ( )
538+ . mockReturnValueOnce ( undefined )
539+ . mockReturnValueOnce ( undefined ) ,
536540 latestAgentMessage : vi
537541 . fn ( )
538542 . mockReturnValueOnce ( undefined )
@@ -576,7 +580,7 @@ describe("AgentRouter.runEvent — JSONL advancement guarantees", () => {
576580 . fn ( )
577581 . mockReturnValueOnce ( 0 )
578582 . mockReturnValueOnce ( 1 ) ,
579- latestAgentMessage : vi
583+ latestAgentOutcome : vi
580584 . fn ( )
581585 . mockReturnValueOnce ( undefined )
582586 . mockReturnValueOnce ( {
@@ -589,6 +593,18 @@ describe("AgentRouter.runEvent — JSONL advancement guarantees", () => {
589593 tokensOut : 7 ,
590594 costUsd : 0.12 ,
591595 } ) ,
596+ latestAgentMessage : vi
597+ . fn ( )
598+ . mockReturnValueOnce ( {
599+ eventId : "evt_new" ,
600+ sessionId : "ses_unused" ,
601+ type : "agent.message" ,
602+ content : "done" ,
603+ createdAt : Date . now ( ) ,
604+ tokensIn : 11 ,
605+ tokensOut : 7 ,
606+ costUsd : 0.12 ,
607+ } ) ,
592608 } ;
593609 const { router, store } = makeRouter ( {
594610 poolStub : {
@@ -612,7 +628,7 @@ describe("AgentRouter.runEvent — JSONL advancement guarantees", () => {
612628 expect ( finished ?. costUsd ) . toBe ( 0.12 ) ;
613629 } ) ;
614630
615- it ( "bakes first- turn model and thinking overrides into spawn options " , async ( ) => {
631+ it ( "keeps the turn successful when a tool result advances but no final agent.message is written " , async ( ) => {
616632 vi . stubGlobal (
617633 "fetch" ,
618634 vi . fn ( ) . mockResolvedValue (
@@ -631,7 +647,63 @@ describe("AgentRouter.runEvent — JSONL advancement guarantees", () => {
631647 . fn ( )
632648 . mockReturnValueOnce ( 0 )
633649 . mockReturnValueOnce ( 1 ) ,
650+ latestAgentOutcome : vi
651+ . fn ( )
652+ . mockReturnValueOnce ( undefined )
653+ . mockReturnValueOnce ( {
654+ eventId : "evt_tool_result" ,
655+ sessionId : "ses_unused" ,
656+ type : "agent.tool_result" ,
657+ content : "Fri Apr 24 17:58:01 UTC 2026" ,
658+ createdAt : Date . now ( ) ,
659+ toolName : "exec" ,
660+ toolCallId : "call-date" ,
661+ } ) ,
634662 latestAgentMessage : vi
663+ . fn ( )
664+ . mockReturnValueOnce ( undefined ) ,
665+ } ;
666+ const { router, store } = makeRouter ( {
667+ poolStub : {
668+ acquireForSession : async ( ) =>
669+ ( { baseUrl : "http://container.test" , token : "tok" } ) as any ,
670+ evictSession : async ( ) => { } ,
671+ } ,
672+ eventReaderStub : fakeEvents as unknown as PiJsonlEventReader ,
673+ } ) ;
674+ const agent = seedAgent ( store ) ;
675+ const session = router . createSession ( agent . agentId ) ;
676+
677+ await router . runEvent ( { sessionId : session . sessionId , content : "what time is it?" } ) ;
678+ await waitForSessionToStopRunning ( store , session . sessionId ) ;
679+
680+ const finished = store . sessions . get ( session . sessionId ) ;
681+ expect ( finished ?. status ) . toBe ( "idle" ) ;
682+ expect ( finished ?. error ) . toBeNull ( ) ;
683+ expect ( finished ?. tokensIn ) . toBe ( 11 ) ;
684+ expect ( finished ?. tokensOut ) . toBe ( 7 ) ;
685+ } ) ;
686+
687+ it ( "bakes first-turn model and thinking overrides into spawn options" , async ( ) => {
688+ vi . stubGlobal (
689+ "fetch" ,
690+ vi . fn ( ) . mockResolvedValue (
691+ new Response (
692+ JSON . stringify ( {
693+ choices : [ { message : { content : "done" } } ] ,
694+ usage : { prompt_tokens : 11 , completion_tokens : 7 } ,
695+ } ) ,
696+ { status : 200 , headers : { "content-type" : "application/json" } } ,
697+ ) ,
698+ ) ,
699+ ) ;
700+ const fakeEvents = {
701+ stateRoot : "/tmp/test-state" ,
702+ countUserTurns : vi
703+ . fn ( )
704+ . mockReturnValueOnce ( 0 )
705+ . mockReturnValueOnce ( 1 ) ,
706+ latestAgentOutcome : vi
635707 . fn ( )
636708 . mockReturnValueOnce ( undefined )
637709 . mockReturnValueOnce ( {
@@ -643,6 +715,17 @@ describe("AgentRouter.runEvent — JSONL advancement guarantees", () => {
643715 tokensIn : 11 ,
644716 tokensOut : 7 ,
645717 } ) ,
718+ latestAgentMessage : vi
719+ . fn ( )
720+ . mockReturnValueOnce ( {
721+ eventId : "evt_new" ,
722+ sessionId : "ses_unused" ,
723+ type : "agent.message" ,
724+ content : "done" ,
725+ createdAt : Date . now ( ) ,
726+ tokensIn : 11 ,
727+ tokensOut : 7 ,
728+ } ) ,
646729 } ;
647730 let capturedSpawnOptions : unknown ;
648731 const { router, store } = makeRouter ( {
@@ -701,9 +784,26 @@ describe("AgentRouter.runEvent — JSONL advancement guarantees", () => {
701784 . fn ( )
702785 . mockReturnValueOnce ( 1 )
703786 . mockReturnValueOnce ( 2 ) ,
787+ latestAgentOutcome : vi
788+ . fn ( )
789+ . mockReturnValueOnce ( {
790+ eventId : "evt_old" ,
791+ sessionId : "ses_unused" ,
792+ type : "agent.message" ,
793+ content : "old" ,
794+ createdAt : 1 ,
795+ } )
796+ . mockReturnValueOnce ( {
797+ eventId : "evt_new" ,
798+ sessionId : "ses_unused" ,
799+ type : "agent.message" ,
800+ content : "done" ,
801+ createdAt : Date . now ( ) ,
802+ tokensIn : 11 ,
803+ tokensOut : 7 ,
804+ } ) ,
704805 latestAgentMessage : vi
705806 . fn ( )
706- . mockReturnValueOnce ( { eventId : "evt_old" , sessionId : "ses_unused" , type : "agent.message" , content : "old" , createdAt : 1 } )
707807 . mockReturnValueOnce ( {
708808 eventId : "evt_new" ,
709809 sessionId : "ses_unused" ,
@@ -782,7 +882,7 @@ describe("AgentRouter.runEvent — JSONL advancement guarantees", () => {
782882 . fn ( )
783883 . mockReturnValueOnce ( 0 )
784884 . mockReturnValueOnce ( 1 ) ,
785- latestAgentMessage : vi
885+ latestAgentOutcome : vi
786886 . fn ( )
787887 . mockReturnValueOnce ( undefined )
788888 . mockReturnValueOnce ( {
@@ -795,6 +895,18 @@ describe("AgentRouter.runEvent — JSONL advancement guarantees", () => {
795895 tokensOut : 45 ,
796896 costUsd : 0.42 ,
797897 } ) ,
898+ latestAgentMessage : vi
899+ . fn ( )
900+ . mockReturnValueOnce ( {
901+ eventId : "evt_new" ,
902+ sessionId : "ses_unused" ,
903+ type : "agent.message" ,
904+ content : "done" ,
905+ createdAt : Date . now ( ) ,
906+ tokensIn : 321 ,
907+ tokensOut : 45 ,
908+ costUsd : 0.42 ,
909+ } ) ,
798910 } ;
799911 const { router, store } = makeRouter ( {
800912 poolStub : {
@@ -836,7 +948,7 @@ describe("AgentRouter.runEvent — JSONL advancement guarantees", () => {
836948 . fn ( )
837949 . mockReturnValueOnce ( 0 )
838950 . mockReturnValueOnce ( 1 ) ,
839- latestAgentMessage : vi
951+ latestAgentOutcome : vi
840952 . fn ( )
841953 . mockReturnValueOnce ( undefined )
842954 . mockReturnValueOnce ( {
@@ -847,6 +959,16 @@ describe("AgentRouter.runEvent — JSONL advancement guarantees", () => {
847959 createdAt : Date . now ( ) ,
848960 costUsd : 0.05 ,
849961 } ) ,
962+ latestAgentMessage : vi
963+ . fn ( )
964+ . mockReturnValueOnce ( {
965+ eventId : "evt_new" ,
966+ sessionId : "ses_unused" ,
967+ type : "agent.message" ,
968+ content : "done" ,
969+ createdAt : Date . now ( ) ,
970+ costUsd : 0.05 ,
971+ } ) ,
850972 } ;
851973 const { router, store } = makeRouter ( {
852974 poolStub : {
@@ -909,7 +1031,7 @@ describe("AgentRouter.runEvent — JSONL advancement guarantees", () => {
9091031 . fn ( )
9101032 . mockReturnValueOnce ( 0 )
9111033 . mockReturnValueOnce ( 1 ) ,
912- latestAgentMessage : vi
1034+ latestAgentOutcome : vi
9131035 . fn ( )
9141036 . mockReturnValueOnce ( undefined )
9151037 . mockReturnValueOnce ( {
@@ -922,6 +1044,18 @@ describe("AgentRouter.runEvent — JSONL advancement guarantees", () => {
9221044 tokensOut : 45 ,
9231045 model : "zenmux/openai/gpt-5.4" ,
9241046 } ) ,
1047+ latestAgentMessage : vi
1048+ . fn ( )
1049+ . mockReturnValueOnce ( {
1050+ eventId : "evt_new" ,
1051+ sessionId : "ses_unused" ,
1052+ type : "agent.message" ,
1053+ content : "done" ,
1054+ createdAt : Date . now ( ) ,
1055+ tokensIn : 321 ,
1056+ tokensOut : 45 ,
1057+ model : "zenmux/openai/gpt-5.4" ,
1058+ } ) ,
9251059 } ;
9261060 const { router, store } = makeRouter ( {
9271061 poolStub : {
0 commit comments