diff --git a/extensions/memory-lancedb/config.test.ts b/extensions/memory-lancedb/config.test.ts index 59955efbbdc..8c4f007f1bd 100644 --- a/extensions/memory-lancedb/config.test.ts +++ b/extensions/memory-lancedb/config.test.ts @@ -61,7 +61,7 @@ describe("memory-lancedb config", () => { expect(parsed.embedding.provider).toBe("openai"); }); - it("rejects empty embedding placeholders in the manifest schema", () => { + it("rejects empty embedding config in the manifest schema and runtime parser", () => { const manifestResult = validateJsonSchemaValue({ schema: manifest.configSchema, cacheKey: "memory-lancedb.manifest.empty-embedding", @@ -71,6 +71,17 @@ describe("memory-lancedb config", () => { }); expect(manifestResult.ok).toBe(false); + if (!manifestResult.ok) { + expect(manifestResult.errors.map((error) => error.text)).toContain( + "embedding: must NOT have fewer than 1 properties", + ); + } + + expect(() => { + memoryConfigSchema.parse({ + embedding: {}, + }); + }).toThrow("embedding config must include at least one setting"); }); it("rejects empty embedding providers", () => { diff --git a/extensions/memory-lancedb/config.ts b/extensions/memory-lancedb/config.ts index 94ee305fa00..0c2648b6b2c 100644 --- a/extensions/memory-lancedb/config.ts +++ b/extensions/memory-lancedb/config.ts @@ -58,6 +58,7 @@ const EMBEDDING_DIMENSIONS: Record = { "text-embedding-3-small": 1536, "text-embedding-3-large": 3072, }; +const EMBEDDING_CONFIG_KEYS = ["provider", "apiKey", "model", "baseUrl", "dimensions"] as const; function assertAllowedKeys(value: Record, allowed: string[], label: string) { const unknown = Object.keys(value).filter((key) => !allowed.includes(key)); @@ -118,11 +119,10 @@ export const memoryConfigSchema = { if (!embedding || typeof embedding !== "object" || Array.isArray(embedding)) { throw new Error("embedding config required"); } - assertAllowedKeys( - embedding, - ["provider", "apiKey", "model", "baseUrl", "dimensions"], - "embedding config", - ); + assertAllowedKeys(embedding, [...EMBEDDING_CONFIG_KEYS], "embedding config"); + if (Object.keys(embedding).length === 0) { + throw new Error("embedding config must include at least one setting"); + } const model = resolveEmbeddingModel(embedding); const provider = typeof embedding.provider === "string" ? embedding.provider.trim() : "openai"; diff --git a/extensions/memory-lancedb/openclaw.plugin.json b/extensions/memory-lancedb/openclaw.plugin.json index d81873dbaf7..ef2df411add 100644 --- a/extensions/memory-lancedb/openclaw.plugin.json +++ b/extensions/memory-lancedb/openclaw.plugin.json @@ -73,6 +73,7 @@ "type": "object", "minProperties": 1, "additionalProperties": false, + "minProperties": 1, "properties": { "apiKey": { "type": "string" diff --git a/src/cli/plugins-cli.install.test.ts b/src/cli/plugins-cli.install.test.ts index 3b4e329a47c..5b64d869347 100644 --- a/src/cli/plugins-cli.install.test.ts +++ b/src/cli/plugins-cli.install.test.ts @@ -517,6 +517,32 @@ describe("plugins cli install", () => { expect(runtimeLogs.some((line) => line.includes("requires configuration first"))).toBe(true); }); + it("enables config-gated bundled installs when provider-backed config is explicit", async () => { + const cfg = { + plugins: { + entries: { + "memory-lancedb": { + config: { + embedding: { + provider: "openai", + model: "text-embedding-3-small", + }, + }, + }, + }, + }, + } as OpenClawConfig; + const enabledCfg = createEnabledPluginConfig("memory-lancedb"); + loadConfig.mockReturnValue(cfg); + enablePluginInConfig.mockReturnValue({ config: enabledCfg }); + + await runPluginsCommand(["plugins", "install", "memory-lancedb"]); + + expect(enablePluginInConfig).toHaveBeenCalled(); + expect(writeConfigFile).toHaveBeenCalledWith(enabledCfg); + expect(runtimeLogs.some((line) => line.includes("requires configuration first"))).toBe(false); + }); + it("passes force through as overwrite mode for ClawHub installs", async () => { const cfg = { plugins: {