Channels — Multi-Platform Messaging Abstraction
Unified Messaging for 25+ Platforms — WhatsApp, Telegram, Slack, Discord & More
The Channels module provides a single abstraction layer that normalizes messaging across 25+ platforms. It handles inbound/outbound message routing, sender allowlists, command gating, acknowledgement reactions, typing indicators, thread bindings, session recording, and per-channel configuration — all without any channel-specific logic leaking into the rest of the codebase.
Multi-Channel Allowlists Status Reactions Command Gating
📚 What is this & why does it exist?
The Channels module is the front door of OpenClaw. Every message from every messaging platform flows through it. It normalizes the wild variety of platform-specific message formats into a single internal representation, decides who is allowed to talk to the agent, gates slash commands behind access controls, and provides visual feedback (emoji reactions, typing indicators) so users know the agent is working.
💡 Real-world analogy
Think of a multilingual reception desk at a global hotel. Guests arrive speaking 25 different languages (WhatsApp, Telegram, Slack…), but the concierge (OpenClaw agent) only speaks one internal language. The channels module is the team of translators at the desk — they normalize every request into a standard form, check the guest’s ID against the VIP list (allowlists), decide whether they’re authorized for premium services (command gating), and send a “please wait” signal (typing indicator) while the concierge works on the request.
Architecture
flowchart LR
subgraph Inbound["Inbound Pipeline"]
RAW["Raw Platform Message"]
NORM["Message Normalization"]
ALLOW["Allowlist Check"]
MENTION["Mention Gating"]
DEBNC["Debounce Policy"]
SESS["Session Recording"]
end
subgraph Config["Configuration"]
CHCFG["channel-config.ts"]
CFGP["config-presence.ts"]
CHAT["chat-type.ts"]
IDS["ids.ts"]
end
subgraph Access["Access Control"]
ALMATCH["allowlist-match.ts"]
ALFROM["allow-from.ts"]
CMDGATE["command-gating.ts"]
MGATE["mention-gating.ts"]
end
subgraph Feedback["Status Feedback"]
ACK["ack-reactions.ts"]
STATUS["status-reactions.ts"]
TYPING["typing.ts"]
TLIFE["typing-lifecycle.ts"]
end
RAW --> NORM
NORM --> ALLOW
ALLOW --> MENTION
MENTION --> DEBNC
DEBNC --> SESS
Config -.-> ALLOW
Access -.-> ALLOW
Access -.-> MENTION
Feedback -.-> SESS
Data Flow
sequenceDiagram
participant Platform as Messaging Platform
participant Adapter as Channel Adapter
participant Allowlist as Allowlist Engine
participant Gate as Command/Mention Gate
participant Session as Session Recorder
participant Agent as OpenClaw Agent
participant Reaction as Status Reactions
Platform->>Adapter: Raw inbound message
Adapter->>Adapter: Normalize to internal format
Adapter->>Allowlist: Check sender identity
Allowlist-->>Adapter: Allowed / Denied
alt Sender allowed
Adapter->>Gate: Check command & mention gating
Gate-->>Adapter: Pass / Block
Adapter->>Session: Record inbound session
Adapter->>Agent: Route to agent
Agent->>Reaction: setQueued()
Reaction->>Platform: React with emoji
Agent->>Agent: Process request
Agent->>Reaction: setDone()
Reaction->>Platform: Update emoji
Agent->>Adapter: Outbound reply
Adapter->>Platform: Send formatted message
end
🚶 Step-by-Step Narrated Walkthrough
- Platform delivers raw message — WhatsApp webhook, Telegram update, Slack event, etc.
- Channel adapter normalizes — Extracts sender identity, chat type, thread ID, media attachments into a uniform
MsgContext. - Allowlist check —
allowlist-match.tscompiles allowlist entries into aCompiledAllowlistSet and checks sender candidates (ID, username, tag) against it. Wildcard*allows everyone. - Mention gating — In group chats,
mention-gating.tschecks if the bot was @mentioned. Bypass rules apply for authorized control commands. - Debounce policy — Rapid text-only messages are batched within a configurable window (100–500ms per channel).
- Session recording —
session.tspersists the last channel, account, and thread for/continuesupport. - Agent processes — Status reactions update emoji (queued → thinking → tool → done) with debounce and stall timers.
- Outbound delivery — Reply formatted per channel capabilities, sent through outbound adapter.
Key Types & Interfaces
| Type | File | Description |
|---|---|---|
AllowlistMatch<T> | allowlist-match.ts | Result of allowlist resolution: allowed flag, match key, match source |
CompiledAllowlist | allowlist-match.ts | Pre-compiled Set + wildcard flag for fast lookups |
ChannelEntryMatch<T> | channel-config.ts | Config resolution result with direct/parent/wildcard fallback |
ChatType | chat-type.ts | Union: "direct" | "group" | "channel" |
AckReactionScope | ack-reactions.ts | When to show ack reactions: all, direct, group-all, group-mentions, off |
CommandAuthorizer | command-gating.ts | Configured + allowed flags for command access control |
StatusReactionController | status-reactions.ts | State machine for debounced emoji status updates |
TypingCallbacks | typing.ts | Start/stop typing with keepalive loop and TTL safety |
MentionGateResult | mention-gating.ts | Whether message should be skipped based on mention requirements |
ChatChannelId | ids.ts | Union of 9 built-in channel IDs (telegram, whatsapp, discord…) |
Configuration Knobs
| Parameter | Type | Description |
|---|---|---|
allowFrom | string[] | Allowlist of sender IDs/handles who can trigger the agent. "*" = allow all. |
groupAllowFrom | string[] | Separate allowlist for group chats; falls back to allowFrom if empty. |
commandGating | CommandGatingModeWhenAccessGroupsOff | Control command access: "allow", "deny", or "configured". |
requireMention | boolean | In groups, require @mention before processing the message. |
debounceMs | number | Inbound message debounce window (per-channel, 100–500ms). |
statusReactionEmojis | StatusReactionEmojis | Map of emoji per agent state: queued, thinking, tool, done, error, stall. |
stallSoftMs / stallHardMs | number | Stall warning thresholds (default 10s soft, 30s hard). |
ackReactionScope | AckReactionScope | When to show acknowledgement reactions: all, direct, group-mentions, off. |
replyMode | string | Thread binding strategy: reply, mention, edit, standalone. |
mediaLimits | object | Size/type constraints per channel for media attachments. |
Core Algorithms
Allowlist Matching
The allowlist system compiles a list of sender identifiers into a CompiledAllowlist containing a ReadonlySet<string> and a wildcard flag. On each inbound message, sender identity candidates (ID, username, tag, prefixed-id) are checked against the set in order. The first match wins.
export function resolveCompiledAllowlistMatch<TSource extends string>(params: {
compiledAllowlist: CompiledAllowlist;
candidates: Array<{ value?: string; source: TSource }>;
}): AllowlistMatch<TSource> {
if (params.compiledAllowlist.set.size === 0) {
return { allowed: false };
}
if (params.compiledAllowlist.wildcard) {
return { allowed: true, matchKey: "*", matchSource: "wildcard" as TSource };
}
return resolveAllowlistCandidates(params);
}
Status Reaction State Machine
The status reaction controller manages emoji-based feedback with debounce timers, stall detection, and serialized API calls. Each state transition is debounced (700ms default), but terminal states (done/error) fire immediately. Soft stall at 10s, hard stall at 30s trigger warning emoji.
stateDiagram-v2
[*] --> Queued
Queued --> Thinking : Agent starts
Thinking --> Tool : Tool call
Tool --> Thinking : Tool complete
Thinking --> Done : Response ready
Tool --> Done : Response ready
Thinking --> Error : Failure
Tool --> Error : Failure
Thinking --> StallSoft : 10s timeout
StallSoft --> StallHard : 30s timeout
StallSoft --> Done : Response ready
StallHard --> Done : Response ready
Done --> [*]
Error --> [*]
Typing Indicator with TTL Safety
Typing indicators use a keepalive loop (every 3s) with a TTL guard (60s max). A start guard tracks consecutive failures and trips the loop after N failures. A single stop() call is gated by a stopSent flag to prevent duplicate API calls.
Cross-Module Interactions
flowchart TD
CHAN["src/channels/"]
CFG["src/config/"]
PLUG["src/plugins/"]
SDK["src/plugin-sdk/"]
AUTO["src/auto-reply/"]
INFRA["src/infra/"]
AGENTS["src/agents/"]
CMDS["src/commands/"]
GW["src/gateway/"]
HOOKS["src/hooks/"]
CHAN --> CFG
CHAN --> PLUG
CHAN --> SDK
CHAN --> AUTO
CHAN --> INFRA
CHAN --> AGENTS
CHAN --> CMDS
CHAN --> GW
CHAN --> HOOKS
| Module | Interaction |
|---|---|
src/config/ | Reads channel config schema, session store paths |
src/plugins/ | Loads channel plugins from registry, runs plugin hooks |
src/plugin-sdk/ | Exposes channel types via public contract |
src/auto-reply/ | Session metadata, debounce policies, inbound templating |
src/infra/ | Outbound delivery, channel activity tracking |
src/agents/ | Tool discovery for channel-specific message actions |
src/commands/ | Channel setup, status, capability queries |
src/gateway/ | Webhook ingress, gateway-scoped channel bindings |
Sub-Module Deep-Dives
🔒 Allowlists & Access Control
Pattern-based sender filtering, compiled allowlist sets, DM/group policies, and identity candidate resolution.
- allowlist-match.ts — Core matching engine
- allow-from.ts — Source merging rules
- command-gating.ts — Slash command access control
⚙ Channel Configuration
Per-channel config resolution with direct/parent/wildcard fallback, chat type normalization, and channel presence detection.
- channel-config.ts — Config match resolution
- config-presence.ts — Channel detection
- chat-type.ts — DM/group/channel normalization
💬 Message Processing
Acknowledgement reactions, status emoji state machine, typing indicators with TTL safety, and message normalization.
- ack-reactions.ts — Reaction scope gating
- status-reactions.ts — Emoji state machine
- typing.ts — Typing indicator lifecycle
Glossary
| Term | Definition |
|---|---|
| Debounce Policy | Delay window before processing inbound text to avoid thrashing on rapid keystrokes |
| Status Reaction | Emoji indicator showing agent state (queued, running, done, error) |
| Stall Timer | Soft/hard warnings if task exceeds expected duration |
| Thread Binding | Mapping between channel conversations (DM, group, Slack thread, Discord forum) |
| Last Route | Memory of last channel/account/thread used for /continue command |
| Capability Discovery | Query what actions (edit, unsend, reactions) a channel supports |
| Allowlist Matching | Pattern-based sender filtering (handle, phone, email, ID) |
| Chat Type | DM, group, or channel — determines routing and gating behavior |
| Mention Gating | Require @mentions in group chats before processing |
| Command Gating | Restrict slash commands to owner/approvers only |
Source Tree
channels/ (202 files)
channels/
├── ids.ts ← Built-in channel ID constants
├── chat-type.ts ← ChatType union & normalization
├── chat-meta.ts ← ChatChannelMeta for 9 channels
├── registry.ts ← Channel plugin registry
├── allowlist-match.ts ← Compiled allowlist matching
├── allow-from.ts ← DM/group allowFrom merging
├── command-gating.ts ← Slash command authorization
├── mention-gating.ts ← Group mention requirements
├── channel-config.ts ← Config resolution with fallback
├── config-presence.ts ← Channel detection from config/env
├── ack-reactions.ts ← Acknowledgement reaction gating
├── status-reactions.ts ← Emoji state machine controller
├── typing.ts ← Typing indicator with TTL
├── typing-lifecycle.ts ← Keepalive loop management
├── session.ts ← Inbound session recording
├── targets.ts ← Outbound target resolution
├── thread-binding-id.ts ← Thread binding identifiers
├── thread-bindings-policy.ts ← Thread binding strategies
├── inbound-debounce-policy.ts ← Per-channel debounce windows
├── run-state-machine.ts ← Channel run state transitions
├── allowlists/ ← Allowlist resolution utilities
├── plugins/ ← 87 channel plugin files
├── transport/ ← Channel transport layer
└── web/ ← Web-based channel integrations