訊息閘道器是一個長駐程序,透過統一架構將 Hermes 連接到 20 多個外部訊息平台。
關鍵檔案
| 檔案 | 用途 |
|---|---|
gateway/run.py | GatewayRunner — 主迴圈、斜線命令、訊息分派(大型檔案;請查看 git 取得目前行數) |
gateway/session.py | SessionStore — 對話持久化和會話金鑰建構 |
gateway/delivery.py | 外發訊息遞送到目標平台/頻道 |
gateway/pairing.py | DM 配對流程用於使用者授權 |
gateway/channel_directory.py | 將聊天 ID 映射為人類可讀的名稱,用於排程遞送 |
gateway/hooks.py | Hook 發現、載入和生命週期事件分派 |
gateway/mirror.py | 用於 send_message 的跨會話訊息鏡像 |
gateway/status.py | 用於 profile 範圍閘道器實例的 Token 鎖管理 |
gateway/builtin_hooks/ | 永遠註冊的 hook 擴充點(目前無內建) |
gateway/platforms/ | 平台適配器(每個訊息平台一個) |
架構概覽
┌─────────────────────────────────────────────────┐
│ GatewayRunner │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Telegram │ │ Discord │ │ Slack │ │
│ │ Adapter │ │ Adapter │ │ Adapter │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
│ │ │ │ │
│ └─────────────┼─────────────┘ │
│ ▼ │
│ _handle_message() │
│ │ │
│ ┌───────────┼───────────┐ │
│ ▼ ▼ ▼ │
│ Slash command AIAgent Queue/BG │
│ dispatch creation sessions │
│ │ │
│ ▼ │
│ SessionStore │
│ (SQLite persistence) │
└───────┴─────────────┴─────────────┴─────────────┘
訊息流程
當訊息從任何平台抵達時:
- 平台適配器收到原始事件,將其正規化為
MessageEvent - 基礎適配器檢查活動會話守衛:
- 如果 Agent 正在為此會話執行 → 排隊訊息,設定中斷事件
- 如果是
/approve、/deny、/stop→ 繞過守衛(直接分派)
- **GatewayRunner._handle_message()**接收事件:
- 透過
_session_key_for_source()解析會話金鑰(格式:agent:main:{platform}:{chat_type}:{chat_id}) - 檢查授權(見下方授權)
- 檢查是否為斜線命令 → 分派到命令處理器
- 檢查 Agent 是否已在執行 → 攔截
/stop、/status等命令 - 否則 → 建立
AIAgent實例並執行對話
- 透過
- 回應透過平台適配器送回
會話金鑰格式
會話金鑰編碼完整的路由上下文:
agent:main:{platform}:{chat_type}:{chat_id}
例如:agent:main:telegram:private:123456789
支援執行緒的平台(Telegram 論壇主題、Discord 執行緒、Slack 執行緒)可能在 chat_id 部分包含執行緒 ID。永遠不要手動建構會話金鑰——始終使用 gateway/session.py 中的 build_session_key()。
雙層訊息守衛
當 Agent 正在活躍執行時,傳入訊息通過兩個順序守衛:
-
第 1 層——基礎適配器(
gateway/platforms/base.py):檢查_active_sessions。如果會話處於活躍狀態,將訊息排隊到_pending_messages並設定中斷事件。這在訊息到達閘道器運行器之前就攔截了它們。 -
第 2 層——閘道器運行器(
gateway/run.py):檢查_running_agents。攔截特定命令(/stop、/new、/queue、/status、/approve、/deny)並適當路由。其他所有操作觸發running_agent.interrupt()。
在 Agent 被阻塞時必須到達運行器的命令(如 /approve)透過 await self._message_handler(event) 直接分派——它們繞過背景任務系統以避免競爭條件。
授權
閘道器使用多層授權檢查,按順序評估:
- 每個平台的全域允許標誌(例如
TELEGRAM_ALLOW_ALL_USERS)——若設定,該平台上所有使用者都被授權 - 平台允許清單(例如
TELEGRAM_ALLOWED_USERS)——逗號分隔的使用者 ID - DM 配對——已驗證的使用者可透過配對碼配對新使用者
- 全域允許(
GATEWAY_ALLOW_ALL_USERS)——若設定,所有平台上所有使用者都被授權 - 預設:拒絕——未授權的使用者被拒絕
DM 配對流程
Admin: /pair
Gateway: "Pairing code: ABC123. Share with the user."
New user: ABC123
Gateway: "Paired! You're now authorized."
配對狀態在 gateway/pairing.py 中持久化,並在重啟後存活。
斜線命令分派
閘道器中的所有斜線命令都通過相同的解析管線:
hermes_cli/commands.py中的resolve_command()將輸入映射為標準名稱(處理別名、前綴匹配)- 標準名稱針對
GATEWAY_KNOWN_COMMANDS進行檢查 _handle_message()中的處理器根據標準名稱分派- 部分命令受設定閘控(
CommandDef上的gateway_config_gate)
活動 Agent 守衛
在 Agent 處理期間不能執行的命令會被提前拒絕:
if _quick_key in self._running_agents:
if canonical == "model":
return "⏳ Agent is running — wait for it to finish or /stop first."
繞過命令(/stop、/new、/approve、/deny、/queue、/status)有特殊處理。
設定來源
閘道器從多個來源讀取設定:
| 來源 | 提供的內容 |
|---|---|
~/.hermes/.env | API 金鑰、機器人權杖、平台憑證 |
~/.hermes/config.yaml | 模型設定、工具設定、顯示選項 |
| 環境變數 | 覆寫上述任何設定 |
與 CLI(使用 load_cli_config() 搭配硬編碼預設值)不同,閘道器直接透過 YAML 載入器讀取 config.yaml。這意味著存在於 CLI 預設字典中但不在使用者設定檔中的設定鍵,在 CLI 和閘道器之間可能有不同的行為。
平台適配器
每個訊息平台在 gateway/platforms/ 中有一個適配器:
gateway/platforms/
├── base.py # BaseAdapter——所有平台共用的邏輯
├── telegram.py # Telegram Bot API(長輪詢或 webhook)
├── discord.py # 透過 discord.py 的 Discord 機器人
├── slack.py # Slack Socket Mode
├── whatsapp.py # WhatsApp Business Cloud API
├── signal.py # 透過 signal-cli REST API 的 Signal
├── matrix.py # 透過 mautrix 的 Matrix(可選 E2EE)
├── mattermost.py # Mattermost WebSocket API
├── email.py # 透過 IMAP/SMTP 的 Email
├── sms.py # 透過 Twilio 的 SMS
├── dingtalk.py # DingTalk WebSocket
├── feishu.py # Feishu/Lark WebSocket 或 webhook
├── wecom.py # WeCom(企業微信)callback
├── weixin.py # Weixin(個人微信)透過 iLink Bot API
├── bluebubbles.py # 透過 BlueBubbles macOS 伺服器的 Apple iMessage
├── qqbot/ # QQ Bot(騰訊 QQ)透過 Official API v2(子套件:adapter.py、crypto.py、keyboards.py、…)
├── yuanbao.py # Yuanbao(騰訊)DM/群組適配器
├── feishu_comment.py # Feishu 文件/雲端硬碟留言回覆處理器
├── msgraph_webhook.py # Microsoft Graph 變更通知 webhook(Teams、Outlook 等)
├── webhook.py # 入站/出站 webhook 適配器
├── api_server.py # REST API 伺服器適配器
└── homeassistant.py # Home Assistant 對話整合
適配器實作共用介面:
connect()/disconnect()— 生命週期管理send_message()— 外發訊息遞送on_message()— 入站訊息正規化 →MessageEvent
Token 鎖
使用唯一憑證連接的適配器在 connect() 中呼叫 acquire_scoped_lock(),在 disconnect() 中呼叫 release_scoped_lock()。這防止兩個 profile 同時使用相同的機器人權杖。
遞送路徑
外發遞送(gateway/delivery.py)處理:
- 直接回覆 — 將回應送回原始聊天
- 主頻道遞送 — 將排程任務輸出和背景結果路由到設定的主頻道
- 明確目標遞送 —
send_message工具指定telegram:-1001234567890,或用於 shell 腳本的hermes sendCLI 包裝相同工具 - 跨平台遞送 — 遞送到與原始訊息不同的平台
排程任務遞送不會被鏡像到閘道器會話歷史中——它們只存在於自己的排程會話中。這是刻意的設計選擇,以避免訊息交替違反。
Hooks
閘道器 hooks 是回應生命週期事件的 Python 模組:
閘道器 Hook 事件
| 事件 | 觸發時機 |
|---|---|
gateway:startup | 閘道器程序啟動 |
session:start | 新對話會話開始 |
session:end | 會話完成或逾時 |
session:reset | 使用者透過 /new 重置會話 |
agent:start | Agent 開始處理訊息 |
agent:step | Agent 完成一次工具呼叫迭代 |
agent:end | Agent 結束並回傳回應 |
command:* | 任何斜線命令被執行 |
Hooks 從 gateway/builtin_hooks/(一個擴充點——目前在發行版中為空;_register_builtin_hooks() 是一個無操作的 stub)和 ~/.hermes/hooks/(使用者安裝)發現。每個 hook 是一個包含 HOOK.yaml 設定檔和 handler.py 的目錄。
Memory Provider 整合
當 memory provider 外掛(例如 Honcho)啟用時:
- 閘道器為每則訊息建立帶有 session ID 的
AIAgent MemoryManager用會話上下文初始化 provider- Provider 工具(例如
honcho_profile、viking_search)透過以下路由:
AIAgent._invoke_tool()
→ self._memory_manager.handle_tool_call(name, args)
→ provider.handle_tool_call(name, args)
- 在會話結束/重置時,
on_session_end()觸發以進行清理和最終資料刷新
記憶刷新生命週期
當會話被重置、恢復或過期時:
- 內建記憶被刷新到磁碟
- Memory provider 的
on_session_end()hook 觸發 - 臨時
AIAgent執行一個僅記憶的對話輪次 - 上下文然後被捨棄或歸檔
背景維護
閘道器在訊息處理的同時執行週期性維護:
- 排程心跳 — 檢查任務排程並觸發到期任務
- 會話過期 — 在逾時後清理被放棄的會話
- 記憶刷新 — 在會話過期前主動刷新記憶
- 快取刷新 — 刷新模型列表和供應商狀態
程序管理
閘道器作為長駐程序運行,透過以下方式管理:
hermes gateway start/hermes gateway stop— 手動控制systemctl(Linux)或launchctl(macOS)— 服務管理- 位於
~/.hermes/gateway.pid的 PID 檔案 — profile 範圍的程序追蹤
Profile 範圍 vs 全域:start_gateway() 使用 profile 範圍的 PID 檔案。hermes gateway stop 只停止當前 profile 的閘道器。hermes gateway stop --all 使用全域 ps aux 掃描來終止所有閘道器程序(在更新期間使用)。