2026-01-12 11:22:56 +00:00
|
|
|
import { describe, expect, it } from "vitest";
|
2026-02-17 12:21:55 +09:00
|
|
|
import type { OpenClawConfig } from "../config/config.js";
|
2026-01-12 11:22:56 +00:00
|
|
|
import { resolveMemorySearchConfig } from "./memory-search.js";
|
|
|
|
|
|
2026-02-17 12:21:55 +09:00
|
|
|
const asConfig = (cfg: OpenClawConfig): OpenClawConfig => cfg;
|
|
|
|
|
|
2026-01-12 11:22:56 +00:00
|
|
|
describe("memory search config", () => {
|
2026-02-22 17:11:17 +00:00
|
|
|
function configWithDefaultProvider(provider: "openai" | "local" | "gemini"): OpenClawConfig {
|
|
|
|
|
return asConfig({
|
|
|
|
|
agents: {
|
|
|
|
|
defaults: {
|
|
|
|
|
memorySearch: {
|
|
|
|
|
provider,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function expectDefaultRemoteBatch(resolved: ReturnType<typeof resolveMemorySearchConfig>): void {
|
|
|
|
|
expect(resolved?.remote?.batch).toEqual({
|
|
|
|
|
enabled: false,
|
|
|
|
|
wait: true,
|
|
|
|
|
concurrency: 2,
|
|
|
|
|
pollIntervalMs: 2000,
|
|
|
|
|
timeoutMinutes: 60,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-12 11:22:56 +00:00
|
|
|
it("returns null when disabled", () => {
|
2026-02-17 12:21:55 +09:00
|
|
|
const cfg = asConfig({
|
2026-01-12 11:22:56 +00:00
|
|
|
agents: {
|
|
|
|
|
defaults: {
|
|
|
|
|
memorySearch: { enabled: true },
|
|
|
|
|
},
|
|
|
|
|
list: [
|
|
|
|
|
{
|
|
|
|
|
id: "main",
|
|
|
|
|
default: true,
|
|
|
|
|
memorySearch: { enabled: false },
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
},
|
2026-02-17 12:21:55 +09:00
|
|
|
});
|
2026-01-12 11:22:56 +00:00
|
|
|
const resolved = resolveMemorySearchConfig(cfg, "main");
|
|
|
|
|
expect(resolved).toBeNull();
|
|
|
|
|
});
|
|
|
|
|
|
2026-01-18 15:29:16 +00:00
|
|
|
it("defaults provider to auto when unspecified", () => {
|
2026-02-17 12:21:55 +09:00
|
|
|
const cfg = asConfig({
|
2026-01-18 15:29:16 +00:00
|
|
|
agents: {
|
|
|
|
|
defaults: {
|
|
|
|
|
memorySearch: {
|
|
|
|
|
enabled: true,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
2026-02-17 12:21:55 +09:00
|
|
|
});
|
2026-01-18 15:29:16 +00:00
|
|
|
const resolved = resolveMemorySearchConfig(cfg, "main");
|
|
|
|
|
expect(resolved?.provider).toBe("auto");
|
|
|
|
|
expect(resolved?.fallback).toBe("none");
|
|
|
|
|
});
|
|
|
|
|
|
2026-01-12 11:22:56 +00:00
|
|
|
it("merges defaults and overrides", () => {
|
2026-02-17 12:21:55 +09:00
|
|
|
const cfg = asConfig({
|
2026-01-12 11:22:56 +00:00
|
|
|
agents: {
|
|
|
|
|
defaults: {
|
|
|
|
|
memorySearch: {
|
|
|
|
|
provider: "openai",
|
|
|
|
|
model: "text-embedding-3-small",
|
2026-01-17 18:02:25 +00:00
|
|
|
store: {
|
|
|
|
|
vector: {
|
|
|
|
|
enabled: false,
|
|
|
|
|
extensionPath: "/opt/sqlite-vec.dylib",
|
|
|
|
|
},
|
|
|
|
|
},
|
2026-01-12 11:22:56 +00:00
|
|
|
chunking: { tokens: 500, overlap: 100 },
|
|
|
|
|
query: { maxResults: 4, minScore: 0.2 },
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
list: [
|
|
|
|
|
{
|
|
|
|
|
id: "main",
|
|
|
|
|
default: true,
|
|
|
|
|
memorySearch: {
|
|
|
|
|
chunking: { tokens: 320 },
|
|
|
|
|
query: { maxResults: 8 },
|
2026-01-17 18:02:25 +00:00
|
|
|
store: {
|
|
|
|
|
vector: {
|
|
|
|
|
enabled: true,
|
|
|
|
|
},
|
|
|
|
|
},
|
2026-01-12 11:22:56 +00:00
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
},
|
2026-02-17 12:21:55 +09:00
|
|
|
});
|
2026-01-12 11:22:56 +00:00
|
|
|
const resolved = resolveMemorySearchConfig(cfg, "main");
|
|
|
|
|
expect(resolved?.provider).toBe("openai");
|
|
|
|
|
expect(resolved?.model).toBe("text-embedding-3-small");
|
|
|
|
|
expect(resolved?.chunking.tokens).toBe(320);
|
|
|
|
|
expect(resolved?.chunking.overlap).toBe(100);
|
|
|
|
|
expect(resolved?.query.maxResults).toBe(8);
|
|
|
|
|
expect(resolved?.query.minScore).toBe(0.2);
|
2026-01-17 18:02:25 +00:00
|
|
|
expect(resolved?.store.vector.enabled).toBe(true);
|
|
|
|
|
expect(resolved?.store.vector.extensionPath).toBe("/opt/sqlite-vec.dylib");
|
2026-01-12 11:22:56 +00:00
|
|
|
});
|
2026-01-13 03:11:03 +00:00
|
|
|
|
2026-01-28 21:49:38 -05:00
|
|
|
it("merges extra memory paths from defaults and overrides", () => {
|
2026-02-17 12:21:55 +09:00
|
|
|
const cfg = asConfig({
|
2026-01-28 21:49:38 -05:00
|
|
|
agents: {
|
|
|
|
|
defaults: {
|
|
|
|
|
memorySearch: {
|
|
|
|
|
extraPaths: ["/shared/notes", " docs "],
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
list: [
|
|
|
|
|
{
|
|
|
|
|
id: "main",
|
|
|
|
|
default: true,
|
|
|
|
|
memorySearch: {
|
|
|
|
|
extraPaths: ["/shared/notes", "../team-notes"],
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
},
|
2026-02-17 12:21:55 +09:00
|
|
|
});
|
2026-01-28 21:49:38 -05:00
|
|
|
const resolved = resolveMemorySearchConfig(cfg, "main");
|
|
|
|
|
expect(resolved?.extraPaths).toEqual(["/shared/notes", "docs", "../team-notes"]);
|
|
|
|
|
});
|
|
|
|
|
|
2026-01-18 00:29:18 +00:00
|
|
|
it("includes batch defaults for openai without remote overrides", () => {
|
2026-02-22 17:11:17 +00:00
|
|
|
const cfg = configWithDefaultProvider("openai");
|
2026-01-18 00:29:18 +00:00
|
|
|
const resolved = resolveMemorySearchConfig(cfg, "main");
|
2026-02-22 17:11:17 +00:00
|
|
|
expectDefaultRemoteBatch(resolved);
|
2026-01-18 00:29:18 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("keeps remote unset for local provider without overrides", () => {
|
2026-02-22 17:11:17 +00:00
|
|
|
const cfg = configWithDefaultProvider("local");
|
2026-01-18 00:29:18 +00:00
|
|
|
const resolved = resolveMemorySearchConfig(cfg, "main");
|
|
|
|
|
expect(resolved?.remote).toBeUndefined();
|
|
|
|
|
});
|
|
|
|
|
|
2026-01-18 09:09:13 +00:00
|
|
|
it("includes remote defaults for gemini without overrides", () => {
|
2026-02-22 17:11:17 +00:00
|
|
|
const cfg = configWithDefaultProvider("gemini");
|
2026-01-18 09:09:13 +00:00
|
|
|
const resolved = resolveMemorySearchConfig(cfg, "main");
|
2026-02-22 17:11:17 +00:00
|
|
|
expectDefaultRemoteBatch(resolved);
|
2026-01-18 09:09:13 +00:00
|
|
|
});
|
|
|
|
|
|
2026-01-21 10:37:52 +00:00
|
|
|
it("defaults session delta thresholds", () => {
|
2026-02-17 12:21:55 +09:00
|
|
|
const cfg = asConfig({
|
2026-01-21 10:37:52 +00:00
|
|
|
agents: {
|
|
|
|
|
defaults: {
|
|
|
|
|
memorySearch: {
|
|
|
|
|
provider: "openai",
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
2026-02-17 12:21:55 +09:00
|
|
|
});
|
2026-01-21 10:37:52 +00:00
|
|
|
const resolved = resolveMemorySearchConfig(cfg, "main");
|
|
|
|
|
expect(resolved?.sync.sessions).toEqual({
|
|
|
|
|
deltaBytes: 100000,
|
|
|
|
|
deltaMessages: 50,
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
2026-01-13 03:11:03 +00:00
|
|
|
it("merges remote defaults with agent overrides", () => {
|
2026-02-17 12:21:55 +09:00
|
|
|
const cfg = asConfig({
|
2026-01-13 03:11:03 +00:00
|
|
|
agents: {
|
|
|
|
|
defaults: {
|
|
|
|
|
memorySearch: {
|
2026-01-18 15:29:16 +00:00
|
|
|
provider: "openai",
|
2026-01-13 03:11:03 +00:00
|
|
|
remote: {
|
|
|
|
|
baseUrl: "https://default.example/v1",
|
|
|
|
|
apiKey: "default-key",
|
|
|
|
|
headers: { "X-Default": "on" },
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
list: [
|
|
|
|
|
{
|
|
|
|
|
id: "main",
|
|
|
|
|
default: true,
|
|
|
|
|
memorySearch: {
|
|
|
|
|
remote: {
|
|
|
|
|
baseUrl: "https://agent.example/v1",
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
},
|
2026-02-17 12:21:55 +09:00
|
|
|
});
|
2026-01-13 03:11:03 +00:00
|
|
|
const resolved = resolveMemorySearchConfig(cfg, "main");
|
|
|
|
|
expect(resolved?.remote).toEqual({
|
|
|
|
|
baseUrl: "https://agent.example/v1",
|
|
|
|
|
apiKey: "default-key",
|
|
|
|
|
headers: { "X-Default": "on" },
|
2026-01-17 22:31:12 +00:00
|
|
|
batch: {
|
2026-02-10 17:31:58 +13:00
|
|
|
enabled: false,
|
2026-01-17 22:31:12 +00:00
|
|
|
wait: true,
|
2026-01-18 01:24:16 +00:00
|
|
|
concurrency: 2,
|
2026-01-18 04:27:58 +00:00
|
|
|
pollIntervalMs: 2000,
|
2026-01-17 22:31:12 +00:00
|
|
|
timeoutMinutes: 60,
|
|
|
|
|
},
|
2026-01-13 03:11:03 +00:00
|
|
|
});
|
|
|
|
|
});
|
2026-01-17 18:53:48 +00:00
|
|
|
|
|
|
|
|
it("gates session sources behind experimental flag", () => {
|
2026-02-17 12:21:55 +09:00
|
|
|
const cfg = asConfig({
|
2026-01-17 18:53:48 +00:00
|
|
|
agents: {
|
|
|
|
|
defaults: {
|
|
|
|
|
memorySearch: {
|
2026-01-18 15:29:16 +00:00
|
|
|
provider: "openai",
|
2026-01-17 18:53:48 +00:00
|
|
|
sources: ["memory", "sessions"],
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
list: [
|
|
|
|
|
{
|
|
|
|
|
id: "main",
|
|
|
|
|
default: true,
|
|
|
|
|
memorySearch: {
|
|
|
|
|
experimental: { sessionMemory: false },
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
},
|
2026-02-17 12:21:55 +09:00
|
|
|
});
|
2026-01-17 18:53:48 +00:00
|
|
|
const resolved = resolveMemorySearchConfig(cfg, "main");
|
|
|
|
|
expect(resolved?.sources).toEqual(["memory"]);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("allows session sources when experimental flag is enabled", () => {
|
2026-02-17 12:21:55 +09:00
|
|
|
const cfg = asConfig({
|
2026-01-17 18:53:48 +00:00
|
|
|
agents: {
|
|
|
|
|
defaults: {
|
|
|
|
|
memorySearch: {
|
2026-01-18 15:29:16 +00:00
|
|
|
provider: "openai",
|
2026-01-17 18:53:48 +00:00
|
|
|
sources: ["memory", "sessions"],
|
|
|
|
|
experimental: { sessionMemory: true },
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
2026-02-17 12:21:55 +09:00
|
|
|
});
|
2026-01-17 18:53:48 +00:00
|
|
|
const resolved = resolveMemorySearchConfig(cfg, "main");
|
|
|
|
|
expect(resolved?.sources).toContain("sessions");
|
|
|
|
|
});
|
2026-01-12 11:22:56 +00:00
|
|
|
});
|