Luigit
repositories / pi-ext

pi-ext

bugabingas pi extensions

owned by admin

extensions/continuity/__tests__/unit.test.ts

Raw
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import continuityExtension, {
	__test,
	buildContinuationPrompt,
} from "../index.ts";

type Handler = (event: any, ctx: any) => unknown;

function compactionEvent(
	overrides: Record<string, unknown> = {},
): Record<string, unknown> {
	return {
		type: "session_compact",
		compactionEntry: { id: "compact-1" },
		fromExtension: false,
		reason: "manual",
		willRetry: false,
		...overrides,
	};
}

function createHarness() {
	const handlers = new Map<string, Handler[]>();
	const sent: Array<{ message: any; options: any }> = [];
	const notices: Array<[string, string]> = [];
	let activeTools: string[] = [];
	let idle = true;
	let pendingMessages = false;
	let sessionId = "session-1";
	let sessionFile: string | undefined = "/sessions/work.jsonl";
	let branch: any[] = [];
	let sendError: Error | undefined;

	const pi = {
		on(event: string, handler: Handler) {
			handlers.set(event, [...(handlers.get(event) ?? []), handler]);
		},
		getActiveTools: () => activeTools,
		sendMessage(message: any, options: any) {
			if (sendError) throw sendError;
			sent.push({ message, options });
		},
	};
	const ctx = {
		isIdle: () => idle,
		hasPendingMessages: () => pendingMessages,
		sessionManager: {
			getSessionId: () => sessionId,
			getSessionFile: () => sessionFile,
			getBranch: () => branch,
		},
		ui: {
			notify(message: string, severity: string) {
				notices.push([message, severity]);
			},
		},
	};
	continuityExtension(pi as never);

	return {
		sent,
		notices,
		ctx,
		emit(event: string, payload: any = {}) {
			for (const handler of handlers.get(event) ?? []) handler(payload, ctx);
		},
		setActiveTools(names: string[]) {
			activeTools = names;
		},
		setIdle(value: boolean) {
			idle = value;
		},
		setPendingMessages(value: boolean) {
			pendingMessages = value;
		},
		setSessionId(value: string) {
			sessionId = value;
		},
		setSessionFile(value: string | undefined) {
			sessionFile = value;
		},
		setBranch(value: any[]) {
			branch = value;
		},
		setSendError(error: Error | undefined) {
			sendError = error;
		},
	};
}

beforeEach(() => vi.useFakeTimers());
afterEach(() => vi.useRealTimers());

describe("continuation prompt", () => {
	it("uses the summary first and persisted active history only as fallback", () => {
		const prompt = buildContinuationPrompt(
			'/sessions/a "quoted" session.jsonl',
			"compact-7",
		);

		expect(prompt).toContain(
			"Recover the user's current intent from the compaction summary and current worktree.",
		);
		expect(prompt).toContain(
			'Persisted session JSONL: "/sessions/a \\"quoted\\" session.jsonl".',
		);
		expect(prompt).toContain("following parentId links");
		expect(prompt).toContain("Do not treat append order as branch order");
		expect(prompt).toContain('Compaction entry ID: "compact-7".');
		expect(prompt).toContain("If actionable work remains, continue it.");
		expect(prompt).toContain("all remaining work is blocked");
		expect(prompt).toContain("Do not rerun completed checks");
		expect(prompt).not.toContain("immediately perform");
	});

	it("handles ephemeral sessions without claiming history exists", () => {
		const prompt = buildContinuationPrompt(undefined, "compact-1");

		expect(prompt).toContain("This session is ephemeral");
		expect(prompt).not.toContain("Persisted session JSONL");
		expect(prompt).toContain('Compaction entry ID: "compact-1".');
	});
});

