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 {
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;

View File

@@ -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<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 {
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<ChannelId, ProviderSummaryMetadata>,
): 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<string, unknown>)[entry.provider];
return Boolean(entry.configured) || Boolean(providerConfig);
}
return Boolean(entry.configured);
function shouldShowProviderEntry(params: {
entry: ProviderAccountStatus;
cfg: OpenClawConfig;
metadataByProvider: ReadonlyMap<ChannelId, ProviderSummaryMetadata>;
}): boolean {
const visibleInConfiguredLists =
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);
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);
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<string, ProviderAccountStatus>;
providerMetadata?: ReadonlyMap<ChannelId, ProviderSummaryMetadata>;
}): 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<string>();
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));
}
}

View File

@@ -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 =

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 { 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") {