From ab234d5f12be344cec26f35be130b11160e02d95 Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Thu, 15 Oct 2026 08:31:00 +0200 Subject: [PATCH] fix(app): skip prose classification for blocks over the render cap --- .../app/src/agent-stream/presentation.test.ts | 32 +++++++++++++++++++ .../app/src/components/markdown-prose/runs.ts | 4 +++ 2 files changed, 36 insertions(+) diff --git a/packages/app/src/agent-stream/presentation.test.ts b/packages/app/src/agent-stream/presentation.test.ts index 3376ffe8c6..d85338cfd2 100644 --- a/packages/app/src/agent-stream/presentation.test.ts +++ b/packages/app/src/agent-stream/presentation.test.ts @@ -1,4 +1,5 @@ import { QueryClient } from "@tanstack/react-query"; +import { ASSISTANT_MESSAGE_RENDER_CHARACTER_LIMIT } from "@/components/assistant-message-render-limit"; import { describe, expect, it } from "vitest"; import type { AgentStreamEventPayload } from "@getpaseo/protocol/messages"; import { runPluginClientBundle, type PluginClientRuntime } from "@/plugins/evaluate"; @@ -124,6 +125,37 @@ function rows(result: { tail: StreamItem[]; head: StreamItem[] }): StreamItem[] } describe("native prose runs", () => { + it("keeps an oversized history block on the existing block renderer", () => { + const text = "a".repeat(ASSISTANT_MESSAGE_RENDER_CHARACTER_LIMIT + 1); + const source = hydrateStreamState([{ event: assistant(text), timestamp: new Date(1000) }]); + const result = createStreamPresentation({ markdown: "prose-runs" })({ + ...presentationOptions, + tail: source, + head: [], + transform: undefined, + }); + expect(result.tail).toHaveLength(1); + expect(result.tail[0]).toMatchObject({ text, markdownKind: "block" }); + expect(result.tail[0]).not.toHaveProperty("markdownProseBlocks", expect.anything()); + }); + + it("keeps a normal history paragraph as selectable prose", () => { + const text = "A normal paragraph."; + const source = hydrateStreamState([{ event: assistant(text), timestamp: new Date(1000) }]); + const result = createStreamPresentation({ markdown: "prose-runs" })({ + ...presentationOptions, + tail: source, + head: [], + transform: undefined, + }); + expect(result.tail).toHaveLength(1); + expect(result.tail[0]).toMatchObject({ + text, + markdownKind: "prose", + markdownProseBlocks: [text], + }); + }); + it("retains completed runs and their source arrays while the tail grows", () => { const present = createStreamPresentation({ markdown: "prose-runs" }); let state: { tail: StreamItem[]; head: StreamItem[] } = { tail: [], head: [] }; diff --git a/packages/app/src/components/markdown-prose/runs.ts b/packages/app/src/components/markdown-prose/runs.ts index a58472afdf..6c61f7fff9 100644 --- a/packages/app/src/components/markdown-prose/runs.ts +++ b/packages/app/src/components/markdown-prose/runs.ts @@ -9,6 +9,10 @@ const partsByBlock = new WeakMap() function splitBlock(block: AssistantMessageItem): AssistantMessageItem[] { const cached = partsByBlock.get(block); if (cached) return cached; + // Oversized blocks cannot join capped runs; the existing renderer caps text before parsing. + if (block.text.length > ASSISTANT_MESSAGE_RENDER_CHARACTER_LIMIT) { + return [{ ...block, markdownKind: "block" }]; + } const environment: { references?: unknown } = {}; const tokens = parser.parse(block.text, environment); const preserved = tokens.some(