mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-19 06:14:52 +00:00
fix: show deep status config warnings
This commit is contained in:
@@ -72,8 +72,14 @@ const resolveStateDir = vi.fn(
|
||||
const resolveConfigPath = vi.fn((env: NodeJS.ProcessEnv, stateDir: string) => {
|
||||
return env.OPENCLAW_CONFIG_PATH ?? `${stateDir}/openclaw.json`;
|
||||
});
|
||||
const createConfigIOCalls = vi.fn((configPath: string, pluginValidation?: "full" | "skip") => ({
|
||||
configPath,
|
||||
pluginValidation,
|
||||
}));
|
||||
const readConfigFileSnapshotCalls = vi.fn((configPath: string) => configPath);
|
||||
const loadConfigCalls = vi.fn((configPath: string) => configPath);
|
||||
let daemonConfigWarnings: Array<{ path: string; message: string }> = [];
|
||||
let cliConfigWarnings: Array<{ path: string; message: string }> = [];
|
||||
let daemonLoadedConfig: Record<string, unknown> = {
|
||||
gateway: {
|
||||
bind: "lan",
|
||||
@@ -88,9 +94,17 @@ let cliLoadedConfig: Record<string, unknown> = {
|
||||
};
|
||||
|
||||
vi.mock("../../config/config.js", () => ({
|
||||
createConfigIO: ({ configPath }: { configPath: string }) => {
|
||||
createConfigIO: ({
|
||||
configPath,
|
||||
pluginValidation,
|
||||
}: {
|
||||
configPath: string;
|
||||
pluginValidation?: "full" | "skip";
|
||||
}) => {
|
||||
const isDaemon = configPath.includes("/openclaw-daemon/");
|
||||
const runtimeConfig = isDaemon ? daemonLoadedConfig : cliLoadedConfig;
|
||||
const warnings = isDaemon ? daemonConfigWarnings : cliConfigWarnings;
|
||||
createConfigIOCalls(configPath, pluginValidation);
|
||||
return {
|
||||
readConfigFileSnapshot: async () => {
|
||||
readConfigFileSnapshotCalls(configPath);
|
||||
@@ -99,6 +113,7 @@ vi.mock("../../config/config.js", () => ({
|
||||
exists: true,
|
||||
valid: true,
|
||||
issues: [],
|
||||
warnings: pluginValidation === "full" ? warnings : [],
|
||||
runtimeConfig,
|
||||
config: runtimeConfig,
|
||||
};
|
||||
@@ -186,11 +201,14 @@ describe("gatherDaemonStatus", () => {
|
||||
delete process.env.DAEMON_GATEWAY_TOKEN;
|
||||
delete process.env.DAEMON_GATEWAY_PASSWORD;
|
||||
callGatewayStatusProbe.mockClear();
|
||||
createConfigIOCalls.mockClear();
|
||||
loadGatewayTlsRuntime.mockClear();
|
||||
inspectGatewayRestart.mockClear();
|
||||
readGatewayRestartHandoffSync.mockClear();
|
||||
readConfigFileSnapshotCalls.mockClear();
|
||||
loadConfigCalls.mockClear();
|
||||
daemonConfigWarnings = [];
|
||||
cliConfigWarnings = [];
|
||||
daemonLoadedConfig = {
|
||||
gateway: {
|
||||
bind: "lan",
|
||||
@@ -479,6 +497,51 @@ describe("gatherDaemonStatus", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("uses full plugin-aware config validation for deep status", async () => {
|
||||
const tmp = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-status-config-"));
|
||||
const configPath = path.join(tmp, "openclaw.json");
|
||||
await fs.writeFile(
|
||||
configPath,
|
||||
JSON.stringify({
|
||||
gateway: {
|
||||
bind: "loopback",
|
||||
},
|
||||
}),
|
||||
);
|
||||
process.env.OPENCLAW_STATE_DIR = tmp;
|
||||
process.env.OPENCLAW_CONFIG_PATH = configPath;
|
||||
cliLoadedConfig = {
|
||||
gateway: {
|
||||
bind: "loopback",
|
||||
},
|
||||
};
|
||||
cliConfigWarnings = [
|
||||
{
|
||||
path: "plugins.entries.test-bad-plugin",
|
||||
message:
|
||||
"plugin test-bad-plugin: channel plugin manifest declares test-bad-plugin without channelConfigs metadata",
|
||||
},
|
||||
];
|
||||
serviceReadCommand.mockResolvedValueOnce({
|
||||
programArguments: ["/bin/node", "cli", "gateway", "--port", "19001"],
|
||||
});
|
||||
|
||||
try {
|
||||
const status = await gatherDaemonStatus({
|
||||
rpc: {},
|
||||
probe: false,
|
||||
deep: true,
|
||||
});
|
||||
|
||||
expect(createConfigIOCalls).toHaveBeenCalledWith(configPath, "full");
|
||||
expect(readConfigFileSnapshotCalls).toHaveBeenCalledWith(configPath);
|
||||
expect(status.config?.cli.warnings).toEqual(cliConfigWarnings);
|
||||
expect(status.config?.daemon).toBe(status.config?.cli);
|
||||
} finally {
|
||||
await fs.rm(tmp, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("resolves daemon gateway auth password SecretRef values before probing", async () => {
|
||||
daemonLoadedConfig = {
|
||||
gateway: {
|
||||
|
||||
@@ -44,6 +44,7 @@ type ConfigSummary = {
|
||||
exists: boolean;
|
||||
valid: boolean;
|
||||
issues?: Array<{ path: string; message: string }>;
|
||||
warnings?: ConfigFileSnapshot["warnings"];
|
||||
controlUi?: GatewayControlUiConfig;
|
||||
};
|
||||
|
||||
@@ -198,11 +199,16 @@ async function readFastStatusConfig(configPath: string): Promise<StatusConfigRea
|
||||
async function readFullStatusConfig(params: {
|
||||
env: NodeJS.ProcessEnv;
|
||||
configPath: string;
|
||||
pluginValidation?: "full" | "skip";
|
||||
}): Promise<StatusConfigRead> {
|
||||
const io = createConfigIO({
|
||||
env: params.env,
|
||||
configPath: params.configPath,
|
||||
pluginValidation: "skip",
|
||||
pluginValidation: params.pluginValidation ?? "skip",
|
||||
logger: {
|
||||
error: () => {},
|
||||
warn: () => {},
|
||||
},
|
||||
});
|
||||
const snapshot = await io.readConfigFileSnapshot().catch(() => null);
|
||||
const cfg = resolveSnapshotRuntimeConfig(snapshot) ?? io.loadConfig();
|
||||
@@ -212,6 +218,7 @@ async function readFullStatusConfig(params: {
|
||||
exists: snapshot?.exists ?? false,
|
||||
valid: snapshot?.valid ?? true,
|
||||
...(snapshot?.issues?.length ? { issues: snapshot.issues } : {}),
|
||||
...(snapshot?.warnings?.length ? { warnings: snapshot.warnings } : {}),
|
||||
controlUi: cfg.gateway?.controlUi,
|
||||
},
|
||||
cfg,
|
||||
@@ -222,12 +229,14 @@ async function readFullStatusConfig(params: {
|
||||
async function readStatusConfig(params: {
|
||||
env: NodeJS.ProcessEnv;
|
||||
configPath: string;
|
||||
deep?: boolean;
|
||||
}): Promise<StatusConfigRead> {
|
||||
return (
|
||||
(await readFastStatusConfig(params.configPath)) ??
|
||||
(params.deep ? null : await readFastStatusConfig(params.configPath)) ??
|
||||
(await readFullStatusConfig({
|
||||
env: params.env,
|
||||
configPath: params.configPath,
|
||||
pluginValidation: params.deep ? "full" : "skip",
|
||||
}))
|
||||
);
|
||||
}
|
||||
@@ -323,6 +332,7 @@ function resolveCliStatusSummary(argv: string[] = process.argv): CliStatusSummar
|
||||
|
||||
async function loadDaemonConfigContext(
|
||||
serviceEnv?: Record<string, string>,
|
||||
opts: { deep?: boolean } = {},
|
||||
): Promise<DaemonConfigContext> {
|
||||
const mergedDaemonEnv = {
|
||||
...(process.env as Record<string, string | undefined>),
|
||||
@@ -338,6 +348,7 @@ async function loadDaemonConfigContext(
|
||||
const cliConfigRead = await readStatusConfig({
|
||||
env: process.env,
|
||||
configPath: cliConfigPath,
|
||||
deep: opts.deep,
|
||||
});
|
||||
const sharesDaemonConfigContext =
|
||||
sameConfigPath && (cliConfigRead.mode === "fast" || !serviceEnv);
|
||||
@@ -346,6 +357,7 @@ async function loadDaemonConfigContext(
|
||||
: await readStatusConfig({
|
||||
env: mergedDaemonEnv as NodeJS.ProcessEnv,
|
||||
configPath: daemonConfigPath,
|
||||
deep: opts.deep,
|
||||
});
|
||||
|
||||
return {
|
||||
@@ -477,7 +489,7 @@ export async function gatherDaemonStatus(
|
||||
cliConfigSummary,
|
||||
daemonConfigSummary,
|
||||
configMismatch,
|
||||
} = await loadDaemonConfigContext(command?.environment);
|
||||
} = await loadDaemonConfigContext(command?.environment, { deep: opts.deep });
|
||||
const { gateway, daemonPort, cliPort, probeUrlOverride } = await resolveGatewayStatusSummary({
|
||||
cliCfg,
|
||||
daemonCfg,
|
||||
|
||||
@@ -290,4 +290,40 @@ describe("printDaemonStatus", () => {
|
||||
tlsEnabled: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("prints deep config warnings", () => {
|
||||
printDaemonStatus(
|
||||
{
|
||||
service: {
|
||||
label: "LaunchAgent",
|
||||
loaded: true,
|
||||
loadedText: "loaded",
|
||||
notLoadedText: "not loaded",
|
||||
runtime: { status: "running", pid: 8000 },
|
||||
},
|
||||
config: {
|
||||
cli: {
|
||||
path: "/tmp/openclaw-cli/openclaw.json",
|
||||
exists: true,
|
||||
valid: true,
|
||||
warnings: [
|
||||
{
|
||||
path: "plugins.entries.test-bad-plugin",
|
||||
message:
|
||||
"plugin test-bad-plugin: channel plugin manifest declares test-bad-plugin without channelConfigs metadata",
|
||||
},
|
||||
],
|
||||
},
|
||||
mismatch: false,
|
||||
},
|
||||
extraServices: [],
|
||||
},
|
||||
{ json: false },
|
||||
);
|
||||
|
||||
expect(runtime.error).toHaveBeenCalledWith(expect.stringContaining("Config warnings:"));
|
||||
expect(runtime.error).toHaveBeenCalledWith(
|
||||
expect.stringContaining("without channelConfigs metadata"),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -132,6 +132,14 @@ export function printDaemonStatus(status: DaemonStatus, opts: { json: boolean })
|
||||
);
|
||||
}
|
||||
}
|
||||
if (status.config.cli.warnings?.length) {
|
||||
defaultRuntime.error(warnText("Config warnings:"));
|
||||
for (const warning of status.config.cli.warnings.slice(0, 5)) {
|
||||
defaultRuntime.error(
|
||||
warnText(formatConfigIssueLine(warning, "-", { normalizeRoot: true })),
|
||||
);
|
||||
}
|
||||
}
|
||||
if (status.config.daemon) {
|
||||
const daemonCfg = `${shortenHomePath(status.config.daemon.path)}${status.config.daemon.exists ? "" : " (missing)"}${status.config.daemon.valid ? "" : " (invalid)"}`;
|
||||
defaultRuntime.log(`${label("Config (service):")} ${infoText(daemonCfg)}`);
|
||||
@@ -142,6 +150,18 @@ export function printDaemonStatus(status: DaemonStatus, opts: { json: boolean })
|
||||
);
|
||||
}
|
||||
}
|
||||
if (status.config.daemon !== status.config.cli && status.config.daemon.warnings?.length) {
|
||||
const warningsLabel =
|
||||
status.config.daemon.path === status.config.cli.path
|
||||
? "Config warnings:"
|
||||
: "Service config warnings:";
|
||||
defaultRuntime.error(warnText(warningsLabel));
|
||||
for (const warning of status.config.daemon.warnings.slice(0, 5)) {
|
||||
defaultRuntime.error(
|
||||
warnText(formatConfigIssueLine(warning, "-", { normalizeRoot: true })),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (status.config.mismatch) {
|
||||
defaultRuntime.error(
|
||||
|
||||
Reference in New Issue
Block a user