채널 개요
Ongrid 는 채널 을 통해 사람과 대화합니다. 채널은 다음 둘 중 하나입니다.
- 알림 채널 — Ongrid 가 채팅 표면으로 알림을 푸시합니다. 사람은 읽기만 하며, 채널 자체가 답장을 에이전트에게 다시 전달하는 일은 없습니다. DB 의
notify_channels와internal/pkg/notify/webhook.go의 Sender 가 여기에 해당합니다. - IM 채널 — Ongrid 가 워크스페이스의 채팅 표면에 봇으로 상주하여 들어오는 메시지를 읽고, 웹 UI 가 돌리는 것과 동일한 에이전트 추론을 실행한 뒤, 자리표시자 메시지에 대한 편집 (edit) 으로 답을 스트리밍해 돌려보냅니다. DB 의
im_apps와internal/manager/biz/imbridge/provider/*가 여기에 해당합니다.
이 둘은 독립적입니다. Telegram 알림 (sendMessage 발신) 을 연결해도 사용자가 Telegram 에서 에이전트와 대화할 수는 없으며, 그 반대도 마찬가지입니다.
다른 테이블, 다른 자격 증명, 같은 UI
두 표면 모두 웹 UI 의 Settings → Channels 에서 구성합니다. 알림 채널은 Notify 탭으로, IM 채널은 IM bridge 탭으로 갑니다.
무엇을 원하나요?
| 목표 | 사용 대상 | 파일 |
|---|---|---|
| 발생한 알림을 Slack 채널로 푸시 | Slack incoming webhook (Notify) | internal/pkg/notify/webhook.go NewSlackSender |
| 발생한 알림을 Feishu/Lark 그룹으로 푸시 | Feishu 커스텀 봇 (Notify) | NewFeishuSender |
| 발생한 알림을 DingTalk 그룹으로 푸시 | DingTalk 커스텀 봇 (Notify) | NewDingTalkSender |
| 발생한 알림을 WeCom 그룹으로 푸시 | WeCom 그룹 봇 (Notify) | NewWeComSender |
| 자체 서비스로 일반 JSON POST | Webhook (Notify) | NewGenericWebhookSender |
| Telegram 알림 (단방향) | Telegram bot sendMessage (Notify) | NewTelegramSender |
| Slack 에서 봇 형태로 에이전트와 대화 | Slack Socket Mode 앱 (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 앱을 함께 둘 수 있고, 둘은 상태를 공유하지 않습니다.
알림 채널
알림 채널은 상태 없는 Send(ctx, Message) Sender 입니다. 알림이 발생할 때 (또는 복구되거나 dampening 윈도가 만료될 때) 알림 파이프라인이 호출합니다. 모든 채널 유형은 같은 정규 notify.Message 모양을 렌더링합니다. 제공자별로 다른 것은 페이로드 포맷 과 서명 프로토콜 뿐입니다.
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, Source, Rule, Incident, Device, Dedupe), 그리고 ts 푸터까지. Feishu / DingTalk / WeCom Sender 는 평탄화하여 [SEV] subject\nbody\nsource:…\ndedupe:… 텍스트 페이로드로 만듭니다 — 해당 봇 API 가 v1 에서는 일반 텍스트만 전달하기 때문입니다.
서명 모델 한눈에 보기
| 제공자 | 인증 방식 |
|---|---|
| Slack | webhook URL 자체가 비밀입니다. 별도 서명 없음. |
| Feishu | HMAC-SHA256(ts\nsecret), JSON body 에 sign 으로 포함. |
| DingTalk | HMAC-SHA256(ts\nsecret), URL query 에 sign 으로 포함. |
| WeCom | Bot key 가 URL query 에 들어감. 별도 서명 없음. |
| Telegram | Bot token 이 경로 (/bot<TOKEN>/sendMessage) 에 포함. |
| Webhook | 선택적 X-Ongrid-Signature: sha256=<HMAC> (본문 기준). |
정확한 구현은 internal/pkg/notify/webhook.go 에 있습니다: signFeishu, signDingTalkURL, signGenericWebhook.
Slack 은 secret 필드를 조용히 버립니다
Slack incoming webhook URL 자체가 자격 증명입니다. Slack 채널의 Secret 필드를 채워도, 채널 빌더가 Sender 를 만들기 전에 그 값을 버립니다. 의도된 동작입니다 — Slack 프로토콜에는 incoming webhook 에 대한 별도 서명 면이 없기 때문입니다.
IM 채널
IM 채널은 다음 일을 하는 장기 실행 goroutine 입니다.
- 제공자로 아웃바운드 연결 (Feishu/Slack 은 websocket, Telegram 은 long poll). 매니저에 인바운드 포트는 열리지 않습니다.
- 들어오는 사용자 메시지를 받아
allow_from으로 필터링한 뒤, 텍스트를bizbridge.Bridge.HandleInbound로 넘깁니다. HandleInbound가 자리표시자 답장을 띄우고, 전체 에이전트 그래프 (웹 UI 가 쓰는 바로 그것) 를 실행하면서 자리표시자 메시지 ID 로 편집을 스트리밍해 돌려보냅니다.
같은 에이전트 커널, skill, persona 레지스트리가 웹 UI 와 IM 채널 양쪽에 전력을 공급합니다. "IM 전용 에이전트" 같은 것은 없습니다 — coordinator 는 /chat 에서 보는 그 것과 동일합니다.
인바운드는 제공자별, 아웃바운드는 균일
제공자 간 차이 (Slack 의 envelope_id ack, Telegram 의 update offset, Feishu 의 encrypt_key) 는 imbridge/provider/*/stream.go 에 있습니다. 일단 메시지가 bizbridge.HandleInbound 안에 들어오면, 그 이후의 모든 것은 제공자에 무관합니다.
default_locale
IM 과 Notify 양쪽 행 모두 선택적 default_locale 을 갖습니다. 밸리데이터는 빈 문자열 (auto), en, zh 만 받아들이며 — en-US / zh-CN 은 1차 서브태그로 접히고, EN-us / 오타 locale 은 AppInput.validate 에서 사전에 거절됩니다.
| 값 | 동작 |
|---|---|
"" (auto) | 지시문 없음. LLM 이 사용자의 언어를 따라갑니다. 기존 기본값. |
en | 시스템 프롬프트에 Respond in English 지시문을 추가. |
zh | 시스템 프롬프트에 「请用中文回复」 지시문을 추가. |
이는 UI locale (ONGRID_DEFAULT_LOCALE 환경 변수 또는 브라우저 언어) 와는 독립적입니다. IM 채널을 통해 받은 메시지에 대해서는 항상 IM 채널 쪽이 이깁니다. 매니저가 자체적으로 발생시키는 출력에 대한 자동 트리거 폴백 규칙은 AI 출력 locale 피드백 을 참고하세요.
allow_from
발신자 허용 목록입니다. Telegram 과 Slack 에서는 필수, Feishu/DingTalk 에서는 선택입니다. 정확히 한 번 ParseAllowFrom 이 파싱하며, 밸리데이터와 모든 제공자의 poll/stream 루프에서 공유되어 파싱 규칙이 단일 정의를 갖습니다.
문법. 쉼표, 공백, 줄바꿈, 탭, 세미콜론 — 임의 조합으로 토큰을 구분합니다. 순서는 보존되고 중복은 제거됩니다. telegram: 과 tg: 접두사는 조용히 제거됩니다 (OpenClaw 호환).
제공자별 포맷.
- Telegram. 숫자 사용자 ID 만 허용. 숫자가 아닌 토큰과 음수 (그룹 채팅 ID) 는 검증 시점에 버려집니다. 최소 한 개의 ID 가 필요 합니다 — 봇은 username 으로 누구나 검색해 찾을 수 있으므로, 빈 허용 목록이면 임의의 Telegram 사용자가 도구로 무장한 에이전트에 명령을 내릴 수 있게 됩니다. ADR-031 참고.
- Slack.
U로 시작하는 사용자 ID (또는 Enterprise 게스트의 경우W). 최소 한 개가 필요합니다. 워크스페이스 프로필 →⋯→ Copy member ID 로 본인 ID 를 찾을 수 있습니다. - Feishu / DingTalk. 선택. 이 플랫폼들은 기업 테넌트 멤버십으로 게이팅되므로, 조직 구성원만 봇에 도달할 수 있습니다.
Telegram 또는 Slack 에서 빈 허용 목록은 거절
파싱된 리스트가 비면 밸리데이터가 telegram requires allow_from / slack requires allow_from 을 반환합니다. "기본은 거절이지만 나중에 설정 가능" 같은 모드는 없습니다. 봇은 공개적으로 접근 가능하므로 운영자가 의식적으로 문을 열어야 합니다.
실패 모드는 침묵 드롭. 허용되지 않은 발신자의 메시지는 발신자의 user_id 와 함께 WARN 으로 로깅된 후 버려집니다. 답장도, 자리표시자도, 에이전트 실행도 없습니다. 봇은 자신이 존재한다는 사실조차 확인해 주지 않습니다 — 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더 깊은 내용은 채널별 페이지를 참고하세요: