@@ -303,41 +303,6 @@ function mockCall(mock: unknown, label: string, index = 0): unknown[] {
303303 return call ;
304304}
305305
306- async function waitForPromiseForTest < T > (
307- promise : Promise < T > ,
308- timeoutMs : number ,
309- label : string ,
310- ) : Promise < T > {
311- let timer : ReturnType < typeof setTimeout > | undefined ;
312- try {
313- return await Promise . race ( [
314- promise ,
315- new Promise < never > ( ( _resolve , reject ) => {
316- timer = setTimeout ( ( ) => reject ( new Error ( `${ label } timed out` ) ) , timeoutMs ) ;
317- } ) ,
318- ] ) ;
319- } finally {
320- if ( timer ) {
321- clearTimeout ( timer ) ;
322- }
323- }
324- }
325-
326- async function drainPromiseForTest (
327- promise : Promise < unknown > ,
328- timeoutMs : number ,
329- label : string ,
330- ) : Promise < void > {
331- await waitForPromiseForTest (
332- promise . then (
333- ( ) => undefined ,
334- ( ) => undefined ,
335- ) ,
336- timeoutMs ,
337- label ,
338- ) ;
339- }
340-
341306function openSocket ( url : string ) : Promise < WebSocket > {
342307 return new Promise ( ( resolve , reject ) => {
343308 const socket = new WebSocket ( url ) ;
@@ -3243,84 +3208,51 @@ describe("runCodexAppServerAttempt", () => {
32433208 }
32443209 } ) ;
32453210
3246- it ( "releases the turn after terminal dynamic tool responses" , async ( ) => {
3247- const harness = createStartedThreadHarness ( ) ;
3248- testing . setOpenClawCodingToolsFactoryForTests ( ( options ) => [
3249- {
3250- ...createRuntimeDynamicTool ( "image_generate" ) ,
3251- execute : vi . fn ( async ( ) => {
3252- await options ?. onYield ?.( "Image generation started; wait for completion." ) ;
3253- return {
3254- content : [ { type : "text" as const , text : "Background task started." } ] ,
3255- details : { async : true , status : "started" , taskId : "task-1" } ,
3256- terminate : true ,
3257- } ;
3258- } ) ,
3259- } ,
3260- ] ) ;
3261-
3262- const params = createParams (
3263- path . join ( tempDir , "session.jsonl" ) ,
3264- path . join ( tempDir , "workspace" ) ,
3265- ) ;
3266- const abortController = new AbortController ( ) ;
3267- params . abortSignal = abortController . signal ;
3268- params . disableTools = false ;
3269- params . runtimePlan = createCodexRuntimePlanFixture ( ) ;
3270-
3271- const run = runCodexAppServerAttempt ( params ) ;
3272- let completed = false ;
3273- try {
3274- await harness . waitForMethod ( "turn/start" , 10_000 ) ;
3275-
3276- const toolResult = ( await harness . handleServerRequest ( {
3277- id : "request-image-generate" ,
3278- method : "item/tool/call" ,
3279- params : {
3280- threadId : "thread-1" ,
3281- turnId : "turn-1" ,
3282- callId : "call-image-1" ,
3283- namespace : null ,
3284- tool : "image_generate" ,
3285- arguments : { prompt : "lighthouse" } ,
3286- } ,
3287- } ) ) as {
3288- contentItems ?: Array < { text ?: string ; type ?: string } > ;
3289- success ?: boolean ;
3290- } ;
3291-
3292- expect ( toolResult ) . toEqual ( {
3293- success : true ,
3294- contentItems : [ { type : "inputText" , text : "Background task started." } ] ,
3295- } ) ;
3296- expect ( harness . requests . some ( ( request ) => request . method === "turn/interrupt" ) ) . toBe ( false ) ;
3297- const result = await waitForPromiseForTest ( run , 20_000 , "Codex terminal dynamic tool run" ) ;
3298- completed = true ;
3299-
3300- expect ( result . timedOut ) . toBe ( false ) ;
3301- expect ( result . promptError ) . toBeNull ( ) ;
3302- expect ( result . yieldDetected ) . toBe ( true ) ;
3303- expect ( result . messagesSnapshot . map ( ( message ) => message . role ) ) . toEqual ( [
3304- "user" ,
3305- "assistant" ,
3306- "toolResult" ,
3307- ] ) ;
3308- expect (
3309- harness . requests . some (
3310- ( request ) =>
3311- request . method === "turn/interrupt" &&
3312- ( request . params as { turnId ?: string } | undefined ) ?. turnId === "turn-1" ,
3313- ) ,
3314- ) . toBe ( true ) ;
3315- } finally {
3316- if ( ! completed ) {
3317- harness . close ( ) ;
3318- abortController . abort ( new Error ( "test cleanup" ) ) ;
3319- await drainPromiseForTest ( run , 1_000 , "Codex terminal dynamic tool cleanup" ) . catch (
3320- ( ) => undefined ,
3321- ) ;
3322- }
3323- }
3211+ it ( "allows turn release after successful terminal dynamic tool responses" , ( ) => {
3212+ expect (
3213+ testing . shouldReleaseTurnAfterTerminalDynamicTool ( {
3214+ completed : false ,
3215+ aborted : false ,
3216+ responseSuccess : true ,
3217+ currentTurnHadNonTerminalDynamicToolResult : false ,
3218+ activeAppServerTurnRequests : 0 ,
3219+ activeTurnItemIdsCount : 0 ,
3220+ pendingOpenClawDynamicToolCompletionIdsCount : 0 ,
3221+ } ) ,
3222+ ) . toBe ( true ) ;
3223+ expect (
3224+ testing . shouldReleaseTurnAfterTerminalDynamicTool ( {
3225+ completed : false ,
3226+ aborted : false ,
3227+ responseSuccess : true ,
3228+ currentTurnHadNonTerminalDynamicToolResult : true ,
3229+ activeAppServerTurnRequests : 0 ,
3230+ activeTurnItemIdsCount : 0 ,
3231+ pendingOpenClawDynamicToolCompletionIdsCount : 0 ,
3232+ } ) ,
3233+ ) . toBe ( false ) ;
3234+ expect (
3235+ testing . shouldReleaseTurnAfterTerminalDynamicTool ( {
3236+ completed : false ,
3237+ aborted : false ,
3238+ responseSuccess : true ,
3239+ currentTurnHadNonTerminalDynamicToolResult : false ,
3240+ activeAppServerTurnRequests : 1 ,
3241+ activeTurnItemIdsCount : 0 ,
3242+ pendingOpenClawDynamicToolCompletionIdsCount : 0 ,
3243+ } ) ,
3244+ ) . toBe ( false ) ;
3245+ expect (
3246+ testing . shouldReleaseTurnAfterTerminalDynamicTool ( {
3247+ completed : false ,
3248+ aborted : false ,
3249+ responseSuccess : true ,
3250+ currentTurnHadNonTerminalDynamicToolResult : false ,
3251+ activeAppServerTurnRequests : 0 ,
3252+ activeTurnItemIdsCount : 0 ,
3253+ pendingOpenClawDynamicToolCompletionIdsCount : 1 ,
3254+ } ) ,
3255+ ) . toBe ( false ) ;
33243256 } ) ;
33253257
33263258 it ( "keeps mixed dynamic tool batches running after one terminal result" , async ( ) => {
0 commit comments