describe("continuity lifecycle", () => {
	it.each(["manual", "threshold"])(
		"continues once after successful %s compaction",
		async (reason) => {
			const h = createHarness();

			h.emit("session_compact", compactionEvent({ reason }));
			await vi.runAllTimersAsync();

			expect(h.sent).toHaveLength(1);
			expect(h.sent[0].message).toMatchObject({
				customType: __test.CONTINUATION_TYPE,
				display: false,
				details: { compactionEntryId: "compact-1", reason },
			});
			expect(h.sent[0].options).toEqual({
				triggerTurn: true,
				deliverAs: "followUp",
			});
		},
	);

	it("does not duplicate Pi's overflow retry", async () => {
		const h = createHarness();

		h.emit(
			"session_compact",
			compactionEvent({ reason: "overflow", willRetry: true }),
		);
		await vi.runAllTimersAsync();

		expect(h.sent).toEqual([]);
	});

	it("continues after non-retrying overflow compaction", async () => {
		const h = createHarness();

		h.emit(
			"session_compact",
			compactionEvent({ reason: "overflow", willRetry: false }),
		);
		await vi.runAllTimersAsync();

		expect(h.sent).toHaveLength(1);
	});

	it("leaves an already queued continuation or user message alone", async () => {
		const h = createHarness();
		h.setPendingMessages(true);

		h.emit("session_compact", compactionEvent());
		await vi.runAllTimersAsync();

		expect(h.sent).toEqual([]);
	});

	it("yields when a message becomes pending during deferral", async () => {
		const h = createHarness();

		h.emit("session_compact", compactionEvent());
		h.setPendingMessages(true);
		await vi.runAllTimersAsync();

		expect(h.sent).toEqual([]);
	});

	it("yields when another run starts during deferral", async () => {
		const h = createHarness();

		h.emit("session_compact", compactionEvent());
		h.emit("agent_start");
		await vi.runAllTimersAsync();

		expect(h.sent).toEqual([]);
	});

	it("does not dispatch while Pi remains busy", async () => {
		const h = createHarness();
		h.setIdle(false);

		h.emit("session_compact", compactionEvent());
		await vi.runAllTimersAsync();

		expect(h.sent).toEqual([]);
	});

	it("does not deliver into a replacement session", async () => {
		const h = createHarness();

		h.emit("session_compact", compactionEvent());
		h.setSessionId("session-2");
		await vi.runAllTimersAsync();

		expect(h.sent).toEqual([]);
	});

	it("replaces an obsolete scheduled continuation with the latest compaction", async () => {
		const h = createHarness();

		h.emit("session_compact", compactionEvent());
		h.emit(
			"session_compact",
			compactionEvent({ compactionEntry: { id: "compact-2" } }),
		);
		await vi.runAllTimersAsync();

		expect(h.sent).toHaveLength(1);
		expect(h.sent[0].message.details.compactionEntryId).toBe("compact-2");
	});

	it("clears scheduled work on session shutdown", async () => {
		const h = createHarness();

		h.emit("session_compact", compactionEvent());
		h.emit("session_shutdown");
		await vi.runAllTimersAsync();

		expect(h.sent).toEqual([]);
	});

	it("yields to specialized continuation capability update_goal", async () => {
		const h = createHarness();
		h.setActiveTools(["update_goal"]);

		h.emit("session_compact", compactionEvent());
		await vi.runAllTimersAsync();

		expect(h.sent).toEqual([]);
	});

	it("yields to an active first-turn goal before update_goal becomes active", async () => {
		const h = createHarness();
		h.setBranch([
			{
				type: "custom",
				customType: __test.GOAL_STATE_TYPE,
				data: { status: "active", sessionId: "session-1", turnsUsed: 0 },
			},
		]);

		h.emit("session_compact", compactionEvent());
		await vi.runAllTimersAsync();

		expect(h.sent).toEqual([]);
	});

	it.each([
		["paused goal", { status: "paused", sessionId: "session-1" }],
		["foreign goal", { status: "active", sessionId: "session-2" }],
		["cleared goal", { cleared: true }],
	])("does not yield to %s state", async (_name, data) => {
		const h = createHarness();
		h.setBranch([{ type: "custom", customType: __test.GOAL_STATE_TYPE, data }]);

		h.emit("session_compact", compactionEvent());
		await vi.runAllTimersAsync();

		expect(h.sent).toHaveLength(1);
	});

	it("uses the latest goal state event", async () => {
		const h = createHarness();
		h.setBranch([
			{
				type: "custom",
				customType: __test.GOAL_STATE_TYPE,
				data: { status: "active", sessionId: "session-1" },
			},
			{
				type: "custom",
				customType: __test.GOAL_STATE_TYPE,
				data: { status: "paused", sessionId: "session-1" },
			},
		]);

		h.emit("session_compact", compactionEvent());
		await vi.runAllTimersAsync();

		expect(h.sent).toHaveLength(1);
	});

	it("rechecks specialized continuation capabilities during deferral", async () => {
		const h = createHarness();

		h.emit("session_compact", compactionEvent());
		h.setActiveTools(["update_goal"]);
		await vi.runAllTimersAsync();

		expect(h.sent).toEqual([]);
	});

	it("does not yield merely because inactive tools exist", async () => {
		const h = createHarness();
		h.setActiveTools(["read", "bash"]);

		h.emit("session_compact", compactionEvent());
		await vi.runAllTimersAsync();

		expect(h.sent).toHaveLength(1);
	});

	it("reports dispatch failures without throwing from the timer", async () => {
		const h = createHarness();
		h.setSendError(new Error("stale runtime"));

		h.emit("session_compact", compactionEvent());
		await vi.runAllTimersAsync();

		expect(h.sent).toEqual([]);
		expect(h.notices).toEqual([
			["Continuity could not resume: stale runtime", "error"],
		]);
	});

	it("uses ephemeral context from the live session", async () => {
		const h = createHarness();
		h.setSessionFile(undefined);

		h.emit("session_compact", compactionEvent());
		await vi.runAllTimersAsync();

		expect(h.sent[0].message.content).toContain("This session is ephemeral");
	});
});