From a7d6df9f3cb019ce7cf5c3b05499ae9cd82b51f6 Mon Sep 17 00:00:00 2001 From: Taylor Ho Date: Mon, 15 Jun 2026 10:31:26 -0700 Subject: [PATCH] fix(agents): render formatted prompt bubbles - Render user prompt transcript bubbles through the shared Markdown component so bullets, line breaks, and inline formatting display like normal messages - Preserve multi-line Buzz event Content fields when parsing ACP prompt payloads, stopping before harness metadata such as Tags and Parsed fields - Add transcript helper coverage for a multi-line mention prompt to guard against flattening formatted responses back to one line --- .../agents/ui/AgentSessionTranscriptList.tsx | 4 +-- .../ui/agentSessionTranscriptHelpers.test.mjs | 31 +++++++++++++++++ .../ui/agentSessionTranscriptHelpers.ts | 34 +++++++++++++++++-- 3 files changed, 65 insertions(+), 4 deletions(-) diff --git a/desktop/src/features/agents/ui/AgentSessionTranscriptList.tsx b/desktop/src/features/agents/ui/AgentSessionTranscriptList.tsx index f12e650e47..358e369a7f 100644 --- a/desktop/src/features/agents/ui/AgentSessionTranscriptList.tsx +++ b/desktop/src/features/agents/ui/AgentSessionTranscriptList.tsx @@ -291,7 +291,7 @@ function PromptUserMessage({ />
-

{text}

+ {contextOpen && context ? ( ) : null} @@ -607,7 +607,7 @@ function MessageItem({ ) : ( <> -

{text}

+ )} diff --git a/desktop/src/features/agents/ui/agentSessionTranscriptHelpers.test.mjs b/desktop/src/features/agents/ui/agentSessionTranscriptHelpers.test.mjs index e84af14683..1f7b4b5a22 100644 --- a/desktop/src/features/agents/ui/agentSessionTranscriptHelpers.test.mjs +++ b/desktop/src/features/agents/ui/agentSessionTranscriptHelpers.test.mjs @@ -65,6 +65,37 @@ test("parsePromptText extracts content, hex pubkey, and a title-cased kind", () ); }); +test("parsePromptText preserves multiline event content in the user bubble text", () => { + const text = [ + "[Buzz event: @mention]", + "Event ID: event-1", + "Channel: agents", + `From: tho (hex: ${HEX})`, + "Time: 2026-06-15T17:15:00Z", + "Content: @Ned", + "", + "- remove that stray cherry pick if it's not adding value here", + "- help me understand what that e2eBridge change does", + "- we'd want the e2e seed path as a separate pull request", + 'Tags: [["h","agents"]]', + "Parsed: mentions=[Ned]", + ].join("\n"); + + const result = parsePromptText(text); + + assert.equal( + result.userText, + [ + "@Ned", + "", + "- remove that stray cherry pick if it's not adding value here", + "- help me understand what that e2eBridge change does", + "- we'd want the e2e seed path as a separate pull request", + ].join("\n"), + ); + assert.equal(result.userPubkey, HEX); +}); + test("parsePromptText lowercases the extracted hex pubkey", () => { const text = [ "[Buzz event: dm]", diff --git a/desktop/src/features/agents/ui/agentSessionTranscriptHelpers.ts b/desktop/src/features/agents/ui/agentSessionTranscriptHelpers.ts index 00c13c0567..b74f8cd8ac 100644 --- a/desktop/src/features/agents/ui/agentSessionTranscriptHelpers.ts +++ b/desktop/src/features/agents/ui/agentSessionTranscriptHelpers.ts @@ -85,9 +85,39 @@ function parsePromptSections(text: string): PromptSection[] { return sections; } +const EVENT_CONTENT_BOUNDARY_RE = + /^(?:Event ID|Channel|Kind|From|Time|Tags|Parsed):\s*/; +const EVENT_BLOCK_BOUNDARY_RE = /^--- Event \d+\b/; + function extractEventContent(body: string): string { - const contentMatch = body.match(/^Content:\s*(.*)$/m); - return contentMatch?.[1]?.trim() ?? ""; + const lines = body.split(/\r?\n/); + const chunks: string[] = []; + + for (let i = 0; i < lines.length; i++) { + const match = lines[i].match(/^Content:\s?(.*)$/); + if (!match) { + continue; + } + + const contentLines = [match[1] ?? ""]; + for (let j = i + 1; j < lines.length; j++) { + const line = lines[j]; + if ( + EVENT_CONTENT_BOUNDARY_RE.test(line) || + EVENT_BLOCK_BOUNDARY_RE.test(line) + ) { + break; + } + contentLines.push(line); + } + + const content = contentLines.join("\n").trim(); + if (content) { + chunks.push(content); + } + } + + return chunks.join("\n\n"); } function extractEventAuthorPubkey(body: string): string | null {