import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import { afterEach, describe, expect, it } from "vitest"; import { agentInstructionsService } from "../services/agent-instructions.js"; type TestAgent = { id: string; companyId: string; name: string; adapterConfig: Record; }; async function makeTempDir(prefix: string) { return fs.mkdtemp(path.join(os.tmpdir(), prefix)); } function makeAgent(adapterConfig: Record): TestAgent { return { id: "agent-1", companyId: "company-1", name: "Agent 1", adapterConfig, }; } describe("agent instructions service", () => { const originalPaperclipHome = process.env.PAPERCLIP_HOME; const originalPaperclipInstanceId = process.env.PAPERCLIP_INSTANCE_ID; const cleanupDirs = new Set(); afterEach(async () => { if (originalPaperclipHome === undefined) delete process.env.PAPERCLIP_HOME; else process.env.PAPERCLIP_HOME = originalPaperclipHome; if (originalPaperclipInstanceId === undefined) delete process.env.PAPERCLIP_INSTANCE_ID; else process.env.PAPERCLIP_INSTANCE_ID = originalPaperclipInstanceId; await Promise.all([...cleanupDirs].map(async (dir) => { await fs.rm(dir, { recursive: true, force: true }); cleanupDirs.delete(dir); })); }); it("copies the existing bundle into the managed root when switching to managed mode", async () => { const paperclipHome = await makeTempDir("paperclip-agent-instructions-home-"); const externalRoot = await makeTempDir("paperclip-agent-instructions-external-"); cleanupDirs.add(paperclipHome); cleanupDirs.add(externalRoot); process.env.PAPERCLIP_HOME = paperclipHome; process.env.PAPERCLIP_INSTANCE_ID = "test-instance"; await fs.writeFile(path.join(externalRoot, "AGENTS.md"), "# External Agent\n", "utf8"); await fs.mkdir(path.join(externalRoot, "docs"), { recursive: true }); await fs.writeFile(path.join(externalRoot, "docs", "TOOLS.md"), "## Tools\n", "utf8"); const svc = agentInstructionsService(); const agent = makeAgent({ instructionsBundleMode: "external", instructionsRootPath: externalRoot, instructionsEntryFile: "AGENTS.md", instructionsFilePath: path.join(externalRoot, "AGENTS.md"), }); const result = await svc.updateBundle(agent, { mode: "managed" }); expect(result.bundle.mode).toBe("managed"); expect(result.bundle.managedRootPath).toBe( path.join( paperclipHome, "instances", "test-instance", "companies", "company-1", "agents", "agent-1", "instructions", ), ); expect(result.bundle.files.map((file) => file.path)).toEqual(["AGENTS.md", "docs/TOOLS.md"]); await expect(fs.readFile(path.join(result.bundle.managedRootPath, "AGENTS.md"), "utf8")).resolves.toBe("# External Agent\n"); await expect(fs.readFile(path.join(result.bundle.managedRootPath, "docs", "TOOLS.md"), "utf8")).resolves.toBe("## Tools\n"); }); it("creates the target entry file when switching to a new external root", async () => { const paperclipHome = await makeTempDir("paperclip-agent-instructions-home-"); const managedRoot = path.join( paperclipHome, "instances", "test-instance", "companies", "company-1", "agents", "agent-1", "instructions", ); const externalRoot = await makeTempDir("paperclip-agent-instructions-new-external-"); cleanupDirs.add(paperclipHome); cleanupDirs.add(externalRoot); process.env.PAPERCLIP_HOME = paperclipHome; process.env.PAPERCLIP_INSTANCE_ID = "test-instance"; await fs.mkdir(managedRoot, { recursive: true }); await fs.writeFile(path.join(managedRoot, "AGENTS.md"), "# Managed Agent\n", "utf8"); const svc = agentInstructionsService(); const agent = makeAgent({ instructionsBundleMode: "managed", instructionsRootPath: managedRoot, instructionsEntryFile: "AGENTS.md", instructionsFilePath: path.join(managedRoot, "AGENTS.md"), }); const result = await svc.updateBundle(agent, { mode: "external", rootPath: externalRoot, entryFile: "docs/AGENTS.md", }); expect(result.bundle.mode).toBe("external"); expect(result.bundle.rootPath).toBe(externalRoot); await expect(fs.readFile(path.join(externalRoot, "docs", "AGENTS.md"), "utf8")).resolves.toBe("# Managed Agent\n"); }); });