fix: scope qmd root memory collection

This commit is contained in:
Peter Steinberger
2026-04-27 01:01:49 +01:00
parent d2786fb969
commit be56f172ab
5 changed files with 31 additions and 15 deletions

View File

@@ -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.

View File

@@ -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"]);
});
});

View File

@@ -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"];
}

View File

@@ -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"),
);

View File

@@ -333,7 +333,7 @@ export class QmdMemoryManager implements MemorySearchManager {
private attemptedNullByteCollectionRepair = false;
private attemptedDuplicateDocumentRepair = false;
private readonly sessionWarm = new Set<string>();
private collectionPatternFlag: QmdCollectionPatternFlag | null = "--glob";
private collectionPatternFlag: QmdCollectionPatternFlag | null = "--mask";
private constructor(params: {
agentId: string;