通道适配器

LibreFang 通过 45 个通道适配器 连接各类消息平台,让用户可以在所有主流通信平台上与 Agent 交互。适配器覆盖消费级即时通讯、企业协作、社交媒体、社区平台、隐私优先协议、通用 Webhook 以及可扩展的 Sidecar 进程。

所有适配器共享统一的基础能力:通过 watch::channel 实现优雅关闭、连接失败时指数退避重试、使用 Zeroizing<String> 保护密钥、超出平台长度限制时自动拆分消息、支持按通道覆盖模型/提示词、DM/群组策略控制、按用户速率限制,以及输出格式化(Markdown、TelegramHTML、SlackMrkdwn、PlainText)。

目录

  • 核心消息渠道 -- Telegram、Discord、Slack、WhatsApp、微信、Signal、Matrix、Email、WebChat
  • 企业协作 -- Teams、Mattermost、Google Chat、Webex、飞书、Zulip、Flock、Twist、Pumble、Guilded
  • 社交与社区 -- Reddit、Mastodon、Bluesky、Twitch、Gitter、Revolt、Keybase
  • 集成与协议 -- LINE、Threema、钉钉、QQ、企业微信、IRC、XMPP、Ntfy、Gotify、Nextcloud、Mumble、Webhook、Voice
  • 自定义适配器 -- 编写自定义适配器

通道配置

大多数通道配置位于 ~/.librefang/config.toml[channels] 段落下,每个内置通道是一个子段落。Telegram 现已改为 sidecar 模式,使用 [[sidecar_channels]] 声明——可选 Python(默认)或 Rust(自 #5831 起;见 Rust Telegram sidecar 适配器):

# Telegram via Python sidecar(默认)
[[sidecar_channels]]
name = "telegram"
command = "python3"
args = ["-m", "librefang.sidecar.adapters.telegram"]
channel_type = "telegram"
[sidecar_channels.env]
TELEGRAM_BOT_TOKEN = "..."
# ALLOWED_USERS = "123456789,@alice"

# Telegram via Rust sidecar(自 #5831 起,不需要 Python 运行时)
# [[sidecar_channels]]
# name = "telegram"
# command = "/abs/path/to/target/release/librefang-sidecar-telegram"
# channel_type = "telegram"
# [sidecar_channels.env]
# TELEGRAM_BOT_TOKEN = "..."

# Discord (sidecar)
[[sidecar_channels]]
name = "discord"
command = "python3"
args = ["-m", "librefang.sidecar.adapters.discord"]
channel_type = "discord"
[sidecar_channels.env]
DISCORD_BOT_TOKEN = "..."

# Slack (sidecar)
[[sidecar_channels]]
name = "slack"
command = "python3"
args = ["-m", "librefang.sidecar.adapters.slack"]
channel_type = "slack"
[sidecar_channels.env]
SLACK_APP_TOKEN = "xapp-..."
SLACK_BOT_TOKEN = "xoxb-..."

# 企业示例(Teams — sidecar)
[[sidecar_channels]]
name = "teams"
command = "python3"
args = ["-m", "librefang.sidecar.adapters.teams"]
channel_type = "teams"
[sidecar_channels.env]
TEAMS_APP_ID = "00000000-0000-0000-0000-000000000000"
TEAMS_WEBHOOK_PORT = "8459"

# 社交示例(Mastodon — sidecar)
[[sidecar_channels]]
name = "mastodon"
command = "python3"
args = ["-m", "librefang.sidecar.adapters.mastodon"]
channel_type = "mastodon"
[sidecar_channels.env]
MASTODON_INSTANCE_URL = "https://mastodon.social"

通用字段

  • bot_token_env / token_env -- 保存 Bot/访问令牌的环境变量名。LibreFang 在启动时从该环境变量读取令牌。所有密钥以 Zeroizing<String> 存储,释放时自动清零。
  • default_agent -- 当没有特定路由规则时,接收消息的 Agent 名称(或 ID)。
  • allowed_users -- 可选的平台用户 ID 白名单。留空表示允许所有用户。
  • overrides -- 可选的按通道行为覆盖(参见下方通道覆盖)。

环境变量参考(核心通道)

通道必需的环境变量
TelegramTELEGRAM_BOT_TOKEN
DiscordDISCORD_BOT_TOKEN
SlackSLACK_BOT_TOKEN, SLACK_APP_TOKEN
WhatsAppWA_ACCESS_TOKEN, WA_PHONE_ID, WA_VERIFY_TOKEN
MatrixMATRIX_TOKEN
EmailEMAIL_PASSWORD

其他通道的环境变量详见上方全部 45 个通道表格。


通道覆盖

ChannelOverrides 用来让 agent 按通道自定义行为。Sidecar 迁移后通道级这一层已经移除([channels.<name>.overrides] 子表挂在已删的进程内 ChannelsConfig 上),只剩 agent.toml 里的 agent 级 覆盖。原来通道级表里的大多数 knob 现在改到对应 sidecar 的 [sidecar_channels.env] 环境变量里 —— 详见频道配置指南里的迁移对照表

  • Agent 级agent.toml —— 只对该 agent 在所有通道上生效。加顶层 [channel_overrides] 段。

解析顺序(迁移后):agent 级 → sidecar env → 内置默认。哪一层设了哪一层赢。

# agent.toml —— 每个 agent 自己的覆盖(比如 "这个 agent 永远回 DM")
[channel_overrides]
dm_policy = "always"
group_policy = "trigger_only"
group_trigger_patterns = ["(?i)\\bmy-bot\\b"]
# config.toml —— 通道级覆盖(历史 —— 已不再被识别)。
# #5463 之前,使用该通道的每个 agent 都继承下面这套默认。保留只为
# 迁移对照参考;新写法把等价值设在 agent 上(`agent.toml [channel_overrides]`)
# 或写到 sidecar 的 `[sidecar_channels.env]` 环境变量(如 `*_DM_POLICY`)。
[channels.discord.overrides]
model = "gemini-2.5-flash"
system_prompt = "You are a concise Discord assistant. Keep replies under 200 words."
dm_policy = "respond"
group_policy = "mention_only"
rate_limit_per_user = 10
threading = true
output_format = "markdown"
usage_footer = "compact"

覆盖字段

字段类型默认值说明
modelOption<String>Agent 默认值覆盖此通道使用的 LLM 模型。
system_promptOption<String>Agent 默认值覆盖此通道的系统提示词。
dm_policyDmPolicyRespond如何处理私信。
group_policyGroupPolicyMentionOnly如何处理群组/频道消息。
rate_limit_per_useru320(无限制)每用户每分钟最大消息数。
threadingboolfalse以线程回复形式发送响应(仅限支持的平台)。
output_formatOption<OutputFormat>Markdown此通道的输出格式。
usage_footerOption<UsageFooterMode>是否在响应末尾附加 token 用量信息。

格式化器、速率限制器与策略

输出格式化器

formatter 模块(librefang-channels/src/formatter.rs)将 LLM 输出的 Markdown 转换为平台原生格式:

OutputFormat目标格式说明
Markdown标准 Markdown默认值;原样传递。
TelegramHtmlTelegram HTML 子集**bold** 转换为 <b>`code` 转换为 <code> 等。
SlackMrkdwnSlack mrkdwn**bold** 转换为 *bold*、链接转换为 <url|text> 等。
PlainText纯文本去除所有格式。

按用户速率限制器

ChannelRateLimiterlibrefang-channels/src/rate_limiter.rs)使用 DashMap 跟踪每用户消息计数。当通道覆盖中设置了 rate_limit_per_user 时,限制器以滑动窗口方式限制每分钟最多 N 条消息。超出限制的消息会收到友好的拒绝提示。

