@@ -203,10 +203,191 @@ describe("CodexNativeSubagentMonitor", () => {
203203 task : "inspect the repo" ,
204204 } ) ,
205205 ) ;
206+ expect ( runtime . recordTaskRunProgressByRunId ) . toHaveBeenCalledWith (
207+ expect . objectContaining ( {
208+ runId : "codex-thread:child-thread" ,
209+ progressSummary : "Codex native subagent is idle." ,
210+ } ) ,
211+ ) ;
212+ expect ( runtime . finalizeTaskRunByRunId ) . not . toHaveBeenCalled ( ) ;
213+ } ) ;
214+
215+ it . each ( [
216+ { label : "remote V1" , codexHome : undefined , finalizes : true } ,
217+ { label : "local transcript-backed V1" , codexHome : "/tmp/codex-home" , finalizes : false } ,
218+ ] ) (
219+ "uses collab completion as a terminal fallback only for $label" ,
220+ async ( { codexHome, finalizes } ) => {
221+ const client = createClient ( ) ;
222+ const runtime = createRuntime ( ) ;
223+ const monitor = new CodexNativeSubagentMonitor ( client , runtime , { codexHome } ) ;
224+ monitor . registerParent ( {
225+ parentThreadId : "parent-thread" ,
226+ requesterSessionKey : "agent:main:main" ,
227+ taskRuntimeScope : createTaskScope ( "agent:main:main" ) ,
228+ agentId : "main" ,
229+ } ) ;
230+
231+ await notifyChildStarted ( client , "parent-thread" , "child-thread" , "" ) ;
232+ await client . notify ( {
233+ method : "item/completed" ,
234+ params : {
235+ threadId : "parent-thread" ,
236+ item : {
237+ type : "collabAgentToolCall" ,
238+ tool : "wait" ,
239+ senderThreadId : "parent-thread" ,
240+ agentsStates : {
241+ "child-thread" : {
242+ status : "completed" ,
243+ message : "child final result" ,
244+ } ,
245+ } ,
246+ } ,
247+ } ,
248+ } ) ;
249+
250+ if ( finalizes ) {
251+ expect ( runtime . finalizeTaskRunByRunId ) . toHaveBeenCalledWith (
252+ expect . objectContaining ( {
253+ runId : "codex-thread:child-thread" ,
254+ status : "succeeded" ,
255+ terminalSummary : "child final result" ,
256+ } ) ,
257+ ) ;
258+ } else {
259+ expect ( runtime . recordTaskRunProgressByRunId ) . toHaveBeenCalledWith (
260+ expect . objectContaining ( {
261+ runId : "codex-thread:child-thread" ,
262+ progressSummary : "child final result" ,
263+ } ) ,
264+ ) ;
265+ expect ( runtime . finalizeTaskRunByRunId ) . not . toHaveBeenCalled ( ) ;
266+ }
267+ monitor . dispose ( ) ;
268+ } ,
269+ ) ;
270+
271+ it ( "does not complete mirrored task rows from idle status before native completion" , async ( ) => {
272+ const client = createClient ( ) ;
273+ const runtime = createRuntime ( ) ;
274+ const monitor = new CodexNativeSubagentMonitor ( client , runtime ) ;
275+ monitor . registerParent ( {
276+ parentThreadId : "parent-thread" ,
277+ requesterSessionKey : "agent:main:discord:channel:C123" ,
278+ taskRuntimeScope : createTaskScope ( ) ,
279+ agentId : "main" ,
280+ } ) ;
281+
282+ await notifyChildStarted ( client ) ;
283+ await client . notify ( {
284+ method : "thread/status/changed" ,
285+ params : {
286+ threadId : "child-thread" ,
287+ status : { type : "idle" } ,
288+ } ,
289+ } ) ;
290+
291+ expect ( runtime . finalizeTaskRunByRunId ) . not . toHaveBeenCalled ( ) ;
292+ expect ( runtime . deliverAgentHarnessTaskCompletion ) . not . toHaveBeenCalled ( ) ;
293+
294+ await client . notify (
295+ nativeCompletionNotification ( {
296+ agentPath : "child-thread" ,
297+ statusLabel : "completed" ,
298+ result : "child final result" ,
299+ } ) ,
300+ ) ;
301+
302+ expect ( runtime . finalizeTaskRunByRunId ) . toHaveBeenCalledWith (
303+ expect . objectContaining ( {
304+ runId : "codex-thread:child-thread" ,
305+ status : "succeeded" ,
306+ terminalSummary : "child final result" ,
307+ } ) ,
308+ ) ;
309+ expect ( runtime . deliverAgentHarnessTaskCompletion ) . toHaveBeenCalledWith (
310+ expect . objectContaining ( {
311+ childSessionId : "child-thread" ,
312+ result : "child final result" ,
313+ } ) ,
314+ ) ;
315+ } ) ;
316+
317+ it ( "keeps late idle lifecycle updates from overwriting native completion results" , async ( ) => {
318+ const client = createClient ( ) ;
319+ const runtime = createRuntime ( ) ;
320+ const monitor = new CodexNativeSubagentMonitor ( client , runtime ) ;
321+ monitor . registerParent ( {
322+ parentThreadId : "parent-thread" ,
323+ requesterSessionKey : "agent:main:discord:channel:C123" ,
324+ taskRuntimeScope : createTaskScope ( ) ,
325+ agentId : "main" ,
326+ } ) ;
327+
328+ await notifyChildStarted ( client ) ;
329+ await client . notify (
330+ nativeCompletionNotification ( {
331+ agentPath : "child-thread" ,
332+ statusLabel : "completed" ,
333+ result : "child final result" ,
334+ } ) ,
335+ ) ;
336+ runtime . recordTaskRunProgressByRunId . mockClear ( ) ;
337+
338+ await client . notify ( {
339+ method : "thread/status/changed" ,
340+ params : {
341+ threadId : "child-thread" ,
342+ status : { type : "idle" } ,
343+ } ,
344+ } ) ;
345+
346+ expect ( runtime . recordTaskRunProgressByRunId ) . not . toHaveBeenCalled ( ) ;
347+ expect ( runtime . finalizeTaskRunByRunId ) . toHaveBeenCalledTimes ( 1 ) ;
206348 expect ( runtime . finalizeTaskRunByRunId ) . toHaveBeenCalledWith (
207349 expect . objectContaining ( {
208350 runId : "codex-thread:child-thread" ,
209351 status : "succeeded" ,
352+ terminalSummary : "child final result" ,
353+ } ) ,
354+ ) ;
355+ } ) ;
356+
357+ it ( "keeps later lifecycle errors from rewriting native completion results" , async ( ) => {
358+ const client = createClient ( ) ;
359+ const runtime = createRuntime ( ) ;
360+ const monitor = new CodexNativeSubagentMonitor ( client , runtime ) ;
361+ monitor . registerParent ( {
362+ parentThreadId : "parent-thread" ,
363+ requesterSessionKey : "agent:main:discord:channel:C123" ,
364+ taskRuntimeScope : createTaskScope ( ) ,
365+ agentId : "main" ,
366+ } ) ;
367+
368+ await notifyChildStarted ( client ) ;
369+ await client . notify (
370+ nativeCompletionNotification ( {
371+ agentPath : "child-thread" ,
372+ statusLabel : "completed" ,
373+ result : "child final result" ,
374+ } ) ,
375+ ) ;
376+
377+ await client . notify ( {
378+ method : "thread/status/changed" ,
379+ params : {
380+ threadId : "child-thread" ,
381+ status : { type : "systemError" } ,
382+ } ,
383+ } ) ;
384+
385+ expect ( runtime . finalizeTaskRunByRunId ) . toHaveBeenCalledTimes ( 1 ) ;
386+ expect ( runtime . finalizeTaskRunByRunId ) . toHaveBeenCalledWith (
387+ expect . objectContaining ( {
388+ runId : "codex-thread:child-thread" ,
389+ status : "succeeded" ,
390+ terminalSummary : "child final result" ,
210391 } ) ,
211392 ) ;
212393 } ) ;
0 commit comments