# 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<TOKEN>/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.
