UNPKG

@gguf/claw

Version:

WhatsApp gateway CLI (Baileys web) with Pi RPC agent

1,359 lines (1,340 loc) 312 kB
import { G as CHANNEL_IDS, J as normalizeAnyChannelId, K as CHAT_CHANNEL_ORDER, Q as requireActivePluginRegistry, S as resolveConfigDir, X as normalizeChatChannelId, Z as getActivePluginRegistry, l as createSubsystemLogger, m as CONFIG_DIR, n as runExec, q as getChatChannelMeta, u as defaultRuntime, w as resolveUserPath, x as normalizeE164 } from "./exec-CjyVHUuE.js"; import { E as resolveWhatsAppAccount, d as normalizePluginsConfig, f as resolveEnableState, i as writeConfigFile, l as LEGACY_MANIFEST_KEYS, p as resolveMemorySlotDecision, s as loadPluginManifestRegistry, t as loadConfig, u as MANIFEST_KEY } from "./config-D5CsedNn.js"; import { A as normalizeAccountId$1, C as resolveOpenClawPackageRootSync, D as buildAgentMainSessionKey, M as normalizeMainKey, N as resolveAgentIdFromSessionKey, T as DEFAULT_AGENT_ID, _ as DEFAULT_TOOLS_FILENAME, c as resolveDefaultAgentId, d as DEFAULT_AGENTS_FILENAME, f as DEFAULT_AGENT_WORKSPACE_DIR, g as DEFAULT_SOUL_FILENAME, h as DEFAULT_IDENTITY_FILENAME, j as normalizeAgentId, l as resolveSessionAgentId, m as DEFAULT_HEARTBEAT_FILENAME, n as resolveAgentConfig, p as DEFAULT_BOOTSTRAP_FILENAME, v as DEFAULT_USER_FILENAME, w as DEFAULT_ACCOUNT_ID, y as ensureAgentWorkspace } from "./agent-scope-qNPdmst1.js"; import { i as resolveGatewayPort, t as STATE_DIR } from "./paths-BFxmmTT5.js"; import { G as isTruthyEnvValue, K as parseBooleanValue } from "./model-selection-Bo7pocNu.js"; import { t as formatCliCommand } from "./command-format-CFzL448l.js"; import { r as resolveSessionTranscriptPath, t as resolveDefaultSessionStorePath } from "./paths-Bkjq_KcG.js"; import { t as emitSessionTranscriptUpdate } from "./transcript-events-DOI14YeV.js"; import { C as DEFAULT_OPENCLAW_BROWSER_ENABLED, D as formatErrorMessage, E as extractErrorCode, S as DEFAULT_OPENCLAW_BROWSER_COLOR, _ as DEFAULT_AI_SNAPSHOT_EFFICIENT_DEPTH, a as resolveOpenClawUserDataDir, b as DEFAULT_BROWSER_DEFAULT_PROFILE_NAME, c as captureScreenshot, d as normalizeCdpWsUrl, f as snapshotAria, g as stopChromeExtensionRelayServer, h as ensureChromeExtensionRelayServer, i as launchOpenClawChrome, l as createTargetViaCdp, m as getHeadersWithAuth, n as isChromeCdpReady, o as stopOpenClawChrome, p as appendCdpPath, r as isChromeReachable, s as resolveBrowserExecutableForPlatform, v as DEFAULT_AI_SNAPSHOT_EFFICIENT_MAX_CHARS, w as DEFAULT_OPENCLAW_BROWSER_PROFILE_NAME, x as DEFAULT_BROWSER_EVALUATE_ENABLED, y as DEFAULT_AI_SNAPSHOT_MAX_CHARS } from "./chrome-DMuEXuKT.js"; import os from "node:os"; import path from "node:path"; import fs, { createWriteStream } from "node:fs"; import json5 from "json5"; import fs$1 from "node:fs/promises"; import { spawn } from "node:child_process"; import { fileURLToPath } from "node:url"; import crypto, { createHash } from "node:crypto"; import { CURRENT_SESSION_VERSION, SessionManager, formatSkillsForPrompt, loadSkillsFromDir } from "@mariozechner/pi-coding-agent"; import { fileTypeFromBuffer } from "file-type"; import { request } from "node:https"; import { pipeline } from "node:stream/promises"; import { lookup } from "node:dns"; import { lookup as lookup$1 } from "node:dns/promises"; import { Agent } from "undici"; import YAML from "yaml"; import express from "express"; //#region src/config/group-policy.ts function normalizeSenderKey(value) { const trimmed = value.trim(); if (!trimmed) return ""; return (trimmed.startsWith("@") ? trimmed.slice(1) : trimmed).toLowerCase(); } function resolveToolsBySender(params) { const toolsBySender = params.toolsBySender; if (!toolsBySender) return; const entries = Object.entries(toolsBySender); if (entries.length === 0) return; const normalized = /* @__PURE__ */ new Map(); let wildcard; for (const [rawKey, policy] of entries) { if (!policy) continue; const key = normalizeSenderKey(rawKey); if (!key) continue; if (key === "*") { wildcard = policy; continue; } if (!normalized.has(key)) normalized.set(key, policy); } const candidates = []; const pushCandidate = (value) => { const trimmed = value?.trim(); if (!trimmed) return; candidates.push(trimmed); }; pushCandidate(params.senderId); pushCandidate(params.senderE164); pushCandidate(params.senderUsername); pushCandidate(params.senderName); for (const candidate of candidates) { const key = normalizeSenderKey(candidate); if (!key) continue; const match = normalized.get(key); if (match) return match; } return wildcard; } function resolveChannelGroups(cfg, channel, accountId) { const normalizedAccountId = normalizeAccountId$1(accountId); const channelConfig = cfg.channels?.[channel]; if (!channelConfig) return; return channelConfig.accounts?.[normalizedAccountId]?.groups ?? channelConfig.accounts?.[Object.keys(channelConfig.accounts ?? {}).find((key) => key.toLowerCase() === normalizedAccountId.toLowerCase()) ?? ""]?.groups ?? channelConfig.groups; } function resolveChannelGroupPolicy(params) { const { cfg, channel } = params; const groups = resolveChannelGroups(cfg, channel, params.accountId); const allowlistEnabled = Boolean(groups && Object.keys(groups).length > 0); const normalizedId = params.groupId?.trim(); const groupConfig = normalizedId && groups ? groups[normalizedId] : void 0; const defaultConfig = groups?.["*"]; return { allowlistEnabled, allowed: !allowlistEnabled || allowlistEnabled && Boolean(groups && Object.hasOwn(groups, "*")) || (normalizedId ? Boolean(groups && Object.hasOwn(groups, normalizedId)) : false), groupConfig, defaultConfig }; } function resolveChannelGroupRequireMention(params) { const { requireMentionOverride, overrideOrder = "after-config" } = params; const { groupConfig, defaultConfig } = resolveChannelGroupPolicy(params); const configMention = typeof groupConfig?.requireMention === "boolean" ? groupConfig.requireMention : typeof defaultConfig?.requireMention === "boolean" ? defaultConfig.requireMention : void 0; if (overrideOrder === "before-config" && typeof requireMentionOverride === "boolean") return requireMentionOverride; if (typeof configMention === "boolean") return configMention; if (overrideOrder !== "before-config" && typeof requireMentionOverride === "boolean") return requireMentionOverride; return true; } function resolveChannelGroupToolsPolicy(params) { const { groupConfig, defaultConfig } = resolveChannelGroupPolicy(params); const groupSenderPolicy = resolveToolsBySender({ toolsBySender: groupConfig?.toolsBySender, senderId: params.senderId, senderName: params.senderName, senderUsername: params.senderUsername, senderE164: params.senderE164 }); if (groupSenderPolicy) return groupSenderPolicy; if (groupConfig?.tools) return groupConfig.tools; const defaultSenderPolicy = resolveToolsBySender({ toolsBySender: defaultConfig?.toolsBySender, senderId: params.senderId, senderName: params.senderName, senderUsername: params.senderUsername, senderE164: params.senderE164 }); if (defaultSenderPolicy) return defaultSenderPolicy; if (defaultConfig?.tools) return defaultConfig.tools; } //#endregion //#region src/discord/token.ts function normalizeDiscordToken(raw) { if (!raw) return; const trimmed = raw.trim(); if (!trimmed) return; return trimmed.replace(/^Bot\s+/i, ""); } function resolveDiscordToken(cfg, opts = {}) { const accountId = normalizeAccountId$1(opts.accountId); const discordCfg = cfg?.channels?.discord; const accountToken = normalizeDiscordToken((accountId !== DEFAULT_ACCOUNT_ID ? discordCfg?.accounts?.[accountId] : discordCfg?.accounts?.[DEFAULT_ACCOUNT_ID])?.token ?? void 0); if (accountToken) return { token: accountToken, source: "config" }; const allowEnv = accountId === DEFAULT_ACCOUNT_ID; const configToken = allowEnv ? normalizeDiscordToken(discordCfg?.token ?? void 0) : void 0; if (configToken) return { token: configToken, source: "config" }; const envToken = allowEnv ? normalizeDiscordToken(opts.envToken ?? process.env.DISCORD_BOT_TOKEN) : void 0; if (envToken) return { token: envToken, source: "env" }; return { token: "", source: "none" }; } //#endregion //#region src/discord/accounts.ts function listConfiguredAccountIds$4(cfg) { const accounts = cfg.channels?.discord?.accounts; if (!accounts || typeof accounts !== "object") return []; return Object.keys(accounts).filter(Boolean); } function listDiscordAccountIds(cfg) { const ids = listConfiguredAccountIds$4(cfg); if (ids.length === 0) return [DEFAULT_ACCOUNT_ID]; return ids.toSorted((a, b) => a.localeCompare(b)); } function resolveDefaultDiscordAccountId(cfg) { const ids = listDiscordAccountIds(cfg); if (ids.includes(DEFAULT_ACCOUNT_ID)) return DEFAULT_ACCOUNT_ID; return ids[0] ?? DEFAULT_ACCOUNT_ID; } function resolveAccountConfig$4(cfg, accountId) { const accounts = cfg.channels?.discord?.accounts; if (!accounts || typeof accounts !== "object") return; return accounts[accountId]; } function mergeDiscordAccountConfig(cfg, accountId) { const { accounts: _ignored, ...base } = cfg.channels?.discord ?? {}; const account = resolveAccountConfig$4(cfg, accountId) ?? {}; return { ...base, ...account }; } function resolveDiscordAccount(params) { const accountId = normalizeAccountId$1(params.accountId); const baseEnabled = params.cfg.channels?.discord?.enabled !== false; const merged = mergeDiscordAccountConfig(params.cfg, accountId); const accountEnabled = merged.enabled !== false; const enabled = baseEnabled && accountEnabled; const tokenResolution = resolveDiscordToken(params.cfg, { accountId }); return { accountId, enabled, name: merged.name?.trim() || void 0, token: tokenResolution.token, tokenSource: tokenResolution.source, config: merged }; } function listEnabledDiscordAccounts(cfg) { return listDiscordAccountIds(cfg).map((accountId) => resolveDiscordAccount({ cfg, accountId })).filter((account) => account.enabled); } //#endregion //#region src/imessage/accounts.ts function listConfiguredAccountIds$3(cfg) { const accounts = cfg.channels?.imessage?.accounts; if (!accounts || typeof accounts !== "object") return []; return Object.keys(accounts).filter(Boolean); } function listIMessageAccountIds(cfg) { const ids = listConfiguredAccountIds$3(cfg); if (ids.length === 0) return [DEFAULT_ACCOUNT_ID]; return ids.toSorted((a, b) => a.localeCompare(b)); } function resolveDefaultIMessageAccountId(cfg) { const ids = listIMessageAccountIds(cfg); if (ids.includes(DEFAULT_ACCOUNT_ID)) return DEFAULT_ACCOUNT_ID; return ids[0] ?? DEFAULT_ACCOUNT_ID; } function resolveAccountConfig$3(cfg, accountId) { const accounts = cfg.channels?.imessage?.accounts; if (!accounts || typeof accounts !== "object") return; return accounts[accountId]; } function mergeIMessageAccountConfig(cfg, accountId) { const { accounts: _ignored, ...base } = cfg.channels?.imessage ?? {}; const account = resolveAccountConfig$3(cfg, accountId) ?? {}; return { ...base, ...account }; } function resolveIMessageAccount(params) { const accountId = normalizeAccountId$1(params.accountId); const baseEnabled = params.cfg.channels?.imessage?.enabled !== false; const merged = mergeIMessageAccountConfig(params.cfg, accountId); const accountEnabled = merged.enabled !== false; const configured = Boolean(merged.cliPath?.trim() || merged.dbPath?.trim() || merged.service || merged.region?.trim() || merged.allowFrom && merged.allowFrom.length > 0 || merged.groupAllowFrom && merged.groupAllowFrom.length > 0 || merged.dmPolicy || merged.groupPolicy || typeof merged.includeAttachments === "boolean" || typeof merged.mediaMaxMb === "number" || typeof merged.textChunkLimit === "number" || merged.groups && Object.keys(merged.groups).length > 0); return { accountId, enabled: baseEnabled && accountEnabled, name: merged.name?.trim() || void 0, config: merged, configured }; } //#endregion //#region src/signal/accounts.ts function listConfiguredAccountIds$2(cfg) { const accounts = cfg.channels?.signal?.accounts; if (!accounts || typeof accounts !== "object") return []; return Object.keys(accounts).filter(Boolean); } function listSignalAccountIds(cfg) { const ids = listConfiguredAccountIds$2(cfg); if (ids.length === 0) return [DEFAULT_ACCOUNT_ID]; return ids.toSorted((a, b) => a.localeCompare(b)); } function resolveDefaultSignalAccountId(cfg) { const ids = listSignalAccountIds(cfg); if (ids.includes(DEFAULT_ACCOUNT_ID)) return DEFAULT_ACCOUNT_ID; return ids[0] ?? DEFAULT_ACCOUNT_ID; } function resolveAccountConfig$2(cfg, accountId) { const accounts = cfg.channels?.signal?.accounts; if (!accounts || typeof accounts !== "object") return; return accounts[accountId]; } function mergeSignalAccountConfig(cfg, accountId) { const { accounts: _ignored, ...base } = cfg.channels?.signal ?? {}; const account = resolveAccountConfig$2(cfg, accountId) ?? {}; return { ...base, ...account }; } function resolveSignalAccount(params) { const accountId = normalizeAccountId$1(params.accountId); const baseEnabled = params.cfg.channels?.signal?.enabled !== false; const merged = mergeSignalAccountConfig(params.cfg, accountId); const accountEnabled = merged.enabled !== false; const enabled = baseEnabled && accountEnabled; const host = merged.httpHost?.trim() || "127.0.0.1"; const port = merged.httpPort ?? 8080; const baseUrl = merged.httpUrl?.trim() || `http://${host}:${port}`; const configured = Boolean(merged.account?.trim() || merged.httpUrl?.trim() || merged.cliPath?.trim() || merged.httpHost?.trim() || typeof merged.httpPort === "number" || typeof merged.autoStart === "boolean"); return { accountId, enabled, name: merged.name?.trim() || void 0, baseUrl, configured, config: merged }; } function listEnabledSignalAccounts(cfg) { return listSignalAccountIds(cfg).map((accountId) => resolveSignalAccount({ cfg, accountId })).filter((account) => account.enabled); } //#endregion //#region src/channels/chat-type.ts function normalizeChatType(raw) { const value = raw?.trim().toLowerCase(); if (!value) return; if (value === "direct" || value === "dm") return "direct"; if (value === "group") return "group"; if (value === "channel") return "channel"; } //#endregion //#region src/slack/token.ts function normalizeSlackToken(raw) { const trimmed = raw?.trim(); return trimmed ? trimmed : void 0; } function resolveSlackBotToken(raw) { return normalizeSlackToken(raw); } function resolveSlackAppToken(raw) { return normalizeSlackToken(raw); } //#endregion //#region src/slack/accounts.ts function listConfiguredAccountIds$1(cfg) { const accounts = cfg.channels?.slack?.accounts; if (!accounts || typeof accounts !== "object") return []; return Object.keys(accounts).filter(Boolean); } function listSlackAccountIds(cfg) { const ids = listConfiguredAccountIds$1(cfg); if (ids.length === 0) return [DEFAULT_ACCOUNT_ID]; return ids.toSorted((a, b) => a.localeCompare(b)); } function resolveDefaultSlackAccountId(cfg) { const ids = listSlackAccountIds(cfg); if (ids.includes(DEFAULT_ACCOUNT_ID)) return DEFAULT_ACCOUNT_ID; return ids[0] ?? DEFAULT_ACCOUNT_ID; } function resolveAccountConfig$1(cfg, accountId) { const accounts = cfg.channels?.slack?.accounts; if (!accounts || typeof accounts !== "object") return; return accounts[accountId]; } function mergeSlackAccountConfig(cfg, accountId) { const { accounts: _ignored, ...base } = cfg.channels?.slack ?? {}; const account = resolveAccountConfig$1(cfg, accountId) ?? {}; return { ...base, ...account }; } function resolveSlackAccount(params) { const accountId = normalizeAccountId$1(params.accountId); const baseEnabled = params.cfg.channels?.slack?.enabled !== false; const merged = mergeSlackAccountConfig(params.cfg, accountId); const accountEnabled = merged.enabled !== false; const enabled = baseEnabled && accountEnabled; const allowEnv = accountId === DEFAULT_ACCOUNT_ID; const envBot = allowEnv ? resolveSlackBotToken(process.env.SLACK_BOT_TOKEN) : void 0; const envApp = allowEnv ? resolveSlackAppToken(process.env.SLACK_APP_TOKEN) : void 0; const configBot = resolveSlackBotToken(merged.botToken); const configApp = resolveSlackAppToken(merged.appToken); const botToken = configBot ?? envBot; const appToken = configApp ?? envApp; const botTokenSource = configBot ? "config" : envBot ? "env" : "none"; const appTokenSource = configApp ? "config" : envApp ? "env" : "none"; return { accountId, enabled, name: merged.name?.trim() || void 0, botToken, appToken, botTokenSource, appTokenSource, config: merged, groupPolicy: merged.groupPolicy, textChunkLimit: merged.textChunkLimit, mediaMaxMb: merged.mediaMaxMb, reactionNotifications: merged.reactionNotifications, reactionAllowlist: merged.reactionAllowlist, replyToMode: merged.replyToMode, replyToModeByChatType: merged.replyToModeByChatType, actions: merged.actions, slashCommand: merged.slashCommand, dm: merged.dm, channels: merged.channels }; } function listEnabledSlackAccounts(cfg) { return listSlackAccountIds(cfg).map((accountId) => resolveSlackAccount({ cfg, accountId })).filter((account) => account.enabled); } function resolveSlackReplyToMode(account, chatType) { const normalized = normalizeChatType(chatType ?? void 0); if (normalized && account.replyToModeByChatType?.[normalized] !== void 0) return account.replyToModeByChatType[normalized] ?? "off"; if (normalized === "direct" && account.dm?.replyToMode !== void 0) return account.dm.replyToMode; return account.replyToMode ?? "off"; } //#endregion //#region src/slack/threading-tool-context.ts function buildSlackThreadingToolContext(params) { const configuredReplyToMode = resolveSlackReplyToMode(resolveSlackAccount({ cfg: params.cfg, accountId: params.accountId }), params.context.ChatType); const effectiveReplyToMode = params.context.ThreadLabel ? "all" : configuredReplyToMode; const threadId = params.context.MessageThreadId ?? params.context.ReplyToId; return { currentChannelId: params.context.To?.startsWith("channel:") ? params.context.To.slice(8) : void 0, currentThreadTs: threadId != null ? String(threadId) : void 0, replyToMode: effectiveReplyToMode, hasRepliedRef: params.hasRepliedRef }; } //#endregion //#region src/routing/bindings.ts function normalizeBindingChannelId(raw) { const normalized = normalizeChatChannelId(raw); if (normalized) return normalized; return (raw ?? "").trim().toLowerCase() || null; } function listBindings(cfg) { return Array.isArray(cfg.bindings) ? cfg.bindings : []; } function listBoundAccountIds(cfg, channelId) { const normalizedChannel = normalizeBindingChannelId(channelId); if (!normalizedChannel) return []; const ids = /* @__PURE__ */ new Set(); for (const binding of listBindings(cfg)) { if (!binding || typeof binding !== "object") continue; const match = binding.match; if (!match || typeof match !== "object") continue; const channel = normalizeBindingChannelId(match.channel); if (!channel || channel !== normalizedChannel) continue; const accountId = typeof match.accountId === "string" ? match.accountId.trim() : ""; if (!accountId || accountId === "*") continue; ids.add(normalizeAccountId$1(accountId)); } return Array.from(ids).toSorted((a, b) => a.localeCompare(b)); } function resolveDefaultAgentBoundAccountId(cfg, channelId) { const normalizedChannel = normalizeBindingChannelId(channelId); if (!normalizedChannel) return null; const defaultAgentId = normalizeAgentId(resolveDefaultAgentId(cfg)); for (const binding of listBindings(cfg)) { if (!binding || typeof binding !== "object") continue; if (normalizeAgentId(binding.agentId) !== defaultAgentId) continue; const match = binding.match; if (!match || typeof match !== "object") continue; const channel = normalizeBindingChannelId(match.channel); if (!channel || channel !== normalizedChannel) continue; const accountId = typeof match.accountId === "string" ? match.accountId.trim() : ""; if (!accountId || accountId === "*") continue; return normalizeAccountId$1(accountId); } return null; } //#endregion //#region src/telegram/token.ts function resolveTelegramToken(cfg, opts = {}) { const accountId = normalizeAccountId$1(opts.accountId); const telegramCfg = cfg?.channels?.telegram; const resolveAccountCfg = (id) => { const accounts = telegramCfg?.accounts; if (!accounts || typeof accounts !== "object" || Array.isArray(accounts)) return; const direct = accounts[id]; if (direct) return direct; const matchKey = Object.keys(accounts).find((key) => normalizeAccountId$1(key) === id); return matchKey ? accounts[matchKey] : void 0; }; const accountCfg = resolveAccountCfg(accountId !== DEFAULT_ACCOUNT_ID ? accountId : DEFAULT_ACCOUNT_ID); const accountTokenFile = accountCfg?.tokenFile?.trim(); if (accountTokenFile) { if (!fs.existsSync(accountTokenFile)) { opts.logMissingFile?.(`channels.telegram.accounts.${accountId}.tokenFile not found: ${accountTokenFile}`); return { token: "", source: "none" }; } try { const token = fs.readFileSync(accountTokenFile, "utf-8").trim(); if (token) return { token, source: "tokenFile" }; } catch (err) { opts.logMissingFile?.(`channels.telegram.accounts.${accountId}.tokenFile read failed: ${String(err)}`); return { token: "", source: "none" }; } return { token: "", source: "none" }; } const accountToken = accountCfg?.botToken?.trim(); if (accountToken) return { token: accountToken, source: "config" }; const allowEnv = accountId === DEFAULT_ACCOUNT_ID; const tokenFile = telegramCfg?.tokenFile?.trim(); if (tokenFile && allowEnv) { if (!fs.existsSync(tokenFile)) { opts.logMissingFile?.(`channels.telegram.tokenFile not found: ${tokenFile}`); return { token: "", source: "none" }; } try { const token = fs.readFileSync(tokenFile, "utf-8").trim(); if (token) return { token, source: "tokenFile" }; } catch (err) { opts.logMissingFile?.(`channels.telegram.tokenFile read failed: ${String(err)}`); return { token: "", source: "none" }; } } const configToken = telegramCfg?.botToken?.trim(); if (configToken && allowEnv) return { token: configToken, source: "config" }; const envToken = allowEnv ? (opts.envToken ?? process.env.TELEGRAM_BOT_TOKEN)?.trim() : ""; if (envToken) return { token: envToken, source: "env" }; return { token: "", source: "none" }; } //#endregion //#region src/telegram/accounts.ts const debugAccounts = (...args) => { if (isTruthyEnvValue(process.env.OPENCLAW_DEBUG_TELEGRAM_ACCOUNTS)) console.warn("[telegram:accounts]", ...args); }; function listConfiguredAccountIds(cfg) { const accounts = cfg.channels?.telegram?.accounts; if (!accounts || typeof accounts !== "object") return []; const ids = /* @__PURE__ */ new Set(); for (const key of Object.keys(accounts)) { if (!key) continue; ids.add(normalizeAccountId$1(key)); } return [...ids]; } function listTelegramAccountIds(cfg) { const ids = Array.from(new Set([...listConfiguredAccountIds(cfg), ...listBoundAccountIds(cfg, "telegram")])); debugAccounts("listTelegramAccountIds", ids); if (ids.length === 0) return [DEFAULT_ACCOUNT_ID]; return ids.toSorted((a, b) => a.localeCompare(b)); } function resolveDefaultTelegramAccountId(cfg) { const boundDefault = resolveDefaultAgentBoundAccountId(cfg, "telegram"); if (boundDefault) return boundDefault; const ids = listTelegramAccountIds(cfg); if (ids.includes(DEFAULT_ACCOUNT_ID)) return DEFAULT_ACCOUNT_ID; return ids[0] ?? DEFAULT_ACCOUNT_ID; } function resolveAccountConfig(cfg, accountId) { const accounts = cfg.channels?.telegram?.accounts; if (!accounts || typeof accounts !== "object") return; const direct = accounts[accountId]; if (direct) return direct; const normalized = normalizeAccountId$1(accountId); const matchKey = Object.keys(accounts).find((key) => normalizeAccountId$1(key) === normalized); return matchKey ? accounts[matchKey] : void 0; } function mergeTelegramAccountConfig(cfg, accountId) { const { accounts: _ignored, ...base } = cfg.channels?.telegram ?? {}; const account = resolveAccountConfig(cfg, accountId) ?? {}; return { ...base, ...account }; } function resolveTelegramAccount(params) { const hasExplicitAccountId = Boolean(params.accountId?.trim()); const baseEnabled = params.cfg.channels?.telegram?.enabled !== false; const resolve = (accountId) => { const merged = mergeTelegramAccountConfig(params.cfg, accountId); const accountEnabled = merged.enabled !== false; const enabled = baseEnabled && accountEnabled; const tokenResolution = resolveTelegramToken(params.cfg, { accountId }); debugAccounts("resolve", { accountId, enabled, tokenSource: tokenResolution.source }); return { accountId, enabled, name: merged.name?.trim() || void 0, token: tokenResolution.token, tokenSource: tokenResolution.source, config: merged }; }; const primary = resolve(normalizeAccountId$1(params.accountId)); if (hasExplicitAccountId) return primary; if (primary.tokenSource !== "none") return primary; const fallbackId = resolveDefaultTelegramAccountId(params.cfg); if (fallbackId === primary.accountId) return primary; const fallback = resolve(fallbackId); if (fallback.tokenSource === "none") return primary; return fallback; } function listEnabledTelegramAccounts(cfg) { return listTelegramAccountIds(cfg).map((accountId) => resolveTelegramAccount({ cfg, accountId })).filter((account) => account.enabled); } //#endregion //#region src/whatsapp/normalize.ts const WHATSAPP_USER_JID_RE = /^(\d+)(?::\d+)?@s\.whatsapp\.net$/i; const WHATSAPP_LID_RE = /^(\d+)@lid$/i; function stripWhatsAppTargetPrefixes(value) { let candidate = value.trim(); for (;;) { const before = candidate; candidate = candidate.replace(/^whatsapp:/i, "").trim(); if (candidate === before) return candidate; } } function isWhatsAppGroupJid(value) { const candidate = stripWhatsAppTargetPrefixes(value); if (!candidate.toLowerCase().endsWith("@g.us")) return false; const localPart = candidate.slice(0, candidate.length - 5); if (!localPart || localPart.includes("@")) return false; return /^[0-9]+(-[0-9]+)*$/.test(localPart); } /** * Check if value looks like a WhatsApp user target (e.g. "41796666864:0@s.whatsapp.net" or "123@lid"). */ function isWhatsAppUserTarget(value) { const candidate = stripWhatsAppTargetPrefixes(value); return WHATSAPP_USER_JID_RE.test(candidate) || WHATSAPP_LID_RE.test(candidate); } /** * Extract the phone number from a WhatsApp user JID. * "41796666864:0@s.whatsapp.net" -> "41796666864" * "123456@lid" -> "123456" */ function extractUserJidPhone(jid) { const userMatch = jid.match(WHATSAPP_USER_JID_RE); if (userMatch) return userMatch[1]; const lidMatch = jid.match(WHATSAPP_LID_RE); if (lidMatch) return lidMatch[1]; return null; } function normalizeWhatsAppTarget(value) { const candidate = stripWhatsAppTargetPrefixes(value); if (!candidate) return null; if (isWhatsAppGroupJid(candidate)) return `${candidate.slice(0, candidate.length - 5)}@g.us`; if (isWhatsAppUserTarget(candidate)) { const phone = extractUserJidPhone(candidate); if (!phone) return null; const normalized = normalizeE164(phone); return normalized.length > 1 ? normalized : null; } if (candidate.includes("@")) return null; const normalized = normalizeE164(candidate); return normalized.length > 1 ? normalized : null; } //#endregion //#region src/channels/plugins/group-mentions.ts function normalizeDiscordSlug(value) { if (!value) return ""; let text = value.trim().toLowerCase(); if (!text) return ""; text = text.replace(/^[@#]+/, ""); text = text.replace(/[\s_]+/g, "-"); text = text.replace(/[^a-z0-9-]+/g, "-"); text = text.replace(/-{2,}/g, "-").replace(/^-+|-+$/g, ""); return text; } function normalizeSlackSlug(raw) { const trimmed = raw?.trim().toLowerCase() ?? ""; if (!trimmed) return ""; return trimmed.replace(/\s+/g, "-").replace(/[^a-z0-9#@._+-]+/g, "-").replace(/-{2,}/g, "-").replace(/^[-.]+|[-.]+$/g, ""); } function parseTelegramGroupId(value) { const raw = value?.trim() ?? ""; if (!raw) return { chatId: void 0, topicId: void 0 }; const parts = raw.split(":").filter(Boolean); if (parts.length >= 3 && parts[1] === "topic" && /^-?\d+$/.test(parts[0]) && /^\d+$/.test(parts[2])) return { chatId: parts[0], topicId: parts[2] }; if (parts.length >= 2 && /^-?\d+$/.test(parts[0]) && /^\d+$/.test(parts[1])) return { chatId: parts[0], topicId: parts[1] }; return { chatId: raw, topicId: void 0 }; } function resolveTelegramRequireMention(params) { const { cfg, chatId, topicId } = params; if (!chatId) return; const groupConfig = cfg.channels?.telegram?.groups?.[chatId]; const groupDefault = cfg.channels?.telegram?.groups?.["*"]; const topicConfig = topicId && groupConfig?.topics ? groupConfig.topics[topicId] : void 0; const defaultTopicConfig = topicId && groupDefault?.topics ? groupDefault.topics[topicId] : void 0; if (typeof topicConfig?.requireMention === "boolean") return topicConfig.requireMention; if (typeof defaultTopicConfig?.requireMention === "boolean") return defaultTopicConfig.requireMention; if (typeof groupConfig?.requireMention === "boolean") return groupConfig.requireMention; if (typeof groupDefault?.requireMention === "boolean") return groupDefault.requireMention; } function resolveDiscordGuildEntry(guilds, groupSpace) { if (!guilds || Object.keys(guilds).length === 0) return null; const space = groupSpace?.trim() ?? ""; if (space && guilds[space]) return guilds[space]; const normalized = normalizeDiscordSlug(space); if (normalized && guilds[normalized]) return guilds[normalized]; if (normalized) { const match = Object.values(guilds).find((entry) => normalizeDiscordSlug(entry?.slug ?? void 0) === normalized); if (match) return match; } return guilds["*"] ?? null; } function resolveTelegramGroupRequireMention(params) { const { chatId, topicId } = parseTelegramGroupId(params.groupId); const requireMention = resolveTelegramRequireMention({ cfg: params.cfg, chatId, topicId }); if (typeof requireMention === "boolean") return requireMention; return resolveChannelGroupRequireMention({ cfg: params.cfg, channel: "telegram", groupId: chatId ?? params.groupId, accountId: params.accountId }); } function resolveWhatsAppGroupRequireMention(params) { return resolveChannelGroupRequireMention({ cfg: params.cfg, channel: "whatsapp", groupId: params.groupId, accountId: params.accountId }); } function resolveIMessageGroupRequireMention(params) { return resolveChannelGroupRequireMention({ cfg: params.cfg, channel: "imessage", groupId: params.groupId, accountId: params.accountId }); } function resolveDiscordGroupRequireMention(params) { const guildEntry = resolveDiscordGuildEntry(params.cfg.channels?.discord?.guilds, params.groupSpace); const channelEntries = guildEntry?.channels; if (channelEntries && Object.keys(channelEntries).length > 0) { const groupChannel = params.groupChannel; const channelSlug = normalizeDiscordSlug(groupChannel); const entry = (params.groupId ? channelEntries[params.groupId] : void 0) ?? (channelSlug ? channelEntries[channelSlug] ?? channelEntries[`#${channelSlug}`] : void 0) ?? (groupChannel ? channelEntries[normalizeDiscordSlug(groupChannel)] : void 0); if (entry && typeof entry.requireMention === "boolean") return entry.requireMention; } if (typeof guildEntry?.requireMention === "boolean") return guildEntry.requireMention; return true; } function resolveGoogleChatGroupRequireMention(params) { return resolveChannelGroupRequireMention({ cfg: params.cfg, channel: "googlechat", groupId: params.groupId, accountId: params.accountId }); } function resolveGoogleChatGroupToolPolicy(params) { return resolveChannelGroupToolsPolicy({ cfg: params.cfg, channel: "googlechat", groupId: params.groupId, accountId: params.accountId, senderId: params.senderId, senderName: params.senderName, senderUsername: params.senderUsername, senderE164: params.senderE164 }); } function resolveSlackGroupRequireMention(params) { const channels = resolveSlackAccount({ cfg: params.cfg, accountId: params.accountId }).channels ?? {}; if (Object.keys(channels).length === 0) return true; const channelId = params.groupId?.trim(); const channelName = params.groupChannel?.replace(/^#/, ""); const normalizedName = normalizeSlackSlug(channelName); const candidates = [ channelId ?? "", channelName ? `#${channelName}` : "", channelName ?? "", normalizedName ].filter(Boolean); let matched; for (const candidate of candidates) if (candidate && channels[candidate]) { matched = channels[candidate]; break; } const fallback = channels["*"]; const resolved = matched ?? fallback; if (typeof resolved?.requireMention === "boolean") return resolved.requireMention; return true; } function resolveBlueBubblesGroupRequireMention(params) { return resolveChannelGroupRequireMention({ cfg: params.cfg, channel: "bluebubbles", groupId: params.groupId, accountId: params.accountId }); } function resolveTelegramGroupToolPolicy(params) { const { chatId } = parseTelegramGroupId(params.groupId); return resolveChannelGroupToolsPolicy({ cfg: params.cfg, channel: "telegram", groupId: chatId ?? params.groupId, accountId: params.accountId, senderId: params.senderId, senderName: params.senderName, senderUsername: params.senderUsername, senderE164: params.senderE164 }); } function resolveWhatsAppGroupToolPolicy(params) { return resolveChannelGroupToolsPolicy({ cfg: params.cfg, channel: "whatsapp", groupId: params.groupId, accountId: params.accountId, senderId: params.senderId, senderName: params.senderName, senderUsername: params.senderUsername, senderE164: params.senderE164 }); } function resolveIMessageGroupToolPolicy(params) { return resolveChannelGroupToolsPolicy({ cfg: params.cfg, channel: "imessage", groupId: params.groupId, accountId: params.accountId, senderId: params.senderId, senderName: params.senderName, senderUsername: params.senderUsername, senderE164: params.senderE164 }); } function resolveDiscordGroupToolPolicy(params) { const guildEntry = resolveDiscordGuildEntry(params.cfg.channels?.discord?.guilds, params.groupSpace); const channelEntries = guildEntry?.channels; if (channelEntries && Object.keys(channelEntries).length > 0) { const groupChannel = params.groupChannel; const channelSlug = normalizeDiscordSlug(groupChannel); const entry = (params.groupId ? channelEntries[params.groupId] : void 0) ?? (channelSlug ? channelEntries[channelSlug] ?? channelEntries[`#${channelSlug}`] : void 0) ?? (groupChannel ? channelEntries[normalizeDiscordSlug(groupChannel)] : void 0); const senderPolicy = resolveToolsBySender({ toolsBySender: entry?.toolsBySender, senderId: params.senderId, senderName: params.senderName, senderUsername: params.senderUsername, senderE164: params.senderE164 }); if (senderPolicy) return senderPolicy; if (entry?.tools) return entry.tools; } const guildSenderPolicy = resolveToolsBySender({ toolsBySender: guildEntry?.toolsBySender, senderId: params.senderId, senderName: params.senderName, senderUsername: params.senderUsername, senderE164: params.senderE164 }); if (guildSenderPolicy) return guildSenderPolicy; if (guildEntry?.tools) return guildEntry.tools; } function resolveSlackGroupToolPolicy(params) { const channels = resolveSlackAccount({ cfg: params.cfg, accountId: params.accountId }).channels ?? {}; if (Object.keys(channels).length === 0) return; const channelId = params.groupId?.trim(); const channelName = params.groupChannel?.replace(/^#/, ""); const normalizedName = normalizeSlackSlug(channelName); const candidates = [ channelId ?? "", channelName ? `#${channelName}` : "", channelName ?? "", normalizedName ].filter(Boolean); let matched; for (const candidate of candidates) if (candidate && channels[candidate]) { matched = channels[candidate]; break; } const resolved = matched ?? channels["*"]; const senderPolicy = resolveToolsBySender({ toolsBySender: resolved?.toolsBySender, senderId: params.senderId, senderName: params.senderName, senderUsername: params.senderUsername, senderE164: params.senderE164 }); if (senderPolicy) return senderPolicy; if (resolved?.tools) return resolved.tools; } function resolveBlueBubblesGroupToolPolicy(params) { return resolveChannelGroupToolsPolicy({ cfg: params.cfg, channel: "bluebubbles", groupId: params.groupId, accountId: params.accountId, senderId: params.senderId, senderName: params.senderName, senderUsername: params.senderUsername, senderE164: params.senderE164 }); } //#endregion //#region src/channels/dock.ts const formatLower = (allowFrom) => allowFrom.map((entry) => String(entry).trim()).filter(Boolean).map((entry) => entry.toLowerCase()); const escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); const DOCKS = { telegram: { id: "telegram", capabilities: { chatTypes: [ "direct", "group", "channel", "thread" ], nativeCommands: true, blockStreaming: true }, outbound: { textChunkLimit: 4e3 }, config: { resolveAllowFrom: ({ cfg, accountId }) => (resolveTelegramAccount({ cfg, accountId }).config.allowFrom ?? []).map((entry) => String(entry)), formatAllowFrom: ({ allowFrom }) => allowFrom.map((entry) => String(entry).trim()).filter(Boolean).map((entry) => entry.replace(/^(telegram|tg):/i, "")).map((entry) => entry.toLowerCase()) }, groups: { resolveRequireMention: resolveTelegramGroupRequireMention, resolveToolPolicy: resolveTelegramGroupToolPolicy }, threading: { resolveReplyToMode: ({ cfg }) => cfg.channels?.telegram?.replyToMode ?? "first", buildToolContext: ({ context, hasRepliedRef }) => { const threadId = context.MessageThreadId ?? context.ReplyToId; return { currentChannelId: context.To?.trim() || void 0, currentThreadTs: threadId != null ? String(threadId) : void 0, hasRepliedRef }; } } }, whatsapp: { id: "whatsapp", capabilities: { chatTypes: ["direct", "group"], polls: true, reactions: true, media: true }, commands: { enforceOwnerForCommands: true, skipWhenConfigEmpty: true }, outbound: { textChunkLimit: 4e3 }, config: { resolveAllowFrom: ({ cfg, accountId }) => resolveWhatsAppAccount({ cfg, accountId }).allowFrom ?? [], formatAllowFrom: ({ allowFrom }) => allowFrom.map((entry) => String(entry).trim()).filter((entry) => Boolean(entry)).map((entry) => entry === "*" ? entry : normalizeWhatsAppTarget(entry)).filter((entry) => Boolean(entry)) }, groups: { resolveRequireMention: resolveWhatsAppGroupRequireMention, resolveToolPolicy: resolveWhatsAppGroupToolPolicy, resolveGroupIntroHint: () => "WhatsApp IDs: SenderId is the participant JID; [message_id: ...] is the message id for reactions (use SenderId as participant)." }, mentions: { stripPatterns: ({ ctx }) => { const selfE164 = (ctx.To ?? "").replace(/^whatsapp:/, ""); if (!selfE164) return []; const escaped = escapeRegExp(selfE164); return [escaped, `@${escaped}`]; } }, threading: { buildToolContext: ({ context, hasRepliedRef }) => { return { currentChannelId: context.From?.trim() || context.To?.trim() || void 0, currentThreadTs: context.ReplyToId, hasRepliedRef }; } } }, discord: { id: "discord", capabilities: { chatTypes: [ "direct", "channel", "thread" ], polls: true, reactions: true, media: true, nativeCommands: true, threads: true }, outbound: { textChunkLimit: 2e3 }, streaming: { blockStreamingCoalesceDefaults: { minChars: 1500, idleMs: 1e3 } }, elevated: { allowFromFallback: ({ cfg }) => cfg.channels?.discord?.dm?.allowFrom }, config: { resolveAllowFrom: ({ cfg, accountId }) => (resolveDiscordAccount({ cfg, accountId }).config.dm?.allowFrom ?? []).map((entry) => String(entry)), formatAllowFrom: ({ allowFrom }) => formatLower(allowFrom) }, groups: { resolveRequireMention: resolveDiscordGroupRequireMention, resolveToolPolicy: resolveDiscordGroupToolPolicy }, mentions: { stripPatterns: () => ["<@!?\\d+>"] }, threading: { resolveReplyToMode: ({ cfg }) => cfg.channels?.discord?.replyToMode ?? "off", buildToolContext: ({ context, hasRepliedRef }) => ({ currentChannelId: context.To?.trim() || void 0, currentThreadTs: context.ReplyToId, hasRepliedRef }) } }, googlechat: { id: "googlechat", capabilities: { chatTypes: [ "direct", "group", "thread" ], reactions: true, media: true, threads: true, blockStreaming: true }, outbound: { textChunkLimit: 4e3 }, config: { resolveAllowFrom: ({ cfg, accountId }) => { const channel = cfg.channels?.googlechat; const normalized = normalizeAccountId$1(accountId); return ((channel?.accounts?.[normalized] ?? channel?.accounts?.[Object.keys(channel?.accounts ?? {}).find((key) => key.toLowerCase() === normalized.toLowerCase()) ?? ""])?.dm?.allowFrom ?? channel?.dm?.allowFrom ?? []).map((entry) => String(entry)); }, formatAllowFrom: ({ allowFrom }) => allowFrom.map((entry) => String(entry).trim()).filter(Boolean).map((entry) => entry.replace(/^(googlechat|google-chat|gchat):/i, "").replace(/^user:/i, "").replace(/^users\//i, "").toLowerCase()) }, groups: { resolveRequireMention: resolveGoogleChatGroupRequireMention, resolveToolPolicy: resolveGoogleChatGroupToolPolicy }, threading: { resolveReplyToMode: ({ cfg }) => cfg.channels?.googlechat?.replyToMode ?? "off", buildToolContext: ({ context, hasRepliedRef }) => { const threadId = context.MessageThreadId ?? context.ReplyToId; return { currentChannelId: context.To?.trim() || void 0, currentThreadTs: threadId != null ? String(threadId) : void 0, hasRepliedRef }; } } }, slack: { id: "slack", capabilities: { chatTypes: [ "direct", "channel", "thread" ], reactions: true, media: true, nativeCommands: true, threads: true }, outbound: { textChunkLimit: 4e3 }, streaming: { blockStreamingCoalesceDefaults: { minChars: 1500, idleMs: 1e3 } }, config: { resolveAllowFrom: ({ cfg, accountId }) => (resolveSlackAccount({ cfg, accountId }).dm?.allowFrom ?? []).map((entry) => String(entry)), formatAllowFrom: ({ allowFrom }) => formatLower(allowFrom) }, groups: { resolveRequireMention: resolveSlackGroupRequireMention, resolveToolPolicy: resolveSlackGroupToolPolicy }, threading: { resolveReplyToMode: ({ cfg, accountId, chatType }) => resolveSlackReplyToMode(resolveSlackAccount({ cfg, accountId }), chatType), allowTagsWhenOff: true, buildToolContext: (params) => buildSlackThreadingToolContext(params) } }, signal: { id: "signal", capabilities: { chatTypes: ["direct", "group"], reactions: true, media: true }, outbound: { textChunkLimit: 4e3 }, streaming: { blockStreamingCoalesceDefaults: { minChars: 1500, idleMs: 1e3 } }, config: { resolveAllowFrom: ({ cfg, accountId }) => (resolveSignalAccount({ cfg, accountId }).config.allowFrom ?? []).map((entry) => String(entry)), formatAllowFrom: ({ allowFrom }) => allowFrom.map((entry) => String(entry).trim()).filter(Boolean).map((entry) => entry === "*" ? "*" : normalizeE164(entry.replace(/^signal:/i, ""))).filter(Boolean) }, threading: { buildToolContext: ({ context, hasRepliedRef }) => { return { currentChannelId: (context.ChatType?.toLowerCase() === "direct" ? context.From ?? context.To : context.To)?.trim() || void 0, currentThreadTs: context.ReplyToId, hasRepliedRef }; } } }, imessage: { id: "imessage", capabilities: { chatTypes: ["direct", "group"], reactions: true, media: true }, outbound: { textChunkLimit: 4e3 }, config: { resolveAllowFrom: ({ cfg, accountId }) => (resolveIMessageAccount({ cfg, accountId }).config.allowFrom ?? []).map((entry) => String(entry)), formatAllowFrom: ({ allowFrom }) => allowFrom.map((entry) => String(entry).trim()).filter(Boolean) }, groups: { resolveRequireMention: resolveIMessageGroupRequireMention, resolveToolPolicy: resolveIMessageGroupToolPolicy }, threading: { buildToolContext: ({ context, hasRepliedRef }) => { return { currentChannelId: (context.ChatType?.toLowerCase() === "direct" ? context.From ?? context.To : context.To)?.trim() || void 0, currentThreadTs: context.ReplyToId, hasRepliedRef }; } } } }; function buildDockFromPlugin(plugin) { return { id: plugin.id, capabilities: plugin.capabilities, commands: plugin.commands, outbound: plugin.outbound?.textChunkLimit ? { textChunkLimit: plugin.outbound.textChunkLimit } : void 0, streaming: plugin.streaming ? { blockStreamingCoalesceDefaults: plugin.streaming.blockStreamingCoalesceDefaults } : void 0, elevated: plugin.elevated, config: plugin.config ? { resolveAllowFrom: plugin.config.resolveAllowFrom, formatAllowFrom: plugin.config.formatAllowFrom } : void 0, groups: plugin.groups, mentions: plugin.mentions, threading: plugin.threading, agentPrompt: plugin.agentPrompt }; } function listPluginDockEntries() { const registry = requireActivePluginRegistry(); const entries = []; const seen = /* @__PURE__ */ new Set(); for (const entry of registry.channels) { const plugin = entry.plugin; const id = String(plugin.id).trim(); if (!id || seen.has(id)) continue; seen.add(id); if (CHAT_CHANNEL_ORDER.includes(plugin.id)) continue; const dock = entry.dock ?? buildDockFromPlugin(plugin); entries.push({ id: plugin.id, dock, order: plugin.meta.order }); } return entries; } function listChannelDocks() { const baseEntries = CHAT_CHANNEL_ORDER.map((id) => ({ id, dock: DOCKS[id], order: getChatChannelMeta(id).order })); const pluginEntries = listPluginDockEntries(); const combined = [...baseEntries, ...pluginEntries]; combined.sort((a, b) => { const indexA = CHAT_CHANNEL_ORDER.indexOf(a.id); const indexB = CHAT_CHANNEL_ORDER.indexOf(b.id); const orderA = a.order ?? (indexA === -1 ? 999 : indexA); const orderB = b.order ?? (indexB === -1 ? 999 : indexB); if (orderA !== orderB) return orderA - orderB; return String(a.id).localeCompare(String(b.id)); }); return combined.map((entry) => entry.dock); } function getChannelDock(id) { const core = DOCKS[id]; if (core) return core; const pluginEntry = requireActivePluginRegistry().channels.find((entry) => entry.plugin.id === id); if (!pluginEntry) return; return pluginEntry.dock ?? buildDockFromPlugin(pluginEntry.plugin); } //#endregion //#region src/channels/targets.ts function normalizeTargetId(kind, id) { return `${kind}:${id}`.toLowerCase(); } function buildMessagingTarget(kind, id, raw) { return { kind, id, raw, normalized: normalizeTargetId(kind, id) }; } function ensureTargetId(params) { if (!params.pattern.test(params.candidate)) throw new Error(params.errorMessage); return params.candidate; } function requireTargetKind(params) { const kindLabel = params.kind; if (!params.target) throw new Error(`${params.platform} ${kindLabel} id is required.`); if (params.target.kind !== params.kind) throw new Error(`${params.platform} ${kindLabel} id is required (use ${kindLabel}:<id>).`); return params.target.id; } //#endregion //#region src/slack/targets.ts function parseSlackTarget(raw, options = {}) { const trimmed = raw.trim(); if (!trimmed) return; const mentionMatch = trimmed.match(/^<@([A-Z0-9]+)>$/i); if (mentionMatch) return buildMessagingTarget("user", mentionMatch[1], trimmed); if (trimmed.startsWith("user:")) { const id = trimmed.slice(5).trim(); return id ? buildMessagingTarget("user", id, trimmed) : void 0; } if (trimmed.startsWith("channel:")) { const id = trimmed.slice(8).trim(); return id ? buildMessagingTarget("channel", id, trimmed) : void 0; } if (trimmed.startsWith("slack:")) { const id = trimmed.slice(6).trim(); return id ? buildMessagingTarget("user", id, trimmed) : void 0; } if (trimmed.startsWith("@")) return buildMessagingTarget("user", ensureTargetId({ candidate: trimmed.slice(1).trim(), pattern: /^[A-Z0-9]+$/i, errorMessage: "Slack DMs require a user id (use user:<id> or <@id>)" }), trimmed); if (trimmed.startsWith("#")) return buildMessagingTarget("channel", ensureTargetId({ candidate: trimmed.slice(1).trim(), pattern: /^[A-Z0-9]+$/i, errorMessage: "Slack channels require a channel id (use channel:<id>)" }), trimmed); if (options.defaultKind) return buildMessagingTarget(options.defaultKind, trimmed, trimmed); return buildMessagingTarget("channel", trimmed, trimmed); } function resolveSlackChannelId(raw) { return requireTargetKind({ platform: "Slack", target: parseSlackTarget(raw, { defaultKind: "channel" }), kind: "channel" }); } //#endregion //#region src/channels/plugins/normalize/slack.ts function normalizeSlackMessagingTarget(raw) { return parseSlackTarget(raw, { defaultKind: "channel" })?.normalized; } function looksLikeSlackTargetId(raw) { const trimmed = raw.trim(); if (!trimmed) return false; if (/^<@([A-Z0-9]+)>$/i.test(trimmed)) return true; if (/^(user|channel):/i.test(trimmed)) return true; if (/^slack:/i.test(trimmed)) return true; if (/^[@#]/.test(trimmed)) return true; return /^[CUWGD][A-Z0-9]{8,}$/i.test(trimmed); } //#endregion //#region src/channels/plugins/directory-config.ts async function listSlackDirectoryPeersFromConfig(params) { const account = resolveSlackAccount({ cfg: params.cfg, accountId: params.accountId }); const q = params.quer