From c5ff4d40ae4f49aeaa0e104c7810952313a2cf2b Mon Sep 17 00:00:00 2001 From: OpenClaw Agent Date: Tue, 28 Apr 2026 14:08:21 +0800 Subject: [PATCH] fix: sendPayload now handles payload.mediaUrls (iterate all items, extraContent only on first) --- extensions/matrix/src/outbound.test.ts | 93 ++++++++++++++++++++++++++ extensions/matrix/src/outbound.ts | 28 +++++++- 2 files changed, 120 insertions(+), 1 deletion(-) diff --git a/extensions/matrix/src/outbound.test.ts b/extensions/matrix/src/outbound.test.ts index c34fee012fe..be3e7e0ca4f 100644 --- a/extensions/matrix/src/outbound.test.ts +++ b/extensions/matrix/src/outbound.test.ts @@ -257,4 +257,97 @@ describe("matrixOutbound cfg threading", () => { }), ); }); + + it("sends all media URLs via sendPayload", async () => { + const cfg = { + channels: { + matrix: { + accessToken: "resolved-token", + }, + }, + } as OpenClawConfig; + + await matrixOutbound.sendPayload!({ + cfg, + to: "room:!room:example", + text: "caption", + payload: { + text: "caption", + mediaUrls: ["file:///tmp/a.png", "file:///tmp/b.png"], + }, + accountId: "default", + threadId: "$thread", + }); + + expect(mocks.sendMessageMatrix).toHaveBeenCalledTimes(2); + // First call: caption + media + expect(mocks.sendMessageMatrix).toHaveBeenNthCalledWith( + 1, + "room:!room:example", + "caption", + expect.objectContaining({ + mediaUrl: "file:///tmp/a.png", + threadId: "$thread", + }), + ); + // Second call: no text, just media + expect(mocks.sendMessageMatrix).toHaveBeenNthCalledWith( + 2, + "room:!room:example", + "", + expect.objectContaining({ + mediaUrl: "file:///tmp/b.png", + threadId: "$thread", + }), + ); + }); + + it("sends mediaUrls with extraContent only on first item", async () => { + const cfg = { + channels: { + matrix: { + accessToken: "resolved-token", + }, + }, + } as OpenClawConfig; + + await matrixOutbound.sendPayload!({ + cfg, + to: "room:!room:example", + text: "caption", + payload: { + text: "caption", + mediaUrls: ["file:///tmp/a.png", "file:///tmp/b.png"], + channelData: { + matrix: { + extraContent: { + "com.openclaw.presentation": { version: 1 }, + }, + }, + }, + }, + accountId: "default", + threadId: "$thread", + }); + + expect(mocks.sendMessageMatrix).toHaveBeenCalledTimes(2); + // First call gets extraContent + expect(mocks.sendMessageMatrix).toHaveBeenNthCalledWith( + 1, + "room:!room:example", + "caption", + expect.objectContaining({ + extraContent: { "com.openclaw.presentation": { version: 1 } }, + }), + ); + // Second call does NOT get extraContent + expect(mocks.sendMessageMatrix).toHaveBeenNthCalledWith( + 2, + "room:!room:example", + "", + expect.not.objectContaining({ + extraContent: expect.anything(), + }), + ); + }); }); diff --git a/extensions/matrix/src/outbound.ts b/extensions/matrix/src/outbound.ts index 93f98898bae..160b0e14202 100644 --- a/extensions/matrix/src/outbound.ts +++ b/extensions/matrix/src/outbound.ts @@ -2,6 +2,7 @@ import { renderMessagePresentationFallbackText, type MessagePresentation, } from "openclaw/plugin-sdk/interactive-runtime"; +import { resolvePayloadMediaUrls } from "openclaw/plugin-sdk/reply-payload"; import type { ReplyPayload } from "openclaw/plugin-sdk/reply-runtime"; import { sendMessageMatrix, sendPollMatrix } from "./matrix/send.js"; import type { MatrixExtraContentFields } from "./matrix/send/types.js"; @@ -95,13 +96,38 @@ export const matrixOutbound: ChannelOutboundAdapter = { resolveOutboundSendDep(deps, "matrix") ?? sendMessageMatrix; const resolvedThreadId = threadId !== undefined && threadId !== null ? String(threadId) : undefined; + const resolvedReplyToId = replyToId ?? undefined; + const urls = resolvePayloadMediaUrls(payload); + if (urls.length > 0) { + let lastResult: Awaited> | undefined; + for (let i = 0; i < urls.length; i++) { + const isFirst = i === 0; + lastResult = await send(to, isFirst ? (payload.text ?? "") : "", { + cfg, + mediaUrl: urls[i], + mediaAccess, + mediaLocalRoots, + mediaReadFile, + replyToId: resolvedReplyToId, + threadId: resolvedThreadId, + accountId: accountId ?? undefined, + audioAsVoice, + extraContent: isFirst ? resolveMatrixExtraContent(payload) : undefined, + }); + } + return { + channel: "matrix", + messageId: lastResult!.messageId, + roomId: lastResult!.roomId, + }; + } const result = await send(to, payload.text ?? "", { cfg, mediaUrl: payload.mediaUrl, mediaAccess, mediaLocalRoots, mediaReadFile, - replyToId: replyToId ?? undefined, + replyToId: resolvedReplyToId, threadId: resolvedThreadId, accountId: accountId ?? undefined, audioAsVoice,