feat(memory): allow QMD searches via mcporter keep-alive (openclaw#19617) thanks @vignesh07

Verified:
- pnpm build
- pnpm check
- pnpm test:macmini

Co-authored-by: vignesh07 <1436853+vignesh07@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
This commit is contained in:
Vignesh
2026-02-21 16:54:33 -08:00
committed by GitHub
parent 2e8e357bf7
commit 3317b49d3b
10 changed files with 482 additions and 8 deletions

View File

@@ -148,6 +148,8 @@ describe("QmdMemoryManager", () => {
afterEach(async () => {
vi.useRealTimers();
delete process.env.OPENCLAW_STATE_DIR;
delete (globalThis as Record<string, unknown>).__openclawMcporterDaemonStart;
delete (globalThis as Record<string, unknown>).__openclawMcporterColdStartWarned;
});
it("debounces back-to-back sync calls", async () => {
@@ -910,6 +912,170 @@ describe("QmdMemoryManager", () => {
await manager.close();
});
it("runs qmd searches via mcporter and warns when startDaemon=false", async () => {
cfg = {
...cfg,
memory: {
backend: "qmd",
qmd: {
includeDefaultMemory: false,
update: { interval: "0s", debounceMs: 60_000, onBoot: false },
paths: [{ path: workspaceDir, pattern: "**/*.md", name: "workspace" }],
mcporter: { enabled: true, serverName: "qmd", startDaemon: false },
},
},
} as OpenClawConfig;
spawnMock.mockImplementation((cmd: string, args: string[]) => {
const child = createMockChild({ autoClose: false });
if (cmd === "mcporter" && args[0] === "call") {
emitAndClose(child, "stdout", JSON.stringify({ results: [] }));
return child;
}
emitAndClose(child, "stdout", "[]");
return child;
});
const { manager } = await createManager();
logWarnMock.mockClear();
await expect(
manager.search("hello", { sessionKey: "agent:main:slack:dm:u123" }),
).resolves.toEqual([]);
const mcporterCalls = spawnMock.mock.calls.filter((call: unknown[]) => call[0] === "mcporter");
expect(mcporterCalls.length).toBeGreaterThan(0);
expect(mcporterCalls.some((call: unknown[]) => (call[1] as string[])[0] === "daemon")).toBe(
false,
);
expect(logWarnMock).toHaveBeenCalledWith(expect.stringContaining("cold-start"));
await manager.close();
});
it("passes manager-scoped XDG env to mcporter commands", async () => {
cfg = {
...cfg,
memory: {
backend: "qmd",
qmd: {
includeDefaultMemory: false,
update: { interval: "0s", debounceMs: 60_000, onBoot: false },
paths: [{ path: workspaceDir, pattern: "**/*.md", name: "workspace" }],
mcporter: { enabled: true, serverName: "qmd", startDaemon: false },
},
},
} as OpenClawConfig;
spawnMock.mockImplementation((cmd: string, args: string[]) => {
const child = createMockChild({ autoClose: false });
if (cmd === "mcporter" && args[0] === "call") {
emitAndClose(child, "stdout", JSON.stringify({ results: [] }));
return child;
}
emitAndClose(child, "stdout", "[]");
return child;
});
const { manager } = await createManager();
await manager.search("hello", { sessionKey: "agent:main:slack:dm:u123" });
const mcporterCall = spawnMock.mock.calls.find(
(call: unknown[]) => call[0] === "mcporter" && (call[1] as string[])[0] === "call",
);
expect(mcporterCall).toBeDefined();
const spawnOpts = mcporterCall?.[2] as { env?: NodeJS.ProcessEnv } | undefined;
expect(spawnOpts?.env?.XDG_CONFIG_HOME).toContain("/agents/main/qmd/xdg-config");
expect(spawnOpts?.env?.XDG_CACHE_HOME).toContain("/agents/main/qmd/xdg-cache");
await manager.close();
});
it("retries mcporter daemon start after a failure", async () => {
cfg = {
...cfg,
memory: {
backend: "qmd",
qmd: {
includeDefaultMemory: false,
update: { interval: "0s", debounceMs: 60_000, onBoot: false },
paths: [{ path: workspaceDir, pattern: "**/*.md", name: "workspace" }],
mcporter: { enabled: true, serverName: "qmd", startDaemon: true },
},
},
} as OpenClawConfig;
let daemonAttempts = 0;
spawnMock.mockImplementation((cmd: string, args: string[]) => {
const child = createMockChild({ autoClose: false });
if (cmd === "mcporter" && args[0] === "daemon") {
daemonAttempts += 1;
if (daemonAttempts === 1) {
emitAndClose(child, "stderr", "failed", 1);
} else {
emitAndClose(child, "stdout", "");
}
return child;
}
if (cmd === "mcporter" && args[0] === "call") {
emitAndClose(child, "stdout", JSON.stringify({ results: [] }));
return child;
}
emitAndClose(child, "stdout", "[]");
return child;
});
const { manager } = await createManager();
await manager.search("one", { sessionKey: "agent:main:slack:dm:u123" });
await manager.search("two", { sessionKey: "agent:main:slack:dm:u123" });
expect(daemonAttempts).toBe(2);
await manager.close();
});
it("starts the mcporter daemon only once when enabled", async () => {
cfg = {
...cfg,
memory: {
backend: "qmd",
qmd: {
includeDefaultMemory: false,
update: { interval: "0s", debounceMs: 60_000, onBoot: false },
paths: [{ path: workspaceDir, pattern: "**/*.md", name: "workspace" }],
mcporter: { enabled: true, serverName: "qmd", startDaemon: true },
},
},
} as OpenClawConfig;
spawnMock.mockImplementation((cmd: string, args: string[]) => {
const child = createMockChild({ autoClose: false });
if (cmd === "mcporter" && args[0] === "daemon") {
emitAndClose(child, "stdout", "");
return child;
}
if (cmd === "mcporter" && args[0] === "call") {
emitAndClose(child, "stdout", JSON.stringify({ results: [] }));
return child;
}
emitAndClose(child, "stdout", "[]");
return child;
});
const { manager } = await createManager();
await manager.search("one", { sessionKey: "agent:main:slack:dm:u123" });
await manager.search("two", { sessionKey: "agent:main:slack:dm:u123" });
const daemonStarts = spawnMock.mock.calls.filter(
(call: unknown[]) => call[0] === "mcporter" && (call[1] as string[])[0] === "daemon",
);
expect(daemonStarts).toHaveLength(1);
await manager.close();
});
it("fails closed when no managed collections are configured", async () => {
cfg = {
...cfg,