diff --git a/CHANGELOG.md b/CHANGELOG.md index 667c88e56f7..eb9d23cfe2b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ Docs: https://docs.openclaw.ai ### Fixes +- Memory/QMD: prefer QMD's `--mask` collection pattern flag so root memory indexing stays scoped to `MEMORY.md` instead of widening to every markdown file in the workspace. Thanks @codex. - Codex harness: normalize cached input tokens before session/context accounting so prompt cache reads are not double-counted in `/status`, `session_status`, or persisted `sessionEntry.totalTokens`. Fixes #69298. Thanks @richardmqq. - Hooks/session-memory: use the host local timezone for memory filenames, fallback timestamp slugs, and markdown headers instead of UTC dates. Fixes #46703. (#46721) Thanks @Astro-Han. - Feishu: extract quoted/replied interactive-card text across schema 1.0, schema 2.0, i18n, template-variable, and post-format fallback shapes without carrying broad generated/config churn from related parser experiments. (#38776, #60383, #42218, #45936) Thanks @lishuaigit, @lskun, @just2gooo, and @Br1an67. diff --git a/extensions/memory-core/src/memory/qmd-compat.test.ts b/extensions/memory-core/src/memory/qmd-compat.test.ts index 2846167d8cb..9e926ebd730 100644 --- a/extensions/memory-core/src/memory/qmd-compat.test.ts +++ b/extensions/memory-core/src/memory/qmd-compat.test.ts @@ -2,12 +2,12 @@ import { describe, expect, it } from "vitest"; import { resolveQmdCollectionPatternFlags } from "./qmd-compat.js"; describe("resolveQmdCollectionPatternFlags", () => { - it("prefers modern --glob by default and falls back to legacy --mask", () => { - expect(resolveQmdCollectionPatternFlags(null)).toEqual(["--glob", "--mask"]); - expect(resolveQmdCollectionPatternFlags("--glob")).toEqual(["--glob", "--mask"]); - }); - - it("keeps preferring legacy --mask after a legacy-only qmd succeeds", () => { + it("prefers --mask by default and falls back to --glob", () => { + expect(resolveQmdCollectionPatternFlags(null)).toEqual(["--mask", "--glob"]); expect(resolveQmdCollectionPatternFlags("--mask")).toEqual(["--mask", "--glob"]); }); + + it("keeps preferring --glob after a glob-only qmd succeeds", () => { + expect(resolveQmdCollectionPatternFlags("--glob")).toEqual(["--glob", "--mask"]); + }); }); diff --git a/extensions/memory-core/src/memory/qmd-compat.ts b/extensions/memory-core/src/memory/qmd-compat.ts index 5c1c5240be4..30090cc384b 100644 --- a/extensions/memory-core/src/memory/qmd-compat.ts +++ b/extensions/memory-core/src/memory/qmd-compat.ts @@ -3,5 +3,5 @@ export type QmdCollectionPatternFlag = "--glob" | "--mask"; export function resolveQmdCollectionPatternFlags( preferredFlag: QmdCollectionPatternFlag | null, ): QmdCollectionPatternFlag[] { - return preferredFlag === "--mask" ? ["--mask", "--glob"] : ["--glob", "--mask"]; + return preferredFlag === "--glob" ? ["--glob", "--mask"] : ["--mask", "--glob"]; } diff --git a/extensions/memory-core/src/memory/qmd-manager.test.ts b/extensions/memory-core/src/memory/qmd-manager.test.ts index 5a1b4a9bcc6..7a35e8a5242 100644 --- a/extensions/memory-core/src/memory/qmd-manager.test.ts +++ b/extensions/memory-core/src/memory/qmd-manager.test.ts @@ -929,7 +929,12 @@ describe("QmdMemoryManager", () => { const child = createMockChild({ autoClose: false }); const pathArg = args[2] ?? ""; const name = args[args.indexOf("--name") + 1] ?? ""; - const pattern = args[args.indexOf("--glob") + 1] ?? args[args.indexOf("--mask") + 1] ?? ""; + const patternIndex = args.includes("--glob") + ? args.indexOf("--glob") + 1 + : args.includes("--mask") + ? args.indexOf("--mask") + 1 + : -1; + const pattern = patternIndex >= 0 ? (args[patternIndex] ?? "") : ""; const hasConflict = [...listedCollections.entries()].some( ([existingName, info]) => existingName !== name && info.path === pathArg && info.pattern === pattern, @@ -1023,7 +1028,12 @@ describe("QmdMemoryManager", () => { if (args[0] === "collection" && args[1] === "add") { const child = createMockChild({ autoClose: false }); const name = args[args.indexOf("--name") + 1] ?? ""; - const pattern = args[args.indexOf("--glob") + 1] ?? args[args.indexOf("--mask") + 1] ?? ""; + const patternIndex = args.includes("--glob") + ? args.indexOf("--glob") + 1 + : args.includes("--mask") + ? args.indexOf("--mask") + 1 + : -1; + const pattern = patternIndex >= 0 ? (args[patternIndex] ?? "") : ""; const attempts = addAttempts.get(name) ?? 0; addAttempts.set(name, attempts + 1); if (name === "memory-root-main" && attempts === 0) { @@ -1097,7 +1107,12 @@ describe("QmdMemoryManager", () => { if (args[0] === "collection" && args[1] === "add") { const child = createMockChild({ autoClose: false }); const name = args[args.indexOf("--name") + 1] ?? ""; - const pattern = args[args.indexOf("--glob") + 1] ?? args[args.indexOf("--mask") + 1] ?? ""; + const patternIndex = args.includes("--glob") + ? args.indexOf("--glob") + 1 + : args.includes("--mask") + ? args.indexOf("--mask") + 1 + : -1; + const pattern = patternIndex >= 0 ? (args[patternIndex] ?? "") : ""; added.set(name, pattern); queueMicrotask(() => child.closeWith(0)); return child; @@ -1113,7 +1128,7 @@ describe("QmdMemoryManager", () => { expect(removed).not.toContain("memory-dir-main"); }); - it("falls back to --mask when qmd collection add rejects --glob", async () => { + it("falls back to --glob when qmd collection add rejects --mask", async () => { cfg = { ...cfg, memory: { @@ -1137,8 +1152,8 @@ describe("QmdMemoryManager", () => { const child = createMockChild({ autoClose: false }); const flag = args.includes("--glob") ? "--glob" : args.includes("--mask") ? "--mask" : ""; addFlagCalls.push(flag); - if (flag === "--glob") { - emitAndClose(child, "stderr", "unknown flag: --glob", 1); + if (flag === "--mask") { + emitAndClose(child, "stderr", "unknown flag: --mask", 1); return child; } queueMicrotask(() => child.closeWith(0)); @@ -1150,7 +1165,7 @@ describe("QmdMemoryManager", () => { const { manager } = await createManager({ mode: "full" }); await manager.close(); - expect(addFlagCalls).toEqual(["--glob", "--mask", "--mask"]); + expect(addFlagCalls).toEqual(["--mask", "--glob", "--glob"]); expect(logWarnMock).toHaveBeenCalledWith( expect.stringContaining("retrying with legacy compatibility flag"), ); diff --git a/extensions/memory-core/src/memory/qmd-manager.ts b/extensions/memory-core/src/memory/qmd-manager.ts index c0a3b075737..5ca07dad05b 100644 --- a/extensions/memory-core/src/memory/qmd-manager.ts +++ b/extensions/memory-core/src/memory/qmd-manager.ts @@ -333,7 +333,7 @@ export class QmdMemoryManager implements MemorySearchManager { private attemptedNullByteCollectionRepair = false; private attemptedDuplicateDocumentRepair = false; private readonly sessionWarm = new Set(); - private collectionPatternFlag: QmdCollectionPatternFlag | null = "--glob"; + private collectionPatternFlag: QmdCollectionPatternFlag | null = "--mask"; private constructor(params: { agentId: string;