[2026-06-18] taiga-vault: family/how-to/hermes-eagle-mac.md family/how-to/kraken-access.md family/how-to/switch-emulation-rom-infra.md personal/projects/balda/balda-valera.md personal/projects/balda/claudio-klaudiy.md personal/projects/balda/fast-rlm-integration.md personal/projects/balda/setup.md personal/projects/hermes-configs/eagle-config.example.yaml personal/projects/hermes-configs/whale-config.example.yaml personal/projects/personal-os/eagle-dashboard.md
This commit is contained in:
@@ -1,19 +1,3 @@
|
||||
---
|
||||
title: Hermes на Eagle (Mac M4 Max) — Настройка и подводные камни
|
||||
type: reference
|
||||
namespace: work
|
||||
tags:
|
||||
- hermes
|
||||
- mac
|
||||
- eagle
|
||||
- claude-proxy
|
||||
- zulip
|
||||
- pitfalls
|
||||
created: '2026-05-21'
|
||||
updated: '2026-05-22'
|
||||
last_synced: '2026-05-22'
|
||||
confidence: 0.9
|
||||
---
|
||||
# Hermes на Eagle (Mac M4 Max) — Настройка и подводные камни
|
||||
|
||||
Hermes работает нативно (не в Docker) на Mac через `hermes gateway`.
|
||||
@@ -24,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 |
|
||||
@@ -225,6 +209,132 @@ Claude падает с "Failed to spawn process". Проверить логи:
|
||||
|
||||
---
|
||||
|
||||
## Схема маршрутизации сообщений
|
||||
|
||||
```
|
||||
Zulip Events API
|
||||
↓ poll (credentials Eagle)
|
||||
[zulip-router] (Go, Docker)
|
||||
│
|
||||
├── (без @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
|
||||
```
|
||||
|
||||
### Кто что получает
|
||||
|
||||
| Компонент | @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`) |
|
||||
|
||||
**Два пути доставки:**
|
||||
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 адресован другому боту.
|
||||
|
||||
**Должно быть:** если @mention другого бота — ownership обновляется,
|
||||
НО НЕ форвардить. Бот получит сообщение через свой outgoing webhook
|
||||
(от Zulip напрямую).
|
||||
|
||||
**Текущее состояние (2026-06-17):** ownership обновляется через @mention
|
||||
от другого бота, но НЕ форвардит — `processEvent` проверяет
|
||||
`!senderIsBot && !isOtherBotMention()` для mention-сообщений.
|
||||
Плюс guard `skip_bot_messages: true` отсекает все бот-сообщения целиком.
|
||||
|
||||
---
|
||||
|
||||
## Eagle: интеграция с zulip-router (2026-06-17)
|
||||
|
||||
### Архитектура
|
||||
|
||||
У Eagle **два пути получения сообщений:**
|
||||
|
||||
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`
|
||||
|
||||
Ответ уходит в Zulip напрямую через Zulip API (бот `eagle-bot`), **в тот же стрим и топик** откуда пришло сообщение — chat_id выводится в `_deliver_cross_platform` из `payload.display_recipient::subject`.
|
||||
|
||||
**Важно:** Eagle НЕ запускает polling Zulip Events API (`ZULIP_POLLING_DISABLED=true`).
|
||||
|
||||
### Конфиг Eagle (актуальный)
|
||||
|
||||
`~/.hermes/config.yaml` — секция `platforms`:
|
||||
```yaml
|
||||
platforms:
|
||||
webhook:
|
||||
enabled: true
|
||||
extra:
|
||||
host: "0.0.0.0"
|
||||
port: 8644
|
||||
routes:
|
||||
eagle:
|
||||
secret: INSECURE_NO_AUTH
|
||||
prompt: '{{message.content}}'
|
||||
deliver: zulip
|
||||
```
|
||||
|
||||
- `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
|
||||
|
||||
В `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)
|
||||
|
||||
**Проблема:** Каждое сообщение через 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`
|
||||
|
||||
---
|
||||
|
||||
## Связанные страницы
|
||||
|
||||
- [[claude-python-cli-proxy]] — Python Claude CLI proxy (claude-code-openai-wrapper, порт 8090)
|
||||
|
||||
@@ -4,6 +4,13 @@
|
||||
|
||||
## SSH с любой машины
|
||||
|
||||
**Контейнер hermes-kraken:**
|
||||
- Маппинг `/home/kraken/.ssh:/opt/data/.ssh:ro` в docker-compose.yml
|
||||
- Кастомный `/etc/passwd` с пользователем `kraken:x:1000:1000` (файл `passwd-with-kraken` рядом с compose)
|
||||
- `HERMES_UID=1000 HERMES_GID=1000` чтоб entrypoint ремапил пользователя
|
||||
- SSH из контейнера: `ssh -i /opt/data/.ssh/id_ed25519 kraken@localhost`
|
||||
- Ключ `kraken` добавлен в `authorized_keys` на хосте
|
||||
|
||||
`~/.ssh/config` на Eagle:
|
||||
```
|
||||
Host kraken
|
||||
|
||||
@@ -204,7 +204,11 @@ Web UI: `http://192.168.1.15:9117`
|
||||
### API — как Eagle ищет игру
|
||||
```bash
|
||||
# Поиск через Jackett Torznab API
|
||||
<<<<<<< HEAD
|
||||
curl "http://192.168.1.15:9117/api/v2.0/indexers/rutracker/results/torznab/?apikey=JACKETT_KEY&t=search&q=Donkey+Kong+Tropical+Freeze+Switch&cat=8000"
|
||||
=======
|
||||
curl "http://192.168.1.15:9117/api/v2.0/indexers/rutracker/results/torznab/?apikey=***&t=search&q=Donkey+Kong+Tropical+Freeze+Switch&cat=8000"
|
||||
>>>>>>> nas/main
|
||||
# Возвращает XML/JSON с magnet-ссылками и метаданными
|
||||
```
|
||||
|
||||
|
||||
Reference in New Issue
Block a user