@@ -1547,3 +1547,110 @@ describe("session.message-v2.fromError", () => {
15471547 expect ( result . name ) . toBe ( "MessageAbortedError" )
15481548 } )
15491549} )
1550+
1551+ describe ( "session.message-v2.latest" , ( ) => {
1552+ const TAIL_USER = MessageID . make ( "msg_001" )
1553+ const OVERFLOW_ASSISTANT = MessageID . make ( "msg_002" )
1554+ const COMPACTION_USER = MessageID . make ( "msg_003" )
1555+ const SUMMARY_ASSISTANT = MessageID . make ( "msg_004" )
1556+ const CONTINUE_USER = MessageID . make ( "msg_005" )
1557+ const NEW_COMPACTION_USER = MessageID . make ( "msg_006" )
1558+
1559+ const tailUser : MessageV2 . WithParts = {
1560+ info : userInfo ( TAIL_USER ) ,
1561+ parts : [ { ...basePart ( TAIL_USER , "p1" ) , type : "text" , text : "original prompt" } ] as MessageV2 . Part [ ] ,
1562+ }
1563+
1564+ const overflowAssistant : MessageV2 . WithParts = {
1565+ info : {
1566+ ...assistantInfo ( OVERFLOW_ASSISTANT , TAIL_USER ) ,
1567+ finish : "tool-calls" ,
1568+ tokens : { input : 280_000 , output : 200 , reasoning : 0 , cache : { read : 0 , write : 0 } , total : 280_200 } ,
1569+ } as MessageV2 . Assistant ,
1570+ parts : [ ] ,
1571+ }
1572+
1573+ const compactionUser : MessageV2 . WithParts = {
1574+ info : userInfo ( COMPACTION_USER ) ,
1575+ parts : [
1576+ {
1577+ ...basePart ( COMPACTION_USER , "p1" ) ,
1578+ type : "compaction" ,
1579+ auto : true ,
1580+ tail_start_id : TAIL_USER ,
1581+ } ,
1582+ ] as MessageV2 . Part [ ] ,
1583+ }
1584+
1585+ const summaryAssistant : MessageV2 . WithParts = {
1586+ info : {
1587+ ...assistantInfo ( SUMMARY_ASSISTANT , COMPACTION_USER ) ,
1588+ summary : true ,
1589+ finish : "stop" ,
1590+ tokens : { input : 150_000 , output : 1_500 , reasoning : 0 , cache : { read : 0 , write : 0 } , total : 151_500 } ,
1591+ } as MessageV2 . Assistant ,
1592+ parts : [ ] ,
1593+ }
1594+
1595+ const continueUser : MessageV2 . WithParts = {
1596+ info : userInfo ( CONTINUE_USER ) ,
1597+ parts : [
1598+ {
1599+ ...basePart ( CONTINUE_USER , "p1" ) ,
1600+ type : "text" ,
1601+ text : "Continue if you have next steps..." ,
1602+ synthetic : true ,
1603+ metadata : { compaction_continue : true } ,
1604+ } ,
1605+ ] as MessageV2 . Part [ ] ,
1606+ }
1607+
1608+ // Regression for double auto-compaction. The reorder in filterCompacted
1609+ // (#27145) returns [compaction-user, summary, ...tail..., continue-user],
1610+ // so picking lastFinished by array position landed on the pre-compaction
1611+ // overflow assistant and bypassed the `summary !== true` overflow guard
1612+ // in SessionPrompt.runLoop, firing a second compaction.create immediately.
1613+ test ( "finished is the chronologically-latest finished assistant, not the array-latest" , ( ) => {
1614+ const filtered = MessageV2 . filterCompacted ( [
1615+ continueUser ,
1616+ summaryAssistant ,
1617+ compactionUser ,
1618+ overflowAssistant ,
1619+ tailUser ,
1620+ ] )
1621+
1622+ const state = MessageV2 . latest ( filtered )
1623+
1624+ expect ( state . finished ?. id ) . toBe ( SUMMARY_ASSISTANT )
1625+ expect ( state . finished ?. summary ) . toBe ( true )
1626+ expect ( state . user ?. id ) . toBe ( CONTINUE_USER )
1627+ expect ( state . tasks ) . toEqual ( [ ] )
1628+ } )
1629+
1630+ test ( "a fresh compaction-user newer than the latest summary surfaces in tasks" , ( ) => {
1631+ const newCompactionUser : MessageV2 . WithParts = {
1632+ info : userInfo ( NEW_COMPACTION_USER ) ,
1633+ parts : [
1634+ {
1635+ ...basePart ( NEW_COMPACTION_USER , "p1" ) ,
1636+ type : "compaction" ,
1637+ auto : true ,
1638+ } ,
1639+ ] as MessageV2 . Part [ ] ,
1640+ }
1641+
1642+ const state = MessageV2 . latest ( [
1643+ tailUser ,
1644+ overflowAssistant ,
1645+ compactionUser ,
1646+ summaryAssistant ,
1647+ continueUser ,
1648+ newCompactionUser ,
1649+ ] )
1650+
1651+ expect ( state . finished ?. id ) . toBe ( SUMMARY_ASSISTANT )
1652+ expect ( state . user ?. id ) . toBe ( NEW_COMPACTION_USER )
1653+ expect ( state . tasks ) . toHaveLength ( 1 )
1654+ expect ( state . tasks [ 0 ] ) . toMatchObject ( { type : "compaction" , auto : true } )
1655+ } )
1656+ } )
0 commit comments