# Zulip Router _Последнее обновление: 2026-06-16_ ## Цель Event-роутер между Zulip и balda-ботами (Валера, Клавдий). Решает проблему конкурентного получения событий — сейчас оба бота через Events API видят все сообщения одновременно. ## Проблема (root cause) - Каждый balda-бот регистрирует собственную event queue в Zulip Events API - Без роутера оба получают все сообщения, и каждый может ответить на @mention другого - `extractOtherMention()` патч в коде — костыль, не решает проблему полностью ## Как сейчас (до роутера) ``` Zulip ├── Events API ← Валера (valera-bot, poll на :8091) └── Events API ← Клавдий (claudio-bot, poll на :8092) └── Webhook → оба (outgoing webhook bot_type=3 через @mention) ``` ## Архитектура (с роутером) ``` Zulip Event Queue ↓ poll (отдельный бот-роутер или reuse одного из существующих) [zulip-router] (~/Docker/zulip-router/) ├── @Валера / @Valera → POST http://balda-agent-valera-1:8091/zulip/webhook ├── @Клавдий / @Klavdiy → POST http://claudio-agent-claudio-1:8092/zulip/webhook └── (default / unknown) → POST http://balda-agent-valera-1:8091/zulip/webhook ↕ ownership: stream+topic → bot (TTL 24h, persisted to /data/ownership.json) ``` **Важно:** таргеты — не Hermes инстансы, а balda-боты (Go бинарники). У каждого уже есть webhook endpoint `/zulip/webhook`. ## Правила маршрутизации 1. `@Валера` или `@Valera` в тексте → Валера (+ ownership обновляется) 2. `@Клавдий` или `@Klavdiy` в тексте → Клавдий (+ ownership обновляется) 3. Топик уже у кого-то → туда же (TTL refreshed) 4. Новый топик без @mention → Валера (default) 5. `reset` без @mention → только владельцу топика ## Что меняется в balda После деплоя роутера у обоих ботов нужно **отключить Events API polling** в config.yaml: ```yaml zulip: events_polling: enabled: false # ← было true ``` После этого сообщения приходят **только через webhook** (от роутера). Перезапуск: `docker-compose up -d` (в каждом compose). ## Код **Репозиторий:** `~/Developer/zulip-router/` — Go, ~260 LOC (план) **Deploy:** `~/Docker/zulip-router/docker-compose.yaml` | Файл | Назначение | |------|------------| | `main.go` | Poll loop, routing, dispatch | | `config.go` | Config struct + YAML loading + env var expansion | | `zulip.go` | Zulip API client (register_queue, get_events, long-poll) | | `ownership.go` | Topic ownership store (RW mutex + JSON persistence) | | `forwarder.go` | HTTP POST к balda webhook endpoints | ## Deployment **Compose:** `~/Docker/zulip-router/docker-compose.yaml` **Env vars:** ```env ZULIP_ROUTER_API_KEY= ``` **Запуск:** ```bash cd ~/Docker/zulip-router docker-compose up -d ``` ## TODO - [ ] Написать код роутера (~260 LOC) - [ ] Создать compose `~/Docker/zulip-router/docker-compose.yaml` - [ ] Создать бота-роутера в Zulip (или реюзнуть valera-bot/claudio-bot), получить API key - [ ] `docker-compose up -d` - [ ] Проверить логи: `docker logs zulip-router --tail 50` - [ ] Отключить `events_polling.enabled` у Валеры → `docker-compose up -d` - [ ] Отключить `events_polling.enabled` у Клавдия → `docker-compose up -d` - [ ] Протестировать: @Клавдий и @Валера в разных топиках - [ ] Проверить ownership: сообщение в топик без @mention → последний бот - [ ] Удалить `extractOtherMention()` патч из кода (больше не нужен)