From 1464818fc17fe7da5fd1ab16f979ed4c5a68b5c4 Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Mon, 27 Jul 2026 18:39:30 +0200 Subject: [PATCH] fix(app): settle attachment-only submissions Canonical providers can acknowledge image-only prompts with empty text. Reconcile those events by client identity without rendering a blank canonical row. --- packages/app/e2e/agent-message-submission.spec.ts | 15 ++++++++++++--- packages/app/src/types/stream.ts | 5 +---- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/packages/app/e2e/agent-message-submission.spec.ts b/packages/app/e2e/agent-message-submission.spec.ts index d67332e68..509f408de 100644 --- a/packages/app/e2e/agent-message-submission.spec.ts +++ b/packages/app/e2e/agent-message-submission.spec.ts @@ -153,6 +153,16 @@ async function submitMessageWithImage(page: Page, prompt: string): Promise { + await attachImageFromMenu(page, IMAGE); + await expectAttachmentPill(page, "composer-image-attachment-pill"); + await page.getByRole("textbox", { name: "Message agent..." }).first().press("Enter"); + const userMessage = page.getByTestId("user-message").last(); + await expect(userMessage).toBeVisible(); + await expect(userMessage.getByRole("button", { name: "Open image attachment" })).toBeVisible(); + return userMessage; +} + async function expectPendingSubmission(page: Page, userMessage: Locator): Promise { await expect(userMessage).toBeVisible(); await expect(page.getByTestId("turn-working-indicator")).toBeVisible(); @@ -312,13 +322,12 @@ async function expectCompletedSubmissionClearsAfterMissedRunningTransition( title: "Submission missed running transition", model: "ten-second-stream", }); - const prompt = "Clear this submission from canonical history."; try { await openAgentRoute(page, { workspaceId: agent.workspaceId, agentId: agent.agentId }); await expectComposerVisible(page); await expectAgentIdle(page); gate.holdNextClientRequest("send_agent_message_request"); - const userMessage = await submitMessageWithImage(page, prompt); + const userMessage = await submitImageOnlyMessage(page); await gate.waitForHeldClientRequest(); gate.setServerMessageSuppressed("agent_status", true); gate.setServerMessageSuppressed("agent_update", true); @@ -591,7 +600,7 @@ test.describe("Agent message submission", () => { await expectInterruptedTurnOrderAfterReconnect(page, testInfo); }); - test("clears an accepted submission when canonical history arrives after a missed running transition", async ({ + test("clears an attachment-only submission when canonical history arrives after a missed running transition", async ({ page, }, testInfo) => { test.setTimeout(90_000); diff --git a/packages/app/src/types/stream.ts b/packages/app/src/types/stream.ts index 645c4c9cc..2b39e498c 100644 --- a/packages/app/src/types/stream.ts +++ b/packages/app/src/types/stream.ts @@ -1498,9 +1498,6 @@ function applyCanonicalUserMessageEvent(params: { const { tail, head, event, timestamp } = params; if (event.type !== "timeline" || event.item.type !== "user_message") return null; const normalized = normalizeChunk(event.item.text); - if (!normalized.hasContent) { - return { tail, head, changedTail: false, changedHead: false }; - } const flushedTail = head.length > 0 ? flushHeadToTail(tail, head) : tail; const flushedHead = head.length > 0 ? [] : head; @@ -1517,7 +1514,7 @@ function applyCanonicalUserMessageEvent(params: { tail: flushedTail, head: flushedHead, message: canonical, - insert: "tail", + insert: normalized.hasContent ? "tail" : "none", presentation: "existing", }); return {