"use strict";Object.defineProperty(exports, "__esModule", { value: true });exports.n = monitorWebInbox;exports.r = resolveWhatsAppHeartbeatRecipients;exports.t = monitorWebChannel;var _runWithConcurrency2ga3CMk = require("./run-with-concurrency-2ga3-CMk.js"); var _accountsBFBjizxh = require("./accounts-BFBjizxh.js"); var _dispatchF_Zbttj = require("./dispatch-F_Zbttj6.js"); var _configDiiPndBn = require("./config-DiiPndBn.js"); var _loggerU3s76KST = require("./logger-U3s76KST.js"); var _thinkingCfIPyoMg = require("./thinking-CfIPyoMg.js"); var _piEmbeddedHelpersDmr3bcbH = require("./pi-embedded-helpers-Dmr3bcbH.js"); var _sendDPZOoQ = require("./send-D-p-zOoQ.js"); var _pathsYc45qYMp = require("./paths-yc45qYMp.js"); var _channelActivityDH8wxjI = require("./channel-activity-DH-8wxjI.js"); var _localRootsZhwi3hFj = require("./local-roots-Zhwi3hFj.js"); var _irKp5uANes = require("./ir-Kp5uANes.js"); var _tablesDkBUhlLj = require("./tables-DkBUhlLj.js"); var _sendXbYokfdQ = require("./send-XbYokfdQ.js"); var _audioTranscriptionRunnerC72KQQVh = require("./audio-transcription-runner-C72KQQVh.js"); var _storeBfiJnRiX = require("./store-BfiJnRiX.js"); var _activeListenerCMb_0BEz = require("./active-listener-CMb_0BEz.js"); var _outboundDV7ZS1T = require("./outbound-DV7ZS1T6.js"); var _sessionDwLPNct = require("./session-DwL-pNct.js"); var _nodeCrypto = require("node:crypto"); var _baileys = require("@whiskeysockets/baileys"); //#endregion //#region src/channels/plugins/whatsapp-heartbeat.ts function getSessionRecipients(cfg) { if ((cfg.session?.scope ?? "per-sender") === "global") return []; const store = (0, _piEmbeddedHelpersDmr3bcbH.vt)((0, _pathsYc45qYMp.s)(cfg.session?.store)); const isGroupKey = (key) => key.includes(":group:") || key.includes(":channel:") || key.includes("@g.us"); const isCronKey = (key) => key.startsWith("cron:"); const recipients = Object.entries(store).filter(([key]) => key !== "global" && key !== "unknown").filter(([key]) => !isGroupKey(key) && !isCronKey(key)).map(([_, entry]) => ({ to: (0, _configDiiPndBn.Et)(entry?.lastChannel) === "whatsapp" && entry?.lastTo ? (0, _loggerU3s76KST.C)(entry.lastTo) : "", updatedAt: entry?.updatedAt ?? 0 })).filter(({ to }) => to.length > 1).toSorted((a, b) => b.updatedAt - a.updatedAt); const seen = /* @__PURE__ */new Set(); return recipients.filter((r) => { if (seen.has(r.to)) return false; seen.add(r.to); return true; }); } function resolveWhatsAppHeartbeatRecipients(cfg, opts = {}) { if (opts.to) return { recipients: [(0, _loggerU3s76KST.C)(opts.to)], source: "flag" }; const sessionRecipients = getSessionRecipients(cfg); const configuredAllowFrom = Array.isArray(cfg.channels?.whatsapp?.allowFrom) && cfg.channels.whatsapp.allowFrom.length > 0 ? cfg.channels.whatsapp.allowFrom.filter((v) => v !== "*").map(_loggerU3s76KST.C) : []; const storeAllowFrom = (0, _sendXbYokfdQ.et)("whatsapp", process.env, _runWithConcurrency2ga3CMk.lt).map(_loggerU3s76KST.C); const unique = (list) => [...new Set(list.filter(Boolean))]; const allowFrom = unique([...configuredAllowFrom, ...storeAllowFrom]); if (opts.all) return { recipients: unique([...sessionRecipients.map((s) => s.to), ...allowFrom]), source: "all" }; if (allowFrom.length > 0) { const allowSet = new Set(allowFrom); const authorizedSessionRecipients = sessionRecipients.map((entry) => entry.to).filter((recipient) => allowSet.has(recipient)); if (authorizedSessionRecipients.length === 1) return { recipients: [authorizedSessionRecipients[0]], source: "session-single" }; if (authorizedSessionRecipients.length > 1) return { recipients: authorizedSessionRecipients, source: "session-ambiguous" }; return { recipients: allowFrom, source: "allowFrom" }; } if (sessionRecipients.length === 1) return { recipients: [sessionRecipients[0].to], source: "session-single" }; if (sessionRecipients.length > 1) return { recipients: sessionRecipients.map((s) => s.to), source: "session-ambiguous" }; return { recipients: allowFrom, source: "allowFrom" }; } const DEFAULT_RECONNECT_POLICY = { initialMs: 2e3, maxMs: 3e4, factor: 1.8, jitter: .25, maxAttempts: 12 }; function resolveHeartbeatSeconds(cfg, overrideSeconds) { const candidate = overrideSeconds ?? cfg.web?.heartbeatSeconds; if (typeof candidate === "number" && candidate > 0) return candidate; return 60; } function resolveReconnectPolicy(cfg, overrides) { const reconnectOverrides = cfg.web?.reconnect ?? {}; const overrideConfig = overrides ?? {}; const merged = { ...DEFAULT_RECONNECT_POLICY, ...reconnectOverrides, ...overrideConfig }; merged.initialMs = Math.max(250, merged.initialMs); merged.maxMs = Math.max(merged.initialMs, merged.maxMs); merged.factor = (0, _loggerU3s76KST.m)(merged.factor, 1.1, 10); merged.jitter = (0, _loggerU3s76KST.m)(merged.jitter, 0, 1); merged.maxAttempts = Math.max(0, Math.floor(merged.maxAttempts)); return merged; } function newConnectionId() { return (0, _nodeCrypto.randomUUID)(); } //#endregion //#region src/web/auto-reply/loggers.ts const whatsappLog = (0, _loggerU3s76KST.a)("gateway/channels/whatsapp"); const whatsappInboundLog = whatsappLog.child("inbound"); const whatsappOutboundLog = whatsappLog.child("outbound"); const whatsappHeartbeatLog = whatsappLog.child("heartbeat"); //#endregion //#region src/cli/wait.ts function waitForever() { setInterval(() => {}, 1e6).unref(); return new Promise(() => {}); } //#endregion //#region src/web/inbound/dedupe.ts const recentInboundMessages = (0, _dispatchF_Zbttj.ot)({ ttlMs: 20 * 6e4, maxSize: 5e3 }); function isRecentInboundMessage(key) { return recentInboundMessages.check(key); } //#endregion //#region src/web/vcard.ts const ALLOWED_VCARD_KEYS = new Set([ "FN", "N", "TEL"] ); function parseVcard(vcard) { if (!vcard) return { phones: [] }; const lines = vcard.split(/\r?\n/); let nameFromN; let nameFromFn; const phones = []; for (const rawLine of lines) { const line = rawLine.trim(); if (!line) continue; const colonIndex = line.indexOf(":"); if (colonIndex === -1) continue; const key = line.slice(0, colonIndex).toUpperCase(); const rawValue = line.slice(colonIndex + 1).trim(); if (!rawValue) continue; const baseKey = normalizeVcardKey(key); if (!baseKey || !ALLOWED_VCARD_KEYS.has(baseKey)) continue; const value = cleanVcardValue(rawValue); if (!value) continue; if (baseKey === "FN" && !nameFromFn) { nameFromFn = normalizeVcardName(value); continue; } if (baseKey === "N" && !nameFromN) { nameFromN = normalizeVcardName(value); continue; } if (baseKey === "TEL") { const phone = normalizeVcardPhone(value); if (phone) phones.push(phone); } } return { name: nameFromFn ?? nameFromN, phones }; } function normalizeVcardKey(key) { const [primary] = key.split(";"); if (!primary) return; const segments = primary.split("."); return segments[segments.length - 1] || void 0; } function cleanVcardValue(value) { return value.replace(/\\n/gi, " ").replace(/\\,/g, ",").replace(/\\;/g, ";").trim(); } function normalizeVcardName(value) { return value.replace(/;/g, " ").replace(/\s+/g, " ").trim(); } function normalizeVcardPhone(value) { const trimmed = value.trim(); if (!trimmed) return ""; if (trimmed.toLowerCase().startsWith("tel:")) return trimmed.slice(4).trim(); return trimmed; } //#endregion //#region src/web/inbound/extract.ts function unwrapMessage$1(message) { return (0, _baileys.normalizeMessageContent)(message); } function extractContextInfo(message) { if (!message) return; const contentType = (0, _baileys.getContentType)(message); const candidate = contentType ? message[contentType] : void 0; const contextInfo = candidate && typeof candidate === "object" && "contextInfo" in candidate ? candidate.contextInfo : void 0; if (contextInfo) return contextInfo; const fallback = message.extendedTextMessage?.contextInfo ?? message.imageMessage?.contextInfo ?? message.videoMessage?.contextInfo ?? message.documentMessage?.contextInfo ?? message.audioMessage?.contextInfo ?? message.stickerMessage?.contextInfo ?? message.buttonsResponseMessage?.contextInfo ?? message.listResponseMessage?.contextInfo ?? message.templateButtonReplyMessage?.contextInfo ?? message.interactiveResponseMessage?.contextInfo ?? message.buttonsMessage?.contextInfo ?? message.listMessage?.contextInfo; if (fallback) return fallback; for (const value of Object.values(message)) { if (!value || typeof value !== "object") continue; if (!("contextInfo" in value)) continue; const candidateContext = value.contextInfo; if (candidateContext) return candidateContext; } } function extractMentionedJids(rawMessage) { const message = unwrapMessage$1(rawMessage); if (!message) return; const flattened = [ message.extendedTextMessage?.contextInfo?.mentionedJid, message.extendedTextMessage?.contextInfo?.quotedMessage?.extendedTextMessage?.contextInfo?.mentionedJid, message.imageMessage?.contextInfo?.mentionedJid, message.videoMessage?.contextInfo?.mentionedJid, message.documentMessage?.contextInfo?.mentionedJid, message.audioMessage?.contextInfo?.mentionedJid, message.stickerMessage?.contextInfo?.mentionedJid, message.buttonsResponseMessage?.contextInfo?.mentionedJid, message.listResponseMessage?.contextInfo?.mentionedJid]. flatMap((arr) => arr ?? []).filter(Boolean); if (flattened.length === 0) return; return Array.from(new Set(flattened)); } function extractText(rawMessage) { const message = unwrapMessage$1(rawMessage); if (!message) return; const extracted = (0, _baileys.extractMessageContent)(message); const candidates = [message, extracted && extracted !== message ? extracted : void 0]; for (const candidate of candidates) { if (!candidate) continue; if (typeof candidate.conversation === "string" && candidate.conversation.trim()) return candidate.conversation.trim(); const extended = candidate.extendedTextMessage?.text; if (extended?.trim()) return extended.trim(); const caption = candidate.imageMessage?.caption ?? candidate.videoMessage?.caption ?? candidate.documentMessage?.caption; if (caption?.trim()) return caption.trim(); } const contactPlaceholder = extractContactPlaceholder(message) ?? (extracted && extracted !== message ? extractContactPlaceholder(extracted) : void 0); if (contactPlaceholder) return contactPlaceholder; } function extractMediaPlaceholder(rawMessage) { const message = unwrapMessage$1(rawMessage); if (!message) return; if (message.imageMessage) return ""; if (message.videoMessage) return ""; if (message.audioMessage) return ""; if (message.documentMessage) return ""; if (message.stickerMessage) return ""; } function extractContactPlaceholder(rawMessage) { const message = unwrapMessage$1(rawMessage); if (!message) return; const contact = message.contactMessage ?? void 0; if (contact) { const { name, phones } = describeContact({ displayName: contact.displayName, vcard: contact.vcard }); return formatContactPlaceholder(name, phones); } const contactsArray = message.contactsArrayMessage?.contacts ?? void 0; if (!contactsArray || contactsArray.length === 0) return; return formatContactsPlaceholder(contactsArray.map((entry) => describeContact({ displayName: entry.displayName, vcard: entry.vcard })).map((entry) => formatContactLabel(entry.name, entry.phones)).filter((value) => Boolean(value)), contactsArray.length); } function describeContact(input) { const displayName = (input.displayName ?? "").trim(); const parsed = parseVcard(input.vcard ?? void 0); return { name: displayName || parsed.name, phones: parsed.phones }; } function formatContactPlaceholder(name, phones) { const label = formatContactLabel(name, phones); if (!label) return ""; return ``; } function formatContactsPlaceholder(labels, total) { const cleaned = labels.map((label) => label.trim()).filter(Boolean); if (cleaned.length === 0) return ``; const remaining = Math.max(total - cleaned.length, 0); const suffix = remaining > 0 ? ` +${remaining} more` : ""; return ``; } function formatContactLabel(name, phones) { const parts = [name, formatPhoneList(phones)].filter((value) => Boolean(value)); if (parts.length === 0) return; return parts.join(", "); } function formatPhoneList(phones) { const cleaned = phones?.map((phone) => phone.trim()).filter(Boolean) ?? []; if (cleaned.length === 0) return; const { shown, remaining } = summarizeList(cleaned, cleaned.length, 1); const [primary] = shown; if (!primary) return; if (remaining === 0) return primary; return `${primary} (+${remaining} more)`; } function summarizeList(values, total, maxShown) { const shown = values.slice(0, maxShown); return { shown, remaining: Math.max(total - shown.length, 0) }; } function extractLocationData(rawMessage) { const message = unwrapMessage$1(rawMessage); if (!message) return null; const live = message.liveLocationMessage ?? void 0; if (live) { const latitudeRaw = live.degreesLatitude; const longitudeRaw = live.degreesLongitude; if (latitudeRaw != null && longitudeRaw != null) { const latitude = Number(latitudeRaw); const longitude = Number(longitudeRaw); if (Number.isFinite(latitude) && Number.isFinite(longitude)) return { latitude, longitude, accuracy: live.accuracyInMeters ?? void 0, caption: live.caption ?? void 0, source: "live", isLive: true }; } } const location = message.locationMessage ?? void 0; if (location) { const latitudeRaw = location.degreesLatitude; const longitudeRaw = location.degreesLongitude; if (latitudeRaw != null && longitudeRaw != null) { const latitude = Number(latitudeRaw); const longitude = Number(longitudeRaw); if (Number.isFinite(latitude) && Number.isFinite(longitude)) { const isLive = Boolean(location.isLive); return { latitude, longitude, accuracy: location.accuracyInMeters ?? void 0, name: location.name ?? void 0, address: location.address ?? void 0, caption: location.comment ?? void 0, source: isLive ? "live" : location.name || location.address ? "place" : "pin", isLive }; } } } return null; } function describeReplyContext(rawMessage) { const message = unwrapMessage$1(rawMessage); if (!message) return null; const contextInfo = extractContextInfo(message); const quoted = (0, _baileys.normalizeMessageContent)(contextInfo?.quotedMessage); if (!quoted) return null; const location = extractLocationData(quoted); const locationText = location ? (0, _sendXbYokfdQ.Y)(location) : void 0; let body = [extractText(quoted), locationText].filter(Boolean).join("\n").trim(); if (!body) body = extractMediaPlaceholder(quoted); if (!body) { const quotedType = quoted ? (0, _baileys.getContentType)(quoted) : void 0; (0, _loggerU3s76KST.R)(`Quoted message missing extractable body${quotedType ? ` (type ${quotedType})` : ""}`); return null; } const senderJid = contextInfo?.participant ?? void 0; const senderE164 = senderJid ? (0, _loggerU3s76KST.S)(senderJid) ?? senderJid : void 0; const sender = senderE164 ?? "unknown sender"; return { id: contextInfo?.stanzaId ? String(contextInfo.stanzaId) : void 0, body, sender, senderJid, senderE164 }; } //#endregion //#region src/web/inbound/access-control.ts const PAIRING_REPLY_HISTORY_GRACE_MS = 3e4; function resolveWhatsAppRuntimeGroupPolicy(params) { return (0, _sendDPZOoQ.Pt)({ providerConfigPresent: params.providerConfigPresent, groupPolicy: params.groupPolicy, defaultGroupPolicy: params.defaultGroupPolicy }); } async function checkInboundAccessControl(params) { const cfg = (0, _configDiiPndBn.i)(); const account = (0, _accountsBFBjizxh.i)({ cfg, accountId: params.accountId }); const dmPolicy = account.dmPolicy ?? "pairing"; const configuredAllowFrom = account.allowFrom ?? []; const storeAllowFrom = await (0, _dispatchF_Zbttj.Vt)({ provider: "whatsapp", accountId: account.accountId, dmPolicy }); const defaultAllowFrom = configuredAllowFrom.length === 0 && params.selfE164 ? [params.selfE164] : []; const dmAllowFrom = configuredAllowFrom.length > 0 ? configuredAllowFrom : defaultAllowFrom; const groupAllowFrom = account.groupAllowFrom ?? (configuredAllowFrom.length > 0 ? configuredAllowFrom : void 0); const isSamePhone = params.from === params.selfE164; const isSelfChat = account.selfChatMode ?? (0, _loggerU3s76KST.x)(params.selfE164, configuredAllowFrom); const pairingGraceMs = typeof params.pairingGraceMs === "number" && params.pairingGraceMs > 0 ? params.pairingGraceMs : PAIRING_REPLY_HISTORY_GRACE_MS; const suppressPairingReply = typeof params.connectedAtMs === "number" && typeof params.messageTimestampMs === "number" && params.messageTimestampMs < params.connectedAtMs - pairingGraceMs; const defaultGroupPolicy = (0, _sendDPZOoQ.Nt)(cfg); const { groupPolicy, providerMissingFallbackApplied } = resolveWhatsAppRuntimeGroupPolicy({ providerConfigPresent: cfg.channels?.whatsapp !== void 0, groupPolicy: account.groupPolicy, defaultGroupPolicy }); (0, _sendDPZOoQ.It)({ providerMissingFallbackApplied, providerKey: "whatsapp", accountId: account.accountId, log: (message) => (0, _loggerU3s76KST.R)(message) }); const normalizedDmSender = (0, _loggerU3s76KST.C)(params.from); const normalizedGroupSender = typeof params.senderE164 === "string" ? (0, _loggerU3s76KST.C)(params.senderE164) : null; const access = (0, _dispatchF_Zbttj.Gt)({ isGroup: params.group, dmPolicy, groupPolicy, allowFrom: params.group ? configuredAllowFrom : dmAllowFrom, groupAllowFrom, storeAllowFrom, isSenderAllowed: (allowEntries) => { if (allowEntries.includes("*")) return true; const normalizedEntrySet = new Set(allowEntries.map((entry) => (0, _loggerU3s76KST.C)(String(entry))).filter((entry) => Boolean(entry))); if (!params.group && isSamePhone) return true; return params.group ? Boolean(normalizedGroupSender && normalizedEntrySet.has(normalizedGroupSender)) : normalizedEntrySet.has(normalizedDmSender); } }); if (params.group && access.decision !== "allow") { if (access.reason === "groupPolicy=disabled") (0, _loggerU3s76KST.R)("Blocked group message (groupPolicy: disabled)");else if (access.reason === "groupPolicy=allowlist (empty allowlist)") (0, _loggerU3s76KST.R)("Blocked group message (groupPolicy: allowlist, no groupAllowFrom)");else (0, _loggerU3s76KST.R)(`Blocked group message from ${params.senderE164 ?? "unknown sender"} (groupPolicy: allowlist)`); return { allowed: false, shouldMarkRead: false, isSelfChat, resolvedAccountId: account.accountId }; } if (!params.group) { if (params.isFromMe && !isSamePhone) { (0, _loggerU3s76KST.R)("Skipping outbound DM (fromMe); no pairing reply needed."); return { allowed: false, shouldMarkRead: false, isSelfChat, resolvedAccountId: account.accountId }; } if (access.decision === "block" && access.reason === "dmPolicy=disabled") { (0, _loggerU3s76KST.R)("Blocked dm (dmPolicy: disabled)"); return { allowed: false, shouldMarkRead: false, isSelfChat, resolvedAccountId: account.accountId }; } if (access.decision === "pairing" && !isSamePhone) { const candidate = params.from; if (suppressPairingReply) (0, _loggerU3s76KST.R)(`Skipping pairing reply for historical DM from ${candidate}.`);else await (0, _dispatchF_Zbttj.Nt)({ channel: "whatsapp", senderId: candidate, senderIdLine: `Your WhatsApp phone number: ${candidate}`, meta: { name: (params.pushName ?? "").trim() || void 0 }, upsertPairingRequest: async ({ id, meta }) => await (0, _sendXbYokfdQ.nt)({ channel: "whatsapp", id, accountId: account.accountId, meta }), onCreated: () => { (0, _loggerU3s76KST.R)(`whatsapp pairing request sender=${candidate} name=${params.pushName ?? "unknown"}`); }, sendPairingReply: async (text) => { await params.sock.sendMessage(params.remoteJid, { text }); }, onReplyError: (err) => { (0, _loggerU3s76KST.R)(`whatsapp pairing reply failed for ${candidate}: ${String(err)}`); } }); return { allowed: false, shouldMarkRead: false, isSelfChat, resolvedAccountId: account.accountId }; } if (access.decision !== "allow") { (0, _loggerU3s76KST.R)(`Blocked unauthorized sender ${params.from} (dmPolicy=${dmPolicy})`); return { allowed: false, shouldMarkRead: false, isSelfChat, resolvedAccountId: account.accountId }; } } return { allowed: true, shouldMarkRead: true, isSelfChat, resolvedAccountId: account.accountId }; } //#endregion //#region src/web/inbound/media.ts function unwrapMessage(message) { return (0, _baileys.normalizeMessageContent)(message); } /** * Resolve the MIME type for an inbound media message. * Falls back to WhatsApp's standard formats when Baileys omits the MIME. */ function resolveMediaMimetype(message) { const explicit = message.imageMessage?.mimetype ?? message.videoMessage?.mimetype ?? message.documentMessage?.mimetype ?? message.audioMessage?.mimetype ?? message.stickerMessage?.mimetype ?? void 0; if (explicit) return explicit; if (message.audioMessage) return "audio/ogg; codecs=opus"; if (message.imageMessage) return "image/jpeg"; if (message.videoMessage) return "video/mp4"; if (message.stickerMessage) return "image/webp"; } async function downloadInboundMedia(msg, sock) { const message = unwrapMessage(msg.message); if (!message) return; const mimetype = resolveMediaMimetype(message); const fileName = message.documentMessage?.fileName ?? void 0; if (!message.imageMessage && !message.videoMessage && !message.documentMessage && !message.audioMessage && !message.stickerMessage) return; try { return { buffer: await (0, _baileys.downloadMediaMessage)(msg, "buffer", {}, { reuploadRequest: sock.updateMediaMessage, logger: sock.logger }), mimetype, fileName }; } catch (err) { (0, _loggerU3s76KST.R)(`downloadMediaMessage failed: ${String(err)}`); return; } } //#endregion //#region src/web/inbound/send-api.ts function recordWhatsAppOutbound(accountId) { (0, _channelActivityDH8wxjI.n)({ channel: "whatsapp", accountId, direction: "outbound" }); } function resolveOutboundMessageId(result) { return typeof result === "object" && result && "key" in result ? String(result.key?.id ?? "unknown") : "unknown"; } function createWebSendApi(params) { return { sendMessage: async (to, text, mediaBuffer, mediaType, sendOptions) => { const jid = (0, _loggerU3s76KST.N)(to); let payload; if (mediaBuffer && mediaType) {if (mediaType.startsWith("image/")) payload = { image: mediaBuffer, caption: text || void 0, mimetype: mediaType };else if (mediaType.startsWith("audio/")) payload = { audio: mediaBuffer, ptt: true, mimetype: mediaType };else if (mediaType.startsWith("video/")) { const gifPlayback = sendOptions?.gifPlayback; payload = { video: mediaBuffer, caption: text || void 0, mimetype: mediaType, ...(gifPlayback ? { gifPlayback: true } : {}) }; } else payload = { document: mediaBuffer, fileName: sendOptions?.fileName?.trim() || "file", caption: text || void 0, mimetype: mediaType };} else payload = { text }; const result = await params.sock.sendMessage(jid, payload); recordWhatsAppOutbound(sendOptions?.accountId ?? params.defaultAccountId); return { messageId: resolveOutboundMessageId(result) }; }, sendPoll: async (to, poll) => { const jid = (0, _loggerU3s76KST.N)(to); const result = await params.sock.sendMessage(jid, { poll: { name: poll.question, values: poll.options, selectableCount: poll.maxSelections ?? 1 } }); recordWhatsAppOutbound(params.defaultAccountId); return { messageId: resolveOutboundMessageId(result) }; }, sendReaction: async (chatJid, messageId, emoji, fromMe, participant) => { const jid = (0, _loggerU3s76KST.N)(chatJid); await params.sock.sendMessage(jid, { react: { text: emoji, key: { remoteJid: jid, id: messageId, fromMe, participant: participant ? (0, _loggerU3s76KST.N)(participant) : void 0 } } }); }, sendComposingTo: async (to) => { const jid = (0, _loggerU3s76KST.N)(to); await params.sock.sendPresenceUpdate("composing", jid); } }; } //#endregion //#region src/web/inbound/monitor.ts async function monitorWebInbox(options) { const inboundLogger = (0, _loggerU3s76KST.K)({ module: "web-inbound" }); const inboundConsoleLog = (0, _loggerU3s76KST.a)("gateway/channels/whatsapp").child("inbound"); const sock = await (0, _sessionDwLPNct.t)(false, options.verbose, { authDir: options.authDir }); await (0, _sessionDwLPNct.i)(sock); const connectedAtMs = Date.now(); let onCloseResolve = null; const onClose = new Promise((resolve) => { onCloseResolve = resolve; }); const resolveClose = (reason) => { if (!onCloseResolve) return; const resolver = onCloseResolve; onCloseResolve = null; resolver(reason); }; try { await sock.sendPresenceUpdate("available"); if ((0, _loggerU3s76KST.B)()) (0, _loggerU3s76KST.R)("Sent global 'available' presence on connect"); } catch (err) { (0, _loggerU3s76KST.R)(`Failed to send 'available' presence on connect: ${String(err)}`); } const selfJid = sock.user?.id; const selfE164 = selfJid ? (0, _loggerU3s76KST.S)(selfJid) : null; const debouncer = (0, _dispatchF_Zbttj.Z)({ debounceMs: options.debounceMs ?? 0, buildKey: (msg) => { const senderKey = msg.chatType === "group" ? msg.senderJid ?? msg.senderE164 ?? msg.senderName ?? msg.from : msg.from; if (!senderKey) return null; const conversationKey = msg.chatType === "group" ? msg.chatId : msg.from; return `${msg.accountId}:${conversationKey}:${senderKey}`; }, shouldDebounce: options.shouldDebounce, onFlush: async (entries) => { const last = entries.at(-1); if (!last) return; if (entries.length === 1) { await options.onMessage(last); return; } const mentioned = /* @__PURE__ */new Set(); for (const entry of entries) for (const jid of entry.mentionedJids ?? []) mentioned.add(jid); const combinedBody = entries.map((entry) => entry.body).filter(Boolean).join("\n"); const combinedMessage = { ...last, body: combinedBody, mentionedJids: mentioned.size > 0 ? Array.from(mentioned) : void 0 }; await options.onMessage(combinedMessage); }, onError: (err) => { inboundLogger.error({ error: String(err) }, "failed handling inbound web message"); inboundConsoleLog.error(`Failed handling inbound web message: ${String(err)}`); } }); const groupMetaCache = /* @__PURE__ */new Map(); const GROUP_META_TTL_MS = 300 * 1e3; const lidLookup = sock.signalRepository?.lidMapping; const resolveInboundJid = async (jid) => (0, _loggerU3s76KST.E)(jid, { authDir: options.authDir, lidLookup }); const getGroupMeta = async (jid) => { const cached = groupMetaCache.get(jid); if (cached && cached.expires > Date.now()) return cached; try { const meta = await sock.groupMetadata(jid); const participants = (await Promise.all(meta.participants?.map(async (p) => { return (await resolveInboundJid(p.id)) ?? p.id; }) ?? [])).filter(Boolean) ?? []; const entry = { subject: meta.subject, participants, expires: Date.now() + GROUP_META_TTL_MS }; groupMetaCache.set(jid, entry); return entry; } catch (err) { (0, _loggerU3s76KST.R)(`Failed to fetch group metadata for ${jid}: ${String(err)}`); return { expires: Date.now() + GROUP_META_TTL_MS }; } }; const normalizeInboundMessage = async (msg) => { const id = msg.key?.id ?? void 0; const remoteJid = msg.key?.remoteJid; if (!remoteJid) return null; if (remoteJid.endsWith("@status") || remoteJid.endsWith("@broadcast")) return null; const group = (0, _baileys.isJidGroup)(remoteJid) === true; if (id) { if (isRecentInboundMessage(`${options.accountId}:${remoteJid}:${id}`)) return null; } const participantJid = msg.key?.participant ?? void 0; const from = group ? remoteJid : await resolveInboundJid(remoteJid); if (!from) return null; const senderE164 = group ? participantJid ? await resolveInboundJid(participantJid) : null : from; let groupSubject; let groupParticipants; if (group) { const meta = await getGroupMeta(remoteJid); groupSubject = meta.subject; groupParticipants = meta.participants; } const messageTimestampMs = msg.messageTimestamp ? Number(msg.messageTimestamp) * 1e3 : void 0; const access = await checkInboundAccessControl({ accountId: options.accountId, from, selfE164, senderE164, group, pushName: msg.pushName ?? void 0, isFromMe: Boolean(msg.key?.fromMe), messageTimestampMs, connectedAtMs, sock: { sendMessage: (jid, content) => sock.sendMessage(jid, content) }, remoteJid }); if (!access.allowed) return null; return { id, remoteJid, group, participantJid, from, senderE164, groupSubject, groupParticipants, messageTimestampMs, access }; }; const maybeMarkInboundAsRead = async (inbound) => { const { id, remoteJid, participantJid, access } = inbound; if (id && !access.isSelfChat && options.sendReadReceipts !== false) try { await sock.readMessages([{ remoteJid, id, participant: participantJid, fromMe: false }]); if ((0, _loggerU3s76KST.B)()) (0, _loggerU3s76KST.R)(`Marked message ${id} as read for ${remoteJid}${participantJid ? ` (participant ${participantJid})` : ""}`); } catch (err) { (0, _loggerU3s76KST.R)(`Failed to mark message ${id} read: ${String(err)}`); } else if (id && access.isSelfChat && (0, _loggerU3s76KST.B)()) (0, _loggerU3s76KST.R)(`Self-chat mode: skipping read receipt for ${id}`); }; const enrichInboundMessage = async (msg) => { const location = extractLocationData(msg.message ?? void 0); const locationText = location ? (0, _sendXbYokfdQ.Y)(location) : void 0; let body = extractText(msg.message ?? void 0); if (locationText) body = [body, locationText].filter(Boolean).join("\n").trim(); if (!body) { body = extractMediaPlaceholder(msg.message ?? void 0); if (!body) return null; } const replyContext = describeReplyContext(msg.message); let mediaPath; let mediaType; let mediaFileName; try { const inboundMedia = await downloadInboundMedia(msg, sock); if (inboundMedia) { const maxBytes = (typeof options.mediaMaxMb === "number" && options.mediaMaxMb > 0 ? options.mediaMaxMb : 50) * 1024 * 1024; mediaPath = (await (0, _storeBfiJnRiX.a)(inboundMedia.buffer, inboundMedia.mimetype, "inbound", maxBytes, inboundMedia.fileName)).path; mediaType = inboundMedia.mimetype; mediaFileName = inboundMedia.fileName; } } catch (err) { (0, _loggerU3s76KST.R)(`Inbound media download failed: ${String(err)}`); } return { body, location: location ?? void 0, replyContext, mediaPath, mediaType, mediaFileName }; }; const enqueueInboundMessage = async (msg, inbound, enriched) => { const chatJid = inbound.remoteJid; const sendComposing = async () => { try { await sock.sendPresenceUpdate("composing", chatJid); } catch (err) { (0, _loggerU3s76KST.R)(`Presence update failed: ${String(err)}`); } }; const reply = async (text) => { await sock.sendMessage(chatJid, { text }); }; const sendMedia = async (payload) => { await sock.sendMessage(chatJid, payload); }; const timestamp = inbound.messageTimestampMs; const mentionedJids = extractMentionedJids(msg.message); const senderName = msg.pushName ?? void 0; inboundLogger.info({ from: inbound.from, to: selfE164 ?? "me", body: enriched.body, mediaPath: enriched.mediaPath, mediaType: enriched.mediaType, mediaFileName: enriched.mediaFileName, timestamp }, "inbound message"); const inboundMessage = { id: inbound.id, from: inbound.from, conversationId: inbound.from, to: selfE164 ?? "me", accountId: inbound.access.resolvedAccountId, body: enriched.body, pushName: senderName, timestamp, chatType: inbound.group ? "group" : "direct", chatId: inbound.remoteJid, senderJid: inbound.participantJid, senderE164: inbound.senderE164 ?? void 0, senderName, replyToId: enriched.replyContext?.id, replyToBody: enriched.replyContext?.body, replyToSender: enriched.replyContext?.sender, replyToSenderJid: enriched.replyContext?.senderJid, replyToSenderE164: enriched.replyContext?.senderE164, groupSubject: inbound.groupSubject, groupParticipants: inbound.groupParticipants, mentionedJids: mentionedJids ?? void 0, selfJid, selfE164, fromMe: Boolean(msg.key?.fromMe), location: enriched.location ?? void 0, sendComposing, reply, sendMedia, mediaPath: enriched.mediaPath, mediaType: enriched.mediaType, mediaFileName: enriched.mediaFileName }; try { Promise.resolve(debouncer.enqueue(inboundMessage)).catch((err) => { inboundLogger.error({ error: String(err) }, "failed handling inbound web message"); inboundConsoleLog.error(`Failed handling inbound web message: ${String(err)}`); }); } catch (err) { inboundLogger.error({ error: String(err) }, "failed handling inbound web message"); inboundConsoleLog.error(`Failed handling inbound web message: ${String(err)}`); } }; const handleMessagesUpsert = async (upsert) => { if (upsert.type !== "notify" && upsert.type !== "append") return; for (const msg of upsert.messages ?? []) { (0, _channelActivityDH8wxjI.n)({ channel: "whatsapp", accountId: options.accountId, direction: "inbound" }); const inbound = await normalizeInboundMessage(msg); if (!inbound) continue; await maybeMarkInboundAsRead(inbound); if (upsert.type === "append") continue; const enriched = await enrichInboundMessage(msg); if (!enriched) continue; await enqueueInboundMessage(msg, inbound, enriched); } }; sock.ev.on("messages.upsert", handleMessagesUpsert); const handleConnectionUpdate = (update) => { try { if (update.connection === "close") { const status = (0, _sessionDwLPNct.r)(update.lastDisconnect?.error); resolveClose({ status, isLoggedOut: status === _baileys.DisconnectReason.loggedOut, error: update.lastDisconnect?.error }); } } catch (err) { inboundLogger.error({ error: String(err) }, "connection.update handler error"); resolveClose({ status: void 0, isLoggedOut: false, error: err }); } }; sock.ev.on("connection.update", handleConnectionUpdate); return { close: async () => { try { const ev = sock.ev; const messagesUpsertHandler = handleMessagesUpsert; const connectionUpdateHandler = handleConnectionUpdate; if (typeof ev.off === "function") { ev.off("messages.upsert", messagesUpsertHandler); ev.off("connection.update", connectionUpdateHandler); } else if (typeof ev.removeListener === "function") { ev.removeListener("messages.upsert", messagesUpsertHandler); ev.removeListener("connection.update", connectionUpdateHandler); } sock.ws?.close(); } catch (err) { (0, _loggerU3s76KST.R)(`Socket close failed: ${String(err)}`); } }, onClose, signalClose: (reason) => { resolveClose(reason ?? { status: void 0, isLoggedOut: false, error: "closed" }); }, ...createWebSendApi({ sock: { sendMessage: (jid, content) => sock.sendMessage(jid, content), sendPresenceUpdate: (presence, jid) => sock.sendPresenceUpdate(presence, jid) }, defaultAccountId: options.accountId }) }; } //#endregion //#region src/web/auto-reply/mentions.ts function buildMentionConfig(cfg, agentId) { return { mentionRegexes: (0, _dispatchF_Zbttj.tt)(cfg, agentId), allowFrom: cfg.channels?.whatsapp?.allowFrom }; } function resolveMentionTargets(msg, authDir) { const jidOptions = authDir ? { authDir } : void 0; return { normalizedMentions: msg.mentionedJids?.length ? msg.mentionedJids.map((jid) => (0, _loggerU3s76KST.S)(jid, jidOptions) ?? jid).filter(Boolean) : [], selfE164: msg.selfE164 ?? (msg.selfJid ? (0, _loggerU3s76KST.S)(msg.selfJid, jidOptions) : null), selfJid: msg.selfJid ? msg.selfJid.replace(/:\\d+/, "") : null }; } function isBotMentionedFromTargets(msg, mentionCfg, targets) { const clean = (text) => (0, _dispatchF_Zbttj.nt)(text); const isSelfChat = (0, _loggerU3s76KST.x)(targets.selfE164, mentionCfg.allowFrom); const hasMentions = (msg.mentionedJids?.length ?? 0) > 0; if (hasMentions && !isSelfChat) { if (targets.selfE164 && targets.normalizedMentions.includes(targets.selfE164)) return true; if (targets.selfJid) { if (targets.normalizedMentions.includes(targets.selfJid)) return true; } return false; } else if (hasMentions && isSelfChat) {} const bodyClean = clean(msg.body); if (mentionCfg.mentionRegexes.some((re) => re.test(bodyClean))) return true; if (targets.selfE164) { const selfDigits = targets.selfE164.replace(/\D/g, ""); if (selfDigits) { if (bodyClean.replace(/[^\d]/g, "").includes(selfDigits)) return true; const bodyNoSpace = msg.body.replace(/[\s-]/g, ""); if (new RegExp(`\\+?${selfDigits}`, "i").test(bodyNoSpace)) return true; } } return false; } function debugMention(msg, mentionCfg, authDir) { const mentionTargets = resolveMentionTargets(msg, authDir); return { wasMentioned: isBotMentionedFromTargets(msg, mentionCfg, mentionTargets), details: { from: msg.from, body: msg.body, bodyClean: (0, _dispatchF_Zbttj.nt)(msg.body), mentionedJids: msg.mentionedJids ?? null, normalizedMentionedJids: mentionTargets.normalizedMentions.length ? mentionTargets.normalizedMentions : null, selfJid: msg.selfJid ?? null, selfJidBare: mentionTargets.selfJid, selfE164: msg.selfE164 ?? null, resolvedSelfE164: mentionTargets.selfE164 } }; } function resolveOwnerList(mentionCfg, selfE164) { const allowFrom = mentionCfg.allowFrom; return (Array.isArray(allowFrom) && allowFrom.length > 0 ? allowFrom : selfE164 ? [selfE164] : []).filter((entry) => Boolean(entry && entry !== "*")).map((entry) => (0, _loggerU3s76KST.C)(entry)).filter((entry) => Boolean(entry)); } //#endregion //#region src/web/auto-reply/monitor/echo.ts function createEchoTracker(params) { const recentlySent = /* @__PURE__ */new Set(); const maxItems = Math.max(1, params.maxItems ?? 100); const buildCombinedKey = (p) => `combined:${p.sessionKey}:${p.combinedBody}`; const trim = () => { while (recentlySent.size > maxItems) { const firstKey = recentlySent.values().next().value; if (!firstKey) break; recentlySent.delete(firstKey); } }; const rememberText = (text, opts) => { if (!text) return; recentlySent.add(text); if (opts.combinedBody && opts.combinedBodySessionKey) recentlySent.add(buildCombinedKey({ sessionKey: opts.combinedBodySessionKey, combinedBody: opts.combinedBody })); if (opts.logVerboseMessage) params.logVerbose?.(`Added to echo detection set (size now: ${recentlySent.size}): ${text.substring(0, 50)}...`); trim(); }; return { rememberText, has: (key) => recentlySent.has(key), forget: (key) => { recentlySent.delete(key); }, buildCombinedKey }; } //#endregion //#region src/web/auto-reply/monitor/broadcast.ts function buildBroadcastRouteKeys(params) { const sessionKey = (0, _dispatchF_Zbttj.St)({ agentId: params.agentId, channel: "whatsapp", accountId: params.route.accountId, peer: { kind: params.msg.chatType === "group" ? "group" : "direct", id: params.peerId }, dmScope: params.cfg.session?.dmScope, identityLinks: params.cfg.session?.identityLinks }); const mainSessionKey = (0, _runWithConcurrency2ga3CMk.Q)({ agentId: params.agentId, mainKey: _runWithConcurrency2ga3CMk.Z }); return { sessionKey, mainSessionKey, lastRoutePolicy: (0, _dispatchF_Zbttj.Ct)({ sessionKey, mainSessionKey }) }; } async function maybeBroadcastMessage(params) { const broadcastAgents = params.cfg.broadcast?.[params.peerId]; if (!broadcastAgents || !Array.isArray(broadcastAgents)) return false; if (broadcastAgents.length === 0) return false; const strategy = params.cfg.broadcast?.strategy || "parallel"; whatsappInboundLog.info(`Broadcasting message to ${broadcastAgents.length} agents (${strategy})`); const agentIds = params.cfg.agents?.list?.map((agent) => (0, _runWithConcurrency2ga3CMk.rt)(agent.id)); const hasKnownAgents = (agentIds?.length ?? 0) > 0; const groupHistorySnapshot = params.msg.chatType === "group" ? params.groupHistories.get(params.groupHistoryKey) ?? [] : void 0; const processForAgent = async (agentId) => { const normalizedAgentId = (0, _runWithConcurrency2ga3CMk.rt)(agentId); if (hasKnownAgents && !agentIds?.includes(normalizedAgentId)) { whatsappInboundLog.warn(`Broadcast agent ${agentId} not found in agents.list; skipping`); return false; } const routeKeys = buildBroadcastRouteKeys({ cfg: params.cfg, msg: params.msg, route: params.route, peerId: params.peerId, agentId: normalizedAgentId }); const agentRoute = { ...params.route, agentId: normalizedAgentId, ...routeKeys }; try { return await params.processMessage(params.msg, agentRoute, params.groupHistoryKey, { groupHistory: groupHistorySnapshot, suppressGroupHistoryClear: true }); } catch (err) { whatsappInboundLog.error(`Broadcast agent ${agentId} failed: ${(0, _sessionDwLPNct.n)(err)}`); return false; } }; if (strategy === "sequential") for (const agentId of broadcastAgents) await processForAgent(agentId);else await Promise.allSettled(broadcastAgents.map(processForAgent)); if (params.msg.chatType === "group") params.groupHistories.set(params.groupHistoryKey, []); return true; } //#endregion //#region src/web/auto-reply/monitor/commands.ts function stripMentionsForCommand(text, mentionRegexes, selfE164) { let result = text; for (const re of mentionRegexes) result = result.replace(re, " "); if (selfE164) { const digits = selfE164.replace(/\D/g, ""); if (digits) { const pattern = new RegExp(`\\+?${digits}`, "g"); result = result.replace(pattern, " "); } } return result.replace(/\s+/g, " ").trim(); } //#endregion //#region src/web/auto-reply/monitor/group-activation.ts function resolveGroupPolicyFor(cfg, conversationId) { const groupId = (0, _piEmbeddedHelpersDmr3bcbH.sn)({ From: conversationId, ChatType: "group", Provider: "whatsapp" })?.id; const whatsappCfg = cfg.channels?.whatsapp; const hasGroupAllowFrom = Boolean(whatsappCfg?.groupAllowFrom?.length || whatsappCfg?.allowFrom?.length); return (0, _thinkingCfIPyoMg.P)({ cfg, channel: "whatsapp", groupId: groupId ?? conversationId, hasGroupAllowFrom }); } function resolveGroupRequireMentionFor(cfg, conversationId) { const groupId = (0, _piEmbeddedHelpersDmr3bcbH.sn)({ From: conversationId, ChatType: "group", Provider: "whatsapp" })?.id; return (0, _thinkingCfIPyoMg.F)({ cfg, channel: "whatsapp", groupId: groupId ?? conversationId }); } function resolveGroupActivationFor(params) { const entry = (0, _piEmbeddedHelpersDmr3bcbH.vt)((0, _pathsYc45qYMp.s)(params.cfg.session?.store, { agentId: params.agentId }))[params.sessionKey]; const defaultActivation = !resolveGroupRequireMentionFor(params.cfg, params.conversationId) ? "always" : "mention"; return (0, _dispatchF_Zbttj.st)(entry?.groupActivation) ?? defaultActivation; } //#endregion //#region src/web/auto-reply/monitor/group-members.ts function appendNormalizedUnique(entries, seen, ordered) { for (const entry of entries) { const normalized = (0, _loggerU3s76KST.C)(entry) ?? entry; if (!normalized || seen.has(normalized)) continue; seen.add(normalized); ordered.push(normalized); } } function noteGroupMember(groupMemberNames, conversationId, e164, name) { if (!e164 || !name) return; const key = (0, _loggerU3s76KST.C)(e164) ?? e164; if (!key) return; let roster = groupMemberNames.get(conversationId); if (!roster) { roster = /* @__PURE__ */new Map(); groupMemberNames.set(conversationId, roster); } roster.set(key, name); } function formatGroupMembers(params) { const { participants, roster, fallbackE164 } = params; const seen = /* @__PURE__ */new Set(); const ordered = []; if (participants?.length) appendNormalizedUnique(participants, seen, ordered); if (roster) appendNormalizedUnique(roster.keys(), seen, ordered); if (ordered.length === 0 && fallbackE164) { const normalized = (0, _loggerU3s76KST.C)(fallbackE164) ?? fallbackE164; if (normalized) ordered.push(normalized); } if (ordered.length === 0) return; return ordered.map((entry) => { const name = roster?.get(entry); return name ? `${name} (${entry})` : entry; }).join(", "); } //#endregion //#region src/web/auto-reply/monitor/group-gating.ts function isOwnerSender(baseMentionConfig, msg) { const sender = (0, _loggerU3s76KST.C)(msg.senderE164 ?? ""); if (!sender) return false; return resolveOwnerList(baseMentionConfig, msg.selfE164 ?? void 0).includes(sender); } function recordPendingGroupHistoryEntry(params) { const sender = params.msg.senderName && params.msg.senderE164 ? `${params.msg.senderName} (${params.msg.senderE164})` : params.msg.senderName ?? params.msg.senderE164 ?? "Unknown"; (0, _dispatchF_Zbttj.H)({ historyMap: params.groupHistories, historyKey: params.groupHistoryKey, limit: params.groupHistoryLimit, entry: { sender, body: params.msg.body, timestamp: params.msg.timestamp, id: params.msg.id, senderJid: params.msg.senderJid } }); } function skipGroupMessageAndStoreHistory(params, verboseMessage) { params.logVerbose(verboseMessage); recordPendingGroupHistoryEntry({ msg: params.msg, groupHistories: params.groupHistories, groupHistoryKey: params.groupHistoryKey, groupHistoryLimit: params.groupHistoryLimit }); return { shouldProcess: false }; } function applyGroupGating(params) { const groupPolicy = resolveGroupPolicyFor(params.cfg, params.conversationId); if (groupPolicy.allowlistEnabled && !groupPolicy.allowed) { params.logVerbose(`Skipping group message ${params.conversationId} (not in allowlist)`); return { shouldProcess: false }; } noteGroupMember(params.groupMemberNames, params.groupHistoryKey, params.msg.senderE164, params.msg.senderName); const mentionConfig = buildMentionConfig(params.cfg, params.agentId); const commandBody = stripMentionsForCommand(params.msg.body, mentionConfig.mentionRegexes, params.msg.selfE164); const activationCommand = (0, _dispatchF_Zbttj.ct)(commandBody); const owner = isOwnerSender(params.baseMentionConfig, params.msg); const shouldBypassMention = owner && (0, _dispatchF_Zbttj.$)(commandBody, params.cfg); if (activationCommand.hasCommand && !owner) return skipGroupMessageAndStoreHistory(params, `Ignoring /activation from non-owner in group ${params.conversationId}`); const mentionDebug = debugMention(params.msg, mentionConfig, params.authDir); params.replyLogger.debug({ conversationId: params.conversationId, wasMentioned: mentionDebug.wasMentioned, ...mentionDebug.details }, "group mention debug"); const wasMentioned = mentionDebug.wasMentioned; const requireMention = resolveGroupActivationFor({ cfg: params.cfg, agentId: params.agentId, sessionKey: params.sessionKey, conversationId: params.conversationId }) !== "always"; const selfJid = params.msg.selfJid?.replace(/:\\d+/, ""); const replySenderJid = params.msg.replyToSenderJid?.replace(/:\\d+/, ""); const selfE164 = params.msg.selfE164 ? (0, _loggerU3s76KST.C)(params.msg.selfE164) : null; const replySenderE164 = params.msg.replyToSenderE164 ? (0, _loggerU3s76KST.C)(params.msg.replyToSenderE164) : null; const mentionGate = (0, _dispatchF_Zbttj.D)({ requireMention, canDetectMention: true, wasMentioned, implicitMention: Boolean(selfJid && replySenderJid && selfJid === replySenderJid || selfE164 && replySenderE164 && selfE164 === replySenderE164), shouldBypassMention }); params.msg.wasMentioned = mentionGate.effectiveWasMentioned; if (!shouldBypassMention && requireMention && mentionGate.shouldSkip) return skipGroupMessageAndStoreHistory(params, `Group message stored for context (no mention detected) in ${params.conversationId}: ${params.msg.body}`); return { shouldProcess: true }; } //#endregion //#region src/web/auto-reply/monitor/last-route.ts function trackBackgroundTask(backgroundTasks, task) { backgroundTasks.add(task); task.finally(() => { backgroundTasks.delete(task); }); } function updateLastRouteInBackground(params) { const storePath = (0, _pathsYc45qYMp.s)(params.cfg.session?.store, { agentId: params.storeAgentId }); const task = (0, _piEmbeddedHelpersDmr3bcbH.St)({ storePath, sessionKey: params.sessionKey, deliveryContext: { channel: params.channel, to: params.to, accountId: params.accountId }, ctx: params.ctx }).catch((err) => { params.warn({ error: (0, _sessionDwLPNct.n)(err), storePath, sessionKey: params.sessionKey, to: params.to }, "failed updating last route"); }); trackBackgroundTask(params.backgroundTasks, task); } //#endregion //#region src/web/auto-reply/monitor/peer.ts function resolvePeerId(msg) { if (msg.chatType === "group") return msg.conversationId ?? msg.from; if (msg.senderE164) return (0, _loggerU3s76KST.C)(msg.senderE164) ?? msg.senderE164; if (msg.from.includes("@")) return (0, _loggerU3s76KST.S)(msg.from) ?? msg.from; return (0, _loggerU3s76KST.C)(msg.from) ?? msg.from; } //#endregion //#region src/web/auto-reply/util.ts function elide(text, limit = 400) { if (!text) return text; if (text.length <= limit) return text; return `${text.slice(0, limit)}… (truncated ${text.length - limit} chars)`; } function isLikelyWhatsAppCryptoError(reason) { const formatReason = (value) => { if (value == null) return ""; if (typeof value === "string") return value; if (value instanceof Error) return `${value.message}\n${value.stack ?? ""}`; if (typeof value === "object") try { return JSON.stringify(value); } catch { return Object.prototype.toString.call(value); } if (typeof value === "number") return String(value); if (typeof value === "boolean") return String(value); if (typeof value === "bigint") return String(value); if (typeof value === "symbol") return value.description ?? value.toString(); if (typeof value === "function") return value.name ? `[function ${value.name}]` : "[function]"; return Object.prototype.toString.call(value); }; const haystack = (reason instanceof Error ? `${reason.message}\n${reason.stack ?? ""}` : formatReason(reason)).toLowerCase(); if (!(haystack.includes("unsupported state or unable to authenticate data") || haystack.includes("bad mac"))) return false; return haystack.includes("@whiskeysockets/baileys") || haystack.includes("baileys") || haystack.includes("noise-handler") || haystack.includes("aesdecryptgcm"); } //#endregion //#region src/web/auto-reply/deliver-reply.ts const REASONING_PREFIX = "reasoning:"; function shouldSuppressReasoningReply(payload) { if (payload.isReasoning === true) return true; const text = payload.text; if (typeof text !== "string") return false; return text.trimStart().toLowerCase().startsWith(REASONING_PREFIX); } async function deliverWebReply(params) { const { replyResult, msg, maxMediaBytes, textLimit, replyLogger, connectionId, skipLog } = params; const replyStarted = Date.now(); if (shouldSuppressReasoningReply(replyResult)) { whatsappOutboundLog.debug(`Suppressed reasoning payload to ${msg.from}`); return; } const tableMode = params.tableMode ?? "code"; const chunkMode = params.chunkMode ?? "length"; const textChunks = (0, _irKp5uANes.c)((0, _outboundDV7ZS1T.i)((0, _tablesDkBUhlLj.t)(replyResult.text || "", tableMode)), textLimit, chunkMode); const mediaList = replyResult.mediaUrls?.length ? replyResult.mediaUrls : replyResult.mediaUrl ? [replyResult.mediaUrl] : []; const sendWithRetry = async (fn, label, maxAttempts = 3) => { let lastErr; for (let attempt = 1; attempt <= maxAttempts; attempt++) try { return await fn(); } catch (err) { lastErr = err; const errText = (0, _sessionDwLPNct.n)(err); const isLast = attempt === maxAttempts; if (!/closed|reset|timed\s*out|disconnect/i.test(errText) || isLast) throw err; const backoffMs = 500 * attempt; (0, _loggerU3s76KST.R)(`Retrying ${label} to ${msg.from} after failure (${attempt}/${maxAttempts - 1}) in ${backoffMs}ms: ${errText}`); await (0, _loggerU3s76KST.j)(backoffMs); } throw lastErr; }; if (mediaList.length === 0 && textChunks.length) { const totalChunks = textChunks.length; for (const [index, chunk] of textChunks.entries()) { const chunkStarted = Date.now(); await sendWithRetry(() => msg.reply(chunk), "text"); if (!skipLog) { const durationMs = Date.now() - chunkStarted; whatsappOutboundLog.debug(`Sent chunk ${index + 1}/${totalChunks} to ${msg.from} (${durationMs.toFixed(0)}ms)`); } } replyLogger.info({ correlationId: msg.id ?? newConnectionId(), connectionId: connectionId ?? null, to: msg.from, from: msg.to, text: elide(replyResult.text, 240), mediaUrl: null, mediaSizeBytes: null, mediaKind: null, durationMs: Date.now() - replyStarted }, "auto-reply sent (text)"); return; } const remainingText = [...textChunks]; for (const [index, mediaUrl] of mediaList.entries()) { const caption = index === 0 ? remainingText.shift() || void 0 : void 0; try { const media = await (0, _irKp5uANes.v)(mediaUrl, { maxBytes: maxMediaBytes, localRoots: params.mediaLocalRoots }); if ((0, _loggerU3s76KST.B)()) { (0, _loggerU3s76KST.R)(`Web auto-reply media size: ${(media.buffer.length / (1024 * 1024)).toFixed(2)}MB`); (0, _loggerU3s76KST.R)(`Web auto-reply media source: ${mediaUrl} (kind ${media.kind})`); } if (media.kind === "image") await sendWithRetry(() => msg.sendMedia({ image: media.buffer, caption, mimetype: media.contentType }), "media:image");else if (media.kind === "audio") await sendWithRetry(() => msg.sendMedia({ audio: media.buffer, ptt: true, mimetype: media.contentType, caption }), "media:audio");else if (media.kind === "video") await sendWithRetry(() => msg.sendMedia({ video: media.buffer, caption, mimetype: media.contentType }), "media:video");else { const fileName = media.fileName ?? mediaUrl.split("/").pop() ?? "file"; const mimetype = media.contentType ?? "application/octet-stream"; await sendWithRetry(() => msg.sendMedia({ document: media.buffer, fileName, caption, mimetype }), "media:document"); } whatsappOutboundLog.info(`Sent media reply to ${msg.from} (${(media.buffer.length / (1024 * 1024)).toFixed(2)}MB)`); replyLogger.info({ correlationId: msg.id ?? newConnectionId(), connectionId: connectionId ?? null, to: msg.from, from: msg.to, text: caption ?? null, mediaUrl, mediaSizeBytes: media.buffer.length, mediaKind: media.kind, durationMs: Date.now() - replyStarted }, "auto-reply sent (media)"); } catch (err) { whatsappOutboundLog.error(`Failed sending web media to ${msg.from}: ${(0, _sessionDwLPNct.n)(err)}`); replyLogger.warn({ err, mediaUrl }, "failed to send web media reply"); if (index === 0) { const warning = err instanceof Error ? `⚠️ Media failed: ${err.message}` : "⚠️ Media failed."; const fallbackText = [remainingText.shift() ?? caption ?? "", warning].filter(Boolean).join("\n"); if (fallbackText) { whatsappOutboundLog.warn(`Media skipped; sent text-only to ${msg.from}`); await msg.reply(fallbackText); } } } } for (const chunk of remainingText) await msg.reply(chunk); } //#endregion //#region src/web/auto-reply/monitor/ack-reaction.ts function maybeSendAckReaction(params) { if (!params.msg.id) return; const ackConfig = params.cfg.channels?.whatsapp?.ackReaction; const emoji = (ackConfig?.emoji ?? "").trim(); const directEnabled = ackConfig?.direct ?? true; const groupMode = ackConfig?.group ?? "mentions"; const conversationIdForCheck = params.msg.conversationId ?? params.msg.from; const activation = params.msg.chatType === "group" ? resolveGroupActivationFor({ cfg: params.cfg, agentId: params.agentId, sessionKey: params.sessionKey, conversationId: conversationIdForCheck }) : null; const shouldSendReaction = () => (0, _dispatchF_Zbttj.J)({ emoji, isDirect: params.msg.chatType === "direct", isGroup: params.msg.chatType === "group", directEnabled, groupMode, wasMentioned: params.msg.wasMentioned === true, groupActivated: activation === "always" }); if (!shouldSendReaction()) return; params.info({ chatId: params.msg.chatId, messageId: params.msg.id, emoji }, "sending ack reaction"); (0, _outboundDV7ZS1T.r)(params.msg.chatId, params.msg.id, emoji, { verbose: params.verbose, fromMe: false, participant: params.msg.senderJid, accountId: params.accountId }).catch((err) => { params.warn({ error: (0, _sessionDwLPNct.n)(err), chatId: params.msg.chatId, messageId: params.msg.id }, "failed to send ack reaction"); (0, _loggerU3s76KST.R)(`WhatsApp ack reaction failed for chat ${params.msg.chatId}: ${(0, _sessionDwLPNct.n)(err)}`); }); } //#endregion //#region src/web/auto-reply/monitor/message-line.ts function formatReplyContext(msg) { if (!msg.replyToBody) return null; return `[Replying to ${msg.replyToSender ?? "unknown sender"}${msg.replyToId ? ` id:${msg.replyToId}` : ""}]\n${msg.replyToBody}\n[/Replying]`; } function buildInboundLine(params) { const { cfg, msg, agentId, previousTimestamp, envelope } = params; const messagePrefix = (0, _dispatchF_Zbttj.vt)(cfg, agentId, { configured: cfg.channels?.whatsapp?.messagePrefix, hasAllowFrom: (cfg.channels?.whatsapp?.allowFrom?.length ?? 0) > 0 }); const prefixStr = messagePrefix ? `${messagePrefix} ` : ""; const replyContext = formatReplyContext(msg); const baseLine = `${prefixStr}${msg.body}${replyContext ? `\n\n${replyContext}` : ""}`; return (0, _dispatchF_Zbttj.Ft)({ channel: "WhatsApp", from: msg.chatType === "group" ? msg.from : msg.from?.replace(/^whatsapp:/, ""), timestamp: msg.timestamp, body: baseLine, chatType: msg.chatType, sender: { name: msg.senderName, e164: msg.senderE164, id: msg.senderJid }, previousTimestamp, envelope, fromMe: msg.fromMe }); } //#endregion //#region src/web/auto-reply/monitor/process-message.ts async function resolveWhatsAppCommandAuthorized(params) { const useAccessGroups = params.cfg.commands?.useAccessGroups !== false; if (!useAccessGroups) return true; const isGroup = params.msg.chatType === "group"; const senderE164 = (0, _loggerU3s76KST.C)(isGroup ? params.msg.senderE164 ?? "" : params.msg.senderE164 ?? params.msg.from ?? ""); if (!senderE164) return false; const account = (0, _accountsBFBjizxh.i)({ cfg: params.cfg, accountId: params.msg.accountId }); const dmPolicy = account.dmPolicy ?? "pairing"; const groupPolicy = account.groupPolicy ?? "allowlist"; const configuredAllowFrom = account.allowFrom ?? []; const configuredGroupAllowFrom = account.groupAllowFrom ?? (configuredAllowFrom.length > 0 ? configuredAllowFrom : void 0); const storeAllowFrom = isGroup ? [] : await (0, _dispatchF_Zbttj.Vt)({ provider: "whatsapp", accountId: params.msg.accountId, dmPolicy }); return (0, _dispatchF_Zbttj.Wt)({ isGroup, dmPolicy, groupPolicy, allowFrom: configuredAllowFrom.length > 0 ? configuredAllowFrom : params.msg.selfE164 ? [params.msg.selfE164] : [], groupAllowFrom: configuredGroupAllowFrom, storeAllowFrom, isSenderAllowed: (allowEntries) => { if (allowEntries.includes("*")) return true; return allowEntries.map((entry) => (0, _loggerU3s76KST.C)(String(entry))).filter((entry) => Boolean(entry)).includes(senderE164); }, command: { useAccessGroups, allowTextCommands: true, hasControlCommand: true } }).commandAuthorized; } function resolvePinnedMainDmRecipient(params) { const account = (0, _accountsBFBjizxh.i)({ cfg: params.cfg, accountId: params.msg.accountId }); return (0, _dispatchF_Zbttj.qt)({ dmScope: params.cfg.session?.dmScope, allowFrom: account.allowFrom, normalizeEntry: (entry) => (0, _loggerU3s76KST.C)(entry) }); } async function processMessage(params) { const conversationId = params.msg.conversationId ?? params.msg.from; const { storePath, envelopeOptions, previousTimestamp } = (0, _dispatchF_Zbttj.Pt)({ cfg: params.cfg, agentId: params.route.agentId, sessionKey: params.route.sessionKey }); let combinedBody = buildInboundLine({ cfg: params.cfg, msg: params.msg, agentId: params.route.agentId, previousTimestamp, envelope: envelopeOptions }); let shouldClearGroupHistory = false; if (params.msg.chatType === "group") { const history = params.groupHistory ?? params.groupHistories.get(params.groupHistoryKey) ?? []; if (history.length > 0) combinedBody = (0, _dispatchF_Zbttj.I)({ entries: history.map((m) => ({ sender: m.sender, body: m.body, timestamp: m.timestamp })), currentMessage: combinedBody, excludeLast: false, formatEntry: (entry) => { return (0, _dispatchF_Zbttj.Ft)({ channel: "WhatsApp", from: conversationId, timestamp: entry.timestamp, body: entry.body, chatType: "group", senderLabel: entry.sender, envelope: envelopeOptions }); } }); shouldClearGroupHistory = !(params.suppressGroupHistoryClear ?? false); } const combinedEchoKey = params.buildCombinedEchoKey({ sessionKey: params.route.sessionKey, combinedBody }); if (params.echoHas(combinedEchoKey)) { (0, _loggerU3s76KST.R)("Skipping auto-reply: detected echo for combined message"); params.echoForget(combinedEchoKey); return false; } maybeSendAckReaction({ cfg: params.cfg, msg: params.msg, agentId: params.route.agentId, sessionKey: params.route.sessionKey, conversationId, verbose: params.verbose, accountId: params.route.accountId, info: params.replyLogger.info.bind(params.replyLogger), warn: params.replyLogger.warn.bind(params.replyLogger) }); const correlationId = params.msg.id ?? newConnectionId(); params.replyLogger.info({ connectionId: params.connectionId, correlationId, from: params.msg.chatType === "group" ? conversationId : params.msg.from, to: params.msg.to, body: elide(combinedBody, 240), mediaType: params.msg.mediaType ?? null, mediaPath: params.msg.mediaPath ?? null }, "inbound web message"); const fromDisplay = params.msg.chatType === "group" ? conversationId : params.msg.from; const kindLabel = params.msg.mediaType ? `, ${params.msg.mediaType}` : ""; whatsappInboundLog.info(`Inbound message ${fromDisplay} -> ${params.msg.to} (${params.msg.chatType}${kindLabel}, ${combinedBody.length} chars)`); if ((0, _loggerU3s76KST.B)()) whatsappInboundLog.debug(`Inbound body: ${elide(combinedBody, 400)}`); const dmRouteTarget = params.msg.chatType !== "group" ? (() => { if (params.msg.senderE164) return (0, _loggerU3s76KST.C)(params.msg.senderE164); if (params.msg.from.includes("@")) return (0, _loggerU3s76KST.S)(params.msg.from); return (0, _loggerU3s76KST.C)(params.msg.from); })() : void 0; const textLimit = params.maxMediaTextChunkLimit ?? (0, _irKp5uANes.f)(params.cfg, "whatsapp"); const chunkMode = (0, _irKp5uANes.d)(params.cfg, "whatsapp", params.route.accountId); const tableMode = (0, _irKp5uANes.i)({ cfg: params.cfg, channel: "whatsapp", accountId: params.route.accountId }); const mediaLocalRoots = (0, _localRootsZhwi3hFj.t)(params.cfg, params.route.agentId); let didLogHeartbeatStrip = false; let didSendReply = false; const commandAuthorized = (0, _dispatchF_Zbttj.et)(params.msg.body, params.cfg) ? await resolveWhatsAppCommandAuthorized({ cfg: params.cfg, msg: params.msg }) : void 0; const configuredResponsePrefix = params.cfg.messages?.responsePrefix; const { onModelSelected, ...prefixOptions } = (0, _dispatchF_Zbttj.j)({ cfg: params.cfg, agentId: params.route.agentId, channel: "whatsapp", accountId: params.route.accountId }); const isSelfChat = params.msg.chatType !== "group" && Boolean(params.msg.selfE164) && (0, _loggerU3s76KST.C)(params.msg.from) === (0, _loggerU3s76KST.C)(params.msg.selfE164 ?? ""); const responsePrefix = prefixOptions.responsePrefix ?? (configuredResponsePrefix === void 0 && isSelfChat ? (0, _dispatchF_Zbttj._t)(params.cfg, params.route.agentId) : void 0); const inboundHistory = params.msg.chatType === "group" ? (params.groupHistory ?? params.groupHistories.get(params.groupHistoryKey) ?? []).map((entry) => ({ sender: entry.sender, body: entry.body, timestamp: entry.timestamp })) : void 0; const ctxPayload = (0, _dispatchF_Zbttj.Et)({ Body: combinedBody, BodyForAgent: params.msg.body, InboundHistory: inboundHistory, RawBody: params.msg.body, CommandBody: params.msg.body, From: params.msg.from, To: params.msg.to, SessionKey: params.route.sessionKey, AccountId: params.route.accountId, MessageSid: params.msg.id, ReplyToId: params.msg.replyToId, ReplyToBody: params.msg.replyToBody, ReplyToSender: params.msg.replyToSender, MediaPath: params.msg.mediaPath, MediaUrl: params.msg.mediaUrl, MediaType: params.msg.mediaType, ChatType: params.msg.chatType, ConversationLabel: params.msg.chatType === "group" ? conversationId : params.msg.from, GroupSubject: params.msg.groupSubject, GroupMembers: formatGroupMembers({ participants: params.msg.groupParticipants, roster: params.groupMemberNames.get(params.groupHistoryKey), fallbackE164: params.msg.senderE164 }), SenderName: params.msg.senderName, SenderId: params.msg.senderJid?.trim() || params.msg.senderE164, SenderE164: params.msg.senderE164, CommandAuthorized: commandAuthorized, WasMentioned: params.msg.wasMentioned, ...(params.msg.location ? (0, _sendXbYokfdQ.X)(params.msg.location) : {}), Provider: "whatsapp", Surface: "whatsapp", OriginatingChannel: "whatsapp", OriginatingTo: params.msg.from }); const pinnedMainDmRecipient = resolvePinnedMainDmRecipient({ cfg: params.cfg, msg: params.msg }); const shouldUpdateMainLastRoute = !pinnedMainDmRecipient || pinnedMainDmRecipient === dmRouteTarget; const inboundLastRouteSessionKey = (0, _dispatchF_Zbttj.Tt)({ route: params.route, sessionKey: params.route.sessionKey }); if (dmRouteTarget && inboundLastRouteSessionKey === params.route.mainSessionKey && shouldUpdateMainLastRoute) updateLastRouteInBackground({ cfg: params.cfg, backgroundTasks: params.backgroundTasks, storeAgentId: params.route.agentId, sessionKey: params.route.mainSessionKey, channel: "whatsapp", to: dmRouteTarget, accountId: params.route.accountId, ctx: ctxPayload, warn: params.replyLogger.warn.bind(params.replyLogger) });else if (dmRouteTarget && inboundLastRouteSessionKey === params.route.mainSessionKey && pinnedMainDmRecipient) (0, _loggerU3s76KST.R)(`Skipping main-session last route update for ${dmRouteTarget} (pinned owner ${pinnedMainDmRecipient})`); const metaTask = (0, _piEmbeddedHelpersDmr3bcbH.bt)({ storePath, sessionKey: params.route.sessionKey, ctx: ctxPayload }).catch((err) => { params.replyLogger.warn({ error: (0, _sessionDwLPNct.n)(err), storePath, sessionKey: params.route.sessionKey }, "failed updating session meta"); }); trackBackgroundTask(params.backgroundTasks, metaTask); const { queuedFinal } = await (0, _dispatchF_Zbttj.Y)({ ctx: ctxPayload, cfg: params.cfg, replyResolver: params.replyResolver, dispatcherOptions: { ...prefixOptions, responsePrefix, onHeartbeatStrip: () => { if (!didLogHeartbeatStrip) { didLogHeartbeatStrip = true; (0, _loggerU3s76KST.R)("Stripped stray HEARTBEAT_OK token from web reply"); } }, deliver: async (payload, info) => { if (info.kind !== "final") return; await deliverWebReply({ replyResult: payload, msg: params.msg, mediaLocalRoots, maxMediaBytes: params.maxMediaBytes, textLimit, chunkMode, replyLogger: params.replyLogger, connectionId: params.connectionId, skipLog: false, tableMode }); didSendReply = true; const shouldLog = payload.text ? true : void 0; params.rememberSentText(payload.text, { combinedBody, combinedBodySessionKey: params.route.sessionKey, logVerboseMessage: shouldLog }); const fromDisplay = params.msg.chatType === "group" ? conversationId : params.msg.from ?? "unknown"; const hasMedia = Boolean(payload.mediaUrl || payload.mediaUrls?.length); whatsappOutboundLog.info(`Auto-replied to ${fromDisplay}${hasMedia ? " (media)" : ""}`); if ((0, _loggerU3s76KST.B)()) { const preview = payload.text != null ? elide(payload.text, 400) : ""; whatsappOutboundLog.debug(`Reply body: ${preview}${hasMedia ? " (media)" : ""}`); } }, onError: (err, info) => { const label = info.kind === "tool" ? "tool update" : info.kind === "block" ? "block update" : "auto-reply"; whatsappOutboundLog.error(`Failed sending web ${label} to ${params.msg.from ?? conversationId}: ${(0, _sessionDwLPNct.n)(err)}`); }, onReplyStart: params.msg.sendComposing }, replyOptions: { disableBlockStreaming: true, onModelSelected } }); if (!queuedFinal) { if (shouldClearGroupHistory) params.groupHistories.set(params.groupHistoryKey, []); (0, _loggerU3s76KST.R)("Skipping auto-reply: silent token or no text/media returned from resolver"); return false; } if (shouldClearGroupHistory) params.groupHistories.set(params.groupHistoryKey, []); return didSendReply; } //#endregion //#region src/web/auto-reply/monitor/on-message.ts function createWebOnMessageHandler(params) { const processForRoute = async (msg, route, groupHistoryKey, opts) => processMessage({ cfg: params.cfg, msg, route, groupHistoryKey, groupHistories: params.groupHistories, groupMemberNames: params.groupMemberNames, connectionId: params.connectionId, verbose: params.verbose, maxMediaBytes: params.maxMediaBytes, replyResolver: params.replyResolver, replyLogger: params.replyLogger, backgroundTasks: params.backgroundTasks, rememberSentText: params.echoTracker.rememberText, echoHas: params.echoTracker.has, echoForget: params.echoTracker.forget, buildCombinedEchoKey: params.echoTracker.buildCombinedKey, groupHistory: opts?.groupHistory, suppressGroupHistoryClear: opts?.suppressGroupHistoryClear }); return async (msg) => { const conversationId = msg.conversationId ?? msg.from; const peerId = resolvePeerId(msg); const route = (0, _dispatchF_Zbttj.wt)({ cfg: (0, _configDiiPndBn.i)(), channel: "whatsapp", accountId: msg.accountId, peer: { kind: msg.chatType === "group" ? "group" : "direct", id: peerId } }); const groupHistoryKey = msg.chatType === "group" ? (0, _runWithConcurrency2ga3CMk.et)({ channel: "whatsapp", accountId: route.accountId, peerKind: "group", peerId }) : route.sessionKey; if (msg.from === msg.to) (0, _loggerU3s76KST.R)(`📱 Same-phone mode detected (from === to: ${msg.from})`); if (params.echoTracker.has(msg.body)) { (0, _loggerU3s76KST.R)("Skipping auto-reply: detected echo (message matches recently sent text)"); params.echoTracker.forget(msg.body); return; } if (msg.chatType === "group") { const metaCtx = { From: msg.from, To: msg.to, SessionKey: route.sessionKey, AccountId: route.accountId, ChatType: msg.chatType, ConversationLabel: conversationId, GroupSubject: msg.groupSubject, SenderName: msg.senderName, SenderId: msg.senderJid?.trim() || msg.senderE164, SenderE164: msg.senderE164, Provider: "whatsapp", Surface: "whatsapp", OriginatingChannel: "whatsapp", OriginatingTo: conversationId }; updateLastRouteInBackground({ cfg: params.cfg, backgroundTasks: params.backgroundTasks, storeAgentId: route.agentId, sessionKey: route.sessionKey, channel: "whatsapp", to: conversationId, accountId: route.accountId, ctx: metaCtx, warn: params.replyLogger.warn.bind(params.replyLogger) }); if (!applyGroupGating({ cfg: params.cfg, msg, conversationId, groupHistoryKey, agentId: route.agentId, sessionKey: route.sessionKey, baseMentionConfig: params.baseMentionConfig, authDir: params.account.authDir, groupHistories: params.groupHistories, groupHistoryLimit: params.groupHistoryLimit, groupMemberNames: params.groupMemberNames, logVerbose: _loggerU3s76KST.R, replyLogger: params.replyLogger }).shouldProcess) return; } else if (!msg.senderE164 && peerId && peerId.startsWith("+")) msg.senderE164 = (0, _loggerU3s76KST.C)(peerId) ?? msg.senderE164; if (await maybeBroadcastMessage({ cfg: params.cfg, msg, peerId, route, groupHistoryKey, groupHistories: params.groupHistories, processMessage: processForRoute })) return; await processForRoute(msg, route, groupHistoryKey); }; } //#endregion //#region src/web/auto-reply/monitor.ts function isNonRetryableWebCloseStatus(statusCode) { return statusCode === 440; } async function monitorWebChannel(verbose, listenerFactory = monitorWebInbox, keepAlive = true, replyResolver = _dispatchF_Zbttj.r, runtime = _loggerU3s76KST.l, abortSignal, tuning = {}) { const runId = newConnectionId(); const replyLogger = (0, _loggerU3s76KST.K)({ module: "web-auto-reply", runId }); const heartbeatLogger = (0, _loggerU3s76KST.K)({ module: "web-heartbeat", runId }); const reconnectLogger = (0, _loggerU3s76KST.K)({ module: "web-reconnect", runId }); const status = { running: true, connected: false, reconnectAttempts: 0, lastConnectedAt: null, lastDisconnect: null, lastMessageAt: null, lastEventAt: null, lastError: null }; const emitStatus = () => { tuning.statusSink?.({ ...status, lastDisconnect: status.lastDisconnect ? { ...status.lastDisconnect } : null }); }; emitStatus(); const baseCfg = (0, _configDiiPndBn.i)(); const account = (0, _accountsBFBjizxh.i)({ cfg: baseCfg, accountId: tuning.accountId }); const cfg = { ...baseCfg, channels: { ...baseCfg.channels, whatsapp: { ...baseCfg.channels?.whatsapp, ackReaction: account.ackReaction, messagePrefix: account.messagePrefix, allowFrom: account.allowFrom, groupAllowFrom: account.groupAllowFrom, groupPolicy: account.groupPolicy, textChunkLimit: account.textChunkLimit, chunkMode: account.chunkMode, mediaMaxMb: account.mediaMaxMb, blockStreaming: account.blockStreaming, groups: account.groups } } }; const maxMediaBytes = (0, _accountsBFBjizxh.o)(account); const heartbeatSeconds = resolveHeartbeatSeconds(cfg, tuning.heartbeatSeconds); const reconnectPolicy = resolveReconnectPolicy(cfg, tuning.reconnect); const baseMentionConfig = buildMentionConfig(cfg); const groupHistoryLimit = cfg.channels?.whatsapp?.accounts?.[tuning.accountId ?? ""]?.historyLimit ?? cfg.channels?.whatsapp?.historyLimit ?? cfg.messages?.groupChat?.historyLimit ?? 50; const groupHistories = /* @__PURE__ */new Map(); const groupMemberNames = /* @__PURE__ */new Map(); const echoTracker = createEchoTracker({ maxItems: 100, logVerbose: _loggerU3s76KST.R }); const sleep = tuning.sleep ?? ((ms, signal) => (0, _dispatchF_Zbttj.at)(ms, signal ?? abortSignal)); const stopRequested = () => abortSignal?.aborted === true; const abortPromise = abortSignal && new Promise((resolve) => abortSignal.addEventListener("abort", () => resolve("aborted"), { once: true })); const currentMaxListeners = process.getMaxListeners?.() ?? 10; if (process.setMaxListeners && currentMaxListeners < 50) process.setMaxListeners(50); let sigintStop = false; const handleSigint = () => { sigintStop = true; }; process.once("SIGINT", handleSigint); let reconnectAttempts = 0; while (true) { if (stopRequested()) break; const connectionId = newConnectionId(); const startedAt = Date.now(); let heartbeat = null; let watchdogTimer = null; let lastMessageAt = null; let handledMessages = 0; let unregisterUnhandled = null; const MESSAGE_TIMEOUT_MS = tuning.messageTimeoutMs ?? 1800 * 1e3; const WATCHDOG_CHECK_MS = tuning.watchdogCheckMs ?? 60 * 1e3; const backgroundTasks = /* @__PURE__ */new Set(); const onMessage = createWebOnMessageHandler({ cfg, verbose, connectionId, maxMediaBytes, groupHistoryLimit, groupHistories, groupMemberNames, echoTracker, backgroundTasks, replyResolver: replyResolver ?? _dispatchF_Zbttj.r, replyLogger, baseMentionConfig, account }); const inboundDebounceMs = (0, _dispatchF_Zbttj.Q)({ cfg, channel: "whatsapp" }); const shouldDebounce = (msg) => { if (msg.mediaPath || msg.mediaType) return false; if (msg.location) return false; if (msg.replyToId || msg.replyToBody) return false; return !(0, _dispatchF_Zbttj.$)(msg.body, cfg); }; const listener = await (listenerFactory ?? monitorWebInbox)({ verbose, accountId: account.accountId, authDir: account.authDir, mediaMaxMb: account.mediaMaxMb, sendReadReceipts: account.sendReadReceipts, debounceMs: inboundDebounceMs, shouldDebounce, onMessage: async (msg) => { handledMessages += 1; lastMessageAt = Date.now(); status.lastMessageAt = lastMessageAt; status.lastEventAt = lastMessageAt; emitStatus(); await onMessage(msg); } }); Object.assign(status, (0, _dispatchF_Zbttj.E)()); status.lastError = null; emitStatus(); const { e164: selfE164 } = (0, _accountsBFBjizxh.m)(account.authDir); const connectRoute = (0, _dispatchF_Zbttj.wt)({ cfg, channel: "whatsapp", accountId: account.accountId }); (0, _dispatchF_Zbttj.ht)(`WhatsApp gateway connected${selfE164 ? ` as ${selfE164}` : ""}.`, { sessionKey: connectRoute.sessionKey }); (0, _activeListenerCMb_0BEz.r)(account.accountId, listener); unregisterUnhandled = (0, _audioTranscriptionRunnerC72KQQVh.p)((reason) => { if (!isLikelyWhatsAppCryptoError(reason)) return false; const errorStr = (0, _sessionDwLPNct.n)(reason); reconnectLogger.warn({ connectionId, error: errorStr }, "web reconnect: unhandled rejection from WhatsApp socket; forcing reconnect"); listener.signalClose?.({ status: 499, isLoggedOut: false, error: reason }); return true; }); const closeListener = async () => { (0, _activeListenerCMb_0BEz.r)(account.accountId, null); if (unregisterUnhandled) { unregisterUnhandled(); unregisterUnhandled = null; } if (heartbeat) clearInterval(heartbeat); if (watchdogTimer) clearInterval(watchdogTimer); if (backgroundTasks.size > 0) { await Promise.allSettled(backgroundTasks); backgroundTasks.clear(); } try { await listener.close(); } catch (err) { (0, _loggerU3s76KST.R)(`Socket close failed: ${(0, _sessionDwLPNct.n)(err)}`); } }; if (keepAlive) { heartbeat = setInterval(() => { const authAgeMs = (0, _accountsBFBjizxh.c)(account.authDir); const minutesSinceLastMessage = lastMessageAt ? Math.floor((Date.now() - lastMessageAt) / 6e4) : null; const logData = { connectionId, reconnectAttempts, messagesHandled: handledMessages, lastMessageAt, authAgeMs, uptimeMs: Date.now() - startedAt, ...(minutesSinceLastMessage !== null && minutesSinceLastMessage > 30 ? { minutesSinceLastMessage } : {}) }; if (minutesSinceLastMessage && minutesSinceLastMessage > 30) heartbeatLogger.warn(logData, "⚠️ web gateway heartbeat - no messages in 30+ minutes");else heartbeatLogger.info(logData, "web gateway heartbeat"); }, heartbeatSeconds * 1e3); watchdogTimer = setInterval(() => { if (!lastMessageAt) return; const timeSinceLastMessage = Date.now() - lastMessageAt; if (timeSinceLastMessage <= MESSAGE_TIMEOUT_MS) return; const minutesSinceLastMessage = Math.floor(timeSinceLastMessage / 6e4); heartbeatLogger.warn({ connectionId, minutesSinceLastMessage, lastMessageAt: new Date(lastMessageAt), messagesHandled: handledMessages }, "Message timeout detected - forcing reconnect"); whatsappHeartbeatLog.warn(`No messages received in ${minutesSinceLastMessage}m - restarting connection`); closeListener().catch((err) => { (0, _loggerU3s76KST.R)(`Close listener failed: ${(0, _sessionDwLPNct.n)(err)}`); }); listener.signalClose?.({ status: 499, isLoggedOut: false, error: "watchdog-timeout" }); }, WATCHDOG_CHECK_MS); } whatsappLog.info("Listening for personal WhatsApp inbound messages."); if (process.stdout.isTTY || process.stderr.isTTY) whatsappLog.raw("Ctrl+C to stop."); if (!keepAlive) { await closeListener(); process.removeListener("SIGINT", handleSigint); return; } const reason = await Promise.race([listener.onClose?.catch((err) => { reconnectLogger.error({ error: (0, _sessionDwLPNct.n)(err) }, "listener.onClose rejected"); return { status: 500, isLoggedOut: false, error: err }; }) ?? waitForever(), abortPromise ?? waitForever()]); if (Date.now() - startedAt > heartbeatSeconds * 1e3) reconnectAttempts = 0; status.reconnectAttempts = reconnectAttempts; emitStatus(); if (stopRequested() || sigintStop || reason === "aborted") { await closeListener(); break; } const statusCode = (typeof reason === "object" && reason && "status" in reason ? reason.status : void 0) ?? "unknown"; const loggedOut = typeof reason === "object" && reason && "isLoggedOut" in reason && reason.isLoggedOut; const errorStr = (0, _sessionDwLPNct.n)(reason); status.connected = false; status.lastEventAt = Date.now(); status.lastDisconnect = { at: status.lastEventAt, status: typeof statusCode === "number" ? statusCode : void 0, error: errorStr, loggedOut: Boolean(loggedOut) }; status.lastError = errorStr; status.reconnectAttempts = reconnectAttempts; emitStatus(); reconnectLogger.info({ connectionId, status: statusCode, loggedOut, reconnectAttempts, error: errorStr }, "web reconnect: connection closed"); (0, _dispatchF_Zbttj.ht)(`WhatsApp gateway disconnected (status ${statusCode ?? "unknown"})`, { sessionKey: connectRoute.sessionKey }); if (loggedOut) { runtime.error(`WhatsApp session logged out. Run \`${(0, _configDiiPndBn.Br)("openclaw channels login --channel web")}\` to relink.`); await closeListener(); break; } if (isNonRetryableWebCloseStatus(statusCode)) { reconnectLogger.warn({ connectionId, status: statusCode, error: errorStr }, "web reconnect: non-retryable close status; stopping monitor"); runtime.error(`WhatsApp Web connection closed (status ${statusCode}: session conflict). Resolve conflicting WhatsApp Web sessions, then relink with \`${(0, _configDiiPndBn.Br)("openclaw channels login --channel web")}\`. Stopping web monitoring.`); await closeListener(); break; } reconnectAttempts += 1; status.reconnectAttempts = reconnectAttempts; emitStatus(); if (reconnectPolicy.maxAttempts > 0 && reconnectAttempts >= reconnectPolicy.maxAttempts) { reconnectLogger.warn({ connectionId, status: statusCode, reconnectAttempts, maxAttempts: reconnectPolicy.maxAttempts }, "web reconnect: max attempts reached; continuing in degraded mode"); runtime.error(`WhatsApp Web reconnect: max attempts reached (${reconnectAttempts}/${reconnectPolicy.maxAttempts}). Stopping web monitoring.`); await closeListener(); break; } const delay = (0, _dispatchF_Zbttj.it)(reconnectPolicy, reconnectAttempts); reconnectLogger.info({ connectionId, status: statusCode, reconnectAttempts, maxAttempts: reconnectPolicy.maxAttempts || "unlimited", delayMs: delay }, "web reconnect: scheduling retry"); runtime.error(`WhatsApp Web connection closed (status ${statusCode}). Retry ${reconnectAttempts}/${reconnectPolicy.maxAttempts || "∞"} in ${(0, _dispatchF_Zbttj.mt)(delay)}… (${errorStr})`); await closeListener(); try { await sleep(delay, abortSignal); } catch { break; } } status.running = false; status.connected = false; status.lastEventAt = Date.now(); emitStatus(); process.removeListener("SIGINT", handleSigint); } //#endregion /* v9-bca62725d6528c8f */