diff --git a/docs/gateway/config-agents.md b/docs/gateway/config-agents.md index 046614bd6f0d..34a465b16d6e 100644 --- a/docs/gateway/config-agents.md +++ b/docs/gateway/config-agents.md @@ -1331,6 +1331,7 @@ Variables are case-insensitive. `{think}` is an alias for `{thinkingLevel}`. - Per-channel overrides: `channels..ackReaction`, `channels..accounts..ackReaction`. - Resolution order: account → channel → `messages.ackReaction` → identity fallback. - Scope: `group-mentions` (default), `group-all`, `direct`, `all`, or `off`/`none` (disables ack reactions entirely). +- `group-mentions` acks group messages that mention the agent, including in groups with `requireMention: false`. Use `group-all` to ack every group message. - `messages.statusReactions.enabled`: enables lifecycle status reactions on Slack, Discord, Signal, Telegram, and WhatsApp. On Discord, unset keeps status reactions enabled when ack reactions are active. On Slack, Signal, Telegram, and WhatsApp, set it explicitly to `true` to enable lifecycle status reactions. diff --git a/extensions/discord/src/monitor/message-handler.process-reactions.ts b/extensions/discord/src/monitor/message-handler.process-reactions.ts index 4676444d3b79..895e086ada6a 100644 --- a/extensions/discord/src/monitor/message-handler.process-reactions.ts +++ b/extensions/discord/src/monitor/message-handler.process-reactions.ts @@ -49,7 +49,6 @@ export function createDiscordMessageReactionRuntime(params: { isGuildMessage, isDirectMessage, isGroupDm, - shouldRequireMention, canDetectMention, effectiveWasMentioned, shouldBypassMention, @@ -67,7 +66,6 @@ export function createDiscordMessageReactionRuntime(params: { isDirect: isDirectMessage, isGroup: isGuildMessage || isGroupDm, isMentionableGroup: isGuildMessage, - requireMention: shouldRequireMention, canDetectMention, effectiveWasMentioned, shouldBypassMention, diff --git a/extensions/matrix/src/matrix/monitor/handler-context.ts b/extensions/matrix/src/matrix/monitor/handler-context.ts index acd9d5e29c56..cb489b96c3c5 100644 --- a/extensions/matrix/src/matrix/monitor/handler-context.ts +++ b/extensions/matrix/src/matrix/monitor/handler-context.ts @@ -334,7 +334,6 @@ export async function resolveMatrixInboundContext(config: { isDirect: isDirectMessage, isGroup: isRoom, isMentionableGroup: isRoom, - requireMention: shouldRequireMention, canDetectMention, effectiveWasMentioned, shouldBypassMention, diff --git a/extensions/signal/src/monitor/event-handler.ts b/extensions/signal/src/monitor/event-handler.ts index 9eb0a6e3473c..f4b5c76235c4 100644 --- a/extensions/signal/src/monitor/event-handler.ts +++ b/extensions/signal/src/monitor/event-handler.ts @@ -344,7 +344,6 @@ export function createSignalEventHandler(deps: SignalEventHandlerDeps) { isDirect: !entry.isGroup, isGroup: entry.isGroup, isMentionableGroup: entry.isGroup, - requireMention: entry.requireMention === true, canDetectMention: entry.canDetectMention === true, effectiveWasMentioned: entry.wasMentioned === true, }), diff --git a/extensions/slack/src/monitor/message-handler/prepare.ts b/extensions/slack/src/monitor/message-handler/prepare.ts index 946daaf1459d..434ff1f3d04a 100644 --- a/extensions/slack/src/monitor/message-handler/prepare.ts +++ b/extensions/slack/src/monitor/message-handler/prepare.ts @@ -1348,7 +1348,6 @@ export async function prepareSlackMessage(params: { isDirect: isDirectMessage, isGroup: isRoomish, isMentionableGroup: isRoom, - requireMention: shouldRequireMention, canDetectMention, effectiveWasMentioned, shouldBypassMention, diff --git a/extensions/telegram/src/bot-message-context.ts b/extensions/telegram/src/bot-message-context.ts index 3705613dc5f8..49e3ffe1f282 100644 --- a/extensions/telegram/src/bot-message-context.ts +++ b/extensions/telegram/src/bot-message-context.ts @@ -545,7 +545,6 @@ export const buildTelegramMessageContext = async ({ isDirect: !isGroup, isGroup, isMentionableGroup: isGroup, - requireMention: Boolean(requireMention), canDetectMention: bodyResult.canDetectMention, effectiveWasMentioned: bodyResult.effectiveWasMentioned, shouldBypassMention: bodyResult.shouldBypassMention, diff --git a/src/channels/ack-reactions.test.ts b/src/channels/ack-reactions.test.ts index 56001119944f..5e8f683ef77f 100644 --- a/src/channels/ack-reactions.test.ts +++ b/src/channels/ack-reactions.test.ts @@ -20,7 +20,6 @@ describe("shouldAckReaction", () => { isDirect: true, isGroup: false, isMentionableGroup: false, - requireMention: false, canDetectMention: false, effectiveWasMentioned: false, }), @@ -32,7 +31,6 @@ describe("shouldAckReaction", () => { isDirect: false, isGroup: true, isMentionableGroup: true, - requireMention: false, canDetectMention: false, effectiveWasMentioned: false, }), @@ -46,7 +44,6 @@ describe("shouldAckReaction", () => { isDirect: true, isGroup: true, isMentionableGroup: true, - requireMention: true, canDetectMention: true, effectiveWasMentioned: true, }), @@ -67,7 +64,6 @@ describe("shouldAckReaction", () => { isDirect: false, isGroup: true, isMentionableGroup: true, - requireMention: false, canDetectMention: true, effectiveWasMentioned: false, }), @@ -81,7 +77,6 @@ describe("shouldAckReaction", () => { isDirect: false, isGroup: true, isMentionableGroup: true, - requireMention: true, canDetectMention: true, effectiveWasMentioned: true, }), @@ -94,17 +89,13 @@ describe("shouldAckReaction", () => { isDirect: false, isGroup: true, isMentionableGroup: true, - requireMention: true, canDetectMention: true, effectiveWasMentioned: true, }; - expect( - shouldAckReaction({ - ...groupMentionsScope, - requireMention: false, - }), - ).toBe(false); + // A group that answers every message still acks the ones addressing the + // agent: whether the group requires a mention is a separate policy. + expect(shouldAckReaction(groupMentionsScope)).toBe(true); expect( shouldAckReaction({ @@ -137,6 +128,32 @@ describe("shouldAckReaction", () => { }); describe("shouldAckReactionForWhatsApp", () => { + it("acks a mention in a group that does not require mentions", () => { + // Regression: the gate used to return false whenever the group did not + // require mentions, so an explicitly mentioned message the agent answered + // got no ack under the default scope. + expect( + shouldAckReaction({ + scope: "group-mentions", + isDirect: false, + isGroup: true, + isMentionableGroup: true, + canDetectMention: true, + effectiveWasMentioned: true, + }), + ).toBe(true); + expect( + shouldAckReaction({ + scope: "group-mentions", + isDirect: false, + isGroup: true, + isMentionableGroup: true, + canDetectMention: true, + effectiveWasMentioned: false, + }), + ).toBe(false); + }); + it("respects direct and group modes", () => { expect( shouldAckReactionForWhatsApp({ diff --git a/src/channels/ack-reactions.ts b/src/channels/ack-reactions.ts index a4ac7adf0b9e..7f5185650483 100644 --- a/src/channels/ack-reactions.ts +++ b/src/channels/ack-reactions.ts @@ -26,7 +26,6 @@ export type AckReactionGateParams = { isDirect: boolean; isGroup: boolean; isMentionableGroup: boolean; - requireMention: boolean; canDetectMention: boolean; effectiveWasMentioned: boolean; shouldBypassMention?: boolean; @@ -56,12 +55,11 @@ export function shouldAckReaction(params: AckReactionGateParams): boolean { if (!params.isMentionableGroup) { return false; } - if (!params.requireMention) { - return false; - } if (!params.canDetectMention) { return false; } + // Whether the group *requires* a mention is a separate policy: a group that + // answers everything still acks the messages that address the agent. // Group activation can stand in for a literal mention when another gate already established // that this inbound message belongs to the active conversation. return params.effectiveWasMentioned || params.shouldBypassMention === true; @@ -101,7 +99,6 @@ export function shouldAckReactionForWhatsApp(params: { isDirect: false, isGroup: true, isMentionableGroup: true, - requireMention: true, canDetectMention: true, effectiveWasMentioned: params.wasMentioned, shouldBypassMention: params.groupActivated, diff --git a/src/config/schema.help.automation.ts b/src/config/schema.help.automation.ts index f42c29ab7ddc..ad508dba645d 100644 --- a/src/config/schema.help.automation.ts +++ b/src/config/schema.help.automation.ts @@ -326,7 +326,7 @@ export const AUTOMATION_FIELD_HELP: Record = { "When true, suppress ⚠️ tool-error warnings from being shown to the user. The agent already sees errors in context and can retry. Default: false.", "messages.ackReaction": "Emoji reaction used to acknowledge inbound messages (empty disables).", "messages.ackReactionScope": - 'When to send ack reactions ("group-mentions", "group-all", "direct", "all", "off", "none"). "off"/"none" disables ack reactions entirely.', + 'When to send ack reactions ("group-mentions", "group-all", "direct", "all", "off", "none"). "group-mentions" acks group messages that mention the agent, whether or not the group requires mentions; "group-all" acks every group message. "off"/"none" disables ack reactions entirely.', "messages.statusReactions": "Lifecycle status reactions that update the emoji on the trigger message as the agent progresses (queued → thinking → tool → done/error).", "messages.statusReactions.enabled":