Slack
Ongrid se integra con Slack en dos modos distintos, configurados en paneles separados de Settings:
| Modo | Úsalo para | Tokens |
|---|---|---|
| Notificación | Empujar alertas disparadas a un canal | Solo la URL del webhook |
| Socket Mode (IM) | Hablar con el agente desde Slack | xapp-… + xoxb-… |
Puedes correr ambos. No comparten nada.
Notificación (incoming webhook)
La integración más simple. Slack asigna una URL específica del workspace de la forma https://hooks.slack.com/services/T…/B…/…; Ongrid POSTea el payload de alerta ahí.
Cómo se ve el payload
El sender de Slack renderiza un notify.Message en el formato attachments (elegido sobre Block Kit porque lleva el side rail coloreado que los operadores leen como severidad, y porque el schema es JSON-flat y está universalmente soportado):
{
"text": "[CRITICAL] node-01 swap_high",
"attachments": [
{
"color": "#d92f2f",
"fallback": "[CRITICAL] node-01 swap_high",
"title": "node-01 swap_high",
"text": "swap_in_pages > 1000 for 5m",
"mrkdwn_in": ["text"],
"fields": [
{"title": "Severity", "value": "CRITICAL", "short": true},
{"title": "Source", "value": "alert", "short": true},
{"title": "Rule", "value": "swap_high","short": true},
{"title": "Incident", "value": "#1234", "short": true},
{"title": "Device", "value": "#7", "short": true},
{"title": "Dedupe key", "value": "alert:swap_high:device=7", "short": false}
],
"footer": "ongrid",
"ts": 1717012345
}
]
}El campo text se mantiene poblado para que los previews de push de Slack / digest por email muestren una one-liner útil incluso cuando el cliente elimina attachments.
Severidad → color
| Severidad | Hex |
|---|---|
| critical | #d92f2f |
| warning | #f2c037 |
| info | #36a64f |
| (unknown) | #6f7a87 |
Hex fijo (no los sentinels danger / warning de Slack) para que el tono sea estable a través de versiones del cliente de Slack.
Setup
- En Slack: Apps → Incoming Webhooks → Add to Slack, elige el canal.
- Copia la URL del webhook.
- En Ongrid: Settings → Channels → New → Provider =
slack→ pega la URL en Endpoint.
El campo Secret del formulario se ignora para los incoming webhooks de Slack. La URL es la credencial; no hay superficie de firma separada. El builder del canal descarta el secret antes de construir NewSlackSender.
Prueba el canal
El botón Test en Settings → Channels envía un notify.Message{Severity: "info", Subject: "Ongrid test", …} sintético. Si el attachment aparece en el canal target con el rail verde, el canal está cableado.
Socket Mode (IM bridge)
Socket Mode es la integración bidireccional. El texto de usuario entrante se entrega sobre una WebSocket saliente (no se requiere ingress público — la misma forma que getUpdates de Telegram), y el manager responde usando la Web API estándar (chat.postMessage, chat.update).
Por qué Socket Mode y no Events API
La Events API requiere que Slack alcance el manager entrante, lo que significa un endpoint HTTPS público con un certificado válido. La mayoría de los despliegues de Ongrid son privados. Socket Mode es la alternativa soportada en la que el manager marca hacia afuera — honra HTTPS_PROXY / NO_PROXY y funciona detrás de NATs y la GFW. El validator rechaza mode != stream para Slack:
slack only supports stream mode (Socket Mode)
Tokens requeridos
Slack usa dos tokens para Socket Mode. Tienen scopes distintos:
| Token | Prefijo | Usado para |
|---|---|---|
| App-level token | xapp-… | apps.connections.open (obtiene una URL WebSocket). |
| Bot token | xoxb-… | chat.postMessage, chat.update, todas las demás llamadas a la Web API. |
Ongrid los almacena como un objeto JSON dentro de im_apps.app_secret. La función ParseSecret valida los prefijos de entrada para que un token mal pegado aparezca como un error limpio en vez de un 401 de chat.postMessage.
{
"app_token": "xapp-1-A0…",
"bot_token": "xoxb-1234567890-1234567890-…"
}Scopes de app requeridos
En la config de la app de Slack:
OAuth & Permissions → Bot Token Scopes
app_mentions:read— recibir eventosapp_mention.channels:history— recibir eventosmessageen canales públicos.groups:history— lo mismo para canales privados.im:history— lo mismo para DMs.chat:write—chat.postMessage/chat.update.
Socket Mode → Enable, luego Basic Information → App-Level Tokens con scope connections:write para obtener el token xapp-….
Event Subscriptions debe estar habilitado (toggle on), con el bot suscrito a message.channels, message.groups, message.im, app_mention. Slack los envía por Socket Mode igual que lo haría por webhook.
allow_from
Una lista separada por comas de IDs de usuario de Slack que pueden conversar con el bot. Los IDs de usuario empiezan con U (o W para invitados Enterprise). Se requiere al menos un ID:
slack requires allow_from — at least one Slack user ID (e.g. UABC123, find via Profile → ⋯ → Copy member ID). Without it any workspace member could command a tool-equipped agent
Encuentra tu propio ID en Slack: haz clic en tu avatar → Profile → el menú ⋯ → Copy member ID.
Los remitentes no allowlisted se descartan silenciosamente en handleEvent. El bot no responde — no confirma que existe. El comportamiento espeja Telegram allow_from.
Comportamiento de respuesta en streaming
Cuando un usuario allowlisted envía un mensaje:
- El bridge llama a
chat.postMessageuna vez con texto placeholder (Working on it…) y graba eltsdevuelto. - El agente corre. Cada chunk en streaming dispara un
chat.updateen el mismo(channel, ts). - El token terminal hace flush del texto final al mismo mensaje.
Slack acepta un chat.update no-op (mismo texto) silenciosamente — a diferencia de Telegram, que devuelve un 400. No hay swallow especial del lado de Slack. Ver senderAdapter en stream.go.
Loops de bot, ediciones e IDs de hilo
- Los mensajes con
bot_id != ""se descartan — son las propias respuestas del manager y crearían un feedback loop. - Los mensajes con cualquier
subtypeno vacío (message_changed,channel_join, …) se descartan. thread_tsse almacena comoImThread.ImThreadIDpara que una respuesta dentro de un hilo continúe la misma sesión de conversación.- El markup de mención de Slack
<@U…>se reescribe a un token desnudo@U…porstripMentionspara que el modelo vea una referencia estable sin un round-trip ausers.infopor mensaje.
Setup
- Crea una app de Slack: api.slack.com → Create New App → from scratch, elige el workspace.
- Habilita Socket Mode, genera un app-level token con
connections:write. Copia el valorxapp-…. - Establece los Bot Token Scopes listados arriba.
- Instala la app al workspace. Copia el Bot User OAuth Token
xoxb-…. - Habilita Event Subscriptions, suscribe al bot a los eventos listados arriba.
- En Ongrid: Settings → IM bridge → New → Provider =
slack→ Mode =stream→ pega app_id (el handle del bot, p. ej.ongrid-bot), el JSON de dos tokens en App secret, y al menos un ID de usuario de Slack en allow_from.
Reinstala la app después de añadir scopes
Slack no otorga scopes retroactivamente a bots ya instalados. Si chat.postMessage devuelve missing_scope después de que añadiste chat:write, reinstala la app al workspace.
Keep-alive
El loop de stream pingea cada 20s (pingInterval). Slack cierra conexiones Socket Mode idle después de ~30s. El ping corre en una goroutine para que el read loop nunca quede bloqueado detrás de un write. Un envelope disconnect iniciado por Slack cierra limpiamente y el supervisor reconecta inmediatamente (sin sleep de backoff) con una URL fresca de apps.connections.open.