From 8fe449c8834bc3e2f25ccfa3db074febb188eb8a Mon Sep 17 00:00:00 2001 From: Shakker Date: Sun, 26 Apr 2026 08:13:46 +0100 Subject: [PATCH] fix: avoid channel runtime in format summaries --- src/commands/agents.commands.list.ts | 7 +- src/commands/agents.providers.ts | 95 +++++++++++++++++++--------- src/commands/health-format.ts | 4 +- src/commands/message-format.ts | 4 +- 4 files changed, 73 insertions(+), 37 deletions(-) diff --git a/src/commands/agents.commands.list.ts b/src/commands/agents.commands.list.ts index edef5576f23..7ab660c2b20 100644 --- a/src/commands/agents.commands.list.ts +++ b/src/commands/agents.commands.list.ts @@ -11,6 +11,7 @@ import type { AgentSummary } from "./agents.config.js"; import { buildAgentSummaries } from "./agents.config.js"; import { buildProviderStatusIndex, + buildProviderSummaryMetadataIndex, listProvidersForAgent, summarizeBindings, } from "./agents.providers.js"; @@ -107,11 +108,12 @@ export async function agentsListCommand( // catalog entry, this keeps `agents list --json` on the config-only path. const includeProviderDetails = !opts.json || opts.bindings === true; const providerStatus = includeProviderDetails ? await buildProviderStatusIndex(cfg) : null; + const providerMetadata = includeProviderDetails ? buildProviderSummaryMetadataIndex(cfg) : null; for (const summary of summaries) { const bindings = bindingMap.get(summary.id) ?? []; - if (includeProviderDetails && providerStatus) { - const routes = summarizeBindings(cfg, bindings); + if (includeProviderDetails && providerStatus && providerMetadata) { + const routes = summarizeBindings(cfg, bindings, providerMetadata); if (routes.length > 0) { summary.routes = routes; } else if (summary.isDefault) { @@ -123,6 +125,7 @@ export async function agentsListCommand( cfg, bindings, providerStatus, + providerMetadata, }); if (providerLines.length > 0) { summary.providers = providerLines; diff --git a/src/commands/agents.providers.ts b/src/commands/agents.providers.ts index b0a9bd28cec..34718f0fda9 100644 --- a/src/commands/agents.providers.ts +++ b/src/commands/agents.providers.ts @@ -1,6 +1,6 @@ import { isChannelVisibleInConfiguredLists } from "../channels/plugins/exposure.js"; import { resolveChannelDefaultAccountId } from "../channels/plugins/helpers.js"; -import { getChannelPlugin, normalizeChannelId } from "../channels/plugins/index.js"; +import { normalizeChannelId } from "../channels/plugins/index.js"; import { listReadOnlyChannelPluginsForConfig } from "../channels/plugins/read-only.js"; import type { ChannelPlugin } from "../channels/plugins/types.plugin.js"; import type { ChannelId } from "../channels/plugins/types.public.js"; @@ -19,10 +19,37 @@ type ProviderAccountStatus = { visibleInConfiguredLists?: boolean; }; +export type ProviderSummaryMetadata = { + label: string; + defaultAccountId: string; + visibleInConfiguredLists: boolean; +}; + function providerAccountKey(provider: ChannelId, accountId?: string) { return `${provider}:${accountId ?? DEFAULT_ACCOUNT_ID}`; } +export function buildProviderSummaryMetadataIndex( + cfg: OpenClawConfig, +): Map { + return new Map( + listReadOnlyChannelPluginsForConfig(cfg, { + includeSetupRuntimeFallback: false, + }).map((plugin) => [ + plugin.id, + { + label: plugin.meta.label, + defaultAccountId: resolveChannelDefaultAccountId({ + plugin, + cfg, + accountIds: plugin.config.listAccountIds(cfg), + }), + visibleInConfiguredLists: isChannelVisibleInConfiguredLists(plugin.meta), + }, + ]), + ); +} + function isUnresolvedSecretRefResolutionError(error: unknown): boolean { return ( error instanceof Error && @@ -37,8 +64,7 @@ function formatChannelAccountLabel(params: { accountId: string; name?: string; }): string { - const label = - params.providerLabel ?? getChannelPlugin(params.provider)?.meta.label ?? params.provider; + const label = params.providerLabel ?? params.provider; const account = params.name?.trim() ? `${params.accountId} (${params.name.trim()})` : params.accountId; @@ -134,31 +160,26 @@ export async function buildProviderStatusIndex( return map; } -function resolveDefaultAccountId(cfg: OpenClawConfig, provider: ChannelId): string { - const plugin = getChannelPlugin(provider); - if (!plugin) { - return DEFAULT_ACCOUNT_ID; - } - return resolveChannelDefaultAccountId({ plugin, cfg }); +function resolveDefaultAccountId( + provider: ChannelId, + metadataByProvider: ReadonlyMap, +): string { + return metadataByProvider.get(provider)?.defaultAccountId ?? DEFAULT_ACCOUNT_ID; } -function shouldShowProviderEntry(entry: ProviderAccountStatus, cfg: OpenClawConfig): boolean { - if (entry.visibleInConfiguredLists !== undefined) { - if (!entry.visibleInConfiguredLists) { - const providerConfig = (cfg as Record)[entry.provider]; - return Boolean(entry.configured) || Boolean(providerConfig); - } - return Boolean(entry.configured); +function shouldShowProviderEntry(params: { + entry: ProviderAccountStatus; + cfg: OpenClawConfig; + metadataByProvider: ReadonlyMap; +}): boolean { + const visibleInConfiguredLists = + params.entry.visibleInConfiguredLists ?? + params.metadataByProvider.get(params.entry.provider)?.visibleInConfiguredLists; + if (visibleInConfiguredLists === false) { + const providerConfig = (params.cfg as Record)[params.entry.provider]; + return Boolean(params.entry.configured) || Boolean(providerConfig); } - const plugin = getChannelPlugin(entry.provider); - if (!plugin) { - return Boolean(entry.configured); - } - if (!isChannelVisibleInConfiguredLists(plugin.meta)) { - const providerConfig = (cfg as Record)[plugin.id]; - return Boolean(entry.configured) || Boolean(providerConfig); - } - return Boolean(entry.configured); + return Boolean(params.entry.configured); } function formatProviderEntry(entry: ProviderAccountStatus): string { @@ -171,7 +192,11 @@ function formatProviderEntry(entry: ProviderAccountStatus): string { return `${label}: ${formatProviderState(entry)}`; } -export function summarizeBindings(cfg: OpenClawConfig, bindings: AgentBinding[]): string[] { +export function summarizeBindings( + cfg: OpenClawConfig, + bindings: AgentBinding[], + metadataByProvider = buildProviderSummaryMetadataIndex(cfg), +): string[] { if (bindings.length === 0) { return []; } @@ -181,11 +206,13 @@ export function summarizeBindings(cfg: OpenClawConfig, bindings: AgentBinding[]) if (!channel) { continue; } - const accountId = binding.match.accountId ?? resolveDefaultAccountId(cfg, channel); + const accountId = + binding.match.accountId ?? resolveDefaultAccountId(channel, metadataByProvider); const key = providerAccountKey(channel, accountId); if (!seen.has(key)) { const label = formatChannelAccountLabel({ provider: channel, + providerLabel: metadataByProvider.get(channel)?.label, accountId, }); seen.set(key, label); @@ -199,9 +226,12 @@ export function listProvidersForAgent(params: { cfg: OpenClawConfig; bindings: AgentBinding[]; providerStatus: Map; + providerMetadata?: ReadonlyMap; }): string[] { const allProviderEntries = [...params.providerStatus.values()]; const providerLines: string[] = []; + const metadataByProvider = + params.providerMetadata ?? buildProviderSummaryMetadataIndex(params.cfg); if (params.bindings.length > 0) { const seen = new Set(); for (const binding of params.bindings) { @@ -209,7 +239,8 @@ export function listProvidersForAgent(params: { if (!channel) { continue; } - const accountId = binding.match.accountId ?? resolveDefaultAccountId(params.cfg, channel); + const accountId = + binding.match.accountId ?? resolveDefaultAccountId(channel, metadataByProvider); const key = providerAccountKey(channel, accountId); if (seen.has(key)) { continue; @@ -220,7 +251,11 @@ export function listProvidersForAgent(params: { providerLines.push(formatProviderEntry(status)); } else { providerLines.push( - `${formatChannelAccountLabel({ provider: channel, accountId })}: unknown`, + `${formatChannelAccountLabel({ + provider: channel, + providerLabel: metadataByProvider.get(channel)?.label, + accountId, + })}: unknown`, ); } } @@ -229,7 +264,7 @@ export function listProvidersForAgent(params: { if (params.summaryIsDefault) { for (const entry of allProviderEntries) { - if (shouldShowProviderEntry(entry, params.cfg)) { + if (shouldShowProviderEntry({ entry, cfg: params.cfg, metadataByProvider })) { providerLines.push(formatProviderEntry(entry)); } } diff --git a/src/commands/health-format.ts b/src/commands/health-format.ts index 92a2d6e385f..82339a58d37 100644 --- a/src/commands/health-format.ts +++ b/src/commands/health-format.ts @@ -1,4 +1,3 @@ -import { getChannelPlugin } from "../channels/plugins/index.js"; import { formatChannelStatusState } from "../channels/plugins/status-state.js"; import { asNullableRecord } from "../shared/record-coerce.js"; import { colorize, isRich, theme } from "../terminal/theme.js"; @@ -147,8 +146,7 @@ export const formatHealthChannelLines = ( if (!channelSummary) { continue; } - const plugin = getChannelPlugin(channelId as never); - const label = summary.channelLabels?.[channelId] ?? plugin?.meta.label ?? channelId; + const label = summary.channelLabels?.[channelId] ?? channelId; const accountSummaries = channelSummary.accounts ?? {}; const accountIds = opts.accountIdsByChannel?.[channelId]; const filteredSummaries = diff --git a/src/commands/message-format.ts b/src/commands/message-format.ts index bebcd8c2018..1c7384f64af 100644 --- a/src/commands/message-format.ts +++ b/src/commands/message-format.ts @@ -1,4 +1,4 @@ -import { getChannelPlugin } from "../channels/plugins/index.js"; +import { getLoadedChannelPlugin } from "../channels/plugins/index.js"; import type { ChannelId, ChannelMessageActionName } from "../channels/plugins/types.public.js"; import type { OutboundDeliveryResult } from "../infra/outbound/deliver.js"; import { formatGatewaySummary, formatOutboundDeliverySummary } from "../infra/outbound/format.js"; @@ -11,7 +11,7 @@ import { isRich, theme } from "../terminal/theme.js"; import { shortenText } from "./text-format.js"; const resolveChannelLabel = (channel: ChannelId) => - getChannelPlugin(channel)?.meta.label ?? channel; + getLoadedChannelPlugin(channel)?.meta.label ?? channel; function extractMessageId(payload: unknown): string | null { if (!payload || typeof payload !== "object") {