Discord: honor accountId across channel actions (refs #1489)

This commit is contained in:
Sergii Kozak
2026-01-23 00:50:50 -08:00
parent dc89bc4004
commit 716f901504
8 changed files with 322 additions and 146 deletions

View File

@@ -3,6 +3,7 @@ import { describe, expect, it, vi } from "vitest";
import type { ClawdbotConfig } from "../../../config/config.js";
type SendMessageDiscord = typeof import("../../../discord/send.js").sendMessageDiscord;
type SendPollDiscord = typeof import("../../../discord/send.js").sendPollDiscord;
type ReactMessageDiscord = typeof import("../../../discord/send.js").reactMessageDiscord;
const sendMessageDiscord = vi.fn<Parameters<SendMessageDiscord>, ReturnType<SendMessageDiscord>>(
async () => ({ ok: true }) as Awaited<ReturnType<SendMessageDiscord>>,
@@ -10,6 +11,9 @@ const sendMessageDiscord = vi.fn<Parameters<SendMessageDiscord>, ReturnType<Send
const sendPollDiscord = vi.fn<Parameters<SendPollDiscord>, ReturnType<SendPollDiscord>>(
async () => ({ ok: true }) as Awaited<ReturnType<SendPollDiscord>>,
);
const reactMessageDiscord = vi.fn<Parameters<ReactMessageDiscord>, ReturnType<ReactMessageDiscord>>(
async () => ({ ok: true }) as Awaited<ReturnType<ReactMessageDiscord>>,
);
vi.mock("../../../discord/send.js", async () => {
const actual = await vi.importActual<typeof import("../../../discord/send.js")>(
@@ -19,6 +23,7 @@ vi.mock("../../../discord/send.js", async () => {
...actual,
sendMessageDiscord: (...args: Parameters<SendMessageDiscord>) => sendMessageDiscord(...args),
sendPollDiscord: (...args: Parameters<SendPollDiscord>) => sendPollDiscord(...args),
reactMessageDiscord: (...args: Parameters<ReactMessageDiscord>) => reactMessageDiscord(...args),
};
});
@@ -104,4 +109,29 @@ describe("handleDiscordMessageAction", () => {
}),
);
});
it("forwards accountId for reaction actions", async () => {
reactMessageDiscord.mockClear();
const handleDiscordMessageAction = await loadHandleDiscordMessageAction();
await handleDiscordMessageAction({
action: "react",
params: {
channelId: "123",
messageId: "m1",
emoji: "👍",
},
cfg: {} as ClawdbotConfig,
accountId: "ops",
});
expect(reactMessageDiscord).toHaveBeenCalledWith(
"123",
"m1",
"👍",
expect.objectContaining({
accountId: "ops",
}),
);
});
});

View File

