diff --git a/CHANGELOG.md b/CHANGELOG.md index 501dc62fe88..8480dc9b549 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ Docs: https://docs.openclaw.ai - Pi embedded runs: pass real built-in tools into Pi session creation and then narrow active tool names after custom tool registration, so the runner and compaction paths compile cleanly and keep OpenClaw-managed custom tool allowlists without feeding string arrays into `createAgentSession`. Thanks @vincentkoc. - Agents/OpenAI websocket: route native OpenAI websocket metadata and session-header decisions through the shared endpoint classifier so local mocks and custom `models.providers.openai.baseUrl` endpoints stay out of the native OpenAI path consistently across embedded-runner and websocket transport code. Thanks @vincentkoc. +- Config: render validation warnings with real line breaks instead of a literal `\n` sequence in CLI/audit output. Fixes #70140. - Cron/doctor: repair malformed persisted cron job IDs through `openclaw doctor`, including legacy `jobId`, non-string `id`, and missing `id` rows, so `cron list` no longer needs display-layer coercion for corrupt store data. Fixes #70128. - Discord: normalize prefixed channel targets only at the thread-binding API boundary, so `sessions_spawn({ runtime: "acp", thread: true })` can create child threads from Discord channels without breaking current-channel ACP bindings. (#68034) Thanks @Zetarcos. - Discord: harden inbound thread metadata handling against partial Carbon channel getters, so non-command thread messages and queued jobs no longer crash when `name`, `parentId`, `parent`, or `ownerId` requires fetched raw data. diff --git a/src/config/io.compat.test.ts b/src/config/io.compat.test.ts index e92acb6a0b1..e75f9135f4c 100644 --- a/src/config/io.compat.test.ts +++ b/src/config/io.compat.test.ts @@ -1,7 +1,7 @@ import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; -import { describe, expect, it } from "vitest"; +import { describe, expect, it, vi } from "vitest"; import { applyRuntimeLegacyConfigMigrations } from "../commands/doctor/shared/runtime-compat-api.js"; import { createConfigIO } from "./io.js"; import { normalizeExecSafeBinProfilesInConfig } from "./normalize-exec-safe-bin.js"; @@ -70,6 +70,45 @@ describe("config io paths", () => { }); }); + it("logs validation warnings with real line breaks", async () => { + await withTempHome(async (home) => { + const configPath = path.join(home, ".openclaw", "openclaw.json"); + await fs.mkdir(path.dirname(configPath), { recursive: true }); + await fs.writeFile( + configPath, + JSON.stringify( + { + plugins: { + entries: { + "google-antigravity-auth": { + enabled: false, + config: { stale: true }, + }, + }, + }, + }, + null, + 2, + ), + ); + const logger = { + error: vi.fn(), + warn: vi.fn(), + }; + + const io = createConfigIO({ + configPath, + env: {} as NodeJS.ProcessEnv, + homedir: () => home, + logger, + }); + io.loadConfig(); + + expect(logger.warn).toHaveBeenCalledWith(expect.stringMatching(/^Config warnings:\n- /)); + expect(logger.warn).not.toHaveBeenCalledWith(expect.stringContaining("Config warnings:\\n")); + }); + }); + it("normalizes safe-bin config entries at config load time", async () => { const cfg = { tools: { diff --git a/src/config/io.ts b/src/config/io.ts index 3548954a6e9..15f795f8edb 100644 --- a/src/config/io.ts +++ b/src/config/io.ts @@ -1198,7 +1198,7 @@ export function createConfigIO(overrides: ConfigIoDeps = {}) { `- ${sanitizeTerminalText(iss.path || "")}: ${sanitizeTerminalText(iss.message)}`, ) .join("\n"); - deps.logger.warn(`Config warnings:\\n${details}`); + deps.logger.warn(`Config warnings:\n${details}`); } warnIfConfigFromFuture(validated.config, deps.logger); const cfg = materializeRuntimeConfig(validated.config, "load");