Adding a Channel
ClawDesk's channel system uses a layered trait architecture. To add a new channel (e.g., Microsoft Teams, Matrix), you implement the core Channel trait and optionally adopt Layer 1 capability traits.
Architecture Overview
Layer 0 (Required): Channel — Send/receive messages
Layer 1 (Optional): Threaded — Thread support
Streaming — Real-time streaming
Reactions — Emoji reactions
GroupManagement — Group/room management
Directory — User directory lookup
Pairing — DM pairing
Step 1: Create the Channel Module
Add a new module in crates/clawdesk-channels/src/:
crates/clawdesk-channels/src/
├── lib.rs
├── telegram.rs
├── discord.rs
├── slack.rs
└── teams.rs ← New file
Step 2: Implement the Channel Trait
The core Channel trait is defined in clawdesk-channel:
// crates/clawdesk-channels/src/teams.rs
use async_trait::async_trait;
use clawdesk_channel::{Channel, ChannelError};
use clawdesk_types::{ChannelId, Message, Session};
pub struct TeamsChannel {
client: TeamsClient,
config: TeamsConfig,
}
#[async_trait]
impl Channel for TeamsChannel {
fn id(&self) -> &ChannelId {
&self.config.channel_id
}
fn name(&self) -> &str {
"microsoft-teams"
}
async fn send(&self, session: &Session, message: &Message) -> Result<(), ChannelError> {
self.client
.post_message(&session.external_id, &message.content)
.await
.map_err(|e| ChannelError::ConnectionFailed(e.to_string()))
}
async fn receive(&self) -> Result<Option<Message>, ChannelError> {
// Poll or webhook-based receive logic
self.client
.poll_messages()
.await
.map_err(|e| ChannelError::ConnectionFailed(e.to_string()))
}
}
Step 3: Add Optional Layer 1 Traits
Implement additional capabilities as needed:
use clawdesk_channel::{Threaded, Streaming};
#[async_trait]
impl Threaded for TeamsChannel {
async fn reply_in_thread(
&self,
thread_id: &str,
message: &Message,
) -> Result<(), ChannelError> {
self.client
.reply_to_thread(thread_id, &message.content)
.await
.map_err(|e| ChannelError::ConnectionFailed(e.to_string()))
}
async fn create_thread(
&self,
session: &Session,
title: &str,
) -> Result<String, ChannelError> {
self.client
.create_thread(&session.external_id, title)
.await
.map_err(|e| ChannelError::ConnectionFailed(e.to_string()))
}
}
Step 4: Add Configuration
Define the channel's configuration struct:
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TeamsConfig {
pub channel_id: ChannelId,
pub tenant_id: String,
pub client_id: String,
pub client_secret: String,
pub webhook_url: Option<String>,
}
Add the configuration to the global config TOML:
[[channels]]
type = "microsoft-teams"
tenant_id = "your-tenant-id"
client_id = "your-client-id"
client_secret = "${TEAMS_CLIENT_SECRET}"
Step 5: Register the Channel
Add the channel to the registry in crates/clawdesk-channels/src/lib.rs:
mod teams;
pub use teams::{TeamsChannel, TeamsConfig};
pub fn register_channels(registry: &mut ChannelRegistry, config: &AppConfig) {
// ... existing channels ...
if let Some(teams_cfg) = &config.channels.teams {
let channel = TeamsChannel::new(teams_cfg.clone());
registry.register(Box::new(channel));
}
}
Step 6: Write Tests
Unit Tests
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn teams_channel_has_correct_name() {
let config = TeamsConfig::test_default();
let channel = TeamsChannel::new(config);
assert_eq!(channel.name(), "microsoft-teams");
}
}
Integration Tests
#[tokio::test]
async fn teams_send_and_receive_roundtrip() {
let channel = TeamsChannel::new(TeamsConfig::test_default());
let session = Session::new("teams-test");
let message = Message::user("Hello Teams!");
let result = channel.send(&session, &message).await;
assert!(result.is_ok());
}
Step 7: Update Documentation
- Add the channel to docs/usage/channels.md
- Add configuration reference to docs/getting-started/configuration.md
- Update the channel trait matrix in architecture docs
Checklist
- Implement
Channeltrait (Layer 0) - Implement relevant Layer 1 traits
- Add
{Channel}Configwith serde support - Register in
ChannelRegistry - Add unit tests
- Add integration tests
- Update documentation
- Add environment variable entries
- Handle rate limiting via
clawdesk-channel::RateLimiter - Implement health checks via
clawdesk-channel::HealthCheck
info
See the existing Telegram and Discord implementations for complete reference examples.