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.

202
Source Files
25+
Platforms
9
Chat Channels
TypeScript
Language

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

  1. Platform delivers raw message — WhatsApp webhook, Telegram update, Slack event, etc.
  2. Channel adapter normalizes — Extracts sender identity, chat type, thread ID, media attachments into a uniform MsgContext.
  3. Allowlist check — allowlist-match.ts compiles allowlist entries into a CompiledAllowlist Set and checks sender candidates (ID, username, tag) against it. Wildcard * allows everyone.
  4. Mention gating — In group chats, mention-gating.ts checks if the bot was @mentioned. Bypass rules apply for authorized control commands.
  5. Debounce policy — Rapid text-only messages are batched within a configurable window (100–500ms per channel).
  6. Session recording — session.ts persists the last channel, account, and thread for /continue support.
  7. Agent processes — Status reactions update emoji (queued → thinking → tool → done) with debounce and stall timers.
  8. Outbound delivery — Reply formatted per channel capabilities, sent through outbound adapter.

Key Types & Interfaces

TypeFileDescription
AllowlistMatch<T>allowlist-match.tsResult of allowlist resolution: allowed flag, match key, match source
CompiledAllowlistallowlist-match.tsPre-compiled Set + wildcard flag for fast lookups
ChannelEntryMatch<T>channel-config.tsConfig resolution result with direct/parent/wildcard fallback
ChatTypechat-type.tsUnion: "direct" | "group" | "channel"
AckReactionScopeack-reactions.tsWhen to show ack reactions: all, direct, group-all, group-mentions, off
CommandAuthorizercommand-gating.tsConfigured + allowed flags for command access control
StatusReactionControllerstatus-reactions.tsState machine for debounced emoji status updates
TypingCallbackstyping.tsStart/stop typing with keepalive loop and TTL safety
MentionGateResultmention-gating.tsWhether message should be skipped based on mention requirements
ChatChannelIdids.tsUnion of 9 built-in channel IDs (telegram, whatsapp, discord…)

Configuration Knobs

ParameterTypeDescription
allowFromstring[]Allowlist of sender IDs/handles who can trigger the agent. "*" = allow all.
groupAllowFromstring[]Separate allowlist for group chats; falls back to allowFrom if empty.
commandGatingCommandGatingModeWhenAccessGroupsOffControl command access: "allow", "deny", or "configured".
requireMentionbooleanIn groups, require @mention before processing the message.
debounceMsnumberInbound message debounce window (per-channel, 100–500ms).
statusReactionEmojisStatusReactionEmojisMap of emoji per agent state: queued, thinking, tool, done, error, stall.
stallSoftMs / stallHardMsnumberStall warning thresholds (default 10s soft, 30s hard).
ackReactionScopeAckReactionScopeWhen to show acknowledgement reactions: all, direct, group-mentions, off.
replyModestringThread binding strategy: reply, mention, edit, standalone.
mediaLimitsobjectSize/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
ModuleInteraction
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

TermDefinition
Debounce PolicyDelay window before processing inbound text to avoid thrashing on rapid keystrokes
Status ReactionEmoji indicator showing agent state (queued, running, done, error)
Stall TimerSoft/hard warnings if task exceeds expected duration
Thread BindingMapping between channel conversations (DM, group, Slack thread, Discord forum)
Last RouteMemory of last channel/account/thread used for /continue command
Capability DiscoveryQuery what actions (edit, unsend, reactions) a channel supports
Allowlist MatchingPattern-based sender filtering (handle, phone, email, ID)
Chat TypeDM, group, or channel — determines routing and gating behavior
Mention GatingRequire @mentions in group chats before processing
Command GatingRestrict 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