Skip to main content
The Channel interface defines the contract that all NanoClaw channels must implement. Channels handle communication with external platforms like WhatsApp, Telegram, Slack, Discord, and Gmail.

Interface Definition

Location: src/types.ts:82-93
export interface Channel {
  name: string;
  connect(): Promise<void>;
  sendMessage(jid: string, text: string): Promise<void>;
  isConnected(): boolean;
  ownsJid(jid: string): boolean;
  disconnect(): Promise<void>;
  // Optional methods:
  setTyping?(jid: string, isTyping: boolean): Promise<void>;
  syncGroups?(force: boolean): Promise<void>;
}

Required Properties

name
string
required
Unique identifier for the channel (e.g., "whatsapp", "telegram", "slack").This name is used:
  • In the channel registry
  • For logging and debugging
  • As a prefix for folder names ({channel}_{group-name})

Required Methods

connect
() => Promise<void>
required
Initialize and establish connection to the external platform.Called: Once at startup by the orchestrator.Responsibilities:
  • Authenticate with the platform
  • Set up event listeners for incoming messages
  • Restore any persisted session state
  • Call onMessage callback when messages are received
  • Call onChatMetadata callback when discovering new chats
Example:
async connect(): Promise<void> {
  this.client = await createClient();
  this.client.on('message', (msg) => {
    this.opts.onMessage(msg.chatJid, {
      id: msg.id,
      chat_jid: msg.chatJid,
      sender: msg.sender,
      sender_name: msg.senderName,
      content: msg.content,
      timestamp: new Date().toISOString(),
    });
  });
  await this.client.connect();
}
sendMessage
(jid: string, text: string) => Promise<void>
required
Send a text message to the specified chat or group.Parameters:
  • jid - Platform-specific chat identifier (e.g., "[email protected]" for WhatsApp)
  • text - Message text to send
Called: By the router when sending agent responses or scheduled task output.Example:
async sendMessage(jid: string, text: string): Promise<void> {
  await this.client.sendMessage(jid, { text });
}
isConnected
() => boolean
required
Check if the channel is currently connected to the platform.Returns: true if connected and ready to send/receive messages, false otherwise.Example:
isConnected(): boolean {
  return this.client !== null && this.client.state === 'connected';
}
ownsJid
(jid: string) => boolean
required
Determine if this channel handles messages for the given JID.Called: By the router to determine which channel should send a message.Returns: true if this channel owns the JID, false otherwise.Example:
ownsJid(jid: string): boolean {
  // WhatsApp JIDs end with @s.whatsapp.net or @g.us
  return jid.endsWith('@s.whatsapp.net') || jid.endsWith('@g.us');
}
disconnect
() => Promise<void>
required
Gracefully disconnect from the platform.Called: During shutdown to clean up resources.Example:
async disconnect(): Promise<void> {
  if (this.client) {
    await this.client.disconnect();
    this.client = null;
  }
}

Optional Methods

setTyping
(jid: string, isTyping: boolean) => Promise<void>
Show or hide typing indicator in the specified chat.Implementation is optional. Channels that support typing indicators (WhatsApp, Telegram) can implement this for better UX.Parameters:
  • jid - Chat identifier
  • isTyping - true to show typing indicator, false to hide
Example:
async setTyping(jid: string, isTyping: boolean): Promise<void> {
  if (isTyping) {
    await this.client.sendPresenceUpdate('composing', jid);
  } else {
    await this.client.sendPresenceUpdate('paused', jid);
  }
}
syncGroups
(force: boolean) => Promise<void>
Sync chat/group names and metadata from the platform.Implementation is optional. Useful for channels where chat metadata is not delivered inline with messages.Parameters:
  • force - If true, force a full sync even if recently synced
Responsibilities:
  • Fetch list of chats/groups from the platform
  • Call onChatMetadata for each chat with name and metadata
