From 83f2eabd49763ae06fe3d76e6fa87bf5c2ae9161 Mon Sep 17 00:00:00 2001 From: Marcus Castro Date: Wed, 1 Apr 2026 23:54:05 -0300 Subject: [PATCH] fix: guard mediaType fallback, add missing MIME + send-api tests, changelog --- CHANGELOG.md | 1 + .../whatsapp/src/inbound/send-api.test.ts | 20 +++++++++++++++++++ extensions/whatsapp/src/inbound/send-api.ts | 4 +++- src/media/mime.test.ts | 1 + 4 files changed, 25 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b346ee32922..117a5b7d348 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1030,6 +1030,7 @@ Docs: https://docs.openclaw.ai - Exec: harden host env override handling across gateway and node (#51207) Thanks @gladiator9797 and @joshavant. - Voice Call: enforce spoken-output contract and fix stream TTS silence regression (#51500) Thanks @joshavant. - xAI/models: rename the bundled Grok 4.20 catalog entries to the GA IDs and normalize saved deprecated beta IDs at runtime so existing configs and sessions keep resolving. (#50772) thanks @Jaaneek +- WhatsApp/outbound media: fix HTML, XML, and CSS files being silently dropped on outbound send by adding missing MIME entries and falling back to `application/octet-stream` for unknown media types. (#51562) Thanks @bobbyt74 - Agents/bootstrap warnings: move bootstrap truncation warnings out of the system prompt and into the per-turn prompt body so prompt-cache reuse stays stable when truncation warnings appear or disappear. (#48753) Thanks @scoootscooob and @obviyus. - Telegram/DM topic session keys: route named-account DM topics through the same per-account base session key across inbound messages, native commands, and session-state lookups so `/status` and thread recovery stop creating phantom `agent:main:main:thread:...` sessions. (#48204) Thanks @vincentkoc. - ACP/configured bindings: reinitialize configured ACP sessions that are stuck in `error` state instead of reusing the failed runtime. diff --git a/extensions/whatsapp/src/inbound/send-api.test.ts b/extensions/whatsapp/src/inbound/send-api.test.ts index 8173176f7af..92ca1009d5c 100644 --- a/extensions/whatsapp/src/inbound/send-api.test.ts +++ b/extensions/whatsapp/src/inbound/send-api.test.ts @@ -162,4 +162,24 @@ describe("createWebSendApi", () => { await api.sendComposingTo("+1555"); expect(sendPresenceUpdate).toHaveBeenCalledWith("composing", "1555@s.whatsapp.net"); }); + + it("sends media as document when mediaType is undefined", async () => { + const mediaBuffer = Buffer.from("test"); + + await api.sendMessage("123", "hello", mediaBuffer, undefined); + + expect(sendMessage).toHaveBeenCalledWith( + "123@s.whatsapp.net", + expect.objectContaining({ + document: mediaBuffer, + mimetype: "application/octet-stream", + }), + ); + }); + + it("does not set mediaType when mediaBuffer is absent", async () => { + await api.sendMessage("123", "hello"); + + expect(sendMessage).toHaveBeenCalledWith("123@s.whatsapp.net", { text: "hello" }); + }); }); diff --git a/extensions/whatsapp/src/inbound/send-api.ts b/extensions/whatsapp/src/inbound/send-api.ts index 7b7145a5e28..3c5d91c8069 100644 --- a/extensions/whatsapp/src/inbound/send-api.ts +++ b/extensions/whatsapp/src/inbound/send-api.ts @@ -34,7 +34,9 @@ export function createWebSendApi(params: { ): Promise<{ messageId: string }> => { const jid = toWhatsappJid(to); let payload: AnyMessageContent; - mediaType ??= "application/octet-stream"; + if (mediaBuffer) { + mediaType ??= "application/octet-stream"; + } if (mediaBuffer && mediaType) { if (mediaType.startsWith("image/")) { payload = { diff --git a/src/media/mime.test.ts b/src/media/mime.test.ts index 3e699ba5b94..68da516c23d 100644 --- a/src/media/mime.test.ts +++ b/src/media/mime.test.ts @@ -138,6 +138,7 @@ describe("extensionForMime", () => { { mime: "text/html", expected: ".html" }, { mime: "text/xml", expected: ".xml" }, { mime: "text/css", expected: ".css" }, + { mime: "application/xml", expected: ".xml" }, { mime: "IMAGE/JPEG", expected: ".jpg" }, { mime: "Audio/X-M4A", expected: ".m4a" }, { mime: "Video/QuickTime", expected: ".mov" },