@velvetmonkey/flywheel-memory 2.0.60 → 2.0.62
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/dist/index.js +55 -7
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1816,7 +1816,23 @@ init_levenshtein();
|
|
|
1816
1816
|
import * as crypto from "crypto";
|
|
1817
1817
|
import * as fs3 from "fs";
|
|
1818
1818
|
import * as path2 from "path";
|
|
1819
|
-
var
|
|
1819
|
+
var MODEL_REGISTRY = {
|
|
1820
|
+
"Xenova/all-MiniLM-L6-v2": { id: "Xenova/all-MiniLM-L6-v2", dims: 384 },
|
|
1821
|
+
"Xenova/bge-small-en-v1.5": { id: "Xenova/bge-small-en-v1.5", dims: 384 },
|
|
1822
|
+
"Xenova/all-MiniLM-L12-v2": { id: "Xenova/all-MiniLM-L12-v2", dims: 384 },
|
|
1823
|
+
"nomic-ai/nomic-embed-text-v1": { id: "nomic-ai/nomic-embed-text-v1", dims: 768 }
|
|
1824
|
+
};
|
|
1825
|
+
var DEFAULT_MODEL = "Xenova/all-MiniLM-L6-v2";
|
|
1826
|
+
function getModelConfig() {
|
|
1827
|
+
const envModel = process.env.EMBEDDING_MODEL?.trim();
|
|
1828
|
+
if (!envModel) return MODEL_REGISTRY[DEFAULT_MODEL];
|
|
1829
|
+
if (MODEL_REGISTRY[envModel]) return MODEL_REGISTRY[envModel];
|
|
1830
|
+
return { id: envModel, dims: 0 };
|
|
1831
|
+
}
|
|
1832
|
+
var activeModelConfig = getModelConfig();
|
|
1833
|
+
function getActiveModelId() {
|
|
1834
|
+
return activeModelConfig.id;
|
|
1835
|
+
}
|
|
1820
1836
|
var EXCLUDED_DIRS2 = /* @__PURE__ */ new Set([
|
|
1821
1837
|
".obsidian",
|
|
1822
1838
|
".trash",
|
|
@@ -1852,9 +1868,14 @@ async function initEmbeddings() {
|
|
|
1852
1868
|
initPromise = (async () => {
|
|
1853
1869
|
try {
|
|
1854
1870
|
const transformers = await Function("specifier", "return import(specifier)")("@huggingface/transformers");
|
|
1855
|
-
pipeline = await transformers.pipeline("feature-extraction",
|
|
1871
|
+
pipeline = await transformers.pipeline("feature-extraction", activeModelConfig.id, {
|
|
1856
1872
|
dtype: "fp32"
|
|
1857
1873
|
});
|
|
1874
|
+
if (activeModelConfig.dims === 0) {
|
|
1875
|
+
const probe = await pipeline("test", { pooling: "mean", normalize: true });
|
|
1876
|
+
activeModelConfig.dims = probe.data.length;
|
|
1877
|
+
console.error(`[Semantic] Probed model ${activeModelConfig.id}: ${activeModelConfig.dims} dims`);
|
|
1878
|
+
}
|
|
1858
1879
|
} catch (err) {
|
|
1859
1880
|
initPromise = null;
|
|
1860
1881
|
if (err instanceof Error && (err.message.includes("Cannot find package") || err.message.includes("MODULE_NOT_FOUND") || err.message.includes("Cannot find module") || err.message.includes("ERR_MODULE_NOT_FOUND"))) {
|
|
@@ -1927,7 +1948,7 @@ async function buildEmbeddingsIndex(vaultPath2, onProgress) {
|
|
|
1927
1948
|
}
|
|
1928
1949
|
const embedding = await embedText(content);
|
|
1929
1950
|
const buf = Buffer.from(embedding.buffer, embedding.byteOffset, embedding.byteLength);
|
|
1930
|
-
upsert.run(file.path, buf, hash,
|
|
1951
|
+
upsert.run(file.path, buf, hash, activeModelConfig.id, Date.now());
|
|
1931
1952
|
} catch {
|
|
1932
1953
|
progress.skipped++;
|
|
1933
1954
|
}
|
|
@@ -1956,7 +1977,7 @@ async function updateEmbedding(notePath, absolutePath) {
|
|
|
1956
1977
|
db.prepare(`
|
|
1957
1978
|
INSERT OR REPLACE INTO note_embeddings (path, embedding, content_hash, model, updated_at)
|
|
1958
1979
|
VALUES (?, ?, ?, ?, ?)
|
|
1959
|
-
`).run(notePath, buf, hash,
|
|
1980
|
+
`).run(notePath, buf, hash, activeModelConfig.id, Date.now());
|
|
1960
1981
|
} catch {
|
|
1961
1982
|
}
|
|
1962
1983
|
}
|
|
@@ -2058,6 +2079,20 @@ function hasEmbeddingsIndex() {
|
|
|
2058
2079
|
return false;
|
|
2059
2080
|
}
|
|
2060
2081
|
}
|
|
2082
|
+
function getStoredEmbeddingModel() {
|
|
2083
|
+
if (!db) return null;
|
|
2084
|
+
try {
|
|
2085
|
+
const row = db.prepare("SELECT model FROM note_embeddings LIMIT 1").get();
|
|
2086
|
+
return row?.model ?? null;
|
|
2087
|
+
} catch {
|
|
2088
|
+
return null;
|
|
2089
|
+
}
|
|
2090
|
+
}
|
|
2091
|
+
function needsEmbeddingRebuild() {
|
|
2092
|
+
const stored = getStoredEmbeddingModel();
|
|
2093
|
+
if (stored === null) return false;
|
|
2094
|
+
return stored !== activeModelConfig.id;
|
|
2095
|
+
}
|
|
2061
2096
|
function getEmbeddingsCount() {
|
|
2062
2097
|
if (!db) return 0;
|
|
2063
2098
|
try {
|
|
@@ -2129,7 +2164,7 @@ async function buildEntityEmbeddingsIndex(vaultPath2, entities, onProgress) {
|
|
|
2129
2164
|
}
|
|
2130
2165
|
const embedding = await embedTextCached(text);
|
|
2131
2166
|
const buf = Buffer.from(embedding.buffer, embedding.byteOffset, embedding.byteLength);
|
|
2132
|
-
upsert.run(name, buf, hash,
|
|
2167
|
+
upsert.run(name, buf, hash, activeModelConfig.id, Date.now());
|
|
2133
2168
|
updated++;
|
|
2134
2169
|
} catch {
|
|
2135
2170
|
}
|
|
@@ -2156,7 +2191,7 @@ async function updateEntityEmbedding(entityName, entity, vaultPath2) {
|
|
|
2156
2191
|
db.prepare(`
|
|
2157
2192
|
INSERT OR REPLACE INTO entity_embeddings (entity_name, embedding, source_hash, model, updated_at)
|
|
2158
2193
|
VALUES (?, ?, ?, ?, ?)
|
|
2159
|
-
`).run(entityName, buf, hash,
|
|
2194
|
+
`).run(entityName, buf, hash, activeModelConfig.id, Date.now());
|
|
2160
2195
|
entityEmbeddingsMap.set(entityName, embedding);
|
|
2161
2196
|
} catch {
|
|
2162
2197
|
}
|
|
@@ -9112,6 +9147,7 @@ function registerHealthTools(server2, getIndex, getVaultPath, getConfig = () =>
|
|
|
9112
9147
|
embeddings_building: z3.boolean().describe("Whether semantic embeddings are currently building"),
|
|
9113
9148
|
embeddings_ready: z3.boolean().describe("Whether semantic embeddings have been built (enables hybrid keyword+semantic search)"),
|
|
9114
9149
|
embeddings_count: z3.coerce.number().describe("Number of notes with semantic embeddings"),
|
|
9150
|
+
embedding_model: z3.string().optional().describe("Active embedding model ID (when embeddings are built)"),
|
|
9115
9151
|
tasks_ready: z3.boolean().describe("Whether the task cache is ready to serve queries"),
|
|
9116
9152
|
tasks_building: z3.boolean().describe("Whether the task cache is currently rebuilding"),
|
|
9117
9153
|
watcher_state: z3.enum(["starting", "ready", "rebuilding", "dirty", "error"]).optional().describe("Current file watcher state"),
|
|
@@ -9297,6 +9333,7 @@ function registerHealthTools(server2, getIndex, getVaultPath, getConfig = () =>
|
|
|
9297
9333
|
embeddings_building: isEmbeddingsBuilding(),
|
|
9298
9334
|
embeddings_ready: hasEmbeddingsIndex(),
|
|
9299
9335
|
embeddings_count: getEmbeddingsCount(),
|
|
9336
|
+
embedding_model: hasEmbeddingsIndex() ? getActiveModelId() : void 0,
|
|
9300
9337
|
tasks_ready: isTaskCacheReady(),
|
|
9301
9338
|
tasks_building: isTaskCacheBuilding(),
|
|
9302
9339
|
watcher_state: getWatcherStatus2()?.state,
|
|
@@ -19547,7 +19584,18 @@ async function runPostIndexWork(index) {
|
|
|
19547
19584
|
serverLog("config", `Vault: ${flywheelConfig.vault_name}`);
|
|
19548
19585
|
}
|
|
19549
19586
|
if (process.env.FLYWHEEL_SKIP_EMBEDDINGS !== "true") {
|
|
19550
|
-
|
|
19587
|
+
let modelChanged = false;
|
|
19588
|
+
if (hasEmbeddingsIndex() && needsEmbeddingRebuild()) {
|
|
19589
|
+
const oldModel = getStoredEmbeddingModel();
|
|
19590
|
+
serverLog("semantic", `Embedding model changed from ${oldModel} to ${getActiveModelId()}, rebuilding`);
|
|
19591
|
+
if (stateDb) {
|
|
19592
|
+
stateDb.db.exec("DELETE FROM note_embeddings");
|
|
19593
|
+
stateDb.db.exec("DELETE FROM entity_embeddings");
|
|
19594
|
+
}
|
|
19595
|
+
setEmbeddingsBuildState("none");
|
|
19596
|
+
modelChanged = true;
|
|
19597
|
+
}
|
|
19598
|
+
if (hasEmbeddingsIndex() && !modelChanged) {
|
|
19551
19599
|
serverLog("semantic", "Embeddings already built, skipping full scan");
|
|
19552
19600
|
} else {
|
|
19553
19601
|
setEmbeddingsBuilding(true);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@velvetmonkey/flywheel-memory",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.62",
|
|
4
4
|
"description": "MCP server that gives Claude full read/write access to your Obsidian vault. Select from 42 tools for search, backlinks, graph queries, mutations, and hybrid semantic search.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
},
|
|
53
53
|
"dependencies": {
|
|
54
54
|
"@modelcontextprotocol/sdk": "^1.25.1",
|
|
55
|
-
"@velvetmonkey/vault-core": "^2.0.
|
|
55
|
+
"@velvetmonkey/vault-core": "^2.0.62",
|
|
56
56
|
"better-sqlite3": "^11.0.0",
|
|
57
57
|
"chokidar": "^4.0.0",
|
|
58
58
|
"gray-matter": "^4.0.3",
|