diff --git a/extensions/discord/src/monitor/message-handler.process.test.ts b/extensions/discord/src/monitor/message-handler.process.test.ts index 88665fac845..89018c7dfcd 100644 --- a/extensions/discord/src/monitor/message-handler.process.test.ts +++ b/extensions/discord/src/monitor/message-handler.process.test.ts @@ -410,6 +410,30 @@ describe("processDiscordMessage ack reactions", () => { expect(emojis).toContain("🏁"); }); + it("falls back to plain ack when status reactions are disabled", async () => { + dispatchInboundMessage.mockImplementationOnce(async (params?: DispatchInboundParams) => { + await params?.replyOptions?.onReasoningStream?.(); + return createNoQueuedDispatchResult(); + }); + + const ctx = await createBaseContext({ + cfg: { + messages: { + ackReaction: "👀", + statusReactions: { + enabled: false, + timing: { debounceMs: 0 }, + }, + }, + session: { store: "/tmp/openclaw-discord-process-test-sessions.json" }, + }, + }); + + await runProcessDiscordMessage(ctx); + + expect(getReactionEmojis()).toEqual(["👀"]); + }); + it("shows compacting reaction during auto-compaction and resumes thinking", async () => { vi.useFakeTimers(); dispatchInboundMessage.mockImplementationOnce(async (params?: DispatchInboundParams) => { @@ -466,6 +490,26 @@ describe("processDiscordMessage ack reactions", () => { expect(sendMocks.removeReactionDiscord).toHaveBeenCalledWith("c1", "m1", "👀", { rest: {} }); }); + + it("removes the plain ack reaction when status reactions are disabled and removeAckAfterReply is enabled", async () => { + const ctx = await createBaseContext({ + cfg: { + messages: { + ackReaction: "👀", + removeAckAfterReply: true, + statusReactions: { + enabled: false, + }, + }, + session: { store: "/tmp/openclaw-discord-process-test-sessions.json" }, + }, + }); + + await runProcessDiscordMessage(ctx); + + expect(getReactionEmojis()).toEqual(["👀"]); + expect(sendMocks.removeReactionDiscord).toHaveBeenCalledWith("c1", "m1", "👀", { rest: {} }); + }); }); describe("processDiscordMessage session routing", () => { diff --git a/extensions/discord/src/monitor/message-handler.process.ts b/extensions/discord/src/monitor/message-handler.process.ts index 1c8687f2a77..dd095bc55bc 100644 --- a/extensions/discord/src/monitor/message-handler.process.ts +++ b/extensions/discord/src/monitor/message-handler.process.ts @@ -193,7 +193,9 @@ export async function processDiscordMessage( shouldBypassMention, }), ); - const statusReactionsEnabled = shouldAckReaction(); + const shouldSendAckReaction = shouldAckReaction(); + const statusReactionsEnabled = + shouldSendAckReaction && cfg.messages?.statusReactions?.enabled !== false; // Discord outbound helpers expect Carbon's request client shape explicitly. const discordRest = client.rest as unknown as RequestClient; const discordAdapter: StatusReactionAdapter = { @@ -225,6 +227,15 @@ export async function processDiscordMessage( }); if (statusReactionsEnabled) { void statusReactions.setQueued(); + } else if (shouldSendAckReaction && ackReaction) { + void discordAdapter.setReaction(ackReaction).catch((err) => { + logAckFailure({ + log: logVerbose, + channel: "discord", + target: `${messageChannelId}/${message.id}`, + error: err, + }); + }); } const { createReplyDispatcherWithTyping, dispatchInboundMessage } = await loadReplyRuntime(); @@ -931,6 +942,15 @@ export async function processDiscordMessage( void statusReactions.restoreInitial(); } } + } else if (shouldSendAckReaction && ackReaction && removeAckAfterReply) { + void discordAdapter.removeReaction(ackReaction).catch((err) => { + logAckFailure({ + log: logVerbose, + channel: "discord", + target: `${messageChannelId}/${message.id}`, + error: err, + }); + }); } } if (dispatchAborted) {