From dc89bc4004503c02b6438b4ab95c05b61262e5ff Mon Sep 17 00:00:00 2001 From: Sergii Kozak Date: Thu, 22 Jan 2026 23:51:58 -0800 Subject: [PATCH 1/3] Discord: preserve accountId in message actions (refs #1489) --- src/agents/tools/cron-tool.ts | 10 ++ src/agents/tools/discord-actions-messaging.ts | 11 ++- src/agents/tools/message-tool.ts | 3 + src/channels/plugins/actions/discord.test.ts | 91 ++++++++++++++++++- src/channels/plugins/actions/discord.ts | 4 +- .../plugins/actions/discord/handle-action.ts | 5 +- src/cron/isolated-agent/run.ts | 1 + .../outbound/message-action-runner.test.ts | 62 +++++++++++++ src/infra/outbound/message-action-runner.ts | 3 + 9 files changed, 181 insertions(+), 9 deletions(-) diff --git a/src/agents/tools/cron-tool.ts b/src/agents/tools/cron-tool.ts index e8995a0b9..e640a8d94 100644 --- a/src/agents/tools/cron-tool.ts +++ b/src/agents/tools/cron-tool.ts @@ -5,6 +5,7 @@ import { truncateUtf16Safe } from "../../utils.js"; import { optionalStringEnum, stringEnum } from "../schema/typebox.js"; import { type AnyAgentTool, jsonResult, readStringParam } from "./common.js"; import { callGatewayTool, type GatewayCallOptions } from "./gateway.js"; +import { resolveSessionAgentId } from "../agent-scope.js"; import { resolveInternalSessionKey, resolveMainSessionAlias } from "./sessions-helpers.js"; // NOTE: We use Type.Object({}, { additionalProperties: true }) for job/patch @@ -158,6 +159,15 @@ export function createCronTool(opts?: CronToolOptions): AnyAgentTool { throw new Error("job required"); } const job = normalizeCronJobCreate(params.job) ?? params.job; + if (job && typeof job === "object") { + const cfg = loadConfig(); + const agentId = opts?.agentSessionKey + ? resolveSessionAgentId({ sessionKey: opts.agentSessionKey, config: cfg }) + : undefined; + if (agentId && !(job as { agentId?: unknown }).agentId) { + (job as { agentId?: string }).agentId = agentId; + } + } const contextMessages = typeof params.contextMessages === "number" && Number.isFinite(params.contextMessages) ? params.contextMessages diff --git a/src/agents/tools/discord-actions-messaging.ts b/src/agents/tools/discord-actions-messaging.ts index f552f17fd..ae49d25bf 100644 --- a/src/agents/tools/discord-actions-messaging.ts +++ b/src/agents/tools/discord-actions-messaging.ts @@ -114,7 +114,8 @@ export async function handleDiscordMessagingAction( required: true, label: "stickerIds", }); - await sendStickerDiscord(to, stickerIds, { content }); + const accountId = readStringParam(params, "accountId"); + await sendStickerDiscord(to, stickerIds, { content, accountId: accountId ?? undefined }); return jsonResult({ ok: true }); } case "poll": { @@ -137,10 +138,11 @@ export async function handleDiscordMessagingAction( const durationHours = typeof durationRaw === "number" && Number.isFinite(durationRaw) ? durationRaw : undefined; const maxSelections = allowMultiselect ? Math.max(2, answers.length) : 1; + const accountId = readStringParam(params, "accountId"); await sendPollDiscord( to, { question, options: answers, maxSelections, durationHours }, - { content }, + { content, accountId: accountId ?? undefined }, ); return jsonResult({ ok: true }); } @@ -211,7 +213,10 @@ export async function handleDiscordMessagingAction( const replyTo = readStringParam(params, "replyTo"); const embeds = Array.isArray(params.embeds) && params.embeds.length > 0 ? params.embeds : undefined; + const accountId = readStringParam(params, "accountId"); + const result = await sendMessageDiscord(to, content, { + accountId: accountId ?? undefined, mediaUrl, replyTo, embeds, @@ -298,7 +303,9 @@ export async function handleDiscordMessagingAction( }); const mediaUrl = readStringParam(params, "mediaUrl"); const replyTo = readStringParam(params, "replyTo"); + const accountId = readStringParam(params, "accountId"); const result = await sendMessageDiscord(`channel:${channelId}`, content, { + accountId: accountId ?? undefined, mediaUrl, replyTo, }); diff --git a/src/agents/tools/message-tool.ts b/src/agents/tools/message-tool.ts index 4ab3c7e18..21974f074 100644 --- a/src/agents/tools/message-tool.ts +++ b/src/agents/tools/message-tool.ts @@ -342,6 +342,9 @@ export function createMessageTool(options?: MessageToolOptions): AnyAgentTool { }) as ChannelMessageActionName; const accountId = readStringParam(params, "accountId") ?? agentAccountId; + if (accountId) { + params.accountId = accountId; + } const gateway = { url: readStringParam(params, "gatewayUrl", { trim: false }), diff --git a/src/channels/plugins/actions/discord.test.ts b/src/channels/plugins/actions/discord.test.ts index d68aba74b..8deda7dc6 100644 --- a/src/channels/plugins/actions/discord.test.ts +++ b/src/channels/plugins/actions/discord.test.ts @@ -1,11 +1,41 @@ -import { describe, expect, it } from "vitest"; +import { describe, expect, it, vi } from "vitest"; import type { ClawdbotConfig } from "../../../config/config.js"; -import { discordMessageActions } from "./discord.js"; +type SendMessageDiscord = typeof import("../../../discord/send.js").sendMessageDiscord; +type SendPollDiscord = typeof import("../../../discord/send.js").sendPollDiscord; + +const sendMessageDiscord = vi.fn, ReturnType>( + async () => ({ ok: true }) as Awaited>, +); +const sendPollDiscord = vi.fn, ReturnType>( + async () => ({ ok: true }) as Awaited>, +); + +vi.mock("../../../discord/send.js", async () => { + const actual = await vi.importActual( + "../../../discord/send.js", + ); + return { + ...actual, + sendMessageDiscord: (...args: Parameters) => sendMessageDiscord(...args), + sendPollDiscord: (...args: Parameters) => sendPollDiscord(...args), + }; +}); + +const loadHandleDiscordMessageAction = async () => { + const mod = await import("./discord/handle-action.js"); + return mod.handleDiscordMessageAction; +}; + +const loadDiscordMessageActions = async () => { + const mod = await import("./discord.js"); + return mod.discordMessageActions; +}; describe("discord message actions", () => { - it("lists channel and upload actions by default", () => { + it("lists channel and upload actions by default", async () => { const cfg = { channels: { discord: { token: "d0" } } } as ClawdbotConfig; + const discordMessageActions = await loadDiscordMessageActions(); const actions = discordMessageActions.listActions?.({ cfg }) ?? []; expect(actions).toContain("emoji-upload"); @@ -13,12 +43,65 @@ describe("discord message actions", () => { expect(actions).toContain("channel-create"); }); - it("respects disabled channel actions", () => { + it("respects disabled channel actions", async () => { const cfg = { channels: { discord: { token: "d0", actions: { channels: false } } }, } as ClawdbotConfig; + const discordMessageActions = await loadDiscordMessageActions(); const actions = discordMessageActions.listActions?.({ cfg }) ?? []; expect(actions).not.toContain("channel-create"); }); }); + +describe("handleDiscordMessageAction", () => { + it("forwards context accountId for send", async () => { + sendMessageDiscord.mockClear(); + const handleDiscordMessageAction = await loadHandleDiscordMessageAction(); + + await handleDiscordMessageAction({ + action: "send", + params: { + to: "channel:123", + message: "hi", + }, + cfg: {} as ClawdbotConfig, + accountId: "ops", + }); + + expect(sendMessageDiscord).toHaveBeenCalledWith( + "channel:123", + "hi", + expect.objectContaining({ + accountId: "ops", + }), + ); + }); + + it("falls back to params accountId when context missing", async () => { + sendPollDiscord.mockClear(); + const handleDiscordMessageAction = await loadHandleDiscordMessageAction(); + + await handleDiscordMessageAction({ + action: "poll", + params: { + to: "channel:123", + pollQuestion: "Ready?", + pollOption: ["Yes", "No"], + accountId: "marve", + }, + cfg: {} as ClawdbotConfig, + }); + + expect(sendPollDiscord).toHaveBeenCalledWith( + "channel:123", + expect.objectContaining({ + question: "Ready?", + options: ["Yes", "No"], + }), + expect.objectContaining({ + accountId: "marve", + }), + ); + }); +}); diff --git a/src/channels/plugins/actions/discord.ts b/src/channels/plugins/actions/discord.ts index fcae08633..ebed5eb0d 100644 --- a/src/channels/plugins/actions/discord.ts +++ b/src/channels/plugins/actions/discord.ts @@ -80,7 +80,7 @@ export const discordMessageActions: ChannelMessageActionAdapter = { } return null; }, - handleAction: async ({ action, params, cfg }) => { - return await handleDiscordMessageAction({ action, params, cfg }); + handleAction: async ({ action, params, cfg, accountId }) => { + return await handleDiscordMessageAction({ action, params, cfg, accountId }); }, }; diff --git a/src/channels/plugins/actions/discord/handle-action.ts b/src/channels/plugins/actions/discord/handle-action.ts index 82f08e686..031ca9f5b 100644 --- a/src/channels/plugins/actions/discord/handle-action.ts +++ b/src/channels/plugins/actions/discord/handle-action.ts @@ -18,9 +18,10 @@ function readParentIdParam(params: Record): string | null | und } export async function handleDiscordMessageAction( - ctx: Pick, + ctx: Pick, ): Promise> { const { action, params, cfg } = ctx; + const accountId = ctx.accountId ?? readStringParam(params, "accountId"); const resolveChannelId = () => resolveDiscordChannelId( @@ -39,6 +40,7 @@ export async function handleDiscordMessageAction( return await handleDiscordAction( { action: "sendMessage", + accountId: accountId ?? undefined, to, content, mediaUrl: mediaUrl ?? undefined, @@ -62,6 +64,7 @@ export async function handleDiscordMessageAction( return await handleDiscordAction( { action: "poll", + accountId: accountId ?? undefined, to, question, answers, diff --git a/src/cron/isolated-agent/run.ts b/src/cron/isolated-agent/run.ts index 296cf6aad..4f8f4deb3 100644 --- a/src/cron/isolated-agent/run.ts +++ b/src/cron/isolated-agent/run.ts @@ -299,6 +299,7 @@ export async function runCronIsolatedAgentTurn(params: { sessionId: cronSession.sessionEntry.sessionId, sessionKey: agentSessionKey, messageChannel, + agentAccountId: resolvedDelivery.accountId, sessionFile, workspaceDir, config: cfgWithAgentDefaults, diff --git a/src/infra/outbound/message-action-runner.test.ts b/src/infra/outbound/message-action-runner.test.ts index 0fd10eb3b..9b592d9d2 100644 --- a/src/infra/outbound/message-action-runner.test.ts +++ b/src/infra/outbound/message-action-runner.test.ts @@ -410,3 +410,65 @@ describe("runMessageAction sendAttachment hydration", () => { ); }); }); + +describe("runMessageAction accountId defaults", () => { + const handleAction = vi.fn(async () => jsonResult({ ok: true })); + const accountPlugin: ChannelPlugin = { + id: "discord", + meta: { + id: "discord", + label: "Discord", + selectionLabel: "Discord", + docsPath: "/channels/discord", + blurb: "Discord test plugin.", + }, + capabilities: { chatTypes: ["direct"] }, + config: { + listAccountIds: () => ["default"], + resolveAccount: () => ({}), + }, + actions: { + listActions: () => ["send"], + handleAction, + }, + }; + + beforeEach(() => { + setActivePluginRegistry( + createTestRegistry([ + { + pluginId: "discord", + source: "test", + plugin: accountPlugin, + }, + ]), + ); + handleAction.mockClear(); + }); + + afterEach(() => { + setActivePluginRegistry(createTestRegistry([])); + vi.clearAllMocks(); + }); + + it("propagates defaultAccountId into params", async () => { + await runMessageAction({ + cfg: {} as ClawdbotConfig, + action: "send", + params: { + channel: "discord", + target: "channel:123", + message: "hi", + }, + defaultAccountId: "ops", + }); + + expect(handleAction).toHaveBeenCalled(); + const ctx = handleAction.mock.calls[0]?.[0] as { + accountId?: string | null; + params: Record; + }; + expect(ctx.accountId).toBe("ops"); + expect(ctx.params.accountId).toBe("ops"); + }); +}); diff --git a/src/infra/outbound/message-action-runner.ts b/src/infra/outbound/message-action-runner.ts index dc8aeddf3..051098f34 100644 --- a/src/infra/outbound/message-action-runner.ts +++ b/src/infra/outbound/message-action-runner.ts @@ -803,6 +803,9 @@ export async function runMessageAction( const channel = await resolveChannel(cfg, params); const accountId = readStringParam(params, "accountId") ?? input.defaultAccountId; + if (accountId) { + params.accountId = accountId; + } const dryRun = Boolean(input.dryRun ?? readBooleanParam(params, "dryRun")); await hydrateSendAttachmentParams({ From 716f9015044cfeeb873daf3d4bdf4682c452c90a Mon Sep 17 00:00:00 2001 From: Sergii Kozak Date: Fri, 23 Jan 2026 00:50:50 -0800 Subject: [PATCH 2/3] Discord: honor accountId across channel actions (refs #1489) --- src/agents/tools/cron-tool.test.ts | 21 +++ src/agents/tools/cron-tool.ts | 2 +- src/agents/tools/discord-actions-guild.ts | 163 ++++++++++-------- src/agents/tools/discord-actions-messaging.ts | 112 +++++++----- .../tools/discord-actions-moderation.ts | 37 ++-- src/channels/plugins/actions/discord.test.ts | 30 ++++ .../discord/handle-action.guild-admin.ts | 72 ++++++-- .../plugins/actions/discord/handle-action.ts | 31 +++- 8 files changed, 322 insertions(+), 146 deletions(-) diff --git a/src/agents/tools/cron-tool.test.ts b/src/agents/tools/cron-tool.test.ts index 4b7cd6615..08bd9a834 100644 --- a/src/agents/tools/cron-tool.test.ts +++ b/src/agents/tools/cron-tool.test.ts @@ -5,6 +5,10 @@ vi.mock("../../gateway/call.js", () => ({ callGateway: (opts: unknown) => callGatewayMock(opts), })); +vi.mock("../agent-scope.js", () => ({ + resolveSessionAgentId: () => "agent-123", +})); + import { createCronTool } from "./cron-tool.js"; describe("cron tool", () => { @@ -85,6 +89,23 @@ describe("cron tool", () => { }); }); + it("does not default agentId when job.agentId is null", async () => { + const tool = createCronTool({ agentSessionKey: "main" }); + await tool.execute("call-null", { + action: "add", + job: { + name: "wake-up", + schedule: { atMs: 123 }, + agentId: null, + }, + }); + + const call = callGatewayMock.mock.calls[0]?.[0] as { + params?: { agentId?: unknown }; + }; + expect(call?.params?.agentId).toBeNull(); + }); + it("adds recent context for systemEvent reminders when contextMessages > 0", async () => { callGatewayMock .mockResolvedValueOnce({ diff --git a/src/agents/tools/cron-tool.ts b/src/agents/tools/cron-tool.ts index e640a8d94..e1bc15024 100644 --- a/src/agents/tools/cron-tool.ts +++ b/src/agents/tools/cron-tool.ts @@ -164,7 +164,7 @@ export function createCronTool(opts?: CronToolOptions): AnyAgentTool { const agentId = opts?.agentSessionKey ? resolveSessionAgentId({ sessionKey: opts.agentSessionKey, config: cfg }) : undefined; - if (agentId && !(job as { agentId?: unknown }).agentId) { + if (agentId && !("agentId" in (job as { agentId?: unknown }))) { (job as { agentId?: string }).agentId = agentId; } } diff --git a/src/agents/tools/discord-actions-guild.ts b/src/agents/tools/discord-actions-guild.ts index cf43f90af..ce21bacfd 100644 --- a/src/agents/tools/discord-actions-guild.ts +++ b/src/agents/tools/discord-actions-guild.ts @@ -39,6 +39,9 @@ export async function handleDiscordGuildAction( params: Record, isActionEnabled: ActionGate, ): Promise> { + const accountId = readStringParam(params, "accountId"); + const accountOpts = accountId ? { accountId } : {}; + switch (action) { case "memberInfo": { if (!isActionEnabled("memberInfo")) { @@ -50,7 +53,7 @@ export async function handleDiscordGuildAction( const userId = readStringParam(params, "userId", { required: true, }); - const member = await fetchMemberInfoDiscord(guildId, userId); + const member = await fetchMemberInfoDiscord(guildId, userId, accountOpts); return jsonResult({ ok: true, member }); } case "roleInfo": { @@ -60,7 +63,7 @@ export async function handleDiscordGuildAction( const guildId = readStringParam(params, "guildId", { required: true, }); - const roles = await fetchRoleInfoDiscord(guildId); + const roles = await fetchRoleInfoDiscord(guildId, accountOpts); return jsonResult({ ok: true, roles }); } case "emojiList": { @@ -70,7 +73,7 @@ export async function handleDiscordGuildAction( const guildId = readStringParam(params, "guildId", { required: true, }); - const emojis = await listGuildEmojisDiscord(guildId); + const emojis = await listGuildEmojisDiscord(guildId, accountOpts); return jsonResult({ ok: true, emojis }); } case "emojiUpload": { @@ -85,12 +88,15 @@ export async function handleDiscordGuildAction( required: true, }); const roleIds = readStringArrayParam(params, "roleIds"); - const emoji = await uploadEmojiDiscord({ - guildId, - name, - mediaUrl, - roleIds: roleIds?.length ? roleIds : undefined, - }); + const emoji = await uploadEmojiDiscord( + { + guildId, + name, + mediaUrl, + roleIds: roleIds?.length ? roleIds : undefined, + }, + accountOpts, + ); return jsonResult({ ok: true, emoji }); } case "stickerUpload": { @@ -108,13 +114,16 @@ export async function handleDiscordGuildAction( const mediaUrl = readStringParam(params, "mediaUrl", { required: true, }); - const sticker = await uploadStickerDiscord({ - guildId, - name, - description, - tags, - mediaUrl, - }); + const sticker = await uploadStickerDiscord( + { + guildId, + name, + description, + tags, + mediaUrl, + }, + accountOpts, + ); return jsonResult({ ok: true, sticker }); } case "roleAdd": { @@ -128,7 +137,7 @@ export async function handleDiscordGuildAction( required: true, }); const roleId = readStringParam(params, "roleId", { required: true }); - await addRoleDiscord({ guildId, userId, roleId }); + await addRoleDiscord({ guildId, userId, roleId }, accountOpts); return jsonResult({ ok: true }); } case "roleRemove": { @@ -142,7 +151,7 @@ export async function handleDiscordGuildAction( required: true, }); const roleId = readStringParam(params, "roleId", { required: true }); - await removeRoleDiscord({ guildId, userId, roleId }); + await removeRoleDiscord({ guildId, userId, roleId }, accountOpts); return jsonResult({ ok: true }); } case "channelInfo": { @@ -152,7 +161,7 @@ export async function handleDiscordGuildAction( const channelId = readStringParam(params, "channelId", { required: true, }); - const channel = await fetchChannelInfoDiscord(channelId); + const channel = await fetchChannelInfoDiscord(channelId, accountOpts); return jsonResult({ ok: true, channel }); } case "channelList": { @@ -162,7 +171,7 @@ export async function handleDiscordGuildAction( const guildId = readStringParam(params, "guildId", { required: true, }); - const channels = await listGuildChannelsDiscord(guildId); + const channels = await listGuildChannelsDiscord(guildId, accountOpts); return jsonResult({ ok: true, channels }); } case "voiceStatus": { @@ -175,7 +184,7 @@ export async function handleDiscordGuildAction( const userId = readStringParam(params, "userId", { required: true, }); - const voice = await fetchVoiceStatusDiscord(guildId, userId); + const voice = await fetchVoiceStatusDiscord(guildId, userId, accountOpts); return jsonResult({ ok: true, voice }); } case "eventList": { @@ -185,7 +194,7 @@ export async function handleDiscordGuildAction( const guildId = readStringParam(params, "guildId", { required: true, }); - const events = await listScheduledEventsDiscord(guildId); + const events = await listScheduledEventsDiscord(guildId, accountOpts); return jsonResult({ ok: true, events }); } case "eventCreate": { @@ -215,7 +224,7 @@ export async function handleDiscordGuildAction( entity_metadata: entityType === 3 && location ? { location } : undefined, privacy_level: 2, }; - const event = await createScheduledEventDiscord(guildId, payload); + const event = await createScheduledEventDiscord(guildId, payload, accountOpts); return jsonResult({ ok: true, event }); } case "channelCreate": { @@ -229,15 +238,18 @@ export async function handleDiscordGuildAction( const topic = readStringParam(params, "topic"); const position = readNumberParam(params, "position", { integer: true }); const nsfw = params.nsfw as boolean | undefined; - const channel = await createChannelDiscord({ - guildId, - name, - type: type ?? undefined, - parentId: parentId ?? undefined, - topic: topic ?? undefined, - position: position ?? undefined, - nsfw, - }); + const channel = await createChannelDiscord( + { + guildId, + name, + type: type ?? undefined, + parentId: parentId ?? undefined, + topic: topic ?? undefined, + position: position ?? undefined, + nsfw, + }, + accountOpts, + ); return jsonResult({ ok: true, channel }); } case "channelEdit": { @@ -255,15 +267,18 @@ export async function handleDiscordGuildAction( const rateLimitPerUser = readNumberParam(params, "rateLimitPerUser", { integer: true, }); - const channel = await editChannelDiscord({ - channelId, - name: name ?? undefined, - topic: topic ?? undefined, - position: position ?? undefined, - parentId, - nsfw, - rateLimitPerUser: rateLimitPerUser ?? undefined, - }); + const channel = await editChannelDiscord( + { + channelId, + name: name ?? undefined, + topic: topic ?? undefined, + position: position ?? undefined, + parentId, + nsfw, + rateLimitPerUser: rateLimitPerUser ?? undefined, + }, + accountOpts, + ); return jsonResult({ ok: true, channel }); } case "channelDelete": { @@ -273,7 +288,7 @@ export async function handleDiscordGuildAction( const channelId = readStringParam(params, "channelId", { required: true, }); - const result = await deleteChannelDiscord(channelId); + const result = await deleteChannelDiscord(channelId, accountOpts); return jsonResult(result); } case "channelMove": { @@ -286,12 +301,15 @@ export async function handleDiscordGuildAction( }); const parentId = readParentIdParam(params); const position = readNumberParam(params, "position", { integer: true }); - await moveChannelDiscord({ - guildId, - channelId, - parentId, - position: position ?? undefined, - }); + await moveChannelDiscord( + { + guildId, + channelId, + parentId, + position: position ?? undefined, + }, + accountOpts, + ); return jsonResult({ ok: true }); } case "categoryCreate": { @@ -301,12 +319,15 @@ export async function handleDiscordGuildAction( const guildId = readStringParam(params, "guildId", { required: true }); const name = readStringParam(params, "name", { required: true }); const position = readNumberParam(params, "position", { integer: true }); - const channel = await createChannelDiscord({ - guildId, - name, - type: 4, - position: position ?? undefined, - }); + const channel = await createChannelDiscord( + { + guildId, + name, + type: 4, + position: position ?? undefined, + }, + accountOpts, + ); return jsonResult({ ok: true, category: channel }); } case "categoryEdit": { @@ -318,11 +339,14 @@ export async function handleDiscordGuildAction( }); const name = readStringParam(params, "name"); const position = readNumberParam(params, "position", { integer: true }); - const channel = await editChannelDiscord({ - channelId: categoryId, - name: name ?? undefined, - position: position ?? undefined, - }); + const channel = await editChannelDiscord( + { + channelId: categoryId, + name: name ?? undefined, + position: position ?? undefined, + }, + accountOpts, + ); return jsonResult({ ok: true, category: channel }); } case "categoryDelete": { @@ -332,7 +356,7 @@ export async function handleDiscordGuildAction( const categoryId = readStringParam(params, "categoryId", { required: true, }); - const result = await deleteChannelDiscord(categoryId); + const result = await deleteChannelDiscord(categoryId, accountOpts); return jsonResult(result); } case "channelPermissionSet": { @@ -349,13 +373,16 @@ export async function handleDiscordGuildAction( const targetType = targetTypeRaw === "member" ? 1 : 0; const allow = readStringParam(params, "allow"); const deny = readStringParam(params, "deny"); - await setChannelPermissionDiscord({ - channelId, - targetId, - targetType, - allow: allow ?? undefined, - deny: deny ?? undefined, - }); + await setChannelPermissionDiscord( + { + channelId, + targetId, + targetType, + allow: allow ?? undefined, + deny: deny ?? undefined, + }, + accountOpts, + ); return jsonResult({ ok: true }); } case "channelPermissionRemove": { @@ -366,7 +393,7 @@ export async function handleDiscordGuildAction( required: true, }); const targetId = readStringParam(params, "targetId", { required: true }); - await removeChannelPermissionDiscord(channelId, targetId); + await removeChannelPermissionDiscord(channelId, targetId, accountOpts); return jsonResult({ ok: true }); } default: diff --git a/src/agents/tools/discord-actions-messaging.ts b/src/agents/tools/discord-actions-messaging.ts index ae49d25bf..eb4c0547f 100644 --- a/src/agents/tools/discord-actions-messaging.ts +++ b/src/agents/tools/discord-actions-messaging.ts @@ -65,6 +65,8 @@ export async function handleDiscordMessagingAction( (message as { timestamp?: unknown }).timestamp, ); }; + const accountId = readStringParam(params, "accountId"); + const accountOpts = accountId ? { accountId } : {}; switch (action) { case "react": { if (!isActionEnabled("reactions")) { @@ -78,14 +80,14 @@ export async function handleDiscordMessagingAction( removeErrorMessage: "Emoji is required to remove a Discord reaction.", }); if (remove) { - await removeReactionDiscord(channelId, messageId, emoji); + await removeReactionDiscord(channelId, messageId, emoji, accountOpts); return jsonResult({ ok: true, removed: emoji }); } if (isEmpty) { - const removed = await removeOwnReactionsDiscord(channelId, messageId); + const removed = await removeOwnReactionsDiscord(channelId, messageId, accountOpts); return jsonResult({ ok: true, removed: removed.removed }); } - await reactMessageDiscord(channelId, messageId, emoji); + await reactMessageDiscord(channelId, messageId, emoji, accountOpts); return jsonResult({ ok: true, added: emoji }); } case "reactions": { @@ -100,6 +102,7 @@ export async function handleDiscordMessagingAction( const limit = typeof limitRaw === "number" && Number.isFinite(limitRaw) ? limitRaw : undefined; const reactions = await fetchReactionsDiscord(channelId, messageId, { + ...accountOpts, limit, }); return jsonResult({ ok: true, reactions }); @@ -114,8 +117,10 @@ export async function handleDiscordMessagingAction( required: true, label: "stickerIds", }); - const accountId = readStringParam(params, "accountId"); - await sendStickerDiscord(to, stickerIds, { content, accountId: accountId ?? undefined }); + await sendStickerDiscord(to, stickerIds, { + content, + accountId: accountId ?? undefined, + }); return jsonResult({ ok: true }); } case "poll": { @@ -138,7 +143,6 @@ export async function handleDiscordMessagingAction( const durationHours = typeof durationRaw === "number" && Number.isFinite(durationRaw) ? durationRaw : undefined; const maxSelections = allowMultiselect ? Math.max(2, answers.length) : 1; - const accountId = readStringParam(params, "accountId"); await sendPollDiscord( to, { question, options: answers, maxSelections, durationHours }, @@ -151,7 +155,7 @@ export async function handleDiscordMessagingAction( throw new Error("Discord permissions are disabled."); } const channelId = resolveChannelId(); - const permissions = await fetchChannelPermissionsDiscord(channelId); + const permissions = await fetchChannelPermissionsDiscord(channelId, accountOpts); return jsonResult({ ok: true, permissions }); } case "fetchMessage": { @@ -173,7 +177,7 @@ export async function handleDiscordMessagingAction( "Discord message fetch requires guildId, channelId, and messageId (or a valid messageLink).", ); } - const message = await fetchMessageDiscord(channelId, messageId); + const message = await fetchMessageDiscord(channelId, messageId, accountOpts); return jsonResult({ ok: true, message: normalizeMessage(message), @@ -187,15 +191,19 @@ export async function handleDiscordMessagingAction( throw new Error("Discord message reads are disabled."); } const channelId = resolveChannelId(); - const messages = await readMessagesDiscord(channelId, { - limit: - typeof params.limit === "number" && Number.isFinite(params.limit) - ? params.limit - : undefined, - before: readStringParam(params, "before"), - after: readStringParam(params, "after"), - around: readStringParam(params, "around"), - }); + const messages = await readMessagesDiscord( + channelId, + { + limit: + typeof params.limit === "number" && Number.isFinite(params.limit) + ? params.limit + : undefined, + before: readStringParam(params, "before"), + after: readStringParam(params, "after"), + around: readStringParam(params, "around"), + }, + accountOpts, + ); return jsonResult({ ok: true, messages: messages.map((message) => normalizeMessage(message)), @@ -213,8 +221,6 @@ export async function handleDiscordMessagingAction( const replyTo = readStringParam(params, "replyTo"); const embeds = Array.isArray(params.embeds) && params.embeds.length > 0 ? params.embeds : undefined; - const accountId = readStringParam(params, "accountId"); - const result = await sendMessageDiscord(to, content, { accountId: accountId ?? undefined, mediaUrl, @@ -234,9 +240,14 @@ export async function handleDiscordMessagingAction( const content = readStringParam(params, "content", { required: true, }); - const message = await editMessageDiscord(channelId, messageId, { - content, - }); + const message = await editMessageDiscord( + channelId, + messageId, + { + content, + }, + accountOpts, + ); return jsonResult({ ok: true, message }); } case "deleteMessage": { @@ -247,7 +258,7 @@ export async function handleDiscordMessagingAction( const messageId = readStringParam(params, "messageId", { required: true, }); - await deleteMessageDiscord(channelId, messageId); + await deleteMessageDiscord(channelId, messageId, accountOpts); return jsonResult({ ok: true }); } case "threadCreate": { @@ -262,11 +273,15 @@ export async function handleDiscordMessagingAction( typeof autoArchiveMinutesRaw === "number" && Number.isFinite(autoArchiveMinutesRaw) ? autoArchiveMinutesRaw : undefined; - const thread = await createThreadDiscord(channelId, { - name, - messageId, - autoArchiveMinutes, - }); + const thread = await createThreadDiscord( + channelId, + { + name, + messageId, + autoArchiveMinutes, + }, + accountOpts, + ); return jsonResult({ ok: true, thread }); } case "threadList": { @@ -284,13 +299,16 @@ export async function handleDiscordMessagingAction( typeof params.limit === "number" && Number.isFinite(params.limit) ? params.limit : undefined; - const threads = await listThreadsDiscord({ - guildId, - channelId, - includeArchived, - before, - limit, - }); + const threads = await listThreadsDiscord( + { + guildId, + channelId, + includeArchived, + before, + limit, + }, + accountOpts, + ); return jsonResult({ ok: true, threads }); } case "threadReply": { @@ -303,7 +321,6 @@ export async function handleDiscordMessagingAction( }); const mediaUrl = readStringParam(params, "mediaUrl"); const replyTo = readStringParam(params, "replyTo"); - const accountId = readStringParam(params, "accountId"); const result = await sendMessageDiscord(`channel:${channelId}`, content, { accountId: accountId ?? undefined, mediaUrl, @@ -319,7 +336,7 @@ export async function handleDiscordMessagingAction( const messageId = readStringParam(params, "messageId", { required: true, }); - await pinMessageDiscord(channelId, messageId); + await pinMessageDiscord(channelId, messageId, accountOpts); return jsonResult({ ok: true }); } case "unpinMessage": { @@ -330,7 +347,7 @@ export async function handleDiscordMessagingAction( const messageId = readStringParam(params, "messageId", { required: true, }); - await unpinMessageDiscord(channelId, messageId); + await unpinMessageDiscord(channelId, messageId, accountOpts); return jsonResult({ ok: true }); } case "listPins": { @@ -338,7 +355,7 @@ export async function handleDiscordMessagingAction( throw new Error("Discord pins are disabled."); } const channelId = resolveChannelId(); - const pins = await listPinsDiscord(channelId); + const pins = await listPinsDiscord(channelId, accountOpts); return jsonResult({ ok: true, pins: pins.map((pin) => normalizeMessage(pin)) }); } case "searchMessages": { @@ -361,13 +378,16 @@ export async function handleDiscordMessagingAction( : undefined; const channelIdList = [...(channelIds ?? []), ...(channelId ? [channelId] : [])]; const authorIdList = [...(authorIds ?? []), ...(authorId ? [authorId] : [])]; - const results = await searchMessagesDiscord({ - guildId, - content, - channelIds: channelIdList.length ? channelIdList : undefined, - authorIds: authorIdList.length ? authorIdList : undefined, - limit, - }); + const results = await searchMessagesDiscord( + { + guildId, + content, + channelIds: channelIdList.length ? channelIdList : undefined, + authorIds: authorIdList.length ? authorIdList : undefined, + limit, + }, + accountOpts, + ); if (!results || typeof results !== "object") { return jsonResult({ ok: true, results }); } diff --git a/src/agents/tools/discord-actions-moderation.ts b/src/agents/tools/discord-actions-moderation.ts index 260ce85ea..5889d4880 100644 --- a/src/agents/tools/discord-actions-moderation.ts +++ b/src/agents/tools/discord-actions-moderation.ts @@ -8,6 +8,9 @@ export async function handleDiscordModerationAction( params: Record, isActionEnabled: ActionGate, ): Promise> { + const accountId = readStringParam(params, "accountId"); + const accountOpts = accountId ? { accountId } : {}; + switch (action) { case "timeout": { if (!isActionEnabled("moderation", false)) { @@ -25,13 +28,16 @@ export async function handleDiscordModerationAction( : undefined; const until = readStringParam(params, "until"); const reason = readStringParam(params, "reason"); - const member = await timeoutMemberDiscord({ - guildId, - userId, - durationMinutes, - until, - reason, - }); + const member = await timeoutMemberDiscord( + { + guildId, + userId, + durationMinutes, + until, + reason, + }, + accountOpts, + ); return jsonResult({ ok: true, member }); } case "kick": { @@ -45,7 +51,7 @@ export async function handleDiscordModerationAction( required: true, }); const reason = readStringParam(params, "reason"); - await kickMemberDiscord({ guildId, userId, reason }); + await kickMemberDiscord({ guildId, userId, reason }, accountOpts); return jsonResult({ ok: true }); } case "ban": { @@ -63,12 +69,15 @@ export async function handleDiscordModerationAction( typeof params.deleteMessageDays === "number" && Number.isFinite(params.deleteMessageDays) ? params.deleteMessageDays : undefined; - await banMemberDiscord({ - guildId, - userId, - reason, - deleteMessageDays, - }); + await banMemberDiscord( + { + guildId, + userId, + reason, + deleteMessageDays, + }, + accountOpts, + ); return jsonResult({ ok: true }); } default: diff --git a/src/channels/plugins/actions/discord.test.ts b/src/channels/plugins/actions/discord.test.ts index 8deda7dc6..d38f0ba88 100644 --- a/src/channels/plugins/actions/discord.test.ts +++ b/src/channels/plugins/actions/discord.test.ts @@ -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, ReturnType>( async () => ({ ok: true }) as Awaited>, @@ -10,6 +11,9 @@ const sendMessageDiscord = vi.fn, ReturnType, ReturnType>( async () => ({ ok: true }) as Awaited>, ); +const reactMessageDiscord = vi.fn, ReturnType>( + async () => ({ ok: true }) as Awaited>, +); vi.mock("../../../discord/send.js", async () => { const actual = await vi.importActual( @@ -19,6 +23,7 @@ vi.mock("../../../discord/send.js", async () => { ...actual, sendMessageDiscord: (...args: Parameters) => sendMessageDiscord(...args), sendPollDiscord: (...args: Parameters) => sendPollDiscord(...args), + reactMessageDiscord: (...args: Parameters) => 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", + }), + ); + }); }); diff --git a/src/channels/plugins/actions/discord/handle-action.guild-admin.ts b/src/channels/plugins/actions/discord/handle-action.guild-admin.ts index c2470e1dd..90091d881 100644 --- a/src/channels/plugins/actions/discord/handle-action.guild-admin.ts +++ b/src/channels/plugins/actions/discord/handle-action.guild-admin.ts @@ -7,7 +7,7 @@ import { import { handleDiscordAction } from "../../../../agents/tools/discord-actions.js"; import type { ChannelMessageActionContext } from "../../types.js"; -type Ctx = Pick; +type Ctx = Pick; export async function tryHandleDiscordMessageActionGuildAdmin(params: { ctx: Ctx; @@ -16,27 +16,38 @@ export async function tryHandleDiscordMessageActionGuildAdmin(params: { }): Promise | 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"), diff --git a/src/channels/plugins/actions/discord/handle-action.ts b/src/channels/plugins/actions/discord/handle-action.ts index 031ca9f5b..6c14ad209 100644 --- a/src/channels/plugins/actions/discord/handle-action.ts +++ b/src/channels/plugins/actions/discord/handle-action.ts @@ -22,6 +22,7 @@ export async function handleDiscordMessageAction( ): Promise> { 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"), From d371a4c8c3e1474e5963e5e41b1035e830791faf Mon Sep 17 00:00:00 2001 From: Sergii Kozak Date: Fri, 23 Jan 2026 01:11:54 -0800 Subject: [PATCH 3/3] Discord Actions: Update tests for optional config parameter --- src/agents/tools/discord-actions.test.ts | 203 +++++++++++++---------- 1 file changed, 118 insertions(+), 85 deletions(-) diff --git a/src/agents/tools/discord-actions.test.ts b/src/agents/tools/discord-actions.test.ts index 3eead3f40..cef4bf30c 100644 --- a/src/agents/tools/discord-actions.test.ts +++ b/src/agents/tools/discord-actions.test.ts @@ -78,7 +78,7 @@ describe("handleDiscordMessagingAction", () => { }, enableAllActions, ); - expect(reactMessageDiscord).toHaveBeenCalledWith("C1", "M1", "✅"); + expect(reactMessageDiscord).toHaveBeenCalledWith("C1", "M1", "✅", {}); }); it("removes reactions on empty emoji", async () => { @@ -91,7 +91,7 @@ describe("handleDiscordMessagingAction", () => { }, enableAllActions, ); - expect(removeOwnReactionsDiscord).toHaveBeenCalledWith("C1", "M1"); + expect(removeOwnReactionsDiscord).toHaveBeenCalledWith("C1", "M1", {}); }); it("removes reactions when remove flag set", async () => { @@ -105,7 +105,7 @@ describe("handleDiscordMessagingAction", () => { }, enableAllActions, ); - expect(removeReactionDiscord).toHaveBeenCalledWith("C1", "M1", "✅"); + expect(removeReactionDiscord).toHaveBeenCalledWith("C1", "M1", "✅", {}); }); it("rejects removes without emoji", async () => { @@ -227,15 +227,18 @@ describe("handleDiscordGuildAction - channel management", () => { }, channelsEnabled, ); - expect(createChannelDiscord).toHaveBeenCalledWith({ - guildId: "G1", - name: "test-channel", - type: 0, - parentId: undefined, - topic: "Test topic", - position: undefined, - nsfw: undefined, - }); + expect(createChannelDiscord).toHaveBeenCalledWith( + { + guildId: "G1", + name: "test-channel", + type: 0, + parentId: undefined, + topic: "Test topic", + position: undefined, + nsfw: undefined, + }, + {}, + ); expect(result.details).toMatchObject({ ok: true }); }); @@ -255,15 +258,18 @@ describe("handleDiscordGuildAction - channel management", () => { }, channelsEnabled, ); - expect(editChannelDiscord).toHaveBeenCalledWith({ - channelId: "C1", - name: "new-name", - topic: "new topic", - position: undefined, - parentId: undefined, - nsfw: undefined, - rateLimitPerUser: undefined, - }); + expect(editChannelDiscord).toHaveBeenCalledWith( + { + channelId: "C1", + name: "new-name", + topic: "new topic", + position: undefined, + parentId: undefined, + nsfw: undefined, + rateLimitPerUser: undefined, + }, + {}, + ); }); it("clears the channel parent when parentId is null", async () => { @@ -275,15 +281,18 @@ describe("handleDiscordGuildAction - channel management", () => { }, channelsEnabled, ); - expect(editChannelDiscord).toHaveBeenCalledWith({ - channelId: "C1", - name: undefined, - topic: undefined, - position: undefined, - parentId: null, - nsfw: undefined, - rateLimitPerUser: undefined, - }); + expect(editChannelDiscord).toHaveBeenCalledWith( + { + channelId: "C1", + name: undefined, + topic: undefined, + position: undefined, + parentId: null, + nsfw: undefined, + rateLimitPerUser: undefined, + }, + {}, + ); }); it("clears the channel parent when clearParent is true", async () => { @@ -295,20 +304,23 @@ describe("handleDiscordGuildAction - channel management", () => { }, channelsEnabled, ); - expect(editChannelDiscord).toHaveBeenCalledWith({ - channelId: "C1", - name: undefined, - topic: undefined, - position: undefined, - parentId: null, - nsfw: undefined, - rateLimitPerUser: undefined, - }); + expect(editChannelDiscord).toHaveBeenCalledWith( + { + channelId: "C1", + name: undefined, + topic: undefined, + position: undefined, + parentId: null, + nsfw: undefined, + rateLimitPerUser: undefined, + }, + {}, + ); }); it("deletes a channel", async () => { await handleDiscordGuildAction("channelDelete", { channelId: "C1" }, channelsEnabled); - expect(deleteChannelDiscord).toHaveBeenCalledWith("C1"); + expect(deleteChannelDiscord).toHaveBeenCalledWith("C1", {}); }); it("moves a channel", async () => { @@ -322,12 +334,15 @@ describe("handleDiscordGuildAction - channel management", () => { }, channelsEnabled, ); - expect(moveChannelDiscord).toHaveBeenCalledWith({ - guildId: "G1", - channelId: "C1", - parentId: "P1", - position: 5, - }); + expect(moveChannelDiscord).toHaveBeenCalledWith( + { + guildId: "G1", + channelId: "C1", + parentId: "P1", + position: 5, + }, + {}, + ); }); it("clears the channel parent on move when parentId is null", async () => { @@ -340,12 +355,15 @@ describe("handleDiscordGuildAction - channel management", () => { }, channelsEnabled, ); - expect(moveChannelDiscord).toHaveBeenCalledWith({ - guildId: "G1", - channelId: "C1", - parentId: null, - position: undefined, - }); + expect(moveChannelDiscord).toHaveBeenCalledWith( + { + guildId: "G1", + channelId: "C1", + parentId: null, + position: undefined, + }, + {}, + ); }); it("clears the channel parent on move when clearParent is true", async () => { @@ -358,12 +376,15 @@ describe("handleDiscordGuildAction - channel management", () => { }, channelsEnabled, ); - expect(moveChannelDiscord).toHaveBeenCalledWith({ - guildId: "G1", - channelId: "C1", - parentId: null, - position: undefined, - }); + expect(moveChannelDiscord).toHaveBeenCalledWith( + { + guildId: "G1", + channelId: "C1", + parentId: null, + position: undefined, + }, + {}, + ); }); it("creates a category with type=4", async () => { @@ -372,12 +393,15 @@ describe("handleDiscordGuildAction - channel management", () => { { guildId: "G1", name: "My Category" }, channelsEnabled, ); - expect(createChannelDiscord).toHaveBeenCalledWith({ - guildId: "G1", - name: "My Category", - type: 4, - position: undefined, - }); + expect(createChannelDiscord).toHaveBeenCalledWith( + { + guildId: "G1", + name: "My Category", + type: 4, + position: undefined, + }, + {}, + ); }); it("edits a category", async () => { @@ -386,16 +410,19 @@ describe("handleDiscordGuildAction - channel management", () => { { categoryId: "CAT1", name: "Renamed Category" }, channelsEnabled, ); - expect(editChannelDiscord).toHaveBeenCalledWith({ - channelId: "CAT1", - name: "Renamed Category", - position: undefined, - }); + expect(editChannelDiscord).toHaveBeenCalledWith( + { + channelId: "CAT1", + name: "Renamed Category", + position: undefined, + }, + {}, + ); }); it("deletes a category", async () => { await handleDiscordGuildAction("categoryDelete", { categoryId: "CAT1" }, channelsEnabled); - expect(deleteChannelDiscord).toHaveBeenCalledWith("CAT1"); + expect(deleteChannelDiscord).toHaveBeenCalledWith("CAT1", {}); }); it("sets channel permissions for role", async () => { @@ -410,13 +437,16 @@ describe("handleDiscordGuildAction - channel management", () => { }, channelsEnabled, ); - expect(setChannelPermissionDiscord).toHaveBeenCalledWith({ - channelId: "C1", - targetId: "R1", - targetType: 0, - allow: "1024", - deny: "2048", - }); + expect(setChannelPermissionDiscord).toHaveBeenCalledWith( + { + channelId: "C1", + targetId: "R1", + targetType: 0, + allow: "1024", + deny: "2048", + }, + {}, + ); }); it("sets channel permissions for member", async () => { @@ -430,13 +460,16 @@ describe("handleDiscordGuildAction - channel management", () => { }, channelsEnabled, ); - expect(setChannelPermissionDiscord).toHaveBeenCalledWith({ - channelId: "C1", - targetId: "U1", - targetType: 1, - allow: "1024", - deny: undefined, - }); + expect(setChannelPermissionDiscord).toHaveBeenCalledWith( + { + channelId: "C1", + targetId: "U1", + targetType: 1, + allow: "1024", + deny: undefined, + }, + {}, + ); }); it("removes channel permissions", async () => { @@ -445,6 +478,6 @@ describe("handleDiscordGuildAction - channel management", () => { { channelId: "C1", targetId: "R1" }, channelsEnabled, ); - expect(removeChannelPermissionDiscord).toHaveBeenCalledWith("C1", "R1"); + expect(removeChannelPermissionDiscord).toHaveBeenCalledWith("C1", "R1", {}); }); });