74 lines
2.4 KiB
TypeScript
74 lines
2.4 KiB
TypeScript
import os from "node:os";
|
|
import path from "node:path";
|
|
import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
import { afterEach, describe, expect, it } from "vitest";
|
|
import { runHeartbeatOnce, setHeartbeatsEnabled } from "./runner.js";
|
|
|
|
type StubAgent = {
|
|
closed: boolean;
|
|
sessionId: string;
|
|
ensureInitialized: () => Promise<void>;
|
|
getMessages: () => Array<any>;
|
|
write: (content: string) => void;
|
|
waitForIdle: () => Promise<void>;
|
|
getHeartbeatConfig: () => { prompt?: string; ackMaxChars?: number; enabled?: boolean };
|
|
getPendingWrites: () => number;
|
|
getProfileDir: () => string | undefined;
|
|
};
|
|
|
|
function createStubAgent(opts?: {
|
|
profileDir?: string;
|
|
replyText?: string;
|
|
heartbeatEnabled?: boolean;
|
|
}): StubAgent {
|
|
const messages: Array<any> = [];
|
|
const replyText = opts?.replyText ?? "HEARTBEAT_OK";
|
|
|
|
return {
|
|
closed: false,
|
|
sessionId: "test-session",
|
|
ensureInitialized: async () => {},
|
|
getMessages: () => messages,
|
|
write: (content: string) => {
|
|
messages.push({ role: "user", content });
|
|
messages.push({ role: "assistant", content: [{ type: "text", text: replyText }] });
|
|
},
|
|
waitForIdle: async () => {},
|
|
getHeartbeatConfig: () =>
|
|
typeof opts?.heartbeatEnabled === "boolean"
|
|
? { enabled: opts.heartbeatEnabled }
|
|
: {},
|
|
getPendingWrites: () => 0,
|
|
getProfileDir: () => opts?.profileDir,
|
|
};
|
|
}
|
|
|
|
describe("heartbeat runner", () => {
|
|
afterEach(() => {
|
|
setHeartbeatsEnabled(true);
|
|
});
|
|
|
|
it("skips when no agent is available", async () => {
|
|
const result = await runHeartbeatOnce({ agent: null });
|
|
expect(result).toEqual({ status: "skipped", reason: "disabled" });
|
|
});
|
|
|
|
it("skips when heartbeat file is effectively empty", async () => {
|
|
const dir = await mkdtemp(path.join(os.tmpdir(), "heartbeat-test-"));
|
|
try {
|
|
await writeFile(path.join(dir, "heartbeat.md"), "# keep empty\n", "utf-8");
|
|
const agent = createStubAgent({ profileDir: dir });
|
|
const result = await runHeartbeatOnce({ agent: agent as any });
|
|
expect(result).toEqual({ status: "skipped", reason: "empty-heartbeat-file" });
|
|
} finally {
|
|
await rm(dir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
it("runs and returns ran for heartbeat acknowledgements", async () => {
|
|
const agent = createStubAgent({ replyText: "HEARTBEAT_OK" });
|
|
const result = await runHeartbeatOnce({ agent: agent as any, reason: "manual" });
|
|
|
|
expect(result.status).toBe("ran");
|
|
});
|
|
});
|