




















@@ -5,6 +5,7 @@ import { listReadOnlyChannelPluginsForConfig } from "../../channels/plugins/read
55import { buildChannelAccountSnapshot } from "../../channels/plugins/status.js";
66import type { ChannelPlugin } from "../../channels/plugins/types.plugin.js";
77import type { ChannelAccountSnapshot } from "../../channels/plugins/types.public.js";
8+import { callGateway } from "../../gateway/call.js";
89import { defaultRuntime, type RuntimeEnv, writeRuntimeJson } from "../../runtime.js";
910import { formatDocsLink } from "../../terminal/links.js";
1011import { theme } from "../../terminal/theme.js";
@@ -17,6 +18,47 @@ export type ChannelsListOptions = {
1718all?: boolean;
1819};
192021+type RuntimeChannelStatus = {
22+channelAccounts?: Record<string, unknown>;
23+};
24+25+function normalizeRuntimeAccounts(
26+payload: RuntimeChannelStatus | null,
27+): Map<string, ChannelAccountSnapshot[]> {
28+const out = new Map<string, ChannelAccountSnapshot[]>();
29+const rawAccounts = payload?.channelAccounts;
30+if (!rawAccounts || typeof rawAccounts !== "object") {
31+return out;
32+}
33+for (const [channelId, accounts] of Object.entries(rawAccounts)) {
34+if (!Array.isArray(accounts)) {
35+continue;
36+}
37+const normalized = accounts.filter(
38+(account): account is ChannelAccountSnapshot =>
39+Boolean(account) &&
40+typeof account === "object" &&
41+typeof (account as { accountId?: unknown }).accountId === "string",
42+);
43+if (normalized.length > 0) {
44+out.set(channelId, normalized);
45+}
46+}
47+return out;
48+}
49+50+async function readGatewayChannelStatus(): Promise<RuntimeChannelStatus | null> {
51+try {
52+return (await callGateway({
53+method: "channels.status",
54+params: { probe: false, timeoutMs: 5_000 },
55+timeoutMs: 5_000,
56+})) as RuntimeChannelStatus;
57+} catch {
58+return null;
59+}
60+}
61+2062const colorValue = (value: string) => {
2163if (value === "none") {
2264return theme.error(value);
@@ -39,14 +81,20 @@ function formatInstalled(value: boolean): string {
3981return value ? theme.success("installed") : theme.warn("not installed");
4082}
418342-function formatTokenSource(source?: string): string {
84+function formatCredentialSource(source?: string, status?: string): string {
4385const value = source || "none";
44-return `token=${colorValue(value)}`;
86+if (status === "configured_unavailable" && value !== "none") {
87+return theme.warn(`${value}-unavailable`);
88+}
89+return colorValue(value);
4590}
469147-function formatSource(label: string, source?: string): string {
48-const value = source || "none";
49-return `${label}=${colorValue(value)}`;
92+function formatTokenSource(source?: string, status?: string): string {
93+return `token=${formatCredentialSource(source, status)}`;
94+}
95+96+function formatSource(label: string, source?: string, status?: string): string {
97+return `${label}=${formatCredentialSource(source, status)}`;
5098}
519952100function formatLinked(value: boolean): string {
@@ -83,13 +131,13 @@ function formatAccountLine(params: {
83131bits.push(formatLinked(snapshot.linked));
84132}
85133if (snapshot.tokenSource) {
86-bits.push(formatTokenSource(snapshot.tokenSource));
134+bits.push(formatTokenSource(snapshot.tokenSource, snapshot.tokenStatus));
87135}
88136if (snapshot.botTokenSource) {
89-bits.push(formatSource("bot", snapshot.botTokenSource));
137+bits.push(formatSource("bot", snapshot.botTokenSource, snapshot.botTokenStatus));
90138}
91139if (snapshot.appTokenSource) {
92-bits.push(formatSource("app", snapshot.appTokenSource));
140+bits.push(formatSource("app", snapshot.appTokenSource, snapshot.appTokenStatus));
93141}
94142if (snapshot.baseUrl) {
95143bits.push(`base=${theme.muted(snapshot.baseUrl)}`);
@@ -129,6 +177,10 @@ export async function channelsListCommand(
129177 cfg,
130178 ...(workspaceDir ? { workspaceDir } : {}),
131179});
180+const runtimeAccountsByChannel =
181+opts.json === true
182+ ? new Map<string, ChannelAccountSnapshot[]>()
183+ : normalizeRuntimeAccounts(await readGatewayChannelStatus());
132184const installedByChannelId = new Map<string, boolean>();
133185for (const entry of catalogEntries) {
134186installedByChannelId.set(
@@ -158,8 +210,14 @@ export async function channelsListCommand(
158210const accountIds = plugin.config.listAccountIds(cfg);
159211if (accountIds && accountIds.length > 0) {
160212renderedChannelIds.add(plugin.id);
161-for (const accountId of accountIds) {
162-const snapshot = await buildChannelAccountSnapshot({ plugin, cfg, accountId });
213+const runtimeAccounts = runtimeAccountsByChannel.get(plugin.id) ?? [];
214+const mergedAccountIds = [
215+ ...new Set([...accountIds, ...runtimeAccounts.map((account) => account.accountId)]),
216+];
217+for (const accountId of mergedAccountIds) {
218+const runtimeSnapshot = runtimeAccounts.find((account) => account.accountId === accountId);
219+const snapshot =
220+runtimeSnapshot ?? (await buildChannelAccountSnapshot({ plugin, cfg, accountId }));
163221accountLines.push({
164222 plugin,
165223 snapshot,
@@ -184,10 +242,13 @@ export async function channelsListCommand(
184242 cfg,
185243accountId: "default",
186244});
245+const runtimeSnapshot = runtimeAccountsByChannel
246+.get(plugin.id)
247+?.find((account) => account.accountId === "default");
187248renderedChannelIds.add(plugin.id);
188249accountLines.push({
189250 plugin,
190- snapshot,
251+snapshot: runtimeSnapshot ?? snapshot,
191252installed: isInstalled(plugin.id),
192253});
193254}
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。