@@ -1565,6 +1565,166 @@ describe("fleet-dry open-task drive (CL-7540)", () => {
15651565 { width : 80 , height : 24 } ,
15661566 ) ;
15671567 } ) ;
1568+
1569+ test ( "occupancy send failure re-arms, clears continuation hold, and drains follow-ups" , async ( ) => {
1570+ await withTestRenderer (
1571+ async ( h ) => {
1572+ const shell = createAppShell ( h . renderer , {
1573+ terminal : { columns : 80 , rows : 24 } ,
1574+ wireKeys : false ,
1575+ run : "idle" ,
1576+ } ) ;
1577+ const port = createRecordingPort ( ) ;
1578+ const bridge = attachSessionBridge ( shell , port ) ;
1579+ try {
1580+ const prompt = "The fleet has gone dry. Remaining open tasks:\n- t1: keep going (todo)\n" ;
1581+ let drives = 0 ;
1582+ bridge . setDryOpenTaskDriver ( ( ) => {
1583+ drives += 1 ;
1584+ bridge . beginSystemContinuation ( prompt ) ;
1585+ if ( drives === 1 ) {
1586+ void Promise . resolve ( ) . then ( ( ) => {
1587+ bridge . abortSystemContinuation ( ) ;
1588+ } ) ;
1589+ }
1590+ return true ;
1591+ } ) ;
1592+ bridge . submit ( "dispatch workers" , "immediate" ) ;
1593+ bridge . handle ( { type : "fleet" , running : 1 } ) ;
1594+ settleToollessTurn ( bridge ) ;
1595+ expect ( shell . session . run ) . toBe ( "busy" ) ;
1596+ bridge . handle ( { type : "fleet" , running : 0 } ) ;
1597+ expect ( drives ) . toBe ( 1 ) ;
1598+ expect ( shell . session . run ) . toBe ( "busy" ) ;
1599+ bridge . submit ( "when it finishes, summarize" , "queue" ) ;
1600+ expect ( badgeCount ( shell . session ) ) . toBe ( 1 ) ;
1601+ port . clear ( ) ;
1602+ await Promise . resolve ( ) ;
1603+ expect ( shell . session . run ) . toBe ( "idle" ) ;
1604+ expect ( badgeCount ( shell . session ) ) . toBe ( 0 ) ;
1605+ const deliver = port . calls . find ( ( c ) => c . op === "deliver" ) ;
1606+ expect ( deliver ) . toEqual ( {
1607+ op : "deliver" ,
1608+ item : expect . objectContaining ( {
1609+ text : "when it finishes, summarize" ,
1610+ kind : "queue" ,
1611+ } ) ,
1612+ } ) ;
1613+ bridge . handle ( { type : "connector.reply" , data : { content : "" } } ) ;
1614+ expect ( shell . session . run ) . toBe ( "idle" ) ;
1615+ bridge . submit ( "continue the remaining work" , "immediate" ) ;
1616+ expect ( shell . session . run ) . toBe ( "busy" ) ;
1617+ settleToollessTurn ( bridge ) ;
1618+ expect ( drives ) . toBe ( 2 ) ;
1619+ expect ( shell . session . run ) . toBe ( "busy" ) ;
1620+ } finally {
1621+ bridge . dispose ( ) ;
1622+ shell . dispose ( ) ;
1623+ }
1624+ } ,
1625+ { width : 80 , height : 24 } ,
1626+ ) ;
1627+ } ) ;
1628+
1629+ test ( "occupancy send abort resets the turn without a following reply" , async ( ) => {
1630+ await withTestRenderer (
1631+ async ( h ) => {
1632+ const shell = createAppShell ( h . renderer , {
1633+ terminal : { columns : 80 , rows : 24 } ,
1634+ wireKeys : false ,
1635+ run : "idle" ,
1636+ } ) ;
1637+ const port = createRecordingPort ( ) ;
1638+ const nowMs = 0 ;
1639+ let tick : ( ( ) => void ) | undefined ;
1640+ const prompt = "The fleet has gone dry. Remaining open tasks:\n- t1: keep going (todo)\n" ;
1641+ const bridge = attachSessionBridge ( shell , port , {
1642+ now : ( ) => nowMs ,
1643+ stallNoticeMs : 400 ,
1644+ stallTimeoutMs : 1_000 ,
1645+ schedule : ( fn ) => {
1646+ tick = fn ;
1647+ return ( ) => {
1648+ tick = undefined ;
1649+ } ;
1650+ } ,
1651+ } ) ;
1652+ try {
1653+ bridge . setDryOpenTaskDriver ( ( ) => {
1654+ bridge . beginSystemContinuation ( prompt ) ;
1655+ void Promise . resolve ( ) . then ( ( ) => {
1656+ bridge . abortSystemContinuation ( ) ;
1657+ } ) ;
1658+ return true ;
1659+ } ) ;
1660+ bridge . submit ( "dispatch workers" , "immediate" ) ;
1661+ bridge . handle ( { type : "fleet" , running : 1 } ) ;
1662+ settleToollessTurn ( bridge ) ;
1663+ bridge . handle ( { type : "fleet" , running : 0 } ) ;
1664+ expect ( bridge . turn . isProcessing ) . toBe ( true ) ;
1665+ expect ( tick ) . toBeDefined ( ) ;
1666+ await Promise . resolve ( ) ;
1667+ expect ( bridge . turn . isProcessing ) . toBe ( false ) ;
1668+ expect ( bridge . turn . awaitingResponse ) . toBe ( false ) ;
1669+ expect ( bridge . turn . status ) . not . toBe ( "running" ) ;
1670+ expect ( shell . lockupPhase ) . toBeNull ( ) ;
1671+ expect ( tick ) . toBeUndefined ( ) ;
1672+ } finally {
1673+ bridge . dispose ( ) ;
1674+ shell . dispose ( ) ;
1675+ }
1676+ } ,
1677+ { width : 80 , height : 24 } ,
1678+ ) ;
1679+ } ) ;
1680+
1681+ test ( "occupancy continuation is what quota auto-retry resubmits" , async ( ) => {
1682+ await withTestRenderer (
1683+ async ( h ) => {
1684+ const shell = createAppShell ( h . renderer , {
1685+ terminal : { columns : 80 , rows : 24 } ,
1686+ wireKeys : false ,
1687+ run : "idle" ,
1688+ } ) ;
1689+ const port = createRecordingPort ( ) ;
1690+ let nowMs = 0 ;
1691+ let tick : ( ( ) => void ) | undefined ;
1692+ const continuation =
1693+ "The fleet has gone dry. Remaining open tasks:\n- t1: keep going (todo)\n" ;
1694+ const bridge = attachSessionBridge ( shell , port , {
1695+ now : ( ) => nowMs ,
1696+ schedule : ( fn ) => {
1697+ tick = fn ;
1698+ return ( ) => {
1699+ tick = undefined ;
1700+ } ;
1701+ } ,
1702+ } ) ;
1703+ try {
1704+ bridge . setDryOpenTaskDriver ( ( ) => {
1705+ bridge . beginSystemContinuation ( continuation ) ;
1706+ return true ;
1707+ } ) ;
1708+ bridge . submit ( "dispatch workers" , "immediate" ) ;
1709+ bridge . handle ( { type : "fleet" , running : 1 } ) ;
1710+ settleToollessTurn ( bridge ) ;
1711+ bridge . handle ( { type : "fleet" , running : 0 } ) ;
1712+ port . clear ( ) ;
1713+ bridge . handle ( {
1714+ type : "inference.error" ,
1715+ data : { error : { category : "quota_exhausted" , retryAfterMs : 1_000 } } ,
1716+ } ) ;
1717+ nowMs += 10_000 ;
1718+ tick ?.( ) ;
1719+ expect ( port . calls ) . toEqual ( [ { op : "sendImmediate" , text : continuation . trim ( ) } ] ) ;
1720+ } finally {
1721+ bridge . dispose ( ) ;
1722+ shell . dispose ( ) ;
1723+ }
1724+ } ,
1725+ { width : 80 , height : 24 } ,
1726+ ) ;
1727+ } ) ;
15681728} ) ;
15691729
15701730describe ( "syncAgentProgress" , ( ) => {
0 commit comments