Внутреннее устройство шлюза
Шлюз обмена сообщениями — это долгоживущий процесс, который соединяет VibeOS с более чем 20 внешними платформами обмена сообщениями через единую архитектуру.
Ключевые файлы
| Файл | Назначение |
|---|---|
gateway/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 | Управление блокировками токенов для экземпляров шлюза в рамках профиля |
gateway/builtin_hooks/ | Точка расширения для постоянно зарегистрированных хуков (не поставляются) |
gateway/platforms/ | Адаптеры платформ (по одному на каждую платформу обмена сообщениями) |
Обзор архитектуры
┌─────────────────────────────────────────────────┐
│ GatewayRunner │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Telegram │ │ Discord │ │ Slack │ │
│ │ Адаптер │ │ Адаптер │ │ Адаптер │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
│ │ │ │ │
│ └─────────────┼─────────────┘ │
│ ▼ │
│ _handle_message() │
│ │ │
│ ┌───────────┼───────────┐ │
│ ▼ ▼ ▼ │
│ Слеш-команда AIAgent Очередь/Фон. │
│ диспетчер. создание сессии │
│ │ │
│ ▼ │
│ SessionStore │
│ (SQLite-персистентность) │
└───────┴─────────────┴─────────────┴─────────────┘
Поток сообщений
Когда сообщение поступает с любой платформы:
- Адаптер платформы получает сырое событие, нормализует его в
MessageEvent - Базовый адаптер проверяет защиту активной сессии:
- Если агент выполняется для этой сессии → поставить сообщение в очередь, установить событие прерывания
- Если
/approve,/deny,/stop→ обойти защиту (обрабатываются встроенно)
- GatewayRunner._handle_message() получает событие:
- Определить ключ сессии через
_session_key_for_source()(формат:agent:main:{platform}:{chat_type}:{chat_id}) - Проверить авторизацию (см. Авторизацию ниже)
- Проверить, является ли это слеш-командой → передать обработчику команд
- Проверить, не выполняется ли уже агент → перехватить команды типа
/stop,/status - В противном случае → создать экземпляр
AIAgentи запустить беседу
- Определить ключ сессии через
- Ответ отправляется обратно через адаптер платформы
Формат ключа сессии
Ключи сессии кодируют полный контекст маршрутизации:
agent:main:{platform}:{chat_type}:{chat_id}
Например: agent:main:telegram:private:123456789
Платформы с поддержкой тредов (темы форумов Telegram, треды Discord, треды Slack) могут включать ID тредов в часть chat_id. Никогда не создавайте ключи сессий вручную — всегда используйте build_session_key() из gateway/session.py.
Двухуровневая защита сообщений
Когда агент активно выполняется, входящие сообщения проходят через две последовательные защиты:
-
Уровень 1 — Базовый адаптер (
gateway/platforms/base.py): Проверяет_active_sessions. Если сессия активна, помещает сообщение в_pending_messagesи устанавливает событие прерывания. Это перехватывает сообщения до того, как они достигнут исполнителя шлюза. -
Уровень 2 — Исполнитель шлюза (
gateway/run.py): Проверяет_running_agents. Перехватывает определенные команды (/stop,/new,/queue,/status,/approve,/deny) и маршрутизирует их соответствующим образом. Всё остальное вызываетrunning_agent.interrupt().
Команды, которые должны достичь исполнителя, пока агент заблокирован (например, /approve), отправляются встроенно через await self._message_handler(event) — они обходят систему фоновых задач, чтобы избежать состояний гонки.
Авторизация
Шлюз использует многоуровневую проверку авторизации, оцениваемую по порядку:
- Флаг разрешить всех для платформы (например,
TELEGRAM_ALLOW_ALL_USERS) — если установлен, все пользователи на этой платформе авторизованы - Белый список платформы (например,
TELEGRAM_ALLOWED_USERS) — ID пользователей через запятую - Сопряжение в личных сообщениях — аутентифицированные пользователи могут подключать новых пользователей через код сопряжения
- Глобальный флаг разрешить всех (
GATEWAY_ALLOW_ALL_USERS) — если установлен, все пользователи на всех платформах авторизованы - По умолчанию: запретить — неавторизованные пользователи отклоняются
Процесс сопряжения в личных сообщениях
Администратор: /pair
Шлюз: «Код сопряжения: ABC123. Поделитесь с пользователем.»
Новый пользователь: ABC123
Шлюз: «Сопряжение выполнено! Теперь вы авторизованы.»
Состояние сопряжения сохраняется в gateway/pairing.py и переживает перезапуски.
Диспетчеризация слеш-команд
Все слеш-команды в шлюзе проходят через один и тот же конвейер разрешения:
resolve_command()изvibeos_cli/commands.pyсопоставляет ввод с каноническим именем (обрабатывает псевдонимы, префиксное сопоставление)- Каноническое имя проверяется на наличие в
GATEWAY_KNOWN_COMMANDS - Обработчик в
_handle_message()диспетчеризует на основе канонического имени - Некоторые команды ограничены конфигурацией (
gateway_config_gateнаCommandDef)
Защита выполняющегося агента
Команды, которые НЕ ДОЛЖНЫ выполняться, пока агент обрабатывает запрос, отклоняются на раннем этапе:
if _quick_key in self._running_agents:
if canonical == "model":
return "⏳ Агент выполняется — дождитесь завершения или используйте /stop."
Команды обхода (/stop, /new, /approve, /deny, /queue, /status) имеют специальную обработку.
Источники конфигурации
Шлюз читает конфигурацию из нескольких источников:
| Источник | Что предоставляет |
|---|---|
~/.vibeos/.env | API-ключи, токены ботов, учетные данные платформ |
~/.vibeos/config.yaml | Настройки модели, конфигурация инструментов, параметры отображения |
| Переменные окружения | Переопределяют любой из вышеперечисленных |
В отличие от CLI (который использует load_cli_config() с жестко заданными значениями по умолчанию), шлюз читает config.yaml напрямую через YAML-загрузчик. Это означает, что ключи конфигурации, которые существуют в словаре значений по умолчанию CLI, но отсутствуют в файле конфигурации пользователя, могут вести себя по-разному между CLI и шлюзом.
Адаптеры платформ
Большинство платформ обмена сообщениями поставляются как плагины-адаптеры в plugins/platforms/<name>/adapter.py; некоторые устаревшие адаптеры все еще находятся непосредственно в gateway/platforms/. Все они расширяют BasePlatformAdapter из gateway/platforms/base.py:
plugins/platforms/ # адаптеры в виде плагинов (по одному каталогу)
├── telegram/adapter.py # Telegram Bot API (длинный опрос или вебхук)
├── discord/adapter.py # Discord бот через discord.py
├── slack/adapter.py # Slack Socket Mode
├── whatsapp/adapter.py # WhatsApp Business Cloud API
├── matrix/adapter.py # Matrix через mautrix (опционально E2EE)
├── mattermost/adapter.py # Mattermost WebSocket API
├── email/adapter.py # Email через IMAP/SMTP
├── sms/adapter.py # SMS через Twilio
├── dingtalk/adapter.py # DingTalk WebSocket
├── feishu/adapter.py # Feishu/Lark WebSocket или вебхук
├── wecom/adapter.py # WeCom (WeChat Work) обратный вызов
├── line/adapter.py # LINE Messaging API
├── teams/adapter.py # Microsoft Teams
├── irc/adapter.py # IRC (канонический пример с блокировкой области видимости)
├── homeassistant/adapter.py # Интеграция с Home Assistant
└── … # google_chat, ntfy, photon, raft, simplex, …
gateway/platforms/ # базовая основа + устаревшие прямые адаптеры
├── base.py # BasePlatformAdapter — общая логика для всех платформ
├── signal.py # Signal через signal-cli REST API
├── weixin.py # Weixin (личный WeChat) через iLink Bot API
├── bluebubbles.py # Apple iMessage через сервер BlueBubbles macOS
├── qqbot/ # QQ Bot (Tencent QQ) через Official API v2 (подпакет)
├── yuanbao.py # Yuanbao (Tencent) адаптер личных сообщений/групп
├── msgraph_webhook.py # Вебхук уведомлений об изменениях Microsoft Graph (Teams, Outlook и т.д.)
├── webhook.py # Адаптер входящих/исходящих вебхуков
└── api_server.py # Адаптер REST API сервера
Экспериментальные платформы на основе коннекторов используют универсальный релейный адаптер в gateway/relay/ вместо прямого модуля платформы. Когда настроен GATEWAY_RELAY_URL или gateway.relay_url, шлюз регистрирует платформу relay, устанавливает соединение с коннектором через исходящий WebSocket и получает фреймы descriptor, inbound и interrupt_inbound на том же сокете. Коннектор рекламирует CapabilityDescriptor; VibeOS может отправлять обычные исходящие ответы, операции follow_up без токена и фреймы прерывания обратно через реле. Контракт проводки на основе источника находится в docs/relay-connector-contract.md.
Адаптеры реализуют общий интерфейс:
connect()/disconnect()— управление жизненным цикломsend_message()— доставка исходящих сообщенийon_message()— нормализация входящих сообщений →MessageEvent
Блокировки токенов
Адаптеры, которые подключаются с уникальными учетными данными, вызывают acquire_scoped_lock() в connect() и release_scoped_lock() в disconnect(). Это предотвращает одновременное использование одного и того же токена бота двумя профилями.
Путь доставки
Исходящие доставки (gateway/delivery.py) обрабатывают:
- Прямой ответ — отправка ответа обратно в исходный чат
- Доставка в домашний канал — маршрутизация результатов заданий по расписанию и фоновых результатов в настроенный домашний канал
- Явная целевая доставка — инструмент
send_message, указывающийtelegram:-1001234567890, или CLIvibeos send, оборачивающий тот же инструмент для shell-скриптов - Кроссплатформенная доставка — доставка на другую платформу, отличную от исходного сообщения
Доставки заданий по расписанию НЕ зеркалируются в историю сессии шлюза — они существуют только в своей собственной сессии расписания. Это осознанный выбор дизайна, чтобы избежать нарушений чередования сообщений.
Хуки
Хуки шлюза — это Python-модули, которые реагируют на события жизненного цикла:
События хуков шлюза
| Событие | Когда срабатывает |
|---|---|
gateway:startup | Запуск процесса шлюза |
session:start | Начало новой сессии беседы |
session:end | Завершение сессии или истечение тайм-аута |
session:reset | Сброс сессии пользователем с помощью /new |
agent:start | Агент начинает обработку сообщения |
agent:step | Агент завершает одну итерацию вызова инструмента |
agent:end | Агент завершает работу и возвращает ответ |
command:* | Выполнение любой слеш-команды |
Хуки обнаруживаются в gateway/builtin_hooks/ (точка расширения — в настоящее время пуста в поставляемом дистрибутиве; _register_builtin_hooks() — это заглушка без операции) и ~/.vibeos/hooks/ (установленные пользователем). Каждый хук — это каталог с манифестом HOOK.yaml и handler.py.
Интеграция с провайдером памяти
Когда включен плагин провайдера памяти (например, Honcho):
- Шлюз создает
AIAgentдля каждого сообщения с ID сессии MemoryManagerинициализирует провайдера с контекстом сессии- Инструменты провайдера (например,
honcho_profile,viking_search) маршрутизируются через:
AIAgent._invoke_tool()
→ self._memory_manager.handle_tool_call(name, args)
→ provider.handle_tool_call(name, args)
- При завершении/сбросе сессии вызывается
on_session_end()для очистки и окончательного сброса данных
Жизненный цикл сброса памяти
Когда сессия сбрасывается, возобновляется или истекает:
- Встроенные памяти сбрасываются на диск
- Срабатывает хук
on_session_end()провайдера памяти - Временный
AIAgentвыполняет один оборот беседы, ориентированной только на память - Затем контекст отбрасывается или архивируется
Фоновое обслуживание
Шлюз выполняет периодическое обслуживание параллельно с обработкой сообщений:
- Тиканье расписания — проверяет расписания заданий и запускает просроченные задания
- Истечение сессий — очищает заброшенные сессии после тайм-аута
- Сброс памяти — упреждающе сбрасывает память до истечения сессии
- Обновление кэша — обновляет списки моделей и статус провайдеров
Управление процессами
Шлюз работает как долгоживущий процесс, управляемый через:
vibeos gateway start/vibeos gateway stop— ручное управлениеsystemctl(Linux) илиlaunchctl(macOS) — управление службами- PID-файл в
~/.vibeos/gateway.pid— отслеживание процессов в рамках профиля
В рамках профиля vs глобально: start_gateway() использует PID-файлы в рамках профиля. vibeos gateway stop останавливает только шлюз текущего профиля. vibeos gateway stop --all использует глобальное сканирование ps aux для завершения всех процессов шлюза (используется во время обновлений).