mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-28 03:10:36 +00:00
test(codex): verify completion idle watch arms after non-assistant rawResponseItem/completed
Regression test for the binary stall fix: when rawResponseItem/completed arrives with a non-assistant type (e.g. "reasoning") and all tracked items have completed, the completion idle watch must stay armed so the stall is caught in 60s, not 30 minutes. Refs openclaw/openclaw#87071 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
committed by
Peter Steinberger
parent
a36c82ba8b
commit
4d6bcf9f17
@@ -5471,6 +5471,111 @@ describe("runCodexAppServerAttempt", () => {
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("arms completion idle watch after non-assistant rawResponseItem/completed with no active items", async () => {
|
||||
let notify: (notification: CodexServerNotification) => Promise<void> = async () => undefined;
|
||||
let handleRequest:
|
||||
| ((request: { id: string; method: string; params?: unknown }) => Promise<unknown>)
|
||||
| undefined;
|
||||
const warn = vi.spyOn(embeddedAgentLog, "warn").mockImplementation(() => undefined);
|
||||
const request = vi.fn(async (method: string) => {
|
||||
if (method === "thread/start") {
|
||||
return threadStartResult("thread-1");
|
||||
}
|
||||
if (method === "turn/start") {
|
||||
return turnStartResult("turn-1", "inProgress");
|
||||
}
|
||||
return {};
|
||||
});
|
||||
setCodexAppServerClientFactoryForTest(
|
||||
async () =>
|
||||
({
|
||||
request,
|
||||
addNotificationHandler: (handler: typeof notify) => {
|
||||
notify = handler;
|
||||
return () => undefined;
|
||||
},
|
||||
addRequestHandler: (
|
||||
handler: (request: {
|
||||
id: string;
|
||||
method: string;
|
||||
params?: unknown;
|
||||
}) => Promise<unknown>,
|
||||
) => {
|
||||
handleRequest = handler;
|
||||
return () => undefined;
|
||||
},
|
||||
}) as never,
|
||||
);
|
||||
const params = createParams(
|
||||
path.join(tempDir, "session.jsonl"),
|
||||
path.join(tempDir, "workspace"),
|
||||
);
|
||||
params.timeoutMs = 60_000;
|
||||
|
||||
const run = runCodexAppServerAttempt(params, {
|
||||
turnCompletionIdleTimeoutMs: 5,
|
||||
turnAssistantCompletionIdleTimeoutMs: 500,
|
||||
turnTerminalIdleTimeoutMs: 500,
|
||||
});
|
||||
await vi.waitFor(() => expect(handleRequest).toBeTypeOf("function"), fastWait);
|
||||
|
||||
const toolResult = (await handleRequest?.({
|
||||
id: "request-tool-1",
|
||||
method: "item/tool/call",
|
||||
params: {
|
||||
threadId: "thread-1",
|
||||
turnId: "turn-1",
|
||||
callId: "call-1",
|
||||
namespace: null,
|
||||
tool: "message",
|
||||
arguments: { action: "send", text: "already sent" },
|
||||
},
|
||||
})) as { success?: boolean };
|
||||
expect(toolResult.success).toBe(false);
|
||||
// Send a rawResponseItem/completed with type "reasoning" — this does NOT
|
||||
// qualify as postToolRawAssistantCompletionNeedsTerminalGuard (which
|
||||
// requires type=message + role=assistant + text preview). Before the fix,
|
||||
// this would disarm the completion idle watch via the catch-all disarm
|
||||
// block, leaving only the 30-minute terminal timeout. After the fix,
|
||||
// rawResponseItemCompletedWithNoActiveItems keeps the 60s (here 5ms)
|
||||
// completion idle watch armed.
|
||||
await notify({
|
||||
method: "rawResponseItem/completed",
|
||||
params: {
|
||||
threadId: "thread-1",
|
||||
turnId: "turn-1",
|
||||
item: {
|
||||
type: "reasoning",
|
||||
id: "raw-reasoning-1",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const result = await run;
|
||||
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",
|
||||
);
|
||||
const completionWarnCall = warn.mock.calls.find(
|
||||
([message]) => message === "codex app-server turn idle timed out waiting for completion",
|
||||
);
|
||||
expect(completionWarnCall).toBeDefined();
|
||||
const completionWarnData = completionWarnCall?.[1] as
|
||||
| { lastActivityReason?: string; timeoutMs?: number }
|
||||
| undefined;
|
||||
expect(completionWarnData?.timeoutMs).toBe(5);
|
||||
expect(completionWarnData?.lastActivityReason).toBe("notification:rawResponseItem/completed");
|
||||
// The terminal idle watch (500ms) should NOT have fired — the shorter
|
||||
// completion idle watch (5ms) should catch the stall first.
|
||||
expect(
|
||||
warn.mock.calls.some(
|
||||
([message]) =>
|
||||
message === "codex app-server turn idle timed out waiting for terminal event",
|
||||
),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("releases the session when Codex accepts a turn but never sends progress", async () => {
|
||||
const harness = createStartedThreadHarness();
|
||||
const params = createParams(
|
||||
|
||||
Reference in New Issue
Block a user