diff --git a/src/commands/status.scan.shared.test.ts b/src/commands/status.scan.shared.test.ts index 21bc68be30ad..eb476bda4efe 100644 --- a/src/commands/status.scan.shared.test.ts +++ b/src/commands/status.scan.shared.test.ts @@ -573,6 +573,66 @@ describe("resolveSharedMemoryStatusSnapshot", () => { expect(getMemorySearchManager).not.toHaveBeenCalled(); }); + it("recognizes shipped memory tables before the manager migrates them", async () => { + const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-status-memory-")); + const databasePath = path.join(tempDir, "openclaw-agent.sqlite"); + const db = new DatabaseSync(databasePath); + db.exec(` + CREATE TABLE meta (key TEXT PRIMARY KEY, value TEXT NOT NULL); + CREATE TABLE files ( + path TEXT PRIMARY KEY, + source TEXT NOT NULL DEFAULT 'memory', + hash TEXT NOT NULL, + mtime INTEGER NOT NULL, + size INTEGER NOT NULL + ); + CREATE TABLE chunks ( + id TEXT PRIMARY KEY, + path TEXT NOT NULL, + source TEXT NOT NULL DEFAULT 'memory', + start_line INTEGER NOT NULL, + end_line INTEGER NOT NULL, + hash TEXT NOT NULL, + model TEXT NOT NULL, + text TEXT NOT NULL, + embedding TEXT NOT NULL, + updated_at INTEGER NOT NULL + ); + INSERT INTO files VALUES ('MEMORY.md', 'memory', 'file-hash', 10, 20); + `); + db.close(); + const manager = { + probeVectorStoreAvailability: vi.fn(async () => true), + probeVectorAvailability: vi.fn(async () => true), + status: vi.fn(() => ({ + backend: "builtin" as const, + provider: "openai", + files: 1, + chunks: 0, + vector: { enabled: true, available: true }, + fts: { enabled: true, available: true }, + })), + close: vi.fn(async () => {}), + }; + const getMemorySearchManager = vi.fn(async () => ({ manager })); + + try { + const result = await resolveSharedMemoryStatusSnapshot({ + cfg: {}, + agentStatus: { defaultId: "main" }, + memoryPlugin: { enabled: true, slot: "memory-core" }, + resolveMemoryConfig: vi.fn(() => ({ store: { databasePath } })), + getMemorySearchManager, + requireDefaultDatabasePath: () => databasePath, + }); + + expect(getMemorySearchManager).toHaveBeenCalledOnce(); + expect(result?.files).toBe(1); + } finally { + fs.rmSync(tempDir, { recursive: true, force: true }); + } + }); + it("does not initialize memory status for an agent database owned by another feature", async () => { const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-status-memory-")); const databasePath = path.join(tempDir, "openclaw-agent.sqlite"); diff --git a/src/commands/status.scan.shared.ts b/src/commands/status.scan.shared.ts index b4c930480f30..2783182a6c67 100644 --- a/src/commands/status.scan.shared.ts +++ b/src/commands/status.scan.shared.ts @@ -56,35 +56,51 @@ function hasBuiltInMemoryState(databasePath: string): boolean { let db: DatabaseSync | undefined; try { db = new DatabaseSync(databasePath, { readOnly: true }); - const builtInMemoryTables = [ - MEMORY_INDEX_META_TABLE, - MEMORY_INDEX_SOURCES_TABLE, - MEMORY_INDEX_CHUNKS_TABLE, + const builtInMemoryTableSets = [ + { + meta: MEMORY_INDEX_META_TABLE, + sources: MEMORY_INDEX_SOURCES_TABLE, + chunks: MEMORY_INDEX_CHUNKS_TABLE, + }, + { + meta: "meta", + sources: "files", + chunks: "chunks", + }, ] as const; + const builtInMemoryTables = builtInMemoryTableSets.flatMap(({ meta, sources, chunks }) => [ + meta, + sources, + chunks, + ]); const tableNames = new Set( ( db - .prepare(`SELECT name FROM sqlite_master WHERE type = 'table' AND name IN (?, ?, ?)`) + .prepare( + `SELECT name FROM sqlite_master WHERE type = 'table' AND name IN (${builtInMemoryTables.map(() => "?").join(", ")})`, + ) .all(...builtInMemoryTables) as Array<{ name?: unknown }> ) .map((row) => row.name) .filter((name): name is string => typeof name === "string"), ); - if ( - tableNames.has(MEMORY_INDEX_META_TABLE) && - db - .prepare(`SELECT 1 AS ok FROM ${MEMORY_INDEX_META_TABLE} WHERE key = ? LIMIT 1`) - .get(MEMORY_INDEX_META_KEY) - ) { - return true; - } - for (const tableName of [MEMORY_INDEX_SOURCES_TABLE, MEMORY_INDEX_CHUNKS_TABLE] as const) { + for (const tables of builtInMemoryTableSets) { if ( - tableNames.has(tableName) && - db.prepare(`SELECT 1 AS ok FROM ${tableName} LIMIT 1`).get() + tableNames.has(tables.meta) && + db + .prepare(`SELECT 1 AS ok FROM ${tables.meta} WHERE key = ? LIMIT 1`) + .get(MEMORY_INDEX_META_KEY) ) { return true; } + for (const tableName of [tables.sources, tables.chunks]) { + if ( + tableNames.has(tableName) && + db.prepare(`SELECT 1 AS ok FROM ${tableName} LIMIT 1`).get() + ) { + return true; + } + } } return false; } catch {