@@ -4,6 +4,7 @@ import { hasCommittedMessagingToolDeliveryEvidence } from "./delivery-evidence.j
44import { makeAttemptResult } from "./run.overflow-compaction.fixture.js" ;
55import {
66 loadRunOverflowCompactionHarness ,
7+ mockedBuildEmbeddedRunPayloads ,
78 mockedClassifyFailoverReason ,
89 mockedGlobalHookRunner ,
910 mockedLog ,
@@ -1181,6 +1182,37 @@ describe("runEmbeddedPiAgent incomplete-turn safety", () => {
11811182 expect ( incompleteTurnText ) . toBeNull ( ) ;
11821183 } ) ;
11831184
1185+ it ( "uses current attempt assistant state instead of stale lastAssistant for incomplete-turn detection (#80918)" , ( ) => {
1186+ const incompleteTurnText = resolveIncompleteTurnPayloadText ( {
1187+ payloadCount : 1 ,
1188+ aborted : false ,
1189+ timedOut : false ,
1190+ attempt : makeAttemptResult ( {
1191+ assistantTexts : [ "Compression complete and verified. Here is the summary." ] ,
1192+ toolMetas : [ { toolName : "update_plan" } ] ,
1193+ lastAssistant : {
1194+ role : "assistant" ,
1195+ stopReason : "toolUse" ,
1196+ provider : "openrouter" ,
1197+ model : "deepseek/deepseek-v4-pro" ,
1198+ content : [ { type : "tool_use" , id : "tool_1" , name : "update_plan" , input : { } } ] ,
1199+ } as unknown as EmbeddedRunAttemptResult [ "lastAssistant" ] ,
1200+ currentAttemptAssistant : {
1201+ role : "assistant" ,
1202+ stopReason : "stop" ,
1203+ provider : "openrouter" ,
1204+ model : "deepseek/deepseek-v4-pro" ,
1205+ content : [
1206+ { type : "thinking" , thinking : "update_plan returned successfully" } ,
1207+ { type : "text" , text : "Compression complete and verified. Here is the summary." } ,
1208+ ] ,
1209+ } as unknown as EmbeddedRunAttemptResult [ "currentAttemptAssistant" ] ,
1210+ } ) ,
1211+ } ) ;
1212+
1213+ expect ( incompleteTurnText ) . toBeNull ( ) ;
1214+ } ) ;
1215+
11841216 it ( "surfaces an error for tool-use terminal turn with pre-tool text via runEmbeddedPiAgent (#76477)" , async ( ) => {
11851217 mockedClassifyFailoverReason . mockReturnValue ( null ) ;
11861218 mockedRunEmbeddedAttempt . mockResolvedValueOnce (
@@ -1212,6 +1244,56 @@ describe("runEmbeddedPiAgent incomplete-turn safety", () => {
12121244 expectWarnMessageWith ( "incomplete turn detected" ) ;
12131245 } ) ;
12141246
1247+ it ( "preserves final assistant text when lastAssistant is stale after update_plan (#80918)" , async ( ) => {
1248+ mockedClassifyFailoverReason . mockReturnValue ( null ) ;
1249+ mockedBuildEmbeddedRunPayloads . mockReturnValueOnce ( [
1250+ { text : "Compression complete and verified. Here is the summary." } ,
1251+ ] ) ;
1252+ const currentAttemptAssistant = {
1253+ role : "assistant" ,
1254+ stopReason : "stop" ,
1255+ provider : "openrouter" ,
1256+ model : "deepseek/deepseek-v4-pro" ,
1257+ content : [
1258+ { type : "thinking" , thinking : "update_plan returned successfully" } ,
1259+ { type : "text" , text : "Compression complete and verified. Here is the summary." } ,
1260+ ] ,
1261+ } as unknown as EmbeddedRunAttemptResult [ "currentAttemptAssistant" ] ;
1262+ mockedRunEmbeddedAttempt . mockResolvedValueOnce (
1263+ makeAttemptResult ( {
1264+ assistantTexts : [ "Compression complete and verified. Here is the summary." ] ,
1265+ toolMetas : [ { toolName : "update_plan" } ] ,
1266+ lastAssistant : {
1267+ stopReason : "toolUse" ,
1268+ provider : "openrouter" ,
1269+ model : "deepseek/deepseek-v4-pro" ,
1270+ content : [ { type : "tool_use" , id : "tool_1" , name : "update_plan" , input : { } } ] ,
1271+ } as unknown as EmbeddedRunAttemptResult [ "lastAssistant" ] ,
1272+ currentAttemptAssistant,
1273+ } ) ,
1274+ ) ;
1275+
1276+ const result = await runEmbeddedPiAgent ( {
1277+ ...overflowBaseRunParams ,
1278+ provider : "openrouter" ,
1279+ model : "deepseek/deepseek-v4-pro" ,
1280+ runId : "run-update-plan-stale-last-assistant" ,
1281+ } ) ;
1282+
1283+ expect ( mockedRunEmbeddedAttempt ) . toHaveBeenCalledTimes ( 1 ) ;
1284+ expect ( mockedBuildEmbeddedRunPayloads ) . toHaveBeenCalledWith (
1285+ expect . objectContaining ( { lastAssistant : currentAttemptAssistant } ) ,
1286+ ) ;
1287+ expect ( result . payloads ?. [ 0 ] ) . toMatchObject ( {
1288+ text : "Compression complete and verified. Here is the summary." ,
1289+ } ) ;
1290+ expect ( result . payloads ?. [ 0 ] ?. isError ) . not . toBe ( true ) ;
1291+ expectNoWarnMessageWith ( "incomplete turn detected" ) ;
1292+ expect ( result . meta ?. finalAssistantVisibleText ) . toBe (
1293+ "Compression complete and verified. Here is the summary." ,
1294+ ) ;
1295+ } ) ;
1296+
12151297 it ( "treats missing replay metadata as replay-invalid" , ( ) => {
12161298 const attempt = makeAttemptResult ( ) ;
12171299 delete ( attempt as Partial < EmbeddedRunAttemptResult > ) . replayMetadata ;
0 commit comments