fix: avoid channel runtime in format summaries

This commit is contained in:
Shakker
2026-04-26 08:13:46 +01:00
parent 8b32c31252
commit 8fe449c883
4 changed files with 73 additions and 37 deletions

View File

@@ -11,6 +11,7 @@ import type { AgentSummary } from "./agents.config.js";
import { buildAgentSummaries } from "./agents.config.js"; import { buildAgentSummaries } from "./agents.config.js";
import { import {
buildProviderStatusIndex, buildProviderStatusIndex,
buildProviderSummaryMetadataIndex,
listProvidersForAgent, listProvidersForAgent,
summarizeBindings, summarizeBindings,
} from "./agents.providers.js"; } from "./agents.providers.js";
@@ -107,11 +108,12 @@ export async function agentsListCommand(
// catalog entry, this keeps `agents list --json` on the config-only path. // catalog entry, this keeps `agents list --json` on the config-only path.
const includeProviderDetails = !opts.json || opts.bindings === true; const includeProviderDetails = !opts.json || opts.bindings === true;
const providerStatus = includeProviderDetails ? await buildProviderStatusIndex(cfg) : null; const providerStatus = includeProviderDetails ? await buildProviderStatusIndex(cfg) : null;
const providerMetadata = includeProviderDetails ? buildProviderSummaryMetadataIndex(cfg) : null;
for (const summary of summaries) { for (const summary of summaries) {
const bindings = bindingMap.get(summary.id) ?? []; const bindings = bindingMap.get(summary.id) ?? [];
if (includeProviderDetails && providerStatus) { if (includeProviderDetails && providerStatus && providerMetadata) {
const routes = summarizeBindings(cfg, bindings); const routes = summarizeBindings(cfg, bindings, providerMetadata);
if (routes.length > 0) { if (routes.length > 0) {
summary.routes = routes; summary.routes = routes;
} else if (summary.isDefault) { } else if (summary.isDefault) {
@@ -123,6 +125,7 @@ export async function agentsListCommand(
cfg, cfg,
bindings, bindings,
providerStatus, providerStatus,
providerMetadata,
}); });
if (providerLines.length > 0) { if (providerLines.length > 0) {
summary.providers = providerLines; summary.providers = providerLines;

View File

@@ -1,6 +1,6 @@
import { isChannelVisibleInConfiguredLists } from "../channels/plugins/exposure.js"; import { isChannelVisibleInConfiguredLists } from "../channels/plugins/exposure.js";
import { resolveChannelDefaultAccountId } from "../channels/plugins/helpers.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 { listReadOnlyChannelPluginsForConfig } from "../channels/plugins/read-only.js";
import type { ChannelPlugin } from "../channels/plugins/types.plugin.js"; import type { ChannelPlugin } from "../channels/plugins/types.plugin.js";
import type { ChannelId } from "../channels/plugins/types.public.js"; import type { ChannelId } from "../channels/plugins/types.public.js";
@@ -19,10 +19,37 @@ type ProviderAccountStatus = {
visibleInConfiguredLists?: boolean; visibleInConfiguredLists?: boolean;
}; };
export type ProviderSummaryMetadata = {
label: string;
defaultAccountId: string;
visibleInConfiguredLists: boolean;
};
function providerAccountKey(provider: ChannelId, accountId?: string) { function providerAccountKey(provider: ChannelId, accountId?: string) {
return `${provider}:${accountId ?? DEFAULT_ACCOUNT_ID}`; return `${provider}:${accountId ?? DEFAULT_ACCOUNT_ID}`;
} }
export function buildProviderSummaryMetadataIndex(
cfg: OpenClawConfig,
): Map<ChannelId, ProviderSummaryMetadata> {
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 { function isUnresolvedSecretRefResolutionError(error: unknown): boolean {
return ( return (
error instanceof Error && error instanceof Error &&
@@ -37,8 +64,7 @@ function formatChannelAccountLabel(params: {
accountId: string; accountId: string;
name?: string; name?: string;
}): string { }): string {
const label = const label = params.providerLabel ?? params.provider;
params.providerLabel ?? getChannelPlugin(params.provider)?.meta.label ?? params.provider;
const account = params.name?.trim() const account = params.name?.trim()
? `${params.accountId} (${params.name.trim()})` ? `${params.accountId} (${params.name.trim()})`
: params.accountId; : params.accountId;
@@ -134,31 +160,26 @@ export async function buildProviderStatusIndex(
return map; return map;
} }
function resolveDefaultAccountId(cfg: OpenClawConfig, provider: ChannelId): string { function resolveDefaultAccountId(
const plugin = getChannelPlugin(provider); provider: ChannelId,
if (!plugin) { metadataByProvider: ReadonlyMap<ChannelId, ProviderSummaryMetadata>,
return DEFAULT_ACCOUNT_ID; ): string {
} return metadataByProvider.get(provider)?.defaultAccountId ?? DEFAULT_ACCOUNT_ID;
return resolveChannelDefaultAccountId({ plugin, cfg });
} }
function shouldShowProviderEntry(entry: ProviderAccountStatus, cfg: OpenClawConfig): boolean { function shouldShowProviderEntry(params: {
if (entry.visibleInConfiguredLists !== undefined) { entry: ProviderAccountStatus;
if (!entry.visibleInConfiguredLists) { cfg: OpenClawConfig;
const providerConfig = (cfg as Record<string, unknown>)[entry.provider]; metadataByProvider: ReadonlyMap<ChannelId, ProviderSummaryMetadata>;
return Boolean(entry.configured) || Boolean(providerConfig); }): boolean {
} const visibleInConfiguredLists =
return Boolean(entry.configured); params.entry.visibleInConfiguredLists ??
params.metadataByProvider.get(params.entry.provider)?.visibleInConfiguredLists;
if (visibleInConfiguredLists === false) {
const providerConfig = (params.cfg as Record<string, unknown>)[params.entry.provider];
return Boolean(params.entry.configured) || Boolean(providerConfig);
} }
const plugin = getChannelPlugin(entry.provider); return Boolean(params.entry.configured);
if (!plugin) {
return Boolean(entry.configured);
}
if (!isChannelVisibleInConfiguredLists(plugin.meta)) {
const providerConfig = (cfg as Record<string, unknown>)[plugin.id];
return Boolean(entry.configured) || Boolean(providerConfig);
}
return Boolean(entry.configured);
} }
function formatProviderEntry(entry: ProviderAccountStatus): string { function formatProviderEntry(entry: ProviderAccountStatus): string {
@@ -171,7 +192,11 @@ function formatProviderEntry(entry: ProviderAccountStatus): string {
return `${label}: ${formatProviderState(entry)}`; 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) { if (bindings.length === 0) {
return []; return [];
} }
@@ -181,11 +206,13 @@ export function summarizeBindings(cfg: OpenClawConfig, bindings: AgentBinding[])
if (!channel) { if (!channel) {
continue; continue;
} }
const accountId = binding.match.accountId ?? resolveDefaultAccountId(cfg, channel); const accountId =
binding.match.accountId ?? resolveDefaultAccountId(channel, metadataByProvider);
const key = providerAccountKey(channel, accountId); const key = providerAccountKey(channel, accountId);
if (!seen.has(key)) { if (!seen.has(key)) {
const label = formatChannelAccountLabel({ const label = formatChannelAccountLabel({
provider: channel, provider: channel,
providerLabel: metadataByProvider.get(channel)?.label,
accountId, accountId,
}); });
seen.set(key, label); seen.set(key, label);
@@ -199,9 +226,12 @@ export function listProvidersForAgent(params: {
cfg: OpenClawConfig; cfg: OpenClawConfig;
bindings: AgentBinding[]; bindings: AgentBinding[];
providerStatus: Map<string, ProviderAccountStatus>; providerStatus: Map<string, ProviderAccountStatus>;
providerMetadata?: ReadonlyMap<ChannelId, ProviderSummaryMetadata>;
}): string[] { }): string[] {
const allProviderEntries = [...params.providerStatus.values()]; const allProviderEntries = [...params.providerStatus.values()];
const providerLines: string[] = []; const providerLines: string[] = [];
const metadataByProvider =
params.providerMetadata ?? buildProviderSummaryMetadataIndex(params.cfg);
if (params.bindings.length > 0) { if (params.bindings.length > 0) {
const seen = new Set<string>(); const seen = new Set<string>();
for (const binding of params.bindings) { for (const binding of params.bindings) {
@@ -209,7 +239,8 @@ export function listProvidersForAgent(params: {
if (!channel) { if (!channel) {
continue; continue;
} }
const accountId = binding.match.accountId ?? resolveDefaultAccountId(params.cfg, channel); const accountId =
binding.match.accountId ?? resolveDefaultAccountId(channel, metadataByProvider);
const key = providerAccountKey(channel, accountId); const key = providerAccountKey(channel, accountId);
if (seen.has(key)) { if (seen.has(key)) {
continue; continue;
@@ -220,7 +251,11 @@ export function listProvidersForAgent(params: {
providerLines.push(formatProviderEntry(status)); providerLines.push(formatProviderEntry(status));
} else { } else {
providerLines.push( 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) { if (params.summaryIsDefault) {
for (const entry of allProviderEntries) { for (const entry of allProviderEntries) {
if (shouldShowProviderEntry(entry, params.cfg)) { if (shouldShowProviderEntry({ entry, cfg: params.cfg, metadataByProvider })) {
providerLines.push(formatProviderEntry(entry)); providerLines.push(formatProviderEntry(entry));
} }
} }

View File

@@ -1,4 +1,3 @@
import { getChannelPlugin } from "../channels/plugins/index.js";
import { formatChannelStatusState } from "../channels/plugins/status-state.js"; import { formatChannelStatusState } from "../channels/plugins/status-state.js";
import { asNullableRecord } from "../shared/record-coerce.js"; import { asNullableRecord } from "../shared/record-coerce.js";
import { colorize, isRich, theme } from "../terminal/theme.js"; import { colorize, isRich, theme } from "../terminal/theme.js";
@@ -147,8 +146,7 @@ export const formatHealthChannelLines = (
if (!channelSummary) { if (!channelSummary) {
continue; continue;
} }
const plugin = getChannelPlugin(channelId as never); const label = summary.channelLabels?.[channelId] ?? channelId;
const label = summary.channelLabels?.[channelId] ?? plugin?.meta.label ?? channelId;
const accountSummaries = channelSummary.accounts ?? {}; const accountSummaries = channelSummary.accounts ?? {};
const accountIds = opts.accountIdsByChannel?.[channelId]; const accountIds = opts.accountIdsByChannel?.[channelId];
const filteredSummaries = const filteredSummaries =

View File

@@ -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 { ChannelId, ChannelMessageActionName } from "../channels/plugins/types.public.js";
import type { OutboundDeliveryResult } from "../infra/outbound/deliver.js"; import type { OutboundDeliveryResult } from "../infra/outbound/deliver.js";
import { formatGatewaySummary, formatOutboundDeliverySummary } from "../infra/outbound/format.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"; import { shortenText } from "./text-format.js";
const resolveChannelLabel = (channel: ChannelId) => const resolveChannelLabel = (channel: ChannelId) =>
getChannelPlugin(channel)?.meta.label ?? channel; getLoadedChannelPlugin(channel)?.meta.label ?? channel;
function extractMessageId(payload: unknown): string | null { function extractMessageId(payload: unknown): string | null {
if (!payload || typeof payload !== "object") { if (!payload || typeof payload !== "object") {