Files
flynn/src/channels/webchat/adapter.ts
T
William Valentin aa95f2132c 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)
2026-02-05 20:00:36 -08:00

82 lines
2.5 KiB
TypeScript

/**
* WebChat channel adapter.
*
* Thin wrapper around the existing GatewayServer. The gateway already
* handles WebSocket connections, sessions, and agent routing. This adapter
* exposes the gateway as a ChannelAdapter so the ChannelRegistry has a
* uniform interface for all channels.
*/
import type { GatewayServer } from '../../gateway/index.js';
import type {
InboundMessage,
OutboundMessage,
ChannelAdapter,
ChannelStatus,
} from '../types.js';
/** Configuration for the WebChat adapter. */
export interface WebChatAdapterConfig {
gateway: GatewayServer;
}
/**
* WebChatAdapter wraps a GatewayServer to satisfy the ChannelAdapter interface.
*
* The gateway's lifecycle (start/stop) is managed by the daemon, not by
* this adapter. Connect/disconnect only track the adapter's logical status.
*/
export class WebChatAdapter implements ChannelAdapter {
readonly name = 'webchat';
private _status: ChannelStatus = 'disconnected';
private gateway: GatewayServer;
private messageHandler?: (msg: InboundMessage) => void;
get status(): ChannelStatus {
return this._status;
}
constructor(config: WebChatAdapterConfig) {
this.gateway = config.gateway;
}
/** Register the inbound message handler. Called by registry before connect(). */
onMessage(handler: (msg: InboundMessage) => void): void {
this.messageHandler = handler;
}
/**
* Connect the adapter. The gateway's lifecycle is managed by the daemon,
* so this just marks the adapter as connected. The gateway should already
* be started (or will be started) by the daemon.
*/
async connect(): Promise<void> {
this._status = 'connected';
}
/**
* Disconnect the adapter. Does NOT stop the gateway — that's managed
* by the daemon lifecycle. Just marks this adapter as disconnected.
*/
async disconnect(): Promise<void> {
this._status = 'disconnected';
}
/**
* Send a message to a WebSocket peer. This is a no-op placeholder —
* the gateway handles outbound messages directly via its own WS connections.
* This method exists to satisfy the ChannelAdapter interface.
*/
async send(_peerId: string, _message: OutboundMessage): Promise<void> {
// Gateway handles outbound via its own WS event system (GatewayEvent).
// This adapter doesn't need to implement send() because the gateway's
// agent.send handler already streams responses back to the WS client.
}
/** Get the underlying gateway server. */
getGateway(): GatewayServer {
return this.gateway;
}
}