mirror of
https://github.com/clawdbot/clawdbot.git
synced 2026-01-31 11:27:45 +01:00
fix(memory-qmd): write XDG index.yml + legacy compat
This commit is contained in:
committed by
vignesh07
parent
bdf692ae54
commit
e274914b86
@@ -268,7 +268,7 @@ export async function runMemoryStatus(opts: MemoryCommandOptions) {
|
||||
| Awaited<ReturnType<typeof manager.probeEmbeddingAvailability>>
|
||||
| undefined;
|
||||
let indexError: string | undefined;
|
||||
const syncFn = manager.sync;
|
||||
const syncFn = manager.sync ? manager.sync.bind(manager) : undefined;
|
||||
if (deep) {
|
||||
await withProgress({ label: "Checking memory…", total: 2 }, async (progress) => {
|
||||
progress.setLabel("Probing vector…");
|
||||
@@ -517,7 +517,7 @@ export function registerMemoryCli(program: Command) {
|
||||
},
|
||||
run: async (manager) => {
|
||||
try {
|
||||
const syncFn = manager.sync;
|
||||
const syncFn = manager.sync ? manager.sync.bind(manager) : undefined;
|
||||
if (opts.verbose) {
|
||||
const status = manager.status();
|
||||
const rich = isRich();
|
||||
|
||||
@@ -6,7 +6,7 @@ import { EventEmitter } from "node:events";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
vi.mock("node:child_process", () => {
|
||||
const spawn = vi.fn((cmd: string, _args: string[]) => {
|
||||
const spawn = vi.fn((_cmd: string, _args: string[]) => {
|
||||
const stdout = new EventEmitter();
|
||||
const stderr = new EventEmitter();
|
||||
const child = new EventEmitter() as {
|
||||
|
||||
@@ -74,6 +74,8 @@ export class QmdMemoryManager implements MemorySearchManager {
|
||||
private readonly xdgCacheHome: string;
|
||||
private readonly collectionsFile: string;
|
||||
private readonly indexPath: string;
|
||||
private readonly legacyCollectionsFile: string;
|
||||
private readonly legacyIndexPath: string;
|
||||
private readonly env: NodeJS.ProcessEnv;
|
||||
private readonly collectionRoots = new Map<string, CollectionRoot>();
|
||||
private readonly sources = new Set<MemorySource>();
|
||||
@@ -107,6 +109,12 @@ export class QmdMemoryManager implements MemorySearchManager {
|
||||
this.xdgCacheHome = path.join(this.qmdDir, "xdg-cache");
|
||||
this.collectionsFile = path.join(this.xdgConfigHome, "qmd", "index.yml");
|
||||
this.indexPath = path.join(this.xdgCacheHome, "qmd", "index.sqlite");
|
||||
|
||||
// Legacy locations (older builds wrote here). Keep them in sync via symlinks
|
||||
// so upgrades don't strand an empty index.
|
||||
this.legacyCollectionsFile = path.join(this.qmdDir, "config", "index.yml");
|
||||
this.legacyIndexPath = path.join(this.qmdDir, "cache", "index.sqlite");
|
||||
|
||||
this.env = {
|
||||
...process.env,
|
||||
XDG_CONFIG_HOME: this.xdgConfigHome,
|
||||
@@ -141,8 +149,13 @@ export class QmdMemoryManager implements MemorySearchManager {
|
||||
await fs.mkdir(path.dirname(this.collectionsFile), { recursive: true });
|
||||
await fs.mkdir(path.dirname(this.indexPath), { recursive: true });
|
||||
|
||||
// Legacy dirs
|
||||
await fs.mkdir(path.dirname(this.legacyCollectionsFile), { recursive: true });
|
||||
await fs.mkdir(path.dirname(this.legacyIndexPath), { recursive: true });
|
||||
|
||||
this.bootstrapCollections();
|
||||
await this.writeCollectionsConfig();
|
||||
await this.ensureLegacyCompatSymlinks();
|
||||
|
||||
if (this.qmd.update.onBoot) {
|
||||
await this.runUpdate("boot", true);
|
||||
@@ -176,6 +189,42 @@ export class QmdMemoryManager implements MemorySearchManager {
|
||||
}
|
||||
const yaml = YAML.stringify({ collections }, { indent: 2, lineWidth: 0 });
|
||||
await fs.writeFile(this.collectionsFile, yaml, "utf-8");
|
||||
|
||||
// Also write legacy path so older qmd homes remain usable.
|
||||
await fs.writeFile(this.legacyCollectionsFile, yaml, "utf-8");
|
||||
}
|
||||
|
||||
private async ensureLegacyCompatSymlinks(): Promise<void> {
|
||||
// Best-effort: keep legacy locations pointing at the XDG locations.
|
||||
// This helps when users have old state dirs on disk.
|
||||
try {
|
||||
await fs.rm(this.legacyCollectionsFile, { force: true });
|
||||
} catch {}
|
||||
try {
|
||||
await fs.symlink(this.collectionsFile, this.legacyCollectionsFile);
|
||||
} catch {}
|
||||
|
||||
try {
|
||||
// If a legacy index already exists (from an older version), prefer it by
|
||||
// linking the XDG path to the legacy DB.
|
||||
const legacyExists = await fs
|
||||
.stat(this.legacyIndexPath)
|
||||
.then(() => true)
|
||||
.catch(() => false);
|
||||
const xdgExists = await fs
|
||||
.stat(this.indexPath)
|
||||
.then(() => true)
|
||||
.catch(() => false);
|
||||
|
||||
if (legacyExists && !xdgExists) {
|
||||
await fs.symlink(this.legacyIndexPath, this.indexPath);
|
||||
} else if (!legacyExists && xdgExists) {
|
||||
// nothing to do
|
||||
} else if (!legacyExists && !xdgExists) {
|
||||
// Create an empty file so the path exists for read-only opens later.
|
||||
await fs.writeFile(this.indexPath, "");
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
|
||||
async search(
|
||||
|
||||
@@ -59,6 +59,7 @@ describe("getMemorySearchManager caching", () => {
|
||||
const second = await getMemorySearchManager({ cfg, agentId: "main" });
|
||||
|
||||
expect(first.manager).toBe(second.manager);
|
||||
// eslint-disable-next-line @typescript-eslint/unbound-method
|
||||
expect(QmdMemoryManager.create).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,7 +2,6 @@ import { createSubsystemLogger } from "../logging/subsystem.js";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import { resolveMemoryBackendConfig } from "./backend-config.js";
|
||||
import type { ResolvedQmdConfig } from "./backend-config.js";
|
||||
import type { MemoryIndexManager } from "./manager.js";
|
||||
import type {
|
||||
MemoryEmbeddingProbeResult,
|
||||
MemorySearchManager,
|
||||
|
||||
Reference in New Issue
Block a user