diff --git a/CHANGELOG.md b/CHANGELOG.md index 99c479520b9..31571ac574b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ Docs: https://docs.openclaw.ai ### Fixes +- Config/doctor: stop masking unknown-key validation diagnostics such as `agents.defaults.llm`, and have `openclaw doctor --fix` remove the retired `agents.defaults.llm` timeout block. Thanks @aidiffuser. - CLI/plugins: preserve unversioned ClawHub install specs so `plugins update` can follow newer ClawHub releases instead of pinning to the initially resolved version. Fixes #63010; supersedes #58426. Thanks @kangsen1234 and @robinspt. - Memory-core/subagents: tag plugin-created subagent sessions with their plugin owner so dreaming narrative cleanup can delete its own ephemeral sessions without granting broad admin session deletion. Fixes #72712. Thanks @BSG2000. - Gateway/models: move local-provider pricing opt-outs, OpenRouter/LiteLLM aliases, and proxy passthrough pricing lookup into plugin manifest metadata so core no longer carries extension-specific pricing tables. Thanks @codex. diff --git a/docs/gateway/doctor.md b/docs/gateway/doctor.md index f7e5a6b7f56..beb4fb95cc9 100644 --- a/docs/gateway/doctor.md +++ b/docs/gateway/doctor.md @@ -194,6 +194,7 @@ That stages grounded durable candidates into the short-term dreaming store while - `identity` → `agents.list[].identity` - `agent.*` → `agents.defaults` + `tools.*` (tools/elevated/exec/sandbox/subagents) - `agent.model`/`allowedModels`/`modelAliases`/`modelFallbacks`/`imageModelFallbacks` → `agents.defaults.models` + `agents.defaults.model.primary/fallbacks` + `agents.defaults.imageModel.primary/fallbacks` + - remove `agents.defaults.llm`; use `models.providers..timeoutSeconds` for slow provider/model timeouts - `browser.ssrfPolicy.allowPrivateNetwork` → `browser.ssrfPolicy.dangerouslyAllowPrivateNetwork` - `browser.profiles.*.driver: "extension"` → `"existing-session"` - remove `browser.relayBindHost` (legacy extension relay setting) diff --git a/src/commands/doctor/shared/deprecation-compat.ts b/src/commands/doctor/shared/deprecation-compat.ts index a69dc88f547..81593803394 100644 --- a/src/commands/doctor/shared/deprecation-compat.ts +++ b/src/commands/doctor/shared/deprecation-compat.ts @@ -57,6 +57,18 @@ function deprecatedCompatRecord( // doctor fixes, and replacement notes should be revalidated against the current // architecture because ownership and config footprint can shift during rollout. export const DOCTOR_DEPRECATION_COMPAT_RECORDS = [ + deprecatedCompatRecord({ + code: "doctor-agent-llm-timeout", + owner: "agent-runtime", + introduced: "2026-04-27", + source: "agents.defaults.llm.idleTimeoutSeconds", + migration: "src/commands/doctor/shared/legacy-config-migrations.runtime.agents.ts", + replacement: "models.providers..timeoutSeconds", + docsPath: "/gateway/config-agents", + tests: ["src/commands/doctor/shared/legacy-config-migrate.test.ts"], + notes: + "The old agent-level idle timeout knob was collapsed into provider request timeout handling.", + }), deprecatedCompatRecord({ code: "doctor-agent-runtime-embedded-harness", owner: "agent-runtime", diff --git a/src/commands/doctor/shared/legacy-config-migrate.test.ts b/src/commands/doctor/shared/legacy-config-migrate.test.ts index 78502198c32..cb4151c3c44 100644 --- a/src/commands/doctor/shared/legacy-config-migrate.test.ts +++ b/src/commands/doctor/shared/legacy-config-migrate.test.ts @@ -98,6 +98,26 @@ describe("legacy migrate mention routing", () => { }); describe("legacy migrate sandbox scope aliases", () => { + it("removes legacy agents.defaults.llm timeout config", () => { + const res = migrateLegacyConfigForTest({ + agents: { + defaults: { + model: { primary: "openai/gpt-5.4" }, + llm: { + idleTimeoutSeconds: 120, + }, + }, + }, + }); + + expect(res.changes).toContain( + "Removed agents.defaults.llm; model idle timeout now follows models.providers..timeoutSeconds.", + ); + expect(res.config?.agents?.defaults).toEqual({ + model: { primary: "openai/gpt-5.4" }, + }); + }); + it("moves legacy embeddedHarness runtime policy into agentRuntime", () => { const res = migrateLegacyConfigForTest({ agents: { diff --git a/src/commands/doctor/shared/legacy-config-migrations.runtime.agents.ts b/src/commands/doctor/shared/legacy-config-migrations.runtime.agents.ts index bfa41aa7f35..e5df756c041 100644 --- a/src/commands/doctor/shared/legacy-config-migrations.runtime.agents.ts +++ b/src/commands/doctor/shared/legacy-config-migrations.runtime.agents.ts @@ -69,6 +69,15 @@ const LEGACY_AGENT_RUNTIME_POLICY_RULES: LegacyConfigRule[] = [ }, ]; +const LEGACY_AGENT_LLM_TIMEOUT_RULES: LegacyConfigRule[] = [ + { + path: ["agents", "defaults", "llm"], + message: + 'agents.defaults.llm is legacy; use models.providers..timeoutSeconds for slow model/provider timeouts. Run "openclaw doctor --fix".', + match: (value) => getRecord(value) !== null, + }, +]; + function sandboxScopeFromPerSession(perSession: boolean): "session" | "shared" { return perSession ? "session" : "shared"; } @@ -194,6 +203,21 @@ function migrateLegacyAgentRuntimePolicy( } export const LEGACY_CONFIG_MIGRATIONS_RUNTIME_AGENTS: LegacyConfigMigrationSpec[] = [ + defineLegacyConfigMigration({ + id: "agents.defaults.llm->models.providers.timeoutSeconds", + describe: "Remove legacy agents.defaults.llm timeout config", + legacyRules: LEGACY_AGENT_LLM_TIMEOUT_RULES, + apply: (raw, changes) => { + const defaults = getRecord(getRecord(raw.agents)?.defaults); + if (!defaults || getRecord(defaults.llm) === null) { + return; + } + delete defaults.llm; + changes.push( + "Removed agents.defaults.llm; model idle timeout now follows models.providers..timeoutSeconds.", + ); + }, + }), defineLegacyConfigMigration({ id: "agents.embeddedHarness->agentRuntime", describe: "Move legacy embeddedHarness runtime policy to agentRuntime", diff --git a/src/logging/redact.test.ts b/src/logging/redact.test.ts index dfa4f5e6e0f..355b6afc54b 100644 --- a/src/logging/redact.test.ts +++ b/src/logging/redact.test.ts @@ -107,6 +107,15 @@ describe("redactSensitiveText", () => { expect(output).toBe("TOKEN=***"); }); + it("does not redact lowercase key diagnostics", () => { + const input = 'agents.defaults: Unrecognized key: "llm"'; + const output = redactSensitiveText(input, { + mode: "tools", + patterns: defaults, + }); + expect(output).toBe(input); + }); + it("redacts private key blocks", () => { const input = [ "-----BEGIN PRIVATE KEY-----", diff --git a/src/logging/redact.ts b/src/logging/redact.ts index 9a870fd1c91..e377f26161f 100644 --- a/src/logging/redact.ts +++ b/src/logging/redact.ts @@ -11,8 +11,9 @@ const DEFAULT_REDACT_KEEP_START = 6; const DEFAULT_REDACT_KEEP_END = 4; const DEFAULT_REDACT_PATTERNS: string[] = [ - // ENV-style assignments. - String.raw`\b[A-Z0-9_]*(?:KEY|TOKEN|SECRET|PASSWORD|PASSWD)\b\s*[=:]\s*(["']?)([^\s"'\\]+)\1`, + // ENV-style assignments. Keep this case-sensitive so diagnostics like + // `Unrecognized key: "llm"` do not lose the actual config key. + String.raw`/\b[A-Z0-9_]*(?:KEY|TOKEN|SECRET|PASSWORD|PASSWD)\b\s*[=:]\s*(["']?)([^\s"'\\]+)\1/g`, // JSON fields. String.raw`"(?:apiKey|token|secret|password|passwd|accessToken|refreshToken)"\s*:\s*"([^"]+)"`, // CLI flags.