Skip to content

[SPRINT-3] Add multi-platform messaging gateway (Telegram, Discord, Slack, WhatsApp) #188

@redsand

Description

@redsand

Priority: Medium

Hermes's gateway is what makes it a 24/7 agent — it pushes scheduled results, cron outputs, and subagent completions to wherever you are. Our Discord integration exists but is chat-only. A proper gateway enables the agent to deliver proactive messages, not just respond to incoming ones.

Depends on: #185 (cron engine needs delivery targets), #180 (MEMORY.md for session continuity across platforms)

Blocks: None

Acceptance Criteria

  • src/integrations/gateway/gateway-engine.ts created with GatewayEngine class
  • Platform adapters for: Telegram, Discord (enhance existing), Slack, WhatsApp (via Signal bridge)
  • Each adapter implements send(userId: string, message: string, options?: DeliveryOptions) and receive(): AsyncIterable<IncomingMessage>
  • deliver tool registered for sending messages to platforms
  • Session continuity: conversation started on Telegram can continue on Discord
  • Gateway runs as a background service, reconnects on disconnect
  • [SILENT] trick: messages containing [SILENT] are suppressed (no delivery)
  • Delivery receipts tracked in data/gateway/delivery-log.json
  • Unit tests in tests/unit/integrations/gateway/

Coding Prompt

Files to modify: src/integrations/gateway/gateway-engine.ts (new), src/integrations/gateway/platform-adapter.ts (new), src/integrations/gateway/telegram-adapter.ts (new), src/integrations/gateway/slack-adapter.ts (new), src/integrations/gateway/whatsapp-adapter.ts (new), src/integrations/discord/ (enhance existing), src/agent/tool-registry.ts (register deliver tool), src/agent/tool-dispatcher.ts (handle deliver tool), src/server.ts (start gateway on boot), src/config/env.ts (GATEWAY_ENABLED, platform tokens)

Current behavior: The Discord integration in src/integrations/discord/ handles incoming messages and routes them to the agent. There is no outbound delivery mechanism — the agent can only respond in the same channel where the message came from. There is no Telegram, Slack, or WhatsApp integration. The Signal integration in src/integrations/signal/ exists but is receive-only.

Required change:

  1. Create src/integrations/gateway/platform-adapter.ts:

    interface PlatformAdapter {
      platform: string;
      send(userId: string, message: string, options?: DeliveryOptions): Promise<DeliveryResult>;
      receive(): AsyncIterable<IncomingMessage>;
      start(): Promise<void>;
      stop(): Promise<void>;
      isConnected(): boolean;
    }
    
    interface DeliveryOptions {
      parseMode?: 'markdown' | 'html' | 'plain';
      silent?: boolean;
      replyToMessageId?: string;
    }
    
    interface DeliveryResult {
      success: boolean;
      messageId?: string;
      platform: string;
      timestamp: string;
      suppressed: boolean; // true if [SILENT] was detected
    }
    
    interface IncomingMessage {
      platform: string;
      userId: string;
      channelId: string;
      content: string;
      timestamp: string;
      metadata?: Record<string, unknown>;
    }
  2. Create src/integrations/gateway/gateway-engine.ts:

    • GatewayEngine class that manages all platform adapters
    • start(): Initialize all configured adapters, begin listening
    • stop(): Graceful shutdown, wait for in-flight messages
    • send(platform, userId, message, options?): Route message to correct adapter
    • broadcast(message, platforms?): Send to all platforms for a user
    • Session mapping: data/gateway/sessions.json maps {userId, platform}sessionId for cross-platform continuity
    • [SILENT] detection: if message contains [SILENT], return { suppressed: true } without sending
    • Delivery logging: append to data/gateway/delivery-log.json
  3. Create src/integrations/gateway/telegram-adapter.ts:

    • Use node-telegram-bot-api (add as dependency)
    • Polling mode (no webhook required for MVP)
    • Handle /start command to register user and link session
    • Support markdown formatting
    • Reconnect on disconnect with exponential backoff
  4. Create src/integrations/gateway/slack-adapter.ts:

    • Use @slack/web-api and @slack/socket-mode (add as dependencies)
    • Socket Mode for real-time messages (no public URL needed)
    • Support thread replies and channel messages
    • Handle app mentions and DMs
  5. Enhance existing src/integrations/discord/:

    • Add outbound delivery method (currently receive-only)
    • Support DMs and channel messages
    • Reuse existing Discord.js client
  6. Create src/integrations/gateway/whatsapp-adapter.ts:

    • Bridge through Signal (reuse existing src/integrations/signal/)
    • WhatsApp messages → Signal → gateway → agent
    • Document the Signal-WhatsApp bridge setup in README
  7. Register deliver tool:

    • platform: "telegram" | "discord" | "slack" | "whatsapp"
    • user_id: string (platform-specific user ID)
    • message: string
    • silent: boolean (optional, default false)

Code patterns to follow:

  • Follow the existing adapter pattern from src/integrations/discord/
  • Use environment variables for platform tokens (consistent with src/config/env.ts)
  • AsyncIterable for message streams (follow Node.js AsyncGenerator pattern)
  • Delivery logging follows the JSON file pattern from ConversationManager
  • Error handling: each adapter catches its own errors and reports via DeliveryResult, never crashes the gateway

Files to add/update tests in: tests/unit/integrations/gateway/gateway-engine.test.ts, tests/unit/integrations/gateway/platform-adapter.test.ts

Reasoning: The gateway is what makes an agent proactive rather than reactive. Without it, the agent can only respond when you type. With it, scheduled tasks, reflections, and subagent results can reach you wherever you are. Cross-platform session continuity means you can start on Telegram and pick up on Discord without losing context.

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions