Skip to content

Overview de canales

Ongrid habla con humanos a través de canales. Un canal es:

  • un canal de notificación — Ongrid empuja alertas a una superficie de chat; el humano lee, pero el canal en sí nunca lleva una respuesta de vuelta al agente. Esto es notify_channels en la DB y los Senders de internal/pkg/notify/webhook.go.
  • un canal IM — Ongrid se sienta en la superficie de chat de un workspace como un bot, lee los mensajes entrantes y corre el mismo razonamiento de agente que corre la UI web, streameando la respuesta de vuelta como ediciones a un mensaje placeholder. Esto es im_apps en la DB e internal/manager/biz/imbridge/provider/*.

Los dos son independientes. Cablear alertas de Telegram (sendMessage out) no permite a los usuarios chatear con el agente en Telegram, y viceversa.

Distinta tabla, distinta credencial, misma UI

Ambas superficies se configuran en Settings → Channels en la UI web. Los canales de notificación van a la pestaña Notify; los canales IM van a la pestaña IM bridge.

¿Cuál quiero?

ObjetivoUsaArchivo
Empujar alertas disparadas a un canal de SlackSlack incoming webhook (Notify)internal/pkg/notify/webhook.go NewSlackSender
Empujar alertas disparadas a un grupo de Feishu/LarkBot custom de Feishu (Notify)NewFeishuSender
Empujar alertas disparadas a un grupo de DingTalkBot custom de DingTalk (Notify)NewDingTalkSender
Empujar alertas disparadas a un grupo de WeComBot de grupo de WeCom (Notify)NewWeComSender
POST JSON genérico a tu propio servicioWebhook (Notify)NewGenericWebhookSender
Alertas de Telegram (una sola dirección)sendMessage del bot de Telegram (Notify)NewTelegramSender
Hablar con el agente desde Slack como botApp Socket Mode de Slack (IM bridge)imbridge/provider/slack/
Hablar con el agente desde TelegramgetUpdates del bot de Telegram (IM bridge)imbridge/provider/telegram/
Hablar con el agente desde Feishu/LarksuiteLong-connection de Feishu (IM bridge)imbridge/provider/feishu/

Un workspace puede correr ambos: p. ej. un incoming webhook de Slack para alertas más una app Socket Mode de Slack separada para conversación. No comparten estado.

Canales de notificación

Un canal de notificación es un Sender stateless Send(ctx, Message). Lo llama el pipeline de alertas cada vez que dispara una alerta (o se recupera, o expira la ventana de dampening). Todos los tipos de canal renderizan la misma forma canónica notify.Message; solo difieren por provider el formato del payload y el protocolo de firma.

Campos comunes que recibe un Sender

go
type Message struct {
    Severity   Severity            // critical | warning | info
    Subject    string              // alert rule name + target
    Body       string              // human-readable detail
    Source     string              // "alert" | "test" | ...
    Labels     map[string]string   // rule, incident_id, device_id, ...
    DedupeKey  string              // pipeline:rule:label-set
    OccurredAt time.Time
}

El sender de Slack lo renderiza en el formato de attachments con un side rail coloreado por severidad, campos estructurados (Severity, Source, Rule, Incident, Device, Dedupe) y un footer ts. Los senders de Feishu / DingTalk / WeCom lo aplanan a un payload de texto [SEV] subject\nbody\nsource:…\ndedupe:… porque sus APIs de bot solo envían texto plano en v1.

Modelos de firma de un vistazo

ProviderCómo autentica
SlackLa propia URL del webhook es el secreto. Sin firma extra.
FeishuHMAC-SHA256(ts\nsecret), puesto en el body JSON como sign.
DingTalkHMAC-SHA256(ts\nsecret), puesto en el query de URL como sign.
WeComBot key en el query de URL. Sin firma extra.
TelegramBot token en el path (/bot<TOKEN>/sendMessage).
WebhookX-Ongrid-Signature: sha256=<HMAC> opcional sobre el body.

Las implementaciones exactas viven en internal/pkg/notify/webhook.go: signFeishu, signDingTalkURL, signGenericWebhook.

Slack descarta el campo secret silenciosamente

La URL del incoming webhook de Slack es por sí misma la credencial. Si rellenas el campo Secret para un canal de Slack, el builder del canal lo descarta antes de construir el Sender. Esto es intencional — el protocolo de Slack no tiene una superficie de firma separada para incoming webhooks.

Canales IM

Un canal IM es una goroutine de larga vida que:

  1. Conecta saliente al provider (websocket para Feishu/Slack, long poll para Telegram). No abre puertos entrantes en el manager.
  2. Recibe mensajes entrantes del usuario, los filtra por allow_from y entrega el texto a bizbridge.Bridge.HandleInbound.
  3. HandleInbound postea una respuesta placeholder, corre el grafo completo del agente (el mismo que usa la UI web), y streamea ediciones de vuelta al id de mensaje placeholder.

El mismo kernel de agente, skills y registry de personas alimentan tanto la UI web como los canales IM. No hay "agente específico de IM" — el coordinator es el mismo que ves en /chat.

El inbound es específico del provider, el outbound es uniforme

Las diferencias de provider (ack envelope_id de Slack, offset de update de Telegram, encrypt_key de Feishu) viven en imbridge/provider/*/stream.go. Una vez que un mensaje está dentro de bizbridge.HandleInbound, todo downstream es agnóstico del provider.

default_locale

Tanto las filas IM como Notify llevan un default_locale opcional. El validator acepta string vacío (auto), en, o zh solo — en-US / zh-CN se doblan a su subtag primaria, y EN-us / un locale tipeado mal son rechazados de entrada en AppInput.validate.

ValorComportamiento
"" (auto)Sin directiva. El LLM espeja el idioma del usuario. Default legacy.
enAñade una directiva Respond in English al system prompt.
zhAñade una directiva 「请用中文回复」 al system prompt.

Esto es independiente del locale de UI (env ONGRID_DEFAULT_LOCALE o idioma del navegador). Un canal IM siempre gana para los mensajes recibidos a través de él. Ver el feedback de locale de salida de IA para la regla de fallback en triggers automáticos sobre salidas iniciadas por el manager.

allow_from

La allowlist de remitentes. Requerida para Telegram y Slack; opcional para Feishu/DingTalk. La parsea exactamente una vez ParseAllowFrom, compartida entre el validator y el loop de poll/stream de cada provider para que la regla de parse tenga una única definición.

Sintaxis. Coma, espacio, salto de línea, tab, punto y coma — cualquier combinación separa tokens. Se preserva el orden, se eliminan duplicados. Los prefijos telegram: y tg: se eliminan silenciosamente (compatibilidad con OpenClaw).

Formato por-provider.

  • Telegram. IDs numéricos de usuario solamente. Los tokens no numéricos y los valores negativos (IDs de grupo de chat) se descartan en tiempo de validate. Se requiere al menos un ID — el bot es públicamente descubrible por username, así que una allowlist vacía dejaría que cualquier usuario de Telegram comandara un agente equipado con tools. Ver ADR-031.
  • Slack. IDs de usuario empezando con U (o W para invitados Enterprise). Se requiere al menos uno. Encuentra el tuyo haciendo clic en tu perfil del workspace → Copy member ID.
  • Feishu / DingTalk. Opcional. Estas plataformas están gated por membresía de tenant empresarial; solo los miembros de la organización pueden alcanzar al bot.

Una allowlist vacía para Telegram o Slack se rechaza

El validator devuelve telegram requires allow_from / slack requires allow_from cuando la lista parseada está vacía. No hay modo "deny por defecto pero permite setup después". El bot es públicamente alcanzable; el operador debe abrir la puerta conscientemente.

El modo de falla es drop silencioso. Un mensaje de un remitente no allowlisted se loguea en WARN con el user_id del remitente, luego se descarta. Sin respuesta, sin placeholder, sin ejecución del agente. El bot ni siquiera confirma que existe — la misma forma que dmPolicy: allowFrom de OpenClaw.

Cómo se ve una entrega de alerta disparada

text
alert evaluator         → produces notify.Message

notify.Sender (per channel)   ↓ buildBody(msg) → JSON payload
                              ↓ signTarget(endpoint, secret, body) → headers / URL params
                              ↓ POST endpoint
                              ↓ resp.StatusCode in [200, 299] → success

                          chat surface

Para detalle más profundo ver las páginas por-canal: