Files
hermes-skills/telegram-integration/SKILL.md

157 lines
12 KiB
Markdown
Raw Normal View History

---
name: telegram-integration
description: "Hermes Telegram platform config, Bot API features, local server setup, and integration improvements."
version: 1.0.0
author: Hermes Agent
---
# Telegram Integration
Configure and extend Hermes' Telegram platform adapter. Covers `platforms.telegram.extra` options, Bot API feature enablement, and local Bot API server setup.
## Config Extras
All live under `platforms.telegram.extra` in config.yaml. Apply to ALL profiles when changing shared settings.
| Extra | Default | Effect |
|---|---|---|
| `rich_messages` | false | Bot API 10.1 — native tables, task lists, details, math, code blocks |
| `disable_link_previews` | false | Suppress URL preview cards |
| `local_mode` | false | Use local telegram-bot-api server instead of api.telegram.org |
| `guest_mode` | false | Bot API 10.0 — reply in chats without joining |
| `dm_topics` | [] | Per-DM isolated topic sessions |
| `channel_prompts` | `{}` | Per-chat ephemeral system prompts injected at API call time. Map of chat_id → prompt string. Never persisted to history. Use for voice-optimization, per-chat personality overrides, or platform-specific constraints. |
| `require_mention` | varies | Force @mention to trigger response |
## Runtime Footer
`display.runtime_footer.enabled: true` appends `model · 12%` to every reply. Fields: `model`, `context_pct`, `cwd`. Per-platform overrides at `display.platforms.telegram.runtime_footer`.
## Local Bot API Server
For privacy: runs a local `telegram-bot-api` binary that connects directly to Telegram's MTProto backend instead of `api.telegram.org`. Requires `api_id` + `api_hash` from my.telegram.org/apps.
Setup via Docker:
```bash
mkdir -p ~/telegram-bot-api-data
docker run -d --name telegram-bot-api --restart unless-stopped \
-p 8081:8081 \
-v ~/telegram-bot-api-data:/var/lib/telegram-bot-api \
-e TELEGRAM_API_ID=<id> \
-e TELEGRAM_API_HASH=<hash> \
aiogram/telegram-bot-api
```
Then set in ALL profile configs:
```yaml
platforms:
telegram:
extra:
base_url: "http://localhost:8081/bot"
base_file_url: "http://localhost:8081/file/bot" # MUST include /file/ — file downloads 404 without it
local_mode: true
```
**Critical: `base_file_url` vs `base_url`** — PTB has two separate URL bases. `base_url` is for API calls (getUpdates, sendMessage, getFile). `base_file_url` is for file downloads. The local Bot API server serves files at `/file/bot<token>/<path>`, not `/bot<token>/<path>`. If `base_file_url` is missing or set to the same as `base_url`, file downloads return 404 which PTB reports as `telegram.error.InvalidToken: Not Found` — misleading because the token is fine. This must be fixed in **every** profile that has a `platforms.telegram.extra` block, not only the active one; otherwise a spawned profile or subagent can hit the same 404 when handling voice.
**Docker permission issue** — the `aiogram/telegram-bot-api` container creates files owned by `postfix:_ssh` (uid 101:gid 101) with mode `640`. The Hermes gateway (user `n8n`, uid 1000) cannot read them. With `local_mode: true`, PTB tries to read files from disk first, fails due to permissions, falls back to HTTP, and 404s. Fix permissions from inside the container:
```bash
docker exec telegram-bot-api sh -c 'find /var/lib/telegram-bot-api/ -type d -exec chmod 755 {} \;'
docker exec telegram-bot-api sh -c 'find /var/lib/telegram-bot-api/ -type f -exec chmod 666 {} \;'
```
The container uses BusyBox `find`, so `find -printf` is unavailable; use the simple `-type d`/`-type f` form above. Also add `n8n` to the `_ssh` group on the host (`sudo usermod -aG _ssh n8n`) and restart the gateway. For persistence, schedule a cron job that re-chmods new files every minute. See `voice-systems/references/telegram-bot-api-permission-fix.md` for the full recipe.
**Critical:** Before switching, log the bot out of `api.telegram.org`:
```bash
curl -s "https://api.telegram.org/bot<TOKEN>/logOut"
```
Without this, updates split between cloud and local — the bot misses messages.
Phone still works remotely — local server maintains its own outbound MTProto connection. No inbound port forwarding needed.
## Channel Prompts
`telegram.channel_prompts` maps chat_id → ephemeral system prompt injected at API call time, never persisted to history. Use for voice-optimization, per-chat personality overrides, or platform-specific constraints.
**Pitfall — YAML corruption of XML tag references:** When a channel prompt contains ` response` or ` thinking` (Hermes internal XML markers), YAML processing can mangle them into corrupted strings like ` response... response`. The agent then emits responses wrapped in these tags, and the gateway's extraction pipeline strips them, leaving empty content. The error in logs is `response_delivery_dropped: non-empty response ... empty after extract, recovery yielded nothing`. Fix: remove all references to ` response`/` thinking` tags from channel prompts. Use plain language instead ("no markdown, no code blocks, no lists, no tables" is sufficient — the agent doesn't need to be told about internal XML tags).
**Pitfall — stale gateway PID blocking restart:** When a gateway process was started manually (e.g., `hermes gateway run --replace` in background), the systemd service sees the PID and refuses to start. The error in logs is `Another gateway instance is already running (PID N)`. Fix: `kill -9 <PID>` then `hermes -p <profile> gateway restart`. The systemd service will then start cleanly.
**Pitfall — `Auto-TTS failed: name 'tempfile' is not defined`:** When the gateway tries to auto-TTS a voice reply, it calls `tempfile.gettempdir()` in `gateway/platforms/base.py` line 4313. If `import tempfile` is missing from the file's imports, this raises `NameError`, the TTS generation fails, and the response is dropped with `response_delivery_dropped: non-empty response ... empty after extract, recovery yielded nothing`. The root cause is a missing import in the gateway source, not a config issue. Fix: add `import tempfile` to the imports block in `gateway/platforms/base.py` (after `import sys`, before `import time`). Gateway restart required after the fix.
**Pitfall — multi-profile gateway token conflict (Telegram gateway crash-loop):** When two Hermes profiles both have `platforms.telegram` config blocks, the first gateway to start grabs the Telegram bot token via a scoped lock file. Any second gateway (even a dedicated telegram profile's systemd service) hits `Telegram bot token already in use (PID N)` and exits. Systemd's `Restart=on-failure` creates an infinite crash-loop. The gateway loads platforms based on **config presence** (`platforms.telegram` block exists), NOT on `plugins.enabled`. A profile with `plugins.enabled: []` but a `platforms.telegram.extra` block still loads the Telegram adapter and claims the token.
**Fix (remove config block from non-Telegram profiles):**
```bash
# Remove the platforms.telegram block from the conflicting profile
hermes config set platforms.telegram null --profile <name>
# Also remove from base config if present
hermes config set platforms.telegram null
# Kill the conflicting gateway so the dedicated one can start
kill <PID>
# Wait for systemd restart cycle (~5-10s), then verify
hermes gateway status --profile telegram
journalctl --user -u hermes-gateway-telegram --since "1 minute ago" --no-pager | grep '✓ telegram connected'
```
**Diagnosing which PID holds the lock:**
```bash
# Inspect the scoped lock file
cat ~/.local/state/hermes/gateway-locks/telegram-bot-token-*.lock
# Shows: {"pid": <PID>, "scope": "telegram-bot-token", ...}
# Check which gateway process owns it
ps aux | grep 'hermes.*gateway' | grep -v grep
```
**Verification checklist after fix:**
1. Only ONE gateway process running with Telegram (`ps aux | grep 'hermes.*gateway'`)
2. Lock file shows the correct PID
3. `hermes gateway status --profile telegram` shows `✓ telegram connected`
4. Gateway log shows `✓ telegram connected` (not `✗ telegram failed to connect`)
5. No `token already in use` errors in `journalctl --user -u hermes-gateway-telegram`
**Pitfall — stale `gateway_voice_mode.json` in wrong HERMES_HOME:** The gateway reads voice mode state from `HERMES_HOME / "gateway_voice_mode.json"` (run.py:2790). When Telegram was handled by the general profile, the voice mode file lived at `~/.hermes/gateway_voice_mode.json`. After moving Telegram to its own profile, the active file is at `~/.hermes/profiles/telegram/gateway_voice_mode.json`. The old copy at the base HERMES_HOME is dead weight — delete it to avoid confusion.
**Pitfall — cron job delivery requires platform-connected profile:** Cron jobs with `deliver='telegram'` (or any platform-specific delivery) must live in the profile whose gateway actually holds that platform's connection. If the general profile has `platforms.telegram` in its config but the telegram profile's gateway holds the bot token, a cron job in the general profile with `deliver='telegram'` will fail with `no delivery target resolved for deliver=telegram`. The cron scheduler runs inside each profile's gateway and can only deliver through platforms that gateway has connected. Move the job to the profile where the platform is actually connected (write the job JSON to that profile's `cron/jobs.json` and restart its gateway).
**Pitfall — cron ticker silent failure (cron.enabled not set):** The cron scheduler won't start unless `cron.enabled: true` is explicitly set in the profile config. Without it, the gateway starts, Telegram connects, but no cron jobs fire — and there's no error or warning in the logs. The ticker heartbeat file (`cron/ticker_heartbeat`) is never created. Also set `cron.max_parallel_jobs: 3` (or a reasonable number) — `null` may block the ticker. Verify with: `cat ~/.hermes/profiles/<name>/cron/ticker_heartbeat` — if the file exists and has a recent timestamp, the ticker is alive. Fix:
```bash
hermes config set cron.enabled true --profile <name>
hermes config set cron.max_parallel_jobs 3 --profile <name>
hermes -p <name> gateway restart
```
## Profile-Scoped Organization
All Telegram-related user files should live under the telegram profile or workspace, not scattered across the base Hermes directory or other profiles.
**Files that belong in the telegram profile (`~/.hermes/profiles/telegram/`):**
- `config.yaml` — Telegram platform config, TTS/STT settings, channel prompts
- `gateway_voice_mode.json` — per-chat voice mode state
- `skills/` — Telegram-specific skills (telegram-integration, telegram-voice-mode, voice-systems references)
- `scripts/` — Telegram maintenance scripts (fix-telegram-bot-api-perms.sh)
**Files that belong in the telegram workspace (`~/workspace/telegram/`):**
- `AGENTS.md`, `README.md` — workspace context
- `docker-compose.yml`, `.env`, `entrypoint-fix.sh`, `rebuild-bot-api-container.sh` — Bot API container management
- `docs/` — Telegram documentation (how_telegram_works.md, telegram.md)
**Files that stay outside by necessity:**
- `~/.config/systemd/user/hermes-gateway-telegram.service` — systemd unit path is fixed
- `~/.local/state/hermes/gateway-locks/telegram-bot-token-*.lock` — machine-local lock, scoped by HERMES_HOME
- `~/telegram-bot-api-data/` — Docker bind-mount data, not Hermes config
- `~/.hermes/hermes-agent/` — shared source code, not profile-specific
**Cleanup after migrating Telegram to its own profile:**
1. Remove `platforms.telegram` from base config: `hermes config set platforms.telegram null`
2. Remove `platforms.telegram` from general profile: `hermes config set platforms.telegram null --profile general`
3. Delete stale `~/.hermes/gateway_voice_mode.json` (telegram profile has its own)
4. Move any lingering Telegram scripts/skills/docs into the telegram profile or workspace
- `references/bot-api-2026.md` — Telegram Bot API changelog (10.1, 10.0, 9.6) with Hermes implementation status
- `references/telegram-voice-validation-checklist.md` — 12-point systematic validation checklist for Telegram + voice pipeline audits