DM 策略

控制适配器如何处理私信:

DmPolicy行为
Respond回复所有私信(默认)。
AllowedOnly仅回复 allowed_users 中用户的私信。
Ignore静默丢弃所有私信。

群组策略

控制适配器如何处理群聊、频道和房间中的消息:

GroupPolicy行为
All回复群组中的所有消息。
MentionOnly仅在 Bot 被 @提及时回复(默认)。
CommandsOnly仅回复 /command 命令消息。
Ignore静默忽略所有群组消息。

策略在 dispatch_message() 中执行,早于消息到达 Agent 循环。这意味着被忽略的消息不会消耗任何 LLM token。


Agent 路由

AgentRouter 决定哪个 Agent 接收传入的消息。路由逻辑如下:

  1. 按通道默认值:每个通道配置都有一个 default_agent 字段。来自该通道的消息会发送到该 Agent。
  2. 用户-Agent 绑定:如果用户之前已与特定 Agent 关联(通过命令或配置),来自该用户的消息会路由到该 Agent。
  3. 命令前缀:用户可以在聊天中发送类似 /agent coder 的命令来切换 Agent。后续消息将路由到 "coder" Agent。
  4. 兜底:如果没有匹配的路由规则,消息会发送到第一个可用的 Agent。

当渠道配置了 default_agent 时,来自该渠道的消息会跳过语义路由和关键词路由,直接发送到指定的 Agent。用户仍可通过 /agent 命令手动切换 Agent。


出站标记

ChannelOverrides 里设 prefix_agent_name 给每条出站消息加 agent 名前缀。多个 agent 共享一个频道时有用——读者一眼能看出是哪个 agent 回的。

风格包装效果
Off(默认)不变——跟 agent 文本字节相同
Bracket[agent-name] 回复内容
BoldBracket**[agent-name]** 回复内容(粗体渲染依赖通道的输出格式)

prefix_agent_name 设到 agent 清单(agent.toml)的 [channel_overrides] 里;迁移前的 [channels.<name>.overrides] 位置已不再被识别。

# agent.toml
[channel_overrides]
prefix_agent_name = "BoldBracket"

包装在每个非流式成功路径上跑一次(auto_reply、kernel-streaming-with-status accumulated、streaming-fallback buffered_text、非流式 fallback、re-resolution 后重试、dispatch_with_blocks)。streaming tee(每个 chunk 到达就转发)包装——前缀会跟 chunk 边界穿插。

