This commit is contained in:
Rodrigo Gomes da Silva 2026-01-30 13:47:39 -03:00 committed by GitHub
commit 960c321094
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 45 additions and 15 deletions

View File

@ -200,7 +200,10 @@ The wizard uses it to set your **allowlist/owner** so your own DMs are permitted
- Activation modes:
- `mention` (default): requires @mention or regex match.
- `always`: always triggers.
- `/activation mention|always` is owner-only and must be sent as a standalone message.
- `replies`: only triggers when someone replies to a bot message.
- `mention+replies`: triggers on @mention/regex match OR replies to bot messages.
- `never`: never triggers (except for control commands from owners).
- `/activation mention|always|replies|mention+replies|never` is owner-only and must be sent as a standalone message.
- Owner = `channels.whatsapp.allowFrom` (or self E.164 if unset).
- **History injection** (pending-only):
- Recent *unprocessed* messages (default 50) inserted under:

View File

@ -21,7 +21,10 @@ export function getAvailableCommands(): AvailableCommand[] {
{ name: "dock-telegram", description: "Route replies to Telegram." },
{ name: "dock-discord", description: "Route replies to Discord." },
{ name: "dock-slack", description: "Route replies to Slack." },
{ name: "activation", description: "Set group activation (mention|always)." },
{
name: "activation",
description: "Set group activation (mention|always|replies|mention+replies|never).",
},
{ name: "send", description: "Set send mode (on|off|inherit)." },
{ name: "reset", description: "Reset the session (/new)." },
{ name: "new", description: "Reset the session (/reset)." },

View File

@ -1,11 +1,14 @@
import { normalizeCommandBody } from "./commands-registry.js";
export type GroupActivationMode = "mention" | "always";
export type GroupActivationMode = "mention" | "always" | "replies" | "mention+replies" | "never";
export function normalizeGroupActivation(raw?: string | null): GroupActivationMode | undefined {
const value = raw?.trim().toLowerCase();
if (value === "mention") return "mention";
if (value === "always") return "always";
if (value === "replies") return "replies";
if (value === "mention+replies") return "mention+replies";
if (value === "never") return "never";
return undefined;
}

View File

@ -66,7 +66,7 @@ export const handleActivationCommand: CommandHandler = async (params, allowTextC
if (!activationCommand.mode) {
return {
shouldContinue: false,
reply: { text: "⚙️ Usage: /activation mention|always" },
reply: { text: "⚙️ Usage: /activation mention|always|replies|mention+replies|never" },
};
}
if (params.sessionEntry && params.sessionStore && params.sessionKey) {

View File

@ -59,7 +59,7 @@ type StatusArgs = {
sessionEntry?: SessionEntry;
sessionKey?: string;
sessionScope?: SessionScope;
groupActivation?: "mention" | "always";
groupActivation?: "mention" | "always" | "replies" | "mention+replies" | "never";
resolvedThink?: ThinkLevel;
resolvedVerbose?: VerboseLevel;
resolvedReasoning?: ReasoningLevel;

View File

@ -54,7 +54,7 @@ export type SessionEntry = {
authProfileOverride?: string;
authProfileOverrideSource?: "auto" | "user";
authProfileOverrideCompactionCount?: number;
groupActivation?: "mention" | "always";
groupActivation?: "mention" | "always" | "replies" | "mention+replies" | "never";
groupActivationNeedsSystemIntro?: boolean;
sendPolicy?: "allow" | "deny";
queueMode?:

View File

@ -303,7 +303,9 @@ export async function applySessionsPatchToStore(params: {
} else if (raw !== undefined) {
const normalized = normalizeGroupActivation(String(raw));
if (!normalized) {
return invalid('invalid groupActivation (use "mention"|"always")');
return invalid(
'invalid groupActivation (use "mention"|"always"|"replies"|"mention+replies"|"never")',
);
}
next.groupActivation = normalized;
}

View File

@ -150,7 +150,7 @@ export function helpText(options: SlashCommandOptions = {}): string {
"/usage <off|tokens|full>",
"/elevated <on|off|ask|full>",
"/elev <on|off|ask|full>",
"/activation <mention|always>",
"/activation <mention|always|replies|mention+replies|never>",
"/new or /reset",
"/abort",
"/settings",

View File

@ -391,7 +391,7 @@ export function createCommandHandlers(context: CommandHandlerContext) {
break;
case "activation":
if (!args) {
chatLog.addSystem("usage: /activation <mention|always>");
chatLog.addSystem("usage: /activation <mention|always|replies|mention+replies|never>");
break;
}
try {

View File

@ -101,28 +101,47 @@ export function applyGroupGating(params: {
sessionKey: params.sessionKey,
conversationId: params.conversationId,
});
const requireMention = activation !== "always";
// Check if this message is a reply to the bot
const selfJid = params.msg.selfJid?.replace(/:\\d+/, "");
const replySenderJid = params.msg.replyToSenderJid?.replace(/:\\d+/, "");
const selfE164 = params.msg.selfE164 ? normalizeE164(params.msg.selfE164) : null;
const replySenderE164 = params.msg.replyToSenderE164
? normalizeE164(params.msg.replyToSenderE164)
: null;
const implicitMention = Boolean(
const isReplyToBot = Boolean(
(selfJid && replySenderJid && selfJid === replySenderJid) ||
(selfE164 && replySenderE164 && selfE164 === replySenderE164),
);
// Ensure safe default for shouldBypassMention in case it's undefined in some contexts
const safeShouldBypassMention = typeof shouldBypassMention !== "undefined" ? shouldBypassMention : false;
// Determine if we should process based on activation mode
const shouldProcess = (() => {
if (activation === "always") return true;
if (activation === "never") return safeShouldBypassMention;
if (activation === "replies") return isReplyToBot || safeShouldBypassMention;
if (activation === "mention+replies")
return wasMentioned || isReplyToBot || safeShouldBypassMention;
// Default to "mention" mode
return wasMentioned || safeShouldBypassMention;
})();
// require mention only in strict 'mention' mode
const requireMention = activation === "mention";
const mentionGate = resolveMentionGating({
requireMention,
canDetectMention: true,
wasMentioned,
implicitMention,
shouldBypassMention,
implicitMention: isReplyToBot, // treat reply-to-bot as an implicit mention
shouldBypassMention: safeShouldBypassMention,
});
params.msg.wasMentioned = mentionGate.effectiveWasMentioned;
if (!shouldBypassMention && requireMention && mentionGate.shouldSkip) {
if (!shouldProcess) {
params.logVerbose(
`Group message stored for context (no mention detected) in ${params.conversationId}: ${params.msg.body}`,
`Group message stored for context (no activation pass) in ${params.conversationId}: ${params.msg.body}`,
);
const sender =
params.msg.senderName && params.msg.senderE164