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>
115 lines
4.1 KiB
TypeScript
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),
|
|
);
|
|
});
|
|
});
|