Skip to main content

@tinyclaw/gateway

The outbound gateway routes proactive messages from the agent system (Pulse, Intercom, background tasks) to users across channels. Each channel plugin registers a sender with a userId prefix (e.g. discord, friend, web), and the gateway resolves the prefix to dispatch messages to the correct channel.

Installation

bun add @tinyclaw/gateway

Overview

Architecture:
Pulse/Intercom/Agent
  → gateway.send(userId, message)
    → resolve prefix from userId
      → channelSender.send(userId, message)
        → Discord DM / Web SSE / Friends push
Features:
  • Prefix-based channel routing
  • Broadcast to all registered channels
  • Graceful handling of unregistered channels
  • Delivery result tracking
  • Zero external dependencies

API Reference

createGateway()

Create an outbound gateway instance. Returns: OutboundGateway - A fully functional gateway ready for channel registration.
import { createGateway } from '@tinyclaw/gateway';

const gateway = createGateway();

// Register channels during boot
gateway.register('web', webSender);
gateway.register('discord', discordSender);

// Send a proactive message
await gateway.send('web:owner', {
  content: 'Your background task is complete!',
  priority: 'normal',
  source: 'background_task',
});

OutboundGateway Interface

register
(prefix: string, sender: ChannelSender) => void
Register a channel sender for a given userId prefix.Parameters:
  • prefix - Channel prefix (e.g. ‘discord’, ‘web’, ‘friend’)
  • sender - ChannelSender implementation
unregister
(prefix: string) => void
Unregister a channel sender (e.g. during shutdown).
send
(userId: string, message: OutboundMessage) => Promise<OutboundDeliveryResult>
Send a message to a specific user. Resolves the userId prefix and dispatches to the correct channel.UserId format: "prefix:identifier" (e.g. "discord:123456", "web:owner")
broadcast
(message: OutboundMessage) => Promise<OutboundDeliveryResult[]>
Broadcast a message to all registered channels. Each channel decides how to handle the broadcast.
getRegisteredChannels
() => string[]
Get all registered channel prefixes.

Type Definitions

OutboundMessage

interface OutboundMessage {
  content: string;           // Message content to deliver
  priority: OutboundPriority; // 'urgent' | 'normal' | 'low'
  source: OutboundSource;     // What triggered this message
  metadata?: Record<string, unknown>; // Optional metadata
}

OutboundDeliveryResult

interface OutboundDeliveryResult {
  success: boolean;    // Whether delivery succeeded
  channel: string;     // The channel prefix that handled delivery
  userId: string;      // The target userId
  error?: string;      // Error message if delivery failed
}

ChannelSender

interface ChannelSender {
  readonly name: string; // Channel name (for logging)
  send(userId: string, message: OutboundMessage): Promise<void>;
  broadcast?(message: OutboundMessage): Promise<void>; // Optional
}

Usage Examples

Basic Setup

import { createGateway } from '@tinyclaw/gateway';

const gateway = createGateway();

// Register channels
const webSender: ChannelSender = {
  name: 'Web',
  async send(userId, message) {
    // Send via SSE to web UI
    sseManager.send(userId, message);
  },
};

gateway.register('web', webSender);

Sending Messages

// Send to specific user
const result = await gateway.send('discord:123456', {
  content: 'Your background research is complete!',
  priority: 'normal',
  source: 'background_task',
  metadata: { taskId: 'abc123' },
});

if (result.success) {
  console.log(`Delivered via ${result.channel}`);
} else {
  console.error(`Delivery failed: ${result.error}`);
}

Broadcasting

// Broadcast to all channels
const results = await gateway.broadcast({
  content: 'System maintenance in 5 minutes',
  priority: 'urgent',
  source: 'system',
});

console.log(`Delivered to ${results.filter(r => r.success).length} channels`);

Error Handling

The gateway gracefully handles:
  • Invalid userId format - Returns error result
  • Unregistered channel - Returns error result with appropriate message
  • Channel sender errors - Catches errors and returns delivery failure

Integration with Other Systems

Pulse Jobs

import { createPulseScheduler } from '@tinyclaw/pulse';

const pulse = createPulseScheduler();

pulse.register({
  id: 'daily-summary',
  schedule: '24h',
  async handler() {
    await gateway.send('web:owner', {
      content: 'Here\'s your daily summary...',
      priority: 'low',
      source: 'pulse',
    });
  },
});

Intercom Events

import { createIntercom } from '@tinyclaw/intercom';

const intercom = createIntercom();

intercom.on('task:completed', async (event) => {
  await gateway.send(event.userId, {
    content: `Task completed: ${event.data.summary}`,
    priority: 'normal',
    source: 'background_task',
  });
});

Performance

  • Routing: O(1) prefix lookup via Map
  • Memory: Minimal - only stores channel registrations
  • Latency: <1ms for resolution, actual delivery depends on channel