import { resolveAgentConfig } from "../../agents/agent-scope.js"; import type { ClawdbotConfig } from "../../config/config.js"; import type { MsgContext } from "../templating.js"; function escapeRegExp(text: string): string { return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); } function deriveMentionPatterns(identity?: { name?: string; emoji?: string }) { const patterns: string[] = []; const name = identity?.name?.trim(); if (name) { const parts = name.split(/\s+/).filter(Boolean).map(escapeRegExp); const re = parts.length ? parts.join(String.raw`\s+`) : escapeRegExp(name); patterns.push(String.raw`\b@?${re}\b`); } const emoji = identity?.emoji?.trim(); if (emoji) { patterns.push(escapeRegExp(emoji)); } return patterns; } const BACKSPACE_CHAR = "\u0008"; export const CURRENT_MESSAGE_MARKER = "[Current message - respond to this]"; function normalizeMentionPattern(pattern: string): string { if (!pattern.includes(BACKSPACE_CHAR)) return pattern; return pattern.split(BACKSPACE_CHAR).join("\\b"); } function normalizeMentionPatterns(patterns: string[]): string[] { return patterns.map(normalizeMentionPattern); } function resolveMentionPatterns( cfg: ClawdbotConfig | undefined, agentId?: string, ): string[] { if (!cfg) return []; const agentConfig = agentId ? resolveAgentConfig(cfg, agentId) : undefined; const agentGroupChat = agentConfig?.groupChat; if (agentGroupChat && Object.hasOwn(agentGroupChat, "mentionPatterns")) { return agentGroupChat.mentionPatterns ?? []; } const globalGroupChat = cfg.messages?.groupChat; if (globalGroupChat && Object.hasOwn(globalGroupChat, "mentionPatterns")) { return globalGroupChat.mentionPatterns ?? []; } const derived = deriveMentionPatterns(agentConfig?.identity); return derived.length > 0 ? derived : []; } export function buildMentionRegexes( cfg: ClawdbotConfig | undefined, agentId?: string, ): RegExp[] { const patterns = normalizeMentionPatterns( resolveMentionPatterns(cfg, agentId), ); return patterns .map((pattern) => { try { return new RegExp(pattern, "i"); } catch { return null; } }) .filter((value): value is RegExp => Boolean(value)); } export function normalizeMentionText(text: string): string { return (text ?? "") .replace(/[\u200b-\u200f\u202a-\u202e\u2060-\u206f]/g, "") .toLowerCase(); } export function matchesMentionPatterns( text: string, mentionRegexes: RegExp[], ): boolean { if (mentionRegexes.length === 0) return false; const cleaned = normalizeMentionText(text ?? ""); if (!cleaned) return false; return mentionRegexes.some((re) => re.test(cleaned)); } export function stripStructuralPrefixes(text: string): string { // Ignore wrapper labels, timestamps, and sender prefixes so directive-only // detection still works in group batches that include history/context. const afterMarker = text.includes(CURRENT_MESSAGE_MARKER) ? text .slice( text.indexOf(CURRENT_MESSAGE_MARKER) + CURRENT_MESSAGE_MARKER.length, ) .trimStart() : text; return afterMarker .replace(/\[[^\]]+\]\s*/g, "") .replace(/^[ \t]*[A-Za-z0-9+()\-_. ]+:\s*/gm, "") .replace(/\\n/g, " ") .replace(/\s+/g, " ") .trim(); } export function stripMentions( text: string, ctx: MsgContext, cfg: ClawdbotConfig | undefined, agentId?: string, ): string { let result = text; const rawPatterns = resolveMentionPatterns(cfg, agentId); const patterns = normalizeMentionPatterns(rawPatterns); for (const p of patterns) { try { const re = new RegExp(p, "gi"); result = result.replace(re, " "); } catch { // ignore invalid regex } } const selfE164 = (ctx.To ?? "").replace(/^whatsapp:/, ""); if (selfE164) { const esc = selfE164.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); result = result .replace(new RegExp(esc, "gi"), " ") .replace(new RegExp(`@${esc}`, "gi"), " "); } // Generic mention patterns like @123456789 or plain digits result = result.replace(/@[0-9+]{5,}/g, " "); // Discord-style mentions (<@123> or <@!123>) result = result.replace(/<@!?\d+>/g, " "); return result.replace(/\s+/g, " ").trim(); }