Memory: make qmd search-mode flags compatible

This commit is contained in:
Vignesh Natarajan
2026-02-07 19:59:40 -08:00
committed by Vignesh
parent 6d9d4d04ed
commit 36e27ad561
3 changed files with 79 additions and 11 deletions

View File

@@ -316,13 +316,79 @@ describe("QmdMemoryManager", () => {
if (!manager) {
throw new Error("manager missing");
}
const maxResults = resolved.qmd?.limits.maxResults;
if (!maxResults) {
throw new Error("qmd maxResults missing");
}
await expect(
manager.search("test", { sessionKey: "agent:main:slack:dm:u123" }),
).resolves.toEqual([]);
expect(spawnMock.mock.calls.some((call) => call[1]?.[0] === "search")).toBe(true);
const searchCall = spawnMock.mock.calls.find((call) => call[1]?.[0] === "search");
expect(searchCall?.[1]).toEqual(["search", "test", "--json"]);
expect(spawnMock.mock.calls.some((call) => call[1]?.[0] === "query")).toBe(false);
expect(maxResults).toBeGreaterThan(0);
await manager.close();
});
it("retries search with qmd query when configured mode rejects flags", async () => {
cfg = {
...cfg,
memory: {
backend: "qmd",
qmd: {
includeDefaultMemory: false,
searchMode: "search",
update: { interval: "0s", debounceMs: 60_000, onBoot: false },
paths: [{ path: workspaceDir, pattern: "**/*.md", name: "workspace" }],
},
},
} as OpenClawConfig;
spawnMock.mockImplementation((_cmd: string, args: string[]) => {
if (args[0] === "search") {
const child = createMockChild({ autoClose: false });
setTimeout(() => {
child.stderr.emit("data", "unknown flag: --json");
child.closeWith(2);
}, 0);
return child;
}
if (args[0] === "query") {
const child = createMockChild({ autoClose: false });
setTimeout(() => {
child.stdout.emit("data", "[]");
child.closeWith(0);
}, 0);
return child;
}
return createMockChild();
});
const resolved = resolveMemoryBackendConfig({ cfg, agentId });
const manager = await QmdMemoryManager.create({ cfg, agentId, resolved });
expect(manager).toBeTruthy();
if (!manager) {
throw new Error("manager missing");
}
const maxResults = resolved.qmd?.limits.maxResults;
if (!maxResults) {
throw new Error("qmd maxResults missing");
}
await expect(
manager.search("test", { sessionKey: "agent:main:slack:dm:u123" }),
).resolves.toEqual([]);
const searchAndQueryCalls = spawnMock.mock.calls
.map((call) => call[1])
.filter(
(args): args is string[] => Array.isArray(args) && ["search", "query"].includes(args[0]),
);
expect(searchAndQueryCalls).toEqual([
["search", "test", "--json"],
["query", "test", "--json", "-n", String(maxResults)],
]);
await manager.close();
});

View File

@@ -261,7 +261,10 @@ export class QmdMemoryManager implements MemorySearchManager {
return [];
}
const qmdSearchCommand = this.qmd.searchMode;
const args = this.buildSearchArgs(qmdSearchCommand, trimmed, limit, collectionFilterArgs);
const args = this.buildSearchArgs(qmdSearchCommand, trimmed, limit);
if (qmdSearchCommand === "query") {
args.push(...collectionFilterArgs);
}
let stdout: string;
let stderr: string;
try {
@@ -274,10 +277,11 @@ export class QmdMemoryManager implements MemorySearchManager {
`qmd ${qmdSearchCommand} does not support configured flags; retrying search with qmd query`,
);
try {
const fallback = await this.runQmd(
this.buildSearchArgs("query", trimmed, limit, collectionFilterArgs),
{ timeoutMs: this.qmd.limits.timeoutMs },
);
const fallbackArgs = this.buildSearchArgs("query", trimmed, limit);
fallbackArgs.push(...collectionFilterArgs);
const fallback = await this.runQmd(fallbackArgs, {
timeoutMs: this.qmd.limits.timeoutMs,
});
stdout = fallback.stdout;
stderr = fallback.stderr;
} catch (fallbackErr) {
@@ -1011,10 +1015,9 @@ export class QmdMemoryManager implements MemorySearchManager {
command: "query" | "search" | "vsearch",
query: string,
limit: number,
collectionFilterArgs: string[],
): string[] {
if (command === "query") {
return ["query", query, "--json", "-n", String(limit), ...collectionFilterArgs];
return ["query", query, "--json", "-n", String(limit)];
}
return [command, query, "--json"];
}