Hermes-agent

This commit is contained in:
Zakaria
2026-06-14 14:30:48 -04:00
commit dac4b88b94
5058 changed files with 1884848 additions and 0 deletions
@@ -0,0 +1,143 @@
# BlueBubblesiMessage
通过 [BlueBubbles](https://bluebubbles.app/) 将 Hermes 连接至 Apple iMessage——这是一款免费、开源的 macOS 服务端,可将 iMessage 桥接至任意设备。
## 前提条件
- 一台**始终开机的 Mac**,运行 [BlueBubbles Server](https://bluebubbles.app/)
- 该 Mac 上的 Messages.app 已登录 Apple ID
- BlueBubbles Server v1.0.0+webhook 需要此版本)
- Hermes 与 BlueBubbles 服务端之间的网络连通性
## 配置步骤
### 1. 安装 BlueBubbles Server
从 [bluebubbles.app](https://bluebubbles.app/) 下载并安装。完成设置向导——使用 Apple ID 登录,并配置连接方式(本地网络、Ngrok、Cloudflare 或动态 DNS)。
### 2. 获取服务端 URL 和密码
在 BlueBubbles Server → **Settings → API** 中,记录:
- **Server URL**(例如 `http://192.168.1.10:1234`
- **Server Password**
### 3. 配置 Hermes
运行设置向导:
```bash
hermes gateway setup
```
选择 **BlueBubbles (iMessage)** 并输入服务端 URL 和密码。
或直接在 `~/.hermes/.env` 中设置环境变量:
```bash
BLUEBUBBLES_SERVER_URL=http://192.168.1.10:1234
BLUEBUBBLES_PASSWORD=your-server-password
```
### 4. 授权用户
选择以下任一方式:
**DM 配对(推荐):**
当有人向你的 iMessage 发送消息时,Hermes 会自动向其发送配对码。使用以下命令批准:
```bash
hermes pairing approve bluebubbles <CODE>
```
使用 `hermes pairing list` 查看待处理的配对码和已授权用户。
**预授权特定用户**(在 `~/.hermes/.env` 中):
```bash
BLUEBUBBLES_ALLOWED_USERS=user@icloud.com,+15551234567
```
**开放访问**(在 `~/.hermes/.env` 中):
```bash
BLUEBUBBLES_ALLOW_ALL_USERS=true
```
### 5. 启动 Gateway
```bash
hermes gateway run
```
Hermes 将连接至你的 BlueBubbles 服务端,注册 webhook,并开始监听 iMessage 消息。
## 工作原理
```
iMessage → Messages.app → BlueBubbles Server → Webhook → Hermes
Hermes → BlueBubbles REST API → Messages.app → iMessage
```
- **入站:** 新消息到达时,BlueBubbles 向本地监听器发送 webhook 事件。无需轮询——即时送达。
- **出站:** Hermes 通过 BlueBubbles REST API 发送消息。
- **媒体:** 双向支持图片、语音消息、视频和文档。入站附件会被下载并在本地缓存,供 Agent 处理。
## 环境变量
| 变量 | 必填 | 默认值 | 说明 |
|----------|----------|---------|-------------|
| `BLUEBUBBLES_SERVER_URL` | 是 | — | BlueBubbles 服务端 URL |
| `BLUEBUBBLES_PASSWORD` | 是 | — | 服务端密码 |
| `BLUEBUBBLES_WEBHOOK_HOST` | 否 | `127.0.0.1` | Webhook 监听器绑定地址 |
| `BLUEBUBBLES_WEBHOOK_PORT` | 否 | `8645` | Webhook 监听器端口 |
| `BLUEBUBBLES_WEBHOOK_PATH` | 否 | `/bluebubbles-webhook` | Webhook URL 路径 |
| `BLUEBUBBLES_HOME_CHANNEL` | 否 | — | cron 投递使用的手机号/邮箱 |
| `BLUEBUBBLES_ALLOWED_USERS` | 否 | — | 逗号分隔的授权用户列表 |
| `BLUEBUBBLES_ALLOW_ALL_USERS` | 否 | `false` | 允许所有用户 |
自动将消息标记为已读由 `~/.hermes/config.yaml``platforms.bluebubbles.extra` 下的 `send_read_receipts` 键控制(默认值:`true`)。该选项没有对应的环境变量。
## 功能特性
### 文字消息
发送和接收 iMessage。Markdown 会自动去除,以确保纯文本的整洁呈现。
### 富媒体
- **图片:** 照片在 iMessage 对话中原生显示
- **语音消息:** 音频文件以 iMessage 语音消息形式发送
- **视频:** 视频附件
- **文档:** 文件以 iMessage 附件形式发送
### Tapback 反应
支持喜爱、点赞、踩、大笑、强调和疑问等反应。需要 BlueBubbles [Private API helper](https://docs.bluebubbles.app/helper-bundle/installation)。
### 正在输入指示器
Agent 处理消息期间,iMessage 对话中会显示"正在输入……"。需要 Private API。
### 已读回执
处理消息后自动标记为已读。需要 Private API。
### 聊天寻址
你可以通过邮箱或手机号寻址聊天——Hermes 会自动将其解析为 BlueBubbles 聊天 GUID,无需使用原始 GUID 格式。
## Private API
部分功能需要 BlueBubbles [Private API helper](https://docs.bluebubbles.app/helper-bundle/installation)
- Tapback 反应
- 正在输入指示器
- 已读回执
- 通过地址创建新聊天
不使用 Private API 时,基本文字消息和媒体功能仍可正常使用。
## 故障排查
### "Cannot reach server"
- 确认服务端 URL 正确且 Mac 已开机
- 检查 BlueBubbles Server 是否正在运行
- 确保网络连通(防火墙、端口转发)
### 消息未送达
- 检查 webhook 是否已在 BlueBubbles Server → Settings → API → Webhooks 中注册
- 确认 webhook URL 可从 Mac 访问
- 查看 `hermes logs gateway` 中的 webhook 错误(或使用 `hermes logs -f` 实时跟踪)
### "Private API helper not connected"
- 安装 Private API helper[docs.bluebubbles.app](https://docs.bluebubbles.app/helper-bundle/installation)
- 不安装也可使用基本消息功能——仅反应、正在输入和已读回执需要它
@@ -0,0 +1,283 @@
---
sidebar_position: 10
title: "DingTalk"
description: "将 Hermes Agent 设置为钉钉聊天机器人"
---
# 钉钉设置
Hermes Agent 可作为聊天机器人集成到钉钉(DingTalk),让你通过单聊或群聊与 AI 助手对话。机器人通过钉钉的 Stream Mode(流模式)连接——一种长连接 WebSocket,无需公网 URL 或 webhook 服务器——并通过钉钉的 session webhook API 以 markdown 格式回复消息。
在开始设置之前,先了解大多数人最关心的内容:Hermes 进入你的钉钉工作空间后的行为方式。
## Hermes 的行为方式
| 场景 | 行为 |
|---------|----------|
| **单聊(1:1 对话)** | Hermes 响应每条消息,无需 `@提及`,每个单聊有独立会话。 |
| **群聊** | Hermes 仅在被 `@提及` 时响应,未被提及则忽略消息。 |
| **多用户共享群聊** | 默认情况下,Hermes 在群内按用户隔离会话历史。同一群中的两个用户不共享同一对话记录,除非你明确禁用该功能。 |
### 钉钉中的会话模型
默认情况下:
- 每个单聊有独立会话
- 共享群聊中的每个用户在该群内有独立会话
通过 `config.yaml` 控制:
```yaml
group_sessions_per_user: true
```
仅当你明确希望整个群共享一个对话时,才将其设为 `false`
```yaml
group_sessions_per_user: false
```
本指南将带你完成完整的设置流程——从创建钉钉机器人到发送第一条消息。
## 前置条件
安装所需的 Python 包:
```bash
pip install "hermes-agent[dingtalk]"
```
或单独安装:
```bash
pip install dingtalk-stream httpx alibabacloud-dingtalk
```
- `dingtalk-stream` — 钉钉官方 Stream Mode SDK(基于 WebSocket 的实时消息)
- `httpx` — 异步 HTTP 客户端,用于通过 session webhook 发送回复
- `alibabacloud-dingtalk` — 钉钉 OpenAPI SDK,用于 AI 卡片、emoji 反应和媒体下载
## 第一步:创建钉钉应用
1. 前往[钉钉开发者控制台](https://open-dev.dingtalk.com/)。
2. 使用钉钉管理员账号登录。
3. 点击**应用开发** → **自建应用****创建 H5 微应用**(或根据控制台版本选择**机器人**)。
4. 填写:
- **应用名称**:例如 `Hermes Agent`
- **描述**:可选
5. 创建完成后,进入**凭证与基础信息**,找到你的 **Client ID**AppKey)和 **Client Secret**AppSecret),复制两者。
:::warning[凭证仅显示一次]
Client Secret 仅在创建应用时显示一次。如果丢失,需要重新生成。切勿公开分享这些凭证或将其提交到 Git。
:::
## 第二步:启用机器人能力
1. 在应用设置页面,进入**添加能力** → **机器人**
2. 启用机器人能力。
3. 在**消息接收模式**下,选择 **Stream Mode**(推荐——无需公网 URL)。
:::tip
Stream Mode 是推荐的设置方式。它使用从你的机器发起的长连接 WebSocket,无需公网 IP、域名或 webhook 端点,可在 NAT、防火墙及本地机器后正常工作。
:::
## 第三步:找到你的钉钉用户 ID
Hermes Agent 使用你的钉钉用户 ID 来控制谁可以与机器人交互。钉钉用户 ID 是由组织管理员设置的字母数字字符串。
查找方式:
1. 询问你的钉钉组织管理员——用户 ID 在钉钉管理后台的**通讯录** → **成员**中配置。
2. 或者,机器人会在日志中记录每条传入消息的 `sender_id`。启动 gateway,向机器人发送一条消息,然后在日志中查找你的 ID。
## 第四步:配置 Hermes Agent
### 方式 A:交互式设置(推荐)
运行引导式设置命令:
```bash
hermes gateway setup
```
在提示时选择 **DingTalk**。设置向导支持两种授权路径:
- **二维码设备流(推荐)。** 用钉钉手机 App 扫描终端中打印的二维码——Client ID 和 Client Secret 将自动返回并写入 `~/.hermes/.env`,无需前往开发者控制台。
- **手动粘贴。** 如果你已有凭证(或扫码不方便),在提示时粘贴你的 Client ID、Client Secret 和允许的用户 ID。
:::note openClaw 品牌披露
由于钉钉的 `verification_uri_complete` 在 API 层硬编码为 openClaw 身份,在 Alibaba / DingTalk-Real-AI 在服务端注册 Hermes 专属模板之前,二维码目前以 `openClaw` 来源字符串进行授权。这仅是钉钉呈现授权界面的方式——你创建的机器人完全属于你,且对你的租户私有。
:::
### 方式 B:手动配置
`~/.hermes/.env` 文件中添加以下内容:
```bash
# 必填
DINGTALK_CLIENT_ID=your-app-key
DINGTALK_CLIENT_SECRET=your-app-secret
# 安全:限制可与机器人交互的用户
DINGTALK_ALLOWED_USERS=user-id-1
# 多个允许用户(逗号分隔)
# DINGTALK_ALLOWED_USERS=user-id-1,user-id-2
# 可选:群聊门控(与 Slack/Telegram/Discord/WhatsApp 保持一致)
# DINGTALK_REQUIRE_MENTION=true
# DINGTALK_FREE_RESPONSE_CHATS=cidABC==,cidDEF==
# DINGTALK_MENTION_PATTERNS=^小马
# DINGTALK_HOME_CHANNEL=cidXXXX==
# DINGTALK_ALLOW_ALL_USERS=true
```
`~/.hermes/config.yaml` 中的可选行为设置:
```yaml
group_sessions_per_user: true
gateway:
platforms:
dingtalk:
extra:
# 在群聊中要求 @提及 后机器人才回复(与 Slack/Telegram/Discord 保持一致)。
# 单聊忽略此设置——机器人始终在 1:1 对话中回复。
require_mention: true
# 平台级白名单。设置后,只有这些钉钉用户 ID 可与机器人交互
# (语义与 DINGTALK_ALLOWED_USERS 相同,但作用域在此处而非 .env)。
allowed_users:
- user-id-1
- user-id-2
```
- `group_sessions_per_user: true` 在共享群聊中保持每个参与者的上下文隔离
- `require_mention: true` 防止机器人响应每条群消息——仅在有人 @提及 时才回答
- `dingtalk.extra` 下的 `allowed_users``DINGTALK_ALLOWED_USERS` 的替代方式;若两者同时设置,则合并生效
### 启动 Gateway
配置完成后,启动钉钉 gateway:
```bash
hermes gateway
```
机器人应在几秒内连接到钉钉的 Stream Mode。发送一条消息——单聊或已添加机器人的群聊均可——进行测试。
:::tip
你可以在后台运行 `hermes gateway`,或将其配置为 systemd 服务以持续运行。详见部署文档。
:::
## 功能特性
### AI 卡片
Hermes 可以使用钉钉 AI 卡片代替纯 markdown 消息进行回复。卡片提供更丰富、更结构化的展示,并支持在 agent 生成响应时进行流式更新。
要启用 AI 卡片,在 `config.yaml` 中配置卡片模板 ID
```yaml
platforms:
dingtalk:
enabled: true
extra:
card_template_id: "your-card-template-id"
```
你可以在钉钉开发者控制台的应用 AI 卡片设置中找到卡片模板 ID。启用 AI 卡片后,所有回复均以带流式文本更新的卡片形式发送。
### Emoji 反应
Hermes 会自动在你的消息上添加 emoji 反应以显示处理状态:
- 🤔Thinking — 机器人开始处理你的消息时添加
- 🥳Done — 响应完成时添加(替换 Thinking 反应)
这些反应在单聊和群聊中均有效。
### 显示设置
你可以独立于其他平台自定义钉钉的显示行为:
```yaml
display:
platforms:
dingtalk:
show_reasoning: false # 在回复中显示模型推理/思考过程
streaming: true # 启用流式响应(与 AI 卡片配合使用)
tool_progress: all # 显示工具执行进度(all/new/off
interim_assistant_messages: true # 显示中间注释消息
```
若要禁用工具进度和中间消息以获得更简洁的体验:
```yaml
display:
platforms:
dingtalk:
tool_progress: off
interim_assistant_messages: false
```
## 故障排查
### 机器人不响应消息
**原因**:机器人能力未启用,或 `DINGTALK_ALLOWED_USERS` 中不包含你的用户 ID。
**解决方法**:确认应用设置中已启用机器人能力且已选择 Stream Mode。检查你的用户 ID 是否在 `DINGTALK_ALLOWED_USERS` 中。重启 gateway。
### "dingtalk-stream not installed" 错误
**原因**Python 包 `dingtalk-stream` 未安装。
**解决方法**:安装它:
```bash
pip install dingtalk-stream httpx
```
### "DINGTALK_CLIENT_ID and DINGTALK_CLIENT_SECRET required"
**原因**:凭证未在环境变量或 `.env` 文件中设置。
**解决方法**:确认 `DINGTALK_CLIENT_ID``DINGTALK_CLIENT_SECRET` 已在 `~/.hermes/.env` 中正确设置。Client ID 是你的 AppKeyClient Secret 是钉钉开发者控制台中的 AppSecret。
### Stream 断开 / 重连循环
**原因**:网络不稳定、钉钉平台维护或凭证问题。
**解决方法**:适配器会以指数退避(2s → 5s → 10s → 30s → 60s)自动重连。检查凭证是否有效,以及应用是否未被停用。确认你的网络允许出站 WebSocket 连接。
### 机器人离线
**原因**Hermes gateway 未运行,或连接失败。
**解决方法**:检查 `hermes gateway` 是否正在运行。查看终端输出中的错误信息。常见问题:凭证错误、应用被停用、`dingtalk-stream``httpx` 未安装。
### "No session_webhook available"
**原因**:机器人尝试回复但没有 session webhook URL。通常发生在 webhook 过期或机器人在收到消息和发送回复之间重启的情况下。
**解决方法**:向机器人发送一条新消息——每条传入消息都会提供一个新的 session webhook 用于回复。这是钉钉的正常限制;机器人只能回复最近收到的消息。
## 安全
:::warning
务必设置 `DINGTALK_ALLOWED_USERS` 以限制可与机器人交互的用户。若未设置,gateway 默认拒绝所有用户作为安全措施。只添加你信任的人的用户 ID——已授权用户对 agent 的全部能力拥有完整访问权限,包括工具使用和系统访问。
:::
有关保护 Hermes Agent 部署的更多信息,请参阅[安全指南](../security.md)。
## 注意事项
- **Stream Mode**:无需公网 URL、域名或 webhook 服务器。连接由你的机器通过 WebSocket 发起,可在 NAT 和防火墙后正常工作。
- **AI 卡片**:可选择使用富文本 AI 卡片代替纯 markdown 回复。通过 `card_template_id` 配置。
- **Emoji 反应**:自动添加 🤔Thinking/🥳Done 反应以显示处理状态。
- **Markdown 响应**:回复以钉钉 markdown 格式呈现,支持富文本展示。
- **媒体支持**:传入消息中的图片和文件会自动解析,可由视觉工具处理。
- **消息去重**:适配器在 5 分钟窗口内对消息进行去重,防止同一消息被处理两次。
- **自动重连**:若 stream 连接断开,适配器会以指数退避自动重连。
- **消息长度限制**:每条消息的响应上限为 20,000 个字符,超出部分将被截断。
@@ -0,0 +1,799 @@
---
sidebar_position: 3
title: "Discord"
description: "将 Hermes Agent 设置为 Discord 机器人"
---
# Discord 设置
Hermes Agent 以机器人形式与 Discord 集成,让你可以通过私信或服务器频道与 AI 助手对话。机器人接收你的消息,通过 Hermes Agent 管道(包括工具调用、记忆和推理)进行处理,并实时响应。它支持文本、语音消息、文件附件和斜杠命令。
在开始设置之前,先介绍大多数人最想了解的内容:Hermes 进入服务器后的行为方式。
## Hermes 的行为方式
| 上下文 | 行为 |
|---------|----------|
| **私信(DM** | Hermes 响应每条消息,无需 `@提及`。每个私信有独立的会话。 |
| **服务器频道** | 默认情况下,Hermes 仅在被 `@提及` 时响应。如果你在频道中发帖但未提及它,Hermes 会忽略该消息。 |
| **自由响应频道** | 你可以通过 `DISCORD_FREE_RESPONSE_CHANNELS` 将特定频道设为无需提及,或通过 `DISCORD_REQUIRE_MENTION=false` 全局禁用提及要求。这些频道中的消息会直接回复——自动创建线程功能会被跳过,使频道保持轻量级聊天状态。 |
| **线程(Thread** | Hermes 在同一线程中回复。提及规则仍然适用,除非该线程或其父频道被配置为自由响应。线程的会话历史与父频道相互隔离。 |
| **多用户共享频道** | 默认情况下,Hermes 为安全和清晰起见,在频道内按用户隔离会话历史。在同一频道中交谈的两个人不会共享同一份对话记录,除非你明确禁用该功能。 |
| **提及其他用户的消息** | 当 `DISCORD_IGNORE_NO_MENTION``true`(默认值)时,如果消息 @提及了其他用户但**未**提及机器人,Hermes 保持沉默。这可防止机器人介入针对其他人的对话。如果你希望机器人响应所有消息而不管提及了谁,请设置为 `false`。此设置仅适用于服务器频道,不适用于私信。 |
:::tip
如果你想要一个普通的机器人帮助频道,让用户无需每次都 @标记就能与 Hermes 对话,请将该频道添加到 `DISCORD_FREE_RESPONSE_CHANNELS`
:::
### Discord Gateway(网关)模型
Hermes 在 Discord 上不是无状态回复的 webhook(网络钩子)。它通过完整的消息网关运行,这意味着每条传入消息都会经过:
1. 授权验证(`DISCORD_ALLOWED_USERS`
2. 提及 / 自由响应检查
3. 会话查找
4. 会话记录加载
5. 正常的 Hermes agent 执行,包括工具、记忆和斜杠命令
6. 将响应发送回 Discord
这一点很重要,因为在繁忙服务器中的行为取决于 Discord 路由和 Hermes 会话策略两者。
### Discord 中的会话模型
默认情况下:
- 每个私信有独立的会话
- 每个服务器线程有独立的会话命名空间
- 共享频道中的每个用户在该频道内有独立的会话
因此,如果 Alice 和 Bob 都在 `#research` 中与 Hermes 对话,即使他们使用的是同一个可见的 Discord 频道,Hermes 默认也会将其视为独立的对话。
这由 `config.yaml` 控制:
```yaml
group_sessions_per_user: true
```
仅当你明确希望整个房间共享一个对话时,才将其设置为 `false`
```yaml
group_sessions_per_user: false
```
共享会话对协作房间可能有用,但这也意味着:
- 用户共享上下文增长和 token(令牌)成本
- 一个人的长时间重度工具任务会使所有人的上下文膨胀
- 一个人正在进行的运行可能会中断同一房间中另一个人的后续操作
### 中断与并发
Hermes 按会话键跟踪正在运行的 agent。
使用默认的 `group_sessions_per_user: true` 时:
- Alice 中断自己正在进行的请求只影响她在该频道中的会话
- Bob 可以继续在同一频道中交谈,不会继承 Alice 的历史记录或中断 Alice 的运行
使用 `group_sessions_per_user: false` 时:
- 整个房间共享该频道/线程的一个正在运行的 agent 槽位
- 不同人的后续消息可能会相互中断或排队等待
本指南将引导你完成完整的设置流程——从在 Discord 开发者门户创建机器人到发送第一条消息。
## 第一步:创建 Discord 应用
1. 前往 [Discord 开发者门户](https://discord.com/developers/applications) 并使用你的 Discord 账号登录。
2. 点击右上角的 **New Application**
3. 输入应用名称(例如"Hermes Agent")并接受开发者服务条款。
4. 点击 **Create**
你将进入 **General Information** 页面。记下 **Application ID**——稍后构建邀请 URL 时需要用到。
## 第二步:创建机器人
1. 在左侧边栏中,点击 **Bot**
2. Discord 会自动为你的应用创建一个机器人用户。你会看到机器人的用户名,可以自定义。
3.**Authorization Flow** 下:
-**Public Bot** 设置为 **ON**——使用 Discord 提供的邀请链接时需要此设置(推荐)。这允许 Installation 标签页生成默认授权 URL。
-**Require OAuth2 Code Grant** 保持为 **OFF**
:::tip
你可以在此页面为机器人设置自定义头像和横幅,这是用户在 Discord 中看到的样子。
:::
:::info[私有机器人替代方案]
如果你希望保持机器人私有(Public Bot = OFF),则**必须**在第五步中使用**手动 URL** 方法,而不是 Installation 标签页。Discord 提供的链接需要启用 Public Bot。
:::
## 第三步:启用特权网关 Intent(意图)
这是整个设置过程中最关键的步骤。如果没有启用正确的 intent,你的机器人将连接到 Discord,但**无法读取消息内容**。
**Bot** 页面,向下滚动到 **Privileged Gateway Intents**。你会看到三个开关:
| Intent | 用途 | 是否必需? |
|--------|---------|-----------|
| **Presence Intent** | 查看用户在线/离线状态 | 可选 |
| **Server Members Intent** | 访问成员列表、解析用户名 | **必需** |
| **Message Content Intent** | 读取消息的文本内容 | **必需** |
**将 Server Members Intent 和 Message Content Intent 都切换为 ON。**
- 没有 **Message Content Intent**,你的机器人会收到消息事件,但消息文本为空——机器人实际上看不到你输入的内容。
- 没有 **Server Members Intent**,机器人无法解析允许用户列表中的用户名,可能无法识别是谁在发消息。
:::warning[这是 Discord 机器人不工作的第一大原因]
如果你的机器人在线但从不响应消息,**Message Content Intent** 几乎可以肯定是被禁用了。返回 [开发者门户](https://discord.com/developers/applications),选择你的应用 → Bot → Privileged Gateway Intents,确保 **Message Content Intent** 已切换为 ON。点击 **Save Changes**
:::
**关于服务器数量:**
- 如果你的机器人在**少于 100 个服务器**中,可以自由切换 intent。
- 如果你的机器人在 **100 个或更多服务器**中,Discord 要求你提交验证申请才能使用特权 intent。对于个人使用,这不是问题。
点击页面底部的 **Save Changes**
## 第四步:获取机器人 Token
机器人 token(令牌)是 Hermes Agent 用于以你的机器人身份登录的凭据。仍在 **Bot** 页面:
1.**Token** 部分,点击 **Reset Token**
2. 如果你的 Discord 账号启用了双重身份验证,请输入你的 2FA 代码。
3. Discord 将显示你的新 token。**立即复制它。**
:::warning[Token 仅显示一次]
Token 只显示一次。如果丢失,你需要重置并生成新的 token。切勿公开分享你的 token 或将其提交到 Git——任何拥有此 token 的人都可以完全控制你的机器人。
:::
将 token 存储在安全的地方(例如密码管理器)。你将在第八步中用到它。
## 第五步:生成邀请 URL
你需要一个 OAuth2 URL 来将机器人邀请到你的服务器。有两种方式:
### 方式 A:使用 Installation 标签页(推荐)
:::note[需要 Public Bot]
此方法要求在第二步中将 **Public Bot** 设置为 **ON**。如果你将 Public Bot 设置为 OFF,请改用下面的手动 URL 方法。
:::
1. 在左侧边栏中,点击 **Installation**
2.**Installation Contexts** 下,启用 **Guild Install**
3. 对于 **Install Link**,选择 **Discord Provided Link**
4. 在 Guild Install 的 **Default Install Settings** 下:
- **Scopes**:选择 `bot``applications.commands`
- **Permissions**:选择下面列出的权限。
### 方式 B:手动 URL
你可以使用以下格式直接构建邀请 URL:
```
https://discord.com/oauth2/authorize?client_id=YOUR_APP_ID&scope=bot+applications.commands&permissions=274878286912
```
`YOUR_APP_ID` 替换为第一步中的 Application ID。
### 所需权限
以下是机器人所需的最低权限:
- **View Channels** — 查看其有权访问的频道
- **Send Messages** — 响应你的消息
- **Embed Links** — 格式化富文本响应
- **Attach Files** — 发送图片、音频和文件输出
- **Read Message History** — 维护对话上下文
### 推荐的附加权限
- **Send Messages in Threads** — 在线程对话中响应
- **Add Reactions** — 对消息添加反应以示确认
### 权限整数
| 级别 | 权限整数 | 包含内容 |
|-------|-------------------|-----------------|
| 最低 | `117760` | View Channels、Send Messages、Read Message History、Attach Files |
| 推荐 | `274878286912` | 以上所有权限,加上 Embed Links、Send Messages in Threads、Add Reactions |
## 第六步:邀请到你的服务器
1. 在浏览器中打开邀请 URL(来自 Installation 标签页或你构建的手动 URL)。
2.**Add to Server** 下拉菜单中,选择你的服务器。
3. 点击 **Continue**,然后点击 **Authorize**
4. 如有提示,完成 CAPTCHA 验证。
:::info
你需要在 Discord 服务器上拥有 **Manage Server** 权限才能邀请机器人。如果你在下拉菜单中看不到你的服务器,请让服务器管理员使用邀请链接。
:::
授权后,机器人将出现在你服务器的成员列表中(在你启动 Hermes 网关之前,它会显示为离线)。
## 第七步:找到你的 Discord 用户 ID
Hermes Agent 使用你的 Discord 用户 ID 来控制谁可以与机器人交互。查找方式:
1. 打开 Discord(桌面或网页应用)。
2. 前往 **Settings****Advanced** → 将 **Developer Mode** 切换为 **ON**
3. 关闭设置。
4. 右键点击你自己的用户名(在消息中、成员列表中或你的个人资料中)→ **Copy User ID**
你的用户 ID 是一个类似 `284102345871466496` 的长数字。
:::tip
开发者模式还允许你以相同方式复制**频道 ID** 和**服务器 ID**——右键点击频道或服务器名称并选择 Copy ID。如果你想手动设置主频道,将需要频道 ID。
:::
## 第八步:配置 Hermes Agent
### 方式 A:交互式设置(推荐)
运行引导式设置命令:
```bash
hermes gateway setup
```
在提示时选择 **Discord**,然后在询问时粘贴你的机器人 token 和用户 ID。
### 方式 B:手动配置
将以下内容添加到你的 `~/.hermes/.env` 文件:
```bash
# 必填
DISCORD_BOT_TOKEN=your-bot-token
DISCORD_ALLOWED_USERS=284102345871466496
# 多个允许用户(逗号分隔)
# DISCORD_ALLOWED_USERS=284102345871466496,198765432109876543
```
然后启动网关:
```bash
hermes gateway
```
机器人应在几秒钟内在 Discord 中上线。发送一条消息——私信或在它可以看到的频道中——进行测试。
:::tip
你可以在后台运行 `hermes gateway` 或将其作为 systemd 服务以持续运行。详情请参阅部署文档。
:::
## 配置参考
Discord 行为通过两个文件控制:**`~/.hermes/.env`** 用于凭据和环境级开关,**`~/.hermes/config.yaml`** 用于结构化设置。当两者都设置时,环境变量始终优先于 config.yaml 的值。
### 环境变量(`.env`
| 变量 | 是否必填 | 默认值 | 描述 |
|----------|----------|---------|-------------|
| `DISCORD_BOT_TOKEN` | **是** | — | 来自 [Discord 开发者门户](https://discord.com/developers/applications) 的机器人 token。 |
| `DISCORD_ALLOWED_USERS` | **是** | — | 允许与机器人交互的 Discord 用户 ID,逗号分隔。没有此项**或** `DISCORD_ALLOWED_ROLES`,网关将拒绝所有用户。 |
| `DISCORD_ALLOWED_ROLES` | 否 | — | Discord 角色 ID,逗号分隔。拥有其中任一角色的成员即被授权——与 `DISCORD_ALLOWED_USERS` 为 OR 语义。连接时自动启用 **Server Members Intent**。适用于管理团队频繁变动的场景:新管理员一旦被授予角色即可获得访问权限,无需推送配置。 |
| `DISCORD_HOME_CHANNEL` | 否 | — | 机器人发送主动消息(cron 输出、提醒、通知)的频道 ID。 |
| `DISCORD_HOME_CHANNEL_NAME` | 否 | `"Home"` | 主频道在日志和状态输出中的显示名称。 |
| `DISCORD_COMMAND_SYNC_POLICY` | 否 | `"safe"` | 控制原生斜杠命令启动同步。`"safe"` 对现有全局命令进行差异比较,仅更新已更改的内容,当 Discord 元数据更改无法通过补丁应用时重新创建命令。`"bulk"` 保留旧的 `tree.sync()` 行为。`"off"` 完全跳过启动同步。 |
| `DISCORD_REQUIRE_MENTION` | 否 | `true` | 为 `true` 时,机器人仅在服务器频道中被 `@提及` 时响应。设置为 `false` 可响应每个频道中的所有消息。 |
| `DISCORD_THREAD_REQUIRE_MENTION` | 否 | `false` | 为 `true` 时,禁用线程内的提及快捷方式——线程与频道的门控方式相同,即使机器人已经参与其中,也需要 `@提及`。当多个机器人共享一个线程且你希望每个机器人仅在明确 `@提及` 时触发时使用此设置。 |
| `DISCORD_FREE_RESPONSE_CHANNELS` | 否 | — | 机器人无需 `@提及` 即可响应的频道 ID,逗号分隔,即使 `DISCORD_REQUIRE_MENTION``true` 也适用。 |
| `DISCORD_IGNORE_NO_MENTION` | 否 | `true` | 为 `true` 时,如果消息 `@提及` 了其他用户但**未**提及机器人,机器人保持沉默。防止机器人介入针对其他人的对话。仅适用于服务器频道,不适用于私信。 |
| `DISCORD_AUTO_THREAD` | 否 | `true` | 为 `true` 时,自动为文本频道中的每次 `@提及` 创建新线程,使每个对话相互隔离(类似 Slack 行为)。已在线程或私信中的消息不受影响。 |
| `DISCORD_ALLOW_BOTS` | 否 | `"none"` | 控制机器人如何处理来自其他 Discord 机器人的消息。`"none"` — 忽略所有其他机器人。`"mentions"` — 仅接受 `@提及` Hermes 的机器人消息。`"all"` — 接受所有机器人消息。 |
| `DISCORD_REACTIONS` | 否 | `true` | 为 `true` 时,机器人在处理过程中为消息添加 emoji 反应(开始时 👀,成功时 ✅,出错时 ❌)。设置为 `false` 可完全禁用反应。 |
| `DISCORD_IGNORED_CHANNELS` | 否 | — | 机器人**永不**响应的频道 ID,逗号分隔,即使被 `@提及` 也不响应。优先于所有其他频道设置。 |
| `DISCORD_ALLOWED_CHANNELS` | 否 | — | 频道 ID,逗号分隔。设置后,机器人**仅**在这些频道(以及允许的私信)中响应。覆盖 `config.yaml` 中的 `discord.allowed_channels`。与 `DISCORD_IGNORED_CHANNELS` 结合使用可表达允许/拒绝规则。 |
| `DISCORD_NO_THREAD_CHANNELS` | 否 | — | 机器人直接在频道中响应而不创建线程的频道 ID,逗号分隔。仅在 `DISCORD_AUTO_THREAD``true` 时有效。 |
| `DISCORD_HISTORY_BACKFILL` | 否 | `true` | 为 `true` 时,当机器人被提及时,将最近的频道滚动历史(自机器人上次响应以来)前置到用户消息中。恢复机器人在 `require_mention` 模式下会错过的上下文。在私信和自由响应频道中跳过。设置为 `false` 可禁用。 |
| `DISCORD_HISTORY_BACKFILL_LIMIT` | 否 | `50` | 组装回填块时向后扫描的最大消息数。实际上扫描通常会更早停止——在机器人自己在频道中的最后一条消息处。 |
| `DISCORD_REPLY_TO_MODE` | 否 | `"first"` | 控制回复引用行为:`"off"` — 从不回复原始消息,`"first"` — 仅在第一个消息块上添加回复引用(默认),`"all"` — 在每个块上都添加回复引用。 |
| `DISCORD_ALLOW_MENTION_EVERYONE` | 否 | `false` | 为 `false`(默认)时,即使响应中包含这些 token,机器人也无法 ping `@everyone``@here`。设置为 `true` 可重新启用。参见下方[提及控制](#mention-control)。 |
| `DISCORD_ALLOW_MENTION_ROLES` | 否 | `false` | 为 `false`(默认)时,机器人无法 ping `@role` 提及。设置为 `true` 可允许。 |
| `DISCORD_ALLOW_MENTION_USERS` | 否 | `true` | 为 `true`(默认)时,机器人可以通过 ID ping 单个用户。 |
| `DISCORD_ALLOW_MENTION_REPLIED_USER` | 否 | `true` | 为 `true`(默认)时,回复消息会 ping 原始作者。 |
| `DISCORD_PROXY` | 否 | — | Discord 连接的代理 URLHTTP、WebSocket、REST)。覆盖 `HTTPS_PROXY`/`ALL_PROXY`。支持 `http://``https://``socks5://` 协议。 |
| `DISCORD_ALLOW_ANY_ATTACHMENT` | 否 | `false` | 为 `true` 时,机器人接受任何文件类型的附件(不仅限于内置的 PDF/文本/zip/office 允许列表)。未知类型会被缓存到磁盘,并以 `application/octet-stream` MIME 类型作为本地路径提供给 agent,以便它可以使用 `terminal` / `read_file` / `ffprobe` 等工具检查。 |
| `DISCORD_MAX_ATTACHMENT_BYTES` | 否 | `33554432` | 网关将下载并缓存的每个附件的最大字节数。默认 32 MiB。设置为 `0` 表示无上限(附件在写入时保存在内存中,因此无限制会带来真实的内存成本)。 |
| `HERMES_DISCORD_TEXT_BATCH_DELAY_SECONDS` | 否 | `0.6` | 适配器在刷新排队文本块之前等待的宽限窗口。用于平滑流式输出。 |
| `HERMES_DISCORD_TEXT_BATCH_SPLIT_DELAY_SECONDS` | 否 | `2.0` | 当单条消息超过 Discord 长度限制时,分割块之间的延迟。 |
### 配置文件(`config.yaml`
`~/.hermes/config.yaml` 中的 `discord` 部分与上述环境变量对应。config.yaml 设置作为默认值应用——如果已设置等效的环境变量,则环境变量优先。
```yaml
# Discord 特定设置
discord:
require_mention: true # 在服务器频道中需要 @提及
thread_require_mention: false # 为 true 时,线程中也需要 @提及(多机器人线程)
free_response_channels: "" # 逗号分隔的频道 ID(或 YAML 列表)
auto_thread: true # 在 @提及 时自动创建线程
reactions: true # 处理过程中添加 emoji 反应
ignored_channels: [] # 机器人永不响应的频道 ID
no_thread_channels: [] # 机器人不创建线程直接响应的频道 ID
history_backfill: true # 在提及时前置最近的频道滚动历史(默认:true)
history_backfill_limit: 50 # 向后扫描的最大消息数(默认:50
channel_prompts: {} # 每个频道的临时系统 prompt(提示词)
allow_mentions: # 机器人允许 ping 的内容(安全默认值)
everyone: false # @everyone / @here ping(默认:false
roles: false # @role ping(默认:false
users: true # @user ping(默认:true
replied_user: true # 回复引用会 ping 作者(默认:true)
# 会话隔离(适用于所有网关平台,不仅限于 Discord)
group_sessions_per_user: true # 在共享频道中按用户隔离会话
```
#### `discord.require_mention`
**类型:** 布尔值 — **默认值:** `true`
启用后,机器人仅在服务器频道中被直接 `@提及` 时响应。无论此设置如何,私信始终会得到响应。
#### `discord.thread_require_mention`
**类型:** 布尔值 — **默认值:** `false`
默认情况下,一旦机器人参与了某个线程(通过 `@提及` 自动创建或回复过一次),它就会继续响应该线程中的每条后续消息,无需再次 `@提及`。这对于一对一对话来说是正确的默认行为。
在**多机器人线程**中,用户每次只与一个机器人交流,这个默认行为会成为隐患——线程中的每个其他机器人也会对每条消息触发,消耗额度并刷屏。将 `thread_require_mention: true` 设置为禁用线程内快捷方式,使线程与频道的门控方式相同。显式 `@提及` 仍然有效。
```yaml
discord:
require_mention: true
thread_require_mention: true # 多机器人设置
```
#### `discord.free_response_channels`
**类型:** 字符串或列表 — **默认值:** `""`
机器人无需 `@提及` 即可响应所有消息的频道 ID。接受逗号分隔的字符串或 YAML 列表:
```yaml
# 字符串格式
discord:
free_response_channels: "1234567890,9876543210"
# 列表格式
discord:
free_response_channels:
- 1234567890
- 9876543210
```
如果线程的父频道在此列表中,该线程也变为无需提及。
自由响应频道还会**跳过自动创建线程**——机器人直接回复而不是为每条消息创建新线程。这使频道可用作轻量级聊天界面。如果你想要线程行为,不要将频道列为自由响应(改用普通的 `@提及` 流程)。
#### `discord.auto_thread`
**类型:** 布尔值 — **默认值:** `true`
启用后,普通文本频道中的每次 `@提及` 都会自动为对话创建新线程。这保持主频道整洁,并为每个对话提供独立的会话历史。一旦创建线程,该线程中的后续消息不需要 `@提及`——机器人知道它已经在参与其中。对于多机器人设置,将 [`thread_require_mention`](#discordthread_require_mention) 设置为 `true` 可禁用此线程内快捷方式。
在现有线程或私信中发送的消息不受此设置影响。`discord.free_response_channels``discord.no_thread_channels` 中列出的频道也会绕过自动创建线程,改为直接回复。
#### `discord.reactions`
**类型:** 布尔值 — **默认值:** `true`
控制机器人是否为消息添加 emoji 反应作为视觉反馈:
- 👀 机器人开始处理你的消息时添加
- ✅ 响应成功发送时添加
- ❌ 处理过程中发生错误时添加
如果你觉得反应令人分心,或者机器人的角色没有 **Add Reactions** 权限,请禁用此功能。
#### `discord.ignored_channels`
**类型:** 字符串或列表 — **默认值:** `[]`
机器人**永不**响应的频道 ID,即使被直接 `@提及` 也不响应。这具有最高优先级——如果频道在此列表中,机器人会静默忽略那里的所有消息,无论 `require_mention``free_response_channels` 或任何其他设置如何。
```yaml
# 字符串格式
discord:
ignored_channels: "1234567890,9876543210"
# 列表格式
discord:
ignored_channels:
- 1234567890
- 9876543210
```
如果线程的父频道在此列表中,该线程中的消息也会被忽略。
#### `discord.no_thread_channels`
**类型:** 字符串或列表 — **默认值:** `[]`
机器人直接在频道中响应而不自动创建线程的频道 ID。仅在 `auto_thread``true`(默认值)时有效。在这些频道中,机器人像普通消息一样直接回复,而不是创建新线程。
```yaml
discord:
no_thread_channels:
- 1234567890 # 机器人在此处直接回复
```
适用于专门用于机器人交互的频道,在这些频道中线程会增加不必要的噪音。
#### `discord.channel_prompts`
**类型:** 映射 — **默认值:** `{}`
每个频道的临时系统 prompt(提示词),在匹配的 Discord 频道或线程的每次对话轮次中注入,不会持久化到对话记录历史中。
```yaml
discord:
channel_prompts:
"1234567890": |
This channel is for research tasks. Prefer deep comparisons,
citations, and concise synthesis.
"9876543210": |
This forum is for therapy-style support. Be warm, grounded,
and non-judgmental.
```
行为:
- 精确的线程/频道 ID 匹配优先。
- 如果消息到达线程或论坛帖子内,且该线程没有明确条目,Hermes 会回退到父频道/论坛 ID。
- Prompt 在运行时临时应用,因此更改后立即影响后续轮次,无需重写过去的会话历史。
#### `discord.history_backfill`
**类型:** 布尔值 — **默认值:** `true`
启用后,机器人在每次 `@提及` 时恢复错过的频道消息。当 `require_mention: true` 时,机器人只处理直接标记它的消息——频道中的其他所有内容对会话记录都是不可见的。历史回填在触发时向后扫描最近的频道历史,收集机器人上次响应与当前提及之间的消息,并将其作为上下文包含进来。
按界面的行为:
- **服务器频道**(使用 `require_mention: true`):回填扫描自机器人上次响应以来的频道。当其他参与者在机器人未被提及时发帖时很有用。
- **线程**:回填仅扫描该线程——Discord 对线程的 `channel.history()` 只返回该线程的消息,不包括父频道。这是正确的范围,因为线程通常是自包含的对话。
- **私信**:跳过。每条私信消息都会触发机器人,因此会话记录已经完整——没有提及间隙需要填补。
- **自由响应频道**和**机器人自动创建的线程**:出于同样的原因跳过——没有提及门控意味着没有间隙。
每用户会话(`group_sessions_per_user: true`,默认值)也受益:用户的会话缺少其他频道参与者发布的上下文以及用户在标记机器人之前自己的消息。回填填补了这两个间隙。
```yaml
discord:
history_backfill: true # 默认
```
关闭方式:
```yaml
discord:
history_backfill: false
```
> **注意:** 机器人处理*过程中*到达的消息(在触发和响应之间)不会被捕获。这是一个可接受的简化——用户可以重新发送或再次标记。
#### `discord.history_backfill_limit`
**类型:** 整数 — **默认值:** `50`
恢复频道上下文时向后扫描的最大消息数。实际上扫描通常会更早停止——在机器人自己在频道中的最后一条消息处,这是轮次之间的自然边界。此限制是冷启动和长间隙(最近历史中不存在先前机器人消息)的安全上限。
```yaml
discord:
history_backfill: true
history_backfill_limit: 50
```
#### `group_sessions_per_user`
**类型:** 布尔值 — **默认值:** `true`
这是一个全局网关设置(非 Discord 专用),控制同一频道中的用户是否获得隔离的会话历史。
`true` 时:Alice 和 Bob 在 `#research` 中交谈,各自与 Hermes 有独立的对话。为 `false` 时:整个频道共享一份对话记录和一个正在运行的 agent 槽位。
```yaml
group_sessions_per_user: true
```
有关每种模式的完整含义,请参阅上方的[会话模型](#session-model-in-discord)部分。
#### `display.tool_progress`
**类型:** 字符串 — **默认值:** `"all"`**可选值:** `off``new``all``verbose`
控制机器人在处理过程中是否在聊天中发送进度消息(例如"正在读取文件……"、"正在运行终端命令……")。这是适用于所有平台的全局网关设置。
```yaml
display:
tool_progress: "all" # off | new | all | verbose
```
- `off` — 不发送进度消息
- `new` — 每次轮次只显示第一个工具调用
- `all` — 显示所有工具调用(在网关消息中截断为 40 个字符)
- `verbose` — 显示完整的工具调用详情(可能产生较长的消息)
#### `display.tool_progress_command`
**类型:** 布尔值 — **默认值:** `false`
启用后,在网关中提供 `/verbose` 斜杠命令,让你无需编辑 config.yaml 即可循环切换工具进度模式(`off → new → all → verbose → off`)。
```yaml
display:
tool_progress_command: true
```
## 斜杠命令访问控制
默认情况下,每个允许的用户都可以运行每个斜杠命令。要将你的允许列表分为**管理员**(完整斜杠命令访问权限)和**普通用户**(仅你明确启用的命令),请在 Discord 平台的 `extra` 块中添加 `allow_admin_from``user_allowed_commands`
```yaml
gateway:
platforms:
discord:
extra:
# 现有用户允许列表(不变)
allow_from:
- "123456789012345678" # 管理员用户 ID
- "999888777666555444" # 普通用户 ID
# 新增 — 管理员可访问所有斜杠命令(内置 + 插件)
allow_admin_from:
- "123456789012345678"
# 新增 — 非管理员允许用户只能运行这些斜杠命令。
# /help 和 /whoami 始终允许,以便用户查看其访问权限。
user_allowed_commands:
- status
- model
- history
# 可选:为服务器频道设置单独的管理员/命令列表
group_allow_admin_from:
- "123456789012345678"
group_user_allowed_commands:
- status
```
**行为:**
- 在某个范围(私信或服务器频道)的 `allow_admin_from` 中的用户可以通过实时命令注册表运行**每个**已注册的斜杠命令——内置的和插件注册的都包括。
- 不在 `allow_admin_from` 中的用户只能运行 `user_allowed_commands` 中列出的命令,加上始终允许的基础命令:`/help``/whoami`
- 普通聊天(非斜杠消息)不受影响。非管理员用户仍然可以正常与 agent 对话;他们只是无法触发任意命令。
- **向后兼容:** 如果某个范围未设置 `allow_admin_from`,则该范围的斜杠命令门控被禁用。现有安装无需任何更改即可继续工作。
- 私信管理员状态不意味着服务器频道管理员状态。每个范围有自己的管理员列表。
使用 `/whoami` 查看当前范围、你的级别(管理员 / 用户 / 无限制)以及你可以运行的斜杠命令。
## 交互式模型选择器
在 Discord 频道中不带参数发送 `/model` 以打开基于下拉菜单的模型选择器:
1. **提供商选择** — 显示可用提供商的 Select 下拉菜单(最多 25 个)。
2. **模型选择** — 显示所选提供商模型的第二个下拉菜单(最多 25 个)。
选择器在 120 秒后超时。只有授权用户(`DISCORD_ALLOWED_USERS` 中的用户)才能与其交互。如果你知道模型名称,可以直接输入 `/model <名称>`
## 技能的原生斜杠命令
Hermes 自动将已安装的技能注册为**原生 Discord 应用命令**。这意味着技能会出现在 Discord 的自动补全 `/` 菜单中,与内置命令并列。
- 每个技能成为一个 Discord 斜杠命令(例如 `/code-review``/ascii-art`
- 技能接受一个可选的 `args` 字符串参数
- Discord 每个机器人有 100 个应用命令的限制——如果你的技能数量超过可用槽位,多余的技能会被跳过并在日志中显示警告
- 技能在机器人启动时与内置命令(如 `/model``/reset``/background`)一起注册
无需额外配置——通过 `hermes skills install` 安装的任何技能都会在下次网关重启时自动注册为 Discord 斜杠命令。
### 禁用斜杠命令注册
如果你针对同一个 Discord 应用运行多个 Hermes 网关(例如测试环境 + 生产环境),只有其中一个应该拥有全局斜杠命令注册——否则最后启动的那个会覆盖之前的注册,导致注册状态不稳定。在"从属"网关上关闭斜杠注册:
```yaml
gateway:
platforms:
discord:
extra:
slash_commands: false # 默认:true
```
在"主"网关上保持 `true` 可维持正常行为——为内置命令和已安装技能提供全局 `/` 菜单命令。
## 发送媒体(`send_message` + `MEDIA:` 标签)
Discord 适配器通过 `send_message` 工具和 agent 发出的内联 `MEDIA:/path/to/file` 标签,支持所有常见媒体类型的原生文件上传:
| 类型 | 发送方式 |
|---|---|
| 图片(PNG/JPG/WebP | 原生 Discord 图片附件,带内联预览 |
| 动态 GIF | `send_animation``animation.gif` 上传,使 Discord 内联播放(而非静态缩略图) |
| 视频(MP4/MOV | `send_video` — 原生视频播放器 |
| 音频 / 语音 | `send_voice` — 尽可能使用原生语音消息,否则使用文件附件 |
| 文档(PDF/ZIP/docx 等) | `send_document` — 带下载按钮的原生附件 |
Discord 的每次上传大小限制取决于服务器的加成等级(免费 25 MB,最高 500 MB)。如果 Hermes 收到 HTTP 413,适配器会回退到指向本地缓存路径的链接,而不是静默失败。
## 接收任意文件类型
默认情况下,机器人缓存与内置允许列表匹配的上传——图片、音频、视频、PDF、文本/markdown/csv/log、JSON/XML/YAML/TOML、zip、docx/xlsx/pptx。其他任何内容(`.wav``.bin`、自定义扩展名的转储文件)都会被记录为 `Unsupported document type` 并在 agent 看到之前被丢弃。
要接受任意文件类型,启用 `discord.allow_any_attachment`
```yaml
discord:
allow_any_attachment: true
# 可选 — 提高/禁用每文件大小上限。默认为 32 MiB。
# 整个文件在缓存时保存在内存中,因此无限制
# 上传会带来真实的内存成本。
max_attachment_bytes: 33554432 # 字节;0 = 无限制
```
启用该标志后,任何上传的文件都会被下载、缓存到 `~/.hermes/cache/documents/` 下,并以 `application/octet-stream` MIME 类型的 `DOCUMENT` 类型消息事件提供给 agent。Agent 收到指向本地路径的上下文说明(通过 `to_agent_visible_cache_path` 为 Docker/Modal 沙盒终端自动转换),可以使用 `terminal``ffprobe``unzip``file``strings` 等)或 `read_file` 检查文件。文件内容**不会**内联到 prompt 中——只有路径——因此二进制上传不会撑爆上下文窗口。
已在允许列表中的已知文本格式(`.txt``.md``.log`)继续自动注入最多 100 KiB 的内容;启用该标志后此行为不变。
等效环境变量:`DISCORD_ALLOW_ANY_ATTACHMENT=true``DISCORD_MAX_ATTACHMENT_BYTES=33554432`(或 `0` 表示无上限)。
:::warning 无限制的内存成本
禁用大小上限(`max_attachment_bytes: 0`)意味着用户可以向机器人上传数 GB 的文件,网关会尽职地在缓存到磁盘时将其缓冲到内存中。仅在受信任的单用户安装中设置此项。对于共享机器人,保持默认的 32 MiB 或保守地提高上限。
:::
## 交互式提示(clarify
当 agent 调用 `clarify` 工具时——询问你偏好哪种方式、获取任务后反馈或在非平凡决策前确认——Discord 会以**每个选项一个按钮**的形式渲染问题:
> 我应该为仪表板使用哪个框架?
>
> [1. Next.js] [2. Remix] [3. Astro] [其他(输入答案)]
点击编号按钮作答,或点击**其他**输入自由格式的响应(你在该频道中发送的下一条消息将成为答案)。开放式的 `clarify` 调用(没有预设选项)会跳过按钮,直接捕获你的下一条消息。
按钮在做出选择后会自动禁用,防止重复点击导致重复解析提示。通过 `~/.hermes/config.yaml` 中的 `agent.clarify_timeout` 配置响应超时(默认 `600` 秒)。如果你在超时内没有响应,agent 会以一条哨兵消息解除阻塞并自行调整,而不是一直挂起。
## 主频道
你可以指定一个"主频道",机器人在此发送主动消息(例如 cron 任务输出、提醒和通知)。有两种设置方式:
### 使用斜杠命令
在机器人所在的任意 Discord 频道中输入 `/sethome`。该频道即成为主频道。
### 手动配置
将以下内容添加到你的 `~/.hermes/.env`
```bash
DISCORD_HOME_CHANNEL=123456789012345678
DISCORD_HOME_CHANNEL_NAME="#bot-updates"
```
将 ID 替换为实际的频道 ID(开启开发者模式后右键点击 → Copy Channel ID)。
## 语音消息
Hermes Agent 支持 Discord 语音消息:
- **传入语音消息**使用配置的 STT 提供商自动转录:本地 `faster-whisper`(无需密钥)、Groq Whisper`GROQ_API_KEY`)或 OpenAI Whisper`VOICE_TOOLS_OPENAI_KEY`)。
- **文字转语音**:使用 `/voice tts` 让机器人在文字回复的同时发送语音音频响应。
- **Discord 语音频道**:Hermes 还可以加入语音频道,聆听用户说话,并在频道中回话。
完整的设置和操作指南,请参阅:
- [语音模式](/user-guide/features/voice-mode)
- [与 Hermes 使用语音模式](/guides/use-voice-mode-with-hermes)
## 论坛频道
Discord 论坛频道(类型 15)不接受直接消息——论坛中的每个帖子都必须是线程。Hermes 自动检测论坛频道,并在需要发送消息时创建新的线程帖子,因此 `send_message`、TTS、图片、语音消息和文件附件都无需 agent 进行特殊处理即可正常工作。
- **线程名称**从消息的第一行派生(去除 markdown 标题前缀,上限 100 个字符)。当消息仅包含附件时,文件名用作备用线程名称。
- **附件**随新线程的起始消息一起发送——无需单独上传步骤,不会出现部分发送。
- **一次调用,一个线程**:每次论坛发送都会创建一个新线程。因此,连续向同一论坛发送消息会产生独立的线程。
- **检测分三层**:首先是频道目录缓存,其次是进程本地探测缓存,最后是实时 `GET /channels/{id}` 探测(其结果在进程生命周期内被记忆化)。
刷新目录(在暴露该功能的平台上使用 `/channels refresh`,或重启网关)会将机器人启动后创建的任何论坛频道填充到缓存中。
## 故障排除
### 机器人在线但不响应消息
**原因**Message Content Intent 被禁用。
**解决方法**:前往[开发者门户](https://discord.com/developers/applications) → 你的应用 → Bot → Privileged Gateway Intents → 启用 **Message Content Intent** → Save Changes。重启网关。
### 启动时出现"Disallowed Intents"错误
**原因**:你的代码请求了开发者门户中未启用的 intent。
**解决方法**:在 Bot 设置中启用所有三个 Privileged Gateway IntentsPresence、Server Members、Message Content),然后重启。
### 机器人看不到特定频道中的消息
**原因**:机器人的角色没有查看该频道的权限。
**解决方法**:在 Discord 中,前往频道设置 → Permissions → 为机器人的角色添加 **View Channel****Read Message History** 权限。
### 403 Forbidden 错误
**原因**:机器人缺少所需权限。
**解决方法**:使用第五步中的 URL 以正确权限重新邀请机器人,或在 Server Settings → Roles 中手动调整机器人的角色权限。
### 机器人离线
**原因**Hermes 网关未运行,或 token 不正确。
**解决方法**:检查 `hermes gateway` 是否正在运行。验证 `.env` 文件中的 `DISCORD_BOT_TOKEN`。如果你最近重置了 token,请更新它。
### "User not allowed" / 机器人忽略你
**原因**:你的用户 ID 不在 `DISCORD_ALLOWED_USERS` 中。
**解决方法**:将你的用户 ID 添加到 `~/.hermes/.env` 中的 `DISCORD_ALLOWED_USERS` 并重启网关。
### 同一频道中的用户意外共享上下文
**原因**`group_sessions_per_user` 被禁用,或平台无法为该上下文中的消息提供用户 ID。
**解决方法**:在 `~/.hermes/config.yaml` 中进行以下设置并重启网关:
```yaml
group_sessions_per_user: true
```
如果你有意想要共享房间对话,则保持关闭——只需预期会有共享的对话记录历史和共享的中断行为。
## 安全
:::warning
始终设置 `DISCORD_ALLOWED_USERS`(或 `DISCORD_ALLOWED_ROLES`)以限制谁可以与机器人交互。没有任何一项,网关默认拒绝所有用户作为安全措施。只授权你信任的人——授权用户对 agent 的功能拥有完全访问权限,包括工具调用和系统访问。
:::
### 基于角色的访问控制
对于通过角色而非个人用户列表管理访问权限的服务器(管理团队、支持人员、内部工具),使用 `DISCORD_ALLOWED_ROLES`——逗号分隔的角色 ID 列表。拥有其中任一角色的成员即被授权。
```bash
# ~/.hermes/.env — 与 DISCORD_ALLOWED_USERS 配合使用或替代使用
DISCORD_ALLOWED_ROLES=987654321098765432,876543210987654321
```
语义:
- **与用户允许列表为 OR 关系。** 如果用户 ID 在 `DISCORD_ALLOWED_USERS` 中**或**拥有 `DISCORD_ALLOWED_ROLES` 中的任一角色,则该用户被授权。
- **自动启用 Server Members Intent。** 设置 `DISCORD_ALLOWED_ROLES` 后,机器人在连接时启用 Members intent——Discord 需要此 intent 才能在成员记录中发送角色信息。
- **角色 ID,不是名称。** 从 Discord 获取:**用户设置 → 高级 → 开启开发者模式**,然后右键点击任意角色 → **Copy Role ID**
- **私信回退。** 在私信中,角色检查会扫描共同服务器;在任何共享服务器中拥有允许角色的用户在私信中也被授权。
当管理团队频繁变动时,这是首选模式——新管理员一旦被授予角色即可获得访问权限,无需编辑 `.env` 或重启网关。
### 提及控制
默认情况下,Hermes 会阻止机器人 ping `@everyone``@here` 和角色提及,即使其回复中包含这些 token 也不例外。这可防止措辞不当的 prompt 或回显的用户内容向整个服务器发送垃圾消息。个人 `@user` ping 和回复引用 ping"回复……"小标签)保持启用,以便正常对话仍然有效。
你可以通过环境变量或 `config.yaml` 放宽这些默认值:
```yaml
# ~/.hermes/config.yaml
discord:
allow_mentions:
everyone: false # 允许机器人 ping @everyone / @here
roles: false # 允许机器人 ping @role 提及
users: true # 允许机器人 ping 个人 @user
replied_user: true # 回复消息时 ping 原始作者
```
```bash
# ~/.hermes/.env — 环境变量优先于 config.yaml
DISCORD_ALLOW_MENTION_EVERYONE=false
DISCORD_ALLOW_MENTION_ROLES=false
DISCORD_ALLOW_MENTION_USERS=true
DISCORD_ALLOW_MENTION_REPLIED_USER=true
```
:::tip
除非你确切知道为什么需要,否则将 `everyone``roles` 保持为 `false`。LLM 很容易在看似正常的响应中生成字符串 `@everyone`;没有此保护,这将通知你服务器的每个成员。
:::
有关保护 Hermes Agent 部署的更多信息,请参阅[安全指南](../security.md)。
@@ -0,0 +1,190 @@
---
sidebar_position: 7
title: "电子邮件"
description: "通过 IMAP/SMTP 将 Hermes Agent 设置为电子邮件助手"
---
# 电子邮件设置
Hermes 可以使用标准 IMAP 和 SMTP 协议接收并回复电子邮件。向 Agent 的邮箱地址发送邮件,它会在同一线程中回复——无需特殊客户端或 bot API。支持 Gmail、Outlook、Yahoo、Fastmail,以及任何支持 IMAP/SMTP 的邮件服务商。
:::info 无外部依赖
Email 适配器使用 Python 内置的 `imaplib``smtplib``email` 模块,无需额外安装软件包或外部服务。
:::
---
## 前提条件
- **为 Hermes Agent 准备一个专用邮箱账户**(不要使用个人邮箱)
- **在该邮箱账户上启用 IMAP**
- **如果使用 Gmail 或其他开启了双重验证的服务商,需要准备应用专用密码**
### Gmail 设置
1. 在 Google 账户上启用双重验证(2FA)
2. 前往 [应用专用密码](https://myaccount.google.com/apppasswords)
3. 创建一个新的应用专用密码(选择"邮件"或"其他"
4. 复制这个 16 位密码——使用它代替常规密码
### Outlook / Microsoft 365
1. 前往 [安全设置](https://account.microsoft.com/security)
2. 如尚未启用,请开启双重验证
3. 在"其他安全选项"下创建应用专用密码
4. IMAP 主机:`outlook.office365.com`SMTP 主机:`smtp.office365.com`
### 其他服务商
大多数邮件服务商支持 IMAP/SMTP。请查阅服务商文档,了解:
- IMAP 主机和端口(通常为端口 993,使用 SSL)
- SMTP 主机和端口(通常为端口 587,使用 STARTTLS
- 是否需要应用专用密码
---
## 第一步:配置 Hermes
最简便的方式:
```bash
hermes gateway setup
```
从平台菜单中选择 **Email**。向导会提示输入邮箱地址、密码、IMAP/SMTP 主机以及允许的发件人。
### 手动配置
`~/.hermes/.env` 中添加:
```bash
# 必填
EMAIL_ADDRESS=hermes@gmail.com
EMAIL_PASSWORD=abcd efgh ijkl mnop # 应用专用密码(非常规密码)
EMAIL_IMAP_HOST=imap.gmail.com
EMAIL_SMTP_HOST=smtp.gmail.com
# 安全设置(推荐)
EMAIL_ALLOWED_USERS=your@email.com,colleague@work.com
# 可选
EMAIL_IMAP_PORT=993 # 默认:993IMAP SSL
EMAIL_SMTP_PORT=587 # 默认:587SMTP STARTTLS
EMAIL_POLL_INTERVAL=15 # 收件箱检查间隔(秒),默认:15
EMAIL_HOME_ADDRESS=your@email.com # cron 任务的默认投递目标
```
---
## 第二步:启动 Gateway
```bash
hermes gateway # 在前台运行
hermes gateway install # 安装为用户服务
sudo hermes gateway install --system # 仅 Linux:开机自启的系统服务
```
启动时,适配器会:
1. 测试 IMAP 和 SMTP 连接
2. 将收件箱中所有现有邮件标记为"已读"(仅处理新邮件)
3. 开始轮询新邮件
---
## 工作原理
### 接收邮件
适配器按可配置的间隔(默认:15 秒)轮询 IMAP 收件箱中的未读邮件。对于每封新邮件:
- **主题行**作为上下文包含在内(例如 `[Subject: Deploy to production]`
- **回复邮件**(主题以 `Re:` 开头)跳过主题前缀——线程上下文已经建立
- **附件**会缓存到本地:
- 图片(JPEG、PNG、GIF、WebP)→ 可供视觉工具使用
- 文档(PDF、ZIP 等)→ 可供文件访问工具使用
- **纯 HTML 邮件**会剥离标签以提取纯文本
- **自发邮件**会被过滤,防止回复循环
- **自动化/无回复发件人**会被静默忽略——`noreply@``mailer-daemon@``bounce@``no-reply@`,以及包含 `Auto-Submitted``Precedence: bulk``List-Unsubscribe` 头部的邮件
### 发送回复
回复通过 SMTP 发送,并正确维护邮件线程:
- **In-Reply-To** 和 **References** 头部用于维持线程
- **主题行**保留并添加 `Re:` 前缀(不会出现 `Re: Re:` 重复)
- **Message-ID** 使用 Agent 的域名生成
- 回复以纯文本(UTF-8)发送
### 文件附件
Agent 可以在回复中发送文件附件。在响应中包含 `MEDIA:/path/to/file`,该文件将作为附件添加到发出的邮件中。
### 跳过附件
如需忽略所有传入附件(用于防范恶意软件或节省带宽),在 `config.yaml` 中添加:
```yaml
platforms:
email:
skip_attachments: true
```
启用后,附件和内嵌部分会在解码前被跳过,邮件正文文本仍正常处理。
---
## 访问控制
电子邮件访问遵循与所有其他 Hermes 平台相同的模式:
1. **设置了 `EMAIL_ALLOWED_USERS`** → 仅处理来自这些地址的邮件
2. **未设置白名单** → 未知发件人会收到配对码
3. **`EMAIL_ALLOW_ALL_USERS=true`** → 接受任意发件人(请谨慎使用)
:::warning
**请务必配置 `EMAIL_ALLOWED_USERS`。** 若不配置,任何知道 Agent 邮箱地址的人都可以发送命令。Agent 默认具有终端访问权限。
:::
---
## 故障排查
| 问题 | 解决方案 |
|---------|----------|
| 启动时出现 **"IMAP connection failed"** | 检查 `EMAIL_IMAP_HOST``EMAIL_IMAP_PORT`。确保账户已启用 IMAP。对于 Gmail,在设置 → 转发和 POP/IMAP 中启用。 |
| 启动时出现 **"SMTP connection failed"** | 检查 `EMAIL_SMTP_HOST``EMAIL_SMTP_PORT`。确认密码正确(Gmail 请使用应用专用密码)。 |
| **未收到邮件** | 检查 `EMAIL_ALLOWED_USERS` 是否包含发件人邮箱。检查垃圾邮件文件夹——部分服务商会将自动回复标记为垃圾邮件。 |
| **"Authentication failed"** | 对于 Gmail,必须使用应用专用密码,而非常规密码。请先确保已启用双重验证。 |
| **重复回复** | 确保只有一个 gateway 实例在运行。检查 `hermes gateway status`。 |
| **响应缓慢** | 默认轮询间隔为 15 秒。设置 `EMAIL_POLL_INTERVAL=5` 可加快响应速度(但会增加 IMAP 连接次数)。 |
| **回复未归入线程** | 适配器使用 In-Reply-To 头部。部分邮件客户端(尤其是网页版)可能无法正确将自动回复归入线程。 |
---
## 安全
:::warning
**请使用专用邮箱账户。** 不要使用个人邮箱——Agent 会将密码存储在 `.env` 文件中,并通过 IMAP 拥有完整的收件箱访问权限。
:::
- 使用**应用专用密码**代替主密码(Gmail 开启双重验证后必须如此)
- 设置 `EMAIL_ALLOWED_USERS` 以限制可与 Agent 交互的用户
- 密码存储在 `~/.hermes/.env` 中——请保护此文件(`chmod 600`
- IMAP 默认使用 SSL(端口 993),SMTP 默认使用 STARTTLS(端口 587)——连接已加密
---
## 环境变量参考
| 变量 | 是否必填 | 默认值 | 说明 |
|----------|----------|---------|-------------|
| `EMAIL_ADDRESS` | 是 | — | Agent 的邮箱地址 |
| `EMAIL_PASSWORD` | 是 | — | 邮箱密码或应用专用密码 |
| `EMAIL_IMAP_HOST` | 是 | — | IMAP 服务器主机(例如 `imap.gmail.com` |
| `EMAIL_SMTP_HOST` | 是 | — | SMTP 服务器主机(例如 `smtp.gmail.com` |
| `EMAIL_IMAP_PORT` | 否 | `993` | IMAP 服务器端口 |
| `EMAIL_SMTP_PORT` | 否 | `587` | SMTP 服务器端口 |
| `EMAIL_POLL_INTERVAL` | 否 | `15` | 收件箱检查间隔(秒) |
| `EMAIL_ALLOWED_USERS` | 否 | — | 允许的发件人地址,逗号分隔 |
| `EMAIL_HOME_ADDRESS` | 否 | — | cron 任务的默认投递目标 |
| `EMAIL_ALLOW_ALL_USERS` | 否 | `false` | 允许所有发件人(不推荐) |
@@ -0,0 +1,533 @@
---
sidebar_position: 11
title: "飞书 / Lark"
description: "将 Hermes Agent 配置为飞书或 Lark 机器人"
---
# 飞书 / Lark 配置
Hermes Agent 可作为全功能机器人与飞书和 Lark 集成。连接后,你可以在私信或群聊中与 Agent 对话,在 home chat 中接收 cron job 结果,并通过标准 gateway 流程发送文本、图片、音频和文件附件。
该集成支持两种连接模式:
- `websocket` — 推荐;Hermes 主动建立出站连接,无需公开 webhook 端点
- `webhook` — 适用于已将 Hermes 部署在可访问 HTTP 端点后的场景
## Hermes 的行为方式
| 场景 | 行为 |
|---------|----------|
| 私信 | Hermes 回复每一条消息。 |
| 群聊 | Hermes 仅在被 @提及 时回复。 |
| 共享群聊 | 默认情况下,每位用户在共享群聊中的会话历史相互隔离。 |
共享群聊行为由 `config.yaml` 控制:
```yaml
group_sessions_per_user: true
```
仅当你明确希望每个群聊共享同一个对话时,才将其设为 `false`
## 第一步:创建飞书 / Lark 应用
### 推荐:扫码创建(一条命令)
```bash
hermes gateway setup
```
选择 **飞书 / Lark**,用飞书或 Lark 手机端扫描二维码。Hermes 将自动创建具有正确权限的机器人应用并保存凭据。
### 备选:手动配置
如果扫码创建不可用,向导将回退到手动输入:
1. 打开飞书或 Lark 开发者控制台:
- 飞书:[https://open.feishu.cn/](https://open.feishu.cn/)
- Lark[https://open.larksuite.com/](https://open.larksuite.com/)
2. 创建新应用。
3.**凭证与基础信息** 中,复制 **App ID****App Secret**
4. 为应用开启 **机器人** 能力。
5. 运行 `hermes gateway setup`,选择 **飞书 / Lark**,并在提示时输入凭据。
:::warning
请妥善保管 App Secret。任何持有它的人都可以冒充你的应用。
:::
## 第二步:选择连接模式
### 推荐:WebSocket 模式
当 Hermes 运行在你的笔记本、工作站或私有服务器上时,使用 WebSocket 模式。无需公开 URL。官方 Lark SDK 会建立并维护一个持久的出站 WebSocket 连接,并支持自动重连。
```bash
FEISHU_CONNECTION_MODE=websocket
```
**依赖:** 必须安装 `websockets` Python 包。SDK 在内部处理连接生命周期、心跳和自动重连。
**工作原理:** 适配器在后台 executor 线程中运行 Lark SDK 的 WebSocket 客户端。入站事件(消息、表情回应、卡片操作)被分发到主 asyncio 循环。断开连接时,SDK 将自动尝试重连。
### 可选:Webhook 模式
仅当 Hermes 已部署在可访问的 HTTP 端点后时,才使用 webhook 模式。
```bash
FEISHU_CONNECTION_MODE=webhook
```
在 webhook 模式下,Hermes 启动一个 HTTP 服务器(通过 `aiohttp`),并在以下路径提供飞书端点:
```text
/feishu/webhook
```
**依赖:** 必须安装 `aiohttp` Python 包。
你可以自定义 webhook 服务器的绑定地址和路径:
```bash
FEISHU_WEBHOOK_HOST=127.0.0.1 # 默认:127.0.0.1
FEISHU_WEBHOOK_PORT=8765 # 默认:8765
FEISHU_WEBHOOK_PATH=/feishu/webhook # 默认:/feishu/webhook
```
当飞书发送 URL 验证挑战(`type: url_verification`)时,webhook 会自动响应,以便你在飞书开发者控制台完成订阅配置。当设置了 `FEISHU_VERIFICATION_TOKEN` 时,挑战响应会进行 token 校验——token 缺失或不匹配的挑战请求将被拒绝,防止未经认证的远端通过回显攻击者控制的挑战数据来证明端点控制权。
## 第三步:配置 Hermes
### 方式 A:交互式配置
```bash
hermes gateway setup
```
选择 **飞书 / Lark** 并填写提示信息。
### 方式 B:手动配置
`~/.hermes/.env` 中添加以下内容:
```bash
FEISHU_APP_ID=cli_xxx
FEISHU_APP_SECRET=secret_xxx
FEISHU_DOMAIN=feishu
FEISHU_CONNECTION_MODE=websocket
# 可选但强烈推荐
FEISHU_ALLOWED_USERS=ou_xxx,ou_yyy
FEISHU_HOME_CHANNEL=oc_xxx
```
`FEISHU_DOMAIN` 接受:
- `feishu` 对应飞书(中国)
- `lark` 对应 Lark(国际版)
## 第四步:启动 Gateway
```bash
hermes gateway
```
然后从飞书/Lark 向机器人发送消息,确认连接已建立。
## Home Chat
在飞书/Lark 聊天中使用 `/set-home` 将其标记为 cron job 结果和跨平台通知的 home channel。
也可以预先配置:
```bash
FEISHU_HOME_CHANNEL=oc_xxx
```
## 安全
### 用户白名单
在生产环境中,请设置飞书 Open ID 白名单:
```bash
FEISHU_ALLOWED_USERS=ou_xxx,ou_yyy
```
如果白名单为空,任何能访问机器人的人都可能使用它。在群聊中,消息处理前会根据发送者的 open_id 检查白名单。
### Webhook 加密密钥
在 webhook 模式下运行时,设置加密密钥以启用入站 webhook payload 的签名验证:
```bash
FEISHU_ENCRYPT_KEY=your-encrypt-key
```
该密钥可在飞书应用配置的 **事件订阅** 部分找到。设置后,适配器使用以下签名算法验证每个 webhook 请求:
```
SHA256(timestamp + nonce + encrypt_key + body)
```
计算出的哈希值与 `x-lark-signature` 请求头进行时序安全比较。签名无效或缺失的请求将被拒绝,返回 HTTP 401。
:::tip
在 WebSocket 模式下,签名验证由 SDK 自身处理,因此 `FEISHU_ENCRYPT_KEY` 是可选的。在 webhook 模式下,生产环境强烈推荐设置。
:::
### 验证 Token
对 webhook payload 中 `token` 字段进行检查的额外认证层:
```bash
FEISHU_VERIFICATION_TOKEN=your-verification-token
```
该 token 同样可在飞书应用的 **事件订阅** 部分找到。设置后,每个入站 webhook payload 的 `header` 对象中必须包含匹配的 `token`。token 不匹配的请求将被拒绝,返回 HTTP 401。
`FEISHU_ENCRYPT_KEY``FEISHU_VERIFICATION_TOKEN` 可同时使用,实现纵深防御。
## 群消息策略
`FEISHU_GROUP_POLICY` 环境变量控制 Hermes 是否以及如何在群聊中响应:
```bash
FEISHU_GROUP_POLICY=allowlist # 默认
```
| 值 | 行为 |
|-------|----------|
| `open` | Hermes 响应任意群中任意用户的 @提及。 |
| `allowlist` | Hermes 仅响应 `FEISHU_ALLOWED_USERS` 中列出的用户的 @提及。 |
| `disabled` | Hermes 完全忽略所有群消息。 |
在所有模式下,消息处理前机器人必须被明确 @提及(或 @all)。私信始终绕过此限制。
设置 `FEISHU_REQUIRE_MENTION=false` 可让 Hermes 读取所有群消息而无需 @提及
```bash
FEISHU_REQUIRE_MENTION=false
```
如需按群控制,在 `group_rules` 条目中设置 `require_mention`——参见下方[按群访问控制](#per-group-access-control)。
### 机器人身份
Hermes 在启动时自动检测机器人的 `open_id` 和显示名称。仅当自动检测无法访问飞书 API,或你的应用使用租户范围用户 ID 时,才需要手动设置:
```bash
FEISHU_BOT_OPEN_ID=ou_xxx # 仅在自动检测失败时使用
FEISHU_BOT_USER_ID=xxx # 若应用使用 sender_id_type=user_id 则必填
FEISHU_BOT_NAME=MyBot # 仅在自动检测失败时使用
```
## 机器人间消息传递
默认情况下,Hermes 忽略其他机器人发送的消息。当你希望 Hermes 参与 A2A 编排或接收同一群中其他机器人的通知时,可启用机器人间消息传递。
```bash
FEISHU_ALLOW_BOTS=mentions # 默认:none
```
| 值 | 行为 |
|-------|----------|
| `none` | 忽略所有其他机器人的消息(默认)。 |
| `mentions` | 仅当对端机器人 @提及 Hermes 时接受。 |
| `all` | 接受所有对端机器人消息。 |
也可在 `config.yaml` 中配置为 `feishu.allow_bots`(两者同时设置时,环境变量优先)。
对端机器人无需加入 `FEISHU_ALLOWED_USERS`——该白名单仅适用于人类发送者。
授予 `application:bot.basic_info:read` 权限范围可显示对端机器人名称;未授权时,对端机器人仍可正常路由,但显示为其 `open_id`
## 交互式卡片操作
当用户点击机器人发送的交互式卡片上的按钮或与其交互时,适配器将这些操作路由为合成的 `/card` 命令事件:
- 按钮点击变为:`/card button {"key": "value", ...}`
- 卡片定义中操作的 `value` payload 以 JSON 形式包含在内。
- 卡片操作在 15 分钟窗口内去重,防止重复处理。
Gateway 驱动的更新提示使用原生飞书 `Yes` / `No` 卡片,而非回退到纯文本回复。当 `hermes update --gateway` 需要确认时,适配器将所选答案记录到 Hermes 的 `.update_response` 文件中,并将卡片内联替换为已解决状态。
卡片操作事件以 `MessageType.COMMAND` 分发,因此流经标准命令处理管道。
**命令审批**也通过此机制实现——当 Agent 需要执行危险命令时,会发送一张带有「允许一次 / 本次会话 / 始终允许 / 拒绝」按钮的交互式卡片。用户点击按钮后,卡片操作回调将审批决定传回 Agent。
### 飞书应用所需配置
交互式卡片需要在飞书开发者控制台完成**三项**配置。缺少任何一项,用户点击卡片按钮时将出现错误 **200340**
1. **订阅卡片操作事件:**
**事件订阅** 中,将 `card.action.trigger` 添加到已订阅事件。
2. **启用交互式卡片能力:**
**应用功能 > 机器人** 中,确保 **交互式卡片** 开关已启用。这告知飞书你的应用可以接收卡片操作回调。
3. **配置卡片请求 URL(仅 webhook 模式):**
**应用功能 > 机器人 > 消息卡片请求网址** 中,将 URL 设置为与事件 webhook 相同的端点(例如 `https://your-server:8765/feishu/webhook`)。WebSocket 模式下,SDK 会自动处理此项。
:::warning
缺少以上任意一步,飞书将成功*发送*交互式卡片(发送仅需 `im:message:send` 权限),但点击任意按钮将返回错误 200340。卡片看起来正常——错误仅在用户与其交互时才会出现。
:::
## 文档评论智能回复
除聊天外,适配器还可以回复**飞书/Lark 文档**中的 `@` 提及。当用户在文档中评论(局部文本选区或全文评论)并 @提及机器人时,Hermes 读取文档内容及周围的评论线程,并在线程中内联发布 LLM 回复。
`drive.notice.comment_add_v1` 事件驱动,处理器:
- 并行获取文档内容和评论时间线(全文线程取 20 条消息,局部选区线程取 12 条)。
-`feishu_doc` + `feishu_drive` 工具集运行 Agent,范围限定于该单次评论会话。
- 每 4000 字符分块,以线程回复形式发布。
- 按文档缓存会话,有效期 1 小时,上限 50 条消息,使同一文档的后续评论保持上下文。
### 三级访问控制
文档评论回复为**显式授权模式**——不存在隐式全员允许模式。权限按以下顺序解析(每个字段取第一个匹配项):
1. **精确文档** — 限定于特定文档 token 的规则。
2. **通配符** — 匹配文档模式的规则。
3. **顶层** — 工作区的默认规则。
每条规则支持两种策略:
- **`allowlist`** — 静态用户/租户列表。
- **`pairing`** — 静态列表 ∪ 运行时审批存储。适用于管理员可实时授权的灰度发布场景。
规则存储在 `~/.hermes/feishu_comment_rules.json`pairing 授权存储在 `~/.hermes/feishu_comment_pairing.json`),支持基于 mtime 缓存的热重载——编辑后无需重启 gateway,下一个评论事件即生效。
CLI
```bash
# 查看当前规则和 pairing 状态
python -m gateway.platforms.feishu_comment_rules status
# 模拟特定文档 + 用户的访问检查
python -m gateway.platforms.feishu_comment_rules check <fileType:fileToken> <user_open_id>
# 运行时管理 pairing 授权
python -m gateway.platforms.feishu_comment_rules pairing list
python -m gateway.platforms.feishu_comment_rules pairing add <user_open_id>
python -m gateway.platforms.feishu_comment_rules pairing remove <user_open_id>
```
### 飞书应用所需配置
在已授予的聊天/卡片权限基础上,添加文档评论事件:
-**事件订阅** 中订阅 `drive.notice.comment_add_v1`
- 授予 `docs:doc:readonly``drive:drive:readonly` 权限范围,以便处理器读取文档内容。
## 媒体支持
### 入站(接收)
适配器接收并缓存以下来自用户的媒体类型:
| 类型 | 扩展名 | 处理方式 |
|------|-----------|-------------------|
| **图片** | .jpg, .jpeg, .png, .gif, .webp, .bmp | 通过飞书 API 下载并本地缓存 |
| **音频** | .ogg, .mp3, .wav, .m4a, .aac, .flac, .opus, .webm | 下载并缓存;小型文本文件自动提取内容 |
| **视频** | .mp4, .mov, .avi, .mkv, .webm, .m4v, .3gp | 下载并作为文档缓存 |
| **文件** | .pdf, .doc, .docx, .xls, .xlsx, .ppt, .pptx 等 | 下载并作为文档缓存 |
富文本(post)消息中的媒体,包括内联图片和文件附件,也会被提取并缓存。
对于小型文本文档(.txt, .md),文件内容会自动注入消息文本,使 Agent 无需工具即可直接读取。
### 出站(发送)
| 方法 | 发送内容 |
|--------|--------------|
| `send` | 文本或富文本 post 消息(根据 markdown 内容自动检测) |
| `send_image` / `send_image_file` | 上传图片到飞书,然后以原生图片气泡发送(可附带说明文字) |
| `send_document` | 上传文件到飞书 API,然后以文件附件发送 |
| `send_voice` | 以飞书文件附件形式上传音频文件 |
| `send_video` | 上传视频并以原生媒体消息发送 |
| `send_animation` | GIF 降级为文件附件(飞书不支持原生 GIF 气泡) |
文件上传路由根据扩展名自动判断:
- `.ogg`, `.opus` → 以 `opus` 音频上传
- `.mp4`, `.mov`, `.avi`, `.m4v` → 以 `mp4` 媒体上传
- `.pdf`, `.doc(x)`, `.xls(x)`, `.ppt(x)` → 以对应文档类型上传
- 其他所有格式 → 以通用流文件上传
## Markdown 渲染与 Post 回退
当出站文本包含 markdown 格式(标题、加粗、列表、代码块、链接等)时,适配器自动将其以飞书 **post** 消息形式发送,并嵌入 `md` 标签,而非纯文本。这使飞书客户端能够富文本渲染。
如果飞书 API 拒绝 post payload(例如因不支持的 markdown 语法),适配器自动回退为发送去除 markdown 的纯文本。这种两阶段回退确保消息始终能送达。
纯文本消息(未检测到 markdown)以简单的 `text` 消息类型发送。
## 处理状态表情回应
Agent 工作期间,机器人会在你的消息上显示 `Typing` 表情回应。回复到达后清除,处理失败则替换为 `CrossMark`
设置 `FEISHU_REACTIONS=false` 可关闭此功能。
## 突发保护与批处理
适配器对快速消息突发进行防抖处理,避免压垮 Agent:
### 文本批处理
当用户快速连续发送多条文本消息时,它们会在分发前合并为单个事件:
| 设置 | 环境变量 | 默认值 |
|---------|---------|---------|
| 静默期 | `HERMES_FEISHU_TEXT_BATCH_DELAY_SECONDS` | 0.6s |
| 每批最大消息数 | `HERMES_FEISHU_TEXT_BATCH_MAX_MESSAGES` | 8 |
| 每批最大字符数 | `HERMES_FEISHU_TEXT_BATCH_MAX_CHARS` | 4000 |
### 媒体批处理
快速连续发送的多个媒体附件(例如拖拽多张图片)会合并为单个事件:
| 设置 | 环境变量 | 默认值 |
|---------|---------|---------|
| 静默期 | `HERMES_FEISHU_MEDIA_BATCH_DELAY_SECONDS` | 0.8s |
### 按聊天串行化
同一聊天中的消息串行处理(每次一条),以保持对话连贯性。每个聊天有独立的锁,不同聊天的消息并发处理。
## 速率限制(Webhook 模式)
在 webhook 模式下,适配器对每个 IP 强制执行速率限制,防止滥用:
- **窗口:** 60 秒滑动窗口
- **限制:** 每个(app_id, path, IP)三元组每窗口 120 次请求
- **追踪上限:** 最多追踪 4096 个唯一键(防止内存无限增长)
超出限制的请求将收到 HTTP 429(请求过多)。
### Webhook 异常追踪
适配器追踪每个 IP 地址的连续错误响应。同一 IP 在 6 小时窗口内连续出现 25 次错误后,将记录警告日志。这有助于检测配置错误的客户端或探测行为。
额外的 webhook 保护措施:
- **请求体大小限制:** 最大 1 MB
- **请求体读取超时:** 30 秒
- **Content-Type 强制:** 仅接受 `application/json`
## WebSocket 调优
使用 `websocket` 模式时,可自定义重连和 ping 行为:
```yaml
platforms:
feishu:
extra:
ws_reconnect_interval: 120 # 重连尝试间隔秒数(默认:120
ws_ping_interval: 30 # WebSocket ping 间隔秒数(可选;未设置时使用 SDK 默认值)
```
| 设置 | 配置键 | 默认值 | 说明 |
|---------|-----------|---------|-------------|
| 重连间隔 | `ws_reconnect_interval` | 120s | 两次重连尝试之间的等待时间 |
| Ping 间隔 | `ws_ping_interval` | _SDK 默认)_ | WebSocket 保活 ping 的频率 |
## 按群访问控制
除全局 `FEISHU_GROUP_POLICY` 外,还可在 config.yaml 的 `group_rules` 中为每个群聊设置细粒度规则:
```yaml
platforms:
feishu:
extra:
default_group_policy: "open" # 未在 group_rules 中列出的群的默认策略
admins: # 可管理机器人设置的用户
- "ou_admin_open_id"
group_rules:
"oc_group_chat_id_1":
policy: "allowlist" # open | allowlist | blacklist | admin_only | disabled
allowlist:
- "ou_user_open_id_1"
- "ou_user_open_id_2"
"oc_group_chat_id_2":
policy: "admin_only"
"oc_group_chat_id_3":
policy: "blacklist"
blacklist:
- "ou_blocked_user"
"oc_free_chat":
policy: "open"
require_mention: false # 覆盖此聊天的 FEISHU_REQUIRE_MENTION
```
| 策略 | 说明 |
|--------|-------------|
| `open` | 群内任何人均可使用机器人 |
| `allowlist` | 仅群 `allowlist` 中的用户可使用机器人 |
| `blacklist` | 除群 `blacklist` 中的用户外,所有人均可使用机器人 |
| `admin_only` | 仅全局 `admins` 列表中的用户可在此群使用机器人 |
| `disabled` | 机器人忽略此群的所有消息 |
`group_rules` 条目中设置 `require_mention: false` 可跳过该特定聊天的 @提及要求。省略时,该聊天继承全局 `FEISHU_REQUIRE_MENTION` 值。
未在 `group_rules` 中列出的群回退到 `default_group_policy`(默认为 `FEISHU_GROUP_POLICY` 的值)。
## 去重
入站消息使用消息 ID 去重,TTL 为 24 小时。去重状态持久化到 `~/.hermes/feishu_seen_message_ids.json`,重启后仍有效。
| 设置 | 环境变量 | 默认值 |
|---------|---------|---------|
| 缓存大小 | `HERMES_FEISHU_DEDUP_CACHE_SIZE` | 2048 条 |
## 所有环境变量
| 变量 | 必填 | 默认值 | 说明 |
|----------|----------|---------|-------------|
| `FEISHU_APP_ID` | ✅ | — | 飞书/Lark App ID |
| `FEISHU_APP_SECRET` | ✅ | — | 飞书/Lark App Secret |
| `FEISHU_DOMAIN` | — | `feishu` | `feishu`(中国)或 `lark`(国际版) |
| `FEISHU_CONNECTION_MODE` | — | `websocket` | `websocket``webhook` |
| `FEISHU_ALLOWED_USERS` | — | _(空)_ | 用户白名单的逗号分隔 open_id 列表 |
| `FEISHU_ALLOW_BOTS` | — | `none` | 接受其他机器人消息:`none``mentions``all` |
| `FEISHU_REQUIRE_MENTION` | — | `true` | 群消息是否必须 @提及 机器人 |
| `FEISHU_HOME_CHANNEL` | — | — | cron/通知输出的聊天 ID |
| `FEISHU_ENCRYPT_KEY` | — | _(空)_ | webhook 签名验证的加密密钥 |
| `FEISHU_VERIFICATION_TOKEN` | — | _(空)_ | webhook payload 认证的验证 token |
| `FEISHU_GROUP_POLICY` | — | `allowlist` | 群消息策略:`open``allowlist``disabled` |
| `FEISHU_BOT_OPEN_ID` | — | _(空)_ | 机器人的 open_id(用于 @提及 检测) |
| `FEISHU_BOT_USER_ID` | — | _(空)_ | 机器人的 user_id(用于 @提及 检测) |
| `FEISHU_BOT_NAME` | — | _(空)_ | 机器人的显示名称(用于 @提及 检测) |
| `FEISHU_WEBHOOK_HOST` | — | `127.0.0.1` | Webhook 服务器绑定地址 |
| `FEISHU_WEBHOOK_PORT` | — | `8765` | Webhook 服务器端口 |
| `FEISHU_WEBHOOK_PATH` | — | `/feishu/webhook` | Webhook 端点路径 |
| `HERMES_FEISHU_DEDUP_CACHE_SIZE` | — | `2048` | 最大去重消息 ID 追踪数量 |
| `HERMES_FEISHU_TEXT_BATCH_DELAY_SECONDS` | — | `0.6` | 文本突发防抖静默期 |
| `HERMES_FEISHU_TEXT_BATCH_MAX_MESSAGES` | — | `8` | 每批文本合并的最大消息数 |
| `HERMES_FEISHU_TEXT_BATCH_MAX_CHARS` | — | `4000` | 每批文本合并的最大字符数 |
| `HERMES_FEISHU_MEDIA_BATCH_DELAY_SECONDS` | — | `0.8` | 媒体突发防抖静默期 |
WebSocket 和按群 ACL 设置通过 `config.yaml``platforms.feishu.extra` 配置(参见上方 [WebSocket 调优](#websocket-tuning) 和[按群访问控制](#per-group-access-control))。
## 故障排查
| 问题 | 解决方法 |
|---------|-----|
| `lark-oapi not installed` | 安装 SDK`pip install lark-oapi` |
| `websockets not installed; websocket mode unavailable` | 安装 websockets`pip install websockets` |
| `aiohttp not installed; webhook mode unavailable` | 安装 aiohttp`pip install aiohttp` |
| `FEISHU_APP_ID or FEISHU_APP_SECRET not set` | 设置两个环境变量,或通过 `hermes gateway setup` 配置 |
| `Another local Hermes gateway is already using this Feishu app_id` | 同一时间只能有一个 Hermes 实例使用相同的 app_id。请先停止另一个 gateway。 |
| 机器人在群聊中不响应 | 确保机器人被 @提及,检查 `FEISHU_GROUP_POLICY`,若策略为 `allowlist` 则验证发送者是否在 `FEISHU_ALLOWED_USERS` 中 |
| `Webhook rejected: invalid verification token` | 确保 `FEISHU_VERIFICATION_TOKEN` 与飞书应用事件订阅配置中的 token 一致 |
| `Webhook rejected: invalid signature` | 确保 `FEISHU_ENCRYPT_KEY` 与飞书应用配置中的加密密钥一致 |
| Post 消息显示为纯文本 | 飞书 API 拒绝了 post payload;这是正常的回退行为。查看日志了解详情。 |
| 机器人未收到图片/文件 | 为飞书应用授予 `im:message``im:resource` 权限范围 |
| 机器人身份未自动检测 | 通常是访问飞书机器人信息端点时的瞬时网络问题。可手动设置 `FEISHU_BOT_OPEN_ID``FEISHU_BOT_NAME` 作为临时解决方案。 |
| 启用 `FEISHU_ALLOW_BOTS` 后对端机器人消息仍被忽略 | Hermes 尚无法识别自身——请设置 `FEISHU_BOT_OPEN_ID`(若应用使用 `sender_id_type=user_id` 则同时设置 `FEISHU_BOT_USER_ID`)。 |
| 对端机器人显示为 `ou_xxxxxx` 而非名称 | 授予 `application:bot.basic_info:read` 权限范围。 |
| 点击审批按钮时出现错误 200340 | 在飞书开发者控制台启用**交互式卡片**能力并配置**卡片请求 URL**。参见上方[飞书应用所需配置](#required-feishu-app-configuration)。 |
| `Webhook rate limit exceeded` | 同一 IP 每分钟请求超过 120 次。通常是配置错误或循环导致。 |
## 工具集
飞书 / Lark 使用 `hermes-feishu` 平台预设,包含与 Telegram 及其他基于 gateway 的消息平台相同的核心工具。
@@ -0,0 +1,281 @@
---
sidebar_position: 12
title: "Google Chat"
description: "使用 Cloud Pub/Sub 将 Hermes Agent 设置为 Google Chat 机器人"
---
# Google Chat 设置
将 Hermes Agent 作为机器人接入 Google Chat。该集成使用 Cloud Pub/Sub 拉取订阅接收入站事件,使用 Chat REST API 发送出站消息。与 Slack Socket Mode 或 Telegram 长轮询的使用体验相当:Hermes 进程无需公网 URL、隧道或 TLS 证书。它直接连接、认证并监听订阅——就像 Telegram 机器人通过 token 监听一样。
:::note Workspace 版本
Google Chat 是 Google Workspace 的一部分。你可以在个人 Workspace(通过 Google 注册的 `@yourdomain.com`)或拥有管理员权限可发布应用的企业 Workspace 中使用此集成。仅有 Gmail 账号的用户无法托管 Chat 应用。
:::
## 概览
| 组件 | 值 |
|-----------|-------|
| **依赖库** | `google-cloud-pubsub``google-api-python-client``google-auth` |
| **入站传输** | Cloud Pub/Sub 拉取订阅(无需公网端点) |
| **出站传输** | Chat REST API`chat.googleapis.com` |
| **认证** | 在订阅上具有 `roles/pubsub.subscriber` 的 Service Account JSON |
| **用户标识** | Chat 资源名称(`users/{id}`+ 邮箱 |
---
## 第一步:创建或选择 GCP 项目
你需要一个 Google Cloud 项目来托管 Pub/Sub topic(主题)。如果还没有,请在 [console.cloud.google.com](https://console.cloud.google.com) 创建——个人账号有免费额度,足以覆盖机器人流量。
记下项目 ID(例如 `my-chat-bot-123`),后续每一步都会用到。
---
## 第二步:启用两个 API
在控制台中,进入 **APIs & Services → Library**,启用:
- **Google Chat API**
- **Cloud Pub/Sub API**
个人机器人产生的流量完全在免费额度内。
---
## 第三步:创建 Service Account
**IAM & Admin → Service Accounts → Create Service Account。**
- 名称:`hermes-chat-bot`
- 跳过"Grant this service account access to project"步骤。你只需要在特定订阅上配置 IAM,**不要**授予项目级别的 Pub/Sub 角色。
创建完成后,打开该 SA,进入 **Keys → Add Key → Create new key → JSON**,下载文件。将其保存到只有 Hermes 可读的位置(例如 `~/.hermes/google-chat-sa.json``chmod 600`)。
:::caution 不存在"Chat Bot Caller"角色
一个常见错误是搜索 Chat 专属 IAM 角色并在项目级别授予。该角色并不存在。Chat 机器人的权限来自被安装到某个 space(空间),而非 IAM。你的 SA 只需要在下一步创建的订阅上具有 Pub/Sub subscriber 权限。
:::
---
## 第四步:创建 Pub/Sub topic 和订阅
**Pub/Sub → Topics → Create topic。**
- Topic ID`hermes-chat-events`
- 其余选项保持默认。
创建完成后,topic 详情页有 **Subscriptions** 标签页。在此创建一个订阅:
- Subscription ID`hermes-chat-events-sub`
- 投递类型:**Pull**
- 消息保留:**7 天**(这样 Hermes 重启后积压消息不会丢失)
- 其余保持默认。
---
## 第五步:在 topic 上配置 IAM 绑定(关键)
**topic**(不是订阅)上添加一个 IAM 主体:
- 主体:`chat-api-push@system.gserviceaccount.com`
- 角色:`Pub/Sub Publisher`
若不配置此项,Google Chat 将无法向你的 topic 发布事件,机器人将永远收不到任何消息。
---
## 第六步:在订阅上配置 IAM 绑定
**订阅** 上,将你自己的 Service Account 添加为主体:
- 主体:`hermes-chat-bot@<your-project>.iam.gserviceaccount.com`
- 角色:`Pub/Sub Subscriber`
同时在同一订阅上授予 `Pub/Sub Viewer`——Hermes 在启动时会调用 `subscription.get()` 进行可达性检查。
---
## 第七步:配置 Chat 应用
进入 **APIs & Services → Google Chat API → Configuration**
- **App name**:用户看到的名称("Hermes"即可)。
- **Avatar URL**:任意公开 PNG 图片(Google 提供了一些默认选项)。
- **Description**:显示在应用目录中的简短说明。
- **Functionality**:启用 **Receive 1:1 messages****Join spaces and group conversations**
- **Connection settings**:选择 **Cloud Pub/Sub**,输入 topic 名称 `projects/<your-project>/topics/hermes-chat-events`
- **Visibility**:限制为你的 Workspace(或特定用户)——测试期间不要向所有人开放。
保存。
---
## 第八步:在测试 space 中安装机器人
在浏览器中打开 Google Chat。在 **+ New Chat** 菜单中搜索应用名称,向其发起私信。第一次发消息时,Google 会发送一个 `ADDED_TO_SPACE` 事件,Hermes 用它来缓存机器人自身的 `users/{id}`,以便过滤自发消息。
---
## 第九步:配置 Hermes
`~/.hermes/.env` 中添加 Google Chat 配置段:
```bash
# 必填
GOOGLE_CHAT_PROJECT_ID=my-chat-bot-123
GOOGLE_CHAT_SUBSCRIPTION_NAME=projects/my-chat-bot-123/subscriptions/hermes-chat-events-sub
GOOGLE_CHAT_SERVICE_ACCOUNT_JSON=/home/you/.hermes/google-chat-sa.json
# 授权 — 粘贴允许与机器人对话的用户邮箱
GOOGLE_CHAT_ALLOWED_USERS=you@yourdomain.com,coworker@yourdomain.com
# 可选
GOOGLE_CHAT_HOME_CHANNEL=spaces/AAAA... # cron 任务的默认投递目标
GOOGLE_CHAT_MAX_MESSAGES=1 # Pub/Sub FlowControl1 表示每个会话串行执行命令
GOOGLE_CHAT_MAX_BYTES=16777216 # 16 MiB — 在途消息字节上限
```
项目 ID 也可回退到 `GOOGLE_CLOUD_PROJECT`SA 路径可回退到 `GOOGLE_APPLICATION_CREDENTIALS`——使用你偏好的约定即可。
安装 Google Chat 适配器所需的依赖(目前没有发布 Hermes extra,请直接安装):
```bash
pip install google-cloud-pubsub google-api-python-client google-auth google-auth-oauthlib
```
启动 gateway(网关):
```bash
hermes gateway
```
你应该会看到如下日志:
```
[GoogleChat] Connected; project=my-chat-bot-123, subscription=<redacted>,
bot_user_id=users/XXXX, flow_control(msgs=1, bytes=16777216)
```
在测试私信中发送"hola"。机器人会先发送一条"Hermes is thinking…"占位消息,然后原地编辑该消息为真实回复——不会留下"消息已删除"的墓碑。
---
## 格式化与功能
Google Chat 支持有限的 Markdown 子集:
| 支持 | 不支持 |
|-----------|---------------|
| `*粗体*``_斜体_``~删除线~`、`` `代码` `` | 标题、列表 |
| 通过 URL 内联图片 | 交互式 Card v2 按钮(此 gateway 为 v1 |
| 原生文件附件(执行 `/setup-files` 后——见第十步) | 原生语音消息 / 圆形视频消息 |
Agent 的系统 prompt(提示词)包含 Google Chat 专属提示,使其了解这些限制,避免使用无法渲染的格式。
消息大小限制:每条消息 4000 个字符。较长的 agent 回复会自动拆分为多条消息。
Thread(线程)支持:当用户在 thread 中回复时,Hermes 会检测 `thread.name` 并在同一 thread 中发送回复,每个 thread 对应独立的 Hermes 会话。
---
## 第十步:原生附件投递(可选)
默认情况下,机器人可以发送文本、通过 URL 内联图片,以及音频/视频/文档的下载卡片。若要投递**原生** Chat 附件——即人工拖放文件时出现的文件 widget——每位用户需通过一次性 OAuth 流程授权机器人。
### 为何需要单独的流程
Google Chat 的 `media.upload` 端点会硬拒绝 service account 认证:
> This method doesn't support app authentication with a service account.
> Authenticate with a user account.
没有任何 IAM 角色或 scope 能解决这个问题。该端点只接受用户凭据。因此,机器人在上传文件时必须*以用户身份*操作——具体来说,是以请求文件的用户身份。
### 一次性宿主机设置
1. 在同一 GCP 项目中,进入 **APIs & Services → Credentials**。
2. **Create credentials → OAuth client ID → Desktop app**。
3. 下载 JSON 文件,移动到运行 Hermes 的宿主机上。
4. 在宿主机上,向 Hermes 注册该客户端:
```bash
python -m gateway.platforms.google_chat_user_oauth \
--client-secret /path/to/client_secret.json
```
该命令会写入 `~/.hermes/google_chat_user_client_secret.json`。这是共享基础设施——它标识 OAuth *应用*,而非某个具体用户。无论后续有多少用户授权,每台宿主机只需一个文件。
### 每用户授权(在 Chat 中操作)
每位用户在与机器人的私信中执行一次流程:
1. 向机器人发送 `/setup-files`,机器人回复当前状态和下一步操作。
2. 发送 `/setup-files start`,机器人回复一个 OAuth URL。
3. 打开该 URL,点击 **Allow**,浏览器会尝试加载 `http://localhost:1/?...&code=...` 并失败。这是预期行为——auth code 在地址栏的 URL 中。
4. 复制失败的 URL(或仅复制 `code=...` 的值),粘贴回 Chat 中作为 `/setup-files <PASTED_URL>`。机器人将其换取 refresh token。
token 保存在 `~/.hermes/google_chat_user_tokens/<sanitized_email>.json`。该用户私信中后续的文件请求将使用*其*token,机器人以其身份上传,消息投递到其 space。
如需撤销:`/setup-files revoke` 仅删除该用户的 token,其他用户的 token 不受影响。
### Scope
该流程仅请求一个 scope`chat.messages.create`。它同时覆盖 `media.upload` 和引用已上传 `attachmentDataRef` 的 `messages.create`。没有 Drive,没有更广泛的 Chat scope——这是有意为之的最小权限原则。
### 多用户行为
当请求者尚无每用户 token 时,机器人会回退到 `~/.hermes/google_chat_user_token.json` 中的旧版单用户 token(如果存在于多用户支持之前的安装中)。两者均不可用时,机器人会发送清晰的文字提示,告知请求者运行 `/setup-files`。
用户撤销只清除自己的槽位。某用户 token 产生的 401/403 只驱逐该用户的缓存,不影响其他用户。
---
## 故障排查
**发送"hola"后机器人没有任何响应。**
1. 在控制台检查 Pub/Sub 订阅是否有未投递消息。如果有,说明 Hermes 未通过认证——验证 `GOOGLE_CHAT_SERVICE_ACCOUNT_JSON`,并确认 SA 在订阅上具有 `Pub/Sub Subscriber` 角色。
2. 如果订阅中消息数为零,说明 Google Chat 没有发布消息。再次检查 **topic** 上的 IAM 绑定:`chat-api-push@system.gserviceaccount.com` 必须具有 `Pub/Sub Publisher` 角色。
3. 检查 `hermes gateway` 日志中是否有 `[GoogleChat] Connected`。如果看到 `[GoogleChat] Config validation failed`,错误信息会告诉你需要修复哪个环境变量。
**机器人有回复,但显示的是错误信息而非 agent 的答案。**
检查日志中是否有 `[GoogleChat] Pub/Sub stream died`——如果反复出现,可能是 SA 凭据已轮换或订阅已被删除。重试 10 次后,适配器会将自身标记为致命错误。
**每条出站消息都返回"403 Forbidden"。**
机器人已被从 space 中移除,或你在 Chat API 控制台中撤销了它。在 space 中重新安装(下一个 `ADDED_TO_SPACE` 事件会自动恢复消息发送功能)。
**出现过多"Rate limit hit"警告。**
Chat API 默认配额为每个 space 每分钟 60 条消息。如果 agent 产生的长流式回复超过该限制,适配器会以指数退避重试——但用户仍会感受到延迟。建议使用简洁回复,或在 GCP 控制台中提升配额。
**机器人持续发送"/setup-files"提示而非文件。**
请求者没有每用户 OAuth token,也没有旧版回退。在其私信中运行 `/setup-files` 并按照第十步操作。交换完成后,下次文件请求将原生上传,无需重启 gateway。
**`/setup-files start` 提示"No client credentials stored on the host."**
一次性宿主机设置未完成。在运行 Hermes 的宿主机终端中执行:
```bash
python -m gateway.platforms.google_chat_user_oauth \
--client-secret /path/to/client_secret.json
```
然后再次发送 `/setup-files start`。
**`/setup-files <PASTED_URL>` 提示"Token exchange failed."**
auth code 是一次性的且有效期很短(通常几分钟)。发送 `/setup-files start` 获取新 URL 后重试。
---
## 安全说明
- **Service Account scope**:适配器请求 `chat.bot` 和 `pubsub` scope。IAM 应作为实际执行层——仅授予 SA 最小权限(订阅上的 `roles/pubsub.subscriber` + `roles/pubsub.viewer`),不要授予项目级或组织级 Pub/Sub 角色。
- **附件下载保护**Hermes 只会将 SA bearer token 附加到主机名匹配 Google 自有域名短名单的 URL`googleapis.com`、`drive.google.com`、`lh[3-6].googleusercontent.com` 等)。其他主机在发起 HTTP 请求前即被拒绝,以防范 SSRF 场景——即精心构造的事件将 bearer token 重定向到 GCE 元数据服务。
- **脱敏处理**Service Account 邮箱、订阅路径和 topic 路径会被 `agent/redact.py` 从日志输出中剥离。调试信封转储(`GOOGLE_CHAT_DEBUG_RAW=1`)经过同一脱敏过滤器,以 DEBUG 级别记录。
- **合规性**:如果你计划将此机器人接入受监管的 Workspace(任何有数据驻留或 AI 治理政策的环境),请在首次安装前获得相应审批。
- **用户 OAuth scope**:每用户附件流程*仅*请求 `chat.messages.create`——覆盖 `media.upload` 及后续 `messages.create` 所需的最小权限。token 以明文 JSON 形式持久化在 `~/.hermes/google_chat_user_tokens/<sanitized_email>.json`(文件系统权限是保护手段——与 SA 密钥文件采用相同模型)。每个 token 归属于唯一一位用户;撤销操作仅限于该用户。
@@ -0,0 +1,252 @@
---
title: Home Assistant
description: 通过 Home Assistant 集成,使用 Hermes Agent 控制您的智能家居。
sidebar_label: Home Assistant
sidebar_position: 5
---
# Home Assistant 集成
Hermes Agent 通过以下两种方式与 [Home Assistant](https://www.home-assistant.io/) 集成:
1. **Gateway 平台** — 通过 WebSocket 订阅实时状态变更并响应事件
2. **智能家居工具** — 四个可供 LLM 调用的工具,通过 REST API 查询和控制设备
## 配置
### 1. 创建长期访问令牌
1. 打开您的 Home Assistant 实例
2. 进入**个人资料**(点击侧边栏中的用户名)
3. 滚动至**长期访问令牌**
4. 点击**创建令牌**,命名为"Hermes Agent"
5. 复制令牌
### 2. 配置环境变量
```bash
# Add to ~/.hermes/.env
# Required: your Long-Lived Access Token
HASS_TOKEN=your-long-lived-access-token
# Optional: HA URL (default: http://homeassistant.local:8123)
HASS_URL=http://192.168.1.100:8123
```
:::info
设置 `HASS_TOKEN` 后,`homeassistant` 工具集将自动启用。Gateway 平台和设备控制工具均通过这一个令牌激活。
:::
### 3. 启动 Gateway
```bash
hermes gateway
```
Home Assistant 将作为已连接平台出现,与其他消息平台(Telegram、Discord 等)并列显示。
## 可用工具
Hermes Agent 注册了四个智能家居控制工具:
### `ha_list_entities`
列出 Home Assistant 实体,可按域(domain)或区域(area)过滤。
**参数:**
- `domain` *(可选)* — 按实体域过滤:`light``switch``climate``sensor``binary_sensor``cover``fan``media_player` 等。
- `area` *(可选)* — 按区域/房间名称过滤(与友好名称匹配):`living room``kitchen``bedroom` 等。
**示例:**
```
List all lights in the living room
```
返回实体 ID、状态及友好名称。
### `ha_get_state`
获取单个实体的详细状态,包括所有属性(亮度、颜色、温度设定值、传感器读数等)。
**参数:**
- `entity_id` *(必填)* — 要查询的实体,例如 `light.living_room``climate.thermostat``sensor.temperature`
**示例:**
```
What's the current state of climate.thermostat?
```
返回:状态、所有属性、最后变更/更新时间戳。
### `ha_list_services`
列出可用于设备控制的服务(操作)。显示每种设备类型可执行的操作及其接受的参数。
**参数:**
- `domain` *(可选)* — 按域过滤,例如 `light``climate``switch`
**示例:**
```
What services are available for climate devices?
```
### `ha_call_service`
调用 Home Assistant 服务以控制设备。
**参数:**
- `domain` *(必填)* — 服务域:`light``switch``climate``cover``media_player``fan``scene``script`
- `service` *(必填)* — 服务名称:`turn_on``turn_off``toggle``set_temperature``set_hvac_mode``open_cover``close_cover``set_volume_level`
- `entity_id` *(可选)* — 目标实体,例如 `light.living_room`
- `data` *(可选)* — 以 JSON 对象形式传入的附加参数
**示例:**
```
Turn on the living room lights
→ ha_call_service(domain="light", service="turn_on", entity_id="light.living_room")
```
```
Set the thermostat to 22 degrees in heat mode
→ ha_call_service(domain="climate", service="set_temperature",
entity_id="climate.thermostat", data={"temperature": 22, "hvac_mode": "heat"})
```
```
Set living room lights to blue at 50% brightness
→ ha_call_service(domain="light", service="turn_on",
entity_id="light.living_room", data={"brightness": 128, "color_name": "blue"})
```
## Gateway 平台:实时事件
Home Assistant gateway 适配器通过 WebSocket 连接并订阅 `state_changed` 事件。当设备状态发生变更且符合过滤条件时,该事件将作为消息转发给 agent。
### 事件过滤
:::warning 必要配置
默认情况下,**不转发任何事件**。您必须配置 `watch_domains``watch_entities``watch_all` 中的至少一项才能接收事件。若未设置过滤器,启动时将记录警告日志,所有状态变更将被静默丢弃。
:::
`~/.hermes/config.yaml` 中,于 Home Assistant 平台的 `extra` 部分配置 agent 接收的事件:
```yaml
platforms:
homeassistant:
enabled: true
extra:
watch_domains:
- climate
- binary_sensor
- alarm_control_panel
- light
watch_entities:
- sensor.front_door_battery
ignore_entities:
- sensor.uptime
- sensor.cpu_usage
- sensor.memory_usage
cooldown_seconds: 30
```
| 设置 | 默认值 | 说明 |
|---------|---------|-------------|
| `watch_domains` | *(无)* | 仅监听这些实体域(例如 `climate``light``binary_sensor` |
| `watch_entities` | *(无)* | 仅监听这些特定实体 ID |
| `watch_all` | `false` | 设为 `true` 以接收**所有**状态变更(不推荐用于大多数场景) |
| `ignore_entities` | *(无)* | 始终忽略这些实体(在域/实体过滤器之前应用) |
| `cooldown_seconds` | `30` | 同一实体两次事件之间的最小间隔秒数 |
:::tip
从一组精简的域开始 — `climate``binary_sensor``alarm_control_panel` 已覆盖最常用的自动化场景。按需添加更多域。使用 `ignore_entities` 屏蔽 CPU 温度或运行时间计数器等噪声传感器。
:::
### 事件格式化
状态变更将根据域格式化为人类可读的消息:
| 域 | 格式 |
|--------|--------|
| `climate` | "HVAC mode changed from 'off' to 'heat' (current: 21, target: 23)" |
| `sensor` | "changed from 21°C to 22°C" |
| `binary_sensor` | "triggered" / "cleared" |
| `light``switch``fan` | "turned on" / "turned off" |
| `alarm_control_panel` | "alarm state changed from 'armed_away' to 'triggered'" |
| *(其他)* | "changed from 'old' to 'new'" |
### Agent 响应
Agent 发出的消息将以 **Home Assistant 持久通知**的形式推送(通过 `persistent_notification.create`),标题为"Hermes Agent",显示在 HA 通知面板中。
### 连接管理
- **WebSocket** 每 30 秒发送一次心跳,用于实时事件
- **自动重连**,退避策略:5s → 10s → 30s → 60s
- **REST API** 用于出站通知(独立会话,避免与 WebSocket 冲突)
- **鉴权** — HA 事件始终已授权(无需用户白名单,`HASS_TOKEN` 负责验证连接)
## 安全性
Home Assistant 工具强制执行安全限制:
:::warning 已屏蔽的域
以下服务域已被**屏蔽**,以防止在 HA 主机上执行任意代码:
- `shell_command` — 任意 shell 命令
- `command_line` — 执行命令的传感器/开关
- `python_script` — 脚本化 Python 执行
- `pyscript` — 更广泛的脚本集成
- `hassio` — 插件控制、主机关机/重启
- `rest_command` — 来自 HA 服务器的 HTTP 请求(SSRF 向量)
尝试调用这些域中的服务将返回错误。
:::
实体 ID 将通过正则表达式 `^[a-z_][a-z0-9_]*\.[a-z0-9_]+$` 进行验证,以防止注入攻击。
## 自动化示例
### 晨间例程
```
User: Start my morning routine
Agent:
1. ha_call_service(domain="light", service="turn_on",
entity_id="light.bedroom", data={"brightness": 128})
2. ha_call_service(domain="climate", service="set_temperature",
entity_id="climate.thermostat", data={"temperature": 22})
3. ha_call_service(domain="media_player", service="turn_on",
entity_id="media_player.kitchen_speaker")
```
### 安全检查
```
User: Is the house secure?
Agent:
1. ha_list_entities(domain="binary_sensor")
→ checks door/window sensors
2. ha_get_state(entity_id="alarm_control_panel.home")
→ checks alarm status
3. ha_list_entities(domain="lock")
→ checks lock states
4. Reports: "All doors closed, alarm is armed_away, all locks engaged."
```
### 响应式自动化(通过 Gateway 事件)
作为 gateway 平台连接后,agent 可对事件作出响应:
```
[Home Assistant] Front Door: triggered (was cleared)
Agent automatically:
1. ha_get_state(entity_id="binary_sensor.front_door")
2. ha_call_service(domain="light", service="turn_on",
entity_id="light.hallway")
3. Sends notification: "Front door opened. Hallway lights turned on."
```
@@ -0,0 +1,549 @@
---
sidebar_position: 1
title: "消息网关"
description: "通过 Telegram、Discord、Slack、WhatsApp、Signal、SMS、Email、Home Assistant、Mattermost、Matrix、DingTalk、Yuanbao、Microsoft Teams、LINE、Webhooks 或任何兼容 OpenAI 的前端与 Hermes 对话 — 架构与配置概览"
---
# 消息网关
通过 Telegram、Discord、Slack、WhatsApp、Signal、SMS、Email、Home Assistant、Mattermost、Matrix、DingTalk、Feishu/Lark、WeCom、Weixin、BlueBubblesiMessage)、QQ、Yuanbao、Microsoft Teams、LINE、ntfy 或浏览器与 Hermes 对话。网关是一个单一后台进程,连接所有已配置的平台,管理会话,运行 cron 任务,并传递语音消息。
完整的语音功能集——包括 CLI 麦克风模式、消息中的语音回复以及 Discord 语音频道对话——请参阅 [Voice Mode](/user-guide/features/voice-mode) 和 [Use Voice Mode with Hermes](/guides/use-voice-mode-with-hermes)。
## 平台对比
| 平台 | 语音 | 图片 | 文件 | 线程 | 表情反应 | 输入提示 | 流式输出 |
|----------|:-----:|:------:|:-----:|:-------:|:---------:|:------:|:---------:|
| Telegram | ✅ | ✅ | ✅ | ✅ | — | ✅ | ✅ |
| Discord | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Slack | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Google Chat | — | ✅ | ✅ | ✅ | — | ✅ | — |
| WhatsApp | — | ✅ | ✅ | — | — | ✅ | ✅ |
| Signal | — | ✅ | ✅ | — | — | ✅ | ✅ |
| SMS | — | — | — | — | — | — | — |
| Email | — | ✅ | ✅ | ✅ | — | — | — |
| Home Assistant | — | — | — | — | — | — | — |
| Mattermost | ✅ | ✅ | ✅ | ✅ | — | ✅ | ✅ |
| Matrix | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| DingTalk | — | ✅ | ✅ | — | ✅ | — | ✅ |
| Feishu/Lark | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| WeCom | ✅ | ✅ | ✅ | — | — | — | — |
| WeCom Callback | — | — | — | — | — | — | — |
| Weixin | ✅ | ✅ | ✅ | — | — | ✅ | ✅ |
| BlueBubbles | — | ✅ | ✅ | — | ✅ | ✅ | — |
| QQ | ✅ | ✅ | ✅ | — | — | ✅ | — |
| Yuanbao | ✅ | ✅ | ✅ | — | — | ✅ | ✅ |
| Microsoft Teams | — | ✅ | — | ✅ | — | ✅ | — |
| LINE | — | ✅ | ✅ | — | — | ✅ | — |
| ntfy | — | — | — | — | — | — | — |
**语音** = TTS 音频回复和/或语音消息转录。**图片** = 发送/接收图片。**文件** = 发送/接收文件附件。**线程** = 线程式对话。**表情反应** = 对消息添加 emoji 反应。**输入提示** = 处理时显示正在输入状态。**流式输出** = 通过编辑消息实现渐进式更新。
## 架构
```mermaid
flowchart TB
subgraph Gateway["Hermes Gateway"]
subgraph Adapters["Platform adapters"]
tg[Telegram]
dc[Discord]
wa[WhatsApp]
sl[Slack]
gc[Google Chat]
sig[Signal]
sms[SMS]
em[Email]
ha[Home Assistant]
mm[Mattermost]
mx[Matrix]
dt[DingTalk]
fs[Feishu/Lark]
wc[WeCom]
wcb[WeCom Callback]
wx[Weixin]
bb[BlueBubbles]
qq[QQ]
yb[Yuanbao]
ms[Microsoft Teams]
api["API Server<br/>(OpenAI-compatible)"]
wh[Webhooks]
end
store["Session store<br/>per chat"]
agent["AIAgent<br/>run_agent.py"]
cron["Cron scheduler<br/>ticks every 60s"]
end
tg --> store
dc --> store
wa --> store
sl --> store
gc --> store
sig --> store
sms --> store
em --> store
ha --> store
mm --> store
mx --> store
dt --> store
fs --> store
wc --> store
wcb --> store
wx --> store
bb --> store
qq --> store
yb --> store
ms --> store
api --> store
wh --> store
store --> agent
cron --> store
```
每个平台适配器接收消息,通过每个聊天的会话存储进行路由,并将其分发给 AIAgent 处理。网关还运行 cron 调度器,每 60 秒触发一次以执行到期任务。
## 快速配置
配置消息平台最简单的方式是使用交互式向导:
```bash
hermes gateway setup # 交互式配置所有消息平台
```
该向导引导你通过方向键选择配置各平台,显示哪些平台已配置,并在完成后提示启动/重启网关。
## 网关命令
```bash
hermes gateway # 在前台运行
hermes gateway setup # 交互式配置消息平台
hermes gateway install # 安装为用户服务(Linux/ launchd 服务(macOS
sudo hermes gateway install --system # 仅 Linux:安装开机启动的系统服务
hermes gateway start # 启动默认服务
hermes gateway stop # 停止默认服务
hermes gateway status # 检查默认服务状态
hermes gateway status --system # 仅 Linux:显式检查系统服务
```
## 聊天命令(在消息平台内使用)
| 命令 | 说明 |
|---------|-------------|
| `/new``/reset` | 开始新对话 |
| `/model [provider:model]` | 显示或切换模型(支持 `provider:model` 语法) |
| `/personality [name]` | 设置人格 |
| `/retry` | 重试上一条消息 |
| `/undo` | 删除上一轮对话 |
| `/status` | 显示会话信息 |
| `/whoami` | 显示你在当前范围内的斜杠命令权限(管理员 / 普通用户 / 无限制) |
| `/stop` | 停止正在运行的 agent |
| `/approve` | 批准待执行的危险命令 |
| `/deny` | 拒绝待执行的危险命令 |
| `/sethome` | 将此聊天设为主频道 |
| `/compress` | 手动压缩对话上下文 |
| `/title [name]` | 设置或显示会话标题 |
| `/resume [name]` | 恢复之前命名的会话 |
| `/usage` | 显示本会话的 token 用量 |
| `/insights [days]` | 显示用量洞察与分析 |
| `/reasoning [level\|show\|hide]` | 更改推理强度或切换推理显示 |
| `/voice [on\|off\|tts\|join\|leave\|status]` | 控制消息语音回复和 Discord 语音频道行为 |
| `/rollback [number]` | 列出或恢复文件系统检查点 |
| `/background <prompt>` | 在独立后台会话中运行 prompt(提示词) |
| `/reload-mcp` | 从配置重新加载 MCP 服务器 |
| `/update` | 将 Hermes Agent 更新至最新版本 |
| `/help` | 显示可用命令 |
| `/<skill-name>` | 调用任意已安装的技能 |
## 会话管理
### 会话持久化
会话在消息之间持续保留,直到重置。Agent 会记住你的对话上下文。
### 重置策略
会话根据可配置的策略重置:
| 策略 | 默认值 | 说明 |
|--------|---------|-------------|
| 每日 | 凌晨 4:00 | 每天在指定时间重置 |
| 空闲 | 1440 分钟 | 空闲 N 分钟后重置 |
| 两者 | (组合) | 以先触发者为准 |
`~/.hermes/gateway.json` 中配置各平台的覆盖设置:
```json
{
"reset_by_platform": {
"telegram": { "mode": "idle", "idle_minutes": 240 },
"discord": { "mode": "idle", "idle_minutes": 60 }
}
}
```
## 安全
**默认情况下,网关拒绝所有不在白名单中或未通过私信配对的用户。** 这是具有终端访问权限的机器人的安全默认设置。
```bash
# 限制为特定用户(推荐):
TELEGRAM_ALLOWED_USERS=123456789,987654321
DISCORD_ALLOWED_USERS=123456789012345678
SIGNAL_ALLOWED_USERS=+155****4567,+155****6543
SMS_ALLOWED_USERS=+155****4567,+155****6543
EMAIL_ALLOWED_USERS=trusted@example.com,colleague@work.com
MATTERMOST_ALLOWED_USERS=3uo8dkh1p7g1mfk49ear5fzs5c
MATRIX_ALLOWED_USERS=@alice:matrix.org
DINGTALK_ALLOWED_USERS=user-id-1
FEISHU_ALLOWED_USERS=ou_xxxxxxxx,ou_yyyyyyyy
WECOM_ALLOWED_USERS=user-id-1,user-id-2
WECOM_CALLBACK_ALLOWED_USERS=user-id-1,user-id-2
TEAMS_ALLOWED_USERS=aad-object-id-1,aad-object-id-2
# 或允许
GATEWAY_ALLOWED_USERS=123456789,987654321
# 或显式允许所有用户(不推荐用于具有终端访问权限的机器人):
GATEWAY_ALLOW_ALL_USERS=true
```
### 私信配对(白名单的替代方案)
无需手动配置用户 ID,未知用户私信机器人时会收到一次性配对码:
```bash
# 用户看到:"Pairing code: XKGH5N7P"
# 你通过以下命令批准:
hermes pairing approve telegram XKGH5N7P
# 其他配对命令:
hermes pairing list # 查看待审核和已批准的用户
hermes pairing revoke telegram 123456789 # 撤销访问权限
```
配对码 1 小时后过期,有频率限制,并使用密码学随机数生成。
### 管理员与普通用户
白名单解决的是"此人能否访问机器人"的问题。**管理员 / 普通用户的划分**解决的是"既然已经进来了,他们被允许做什么"的问题。
每个允许的用户在每个范围(私信 vs 群组/频道)内属于以下两个层级之一:
- **管理员** — 完全访问权限。可运行所有已注册的斜杠命令(内置 + 插件)并使用所有受限功能。
- **普通用户** — 受限访问权限。可正常与 agent 聊天,但只能运行你明确启用的斜杠命令。始终允许的最低权限为 `/help``/whoami`
层级按平台和范围分别配置。私信管理员身份不意味着群组/频道管理员身份——每个范围有各自的管理员列表。
**当前层级控制的内容:** 斜杠命令。该划分贯穿实时命令注册表,因此无需逐功能配置即可覆盖内置命令和插件注册的命令。普通聊天不受影响——非管理员仍可与 agent 对话。
**未来可能受控的内容:** 更多功能面(工具访问、模型切换、高消耗操作)将随着我们的添加挂载到同一管理员 / 普通用户区分上。现在配置好划分,意味着未来的限制可以干净落地,无需重新规划谁是管理员。
#### 配置
```yaml
gateway:
platforms:
discord:
extra:
allow_from: ["111", "222", "333"]
allow_admin_from: ["111"] # 管理员 → 所有斜杠命令
user_allowed_commands: [status, model] # 非管理员可运行的命令
# 可选:单独配置群组/频道范围
group_allow_admin_from: ["111"]
group_user_allowed_commands: [status]
```
**向后兼容:** 如果某个范围未设置 `allow_admin_from`,则该范围的层级划分被禁用,所有允许的用户拥有完全访问权限。现有安装无需任何更改即可继续工作——需要区分时再选择启用。
#### 查看你的权限
在任意平台使用 `/whoami` 查看当前范围、你的层级(管理员 / 普通用户 / 无限制)以及你可以运行的斜杠命令。平台特定示例请参阅 [Telegram](/user-guide/messaging/telegram#slash-command-access-control) 和 [Discord](/user-guide/messaging/discord#slash-command-access-control) 页面。
## 中断 Agent
在 agent 工作时发送任意消息即可中断它。关键行为:
- **正在执行的终端命令立即终止**SIGTERM1 秒后 SIGKILL
- **工具调用被取消** — 仅当前正在执行的工具调用会运行,其余跳过
- **多条消息合并** — 中断期间发送的消息合并为一个 prompt
- **`/stop` 命令** — 中断而不排队后续消息
### 队列 vs 中断 vs 引导(繁忙输入模式)
默认情况下,向繁忙的 agent 发送消息会中断它。另有两种模式可用:
- `queue` — 后续消息等待,在当前任务完成后作为下一轮运行。
- `steer` — 后续消息通过 `/steer` 注入当前运行,在下一次工具调用后到达 agent。不中断,不开新轮次。如果 agent 尚未开始,则回退为 `queue` 行为。
```yaml
display:
busy_input_mode: steer # 或 queue,或 interrupt(默认)
busy_ack_enabled: true # 设为 false 可完全抑制 ⚡/⏳/⏩ 聊天回复
```
第一次在任意平台向繁忙的 agent 发送消息时,Hermes 会在繁忙确认中附加一行提示,说明该配置项(`"💡 First-time tip — …"`)。该提示每次安装只触发一次——由 `onboarding.seen.busy_input_prompt` 下的标志锁定。删除该键可再次看到提示。
如果你觉得繁忙确认消息过多——尤其是使用语音输入或快速连续发送消息时——可设置 `display.busy_ack_enabled: false`。你的输入仍会正常排队/引导/中断,只是聊天回复被静默。
## 工具进度通知
`~/.hermes/config.yaml` 中控制显示多少工具活动信息:
```yaml
display:
tool_progress: all # off | new | all | verbose
tool_progress_command: false # 设为 true 可在消息平台中启用 /verbose
```
启用后,机器人在工作时发送状态消息:
```text
💻 `ls -la`...
🔍 web_search...
📄 web_extract...
🐍 execute_code...
```
## 后台会话
在独立的后台会话中运行 prompt,让 agent 独立处理,同时保持主聊天响应:
```
/background Check all servers in the cluster and report any that are down
```
Hermes 立即确认:
```
🔄 Background task started: "Check all servers in the cluster..."
Task ID: bg_143022_a1b2c3
```
### 工作原理
每个 `/background` prompt 会生成一个**独立的 agent 实例**异步运行:
- **隔离会话** — 后台 agent 拥有自己的会话和对话历史。它不了解你当前的聊天上下文,只接收你提供的 prompt。
- **相同配置** — 继承当前网关配置中的模型、提供商、工具集、推理设置和提供商路由。
- **非阻塞** — 你的主聊天保持完全交互。在后台任务运行期间,你可以发送消息、运行其他命令或启动更多后台任务。
- **结果传递** — 任务完成后,结果发送回**发出命令的同一聊天或频道**,前缀为"✅ Background task complete"。如果失败,你会看到"❌ Background task failed"及错误信息。
### 后台进程通知
当运行后台会话的 agent 使用 `terminal(background=true)` 启动长时间运行的进程(服务器、构建等)时,网关可以向你的聊天推送状态更新。通过 `~/.hermes/config.yaml` 中的 `display.background_process_notifications` 控制:
```yaml
display:
background_process_notifications: all # all | result | error | off
```
| 模式 | 你收到的内容 |
|------|-----------------|
| `all` | 运行输出更新**以及**最终完成消息(默认) |
| `result` | 仅最终完成消息(无论退出码) |
| `error` | 仅在退出码非零时的最终消息 |
| `off` | 不接收任何进程监控消息 |
也可通过环境变量设置:
```bash
HERMES_BACKGROUND_NOTIFICATIONS=result
```
### 使用场景
- **服务器监控** — "/background Check the health of all services and alert me if anything is down"
- **长时间构建** — "/background Build and deploy the staging environment",同时继续聊天
- **研究任务** — "/background Research competitor pricing and summarize in a table"
- **文件操作** — "/background Organize the photos in ~/Downloads by date into folders"
:::tip
消息平台上的后台任务是即发即忘的——你无需等待或主动查询。任务完成后,结果会自动出现在同一聊天中。
:::
## 服务管理
### Linuxsystemd
```bash
hermes gateway install # 安装为用户服务
hermes gateway start # 启动服务
hermes gateway stop # 停止服务
hermes gateway status # 检查状态
journalctl --user -u hermes-gateway -f # 查看日志
# 启用 lingering(注销后保持运行)
sudo loginctl enable-linger $USER
# 或安装开机启动的系统服务,仍以你的用户身份运行
sudo hermes gateway install --system
sudo hermes gateway start --system
sudo hermes gateway status --system
journalctl -u hermes-gateway -f
```
笔记本和开发机使用用户服务。VPS 或无头主机(需要开机自动启动而不依赖 systemd linger)使用系统服务。
除非你确实有此需要,否则避免同时安装用户和系统网关单元。Hermes 检测到两者同时存在时会发出警告,因为 start/stop/status 行为会变得不明确。
:::info 多个安装
如果你在同一台机器上运行多个 Hermes 安装(使用不同的 `HERMES_HOME` 目录),每个安装都有自己的 systemd 服务名称。默认的 `~/.hermes` 使用 `hermes-gateway`;其他安装使用 `hermes-gateway-<hash>``hermes gateway` 命令会自动针对当前 `HERMES_HOME` 对应的正确服务。
:::
### macOSlaunchd
```bash
hermes gateway install # 安装为 launchd agent
hermes gateway start # 启动服务
hermes gateway stop # 停止服务
hermes gateway status # 检查状态
tail -f ~/.hermes/logs/gateway.log # 查看日志
```
生成的 plist 文件位于 `~/Library/LaunchAgents/ai.hermes.gateway.plist`。它包含三个环境变量:
- **PATH** — 安装时你的完整 shell PATH,并在前面添加了 venv `bin/``node_modules/.bin`。这确保用户安装的工具(Node.js、ffmpeg 等)可供网关子进程(如 WhatsApp 桥接)使用。
- **VIRTUAL_ENV** — 指向 Python 虚拟环境,使工具能正确解析包。
- **HERMES_HOME** — 将网关限定到你的 Hermes 安装。
:::tip 安装后 PATH 变更
launchd plist 是静态的——如果你在配置网关后安装了新工具(例如通过 nvm 安装新版 Node.js,或通过 Homebrew 安装 ffmpeg),请重新运行 `hermes gateway install` 以捕获更新后的 PATH。网关会检测到过时的 plist 并自动重新加载。
:::
:::info 多个安装
与 Linux systemd 服务类似,每个 `HERMES_HOME` 目录都有自己的 launchd 标签。默认的 `~/.hermes` 使用 `ai.hermes.gateway`;其他安装使用 `ai.hermes.gateway-<suffix>`
:::
## 平台专属工具集
每个平台有自己的工具集:
| 平台 | 工具集 | 功能 |
|----------|---------|--------------|
| CLI | `hermes-cli` | 完全访问 |
| Telegram | `hermes-telegram` | 完整工具,包括终端 |
| Discord | `hermes-discord` | 完整工具,包括终端 |
| WhatsApp | `hermes-whatsapp` | 完整工具,包括终端 |
| Slack | `hermes-slack` | 完整工具,包括终端 |
| Google Chat | `hermes-google_chat` | 完整工具,包括终端 |
| Signal | `hermes-signal` | 完整工具,包括终端 |
| SMS | `hermes-sms` | 完整工具,包括终端 |
| Email | `hermes-email` | 完整工具,包括终端 |
| Home Assistant | `hermes-homeassistant` | 完整工具 + HA 设备控制(ha_list_entities、ha_get_state、ha_call_service、ha_list_services |
| Mattermost | `hermes-mattermost` | 完整工具,包括终端 |
| Matrix | `hermes-matrix` | 完整工具,包括终端 |
| DingTalk | `hermes-dingtalk` | 完整工具,包括终端 |
| Feishu/Lark | `hermes-feishu` | 完整工具,包括终端 |
| WeCom | `hermes-wecom` | 完整工具,包括终端 |
| WeCom Callback | `hermes-wecom-callback` | 完整工具,包括终端 |
| Weixin | `hermes-weixin` | 完整工具,包括终端 |
| BlueBubbles | `hermes-bluebubbles` | 完整工具,包括终端 |
| QQBot | `hermes-qqbot` | 完整工具,包括终端 |
| Yuanbao | `hermes-yuanbao` | 完整工具,包括终端 |
| Microsoft Teams | `hermes-teams` | 完整工具,包括终端 |
| API Server | `hermes-api-server` | 完整工具(去除 `clarify``send_message``text_to_speech`——程序化访问没有交互用户) |
| Webhooks | `hermes-webhook` | 完整工具,包括终端 |
## 运营多平台网关
网关通常同时运行多个适配器(Telegram + Discord + Slack 等)。以下章节涵盖跨所有平台的日常运维操作。
### `/platform` 命令
网关运行后,可从任意已连接的 CLI 会话或聊天使用 `/platform` 斜杠命令检查和控制单个适配器,无需重启整个网关:
```
/platform list # 显示所有适配器及其状态
/platform pause <name> # 停止向某个适配器分发新消息
/platform resume <name> # 重新启用已暂停的适配器
```
`/platform list` 显示每个适配器是 `running`(运行中)、`paused`(手动暂停)还是 `paused-by-breaker`(见下文)。暂停会保持适配器加载状态及其后台循环——传入消息被丢弃,但连接本身保持开启,因此恢复是即时的。
另请参阅更广泛的状态汇总命令 [`/platforms`](../../reference/slash-commands.md#info)。
### 自动熔断器
每个适配器都包裹在熔断器中。反复出现的可重试失败(网络抖动、限流回复、上游 5xx 响应、websocket 断开)会导致熔断器触发——适配器被自动暂停,当配置了主频道时向另一个存活平台的主频道发送运营通知,并输出结构化日志行。
熔断器**不会自动恢复**——它保持断开状态,直到你手动运行 `/platform resume <name>`。这是有意为之:如果某个平台持续故障,你不希望网关不断重试重连。
### 适配器暂停时的排查步骤
当适配器暂停时,检查:
1. **网关日志**`~/.hermes/logs/gateway.log` 或 systemd / launchd 单元日志)。搜索平台名称以及 `circuit breaker``paused``disabled`。触发事件包含失败次数和最后一个错误。
2. **`/platform list`** 输出——显示当前状态和最后原因。
3. **提供商状态页面**Telegram bot API 状态、Discord 状态等)。熔断器触发是因为平台不健康;在平台恢复之前不要尝试恢复。
上游恢复正常后,`/platform resume <name>` 清除熔断器并重新激活适配器。
### 重启通知
当网关重启(或在有进行中会话时关闭)时,它可以向每个平台的主频道发送一条"agent 已恢复"/"agent 被中断"的一次性消息。这由 `gateway-config.yaml` 中每个平台的 `gateway_restart_notification` 标志控制,默认为 `true`
```yaml
gateway:
platforms:
telegram:
home_chat_id: "123456789"
gateway_restart_notification: false # 为此平台关闭
discord:
home_chat_id: "987654321"
# gateway_restart_notification 未设置 → 默认为 true
```
在嘈杂或低优先级的平台上禁用,同时在主要聊天上保持启用。无论有多少会话正在进行,每次重启只发送一次通知。
### 网关重启后的会话恢复
当网关在工具调用或生成进行中时关闭,受影响的会话被标记为 `restart_interrupted`。下次启动时,网关为每个会话安排自动恢复——用户在聊天中收到简短提示("Send any message after restart and I'll try to resume where you left off."),当他们回复时,会话从最后提交的轮次继续。
此行为默认开启,并在网关启动时记录日志:
```
Scheduled auto-resume for N restart-interrupted session(s)
```
无需配置。如果你不想要提示消息,在该平台上设置 `gateway_restart_notification: false`
### 进度气泡清理(可选启用)
工具进度消息、"仍在处理中……"心跳以及状态回调气泡可在最终响应落地后自动删除。通过 `display.platforms.<platform>.cleanup_progress` 按平台启用:
```yaml
display:
platforms:
telegram:
cleanup_progress: true
discord:
cleanup_progress: true
```
默认为 `false`。仅实现了 `delete_message` 的适配器平台支持此设置(目前为 Telegram 和 Discord)。运行失败时**跳过**清理,气泡保留作为调试线索。
## 后续步骤
- [Telegram 配置](telegram.md)
- [Discord 配置](discord.md)
- [Slack 配置](slack.md)
- [Google Chat 配置](google_chat.md)
- [WhatsApp 配置](whatsapp.md)
- [Signal 配置](signal.md)
- [SMS 配置(Twilio](sms.md)
- [Email 配置](email.md)
- [Home Assistant 集成](homeassistant.md)
- [Mattermost 配置](mattermost.md)
- [Matrix 配置](matrix.md)
- [DingTalk 配置](dingtalk.md)
- [Feishu/Lark 配置](feishu.md)
- [WeCom 配置](wecom.md)
- [WeCom Callback 配置](wecom-callback.md)
- [Weixin 配置(微信)](weixin.md)
- [BlueBubbles 配置(iMessage](bluebubbles.md)
- [QQBot 配置](qqbot.md)
- [Yuanbao 配置](yuanbao.md)
- [Microsoft Teams 配置](teams.md)
- [Teams 会议流水线](teams-meetings.md)
- [Open WebUI + API Server](open-webui.md)
- [Webhooks](webhooks.md)
@@ -0,0 +1,198 @@
---
sidebar_position: 17
title: "LINE"
description: "将 Hermes Agent 设置为 LINE Messaging API 机器人"
---
# LINE 配置
通过官方 LINE Messaging API 将 Hermes Agent 作为 [LINE](https://line.me/) 机器人运行。适配器以捆绑平台插件的形式存放于 `plugins/platforms/line/` — 无需修改核心代码,像其他平台一样启用即可。
LINE 是日本、台湾和泰国的主流即时通讯应用。如果你的用户在这些地区,这就是他们与你沟通的方式。
## 机器人响应方式
| 场景 | 行为 |
|---------|----------|
| **1:1 聊天**`U` 开头 ID | 响应每条消息 |
| **群聊**`C` 开头 ID) | 仅当群组在白名单中时响应 |
| **多人房间**`R` 开头 ID) | 仅当房间在白名单中时响应 |
入站的文本、图片、音频、视频、文件、贴纸和位置信息均可处理。出站文本优先使用**免费 reply token**(单次使用,有效期约 60 秒),token 过期后回退至计费的 Push API。
---
## 第一步:创建 LINE Messaging API 频道
1. 前往 [LINE Developers Console](https://developers.line.biz/console/)。
2. 创建一个 Provider,然后在其下创建一个 **Messaging API** 频道。
3. 在频道的 **Basic settings** 标签页中,复制 **Channel secret**
4.**Messaging API** 标签页中,滚动至 **Channel access token (long-lived)** 并点击 **Issue**,复制该 token。
5.**Messaging API** 标签页中,同时禁用 **Auto-reply messages****Greeting messages**,避免与机器人回复冲突。
---
## 第二步:暴露 webhook 端口
LINE 通过公网 HTTPS 推送 webhook。默认端口为 `8646` — 如需修改,可通过 `LINE_PORT` 覆盖。
```bash
# Cloudflare Tunnel(推荐用于生产环境 — 固定主机名)
cloudflared tunnel --url http://localhost:8646
# ngrok(适合开发环境)
ngrok http 8646
# devtunnel
devtunnel create hermes-line --allow-anonymous
devtunnel port create hermes-line -p 8646 --protocol https
devtunnel host hermes-line
```
复制 `https://...` URL — 稍后将其设置为 webhook URL。**保持隧道运行**以便测试。生产环境请配置固定的 Cloudflare 命名隧道,避免重启后 webhook URL 变更。
---
## 第三步:配置 Hermes
`~/.hermes/.env` 中添加:
```env
LINE_CHANNEL_ACCESS_TOKEN=YOUR_LONG_LIVED_TOKEN
LINE_CHANNEL_SECRET=YOUR_CHANNEL_SECRET
# 白名单 — 至少填写其中一项(开发环境可使用 LINE_ALLOW_ALL_USERS=true
LINE_ALLOWED_USERS=U1234567890abcdef... # 逗号分隔的 U 开头 ID
LINE_ALLOWED_GROUPS=C1234567890abcdef... # 可选的群组 ID
LINE_ALLOWED_ROOMS=R1234567890abcdef... # 可选的房间 ID
# 发送图片 / 音频 / 视频时必填 — 隧道解析到的公网 HTTPS 基础 URL
# 未设置时,send_image/voice/video 将拒绝执行
LINE_PUBLIC_URL=https://my-tunnel.example.com
```
然后在 `~/.hermes/config.yaml` 中:
```yaml
gateway:
platforms:
line:
enabled: true
```
这就够了 — `gateway/config.py` 中的捆绑插件扫描会自动识别 `plugins/platforms/line/`。无需编辑 `Platform.LINE` 枚举,无需注册 `_create_adapter`
---
## 第四步:设置 webhook URL
回到 LINE 控制台:
1. 打开你的频道 → **Messaging API** 标签页。
2.**Webhook settings****Webhook URL** 下,粘贴 `https://<your-tunnel>/line/webhook`(注意 `/line/webhook` 路径 — 适配器在此监听)。
3. 点击 **Verify**。LINE 会 ping 该 URL,你应看到 200 响应。
4.**Use webhook** 切换为 **On**
---
## 第五步:运行 gateway
```bash
hermes gateway
```
Agent 日志显示:
```
LINE: webhook listening on 0.0.0.0:8646/line/webhook (public: https://my-tunnel.example.com)
```
从 LINE 应用将机器人添加为好友(扫描频道 **Messaging API** 标签页中的二维码),然后发送一条消息。
---
## LLM 响应缓慢
LINE 的 reply token 为单次使用,在入站事件发生后约 60 秒过期。LLM 响应过慢时将无法及时回复,通常会被迫调用付费的 Push API。
当 LLM 运行时间超过 `LINE_SLOW_RESPONSE_THRESHOLD` 秒(默认 `45`)时,适配器会消耗原始 reply token,发送一个 **Template Buttons** 气泡:
> 🤔 Still thinking. Tap below to fetch the answer when it's ready.
>
> [ Get answer ]
用户在方便时点击 **Get answer** — 该 postback 会带来一个*新的* reply token,适配器用它发送缓存的答案(仍然免费)。
状态机:`PENDING → READY → DELIVERED`,以及 `ERROR`(用于已取消的运行 — 执行 `/stop` 后,孤立的 PENDING 状态会解析为"Run was interrupted before completion.",避免持久按钮循环触发)。
如需禁用 postback 按钮并始终回退至 Push API
```env
LINE_SLOW_RESPONSE_THRESHOLD=0
```
为使 postback 流程可靠触发,请抑制可能在阈值前消耗 reply token 的冗余输出:
```yaml
# ~/.hermes/config.yaml
display:
interim_assistant_messages: false
platforms:
line:
tool_progress: off
```
---
## Cron / 通知推送
```env
LINE_HOME_CHANNEL=Uxxxxxxxxxxxxxxxxxxxx # 默认推送目标
```
设置了 `deliver: line` 的 Cron 任务会路由至 `LINE_HOME_CHANNEL`。适配器内置独立的仅 Push 发送器,因此即使 cron 在独立进程中运行,也能正常工作。
---
## 环境变量参考
| 变量 | 是否必填 | 默认值 | 说明 |
|---|---|---|---|
| `LINE_CHANNEL_ACCESS_TOKEN` | 是 | — | 长期有效的频道访问 token |
| `LINE_CHANNEL_SECRET` | 是 | — | Channel secret(用于 HMAC-SHA256 webhook 验证) |
| `LINE_HOST` | 否 | `0.0.0.0` | Webhook 绑定主机 |
| `LINE_PORT` | 否 | `8646` | Webhook 绑定端口 |
| `LINE_PUBLIC_URL` | 媒体发送时必填 | — | 公网 HTTPS 基础 URL;发送图片/音频/视频时必须设置 |
| `LINE_ALLOWED_USERS` | 三选一 | — | 逗号分隔的用户 ID(U 开头) |
| `LINE_ALLOWED_GROUPS` | 三选一 | — | 逗号分隔的群组 ID(C 开头) |
| `LINE_ALLOWED_ROOMS` | 三选一 | — | 逗号分隔的房间 ID(R 开头) |
| `LINE_ALLOW_ALL_USERS` | 仅开发环境 | `false` | 完全跳过白名单验证 |
| `LINE_HOME_CHANNEL` | 否 | — | 默认 cron / 通知推送目标 |
| `LINE_SLOW_RESPONSE_THRESHOLD` | 否 | `45` | 触发 postback 按钮的等待秒数(`0` = 禁用) |
| `LINE_PENDING_TEXT` | 否 | "🤔 Still thinking…" | postback 按钮旁显示的气泡文本 |
| `LINE_BUTTON_LABEL` | 否 | "Get answer" | 按钮标签 |
| `LINE_DELIVERED_TEXT` | 否 | "Already replied ✅" | 再次点击已送达按钮时的回复 |
| `LINE_INTERRUPTED_TEXT` | 否 | "Run was interrupted before completion." | 点击 `/stop` 孤立按钮时的回复 |
---
## 故障排查
**webhook 验证时提示"invalid signature"。** `Channel secret` 复制有误,或隧道重写了请求体。请先用 `curl -i https://<tunnel>/line/webhook/health` 验证 — 应返回 `{"status":"ok","platform":"line"}`
**机器人在群组中收不到消息。** 检查 `LINE_ALLOWED_GROUPS` 是否包含对应的 `C...` 群组 ID。如需查找群组 ID,发送一条测试消息后在 `~/.hermes/logs/gateway.log` 中搜索 `LINE: rejecting unauthorized source` — 被拒绝的 source 字典中包含相关 ID。
**`send_image` 报错"LINE_PUBLIC_URL must be set"。** LINE Messaging API 不接受二进制上传 — 图片、音频和视频必须是可访问的 HTTPS URL。将 `LINE_PUBLIC_URL` 设置为隧道的公网主机名,适配器会自动从 `/line/media/<token>/<filename>` 提供文件服务。
**postback 按钮始终不出现。** 要么 LLM 的响应速度快于 `LINE_SLOW_RESPONSE_THRESHOLD`,要么其他气泡(工具进度、流式输出)已提前消耗了 reply token。参见"LLM 响应缓慢"中的抑制配置。
**"already in use by another profile"。** 同一个频道访问 token 已被另一个运行中的 Hermes profile 占用。请停止另一个 gateway,或使用独立的频道。
---
## 限制
* **气泡与长度上限。** 每个 LINE 文本气泡最多 5000 个字符。超长响应会在每次 Reply/Push 调用中按约 4500 个字符智能分块(最多 5 个气泡),并尽可能在自然边界处切分。
* **不支持原生消息编辑。** LINE 没有编辑消息的 API — 流式响应始终发送新气泡,不会编辑已有气泡。
* **不支持 Markdown 渲染。** 粗体(`**`)、斜体(`*`)、代码块和标题均以字面字符显示。适配器在发送前会将其剥离;URL 会被保留(`[label](url)` 转换为 `label (url)`)。
* **加载指示器仅限私聊。** LINE 对群组和房间拒绝 chat/loading API,因此输入指示器仅在 1:1 聊天中显示。
@@ -0,0 +1,676 @@
---
sidebar_position: 9
title: "Matrix"
description: "将 Hermes Agent 设置为 Matrix 机器人"
---
# Matrix 设置
Hermes Agent 与 Matrix 集成,Matrix 是一种开放的联邦消息协议。Matrix 允许你运行自己的 homeserver,也可以使用 matrix.org 等公共 homeserver——无论哪种方式,你都保持对通信的控制权。机器人通过 `mautrix` Python SDK 连接,通过 Hermes Agent 管道(包括工具调用、记忆和推理)处理消息,并实时响应。它支持文本、文件附件、图片、音频、视频,以及可选的端对端加密(E2EE)。
Hermes 兼容任何 Matrix homeserver——Synapse、Conduit、Dendrite 或 matrix.org。
在开始设置之前,先了解大多数人最想知道的:Hermes 连接后的行为方式。
## Hermes 的行为方式
| 场景 | 行为 |
|---------|----------|
| **私聊(DM** | Hermes 响应每条消息,无需 `@提及`。每个 DM 有独立的会话。设置 `MATRIX_DM_MENTION_THREADS=true` 可在 DM 中被 `@提及` 时创建线程。 |
| **房间** | 默认情况下,Hermes 需要 `@提及` 才会响应。设置 `MATRIX_REQUIRE_MENTION=false` 或将房间 ID 添加到 `MATRIX_FREE_RESPONSE_ROOMS` 可开启自由响应模式。房间邀请会被自动接受。 |
| **线程** | Hermes 支持 Matrix 线程(MSC3440)。在线程中回复时,Hermes 会将线程上下文与主房间时间线隔离。机器人已参与的线程无需提及即可响应。 |
| **自动线程** | 默认情况下,Hermes 会为其在房间中响应的每条消息自动创建线程,以保持对话隔离。设置 `MATRIX_AUTO_THREAD=false` 可禁用此功能。设置 `MATRIX_DM_AUTO_THREAD=true`(默认 false)可同时为私聊消息自动创建线程——这与 `MATRIX_DM_MENTION_THREADS` 不同,后者仅在私聊中 @提及 Bot 时才创建线程。 |
| **多用户共享房间** | 默认情况下,Hermes 在房间内按用户隔离会话历史。同一房间中的两个人不会共享同一对话记录,除非你明确禁用该功能。 |
:::tip
机器人在被邀请时会自动加入房间。只需将机器人的 Matrix 用户邀请到任意房间,它就会加入并开始响应。
:::
### Matrix 中的会话模型
默认情况下:
- 每个 DM 有独立的会话
- 每个线程有独立的会话命名空间
- 共享房间中的每个用户在该房间内有独立的会话
这由 `config.yaml` 控制:
```yaml
group_sessions_per_user: true
```
仅当你明确希望整个房间共享一个对话时,才将其设置为 `false`
```yaml
group_sessions_per_user: false
```
共享会话在协作房间中可能有用,但也意味着:
- 用户共享上下文增长和 token 消耗
- 某人的长时间工具密集型任务会膨胀所有人的上下文
- 某人正在进行的任务可能会打断同一房间中另一人的后续操作
### 提及与线程配置
你可以通过环境变量或 `config.yaml` 配置提及和自动线程行为:
```yaml
matrix:
require_mention: true # 在房间中要求 @提及(默认:true)
free_response_rooms: # 免除提及要求的房间
- "!abc123:matrix.org"
auto_thread: true # 自动为响应创建线程(默认:true)
dm_mention_threads: false # 在 DM 中被 @提及时创建线程(默认:false)
```
或通过环境变量:
```bash
MATRIX_REQUIRE_MENTION=true
MATRIX_FREE_RESPONSE_ROOMS=!abc123:matrix.org,!def456:matrix.org
MATRIX_AUTO_THREAD=true
MATRIX_DM_MENTION_THREADS=false
MATRIX_REACTIONS=true # 默认:true——处理过程中发送 emoji 反应
```
:::tip 禁用反应
`MATRIX_REACTIONS=false` 会关闭机器人在收到消息时发布的处理生命周期 emoji 反应(👀/✅/❌)。适用于反应事件较为嘈杂或部分参与客户端不支持的房间。
:::
:::note
如果你从没有 `MATRIX_REQUIRE_MENTION` 的版本升级,机器人之前会响应房间中的所有消息。要保留该行为,请设置 `MATRIX_REQUIRE_MENTION=false`
:::
本指南将引导你完成完整的设置流程——从创建机器人账户到发送第一条消息。
## 第一步:创建机器人账户
你需要为机器人准备一个 Matrix 用户账户。有以下几种方式:
### 方式 A:在你的 Homeserver 上注册(推荐)
如果你运行自己的 homeserverSynapse、Conduit、Dendrite):
1. 使用管理员 API 或注册工具创建新用户:
```bash
# Synapse 示例
register_new_matrix_user -c /etc/synapse/homeserver.yaml http://localhost:8008
```
2. 选择一个用户名,例如 `hermes`——完整的用户 ID 将是 `@hermes:your-server.org`
### 方式 B:使用 matrix.org 或其他公共 Homeserver
1. 前往 [Element Web](https://app.element.io) 创建新账户。
2. 为机器人选择一个用户名(例如 `hermes-bot`)。
### 方式 C:使用你自己的账户
你也可以以自己的用户身份运行 Hermes。这意味着机器人以你的名义发帖——适合个人助手场景。
## 第二步:获取访问令牌
Hermes 需要访问令牌(access token)来向 homeserver 进行身份验证。有两种方式:
### 方式 A:访问令牌(推荐)
获取令牌最可靠的方式:
**通过 Element**
1. 使用机器人账户登录 [Element](https://app.element.io)。
2. 前往 **设置****帮助与关于**
3. 向下滚动并展开 **高级**——访问令牌显示在那里。
4. **立即复制。**
**通过 API**
```bash
curl -X POST https://your-server/_matrix/client/v3/login \
-H "Content-Type: application/json" \
-d '{
"type": "m.login.password",
"user": "@hermes:your-server.org",
"password": "your-password"
}'
```
响应中包含 `access_token` 字段——复制它。
:::warning[保管好你的访问令牌]
访问令牌可完全访问机器人的 Matrix 账户。切勿公开分享或提交到 Git。如果泄露,请通过注销该用户的所有会话来撤销它。
:::
### 方式 B:密码登录
你可以不提供访问令牌,而是提供机器人的用户 ID 和密码。Hermes 会在启动时自动登录。这种方式更简单,但密码会存储在你的 `.env` 文件中。
```bash
MATRIX_USER_ID=@hermes:your-server.org
MATRIX_PASSWORD=your-password
```
## 第三步:找到你的 Matrix 用户 ID
Hermes Agent 使用你的 Matrix 用户 ID 来控制谁可以与机器人交互。Matrix 用户 ID 的格式为 `@username:server`
查找方式:
1. 打开 [Element](https://app.element.io)(或你偏好的 Matrix 客户端)。
2. 点击你的头像 → **设置**
3. 你的用户 ID 显示在个人资料顶部(例如 `@alice:matrix.org`)。
:::tip
Matrix 用户 ID 始终以 `@` 开头,并包含 `:` 后跟服务器名称。例如:`@alice:matrix.org``@bob:your-server.com`
:::
## 第四步:配置 Hermes Agent
### 方式 A:交互式设置(推荐)
运行引导式设置命令:
```bash
hermes gateway setup
```
在提示时选择 **Matrix**,然后按提示提供你的 homeserver URL、访问令牌(或用户 ID + 密码)以及允许的用户 ID。
### 方式 B:手动配置
将以下内容添加到你的 `~/.hermes/.env` 文件:
**使用访问令牌:**
```bash
# 必填
MATRIX_HOMESERVER=https://matrix.example.org
MATRIX_ACCESS_TOKEN=***
# 可选:用户 ID(如省略则从令牌自动检测)
# MATRIX_USER_ID=@hermes:matrix.example.org
# 安全:限制可与机器人交互的用户
MATRIX_ALLOWED_USERS=@alice:matrix.example.org
# 多个允许用户(逗号分隔)
# MATRIX_ALLOWED_USERS=@alice:matrix.example.org,@bob:matrix.example.org
```
**使用密码登录:**
```bash
# 必填
MATRIX_HOMESERVER=https://matrix.example.org
MATRIX_USER_ID=@hermes:matrix.example.org
MATRIX_PASSWORD=***
# 安全
MATRIX_ALLOWED_USERS=@alice:matrix.example.org
```
`~/.hermes/config.yaml` 中的可选行为设置:
```yaml
group_sessions_per_user: true
```
- `group_sessions_per_user: true` 在共享房间内保持每个参与者的上下文隔离
### 启动 Gateway
配置完成后,启动 Matrix gateway
```bash
hermes gateway
```
机器人应在几秒内连接到你的 homeserver 并开始同步。发送一条消息——DM 或机器人已加入的房间——进行测试。
:::tip
你可以在后台运行 `hermes gateway`,或将其作为 systemd 服务以持续运行。详情请参阅部署文档。
:::
## 端对端加密(E2EE
Hermes 支持 Matrix 端对端加密,你可以在加密房间中与机器人聊天。
### 前提条件
E2EE 需要带有加密扩展的 `mautrix` 库以及 `libolm` C 库:
```bash
# 安装带 E2EE 支持的 mautrix
pip install 'mautrix[encryption]'
# 或通过 hermes extras 安装
pip install 'hermes-agent[matrix]'
```
你还需要在系统上安装 `libolm`
```bash
# Debian/Ubuntu
sudo apt install libolm-dev
# macOS
brew install libolm
# Fedora
sudo dnf install libolm-devel
```
### 启用 E2EE
`~/.hermes/.env` 中添加:
```bash
MATRIX_ENCRYPTION=true
```
启用 E2EE 后,Hermes 会:
- 将加密密钥存储在 `~/.hermes/platforms/matrix/store/`(旧版安装:`~/.hermes/matrix/store/`
- 在首次连接时上传设备密钥
- 自动解密传入消息并加密传出消息
- 被邀请时自动加入加密房间
### 交叉签名验证(推荐)
如果你的 Matrix 账户启用了交叉签名(Element 中的默认设置),请设置恢复密钥,以便机器人在启动时自签其设备。若不设置,其他 Matrix 客户端在设备密钥轮换后可能拒绝与机器人共享加密会话。
```bash
MATRIX_RECOVERY_KEY=EsT... 你的恢复密钥
```
**查找位置:** 在 Element 中,前往 **设置****安全与隐私****加密** → 你的恢复密钥(也称为"安全密钥")。这是你首次设置交叉签名时被要求保存的密钥。
每次启动时,如果设置了 `MATRIX_RECOVERY_KEY`Hermes 会从 homeserver 的安全密钥存储中导入交叉签名密钥并对当前设备进行签名。此操作是幂等的,可以永久启用。
:::warning[删除加密存储]
如果你删除了 `~/.hermes/platforms/matrix/store/crypto.db`,机器人将失去其加密身份。仅使用相同的设备 ID 重启**不能**完全恢复——homeserver 仍持有使用旧身份密钥签名的一次性密钥,对等方无法建立新的 Olm 会话。
Hermes 在启动时会检测到此情况并拒绝启用 E2EE,日志显示:`device XXXX has stale one-time keys on the server signed with a previous identity key`
**最简恢复方式:生成新的访问令牌**(获得一个没有过期密钥历史的全新设备 ID)。请参阅下方"从带有 E2EE 的旧版本升级"章节。这是最可靠的路径,无需操作 homeserver 数据库。
**手动恢复**(高级——保留相同设备 ID):
1. 停止 Synapse 并从其数据库中删除旧设备:
```bash
sudo systemctl stop matrix-synapse
sudo sqlite3 /var/lib/matrix-synapse/homeserver.db "
DELETE FROM e2e_device_keys_json WHERE device_id = 'DEVICE_ID' AND user_id = '@hermes:your-server';
DELETE FROM e2e_one_time_keys_json WHERE device_id = 'DEVICE_ID' AND user_id = '@hermes:your-server';
DELETE FROM e2e_fallback_keys_json WHERE device_id = 'DEVICE_ID' AND user_id = '@hermes:your-server';
DELETE FROM devices WHERE device_id = 'DEVICE_ID' AND user_id = '@hermes:your-server';
"
sudo systemctl start matrix-synapse
```
或通过 Synapse 管理员 API(注意 URL 编码的用户 ID):
```bash
curl -X DELETE -H "Authorization: Bearer ADMIN_TOKEN" \
'https://your-server/_synapse/admin/v2/users/%40hermes%3Ayour-server/devices/DEVICE_ID'
```
注意:通过管理员 API 删除设备也可能使关联的访问令牌失效。之后你可能需要生成新令牌。
2. 删除本地加密存储并重启 Hermes:
```bash
rm -f ~/.hermes/platforms/matrix/store/crypto.db*
# 重启 hermes
```
其他 Matrix 客户端(Element、matrix-commander)可能缓存了旧的设备密钥。恢复后,在 Element 中输入 `/discardsession` 以强制与机器人建立新的加密会话。
:::
:::info
如果未安装 `mautrix[encryption]` 或缺少 `libolm`,机器人会自动回退到普通(未加密)客户端。你会在日志中看到警告。
:::
## 主房间
你可以指定一个"主房间",机器人在此发送主动消息(例如 cron 任务输出、提醒和通知)。有两种设置方式:
### 使用斜杠命令
在机器人所在的任意 Matrix 房间中输入 `/sethome`。该房间即成为主房间。
### 手动配置
在 `~/.hermes/.env` 中添加:
```bash
MATRIX_HOME_ROOM=!abc123def456:matrix.example.org
```
## 房间白名单(`allowed_rooms`
将机器人限制在固定的 Matrix 房间集合中。设置后,机器人**仅**在 ID 出现在列表中的房间响应——来自其他房间的消息会被静默忽略,即使提及了机器人。
**私聊(DM 房间)不受此过滤器限制**,因此授权用户始终可以一对一联系机器人。
```yaml
matrix:
allowed_rooms:
- "!abc123def456:matrix.example.org"
- "!opsroom789:matrix.example.org"
```
或通过环境变量(逗号分隔):
```bash
MATRIX_ALLOWED_ROOMS="!abc123def456:matrix.example.org,!opsroom789:matrix.example.org"
```
行为说明:
- 空值/未设置 → 无限制(默认)。
- 非空 → 房间 ID 必须在列表中。该检查在所有其他门控(提及要求、发送者白名单等)**之前**运行。
- 使用房间的**内部 ID**`!abc...:server`),而非别名(`#room:server`)。你可以在 Element 中通过 房间 → 设置 → 高级 找到房间的内部 ID。
另请参阅:[管理员/用户斜杠命令分离](../../reference/slash-commands.md#permissions-and-adminuser-split)。
:::tip
查找房间 ID:在 Element 中,进入房间 → **设置** → **高级** → **内部房间 ID**(以 `!` 开头)。
:::
## 故障排查
### 机器人不响应消息
**原因**:机器人未加入房间,或 `MATRIX_ALLOWED_USERS` 中不包含你的用户 ID。
**解决方法**:邀请机器人进入房间——它会在收到邀请时自动加入。确认你的用户 ID 在 `MATRIX_ALLOWED_USERS` 中(使用完整的 `@user:server` 格式)。重启 gateway。
### 机器人加入房间但静默丢弃所有消息(时钟偏差)
**原因**:主机系统时钟超前于实际时间。Matrix 适配器应用了 5 秒启动宽限过滤器(`event_ts < startup_ts - 5`)以忽略初始同步中重放的事件。当系统时钟超前时,每个传入事件看起来都"早于启动时间",在到达消息处理器之前就被丢弃——机器人看起来已连接但从不回复。参见 [#12614](https://github.com/NousResearch/hermes-agent/issues/12614)。
**症状**Gateway 日志显示 `Matrix: dropped N live events as 'too old' more than 30s after startup`。
**解决方法**:使用 NTP 同步主机时钟并重启机器人:
```bash
# Debian/Ubuntu
sudo timedatectl set-ntp true
timedatectl status # 确认 "System clock synchronized: yes"
# macOS
sudo sntp -sS time.apple.com
```
### 启动时出现"身份验证失败"/"whoami 失败"
**原因**:访问令牌或 homeserver URL 不正确。
**解决方法**:确认 `MATRIX_HOMESERVER` 指向你的 homeserver(包含 `https://`,无尾部斜杠)。检查 `MATRIX_ACCESS_TOKEN` 是否有效——用 curl 测试:
```bash
curl -H "Authorization: Bearer YOUR_TOKEN" \
https://your-server/_matrix/client/v3/account/whoami
```
如果返回你的用户信息,令牌有效。如果返回错误,请生成新令牌。
### "mautrix 未安装"错误
**原因**:未安装 `mautrix` Python 包。
**解决方法**:安装它:
```bash
pip install 'mautrix[encryption]'
```
或通过 Hermes extras
```bash
pip install 'hermes-agent[matrix]'
```
### 加密错误/"无法解密事件"
**原因**:缺少加密密钥、未安装 `libolm`,或机器人设备未被信任。
**解决方法**
1. 确认系统上已安装 `libolm`(参见上方 E2EE 章节)。
2. 确保 `.env` 中设置了 `MATRIX_ENCRYPTION=true`。
3. 在你的 Matrix 客户端(Element)中,进入机器人的个人资料 → 会话 → 验证/信任机器人的设备。
4. 如果机器人刚加入加密房间,它只能解密*加入后*发送的消息。更早的消息无法访问。
### 从带有 E2EE 的旧版本升级
:::tip
如果你同时手动删除了 `crypto.db`,请参阅 E2EE 章节中的"删除加密存储"警告——还需要额外步骤来清除 homeserver 上的过期一次性密钥。
:::
如果你之前使用 `MATRIX_ENCRYPTION=true` 运行 Hermes,并正在升级到使用新的基于 SQLite 的加密存储的版本,机器人的加密身份已发生变化。你的 Matrix 客户端(Element)可能缓存了旧的设备密钥,并拒绝与机器人共享加密会话。
**症状**:机器人连接并在日志中显示"E2EE 已启用",但所有消息显示"无法解密事件",机器人从不响应。
**发生了什么**:旧的加密状态(来自之前的 `matrix-nio` 或基于序列化的 `mautrix` 后端)与新的 SQLite 加密存储不兼容。机器人创建了全新的加密身份,但你的 Matrix 客户端仍缓存了旧密钥,不会与密钥已更改的设备共享房间的加密会话。这是 Matrix 的安全特性——客户端将同一设备的身份密钥变更视为可疑行为。
**解决方法**(一次性迁移):
1. **生成新的访问令牌**以获得全新的设备 ID。最简单的方式:
```bash
curl -X POST https://your-server/_matrix/client/v3/login \
-H "Content-Type: application/json" \
-d '{
"type": "m.login.password",
"identifier": {"type": "m.id.user", "user": "@hermes:your-server.org"},
"password": "***",
"initial_device_display_name": "Hermes Agent"
}'
```
复制新的 `access_token` 并更新 `~/.hermes/.env` 中的 `MATRIX_ACCESS_TOKEN`。
2. **删除旧的加密状态**
```bash
rm -f ~/.hermes/platforms/matrix/store/crypto.db
rm -f ~/.hermes/platforms/matrix/store/crypto_store.*
```
3. **设置恢复密钥**(如果你使用交叉签名——大多数 Element 用户都使用)。在 `~/.hermes/.env` 中添加:
```bash
MATRIX_RECOVERY_KEY=EsT... 你的恢复密钥
```
这让机器人在启动时使用交叉签名密钥自签,使 Element 立即信任新设备。若不设置,Element 可能将新设备视为未验证并拒绝共享加密会话。在 Element 的 **设置** → **安全与隐私** → **加密** 中找到你的恢复密钥。
4. **强制你的 Matrix 客户端轮换加密会话**。在 Element 中,打开与机器人的 DM 房间并输入 `/discardsession`。这会强制 Element 创建新的加密会话并与机器人的新设备共享。
5. **重启 gateway**
```bash
hermes gateway run
```
如果设置了 `MATRIX_RECOVERY_KEY`,你应在日志中看到 `Matrix: cross-signing verified via recovery key`。
6. **发送新消息**。机器人应能正常解密并响应。
:::note
迁移后,升级*之前*发送的消息无法解密——旧的加密密钥已丢失。这只影响过渡期;新消息可正常工作。
:::
:::tip
**新安装不受影响。** 此迁移仅在你之前使用旧版 Hermes 配置了可用的 E2EE 并正在升级时才需要。
**为什么需要新的访问令牌?** 每个 Matrix 访问令牌绑定到特定的设备 ID。使用相同设备 ID 但新的加密密钥会导致其他 Matrix 客户端不信任该设备(它们将身份密钥的变更视为潜在的安全漏洞)。新的访问令牌获得一个没有过期密钥历史的新设备 ID,其他客户端会立即信任它。
:::
## 代理模式(macOS 上的 E2EE
Matrix E2EE 需要 `libolm`,而该库无法在 macOS ARM64Apple Silicon)上编译。`hermes-agent[matrix]` extra 仅限 Linux。如果你在 macOS 上,代理模式允许你在 Linux 虚拟机的 Docker 容器中运行 E2EE,而实际的 agent 在 macOS 上原生运行,可完整访问你的本地文件、记忆和技能。
### 工作原理
```
macOS(主机):
└─ hermes gateway
├─ api_server 适配器 ← 监听 0.0.0.0:8642
├─ AIAgent ← 单一数据源
├─ 会话、记忆、技能
└─ 本地文件访问(Obsidian、项目等)
Linux 虚拟机(Docker):
└─ hermes gateway(代理模式)
├─ Matrix 适配器 ← E2EE 解密/加密
└─ HTTP 转发 → macOS:8642/v1/chat/completions
(无 LLM API 密钥,无 agent,无推理)
```
Docker 容器仅处理 Matrix 协议和 E2EE。消息到达时,容器解密消息并通过标准 HTTP 请求将文本转发给主机。主机运行 agent、调用工具、生成响应并流式返回。容器加密响应并发送到 Matrix。所有会话统一——CLI、Matrix、Telegram 及其他平台共享相同的记忆和对话历史。
### 第一步:配置主机(macOS)
启用 API 服务器,使主机接受来自 Docker 容器的请求。
在 `~/.hermes/.env` 中添加:
```bash
API_SERVER_ENABLED=true
API_SERVER_KEY=your-secret-key-here
API_SERVER_HOST=0.0.0.0
```
- `API_SERVER_HOST=0.0.0.0` 绑定到所有接口,使 Docker 容器可以访问。
- `API_SERVER_KEY` 是非回环绑定的必填项。请选择一个强随机字符串。
- API 服务器默认运行在端口 8642(如需更改,使用 `API_SERVER_PORT`)。
启动 gateway
```bash
hermes gateway
```
你应该看到 API 服务器与其他已配置的平台一起启动。从虚拟机验证其可达性:
```bash
# 从 Linux 虚拟机
curl http://<mac-ip>:8642/health
```
### 第二步:配置 Docker 容器(Linux 虚拟机)
容器需要 Matrix 凭据和代理 URL。它**不需要** LLM API 密钥。
**`docker-compose.yml`**
```yaml
services:
hermes-matrix:
build: .
environment:
# Matrix 凭据
MATRIX_HOMESERVER: "https://matrix.example.org"
MATRIX_ACCESS_TOKEN: "syt_..."
MATRIX_ALLOWED_USERS: "@you:matrix.example.org"
MATRIX_ENCRYPTION: "true"
MATRIX_DEVICE_ID: "HERMES_BOT"
# 代理模式——转发到主机 agent
GATEWAY_PROXY_URL: "http://192.168.1.100:8642"
GATEWAY_PROXY_KEY: "your-secret-key-here"
volumes:
- ./matrix-store:/root/.hermes/platforms/matrix/store
```
**`Dockerfile`**
```dockerfile
FROM python:3.11-slim
RUN apt-get update && apt-get install -y libolm-dev && rm -rf /var/lib/apt/lists/*
RUN pip install 'hermes-agent[matrix]'
CMD ["hermes", "gateway"]
```
这就是整个容器。无需 OpenRouter、Anthropic 或任何推理提供商的 API 密钥。
### 第三步:同时启动
1. 先启动主机 gateway
```bash
hermes gateway
```
2. 启动 Docker 容器:
```bash
docker compose up -d
```
3. 在加密的 Matrix 房间中发送消息。容器解密消息,转发给主机,并将响应流式返回。
### 配置参考
代理模式在**容器侧**(精简 gateway)配置:
| 设置 | 说明 |
|---------|-------------|
| `GATEWAY_PROXY_URL` | 远程 Hermes API 服务器的 URL(例如 `http://192.168.1.100:8642` |
| `GATEWAY_PROXY_KEY` | 用于身份验证的 Bearer token(必须与主机上的 `API_SERVER_KEY` 匹配) |
| `gateway.proxy_url` | 与 `GATEWAY_PROXY_URL` 相同,但在 `config.yaml` 中配置 |
主机侧需要:
| 设置 | 说明 |
|---------|-------------|
| `API_SERVER_ENABLED` | 设置为 `true` |
| `API_SERVER_KEY` | Bearer token(与容器共享) |
| `API_SERVER_HOST` | 设置为 `0.0.0.0` 以允许网络访问 |
| `API_SERVER_PORT` | 端口号(默认:`8642` |
### 适用于任何平台
代理模式不限于 Matrix。任何平台适配器都可以使用它——在任意 gateway 实例上设置 `GATEWAY_PROXY_URL`,它将转发到远程 agent 而不是在本地运行。这适用于平台适配器需要在与 agent 不同的环境中运行的任何部署场景(网络隔离、E2EE 要求、资源限制)。
:::tip
会话连续性通过 `X-Hermes-Session-Id` 请求头维护。主机的 API 服务器按此 ID 跟踪会话,因此对话在消息之间持续存在,就像使用本地 agent 一样。
:::
:::note
**限制(v1):** 来自远程 agent 的工具进度消息不会被中继回来——用户只能看到流式传输的最终响应,而非单个工具调用。危险命令审批提示在主机侧处理,不会中继给 Matrix 用户。这些问题可在未来版本中解决。
:::
### 同步问题/机器人落后
**原因**:长时间运行的工具执行可能延迟同步循环,或 homeserver 响应较慢。
**解决方法**:同步循环在出错时每 5 秒自动重试。检查 Hermes 日志中与同步相关的警告。如果机器人持续落后,请确保你的 homeserver 有足够的资源。
### 机器人离线
**原因**Hermes gateway 未运行,或连接失败。
**解决方法**:检查 `hermes gateway` 是否正在运行。查看终端输出中的错误消息。常见问题:homeserver URL 错误、访问令牌过期、homeserver 不可达。
### "用户不被允许"/机器人忽略你
**原因**:你的用户 ID 不在 `MATRIX_ALLOWED_USERS` 中。
**解决方法**:将你的用户 ID 添加到 `~/.hermes/.env` 中的 `MATRIX_ALLOWED_USERS` 并重启 gateway。使用完整的 `@user:server` 格式。
## 安全
:::warning
始终设置 `MATRIX_ALLOWED_USERS` 以限制可与机器人交互的用户。若不设置,gateway 默认拒绝所有用户作为安全措施。只添加你信任的人的用户 ID——授权用户可完整访问 agent 的所有功能,包括工具调用和系统访问。
:::
有关保护 Hermes Agent 部署的更多信息,请参阅[安全指南](../security.md)。
## 注意事项
- **任何 homeserver**:兼容 Synapse、Conduit、Dendrite、matrix.org 或任何符合规范的 Matrix homeserver。无需特定的 homeserver 软件。
- **联邦**:如果你在联邦 homeserver 上,机器人可以与其他服务器的用户通信——只需将他们的完整 `@user:server` ID 添加到 `MATRIX_ALLOWED_USERS`。
- **自动加入**:机器人自动接受房间邀请并加入,加入后立即开始响应。
- **媒体支持**:Hermes 可以发送和接收图片、音频、视频和文件附件。媒体通过 Matrix 内容仓库 API 上传到你的 homeserver。
- **原生语音消息(MSC3245)**:Matrix 适配器自动为传出的语音消息添加 `org.matrix.msc3245.voice` 标志。这意味着 TTS 响应和语音音频在支持 MSC3245 的 Element 及其他客户端中以**原生语音气泡**形式呈现,而非普通音频文件附件。带有 MSC3245 标志的传入语音消息也会被正确识别并路由到语音转文字转录。无需任何配置——自动生效。
@@ -0,0 +1,340 @@
---
sidebar_position: 8
title: "Mattermost"
description: "将 Hermes Agent 配置为 Mattermost 机器人"
---
# Mattermost 配置
Hermes Agent 以机器人身份集成到 Mattermost,让你可以通过私信或团队频道与 AI 助手对话。Mattermost 是一个自托管的开源 Slack 替代品——运行在你自己的基础设施上,完全掌控数据。机器人通过 Mattermost 的 REST APIv4)和 WebSocket 连接以接收实时事件,将消息通过 Hermes Agent 管道(包括工具调用、记忆和推理)处理后实时响应。支持文本、文件附件、图片和斜杠命令。
无需额外的 Mattermost 库——适配器使用 `aiohttp`,该库已作为 Hermes 的依赖项包含在内。
在开始配置之前,先了解大多数人最关心的部分:Hermes 进入你的 Mattermost 实例后的行为方式。
## Hermes 的行为方式
| 场景 | 行为 |
|---------|----------|
| **私信(DM** | Hermes 响应每一条消息,无需 `@提及`。每个私信有独立的会话。 |
| **公开/私有频道** | Hermes 仅在被 `@提及` 时响应。未被提及时,Hermes 忽略消息。 |
| **线程(Thread** | 若设置 `MATTERMOST_REPLY_MODE=thread`,Hermes 在你的消息下方以线程形式回复。线程上下文与父频道隔离。 |
| **多用户共享频道** | 默认情况下,Hermes 在频道内按用户隔离会话历史。同一频道中的两个人不会共享同一份对话记录,除非你明确禁用该设置。 |
:::tip
如果你希望 Hermes 以线程对话方式回复(嵌套在原始消息下方),请设置 `MATTERMOST_REPLY_MODE=thread`。默认值为 `off`,即在频道中发送普通消息。
:::
### Mattermost 中的会话模型
默认情况下:
- 每个私信有独立的会话
- 每个线程有独立的会话命名空间
- 共享频道中的每个用户在该频道内有独立的会话
这由 `config.yaml` 控制:
```yaml
group_sessions_per_user: true
```
仅当你明确希望整个频道共享一个对话时,才将其设为 `false`
```yaml
group_sessions_per_user: false
```
共享会话在协作频道中可能有用,但也意味着:
- 用户共享上下文增长和 token 消耗
- 一个人的长时间重度工具调用任务会使所有人的上下文膨胀
- 一个人正在进行的任务可能会打断同一频道中另一个人的后续操作
本指南将带你完成完整的配置流程——从在 Mattermost 上创建机器人到发送第一条消息。
## 第一步:启用机器人账户
在创建机器人账户之前,必须先在 Mattermost 服务器上启用该功能。
1. 以**系统管理员**身份登录 Mattermost。
2. 前往**系统控制台** → **集成****机器人账户**
3. 将**启用机器人账户创建**设置为 **true**
4. 点击**保存**。
:::info
如果你没有系统管理员权限,请联系 Mattermost 管理员启用机器人账户并为你创建一个。
:::
## 第二步:创建机器人账户
1. 在 Mattermost 中,点击左上角的 **☰** 菜单 → **集成****机器人账户**
2. 点击**添加机器人账户**。
3. 填写详细信息:
- **用户名**:例如 `hermes`
- **显示名称**:例如 `Hermes Agent`
- **描述**:可选
- **角色**`Member` 即可
4. 点击**创建机器人账户**。
5. Mattermost 将显示**机器人 token**。**立即复制。**
:::warning[Token 仅显示一次]
机器人 token 仅在创建机器人账户时显示一次。如果丢失,需要在机器人账户设置中重新生成。切勿公开分享你的 token 或将其提交到 Git——任何持有此 token 的人都能完全控制该机器人。
:::
将 token 保存在安全的地方(例如密码管理器)。第五步中会用到它。
:::tip
你也可以使用**个人访问 token** 代替机器人账户。前往**个人资料** → **安全****个人访问 Token****创建 Token**。如果你希望 Hermes 以你自己的用户身份发帖而非独立的机器人用户,这种方式很有用。
:::
## 第三步:将机器人添加到频道
机器人需要成为你希望它响应的频道的成员:
1. 打开你希望添加机器人的频道。
2. 点击频道名称 → **添加成员**
3. 搜索你的机器人用户名(例如 `hermes`)并添加。
对于私信,直接与机器人开启私信即可——它将立即能够响应。
## 第四步:查找你的 Mattermost 用户 ID
Hermes Agent 使用你的 Mattermost 用户 ID 来控制谁可以与机器人交互。查找方式:
1. 点击左上角的**头像** → **个人资料**
2. 用户 ID 显示在个人资料对话框中——点击即可复制。
你的用户 ID 是一个 26 位字母数字字符串,例如 `3uo8dkh1p7g1mfk49ear5fzs5c`
:::warning
你的用户 ID **不是**你的用户名。用户名是 `@` 后面显示的内容(例如 `@alice`)。用户 ID 是 Mattermost 内部使用的长字母数字标识符。
:::
**替代方法**:你也可以通过 API 获取用户 ID:
```bash
curl -H "Authorization: Bearer YOUR_TOKEN" \
https://your-mattermost-server/api/v4/users/me | jq .id
```
:::tip
要获取**频道 ID**:点击频道名称 → **查看信息**。频道 ID 显示在信息面板中。如果你想手动设置主频道,需要用到它。
:::
## 第五步:配置 Hermes Agent
### 方式 A:交互式配置(推荐)
运行引导式配置命令:
```bash
hermes gateway setup
```
在提示时选择 **Mattermost**,然后按提示粘贴你的服务器 URL、机器人 token 和用户 ID。
### 方式 B:手动配置
在你的 `~/.hermes/.env` 文件中添加以下内容:
```bash
# 必填
MATTERMOST_URL=https://mm.example.com
MATTERMOST_TOKEN=***
MATTERMOST_ALLOWED_USERS=3uo8dkh1p7g1mfk49ear5fzs5c
# 多个允许的用户(逗号分隔)
# MATTERMOST_ALLOWED_USERS=3uo8dkh1p7g1mfk49ear5fzs5c,8fk2jd9s0a7bncm1xqw4tp6r3e
# 可选:回复模式(thread 或 off,默认:off
# MATTERMOST_REPLY_MODE=thread
# 可选:无需 @提及 即可响应(默认:true = 需要提及)
# MATTERMOST_REQUIRE_MENTION=false
# 可选:机器人无需 @提及 即可响应的频道(逗号分隔的频道 ID)
# MATTERMOST_FREE_RESPONSE_CHANNELS=channel_id_1,channel_id_2
```
`~/.hermes/config.yaml` 中的可选行为设置:
```yaml
group_sessions_per_user: true
```
- `group_sessions_per_user: true` 使每个参与者在共享频道和线程中的上下文保持隔离
### 启动 Gateway
配置完成后,启动 Mattermost gateway
```bash
hermes gateway
```
机器人应在几秒内连接到你的 Mattermost 服务器。发送一条消息——私信或在已添加机器人的频道中——进行测试。
:::tip
你可以在后台运行 `hermes gateway`,或将其配置为 systemd 服务以持续运行。详情参见部署文档。
:::
## 主频道
你可以指定一个"主频道",机器人将在此频道发送主动消息(例如 cron 任务输出、提醒和通知)。有两种设置方式:
### 使用斜杠命令
在机器人所在的任意 Mattermost 频道中输入 `/sethome`。该频道即成为主频道。
### 手动配置
在你的 `~/.hermes/.env` 中添加:
```bash
MATTERMOST_HOME_CHANNEL=abc123def456ghi789jkl012mn
```
将 ID 替换为实际的频道 ID(点击频道名称 → 查看信息 → 复制 ID)。
## 回复模式
`MATTERMOST_REPLY_MODE` 设置控制 Hermes 发布响应的方式:
| 模式 | 行为 |
|------|----------|
| `off`(默认) | Hermes 在频道中发送普通消息,与普通用户一样。 |
| `thread` | Hermes 在你的原始消息下方以线程形式回复。在大量来回交流时保持频道整洁。 |
在你的 `~/.hermes/.env` 中设置:
```bash
MATTERMOST_REPLY_MODE=thread
```
## 提及行为
默认情况下,机器人仅在频道中被 `@提及` 时响应。你可以更改此行为:
| 变量 | 默认值 | 描述 |
|----------|---------|-------------|
| `MATTERMOST_REQUIRE_MENTION` | `true` | 设为 `false` 可响应频道中的所有消息(私信始终有效)。 |
| `MATTERMOST_FREE_RESPONSE_CHANNELS` | _(无)_ | 逗号分隔的频道 ID,机器人在这些频道中无需 `@提及` 即可响应,即使 require_mention 为 true。 |
在 Mattermost 中查找频道 ID:打开频道,点击频道名称标题,在 URL 或频道详情中查找 ID。
当机器人被 `@提及` 时,提及内容会在处理前自动从消息中去除。
## 频道白名单(`allowed_channels`
将机器人限制在固定的 Mattermost 频道集合中。设置后,机器人**仅**在 ID 出现在列表中的频道响应——来自其他频道的消息将被静默忽略,即使机器人被 `@提及`
**私信不受此过滤器限制**,因此授权用户始终可以通过私信联系机器人。
```yaml
mattermost:
allowed_channels:
- "abc123def456ghi789jkl012mno" # #ops
- "xyz987uvw654rst321opq098nml" # #incident-response
```
或通过环境变量设置(逗号分隔):
```bash
MATTERMOST_ALLOWED_CHANNELS="abc123def456ghi789jkl012mno,xyz987uvw654rst321opq098nml"
```
行为说明:
- 空值/未设置 → 无限制(完全向后兼容)。
- 非空值 → 频道 ID 必须在列表中,否则消息在任何其他门控(提及要求、`MATTERMOST_FREE_RESPONSE_CHANNELS` 等)运行之前即被丢弃。
- 通过 Mattermost UI → 频道标题 → "查看信息"查找频道 ID,或从频道 URL 中读取。
另请参阅:[管理员/用户斜杠命令分离](../../reference/slash-commands.md#permissions-and-adminuser-split)。
## 故障排查
### 机器人不响应消息
**原因**:机器人不是该频道的成员,或 `MATTERMOST_ALLOWED_USERS` 中未包含你的用户 ID。
**解决方法**:将机器人添加到频道(频道名称 → 添加成员 → 搜索机器人)。确认你的用户 ID 在 `MATTERMOST_ALLOWED_USERS` 中。重启 gateway。
### 403 Forbidden 错误
**原因**:机器人 token 无效,或机器人没有在该频道发帖的权限。
**解决方法**:检查 `.env` 文件中的 `MATTERMOST_TOKEN` 是否正确。确认机器人账户未被停用。确认机器人已被添加到频道。如果使用个人访问 token,确保你的账户具有所需权限。
### WebSocket 断开连接/重连循环
**原因**:网络不稳定、Mattermost 服务器重启,或防火墙/代理对 WebSocket 连接的干扰。
**解决方法**:适配器会以指数退避方式(2s → 60s)自动重连。检查服务器的 WebSocket 配置——反向代理(nginx、Apache)需要配置 WebSocket 升级头。确认没有防火墙阻止 Mattermost 服务器上的 WebSocket 连接。
对于 nginx,确保你的配置包含:
```nginx
location /api/v4/websocket {
proxy_pass http://mattermost-backend;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 600s;
}
```
### 启动时出现"Failed to authenticate"
**原因**:token 或服务器 URL 不正确。
**解决方法**:确认 `MATTERMOST_URL` 指向你的 Mattermost 服务器(包含 `https://`,末尾无斜杠)。检查 `MATTERMOST_TOKEN` 是否有效——用 curl 测试:
```bash
curl -H "Authorization: Bearer YOUR_TOKEN" \
https://your-server/api/v4/users/me
```
如果返回机器人的用户信息,则 token 有效。如果返回错误,请重新生成 token。
### 机器人离线
**原因**Hermes gateway 未运行,或连接失败。
**解决方法**:检查 `hermes gateway` 是否正在运行。查看终端输出中的错误信息。常见问题:URL 错误、token 过期、Mattermost 服务器无法访问。
### "User not allowed"/机器人忽略你
**原因**:你的用户 ID 不在 `MATTERMOST_ALLOWED_USERS` 中。
**解决方法**:将你的用户 ID 添加到 `~/.hermes/.env` 中的 `MATTERMOST_ALLOWED_USERS`,然后重启 gateway。注意:用户 ID 是 26 位字母数字字符串,不是你的 `@用户名`
## 按频道设置 Prompt
为特定 Mattermost 频道分配临时系统 prompt(提示词)。该 prompt 在每次对话轮次中于运行时注入——从不持久化到对话记录——因此更改立即生效。
```yaml
mattermost:
channel_prompts:
"channel_id_abc123": |
You are a research assistant. Focus on academic sources,
citations, and concise synthesis.
"channel_id_def456": |
Code review mode. Be precise about edge cases and
performance implications.
```
键为 Mattermost 频道 ID(在频道 URL 或通过 API 查找)。匹配频道中的所有消息都会将该 prompt 作为临时系统指令注入。
## 安全
:::warning
务必设置 `MATTERMOST_ALLOWED_USERS` 以限制谁可以与机器人交互。若未设置,gateway 默认拒绝所有用户作为安全措施。仅添加你信任的人的用户 ID——授权用户对 agent 的所有功能拥有完整访问权限,包括工具调用和系统访问。
:::
有关保护 Hermes Agent 部署的更多信息,请参阅[安全指南](../security.md)。
## 说明
- **自托管友好**:适用于任何自托管的 Mattermost 实例。无需 Mattermost Cloud 账户或订阅。
- **无额外依赖**:适配器使用 `aiohttp` 处理 HTTP 和 WebSocket,该库已包含在 Hermes Agent 中。
- **兼容团队版**:同时支持 Mattermost 团队版(免费)和企业版。
@@ -0,0 +1,137 @@
---
sidebar_position: 23
title: "Microsoft Graph Webhook 监听器"
description: "在 Hermes 中接收 Microsoft Graph 变更通知(会议、日历、聊天等)"
---
# Microsoft Graph Webhook 监听器
`msgraph_webhook` gateway 平台是一个入站事件监听器。它是 Hermes 接收来自 Microsoft Graph 的**变更通知**的方式——"一个 Teams 会议已结束"、"此聊天中收到了一条新消息"、"此日历事件已更新"。与 `teams` 平台(用户向其发送消息的聊天机器人)不同——此平台是 M365 告知 Hermes 某事已发生,而非来自用户的消息。
目前主要的消费者是 Teams 会议摘要流水线:Graph 在会议产生转录文本时发出通知,流水线获取该内容,Hermes 将摘要发回 Teams。其他 Graph 资源(`/chats/.../messages``/users/.../events`)使用同一监听器——流水线消费者通过各自的 PR 接入。
## 前提条件
- Microsoft Graph 应用凭据——[注册 Microsoft Graph 应用程序](/guides/microsoft-graph-app-registration)
- 一个 Microsoft Graph 可访问的**公开 HTTPS URL**(Graph 不会调用私有端点)。测试时可使用 dev tunnel;生产环境需要具有有效证书的真实域名。
- 一个强共享密钥,用作 `clientState` 的值。使用 `openssl rand -hex 32` 生成,并以 `MSGRAPH_WEBHOOK_CLIENT_STATE` 写入 `~/.hermes/.env`
## 快速开始
最小化 `~/.hermes/config.yaml`
```yaml
platforms:
msgraph_webhook:
enabled: true
extra:
port: 8646
client_state: "replace-with-a-strong-secret"
accepted_resources:
- "communications/onlineMeetings"
```
或通过 `~/.hermes/.env` 中的环境变量(启动时自动合并):
```bash
MSGRAPH_WEBHOOK_ENABLED=true
MSGRAPH_WEBHOOK_PORT=8646
MSGRAPH_WEBHOOK_CLIENT_STATE=<generate-with-openssl-rand-hex-32>
MSGRAPH_WEBHOOK_ACCEPTED_RESOURCES=communications/onlineMeetings
```
启动 gateway`hermes gateway run`。监听器暴露以下端点:
- `POST /msgraph/webhook` — 来自 Graph 的变更通知
- `GET /msgraph/webhook?validationToken=...` — Graph 订阅验证握手
- `GET /health` — 就绪探针,包含已接受/重复计数器
将监听器公开暴露(反向代理、dev tunnel、ingress)。Graph 订阅的通知 URL 为你的公开 HTTPS 源地址加上 `/msgraph/webhook`
```
https://ops.example.com/msgraph/webhook
```
## 配置
所有设置位于 `platforms.msgraph_webhook.extra` 下:
| 设置 | 默认值 | 说明 |
|------|--------|------|
| `host` | `0.0.0.0` | HTTP 监听器的绑定地址。 |
| `port` | `8646` | 绑定端口。 |
| `webhook_path` | `/msgraph/webhook` | Graph POST 请求的 URL 路径。 |
| `health_path` | `/health` | 就绪端点。 |
| `client_state` | — | Graph 在每条通知中回传的共享密钥。使用 `hmac.compare_digest` 进行比较——使用 `openssl rand -hex 32` 生成。 |
| `accepted_resources` | `[]`(接受全部) | Graph 资源路径/模式的白名单。末尾 `*` 作为前缀匹配。可容忍开头的 `/`。示例:`["communications/onlineMeetings", "chats/*/messages"]`。 |
| `max_seen_receipts` | `5000` | 通知 ID 的去重缓存大小。达到上限时淘汰最旧的条目。 |
| `allowed_source_cidrs` | `[]`(允许全部) | 可选的源 IP 白名单。见下文。 |
大多数设置也有对应的环境变量(`MSGRAPH_WEBHOOK_*`),在 gateway 启动时合并到配置中(例外是 `host`,它仅可通过配置文件设置——参见上方说明)——参见[环境变量参考](/reference/environment-variables#microsoft-graph-teams-meetings)。
## 安全加固
### clientState 是主要的认证检查
每条 Graph 通知都包含你在订阅时注册的 `clientState` 字符串。监听器使用时序安全比较拒绝任何 `clientState` 不匹配的通知。这是 Microsoft 的官方机制——请将该值视为强共享密钥。
如果未设置 `client_state`,监听器将接受所有格式正确的 POST 请求。**生产环境中请勿在未设置的情况下运行。**
### 源 IP 白名单(生产部署)
在生产环境中,将监听器限制为 Microsoft 公布的 Graph webhook 源 IP 范围。Microsoft 在 [Office 365 IP 地址和 URL Web 服务](https://learn.microsoft.com/en-us/microsoft-365/enterprise/urls-and-ip-address-ranges)中记录了出口范围。配置方式如下:
```yaml
platforms:
msgraph_webhook:
enabled: true
extra:
client_state: "..."
allowed_source_cidrs:
- "52.96.0.0/14"
- "52.104.0.0/14"
# ...添加当前 Microsoft 365 "Common" + "Teams" 类别的出口范围
```
或通过环境变量:
```bash
MSGRAPH_WEBHOOK_ALLOWED_SOURCE_CIDRS="52.96.0.0/14,52.104.0.0/14"
```
空白名单 = 接受来自任何地址的请求(默认;保留 dev tunnel 工作流)。无效的 CIDR 字符串会记录警告并被忽略。**请每季度审查 Microsoft IP 列表**——它会变更。
### HTTPS 终止
监听器使用纯 HTTP。在你的反向代理(Caddy、Nginx、Cloudflare Tunnel、AWS ALB)处终止 TLS,并通过本地网络代理到监听器。Graph 拒绝向非 HTTPS 端点投递,因此来自 Graph 的未加密流量不存在可达路径。
### 响应规范
成功时,监听器返回 `202 Accepted` 且响应体为空——内部计数器不会出现在响应中。运维人员可通过 `/health` 观察计数。
状态码说明:
| 结果 | 状态码 |
|------|--------|
| 通知已接受或已去重 | 202 |
| 验证握手(带 `validationToken` 的 GET | 200(原样回传 token |
| 批次中所有条目的 clientState 均失败 | 403 |
| JSON 格式错误 / 缺少 `value` 数组 / 未知资源 | 400 |
| 源 IP 不在白名单中 | 403 |
| 不带 `validationToken` 的裸 GET | 400 |
## 故障排查
| 问题 | 检查项 |
|------|--------|
| Graph 订阅验证失败 | 公开 URL 可访问,`/msgraph/webhook` 路径匹配,带 `validationToken` 的 GET 在 10 秒内以 `text/plain` 原样回传 token。 |
| 通知 POST 成功但无内容被摄取 | `client_state` 与订阅时注册的值一致。如值已漂移,重新运行 `openssl rand -hex 32` 并创建新订阅。检查 `accepted_resources` 是否包含 Graph 发送的资源路径。 |
| 每条通知均返回 403 | `clientState` 不匹配(伪造,或订阅时使用了不同的值)。使用 `hermes teams-pipeline subscribe --client-state "$MSGRAPH_WEBHOOK_CLIENT_STATE" ...` 重新创建订阅(随流水线运行时 PR 一同发布)。 |
| 监听器已启动,但 `curl http://localhost:8646/health` 挂起 | 端口绑定冲突。检查 `ss -tlnp \| grep 8646`,如有需要更改 `port:`。 |
| 来自 Microsoft 的真实 Graph 请求返回 403 | 源 IP 白名单范围过窄。临时移除 `allowed_source_cidrs`,确认流量正常后,将列表扩展至包含当前 Microsoft 出口范围。 |
## 相关文档
- [注册 Microsoft Graph 应用程序](/guides/microsoft-graph-app-registration) — Azure 应用注册前提条件
- [环境变量 → Microsoft Graph](/reference/environment-variables#microsoft-graph-teams-meetings) — 完整环境变量列表
- [Microsoft Teams 机器人设置](/user-guide/messaging/teams) — 允许用户在 Teams 中与 Hermes 聊天的另一平台
@@ -0,0 +1,155 @@
# ntfy
[ntfy](https://ntfy.sh/) 是一个简单的基于 HTTP 的发布-订阅通知服务。它可与 `ntfy.sh` 上的免费公共服务器或任何自托管实例配合使用,支持任何能发起 HTTP 请求的客户端——手机、浏览器、脚本、手表。
ntfy 是 Hermes 的轻量级推送渠道的理想选择:通过 [ntfy 移动应用](https://ntfy.sh/docs/subscribe/phone/) 订阅一个 topic(主题),向该 topic 发送消息与 agent 对话,然后在手机上收到回复。
## 前提条件
- 一个 topic 名称(任意唯一字符串——`hermes-myname-2026` 即可)
- 已安装 [ntfy 移动应用](https://ntfy.sh/docs/subscribe/phone/) 并订阅该 topic
- 可选:自托管的 ntfy 服务器,或用于私有/保留 topic 的 `ntfy.sh` 账户 token
仅此而已。无需 SDK、无需守护进程、无需 Node.js。适配器使用 `httpx`,该库已是 Hermes 的依赖项。
## 配置 Hermes
### 通过设置向导
```bash
hermes gateway setup
```
选择 **ntfy** 并按提示操作。
### 通过环境变量
将以下内容添加到 `~/.hermes/.env`
```
NTFY_TOPIC=hermes-myname-2026
NTFY_ALLOWED_USERS=hermes-myname-2026
NTFY_HOME_CHANNEL=hermes-myname-2026
```
| 变量 | 是否必填 | 说明 |
|---|---|---|
| `NTFY_TOPIC` | 是 | 要订阅的 topic(接收消息) |
| `NTFY_SERVER_URL` | 可选 | 服务器 URL(默认:`https://ntfy.sh`)——指向自托管 ntfy 以保护隐私 |
| `NTFY_TOKEN` | 可选 | Bearer token(如 `tk_xyz`)或用于 Basic 认证的 `user:pass` |
| `NTFY_PUBLISH_TOPIC` | 可选 | 用于发送回复的不同 topic(默认与 `NTFY_TOPIC` 相同) |
| `NTFY_MARKDOWN` | 可选 | 设为 `true` 以使用 `X-Markdown: true` 请求头发送回复 |
| `NTFY_ALLOWED_USERS` | 推荐 | 允许的 topic 名称(逗号分隔,视为用户 ID;见下文) |
| `NTFY_ALLOW_ALL_USERS` | 可选 | 设为 `true` 以允许所有发布者——仅在具有读取 token 的私有 topic 下安全 |
| `NTFY_HOME_CHANNEL` | 可选 | cron 任务/通知投递的默认 topic |
| `NTFY_HOME_CHANNEL_NAME` | 可选 | 主渠道的可读标签 |
## 身份模型——部署前请阅读
ntfy 没有原生的已认证用户身份。已发布消息中的 `title` 字段由**发布者控制**,可以是发布者想要的任何内容。Hermes 适配器**不**使用 `title` 进行授权——否则任何知道 topic 的发布者都可以伪造允许的用户。
相反,**topic 名称本身即为身份**。发布到该 topic 的每条消息都被视为来自同一个逻辑用户(即该 topic)。因此 `NTFY_ALLOWED_USERS` 通常就是 topic 名称本身——一个控制整个渠道访问的单条目白名单。
这意味着**任何知道 topic 的人都可以与 agent 对话**。要将其变为真正的信任边界:
- **自托管 ntfy** 并通过[访问控制](https://docs.ntfy.sh/config/#access-control)锁定 topic。只有持有读/写 token 的授权客户端才能发布。
- 或**在 ntfy.sh 上使用私有 topic**[保留 topic](https://docs.ntfy.sh/publish/#reserved-topics) 需要账户),并通过 `NTFY_TOKEN` 保护。
- 或**选择一个长且难以猜测的 topic 名称**(`hermes-7d4f9c8b-2026`),将其视为共享密钥。这是最轻量的方案,但 topic 名称可能通过日志或截图泄露。
在任何情况下,除非底层 topic 已启用访问控制,否则不要通过 ntfy 传输敏感数据。
## 快速开始——从手机与 agent 对话
1. 选择一个 topic 名称:`hermes-myname-2026`
2. 在手机上:安装 [ntfy 应用](https://ntfy.sh/docs/subscribe/phone/),点击 **+**,输入 `hermes-myname-2026`
3. 在主机上:
```bash
echo 'NTFY_TOPIC=hermes-myname-2026' >> ~/.hermes/.env
echo 'NTFY_ALLOWED_USERS=hermes-myname-2026' >> ~/.hermes/.env
hermes gateway restart
```
4. 从 ntfy 应用向该 topic 发送一条消息。agent 的回复将以推送通知的形式送达。
## 在 cron 任务中使用 ntfy
设置 `NTFY_HOME_CHANNEL` 后,cron 任务即可投递到 ntfy:
```python
cronjob(
action="create",
schedule="every 1h",
deliver="ntfy", # uses NTFY_HOME_CHANNEL
prompt="Check for alerts and summarise."
)
```
或显式指定目标 topic
```python
send_message(target="ntfy:alerts-channel", message="Done!")
```
即使 cron 在 gateway 进程外运行,此功能也有效——插件注册了一个 `standalone_sender_fn`,会自行建立 HTTP 连接。
## 自托管 ntfy
如需完全掌控:
```bash
# Docker
docker run -p 80:80 -it binwiederhier/ntfy serve
# Native
go install heckel.io/ntfy/v2@latest
ntfy serve
```
然后将 Hermes 指向该实例:
```
NTFY_SERVER_URL=https://ntfy.mydomain.com
NTFY_TOPIC=hermes
NTFY_TOKEN=tk_abc123 # if you've set up access control
```
自托管可提供 topic 访问控制、消息持久化策略、附件和 emoji 标签。参见 [ntfy 服务器文档](https://docs.ntfy.sh/install/)。
## Markdown 格式化
当发布者设置 `X-Markdown: true` 请求头时,ntfy 客户端会渲染 Markdown。要为 Hermes 的出站回复启用此功能:
```
NTFY_MARKDOWN=true
```
或在 `config.yaml` 中配置:
```yaml
platforms:
ntfy:
extra:
markdown: true
```
移动应用支持 CommonMark 的子集——粗体、斜体、列表、链接、围栏代码块。确切支持范围参见 [ntfy 的 Markdown 文档](https://docs.ntfy.sh/publish/#markdown-formatting)。
## 仅出站设置(只推送通知,不接收消息)
如果只希望 Hermes *推送*通知到 ntfy(cron 摘要、告警),而不接受任何回复消息,可将 `NTFY_TOPIC` 和 `NTFY_PUBLISH_TOPIC` 设为相同值,并完全省略 `NTFY_ALLOWED_USERS`。没有白名单时,agent 不会响应任何入站消息——手机可收到推送,但对话是单向的。
## 限制
- **消息大小**:ntfy 将消息体上限设为 4096 个字符。超出时 Hermes 会截断并发出警告。
- **无输入状态指示**:协议不支持此功能;`send_typing` 为空操作。
- **无线程或附件**:ntfy 是纯推送通知。长回复保留在消息体中,不会分线程展开。
- **无原生用户身份**:参见上文的身份模型章节。
## 故障排查
**认证失败 / 401** — `NTFY_TOKEN` 有误,或该 token 对此 topic 没有发布/订阅权限。适配器在收到 401 时会停止重连循环,gateway 运行时状态将显示 `fatal: ntfy_unauthorized`。修正 token 后重启 gateway。
**Topic 未找到 / 404** — `NTFY_TOPIC` 在所配置的服务器上不存在。对于 ntfy.sh,topic 在首次发布时自动创建,因此 404 意味着你指向的自托管服务器尚未创建该 topic。适配器会停止重连循环并显示 `fatal: ntfy_topic_not_found`。
**已连接但收不到消息** — 检查 `NTFY_ALLOWED_USERS` 是否包含 topic 名称本身。在 ntfy 的身份模型中,topic 即用户;白名单为空时所有消息都会被拒绝。
**每 60 秒重连一次** — 流式 keepalive 默认为 55 秒;ntfy 可能存在间歇性网络问题。适配器采用指数退避(2 → 5 → 10 → 30 → 60 秒),一旦流保持存活 ≥60 秒则重置为 0。
@@ -0,0 +1,334 @@
---
sidebar_position: 8
title: "Open WebUI"
description: "通过 OpenAI 兼容 API 服务器将 Open WebUI 连接到 Hermes Agent"
---
# Open WebUI 集成
[Open WebUI](https://github.com/open-webui/open-webui)(126k★)是最受欢迎的自托管 AI 聊天界面。借助 Hermes Agent 内置的 API 服务器,你可以将 Open WebUI 用作 agent 的精美 Web 前端——完整支持对话管理、用户账户和现代聊天界面。
## 架构
```mermaid
flowchart LR
A["Open WebUI<br/>浏览器 UI<br/>端口 3000"]
B["hermes-agent<br/>gateway API 服务器<br/>端口 8642"]
A -->|POST /v1/chat/completions| B
B -->|SSE 流式响应| A
```
Open WebUI 连接 Hermes Agent 的 API 服务器,方式与连接 OpenAI 完全相同。Hermes 使用其完整工具集——终端、文件操作、网络搜索、记忆、技能——处理请求并返回最终响应。
:::important 运行时位置
API 服务器是一个 **Hermes agent 运行时**,而非纯 LLM 代理。对于每个请求,Hermes 会在 API 服务器所在主机上创建一个服务端 `AIAgent`。工具调用在该 API 服务器运行的位置执行。
例如,如果笔记本电脑将 Open WebUI 或其他 OpenAI 兼容客户端指向远程机器上的 Hermes API 服务器,则 `pwd`、文件工具、浏览器工具、本地 MCP 工具及其他工作区工具将在远程 API 服务器主机上运行,而非在笔记本电脑上。
:::
Open WebUI 与 Hermes 之间是服务器到服务器的通信,因此此集成无需配置 `API_SERVER_CORS_ORIGINS`
## 快速设置
### 本地一键引导(macOS/Linux,无需 Docker
如果你希望在本地将 Hermes 与 Open WebUI 连接并使用可复用的启动器,请运行:
```bash
cd ~/.hermes/hermes-agent
bash scripts/setup_open_webui.sh
```
脚本执行内容:
- 确保 `~/.hermes/.env` 包含 `API_SERVER_ENABLED``API_SERVER_HOST``API_SERVER_KEY``API_SERVER_PORT``API_SERVER_MODEL_NAME`
- 重启 Hermes gateway 以启动 API 服务器
- 将 Open WebUI 安装到 `~/.local/open-webui-venv`
-`~/.local/bin/start-open-webui-hermes.sh` 写入启动器
- 在 macOS 上安装 `launchd` 用户服务;在支持 `systemd --user` 的 Linux 上安装用户服务
默认值:
- Hermes API`http://127.0.0.1:8642/v1`
- Open WebUI`http://127.0.0.1:8080`
- 向 Open WebUI 公告的模型名称:`Hermes Agent`
常用覆盖参数:
```bash
OPEN_WEBUI_NAME='My Hermes UI' \
OPEN_WEBUI_ENABLE_SIGNUP=true \
HERMES_API_MODEL_NAME='My Hermes Agent' \
bash scripts/setup_open_webui.sh
```
在 Linux 上,自动后台服务设置需要可用的 `systemd --user` 会话。如果你在无头 SSH 机器上并希望跳过服务安装,请运行:
```bash
OPEN_WEBUI_ENABLE_SERVICE=false bash scripts/setup_open_webui.sh
```
### 1. 启用 API 服务器
```bash
hermes config set API_SERVER_ENABLED true
hermes config set API_SERVER_KEY your-secret-key
```
`hermes config set` 会自动将标志路由到 `config.yaml`,将密钥路由到 `~/.hermes/.env`。如果 gateway 已在运行,请重启以使更改生效:
```bash
hermes gateway stop && hermes gateway
```
### 2. 启动 Hermes Agent gateway
```bash
hermes gateway
```
你应该看到:
```
[API Server] API server listening on http://127.0.0.1:8642
```
### 3. 验证 API 服务器可访问
```bash
curl -s http://127.0.0.1:8642/health
# {"status": "ok", ...}
curl -s -H "Authorization: Bearer your-secret-key" http://127.0.0.1:8642/v1/models
# {"object":"list","data":[{"id":"hermes-agent", ...}]}
```
如果 `/health` 失败,说明 gateway 未加载 `API_SERVER_ENABLED=true`——重启它。如果 `/v1/models` 返回 `401`,说明你的 `Authorization` 头与 `API_SERVER_KEY` 不匹配。
### 4. 启动 Open WebUI
```bash
docker run -d -p 3000:8080 \
-e OPENAI_API_BASE_URL=http://host.docker.internal:8642/v1 \
-e OPENAI_API_KEY=your-secret-key \
-e ENABLE_OLLAMA_API=false \
--add-host=host.docker.internal:host-gateway \
-v open-webui:/app/backend/data \
--name open-webui \
--restart always \
ghcr.io/open-webui/open-webui:main
```
`ENABLE_OLLAMA_API=false` 会禁用默认的 Ollama 后端,否则它会显示为空并干扰模型选择器。如果你确实在同时运行 Ollama,可以省略此参数。
首次启动需要 1530 秒:Open WebUI 在第一次启动时会下载 sentence-transformer embedding(嵌入)模型(约 150MB)。请等待 `docker logs open-webui` 输出稳定后再打开 UI。
### 5. 打开 UI
访问 **http://localhost:3000** 。创建管理员账户(第一个用户将成为管理员)。你应该能在模型下拉列表中看到你的 agent(以你的 profile 命名,默认 profile 则显示为 **hermes-agent**)。开始聊天吧!
## Docker Compose 设置
如需更持久的设置,创建 `docker-compose.yml`
```yaml
services:
open-webui:
image: ghcr.io/open-webui/open-webui:main
ports:
- "3000:8080"
volumes:
- open-webui:/app/backend/data
environment:
- OPENAI_API_BASE_URL=http://host.docker.internal:8642/v1
- OPENAI_API_KEY=your-secret-key
- ENABLE_OLLAMA_API=false
extra_hosts:
- "host.docker.internal:host-gateway"
restart: always
volumes:
open-webui:
```
然后:
```bash
docker compose up -d
```
## 通过管理员 UI 配置
如果你更倾向于通过 UI 而非环境变量配置连接:
1.**http://localhost:3000** 登录 Open WebUI
2. 点击你的**头像** → **Admin Settings**
3. 进入 **Connections**
4.**OpenAI API** 下,点击**扳手图标**Manage
5. 点击 **+ Add New Connection**
6. 填写:
- **URL**`http://host.docker.internal:8642/v1`
- **API Key**:与 Hermes 中 `API_SERVER_KEY` 完全相同的值
7. 点击**对勾**验证连接
8. **保存**
你的 agent 模型现在应出现在模型下拉列表中(以你的 profile 命名,默认 profile 则显示为 **hermes-agent**)。
:::warning
环境变量仅在 Open WebUI **首次启动**时生效。此后,连接设置存储在其内部数据库中。如需后续修改,请使用管理员 UI,或删除 Docker 卷后重新启动。
:::
## API 类型:Chat Completions 与 Responses
Open WebUI 连接后端时支持两种 API 模式:
| 模式 | 格式 | 使用场景 |
|------|--------|-------------|
| **Chat Completions**(默认) | `/v1/chat/completions` | 推荐。开箱即用。 |
| **Responses**(实验性) | `/v1/responses` | 通过 `previous_response_id` 实现服务端对话状态。 |
### 使用 Chat Completions(推荐)
这是默认模式,无需额外配置。Open WebUI 发送标准 OpenAI 格式请求,Hermes Agent 相应响应。每个请求包含完整的对话历史。
### 使用 Responses API
启用 Responses API 模式:
1. 进入 **Admin Settings****Connections****OpenAI****Manage**
2. 编辑你的 hermes-agent 连接
3.**API Type** 从 "Chat Completions" 改为 **"Responses (Experimental)"**
4. 保存
使用 Responses API 时,Open WebUI 以 Responses 格式发送请求(`input` 数组 + `instructions`),Hermes Agent 可通过 `previous_response_id` 在多轮对话中保留完整的工具调用历史。当 `stream: true` 时,Hermes 还会流式传输符合规范的 `function_call``function_call_output` 事件,这使得支持 Responses 事件渲染的客户端能够展示自定义结构化工具调用 UI。
:::note
Open WebUI 目前即使在 Responses 模式下也在客户端管理对话历史——它在每个请求中发送完整的消息历史,而非使用 `previous_response_id`。Responses 模式目前的主要优势在于结构化事件流:文本增量、`function_call``function_call_output` 事件以 OpenAI Responses SSE 事件形式到达,而非 Chat Completions 分块。
:::
## 工作原理
当你在 Open WebUI 中发送消息时:
1. Open WebUI 发送包含你的消息和对话历史的 `POST /v1/chat/completions` 请求
2. Hermes Agent 使用 API 服务器的 profile、模型/提供商配置、记忆、技能和已配置的 API 服务器工具集,在服务端创建一个 `AIAgent` 实例
3. Agent 处理你的请求——它可能在 API 服务器主机上调用工具(终端、文件操作、网络搜索等)
4. 工具执行时,**内联进度消息会流式传输到 UI**,让你实时看到 agent 的操作(例如 `` `💻 ls -la` ``、`` `🔍 Python 3.12 release` ``
5. Agent 的最终文本响应流式返回给 Open WebUI
6. Open WebUI 在聊天界面中显示响应
你的 agent 可以访问该 API 服务器 Hermes 实例所拥有的相同工具和能力。如果 API 服务器是远程的,这些工具也是远程的。
如果你今天需要工具在**本地**工作区运行,请在本地运行 Hermes 并将其指向纯 LLM 提供商或纯 OpenAI 兼容模型代理(例如 vLLM、LiteLLM、Ollama、llama.cpp、OpenAI、OpenRouter 等)。"远程大脑、本地执行"的分离运行时模式正在 [#18715](https://github.com/NousResearch/hermes-agent/issues/18715) 中跟踪;这不是当前 API 服务器的行为。
:::tip 工具进度
启用流式传输(默认)后,工具运行时你会看到简短的内联指示——工具 emoji 及其关键参数。这些内容在 agent 最终答案之前出现在响应流中,让你了解后台正在发生的事情。
:::
## 配置参考
### Hermes AgentAPI 服务器)
| 变量 | 默认值 | 描述 |
|----------|---------|-------------|
| `API_SERVER_ENABLED` | `false` | 启用 API 服务器 |
| `API_SERVER_PORT` | `8642` | HTTP 服务器端口 |
| `API_SERVER_HOST` | `127.0.0.1` | 绑定地址 |
| `API_SERVER_KEY` | _(必填)_ | 用于认证的 Bearer token(令牌)。需与 `OPENAI_API_KEY` 匹配。 |
### Open WebUI
| 变量 | 描述 |
|----------|-------------|
| `OPENAI_API_BASE_URL` | Hermes Agent 的 API URL(包含 `/v1` |
| `OPENAI_API_KEY` | 不能为空。需与你的 `API_SERVER_KEY` 匹配。 |
## 故障排查
### 下拉列表中没有模型
- **检查 URL 是否有 `/v1` 后缀**`http://host.docker.internal:8642/v1`(不只是 `:8642`
- **验证 gateway 是否运行**`curl http://localhost:8642/health` 应返回 `{"status": "ok"}`
- **检查模型列表**`curl -H "Authorization: Bearer your-secret-key" http://localhost:8642/v1/models` 应返回包含 `hermes-agent` 的列表
- **Docker 网络**:在 Docker 内部,`localhost` 指容器本身,而非你的主机。请使用 `host.docker.internal``--network=host`
- **空 Ollama 后端遮挡选择器**:如果你省略了 `ENABLE_OLLAMA_API=false`Open WebUI 会在你的 Hermes 模型上方显示一个空的 Ollama 区域。请使用 `-e ENABLE_OLLAMA_API=false` 重启容器,或在 **Admin Settings → Connections** 中禁用 Ollama。
### 连接测试通过但模型无法加载
这几乎总是因为缺少 `/v1` 后缀。Open WebUI 的连接测试只是基本的连通性检查——它不验证模型列表是否正常工作。
### 响应耗时很长
Hermes Agent 可能在生成最终响应之前执行了多次工具调用(读取文件、运行命令、搜索网络)。对于复杂查询,这是正常现象。响应会在 agent 完成后一次性出现。
### "Invalid API key" 错误
确保 Open WebUI 中的 `OPENAI_API_KEY` 与 Hermes Agent 中的 `API_SERVER_KEY` 匹配。
:::warning
Open WebUI 在首次启动后会将 OpenAI 兼容连接设置持久化到其自身数据库中。如果你在管理员 UI 中误保存了错误的密钥,仅修改环境变量是不够的——请在 **Admin Settings → Connections** 中更新或删除已保存的连接,或重置 Open WebUI 数据目录/数据库。
:::
## 多用户设置与 Profiles
要为每个用户运行独立的 Hermes 实例——各自拥有独立的配置、记忆和技能——请使用 [profiles](/user-guide/profiles)。每个 profile 在不同端口上运行自己的 API 服务器,并自动将 profile 名称作为模型名称公告给 Open WebUI。
### 1. 创建 profiles 并配置 API 服务器
`API_SERVER_*` 是环境变量,而非 YAML 配置键,因此请将它们写入每个 profile 的 `.env`。选择默认平台范围之外的端口(`8644` 是 webhook 适配器,`8645` 是 wecom-callback`8646` 是 msgraph-webhook),例如 `8650+`
```bash
hermes profile create alice
cat >> ~/.hermes/profiles/alice/.env <<EOF
API_SERVER_ENABLED=true
API_SERVER_PORT=8650
API_SERVER_KEY=alice-secret
EOF
hermes profile create bob
cat >> ~/.hermes/profiles/bob/.env <<EOF
API_SERVER_ENABLED=true
API_SERVER_PORT=8651
API_SERVER_KEY=bob-secret
EOF
```
### 2. 启动各 gateway
```bash
hermes -p alice gateway &
hermes -p bob gateway &
```
### 3. 在 Open WebUI 中添加连接
**Admin Settings****Connections****OpenAI API****Manage** 中,为每个 profile 添加一个连接:
| 连接 | URL | API Key |
|-----------|-----|---------|
| Alice | `http://host.docker.internal:8650/v1` | `alice-secret` |
| Bob | `http://host.docker.internal:8651/v1` | `bob-secret` |
模型下拉列表将显示 `alice``bob` 作为独立模型。你可以通过管理员面板将模型分配给 Open WebUI 用户,为每个用户提供其独立的 Hermes agent。
:::tip 自定义模型名称
模型名称默认为 profile 名称。如需覆盖,请在 profile 的 `.env` 中设置 `API_SERVER_MODEL_NAME`
```bash
hermes -p alice config set API_SERVER_MODEL_NAME "Alice's Agent"
```
:::
## Linux Docker(无 Docker Desktop
在没有 Docker Desktop 的 Linux 上,`host.docker.internal` 默认无法解析。可选方案:
```bash
# 方案 1:添加主机映射
docker run --add-host=host.docker.internal:host-gateway ...
# 方案 2:使用主机网络
docker run --network=host -e OPENAI_API_BASE_URL=http://localhost:8642/v1 ...
# 方案 3:使用 Docker bridge IP
docker run -e OPENAI_API_BASE_URL=http://172.17.0.1:8642/v1 ...
```
@@ -0,0 +1,123 @@
# QQ Bot
通过**官方 QQ Bot APIv2**将 Hermes 接入 QQ——支持私聊(C2C)、群组 @-提及、频道及直接消息,并具备语音转写功能。
## 概述
QQ Bot 适配器使用[官方 QQ Bot API](https://bot.q.qq.com/wiki/develop/api-v2/) 实现以下功能:
- 通过持久 **WebSocket** 连接至 QQ Gateway(网关)接收消息
- 通过 **REST API** 发送文本和 Markdown 回复
- 下载并处理图片、语音消息及文件附件
- 使用腾讯内置 ASR 或可配置的 STT(语音转文字)提供商转写语音消息
## 前提条件
1. **QQ Bot 应用** — 在 [q.qq.com](https://q.qq.com) 注册:
- 创建新应用并记录您的 **App ID****App Secret**
- 启用所需 intent(意图):C2C 消息、群组 @-消息、频道消息
- 在沙盒模式下配置机器人以进行测试,或发布至生产环境
2. **依赖项** — 适配器需要 `aiohttp``httpx`
```bash
pip install aiohttp httpx
```
## 配置
### 交互式设置
```bash
hermes gateway setup
```
从平台列表中选择 **QQ Bot** 并按提示操作。
### 手动配置
在 `~/.hermes/.env` 中设置所需环境变量:
```bash
QQ_APP_ID=your-app-id
QQ_CLIENT_SECRET=your-app-secret
```
## 环境变量
| 变量 | 描述 | 默认值 |
|---|---|---|
| `QQ_APP_ID` | QQ Bot App ID(必填) | — |
| `QQ_CLIENT_SECRET` | QQ Bot App Secret(必填) | — |
| `QQBOT_HOME_CHANNEL` | 用于 cron/通知投递的 OpenID | — |
| `QQBOT_HOME_CHANNEL_NAME` | 主频道显示名称 | `Home` |
| `QQ_ALLOWED_USERS` | 允许私聊访问的用户 OpenID 列表(逗号分隔) | 开放(所有用户) |
| `QQ_GROUP_ALLOWED_USERS` | 允许群组访问的群组 OpenID 列表(逗号分隔) | — |
| `QQ_ALLOW_ALL_USERS` | 设为 `true` 以允许所有私聊 | `false` |
| `QQ_PORTAL_HOST` | 覆盖 QQ portal 主机(沙盒路由设为 `sandbox.q.qq.com` | `q.qq.com` |
| `QQ_STT_API_KEY` | 语音转文字提供商的 API 密钥 | — |
| `QQ_STT_BASE_URL` | (不直接读取——请在 `config.yaml` 中设置 `platforms.qqbot.extra.stt.baseUrl` | n/a |
| `QQ_STT_MODEL` | STT 模型名称 | `glm-asr` |
## 高级配置
如需精细控制,可在 `~/.hermes/config.yaml` 中添加平台设置:
```yaml
platforms:
qqbot:
enabled: true
extra:
app_id: "your-app-id"
client_secret: "your-secret"
markdown_support: true # enable QQ markdown (msg_type 2). Config-only; no env-var equivalent.
dm_policy: "open" # open | allowlist | disabled
allow_from:
- "user_openid_1"
group_policy: "open" # open | allowlist | disabled
group_allow_from:
- "group_openid_1"
stt:
provider: "zai" # zai (GLM-ASR), openai (Whisper), etc.
baseUrl: "https://open.bigmodel.cn/api/coding/paas/v4"
apiKey: "your-stt-key"
model: "glm-asr"
```
## 语音消息(STT
语音转写分两个阶段进行:
1. **QQ 内置 ASR**(免费,始终优先尝试)——QQ 在语音消息附件中提供 `asr_refer_text`,使用腾讯自有语音识别
2. **已配置的 STT 提供商**(备用)——若 QQ 的 ASR 未返回文本,适配器将调用兼容 OpenAI 的 STT API
- **智谱/GLM(zai)**:默认提供商,使用 `glm-asr` 模型
- **OpenAI Whisper**:设置 `QQ_STT_BASE_URL` 和 `QQ_STT_MODEL`
- 任何兼容 OpenAI 的 STT 端点
## 故障排查
### 机器人立即断开连接(快速断连)
通常原因如下:
- **App ID / Secret 无效** — 在 q.qq.com 仔细核对您的凭据
- **缺少权限** — 确保机器人已启用所需 intent
- **仅限沙盒的机器人** — 若机器人处于沙盒模式,只能接收来自 QQ 沙盒测试频道的消息
### 语音消息未被转写
1. 检查附件数据中是否存在 QQ 内置的 `asr_refer_text`
2. 若使用自定义 STT 提供商,验证 `QQ_STT_API_KEY` 是否正确设置
3. 查看 gateway 日志中的 STT 错误信息
### 消息未送达
- 在 q.qq.com 验证机器人的 **intent** 是否已启用
- 若私聊访问受限,检查 `QQ_ALLOWED_USERS`
- 对于群组消息,确保机器人被 **@提及**(群组策略可能需要加入白名单)
- 检查 `QQBOT_HOME_CHANNEL` 以确认 cron/通知投递配置
### 连接错误
- 确保已安装 `aiohttp` 和 `httpx``pip install aiohttp httpx`
- 检查与 `api.sgroup.qq.com` 及 WebSocket gateway 的网络连通性
- 查看 gateway 日志以获取详细错误信息和重连行为
@@ -0,0 +1,253 @@
---
sidebar_position: 6
title: "Signal"
description: "通过 signal-cli 守护进程将 Hermes Agent 设置为 Signal 机器人"
---
# Signal 配置
Hermes 通过以 HTTP 模式运行的 [signal-cli](https://github.com/AsamK/signal-cli) 守护进程连接到 Signal。适配器通过 SSEServer-Sent Events,服务器推送事件)实时接收消息,并通过 JSON-RPC 发送响应。
Signal 是隐私保护最完善的主流即时通讯工具——默认端对端加密、开源协议、极少的元数据收集。这使其非常适合对安全性要求较高的 Agent 工作流。
:::info 无需新增 Python 依赖
Signal 适配器使用 `httpx`(已是 Hermes 的核心依赖)进行所有通信,无需安装额外的 Python 包。你只需在外部安装 signal-cli。
:::
---
## 前提条件
- **signal-cli** — 基于 Java 的 Signal 客户端([GitHub](https://github.com/AsamK/signal-cli)
- **Java 17+** 运行时 — signal-cli 所需
- **一个已安装 Signal 的手机号**(用于作为辅助设备关联)
### 安装 signal-cli
```bash
# macOS
brew install signal-cli
# Linux(下载最新版本)
VERSION=$(curl -Ls -o /dev/null -w %{url_effective} \
https://github.com/AsamK/signal-cli/releases/latest | sed 's/^.*\/v//')
curl -L -O "https://github.com/AsamK/signal-cli/releases/download/v${VERSION}/signal-cli-${VERSION}.tar.gz"
sudo tar xf "signal-cli-${VERSION}.tar.gz" -C /opt
sudo ln -sf "/opt/signal-cli-${VERSION}/bin/signal-cli" /usr/local/bin/
```
:::caution
signal-cli **不在** apt 或 snap 仓库中。上述 Linux 安装方式直接从 [GitHub releases](https://github.com/AsamK/signal-cli/releases) 下载。
:::
---
## 第一步:关联你的 Signal 账号
signal-cli 作为**关联设备**运行——类似 WhatsApp Web,但用于 Signal。你的手机仍是主设备。
```bash
# 生成关联 URI(显示二维码或链接)
signal-cli link -n "HermesAgent"
```
1. 在手机上打开 **Signal**
2. 进入 **设置 → 关联设备**
3. 点击 **关联新设备**
4. 扫描二维码或输入 URI
---
## 第二步:启动 signal-cli 守护进程
```bash
# 将 +1234567890 替换为你的 Signal 手机号(E.164 格式)
signal-cli --account +1234567890 daemon --http 127.0.0.1:8080
```
:::tip
保持此进程在后台运行。你可以使用 `systemd``tmux``screen`,或将其作为服务运行。
:::
验证是否正在运行:
```bash
curl http://127.0.0.1:8080/api/v1/check
# 应返回:{"versions":{"signal-cli":...}}
```
---
## 第三步:配置 Hermes
最简单的方式:
```bash
hermes gateway setup
```
从平台菜单中选择 **Signal**。向导将:
1. 检查 signal-cli 是否已安装
2. 提示输入 HTTP URL(默认:`http://127.0.0.1:8080`
3. 测试与守护进程的连通性
4. 询问你的账号手机号
5. 配置允许的用户和访问策略
### 手动配置
`~/.hermes/.env` 中添加:
```bash
# 必填
SIGNAL_HTTP_URL=http://127.0.0.1:8080
SIGNAL_ACCOUNT=+1234567890
# 安全设置(推荐)
SIGNAL_ALLOWED_USERS=+1234567890,+0987654321 # 逗号分隔的 E.164 号码或 UUID
# 可选
SIGNAL_GROUP_ALLOWED_USERS=groupId1,groupId2 # 启用群组(省略则禁用,* 表示全部)
SIGNAL_HOME_CHANNEL=+1234567890 # cron 任务的默认投递目标
```
然后启动 gateway
```bash
hermes gateway # 前台运行
hermes gateway install # 安装为用户服务
sudo hermes gateway install --system # 仅 Linux:开机自启系统服务
```
---
## 访问控制
### 私信访问
私信访问遵循与其他 Hermes 平台相同的模式:
1. **已设置 `SIGNAL_ALLOWED_USERS`** → 仅允许这些用户发送消息
2. **未设置白名单** → 未知用户会收到私信配对码(通过 `hermes pairing approve signal CODE` 审批)
3. **`SIGNAL_ALLOW_ALL_USERS=true`** → 任何人均可发送消息(谨慎使用)
### 群组访问
群组访问由 `SIGNAL_GROUP_ALLOWED_USERS` 环境变量控制:
| 配置 | 行为 |
|------|------|
| 未设置(默认) | 忽略所有群组消息,机器人仅响应私信。 |
| 设置群组 ID | 仅监听列出的群组(如 `groupId1,groupId2`)。 |
| 设置为 `*` | 机器人在其所在的任意群组中均会响应。 |
---
## 功能特性
### 附件
适配器支持双向收发媒体文件。
**接收**(用户 → Agent):
- **图片** — PNG、JPEG、GIF、WebP(通过魔数自动检测)
- **音频** — MP3、OGG、WAV、M4A(若已配置 Whisper,语音消息将自动转录)
- **文档** — PDF、ZIP 及其他文件类型
**发送**Agent → 用户):
Agent 可通过响应中的 `MEDIA:` 标签发送媒体文件,支持以下投递方式:
- **图片** — `send_multiple_images``send_image_file` 将 PNG、JPEG、GIF、WebP 作为原生 Signal 附件发送
- **语音** — `send_voice` 将音频文件(OGG、MP3、WAV、M4A、AAC)作为附件发送
- **视频** — `send_video` 发送 MP4 视频文件
- **文档** — `send_document` 发送任意文件类型(PDF、ZIP 等)
所有外发媒体均通过 Signal 标准附件 API 处理。与某些平台不同,Signal 在协议层面不区分语音消息和文件附件。
附件大小限制:**100 MB**(双向)。
:::warning
**Signal 服务器会对附件上传进行速率限制**,适配器使用调度器批量发送多张图片,每批最多 32 张,并按照 Signal 服务器策略限速上传。
:::
### 原生格式、引用回复与表情回应
Signal 消息以**原生格式**渲染,而非显示原始 markdown 字符。适配器将 markdown`**粗体**``*斜体*`、`` `代码` ``、`~~删除线~~`、`||剧透||`、标题)转换为 Signal `bodyRanges`,使文本在接收方客户端以真实样式显示,而非可见的 `**` 或 `` ` `` 字符。
**引用回复。** 当 Hermes 回复某条特定消息时,会发送原生引用回复——与 Signal 用户使用"回复"功能时看到的 UI 效果相同。对于响应入站消息而生成的回复,此功能自动生效。
**表情回应。** Agent 可通过标准 reaction API 对消息添加表情回应;回应会以 emoji 形式显示在被引用消息上,而非额外的文字。
以上功能无需额外配置——在近期的 signal-cli 版本中默认启用。若你的 `signal-cli` 版本过旧,Hermes 会回退到纯文本投递,并记录一次性警告日志。
### 正在输入指示器
机器人在处理消息时会发送正在输入指示器,每 8 秒刷新一次。
### 手机号脱敏
所有手机号在日志中自动脱敏:
- `+15551234567` → `+155****4567`
- 适用于 Hermes gateway 日志和全局脱敏系统
### 给自己发消息(单号码配置)
如果你将 signal-cli 作为自己手机号的**关联辅助设备**运行(而非单独的机器人号码),可以通过 Signal 的"给自己发消息"功能与 Hermes 交互。
只需从手机向自己发送消息——signal-cli 会接收到该消息,Hermes 在同一会话中响应。
**工作原理:**
- "给自己发消息"以 `syncMessage.sentMessage` 信封形式到达
- 适配器检测到这些消息是发给机器人自身账号的,并将其作为普通入站消息处理
- 回声保护(已发时间戳追踪)防止无限循环——机器人自身的回复会被自动过滤
**无需额外配置。** 只要 `SIGNAL_ACCOUNT` 与你的手机号匹配,此功能自动生效。
### 健康监控
适配器监控 SSE 连接,并在以下情况自动重连:
- 连接断开(指数退避:2s → 60s)
- 120 秒内无任何活动(向 signal-cli 发送 ping 以验证连通性)
---
## 故障排查
| 问题 | 解决方案 |
|------|----------|
| 配置时提示 **"Cannot reach signal-cli"** | 确保 signal-cli 守护进程正在运行:`signal-cli --account +YOUR_NUMBER daemon --http 127.0.0.1:8080` |
| **消息未收到** | 检查 `SIGNAL_ALLOWED_USERS` 是否包含发送方号码(E.164 格式,带 `+` 前缀) |
| **"signal-cli not found on PATH"** | 安装 signal-cli 并确保其在 PATH 中,或使用 Docker |
| **连接持续断开** | 检查 signal-cli 日志中的错误信息,确保已安装 Java 17+。 |
| **群组消息被忽略** | 使用具体群组 ID 配置 `SIGNAL_GROUP_ALLOWED_USERS`,或设为 `*` 允许所有群组。 |
| **机器人对所有人无响应** | 配置 `SIGNAL_ALLOWED_USERS`,使用私信配对,或通过 gateway 策略显式允许所有用户(如需更广泛的访问权限)。 |
| **消息重复** | 确保只有一个 signal-cli 实例在监听你的手机号 |
---
## 安全
:::warning
**务必配置访问控制。** 机器人默认具有终端访问权限。若未设置 `SIGNAL_ALLOWED_USERS` 或私信配对,gateway 会拒绝所有入站消息作为安全措施。
:::
- 手机号在所有日志输出中均已脱敏
- 使用私信配对或显式白名单安全地引导新用户
- 除非明确需要群组支持,否则保持群组禁用状态,或仅将受信任的群组加入白名单
- Signal 的端对端加密保护传输中的消息内容
- `~/.local/share/signal-cli/` 中的 signal-cli 会话数据包含账号凭据——请像保护密码一样保护它
---
## 环境变量参考
| 变量 | 必填 | 默认值 | 说明 |
|------|------|--------|------|
| `SIGNAL_HTTP_URL` | 是 | — | signal-cli HTTP 端点 |
| `SIGNAL_ACCOUNT` | 是 | — | 机器人手机号(E.164) |
| `SIGNAL_ALLOWED_USERS` | 否 | — | 逗号分隔的手机号/UUID |
| `SIGNAL_GROUP_ALLOWED_USERS` | 否 | — | 要监听的群组 ID,或 `*` 表示全部(省略则禁用群组) |
| `SIGNAL_ALLOW_ALL_USERS` | 否 | `false` | 允许任意用户交互(跳过白名单) |
| `SIGNAL_HOME_CHANNEL` | 否 | — | cron 任务的默认投递目标 |
@@ -0,0 +1,98 @@
# SimpleX Chat
[SimpleX Chat](https://simplex.chat/) 是一个私密的去中心化即时通讯平台,用户完全掌控自己的联系人和群组。与其他平台不同,SimpleX 不分配任何持久用户 ID——每个联系人在建立连接时由系统生成一个不透明的内部 ID,这使其成为目前隐私性最强的即时通讯工具之一。
## 前提条件
- 已安装并以守护进程方式运行的 **simplex-chat** CLI
- Python 包 **websockets**`pip install websockets`
## 安装 simplex-chat
从 [simplex-chat GitHub releases](https://github.com/simplex-chat/simplex-chat/releases) 页面下载最新版本:
```bash
# Linux / macOS binary
curl -L https://github.com/simplex-chat/simplex-chat/releases/latest/download/simplex-chat-ubuntu-22_04-x86-64 -o simplex-chat
chmod +x simplex-chat
```
SimpleX Chat 项目未发布聊天客户端的预构建 Docker 镜像;如需在 Docker 下运行,请从 [simplex-chat 仓库](https://github.com/simplex-chat/simplex-chat) 源码构建。
## 启动守护进程
```bash
simplex-chat -p 5225
```
守护进程默认在 `ws://127.0.0.1:5225` 上监听 WebSocket 连接。
## 配置 Hermes
### 通过设置向导
```bash
hermes gateway setup
```
选择 **SimpleX Chat** 并按提示操作。
### 通过环境变量
将以下内容添加到 `~/.hermes/.env`
```
SIMPLEX_WS_URL=ws://127.0.0.1:5225
SIMPLEX_ALLOWED_USERS=<contact-id-1>,<contact-id-2>
SIMPLEX_HOME_CHANNEL=<contact-id>
```
| 变量 | 是否必填 | 说明 |
|---|---|---|
| `SIMPLEX_WS_URL` | 是 | simplex-chat 守护进程的 WebSocket URL |
| `SIMPLEX_ALLOWED_USERS` | 建议填写 | 允许使用 Agent 的联系人 ID,以逗号分隔 |
| `SIMPLEX_ALLOW_ALL_USERS` | 可选 | 设为 `true` 以允许所有联系人(请谨慎使用) |
| `SIMPLEX_HOME_CHANNEL` | 可选 | cron 任务投递的默认联系人 ID |
| `SIMPLEX_HOME_CHANNEL_NAME` | 可选 | 主频道的可读标签 |
## 查找联系人 ID
启动守护进程后,与你的 Agent 联系人开启一段对话。联系人 ID 将出现在会话日志中,或通过 `hermes send_message action=list` 查看。
## 授权
默认情况下**所有联系人均被拒绝访问**。你必须选择以下方式之一:
1.`SIMPLEX_ALLOWED_USERS` 设置为以逗号分隔的联系人 ID 列表,或
2. 使用 **DM 配对**——向 Bot 发送任意消息,Bot 将回复一个配对码。通过 `hermes pairing approve simplex <CODE>` 输入该配对码。
## 在 cron 任务中使用 SimpleX
```python
cronjob(
action="create",
schedule="every 1h",
deliver="simplex", # uses SIMPLEX_HOME_CHANNEL
prompt="Check for alerts and summarise."
)
```
或指定特定联系人:
```python
send_message(target="simplex:<contact-id>", message="Done!")
```
## 隐私说明
- SimpleX 从不暴露手机号或电子邮件地址——联系人使用不透明 ID 标识
- Hermes 与守护进程之间的连接为本地 WebSocket(`ws://127.0.0.1:5225`)——数据不会离开你的机器
- 消息在到达守护进程之前已由 SimpleX 协议进行端到端加密
## 故障排查
**"Cannot reach daemon"** — 确保 `simplex-chat -p 5225` 正在运行,且端口与 `SIMPLEX_WS_URL` 一致。
**"websockets not installed"** — 运行 `pip install websockets`
**消息未收到** — 检查该联系人的 ID 是否已加入 `SIMPLEX_ALLOWED_USERS`,或通过 DM 配对方式批准该联系人。
@@ -0,0 +1,593 @@
---
sidebar_position: 4
title: "Slack"
description: "使用 Socket Mode 将 Hermes Agent 设置为 Slack 机器人"
---
# Slack 设置
使用 Socket Mode 将 Hermes Agent 作为机器人连接到 Slack。Socket Mode 使用 WebSocket 而非公开 HTTP 端点,因此你的 Hermes 实例无需公开访问——它可以在防火墙后、笔记本电脑上或私有服务器上正常运行。
:::warning 经典 Slack 应用已弃用
使用 RTM API 的经典 Slack 应用已于 **2025 年 3 月完全弃用**。Hermes 使用带有 Socket Mode 的现代 Bolt SDK。如果你有旧的经典应用,必须按照以下步骤创建新应用。
:::
## 概述
| 组件 | 值 |
|-----------|-------|
| **库** | Python 的 `slack-bolt` / `slack_sdk`Socket Mode |
| **连接方式** | WebSocket——无需公开 URL |
| **所需认证令牌** | Bot Token`xoxb-`+ App-Level Token`xapp-` |
| **用户标识** | Slack Member ID(例如 `U01ABC2DEF3` |
---
## 第一步:创建 Slack 应用
最快的方式是粘贴 Hermes 为你生成的 manifest(清单文件)。它会一次性声明所有内置斜杠命令(`/btw``/stop``/model`……)、所有必需的 OAuth 权限范围、所有事件订阅,并启用 Socket Mode。
### 方式 A:使用 Hermes 生成的 manifest(推荐)
1. 生成 manifest
```bash
hermes slack manifest --write
```
此命令会将 `~/.hermes/slack-manifest.json` 写入磁盘并打印粘贴说明。
2. 前往 [https://api.slack.com/apps](https://api.slack.com/apps) →
**Create New App** → **From an app manifest**
3. 选择你的工作区,粘贴 JSON 内容,检查后点击 **Next** → **Create**
4. 直接跳至**第六步:将应用安装到工作区**。manifest 已为你处理好权限范围、事件和斜杠命令。
### 方式 B:从头手动创建
1. 前往 [https://api.slack.com/apps](https://api.slack.com/apps)
2. 点击 **Create New App**
3. 选择 **From scratch**
4. 输入应用名称(例如 "Hermes Agent")并选择你的工作区
5. 点击 **Create App**
你将进入应用的 **Basic Information** 页面。继续执行下方第 2–6 步。
---
## 第二步:配置 Bot Token 权限范围
在侧边栏导航至 **Features → OAuth & Permissions**。向下滚动至 **Scopes → Bot Token Scopes**,添加以下权限:
| 权限范围 | 用途 |
|-------|---------|
| `chat:write` | 以机器人身份发送消息 |
| `app_mentions:read` | 检测在频道中被 @ 提及的情况 |
| `channels:history` | 读取机器人所在公开频道的消息 |
| `channels:read` | 列出并获取公开频道信息 |
| `groups:history` | 读取机器人被邀请加入的私有频道消息 |
| `im:history` | 读取私信历史记录 |
| `im:read` | 查看基本私信信息 |
| `im:write` | 打开并管理私信 |
| `users:read` | 查询用户信息 |
| `files:read` | 读取并下载附件文件,包括语音备忘录/音频 |
| `files:write` | 上传文件(图片、音频、文档) |
:::caution 缺少权限范围 = 功能缺失
没有 `channels:history` 和 `groups:history`,机器人**将无法接收频道消息**——它只能在私信中工作。没有 `files:read`,Hermes 可以聊天,但**无法可靠读取用户上传的附件**。这是最常被遗漏的权限范围。
:::
**可选权限范围:**
| 权限范围 | 用途 |
|-------|---------|
| `groups:read` | 列出并获取私有频道信息 |
---
## 第三步:启用 Socket Mode
Socket Mode 让机器人通过 WebSocket 连接,无需公开 URL。
1. 在侧边栏前往 **Settings → Socket Mode**
2. 将 **Enable Socket Mode** 切换为开启
3. 系统会提示你创建一个 **App-Level Token**
- 命名为类似 `hermes-socket` 的名称(名称不重要)
- 添加 **`connections:write`** 权限范围
- 点击 **Generate**
4. **复制该令牌**——它以 `xapp-` 开头。这就是你的 `SLACK_APP_TOKEN`
:::tip
你随时可以在 **Settings → Basic Information → App-Level Tokens** 下找到或重新生成 App-Level Token。
:::
---
## 第四步:订阅事件
此步骤至关重要——它控制机器人能看到哪些消息。
1. 在侧边栏前往 **Features → Event Subscriptions**
2. 将 **Enable Events** 切换为开启
3. 展开 **Subscribe to bot events** 并添加:
| 事件 | 是否必需 | 用途 |
|-------|-----------|---------|
| `message.im` | **必需** | 机器人接收私信 |
| `message.channels` | **必需** | 机器人接收其加入的**公开**频道消息 |
| `message.groups` | **推荐** | 机器人接收被邀请加入的**私有**频道消息 |
| `app_mention` | **必需** | 防止机器人被 @ 提及时出现 Bolt SDK 错误 |
4. 点击页面底部的 **Save Changes**
:::danger 缺少事件订阅是第一大设置问题
如果机器人在私信中正常工作但**在频道中不响应**,你几乎肯定忘记添加 `message.channels`(公开频道)和/或 `message.groups`(私有频道)。没有这些事件,Slack 根本不会将频道消息传递给机器人。
:::
---
## 第五步:启用 Messages Tab
此步骤启用对机器人的私信功能。没有它,用户在尝试私信机器人时会看到**"向此应用发送消息已被关闭"**的提示。
1. 在侧边栏前往 **Features → App Home**
2. 向下滚动至 **Show Tabs**
3. 将 **Messages Tab** 切换为开启
4. 勾选 **"Allow users to send Slash commands and messages from the messages tab"**
:::danger 没有此步骤,私信将被完全屏蔽
即使拥有所有正确的权限范围和事件订阅,除非启用 Messages Tab,否则 Slack 不允许用户向机器人发送私信。这是 Slack 平台的要求,而非 Hermes 的配置问题。
:::
---
## 第六步:将应用安装到工作区
1. 在侧边栏前往 **Settings → Install App**
2. 点击 **Install to Workspace**
3. 检查权限并点击 **Allow**
4. 授权后,你将看到一个以 `xoxb-` 开头的 **Bot User OAuth Token**
5. **复制此令牌**——这就是你的 `SLACK_BOT_TOKEN`
:::tip
如果你之后更改了权限范围或事件订阅,**必须重新安装应用**才能使更改生效。Install App 页面会显示提示横幅。
:::
---
## 第七步:查找用于白名单的用户 ID
Hermes 使用 Slack **Member ID**(而非用户名或显示名称)作为白名单。
查找 Member ID 的方法:
1. 在 Slack 中点击用户的名称或头像
2. 点击 **View full profile**
3. 点击 **⋮**(更多)按钮
4. 选择 **Copy member ID**
Member ID 格式类似 `U01ABC2DEF3`。你至少需要自己的 Member ID。
---
## 第八步:配置 Hermes
将以下内容添加到你的 `~/.hermes/.env` 文件:
```bash
# 必需
SLACK_BOT_TOKEN=xoxb-your-bot-token-here
SLACK_APP_TOKEN=xapp-your-app-token-here
SLACK_ALLOWED_USERS=U01ABC2DEF3 # 逗号分隔的 Member ID
# 可选
SLACK_HOME_CHANNEL=C01234567890 # 定时/计划消息的默认频道
SLACK_HOME_CHANNEL_NAME=general # 主频道的可读名称(可选)
```
或运行交互式设置:
```bash
hermes gateway setup # 提示时选择 Slack
```
然后启动 gateway
```bash
hermes gateway # 前台运行
hermes gateway install # 安装为用户服务
sudo hermes gateway install --system # 仅 Linux:开机启动系统服务
```
---
## 第九步:将机器人邀请到频道
启动 gateway 后,你需要**邀请机器人**加入希望它响应的频道:
```
/invite @Hermes Agent
```
机器人**不会**自动加入频道。你必须逐个频道邀请它。
---
## 斜杠命令
每个 Hermes 命令(`/btw`、`/stop`、`/new`、`/model`、`/help`……)都是原生 Slack 斜杠命令——与它们在 Telegram 和 Discord 上的工作方式完全相同。在 Slack 中输入 `/`,自动补全选择器会列出每个 Hermes 命令及其描述。
底层实现:Hermes 附带一个生成的 Slack 应用 manifest(见第一步,方式 A),它将 [`COMMAND_REGISTRY`](https://github.com/NousResearch/hermes-agent/blob/main/hermes_cli/commands.py) 中的每个命令声明为斜杠命令。在 Socket Mode 下,无论 manifest 的 `url` 字段如何,Slack 都会通过 WebSocket 路由命令事件。
### 更新后刷新斜杠命令
当 Hermes 添加新命令时(例如执行 `hermes update` 后),重新生成 manifest 并更新你的 Slack 应用:
```bash
hermes slack manifest --write
```
然后在 Slack 中:
1. 打开 [https://api.slack.com/apps](https://api.slack.com/apps) →
你的 Hermes 应用
2. **Features → App Manifest → Edit**
3. 粘贴 `~/.hermes/slack-manifest.json` 的新内容
4. **保存**。如果权限范围或斜杠命令有变化,Slack 会提示重新安装应用。
### 旧版 `/hermes <子命令>` 仍然有效
为了向后兼容旧版 manifest,你仍然可以输入 `/hermes btw run the tests`——Hermes 会以与 `/btw run the tests` 相同的方式路由它。自由形式的问题也有效:`/hermes what's the weather?` 会被当作普通消息处理。
### 在话题(thread)中使用命令(`!cmd` 前缀)
Slack 本身会阻止在话题回复中使用原生斜杠命令——在话题中尝试 `/queue`Slack 会回复 *"/queue is not supported in threads. Sorry!"*。没有任何应用端设置可以重新启用它们;Slack 从不将它们传递给 Hermes。
作为解决方案,Hermes 识别前导 `!` 作为在话题(以及任何其他地方)中有效的替代命令前缀。在话题回复中输入 `!queue`、`!stop`、`!model gpt-5.4` 等普通回复——Hermes 会以与斜杠形式完全相同的方式处理,并在同一话题中回复。
只有第一个 token(词元)会与已知命令列表进行匹配,因此像 `!nice work` 这样的随意消息会原样传递给 agent。
### 高级:仅输出斜杠命令数组
如果你手动维护 Slack manifest 并只需要斜杠命令列表:
```bash
hermes slack manifest --slashes-only > /tmp/slashes.json
```
将该数组粘贴到现有 manifest 的 `features.slash_commands` 键中。
---
## 机器人的响应方式
了解 Hermes 在不同场景下的行为:
| 场景 | 行为 |
|---------|----------|
| **私信** | 机器人响应每条消息——无需 @ 提及 |
| **频道** | 机器人**仅在被 @ 提及时响应**(例如 `@Hermes Agent what time is it?`)。在频道中,Hermes 在该消息附带的话题中回复。 |
| **话题** | 如果你在现有话题中 @ 提及 Hermes,它会在同一话题中回复。一旦机器人在话题中有活跃会话,**该话题中的后续回复无需 @ 提及**——机器人会自然跟进对话。 |
:::tip
在频道中,始终 @ 提及机器人来开始对话。一旦机器人在话题中活跃,你可以在该话题中回复而无需提及它。话题之外,没有 @ 提及的消息会被忽略,以防止在繁忙频道中产生噪音。
:::
---
## 配置选项
除了第八步中的必需环境变量外,你还可以通过 `~/.hermes/config.yaml` 自定义 Slack 机器人行为。
### 话题与回复行为
```yaml
platforms:
slack:
# 控制多部分响应的话题方式
# "off" — 永不将回复串入原始消息的话题
# "first" — 第一个分块串入用户消息(默认)
# "all" — 所有分块串入用户消息
reply_to_mode: "first"
extra:
# 是否在话题中回复(默认:true)。
# 为 false 时,频道消息直接在频道中回复,而非话题。
# 已在话题中的消息仍在话题中回复。
reply_in_thread: true
# 同时将话题回复发布到主频道
# Slack 的"同时发送到频道"功能)。
# 仅广播第一条回复的第一个分块。
reply_broadcast: false
```
| 键 | 默认值 | 描述 |
|-----|---------|-------------|
| `platforms.slack.reply_to_mode` | `"first"` | 多部分消息的话题模式:`"off"`、`"first"` 或 `"all"` |
| `platforms.slack.extra.reply_in_thread` | `true` | 为 `false` 时,频道消息直接回复而非话题。已在话题中的消息仍在话题中回复。 |
| `platforms.slack.extra.reply_broadcast` | `false` | 为 `true` 时,话题回复也会发布到主频道。仅广播第一个分块。 |
### 会话隔离
```yaml
# 全局设置——适用于 Slack 和所有其他平台
group_sessions_per_user: true
```
为 `true`(默认值)时,共享频道中的每个用户都有自己独立的对话会话。在 `#general` 中与 Hermes 对话的两个人将有各自独立的历史记录和上下文。
设为 `false` 可启用协作模式,整个频道共享一个对话会话。请注意,这意味着用户共享上下文增长和 token 成本,且一个用户的 `/reset` 会清除所有人的会话。
### 提及与触发行为
```yaml
slack:
# 在频道中要求 @mention(这是默认行为;
# Slack 适配器无论如何都会在频道中强制执行 @mention 门控,
# 但你可以明确设置此项以与其他平台保持一致)
require_mention: true
# 防止话题自动参与:仅回复包含明确 @mention 的频道消息。
# 关闭此项(默认),Slack 可以"自动参与"——记住话题中的过去提及,
# 跟进机器人消息的回复,并在无需新提及的情况下恢复活跃会话。
# 开启 strict_mention 后,每条新频道消息都必须 @mention 机器人,
# Hermes 才会响应。
strict_mention: false
# 触发机器人的自定义提及模式
# (除默认 @mention 检测外)
mention_patterns:
- "hey hermes"
- "hermes,"
# 每条发出消息前添加的文本
reply_prefix: ""
```
:::tip 何时使用 `strict_mention`
在繁忙工作区中,如果 Slack 默认的"机器人记住此话题"行为让用户感到意外,请将此项设为 `true`——例如,在一个长技术支持话题中,机器人在开始时提供了帮助,而你希望它保持沉默,除非被明确 @ 提及。私信和活跃的交互会话不受影响。
:::
:::info
Slack 支持两种模式:默认情况下需要 `@mention` 才能开始对话,但你可以通过 `SLACK_FREE_RESPONSE_CHANNELS`(逗号分隔的频道 ID)或 `config.yaml` 中的 `slack.free_response_channels` 为特定频道取消此限制。一旦机器人在话题中有活跃会话,后续话题回复无需提及。在私信中,机器人始终响应,无需提及。
:::
### 频道白名单(`allowed_channels`
将机器人限制在固定的 Slack 频道集合中——当机器人被邀请到许多频道但只应在少数频道中响应时很有用。设置后,不在此列表中的频道消息将被**静默忽略**,即使机器人被 `@mention`。
**私信不受此过滤器影响**,因此授权用户始终可以通过私信联系机器人。
```yaml
slack:
allowed_channels:
- "C0123456789" # #ops
- "C0987654321" # #incident-response
```
或通过环境变量(逗号分隔):
```bash
SLACK_ALLOWED_CHANNELS="C0123456789,C0987654321"
```
行为说明:
- 空/未设置 → 无限制(完全向后兼容)。
- 非空 → 频道 ID 必须在列表中,否则消息在任何其他门控(提及要求、`free_response_channels` 等)运行之前被丢弃。
- Slack 频道 ID 以 `C`(公开)、`G`(私有)或 `D`(私信)开头。可通过 Slack UI 的"打开频道详情"→"关于"面板或 API 查找。
另见:[管理员/用户斜杠命令分离](../../reference/slash-commands.md#permissions-and-adminuser-split)。
### 未授权用户处理
```yaml
slack:
# 当未授权用户(不在 SLACK_ALLOWED_USERS 中)私信机器人时的处理方式
# "pair" — 提示他们输入配对码(默认)
# "ignore" — 静默丢弃消息
unauthorized_dm_behavior: "pair"
```
你也可以为所有平台全局设置:
```yaml
unauthorized_dm_behavior: "pair"
```
`slack:` 下的平台特定设置优先于全局设置。
### 语音转录
```yaml
# 全局设置——启用/禁用传入语音消息的自动转录
stt_enabled: true
```
为 `true`(默认值)时,传入的音频消息会在被 agent 处理之前,使用配置的 STT 提供商自动转录。
### 完整示例
```yaml
# 全局 gateway 设置
group_sessions_per_user: true
unauthorized_dm_behavior: "pair"
stt_enabled: true
# Slack 特定设置
slack:
require_mention: true
unauthorized_dm_behavior: "pair"
# 平台配置
platforms:
slack:
reply_to_mode: "first"
extra:
reply_in_thread: true
reply_broadcast: false
```
---
## 主频道
将 `SLACK_HOME_CHANNEL` 设置为频道 ID,Hermes 将在此频道发送计划消息、定时任务结果和其他主动通知。查找频道 ID 的方法:
1. 在 Slack 中右键点击频道名称
2. 点击 **View channel details**
3. 向下滚动——频道 ID 显示在底部
```bash
SLACK_HOME_CHANNEL=C01234567890
```
确保机器人已被**邀请到该频道**`/invite @Hermes Agent`)。
---
## 多工作区支持
Hermes 可以使用单个 gateway 实例**同时连接多个 Slack 工作区**。每个工作区使用其自己的机器人用户 ID 独立认证。
### 配置
在 `SLACK_BOT_TOKEN` 中以**逗号分隔列表**的形式提供多个 bot token
```bash
# 多个 bot token——每个工作区一个
SLACK_BOT_TOKEN=xoxb-workspace1-token,xoxb-workspace2-token,xoxb-workspace3-token
# Socket Mode 仍使用单个 app-level token
SLACK_APP_TOKEN=xapp-your-app-token
```
或在 `~/.hermes/config.yaml` 中:
```yaml
platforms:
slack:
token: "xoxb-workspace1-token,xoxb-workspace2-token"
```
### OAuth Token 文件
除了环境变量或配置中的 token 外,Hermes 还会从以下位置的 **OAuth token 文件**加载 token
```
~/.hermes/slack_tokens.json
```
此文件是一个将团队 ID 映射到 token 条目的 JSON 对象:
```json
{
"T01ABC2DEF3": {
"token": "xoxb-workspace-token-here",
"team_name": "My Workspace"
}
}
```
此文件中的 token 会与通过 `SLACK_BOT_TOKEN` 指定的 token 合并。重复的 token 会自动去重。
### 工作原理
- 列表中的**第一个 token** 是主 token,用于 Socket Mode 连接(AsyncApp)。
- 每个 token 在启动时通过 `auth.test` 进行认证。gateway 将每个 `team_id` 映射到其自己的 `WebClient` 和 `bot_user_id`。
- 消息到达时,Hermes 使用正确的工作区特定客户端进行响应。
- 主 `bot_user_id`(来自第一个 token)用于向后兼容期望单一机器人身份的功能。
---
## 语音消息
Hermes 支持 Slack 上的语音功能:
- **传入:** 语音/音频消息使用配置的 STT 提供商自动转录:本地 `faster-whisper`、Groq Whisper`GROQ_API_KEY`)或 OpenAI Whisper`VOICE_TOOLS_OPENAI_KEY`
- **传出:** TTS 响应以音频文件附件形式发送
---
## 按频道设置 Prompt
为特定 Slack 频道分配临时系统 prompt(提示词)。该 prompt 在运行时每轮注入——从不持久化到对话历史——因此更改立即生效。
```yaml
slack:
channel_prompts:
"C01RESEARCH": |
You are a research assistant. Focus on academic sources,
citations, and concise synthesis.
"C02ENGINEERING": |
Code review mode. Be precise about edge cases and
performance implications.
```
键为 Slack 频道 ID(通过频道详情 → "关于" → 滚动到底部查找)。匹配频道中的所有消息都会将该 prompt 作为临时系统指令注入。
## 按频道绑定技能
在特定频道或私信中新会话开始时自动加载技能。与按频道设置 prompt(每轮注入)不同,技能绑定在**会话开始时**将技能内容作为用户消息注入——它成为对话历史的一部分,后续轮次无需重新加载。
这非常适合有专用用途的私信或频道(闪卡、特定领域问答机器人、支持分类频道等),在这些场景中你不希望模型自己的技能选择器在每次简短回复时决定是否加载。
```yaml
slack:
channel_skill_bindings:
# 私信频道——始终以"german-flashcards"模式运行
- id: "D0ATH9TQ0G6"
skills:
- german-flashcards
# 研究频道——按顺序预加载多个技能
- id: "C01RESEARCH"
skills:
- arxiv
- writing-plans
# 简写形式:单个技能作为字符串
- id: "C02SUPPORT"
skill: hubspot-on-demand
```
注意事项:
- 绑定按频道 ID 匹配。对于绑定频道中的话题消息,话题继承父频道的绑定。
- 技能仅在会话开始时加载(新会话或自动重置后)。如果更改绑定,请运行 `/new` 或等待会话自动重置以使其生效。
- 与 `channel_prompts` 结合使用,可在技能指令之上为每个频道设置语气/约束。
## 故障排除
| 问题 | 解决方案 |
|---------|----------|
| 机器人不响应私信 | 验证 `message.im` 在事件订阅中,且应用已重新安装 |
| 机器人在私信中正常但在频道中不响应 | **最常见问题。** 将 `message.channels` 和 `message.groups` 添加到事件订阅,重新安装应用,并用 `/invite @Hermes Agent` 邀请机器人加入频道 |
| 机器人不响应频道中的 @mention | 1) 检查 `message.channels` 事件是否已订阅。2) 机器人必须被邀请到频道。3) 确保已添加 `channels:history` 权限范围。4) 更改权限范围/事件后重新安装应用 |
| 机器人忽略私有频道中的消息 | 添加 `message.groups` 事件订阅和 `groups:history` 权限范围,然后重新安装应用并 `/invite` 机器人 |
| 私信中出现"向此应用发送消息已被关闭" | 在 App Home 设置中启用 **Messages Tab**(见第五步) |
| "not_authed" 或 "invalid_auth" 错误 | 重新生成 Bot Token 和 App Token,更新 `.env` |
| 机器人响应但无法在频道中发帖 | 用 `/invite @Hermes Agent` 邀请机器人加入频道 |
| 机器人可以聊天但无法读取上传的图片/文件 | 添加 `files:read`,然后**重新安装**应用。当 Slack 返回权限范围/认证/权限失败时,Hermes 现在会在聊天中显示附件访问诊断信息。 |
| `missing_scope` 错误 | 在 OAuth & Permissions 中添加所需权限范围,然后**重新安装**应用 |
| Socket 频繁断开 | 检查你的网络;Bolt 会自动重连,但不稳定的连接会导致延迟 |
| 更改了权限范围/事件但没有任何变化 | 更改任何权限范围或事件订阅后,**必须重新安装**应用到工作区 |
### 快速检查清单
如果机器人在频道中不工作,请验证以下**所有**项目:
1. ✅ 已订阅 `message.channels` 事件(公开频道)
2. ✅ 已订阅 `message.groups` 事件(私有频道)
3. ✅ 已订阅 `app_mention` 事件
4. ✅ 已添加 `channels:history` 权限范围(公开频道)
5. ✅ 已添加 `groups:history` 权限范围(私有频道)
6. ✅ 添加权限范围/事件后已**重新安装**应用
7. ✅ 已**邀请**机器人加入频道(`/invite @Hermes Agent`
8. ✅ 你在消息中**@mention** 了机器人
---
## 安全
:::warning
**始终设置 `SLACK_ALLOWED_USERS`**,填入授权用户的 Member ID。没有此设置,gateway 默认会**拒绝所有消息**作为安全措施。切勿分享你的 bot token——像密码一样对待它们。
:::
- Token 应存储在 `~/.hermes/.env` 中(文件权限 `600`
- 定期通过 Slack 应用设置轮换 token
- 审计谁有权访问你的 Hermes 配置目录
- Socket Mode 意味着不暴露公开端点——减少一个攻击面
@@ -0,0 +1,203 @@
---
sidebar_position: 8
sidebar_label: "SMS (Twilio)"
title: "SMS (Twilio)"
description: "通过 Twilio 将 Hermes Agent 设置为 SMS 聊天机器人"
---
# SMS 设置(Twilio
Hermes 通过 [Twilio](https://www.twilio.com/) API 接入 SMS。用户向你的 Twilio 电话号码发送短信,即可获得 AI 回复——与 Telegram 或 Discord 的对话体验相同,但通过标准短信进行。
:::info 共享凭据
SMS gateway(网关)与可选的 [telephony skill](/reference/skills-catalog) 共享凭据。如果你已为语音通话或单次 SMS 配置了 Twilio,该 gateway 可直接使用相同的 `TWILIO_ACCOUNT_SID``TWILIO_AUTH_TOKEN``TWILIO_PHONE_NUMBER`
:::
---
## 前提条件
- **Twilio 账户** — [在 twilio.com 注册](https://www.twilio.com/try-twilio)(提供免费试用)
- **具备 SMS 功能的 Twilio 电话号码**
- **可公开访问的服务器** — Twilio 在收到 SMS 时会向你的服务器发送 webhook
- **aiohttp** — `pip install 'hermes-agent[sms]'`
---
## 第一步:获取 Twilio 凭据
1. 前往 [Twilio 控制台](https://console.twilio.com/)
2. 从仪表板复制你的 **Account SID****Auth Token**
3. 前往 **Phone Numbers → Manage → Active Numbers**,记录 E.164 格式的电话号码(例如 `+15551234567`
---
## 第二步:配置 Hermes
### 交互式设置(推荐)
```bash
hermes gateway setup
```
从平台列表中选择 **SMS (Twilio)**,向导将提示你输入凭据。
### 手动设置
`~/.hermes/.env` 中添加:
```bash
TWILIO_ACCOUNT_SID=ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
TWILIO_AUTH_TOKEN=your_auth_token_here
TWILIO_PHONE_NUMBER=+15551234567
# 安全:限制特定电话号码(推荐)
SMS_ALLOWED_USERS=+15559876543,+15551112222
# 可选:为 cron 任务投递设置主频道
SMS_HOME_CHANNEL=+15559876543
```
---
## 第三步:配置 Twilio Webhook
Twilio 需要知道将传入消息发送到哪里。在 [Twilio 控制台](https://console.twilio.com/) 中:
1. 前往 **Phone Numbers → Manage → Active Numbers**
2. 点击你的电话号码
3.**Messaging → A MESSAGE COMES IN** 下,设置:
- **Webhook**`https://your-server:8080/webhooks/twilio`
- **HTTP Method**`POST`
:::tip 暴露你的 Webhook
如果你在本地运行 Hermes,请使用隧道工具暴露 webhook:
```bash
# 使用 cloudflared
cloudflared tunnel --url http://localhost:8080
# 使用 ngrok
ngrok http 8080
```
将生成的公网 URL 设置为你的 Twilio webhook。
:::
**将 `SMS_WEBHOOK_URL` 设置为你在 Twilio 中配置的相同 URL。** 这是 Twilio 签名验证所必需的——如果未设置,适配器将拒绝启动:
```bash
# 必须与 Twilio 控制台中的 webhook URL 一致
SMS_WEBHOOK_URL=https://your-server:8080/webhooks/twilio
```
webhook 端口默认为 `8080`,可通过以下方式覆盖:
```bash
SMS_WEBHOOK_PORT=3000
```
---
## 第四步:启动 Gateway
```bash
hermes gateway
```
你应该看到:
```
[sms] Twilio webhook server listening on 127.0.0.1:8080, from: +1555***4567
```
如果看到 `Refusing to start: SMS_WEBHOOK_URL is required`,请将 `SMS_WEBHOOK_URL` 设置为你在 Twilio 控制台中配置的公网 URL(参见第三步)。
向你的 Twilio 号码发送短信——Hermes 将通过 SMS 回复。
---
## 环境变量
| 变量 | 是否必填 | 说明 |
|----------|----------|-------------|
| `TWILIO_ACCOUNT_SID` | 是 | Twilio Account SID(以 `AC` 开头) |
| `TWILIO_AUTH_TOKEN` | 是 | Twilio Auth Token(同时用于 webhook 签名验证) |
| `TWILIO_PHONE_NUMBER` | 是 | 你的 Twilio 电话号码(E.164 格式) |
| `SMS_WEBHOOK_URL` | 是 | 用于 Twilio 签名验证的公网 URL——必须与 Twilio 控制台中的 webhook URL 一致 |
| `SMS_WEBHOOK_PORT` | 否 | Webhook 监听端口(默认:`8080` |
| `SMS_WEBHOOK_HOST` | 否 | Webhook 绑定地址(默认:`127.0.0.1` |
| `SMS_INSECURE_NO_SIGNATURE` | 否 | 设为 `true` 可禁用签名验证(仅限本地开发——**不适用于生产环境**) |
| `SMS_ALLOWED_USERS` | 否 | 允许聊天的 E.164 格式电话号码,逗号分隔 |
| `SMS_ALLOW_ALL_USERS` | 否 | 设为 `true` 允许所有人(不推荐) |
| `SMS_HOME_CHANNEL` | 否 | 用于 cron 任务/通知投递的电话号码 |
| `SMS_HOME_CHANNEL_NAME` | 否 | 主频道的显示名称(默认:`Home` |
---
## SMS 特有行为
- **纯文本** — Markdown 会被自动剥离,因为 SMS 会将其渲染为字面字符
- **1600 字符限制** — 较长的回复会在自然边界处(换行符,其次是空格)拆分为多条消息
- **防回声** — 来自你自己 Twilio 号码的消息将被忽略,以防止循环
- **电话号码脱敏** — 日志中的电话号码会被脱敏处理以保护隐私
---
## 安全
### Webhook 签名验证
Hermes 通过验证 `X-Twilio-Signature` 头(HMAC-SHA1)来确认入站 webhook 确实来自 Twilio,防止攻击者注入伪造消息。
**`SMS_WEBHOOK_URL` 为必填项。** 将其设置为你在 Twilio 控制台中配置的公网 URL,否则适配器将拒绝启动。
如需在本地开发时不使用公网 URL,可禁用验证:
```bash
# 仅限本地开发——不适用于生产环境
SMS_INSECURE_NO_SIGNATURE=true
```
### 用户白名单
**Gateway 默认拒绝所有用户。** 请配置白名单:
```bash
# 推荐:限制特定电话号码
SMS_ALLOWED_USERS=+15559876543,+15551112222
# 或允许所有人(对于具有终端访问权限的机器人,不推荐)
SMS_ALLOW_ALL_USERS=true
```
:::warning
SMS 没有内置加密。除非你了解相关安全风险,否则不要通过 SMS 进行敏感操作。对于敏感场景,请优先使用 Signal 或 Telegram。
:::
---
## 故障排查
### 消息未到达
1. 检查 Twilio webhook URL 是否正确且可公开访问
2. 验证 `TWILIO_ACCOUNT_SID``TWILIO_AUTH_TOKEN` 是否正确
3. 在 Twilio 控制台 → **Monitor → Logs → Messaging** 中查看投递错误
4. 确保你的电话号码在 `SMS_ALLOWED_USERS` 中(或设置 `SMS_ALLOW_ALL_USERS=true`
### 回复未发送
1. 检查 `TWILIO_PHONE_NUMBER` 是否正确设置(E.164 格式,带 `+`
2. 验证你的 Twilio 账户是否有支持 SMS 的号码
3. 查看 Hermes gateway 日志中的 Twilio API 错误
### Webhook 端口冲突
如果 8080 端口已被占用,请更改端口:
```bash
SMS_WEBHOOK_PORT=3001
```
并在 Twilio 控制台中更新 webhook URL 以匹配新端口。
@@ -0,0 +1,233 @@
---
sidebar_position: 6
title: "Teams 会议"
description: "使用 Microsoft Graph webhook 配置 Microsoft Teams 会议摘要流水线"
---
# Microsoft Teams 会议
当你希望 Hermes 接收 Microsoft Graph 会议事件、优先获取转录文本、在无可用转录时回退到录音加 STT(语音转文字),并将结构化摘要输出到下游 sink 时,请使用 Teams 会议流水线。
本页重点介绍配置与启用:
- Graph 凭据
- webhook 监听器配置
- Teams 投递模式
- 流水线配置结构
关于上线后的日常运维、上线检查及运维工作表,请参阅专项指南:[运维 Teams 会议流水线](/guides/operate-teams-meeting-pipeline)。
## 功能说明
该流水线:
1. 接收 Microsoft Graph webhook 事件
2. 解析会议并优先使用转录文件
3. 在无可用转录时回退到录音下载加 STT
4. 在本地存储持久化任务状态和 sink 记录
5. 可将摘要写入 Notion、Linear 和 Microsoft Teams
运维操作通过 CLI 完成(`teams-pipeline` 子命令由 `teams_pipeline` 插件注册——通过 `hermes plugins enable teams_pipeline` 启用,或在 `config.yaml` 中设置 `plugins.enabled: [teams_pipeline]`):
```bash
hermes teams-pipeline validate
hermes teams-pipeline list
hermes teams-pipeline maintain-subscriptions
```
## 前提条件
启用会议流水线前,请确保已具备:
- 可正常运行的 Hermes 安装
- 若需要 Teams 出站投递,需完成现有的 [Microsoft Teams bot 配置](/user-guide/messaging/teams)
- 具备订阅所需会议资源权限的 Microsoft Graph 应用凭据
- Microsoft Graph 可调用的公网 HTTPS URL,用于 webhook 投递
- 若需要录音加 STT 回退,需安装 `ffmpeg`
## 第一步:添加 Microsoft Graph 凭据
将 Graph 应用凭据添加到 `~/.hermes/.env`
```bash
MSGRAPH_TENANT_ID=<tenant-id>
MSGRAPH_CLIENT_ID=<client-id>
MSGRAPH_CLIENT_SECRET=<client-secret>
```
这些凭据用于:
- Graph 客户端基础层
- 订阅维护命令
- 会议解析和文件获取
- 未提供专用 Teams 访问令牌时,通过 Graph 进行 Teams 出站投递
## 第二步:启用 Graph Webhook 监听器
webhook 监听器是一个名为 `msgraph_webhook` 的 gateway 平台。至少需要启用它并设置一个 client state 值:
```bash
MSGRAPH_WEBHOOK_ENABLED=true
MSGRAPH_WEBHOOK_PORT=8646
MSGRAPH_WEBHOOK_CLIENT_STATE=<random-shared-secret>
MSGRAPH_WEBHOOK_ACCEPTED_RESOURCES=communications/onlineMeetings
```
监听器暴露以下端点:
- `/msgraph/webhook` 用于接收 Graph 通知
- `/health` 用于简单健康检查
你需要将公网 HTTPS 端点路由到该监听器。例如,若你的公网域名为 `https://ops.example.com`Graph 通知 URL 通常为:
```text
https://ops.example.com/msgraph/webhook
```
## 第三步:配置 Teams 投递与流水线行为
会议流水线从现有的 `teams` 平台条目读取运行时配置。流水线专属参数位于 `teams.extra.meeting_pipeline` 下。Teams 出站投递仍使用常规 Teams 平台配置。
`~/.hermes/config.yaml` 示例:
```yaml
platforms:
msgraph_webhook:
enabled: true
extra:
port: 8646
client_state: "replace-me"
accepted_resources:
- "communications/onlineMeetings"
teams:
enabled: true
extra:
client_id: "your-teams-client-id"
client_secret: "your-teams-client-secret"
tenant_id: "your-teams-tenant-id"
# outbound summary delivery
delivery_mode: "graph" # or incoming_webhook
team_id: "team-id"
channel_id: "channel-id"
# incoming_webhook_url: "https://..."
meeting_pipeline:
transcript_min_chars: 80
transcript_required: false
transcription_fallback: true
ffmpeg_extract_audio: true
notion:
enabled: false
linear:
enabled: false
```
## Teams 投递模式
流水线在现有 Teams 插件内支持两种 Teams 摘要投递模式。
### `incoming_webhook`
当你希望通过简单的 webhook 将消息发送到 Teams,而无需通过 Graph 创建频道消息时,使用此模式。
所需配置:
```yaml
platforms:
teams:
enabled: true
extra:
delivery_mode: "incoming_webhook"
incoming_webhook_url: "https://..."
```
### `graph`
当你希望 Hermes 通过 Microsoft Graph 将摘要发送到 Teams 聊天或频道时,使用此模式。
支持的目标:
- `chat_id`
- `team_id` + `channel_id`
- 现有 Teams 平台的 `team_id` + `home_channel` 回退
示例:
```yaml
platforms:
teams:
enabled: true
extra:
delivery_mode: "graph"
team_id: "team-id"
channel_id: "channel-id"
```
## 第四步:启动 Gateway
更新配置后正常启动 Hermes
```bash
hermes gateway run
```
若你在 Docker 中运行 Hermes,按现有部署方式启动 gateway 即可。
检查监听器:
```bash
curl http://localhost:8646/health
```
## 第五步:创建 Graph 订阅
使用插件 CLI 创建和查看订阅。
示例:
```bash
hermes teams-pipeline subscribe \
--resource communications/onlineMeetings/getAllTranscripts \
--notification-url https://ops.example.com/msgraph/webhook \
--client-state "$MSGRAPH_WEBHOOK_CLIENT_STATE"
hermes teams-pipeline subscribe \
--resource communications/onlineMeetings/getAllRecordings \
--notification-url https://ops.example.com/msgraph/webhook \
--client-state "$MSGRAPH_WEBHOOK_CLIENT_STATE"
```
:::warning Graph 订阅在 72 小时后过期
Microsoft Graph 将 webhook 订阅上限设为 72 小时,且不会自动续期。你**必须**在上线前调度 `hermes teams-pipeline maintain-subscriptions`,否则通知将在手动创建订阅三天后静默停止。请参阅运维手册中的[自动化订阅续期](/guides/operate-teams-meeting-pipeline#automating-subscription-renewal-required-for-production)——提供三种方案(Hermes cron、systemd timer、普通 crontab)。
:::
关于订阅维护和上线后的运维流程,请继续阅读指南:[运维 Teams 会议流水线](/guides/operate-teams-meeting-pipeline)。
## 验证
运行内置验证快照:
```bash
hermes teams-pipeline validate
```
常用辅助检查:
```bash
hermes teams-pipeline token-health
hermes teams-pipeline subscriptions
```
## 故障排查
| 问题 | 检查项 |
|---------|---------------|
| Graph webhook 验证失败 | 确认公网 URL 正确且可访问,并确认 Graph 调用的路径为 `/msgraph/webhook` |
| `hermes teams-pipeline list` 中未出现任务 | 确认 `msgraph_webhook` 已启用,且订阅指向正确的通知 URL |
| 转录优先从未成功 | 检查转录资源的 Graph 权限,以及该会议是否存在转录文件 |
| 录音回退失败 | 确认已安装 `ffmpeg`,且 Graph 应用可访问录音文件 |
| Teams 摘要投递失败 | 重新检查 `delivery_mode`、目标 ID 及 Teams 认证配置 |
## 相关文档
- [Microsoft Teams bot 配置](/user-guide/messaging/teams)
- [运维 Teams 会议流水线](/guides/operate-teams-meeting-pipeline)
@@ -0,0 +1,252 @@
---
sidebar_position: 5
title: "Microsoft Teams"
description: "将 Hermes Agent 设置为 Microsoft Teams 机器人"
---
# Microsoft Teams 设置
将 Hermes Agent 作为机器人接入 Microsoft Teams。与 Slack 的 Socket Mode 不同,Teams 通过调用**公开 HTTPS webhook**(钩子)来投递消息,因此你的实例需要一个可公开访问的端点——本地开发时使用开发隧道,生产环境使用真实域名。
如果你需要的是来自 Microsoft Graph 事件的会议摘要,而非普通的机器人对话,请使用专用设置页面:[Teams 会议](/user-guide/messaging/teams-meetings)。
## 机器人的响应方式
| 场景 | 行为 |
|------|------|
| **个人聊天(私信)** | 机器人响应每一条消息,无需 @提及。 |
| **群聊** | 机器人仅在被 @提及时响应。 |
| **频道** | 机器人仅在被 @提及时响应。 |
Teams 将 @提及作为普通消息投递,其中包含 `<at>BotName</at>` 标签,Hermes 在处理前会自动去除这些标签。
---
## 第一步:安装 Teams CLI
`@microsoft/teams.cli` 可自动完成机器人注册,无需进入 Azure 门户。
```bash
npm install -g @microsoft/teams.cli@preview
teams login
```
验证登录状态并查找你自己的 AAD 对象 ID(`TEAMS_ALLOWED_USERS` 需要用到):
```bash
teams status --verbose
```
---
## 第二步:暴露 Webhook 端口
Teams 无法向 `localhost` 投递消息。本地开发时,使用任意隧道工具获取一个公开的 HTTPS URL。默认端口为 `3978`,如需更改可通过 `TEAMS_PORT` 设置。
```bash
# devtunnelMicrosoft 官方)
devtunnel create hermes-bot --allow-anonymous
devtunnel port create hermes-bot -p 3978 --protocol https # 如已修改 TEAMS_PORT,请替换 3978
devtunnel host hermes-bot
# ngrok
ngrok http 3978 # 如已修改 TEAMS_PORT,请替换 3978
# cloudflared
cloudflared tunnel --url http://localhost:3978 # 如已修改 TEAMS_PORT,请替换 3978
```
从输出中复制 `https://` URL——下一步会用到。开发期间保持隧道运行。
生产环境请将机器人端点指向服务器的公开域名(参见[生产部署](#production-deployment))。
---
## 第三步:创建机器人
```bash
teams app create \
--name "Hermes" \
--endpoint "https://<your-tunnel-url>/api/messages"
```
CLI 会输出你的 `CLIENT_ID``CLIENT_SECRET``TENANT_ID`,以及第六步所需的安装链接。请保存客户端密钥——它不会再次显示。
---
## 第四步:配置环境变量
添加到 `~/.hermes/.env`
```bash
# 必填
TEAMS_CLIENT_ID=<your-client-id>
TEAMS_CLIENT_SECRET=<your-client-secret>
TEAMS_TENANT_ID=<your-tenant-id>
# 限制特定用户访问(推荐)
# 使用 `teams status --verbose` 获取 AAD 对象 ID
TEAMS_ALLOWED_USERS=<your-aad-object-id>
```
---
## 第五步:启动 Gateway
```bash
HERMES_UID=$(id -u) HERMES_GID=$(id -g) docker compose up -d gateway
```
此命令启动 gateway。默认 webhook 端口为 `3978`(可通过 `TEAMS_PORT` 覆盖)。检查运行状态:
```bash
curl http://localhost:3978/health # 应返回:ok
docker logs -f hermes
```
查找以下日志:
```
[teams] Webhook server listening on 0.0.0.0:3978/api/messages
```
---
## 第六步:在 Teams 中安装应用
```bash
teams app get <teamsAppId> --install-link
```
在浏览器中打开输出的链接——它会直接在 Teams 客户端中打开。安装完成后,向机器人发送一条私信,即可开始使用。
---
## 配置参考
### 环境变量
| 变量 | 说明 |
|------|------|
| `TEAMS_CLIENT_ID` | Azure AD 应用(客户端)ID |
| `TEAMS_CLIENT_SECRET` | Azure AD 客户端密钥 |
| `TEAMS_TENANT_ID` | Azure AD 租户 ID |
| `TEAMS_ALLOWED_USERS` | 允许使用机器人的 AAD 对象 ID,逗号分隔 |
| `TEAMS_ALLOW_ALL_USERS` | 设为 `true` 可跳过白名单,允许所有人使用 |
| `TEAMS_HOME_CHANNEL` | 用于 cron/主动消息投递的会话 ID |
| `TEAMS_HOME_CHANNEL_NAME` | 主频道的显示名称 |
| `TEAMS_PORT` | Webhook 端口(默认:`3978` |
### config.yaml
也可通过 `~/.hermes/config.yaml` 进行配置:
```yaml
platforms:
teams:
enabled: true
extra:
client_id: "your-client-id"
client_secret: "your-secret"
tenant_id: "your-tenant-id"
port: 3978
```
---
## 功能特性
### 交互式审批卡片
当 Agent 需要执行可能存在风险的命令时,它会发送一张带有四个按钮的 Adaptive Card,而不是要求你输入 `/approve`
- **Allow Once**——仅批准此次特定命令
- **Allow Session**——在本次会话期间批准此模式
- **Always Allow**——永久批准此模式
- **Deny**——拒绝该命令
点击按钮即可内联完成审批,卡片会被替换为决策结果。
### 会议摘要投递(Teams 会议 Pipeline
当 [Teams 会议 pipeline 插件](/user-guide/messaging/msgraph-webhook)启用后,此适配器同时负责会议摘要的出站投递——一个 Teams 集成面,而非两个。会议转录摘要生成后,写入器会将摘要发布到你指定的 Teams 目标。
Pipeline 摘要投递在 `teams` 平台条目下与机器人配置并列配置:
```yaml
platforms:
teams:
enabled: true
extra:
# 现有机器人配置(client_id、client_secret、tenant_id、port...
# 会议摘要投递(仅在 teams_pipeline 插件启用时生效)
delivery_mode: "graph" # 或 "incoming_webhook"
# 对于 delivery_mode: graph — 选择其中一项:
chat_id: "19:meeting_..." # 发布到 Teams 聊天
# team_id: "..." # 或发布到频道
# channel_id: "..."
# access_token: "..." # 可选;回退到 MSGRAPH_* 应用凭据
# 对于 delivery_mode: incoming_webhook
# incoming_webhook_url: "https://outlook.office.com/webhook/..."
```
| 模式 | 适用场景 | 权衡 |
|------|----------|------|
| `incoming_webhook` | 使用 Teams 生成的静态 URL,简单地将摘要发布到某个频道。 | 不支持回复线程和表情回应,显示为 webhook 配置的身份。 |
| `graph` | 通过 Microsoft Graph 以机器人身份发布带线程的频道帖子或 1:1/群聊消息。 | 需要完成 [Graph 应用注册](/guides/microsoft-graph-app-registration),并具备 `ChannelMessage.Send`(频道)或 `Chat.ReadWrite.All`(聊天)应用权限。 |
如果 `teams_pipeline` 插件**未启用**,这些设置不会生效——它们仅在 pipeline 运行时绑定到 Graph webhook 入口时才会激活。
---
## 生产部署
对于永久服务器,跳过 devtunnel,使用服务器的公开 HTTPS 端点注册机器人:
```bash
teams app create \
--name "Hermes" \
--endpoint "https://your-domain.com/api/messages"
```
如果机器人已创建,只需更新端点:
```bash
teams app update --id <teamsAppId> --endpoint "https://your-domain.com/api/messages"
```
确保你配置的端口(`TEAMS_PORT`,默认 `3978`)可从互联网访问,且 TLS 证书有效——Teams 会拒绝自签名证书。
---
## 故障排查
| 问题 | 解决方案 |
|------|----------|
| `health` 端点正常但机器人不响应 | 检查隧道是否仍在运行,以及机器人的消息端点是否与隧道 URL 匹配 |
| 日志中出现 `KeyError: 'teams'` | 重启容器——此问题已在当前版本中修复 |
| 机器人响应时出现认证错误 | 验证 `TEAMS_CLIENT_ID``TEAMS_CLIENT_SECRET``TEAMS_TENANT_ID` 是否均已正确设置 |
| `No inference provider configured` | 检查 `~/.hermes/.env` 中是否设置了 `ANTHROPIC_API_KEY`(或其他提供商密钥) |
| 机器人收到消息但忽略它们 | 你的 AAD 对象 ID 可能不在 `TEAMS_ALLOWED_USERS` 中。运行 `teams status --verbose` 查找 |
| 隧道 URL 在重启后变更 | 使用命名隧道(`devtunnel create hermes-bot`)时,devtunnel URL 是持久的。ngrok 和 cloudflared 每次运行都会生成新 URL(除非你有付费计划)——URL 变更时请用 `teams app update` 更新机器人端点 |
| Teams 显示"此机器人未响应" | Webhook 返回了错误。检查 `docker logs hermes` 中的错误堆栈 |
| 日志中出现 `[teams] Failed to connect` | SDK 认证失败。仔细检查凭据,并确认租户 ID 与 `teams login` 时使用的账户匹配 |
---
## 安全性
:::warning
**务必设置 `TEAMS_ALLOWED_USERS`**,填入授权用户的 AAD 对象 ID。否则,任何能找到或安装你的机器人的人都可以与其交互。
`TEAMS_CLIENT_SECRET` 视同密码对待——定期通过 Azure 门户或 Teams CLI 进行轮换。
:::
- 将凭据存储在权限为 `600``~/.hermes/.env` 中(`chmod 600 ~/.hermes/.env`
- 机器人仅接受 `TEAMS_ALLOWED_USERS` 中用户的消息;未授权的消息会被静默丢弃
- 你的公开端点(`/api/messages`)由 Teams Bot Framework 进行认证——不含有效 JWT 的请求会被拒绝
## 相关文档
- [Teams 会议](/user-guide/messaging/teams-meetings)
- [运营 Teams 会议 Pipeline](/guides/operate-teams-meeting-pipeline)
@@ -0,0 +1,484 @@
---
sidebar_position: 13
title: "Webhooks"
description: "接收来自 GitHub、GitLab 等服务的事件以触发 Hermes agent 运行"
---
# Webhooks
接收来自外部服务(GitHub、GitLab、JIRA、Stripe 等)的事件,并自动触发 Hermes agent 运行。Webhook 适配器运行一个 HTTP 服务器,接受 POST 请求、验证 HMAC 签名、将 payload(载荷)转换为 agent prompt(提示词),并将响应路由回来源或其他已配置的平台。
agent 处理事件后,可通过在 PR 上发布评论、向 Telegram/Discord 发送消息或记录结果来响应。
## 视频教程
<div style={{position: 'relative', width: '100%', aspectRatio: '16 / 9', marginBottom: '1.5rem'}}>
<iframe
src="https://www.youtube.com/embed/WNYe5mD4fY8"
title="Hermes Agent — Webhooks Tutorial"
style={{position: 'absolute', top: 0, left: 0, width: '100%', height: '100%', border: 0}}
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
allowFullScreen
/>
</div>
---
## 快速开始
1. 通过 `hermes gateway setup` 或环境变量启用
2.`config.yaml` 中定义路由,**或**使用 `hermes webhook subscribe` 动态创建
3. 将你的服务指向 `http://your-server:8644/webhooks/<route-name>`
---
## 设置
有两种方式启用 webhook 适配器。
### 通过设置向导
```bash
hermes gateway setup
```
按照提示启用 webhooks、设置端口和全局 HMAC secret。
### 通过环境变量
添加到 `~/.hermes/.env`
```bash
WEBHOOK_ENABLED=true
WEBHOOK_PORT=8644 # default
WEBHOOK_SECRET=your-global-secret
```
### 验证服务器
gateway 运行后:
```bash
curl http://localhost:8644/health
```
预期响应:
```json
{"status": "ok", "platform": "webhook"}
```
---
## 配置路由 {#configuring-routes}
路由定义了不同 webhook 来源的处理方式。每个路由是 `config.yaml``platforms.webhook.extra.routes` 下的一个命名条目。
### 路由属性
| 属性 | 是否必填 | 描述 |
|----------|----------|-------------|
| `events` | 否 | 要接受的事件类型列表(例如 `["pull_request"]`)。若为空,则接受所有事件。事件类型从 `X-GitHub-Event``X-GitLab-Event` 或 payload 中的 `event_type` 读取。 |
| `secret` | **是** | 用于签名验证的 HMAC secret。若路由未设置,则回退到全局 `secret`。仅用于测试时可设为 `"INSECURE_NO_AUTH"`(跳过验证)。 |
| `prompt` | 否 | 使用点号表示法访问 payload 字段的模板字符串(例如 `{pull_request.title}`)。若省略,则将完整 JSON payload 转储到 prompt 中。 |
| `skills` | 否 | agent 运行时加载的 skill 名称列表。 |
| `deliver` | 否 | 响应发送目标:`github_comment``telegram``discord``slack``signal``sms``whatsapp``matrix``mattermost``homeassistant``email``dingtalk``feishu``wecom``weixin``bluebubbles``qqbot`,或 `log`(默认)。 |
| `deliver_extra` | 否 | 额外的投递配置——键取决于 `deliver` 类型(例如 `repo``pr_number``chat_id`)。值支持与 `prompt` 相同的 `{dot.notation}` 模板语法。 |
| `deliver_only` | 否 | 若为 `true`,完全跳过 agent——渲染后的 `prompt` 模板直接作为消息体投递。零 LLM token 消耗,亚秒级投递。参见[直接投递模式](#direct-delivery-mode)了解使用场景。要求 `deliver` 为真实目标(非 `log`)。 |
### 完整示例
```yaml
platforms:
webhook:
enabled: true
extra:
port: 8644
secret: "global-fallback-secret"
routes:
github-pr:
events: ["pull_request"]
secret: "github-webhook-secret"
prompt: |
Review this pull request:
Repository: {repository.full_name}
PR #{number}: {pull_request.title}
Author: {pull_request.user.login}
URL: {pull_request.html_url}
Diff URL: {pull_request.diff_url}
Action: {action}
skills: ["github-code-review"]
deliver: "github_comment"
deliver_extra:
repo: "{repository.full_name}"
pr_number: "{number}"
deploy-notify:
events: ["push"]
secret: "deploy-secret"
prompt: "New push to {repository.full_name} branch {ref}: {head_commit.message}"
deliver: "telegram"
```
### Prompt 模板
Prompt 使用点号表示法访问 webhook payload 中的嵌套字段:
- `{pull_request.title}` 解析为 `payload["pull_request"]["title"]`
- `{repository.full_name}` 解析为 `payload["repository"]["full_name"]`
- `{__raw__}` — 特殊 token,将**整个 payload** 以缩进 JSON 格式转储(截断至 4000 个字符)。适用于监控告警或通用 webhook,agent 需要完整上下文时使用。
- 缺失的键保留为字面量 `{key}` 字符串(不报错)
- 嵌套的 dict 和 list 会被 JSON 序列化并截断至 2000 个字符
可以将 `{__raw__}` 与常规模板变量混合使用:
```yaml
prompt: "PR #{pull_request.number} by {pull_request.user.login}: {__raw__}"
```
若路由未配置 `prompt` 模板,则将整个 payload 以缩进 JSON 格式转储(截断至 4000 个字符)。
`deliver_extra` 的值中同样支持点号表示法模板。
### 论坛话题投递
向 Telegram 投递 webhook 响应时,可通过在 `deliver_extra` 中包含 `message_thread_id`(或 `thread_id`)来指定特定论坛话题:
```yaml
webhooks:
routes:
alerts:
events: ["alert"]
prompt: "Alert: {__raw__}"
deliver: "telegram"
deliver_extra:
chat_id: "-1001234567890"
message_thread_id: "42"
```
`deliver_extra` 中未提供 `chat_id`,则回退到目标平台配置的主频道。
---
## GitHub PR 审查(分步说明) {#github-pr-review}
本演练将为每个 pull request 设置自动代码审查。
### 1. 在 GitHub 中创建 webhook
1. 进入你的仓库 → **Settings****Webhooks****Add webhook**
2.**Payload URL** 设为 `http://your-server:8644/webhooks/github-pr`
3.**Content type** 设为 `application/json`
4.**Secret** 设为与路由配置匹配的值(例如 `github-webhook-secret`
5.**Which events?** 下,选择 **Let me select individual events** 并勾选 **Pull requests**
6. 点击 **Add webhook**
### 2. 添加路由配置
按照上方示例,将 `github-pr` 路由添加到 `~/.hermes/config.yaml`
### 3. 确保 `gh` CLI 已认证
`github_comment` 投递类型使用 GitHub CLI 发布评论:
```bash
gh auth login
```
### 4. 测试
在仓库中打开一个 pull request。webhook 触发后,Hermes 处理事件并在 PR 上发布审查评论。
---
## GitLab Webhook 设置 {#gitlab-webhook-setup}
GitLab webhook 的工作方式类似,但使用不同的认证机制。GitLab 通过 `X-Gitlab-Token` 请求头以明文字符串匹配(非 HMAC)发送 secret。
### 1. 在 GitLab 中创建 webhook
1. 进入你的项目 → **Settings****Webhooks**
2.**URL** 设为 `http://your-server:8644/webhooks/gitlab-mr`
3. 输入你的 **Secret token**
4. 选择 **Merge request events**(以及其他你需要的事件)
5. 点击 **Add webhook**
### 2. 添加路由配置
```yaml
platforms:
webhook:
enabled: true
extra:
routes:
gitlab-mr:
events: ["merge_request"]
secret: "your-gitlab-secret-token"
prompt: |
Review this merge request:
Project: {project.path_with_namespace}
MR !{object_attributes.iid}: {object_attributes.title}
Author: {object_attributes.last_commit.author.name}
URL: {object_attributes.url}
Action: {object_attributes.action}
deliver: "log"
```
---
## 投递选项 {#delivery-options}
`deliver` 字段控制 agent 处理 webhook 事件后响应的发送目标。
| 投递类型 | 描述 |
|-------------|-------------|
| `log` | 将响应记录到 gateway 日志输出。这是默认值,适合测试使用。 |
| `github_comment` | 通过 `gh` CLI 将响应作为 PR/issue 评论发布。需要 `deliver_extra.repo``deliver_extra.pr_number``gh` CLI 必须安装并在 gateway 主机上完成认证(`gh auth login`)。 |
| `telegram` | 将响应路由到 Telegram。使用主频道,或在 `deliver_extra` 中指定 `chat_id`。 |
| `discord` | 将响应路由到 Discord。使用主频道,或在 `deliver_extra` 中指定 `chat_id`。 |
| `slack` | 将响应路由到 Slack。使用主频道,或在 `deliver_extra` 中指定 `chat_id`。 |
| `signal` | 将响应路由到 Signal。使用主频道,或在 `deliver_extra` 中指定 `chat_id`。 |
| `sms` | 通过 Twilio 将响应路由到 SMS。使用主频道,或在 `deliver_extra` 中指定 `chat_id`。 |
| `whatsapp` | 将响应路由到 WhatsApp。使用主频道,或在 `deliver_extra` 中指定 `chat_id`。 |
| `matrix` | 将响应路由到 Matrix。使用主频道,或在 `deliver_extra` 中指定 `chat_id`。 |
| `mattermost` | 将响应路由到 Mattermost。使用主频道,或在 `deliver_extra` 中指定 `chat_id`。 |
| `homeassistant` | 将响应路由到 Home Assistant。使用主频道,或在 `deliver_extra` 中指定 `chat_id`。 |
| `email` | 将响应路由到 Email。使用主频道,或在 `deliver_extra` 中指定 `chat_id`。 |
| `dingtalk` | 将响应路由到 DingTalk。使用主频道,或在 `deliver_extra` 中指定 `chat_id`。 |
| `feishu` | 将响应路由到 Feishu/Lark。使用主频道,或在 `deliver_extra` 中指定 `chat_id`。 |
| `wecom` | 将响应路由到 WeCom。使用主频道,或在 `deliver_extra` 中指定 `chat_id`。 |
| `weixin` | 将响应路由到 Weixin(微信)。使用主频道,或在 `deliver_extra` 中指定 `chat_id`。 |
| `bluebubbles` | 将响应路由到 BlueBubblesiMessage)。使用主频道,或在 `deliver_extra` 中指定 `chat_id`。 |
跨平台投递时,目标平台也必须在 gateway 中启用并连接。若 `deliver_extra` 中未提供 `chat_id`,响应将发送到该平台配置的主频道。
---
## 直接投递模式 {#direct-delivery-mode}
默认情况下,每次 webhook POST 都会触发一次 agent 运行——payload 成为 prompt,agent 处理后投递响应。这会在每次事件时消耗 LLM token。
对于只需**推送纯文本通知**的场景——无需推理、无需 agent 循环,只需投递消息——可在路由上设置 `deliver_only: true`。渲染后的 `prompt` 模板直接作为消息体,适配器将其直接分发到配置的投递目标。
### 何时使用直接投递
- **外部服务推送** — Supabase/Firebase webhook 在数据库变更时触发 → 即时通知 Telegram 用户
- **监控告警** — Datadog/Grafana 告警 webhook → 推送到 Discord 频道
- **agent 间通知** — Agent A 通知 Agent B 的用户某个长时任务已完成
- **后台任务完成** — Cron 任务完成 → 将结果发布到 Slack
优势:
- **零 LLM token** — agent 从不被调用
- **亚秒级投递** — 单次适配器调用,无推理循环
- **与 agent 模式相同的安全性** — HMAC 认证、速率限制、幂等性和请求体大小限制均正常生效
- **同步响应** — 投递成功后 POST 返回 `200 OK`,若目标拒绝则返回 `502`,便于上游服务智能重试
### 示例:从 Supabase 推送到 Telegram
```yaml
platforms:
webhook:
enabled: true
extra:
port: 8644
secret: "global-secret"
routes:
antenna-matches:
secret: "antenna-webhook-secret"
deliver: "telegram"
deliver_only: true
prompt: "🎉 New match: {match.user_name} matched with you!"
deliver_extra:
chat_id: "{match.telegram_chat_id}"
```
你的 Supabase edge function 使用 HMAC-SHA256 对 payload 签名并 POST 到 `https://your-server:8644/webhooks/antenna-matches`。webhook 适配器验证签名、从 payload 渲染模板、投递到 Telegram,并返回 `200 OK`
### 示例:通过 CLI 动态订阅
```bash
hermes webhook subscribe antenna-matches \
--deliver telegram \
--deliver-chat-id "123456789" \
--deliver-only \
--prompt "🎉 New match: {match.user_name} matched with you!" \
--description "Antenna match notifications"
```
### 响应状态码
| 状态码 | 含义 |
|--------|---------|
| `200 OK` | 投递成功。响应体:`{"status": "delivered", "route": "...", "target": "...", "delivery_id": "..."}` |
| `200 OK`status=duplicate | 在幂等性 TTL(1 小时)内重复的 `X-GitHub-Delivery` ID。不重复投递。 |
| `401 Unauthorized` | HMAC 签名无效或缺失。 |
| `400 Bad Request` | JSON 请求体格式错误。 |
| `404 Not Found` | 未知路由名称。 |
| `413 Payload Too Large` | 请求体超过 `max_body_bytes`。 |
| `429 Too Many Requests` | 路由速率限制已超出。 |
| `502 Bad Gateway` | 目标适配器拒绝消息或抛出异常。错误记录在服务端日志中;响应体为通用的 `Delivery failed`,避免泄露适配器内部信息。 |
### 配置注意事项
- `deliver_only: true` 要求 `deliver` 为真实目标。`deliver: log`(或省略 `deliver`)在启动时会被拒绝——适配器发现路由配置错误时拒绝启动。
- 直接投递模式下 `skills` 字段被忽略(不运行 agent,无处注入 skill)。
- 模板渲染使用与 agent 模式相同的 `{dot.notation}` 语法,包括 `{__raw__}` token。
- 幂等性使用相同的 `X-GitHub-Delivery` / `X-Request-ID` 请求头——携带相同 ID 的重试返回 `status=duplicate` 且**不**重复投递。
---
## 动态订阅(CLI {#dynamic-subscriptions}
除了 `config.yaml` 中的静态路由,还可以使用 `hermes webhook` CLI 命令动态创建 webhook 订阅。当 agent 本身需要设置事件驱动触发器时,这尤为有用。
### 创建订阅
```bash
hermes webhook subscribe github-issues \
--events "issues" \
--prompt "New issue #{issue.number}: {issue.title}\nBy: {issue.user.login}\n\n{issue.body}" \
--deliver telegram \
--deliver-chat-id "-100123456789" \
--description "Triage new GitHub issues"
```
此命令返回 webhook URL 和自动生成的 HMAC secret。将你的服务配置为 POST 到该 URL。
### 列出订阅
```bash
hermes webhook list
```
### 删除订阅
```bash
hermes webhook remove github-issues
```
### 测试订阅
```bash
hermes webhook test github-issues
hermes webhook test github-issues --payload '{"issue": {"number": 42, "title": "Test"}}'
```
### 动态订阅的工作原理
- 订阅存储在 `~/.hermes/webhook_subscriptions.json`
- webhook 适配器在每次收到请求时热重载该文件(基于 mtime 检测,开销可忽略不计)
- `config.yaml` 中的静态路由始终优先于同名的动态订阅
- 动态订阅与静态路由使用相同的格式和功能(events、prompt 模板、skills、delivery
- 无需重启 gateway——订阅后立即生效
### agent 驱动的订阅
agent 可通过 terminal 工具在 `webhook-subscriptions` skill 的引导下创建订阅。向 agent 请求"为 GitHub issues 设置 webhook",它将运行相应的 `hermes webhook subscribe` 命令。
---
## 安全性 {#security}
webhook 适配器包含多层安全机制:
### HMAC 签名验证
适配器使用适合各来源的方式验证传入的 webhook 签名:
- **GitHub**`X-Hub-Signature-256` 请求头——以 `sha256=` 为前缀的 HMAC-SHA256 十六进制摘要
- **GitLab**`X-Gitlab-Token` 请求头——明文 secret 字符串匹配
- **通用**`X-Webhook-Signature` 请求头——原始 HMAC-SHA256 十六进制摘要
若已配置 secret 但请求中不存在已识别的签名请求头,则请求被拒绝。
### Secret 为必填项
每个路由必须有 secret——直接设置在路由上或从全局 `secret` 继承。没有 secret 的路由会导致适配器在启动时报错退出。仅用于开发/测试时,可将 secret 设为 `"INSECURE_NO_AUTH"` 以完全跳过验证。
`INSECURE_NO_AUTH` 仅在 gateway 绑定到回环地址(`127.0.0.1``localhost``::1`)时被接受。若与非回环绑定(如 `0.0.0.0` 或局域网 IP)组合使用,适配器拒绝启动——这可防止在公共接口上意外暴露未认证的端点。
### 速率限制
每个路由默认限制为**每分钟 30 次请求**(固定窗口)。可全局配置:
```yaml
platforms:
webhook:
extra:
rate_limit: 60 # requests per minute
```
超出限制的请求收到 `429 Too Many Requests` 响应。
### 幂等性
投递 ID(来自 `X-GitHub-Delivery``X-Request-ID` 或时间戳回退)缓存 **1 小时**。重复投递(例如 webhook 重试)会被静默跳过并返回 `200` 响应,防止重复触发 agent 运行。
### 请求体大小限制
超过 **1 MB** 的 payload 在读取请求体之前即被拒绝。可配置:
```yaml
platforms:
webhook:
extra:
max_body_bytes: 2097152 # 2 MB
```
### Prompt 注入风险
:::warning
Webhook payload 包含攻击者可控的数据——PR 标题、commit 消息、issue 描述等均可能包含恶意指令。在暴露于互联网时,请在沙箱环境(Docker、VM)中运行 gateway。考虑使用 Docker 或 SSH terminal 后端进行隔离。
:::
---
## 故障排查 {#troubleshooting}
### Webhook 未到达
- 验证端口已暴露且可从 webhook 来源访问
- 检查防火墙规则——端口 `8644`(或你配置的端口)必须开放
- 验证 URL 路径是否匹配:`http://your-server:8644/webhooks/<route-name>`
- 使用 `/health` 端点确认服务器正在运行
### 签名验证失败
- 确保路由配置中的 secret 与 webhook 来源中配置的 secret 完全一致
- 对于 GitHubsecret 基于 HMAC——检查 `X-Hub-Signature-256`
- 对于 GitLabsecret 为明文 token 匹配——检查 `X-Gitlab-Token`
- 检查 gateway 日志中的 `Invalid signature` 警告
### 事件被忽略
- 检查事件类型是否在路由的 `events` 列表中
- GitHub 事件使用如 `pull_request``push``issues` 等值(`X-GitHub-Event` 请求头的值)
- GitLab 事件使用如 `merge_request``push` 等值(`X-GitLab-Event` 请求头的值)
-`events` 为空或未设置,则接受所有事件
### Agent 未响应
- 在前台运行 gateway 以查看日志:`hermes gateway run`
- 检查 prompt 模板是否正确渲染
- 验证投递目标已配置并连接
### 重复响应
- 幂等性缓存应能防止此问题——检查 webhook 来源是否发送了投递 ID 请求头(`X-GitHub-Delivery``X-Request-ID`
- 投递 ID 缓存 1 小时
### `gh` CLI 错误(GitHub 评论投递)
- 在 gateway 主机上运行 `gh auth login`
- 确保已认证的 GitHub 用户对该仓库有写权限
- 检查 `gh` 是否已安装并在 PATH 中
---
## 环境变量 {#environment-variables}
| 变量 | 描述 | 默认值 |
|----------|-------------|---------|
| `WEBHOOK_ENABLED` | 启用 webhook 平台适配器 | `false` |
| `WEBHOOK_PORT` | 接收 webhook 的 HTTP 服务器端口 | `8644` |
| `WEBHOOK_SECRET` | 全局 HMAC secret(路由未指定自身 secret 时作为回退) | _(无)_ |
@@ -0,0 +1,149 @@
---
sidebar_position: 15
---
# WeCom 回调(自建应用)
通过回调/webhook 模式,将 Hermes 作为企业自建应用接入企业微信(WeCom)。
:::info WeCom Bot 与 WeCom 回调
Hermes 支持两种企业微信集成模式:
- **[WeCom Bot](wecom.md)** — Bot 风格,通过 WebSocket 连接。配置简单,支持群聊。
- **WeCom 回调**(本页)— 自建应用,接收加密 XML 回调。在用户企业微信侧边栏中显示为一级应用,支持多企业路由。
:::
## 工作原理
1. 在企业微信管理后台注册自建应用
2. 企业微信将加密 XML 推送至你的 HTTP 回调端点
3. Hermes 解密消息,将其加入 agent 处理队列
4. 立即确认(静默——不向用户显示任何内容)
5. Agent 处理请求(通常需要 3–30 分钟)
6. 通过企业微信 `message/send` API 主动下发回复
## 前置条件
- 具有管理员权限的企业微信账号
- `aiohttp``httpx` Python 包(默认安装已包含)
- 可公网访问的服务器用于回调 URL(或使用 ngrok 等隧道工具)
## 配置步骤
### 1. 在企业微信中创建自建应用
1. 进入[企业微信管理后台](https://work.weixin.qq.com/) → **应用管理****创建应用**
2. 记录你的 **Corp ID**(显示在管理后台顶部)
3. 在应用设置中创建 **Corp Secret**
4. 在应用概览页记录 **Agent ID**
5. 在**接收消息**下配置回调 URL:
- URL`http://YOUR_PUBLIC_IP:8645/wecom/callback`
- Token:生成一个随机 token(企业微信会提供)
- EncodingAESKey:生成一个密钥(企业微信会提供)
### 2. 配置环境变量
`.env` 文件中添加:
```bash
WECOM_CALLBACK_CORP_ID=your-corp-id
WECOM_CALLBACK_CORP_SECRET=your-corp-secret
WECOM_CALLBACK_AGENT_ID=1000002
WECOM_CALLBACK_TOKEN=your-callback-token
WECOM_CALLBACK_ENCODING_AES_KEY=your-43-char-aes-key
# 可选
WECOM_CALLBACK_HOST=0.0.0.0
WECOM_CALLBACK_PORT=8645
WECOM_CALLBACK_ALLOWED_USERS=user1,user2
```
### 3. 启动 Gateway
```bash
hermes gateway
```
(仅在通过 `hermes gateway install` 注册 systemd/launchd 服务后,才使用 `hermes gateway start`。)
回调适配器会在配置的端口上启动 HTTP 服务器。企业微信将通过 GET 请求验证回调 URL,随后开始通过 POST 发送消息。
## 配置参考
`config.yaml``platforms.wecom_callback.extra` 下设置,或使用环境变量:
| 配置项 | 默认值 | 说明 |
|--------|--------|------|
| `corp_id` | — | 企业微信 Corp ID(必填) |
| `corp_secret` | — | 自建应用的 Corp Secret(必填) |
| `agent_id` | — | 自建应用的 Agent ID(必填) |
| `token` | — | 回调验证 token(必填) |
| `encoding_aes_key` | — | 43 字符的 AES 密钥,用于回调加密(必填) |
| `host` | `0.0.0.0` | HTTP 回调服务器绑定地址 |
| `port` | `8645` | HTTP 回调服务器端口 |
| `path` | `/wecom/callback` | 回调端点的 URL 路径 |
## 多应用路由
对于运行多个自建应用的企业(例如跨部门或子公司),在 `config.yaml` 中配置 `apps` 列表:
```yaml
platforms:
wecom_callback:
enabled: true
extra:
host: "0.0.0.0"
port: 8645
apps:
- name: "dept-a"
corp_id: "ww_corp_a"
corp_secret: "secret-a"
agent_id: "1000002"
token: "token-a"
encoding_aes_key: "key-a-43-chars..."
- name: "dept-b"
corp_id: "ww_corp_b"
corp_secret: "secret-b"
agent_id: "1000003"
token: "token-b"
encoding_aes_key: "key-b-43-chars..."
```
用户以 `corp_id:user_id` 为作用域,防止跨企业冲突。当用户发送消息时,适配器会记录其所属应用(企业),并通过对应应用的 access token 路由回复。
## 访问控制
限制哪些用户可以与应用交互:
```bash
# 白名单指定用户
WECOM_CALLBACK_ALLOWED_USERS=zhangsan,lisi,wangwu
# 或允许所有用户
WECOM_CALLBACK_ALLOW_ALL_USERS=true
```
## 端点
适配器暴露以下端点:
| 方法 | 路径 | 用途 |
|------|------|------|
| GET | `/wecom/callback` | URL 验证握手(企业微信在配置时发送) |
| POST | `/wecom/callback` | 加密消息回调(企业微信将用户消息发送至此) |
| GET | `/health` | 健康检查——返回 `{"status": "ok"}` |
## 加密
所有回调载荷均使用 EncodingAESKey 通过 AES-CBC 加密。适配器处理:
- **入站**:解密 XML 载荷,验证 SHA1 签名
- **出站**:通过主动调用 API 发送回复(非加密回调响应)
加密实现与腾讯官方 WXBizMsgCrypt SDK 兼容。
## 限制
- **不支持流式输出** — 回复在 agent 完成处理后以完整消息形式送达
- **不支持正在输入提示** — 回调模式不支持输入状态
- **仅支持文本** — 目前仅支持文本消息输入;图片/文件/语音输入尚未实现。Agent 可通过企业微信平台提示感知出站媒体能力(图片、文档、视频、语音)。
- **响应延迟** — Agent 会话需要 3–30 分钟;用户在处理完成后收到回复
@@ -0,0 +1,292 @@
---
sidebar_position: 14
title: "WeCom(企业微信)"
description: "通过 AI Bot WebSocket 网关将 Hermes Agent 连接到 WeCom"
---
# WeCom(企业微信)
将 Hermes 连接到 [WeCom](https://work.weixin.qq.com/)(企业微信),腾讯的企业即时通讯平台。该适配器使用 WeCom 的 AI Bot WebSocket 网关实现实时双向通信——无需公开端点或 webhook。
## 前提条件
- 一个 WeCom 组织账号
- 在 WeCom 管理后台创建的 AI Bot
- 来自机器人凭据页面的 Bot ID 和 Secret
- Python 包:`aiohttp``httpx`
## 设置
### 第一步:创建 AI Bot
#### 推荐方式:扫码创建(一条命令)
```bash
hermes gateway setup
```
选择 **WeCom**,用企业微信手机端扫描二维码。Hermes 将自动创建具有正确权限的机器人应用并保存凭据。
设置向导将:
1. 在终端中显示二维码
2. 等待你用企业微信手机端扫描
3. 自动获取 Bot ID 和 Secret
4. 引导你完成访问控制配置
#### 备选方式:手动设置
如果扫码创建不可用,向导将回退到手动输入:
1. 登录 [WeCom 管理后台](https://work.weixin.qq.com/wework_admin/frame)
2. 导航至 **应用管理****创建应用****AI Bot**
3. 配置机器人名称和描述
4. 从凭据页面复制 **Bot ID****Secret**
5. 运行 `hermes gateway setup`,选择 **WeCom**,并在提示时输入凭据
:::warning
请妥善保管 Bot Secret。任何持有它的人都可以冒充你的机器人。
:::
### 第二步:配置 Hermes
#### 方式 A:交互式设置(推荐)
```bash
hermes gateway setup
```
选择 **WeCom** 并按照提示操作。向导将引导你完成:
- 机器人凭据(通过二维码扫描或手动输入)
- 访问控制设置(白名单、配对模式或开放访问)
- 用于通知的主频道
#### 方式 B:手动配置
将以下内容添加到 `~/.hermes/.env`
```bash
WECOM_BOT_ID=your-bot-id
WECOM_SECRET=your-secret
# 可选:限制访问
WECOM_ALLOWED_USERS=user_id_1,user_id_2
# 可选:用于定时任务/通知的主频道
WECOM_HOME_CHANNEL=chat_id
```
### 第三步:启动网关
```bash
hermes gateway
```
## 功能特性
- **WebSocket 传输** — 持久连接,无需公开端点
- **私聊和群组消息** — 可配置的访问策略
- **按群组的发送者白名单** — 精细控制每个群组中可交互的用户
- **媒体支持** — 图片、文件、语音、视频的上传和下载
- **AES 加密媒体** — 自动解密入站附件
- **引用上下文** — 保留回复线程
- **Markdown 渲染** — 富文本响应
- **回复模式流式传输** — 将响应与入站消息上下文关联
- **自动重连** — 连接断开时指数退避重试
## 配置选项
`config.yaml``platforms.wecom.extra` 下设置以下选项:
| 键 | 默认值 | 描述 |
|-----|---------|-------------|
| `bot_id` | — | WeCom AI Bot ID(必填) |
| `secret` | — | WeCom AI Bot Secret(必填) |
| `websocket_url` | `wss://openws.work.weixin.qq.com` | WebSocket 网关 URL |
| `dm_policy` | `open` | 私聊访问策略:`open``allowlist``disabled``pairing` |
| `group_policy` | `open` | 群组访问策略:`open``allowlist``disabled` |
| `allow_from` | `[]` | 允许私聊的用户 ID(当 dm_policy=allowlist 时) |
| `group_allow_from` | `[]` | 允许的群组 ID(当 group_policy=allowlist 时) |
| `groups` | `{}` | 按群组配置(见下文) |
## 访问策略
### 私聊策略
控制哪些用户可以向机器人发送私信:
| 值 | 行为 |
|-------|----------|
| `open` | 任何人均可私聊机器人(默认) |
| `allowlist` | 仅 `allow_from` 中的用户 ID 可私聊 |
| `disabled` | 所有私聊均被忽略 |
| `pairing` | 配对模式(用于初始设置) |
```bash
WECOM_DM_POLICY=allowlist
```
### 群组策略
控制机器人在哪些群组中响应:
| 值 | 行为 |
|-------|----------|
| `open` | 机器人在所有群组中响应(默认) |
| `allowlist` | 机器人仅在 `group_allow_from` 中列出的群组 ID 中响应 |
| `disabled` | 所有群组消息均被忽略 |
```bash
WECOM_GROUP_POLICY=allowlist
```
### 按群组的发送者白名单
如需精细控制,可以限制特定群组内哪些用户可以与机器人交互。在 `config.yaml` 中配置:
```yaml
platforms:
wecom:
enabled: true
extra:
bot_id: "your-bot-id"
secret: "your-secret"
group_policy: "allowlist"
group_allow_from:
- "group_id_1"
- "group_id_2"
groups:
group_id_1:
allow_from:
- "user_alice"
- "user_bob"
group_id_2:
allow_from:
- "user_charlie"
"*":
allow_from:
- "user_admin"
```
**工作原理:**
1. `group_policy``group_allow_from` 控制决定某个群组是否被允许。
2. 如果群组通过了顶层检查,`groups.<group_id>.allow_from` 列表(如果存在)将进一步限制该群组内哪些发送者可以与机器人交互。
3. 通配符 `"*"` 群组条目作为未明确列出的群组的默认配置。
4. 白名单条目支持 `*` 通配符以允许所有用户,且条目不区分大小写。
5. 条目可以选择使用 `wecom:user:``wecom:group:` 前缀格式——前缀会被自动去除。
如果某个群组未配置 `allow_from`,则该群组中的所有用户均被允许(前提是该群组本身通过了顶层策略检查)。
## 媒体支持
### 入站(接收)
适配器接收用户发送的媒体附件并在本地缓存,供 Agent 处理:
| 类型 | 处理方式 |
|------|-----------------|
| **图片** | 下载并在本地缓存。支持基于 URL 和 base64 编码的图片。 |
| **文件** | 下载并缓存。文件名从原始消息中保留。 |
| **语音** | 如果可用,提取语音消息的文字转录。 |
| **混合消息** | WeCom 混合类型消息(文本 + 图片)会被解析并提取所有组件。 |
**引用消息:** 被引用(回复)消息中的媒体也会被提取,以便 Agent 了解用户正在回复的内容。
### AES 加密媒体解密
WeCom 对部分入站媒体附件使用 AES-256-CBC 加密。适配器会自动处理:
- 当入站媒体项包含 `aeskey` 字段时,适配器下载加密字节并使用带 PKCS#7 填充的 AES-256-CBC 进行解密。
- AES 密钥是 `aeskey` 字段的 base64 解码值(必须恰好为 32 字节)。
- IV 由密钥的前 16 字节派生。
- 此功能需要 `cryptography` Python 包(`pip install cryptography`)。
无需任何配置——收到加密媒体时解密会自动透明地进行。
### 出站(发送)
| 方法 | 发送内容 | 大小限制 |
|--------|--------------|------------|
| `send` | Markdown 文本消息 | 4000 字符 |
| `send_image` / `send_image_file` | 原生图片消息 | 10 MB |
| `send_document` | 文件附件 | 20 MB |
| `send_voice` | 语音消息(原生语音仅支持 AMR 格式) | 2 MB |
| `send_video` | 视频消息 | 10 MB |
**分块上传:** 文件通过三步协议(初始化 → 分块 → 完成)以 512 KB 为单位分块上传。适配器会自动处理此过程。
**自动降级:** 当媒体超过原生类型的大小限制但低于 20 MB 绝对限制时,会自动作为通用文件附件发送:
- 图片 > 10 MB → 作为文件发送
- 视频 > 10 MB → 作为文件发送
- 语音 > 2 MB → 作为文件发送
- 非 AMR 音频 → 作为文件发送(WeCom 原生语音仅支持 AMR)
超过 20 MB 绝对限制的文件将被拒绝,并向聊天发送提示消息。
## 回复模式流式响应
当机器人通过 WeCom 回调接收到消息时,适配器会记住入站请求 ID。如果在请求上下文仍然有效期间发送响应,适配器将使用 WeCom 的回复模式(`aibot_respond_msg`)配合流式传输,将响应直接与入站消息关联。这在 WeCom 客户端中提供了更自然的对话体验。
如果入站请求上下文已过期或不可用,适配器将回退到通过 `aibot_send_msg` 主动发送消息。
回复模式同样适用于媒体:上传的媒体可以作为对原始消息的回复发送。
## 连接与重连
适配器在 `wss://openws.work.weixin.qq.com` 维护与 WeCom 网关的持久 WebSocket 连接。
### 连接生命周期
1. **连接:** 建立 WebSocket 连接,并发送包含 bot_id 和 secret 的 `aibot_subscribe` 认证帧。
2. **心跳:** 每 30 秒发送一次应用层 ping 帧以保持连接活跃。
3. **监听:** 持续读取入站帧并分发消息回调。
### 重连行为
连接断开时,适配器使用指数退避进行重连:
| 尝试次数 | 延迟 |
|---------|-------|
| 第 1 次重试 | 2 秒 |
| 第 2 次重试 | 5 秒 |
| 第 3 次重试 | 10 秒 |
| 第 4 次重试 | 30 秒 |
| 第 5 次及以后 | 60 秒 |
每次成功重连后,退避计数器重置为零。断开连接时所有待处理的请求 future 都会失败,以防调用方无限期挂起。
### 去重
入站消息使用消息 ID 进行去重,时间窗口为 5 分钟,最大缓存 1000 条。这可防止在重连或网络抖动期间重复处理消息。
## 所有环境变量
| 变量 | 是否必填 | 默认值 | 描述 |
|----------|----------|---------|-------------|
| `WECOM_BOT_ID` | ✅ | — | WeCom AI Bot ID |
| `WECOM_SECRET` | ✅ | — | WeCom AI Bot Secret |
| `WECOM_ALLOWED_USERS` | — | _(空)_ | 网关级白名单的逗号分隔用户 ID |
| `WECOM_HOME_CHANNEL` | — | — | 定时任务/通知输出的聊天 ID |
| `WECOM_WEBSOCKET_URL` | — | `wss://openws.work.weixin.qq.com` | WebSocket 网关 URL |
| `WECOM_DM_POLICY` | — | `open` | 私聊访问策略 |
| `WECOM_GROUP_POLICY` | — | `open` | 群组访问策略 |
## 故障排查
| 问题 | 解决方法 |
|---------|-----|
| `WECOM_BOT_ID and WECOM_SECRET are required` | 设置两个环境变量,或在设置向导中配置 |
| `WeCom startup failed: aiohttp not installed` | 安装 aiohttp`pip install aiohttp` |
| `WeCom startup failed: httpx not installed` | 安装 httpx`pip install httpx` |
| `invalid secret (errcode=40013)` | 验证 secret 是否与机器人凭据匹配 |
| `Timed out waiting for subscribe acknowledgement` | 检查到 `openws.work.weixin.qq.com` 的网络连通性 |
| 机器人在群组中不响应 | 检查 `group_policy` 设置,并确保群组 ID 在 `group_allow_from` 中 |
| 机器人忽略群组中的某些用户 | 检查 `groups` 配置节中按群组的 `allow_from` 列表 |
| 媒体解密失败 | 安装 `cryptography``pip install cryptography` |
| `cryptography is required for WeCom media decryption` | 入站媒体已被 AES 加密。安装:`pip install cryptography` |
| 语音消息作为文件发送 | WeCom 原生语音仅支持 AMR 格式,其他格式会自动降级为文件。 |
| `File too large` 错误 | WeCom 对所有文件上传有 20 MB 的绝对限制。请压缩或拆分文件。 |
| 图片作为文件发送 | 图片 > 10 MB 超过原生图片限制,会自动降级为文件附件。 |
| `Timeout sending message to WeCom` | WebSocket 可能已断开。检查日志中的重连消息。 |
| `WeCom websocket closed during authentication` | 网络问题或凭据不正确。验证 bot_id 和 secret。 |
@@ -0,0 +1,312 @@
---
sidebar_position: 15
title: "微信(Weixin"
description: "通过 iLink Bot API 将 Hermes Agent 连接到个人微信账号"
---
# 微信(Weixin / WeChat
将 Hermes 连接到 [微信](https://weixin.qq.com/)(WeChat),腾讯的个人即时通讯平台。该适配器使用腾讯的 **iLink Bot API** 对接个人微信账号——与企业微信(WeCom)不同。消息通过长轮询(long-polling)方式传递,无需公网端点或 webhook。
:::info
本适配器适用于**个人微信账号**(微信)。如需对接企业微信,请参阅 [WeCom 适配器](./wecom.md)。
:::
:::warning iLink bot 身份——普通微信群可能无法使用
扫码登录后,Hermes 连接的是一个 **iLink bot 身份**(例如 `a5ace6fd482e@im.bot`),**而非**可完全脚本化的普通个人微信账号。具体影响如下:
- iLink bot 身份通常**无法像普通联系人一样被邀请进入普通微信群**。
- 对于大多数 bot 类型账号,iLink 通常**不会将普通微信群事件**(包括对扫码登录所用个人账号的 `@` 提及)推送到网关。
- `@` 提及用于扫码的个人微信账号,**不等同于** `@` 提及 iLink bot——两者是独立身份。
- 下方的 `WEIXIN_GROUP_POLICY` / `WEIXIN_GROUP_ALLOWED_USERS` 设置仅在 iLink 实际为你的账号类型返回群事件时才生效。若 iLink 不返回群事件,无论策略如何配置,群消息都不会到达 Hermes。
实际部署中,大多数情况下只有发送给 iLink bot 的私信(DM)能可靠工作。若配置完成后群消息仍无法送达,限制来自 iLink 侧,而非 Hermes。只要 `WEIXIN_GROUP_POLICY` 设置为 `disabled` 以外的值,网关在启动时会记录一条 `WARNING`
:::
## 前置条件
- 一个个人微信账号
- Python 包:`aiohttp``cryptography`
- 使用 `messaging` 扩展安装 Hermes 时已内置终端二维码渲染功能
安装所需依赖:
```bash
pip install aiohttp cryptography
# 可选:用于终端二维码显示
pip install hermes-agent[messaging]
```
## 配置步骤
### 1. 运行配置向导
连接微信账号最简便的方式是通过交互式配置向导:
```bash
hermes gateway setup
```
在提示中选择 **Weixin**。向导将执行以下步骤:
1. 向 iLink Bot API 请求二维码
2. 在终端中显示二维码(或提供 URL)
3. 等待你用微信手机端扫描二维码
4. 提示你在手机上确认登录
5. 自动将账号凭据保存至 `~/.hermes/weixin/accounts/`
确认后,你将看到如下消息:
```
微信连接成功,account_id=your-account-id
```
向导会保存 `account_id``token``base_url`,无需手动配置。
### 2. 配置环境变量
完成首次扫码登录后,在 `~/.hermes/.env` 中至少设置账号 ID
```bash
WEIXIN_ACCOUNT_ID=your-account-id
# 可选:覆盖 token(通常由扫码登录自动保存)
# WEIXIN_TOKEN=your-bot-token
# 可选:限制访问权限
WEIXIN_DM_POLICY=open
WEIXIN_ALLOWED_USERS=user_id_1,user_id_2
# 可选:恢复旧版多行拆分行为
# WEIXIN_SPLIT_MULTILINE_MESSAGES=true
# 可选:cron/通知的默认频道
WEIXIN_HOME_CHANNEL=chat_id
WEIXIN_HOME_CHANNEL_NAME=Home
```
### 3. 启动网关
```bash
hermes gateway
```
适配器将恢复已保存的凭据,连接到 iLink API,并开始长轮询消息。
## 功能特性
- **长轮询传输** — 无需公网端点、webhook 或 WebSocket
- **扫码登录** — 通过 `hermes gateway setup` 扫码连接
- **私信(DM)消息** — 可配置访问策略;群消息功能取决于 iLink 是否实际为所连接身份推送群事件(iLink bot 账号通常不推送,详见上方警告)
- **媒体支持** — 图片、视频、文件和语音消息
- **AES-128-ECB 加密 CDN** — 所有媒体传输自动加解密
- **上下文 token 持久化** — 基于磁盘的回复连续性,重启后仍可保持
- **Markdown 格式化** — 保留 Markdown 格式(包括标题、表格和代码块),支持 Markdown 的微信客户端可原生渲染
- **智能消息分块** — 未超出长度限制时保持单条消息气泡;仅超长内容在逻辑边界处拆分
- **正在输入提示** — 代理处理消息时在微信客户端显示"正在输入…"状态
- **SSRF 防护** — 下载前验证外发媒体 URL
- **消息去重** — 5 分钟滑动窗口防止重复处理
- **自动重试与退避** — 从瞬时 API 错误中自动恢复
## 配置选项
`config.yaml``platforms.weixin.extra` 下设置:
| 键 | 默认值 | 说明 |
|-----|---------|-------------|
| `account_id` | — | iLink Bot 账号 ID(必填) |
| `token` | — | iLink Bot token(必填,由扫码登录自动保存) |
| `base_url` | `https://ilinkai.weixin.qq.com` | iLink API 基础 URL |
| `cdn_base_url` | `https://novac2c.cdn.weixin.qq.com/c2c` | 媒体传输 CDN 基础 URL |
| `dm_policy` | `open` | 私信访问策略:`open``allowlist``disabled``pairing` |
| `group_policy` | `disabled` | 群组访问策略:`open``allowlist``disabled` |
| `allow_from` | `[]` | 允许发送私信的用户 ID(当 dm_policy=allowlist 时生效) |
| `group_allow_from` | `[]` | 允许的群组 ID(当 group_policy=allowlist 时生效) |
| `split_multiline_messages` | `false` | 为 `true` 时,将多行回复拆分为多条消息(旧版行为);为 `false` 时,多行回复保持为单条消息,除非超出长度限制。 |
## 访问策略
### 私信策略
控制哪些用户可以向 bot 发送私信:
| 值 | 行为 |
|-------|----------|
| `open` | 任何人均可向 bot 发送私信(默认) |
| `allowlist` | 仅 `allow_from` 中的用户 ID 可发送私信 |
| `disabled` | 忽略所有私信 |
| `pairing` | 配对模式(用于初始设置) |
```bash
WEIXIN_DM_POLICY=allowlist
WEIXIN_ALLOWED_USERS=user_id_1,user_id_2
```
### 群组策略
控制 bot 在哪些群组中响应消息,**前提是 iLink 为所连接身份推送了群事件**。对于扫码登录的 iLink bot 身份(例如 `...@im.bot`),群事件通常根本不会被推送,因此该策略可能不起作用——详见页面顶部的 iLink bot 限制警告。
| 值 | 行为 |
|-------|----------|
| `open` | bot 在所有群组中响应(如果事件被推送) |
| `allowlist` | bot 仅在 `group_allow_from` 中列出的群组 ID 中响应(如果事件被推送) |
| `disabled` | 忽略所有群消息(默认) |
```bash
WEIXIN_GROUP_POLICY=allowlist
# 注意:这是以逗号分隔的群聊 ID 列表,而非成员用户 ID,
# 尽管变量名中包含"USERS"。配置时请注意区分。
WEIXIN_GROUP_ALLOWED_USERS=group_id_1,group_id_2
```
:::note
微信的默认群组策略为 `disabled`(与企业微信默认为 `open` 不同)。这是有意为之——个人微信账号可能加入了很多群,且 iLink bot 身份通常根本无法接收普通微信群消息。若将 `WEIXIN_GROUP_POLICY` 设置为 `disabled` 以外的值,网关在启动时会记录一条 `WARNING`
:::
## 媒体支持
### 入站(接收)
适配器接收用户发送的媒体附件,从微信 CDN 下载并解密,然后在本地缓存供代理处理:
| 类型 | 处理方式 |
|------|-----------------|
| **图片** | 下载、AES 解密后缓存为 JPEG。 |
| **视频** | 下载、AES 解密后缓存为 MP4。 |
| **文件** | 下载、AES 解密后缓存,保留原始文件名。 |
| **语音** | 若有文字转录,则提取为文本;否则下载音频(SILK 格式)并缓存。 |
**引用消息:** 引用(回复)消息中的媒体也会被提取,以便代理了解用户回复的上下文。
### AES-128-ECB 加密 CDN
微信媒体文件通过加密 CDN 传输。适配器透明处理加解密:
- **入站:** 使用 `encrypted_query_param` URL 从 CDN 下载加密媒体,再使用消息载荷中提供的每文件密钥进行 AES-128-ECB 解密。
- **出站:** 使用随机 AES-128-ECB 密钥在本地加密文件,上传至 CDN,并在出站消息中包含加密引用。
- AES 密钥为 16 字节(128 位)。密钥可能以原始 base64 或十六进制编码形式到达——适配器两种格式均支持。
- 需要安装 `cryptography` Python 包。
无需任何配置——加解密自动完成。
### 出站(发送)
| 方法 | 发送内容 |
|--------|--------------|
| `send` | 带 Markdown 格式的文本消息 |
| `send_image` / `send_image_file` | 原生图片消息(通过 CDN 上传) |
| `send_document` | 文件附件(通过 CDN 上传) |
| `send_video` | 视频消息(通过 CDN 上传) |
所有出站媒体均通过加密 CDN 上传流程处理:
1. 生成随机 AES-128 密钥
2. 使用 AES-128-ECB + PKCS#7 填充加密文件
3. 向 iLink API 请求上传 URL`getuploadurl`
4. 将密文上传至 CDN
5. 发送包含加密媒体引用的消息
## 上下文 Token 持久化
iLink Bot API 要求在每条出站消息中回传 `context_token`(针对特定对话方)。适配器维护一个基于磁盘的上下文 token 存储:
- Token 按账号+对话方保存至 `~/.hermes/weixin/accounts/<account_id>.context-tokens.json`
- 启动时恢复之前保存的 token
- 每条入站消息都会更新该发送方的已存储 token
- 出站消息自动包含最新的上下文 token
这确保了即使网关重启后,回复连续性也不会中断。
## Markdown 格式化
通过 iLink Bot API 连接的微信客户端可以直接渲染 Markdown,因此适配器保留 Markdown 而不对其进行改写:
- **标题** 保持为 Markdown 标题格式(`#``##` 等)
- **表格** 保持为 Markdown 表格
- **代码围栏** 保持为围栏代码块
- **多余空行** 在围栏代码块外折叠为双换行
## 消息分块
消息在不超出平台限制时以单条消息发送。仅超长内容才会被拆分发送:
- 最大消息长度:**4000 个字符**
- 未超出限制的消息保持完整,即使包含多个段落或换行
- 超长消息在逻辑边界处拆分(段落、空行、代码围栏)
- 代码围栏尽可能保持完整(除非围栏本身超出限制,否则不在块中间拆分)
- 超长的单个块回退到基础适配器的截断逻辑
- 发送多个分块时,块间延迟 0.3 秒,防止触发微信频率限制
## 正在输入提示
适配器在微信客户端中显示输入状态:
1. 消息到达时,适配器通过 `getconfig` API 获取 `typing_ticket`
2. 输入票据(typing ticket)按用户缓存 10 分钟
3. `send_typing` 发送开始输入信号;`stop_typing` 发送停止输入信号
4. 网关在代理处理消息期间自动触发输入提示
## 长轮询连接
适配器使用 HTTP 长轮询(而非 WebSocket)接收消息:
### 工作原理
1. **连接:** 验证凭据并启动轮询循环
2. **轮询:** 以 35 秒超时调用 `getupdates`;服务器保持请求直到消息到达或超时
3. **分发:** 入站消息通过 `asyncio.create_task` 并发分发
4. **同步缓冲区:** 持久化同步游标(`get_updates_buf`)保存至磁盘,确保重启后从正确位置恢复
### 重试行为
发生 API 错误时,适配器采用简单的重试策略:
| 条件 | 行为 |
|-----------|----------|
| 瞬时错误(第 1–2 次) | 2 秒后重试 |
| 持续错误(第 3 次及以上) | 退避 30 秒后重置计数器 |
| 会话过期(`errcode=-14`) | 暂停 10 分钟(可能需要重新登录) |
| 超时 | 立即重新轮询(正常长轮询行为) |
### 去重
入站消息使用消息 ID 在 5 分钟窗口内去重,防止网络抖动或轮询响应重叠时重复处理。
### Token 锁
同一时间只有一个微信网关实例可以使用给定的 token。适配器在启动时获取作用域锁,关闭时释放。若另一个网关已在使用相同 token,启动将失败并显示详细错误信息。
## 所有环境变量
| 变量 | 必填 | 默认值 | 说明 |
|----------|----------|---------|-------------|
| `WEIXIN_ACCOUNT_ID` | ✅ | — | iLink Bot 账号 ID(来自扫码登录) |
| `WEIXIN_TOKEN` | ✅ | — | iLink Bot token(由扫码登录自动保存) |
| `WEIXIN_BASE_URL` | — | `https://ilinkai.weixin.qq.com` | iLink API 基础 URL |
| `WEIXIN_CDN_BASE_URL` | — | `https://novac2c.cdn.weixin.qq.com/c2c` | 媒体传输 CDN 基础 URL |
| `WEIXIN_DM_POLICY` | — | `open` | 私信访问策略:`open``allowlist``disabled``pairing` |
| `WEIXIN_GROUP_POLICY` | — | `disabled` | 群组访问策略:`open``allowlist``disabled` |
| `WEIXIN_ALLOWED_USERS` | — | _(空)_ | 私信白名单的逗号分隔用户 ID |
| `WEIXIN_GROUP_ALLOWED_USERS` | — | _(空)_ | 群组白名单的逗号分隔**群聊 ID**(非成员用户 ID)。变量名为历史遗留,实际填写的是群 ID 而非用户 ID。 |
| `WEIXIN_HOME_CHANNEL` | — | — | cron/通知输出的聊天 ID |
| `WEIXIN_HOME_CHANNEL_NAME` | — | `Home` | 默认频道的显示名称 |
| `WEIXIN_ALLOW_ALL_USERS` | — | — | 网关级别的允许所有用户标志(由配置向导使用) |
## 故障排查
| 问题 | 解决方法 |
|---------|-----|
| `Weixin startup failed: aiohttp and cryptography are required` | 安装两者:`pip install aiohttp cryptography` |
| `Weixin startup failed: WEIXIN_TOKEN is required` | 运行 `hermes gateway setup` 完成扫码登录,或手动设置 `WEIXIN_TOKEN` |
| `Weixin startup failed: WEIXIN_ACCOUNT_ID is required` | 在 `.env` 中设置 `WEIXIN_ACCOUNT_ID`,或运行 `hermes gateway setup` |
| `Another local Hermes gateway is already using this Weixin token` | 先停止另一个网关实例——每个 token 只允许一个轮询器 |
| 会话过期(`errcode=-14` | 登录会话已过期。重新运行 `hermes gateway setup` 扫描新二维码 |
| 配置过程中二维码过期 | 二维码最多自动刷新 3 次。若持续过期,请检查网络连接 |
| Bot 不响应私信 | 检查 `WEIXIN_DM_POLICY`——若设置为 `allowlist`,发送方必须在 `WEIXIN_ALLOWED_USERS` 中 |
| Bot 忽略群消息 | 群组策略默认为 `disabled`。设置 `WEIXIN_GROUP_POLICY=open``allowlist`——但请注意,扫码登录的 iLink bot 身份(`...@im.bot`)通常根本无法接收普通微信群消息。若网关日志中没有群消息的原始入站事件,限制来自 iLink 侧,而非 Hermes。 |
| 媒体下载/上传失败 | 确保已安装 `cryptography`。检查对 `novac2c.cdn.weixin.qq.com` 的网络访问 |
| `Blocked unsafe URL (SSRF protection)` | 出站媒体 URL 指向私有/内部地址,仅允许公网 URL |
| 语音消息显示为文本 | 若微信提供了转录文本,适配器会使用文本内容,这是预期行为 |
| 消息出现重复 | 适配器通过消息 ID 去重。若仍出现重复,检查是否有多个网关实例在运行 |
| `iLink POST ... HTTP 4xx/5xx` | iLink 服务返回 API 错误。检查 token 有效性和网络连通性 |
| 终端二维码无法渲染 | 使用 messaging 扩展重新安装:`pip install hermes-agent[messaging]`。或者,打开二维码上方打印的 URL |
@@ -0,0 +1,236 @@
---
sidebar_position: 5
title: "WhatsApp"
description: "通过内置 Baileys 桥接将 Hermes Agent 设置为 WhatsApp 机器人"
---
# WhatsApp 配置
Hermes 通过基于 **Baileys** 的内置桥接连接到 WhatsApp。其工作原理是模拟 WhatsApp Web 会话——**而非**通过官方 WhatsApp Business API。无需 Meta 开发者账号或 Business 认证。
:::warning 非官方 API — 封号风险
WhatsApp **不**官方支持 Business API 以外的第三方机器人。使用第三方桥接存在账号受限的小概率风险。为降低风险:
- **为机器人使用专用手机号**(而非个人号码)
- **不要发送批量/垃圾消息**——保持对话式使用
- **不要向未主动发消息的用户自动发送外发消息**
:::
:::warning WhatsApp Web 协议更新
WhatsApp 会定期更新其 Web 协议,这可能导致第三方桥接暂时失效。
发生这种情况时,Hermes 会更新桥接依赖。如果机器人在 WhatsApp 更新后停止工作,
请拉取最新版 Hermes 并重新配对。
:::
## 两种模式
| 模式 | 工作方式 | 适用场景 |
|------|---------|---------|
| **独立机器人号码**(推荐) | 为机器人专用一个手机号,用户直接向该号码发消息。 | 体验简洁、多用户、封号风险低 |
| **个人自聊** | 使用你自己的 WhatsApp,向自己发消息与 Agent 对话。 | 快速配置、单用户、测试用途 |
---
## 前置条件
- **Node.js v18+** 和 **npm**——WhatsApp 桥接作为 Node.js 进程运行
- **已安装 WhatsApp 的手机**(用于扫描二维码)
与旧版浏览器驱动的桥接不同,当前基于 Baileys 的桥接**不**需要本地 Chromium 或 Puppeteer 依赖栈。
---
## 第一步:运行配置向导
```bash
hermes whatsapp
```
向导将:
1. 询问你想要哪种模式(**bot** 或 **self-chat**
2. 如有需要,安装桥接依赖
3. 在终端中显示**二维码**
4. 等待你扫描
**扫描二维码的步骤:**
1. 在手机上打开 WhatsApp
2. 进入**设置 → 已关联设备**
3. 点击**关联设备**
4. 将摄像头对准终端中的二维码
配对成功后,向导确认连接并退出。你的会话将自动保存。
:::tip
如果二维码显示乱码,请确保终端宽度至少为 60 列且支持 Unicode。
也可以尝试换用其他终端模拟器。
:::
---
## 第二步:获取第二个手机号(机器人模式)
机器人模式需要一个尚未注册 WhatsApp 的手机号。有三种选择:
| 选项 | 费用 | 说明 |
|------|------|------|
| **Google Voice** | 免费 | 仅限美国。在 [voice.google.com](https://voice.google.com) 获取号码,通过 Google Voice 应用以短信验证 WhatsApp。 |
| **预付费 SIM 卡** | 一次性 $5–15 | 任意运营商。激活后验证 WhatsApp,SIM 卡可放置不用。号码需保持有效(每 90 天拨打一次电话)。 |
| **VoIP 服务** | 免费–$5/月 | TextNow、TextFree 等。部分 VoIP 号码被 WhatsApp 屏蔽——如第一个不可用,可多试几个。 |
获取号码后:
1. 在手机上安装 WhatsApp(或使用支持双 SIM 的 WhatsApp Business 应用)
2. 用新号码注册 WhatsApp
3. 运行 `hermes whatsapp` 并从该 WhatsApp 账号扫描二维码
---
## 第三步:配置 Hermes
`~/.hermes/.env` 文件中添加以下内容:
```bash
# 必填
WHATSAPP_ENABLED=true
WHATSAPP_MODE=bot # "bot" 或 "self-chat"
# 访问控制——选择以下其中一项:
WHATSAPP_ALLOWED_USERS=15551234567 # 逗号分隔的手机号(含国家代码,不含 +)
# WHATSAPP_ALLOWED_USERS=* # 或使用 * 允许所有人
# WHATSAPP_ALLOW_ALL_USERS=true # 或设置此标志(效果等同于 *)
```
:::tip 允许所有人的简写
`WHATSAPP_ALLOWED_USERS=*` 设置为允许**所有**发送者(等同于 `WHATSAPP_ALLOW_ALL_USERS=true`)。
这与 [Signal 群组白名单](/reference/environment-variables) 保持一致。
如需使用配对流程,请移除这两个变量,改用
[私信配对系统](/user-guide/security#dm-pairing-system)。
:::
`~/.hermes/config.yaml` 中可选的行为设置:
```yaml
unauthorized_dm_behavior: pair
whatsapp:
unauthorized_dm_behavior: ignore
```
- `unauthorized_dm_behavior: pair` 是全局默认值。未知私信发送者将收到配对码。
- `whatsapp.unauthorized_dm_behavior: ignore` 使 WhatsApp 对未授权私信保持静默,通常更适合私人号码。
然后启动 gateway(网关):
```bash
hermes gateway # 前台运行
hermes gateway install # 安装为用户服务
sudo hermes gateway install --system # 仅 Linux:开机启动系统服务
```
Gateway 会使用已保存的会话自动启动 WhatsApp 桥接。
---
## 会话持久化
Baileys 桥接将会话保存在 `~/.hermes/platforms/whatsapp/session` 目录下。这意味着:
- **会话在重启后仍然有效**——无需每次重新扫描二维码
- 会话数据包含加密密钥和设备凭证
- **请勿共享或提交此会话目录**——它可授予对 WhatsApp 账号的完整访问权限
---
## 重新配对
如果会话中断(手机重置、WhatsApp 更新、手动取消关联),你将在 gateway 日志中看到连接错误。修复方法:
```bash
hermes whatsapp
```
这将生成新的二维码。重新扫描后会话即恢复。Gateway 会通过重连逻辑自动处理**临时**断线(网络抖动、手机短暂离线)。
---
## 语音消息
Hermes 支持 WhatsApp 上的语音功能:
- **接收:** 语音消息(`.ogg` opus 格式)会使用已配置的 STT 提供商自动转录:本地 `faster-whisper`、Groq Whisper`GROQ_API_KEY`)或 OpenAI Whisper`VOICE_TOOLS_OPENAI_KEY`
- **发送:** TTS 响应以 MP3 音频文件附件形式发送
- Agent 响应默认以"⚕ **Hermes Agent**"为前缀。可在 `config.yaml` 中自定义或禁用:
```yaml
# ~/.hermes/config.yaml
whatsapp:
reply_prefix: "" # 空字符串禁用标题
# reply_prefix: "🤖 *My Bot*\n──────\n" # 自定义前缀(支持 \n 换行)
```
---
## 消息格式与投递
WhatsApp 支持**流式(渐进式)响应**——机器人在 AI 生成文本时实时编辑消息,与 Discord 和 Telegram 一样。在内部,WhatsApp 被归类为 TIER_MEDIUM 平台(投递能力中等)。
### 分块
长响应会自动按每块 **4,096 个字符**拆分为多条消息(WhatsApp 的实际显示上限)。无需任何配置——gateway 会自动处理拆分并按顺序发送各块。
### WhatsApp 兼容 Markdown
AI 响应中的标准 Markdown 会自动转换为 WhatsApp 的原生格式:
| Markdown | WhatsApp | 渲染效果 |
|----------|----------|---------|
| `**bold**` | `*bold*` | **粗体** |
| `~~strikethrough~~` | `~strikethrough~` | ~~删除线~~ |
| `# Heading` | `*Heading*` | 粗体文本(无原生标题) |
| `[link text](url)` | `link text (url)` | 内联 URL |
代码块和内联代码保持原样,因为 WhatsApp 原生支持三反引号格式。
### 工具进度
当 Agent 调用工具(网页搜索、文件操作等)时,WhatsApp 会显示实时进度指示器,显示正在运行的工具。此功能默认启用,无需配置。
---
## 故障排查
| 问题 | 解决方案 |
|------|---------|
| **二维码无法扫描** | 确保终端宽度足够(60 列以上)。尝试换用其他终端。确保从正确的 WhatsApp 账号(机器人号码,而非个人号码)扫描。 |
| **二维码过期** | 二维码约每 20 秒刷新一次。如果超时,重新运行 `hermes whatsapp`。 |
| **会话未持久化** | 检查 `~/.hermes/platforms/whatsapp/session` 是否存在且可写。如在容器中运行,请将其挂载为持久卷。 |
| **意外退出登录** | WhatsApp 会在长时间不活跃后取消关联设备。保持手机开机并连接网络,如有需要使用 `hermes whatsapp` 重新配对。 |
| **桥接崩溃或重连循环** | 重启 gateway,更新 Hermes,如会话因 WhatsApp 协议变更而失效则重新配对。 |
| **WhatsApp 更新后机器人停止工作** | 更新 Hermes 以获取最新桥接版本,然后重新配对。 |
| **macOS"Node.js not installed"但终端中 node 可用** | launchd 服务不继承你的 shell PATH。运行 `hermes gateway install` 将当前 PATH 重新快照到 plist 中,然后运行 `hermes gateway start`。详见 [Gateway 服务文档](./index.md#macos-launchd)。 |
| **未收到消息** | 确认 `WHATSAPP_ALLOWED_USERS` 包含发送者号码(含国家代码,不含 `+` 或空格),或将其设为 `*` 允许所有人。在 `.env` 中设置 `WHATSAPP_DEBUG=true` 并重启 gateway,可在 `bridge.log` 中查看原始消息事件。 |
| **机器人向陌生人回复配对码** | 如需对未授权私信静默处理,在 `~/.hermes/config.yaml` 中设置 `whatsapp.unauthorized_dm_behavior: ignore`。 |
---
## 安全
:::warning
**上线前请配置访问控制。**`WHATSAPP_ALLOWED_USERS` 中填写具体手机号(含国家代码,不含 `+`),
使用 `*` 允许所有人,或设置 `WHATSAPP_ALLOW_ALL_USERS=true`
若未配置上述任何一项,gateway 将**拒绝所有传入消息**作为安全措施。
:::
默认情况下,未授权私信仍会收到配对码回复。如果你希望私人 WhatsApp 号码对陌生人完全静默,请设置:
```yaml
whatsapp:
unauthorized_dm_behavior: ignore
```
- `~/.hermes/platforms/whatsapp/session` 目录包含完整会话凭证——请像保护密码一样保护它
- 设置文件权限:`chmod 700 ~/.hermes/platforms/whatsapp/session`
- 为机器人使用**专用手机号**,将风险与个人账号隔离
- 如怀疑账号被入侵,在 WhatsApp → 设置 → 已关联设备中取消关联该设备
- 日志中的手机号已部分脱敏,但请审查你的日志保留策略
@@ -0,0 +1,341 @@
---
sidebar_position: 16
title: "Yuanbao"
description: "通过 WebSocket gateway 将 Hermes Agent 连接到元宝企业消息平台"
---
# Yuanbao
将 Hermes 连接到腾讯企业消息平台 [元宝(Yuanbao](https://yuanbao.tencent.com/)。该适配器使用 WebSocket gateway 实现实时消息传递,支持单聊(C2C)和群聊两种会话模式。
:::info
元宝是一个企业消息平台,主要用于腾讯内部及企业环境。它使用 WebSocket 进行实时通信,采用基于 HMAC 的认证方式,支持图片、文件和语音消息等富媒体内容。
:::
## 前提条件
- 拥有机器人创建权限的元宝账号
- 元宝 APP_ID 和 APP_SECRET(由平台管理员提供)
- Python 包:`websockets``httpx`
- 媒体支持需要:`aiofiles`
安装所需依赖:
```bash
pip install websockets httpx aiofiles
```
## 配置
### 1. 在元宝中创建机器人
1. 从 [https://yuanbao.tencent.com/](https://yuanbao.tencent.com/) 下载元宝应用
2. 在应用中进入 **PAI → 我的机器人**,创建一个新机器人
3. 机器人创建完成后,复制 **APP_ID****APP_SECRET**
### 2. 运行配置向导
配置元宝最简便的方式是通过交互式向导:
```bash
hermes gateway setup
```
在提示时选择 **Yuanbao**。向导将:
1. 询问你的 APP_ID
2. 询问你的 APP_SECRET
3. 自动保存配置
:::tip
WebSocket URL 和 API Domain 均内置了合理的默认值。只需提供 APP_ID 和 APP_SECRET 即可开始使用。
:::
### 3. 配置环境变量
初始配置完成后,在 `~/.hermes/.env` 中验证以下变量:
```bash
# 必填
YUANBAO_APP_ID=your-app-id
YUANBAO_APP_SECRET=your-app-secret
YUANBAO_WS_URL=wss://api.yuanbao.example.com/ws
YUANBAO_API_DOMAIN=https://api.yuanbao.example.com
# 可选:机器人账号 ID(通常从 sign-token 自动获取)
# YUANBAO_BOT_ID=your-bot-id
# 可选:内部路由环境(如 test/staging/production
# YUANBAO_ROUTE_ENV=production
# 可选:cron/通知的主频道(格式:direct:<account> 或 group:<group_code>
YUANBAO_HOME_CHANNEL=direct:bot_account_id
YUANBAO_HOME_CHANNEL_NAME="Bot Notifications"
# 可选:限制访问(旧版,细粒度策略请参见下方访问控制)
YUANBAO_ALLOWED_USERS=user_account_1,user_account_2
```
### 4. 启动 Gateway
```bash
hermes gateway
```
适配器将连接到元宝 WebSocket gateway,使用 HMAC 签名进行认证,并开始处理消息。
## 功能特性
- **WebSocket gateway** — 实时双向通信
- **HMAC 认证** — 使用 APP_ID/APP_SECRET 进行安全请求签名
- **C2C 消息** — 用户与机器人的单聊会话
- **群聊消息** — 群组聊天中的会话
- **媒体支持** — 通过 COS(云对象存储)支持图片、文件和语音消息
- **Markdown 格式化** — 消息自动分块以适应元宝的大小限制
- **消息去重** — 防止同一消息被重复处理
- **心跳/保活** — 维持 WebSocket 连接稳定性
- **输入状态指示** — 在 agent 处理期间显示"正在输入…"状态
- **自动重连** — 以指数退避方式处理 WebSocket 断线
- **群组信息查询** — 获取群组详情和成员列表
- **表情/Emoji 支持** — 在会话中发送 TIMFaceElem 表情和 emoji
- **自动设置主频道** — 第一个向机器人发消息的用户自动成为主频道所有者
- **慢响应通知** — 当 agent 处理时间超出预期时发送等待提示
## 配置选项
### 聊天 ID 格式
元宝根据会话类型使用带前缀的标识符:
| 聊天类型 | 格式 | 示例 |
|----------|------|------|
| 单聊(C2C | `direct:<account>` | `direct:user123` |
| 群聊 | `group:<group_code>` | `group:grp456` |
### 媒体上传
元宝适配器通过 COS(腾讯云对象存储)自动处理媒体上传:
- **图片**:支持 JPEG、PNG、GIF、WebP
- **文件**:支持所有常见文档类型
- **语音**:支持 WAV、MP3、OGG
媒体 URL 在上传前会自动验证并下载,以防止 SSRF 攻击。
## 主频道
在任意元宝聊天(单聊或群聊)中使用 `/sethome` 命令,将其指定为**主频道**。定时任务(cron job)的结果将发送到该频道。
:::tip 自动设置主频道
如果未配置主频道,第一个向机器人发消息的用户将自动成为主频道所有者。如果当前主频道是群聊,第一条单聊消息将把主频道升级为直接频道。
:::
也可以在 `~/.hermes/.env` 中手动设置:
```bash
YUANBAO_HOME_CHANNEL=direct:user_account_id
# 或者设置为群组:
# YUANBAO_HOME_CHANNEL=group:group_code
YUANBAO_HOME_CHANNEL_NAME="My Bot Updates"
```
### 示例:设置主频道
1. 在元宝中与机器人开始对话
2. 发送命令:`/sethome`
3. 机器人回复:"Home channel set to [chat_name] with ID [chat_id]. Cron jobs will deliver to this location."
4. 后续 cron job 和通知将发送到该频道
### 示例:Cron Job 投递
创建一个 cron job
```bash
/cron "0 9 * * *" Check server status
```
定时输出将在每天上午 9 点发送到你的元宝主频道。
## 使用技巧
### 开始对话
在元宝中向机器人发送任意消息:
```
hello
```
机器人将在同一会话线程中回复。
### 可用命令
所有标准 Hermes 命令均可在元宝上使用:
| 命令 | 描述 |
|------|------|
| `/new` | 开始新对话 |
| `/model [provider:model]` | 查看或切换模型 |
| `/sethome` | 将当前聊天设为主频道 |
| `/status` | 显示会话信息 |
| `/help` | 显示可用命令 |
### 发送文件
在元宝聊天中直接附加文件即可发送给机器人。机器人将自动下载并处理附件。
也可以在附件中附带消息:
```
Please analyze this document
```
### 接收文件
当你要求机器人创建或导出文件时,它会直接将文件发送到你的元宝聊天中。
## 故障排查
### 机器人在线但不响应消息
**原因**:WebSocket 握手期间认证失败。
**解决方法**
1. 验证 APP_ID 和 APP_SECRET 是否正确
2. 检查 WebSocket URL 是否可访问
3. 确保机器人账号拥有适当权限
4. 查看 gateway 日志:`tail -f ~/.hermes/logs/gateway.log`
### "Connection refused" 错误
**原因**WebSocket URL 不可达或不正确。
**解决方法**
1. 验证 WebSocket URL 格式(应以 `wss://` 开头)
2. 检查到元宝 API 域名的网络连通性
3. 确认防火墙允许 WebSocket 连接
4. 使用以下命令测试 URL`curl -I https://[YUANBAO_API_DOMAIN]`
### 媒体上传失败
**原因**:COS 凭证无效或媒体服务器不可达。
**解决方法**
1. 验证 API_DOMAIN 是否正确
2. 检查机器人是否已启用媒体上传权限
3. 确保媒体文件可访问且未损坏
4. 联系平台管理员检查 COS bucket 配置
### 消息未投递到主频道
**原因**:主频道 ID 格式不正确或 cron job 尚未触发。
**解决方法**
1. 验证 YUANBAO_HOME_CHANNEL 格式是否正确
2. 使用 `/sethome` 命令自动检测正确格式
3. 使用 `/status` 检查 cron job 计划
4. 验证机器人在目标聊天中是否有发送权限
### 频繁断线
**原因**:WebSocket 连接不稳定或网络不可靠。
**解决方法**
1. 检查 gateway 日志中的错误模式
2. 在连接设置中增加心跳超时时间
3. 确保到元宝 API 的网络连接稳定
4. 考虑启用详细日志:`HERMES_LOG_LEVEL=debug`
## 访问控制
元宝支持对单聊和群聊进行细粒度访问控制:
```bash
# 单聊策略:open(默认)| allowlist | disabled
YUANBAO_DM_POLICY=open
# 允许单聊机器人的用户 ID,逗号分隔(仅在 DM_POLICY=allowlist 时生效)
YUANBAO_DM_ALLOW_FROM=user_id_1,user_id_2
# 群聊策略:open(默认)| allowlist | disabled
YUANBAO_GROUP_POLICY=open
# 允许的群组代码,逗号分隔(仅在 GROUP_POLICY=allowlist 时生效)
YUANBAO_GROUP_ALLOW_FROM=group_code_1,group_code_2
```
也可以在 `config.yaml` 中设置:
```yaml
platforms:
yuanbao:
extra:
dm_policy: allowlist
dm_allow_from: "user1,user2"
group_policy: open
group_allow_from: ""
```
## 高级配置
### 消息分块
元宝有最大消息大小限制。Hermes 自动对大响应进行分块,采用 Markdown 感知拆分(遵守代码围栏、表格和段落边界)。
### 连接参数
以下连接参数内置于适配器中,具有合理的默认值:
| 参数 | 默认值 | 描述 |
|------|--------|------|
| WebSocket 连接超时 | 15 秒 | 等待 WS 握手的时间 |
| 心跳间隔 | 30 秒 | 保持连接活跃的 ping 频率 |
| 最大重连次数 | 100 | 最大重连尝试次数 |
| 重连退避 | 1s → 60s(指数) | 重连尝试之间的等待时间 |
| 回复心跳间隔 | 2 秒 | RUNNING 状态发送频率 |
| 发送超时 | 30 秒 | 出站 WS 消息的超时时间 |
:::note
这些值目前无法通过环境变量配置,已针对典型元宝部署场景进行优化。
:::
### 详细日志
启用 debug 日志以排查连接问题:
```bash
HERMES_LOG_LEVEL=debug hermes gateway
```
## 与其他功能集成
### Cron Job
在元宝上调度定时任务:
```
/cron "0 */4 * * *" Report system health
```
结果将投递到你的主频道。
### 后台任务
在不阻塞会话的情况下运行长时间操作:
```
/background Analyze all files in the archive
```
### 跨平台消息
从 CLI 向元宝发送消息:
```bash
hermes chat -q "Send 'Hello from CLI' to yuanbao:group:group_code"
```
## 相关文档
- [消息 Gateway 概览](./index.md)
- [斜杠命令参考](/reference/slash-commands)
- [Cron Job](/user-guide/features/cron)
- [后台会话](/user-guide/cli#background-sessions)