diff --git a/server/src/__tests__/company-portability.test.ts b/server/src/__tests__/company-portability.test.ts index bf6cfabe..a3410df6 100644 --- a/server/src/__tests__/company-portability.test.ts +++ b/server/src/__tests__/company-portability.test.ts @@ -2108,5 +2108,78 @@ describe("company portability", () => { replaceExisting: true, }), ); + const materializedFiles = agentInstructionsSvc.materializeManagedBundle.mock.calls[0]?.[1] as Record; + expect(materializedFiles["AGENTS.md"]).not.toMatch(/^---\n/); + expect(materializedFiles["AGENTS.md"]).not.toContain('name: "ClaudeCoder"'); + }); + + it("strips root AGENTS frontmatter when importing a nested agent entry path", async () => { + const portability = companyPortabilityService({} as any); + + companySvc.create.mockResolvedValue({ + id: "company-imported", + name: "Imported Paperclip", + }); + accessSvc.ensureMembership.mockResolvedValue(undefined); + agentSvc.create.mockResolvedValue({ + id: "agent-created", + name: "ClaudeCoder", + }); + + const exported = await portability.exportBundle("company-1", { + include: { + company: true, + agents: true, + projects: false, + issues: false, + }, + }); + const originalAgentsMarkdown = exported.files["agents/claudecoder/AGENTS.md"]; + expect(typeof originalAgentsMarkdown).toBe("string"); + + const files = { + ...exported.files, + "agents/claudecoder/nested/AGENTS.md": originalAgentsMarkdown!, + }; + + agentSvc.list.mockResolvedValue([]); + + await portability.importBundle({ + source: { + type: "inline", + rootPath: exported.rootPath, + files, + }, + include: { + company: true, + agents: true, + projects: false, + issues: false, + }, + target: { + mode: "new_company", + newCompanyName: "Imported Paperclip", + }, + agents: ["claudecoder"], + collisionStrategy: "rename", + adapterOverrides: { + claudecoder: { + adapterType: "codex_local", + adapterConfig: { + dangerouslyBypassApprovalsAndSandbox: true, + }, + }, + }, + }, "user-1"); + + const nestedMaterializedFiles = agentInstructionsSvc.materializeManagedBundle.mock.calls + .map(([, filesArg]) => filesArg as Record) + .find((filesArg) => typeof filesArg["nested/AGENTS.md"] === "string"); + + expect(nestedMaterializedFiles).toBeDefined(); + expect(nestedMaterializedFiles?.["nested/AGENTS.md"]).toContain("You are ClaudeCoder."); + expect(nestedMaterializedFiles?.["AGENTS.md"]).toContain("You are ClaudeCoder."); + expect(nestedMaterializedFiles?.["AGENTS.md"]).not.toMatch(/^---\n/); + expect(nestedMaterializedFiles?.["AGENTS.md"]).not.toContain('name: "ClaudeCoder"'); }); }); diff --git a/server/src/services/company-portability.ts b/server/src/services/company-portability.ts index a1f6a7c8..7cfe8ffa 100644 --- a/server/src/services/company-portability.ts +++ b/server/src/services/company-portability.ts @@ -3864,6 +3864,16 @@ export function companyPortabilityService(db: Db, storage?: StorageService) { : []), ); const markdownRaw = bundleFiles["AGENTS.md"] ?? readPortableTextFile(plan.source.files, manifestAgent.path); + const entryRelativePath = normalizePortablePath(manifestAgent.path).startsWith(bundlePrefix) + ? normalizePortablePath(manifestAgent.path).slice(bundlePrefix.length) + : "AGENTS.md"; + if (typeof markdownRaw === "string") { + const importedInstructionsBody = parseFrontmatterMarkdown(markdownRaw).body; + bundleFiles[entryRelativePath] = importedInstructionsBody; + if (entryRelativePath !== "AGENTS.md") { + bundleFiles["AGENTS.md"] = importedInstructionsBody; + } + } const fallbackPromptTemplate = asString((manifestAgent.adapterConfig as Record).promptTemplate) || ""; if (!markdownRaw && fallbackPromptTemplate) { bundleFiles["AGENTS.md"] = fallbackPromptTemplate;