fix: normalize literal \n escape sequences in parseMarkdownChecklist

Some models/providers send the todos parameter with literal escape
sequences (the two-character string \n) rather than actual newline
characters. This causes all checklist items to end up on a single line,
resulting in only one todo item being parsed.

Normalize literal \n and \r\n sequences into actual newlines before
splitting. Adds test coverage for this scenario.

Closes #11977
This commit is contained in:
Roo Code 2026-03-21 19:01:25 +00:00
parent 137d3f4fd8
commit 7a5964c797
2 changed files with 33 additions and 1 deletions

View file

@ -177,7 +177,10 @@ function normalizeStatus(status: string | undefined): TodoStatus {
export function parseMarkdownChecklist(md: string): TodoItem[] {
if (typeof md !== "string") return []
const lines = md
// Normalize literal escape sequences (e.g. "\\n", "\\r\\n") that some
// models/providers send instead of actual newline characters.
const normalized = md.replace(/\\r\\n|\\n/g, "\n")
const lines = normalized
.split(/\r?\n/)
.map((l) => l.trim())
.filter(Boolean)

View file

@ -205,6 +205,35 @@ Just some text
})
})
describe("literal escape sequence normalization", () => {
it("should parse items separated by literal \\n escape sequences", () => {
const md = "[ ] Task 1\\n[x] Task 2\\n[-] Task 3"
const result = parseMarkdownChecklist(md)
expect(result).toHaveLength(3)
expect(result[0].content).toBe("Task 1")
expect(result[0].status).toBe("pending")
expect(result[1].content).toBe("Task 2")
expect(result[1].status).toBe("completed")
expect(result[2].content).toBe("Task 3")
expect(result[2].status).toBe("in_progress")
})
it("should parse items separated by literal \\r\\n escape sequences", () => {
const md = "[ ] Task 1\\r\\n- [x] Task 2\\r\\n[~] Task 3"
const result = parseMarkdownChecklist(md)
expect(result).toHaveLength(3)
expect(result[0].content).toBe("Task 1")
expect(result[1].content).toBe("Task 2")
expect(result[2].content).toBe("Task 3")
})
it("should handle a mix of literal and actual newlines", () => {
const md = "[ ] Task 1\\n[x] Task 2\n[-] Task 3"
const result = parseMarkdownChecklist(md)
expect(result).toHaveLength(3)
})
})
describe("ID generation", () => {
it("should generate consistent IDs for the same content and status", () => {
const md1 = `[ ] Task 1