import { describe, it, expect } from "vitest";
import os from "node:os";
import { buildNolanDigest, NOLAN_RECENT_MESSAGE_LIMIT } from "./nolan-context.js";
import { createTools } from "../tools/index.js";
// Mirror the sidecar's Nolan allow-list so the filter test tracks the real set.
const NOLAN_ALLOWED_TOOLS = [
    "read",
    "grep",
    "find",
    "ls",
    "source_path",
    "web_fetch",
    "web_search",
    "screenshot",
];
const NOLAN_ALLOWED_MCP_SERVERS = ["kencode-search"];
// Mirror of AgentSession.isToolAllowed (which is private): a tool passes when
// its name is in the allow-list, OR it's an mcp__<server>__<tool> whose server
// is whitelisted. Kept in lockstep so this test tracks the real filter.
function isToolAllowed(name) {
    if (NOLAN_ALLOWED_TOOLS.includes(name))
        return true;
    if (name.startsWith("mcp__")) {
        const server = name.slice("mcp__".length).split("__")[0];
        return NOLAN_ALLOWED_MCP_SERVERS.includes(server);
    }
    return false;
}
describe("Nolan allowedTools filter", () => {
    it("excludes every mutating tool from the Nolan set", async () => {
        const { tools, processManager, lspManager } = await createTools(os.tmpdir(), {
            lspDiagnostics: false,
        });
        try {
            const nolanTools = tools.filter((t) => isToolAllowed(t.name)).map((t) => t.name);
            // The mutating / orchestration tools must NOT survive the filter.
            for (const banned of ["write", "edit", "bash", "tasks", "subagent", "generate_image"]) {
                expect(nolanTools).not.toContain(banned);
            }
            // The read-only research/vision tools must survive.
            for (const allowed of ["read", "grep", "find", "ls", "screenshot"]) {
                expect(nolanTools).toContain(allowed);
            }
        }
        finally {
            processManager.shutdownAll();
            lspManager?.shutdownAll();
        }
    });
    it("allows whitelisted kencode-search MCP tools but blocks other MCP tools", () => {
        // kencode-search is Nolan's research server: all its tools pass.
        expect(isToolAllowed("mcp__kencode-search__searchCode")).toBe(true);
        expect(isToolAllowed("mcp__kencode-search__referenceSources")).toBe(true);
        expect(isToolAllowed("mcp__kencode-search__discoverRepos")).toBe(true);
        // A non-whitelisted MCP server (e.g. a user-configured one) is blocked,
        // even if it exposes an innocuous-looking name.
        expect(isToolAllowed("mcp__some-other-server__searchCode")).toBe(false);
        expect(isToolAllowed("mcp__filesystem__write_file")).toBe(false);
    });
});
describe("buildNolanDigest", () => {
    const base = {
        question: "what next?",
        projectContext: ["### CLAUDE.md\n\nBuild a todo app."],
        cwd: "/tmp/proj",
        gitBranch: "main",
        platform: "darwin",
    };
    it("includes the project context, env, and the question", () => {
        const digest = buildNolanDigest({ ...base, messages: [] });
        expect(digest).toContain("Build a todo app.");
        expect(digest).toContain("/tmp/proj");
        expect(digest).toContain("main");
        expect(digest).toContain("what next?");
        expect(digest).toContain("(no conversation yet)");
    });
    it("caps recent activity at the last-N messages", () => {
        const messages = [];
        for (let i = 0; i < NOLAN_RECENT_MESSAGE_LIMIT + 10; i++) {
            messages.push({ role: "user", content: `msg-${i}` });
        }
        const digest = buildNolanDigest({ ...base, messages });
        // The earliest messages fall outside the cap.
        expect(digest).not.toContain("msg-0");
        expect(digest).not.toContain("msg-5");
        // The newest message is kept.
        expect(digest).toContain(`msg-${NOLAN_RECENT_MESSAGE_LIMIT + 9}`);
    });
    it("strips image blocks from user messages", () => {
        const messages = [
            {
                role: "user",
                content: [
                    { type: "text", text: "look at this" },
                    { type: "image", mediaType: "image/png", data: "AAAABBBBCCCC" },
                ],
            },
        ];
        const digest = buildNolanDigest({ ...base, messages });
        expect(digest).toContain("look at this");
        expect(digest).not.toContain("AAAABBBBCCCC");
    });
    it("uses the latest compaction summary as the story-so-far base", () => {
        const messages = [
            { role: "user", content: "old turn that should be summarized away" },
            { role: "user", content: "[Previous conversation summary]\n\nWe scaffolded the app." },
            { role: "assistant", content: "Added the header." },
        ];
        const digest = buildNolanDigest({ ...base, messages });
        expect(digest).toContain("Story so far");
        expect(digest).toContain("We scaffolded the app.");
        // Pre-summary messages are not echoed into recent activity.
        expect(digest).not.toContain("old turn that should be summarized away");
        // Post-summary activity is kept.
        expect(digest).toContain("Added the header.");
    });
});
//# sourceMappingURL=nolan-context.test.js.map