# Telegram integration (MVP)

Rho includes a polling-first Telegram adapter (`extensions/telegram`) that maps Telegram chats to stable rho sessions.

## What it does

- Polls Telegram `getUpdates` with durable offset state
- Normalizes inbound messages and applies authz gates
- Maps chat -> session deterministically (`dm:<chat_id>`, `group:<chat_id>`)
- Runs prompts through rho RPC (`pi --mode rpc`)
- Transcribes inbound `voice` / `audio` / audio-document messages via ElevenLabs STT
- Supports `/tts <text>` to generate and return Telegram voice replies via ElevenLabs TTS
- Sends responses back with chunking + retry/backoff
- Exposes operator controls via tool + `/telegram` command
- Follows shared slash RPC contract (`docs/slash-command-contract.md`) for classification, execution, and errors

## Enable

In `~/.rho/init.toml`:

```toml
[modules.tools]
telegram = true

[settings.telegram]
enabled = true
mode = "polling"
bot_token_env = "TELEGRAM_BOT_TOKEN"
poll_timeout_seconds = 30
rpc_prompt_timeout_seconds = 60
allowed_chat_ids = []
allowed_user_ids = []
require_mention_in_groups = true
```

Set your token:

```bash
export TELEGRAM_BOT_TOKEN="<your-bot-token>"
```

Apply config:

```bash
rho sync
```

## ElevenLabs media features (STT + /tts)

Set an ElevenLabs API key for voice features:

```bash
export ELEVENLABS_API_KEY="<your-elevenlabs-key>"
```

Optional TTS voice override (default is ElevenLabs voice `EXAVITQu4vr4xnSDxMaL`):

```bash
export ELEVENLABS_TTS_VOICE_ID="<voice-id>"
# or legacy alias:
export ELEVENLABS_VOICE_ID="<voice-id>"
```

Behavior:

- Inbound `voice`, `audio`, and `document` messages with `audio/*` MIME type are transcribed, and that transcript is used as the prompt to rho (reply contains the assistant result, not just raw transcript).
- `/tts <text>` generates a playable Telegram voice reply.
- Missing key or API failures return actionable in-chat error text instead of crashing the worker.

## Worker lifecycle (important)

Telegram polling is owned by the dedicated worker process.

```bash
rho telegram start
rho telegram status
rho telegram logs -f
rho telegram stop
```

Use `rho start` for heartbeat/session daemon lifecycle; use `rho telegram ...` for Telegram transport lifecycle.

## Onboarding + approval flow

### Guided onboarding

```bash
rho telegram onboard
```

This validates the token, detects chat/user, and can lock allowlists.

### Pending access approvals

When strict allowlists block a sender, they receive a PIN. Operator flow:

```bash
rho telegram pending
rho telegram approve --pin 123456
rho telegram reject --pin 123456
```

## Operator controls

Slash command:

```text
/telegram status
/telegram check
/telegram allow-chat <chat_id>
/telegram revoke-chat <chat_id>
/telegram allow-user <user_id>
/telegram revoke-user <user_id>
/jobs
/job <job_id>
/cancel <job_id>
```

Tool action interface (`telegram` tool):

- `status`
- `check`
- `send` (`chat_id`, `text`, optional `reply_to_message_id`)
- `allow` / `revoke` (`target=chat|user`, `id`)
- `list_chats`

## Long prompt handling

Telegram uses a foreground soft timeout (`rpc_prompt_timeout_seconds`) to detect long-running work without hard-failing it.

If a prompt hits that timeout, the worker:

1. forks the work into a durable job (`/jobs`),
2. rotates the chat onto a fresh main session so follow-up messages stay responsive,
3. runs the forked job without a hard timeout,
4. posts final completion/failure back in-thread.

Use:

- `/jobs` to list recent jobs
- `/job <job_id>` to inspect one job
- `/cancel <job_id>` to stop a running job

Tune with:

- `rpc_prompt_timeout_seconds` (default `60`)

## Security model

- Default-off transport (`settings.telegram.enabled = false`)
- Optional allowlists (`allowed_chat_ids`, `allowed_user_ids`)
- Group activation gate (`require_mention_in_groups`)
- Runtime operator allow/revoke persistence in `~/.rho/telegram/config.json`

## State files

- `~/.rho/telegram/state.json` (poll offset + health)
- `~/.rho/telegram/session-map.json` (chat/session mapping)
- `~/.rho/telegram/log.jsonl` (audit events)
- `~/.rho/telegram/config.json` (runtime allow/revoke overrides)
- `~/.rho/telegram/inbound.queue.json` (durable inbound queue)
- `~/.rho/telegram/outbound.queue.json` (durable outbound queue)
- `~/.rho/telegram/jobs.json` (durable job records: queued/running/completed/failed/cancelled)
- `~/.rho/telegram/background.queue.json` (legacy deferred queue, auto-migrated when present)
- `~/.rho/telegram/pending-approvals.json` (approval requests)

## Smoke test

Run local smoke harness (real Telegram API):

```bash
export TELEGRAM_BOT_TOKEN="<token>"
export TELEGRAM_SMOKE_CHAT_ID="<chat-id>"
npm run telegram:smoke
```

Expected output:

- `sent message_id=... chat_id=...`
- `fetched updates=...`

## Known limits (MVP)

- Polling mode only (webhook deferred)
- Text-first outbound rendering (except `/tts` voice replies)
- STT/TTS depend on ElevenLabs availability and API quotas
- `/tts` currently uses a single configured/default ElevenLabs voice
- Operator controls optimized for single-agent runtime
