import { describe, expect, it } from "vitest"; import { createManagedRun, enqueueSystemEventMock, requestHeartbeatNowMock, setupCliRunnerTestModule, supervisorSpawnMock, } from "./cli-runner.test-support.js"; import { resolveCliNoOutputTimeoutMs } from "./cli-runner/helpers.js"; describe("runCliAgent reliability", () => { it("fails with timeout when no-output watchdog trips", async () => { const runCliAgent = await setupCliRunnerTestModule(); supervisorSpawnMock.mockResolvedValueOnce( createManagedRun({ reason: "no-output-timeout", exitCode: null, exitSignal: "SIGKILL", durationMs: 200, stdout: "", stderr: "", timedOut: true, noOutputTimedOut: true, }), ); await expect( runCliAgent({ sessionId: "s1", sessionFile: "/tmp/session.jsonl", workspaceDir: "/tmp", prompt: "hi", provider: "codex-cli", model: "gpt-5.2-codex", timeoutMs: 1_000, runId: "run-2", cliSessionId: "thread-123", }), ).rejects.toThrow("produced no output"); }); it("enqueues a system event and heartbeat wake on no-output watchdog timeout for session runs", async () => { const runCliAgent = await setupCliRunnerTestModule(); supervisorSpawnMock.mockResolvedValueOnce( createManagedRun({ reason: "no-output-timeout", exitCode: null, exitSignal: "SIGKILL", durationMs: 200, stdout: "", stderr: "", timedOut: true, noOutputTimedOut: true, }), ); await expect( runCliAgent({ sessionId: "s1", sessionKey: "agent:main:main", sessionFile: "/tmp/session.jsonl", workspaceDir: "/tmp", prompt: "hi", provider: "codex-cli", model: "gpt-5.2-codex", timeoutMs: 1_000, runId: "run-2b", cliSessionId: "thread-123", }), ).rejects.toThrow("produced no output"); expect(enqueueSystemEventMock).toHaveBeenCalledTimes(1); const [notice, opts] = enqueueSystemEventMock.mock.calls[0] ?? []; expect(String(notice)).toContain("produced no output"); expect(String(notice)).toContain("interactive input or an approval prompt"); expect(opts).toMatchObject({ sessionKey: "agent:main:main" }); expect(requestHeartbeatNowMock).toHaveBeenCalledWith({ reason: "cli:watchdog:stall", sessionKey: "agent:main:main", }); }); it("fails with timeout when overall timeout trips", async () => { const runCliAgent = await setupCliRunnerTestModule(); supervisorSpawnMock.mockResolvedValueOnce( createManagedRun({ reason: "overall-timeout", exitCode: null, exitSignal: "SIGKILL", durationMs: 200, stdout: "", stderr: "", timedOut: true, noOutputTimedOut: false, }), ); await expect( runCliAgent({ sessionId: "s1", sessionFile: "/tmp/session.jsonl", workspaceDir: "/tmp", prompt: "hi", provider: "codex-cli", model: "gpt-5.2-codex", timeoutMs: 1_000, runId: "run-3", cliSessionId: "thread-123", }), ).rejects.toThrow("exceeded timeout"); }); it("rethrows the retry failure when session-expired recovery retry also fails", async () => { const runCliAgent = await setupCliRunnerTestModule(); supervisorSpawnMock.mockResolvedValueOnce( createManagedRun({ reason: "exit", exitCode: 1, exitSignal: null, durationMs: 150, stdout: "", stderr: "session expired", timedOut: false, noOutputTimedOut: false, }), ); supervisorSpawnMock.mockResolvedValueOnce( createManagedRun({ reason: "exit", exitCode: 1, exitSignal: null, durationMs: 150, stdout: "", stderr: "rate limit exceeded", timedOut: false, noOutputTimedOut: false, }), ); await expect( runCliAgent({ sessionId: "s1", sessionKey: "agent:main:subagent:retry", sessionFile: "/tmp/session.jsonl", workspaceDir: "/tmp", prompt: "hi", provider: "codex-cli", model: "gpt-5.2-codex", timeoutMs: 1_000, runId: "run-retry-failure", cliSessionId: "thread-123", }), ).rejects.toThrow("rate limit exceeded"); expect(supervisorSpawnMock).toHaveBeenCalledTimes(2); }); }); describe("resolveCliNoOutputTimeoutMs", () => { it("uses backend-configured resume watchdog override", () => { const timeoutMs = resolveCliNoOutputTimeoutMs({ backend: { command: "codex", reliability: { watchdog: { resume: { noOutputTimeoutMs: 42_000, }, }, }, }, timeoutMs: 120_000, useResume: true, }); expect(timeoutMs).toBe(42_000); }); });