Merge pull request #1668 from paperclipai/pr/pap-803-imported-agent-frontmatter

Fix imported agent bundle frontmatter leakage
This commit is contained in:
Dotta 2026-03-23 21:27:42 -05:00 committed by GitHub
commit f92d2c3326
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 83 additions and 0 deletions

View file

@ -2108,5 +2108,78 @@ describe("company portability", () => {
replaceExisting: true,
}),
);
const materializedFiles = agentInstructionsSvc.materializeManagedBundle.mock.calls[0]?.[1] as Record<string, string>;
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<string, string>)
.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"');
});
});

View file

@ -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<string, unknown>).promptTemplate) || "";
if (!markdownRaw && fallbackPromptTemplate) {
bundleFiles["AGENTS.md"] = fallbackPromptTemplate;