fix: honor accountId in message actions
This commit is contained in:
@@ -188,4 +188,26 @@ describe("cron tool", () => {
|
||||
const text = cronCall.params?.payload?.text ?? "";
|
||||
expect(text).not.toContain("Recent context:");
|
||||
});
|
||||
|
||||
it("preserves explicit agentId null on add", async () => {
|
||||
callGatewayMock.mockResolvedValueOnce({ ok: true });
|
||||
|
||||
const tool = createCronTool({ agentSessionKey: "main" });
|
||||
await tool.execute("call6", {
|
||||
action: "add",
|
||||
job: {
|
||||
name: "reminder",
|
||||
schedule: { atMs: 123 },
|
||||
agentId: null,
|
||||
payload: { kind: "systemEvent", text: "Reminder: the thing." },
|
||||
},
|
||||
});
|
||||
|
||||
const call = callGatewayMock.mock.calls[0]?.[0] as {
|
||||
method?: string;
|
||||
params?: { agentId?: string | null };
|
||||
};
|
||||
expect(call.method).toBe("cron.add");
|
||||
expect(call.params?.agentId).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,6 +3,7 @@ import { normalizeCronJobCreate, normalizeCronJobPatch } from "../../cron/normal
|
||||
import { loadConfig } from "../../config/config.js";
|
||||
import { truncateUtf16Safe } from "../../utils.js";
|
||||
import { optionalStringEnum, stringEnum } from "../schema/typebox.js";
|
||||
import { resolveSessionAgentId } from "../agent-scope.js";
|
||||
import { type AnyAgentTool, jsonResult, readStringParam } from "./common.js";
|
||||
import { callGatewayTool, type GatewayCallOptions } from "./gateway.js";
|
||||
import { resolveInternalSessionKey, resolveMainSessionAlias } from "./sessions-helpers.js";
|
||||
@@ -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" && !("agentId" in job)) {
|
||||
const cfg = loadConfig();
|
||||
const agentId = opts?.agentSessionKey
|
||||
? resolveSessionAgentId({ sessionKey: opts.agentSessionKey, config: cfg })
|
||||
: undefined;
|
||||
if (agentId) {
|
||||
(job as { agentId?: string }).agentId = agentId;
|
||||
}
|
||||
}
|
||||
const contextMessages =
|
||||
typeof params.contextMessages === "number" && Number.isFinite(params.contextMessages)
|
||||
? params.contextMessages
|
||||
|
||||
@@ -39,6 +39,7 @@ export async function handleDiscordGuildAction(
|
||||
params: Record<string, unknown>,
|
||||
isActionEnabled: ActionGate<DiscordActionConfig>,
|
||||
): Promise<AgentToolResult<unknown>> {
|
||||
const accountId = readStringParam(params, "accountId");
|
||||
switch (action) {
|
||||
case "memberInfo": {
|
||||
if (!isActionEnabled("memberInfo")) {
|
||||
@@ -50,7 +51,9 @@ export async function handleDiscordGuildAction(
|
||||
const userId = readStringParam(params, "userId", {
|
||||
required: true,
|
||||
});
|
||||
const member = await fetchMemberInfoDiscord(guildId, userId);
|
||||
const member = accountId
|
||||
? await fetchMemberInfoDiscord(guildId, userId, { accountId })
|
||||
: await fetchMemberInfoDiscord(guildId, userId);
|
||||
return jsonResult({ ok: true, member });
|
||||
}
|
||||
case "roleInfo": {
|
||||
@@ -60,7 +63,9 @@ export async function handleDiscordGuildAction(
|
||||
const guildId = readStringParam(params, "guildId", {
|
||||
required: true,
|
||||
});
|
||||
const roles = await fetchRoleInfoDiscord(guildId);
|
||||
const roles = accountId
|
||||
? await fetchRoleInfoDiscord(guildId, { accountId })
|
||||
: await fetchRoleInfoDiscord(guildId);
|
||||
return jsonResult({ ok: true, roles });
|
||||
}
|
||||
case "emojiList": {
|
||||
@@ -70,7 +75,9 @@ export async function handleDiscordGuildAction(
|
||||
const guildId = readStringParam(params, "guildId", {
|
||||
required: true,
|
||||
});
|
||||
const emojis = await listGuildEmojisDiscord(guildId);
|
||||
const emojis = accountId
|
||||
? await listGuildEmojisDiscord(guildId, { accountId })
|
||||
: await listGuildEmojisDiscord(guildId);
|
||||
return jsonResult({ ok: true, emojis });
|
||||
}
|
||||
case "emojiUpload": {
|
||||
@@ -85,12 +92,22 @@ 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 = accountId
|
||||
? await uploadEmojiDiscord(
|
||||
{
|
||||
guildId,
|
||||
name,
|
||||
mediaUrl,
|
||||
roleIds: roleIds?.length ? roleIds : undefined,
|
||||
},
|
||||
{ accountId },
|
||||
)
|
||||
: await uploadEmojiDiscord({
|
||||
guildId,
|
||||
name,
|
||||
mediaUrl,
|
||||
roleIds: roleIds?.length ? roleIds : undefined,
|
||||
});
|
||||
return jsonResult({ ok: true, emoji });
|
||||
}
|
||||
case "stickerUpload": {
|
||||
@@ -108,13 +125,24 @@ export async function handleDiscordGuildAction(
|
||||
const mediaUrl = readStringParam(params, "mediaUrl", {
|
||||
required: true,
|
||||
});
|
||||
const sticker = await uploadStickerDiscord({
|
||||
guildId,
|
||||
name,
|
||||
description,
|
||||
tags,
|
||||
mediaUrl,
|
||||
});
|
||||
const sticker = accountId
|
||||
? await uploadStickerDiscord(
|
||||
{
|
||||
guildId,
|
||||
name,
|
||||
description,
|
||||
tags,
|
||||
mediaUrl,
|
||||
},
|
||||
{ accountId },
|
||||
)
|
||||
: await uploadStickerDiscord({
|
||||
guildId,
|
||||
name,
|
||||
description,
|
||||
tags,
|
||||
mediaUrl,
|
||||
});
|
||||
return jsonResult({ ok: true, sticker });
|
||||
}
|
||||
case "roleAdd": {
|
||||
@@ -128,7 +156,11 @@ export async function handleDiscordGuildAction(
|
||||
required: true,
|
||||
});
|
||||
const roleId = readStringParam(params, "roleId", { required: true });
|
||||
await addRoleDiscord({ guildId, userId, roleId });
|
||||
if (accountId) {
|
||||
await addRoleDiscord({ guildId, userId, roleId }, { accountId });
|
||||
} else {
|
||||
await addRoleDiscord({ guildId, userId, roleId });
|
||||
}
|
||||
return jsonResult({ ok: true });
|
||||
}
|
||||
case "roleRemove": {
|
||||
@@ -142,7 +174,11 @@ export async function handleDiscordGuildAction(
|
||||
required: true,
|
||||
});
|
||||
const roleId = readStringParam(params, "roleId", { required: true });
|
||||
await removeRoleDiscord({ guildId, userId, roleId });
|
||||
if (accountId) {
|
||||
await removeRoleDiscord({ guildId, userId, roleId }, { accountId });
|
||||
} else {
|
||||
await removeRoleDiscord({ guildId, userId, roleId });
|
||||
}
|
||||
return jsonResult({ ok: true });
|
||||
}
|
||||
case "channelInfo": {
|
||||
@@ -152,7 +188,9 @@ export async function handleDiscordGuildAction(
|
||||
const channelId = readStringParam(params, "channelId", {
|
||||
required: true,
|
||||
});
|
||||
const channel = await fetchChannelInfoDiscord(channelId);
|
||||
const channel = accountId
|
||||
? await fetchChannelInfoDiscord(channelId, { accountId })
|
||||
: await fetchChannelInfoDiscord(channelId);
|
||||
return jsonResult({ ok: true, channel });
|
||||
}
|
||||
case "channelList": {
|
||||
@@ -162,7 +200,9 @@ export async function handleDiscordGuildAction(
|
||||
const guildId = readStringParam(params, "guildId", {
|
||||
required: true,
|
||||
});
|
||||
const channels = await listGuildChannelsDiscord(guildId);
|
||||
const channels = accountId
|
||||
? await listGuildChannelsDiscord(guildId, { accountId })
|
||||
: await listGuildChannelsDiscord(guildId);
|
||||
return jsonResult({ ok: true, channels });
|
||||
}
|
||||
case "voiceStatus": {
|
||||
@@ -175,7 +215,9 @@ export async function handleDiscordGuildAction(
|
||||
const userId = readStringParam(params, "userId", {
|
||||
required: true,
|
||||
});
|
||||
const voice = await fetchVoiceStatusDiscord(guildId, userId);
|
||||
const voice = accountId
|
||||
? await fetchVoiceStatusDiscord(guildId, userId, { accountId })
|
||||
: await fetchVoiceStatusDiscord(guildId, userId);
|
||||
return jsonResult({ ok: true, voice });
|
||||
}
|
||||
case "eventList": {
|
||||
@@ -185,7 +227,9 @@ export async function handleDiscordGuildAction(
|
||||
const guildId = readStringParam(params, "guildId", {
|
||||
required: true,
|
||||
});
|
||||
const events = await listScheduledEventsDiscord(guildId);
|
||||
const events = accountId
|
||||
? await listScheduledEventsDiscord(guildId, { accountId })
|
||||
: await listScheduledEventsDiscord(guildId);
|
||||
return jsonResult({ ok: true, events });
|
||||
}
|
||||
case "eventCreate": {
|
||||
@@ -215,7 +259,9 @@ export async function handleDiscordGuildAction(
|
||||
entity_metadata: entityType === 3 && location ? { location } : undefined,
|
||||
privacy_level: 2,
|
||||
};
|
||||
const event = await createScheduledEventDiscord(guildId, payload);
|
||||
const event = accountId
|
||||
? await createScheduledEventDiscord(guildId, payload, { accountId })
|
||||
: await createScheduledEventDiscord(guildId, payload);
|
||||
return jsonResult({ ok: true, event });
|
||||
}
|
||||
case "channelCreate": {
|
||||
@@ -229,15 +275,28 @@ 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 = accountId
|
||||
? await createChannelDiscord(
|
||||
{
|
||||
guildId,
|
||||
name,
|
||||
type: type ?? undefined,
|
||||
parentId: parentId ?? undefined,
|
||||
topic: topic ?? undefined,
|
||||
position: position ?? undefined,
|
||||
nsfw,
|
||||
},
|
||||
{ accountId },
|
||||
)
|
||||
: await createChannelDiscord({
|
||||
guildId,
|
||||
name,
|
||||
type: type ?? undefined,
|
||||
parentId: parentId ?? undefined,
|
||||
topic: topic ?? undefined,
|
||||
position: position ?? undefined,
|
||||
nsfw,
|
||||
});
|
||||
return jsonResult({ ok: true, channel });
|
||||
}
|
||||
case "channelEdit": {
|
||||
@@ -255,15 +314,28 @@ 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 = accountId
|
||||
? await editChannelDiscord(
|
||||
{
|
||||
channelId,
|
||||
name: name ?? undefined,
|
||||
topic: topic ?? undefined,
|
||||
position: position ?? undefined,
|
||||
parentId,
|
||||
nsfw,
|
||||
rateLimitPerUser: rateLimitPerUser ?? undefined,
|
||||
},
|
||||
{ accountId },
|
||||
)
|
||||
: await editChannelDiscord({
|
||||
channelId,
|
||||
name: name ?? undefined,
|
||||
topic: topic ?? undefined,
|
||||
position: position ?? undefined,
|
||||
parentId,
|
||||
nsfw,
|
||||
rateLimitPerUser: rateLimitPerUser ?? undefined,
|
||||
});
|
||||
return jsonResult({ ok: true, channel });
|
||||
}
|
||||
case "channelDelete": {
|
||||
@@ -273,7 +345,9 @@ export async function handleDiscordGuildAction(
|
||||
const channelId = readStringParam(params, "channelId", {
|
||||
required: true,
|
||||
});
|
||||
const result = await deleteChannelDiscord(channelId);
|
||||
const result = accountId
|
||||
? await deleteChannelDiscord(channelId, { accountId })
|
||||
: await deleteChannelDiscord(channelId);
|
||||
return jsonResult(result);
|
||||
}
|
||||
case "channelMove": {
|
||||
@@ -286,12 +360,24 @@ export async function handleDiscordGuildAction(
|
||||
});
|
||||
const parentId = readParentIdParam(params);
|
||||
const position = readNumberParam(params, "position", { integer: true });
|
||||
await moveChannelDiscord({
|
||||
guildId,
|
||||
channelId,
|
||||
parentId,
|
||||
position: position ?? undefined,
|
||||
});
|
||||
if (accountId) {
|
||||
await moveChannelDiscord(
|
||||
{
|
||||
guildId,
|
||||
channelId,
|
||||
parentId,
|
||||
position: position ?? undefined,
|
||||
},
|
||||
{ accountId },
|
||||
);
|
||||
} else {
|
||||
await moveChannelDiscord({
|
||||
guildId,
|
||||
channelId,
|
||||
parentId,
|
||||
position: position ?? undefined,
|
||||
});
|
||||
}
|
||||
return jsonResult({ ok: true });
|
||||
}
|
||||
case "categoryCreate": {
|
||||
@@ -301,12 +387,22 @@ 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 = accountId
|
||||
? await createChannelDiscord(
|
||||
{
|
||||
guildId,
|
||||
name,
|
||||
type: 4,
|
||||
position: position ?? undefined,
|
||||
},
|
||||
{ accountId },
|
||||
)
|
||||
: await createChannelDiscord({
|
||||
guildId,
|
||||
name,
|
||||
type: 4,
|
||||
position: position ?? undefined,
|
||||
});
|
||||
return jsonResult({ ok: true, category: channel });
|
||||
}
|
||||
case "categoryEdit": {
|
||||
@@ -318,11 +414,20 @@ 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 = accountId
|
||||
? await editChannelDiscord(
|
||||
{
|
||||
channelId: categoryId,
|
||||
name: name ?? undefined,
|
||||
position: position ?? undefined,
|
||||
},
|
||||
{ accountId },
|
||||
)
|
||||
: await editChannelDiscord({
|
||||
channelId: categoryId,
|
||||
name: name ?? undefined,
|
||||
position: position ?? undefined,
|
||||
});
|
||||
return jsonResult({ ok: true, category: channel });
|
||||
}
|
||||
case "categoryDelete": {
|
||||
@@ -332,7 +437,9 @@ export async function handleDiscordGuildAction(
|
||||
const categoryId = readStringParam(params, "categoryId", {
|
||||
required: true,
|
||||
});
|
||||
const result = await deleteChannelDiscord(categoryId);
|
||||
const result = accountId
|
||||
? await deleteChannelDiscord(categoryId, { accountId })
|
||||
: await deleteChannelDiscord(categoryId);
|
||||
return jsonResult(result);
|
||||
}
|
||||
case "channelPermissionSet": {
|
||||
@@ -349,13 +456,26 @@ 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,
|
||||
});
|
||||
if (accountId) {
|
||||
await setChannelPermissionDiscord(
|
||||
{
|
||||
channelId,
|
||||
targetId,
|
||||
targetType,
|
||||
allow: allow ?? undefined,
|
||||
deny: deny ?? undefined,
|
||||
},
|
||||
{ accountId },
|
||||
);
|
||||
} else {
|
||||
await setChannelPermissionDiscord({
|
||||
channelId,
|
||||
targetId,
|
||||
targetType,
|
||||
allow: allow ?? undefined,
|
||||
deny: deny ?? undefined,
|
||||
});
|
||||
}
|
||||
return jsonResult({ ok: true });
|
||||
}
|
||||
case "channelPermissionRemove": {
|
||||
@@ -366,7 +486,11 @@ export async function handleDiscordGuildAction(
|
||||
required: true,
|
||||
});
|
||||
const targetId = readStringParam(params, "targetId", { required: true });
|
||||
await removeChannelPermissionDiscord(channelId, targetId);
|
||||
if (accountId) {
|
||||
await removeChannelPermissionDiscord(channelId, targetId, { accountId });
|
||||
} else {
|
||||
await removeChannelPermissionDiscord(channelId, targetId);
|
||||
}
|
||||
return jsonResult({ ok: true });
|
||||
}
|
||||
default:
|
||||
|
||||
@@ -58,6 +58,7 @@ export async function handleDiscordMessagingAction(
|
||||
required: true,
|
||||
}),
|
||||
);
|
||||
const accountId = readStringParam(params, "accountId");
|
||||
const normalizeMessage = (message: unknown) => {
|
||||
if (!message || typeof message !== "object") return message;
|
||||
return withNormalizedTimestamp(
|
||||
@@ -78,14 +79,24 @@ export async function handleDiscordMessagingAction(
|
||||
removeErrorMessage: "Emoji is required to remove a Discord reaction.",
|
||||
});
|
||||
if (remove) {
|
||||
await removeReactionDiscord(channelId, messageId, emoji);
|
||||
if (accountId) {
|
||||
await removeReactionDiscord(channelId, messageId, emoji, { accountId });
|
||||
} else {
|
||||
await removeReactionDiscord(channelId, messageId, emoji);
|
||||
}
|
||||
return jsonResult({ ok: true, removed: emoji });
|
||||
}
|
||||
if (isEmpty) {
|
||||
const removed = await removeOwnReactionsDiscord(channelId, messageId);
|
||||
const removed = accountId
|
||||
? await removeOwnReactionsDiscord(channelId, messageId, { accountId })
|
||||
: await removeOwnReactionsDiscord(channelId, messageId);
|
||||
return jsonResult({ ok: true, removed: removed.removed });
|
||||
}
|
||||
await reactMessageDiscord(channelId, messageId, emoji);
|
||||
if (accountId) {
|
||||
await reactMessageDiscord(channelId, messageId, emoji, { accountId });
|
||||
} else {
|
||||
await reactMessageDiscord(channelId, messageId, emoji);
|
||||
}
|
||||
return jsonResult({ ok: true, added: emoji });
|
||||
}
|
||||
case "reactions": {
|
||||
@@ -100,6 +111,7 @@ export async function handleDiscordMessagingAction(
|
||||
const limit =
|
||||
typeof limitRaw === "number" && Number.isFinite(limitRaw) ? limitRaw : undefined;
|
||||
const reactions = await fetchReactionsDiscord(channelId, messageId, {
|
||||
...(accountId ? { accountId } : {}),
|
||||
limit,
|
||||
});
|
||||
return jsonResult({ ok: true, reactions });
|
||||
@@ -114,7 +126,10 @@ export async function handleDiscordMessagingAction(
|
||||
required: true,
|
||||
label: "stickerIds",
|
||||
});
|
||||
await sendStickerDiscord(to, stickerIds, { content });
|
||||
await sendStickerDiscord(to, stickerIds, {
|
||||
...(accountId ? { accountId } : {}),
|
||||
content,
|
||||
});
|
||||
return jsonResult({ ok: true });
|
||||
}
|
||||
case "poll": {
|
||||
@@ -140,7 +155,7 @@ export async function handleDiscordMessagingAction(
|
||||
await sendPollDiscord(
|
||||
to,
|
||||
{ question, options: answers, maxSelections, durationHours },
|
||||
{ content },
|
||||
{ ...(accountId ? { accountId } : {}), content },
|
||||
);
|
||||
return jsonResult({ ok: true });
|
||||
}
|
||||
@@ -149,7 +164,9 @@ export async function handleDiscordMessagingAction(
|
||||
throw new Error("Discord permissions are disabled.");
|
||||
}
|
||||
const channelId = resolveChannelId();
|
||||
const permissions = await fetchChannelPermissionsDiscord(channelId);
|
||||
const permissions = accountId
|
||||
? await fetchChannelPermissionsDiscord(channelId, { accountId })
|
||||
: await fetchChannelPermissionsDiscord(channelId);
|
||||
return jsonResult({ ok: true, permissions });
|
||||
}
|
||||
case "fetchMessage": {
|
||||
@@ -171,7 +188,9 @@ export async function handleDiscordMessagingAction(
|
||||
"Discord message fetch requires guildId, channelId, and messageId (or a valid messageLink).",
|
||||
);
|
||||
}
|
||||
const message = await fetchMessageDiscord(channelId, messageId);
|
||||
const message = accountId
|
||||
? await fetchMessageDiscord(channelId, messageId, { accountId })
|
||||
: await fetchMessageDiscord(channelId, messageId);
|
||||
return jsonResult({
|
||||
ok: true,
|
||||
message: normalizeMessage(message),
|
||||
@@ -185,7 +204,7 @@ export async function handleDiscordMessagingAction(
|
||||
throw new Error("Discord message reads are disabled.");
|
||||
}
|
||||
const channelId = resolveChannelId();
|
||||
const messages = await readMessagesDiscord(channelId, {
|
||||
const query = {
|
||||
limit:
|
||||
typeof params.limit === "number" && Number.isFinite(params.limit)
|
||||
? params.limit
|
||||
@@ -193,7 +212,10 @@ export async function handleDiscordMessagingAction(
|
||||
before: readStringParam(params, "before"),
|
||||
after: readStringParam(params, "after"),
|
||||
around: readStringParam(params, "around"),
|
||||
});
|
||||
};
|
||||
const messages = accountId
|
||||
? await readMessagesDiscord(channelId, query, { accountId })
|
||||
: await readMessagesDiscord(channelId, query);
|
||||
return jsonResult({
|
||||
ok: true,
|
||||
messages: messages.map((message) => normalizeMessage(message)),
|
||||
@@ -212,6 +234,7 @@ export async function handleDiscordMessagingAction(
|
||||
const embeds =
|
||||
Array.isArray(params.embeds) && params.embeds.length > 0 ? params.embeds : undefined;
|
||||
const result = await sendMessageDiscord(to, content, {
|
||||
...(accountId ? { accountId } : {}),
|
||||
mediaUrl,
|
||||
replyTo,
|
||||
embeds,
|
||||
@@ -229,9 +252,9 @@ export async function handleDiscordMessagingAction(
|
||||
const content = readStringParam(params, "content", {
|
||||
required: true,
|
||||
});
|
||||
const message = await editMessageDiscord(channelId, messageId, {
|
||||
content,
|
||||
});
|
||||
const message = accountId
|
||||
? await editMessageDiscord(channelId, messageId, { content }, { accountId })
|
||||
: await editMessageDiscord(channelId, messageId, { content });
|
||||
return jsonResult({ ok: true, message });
|
||||
}
|
||||
case "deleteMessage": {
|
||||
@@ -242,7 +265,11 @@ export async function handleDiscordMessagingAction(
|
||||
const messageId = readStringParam(params, "messageId", {
|
||||
required: true,
|
||||
});
|
||||
await deleteMessageDiscord(channelId, messageId);
|
||||
if (accountId) {
|
||||
await deleteMessageDiscord(channelId, messageId, { accountId });
|
||||
} else {
|
||||
await deleteMessageDiscord(channelId, messageId);
|
||||
}
|
||||
return jsonResult({ ok: true });
|
||||
}
|
||||
case "threadCreate": {
|
||||
@@ -257,11 +284,13 @@ export async function handleDiscordMessagingAction(
|
||||
typeof autoArchiveMinutesRaw === "number" && Number.isFinite(autoArchiveMinutesRaw)
|
||||
? autoArchiveMinutesRaw
|
||||
: undefined;
|
||||
const thread = await createThreadDiscord(channelId, {
|
||||
name,
|
||||
messageId,
|
||||
autoArchiveMinutes,
|
||||
});
|
||||
const thread = accountId
|
||||
? await createThreadDiscord(
|
||||
channelId,
|
||||
{ name, messageId, autoArchiveMinutes },
|
||||
{ accountId },
|
||||
)
|
||||
: await createThreadDiscord(channelId, { name, messageId, autoArchiveMinutes });
|
||||
return jsonResult({ ok: true, thread });
|
||||
}
|
||||
case "threadList": {
|
||||
@@ -279,13 +308,24 @@ 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 = accountId
|
||||
? await listThreadsDiscord(
|
||||
{
|
||||
guildId,
|
||||
channelId,
|
||||
includeArchived,
|
||||
before,
|
||||
limit,
|
||||
},
|
||||
{ accountId },
|
||||
)
|
||||
: await listThreadsDiscord({
|
||||
guildId,
|
||||
channelId,
|
||||
includeArchived,
|
||||
before,
|
||||
limit,
|
||||
});
|
||||
return jsonResult({ ok: true, threads });
|
||||
}
|
||||
case "threadReply": {
|
||||
@@ -299,6 +339,7 @@ export async function handleDiscordMessagingAction(
|
||||
const mediaUrl = readStringParam(params, "mediaUrl");
|
||||
const replyTo = readStringParam(params, "replyTo");
|
||||
const result = await sendMessageDiscord(`channel:${channelId}`, content, {
|
||||
...(accountId ? { accountId } : {}),
|
||||
mediaUrl,
|
||||
replyTo,
|
||||
});
|
||||
@@ -312,7 +353,11 @@ export async function handleDiscordMessagingAction(
|
||||
const messageId = readStringParam(params, "messageId", {
|
||||
required: true,
|
||||
});
|
||||
await pinMessageDiscord(channelId, messageId);
|
||||
if (accountId) {
|
||||
await pinMessageDiscord(channelId, messageId, { accountId });
|
||||
} else {
|
||||
await pinMessageDiscord(channelId, messageId);
|
||||
}
|
||||
return jsonResult({ ok: true });
|
||||
}
|
||||
case "unpinMessage": {
|
||||
@@ -323,7 +368,11 @@ export async function handleDiscordMessagingAction(
|
||||
const messageId = readStringParam(params, "messageId", {
|
||||
required: true,
|
||||
});
|
||||
await unpinMessageDiscord(channelId, messageId);
|
||||
if (accountId) {
|
||||
await unpinMessageDiscord(channelId, messageId, { accountId });
|
||||
} else {
|
||||
await unpinMessageDiscord(channelId, messageId);
|
||||
}
|
||||
return jsonResult({ ok: true });
|
||||
}
|
||||
case "listPins": {
|
||||
@@ -331,7 +380,9 @@ export async function handleDiscordMessagingAction(
|
||||
throw new Error("Discord pins are disabled.");
|
||||
}
|
||||
const channelId = resolveChannelId();
|
||||
const pins = await listPinsDiscord(channelId);
|
||||
const pins = accountId
|
||||
? await listPinsDiscord(channelId, { accountId })
|
||||
: await listPinsDiscord(channelId);
|
||||
return jsonResult({ ok: true, pins: pins.map((pin) => normalizeMessage(pin)) });
|
||||
}
|
||||
case "searchMessages": {
|
||||
@@ -354,13 +405,24 @@ 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 = accountId
|
||||
? await searchMessagesDiscord(
|
||||
{
|
||||
guildId,
|
||||
content,
|
||||
channelIds: channelIdList.length ? channelIdList : undefined,
|
||||
authorIds: authorIdList.length ? authorIdList : undefined,
|
||||
limit,
|
||||
},
|
||||
{ accountId },
|
||||
)
|
||||
: await searchMessagesDiscord({
|
||||
guildId,
|
||||
content,
|
||||
channelIds: channelIdList.length ? channelIdList : undefined,
|
||||
authorIds: authorIdList.length ? authorIdList : undefined,
|
||||
limit,
|
||||
});
|
||||
if (!results || typeof results !== "object") {
|
||||
return jsonResult({ ok: true, results });
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ export async function handleDiscordModerationAction(
|
||||
params: Record<string, unknown>,
|
||||
isActionEnabled: ActionGate<DiscordActionConfig>,
|
||||
): Promise<AgentToolResult<unknown>> {
|
||||
const accountId = readStringParam(params, "accountId");
|
||||
switch (action) {
|
||||
case "timeout": {
|
||||
if (!isActionEnabled("moderation", false)) {
|
||||
@@ -25,13 +26,24 @@ 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 = accountId
|
||||
? await timeoutMemberDiscord(
|
||||
{
|
||||
guildId,
|
||||
userId,
|
||||
durationMinutes,
|
||||
until,
|
||||
reason,
|
||||
},
|
||||
{ accountId },
|
||||
)
|
||||
: await timeoutMemberDiscord({
|
||||
guildId,
|
||||
userId,
|
||||
durationMinutes,
|
||||
until,
|
||||
reason,
|
||||
});
|
||||
return jsonResult({ ok: true, member });
|
||||
}
|
||||
case "kick": {
|
||||
@@ -45,7 +57,11 @@ export async function handleDiscordModerationAction(
|
||||
required: true,
|
||||
});
|
||||
const reason = readStringParam(params, "reason");
|
||||
await kickMemberDiscord({ guildId, userId, reason });
|
||||
if (accountId) {
|
||||
await kickMemberDiscord({ guildId, userId, reason }, { accountId });
|
||||
} else {
|
||||
await kickMemberDiscord({ guildId, userId, reason });
|
||||
}
|
||||
return jsonResult({ ok: true });
|
||||
}
|
||||
case "ban": {
|
||||
@@ -63,12 +79,24 @@ export async function handleDiscordModerationAction(
|
||||
typeof params.deleteMessageDays === "number" && Number.isFinite(params.deleteMessageDays)
|
||||
? params.deleteMessageDays
|
||||
: undefined;
|
||||
await banMemberDiscord({
|
||||
guildId,
|
||||
userId,
|
||||
reason,
|
||||
deleteMessageDays,
|
||||
});
|
||||
if (accountId) {
|
||||
await banMemberDiscord(
|
||||
{
|
||||
guildId,
|
||||
userId,
|
||||
reason,
|
||||
deleteMessageDays,
|
||||
},
|
||||
{ accountId },
|
||||
);
|
||||
} else {
|
||||
await banMemberDiscord({
|
||||
guildId,
|
||||
userId,
|
||||
reason,
|
||||
deleteMessageDays,
|
||||
});
|
||||
}
|
||||
return jsonResult({ ok: true });
|
||||
}
|
||||
default:
|
||||
|
||||
@@ -3,6 +3,7 @@ import { describe, expect, it, vi } from "vitest";
|
||||
import type { DiscordActionConfig } from "../../config/config.js";
|
||||
import { handleDiscordGuildAction } from "./discord-actions-guild.js";
|
||||
import { handleDiscordMessagingAction } from "./discord-actions-messaging.js";
|
||||
import { handleDiscordModerationAction } from "./discord-actions-moderation.js";
|
||||
|
||||
const createChannelDiscord = vi.fn(async () => ({
|
||||
id: "new-channel",
|
||||
@@ -35,8 +36,12 @@ const sendPollDiscord = vi.fn(async () => ({}));
|
||||
const sendStickerDiscord = vi.fn(async () => ({}));
|
||||
const setChannelPermissionDiscord = vi.fn(async () => ({ ok: true }));
|
||||
const unpinMessageDiscord = vi.fn(async () => ({}));
|
||||
const timeoutMemberDiscord = vi.fn(async () => ({}));
|
||||
const kickMemberDiscord = vi.fn(async () => ({}));
|
||||
const banMemberDiscord = vi.fn(async () => ({}));
|
||||
|
||||
vi.mock("../../discord/send.js", () => ({
|
||||
banMemberDiscord: (...args: unknown[]) => banMemberDiscord(...args),
|
||||
createChannelDiscord: (...args: unknown[]) => createChannelDiscord(...args),
|
||||
createThreadDiscord: (...args: unknown[]) => createThreadDiscord(...args),
|
||||
deleteChannelDiscord: (...args: unknown[]) => deleteChannelDiscord(...args),
|
||||
@@ -46,6 +51,7 @@ vi.mock("../../discord/send.js", () => ({
|
||||
fetchMessageDiscord: (...args: unknown[]) => fetchMessageDiscord(...args),
|
||||
fetchChannelPermissionsDiscord: (...args: unknown[]) => fetchChannelPermissionsDiscord(...args),
|
||||
fetchReactionsDiscord: (...args: unknown[]) => fetchReactionsDiscord(...args),
|
||||
kickMemberDiscord: (...args: unknown[]) => kickMemberDiscord(...args),
|
||||
listPinsDiscord: (...args: unknown[]) => listPinsDiscord(...args),
|
||||
listThreadsDiscord: (...args: unknown[]) => listThreadsDiscord(...args),
|
||||
moveChannelDiscord: (...args: unknown[]) => moveChannelDiscord(...args),
|
||||
@@ -60,12 +66,15 @@ vi.mock("../../discord/send.js", () => ({
|
||||
sendPollDiscord: (...args: unknown[]) => sendPollDiscord(...args),
|
||||
sendStickerDiscord: (...args: unknown[]) => sendStickerDiscord(...args),
|
||||
setChannelPermissionDiscord: (...args: unknown[]) => setChannelPermissionDiscord(...args),
|
||||
timeoutMemberDiscord: (...args: unknown[]) => timeoutMemberDiscord(...args),
|
||||
unpinMessageDiscord: (...args: unknown[]) => unpinMessageDiscord(...args),
|
||||
}));
|
||||
|
||||
const enableAllActions = () => true;
|
||||
|
||||
const disabledActions = (key: keyof DiscordActionConfig) => key !== "reactions";
|
||||
const channelInfoEnabled = (key: keyof DiscordActionConfig) => key === "channelInfo";
|
||||
const moderationEnabled = (key: keyof DiscordActionConfig) => key === "moderation";
|
||||
|
||||
describe("handleDiscordMessagingAction", () => {
|
||||
it("adds reactions", async () => {
|
||||
@@ -81,6 +90,20 @@ describe("handleDiscordMessagingAction", () => {
|
||||
expect(reactMessageDiscord).toHaveBeenCalledWith("C1", "M1", "✅");
|
||||
});
|
||||
|
||||
it("forwards accountId for reactions", async () => {
|
||||
await handleDiscordMessagingAction(
|
||||
"react",
|
||||
{
|
||||
channelId: "C1",
|
||||
messageId: "M1",
|
||||
emoji: "✅",
|
||||
accountId: "ops",
|
||||
},
|
||||
enableAllActions,
|
||||
);
|
||||
expect(reactMessageDiscord).toHaveBeenCalledWith("C1", "M1", "✅", { accountId: "ops" });
|
||||
});
|
||||
|
||||
it("removes reactions on empty emoji", async () => {
|
||||
await handleDiscordMessagingAction(
|
||||
"react",
|
||||
@@ -245,6 +268,15 @@ describe("handleDiscordGuildAction - channel management", () => {
|
||||
).rejects.toThrow(/Discord channel management is disabled/);
|
||||
});
|
||||
|
||||
it("forwards accountId for channelList", async () => {
|
||||
await handleDiscordGuildAction(
|
||||
"channelList",
|
||||
{ guildId: "G1", accountId: "ops" },
|
||||
channelInfoEnabled,
|
||||
);
|
||||
expect(listGuildChannelsDiscord).toHaveBeenCalledWith("G1", { accountId: "ops" });
|
||||
});
|
||||
|
||||
it("edits a channel", async () => {
|
||||
await handleDiscordGuildAction(
|
||||
"channelEdit",
|
||||
@@ -448,3 +480,26 @@ describe("handleDiscordGuildAction - channel management", () => {
|
||||
expect(removeChannelPermissionDiscord).toHaveBeenCalledWith("C1", "R1");
|
||||
});
|
||||
});
|
||||
|
||||
describe("handleDiscordModerationAction", () => {
|
||||
it("forwards accountId for timeout", async () => {
|
||||
await handleDiscordModerationAction(
|
||||
"timeout",
|
||||
{
|
||||
guildId: "G1",
|
||||
userId: "U1",
|
||||
durationMinutes: 5,
|
||||
accountId: "ops",
|
||||
},
|
||||
moderationEnabled,
|
||||
);
|
||||
expect(timeoutMemberDiscord).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
guildId: "G1",
|
||||
userId: "U1",
|
||||
durationMinutes: 5,
|
||||
}),
|
||||
{ accountId: "ops" },
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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 }),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
import type { ClawdbotConfig } from "../../../config/config.js";
|
||||
type SendMessageDiscord = typeof import("../../../discord/send.js").sendMessageDiscord;
|
||||
@@ -32,6 +32,32 @@ const loadDiscordMessageActions = async () => {
|
||||
return mod.discordMessageActions;
|
||||
};
|
||||
|
||||
type SendMessageDiscord = typeof import("../../../discord/send.js").sendMessageDiscord;
|
||||
type SendPollDiscord = typeof import("../../../discord/send.js").sendPollDiscord;
|
||||
|
||||
const sendMessageDiscord = vi.fn<Parameters<SendMessageDiscord>, ReturnType<SendMessageDiscord>>(
|
||||
async () => ({ ok: true }) as Awaited<ReturnType<SendMessageDiscord>>,
|
||||
);
|
||||
const sendPollDiscord = vi.fn<Parameters<SendPollDiscord>, ReturnType<SendPollDiscord>>(
|
||||
async () => ({ ok: true }) as Awaited<ReturnType<SendPollDiscord>>,
|
||||
);
|
||||
|
||||
vi.mock("../../../discord/send.js", async () => {
|
||||
const actual = await vi.importActual<typeof import("../../../discord/send.js")>(
|
||||
"../../../discord/send.js",
|
||||
);
|
||||
return {
|
||||
...actual,
|
||||
sendMessageDiscord: (...args: Parameters<SendMessageDiscord>) => sendMessageDiscord(...args),
|
||||
sendPollDiscord: (...args: Parameters<SendPollDiscord>) => sendPollDiscord(...args),
|
||||
};
|
||||
});
|
||||
|
||||
const loadHandleDiscordMessageAction = async () => {
|
||||
const mod = await import("./discord/handle-action.js");
|
||||
return mod.handleDiscordMessageAction;
|
||||
};
|
||||
|
||||
describe("discord message actions", () => {
|
||||
it("lists channel and upload actions by default", async () => {
|
||||
const cfg = { channels: { discord: { token: "d0" } } } as ClawdbotConfig;
|
||||
@@ -53,3 +79,78 @@ describe("discord message actions", () => {
|
||||
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",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("forwards accountId for thread replies", async () => {
|
||||
sendMessageDiscord.mockClear();
|
||||
const handleDiscordMessageAction = await loadHandleDiscordMessageAction();
|
||||
|
||||
await handleDiscordMessageAction({
|
||||
action: "thread-reply",
|
||||
params: {
|
||||
channelId: "123",
|
||||
message: "hi",
|
||||
},
|
||||
cfg: {} as ClawdbotConfig,
|
||||
accountId: "ops",
|
||||
});
|
||||
|
||||
expect(sendMessageDiscord).toHaveBeenCalledWith(
|
||||
"channel:123",
|
||||
"hi",
|
||||
expect.objectContaining({
|
||||
accountId: "ops",
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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 });
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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,37 @@ 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");
|
||||
|
||||
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: accountId ?? undefined, 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: accountId ?? undefined, 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: accountId ?? undefined, guildId },
|
||||
cfg,
|
||||
);
|
||||
}
|
||||
|
||||
if (action === "emoji-upload") {
|
||||
@@ -50,7 +60,14 @@ export async function tryHandleDiscordMessageActionGuildAdmin(params: {
|
||||
});
|
||||
const roleIds = readStringArrayParam(actionParams, "roleIds");
|
||||
return await handleDiscordAction(
|
||||
{ action: "emojiUpload", guildId, name, mediaUrl, roleIds },
|
||||
{
|
||||
action: "emojiUpload",
|
||||
accountId: accountId ?? undefined,
|
||||
guildId,
|
||||
name,
|
||||
mediaUrl,
|
||||
roleIds,
|
||||
},
|
||||
cfg,
|
||||
);
|
||||
}
|
||||
@@ -73,7 +90,15 @@ export async function tryHandleDiscordMessageActionGuildAdmin(params: {
|
||||
trim: false,
|
||||
});
|
||||
return await handleDiscordAction(
|
||||
{ action: "stickerUpload", guildId, name, description, tags, mediaUrl },
|
||||
{
|
||||
action: "stickerUpload",
|
||||
accountId: accountId ?? undefined,
|
||||
guildId,
|
||||
name,
|
||||
description,
|
||||
tags,
|
||||
mediaUrl,
|
||||
},
|
||||
cfg,
|
||||
);
|
||||
}
|
||||
@@ -87,6 +112,7 @@ export async function tryHandleDiscordMessageActionGuildAdmin(params: {
|
||||
return await handleDiscordAction(
|
||||
{
|
||||
action: action === "role-add" ? "roleAdd" : "roleRemove",
|
||||
accountId: accountId ?? undefined,
|
||||
guildId,
|
||||
userId,
|
||||
roleId,
|
||||
@@ -99,14 +125,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: accountId ?? undefined, 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: accountId ?? undefined, guildId },
|
||||
cfg,
|
||||
);
|
||||
}
|
||||
|
||||
if (action === "channel-create") {
|
||||
@@ -124,6 +156,7 @@ export async function tryHandleDiscordMessageActionGuildAdmin(params: {
|
||||
return await handleDiscordAction(
|
||||
{
|
||||
action: "channelCreate",
|
||||
accountId: accountId ?? undefined,
|
||||
guildId,
|
||||
name,
|
||||
type: type ?? undefined,
|
||||
@@ -153,6 +186,7 @@ export async function tryHandleDiscordMessageActionGuildAdmin(params: {
|
||||
return await handleDiscordAction(
|
||||
{
|
||||
action: "channelEdit",
|
||||
accountId: accountId ?? undefined,
|
||||
channelId,
|
||||
name: name ?? undefined,
|
||||
topic: topic ?? undefined,
|
||||
@@ -169,7 +203,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: accountId ?? undefined, channelId },
|
||||
cfg,
|
||||
);
|
||||
}
|
||||
|
||||
if (action === "channel-move") {
|
||||
@@ -186,6 +223,7 @@ export async function tryHandleDiscordMessageActionGuildAdmin(params: {
|
||||
return await handleDiscordAction(
|
||||
{
|
||||
action: "channelMove",
|
||||
accountId: accountId ?? undefined,
|
||||
guildId,
|
||||
channelId,
|
||||
parentId: parentId === undefined ? undefined : parentId,
|
||||
@@ -206,6 +244,7 @@ export async function tryHandleDiscordMessageActionGuildAdmin(params: {
|
||||
return await handleDiscordAction(
|
||||
{
|
||||
action: "categoryCreate",
|
||||
accountId: accountId ?? undefined,
|
||||
guildId,
|
||||
name,
|
||||
position: position ?? undefined,
|
||||
@@ -225,6 +264,7 @@ export async function tryHandleDiscordMessageActionGuildAdmin(params: {
|
||||
return await handleDiscordAction(
|
||||
{
|
||||
action: "categoryEdit",
|
||||
accountId: accountId ?? undefined,
|
||||
categoryId,
|
||||
name: name ?? undefined,
|
||||
position: position ?? undefined,
|
||||
@@ -237,7 +277,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: accountId ?? undefined, categoryId },
|
||||
cfg,
|
||||
);
|
||||
}
|
||||
|
||||
if (action === "voice-status") {
|
||||
@@ -245,14 +288,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: accountId ?? undefined, 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: accountId ?? undefined, guildId },
|
||||
cfg,
|
||||
);
|
||||
}
|
||||
|
||||
if (action === "event-create") {
|
||||
@@ -271,6 +320,7 @@ export async function tryHandleDiscordMessageActionGuildAdmin(params: {
|
||||
return await handleDiscordAction(
|
||||
{
|
||||
action: "eventCreate",
|
||||
accountId: accountId ?? undefined,
|
||||
guildId,
|
||||
name,
|
||||
startTime,
|
||||
@@ -301,6 +351,7 @@ export async function tryHandleDiscordMessageActionGuildAdmin(params: {
|
||||
return await handleDiscordAction(
|
||||
{
|
||||
action: discordAction,
|
||||
accountId: accountId ?? undefined,
|
||||
guildId,
|
||||
userId,
|
||||
durationMinutes,
|
||||
@@ -325,6 +376,7 @@ export async function tryHandleDiscordMessageActionGuildAdmin(params: {
|
||||
return await handleDiscordAction(
|
||||
{
|
||||
action: "threadList",
|
||||
accountId: accountId ?? undefined,
|
||||
guildId,
|
||||
channelId,
|
||||
includeArchived,
|
||||
@@ -344,6 +396,7 @@ export async function tryHandleDiscordMessageActionGuildAdmin(params: {
|
||||
return await handleDiscordAction(
|
||||
{
|
||||
action: "threadReply",
|
||||
accountId: accountId ?? undefined,
|
||||
channelId: resolveChannelId(),
|
||||
content,
|
||||
mediaUrl: mediaUrl ?? undefined,
|
||||
@@ -361,6 +414,7 @@ export async function tryHandleDiscordMessageActionGuildAdmin(params: {
|
||||
return await handleDiscordAction(
|
||||
{
|
||||
action: "searchMessages",
|
||||
accountId: accountId ?? undefined,
|
||||
guildId,
|
||||
content: query,
|
||||
channelId: readStringParam(actionParams, "channelId"),
|
||||
|
||||
@@ -18,9 +18,10 @@ function readParentIdParam(params: Record<string, unknown>): string | null | und
|
||||
}
|
||||
|
||||
export async function handleDiscordMessageAction(
|
||||
ctx: Pick<ChannelMessageActionContext, "action" | "params" | "cfg">,
|
||||
ctx: Pick<ChannelMessageActionContext, "action" | "params" | "cfg" | "accountId">,
|
||||
): Promise<AgentToolResult<unknown>> {
|
||||
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,
|
||||
@@ -80,6 +83,7 @@ export async function handleDiscordMessageAction(
|
||||
return await handleDiscordAction(
|
||||
{
|
||||
action: "react",
|
||||
accountId: accountId ?? undefined,
|
||||
channelId: resolveChannelId(),
|
||||
messageId,
|
||||
emoji,
|
||||
@@ -93,7 +97,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: accountId ?? undefined,
|
||||
channelId: resolveChannelId(),
|
||||
messageId,
|
||||
limit,
|
||||
},
|
||||
cfg,
|
||||
);
|
||||
}
|
||||
@@ -103,6 +113,7 @@ export async function handleDiscordMessageAction(
|
||||
return await handleDiscordAction(
|
||||
{
|
||||
action: "readMessages",
|
||||
accountId: accountId ?? undefined,
|
||||
channelId: resolveChannelId(),
|
||||
limit,
|
||||
before: readStringParam(params, "before"),
|
||||
@@ -119,6 +130,7 @@ export async function handleDiscordMessageAction(
|
||||
return await handleDiscordAction(
|
||||
{
|
||||
action: "editMessage",
|
||||
accountId: accountId ?? undefined,
|
||||
channelId: resolveChannelId(),
|
||||
messageId,
|
||||
content,
|
||||
@@ -130,7 +142,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: accountId ?? undefined,
|
||||
channelId: resolveChannelId(),
|
||||
messageId,
|
||||
},
|
||||
cfg,
|
||||
);
|
||||
}
|
||||
@@ -141,6 +158,7 @@ export async function handleDiscordMessageAction(
|
||||
return await handleDiscordAction(
|
||||
{
|
||||
action: action === "pin" ? "pinMessage" : action === "unpin" ? "unpinMessage" : "listPins",
|
||||
accountId: accountId ?? undefined,
|
||||
channelId: resolveChannelId(),
|
||||
messageId,
|
||||
},
|
||||
@@ -149,7 +167,14 @@ export async function handleDiscordMessageAction(
|
||||
}
|
||||
|
||||
if (action === "permissions") {
|
||||
return await handleDiscordAction({ action: "permissions", channelId: resolveChannelId() }, cfg);
|
||||
return await handleDiscordAction(
|
||||
{
|
||||
action: "permissions",
|
||||
accountId: accountId ?? undefined,
|
||||
channelId: resolveChannelId(),
|
||||
},
|
||||
cfg,
|
||||
);
|
||||
}
|
||||
|
||||
if (action === "thread-create") {
|
||||
@@ -161,6 +186,7 @@ export async function handleDiscordMessageAction(
|
||||
return await handleDiscordAction(
|
||||
{
|
||||
action: "threadCreate",
|
||||
accountId: accountId ?? undefined,
|
||||
channelId: resolveChannelId(),
|
||||
name,
|
||||
messageId,
|
||||
@@ -179,6 +205,7 @@ export async function handleDiscordMessageAction(
|
||||
return await handleDiscordAction(
|
||||
{
|
||||
action: "sticker",
|
||||
accountId: accountId ?? undefined,
|
||||
to: readStringParam(params, "to", { required: true }),
|
||||
stickerIds,
|
||||
content: readStringParam(params, "message"),
|
||||
|
||||
@@ -299,6 +299,7 @@ export async function runCronIsolatedAgentTurn(params: {
|
||||
sessionId: cronSession.sessionEntry.sessionId,
|
||||
sessionKey: agentSessionKey,
|
||||
messageChannel,
|
||||
agentAccountId: resolvedDelivery.accountId,
|
||||
sessionFile,
|
||||
workspaceDir,
|
||||
config: cfgWithAgentDefaults,
|
||||
|
||||
@@ -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<string, unknown>;
|
||||
};
|
||||
expect(ctx.accountId).toBe("ops");
|
||||
expect(ctx.params.accountId).toBe("ops");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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({
|
||||
|
||||
Reference in New Issue
Block a user