Nội bộ cổng
Cổng nhắn tin là một quy trình dài hạn kết nối Hermes với hơn 14 nền tảng nhắn tin bên ngoài thông qua một kiến trúc thống nhất.
Tệp chính
| Tập tin | Mục đích |
|---|---|
gateway/run.py | GatewayRunner — vòng lặp chính, lệnh gạch chéo, gửi tin nhắn (~7.500 dòng) |
gateway/session.py | SessionStore — tính bền vững của cuộc hội thoại và xây dựng khóa phiên |
gateway/delivery.py | Gửi tin nhắn đi tới các nền tảng/kênh mục tiêu |
gateway/pairing.py | Luồng ghép nối DM để ủy quyền người dùng |
gateway/channel_directory.py | Ánh xạ ID trò chuyện thành tên mà con người có thể đọc được để phân phối định kỳ |
gateway/hooks.py | Khám phá móc, tải và gửi sự kiện trong vòng đời |
gateway/mirror.py | Phản chiếu tin nhắn giữa các phiên cho send_message |
gateway/status.py | Quản lý khóa mã thông báo cho các phiên bản cổng có phạm vi hồ sơ |
gateway/buildin_hooks/ | Các hook luôn được đăng ký (ví dụ: hook nhắc hệ thống BOOT.md) |
cổng/nền tảng/ | Bộ điều hợp nền tảng (một bộ điều hợp cho mỗi nền tảng nhắn tin) |
Tổng quan về kiến trúc
┌─────────────────────────────────────────────────┐
│ GatewayRunner │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Telegram │ │ Discord │ │ Slack │ ... │
│ │ Adapter │ │ Adapter │ │ Adapter │ │
│ └─────┬─────┘ └─────┬────┘ └─────┬────┘ │
│ │ │ │ │
│ └──────────────┼──────────────┘ │
│ ▼ │
│ _handle_message() │
│ │ │
│ ┌────────────┼────────────┐ │
│ ▼ ▼ ▼ │
│ Slash command AIAgent Queue/BG │
│ dispatch creation sessions │
│ │ │
│ ▼ │
│ SessionStore │
│ (SQLite persistence) │
└─────────────────────────────────────────────────┘
Luồng tin nhắn
Khi có tin nhắn đến từ bất kỳ nền tảng nào:
- Bộ điều hợp nền tảng nhận sự kiện thô, chuẩn hóa nó thành
MessageEvent - Bộ điều hợp cơ sở kiểm tra bảo vệ phiên hoạt động:
- Nếu tác nhân đang chạy trong phiên này → thông báo xếp hàng, hãy đặt sự kiện ngắt
- Nếu
/approve,/deny,/stop→ bỏ qua bảo vệ (gửi nội tuyến)
- GatewayRunner._handle_message() nhận được sự kiện:
- Giải quyết session key thông qua
_session_key_for_source()(định dạng:agent:main:{platform}:{chat_type}:{chat_id}) - Kiểm tra ủy quyền (xem Ủy quyền bên dưới)
- Kiểm tra xem đó có phải là lệnh gạch chéo không → gửi đến trình xử lý lệnh
- Kiểm tra xem tác nhân đã chạy chưa → chặn các lệnh như
/stop,/status - Ngược lại → tạo
AIAgentinstance và chạy hội thoại
- Phản hồi được gửi lại thông qua bộ chuyển đổi nền tảng
Định dạng khóa phiên
Khóa phiên mã hóa bối cảnh định tuyến đầy đủ:
agent:main:{platform}:{chat_type}:{chat_id}
Ví dụ: đại lý:chính:telegram:riêng tư:123456789
Các nền tảng nhận biết luồng (chủ đề diễn đàn Telegram, luồng Discord, luồng Slack) có thể bao gồm ID luồng trong phần chat_id. Không bao giờ tạo khóa phiên theo cách thủ công — luôn sử dụng build_session_key() từ gateway/session.py.
Bảo vệ tin nhắn hai cấp
Khi một tác nhân đang hoạt động tích cực, các tin nhắn đến sẽ đi qua hai bộ bảo vệ tuần tự:
-
Cấp 1 — Bộ điều hợp cơ sở (
gateway/platforms/base.py): Kiểm tra_active_sessions. Nếu phiên đang hoạt động, hãy xếp hàng tin nhắn trong_pending_messagesvà đặt sự kiện gián đoạn. Điều này sẽ bắt các tin nhắn trước khi chúng đến được người chạy cổng. -
Cấp 2 — Người chạy cổng (
gateway/run.py): Kiểm tra_running_agents. Chặn các lệnh cụ thể (/stop,/new,/queue,/status,/approve,/deny) và định tuyến chúng một cách thích hợp. Mọi thứ khác đều kích hoạtrunning_agent.interrupt().
Các lệnh phải đến được người chạy trong khi tác nhân bị chặn (như /approve) được gửi nội tuyến thông qua await self._message_handler(event) — chúng bỏ qua hệ thống tác vụ nền để tránh các điều kiện chạy đua.
Ủy quyền
Cổng sử dụng kiểm tra ủy quyền nhiều lớp, được đánh giá theo thứ tự:
- Cờ cho phép tất cả trên mỗi nền tảng (ví dụ:
TELEGRAM_ALLOW_ALL_USERS) — nếu được đặt, tất cả người dùng trên nền tảng đó đều được ủy quyền - Danh sách cho phép nền tảng (ví dụ:
TELEGRAM_ALLOWED_USERS) — ID người dùng được phân tách bằng dấu phẩy - Ghép nối DM — người dùng đã xác thực có thể ghép nối người dùng mới thông qua mã ghép nối
- Cho phép tất cả toàn cầu (
GATEWAY_ALLOW_ALL_USERS) — nếu được đặt, tất cả người dùng trên tất cả các nền tảng đều được ủy quyền - Mặc định: từ chối — người dùng trái phép sẽ bị từ chối
Luồng ghép nối DM
Admin: /pair
Gateway: "Pairing code: ABC123. Share with the user."
New user: ABC123
Gateway: "Paired! You're now authorized."
Trạng thái ghép nối vẫn được duy trì trong gateway/pairing.py và vẫn tiếp tục khởi động lại.
Gửi lệnh gạch chéo
Tất cả các lệnh gạch chéo trong cổng đều đi qua cùng một đường dẫn phân giải:
resolve_command()từhermes_cli/commands.pyánh xạ đầu vào thành tên chuẩn (xử lý bí danh, khớp tiền tố)- Tên chuẩn được kiểm tra dựa trên
GATEWAY_KNOWN_COMMANDS - Trình xử lý trong các công văn
_handle_message()dựa trên tên chuẩn - Một số lệnh được kiểm soát trên config (
gateway_config_gatetrênCommandDef)
Người bảo vệ đặc vụ đang chạy
Các lệnh KHÔNG được thực thi trong khi tác nhân đang xử lý sẽ bị từ chối sớm:
if _quick_key in self._running_agents:
if canonical == "model":
return "⏳ Agent is running — wait for it to finish or /stop first."
Các lệnh bỏ qua (/stop, /new, /approve, /deny, /queue, /status) có cách xử lý đặc biệt.
Nguồn cấu hình
Cổng đọc cấu hình từ nhiều nguồn:
| Nguồn | Nó cung cấp những gì |
|---|---|
~/.hermes/.env | Khóa API, mã thông báo bot, thông tin xác thực nền tảng |
~/.hermes/config.yaml | Cài đặt mô hình, cấu hình công cụ, tùy chọn hiển thị |
| Biến môi trường | Ghi đè bất kỳ mục nào ở trên |
Không giống như CLI (sử dụng load_cli_config() với các giá trị mặc định được mã hóa cứng), cổng đọc config.yaml trực tiếp thông qua trình tải YAML. Điều này có nghĩa là các khóa cấu hình tồn tại trong lệnh mặc định của CLI nhưng không có trong tệp cấu hình của người dùng có thể hoạt động khác nhau giữa CLI và cổng.
Bộ điều hợp nền tảng
Mỗi nền tảng nhắn tin có một bộ điều hợp trong gateway/platforms/:
gateway/platforms/
├── base.py # BaseAdapter — shared logic for all platforms
├── telegram.py # Telegram Bot API (long polling or webhook)
├── discord.py # Discord bot via discord.py
├── slack.py # Slack Socket Mode
├── whatsapp.py # WhatsApp Business Cloud API
├── signal.py # Signal via signal-cli REST API
├── matrix.py # Matrix via matrix-nio (optional E2EE)
├── mattermost.py # Mattermost WebSocket API
├── email.py # Email via IMAP/SMTP
├── sms.py # SMS via Twilio
├── dingtalk.py # DingTalk WebSocket
├── feishu.py # Feishu/Lark WebSocket or webhook
├── wecom.py # WeCom (WeChat Work) callback
├── webhook.py # Inbound/outbound webhook adapter
├── api_server.py # REST API server adapter
└── homeassistant.py # Home Assistant conversation integration
Bộ điều hợp thực hiện một giao diện chung:
connect()/disconnect()— quản lý vòng đờisend_message()— gửi tin nhắn đion_message()— chuẩn hóa tin nhắn gửi đến →MessageEvent
Khóa mã thông báo
Bộ điều hợp kết nối bằng thông tin xác thực duy nhất gọi acquire_scoped_lock() trong connect() và release_scoped_lock() trong disconnect(). Điều này ngăn hai hồ sơ sử dụng cùng một mã thông báo bot.
Đường dẫn giao hàng
Quá trình gửi đi (gateway/delivery.py) xử lý:
- Trả lời trực tiếp — gửi phản hồi trở lại cuộc trò chuyện ban đầu
- Phân phối kênh gia đình — định tuyến kết quả công việc định kỳ và kết quả nền tới kênh chính đã được định cấu hình
- Gửi mục tiêu rõ ràng — công cụ
send_messagechỉ địnhtelegram:-1001234567890 - Phân phối đa nền tảng — phân phối tới một nền tảng khác với tin nhắn ban đầu
Việc phân phối công việc định kỳ KHÔNG được phản ánh vào lịch sử phiên cổng - chúng chỉ tồn tại trong phiên định kỳ của riêng chúng. Đây là sự lựa chọn thiết kế có chủ ý để tránh vi phạm việc luân phiên thông báo.
Móc
Móc cổng là các mô-đun Python phản hồi các sự kiện trong vòng đời:
Sự kiện móc cổng
| Sự kiện | Khi bị sa thải |
|---|---|
cổng:khởi động | Quá trình cổng bắt đầu |
phiên:bắt đầu | Phiên trò chuyện mới bắt đầu |
phiên:kết thúc | Phiên hoàn thành hoặc hết thời gian |
phiên:đặt lại | Người dùng đặt lại phiên bằng /new |
đại lý:bắt đầu | Đại lý bắt đầu xử lý tin nhắn |
đại lý:bước | Đại lý hoàn thành một lần lặp gọi công cụ |
đại lý:cuối | Đại lý kết thúc và trả lời phản hồi |
lệnh:* | Bất kỳ lệnh gạch chéo nào đều được thực thi |
Móc được phát hiện từ gateway/buildin_hooks/ (luôn hoạt động) và ~/.hermes/hooks/ (do người dùng cài đặt). Mỗi hook là một thư mục có tệp kê khai HOOK.yaml và handler.py.
Tích hợp nhà cung cấp bộ nhớ
Khi plugin nhà cung cấp bộ nhớ (ví dụ: Honcho) được bật:
- Gateway tạo một
AIAgentcho mỗi tin nhắn với ID phiên MemoryManagerkhởi tạo nhà cung cấp với ngữ cảnh phiên- Các công cụ của nhà cung cấp (ví dụ:
honcho_profile,viking_search) được định tuyến thông qua:
AIAgent._invoke_tool()
→ self._memory_manager.handle_tool_call(name, args)
→ provider.handle_tool_call(name, args)
- Khi kết thúc/đặt lại phiên,
on_session_end()kích hoạt để dọn dẹp và xóa dữ liệu cuối cùng
Vòng đời xóa bộ nhớ
Khi một phiên được đặt lại, tiếp tục hoặc hết hạn:
- Bộ nhớ tích hợp được xóa vào đĩa
- Hook
on_session_end()của nhà cung cấp bộ nhớ kích hoạt AIAgenttạm thời chạy lượt hội thoại chỉ trong bộ nhớ- Ngữ cảnh sau đó sẽ bị loại bỏ hoặc lưu trữ
Bảo trì nền
Cổng này chạy bảo trì định kỳ cùng với việc xử lý tin nhắn:
- Cron tích tắc — kiểm tra lịch trình công việc và sa thải công việc đến hạn
- Phiên hết hạn — dọn sạch các phiên bị bỏ rơi sau khi hết thời gian chờ
- Xóa bộ nhớ — chủ động xóa bộ nhớ trước khi hết phiên
- Làm mới bộ đệm — làm mới danh sách mô hình và trạng thái nhà cung cấp
Quản lý quy trình
Cổng hoạt động như một quy trình lâu dài, được quản lý thông qua:
bắt đầu cổng hermes/dừng cổng hermes— điều khiển thủ côngsystemctl(Linux) hoặclaunchctl(macOS) — quản lý dịch vụ- Tệp PID tại
~/.hermes/gateway.pid— theo dõi quy trình trong phạm vi hồ sơ
Trong phạm vi hồ sơ so với toàn cầu: start_gateway() sử dụng các tệp PID trong phạm vi hồ sơ. dừng cổng hermes chỉ dừng cổng của cấu hình hiện tại. hermes Gateway Stop --all sử dụng tính năng quét ps aux toàn cầu để loại bỏ tất cả các quy trình cổng (được sử dụng trong quá trình cập nhật).