# Hermes + TelegramForwarder Group Integration ## Problem Forwarder sends messages to a group, but Hermes bot (also in the group) ignores them. ## Root Causes (check ALL of these) | # | Cause | Symptom | Fix | |---|-------|---------|-----| | 0 | **Competing gateway process** (old OpenClaw, duplicate Hermes) | Adapter shows "connected" but `getUpdates` returns 0 without 409 conflict. No "✓ telegram connected" in logs. Group messages NEVER worked. | `ps aux \| grep gateway` → kill stale process → restart Hermes | | 1 | Bot Privacy Mode is ON | Bot receives 0 group messages | @BotFather → /mybots → Bot Settings → Group Privacy → Turn off | | 2 | Wrong config key `free_response_channels` | Bot sees @mentions but ignores regular messages | Use `extra.free_response_chats` (NOT top-level `free_response_channels`) | | 3 | Group migrated to supergroup | `An invalid Peer was used` in forwarder logs | Update chat ID (see below) | | 4 | Privacy mode not propagated | `can_read_all_group_messages=true` but bot still can't see messages | Kick bot from group, re-add it | **⚠️ Check #0 FIRST.** A stale gateway process is invisible — the state file says "connected", no errors appear, but the adapter never actually polls. This was the actual root cause for a user whose group messages had NEVER worked for over a week (old OpenClaw Gateway PID 900 running since June 22). Always run `ps aux | grep gateway | grep -v grep` before investigating other causes. **⚠️ Privacy mode is the #1 cause.** Even with `free_response_chats` correctly configured, if privacy mode is ON, the bot literally cannot see group messages. Fix privacy mode FIRST. ## Step-by-step ### 0. Check for Competing Gateway Processes (DO THIS FIRST) ```bash ps aux | grep "gateway\|openclaw" | grep -v grep ``` If you see any `openclaw ... gateway` or duplicate `hermes gateway` processes → **kill them immediately**. They steal the bot token polling session. Verify nobody else is polling: ```bash python3 -c " import json, urllib.request with open('/home/openclaw/.hermes/.env') as f: for line in f: if line.startswith('TELEGRAM_BOT_TOKEN'): token = line.strip().split('=',1)[1] break proxy = urllib.request.ProxyHandler({'http': 'http://127.0.0.1:7890', 'https': 'http://127.0.0.1:7890'}) opener = urllib.request.build_opener(proxy) try: req = urllib.request.Request(f'https://api.telegram.org/bot{token}/getUpdates?timeout=1') resp = opener.open(req, timeout=5) print(f'getUpdates: {len(json.loads(resp.read()).get(\"result\",[]))} updates — nobody polling!' ) except Exception as e: print(f'409 Conflict — adapter IS polling (normal)' if '409' in str(e) else f'Error: {e}') " ``` - 0 updates without 409 → broken (check for competing processes) - 409 Conflict → normal (adapter is polling) ### 1. Disable Bot Privacy Mode (MANDATORY) Open Telegram → @BotFather: ``` /mybots → select Hermes bot → Bot Settings → Group Privacy → Turn off ``` Do this for the Hermes bot. If the forwarder bot also needs to read messages in the group, do it for that bot too. **After disabling, kick the bot from the group and re-add it.** The change does not apply to bots already in a group. Verify: `curl -s --proxy http://127.0.0.1:7890 "https://api.telegram.org/bot/getMe"` → `can_read_all_group_messages` must be `true`. ### 2. Find the destination group's chat_id ```bash cd ~/TelegramForwarder docker exec telegram-forwarder python3 -c " import sqlite3 conn = sqlite3.connect('/app/db/forward.db') c = conn.cursor() c.execute('SELECT id, telegram_chat_id, name FROM chats') for ch in c.fetchall(): print(ch) conn.close() " ``` The column is `telegram_chat_id` (NOT `chat_id`). **⚠️ Supergroup IDs**: If the group was migrated, the old ID is stale. Use `-100` + `channel_id` format. To find the real channel_id: ```python entity = await bot.get_entity(old_id) # If entity has migrated_to: new_id = -100 * entity.migrated_to.channel_id # e.g., -1003966251111 ``` ### 3. Edit Hermes config (CORRECT KEY!) `~/.hermes/config.yaml` — add inside the `telegram:` → `extra:` section: ```yaml telegram: extra: free_response_chats: '-1003966251111' # ← CORRECT key # free_response_channels: '...' ← WRONG — code ignores this! ``` **⚠️ CRITICAL**: The Hermes source code reads `self.config.extra.get("free_response_chats")`. The top-level `free_response_channels` is a DIFFERENT field that is NOT read by the group-message processing logic. Using the wrong key = silently ignored, bot only responds to @mentions. Multiple groups: comma-separated `'-1003966251111,-1001234567890'` ### 4. Restart Hermes ```bash hermes gateway restart # from a separate shell, NOT inside the gateway ``` If refused ("Refusing to restart from inside the gateway"), ask the user to SSH in and run it. ## Config Field Reference | Field | Location | Default | Purpose | |-------|----------|---------|---------| | `require_mention` | `telegram:` | `true` | If true, bot only responds when @mentioned in groups | | `free_response_chats` | `telegram:extra:` | `''` | Comma-separated chat_ids exempt from @mention requirement | | `free_response_channels` | `telegram:` | `''` | **NOT used by group message processing** — legacy field, ignore | | `allowed_chats` | `telegram:` | `''` | If set, ONLY these chats can interact with the bot (whitelist) | ## Signal Forwarding with channel_prompts When you want Hermes to process group messages (e.g., trading signals) and forward results to a user DM instead of replying in the group: ```yaml telegram: channel_prompts: "-1003966251111": "你是交易信号处理器。收到群里的交易信号后,解析【币种】【方向】【仓位】,然后用send_message工具发送到telegram:656685714(用户DM),格式简洁:信号摘要+当前价格+建议操作+风险提示。不要在群里回复。" ``` This configures a per-group system prompt. The agent in that group session will follow these instructions instead of the default behavior. ## End-to-End Verification After setting up the forwarding → Hermes chain, verify each link: 1. **Forwarder listening**: `docker logs telegram-forwarder 2>&1 | grep "收到新消息"` — should show messages from source channel 2. **Forwarder sending**: `docker logs telegram-forwarder 2>&1 | grep "已发送"` — should show successful sends to target group 3. **Hermes receiving**: `hermes logs 2>&1 | grep "inbound message"` — should show messages from the group (chat ID should match the group, not the user DM) 4. **Hermes responding**: Check the group for Hermes's reply **Common failure chain**: - Forwarder logs show "收到新消息" but no "已发送" → keyword filter issue or invalid Peer (check group ID) - Forwarder logs show "已发送" but Hermes shows no inbound from group → privacy mode still on, or `free_response_chats` wrong key - Hermes shows inbound but no response → check `require_mention` and `free_response_chats` **⚠️ For testing, use a human account** (not the forwarder bot) to send messages to the source channel or directly to the group. Bot-sent messages may be filtered by Hermes. ## Pitfalls - `free_response_chats` takes string values, not integers: `'-1003966251111'` not `-1003966251111` - Config changes require Hermes restart to take effect - If `allowed_chats` is set (non-empty), the group must also be in that list - The forwarder's `handle_mode: FORWARD` uses Telegram's native forward (shows "Forwarded from"). If user wants clean copies, change to `COPY` mode. - **Don't test by sending directly to the target group** — this bypasses the forwarding chain and proves nothing. Test by sending to the SOURCE group and verifying the full pipeline: source → forwarder → target → Hermes response. - **Bot-to-bot messaging FAILS**: Telegram prohibits bot→bot messaging. Forward to a GROUP where both bots reside, not directly to a bot. - **Session database locked**: If you see `sqlite3.OperationalError: database is locked`, stop the container first, edit DB, then restart.