Signal 默认纯文本

Signal 默认 OutputFormat::PlainText,原因是 signal-cli 会把 Markdown 的 *_ 当字面量渲染,保留会产生可见噪音。这个映射写在 default_output_format_for_channel("signal") 中(见 crates/librefang-channels/src/formatter.rs),按 channel_type 字符串匹配;sidecar 迁移后 [[sidecar_channels]] 里仍然保留 channel_type = "signal",所以默认值依旧生效。

[[sidecar_channels]] 目前不暴露 output_format 的逐通道覆盖(原 overrides 块挂在 [channels.signal] 之类的进程内配置上,已随之一起删除)。如果某个 agent 必须原样发送 Markdown,请在 agent 这一端翻转 format。

其他通道保留各自的 formatter 默认值——只翻了 Signal。

反应表情与处理状态

几个 adapter 可以贴一个反应到用户消息表示"我在处理",发送回复时移除。各通道独立:

  • Slackreactions_enabled 开关(环境变量 SLACK_REACTIONS,默认 true)。收消息加 👀,回复时换成 ✅。already_reacted / no_reaction 错误静默忽略(fail-open)。
  • Feishu — 收消息加 Typing reaction,回复发送时移除。两个调用都 fire-and-forget;API 失败 warn! 但绝不阻塞消息处理。

要在自定义 adapter 上加同样能力,把 reaction 调用 tokio::spawn 出去,把 (reaction_id, message_id) 存在按 chat id 索引的 map 里。

Feishu @提及保留

Feishu 之前会静默删掉传入文本里的 @_user_N 占位符。Feishu adapter 现在用 mention payload 解析出的 @<display-name> 替换(name 缺失时回退 open_id),并把 @_all 重写为 @all。agent 看到原始对话语气——"@alice 你能 check 下吗?"——而不是裸标点。

Signal 媒体附件

已下线的进程内 Rust adapter 支持下载媒体 URL、base64 编码后塞进 POST /v2/sendbase64_attachments,覆盖 ImageVoiceVideoAudioAnimationFileFileDataMediaGroup。Sidecar 迁移(librefang.sidecar.adapters.signal)之后,非文本内容暂时统一退回 (Unsupported content type) 占位符;恢复 base64 round-trip 是后续工作。

WhatsApp DM / 群消息策略

迁移到 sidecar(librefang.sidecar.adapters.whatsapp)后,DM / 群组策略改通过 [[sidecar_channels]] 块上的环境变量配置:

  • WHATSAPP_DM_POLICYrespond(默认)/ allowed_only / ignoreallowed_onlyWHATSAPP_ALLOWED_USERS 配合使用。
  • WHATSAPP_GROUP_POLICYall(默认)/ mention_only / commands_only / ignoremention_only 模式下检查文本是否包含 WHATSAPP_BOT_PHONE(带/不带 @、去 +)或 WHATSAPP_BOT_NAME,大小写不敏感。

Cloud API 模式 出站支持 voice / image / file / location(通过标准 sidecar Content 变体)。Web/QR(Baileys)模式 把非文本内容降级为文本(voice URL → (Voice message: <url>)、无标题 image → (Image — not supported in Web mode)),对应原 Rust 适配器 whatsapp.rs:493-519 的 gateway-mode fallback。

Webhook deliver_only 模式

Webhook sidecar 可作为 pass-through 跑:传入 payload 完全绕过 LLM(无 sanitizer、无 rate limiter、无 agent 查找),直接转发到目标通道。适合只需要让消息落到 Telegram / Slack 等地方的推送通知和带外告警。

[[sidecar_channels]]
name = "webhook"
command = "python3"
args = ["-m", "librefang.sidecar.adapters.webhook"]
channel_type = "webhook"
[sidecar_channels.env]
WEBHOOK_LISTEN_PORT = "8461"
WEBHOOK_DELIVER_ONLY = "1"
WEBHOOK_DELIVER = "telegram:123456789"  # WEBHOOK_DELIVER_ONLY=1 时必填

WEBHOOK_SECRET 写入 ~/.librefang/secrets.envWEBHOOK_DELIVER_ONLY=1 时若 WEBHOOK_DELIVER 为空,sidecar 启动时 fail-closed——原 Rust validator 同位置只 warn-and-continue,结果运行时入站被静默丢弃。扇出在内部用 __deliver_only__ / __deliver_target__ metadata 信号化,kernel bridge.rs 路由读这两个字段后短路 LLM 调用。

通道文件下载

Telegram(及其他通道)发的文件以临时认证 URL 形式过来,LLM 无法直接访问。bridge 现在下载到可配置目录,把消息重写成本地路径 content block——非图片文件变成 ContentBlock::Text 带保存路径(agent 调 file_read),图片变成 ContentBlock::ImageFile

[channels]
file_download_dir = "/var/lib/librefang/channel-files"   # 默认:~/.librefang/data/channel-files
file_download_max_bytes = 33554432                       # 默认:32 MiB

启动 sweep 移除 24h 以上的过期文件;下载过程中还有概率 1/256 的额外 sweep,长跑的守护进程不会堆积孤儿附件。