Example:
async syncGroups(force: boolean): Promise<void> {
  const chats = await this.client.getChats();
  for (const chat of chats) {
    this.opts.onChatMetadata(
      chat.jid,
      new Date().toISOString(),
      chat.name,
      'whatsapp',
      chat.isGroup
    );
  }
}

Callback Types

Channels receive callback functions via ChannelOpts to communicate with the orchestrator.

OnInboundMessage

Location: src/types.ts:96
export type OnInboundMessage = (chatJid: string, message: NewMessage) => void;
Call this when receiving a message from the platform. Parameters:
  • chatJid - Platform-specific chat identifier
  • message - Message object with the following structure:
interface NewMessage {
  id: string;              // Unique message ID from platform
  chat_jid: string;        // Chat identifier
  sender: string;          // Sender identifier (phone number, user ID, etc.)
  sender_name: string;     // Display name of sender
  content: string;         // Message text
  timestamp: string;       // ISO timestamp
  is_from_me?: boolean;    // True if sent by the bot itself
  is_bot_message?: boolean; // True if sent by another bot
}

OnChatMetadata

Location: src/types.ts:98-105
export type OnChatMetadata = (
  chatJid: string,
  timestamp: string,
  name?: string,
  channel?: string,
  isGroup?: boolean,
) => void;
Call this when discovering chat metadata. When to use:
  • Inline delivery: Some channels (Telegram) deliver chat names with every message - call this from connect() or message handlers
  • Separate sync: Other channels (WhatsApp) need explicit syncing - call this from syncGroups()
Parameters:
  • chatJid - Platform-specific chat identifier
  • timestamp - ISO timestamp of discovery
  • name - (Optional) Display name of the chat/group
  • channel - (Optional) Channel name
  • isGroup - (Optional) True if this is a group chat

Implementation Example

Here’s a complete minimal channel implementation:
import { Channel, OnInboundMessage, OnChatMetadata } from '../types.js';
import { ChannelOpts } from './registry.js';

export class ExampleChannel implements Channel {
  name = 'example';
  private client: any = null;
  private opts: ChannelOpts;

  constructor(opts: ChannelOpts) {
    this.opts = opts;
  }

  async connect(): Promise<void> {
    // Initialize client
    this.client = await createExampleClient();

    // Set up message handler
    this.client.on('message', (msg: any) => {
      this.opts.onMessage(msg.chatId, {
        id: msg.id,
        chat_jid: msg.chatId,
        sender: msg.from,
        sender_name: msg.fromName,
        content: msg.text,
        timestamp: new Date().toISOString(),
      });
    });

    // Connect to platform
    await this.client.connect();
  }

  async sendMessage(jid: string, text: string): Promise<void> {
    await this.client.send(jid, text);
  }

  isConnected(): boolean {
    return this.client?.connected ?? false;
  }

  ownsJid(jid: string): boolean {
    return jid.startsWith('example:');
  }

  async disconnect(): Promise<void> {
    await this.client?.disconnect();
    this.client = null;
  }

  // Optional: typing indicator
  async setTyping(jid: string, isTyping: boolean): Promise<void> {
    await this.client.setTyping(jid, isTyping);
  }

  // Optional: sync groups
  async syncGroups(force: boolean): Promise<void> {
    const chats = await this.client.getChats();
    for (const chat of chats) {
      this.opts.onChatMetadata(
        chat.id,
        new Date().toISOString(),
        chat.name,
        'example',
        chat.isGroup
      );
    }
  }
}

JID Format Guidelines

Each channel should use platform-specific JID formats that are:
  • Unique: No collisions between channels
  • Stable: Same chat always has same JID
  • Reversible: Can be used to send messages back
Common patterns:
ChannelJID FormatExample
WhatsApp{number}@{suffix}[email protected]
Telegramtg:{chat_id}tg:-1001234567890
Slackslack:{team}:{channel}slack:T1234:C5678
Discorddc:{guild}:{channel}dc:123456789:987654321
Gmailgmail:{email}gmail:[email protected]

Build docs developers (and LLMs) love