@velvetmonkey/flywheel-memory 2.0.61 → 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.
Files changed (2) hide show
  1. package/dist/index.js +55 -7
  2. 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 MODEL_ID = "Xenova/all-MiniLM-L6-v2";
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", MODEL_ID, {
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, MODEL_ID, Date.now());
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, MODEL_ID, Date.now());
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, MODEL_ID, Date.now());
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, MODEL_ID, Date.now());
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
- if (hasEmbeddingsIndex()) {
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.61",
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.60",
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",