fix(extensions): preserve mediaLocalRoots in telegram/discord sendMedia

This commit is contained in:
Peter Steinberger
2026-02-22 22:52:49 +01:00
parent 1e582dcc6f
commit 4adfe80027
5 changed files with 90 additions and 2 deletions

View File

@@ -32,6 +32,7 @@ Docs: https://docs.openclaw.ai
### Fixes
- Telegram/Discord extensions: propagate trusted `mediaLocalRoots` through extension outbound `sendMedia` options so extension direct-send media paths honor agent-scoped local-media allowlists. (#20029, #21903, #23227)
- Plugins/Media sandbox: propagate trusted `mediaLocalRoots` through plugin action dispatch (including Discord/Telegram action adapters) so plugin send paths enforce the same agent-scoped local-media sandbox roots as core outbound sends. (#20258, #22718)
- Agents/Workspace guard: map sandbox container-workdir file-tool paths (for example `/workspace/...` and `file:///workspace/...`) to host workspace roots before workspace-only validation, preventing false `Path escapes sandbox root` rejections for sandbox file tools. (#9560)
- Gateway/Exec approvals: expire approval requests immediately when no approval-capable gateway clients are connected and no forwarding targets are available, avoiding delayed approvals after restarts/offline approver windows. (#22144)

View File

@@ -0,0 +1,36 @@
import type { OpenClawConfig, PluginRuntime } from "openclaw/plugin-sdk";
import { describe, expect, it, vi } from "vitest";
import { discordPlugin } from "./channel.js";
import { setDiscordRuntime } from "./runtime.js";
describe("discordPlugin outbound", () => {
it("forwards mediaLocalRoots to sendMessageDiscord", async () => {
const sendMessageDiscord = vi.fn(async () => ({ messageId: "m1" }));
setDiscordRuntime({
channel: {
discord: {
sendMessageDiscord,
},
},
} as unknown as PluginRuntime);
const result = await discordPlugin.outbound!.sendMedia!({
cfg: {} as OpenClawConfig,
to: "channel:123",
text: "hi",
mediaUrl: "/tmp/image.png",
mediaLocalRoots: ["/tmp/agent-root"],
accountId: "work",
});
expect(sendMessageDiscord).toHaveBeenCalledWith(
"channel:123",
"hi",
expect.objectContaining({
mediaUrl: "/tmp/image.png",
mediaLocalRoots: ["/tmp/agent-root"],
}),
);
expect(result).toMatchObject({ channel: "discord", messageId: "m1" });
});
});

View File

@@ -311,11 +311,21 @@ export const discordPlugin: ChannelPlugin<ResolvedDiscordAccount> = {
});
return { channel: "discord", ...result };
},
sendMedia: async ({ to, text, mediaUrl, accountId, deps, replyToId, silent }) => {
sendMedia: async ({
to,
text,
mediaUrl,
mediaLocalRoots,
accountId,
deps,
replyToId,
silent,
}) => {
const send = deps?.sendDiscord ?? getDiscordRuntime().channel.discord.sendMessageDiscord;
const result = await send(to, text, {
verbose: false,
mediaUrl,
mediaLocalRoots,
replyTo: replyToId ?? undefined,
accountId: accountId ?? undefined,
silent: silent ?? undefined,

View File

@@ -162,4 +162,34 @@ describe("telegramPlugin duplicate token guard", () => {
}),
);
});
it("forwards mediaLocalRoots to sendMessageTelegram for outbound media sends", async () => {
const sendMessageTelegram = vi.fn(async () => ({ messageId: "tg-1" }));
setTelegramRuntime({
channel: {
telegram: {
sendMessageTelegram,
},
},
} as unknown as PluginRuntime);
const result = await telegramPlugin.outbound!.sendMedia!({
cfg: createCfg(),
to: "12345",
text: "hello",
mediaUrl: "/tmp/image.png",
mediaLocalRoots: ["/tmp/agent-root"],
accountId: "ops",
});
expect(sendMessageTelegram).toHaveBeenCalledWith(
"12345",
"hello",
expect.objectContaining({
mediaUrl: "/tmp/image.png",
mediaLocalRoots: ["/tmp/agent-root"],
}),
);
expect(result).toMatchObject({ channel: "telegram", messageId: "tg-1" });
});
});

View File

@@ -332,13 +332,24 @@ export const telegramPlugin: ChannelPlugin<ResolvedTelegramAccount, TelegramProb
});
return { channel: "telegram", ...result };
},
sendMedia: async ({ to, text, mediaUrl, accountId, deps, replyToId, threadId, silent }) => {
sendMedia: async ({
to,
text,
mediaUrl,
mediaLocalRoots,
accountId,
deps,
replyToId,
threadId,
silent,
}) => {
const send = deps?.sendTelegram ?? getTelegramRuntime().channel.telegram.sendMessageTelegram;
const replyToMessageId = parseTelegramReplyToMessageId(replyToId);
const messageThreadId = parseTelegramThreadId(threadId);
const result = await send(to, text, {
verbose: false,
mediaUrl,
mediaLocalRoots,
messageThreadId,
replyToMessageId,
accountId: accountId ?? undefined,