Обзор каналов
Ongrid разговаривает с людьми через каналы. Канал — это либо:
- notification-канал — Ongrid пушит алерты на chat-поверхность; человек читает, но сам канал никогда не несёт ответ обратно агенту. Это
notify_channelsв БД и Sender'ыinternal/pkg/notify/webhook.go. - IM-канал — Ongrid сидит на chat-поверхности воркспейса как бот, читает входящие сообщения и запускает тот же агент-reasoning, который запускает веб-UI, стримя ответ обратно как edit к placeholder- сообщению. Это
im_appsв БД иinternal/manager/biz/imbridge/provider/*.
Эти два — независимы. Подключение Telegram-алертов (sendMessage out) не позволяет пользователям чатиться с агентом в Telegram, и наоборот.
Разная таблица, разный credential, тот же UI
Обе поверхности конфигурируются в Settings → Channels в веб-UI. Notification-каналы идут на вкладку Notify; IM-каналы идут на вкладку IM bridge.
Что мне нужно?
| Цель | Используйте | Файл |
|---|---|---|
| Пушить сработавшие алерты в Slack-канал | Slack incoming webhook (Notify) | internal/pkg/notify/webhook.go NewSlackSender |
| Пушить сработавшие алерты в Feishu/Lark-группу | Feishu custom bot (Notify) | NewFeishuSender |
| Пушить сработавшие алерты в DingTalk-группу | DingTalk custom bot (Notify) | NewDingTalkSender |
| Пушить сработавшие алерты в WeCom-группу | WeCom group bot (Notify) | NewWeComSender |
| Generic JSON POST в ваш собственный сервис | Webhook (Notify) | NewGenericWebhookSender |
| Telegram-алерты (одно направление) | Telegram bot sendMessage (Notify) | NewTelegramSender |
| Разговаривать с агентом из Slack как с ботом | Slack Socket Mode app (IM bridge) | imbridge/provider/slack/ |
| Разговаривать с агентом из Telegram | Telegram bot getUpdates (IM bridge) | imbridge/provider/telegram/ |
| Разговаривать с агентом из Feishu/Larksuite | Feishu long-connection (IM bridge) | imbridge/provider/feishu/ |
Воркспейс может запускать оба: например, Slack incoming webhook для алертов плюс отдельный Slack Socket Mode app для разговора. Они не делят никакого состояния.
Notification-каналы
Notification-канал — это stateless Send(ctx, Message) Sender. Он вызывается конвейером алертов всякий раз, когда алерт срабатывает (или восстанавливается, или истекает окно дампенинга). Все типы каналов рендерят одну каноническую форму notify.Message; различаются только формат payload и протокол подписи per provider.
Общие поля, которые получает Sender
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
}Slack sender рендерит это в attachments-формат с severity-окрашенным боковым рельсом, структурированными полями (Severity, Source, Rule, Incident, Device, Dedupe) и ts footer. Senders Feishu / DingTalk / WeCom плющат это в text-payload [SEV] subject\nbody\nsource:…\ndedupe:…, потому что их bot API в v1 поставляют только plain text.
Модели подписи с одного взгляда
| Provider | Как аутентифицирует |
|---|---|
| Slack | Сам webhook URL — это secret. Без дополнительной подписи. |
| Feishu | HMAC-SHA256(ts\nsecret), помещён в JSON body как sign. |
| DingTalk | HMAC-SHA256(ts\nsecret), помещён в URL query как sign. |
| WeCom | Bot key в URL query. Без дополнительной подписи. |
| Telegram | Bot-токен в пути (/bot<TOKEN>/sendMessage). |
| Webhook | Опциональный X-Ongrid-Signature: sha256=<HMAC> поверх body. |
Точные реализации живут в internal/pkg/notify/webhook.go: signFeishu, signDingTalkURL, signGenericWebhook.
Slack молча отбрасывает поле secret
Slack incoming webhook URL — сам по себе credential. Если вы заполняете поле Secret для Slack-канала, channel builder отбрасывает его до конструирования Sender. Это намеренно — у протокола Slack нет отдельной поверхности подписи для incoming webhook.
IM-каналы
IM-канал — это долгоживущая горутина, которая:
- Соединяется исходящим к провайдеру (websocket для Feishu/Slack, long poll для Telegram). Никакие входящие порты не открываются на manager.
- Получает входящие сообщения пользователей, фильтрует их через
allow_from, и передаёт текстbizbridge.Bridge.HandleInbound. HandleInboundпостит placeholder-ответ, запускает полный agent- граф (тот же, что использует веб-UI), и стримит edits обратно на id placeholder-сообщения.
То же agent kernel, скиллы и реестр персон питают и веб- UI, и IM-каналы. Нет «IM-specific agent» — coordinator — тот же, что вы видите на /chat.
Входящее provider-specific, исходящее единообразное
Различия между провайдерами (Slack envelope_id ack, Telegram update offset, Feishu encrypt_key) живут в imbridge/provider/*/stream.go. Как только сообщение внутри bizbridge.HandleInbound, всё ниже по потоку provider-agnostic.
default_locale
И IM, и Notify строки несут опциональный default_locale. Валидатор принимает только пустую строку (auto), en или zh — en-US / zh-CN сворачиваются к их primary subtag, а EN-us / опечатанная локаль отвергается на входе в AppInput.validate.
| Значение | Поведение |
|---|---|
"" (auto) | Без директивы. LLM зеркалит язык пользователя. Legacy default. |
en | Добавить директиву Respond in English в системный промпт. |
zh | Добавить директиву 「请用中文回复」 в системный промпт. |
Это независимо от локали UI (env-переменная ONGRID_DEFAULT_LOCALE или язык браузера). IM-канал всегда побеждает для сообщений, полученных через него. См. feedback по AI output locale для правила auto-trigger fallback на manager-initiated выводах.
allow_from
Allowlist отправителей. Обязателен для Telegram и Slack; опционален для Feishu/DingTalk. Парсится ровно один раз ParseAllowFrom, разделяется между валидатором и poll/stream-циклом каждого провайдера, так что правило парсинга имеет единственное определение.
Синтаксис. Запятая, пробел, перевод строки, табуляция, точка с запятой — любая комбинация разделяет токены. Порядок сохраняется, дубликаты отбрасываются. Префиксы telegram: и tg: срезаются молча (OpenClaw compatibility).
Per-provider формат.
- Telegram. Только числовые user ID. Нечисловые токены и отрицательные значения (group chat ID) отбрасываются на validate-времени. Минимум один ID требуется — бот публично discoverable по username, так что пустой allowlist позволил бы любому Telegram-пользователю командовать tool-equipped агентом. См. ADR-031.
- Slack. User ID, начинающиеся с
U(илиWдля Enterprise guests). Минимум один требуется. Найдите свой, кликнув в workspace-профиль →⋯→ Copy member ID. - Feishu / DingTalk. Опционально. Эти платформы гейтятся членством в enterprise-тенанте; только org-члены добираются до бота.
Пустой allowlist для Telegram или Slack отвергается
Валидатор возвращает telegram requires allow_from / slack requires allow_from, когда распарсенный список пуст. Нет режима «отказывать по умолчанию, но разрешить настройку позже». Бот публично достижим; оператор должен сознательно открыть дверь.
Режим отказа — молчаливое отбрасывание. Сообщение от не-allowlisted отправителя логируется на WARN с user_id отправителя, потом отбрасывается. Без ответа, без placeholder, без agent-прогона. Бот даже не подтверждает, что существует — та же форма, что у OpenClaw dmPolicy: allowFrom.
Как выглядит доставка сработавшего алерта
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Для более глубоких деталей см. per-channel страницы: