import { afterEach, expect, test, vi } from "vitest"; import { resetDiagnosticSessionStateForTest } from "../logging/diagnostic-session-state.js"; import { addSession, appendOutput, markExited, resetProcessRegistryForTests, } from "./bash-process-registry.js"; import { createProcessSessionFixture } from "./bash-process-registry.test-helpers.js"; import { createProcessTool } from "./bash-tools.process.js"; afterEach(() => { resetProcessRegistryForTests(); resetDiagnosticSessionStateForTest(); }); function createProcessSessionHarness(sessionId: string) { const processTool = createProcessTool(); const session = createProcessSessionFixture({ id: sessionId, command: "test", backgrounded: true, }); addSession(session); return { processTool, session }; } async function pollSession( processTool: ReturnType, callId: string, sessionId: string, timeout?: number | string, ) { return processTool.execute(callId, { action: "poll", sessionId, ...(timeout === undefined ? {} : { timeout }), }); } function retryMs(result: Awaited["execute"]>>) { return (result.details as { retryInMs?: number }).retryInMs; } function pollStatus(result: Awaited["execute"]>>) { return (result.details as { status?: string }).status; } test("process poll waits for completion when timeout is provided", async () => { vi.useFakeTimers(); try { const sessionId = "sess"; const { processTool, session } = createProcessSessionHarness(sessionId); setTimeout(() => { appendOutput(session, "stdout", "done\n"); markExited(session, 0, null, "completed"); }, 10); const pollPromise = pollSession(processTool, "toolcall", sessionId, 2000); let resolved = false; void pollPromise.finally(() => { resolved = true; }); await vi.advanceTimersByTimeAsync(200); expect(resolved).toBe(false); await vi.advanceTimersByTimeAsync(100); const poll = await pollPromise; const details = poll.details as { status?: string; aggregated?: string }; expect(details.status).toBe("completed"); expect(details.aggregated ?? "").toContain("done"); } finally { vi.useRealTimers(); } }); test("process poll accepts string timeout values", async () => { vi.useFakeTimers(); try { const sessionId = "sess-2"; const { processTool, session } = createProcessSessionHarness(sessionId); setTimeout(() => { appendOutput(session, "stdout", "done\n"); markExited(session, 0, null, "completed"); }, 10); const pollPromise = pollSession(processTool, "toolcall", sessionId, "2000"); await vi.advanceTimersByTimeAsync(350); const poll = await pollPromise; const details = poll.details as { status?: string; aggregated?: string }; expect(details.status).toBe("completed"); expect(details.aggregated ?? "").toContain("done"); } finally { vi.useRealTimers(); } }); test("process poll exposes adaptive retryInMs for repeated no-output polls", async () => { const sessionId = "sess-retry"; const { processTool } = createProcessSessionHarness(sessionId); const polls = await Promise.all([ pollSession(processTool, "toolcall-1", sessionId), pollSession(processTool, "toolcall-2", sessionId), pollSession(processTool, "toolcall-3", sessionId), pollSession(processTool, "toolcall-4", sessionId), pollSession(processTool, "toolcall-5", sessionId), ]); expect(polls.map((poll) => retryMs(poll))).toEqual([5000, 10000, 30000, 60000, 60000]); }); test("process poll resets retryInMs when output appears and clears on completion", async () => { const sessionId = "sess-reset"; const { processTool, session } = createProcessSessionHarness(sessionId); const poll1 = await pollSession(processTool, "toolcall-1", sessionId); const poll2 = await pollSession(processTool, "toolcall-2", sessionId); expect(retryMs(poll1)).toBe(5000); expect(retryMs(poll2)).toBe(10000); appendOutput(session, "stdout", "step complete\n"); const pollWithOutput = await pollSession(processTool, "toolcall-output", sessionId); expect(retryMs(pollWithOutput)).toBe(5000); markExited(session, 0, null, "completed"); const pollCompleted = await pollSession(processTool, "toolcall-completed", sessionId); expect(pollStatus(pollCompleted)).toBe("completed"); expect(retryMs(pollCompleted)).toBeUndefined(); const pollFinished = await pollSession(processTool, "toolcall-finished", sessionId); expect(pollStatus(pollFinished)).toBe("completed"); expect(retryMs(pollFinished)).toBeUndefined(); });