feat: add channel adapter abstraction with Telegram and WebChat adapters

Implement Phase 3 channel adapters that decouple message sources from
the agent via a uniform ChannelAdapter interface and ChannelRegistry.

- Add ChannelAdapter/InboundMessage/OutboundMessage types
- Add ChannelRegistry for adapter lifecycle and message routing
- Add TelegramAdapter (grammy bot, auth middleware, confirmations, chunking)
- Add WebChatAdapter (thin shim over GatewayServer)
- Refactor daemon to use ChannelRegistry with per-channel-per-user agents
- Add config.get/config.patch gateway handlers (Phase 2 loose end)
- Add system.restart gateway handler (Phase 2 loose end)
- Add implementation plans and design docs

Tests: 225 passing (33 new channel adapter + gateway handler tests)
This commit is contained in:
William Valentin
2026-02-05 20:00:36 -08:00
parent 282a15d2b9
commit aa95f2132c
19 changed files with 4123 additions and 37 deletions
+75
View File
@@ -0,0 +1,75 @@
/**
* Channel adapter type definitions.
*
* Pure type definitions for the channel abstraction layer.
* Each channel adapter (Telegram, webchat, etc.) implements
* the ChannelAdapter interface to provide a uniform messaging API.
*/
/** Inbound message received from a channel platform. */
export interface InboundMessage {
/** Platform message ID. */
id: string;
/** Adapter name: "telegram", "webchat", etc. */
channel: string;
/** Platform user ID. */
senderId: string;
/** Display name (optional). */
senderName?: string;
/** Message text. */
text: string;
/** ID of message being replied to. */
replyTo?: string;
/** Unix ms. */
timestamp: number;
/** Platform-specific extras. */
metadata?: Record<string, unknown>;
}
/** Outbound message to send via a channel adapter. */
export interface OutboundMessage {
/** Response text (markdown). */
text: string;
/** Original message ID to reply to. */
replyTo?: string;
/** Platform-specific extras. */
metadata?: Record<string, unknown>;
}
/** Tool execution status event for streaming feedback. */
export interface ToolStatusEvent {
type: 'start' | 'end';
tool: string;
args?: unknown;
result?: { success: boolean; output: string; error?: string };
}
/** Connection status of a channel adapter. */
export type ChannelStatus = 'disconnected' | 'connecting' | 'connected' | 'error';
/** Uniform interface that every channel adapter must implement. */
export interface ChannelAdapter {
/** Unique channel name (e.g. "telegram", "webchat"). */
readonly name: string;
/** Current connection status. */
readonly status: ChannelStatus;
/** Start the adapter (connect to platform, begin listening). */
connect(): Promise<void>;
/** Stop the adapter (disconnect, clean up). */
disconnect(): Promise<void>;
/** Send a message to a specific peer on this channel. */
send(peerId: string, message: OutboundMessage): Promise<void>;
/** Register the inbound message handler. Called by registry before connect(). */
onMessage(handler: (msg: InboundMessage) => void): void;
}
/** Callback type for the registry's message handler. */
export type MessageHandler = (
msg: InboundMessage,
reply: (response: OutboundMessage) => Promise<void>,
) => Promise<void>;