File size: 3,387 Bytes
76289e7 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 | import { afterEach, expect, it, vi } from "vitest";
import { recordCommandPoll } from "../agents/command-poll-backoff.js";
import { detectToolCallLoop, recordToolCall } from "../agents/tool-loop-detection.js";
import {
onDiagnosticEvent,
setDiagnosticsEnabledForProcess,
waitForDiagnosticEventsDrained,
type DiagnosticEventPayload,
} from "../infra/diagnostic-events.js";
import {
getDiagnosticSessionState,
isDiagnosticSessionStateCurrent,
peekDiagnosticSessionState,
} from "./diagnostic-session-state.js";
import {
logMessageQueued,
logSessionStateChange,
logWebhookReceived,
startDiagnosticHeartbeat,
stopDiagnosticHeartbeat,
} from "./diagnostic.js";
import { resetDiagnosticStateForTest } from "./diagnostic.test-support.js";
afterEach(() => {
resetDiagnosticStateForTest();
setDiagnosticsEnabledForProcess(true);
vi.useRealTimers();
});
it("preserves independent tool-loop and poll-backoff policy when diagnostic observation stops", () => {
const session = { sessionKey: "diagnostic-tool-history" };
const state = getDiagnosticSessionState(session);
const args = { path: "fixture.txt" };
for (let index = 0; index < 10; index += 1) {
recordToolCall(state, "read", args);
}
const before = detectToolCallLoop(state, "read", args, { enabled: true });
expect(before).toMatchObject({ stuck: true, detector: "generic_repeat", count: 10 });
expect(recordCommandPoll(state, "fixture-command", false)).toBe(5_000);
expect(recordCommandPoll(state, "fixture-command", false)).toBe(10_000);
setDiagnosticsEnabledForProcess(false);
stopDiagnosticHeartbeat();
const current = getDiagnosticSessionState(session);
expect(detectToolCallLoop(current, "read", args, { enabled: true })).toEqual(before);
expect(recordCommandPoll(current, "fixture-command", false)).toBe(30_000);
});
it("retires interrupted diagnostic observations before re-enable without reviving their authority", async () => {
vi.useFakeTimers({ toFake: ["setInterval", "clearInterval"] });
setDiagnosticsEnabledForProcess(true);
const events: DiagnosticEventPayload[] = [];
const unsubscribe = onDiagnosticEvent((event) => events.push(event));
const session = { sessionKey: "diagnostic-lifecycle", sessionId: "diagnostic-lifecycle" };
try {
startDiagnosticHeartbeat({}, { sampleLiveness: () => null });
logMessageQueued({ ...session, source: "test" });
logSessionStateChange({ ...session, state: "processing" });
const generation = peekDiagnosticSessionState(session)?.generation;
expect(generation).toBeTypeOf("number");
setDiagnosticsEnabledForProcess(false);
stopDiagnosticHeartbeat();
logSessionStateChange({ ...session, state: "idle" });
setDiagnosticsEnabledForProcess(true);
startDiagnosticHeartbeat({}, { sampleLiveness: () => null });
logWebhookReceived({ channel: "test" });
await vi.advanceTimersByTimeAsync(30_000);
await waitForDiagnosticEventsDrained();
expect(events.findLast((event) => event.type === "diagnostic.heartbeat")).toMatchObject({
active: 0,
queued: 0,
waiting: 0,
});
logMessageQueued({ ...session, source: "test" });
logSessionStateChange({ ...session, state: "processing" });
expect(isDiagnosticSessionStateCurrent({ ...session, generation, state: "processing" })).toBe(
false,
);
} finally {
unsubscribe();
}
});
|