diff --git a/src/commands/doctor/shared/legacy-x-search-migrate.test.ts b/src/commands/doctor/shared/legacy-x-search-migrate.test.ts index e6b26419559..d86ada5ca6c 100644 --- a/src/commands/doctor/shared/legacy-x-search-migrate.test.ts +++ b/src/commands/doctor/shared/legacy-x-search-migrate.test.ts @@ -80,6 +80,42 @@ describe("legacy x_search config migration", () => { }); }); + it("moves legacy x_search SecretRefs into the xai plugin auth slot unchanged", () => { + const res = migrateLegacyXSearchConfig({ + tools: { + web: { + x_search: { + apiKey: { + source: "env", + provider: "default", + id: "X_SEARCH_KEY_REF", + }, + enabled: true, + }, + } as Record, + }, + } as OpenClawConfig); + + expect((res.config.tools?.web as Record | undefined)?.x_search).toEqual({ + enabled: true, + }); + expect(res.config.plugins?.entries?.xai).toEqual({ + enabled: true, + config: { + webSearch: { + apiKey: { + source: "env", + provider: "default", + id: "X_SEARCH_KEY_REF", + }, + }, + }, + }); + expect(res.changes).toEqual([ + "Moved tools.web.x_search.apiKey → plugins.entries.xai.config.webSearch.apiKey.", + ]); + }); + it("does nothing for knob-only x_search config without a legacy apiKey", () => { const config = { tools: { diff --git a/src/config/config-misc.test.ts b/src/config/config-misc.test.ts index d7dd4e0cc61..9a4a27d0342 100644 --- a/src/config/config-misc.test.ts +++ b/src/config/config-misc.test.ts @@ -651,6 +651,41 @@ describe("config strict validation", () => { }); }); + it("accepts legacy x_search SecretRefs via auto-migration and reports legacyIssues", async () => { + await withTempHome(async (home) => { + await writeOpenClawConfig(home, { + tools: { + web: { + x_search: { + apiKey: { + source: "env", + provider: "default", + id: "X_SEARCH_KEY_REF", + }, + }, + }, + }, + }); + + const snap = await readConfigFileSnapshot(); + + expect(snap.valid).toBe(true); + expect(snap.legacyIssues.some((issue) => issue.path === "tools.web.x_search.apiKey")).toBe( + true, + ); + expect(snap.sourceConfig.plugins?.entries?.xai?.config?.webSearch).toMatchObject({ + apiKey: { + source: "env", + provider: "default", + id: "X_SEARCH_KEY_REF", + }, + }); + expect( + (snap.sourceConfig.tools?.web?.x_search as Record | undefined)?.apiKey, + ).toBeUndefined(); + }); + }); + it("accepts legacy thread binding ttlHours via auto-migration and reports legacyIssues", async () => { await withTempHome(async (home) => { await writeOpenClawConfig(home, { diff --git a/src/secrets/runtime-web-tools.ts b/src/secrets/runtime-web-tools.ts index 70582e16d1d..f4f9cbd2d68 100644 --- a/src/secrets/runtime-web-tools.ts +++ b/src/secrets/runtime-web-tools.ts @@ -382,16 +382,6 @@ export async function resolveRuntimeWebTools(params: { const sourceTools = isRecord(params.sourceConfig.tools) ? params.sourceConfig.tools : undefined; const sourceWeb = isRecord(sourceTools?.web) ? sourceTools.web : undefined; - const resolvedTools = isRecord(params.resolvedConfig.tools) - ? params.resolvedConfig.tools - : undefined; - const resolvedWeb = isRecord(resolvedTools?.web) ? resolvedTools.web : undefined; - const legacyXSearchSourceRaw: unknown = sourceWeb?.x_search; - const legacyXSearchResolvedRaw: unknown = resolvedWeb?.x_search; - const legacyXSearchSource = isRecord(legacyXSearchSourceRaw) ? legacyXSearchSourceRaw : undefined; - const legacyXSearchResolved = isRecord(legacyXSearchResolvedRaw) - ? legacyXSearchResolvedRaw - : undefined; if (!sourceWeb && !hasPluginWebToolConfig(params.sourceConfig)) { return { search: { @@ -405,24 +395,6 @@ export async function resolveRuntimeWebTools(params: { diagnostics, }; } - if ( - legacyXSearchSource && - legacyXSearchResolved && - Object.prototype.hasOwnProperty.call(legacyXSearchSource, "apiKey") - ) { - const apiKey = legacyXSearchSource["apiKey"]; - const resolution = await resolveSecretInputWithEnvFallback({ - sourceConfig: params.sourceConfig, - context: params.context, - defaults, - value: apiKey, - path: "tools.web.x_search.apiKey", - envVars: ["XAI_API_KEY"], - }); - if (resolution.value) { - legacyXSearchResolved["apiKey"] = resolution.value; - } - } const search = isRecord(sourceWeb?.search) ? sourceWeb.search : undefined; const rawProvider = typeof search?.provider === "string" ? search.provider.trim().toLowerCase() : ""; diff --git a/src/secrets/runtime.test.ts b/src/secrets/runtime.test.ts index 9e9743d4317..b2ad2e1c59f 100644 --- a/src/secrets/runtime.test.ts +++ b/src/secrets/runtime.test.ts @@ -1947,7 +1947,7 @@ describe("secrets runtime snapshot", () => { } }); - it("keeps legacy x_search SecretRefs in place until doctor repairs them", async () => { + it("leaves legacy x_search SecretRefs untouched so doctor owns the migration", async () => { const snapshot = await prepareSecretsRuntimeSnapshot({ config: asConfig({ tools: { @@ -1968,45 +1968,13 @@ describe("secrets runtime snapshot", () => { }); expect((snapshot.config.tools?.web as Record | undefined)?.x_search).toEqual({ - apiKey: "xai-runtime-key", + apiKey: { source: "env", provider: "default", id: "X_SEARCH_KEY_REF" }, enabled: true, model: "grok-4-1-fast", }); expect(snapshot.config.plugins?.entries?.xai).toBeUndefined(); }); - it("still resolves legacy x_search auth in place even when unrelated legacy config is present", async () => { - const snapshot = await prepareSecretsRuntimeSnapshot({ - config: asConfig({ - tools: { - web: { - x_search: { - apiKey: { source: "env", provider: "default", id: "X_SEARCH_KEY_REF" }, - enabled: true, - }, - }, - }, - channels: { - telegram: { - groupMentionsOnly: true, - groups: [], - }, - }, - }), - env: { - X_SEARCH_KEY_REF: "xai-runtime-key-invalid-config", - }, - agentDirs: ["/tmp/openclaw-agent-main"], - loadAuthStore: () => ({ version: 1, profiles: {} }), - }); - - expect((snapshot.config.tools?.web as Record | undefined)?.x_search).toEqual({ - apiKey: "xai-runtime-key-invalid-config", - enabled: true, - }); - expect(snapshot.config.plugins?.entries?.xai).toBeUndefined(); - }); - it("does not force-enable xai at runtime for knob-only x_search config", async () => { const snapshot = await prepareSecretsRuntimeSnapshot({ config: asConfig({