feat(telegram): harden channel reliability with retries and error diagnostics

This commit is contained in:
William Valentin
2026-02-18 13:12:11 -08:00
parent 0ccbe65d3f
commit 42ae4a75df
11 changed files with 323 additions and 44 deletions
+23
View File
@@ -116,6 +116,29 @@ describe('discoverServices', () => {
expect(services.find(s => s.name === 'telegram')?.status).toBe('connected');
});
it('surfaces adapter error details when channel is in error state', () => {
const cfg = makeBaseConfig();
withMutableConfig(cfg).telegram = { bot_token: 'x', allowed_chat_ids: [123], require_mention: false };
const reg = new ChannelRegistry();
reg.register({
name: 'telegram',
status: 'error',
lastError: 'Telegram polling error: 429 Too Many Requests',
lastErrorAt: 1708080000000,
connect: async () => {},
disconnect: async () => {},
send: async () => {},
onMessage: () => {},
});
const services = discoverServices(cfg, reg);
const telegram = services.find(s => s.name === 'telegram');
expect(telegram?.status).toBe('error');
expect(telegram?.error).toContain('429');
expect(telegram?.metadata).toMatchObject({ lastErrorAt: 1708080000000 });
});
it('marks enabled automation subsystems as configured and carries item counts', () => {
const cfg = makeBaseConfig();
cfg.automation.cron = [
+8 -1
View File
@@ -73,7 +73,14 @@ export function discoverServices(
}
if (registered) {
services.push({ name, type: 'channel', status: registered.status as ServiceStatus, description });
services.push({
name,
type: 'channel',
status: registered.status as ServiceStatus,
description,
...(registered.lastError ? { error: registered.lastError } : {}),
...(registered.lastErrorAt ? { metadata: { lastErrorAt: registered.lastErrorAt } } : {}),
});
continue;
}
+1 -1
View File
@@ -73,7 +73,7 @@ export interface SystemHandlerDeps {
getConnectionCount: () => number;
/** Optional callback to trigger a graceful restart. If not provided, system.restart returns an error. */
restart?: () => Promise<void>;
getChannels?: () => Array<{ name: string; status: string }>;
getChannels?: () => Array<{ name: string; status: string; error?: string; lastErrorAt?: number }>;
getUsage?: () => { totalSessions: number; activeConnections: number };
/** Optional callback to retrieve per-session token usage data. */
getTokenUsage?: () => TokenUsageEntry[];
+6 -1
View File
@@ -213,7 +213,12 @@ export class GatewayServer {
getSessionAnalytics: ({ days, topLimit } = {}) => this.config.sessionManager.getSessionAnalytics({ days, topLimit }),
restart: this.config.restart,
getChannels: channelRegistry
? () => channelRegistry.list().map(a => ({ name: a.name, status: a.status }))
? () => channelRegistry.list().map(a => ({
name: a.name,
status: a.status,
...(a.lastError ? { error: a.lastError } : {}),
...(a.lastErrorAt ? { lastErrorAt: a.lastErrorAt } : {}),
}))
: undefined,
getServices: runtimeConfig && channelRegistry
? () => discoverServices(runtimeConfig, channelRegistry)
+1
View File
@@ -818,6 +818,7 @@ function updateServices(servicesData) {
</div>
<span class="text-xs uppercase ${statusColor}">${escapeHtml(svc.status)}</span>
<span class="text-xs text-zinc-500">${escapeHtml(svc.description)}</span>
${svc.error ? `<span class="text-xs text-red-400">Error: ${escapeHtml(String(svc.error))}</span>` : ''}
</div>`;
}).join('');
}