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
|
@@ -2,6 +2,31 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [Unreleased]
|
|
6
|
+
|
|
7
|
+
## [1.1.13] - 2026-07-17
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- **Codex installation proof** -- `memorix doctor agents --agent codex --scope global` now checks the local plugin bundle, Personal marketplace entry, five declared and trusted lifecycle hooks, and the installed/enabled state reported by `codex plugin list`.
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- **Codex plugin version drift** -- Global setup now stamps the copied Codex plugin manifest with the installed Memorix version, so Codex no longer reports the old template version after an upgrade. The source template is covered by a release regression test as well.
|
|
14
|
+
- **Workspace package publish trap** -- `@memorix/ai`, `@memorix/agent-core`, `@memorix/tui`, and `@memorix/memcode` are now explicitly internal workspaces. The root `memorix` package ships their bundled runtime, and the release workflow publishes only that supported public package.
|
|
15
|
+
|
|
16
|
+
## [1.1.12] - 2026-07-17
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
- **Codex lifecycle hook capture** -- The Memorix Codex plugin now bundles the documented `SessionStart`, `UserPromptSubmit`, `PostToolUse`, `PreCompact`, and `Stop` hook events, with a Windows-safe `commandWindows` command override.
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
- **Codex hook normalization and injection** -- Codex lifecycle payloads now preserve their event names, `SessionStart` returns Codex-native `hookSpecificOutput.additionalContext`, and capture-only events stay quiet instead of filling the agent context with save-status messages.
|
|
23
|
+
- **OpenCode hooks on Windows** -- The generated plugin resolves the installed npm `memorix.cmd` shim while setup runs and uses that stable path inside OpenCode, instead of relying on OpenCode's inherited `PATH`. Hook delivery stays quiet unless `MEMORIX_HOOK_DEBUG=1` is explicitly set. Fixes #125.
|
|
24
|
+
- **Setup scope isolation** -- Project-level setup no longer installs Claude, Codex, or Copilot plugin bundles in the user home. Use `--global` for user-level plugins, skills, and lifecycle hooks.
|
|
25
|
+
- **TUI embedding startup** -- TUI health, chat, and quick-search paths now use the same lexical-ready startup flow as MCP instead of making a remote dimension probe during the first screen or search.
|
|
26
|
+
- **Semantic recall after restart** -- API-backed embeddings now restore compatible vectors from the local cache during index hydration, without placing remote embedding calls on the MCP startup path. Redundant namespace-scoped cache metadata survives a lost dimensions cache; cache misses enter the existing bounded background recovery lane, and the first successful vector upgrades a cache-less lexical index in the same process. Based on the diagnosis in #126 by @Tom-Ma-Ming.
|
|
27
|
+
- **Concurrent index startup** -- Cache-only startup and ordinary database callers now share one Orama initialization, so a later vector-schema attempt cannot replace an already hydrated lexical index. Resets also discard obsolete in-flight instances.
|
|
28
|
+
- **Lazy API vector-cache I/O** -- A cache-only start with no trusted dimension metadata now skips parsing the potentially large API vector cache entirely; the cache is loaded only after compatible local metadata or a normal embedding initialization establishes its dimensions.
|
|
29
|
+
|
|
5
30
|
## [1.1.11] - 2026-07-16
|
|
6
31
|
|
|
7
32
|
### Added
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@memorix/memcode",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.12",
|
|
4
|
+
"private": true,
|
|
4
5
|
"description": "Memorix-native coding agent CLI with terminal chat, project memory, hooks, and session management",
|
|
5
6
|
"type": "module",
|
|
6
7
|
"piConfig": {
|
|
@@ -34,9 +35,9 @@
|
|
|
34
35
|
"prepublishOnly": "npm run clean && npm run build"
|
|
35
36
|
},
|
|
36
37
|
"dependencies": {
|
|
37
|
-
"@memorix/agent-core": "1.1.
|
|
38
|
-
"@memorix/ai": "1.1.
|
|
39
|
-
"@memorix/tui": "1.1.
|
|
38
|
+
"@memorix/agent-core": "1.1.12",
|
|
39
|
+
"@memorix/ai": "1.1.12",
|
|
40
|
+
"@memorix/tui": "1.1.12",
|
|
40
41
|
"@opentui/core": "^0.3.4",
|
|
41
42
|
"@opentui/react": "^0.3.4",
|
|
42
43
|
"@silvia-odwyer/photon-node": "0.3.4",
|
package/dist/sdk.js
CHANGED
|
@@ -2813,6 +2813,45 @@ function dimsCacheKey(config) {
|
|
|
2813
2813
|
config.requestedDimensions ?? "native"
|
|
2814
2814
|
].join("|");
|
|
2815
2815
|
}
|
|
2816
|
+
function isValidDimension(value) {
|
|
2817
|
+
return typeof value === "number" && Number.isInteger(value) && value > 0;
|
|
2818
|
+
}
|
|
2819
|
+
async function loadCachedVectorDimensions(config) {
|
|
2820
|
+
try {
|
|
2821
|
+
const raw = await readFile2(CACHE_META_FILE, "utf-8");
|
|
2822
|
+
const data = JSON.parse(raw);
|
|
2823
|
+
const namespace = cacheNamespace(config);
|
|
2824
|
+
if (!Array.isArray(data?.entries)) return null;
|
|
2825
|
+
const entry = data.entries.find(
|
|
2826
|
+
(candidate) => typeof candidate === "object" && candidate !== null && candidate.namespace === namespace && isValidDimension(candidate.dimensions)
|
|
2827
|
+
);
|
|
2828
|
+
return entry?.dimensions ?? null;
|
|
2829
|
+
} catch {
|
|
2830
|
+
return null;
|
|
2831
|
+
}
|
|
2832
|
+
}
|
|
2833
|
+
async function saveCachedVectorDimensions(config, dimensions) {
|
|
2834
|
+
if (!isValidDimension(dimensions)) return;
|
|
2835
|
+
try {
|
|
2836
|
+
await mkdir2(CACHE_DIR2, { recursive: true });
|
|
2837
|
+
let entries = [];
|
|
2838
|
+
try {
|
|
2839
|
+
const raw = await readFile2(CACHE_META_FILE, "utf-8");
|
|
2840
|
+
const data = JSON.parse(raw);
|
|
2841
|
+
if (Array.isArray(data?.entries)) {
|
|
2842
|
+
entries = data.entries.filter(
|
|
2843
|
+
(candidate) => typeof candidate === "object" && candidate !== null && typeof candidate.namespace === "string" && isValidDimension(candidate.dimensions) && typeof candidate.ts === "number"
|
|
2844
|
+
);
|
|
2845
|
+
}
|
|
2846
|
+
} catch {
|
|
2847
|
+
}
|
|
2848
|
+
const namespace = cacheNamespace(config);
|
|
2849
|
+
entries = entries.filter((entry) => entry.namespace !== namespace);
|
|
2850
|
+
entries.push({ namespace, dimensions, ts: Date.now() });
|
|
2851
|
+
await writeFile2(CACHE_META_FILE, JSON.stringify({ version: 1, entries }));
|
|
2852
|
+
} catch {
|
|
2853
|
+
}
|
|
2854
|
+
}
|
|
2816
2855
|
async function loadCachedDims(config) {
|
|
2817
2856
|
try {
|
|
2818
2857
|
const raw = await readFile2(DIMS_CACHE_FILE, "utf-8");
|
|
@@ -2875,6 +2914,7 @@ async function saveCachedDims(config, dimensions) {
|
|
|
2875
2914
|
await writeFile2(DIMS_CACHE_FILE, JSON.stringify({ entries }));
|
|
2876
2915
|
} catch {
|
|
2877
2916
|
}
|
|
2917
|
+
await saveCachedVectorDimensions(config, dimensions);
|
|
2878
2918
|
}
|
|
2879
2919
|
async function saveDiskCacheNow() {
|
|
2880
2920
|
if (!diskCacheDirty2) return;
|
|
@@ -2957,7 +2997,7 @@ async function fetchWithRetry(url, apiKey, body, attempt = 0) {
|
|
|
2957
2997
|
const errorText2 = await response.text().catch(() => "unknown error");
|
|
2958
2998
|
throw new Error(`Embedding API error (${response.status}): ${errorText2}`);
|
|
2959
2999
|
}
|
|
2960
|
-
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;
|
|
3000
|
+
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;
|
|
2961
3001
|
var init_api_provider = __esm({
|
|
2962
3002
|
"src/embedding/api-provider.ts"() {
|
|
2963
3003
|
"use strict";
|
|
@@ -2965,6 +3005,7 @@ var init_api_provider = __esm({
|
|
|
2965
3005
|
CACHE_DIR2 = process.env.MEMORIX_DATA_DIR || join3(homedir7(), ".memorix", "data");
|
|
2966
3006
|
CACHE_FILE2 = join3(CACHE_DIR2, ".embedding-api-cache.json");
|
|
2967
3007
|
DIMS_CACHE_FILE = join3(CACHE_DIR2, ".embedding-dims-cache.json");
|
|
3008
|
+
CACHE_META_FILE = join3(CACHE_DIR2, ".embedding-api-cache-meta.json");
|
|
2968
3009
|
cache3 = /* @__PURE__ */ new Map();
|
|
2969
3010
|
MAX_CACHE_SIZE3 = 1e4;
|
|
2970
3011
|
diskCacheDirty2 = false;
|
|
@@ -2991,13 +3032,22 @@ var init_api_provider = __esm({
|
|
|
2991
3032
|
this.dimensions = detectedDimensions;
|
|
2992
3033
|
this.name = `api-${config.model.replace(/\//g, "-")}`;
|
|
2993
3034
|
}
|
|
2994
|
-
static async create() {
|
|
3035
|
+
static async create(options = {}) {
|
|
2995
3036
|
const config = _APIEmbeddingProvider.resolveConfig();
|
|
2996
|
-
|
|
3037
|
+
const allowNetworkProbe = options.allowNetworkProbe !== false;
|
|
2997
3038
|
let probeDimensions = await loadCachedDims(config);
|
|
3039
|
+
let dimensionSource = "dims-cache";
|
|
3040
|
+
if (probeDimensions === null) {
|
|
3041
|
+
probeDimensions = await loadCachedVectorDimensions(config);
|
|
3042
|
+
if (probeDimensions !== null) dimensionSource = "vector-cache";
|
|
3043
|
+
}
|
|
2998
3044
|
if (probeDimensions !== null) {
|
|
2999
|
-
console.error(`[memorix] API embedding: ${config.model} @ ${config.baseUrl} (${probeDimensions}d) [
|
|
3045
|
+
console.error(`[memorix] API embedding: ${config.model} @ ${config.baseUrl} (${probeDimensions}d) [${dimensionSource}]`);
|
|
3046
|
+
if (dimensionSource === "dims-cache") {
|
|
3047
|
+
void saveCachedVectorDimensions(config, probeDimensions);
|
|
3048
|
+
}
|
|
3000
3049
|
} else {
|
|
3050
|
+
if (!allowNetworkProbe) return null;
|
|
3001
3051
|
probeDimensions = await _APIEmbeddingProvider.probeAPI(config);
|
|
3002
3052
|
console.error(`[memorix] API embedding: ${config.model} @ ${config.baseUrl} (${probeDimensions}d)`);
|
|
3003
3053
|
saveCachedDims(config, probeDimensions).catch(() => {
|
|
@@ -3006,6 +3056,7 @@ var init_api_provider = __esm({
|
|
|
3006
3056
|
if (config.requestedDimensions) {
|
|
3007
3057
|
console.error(`[memorix] Dimension shortening: ${config.requestedDimensions}d requested`);
|
|
3008
3058
|
}
|
|
3059
|
+
startDiskCacheLoad();
|
|
3009
3060
|
return new _APIEmbeddingProvider(config, probeDimensions);
|
|
3010
3061
|
}
|
|
3011
3062
|
static resolveConfig() {
|
|
@@ -3184,6 +3235,13 @@ var init_api_provider = __esm({
|
|
|
3184
3235
|
scheduleDiskSave();
|
|
3185
3236
|
return results;
|
|
3186
3237
|
}
|
|
3238
|
+
async getCachedEmbeddings(texts) {
|
|
3239
|
+
await ensureDiskCacheLoaded();
|
|
3240
|
+
return texts.map((text) => {
|
|
3241
|
+
const hash = textHash2(normalizeText(text), this.cacheKeyNamespace);
|
|
3242
|
+
return cache3.get(hash) ?? null;
|
|
3243
|
+
});
|
|
3244
|
+
}
|
|
3187
3245
|
getStats() {
|
|
3188
3246
|
return {
|
|
3189
3247
|
totalTokens: this.totalTokensUsed,
|
|
@@ -3292,10 +3350,10 @@ async function createTransformersProvider() {
|
|
|
3292
3350
|
return null;
|
|
3293
3351
|
}
|
|
3294
3352
|
}
|
|
3295
|
-
async function createAPIProvider() {
|
|
3353
|
+
async function createAPIProvider(options) {
|
|
3296
3354
|
try {
|
|
3297
3355
|
const { APIEmbeddingProvider: APIEmbeddingProvider2 } = await Promise.resolve().then(() => (init_api_provider(), api_provider_exports));
|
|
3298
|
-
return await APIEmbeddingProvider2.create();
|
|
3356
|
+
return await APIEmbeddingProvider2.create(options ?? {});
|
|
3299
3357
|
} catch (e) {
|
|
3300
3358
|
warnOnce(`[memorix] Failed to init API embedding: ${e instanceof Error ? e.message : e}`);
|
|
3301
3359
|
return null;
|
|
@@ -3350,11 +3408,24 @@ function wrapProvider(candidate) {
|
|
|
3350
3408
|
}
|
|
3351
3409
|
throw error;
|
|
3352
3410
|
}
|
|
3353
|
-
}
|
|
3411
|
+
},
|
|
3412
|
+
...candidate.getCachedEmbeddings ? {
|
|
3413
|
+
async getCachedEmbeddings(texts) {
|
|
3414
|
+
return candidate.getCachedEmbeddings(texts);
|
|
3415
|
+
}
|
|
3416
|
+
} : {}
|
|
3354
3417
|
};
|
|
3355
3418
|
}
|
|
3356
|
-
async function getEmbeddingProvider() {
|
|
3419
|
+
async function getEmbeddingProvider(options = {}) {
|
|
3357
3420
|
if (provider) return provider;
|
|
3421
|
+
if (options.allowNetworkProbe === false) {
|
|
3422
|
+
const mode = getEmbeddingMode2();
|
|
3423
|
+
if (mode !== "api" && !(mode === "auto" && hasAPIEmbeddingConfig())) {
|
|
3424
|
+
return null;
|
|
3425
|
+
}
|
|
3426
|
+
const cached = await createAPIProvider({ allowNetworkProbe: false });
|
|
3427
|
+
return cached ? wrapProvider(cached) : null;
|
|
3428
|
+
}
|
|
3358
3429
|
if (lastInitWasTemporaryFailure) {
|
|
3359
3430
|
const elapsed = Date.now() - lastFailureTimestamp;
|
|
3360
3431
|
if (elapsed < RETRY_COOLDOWN_MS) {
|
|
@@ -4867,12 +4938,15 @@ __export(orama_store_exports, {
|
|
|
4867
4938
|
batchGenerateEmbeddings: () => batchGenerateEmbeddings,
|
|
4868
4939
|
generateEmbedding: () => generateEmbedding,
|
|
4869
4940
|
getDb: () => getDb,
|
|
4941
|
+
getDeferredCachedVectorHydration: () => getDeferredCachedVectorHydration,
|
|
4870
4942
|
getLastSearchMode: () => getLastSearchMode,
|
|
4871
4943
|
getObservationCount: () => getObservationCount,
|
|
4872
4944
|
getObservationsByIds: () => getObservationsByIds,
|
|
4873
4945
|
getTimeline: () => getTimeline,
|
|
4874
4946
|
getVectorDimensions: () => getVectorDimensions,
|
|
4947
|
+
hasObservationVector: () => hasObservationVector,
|
|
4875
4948
|
hydrateIndex: () => hydrateIndex,
|
|
4949
|
+
hydrateIndexForStartup: () => hydrateIndexForStartup,
|
|
4876
4950
|
insertObservation: () => insertObservation,
|
|
4877
4951
|
isEmbeddingEnabled: () => isEmbeddingEnabled,
|
|
4878
4952
|
makeOramaObservationId: () => makeOramaObservationId,
|
|
@@ -4933,11 +5007,10 @@ function stripVectorSearchParams(params) {
|
|
|
4933
5007
|
const { mode, vector, similarity, hybridWeights, ...rest } = params;
|
|
4934
5008
|
return rest;
|
|
4935
5009
|
}
|
|
4936
|
-
async function
|
|
4937
|
-
|
|
4938
|
-
const
|
|
4939
|
-
|
|
4940
|
-
embeddingDimensions = provider2?.dimensions ?? null;
|
|
5010
|
+
async function initializeDb(options, generation) {
|
|
5011
|
+
const provider2 = await getEmbeddingProvider({ allowNetworkProbe: options.allowNetworkProbe });
|
|
5012
|
+
const nextEmbeddingEnabled = provider2 !== null;
|
|
5013
|
+
const nextEmbeddingDimensions = provider2?.dimensions ?? null;
|
|
4941
5014
|
const baseSchema = {
|
|
4942
5015
|
id: "string",
|
|
4943
5016
|
observationId: "number",
|
|
@@ -4960,15 +5033,39 @@ async function getDb() {
|
|
|
4960
5033
|
documentType: "string",
|
|
4961
5034
|
knowledgeLayer: "string"
|
|
4962
5035
|
};
|
|
4963
|
-
const dims =
|
|
4964
|
-
const schema =
|
|
4965
|
-
|
|
4966
|
-
|
|
5036
|
+
const dims = nextEmbeddingDimensions ?? 384;
|
|
5037
|
+
const schema = nextEmbeddingEnabled ? { ...baseSchema, embedding: `vector[${dims}]` } : baseSchema;
|
|
5038
|
+
const nextDb = await create({ schema });
|
|
5039
|
+
if (generation !== dbGeneration) {
|
|
5040
|
+
return getDb(options);
|
|
5041
|
+
}
|
|
5042
|
+
indexEmbeddingProvider = provider2;
|
|
5043
|
+
embeddingEnabled = nextEmbeddingEnabled;
|
|
5044
|
+
embeddingDimensions = nextEmbeddingDimensions;
|
|
5045
|
+
db = nextDb;
|
|
5046
|
+
return nextDb;
|
|
5047
|
+
}
|
|
5048
|
+
async function getDb(options = {}) {
|
|
5049
|
+
if (db) return db;
|
|
5050
|
+
if (dbInitPromise) return dbInitPromise;
|
|
5051
|
+
const initPromise2 = initializeDb(options, dbGeneration);
|
|
5052
|
+
dbInitPromise = initPromise2;
|
|
5053
|
+
try {
|
|
5054
|
+
return await initPromise2;
|
|
5055
|
+
} finally {
|
|
5056
|
+
if (dbInitPromise === initPromise2) {
|
|
5057
|
+
dbInitPromise = null;
|
|
5058
|
+
}
|
|
5059
|
+
}
|
|
4967
5060
|
}
|
|
4968
5061
|
async function resetDb() {
|
|
5062
|
+
dbGeneration++;
|
|
4969
5063
|
db = null;
|
|
5064
|
+
dbInitPromise = null;
|
|
4970
5065
|
embeddingEnabled = false;
|
|
4971
5066
|
embeddingDimensions = null;
|
|
5067
|
+
indexEmbeddingProvider = null;
|
|
5068
|
+
deferredCachedVectorHydration = null;
|
|
4972
5069
|
lastSearchModeByProject.clear();
|
|
4973
5070
|
docByObservationKey.clear();
|
|
4974
5071
|
}
|
|
@@ -4996,14 +5093,64 @@ async function batchGenerateEmbeddings(texts) {
|
|
|
4996
5093
|
return texts.map(() => null);
|
|
4997
5094
|
}
|
|
4998
5095
|
}
|
|
4999
|
-
async function
|
|
5000
|
-
|
|
5096
|
+
async function getCachedEmbeddings(texts) {
|
|
5097
|
+
if (!embeddingEnabled || texts.length === 0) return texts.map(() => null);
|
|
5098
|
+
const provider2 = indexEmbeddingProvider;
|
|
5099
|
+
if (!provider2?.getCachedEmbeddings) return texts.map(() => null);
|
|
5100
|
+
try {
|
|
5101
|
+
return await provider2.getCachedEmbeddings(texts);
|
|
5102
|
+
} catch {
|
|
5103
|
+
return texts.map(() => null);
|
|
5104
|
+
}
|
|
5105
|
+
}
|
|
5106
|
+
function observationEmbeddingText(observation) {
|
|
5107
|
+
return [
|
|
5108
|
+
observation.title ?? "",
|
|
5109
|
+
observation.narrative ?? "",
|
|
5110
|
+
...Array.isArray(observation.facts) ? observation.facts : []
|
|
5111
|
+
].join(" ");
|
|
5112
|
+
}
|
|
5113
|
+
function documentEmbeddingText(document) {
|
|
5114
|
+
return [document.title ?? "", document.narrative ?? "", document.facts ?? ""].join(" ");
|
|
5115
|
+
}
|
|
5116
|
+
function isCompatibleCachedVector(vector) {
|
|
5117
|
+
return Boolean(
|
|
5118
|
+
vector && embeddingDimensions !== null && vector.length === embeddingDimensions && vector.every(Number.isFinite)
|
|
5119
|
+
);
|
|
5120
|
+
}
|
|
5121
|
+
async function attachCachedVectors(database, candidates) {
|
|
5122
|
+
const cachedVectors = await getCachedEmbeddings(
|
|
5123
|
+
candidates.map(({ observation }) => observationEmbeddingText(observation))
|
|
5124
|
+
);
|
|
5125
|
+
if (db !== database) return;
|
|
5126
|
+
for (let index = 0; index < candidates.length; index++) {
|
|
5127
|
+
const vector = cachedVectors[index];
|
|
5128
|
+
if (!isCompatibleCachedVector(vector)) continue;
|
|
5129
|
+
const existing = getByID(database, candidates[index].id);
|
|
5130
|
+
if (!existing || documentEmbeddingText(existing) !== observationEmbeddingText(candidates[index].observation)) continue;
|
|
5131
|
+
try {
|
|
5132
|
+
await update(database, candidates[index].id, { ...existing, embedding: vector });
|
|
5133
|
+
} catch {
|
|
5134
|
+
}
|
|
5135
|
+
}
|
|
5136
|
+
}
|
|
5137
|
+
async function hydrateIndex(observations2, options = {}) {
|
|
5138
|
+
const database = await getDb({ allowNetworkProbe: options.allowNetworkProbe });
|
|
5139
|
+
const candidates = [];
|
|
5140
|
+
for (const observation of observations2) {
|
|
5141
|
+
if (!observation || !observation.id || !observation.projectId) continue;
|
|
5142
|
+
const id = makeOramaObservationId(observation.projectId, observation.id);
|
|
5143
|
+
if (getByID(database, id)) continue;
|
|
5144
|
+
candidates.push({ observation, id });
|
|
5145
|
+
}
|
|
5146
|
+
const deferCachedVectors = options.deferCachedVectors === true && Boolean(indexEmbeddingProvider?.getCachedEmbeddings);
|
|
5147
|
+
const cachedVectors = deferCachedVectors ? candidates.map(() => null) : await getCachedEmbeddings(candidates.map(({ observation }) => observationEmbeddingText(observation)));
|
|
5001
5148
|
let inserted = 0;
|
|
5002
|
-
for (
|
|
5003
|
-
|
|
5149
|
+
for (let index = 0; index < candidates.length; index++) {
|
|
5150
|
+
const { observation: obs, id } = candidates[index];
|
|
5004
5151
|
try {
|
|
5005
|
-
const
|
|
5006
|
-
|
|
5152
|
+
const vector = cachedVectors[index];
|
|
5153
|
+
const compatibleVector = isCompatibleCachedVector(vector) ? vector : null;
|
|
5007
5154
|
const doc = {
|
|
5008
5155
|
id,
|
|
5009
5156
|
observationId: obs.id,
|
|
@@ -5022,7 +5169,8 @@ async function hydrateIndex(observations2) {
|
|
|
5022
5169
|
status: obs.status ?? "active",
|
|
5023
5170
|
source: obs.source || "agent",
|
|
5024
5171
|
documentType: "observation",
|
|
5025
|
-
knowledgeLayer: resolveKnowledgeLayer("observation", obs.sourceDetail, obs.source)
|
|
5172
|
+
knowledgeLayer: resolveKnowledgeLayer("observation", obs.sourceDetail, obs.source),
|
|
5173
|
+
...compatibleVector ? { embedding: compatibleVector } : {}
|
|
5026
5174
|
};
|
|
5027
5175
|
await insert2(database, doc);
|
|
5028
5176
|
rememberObservationDoc(doc);
|
|
@@ -5030,8 +5178,24 @@ async function hydrateIndex(observations2) {
|
|
|
5030
5178
|
} catch {
|
|
5031
5179
|
}
|
|
5032
5180
|
}
|
|
5181
|
+
deferredCachedVectorHydration = deferCachedVectors ? attachCachedVectors(database, candidates).catch(() => {
|
|
5182
|
+
}) : null;
|
|
5033
5183
|
return inserted;
|
|
5034
5184
|
}
|
|
5185
|
+
async function hydrateIndexForStartup(observations2) {
|
|
5186
|
+
return hydrateIndex(observations2, {
|
|
5187
|
+
allowNetworkProbe: false,
|
|
5188
|
+
deferCachedVectors: true
|
|
5189
|
+
});
|
|
5190
|
+
}
|
|
5191
|
+
function getDeferredCachedVectorHydration() {
|
|
5192
|
+
return deferredCachedVectorHydration;
|
|
5193
|
+
}
|
|
5194
|
+
function hasObservationVector(projectId, observationId) {
|
|
5195
|
+
if (!db || !embeddingEnabled || embeddingDimensions === null) return false;
|
|
5196
|
+
const document = getByID(db, makeOramaObservationId(projectId, observationId));
|
|
5197
|
+
return Array.isArray(document?.embedding) && document.embedding.length === embeddingDimensions && document.embedding.every(Number.isFinite);
|
|
5198
|
+
}
|
|
5035
5199
|
async function insertObservation(doc) {
|
|
5036
5200
|
const database = await getDb();
|
|
5037
5201
|
await insert2(database, doc);
|
|
@@ -5565,7 +5729,7 @@ function formatTime(isoDate) {
|
|
|
5565
5729
|
return isoDate;
|
|
5566
5730
|
}
|
|
5567
5731
|
}
|
|
5568
|
-
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;
|
|
5732
|
+
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;
|
|
5569
5733
|
var init_orama_store = __esm({
|
|
5570
5734
|
"src/store/orama-store.ts"() {
|
|
5571
5735
|
"use strict";
|
|
@@ -5577,8 +5741,12 @@ var init_orama_store = __esm({
|
|
|
5577
5741
|
init_intent_detector();
|
|
5578
5742
|
init_query_expansion();
|
|
5579
5743
|
db = null;
|
|
5744
|
+
dbInitPromise = null;
|
|
5745
|
+
dbGeneration = 0;
|
|
5580
5746
|
embeddingEnabled = false;
|
|
5581
5747
|
embeddingDimensions = null;
|
|
5748
|
+
indexEmbeddingProvider = null;
|
|
5749
|
+
deferredCachedVectorHydration = null;
|
|
5582
5750
|
docByObservationKey = /* @__PURE__ */ new Map();
|
|
5583
5751
|
NON_CJK_HYBRID_SIMILARITY = 0.45;
|
|
5584
5752
|
lastSearchModeByProject = /* @__PURE__ */ new Map();
|
|
@@ -7359,7 +7527,18 @@ async function bindObservationCodeRefsBestEffort(observation) {
|
|
|
7359
7527
|
function isVectorCompatibleWithCurrentIndex(embedding) {
|
|
7360
7528
|
if (!embedding) return false;
|
|
7361
7529
|
const vectorDimensions = getVectorDimensions();
|
|
7362
|
-
return vectorDimensions
|
|
7530
|
+
return vectorDimensions !== null && embedding.length === vectorDimensions && embedding.every(Number.isFinite);
|
|
7531
|
+
}
|
|
7532
|
+
async function upgradeVectorSchemaAfterFirstEmbedding(embedding) {
|
|
7533
|
+
if (isVectorCompatibleWithCurrentIndex(embedding)) return true;
|
|
7534
|
+
if (getVectorDimensions() !== null || !embedding.every(Number.isFinite)) return false;
|
|
7535
|
+
const targetProjectDir = projectDir;
|
|
7536
|
+
if (!vectorSchemaUpgradePromise) {
|
|
7537
|
+
vectorSchemaUpgradePromise = reindexObservations().then(() => projectDir === targetProjectDir && isVectorCompatibleWithCurrentIndex(embedding)).catch(() => false).finally(() => {
|
|
7538
|
+
vectorSchemaUpgradePromise = null;
|
|
7539
|
+
});
|
|
7540
|
+
}
|
|
7541
|
+
return vectorSchemaUpgradePromise;
|
|
7363
7542
|
}
|
|
7364
7543
|
async function initObservations(dir) {
|
|
7365
7544
|
if (projectDir === dir) return;
|
|
@@ -7369,6 +7548,7 @@ async function initObservations(dir) {
|
|
|
7369
7548
|
nextId = await store.loadIdCounter();
|
|
7370
7549
|
projectDir = dir;
|
|
7371
7550
|
searchIndexPrepared = false;
|
|
7551
|
+
vectorSchemaUpgradePromise = null;
|
|
7372
7552
|
}
|
|
7373
7553
|
async function ensureFreshObservations() {
|
|
7374
7554
|
if (!projectDir) return false;
|
|
@@ -7548,6 +7728,9 @@ async function storeObservation(input) {
|
|
|
7548
7728
|
const searchableText = [input.title, input.narrative, ...input.facts ?? []].join(" ");
|
|
7549
7729
|
generateEmbedding(searchableText).then(async (embedding) => {
|
|
7550
7730
|
if (embedding) {
|
|
7731
|
+
if (!isVectorCompatibleWithCurrentIndex(embedding)) {
|
|
7732
|
+
await upgradeVectorSchemaAfterFirstEmbedding(embedding);
|
|
7733
|
+
}
|
|
7551
7734
|
if (!isVectorCompatibleWithCurrentIndex(embedding)) {
|
|
7552
7735
|
const vectorDimensions = getVectorDimensions();
|
|
7553
7736
|
console.error(
|
|
@@ -7658,6 +7841,13 @@ async function upsertObservation(existing, input, now) {
|
|
|
7658
7841
|
vectorMissingIds.add(obsId);
|
|
7659
7842
|
generateEmbedding(searchableText).then(async (embedding) => {
|
|
7660
7843
|
if (embedding) {
|
|
7844
|
+
if (!isVectorCompatibleWithCurrentIndex(embedding)) {
|
|
7845
|
+
await upgradeVectorSchemaAfterFirstEmbedding(embedding);
|
|
7846
|
+
}
|
|
7847
|
+
if (!isVectorCompatibleWithCurrentIndex(embedding)) {
|
|
7848
|
+
queueVectorBackfill(existing.projectId);
|
|
7849
|
+
return;
|
|
7850
|
+
}
|
|
7661
7851
|
try {
|
|
7662
7852
|
const { removeObservation: removeObs } = await Promise.resolve().then(() => (init_orama_store(), orama_store_exports));
|
|
7663
7853
|
await removeObs(makeOramaObservationId(existing.projectId, obsId));
|
|
@@ -7802,14 +7992,21 @@ async function reindexObservations() {
|
|
|
7802
7992
|
let embeddings = observations.map(() => null);
|
|
7803
7993
|
const provider2 = await getEmbeddingProvider();
|
|
7804
7994
|
const canBatchEmbedAtStartup = provider2 !== null && !provider2.name.startsWith("api-");
|
|
7995
|
+
await getDb();
|
|
7996
|
+
const texts = observations.map(
|
|
7997
|
+
(obs) => [obs.title, obs.narrative, ...obs.facts].join(" ")
|
|
7998
|
+
);
|
|
7999
|
+
if (provider2?.getCachedEmbeddings) {
|
|
8000
|
+
try {
|
|
8001
|
+
embeddings = await provider2.getCachedEmbeddings(texts);
|
|
8002
|
+
} catch {
|
|
8003
|
+
}
|
|
8004
|
+
}
|
|
7805
8005
|
if (provider2 && !canBatchEmbedAtStartup) {
|
|
7806
|
-
console.error("[memorix] Startup reindex:
|
|
8006
|
+
console.error("[memorix] Startup reindex: restored cached API embeddings; uncached vectors stay in background recovery");
|
|
7807
8007
|
}
|
|
7808
8008
|
if (canBatchEmbedAtStartup) {
|
|
7809
8009
|
try {
|
|
7810
|
-
const texts = observations.map(
|
|
7811
|
-
(obs) => [obs.title, obs.narrative, ...obs.facts].join(" ")
|
|
7812
|
-
);
|
|
7813
8010
|
embeddings = await batchGenerateEmbeddings(texts);
|
|
7814
8011
|
} catch {
|
|
7815
8012
|
}
|
|
@@ -7862,16 +8059,31 @@ async function reindexObservations() {
|
|
|
7862
8059
|
}
|
|
7863
8060
|
async function prepareSearchIndex() {
|
|
7864
8061
|
if (searchIndexPrepared) return 0;
|
|
7865
|
-
const count2 = await
|
|
8062
|
+
const count2 = await hydrateIndexForStartup(observations);
|
|
7866
8063
|
if (count2 === 0) {
|
|
7867
8064
|
searchIndexPrepared = true;
|
|
7868
8065
|
return 0;
|
|
7869
8066
|
}
|
|
7870
|
-
|
|
7871
|
-
|
|
7872
|
-
|
|
7873
|
-
|
|
8067
|
+
const hydratedProjectDir = projectDir;
|
|
8068
|
+
const queueMissingVectors = () => {
|
|
8069
|
+
if (projectDir !== hydratedProjectDir) return;
|
|
8070
|
+
vectorMissingIds.clear();
|
|
8071
|
+
if (!isEmbeddingExplicitlyDisabled()) {
|
|
8072
|
+
const projectsWithMissingVectors = /* @__PURE__ */ new Set();
|
|
8073
|
+
for (const obs of observations) {
|
|
8074
|
+
if (!hasObservationVector(obs.projectId, obs.id)) {
|
|
8075
|
+
vectorMissingIds.add(obs.id);
|
|
8076
|
+
projectsWithMissingVectors.add(obs.projectId);
|
|
8077
|
+
}
|
|
8078
|
+
}
|
|
8079
|
+
for (const projectId of projectsWithMissingVectors) queueVectorBackfill(projectId);
|
|
7874
8080
|
}
|
|
8081
|
+
};
|
|
8082
|
+
const cacheHydration = getDeferredCachedVectorHydration();
|
|
8083
|
+
if (cacheHydration) {
|
|
8084
|
+
void cacheHydration.finally(queueMissingVectors);
|
|
8085
|
+
} else {
|
|
8086
|
+
queueMissingVectors();
|
|
7875
8087
|
}
|
|
7876
8088
|
searchIndexPrepared = true;
|
|
7877
8089
|
return count2;
|
|
@@ -7931,6 +8143,9 @@ async function backfillVectorEmbeddings(options = {}) {
|
|
|
7931
8143
|
try {
|
|
7932
8144
|
const embedding = await generateEmbedding(text);
|
|
7933
8145
|
if (embedding) {
|
|
8146
|
+
if (!isVectorCompatibleWithCurrentIndex(embedding)) {
|
|
8147
|
+
await upgradeVectorSchemaAfterFirstEmbedding(embedding);
|
|
8148
|
+
}
|
|
7934
8149
|
if (!isVectorCompatibleWithCurrentIndex(embedding)) {
|
|
7935
8150
|
const vectorDimensions = getVectorDimensions();
|
|
7936
8151
|
console.error(
|
|
@@ -7993,7 +8208,7 @@ async function backfillVectorEmbeddings(options = {}) {
|
|
|
7993
8208
|
}
|
|
7994
8209
|
return { attempted: ids.length, succeeded, failed };
|
|
7995
8210
|
}
|
|
7996
|
-
var observations, nextId, projectDir, searchIndexPrepared, vectorMissingIds, vectorBackfillRunning, lastVectorBackfill, embeddingFailureLogTimestamps, EMBEDDING_FAILURE_LOG_COOLDOWN_MS;
|
|
8211
|
+
var observations, nextId, projectDir, searchIndexPrepared, vectorMissingIds, vectorBackfillRunning, vectorSchemaUpgradePromise, lastVectorBackfill, embeddingFailureLogTimestamps, EMBEDDING_FAILURE_LOG_COOLDOWN_MS;
|
|
7997
8212
|
var init_observations = __esm({
|
|
7998
8213
|
"src/memory/observations.ts"() {
|
|
7999
8214
|
"use strict";
|
|
@@ -8011,6 +8226,7 @@ var init_observations = __esm({
|
|
|
8011
8226
|
searchIndexPrepared = false;
|
|
8012
8227
|
vectorMissingIds = /* @__PURE__ */ new Set();
|
|
8013
8228
|
vectorBackfillRunning = false;
|
|
8229
|
+
vectorSchemaUpgradePromise = null;
|
|
8014
8230
|
lastVectorBackfill = null;
|
|
8015
8231
|
embeddingFailureLogTimestamps = /* @__PURE__ */ new Map();
|
|
8016
8232
|
EMBEDDING_FAILURE_LOG_COOLDOWN_MS = 3e4;
|
|
@@ -16434,6 +16650,7 @@ __export(installers_exports, {
|
|
|
16434
16650
|
getProjectConfigPath: () => getProjectConfigPath,
|
|
16435
16651
|
installAgentGuidance: () => installAgentGuidance,
|
|
16436
16652
|
installHooks: () => installHooks,
|
|
16653
|
+
resolveOpenCodeHookCommand: () => resolveOpenCodeHookCommand,
|
|
16437
16654
|
uninstallHooks: () => uninstallHooks
|
|
16438
16655
|
});
|
|
16439
16656
|
import * as fs14 from "fs/promises";
|
|
@@ -16446,6 +16663,22 @@ function resolveHookCommand() {
|
|
|
16446
16663
|
}
|
|
16447
16664
|
return "memorix";
|
|
16448
16665
|
}
|
|
16666
|
+
function resolveWindowsMemorixShim() {
|
|
16667
|
+
try {
|
|
16668
|
+
const output = String(execSync3("where.exe memorix.cmd", {
|
|
16669
|
+
encoding: "utf-8",
|
|
16670
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
16671
|
+
windowsHide: true
|
|
16672
|
+
}));
|
|
16673
|
+
return output.split(/\r?\n/).map((candidate) => candidate.trim()).find((candidate) => path19.isAbsolute(candidate)) ?? null;
|
|
16674
|
+
} catch {
|
|
16675
|
+
return null;
|
|
16676
|
+
}
|
|
16677
|
+
}
|
|
16678
|
+
function resolveOpenCodeHookCommand(platform = process.platform, resolveWindowsShim = resolveWindowsMemorixShim) {
|
|
16679
|
+
if (platform !== "win32") return "memorix";
|
|
16680
|
+
return resolveWindowsShim() ?? "memorix.cmd";
|
|
16681
|
+
}
|
|
16449
16682
|
function generateClaudeConfig() {
|
|
16450
16683
|
const cmd = `${resolveHookCommand()} hook`;
|
|
16451
16684
|
const hookEntry = {
|
|
@@ -16645,6 +16878,7 @@ async function installOfficialSkillsForAgent(agent, projectRoot, global = false)
|
|
|
16645
16878
|
return skillPaths;
|
|
16646
16879
|
}
|
|
16647
16880
|
function generateOpenCodePlugin() {
|
|
16881
|
+
const hookCommand = JSON.stringify(resolveOpenCodeHookCommand());
|
|
16648
16882
|
return `/**
|
|
16649
16883
|
* Memorix - Cross-Agent Memory Bridge Plugin for OpenCode
|
|
16650
16884
|
* @generated-version ${OPENCODE_PLUGIN_VERSION}
|
|
@@ -16663,6 +16897,16 @@ export const MemorixPlugin = async ({ project, client, $, directory, worktree })
|
|
|
16663
16897
|
const sessionId = \`opencode-\${Date.now().toString(36)}-\${Math.random().toString(36).slice(2, 8)}\`;
|
|
16664
16898
|
let pendingAssistantResponse = null;
|
|
16665
16899
|
let lastDeliveredAssistantKey = '';
|
|
16900
|
+
let hookFailureReported = false;
|
|
16901
|
+
const hookCommand = ${hookCommand};
|
|
16902
|
+
|
|
16903
|
+
function reportHookFailure(eventName, detail) {
|
|
16904
|
+
// OpenCode renders console.error output inside the conversation. Delivery is
|
|
16905
|
+
// best-effort, so keep normal sessions quiet while retaining opt-in diagnostics.
|
|
16906
|
+
if (process.env.MEMORIX_HOOK_DEBUG !== '1' || hookFailureReported) return;
|
|
16907
|
+
hookFailureReported = true;
|
|
16908
|
+
console.error('[memorix-plugin] hook delivery failed:', eventName, detail);
|
|
16909
|
+
}
|
|
16666
16910
|
|
|
16667
16911
|
/**
|
|
16668
16912
|
* Send event JSON to \`memorix hook\` via child_process.spawnSync.
|
|
@@ -16678,8 +16922,7 @@ export const MemorixPlugin = async ({ project, client, $, directory, worktree })
|
|
|
16678
16922
|
const data = JSON.stringify(payload);
|
|
16679
16923
|
const eventName = payload.hook_event_name || 'unknown';
|
|
16680
16924
|
try {
|
|
16681
|
-
const
|
|
16682
|
-
const result = spawnSync(cmd, ['hook'], {
|
|
16925
|
+
const result = spawnSync(hookCommand, ['hook'], {
|
|
16683
16926
|
input: data,
|
|
16684
16927
|
timeout: 10_000,
|
|
16685
16928
|
encoding: 'utf-8',
|
|
@@ -16687,12 +16930,14 @@ export const MemorixPlugin = async ({ project, client, $, directory, worktree })
|
|
|
16687
16930
|
shell: process.platform === 'win32',
|
|
16688
16931
|
});
|
|
16689
16932
|
if (result.status !== 0) {
|
|
16690
|
-
|
|
16691
|
-
|
|
16692
|
-
|
|
16933
|
+
reportHookFailure(eventName, {
|
|
16934
|
+
exit: result.status,
|
|
16935
|
+
stderr: (result.stderr || '').slice(0, 200),
|
|
16936
|
+
error: result.error?.message,
|
|
16937
|
+
});
|
|
16693
16938
|
}
|
|
16694
16939
|
} catch (e) {
|
|
16695
|
-
|
|
16940
|
+
reportHookFailure(eventName, e?.message ?? e);
|
|
16696
16941
|
}
|
|
16697
16942
|
}
|
|
16698
16943
|
|
|
@@ -17519,7 +17764,7 @@ var init_installers = __esm({
|
|
|
17519
17764
|
"use strict";
|
|
17520
17765
|
init_esm_shims();
|
|
17521
17766
|
init_official_skills();
|
|
17522
|
-
OPENCODE_PLUGIN_VERSION =
|
|
17767
|
+
OPENCODE_PLUGIN_VERSION = 7;
|
|
17523
17768
|
AGENT_SKILL_DIRS = {
|
|
17524
17769
|
cursor: { project: path19.join(".cursor", "skills"), global: path19.join(".cursor", "skills") },
|
|
17525
17770
|
windsurf: { project: path19.join(".windsurf", "skills"), global: path19.join(".windsurf", "skills") },
|
|
@@ -21554,7 +21799,7 @@ The path should point to a directory containing a .git folder.`
|
|
|
21554
21799
|
};
|
|
21555
21800
|
const server = existingServer ?? new McpServer({
|
|
21556
21801
|
name: "memorix",
|
|
21557
|
-
version: true ? "1.1.
|
|
21802
|
+
version: true ? "1.1.13" : "1.0.1"
|
|
21558
21803
|
});
|
|
21559
21804
|
const originalRegisterTool = server.registerTool.bind(server);
|
|
21560
21805
|
server.registerTool = ((name, ...args) => {
|