@gguf/claw
Version:
WhatsApp gateway CLI (Baileys web) with Pi RPC agent
1,359 lines (1,340 loc) • 312 kB
JavaScript
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