From 59866dd253a9c151fac9a315cd99836ef3cef810 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Tue, 31 Mar 2026 23:13:50 +0900 Subject: [PATCH] fix(memory): restore readonly recovery helper seams --- .../src/memory/manager-sync-ops.ts | 32 ++++++------ extensions/memory-core/src/memory/manager.ts | 49 ++++++++++++++++++- 2 files changed, 64 insertions(+), 17 deletions(-) diff --git a/extensions/memory-core/src/memory/manager-sync-ops.ts b/extensions/memory-core/src/memory/manager-sync-ops.ts index 0dddc4955d1..11d10561818 100644 --- a/extensions/memory-core/src/memory/manager-sync-ops.ts +++ b/extensions/memory-core/src/memory/manager-sync-ops.ts @@ -85,6 +85,18 @@ const IGNORED_MEMORY_WATCH_DIR_NAMES = new Set([ const log = createSubsystemLogger("memory"); +export function openMemoryDatabaseAtPath(dbPath: string, allowExtension: boolean): DatabaseSync { + const dir = path.dirname(dbPath); + ensureDir(dir); + const { DatabaseSync } = requireNodeSqlite(); + const db = new DatabaseSync(dbPath, { allowExtension }); + // busy_timeout is per-connection and resets to 0 on restart. + // Set it on every open so concurrent processes retry instead of + // failing immediately with SQLITE_BUSY. + db.exec("PRAGMA busy_timeout = 5000"); + return db; +} + function shouldIgnoreMemoryWatchPath(watchPath: string): boolean { const normalized = path.normalize(watchPath); const parts = normalized.split(path.sep).map((segment) => segment.trim().toLowerCase()); @@ -259,19 +271,7 @@ export abstract class MemoryManagerSyncOps { protected openDatabase(): DatabaseSync { const dbPath = resolveUserPath(this.settings.store.path); - return this.openDatabaseAtPath(dbPath); - } - - private openDatabaseAtPath(dbPath: string): DatabaseSync { - const dir = path.dirname(dbPath); - ensureDir(dir); - const { DatabaseSync } = requireNodeSqlite(); - const db = new DatabaseSync(dbPath, { allowExtension: this.settings.store.vector.enabled }); - // busy_timeout is per-connection and resets to 0 on restart. - // Set it on every open so concurrent processes retry instead of - // failing immediately with SQLITE_BUSY. - db.exec("PRAGMA busy_timeout = 5000"); - return db; + return openMemoryDatabaseAtPath(dbPath, this.settings.store.vector.enabled); } private seedEmbeddingCache(sourceDb: DatabaseSync): void { @@ -1149,7 +1149,7 @@ export abstract class MemoryManagerSyncOps { }): Promise { const dbPath = resolveUserPath(this.settings.store.path); const tempDbPath = `${dbPath}.tmp-${randomUUID()}`; - const tempDb = this.openDatabaseAtPath(tempDbPath); + const tempDb = openMemoryDatabaseAtPath(tempDbPath, this.settings.store.vector.enabled); const originalDb = this.db; let originalDbClosed = false; @@ -1164,7 +1164,7 @@ export abstract class MemoryManagerSyncOps { const restoreOriginalState = () => { if (originalDbClosed) { - this.db = this.openDatabaseAtPath(dbPath); + this.db = openMemoryDatabaseAtPath(dbPath, this.settings.store.vector.enabled); } else { this.db = originalDb; } @@ -1237,7 +1237,7 @@ export abstract class MemoryManagerSyncOps { await this.swapIndexFiles(dbPath, tempDbPath); - this.db = this.openDatabaseAtPath(dbPath); + this.db = openMemoryDatabaseAtPath(dbPath, this.settings.store.vector.enabled); this.vectorReady = null; this.vector.available = null; this.vector.loadError = undefined; diff --git a/extensions/memory-core/src/memory/manager.ts b/extensions/memory-core/src/memory/manager.ts index 41db96f03cb..09560853183 100644 --- a/extensions/memory-core/src/memory/manager.ts +++ b/extensions/memory-core/src/memory/manager.ts @@ -726,7 +726,54 @@ export class MemoryIndexManager extends MemoryManagerEmbeddingOps implements Mem sessionFiles?: string[]; progress?: (update: MemorySyncProgressUpdate) => void; }): Promise { - await runMemorySyncWithReadonlyRecovery(this, params); + const thisManager = this; + const state: MemoryReadonlyRecoveryState = { + get closed() { + return thisManager.closed; + }, + get db() { + return thisManager.db; + }, + set db(value) { + thisManager.db = value; + }, + get vectorReady() { + return thisManager.vectorReady; + }, + set vectorReady(value) { + thisManager.vectorReady = value; + }, + vector: this.vector, + get readonlyRecoveryAttempts() { + return thisManager.readonlyRecoveryAttempts; + }, + set readonlyRecoveryAttempts(value) { + thisManager.readonlyRecoveryAttempts = value; + }, + get readonlyRecoverySuccesses() { + return thisManager.readonlyRecoverySuccesses; + }, + set readonlyRecoverySuccesses(value) { + thisManager.readonlyRecoverySuccesses = value; + }, + get readonlyRecoveryFailures() { + return thisManager.readonlyRecoveryFailures; + }, + set readonlyRecoveryFailures(value) { + thisManager.readonlyRecoveryFailures = value; + }, + get readonlyRecoveryLastError() { + return thisManager.readonlyRecoveryLastError; + }, + set readonlyRecoveryLastError(value) { + thisManager.readonlyRecoveryLastError = value; + }, + runSync: (nextParams) => this.runSync(nextParams), + openDatabase: () => this.openDatabase(), + ensureSchema: () => this.ensureSchema(), + readMeta: () => this.readMeta() ?? undefined, + }; + await runMemorySyncWithReadonlyRecovery(state, params); } async readFile(params: {