Telegram
Telegram funciona en ambos modos:
| Modo | Qué hace |
|---|---|
| Notificación | sendMessage unidireccional desde el pipeline de alertas. |
| IM bridge | Chat bidireccional con el agente vía long-poll getUpdates. |
El mismo bot token (8…:AA…, de @BotFather) alimenta ambos — Telegram autentica por token-en-path. No hay un esquema de firma separado.
Diseñado para usuarios fuera del territorio Feishu / DingTalk
Telegram es el canal IM que Ongrid recomienda para equipos no-China y para despliegues híbridos donde los operadores residen en Telegram. El provider se añadió en ADR-031.
Modo notificación
Un notify.Sender POSTea {chat_id, text} a https://api.telegram.org/bot<TOKEN>/sendMessage. La credencial es el bot token (en el path); el chat_id (numérico) es el chat de destino.
// internal/pkg/notify/webhook.go
NewTelegramSender(name, endpoint, chatID, client)Donde endpoint es la URL literal …/bot<TOKEN>/sendMessage y chatID es el chat target (el ID numérico de un usuario para un DM, o el número negativo que Telegram te da para un grupo).
Setup:
- DM
@BotFather→/newbot→ elige un username → copia el token. - Añade el bot al chat target (grupo / canal) o hazle DM una vez para que sepa de ti.
- Toma el chat ID. La forma más rápida: envía cualquier mensaje en el chat, luego
curl https://api.telegram.org/bot<TOKEN>/getUpdatesy leeresult[0].message.chat.id. - En Ongrid: Settings → Channels → New → Provider =
telegram→ Endpoint =https://api.telegram.org/bot<TOKEN>/sendMessage, chat_id = el número del paso 3.
Modo IM bridge (bidireccional)
El inbound se hace long-poll desde la conexión HTTPS saliente del manager. Como la llamada es saliente, Telegram funciona detrás de NAT, firewalls y proxies HTTPS. setWebhook (la alternativa) requeriría que Telegram alcanzara al manager — eso es incompatible con la mayoría de los despliegues private-cloud y poco fiable desde China continental.
stream es el único modo
El validator rechaza cualquier otra cosa:
telegram only supports stream mode
El modo webhook no se expone en la UI para Telegram.
Mapeo de credenciales
La fila im_apps reutiliza columnas existentes — sin cambios de schema:
Columna im_apps | Significado en Telegram |
|---|---|
provider | "telegram" |
mode | "stream" (el validator lo fija) |
app_id | Username del bot (p. ej. ongridbot). Display + dedupe. |
app_secret | Token de BotFather (8…:AA…). Encriptado en reposo. |
allow_from | Requerido comma-separated IDs numéricos de usuario. |
verify_token | No usado. |
encrypt_key | No usado. |
allow_from
Una lista no vacía de IDs numéricos de usuario de Telegram que pueden conversar con el bot. El validator descarta los tokens no numéricos (así un alice tipeado mal aterriza como un error de required limpio, no como una allowlist silenciosamente vacía) y rechaza los tokens negativos (esos son IDs de chat de grupo / supergrupo y no pertenecen a una allowlist de remitentes).
telegram requires allow_from — at least one numeric Telegram user ID (the bot is publicly reachable; an empty allowlist would let anyone command the agent)
Cómo encontrar un ID numérico de usuario:
- Hazle DM a
@userinfoboty te responde con tu ID numérico. - O envía un mensaje en el chat después de registrarte, corre
curl https://api.telegram.org/bot<TOKEN>/getUpdates, y leeresult[0].message.from.id.
Los prefijos telegram: / tg: se eliminan silenciosamente (compatibilidad con OpenClaw), así que telegram:123456789 y 123456789 son la misma entrada.
Drop silencioso al fallar
Los remitentes no allowlisted se descartan con un log WARN:
telegram inbound from non-allowlisted sender — ignored
user_id=42 user_name=alice chat_id=42No hay respuesta. No hay placeholder. El bot ni siquiera confirma que existe. Esto espeja allowFrom de OpenClaw — una respuesta filtraría que un bot respaldado por agente vive en este username y tentaría a la gente a probarlo.
Setup
- DM
@BotFather→/newbot→ elige un nombre y un username único terminando enbot. Copia el token. - (Opcional)
@BotFather → /setprivacy → Disablesi quieres que el bot vea mensajes en grupos (el default es solo-mención). - Encuentra tu ID numérico de usuario vía
@userinfobot. - En Ongrid: Settings → IM bridge → New → Provider =
telegram→ Mode =stream→ App ID = el username del bot → App secret = el token de BotFather →allow_from= tu ID numérico (y los de tus teammates). Save y Enable. - DM al bot desde una cuenta allowlisted. El bot responde con un placeholder, luego lo edita in place mientras el agente razona.
La historia del proxy
El host de la API de Telegram (api.telegram.org) es alcanzable en la mayoría de redes pero está bloqueado desde China continental. El provider usa un http.Client de valor cero para que honre HTTPS_PROXY / HTTP_PROXY / NO_PROXY del entorno del manager — el mismo proxy que lleva getUpdates lleva sendMessage.
En despliegues docker-compose, persiste el proxy en docker-compose.override.yml:
services:
manager:
environment:
HTTPS_PROXY: http://your-proxy:8080
NO_PROXY: localhost,127.0.0.1,manager,mysql,prometheus,loki,tempo,grafana,qdrantNo pongas el proxy en el docker-compose.yml principal
El docker-compose.yml principal viene con cada release. Un archivo de override es por-entorno y sobrevive a reruns de make package / install script.
Quirks que vale la pena conocer
message is not modified es inocuo
Telegram devuelve HTTP 400 (message is not modified) cuando un payload de editMessageText coincide exactamente con el texto actual del mensaje. El streaming progresivo puede repetir el mismo chunk en un tick throttled o en el flush final. El cliente traga exactamente este string de error y devuelve nil — cualquier otra cosa (400, 403, 5xx) sigue propagándose. Ver EditMessageText.
Solo un poller por bot
Telegram rechaza llamadas concurrentes a getUpdates. El StreamSupervisor refuerza un cliente por fila im_app. Si duplicas una fila de app de Telegram, solo una pollearía con éxito — la otra verá 409 Conflict y hará backoff.
Retries por rate-limit
429 Too Many Requests se reintenta hasta 3 veces. La espera honra el parameters.retry_after de Telegram, topado en 60s. Los errores 5xx reintentan con backoff 1s / 2s / 4s. Los 4xx duros (400/401/403) no reintentan — burbujean al supervisor y probablemente indican un problema de token / chat-id. Ver maxCallRetries.
Timeout del long-poll
El long poll del lado del servidor espera 25s (pollTimeoutSec) con un buffer de 10s del lado del cliente. Las redes lentas solo ven ciclos de poll más largos; el supervisor no está haciendo reconnects busy-wait.
Solo texto
Stickers, fotos, voice notes y archivos se descartan silenciosamente. Solo los eventos message.text mueven un turno del agente. El bridge es intencionalmente S1 (texto in / texto out); el rich media es una expansión futura.