Luigit
repositories / pi-ext

pi-ext

bugabingas pi extensions

owned by admin

extensions/the-system/__tests__/contract.test.ts

Raw
import { readdirSync, readFileSync, writeFileSync } from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { fauxAssistantMessage, fauxToolCall } from "@earendil-works/pi-ai";
import {
	createCodemodeExtension,
	type ExtensionUIContext,
} from "@earendil-works/pi-coding-agent";
import { afterEach, describe, expect, it, vi } from "vitest";
import {
	createPiSession,
	type PiSession,
} from "../../../test/fixture/create-pi-session.ts";
import { applyInitialization } from "../init.ts";

const ENTRY = fileURLToPath(new URL("../index.ts", import.meta.url));

const recordingUi = (
	select: (title: string, items: string[]) => string | undefined,
): ExtensionUIContext =>
	({
		select: async (title: string, items: string[]) => select(title, items),
		confirm: async () => false,
		input: async () => undefined,
		notify: () => {},
		onTerminalInput: () => () => {},
		setStatus: () => {},
		setWorkingMessage: () => {},
		setWorkingVisible: () => {},
		setWorkingIndicator: () => {},
		setHiddenThinkingLabel: () => {},
		setWidget: () => {},
		setFooter: () => {},
		setHeader: () => {},
		setTitle: () => {},
		custom: async () => undefined,
		pasteToEditor: () => {},
		setEditorText: () => {},
		getEditorText: () => "",
		editor: async () => undefined,
		addAutocompleteProvider: () => {},
		setEditorComponent: () => {},
		getEditorComponent: () => undefined,
		getAllThemes: () => [],
		getTheme: () => undefined,
		setTheme: () => ({ success: false, error: "UI not available" }),
		getToolsExpanded: () => false,
		setToolsExpanded: () => {},
	}) as unknown as ExtensionUIContext;

describe("the-system pi-test-harness", () => {
	let session: PiSession | undefined;

	afterEach(async () => {
		await session?.dispose();
		session = undefined;
	});

	it("loads in real Pi extension runtime", async () => {
		session = await createPiSession({ extensionPaths: [ENTRY] });

		expect(session.extensions).toHaveLength(1);
		expect(session.extensions[0]!.path).toBe(ENTRY);
		expect(session.session.getActiveToolNames()).not.toContain("mockup");
	});

	it("writes debug lifecycle records in its sandbox when enabled", async () => {
		session = await createPiSession({
			env: { PI_THE_SYSTEM_DEBUG: "1" },
			extensionPaths: [ENTRY],
		});
		const context = session.session.extensionRunner.createCommandContext();
		const [lifecycleExtension] = session.extensions;
		if (!lifecycleExtension) throw new Error("the-system extension not loaded");
		for (const handler of lifecycleExtension.handlers.get("session_start") ??
			[])
			await handler({ type: "session_start" }, context);
		for (const handler of lifecycleExtension.handlers.get("session_shutdown") ??
			[])
			await handler({ type: "session_shutdown" }, context);
		const directory = path.join(
			session.cwd,
			".test-state",
			"pi-ext",
			"debug",
			"the-system",
		);
		const records = readFileSync(
			path.join(directory, readdirSync(directory)[0]!),
			"utf8",
		)
			.trim()
			.split("\n")
			.map((line) => JSON.parse(line));
		expect(records.map((record) => record.event)).toEqual(
			expect.arrayContaining(["session.start", "session.shutdown"]),
		);
	});

	it("registers scoped tools deferred without declaring them to the model", async () => {
		session = await createPiSession({ extensionPaths: [ENTRY] });
		applyInitialization(session.cwd, "PX");
		const extension = session.extensions.find((item) => item.path === ENTRY);
		if (!extension) throw new Error("the-system extension not loaded");
		const sendUserMessage = vi
			.spyOn(session.session, "sendUserMessage")
			.mockResolvedValue();
		const systemCommand = extension.commands.get("system");
		if (!systemCommand) throw new Error("system command not registered");
		await systemCommand.handler(
			"spec let us discuss caching",
			session.session.extensionRunner.createCommandContext(),
		);
		expect(sendUserMessage).toHaveBeenCalledOnce();
		for (const name of [
			"system_bundle_slot",
			"system_board_propose",
			"system_board_update",
		]) {
			const tool = extension.tools.get(name)?.definition;
			expect(tool?.exposure).toBe("deferred");
			expect(tool?.annotations?.readOnlyHint).toBe(false);
			expect(session.session.getCallableToolNames()).toContain(name);
			expect(session.session.getActiveToolNames()).not.toContain(name);
			expect(session.session.systemPrompt).not.toContain(`- ${name}:`);
		}
	});

	it.each([true, false])(
		"preserves nested codemode mutation approval: %s",
		async (approve) => {
			session = await createPiSession({
				extensionPaths: [ENTRY],
				extensionFactories: [createCodemodeExtension()],
				userSettings: { defaultTools: ["+codemode"] },
				responses: [
					fauxAssistantMessage(
						fauxToolCall("codemode", {
							code: 'await tools.edit({ path: ".system/SYSTEM.md", edits: [{ oldText: "Approved.", newText: "Human-approved." }] });',
						}),
					),
					fauxAssistantMessage("approval requested"),
				],
			});
			applyInitialization(session.cwd, "PX");
			const extension = session.extensions.find((item) => item.path === ENTRY);
			if (!extension) throw new Error("the-system extension not loaded");
			const systemCommand = extension.commands.get("system");
			if (!systemCommand) throw new Error("system command not registered");
			vi.spyOn(session.session, "sendUserMessage").mockResolvedValue();
			await systemCommand.handler(
				"spec nested confirmation",
				session.session.extensionRunner.createCommandContext(),
			);
			writeFileSync(
				path.join(session.cwd, ".system", "SYSTEM.md"),
				"Approved.\n",
			);
			const nestedStarts: {
				toolName: string;
				parentToolCallId?: string | undefined;
			}[] = [];
			const unsubscribe = session.session.subscribe((event) => {
				if (event.type === "tool_execution_start" && event.toolName === "edit")
					nestedStarts.push({
						toolName: event.toolName,
						parentToolCallId: event.parentToolCallId,
					});
			});
			const selectCalls: { args: [string, string[]] }[] = [];
			session.session.extensionRunner.setUIContext(
				recordingUi((title, items) => {
					selectCalls.push({ args: [title, items] });
					return items[approve ? 0 : 1];
				}),
				"tui",
			);

			await session.session.prompt("run the scripted nested edit");
			unsubscribe();
			const [approval] = selectCalls;
			expect(approval?.args).toEqual([
				"The System ownership approval",
				[
					"Approve once: edit .system/SYSTEM.md",
					"Deny: keep .system/SYSTEM.md unchanged",
				],
			]);
			expect(nestedStarts).toEqual([
				{ toolName: "edit", parentToolCallId: expect.any(String) },
			]);
			expect(
				readFileSync(path.join(session.cwd, ".system", "SYSTEM.md"), "utf8"),
			).toBe(approve ? "Human-approved.\n" : "Approved.\n");
		},
	);
});