mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-28 05:36:17 +00:00
fix(codex): keep attempt watchdog for queued terminal turns
Keep the Codex app-server full attempt watchdog armed after a terminal turn notification is queued, so a wedged notification projector cannot leave a run stuck indefinitely. Proof: - `git diff --check origin/main...HEAD` - `node scripts/run-oxlint.mjs extensions/codex/src/app-server/run-attempt.ts extensions/codex/src/app-server/run-attempt.test.ts` - `node scripts/run-vitest.mjs run extensions/codex/src/app-server/run-attempt.test.ts --testNamePattern "keeps the attempt watchdog armed"` passed in PR proof (`1 passed | 232 skipped`) - `OPENCLAW_TESTBOX=1 pnpm check:changed` passed in `tbx_01kskyg44ej461k574jee8ffjc` - CI required checks green after `build-artifacts` rerun job `78031279635` passed Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
This commit is contained in:
@@ -9365,6 +9365,65 @@ describe("runCodexAppServerAttempt", () => {
|
||||
expect(result.timedOut).toBe(false);
|
||||
});
|
||||
|
||||
it("keeps the attempt watchdog armed when terminal projection wedges", async () => {
|
||||
const harness = createStartedThreadHarness();
|
||||
vi.spyOn(CodexAppServerEventProjector.prototype, "handleNotification").mockImplementation(
|
||||
async () => new Promise(() => undefined),
|
||||
);
|
||||
const warn = vi.spyOn(embeddedAgentLog, "warn").mockImplementation(() => undefined);
|
||||
const params = createParams(
|
||||
path.join(tempDir, "session.jsonl"),
|
||||
path.join(tempDir, "workspace"),
|
||||
);
|
||||
params.timeoutMs = 120;
|
||||
|
||||
const run = runCodexAppServerAttempt(params, {
|
||||
turnCompletionIdleTimeoutMs: 5,
|
||||
turnTerminalIdleTimeoutMs: 5,
|
||||
});
|
||||
await harness.waitForMethod("turn/start");
|
||||
|
||||
void harness.notify({
|
||||
method: "turn/completed",
|
||||
params: {
|
||||
threadId: "thread-1",
|
||||
turnId: "turn-1",
|
||||
turn: { id: "turn-1", status: "completed" },
|
||||
},
|
||||
});
|
||||
|
||||
const result = await Promise.race([
|
||||
run,
|
||||
new Promise<never>((_, reject) => {
|
||||
setTimeout(
|
||||
() => reject(new Error("attempt watchdog did not release queued terminal turn")),
|
||||
1_000,
|
||||
);
|
||||
}),
|
||||
]);
|
||||
expect(result.aborted).toBe(true);
|
||||
expect(result.timedOut).toBe(true);
|
||||
expect(result.promptError).toBe(
|
||||
"codex app-server turn idle timed out waiting for turn/completed",
|
||||
);
|
||||
expect(
|
||||
warn.mock.calls.some(
|
||||
([message]) => message === "codex app-server turn idle timed out waiting for progress",
|
||||
),
|
||||
).toBe(true);
|
||||
expect(
|
||||
warn.mock.calls.some(
|
||||
([message]) =>
|
||||
message === "codex app-server turn idle timed out waiting for terminal event",
|
||||
),
|
||||
).toBe(false);
|
||||
expect(
|
||||
warn.mock.calls.some(
|
||||
([message]) => message === "codex app-server turn idle timed out waiting for completion",
|
||||
),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("routes Computer Use MCP elicitations through the native bridge", async () => {
|
||||
let notify: (notification: CodexServerNotification) => Promise<void> = async () => undefined;
|
||||
let handleRequest:
|
||||
|
||||
@@ -1980,6 +1980,8 @@ export async function runCodexAppServerAttempt(
|
||||
};
|
||||
|
||||
const fireTurnAttemptIdleTimeout = () => {
|
||||
// terminalTurnNotificationQueued only suppresses short idle guards; a
|
||||
// wedged notification queue still needs the full attempt timeout backstop.
|
||||
if (completed || runAbortController.signal.aborted || !turnAttemptIdleWatchArmed) {
|
||||
return;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user