From d0efaceb97f3ba4b4dc37d23bd937c09845471e2 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 16 May 2026 14:34:32 +0100 Subject: [PATCH] fix: keep room events quiet across legacy helpers --- .../monitor/message-handler.process.test.ts | 36 +++++++++++++++++++ .../src/monitor/message-handler.process.ts | 3 ++ src/plugin-sdk/channel-inbound.test.ts | 1 + src/plugin-sdk/channel-inbound.ts | 10 ++++-- 4 files changed, 48 insertions(+), 2 deletions(-) diff --git a/extensions/discord/src/monitor/message-handler.process.test.ts b/extensions/discord/src/monitor/message-handler.process.test.ts index cb26045769c..02a1f2df9b0 100644 --- a/extensions/discord/src/monitor/message-handler.process.test.ts +++ b/extensions/discord/src/monitor/message-handler.process.test.ts @@ -1292,6 +1292,42 @@ describe("processDiscordMessage session routing", () => { expect(emojis).toContain(DEFAULT_EMOJIS.done); }); + it("suppresses Discord reactions for room events even when status reactions are explicit", async () => { + vi.useFakeTimers(); + dispatchInboundMessage.mockImplementationOnce(async (params?: DispatchInboundParams) => { + await params?.replyOptions?.onReasoningStream?.(); + await new Promise((resolve) => setTimeout(resolve, 1_000)); + return createNoQueuedDispatchResult(); + }); + const ctx = await createBaseContext({ + shouldRequireMention: false, + effectiveWasMentioned: false, + inboundEventKind: "room_event", + ackReactionScope: "all", + cfg: { + messages: { + ackReaction: "👀", + ackReactionScope: "all", + statusReactions: { + enabled: true, + timing: { debounceMs: 0 }, + }, + }, + session: { store: "/tmp/openclaw-discord-process-test-sessions.json" }, + }, + route: BASE_CHANNEL_ROUTE, + }); + + const runPromise = runProcessDiscordMessage(ctx); + await vi.advanceTimersByTimeAsync(1_000); + await vi.runAllTimersAsync(); + await runPromise; + + expect(getLastDispatchReplyOptions()?.sourceReplyDeliveryMode).toBe("message_tool_only"); + expect(getReactionEmojis()).toEqual([]); + expect(sendMocks.removeReactionDiscord).not.toHaveBeenCalled(); + }); + it("uses PluralKit original ids for inbound dedupe while preserving the Discord message id", async () => { const ctx = await createBaseContext({ canonicalMessageId: "orig-123", diff --git a/extensions/discord/src/monitor/message-handler.process.ts b/extensions/discord/src/monitor/message-handler.process.ts index fc37a5a61cb..24c37150ba3 100644 --- a/extensions/discord/src/monitor/message-handler.process.ts +++ b/extensions/discord/src/monitor/message-handler.process.ts @@ -210,8 +210,10 @@ export async function processDiscordMessage( }); const removeAckAfterReply = cfg.messages?.removeAckAfterReply ?? false; const mediaLocalRoots = getAgentScopedMediaLocalRoots(cfg, route.agentId); + const isRoomEvent = ctx.inboundEventKind === "room_event"; const shouldAckReaction = () => Boolean( + !isRoomEvent && ackReaction && shouldAckReactionGate({ scope: ackReactionScope, @@ -227,6 +229,7 @@ export async function processDiscordMessage( const shouldSendAckReaction = shouldAckReaction(); const statusReactionsExplicitlyEnabled = cfg.messages?.statusReactions?.enabled === true; const statusReactionsEnabled = + !isRoomEvent && shouldSendAckReaction && cfg.messages?.statusReactions?.enabled !== false && (!sourceRepliesAreToolOnly || statusReactionsExplicitlyEnabled); diff --git a/src/plugin-sdk/channel-inbound.test.ts b/src/plugin-sdk/channel-inbound.test.ts index 88ccf0ef1d1..bd02cb127fc 100644 --- a/src/plugin-sdk/channel-inbound.test.ts +++ b/src/plugin-sdk/channel-inbound.test.ts @@ -36,5 +36,6 @@ describe("channel-inbound public compatibility helpers", () => { const ctx = buildChannelTurnContext(createLegacyTurnParams()); expect(ctx.InboundEventKind).toBe("room_event"); + expect(ctx.InboundTurnKind).toBe("room_event"); }); }); diff --git a/src/plugin-sdk/channel-inbound.ts b/src/plugin-sdk/channel-inbound.ts index 83946b51026..f3ab2ff2c84 100644 --- a/src/plugin-sdk/channel-inbound.ts +++ b/src/plugin-sdk/channel-inbound.ts @@ -74,19 +74,25 @@ export type BuildChannelTurnContextParams = Omit< inboundTurnKind?: InboundEventKind; }; }; -export type BuiltChannelTurnContext = BuiltChannelInboundEventContext; +export type BuiltChannelTurnContext = BuiltChannelInboundEventContext & { + InboundTurnKind: InboundEventKind; +}; export function buildChannelTurnContext( params: BuildChannelTurnContextParams, ): BuiltChannelTurnContext { const inboundEventKind = params.message.inboundEventKind ?? params.message.inboundTurnKind; - return buildChannelInboundEventContext({ + const ctx = buildChannelInboundEventContext({ ...params, message: { ...params.message, ...(inboundEventKind ? { inboundEventKind } : {}), }, }); + return { + ...ctx, + InboundTurnKind: ctx.InboundEventKind, + }; } export const filterChannelTurnSupplementalContext = filterChannelInboundSupplementalContext;