Внутренний шлюз устройства

Шлюз обмена сообщениями — это долго работающий процесс, который соединяет Hermes с более чем 20 платформами обмена сообщениями через единую архитектуру.

Ключевые файлы

Файл Назначение
шлюз/run.py GatewayRunner — главный цикл, слэш-команды, отправка сообщений (большой файл; проверьте git для текущего LOC)
gateway/session.py SessionStore — сохранение диалогов и построение переключения сессии
gateway/delivery.py Доставка исходящих сообщений на целевые платформы/каналы
gateway/pairing.py Процесс привязки через ЛС для авторизации пользователя
gateway/channel_directory.py Сопоставление ID чатов с человекочитаемыми именами для доставки по расписанию
gateway/hooks.py Обнаружение хуков, загрузка и диспетчеризация событий жизненного цикла
gateway/mirror.py Зеркалирование сообщений между сессиями для send_message
gateway/status.py Управление блокировкой токенов для экземпляров шлюза с видимым внешним профилем
шлюз/builtin_hooks/ Расширение точки для всегда зарегистрированных хуков (без ограничений)
шлюз/платформы/ Адаптеры платформы (по одной на каждой платформе обмена сообщениями)

Обзор конструкции

┌─────────────────────────────────────────────────┐
│                  GatewayRunner                  │
│                                                 │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐       │
│  │ Telegram │  │ Discord  │  │  Slack   │       │
│  │ Adapter  │  │ Adapter  │  │ Adapter  │       │
│  └────┬─────┘  └────┬─────┘  └────┬─────┘       │
│       │             │             │             │
│       └─────────────┼─────────────┘             │
│                     ▼                           │
│              _handle_message()                  │
│                     │                           │
│         ┌───────────┼───────────┐               │
│         ▼           ▼           ▼               │
│  Slash command   AIAgent    Queue/BG            │
│    dispatch      creation   sessions            │
│                     │                           │
│                     ▼                           │
│                 SessionStore                    │
│              (SQLite persistence)               │
└───────┴─────────────┴─────────────┴─────────────┘

Поток сообщений

Когда сообщение приходит с любой платформы:

  1. Адаптер платформы получает исходное событие и нормализует его в MessageEvent
  2. Базовый адаптер на передней стороне активной сессии:
  3. Если агент активирован для этой сессии → по очереди разместить сообщение, установить событие прерывания.
  4. Если /approve, /deny, /stop → пропустить защиту (обрабатывается встроенно)
  5. GatewayRunner._handle_message() получает событие:
  6. Разрешить ключ сессии через _session_key_for_source() (формат: agent:main:{platform}:{chat_type}:{chat_id})
  7. Посмотреть авторизацию (см. Авторизация ниже)
  8. Проверить, является ли это слэш-командой → передать обработчику команды
  9. Просмотр, не запущенный уже агентом → перехватить команды типа /stop, /status
  10. В противном случае → создать экземпляр AIAgent и настроить диалог
  11. Ответ отправить обратно через платформу адаптера.

Формат переключения сессии

Ключи кода сессии подтверждают полный контекст маршрутизации:

agent:main:{platform}:{chat_type}:{chat_id}

Например: агент:main:telegram:private:123456789

Платформы с поддержкой тредов (темы форумов Telegram, треды Discord, треды Slack) могут включать идентификатор треда в частьchat_id. Никогда не создавайте ключи сеанса вручную — всегда воспользуйтесь build_session_key() из gateway/session.py.

Двухуровневая защита сообщений

Когда агент активно работает, входящие сообщения передаются через две последовательные проверки:

  1. Уровень 1 — Базовый адаптер (gateway/platforms/base.py): Проверяет _active_sessions. Если сессия активна, помещается сообщение в очередь _pending_messages и устанавливается событие прерывания. Это перехватывает сообщения до того, как они добились исполнения шлюза.

  2. Уровень 2 — Исполнитель шлюза (gateway/run.py): Проверяет _running_agents. Перехватывает определенные команды (/stop, /new, /queue, /status, /approve, /deny) и направляет их соответствующим образом. Всё остальное вызывает running_agent.interrupt().

Команды, которые должны достичь исполнителя, пока агент заблокирован (например, /approve), обрабатываются встроенно через await self._message_handler(event) — они обходят фоновые задачи системы, чтобы избежать прохождения гонки.

Авторизация

Шлюз использует многоуровневую проверку авторизации, выполняющую порядку:

  1. Флаг разрешения для всех платформ (например, «TELEGRAM_ALLOW_ALL_USERS») — если этот флажок установлен, все пользователи на этой платформе авторизованы.
  2. Белый список платформы (например, TELEGRAM_ALLOWED_USERS) — идентификатор пользователей через запятую.
  3. Привязка через ЛС — авторизованные пользователи могут привязывать новые пользователи с помощью кода привязки.
  4. Глобальное разрешение всех (GATEWAY_ALLOW_ALL_USERS) — если установлено, все пользователи на всех платформах авторизованы.
  5. По: запрет — неавторизованные настройки пользователи отклоняются.

Процесс# привязки через ЛС

Admin: /pair
Gateway: "Pairing code: ABC123. Share with the user."
New user: ABC123
Gateway: "Paired! You're now authorized."

Состояние привязки сохраняется в gateway/pairing.py и переживает перезапуски.

Обработка слэш-команды

