repositories / pi-ext
pi-ext
bugabingas pi extensions
owned by admin
extensions/handoff/__tests__/contract.test.ts
Rawimport { fileURLToPath } from "node:url";
import { SessionManager } 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 { getHandoffMessages, SYSTEM_PROMPT } from "../core.ts";
function extensionPath(): string {
return fileURLToPath(new URL("../index.ts", import.meta.url));
}
describe("handoff contract", () => {
let session: PiSession | undefined;
afterEach(async () => {
await session?.dispose();
session = undefined;
vi.restoreAllMocks();
});
it("registers only /handoff and no tools", async () => {
session = await createPiSession({ extensionPaths: [extensionPath()] });
expect(session.errors).toEqual([]);
const extension = session.extensions[0]!;
expect(extension.path).toBe(extensionPath());
expect([...extension.commands.keys()]).toEqual(["handoff"]);
expect([...extension.tools.keys()]).toEqual([]);
});
it("refuses to run outside the TUI", async () => {
session = await createPiSession({ extensionPaths: [extensionPath()] });
const ctx = session.session.extensionRunner.createContext();
const notify = vi.spyOn(ctx.ui, "notify").mockImplementation(() => {});
await session.extensions[0]!.commands.get("handoff")!.handler(
"ship it",
ctx as never,
);
expect(notify).toHaveBeenCalledWith(
"handoff requires interactive mode",
"error",
);
});
it("honors a compaction entry when gathering branch messages", () => {
const manager = SessionManager.inMemory("/tmp");
manager.appendMessage({
role: "user",
content: "old request",
timestamp: 1,
});
manager.appendMessage({
role: "assistant",
content: [{ type: "text", text: "old answer" }],
api: "openai-completions",
provider: "faux",
model: "faux-model",
usage: {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
totalTokens: 0,
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
},
stopReason: "stop",
timestamp: 2,
});
const kept = manager.appendMessage({
role: "user",
content: "kept request",
timestamp: 3,
});
manager.appendCompaction("summary of old work", kept, 1_000);
manager.appendMessage({
role: "user",
content: "after compaction",
timestamp: 4,
});
const messages = getHandoffMessages(manager.getBranch());
expect(messages.map((m) => m.role)).toEqual([
"compactionSummary",
"user",
"user",
]);
expect(JSON.stringify(messages)).toContain("summary of old work");
expect(JSON.stringify(messages)).toContain("kept request");
expect(JSON.stringify(messages)).not.toContain("old answer");
});
it("maps every entry without compaction", () => {
const manager = SessionManager.inMemory("/tmp");
manager.appendMessage({ role: "user", content: "one", timestamp: 1 });
manager.appendMessage({ role: "user", content: "two", timestamp: 2 });
expect(getHandoffMessages(manager.getBranch())).toHaveLength(2);
});
it("instructs the generator about artifacts, redaction, and skills", () => {
expect(SYSTEM_PROMPT).toContain("by file path, commit hash, or URL");
expect(SYSTEM_PROMPT).toContain("[REDACTED]");
expect(SYSTEM_PROMPT).toContain("## Suggested skills");
});
});