fix(media): enforce agent media roots in plugin send actions

Co-authored-by: Oliver Drobnik <333270+odrobnik@users.noreply.github.com>
Co-authored-by: thisischappy <257418353+thisischappy@users.noreply.github.com>
This commit is contained in:
Peter Steinberger
2026-02-22 21:17:09 +01:00
parent 33a43a151d
commit 7bbd597383
13 changed files with 193 additions and 10 deletions

View File

@@ -56,6 +56,9 @@ export async function handleDiscordMessagingAction(
action: string,
params: Record<string, unknown>,
isActionEnabled: ActionGate<DiscordActionConfig>,
options?: {
mediaLocalRoots?: readonly string[];
},
): Promise<AgentToolResult<unknown>> {
const resolveChannelId = () =>
resolveDiscordChannelId(
@@ -308,6 +311,7 @@ export async function handleDiscordMessagingAction(
const result = await sendMessageDiscord(to, content ?? "", {
...(accountId ? { accountId } : {}),
mediaUrl,
mediaLocalRoots: options?.mediaLocalRoots,
replyTo,
components,
embeds,
@@ -416,6 +420,7 @@ export async function handleDiscordMessagingAction(
const result = await sendMessageDiscord(`channel:${channelId}`, content, {
...(accountId ? { accountId } : {}),
mediaUrl,
mediaLocalRoots: options?.mediaLocalRoots,
replyTo,
});
return jsonResult({ ok: true, result });

View File

@@ -264,6 +264,28 @@ describe("handleDiscordMessagingAction", () => {
expect(sendMessageDiscord).not.toHaveBeenCalled();
});
it("forwards trusted mediaLocalRoots into sendMessageDiscord", async () => {
sendMessageDiscord.mockClear();
await handleDiscordMessagingAction(
"sendMessage",
{
to: "channel:123",
content: "hello",
mediaUrl: "/tmp/image.png",
},
enableAllActions,
{ mediaLocalRoots: ["/tmp/agent-root"] },
);
expect(sendMessageDiscord).toHaveBeenCalledWith(
"channel:123",
"hello",
expect.objectContaining({
mediaUrl: "/tmp/image.png",
mediaLocalRoots: ["/tmp/agent-root"],
}),
);
});
it("rejects voice messages that include content", async () => {
await expect(
handleDiscordMessagingAction(

View File

@@ -58,13 +58,16 @@ const presenceActions = new Set(["setPresence"]);
export async function handleDiscordAction(
params: Record<string, unknown>,
cfg: OpenClawConfig,
options?: {
mediaLocalRoots?: readonly string[];
},
): Promise<AgentToolResult<unknown>> {
const action = readStringParam(params, "action", { required: true });
const accountId = readStringParam(params, "accountId");
const isActionEnabled = createDiscordActionGate({ cfg, accountId });
if (messagingActions.has(action)) {
return await handleDiscordMessagingAction(action, params, isActionEnabled);
return await handleDiscordMessagingAction(action, params, isActionEnabled, options);
}
if (guildActions.has(action)) {
return await handleDiscordGuildAction(action, params, isActionEnabled);

View File

@@ -243,6 +243,23 @@ describe("handleTelegramAction", () => {
});
});
it("forwards trusted mediaLocalRoots into sendMessageTelegram", async () => {
await handleTelegramAction(
{
action: "sendMessage",
to: "@testchannel",
content: "Hello with local media",
},
telegramConfig(),
{ mediaLocalRoots: ["/tmp/agent-root"] },
);
expect(sendMessageTelegram).toHaveBeenCalledWith(
"@testchannel",
"Hello with local media",
expect.objectContaining({ mediaLocalRoots: ["/tmp/agent-root"] }),
);
});
it.each([
{
name: "media",

View File

@@ -85,6 +85,9 @@ export function readTelegramButtons(
export async function handleTelegramAction(
params: Record<string, unknown>,
cfg: OpenClawConfig,
options?: {
mediaLocalRoots?: readonly string[];
},
): Promise<AgentToolResult<unknown>> {
const action = readStringParam(params, "action", { required: true });
const accountId = readStringParam(params, "accountId");
@@ -198,6 +201,7 @@ export async function handleTelegramAction(
token,
accountId: accountId ?? undefined,
mediaUrl: mediaUrl || undefined,
mediaLocalRoots: options?.mediaLocalRoots,
buttons,
replyToMessageId: replyToMessageId ?? undefined,
messageThreadId: messageThreadId ?? undefined,