diff --git a/src/core/tools/UpdateTodoListTool.ts b/src/core/tools/UpdateTodoListTool.ts index 7414b713cf..3f23ed51ed 100644 --- a/src/core/tools/UpdateTodoListTool.ts +++ b/src/core/tools/UpdateTodoListTool.ts @@ -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) diff --git a/src/core/tools/__tests__/updateTodoListTool.spec.ts b/src/core/tools/__tests__/updateTodoListTool.spec.ts index ebe0500d66..6549090f81 100644 --- a/src/core/tools/__tests__/updateTodoListTool.spec.ts +++ b/src/core/tools/__tests__/updateTodoListTool.spec.ts @@ -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