mirror of
https://github.com/clawdbot/clawdbot.git
synced 2026-01-31 19:37: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>>
|
| Awaited<ReturnType<typeof manager.probeEmbeddingAvailability>>
|
||||||
| undefined;
|
| undefined;
|
||||||
let indexError: string | undefined;
|
let indexError: string | undefined;
|
||||||
const syncFn = manager.sync;
|
const syncFn = manager.sync ? manager.sync.bind(manager) : undefined;
|
||||||
if (deep) {
|
if (deep) {
|
||||||
await withProgress({ label: "Checking memory…", total: 2 }, async (progress) => {
|
await withProgress({ label: "Checking memory…", total: 2 }, async (progress) => {
|
||||||
progress.setLabel("Probing vector…");
|
progress.setLabel("Probing vector…");
|
||||||
@@ -517,7 +517,7 @@ export function registerMemoryCli(program: Command) {
|
|||||||
},
|
},
|
||||||
run: async (manager) => {
|
run: async (manager) => {
|
||||||
try {
|
try {
|
||||||
const syncFn = manager.sync;
|
const syncFn = manager.sync ? manager.sync.bind(manager) : undefined;
|
||||||
if (opts.verbose) {
|
if (opts.verbose) {
|
||||||
const status = manager.status();
|
const status = manager.status();
|
||||||
const rich = isRich();
|
const rich = isRich();
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { EventEmitter } from "node:events";
|
|||||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
vi.mock("node:child_process", () => {
|
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 stdout = new EventEmitter();
|
||||||
const stderr = new EventEmitter();
|
const stderr = new EventEmitter();
|
||||||
const child = new EventEmitter() as {
|
const child = new EventEmitter() as {
|
||||||
|
|||||||
@@ -74,6 +74,8 @@ export class QmdMemoryManager implements MemorySearchManager {
|
|||||||
private readonly xdgCacheHome: string;
|
private readonly xdgCacheHome: string;
|
||||||
private readonly collectionsFile: string;
|
private readonly collectionsFile: string;
|
||||||
private readonly indexPath: string;
|
private readonly indexPath: string;
|
||||||
|
private readonly legacyCollectionsFile: string;
|
||||||
|
private readonly legacyIndexPath: string;
|
||||||
private readonly env: NodeJS.ProcessEnv;
|
private readonly env: NodeJS.ProcessEnv;
|
||||||
private readonly collectionRoots = new Map<string, CollectionRoot>();
|
private readonly collectionRoots = new Map<string, CollectionRoot>();
|
||||||
private readonly sources = new Set<MemorySource>();
|
private readonly sources = new Set<MemorySource>();
|
||||||
@@ -107,6 +109,12 @@ export class QmdMemoryManager implements MemorySearchManager {
|
|||||||
this.xdgCacheHome = path.join(this.qmdDir, "xdg-cache");
|
this.xdgCacheHome = path.join(this.qmdDir, "xdg-cache");
|
||||||
this.collectionsFile = path.join(this.xdgConfigHome, "qmd", "index.yml");
|
this.collectionsFile = path.join(this.xdgConfigHome, "qmd", "index.yml");
|
||||||
this.indexPath = path.join(this.xdgCacheHome, "qmd", "index.sqlite");
|
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 = {
|
this.env = {
|
||||||
...process.env,
|
...process.env,
|
||||||
XDG_CONFIG_HOME: this.xdgConfigHome,
|
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.collectionsFile), { recursive: true });
|
||||||
await fs.mkdir(path.dirname(this.indexPath), { 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();
|
this.bootstrapCollections();
|
||||||
await this.writeCollectionsConfig();
|
await this.writeCollectionsConfig();
|
||||||
|
await this.ensureLegacyCompatSymlinks();
|
||||||
|
|
||||||
if (this.qmd.update.onBoot) {
|
if (this.qmd.update.onBoot) {
|
||||||
await this.runUpdate("boot", true);
|
await this.runUpdate("boot", true);
|
||||||
@@ -176,6 +189,42 @@ export class QmdMemoryManager implements MemorySearchManager {
|
|||||||
}
|
}
|
||||||
const yaml = YAML.stringify({ collections }, { indent: 2, lineWidth: 0 });
|
const yaml = YAML.stringify({ collections }, { indent: 2, lineWidth: 0 });
|
||||||
await fs.writeFile(this.collectionsFile, yaml, "utf-8");
|
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(
|
async search(
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ describe("getMemorySearchManager caching", () => {
|
|||||||
const second = await getMemorySearchManager({ cfg, agentId: "main" });
|
const second = await getMemorySearchManager({ cfg, agentId: "main" });
|
||||||
|
|
||||||
expect(first.manager).toBe(second.manager);
|
expect(first.manager).toBe(second.manager);
|
||||||
|
// eslint-disable-next-line @typescript-eslint/unbound-method
|
||||||
expect(QmdMemoryManager.create).toHaveBeenCalledTimes(1);
|
expect(QmdMemoryManager.create).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { createSubsystemLogger } from "../logging/subsystem.js";
|
|||||||
import type { OpenClawConfig } from "../config/config.js";
|
import type { OpenClawConfig } from "../config/config.js";
|
||||||
import { resolveMemoryBackendConfig } from "./backend-config.js";
|
import { resolveMemoryBackendConfig } from "./backend-config.js";
|
||||||
import type { ResolvedQmdConfig } from "./backend-config.js";
|
import type { ResolvedQmdConfig } from "./backend-config.js";
|
||||||
import type { MemoryIndexManager } from "./manager.js";
|
|
||||||
import type {
|
import type {
|
||||||
MemoryEmbeddingProbeResult,
|
MemoryEmbeddingProbeResult,
|
||||||
MemorySearchManager,
|
MemorySearchManager,
|
||||||
|
|||||||
Reference in New Issue
Block a user