15 KiB
Channels
The channels module lets OpenJarvis send and receive messages through external messaging platforms. Each platform has a dedicated channel implementation that connects directly to the platform's API -- there is no intermediate gateway.
!!! note "Channels are disabled by default"
The [channel] config section defaults to enabled = false. You must set enabled = true and configure platform-specific credentials before channel features become active.
Overview
Channel messaging is built around the BaseChannel ABC. Each platform (Telegram, Discord, Slack, WhatsApp, etc.) has its own implementation registered via @ChannelRegistry.register("name"). Channels connect directly to their platform APIs, register handlers for incoming messages, and send outgoing messages.
graph LR
A[Your Code] -->|send| B[TelegramChannel / DiscordChannel / SlackChannel / ...]
B -->|Platform API| C[Telegram / Discord / Slack / ...]
C -->|incoming messages| B
B -->|on_message handlers| D[Your Handlers]
Supported Channels
| Channel | Registry Key | Platform | Pip Extra | Auth |
|---|---|---|---|---|
TelegramChannel |
telegram |
Telegram Bot API | channel-telegram |
Bot token |
DiscordChannel |
discord |
Discord Bot API | channel-discord |
Bot token |
SlackChannel |
slack |
Slack Web API | channel-slack |
Bot + App tokens |
WhatsAppChannel |
whatsapp |
WhatsApp Business API | — | API token |
WhatsAppBaileysChannel |
whatsapp_baileys |
WhatsApp (Baileys) | — | QR code auth |
WebhookChannel |
webhook |
Generic HTTP webhook | — | URL + optional secret |
EmailChannel |
email |
SMTP/IMAP email | — | Email credentials |
SignalChannel |
signal |
Signal Messenger | — | Signal CLI |
GoogleChatChannel |
google_chat |
Google Chat | — | Service account |
IRCChannel |
irc |
IRC | — | Server credentials |
WebChatChannel |
webchat |
Browser-based chat | — | None |
TeamsChannel |
teams |
Microsoft Teams | — | Bot credentials |
MatrixChannel |
matrix |
Matrix protocol | — | Homeserver + token |
MattermostChannel |
mattermost |
Mattermost | — | Bot token |
FeishuChannel |
feishu |
Feishu/Lark | — | App credentials |
BlueBubblesChannel |
bluebubbles |
iMessage (BlueBubbles) | — | BlueBubbles server |
LineChannel |
line |
LINE Messaging API | channel-line |
Channel access token |
ViberChannel |
viber |
Viber Bot API | channel-viber |
Auth token |
MessengerChannel |
messenger |
Facebook Messenger | channel-messenger |
Page access token |
RedditChannel |
reddit |
Reddit API | channel-reddit |
OAuth credentials |
MastodonChannel |
mastodon |
Mastodon API | channel-mastodon |
Access token |
XMPPChannel |
xmpp |
XMPP/Jabber | channel-xmpp |
JID + password |
RocketChatChannel |
rocketchat |
Rocket.Chat API | channel-rocketchat |
User credentials |
ZulipChannel |
zulip |
Zulip API | channel-zulip |
Bot email + API key |
TwitchChannel |
twitch |
Twitch IRC/API | channel-twitch |
OAuth token |
NostrChannel |
nostr |
Nostr protocol | channel-nostr |
Private key (nsec) |
Using a Channel
Connecting
from openjarvis.channels.telegram import TelegramChannel
channel = TelegramChannel(
bot_token="YOUR_BOT_TOKEN", # (1)!
)
channel.connect()
print(channel.status()) # ChannelStatus.CONNECTED
- Falls back to the
TELEGRAM_BOT_TOKENenvironment variable if not provided.
Sending Messages
from openjarvis.channels.telegram import TelegramChannel
channel = TelegramChannel()
channel.connect()
# Send to a chat by ID
ok = channel.send(
"123456789",
"Analysis complete. Results are ready.",
conversation_id="thread-abc123", # optional, for threading
)
if ok:
print("Message delivered")
else:
print("Delivery failed")
channel.disconnect()
Receiving Messages
Register handler callbacks before calling connect(). Each handler receives a ChannelMessage and can optionally return a reply string.
from openjarvis.channels._stubs import ChannelMessage
from openjarvis.channels.discord_channel import DiscordChannel
channel = DiscordChannel()
def handle_incoming(msg: ChannelMessage) -> None:
print(f"[{msg.channel}] {msg.sender}: {msg.content}")
print(f" conversation_id={msg.conversation_id}")
print(f" message_id={msg.message_id}")
channel.on_message(handle_incoming) # (1)!
channel.connect() # (2)!
# Messages now arrive asynchronously via the background listener thread
# Your main thread can continue doing other work
- Register one or more handlers. All registered handlers are called for every incoming message.
connect()starts the background listener thread after establishing the platform connection.
Listing Available Channels
from openjarvis.channels.slack import SlackChannel
channel = SlackChannel()
channel.connect()
channels = channel.list_channels()
print(channels) # ["general", "random", "dev"]
Disconnecting
channel.disconnect()
# Stops the listener thread and closes the platform connection
# Status becomes ChannelStatus.DISCONNECTED
ChannelMessage Fields
Every incoming message is delivered to handlers as a ChannelMessage dataclass.
| Field | Type | Description |
|---|---|---|
channel |
str |
Name of the channel the message arrived on |
sender |
str |
Identifier of the message sender |
content |
str |
Message text |
message_id |
str |
Unique message identifier (may be empty) |
conversation_id |
str |
Thread/conversation identifier (may be empty) |
session_id |
str |
Session identifier (may be empty) |
metadata |
dict[str, Any] |
Additional platform-specific metadata |
Event Bus Integration
Pass an EventBus to publish channel events to the rest of the system:
from openjarvis.core.events import EventBus, EventType
from openjarvis.channels.telegram import TelegramChannel
bus = EventBus()
def on_received(event):
print(f"Message received on {event.data['channel']}: {event.data['content']}")
def on_sent(event):
print(f"Message sent to {event.data['channel']}")
bus.subscribe(EventType.CHANNEL_MESSAGE_RECEIVED, on_received)
bus.subscribe(EventType.CHANNEL_MESSAGE_SENT, on_sent)
channel = TelegramChannel(bus=bus)
channel.connect()
| Event | Published When | Data Keys |
|---|---|---|
CHANNEL_MESSAGE_RECEIVED |
A message arrives from the platform | channel, sender, content, message_id |
CHANNEL_MESSAGE_SENT |
A message is successfully sent | channel, content, conversation_id |
CLI Commands
The jarvis channel subcommand group provides quick access to channel operations.
List Channels
jarvis channel list
Send a Message
# Send to a channel by name
jarvis channel send telegram "Build completed successfully"
Show Status
jarvis channel status
API Server Endpoints
When jarvis serve is running, three channel endpoints are available. Channels must be configured and enabled in [channel] for these endpoints to return data.
GET /v1/channels
Returns the list of registered channels and their status.
curl http://localhost:8000/v1/channels
{
"channels": ["telegram", "discord", "slack"],
"status": "connected"
}
If no channels are configured:
{"channels": [], "message": "No channels configured"}
POST /v1/channels/send
Send a message to a channel.
curl -X POST http://localhost:8000/v1/channels/send \
-H "Content-Type: application/json" \
-d '{"channel": "telegram", "content": "Hello!", "conversation_id": "conv-1"}'
{"status": "sent", "channel": "telegram"}
Required fields: channel, content. conversation_id is optional.
GET /v1/channels/status
Returns the connection status for each configured channel.
curl http://localhost:8000/v1/channels/status
{"status": "connected"}
Possible values: connected, disconnected, connecting, error, not_configured.
Configuration
Channel settings live in the [channel] section of ~/.openjarvis/config.toml. Each platform has its own nested sub-section.
[channel]
enabled = true
default_channel = ""
default_agent = "simple"
[channel.telegram]
bot_token = "YOUR_TELEGRAM_BOT_TOKEN"
[channel.discord]
bot_token = "YOUR_DISCORD_BOT_TOKEN"
[channel.slack]
bot_token = "YOUR_SLACK_BOT_TOKEN"
app_token = "YOUR_SLACK_APP_TOKEN"
Configuration Reference
| Key | Type | Default | Description |
|---|---|---|---|
enabled |
bool |
false |
Enable channel messaging |
default_channel |
str |
"" |
Default channel to use when not specified |
default_agent |
str |
simple |
Agent to use for handling inbound messages |
Platform-specific settings are configured in nested sub-sections (e.g., [channel.telegram], [channel.discord]).
Complete Example
This example connects a Telegram channel, registers a handler that echoes messages back, sends a test message, and then disconnects after a short wait.
import time
from openjarvis.channels._stubs import ChannelMessage
from openjarvis.channels.telegram import TelegramChannel
from openjarvis.core.events import EventBus
bus = EventBus()
channel = TelegramChannel(
bot_token="YOUR_BOT_TOKEN",
bus=bus,
)
received_messages = []
def on_message(msg: ChannelMessage) -> None:
received_messages.append(msg)
print(f"Received from {msg.sender} on #{msg.channel}: {msg.content}")
channel.on_message(on_message)
channel.connect()
# List available channels
channels = channel.list_channels()
print(f"Available channels: {channels}")
# Send a message
if channels:
channel.send(channels[0], "Hello from OpenJarvis!")
# Wait for incoming messages
time.sleep(10)
channel.disconnect()
print(f"Total messages received: {len(received_messages)}")
WhatsAppBaileysChannel
WhatsAppBaileysChannel is registered as "whatsapp_baileys" in ChannelRegistry and provides bidirectional WhatsApp messaging using the Baileys protocol. It spawns a Node.js bridge subprocess that handles QR-code authentication, incoming message forwarding, and outbound message delivery.
!!! warning "Node.js 22+ required"
The Baileys bridge is a compiled Node.js application bundled inside the package. It is auto-installed to ~/.openjarvis/whatsapp_baileys_bridge/ on first connect() call. If node is not found on PATH, connect() logs an error and sets the channel to ChannelStatus.ERROR.
!!! note "WhatsApp account required" WhatsApp does not offer an official API for personal accounts. Baileys operates on the WhatsApp Web protocol. You must scan a QR code with your WhatsApp mobile app to authenticate on first use.
Connecting
from openjarvis.channels.whatsapp_baileys import WhatsAppBaileysChannel
channel = WhatsAppBaileysChannel(
assistant_name="Jarvis", # (1)!
assistant_has_own_number=False, # (2)!
)
channel.connect() # spawns the Node.js bridge subprocess
- Display name used in conversation context.
- Set
Trueif the assistant has a dedicated WhatsApp number and should not filter its own messages.
On first connection, the bridge will print a QR code to the terminal. Scan it with the WhatsApp app on your phone to authenticate. Authentication state is saved to ~/.openjarvis/whatsapp_baileys_bridge/auth/ and reused on subsequent connections.
Receiving Messages
from openjarvis.channels._stubs import ChannelMessage
from openjarvis.channels.whatsapp_baileys import WhatsAppBaileysChannel
channel = WhatsAppBaileysChannel()
def on_message(msg: ChannelMessage) -> None:
print(f"[{msg.sender}] {msg.content}")
# msg.conversation_id is the WhatsApp JID (e.g. "15551234567@s.whatsapp.net")
channel.on_message(on_message)
channel.connect()
# Background reader thread is running; your code continues here
Sending Messages
Messages are addressed by WhatsApp JID (Jabber ID) -- the canonical identifier for a WhatsApp contact or group.
# Individual contact JID format: <country-code><number>@s.whatsapp.net
# Group JID format: <group-id>@g.us
ok = channel.send(
"15551234567@s.whatsapp.net", # JID of the recipient
"Hello from OpenJarvis!",
)
if not ok:
print("Send failed -- check that the bridge is connected")
Disconnecting
channel.disconnect()
# Sends disconnect command to bridge, terminates subprocess, stops reader thread
Constructor Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
auth_dir |
str |
~/.openjarvis/whatsapp_baileys_bridge/auth |
Baileys auth state directory |
assistant_name |
str |
"Jarvis" |
Display name for the assistant |
assistant_has_own_number |
bool |
False |
Whether the assistant has a dedicated WhatsApp number |
bus |
EventBus |
None |
Event bus for publishing channel events |
Bridge Events
The Node.js bridge communicates with Python via JSON lines on stdio. Python interprets the following event types:
| Bridge event type | Effect |
|---|---|
status |
Updates ChannelStatus (connected / disconnected) |
qr |
Logs "QR code received -- scan to authenticate" |
message |
Dispatches to all registered on_message handlers |
error |
Logs the error and sets status to ChannelStatus.ERROR |
Event Bus Integration
When a bus is provided, WhatsAppBaileysChannel publishes the same events as other channels:
| Event | Published When | Data Keys |
|---|---|---|
CHANNEL_MESSAGE_RECEIVED |
An inbound WhatsApp message arrives | channel, sender, content, message_id |
CHANNEL_MESSAGE_SENT |
A message is successfully sent | channel, content, conversation_id |
Configuration
WhatsApp Baileys channel settings live in the [channel.whatsapp_baileys] subsection:
[channel.whatsapp_baileys]
auth_dir = "/home/user/.openjarvis/whatsapp_baileys_bridge/auth"
assistant_name = "Jarvis"
assistant_has_own_number = false
See Also
- Architecture: Channels -- listener loop internals and channel design
- API Reference: Channels -- full class and type signatures
- Getting Started: Configuration -- full config reference
- User Guide: Agents -- agent system documentation