memorix 1.2.4 → 1.2.5
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 +13 -0
- package/dist/cli/index.js +147 -61
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +92 -55
- package/dist/index.js.map +1 -1
- package/dist/maintenance-runner.js +79 -35
- package/dist/maintenance-runner.js.map +1 -1
- package/dist/memcode-runtime/CHANGELOG.md +13 -0
- package/dist/sdk.d.ts +4 -2
- package/dist/sdk.js +93 -55
- package/dist/sdk.js.map +1 -1
- package/dist/types.d.ts +8 -1
- package/dist/types.js.map +1 -1
- package/docs/1.2.5-RETRIEVAL-PERFORMANCE.md +85 -0
- package/docs/PERFORMANCE.md +22 -1
- package/docs/dev-log/progress.txt +33 -8
- package/package.json +2 -1
- package/plugins/codex/memorix/.codex-plugin/plugin.json +1 -1
- package/src/cli/commands/memory.ts +12 -3
- package/src/cli/commands/operator-shared.ts +74 -2
- package/src/cli/index.ts +2 -0
- package/src/config/resolved-config.ts +29 -4
- package/src/runtime/project-maintenance.ts +1 -14
- package/src/sdk.ts +4 -0
- package/src/server.ts +8 -18
- package/src/store/orama-store.ts +39 -18
- package/src/timeout.ts +23 -0
- package/src/types.ts +8 -0
|
@@ -1307,6 +1307,25 @@ function getResolvedConfig(options = {}) {
|
|
|
1307
1307
|
const legacy = loadFileConfig();
|
|
1308
1308
|
const embeddingBaseUrl = first(process.env.MEMORIX_EMBEDDING_BASE_URL, toml.embedding?.base_url, yaml.embedding?.baseUrl, legacy.embeddingApi?.baseUrl);
|
|
1309
1309
|
const openRouterEmbeddingApiKey = isOpenRouterUrl(embeddingBaseUrl) ? process.env.OPENROUTER_API_KEY : void 0;
|
|
1310
|
+
const memoryLlmProvider = first(
|
|
1311
|
+
process.env.MEMORIX_LLM_PROVIDER,
|
|
1312
|
+
toml.memory?.llm?.provider,
|
|
1313
|
+
yaml.llm?.provider,
|
|
1314
|
+
legacy.llm?.provider
|
|
1315
|
+
);
|
|
1316
|
+
const memoryLlmModel = first(
|
|
1317
|
+
process.env.MEMORIX_LLM_MODEL,
|
|
1318
|
+
toml.memory?.llm?.model,
|
|
1319
|
+
yaml.llm?.model,
|
|
1320
|
+
legacy.llm?.model
|
|
1321
|
+
);
|
|
1322
|
+
const memoryLlmBaseUrl = first(
|
|
1323
|
+
process.env.MEMORIX_LLM_BASE_URL,
|
|
1324
|
+
toml.memory?.llm?.base_url,
|
|
1325
|
+
yaml.llm?.baseUrl,
|
|
1326
|
+
legacy.llm?.baseUrl
|
|
1327
|
+
);
|
|
1328
|
+
const openRouterMemoryLlmApiKey = isOpenRouterMemoryLane(memoryLlmProvider, memoryLlmBaseUrl) ? process.env.OPENROUTER_API_KEY : void 0;
|
|
1310
1329
|
const resolved = {
|
|
1311
1330
|
agent: {
|
|
1312
1331
|
provider: first(
|
|
@@ -1344,9 +1363,9 @@ function getResolvedConfig(options = {}) {
|
|
|
1344
1363
|
autoCleanup: firstBool(toml.memory?.auto_cleanup, yaml.behavior?.autoCleanup),
|
|
1345
1364
|
syncAdvisory: firstBool(toml.memory?.sync_advisory, yaml.behavior?.syncAdvisory),
|
|
1346
1365
|
llm: {
|
|
1347
|
-
provider:
|
|
1348
|
-
model:
|
|
1349
|
-
baseUrl:
|
|
1366
|
+
provider: memoryLlmProvider,
|
|
1367
|
+
model: memoryLlmModel,
|
|
1368
|
+
baseUrl: memoryLlmBaseUrl,
|
|
1350
1369
|
apiKey: first(
|
|
1351
1370
|
process.env.MEMORIX_LLM_API_KEY,
|
|
1352
1371
|
process.env.MEMORIX_API_KEY,
|
|
@@ -1355,7 +1374,7 @@ function getResolvedConfig(options = {}) {
|
|
|
1355
1374
|
legacy.llm?.apiKey,
|
|
1356
1375
|
process.env.OPENAI_API_KEY,
|
|
1357
1376
|
process.env.ANTHROPIC_API_KEY,
|
|
1358
|
-
|
|
1377
|
+
openRouterMemoryLlmApiKey
|
|
1359
1378
|
)
|
|
1360
1379
|
}
|
|
1361
1380
|
},
|
|
@@ -1492,6 +1511,9 @@ function isOpenRouterUrl(value) {
|
|
|
1492
1511
|
return /(^|\.)openrouter\.ai(?::|\/|$)/i.test(value);
|
|
1493
1512
|
}
|
|
1494
1513
|
}
|
|
1514
|
+
function isOpenRouterMemoryLane(provider2, baseUrl) {
|
|
1515
|
+
return provider2?.trim().toLowerCase() === "openrouter" || isOpenRouterUrl(baseUrl);
|
|
1516
|
+
}
|
|
1495
1517
|
function normalizeExternalContext(value) {
|
|
1496
1518
|
const normalized = value?.trim().toLowerCase();
|
|
1497
1519
|
if (normalized === "auto" || normalized === "off") return normalized;
|
|
@@ -4259,6 +4281,29 @@ Rules:
|
|
|
4259
4281
|
}
|
|
4260
4282
|
});
|
|
4261
4283
|
|
|
4284
|
+
// src/timeout.ts
|
|
4285
|
+
function withTimeout(promise, ms, label) {
|
|
4286
|
+
return new Promise((resolve, reject) => {
|
|
4287
|
+
const timer = setTimeout(() => reject(new Error(`${label} timed out after ${ms}ms`)), ms);
|
|
4288
|
+
promise.then(
|
|
4289
|
+
(value) => {
|
|
4290
|
+
clearTimeout(timer);
|
|
4291
|
+
resolve(value);
|
|
4292
|
+
},
|
|
4293
|
+
(error) => {
|
|
4294
|
+
clearTimeout(timer);
|
|
4295
|
+
reject(error);
|
|
4296
|
+
}
|
|
4297
|
+
);
|
|
4298
|
+
});
|
|
4299
|
+
}
|
|
4300
|
+
var init_timeout = __esm({
|
|
4301
|
+
"src/timeout.ts"() {
|
|
4302
|
+
"use strict";
|
|
4303
|
+
init_esm_shims();
|
|
4304
|
+
}
|
|
4305
|
+
});
|
|
4306
|
+
|
|
4262
4307
|
// src/project/aliases.ts
|
|
4263
4308
|
var aliases_exports = {};
|
|
4264
4309
|
__export(aliases_exports, {
|
|
@@ -5024,8 +5069,12 @@ async function searchObservations(options) {
|
|
|
5024
5069
|
}
|
|
5025
5070
|
};
|
|
5026
5071
|
const modeKey = options.projectId ?? SEARCH_MODE_DEFAULT_KEY;
|
|
5027
|
-
|
|
5072
|
+
const quality = options.quality ?? "balanced";
|
|
5028
5073
|
const database = await getDb();
|
|
5074
|
+
lastSearchModeByProject.set(
|
|
5075
|
+
modeKey,
|
|
5076
|
+
quality === "fast" ? "fulltext (fast profile)" : embeddingEnabled ? "hybrid" : "fulltext"
|
|
5077
|
+
);
|
|
5029
5078
|
let projectIds = null;
|
|
5030
5079
|
if (options.projectId) {
|
|
5031
5080
|
try {
|
|
@@ -5047,11 +5096,15 @@ async function searchObservations(options) {
|
|
|
5047
5096
|
}
|
|
5048
5097
|
const hasQuery = options.query && options.query.trim().length > 0;
|
|
5049
5098
|
const originalQuery = options.query;
|
|
5050
|
-
const tier = hasQuery ? classifyQueryTier(originalQuery) : "fast";
|
|
5099
|
+
const tier = quality === "fast" ? "fast" : hasQuery ? classifyQueryTier(originalQuery) : "fast";
|
|
5051
5100
|
mark(`tier=${tier}`);
|
|
5052
|
-
|
|
5101
|
+
if (quality === "thorough" && hasQuery) {
|
|
5102
|
+
const { initLLM: initLLM2, isLLMEnabled: isLLMEnabled2 } = await Promise.resolve().then(() => (init_provider(), provider_exports));
|
|
5103
|
+
if (!isLLMEnabled2()) initLLM2();
|
|
5104
|
+
}
|
|
5105
|
+
const expandedEmbeddingQuery = quality === "thorough" && tier === "heavy" ? await maybeExpandSearchQuery(options.query) : options.query;
|
|
5053
5106
|
mark("queryExpansion");
|
|
5054
|
-
const intentResult = hasQuery ? detectQueryIntent(originalQuery)
|
|
5107
|
+
const intentResult = quality === "fast" || !hasQuery ? null : detectQueryIntent(originalQuery);
|
|
5055
5108
|
const requestLimit = projectIds ? (options.limit ?? 20) * 3 : options.limit ?? 20;
|
|
5056
5109
|
const defaultBoost = {
|
|
5057
5110
|
title: 3,
|
|
@@ -5065,17 +5118,21 @@ async function searchObservations(options) {
|
|
|
5065
5118
|
let searchParams = {
|
|
5066
5119
|
term: originalQuery,
|
|
5067
5120
|
limit: requestLimit,
|
|
5068
|
-
|
|
5121
|
+
// Fast search never uses vectors, so returning them only adds work and
|
|
5122
|
+
// payload pressure to an explicitly latency-sensitive path.
|
|
5123
|
+
includeVectors: quality !== "fast",
|
|
5069
5124
|
...Object.keys(filters).length > 0 ? { where: filters } : {},
|
|
5070
5125
|
// Search specific fields (not tokens, accessCount, etc.)
|
|
5071
5126
|
properties: ["title", "entityName", "narrative", "facts", "concepts", "filesModified"],
|
|
5072
5127
|
// Field boosting: intent-aware or default
|
|
5073
5128
|
boost: fieldBoost,
|
|
5074
5129
|
// Fuzzy tolerance: allow 1-char typos for short queries, 2 for longer
|
|
5075
|
-
|
|
5130
|
+
// Fuzzy matching is useful for ordinary recall, but grows sharply with
|
|
5131
|
+
// corpus size. The fast profile deliberately uses exact lexical matching.
|
|
5132
|
+
...hasQuery && quality !== "fast" ? { tolerance: originalQuery.length > 6 ? 2 : 1 } : {}
|
|
5076
5133
|
};
|
|
5077
5134
|
let queryVector = null;
|
|
5078
|
-
if (embeddingEnabled && hasQuery && tier !== "fast") {
|
|
5135
|
+
if (quality !== "fast" && embeddingEnabled && hasQuery && tier !== "fast") {
|
|
5079
5136
|
try {
|
|
5080
5137
|
const provider2 = await getEmbeddingProvider();
|
|
5081
5138
|
if (provider2) {
|
|
@@ -5090,11 +5147,11 @@ async function searchObservations(options) {
|
|
|
5090
5147
|
);
|
|
5091
5148
|
} else {
|
|
5092
5149
|
const EMBEDDING_TIMEOUT_MS = 15e3;
|
|
5093
|
-
|
|
5094
|
-
|
|
5095
|
-
|
|
5150
|
+
queryVector = await withTimeout(
|
|
5151
|
+
provider2.embed(expandedEmbeddingQuery),
|
|
5152
|
+
EMBEDDING_TIMEOUT_MS,
|
|
5153
|
+
"Embedding"
|
|
5096
5154
|
);
|
|
5097
|
-
queryVector = await Promise.race([embedPromise, timeoutPromise]);
|
|
5098
5155
|
mark("embedding");
|
|
5099
5156
|
const cjkRatio = (originalQuery.match(/[\u4e00-\u9fff\u3040-\u309f\u30a0-\u30ff\uac00-\ud7af]/g) || []).length / originalQuery.length;
|
|
5100
5157
|
const isCJKHeavy = cjkRatio > 0.3;
|
|
@@ -5335,7 +5392,7 @@ async function searchObservations(options) {
|
|
|
5335
5392
|
}
|
|
5336
5393
|
}
|
|
5337
5394
|
}
|
|
5338
|
-
const shouldRerank = tier === "heavy" && hasQuery && intermediate.length > 2 && (() => {
|
|
5395
|
+
const shouldRerank = quality === "thorough" && tier === "heavy" && hasQuery && intermediate.length > 2 && (() => {
|
|
5339
5396
|
const top = intermediate[0]?.score ?? 0;
|
|
5340
5397
|
const second = intermediate[1]?.score ?? 0;
|
|
5341
5398
|
return top > 0 && second / top > 0.7;
|
|
@@ -5359,11 +5416,11 @@ async function searchObservations(options) {
|
|
|
5359
5416
|
}));
|
|
5360
5417
|
const _parsedRerank = parseInt(process.env.MEMORIX_RERANK_TIMEOUT_MS || "", 10);
|
|
5361
5418
|
const RERANK_TIMEOUT_MS = Number.isFinite(_parsedRerank) && _parsedRerank > 0 ? _parsedRerank : 5e3;
|
|
5362
|
-
const
|
|
5363
|
-
|
|
5364
|
-
|
|
5419
|
+
const { reranked, usedLLM } = await withTimeout(
|
|
5420
|
+
rerankResults2(originalQuery, candidates),
|
|
5421
|
+
RERANK_TIMEOUT_MS,
|
|
5422
|
+
"LLM rerank"
|
|
5365
5423
|
);
|
|
5366
|
-
const { reranked, usedLLM } = await Promise.race([rerankPromise, timeoutPromise]);
|
|
5367
5424
|
mark(`rerank(usedLLM=${usedLLM})`);
|
|
5368
5425
|
if (usedLLM) {
|
|
5369
5426
|
lastSearchModeByProject.set(modeKey, (lastSearchModeByProject.get(modeKey) ?? "fulltext") + " + LLM rerank");
|
|
@@ -5555,6 +5612,7 @@ var init_orama_store = __esm({
|
|
|
5555
5612
|
init_project_affinity();
|
|
5556
5613
|
init_intent_detector();
|
|
5557
5614
|
init_query_expansion();
|
|
5615
|
+
init_timeout();
|
|
5558
5616
|
db = null;
|
|
5559
5617
|
dbInitPromise = null;
|
|
5560
5618
|
dbGeneration = 0;
|
|
@@ -11799,6 +11857,7 @@ var DEFAULT_TIMEOUT_MS = 5 * 6e4;
|
|
|
11799
11857
|
// src/runtime/project-maintenance.ts
|
|
11800
11858
|
init_esm_shims();
|
|
11801
11859
|
init_lifecycle();
|
|
11860
|
+
init_timeout();
|
|
11802
11861
|
var DEFAULT_VECTOR_BATCH_SIZE = 12;
|
|
11803
11862
|
var DEFAULT_RETENTION_BATCH_SIZE = 100;
|
|
11804
11863
|
var DEFAULT_CONSOLIDATION_BATCH_SIZE = 200;
|
|
@@ -11869,21 +11928,6 @@ async function loadWorkspaceForMaintenance(projectId, projectDir2, mode) {
|
|
|
11869
11928
|
]);
|
|
11870
11929
|
return versioned ?? local;
|
|
11871
11930
|
}
|
|
11872
|
-
function withTimeout(promise, ms, label) {
|
|
11873
|
-
return new Promise((resolve, reject) => {
|
|
11874
|
-
const timer = setTimeout(() => reject(new Error(`${label} timed out after ${ms}ms`)), ms);
|
|
11875
|
-
promise.then(
|
|
11876
|
-
(value) => {
|
|
11877
|
-
clearTimeout(timer);
|
|
11878
|
-
resolve(value);
|
|
11879
|
-
},
|
|
11880
|
-
(error) => {
|
|
11881
|
-
clearTimeout(timer);
|
|
11882
|
-
reject(error);
|
|
11883
|
-
}
|
|
11884
|
-
);
|
|
11885
|
-
});
|
|
11886
|
-
}
|
|
11887
11931
|
async function runAutomaticConsolidation(projectId, projectDir2, options) {
|
|
11888
11932
|
const { isLLMEnabled: isLLMEnabled2 } = await Promise.resolve().then(() => (init_provider(), provider_exports));
|
|
11889
11933
|
if (!isLLMEnabled2()) {
|