Все слэш-команды в шлюзе проходят через один и тот же конвейер разрешения:

  1. resolve_command() из hermes_cli/commands.py содержит вводы с конвенционным именем (обработка псевдонимов, префиксное совпадение)
  2. Каноническое имя вчера по GATEWAY_KNOWN_COMMANDS
  3. Обработчик в _handle_message() диспетчеризирует на основе канонического имени.
  4. Некоторые команды ограничены конфигурацией (gateway_config_gate в CommandDef)

Защита работающего агента

Команды, которые НЕ ДОЛЖНЫ выполняться, пока агент обрабатывает запрос, отклоняются на раннем этапе:

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() с жёстко заданными значениями по умолчанию), шлюз читает config.yaml напрямую через загрузчик YAML. Это означает, что ключи конфигурации, которые существуют в словаре результатов по умолчанию CLI, но отсутствуют в конфигурационном файле пользователя, могут вести себя по-разному между CLI и шлюзом.

Адаптеры платформы

Каждая платформа обмена сообщениями имеет адаптер в gateway/platforms/:

gateway/platforms/
├── base.py              # BaseAdapter — общая логика для всех платформ
├── telegram.py          # Telegram Bot API (длинный опрос или вебхук)
├── discord.py           # Discord бот через discord.py
├── slack.py             # Slack Socket Mode
├── whatsapp.py          # WhatsApp Business Cloud API
├── signal.py            # Signal через REST API signal-cli
├── matrix.py            # Matrix через mautrix (опционально E2EE)
├── mattermost.py        # Mattermost WebSocket API
├── email.py             # Email через IMAP/SMTP
├── sms.py               # SMS через Twilio
├── dingtalk.py          # DingTalk WebSocket
├── feishu.py            # Feishu/Lark WebSocket или вебхук
├── wecom.py             # WeCom (WeChat Work) обратный вызов
├── weixin.py            # Weixin (личный WeChat) через API iLink Bot
├── bluebubbles.py       # Apple iMessage через сервер BlueBubbles macOS
├── qqbot/               # QQ Bot (Tencent QQ) через Official API v2 (подпакет: adapter.py, crypto.py, keyboards.py, …)
├── yuanbao.py           # Yuanbao (Tencent) адаптер ЛС/групп
├── feishu_comment.py    # Обработчик комментариев к документам/дискам Feishu
├── msgraph_webhook.py   # Вебхук уведомлений об изменениях Microsoft Graph (Teams, Outlook и т.д.)
├── webhook.py           # Адаптер входящего/исходящего вебхука
├── api_server.py        # Адаптер REST API сервера
└── homeassistant.py     # Интеграция с диалогами Home Assistant

Адаптеры реализуют общий интерфейс: - connect() / disconnect() — управление жизненным циклом - send_message() — доставка исходящих сообщений - on_message() — нормализация входящих сообщений → MessageEvent

Блокировка токенов

Адаптеры, которые подключаются с учетом учётных данных, вызывают acquire_scoped_lock() в connect() и release_scoped_lock() в disconnect(). Это собственное использование одного и того же тока бота двумя разными профилями одновременно.

Путь доставки

Исходящие доставки (gateway/delivery.py) обрабатывают:

Доставки запланированных задач НЕ зеркалируются в истории шлюза — они существуют только в своей собственной сессии cron. Это осознанное проектное решение для предотвращения чередования сообщений.

Хуки

Хуки шлюза — это Python-модули, которые реагируют на события жизненного цикла:

События хуков шлюза

Событие Когда реализация
шлюз: запуск Запуск процесса шлюза
сеанс: начало Начало нового сеанса диалога
сеанс: конец Завершение сессии или источение времени ожидания
сеанс: сброс Пользователь сбрасывает сессию команды /new
агент: старт Агент начала обработки сообщений
агент:шаг Агент завершает и выполняет одну терацию инструмента
агент:конец Агент завершает работу и возвращает ответ
команда:* Выполняется любая слэш-команда

Хуки обнаруживаются в gateway/builtin_hooks/ (точка расширения — в настоящее время пуста в поставляемом дистрибутиве; _register_builtin_hooks() — это заглушка без операций) и ~/.hermes/hooks/ (установленные пользователем). Каждый хук представляет собой каталог с манифестом HOOK.yaml и handler.py.

Интеграция с провайдером памяти

При включении разъема провайдера памяти (например, Honcho):

  1. Шлюз создаёт AIAgent для каждого сообщения с идентификатором сессии.
  2. MemoryManager связывает провайдера с контекстом сессии.
  3. Инструменты провайдера (например, honcho_profile, viking_search) маршрутизируются через:
AIAgent._invoke_tool()
  → self._memory_manager.handle_tool_call(name, args)
    → provider.handle_tool_call(name, args)
  1. При завершении/сбросе сессии выполняется on_session_end() для очистки и конечной выгрузки данных.

Жизненный цикл сброса памяти

Когда сессия сбрасывается, возобновляется или прекращается: 1. Встроенные данные памяти сбрасываются на диск. 2. реализация хука on_session_end() провайдера памяти 3. Временный AIAgent выполняет один оборот диалога, управляемый только памятью. 4. После этого контекст выбрасывается или архивируется.

Фоновое обслуживание

Шлюз выполняет периодические задачи по обслуживанию вместе с обработкой сообщений:

Управление процессами

Шлюз работает как долгоживущий процесс, управляемый через:

Область внешнего профиля против глобального: start_gateway() использует PID-файлы с областью внешнего профиля. hermes Gateway Stop останавливает шлюз только текущего профиля. hermes Gateway Stop --all использует глобальное сканирование ps aux для завершения всех процессов шлюза (используется при обновлениях).

Связанные документы