openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
655 lines (654 loc) • 26.6 kB
JavaScript
import { t as isTruthyEnvValue } from "./env-Di49gJwo.js";
import { n as asNullableRecord } from "./record-coerce-DHZ4bFlT.js";
import { j as resolveTimerTimeoutMs } from "./number-coercion-CJQ8TR--.js";
import { i as formatErrorMessage } from "./errors-BXgSefBE.js";
import { n as isRich } from "./theme-vjDs9tao.js";
import { r as writeRuntimeJson } from "./runtime-B4lgFmsS.js";
import "./agent-scope-MrLta7Pq.js";
import { u as normalizeAgentId } from "./session-key-B_NoIfpX.js";
import { c as resolveDefaultAgentId } from "./agent-scope-config-CgCYpZfK.js";
import { n as info } from "./globals-GTrXU4s9.js";
import { a as buildGatewayProbeConnectionDetails, d as formatGatewayTransportErrorJson, f as isGatewayCredentialsRequiredError, i as buildGatewayConnectionDetails, o as callGateway } from "./call-B5-GYOlf.js";
import { o as getActivePluginRegistry } from "./runtime-B2ROiq5l.js";
import { r as listContextEngineQuarantines } from "./registry-Dca-zXEr.js";
import { u as resolveStorePath } from "./paths-NEwU8m3X.js";
import { o as getGatewayModelPricingHealth } from "./model-pricing-cache-state-0MA2CEaX.js";
import { t as isGatewayModelPricingEnabled } from "./model-pricing-config-pKJPI_G6.js";
import { r as resolveHeartbeatSummaryForAgent } from "./heartbeat-summary-B73O--Iv.js";
import { a as resolvePreferredAccountId, t as buildChannelAccountBindings } from "./bindings-BzzSfrl7.js";
import { i as resolveChannelDefaultAccountId } from "./helpers-BLGguEiN.js";
import { n as listReadOnlyChannelPluginsForConfig } from "./read-only-7UA2_pQ4.js";
import { r as withProgress } from "./progress-CN3xUiCO.js";
import { t as styleHealthChannelLine } from "./health-style-Cm1YcOec.js";
import { a as resolveChannelAccountConfigured, o as resolveChannelAccountEnabled, t as inspectChannelAccount } from "./account-inspection-DG-22xoO.js";
import { t as probeGatewayStatus } from "./probe-Cg8o8v0y.js";
import { n as buildChannelAccountSnapshotFromAccount } from "./status-W16cTRep.js";
import { n as DEFAULT_CHANNEL_STALE_EVENT_THRESHOLD_MS, r as evaluateChannelHealth, t as DEFAULT_CHANNEL_CONNECT_GRACE_MS } from "./channel-health-policy-D_eDwUBm.js";
import { t as formatHealthChannelLines } from "./health-format-ebE8IYQV.js";
import { a as gatewayProbeResultSawGateway, i as buildCredentialsRequiredHealthDiagnostic, r as GATEWAY_HEALTH_REACHABLE_LINE } from "./gateway-health-auth-diagnostic-Bd2f-neB.js";
import { t as logGatewayConnectionDetails } from "./status.gateway-connection-BhoLn6II.js";
//#region src/commands/health.ts
/** Collects and renders gateway health for channels, agents, plugins, and sessions. */
const DEFAULT_TIMEOUT_MS = 1e4;
const debugHealth = (...args) => {
if (isTruthyEnvValue(process.env.OPENCLAW_DEBUG_HEALTH)) console.warn("[health:debug]", ...args);
};
const loadConfigRuntime = async () => await import("./config/config.js");
const PUBLIC_IMESSAGE_FULL_DISK_ACCESS_ERROR = "imsg cannot access ~/Library/Messages/chat.db. Grant Full Disk Access to the Gateway/launcher process and restart Gateway.";
const redactIMessageProbeErrorMessage = (message) => {
const trimmed = message.trim();
if (!trimmed) return "";
return trimmed.replaceAll(/\/Users\/[^/\s]+\/Library\/Messages\/chat\.db/g, "~/Library/Messages/chat.db");
};
const buildNonSensitiveProbeFailure = (channelId, probe) => {
const record = asNullableRecord(probe);
if (channelId !== "imessage" || !record || record.ok !== false) return;
if (typeof record.error !== "string") return;
const error = redactIMessageProbeErrorMessage(record.error);
if (!/\bimsg\b/i.test(error) || !error.includes("~/Library/Messages/chat.db") || !/\bFull Disk Access\b/i.test(error)) return;
return {
ok: false,
error: PUBLIC_IMESSAGE_FULL_DISK_ACCESS_ERROR
};
};
const formatDurationParts = (ms) => {
if (!Number.isFinite(ms)) return "unknown";
if (ms < 1e3) return `${Math.max(0, Math.round(ms))}ms`;
const units = [
{
label: "w",
size: 10080 * 60 * 1e3
},
{
label: "d",
size: 1440 * 60 * 1e3
},
{
label: "h",
size: 3600 * 1e3
},
{
label: "m",
size: 60 * 1e3
},
{
label: "s",
size: 1e3
}
];
let remaining = Math.max(0, Math.floor(ms));
const parts = [];
for (const unit of units) {
const value = Math.floor(remaining / unit.size);
if (value > 0) {
parts.push(`${value}${unit.label}`);
remaining -= value * unit.size;
}
}
if (parts.length === 0) return "0s";
return parts.join(" ");
};
function formatEventLoopHealthLine(summary) {
const eventLoop = summary.eventLoop;
if (!eventLoop) return null;
return `Gateway event loop: ${eventLoop.degraded ? "degraded" : "ok"}${eventLoop.reasons.length > 0 ? ` reasons=${eventLoop.reasons.join(",")}` : ""} max=${Math.round(eventLoop.delayMaxMs)}ms p99=${Math.round(eventLoop.delayP99Ms)}ms util=${eventLoop.utilization} cpu=${eventLoop.cpuCoreRatio}`;
}
/** Formats optional model-pricing cache degradation for text health output. */
function formatModelPricingHealthLine(summary) {
const modelPricing = summary.modelPricing;
if (!modelPricing || modelPricing.state === "disabled") return null;
if (modelPricing.state === "ok") return null;
return `Model pricing: warning (optional pricing refresh degraded)${modelPricing.detail ? ` (${modelPricing.detail})` : ""}`;
}
function buildContextEngineHealthSummary() {
const quarantined = [];
for (const entry of listContextEngineQuarantines()) {
const summary = {
engineId: entry.engineId,
operation: entry.operation,
reason: entry.reason,
failedAt: entry.failedAt.getTime()
};
if (entry.owner) summary.owner = entry.owner;
quarantined.push(summary);
}
return quarantined.length > 0 ? { quarantined } : void 0;
}
/** Formats context engine quarantine state for text health output. */
function formatContextEngineHealthLine(summary) {
const quarantined = summary.contextEngines?.quarantined ?? [];
if (quarantined.length === 0) return null;
const engines = quarantined.map((entry) => entry.engineId).join(", ");
return `Context engine: warning (${quarantined.length} quarantined; downgraded to legacy: ${engines})`;
}
const resolveHeartbeatSummary = (cfg, agentId) => resolveHeartbeatSummaryForAgent(cfg, agentId);
const resolveAgentOrder = (cfg) => {
const defaultAgentId = resolveDefaultAgentId(cfg);
const entries = Array.isArray(cfg.agents?.list) ? cfg.agents.list : [];
const seen = /* @__PURE__ */ new Set();
const ordered = [];
for (const entry of entries) {
if (!entry || typeof entry !== "object") continue;
if (typeof entry.id !== "string" || !entry.id.trim()) continue;
const id = normalizeAgentId(entry.id);
if (!id || seen.has(id)) continue;
seen.add(id);
ordered.push({
id,
name: typeof entry.name === "string" ? entry.name : void 0
});
}
if (!seen.has(defaultAgentId)) ordered.unshift({ id: defaultAgentId });
if (ordered.length === 0) ordered.push({ id: defaultAgentId });
return {
defaultAgentId,
ordered
};
};
const buildSessionSummary = async (storePath) => {
const { loadSessionStore } = await import("./store-BQ72aKnY.js");
const store = loadSessionStore(storePath, { clone: false });
const sessions = Object.entries(store).filter(([key]) => key !== "global" && key !== "unknown").map(([key, entry]) => ({
key,
updatedAt: entry?.updatedAt ?? 0
})).toSorted((a, b) => b.updatedAt - a.updatedAt);
const recent = sessions.slice(0, 5).map((s) => ({
key: s.key,
updatedAt: s.updatedAt || null,
age: s.updatedAt ? Date.now() - s.updatedAt : null
}));
return {
path: storePath,
count: sessions.length,
recent
};
};
function buildPluginHealthSummary() {
const registry = getActivePluginRegistry();
if (!registry) return;
const loaded = registry.plugins.filter((plugin) => plugin.status === "loaded").map((plugin) => plugin.id).toSorted((left, right) => left.localeCompare(right));
const errors = registry.plugins.filter((plugin) => plugin.status === "error").map((plugin) => {
const error = {
id: plugin.id,
origin: plugin.origin,
activated: plugin.activated === true,
error: plugin.error ?? "unknown plugin load error"
};
if (plugin.activationSource) error.activationSource = plugin.activationSource;
if (plugin.activationReason) error.activationReason = plugin.activationReason;
if (plugin.failurePhase) error.failurePhase = plugin.failurePhase;
return error;
}).toSorted((left, right) => left.id.localeCompare(right.id));
if (loaded.length === 0 && errors.length === 0) return;
return {
loaded,
errors
};
}
function readBooleanField(value, key) {
const record = asNullableRecord(value);
if (!record) return;
return typeof record[key] === "boolean" ? record[key] : void 0;
}
const hasAccountValue = (account) => account !== null && account !== void 0;
function resolveProbeAccountEnabled(params) {
const fallback = readBooleanField(params.account, "enabled") ?? true;
try {
return resolveChannelAccountEnabled({
plugin: params.plugin,
account: params.account,
cfg: params.cfg
});
} catch (error) {
params.diagnostics.push(`${params.plugin.id}:${params.accountId}: failed to evaluate enabled state (${formatErrorMessage(error)}).`);
return fallback;
}
}
async function resolveProbeAccountConfigured(params) {
const fallback = readBooleanField(params.account, "configured") ?? true;
try {
return await resolveChannelAccountConfigured({
plugin: params.plugin,
account: params.account,
cfg: params.cfg,
readAccountConfiguredField: true
});
} catch (error) {
params.diagnostics.push(`${params.plugin.id}:${params.accountId}: failed to evaluate configured state (${formatErrorMessage(error)}).`);
return fallback;
}
}
async function resolveHealthAccountContext(params) {
const diagnostics = [];
let account;
try {
account = params.plugin.config.resolveAccount(params.cfg, params.accountId);
} catch (error) {
diagnostics.push(`${params.plugin.id}:${params.accountId}: failed to resolve account (${formatErrorMessage(error)}).`);
}
let inspectedAccount;
try {
inspectedAccount = await inspectChannelAccount(params);
} catch (error) {
diagnostics.push(`${params.plugin.id}:${params.accountId}: failed to inspect account (${formatErrorMessage(error)}).`);
}
const probeAccount = hasAccountValue(account) ? account : inspectedAccount;
if (!hasAccountValue(probeAccount)) return {
probeAccount: {},
snapshotAccount: {},
enabled: false,
configured: false,
diagnostics
};
return {
probeAccount,
snapshotAccount: hasAccountValue(inspectedAccount) ? inspectedAccount : probeAccount,
enabled: resolveProbeAccountEnabled({
plugin: params.plugin,
cfg: params.cfg,
accountId: params.accountId,
account: probeAccount,
diagnostics
}),
configured: await resolveProbeAccountConfigured({
plugin: params.plugin,
cfg: params.cfg,
accountId: params.accountId,
account: probeAccount,
diagnostics
}),
diagnostics
};
}
/** Builds the gateway-side health snapshot for channels, agents, plugins, and sessions. */
async function getHealthSnapshot(params) {
const timeoutMs = params?.timeoutMs;
const cfg = await readRuntimeHealthConfig();
const { defaultAgentId, ordered } = resolveAgentOrder(cfg);
const channelBindings = buildChannelAccountBindings(cfg);
const sessionCache = /* @__PURE__ */ new Map();
const agents = [];
for (const entry of ordered) {
const storePath = resolveStorePath(cfg.session?.store, { agentId: entry.id });
const sessions = sessionCache.get(storePath) ?? await buildSessionSummary(storePath);
sessionCache.set(storePath, sessions);
agents.push({
agentId: entry.id,
name: entry.name,
isDefault: entry.id === defaultAgentId,
heartbeat: resolveHeartbeatSummary(cfg, entry.id),
sessions
});
}
const defaultAgent = agents.find((agent) => agent.isDefault) ?? agents[0];
const heartbeatSeconds = defaultAgent?.heartbeat.everyMs ? Math.round(defaultAgent.heartbeat.everyMs / 1e3) : 0;
const sessions = defaultAgent?.sessions ?? await buildSessionSummary(resolveStorePath(cfg.session?.store, { agentId: defaultAgentId }));
const start = Date.now();
const cappedTimeout = resolveTimerTimeoutMs(timeoutMs, DEFAULT_TIMEOUT_MS, 50);
const doProbe = params?.probe !== false;
const includeSensitive = params?.includeSensitive !== false;
const channels = {};
const plugins = listReadOnlyChannelPluginsForConfig(cfg, { includeSetupFallbackPlugins: false });
const channelOrder = plugins.map((plugin) => plugin.id);
const channelLabels = {};
for (const plugin of plugins) {
channelLabels[plugin.id] = plugin.meta.label ?? plugin.id;
const accountIds = plugin.config.listAccountIds(cfg);
const defaultAccountId = resolveChannelDefaultAccountId({
plugin,
cfg,
accountIds
});
const boundAccounts = channelBindings.get(plugin.id)?.get(defaultAgentId) ?? [];
const preferredAccountId = resolvePreferredAccountId({
accountIds,
defaultAccountId,
boundAccounts
});
const boundAccountIdsAll = Array.from(new Set(Array.from(channelBindings.get(plugin.id)?.values() ?? []).flat()));
const accountIdsToProbe = Array.from(new Set([
preferredAccountId,
defaultAccountId,
...accountIds,
...boundAccountIdsAll
].filter((value) => value && value.trim())));
debugHealth("channel", {
id: plugin.id,
accountIds,
defaultAccountId,
boundAccounts,
preferredAccountId,
accountIdsToProbe
});
const accountSummaries = {};
for (const accountId of accountIdsToProbe) {
const { probeAccount, snapshotAccount, enabled, configured, diagnostics } = await resolveHealthAccountContext({
plugin,
cfg,
accountId
});
if (diagnostics.length > 0) debugHealth("account.diagnostics", {
channel: plugin.id,
accountId,
diagnostics
});
let probe;
let lastProbeAt = null;
if (enabled && configured && doProbe && plugin.status?.probeAccount) try {
probe = await plugin.status.probeAccount({
account: probeAccount,
timeoutMs: cappedTimeout,
cfg
});
lastProbeAt = Date.now();
} catch (err) {
probe = {
ok: false,
error: formatErrorMessage(err)
};
lastProbeAt = Date.now();
}
const probeRecord = probe && typeof probe === "object" ? probe : null;
const bot = probeRecord && typeof probeRecord.bot === "object" ? probeRecord.bot : null;
if (bot?.username) debugHealth("probe.bot", {
channel: plugin.id,
accountId,
username: bot.username
});
const runtimeSnapshot = params?.runtimeSnapshot?.channelAccounts[plugin.id]?.[accountId] ?? (accountId === defaultAccountId ? params?.runtimeSnapshot?.channels[plugin.id] : void 0);
const nonSensitiveProbeFailure = buildNonSensitiveProbeFailure(plugin.id, probe);
const snapshot = await buildChannelAccountSnapshotFromAccount({
plugin,
cfg,
accountId,
account: snapshotAccount,
runtime: runtimeSnapshot,
probe: includeSensitive ? probe : nonSensitiveProbeFailure,
enabledFallback: enabled,
configuredFallback: configured
});
if (lastProbeAt) snapshot.lastProbeAt = lastProbeAt;
const health = evaluateChannelHealth(snapshot, {
channelId: plugin.id,
now: Date.now(),
staleEventThresholdMs: DEFAULT_CHANNEL_STALE_EVENT_THRESHOLD_MS,
channelConnectGraceMs: DEFAULT_CHANNEL_CONNECT_GRACE_MS
});
if (!health.healthy) snapshot.healthState = health.reason;
const summary = plugin.status?.buildChannelSummary ? await plugin.status.buildChannelSummary({
account: probeAccount,
cfg,
defaultAccountId: accountId,
snapshot
}) : void 0;
const record = summary && typeof summary === "object" ? {
...snapshot,
...summary
} : {
...snapshot,
accountId,
configured
};
if (record.configured === void 0) record.configured = configured;
if (includeSensitive && record.probe === void 0 && probe !== void 0) record.probe = probe;
if (!includeSensitive) {
const safeProbeFailure = buildNonSensitiveProbeFailure(plugin.id, record.probe) ?? nonSensitiveProbeFailure;
if (safeProbeFailure) record.probe = safeProbeFailure;
else delete record.probe;
}
if (record.lastProbeAt === void 0 && lastProbeAt) record.lastProbeAt = lastProbeAt;
record.accountId = accountId;
accountSummaries[accountId] = record;
}
const fallbackSummary = accountSummaries[preferredAccountId] ?? accountSummaries[defaultAccountId] ?? accountSummaries[accountIdsToProbe[0] ?? preferredAccountId] ?? accountSummaries[Object.keys(accountSummaries)[0]];
if (fallbackSummary) channels[plugin.id] = {
...fallbackSummary,
accounts: accountSummaries
};
}
const pluginHealth = buildPluginHealthSummary();
const contextEngineHealth = buildContextEngineHealthSummary();
return {
ok: true,
ts: Date.now(),
durationMs: Date.now() - start,
...params?.eventLoop ? { eventLoop: params.eventLoop } : {},
...pluginHealth ? { plugins: pluginHealth } : {},
...contextEngineHealth ? { contextEngines: contextEngineHealth } : {},
modelPricing: getGatewayModelPricingHealth({ enabled: isGatewayModelPricingEnabled(cfg) }),
channels,
channelOrder,
channelLabels,
heartbeatSeconds,
defaultAgentId,
agents,
sessions: {
path: sessions.path,
count: sessions.count,
recent: sessions.recent
}
};
}
/** Runs the `openclaw health` command against the gateway and renders JSON or text. */
async function healthCommand(opts, runtime) {
const cfg = opts.config ?? await readBestEffortHealthConfig();
let summary;
try {
summary = await withProgress({
label: "Checking gateway health…",
indeterminate: true,
enabled: opts.json !== true
}, async () => await callGateway({
method: "health",
params: opts.verbose ? { probe: true } : void 0,
timeoutMs: opts.timeoutMs,
config: cfg,
token: opts.token,
password: opts.password
}));
} catch (error) {
if (isGatewayCredentialsRequiredError(error)) {
const details = await buildGatewayProbeConnectionDetails({
config: cfg,
token: opts.token,
password: opts.password
});
if (gatewayProbeResultSawGateway(await probeGatewayStatus({
url: details.url,
token: opts.token,
password: opts.password,
tlsFingerprint: details.tlsFingerprint,
preauthHandshakeTimeoutMs: details.preauthHandshakeTimeoutMs,
timeoutMs: opts.timeoutMs ?? DEFAULT_TIMEOUT_MS,
config: cfg,
json: opts.json
}))) {
const diagnostic = buildCredentialsRequiredHealthDiagnostic();
if (opts.json) {
writeRuntimeJson(runtime, diagnostic);
runtime.exit(1);
return;
}
runtime.log(GATEWAY_HEALTH_REACHABLE_LINE);
runtime.log(diagnostic.error.message);
runtime.exit(1);
return;
}
throw error;
}
if (opts.json) {
const payload = formatGatewayTransportErrorJson(error);
if (payload) {
writeRuntimeJson(runtime, payload);
runtime.exit(1);
return;
}
}
throw error;
}
if (opts.json) writeRuntimeJson(runtime, summary);
else {
const debugEnabled = isTruthyEnvValue(process.env.OPENCLAW_DEBUG_HEALTH);
const rich = isRich();
if (opts.verbose) logGatewayConnectionDetails({
runtime,
info,
message: buildGatewayConnectionDetails({ config: cfg }).message
});
const localAgents = resolveAgentOrder(cfg);
const defaultAgentId = summary.defaultAgentId ?? localAgents.defaultAgentId;
const agents = Array.isArray(summary.agents) ? summary.agents : [];
const resolvedAgents = agents.length > 0 ? agents : await Promise.all(localAgents.ordered.map(async (entry) => {
const storePath = resolveStorePath(cfg.session?.store, { agentId: entry.id });
return {
agentId: entry.id,
name: entry.name,
isDefault: entry.id === localAgents.defaultAgentId,
heartbeat: resolveHeartbeatSummary(cfg, entry.id),
sessions: await buildSessionSummary(storePath)
};
}));
const displayAgents = opts.verbose ? resolvedAgents : resolvedAgents.filter((agent) => agent.agentId === defaultAgentId);
const channelBindings = buildChannelAccountBindings(cfg);
const displayPlugins = listReadOnlyChannelPluginsForConfig(cfg, { includeSetupFallbackPlugins: false });
if (debugEnabled) {
runtime.log(info("[debug] local channel accounts"));
for (const plugin of displayPlugins) {
const accountIds = plugin.config.listAccountIds(cfg);
const defaultAccountId = resolveChannelDefaultAccountId({
plugin,
cfg,
accountIds
});
runtime.log(` ${plugin.id}: accounts=${accountIds.join(", ") || "(none)"} default=${defaultAccountId}`);
for (const accountId of accountIds) {
const { snapshotAccount, configured, diagnostics } = await resolveHealthAccountContext({
plugin,
cfg,
accountId
});
const record = asNullableRecord(snapshotAccount);
const tokenSource = record && typeof record.tokenSource === "string" ? record.tokenSource : void 0;
runtime.log(` - ${accountId}: configured=${configured}${tokenSource ? ` tokenSource=${tokenSource}` : ""}`);
for (const diagnostic of diagnostics) runtime.log(` ! ${diagnostic}`);
}
}
runtime.log(info("[debug] bindings map"));
for (const [channelId, byAgent] of channelBindings.entries()) {
const entries = Array.from(byAgent.entries()).map(([agentId, ids]) => `${agentId}=[${ids.join(", ")}]`);
runtime.log(` ${channelId}: ${entries.join(" ")}`);
}
runtime.log(info("[debug] gateway channel probes"));
for (const [channelId, channelSummary] of Object.entries(summary.channels ?? {})) {
const accounts = channelSummary.accounts ?? {};
const probes = Object.entries(accounts).map(([accountId, accountSummary]) => {
const probe = asNullableRecord(accountSummary.probe);
const bot = probe ? asNullableRecord(probe.bot) : null;
return `${accountId}=${(bot && typeof bot.username === "string" ? bot.username : null) ?? "(no bot)"}`;
});
runtime.log(` ${channelId}: ${probes.join(", ") || "(none)"}`);
}
}
const channelAccountFallbacks = Object.fromEntries(displayPlugins.map((plugin) => {
const accountIds = plugin.config.listAccountIds(cfg);
const preferred = resolvePreferredAccountId({
accountIds,
defaultAccountId: resolveChannelDefaultAccountId({
plugin,
cfg,
accountIds
}),
boundAccounts: channelBindings.get(plugin.id)?.get(defaultAgentId) ?? []
});
return [plugin.id, [preferred]];
}));
const accountIdsByChannel = (() => {
const entries = displayAgents.length > 0 ? displayAgents : resolvedAgents;
const byChannel = {};
for (const [channelId, byAgent] of channelBindings.entries()) {
const accountIds = [];
for (const agent of entries) {
const ids = byAgent.get(agent.agentId) ?? [];
for (const id of ids) if (!accountIds.includes(id)) accountIds.push(id);
}
if (accountIds.length > 0) byChannel[channelId] = accountIds;
}
for (const [channelId, fallbackIds] of Object.entries(channelAccountFallbacks)) if (!byChannel[channelId] || byChannel[channelId].length === 0) byChannel[channelId] = fallbackIds;
return byChannel;
})();
const channelLines = Object.keys(accountIdsByChannel).length > 0 ? formatHealthChannelLines(summary, {
accountMode: opts.verbose ? "all" : "default",
accountIdsByChannel
}) : formatHealthChannelLines(summary, { accountMode: opts.verbose ? "all" : "default" });
for (const line of channelLines) runtime.log(styleHealthChannelLine(line, rich));
const eventLoopLine = formatEventLoopHealthLine(summary);
if (eventLoopLine) runtime.log(styleHealthChannelLine(eventLoopLine, rich));
const modelPricingLine = formatModelPricingHealthLine(summary);
if (modelPricingLine) runtime.log(styleHealthChannelLine(modelPricingLine, rich));
const contextEngineLine = formatContextEngineHealthLine(summary);
if (contextEngineLine) runtime.log(styleHealthChannelLine(contextEngineLine, rich));
for (const plugin of displayPlugins) {
const channelSummary = summary.channels?.[plugin.id];
if (!channelSummary || channelSummary.linked !== true) continue;
if (!plugin.status?.logSelfId) continue;
const boundAccounts = channelBindings.get(plugin.id)?.get(defaultAgentId) ?? [];
const accountIds = plugin.config.listAccountIds(cfg);
const accountId = resolvePreferredAccountId({
accountIds,
defaultAccountId: resolveChannelDefaultAccountId({
plugin,
cfg,
accountIds
}),
boundAccounts
});
const accountContext = await resolveHealthAccountContext({
plugin,
cfg,
accountId
});
if (!accountContext.enabled || !accountContext.configured) continue;
if (accountContext.diagnostics.length > 0) continue;
try {
plugin.status.logSelfId({
account: accountContext.probeAccount,
cfg,
runtime,
includeChannelPrefix: true
});
} catch (error) {
debugHealth("logSelfId.failed", {
channel: plugin.id,
accountId,
error: formatErrorMessage(error)
});
}
}
if (resolvedAgents.length > 0) {
const agentLabels = resolvedAgents.map((agent) => agent.isDefault ? `${agent.agentId} (default)` : agent.agentId);
runtime.log(info(`Agents: ${agentLabels.join(", ")}`));
}
const heartbeatParts = displayAgents.map((agent) => {
const everyMs = agent.heartbeat?.everyMs;
return `${everyMs ? formatDurationParts(everyMs) : "disabled"} (${agent.agentId})`;
}).filter(Boolean);
if (heartbeatParts.length > 0) runtime.log(info(`Heartbeat interval: ${heartbeatParts.join(", ")}`));
if (displayAgents.length === 0) {
runtime.log(info(`Session store: ${summary.sessions.path} (${summary.sessions.count} entries)`));
if (summary.sessions.recent.length > 0) for (const r of summary.sessions.recent) runtime.log(`- ${r.key} (${r.updatedAt ? `${Math.round((Date.now() - r.updatedAt) / 6e4)}m ago` : "no activity"})`);
} else for (const agent of displayAgents) {
runtime.log(info(`Session store (${agent.agentId}): ${agent.sessions.path} (${agent.sessions.count} entries)`));
if (agent.sessions.recent.length > 0) for (const r of agent.sessions.recent) runtime.log(`- ${r.key} (${r.updatedAt ? `${Math.round((Date.now() - r.updatedAt) / 6e4)}m ago` : "no activity"})`);
}
}
}
async function readBestEffortHealthConfig() {
const { readBestEffortConfig } = await loadConfigRuntime();
return await readBestEffortConfig();
}
async function readRuntimeHealthConfig() {
const { getRuntimeConfig } = await loadConfigRuntime();
return getRuntimeConfig();
}
//#endregion
export { healthCommand as i, formatModelPricingHealthLine as n, getHealthSnapshot as r, formatContextEngineHealthLine as t };