memorix 1.8.9 → 1.9.0
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/README.md +7 -0
- package/README.zh-CN.md +6 -0
- package/dist/cli/index.js +3261 -1348
- package/dist/index.js +324 -187
- package/dist/maintenance-runner.js +285 -103
- package/dist/media-audio-runner.js +285 -103
- package/dist/media-video-runner.js +285 -103
- package/dist/memcode-runtime/CHANGELOG.md +18 -0
- package/dist/sdk.js +323 -186
- package/dist/vector-backfill-runner.d.ts +4 -1
- package/dist/vector-backfill-runner.js +288 -104
- package/docs/API_REFERENCE.md +14 -0
- package/docs/CONFIGURATION.md +46 -0
- package/docs/DEVELOPMENT.md +2 -2
- package/docs/MULTI-DEVICE-SYNC-RESEARCH.md +344 -0
- package/docs/PERFORMANCE.md +32 -0
- package/docs/README.md +4 -2
- package/llms-full.txt +1 -0
- package/llms.txt +1 -0
- 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/command-guide.ts +9 -1
- package/src/cli/commands/serve-http.ts +11 -0
- package/src/cli/commands/sync-store.ts +191 -0
- package/src/cli/commands/sync.ts +8 -0
- package/src/memory/observations.ts +15 -2
- package/src/runtime/maintenance-targets.ts +6 -0
- package/src/runtime/vector-backfill-runner.ts +185 -7
- package/src/store/orama-store.ts +85 -102
- package/src/sync/adapters/fs.ts +179 -0
- package/src/sync/adapters/github.ts +258 -0
- package/src/sync/adapters/object-store.ts +267 -0
- package/src/sync/adapters/postgres.ts +269 -0
- package/src/sync/engine.ts +353 -0
- package/src/sync/journal.ts +162 -0
- package/src/sync/merge.ts +116 -0
- package/src/sync/namespace.ts +15 -0
- package/src/sync/paging.ts +45 -0
- package/src/sync/policy.ts +37 -0
- package/src/sync/remote-factory.ts +49 -0
- package/src/sync/store-port.ts +288 -0
- package/src/sync/types.ts +183 -0
|
@@ -5993,6 +5993,14 @@ __export(orama_store_exports, {
|
|
|
5993
5993
|
updateObservationMetadata: () => updateObservationMetadata
|
|
5994
5994
|
});
|
|
5995
5995
|
import { create, insert as insert2, insertMultiple, search, remove as remove2, update, count, getByID } from "@orama/orama";
|
|
5996
|
+
function rememberSearchMode(key, value) {
|
|
5997
|
+
if (!lastSearchModeByProject.has(key) && lastSearchModeByProject.size >= MAX_SEARCH_MODE_KEYS) {
|
|
5998
|
+
const oldest = lastSearchModeByProject.keys().next().value;
|
|
5999
|
+
if (oldest !== void 0) lastSearchModeByProject.delete(oldest);
|
|
6000
|
+
}
|
|
6001
|
+
lastSearchModeByProject.delete(key);
|
|
6002
|
+
lastSearchModeByProject.set(key, value);
|
|
6003
|
+
}
|
|
5996
6004
|
function getLastSearchMode(projectId) {
|
|
5997
6005
|
return lastSearchModeByProject.get(projectId ?? SEARCH_MODE_DEFAULT_KEY) ?? "fulltext";
|
|
5998
6006
|
}
|
|
@@ -6005,9 +6013,6 @@ function makeEntryKey(projectId, observationId2) {
|
|
|
6005
6013
|
function rememberObservationDoc(doc) {
|
|
6006
6014
|
const publicDoc = { ...doc };
|
|
6007
6015
|
delete publicDoc.embedding;
|
|
6008
|
-
if (doc.projectId && typeof doc.observationId === "number") {
|
|
6009
|
-
docByObservationKey.set(makeEntryKey(doc.projectId, doc.observationId), publicDoc);
|
|
6010
|
-
}
|
|
6011
6016
|
return publicDoc;
|
|
6012
6017
|
}
|
|
6013
6018
|
function isCommandLikeQuery(query) {
|
|
@@ -6111,7 +6116,6 @@ async function resetDb() {
|
|
|
6111
6116
|
indexEmbeddingProvider = null;
|
|
6112
6117
|
deferredCachedVectorHydration = null;
|
|
6113
6118
|
lastSearchModeByProject.clear();
|
|
6114
|
-
docByObservationKey.clear();
|
|
6115
6119
|
}
|
|
6116
6120
|
function isEmbeddingEnabled() {
|
|
6117
6121
|
return embeddingEnabled;
|
|
@@ -6174,78 +6178,72 @@ function isCompatibleCachedVector(vector) {
|
|
|
6174
6178
|
vector && embeddingDimensions !== null && vector.length === embeddingDimensions && vector.every(Number.isFinite)
|
|
6175
6179
|
);
|
|
6176
6180
|
}
|
|
6177
|
-
async function attachCachedVectors(database,
|
|
6178
|
-
const
|
|
6179
|
-
|
|
6180
|
-
|
|
6181
|
-
|
|
6182
|
-
|
|
6183
|
-
|
|
6184
|
-
|
|
6185
|
-
|
|
6186
|
-
|
|
6187
|
-
|
|
6188
|
-
|
|
6189
|
-
|
|
6190
|
-
|
|
6191
|
-
|
|
6192
|
-
|
|
6181
|
+
async function attachCachedVectors(database, observations2) {
|
|
6182
|
+
const batchSize = 200;
|
|
6183
|
+
for (let start = 0; start < observations2.length; start += batchSize) {
|
|
6184
|
+
const batch = observations2.slice(start, start + batchSize);
|
|
6185
|
+
const cachedVectors = await getCachedEmbeddings(batch.map(observationEmbeddingText));
|
|
6186
|
+
if (db !== database) return;
|
|
6187
|
+
for (let index = 0; index < batch.length; index++) {
|
|
6188
|
+
const vector = cachedVectors[index];
|
|
6189
|
+
if (!isCompatibleCachedVector(vector)) continue;
|
|
6190
|
+
const id = makeOramaObservationId(batch[index].projectId, batch[index].id);
|
|
6191
|
+
const existing = getByID(database, id);
|
|
6192
|
+
if (!existing || documentEmbeddingText(existing) !== observationEmbeddingText(batch[index])) continue;
|
|
6193
|
+
try {
|
|
6194
|
+
await update(database, id, { ...existing, embedding: vector });
|
|
6195
|
+
} catch {
|
|
6196
|
+
}
|
|
6193
6197
|
}
|
|
6198
|
+
await new Promise((resolve2) => setImmediate(resolve2));
|
|
6194
6199
|
}
|
|
6195
6200
|
}
|
|
6196
6201
|
async function hydrateIndex(observations2, options = {}) {
|
|
6197
6202
|
const database = await getDb({ allowNetworkProbe: options.allowNetworkProbe });
|
|
6198
|
-
const candidates = [];
|
|
6199
|
-
for (const observation of observations2) {
|
|
6200
|
-
if (!observation || !observation.id || !observation.projectId) continue;
|
|
6201
|
-
const id = makeOramaObservationId(observation.projectId, observation.id);
|
|
6202
|
-
if (getByID(database, id)) continue;
|
|
6203
|
-
candidates.push({ observation, id });
|
|
6204
|
-
}
|
|
6205
6203
|
const deferCachedVectors = options.deferCachedVectors === true && Boolean(indexEmbeddingProvider?.getCachedEmbeddings);
|
|
6206
|
-
const cachedVectors = deferCachedVectors ? candidates.map(() => null) : await getCachedEmbeddings(candidates.map(({ observation }) => observationEmbeddingText(observation)));
|
|
6207
|
-
const documents = [];
|
|
6208
|
-
for (let index = 0; index < candidates.length; index++) {
|
|
6209
|
-
const { observation: obs, id } = candidates[index];
|
|
6210
|
-
try {
|
|
6211
|
-
const vector = cachedVectors[index];
|
|
6212
|
-
const compatibleVector = isCompatibleCachedVector(vector) ? vector : null;
|
|
6213
|
-
const doc = {
|
|
6214
|
-
id,
|
|
6215
|
-
observationId: obs.id,
|
|
6216
|
-
entityName: obs.entityName || "",
|
|
6217
|
-
type: obs.type || "discovery",
|
|
6218
|
-
title: obs.title || "",
|
|
6219
|
-
narrative: obs.narrative || "",
|
|
6220
|
-
facts: Array.isArray(obs.facts) ? obs.facts.join(" ") : "",
|
|
6221
|
-
filesModified: Array.isArray(obs.filesModified) ? obs.filesModified.join(" ") : "",
|
|
6222
|
-
concepts: Array.isArray(obs.concepts) ? obs.concepts.join(" ") : "",
|
|
6223
|
-
tokens: obs.tokens ?? 0,
|
|
6224
|
-
createdAt: obs.createdAt || "",
|
|
6225
|
-
projectId: obs.projectId,
|
|
6226
|
-
accessCount: obs.accessCount ?? 0,
|
|
6227
|
-
lastAccessedAt: obs.lastAccessedAt || "",
|
|
6228
|
-
status: obs.status ?? "active",
|
|
6229
|
-
source: obs.source || "agent",
|
|
6230
|
-
sourceDetail: obs.sourceDetail ?? "",
|
|
6231
|
-
valueCategory: obs.valueCategory ?? "",
|
|
6232
|
-
admissionState: obs.admissionState ?? "",
|
|
6233
|
-
admissionReason: obs.admissionReason ?? "",
|
|
6234
|
-
visibility: obs.visibility ?? "project",
|
|
6235
|
-
createdByAgentId: obs.createdByAgentId ?? "",
|
|
6236
|
-
sharedWithAgentIds: JSON.stringify(obs.sharedWithAgentIds ?? []),
|
|
6237
|
-
documentType: "observation",
|
|
6238
|
-
knowledgeLayer: resolveKnowledgeLayer("observation", obs.sourceDetail, obs.source),
|
|
6239
|
-
...compatibleVector ? { embedding: compatibleVector } : {}
|
|
6240
|
-
};
|
|
6241
|
-
documents.push(doc);
|
|
6242
|
-
} catch {
|
|
6243
|
-
}
|
|
6244
|
-
}
|
|
6245
6204
|
let inserted = 0;
|
|
6246
6205
|
const hydrationBatchSize = 200;
|
|
6247
|
-
for (let start = 0; start <
|
|
6248
|
-
const
|
|
6206
|
+
for (let start = 0; start < observations2.length; start += hydrationBatchSize) {
|
|
6207
|
+
const batchObservations = observations2.slice(start, start + hydrationBatchSize).filter((observation) => observation && observation.id && observation.projectId).filter((observation) => !getByID(database, makeOramaObservationId(observation.projectId, observation.id)));
|
|
6208
|
+
if (batchObservations.length === 0) continue;
|
|
6209
|
+
const cachedVectors = deferCachedVectors || options.skipCachedVectors ? batchObservations.map(() => null) : await getCachedEmbeddings(batchObservations.map(observationEmbeddingText));
|
|
6210
|
+
const batch = [];
|
|
6211
|
+
for (let index = 0; index < batchObservations.length; index++) {
|
|
6212
|
+
const obs = batchObservations[index];
|
|
6213
|
+
try {
|
|
6214
|
+
const compatibleVector = isCompatibleCachedVector(cachedVectors[index]) ? cachedVectors[index] : null;
|
|
6215
|
+
batch.push({
|
|
6216
|
+
id: makeOramaObservationId(obs.projectId, obs.id),
|
|
6217
|
+
observationId: obs.id,
|
|
6218
|
+
entityName: obs.entityName || "",
|
|
6219
|
+
type: obs.type || "discovery",
|
|
6220
|
+
title: obs.title || "",
|
|
6221
|
+
narrative: obs.narrative || "",
|
|
6222
|
+
facts: Array.isArray(obs.facts) ? obs.facts.join(" ") : "",
|
|
6223
|
+
filesModified: Array.isArray(obs.filesModified) ? obs.filesModified.join(" ") : "",
|
|
6224
|
+
concepts: Array.isArray(obs.concepts) ? obs.concepts.join(" ") : "",
|
|
6225
|
+
tokens: obs.tokens ?? 0,
|
|
6226
|
+
createdAt: obs.createdAt || "",
|
|
6227
|
+
projectId: obs.projectId,
|
|
6228
|
+
accessCount: obs.accessCount ?? 0,
|
|
6229
|
+
lastAccessedAt: obs.lastAccessedAt || "",
|
|
6230
|
+
status: obs.status ?? "active",
|
|
6231
|
+
source: obs.source || "agent",
|
|
6232
|
+
sourceDetail: obs.sourceDetail ?? "",
|
|
6233
|
+
valueCategory: obs.valueCategory ?? "",
|
|
6234
|
+
admissionState: obs.admissionState ?? "",
|
|
6235
|
+
admissionReason: obs.admissionReason ?? "",
|
|
6236
|
+
visibility: obs.visibility ?? "project",
|
|
6237
|
+
createdByAgentId: obs.createdByAgentId ?? "",
|
|
6238
|
+
sharedWithAgentIds: JSON.stringify(obs.sharedWithAgentIds ?? []),
|
|
6239
|
+
documentType: "observation",
|
|
6240
|
+
knowledgeLayer: resolveKnowledgeLayer("observation", obs.sourceDetail, obs.source),
|
|
6241
|
+
...compatibleVector ? { embedding: compatibleVector } : {}
|
|
6242
|
+
});
|
|
6243
|
+
} catch {
|
|
6244
|
+
}
|
|
6245
|
+
}
|
|
6246
|
+
if (batch.length === 0) continue;
|
|
6249
6247
|
try {
|
|
6250
6248
|
await insertMultiple(database, batch, batch.length);
|
|
6251
6249
|
for (const doc of batch) rememberObservationDoc(doc);
|
|
@@ -6267,7 +6265,7 @@ async function hydrateIndex(observations2, options = {}) {
|
|
|
6267
6265
|
}
|
|
6268
6266
|
await new Promise((resolve2) => setImmediate(resolve2));
|
|
6269
6267
|
}
|
|
6270
|
-
deferredCachedVectorHydration = options.skipCachedVectors ? null : deferCachedVectors ? attachCachedVectors(database,
|
|
6268
|
+
deferredCachedVectorHydration = options.skipCachedVectors ? null : deferCachedVectors ? attachCachedVectors(database, observations2).catch(() => {
|
|
6271
6269
|
}) : null;
|
|
6272
6270
|
return inserted;
|
|
6273
6271
|
}
|
|
@@ -6304,12 +6302,6 @@ async function updateObservationMetadata(projectId, observationId2, patch) {
|
|
|
6304
6302
|
async function removeObservation(oramaId) {
|
|
6305
6303
|
const database = await getDb();
|
|
6306
6304
|
await remove2(database, oramaId);
|
|
6307
|
-
for (const [key, doc] of docByObservationKey.entries()) {
|
|
6308
|
-
if (doc.id === oramaId) {
|
|
6309
|
-
docByObservationKey.delete(key);
|
|
6310
|
-
break;
|
|
6311
|
-
}
|
|
6312
|
-
}
|
|
6313
6305
|
}
|
|
6314
6306
|
async function searchObservations(options) {
|
|
6315
6307
|
const perf = !!process.env.MEMORIX_PERF;
|
|
@@ -6324,7 +6316,7 @@ async function searchObservations(options) {
|
|
|
6324
6316
|
const modeKey = options.projectId ?? SEARCH_MODE_DEFAULT_KEY;
|
|
6325
6317
|
const quality = options.quality ?? "balanced";
|
|
6326
6318
|
const database = await getDb();
|
|
6327
|
-
|
|
6319
|
+
rememberSearchMode(
|
|
6328
6320
|
modeKey,
|
|
6329
6321
|
quality === "fast" ? "fulltext (fast profile)" : embeddingEnabled ? "hybrid" : "fulltext"
|
|
6330
6322
|
);
|
|
@@ -6391,7 +6383,7 @@ async function searchObservations(options) {
|
|
|
6391
6383
|
if (provider2) {
|
|
6392
6384
|
const activeVectorDimensions = getVectorDimensions();
|
|
6393
6385
|
if (activeVectorDimensions !== null && provider2.dimensions !== activeVectorDimensions) {
|
|
6394
|
-
|
|
6386
|
+
rememberSearchMode(
|
|
6395
6387
|
modeKey,
|
|
6396
6388
|
`fulltext (embedding dimension mismatch: provider ${provider2.dimensions}d vs index ${activeVectorDimensions}d)`
|
|
6397
6389
|
);
|
|
@@ -6416,7 +6408,7 @@ async function searchObservations(options) {
|
|
|
6416
6408
|
mark("embedding");
|
|
6417
6409
|
const cjkRatio = (originalQuery.match(/[\u4e00-\u9fff\u3040-\u309f\u30a0-\u30ff\uac00-\ud7af]/g) || []).length / originalQuery.length;
|
|
6418
6410
|
const isCJKHeavy = cjkRatio > 0.3;
|
|
6419
|
-
|
|
6411
|
+
rememberSearchMode(modeKey, "hybrid");
|
|
6420
6412
|
searchParams = {
|
|
6421
6413
|
...searchParams,
|
|
6422
6414
|
mode: "hybrid",
|
|
@@ -6432,7 +6424,7 @@ async function searchObservations(options) {
|
|
|
6432
6424
|
}
|
|
6433
6425
|
}
|
|
6434
6426
|
} catch (error) {
|
|
6435
|
-
|
|
6427
|
+
rememberSearchMode(modeKey, "fulltext (embedding unavailable)");
|
|
6436
6428
|
console.error("[memorix] Embedding failed or timed out, falling back to fulltext search");
|
|
6437
6429
|
}
|
|
6438
6430
|
}
|
|
@@ -6442,7 +6434,7 @@ async function searchObservations(options) {
|
|
|
6442
6434
|
results = await search(database, searchParams);
|
|
6443
6435
|
} catch (error) {
|
|
6444
6436
|
if (queryVector && isVectorDimensionMismatchError(error)) {
|
|
6445
|
-
|
|
6437
|
+
rememberSearchMode(modeKey, "fulltext (embedding dimension mismatch)");
|
|
6446
6438
|
console.error("[memorix] Vector search dimension mismatch detected, retrying without embeddings");
|
|
6447
6439
|
results = await search(database, stripVectorSearchParams(searchParams));
|
|
6448
6440
|
} else {
|
|
@@ -6464,7 +6456,7 @@ async function searchObservations(options) {
|
|
|
6464
6456
|
},
|
|
6465
6457
|
similarity: 0.25
|
|
6466
6458
|
};
|
|
6467
|
-
|
|
6459
|
+
rememberSearchMode(modeKey, "vector-only (hybrid empty fallback)");
|
|
6468
6460
|
results = await search(database, vectorOnlyParams);
|
|
6469
6461
|
} catch {
|
|
6470
6462
|
}
|
|
@@ -6705,7 +6697,7 @@ async function searchObservations(options) {
|
|
|
6705
6697
|
const rerankedTop = neural.map((row) => candidateMap.get(row.id)).filter((entry) => entry != null);
|
|
6706
6698
|
if (rerankedTop.length > 0) {
|
|
6707
6699
|
intermediate = [...rerankedTop, ...intermediate.slice(RERANK_TOP_K)];
|
|
6708
|
-
|
|
6700
|
+
rememberSearchMode(modeKey, (lastSearchModeByProject.get(modeKey) ?? "fulltext") + " + neural rerank");
|
|
6709
6701
|
mark("rerank(usedNeural=true)");
|
|
6710
6702
|
}
|
|
6711
6703
|
}
|
|
@@ -6722,7 +6714,7 @@ async function searchObservations(options) {
|
|
|
6722
6714
|
);
|
|
6723
6715
|
mark(`rerank(usedLLM=${usedLLM})`);
|
|
6724
6716
|
if (usedLLM) {
|
|
6725
|
-
|
|
6717
|
+
rememberSearchMode(modeKey, (lastSearchModeByProject.get(modeKey) ?? "fulltext") + " + LLM rerank");
|
|
6726
6718
|
const rerankedTop = reranked.map((row) => candidateMap.get(row.id)).filter((entry) => entry != null);
|
|
6727
6719
|
if (rerankedTop.length > 0) {
|
|
6728
6720
|
intermediate = [...rerankedTop, ...intermediate.slice(RERANK_TOP_K)];
|
|
@@ -6795,13 +6787,6 @@ async function getObservationsByIds(ids, projectId) {
|
|
|
6795
6787
|
const database = await getDb();
|
|
6796
6788
|
const results = [];
|
|
6797
6789
|
for (const id of ids) {
|
|
6798
|
-
if (projectId) {
|
|
6799
|
-
const cached = docByObservationKey.get(makeEntryKey(projectId, id));
|
|
6800
|
-
if (cached) {
|
|
6801
|
-
results.push(cached);
|
|
6802
|
-
continue;
|
|
6803
|
-
}
|
|
6804
|
-
}
|
|
6805
6790
|
const searchResult = await search(database, {
|
|
6806
6791
|
term: "",
|
|
6807
6792
|
includeVectors: true,
|
|
@@ -6901,7 +6886,7 @@ function formatTime(isoDate) {
|
|
|
6901
6886
|
return isoDate;
|
|
6902
6887
|
}
|
|
6903
6888
|
}
|
|
6904
|
-
var db, dbInitPromise, dbGeneration, embeddingEnabled, embeddingDimensions, indexEmbeddingProvider, deferredCachedVectorHydration,
|
|
6889
|
+
var db, dbInitPromise, dbGeneration, embeddingEnabled, embeddingDimensions, indexEmbeddingProvider, deferredCachedVectorHydration, NON_CJK_HYBRID_SIMILARITY, lastSearchModeByProject, SEARCH_MODE_DEFAULT_KEY, MAX_SEARCH_MODE_KEYS, COMMAND_LOG_TITLE, COMMAND_STYLE_TITLE, COMMAND_LIKE_QUERY2, COMMAND_INTENT_QUERY, NATURAL_LANGUAGE_MARKERS;
|
|
6905
6890
|
var init_orama_store = __esm({
|
|
6906
6891
|
"src/store/orama-store.ts"() {
|
|
6907
6892
|
"use strict";
|
|
@@ -6922,10 +6907,10 @@ var init_orama_store = __esm({
|
|
|
6922
6907
|
embeddingDimensions = null;
|
|
6923
6908
|
indexEmbeddingProvider = null;
|
|
6924
6909
|
deferredCachedVectorHydration = null;
|
|
6925
|
-
docByObservationKey = /* @__PURE__ */ new Map();
|
|
6926
6910
|
NON_CJK_HYBRID_SIMILARITY = 0.45;
|
|
6927
6911
|
lastSearchModeByProject = /* @__PURE__ */ new Map();
|
|
6928
6912
|
SEARCH_MODE_DEFAULT_KEY = "__global__";
|
|
6913
|
+
MAX_SEARCH_MODE_KEYS = 4096;
|
|
6929
6914
|
COMMAND_LOG_TITLE = /^(Ran:|Command:|Executed:)\s/i;
|
|
6930
6915
|
COMMAND_STYLE_TITLE = /(\bfindstr\b|\bSelect-String\b|\bGet-Content\b|\bnpx\s+vitest\b|\bnpx\s+tsc\b|\b2>&1\b)/i;
|
|
6931
6916
|
COMMAND_LIKE_QUERY2 = /\b(git|npm|npx|pnpm|yarn|node|bash|powershell|curl|memorix)\b/i;
|
|
@@ -8322,6 +8307,56 @@ var init_evidence_store = __esm({
|
|
|
8322
8307
|
}
|
|
8323
8308
|
});
|
|
8324
8309
|
|
|
8310
|
+
// src/runtime/maintenance-targets.ts
|
|
8311
|
+
function rowToTarget(row) {
|
|
8312
|
+
return {
|
|
8313
|
+
projectId: row.project_id,
|
|
8314
|
+
projectRoot: row.project_root,
|
|
8315
|
+
dataDir: row.data_dir,
|
|
8316
|
+
updatedAt: Number(row.updated_at)
|
|
8317
|
+
};
|
|
8318
|
+
}
|
|
8319
|
+
var MaintenanceTargetStore;
|
|
8320
|
+
var init_maintenance_targets = __esm({
|
|
8321
|
+
"src/runtime/maintenance-targets.ts"() {
|
|
8322
|
+
"use strict";
|
|
8323
|
+
init_esm_shims();
|
|
8324
|
+
init_sqlite_db();
|
|
8325
|
+
MaintenanceTargetStore = class {
|
|
8326
|
+
db;
|
|
8327
|
+
constructor(dataDir) {
|
|
8328
|
+
this.db = getDatabase(dataDir);
|
|
8329
|
+
}
|
|
8330
|
+
register(input) {
|
|
8331
|
+
if (!input.projectId.trim()) throw new Error("Maintenance target requires a project ID");
|
|
8332
|
+
if (!input.projectRoot.trim()) throw new Error("Maintenance target requires a project root");
|
|
8333
|
+
if (!input.dataDir.trim()) throw new Error("Maintenance target requires a data directory");
|
|
8334
|
+
const now3 = input.now ?? Date.now();
|
|
8335
|
+
this.db.prepare(`
|
|
8336
|
+
INSERT INTO maintenance_targets (project_id, project_root, data_dir, updated_at)
|
|
8337
|
+
VALUES (?, ?, ?, ?)
|
|
8338
|
+
ON CONFLICT(project_id) DO UPDATE SET
|
|
8339
|
+
project_root = excluded.project_root,
|
|
8340
|
+
data_dir = excluded.data_dir,
|
|
8341
|
+
updated_at = excluded.updated_at
|
|
8342
|
+
`).run(input.projectId, input.projectRoot, input.dataDir, now3);
|
|
8343
|
+
return this.get(input.projectId);
|
|
8344
|
+
}
|
|
8345
|
+
get(projectId) {
|
|
8346
|
+
const row = this.db.prepare(`
|
|
8347
|
+
SELECT * FROM maintenance_targets WHERE project_id = ?
|
|
8348
|
+
`).get(projectId);
|
|
8349
|
+
return row ? rowToTarget(row) : void 0;
|
|
8350
|
+
}
|
|
8351
|
+
list() {
|
|
8352
|
+
return this.db.prepare(`
|
|
8353
|
+
SELECT * FROM maintenance_targets ORDER BY updated_at DESC
|
|
8354
|
+
`).all().map(rowToTarget);
|
|
8355
|
+
}
|
|
8356
|
+
};
|
|
8357
|
+
}
|
|
8358
|
+
});
|
|
8359
|
+
|
|
8325
8360
|
// src/knowledge/workspace-store.ts
|
|
8326
8361
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
8327
8362
|
function arrayValue(raw) {
|
|
@@ -150007,10 +150042,21 @@ __export(vector_backfill_runner_exports, {
|
|
|
150007
150042
|
launchDetachedVectorBackfill: () => launchDetachedVectorBackfill,
|
|
150008
150043
|
main: () => main,
|
|
150009
150044
|
parseVectorBackfillRequest: () => parseVectorBackfillRequest,
|
|
150010
|
-
|
|
150045
|
+
releaseVectorBackfillLock: () => releaseVectorBackfillLock,
|
|
150046
|
+
resolveVectorBackfillRunnerPath: () => resolveVectorBackfillRunnerPath,
|
|
150047
|
+
vectorBackfillLockPath: () => vectorBackfillLockPath
|
|
150011
150048
|
});
|
|
150012
150049
|
import { spawn as spawn3 } from "child_process";
|
|
150013
|
-
import {
|
|
150050
|
+
import {
|
|
150051
|
+
closeSync,
|
|
150052
|
+
existsSync as existsSync11,
|
|
150053
|
+
mkdirSync as mkdirSync2,
|
|
150054
|
+
openSync,
|
|
150055
|
+
readFileSync as readFileSync9,
|
|
150056
|
+
statSync as statSync4,
|
|
150057
|
+
unlinkSync,
|
|
150058
|
+
writeFileSync as writeFileSync2
|
|
150059
|
+
} from "fs";
|
|
150014
150060
|
import path20 from "path";
|
|
150015
150061
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
150016
150062
|
function isNonEmptyString(value) {
|
|
@@ -150037,10 +150083,104 @@ function parseVectorBackfillRequest(raw) {
|
|
|
150037
150083
|
dataDir: value.dataDir
|
|
150038
150084
|
};
|
|
150039
150085
|
}
|
|
150086
|
+
function vectorBackfillLockPath(dataDir) {
|
|
150087
|
+
return path20.join(dataDir, VECTOR_BACKFILL_LOCK_FILE);
|
|
150088
|
+
}
|
|
150089
|
+
function processIsAlive(pid) {
|
|
150090
|
+
if (!Number.isSafeInteger(pid) || pid == null || pid <= 0) return false;
|
|
150091
|
+
try {
|
|
150092
|
+
process.kill(pid, 0);
|
|
150093
|
+
return true;
|
|
150094
|
+
} catch (error) {
|
|
150095
|
+
return error.code === "EPERM";
|
|
150096
|
+
}
|
|
150097
|
+
}
|
|
150098
|
+
function readVectorBackfillLock(lockPath) {
|
|
150099
|
+
try {
|
|
150100
|
+
const value = JSON.parse(readFileSync9(lockPath, "utf8"));
|
|
150101
|
+
if (typeof value.parentPid !== "number" || !Number.isSafeInteger(value.parentPid) || typeof value.startedAt !== "number" || !Number.isFinite(value.startedAt) || typeof value.projectId !== "string") return void 0;
|
|
150102
|
+
return {
|
|
150103
|
+
parentPid: value.parentPid,
|
|
150104
|
+
...typeof value.childPid === "number" ? { childPid: value.childPid } : {},
|
|
150105
|
+
startedAt: value.startedAt,
|
|
150106
|
+
projectId: value.projectId
|
|
150107
|
+
};
|
|
150108
|
+
} catch {
|
|
150109
|
+
return void 0;
|
|
150110
|
+
}
|
|
150111
|
+
}
|
|
150112
|
+
function writeVectorBackfillLock(lockPath, value) {
|
|
150113
|
+
writeFileSync2(lockPath, `${JSON.stringify(value)}
|
|
150114
|
+
`, "utf8");
|
|
150115
|
+
}
|
|
150116
|
+
function acquireVectorBackfillLock(dataDir, projectId, preferredPath) {
|
|
150117
|
+
const lockPath = preferredPath ?? vectorBackfillLockPath(dataDir);
|
|
150118
|
+
mkdirSync2(path20.dirname(lockPath), { recursive: true });
|
|
150119
|
+
const initial = {
|
|
150120
|
+
parentPid: process.pid,
|
|
150121
|
+
startedAt: Date.now(),
|
|
150122
|
+
projectId
|
|
150123
|
+
};
|
|
150124
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
150125
|
+
try {
|
|
150126
|
+
const fd = openSync(lockPath, "wx");
|
|
150127
|
+
try {
|
|
150128
|
+
writeFileSync2(fd, `${JSON.stringify(initial)}
|
|
150129
|
+
`, "utf8");
|
|
150130
|
+
} finally {
|
|
150131
|
+
closeSync(fd);
|
|
150132
|
+
}
|
|
150133
|
+
return {
|
|
150134
|
+
path: lockPath,
|
|
150135
|
+
setChildPid(pid) {
|
|
150136
|
+
try {
|
|
150137
|
+
writeVectorBackfillLock(lockPath, { ...initial, childPid: pid });
|
|
150138
|
+
} catch {
|
|
150139
|
+
}
|
|
150140
|
+
},
|
|
150141
|
+
release() {
|
|
150142
|
+
try {
|
|
150143
|
+
unlinkSync(lockPath);
|
|
150144
|
+
} catch {
|
|
150145
|
+
}
|
|
150146
|
+
}
|
|
150147
|
+
};
|
|
150148
|
+
} catch (error) {
|
|
150149
|
+
if (error.code !== "EEXIST") return void 0;
|
|
150150
|
+
const existing = readVectorBackfillLock(lockPath);
|
|
150151
|
+
const ownerAlive = existing && (processIsAlive(existing.childPid) || processIsAlive(existing.parentPid));
|
|
150152
|
+
if (ownerAlive) return void 0;
|
|
150153
|
+
let stale = false;
|
|
150154
|
+
try {
|
|
150155
|
+
stale = Date.now() - statSync4(lockPath).mtimeMs >= STALE_LOCK_RECOVERY_MS;
|
|
150156
|
+
} catch {
|
|
150157
|
+
stale = false;
|
|
150158
|
+
}
|
|
150159
|
+
if (!existing && !stale) return void 0;
|
|
150160
|
+
try {
|
|
150161
|
+
unlinkSync(lockPath);
|
|
150162
|
+
} catch {
|
|
150163
|
+
return void 0;
|
|
150164
|
+
}
|
|
150165
|
+
}
|
|
150166
|
+
}
|
|
150167
|
+
return void 0;
|
|
150168
|
+
}
|
|
150169
|
+
function releaseVectorBackfillLock(lockPath = process.env.MEMORIX_VECTOR_BACKFILL_LOCK) {
|
|
150170
|
+
if (!lockPath) return;
|
|
150171
|
+
const lock = readVectorBackfillLock(lockPath);
|
|
150172
|
+
if (lock && lock.childPid !== process.pid) return;
|
|
150173
|
+
try {
|
|
150174
|
+
unlinkSync(lockPath);
|
|
150175
|
+
} catch {
|
|
150176
|
+
}
|
|
150177
|
+
}
|
|
150040
150178
|
function launchDetachedVectorBackfill(request, options = {}) {
|
|
150041
150179
|
const runnerPath = options.runnerPath ?? resolveVectorBackfillRunnerPath();
|
|
150042
150180
|
const exists = options.exists ?? existsSync11;
|
|
150043
150181
|
if (!exists(runnerPath)) return false;
|
|
150182
|
+
const lock = acquireVectorBackfillLock(request.dataDir, request.projectId, options.lockPath);
|
|
150183
|
+
if (!lock) return false;
|
|
150044
150184
|
try {
|
|
150045
150185
|
const child = (options.spawn ?? spawn3)(process.execPath, [runnerPath], {
|
|
150046
150186
|
cwd: request.projectRoot,
|
|
@@ -150050,14 +150190,23 @@ function launchDetachedVectorBackfill(request, options = {}) {
|
|
|
150050
150190
|
stdio: "ignore",
|
|
150051
150191
|
// The request contains only local project metadata, never credentials.
|
|
150052
150192
|
// Environment transport avoids a live stdin pipe keeping the CLI alive.
|
|
150053
|
-
env: {
|
|
150193
|
+
env: {
|
|
150194
|
+
...process.env,
|
|
150195
|
+
MEMORIX_VECTOR_BACKFILL_REQUEST: JSON.stringify(request),
|
|
150196
|
+
MEMORIX_VECTOR_BACKFILL_LOCK: lock.path
|
|
150197
|
+
},
|
|
150054
150198
|
windowsHide: true
|
|
150055
150199
|
});
|
|
150200
|
+
if (child.pid) lock.setChildPid(child.pid);
|
|
150201
|
+
else lock.release();
|
|
150056
150202
|
child.once?.("error", () => {
|
|
150057
150203
|
});
|
|
150204
|
+
child.once?.("close", () => lock.release());
|
|
150205
|
+
if (typeof child.once !== "function") lock.release();
|
|
150058
150206
|
child.unref();
|
|
150059
|
-
return
|
|
150207
|
+
return Boolean(child.pid);
|
|
150060
150208
|
} catch {
|
|
150209
|
+
lock.release();
|
|
150061
150210
|
return false;
|
|
150062
150211
|
}
|
|
150063
150212
|
}
|
|
@@ -150069,12 +150218,32 @@ async function executeVectorBackfill(request) {
|
|
|
150069
150218
|
await prepareSearchIndex();
|
|
150070
150219
|
await getDeferredCachedVectorHydration()?.catch(() => {
|
|
150071
150220
|
});
|
|
150221
|
+
const queue = new MaintenanceJobStore(request.dataDir);
|
|
150222
|
+
const targets = new MaintenanceTargetStore(request.dataDir);
|
|
150223
|
+
let preparedRoot = request.projectRoot;
|
|
150224
|
+
const prepareTarget = async (target) => {
|
|
150225
|
+
if (target.projectRoot === preparedRoot) return;
|
|
150226
|
+
initProjectRoot(target.projectRoot);
|
|
150227
|
+
loadDotenv(target.projectRoot);
|
|
150228
|
+
preparedRoot = target.projectRoot;
|
|
150229
|
+
};
|
|
150230
|
+
const handler = async (job) => {
|
|
150231
|
+
const target = targets.get(job.projectId) ?? (job.projectId === request.projectId ? { projectId: request.projectId, projectRoot: request.projectRoot, dataDir: request.dataDir, updatedAt: Date.now() } : void 0);
|
|
150232
|
+
if (!target) return { action: "reschedule", delayMs: 3e4 };
|
|
150233
|
+
await prepareTarget(target);
|
|
150234
|
+
return createProjectMaintenanceHandler(job.projectId, target.dataDir, target.projectRoot)(job);
|
|
150235
|
+
};
|
|
150072
150236
|
const worker = new MaintenanceJobWorker(
|
|
150073
|
-
|
|
150074
|
-
|
|
150075
|
-
{
|
|
150237
|
+
queue,
|
|
150238
|
+
handler,
|
|
150239
|
+
{ kinds: ["vector-backfill"] }
|
|
150076
150240
|
);
|
|
150077
|
-
|
|
150241
|
+
let result = { state: "idle" };
|
|
150242
|
+
while (true) {
|
|
150243
|
+
result = await worker.runOnce();
|
|
150244
|
+
if (result.state === "idle") break;
|
|
150245
|
+
}
|
|
150246
|
+
return result;
|
|
150078
150247
|
}
|
|
150079
150248
|
async function readStdin() {
|
|
150080
150249
|
return new Promise((resolve2, reject) => {
|
|
@@ -150097,9 +150266,11 @@ async function main() {
|
|
|
150097
150266
|
`);
|
|
150098
150267
|
process.exitCode = 1;
|
|
150099
150268
|
} finally {
|
|
150269
|
+
releaseVectorBackfillLock();
|
|
150100
150270
|
closeAllDatabases();
|
|
150101
150271
|
}
|
|
150102
150272
|
}
|
|
150273
|
+
var VECTOR_BACKFILL_LOCK_FILE, STALE_LOCK_RECOVERY_MS;
|
|
150103
150274
|
var init_vector_backfill_runner = __esm({
|
|
150104
150275
|
"src/runtime/vector-backfill-runner.ts"() {
|
|
150105
150276
|
"use strict";
|
|
@@ -150112,7 +150283,10 @@ var init_vector_backfill_runner = __esm({
|
|
|
150112
150283
|
init_orama_store();
|
|
150113
150284
|
init_sqlite_db();
|
|
150114
150285
|
init_maintenance_jobs();
|
|
150286
|
+
init_maintenance_targets();
|
|
150115
150287
|
init_project_maintenance();
|
|
150288
|
+
VECTOR_BACKFILL_LOCK_FILE = ".memorix-vector-backfill.lock";
|
|
150289
|
+
STALE_LOCK_RECOVERY_MS = 24 * 60 * 60 * 1e3;
|
|
150116
150290
|
if (process.argv[1] && process.argv[1].endsWith("vector-backfill-runner.js")) {
|
|
150117
150291
|
void main();
|
|
150118
150292
|
}
|
|
@@ -150154,11 +150328,17 @@ function resetObservationRuntime() {
|
|
|
150154
150328
|
embeddingWorkerProjectRoot = void 0;
|
|
150155
150329
|
vectorMissingIds.clear();
|
|
150156
150330
|
vectorSchemaUpgradePromise = null;
|
|
150331
|
+
embeddingFailureLogTimestamps.clear();
|
|
150157
150332
|
}
|
|
150158
150333
|
function logEmbeddingFailureOnce(key, message) {
|
|
150159
150334
|
const now3 = Date.now();
|
|
150160
150335
|
const last = embeddingFailureLogTimestamps.get(key) ?? 0;
|
|
150161
150336
|
if (now3 - last < EMBEDDING_FAILURE_LOG_COOLDOWN_MS) return;
|
|
150337
|
+
if (!embeddingFailureLogTimestamps.has(key) && embeddingFailureLogTimestamps.size >= MAX_EMBEDDING_FAILURE_KEYS) {
|
|
150338
|
+
const oldest = embeddingFailureLogTimestamps.keys().next().value;
|
|
150339
|
+
if (oldest !== void 0) embeddingFailureLogTimestamps.delete(oldest);
|
|
150340
|
+
}
|
|
150341
|
+
embeddingFailureLogTimestamps.delete(key);
|
|
150162
150342
|
embeddingFailureLogTimestamps.set(key, now3);
|
|
150163
150343
|
console.error(message);
|
|
150164
150344
|
}
|
|
@@ -150188,13 +150368,14 @@ function queueVectorBackfill(projectId, options = {}) {
|
|
|
150188
150368
|
const dataDir = projectDir;
|
|
150189
150369
|
if (!dataDir) return;
|
|
150190
150370
|
void Promise.resolve().then(() => (init_maintenance_jobs(), maintenance_jobs_exports)).then(({ MaintenanceJobStore: MaintenanceJobStore2 }) => {
|
|
150191
|
-
new MaintenanceJobStore2(dataDir).enqueue({
|
|
150371
|
+
const job = new MaintenanceJobStore2(dataDir).enqueue({
|
|
150192
150372
|
projectId,
|
|
150193
150373
|
kind: "vector-backfill",
|
|
150194
150374
|
dedupeKey: "vector-backfill",
|
|
150195
150375
|
payload: { limit: 12 }
|
|
150196
150376
|
});
|
|
150197
|
-
|
|
150377
|
+
const due = !job || job.status === "pending" && job.runAfter <= Date.now();
|
|
150378
|
+
if (due && options.detachedWorker && options.projectRoot) {
|
|
150198
150379
|
void Promise.resolve().then(() => (init_vector_backfill_runner(), vector_backfill_runner_exports)).then(({ launchDetachedVectorBackfill: launchDetachedVectorBackfill2 }) => {
|
|
150199
150380
|
launchDetachedVectorBackfill2({
|
|
150200
150381
|
projectId,
|
|
@@ -151125,7 +151306,7 @@ async function backfillVectorEmbeddings(options = {}) {
|
|
|
151125
151306
|
...lastFailure ? { lastError: lastFailure } : {}
|
|
151126
151307
|
};
|
|
151127
151308
|
}
|
|
151128
|
-
var observations, nextId, projectDir, searchIndexPrepared, embeddingWriteMode, embeddingWorkerProjectRoot, corpusLoaded, loadedObservationStore, vectorMissingIds, vectorBackfillRunning, vectorSchemaUpgradePromise, lastVectorBackfill, embeddingFailureLogTimestamps, EMBEDDING_FAILURE_LOG_COOLDOWN_MS;
|
|
151309
|
+
var observations, nextId, projectDir, searchIndexPrepared, embeddingWriteMode, embeddingWorkerProjectRoot, corpusLoaded, loadedObservationStore, vectorMissingIds, vectorBackfillRunning, vectorSchemaUpgradePromise, lastVectorBackfill, embeddingFailureLogTimestamps, EMBEDDING_FAILURE_LOG_COOLDOWN_MS, MAX_EMBEDDING_FAILURE_KEYS;
|
|
151129
151310
|
var init_observations = __esm({
|
|
151130
151311
|
"src/memory/observations.ts"() {
|
|
151131
151312
|
"use strict";
|
|
@@ -151153,6 +151334,7 @@ var init_observations = __esm({
|
|
|
151153
151334
|
lastVectorBackfill = null;
|
|
151154
151335
|
embeddingFailureLogTimestamps = /* @__PURE__ */ new Map();
|
|
151155
151336
|
EMBEDDING_FAILURE_LOG_COOLDOWN_MS = 3e4;
|
|
151337
|
+
MAX_EMBEDDING_FAILURE_KEYS = 1024;
|
|
151156
151338
|
}
|
|
151157
151339
|
});
|
|
151158
151340
|
|
|
@@ -4,6 +4,24 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [1.9.0] - 2026-09-07
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- **Opt-in multi-device sync** -- adds a project-scoped observation
|
|
11
|
+
event contract with privacy filtering, local outbox retry, conflict evidence,
|
|
12
|
+
device-clone protection, bounded relay pulls, and filesystem/GitHub/S3/
|
|
13
|
+
Postgres transport boundaries. It is disabled by default and keeps local
|
|
14
|
+
SQLite as the canonical store.
|
|
15
|
+
- **Sync hardening** -- retracts records that become private or agent-targeted,
|
|
16
|
+
keeps imported observations and sync state atomic, rejects immutable-key
|
|
17
|
+
collisions, verifies every pulled content hash, and uses stable continuation
|
|
18
|
+
cursors so large pulls do not skip events.
|
|
19
|
+
- **Resource hardening** -- coalesces deferred vector workers across hook
|
|
20
|
+
processes, respects retry cooldowns, drains due vector jobs through one
|
|
21
|
+
recoverable worker, removes the redundant full-document detail cache, and
|
|
22
|
+
batches startup vector hydration to reduce CPU/RAM peaks without imposing a
|
|
23
|
+
small corpus limit.
|
|
24
|
+
|
|
7
25
|
## [1.8.9] - 2026-09-06
|
|
8
26
|
|
|
9
27
|
### Added
|