@@ -1286,6 +1286,128 @@ describe("CodexNativeSubagentMonitor", () => {
12861286 }
12871287 } ) ;
12881288
1289+ it ( "uses every configured completion retry delay before a durable parent handoff" , async ( ) => {
1290+ vi . useFakeTimers ( ) ;
1291+ try {
1292+ const client = createClient ( ) ;
1293+ const runtime = createRuntime ( ) ;
1294+ runtime . deliverAgentHarnessTaskCompletion
1295+ . mockResolvedValueOnce ( {
1296+ delivered : false ,
1297+ path : "direct" as const ,
1298+ error : "completion handoff is still pending" ,
1299+ } )
1300+ . mockResolvedValueOnce ( {
1301+ delivered : false ,
1302+ path : "direct" as const ,
1303+ error : "completion handoff is still pending" ,
1304+ } )
1305+ . mockResolvedValueOnce ( {
1306+ delivered : true ,
1307+ path : "direct" as const ,
1308+ phases : [ { phase : "direct-primary" as const , delivered : true , path : "direct" as const } ] ,
1309+ } ) ;
1310+ const monitor = new CodexNativeSubagentMonitor ( client , runtime , {
1311+ completionDeliveryRetryDelaysMs : [ 10 , 20 ] ,
1312+ } ) ;
1313+ monitor . registerParent ( {
1314+ parentThreadId : "parent-thread" ,
1315+ requesterSessionKey : "agent:main:discord:channel:C123" ,
1316+ taskRuntimeScope : createTaskScope ( ) ,
1317+ agentId : "main" ,
1318+ } ) ;
1319+
1320+ await notifyChildStarted ( client ) ;
1321+ await client . notify (
1322+ nativeCompletionNotification ( {
1323+ agentPath : "child-thread" ,
1324+ statusLabel : "completed" ,
1325+ result : "child final result" ,
1326+ } ) ,
1327+ ) ;
1328+
1329+ expect ( runtime . deliverAgentHarnessTaskCompletion ) . toHaveBeenCalledTimes ( 1 ) ;
1330+
1331+ await vi . advanceTimersByTimeAsync ( 10 ) ;
1332+ expect ( runtime . deliverAgentHarnessTaskCompletion ) . toHaveBeenCalledTimes ( 2 ) ;
1333+
1334+ await vi . advanceTimersByTimeAsync ( 20 ) ;
1335+ expect ( runtime . deliverAgentHarnessTaskCompletion ) . toHaveBeenCalledTimes ( 3 ) ;
1336+ expect ( runtime . setDetachedTaskDeliveryStatusByRunId ) . toHaveBeenCalledWith (
1337+ expect . objectContaining ( {
1338+ runId : "codex-thread:child-thread" ,
1339+ deliveryStatus : "delivered" ,
1340+ } ) ,
1341+ ) ;
1342+ expect ( runtime . setDetachedTaskDeliveryStatusByRunId ) . not . toHaveBeenCalledWith (
1343+ expect . objectContaining ( {
1344+ runId : "codex-thread:child-thread" ,
1345+ deliveryStatus : "failed" ,
1346+ } ) ,
1347+ ) ;
1348+
1349+ await vi . advanceTimersByTimeAsync ( 1_000 ) ;
1350+ expect ( runtime . deliverAgentHarnessTaskCompletion ) . toHaveBeenCalledTimes ( 3 ) ;
1351+
1352+ client . close ( ) ;
1353+ } finally {
1354+ vi . useRealTimers ( ) ;
1355+ }
1356+ } ) ;
1357+
1358+ it ( "fails completion delivery after exhausting retries on permanently non-durable handoff" , async ( ) => {
1359+ vi . useFakeTimers ( ) ;
1360+ try {
1361+ const client = createClient ( ) ;
1362+ const runtime = createRuntime ( ) ;
1363+ runtime . deliverAgentHarnessTaskCompletion . mockResolvedValue ( {
1364+ delivered : false ,
1365+ path : "direct" as const ,
1366+ error : "completion delivery did not produce a parent response" ,
1367+ } ) ;
1368+ const monitor = new CodexNativeSubagentMonitor ( client , runtime , {
1369+ completionDeliveryRetryDelaysMs : [ 10 , 20 ] ,
1370+ } ) ;
1371+ monitor . registerParent ( {
1372+ parentThreadId : "parent-thread" ,
1373+ requesterSessionKey : "agent:main:discord:channel:C123" ,
1374+ taskRuntimeScope : createTaskScope ( ) ,
1375+ agentId : "main" ,
1376+ } ) ;
1377+
1378+ await notifyChildStarted ( client ) ;
1379+ await client . notify (
1380+ nativeCompletionNotification ( {
1381+ agentPath : "child-thread" ,
1382+ statusLabel : "completed" ,
1383+ result : "child final result" ,
1384+ } ) ,
1385+ ) ;
1386+
1387+ expect ( runtime . deliverAgentHarnessTaskCompletion ) . toHaveBeenCalledTimes ( 1 ) ;
1388+
1389+ await vi . advanceTimersByTimeAsync ( 10 ) ;
1390+ expect ( runtime . deliverAgentHarnessTaskCompletion ) . toHaveBeenCalledTimes ( 2 ) ;
1391+
1392+ await vi . advanceTimersByTimeAsync ( 20 ) ;
1393+ expect ( runtime . deliverAgentHarnessTaskCompletion ) . toHaveBeenCalledTimes ( 3 ) ;
1394+ expect ( runtime . setDetachedTaskDeliveryStatusByRunId ) . toHaveBeenCalledWith (
1395+ expect . objectContaining ( {
1396+ runId : "codex-thread:child-thread" ,
1397+ deliveryStatus : "failed" ,
1398+ error : expect . stringContaining ( "retry schedule exhausted" ) ,
1399+ } ) ,
1400+ ) ;
1401+
1402+ await vi . advanceTimersByTimeAsync ( 1_000 ) ;
1403+ expect ( runtime . deliverAgentHarnessTaskCompletion ) . toHaveBeenCalledTimes ( 3 ) ;
1404+
1405+ client . close ( ) ;
1406+ } finally {
1407+ vi . useRealTimers ( ) ;
1408+ }
1409+ } ) ;
1410+
12891411 it ( "reconciles completed native subagents from child rollout transcripts" , async ( ) => {
12901412 const tempDir = await fs . mkdtemp ( path . join ( os . tmpdir ( ) , "openclaw-codex-subagent-" ) ) ;
12911413 const codexHome = path . join ( tempDir , "codex-home" ) ;
0 commit comments