UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

716 lines (715 loc) 26 kB
import { a as normalizeLowercaseStringOrEmpty, s as normalizeOptionalLowercaseString } from "./string-coerce-mnp54Vah.js"; import { l as normalizeStringEntries } from "./string-normalization-WNUDCpXX.js"; import { t as KeyedAsyncQueue } from "./keyed-async-queue-Ckmdd15z.js"; import { i as deliverTextOrMediaReply, m as resolveSendableOutboundReplyParts } from "./reply-payload-D-VMfpYI.js"; import { a as warnMissingProviderGroupPolicyFallbackOnce, i as resolveOpenProviderRuntimeGroupPolicy, r as resolveDefaultGroupPolicy } from "./runtime-group-policy-CSD1Is8G.js"; import "./string-coerce-runtime-CEGJWkQ_.js"; import "./core-DSxVv-v1.js"; import { n as resolveInboundMentionDecision, t as implicitMentionKindWhen } from "./mention-gating-3P8aSD7o.js"; import "./history-6XlO5h6s.js"; import { t as createChannelHistoryWindow } from "./history-window-B0hyPVKC.js"; import { f as summarizeMapping, u as mergeAllowlist } from "./allow-from-DCaV3hzI.js"; import { n as isDangerousNameMatchingEnabled } from "./dangerous-name-matching-D4QRC91G.js"; import "./reply-history-Y9HXucO9.js"; import "./dangerous-name-runtime-zP2lSdMQ.js"; import { o as createDeferred } from "./extension-shared-B8fkO3TV.js"; import "./channel-inbound-bl7VmTdr.js"; import { i as resolveStableChannelMessageIngress } from "./message-access-mwxdTUGC.js"; import "./channel-ingress-runtime-rGltVkKC.js"; import { n as createChannelPairingController } from "./channel-pairing-Bk6_JhTm.js"; import { c as isZalouserGroupEntryAllowed, i as resolveZalouserMessageSid, o as buildZalouserGroupCandidates, r as formatZalouserMessageSidFull, s as findZalouserGroupEntry, t as getZalouserRuntime } from "./runtime-CGCemWjT.js"; import { o as listZaloGroups, r as listZaloFriends, u as resolveZaloGroupContext, v as startZaloListener } from "./zalo-js-B1zE_Wyq.js"; import { i as sendMessageZalouser, o as sendSeenZalouser, s as sendTypingZalouser, t as sendDeliveredZalouser } from "./send-duv5hIXL.js"; //#region extensions/zalouser/src/monitor.ts const ZALOUSER_TEXT_LIMIT = 2e3; function buildNameIndex(items, nameFn) { const index = /* @__PURE__ */ new Map(); for (const item of items) { const name = normalizeOptionalLowercaseString(nameFn(item)); if (!name) continue; const list = index.get(name) ?? []; list.push(item); index.set(name, list); } return index; } function resolveUserAllowlistEntries(entries, byName) { const additions = []; const mapping = []; const unresolved = []; for (const entry of entries) { if (/^\d+$/.test(entry)) { additions.push(entry); continue; } const id = (byName.get(normalizeLowercaseStringOrEmpty(entry)) ?? [])[0]?.userId; if (id) { additions.push(id); mapping.push(`${entry}->${id}`); } else unresolved.push(entry); } return { additions, mapping, unresolved }; } function normalizeZalouserAllowEntry(entry) { return entry.replace(/^(zalouser|zlu):/i, "").trim(); } function normalizeZalouserSender(value) { return normalizeOptionalLowercaseString(normalizeZalouserAllowEntry(value)) || null; } function resolveInboundQueueKey(message) { const threadId = message.threadId?.trim() || "unknown"; if (message.isGroup) return `group:${threadId}`; return `direct:${message.senderId?.trim() || threadId}`; } function resolveZalouserDmSessionScope(config) { const configured = config.session?.dmScope; return configured === "main" || !configured ? "per-channel-peer" : configured; } function resolveZalouserRouteAccess(params) { if (params.groupPolicy === "disabled") return { allowed: false, reason: "disabled" }; if (params.matched && params.enabled === false) return { allowed: false, reason: "route_disabled" }; if (params.groupPolicy !== "allowlist") return { allowed: true }; if (!params.configured) return { allowed: false, reason: "empty_allowlist" }; return params.matched ? { allowed: true } : { allowed: false, reason: "route_not_allowlisted" }; } function senderScopedZalouserGroupPolicy(params) { if (params.groupPolicy === "disabled") return "disabled"; return params.groupAllowFrom.length > 0 ? "allowlist" : "open"; } function resolveZalouserInboundSessionKey(params) { if (params.isGroup) return params.route.sessionKey; const directSessionKey = normalizeLowercaseStringOrEmpty(params.core.channel.routing.buildAgentSessionKey({ agentId: params.route.agentId, channel: "zalouser", accountId: params.route.accountId, peer: { kind: "direct", id: params.senderId }, dmScope: resolveZalouserDmSessionScope(params.config), identityLinks: params.config.session?.identityLinks })); const legacySessionKey = normalizeLowercaseStringOrEmpty(params.core.channel.routing.buildAgentSessionKey({ agentId: params.route.agentId, channel: "zalouser", accountId: params.route.accountId, peer: { kind: "group", id: params.senderId } })); const hasDirectSession = params.core.channel.session.readSessionUpdatedAt({ storePath: params.storePath, sessionKey: directSessionKey }) !== void 0; return params.core.channel.session.readSessionUpdatedAt({ storePath: params.storePath, sessionKey: legacySessionKey }) !== void 0 && !hasDirectSession ? legacySessionKey : directSessionKey; } function logVerbose(core, runtime, message) { if (core.logging.shouldLogVerbose()) runtime.log(`[zalouser] ${message}`); } function resolveGroupRequireMention(params) { const entry = findZalouserGroupEntry(params.groups ?? {}, buildZalouserGroupCandidates({ groupId: params.groupId, groupName: params.groupName, includeGroupIdAlias: true, includeWildcard: true, allowNameMatching: params.allowNameMatching })); if (typeof entry?.requireMention === "boolean") return entry.requireMention; return true; } async function sendZalouserDeliveryAcks(params) { await sendDeliveredZalouser({ profile: params.profile, isGroup: params.isGroup, message: params.message, isSeen: true }); await sendSeenZalouser({ profile: params.profile, isGroup: params.isGroup, message: params.message }); } async function processMessage(message, account, config, core, runtime, historyState, statusSink) { const pairing = createChannelPairingController({ core, channel: "zalouser", accountId: account.accountId }); const rawBody = message.content?.trim(); if (!rawBody) return; const commandBody = message.commandContent?.trim() || rawBody; const isGroup = message.isGroup; const chatId = message.threadId; const senderId = message.senderId?.trim(); if (!senderId) { logVerbose(core, runtime, `zalouser: drop message ${chatId} (missing senderId)`); return; } const senderName = message.senderName ?? ""; const configuredGroupName = message.groupName?.trim() || ""; const groupContext = isGroup && !configuredGroupName ? await resolveZaloGroupContext(account.profile, chatId).catch((err) => { logVerbose(core, runtime, `zalouser: group context lookup failed for ${chatId}: ${String(err)}`); return null; }) : null; const groupName = configuredGroupName || groupContext?.name?.trim() || ""; const groupMembers = groupContext?.members?.slice(0, 20).join(", ") || void 0; if (message.eventMessage) try { await sendZalouserDeliveryAcks({ profile: account.profile, isGroup, message: message.eventMessage }); } catch (err) { logVerbose(core, runtime, `zalouser: delivery/seen ack failed for ${chatId}: ${String(err)}`); } const defaultGroupPolicy = resolveDefaultGroupPolicy(config); const { groupPolicy, providerMissingFallbackApplied } = resolveOpenProviderRuntimeGroupPolicy({ providerConfigPresent: config.channels?.zalouser !== void 0, groupPolicy: account.config.groupPolicy, defaultGroupPolicy }); warnMissingProviderGroupPolicyFallbackOnce({ providerMissingFallbackApplied, providerKey: "zalouser", accountId: account.accountId, log: (entry) => logVerbose(core, runtime, entry) }); const groups = account.config.groups ?? {}; const routeAllowlistConfigured = Object.keys(groups).length > 0; const allowNameMatching = isDangerousNameMatchingEnabled(account.config); if (isGroup) { const groupEntry = findZalouserGroupEntry(groups, buildZalouserGroupCandidates({ groupId: chatId, groupName, includeGroupIdAlias: true, includeWildcard: true, allowNameMatching })); const routeAccess = resolveZalouserRouteAccess({ groupPolicy, configured: routeAllowlistConfigured, matched: Boolean(groupEntry), enabled: isZalouserGroupEntryAllowed(groupEntry) }); if (!routeAccess.allowed) { if (routeAccess.reason === "disabled") logVerbose(core, runtime, `zalouser: drop group ${chatId} (groupPolicy=disabled)`); else if (routeAccess.reason === "empty_allowlist") logVerbose(core, runtime, `zalouser: drop group ${chatId} (groupPolicy=allowlist, no allowlist)`); else if (routeAccess.reason === "route_not_allowlisted") logVerbose(core, runtime, `zalouser: drop group ${chatId} (not allowlisted)`); else if (routeAccess.reason === "route_disabled") logVerbose(core, runtime, `zalouser: drop group ${chatId} (group disabled)`); return; } } const dmPolicy = account.config.dmPolicy ?? "pairing"; const configAllowFrom = normalizeStringEntries(account.config.allowFrom); const configGroupAllowFrom = normalizeStringEntries(account.config.groupAllowFrom); const senderGroupPolicy = routeAllowlistConfigured && configGroupAllowFrom.length === 0 ? groupPolicy : senderScopedZalouserGroupPolicy({ groupPolicy, groupAllowFrom: configGroupAllowFrom }); const shouldComputeCommandAuth = core.channel.commands.shouldComputeCommandAuthorized(commandBody, config); const accessDecision = await resolveStableChannelMessageIngress({ channelId: "zalouser", accountId: account.accountId, identity: { normalize: normalizeZalouserSender, sensitivity: "pii", entryIdPrefix: "zalouser-entry" }, cfg: config, readStoreAllowFrom: async () => await pairing.readAllowFromStore(), subject: { stableId: senderId }, conversation: { kind: isGroup ? "group" : "direct", id: isGroup ? "group" : senderId }, dmPolicy, groupPolicy: senderGroupPolicy, policy: { groupAllowFromFallbackToAllowFrom: false }, allowFrom: configAllowFrom, groupAllowFrom: configGroupAllowFrom, command: shouldComputeCommandAuth ? { directGroupAllowFrom: "effective", commandGroupAllowFromFallbackToAllowFrom: true } : void 0 }); if (isGroup && accessDecision.senderAccess.decision !== "allow") { if (accessDecision.senderAccess.reasonCode === "group_policy_empty_allowlist") logVerbose(core, runtime, "Blocked zalouser group message (no group allowlist)"); else if (accessDecision.senderAccess.reasonCode === "group_policy_not_allowlisted") logVerbose(core, runtime, `Blocked zalouser sender ${senderId} (not in groupAllowFrom/allowFrom)`); return; } if (!isGroup && accessDecision.senderAccess.decision !== "allow") { if (accessDecision.senderAccess.decision === "pairing") { await pairing.issueChallenge({ senderId, senderIdLine: `Your Zalo user id: ${senderId}`, meta: { name: senderName || void 0 }, onCreated: () => { logVerbose(core, runtime, `zalouser pairing request sender=${senderId}`); }, sendPairingReply: async (text) => { await sendMessageZalouser(chatId, text, { profile: account.profile }); statusSink?.({ lastOutboundAt: Date.now() }); }, onReplyError: (err) => { logVerbose(core, runtime, `zalouser pairing reply failed for ${senderId}: ${String(err)}`); } }); return; } if (accessDecision.senderAccess.reasonCode === "dm_policy_disabled") logVerbose(core, runtime, `Blocked zalouser DM from ${senderId} (dmPolicy=disabled)`); else logVerbose(core, runtime, `Blocked unauthorized zalouser sender ${senderId} (dmPolicy=${dmPolicy})`); return; } const commandAuthorized = accessDecision.commandAccess.requested ? accessDecision.commandAccess.authorized : void 0; const hasControlCommand = core.channel.commands.isControlCommandMessage(commandBody, config); if (isGroup && hasControlCommand && commandAuthorized !== true) { logVerbose(core, runtime, `zalouser: drop control command from unauthorized sender ${senderId}`); return; } const peer = isGroup ? { kind: "group", id: chatId } : { kind: "direct", id: senderId }; const route = core.channel.routing.resolveAgentRoute({ cfg: config, channel: "zalouser", accountId: account.accountId, peer: { kind: peer.kind, id: peer.id } }); const historyKey = isGroup ? route.sessionKey : void 0; const channelHistory = createChannelHistoryWindow({ historyMap: historyState.groupHistories }); const requireMention = isGroup ? resolveGroupRequireMention({ groupId: chatId, groupName, groups, allowNameMatching }) : false; const mentionRegexes = core.channel.mentions.buildMentionRegexes(config, route.agentId); const explicitMention = { hasAnyMention: message.hasAnyMention === true, isExplicitlyMentioned: message.wasExplicitlyMentioned === true, canResolveExplicit: message.canResolveExplicitMention === true }; const wasMentioned = isGroup ? core.channel.mentions.matchesMentionWithExplicit({ text: rawBody, mentionRegexes, explicit: explicitMention }) : true; const canDetectMention = mentionRegexes.length > 0 || explicitMention.canResolveExplicit; const mentionDecision = resolveInboundMentionDecision({ facts: { canDetectMention, wasMentioned, hasAnyMention: explicitMention.hasAnyMention, implicitMentionKinds: implicitMentionKindWhen("quoted_bot", message.implicitMention === true) }, policy: { isGroup, requireMention, allowTextCommands: core.channel.commands.shouldHandleTextCommands({ cfg: config, surface: "zalouser" }), hasControlCommand, commandAuthorized: commandAuthorized === true } }); if (isGroup && requireMention && !canDetectMention && !mentionDecision.effectiveWasMentioned) { runtime.error?.(`[${account.accountId}] zalouser mention required but detection unavailable (missing mention regexes and bot self id); dropping group ${chatId}`); return; } if (isGroup && mentionDecision.shouldSkip) { channelHistory.record({ historyKey: historyKey ?? "", limit: historyState.historyLimit, entry: historyKey && rawBody ? { sender: senderName || senderId, body: rawBody, timestamp: message.timestampMs, messageId: resolveZalouserMessageSid({ msgId: message.msgId, cliMsgId: message.cliMsgId, fallback: `${message.timestampMs}` }) } : null }); logVerbose(core, runtime, `zalouser: skip group ${chatId} (mention required, not mentioned)`); return; } const fromLabel = isGroup ? groupName || `group:${chatId}` : senderName || `user:${senderId}`; const storePath = core.channel.session.resolveStorePath(config.session?.store, { agentId: route.agentId }); const inboundSessionKey = resolveZalouserInboundSessionKey({ core, config, route, storePath, isGroup, senderId }); const envelopeOptions = core.channel.reply.resolveEnvelopeFormatOptions(config); const previousTimestamp = core.channel.session.readSessionUpdatedAt({ storePath, sessionKey: inboundSessionKey }); const body = core.channel.reply.formatAgentEnvelope({ channel: "Zalo Personal", from: fromLabel, timestamp: message.timestampMs, previousTimestamp, envelope: envelopeOptions, body: rawBody }); const combinedBody = isGroup && historyKey ? channelHistory.buildPendingContext({ historyKey, limit: historyState.historyLimit, currentMessage: body, formatEntry: (entry) => core.channel.reply.formatAgentEnvelope({ channel: "Zalo Personal", from: fromLabel, timestamp: entry.timestamp, envelope: envelopeOptions, body: `${entry.sender}: ${entry.body}${entry.messageId ? ` [id:${entry.messageId}]` : ""}` }) }) : body; const inboundHistory = isGroup && historyKey && historyState.historyLimit > 0 ? channelHistory.buildInboundHistory({ historyKey, limit: historyState.historyLimit }) : void 0; const normalizedTo = isGroup ? `zalouser:group:${chatId}` : `zalouser:${chatId}`; const messageSid = resolveZalouserMessageSid({ msgId: message.msgId, cliMsgId: message.cliMsgId, fallback: `${message.timestampMs}` }); const messageSidFull = formatZalouserMessageSidFull({ msgId: message.msgId, cliMsgId: message.cliMsgId }); const ctxPayload = core.channel.inbound.buildContext({ channel: "zalouser", accountId: route.accountId, messageId: messageSid, messageIdFull: messageSidFull, timestamp: message.timestampMs, from: isGroup ? `zalouser:group:${chatId}` : `zalouser:${senderId}`, sender: { id: senderId, name: senderName || void 0 }, conversation: { kind: isGroup ? "group" : "direct", id: chatId, label: fromLabel }, route: { agentId: route.agentId, accountId: route.accountId, routeSessionKey: route.sessionKey, dispatchSessionKey: inboundSessionKey }, reply: { to: normalizedTo, originatingTo: normalizedTo }, message: { body: combinedBody, bodyForAgent: rawBody, rawBody, commandBody, inboundHistory }, extra: { BodyForCommands: commandBody, GroupSubject: isGroup ? groupName || void 0 : void 0, GroupChannel: isGroup ? groupName || void 0 : void 0, GroupMembers: isGroup ? groupMembers : void 0, WasMentioned: isGroup ? mentionDecision.effectiveWasMentioned : void 0, CommandAuthorized: commandAuthorized, ReplyToId: message.quotedGlobalMsgId || void 0, ReplyToBody: message.quotedBody || void 0, ReplyToIsQuote: message.quotedGlobalMsgId ? true : void 0 } }); await core.channel.inbound.dispatchReply({ channel: "zalouser", accountId: account.accountId, cfg: config, agentId: route.agentId, routeSessionKey: route.sessionKey, storePath, ctxPayload, recordInboundSession: core.channel.session.recordInboundSession, dispatchReplyWithBufferedBlockDispatcher: core.channel.reply.dispatchReplyWithBufferedBlockDispatcher, delivery: { preparePayload: (payload) => { if (payload.text === void 0) return payload; return { ...payload, text: core.channel.text.convertMarkdownTables(payload.text, core.channel.text.resolveMarkdownTableMode({ cfg: config, channel: "zalouser", accountId: account.accountId })) }; }, durable: () => ({ to: normalizedTo }), deliver: async (payload) => { return await deliverZalouserReply({ payload, profile: account.profile, chatId, isGroup, runtime, core, config, accountId: account.accountId, tableMode: "off" }); }, onDelivered: (_payload, _info, result) => { if (result?.visibleReplySent !== false) statusSink?.({ lastOutboundAt: Date.now() }); }, onError: (err, info) => { runtime.error(`[${account.accountId}] Zalouser ${info.kind} reply failed: ${String(err)}`); } }, replyPipeline: { typing: { start: async () => { await sendTypingZalouser(chatId, { profile: account.profile, isGroup }); }, onStartError: (err) => { runtime.error?.(`[${account.accountId}] zalouser typing start failed for ${chatId}: ${String(err)}`); logVerbose(core, runtime, `zalouser typing failed for ${chatId}: ${String(err)}`); } } }, record: { onRecordError: (err) => { runtime.error?.(`zalouser: failed updating session meta: ${String(err)}`); } } }); if (isGroup && historyKey) channelHistory.clear({ historyKey, limit: historyState.historyLimit }); } async function deliverZalouserReply(params) { const { payload, profile, chatId, isGroup, runtime, core, config, accountId } = params; const tableMode = params.tableMode ?? "code"; let visibleReplySent = false; const reply = resolveSendableOutboundReplyParts(payload, { text: core.channel.text.convertMarkdownTables(payload.text ?? "", tableMode) }); const chunkMode = core.channel.text.resolveChunkMode(config, "zalouser", accountId); const textChunkLimit = core.channel.text.resolveTextChunkLimit(config, "zalouser", accountId, { fallbackLimit: ZALOUSER_TEXT_LIMIT }); await deliverTextOrMediaReply({ payload, text: reply.text, sendText: async (chunk) => { try { await sendMessageZalouser(chatId, chunk, { profile, isGroup, textMode: "markdown", textChunkMode: chunkMode, textChunkLimit }); visibleReplySent = true; } catch (err) { runtime.error(`Zalouser message send failed: ${String(err)}`); } }, sendMedia: async ({ mediaUrl, caption }) => { logVerbose(core, runtime, `Sending media to ${chatId}`); await sendMessageZalouser(chatId, caption ?? "", { profile, mediaUrl, isGroup, textMode: "markdown", textChunkMode: chunkMode, textChunkLimit }); visibleReplySent = true; }, onMediaError: (error) => { runtime.error(`Zalouser media send failed: ${error instanceof Error ? error.message : JSON.stringify(error)}`); } }); return { visibleReplySent }; } async function monitorZalouserProvider(options) { const { config } = options; let { account } = options; const { abortSignal, statusSink, runtime } = options; const core = getZalouserRuntime(); const inboundQueue = new KeyedAsyncQueue(); const historyLimit = Math.max(0, account.config.historyLimit ?? config.messages?.groupChat?.historyLimit ?? 50); const groupHistories = /* @__PURE__ */ new Map(); try { const profile = account.profile; const allowFromEntries = (account.config.allowFrom ?? []).map((entry) => normalizeZalouserAllowEntry(String(entry))).filter((entry) => entry && entry !== "*"); const groupAllowFromEntries = (account.config.groupAllowFrom ?? []).map((entry) => normalizeZalouserAllowEntry(String(entry))).filter((entry) => entry && entry !== "*"); const allowNameMatching = isDangerousNameMatchingEnabled(account.config); if (allowNameMatching && (allowFromEntries.length > 0 || groupAllowFromEntries.length > 0)) { const byName = buildNameIndex(await listZaloFriends(profile), (friend) => friend.displayName); if (allowFromEntries.length > 0) { const { additions, mapping, unresolved } = resolveUserAllowlistEntries(allowFromEntries, byName); const allowFrom = mergeAllowlist({ existing: account.config.allowFrom, additions }); account = { ...account, config: { ...account.config, allowFrom } }; summarizeMapping("zalouser users", mapping, unresolved, runtime); } if (groupAllowFromEntries.length > 0) { const { additions, mapping, unresolved } = resolveUserAllowlistEntries(groupAllowFromEntries, byName); const groupAllowFrom = mergeAllowlist({ existing: account.config.groupAllowFrom, additions }); account = { ...account, config: { ...account.config, groupAllowFrom } }; summarizeMapping("zalouser group users", mapping, unresolved, runtime); } } const groupsConfig = account.config.groups ?? {}; const groupKeys = Object.keys(groupsConfig).filter((key) => key !== "*"); if (allowNameMatching && groupKeys.length > 0) { const byName = buildNameIndex(await listZaloGroups(profile), (group) => group.name); const mapping = []; const unresolved = []; const nextGroups = { ...groupsConfig }; for (const entry of groupKeys) { const cleaned = normalizeZalouserAllowEntry(entry); if (/^\d+$/.test(cleaned)) { if (!nextGroups[cleaned]) nextGroups[cleaned] = groupsConfig[entry]; mapping.push(`${entry}→${cleaned}`); continue; } const id = (byName.get(normalizeLowercaseStringOrEmpty(cleaned)) ?? [])[0]?.groupId; if (id) { if (!nextGroups[id]) nextGroups[id] = groupsConfig[entry]; mapping.push(`${entry}→${id}`); } else unresolved.push(entry); } account = { ...account, config: { ...account.config, groups: nextGroups } }; summarizeMapping("zalouser groups", mapping, unresolved, runtime); } } catch (err) { runtime.log?.(`zalouser resolve failed; using config entries. ${String(err)}`); } let listenerStop = null; let stopped = false; const stop = () => { if (stopped) return; stopped = true; listenerStop?.(); listenerStop = null; }; let settled = false; const { promise: waitForExit, resolve: resolveRun, reject: rejectRun } = createDeferred(); const settleSuccess = () => { if (settled) return; settled = true; stop(); resolveRun(); }; const settleFailure = (error) => { if (settled) return; settled = true; stop(); rejectRun(error instanceof Error ? error : new Error(String(error))); }; const onAbort = () => { settleSuccess(); }; abortSignal.addEventListener("abort", onAbort, { once: true }); let listener; try { listener = await startZaloListener({ accountId: account.accountId, profile: account.profile, abortSignal, onMessage: (msg) => { if (stopped) return; logVerbose(core, runtime, `[${account.accountId}] inbound message`); statusSink?.({ lastInboundAt: Date.now() }); const queueKey = resolveInboundQueueKey(msg); inboundQueue.enqueue(queueKey, async () => { if (stopped || abortSignal.aborted) return; await processMessage(msg, account, config, core, runtime, { historyLimit, groupHistories }, statusSink); }).catch((err) => { runtime.error(`[${account.accountId}] Failed to process message: ${String(err)}`); }); }, onError: (err) => { if (stopped || abortSignal.aborted) return; runtime.error(`[${account.accountId}] Zalo listener error: ${String(err)}`); settleFailure(err); } }); } catch (error) { abortSignal.removeEventListener("abort", onAbort); throw error; } listenerStop = listener.stop; if (stopped) { listenerStop(); listenerStop = null; } if (abortSignal.aborted) settleSuccess(); try { await waitForExit; } finally { abortSignal.removeEventListener("abort", onAbort); } return { stop }; } //#endregion export { monitorZalouserProvider };