memorix 1.1.11 → 1.1.13
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 +25 -0
- package/README.md +3 -1
- package/README.zh-CN.md +3 -1
- package/dist/cli/index.js +679 -78
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +289 -44
- package/dist/index.js.map +1 -1
- package/dist/maintenance-runner.js +252 -36
- package/dist/maintenance-runner.js.map +1 -1
- package/dist/memcode-runtime/CHANGELOG.md +25 -0
- package/dist/memcode-runtime/package.json +5 -4
- package/dist/sdk.js +289 -44
- package/dist/sdk.js.map +1 -1
- package/docs/AGENT_OPERATOR_PLAYBOOK.md +2 -2
- package/docs/DEVELOPMENT.md +3 -10
- package/docs/INTEGRATIONS.md +1 -1
- package/docs/SETUP.md +3 -1
- package/docs/dev-log/progress.txt +34 -53
- package/docs/hooks-architecture.md +24 -12
- package/package.json +1 -1
- package/plugins/codex/memorix/.codex-plugin/plugin.json +2 -1
- package/plugins/codex/memorix/hooks/hooks.json +30 -0
- package/src/cli/commands/agent-integrations.ts +384 -2
- package/src/cli/commands/hooks-install.ts +2 -1
- package/src/cli/commands/setup.ts +20 -2
- package/src/cli/tui/chat-service.ts +3 -9
- package/src/cli/tui/data.ts +9 -11
- package/src/embedding/api-provider.ts +105 -6
- package/src/embedding/provider.ts +31 -3
- package/src/hooks/handler.ts +51 -14
- package/src/hooks/installers/index.ts +48 -7
- package/src/hooks/normalizer.ts +15 -0
- package/src/memory/observations.ts +90 -15
- package/src/store/orama-store.ts +169 -17
package/dist/index.js
CHANGED
|
@@ -2809,6 +2809,45 @@ function dimsCacheKey(config) {
|
|
|
2809
2809
|
config.requestedDimensions ?? "native"
|
|
2810
2810
|
].join("|");
|
|
2811
2811
|
}
|
|
2812
|
+
function isValidDimension(value) {
|
|
2813
|
+
return typeof value === "number" && Number.isInteger(value) && value > 0;
|
|
2814
|
+
}
|
|
2815
|
+
async function loadCachedVectorDimensions(config) {
|
|
2816
|
+
try {
|
|
2817
|
+
const raw = await readFile2(CACHE_META_FILE, "utf-8");
|
|
2818
|
+
const data = JSON.parse(raw);
|
|
2819
|
+
const namespace = cacheNamespace(config);
|
|
2820
|
+
if (!Array.isArray(data?.entries)) return null;
|
|
2821
|
+
const entry = data.entries.find(
|
|
2822
|
+
(candidate) => typeof candidate === "object" && candidate !== null && candidate.namespace === namespace && isValidDimension(candidate.dimensions)
|
|
2823
|
+
);
|
|
2824
|
+
return entry?.dimensions ?? null;
|
|
2825
|
+
} catch {
|
|
2826
|
+
return null;
|
|
2827
|
+
}
|
|
2828
|
+
}
|
|
2829
|
+
async function saveCachedVectorDimensions(config, dimensions) {
|
|
2830
|
+
if (!isValidDimension(dimensions)) return;
|
|
2831
|
+
try {
|
|
2832
|
+
await mkdir2(CACHE_DIR2, { recursive: true });
|
|
2833
|
+
let entries = [];
|
|
2834
|
+
try {
|
|
2835
|
+
const raw = await readFile2(CACHE_META_FILE, "utf-8");
|
|
2836
|
+
const data = JSON.parse(raw);
|
|
2837
|
+
if (Array.isArray(data?.entries)) {
|
|
2838
|
+
entries = data.entries.filter(
|
|
2839
|
+
(candidate) => typeof candidate === "object" && candidate !== null && typeof candidate.namespace === "string" && isValidDimension(candidate.dimensions) && typeof candidate.ts === "number"
|
|
2840
|
+
);
|
|
2841
|
+
}
|
|
2842
|
+
} catch {
|
|
2843
|
+
}
|
|
2844
|
+
const namespace = cacheNamespace(config);
|
|
2845
|
+
entries = entries.filter((entry) => entry.namespace !== namespace);
|
|
2846
|
+
entries.push({ namespace, dimensions, ts: Date.now() });
|
|
2847
|
+
await writeFile2(CACHE_META_FILE, JSON.stringify({ version: 1, entries }));
|
|
2848
|
+
} catch {
|
|
2849
|
+
}
|
|
2850
|
+
}
|
|
2812
2851
|
async function loadCachedDims(config) {
|
|
2813
2852
|
try {
|
|
2814
2853
|
const raw = await readFile2(DIMS_CACHE_FILE, "utf-8");
|
|
@@ -2871,6 +2910,7 @@ async function saveCachedDims(config, dimensions) {
|
|
|
2871
2910
|
await writeFile2(DIMS_CACHE_FILE, JSON.stringify({ entries }));
|
|
2872
2911
|
} catch {
|
|
2873
2912
|
}
|
|
2913
|
+
await saveCachedVectorDimensions(config, dimensions);
|
|
2874
2914
|
}
|
|
2875
2915
|
async function saveDiskCacheNow() {
|
|
2876
2916
|
if (!diskCacheDirty2) return;
|
|
@@ -2953,7 +2993,7 @@ async function fetchWithRetry(url, apiKey, body, attempt = 0) {
|
|
|
2953
2993
|
const errorText2 = await response.text().catch(() => "unknown error");
|
|
2954
2994
|
throw new Error(`Embedding API error (${response.status}): ${errorText2}`);
|
|
2955
2995
|
}
|
|
2956
|
-
var CACHE_DIR2, CACHE_FILE2, DIMS_CACHE_FILE, cache3, MAX_CACHE_SIZE3, diskCacheDirty2, diskSaveTimer, diskCacheLoaded, diskCacheLoadPromise, MAX_INPUT_CHARS, MAX_CONCURRENCY, DISK_SAVE_DEBOUNCE_MS, DEFAULT_MAX_BATCH_SIZE, DASHSCOPE_MAX_BATCH_SIZE, MAX_RETRIES, BASE_DELAY_MS, APIEmbeddingProvider;
|
|
2996
|
+
var CACHE_DIR2, CACHE_FILE2, DIMS_CACHE_FILE, CACHE_META_FILE, cache3, MAX_CACHE_SIZE3, diskCacheDirty2, diskSaveTimer, diskCacheLoaded, diskCacheLoadPromise, MAX_INPUT_CHARS, MAX_CONCURRENCY, DISK_SAVE_DEBOUNCE_MS, DEFAULT_MAX_BATCH_SIZE, DASHSCOPE_MAX_BATCH_SIZE, MAX_RETRIES, BASE_DELAY_MS, APIEmbeddingProvider;
|
|
2957
2997
|
var init_api_provider = __esm({
|
|
2958
2998
|
"src/embedding/api-provider.ts"() {
|
|
2959
2999
|
"use strict";
|
|
@@ -2961,6 +3001,7 @@ var init_api_provider = __esm({
|
|
|
2961
3001
|
CACHE_DIR2 = process.env.MEMORIX_DATA_DIR || join3(homedir7(), ".memorix", "data");
|
|
2962
3002
|
CACHE_FILE2 = join3(CACHE_DIR2, ".embedding-api-cache.json");
|
|
2963
3003
|
DIMS_CACHE_FILE = join3(CACHE_DIR2, ".embedding-dims-cache.json");
|
|
3004
|
+
CACHE_META_FILE = join3(CACHE_DIR2, ".embedding-api-cache-meta.json");
|
|
2964
3005
|
cache3 = /* @__PURE__ */ new Map();
|
|
2965
3006
|
MAX_CACHE_SIZE3 = 1e4;
|
|
2966
3007
|
diskCacheDirty2 = false;
|
|
@@ -2987,13 +3028,22 @@ var init_api_provider = __esm({
|
|
|
2987
3028
|
this.dimensions = detectedDimensions;
|
|
2988
3029
|
this.name = `api-${config.model.replace(/\//g, "-")}`;
|
|
2989
3030
|
}
|
|
2990
|
-
static async create() {
|
|
3031
|
+
static async create(options = {}) {
|
|
2991
3032
|
const config = _APIEmbeddingProvider.resolveConfig();
|
|
2992
|
-
|
|
3033
|
+
const allowNetworkProbe = options.allowNetworkProbe !== false;
|
|
2993
3034
|
let probeDimensions = await loadCachedDims(config);
|
|
3035
|
+
let dimensionSource = "dims-cache";
|
|
3036
|
+
if (probeDimensions === null) {
|
|
3037
|
+
probeDimensions = await loadCachedVectorDimensions(config);
|
|
3038
|
+
if (probeDimensions !== null) dimensionSource = "vector-cache";
|
|
3039
|
+
}
|
|
2994
3040
|
if (probeDimensions !== null) {
|
|
2995
|
-
console.error(`[memorix] API embedding: ${config.model} @ ${config.baseUrl} (${probeDimensions}d) [
|
|
3041
|
+
console.error(`[memorix] API embedding: ${config.model} @ ${config.baseUrl} (${probeDimensions}d) [${dimensionSource}]`);
|
|
3042
|
+
if (dimensionSource === "dims-cache") {
|
|
3043
|
+
void saveCachedVectorDimensions(config, probeDimensions);
|
|
3044
|
+
}
|
|
2996
3045
|
} else {
|
|
3046
|
+
if (!allowNetworkProbe) return null;
|
|
2997
3047
|
probeDimensions = await _APIEmbeddingProvider.probeAPI(config);
|
|
2998
3048
|
console.error(`[memorix] API embedding: ${config.model} @ ${config.baseUrl} (${probeDimensions}d)`);
|
|
2999
3049
|
saveCachedDims(config, probeDimensions).catch(() => {
|
|
@@ -3002,6 +3052,7 @@ var init_api_provider = __esm({
|
|
|
3002
3052
|
if (config.requestedDimensions) {
|
|
3003
3053
|
console.error(`[memorix] Dimension shortening: ${config.requestedDimensions}d requested`);
|
|
3004
3054
|
}
|
|
3055
|
+
startDiskCacheLoad();
|
|
3005
3056
|
return new _APIEmbeddingProvider(config, probeDimensions);
|
|
3006
3057
|
}
|
|
3007
3058
|
static resolveConfig() {
|
|
@@ -3180,6 +3231,13 @@ var init_api_provider = __esm({
|
|
|
3180
3231
|
scheduleDiskSave();
|
|
3181
3232
|
return results;
|
|
3182
3233
|
}
|
|
3234
|
+
async getCachedEmbeddings(texts) {
|
|
3235
|
+
await ensureDiskCacheLoaded();
|
|
3236
|
+
return texts.map((text) => {
|
|
3237
|
+
const hash = textHash2(normalizeText(text), this.cacheKeyNamespace);
|
|
3238
|
+
return cache3.get(hash) ?? null;
|
|
3239
|
+
});
|
|
3240
|
+
}
|
|
3183
3241
|
getStats() {
|
|
3184
3242
|
return {
|
|
3185
3243
|
totalTokens: this.totalTokensUsed,
|
|
@@ -3288,10 +3346,10 @@ async function createTransformersProvider() {
|
|
|
3288
3346
|
return null;
|
|
3289
3347
|
}
|
|
3290
3348
|
}
|
|
3291
|
-
async function createAPIProvider() {
|
|
3349
|
+
async function createAPIProvider(options) {
|
|
3292
3350
|
try {
|
|
3293
3351
|
const { APIEmbeddingProvider: APIEmbeddingProvider2 } = await Promise.resolve().then(() => (init_api_provider(), api_provider_exports));
|
|
3294
|
-
return await APIEmbeddingProvider2.create();
|
|
3352
|
+
return await APIEmbeddingProvider2.create(options ?? {});
|
|
3295
3353
|
} catch (e) {
|
|
3296
3354
|
warnOnce(`[memorix] Failed to init API embedding: ${e instanceof Error ? e.message : e}`);
|
|
3297
3355
|
return null;
|
|
@@ -3346,11 +3404,24 @@ function wrapProvider(candidate) {
|
|
|
3346
3404
|
}
|
|
3347
3405
|
throw error;
|
|
3348
3406
|
}
|
|
3349
|
-
}
|
|
3407
|
+
},
|
|
3408
|
+
...candidate.getCachedEmbeddings ? {
|
|
3409
|
+
async getCachedEmbeddings(texts) {
|
|
3410
|
+
return candidate.getCachedEmbeddings(texts);
|
|
3411
|
+
}
|
|
3412
|
+
} : {}
|
|
3350
3413
|
};
|
|
3351
3414
|
}
|
|
3352
|
-
async function getEmbeddingProvider() {
|
|
3415
|
+
async function getEmbeddingProvider(options = {}) {
|
|
3353
3416
|
if (provider) return provider;
|
|
3417
|
+
if (options.allowNetworkProbe === false) {
|
|
3418
|
+
const mode = getEmbeddingMode2();
|
|
3419
|
+
if (mode !== "api" && !(mode === "auto" && hasAPIEmbeddingConfig())) {
|
|
3420
|
+
return null;
|
|
3421
|
+
}
|
|
3422
|
+
const cached = await createAPIProvider({ allowNetworkProbe: false });
|
|
3423
|
+
return cached ? wrapProvider(cached) : null;
|
|
3424
|
+
}
|
|
3354
3425
|
if (lastInitWasTemporaryFailure) {
|
|
3355
3426
|
const elapsed = Date.now() - lastFailureTimestamp;
|
|
3356
3427
|
if (elapsed < RETRY_COOLDOWN_MS) {
|
|
@@ -4863,12 +4934,15 @@ __export(orama_store_exports, {
|
|
|
4863
4934
|
batchGenerateEmbeddings: () => batchGenerateEmbeddings,
|
|
4864
4935
|
generateEmbedding: () => generateEmbedding,
|
|
4865
4936
|
getDb: () => getDb,
|
|
4937
|
+
getDeferredCachedVectorHydration: () => getDeferredCachedVectorHydration,
|
|
4866
4938
|
getLastSearchMode: () => getLastSearchMode,
|
|
4867
4939
|
getObservationCount: () => getObservationCount,
|
|
4868
4940
|
getObservationsByIds: () => getObservationsByIds,
|
|
4869
4941
|
getTimeline: () => getTimeline,
|
|
4870
4942
|
getVectorDimensions: () => getVectorDimensions,
|
|
4943
|
+
hasObservationVector: () => hasObservationVector,
|
|
4871
4944
|
hydrateIndex: () => hydrateIndex,
|
|
4945
|
+
hydrateIndexForStartup: () => hydrateIndexForStartup,
|
|
4872
4946
|
insertObservation: () => insertObservation,
|
|
4873
4947
|
isEmbeddingEnabled: () => isEmbeddingEnabled,
|
|
4874
4948
|
makeOramaObservationId: () => makeOramaObservationId,
|
|
@@ -4929,11 +5003,10 @@ function stripVectorSearchParams(params) {
|
|
|
4929
5003
|
const { mode, vector, similarity, hybridWeights, ...rest } = params;
|
|
4930
5004
|
return rest;
|
|
4931
5005
|
}
|
|
4932
|
-
async function
|
|
4933
|
-
|
|
4934
|
-
const
|
|
4935
|
-
|
|
4936
|
-
embeddingDimensions = provider2?.dimensions ?? null;
|
|
5006
|
+
async function initializeDb(options, generation) {
|
|
5007
|
+
const provider2 = await getEmbeddingProvider({ allowNetworkProbe: options.allowNetworkProbe });
|
|
5008
|
+
const nextEmbeddingEnabled = provider2 !== null;
|
|
5009
|
+
const nextEmbeddingDimensions = provider2?.dimensions ?? null;
|
|
4937
5010
|
const baseSchema = {
|
|
4938
5011
|
id: "string",
|
|
4939
5012
|
observationId: "number",
|
|
@@ -4956,15 +5029,39 @@ async function getDb() {
|
|
|
4956
5029
|
documentType: "string",
|
|
4957
5030
|
knowledgeLayer: "string"
|
|
4958
5031
|
};
|
|
4959
|
-
const dims =
|
|
4960
|
-
const schema =
|
|
4961
|
-
|
|
4962
|
-
|
|
5032
|
+
const dims = nextEmbeddingDimensions ?? 384;
|
|
5033
|
+
const schema = nextEmbeddingEnabled ? { ...baseSchema, embedding: `vector[${dims}]` } : baseSchema;
|
|
5034
|
+
const nextDb = await create({ schema });
|
|
5035
|
+
if (generation !== dbGeneration) {
|
|
5036
|
+
return getDb(options);
|
|
5037
|
+
}
|
|
5038
|
+
indexEmbeddingProvider = provider2;
|
|
5039
|
+
embeddingEnabled = nextEmbeddingEnabled;
|
|
5040
|
+
embeddingDimensions = nextEmbeddingDimensions;
|
|
5041
|
+
db = nextDb;
|
|
5042
|
+
return nextDb;
|
|
5043
|
+
}
|
|
5044
|
+
async function getDb(options = {}) {
|
|
5045
|
+
if (db) return db;
|
|
5046
|
+
if (dbInitPromise) return dbInitPromise;
|
|
5047
|
+
const initPromise2 = initializeDb(options, dbGeneration);
|
|
5048
|
+
dbInitPromise = initPromise2;
|
|
5049
|
+
try {
|
|
5050
|
+
return await initPromise2;
|
|
5051
|
+
} finally {
|
|
5052
|
+
if (dbInitPromise === initPromise2) {
|
|
5053
|
+
dbInitPromise = null;
|
|
5054
|
+
}
|
|
5055
|
+
}
|
|
4963
5056
|
}
|
|
4964
5057
|
async function resetDb() {
|
|
5058
|
+
dbGeneration++;
|
|
4965
5059
|
db = null;
|
|
5060
|
+
dbInitPromise = null;
|
|
4966
5061
|
embeddingEnabled = false;
|
|
4967
5062
|
embeddingDimensions = null;
|
|
5063
|
+
indexEmbeddingProvider = null;
|
|
5064
|
+
deferredCachedVectorHydration = null;
|
|
4968
5065
|
lastSearchModeByProject.clear();
|
|
4969
5066
|
docByObservationKey.clear();
|
|
4970
5067
|
}
|
|
@@ -4992,14 +5089,64 @@ async function batchGenerateEmbeddings(texts) {
|
|
|
4992
5089
|
return texts.map(() => null);
|
|
4993
5090
|
}
|
|
4994
5091
|
}
|
|
4995
|
-
async function
|
|
4996
|
-
|
|
5092
|
+
async function getCachedEmbeddings(texts) {
|
|
5093
|
+
if (!embeddingEnabled || texts.length === 0) return texts.map(() => null);
|
|
5094
|
+
const provider2 = indexEmbeddingProvider;
|
|
5095
|
+
if (!provider2?.getCachedEmbeddings) return texts.map(() => null);
|
|
5096
|
+
try {
|
|
5097
|
+
return await provider2.getCachedEmbeddings(texts);
|
|
5098
|
+
} catch {
|
|
5099
|
+
return texts.map(() => null);
|
|
5100
|
+
}
|
|
5101
|
+
}
|
|
5102
|
+
function observationEmbeddingText(observation) {
|
|
5103
|
+
return [
|
|
5104
|
+
observation.title ?? "",
|
|
5105
|
+
observation.narrative ?? "",
|
|
5106
|
+
...Array.isArray(observation.facts) ? observation.facts : []
|
|
5107
|
+
].join(" ");
|
|
5108
|
+
}
|
|
5109
|
+
function documentEmbeddingText(document) {
|
|
5110
|
+
return [document.title ?? "", document.narrative ?? "", document.facts ?? ""].join(" ");
|
|
5111
|
+
}
|
|
5112
|
+
function isCompatibleCachedVector(vector) {
|
|
5113
|
+
return Boolean(
|
|
5114
|
+
vector && embeddingDimensions !== null && vector.length === embeddingDimensions && vector.every(Number.isFinite)
|
|
5115
|
+
);
|
|
5116
|
+
}
|
|
5117
|
+
async function attachCachedVectors(database, candidates) {
|
|
5118
|
+
const cachedVectors = await getCachedEmbeddings(
|
|
5119
|
+
candidates.map(({ observation }) => observationEmbeddingText(observation))
|
|
5120
|
+
);
|
|
5121
|
+
if (db !== database) return;
|
|
5122
|
+
for (let index = 0; index < candidates.length; index++) {
|
|
5123
|
+
const vector = cachedVectors[index];
|
|
5124
|
+
if (!isCompatibleCachedVector(vector)) continue;
|
|
5125
|
+
const existing = getByID(database, candidates[index].id);
|
|
5126
|
+
if (!existing || documentEmbeddingText(existing) !== observationEmbeddingText(candidates[index].observation)) continue;
|
|
5127
|
+
try {
|
|
5128
|
+
await update(database, candidates[index].id, { ...existing, embedding: vector });
|
|
5129
|
+
} catch {
|
|
5130
|
+
}
|
|
5131
|
+
}
|
|
5132
|
+
}
|
|
5133
|
+
async function hydrateIndex(observations2, options = {}) {
|
|
5134
|
+
const database = await getDb({ allowNetworkProbe: options.allowNetworkProbe });
|
|
5135
|
+
const candidates = [];
|
|
5136
|
+
for (const observation of observations2) {
|
|
5137
|
+
if (!observation || !observation.id || !observation.projectId) continue;
|
|
5138
|
+
const id = makeOramaObservationId(observation.projectId, observation.id);
|
|
5139
|
+
if (getByID(database, id)) continue;
|
|
5140
|
+
candidates.push({ observation, id });
|
|
5141
|
+
}
|
|
5142
|
+
const deferCachedVectors = options.deferCachedVectors === true && Boolean(indexEmbeddingProvider?.getCachedEmbeddings);
|
|
5143
|
+
const cachedVectors = deferCachedVectors ? candidates.map(() => null) : await getCachedEmbeddings(candidates.map(({ observation }) => observationEmbeddingText(observation)));
|
|
4997
5144
|
let inserted = 0;
|
|
4998
|
-
for (
|
|
4999
|
-
|
|
5145
|
+
for (let index = 0; index < candidates.length; index++) {
|
|
5146
|
+
const { observation: obs, id } = candidates[index];
|
|
5000
5147
|
try {
|
|
5001
|
-
const
|
|
5002
|
-
|
|
5148
|
+
const vector = cachedVectors[index];
|
|
5149
|
+
const compatibleVector = isCompatibleCachedVector(vector) ? vector : null;
|
|
5003
5150
|
const doc = {
|
|
5004
5151
|
id,
|
|
5005
5152
|
observationId: obs.id,
|
|
@@ -5018,7 +5165,8 @@ async function hydrateIndex(observations2) {
|
|
|
5018
5165
|
status: obs.status ?? "active",
|
|
5019
5166
|
source: obs.source || "agent",
|
|
5020
5167
|
documentType: "observation",
|
|
5021
|
-
knowledgeLayer: resolveKnowledgeLayer("observation", obs.sourceDetail, obs.source)
|
|
5168
|
+
knowledgeLayer: resolveKnowledgeLayer("observation", obs.sourceDetail, obs.source),
|
|
5169
|
+
...compatibleVector ? { embedding: compatibleVector } : {}
|
|
5022
5170
|
};
|
|
5023
5171
|
await insert2(database, doc);
|
|
5024
5172
|
rememberObservationDoc(doc);
|
|
@@ -5026,8 +5174,24 @@ async function hydrateIndex(observations2) {
|
|
|
5026
5174
|
} catch {
|
|
5027
5175
|
}
|
|
5028
5176
|
}
|
|
5177
|
+
deferredCachedVectorHydration = deferCachedVectors ? attachCachedVectors(database, candidates).catch(() => {
|
|
5178
|
+
}) : null;
|
|
5029
5179
|
return inserted;
|
|
5030
5180
|
}
|
|
5181
|
+
async function hydrateIndexForStartup(observations2) {
|
|
5182
|
+
return hydrateIndex(observations2, {
|
|
5183
|
+
allowNetworkProbe: false,
|
|
5184
|
+
deferCachedVectors: true
|
|
5185
|
+
});
|
|
5186
|
+
}
|
|
5187
|
+
function getDeferredCachedVectorHydration() {
|
|
5188
|
+
return deferredCachedVectorHydration;
|
|
5189
|
+
}
|
|
5190
|
+
function hasObservationVector(projectId, observationId) {
|
|
5191
|
+
if (!db || !embeddingEnabled || embeddingDimensions === null) return false;
|
|
5192
|
+
const document = getByID(db, makeOramaObservationId(projectId, observationId));
|
|
5193
|
+
return Array.isArray(document?.embedding) && document.embedding.length === embeddingDimensions && document.embedding.every(Number.isFinite);
|
|
5194
|
+
}
|
|
5031
5195
|
async function insertObservation(doc) {
|
|
5032
5196
|
const database = await getDb();
|
|
5033
5197
|
await insert2(database, doc);
|
|
@@ -5561,7 +5725,7 @@ function formatTime(isoDate) {
|
|
|
5561
5725
|
return isoDate;
|
|
5562
5726
|
}
|
|
5563
5727
|
}
|
|
5564
|
-
var db, embeddingEnabled, embeddingDimensions, docByObservationKey, NON_CJK_HYBRID_SIMILARITY, lastSearchModeByProject, SEARCH_MODE_DEFAULT_KEY, COMMAND_LOG_TITLE2, COMMAND_STYLE_TITLE, COMMAND_LIKE_QUERY2, COMMAND_INTENT_QUERY, NATURAL_LANGUAGE_MARKERS;
|
|
5728
|
+
var db, dbInitPromise, dbGeneration, embeddingEnabled, embeddingDimensions, indexEmbeddingProvider, deferredCachedVectorHydration, docByObservationKey, NON_CJK_HYBRID_SIMILARITY, lastSearchModeByProject, SEARCH_MODE_DEFAULT_KEY, COMMAND_LOG_TITLE2, COMMAND_STYLE_TITLE, COMMAND_LIKE_QUERY2, COMMAND_INTENT_QUERY, NATURAL_LANGUAGE_MARKERS;
|
|
5565
5729
|
var init_orama_store = __esm({
|
|
5566
5730
|
"src/store/orama-store.ts"() {
|
|
5567
5731
|
"use strict";
|
|
@@ -5573,8 +5737,12 @@ var init_orama_store = __esm({
|
|
|
5573
5737
|
init_intent_detector();
|
|
5574
5738
|
init_query_expansion();
|
|
5575
5739
|
db = null;
|
|
5740
|
+
dbInitPromise = null;
|
|
5741
|
+
dbGeneration = 0;
|
|
5576
5742
|
embeddingEnabled = false;
|
|
5577
5743
|
embeddingDimensions = null;
|
|
5744
|
+
indexEmbeddingProvider = null;
|
|
5745
|
+
deferredCachedVectorHydration = null;
|
|
5578
5746
|
docByObservationKey = /* @__PURE__ */ new Map();
|
|
5579
5747
|
NON_CJK_HYBRID_SIMILARITY = 0.45;
|
|
5580
5748
|
lastSearchModeByProject = /* @__PURE__ */ new Map();
|
|
@@ -7355,7 +7523,18 @@ async function bindObservationCodeRefsBestEffort(observation) {
|
|
|
7355
7523
|
function isVectorCompatibleWithCurrentIndex(embedding) {
|
|
7356
7524
|
if (!embedding) return false;
|
|
7357
7525
|
const vectorDimensions = getVectorDimensions();
|
|
7358
|
-
return vectorDimensions
|
|
7526
|
+
return vectorDimensions !== null && embedding.length === vectorDimensions && embedding.every(Number.isFinite);
|
|
7527
|
+
}
|
|
7528
|
+
async function upgradeVectorSchemaAfterFirstEmbedding(embedding) {
|
|
7529
|
+
if (isVectorCompatibleWithCurrentIndex(embedding)) return true;
|
|
7530
|
+
if (getVectorDimensions() !== null || !embedding.every(Number.isFinite)) return false;
|
|
7531
|
+
const targetProjectDir = projectDir;
|
|
7532
|
+
if (!vectorSchemaUpgradePromise) {
|
|
7533
|
+
vectorSchemaUpgradePromise = reindexObservations().then(() => projectDir === targetProjectDir && isVectorCompatibleWithCurrentIndex(embedding)).catch(() => false).finally(() => {
|
|
7534
|
+
vectorSchemaUpgradePromise = null;
|
|
7535
|
+
});
|
|
7536
|
+
}
|
|
7537
|
+
return vectorSchemaUpgradePromise;
|
|
7359
7538
|
}
|
|
7360
7539
|
async function initObservations(dir) {
|
|
7361
7540
|
if (projectDir === dir) return;
|
|
@@ -7365,6 +7544,7 @@ async function initObservations(dir) {
|
|
|
7365
7544
|
nextId = await store.loadIdCounter();
|
|
7366
7545
|
projectDir = dir;
|
|
7367
7546
|
searchIndexPrepared = false;
|
|
7547
|
+
vectorSchemaUpgradePromise = null;
|
|
7368
7548
|
}
|
|
7369
7549
|
async function ensureFreshObservations() {
|
|
7370
7550
|
if (!projectDir) return false;
|
|
@@ -7544,6 +7724,9 @@ async function storeObservation(input) {
|
|
|
7544
7724
|
const searchableText = [input.title, input.narrative, ...input.facts ?? []].join(" ");
|
|
7545
7725
|
generateEmbedding(searchableText).then(async (embedding) => {
|
|
7546
7726
|
if (embedding) {
|
|
7727
|
+
if (!isVectorCompatibleWithCurrentIndex(embedding)) {
|
|
7728
|
+
await upgradeVectorSchemaAfterFirstEmbedding(embedding);
|
|
7729
|
+
}
|
|
7547
7730
|
if (!isVectorCompatibleWithCurrentIndex(embedding)) {
|
|
7548
7731
|
const vectorDimensions = getVectorDimensions();
|
|
7549
7732
|
console.error(
|
|
@@ -7654,6 +7837,13 @@ async function upsertObservation(existing, input, now) {
|
|
|
7654
7837
|
vectorMissingIds.add(obsId);
|
|
7655
7838
|
generateEmbedding(searchableText).then(async (embedding) => {
|
|
7656
7839
|
if (embedding) {
|
|
7840
|
+
if (!isVectorCompatibleWithCurrentIndex(embedding)) {
|
|
7841
|
+
await upgradeVectorSchemaAfterFirstEmbedding(embedding);
|
|
7842
|
+
}
|
|
7843
|
+
if (!isVectorCompatibleWithCurrentIndex(embedding)) {
|
|
7844
|
+
queueVectorBackfill(existing.projectId);
|
|
7845
|
+
return;
|
|
7846
|
+
}
|
|
7657
7847
|
try {
|
|
7658
7848
|
const { removeObservation: removeObs } = await Promise.resolve().then(() => (init_orama_store(), orama_store_exports));
|
|
7659
7849
|
await removeObs(makeOramaObservationId(existing.projectId, obsId));
|
|
@@ -7798,14 +7988,21 @@ async function reindexObservations() {
|
|
|
7798
7988
|
let embeddings = observations.map(() => null);
|
|
7799
7989
|
const provider2 = await getEmbeddingProvider();
|
|
7800
7990
|
const canBatchEmbedAtStartup = provider2 !== null && !provider2.name.startsWith("api-");
|
|
7991
|
+
await getDb();
|
|
7992
|
+
const texts = observations.map(
|
|
7993
|
+
(obs) => [obs.title, obs.narrative, ...obs.facts].join(" ")
|
|
7994
|
+
);
|
|
7995
|
+
if (provider2?.getCachedEmbeddings) {
|
|
7996
|
+
try {
|
|
7997
|
+
embeddings = await provider2.getCachedEmbeddings(texts);
|
|
7998
|
+
} catch {
|
|
7999
|
+
}
|
|
8000
|
+
}
|
|
7801
8001
|
if (provider2 && !canBatchEmbedAtStartup) {
|
|
7802
|
-
console.error("[memorix] Startup reindex:
|
|
8002
|
+
console.error("[memorix] Startup reindex: restored cached API embeddings; uncached vectors stay in background recovery");
|
|
7803
8003
|
}
|
|
7804
8004
|
if (canBatchEmbedAtStartup) {
|
|
7805
8005
|
try {
|
|
7806
|
-
const texts = observations.map(
|
|
7807
|
-
(obs) => [obs.title, obs.narrative, ...obs.facts].join(" ")
|
|
7808
|
-
);
|
|
7809
8006
|
embeddings = await batchGenerateEmbeddings(texts);
|
|
7810
8007
|
} catch {
|
|
7811
8008
|
}
|
|
@@ -7858,16 +8055,31 @@ async function reindexObservations() {
|
|
|
7858
8055
|
}
|
|
7859
8056
|
async function prepareSearchIndex() {
|
|
7860
8057
|
if (searchIndexPrepared) return 0;
|
|
7861
|
-
const count2 = await
|
|
8058
|
+
const count2 = await hydrateIndexForStartup(observations);
|
|
7862
8059
|
if (count2 === 0) {
|
|
7863
8060
|
searchIndexPrepared = true;
|
|
7864
8061
|
return 0;
|
|
7865
8062
|
}
|
|
7866
|
-
|
|
7867
|
-
|
|
7868
|
-
|
|
7869
|
-
|
|
8063
|
+
const hydratedProjectDir = projectDir;
|
|
8064
|
+
const queueMissingVectors = () => {
|
|
8065
|
+
if (projectDir !== hydratedProjectDir) return;
|
|
8066
|
+
vectorMissingIds.clear();
|
|
8067
|
+
if (!isEmbeddingExplicitlyDisabled()) {
|
|
8068
|
+
const projectsWithMissingVectors = /* @__PURE__ */ new Set();
|
|
8069
|
+
for (const obs of observations) {
|
|
8070
|
+
if (!hasObservationVector(obs.projectId, obs.id)) {
|
|
8071
|
+
vectorMissingIds.add(obs.id);
|
|
8072
|
+
projectsWithMissingVectors.add(obs.projectId);
|
|
8073
|
+
}
|
|
8074
|
+
}
|
|
8075
|
+
for (const projectId of projectsWithMissingVectors) queueVectorBackfill(projectId);
|
|
7870
8076
|
}
|
|
8077
|
+
};
|
|
8078
|
+
const cacheHydration = getDeferredCachedVectorHydration();
|
|
8079
|
+
if (cacheHydration) {
|
|
8080
|
+
void cacheHydration.finally(queueMissingVectors);
|
|
8081
|
+
} else {
|
|
8082
|
+
queueMissingVectors();
|
|
7871
8083
|
}
|
|
7872
8084
|
searchIndexPrepared = true;
|
|
7873
8085
|
return count2;
|
|
@@ -7927,6 +8139,9 @@ async function backfillVectorEmbeddings(options = {}) {
|
|
|
7927
8139
|
try {
|
|
7928
8140
|
const embedding = await generateEmbedding(text);
|
|
7929
8141
|
if (embedding) {
|
|
8142
|
+
if (!isVectorCompatibleWithCurrentIndex(embedding)) {
|
|
8143
|
+
await upgradeVectorSchemaAfterFirstEmbedding(embedding);
|
|
8144
|
+
}
|
|
7930
8145
|
if (!isVectorCompatibleWithCurrentIndex(embedding)) {
|
|
7931
8146
|
const vectorDimensions = getVectorDimensions();
|
|
7932
8147
|
console.error(
|
|
@@ -7989,7 +8204,7 @@ async function backfillVectorEmbeddings(options = {}) {
|
|
|
7989
8204
|
}
|
|
7990
8205
|
return { attempted: ids.length, succeeded, failed };
|
|
7991
8206
|
}
|
|
7992
|
-
var observations, nextId, projectDir, searchIndexPrepared, vectorMissingIds, vectorBackfillRunning, lastVectorBackfill, embeddingFailureLogTimestamps, EMBEDDING_FAILURE_LOG_COOLDOWN_MS;
|
|
8207
|
+
var observations, nextId, projectDir, searchIndexPrepared, vectorMissingIds, vectorBackfillRunning, vectorSchemaUpgradePromise, lastVectorBackfill, embeddingFailureLogTimestamps, EMBEDDING_FAILURE_LOG_COOLDOWN_MS;
|
|
7993
8208
|
var init_observations = __esm({
|
|
7994
8209
|
"src/memory/observations.ts"() {
|
|
7995
8210
|
"use strict";
|
|
@@ -8007,6 +8222,7 @@ var init_observations = __esm({
|
|
|
8007
8222
|
searchIndexPrepared = false;
|
|
8008
8223
|
vectorMissingIds = /* @__PURE__ */ new Set();
|
|
8009
8224
|
vectorBackfillRunning = false;
|
|
8225
|
+
vectorSchemaUpgradePromise = null;
|
|
8010
8226
|
lastVectorBackfill = null;
|
|
8011
8227
|
embeddingFailureLogTimestamps = /* @__PURE__ */ new Map();
|
|
8012
8228
|
EMBEDDING_FAILURE_LOG_COOLDOWN_MS = 3e4;
|
|
@@ -16430,6 +16646,7 @@ __export(installers_exports, {
|
|
|
16430
16646
|
getProjectConfigPath: () => getProjectConfigPath,
|
|
16431
16647
|
installAgentGuidance: () => installAgentGuidance,
|
|
16432
16648
|
installHooks: () => installHooks,
|
|
16649
|
+
resolveOpenCodeHookCommand: () => resolveOpenCodeHookCommand,
|
|
16433
16650
|
uninstallHooks: () => uninstallHooks
|
|
16434
16651
|
});
|
|
16435
16652
|
import * as fs14 from "fs/promises";
|
|
@@ -16442,6 +16659,22 @@ function resolveHookCommand() {
|
|
|
16442
16659
|
}
|
|
16443
16660
|
return "memorix";
|
|
16444
16661
|
}
|
|
16662
|
+
function resolveWindowsMemorixShim() {
|
|
16663
|
+
try {
|
|
16664
|
+
const output = String(execSync3("where.exe memorix.cmd", {
|
|
16665
|
+
encoding: "utf-8",
|
|
16666
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
16667
|
+
windowsHide: true
|
|
16668
|
+
}));
|
|
16669
|
+
return output.split(/\r?\n/).map((candidate) => candidate.trim()).find((candidate) => path19.isAbsolute(candidate)) ?? null;
|
|
16670
|
+
} catch {
|
|
16671
|
+
return null;
|
|
16672
|
+
}
|
|
16673
|
+
}
|
|
16674
|
+
function resolveOpenCodeHookCommand(platform = process.platform, resolveWindowsShim = resolveWindowsMemorixShim) {
|
|
16675
|
+
if (platform !== "win32") return "memorix";
|
|
16676
|
+
return resolveWindowsShim() ?? "memorix.cmd";
|
|
16677
|
+
}
|
|
16445
16678
|
function generateClaudeConfig() {
|
|
16446
16679
|
const cmd = `${resolveHookCommand()} hook`;
|
|
16447
16680
|
const hookEntry = {
|
|
@@ -16641,6 +16874,7 @@ async function installOfficialSkillsForAgent(agent, projectRoot, global = false)
|
|
|
16641
16874
|
return skillPaths;
|
|
16642
16875
|
}
|
|
16643
16876
|
function generateOpenCodePlugin() {
|
|
16877
|
+
const hookCommand = JSON.stringify(resolveOpenCodeHookCommand());
|
|
16644
16878
|
return `/**
|
|
16645
16879
|
* Memorix - Cross-Agent Memory Bridge Plugin for OpenCode
|
|
16646
16880
|
* @generated-version ${OPENCODE_PLUGIN_VERSION}
|
|
@@ -16659,6 +16893,16 @@ export const MemorixPlugin = async ({ project, client, $, directory, worktree })
|
|
|
16659
16893
|
const sessionId = \`opencode-\${Date.now().toString(36)}-\${Math.random().toString(36).slice(2, 8)}\`;
|
|
16660
16894
|
let pendingAssistantResponse = null;
|
|
16661
16895
|
let lastDeliveredAssistantKey = '';
|
|
16896
|
+
let hookFailureReported = false;
|
|
16897
|
+
const hookCommand = ${hookCommand};
|
|
16898
|
+
|
|
16899
|
+
function reportHookFailure(eventName, detail) {
|
|
16900
|
+
// OpenCode renders console.error output inside the conversation. Delivery is
|
|
16901
|
+
// best-effort, so keep normal sessions quiet while retaining opt-in diagnostics.
|
|
16902
|
+
if (process.env.MEMORIX_HOOK_DEBUG !== '1' || hookFailureReported) return;
|
|
16903
|
+
hookFailureReported = true;
|
|
16904
|
+
console.error('[memorix-plugin] hook delivery failed:', eventName, detail);
|
|
16905
|
+
}
|
|
16662
16906
|
|
|
16663
16907
|
/**
|
|
16664
16908
|
* Send event JSON to \`memorix hook\` via child_process.spawnSync.
|
|
@@ -16674,8 +16918,7 @@ export const MemorixPlugin = async ({ project, client, $, directory, worktree })
|
|
|
16674
16918
|
const data = JSON.stringify(payload);
|
|
16675
16919
|
const eventName = payload.hook_event_name || 'unknown';
|
|
16676
16920
|
try {
|
|
16677
|
-
const
|
|
16678
|
-
const result = spawnSync(cmd, ['hook'], {
|
|
16921
|
+
const result = spawnSync(hookCommand, ['hook'], {
|
|
16679
16922
|
input: data,
|
|
16680
16923
|
timeout: 10_000,
|
|
16681
16924
|
encoding: 'utf-8',
|
|
@@ -16683,12 +16926,14 @@ export const MemorixPlugin = async ({ project, client, $, directory, worktree })
|
|
|
16683
16926
|
shell: process.platform === 'win32',
|
|
16684
16927
|
});
|
|
16685
16928
|
if (result.status !== 0) {
|
|
16686
|
-
|
|
16687
|
-
|
|
16688
|
-
|
|
16929
|
+
reportHookFailure(eventName, {
|
|
16930
|
+
exit: result.status,
|
|
16931
|
+
stderr: (result.stderr || '').slice(0, 200),
|
|
16932
|
+
error: result.error?.message,
|
|
16933
|
+
});
|
|
16689
16934
|
}
|
|
16690
16935
|
} catch (e) {
|
|
16691
|
-
|
|
16936
|
+
reportHookFailure(eventName, e?.message ?? e);
|
|
16692
16937
|
}
|
|
16693
16938
|
}
|
|
16694
16939
|
|
|
@@ -17515,7 +17760,7 @@ var init_installers = __esm({
|
|
|
17515
17760
|
"use strict";
|
|
17516
17761
|
init_esm_shims();
|
|
17517
17762
|
init_official_skills();
|
|
17518
|
-
OPENCODE_PLUGIN_VERSION =
|
|
17763
|
+
OPENCODE_PLUGIN_VERSION = 7;
|
|
17519
17764
|
AGENT_SKILL_DIRS = {
|
|
17520
17765
|
cursor: { project: path19.join(".cursor", "skills"), global: path19.join(".cursor", "skills") },
|
|
17521
17766
|
windsurf: { project: path19.join(".windsurf", "skills"), global: path19.join(".windsurf", "skills") },
|
|
@@ -21639,7 +21884,7 @@ The path should point to a directory containing a .git folder.`
|
|
|
21639
21884
|
};
|
|
21640
21885
|
const server = existingServer ?? new McpServer({
|
|
21641
21886
|
name: "memorix",
|
|
21642
|
-
version: true ? "1.1.
|
|
21887
|
+
version: true ? "1.1.13" : "1.0.1"
|
|
21643
21888
|
});
|
|
21644
21889
|
const originalRegisterTool = server.registerTool.bind(server);
|
|
21645
21890
|
server.registerTool = ((name, ...args) => {
|