diff --git a/extensions/discord/src/monitor/agent-components.ts b/extensions/discord/src/monitor/agent-components.ts index 429b575b140..5516d7ba991 100644 --- a/extensions/discord/src/monitor/agent-components.ts +++ b/extensions/discord/src/monitor/agent-components.ts @@ -614,11 +614,15 @@ async function handleDiscordComponentEvent(params: { return; } } - const eventText = formatDiscordComponentEventText({ - kind: consumed.kind === "select" ? "select" : "button", - label: consumed.label, - values, - }); + // Preserve explicit callback payloads for the built-in fallback path so + // Discord behaves like Telegram when buttons carry synthetic command text. + const eventText = + consumed.callbackData?.trim() || + formatDiscordComponentEventText({ + kind: consumed.kind === "select" ? "select" : "button", + label: consumed.label, + values, + }); try { await params.interaction.reply({ content: "✓", ...replyOpts }); diff --git a/extensions/discord/src/monitor/monitor.test.ts b/extensions/discord/src/monitor/monitor.test.ts index 27e129b0bee..5f36aa23ded 100644 --- a/extensions/discord/src/monitor/monitor.test.ts +++ b/extensions/discord/src/monitor/monitor.test.ts @@ -517,6 +517,22 @@ describe("discord component interactions", () => { expect(resolveDiscordComponentEntry({ id: "btn_1" })).toBeNull(); }); + it("uses raw callbackData for built-in fallback when no plugin handler matches", async () => { + registerDiscordComponentEntries({ + entries: [createButtonEntry({ callbackData: "/codex_resume --browse-projects" })], + modals: [], + }); + + const button = createDiscordComponentButton(createComponentContext()); + const { interaction, reply } = createComponentButtonInteraction(); + + await button.run(interaction, { cid: "btn_1" } as ComponentData); + + expect(reply).toHaveBeenCalledWith({ content: "✓" }); + expect(lastDispatchCtx?.BodyForAgent).toBe("/codex_resume --browse-projects"); + expect(dispatchReplyMock).toHaveBeenCalledTimes(1); + }); + it("keeps reusable buttons active after use", async () => { registerDiscordComponentEntries({ entries: [createButtonEntry({ reusable: true })],