"use strict";Object.defineProperty(exports, "__esModule", { value: true });exports.t = monitorSignalProvider;var _stringCoerceBUSzWgUA = require("./string-coerce-BUSzWgUA.js"); var _utilsD5DtWkEu = require("./utils-D5DtWkEu.js"); var _stringNormalizationDeXXoUde = require("./string-normalization-DeXXoUde.js"); var _runtimeDf11Xt6R = require("./runtime-Df11Xt6R.js"); var _ioBYPX79V = require("./io-B-YPX79V.js"); var _globalsDQvpEApL = require("./globals-DQvpEApL.js"); var _internalHooksCEPnFgdQ = require("./internal-hooks-CEPnFgdQ.js"); var _pathsCrHqfnMp = require("./paths-CrHqfnMp.js"); var _identityLLtDOuch = require("./identity-LLtDOuch.js"); var _storeCw5RrVCw = require("./store-Cw5RrVCw.js"); var _backoffCo9E7aKq = require("./backoff-Co9E7aKq.js"); var _mimeCi7oOV2D = require("./mime-Ci7oOV2D.js"); var _base64Fcs4g96r = require("./base64-fcs4g96r.js"); var _replyPayloadCMyOxXD = require("./reply-payload-C-MyOxXD.js"); var _systemEventsBwxCjOwe = require("./system-events-BwxCjOwe.js"); var _chunkBJOSq3sp = require("./chunk-BJOSq3sp.js"); var _messageHookMappersCIPnjy6o = require("./message-hook-mappers-CIPnjy6o.js"); var _groupPolicyYqA7ta9g = require("./group-policy-YqA7ta9g.js"); var _storeCA7OW_2w = require("./store-CA7OW_2w.js"); var _resolveRouteC1CfLZm = require("./resolve-route-C1CfL-zm.js"); require("./text-runtime-3VPC1sGh.js"); require("./routing-Ewkev_pm.js"); var _commandDetectionDNYq9uM = require("./command-detection-DNYq9uM-.js"); var _dispatchCdc7z0Lu = require("./dispatch-Cdc7z0Lu.js"); var _inboundContextBM3S6N = require("./inbound-context-BM3S6N92.js"); var _envelopeL34d7rAH = require("./envelope-l34d7rAH.js"); var _mentionsBimfLHao = require("./mentions-BimfLHao.js"); var _commandGatingDYM2M_v = require("./command-gating-DYM2M_v6.js"); var _mentionGatingDUv0CJhz = require("./mention-gating-DUv0CJhz.js"); var _sessionBMgtCdKz = require("./session-BMgtCdKz.js"); var _pairingStoreEKDwEjkm = require("./pairing-store-EKDwEjkm.js"); var _runtimeGroupPolicyBUWwjQZ_ = require("./runtime-group-policy-BUWwjQZ_.js"); var _dmPolicySharedD_6CpFGk = require("./dm-policy-shared-D_6CpFGk.js"); var _historyC4oWdkP = require("./history-C4oWdkP5.js"); require("./reply-history-DF6Lm0Ia.js"); var _loggingSOwyEnNl = require("./logging-sOwyEnNl.js"); var _channelReplyPipelineCJZpAxBU = require("./channel-reply-pipeline-CJZpAxBU.js"); var _channelPairingRPdRacuP = require("./channel-pairing-rPdRacuP.js"); require("./runtime-env-DLaqJQah.js"); var _contextVisibilityYnM4GZpy = require("./context-visibility-YnM4GZpy.js"); var _contextVisibilityBpCxpfZ = require("./context-visibility-BpCxpfZ-.js"); require("./config-runtime-Hrpwdk2u.js"); require("./reply-runtime-B2AVTFFN.js"); var _transportReadyCIbz1r5V = require("./transport-ready-CIbz1r5V.js"); require("./infra-runtime-DENIjfhx.js"); require("./media-runtime-_5B9mz0A.js"); require("./conversation-runtime-Du8VNJpk.js"); require("./agent-runtime-Lvb71pCH.js"); require("./security-runtime-CEMag8SV.js"); require("./hook-runtime-DlfBzXYT.js"); require("./command-auth-BNI2Gxd-.js"); require("./channel-feedback-BOu7f9EX.js"); var _channelInboundB3GLEQYw = require("./channel-inbound-B3GLEQYw.js"); var _accountsDmKrW9vL = require("./accounts-DmKrW9vL.js"); var _identityDjM1QVic = require("./identity-DjM1QVic.js"); var _sendYvq8Fn7p = require("./send-Yvq8Fn7p.js"); var _clientDj2vAxFI = require("./client-Dj2vAxFI.js"); var _nodeChild_process = require("node:child_process"); //#region extensions/signal/src/daemon.ts function formatSignalDaemonExit(exit) { return `signal daemon exited (source=${exit.source} code=${exit.code ?? "null"} signal=${exit.signal ?? "null"})`; } function classifySignalCliLogLine(line) { const trimmed = line.trim(); if (!trimmed) return null; if (/\b(ERROR|WARN|WARNING)\b/.test(trimmed)) return "error"; if (/\b(FAILED|SEVERE|EXCEPTION)\b/i.test(trimmed)) return "error"; return "log"; } function bindSignalCliOutput(params) { params.stream?.on("data", (data) => { for (const line of data.toString().split(/\r?\n/)) { const kind = classifySignalCliLogLine(line); if (kind === "log") params.log(`signal-cli: ${line.trim()}`);else if (kind === "error") params.error(`signal-cli: ${line.trim()}`); } }); } function buildDaemonArgs(opts) { const args = []; if (opts.account) args.push("-a", opts.account); args.push("daemon"); args.push("--http", `${opts.httpHost}:${opts.httpPort}`); args.push("--no-receive-stdout"); if (opts.receiveMode) args.push("--receive-mode", opts.receiveMode); if (opts.ignoreAttachments) args.push("--ignore-attachments"); if (opts.ignoreStories) args.push("--ignore-stories"); if (opts.sendReadReceipts) args.push("--send-read-receipts"); return args; } function spawnSignalDaemon(opts) { const args = buildDaemonArgs(opts); const child = (0, _nodeChild_process.spawn)(opts.cliPath, args, { stdio: [ "ignore", "pipe", "pipe"] }); const log = opts.runtime?.log ?? (() => {}); const error = opts.runtime?.error ?? (() => {}); let exited = false; let settledExit = false; let resolveExit; const exitedPromise = new Promise((resolve) => { resolveExit = resolve; }); const settleExit = (value) => { if (settledExit) return; settledExit = true; exited = true; resolveExit(value); }; bindSignalCliOutput({ stream: child.stdout, log, error }); bindSignalCliOutput({ stream: child.stderr, log, error }); child.once("exit", (code, signal) => { settleExit({ source: "process", code: typeof code === "number" ? code : null, signal: signal ?? null }); error(formatSignalDaemonExit({ source: "process", code: code ?? null, signal: signal ?? null })); }); child.once("close", (code, signal) => { settleExit({ source: "process", code: typeof code === "number" ? code : null, signal: signal ?? null }); }); child.on("error", (err) => { error(`signal-cli spawn error: ${String(err)}`); settleExit({ source: "spawn-error", code: null, signal: null }); }); return { pid: child.pid ?? void 0, exited: exitedPromise, isExited: () => exited, stop: () => { if (!child.killed && !exited) child.kill("SIGTERM"); } }; } //#endregion //#region extensions/signal/src/monitor/access-policy.ts async function resolveSignalAccessState(params) { const storeAllowFrom = await (0, _dmPolicySharedD_6CpFGk.n)({ provider: "signal", accountId: params.accountId, dmPolicy: params.dmPolicy }); const resolveAccessDecision = (isGroup) => (0, _dmPolicySharedD_6CpFGk.o)({ isGroup, dmPolicy: params.dmPolicy, groupPolicy: params.groupPolicy, allowFrom: params.allowFrom, groupAllowFrom: params.groupAllowFrom, storeAllowFrom, isSenderAllowed: (allowEntries) => (0, _identityDjM1QVic.a)(params.sender, allowEntries) }); const dmAccess = resolveAccessDecision(false); return { resolveAccessDecision, dmAccess, effectiveDmAllow: dmAccess.effectiveAllowFrom, effectiveGroupAllow: dmAccess.effectiveGroupAllowFrom }; } async function handleSignalDirectMessageAccess(params) { if (params.dmAccessDecision === "allow") return true; if (params.dmAccessDecision === "block") { if (params.dmPolicy !== "disabled") params.log(`Blocked signal sender ${params.senderDisplay} (dmPolicy=${params.dmPolicy})`); return false; } if (params.dmPolicy === "pairing") await (0, _channelPairingRPdRacuP.t)({ channel: "signal", upsertPairingRequest: async ({ id, meta }) => await (0, _pairingStoreEKDwEjkm.d)({ channel: "signal", id, accountId: params.accountId, meta }) })({ senderId: params.senderId, senderIdLine: params.senderIdLine, meta: { name: params.senderName }, sendPairingReply: params.sendPairingReply, onCreated: () => { params.log(`signal pairing request sender=${params.senderId}`); }, onReplyError: (err) => { params.log(`signal pairing reply failed for ${params.senderId}: ${String(err)}`); } }); return false; } //#endregion //#region extensions/signal/src/monitor/inbound-context.ts function resolveSignalQuoteContext(params) { const contextVisibilityMode = (0, _contextVisibilityBpCxpfZ.t)({ cfg: params.cfg, channel: "signal", accountId: params.accountId }); const quoteText = (0, _stringCoerceBUSzWgUA.s)(params.dataMessage?.quote?.text) ?? ""; const quoteSender = (0, _identityDjM1QVic.l)({ sourceNumber: params.dataMessage?.quote?.author ?? null, sourceUuid: params.dataMessage?.quote?.authorUuid ?? null }); const quoteSenderAllowed = !params.isGroup || params.effectiveGroupAllow.length === 0 ? true : quoteSender ? (0, _identityDjM1QVic.a)(quoteSender, params.effectiveGroupAllow) : false; const decision = (0, _contextVisibilityYnM4GZpy.t)({ mode: contextVisibilityMode, kind: "quote", senderAllowed: quoteSenderAllowed }); return { contextVisibilityMode, decision, quoteSenderAllowed, visibleQuoteText: decision.include ? quoteText : "", visibleQuoteSender: decision.include && quoteSender ? (0, _identityDjM1QVic.n)(quoteSender) : void 0 }; } //#endregion //#region extensions/signal/src/monitor/mentions.ts const OBJECT_REPLACEMENT = ""; function isValidMention(mention) { if (!mention) return false; if (!(mention.uuid || mention.number)) return false; if (typeof mention.start !== "number" || Number.isNaN(mention.start)) return false; if (typeof mention.length !== "number" || Number.isNaN(mention.length)) return false; return mention.length > 0; } function clampBounds(start, length, textLength) { const safeStart = Math.max(0, Math.trunc(start)); return { start: safeStart, end: Math.min(textLength, safeStart + Math.max(0, Math.trunc(length))) }; } function renderSignalMentions(message, mentions) { if (!message || !mentions?.length) return message; let normalized = message; const candidates = mentions.filter(isValidMention).toSorted((a, b) => b.start - a.start); for (const mention of candidates) { const identifier = mention.uuid ?? mention.number; if (!identifier) continue; const { start, end } = clampBounds(mention.start, mention.length, normalized.length); if (start >= end) continue; if (!normalized.slice(start, end).includes(OBJECT_REPLACEMENT)) continue; normalized = normalized.slice(0, start) + `@${identifier}` + normalized.slice(end); } return normalized; } //#endregion //#region extensions/signal/src/monitor/event-handler.ts function formatAttachmentKindCount(kind, count) { if (kind === "attachment") return `${count} file${count > 1 ? "s" : ""}`; return `${count} ${kind}${count > 1 ? "s" : ""}`; } function formatAttachmentSummaryPlaceholder(contentTypes) { const kindCounts = /* @__PURE__ */new Map(); for (const contentType of contentTypes) { const kind = (0, _mimeCi7oOV2D.s)(contentType) ?? "attachment"; kindCounts.set(kind, (kindCounts.get(kind) ?? 0) + 1); } return `[${[...kindCounts.entries()].map(([kind, count]) => formatAttachmentKindCount(kind, count)).join(" + ")} attached]`; } function resolveSignalInboundRoute(params) { return (0, _resolveRouteC1CfLZm.i)({ cfg: params.cfg, channel: "signal", accountId: params.accountId, peer: { kind: params.isGroup ? "group" : "direct", id: params.isGroup ? params.groupId ?? "unknown" : params.senderPeerId } }); } function createSignalEventHandler(deps) { async function handleSignalInboundMessage(entry) { const fromLabel = (0, _envelopeL34d7rAH.i)({ isGroup: entry.isGroup, groupLabel: entry.groupName ?? void 0, groupId: entry.groupId ?? "unknown", groupFallback: "Group", directLabel: entry.senderName, directId: entry.senderDisplay }); const route = resolveSignalInboundRoute({ cfg: deps.cfg, accountId: deps.accountId, isGroup: entry.isGroup, groupId: entry.groupId, senderPeerId: entry.senderPeerId }); const storePath = (0, _pathsCrHqfnMp.u)(deps.cfg.session?.store, { agentId: route.agentId }); const envelopeOptions = (0, _envelopeL34d7rAH.a)(deps.cfg); const previousTimestamp = (0, _storeCw5RrVCw.r)({ storePath, sessionKey: route.sessionKey }); const body = (0, _envelopeL34d7rAH.r)({ channel: "Signal", from: fromLabel, timestamp: entry.timestamp ?? void 0, body: entry.bodyText, chatType: entry.isGroup ? "group" : "direct", sender: { name: entry.senderName, id: entry.senderDisplay }, previousTimestamp, envelope: envelopeOptions }); let combinedBody = body; const historyKey = entry.isGroup ? entry.groupId ?? "unknown" : void 0; if (entry.isGroup && historyKey) combinedBody = (0, _historyC4oWdkP.a)({ historyMap: deps.groupHistories, historyKey, limit: deps.historyLimit, currentMessage: combinedBody, formatEntry: (historyEntry) => (0, _envelopeL34d7rAH.r)({ channel: "Signal", from: fromLabel, timestamp: historyEntry.timestamp, body: `${historyEntry.body}${historyEntry.messageId ? ` [id:${historyEntry.messageId}]` : ""}`, chatType: "group", senderLabel: historyEntry.sender, envelope: envelopeOptions }) }); const signalToRaw = entry.isGroup ? `group:${entry.groupId}` : `signal:${entry.senderRecipient}`; const signalTo = (0, _identityDjM1QVic.f)(signalToRaw) ?? signalToRaw; const inboundHistory = entry.isGroup && historyKey && deps.historyLimit > 0 ? (deps.groupHistories.get(historyKey) ?? []).map((historyEntry) => ({ sender: historyEntry.sender, body: historyEntry.body, timestamp: historyEntry.timestamp })) : void 0; const ctxPayload = (0, _inboundContextBM3S6N.t)({ Body: combinedBody, BodyForAgent: entry.bodyText, InboundHistory: inboundHistory, RawBody: entry.bodyText, CommandBody: entry.commandBody, BodyForCommands: entry.commandBody, From: entry.isGroup ? `group:${entry.groupId ?? "unknown"}` : `signal:${entry.senderRecipient}`, To: signalTo, SessionKey: route.sessionKey, AccountId: route.accountId, ChatType: entry.isGroup ? "group" : "direct", ConversationLabel: fromLabel, GroupSubject: entry.isGroup ? entry.groupName ?? void 0 : void 0, SenderName: entry.senderName, SenderId: entry.senderDisplay, Provider: "signal", Surface: "signal", MessageSid: entry.messageId, ReplyToBody: entry.replyToBody, ReplyToSender: entry.replyToSender, ReplyToIsQuote: entry.replyToIsQuote, Timestamp: entry.timestamp ?? void 0, MediaPath: entry.mediaPath, MediaType: entry.mediaType, MediaUrl: entry.mediaPath, MediaPaths: entry.mediaPaths, MediaUrls: entry.mediaPaths, MediaTypes: entry.mediaTypes, WasMentioned: entry.isGroup ? entry.wasMentioned === true : void 0, CommandAuthorized: entry.commandAuthorized, OriginatingChannel: "signal", OriginatingTo: signalTo }); await (0, _sessionBMgtCdKz.t)({ storePath, sessionKey: ctxPayload.SessionKey ?? route.sessionKey, ctx: ctxPayload, updateLastRoute: !entry.isGroup ? { sessionKey: route.mainSessionKey, channel: "signal", to: entry.senderRecipient, accountId: route.accountId, mainDmOwnerPin: (() => { const pinnedOwner = (0, _dmPolicySharedD_6CpFGk.c)({ dmScope: deps.cfg.session?.dmScope, allowFrom: deps.allowFrom, normalizeEntry: _identityDjM1QVic.o }); if (!pinnedOwner) return; return { ownerRecipient: pinnedOwner, senderRecipient: entry.senderRecipient, onSkip: ({ ownerRecipient, senderRecipient }) => { (0, _globalsDQvpEApL.r)(`signal: skip main-session last route for ${senderRecipient} (pinned owner ${ownerRecipient})`); } }; })() } : void 0, onRecordError: (err) => { (0, _globalsDQvpEApL.r)(`signal: failed updating session meta: ${String(err)}`); } }); if ((0, _globalsDQvpEApL.a)()) { const preview = body.slice(0, 200).replace(/\\n/g, "\\\\n"); (0, _globalsDQvpEApL.r)(`signal inbound: from=${ctxPayload.From} len=${body.length} preview="${preview}"`); } const { onModelSelected, typingCallbacks, ...replyPipeline } = (0, _channelReplyPipelineCJZpAxBU.t)({ cfg: deps.cfg, agentId: route.agentId, channel: "signal", accountId: route.accountId, typing: { start: async () => { if (!ctxPayload.To) return; await (0, _sendYvq8Fn7p.r)(ctxPayload.To, { baseUrl: deps.baseUrl, account: deps.account, accountId: deps.accountId }); }, onStartError: (err) => { (0, _loggingSOwyEnNl.r)({ log: _globalsDQvpEApL.r, channel: "signal", target: ctxPayload.To ?? void 0, error: err }); } } }); const { dispatcher, replyOptions, markDispatchIdle } = (0, _dispatchCdc7z0Lu.a)({ ...replyPipeline, humanDelay: (0, _identityLLtDOuch.i)(deps.cfg, route.agentId), typingCallbacks, deliver: async (payload) => { await deps.deliverReplies({ replies: [payload], target: ctxPayload.To, baseUrl: deps.baseUrl, account: deps.account, accountId: deps.accountId, runtime: deps.runtime, maxBytes: deps.mediaMaxBytes, textLimit: deps.textLimit }); }, onError: (err, info) => { deps.runtime.error?.((0, _globalsDQvpEApL.t)(`signal ${info.kind} reply failed: ${String(err)}`)); } }); const { queuedFinal } = await (0, _dispatchCdc7z0Lu.t)({ ctx: ctxPayload, cfg: deps.cfg, dispatcher, replyOptions: { ...replyOptions, disableBlockStreaming: typeof deps.blockStreaming === "boolean" ? !deps.blockStreaming : void 0, onModelSelected } }); markDispatchIdle(); if (!queuedFinal) { if (entry.isGroup && historyKey) (0, _historyC4oWdkP.s)({ historyMap: deps.groupHistories, historyKey, limit: deps.historyLimit }); return; } if (entry.isGroup && historyKey) (0, _historyC4oWdkP.s)({ historyMap: deps.groupHistories, historyKey, limit: deps.historyLimit }); } const { debouncer: inboundDebouncer } = (0, _channelInboundB3GLEQYw.t)({ cfg: deps.cfg, channel: "signal", buildKey: (entry) => { const conversationId = entry.isGroup ? entry.groupId ?? "unknown" : entry.senderPeerId; if (!conversationId || !entry.senderPeerId) return null; return `signal:${deps.accountId}:${conversationId}:${entry.senderPeerId}`; }, shouldDebounce: (entry) => { return (0, _channelInboundB3GLEQYw.n)({ text: entry.bodyText, cfg: deps.cfg, hasMedia: Boolean(entry.mediaPath || entry.mediaType || entry.mediaPaths?.length) }); }, onFlush: async (entries) => { const last = entries.at(-1); if (!last) return; if (entries.length === 1) { await handleSignalInboundMessage(last); return; } const combinedText = entries.map((entry) => entry.bodyText).filter(Boolean).join("\\n"); if (!combinedText.trim()) return; await handleSignalInboundMessage({ ...last, bodyText: combinedText, mediaPath: void 0, mediaType: void 0, mediaPaths: void 0, mediaTypes: void 0 }); }, onError: (err) => { deps.runtime.error?.(`signal debounce flush failed: ${String(err)}`); } }); function handleReactionOnlyInbound(params) { if (params.hasBodyContent) return false; if (params.reaction.isRemove) return true; const emojiLabel = (0, _stringCoerceBUSzWgUA.s)(params.reaction.emoji) ?? "emoji"; const senderName = params.envelope.sourceName ?? params.senderDisplay; (0, _globalsDQvpEApL.r)(`signal reaction: ${emojiLabel} from ${senderName}`); const groupId = params.reaction.groupInfo?.groupId ?? void 0; const groupName = params.reaction.groupInfo?.groupName ?? void 0; const isGroup = Boolean(groupId); const reactionAccess = params.resolveAccessDecision(isGroup); if (reactionAccess.decision !== "allow") { (0, _globalsDQvpEApL.r)(`Blocked signal reaction sender ${params.senderDisplay} (${reactionAccess.reason})`); return true; } const targets = deps.resolveSignalReactionTargets(params.reaction); if (!deps.shouldEmitSignalReactionNotification({ mode: deps.reactionMode, account: deps.account, targets, sender: params.sender, allowlist: deps.reactionAllowlist })) return true; const senderPeerId = (0, _identityDjM1QVic.s)(params.sender); const route = resolveSignalInboundRoute({ cfg: deps.cfg, accountId: deps.accountId, isGroup, groupId, senderPeerId }); const groupLabel = isGroup ? `${groupName ?? "Signal Group"} id:${groupId}` : void 0; const messageId = params.reaction.targetSentTimestamp ? String(params.reaction.targetSentTimestamp) : "unknown"; const text = deps.buildSignalReactionSystemEventText({ emojiLabel, actorLabel: senderName, messageId, targetLabel: targets[0]?.display, groupLabel }); const contextKey = [ "signal", "reaction", "added", messageId, (0, _identityDjM1QVic.r)(params.sender), emojiLabel, groupId ?? ""]. filter(Boolean).join(":"); (0, _systemEventsBwxCjOwe.r)(text, { sessionKey: route.sessionKey, contextKey }); return true; } return async (event) => { if (event.event !== "receive" || !event.data) return; let payload = null; try { payload = JSON.parse(event.data); } catch (err) { deps.runtime.error?.(`failed to parse event: ${String(err)}`); return; } if (payload?.exception?.message) deps.runtime.error?.(`receive exception: ${payload.exception.message}`); const envelope = payload?.envelope; if (!envelope) return; const sender = (0, _identityDjM1QVic.l)(envelope); if (!sender) return; const normalizedAccount = deps.account ? (0, _utilsD5DtWkEu.u)(deps.account) : void 0; if (sender.kind === "phone" && normalizedAccount != null && sender.e164 === normalizedAccount || sender.kind === "uuid" && deps.accountUuid != null && sender.raw === deps.accountUuid) return; if ("syncMessage" in envelope) return; const dataMessage = envelope.dataMessage ?? envelope.editMessage?.dataMessage; const reaction = deps.isSignalReactionMessage(envelope.reactionMessage) ? envelope.reactionMessage : deps.isSignalReactionMessage(dataMessage?.reaction) ? dataMessage?.reaction : null; const messageText = renderSignalMentions(dataMessage?.message ?? "", dataMessage?.mentions).trim(); const groupId = dataMessage?.groupInfo?.groupId ?? void 0; const isGroup = Boolean(groupId); const senderDisplay = (0, _identityDjM1QVic.n)(sender); const { resolveAccessDecision, dmAccess, effectiveDmAllow, effectiveGroupAllow } = await resolveSignalAccessState({ accountId: deps.accountId, dmPolicy: deps.dmPolicy, groupPolicy: deps.groupPolicy, allowFrom: deps.allowFrom, groupAllowFrom: deps.groupAllowFrom, sender }); const quoteText = (0, _stringCoerceBUSzWgUA.s)(dataMessage?.quote?.text) ?? ""; const { contextVisibilityMode, quoteSenderAllowed, visibleQuoteText, visibleQuoteSender } = resolveSignalQuoteContext({ cfg: deps.cfg, accountId: deps.accountId, isGroup, dataMessage, effectiveGroupAllow }); if (quoteText && !visibleQuoteText && isGroup) (0, _globalsDQvpEApL.r)(`signal: drop quote context (mode=${contextVisibilityMode}, sender_allowed=${quoteSenderAllowed ? "yes" : "no"})`); const hasBodyContent = Boolean(messageText || visibleQuoteText) || Boolean(!reaction && dataMessage?.attachments?.length); if (reaction && handleReactionOnlyInbound({ envelope, sender, senderDisplay, reaction, hasBodyContent, resolveAccessDecision })) return; if (!dataMessage) return; const senderRecipient = (0, _identityDjM1QVic.c)(sender); const senderPeerId = (0, _identityDjM1QVic.s)(sender); const senderAllowId = (0, _identityDjM1QVic.r)(sender); if (!senderRecipient) return; const senderIdLine = (0, _identityDjM1QVic.t)(sender); const groupName = dataMessage.groupInfo?.groupName ?? void 0; if (!isGroup) { if (!(await handleSignalDirectMessageAccess({ dmPolicy: deps.dmPolicy, dmAccessDecision: dmAccess.decision, senderId: senderAllowId, senderIdLine, senderDisplay, senderName: envelope.sourceName ?? void 0, accountId: deps.accountId, sendPairingReply: async (text) => { await (0, _sendYvq8Fn7p.t)(`signal:${senderRecipient}`, text, { baseUrl: deps.baseUrl, account: deps.account, maxBytes: deps.mediaMaxBytes, accountId: deps.accountId }); }, log: _globalsDQvpEApL.r }))) return; } if (isGroup) { const groupAccess = resolveAccessDecision(true); if (groupAccess.decision !== "allow") { if (groupAccess.reasonCode === _dmPolicySharedD_6CpFGk.t.GROUP_POLICY_DISABLED) (0, _globalsDQvpEApL.r)("Blocked signal group message (groupPolicy: disabled)");else if (groupAccess.reasonCode === _dmPolicySharedD_6CpFGk.t.GROUP_POLICY_EMPTY_ALLOWLIST) (0, _globalsDQvpEApL.r)("Blocked signal group message (groupPolicy: allowlist, no groupAllowFrom)");else (0, _globalsDQvpEApL.r)(`Blocked signal group sender ${senderDisplay} (not in groupAllowFrom)`); return; } } const useAccessGroups = deps.cfg.commands?.useAccessGroups !== false; const commandDmAllow = isGroup ? deps.allowFrom : effectiveDmAllow; const ownerAllowedForCommands = (0, _identityDjM1QVic.a)(sender, commandDmAllow); const groupAllowedForCommands = (0, _identityDjM1QVic.a)(sender, effectiveGroupAllow); const hasControlCommandInMessage = (0, _commandDetectionDNYq9uM.t)(messageText, deps.cfg); const commandGate = (0, _commandGatingDYM2M_v.n)({ useAccessGroups, authorizers: [{ configured: commandDmAllow.length > 0, allowed: ownerAllowedForCommands }, { configured: effectiveGroupAllow.length > 0, allowed: groupAllowedForCommands }], allowTextCommands: true, hasControlCommand: hasControlCommandInMessage }); const commandAuthorized = commandGate.commandAuthorized; if (isGroup && commandGate.shouldBlock) { (0, _loggingSOwyEnNl.n)({ log: _globalsDQvpEApL.r, channel: "signal", reason: "control command (unauthorized)", target: senderDisplay }); return; } const route = resolveSignalInboundRoute({ cfg: deps.cfg, accountId: deps.accountId, isGroup, groupId, senderPeerId }); const mentionRegexes = (0, _mentionsBimfLHao.n)(deps.cfg, route.agentId); const wasMentioned = isGroup && (0, _mentionsBimfLHao.r)(messageText, mentionRegexes); const requireMention = isGroup && (0, _groupPolicyYqA7ta9g.n)({ cfg: deps.cfg, channel: "signal", groupId, accountId: deps.accountId }); const canDetectMention = mentionRegexes.length > 0; const mentionDecision = (0, _mentionGatingDUv0CJhz.n)({ facts: { canDetectMention, wasMentioned, hasAnyMention: false, implicitMentionKinds: [] }, policy: { isGroup, requireMention, allowTextCommands: true, hasControlCommand: hasControlCommandInMessage, commandAuthorized } }); const effectiveWasMentioned = mentionDecision.effectiveWasMentioned; if (isGroup && requireMention && canDetectMention && mentionDecision.shouldSkip) { (0, _loggingSOwyEnNl.n)({ log: _globalsDQvpEApL.r, channel: "signal", reason: "no mention", target: senderDisplay }); const pendingPlaceholder = (() => { if (!dataMessage.attachments?.length) return ""; if (deps.ignoreAttachments) return ""; const attachmentTypes = (dataMessage.attachments ?? []).map((attachment) => typeof attachment?.contentType === "string" ? attachment.contentType : void 0); if (attachmentTypes.length > 1) return formatAttachmentSummaryPlaceholder(attachmentTypes); const firstContentType = dataMessage.attachments?.[0]?.contentType; const pendingKind = (0, _mimeCi7oOV2D.s)(firstContentType ?? void 0); return pendingKind ? `` : ""; })(); const pendingBodyText = messageText || pendingPlaceholder || visibleQuoteText; const historyKey = groupId ?? "unknown"; (0, _historyC4oWdkP.u)({ historyMap: deps.groupHistories, historyKey, limit: deps.historyLimit, entry: { sender: envelope.sourceName ?? senderDisplay, body: pendingBodyText, timestamp: envelope.timestamp ?? void 0, messageId: typeof envelope.timestamp === "number" ? String(envelope.timestamp) : void 0 } }); const signalGroupPolicy = (0, _groupPolicyYqA7ta9g.t)({ cfg: deps.cfg, channel: "signal", groupId, accountId: deps.accountId }); if ((signalGroupPolicy.groupConfig?.ingest ?? signalGroupPolicy.defaultConfig?.ingest) === true) { const canonicalGroupTarget = (0, _identityDjM1QVic.f)(`group:${groupId}`) ?? `group:${groupId}`; (0, _messageHookMappersCIPnjy6o.f)((0, _internalHooksCEPnFgdQ.m)((0, _internalHooksCEPnFgdQ.n)("message", "received", route.sessionKey, (0, _messageHookMappersCIPnjy6o.i)({ from: `group:${groupId}`, to: canonicalGroupTarget, content: pendingBodyText, timestamp: envelope.timestamp ?? void 0, channelId: "signal", accountId: deps.accountId, conversationId: canonicalGroupTarget, messageId: typeof envelope.timestamp === "number" ? String(envelope.timestamp) : void 0, senderId: senderDisplay, senderName: envelope.sourceName ?? void 0, provider: "signal", surface: "signal", originatingChannel: "signal", originatingTo: canonicalGroupTarget, isGroup: true, groupId: canonicalGroupTarget }))), "signal: mention-skip message hook failed"); } return; } let mediaPath; let mediaType; const mediaPaths = []; const mediaTypes = []; let placeholder = ""; const attachments = dataMessage.attachments ?? []; if (!deps.ignoreAttachments) for (const attachment of attachments) { if (!attachment?.id) continue; try { const fetched = await deps.fetchAttachment({ baseUrl: deps.baseUrl, account: deps.account, attachment, sender: senderRecipient, groupId, maxBytes: deps.mediaMaxBytes }); if (fetched) { mediaPaths.push(fetched.path); mediaTypes.push(fetched.contentType ?? attachment.contentType ?? "application/octet-stream"); if (!mediaPath) { mediaPath = fetched.path; mediaType = fetched.contentType ?? attachment.contentType ?? void 0; } } } catch (err) { deps.runtime.error?.((0, _globalsDQvpEApL.t)(`attachment fetch failed: ${String(err)}`)); } } if (mediaPaths.length > 1) placeholder = formatAttachmentSummaryPlaceholder(mediaTypes);else { const kind = (0, _mimeCi7oOV2D.s)(mediaType ?? void 0); if (kind) placeholder = ``;else if (attachments.length) placeholder = ""; } const bodyText = messageText || placeholder || visibleQuoteText || ""; if (!bodyText) return; const receiptTimestamp = typeof envelope.timestamp === "number" ? envelope.timestamp : typeof dataMessage.timestamp === "number" ? dataMessage.timestamp : void 0; if (deps.sendReadReceipts && !deps.readReceiptsViaDaemon && !isGroup && receiptTimestamp) try { await (0, _sendYvq8Fn7p.n)(`signal:${senderRecipient}`, receiptTimestamp, { baseUrl: deps.baseUrl, account: deps.account, accountId: deps.accountId }); } catch (err) { (0, _globalsDQvpEApL.r)(`signal read receipt failed for ${senderDisplay}: ${String(err)}`); } else if (deps.sendReadReceipts && !deps.readReceiptsViaDaemon && !isGroup && !receiptTimestamp) (0, _globalsDQvpEApL.r)(`signal read receipt skipped (missing timestamp) for ${senderDisplay}`); const senderName = envelope.sourceName ?? senderDisplay; const messageId = typeof envelope.timestamp === "number" ? String(envelope.timestamp) : void 0; await inboundDebouncer.enqueue({ senderName, senderDisplay, senderRecipient, senderPeerId, groupId, groupName, isGroup, bodyText, commandBody: messageText, timestamp: envelope.timestamp ?? void 0, messageId, mediaPath, mediaType, mediaPaths: mediaPaths.length > 0 ? mediaPaths : void 0, mediaTypes: mediaTypes.length > 0 ? mediaTypes : void 0, commandAuthorized, wasMentioned: effectiveWasMentioned, replyToBody: visibleQuoteText || void 0, replyToSender: visibleQuoteSender, replyToIsQuote: visibleQuoteText ? true : void 0 }); }; } //#endregion //#region extensions/signal/src/sse-reconnect.ts const DEFAULT_RECONNECT_POLICY = { initialMs: 1e3, maxMs: 1e4, factor: 2, jitter: .2 }; async function runSignalSseLoop({ baseUrl, account, abortSignal, runtime, onEvent, policy }) { const reconnectPolicy = { ...DEFAULT_RECONNECT_POLICY, ...policy }; let reconnectAttempts = 0; const logReconnectVerbose = (message) => { if (!(0, _globalsDQvpEApL.a)()) return; (0, _globalsDQvpEApL.r)(message); }; for (;;) { if (abortSignal?.aborted) break; try { await (0, _clientDj2vAxFI.r)({ baseUrl, account, abortSignal, onEvent: (event) => { reconnectAttempts = 0; onEvent(event); } }); if (abortSignal?.aborted) return; reconnectAttempts += 1; const delayMs = (0, _backoffCo9E7aKq.t)(reconnectPolicy, reconnectAttempts); logReconnectVerbose(`Signal SSE stream ended, reconnecting in ${delayMs / 1e3}s...`); await (0, _backoffCo9E7aKq.n)(delayMs, abortSignal); } catch (err) { if (abortSignal?.aborted) return; runtime.error?.(`Signal SSE stream error: ${String(err)}`); reconnectAttempts += 1; const delayMs = (0, _backoffCo9E7aKq.t)(reconnectPolicy, reconnectAttempts); runtime.log?.(`Signal SSE connection lost, reconnecting in ${delayMs / 1e3}s...`); try { await (0, _backoffCo9E7aKq.n)(delayMs, abortSignal); } catch (sleepErr) { if (abortSignal?.aborted) return; throw sleepErr; } } } } //#endregion //#region extensions/signal/src/monitor.ts function resolveRuntime(opts) { return opts.runtime ?? (0, _runtimeDf11Xt6R.t)(); } function mergeAbortSignals(a, b) { if (!a && !b) return { signal: void 0, dispose: () => {} }; if (!a) return { signal: b, dispose: () => {} }; if (!b) return { signal: a, dispose: () => {} }; const controller = new AbortController(); const abortFrom = (source) => { if (!controller.signal.aborted) controller.abort(source.reason); }; if (a.aborted) { abortFrom(a); return { signal: controller.signal, dispose: () => {} }; } if (b.aborted) { abortFrom(b); return { signal: controller.signal, dispose: () => {} }; } const onAbortA = () => abortFrom(a); const onAbortB = () => abortFrom(b); a.addEventListener("abort", onAbortA, { once: true }); b.addEventListener("abort", onAbortB, { once: true }); return { signal: controller.signal, dispose: () => { a.removeEventListener("abort", onAbortA); b.removeEventListener("abort", onAbortB); } }; } function createSignalDaemonLifecycle(params) { let daemonHandle = null; let daemonStopRequested = false; let daemonExitError; const daemonAbortController = new AbortController(); const mergedAbort = mergeAbortSignals(params.abortSignal, daemonAbortController.signal); const stop = () => { daemonStopRequested = true; daemonHandle?.stop(); }; const attach = (handle) => { daemonHandle = handle; handle.exited.then((exit) => { if (daemonStopRequested || params.abortSignal?.aborted) return; daemonExitError = new Error(formatSignalDaemonExit(exit)); if (!daemonAbortController.signal.aborted) daemonAbortController.abort(daemonExitError); }); }; const getExitError = () => daemonExitError; return { attach, stop, getExitError, abortSignal: mergedAbort.signal, dispose: mergedAbort.dispose }; } function normalizeAllowList(raw) { return (0, _stringNormalizationDeXXoUde.s)(raw); } function resolveSignalReactionTargets(reaction) { const targets = []; const uuid = reaction.targetAuthorUuid?.trim(); if (uuid) targets.push({ kind: "uuid", id: uuid, display: `uuid:${uuid}` }); const author = reaction.targetAuthor?.trim(); if (author) { const normalized = (0, _utilsD5DtWkEu.u)(author); targets.push({ kind: "phone", id: normalized, display: normalized }); } return targets; } function isSignalReactionMessage(reaction) { if (!reaction) return false; const emoji = reaction.emoji?.trim(); const timestamp = reaction.targetSentTimestamp; const hasTarget = Boolean((0, _stringCoerceBUSzWgUA.s)(reaction.targetAuthor) || (0, _stringCoerceBUSzWgUA.s)(reaction.targetAuthorUuid)); return Boolean(emoji && typeof timestamp === "number" && timestamp > 0 && hasTarget); } function shouldEmitSignalReactionNotification(params) { const { mode, account, targets, sender, allowlist } = params; const effectiveMode = mode ?? "own"; if (effectiveMode === "off") return false; if (effectiveMode === "own") { const accountId = account?.trim(); if (!accountId || !targets || targets.length === 0) return false; const normalizedAccount = (0, _utilsD5DtWkEu.u)(accountId); return targets.some((target) => { if (target.kind === "uuid") return accountId === target.id || accountId === `uuid:${target.id}`; return normalizedAccount === target.id; }); } if (effectiveMode === "allowlist") { if (!sender || !allowlist || allowlist.length === 0) return false; return (0, _identityDjM1QVic.a)(sender, allowlist); } return true; } function buildSignalReactionSystemEventText(params) { const base = `Signal reaction added: ${params.emojiLabel} by ${params.actorLabel} msg ${params.messageId}`; const withTarget = params.targetLabel ? `${base} from ${params.targetLabel}` : base; return params.groupLabel ? `${withTarget} in ${params.groupLabel}` : withTarget; } async function waitForSignalDaemonReady(params) { await (params.waitForTransportReadyFn ?? _transportReadyCIbz1r5V.t)({ label: "signal daemon", timeoutMs: params.timeoutMs, logAfterMs: params.logAfterMs, logIntervalMs: params.logIntervalMs, pollIntervalMs: 150, abortSignal: params.abortSignal, runtime: params.runtime, check: async () => { const res = await (0, _clientDj2vAxFI.t)(params.baseUrl, 1e3); if (res.ok) return { ok: true }; return { ok: false, error: res.error ?? (res.status ? `HTTP ${res.status}` : "unreachable") }; } }); } async function fetchAttachment(params) { const { attachment } = params; if (!attachment?.id) return null; if (typeof attachment.size === "number" && attachment.size > params.maxBytes) throw new Error(`Signal attachment ${attachment.id} exceeds ${(params.maxBytes / (1024 * 1024)).toFixed(0)}MB limit`); const rpcParams = { id: attachment.id }; if (params.account) rpcParams.account = params.account; if (params.groupId) rpcParams.groupId = params.groupId;else if (params.sender) rpcParams.recipient = params.sender;else return null; const result = await (0, _clientDj2vAxFI.n)("getAttachment", rpcParams, { baseUrl: params.baseUrl }); if (!result?.data) return null; if ((0, _base64Fcs4g96r.n)(result.data) > params.maxBytes) throw new Error(`Signal attachment ${attachment.id} exceeds ${(params.maxBytes / (1024 * 1024)).toFixed(0)}MB limit`); const saved = await (0, _storeCA7OW_2w.l)(Buffer.from(result.data, "base64"), attachment.contentType ?? void 0, "inbound", params.maxBytes); return { path: saved.path, contentType: saved.contentType }; } async function deliverReplies(params) { const { replies, target, baseUrl, account, accountId, runtime, maxBytes, textLimit, chunkMode } = params; for (const payload of replies) if ((await (0, _replyPayloadCMyOxXD.i)({ payload, text: (0, _replyPayloadCMyOxXD.p)(payload).text, chunkText: (value) => (0, _chunkBJOSq3sp.o)(value, textLimit, chunkMode), sendText: async (chunk) => { await (0, _sendYvq8Fn7p.t)(target, chunk, { baseUrl, account, maxBytes, accountId }); }, sendMedia: async ({ mediaUrl, caption }) => { await (0, _sendYvq8Fn7p.t)(target, caption ?? "", { baseUrl, account, mediaUrl, maxBytes, accountId }); } })) !== "empty") runtime.log?.(`delivered reply to ${target}`); } async function monitorSignalProvider(opts = {}) { const runtime = resolveRuntime(opts); const cfg = opts.config ?? (0, _ioBYPX79V.a)(); const accountInfo = (0, _accountsDmKrW9vL.i)({ cfg, accountId: opts.accountId }); const historyLimit = Math.max(0, accountInfo.config.historyLimit ?? cfg.messages?.groupChat?.historyLimit ?? 50); const groupHistories = /* @__PURE__ */new Map(); const textLimit = (0, _chunkBJOSq3sp.c)(cfg, "signal", accountInfo.accountId); const chunkMode = (0, _chunkBJOSq3sp.s)(cfg, "signal", accountInfo.accountId); const baseUrl = (0, _stringCoerceBUSzWgUA.s)(opts.baseUrl) ?? accountInfo.baseUrl; const account = (0, _stringCoerceBUSzWgUA.s)(opts.account) ?? (0, _stringCoerceBUSzWgUA.s)(accountInfo.config.account); const dmPolicy = accountInfo.config.dmPolicy ?? "pairing"; const allowFrom = normalizeAllowList(opts.allowFrom ?? accountInfo.config.allowFrom); const groupAllowFrom = normalizeAllowList(opts.groupAllowFrom ?? accountInfo.config.groupAllowFrom ?? (accountInfo.config.allowFrom && accountInfo.config.allowFrom.length > 0 ? accountInfo.config.allowFrom : [])); const defaultGroupPolicy = (0, _runtimeGroupPolicyBUWwjQZ_.r)(cfg); const { groupPolicy, providerMissingFallbackApplied } = (0, _runtimeGroupPolicyBUWwjQZ_.n)({ providerConfigPresent: cfg.channels?.signal !== void 0, groupPolicy: accountInfo.config.groupPolicy, defaultGroupPolicy }); (0, _runtimeGroupPolicyBUWwjQZ_.a)({ providerMissingFallbackApplied, providerKey: "signal", accountId: accountInfo.accountId, log: (message) => runtime.log?.(message) }); const reactionMode = accountInfo.config.reactionNotifications ?? "own"; const reactionAllowlist = normalizeAllowList(accountInfo.config.reactionAllowlist); const mediaMaxBytes = (opts.mediaMaxMb ?? accountInfo.config.mediaMaxMb ?? 8) * 1024 * 1024; const ignoreAttachments = opts.ignoreAttachments ?? accountInfo.config.ignoreAttachments ?? false; const sendReadReceipts = Boolean(opts.sendReadReceipts ?? accountInfo.config.sendReadReceipts); const waitForTransportReadyFn = opts.waitForTransportReady ?? _transportReadyCIbz1r5V.t; const autoStart = opts.autoStart ?? accountInfo.config.autoStart ?? !accountInfo.config.httpUrl; const startupTimeoutMs = Math.min(12e4, Math.max(1e3, opts.startupTimeoutMs ?? accountInfo.config.startupTimeoutMs ?? 3e4)); const readReceiptsViaDaemon = autoStart && sendReadReceipts; const daemonLifecycle = createSignalDaemonLifecycle({ abortSignal: opts.abortSignal }); let daemonHandle = null; if (autoStart) { daemonHandle = spawnSignalDaemon({ cliPath: opts.cliPath ?? accountInfo.config.cliPath ?? "signal-cli", account, httpHost: opts.httpHost ?? accountInfo.config.httpHost ?? "127.0.0.1", httpPort: opts.httpPort ?? accountInfo.config.httpPort ?? 8080, receiveMode: opts.receiveMode ?? accountInfo.config.receiveMode, ignoreAttachments: opts.ignoreAttachments ?? accountInfo.config.ignoreAttachments, ignoreStories: opts.ignoreStories ?? accountInfo.config.ignoreStories, sendReadReceipts, runtime }); daemonLifecycle.attach(daemonHandle); } const onAbort = () => { daemonLifecycle.stop(); }; opts.abortSignal?.addEventListener("abort", onAbort, { once: true }); try { if (daemonHandle) { await waitForSignalDaemonReady({ baseUrl, abortSignal: daemonLifecycle.abortSignal, timeoutMs: startupTimeoutMs, logAfterMs: 1e4, logIntervalMs: 1e4, runtime, waitForTransportReadyFn }); const daemonExitError = daemonLifecycle.getExitError(); if (daemonExitError) throw daemonExitError; } const handleEvent = createSignalEventHandler({ runtime, cfg, baseUrl, account, accountUuid: accountInfo.config.accountUuid, accountId: accountInfo.accountId, blockStreaming: accountInfo.config.blockStreaming, historyLimit, groupHistories, textLimit, dmPolicy, allowFrom, groupAllowFrom, groupPolicy, reactionMode, reactionAllowlist, mediaMaxBytes, ignoreAttachments, sendReadReceipts, readReceiptsViaDaemon, fetchAttachment, deliverReplies: (params) => deliverReplies({ ...params, chunkMode }), resolveSignalReactionTargets, isSignalReactionMessage, shouldEmitSignalReactionNotification, buildSignalReactionSystemEventText }); await runSignalSseLoop({ baseUrl, account, abortSignal: daemonLifecycle.abortSignal, runtime, policy: opts.reconnectPolicy, onEvent: (event) => { handleEvent(event).catch((err) => { runtime.error?.(`event handler failed: ${String(err)}`); }); } }); const daemonExitError = daemonLifecycle.getExitError(); if (daemonExitError) throw daemonExitError; } catch (err) { const daemonExitError = daemonLifecycle.getExitError(); if (opts.abortSignal?.aborted && !daemonExitError) return; throw err; } finally { daemonLifecycle.dispose(); opts.abortSignal?.removeEventListener("abort", onAbort); daemonLifecycle.stop(); } } //#endregion /* v9-5b4d5eeb93f8f109 */