mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-18 10:27:28 +00:00
fix(memory): default qmd searchMode to search + scope search/vsearch to collections
This commit is contained in:
@@ -139,8 +139,8 @@ out to QMD for retrieval. Key points:
|
||||
- Boot refresh now runs in the background by default so chat startup is not
|
||||
blocked; set `memory.qmd.update.waitForBootSync = true` to keep the previous
|
||||
blocking behavior.
|
||||
- Searches run via `memory.qmd.searchMode` (default `qmd query --json`; also
|
||||
supports `search` and `vsearch`). If the selected mode rejects flags on your
|
||||
- Searches run via `memory.qmd.searchMode` (default `qmd search --json`; also
|
||||
supports `vsearch` and `query`). If the selected mode rejects flags on your
|
||||
QMD build, OpenClaw retries with `qmd query`. If QMD fails or the binary is
|
||||
missing, OpenClaw automatically falls back to the builtin SQLite manager so
|
||||
memory tools keep working.
|
||||
@@ -178,8 +178,8 @@ out to QMD for retrieval. Key points:
|
||||
**Config surface (`memory.qmd.*`)**
|
||||
|
||||
- `command` (default `qmd`): override the executable path.
|
||||
- `searchMode` (default `query`): pick which QMD command backs
|
||||
`memory_search` (`query`, `search`, `vsearch`).
|
||||
- `searchMode` (default `search`): pick which QMD command backs
|
||||
`memory_search` (`search`, `vsearch`, `query`).
|
||||
- `includeDefaultMemory` (default `true`): auto-index `MEMORY.md` + `memory/**/*.md`.
|
||||
- `paths[]`: add extra directories/files (`path`, optional `pattern`, optional
|
||||
stable `name`).
|
||||
|
||||
@@ -25,7 +25,7 @@ describe("resolveMemoryBackendConfig", () => {
|
||||
expect(resolved.backend).toBe("qmd");
|
||||
expect(resolved.qmd?.collections.length).toBeGreaterThanOrEqual(3);
|
||||
expect(resolved.qmd?.command).toBe("qmd");
|
||||
expect(resolved.qmd?.searchMode).toBe("query");
|
||||
expect(resolved.qmd?.searchMode).toBe("search");
|
||||
expect(resolved.qmd?.update.intervalMs).toBeGreaterThan(0);
|
||||
expect(resolved.qmd?.update.waitForBootSync).toBe(false);
|
||||
expect(resolved.qmd?.update.commandTimeoutMs).toBe(30_000);
|
||||
|
||||
@@ -66,7 +66,9 @@ const DEFAULT_CITATIONS: MemoryCitationsMode = "auto";
|
||||
const DEFAULT_QMD_INTERVAL = "5m";
|
||||
const DEFAULT_QMD_DEBOUNCE_MS = 15_000;
|
||||
const DEFAULT_QMD_TIMEOUT_MS = 4_000;
|
||||
const DEFAULT_QMD_SEARCH_MODE: MemoryQmdSearchMode = "query";
|
||||
// Defaulting to `query` can be extremely slow on CPU-only systems (query expansion + rerank).
|
||||
// Prefer a faster mode for interactive use; users can opt into `query` for best recall.
|
||||
const DEFAULT_QMD_SEARCH_MODE: MemoryQmdSearchMode = "search";
|
||||
const DEFAULT_QMD_EMBED_INTERVAL = "60m";
|
||||
const DEFAULT_QMD_COMMAND_TIMEOUT_MS = 30_000;
|
||||
const DEFAULT_QMD_UPDATE_TIMEOUT_MS = 120_000;
|
||||
|
||||
@@ -334,7 +334,7 @@ describe("QmdMemoryManager", () => {
|
||||
).resolves.toEqual([]);
|
||||
|
||||
const searchCall = spawnMock.mock.calls.find((call) => call[1]?.[0] === "search");
|
||||
expect(searchCall?.[1]).toEqual(["search", "test", "--json"]);
|
||||
expect(searchCall?.[1]).toEqual(["search", "test", "--json", "-c", "workspace"]);
|
||||
expect(spawnMock.mock.calls.some((call) => call[1]?.[0] === "query")).toBe(false);
|
||||
expect(maxResults).toBeGreaterThan(0);
|
||||
await manager.close();
|
||||
@@ -388,7 +388,7 @@ describe("QmdMemoryManager", () => {
|
||||
(args): args is string[] => Array.isArray(args) && ["search", "query"].includes(args[0]),
|
||||
);
|
||||
expect(searchAndQueryCalls).toEqual([
|
||||
["search", "test", "--json"],
|
||||
["search", "test", "--json", "-c", "workspace"],
|
||||
["query", "test", "--json", "-n", String(maxResults), "-c", "workspace"],
|
||||
]);
|
||||
await manager.close();
|
||||
@@ -535,7 +535,7 @@ describe("QmdMemoryManager", () => {
|
||||
} as OpenClawConfig;
|
||||
|
||||
spawnMock.mockImplementation((_cmd: string, args: string[]) => {
|
||||
if (args[0] === "query") {
|
||||
if (args[0] === "search") {
|
||||
const child = createMockChild({ autoClose: false });
|
||||
emitAndClose(child, "stdout", "[]");
|
||||
return child;
|
||||
@@ -549,24 +549,10 @@ describe("QmdMemoryManager", () => {
|
||||
if (!manager) {
|
||||
throw new Error("manager missing");
|
||||
}
|
||||
const maxResults = resolved.qmd?.limits.maxResults;
|
||||
if (!maxResults) {
|
||||
throw new Error("qmd maxResults missing");
|
||||
}
|
||||
|
||||
await manager.search("test", { sessionKey: "agent:main:slack:dm:u123" });
|
||||
const queryCall = spawnMock.mock.calls.find((call) => call[1]?.[0] === "query");
|
||||
expect(queryCall?.[1]).toEqual([
|
||||
"query",
|
||||
"test",
|
||||
"--json",
|
||||
"-n",
|
||||
String(maxResults),
|
||||
"-c",
|
||||
"workspace",
|
||||
"-c",
|
||||
"notes",
|
||||
]);
|
||||
const searchCall = spawnMock.mock.calls.find((call) => call[1]?.[0] === "search");
|
||||
expect(searchCall?.[1]).toEqual(["search", "test", "--json", "-c", "workspace", "-c", "notes"]);
|
||||
await manager.close();
|
||||
});
|
||||
|
||||
@@ -802,7 +788,7 @@ describe("QmdMemoryManager", () => {
|
||||
|
||||
it("fails search when sqlite index is busy so caller can fallback", async () => {
|
||||
spawnMock.mockImplementation((_cmd: string, args: string[]) => {
|
||||
if (args[0] === "query") {
|
||||
if (args[0] === "search") {
|
||||
const child = createMockChild({ autoClose: false });
|
||||
emitAndClose(
|
||||
child,
|
||||
@@ -839,7 +825,7 @@ describe("QmdMemoryManager", () => {
|
||||
|
||||
it("treats plain-text no-results stdout as an empty result set", async () => {
|
||||
spawnMock.mockImplementation((_cmd: string, args: string[]) => {
|
||||
if (args[0] === "query") {
|
||||
if (args[0] === "search") {
|
||||
const child = createMockChild({ autoClose: false });
|
||||
emitAndClose(child, "stdout", "No results found.");
|
||||
return child;
|
||||
@@ -862,7 +848,7 @@ describe("QmdMemoryManager", () => {
|
||||
|
||||
it("treats plain-text no-results stdout without punctuation as empty", async () => {
|
||||
spawnMock.mockImplementation((_cmd: string, args: string[]) => {
|
||||
if (args[0] === "query") {
|
||||
if (args[0] === "search") {
|
||||
const child = createMockChild({ autoClose: false });
|
||||
emitAndClose(child, "stdout", "No results found\n\n");
|
||||
return child;
|
||||
@@ -885,7 +871,7 @@ describe("QmdMemoryManager", () => {
|
||||
|
||||
it("treats plain-text no-results stderr as an empty result set", async () => {
|
||||
spawnMock.mockImplementation((_cmd: string, args: string[]) => {
|
||||
if (args[0] === "query") {
|
||||
if (args[0] === "search") {
|
||||
const child = createMockChild({ autoClose: false });
|
||||
emitAndClose(child, "stderr", "No results found.\n");
|
||||
return child;
|
||||
|
||||
@@ -262,9 +262,10 @@ export class QmdMemoryManager implements MemorySearchManager {
|
||||
}
|
||||
const qmdSearchCommand = this.qmd.searchMode;
|
||||
const args = this.buildSearchArgs(qmdSearchCommand, trimmed, limit);
|
||||
if (qmdSearchCommand === "query") {
|
||||
args.push(...collectionFilterArgs);
|
||||
}
|
||||
|
||||
// Always scope to managed collections (default + custom). Even for `search`/`vsearch`,
|
||||
// pass collection filters; if a given QMD build rejects these flags, we fall back to `query`.
|
||||
args.push(...collectionFilterArgs);
|
||||
let stdout: string;
|
||||
let stderr: string;
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user