diff --git a/src/agents/tools/discord-actions-messaging.ts b/src/agents/tools/discord-actions-messaging.ts index 3235ed2fba2..9d0b3818334 100644 --- a/src/agents/tools/discord-actions-messaging.ts +++ b/src/agents/tools/discord-actions-messaging.ts @@ -363,13 +363,17 @@ export async function handleDiscordMessagingAction( typeof autoArchiveMinutesRaw === "number" && Number.isFinite(autoArchiveMinutesRaw) ? autoArchiveMinutesRaw : undefined; + const appliedTags = readStringArrayParam(params, "appliedTags"); + const payload = { + name, + messageId, + autoArchiveMinutes, + content, + appliedTags: appliedTags ?? undefined, + }; const thread = accountId - ? await createThreadDiscord( - channelId, - { name, messageId, autoArchiveMinutes, content }, - { accountId }, - ) - : await createThreadDiscord(channelId, { name, messageId, autoArchiveMinutes, content }); + ? await createThreadDiscord(channelId, payload, { accountId }) + : await createThreadDiscord(channelId, payload); return jsonResult({ ok: true, thread }); } case "threadList": { diff --git a/src/agents/tools/message-tool.ts b/src/agents/tools/message-tool.ts index 6b7ddf2b77f..4e8d4a2efe3 100644 --- a/src/agents/tools/message-tool.ts +++ b/src/agents/tools/message-tool.ts @@ -312,6 +312,7 @@ function buildThreadSchema() { return { threadName: Type.Optional(Type.String()), autoArchiveMin: Type.Optional(Type.Number()), + appliedTags: Type.Optional(Type.Array(Type.String())), }; } diff --git a/src/channels/plugins/actions/discord/handle-action.ts b/src/channels/plugins/actions/discord/handle-action.ts index 97fd23a0de8..4c868c71efb 100644 --- a/src/channels/plugins/actions/discord/handle-action.ts +++ b/src/channels/plugins/actions/discord/handle-action.ts @@ -230,6 +230,7 @@ export async function handleDiscordMessageAction( const autoArchiveMinutes = readNumberParam(params, "autoArchiveMin", { integer: true, }); + const appliedTags = readStringArrayParam(params, "appliedTags"); return await handleDiscordAction( { action: "threadCreate", @@ -239,6 +240,7 @@ export async function handleDiscordMessageAction( messageId, content, autoArchiveMinutes, + appliedTags: appliedTags ?? undefined, }, cfg, actionOptions, diff --git a/src/discord/send.creates-thread.test.ts b/src/discord/send.creates-thread.test.ts index 957b709937b..3fd70b99882 100644 --- a/src/discord/send.creates-thread.test.ts +++ b/src/discord/send.creates-thread.test.ts @@ -76,6 +76,44 @@ describe("sendMessageDiscord", () => { ); }); + it("passes applied_tags for forum threads", async () => { + const { rest, getMock, postMock } = makeDiscordRest(); + getMock.mockResolvedValue({ type: ChannelType.GuildForum }); + postMock.mockResolvedValue({ id: "t1" }); + await createThreadDiscord( + "chan1", + { name: "tagged post", appliedTags: ["tag1", "tag2"] }, + { rest, token: "t" }, + ); + expect(postMock).toHaveBeenCalledWith( + Routes.threads("chan1"), + expect.objectContaining({ + body: { + name: "tagged post", + message: { content: "tagged post" }, + applied_tags: ["tag1", "tag2"], + }, + }), + ); + }); + + it("omits applied_tags for non-forum threads", async () => { + const { rest, getMock, postMock } = makeDiscordRest(); + getMock.mockResolvedValue({ type: ChannelType.GuildText }); + postMock.mockResolvedValue({ id: "t1" }); + await createThreadDiscord( + "chan1", + { name: "thread", appliedTags: ["tag1"] }, + { rest, token: "t" }, + ); + expect(postMock).toHaveBeenCalledWith( + Routes.threads("chan1"), + expect.objectContaining({ + body: expect.not.objectContaining({ applied_tags: expect.anything() }), + }), + ); + }); + it("falls back when channel lookup is unavailable", async () => { const { rest, getMock, postMock } = makeDiscordRest(); getMock.mockRejectedValue(new Error("lookup failed")); diff --git a/src/discord/send.messages.ts b/src/discord/send.messages.ts index ae661c027a7..54484def68f 100644 --- a/src/discord/send.messages.ts +++ b/src/discord/send.messages.ts @@ -124,6 +124,9 @@ export async function createThreadDiscord( if (isForumLike) { const starterContent = payload.content?.trim() ? payload.content : payload.name; body.message = { content: starterContent }; + if (payload.appliedTags?.length) { + body.applied_tags = payload.appliedTags; + } } // When creating a standalone thread (no messageId) in a non-forum channel, // default to public thread (type 11). Discord defaults to private (type 12) diff --git a/src/discord/send.types.ts b/src/discord/send.types.ts index a13f90b1e17..c69058f8687 100644 --- a/src/discord/send.types.ts +++ b/src/discord/send.types.ts @@ -74,6 +74,8 @@ export type DiscordThreadCreate = { content?: string; /** Discord thread type (default: PublicThread for standalone threads). */ type?: number; + /** Tag IDs to apply when creating a forum/media thread (Discord `applied_tags`). */ + appliedTags?: string[]; }; export type DiscordThreadList = {