refactor(channels): integrate channel runtime status management and enhance account status handling (#547)

This commit is contained in:
Haze
2026-03-17 11:29:17 +08:00
committed by GitHub
Unverified
parent 43fe7a4d1c
commit d4367d3265
10 changed files with 713 additions and 61 deletions

View File

@@ -24,6 +24,11 @@ import {
ensureQQBotPluginInstalled,
ensureWeComPluginInstalled,
} from '../../utils/plugin-install';
import {
computeChannelRuntimeStatus,
pickChannelRuntimeStatus,
type ChannelRuntimeAccountSnapshot,
} from '../../../src/lib/channel-status';
import { whatsAppLoginManager } from '../../utils/whatsapp-login';
import type { HostApiContext } from '../context';
import { parseJsonBody, sendJson } from '../route-utils';
@@ -123,6 +128,10 @@ interface GatewayChannelStatusPayload {
lastConnectedAt?: number | null;
lastInboundAt?: number | null;
lastOutboundAt?: number | null;
lastProbeAt?: number | null;
probe?: {
ok?: boolean;
} | null;
}>>;
channelDefaultAccountId?: Record<string, string>;
}
@@ -147,35 +156,6 @@ interface ChannelAccountsView {
accounts: ChannelAccountView[];
}
function computeAccountStatus(account: {
connected?: boolean;
linked?: boolean;
running?: boolean;
lastError?: string;
lastConnectedAt?: number | null;
lastInboundAt?: number | null;
lastOutboundAt?: number | null;
}): 'connected' | 'connecting' | 'disconnected' | 'error' {
const now = Date.now();
const recentMs = 10 * 60 * 1000;
const hasRecentActivity =
(typeof account.lastInboundAt === 'number' && now - account.lastInboundAt < recentMs)
|| (typeof account.lastOutboundAt === 'number' && now - account.lastOutboundAt < recentMs)
|| (typeof account.lastConnectedAt === 'number' && now - account.lastConnectedAt < recentMs);
if (account.connected === true || account.linked === true || hasRecentActivity) return 'connected';
if (account.running === true && !account.lastError) return 'connecting';
if (account.lastError) return 'error';
return 'disconnected';
}
function pickChannelStatus(accounts: ChannelAccountView[]): 'connected' | 'connecting' | 'disconnected' | 'error' {
if (accounts.some((account) => account.status === 'connected')) return 'connected';
if (accounts.some((account) => account.status === 'error')) return 'error';
if (accounts.some((account) => account.status === 'connecting')) return 'connecting';
return 'disconnected';
}
async function buildChannelAccountsView(ctx: HostApiContext): Promise<ChannelAccountsView[]> {
const [configuredChannels, configuredAccounts, openClawConfig, agentsSnapshot] = await Promise.all([
listConfiguredChannels(),
@@ -202,6 +182,8 @@ async function buildChannelAccountsView(ctx: HostApiContext): Promise<ChannelAcc
const channelAccountsFromConfig = configuredAccounts[channelType]?.accountIds ?? [];
const hasLocalConfig = configuredChannels.includes(channelType) || Boolean(configuredAccounts[channelType]);
const channelSection = openClawConfig.channels?.[channelType];
const channelSummary =
(gatewayStatus?.channels?.[channelType] as { error?: string; lastError?: string } | undefined) ?? undefined;
const fallbackDefault =
typeof channelSection?.defaultAccount === 'string' && channelSection.defaultAccount.trim()
? channelSection.defaultAccount
@@ -221,7 +203,8 @@ async function buildChannelAccountsView(ctx: HostApiContext): Promise<ChannelAcc
const accounts: ChannelAccountView[] = accountIds.map((accountId) => {
const runtime = runtimeAccounts.find((item) => item.accountId === accountId);
const status = computeAccountStatus(runtime ?? {});
const runtimeSnapshot: ChannelRuntimeAccountSnapshot = runtime ?? {};
const status = computeChannelRuntimeStatus(runtimeSnapshot);
return {
accountId,
name: runtime?.name || accountId,
@@ -243,7 +226,7 @@ async function buildChannelAccountsView(ctx: HostApiContext): Promise<ChannelAcc
channels.push({
channelType,
defaultAccountId,
status: pickChannelStatus(accounts),
status: pickChannelRuntimeStatus(runtimeAccounts, channelSummary),
accounts,
});
}

View File

@@ -195,7 +195,7 @@ export async function handleProviderRoutes(
const registryBaseUrl = getProviderConfig(providerType)?.baseUrl;
const resolvedBaseUrl = body.options?.baseUrl || provider?.baseUrl || registryBaseUrl;
const resolvedProtocol = body.options?.apiProtocol || provider?.apiProtocol;
sendJson(res, 200, await validateApiKeyWithProvider(providerType, body.apiKey, { baseUrl: resolvedBaseUrl, apiProtocol: resolvedProtocol as any }));
sendJson(res, 200, await validateApiKeyWithProvider(providerType, body.apiKey, { baseUrl: resolvedBaseUrl, apiProtocol: resolvedProtocol }));
} catch (error) {
sendJson(res, 500, { valid: false, error: String(error) });
}