fix(subagents): handle CRLF/BOM frontmatter for custom agent files (#1424)

Co-authored-by: Letta Code <noreply@letta.com>
This commit is contained in:
Devansh Jain
2026-03-17 15:07:59 -07:00
committed by GitHub
parent b7600ee2f8
commit ef7defe5d9
3 changed files with 152 additions and 4 deletions

View File

@@ -1,5 +1,39 @@
import { describe, expect, test } from "bun:test";
import { getAllSubagentConfigs } from "../../agent/subagents";
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import {
clearSubagentConfigCache,
getAllSubagentConfigs,
} from "../../agent/subagents";
let tempDir: string | null = null;
function createTempProjectDir(): string {
return mkdtempSync(join(tmpdir(), "letta-subagents-test-"));
}
function writeCustomSubagent(
projectDir: string,
fileName: string,
content: string,
) {
const agentsDir = join(projectDir, ".letta", "agents");
mkdirSync(agentsDir, { recursive: true });
writeFileSync(join(agentsDir, fileName), content, "utf-8");
}
beforeEach(() => {
clearSubagentConfigCache();
});
afterEach(() => {
clearSubagentConfigCache();
if (tempDir) {
rmSync(tempDir, { recursive: true, force: true });
tempDir = null;
}
});
describe("built-in subagents", () => {
test("includes reflection subagent in available configs", async () => {
@@ -15,4 +49,68 @@ describe("built-in subagents", () => {
expect(configs["general-purpose"]?.mode).toBe("stateful");
expect(configs.memory?.mode).toBe("stateful");
});
test("custom CRLF reflection override replaces built-in reflection", async () => {
tempDir = createTempProjectDir();
writeCustomSubagent(
tempDir,
"reflection.md",
[
"---",
"name: reflection",
"description: Custom reflection override",
"tools: Read",
"model: zaisigno/glm-5",
"memoryBlocks: none",
"---",
"Custom prompt body",
].join("\r\n"),
);
const configs = await getAllSubagentConfigs(tempDir);
expect(configs.reflection).toBeDefined();
expect(configs.reflection?.description).toBe("Custom reflection override");
expect(configs.reflection?.recommendedModel).toBe("zaisigno/glm-5");
});
test("blank model field falls back to inherit", async () => {
tempDir = createTempProjectDir();
writeCustomSubagent(
tempDir,
"reflection.md",
`---
name: reflection
description: Custom reflection override
tools: Read
model:
memoryBlocks: none
---
Custom prompt body`,
);
const configs = await getAllSubagentConfigs(tempDir);
expect(configs.reflection).toBeDefined();
expect(configs.reflection?.recommendedModel).toBe("inherit");
});
test("frontmatter name remains override key (filename can differ)", async () => {
tempDir = createTempProjectDir();
writeCustomSubagent(
tempDir,
"reflector.md",
`---
name: reflection
description: Custom reflection override from different filename
tools: Read
memoryBlocks: none
---
Custom prompt body`,
);
const configs = await getAllSubagentConfigs(tempDir);
expect(configs.reflection).toBeDefined();
expect(configs.reflection?.description).toBe(
"Custom reflection override from different filename",
);
});
});

View File

@@ -0,0 +1,41 @@
import { describe, expect, test } from "bun:test";
import { parseFrontmatter } from "../../utils/frontmatter";
describe("parseFrontmatter", () => {
test("parses LF frontmatter", () => {
const content = `---
name: reflection
description: custom reflection
---
Prompt body`;
const { frontmatter, body } = parseFrontmatter(content);
expect(frontmatter.name).toBe("reflection");
expect(frontmatter.description).toBe("custom reflection");
expect(body).toBe("Prompt body");
});
test("parses CRLF frontmatter", () => {
const content =
"---\r\nname: reflection\r\ndescription: custom reflection\r\n---\r\nPrompt body\r\nLine 2";
const { frontmatter, body } = parseFrontmatter(content);
expect(frontmatter.name).toBe("reflection");
expect(frontmatter.description).toBe("custom reflection");
expect(body).toBe("Prompt body\nLine 2");
expect(body.includes("\r")).toBe(false);
});
test("parses BOM + CRLF frontmatter", () => {
const content =
"\uFEFF---\r\nname: reflection\r\ndescription: custom reflection\r\n---\r\nPrompt body";
const { frontmatter, body } = parseFrontmatter(content);
expect(frontmatter.name).toBe("reflection");
expect(frontmatter.description).toBe("custom reflection");
expect(body).toBe("Prompt body");
});
});