fix(qmd): normalize direct file collection paths

This commit is contained in:
SYU8384
2026-04-12 15:44:03 +09:00
parent 7e45272319
commit 3570aa55a7
2 changed files with 46 additions and 4 deletions

View File

@@ -154,6 +154,38 @@ describe("resolveMemoryBackendConfig", () => {
expect(custom?.path).toBe(path.resolve(workspaceRoot, "notes"));
});
it("normalizes direct file paths into parent-root collections", async () => {
const tmpRoot = await fs.mkdtemp(path.join(os.tmpdir(), "qmd-backend-config-file-"));
const workspaceDir = path.join(tmpRoot, "workspace");
const sharedDir = path.join(tmpRoot, "shared");
const sharedFile = path.join(sharedDir, "shared-memory.md");
try {
await fs.mkdir(workspaceDir, { recursive: true });
await fs.mkdir(sharedDir, { recursive: true });
await fs.writeFile(sharedFile, "# Shared memory\n", "utf8");
const cfg = {
agents: {
defaults: { workspace: workspaceDir },
list: [{ id: "main", default: true, workspace: workspaceDir }],
},
memory: {
backend: "qmd",
qmd: {
includeDefaultMemory: false,
paths: [{ path: sharedFile, name: "shared-memory" }],
},
},
} as OpenClawConfig;
const resolved = resolveMemoryBackendConfig({ cfg, agentId: "main" });
const collection = resolved.qmd?.collections.find((entry) => entry.name === "shared-memory");
expect(collection).toBeDefined();
expect(collection?.path).toBe(sharedDir);
expect(collection?.pattern).toBe("shared-memory.md");
} finally {
await fs.rm(tmpRoot, { recursive: true, force: true });
}
});
it("scopes qmd collection names per agent", () => {
const cfg = qmdMultiAgentConfig([{ path: "notes", name: "workspace", pattern: "**/*.md" }]);
const mainNames = resolveCollectionNamesForAgent(cfg, "main");

View File

@@ -283,21 +283,31 @@ function resolveCustomPaths(
} catch {
return;
}
const pattern = normalizeOptionalString(entry.pattern) || "**/*.md";
const dedupeKey = `${resolved}\u0000${pattern}`;
let collectionPath = resolved;
let pattern = normalizeOptionalString(entry.pattern) || "**/*.md";
try {
const stat = fs.statSync(resolved);
if (stat.isFile()) {
collectionPath = path.dirname(resolved);
pattern = path.basename(resolved);
}
} catch {
// Leave unresolved/missing paths as-is; downstream collection setup can decide how to handle them.
}
const dedupeKey = `${collectionPath}\u0000${pattern}`;
if (seenRoots.has(dedupeKey)) {
return;
}
seenRoots.add(dedupeKey);
const explicitName = entry.name?.trim();
const baseName =
explicitName && !isPathInsideRoot(resolved, workspaceDir)
explicitName && !isPathInsideRoot(collectionPath, workspaceDir)
? explicitName
: scopeCollectionBase(explicitName || `custom-${index + 1}`, agentId);
const name = ensureUniqueName(baseName, existing);
collections.push({
name,
path: resolved,
path: collectionPath,
pattern,
kind: "custom",
});