evOo-local/packages/core/src/autopilot.test.ts
cynarAI 54658ee689
feat: autopilot progress, composer start and morning summary (#146)
The autopilot's start line counts its steps and folds open to the latest
checkpoints, and its result reads as a card. The composer's plus menu
starts an autopilot with the draft as the order and a choice of until
done, tomorrow morning or a time. Today lists the autopilots that run,
wait or ended within a day, and their step runs no longer show one by
one. Autopilot runs send no completion or failure push; only a pause
notifies.


Claude-Session: https://claude.ai/code/session_01UmSCnoCgXqBp2fU3vwz4o1

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-05 04:45:49 +02:00

115 lines
4.1 KiB
TypeScript

import type { AutopilotBlock } from "@rakazo/contracts";
import { describe, expect, it } from "vitest";
import {
addAutopilotCheckpoint,
advanceAutopilotGuard,
autopilotContinuationPrompt,
autopilotExpired,
autopilotHistoryText,
autopilotUntil,
formatAutopilotDuration,
markAutopilotLinesEnded,
} from "./autopilot.js";
const started: AutopilotBlock = {
kind: "autopilot",
event: "autopilot.started",
autopilotId: "a1",
goal: "Tidy up the tickets",
focus: "only tidy",
until: "2026-10-05T05:00:00.000Z",
};
describe("autopilot", () => {
it("pauses after three runs without a checkpoint or three failures, not before", () => {
let counts = { idleCount: 0, failCount: 0 };
for (const expected of [null, null, "idle"]) {
const next = advanceAutopilotGuard(counts, { checkpointed: false, failed: false });
expect(next.pause).toBe(expected);
counts = next;
}
expect(advanceAutopilotGuard(counts, { checkpointed: true, failed: false })).toEqual({
idleCount: 0,
failCount: 0,
pause: null,
});
expect(
advanceAutopilotGuard({ idleCount: 0, failCount: 2 }, { checkpointed: true, failed: true })
.pause,
).toBe("failing");
});
it("tells each continuation the order and where it stands", () => {
const prompt = autopilotContinuationPrompt({
goal: "Tidy up the tickets",
focus: "only tidy",
until: null,
lastCheckpoint: "Ticket 2 tidied",
nextStep: "Ticket 3",
stepCount: 2,
});
expect(prompt).toMatch(/^Autopilot:/);
expect(prompt).toContain("Order: Tidy up the tickets");
expect(prompt).toContain("Until: done");
expect(prompt).toContain("Steps so far: 2");
expect(prompt).toContain("Last checkpoint: Ticket 2 tidied");
});
it("ends at its time", () => {
expect(autopilotExpired(null)).toBe(false);
expect(autopilotExpired(new Date(1000), 2000)).toBe(true);
expect(autopilotExpired(new Date(3000), 2000)).toBe(false);
});
it("reads as a line in the model history and loses its controls once over", () => {
expect(autopilotHistoryText(started)).toBe(
"[autopilot on: Tidy up the tickets — until 2026-10-05T05:00:00.000Z — focus: only tidy]",
);
const ended = markAutopilotLinesEnded([started, { kind: "text" }], "a1");
expect(ended?.[0]).toMatchObject({ ended: true });
expect(markAutopilotLinesEnded(ended ?? [], "a1")).toBeNull();
expect(
autopilotHistoryText({
...started,
event: "autopilot.finished",
steps: 5,
text: "Tidied 5 tickets",
}),
).toBe("[autopilot finished after 5 steps: Tidied 5 tickets]");
});
it("words durations briefly", () => {
expect(formatAutopilotDuration(10_000)).toBe("1 min");
expect(formatAutopilotDuration(12 * 60_000)).toBe("12 min");
expect(formatAutopilotDuration(222 * 60_000)).toBe("3 h 42 min");
expect(formatAutopilotDuration(120 * 60_000)).toBe("2 h");
});
it("counts checkpoints on the start line and keeps the latest fifty", () => {
let blocks: AutopilotBlock[] = [started];
for (let step = 1; step <= 52; step += 1) {
blocks =
addAutopilotCheckpoint(blocks, "a1", { text: `Step ${step}`, at: "t" }, step) ?? blocks;
}
expect(blocks[0]?.steps).toBe(52);
expect(blocks[0]?.checkpoints).toHaveLength(50);
expect(blocks[0]?.checkpoints?.at(-1)?.text).toBe("Step 52");
expect(addAutopilotCheckpoint(blocks, "other", { text: "x", at: "t" }, 1)).toBeNull();
});
it("picks the next morning or the next given time", () => {
const evening = new Date(2026, 9, 4, 22, 30);
expect(autopilotUntil({ kind: "done" }, evening)).toBeUndefined();
expect(autopilotUntil({ kind: "morning" }, evening)).toEqual(new Date(2026, 9, 5, 8, 0));
expect(autopilotUntil({ kind: "morning" }, new Date(2026, 9, 5, 2, 0))).toEqual(
new Date(2026, 9, 5, 8, 0),
);
expect(autopilotUntil({ kind: "time", time: "23:15" }, evening)).toEqual(
new Date(2026, 9, 4, 23, 15),
);
expect(autopilotUntil({ kind: "time", time: "06:00" }, evening)).toEqual(
new Date(2026, 9, 5, 6, 0),
);
});
});