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
package/dist/sdk.js
CHANGED
|
@@ -294,6 +294,74 @@ var init_launch_root = __esm({
|
|
|
294
294
|
}
|
|
295
295
|
});
|
|
296
296
|
|
|
297
|
+
// src/store/sqlite-reliability.ts
|
|
298
|
+
function isTransientSqliteError(error) {
|
|
299
|
+
const code = error?.code;
|
|
300
|
+
if (typeof code === "string") {
|
|
301
|
+
return /^SQLITE_BUSY(?:_|$)/i.test(code);
|
|
302
|
+
}
|
|
303
|
+
return error instanceof Error && /\bdatabase is locked\b|\bdatabase is busy\b/i.test(error.message);
|
|
304
|
+
}
|
|
305
|
+
function isSqliteUnavailableError(error) {
|
|
306
|
+
return error instanceof SqliteUnavailableError || error instanceof Error && /SQLite is not available|better-sqlite3.*failed|node:sqlite.*failed|bun:sqlite.*failed/i.test(error.message);
|
|
307
|
+
}
|
|
308
|
+
function degradedReadError(storeName) {
|
|
309
|
+
return new Error(`[memorix] Cannot read ${storeName}: SQLite backend unavailable (memory is not loaded)`);
|
|
310
|
+
}
|
|
311
|
+
function degradedWriteError(storeName) {
|
|
312
|
+
return new Error(`[memorix] Cannot write ${storeName}: SQLite backend unavailable (degraded mode; read-only)`);
|
|
313
|
+
}
|
|
314
|
+
function formatSqliteFailure(error) {
|
|
315
|
+
return error instanceof Error && error.message ? error.message : String(error);
|
|
316
|
+
}
|
|
317
|
+
function delay(ms) {
|
|
318
|
+
return new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
319
|
+
}
|
|
320
|
+
async function retrySqliteBusy(operation, label) {
|
|
321
|
+
for (let attempt = 0; ; attempt++) {
|
|
322
|
+
try {
|
|
323
|
+
return await operation();
|
|
324
|
+
} catch (error) {
|
|
325
|
+
if (!isTransientSqliteError(error) || attempt >= SQLITE_BUSY_RETRY_DELAYS_MS.length) throw error;
|
|
326
|
+
const waitMs = SQLITE_BUSY_RETRY_DELAYS_MS[attempt];
|
|
327
|
+
console.error(
|
|
328
|
+
`[memorix] ${label} SQLite busy (attempt ${attempt + 1}/${SQLITE_BUSY_RETRY_DELAYS_MS.length + 1}), retrying in ${waitMs}ms`
|
|
329
|
+
);
|
|
330
|
+
await delay(waitMs);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
async function initializeSqliteStore(factory, dataDir, label) {
|
|
335
|
+
try {
|
|
336
|
+
loadBetterSqlite3();
|
|
337
|
+
} catch (error) {
|
|
338
|
+
throw new SqliteUnavailableError(error);
|
|
339
|
+
}
|
|
340
|
+
return retrySqliteBusy(async () => {
|
|
341
|
+
const store = factory();
|
|
342
|
+
await store.init(dataDir);
|
|
343
|
+
return store;
|
|
344
|
+
}, label);
|
|
345
|
+
}
|
|
346
|
+
var SQLITE_BUSY_RETRY_DELAYS_MS, SqliteUnavailableError;
|
|
347
|
+
var init_sqlite_reliability = __esm({
|
|
348
|
+
"src/store/sqlite-reliability.ts"() {
|
|
349
|
+
"use strict";
|
|
350
|
+
init_esm_shims();
|
|
351
|
+
init_sqlite_db();
|
|
352
|
+
SQLITE_BUSY_RETRY_DELAYS_MS = [50, 150, 400, 1e3, 2500];
|
|
353
|
+
SqliteUnavailableError = class extends Error {
|
|
354
|
+
code = "MEMORIX_SQLITE_UNAVAILABLE";
|
|
355
|
+
constructor(cause) {
|
|
356
|
+
super(
|
|
357
|
+
cause instanceof Error && cause.message ? cause.message : "[memorix] SQLite is unavailable (install a supported SQLite runtime)"
|
|
358
|
+
);
|
|
359
|
+
this.name = "SqliteUnavailableError";
|
|
360
|
+
}
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
});
|
|
364
|
+
|
|
297
365
|
// src/store/sqlite-fts.ts
|
|
298
366
|
function normalizeFtsToken(token) {
|
|
299
367
|
return token.replace(/"/g, '""');
|
|
@@ -307,22 +375,27 @@ function buildFtsQuery(query) {
|
|
|
307
375
|
function initializeObservationLexicalIndex(db2) {
|
|
308
376
|
const known = ftsAvailability.get(db2);
|
|
309
377
|
if (known !== void 0) return known;
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
db2.
|
|
378
|
+
let lastError;
|
|
379
|
+
for (let attempt = 1; attempt <= FTS_INIT_MAX_ATTEMPTS; attempt++) {
|
|
380
|
+
try {
|
|
381
|
+
db2.exec(CREATE_OBSERVATION_FTS);
|
|
382
|
+
const observationCount = Number(db2.prepare("SELECT COUNT(*) AS count FROM observations").get()?.count ?? 0);
|
|
383
|
+
const indexedCount = Number(db2.prepare(`SELECT COUNT(*) AS count FROM ${FTS_TABLE}`).get()?.count ?? 0);
|
|
384
|
+
if (observationCount !== indexedCount) {
|
|
385
|
+
db2.exec(`INSERT INTO ${FTS_TABLE}(${FTS_TABLE}) VALUES ('rebuild')`);
|
|
386
|
+
}
|
|
387
|
+
ftsAvailability.set(db2, true);
|
|
388
|
+
return true;
|
|
389
|
+
} catch (error) {
|
|
390
|
+
lastError = error;
|
|
391
|
+
if (!isTransientSqliteError(error) || attempt === FTS_INIT_MAX_ATTEMPTS) break;
|
|
316
392
|
}
|
|
317
|
-
ftsAvailability.set(db2, true);
|
|
318
|
-
return true;
|
|
319
|
-
} catch (error) {
|
|
320
|
-
ftsAvailability.set(db2, false);
|
|
321
|
-
console.warn(
|
|
322
|
-
`[memorix] SQLite FTS5 unavailable; using the existing search fallback: ${error instanceof Error ? error.message : String(error)}`
|
|
323
|
-
);
|
|
324
|
-
return false;
|
|
325
393
|
}
|
|
394
|
+
ftsAvailability.set(db2, false);
|
|
395
|
+
console.warn(
|
|
396
|
+
`[memorix] SQLite FTS5 unavailable; using the existing search fallback: ${lastError instanceof Error ? lastError.message : String(lastError)}`
|
|
397
|
+
);
|
|
398
|
+
return false;
|
|
326
399
|
}
|
|
327
400
|
function isObservationLexicalIndexEnabled(db2) {
|
|
328
401
|
return ftsAvailability.get(db2) === true;
|
|
@@ -376,11 +449,12 @@ function searchObservationLexically(db2, options) {
|
|
|
376
449
|
score: Math.max(0, Number(row.lexical_score ?? 0))
|
|
377
450
|
}));
|
|
378
451
|
}
|
|
379
|
-
var ftsAvailability, FTS_TABLE, CREATE_OBSERVATION_FTS;
|
|
452
|
+
var ftsAvailability, FTS_TABLE, CREATE_OBSERVATION_FTS, FTS_INIT_MAX_ATTEMPTS;
|
|
380
453
|
var init_sqlite_fts = __esm({
|
|
381
454
|
"src/store/sqlite-fts.ts"() {
|
|
382
455
|
"use strict";
|
|
383
456
|
init_esm_shims();
|
|
457
|
+
init_sqlite_reliability();
|
|
384
458
|
ftsAvailability = /* @__PURE__ */ new WeakMap();
|
|
385
459
|
FTS_TABLE = "observations_fts";
|
|
386
460
|
CREATE_OBSERVATION_FTS = `
|
|
@@ -414,6 +488,7 @@ CREATE TRIGGER IF NOT EXISTS observations_fts_au AFTER UPDATE ON observations BE
|
|
|
414
488
|
VALUES (new.id, new.entityName, new.title, new.narrative, new.facts, new.filesModified, new.concepts, new.attachments);
|
|
415
489
|
END;
|
|
416
490
|
`;
|
|
491
|
+
FTS_INIT_MAX_ATTEMPTS = 3;
|
|
417
492
|
}
|
|
418
493
|
});
|
|
419
494
|
|
|
@@ -2638,74 +2713,6 @@ var init_types = __esm({
|
|
|
2638
2713
|
}
|
|
2639
2714
|
});
|
|
2640
2715
|
|
|
2641
|
-
// src/store/sqlite-reliability.ts
|
|
2642
|
-
function isTransientSqliteError(error) {
|
|
2643
|
-
const code = error?.code;
|
|
2644
|
-
if (typeof code === "string") {
|
|
2645
|
-
return /^SQLITE_BUSY(?:_|$)/i.test(code);
|
|
2646
|
-
}
|
|
2647
|
-
return error instanceof Error && /\bdatabase is locked\b|\bdatabase is busy\b/i.test(error.message);
|
|
2648
|
-
}
|
|
2649
|
-
function isSqliteUnavailableError(error) {
|
|
2650
|
-
return error instanceof SqliteUnavailableError || error instanceof Error && /SQLite is not available|better-sqlite3.*failed|node:sqlite.*failed|bun:sqlite.*failed/i.test(error.message);
|
|
2651
|
-
}
|
|
2652
|
-
function degradedReadError(storeName) {
|
|
2653
|
-
return new Error(`[memorix] Cannot read ${storeName}: SQLite backend unavailable (memory is not loaded)`);
|
|
2654
|
-
}
|
|
2655
|
-
function degradedWriteError(storeName) {
|
|
2656
|
-
return new Error(`[memorix] Cannot write ${storeName}: SQLite backend unavailable (degraded mode; read-only)`);
|
|
2657
|
-
}
|
|
2658
|
-
function formatSqliteFailure(error) {
|
|
2659
|
-
return error instanceof Error && error.message ? error.message : String(error);
|
|
2660
|
-
}
|
|
2661
|
-
function delay(ms) {
|
|
2662
|
-
return new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
2663
|
-
}
|
|
2664
|
-
async function retrySqliteBusy(operation, label) {
|
|
2665
|
-
for (let attempt = 0; ; attempt++) {
|
|
2666
|
-
try {
|
|
2667
|
-
return await operation();
|
|
2668
|
-
} catch (error) {
|
|
2669
|
-
if (!isTransientSqliteError(error) || attempt >= SQLITE_BUSY_RETRY_DELAYS_MS.length) throw error;
|
|
2670
|
-
const waitMs = SQLITE_BUSY_RETRY_DELAYS_MS[attempt];
|
|
2671
|
-
console.error(
|
|
2672
|
-
`[memorix] ${label} SQLite busy (attempt ${attempt + 1}/${SQLITE_BUSY_RETRY_DELAYS_MS.length + 1}), retrying in ${waitMs}ms`
|
|
2673
|
-
);
|
|
2674
|
-
await delay(waitMs);
|
|
2675
|
-
}
|
|
2676
|
-
}
|
|
2677
|
-
}
|
|
2678
|
-
async function initializeSqliteStore(factory, dataDir, label) {
|
|
2679
|
-
try {
|
|
2680
|
-
loadBetterSqlite3();
|
|
2681
|
-
} catch (error) {
|
|
2682
|
-
throw new SqliteUnavailableError(error);
|
|
2683
|
-
}
|
|
2684
|
-
return retrySqliteBusy(async () => {
|
|
2685
|
-
const store = factory();
|
|
2686
|
-
await store.init(dataDir);
|
|
2687
|
-
return store;
|
|
2688
|
-
}, label);
|
|
2689
|
-
}
|
|
2690
|
-
var SQLITE_BUSY_RETRY_DELAYS_MS, SqliteUnavailableError;
|
|
2691
|
-
var init_sqlite_reliability = __esm({
|
|
2692
|
-
"src/store/sqlite-reliability.ts"() {
|
|
2693
|
-
"use strict";
|
|
2694
|
-
init_esm_shims();
|
|
2695
|
-
init_sqlite_db();
|
|
2696
|
-
SQLITE_BUSY_RETRY_DELAYS_MS = [50, 150, 400, 1e3, 2500];
|
|
2697
|
-
SqliteUnavailableError = class extends Error {
|
|
2698
|
-
code = "MEMORIX_SQLITE_UNAVAILABLE";
|
|
2699
|
-
constructor(cause) {
|
|
2700
|
-
super(
|
|
2701
|
-
cause instanceof Error && cause.message ? cause.message : "[memorix] SQLite is unavailable (install a supported SQLite runtime)"
|
|
2702
|
-
);
|
|
2703
|
-
this.name = "SqliteUnavailableError";
|
|
2704
|
-
}
|
|
2705
|
-
};
|
|
2706
|
-
}
|
|
2707
|
-
});
|
|
2708
|
-
|
|
2709
2716
|
// src/store/mini-skill-store.ts
|
|
2710
2717
|
var mini_skill_store_exports = {};
|
|
2711
2718
|
__export(mini_skill_store_exports, {
|
|
@@ -9207,6 +9214,7 @@ async function searchObservations(options) {
|
|
|
9207
9214
|
};
|
|
9208
9215
|
let queryVector = null;
|
|
9209
9216
|
let embeddingProviderForSearch = null;
|
|
9217
|
+
let embeddingUnavailable = false;
|
|
9210
9218
|
if (quality !== "fast" && embeddingEnabled && hasQuery && tier !== "fast") {
|
|
9211
9219
|
try {
|
|
9212
9220
|
const provider2 = await getEmbeddingProvider();
|
|
@@ -9214,6 +9222,7 @@ async function searchObservations(options) {
|
|
|
9214
9222
|
embeddingProviderForSearch = provider2;
|
|
9215
9223
|
const activeVectorDimensions = getVectorDimensions();
|
|
9216
9224
|
if (activeVectorDimensions !== null && provider2.dimensions !== activeVectorDimensions) {
|
|
9225
|
+
embeddingUnavailable = true;
|
|
9217
9226
|
rememberSearchMode(
|
|
9218
9227
|
modeKey,
|
|
9219
9228
|
`fulltext (embedding dimension mismatch: provider ${provider2.dimensions}d vs index ${activeVectorDimensions}d)`
|
|
@@ -9253,8 +9262,11 @@ async function searchObservations(options) {
|
|
|
9253
9262
|
hybridWeights: isCJKHeavy ? { text: 0.2, vector: 0.8 } : { text: 0.6, vector: 0.4 }
|
|
9254
9263
|
};
|
|
9255
9264
|
}
|
|
9265
|
+
} else {
|
|
9266
|
+
embeddingUnavailable = true;
|
|
9256
9267
|
}
|
|
9257
9268
|
} catch (error) {
|
|
9269
|
+
embeddingUnavailable = true;
|
|
9258
9270
|
rememberSearchMode(modeKey, "fulltext (embedding unavailable)");
|
|
9259
9271
|
console.error("[memorix] Embedding failed or timed out, falling back to fulltext search");
|
|
9260
9272
|
}
|
|
@@ -9262,7 +9274,7 @@ async function searchObservations(options) {
|
|
|
9262
9274
|
mark("preSearch");
|
|
9263
9275
|
let results;
|
|
9264
9276
|
const persistentCorpusEligible = await shouldUsePersistentSearch(projectIds, options.projectId);
|
|
9265
|
-
const canUsePersistentLexical = persistentCorpusEligible && Boolean(hasQuery) && !queryVector && (quality === "fast" || tier === "fast" || !embeddingEnabled);
|
|
9277
|
+
const canUsePersistentLexical = persistentCorpusEligible && Boolean(hasQuery) && !queryVector && (quality === "fast" || tier === "fast" || !embeddingEnabled || embeddingUnavailable);
|
|
9266
9278
|
let persistent = persistentCorpusEligible && queryVector && embeddingProviderForSearch ? await searchPersistentSemantically(options, projectIds, requestLimit, queryVector, embeddingProviderForSearch) : canUsePersistentLexical ? await searchPersistentLexically(options, projectIds, requestLimit) : null;
|
|
9267
9279
|
const durableStore = getOptionalObservationStore();
|
|
9268
9280
|
if (!persistent && queryVector && durableStore?.hasLexicalIndex?.() && count(database) === 0) {
|
|
@@ -166074,6 +166086,161 @@ export const MemorixPlugin = async ({ project, client, $, directory, worktree })
|
|
|
166074
166086
|
},
|
|
166075
166087
|
};
|
|
166076
166088
|
};
|
|
166089
|
+
|
|
166090
|
+
/**
|
|
166091
|
+
* OpenCode v2 compatibility layer. Keep this beside the v1 export so one
|
|
166092
|
+
* generated file can be loaded by both host generations during migration.
|
|
166093
|
+
*/
|
|
166094
|
+
const v2HookCommand = ${hookCommand};
|
|
166095
|
+
|
|
166096
|
+
function v2ReportHookFailure(eventName, detail) {
|
|
166097
|
+
if (process.env.MEMORIX_HOOK_DEBUG !== '1') return;
|
|
166098
|
+
console.error('[memorix-plugin] v2 hook delivery failed:', eventName, detail);
|
|
166099
|
+
}
|
|
166100
|
+
|
|
166101
|
+
function v2EventData(event) {
|
|
166102
|
+
return event && typeof event.data === 'object' && event.data !== null ? event.data : {};
|
|
166103
|
+
}
|
|
166104
|
+
|
|
166105
|
+
function v2SessionId(event) {
|
|
166106
|
+
const data = v2EventData(event);
|
|
166107
|
+
const value = data.sessionID ?? data.sessionId ?? data.session_id
|
|
166108
|
+
?? event?.sessionID ?? event?.sessionId;
|
|
166109
|
+
return typeof value === 'string' ? value : '';
|
|
166110
|
+
}
|
|
166111
|
+
|
|
166112
|
+
function v2RunHook(payload, directory, sessionId) {
|
|
166113
|
+
const nextPayload = { agent: 'opencode', cwd: directory, ...payload };
|
|
166114
|
+
if (sessionId) nextPayload.session_id = sessionId;
|
|
166115
|
+
const eventName = nextPayload.hook_event_name || 'unknown';
|
|
166116
|
+
try {
|
|
166117
|
+
const result = spawnSync(v2HookCommand, ['hook'], {
|
|
166118
|
+
input: JSON.stringify(nextPayload),
|
|
166119
|
+
timeout: 10_000,
|
|
166120
|
+
encoding: 'utf-8',
|
|
166121
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
166122
|
+
shell: process.platform === 'win32',
|
|
166123
|
+
windowsHide: true,
|
|
166124
|
+
});
|
|
166125
|
+
if (result.status !== 0) {
|
|
166126
|
+
v2ReportHookFailure(eventName, {
|
|
166127
|
+
exit: result.status,
|
|
166128
|
+
stderr: (result.stderr || '').slice(0, 200),
|
|
166129
|
+
error: result.error?.message,
|
|
166130
|
+
});
|
|
166131
|
+
}
|
|
166132
|
+
} catch (error) {
|
|
166133
|
+
v2ReportHookFailure(eventName, error?.message ?? error);
|
|
166134
|
+
}
|
|
166135
|
+
}
|
|
166136
|
+
|
|
166137
|
+
function v2SessionState(states, sessionId) {
|
|
166138
|
+
const key = sessionId || '__unknown__';
|
|
166139
|
+
let state = states.get(key);
|
|
166140
|
+
if (!state) {
|
|
166141
|
+
state = { pendingText: '', lastDeliveredKey: '' };
|
|
166142
|
+
states.set(key, state);
|
|
166143
|
+
}
|
|
166144
|
+
return state;
|
|
166145
|
+
}
|
|
166146
|
+
|
|
166147
|
+
function v2FlushAssistant(state, directory, sessionId) {
|
|
166148
|
+
const text = state.pendingText.trim();
|
|
166149
|
+
if (!text || text === state.lastDeliveredKey) return;
|
|
166150
|
+
v2RunHook({ hook_event_name: 'message.updated', ai_response: text }, directory, sessionId);
|
|
166151
|
+
state.lastDeliveredKey = text;
|
|
166152
|
+
state.pendingText = '';
|
|
166153
|
+
}
|
|
166154
|
+
|
|
166155
|
+
function v2FilePath(file) {
|
|
166156
|
+
if (typeof file === 'string') return file;
|
|
166157
|
+
if (!file || typeof file !== 'object') return '';
|
|
166158
|
+
return file.path ?? file.file ?? file.name ?? '';
|
|
166159
|
+
}
|
|
166160
|
+
|
|
166161
|
+
function v2HandleEvent(event, directory, states) {
|
|
166162
|
+
if (!event || typeof event !== 'object') return;
|
|
166163
|
+
const type = typeof event.type === 'string' ? event.type : '';
|
|
166164
|
+
const data = v2EventData(event);
|
|
166165
|
+
const sessionId = v2SessionId(event);
|
|
166166
|
+
const state = v2SessionState(states, sessionId);
|
|
166167
|
+
const run = (payload) => v2RunHook(payload, directory, sessionId);
|
|
166168
|
+
|
|
166169
|
+
switch (type) {
|
|
166170
|
+
case 'session.created':
|
|
166171
|
+
run({ hook_event_name: 'session.created' });
|
|
166172
|
+
break;
|
|
166173
|
+
case 'session.text.delta':
|
|
166174
|
+
state.pendingText += typeof data.delta === 'string' ? data.delta : (typeof data.text === 'string' ? data.text : '');
|
|
166175
|
+
break;
|
|
166176
|
+
case 'session.message.updated':
|
|
166177
|
+
case 'message.updated':
|
|
166178
|
+
if (data.role === 'assistant' && typeof data.text === 'string') state.pendingText = data.text;
|
|
166179
|
+
break;
|
|
166180
|
+
case 'session.step.ended': {
|
|
166181
|
+
const files = Array.isArray(data.files) ? data.files : [];
|
|
166182
|
+
for (const file of files) {
|
|
166183
|
+
const filePath = v2FilePath(file);
|
|
166184
|
+
if (filePath) run({ hook_event_name: 'file.edited', file_path: filePath });
|
|
166185
|
+
}
|
|
166186
|
+
if (typeof data.text === 'string') state.pendingText += data.text;
|
|
166187
|
+
break;
|
|
166188
|
+
}
|
|
166189
|
+
case 'session.idle':
|
|
166190
|
+
v2FlushAssistant(state, directory, sessionId);
|
|
166191
|
+
run({ hook_event_name: 'session.idle' });
|
|
166192
|
+
break;
|
|
166193
|
+
case 'session.compacted':
|
|
166194
|
+
run({ hook_event_name: 'session.compacted' });
|
|
166195
|
+
break;
|
|
166196
|
+
default:
|
|
166197
|
+
break;
|
|
166198
|
+
}
|
|
166199
|
+
}
|
|
166200
|
+
|
|
166201
|
+
function setup(ctx) {
|
|
166202
|
+
const directory = ctx?.location?.directory || ctx?.location?.worktree || process.cwd();
|
|
166203
|
+
const controller = new AbortController();
|
|
166204
|
+
const states = new Map();
|
|
166205
|
+
|
|
166206
|
+
if (ctx?.tool?.hook) {
|
|
166207
|
+
try {
|
|
166208
|
+
Promise.resolve(ctx.tool.hook('execute.after', async (event) => {
|
|
166209
|
+
const sessionId = v2SessionId(event);
|
|
166210
|
+
const data = event && typeof event.input === 'object' ? event.input : {};
|
|
166211
|
+
const tool = event?.tool ?? event?.name ?? '';
|
|
166212
|
+
v2RunHook({
|
|
166213
|
+
hook_event_name: 'tool.execute.after',
|
|
166214
|
+
tool_name: tool,
|
|
166215
|
+
tool_input: data,
|
|
166216
|
+
}, directory, sessionId);
|
|
166217
|
+
if (tool === 'shell') {
|
|
166218
|
+
v2RunHook({
|
|
166219
|
+
hook_event_name: 'command.executed',
|
|
166220
|
+
command: data.command ?? data.cmd ?? '',
|
|
166221
|
+
}, directory, sessionId);
|
|
166222
|
+
}
|
|
166223
|
+
})).catch((error) => v2ReportHookFailure('execute.after', error?.message ?? error));
|
|
166224
|
+
} catch (error) {
|
|
166225
|
+
v2ReportHookFailure('execute.after', error?.message ?? error);
|
|
166226
|
+
}
|
|
166227
|
+
}
|
|
166228
|
+
|
|
166229
|
+
if (ctx?.event?.subscribe) {
|
|
166230
|
+
void (async () => {
|
|
166231
|
+
try {
|
|
166232
|
+
const stream = ctx.event.subscribe({ signal: controller.signal });
|
|
166233
|
+
for await (const event of stream) v2HandleEvent(event, directory, states);
|
|
166234
|
+
} catch (error) {
|
|
166235
|
+
if (!controller.signal.aborted) v2ReportHookFailure('event.subscribe', error?.message ?? error);
|
|
166236
|
+
}
|
|
166237
|
+
})();
|
|
166238
|
+
}
|
|
166239
|
+
|
|
166240
|
+
return () => controller.abort();
|
|
166241
|
+
}
|
|
166242
|
+
|
|
166243
|
+
export default { id: 'memorix', setup };
|
|
166077
166244
|
`;
|
|
166078
166245
|
}
|
|
166079
166246
|
async function installOpenCodeSkills(projectRoot, global2 = false) {
|
|
@@ -166899,7 +167066,7 @@ var init_installers = __esm({
|
|
|
166899
167066
|
"use strict";
|
|
166900
167067
|
init_esm_shims();
|
|
166901
167068
|
init_official_skills();
|
|
166902
|
-
OPENCODE_PLUGIN_VERSION =
|
|
167069
|
+
OPENCODE_PLUGIN_VERSION = 8;
|
|
166903
167070
|
AGENT_SKILL_DIRS = {
|
|
166904
167071
|
cursor: { project: path34.join(".cursor", "skills"), global: path34.join(".cursor", "skills") },
|
|
166905
167072
|
windsurf: { project: path34.join(".windsurf", "skills"), global: path34.join(".windsurf", "skills") },
|
|
@@ -170804,7 +170971,7 @@ var MCP_SERVER_INFO_META_KEY = "io.modelcontextprotocol/serverInfo";
|
|
|
170804
170971
|
function getMcpServerInfo() {
|
|
170805
170972
|
return {
|
|
170806
170973
|
name: "memorix",
|
|
170807
|
-
version: true ? "1.9.
|
|
170974
|
+
version: true ? "1.9.8" : "1.0.1"
|
|
170808
170975
|
};
|
|
170809
170976
|
}
|
|
170810
170977
|
function createMcpDiscoverResult(capabilities, serverInfo) {
|
|
@@ -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) {
|
package/docs/DEVELOPMENT.md
CHANGED
|
@@ -13,7 +13,7 @@ Memorix is a TypeScript project built around:
|
|
|
13
13
|
|
|
14
14
|
## Current Development Baseline
|
|
15
15
|
|
|
16
|
-
The current release line is **1.9.
|
|
16
|
+
The current release line is **1.9.8** and package metadata is kept synchronized
|
|
17
17
|
with the root package and MCP Registry manifest.
|
|
18
18
|
|
|
19
19
|
Contributors should assume the following areas are part of the current release line:
|