CLI: keep diagnostics on explicit best-effort config

This commit is contained in:
Vincent Koc
2026-03-07 10:00:38 -08:00
parent 5c12fea0de
commit 7bd63ecade
13 changed files with 62 additions and 43 deletions

View File

@@ -4,7 +4,7 @@ import {
isGatewayDaemonRuntime,
} from "../../commands/daemon-runtime.js";
import { resolveGatewayInstallToken } from "../../commands/gateway-install-token.js";
import { loadConfig, resolveGatewayPort } from "../../config/config.js";
import { readBestEffortConfig, resolveGatewayPort } from "../../config/config.js";
import { resolveIsNixMode } from "../../config/paths.js";
import { resolveGatewayService } from "../../daemon/service.js";
import { defaultRuntime } from "../../runtime.js";
@@ -26,7 +26,7 @@ export async function runDaemonInstall(opts: DaemonInstallOptions) {
return;
}
const cfg = loadConfig();
const cfg = await readBestEffortConfig();
const portOverride = parsePort(opts.port);
if (opts.port !== undefined && portOverride === null) {
fail("Invalid port");

View File

@@ -1,5 +1,5 @@
import type { Writable } from "node:stream";
import { loadConfig } from "../../config/config.js";
import { readBestEffortConfig } from "../../config/config.js";
import { resolveIsNixMode } from "../../config/paths.js";
import { checkTokenDrift } from "../../daemon/service-audit.js";
import type { GatewayService } from "../../daemon/service.js";
@@ -283,7 +283,7 @@ export async function runServiceRestart(params: {
try {
const command = await params.service.readCommand(process.env);
const serviceToken = command?.environment?.OPENCLAW_GATEWAY_TOKEN;
const cfg = loadConfig();
const cfg = await readBestEffortConfig();
const configToken = resolveGatewayCredentialsFromConfig({
cfg,
env: process.env,

View File

@@ -1,4 +1,4 @@
import { loadConfig, resolveGatewayPort } from "../../config/config.js";
import { readBestEffortConfig, resolveGatewayPort } from "../../config/config.js";
import { resolveGatewayService } from "../../daemon/service.js";
import { defaultRuntime } from "../../runtime.js";
import { theme } from "../../terminal/theme.js";
@@ -32,7 +32,7 @@ async function resolveGatewayRestartPort() {
} as NodeJS.ProcessEnv;
const portFromArgs = parsePortFromArgs(command?.programArguments);
return portFromArgs ?? resolveGatewayPort(loadConfig(), mergedEnv);
return portFromArgs ?? resolveGatewayPort(await readBestEffortConfig(), mergedEnv);
}
export async function runDaemonUninstall(opts: DaemonLifecycleOptions = {}) {
@@ -70,8 +70,8 @@ export async function runDaemonStop(opts: DaemonLifecycleOptions = {}) {
export async function runDaemonRestart(opts: DaemonLifecycleOptions = {}): Promise<boolean> {
const json = Boolean(opts.json);
const service = resolveGatewayService();
const restartPort = await resolveGatewayRestartPort().catch(() =>
resolveGatewayPort(loadConfig(), process.env),
const restartPort = await resolveGatewayRestartPort().catch(async () =>
resolveGatewayPort(await readBestEffortConfig(), process.env),
);
const restartWaitMs = POST_RESTART_HEALTH_ATTEMPTS * POST_RESTART_HEALTH_DELAY_MS;
const restartWaitSeconds = Math.round(restartWaitMs / 1000);

View File

@@ -1,9 +1,11 @@
import type { Command } from "commander";
import type { OpenClawConfig } from "../../config/config.js";
import { callGateway } from "../../gateway/call.js";
import { GATEWAY_CLIENT_MODES, GATEWAY_CLIENT_NAMES } from "../../utils/message-channel.js";
import { withProgress } from "../progress.js";
export type GatewayRpcOpts = {
config?: OpenClawConfig;
url?: string;
token?: string;
password?: string;
@@ -30,6 +32,7 @@ export const callGatewayCli = async (method: string, opts: GatewayRpcOpts, param
},
async () =>
await callGateway({
config: opts.config,
url: opts.url,
token: opts.token,
password: opts.password,

View File

@@ -1,7 +1,7 @@
import type { Command } from "commander";
import { gatewayStatusCommand } from "../../commands/gateway-status.js";
import { formatHealthChannelLines, type HealthSummary } from "../../commands/health.js";
import { loadConfig } from "../../config/config.js";
import { readBestEffortConfig } from "../../config/config.js";
import { discoverGatewayBeacons } from "../../infra/bonjour-discovery.js";
import type { CostUsageSummary } from "../../infra/session-cost-usage.js";
import { resolveWideAreaDiscoveryDomain } from "../../infra/widearea-dns.js";
@@ -120,8 +120,9 @@ export function registerGatewayCli(program: Command) {
.action(async (method, opts, command) => {
await runGatewayCommand(async () => {
const rpcOpts = resolveGatewayRpcOptions(opts, command);
const config = await readBestEffortConfig();
const params = JSON.parse(String(opts.params ?? "{}"));
const result = await callGatewayCli(method, rpcOpts, params);
const result = await callGatewayCli(method, { ...rpcOpts, config }, params);
if (rpcOpts.json) {
defaultRuntime.log(JSON.stringify(result, null, 2));
return;
@@ -144,7 +145,8 @@ export function registerGatewayCli(program: Command) {
await runGatewayCommand(async () => {
const rpcOpts = resolveGatewayRpcOptions(opts, command);
const days = parseDaysOption(opts.days);
const result = await callGatewayCli("usage.cost", rpcOpts, { days });
const config = await readBestEffortConfig();
const result = await callGatewayCli("usage.cost", { ...rpcOpts, config }, { days });
if (rpcOpts.json) {
defaultRuntime.log(JSON.stringify(result, null, 2));
return;
@@ -165,7 +167,8 @@ export function registerGatewayCli(program: Command) {
.action(async (opts, command) => {
await runGatewayCommand(async () => {
const rpcOpts = resolveGatewayRpcOptions(opts, command);
const result = await callGatewayCli("health", rpcOpts);
const config = await readBestEffortConfig();
const result = await callGatewayCli("health", { ...rpcOpts, config });
if (rpcOpts.json) {
defaultRuntime.log(JSON.stringify(result, null, 2));
return;
@@ -211,7 +214,7 @@ export function registerGatewayCli(program: Command) {
.option("--json", "Output JSON", false)
.action(async (opts: GatewayDiscoverOpts) => {
await runGatewayCommand(async () => {
const cfg = loadConfig();
const cfg = await readBestEffortConfig();
const wideAreaDomain = resolveWideAreaDiscoveryDomain({
configDomain: cfg.discovery?.wideArea?.domain,
});

View File

@@ -30,7 +30,7 @@ const shouldEagerRegisterSubcommands = (_argv: string[]) => {
const loadConfig = async (): Promise<OpenClawConfig> => {
const mod = await import("../../config/config.js");
return mod.loadConfig();
return await mod.readBestEffortConfig();
};
// Note for humans and agents:

View File

@@ -126,8 +126,8 @@ export async function runCli(argv: string[] = process.argv) {
if (!shouldSkipPluginRegistration) {
// Register plugin CLI commands before parsing
const { registerPluginCliCommands } = await import("../plugins/cli.js");
const { loadConfig } = await import("../config/config.js");
registerPluginCliCommands(program, loadConfig());
const { readBestEffortConfig } = await import("../config/config.js");
registerPluginCliCommands(program, await readBestEffortConfig());
}
await program.parseAsync(parseArgv);

View File

@@ -1,5 +1,5 @@
import { withProgress } from "../cli/progress.js";
import { loadConfig, resolveGatewayPort } from "../config/config.js";
import { readBestEffortConfig, resolveGatewayPort } from "../config/config.js";
import { probeGateway } from "../gateway/probe.js";
import { discoverGatewayBeacons } from "../infra/bonjour-discovery.js";
import { resolveSshConfig } from "../infra/ssh-config.js";
@@ -35,7 +35,7 @@ export async function gatewayStatusCommand(
runtime: RuntimeEnv,
) {
const startedAt = Date.now();
const cfg = loadConfig();
const cfg = await readBestEffortConfig();
const rich = isRich() && opts.json !== true;
const overallTimeoutMs = parseTimeoutMs(opts.timeout, 3000);
const wideAreaDomain = resolveWideAreaDiscoveryDomain({

View File

@@ -4,7 +4,7 @@ import { getChannelPlugin, listChannelPlugins } from "../channels/plugins/index.
import type { ChannelAccountSnapshot } from "../channels/plugins/types.js";
import { withProgress } from "../cli/progress.js";
import type { OpenClawConfig } from "../config/config.js";
import { loadConfig } from "../config/config.js";
import { loadConfig, readBestEffortConfig } from "../config/config.js";
import { loadSessionStore, resolveStorePath } from "../config/sessions.js";
import { buildGatewayConnectionDetails, callGateway } from "../gateway/call.js";
import { info } from "../globals.js";
@@ -526,7 +526,7 @@ export async function healthCommand(
opts: { json?: boolean; timeoutMs?: number; verbose?: boolean; config?: OpenClawConfig },
runtime: RuntimeEnv,
) {
const cfg = opts.config ?? loadConfig();
const cfg = opts.config ?? (await readBestEffortConfig());
// Always query the running gateway; do not open a direct Baileys socket here.
const summary = await withProgress(
{

View File

@@ -3,7 +3,11 @@ import { formatCliCommand } from "../cli/command-format.js";
import { resolveCommandSecretRefsViaGateway } from "../cli/command-secret-gateway.js";
import { getStatusCommandSecretTargetIds } from "../cli/command-secret-targets.js";
import { withProgress } from "../cli/progress.js";
import { loadConfig, readConfigFileSnapshot, resolveGatewayPort } from "../config/config.js";
import {
readBestEffortConfig,
readConfigFileSnapshot,
resolveGatewayPort,
} from "../config/config.js";
import { readLastGatewayErrorLine } from "../daemon/diagnostics.js";
import { resolveNodeService } from "../daemon/node-service.js";
import type { GatewayService } from "../daemon/service.js";
@@ -38,7 +42,7 @@ export async function statusAllCommand(
): Promise<void> {
await withProgress({ label: "Scanning status --all…", total: 11 }, async (progress) => {
progress.setLabel("Loading config…");
const loadedRaw = loadConfig();
const loadedRaw = await readBestEffortConfig();
const { resolvedConfig: cfg } = await resolveCommandSecretRefsViaGateway({
config: loadedRaw,
commandName: "status --all",

View File

@@ -1,6 +1,7 @@
import fs from "node:fs/promises";
import path from "node:path";
import { resolveAgentWorkspaceDir } from "../agents/agent-scope.js";
import type { OpenClawConfig } from "../config/config.js";
import { loadConfig } from "../config/config.js";
import { loadSessionStore, resolveStorePath } from "../config/sessions.js";
import { listAgentsForGateway } from "../gateway/session-utils.js";
@@ -16,6 +17,13 @@ export type AgentLocalStatus = {
lastActiveAgeMs: number | null;
};
type AgentLocalStatusesResult = {
defaultId: string;
agents: AgentLocalStatus[];
totalSessions: number;
bootstrapPendingCount: number;
};
async function fileExists(p: string): Promise<boolean> {
try {
await fs.access(p);
@@ -25,13 +33,9 @@ async function fileExists(p: string): Promise<boolean> {
}
}
export async function getAgentLocalStatuses(): Promise<{
defaultId: string;
agents: AgentLocalStatus[];
totalSessions: number;
bootstrapPendingCount: number;
}> {
const cfg = loadConfig();
export async function getAgentLocalStatuses(
cfg: OpenClawConfig = loadConfig(),
): Promise<AgentLocalStatusesResult> {
const agentList = listAgentsForGateway(cfg);
const now = Date.now();

View File

@@ -153,6 +153,7 @@ export async function statusCommand(
method: "health",
params: { probe: true },
timeoutMs: opts.timeoutMs,
config: scan.cfg,
}),
)
: undefined;
@@ -162,6 +163,7 @@ export async function statusCommand(
method: "last-heartbeat",
params: {},
timeoutMs: opts.timeoutMs,
config: scan.cfg,
}).catch(() => null)
: null;
@@ -219,7 +221,7 @@ export async function statusCommand(
const warn = (value: string) => (rich ? theme.warn(value) : value);
if (opts.verbose) {
const details = buildGatewayConnectionDetails();
const details = buildGatewayConnectionDetails({ config: scan.cfg });
runtime.log(info("Gateway connection:"));
for (const line of details.message.split("\n")) {
runtime.log(` ${line}`);

View File

@@ -1,7 +1,8 @@
import { resolveCommandSecretRefsViaGateway } from "../cli/command-secret-gateway.js";
import { getStatusCommandSecretTargetIds } from "../cli/command-secret-targets.js";
import { withProgress } from "../cli/progress.js";
import { loadConfig } from "../config/config.js";
import type { OpenClawConfig } from "../config/config.js";
import { readBestEffortConfig } from "../config/config.js";
import { buildGatewayConnectionDetails, callGateway } from "../gateway/call.js";
import { normalizeControlUiBasePath } from "../gateway/control-ui-shared.js";
import { probeGateway } from "../gateway/probe.js";
@@ -59,7 +60,7 @@ function unwrapDeferredResult<T>(result: DeferredResult<T>): T {
return result.value;
}
function resolveMemoryPluginStatus(cfg: ReturnType<typeof loadConfig>): MemoryPluginStatus {
function resolveMemoryPluginStatus(cfg: OpenClawConfig): MemoryPluginStatus {
const pluginsEnabled = cfg.plugins?.enabled !== false;
if (!pluginsEnabled) {
return { enabled: false, slot: null, reason: "plugins disabled" };
@@ -72,10 +73,10 @@ function resolveMemoryPluginStatus(cfg: ReturnType<typeof loadConfig>): MemoryPl
}
async function resolveGatewayProbeSnapshot(params: {
cfg: ReturnType<typeof loadConfig>;
cfg: OpenClawConfig;
opts: { timeoutMs?: number; all?: boolean };
}): Promise<GatewayProbeSnapshot> {
const gatewayConnection = buildGatewayConnectionDetails();
const gatewayConnection = buildGatewayConnectionDetails({ config: params.cfg });
const isRemoteMode = params.cfg.gateway?.mode === "remote";
const remoteUrlRaw =
typeof params.cfg.gateway?.remote?.url === "string" ? params.cfg.gateway.remote.url : "";
@@ -107,6 +108,7 @@ async function resolveGatewayProbeSnapshot(params: {
}
async function resolveChannelsStatus(params: {
cfg: OpenClawConfig;
gatewayReachable: boolean;
opts: { timeoutMs?: number; all?: boolean };
}) {
@@ -114,6 +116,7 @@ async function resolveChannelsStatus(params: {
return null;
}
return await callGateway({
config: params.cfg,
method: "channels.status",
params: {
probe: false,
@@ -124,8 +127,8 @@ async function resolveChannelsStatus(params: {
}
export type StatusScanResult = {
cfg: ReturnType<typeof loadConfig>;
sourceConfig: ReturnType<typeof loadConfig>;
cfg: OpenClawConfig;
sourceConfig: OpenClawConfig;
secretDiagnostics: string[];
osSummary: ReturnType<typeof resolveOsSummary>;
tailscaleMode: string;
@@ -152,7 +155,7 @@ export type StatusScanResult = {
};
async function resolveMemoryStatusSnapshot(params: {
cfg: ReturnType<typeof loadConfig>;
cfg: OpenClawConfig;
agentStatus: Awaited<ReturnType<typeof getAgentLocalStatuses>>;
memoryPlugin: MemoryPluginStatus;
}): Promise<MemoryStatusSnapshot | null> {
@@ -180,7 +183,7 @@ async function scanStatusJsonFast(opts: {
timeoutMs?: number;
all?: boolean;
}): Promise<StatusScanResult> {
const loadedRaw = loadConfig();
const loadedRaw = await readBestEffortConfig();
const { resolvedConfig: cfg, diagnostics: secretDiagnostics } =
await resolveCommandSecretRefsViaGateway({
config: loadedRaw,
@@ -196,7 +199,7 @@ async function scanStatusJsonFast(opts: {
fetchGit: true,
includeRegistry: true,
});
const agentStatusPromise = getAgentLocalStatuses();
const agentStatusPromise = getAgentLocalStatuses(cfg);
const summaryPromise = getStatusSummary({ config: cfg, sourceConfig: loadedRaw });
const tailscaleDnsPromise =
@@ -232,7 +235,7 @@ async function scanStatusJsonFast(opts: {
const gatewaySelf = gatewayProbe?.presence
? pickGatewaySelfPresence(gatewayProbe.presence)
: null;
const channelsStatusPromise = resolveChannelsStatus({ gatewayReachable, opts });
const channelsStatusPromise = resolveChannelsStatus({ cfg, gatewayReachable, opts });
const memoryPlugin = resolveMemoryPluginStatus(cfg);
const memoryPromise = resolveMemoryStatusSnapshot({ cfg, agentStatus, memoryPlugin });
const [channelsStatus, memory] = await Promise.all([channelsStatusPromise, memoryPromise]);
@@ -283,7 +286,7 @@ export async function scanStatus(
},
async (progress) => {
progress.setLabel("Loading config…");
const loadedRaw = loadConfig();
const loadedRaw = await readBestEffortConfig();
const { resolvedConfig: cfg, diagnostics: secretDiagnostics } =
await resolveCommandSecretRefsViaGateway({
config: loadedRaw,
@@ -307,7 +310,7 @@ export async function scanStatus(
includeRegistry: true,
}),
);
const agentStatusPromise = deferResult(getAgentLocalStatuses());
const agentStatusPromise = deferResult(getAgentLocalStatuses(cfg));
const summaryPromise = deferResult(
getStatusSummary({ config: cfg, sourceConfig: loadedRaw }),
);
@@ -345,7 +348,7 @@ export async function scanStatus(
progress.tick();
progress.setLabel("Querying channel status…");
const channelsStatus = await resolveChannelsStatus({ gatewayReachable, opts });
const channelsStatus = await resolveChannelsStatus({ cfg, gatewayReachable, opts });
const channelIssues = channelsStatus ? collectChannelStatusIssues(channelsStatus) : [];
progress.tick();