通道总览
Ongrid 通过通道和人交流。一个通道要么是:
- 通知通道 —— Ongrid 把告警推到聊天界面;人能看到,但通道本身永远不会把回复带回 Agent。在数据库里对应
notify_channels,代码上对应internal/pkg/notify/webhook.go里的 Senders。 - IM 通道 —— Ongrid 作为机器人常驻一个工作区聊天界面,接收用户消息,跑跟 web UI 同一套 Agent 推理,并以"占位消息编辑"的形式把答案流式回传。在数据库里对应
im_apps,代码上对应internal/manager/biz/imbridge/provider/*。
两者互不依赖。配通了 Telegram 告警(sendMessage 出)并不代表用户能在 Telegram 上找 Agent 聊天,反之亦然。
两张表、两套凭据、同一个 UI
两类入口都在 web UI 的 Settings → Channels 配。通知通道在 Notify 标签页;IM 通道在 IM bridge 标签页。
我要哪一种?
| 目标 | 用 | 文件 |
|---|---|---|
| 把告警推到 Slack 频道 | Slack incoming webhook(Notify) | internal/pkg/notify/webhook.go NewSlackSender |
| 把告警推到飞书群 | 飞书自定义机器人(Notify) | NewFeishuSender |
| 把告警推到钉钉群 | 钉钉自定义机器人(Notify) | NewDingTalkSender |
| 把告警推到企业微信群 | 企业微信群机器人(Notify) | NewWeComSender |
| 通用 JSON POST 到你自己的服务 | Webhook(Notify) | NewGenericWebhookSender |
| Telegram 告警(单向) | Telegram bot sendMessage(Notify) | NewTelegramSender |
| 在 Slack 里和 Agent 对话 | Slack Socket Mode app(IM bridge) | imbridge/provider/slack/ |
| 在 Telegram 里和 Agent 对话 | Telegram bot getUpdates(IM bridge) | imbridge/provider/telegram/ |
| 在飞书/Larksuite 里和 Agent 对话 | 飞书长连接(IM bridge) | imbridge/provider/feishu/ |
一个工作区可以同时跑两类:比如 Slack incoming webhook 推告警 + 单独的 Slack Socket Mode app 做对话。它们不共享任何状态。
通知通道
通知通道是一个无状态的 Send(ctx, Message) Sender。告警触发(或恢复、或冷却窗口结束)时由告警流水线调用。所有类型最终都渲染同一个 notify.Message 形状;只有载荷格式和签名协议因 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 页脚。飞书 / 钉钉 / 企微 sender 因为机器人 API v1 只接受纯文本,所以扁平化成 [SEV] subject\nbody\nsource:…\ndedupe:…。
签名模型一览
| Provider | 怎么鉴权 |
|---|---|
| Slack | webhook URL 本身就是秘密。无额外签名。 |
| 飞书 | HMAC-SHA256(ts\nsecret),作为 sign 字段放进 JSON body。 |
| 钉钉 | HMAC-SHA256(ts\nsecret),作为 sign 放进 URL query。 |
| 企微 | URL query 里带 bot key。无额外签名。 |
| Telegram | 路径里带 bot token(/bot<TOKEN>/sendMessage)。 |
| Webhook | 可选 X-Ongrid-Signature: sha256=<HMAC> 对 body 签名。 |
具体实现在 internal/pkg/notify/webhook.go:signFeishu、signDingTalkURL、signGenericWebhook。
Slack 静默丢弃 secret 字段
Slack incoming webhook 的 URL 本身就是凭据。如果你给 Slack 通道填了 Secret,通道构造器会在创建 Sender 之前把它丢掉。这是有意的 —— Slack 的协议里 incoming webhook 没有独立签名面。
IM 通道
一个 IM 通道是一个长跑 goroutine,做三件事:
- 主动连出到 provider(飞书/Slack 用 websocket,Telegram 用长轮询)。manager 上不开任何入站端口。
- 收到入站消息,过一道
allow_from,然后把文本交给bizbridge.Bridge.HandleInbound。 HandleInbound发一个占位回复,跑完整 Agent 图(和 web UI 用的同一个),把流式编辑回写到占位消息 id 上。
同一套 Agent kernel、技能和 persona 注册表同时驱动 web UI 和 IM 通道。没有"专门给 IM 的 Agent" —— 你在 /chat 上看到的 coordinator 就是它。
入站按 provider 区分,出站一致
provider 差异(Slack envelope_id ack、Telegram update offset、飞书 encrypt_key)放在 imbridge/provider/*/stream.go 里。一旦消息进了 bizbridge.HandleInbound,下游全部 provider 无关。
default_locale
IM 和 Notify 两类行上都带可选的 default_locale。校验器只接受空字符串(auto)、en、zh —— en-US / zh-CN 会折叠到主子标签,EN-us 或拼错的 locale 在 AppInput.validate 里直接拒掉。
| 取值 | 行为 |
|---|---|
""(auto) | 不加 directive。LLM 跟随用户语言。历史默认值。 |
en | 给 system prompt 加 Respond in English directive。 |
zh | 给 system prompt 加「请用中文回复」directive。 |
这与 UI locale(ONGRID_DEFAULT_LOCALE 环境变量或浏览器语言)相互独立。IM 通道接收到的消息一律以通道自己的 locale 为准。manager 主动触发的输出走自动回退规则,参见 AI 输出语言反馈。
allow_from
发送方白名单。Telegram 和 Slack 必填,飞书/钉钉可选。只在 ParseAllowFrom 里解析一次,校验器和每个 provider 的轮询/流式循环共享同一份定义。
语法。 逗号、空格、换行、tab、分号 —— 任意组合都作为分隔。保持顺序、去重。telegram: 和 tg: 前缀会被静默剥掉(兼容 OpenClaw)。
按 provider 的格式。
- Telegram。 仅数字 user ID。非数字 token 和负值(群聊 ID)在校验时被丢掉。至少一个 ID 是必须的 —— 机器人能被任何人通过用户名搜到,空白名单等于把带工具的 Agent 暴露给任何 Telegram 用户。见 ADR-031。
- Slack。
U开头的 user ID(Enterprise guest 是W)。至少一个。怎么查自己的 ID:工作区个人资料 →⋯→ Copy member ID。 - 飞书 / 钉钉。 可选。这两个平台天然按企业租户成员关系门控,只有组织成员才能联系到机器人。
Telegram 或 Slack 空白名单会被拒
校验器在解析后列表为空时返回 telegram requires allow_from / slack requires allow_from。没有"默认拒绝、之后再放开"的模式。机器人是公开可达的;运维必须主动放门。
失败模式是静默丢弃。 不在白名单里的发送者来的消息在 WARN 等级带 user_id 记一条日志,然后丢掉。不回复、不发占位、不跑 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深入细节看分通道页面: