@@ -3,7 +3,9 @@ import { createFleetMailbox } from "./agent-fleet.js";
33import {
44 buildMailboxMailPrompt ,
55 driveMailboxMail ,
6+ latchMailboxMailDrive ,
67 MAILBOX_MAIL_WAKE_PREFIX ,
8+ mailboxMailWakeLine ,
79 occupancyShouldYieldWait ,
810} from "./mailbox-mail-drive.js" ;
911import { createSubAgentSessionStore } from "./session-store.js" ;
@@ -41,9 +43,8 @@ describe("buildMailboxMailPrompt", () => {
4143 expect ( prompt . startsWith ( MAILBOX_MAIL_WAKE_PREFIX ) ) . toBe ( true ) ;
4244 expect ( prompt ) . toContain ( "worker-1" ) ;
4345 expect ( prompt ) . toContain ( "shipped" ) ;
44- expect ( prompt ) . toContain (
45- "already collected — do not call wait_agents for these agent_ids" ,
46- ) ;
46+ expect ( prompt ) . toContain ( mailboxMailWakeLine ( ) ) ;
47+ expect ( prompt ) . not . toContain ( "already collected" ) ;
4748 } ) ;
4849} ) ;
4950
@@ -211,6 +212,80 @@ describe("driveMailboxMail", () => {
211212 expect ( records . get ( "w1" ) ?. collected ) . toBe ( true ) ;
212213 } ) ;
213214
215+ test ( "two flushes while send is pending deliver once" , async ( ) => {
216+ const records = new Map < string , FleetDryMailboxRecord > ( [
217+ [ "w1" , { status : "done" , report : "ok" } ] ,
218+ ] ) ;
219+ const mailbox = mapMailbox ( records ) ;
220+ const sends : string [ ] = [ ] ;
221+ let resolveSend : ( ( ok : boolean ) => void ) | undefined ;
222+ const driven = await driveMailboxMail ( {
223+ parentProcessing : false ,
224+ mailbox,
225+ lanes : [ ] ,
226+ beginSystemContinuation : ( ) => undefined ,
227+ send : ( prompt ) => {
228+ sends . push ( prompt ) ;
229+ return new Promise < boolean > ( ( resolve ) => {
230+ resolveSend = resolve ;
231+ } ) ;
232+ } ,
233+ } ) ;
234+ expect ( driven ) . toBe ( true ) ;
235+ expect ( sends ) . toHaveLength ( 1 ) ;
236+ expect ( records . get ( "w1" ) ?. collected ) . not . toBe ( true ) ;
237+ expect (
238+ await driveMailboxMail ( {
239+ parentProcessing : false ,
240+ mailbox,
241+ lanes : [ ] ,
242+ beginSystemContinuation : ( ) => {
243+ throw new Error ( "must not begin" ) ;
244+ } ,
245+ send : ( ) => {
246+ throw new Error ( "must not send" ) ;
247+ } ,
248+ } ) ,
249+ ) . toBe ( false ) ;
250+ expect ( sends ) . toHaveLength ( 1 ) ;
251+ resolveSend ?.( true ) ;
252+ await Promise . resolve ( ) ;
253+ expect ( records . get ( "w1" ) ?. collected ) . toBe ( true ) ;
254+ } ) ;
255+
256+ test ( "failed send can retry once" , async ( ) => {
257+ const records = new Map < string , FleetDryMailboxRecord > ( [
258+ [ "w1" , { status : "done" , report : "ok" } ] ,
259+ ] ) ;
260+ const mailbox = mapMailbox ( records ) ;
261+ const sends : string [ ] = [ ] ;
262+ expect (
263+ await driveMailboxMail ( {
264+ parentProcessing : false ,
265+ mailbox,
266+ lanes : [ ] ,
267+ beginSystemContinuation : ( ) => undefined ,
268+ send : ( ) => {
269+ throw new Error ( "send failed" ) ;
270+ } ,
271+ } ) ,
272+ ) . toBe ( false ) ;
273+ expect ( records . get ( "w1" ) ?. collected ) . not . toBe ( true ) ;
274+ expect (
275+ await driveMailboxMail ( {
276+ parentProcessing : false ,
277+ mailbox,
278+ lanes : [ ] ,
279+ beginSystemContinuation : ( ) => undefined ,
280+ send : ( prompt ) => {
281+ sends . push ( prompt ) ;
282+ } ,
283+ } ) ,
284+ ) . toBe ( true ) ;
285+ expect ( sends ) . toHaveLength ( 1 ) ;
286+ expect ( records . get ( "w1" ) ?. collected ) . toBe ( true ) ;
287+ } ) ;
288+
214289 test ( "awaiting_director is not mailbox mail" , async ( ) => {
215290 const records = new Map < string , FleetDryMailboxRecord > ( [
216291 [ "ask" , { status : "awaiting_director" } ] ,
@@ -232,6 +307,91 @@ describe("driveMailboxMail", () => {
232307 } ) ;
233308} ) ;
234309
310+ describe ( "latchMailboxMailDrive" , ( ) => {
311+ test ( "overlapping flushes send once until the in-flight collect settles" , async ( ) => {
312+ const records = new Map < string , FleetDryMailboxRecord > ( [
313+ [ "done" , { status : "done" , report : "ok" , description : "lane" } ] ,
314+ ] ) ;
315+ const sends : string [ ] = [ ] ;
316+ let resolveSend : ( ( ) => void ) | undefined ;
317+ const sent = new Promise < void > ( ( resolve ) => {
318+ resolveSend = resolve ;
319+ } ) ;
320+ const driver = latchMailboxMailDrive ( ( ) =>
321+ driveMailboxMail ( {
322+ parentProcessing : false ,
323+ mailbox : mapMailbox ( records ) ,
324+ lanes : [ ] ,
325+ beginSystemContinuation : ( ) => undefined ,
326+ send : ( prompt ) => {
327+ sends . push ( prompt ) ;
328+ resolveSend ?.( ) ;
329+ } ,
330+ } ) ,
331+ ) ;
332+ expect ( driver ( ) ) . toBe ( true ) ;
333+ expect ( driver ( ) ) . toBe ( false ) ;
334+ expect ( driver ( ) ) . toBe ( false ) ;
335+ await sent ;
336+ expect ( sends ) . toHaveLength ( 1 ) ;
337+ expect ( sends [ 0 ] ) . toContain ( MAILBOX_MAIL_WAKE_PREFIX ) ;
338+ } ) ;
339+
340+ test ( "a false drive does not latch the next flush" , ( ) => {
341+ let calls = 0 ;
342+ const driver = latchMailboxMailDrive ( ( ) => {
343+ calls += 1 ;
344+ return false ;
345+ } ) ;
346+ expect ( driver ( ) ) . toBe ( false ) ;
347+ expect ( driver ( ) ) . toBe ( false ) ;
348+ expect ( calls ) . toBe ( 2 ) ;
349+ } ) ;
350+
351+ test ( "after the in-flight drive settles, a new terminal can send" , async ( ) => {
352+ const records = new Map < string , FleetDryMailboxRecord > ( [
353+ [ "first" , { status : "done" , report : "one" } ] ,
354+ ] ) ;
355+ const mailbox = mapMailbox ( records ) ;
356+ const sends : string [ ] = [ ] ;
357+ let sawSend : ( ( ) => void ) | undefined ;
358+ const waitForSend = ( ) : Promise < void > =>
359+ new Promise < void > ( ( resolve ) => {
360+ sawSend = resolve ;
361+ } ) ;
362+ const driver = latchMailboxMailDrive ( ( ) =>
363+ driveMailboxMail ( {
364+ parentProcessing : false ,
365+ mailbox,
366+ lanes : [ ] ,
367+ beginSystemContinuation : ( ) => undefined ,
368+ send : ( prompt ) => {
369+ sends . push ( prompt ) ;
370+ sawSend ?.( ) ;
371+ } ,
372+ } ) ,
373+ ) ;
374+ const first = waitForSend ( ) ;
375+ expect ( driver ( ) ) . toBe ( true ) ;
376+ await first ;
377+ expect ( sends ) . toHaveLength ( 1 ) ;
378+ records . set ( "second" , { status : "done" , report : "two" } ) ;
379+ const second = waitForSend ( ) ;
380+ let retried = false ;
381+ for ( let i = 0 ; i < 10 ; i ++ ) {
382+ await Promise . resolve ( ) ;
383+ if ( driver ( ) ) {
384+ retried = true ;
385+ break ;
386+ }
387+ }
388+ expect ( retried ) . toBe ( true ) ;
389+ await second ;
390+ expect ( sends ) . toHaveLength ( 2 ) ;
391+ expect ( sends [ 1 ] ) . toContain ( "second" ) ;
392+ } ) ;
393+ } ) ;
394+
235395describe ( "occupancyShouldYieldWait" , ( ) => {
236396 test ( "yields on uncollected terminal, fail, or ask; not on live or collected" , ( ) => {
237397 expect ( occupancyShouldYieldWait ( undefined ) ) . toBe ( false ) ;
0 commit comments