[2026-06-17] zulip-router: skip_mention_forward docs

This commit is contained in:
Alexey Martemyanov
2026-06-17 23:01:49 +06:00
parent bd9bde4213
commit 1bc25d467b
2 changed files with 183 additions and 68 deletions
+77 -48
View File
@@ -8,7 +8,7 @@ Hermes работает нативно (не в Docker) на Mac через `her
| Компонент | Расположение | Запуск |
|-----------|-------------|--------|
| Hermes config | `~/.hermes/config.yaml` | |
| Hermes config | `~/.hermes/config.yaml` | Eagle Dashboard (supervisor) — `POST /api/services/hermes/start` |
| claude-proxy (Claude proxy) | `/opt/homebrew/bin/claude-proxy` | launchd `ai.claude-proxy` |
| Zulip stack | `~/Developer/zulip/docker-compose.yml` | `docker compose up -d` |
| Obsidian MCP | mcpvault | встроен в Hermes toolset |
@@ -216,28 +216,36 @@ Zulip Events API
↓ poll (credentials Eagle)
[zulip-router] (Go, Docker)
├── @Валера → trigger=owner → POST balda-agent:8091
├── @Клавдий → trigger=owner → POST claudio-agent:8092
├── @Орёл → trigger=owner → POST host.docker.internal:8645/webhooks/eagle
── @Кит → trigger=owner → POST host.docker.internal:8646/webhooks/whale
└── ответы от ботов → в Zulip (боты пишут сами через API)
├── (без @mention, тред закреплён) → trigger=owner → POST боту
├── (новый тред без @mention) → trigger=default → POST default-боту
── @mention → ownership обновляется, НО НЕ форвардит
(бот получит через Zulip outgoing webhook напрямую)
Zulip Outgoing Webhook (при @mention)
├── @Орёл → POST host.docker.internal:8644/webhooks/eagle
├── @Кит → POST host.docker.internal:8645/webhooks/whale
├── @Валера → POST balda-agent:8091
└── @Клавдий → POST claudio-agent:8092
```
### Кто что получает
| Компонент | Получает сообщения через | Шлёт ответы через |
|-----------|-------------------------|-------------------|
| **Eagle (Hermes gateway)** | zulip-router → POST `/webhooks/eagle` | Zulip API напрямую (бот `eagle-bot`) |
| **Кит (Hermes gateway)** | zulip-router → POST `/webhooks/whale` | Zulip API напрямую (бот `whale-bot`) |
| **Валера (balda-agent)** | zulip-router → POST `/zulip/webhook` | Zulip API напрямую (бот `valera-bot`) |
| **Клавдий (claudio-agent)** | zulip-router → POST `/zulip/webhook` | Zulip API напрямую (бот `claudio-bot`) |
| Компонент | @mention (Zulip outgoing webhook) | Без @mention (через zulip-router) | Шлёт ответы через |
|-----------|----------------------------------|-----------------------------------|-------------------|
| **Eagle (Hermes gateway)** | POST `/webhooks/eagle` напрямую | POST `/webhooks/eagle` (trigger=owner/default) | Zulip API (бот `eagle-bot`) |
| **Кит (Hermes gateway)** | POST `/webhooks/whale` напрямую | POST `/webhooks/whale` (trigger=owner/default) | Zulip API (бот `whale-bot`) |
| **Валера (balda-agent)** | POST `:8091` напрямую | POST `:8091` (trigger=owner/default) | Zulip API (бот `valera-bot`) |
| **Клавдий (claudio-agent)** | POST `:8092` напрямую | POST `:8092` (trigger=owner/default) | Zulip API (бот `claudio-bot`) |
**Важно:** `@**Орёл**` и `@**Кит**` НЕ регистрируют свои event queue в Zulip.
Все сообщения приходят **только через zulip-router**. Роутер использует
credentials Eagle для поллинга Zulip Events API, но Eagle сам в Zulip
не поллит.
**Два пути доставки:**
1. **@mention** — Zulip сам шлёт outgoing webhook на webhook URL бота напрямую (минуя роутер). Роутер видит это сообщение в event queue, обновляет ownership, но НЕ форвардит (бот уже получил).
2. **Без @mention** — если тред закреплён за ботом (ownership), роутер форвардит. Если треда нет — default-боту.
### Что не так
**Важно:** Eagle и Кит НЕ регистрируют свои event queue в Zulip. Сообщения без @mention приходят через zulip-router. Роутер использует credentials Eagle для поллинга Zulip Events API, но Eagle сам в Zulip не поллит.
### Что не так (исторически, сейчас пофиксено)
Роутер после установки ownership шлёт ВСЕ сообщения из треда владельцу,
даже если @mention адресован другому боту.
@@ -246,30 +254,29 @@ credentials Eagle для поллинга Zulip Events API, но Eagle сам в
НО НЕ форвардить. Бот получит сообщение через свой outgoing webhook
(от Zulip напрямую).
**Текущее состояние (2026-06-17):** ownership обновляется через @mention
от другого бота, но НЕ форвардит — `processEvent` проверяет
`!senderIsBot && !isOtherBotMention()` для mention-сообщений.
Плюс guard `skip_bot_messages: true` отсекает все бот-сообщения целиком.
---
## Eagle: переключение на zulip-router (2026-06-17)
## Eagle: интеграция с zulip-router (2026-06-17)
### Задача
### Архитектура
Eagle должен получать сообщения:
- `@**Орёл**` → напрямую из Zulip через outgoing webhook (бот `eagle-bot`)
- сообщения без @mention (когда Eagle — owner треда) → через zulip-router
У Eagle **два пути получения сообщений:**
Кастомный код «роутера» в Hermes (`eagle-thread-guard`) не должен мешаться.
1. **@mention (`@**Орёл**`)** → Zulip outgoing webhook → POST напрямую на `host.docker.internal:8644/webhooks/eagle`
(минуя zulip-router). Eagle принимает через `WebhookAdapter` в `webhook.py`.
### План
2. **Без @mention, тред закреплён** → zulip-router → trigger=owner → POST `host.docker.internal:8644/webhooks/eagle`
1. Отключить Zulip polling у Eagle (если включён)
2. Настроить Eagle на приём вебхуков от zulip-router:
- webhook route `/webhooks/eagle` на порту 8645
- роутер шлёт на `host.docker.internal:8645/webhooks/eagle`
3. Проверить:
- curl POST `/webhooks/eagle` → 202
- `@**Орёл**` в Zulip → Eagle отвечает
- ответы только на @mention Орла (не на чужие)
Ответ уходит в Zulip напрямую через Zulip API (бот `eagle-bot`), **в тот же стрим и топик** откуда пришло сообщение — chat_id выводится в `_deliver_cross_platform` из `payload.display_recipient::subject`.
### Конфиг Eagle
**Важно:** Eagle НЕ запускает polling Zulip Events API (`ZULIP_POLLING_DISABLED=true`).
### Конфиг Eagle (актуальный)
`~/.hermes/config.yaml` — секция `platforms`:
```yaml
@@ -277,8 +284,8 @@ platforms:
webhook:
enabled: true
extra:
host: 127.0.0.1
port: 8645
host: "0.0.0.0"
port: 8644
routes:
eagle:
secret: INSECURE_NO_AUTH
@@ -286,23 +293,45 @@ platforms:
deliver: zulip
```
Zulip platform (`platforms.zulip`)отсутствует, polling отключён.
- `host: "0.0.0.0"` — слушаем на всех интерфейсах, чтобы Docker (zulip-router через `host.docker.internal`) мог достучаться
- `secret: INSECURE_NO_AUTH` — Zulip 10.x outgoing webhook не шлёт HMAC-подпись в заголовках. Safety rail на non-loopback снят патчем в коде
- `deliver: zulip` — ответ перенаправляется в Zulip платформу
- chat_id выводится из payload: `message.display_recipient::message.subject` (код в `_deliver_cross_platform`)
### Что сделано
### Изменения в коде Hermes
- Конфиг Eagle: порт 8645, webhook с route `eagle`
- Eagle перезапущен, порт 8645 слушается
- curl POST `/webhooks/eagle` → 202 Accepted
- Роутер должен слать на `:8645`, но шлёт на `:8644` — не настроено
В `gateway/platforms/webhook.py` (закоммичено `d4e98a8b0`):
1. `_BUILTIN_DELIVER_PLATFORMS` — добавлен `"zulip"`
2. Safety rail INSECURE_NO_AUTH на non-loopback — убран
3. `_validate_signature` — поддержка Zulip token в JSON body (+ gzip-декодирование)
4. `_deliver_cross_platform` — при `deliver=zulip` и отсутствии `deliver_extra.chat_id` chat_id выводится из `payload.message.display_recipient::message.subject`
5. Debug-логи: HEADERS, ZULIP_RAW_BODY, cross-platform diagnostics
### Статус
### Контекст сессий (webhook)
- Конфиг Eagle: порт 8645, webhook route `eagle`
- Конфиг Кита: порт 8644, webhook route `whale`
- Eagle (PID 96351) слушает на **8644** — не на своём порту
- Кит (PID 90402) слушает на **8645** — не на своём порту
- Порты перепутаны: Eagle на порту Кита, Кит на порту Eagle
- `@**Орёл**` — не отвечает
**Проблема:** Каждое сообщение через webhook получало уникальный `chat_id = webhook:eagle:{delivery_id}`, поэтому каждое @mention создавало новую сессию без истории.
**Решение (2026-06-17):** Два уровня фикса:
1. **Hermes webhook.py** (`gateway/platforms/webhook.py`) — `session_chat_id` теперь определяется приоритетно:
- `X-Chat-Id` заголовок (ставится zulip-router как `stream::topic`)
- Zulip outgoing webhook payload (`message.display_recipient::subject`)
- fallback — delivery_id (для non-Zulip webhook-ов)
2. **zulip-router** (`forwarder.go`) — при POST добавляет заголовок `X-Chat-Id: stream::topic`
Это покрывает оба пути доставки:
- **через роутер** (без @mention) — `X-Chat-Id` от роутера
- **напрямую от Zulip** (@mention) — Hermes сам определяет `stream::topic` из payload
Важно: у разных ботов (Eagle vs Кит) разный `route_name` в `session_chat_id`, поэтому их сессии не смешиваются даже при одинаковом `stream::topic`.
Git: `forwarder.go` — в репо роутера. `webhook.py` — патч в vendor Hermes (не коммитится).
### Управление
Только через Eagle Dashboard:
- `http://localhost:8880` (localhost)
- `POST /api/services/hermes/start|stop|restart`
---