memorix 1.9.6 → 1.9.8
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 +18 -0
- package/dist/cli/index.js +254 -87
- package/dist/index.js +254 -87
- package/dist/maintenance-runner.js +96 -84
- package/dist/media-audio-runner.js +96 -84
- package/dist/media-video-runner.js +96 -84
- package/dist/memcode-runtime/CHANGELOG.md +18 -0
- package/dist/sdk.js +253 -86
- package/dist/vector-backfill-runner.js +96 -84
- package/docs/DEVELOPMENT.md +1 -1
- package/docs/INTEGRATIONS.md +1 -1
- package/docs/KNOWN_ISSUES_AND_ROADMAP.md +1 -1
- package/docs/README.md +1 -1
- package/docs/SETUP.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/hooks/installers/index.ts +160 -3
- package/src/store/orama-store.ts +16 -1
- package/src/store/sqlite-fts.ts +38 -16
|
@@ -389,6 +389,74 @@ var init_launch_root = __esm({
|
|
|
389
389
|
}
|
|
390
390
|
});
|
|
391
391
|
|
|
392
|
+
// src/store/sqlite-reliability.ts
|
|
393
|
+
function isTransientSqliteError(error) {
|
|
394
|
+
const code = error?.code;
|
|
395
|
+
if (typeof code === "string") {
|
|
396
|
+
return /^SQLITE_BUSY(?:_|$)/i.test(code);
|
|
397
|
+
}
|
|
398
|
+
return error instanceof Error && /\bdatabase is locked\b|\bdatabase is busy\b/i.test(error.message);
|
|
399
|
+
}
|
|
400
|
+
function isSqliteUnavailableError(error) {
|
|
401
|
+
return error instanceof SqliteUnavailableError || error instanceof Error && /SQLite is not available|better-sqlite3.*failed|node:sqlite.*failed|bun:sqlite.*failed/i.test(error.message);
|
|
402
|
+
}
|
|
403
|
+
function degradedReadError(storeName) {
|
|
404
|
+
return new Error(`[memorix] Cannot read ${storeName}: SQLite backend unavailable (memory is not loaded)`);
|
|
405
|
+
}
|
|
406
|
+
function degradedWriteError(storeName) {
|
|
407
|
+
return new Error(`[memorix] Cannot write ${storeName}: SQLite backend unavailable (degraded mode; read-only)`);
|
|
408
|
+
}
|
|
409
|
+
function formatSqliteFailure(error) {
|
|
410
|
+
return error instanceof Error && error.message ? error.message : String(error);
|
|
411
|
+
}
|
|
412
|
+
function delay(ms) {
|
|
413
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
414
|
+
}
|
|
415
|
+
async function retrySqliteBusy(operation, label) {
|
|
416
|
+
for (let attempt = 0; ; attempt++) {
|
|
417
|
+
try {
|
|
418
|
+
return await operation();
|
|
419
|
+
} catch (error) {
|
|
420
|
+
if (!isTransientSqliteError(error) || attempt >= SQLITE_BUSY_RETRY_DELAYS_MS.length) throw error;
|
|
421
|
+
const waitMs = SQLITE_BUSY_RETRY_DELAYS_MS[attempt];
|
|
422
|
+
console.error(
|
|
423
|
+
`[memorix] ${label} SQLite busy (attempt ${attempt + 1}/${SQLITE_BUSY_RETRY_DELAYS_MS.length + 1}), retrying in ${waitMs}ms`
|
|
424
|
+
);
|
|
425
|
+
await delay(waitMs);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
async function initializeSqliteStore(factory, dataDir, label) {
|
|
430
|
+
try {
|
|
431
|
+
loadBetterSqlite3();
|
|
432
|
+
} catch (error) {
|
|
433
|
+
throw new SqliteUnavailableError(error);
|
|
434
|
+
}
|
|
435
|
+
return retrySqliteBusy(async () => {
|
|
436
|
+
const store = factory();
|
|
437
|
+
await store.init(dataDir);
|
|
438
|
+
return store;
|
|
439
|
+
}, label);
|
|
440
|
+
}
|
|
441
|
+
var SQLITE_BUSY_RETRY_DELAYS_MS, SqliteUnavailableError;
|
|
442
|
+
var init_sqlite_reliability = __esm({
|
|
443
|
+
"src/store/sqlite-reliability.ts"() {
|
|
444
|
+
"use strict";
|
|
445
|
+
init_esm_shims();
|
|
446
|
+
init_sqlite_db();
|
|
447
|
+
SQLITE_BUSY_RETRY_DELAYS_MS = [50, 150, 400, 1e3, 2500];
|
|
448
|
+
SqliteUnavailableError = class extends Error {
|
|
449
|
+
code = "MEMORIX_SQLITE_UNAVAILABLE";
|
|
450
|
+
constructor(cause) {
|
|
451
|
+
super(
|
|
452
|
+
cause instanceof Error && cause.message ? cause.message : "[memorix] SQLite is unavailable (install a supported SQLite runtime)"
|
|
453
|
+
);
|
|
454
|
+
this.name = "SqliteUnavailableError";
|
|
455
|
+
}
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
});
|
|
459
|
+
|
|
392
460
|
// src/store/sqlite-fts.ts
|
|
393
461
|
function normalizeFtsToken(token) {
|
|
394
462
|
return token.replace(/"/g, '""');
|
|
@@ -402,22 +470,27 @@ function buildFtsQuery(query) {
|
|
|
402
470
|
function initializeObservationLexicalIndex(db2) {
|
|
403
471
|
const known = ftsAvailability.get(db2);
|
|
404
472
|
if (known !== void 0) return known;
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
db2.
|
|
473
|
+
let lastError;
|
|
474
|
+
for (let attempt = 1; attempt <= FTS_INIT_MAX_ATTEMPTS; attempt++) {
|
|
475
|
+
try {
|
|
476
|
+
db2.exec(CREATE_OBSERVATION_FTS);
|
|
477
|
+
const observationCount = Number(db2.prepare("SELECT COUNT(*) AS count FROM observations").get()?.count ?? 0);
|
|
478
|
+
const indexedCount = Number(db2.prepare(`SELECT COUNT(*) AS count FROM ${FTS_TABLE}`).get()?.count ?? 0);
|
|
479
|
+
if (observationCount !== indexedCount) {
|
|
480
|
+
db2.exec(`INSERT INTO ${FTS_TABLE}(${FTS_TABLE}) VALUES ('rebuild')`);
|
|
481
|
+
}
|
|
482
|
+
ftsAvailability.set(db2, true);
|
|
483
|
+
return true;
|
|
484
|
+
} catch (error) {
|
|
485
|
+
lastError = error;
|
|
486
|
+
if (!isTransientSqliteError(error) || attempt === FTS_INIT_MAX_ATTEMPTS) break;
|
|
411
487
|
}
|
|
412
|
-
ftsAvailability.set(db2, true);
|
|
413
|
-
return true;
|
|
414
|
-
} catch (error) {
|
|
415
|
-
ftsAvailability.set(db2, false);
|
|
416
|
-
console.warn(
|
|
417
|
-
`[memorix] SQLite FTS5 unavailable; using the existing search fallback: ${error instanceof Error ? error.message : String(error)}`
|
|
418
|
-
);
|
|
419
|
-
return false;
|
|
420
488
|
}
|
|
489
|
+
ftsAvailability.set(db2, false);
|
|
490
|
+
console.warn(
|
|
491
|
+
`[memorix] SQLite FTS5 unavailable; using the existing search fallback: ${lastError instanceof Error ? lastError.message : String(lastError)}`
|
|
492
|
+
);
|
|
493
|
+
return false;
|
|
421
494
|
}
|
|
422
495
|
function isObservationLexicalIndexEnabled(db2) {
|
|
423
496
|
return ftsAvailability.get(db2) === true;
|
|
@@ -471,11 +544,12 @@ function searchObservationLexically(db2, options) {
|
|
|
471
544
|
score: Math.max(0, Number(row.lexical_score ?? 0))
|
|
472
545
|
}));
|
|
473
546
|
}
|
|
474
|
-
var ftsAvailability, FTS_TABLE, CREATE_OBSERVATION_FTS;
|
|
547
|
+
var ftsAvailability, FTS_TABLE, CREATE_OBSERVATION_FTS, FTS_INIT_MAX_ATTEMPTS;
|
|
475
548
|
var init_sqlite_fts = __esm({
|
|
476
549
|
"src/store/sqlite-fts.ts"() {
|
|
477
550
|
"use strict";
|
|
478
551
|
init_esm_shims();
|
|
552
|
+
init_sqlite_reliability();
|
|
479
553
|
ftsAvailability = /* @__PURE__ */ new WeakMap();
|
|
480
554
|
FTS_TABLE = "observations_fts";
|
|
481
555
|
CREATE_OBSERVATION_FTS = `
|
|
@@ -509,6 +583,7 @@ CREATE TRIGGER IF NOT EXISTS observations_fts_au AFTER UPDATE ON observations BE
|
|
|
509
583
|
VALUES (new.id, new.entityName, new.title, new.narrative, new.facts, new.filesModified, new.concepts, new.attachments);
|
|
510
584
|
END;
|
|
511
585
|
`;
|
|
586
|
+
FTS_INIT_MAX_ATTEMPTS = 3;
|
|
512
587
|
}
|
|
513
588
|
});
|
|
514
589
|
|
|
@@ -2714,74 +2789,6 @@ var init_types = __esm({
|
|
|
2714
2789
|
}
|
|
2715
2790
|
});
|
|
2716
2791
|
|
|
2717
|
-
// src/store/sqlite-reliability.ts
|
|
2718
|
-
function isTransientSqliteError(error) {
|
|
2719
|
-
const code = error?.code;
|
|
2720
|
-
if (typeof code === "string") {
|
|
2721
|
-
return /^SQLITE_BUSY(?:_|$)/i.test(code);
|
|
2722
|
-
}
|
|
2723
|
-
return error instanceof Error && /\bdatabase is locked\b|\bdatabase is busy\b/i.test(error.message);
|
|
2724
|
-
}
|
|
2725
|
-
function isSqliteUnavailableError(error) {
|
|
2726
|
-
return error instanceof SqliteUnavailableError || error instanceof Error && /SQLite is not available|better-sqlite3.*failed|node:sqlite.*failed|bun:sqlite.*failed/i.test(error.message);
|
|
2727
|
-
}
|
|
2728
|
-
function degradedReadError(storeName) {
|
|
2729
|
-
return new Error(`[memorix] Cannot read ${storeName}: SQLite backend unavailable (memory is not loaded)`);
|
|
2730
|
-
}
|
|
2731
|
-
function degradedWriteError(storeName) {
|
|
2732
|
-
return new Error(`[memorix] Cannot write ${storeName}: SQLite backend unavailable (degraded mode; read-only)`);
|
|
2733
|
-
}
|
|
2734
|
-
function formatSqliteFailure(error) {
|
|
2735
|
-
return error instanceof Error && error.message ? error.message : String(error);
|
|
2736
|
-
}
|
|
2737
|
-
function delay(ms) {
|
|
2738
|
-
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
2739
|
-
}
|
|
2740
|
-
async function retrySqliteBusy(operation, label) {
|
|
2741
|
-
for (let attempt = 0; ; attempt++) {
|
|
2742
|
-
try {
|
|
2743
|
-
return await operation();
|
|
2744
|
-
} catch (error) {
|
|
2745
|
-
if (!isTransientSqliteError(error) || attempt >= SQLITE_BUSY_RETRY_DELAYS_MS.length) throw error;
|
|
2746
|
-
const waitMs = SQLITE_BUSY_RETRY_DELAYS_MS[attempt];
|
|
2747
|
-
console.error(
|
|
2748
|
-
`[memorix] ${label} SQLite busy (attempt ${attempt + 1}/${SQLITE_BUSY_RETRY_DELAYS_MS.length + 1}), retrying in ${waitMs}ms`
|
|
2749
|
-
);
|
|
2750
|
-
await delay(waitMs);
|
|
2751
|
-
}
|
|
2752
|
-
}
|
|
2753
|
-
}
|
|
2754
|
-
async function initializeSqliteStore(factory, dataDir, label) {
|
|
2755
|
-
try {
|
|
2756
|
-
loadBetterSqlite3();
|
|
2757
|
-
} catch (error) {
|
|
2758
|
-
throw new SqliteUnavailableError(error);
|
|
2759
|
-
}
|
|
2760
|
-
return retrySqliteBusy(async () => {
|
|
2761
|
-
const store = factory();
|
|
2762
|
-
await store.init(dataDir);
|
|
2763
|
-
return store;
|
|
2764
|
-
}, label);
|
|
2765
|
-
}
|
|
2766
|
-
var SQLITE_BUSY_RETRY_DELAYS_MS, SqliteUnavailableError;
|
|
2767
|
-
var init_sqlite_reliability = __esm({
|
|
2768
|
-
"src/store/sqlite-reliability.ts"() {
|
|
2769
|
-
"use strict";
|
|
2770
|
-
init_esm_shims();
|
|
2771
|
-
init_sqlite_db();
|
|
2772
|
-
SQLITE_BUSY_RETRY_DELAYS_MS = [50, 150, 400, 1e3, 2500];
|
|
2773
|
-
SqliteUnavailableError = class extends Error {
|
|
2774
|
-
code = "MEMORIX_SQLITE_UNAVAILABLE";
|
|
2775
|
-
constructor(cause) {
|
|
2776
|
-
super(
|
|
2777
|
-
cause instanceof Error && cause.message ? cause.message : "[memorix] SQLite is unavailable (install a supported SQLite runtime)"
|
|
2778
|
-
);
|
|
2779
|
-
this.name = "SqliteUnavailableError";
|
|
2780
|
-
}
|
|
2781
|
-
};
|
|
2782
|
-
}
|
|
2783
|
-
});
|
|
2784
|
-
|
|
2785
2792
|
// src/store/file-lock.ts
|
|
2786
2793
|
import { promises as fs2 } from "fs";
|
|
2787
2794
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
@@ -8674,6 +8681,7 @@ async function searchObservations(options) {
|
|
|
8674
8681
|
};
|
|
8675
8682
|
let queryVector = null;
|
|
8676
8683
|
let embeddingProviderForSearch = null;
|
|
8684
|
+
let embeddingUnavailable = false;
|
|
8677
8685
|
if (quality !== "fast" && embeddingEnabled && hasQuery && tier !== "fast") {
|
|
8678
8686
|
try {
|
|
8679
8687
|
const provider2 = await getEmbeddingProvider();
|
|
@@ -8681,6 +8689,7 @@ async function searchObservations(options) {
|
|
|
8681
8689
|
embeddingProviderForSearch = provider2;
|
|
8682
8690
|
const activeVectorDimensions = getVectorDimensions();
|
|
8683
8691
|
if (activeVectorDimensions !== null && provider2.dimensions !== activeVectorDimensions) {
|
|
8692
|
+
embeddingUnavailable = true;
|
|
8684
8693
|
rememberSearchMode(
|
|
8685
8694
|
modeKey,
|
|
8686
8695
|
`fulltext (embedding dimension mismatch: provider ${provider2.dimensions}d vs index ${activeVectorDimensions}d)`
|
|
@@ -8720,8 +8729,11 @@ async function searchObservations(options) {
|
|
|
8720
8729
|
hybridWeights: isCJKHeavy ? { text: 0.2, vector: 0.8 } : { text: 0.6, vector: 0.4 }
|
|
8721
8730
|
};
|
|
8722
8731
|
}
|
|
8732
|
+
} else {
|
|
8733
|
+
embeddingUnavailable = true;
|
|
8723
8734
|
}
|
|
8724
8735
|
} catch (error) {
|
|
8736
|
+
embeddingUnavailable = true;
|
|
8725
8737
|
rememberSearchMode(modeKey, "fulltext (embedding unavailable)");
|
|
8726
8738
|
console.error("[memorix] Embedding failed or timed out, falling back to fulltext search");
|
|
8727
8739
|
}
|
|
@@ -8729,7 +8741,7 @@ async function searchObservations(options) {
|
|
|
8729
8741
|
mark("preSearch");
|
|
8730
8742
|
let results;
|
|
8731
8743
|
const persistentCorpusEligible = await shouldUsePersistentSearch(projectIds, options.projectId);
|
|
8732
|
-
const canUsePersistentLexical = persistentCorpusEligible && Boolean(hasQuery) && !queryVector && (quality === "fast" || tier === "fast" || !embeddingEnabled);
|
|
8744
|
+
const canUsePersistentLexical = persistentCorpusEligible && Boolean(hasQuery) && !queryVector && (quality === "fast" || tier === "fast" || !embeddingEnabled || embeddingUnavailable);
|
|
8733
8745
|
let persistent = persistentCorpusEligible && queryVector && embeddingProviderForSearch ? await searchPersistentSemantically(options, projectIds, requestLimit, queryVector, embeddingProviderForSearch) : canUsePersistentLexical ? await searchPersistentLexically(options, projectIds, requestLimit) : null;
|
|
8734
8746
|
const durableStore = getOptionalObservationStore();
|
|
8735
8747
|
if (!persistent && queryVector && durableStore?.hasLexicalIndex?.() && count(database) === 0) {
|
|
@@ -419,6 +419,74 @@ var init_launch_root = __esm({
|
|
|
419
419
|
}
|
|
420
420
|
});
|
|
421
421
|
|
|
422
|
+
// src/store/sqlite-reliability.ts
|
|
423
|
+
function isTransientSqliteError(error) {
|
|
424
|
+
const code = error?.code;
|
|
425
|
+
if (typeof code === "string") {
|
|
426
|
+
return /^SQLITE_BUSY(?:_|$)/i.test(code);
|
|
427
|
+
}
|
|
428
|
+
return error instanceof Error && /\bdatabase is locked\b|\bdatabase is busy\b/i.test(error.message);
|
|
429
|
+
}
|
|
430
|
+
function isSqliteUnavailableError(error) {
|
|
431
|
+
return error instanceof SqliteUnavailableError || error instanceof Error && /SQLite is not available|better-sqlite3.*failed|node:sqlite.*failed|bun:sqlite.*failed/i.test(error.message);
|
|
432
|
+
}
|
|
433
|
+
function degradedReadError(storeName) {
|
|
434
|
+
return new Error(`[memorix] Cannot read ${storeName}: SQLite backend unavailable (memory is not loaded)`);
|
|
435
|
+
}
|
|
436
|
+
function degradedWriteError(storeName) {
|
|
437
|
+
return new Error(`[memorix] Cannot write ${storeName}: SQLite backend unavailable (degraded mode; read-only)`);
|
|
438
|
+
}
|
|
439
|
+
function formatSqliteFailure(error) {
|
|
440
|
+
return error instanceof Error && error.message ? error.message : String(error);
|
|
441
|
+
}
|
|
442
|
+
function delay(ms) {
|
|
443
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
444
|
+
}
|
|
445
|
+
async function retrySqliteBusy(operation, label) {
|
|
446
|
+
for (let attempt = 0; ; attempt++) {
|
|
447
|
+
try {
|
|
448
|
+
return await operation();
|
|
449
|
+
} catch (error) {
|
|
450
|
+
if (!isTransientSqliteError(error) || attempt >= SQLITE_BUSY_RETRY_DELAYS_MS.length) throw error;
|
|
451
|
+
const waitMs = SQLITE_BUSY_RETRY_DELAYS_MS[attempt];
|
|
452
|
+
console.error(
|
|
453
|
+
`[memorix] ${label} SQLite busy (attempt ${attempt + 1}/${SQLITE_BUSY_RETRY_DELAYS_MS.length + 1}), retrying in ${waitMs}ms`
|
|
454
|
+
);
|
|
455
|
+
await delay(waitMs);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
async function initializeSqliteStore(factory, dataDir, label) {
|
|
460
|
+
try {
|
|
461
|
+
loadBetterSqlite3();
|
|
462
|
+
} catch (error) {
|
|
463
|
+
throw new SqliteUnavailableError(error);
|
|
464
|
+
}
|
|
465
|
+
return retrySqliteBusy(async () => {
|
|
466
|
+
const store = factory();
|
|
467
|
+
await store.init(dataDir);
|
|
468
|
+
return store;
|
|
469
|
+
}, label);
|
|
470
|
+
}
|
|
471
|
+
var SQLITE_BUSY_RETRY_DELAYS_MS, SqliteUnavailableError;
|
|
472
|
+
var init_sqlite_reliability = __esm({
|
|
473
|
+
"src/store/sqlite-reliability.ts"() {
|
|
474
|
+
"use strict";
|
|
475
|
+
init_esm_shims();
|
|
476
|
+
init_sqlite_db();
|
|
477
|
+
SQLITE_BUSY_RETRY_DELAYS_MS = [50, 150, 400, 1e3, 2500];
|
|
478
|
+
SqliteUnavailableError = class extends Error {
|
|
479
|
+
code = "MEMORIX_SQLITE_UNAVAILABLE";
|
|
480
|
+
constructor(cause) {
|
|
481
|
+
super(
|
|
482
|
+
cause instanceof Error && cause.message ? cause.message : "[memorix] SQLite is unavailable (install a supported SQLite runtime)"
|
|
483
|
+
);
|
|
484
|
+
this.name = "SqliteUnavailableError";
|
|
485
|
+
}
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
});
|
|
489
|
+
|
|
422
490
|
// src/store/sqlite-fts.ts
|
|
423
491
|
function normalizeFtsToken(token) {
|
|
424
492
|
return token.replace(/"/g, '""');
|
|
@@ -432,22 +500,27 @@ function buildFtsQuery(query) {
|
|
|
432
500
|
function initializeObservationLexicalIndex(db2) {
|
|
433
501
|
const known = ftsAvailability.get(db2);
|
|
434
502
|
if (known !== void 0) return known;
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
db2.
|
|
503
|
+
let lastError;
|
|
504
|
+
for (let attempt = 1; attempt <= FTS_INIT_MAX_ATTEMPTS; attempt++) {
|
|
505
|
+
try {
|
|
506
|
+
db2.exec(CREATE_OBSERVATION_FTS);
|
|
507
|
+
const observationCount = Number(db2.prepare("SELECT COUNT(*) AS count FROM observations").get()?.count ?? 0);
|
|
508
|
+
const indexedCount = Number(db2.prepare(`SELECT COUNT(*) AS count FROM ${FTS_TABLE}`).get()?.count ?? 0);
|
|
509
|
+
if (observationCount !== indexedCount) {
|
|
510
|
+
db2.exec(`INSERT INTO ${FTS_TABLE}(${FTS_TABLE}) VALUES ('rebuild')`);
|
|
511
|
+
}
|
|
512
|
+
ftsAvailability.set(db2, true);
|
|
513
|
+
return true;
|
|
514
|
+
} catch (error) {
|
|
515
|
+
lastError = error;
|
|
516
|
+
if (!isTransientSqliteError(error) || attempt === FTS_INIT_MAX_ATTEMPTS) break;
|
|
441
517
|
}
|
|
442
|
-
ftsAvailability.set(db2, true);
|
|
443
|
-
return true;
|
|
444
|
-
} catch (error) {
|
|
445
|
-
ftsAvailability.set(db2, false);
|
|
446
|
-
console.warn(
|
|
447
|
-
`[memorix] SQLite FTS5 unavailable; using the existing search fallback: ${error instanceof Error ? error.message : String(error)}`
|
|
448
|
-
);
|
|
449
|
-
return false;
|
|
450
518
|
}
|
|
519
|
+
ftsAvailability.set(db2, false);
|
|
520
|
+
console.warn(
|
|
521
|
+
`[memorix] SQLite FTS5 unavailable; using the existing search fallback: ${lastError instanceof Error ? lastError.message : String(lastError)}`
|
|
522
|
+
);
|
|
523
|
+
return false;
|
|
451
524
|
}
|
|
452
525
|
function isObservationLexicalIndexEnabled(db2) {
|
|
453
526
|
return ftsAvailability.get(db2) === true;
|
|
@@ -501,11 +574,12 @@ function searchObservationLexically(db2, options) {
|
|
|
501
574
|
score: Math.max(0, Number(row.lexical_score ?? 0))
|
|
502
575
|
}));
|
|
503
576
|
}
|
|
504
|
-
var ftsAvailability, FTS_TABLE, CREATE_OBSERVATION_FTS;
|
|
577
|
+
var ftsAvailability, FTS_TABLE, CREATE_OBSERVATION_FTS, FTS_INIT_MAX_ATTEMPTS;
|
|
505
578
|
var init_sqlite_fts = __esm({
|
|
506
579
|
"src/store/sqlite-fts.ts"() {
|
|
507
580
|
"use strict";
|
|
508
581
|
init_esm_shims();
|
|
582
|
+
init_sqlite_reliability();
|
|
509
583
|
ftsAvailability = /* @__PURE__ */ new WeakMap();
|
|
510
584
|
FTS_TABLE = "observations_fts";
|
|
511
585
|
CREATE_OBSERVATION_FTS = `
|
|
@@ -539,6 +613,7 @@ CREATE TRIGGER IF NOT EXISTS observations_fts_au AFTER UPDATE ON observations BE
|
|
|
539
613
|
VALUES (new.id, new.entityName, new.title, new.narrative, new.facts, new.filesModified, new.concepts, new.attachments);
|
|
540
614
|
END;
|
|
541
615
|
`;
|
|
616
|
+
FTS_INIT_MAX_ATTEMPTS = 3;
|
|
542
617
|
}
|
|
543
618
|
});
|
|
544
619
|
|
|
@@ -1726,74 +1801,6 @@ CREATE UNIQUE INDEX IF NOT EXISTS idx_maintenance_jobs_active_dedupe
|
|
|
1726
1801
|
}
|
|
1727
1802
|
});
|
|
1728
1803
|
|
|
1729
|
-
// src/store/sqlite-reliability.ts
|
|
1730
|
-
function isTransientSqliteError(error) {
|
|
1731
|
-
const code = error?.code;
|
|
1732
|
-
if (typeof code === "string") {
|
|
1733
|
-
return /^SQLITE_BUSY(?:_|$)/i.test(code);
|
|
1734
|
-
}
|
|
1735
|
-
return error instanceof Error && /\bdatabase is locked\b|\bdatabase is busy\b/i.test(error.message);
|
|
1736
|
-
}
|
|
1737
|
-
function isSqliteUnavailableError(error) {
|
|
1738
|
-
return error instanceof SqliteUnavailableError || error instanceof Error && /SQLite is not available|better-sqlite3.*failed|node:sqlite.*failed|bun:sqlite.*failed/i.test(error.message);
|
|
1739
|
-
}
|
|
1740
|
-
function degradedReadError(storeName) {
|
|
1741
|
-
return new Error(`[memorix] Cannot read ${storeName}: SQLite backend unavailable (memory is not loaded)`);
|
|
1742
|
-
}
|
|
1743
|
-
function degradedWriteError(storeName) {
|
|
1744
|
-
return new Error(`[memorix] Cannot write ${storeName}: SQLite backend unavailable (degraded mode; read-only)`);
|
|
1745
|
-
}
|
|
1746
|
-
function formatSqliteFailure(error) {
|
|
1747
|
-
return error instanceof Error && error.message ? error.message : String(error);
|
|
1748
|
-
}
|
|
1749
|
-
function delay(ms) {
|
|
1750
|
-
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
1751
|
-
}
|
|
1752
|
-
async function retrySqliteBusy(operation, label) {
|
|
1753
|
-
for (let attempt = 0; ; attempt++) {
|
|
1754
|
-
try {
|
|
1755
|
-
return await operation();
|
|
1756
|
-
} catch (error) {
|
|
1757
|
-
if (!isTransientSqliteError(error) || attempt >= SQLITE_BUSY_RETRY_DELAYS_MS.length) throw error;
|
|
1758
|
-
const waitMs = SQLITE_BUSY_RETRY_DELAYS_MS[attempt];
|
|
1759
|
-
console.error(
|
|
1760
|
-
`[memorix] ${label} SQLite busy (attempt ${attempt + 1}/${SQLITE_BUSY_RETRY_DELAYS_MS.length + 1}), retrying in ${waitMs}ms`
|
|
1761
|
-
);
|
|
1762
|
-
await delay(waitMs);
|
|
1763
|
-
}
|
|
1764
|
-
}
|
|
1765
|
-
}
|
|
1766
|
-
async function initializeSqliteStore(factory, dataDir, label) {
|
|
1767
|
-
try {
|
|
1768
|
-
loadBetterSqlite3();
|
|
1769
|
-
} catch (error) {
|
|
1770
|
-
throw new SqliteUnavailableError(error);
|
|
1771
|
-
}
|
|
1772
|
-
return retrySqliteBusy(async () => {
|
|
1773
|
-
const store = factory();
|
|
1774
|
-
await store.init(dataDir);
|
|
1775
|
-
return store;
|
|
1776
|
-
}, label);
|
|
1777
|
-
}
|
|
1778
|
-
var SQLITE_BUSY_RETRY_DELAYS_MS, SqliteUnavailableError;
|
|
1779
|
-
var init_sqlite_reliability = __esm({
|
|
1780
|
-
"src/store/sqlite-reliability.ts"() {
|
|
1781
|
-
"use strict";
|
|
1782
|
-
init_esm_shims();
|
|
1783
|
-
init_sqlite_db();
|
|
1784
|
-
SQLITE_BUSY_RETRY_DELAYS_MS = [50, 150, 400, 1e3, 2500];
|
|
1785
|
-
SqliteUnavailableError = class extends Error {
|
|
1786
|
-
code = "MEMORIX_SQLITE_UNAVAILABLE";
|
|
1787
|
-
constructor(cause) {
|
|
1788
|
-
super(
|
|
1789
|
-
cause instanceof Error && cause.message ? cause.message : "[memorix] SQLite is unavailable (install a supported SQLite runtime)"
|
|
1790
|
-
);
|
|
1791
|
-
this.name = "SqliteUnavailableError";
|
|
1792
|
-
}
|
|
1793
|
-
};
|
|
1794
|
-
}
|
|
1795
|
-
});
|
|
1796
|
-
|
|
1797
1804
|
// src/store/file-lock.ts
|
|
1798
1805
|
import { promises as fs2 } from "fs";
|
|
1799
1806
|
import { randomUUID } from "crypto";
|
|
@@ -7686,6 +7693,7 @@ async function searchObservations(options) {
|
|
|
7686
7693
|
};
|
|
7687
7694
|
let queryVector = null;
|
|
7688
7695
|
let embeddingProviderForSearch = null;
|
|
7696
|
+
let embeddingUnavailable = false;
|
|
7689
7697
|
if (quality !== "fast" && embeddingEnabled && hasQuery && tier !== "fast") {
|
|
7690
7698
|
try {
|
|
7691
7699
|
const provider2 = await getEmbeddingProvider();
|
|
@@ -7693,6 +7701,7 @@ async function searchObservations(options) {
|
|
|
7693
7701
|
embeddingProviderForSearch = provider2;
|
|
7694
7702
|
const activeVectorDimensions = getVectorDimensions();
|
|
7695
7703
|
if (activeVectorDimensions !== null && provider2.dimensions !== activeVectorDimensions) {
|
|
7704
|
+
embeddingUnavailable = true;
|
|
7696
7705
|
rememberSearchMode(
|
|
7697
7706
|
modeKey,
|
|
7698
7707
|
`fulltext (embedding dimension mismatch: provider ${provider2.dimensions}d vs index ${activeVectorDimensions}d)`
|
|
@@ -7732,8 +7741,11 @@ async function searchObservations(options) {
|
|
|
7732
7741
|
hybridWeights: isCJKHeavy ? { text: 0.2, vector: 0.8 } : { text: 0.6, vector: 0.4 }
|
|
7733
7742
|
};
|
|
7734
7743
|
}
|
|
7744
|
+
} else {
|
|
7745
|
+
embeddingUnavailable = true;
|
|
7735
7746
|
}
|
|
7736
7747
|
} catch (error) {
|
|
7748
|
+
embeddingUnavailable = true;
|
|
7737
7749
|
rememberSearchMode(modeKey, "fulltext (embedding unavailable)");
|
|
7738
7750
|
console.error("[memorix] Embedding failed or timed out, falling back to fulltext search");
|
|
7739
7751
|
}
|
|
@@ -7741,7 +7753,7 @@ async function searchObservations(options) {
|
|
|
7741
7753
|
mark("preSearch");
|
|
7742
7754
|
let results;
|
|
7743
7755
|
const persistentCorpusEligible = await shouldUsePersistentSearch(projectIds, options.projectId);
|
|
7744
|
-
const canUsePersistentLexical = persistentCorpusEligible && Boolean(hasQuery) && !queryVector && (quality === "fast" || tier === "fast" || !embeddingEnabled);
|
|
7756
|
+
const canUsePersistentLexical = persistentCorpusEligible && Boolean(hasQuery) && !queryVector && (quality === "fast" || tier === "fast" || !embeddingEnabled || embeddingUnavailable);
|
|
7745
7757
|
let persistent = persistentCorpusEligible && queryVector && embeddingProviderForSearch ? await searchPersistentSemantically(options, projectIds, requestLimit, queryVector, embeddingProviderForSearch) : canUsePersistentLexical ? await searchPersistentLexically(options, projectIds, requestLimit) : null;
|
|
7746
7758
|
const durableStore = getOptionalObservationStore();
|
|
7747
7759
|
if (!persistent && queryVector && durableStore?.hasLexicalIndex?.() && count(database) === 0) {
|
|
@@ -6,6 +6,24 @@ All notable changes to this project will be documented in this file.
|
|
|
6
6
|
|
|
7
7
|
No unreleased changes.
|
|
8
8
|
|
|
9
|
+
## [1.9.8] - 2026-10-02
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
- **OpenCode v2 plugin compatibility** -- generated plugins retain the v1
|
|
13
|
+
named export while also exposing the v2 default `id`/`setup` contract,
|
|
14
|
+
including event subscriptions, tool hooks, cleanup, and host session IDs.
|
|
15
|
+
|
|
16
|
+
## [1.9.7] - 2026-09-28
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
- **SQLite FTS5 startup resilience** -- transient `SQLITE_BUSY` contention during
|
|
20
|
+
lexical-index initialization is retried before FTS5 is marked unavailable.
|
|
21
|
+
- **Large-store search fallback** -- runtime embedding failure, provider
|
|
22
|
+
disappearance, and vector-dimension mismatch now all fall back to the durable
|
|
23
|
+
SQLite lexical index instead of returning a silent empty result.
|
|
24
|
+
- **Release metadata** -- synchronized the npm, MCP Registry, and packaged
|
|
25
|
+
integration metadata for the 1.9.7 patch release.
|
|
26
|
+
|
|
9
27
|
## [1.9.6] - 2026-09-22
|
|
10
28
|
|
|
11
29
|
### Added
|