From 1b6c43baa47f9e09a670bc055b73ee32b65bc693 Mon Sep 17 00:00:00 2001 From: Joey Krug Date: Sun, 15 Mar 2026 21:54:48 -0400 Subject: [PATCH] fix: stop inferring file extraction from Body/RawBody mismatch (#46454) --- src/auto-reply/reply/followup-media.ts | 28 +++++------- src/auto-reply/reply/followup-runner.test.ts | 48 ++++++++++++++++++++ src/auto-reply/reply/queue/types.ts | 6 +-- 3 files changed, 62 insertions(+), 20 deletions(-) diff --git a/src/auto-reply/reply/followup-media.ts b/src/auto-reply/reply/followup-media.ts index dca7b4b414b..19b2c061c8c 100644 --- a/src/auto-reply/reply/followup-media.ts +++ b/src/auto-reply/reply/followup-media.ts @@ -292,27 +292,21 @@ export async function applyDeferredMediaUnderstandingToQueuedRun( return; } - const referenceBody = mediaContext.RawBody ?? mediaContext.Body; - // Prefer RawBody-vs-Body comparison when RawBody exists. If RawBody is - // missing, any real ... block plus file-like attachments means - // extraction already ran, even if the stored name came from Content-Disposition - // instead of the attachment path/url basename. - if (!mediaContext.DeferredFileBlocksExtracted && hasAnyFileAttachments(mediaContext)) { - const rawBodyMissing = typeof mediaContext.RawBody !== "string"; - if (mediaContext.Body !== referenceBody) { - mediaContext.DeferredFileBlocksExtracted = true; - } else if ( - rawBodyMissing && - (Boolean(mediaContext.MediaUnderstanding?.length) || - bodyContainsExtractedFileBlock(mediaContext.Body)) - ) { - mediaContext.DeferredFileBlocksExtracted = true; - } - } if (mediaContext.MediaUnderstanding?.length) { mediaContext.DeferredMediaApplied = true; return; } + // Treat followup file extraction as already applied only when we have explicit + // evidence: the queue snapshot already flagged it or Body already contains a + // real extracted ... block. Body/RawBody mismatches are not + // reliable because some channels wrap Body with envelope metadata. + if ( + !mediaContext.DeferredFileBlocksExtracted && + hasAnyFileAttachments(mediaContext) && + bodyContainsExtractedFileBlock(mediaContext.Body) + ) { + mediaContext.DeferredFileBlocksExtracted = true; + } if (mediaContext.DeferredFileBlocksExtracted && hasOnlyFileLikeAttachments(mediaContext)) { mediaContext.DeferredMediaApplied = true; diff --git a/src/auto-reply/reply/followup-runner.test.ts b/src/auto-reply/reply/followup-runner.test.ts index 0c8ac63d4d7..f766aa57321 100644 --- a/src/auto-reply/reply/followup-runner.test.ts +++ b/src/auto-reply/reply/followup-runner.test.ts @@ -1231,6 +1231,54 @@ describe("createFollowupRunner media understanding", () => { expect(agentCall?.prompt?.match(/ { + const fileBlock = '\nreport content\n'; + applyMediaUnderstandingMock.mockImplementationOnce( + async (params: { ctx: Record }) => { + params.ctx.Body = `summarize this\n\n${fileBlock}`; + return { + outputs: [], + decisions: [], + appliedImage: false, + appliedAudio: false, + appliedVideo: false, + appliedFile: true, + }; + }, + ); + runEmbeddedPiAgentMock.mockResolvedValueOnce({ + payloads: [{ text: "file processed" }], + meta: {}, + }); + + const runner = createFollowupRunner({ + opts: { onBlockReply: vi.fn(async () => {}) }, + typing: createMockTypingController(), + typingMode: "instant", + defaultModel: "anthropic/claude-opus-4-5", + }); + + await runner( + createQueuedRun({ + prompt: "[media attached: /tmp/report.pdf]\nLine: Alice\nsummarize this", + mediaContext: { + Body: "Line: Alice\nsummarize this", + RawBody: "summarize this", + MediaPaths: ["/tmp/report.pdf"], + MediaTypes: ["application/pdf"], + }, + }), + ); + + expect(applyMediaUnderstandingMock).toHaveBeenCalledTimes(1); + const agentCall = runEmbeddedPiAgentMock.mock.calls.at(-1)?.[0] as { + prompt?: string; + }; + expect(agentCall?.prompt).toContain("Line: Alice"); + expect(agentCall?.prompt).toContain(fileBlock); + expect(agentCall?.prompt?.match(/ { applyMediaUnderstandingMock.mockImplementationOnce( async (params: { ctx: Record }) => { diff --git a/src/auto-reply/reply/queue/types.ts b/src/auto-reply/reply/queue/types.ts index e1e9e20e5c8..1c2f5e0551a 100644 --- a/src/auto-reply/reply/queue/types.ts +++ b/src/auto-reply/reply/queue/types.ts @@ -52,9 +52,9 @@ export type FollowupMediaContext = { DeferredMediaApplied?: boolean; /** * Set when file extraction has already been applied to Body (either in the - * primary path or by a previous deferred-media run). Checked instead of - * scanning body text for `...` + * blocks. */ DeferredFileBlocksExtracted?: boolean; };