fix: resolve live config paths in status and gateway metadata (#39952)
* fix: resolve live config paths in status and gateway metadata * fix: resolve remaining runtime config path references * test: cover gateway config.set config path response
This commit is contained in:
@@ -25,7 +25,7 @@ import {
|
||||
} from "../../agents/model-selection.js";
|
||||
import { formatCliCommand } from "../../cli/command-format.js";
|
||||
import { withProgressTotals } from "../../cli/progress.js";
|
||||
import { CONFIG_PATH } from "../../config/config.js";
|
||||
import { createConfigIO } from "../../config/config.js";
|
||||
import {
|
||||
resolveAgentModelFallbackValues,
|
||||
resolveAgentModelPrimaryValue,
|
||||
@@ -77,6 +77,7 @@ export async function modelsStatusCommand(
|
||||
if (opts.plain && opts.probe) {
|
||||
throw new Error("--probe cannot be used with --plain output.");
|
||||
}
|
||||
const configPath = createConfigIO().configPath;
|
||||
const cfg = await loadModelsConfig({ commandName: "models status", runtime });
|
||||
const agentId = resolveKnownAgentId({ cfg, rawAgentId: opts.agent });
|
||||
const agentDir = agentId ? resolveAgentDir(cfg, agentId) : resolveOpenClawAgentDir();
|
||||
@@ -326,7 +327,7 @@ export async function modelsStatusCommand(
|
||||
runtime.log(
|
||||
JSON.stringify(
|
||||
{
|
||||
configPath: CONFIG_PATH,
|
||||
configPath,
|
||||
...(agentId ? { agentId } : {}),
|
||||
agentDir,
|
||||
defaultModel: defaultLabel,
|
||||
@@ -389,7 +390,7 @@ export async function modelsStatusCommand(
|
||||
rawModel && rawModel !== resolvedLabel ? `${resolvedLabel} (from ${rawModel})` : resolvedLabel;
|
||||
|
||||
runtime.log(
|
||||
`${label("Config")}${colorize(rich, theme.muted, ":")} ${colorize(rich, theme.info, shortenHomePath(CONFIG_PATH))}`,
|
||||
`${label("Config")}${colorize(rich, theme.muted, ":")} ${colorize(rich, theme.info, shortenHomePath(configPath))}`,
|
||||
);
|
||||
runtime.log(
|
||||
`${label("Agent dir")}${colorize(rich, theme.muted, ":")} ${colorize(
|
||||
|
||||
@@ -64,6 +64,9 @@ const mocks = vi.hoisted(() => {
|
||||
getCustomProviderApiKey: vi.fn().mockReturnValue(undefined),
|
||||
getShellEnvAppliedKeys: vi.fn().mockReturnValue(["OPENAI_API_KEY", "ANTHROPIC_OAUTH_TOKEN"]),
|
||||
shouldEnableShellEnvFallback: vi.fn().mockReturnValue(true),
|
||||
createConfigIO: vi.fn().mockReturnValue({
|
||||
configPath: "/tmp/openclaw-dev/openclaw.json",
|
||||
}),
|
||||
loadConfig: vi.fn().mockReturnValue({
|
||||
agents: {
|
||||
defaults: {
|
||||
@@ -115,6 +118,7 @@ vi.mock("../../config/config.js", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("../../config/config.js")>();
|
||||
return {
|
||||
...actual,
|
||||
createConfigIO: mocks.createConfigIO,
|
||||
loadConfig: mocks.loadConfig,
|
||||
};
|
||||
});
|
||||
@@ -200,6 +204,7 @@ describe("modelsStatusCommand auth overview", () => {
|
||||
|
||||
expect(mocks.resolveOpenClawAgentDir).toHaveBeenCalled();
|
||||
expect(payload.defaultModel).toBe("anthropic/claude-opus-4-5");
|
||||
expect(payload.configPath).toBe("/tmp/openclaw-dev/openclaw.json");
|
||||
expect(payload.auth.storePath).toBe("/tmp/openclaw-agent/auth-profiles.json");
|
||||
expect(payload.auth.shellEnvFallback.enabled).toBe(true);
|
||||
expect(payload.auth.shellEnvFallback.appliedKeys).toContain("OPENAI_API_KEY");
|
||||
|
||||
25
src/config/logging.test.ts
Normal file
25
src/config/logging.test.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
createConfigIO: vi.fn().mockReturnValue({
|
||||
configPath: "/tmp/openclaw-dev/openclaw.json",
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock("./io.js", () => ({
|
||||
createConfigIO: mocks.createConfigIO,
|
||||
}));
|
||||
|
||||
import { formatConfigPath, logConfigUpdated } from "./logging.js";
|
||||
|
||||
describe("config logging", () => {
|
||||
it("formats the live config path when no explicit path is provided", () => {
|
||||
expect(formatConfigPath()).toBe("/tmp/openclaw-dev/openclaw.json");
|
||||
});
|
||||
|
||||
it("logs the live config path when no explicit path is provided", () => {
|
||||
const runtime = { log: vi.fn() };
|
||||
logConfigUpdated(runtime as never);
|
||||
expect(runtime.log).toHaveBeenCalledWith("Updated /tmp/openclaw-dev/openclaw.json");
|
||||
});
|
||||
});
|
||||
@@ -1,18 +1,18 @@
|
||||
import type { RuntimeEnv } from "../runtime.js";
|
||||
import { displayPath } from "../utils.js";
|
||||
import { CONFIG_PATH } from "./paths.js";
|
||||
import { createConfigIO } from "./io.js";
|
||||
|
||||
type LogConfigUpdatedOptions = {
|
||||
path?: string;
|
||||
suffix?: string;
|
||||
};
|
||||
|
||||
export function formatConfigPath(path: string = CONFIG_PATH): string {
|
||||
export function formatConfigPath(path: string = createConfigIO().configPath): string {
|
||||
return displayPath(path);
|
||||
}
|
||||
|
||||
export function logConfigUpdated(runtime: RuntimeEnv, opts: LogConfigUpdatedOptions = {}): void {
|
||||
const path = formatConfigPath(opts.path ?? CONFIG_PATH);
|
||||
const path = formatConfigPath(opts.path ?? createConfigIO().configPath);
|
||||
const suffix = opts.suffix ? ` ${opts.suffix}` : "";
|
||||
runtime.log(`Updated ${path}${suffix}`);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { resolveAgentWorkspaceDir, resolveDefaultAgentId } from "../../agents/agent-scope.js";
|
||||
import { listChannelPlugins } from "../../channels/plugins/index.js";
|
||||
import {
|
||||
CONFIG_PATH,
|
||||
createConfigIO,
|
||||
loadConfig,
|
||||
parseConfigJson5,
|
||||
readConfigFileSnapshot,
|
||||
@@ -197,6 +197,7 @@ function buildConfigRestartSentinelPayload(params: {
|
||||
threadId: ReturnType<typeof extractDeliveryInfo>["threadId"];
|
||||
note: string | undefined;
|
||||
}): RestartSentinelPayload {
|
||||
const configPath = createConfigIO().configPath;
|
||||
return {
|
||||
kind: params.kind,
|
||||
status: "ok",
|
||||
@@ -208,7 +209,7 @@ function buildConfigRestartSentinelPayload(params: {
|
||||
doctorHint: formatDoctorNonInteractiveHint(),
|
||||
stats: {
|
||||
mode: params.mode,
|
||||
root: CONFIG_PATH,
|
||||
root: configPath,
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -323,7 +324,7 @@ export const configHandlers: GatewayRequestHandlers = {
|
||||
true,
|
||||
{
|
||||
ok: true,
|
||||
path: CONFIG_PATH,
|
||||
path: createConfigIO().configPath,
|
||||
config: redactConfigObject(parsed.config, parsed.schema.uiHints),
|
||||
},
|
||||
undefined,
|
||||
@@ -440,7 +441,7 @@ export const configHandlers: GatewayRequestHandlers = {
|
||||
true,
|
||||
{
|
||||
ok: true,
|
||||
path: CONFIG_PATH,
|
||||
path: createConfigIO().configPath,
|
||||
config: redactConfigObject(validated.config, schemaPatch.uiHints),
|
||||
restart,
|
||||
sentinel: {
|
||||
@@ -500,7 +501,7 @@ export const configHandlers: GatewayRequestHandlers = {
|
||||
true,
|
||||
{
|
||||
ok: true,
|
||||
path: CONFIG_PATH,
|
||||
path: createConfigIO().configPath,
|
||||
config: redactConfigObject(parsed.config, parsed.schema.uiHints),
|
||||
restart,
|
||||
sentinel: {
|
||||
|
||||
@@ -94,7 +94,7 @@ export function registerDefaultAuthTokenSuite(): void {
|
||||
});
|
||||
|
||||
test("connect (req) handshake returns hello-ok payload", async () => {
|
||||
const { CONFIG_PATH, STATE_DIR } = await import("../config/config.js");
|
||||
const { STATE_DIR, createConfigIO } = await import("../config/config.js");
|
||||
const ws = await openWs(port);
|
||||
|
||||
const res = await connectReq(ws);
|
||||
@@ -106,7 +106,7 @@ export function registerDefaultAuthTokenSuite(): void {
|
||||
}
|
||||
| undefined;
|
||||
expect(payload?.type).toBe("hello-ok");
|
||||
expect(payload?.snapshot?.configPath).toBe(CONFIG_PATH);
|
||||
expect(payload?.snapshot?.configPath).toBe(createConfigIO().configPath);
|
||||
expect(payload?.snapshot?.stateDir).toBe(STATE_DIR);
|
||||
|
||||
ws.close();
|
||||
|
||||
@@ -47,6 +47,31 @@ async function resetTempDir(name: string): Promise<string> {
|
||||
}
|
||||
|
||||
describe("gateway config methods", () => {
|
||||
it("round-trips config.set and returns the live config path", async () => {
|
||||
const { createConfigIO } = await import("../config/config.js");
|
||||
const current = await rpcReq<{
|
||||
raw?: unknown;
|
||||
hash?: string;
|
||||
config?: Record<string, unknown>;
|
||||
}>(requireWs(), "config.get", {});
|
||||
expect(current.ok).toBe(true);
|
||||
expect(typeof current.payload?.hash).toBe("string");
|
||||
expect(current.payload?.config).toBeTruthy();
|
||||
|
||||
const res = await rpcReq<{
|
||||
ok?: boolean;
|
||||
path?: string;
|
||||
config?: Record<string, unknown>;
|
||||
}>(requireWs(), "config.set", {
|
||||
raw: JSON.stringify(current.payload?.config ?? {}, null, 2),
|
||||
baseHash: current.payload?.hash,
|
||||
});
|
||||
|
||||
expect(res.ok).toBe(true);
|
||||
expect(res.payload?.path).toBe(createConfigIO().configPath);
|
||||
expect(res.payload?.config).toBeTruthy();
|
||||
});
|
||||
|
||||
it("returns a path-scoped config schema lookup", async () => {
|
||||
const res = await rpcReq<{
|
||||
path: string;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { resolveDefaultAgentId } from "../../agents/agent-scope.js";
|
||||
import { getHealthSnapshot, type HealthSummary } from "../../commands/health.js";
|
||||
import { CONFIG_PATH, STATE_DIR, loadConfig } from "../../config/config.js";
|
||||
import { STATE_DIR, createConfigIO, loadConfig } from "../../config/config.js";
|
||||
import { resolveMainSessionKey } from "../../config/sessions.js";
|
||||
import { listSystemPresence } from "../../infra/system-presence.js";
|
||||
import { getUpdateAvailable } from "../../infra/update-startup.js";
|
||||
@@ -16,6 +16,7 @@ let broadcastHealthUpdate: ((snap: HealthSummary) => void) | null = null;
|
||||
|
||||
export function buildGatewaySnapshot(): Snapshot {
|
||||
const cfg = loadConfig();
|
||||
const configPath = createConfigIO().configPath;
|
||||
const defaultAgentId = resolveDefaultAgentId(cfg);
|
||||
const mainKey = normalizeMainKey(cfg.session?.mainKey);
|
||||
const mainSessionKey = resolveMainSessionKey(cfg);
|
||||
@@ -32,7 +33,7 @@ export function buildGatewaySnapshot(): Snapshot {
|
||||
stateVersion: { presence: presenceVersion, health: healthVersion },
|
||||
uptimeMs,
|
||||
// Surface resolved paths so UIs can display the true config location.
|
||||
configPath: CONFIG_PATH,
|
||||
configPath,
|
||||
stateDir: STATE_DIR,
|
||||
sessionDefaults: {
|
||||
defaultAgentId,
|
||||
|
||||
Reference in New Issue
Block a user