diff --git a/extensions/discord/src/activities/tool.test.ts b/extensions/discord/src/activities/tool.test.ts index 4af08cf5ed14..a47ea6fb9637 100644 --- a/extensions/discord/src/activities/tool.test.ts +++ b/extensions/discord/src/activities/tool.test.ts @@ -71,6 +71,36 @@ describe("discord_widget", () => { }); }); + it("resolves a provider-prefixed forum thread target", async () => { + const runtime = createActivityTestRuntime(); + const send = vi.fn(async (..._args: Parameters) => ({ + messageId: "message-1", + channelId: "987654321", + receipt: {}, + })); + const tool = createDiscordWidgetTool( + discordContext({ + nativeChannelId: undefined, + deliveryContext: { channel: "discord", to: "discord:channel:987654321" }, + }), + { + runtime, + sendMessage: send as unknown as typeof sendMessageDiscord, + }, + ); + if (!tool) { + throw new Error("expected Discord widget tool"); + } + + const result = await tool.execute("forum-widget", { + html: "

Forum widget

", + title: "Forum widget", + }); + + expect(result.details).toMatchObject({ channelId: "987654321" }); + expect(send).toHaveBeenCalledWith("channel:987654321", "Forum widget", expect.any(Object)); + }); + it("keeps full documents unchanged and rejects oversized HTML", async () => { const document = "full"; const runtime = createActivityTestRuntime(); @@ -139,4 +169,23 @@ describe("discord_widget", () => { tool.execute("missing-channel", { html: "hello", title: "No channel" }), ).rejects.toThrow("requires a concrete Discord channel"); }); + + it("rejects direct-message targets without a channel", async () => { + const tool = createDiscordWidgetTool( + discordContext({ + nativeChannelId: undefined, + deliveryContext: { channel: "discord", to: "discord:user:987654321" }, + }), + { + runtime: createActivityTestRuntime(), + sendMessage: vi.fn() as unknown as typeof sendMessageDiscord, + }, + ); + if (!tool) { + throw new Error("expected Discord widget tool"); + } + await expect(tool.execute("dm-target", { html: "hello", title: "No channel" })).rejects.toThrow( + "requires a concrete Discord channel", + ); + }); }); diff --git a/extensions/discord/src/activities/tool.ts b/extensions/discord/src/activities/tool.ts index f65e2da04d31..879d4f41559b 100644 --- a/extensions/discord/src/activities/tool.ts +++ b/extensions/discord/src/activities/tool.ts @@ -7,6 +7,7 @@ import { resolveDiscordAccount } from "../accounts.js"; import { buildDiscordActivityCustomId } from "../component-custom-id.js"; import { Button, Row } from "../internal/discord.js"; import { sendMessageDiscord } from "../send.js"; +import { resolveDiscordChannelId as resolveDiscordTargetChannelId } from "../target-parsing.js"; import type { DiscordActivitiesRuntime } from "./runtime.js"; const DISCORD_WIDGET_HTML_MAX_BYTES = 48 * 1024; @@ -47,8 +48,11 @@ function resolveDiscordChannelId(context: OpenClawPluginToolContext): string | u if (!raw) { return undefined; } - const normalized = raw.replace(/^channel:/i, ""); - return /^\d+$/.test(normalized) ? normalized : undefined; + try { + return resolveDiscordTargetChannelId(raw); + } catch { + return undefined; + } } function buildDiscordWidgetDocument(title: string, html: string): string {