memorix 1.9.2 → 1.9.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/dist/cli/index.js +318 -141
- package/dist/index.js +243 -120
- package/dist/maintenance-runner.js +184 -93
- package/dist/media-audio-runner.js +184 -93
- package/dist/media-video-runner.js +184 -93
- package/dist/memcode-runtime/CHANGELOG.md +11 -0
- package/dist/sdk.js +242 -119
- package/dist/vector-backfill-runner.js +184 -93
- package/docs/AGENT_OPERATOR_PLAYBOOK.md +15 -0
- package/docs/ARCHITECTURE.md +23 -0
- package/docs/DEVELOPMENT.md +1 -1
- package/docs/PERFORMANCE.md +50 -0
- package/docs/README.md +1 -1
- package/package.json +1 -1
- package/plugins/antigravity/memorix/plugin.json +1 -1
- package/plugins/claude/memorix/.claude-plugin/plugin.json +1 -1
- package/plugins/codebuddy/memorix/.codebuddy-plugin/plugin.json +1 -1
- package/plugins/codex/memorix/.codex-plugin/plugin.json +1 -1
- package/plugins/copilot/memorix/plugin.json +1 -1
- package/plugins/gemini/memorix/gemini-extension.json +1 -1
- package/plugins/hermes/memorix/plugin.yaml +1 -1
- package/plugins/omp/memorix/package.json +1 -1
- package/plugins/openclaw/memorix/.codex-plugin/plugin.json +1 -1
- package/plugins/pi/memorix/package.json +1 -1
- package/server.json +2 -2
- package/src/cli/commands/doctor.ts +78 -17
- package/src/store/mini-skill-store.ts +31 -22
- package/src/store/obs-store.ts +46 -30
- package/src/store/session-store.ts +35 -21
- package/src/store/sqlite-db.ts +16 -7
- package/src/store/sqlite-reliability.ts +87 -0
- package/src/store/sqlite-store.ts +4 -1
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { loadBetterSqlite3 } from './sqlite-db.js';
|
|
2
|
+
|
|
3
|
+
const SQLITE_BUSY_RETRY_DELAYS_MS = [50, 150, 400, 1000, 2500] as const;
|
|
4
|
+
|
|
5
|
+
export class SqliteUnavailableError extends Error {
|
|
6
|
+
readonly code = 'MEMORIX_SQLITE_UNAVAILABLE';
|
|
7
|
+
|
|
8
|
+
constructor(cause?: unknown) {
|
|
9
|
+
super(
|
|
10
|
+
cause instanceof Error && cause.message
|
|
11
|
+
? cause.message
|
|
12
|
+
: '[memorix] SQLite is unavailable (install a supported SQLite runtime)',
|
|
13
|
+
);
|
|
14
|
+
this.name = 'SqliteUnavailableError';
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** SQLite BUSY means another connection is currently holding the database lock. */
|
|
19
|
+
export function isTransientSqliteError(error: unknown): boolean {
|
|
20
|
+
const code = (error as { code?: unknown } | null)?.code;
|
|
21
|
+
if (typeof code === 'string') {
|
|
22
|
+
// SQLITE_LOCKED is a same-connection/shared-cache conflict. Retrying it
|
|
23
|
+
// would hide a transaction bug; only BUSY is cross-connection contention.
|
|
24
|
+
return /^SQLITE_BUSY(?:_|$)/i.test(code);
|
|
25
|
+
}
|
|
26
|
+
return error instanceof Error && /\bdatabase is locked\b|\bdatabase is busy\b/i.test(error.message);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function isSqliteUnavailableError(error: unknown): boolean {
|
|
30
|
+
return error instanceof SqliteUnavailableError
|
|
31
|
+
|| (error instanceof Error && /SQLite is not available|better-sqlite3.*failed|node:sqlite.*failed|bun:sqlite.*failed/i.test(error.message));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function degradedReadError(storeName: string): Error {
|
|
35
|
+
return new Error(`[memorix] Cannot read ${storeName}: SQLite backend unavailable (memory is not loaded)`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function degradedWriteError(storeName: string): Error {
|
|
39
|
+
return new Error(`[memorix] Cannot write ${storeName}: SQLite backend unavailable (degraded mode; read-only)`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function formatSqliteFailure(error: unknown): string {
|
|
43
|
+
return error instanceof Error && error.message ? error.message : String(error);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function delay(ms: number): Promise<void> {
|
|
47
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Retry only cross-process SQLite contention, keeping each attempt bounded. */
|
|
51
|
+
export async function retrySqliteBusy<T>(operation: () => Promise<T>, label: string): Promise<T> {
|
|
52
|
+
for (let attempt = 0; ; attempt++) {
|
|
53
|
+
try {
|
|
54
|
+
return await operation();
|
|
55
|
+
} catch (error) {
|
|
56
|
+
if (!isTransientSqliteError(error) || attempt >= SQLITE_BUSY_RETRY_DELAYS_MS.length) throw error;
|
|
57
|
+
const waitMs = SQLITE_BUSY_RETRY_DELAYS_MS[attempt];
|
|
58
|
+
console.error(
|
|
59
|
+
`[memorix] ${label} SQLite busy (attempt ${attempt + 1}/${SQLITE_BUSY_RETRY_DELAYS_MS.length + 1}), retrying in ${waitMs}ms`,
|
|
60
|
+
);
|
|
61
|
+
await delay(waitMs);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Establish one complete store instance. Runtime lock errors are retried with
|
|
68
|
+
* fresh instances; a missing SQLite runtime is classified separately so the
|
|
69
|
+
* caller can choose the intentional degraded backend.
|
|
70
|
+
*/
|
|
71
|
+
export async function initializeSqliteStore<T extends { init(dataDir: string): Promise<void> }>(
|
|
72
|
+
factory: () => T,
|
|
73
|
+
dataDir: string,
|
|
74
|
+
label: string,
|
|
75
|
+
): Promise<T> {
|
|
76
|
+
try {
|
|
77
|
+
loadBetterSqlite3();
|
|
78
|
+
} catch (error) {
|
|
79
|
+
throw new SqliteUnavailableError(error);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return retrySqliteBusy(async () => {
|
|
83
|
+
const store = factory();
|
|
84
|
+
await store.init(dataDir);
|
|
85
|
+
return store;
|
|
86
|
+
}, label);
|
|
87
|
+
}
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
import type { Observation } from '../types.js';
|
|
19
19
|
import type { ObservationStore, StoreTransaction } from './obs-store.js';
|
|
20
20
|
import { getDatabase, closeDatabase } from './sqlite-db.js';
|
|
21
|
+
import { retrySqliteBusy } from './sqlite-reliability.js';
|
|
21
22
|
import {
|
|
22
23
|
isObservationLexicalIndexEnabled,
|
|
23
24
|
rebuildObservationLexicalIndex,
|
|
@@ -462,7 +463,9 @@ export class SqliteBackend implements ObservationStore {
|
|
|
462
463
|
const run = this._atomicQueue
|
|
463
464
|
.catch(() => undefined)
|
|
464
465
|
.then(async () => {
|
|
465
|
-
|
|
466
|
+
await retrySqliteBusy(async () => {
|
|
467
|
+
this.db.prepare('BEGIN IMMEDIATE').run();
|
|
468
|
+
}, 'Observation atomic write');
|
|
466
469
|
try {
|
|
467
470
|
const tx: StoreTransaction = {
|
|
468
471
|
loadAll: async () => this.rawLoadAll(),
|