fix: seed memorySearch disabled when no user embedding config (#1148)

This commit is contained in:
paisley
2026-07-07 16:41:00 +08:00
committed by GitHub
parent a0509b9b54
commit e84f13c876
3 changed files with 140 additions and 0 deletions
+14
View File
@@ -29,6 +29,7 @@ import {
} from './provider-keys';
import { normalizePiAiModelCost, type PiAiModelCostRates } from '../shared/pi-ai-model-cost';
import { withConfigLock } from './config-mutex';
import { ensureMemorySearchDisabledDefault, hasUserMemorySearchConfig } from './openclaw-memory-search';
import { PORTS } from './config';
import { getSetting } from './store';
import {
@@ -2675,6 +2676,19 @@ export async function batchSyncConfigFields(token: string): Promise<void> {
console.log('[batch-sync] Seeded agents.defaults.compaction.mode=safeguard');
}
// ── Memory search default ──
// OpenClaw defaults to the openai embedding provider; without a key that
// yields doctor errors and a broken memory_search tool. Seed enabled=false
// only when the user has no memorySearch config anywhere AND no OpenAI key
// (i.e. the default embedding model is unusable). Existing user config is
// never modified.
if (!hasUserMemorySearchConfig(config)
&& !(await getProviderApiKeyFromOpenClaw('openai'))
&& ensureMemorySearchDisabledDefault(config)) {
modified = true;
console.log('[batch-sync] Seeded agents.defaults.memorySearch.enabled=false (no embedding provider configured)');
}
// ── Custom provider contextWindow backfill ──
const backfilledContextWindows = backfillCustomProviderModelContextWindows(config);
if (backfilledContextWindows.length > 0) {
+46
View File
@@ -0,0 +1,46 @@
/**
* Memory search default seeding for openclaw.json.
*
* OpenClaw enables semantic memory search by default with the `openai`
* embedding provider, so a user without an OpenAI key gets doctor errors and
* a broken memory_search tool. ClawX seeds `agents.defaults.memorySearch =
* { enabled: false }` at Gateway prelaunch — but only when the user has no
* memorySearch config anywhere (global defaults or per-agent overrides).
* Existing user config is never modified.
*/
function isRecord(value: unknown): value is Record<string, unknown> {
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
}
/**
* True when the user manages memorySearch themselves: either
* `agents.defaults.memorySearch` or any `agents.list[].memorySearch` exists.
*/
export function hasUserMemorySearchConfig(config: Record<string, unknown>): boolean {
const agents = isRecord(config.agents) ? config.agents : undefined;
if (!agents) return false;
const defaults = isRecord(agents.defaults) ? agents.defaults : undefined;
if (defaults && defaults.memorySearch !== undefined) return true;
const list = Array.isArray(agents.list) ? agents.list : [];
return list.some((entry) => isRecord(entry) && entry.memorySearch !== undefined);
}
/**
* Seed `agents.defaults.memorySearch = { enabled: false }` when the user has
* no memorySearch config at all. Mutates `config` in place and returns true
* when a change was made. Never touches existing memorySearch objects.
*/
export function ensureMemorySearchDisabledDefault(config: Record<string, unknown>): boolean {
if (hasUserMemorySearchConfig(config)) return false;
const agents = (isRecord(config.agents) ? config.agents : {}) as Record<string, unknown>;
const defaults = (isRecord(agents.defaults) ? agents.defaults : {}) as Record<string, unknown>;
defaults.memorySearch = { enabled: false };
agents.defaults = defaults;
config.agents = agents;
return true;
}
+80
View File
@@ -0,0 +1,80 @@
import { describe, expect, it } from 'vitest';
import {
ensureMemorySearchDisabledDefault,
hasUserMemorySearchConfig,
} from '@electron/utils/openclaw-memory-search';
describe('openclaw-memory-search', () => {
it('seeds memorySearch.enabled=false when no memorySearch config exists', () => {
const config: Record<string, unknown> = {
agents: { defaults: { model: { primary: 'custom-customfc/gpt-5.5' } } },
};
expect(ensureMemorySearchDisabledDefault(config)).toBe(true);
expect(config).toEqual({
agents: {
defaults: {
model: { primary: 'custom-customfc/gpt-5.5' },
memorySearch: { enabled: false },
},
},
});
});
it('seeds on a completely empty config', () => {
const config: Record<string, unknown> = {};
expect(ensureMemorySearchDisabledDefault(config)).toBe(true);
expect(config).toEqual({
agents: { defaults: { memorySearch: { enabled: false } } },
});
});
it('never touches existing defaults.memorySearch config', () => {
const config: Record<string, unknown> = {
agents: {
defaults: {
memorySearch: {
provider: 'custom-customfc',
model: 'text-embedding-3-small',
fallback: 'none',
remote: { baseUrl: 'https://taolat.com/v1' },
},
},
},
};
const before = JSON.parse(JSON.stringify(config));
expect(ensureMemorySearchDisabledDefault(config)).toBe(false);
expect(config).toEqual(before);
});
it('never seeds when a per-agent memorySearch override exists', () => {
const config: Record<string, unknown> = {
agents: {
defaults: { model: { primary: 'openai/gpt-4o' } },
list: [
{ id: 'main' },
{ id: 'research', memorySearch: { provider: 'openai' } },
],
},
};
const before = JSON.parse(JSON.stringify(config));
expect(ensureMemorySearchDisabledDefault(config)).toBe(false);
expect(config).toEqual(before);
});
it('treats explicit enabled=true as user config', () => {
const config: Record<string, unknown> = {
agents: { defaults: { memorySearch: { enabled: true } } },
};
expect(hasUserMemorySearchConfig(config)).toBe(true);
expect(ensureMemorySearchDisabledDefault(config)).toBe(false);
expect((config.agents as { defaults: { memorySearch: { enabled: boolean } } }).defaults.memorySearch.enabled).toBe(true);
});
it('treats an empty memorySearch object as user config', () => {
const config: Record<string, unknown> = {
agents: { defaults: { memorySearch: {} } },
};
expect(hasUserMemorySearchConfig(config)).toBe(true);
expect(ensureMemorySearchDisabledDefault(config)).toBe(false);
});
});