import { beforeEach, describe, expect, it } from "vitest"; import { getMemorySearchManagerMockConfigs, getMemorySearchManagerMockParams, resetMemoryToolMockState, setMemoryBackend, setMemorySearchImpl, } from "./memory-tool-manager-mock.js"; import { createMemorySearchTool } from "./tools.js"; import { asOpenClawConfig, createMemorySearchToolOrThrow, expectUnavailableMemorySearchDetails, } from "./tools.test-helpers.js"; describe("memory_search unavailable payloads", () => { beforeEach(() => { resetMemoryToolMockState({ searchImpl: async () => [] }); }); it("returns explicit unavailable metadata for quota failures", async () => { setMemorySearchImpl(async () => { throw new Error("openai embeddings failed: 429 insufficient_quota"); }); const tool = createMemorySearchToolOrThrow(); const result = await tool.execute("quota", { query: "hello" }); expectUnavailableMemorySearchDetails(result.details, { error: "openai embeddings failed: 429 insufficient_quota", warning: "Memory search is unavailable because the embedding provider quota is exhausted.", action: "Top up or switch embedding provider, then retry memory_search.", }); }); it("returns explicit unavailable metadata for non-quota failures", async () => { setMemorySearchImpl(async () => { throw new Error("embedding provider timeout"); }); const tool = createMemorySearchToolOrThrow(); const result = await tool.execute("generic", { query: "hello" }); expectUnavailableMemorySearchDetails(result.details, { error: "embedding provider timeout", warning: "Memory search is unavailable due to an embedding/provider error.", action: "Check embedding provider configuration and retry memory_search.", }); }); it("returns structured search debug metadata for qmd results", async () => { setMemoryBackend("qmd"); setMemorySearchImpl(async (opts) => { opts?.onDebug?.({ backend: "qmd", configuredMode: opts.qmdSearchModeOverride ?? "query", effectiveMode: "query", fallback: "unsupported-search-flags", }); return [ { path: "MEMORY.md", startLine: 1, endLine: 2, score: 0.9, snippet: "ramen", source: "memory", }, ]; }); const tool = createMemorySearchToolOrThrow({ config: { plugins: { entries: { "active-memory": { config: { qmd: { searchMode: "search", }, }, }, }, }, memory: { backend: "qmd", qmd: { searchMode: "query", limits: { maxInjectedChars: 1000, }, }, }, }, agentSessionKey: "agent:main:main:active-memory:debug", }); const result = await tool.execute("debug", { query: "favorite food" }); expect(result.details).toMatchObject({ mode: "query", debug: { backend: "qmd", configuredMode: "search", effectiveMode: "query", fallback: "unsupported-search-flags", hits: 1, }, }); expect((result.details as { debug?: { searchMs?: number } }).debug?.searchMs).toEqual( expect.any(Number), ); }); it("uses explicit plugin context agent over synthetic active-memory session keys", async () => { const tool = createMemorySearchToolOrThrow({ config: asOpenClawConfig({ agents: { list: [ { id: "main", default: true, memorySearch: { enabled: false } }, { id: "recall", memorySearch: { enabled: true } }, ], }, }), agentId: "recall", agentSessionKey: "explicit:user-session:active-memory:abc123", }); await tool.execute("recall", { query: "favorite food" }); expect(getMemorySearchManagerMockParams().at(-1)?.agentId).toBe("recall"); }); it("re-resolves config when executing a previously created tool", async () => { const startupConfig = asOpenClawConfig({ agents: { defaults: { memorySearch: { provider: "ollama", model: "nomic-embed-text", }, }, list: [{ id: "main", default: true }], }, memory: { backend: "builtin", }, }); const patchedConfig = asOpenClawConfig({ agents: { defaults: { memorySearch: { provider: "openai", model: "text-embedding-3-small", }, }, list: [{ id: "main", default: true }], }, memory: { backend: "builtin", }, }); let liveConfig = startupConfig; const tool = createMemorySearchTool({ config: startupConfig, getConfig: () => liveConfig, }); if (!tool) { throw new Error("tool missing"); } liveConfig = patchedConfig; await tool.execute("patched-config", { query: "provider switch" }); expect(getMemorySearchManagerMockConfigs()).toEqual([patchedConfig]); }); });