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
+21 -1
View File
@@ -1,5 +1,5 @@
import type { GatewayRequest, OutboundMessage } from '../protocol.js';
import { makeResponse } from '../protocol.js';
import { makeResponse, makeError, ErrorCode } from '../protocol.js';
export interface SystemHandlerDeps {
startTime: number;
@@ -7,6 +7,8 @@ export interface SystemHandlerDeps {
getSessionCount: () => number;
getToolCount: () => number;
getConnectionCount: () => number;
/** Optional callback to trigger a graceful restart. If not provided, system.restart returns an error. */
restart?: () => Promise<void>;
}
export function createSystemHandlers(deps: SystemHandlerDeps) {
@@ -21,5 +23,23 @@ export function createSystemHandlers(deps: SystemHandlerDeps) {
connections: deps.getConnectionCount(),
});
},
'system.restart': async (request: GatewayRequest): Promise<OutboundMessage> => {
if (!deps.restart) {
return makeError(request.id, ErrorCode.InternalError, 'Restart not available in this environment');
}
// Send response before initiating restart (client receives confirmation)
const response = makeResponse(request.id, { restarting: true });
// Schedule restart after response is sent (next tick)
queueMicrotask(() => {
deps.restart!().catch((err) => {
console.error('Restart failed:', err);
});
});
return response;
},
};
}