@@ -7,7 +7,7 @@ import {
import { handleDiscordAction } from "../../../../agents/tools/discord-actions.js";
import type { ChannelMessageActionContext } from "../../types.js";
type Ctx = Pick<ChannelMessageActionContext, "action" | "params" | "cfg">;
type Ctx = Pick<ChannelMessageActionContext, "action" | "params" | "cfg" | "accountId">;
export async function tryHandleDiscordMessageActionGuildAdmin(params: {
ctx: Ctx;
@@ -16,27 +16,38 @@ export async function tryHandleDiscordMessageActionGuildAdmin(params: {
}): Promise<AgentToolResult<unknown> | undefined> {
const { ctx, resolveChannelId, readParentIdParam } = params;
const { action, params: actionParams, cfg } = ctx;
const accountId = ctx.accountId ?? readStringParam(actionParams, "accountId");
const accountIdParam = accountId ?? undefined;
if (action === "member-info") {
const userId = readStringParam(actionParams, "userId", { required: true });
const guildId = readStringParam(actionParams, "guildId", {
required: true,
});
return await handleDiscordAction({ action: "memberInfo", guildId, userId }, cfg);
return await handleDiscordAction(
{ action: "memberInfo", accountId: accountIdParam, guildId, userId },
cfg,
);
}
if (action === "role-info") {
const guildId = readStringParam(actionParams, "guildId", {
required: true,
});
return await handleDiscordAction({ action: "roleInfo", guildId }, cfg);
return await handleDiscordAction(
{ action: "roleInfo", accountId: accountIdParam, guildId },
cfg,
);
}
if (action === "emoji-list") {
const guildId = readStringParam(actionParams, "guildId", {
required: true,
});
return await handleDiscordAction({ action: "emojiList", guildId }, cfg);
return await handleDiscordAction(
{ action: "emojiList", accountId: accountIdParam, guildId },
cfg,
);
}
if (action === "emoji-upload") {
@@ -50,7 +61,7 @@ export async function tryHandleDiscordMessageActionGuildAdmin(params: {
});
const roleIds = readStringArrayParam(actionParams, "roleIds");
return await handleDiscordAction(
{ action: "emojiUpload", guildId, name, mediaUrl, roleIds },
{ action: "emojiUpload", accountId: accountIdParam, guildId, name, mediaUrl, roleIds },
cfg,
);
}
@@ -73,7 +84,15 @@ export async function tryHandleDiscordMessageActionGuildAdmin(params: {
trim: false,
});
return await handleDiscordAction(
{ action: "stickerUpload", guildId, name, description, tags, mediaUrl },
{
action: "stickerUpload",
accountId: accountIdParam,
guildId,
name,
description,
tags,
mediaUrl,
},
cfg,
);
}
@@ -87,6 +106,7 @@ export async function tryHandleDiscordMessageActionGuildAdmin(params: {
return await handleDiscordAction(
{
action: action === "role-add" ? "roleAdd" : "roleRemove",
accountId: accountIdParam,
guildId,
userId,
roleId,
@@ -99,14 +119,20 @@ export async function tryHandleDiscordMessageActionGuildAdmin(params: {
const channelId = readStringParam(actionParams, "channelId", {
required: true,
});
return await handleDiscordAction({ action: "channelInfo", channelId }, cfg);
return await handleDiscordAction(
{ action: "channelInfo", accountId: accountIdParam, channelId },
cfg,
);
}
if (action === "channel-list") {
const guildId = readStringParam(actionParams, "guildId", {
required: true,
});
return await handleDiscordAction({ action: "channelList", guildId }, cfg);
return await handleDiscordAction(
{ action: "channelList", accountId: accountIdParam, guildId },
cfg,
);
}
if (action === "channel-create") {
@@ -124,6 +150,7 @@ export async function tryHandleDiscordMessageActionGuildAdmin(params: {
return await handleDiscordAction(
{
action: "channelCreate",
accountId: accountIdParam,
guildId,
name,
type: type ?? undefined,
@@ -153,6 +180,7 @@ export async function tryHandleDiscordMessageActionGuildAdmin(params: {
return await handleDiscordAction(
{
action: "channelEdit",
accountId: accountIdParam,
channelId,
name: name ?? undefined,
topic: topic ?? undefined,
@@ -169,7 +197,10 @@ export async function tryHandleDiscordMessageActionGuildAdmin(params: {
const channelId = readStringParam(actionParams, "channelId", {
required: true,
});
return await handleDiscordAction({ action: "channelDelete", channelId }, cfg);
return await handleDiscordAction(
{ action: "channelDelete", accountId: accountIdParam, channelId },
cfg,
);
}
if (action === "channel-move") {
@@ -186,6 +217,7 @@ export async function tryHandleDiscordMessageActionGuildAdmin(params: {
return await handleDiscordAction(
{
action: "channelMove",
accountId: accountIdParam,
guildId,
channelId,
parentId: parentId === undefined ? undefined : parentId,
@@ -206,6 +238,7 @@ export async function tryHandleDiscordMessageActionGuildAdmin(params: {
return await handleDiscordAction(
{
action: "categoryCreate",
accountId: accountIdParam,
guildId,
name,
position: position ?? undefined,
@@ -225,6 +258,7 @@ export async function tryHandleDiscordMessageActionGuildAdmin(params: {
return await handleDiscordAction(
{
action: "categoryEdit",
accountId: accountIdParam,
categoryId,
name: name ?? undefined,
position: position ?? undefined,
@@ -237,7 +271,10 @@ export async function tryHandleDiscordMessageActionGuildAdmin(params: {
const categoryId = readStringParam(actionParams, "categoryId", {
required: true,
});
return await handleDiscordAction({ action: "categoryDelete", categoryId }, cfg);
return await handleDiscordAction(
{ action: "categoryDelete", accountId: accountIdParam, categoryId },
cfg,
);
}
if (action === "voice-status") {
@@ -245,14 +282,20 @@ export async function tryHandleDiscordMessageActionGuildAdmin(params: {
required: true,
});
const userId = readStringParam(actionParams, "userId", { required: true });
return await handleDiscordAction({ action: "voiceStatus", guildId, userId }, cfg);
return await handleDiscordAction(
{ action: "voiceStatus", accountId: accountIdParam, guildId, userId },
cfg,
);
}
if (action === "event-list") {
const guildId = readStringParam(actionParams, "guildId", {
required: true,
});
return await handleDiscordAction({ action: "eventList", guildId }, cfg);
return await handleDiscordAction(
{ action: "eventList", accountId: accountIdParam, guildId },
cfg,
);
}
if (action === "event-create") {
@@ -271,6 +314,7 @@ export async function tryHandleDiscordMessageActionGuildAdmin(params: {
return await handleDiscordAction(
{
action: "eventCreate",
accountId: accountIdParam,
guildId,
name,
startTime,
@@ -301,6 +345,7 @@ export async function tryHandleDiscordMessageActionGuildAdmin(params: {
return await handleDiscordAction(
{
action: discordAction,
accountId: accountIdParam,
guildId,
userId,
durationMinutes,
@@ -325,6 +370,7 @@ export async function tryHandleDiscordMessageActionGuildAdmin(params: {
return await handleDiscordAction(
{
action: "threadList",
accountId: accountIdParam,
guildId,
channelId,
includeArchived,
@@ -344,6 +390,7 @@ export async function tryHandleDiscordMessageActionGuildAdmin(params: {
return await handleDiscordAction(
{
action: "threadReply",
accountId: accountIdParam,
channelId: resolveChannelId(),
content,
mediaUrl: mediaUrl ?? undefined,
@@ -361,6 +408,7 @@ export async function tryHandleDiscordMessageActionGuildAdmin(params: {
return await handleDiscordAction(
{
action: "searchMessages",
accountId: accountIdParam,
guildId,
content: query,
channelId: readStringParam(actionParams, "channelId"),

View File

@@ -22,6 +22,7 @@ export async function handleDiscordMessageAction(
): Promise<AgentToolResult<unknown>> {
const { action, params, cfg } = ctx;
const accountId = ctx.accountId ?? readStringParam(params, "accountId");
const accountIdParam = accountId ?? undefined;
const resolveChannelId = () =>
resolveDiscordChannelId(
@@ -40,7 +41,7 @@ export async function handleDiscordMessageAction(
return await handleDiscordAction(
{
action: "sendMessage",
accountId: accountId ?? undefined,
accountId: accountIdParam,
to,
content,
mediaUrl: mediaUrl ?? undefined,
@@ -64,7 +65,7 @@ export async function handleDiscordMessageAction(
return await handleDiscordAction(
{
action: "poll",
accountId: accountId ?? undefined,
accountId: accountIdParam,
to,
question,
answers,
@@ -83,6 +84,7 @@ export async function handleDiscordMessageAction(
return await handleDiscordAction(
{
action: "react",
accountId: accountIdParam,
channelId: resolveChannelId(),
messageId,
emoji,
@@ -96,7 +98,13 @@ export async function handleDiscordMessageAction(
const messageId = readStringParam(params, "messageId", { required: true });
const limit = readNumberParam(params, "limit", { integer: true });
return await handleDiscordAction(
{ action: "reactions", channelId: resolveChannelId(), messageId, limit },
{
action: "reactions",
accountId: accountIdParam,
channelId: resolveChannelId(),
messageId,
limit,
},
cfg,
);
}
@@ -106,6 +114,7 @@ export async function handleDiscordMessageAction(
return await handleDiscordAction(
{
action: "readMessages",
accountId: accountIdParam,
channelId: resolveChannelId(),
limit,
before: readStringParam(params, "before"),
@@ -122,6 +131,7 @@ export async function handleDiscordMessageAction(
return await handleDiscordAction(
{
action: "editMessage",
accountId: accountIdParam,
channelId: resolveChannelId(),
messageId,
content,
@@ -133,7 +143,12 @@ export async function handleDiscordMessageAction(
if (action === "delete") {
const messageId = readStringParam(params, "messageId", { required: true });
return await handleDiscordAction(
{ action: "deleteMessage", channelId: resolveChannelId(), messageId },
{
action: "deleteMessage",
accountId: accountIdParam,
channelId: resolveChannelId(),
messageId,
},
cfg,
);
}
@@ -144,6 +159,7 @@ export async function handleDiscordMessageAction(
return await handleDiscordAction(
{
action: action === "pin" ? "pinMessage" : action === "unpin" ? "unpinMessage" : "listPins",
accountId: accountIdParam,
channelId: resolveChannelId(),
messageId,
},
@@ -152,7 +168,10 @@ export async function handleDiscordMessageAction(
}
if (action === "permissions") {
return await handleDiscordAction({ action: "permissions", channelId: resolveChannelId() }, cfg);
return await handleDiscordAction(
{ action: "permissions", accountId: accountIdParam, channelId: resolveChannelId() },
cfg,
);
}
if (action === "thread-create") {
@@ -164,6 +183,7 @@ export async function handleDiscordMessageAction(
return await handleDiscordAction(
{
action: "threadCreate",
accountId: accountIdParam,
channelId: resolveChannelId(),
name,
messageId,
@@ -182,6 +202,7 @@ export async function handleDiscordMessageAction(
return await handleDiscordAction(
{
action: "sticker",
accountId: accountIdParam,
to: readStringParam(params, "to", { required: true }),
stickerIds,
content: readStringParam(params, "message"),