Files
wifi-densepose/npm/packages/ruvbot/docs/adr/ADR-010-multi-channel.md
ruv d803bfe2b1 Squashed 'vendor/ruvector/' content from commit b64c2172
git-subtree-dir: vendor/ruvector
git-subtree-split: b64c21726f2bb37286d9ee36a7869fef60cc6900
2026-02-28 14:39:40 -05:00

7.3 KiB

ADR-010: Multi-Channel Integration

Status

Accepted (Partially Implemented)

Date

2026-01-27

Context

Clawdbot supports multiple messaging channels:

  • Slack, Discord, Telegram, Signal, WhatsApp, Line, iMessage
  • Web, CLI, API interfaces

RuvBot must match and exceed with:

  • All Clawdbot channels
  • Multi-tenant channel isolation
  • Unified message handling

Decision

Channel Architecture

┌─────────────────────────────────────────────────────────────────┐
│                    RuvBot Channel Layer                          │
├─────────────────────────────────────────────────────────────────┤
│  Channel Adapters                                                │
│    ├─ SlackAdapter       : @slack/bolt        [IMPLEMENTED]     │
│    ├─ DiscordAdapter     : discord.js         [IMPLEMENTED]     │
│    ├─ TelegramAdapter    : telegraf           [IMPLEMENTED]     │
│    ├─ SignalAdapter      : signal-client      [PLANNED]         │
│    ├─ WhatsAppAdapter    : baileys            [PLANNED]         │
│    ├─ LineAdapter        : @line/bot-sdk      [PLANNED]         │
│    ├─ WebAdapter         : WebSocket + REST   [PLANNED]         │
│    └─ CLIAdapter         : readline + terminal [PLANNED]        │
├─────────────────────────────────────────────────────────────────┤
│  Message Normalization                                           │
│    └─ Unified Message format                                    │
│    └─ Attachment handling                                       │
│    └─ Thread/reply context                                      │
├─────────────────────────────────────────────────────────────────┤
│  Multi-Tenant Isolation                                          │
│    └─ Channel credentials per tenant                            │
│    └─ Namespace isolation                                       │
│    └─ Rate limiting per tenant                                  │
└─────────────────────────────────────────────────────────────────┘

Implementation

Located in /npm/packages/ruvbot/src/channels/:

  • ChannelRegistry.ts - Central registry and routing
  • adapters/BaseAdapter.ts - Abstract base class
  • adapters/SlackAdapter.ts - Slack integration
  • adapters/DiscordAdapter.ts - Discord integration
  • adapters/TelegramAdapter.ts - Telegram integration

Unified Message Interface

interface UnifiedMessage {
  id: string;
  channelId: string;
  channelType: ChannelType;
  tenantId: string;
  userId: string;
  username?: string;
  content: string;
  attachments?: Attachment[];
  threadId?: string;
  replyTo?: string;
  timestamp: Date;
  metadata: Record<string, unknown>;
}

interface Attachment {
  id: string;
  type: 'image' | 'file' | 'audio' | 'video' | 'link';
  url?: string;
  data?: Buffer;
  mimeType?: string;
  filename?: string;
  size?: number;
}

type ChannelType =
  | 'slack' | 'discord' | 'telegram'
  | 'signal' | 'whatsapp' | 'line'
  | 'imessage' | 'web' | 'api' | 'cli';

BaseAdapter Abstract Class

abstract class BaseAdapter {
  type: ChannelType;
  tenantId: string;
  enabled: boolean;

  // Lifecycle
  abstract connect(): Promise<void>;
  abstract disconnect(): Promise<void>;

  // Messaging
  abstract send(channelId: string, content: string, options?: SendOptions): Promise<string>;
  abstract reply(message: UnifiedMessage, content: string, options?: SendOptions): Promise<string>;

  // Event handling
  onMessage(handler: MessageHandler): void;
  offMessage(handler: MessageHandler): void;
  getStatus(): AdapterStatus;
}

Channel Registry

interface ChannelRegistry {
  // Registration
  register(adapter: BaseAdapter): void;
  unregister(type: ChannelType, tenantId: string): boolean;

  // Lookup
  get(type: ChannelType, tenantId: string): BaseAdapter | undefined;
  getByType(type: ChannelType): BaseAdapter[];
  getByTenant(tenantId: string): BaseAdapter[];
  getAll(): BaseAdapter[];

  // Lifecycle
  start(): Promise<void>;
  stop(): Promise<void>;

  // Messaging
  onMessage(handler: MessageHandler): void;
  offMessage(handler: MessageHandler): void;
  broadcast(message: string, channelIds: string[], filter?: ChannelFilter): Promise<Map<string, string>>;

  // Statistics
  getStats(): RegistryStats;
}

interface ChannelRegistryConfig {
  defaultRateLimit?: {
    requests: number;
    windowMs: number;
  };
}

Adapter Configuration

interface AdapterConfig {
  type: ChannelType;
  tenantId: string;
  credentials: ChannelCredentials;
  enabled?: boolean;
  rateLimit?: {
    requests: number;
    windowMs: number;
  };
}

interface ChannelCredentials {
  token?: string;
  apiKey?: string;
  webhookUrl?: string;
  clientId?: string;
  clientSecret?: string;
  botId?: string;
  [key: string]: unknown;
}

Usage Example

import { ChannelRegistry, SlackAdapter, DiscordAdapter } from './channels';

// Create registry with rate limiting
const registry = new ChannelRegistry({
  defaultRateLimit: { requests: 100, windowMs: 60000 }
});

// Register adapters
registry.register(new SlackAdapter({
  type: 'slack',
  tenantId: 'tenant-1',
  credentials: { token: process.env.SLACK_TOKEN }
}));

registry.register(new DiscordAdapter({
  type: 'discord',
  tenantId: 'tenant-1',
  credentials: { token: process.env.DISCORD_TOKEN }
}));

// Handle messages
registry.onMessage(async (message) => {
  console.log(`[${message.channelType}] ${message.userId}: ${message.content}`);
});

// Start all adapters
await registry.start();

Implementation Status

Adapter Status Library Notes
Slack Implemented @slack/bolt Full support
Discord Implemented discord.js Full support
Telegram Implemented telegraf Full support
Signal Planned signal-client Requires native deps
WhatsApp Planned baileys Unofficial API
Line Planned @line/bot-sdk -
Web Planned WebSocket Custom implementation
CLI Planned readline For testing

Consequences

Positive

  • Unified message handling across all channels
  • Multi-tenant channel isolation with per-tenant indexing
  • Easy to add new channels via BaseAdapter
  • Built-in rate limiting per adapter

Negative

  • Complexity of maintaining multiple integrations
  • Different channel capabilities (some don't support threads)
  • Only 3 of 8+ channels currently implemented

RuvBot Advantages over Clawdbot

  • Multi-tenant channel credentials with isolation
  • Channel-specific rate limiting
  • Cross-channel message routing via broadcast
  • Adapter status tracking and statistics