Outbound: allow text-only plugin adapters
This commit is contained in:
parent
e6f0203ef3
commit
efdf2ca0d7
@ -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/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/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.
|
- 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/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.
|
- 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.
|
- 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.
|
||||||
|
|||||||
@ -890,6 +890,37 @@ describe("deliverOutboundPayloads", () => {
|
|||||||
expect(results).toEqual([{ channel: "line", messageId: "ln-1" }]);
|
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 () => {
|
it("emits message_sent failure when delivery errors", async () => {
|
||||||
hookMocks.runner.hasHooks.mockReturnValue(true);
|
hookMocks.runner.hasHooks.mockReturnValue(true);
|
||||||
const sendWhatsApp = vi.fn().mockRejectedValue(new Error("downstream failed"));
|
const sendWhatsApp = vi.fn().mockRejectedValue(new Error("downstream failed"));
|
||||||
|
|||||||
@ -149,7 +149,7 @@ function createPluginHandler(
|
|||||||
params: ChannelHandlerParams & { outbound?: ChannelOutboundAdapter },
|
params: ChannelHandlerParams & { outbound?: ChannelOutboundAdapter },
|
||||||
): ChannelHandler | null {
|
): ChannelHandler | null {
|
||||||
const outbound = params.outbound;
|
const outbound = params.outbound;
|
||||||
if (!outbound?.sendText || !outbound?.sendMedia) {
|
if (!outbound?.sendText) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const baseCtx = createChannelOutboundContextBase(params);
|
const baseCtx = createChannelOutboundContextBase(params);
|
||||||
@ -183,12 +183,19 @@ function createPluginHandler(
|
|||||||
...resolveCtx(overrides),
|
...resolveCtx(overrides),
|
||||||
text,
|
text,
|
||||||
}),
|
}),
|
||||||
sendMedia: async (caption, mediaUrl, overrides) =>
|
sendMedia: async (caption, mediaUrl, overrides) => {
|
||||||
sendMedia({
|
if (sendMedia) {
|
||||||
|
return sendMedia({
|
||||||
|
...resolveCtx(overrides),
|
||||||
|
text: caption,
|
||||||
|
mediaUrl,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return sendText({
|
||||||
...resolveCtx(overrides),
|
...resolveCtx(overrides),
|
||||||
text: caption,
|
text: caption,
|
||||||
mediaUrl,
|
});
|
||||||
}),
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user