@@ -5618,6 +5618,70 @@ describe("runCodexAppServerAttempt", () => {
56185618 await expect ( run ) . resolves . toMatchObject ( { aborted : false , timedOut : false } ) ;
56195619 } ) ;
56205620
5621+ it ( "does not idle-timeout when terminal completion queues behind projection" , async ( ) => {
5622+ const harness = createStartedThreadHarness ( ) ;
5623+ const params = createParams (
5624+ path . join ( tempDir , "session.jsonl" ) ,
5625+ path . join ( tempDir , "workspace" ) ,
5626+ ) ;
5627+ params . timeoutMs = 120 ;
5628+ const turnStartProgressEvents : DiagnosticEventPayload [ ] = [ ] ;
5629+ const stopDiagnostics = onInternalDiagnosticEvent ( ( event ) => {
5630+ if ( event . type === "run.progress" && event . reason === "codex_app_server:turn:start" ) {
5631+ turnStartProgressEvents . push ( event ) ;
5632+ }
5633+ } ) ;
5634+ let resolveReasoningStarted ! : ( ) => void ;
5635+ const reasoningStarted = new Promise < void > ( ( resolve ) => {
5636+ resolveReasoningStarted = resolve ;
5637+ } ) ;
5638+ let releaseProjection ! : ( ) => void ;
5639+ const projectionGate = new Promise < void > ( ( resolve ) => {
5640+ releaseProjection = resolve ;
5641+ } ) ;
5642+ params . onReasoningStream = async ( ) => {
5643+ resolveReasoningStarted ( ) ;
5644+ await projectionGate ;
5645+ } ;
5646+
5647+ let settled = false ;
5648+ const run = runCodexAppServerAttempt ( params , {
5649+ turnCompletionIdleTimeoutMs : 5 ,
5650+ turnTerminalIdleTimeoutMs : 5 ,
5651+ } ) . finally ( ( ) => {
5652+ settled = true ;
5653+ } ) ;
5654+ await harness . waitForMethod ( "turn/start" ) ;
5655+ await vi . waitFor ( ( ) => expect ( turnStartProgressEvents ) . toHaveLength ( 2 ) , { interval : 1 } ) ;
5656+ stopDiagnostics ( ) ;
5657+
5658+ const blockedProjection = harness . notify ( {
5659+ method : "item/reasoning/textDelta" ,
5660+ params : {
5661+ threadId : "thread-1" ,
5662+ turnId : "turn-1" ,
5663+ itemId : "reasoning-1" ,
5664+ delta : "thinking" ,
5665+ } ,
5666+ } ) ;
5667+ void blockedProjection . catch ( ( ) => undefined ) ;
5668+ await reasoningStarted ;
5669+
5670+ const queuedTerminal = harness . completeTurn ( { threadId : "thread-1" , turnId : "turn-1" } ) ;
5671+ void queuedTerminal . catch ( ( ) => undefined ) ;
5672+ await new Promise ( ( resolve ) => setTimeout ( resolve , 30 ) ) ;
5673+
5674+ expect ( settled ) . toBe ( false ) ;
5675+ expect ( harness . request . mock . calls . some ( ( [ method ] ) => method === "turn/interrupt" ) ) . toBe ( false ) ;
5676+
5677+ releaseProjection ( ) ;
5678+ await queuedTerminal ;
5679+ const result = await run ;
5680+ expect ( result . aborted ) . toBe ( false ) ;
5681+ expect ( result . timedOut ) . toBe ( false ) ;
5682+ expect ( result . promptError ) . toBeNull ( ) ;
5683+ } ) ;
5684+
56215685 it ( "releases the session when a completed agent message item goes quiet" , async ( ) => {
56225686 let notify : ( notification : CodexServerNotification ) => Promise < void > = async ( ) => undefined ;
56235687 const request = vi . fn ( async ( method : string ) => {
0 commit comments