export type ChannelConnectionStatus = 'connected' | 'connecting' | 'disconnected' | 'error'; export interface ChannelRuntimeAccountSnapshot { connected?: boolean; linked?: boolean; running?: boolean; lastError?: string | null; lastConnectedAt?: number | null; lastInboundAt?: number | null; lastOutboundAt?: number | null; lastProbeAt?: number | null; probe?: { ok?: boolean | null; } | null; } export interface ChannelRuntimeSummarySnapshot { error?: string | null; lastError?: string | null; } const RECENT_ACTIVITY_MS = 10 * 60 * 1000; function hasNonEmptyError(value: string | null | undefined): boolean { return typeof value === 'string' && value.trim().length > 0; } export function hasRecentChannelActivity( account: Pick, now = Date.now(), recentMs = RECENT_ACTIVITY_MS, ): boolean { return ( (typeof account.lastInboundAt === 'number' && now - account.lastInboundAt < recentMs) || (typeof account.lastOutboundAt === 'number' && now - account.lastOutboundAt < recentMs) || (typeof account.lastConnectedAt === 'number' && now - account.lastConnectedAt < recentMs) ); } export function hasSuccessfulChannelProbe( account: Pick, ): boolean { return account.probe?.ok === true; } export function hasChannelRuntimeError( account: Pick, ): boolean { return hasNonEmptyError(account.lastError); } export function hasSummaryRuntimeError( summary: ChannelRuntimeSummarySnapshot | undefined, ): boolean { if (!summary) return false; return hasNonEmptyError(summary.error) || hasNonEmptyError(summary.lastError); } export function isChannelRuntimeConnected( account: ChannelRuntimeAccountSnapshot, ): boolean { if (account.connected === true || account.linked === true) { return true; } if (hasRecentChannelActivity(account) || hasSuccessfulChannelProbe(account)) { return true; } // OpenClaw integrations such as Feishu/WeCom may stay "running" without ever // setting a durable connected=true flag. Treat healthy running as connected. return account.running === true && !hasChannelRuntimeError(account); } export function computeChannelRuntimeStatus( account: ChannelRuntimeAccountSnapshot, ): ChannelConnectionStatus { if (isChannelRuntimeConnected(account)) return 'connected'; if (hasChannelRuntimeError(account)) return 'error'; if (account.running === true) return 'connecting'; return 'disconnected'; } export function pickChannelRuntimeStatus( accounts: ChannelRuntimeAccountSnapshot[], summary?: ChannelRuntimeSummarySnapshot, ): ChannelConnectionStatus { if (accounts.some((account) => isChannelRuntimeConnected(account))) { return 'connected'; } if (accounts.some((account) => hasChannelRuntimeError(account)) || hasSummaryRuntimeError(summary)) { return 'error'; } if (accounts.some((account) => account.running === true)) { return 'connecting'; } return 'disconnected'; }