@@ -1709,6 +1709,193 @@ describe("fleet-dry open-task drive (CL-7540)", () => {
17091709 ) ;
17101710 } ) ;
17111711
1712+ test ( "already-dry settle drives once even without a live 1→0 fleet event" , async ( ) => {
1713+ await withTestRenderer (
1714+ async ( h ) => {
1715+ const shell = createAppShell ( h . renderer , {
1716+ terminal : { columns : 80 , rows : 24 } ,
1717+ wireKeys : false ,
1718+ run : "idle" ,
1719+ } ) ;
1720+ const port = createRecordingPort ( ) ;
1721+ const bridge = attachSessionBridge ( shell , port ) ;
1722+ try {
1723+ const prompt =
1724+ "The fleet has gone dry. Remaining open tasks:\n- t1: keep going (todo)\n" ;
1725+ let drives = 0 ;
1726+ bridge . setDryOpenTaskDriver ( ( ) => {
1727+ drives += 1 ;
1728+ bridge . beginSystemContinuation ( prompt ) ;
1729+ return true ;
1730+ } ) ;
1731+ bridge . submit ( "dispatch workers" , "immediate" ) ;
1732+ bridge . handle ( { type : "fleet" , running : 0 } ) ;
1733+ expect ( drives ) . toBe ( 0 ) ;
1734+ expect ( shell . session . run ) . toBe ( "busy" ) ;
1735+ settleToollessTurn ( bridge ) ;
1736+ expect ( drives ) . toBe ( 1 ) ;
1737+ expect ( shell . session . run ) . toBe ( "busy" ) ;
1738+ settleToollessTurn ( bridge ) ;
1739+ expect ( drives ) . toBe ( 1 ) ;
1740+ } finally {
1741+ bridge . dispose ( ) ;
1742+ shell . dispose ( ) ;
1743+ }
1744+ } ,
1745+ { width : 80 , height : 24 } ,
1746+ ) ;
1747+ } ) ;
1748+
1749+ test ( "a no-op dry settle does not eat the shot for a later missed 1→0 with open tasks" , async ( ) => {
1750+ await withTestRenderer (
1751+ async ( h ) => {
1752+ const shell = createAppShell ( h . renderer , {
1753+ terminal : { columns : 80 , rows : 24 } ,
1754+ wireKeys : false ,
1755+ run : "idle" ,
1756+ } ) ;
1757+ const port = createRecordingPort ( ) ;
1758+ const bridge = attachSessionBridge ( shell , port ) ;
1759+ try {
1760+ const prompt =
1761+ "The fleet has gone dry. Remaining open tasks:\n- t1: keep going (todo)\n" ;
1762+ let openTasks = false ;
1763+ let drives = 0 ;
1764+ bridge . setDryOpenTaskDriver ( ( ) => {
1765+ if ( ! openTasks ) return false ;
1766+ drives += 1 ;
1767+ bridge . beginSystemContinuation ( prompt ) ;
1768+ return true ;
1769+ } ) ;
1770+ bridge . submit ( "first turn, no todos" , "immediate" ) ;
1771+ bridge . handle ( { type : "fleet" , running : 0 } ) ;
1772+ settleToollessTurn ( bridge ) ;
1773+ expect ( drives ) . toBe ( 0 ) ;
1774+ expect ( shell . session . run ) . toBe ( "idle" ) ;
1775+
1776+ openTasks = true ;
1777+ bridge . submit ( "dispatch workers" , "immediate" ) ;
1778+ bridge . handle ( { type : "fleet" , running : 0 } ) ;
1779+ expect ( drives ) . toBe ( 0 ) ;
1780+ expect ( shell . session . run ) . toBe ( "busy" ) ;
1781+ settleToollessTurn ( bridge ) ;
1782+ expect ( drives ) . toBe ( 1 ) ;
1783+ expect ( shell . session . run ) . toBe ( "busy" ) ;
1784+ settleToollessTurn ( bridge ) ;
1785+ expect ( drives ) . toBe ( 1 ) ;
1786+ } finally {
1787+ bridge . dispose ( ) ;
1788+ shell . dispose ( ) ;
1789+ }
1790+ } ,
1791+ { width : 80 , height : 24 } ,
1792+ ) ;
1793+ } ) ;
1794+
1795+ test ( "a new live lane resets the dry-episode latch for one more occupancy shot" , async ( ) => {
1796+ await withTestRenderer (
1797+ async ( h ) => {
1798+ const shell = createAppShell ( h . renderer , {
1799+ terminal : { columns : 80 , rows : 24 } ,
1800+ wireKeys : false ,
1801+ run : "idle" ,
1802+ } ) ;
1803+ const port = createRecordingPort ( ) ;
1804+ const bridge = attachSessionBridge ( shell , port ) ;
1805+ try {
1806+ const prompt =
1807+ "The fleet has gone dry. Remaining open tasks:\n- t1: keep going (todo)\n" ;
1808+ let drives = 0 ;
1809+ bridge . setDryOpenTaskDriver ( ( ) => {
1810+ drives += 1 ;
1811+ bridge . beginSystemContinuation ( prompt ) ;
1812+ return true ;
1813+ } ) ;
1814+ bridge . submit ( "dispatch workers" , "immediate" ) ;
1815+ settleToollessTurn ( bridge ) ;
1816+ expect ( drives ) . toBe ( 1 ) ;
1817+ expect ( shell . session . run ) . toBe ( "busy" ) ;
1818+ settleToollessTurn ( bridge ) ;
1819+ expect ( drives ) . toBe ( 1 ) ;
1820+ expect ( shell . session . run ) . toBe ( "idle" ) ;
1821+
1822+ bridge . submit ( "dispatch more workers" , "immediate" ) ;
1823+ bridge . handle ( { type : "fleet" , running : 1 } ) ;
1824+ settleToollessTurn ( bridge ) ;
1825+ expect ( drives ) . toBe ( 1 ) ;
1826+ expect ( shell . session . run ) . toBe ( "busy" ) ;
1827+ bridge . handle ( { type : "fleet" , running : 0 } ) ;
1828+ expect ( drives ) . toBe ( 2 ) ;
1829+ expect ( shell . session . run ) . toBe ( "busy" ) ;
1830+ } finally {
1831+ bridge . dispose ( ) ;
1832+ shell . dispose ( ) ;
1833+ }
1834+ } ,
1835+ { width : 80 , height : 24 } ,
1836+ ) ;
1837+ } ) ;
1838+
1839+ test ( "parent still processing does not take the occupancy shot" , async ( ) => {
1840+ await withTestRenderer (
1841+ async ( h ) => {
1842+ const shell = createAppShell ( h . renderer , {
1843+ terminal : { columns : 80 , rows : 24 } ,
1844+ wireKeys : false ,
1845+ run : "idle" ,
1846+ } ) ;
1847+ const port = createRecordingPort ( ) ;
1848+ const bridge = attachSessionBridge ( shell , port ) ;
1849+ try {
1850+ let drives = 0 ;
1851+ bridge . setDryOpenTaskDriver ( ( ) => {
1852+ drives += 1 ;
1853+ return true ;
1854+ } ) ;
1855+ bridge . submit ( "dispatch workers" , "immediate" ) ;
1856+ bridge . handle ( { type : "fleet" , running : 0 } ) ;
1857+ expect ( drives ) . toBe ( 0 ) ;
1858+ expect ( shell . session . run ) . toBe ( "busy" ) ;
1859+ expect ( bridge . turn . isProcessing ) . toBe ( true ) ;
1860+ } finally {
1861+ bridge . dispose ( ) ;
1862+ shell . dispose ( ) ;
1863+ }
1864+ } ,
1865+ { width : 80 , height : 24 } ,
1866+ ) ;
1867+ } ) ;
1868+
1869+ test ( "live fleet still blocks the occupancy drive" , async ( ) => {
1870+ await withTestRenderer (
1871+ async ( h ) => {
1872+ const shell = createAppShell ( h . renderer , {
1873+ terminal : { columns : 80 , rows : 24 } ,
1874+ wireKeys : false ,
1875+ run : "idle" ,
1876+ } ) ;
1877+ const port = createRecordingPort ( ) ;
1878+ const bridge = attachSessionBridge ( shell , port ) ;
1879+ try {
1880+ let drives = 0 ;
1881+ bridge . setDryOpenTaskDriver ( ( ) => {
1882+ drives += 1 ;
1883+ return true ;
1884+ } ) ;
1885+ bridge . submit ( "dispatch workers" , "immediate" ) ;
1886+ bridge . handle ( { type : "fleet" , running : 1 } ) ;
1887+ settleToollessTurn ( bridge ) ;
1888+ expect ( drives ) . toBe ( 0 ) ;
1889+ expect ( shell . session . run ) . toBe ( "busy" ) ;
1890+ } finally {
1891+ bridge . dispose ( ) ;
1892+ shell . dispose ( ) ;
1893+ }
1894+ } ,
1895+ { width : 80 , height : 24 } ,
1896+ ) ;
1897+ } ) ;
1898+
17121899 test ( "occupancy send failure re-arms, clears continuation hold, and drains follow-ups" , async ( ) => {
17131900 await withTestRenderer (
17141901 async ( h ) => {
0 commit comments