From efdf2ca0d79d10146429d040bc641f4fd2a5688d Mon Sep 17 00:00:00 2001 From: liuxiaopai-ai <73659136+liuxiaopai-ai@users.noreply.github.com> Date: Tue, 3 Mar 2026 16:40:45 +0800 Subject: [PATCH] Outbound: allow text-only plugin adapters --- CHANGELOG.md | 1 + src/infra/outbound/deliver.test.ts | 31 ++++++++++++++++++++++++++++++ src/infra/outbound/deliver.ts | 17 +++++++++++----- 3 files changed, 44 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0380829eaf7..a1700b88545 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -75,6 +75,7 @@ Docs: https://docs.openclaw.ai - iOS/Concurrency stability: replace risky shared-state access in camera and gateway connection paths with lock-protected access patterns to reduce crash risk under load. (#33241) thanks @mbelinky. - iOS/Security guardrails: limit production API-key sourcing to app config and make deep-link confirmation prompts safer by coalescing queued requests instead of silently dropping them. (#33031) thanks @mbelinky. - iOS/TTS playback fallback: keep voice playback resilient by switching from PCM to MP3 when provider format support is unavailable, while avoiding sticky fallback on generic local playback errors. (#33032) thanks @mbelinky. +- Plugin outbound/text-only adapter compatibility: allow direct-delivery channel plugins that only implement `sendText` (no `sendMedia`) to remain outbound-capable, and gracefully fall back to text delivery for media payloads when `sendMedia` is absent. (#32788) thanks @liuxiaopai-ai. - Telegram/multi-account default routing clarity: warn only for ambiguous (2+) account setups without an explicit default, add `openclaw doctor` warnings for missing/invalid multi-account defaults across channels, and document explicit-default guidance for channel routing and Telegram config. (#32544) thanks @Sid-Qin. - Telegram/plugin outbound hook parity: run `message_sending` + `message_sent` in Telegram reply delivery, include reply-path hook metadata (`mediaUrls`, `threadId`), and report `message_sent.success=false` when hooks blank text and no outbound message is delivered. (#32649) Thanks @KimGLee. - CLI/Coding-agent reliability: switch default `claude-cli` non-interactive args to `--permission-mode bypassPermissions`, auto-normalize legacy `--dangerously-skip-permissions` backend overrides to the modern permission-mode form, align coding-agent + live-test docs with the non-PTY Claude path, and emit session system-event heartbeat notices when CLI watchdog no-output timeouts terminate runs. (#28610, #31149, #34055). Thanks @niceysam, @cryptomaltese and @vincentkoc. diff --git a/src/infra/outbound/deliver.test.ts b/src/infra/outbound/deliver.test.ts index ca6652b41b1..cbab6d00cf3 100644 --- a/src/infra/outbound/deliver.test.ts +++ b/src/infra/outbound/deliver.test.ts @@ -890,6 +890,37 @@ describe("deliverOutboundPayloads", () => { expect(results).toEqual([{ channel: "line", messageId: "ln-1" }]); }); + it("falls back to sendText when plugin outbound omits sendMedia", async () => { + const sendText = vi.fn().mockResolvedValue({ channel: "matrix", messageId: "mx-1" }); + setActivePluginRegistry( + createTestRegistry([ + { + pluginId: "matrix", + source: "test", + plugin: createOutboundTestPlugin({ + id: "matrix", + outbound: { deliveryMode: "direct", sendText }, + }), + }, + ]), + ); + + const results = await deliverOutboundPayloads({ + cfg: {}, + channel: "matrix", + to: "!room:1", + payloads: [{ text: "caption", mediaUrl: "https://example.com/file.png" }], + }); + + expect(sendText).toHaveBeenCalledTimes(1); + expect(sendText).toHaveBeenCalledWith( + expect.objectContaining({ + text: "caption", + }), + ); + expect(results).toEqual([{ channel: "matrix", messageId: "mx-1" }]); + }); + it("emits message_sent failure when delivery errors", async () => { hookMocks.runner.hasHooks.mockReturnValue(true); const sendWhatsApp = vi.fn().mockRejectedValue(new Error("downstream failed")); diff --git a/src/infra/outbound/deliver.ts b/src/infra/outbound/deliver.ts index 45bff297065..6dcffddc1f5 100644 --- a/src/infra/outbound/deliver.ts +++ b/src/infra/outbound/deliver.ts @@ -149,7 +149,7 @@ function createPluginHandler( params: ChannelHandlerParams & { outbound?: ChannelOutboundAdapter }, ): ChannelHandler | null { const outbound = params.outbound; - if (!outbound?.sendText || !outbound?.sendMedia) { + if (!outbound?.sendText) { return null; } const baseCtx = createChannelOutboundContextBase(params); @@ -183,12 +183,19 @@ function createPluginHandler( ...resolveCtx(overrides), text, }), - sendMedia: async (caption, mediaUrl, overrides) => - sendMedia({ + sendMedia: async (caption, mediaUrl, overrides) => { + if (sendMedia) { + return sendMedia({ + ...resolveCtx(overrides), + text: caption, + mediaUrl, + }); + } + return sendText({ ...resolveCtx(overrides), text: caption, - mediaUrl, - }), + }); + }, }; }