mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-24 03:39:50 +00:00
fix(memory): preserve qmd lexical search for hyphenated queries (#81423)
This commit is contained in:
@@ -44,6 +44,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Feishu: detect SecretRef top-level credentials as a configured default account instead of treating object-backed app secrets as missing.
|
||||
- CLI/completion: resolve concrete PowerShell profile paths and reload commands during setup and doctor completion installation. Fixes #44296. (#83059) Thanks @yu-xin-c.
|
||||
- Providers/Google: preserve and recover Gemini 3 tool-call thought signatures during native replay so function-calling turns no longer fail with missing `thought_signature` 400s. Fixes #72879. (#80358) Thanks @abnershang.
|
||||
- Memory/QMD: keep lexical search on raw hyphenated queries while normalizing semantic QMD sub-searches, avoiding fallback to the builtin index for dashed identifiers and dates. Fixes #81328.
|
||||
- Memory-core: distinguish sqlite-vec load failures from missing semantic vector embeddings in degraded `memory index` warnings, so vector recall diagnostics point at unresolved dimensions instead of blaming sqlite-vec when the store is ready. Fixes #75624. (#83056) Thanks @xuruiray and @Noah3521.
|
||||
- Agents/subagents: preserve sandbox-peer controller ownership while routing completion announcements back to the originating run session, keeping subagent control and completion delivery scoped correctly. Fixes #80201. (#80242) Thanks @Jerry-Xin.
|
||||
- Gateway: continue restarting remaining channels when one hot-reload channel restart fails, while still reporting aggregate reload failure and rolling back plugin pre-replace stops. Fixes #83054. Thanks @zqchris.
|
||||
|
||||
@@ -2698,6 +2698,80 @@ describe("QmdMemoryManager", () => {
|
||||
await manager.close();
|
||||
});
|
||||
|
||||
it("keeps hyphenated tokens in lexical QMD searches while normalizing semantic searches", async () => {
|
||||
cfg = {
|
||||
...cfg,
|
||||
memory: {
|
||||
backend: "qmd",
|
||||
qmd: {
|
||||
includeDefaultMemory: false,
|
||||
searchMode: "query",
|
||||
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 (isMcporterCommand(cmd) && args[0] === "call") {
|
||||
expect(args[1]).toBe("qmd.query");
|
||||
const callArgs = JSON.parse(args[args.indexOf("--args") + 1]);
|
||||
expect(callArgs.searches).toEqual([
|
||||
{ type: "lex", query: "sqlite-vec-qmd backend health 2026-05-04 multi-agent" },
|
||||
{ type: "vec", query: "sqlite vec qmd backend health 2026 05 04 multi agent" },
|
||||
{ type: "hyde", query: "sqlite vec qmd backend health 2026 05 04 multi agent" },
|
||||
]);
|
||||
emitAndClose(child, "stdout", JSON.stringify({ results: [] }));
|
||||
return child;
|
||||
}
|
||||
emitAndClose(child, "stdout", "[]");
|
||||
return child;
|
||||
});
|
||||
|
||||
const { manager } = await createManager();
|
||||
await manager.search("sqlite-vec-qmd backend health 2026-05-04 multi-agent", {
|
||||
sessionKey: "agent:main:slack:dm:u123",
|
||||
});
|
||||
await manager.close();
|
||||
});
|
||||
|
||||
it("normalizes hyphenated tokens for vector-only QMD searches", async () => {
|
||||
cfg = {
|
||||
...cfg,
|
||||
memory: {
|
||||
backend: "qmd",
|
||||
qmd: {
|
||||
includeDefaultMemory: false,
|
||||
searchMode: "vsearch",
|
||||
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 (isMcporterCommand(cmd) && args[0] === "call") {
|
||||
expect(args[1]).toBe("qmd.query");
|
||||
const callArgs = JSON.parse(args[args.indexOf("--args") + 1]);
|
||||
expect(callArgs.searches).toEqual([{ type: "vec", query: "sqlite vec backend health" }]);
|
||||
emitAndClose(child, "stdout", JSON.stringify({ results: [] }));
|
||||
return child;
|
||||
}
|
||||
emitAndClose(child, "stdout", "[]");
|
||||
return child;
|
||||
});
|
||||
|
||||
const { manager } = await createManager();
|
||||
await manager.search("sqlite-vec backend health", {
|
||||
sessionKey: "agent:main:slack:dm:u123",
|
||||
});
|
||||
await manager.close();
|
||||
});
|
||||
|
||||
it("falls back to QMD <1.1 tool names when query tool is not found", async () => {
|
||||
// qmdMcpToolVersion is an instance field — each createManager() starts fresh.
|
||||
|
||||
|
||||
@@ -1952,21 +1952,22 @@ export class QmdMemoryManager implements MemorySearchManager {
|
||||
query: string,
|
||||
searchCommand?: string,
|
||||
): Array<{ type: string; query: string }> {
|
||||
const semanticQuery = normalizeQmdSemanticQuery(query);
|
||||
switch (searchCommand) {
|
||||
case "search":
|
||||
// BM25 keyword search only
|
||||
return [{ type: "lex", query }];
|
||||
case "vsearch":
|
||||
// Vector search only
|
||||
return [{ type: "vec", query }];
|
||||
return [{ type: "vec", query: semanticQuery }];
|
||||
case "query":
|
||||
case undefined:
|
||||
default:
|
||||
// Full hybrid: lex + vec + hyde (query expansion)
|
||||
return [
|
||||
{ type: "lex", query },
|
||||
{ type: "vec", query },
|
||||
{ type: "hyde", query },
|
||||
{ type: "vec", query: semanticQuery },
|
||||
{ type: "hyde", query: semanticQuery },
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -3149,3 +3150,7 @@ function resolveQmdManagerRuntimeConfig(
|
||||
contextLimits: resolveAgentContextLimits(cfg, agentId),
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeQmdSemanticQuery(query: string): string {
|
||||
return query.replace(/(\w)-(?=\w)/g, "$1 ");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user