Перейти к основному содержимому

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

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

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

ФайлНазначение
gateway/run.pyGatewayRunner — главный цикл, слеш-команды, диспетчеризация сообщений (большой файл; проверьте git для текущего LOC)
gateway/session.pySessionStore — сохранение бесед и построение ключей сессий
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-персистентность) │
└───────┴─────────────┴─────────────┴─────────────┘

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

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

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

Формат ключа сессии​

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

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. Уровень 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) — ID пользователей через запятую
  3. Сопряжение в личных сообщениях — аутентифицированные пользователи могут подключать новых пользователей через код сопряжения
  4. Глобальный флаг разрешить всех (GATEWAY_ALLOW_ALL_USERS) — если установлен, все пользователи на всех платформах авторизованы
  5. По умолчанию: запретить — неавторизованные пользователи отклоняются

Процесс сопряжения в личных сообщениях​

Администратор: /pair
Шлюз: «Код сопряжения: ABC123. Поделитесь с пользователем.»
Новый пользователь: ABC123
Шлюз: «Сопряжение выполнено! Теперь вы авторизованы.»

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

Диспетчеризация слеш-команд​

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

  1. resolve_command() из vibeos_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 "⏳ Агент выполняется — дождитесь завершения или используйте /stop."

Команды обхода (/stop, /new, /approve, /deny, /queue, /status) имеют специальную обработку.

Источники конфигурации​

Шлюз читает конфигурацию из нескольких источников:

ИсточникЧто предоставляет
~/.vibeos/.envAPI-ключи, токены ботов, учетные данные платформ
~/.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, или CLI vibeos 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):

  1. Шлюз создает AIAgent для каждого сообщения с ID сессии
  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. Затем контекст отбрасывается или архивируется

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

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

  • Тиканье расписания — проверяет расписания заданий и запускает просроченные задания
  • Истечение сессий — очищает заброшенные сессии после тайм-аута
  • Сброс памяти — упреждающе сбрасывает память до истечения сессии
  • Обновление кэша — обновляет списки моделей и статус провайдеров

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

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

  • 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 для завершения всех процессов шлюза (используется во время обновлений).

Связанная документация​