From 39115527dc6d2326d6e386c29f70fa6d79ba3d03 Mon Sep 17 00:00:00 2001 From: abhay-codes07 Date: Thu, 2 Jul 2026 08:12:51 +0530 Subject: [PATCH] fix(web): stop truncating multi-line plugin transcript messages parseTranscriptMessages builds its regex with the m flag, and the lookahead that terminates a message body ends with |$. Under m, $ matches at every line end, so the lazy [\s\S]*? body stops at the first newline: every line of a message after the first was silently dropped from Codex session and Amp thread documents. Continuation lines carrying "memory id: ..." were lost too, so extractArtifacts never surfaced their Memory ID chips, and previews/message counts were computed from truncated text. Replace the |$ alternative with (?![\s\S]), a true end-of-input assertion unaffected by the m flag. The adjacent parseClaudeCodeTurns regex is fine as-is - it uses $ without the m flag. Add regression tests through parsePluginDocument covering multi-line bodies, memory-id artifact extraction from continuation lines, literal \n normalization, and unchanged single-line behavior. The multi-line cases fail against the previous regex. Fixes #1184 --- apps/web/lib/plugin-document.test.ts | 86 ++++++++++++++++++++++++++++ apps/web/lib/plugin-document.ts | 5 +- 2 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 apps/web/lib/plugin-document.test.ts diff --git a/apps/web/lib/plugin-document.test.ts b/apps/web/lib/plugin-document.test.ts new file mode 100644 index 000000000..79dc630d2 --- /dev/null +++ b/apps/web/lib/plugin-document.test.ts @@ -0,0 +1,86 @@ +import { describe, expect, it } from "bun:test" +import { parsePluginDocument } from "./plugin-document" + +type PluginDocumentInput = Parameters[0] + +function makeCodexSessionDocument(content: string): PluginDocumentInput { + return { + id: "doc_1", + title: "Codex session", + content, + metadata: { sm_source: "codex" }, + memoryEntries: [], + } as unknown as PluginDocumentInput +} + +describe("parsePluginDocument — session transcripts", () => { + it("keeps multi-line message bodies intact", () => { + const parsed = parsePluginDocument( + makeCodexSessionDocument( + [ + "[Session abc-123]", + "1. [user] Hello there", + "Here is more context on line two", + "2. [assistant] Sure!", + "Second line of the reply", + ].join("\n"), + ), + ) + + expect(parsed).not.toBeNull() + expect(parsed?.kind).toBe("codex-session") + expect(parsed?.messages).toHaveLength(2) + expect(parsed?.messages[0]?.text).toBe( + "Hello there\nHere is more context on line two", + ) + expect(parsed?.messages[1]?.text).toBe("Sure!\nSecond line of the reply") + }) + + it("surfaces memory id artifacts from continuation lines", () => { + const parsed = parsePluginDocument( + makeCodexSessionDocument( + [ + "[Session abc-123]", + "1. [user] Remember my editor is Neovim", + "memory id: mem_456", + "2. [assistant] Saved it.", + ].join("\n"), + ), + ) + + expect(parsed?.messages[0]?.text).toBe("Remember my editor is Neovim") + expect(parsed?.artifacts).toContainEqual({ + label: "Memory ID", + value: "mem_456", + }) + }) + + it("normalizes literal \\n escapes before splitting messages", () => { + const parsed = parsePluginDocument( + makeCodexSessionDocument( + "[Session abc-123]\\n1. [user] First line\\nSecond line\\n2. [assistant] Reply", + ), + ) + + expect(parsed?.messages).toHaveLength(2) + expect(parsed?.messages[0]?.text).toBe("First line\nSecond line") + expect(parsed?.messages[1]?.text).toBe("Reply") + }) + + it("parses single-line messages as before", () => { + const parsed = parsePluginDocument( + makeCodexSessionDocument( + ["[Session abc-123]", "1. [user] Hi", "2. [assistant] Hello!"].join( + "\n", + ), + ), + ) + + expect(parsed?.messages).toHaveLength(2) + expect(parsed?.messages[0]?.text).toBe("Hi") + expect(parsed?.messages[1]?.text).toBe("Hello!") + expect(parsed?.summary).toBe( + "1 user message and 1 assistant message captured from Codex.", + ) + }) +}) diff --git a/apps/web/lib/plugin-document.ts b/apps/web/lib/plugin-document.ts index 3a13ec245..1606311f5 100644 --- a/apps/web/lib/plugin-document.ts +++ b/apps/web/lib/plugin-document.ts @@ -227,8 +227,11 @@ function parseTranscriptMessages(content: string): { } { const messages: PluginDocumentMessage[] = [] const artifacts: PluginArtifact[] = [] + // The lookahead must end the last message at the true end of input: + // with the m flag, a bare $ matches every line end and would cut each + // message body off at its first newline. const regex = new RegExp( - `^\\s*(\\d+)\\.\\s+\\[(${TRANSCRIPT_ROLE_PATTERN})\\]\\s*([\\s\\S]*?)(?=^\\s*\\d+\\.\\s+\\[(?:${TRANSCRIPT_ROLE_PATTERN})\\]\\s*|$)`, + `^\\s*(\\d+)\\.\\s+\\[(${TRANSCRIPT_ROLE_PATTERN})\\]\\s*([\\s\\S]*?)(?=^\\s*\\d+\\.\\s+\\[(?:${TRANSCRIPT_ROLE_PATTERN})\\]\\s*|(?![\\s\\S]))`, "gm", )