v2 phase 4: channel adapter interface, registry, and host wiring

ChannelAdapter interface with setup/deliver/teardown/setTyping lifecycle.
Self-registration pattern via channel-registry. Host wiring in index-v2
bridges inbound messages to routeInbound and outbound delivery to adapters.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
gavrielc
2026-04-09 00:10:46 +03:00
parent d35386a46e
commit 7201fe5032
6 changed files with 483 additions and 9 deletions

View File

@@ -0,0 +1,72 @@
/**
* v2 Channel adapter registry.
*
* Channels self-register on import. The host calls initChannelAdapters() at startup
* to instantiate and set up all registered adapters.
*/
import type { ChannelAdapter, ChannelRegistration, ChannelSetup } from './adapter.js';
import { log } from '../log.js';
const registry = new Map<string, ChannelRegistration>();
const activeAdapters = new Map<string, ChannelAdapter>();
/** Register a channel adapter factory. Called by channel modules on import. */
export function registerChannelAdapter(name: string, registration: ChannelRegistration): void {
registry.set(name, registration);
}
/** Get a live adapter by channel type. */
export function getChannelAdapter(channelType: string): ChannelAdapter | undefined {
return activeAdapters.get(channelType);
}
/** Get all active adapters. */
export function getActiveAdapters(): ChannelAdapter[] {
return [...activeAdapters.values()];
}
/** Get all registered channel names. */
export function getRegisteredChannelNames(): string[] {
return [...registry.keys()];
}
/** Get container config for a channel (used by container-runner for additional mounts/env). */
export function getChannelContainerConfig(name: string): ChannelRegistration['containerConfig'] {
return registry.get(name)?.containerConfig;
}
/**
* Instantiate and set up all registered channel adapters.
* Skips adapters that return null (missing credentials).
*/
export async function initChannelAdapters(setupFn: (adapter: ChannelAdapter) => ChannelSetup): Promise<void> {
for (const [name, registration] of registry) {
try {
const adapter = registration.factory();
if (!adapter) {
log.warn('Channel credentials missing, skipping', { channel: name });
continue;
}
const setup = setupFn(adapter);
await adapter.setup(setup);
activeAdapters.set(adapter.channelType, adapter);
log.info('Channel adapter started', { channel: name, type: adapter.channelType });
} catch (err) {
log.error('Failed to start channel adapter', { channel: name, err });
}
}
}
/** Tear down all active adapters. */
export async function teardownChannelAdapters(): Promise<void> {
for (const [name, adapter] of activeAdapters) {
try {
await adapter.teardown();
log.info('Channel adapter stopped', { channel: name });
} catch (err) {
log.error('Failed to stop channel adapter', { channel: name, err });
}
}
activeAdapters.clear();
}