fix: stop codex stdio after missing terminal
This commit is contained in:
@@ -532,8 +532,49 @@ async function runCodexStdioTurnWithSession(options: CodexStdioTurnOptions, sess
|
||||
options.abortSignal?.addEventListener("abort", abortTurn, { once: true });
|
||||
const turnIdleTimeoutMs = positiveTimeout(options.timeoutMs);
|
||||
const idleWarningMs = codexIdleWarningMs(env, turnIdleTimeoutMs);
|
||||
const missingTerminalAfterToolTimeoutMs = codexMissingTerminalAfterToolTimeoutMs(env, turnIdleTimeoutMs);
|
||||
let idleTimeout: NodeJS.Timeout | null = null;
|
||||
let idleWarningTimeout: NodeJS.Timeout | null = null;
|
||||
let missingTerminalAfterToolTimeout: NodeJS.Timeout | null = null;
|
||||
const missingTerminalAfterToolAttrs = (): JsonRecord => ({
|
||||
waitingFor,
|
||||
idleMs: Math.max(0, Date.now() - lastActivityAt),
|
||||
timeoutMs: missingTerminalAfterToolTimeoutMs,
|
||||
lastNotificationMethod,
|
||||
threadId: threadId ?? null,
|
||||
turnId: turnId ?? null,
|
||||
terminalStatus: terminal?.status ?? null,
|
||||
retryable: false,
|
||||
retryAttempt: null,
|
||||
retryMaxAttempts: 0,
|
||||
retryExhausted: true,
|
||||
lastToolCall,
|
||||
});
|
||||
const reportMissingTerminalAfterTool = (): void => {
|
||||
if (!lastToolCall || missingTerminalAfterToolReported) return;
|
||||
missingTerminalAfterToolReported = true;
|
||||
emitCodexOtelSpan("codex_stdio.missing_terminal_after_tool", options, env, missingTerminalAfterToolAttrs());
|
||||
};
|
||||
const clearMissingTerminalAfterToolTimeout = (): void => {
|
||||
if (!missingTerminalAfterToolTimeout) return;
|
||||
clearTimeout(missingTerminalAfterToolTimeout);
|
||||
missingTerminalAfterToolTimeout = null;
|
||||
};
|
||||
const failMissingTerminalAfterTool = (): void => {
|
||||
if (terminal || !lastToolCall) return;
|
||||
reportMissingTerminalAfterTool();
|
||||
terminal = { status: "failed", failureKind: "backend-timeout", message: `codex app-server did not emit turn/completed within ${missingTerminalAfterToolTimeoutMs}ms after tool activity` };
|
||||
const attrs = { ...missingTerminalAfterToolAttrs(), terminalStatus: terminal.status, failureKind: terminal.failureKind };
|
||||
emitEvent({ type: "error", payload: { failureKind: terminal.failureKind, message: terminal.message, phase: "turn:missing-terminal-after-tool-timeout", timeoutMs: missingTerminalAfterToolTimeoutMs, retryable: false, retryAttempt: null, retryMaxAttempts: 0, retryExhausted: true, lastToolCall } });
|
||||
emitCodexOtelSpan("codex_stdio.missing_terminal_after_tool_timeout", options, env, attrs, { status: "error", error: terminal.message });
|
||||
beginInterruptAndStop("missing terminal after tool timeout", "turn:missing-terminal-after-tool-timeout");
|
||||
terminalResolve();
|
||||
};
|
||||
const scheduleMissingTerminalAfterToolTimeout = (): void => {
|
||||
clearMissingTerminalAfterToolTimeout();
|
||||
missingTerminalAfterToolTimeout = setTimeout(failMissingTerminalAfterTool, missingTerminalAfterToolTimeoutMs);
|
||||
missingTerminalAfterToolTimeout.unref?.();
|
||||
};
|
||||
const scheduleIdleWarning = (): void => {
|
||||
if (idleWarningTimeout) clearTimeout(idleWarningTimeout);
|
||||
idleWarningTimeout = setTimeout(() => {
|
||||
@@ -541,10 +582,7 @@ async function runCodexStdioTurnWithSession(options: CodexStdioTurnOptions, sess
|
||||
const idleMs = Math.max(0, Date.now() - lastActivityAt);
|
||||
const attrs = { waitingFor, idleMs, lastNotificationMethod, threadId: threadId ?? null, turnId: turnId ?? null, terminalStatus: null };
|
||||
emitCodexOtelSpan("codex_stdio.idle_warning", options, env, attrs);
|
||||
if (lastToolCall && !missingTerminalAfterToolReported) {
|
||||
missingTerminalAfterToolReported = true;
|
||||
emitCodexOtelSpan("codex_stdio.missing_terminal_after_tool", options, env, { ...attrs, lastToolCall });
|
||||
}
|
||||
reportMissingTerminalAfterTool();
|
||||
}, idleWarningMs);
|
||||
idleWarningTimeout.unref?.();
|
||||
};
|
||||
@@ -552,6 +590,7 @@ async function runCodexStdioTurnWithSession(options: CodexStdioTurnOptions, sess
|
||||
if (terminal) return;
|
||||
lastActivityAt = Date.now();
|
||||
scheduleIdleWarning();
|
||||
if (lastToolCall) scheduleMissingTerminalAfterToolTimeout();
|
||||
if (idleTimeout) clearTimeout(idleTimeout);
|
||||
idleTimeout = setTimeout(() => {
|
||||
if (terminal) return;
|
||||
@@ -569,6 +608,7 @@ async function runCodexStdioTurnWithSession(options: CodexStdioTurnOptions, sess
|
||||
idleTimeout = null;
|
||||
if (idleWarningTimeout) clearTimeout(idleWarningTimeout);
|
||||
idleWarningTimeout = null;
|
||||
clearMissingTerminalAfterToolTimeout();
|
||||
};
|
||||
refreshTurnActivity();
|
||||
const stopNotifications = session.addNotificationHandler((message) => {
|
||||
@@ -580,7 +620,11 @@ async function runCodexStdioTurnWithSession(options: CodexStdioTurnOptions, sess
|
||||
if (normalized.turnId) turnId = normalized.turnId;
|
||||
waitingFor = waitingForAfterNotification(message, normalized.terminal !== undefined);
|
||||
const toolSummary = toolCallSummaryFromNotification(message);
|
||||
if (toolSummary?.status === "completed" || toolSummary?.status === "failed") lastToolCall = toolSummary;
|
||||
if (toolSummary?.status === "completed" || toolSummary?.status === "failed") {
|
||||
lastToolCall = toolSummary;
|
||||
missingTerminalAfterToolReported = false;
|
||||
scheduleMissingTerminalAfterToolTimeout();
|
||||
}
|
||||
exposeActiveTurn(normalized.turnId ? "turn-notification" : "notification");
|
||||
emitEvents(normalized.events);
|
||||
if (normalized.assistantDelta) {
|
||||
@@ -694,10 +738,7 @@ async function runCodexStdioTurnWithSession(options: CodexStdioTurnOptions, sess
|
||||
}
|
||||
}
|
||||
if (!terminal) {
|
||||
if (lastToolCall && !missingTerminalAfterToolReported) {
|
||||
missingTerminalAfterToolReported = true;
|
||||
emitCodexOtelSpan("codex_stdio.missing_terminal_after_tool", options, env, { waitingFor, idleMs: Math.max(0, Date.now() - lastActivityAt), lastNotificationMethod, threadId: threadId ?? null, turnId: turnId ?? null, lastToolCall });
|
||||
}
|
||||
reportMissingTerminalAfterTool();
|
||||
terminal = { status: "failed", failureKind: "backend-response-invalid", message: "codex app-server did not emit turn/completed" };
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -1401,6 +1442,12 @@ function codexIdleWarningMs(env: NodeJS.ProcessEnv, turnTimeoutMs: number): numb
|
||||
return Math.max(250, Math.floor(turnTimeoutMs / 2));
|
||||
}
|
||||
|
||||
function codexMissingTerminalAfterToolTimeoutMs(env: NodeJS.ProcessEnv, turnTimeoutMs: number): number {
|
||||
const configured = Number(env.AGENTRUN_CODEX_MISSING_TERMINAL_AFTER_TOOL_TIMEOUT_MS);
|
||||
if (Number.isFinite(configured) && configured > 0) return Math.max(250, Math.floor(configured));
|
||||
return positiveTimeout(turnTimeoutMs);
|
||||
}
|
||||
|
||||
function emitCodexNotificationOtel(options: CodexStdioTurnOptions, env: NodeJS.ProcessEnv, message: JsonRecord, state: JsonRecord): void {
|
||||
const attributes = { ...state, ...notificationOtelAttributes(message) };
|
||||
emitCodexOtelSpan("codex_stdio.notification", options, env, attributes);
|
||||
|
||||
Reference in New Issue
Block a user