# 通道配置

所有内嵌通道适配器的配置、每通道行为覆盖以及 Sidecar 通道集成。

---

### `[channels]`

所有内嵌通道适配器均在 `[channels.<name>]` 下配置。每个通道采用 `Option<T>` 类型——省略该节将完全禁用该适配器。即使只写了节头（内容为空），也会以默认值启用该适配器。

**通用通道字段：** 每个通道适配器除了自身特有的字段外，还支持以下通用字段：

| 字段 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `default_agent` | string 或 null | `null` | 默认将消息路由到的 Agent 名称。 |
| `account_id` | string 或 null | `null` | 当前 Bot 实例的唯一标识符。用于通过 `[[bindings]]` 匹配规则进行多 Bot 路由。 |
| `overrides` | object | （默认值） | 每个通道的行为覆盖配置。详见[通道行为覆盖](#通道行为覆盖)。 |

#### Telegram

Telegram 现已改为 sidecar 模式。`[channels.telegram]` 配置块不再被接受（#5241 已移除内置适配器）。请改用 `[[sidecar_channels]]` 声明：

```toml
[[sidecar_channels]]
name = "telegram"
command = "python3"
args = ["-m", "librefang.sidecar.adapters.telegram"]
channel_type = "telegram"
[sidecar_channels.env]
TELEGRAM_BOT_TOKEN = "..."
# ALLOWED_USERS = "111,@alice"
```

Sidecar 适配器保留了与原内置适配器相同的运行语义：`TELEGRAM_BOT_TOKEN` 环境变量、可选的 `ALLOWED_USERS` 允许列表、`channel_type = "telegram"` 以及 `channel_role_mapping.telegram` 的 RBAC 映射。详见下方 [`[[sidecar_channels]]`](#sidecar_channels) 章节。

#### Discord（sidecar）

Discord 已于 v2026.5 迁移为进程外 sidecar 适配器，请改用 `[[sidecar_channels]]` 配置。Sidecar 保留原内置适配器的运行语义：`DISCORD_BOT_TOKEN` 环境变量、Gateway Intents、`allowed_guilds` / `allowed_users` 过滤，以及通过 `channel_role_mapping.discord` 的 RBAC 映射。

```toml
[[sidecar_channels]]
name = "discord"
command = "python3"
args = ["-m", "librefang.sidecar.adapters.discord"]
channel_type = "discord"

[sidecar_channels.env]
DISCORD_BOT_TOKEN = "..."
# DISCORD_ALLOWED_GUILDS = "123,456"
# DISCORD_ALLOWED_USERS = "789"
# DISCORD_INTENTS = "37376"
# DISCORD_IGNORE_BOTS = "true"
# DISCORD_MENTION_PATTERNS = "hey bot,!ask"
# DISCORD_ACCOUNT_ID = "guild-42"
```

完整的 supervisor 字段（restart、backoff、ready timeout、message buffer 等）参见下方 [`[[sidecar_channels]]`](#sidecar_channels) 章节。

#### Slack（sidecar）

Slack 已于 v2026.5 迁移为进程外 sidecar 适配器，请改用 `[[sidecar_channels]]` 配置。Sidecar 保留原内置适配器的运行语义——Socket Mode WebSocket + Web API、`SLACK_ALLOWED_CHANNELS` 过滤（DM 豁免）、thread 回复上下文、Block Kit 交互回调、eyes / check 反应，以及通过 `SLACK_ACCOUNT_ID` 的多 Bot 路由。

```toml
[[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-..."
# SLACK_ALLOWED_CHANNELS = "C0123,C0456"
# SLACK_UNFURL_LINKS = "false"
# SLACK_FORCE_FLAT_REPLIES = "false"
# SLACK_REACTIONS = "true"
# SLACK_ACCOUNT_ID = "workspace-prod"
```

完整的 supervisor 字段（restart、backoff、ready timeout、message buffer 等）参见下方 [`[[sidecar_channels]]`](#sidecar_channels) 章节。

#### WhatsApp（Sidecar）

WhatsApp 已从进程内 Rust 适配器迁移到 Python sidecar（`librefang.sidecar.adapters.whatsapp`，仅依赖标准库）。原 `[channels.whatsapp]` 块不再识别。sidecar 同时覆盖 **Cloud API** 和 **Web/QR（Baileys）网关** 两种模式。

**Cloud API 模式**（推荐生产使用）：

```toml
[[sidecar_channels]]
name = "whatsapp"
command = "python3"
args = ["-m", "librefang.sidecar.adapters.whatsapp"]
channel_type = "whatsapp"
[sidecar_channels.env]
WHATSAPP_PHONE_NUMBER_ID = "123456789012345"
WHATSAPP_WEBHOOK_PORT = "8460"
# WHATSAPP_WEBHOOK_PATH = "/webhook"
# WHATSAPP_ALLOWED_USERS = "+15551111,+15552222"
# WHATSAPP_ACCOUNT_ID = "production"
# WHATSAPP_DM_POLICY = "respond"          # respond / allowed_only / ignore
# WHATSAPP_GROUP_POLICY = "all"           # all / mention_only / commands_only / ignore
```

`WHATSAPP_ACCESS_TOKEN`、`WHATSAPP_VERIFY_TOKEN`（用于 Meta Webhook 订阅握手）、`WHATSAPP_APP_SECRET`（入站 `X-Hub-Signature-256` 的 HMAC 密钥）写入 `~/.librefang/secrets.env`。sidecar 在 `WHATSAPP_WEBHOOK_PORT`（默认 8460）上自己跑 webhook server —— Meta 后台填的 Webhook 订阅 URL 变成 `https://<host>:<WHATSAPP_WEBHOOK_PORT><WHATSAPP_WEBHOOK_PATH>`。

原 Rust 适配器的 `start()`（whatsapp.rs:454-483）只是一个 TODO 桩——只 log "webhook ready"，从未解析入站事件。sidecar 实现了真正的 handler，所以 Cloud API 入站现在不靠任何外部 forwarder 就能工作。

**Web/QR 模式**（个人号场景，无需 Meta 开发者账号）：

```toml
[[sidecar_channels]]
name = "whatsapp"
command = "python3"
args = ["-m", "librefang.sidecar.adapters.whatsapp"]
channel_type = "whatsapp"
[sidecar_channels.env]
WHATSAPP_GATEWAY_URL = "http://localhost:3009"
# WHATSAPP_ALLOWED_USERS = "+15551111,+15552222"
# WHATSAPP_ACCOUNT_ID = "personal"
```

Baileys 网关（`@librefang/whatsapp-gateway`，Node.js，监听 `WHATSAPP_GATEWAY_URL`）**需要单独启动** —— kernel 不再内嵌或自动 spawn 它。入站消息由网关直接 POST 到 LibreFang REST API（`POST /api/agents/{id}/message`），绕过 sidecar；出站回复经 sidecar 路由到 `{gateway_url}/message/send`。

#### Signal（Sidecar）

Signal 已从进程内 Rust 适配器迁移到进程外 Python sidecar（`librefang.sidecar.adapters.signal`），后端通过独立运行的 `signal-cli-rest-api` 容器对接 signal-cli。原有 `[channels.signal]` 块不再识别 —— 改为声明 `[[sidecar_channels]]`：

```toml
[[sidecar_channels]]
name = "signal"
command = "python3"
args = ["-m", "librefang.sidecar.adapters.signal"]
channel_type = "signal"
[sidecar_channels.env]
SIGNAL_API_URL = "https://signal-cli.example.com"
SIGNAL_NUMBER  = "+15555550100"
# SIGNAL_ALLOWED_USERS = "+15555550199,+15555550200"  # 可选
# SIGNAL_ACCOUNT_ID = "prod-bot"                       # 可选
# SIGNAL_POLL_INTERVAL_SECS = "2"                      # 可选
# SIGNAL_ALLOW_LOCAL = "1"                             # localhost 部署时显式放行
```

Sidecar 沿用 Rust 适配器相同的 SSRF 保护：`SIGNAL_API_URL` 必须解析到公网地址，除非显式设置 `SIGNAL_ALLOW_LOCAL=1`。可选的 `SIGNAL_API_KEY` 写入 `~/.librefang/secrets.env`。

#### Matrix（Sidecar）

Matrix 已从进程内 Rust 适配器迁移到进程外 Python sidecar（`librefang.sidecar.adapters.matrix`），通过 Matrix Client-Server API 的 `/sync` 长轮询对接 homeserver。原有 `[channels.matrix]` 块不再识别 —— 改为声明 `[[sidecar_channels]]`：

```toml
[[sidecar_channels]]
name = "matrix"
command = "python3"
args = ["-m", "librefang.sidecar.adapters.matrix"]
channel_type = "matrix"
[sidecar_channels.env]
MATRIX_HOMESERVER_URL = "https://matrix.org"
MATRIX_USER_ID = "@librefang:matrix.org"
# MATRIX_ALLOWED_ROOMS = "!abc:matrix.org,!def:matrix.org"  # 可选
# MATRIX_ACCOUNT_ID = "prod-bot"                             # 可选
# MATRIX_MAX_UPLOAD_BYTES = "52428800"                       # 可选，默认 50 MiB
```

密钥放在 `~/.librefang/secrets.env`：`MATRIX_ACCESS_TOKEN`（Bot 在 homeserver 上的 access token，与 Element 一致）。

#### Email（IMAP + SMTP，sidecar）

Email 现以 Python sidecar 适配器（`librefang.sidecar.adapters.email`）提供。原 in-process `[channels.email]` 配置块已被移除。请改为通过 `[[sidecar_channels]]` 条目声明 Email：

```toml
[[sidecar_channels]]
name = "email"
command = "python3"
args = ["-m", "librefang.sidecar.adapters.email"]
channel_type = "email"
[sidecar_channels.env]
EMAIL_IMAP_HOST = "imap.gmail.com"
EMAIL_SMTP_HOST = "smtp.gmail.com"
EMAIL_USERNAME = "bot@example.com"
# EMAIL_IMAP_PORT = "993"
# EMAIL_SMTP_PORT = "587"
# EMAIL_POLL_INTERVAL_SECS = "30"      # IMAP 轮询间隔
# EMAIL_FOLDERS = "INBOX"              # 逗号分隔
# EMAIL_ALLOWED_SENDERS = ""           # 精确地址或 @domain，逗号分隔
# EMAIL_ACCOUNT_ID = "prod"            # 多 bot 路由标识
# EMAIL_TLS_ROOT_CA_PATH = ""          # 额外信任的 CA 证书包（PEM）
# EMAIL_TLS_ACCEPT_INVALID_CERTS = ""  # 临时调试用 escape hatch
```

`EMAIL_PASSWORD`（以及可选的分协议覆盖 `EMAIL_IMAP_PASSWORD` / `EMAIL_SMTP_PASSWORD`）写入 `~/.librefang/secrets.env`——在仪表盘 Channels 页填写 Email 表单时会自动写入。sidecar 用 `UID SEARCH UNSEEN UNKEYWORD Librefang-Quarantine` 轮询 IMAP（服务器拒绝该关键字时退回 `UNSEEN`），先 `LOGIN` 再 `AUTHENTICATE PLAIN` 兼容只支持 SASL PLAIN 的服务器（Lark 等），通过 per-sender `In-Reply-To` + `References` 缓存做外发回复线程化。

#### Microsoft Teams（Sidecar）

Teams 已从进程内 Rust 适配器迁移到进程外 Python sidecar（`librefang.sidecar.adapters.teams`）。原有 `[channels.teams]` 块不再识别——改为声明 `[[sidecar_channels]]`：

```toml
[[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"
# TEAMS_WEBHOOK_PATH = "/webhook"
# TEAMS_ALLOWED_TENANTS = "tenant-a,tenant-b"     # 留空允许全部
# TEAMS_ACCOUNT_ID = "production"
```

`TEAMS_APP_PASSWORD`（Bot Framework 客户端密钥）和 `TEAMS_SECURITY_TOKEN`（Teams 后台 outgoing-webhook 的 base64 token，用于校验每条入站请求的 HMAC-SHA256）写入 `~/.librefang/secrets.env`。sidecar 监听自己的 webhook 端口（默认 8459），所以你在 Azure Bot Channel 配置里登记的公网 URL 从 `https://<host>/channels/teams/webhook` 改为 `https://<host>:<TEAMS_WEBHOOK_PORT><TEAMS_WEBHOOK_PATH>`。

#### Mattermost（Sidecar）

Mattermost 已从进程内 Rust 适配器迁移到进程外 Python sidecar（`librefang.sidecar.adapters.mattermost`）。原有 `[channels.mattermost]` 块不再识别 —— 改为声明 `[[sidecar_channels]]`：

```toml
[[sidecar_channels]]
name = "mattermost"
command = "python3"
args = ["-m", "librefang.sidecar.adapters.mattermost"]
channel_type = "mattermost"
[sidecar_channels.env]
MATTERMOST_SERVER_URL = "https://mattermost.example.com"
# MATTERMOST_ALLOWED_CHANNELS = "ch-id-1,ch-id-2"   # 可选
# MATTERMOST_ACCOUNT_ID = "team-prod"               # 可选
```

`MATTERMOST_TOKEN` 写入 `~/.librefang/secrets.env`。

#### Google Chat

Google Chat 由 Python sidecar adapter (`librefang.sidecar.adapters.google_chat`) 提供。Sidecar 迁移已移除进程内 `[channels.google_chat]` 配置块。请改为 `[[sidecar_channels]]`：

```toml
[[sidecar_channels]]
name = "google_chat"
command = "python3"
args = ["-m", "librefang.sidecar.adapters.google_chat"]
channel_type = "google_chat"
[sidecar_channels.env]
GOOGLE_CHAT_WEBHOOK_PORT = "8090"
# GOOGLE_CHAT_SPACE_IDS = "spaces/AAAA,spaces/BBBB"   # 可选，空 = 所有 space
# GOOGLE_CHAT_ACCOUNT_ID = "prod"                     # 可选，多机器人路由
# GOOGLE_CHAT_API_BASE = "https://chat.googleapis.com/v1"  # 测试覆盖
```

`GOOGLE_CHAT_SERVICE_ACCOUNT_JSON`（完整的 JSON blob 字符串 —— **不是** 路径）写入 `~/.librefang/secrets.env`。Google Cloud Console 上 Bot 配置的 messaging endpoint 改成 `https://<host>:<GOOGLE_CHAT_WEBHOOK_PORT>/webhook`。

#### Twitch

Twitch 由 Python sidecar 适配器 (`librefang.sidecar.adapters.twitch`) 提供。原 in-process `[channels.twitch]` 配置块已在 sidecar 迁移中移除。请改用 `[[sidecar_channels]]` 条目：

```toml
[[sidecar_channels]]
name = "twitch"
command = "python3"
args = ["-m", "librefang.sidecar.adapters.twitch"]
channel_type = "twitch"
[sidecar_channels.env]
TWITCH_NICK = "librefang-bot"
TWITCH_CHANNELS = "channel1,channel2"      # 逗号分隔，不带 '#'
# TWITCH_ACCOUNT_ID = "prod"                # 可选，多机器人路由
# TWITCH_RATE_LIMIT_MSGS = "20"             # 非 mod 账号 20/30s；mod 可设为 100
# TWITCH_RATE_LIMIT_SECS = "30"
```

`TWITCH_OAUTH_TOKEN` 写入 `~/.librefang/secrets.env`。Sidecar 默认通过 TLS 连接 `irc.chat.twitch.tv:6697`；明文模式仅在 `TWITCH_PLAINTEXT=1` 下用于本地 mock 监听。

#### Rocket.Chat

Rocket.Chat 现以 Python sidecar 适配器（`librefang.sidecar.adapters.rocketchat`）提供。原 in-process `[channels.rocketchat]` 配置块已在 sidecar 迁移中移除。请改为通过 `[[sidecar_channels]]` 条目声明：

```bash
[[sidecar_channels]]
name = "rocketchat"
command = "python3"
args = ["-m", "librefang.sidecar.adapters.rocketchat"]
channel_type = "rocketchat"
[sidecar_channels.env]
ROCKETCHAT_SERVER_URL = "https://chat.example.com"
ROCKETCHAT_USER_ID = "abc123"
# ROCKETCHAT_CHANNELS = "GENERAL,room2"        # 可选；留空 = 所有已加入频道
# ROCKETCHAT_ACCOUNT_ID = "prod"                # 可选，多 Bot 路由
```

`ROCKETCHAT_TOKEN` 应写入 `~/.librefang/secrets.env`——在仪表板的 Channels 页面填写 Rocket.Chat 表单时会自动写入。

#### Zulip

Zulip 现在由 Python sidecar 适配器（`librefang.sidecar.adapters.zulip`）提供。原 in-process 的 `[channels.zulip]` 配置块在 sidecar 迁移中已移除。请改用 `[[sidecar_channels]]` 声明——完整配置见上文 Configuration 参考的 "Zulip" 一节。

#### LINE

LINE 由 Python sidecar 适配器（`librefang.sidecar.adapters.line`）提供，进程内 `[channels.line]` 配置块已在 sidecar 迁移中移除。请改用 `[[sidecar_channels]]` 形式声明 —— sidecar 自身运行 HTTP Webhook 服务器（不再挂载在 LibreFang API 端口上），因此你在 LINE Developers 控制台注册的 Webhook URL 现在为 `https://<sidecar-host>:<LINE_WEBHOOK_PORT><LINE_WEBHOOK_PATH>`：

```toml
[[sidecar_channels]]
name = "line"
command = "python3"
args = ["-m", "librefang.sidecar.adapters.line"]
channel_type = "line"
[sidecar_channels.env]
LINE_WEBHOOK_PORT = "9090"
# LINE_WEBHOOK_PATH = "/webhook"
# LINE_ACCOUNT_ID = "production"
```

`LINE_CHANNEL_SECRET`（用于验证入站 `X-Line-Signature` 的 HMAC-SHA256）和 `LINE_CHANNEL_ACCESS_TOKEN`（出站 `POST /v2/bot/message/push` 的 Bearer 凭据）需写入 `~/.librefang/secrets.env`。

#### Reddit

Reddit 由 Python sidecar 适配器（`librefang.sidecar.adapters.reddit`）提供，进程内 `[channels.reddit]` 配置块已在 sidecar 迁移中移除。请以 `[[sidecar_channels]]` 形式声明：

```toml
[[sidecar_channels]]
name = "reddit"
command = "python3"
args = ["-m", "librefang.sidecar.adapters.reddit"]
channel_type = "reddit"
[sidecar_channels.env]
REDDIT_CLIENT_ID = "abc123"
REDDIT_USERNAME = "librefang-bot"
REDDIT_SUBREDDITS = "rust,programming"   # 逗号分隔
# REDDIT_ACCOUNT_ID = "prod"              # 可选，用于多 Bot 路由
# REDDIT_USER_AGENT = "myorg-bot/1.0 (by /u/me)"   # 覆盖默认 User-Agent
```

`REDDIT_CLIENT_SECRET` 和 `REDDIT_PASSWORD` 应放在 `~/.librefang/secrets.env` 中 —— 仪表盘的 Channels 页面会在你填写 Reddit 表单时自动写入。

#### Mastodon

Mastodon 由 Python sidecar 适配器 (`librefang.sidecar.adapters.mastodon`) 提供。原 `[channels.mastodon]` 配置块已在 sidecar 迁移中移除，请改用 `[[sidecar_channels]]`：

```toml
[[sidecar_channels]]
name = "mastodon"
command = "python3"
args = ["-m", "librefang.sidecar.adapters.mastodon"]
channel_type = "mastodon"
[sidecar_channels.env]
MASTODON_INSTANCE_URL = "https://mastodon.social"
# MASTODON_VISIBILITY = "unlisted"          # public | unlisted | private | direct
# MASTODON_MAX_MESSAGE_LEN = "500"          # 实例若放宽限制可调高
# MASTODON_ACCOUNT_ID = "prod"              # 可选，多 bot 路由键
```

`MASTODON_ACCESS_TOKEN`（OAuth bearer）写入 `~/.librefang/secrets.env`——在仪表盘 Channels 页填写 Mastodon 表单时会自动写入。sidecar 继承了原适配器的 SSE 主路径 + REST 轮询回退语义。

#### Bluesky

Bluesky 由 Python sidecar 适配器 (`librefang.sidecar.adapters.bluesky`) 提供。原 `[channels.bluesky]` 配置块已在 sidecar 迁移中移除，请改用 `[[sidecar_channels]]`：

```toml
[[sidecar_channels]]
name = "bluesky"
command = "python3"
args = ["-m", "librefang.sidecar.adapters.bluesky"]
channel_type = "bluesky"
[sidecar_channels.env]
BLUESKY_IDENTIFIER = "mybot.bsky.social"
# BLUESKY_SERVICE_URL = "https://bsky.social"   # 自定义 PDS
# BLUESKY_ACCOUNT_ID = "prod"                   # 可选，多 bot 路由键
```

`BLUESKY_APP_PASSWORD` 写入 `~/.librefang/secrets.env`——在仪表盘 Channels 页填写 Bluesky 表单时会自动写入。sidecar 每 5 秒轮询 `app.bsky.notification.listNotifications`，并通过内存 LRU 缓存（容量 200）将外发回复线程化关联到原通知。

#### 飞书 / Lark（sidecar）

飞书 / Lark 已迁移为进程外 sidecar 适配器；请通过 `[[sidecar_channels]]` 条目声明，而不是 `[channels.feishu]`。

```bash
[[sidecar_channels]]
name = "feishu"
command = "python3"
args = ["-m", "librefang.sidecar.adapters.feishu"]
channel_type = "feishu"
[sidecar_channels.env]
FEISHU_APP_ID = "cli_abc123"
# FEISHU_REGION = "cn"                       # cn（默认）或 intl（Lark）
# FEISHU_RECEIVE_MODE = "websocket"          # websocket（默认）或 webhook
# FEISHU_WEBHOOK_PORT = "8453"               # 仅 webhook 模式
# FEISHU_VERIFICATION_TOKEN = "..."          # 仅 webhook 模式
# FEISHU_ENCRYPT_KEY = "..."                 # 仅 webhook 模式（AES-256-CBC 加密载荷）
# FEISHU_ACCOUNT_ID = "prod"                 # 可选，多 bot 路由标识
```

`FEISHU_APP_SECRET` 写入 `~/.librefang/secrets.env`——在仪表盘 Channels 页填写飞书表单时会自动写入。默认 `websocket` 模式与飞书事件网关建立长连接（无需公网 IP）。`webhook` 模式监听 `FEISHU_WEBHOOK_PORT`，当配置 `FEISHU_ENCRYPT_KEY` 时支持解密 AES-256-CBC + PKCS7 加密载荷。

#### Nextcloud Talk

Nextcloud Talk 现以 Python sidecar 适配器（`librefang.sidecar.adapters.nextcloud`）提供。原 in-process `[channels.nextcloud]` 配置块已被移除。请改为通过 `[[sidecar_channels]]` 条目声明 Nextcloud：

```bash
[[sidecar_channels]]
name = "nextcloud"
command = "python3"
args = ["-m", "librefang.sidecar.adapters.nextcloud"]
channel_type = "nextcloud"
[sidecar_channels.env]
NEXTCLOUD_SERVER_URL = "https://cloud.example.com"
# NEXTCLOUD_ROOMS = "abc123,def456"           # 可选；留空 = 所有已加入房间
# NEXTCLOUD_ACCOUNT_ID = "prod"               # 可选，多 Bot 路由
```

`NEXTCLOUD_TOKEN` 写入 `~/.librefang/secrets.env`——在仪表板的 Channels 页面填写 Nextcloud 表单时会自动写入。出站回复通过 Talk 的 `replyTo` 参数串入线程（相对原 Rust 适配器从未发送 `replyTo` 的改进）。

#### Webex (sidecar)

Webex 已在 v2026.5 中迁移为 out-of-process sidecar；请使用 `[[sidecar_channels]]` 块代替 `[channels.webex]`。

```bash
[[sidecar_channels]]
name = "webex"
command = "python3"
args = ["-m", "librefang.sidecar.adapters.webex"]
channel_type = "webex"

[sidecar_channels.env]
# WEBEX_ALLOWED_ROOMS = "Y2lz...A,Y2lz...B"  # 可选，留空 = 所有房间
# WEBEX_ACCOUNT_ID = "org-prod"              # 可选，多机器人路由键
```

`WEBEX_BOT_TOKEN` 写入 `~/.librefang/secrets.env`——在仪表板的 Channels 页面填写 Webex 表单时会自动写入。出站回复通过 Webex 的 `parentId` 参数串入线程（相对原 Rust 适配器从未发送 `parentId` 的改进）。

#### 钉钉（DingTalk，sidecar）

钉钉现以 Python sidecar 适配器（`librefang.sidecar.adapters.dingtalk`）提供，**仅 stream 模式**。原 in-process `[channels.dingtalk]` 配置块已被移除。请改为通过 `[[sidecar_channels]]` 条目声明：

```toml
[[sidecar_channels]]
name = "dingtalk"
command = "python3"
args = ["-m", "librefang.sidecar.adapters.dingtalk"]
channel_type = "dingtalk"
[sidecar_channels.env]
DINGTALK_APP_KEY = "dingxxx..."
# DINGTALK_ACCOUNT_ID = "prod-bot"     # 可选，多 bot 路由
# DINGTALK_ROBOT_CODE = ""             # 可选，默认用 app_key
```

`DINGTALK_APP_SECRET` 写入 `~/.librefang/secrets.env`——在仪表盘 Channels 页填写钉钉表单时会自动写入。sidecar 通过两步端点发现（`POST /v1.0/gateway/connections/open`）连接到钉钉 Stream Gateway，每条 CALLBACK 都即时 ACK，回复通过事件中携带的 `sessionWebhook` URL 发出。

legacy in-process `webhook` 模式（HMAC-SHA256 + 公网 IP）**未迁移**——依赖 webhook 模式的运维需要在钉钉后台改用 stream 订阅。

#### ntfy

ntfy 由 Python sidecar adapter (`librefang.sidecar.adapters.ntfy`) 提供。Sidecar 迁移已移除进程内 `[channels.ntfy]` 配置块。请改为 `[[sidecar_channels]]`：

```toml
[[sidecar_channels]]
name = "ntfy"
command = "python3"
args = ["-m", "librefang.sidecar.adapters.ntfy"]
channel_type = "ntfy"
[sidecar_channels.env]
NTFY_SERVER_URL = "https://ntfy.sh"
NTFY_TOPIC = "my-agent-topic"
# NTFY_ACCOUNT_ID = "prod"                   # 可选，多机器人路由
```

`NTFY_TOKEN`（认证 token，公开 topic 可选）写入 `~/.librefang/secrets.env`。

#### Gotify

Gotify 由 Python sidecar 适配器 (`librefang.sidecar.adapters.gotify`) 提供。原 `[channels.gotify]` 配置块已在 sidecar 迁移中移除，请改用 `[[sidecar_channels]]`：

```toml
[[sidecar_channels]]
name = "gotify"
command = "python3"
args = ["-m", "librefang.sidecar.adapters.gotify"]
channel_type = "gotify"
[sidecar_channels.env]
GOTIFY_SERVER_URL = "https://gotify.example.com"
# GOTIFY_ACCOUNT_ID = "prod"    # 可选，多 bot 路由键
```

`GOTIFY_APP_TOKEN`（出站发布）和 `GOTIFY_CLIENT_TOKEN`（入站 WebSocket 订阅）写入 `~/.librefang/secrets.env`——在仪表盘 Channels 页填写 Gotify 表单时会自动写入。

#### Webhook（Sidecar）

Webhook 已从进程内 Rust 适配器迁移到 Python sidecar（`librefang.sidecar.adapters.webhook`，仅依赖标准库）。原 `[channels.webhook]` 块不再识别。保留全套 HMAC-SHA256 + 时间戳 + SSRF 防护，外加入站去重和 429 重试（Rust 适配器没有的两项改进）。

```toml
[[sidecar_channels]]
name = "webhook"
command = "python3"
args = ["-m", "librefang.sidecar.adapters.webhook"]
channel_type = "webhook"
[sidecar_channels.env]
WEBHOOK_LISTEN_PORT = "8461"
# WEBHOOK_LISTEN_PATH = "/webhook"
# WEBHOOK_CALLBACK_URL = "https://example.com/incoming"
# WEBHOOK_DELIVER_ONLY = "1"
# WEBHOOK_DELIVER = "telegram"
```

`WEBHOOK_SECRET`（HMAC-SHA256 签名密钥）写入 `~/.librefang/secrets.env`。sidecar 自己监听 `WEBHOOK_LISTEN_PORT`（默认 8461）的 `WEBHOOK_LISTEN_PATH`（默认 `/webhook`）。SSRF 防护（私有 / 回环 / 链路本地 / 多播 / 云元数据 / 100.64-CGN / IPv4-mapped-IPv6 全部拒绝，以及 `localhost.`、`kubernetes.default.svc.cluster.local` 等保留主机名）在 sidecar 启动时**和**每次出站 POST 时都跑——defence in depth：配置热重载把 URL 改成私网时不会泄露签名密钥到 localhost。

`WEBHOOK_DELIVER_ONLY=1` 时 sidecar 在入站消息上加 `__deliver_only__` / `__deliver_target__` metadata；kernel 的 bridge 层读这两个字段，把消息直接转发给目标 channel，绕过 LLM。`WEBHOOK_DELIVER_ONLY=1` 时 `WEBHOOK_DELIVER` 是必填的 —— sidecar 启动时若缺失直接 fail-closed（原 Rust validator 只在运行时 warn）。

#### QQ Bot（Sidecar）

QQ 已从进程内 Rust 适配器迁移到进程外 Python sidecar（`librefang.sidecar.adapters.qq`），通过 WebSocket（网关）+ REST（token + 出站）与 QQ 开放平台通信。原有 `[channels.qq]` 块不再识别 —— 改为声明 `[[sidecar_channels]]`：

```toml
[[sidecar_channels]]
name = "qq"
command = "python3"
args = ["-m", "librefang.sidecar.adapters.qq"]
channel_type = "qq"
[sidecar_channels.env]
QQ_APP_ID = "your-app-id"
# QQ_ALLOWED_USERS = "openid-1,openid-2"   # 可选
# QQ_ACCOUNT_ID = "prod-bot"               # 可选
# QQ_INTENTS = "1073746435"                # 可选，覆盖 intents bitmask
```

密钥放在 `~/.librefang/secrets.env`：`QQ_APP_SECRET`（QQ 开放平台控制台里的 `clientSecret`）。

#### 企业微信（sidecar）

企业微信（WeCom）已从 in-process Rust 适配器迁移到 out-of-process Python sidecar（`librefang.sidecar.adapters.wecom`）。Sidecar 使用 WebSocket 连接到 `wss://openws.work.weixin.qq.com`，凭智能机器人的 Bot ID 和 Secret 鉴权。**Callback 模式**（HTTP webhook + AES-CBC-256 入站解密）不再支持 —— Python 标准库没有 AES，而 sidecar SDK 按惯例只用 stdlib。原先使用 callback 模式的运维人员需要在企业微信管理后台将机器人切换到 WebSocket 模式。

原有 `[channels.wecom]` 不再识别，请改为 `[[sidecar_channels]]`：

```toml
[[sidecar_channels]]
name = "wecom"
command = "python3"
args = ["-m", "librefang.sidecar.adapters.wecom"]
channel_type = "wecom"

[sidecar_channels.env]
WECOM_BOT_ID = "aibxxxxxxx"
# WECOM_ALLOWED_USERS = "alice,bob"
# WECOM_ACCOUNT_ID    = "prod-bot"
```

密钥放在 `~/.librefang/secrets.env`：`WECOM_BOT_SECRET`（企业微信管理后台里的机器人密钥）。

#### 微信（WeChat，sidecar）

微信（个人账号经 iLink 协议）现以 Python sidecar 适配器（`librefang.sidecar.adapters.wechat`）提供。原 in-process `[channels.wechat]` 配置块已被移除。请改为通过 `[[sidecar_channels]]` 条目声明：

```toml
[[sidecar_channels]]
name = "wechat"
command = "python3"
args = ["-m", "librefang.sidecar.adapters.wechat"]
channel_type = "wechat"
[sidecar_channels.env]
# WECHAT_BOT_TOKEN = ""            # 留空触发二维码登录
# WECHAT_ALLOWED_USERS = ""        # 逗号分隔 hash@im.wechat
# WECHAT_ACCOUNT_ID = "prod"       # 可选，多 bot 路由
```

首次启动时，sidecar 运行二维码登录流程，并把二维码字符串以 INFO 级日志输出 —— 用微信 App 扫描确认即可。拿到的 bot_token 可以填到 `WECHAT_BOT_TOKEN` 跳过后续二维码（仪表盘 Channels 页填表时会自动写入 `~/.librefang/secrets.env`）。

---


### 通道行为覆盖（旧版 —— 已移除）

`[channels.<name>.overrides]` 子表**已不再被识别**。它原本挂在进程内 `ChannelsConfig` 上，而 sidecar 迁移已经把这个结构清空（`ChannelsConfig` 现在只剩 `file_download_*` / `file_upload_max_bytes` 这些全局文件传输字段）。下方内容保留作**历史参考与迁移映射**。

**迁移对照表 —— sidecar 模型下的等价做法：**

| 旧的 override | 现在对应的 sidecar 写法 |
|---|---|
| `model`、`system_prompt` | 在 agent（`agent.toml`）上设置而不是按通道 —— sidecar 把每个入站都路由到同一个 agent。 |
| `dm_policy`、`group_policy` | 写到 `[sidecar_channels.env]` 块里的 `*_DM_POLICY` / `*_GROUP_POLICY` 环境变量（Telegram / Discord / Slack / WhatsApp / Teams 五个 sidecar 都识别这套）。 |
| `rate_limit_per_minute`、`rate_limit_per_user` | Sidecar 自己按平台限流；要更严的话，在 sidecar 前面套反向代理。 |
| `output_format` | `[[sidecar_channels]]` 没暴露这个 knob；需要特定输出格式的 agent 应在 agent 侧切换。 |
| `disable_commands`、`allowed_commands`、`blocked_commands` | 在 agent 清单（`agent.toml`）的命令策略区块设置 —— 跨通道统一生效。 |
| `threading`、`usage_footer`、`typing_mode` | 用 sidecar 默认行为（平台支持时启用 threading；不显示用量页脚；instant 打字态）。当前不可按通道配置。 |

下方是**已删除**的 override 结构 —— 保留作参考；**不要**复制到当前的 `config.toml`。

### 通道行为覆盖（历史参考）

迁移前每个通道适配器都支持 `[channels.<name>.overrides]` 子表，用于按通道自定义 Agent 行为。

```toml
[channels.discord.overrides]
model = "claude-haiku-4-5-20251001"
system_prompt = "You are a concise Discord assistant."
dm_policy = "respond"
group_policy = "mention_only"
rate_limit_per_minute = 0
rate_limit_per_user = 10
threading = true
output_format = "markdown"
usage_footer = "tokens"
typing_mode = "instant"
disable_commands = false
allowed_commands = []
blocked_commands = []
```

| 字段 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `model` | string 或 null | `null` | 该通道的模型覆盖。为 null 时使用 Agent 的默认模型。 |
| `system_prompt` | string 或 null | `null` | 该通道的系统提示词覆盖。 |
| `dm_policy` | string | `"respond"` | Bot 处理私聊消息的方式。见下表。 |
| `group_policy` | string | `"mention_only"` | Bot 处理群组消息的方式。见下表。 |
| `rate_limit_per_minute` | u32 | `0` | 该通道的全局速率限制（每分钟消息数）。`0` = 无限制。 |
| `rate_limit_per_user` | u32 | `0` | 每个用户每分钟的最大消息数。`0` = 无限制。 |
| `threading` | bool | `false` | 启用线程回复（在支持的平台上）。 |
| `output_format` | string 或 null | `null` | 覆盖输出格式。见下表。 |
| `usage_footer` | string 或 null | `null` | 覆盖该通道的用量页脚模式。可选值：`off`、`tokens`、`cost`、`full`。 |
| `typing_mode` | string 或 null | `null` | 输入状态指示器行为。见下表。默认为 `instant`。 |
| `disable_commands` | bool | `false` | 关闭该通道所有内置斜杠命令。参见[命令策略](#命令策略)。 |
| `allowed_commands` | list of strings | `[]` | 命令名白名单（不带 `/`）。非空时仅这些命令可用。 |
| `blocked_commands` | list of strings | `[]` | 命令名黑名单（不带 `/`）。仅在 `allowed_commands` 为空时生效。 |

**`dm_policy` 值说明：**

| 值 | 说明 |
|----|------|
| `respond` | 回复所有私聊消息（默认）。 |
| `allowed_only` | 仅回复允许列表中用户的私聊消息。 |
| `ignore` | 忽略所有私聊消息。 |

**`group_policy` 值说明：**

| 值 | 说明 |
|----|------|
| `all` | 回复群聊中的所有消息。 |
| `mention_only` | 仅在被 @ 提及时回复（默认）。 |
| `commands_only` | 仅回复斜杠命令。 |
| `ignore` | 忽略所有群组消息。 |

**`output_format` 值说明：**

| 值 | 说明 |
|----|------|
| `markdown` | 标准 Markdown（默认）。 |
| `telegram_html` | Telegram HTML 子集（`<b>`、`<i>`、`<code>` 等）。 |
| `slack_mrkdwn` | Slack mrkdwn 格式（`*bold*`、`_italic_`、`` `code` ``）。 |
| `plain_text` | 无格式标记。 |

**`typing_mode` 值说明：**

| 值 | 说明 |
|----|------|
| `instant` | 收到消息后立即发送输入状态指示（默认）。 |
| `message` | 仅在 LLM 返回第一个文本增量时发送输入状态指示。 |
| `thinking` | 仅在 LLM 推理/思考阶段发送输入状态指示。 |
| `never` | 从不发送输入状态指示。 |

#### 命令策略

每个通道都自带一组内置斜杠命令（`/agent`、`/new`、`/reboot`、`/model`、`/usage` 等），可用于切换 Agent、开启新会话、查看用量、触发工作流。在**面向公众的 bot**（客服、社区助手）上暴露这些命令是安全隐患——任何人都可以打 `/agent admin` 切到内部 Agent，`/new` 开一个新会话（旧会话仍保留并可在通道里恢复），或者 `/model` 换模型。

下面三个字段按通道对内置命令做访问控制。**优先级：** `disable_commands` > `allowed_commands`（白名单） > `blocked_commands`（黑名单）。

**被拦截的命令会作为普通文本转发给 Agent**——不会返回错误。所以一个设置了 `disable_commands = true` 的公共 bot，当用户打 `/agent admin` 时，会由 Agent 自然语言回复，用户永远不会发现命令系统的存在。

**配方：完全锁定的公共 bot（禁用所有命令）**

```toml
[channels.discord.overrides]
disable_commands = true
```

**配方：保留平台 UX 必需命令的公共 bot**

只保留安全的几个内置命令：

```toml
[channels.discord.overrides]
allowed_commands = ["start", "help"]
```

**配方：只屏蔽危险命令的管理员 bot**

对于面向运维人员的 bot（用 `allowed_users` 限制访问），你可能仍想屏蔽会改变昂贵状态的命令：

```toml
[channels.discord.overrides]
blocked_commands = ["agent", "new", "reboot", "model", "stop"]
```

命令名是内部使用的裸 token —— TOML 里写 `"agent"` 或 `"/agent"` 都行，匹配时会自动去掉前导斜杠。完整命令列表见 `/help`。

---


### `[[sidecar_channels]]`

Sidecar 通道适配器允许外部进程（使用任何编程语言编写）充当通道适配器。通过 stdin/stdout 上的换行分隔 JSON 进行通信。

```toml
[[sidecar_channels]]
name = "my-custom-channel"
command = "python3"
args = ["adapters/my_adapter.py"]
channel_type = "custom_platform"
[sidecar_channels.env]
MY_API_TOKEN = "secret"
```

| 字段 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `name` | string | *必填* | 此适配器的显示名称。 |
| `command` | string | *必填* | 要运行的可执行文件（例如 `"python3"`、`"/usr/local/bin/my-adapter"`）。 |
| `args` | list of strings | `[]` | 传递给命令的参数。 |
| `env` | map of string to string | `{}` | 传递给子进程的额外环境变量。 |
| `channel_type` | string 或 null | `null` | 通道类型标识符。为 null 时默认为 `Custom(<name>)`。 |

---

