@@ -87,6 +87,7 @@ import { filterToolsForVisionInputs } from "./vision-tools.js";
8787
8888const CODEX_DYNAMIC_TOOL_TIMEOUT_MS = 30_000 ;
8989const CODEX_TURN_COMPLETION_IDLE_TIMEOUT_MS = 60_000 ;
90+ const CODEX_TURN_TERMINAL_IDLE_TIMEOUT_MS = 30 * 60_000 ;
9091const CODEX_STEER_ALL_DEBOUNCE_MS = 500 ;
9192
9293type OpenClawCodingToolsOptions = NonNullable <
@@ -226,6 +227,7 @@ export async function runCodexAppServerAttempt(
226227 hookTimeoutSec ?: number ;
227228 } ;
228229 turnCompletionIdleTimeoutMs ?: number ;
230+ turnTerminalIdleTimeoutMs ?: number ;
229231 } = { } ,
230232) : Promise < EmbeddedRunAttemptResult > {
231233 const attemptStartedAt = Date . now ( ) ;
@@ -471,8 +473,13 @@ export async function runCodexAppServerAttempt(
471473 const turnCompletionIdleTimeoutMs = resolveCodexTurnCompletionIdleTimeoutMs (
472474 options . turnCompletionIdleTimeoutMs ,
473475 ) ;
476+ const turnTerminalIdleTimeoutMs = resolveCodexTurnTerminalIdleTimeoutMs (
477+ options . turnTerminalIdleTimeoutMs ,
478+ ) ;
474479 let turnCompletionIdleTimer : ReturnType < typeof setTimeout > | undefined ;
475480 let turnCompletionIdleWatchArmed = false ;
481+ let turnTerminalIdleTimer : ReturnType < typeof setTimeout > | undefined ;
482+ let turnTerminalIdleWatchArmed = false ;
476483 let turnCompletionLastActivityAt = Date . now ( ) ;
477484 let turnCompletionLastActivityReason = "startup" ;
478485 let activeAppServerTurnRequests = 0 ;
@@ -484,6 +491,13 @@ export async function runCodexAppServerAttempt(
484491 }
485492 } ;
486493
494+ const clearTurnTerminalIdleTimer = ( ) => {
495+ if ( turnTerminalIdleTimer ) {
496+ clearTimeout ( turnTerminalIdleTimer ) ;
497+ turnTerminalIdleTimer = undefined ;
498+ }
499+ } ;
500+
487501 const fireTurnCompletionIdleTimeout = ( ) => {
488502 if (
489503 completed ||
@@ -520,6 +534,42 @@ export async function runCodexAppServerAttempt(
520534 runAbortController . abort ( "turn_completion_idle_timeout" ) ;
521535 } ;
522536
537+ const fireTurnTerminalIdleTimeout = ( ) => {
538+ if (
539+ completed ||
540+ runAbortController . signal . aborted ||
541+ ! turnTerminalIdleWatchArmed ||
542+ activeAppServerTurnRequests > 0
543+ ) {
544+ return ;
545+ }
546+ const idleMs = Math . max ( 0 , Date . now ( ) - turnCompletionLastActivityAt ) ;
547+ if ( idleMs < turnTerminalIdleTimeoutMs ) {
548+ scheduleTurnTerminalIdleWatch ( ) ;
549+ return ;
550+ }
551+ timedOut = true ;
552+ turnCompletionIdleTimedOut = true ;
553+ turnCompletionIdleTimeoutMessage =
554+ "codex app-server turn idle timed out waiting for turn/completed" ;
555+ projector ?. markTimedOut ( ) ;
556+ trajectoryRecorder ?. recordEvent ( "turn.terminal_idle_timeout" , {
557+ threadId : thread . threadId ,
558+ turnId,
559+ idleMs,
560+ timeoutMs : turnTerminalIdleTimeoutMs ,
561+ lastActivityReason : turnCompletionLastActivityReason ,
562+ } ) ;
563+ embeddedAgentLog . warn ( "codex app-server turn idle timed out waiting for terminal event" , {
564+ threadId : thread . threadId ,
565+ turnId,
566+ idleMs,
567+ timeoutMs : turnTerminalIdleTimeoutMs ,
568+ lastActivityReason : turnCompletionLastActivityReason ,
569+ } ) ;
570+ runAbortController . abort ( "turn_terminal_idle_timeout" ) ;
571+ } ;
572+
523573 function scheduleTurnCompletionIdleWatch ( ) {
524574 clearTurnCompletionIdleTimer ( ) ;
525575 if (
@@ -536,13 +586,30 @@ export async function runCodexAppServerAttempt(
536586 turnCompletionIdleTimer . unref ?.( ) ;
537587 }
538588
589+ function scheduleTurnTerminalIdleWatch ( ) {
590+ clearTurnTerminalIdleTimer ( ) ;
591+ if (
592+ completed ||
593+ runAbortController . signal . aborted ||
594+ ! turnTerminalIdleWatchArmed ||
595+ activeAppServerTurnRequests > 0
596+ ) {
597+ return ;
598+ }
599+ const elapsedMs = Math . max ( 0 , Date . now ( ) - turnCompletionLastActivityAt ) ;
600+ const delayMs = Math . max ( 1 , turnTerminalIdleTimeoutMs - elapsedMs ) ;
601+ turnTerminalIdleTimer = setTimeout ( fireTurnTerminalIdleTimeout , delayMs ) ;
602+ turnTerminalIdleTimer . unref ?.( ) ;
603+ }
604+
539605 const touchTurnCompletionActivity = ( reason : string , options ?: { arm ?: boolean } ) => {
540606 turnCompletionLastActivityAt = Date . now ( ) ;
541607 turnCompletionLastActivityReason = reason ;
542608 if ( options ?. arm ) {
543609 turnCompletionIdleWatchArmed = true ;
544610 }
545611 scheduleTurnCompletionIdleWatch ( ) ;
612+ scheduleTurnTerminalIdleWatch ( ) ;
546613 } ;
547614
548615 const emitLifecycleStart = ( ) => {
@@ -595,6 +662,7 @@ export async function runCodexAppServerAttempt(
595662 }
596663 completed = true ;
597664 clearTurnCompletionIdleTimer ( ) ;
665+ clearTurnTerminalIdleTimer ( ) ;
598666 resolveCompletion ?.( ) ;
599667 }
600668 }
@@ -839,6 +907,7 @@ export async function runCodexAppServerAttempt(
839907 abort : ( ) => runAbortController . abort ( "aborted" ) ,
840908 } ;
841909 setActiveEmbeddedRun ( params . sessionId , handle , params . sessionKey ) ;
910+ turnTerminalIdleWatchArmed = true ;
842911 touchTurnCompletionActivity ( "turn:start" ) ;
843912
844913 const timeout = setTimeout (
@@ -1005,6 +1074,7 @@ export async function runCodexAppServerAttempt(
10051074 userInputBridge ?. cancelPending ( ) ;
10061075 clearTimeout ( timeout ) ;
10071076 clearTurnCompletionIdleTimer ( ) ;
1077+ clearTurnTerminalIdleTimer ( ) ;
10081078 notificationCleanup ( ) ;
10091079 requestCleanup ( ) ;
10101080 nativeHookRelay ?. unregister ( ) ;
@@ -1305,6 +1375,16 @@ function resolveCodexTurnCompletionIdleTimeoutMs(value: number | undefined): num
13051375 return Math . max ( 1 , Math . floor ( value ) ) ;
13061376}
13071377
1378+ function resolveCodexTurnTerminalIdleTimeoutMs ( value : number | undefined ) : number {
1379+ if ( value === undefined ) {
1380+ return CODEX_TURN_TERMINAL_IDLE_TIMEOUT_MS ;
1381+ }
1382+ if ( ! Number . isFinite ( value ) ) {
1383+ return CODEX_TURN_TERMINAL_IDLE_TIMEOUT_MS ;
1384+ }
1385+ return Math . max ( 1 , Math . floor ( value ) ) ;
1386+ }
1387+
13081388function readDynamicToolCallParams (
13091389 value : JsonValue | undefined ,
13101390) : CodexDynamicToolCallParams | undefined {
@@ -1417,6 +1497,7 @@ function handleApprovalRequest(params: {
14171497export const __testing = {
14181498 CODEX_DYNAMIC_TOOL_TIMEOUT_MS ,
14191499 CODEX_TURN_COMPLETION_IDLE_TIMEOUT_MS ,
1500+ CODEX_TURN_TERMINAL_IDLE_TIMEOUT_MS ,
14201501 buildCodexNativeHookRelayId,
14211502 filterToolsForVisionInputs,
14221503 handleDynamicToolCallWithTimeout,
0 commit comments